1. 二值信号量简介(386.11)
什么是信号量?
- 信号量(Semaphore),是在多任务环境下使用的一种机制,是可以用来保证两个或多个关键代码段不被并
发调用。 - 信号量这个名字,我们可以把它拆分来看,信号可以起到通知信号的作用,然后我们的量还可以用来表示资
源的数量,当我们的量只有0和1的时候,它就可以被称作二值信号量,只有两个状态,当我们的那个量没有
限制的时候,它就可以被称作为计数型信号量。 - 信号量也是队列的一种。
什么是二值信号量?
- 二值信号量其实就是一个长度为1,大小为零的队列,只有0和1两种状态,通常情况下,我们用它来进行互
斥访问或任务同步。 - 互斥访问:比如门钥匙,只有获取到钥匙才可以开门
- 任务同步:比如我录完视频你才可以看视频
二值信号量相关 API 函数
- 创建二值信号量
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 。
2. 二值信号量实操(387.12)
实验需求
- 创建一个二值信号量,按下 KEY1 则释放信号量,按下 KEY2 获取信号量。
cubeMX配置(基于1.muban)
代码实现
- 代码(5.semaphore_binary_test)
myBinarySemHandle = 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(myBinarySemHandle) == pdTRUE)printf("二值信号量放入成功\r\n");elseprintf("二值信号量放入失败\r\n");}while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);}osDelay(10);}/* USER CODE END StartTaskGive */
}
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(myBinarySemHandle, 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 */
}
3. 计数型信号量简介及实操(388.13)
什么是计数型信号量?
- 计数型信号量相当于队列长度大于 1 的队列,因此计数型信号量能够容纳多个资源,这在计数型信号量被创
建的时候确定的。
计数型信号量相关 API 函数
- 计数型信号量的释放和获取与二值信号量完全相同!
SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
- 参数:
- uxMaxCount:可以达到的最大计数值
- uxInitialCount:创建信号量时分配给信号量的计数值
- 返回值:
- 成功,返回对应计数型信号量的句柄;
- 失败,返回 NULL 。
实验需求
- 创建一个计数型信号量,按下 KEY1 则释放信号量,按下 KEY2 获取信号量。
cubeMX配置(基于5.semaphore_binary_test)
- 将 Config parameters 标签里的 USE_COUNTING_SEMAPHORES 设置为 Enabled 。
代码实现
- 代码(6.semaphore_counting_test)
myCountingSemHandle = 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(myCountingSemHandle) == pdTRUE)printf("计数信号量放入成功\r\n");elseprintf("计数信号量放入失败\r\n");}while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);}osDelay(10);
}/* USER CODE END StartTaskGive */
}
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(myCountingSemHandle, 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 */
}
4. 互斥量简介(389.14)
什么是互斥量?
- 在多数情况下,互斥型信号量和二值型信号量非常相似,但是从功能上二值型信号量用于同步,而互斥型信
号量用于资源保护。
互斥型信号量和二值型信号量还有一个最大的区别,互斥型信号量可以有效解决优先级反转现象。
什么是优先级翻转?
- 以上图为例,系统中有 3 个不同优先级的任务 H/M/L,最高优先级任务 H 和最低优先级任务 L 通过信号量机
制,共享资源。目前任务 L 占有资源,锁定了信号量,Task H 运行后将被阻塞,直到 Task L 释放信号量后,
Task H 才能够退出阻塞状态继续运行。但是 Task H 在等待 Task L 释放信号量的过程中,中等优先级任务 M 抢
占了任务 L,从而延迟了信号量的释放时间,导致 Task H 阻塞了更长时间,这种现象称为优先级倒置或反
转。 - 优先级继承:当一个互斥信号量正在被一个低优先级的任务持有时, 如果此时有个高优先级的任务也尝试获
取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务会将低优先级任务的优
先级提升到与自己相同的优先级。 - 优先级继承并不能完全的消除优先级翻转的问题,它只是尽可能的降低优先级翻转带来的影响。
互斥量相关 API 函数
- 互斥信号量不能用于中断服务函数中!
SemaphoreHandle_t xSemaphoreCreateMutex( void )
- 参数:
- 无
- 返回值:
- 成功,返回对应互斥量的句柄;
- 失败,返回 NULL 。
5. 互斥量实操(390.15)
实验需求 1. 演示优先级翻转
- cubeMX配置(基于1.muban)
- 代码(7.mutex_test)
实验需求 2. 使用互斥量优化优先级翻转问题
- cubeMX配置(基于7.mutex_test)
- 代码(7.mutex_test2)
void StartTaskH(void const * argument)
{/* USER CODE BEGIN StartTaskH *//* Infinite loop */for(;;){xSemaphoreTake(myMutexHandle, portMAX_DELAY);printf("TaskH: 我开始进入厕所,发功中。。。\r\n");HAL_Delay(3000);printf("TaskH: 我上完厕所了,真舒服。。。\r\n");xSemaphoreGive(myMutexHandle);osDelay(1000);osDelay(1);}/* USER CODE END StartTaskH */
}
void StartTaskM(void const * argument)
{/* USER CODE BEGIN StartTaskM *//* Infinite loop */for(;;){printf("TaskM: 我就是为了占有CPU资源,带女朋友去兜风~~~\r\n");osDelay(1000);}/* USER CODE END StartTaskM */
}
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(1);}/* USER CODE END StartTaskL */
}
6. 事件标志组简介(391.16)
什么是事件标志组?
- 事件标志位: 表明某个事件是否发生,联想:全局变量 flag。通常按位表示,每一个位表示一个事件(高 8 位
不算) - 事件标志组 是一组事件标志位的集合, 可以简单的理解事件标志组,就是一个整数。
- 事件标志组本质是一个 16 位或 32 位无符号的数据类型 EventBits_t ,由 configUSE_16_BIT_TICKS 决定。
- 虽然使用了 32 位无符号的数据类型变量来存储事件标志, 但其中的高 8 位用作存储事件标志组的控制信息,低 24 位用作存储事件标志 ,所以说一个事件组最多可以存储 24 个事件标志!
事件标志组相关 API 函数
- 创建事件标志组
EventGroupHandle_t xEventGroupCreate( void );
- 参数:
- 无
- 返回值:
- 成功,返回对应事件标志组的句柄;
- 失败,返回 NULL 。
- 设置事件标志位
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet );
- 参数:
- xEventGroup:对应事件组句柄。
- uxBitsToSet:指定要在事件组中设置的一个或多个位的按位值。
- 返回值:
- 设置之后事件组中的事件标志位值。
- 清除事件标志位
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear );
- 参数:
- xEventGroup:对应事件组句柄。
- uxBitsToClear:指定要在事件组中清除的一个或多个位的按位值。
- 返回值:
- 清零之前事件组中事件标志位的值。
- 等待事件标志位
EventBits_t xEventGroupWaitBits(const EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToWaitFor,const BaseType_t xClearOnExit,const BaseType_t xWaitForAllBits,TickType_t xTicksToWait );
- 参数:
- xEventGroup:对应的事件标志组句柄
- uxBitsToWaitFor:指定事件组中要等待的一个或多个事件位的按位值
- xClearOnExit:pdTRUE——清除对应事件位,pdFALSE——不清除
- xWaitForAllBits:pdTRUE——所有等待事件位全为 1(逻辑与),pdFALSE——等待的事件位有一个为 1(逻辑或)
- xTicksToWait:超时
- 返回值:
- 等待的事件标志位值:等待事件标志位成功,返回等待到的事件标志位
- 其他值:等待事件标志位失败,返回事件组中的事件标志位
7. 事件标志组实操(392.17)
实验需求
- 创建一个事件标志组和两个任务( task1 和 task2),task1 检测按键,如果检测到 KEY1 和 KEY2 都按过,
则执行 task2 。
代码实现
- 基于1.muban
- 代码(8.events_test)
EventGroupHandle_t eventgroup_handle;
eventgroup_handle = xEventGroupCreate();
void StartTask1(void const * argument)
{/* USER CODE BEGIN StartTask1 *//* Infinite loop */for(;;){// 等待 KEY1 按下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);}// 等待 KEY2 按下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 StartTask1 */
}
void StartTask2(void const * argument)
{/* USER CODE BEGIN StartTask2 */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);osDelay(1);}/* USER CODE END StartTask2 */
}
8. 任务通知简介(393.18)
什么是任务通知?
- FreeRTOS 从版本 V8.2.0 开始提供任务通知这个功能,每个任务都有一个 32 位的通知值。按照 FreeRTOS
官方的说法,使用消息通知比通过二进制信号量方式解除阻塞任务快 45%, 并且更加省内存(无需创建队列)。 - 在大多数情况下,任务通知可以替代二值信号量、计数信号量、事件标志组,可以替代长度为 1 的队列(可
以保存一个 32 位整数或指针值),并且任务通知速度更快、使用的RAM更少!
任务通知值的更新方式
- FreeRTOS 提供以下几种方式发送通知给任务 :
- 发送消息给任务,如果有通知未读, 不覆盖通知值
- 发送消息给任务,直接覆盖通知值
- 发送消息给任务,设置通知值的一个或者多个位
- 发送消息给任务,递增通知值
- 通过对以上方式的合理使用,可以在一定场合下替代原本的队列、信号量、事件标志组等。
任务通知的优势和劣势
- 任务通知的优势
- 使用任务通知向任务发送事件或数据,比使用队列、事件标志组或信号量快得多。
- 使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体。
- 任务通知的劣势
- 只有任务可以等待通知,中断服务函数中不可以,因为中断没有 TCB 。
- 通知只能一对一,因为通知必须指定任务。
- 等待通知的任务可以被阻塞, 但是发送消息的任务,任何情况下都不会被阻塞等待。
- 任务通知是通过更新任务通知值来发送数据的,任务结构体中只有一个任务通知值,只能保持一个数
据。
任务通知相关 API 函数
1. 发送通知
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction );
- 参数:
- xTaskToNotify:需要接收通知的任务句柄;
- ulValue:用于更新接收任务通知值, 具体如何更新由形参 eAction 决定;
- eAction:一个枚举,代表如何使用任务通知的值;
- 返回值:
- 如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回 pdFALSE, 而其他
情况均返回 pdPASS。
- 如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回 pdFALSE, 而其他
BaseType_t xTaskNotifyAndQuery( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotifyValue );
- 参数:
- xTaskToNotify:需要接收通知的任务句柄;
- ulValue:用于更新接收任务通知值, 具体如何更新由形参 eAction 决定;
- eAction:一个枚举,代表如何使用任务通知的值;
- pulPreviousNotifyValue:对象任务的上一个任务通知值,如果为 NULL, 则不需要回传, 这个时候就等价于函数 xTaskNotify()。
- 返回值:
- 如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回 pdFALSE, 而其他
情况均返回 pdPASS。
- 如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回 pdFALSE, 而其他
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
- 参数:
- xTaskToNotify:接收通知的任务句柄, 并让其自身的任务通知值加 1。
- 返回值:
- 总是返回 pdPASS。
2. 等待通知
等待通知API函数只能用在任务,不可应用于中断中!
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:等待消息通知的最大等待时间。
9. 任务通知实操(394.19)
- 模拟二值信号量
- 基于1.muban
- 代码(9.notify_binary_test)
- 模拟计数型信号量
- 代码(9. notify_counting_test)(基于9.notify_binary_test修改)
3. 模拟事件标志组
- 代码(9. notify_events_test)(基于9.notify_counting_test修改)
- 模拟邮箱
- 代码(9. notify_queue_test)(基于9.notify_events_test修改)
10. 延时函数(395.20)
- 延时函数分类
- 相对延时:vTaskDelay
- 绝对延时:vTaskDelayUntil
- vTaskDelay 与 HAL_Delay 的区别
- vTaskDelay 作用是让任务阻塞,任务阻塞后,RTOS 系统调用其它处于就绪状态的优先级最高的任务来执
行。 - HAL_Delay 一直不停的调用获取系统时间的函数,直到指定的时间流逝然后退出,故其占用了全部 CPU 时
间。
- vTaskDelay 作用是让任务阻塞,任务阻塞后,RTOS 系统调用其它处于就绪状态的优先级最高的任务来执
11. 软件定时器简介(396.21)
什么是定时器?
- 简单可以理解为闹钟,到达指定一段时间后,就会响铃。
- 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 函数
- 创建软件定时器
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:软件定时器超时回调函数
- 返回值:
- 成功:定时器句柄
- 失败:NULL
- 开启软件定时器
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xBlockTime );
- 参数:
- xTimer:待开启的软件定时器的句柄
- xTickToWait:发送命令到软件定时器命令队列的最大等待时间
- 返回值:
- pdPASS:开启成功
- pdFAIL:开启失败
- 停止软件定时器
BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xBlockTime );
- 参数与返回值同上。
- 复位软件定时器
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xBlockTime );
- 参数与返回值同上。
- 该功能将使软件定时器的重新开启定时,复位后的软件定时器以复位时的时刻作为开启时刻重新定时。
- 更改软件定时器定时时间
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer, TickType_t xNewPeriod, TickType_t xBlockTime );
- xNewPeriod:新的定时超时时间,单位:系统时钟节拍。
- 其余参数与返回值同上。
12. 软件定时器实操(397.22)
实验需求
- 创建两个定时器:
- 定时器1,周期定时器,每1 秒打印一次 Jessie shuai
- 定时器2,单次定时器,启动后 2 秒打印一次 Hi,Jessie!
cubeMX配置
- 基于1.muban修改
- 代码(10. timer_test)
13. 中断管理(398.23)
中断定义
- 请参考 51 及 STM32 中断相关课程。
中断优先级
- 任何中断的优先级都大于任务!
- 在我们的操作系统,中断同样是具有优先级的,并且我们也可以设置它的优先级,但是他的优先级并不是从
0~15 ,默认情况下它是从 5~15 ,0~4 这 5 个中断优先级不是 FreeRTOS 控制的(5是取决于
configMAX_SYSCALL_INTERRUPT_PRIORITY)。
相关注意
- 在中断中必需使用中断相关的函数;
- 中断服务函数运行时间越短越好。
实验需求
- 创建一个队列及一个任务,按下按键 KEY1 触发中断,在中断服务函数里向队列里发送数据,任务则阻塞接
收队列数据。
cubeMX配置
代码实现
- stm32f1xx_it.c
#include "cmsis_os.h"
extern osMessageQId myQueue01Handle;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{uint32_t snd = 6;xQueueSendFromISR(myQueue01Handle, &snd, NULL);
}
- freertos.c
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 */
}