当前位置: 首页 > news >正文

CubeMX的FreeRTOS学习

一、FreeRTOS的介绍

什么是FreeRTOS?

Free即免费的,RTOS的全称是Real Time Operating system,中文就是实时操作系统。

注意:RTOS不是指某一个确定的系统,而是指一类的操作系统。比如:us/OS,FreeRTOS,RTX,RT-Thread等这些都是RTOS类操作系统。

FreeRTOS是一个迷你的实时操作系统内核。作为一个轻量级的操作系统,功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功能、软件定时器、协程等,可基本满足较小系统的需要。

由于RTOS需占用一定的系统资源(尤其是RAM资源),只有μC/OS-II、embOS、salvo、FreeRTOS等少数实时操作系统能在小RAM单片机上运行。相对μC/OS-II、embOS等商业操作系统,FreeRTOS操作系统是完全免费的操作系统,具有源码公开、可移植、可裁减、调度策略灵活的特点,可以方便地移植到各种单片机上运行,其最新版本为10.4.4版。

                                                                                                                ------------来自百度                                                                                                              

为什么选择FreeRTOS?

>FreeRTOS是免费的

>很多半导体厂商产品的SDK(Software  Development Kit)软件开发工具包,就使用FreeRTOS作为其操作系统,尤其是WiFi、蓝牙这些带有协议线的芯片或模块。

>简单,因为FreeRTOS的文件数量很少

FreeRTOS资料与源码下载

FreeRTOS™ - FreeRTOS™官网链接

裸机开发与FreeRTOS

上官课程里的简易举例

二、移植FreeRTOS到上官二号平台

手动移植

过程复杂且繁琐,对新手(像我这种)不友好,如需手动移植,参考以下文章

FreeRTOS移植到STM32-CSDN博客链接

使用CubeMX快速移植

快速移植

1.在SYS选项里,将Debug设为Serial  Wire,并且将Timebase Source设为TIM2(其他定时器也行)。

2.将RCC里的HSE设置为Crystal/Ceramic Resonator。

3.时钟树配置

4.选择FREERTOS选项,并将interface 改为CMSIS_V1。

5.配置项目名称,导出项目即可

一些常见问题

1.Timebase Source 为什么不能设置SysTick?

裸机时钟默认SysTick,但是开启FreeRTOS后,FreeRTOS会占用SysTick(用来生成1ms的定时,用于任务调度),所以需要为其他总线提供另外的时钟源。

2.FreeRTOS的版本问题

V2的内核版本更高,功能更多,在大多数情况下V1版本的内核完全够用。

3.FreeRTOS各项配置选项卡的解释

4.内核配置、函数使能的一些翻译

参考博客FreeRTOS系列第6篇---FreeRTOS内核配置说明_vassertcalled-CSDN博客

三、任务的创建与删除

1.什么是任务?

任务可以理解为进程/线程,创建一个任务,就会在内存开辟一个空间。

比如:玩游戏、陪女朋友,都可以视为任务

Windows系统中的MarkText、谷歌浏览器、记事本都是任务。

任务通常都有while(1)死循环。

2.任务创建与删除相关函数

任务创建与删除相关函数有如下三个:

任务动态创建与静态创建的区别:

动态创建任务的堆栈由系统分配,而静态创建任务的堆栈由用户自己传递。

通常情况下使用动态方式创建任务。

xTaskCreate 函数原型

1.pvTaskCode:指向任务函数的指针,任务必须实现为永不返回(即连续循环);

2.pcName:任务的名字,主要是用来测试,默认情况下最大长度是16;

3.pvParameters:指定的任务栈的大小;

4.uxPriority:任务优先级,数值越大,优先级越大;

5.pxCreateTask:用于返回已创建任务的句柄可以被引用。

官方案例:

vTaskDelete函数原型

只需将待删除的任务句柄传入该函数,即可将该任务删除。

当传入的参数为NULL,则代表删除任务自身(当前正在运行的任务)。

3.实操

四、任务的状态

什么是任务调度?

调度器就是使用相关的调度算法来决定当前需要执行的哪个任务。

FreeRTOS中开启任务调度的函数是vTaskStartScheduler(),但在CubeMX中被封装为osKernelStart()。

FreeRTOS的任务调度规则是怎样的?

FreeRTOS是一个实时操作系统,它所奉行的调度规则:

1.高优先级抢占低优先级任务,系统永远执行最高优先级的任务(即抢占式调度);

2.同等优先级的任务轮转调度(即时间片调度);

还有一种调度规则是协程式调度,但官方已明确表示不更新,主要是用在小容量的芯片上,用得也不多。

抢占式调度运行过程

eg:Task1:玩游戏         Task2:老妈喊你吃饭        Task3:女朋友喊你看电视

总结:

1.高优先级任务,优先执行;

2.高优先级任务不停止,低优先级任务无法执行;

3.被抢占的任务将会进入就绪态。

时间片调度运行过程

总结:

1.同等优先级任务,轮流执行,时间片流转;

2.一个时间片大小,取决为滴答定时器中断周期;

3.注意:没有用完的时间片不会再使用,下次任务Task3得到执行,还是按照一个时间片的时钟节拍运行

五、任务的状态

FreeRTOS中任务共存在4种状态

 >Running 运行态

当任务处于实际运行状态称之为运行态,即CPU的使用权被这个任务占用(同一时间仅一个任务牌运行态)。

 >Ready 准备态

处于就绪态的任务是指那些能够运行(没有被阻塞和挂起),但是当前没有运行的任务,因为同优先级或更高优先级的任务正在运行。

 >Blocked 阻塞态

如果一个任务因延时,或等待信号量、消息队列、事件标志组等而处于的状态被称之为阻塞态。

 >Suspended 挂起态

类似暂停,通过调用函数vTaskSuspend()对指定任务进行挂起,挂起后这个任务将不被执行,只有调用函数xTaskResume()才可以将这个任务从挂起状态恢复。

                          

总结:

1.仅就绪态可转变成运行态;

2.其他状态的任务想运行,必须先转变成就绪态。

任务综合小实验

实验需求

创建4个任务:taskLED,taskBEEP,taskKEY1,taskKEY2,任务要求如下:

taskLED:间隔100ms闪烁LED;

taskBEEP:间隔1000ms翻转BEEP;

taskKEY1:如果taskLED存在,则按下KEY1后删除taskLED,否则创建taskLED;

taskKEY2:如果taskBEEP正常运行,则按下KEY2后挂起taskBEEP,否则恢复taskBEEP。

CubeMX配置

代码实现

/* USER CODE END Header_StartTaskLED */
void StartTaskLED(void const * argument)
{/* USER CODE BEGIN StartTaskLED *//* Infinite loop */for(;;){HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);osDelay(100);}/* USER CODE END StartTaskLED */
}/* USER CODE BEGIN Header_StartTaskBeep */
/**
* @brief Function implementing the TaskBeep thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskBeep */
void StartTaskBeep(void const * argument)
{/* USER CODE BEGIN StartTaskBeep *//* Infinite loop */for(;;){HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);osDelay(1000);}/* USER CODE END StartTaskBeep */
}/* USER CODE BEGIN Header_StartTaskKEY1 */
/**
* @brief Function implementing the TaskKEY1 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskKEY1 */
void StartTaskKEY1(void const * argument)
{/* USER CODE BEGIN StartTaskKEY1 *//* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){printf("KEY1按下!\r\n");if(TaskLEDHandle == NULL){printf("任务1不存在,准备创建任务!\r\n");osThreadDef(TaskLED, StartTaskLED, osPriorityNormal, 0, 128);TaskLEDHandle = osThreadCreate(osThread(TaskLED), NULL);if(TaskLEDHandle != NULL)printf("任务1创建完成!\r\n");}else{printf("删除任务1\r\n");osThreadTerminate(TaskLEDHandle);TaskLEDHandle = NULL;}}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);}osDelay(10);}/* USER CODE END StartTaskKEY1 */
}/* USER CODE BEGIN Header_StartTaskKEY2 */
/**
* @brief Function implementing the TaskKEY2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskKEY2 */
void StartTaskKEY2(void const * argument)
{/* USER CODE BEGIN StartTaskKEY2 */static int flag = 0;/* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){printf("KEY2按下!\r\n");if(flag == 0){osThreadSuspend(TaskBeepHandle);printf("任务2已暂停\r\n");flag = 1;}else{osThreadResume(TaskBeepHandle);printf("任务2已恢复\r\n");flag = 0;}}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);}osDelay(10);}/* USER CODE END StartTaskKEY2 */
}

六、队列

什么是队列

队列又称消息队列,是一种常用于任务间通信的数据结构,队列可以在任务与任务间、中断和任务间传递信息。

为什么不使用全局变量?

如果使用全局变量,兔子(任务1)修改了变量a,等待树懒(任务3)处理,但树懒处理速度很慢,在处理数据的过程中,狐狸(任务2)有可能又修改了变量a,导致树懒有可能得到的不是正确的数据。

在这种情况下,就可以使用队列。兔子和狐狸产生的数据放在流水线上,树懒可以慢慢一个一个依次处理。关于队列的几个名词:

队列项目:队列中的每一个数据;

队列长度:队列能够存储队列项目的最大数量;

创建队列时,需要指定队列长度及队列项目大小。

队列特点

1.数据入队出队方式

通常采用先进先出(FIFO)的数据存储缓冲机制,即陷入队的数据会先从队列中被读取。

也可以配置为后进先出(LIFO)方式,但用得比较少。

2.数据传递方式

采用实际值传递,即将数据拷贝到队列中进行传递,也可以传递指针,在传递较大的数据的时候采用指针传递。

3.多任务访问

队列不属于某个任务,任何任务和中断都可以向队列发送/读取消息。

4.出队、入队阻塞

当任务向一个队列发送消息时,可以指定一个阻塞时间,假设此时当队列已满无法入队。

阻塞时间如果设置为:

>0:直接返回不会等待;

>0~port_MAX_DELAY:等待设定的阻塞时间,若在该时间内还无法入队,超时后直接返回不再等待;

>port_MAX_DELAY:死等,一直等到可以入队为止,出队阻塞与入队阻塞类似;

队列相关的API函数

1.创建队列

参数:

>xQueuelength:队列可同时容纳的最大数列;

>uxItemSize:存储队列中的每个数据项所需的大小(以字节为单位)。

返回值:

如果队列创建成功,则返回所创建队列的句柄;如果创建队列所需的内存无法分配,则返回NULL。

2.写队列

写队列总共有以下几个函数:

参数:

>xQueue:队列的句柄,数据项将发送到此队列;

>pvItemToQueue:待写入数据;

>xTicksToWait:阻塞超时时间。

返回值:

如果成功写入数据,返回pdTURE,否则返回errQUEUE_FULL。

3.读队列

读队列总共有以下几个函数:

参数:

>xQueue:待读取的队列;

>pvItemToQueue:数据读取缓冲区;

>xTicksToWait:阻塞超时时间。

实操

实验需求

创建一个队列,按下KEY1向队列发送数据,按下KEY2向队列读取数据

CubeMX配置

代码实现

#include <stdio.h>
/* USER CODE END Header_StartTaskSend */
void StartTaskSend(void const * argument)
{/* USER CODE BEGIN StartTaskSend */uint16_t buf = 100;BaseType_t status;/* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){status = xQueueSend(myQueue01Handle,&buf,0);if(status == pdTRUE)printf("写入队列成功,写入值%d\r\n",buf);elseprintf("写入队列失败\r\n");			}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);}osDelay(10);}/* USER CODE END StartTaskSend */
}/* USER CODE BEGIN Header_StartTaskReceive */
/**
* @brief Function implementing the TaskReceive thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskReceive */
void StartTaskReceive(void const * argument)
{/* USER CODE BEGIN StartTaskReceive */uint16_t buf;BaseType_t status;/* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){status = xQueueReceive(myQueue01Handle,&buf,0);if(status == pdTRUE)printf("读取队列成功,读取值%d\r\n",buf);elseprintf("读取队列失败\r\n");			}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);}osDelay(10);}/* USER CODE END StartTaskReceive */
}

七、二值信号量

什么是信号量

信号量(Semaphore),是在多任务环境下使用的一种机制,是可以用来保证两个或多个关键代码不被并发调用。

信号量这个名字,我们可以把它拆分来看,信号可以起到通知信号的作用,然后我们的量还可以用来表示资源的数量,当我们的量只有0和1的时候,它就可以被称作二值信号量,只有两个状态,当我们的那个量没有限制的时候,它就可以被称作为计数型信号量。

信号量也是队列的一种。

什么是二值信号量?

二值信号量其实就是一个长度为1,大小为零的队列,只有和1两种状态,通常情况下,我们用它来进行互斥访问或任务同步。

互斥访问:比如门钥匙,只有获取到钥匙才可以开门。

任务同步:比如我录完视频你才可以看视频。

 二值信号量相关API函数

1.创建二值信号量

参数:

返回值:

成功,返回对应二值信号量的句柄;

失败,返回NULL。

2.释放二值信号量

参数:

xSemaphore:要释放的信号量句柄。

返回值:

成功,返回pdPASS;

失败,返回errQUEUE_FULL。

3.获取二值信号量

参数:

xSemaphore:要获取的信号量句柄;

xTicksToWait:超时时间,0表示不超时,portMAX_DELAY表示卡死等待。

返回值:

成功,返回pdPASS;

失败,返回errQUEUE_FULL。

实操

实验需求

创建一个二值信号量,按下KEY1则释放信号量,按下KEY2获取信号量。

CubeMX配置

代码实现

#include<stdio.h>
myBinarySem01Handle = xSemaphoreCreateBinary();//自己创建的句柄
void StartTaskGive(void const * argument)
{/* USER CODE BEGIN StartTaskGive *//* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){if(xSemaphoreGive(myBinarySem01Handle) == pdTRUE)printf("二值信号量放入成功\r\n");elseprintf("二值信号量放入失败\r\n");}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);}osDelay(10);}/* USER CODE END StartTaskGive */
}/* USER CODE BEGIN Header_StartTaskTake */
/**
* @brief Function implementing the TaskTake thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskTake */
void StartTaskTake(void const * argument)
{/* USER CODE BEGIN StartTaskTake *//* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){if(xSemaphoreTake(myBinarySem01Handle,portMAX_DELAY) == pdTRUE)printf("二值信号量获取成功\r\n");elseprintf("二值信号量获取失败\r\n");}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);}osDelay(10);}/* USER CODE END StartTaskTake */
}

八、计数型信号量

什么是计数型信号量?

计数型信号量相当于队列长度大于1的队列,因此计数型信号量能够容纳多个资源,这在计数型信号量被创建的时候确定的。

计数型信号量相关API函数

计数型信号量的释放和获取与二值信号量完全相同

参数:

uxMaxCount:可以达到的最大计数值;

uxInitialCount:创建信号量时分配给信号量的计数值。

返回值:

成功,返回对应计数型信号量的句柄;

失败,返回NULL;

实操

实验需求

创建一个计数型信号量,按下KEY1则释放信号量,按下KEY2获取信号量。

CubeMX配置

代码实现

#include <stdio.h>
myCountingSem01Handle = xSemaphoreCreateCounting(3,0);//自己写的
void StartTaskGive(void const * argument)
{/* USER CODE BEGIN StartTaskGive *//* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){if(xSemaphoreGive(myCountingSem01Handle) == pdTRUE)printf("二值信号量放入成功\r\n");elseprintf("二值信号量放入失败\r\n");}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);}osDelay(10);}/* USER CODE END StartTaskGive */
}/* USER CODE BEGIN Header_StartTaskTake */
/**
* @brief Function implementing the TaskTake thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskTake */
void StartTaskTake(void const * argument)
{/* USER CODE BEGIN StartTaskTake *//* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){if(xSemaphoreTake(myCountingSem01Handle,0) == pdTRUE)printf("二值信号量获取成功\r\n");elseprintf("二值信号量获取失败\r\n");}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);}osDelay(10);}/* USER CODE END StartTaskTake */
}

九、 互斥量

什么是互斥量?

在多数情况下,互斥型信号量和二值型信号非常相似,但是从功能上二值型信号量用于同步,而互斥型信号量用于资源保护。

互斥型信号量和二值型信号量还有一个最大的区别,互斥型信号量可以有效解决优先级反转现象。

什么是优先级反转?

以上图为例,系统中有3个不同优先级的任务H/M/L,最高优先级任务H和最低优先级任务L通过信号量机制,共享资源,目前任务L占有资源,锁定了信号量,TaskH运行后将被阻塞,直到TaskL释放信号量后,TaskH才能够退出阻塞状态继续运行。但是TaskH在等待TaskL释放信号量的过程中,中等优先级任务M抢占了任务L,从而延迟了信号量的释放时间,导致TaskH阻塞了更长的时间,这种现象称为优先级倒置或反转。

优先级继承:当一个互斥信号量正在被一个低优先级的任务持有时,如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级。

优先级继承并不能完全的消除优先级反转的问题,它只是尽可能的降低优先级反转带来的影响。

互斥量相关API函数

互斥信号量不能用于中断服务函数中!

参数:

返回值:

成功,返回对应互斥量的句柄;

失败,返回NULL。

实操

实验需求

1.演示优先级反转

2.使用互斥量优化优先级反转问题

CubeMX配置

优先级反转演示配置

现象

互斥量优化优先级反转配置

现象

代码实现

优先级反转与互斥量优化的区别在于句柄不同

#include <stdio.h>
void StartTaskH(void const * argument)
{/* USER CODE BEGIN StartTaskH *//* Infinite loop */for(;;){xSemaphoreTake(myMutex01Handle,portMAX_DELAY);printf("TaskH:我获取到信号量正在运行中。\r\n");HAL_Delay(1000);printf("TaskH:我运行完成了。\r\n");xSemaphoreGive(myMutex01Handle);osDelay(1000);}/* USER CODE END StartTaskH */
}/* USER CODE BEGIN Header_StartTaskM */
/**
* @brief Function implementing the TaskM thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskM */
void StartTaskM(void const * argument)
{/* USER CODE BEGIN StartTaskM *//* Infinite loop */for(;;){printf("TaskM:我就是为了占用CPU资源。。\r\n");osDelay(1000);}/* USER CODE END StartTaskM */
}/* USER CODE BEGIN Header_StartTaskL */
/**
* @brief Function implementing the TaskL thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskL */
void StartTaskL(void const * argument)
{/* USER CODE BEGIN StartTaskL *//* Infinite loop */for(;;){xSemaphoreTake(myMutex01Handle,portMAX_DELAY);printf("TaskL:我获取到信号量正在运行中。\r\n");HAL_Delay(3000);printf("TaskL:我运行完成了。\r\n");xSemaphoreGive(myMutex01Handle);osDelay(1000);}/* USER CODE END StartTaskL */
}

十、事件标志组

什么是事件标志组?

事件标志位:表明某个事件发生,联想:全局变量flag,通常按位表示,每一个位表示一个事件(高八位不算)。

事件标志组是一组事件标志位的集合,可以简单的理解事件标志组,就是一个整数。

事件标志组本质是一个16位或32位无符号的数据类型EventBits_t,由configUSE_16_BIT_TICKS决定。

虽然使用了32位无符号的数据类型变量来存储标志,但其中的高8位用作存储事件标志组的控制信息,低24位用作存储事件标志,所以说一个事件组最多可以存储24个事件标志!

事件标志组相关API函数

1.创建事件标志组

参数:

返回值:

成功,返回对应事件标志组的句柄;

失败,返回NULL。

2.设置事件标志位

参数:

xEventGroup:对应事件的句柄;

uxBitsToSet:指定要在事件组中设置的一个或多个位的按位值。

返回值:

设置之后事件组中的事件标志位值。

3.清除事件标志位

参数:

xEventGroup:对应事件的句柄;

uxBitsToClear:指定要在事件组中清除一个或多个的按位值。

返回值:

返回之前清除事件组中事件标志位的值。

4.等待事件标志位

参数:

xEventGroup:对应事件的句柄;

uxBitsToWaitFor:指定事件组中要等待的一个或多个事件位的按位值;

xClearOnExit:pdTRUE——清除对应事件位,pdFALSE——不清除;

xWaitForAllBits:pdTRUE——所有等待事件全为1(逻辑与),pdFALSE——等待的事件位有一个为1(逻辑或);

xTicksToWait:超时。

返回值:

等待的事件标志位值:等待事件标志位成功,返回等待到的事件标志位;

其他值:等待事件标志位失败,返回事件组中的事件标志位。

实操

实验需求

创建一个事件标志组和两个任务(Task01和Task02),Task01检测按键,如果检测到KEY1和KEY2都按过,则执行Task02。

CubeMX配置

代码实现

#include <stdio.h>
/* USER CODE BEGIN Variables */EventGroupHandle_t eventgroup_handle;
/* USER CODE END Variables *//* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */eventgroup_handle = xEventGroupCreate();
/* USER CODE END RTOS_THREADS */void StartTask01(void const * argument)
{/* USER CODE BEGIN StartTask01 *//* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){			xEventGroupSetBits(eventgroup_handle,0x01);}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);}if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){xEventGroupSetBits(eventgroup_handle,0x02);}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);}osDelay(1); }/* USER CODE END StartTask01 */
}/* USER CODE BEGIN Header_StartTask02 */
/**
* @brief Function implementing the Task02 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask02 */
void StartTask02(void const * argument)
{/* USER CODE BEGIN StartTask02 */EventBits_t event_bit = 0;/* Infinite loop */for(;;){event_bit = xEventGroupWaitBits(eventgroup_handle,0x01 | 0x02,pdTRUE,pdFALSE,portMAX_DELAY);printf("返回值:%#x,逻辑或成功\r\n",event_bit);//内容根据xWaitForAllBits参数来更改或/与osDelay(1);}/* USER CODE END StartTask02 */
}

十一、任务通知

什么是任务通知?

FreeRTOS从版本V8.2.0开始提供任务通知这个功能,每个任务都有一个32位的通知值。按照FreeRTOS官方的说法,使用消息通知比通过二进制信号量方式解除阻塞任务快45%,并且更加省内存(无需创建队列)。

在大多数情况下,任务通知可以代替二值信号量、计数信号量、信号标志组,可以代替长度为1的队列(可以保存一个32位整数或指针值),并且任务通知速度更快、使用的RAM更少!

任务通知值的更新方式

FreeRTOS提供一下几种方式发送通知给任务:

>发送消息给任务,如果有通知未读,不覆盖通知值;

>发送消息给任务,直接覆盖通知值;

>发送消息给任务,设置通知值的一个或者多个位;

>发送消息给任务,递增通知值。

通过对以上方式的合理使用,可以在一定场合下代替原本的队列、信号量、事件标志组等。

任务通知的优势和劣势

任务通知的优势

1.使用任务通知向任务发送事件或数据,比使用队列、事件标志组或信号量快得多。

2.使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体。

任务通知的劣势

1.只有任务可以等待通知,中断服务函数中不可以,因为中断没有TCB。

2.通知只能一对一,因为通知必须指定任务。

3.等待通知的任务可以被阻塞,但是发送消息的任务,任何情况下都不会被阻塞等待。

4.任务通知是通过更新任务通知值来发送数据的,任务结构体中只有一个任务通知值,只能保持一个数据。

任务通知相关API函数

1.发送通知

参数:

xTaskToNotify:需要接收通知的任务句柄;

ulValue:用于更新接收任务通知值,具体如何更新由形参eAction决定;

eAction:一个枚举,代表如何使用任务通知的值。

返回值:

如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回pdFALSE,而其他情况均返回pdPASS。

参数:

xTaskToNotify:需要接收通知的任务句柄;

ulValue:用于更新接收任务通知值,具体如何更新由形参eAction决定;

eAction:一个枚举,代表如何使用任务通知的值;

pulPreviousNotifyValue:对象任务的上一个任务通知值,如果为NULL,则不需要回传,这个时候就等价于函数xTaskNotify()。

返回值:

如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回pdFALSE,而其他情况均返回pdPASS。

参数:

xTaskNotify:接收通知的任务句柄,并让其自身的任务通知值加1。

返回值:

总是返回pdPASS。

2.等待通知

等待通知API通知只能用在任务,不可应用于中断中!

参数:

xClearCountOnExit:指定在成功接收通知后,将通知值清零或减1,pdTRUE:把通知值清零(二值信号量);pdFALSE:把通知值减1(计数型信号量);

xTicksToWait:阻塞等待任务通知值的最大时间。

返回值:

0:接收失败;

非0:接收成功,返回任务通知的通知值。

ulBitsToClearOnEntry:函数执行前清零任务通知值那些位;

ulBitsToClearOnExit:表示在函数退出前,清零任务通知值那些位,在清零前,接收到的任务通知值会先被保存到形参*pulNotificationValue中;

pulNotificationValue:用于保存接收到的任务通知值。如果不需要使用,则设置为NULL即可;

xTicksToWait:等待消息通知的最大等待时间。

实操

实验需求

1.模拟二值信号量

2.模拟计数型信号量

3.模拟事件标志组

4.模拟邮箱

CubeMX配置

代码实现

1.模拟二值信号量

#include <stdio.h>void StartTaskSend(void const * argument)
{/* USER CODE BEGIN StartTaskSend *//* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){xTaskNotifyGive(TaskReceiveHandle);printf("任务通知:模拟二值信号量发送成功!\r\n");}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);}osDelay(10);}/* USER CODE END StartTaskSend */
}/* USER CODE BEGIN Header_StartTaskReceive */
/**
* @brief Function implementing the TaskReceive thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskReceive */
void StartTaskReceive(void const * argument)
{/* USER CODE BEGIN StartTaskReceive */uint32_t rev = 0;/* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){rev = ulTaskNotifyTake(pdTRUE,portMAX_DELAY);if(rev != 0)printf("任务通知:模拟二值信号量接收成功!\r\n");}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);}osDelay(10);}/* USER CODE END StartTaskReceive */
}

 2.模拟计数型信号量

rev = ulTaskNotifyTake(pdFALSE,portMAX_DELAY);//区别于二值信号量的接收任务中参数变成pdFALSE

3. 模拟事件标志组

#include <stdio.h>
void StartTaskSend(void const * argument)
{/* USER CODE BEGIN StartTaskSend *//* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){				printf("将bit0位置1\r\n");xTaskNotify(TaskReceiveHandle,0x01,eSetBits);}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);}if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){printf("将bit1位置1\r\n");xTaskNotify(TaskReceiveHandle,0x02,eSetBits);				}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);}		osDelay(10);}/* USER CODE END StartTaskSend */
}/* USER CODE BEGIN Header_StartTaskReceive */
/**
* @brief Function implementing the TaskReceive thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskReceive */
void StartTaskReceive(void const * argument)
{/* USER CODE BEGIN StartTaskReceive */uint32_t notify_val = 0,event_bit = 0;/* Infinite loop */for(;;){xTaskNotifyWait(0,0xFFFFFFFF,&notify_val,portMAX_DELAY);if(notify_val & 0x01)event_bit |= 0x01;if(notify_val & 0x02)event_bit |= 0x02;if(event_bit == (0x01 | 0x02)){printf("任务通知模拟标志组成功!\r\n");event_bit = 0;}osDelay(1);}/* USER CODE END StartTaskReceive */
}

 4.模拟邮箱(大小为1的队列)

void StartTaskSend(void const * argument)
{/* USER CODE BEGIN StartTaskSend *//* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){                printf("按键1按下\r\n");xTaskNotify(TaskReceiveHandle,0x01,eSetValueWithOverwrite);}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);}if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){printf("按键2按下\r\n");xTaskNotify(TaskReceiveHandle,0x02,eSetValueWithOverwrite);                }while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);}        osDelay(10);}/* USER CODE END StartTaskSend */
}/* USER CODE BEGIN Header_StartTaskReceive */
/**
* @brief Function implementing the TaskReceive thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskReceive */
void StartTaskReceive(void const * argument)
{/* USER CODE BEGIN StartTaskReceive */uint32_t notify_val = 0;/* Infinite loop */for(;;){xTaskNotifyWait(0,0xFFFFFFFF,&notify_val,portMAX_DELAY);printf("接收到的通知值:%d\r\n",notify_val);osDelay(1);}/* USER CODE END StartTaskReceive */
}

十二、延时函数

什么是延时函数?

延时函数是一种暂停程序执行一段时间的函数,可以用来实现简单的延迟效果

延时函数分类

相对延时:vTaskDelay;

绝对延时:vTaskDelayUntil。

vTaskDelay与HAL_Delay的区别

vTaskDelay作用是让任务阻塞,任务阻塞后,RTOS系统调用其它处于就绪状态的优先级最高的任务来执行;

HAL_Delay一直不停的调用获取系统时间的函数,直到指定的时间流逝然后退出。故其占用了全部CPU时间。

十三、软件定时器

什么是定时器?

简单可以理解为闹钟,到达指定一段时间后,就会响铃。

STM32芯片自带硬件定时器,精度较高,达到定时时间后会触发中断,也可以生成PWM、输入捕获、输出比较等等,功能强大,但是由于硬件的限制。个数有限。

软件定时器也可以实现定时功能,达到定时时间后可调用回调函数,可以在回调函数里处理信息。

软件定时器优缺点

优点:

1.简单、成本低

2.只要内存足够,可创建多个。

缺点:

精度较低,容易受中断影响,在大多数情况下够用,但对于精度要求比较高的场合不建议使用。

软件定时器原理

定时器是一个可选的、不属于FreeRTOS内核的功能,它是由定时器服务任务来提供的。在调用函数vTaskStartScheduler()开启任务调度器的时候,会创建一个用于管理软件定时器的任务,这个任务就叫做软件定时器服务任务。

1.负责软件定时器超时的逻辑判断

2.调用超时软件定时器的超时回调函数

3.处理软件定时器命令队列

FreeRTOS提供了很多定时器有关的API函数,这些API函数大多都使用FreeRTOS的队列发送命令给定时器服务任务。这个队列叫做定时器命令队列。定时器命令队列是提供给FreeRTOS的软件定时器使用的,用户不能直接访问。

软件定时器相关配置

软件定时器有一个定时器服务和定时器命令队列,这两个东西肯定是要配置的,相关的配置也是放到文件FreeRTOSConfig.h中的,涉及到的配置如下:

1、configUSE_TIMERS

如果要使用软件定时器的宏configUSE_TIMERS一定要设置为1,当设置为1的话定时器服务任务就会在启动FreeRTOS调度器的时候自动创建。

2、configTIMER_TASK_PRIORITY

设置软件定时器服务任务的任务优先级,可以为0~(configMAX_PRIORITIES-1)。优先级一定要根据实际的应用要求来配置。如果定时器服务任务的优先级设置的高的话,定时器命令从队列中的命令和定时器回调函数就会及时的得到处理。

3、configTIMER_QUEUE_LENGTH

此宏用来设置定时器命令队列长度。

4、configTIMER_TASK_STACK_DEPTH

此宏用来设置定时器服务任务的任务堆栈大小

单次定时器和周期定时器

单次定时器:只超时一次,调用一次回调函数,可手动再开启定时器;

周期定时器:多次超时,多次调用回调函数。

软件定时器相关的API函数

1.创建软件定时器

参数:

pcTimerName:软件定时器名称;

xTimerPeriodInTicks:定时超时时间,单位:系统时钟节拍。宏pdMS_TO_TICKS()可用于将以毫秒为单位指定的时间转化为以tick为单位指定的时间;

uxAutoReload:定时器模式,pdTRUE:周期定时器,pdFALSE:单次定时器;

pvTimerID:软件定时器ID,用于多个软件定时器公用一个超时回调函数

pxCallbackFunction:软件定时器超时回调函数

返回值:

成功,定时器句柄;

失败,NULL。

2.开启软件定时器

参数:

xTimer:待开启的软件定时器的句柄;

xTicksToWait:发送命令到软件定时器命令队列的最大等待时间。

返回值:

pdPASS:开启成功;

pdFAIL:开启失败。

3.停止软件定时器

参数:

xTimer:待开启的软件定时器的句柄;

xTicksToWait:发送命令到软件定时器命令队列的最大等待时间。

返回值:

pdPASS:开启成功;

pdFAIL:开启失败。

4.复位软件定时器

该功能将使软件定时器的重新开启定时时,复位后的软件定时器以复位时的时刻作为开启时刻。

参数:

xTimer:待开启的软件定时器的句柄;

xTicksToWait:发送命令到软件定时器命令队列的最大等待时间。

返回值:

pdPASS:开启成功;

pdFAIL:开启失败。

5.更改软件定时器定时时间

参数:

xTimer:待开启的软件定时器的句柄;

xTicksToWait:发送命令到软件定时器命令队列的最大等待时间;

xNewPeriod:新的定时超时时间,单位:系统时钟节拍。

返回值:

pdPASS:开启成功;

pdFAIL:开启失败。

实操

实验需求

创建两个定时器:

定时器1,周期定时器,每1秒打印一次Periodic Timer

定时器2,单次定时器,启动后2秒打印一次Single Timer

CubeMX配置

代码实现

#include <stdio.h>
void StartDefaultTask(void const * argument)
{/* USER CODE BEGIN StartDefaultTask *///osTimerStart(Timer01Handle,1000);xTimerChangePeriod(Timer01Handle,pdMS_TO_TICKS(1000),0);osTimerStart(Timer02Handle,2000);/* Infinite loop */for(;;){osDelay(1);}/* USER CODE END StartDefaultTask */
}/* Callback01 function */
void Callback01(void const * argument)
{/* USER CODE BEGIN Callback01 */printf("Periodic Timer\r\n");/* USER CODE END Callback01 */
}/* Callback02 function */
void Callback02(void const * argument)
{/* USER CODE BEGIN Callback02 */printf("Single  Timer\r\n");/* USER CODE END Callback02 */
}

十四、中断管理

中断定义

 程序执行过程中CPU会遇到一些特殊情况,是正在执行的程序被“中断”,cpu中止原来正在执行的程序,转到处理异常情况或特殊事件的程序去执行,结束后再返回到原被中止的程序处(断点)继续执行

中断优先级

任何中断的优先级都大于任务!

在我们的操作系统,中断同样是具有优先级的,并且我们也可以设置它的优先级,但是它的优先级并不是从0~15,默认情况下它是从5~15,0~4这5个中断优先级不是FreeRTOS控制的(5是取决于configMAX_SYSCALL_INTERRUPT_PRIORITY)。

相关注意

1.在中断中必须使用中断相关的函数;

2.中断服务函数运行时间越短越好。

实操

实验需求

创建一个队列及一个任务,按下按键KEY1触发中断,在中断服务函数里向队列里发送数据,任务则阻塞接收队列数据。

CubeMX配置

代码实现

stm32f1xx_it.c

#include "cmsis_os.h"
/* USER CODE BEGIN PV */
extern osMessageQId myQueue01Handle;
/* USER CODE END PV */
/* USER CODE BEGIN 1 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{uint32_t snd = 1;xQueueSendFromISR(myQueue01Handle,&snd,NULL);
}
/* USER CODE END 1 */

freertos.c

#include <stdio.h>
void StartDefaultTask(void const * argument)
{/* USER CODE BEGIN StartDefaultTask */uint32_t rev = 0;/* Infinite loop */for(;;){if(xQueueReceive(myQueue01Handle,&rev,portMAX_DELAY) == pdTRUE)printf("rev = %d\r\n",rev);osDelay(1);}/* USER CODE END StartDefaultTask */
}

 

相关文章:

CubeMX的FreeRTOS学习

一、FreeRTOS的介绍 什么是FreeRTOS&#xff1f; Free即免费的&#xff0c;RTOS的全称是Real Time Operating system,中文就是实时操作系统。 注意&#xff1a;RTOS不是指某一个确定的系统&#xff0c;而是指一类的操作系统。比如&#xff1a;us/OS&#xff0c;FreeRTOS&…...

C语言初始:数据类型和变量

、 一.数据类型介绍 人有黄人白人黑人&#xff0c;那么数据呢&#xff1f; 我们大家可以看出谁是黄种人&#xff0c;谁是白种人&#xff0c;谁是黑种人&#xff0c;这是因为他们是类似的。 数据也是有类型的&#xff0c;就譬如整数类型&#xff0c;字符类型&#xff0c;浮点…...

Linux shellcheck工具

安装工具 通过linux yum源下载&#xff0c;可能因为yum源的问题找不到软件包&#xff0c;或者下载的软件包版本太旧。 ShellCheck的源代码托管在GitHub上(推荐下载方式)&#xff1a; GitHub - koalaman/shellcheck: ShellCheck, a static analysis tool for shell scripts 对下…...

FLINK SQL时间属性

Flink三种时间属性简介 在Flink SQL中&#xff0c;时间属性是一个核心概念&#xff0c;它主要用于处理与时间相关的数据流。Flink支持三种时间属性&#xff1a;事件时间&#xff08;event time&#xff09;、处理时间&#xff08;processing time&#xff09;和摄入时间&#…...

android——Groovy gralde 脚本迁移到DSL

1、implementation的转换 implementation com.github.CymChad:BaseRecyclerViewAdapterHelper:*** 转换为 implementation ("com.github.CymChad:BaseRecyclerViewAdapterHelper:***") 2、plugin的转换 apply plugin: kotlin-android-extensions 转换为&#x…...

工程项目管理中的最常见概念!蓝燕云总结!

01 怎么理解工程项目管理&#xff1f; 建设工程项目管理指的是专业性的管理&#xff0c;并非行政事务管理。建设工程项目管理是对工程项目全生命周期的管理&#xff0c;确保项目能够按计划的进度、成本和质量完成。 建设工程项目不同阶段管理的主要内容不同&#xff0c;通常…...

PostgreSQL AUTO INCREMENT

PostgreSQL AUTO INCREMENT 在数据库管理系统中&#xff0c;自动递增&#xff08;AUTO INCREMENT&#xff09;是一种常见特性&#xff0c;用于在插入新记录时自动生成唯一的标识符。PostgreSQL&#xff0c;作为一个功能强大的开源关系数据库管理系统&#xff0c;也提供了类似的…...

24-10-13-读书笔记(二十五)-《一只特立独行的猪》([中] 王小波)用一生来学习艺术

文章目录 《一只特立独行的猪》&#xff08;[中] 王小波&#xff09;目录阅读笔记记录总结 《一只特立独行的猪》&#xff08;[中] 王小波&#xff09; 十月第五篇&#xff0c;放慢脚步&#xff0c;秋季快要过去了&#xff0c;要步入冬季了&#xff0c;心中也是有些跌宕起伏&am…...

Java—继承性与多态性

目录 一、this关键字 1. 理解this 2. this练习 二、继承性 2.1 继承性的理解 2.1.1 多层继承 2.2 继承性的使用练习 2.2.1 练习1 2.2.2 练习2 2.3 方法的重写 2.4 super关键字 2.4.1 子类对象实例化 三、多态性 3.1 多态性的理解 3.2 向下转型与多态练习 四、Ob…...

打通华为认证实验考试“痛点”:备考指南全解析

华为认证体系中的实验考试环节&#xff0c;尤其是针对高端的HCIE认证&#xff0c;是评估考生实践技能的关键部分。这一环节的核心目标是检验考生对华为设备和解决方案的操作熟练度、技术实施技能以及面对现实工作挑战时的问题解决能力。通过在真实环境中进行的实践操作&#xf…...

【软考】子系统划分

目录 1. 子系统划分的原则1.1 子系统要具有相对独立性1.2 子系统之间数据的依赖性尽量小1.3 子系统划分的结果应使数据几余较小1.4 子系统的设置应考虑今后管理发展的需要1.5 子系统的划分应便于系统分阶段实现1.6 子系统的划分应考虑到各类资源的充分利用 2. 子系统结构设计3.…...

【Python】selenium获取鼠标在网页上的位置,并定位到网页位置模拟点击的方法

在使用Selenium写自动化爬虫时&#xff0c;遇到验证码是常事了。我在写爬取测试的时候&#xff0c;遇到了点击型的验证码&#xff0c;例如下图这种&#xff1a; 这种看似很简单&#xff0c;但是它居然卡爬虫&#xff1f;用简单的点触验证码的方法来做也没法实现 平常的点触的方…...

【C++ 真题】B2078 含 k 个 3 的数

含 k 个 3 的数 题目描述 输入两个正整数 m m m 和 k k k&#xff0c;其中 1 < m ≤ 1 0 15 1 \lt m \leq 10^{15} 1<m≤1015&#xff0c; 1 < k ≤ 15 1 \lt k \leq 15 1<k≤15 &#xff0c;判断 m m m 是否恰好含有 k k k 个 3 3 3&#xff0c;如果满足条…...

蓝桥杯省赛真题——冶炼金属

问题描述 小蓝有一个神奇的炉子用于将普通金属 O 冶炼成为一种特殊金属 X。这个炉子有一个称作转换率的属性 V&#xff0c;V 是一个正整数&#xff0c;这意味着消耗 V 个普通金属 O 恰好可以冶炼出一个特殊金属 X&#xff0c;当普通金属 O 的数目不足 V 时&#xff0c;无法继续…...

【Mac苹果电脑安装】DBeaverEE for Mac 数据库管理工具软件教程【保姆级教程】

Mac分享吧 文章目录 DBeaverEE 数据库管理工具 软件安装完成&#xff0c;打开效果图片Mac电脑 DBeaverEE 数据库管理工具 软件安装——v24.21️⃣&#xff1a;下载软件2️⃣&#xff1a;安装JDK&#xff0c;根据下图操作步骤提示完成安装3️⃣&#xff1a;安装DBeaverEE&#…...

数据仓库中的维度建模:深入理解与案例分析

数据仓库中的维度建模&#xff1a;深入理解与案例分析 维度建模是数据仓库设计中最常用的一种方法&#xff0c;旨在简化数据访问、提高查询效率&#xff0c;特别适用于需要对数据进行多维分析的场景。本文将深入探讨维度建模的核心概念、设计步骤以及如何将其应用于实际案例中…...

前端打印功能(vue +springboot)

后端 后端依赖生成pdf的方法pdf转图片使用(用的打印模版是带参数的 ,参数是aaa)总结 前端页面 效果 后端 依赖 依赖 一个是用模版生成对应的pdf,一个是用来将pdf转成图片需要的 <!--打印的--><dependency><groupId>net.sf.jasperreports</groupId>&l…...

中间件有哪些分类?

中间件的分类 中间件是位于操作系统和应用程序之间的软件&#xff0c;它提供了一系列服务来简化分布式系统中的应用程序开发和集成。中间件可以根据其功能和用途被分为不同的类别。以下是中间件的一些主要分类&#xff1a; 1. 通信处理&#xff08;消息&#xff09;中间件&am…...

开始新征程__10.13

好久没有更新 csdn 了&#xff0c;身边的人都说 csdn 水&#xff0c;但是在我看来&#xff0c;它在我大一这一年里对我的帮助很大&#xff0c;最近上账号看看&#xff0c;看见了网友评论&#xff0c;哈哈&#xff0c;决定以后还是继续更新&#xff0c;分享自己的学习心得。...

SAP 联合创始人谈Home Office

软件公司 SAP 的家庭办公室规定继续引发激烈争论&#xff0c;其联合创始人哈索-普拉特纳&#xff08;Hasso Plattner&#xff09;对此也有明确看法。 沃尔多夫--年初&#xff0c;SAP 首席执行官克里斯蒂安-克莱因&#xff08;Christian Klein&#xff09;向员工宣誓 "努力…...

基于Jenkins+K8S构建DevOps自动化运维管理平台

目录 1.k8s助力DevOps在企业落地实践 1.1 传统方式部署项目为什么发布慢&#xff0c;效率低&#xff1f; 1.2 上线一个功能&#xff0c;有多少时间被浪费了&#xff1f; 1.3 如何解决发布慢&#xff0c;效率低的问题呢&#xff1f; 1.5 什么是DevOps&#xff1f; 1.5.1 敏…...

【OpenCV】(一)—— 安装opencv环境

【OpenCV】&#xff08;一&#xff09;—— 安装opencv环境 OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源的计算机视觉和机器学习软件库。OpenCV 是用 C 编写的&#xff0c;但它也有 Python、Java 和 MATLAB 接口&#xff0c;并支持 Windows…...

MybatisPlus操作符和运算值

好久没有更新了&#xff0c;这次更新一个当前端需要对运算符和运算值都需要前端传递给后端&#xff0c;动态拼接运算条件时的处理方法。 1、踩雷 查询年龄 >20&#xff0c;其中>前端下拉框选择&#xff0c;20值前端下拉框选择 1&#xff09;用户表&#xff1a; CREAT…...

Index-1.9B模型部署教程

一、介绍 Index-1.9B 系列是 Index 系列型号的轻量级版本&#xff0c;包含以下型号&#xff1a; Index-1.9B 基础&#xff1a;具有 19 亿个非嵌入参数的基础模型&#xff0c;在 2.8T 主要为中文和英文的语料上进行预训练&#xff0c;在多个评测基准上与同级别模型相比领先。I…...

C语言 | Leetcode C语言题解之第468题验证IP地址

题目&#xff1a; 题解&#xff1a; char * validIPAddress(char * queryIP) {int len strlen(queryIP);if (strchr(queryIP, .)) {// IPv4int last -1;for (int i 0; i < 4; i) {int cur -1;if (i 3) {cur len;} else {char * p strchr(queryIP last 1, .);if (p…...

Qt自定义一个圆角对话框

如何得到一个圆角对话框&#xff1f; 步骤&#xff1a; 1、继承自QDiaglog 2、去掉系统自带的边框 3、设置背景透明,不设置4个角会有多余的部分出现颜色 4、对话框内部添加1个QWidget&#xff0c;给这个widget设置圆角&#xff0c;并添加到布局中让他充满对话框 5、后续对…...

C++ 中的自旋锁应用:SpinLockManual、SpinLockGuard 和 SpinLockTryGuard

在多线程编程中,同步机制是确保线程安全的关键。自旋锁(Spin Lock)是一种常见的同步机制,它通过忙等待(busy-waiting)的方式来实现线程间的互斥访问。在 C++ 中,我们可以使用 std::atomic_flag 来实现自旋锁。本文将介绍如何使用 SpinLockManual、SpinLockGuard 和 Spin…...

计算机网络 tcp和udp

目录 一、TCP 建立连接-TCP 三次握手 1&#xff09; 什么是半连接队列和全连接队列&#xff1f; 2&#xff09; 为什么要三次握手? 3&#xff09; 三次握手过程中可以携带数据吗&#xff1f; 断开连接-TCP 四次挥手 1&#xff09; 为什么要四次挥手&#xff1f; 2&…...

React(一) 认识React、熟悉类组件、JSX书写规范、嵌入变量表达式、绑定属性

文章目录 一、初始React1. React的基本认识2. Hello案例2.1 三个依赖2.2 渲染页面2.3 hello案例完整代码 二、类组件1. 封装类组件2. 组件里的数据3. 组件里的函数 (重点)4. 案例练习(1) 展示电影列表 三、JSX语法1. 认识JSX2. JSX书写规范及注释3. JSX嵌入变量作为子元素4. JS…...

计算机网络 2024 11 10

计算机网络 - 知乎计算机网络&#xff08;四&#xff09;—— 网络层&#xff08;1、2&#xff09;&#xff1a;网络层概述、网络层提供的两种服务_以下属于网络层范畴的是a透明传输比特流b媒体接入控制c ip地址d-CSDN博客 湖科大教书匠的个人空间-湖科大教书匠个人主页-哔哩哔…...