欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 八卦 > 细说STM32单片机FreeRTOS将任务通知用作计数信号量的方法及其应用实例

细说STM32单片机FreeRTOS将任务通知用作计数信号量的方法及其应用实例

2025/9/16 0:00:38 来源:https://blog.csdn.net/wenchm/article/details/148090084  浏览:    关键词:细说STM32单片机FreeRTOS将任务通知用作计数信号量的方法及其应用实例

目录

一、示例功能

二、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 */
}//省略以下的代码

2FreeRTOS初始化

        使用任务通知时,无须创建任何中间对象,所以在函数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,人数会减少。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词