1.FreeRTOS实时系统
FreeRTOS实时系统
一.FreeRTOS介绍
1.什么是FreeRTOS?
free即免费,RTOS的全称是Real time operating system,即实时操作系统。
注意:RTOS不是指一个确定的系统,而是指一类的系统。比如:us/OS,FreeOS,RTX,RT-Thread等,这些都是RTOS类操作系统。
FreeRTOS是一个迷你的实时操作系统内核。作为一个轻量级的操作系统,功能包括:任务管理、时间
管理、信号量、消息队列、内存管理、记录功能、软件定时器、协程等,可基本满足较小系统的需
要。
由于RTOS需占用一定的系统资源(尤其是RAM资源),只有μC/OS-II、embOS、salvo、FreeRTOS等少
数实时操作系统能在小RAM单片机上运行。相对μC/OS-II、embOS等商业操作系统,FreeRTOS操作系
统是完全免费的操作系统,具有源码公开、可移植、可裁减、调度策略灵活的特点,可以方便地移植
到各种单片机上运行,其最新版本为10.4.4版。
(以上来自百度百科)
2.为什么选择 FreeRTOS ?
FreeRTOS 是免费的;
很多半导体厂商产品的SDK(Software Development Kit)软件开发工具包,就使用FreeRTOS作为其
操作系统,尤其是WIFI、蓝牙这些带有协议栈的芯片或模块。
简单,因为FreeRTOS的文件数量很少。
二、FreeRTOS移植到STM32芯片上
使用Cubemx快速移植,
创建一个模板项目
打开mcu

选中芯片

选中system core
配置SYS

配置RCC


配置串口,用于打印调试

选中FreeRTOS系统

项目生成配置


选择generate code,生成代码
选择open project

在usart.c中加入
/* USER CODE BEGIN 0 */
#include "stdio.h"
int fputc(int ch, FILE *f)
{
unsigned char temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
/* USER CODE END 0 */
在main中测试是否打印到串口上
#include "stdio.h"
/* USER CODE BEGIN 2 */
printf("hello world!\r\n");
/* USER CODE END 2 */
一些常见问题:
1.Timebase source为什么不能设置为systick?
裸机版的时钟默认是SysTick,但是开启FreeRTOS后,FreeRTOS会占用SysTick(用来生成1ms的定时用于任务调度),所以需要为其他总线提供另外的时钟源。
2.FreeRTOS版本问题
V2的内核版本更高,更能更多,多数情况下V1版本内核完全够用
3.FreeRTOS 各配置选项卡的解释
- Events:事件相关的创建
- Task and Queues: 任务与队列的创建
- Timers and Semaphores: 定时器和信号量的创建
- Mutexes: 互斥量的创建
- FreeRTOS Heap Usage: 用于查看堆使用情况
- config parameters: 内核参数设置,用户根据自己的实际应用来裁剪定制FreeRTOS 内核
- Include parameters: FreeRTOS 部分函数的使能
- User Constants: 相关宏的定义,可以自建一些常量在工程中使用
- Advanced settings:高级设置
4. 内核配置、函数使能的一些翻译



三.任务的创建与删除
1.什么是任务?
任务可以理解为进程或者是一个任务,会在内存开辟一个空间用于执行任务。
比如,在电脑打开记事本,微信等都是一个任务。
任务通常含有一个while(1)死循环
2.任务创建与删除相关函数
| 函数名称 | 函数作用 |
|---|---|
| xTaskCreate() | 动态方式创建任务 |
| xTaskCreateStatic() | 静态方式创建任务 |
| xTaskDelete() | 删除任务 |
动态创建与静态创建的区别:
动态创建任务的堆栈由系统分配,而静态创建任务的堆栈由用户自己传递。
大多数情况使用动态创建任务。
xTaskCreate()函数原型

- pvTaskCode:指向任务函数的指针,任务必须实现为永不返回(即连续循环);2. pcName:任务的名字,主要是用来调试,默认情况下最大长度是16;
- pvParameters:指定的任务栈的大小;
- uxPriority:任务优先级,数值越大,优先级越大;
- pxCreatedTask:用于返回已创建任务的句柄可以被引用。
| 返回值 | 描述 |
|---|---|
| PdPass | 任务创建成功 |
| errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY | 任务创建失败 |
官方案例:
/* Task to be created. */
void vTaskCode( void * pvParameters )
{
/* The parameter value is expected to be 1 as 1 is passed in the
pvParameters value in the call to xTaskCreate() below.
configASSERT( ( ( uint32_t ) pvParameters ) == 1 );
for( ;; )
{
/* Task code goes here. */
}
}
/* Function that creates a task. */
void vOtherFunction( void )
{
BaseType_t xReturned;
TaskHandle_t xHandle = NULL;
/* Create the task, storing the handle. */
xReturned = xTaskCreate(
vTaskCode, /* Function that implements the task. */
"NAME", /* Text name for the task. */
STACK_SIZE, /* Stack size in words, not bytes. */
( void * ) 1, /* Parameter passed into the task. */
tskIDLE_PRIORITY,/* Priority at which the task is created. */
&xHandle ); /* Used to pass out the created task's handle.
*/
if( xReturned == pdPASS )
{
/* The task was created. Use the task's handle to delete the task. */
vTaskDelete( xHandle );
}
}
vTaskDelete函数原型:
void vTaskDelete(TaskHandle_t xTaskToDelete);
只需将待删除的任务句柄传入该函数,即可将该任务删除。
当传入的参数为NULL,则代表删除任务自身(当前正在运行的任务)。
3.实现

四、任务调度
什么是任务调度器?
调度器就是使用相关的调度算法来决定当前执行到哪个任务了
FreeRTOS中任务调度器的函数是vTaskStartScheduler(),在FreeRTOS被封装为osKernelStart()
调度器规则:
FreeRTOS是一个实时操作系统,需要遵循一些调度规则:
- 抢占式调度:高优先级的抢占低优先级的,永远都是高优先级的优先执行
- 时间片调度:同等优先级的,谁先抢占到CPU资源,谁先执行。
抢占式调度运行过程:

时间片调度运行过程:

任务的状态
FreeRTOS中有4种状态:
- Running运行态:当前任务正在使用CPU的资源,任务正在执行。
- Ready就绪态:当前任务等待CPU的调度,任务等待运行。
- Blocked阻塞态:当前任务因为一些延时、或等待信号量、消息队列、事件标记组而处于一个阻塞的状态。
- Suspended挂起态:类似暂停,通过调用
vTaskSuspend()对指定的任务进行挂起。需用通过调用vTaskResume()才能恢复。
![]()
总结:
- 只有就绪态才能变为运行态
- 其他状态只有调用指定的函数,先成功就绪态才能变为运行态。
任务小实验:
需求:
建 4 个任务:taskLED1,taskLED2,taskKEY1,taskKEY2,任务要求如下:
taskLED1:间隔 500ms 闪烁 LED1;
taskLED2:间隔 1000ms 闪烁 LED2;
taskKEY1:如果 taskLED1 存在,则按下 KEY1 后删除 taskLED1 ,否则创建 taskLED1 ;
taskKEY2:如果 taskLED2 正常运行,则按下 KEY2 后挂起 taskLED2 ,否则恢复 taskLED2
cubeMX配置:


代码实现:
/* USER CODE BEGIN Header_StartTaskLED1 */
/**
* @brief Function implementing the taskLED1 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskLED1 */
void StartTaskLED1(void const * argument)
{
/* USER CODE BEGIN StartTaskLED1 */
/* Infinite loop */
for(;;)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);
osDelay(500);
}
/* USER CODE END StartTaskLED1 */
}
/* USER CODE BEGIN Header_StartTaskLED2 */
/**
* @brief Function implementing the taskLED2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskLED2 */
void StartTaskLED2(void const * argument)
{
/* USER CODE BEGIN StartTaskLED2 */
/* Infinite loop */
for(;;)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);
osDelay(1000);
}
/* USER CODE END StartTaskLED2 */
}
/* 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) == RESET)
{
osDelay(20);
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET)
{
printf("KEY1按下了!\r\n");
if(taskLED1Handle == NULL)
{
printf("任务1不存在,开始创建\r\n");
osThreadDef(taskLED1, StartTaskLED1, osPriorityNormal, 0, 128);
taskLED1Handle = osThreadCreate(osThread(taskLED1), NULL);
if(taskLED1Handle != NULL)
printf("任务1创建成功\r\n");
}
else
{
osThreadTerminate(taskLED1Handle);
taskLED1Handle = NULL;
printf("任务1已删除\r\n");
}
}
while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == 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) == RESET)
{
osDelay(20);
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET)
{
if(flag == 0)
{
osThreadSuspend(taskLED2Handle);
printf("任务2已暂停\r\n");
flag = 1;
}
else
{
osThreadResume(taskLED2Handle);
printf("任务2已恢复\r\n");
flag = 0;
}
}
while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET);
}
osDelay(10);
}
/* USER CODE END StartTaskKEY2 */
}
五、队列
什么队列?
队列又称消息队列,是任务通信的一种数据结构,队列可以在任务与任务之间中断和任务间的传递消息。
为啥不能使用全局变量呢?
全局的变量,在多任务修改的情况下,获取的不是最新的值,容易导致数据的错读。
队列的特点
1.入队出队方式
通常先进先出(FIFO),数据先进去的先被读取。
还有一种后进先出(LIFO),比较少用,一般有于栈。
2.数据传递方式
采用实际值传递,则将数据拷贝到队列进行传递,也可以使用指针,在数据比较大的时候通常采用指针传递。
3、多任务访问
队列不属于某个任务,任何任务都可以使用队列发送跟接收数据。
4、出队、入队阻塞
当向一个队列发送消息是,可以指定一个阻塞时间,假设次数当队列已满无法入队时,可以设置一个阻塞时间:
- 0:直接返回,不阻塞。
- 0-portMAX_DELAY:设置指定的时间,超过该时间还没进入队列直接返回。
- portMAX_DELAY:死等,等到队列有空闲位置为止。
队列相关API
1.创建队列
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,
UBaseType_t uxItemSize );
参数:
- uxQueueLength:队列的可容纳的长度
- uxItemSize:一个队列容纳的大小
返回值:
如何一个队列创建成功,会分配到内存,反之失败则为NULL
2.写队列API
| 函数 | 描述 |
|---|---|
| xQueueSend() | 往队列的尾部写入消息 |
| xQueueSendToBack() | 同xQueueSend() |
| xQueueSendToFront() | 往队列的头部写入消息 |
| xQueueOverwrite() | 覆写消息队列(只能用于长度1的情况) |
| xQueueSendFromISR() | 在中断中往消息队列尾部写入消息 |
| xQueueSendToBackFromISR() | 同xQueueSendFromISR() |
| xQueueSendToFrontFromISR() | 在中断中往队列的头部写入消息 |
| xQueueOverwriteFromISR() | 在中断中覆写消息队列(只能用于长度1的情况) |
BaseType_t xQueueSend(
QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait
);
参数:
- xQueue:队列的句柄,数据项将发送到此队列。
- pvItemToQueue:待写入数据
- xTicksToWait:阻塞超时时间
返回值:
如果成功写入数据,返回 pdTRUE,否则返回 errQUEUE_FULL。
3、读队列API
| 函数 | 描述 |
|---|---|
| xQueueReceive() | 从队列头部读取消息,并删除消息 |
| xQueuePeek() | 从队列头部读取消息,但并不删除消息 |
| xQueueReceiveFromISR() | 在中断中从队列头部读取消息,并删除消息 |
| xQueuePeekFromISR() | 在中断中从队列头部读取消息,但并不删除消息 |
BaseType_t xQueueReceive(
QueueHandle_t xQueue,
void *pvBuffer,
TickType_t xTicksToWait
);
参数:
- xQueue:待读取的队列
- pvItemToQueue:数据读取缓冲区
- xTicksToWait:阻塞超时时间
返回值:
成功返回 pdTRUE,否则返回 pdFALSE。
实验:
创建一个队列,按下 KEY1 向队列发送数据,按下 KEY2 向队列读取数据。
cubeMX配置

代码实现
/* USER CODE BEGIN Header_StartTaskSend */
/**
* @brief Function implementing the taskSend thread.
* @param argument: Not used
* @retval None
*/
/* 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) == RESET)
{
osDelay(20);
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == RESET)
{
status = xQueueSend(myQueue01Handle,&buf,0);
if(status == pdTRUE)
printf("数据发送成功:%d \r\n",buf);
else
printf("数据发送失败\r\n");
}
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == 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 = 100;
BaseType_t status;
/* Infinite loop */
for(;;)
{
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == RESET)
{
osDelay(20);
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == RESET)
{
status = xQueueReceive(myQueue01Handle,&buf,0);
if(status == pdTRUE)
printf("数据接收成功:%d \r\n",buf);
else
printf("数据接收失败\r\n");
}
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == RESET);
}
osDelay(10);
}
/* USER CODE END StartTaskReceive */
}
六、二值信号量
什么是信号量?
信号量(semaphore)实在多任务环境下使用的一种机制,是可以用来保证两个或者多个关键代码段不被并发调用。
信号量可以拆分为信号和量,信号起到了通知的作用,而量表示资源的数量,当我们的量只有0和1的时候,我们称为二值信号量,只有两种状态,当我们那个量没有限制时,称为计数型信号量。
什么是二值信号量?
二值信号量其实是一个长度为1,大小为1的队列,只有0和1两种状态,通常用它进行互斥访问或任务同步。

二值信号量API
| 函数 | 描述 |
|---|---|
| xSemaphoreCreateBinary() | 使用动态方式创建二值信号量 |
| xSemaphoreCreateBinaryStatic() | 使用静态方式创建二值信号量 |
| xSemaphoreGive() | 释放信号量 |
| xSemaphoreGiveFromISR() | 在中断释放信号量 |
| xSemaphoreTake() | 获取信号量 |
| xSemaphoreTakeFromISR() | 在中断获取信号量 |
创建二值信号量
SemaphoreHandle_t xSemaphoreCreateBinary( void )
参数:
无
返回值:
成功,返回对应二值信号量的句柄;
失败,返回 NULL 。
释放二值信号量
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore )
参数:
xSemaphore:要释放的信号量句柄
返回值:
成功,返回 pdPASS ;
失败,返回 errQUEUE_FULL 。
获取二值信号量
BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait );
参数:
xSemaphore:要获取的信号量句柄
xTicksToWait:超时时间,0 表示不超时,portMAX_DELAY表示卡死等待;
返回值:
成功,返回 pdPASS ;
失败,返回 errQUEUE_FULL 。
实现:
创建一个二值信号量,按下 KEY1 则释放信号量,按下 KEY2 获取信号量。
cubeMX配置

代码实现
osSemaphoreDef(myBinarySem);
//myBinarySemHandle = osSemaphoreCreate(osSemaphore(myBinarySem), 1);//使用这个默认会创建一个二值信号量
myBinarySemHandle = xSemaphoreCreateBinary();//需要手动创建
/* USER CODE BEGIN Header_StartTaskSemaphoreGive */
/**
* @brief Function implementing the taskSemaphoreGi thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskSemaphoreGive */
void StartTaskSemaphoreGive(void const * argument)
{
/* USER CODE BEGIN StartTaskSemaphoreGive */
/* Infinite loop */
for(;;)
{
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET)
{
osDelay(20);
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET)
{
if(xSemaphoreGive(myBinarySemHandle) == pdTRUE)
printf("二极信号放入成功\r\n");
else
printf("二极信号放入失败\r\n");
}
while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET);
}
osDelay(10);
}
/* USER CODE END StartTaskSemaphoreGive */
}
/* USER CODE BEGIN Header_StartTaskSemaphoreTake */
/**
* @brief Function implementing the taskSemaphoreTa thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskSemaphoreTake */
void StartTaskSemaphoreTake(void const * argument)
{
/* USER CODE BEGIN StartTaskSemaphoreTake */
/* Infinite loop */
for(;;)
{
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET)
{
osDelay(20);
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET)
{
if(xSemaphoreTake(myBinarySemHandle, 0) == pdTRUE)
printf("二极信号获取成功\r\n");
else
printf("二极信号获取失败\r\n");
}
while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET);
}
osDelay(10);
}
/* USER CODE END StartTaskSemaphoreTake */
}
七、计数型信号量
计量型信号量相当于长度大于1的队列,能容纳更多资源,在创建时确定了长度大小。
应用场景:
一个停车场有固定的停车位,当一辆车开进来时,车位减一,一辆车开走时,车位加一,当车位满了,表示队列装满了,此时再进来只能等待或者是开走。
计数型相关API函数
| 函数 | 描述 |
|---|---|
| xSemaphoreCreateCounting() | 使用动态方式创建计数型信号量 |
| xSemaphoreCreateCountingStatic() | 使用静态方式创建计数型信号量 |
| xSemaphoreGetCount() | 获取计数型信号量 |
计数型信号量的释放和获取与二值信号量完全相同 !
SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount);
参数:
uxMaxCount:可以达到的最大计数值
uxInitialCount:创建信号量时分配给信号量的计数值
返回值:
成功,返回对应计数型信号量的句柄;
失败,返回 NULL 。
实现案例:
创建一个计数型信号量,按下 KEY1 则释放信号量,按下 KEY2 获取信号量。
cubeMX配置
将USE_COUNTING_SEMAPHORES的值从Disabled改成Enabled


代码实现
/* USER CODE BEGIN Header_StartTaskSemaphoreGive */
/**
* @brief Function implementing the taskSemaphoreGi thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskSemaphoreGive */
void StartTaskSemaphoreGive(void const * argument)
{
/* USER CODE BEGIN StartTaskSemaphoreGive */
/* Infinite loop */
for(;;)
{
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET)
{
osDelay(20);
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET)
{
if(xSemaphoreGive(myCountingSemaHandle) == pdTRUE)
printf("计数信号量放入成功\r\n");
else
printf("计数信号量放入失败\r\n");
}
while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET);
}
osDelay(10);
}
/* USER CODE END StartTaskSemaphoreGive */
}
/* USER CODE BEGIN Header_StartTaskSemaphoreTake */
/**
* @brief Function implementing the taskSemaphoreTa thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskSemaphoreTake */
void StartTaskSemaphoreTake(void const * argument)
{
/* USER CODE BEGIN StartTaskSemaphoreTake */
/* Infinite loop */
for(;;)
{
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET)
{
osDelay(20);
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET)
{
if(xSemaphoreTake(myCountingSemaHandle, 0) == pdTRUE)
printf("计数信号量获取成功\r\n");
else
printf("计数信号量获取失败\r\n");
}
while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET);
}
osDelay(10);
}
/* USER CODE END StartTaskSemaphoreTake */
}
八、互斥量
什么是互斥量?
在多数情况下,互斥型信号量与二值型信号量非常相似,但功能上,二值型信号量用于同步,而互斥型信号量用于资源保护。
互斥型信号量和二值型信号量还有一个最大的区别,互斥型信号量可以有效解决优先级反转现象。
什么是优先级翻转?

如图上所示,当低优先级获得CPU资源时,还没执行完就会被高优先级的任务打断,如果执行重要的东西时,容易发送错误,所以互斥型信号量就起到作用了,低优先级 在获取到CPU资源时,不会被其他高优先级的任务打断,直到释放信号。
互斥量相关API
互斥信号量不能用于中断服务函数中!
| 函数 | 描述 |
|---|---|
| xSemaphoreCreateMutex() | 使用动态方法创建互斥信号量。 |
| xSemaphoreCreateMutexStatic() | 使用静态方法创建互斥信号量。 |
SemaphoreHandle_t xSemaphoreCreateMutex( void )
参数:
无
返回值:
成功,返回对应互斥量的句柄;
失败,返回 NULL 。
实现
- 演示优先级翻转
- 使用互斥量优化优先级翻转问题
cubeMX配置

代码实现
创建了三个任务StartTaskH、StartTaskM、StartTaskL
/* USER CODE BEGIN Header_StartTaskH */
/**
* @brief Function implementing the TaskH thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskH */
void StartTaskH(void const * argument)
{
/* USER CODE BEGIN StartTaskH */
/* Infinite loop */
for(;;)
{
xSemaphoreTake(myMutexHandle,portMAX_DELAY);
printf("TaskH:我在上厕所...\r\n");
HAL_Delay(1000);
printf("TaskH:我出来了...\r\n");
xSemaphoreGive(myMutexHandle);
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(myMutexHandle,portMAX_DELAY);
printf("TaskL:我在上厕所...\r\n");
HAL_Delay(3000);
printf("TaskL:我出来了...\r\n");
xSemaphoreGive(myMutexHandle);
osDelay(1000);
}
/* USER CODE END StartTaskL */
}
九、事件标志组
什么是事件标志组?
事件标志位:表示某件事是否发生,联想:全局变量flag,通常每个位表示一个事件(高8位不算)
事件标志组:是一组事件标志位的集合,也就是一个整数
事件标志组本质是一个 16 位或 32 位无符号的数据类型 EventBits_t ,由 configUSE_16_BIT_TICKS 决定。
虽然使用了 32 位无符号的数据类型变量来存储事件标志, 但其中的高8位用作存储事件标志组的控制信息,
低 24 位用作存储事件标志 ,所以说一个事件组最多可以存储 24 个事件标志!

事件标志组API
| 函数 | 描述 |
|---|---|
| xEventGroupCreate() | 使用动态方式创建事件标志组 |
| xEventGroupCreateStatic() | 使用静态方式创建事件标志组 |
| xEventGroupClearBits() | 清零事件标志位 |
| xEventGroupClearBitsFromISR() | 在中断中清零事件标志位 |
| xEventGroupSetBits() | 设置事件标志位 |
| xEventGroupSetBitsFromISR() | 在中断中设置事件标志位 |
| xEventGroupWaitBits() | 等待事件标志位 |
1.创建事件标志组
EventGroupHandle_t xEventGroupCreate( void );
参数:
无
返回值:
成功,返回对应事件标志组的handle;
失败,返回 NULL 。
2.设置事件标志位
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet );
参数:
xEventGroup:对应事件组句柄。
uxBitsToSet:指定要在事件组中设置的一个或多个位的按位值。
返回值:
设置之后事件组中的事件标志位值。
3.清除事件标志位
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear );
参数:
xEventGroup:对应事件组句柄。
uxBitsToClear:指定要在事件组中清除的一个或多个位的按位值。
返回值:
清零之前事件组中事件标志位的值。
4.等待事件标志位
EventBits_t xEventGroupWaitBits(
const EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait );
参数:
xEventGroup:对应的事件标志组handle
uxBitsToWaitFor:指定事件组中要等待的一个或多个事件位的按位值
xClearOnExit:pdTRUE——清除对应事件位,pdFALSE——不清除
xWaitForAllBits:pdTRUE——所有等待事件位全为1(逻辑与),pdFALSE——等待的事件位有一个为1
(逻辑或)
xTicksToWait:超时
返回值:
等待的事件标志位值:等待事件标志位成功,返回等待到的事件标志位
其他值:等待事件标志位失败,返回事件组中的事件标志位
实现:
创建一个事件标志组和两个任务( task1 和 task2),task1 检测按键,如果检测到 KEY1 和 KEY2 都按过,
则执行 task2 。
/* 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 */
/* USER CODE BEGIN Header_StartTask1 */
/**
* @brief Function implementing the Task1 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask1 */
void StartTask1(void const * argument)
{
/* USER CODE BEGIN StartTask1 */
/* Infinite loop */
for(;;)
{
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET)
{
osDelay(20);
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET)
{
xEventGroupSetBits(eventgroup_handle, 0x01);
}
while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET);
}
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET)
{
osDelay(20);
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET)
{
xEventGroupSetBits(eventgroup_handle, 0x02);
}
while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET);
}
osDelay(10);
}
/* USER CODE END StartTask1 */
}
/* USER CODE BEGIN Header_StartTask2 */
/**
* @brief Function implementing the Task2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask2 */
void StartTask2(void const * argument)
{
/* USER CODE BEGIN StartTask2 */
EventBits_t result;
/* Infinite loop */
for(;;)
{
result = xEventGroupWaitBits(eventgroup_handle, 0x01 | 0x02 , pdTRUE, pdFALSE , portMAX_DELAY);
printf("同意请假了:%#x \r\n",result);
osDelay(1);
}
/* USER CODE END StartTask2 */
}
十、任务通知
什么是任务通知?
FreeRTOS 从版本 V8.2.0 开始提供任务通知这个功能,每个任务都有一个 32 位的通知值。按照 FreeRTOS
官方的说法,使用消息通知比通过二进制信号量方式解除阻塞任务快 45%, 并且更加省内存(无需创建队
列)。
在大多数情况下,任务通知可以替代二值信号量、计数信号量、事件标志组,可以替代长度为 1 的队列(可
以保存一个 32 位整数或指针值),并且任务通知速度更快、使用的RAM更少!
任务通知值的更新方式
FreeRTOS 提供以下几种方式发送通知给任务 :
- 发送消息给任务,如果有通知未读, 不覆盖通知值
- 发送消息给任务,直接覆盖通知值
- 发送消息给任务,设置通知值的一个或者多个位
- 发送消息给任务,递增通知值
通过对以上方式的合理使用,可以在一定场合下替代原本的队列、信号量、事件标志组等。
任务通知的优势和劣势
任务通知的优势
- 使用任务通知向任务发送事件或数据,比使用队列、事件标志组或信号量快得多。
- 使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体。
任务通知的劣势
- 只有任务可以等待通知,中断服务函数中不可以,因为中断没有 TCB 。
- 通知只能一对一,因为通知必须指定任务。
- 等待通知的任务可以被阻塞, 但是发送消息的任务,任何情况下都不会被阻塞等待。
- 任务通知是通过更新任务通知值来发送数据的,任务结构体中只有一个任务通知值,只能保持一个数
据。
任务通知相关API函数
1.发送通知
| 函数 | 描述 |
|---|---|
| xTaskNotify() | 发送通知,带有通知值 |
| xTaskNotifyAndQuery() | 发送通知,带有通知值并且保留接收任务的原通知值 |
| xTaskNotifyGive() | 发送通知,不带通知值 |
| xTaskNotifyFromISR() | 在中断中发送通知,带有通知值 |
| xTaskNotifyAndQueryFromISR() | 在中断中发送通知,带有通知值并且保留接收任务的原通知值 |
| xTaskNotifyGiveFromISR() | 在中断中发送通知,不带通知值 |
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction );
参数:
xTaskToNotify:需要接收通知的任务handle;
ulValue:用于更新接收任务通知值, 具体如何更新由形参 eAction 决定;
eAction:一个枚举,代表如何使用任务通知的值;
| 枚举值 | 描述 |
|---|---|
| eNoAction | 发送通知,但不更新值(参数ulValue未使用) |
| eSetBits | 被通知任务的通知值按位或ulValue。(某些场景下可代替事件组, 效率更高) |
| eIncrement | 被通知任务的通知值增加1(参数ulValue未使用),相当于 xTaskNotifyGive |
| eSetValueWithOverWrite | 被通知任务的通知值设置为 ulValue。(某些场景下可代替 xQueueOverwrite ,效率更高) |
| eSetValueWithoutOverwrite | 如果被通知的任务当前没有通知,则被通知的任务的通知值设为ulValue。如果被通知任务没有取走上一个通知,又接收到了一个通知,则这次通知值丢弃,在这种情况下视为调用失败并返回 pdFALSE(某些场景下可代替 xQueueSend ,效率更高) |
返回值:
如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回 pdFALSE, 而其他
情况均返回pdPASS。
BaseType_t xTaskNotifyAndQuery( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t *pulPreviousNotifyValue );
参数:
xTaskToNotify:需要接收通知的任务handle;
ulValue:用于更新接收任务通知值, 具体如何更新由形参 eAction 决定;
eAction:一个枚举,代表如何使用任务通知的值;
pulPreviousNotifyValue:对象任务的上一个任务通知值,如果为 NULL, 则不需要回传, 这个时候就等价
于函数 xTaskNotify()。
返回值:
如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回 pdFALSE, 而其他
情况均返回pdPASS。
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
参数:
xTaskToNotify:接收通知的任务句柄, 并让其自身的任务通知值加 1。
返回值:
总是返回 pdPASS。
2.等待通知
等待通知API函数只能用在任务,不可应用于中断中!
| 函数 | 描述 |
|---|---|
| ulTaskNotifyTake() | 获取任务通知,可以设置在退出此函数的时候将任务通知值清零或者减一。当 任务通知用作二值信号量或者计数信号量的时候,使用此函数来获取信号量。 |
| xTaskNotifyWait() | 获取任务通知,比 ulTaskNotifyTak()更为复杂,可获取通知值和清除通知值的指定位 |
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,
TickType_t xTicksToWait );
参数:
xClearCountOnExit:指定在成功接收通知后,将通知值清零或减 1,pdTRUE:把通知值清零(二值信号
量);pdFALSE:把通知值减一(计数型信号量);
xTicksToWait:阻塞等待任务通知值的最大时间;
返回值:
0:接收失败
非0:接收成功,返回任务通知的通知值
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait );
ulBitsToClearOnEntry:函数执行前清零任务通知值那些位 。
ulBitsToClearOnExit:表示在函数退出前,清零任务通知值那些位,在清 0 前,接收到的任务通知值会先被
保存到形参*pulNotificationValue 中。
pulNotificationValue:用于保存接收到的任务通知值。 如果 不需要使用,则设置为 NULL 即可 。
xTicksToWait:等待消息通知的最大等待时间。
实现:
-
模拟二值信号量
/* USER CODE BEGIN Header_StartTaskSend */ /** * @brief Function implementing the TaskSend thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_StartTaskSend */ void StartTaskSend(void const * argument) { /* USER CODE BEGIN StartTaskSend */ /* Infinite loop */ for(;;) { if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET) { osDelay(20); if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET) { xTaskNotifyGive(TaskReceiveHandle); printf("任务通知:二值信号量数据发送成功! \r\n"); } while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == 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) == RESET) { osDelay(20); if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET) { rev = ulTaskNotifyTake(pdTRUE,portMAX_DELAY); if(rev != 0) printf("任务通知:二值信号量数据接收成功!rev=%d \r\n",rev); } while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET); } osDelay(10); } /* USER CODE END StartTaskReceive */ } -
模拟计数型信号量
/* USER CODE BEGIN Header_StartTaskSend */ /** * @brief Function implementing the TaskSend thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_StartTaskSend */ void StartTaskSend(void const * argument) { /* USER CODE BEGIN StartTaskSend */ /* Infinite loop */ for(;;) { if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET) { osDelay(20); if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET) { xTaskNotifyGive(TaskReceiveHandle); printf("任务通知:计数信号量数据发送成功! \r\n"); } while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == 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) == RESET) { osDelay(20); if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET) { rev = ulTaskNotifyTake(pdFALSE,portMAX_DELAY); if(rev != 0) printf("任务通知:计数信号量数据接收成功!rev=%d \r\n",rev); } while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET); } osDelay(10); } /* USER CODE END StartTaskReceive */ } -
模拟事件标志组
/* USER CODE BEGIN Header_StartTaskSend */ /** * @brief Function implementing the TaskSend thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_StartTaskSend */ void StartTaskSend(void const * argument) { /* USER CODE BEGIN StartTaskSend */ /* Infinite loop */ for(;;) { if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET) { osDelay(20); if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET) { printf("按下了KEY1,发送0x01\r\n"); xTaskNotify(TaskReceiveHandle,0x01,eSetBits); } while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET); } if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET) { osDelay(20); if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET) { printf("按下了KEY2,发送0x02\r\n"); xTaskNotify(TaskReceiveHandle,0x02,eSetBits); } while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == 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,¬ify_val, portMAX_DELAY); if(notify_val & 0x01) event_bit |= 0x01; if(notify_val & 0x02) event_bit |= 0x02; if(event_bit == (0x01 | 0x02)) { event_bit = 0; printf("请假成功\r\n"); } osDelay(10); } /* USER CODE END StartTaskReceive */ } -
模拟邮箱
/* USER CODE BEGIN Header_StartTaskSend */ /** * @brief Function implementing the TaskSend thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_StartTaskSend */ void StartTaskSend(void const * argument) { /* USER CODE BEGIN StartTaskSend */ /* Infinite loop */ for(;;) { if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET) { osDelay(20); if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET) { printf("按下了KEY1\r\n"); xTaskNotify(TaskReceiveHandle,1,eSetValueWithOverwrite); } while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET); } if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET) { osDelay(20); if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET) { printf("按下了KEY2\r\n"); xTaskNotify(TaskReceiveHandle,0x02,eSetValueWithOverwrite); } while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == 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,¬ify_val, portMAX_DELAY); printf("接收到的通知值为:%d \r\n",notify_val); osDelay(10); } /* USER CODE END StartTaskReceive */ }
十一、延时函数
什么是延时函数?
延时函数是延迟执行
延时函数分类
相对延时:vtaskDelay
不能在延迟时间中发生中断
绝对延时:vTaskDelayUntil
延时时间能在中断中,直到延时结束
vtaskDelay与HAL_DELAY区别

vTaskDelay 作用是让任务阻塞,任务阻塞后,RTOS系统调用其它处于就绪状态的优先级最高的任务来执
行。
HAL_Delay 一直不停的调用获取系统时间的函数,直到指定的时间流逝然后退出,故其占用了全部CPU时
间。
十二、软件定时器
什么是定时器?
简单可以理解为闹钟,到达指定一段时间后,就会响铃。
STM32 芯片自带硬件定时器,精度较高,达到定时时间后会触发中断,也可以生成 PWM 、输入捕获、输出
比较,等等,功能强大,但是由于硬件的限制,个数有限。
软件定时器也可以实现定时功能,达到定时时间后可调用回调函数,可以在回调函数里处理信息。
软件定时器优缺点
优点:
- 简单、成本低;
- 只要内存足够,可创建多个;
缺点:
精度较低,容易受中断影响。在大多数情况下够用,但对于精度要求比较高的场合不建议使用。
软件定时器原理
定时器是一个可选的、不属于 FreeRTOS 内核的功能,它是由定时器服务任务来提供的。
在调用函数 vTaskStartScheduler() 开启任务调度器的时候,会创建一个用于管理软件定时器的任务,这个
任务就叫做软件定时器服务任务。
- 负责软件定时器超时的逻辑判断
- 调用超时软件定时器的超时回调函数
- 处理软件定时器命令队列
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函数
| 函数 | 描述 |
|---|---|
| xTimerCreate() | 动态方式创建软件定时器 |
| xTimerCreateStatic() | 静态方式创建软件定时器 |
| xTimerStart() | 开启软件定时器定时 |
| xTimerStop() | 停止软件定时器定时 |
| xTimerReset() | 复位软件定时器定时 |
| xTimerChangePeriod() | 更改软件定时器的定时超时时间 |
| xTimerStartFromISR() | 在中断中开启软件定时器定时 |
| xTimerStopFromISR() | 在中断中停止软件定时器定时 |
| xTimerResetFromISR() | 在中断中复位软件定时器定时 |
| xTimerChangePeriodFromISR() | 在中断中更改定时超时时间 |
1. 创建软件定时器
TimerHandle_t xTimerCreate
( const char * const pcTimerName,
const TickType_t xTimerPeriod,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction );
参数:
pcTimerName:软件定时器名称
xTimerPeriodInTicks:定时超时时间,单位:系统时钟节拍。宏 pdMS_TO_TICKS() 可用于将以毫秒为单位
指定的时间转换为以 tick 为单位指定的时间。
uxAutoReload:定时器模式, pdTRUE:周期定时器, pdFALSE:单次定时器
pvTimerID:软件定时器 ID,用于多个软件定时器公用一个超时回调函数
pxCallbackFunction:软件定时器超时回调函数
返回值:
成功:定时器handle
失败:NULL
2. 开启软件定时器
BaseType_t xTimerStart( TimerHandle_t xTimer,
TickType_t xBlockTime );
参数:
xTimer:待开启的软件定时器的句柄
xTickToWait:发送命令到软件定时器命令队列的最大等待时间
返回值:
pdPASS:开启成功
pdFAIL:开启失败
3. 停止软件定时器
BaseType_t xTimerStop( TimerHandle_t xTimer,
TickType_t xBlockTime );
参数与返回值同上。
4. 复位软件定时器
BaseType_t xTimerReset( TimerHandle_t xTimer,
TickType_t xBlockTime );
参数与返回值同上。
该功能将使软件定时器的重新开启定时,复位后的软件定时器以复位时的时刻作为开启时刻重新定时。
5. 更改软件定时器定时时间
xNewPeriod:新的定时超时时间,单位:系统时钟节拍。
其余参数与返回值同上。
实现:
创建两个定时器:
定时器1,周期定时器,每1秒打印一次 liangxu shuai
定时器2,单次定时器,启动后 2 秒打印一次 laochen shuai
cubeMX配置
software timer ope修改USE_TIMERS的值为Enable


代码实现:
/* USER CODE BEGIN Header_StartDefaultTask */
/**
* @brief Function implementing the defaultTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void const * argument)
{
/* USER CODE BEGIN StartDefaultTask */
osTimerStart(myTimer01Handle, 1000);
//xTimerChangePeriod(myTimer01Handle,pdMS_TO_TICKS(1000),0); //使用原始的时钟节拍 pdMS_TO_TICKS ,单位为毫秒
osTimerStart(myTimer02Handle, 2000);
/* Infinite loop */
for(;;)
{
osDelay(1);
}
/* USER CODE END StartDefaultTask */
}
/* Callback01 function */
void Callback01(void const * argument)
{
/* USER CODE BEGIN Callback01 */
printf("callback01\r\n");
/* USER CODE END Callback01 */
}
/* Callback02 function */
void Callback02(void const * argument)
{
/* USER CODE BEGIN Callback02 */
printf("callback02\r\n");
/* USER CODE END Callback02 */
}
十三、中断管理
中断定义
中断(Interrupt)是一种由硬件或软件触发的信号,用于通知处理器需要立即处理某个事件。中断机制允许处理器暂停当前任务,转而去执行高优先级的任务(中断服务程序,ISR),处理完成后恢复原任务。
中断优先级
任何中断的优先级都大于任务!
在我们的操作系统,中断同样是具有优先级的,并且我们也可以设置它的优先级,但是他的优先级并不是从 0~15 ,默认情况下它是从 5~15 ,0~4 这 5 个中断优先级不是 FreeRTOS 控制的(5是取决于 configMAX_SYSCALL_INTERRUPT_PRIORITY)。
相关注意
- 在中断中必需使用中断相关的函数;
- 中断服务函数运行时间越短越好。
实现
创建一个队列及一个任务,按下按键 KEY1 触发中断,在中断服务函数里向队列里发送数据,任务则阻塞接
收队列数据。
cubeMX配置


代码实现
stm32f1xx_it.c
/* USER CODE BEGIN Includes */
#include "cmsis_os.h"
/* USER CODE END Includes */
/* USER CODE BEGIN 1 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
//往队列里面放入数据
uint32_t send_data = 1;
xQueueSendFromISR(myQueue01Handle,&send_data,NULL);
}
/* USER CODE END 1 */
freetos.c
/* USER CODE BEGIN Header_StartDefaultTask */
/**
* @brief Function implementing the defaultTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void const * argument)
{
/* USER CODE BEGIN StartDefaultTask */
uint32_t rev;
/* Infinite loop */
for(;;)
{
if(xQueueReceive(myQueue01Handle, &rev, portMAX_DELAY) == pdTRUE)
printf("rev = %d \r\n",rev);
osDelay(1);
}
/* USER CODE END StartDefaultTask */
}


浙公网安备 33010602011771号