目录
一、示例功能
二、CubeMX项目设置
1、RCC、SYS、Code Gennerator、USART3、TIM6
2、RTC
3、FreeRTOS
4、GPIO
5、NVIC
三、程序功能实现
1、主程序
2、FreeRTOS初始化
3、任务通知的发送与接收
4、运行测试
任务通知还可以当作二值信号量或计数信号量来使用:使用函数xTaskNotifyGive()发送通知,使接收者的通知值加1;使用函数ulTaskNotifyTake()读取通知,使接收者的通知值减1或清零。
一、示例功能
与计数信号量的工作原理相比,任务通知模拟的计数信号量与实际的计数信号量有细微的差别:实际的计数信号量的初始值不为零,一般用于表示可用资源的个数,例如,餐厅中空余的餐桌个数。而任务通知模拟的计数信号量的初值为0,一般用于表示待处理的事件的个数,例如,模拟进入餐厅的排队人数。
本示例使用任务通知模拟计数信号量,表示餐厅外排队的人数变化。示例的功能和运行流程如下:
- 在FreeRTOS中,创建一个任务Task_CheckIn,其通知值表示当前在排队的人数。
- 在任务Task_CheckIn中连续检测KeyRight键,当KeyRight键按下时,执行函数ulTaskNotifyTake()使通知值减1,表示允许1人进店,使排队人数减1。
- 设置RTC唤醒周期为2s,在唤醒中断里调用vTaskNotifyGiveFromISR()向任务Task_CheckIn发送通知,使其通知值加1,表示又来1人加入排队的队伍。
二、CubeMX项目设置
本示例要用到开发板上的按键和LED。在GPIO设置中,引用KEYLED文件夹,保留KeyRight和LED1的设置。
继续使用旺宝红龙开发板STM32F407ZGT6 KIT V1.0。一些设置可以参考本文作者写的其他文章:
细说STM32单片机FreeRTOS任务通知及其应用实例-CSDN博客 https://wenchm.blog.csdn.net/article/details/148030468?spm=1011.2415.3001.5331
1、RCC、SYS、Code Gennerator、USART3、TIM6
该部分的设置可以参考本文作者发布的其他文章。
设置TIM6作为基础时钟源。
2、RTC
开启LSE和RTC,并在时钟树上设置LSE作为RTC的时钟源。开启RTC的唤醒功能,设置唤醒周期为2s。开启RTC周期唤醒全局中断,在NVIC中设置其优先级为5,因为在其ISR里要用到FreeRTOS的API函数。
3、FreeRTOS
启用FreeRTOS,设置接口为CMSIS_V2,所有“config”和“INCLUDE_”参数保持默认值。创建一个任务Task_CheckIn。
4、GPIO
5、NVIC
三、程序功能实现
1、主程序
完成设置后,我们在CubeMX中生成代码。我们在CubeIDE中打开项目,将KEY_LED添加到项目搜索路径。添加用户功能代码后,主程序代码如下:
自动生成main.c如下代码,完成一系列初始化:
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "cmsis_os.h"
#include "rtc.h"
#include "usart.h"
#include "gpio.h"/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void MX_FREERTOS_Init(void);/*** @brief The application entry point.* @retval int*/
int main(void)
{/* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* Configure the system clock */SystemClock_Config();/* Initialize all configured peripherals */MX_GPIO_Init();MX_USART3_UART_Init();MX_RTC_Init();/* USER CODE BEGIN 2 */// Start Menuuint8_t startstr[] = "Demo8_2:Task Notification.\r\n";HAL_UART_Transmit(&huart3,startstr,sizeof(startstr),0xFFFF);uint8_t startstr1[] = "Simulating people in wait.\r\n";HAL_UART_Transmit(&huart3,startstr1,sizeof(startstr1),0xFFFF);uint8_t startstr2[] = "1. People++ each 2sec.\r\n";HAL_UART_Transmit(&huart3,startstr2,sizeof(startstr2),0xFFFF);uint8_t startstr3[] = "2. Press KeyRight to People--.\r\n\r\n";HAL_UART_Transmit(&huart3,startstr3,sizeof(startstr3),0xFFFF);/* USER CODE END 2 *//* Init scheduler */osKernelInitialize();/* Call init function for freertos objects (in cmsis_os2.c) */MX_FREERTOS_Init();/* Start scheduler */osKernelStart();/* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}//省略以下的代码
2、FreeRTOS初始化
使用任务通知时,无须创建任何中间对象,所以在函数MX_FREERTOS_Init()里只需创建任务。文件freertos.c中的初始代码如下:
自动生成includes,手动添加私有includes:
/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include "usart.h"
/* USER CODE END Includes */
自动生成任务函数定义的代码、自动生成函数原型的代码:
/* Definitions for Task_CheckIn */
osThreadId_t Task_CheckInHandle;
const osThreadAttr_t Task_CheckIn_attributes = {.name = "Task_CheckIn",.stack_size = 128 * 4,.priority = (osPriority_t) osPriorityNormal,
};/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes *//* USER CODE END FunctionPrototypes */void AppTask_CheckIn(void *argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
自动完成RTOS初始化,并在初始化函数里创建任务函数:
/*** @brief FreeRTOS initialization* @param None* @retval None*/
void MX_FREERTOS_Init(void)
{/* Create the thread(s) *//* creation of Task_CheckIn */Task_CheckInHandle = osThreadNew(AppTask_CheckIn, NULL, &Task_CheckIn_attributes);
}
3、任务通知的发送与接收
在RTC的唤醒中断里向任务Task_CheckIn发送任务通知。RTC唤醒事件的回调函数是HAL_RTCEx_WakeUpTimerEventCallback(),直接在文件freertos.c中重新实现这个函数。这个回调函数以及任务Task_CheckIn的任务函数代码如下:
/* USER CODE BEGIN Header_AppTask_CheckIn */
/*** @brief Function implementing the Task_CheckIn thread.* @param argument: Not used* @retval None*/
/* USER CODE END Header_AppTask_CheckIn */
void AppTask_CheckIn(void *argument)
{/* USER CODE BEGIN AppTask_CheckIn *//* Infinite loop */for(;;){KEYS curKey=ScanPressedKey(20);if (curKey==KEY_RIGHT) //KeyRight pressed{BaseType_t clearOnExit=pdFALSE; //退出时通知值减1// 只是在通知值为0时才进入阻塞状态,所以可以多次读取通知值,每次使通知值减1BaseType_t preCount=ulTaskNotifyTake(clearOnExit, portMAX_DELAY);// BaseType_t preCount=ulTaskNotifyTake(clearOnExit, pdMS_TO_TICKS(500));printf("People in waiting= %ld\r\n",preCount-1); // preCount是前一次的通知值vTaskDelay(pdMS_TO_TICKS(300)); //延时,消除按键抖动影响}elsevTaskDelay(pdMS_TO_TICKS(5));}/* USER CODE END AppTask_CheckIn */
}/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
/* RTC周期唤醒中断回调函数 */
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{LED1_Toggle(); //LED1闪烁BaseType_t taskWoken=pdFALSE;vTaskNotifyGiveFromISR(Task_CheckInHandle,&taskWoken); //发送通知,通知值加1portYIELD_FROM_ISR(taskWoken); //必须执行这条语句,申请任务调度
}int __io_putchar(int ch)
{HAL_UART_Transmit(&huart3,(uint8_t*)&ch,1,0xFFFF);return ch;
}
/* USER CODE END Application */
RTC唤醒中断回调函数的代码功能就是执行函数VTaskNotifyGiveFromISR()向任务Task_CheckIn发送通知,使其通知值加1,模拟又有1人加入排队。
在任务Task_CheckIn里,按键KeyRight的状态会得到不断检测。当KeyRight按下时,表示餐厅有空位,调用函数ulTaskNotifyTake()读取任务通知,使通知值减1,相当于从排队的人群里出来1人进入餐厅用餐。函数ulTaskNotifyTake()的执行有如下两个特点。
- 如果当前通知值大于0,执行ulTaskNotifyTake()时不会进入阻塞状态,而是立刻返回。所以,如果当前通知值为5,可以多次按KeyRight键,即使没有新的任务通知到达,也可以看到排队人数在减少。
- 函数ulTaskNotifyTake()返回的是数值减1或清零之前的通知值,所以在程序中,如果要显示当前的排队人数,显示的值是preCount-1。
4、运行测试
构建项目后,将其下载到开发板并运行测试,可以看到LED1闪烁,这说明RTC唤醒中断的回调函数在运行,每2s发送一次任务通知。按下KeyRight键时,串口助手上显示当前排队人数,连续按KeyRight键时,会使排队人数减少,直到减少为0,任务Task_CheckIn就会进入阻塞等待状态。
除了函数ulTaskNotifyTake()和xTaskNotifyWait(),没有其他函数能读取任务的当前通知值,所以在这个示例程序中,不能实时显示排队人数,只有在按下KeyRight键执行一次ulTaskNotifyTake()函数后,才会显示当前排队人数。
任务通知还可以当作二值信号量和事件组使用。如果当作二值信号量使用,就是在执行函数ulTaskNotifyTake(xClearCountOnExit,xTicksToWait)时,将参数xClearCountOnExit设置为pdTRUE,使得读取之后,通知值归零。如果当作事件组使用,就是在调用xTaskNotify(xTaskToNotify,ulValue,eAction)时,将eAction设置为eSetBits,修改通知值的某些位,将通知值当作事件组变量来使用。
首次下载或复位后,立刻按下S5键,串口助手显示等待人数0,如果慢了一会按下S5,等待的人数逐渐增加,每2S增加1人。按下S5,人数会减少。