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

目录

一、示例功能

二、CubeMX项目设置

[1、RCC、SYS、Code Gennerator、USART3、TIM6](#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如下代码,完成一系列初始化:

cpp 复制代码
/* 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 */
}

//省略以下的代码

2 FreeRTOS初始化

使用任务通知时,无须创建任何中间对象,所以在函数MX_FREERTOS_Init()里只需创建任务。文件freertos.c中的初始代码如下:

自动生成includes,手动添加私有includes:

cpp 复制代码
/* 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 */

自动生成任务函数定义的代码、自动生成函数原型的代码:

cpp 复制代码
/* 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初始化,并在初始化函数里创建任务函数:

cpp 复制代码
/**
  * @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的任务函数代码如下:

cpp 复制代码
/* 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,人数会减少。

相关推荐
眰恦ゞLYF20 小时前
嵌入式硬件——IMX6ULL时钟配置
单片机·嵌入式硬件·时钟·imx6ull
小莞尔20 小时前
【51单片机】【protues仿真】基于51单片机秒表系统(LCD1602多功能、可保持30条记录)
c语言·stm32·单片机·嵌入式硬件·51单片机
___波子 Pro Max.21 小时前
Linux与STM32实时性与系统资源解析
linux·stm32
Tolines21 小时前
PCIe外接卡标准尺寸
嵌入式硬件·硬件工程·设计规范
寅双木21 小时前
常见的九种二极管
笔记·嵌入式硬件·稳压二极管·tvs·肖特基二极管·发光二极管·齐纳击穿
Black doncky prince21 小时前
QR反激电源副边整流二极管电压波形分析
单片机·嵌入式硬件·硬件工程
currycheng621 小时前
开关电源测试及方法
单片机·嵌入式硬件·硬件架构·硬件工程
SundayBear1 天前
基于MCU的文件系统
linux·服务器·单片机
自激振荡器1 天前
1,LVGL(V8.3.10版本)裸机移植教程
stm32·lvgl·移植·裸机开发
DIY机器人工房1 天前
关于解决 libwebsockets 库编译时遇到的问题的方法:
服务器·stm32·单片机·嵌入式硬件·tcp