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

发布于:2025-05-22 ⋅ 阅读:(19) ⋅ 点赞:(0)

目录

一、示例功能

二、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 Menu
  uint8_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时才进入阻塞状态,所以可以多次读取通知值,每次使通知值减1
			BaseType_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));  					//延时,消除按键抖动影响
		}
		else
			vTaskDelay(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);  //发送通知,通知值加1
	portYIELD_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,人数会减少。


网站公告

今日签到

点亮在社区的每一天
去签到