一、初识FreeRTOS
1、为何学习?
在裸机系统中,所有操作都是在一个无限的大循环中实现,但是对着产品要实现的功能越来越多,单纯的裸机系统已经不能完美滴解决问题,反而会使编程变得更加复杂。
2、FreeROTS
- RTOS的全称是 RealTime Operating System,实时操作系统
-
RTOS 的任务调度器被设计为可预测的,而这正是嵌入式实时操作系统所需要的,实时环境中要求操作系统必须对某一个事件做出实时的响应,因此系统任务调度器的行为必须是可预测的。
-
FreeRTOS操作系统是由用户给每个任务分配一个任务优先级,任务调度器就可以根据此优先级来决定下一刻应该运行哪个4.FreeRTOS 是一个可裁剪的小型 RTOS 系统,其特点包括:● FreeRTOS 的内核支持抢占式,合作式和时间片调度。● SafeRTOS 衍生自 FreeRTOS,SafeRTOS 在代码完整性上相比 FreeRTOS更胜一筹。● 提供了一个用于低功耗的 Tickless 模式。● 系统的组件在创建时可以选择动态或者静态的 RAM,比如任务、消息队列、信号量、软件定时器等等。● 已经在超过 30 种架构的芯片上进行了移植。● FreeRTOS-MPU 支持 Corex-M 系列中的 MPU 单元,如 STM32F407。● FreeRTOS 系统简单、小巧、易用,通常情况下内核占用 4k-9k 字节的空间。● 高可移植性,代码主要 C 语言编写。● 支持实时任务和协程(co-routines 也有称为合作式、协同程序,本教程均成为协程)。● 任务与任务、任务与中断之间可以使用任务通知、消息队列、二值信号量、数值型信号量、递归互斥信号量和互斥信号量进行通信和同步。● 创新的事件组(或者事件标志)。● 具有优先级继承特性的互斥信号量。● 高效的软件定时器。● 强大的跟踪执行功能。● 堆栈溢出检测功能。● 任务数量不限。
3、FreeRTOS源码下载
4、FreeRTOS文件介绍
二、CubeMX 中配置FreeRTOS相关介绍
具体关于CubeMX中怎么配置FreeRTOS的详细教程见:
三、FreeRTOS启动流程
3.1、任务创建
在系统上电后第一个执行的是启动文件里面的由汇编编写的复位函数Reset_Handler
复位函数的最后会调用C库函数_main。_main函数的主要工作是初始化系统的堆和栈,最后调用C中的main函数。在main()函数中,可以直接进行创建任务操作,FreeRTOS会自动帮我们做初始化的事情,如初始化堆内存。
3.2、任务调度
在创建完任务后,需要开启调度器。调度器中实现任务的真正调度,空闲任务的实现,定时器任务的实现。
在Cortex-M3 架构中,FreeRTOS为了任务启动和任务切换使用了三个异常:SVC、PendSV、SYSTick:
SVC(系统服务调用,亦简称系统调用)用于任务启动,有些操作系统不允许应用程序直接访问硬件,而是通过提供一些系统服务函数,用户程序使用 SVC 发出对系统服务函数的呼叫请求,以这种方法调用它们来间接访问硬件,它就会 产生一个 SVC 异常。
PendSV(可挂起系统调用)用于完成任务切换,它是可以像普通的中断一样被挂起的,它的最大特性是如果当前有优先级比它高的中断在运行,PendSV 会延迟执行,直到高优先级中断执行完毕,这样子产生的 PendSV 中断就不会打断 其他中断的运行。
这里将PendSV和SYSTick异常优先级设置为最低,这样任务切换不会打断某个中断服务程序,中断服务程序也不会被延迟,简化了设计,有利于系统稳定。
3.3、主函数
main函数里面只是创建并启动一些任务或硬件初始化
在临界区创建任务,任务只能在退出临界区的时候才执行最高优先级任务
在启动任务调度器的时候,假如启动成功的话,任务就不会有返回了, 假如启动没有成功,则通过LR寄存器指定的地址退出,在创建任务的时候,任务栈对应的LR寄存器指向是任务退出函数prvTaskExitError(),该函数里面是一个死循环。
四、消息队列
4.1、消息队列简介
队列是为了任务与任务、任务与中断之间的通信而准备的,可以存储有限的、大小固定的数据项目。
通常队里采用先进先出(FIFO)的存储缓冲机制,也就是往队列发送数据时候(入队)永远是发送到队列的尾部,而从队里提取数据的时候(出队)是从队列的头部提取的。
数据发送到队列中发导致数据拷贝,也就是将要发送到数据拷贝到队列中,即队列中存储的是数据的原始值,而不是数据的引用(即只传递数据的指针),这个也叫值传递。
出队阻塞:
当任务尝试从一个队列中读取消息的时候可以指定一个阻塞时间,这个阻塞时间就是当任务从队列中读取消息无效的时候任务阻塞的时间,阻塞时间在0~portMAX_DELAY之间。
入队阻塞:
入队是向队列中发送消息,将消息加入到队列中。和出队阻塞一样,当一个任务向队列发送消息的话也可以设置阻塞时间。
4.2、CubeMX创建工程(详细见可参考第5章)
在usart.c中添加printf重映射函数,包含头文件
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */
…………
/* USER CODE BEGIN 1 */
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0xffff);
return ch;
}
/* USER CODE END 1 */
添加按键文件key.c和key.h
#ifndef __KEY_H
#define __KEY_H
#include "main.h"
#define KEY3 HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin)
#define KEY4 HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin)
#define KEY3_PRES 2
#define KEY4_PRES 3
uint8_t key_scan(uint8_t mode);
#endif
#include "key.h"
uint8_t key_scan(uint8_t mode)
{
static uint8_t key_up = 1; //按键松开标志
uint8_t keyval = 0;
if(mode == 1) key_up = 1; //支持连按
if(key_up &&(KEY3 == 0 ||KEY4 == 0))
{
HAL_Delay(10);
key_up = 0;
if(KEY3 == 0)
keyval = KEY3_PRES;
else if(KEY4 == 0)
keyval = KEY4_PRES;
}
else if(KEY3 == 1 && KEY4 == 1)
key_up = 1;
return keyval;
}
在freertos.c中添加以下代码:
/* USER CODE BEGIN Includes */
#include "key.h"
/* USER CODE END Includes */
…………
/* USER CODE BEGIN PD */
#define QUEUE_LEN 4 //队列的长度,最大可包含多少个消息
#define QUEUE_SIZE 4 //队列中每个消息大小(字节)
/* USER CODE END PD */
…………
/* USER CODE BEGIN Variables */
QueueHandle_t Test_Queue = NULL; //新建一个消息队列句柄
/* USER CODE END Variables */
…………
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
Test_Queue = xQueueCreate((UBaseType_t) QUEUE_LEN, (UBaseType_t) QUEUE_SIZE); //创建一个消息队列
/* USER CODE END RTOS_QUEUES */
…………
/* USER CODE END Header_StartTaskled1 */
void StartTaskled1(void const * argument)
{
/* USER CODE BEGIN StartTaskled1 */
/* Infinite loop */
for(;;)
{
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
osDelay(800);
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
osDelay(200);
}
/* USER CODE END StartTaskled1 */
}
…………
void StartTaskReceive(void const * argument)
{
/* USER CODE BEGIN StartTaskReceive */
BaseType_t xReturn = pdTRUE; /* 定义一个创建信息返回值,默认为pdTRUE */
uint32_t r_queue; /* 定义一个接收消息的变量 */
/* Infinite loop */
for(;;)
{
xReturn = xQueueReceive(Test_Queue, &r_queue, portMAX_DELAY); /* 消息队列的句柄 */
if(pdTRUE == xReturn)
printf("本次接收到的数据是%d\r\n",r_queue);
else
printf("数据接收出错,错误代码0x%lx\n",xReturn);
osDelay(1);
}
/* USER CODE END StartTaskReceive */
}
…………
void StartTaskSend(void const * argument)
{
/* USER CODE BEGIN StartTaskSend */
BaseType_t xReturn = pdPASS; /* 定义一个创建信息返回值,默认为pdPASS */
uint32_t send_data1 = 1;
uint32_t send_data2 = 2;
uint8_t key = 0;
/* Infinite loop */
for(;;)
{
key = key_scan(0);
if(key == KEY3_PRES)
{
printf("发送消息send_data1!\n");
xReturn = xQueueSend(Test_Queue, &send_data1, 0);
if(pdPASS == xReturn)
printf("消息send_data1发送成功!\n\n");
}
else if (key == KEY4_PRES)
{
printf("发送消息send_data2!\n");
xReturn = xQueueSend(Test_Queue, &send_data2, 0);
if(pdPASS == xReturn)
printf("消息send_data2发送成功!\n\n");
}
osDelay(20);
}
/* USER CODE END StartTaskSend */
}
将工程编译烧录后,可以看到LED1指示灯不断闪烁,表示程序正常运行。按下KEY3按键发送消息1,在串口助手中可以看到接收到消息1;按下KEY4按键发送消息2,在串口助手中可以看到接受到消息2,如下所示:
五、信号量
5.1、信号量简介
信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务之间同步或临界资源的互斥访问,常用于协助一组相互竞争的任务来访问临界资源。
信号量一般用来进行资源管理和任务同步,FreeRTOS中信号量又分为二值信号量、计数型信号量、互斥信号量和递归信号量。
5.1.1 二值信号量
二值信号量即可以用于临界资源访问也可以用于同步功能。
用作同步时,信号量再创建后应被置空,任务1获取信号量而进入阻塞,任务2在某种条件发生后,释放信号量,于是任务1获得信号量得以进入就绪态,如果此时任务1 的优先级是最高的,那么会立即切换任务,从而达到了两个任务间的同步。
可以将二值信号量看作只有一个消息的队列,因此这个队列的长度是1,队列只能为空或满,在运用的时候只需知道队列中是否有消息即可,不需关注消息内容。
二值信号量获取,任何任务都可以从创建的二值信号量资源中获取一个而知信号量,获取成功则返回正确,否则任务会根据用户指定的阻塞超时时间来等待其他任务/中断释放信号量。在等待这段时间,系统将任务变成阻塞态,任务将被挂到该信号量的阻塞等待列表中。
5.1.2计数信号量
计数信号量在实际应用中,常将其用于事件计数和资源管理。
每当某个事件发生时,任务或者中断将释放一个信号量(信号量计数值加1),当处理事件时(一般在任务中处理),处理任务将会取走该信号量(信号量计数值减1),信号量的计数值则表示还有多少个事件没被处理。
用于资源管理时,允许多个任务获取信号量访问共享资源但会限制任务的最大数目,并且任务必须先获取到信号量才能获取资源访问权,当信号量的计数值为零时表示系统没有可用的资源;访问的任务数达到可支持的最大数目时,会阻塞其他试图获取该信号量的任务,直到有任务释放了信号量。所以在使用完资源后必须归还信号量,否则计数值为0是任务就无法访问该资源了。
5.2、常用信号量API函数
- xSemaphoreCreateBinary():创建二值信号量
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateBinary()
xQueueGenericCreate( ( UBaseType_t ) 1, (1)
semSEMAPHORE_QUEUE_ITEM_LENGTH, (2)
queueQUEUE_TYPE_BINARY_SEMAPHORE ) (3)
#endif
- xSemaphoreCreateCounting():创建计数信号量
- vSemaphoreDelete():信号量删除函数
- xSemaphoreGive():释放信号量
#define xSemaphoreGive( xSemaphore )
xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ),NULL,semGIVE_BLOCK_TIME,queueSEND_TO_BACK )
- xSemaphoreGiveFromISR():释放一个信号量,带中断保护
#define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken )
xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ),( pxHigherPriorityTaskWoken ) )
- xSemaphoreTake():获取信号量
- xSemaphoreTakeFromISR():获取信号量,带中断保护
5.3、硬件设计所使用到的资源:
5.4、CubeMX工程创建
5.4.1二值信号量
实现的功能:创建3个任务:LED、二值信号量释放、二值信号量获取任务。LED任务控制LED1指示灯闪烁,KEY3和KEY4键控制二值信号量释放,获取二值信号量成功,LED1灯状态翻转,同时通过串口输出提示信息。
在usart.c中添加printf重映射函数,在main.h中包含头文件#include <stdio.h>
/*main.h文件中*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */
…………
/*usart.c文件中*/
/* USER CODE BEGIN 1 */
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0xffff);
return ch;
}
/* USER CODE END 1 */
添加按键文件key.c和key.h
#ifndef __KEY_H
#define __KEY_H
#include "main.h"
#define KEY3 HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin)
#define KEY4 HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin)
#define KEY3_PRES 2
#define KEY4_PRES 3
uint8_t key_scan(uint8_t mode);
#endif
#include "key.h"
uint8_t key_scan(uint8_t mode)
{
static uint8_t key_up = 1; //按键松开标志
uint8_t keyval = 0;
if(mode == 1) key_up = 1; //支持连按
if(key_up &&(KEY3 == 0 ||KEY4 == 0))
{
HAL_Delay(10);
key_up = 0;
if(KEY3 == 0)
keyval = KEY3_PRES;
else if(KEY4 == 0)
keyval = KEY4_PRES;
}
else if(KEY3 == 1 && KEY4 == 1)
key_up = 1;
return keyval;
}
在freertos.c中添加以下代码:
/* USER CODE BEGIN Includes */
#include "key.h"
/* USER CODE END Includes */
…………
/* USER CODE BEGIN Variables */
SemaphoreHandle_t BinarySem_Handle =NULL; //新建一个信号量句柄
/* USER CODE END Variables */
…………
/* USER CODE BEGIN RTOS_SEMAPHORES */
/* add semaphores, ... */
BinarySem_Handle = xSemaphoreCreateBinary(); //创建信号量
/* USER CODE END RTOS_SEMAPHORES */
…………
/* USER CODE END Header_StartTaskled1 */
void StartTaskled1(void const * argument)
{
/* USER CODE BEGIN StartTaskled1 */
/* Infinite loop */
for(;;)
{
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
osDelay(800);
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
osDelay(200);
}
/* USER CODE END StartTaskled1 */
}
…………
void StartTaskReceive(void const * argument)
{
/* USER CODE BEGIN StartTaskReceive */
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
/* Infinite loop */
for(;;)
{
xReturn = xSemaphoreTake(BinarySem_Handle,/* 二值信号量句柄 */
portMAX_DELAY); /* 等待时间 */
if(pdTRUE == xReturn)
printf("BinarySem_Handle 二值信号量获取成功!\n\n");
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
osDelay(1);
}
/* USER CODE END StartTaskReceive */
}
…………
/* USER CODE END Header_StartTaskSend */
void StartTaskSend(void const * argument)
{
/* USER CODE BEGIN StartTaskSend */
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
uint8_t key=0;
/* Infinite loop */
for(;;)
{
key = key_scan(0);
if(key== KEY3_PRES)
{
xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量
if( xReturn == pdTRUE )
printf("BinarySem_Handle 二值信号量释放成功!\r\n");
else
printf("BinarySem_Handle 二值信号量释放失败!\r\n");
}
else if(key==KEY4_PRES)
{
xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量
if( xReturn == pdTRUE )
printf("BinarySem_Handle 二值信号量释放成功!\r\n");
else
printf("BinarySem_Handle 二值信号量释放失败!\r\n");
}
osDelay(20);
}
/* USER CODE END StartTaskSend */
}
5.4.2计数信号量
前面的步骤都和4.1二值信号量工程一样,但是注意要使能USE_COUNTING_SEMAPHORES
同时注意这里的两个任务的优先级应该设为一致,不然就得将高优先级的阻塞时间设置的更长一点 (这样虽然能进入低优先级的任务,但是还是可能按键按下的时候不在高优先级的阻塞时间内)
freertos.c中添加以下代码:
/* USER CODE BEGIN Includes */
#include "key.h"
/* USER CODE END Includes */
…………
/* USER CODE BEGIN Variables */
SemaphoreHandle_t CountSem_Handle =NULL;
/* USER CODE END Variables */
…………
/* add semaphores, ... */
CountSem_Handle = xSemaphoreCreateCounting(5,5); //创建 CountSem ,5个信号量
/* USER CODE END RTOS_SEMAPHORES */
…………
/* USER CODE END Header_StartTaskled1 */
void StartTaskled1(void const * argument)
{
/* USER CODE BEGIN StartTaskled1 */
/* Infinite loop */
for(;;)
{
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
osDelay(200);
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
osDelay(800);
}
/* USER CODE END StartTaskled1 */
}
…………
/* USER CODE END Header_StartTaskReceve */
void StartTaskReceve(void const * argument)
{
/* USER CODE BEGIN StartTaskReceve */
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
uint8_t key=0;
/* Infinite loop */
for(;;)
{
key = key_scan(0);
if(key==KEY3_PRES)
{
xReturn = xSemaphoreGive( CountSem_Handle );//释放计数信号量
if( xReturn == pdTRUE )
printf( "KEY3 被按下,释放 1 个停车位。\r\n" );
else
printf( "KEY3 被按下,但已无车位可以释放!\r\n" );
}
osDelay(20);
}
/* USER CODE END StartTaskReceve */
}
…………
/* USER CODE END Header_StartTaskSend */
void StartTaskSend(void const * argument)
{
/* USER CODE BEGIN StartTaskSend */
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
uint8_t key=0;
/* Infinite loop */
for(;;)
{
key = key_scan(0);
if(key==KEY4_PRES)
{
xReturn = xSemaphoreTake( CountSem_Handle,0 );//获取计数信号量
if( xReturn == pdTRUE )
printf("KEY4 被按下,成功申请到停车位。\r\n");
else
printf("KEY4 被按下,不好意思,现在停车场已满!\r\n");
}
osDelay(20);
}
/* USER CODE END StartTaskSend */
}
main.c文件中添加以下代码:
/* USER CODE BEGIN 2 */
printf("FreeRTOS 计数信号量实验\r\n");
printf("车位默认值为 5 个,按下 KEY4 申请车位,按下 KEY3 释放车位\r\n");
/* USER CODE END 2 */
结果如下:
还有就是在Cubemx中也可以直接创建计数信号量
现象:
六、互斥量
6.1、互斥量简介
互斥信号量其实就是一个拥有优先级继承的二值信号量。互斥量适用于需要互斥访问的应用中。优先级继承尽可能的降低了高优先级任务处于阻塞态的时间,并将出现的“优先级翻转”的影响降到最低。
优先级继承算法是指,暂时提高某个占有某种资源的低优先级任务的优先级,使之在所有等待该资源的任务中优先级最高那个任务的优先级相等,而当这个低优先级任务执行完毕释放该资源时,优先级重新回到初始设定值。
FreeRTOS的优先级继承机制不能解决优先级反转,只能将这种情况的影响降低到最小,硬实时系统在一开始设计时就要避免优先级反转发生。
互斥量的使用比较单一,是以锁的形式存在。在初始化的时候,互斥量处于开锁的状态,而被任务持有的时候则立刻转为闭锁状态。
互斥量不能在中断服务函数中使用,因其特有的优先级继承机制只在任务起作用,在中断上下文环境毫无意义。
6.2、常用互斥量API函数
- 互斥量创建函数xSemaphoreCreateMutex()
- 递归互斥量创建函数 xSemaphoreCreateRecursiveMutex()
- 互斥量删除函数 vSemaphoreDelete()
- 互斥量获取函数 xSemaphoreTake()
- 递归互斥量获取函数 xSemaphoreTakeRecursive()
- 互斥量释放函数 xSemaphoreGive()
- 递归互斥量释放函数 xSemaphoreGiveRecursive()
6.3、CubeMX工程创建
6.3.1、模拟优先级反转
实现功能是:创建3个任务和1个二值信号量,任务分别是高优先级任务,中优先级任务,低优先级任务。低优先级任务在获取信号量时被中优先级打断,中优先级的任务执行时间较长,因为低优先级还未释放信号量,高优先级任务无法获取信号量来运行。
工程创建部分参考第5章节,freertos.c中代码如下:
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
SemaphoreHandle_t BinarySem_Handle =NULL;
/* USER CODE END Init */
/* USER CODE BEGIN RTOS_MUTEX */
/* add mutexes, ... */
/* USER CODE END RTOS_MUTEX */
/* USER CODE BEGIN RTOS_SEMAPHORES */
/* add semaphores, ... */
/* 创建 BinarySem */
BinarySem_Handle = xSemaphoreCreateBinary();
/* USER CODE END RTOS_SEMAPHORES */
/* USER CODE BEGIN RTOS_TIMERS */
/* start timers, add new ones, ... */
/* USER CODE END RTOS_TIMERS */
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
/* USER CODE END RTOS_QUEUES */
/* Create the thread(s) */
/* definition and creation of defaultTask */
osThreadDef(defaultTask, StartDefaultTask, osPriorityIdle, 0, 128);
defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);
/* definition and creation of myTaskled1 */
osThreadDef(myTaskled1, StartTaskled1, osPriorityLow, 0, 128);
myTaskled1Handle = osThreadCreate(osThread(myTaskled1), NULL);
/* definition and creation of Task_Lopriority */
osThreadDef(Task_Lopriority, Task_Lowpriority, osPriorityLow, 0, 128);
Task_LopriorityHandle = osThreadCreate(osThread(Task_Lopriority), NULL);
/* definition and creation of Task_Mpriority */
osThreadDef(Task_Mpriority, Task_Midpriority, osPriorityNormal, 0, 128);
Task_MpriorityHandle = osThreadCreate(osThread(Task_Mpriority), NULL);
/* definition and creation of Task_Hipriority */
osThreadDef(Task_Hipriority, Task_Highpriority, osPriorityHigh, 0, 128);
Task_HipriorityHandle = osThreadCreate(osThread(Task_Hipriority), NULL);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* USER CODE END RTOS_THREADS */
}
/* USER CODE BEGIN Header_StartDefaultTask */
/**
* @brief Function implementing the defaultTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void const * argument)
{
/* USER CODE BEGIN StartDefaultTask */
/* Infinite loop */
for(;;)
{
osDelay(1);
}
/* USER CODE END StartDefaultTask */
}
/* USER CODE BEGIN Header_StartTaskled1 */
/**
* @brief Function implementing the myTaskled1 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskled1 */
void StartTaskled1(void const * argument)
{
/* USER CODE BEGIN StartTaskled1 */
/* Infinite loop */
for(;;)
{
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
osDelay(200);
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
osDelay(800);
}
/* USER CODE END StartTaskled1 */
}
/* USER CODE BEGIN Header_Task_Lowpriority */
/**
* @brief Function implementing the Task_Lopriority thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Task_Lowpriority */
void Task_Lowpriority(void const * argument)
{
/* USER CODE BEGIN Task_Lowpriority */
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
static uint32_t i=0;
/* Infinite loop */
for(;;)
{
printf("LowPriority_Task 获取互斥量\n");
//获取互斥量 MuxSem,没获取到则一直等待
xReturn = xSemaphoreTake(BinarySem_Handle,portMAX_DELAY); /* 等待时间 */
if( xReturn == pdTRUE )
printf("LowPriority_Task Running\n");
for(i=0;i<2000000;i++)//模拟低优先级任务占用互斥量
{
taskYIELD();//发起任务调度
}
printf("LowPriority_Task 释放互斥量!\r\n\n");
xReturn = xSemaphoreGive(BinarySem_Handle);//给出互斥量
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
osDelay(500);
}
/* USER CODE END Task_Lowpriority */
}
/* USER CODE BEGIN Header_Task_Midpriority */
/**
* @brief Function implementing the Task_Mpriority thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Task_Midpriority */
void Task_Midpriority(void const * argument)
{
/* USER CODE BEGIN Task_Midpriority */
for(;;)
{
printf("MidPriority_Task Running\n");
osDelay(500);
}
/* USER CODE END Task_Midpriority */
}
/* USER CODE BEGIN Header_Task_Highpriority */
/**
* @brief Function implementing the Task_Hipriority thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Task_Highpriority */
void Task_Highpriority(void const * argument)
{
/* USER CODE BEGIN Task_Highpriority */
/* Infinite loop */
BaseType_t xReturn = pdTRUE;
/* Infinite loop */
for(;;)
{
printf("HighPriority_Task 获取互斥量\n");
//获取互斥量 MuxSem,没获取到则一直等待
xReturn = xSemaphoreTake(BinarySem_Handle, portMAX_DELAY); /* 等待时间 */
if(pdTRUE == xReturn)
printf("HighPriority_Task Running\n");
else
printf("HighPriority_Task is waiting\n!");
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
printf("HighPriority_Task 释放互斥量!\r\n");
xReturn = xSemaphoreGive(BinarySem_Handle);//给出互斥量
osDelay(500);
}
/* USER CODE END Task_Highpriority */
}
串口打印结果如下:
6.3.2互斥量
互斥量实验是为了测试互斥量的优先级继承机制是否有效。对于互斥量的创建,同样可以在CubeMX中创建或者在工程中直接创建。
- 在工程中直接创建并且直接调用freertos API函数
基础任务创建参考前面几章内容,freertos.c中代码如下:
/* USER CODE BEGIN Variables */
SemaphoreHandle_t MuxSem_Handle = NULL;
/* USER CODE END Variables */
…………
/* USER CODE BEGIN RTOS_SEMAPHORES */
/* add semaphores, ... */
MuxSem_Handle = xSemaphoreCreateMutex();
/* USER CODE END RTOS_SEMAPHORES */
…………
/* USER CODE END Header_StartTaskled1 */
void StartTaskled1(void const * argument)
{
/* USER CODE BEGIN StartTaskled1 */
/* Infinite loop */
for(;;)
{
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
osDelay(200);
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
osDelay(800);
}
/* USER CODE END StartTaskled1 */
}
/* USER CODE BEGIN Header_Task_Lowpriority */
/**
* @brief Function implementing the Task_Lopriority thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Task_Lowpriority */
void Task_Lowpriority(void const * argument)
{
/* USER CODE BEGIN Task_Lowpriority */
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
static uint32_t i=0;
/* Infinite loop */
for(;;)
{
printf("LowPriority_Task 获取互斥量\n");
//获取互斥量 MuxSem,没获取到则一直等待
xReturn = xSemaphoreTake(MuxSem_Handle,portMAX_DELAY); /* 等待时间 */
if( xReturn == pdTRUE )
printf("LowPriority_Task Running\n");
for(i=0;i<2000000;i++)//模拟低优先级任务占用互斥量
{
taskYIELD();//发起任务调度
}
printf("LowPriority_Task 释放互斥量!\r\n");
xReturn = xSemaphoreGive( MuxSem_Handle );//给出互斥量
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
osDelay(500);
}
/* USER CODE END Task_Lowpriority */
}
/* USER CODE BEGIN Header_Task_Midpriority */
/**
* @brief Function implementing the Task_Mpriority thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Task_Midpriority */
void Task_Midpriority(void const * argument)
{
/* USER CODE BEGIN Task_Midpriority */
for(;;)
{
printf("MidPriority_Task Running\n");
osDelay(500);
}
/* USER CODE END Task_Midpriority */
}
/* USER CODE BEGIN Header_Task_Highpriority */
/**
* @brief Function implementing the Task_Hipriority thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Task_Highpriority */
void Task_Highpriority(void const * argument)
{
/* USER CODE BEGIN Task_Highpriority */
BaseType_t xReturn = pdTRUE;
/* Infinite loop */
for(;;)
{
printf("HighPriority_Task 获取互斥量\n");
//获取互斥量 MuxSem,没获取到则一直等待
xReturn = xSemaphoreTake(MuxSem_Handle, portMAX_DELAY); /* 等待时间 */
if(pdTRUE == xReturn)
printf("HighPriority_Task Running\n");
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
printf("HighPriority_Task 释放互斥量!\r\n");
xReturn = xSemaphoreGive( MuxSem_Handle );//给出互斥量
osDelay(500);
}
/* USER CODE END Task_Highpriority */
}
- 在CubeMX中创建,然后在工程中调用CubeMX的封装函数
/* USER CODE END Header_Task_Lowpriority */
void Task_Lowpriority(void const * argument)
{
/* USER CODE BEGIN Task_Lowpriority */
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
static uint32_t i=0;
/* Infinite loop */
for(;;)
{
printf("LowPriority_Task 获取互斥量\n");
//获取互斥量 MuxSem,没获取到则一直等待
xReturn = osMutexWait(Mutex01Handle,portMAX_DELAY); /* 等待时间 */
if( xReturn == osOK )
printf("LowPriority_Task Running\n");
for(i=0;i<2000000;i++)//模拟低优先级任务占用互斥量
{
taskYIELD();//发起任务调度
}
printf("LowPriority_Task 释放互斥量!\r\n");
xReturn = osMutexRelease(Mutex01Handle);//给出互斥量
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
osDelay(500);
}
/* USER CODE END Task_Lowpriority */
}
/* USER CODE BEGIN Header_Task_Midpriority */
/**
* @brief Function implementing the Task_Mpriority thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Task_Midpriority */
void Task_Midpriority(void const * argument)
{
/* USER CODE BEGIN Task_Midpriority */
/* Infinite loop */
for(;;)
{
printf("MidPriority_Task Running\n");
osDelay(500);
}
/* USER CODE END Task_Midpriority */
}
/* USER CODE BEGIN Header_Task_Highpriority */
/**
* @brief Function implementing the Task_Hipriority thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Task_Highpriority */
void Task_Highpriority(void const * argument)
{
/* USER CODE BEGIN Task_Highpriority */
/* Infinite loop */
for(;;)
{
BaseType_t xReturn = pdTRUE;
printf("HighPriority_Task 获取互斥量\n");
//获取互斥量 MuxSem,没获取到则一直等待
xReturn = osMutexWait(Mutex01Handle, portMAX_DELAY); /* 等待时间 */
if(xReturn == osOK)
printf("HighPriority_Task Running\n");
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
printf("HighPriority_Task 释放互斥量!\r\n");
xReturn = osMutexRelease(Mutex01Handle);//给出互斥量
osDelay(500);
}
/* USER CODE END Task_Highpriority */
}
结果是一样的:
七、事件
7.1、事件简介
事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输;可以实现一对多,多对多的同步,即一个任务可以等待多个事件发生。任务可以通过设置事件位来实现事件的触发和等待操作。
在FreeRTOS中,每个事件获取的时候,用户可以选择感兴趣的时间,并且选择读取时间信息标记,有三个属性,分别是逻辑与,逻辑或以及是否清除标记。
当任务等待事件同步时,可以通过任务感兴趣的事件位和事件信息标记来判断当前接受的事件是否满足要求,如果满足则说明任务等待到对应的事件,系统将唤醒等待的任务,否则任务将会根据用户指定的阻塞超时时间继续等待下去。
- 事件应用场景
事件可使用于多种场合,能够在一定程度上替代信号量,用于任务和任务间,中断和任务间的同步。一个任务或中断服务例程发送一个事件给事件对象,事件的发送操作是不可累计的,接受任务可以等待多种事件,这是信号量不具备的,信号量只能识别单一同步动作,不能等待多个事件同步。
- 事件运作机制
接收事件时,可以根据感兴趣的事件类型接收单个或者多个事件类型。事件接受成功后,必须使用xClearOnExit选项来清除已接收的事件类型或者用户显示清除事件位。
用户可以自定义通过传入参数xWaitForALLBits选择读取模式,是等待所有感兴趣的事件还是等待感兴趣的任一事件。
事件不与任务相关联,事件相互独立,一个32位的变量(事件集合,实际上用于表示事件的只有24位),用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0表示该事件类型未发生,1表示该事件类型已经发生),一共24种事件类型如下:
事件唤醒机制,当任务因为等待某个或者多个事件发生而进入阻塞态,事件发生的时候会被唤醒,其具体过程如下:
7.2、常用API函数
7.3、CubeMX工程创建
实现功能:创建两个任务:设置事件任务,等待事件任务;设置事件任务通过检测按键按下的情况设置不同的事件标志位,等待事件任务则获取两个事件标志位并判断这两个事件是否都发生,如果是,则输出相应信息,LED进行翻转。等待事件任务的等待时间是portMAX_DELAY,一直等待事件发生,等待到事件后清除对应的事件标志位。
事件在CubeMX中直接添加不行,就手动添加吧,freertos.c中添加代码如下:
/* USER CODE BEGIN PD */
EventGroupHandle_t Event_Handle =NULL;
#define KEY3_EVENT (0x01 << 0)//设置事件掩码的位 0
#define KEY4_EVENT (0x01 << 1)//设置事件掩码的位 1
/* USER CODE END PD */
…………
/* USER CODE BEGIN Init */
Event_Handle = xEventGroupCreate();
/* USER CODE END Init */
…………
/* USER CODE END Header_StartTaskLED1 */
void StartTaskLED1(void const * argument)
{
/* USER CODE BEGIN StartTaskLED1 */
/* Infinite loop */
for(;;)
{
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
osDelay(800);
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
osDelay(200);
}
/* USER CODE END StartTaskLED1 */
}
/* USER CODE BEGIN Header_StartTaskLED2 */
/**
* @brief Function implementing the myTaskLed2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskLED2 */
void StartTaskLED2(void const * argument)
{
/* USER CODE BEGIN StartTaskLED2 */
EventBits_t r_event; /* 定义一个事件接收变量 */
/* Infinite loop */
for(;;)
{
r_event = xEventGroupWaitBits(Event_Handle, KEY3_EVENT|KEY4_EVENT, pdTRUE, pdTRUE, portMAX_DELAY);/* 指定超时事件,一直等 */
if((r_event & (KEY3_EVENT|KEY4_EVENT)) == (KEY3_EVENT|KEY4_EVENT))
{
printf ( "KEY3 与 KEY4 都按下\n");
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
}
else
printf ("事件错误!\n");
osDelay(1);
}
/* USER CODE END StartTaskLED2 */
}
/* USER CODE BEGIN Header_StartTaskKEY */
/**
* @brief Function implementing the myTaskKEy thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskKEY */
void StartTaskKEY(void const * argument)
{
/* USER CODE BEGIN StartTaskKEY */
uint8_t key=0;
/* Infinite loop */
for(;;)
{
key=key_scan(0);
if(key==KEY3_PRES)
{
printf ( "KEY3 被按下\n" );
/* 触发一个事件 KEY3 */
xEventGroupSetBits(Event_Handle, KEY3_EVENT);
}
else if(key==KEY4_PRES)
{
printf ( "KEY4 被按下\n" );
/* 触发一个事件 KEY4 */
xEventGroupSetBits(Event_Handle, KEY4_EVENT);
}
osDelay(20);
}
/* USER CODE END StartTaskKEY */
}
结果:在串口助手可以看到任务输出信息,按下按键KEY3发送事件3, 按下KEY4按键发送事件4,当事件3和4都发生的时候,LED2会发生翻转
八、软件定时器
8.1、软件定时器简介
定时器,是指从指定的时刻开始,经过一个指定时间,然后触发一个超时事件,用户可以自定义定时器的周期与频率。
定时器有硬件定时器与软件定时器之分:
硬件定时器是芯片本身提供的定时器功能。一般是由外部晶振提供给芯片输入时钟,芯片向软件模块提供一组配置寄存器,接受控制输入,到达设定时间值后芯片中断控制器产生时钟中断。硬件定时器的精度一般都很高,可以达到纳秒量级,并且是中断触发方式。
软件定时器,是有操作系统提供的一类系统接口,它构建在硬件定时器基础上,使系统能够提供不受硬件定时器资源限制的定时器服务。
8.2、常用软件定时器API函数
- 软件定时器创建函数xTimerCreate()
- 软件定时器启动函数
- 软件定时器停止函数
- 软件定时器删除函TimerDelete()
8.3、CubeMX工程创建
/* USER CODE BEGIN Variables */
static uint32_t TmrCb_Count1 = 0; /* 记录软件定时器 1 回调函数执行次数 */
static uint32_t TmrCb_Count2 = 0; /* 记录软件定时器 2 回调函数执行次数 */
/* USER CODE END Variables */
…………
/* add threads, ... */
if(myTimer01Handle != NULL)
{
osTimerStart(myTimer01Handle,5000); //开启定时器
}
if(myTimer02Handle != NULL)
{
osTimerStart(myTimer02Handle,1000);
}
/* USER CODE END RTOS_THREADS */
/* USER CODE END Header_StartLEDTask */
void StartLEDTask(void const * argument)
{
/* USER CODE BEGIN StartLEDTask */
/* Infinite loop */
for(;;)
{
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
osDelay(800);
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
osDelay(200);
}
/* USER CODE END StartLEDTask */
}
/* Callback01 function */
void Callback01(void const * argument)
{
/* USER CODE BEGIN Callback01 */
TickType_t tick_num1;
TmrCb_Count1++; /* 每回调一次加一 */
tick_num1 = xTaskGetTickCount(); /* 获取滴答定时器的计数值 */
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
printf("Swtmr1_Callback 函数执行 %d 次\n", TmrCb_Count1);
printf("滴答定时器数值=%d\n", tick_num1);
/* USER CODE END Callback01 */
}
/* Callback02 function */
void Callback02(void const * argument)
{
/* USER CODE BEGIN Callback02 */
TickType_t tick_num2;
TmrCb_Count2++; /* 每回调一次加一 */
tick_num2 = xTaskGetTickCount(); /* 获取滴答定时器的计数值 */
printf("Swtmr2_Callback 函数执行 %d 次\n", TmrCb_Count2);
printf("滴答定时器数值=%d\n", tick_num2);
/* USER CODE END Callback02 */
}
九、任务通知
9.1、任务通知简介
任务通知的功能, 每个任务都有一个32位的通知值,在大多数情况下,任务通知可以替代二值信号量、计数信号量、事件组,也可以替代长度为1的队列(可以保存一个32位整数或指针值)。
按照FreeRTOS官方的说法,使用任务通知比通过使用信号量等IPC通信方式解除阻塞的任务要快45%,更加省RAM内存空间(使用GCC编译器, -o2优化级别),任务通知的使用无需创建队列。
FreeRTOS提供以下几种方式发送通知给任务:
- 发送通知给任务,如果有通知未读,不覆盖通知值
- 发送通知给任务,直接覆盖通知值
- 发送通知给任务,设置通知值的一个或者多个位,可以当做事件组来使用
- 发送通知给任务,递增通知值,可以当做计数信号量使用
消息通知的限制:
- 只能有一个任务接受通知消息,因为必须指定接受通知的任务
- 只有等待通知的任务可以被阻塞,发送通知的任务,在任务和情况下都不会因为发送失败而进入阻塞态
任务通知运作机制:
9.2、常见任务通知API函数
- 发送任务通知函数 xTaskGenericNotify()
- 中断中发送任务通知通用函数xTakeGenericNotifyFromISR()
- 获取任务通知函数
9.3、CubeMX工程创建
CubeMX中没有相关设置,直接调用FreeRTOS的官方相关的API函数
9.3.1 任务通知代替消息队列
freerots.c文件中添加的代码如下:
/* USER CODE BEGIN PD */
#define ULONG_MAX 0xffffffffUL
#define USE_CHAR 0 /* 测试字符串的时候配置为 1 ,测试变量配置为 0 */
/* USER CODE END PD */
…………
/* USER CODE END Header_StartTaskLED */
void StartTaskLED(void const * argument)
{
/* USER CODE BEGIN StartTaskLED */
/* Infinite loop */
for(;;)
{
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
osDelay(800);
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
osDelay(200);
}
/* USER CODE END StartTaskLED */
}
…………
void StartTaskReceive1(void const * argument)
{
/* USER CODE BEGIN StartTaskReceive1 */
BaseType_t xReturn = pdTRUE;
#if USE_CHAR
char *r_char;
#else
uint32_t r_num;
#endif
/* Infinite loop */
for(;;)
{
//获取任务通知 ,没获取到则一直等待
xReturn=xTaskNotifyWait(0x0, /*进入函数的时候不清除任务bit*/ULONG_MAX, //退出函数的时候清除所有的 bit
#if USE_CHAR
(uint32_t *)&r_char, //保存任务通知值
#else
&r_num, //保存任务通知值
#endif
portMAX_DELAY); //阻塞时间
if( pdTRUE == xReturn )
#if USE_CHAR
printf("Receive1_Task 任务通知消息为 %s \n",r_char);
#else
printf("Receive1_Task 任务通知消息为 %d \n",r_num);
#endif
osDelay(1);
}
/* USER CODE END StartTaskReceive1 */
}
…………
void StartTaskReceive2(void const * argument)
{
/* USER CODE BEGIN StartTaskReceive2 */
BaseType_t xReturn = pdTRUE;
#if USE_CHAR
char *r_char;
#else
uint32_t r_num;
#endif
/* Infinite loop */
for(;;)
{
//获取任务通知 ,没获取到则一直等待
xReturn=xTaskNotifyWait(0x0, //进入函数的时候不清除任务bit
ULONG_MAX, //退出函数的时候清除所有的 bit
#if USE_CHAR
(uint32_t *)&r_char, //保存任务通知值
#else
&r_num, //保存任务通知值
#endif
portMAX_DELAY); //阻塞时间
if( pdTRUE == xReturn )
#if USE_CHAR
printf("Receive2_Task 任务通知消息为 %s \n",r_char);
#else
printf("Receive2_Task 任务通知消息为 %d \n",r_num);
#endif
osDelay(1);
}
/* USER CODE END StartTaskReceive2 */
}
…………
/* USER CODE END Header_StartTaskSend */
void StartTaskSend(void const * argument)
{
/* USER CODE BEGIN StartTaskSend */
BaseType_t xReturn = pdPASS;
uint8_t key=0;
#if USE_CHAR
char test_str1[] = "this is a mail test 1";/* 邮箱消息 test1 */
char test_str2[] = "this is a mail test 2";/* 邮箱消息 test2 */
#else
uint32_t send1 = 1;
uint32_t send2 = 2;
#endif
/* Infinite loop */
for(;;)
{
key = key_scan(0);
if(key==KEY3_PRES)
{
xReturn = xTaskNotify(myTaskReceive1Handle, /*任务句柄*/
#if USE_CHAR
(uint32_t)&test_str1, /* 发送的数据,最大为 4
字节 */
#else
send1, /* 发送的数据,最大为 4 字节 */
#endif
eSetValueWithOverwrite );/*覆盖当前通知*/
if( xReturn == pdPASS )
printf("Receive1_Task_Handle 任务通知消息发送成功!\r\n");
}
else if(key == KEY4_PRES)
{
xReturn = xTaskNotify(myTaskReceive2Handle, /*任务句柄*/
#if USE_CHAR
(uint32_t)&test_str2, /* 发送的数据,最大为 4
字节 */
#else
send2, /* 发送的数据,最大为 4 字节 */
#endif
eSetValueWithOverwrite );/*覆盖当前通知*/
if( xReturn == pdPASS )
printf("Receive2_Task_Handle 任务通知消息发送成功!\r\n");
}
osDelay(20);
}
/* USER CODE END StartTaskSend */
}
串口打印结果如下:
9.3.2 任务通知代替二值信号量
/* USER CODE END Header_StartTaskLED */
void StartTaskLED(void const * argument)
{
/* USER CODE BEGIN StartTaskLED */
/* Infinite loop */
for(;;)
{
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
osDelay(800);
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
osDelay(200);
}
/* USER CODE END StartTaskLED */
}
…………
/* USER CODE END Header_StartTaskReceive1 */
void StartTaskReceive1(void const * argument)
{
/* USER CODE BEGIN StartTaskReceive1 */
/* Infinite loop */
for(;;)
{
//获取任务通知 ,没获取到则一直等待
ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
printf("Receive1_Task 任务通知获取成功!\n\n");
osDelay(1);
}
/* USER CODE END StartTaskReceive1 */
}
…………
/* USER CODE END Header_StartTaskReceive2 */
void StartTaskReceive2(void const * argument)
{
/* USER CODE BEGIN StartTaskReceive2 */
/* Infinite loop */
for(;;)
{
//获取任务通知 ,没获取到则一直等待
ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
printf("Receive2_Task 任务通知获取成功!\n\n");
osDelay(1);
}
/* USER CODE END StartTaskReceive2 */
}
…………
/* USER CODE END Header_StartTaskSend */
void StartTaskSend(void const * argument)
{
/* USER CODE BEGIN StartTaskSend */
BaseType_t xReturn = pdPASS;
uint8_t key=0;
/* Infinite loop */
for(;;)
{
key = key_scan(0);
if(key==KEY3_PRES)
{
xReturn = xTaskNotifyGive(myTaskReceive1Handle);
if( xReturn == pdTRUE )
printf("Receive1_Task_Handle 任务通知发送成功!\r\n");
}
else if(key == KEY4_PRES)
{
xReturn = xTaskNotifyGive(myTaskReceive2Handle);
if( xReturn == pdTRUE )
printf("Receive2_Task_Handle 任务通知发送成功!\r\n");
}
osDelay(20);
}
/* USER CODE END StartTaskSend */
}
串口打印结果如下:
9.3.3 任务通知代替计数信号量
/* USER CODE END Header_StartTaskLED */
void StartTaskLED(void const * argument)
{
/* USER CODE BEGIN StartTaskLED */
/* Infinite loop */
for(;;)
{
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
osDelay(800);
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
osDelay(200);
}
/* USER CODE END StartTaskLED */
}
…………
/* USER CODE END Header_StartTaskTake */
void StartTaskTake(void const * argument)
{
/* USER CODE BEGIN StartTaskTake */
uint32_t take_num = pdTRUE;
uint8_t key=0;
/* Infinite loop */
for(;;)
{
key = key_scan(0);
if(key == KEY4_PRES)
{
//获取任务通知 ,没获取到则不等待
take_num=ulTaskNotifyTake(pdFALSE,0);
if(take_num > 0)
printf( "KEY4 被按下,成功申请到停车位,当前车位为 %d \n",
take_num - 1);
else
printf( "KEY4 被按下,车位已经没有了,请按 KEY3 释放车位\n" );
}
osDelay(20);
}
/* USER CODE END StartTaskTake */
}
…………
/* USER CODE END Header_StartTaskGive */
void StartTaskGive(void const * argument)
{
/* USER CODE BEGIN StartTaskGive */
BaseType_t xReturn = pdPASS;
uint8_t key=0;
/* Infinite loop */
for(;;)
{
key = key_scan(0);
if(key==KEY3_PRES)
{
xTaskNotifyGive(myTaskTakeHandle);//发送任务通知
if ( pdPASS == xReturn )
printf( "KEY3 被按下,释放 1 个停车位。\n" );
}
osDelay(20);
}
/* USER CODE END StartTaskGive */
}
串口打印结果如下:
9.3.4 任务通知代替事件组
/* USER CODE BEGIN PD */
#define ULONG_MAX 0xffffffffUL
#define KEY1_EVENT (0x01 << 0)//设置事件掩码的位 0
#define KEY2_EVENT (0x01 << 1)//设置事件掩码的位 1
/* USER CODE END PD */
…………
/* USER CODE END Header_StartTaskLED */
void StartTaskLED(void const * argument)
{
/* USER CODE BEGIN StartTaskLED */
/* Infinite loop */
for(;;)
{
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
osDelay(800);
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
osDelay(200);
}
/* USER CODE END StartTaskLED */
}
…………
/* USER CODE END Header_StartTaskLED2 */
void StartTaskLED2(void const * argument)
{
/* USER CODE BEGIN StartTaskLED2 */
BaseType_t xReturn = pdTRUE;
uint32_t r_event = 0; /* 定义一个事件接收变量 */
uint32_t last_event = 0;/* 定义一个保存事件的变量 */
/* Infinite loop */
for(;;)
{
//获取任务通知 ,没获取到则一直等待
xReturn = xTaskNotifyWait(0x0, //进入函数的时候不清除任务bit
ULONG_MAX, //退出函数的时候清除所有的 bitR
&r_event, //保存任务通知值
portMAX_DELAY); //阻塞时间
if( pdTRUE == xReturn )
{
last_event |= r_event;
if(last_event == (KEY1_EVENT|KEY2_EVENT))
{
last_event=0;
printf ( "KEY3 与 KEY4 都按下\n");
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
}
else
last_event = r_event;
}
osDelay(1);
}
/* USER CODE END StartTaskLED2 */
}
…………
/* USER CODE END Header_StartTaskKEY */
void StartTaskKEY(void const * argument)
{
/* USER CODE BEGIN StartTaskKEY */
uint8_t key = 0;
/* Infinite loop */
for(;;)
{
key= key_scan(0);
if(key==KEY3_PRES)
{
printf ( "KEY3 被按下\n" );
/* 触发一个事件 1 */
xTaskNotify((TaskHandle_t )TaskLED2Handle,//接收任务通知
(uint32_t )KEY1_EVENT,//要触发的事件
(eNotifyAction)eSetBits);//设置任务通知值中的位
}
else if(key==KEY4_PRES)
{
printf ( "KEY4 被按下\n" );
/* 触发一个事件 2 */
xTaskNotify((TaskHandle_t )TaskLED2Handle,//接收任务通知的任务句柄
(uint32_t )KEY2_EVENT,//要触发的事件
(eNotifyAction)eSetBits);//设置任务通知值中的位
}
osDelay(20);
}
/* USER CODE END StartTaskKEY */
}
串口打印的结果如下:
十、内存管理
10.1、内存管理简介
内存管理是一个系统基本组成部分,FreeRTOS中大量使用到了内存管理,比如创建人物、信号量、队列等会自动从堆中申请内存。用户应用层代码也可以使用FreeRTOS提供的内存管理函数来申请和释放内存。
内存碎片是伴随着内存申请和释放而来的:
10.2、内存分配方法
-
heap_1 内存分配方法
heap_1实现起来就是当需要RAM的时候就从一个大数组(内存堆)中分出一小块来,大数组(内存堆)的容量为configTOTAL_HEAP_SIZE。
-
heap_2 内存分配方法
heap_2提供了内存释放函数
-
heap_3 内存分配方法
heap_3只是简单的封装了标准C库中的malloc()和free()函数,并且能够满足常用的编译器。采用的封装方式是操作内存前挂起调度器、完成后再恢复调度器。
-
heap_4 内存分配方法
-
heap_5 内存分配方法
heap_5使用了和heap_4相同的合并算法,内存管理实现起来基本相同,但是heap_5允许内存堆跨越多个不连续的内存段。
10.3、CubeMX工程创建
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "key.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
uint8_t *Test_Ptr = NULL;
/* USER CODE END PTD */
…………
/* USER CODE END Header_StarTaskLED */
void StarTaskLED(void const * argument)
{
/* USER CODE BEGIN StarTaskLED */
/* Infinite loop */
for(;;)
{
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
osDelay(800);
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
osDelay(200);
}
/* USER CODE END StarTaskLED */
}
/* USER CODE END Header_StartTaskKEY */
void StartTaskKEY(void const * argument)
{
/* USER CODE BEGIN StartTaskKEY */
uint8_t key=0;
uint32_t g_memsize;
/* Infinite loop */
for(;;)
{
key = key_scan(0);
if(key==KEY3_PRES)
{
if(NULL == Test_Ptr)
{
/* 获取当前内存大小 */
g_memsize = xPortGetFreeHeapSize();
printf("系统当前内存大小为 %d 字节,开始申请内存\n",g_memsize);
Test_Ptr = pvPortMalloc(1024);
if(NULL != Test_Ptr)
{
printf("内存申请成功\n");
printf("申请到的内存地址为%#x\n",(int)Test_Ptr);
/* 获取当前内剩余存大小 */
g_memsize = xPortGetFreeHeapSize();
printf("系统当前内存剩余存大小为 %d 字节\n",g_memsize);
//向 Test_Ptr 中写入当数据:当前系统时间
sprintf((char*)Test_Ptr,"当前系统 TickCount = %d\n",xTaskGetTickCount());
printf("写入的数据是 %s \n",(char*)Test_Ptr);
}
}
else
printf("请先按下 KEY4 释放内存再申请\n");
}
if(key==KEY4_PRES)
{
if(NULL != Test_Ptr)
{
printf("释放内存\n");
vPortFree(Test_Ptr);//释放内存
Test_Ptr=NULL;
/* 获取当前内剩余存大小 */
g_memsize = xPortGetFreeHeapSize();
printf("系统当前内存大小为 %d 字节,内存释放完成\n",g_memsize);
}
else
printf("请先按下 KEY3 申请内存再释放\n");
}
}
/* USER CODE END StartTaskKEY */
}
串口打印结果如下:
十一、中断管理
11.1、中断管理简介
l临界段就是一段在执行的时候不能被中断的代码段,在freeRTOS中,临界段最常出现的就是对全局变量的操作。
ARM Cortex-M 系列内核的中断是有硬件管理的,它并不接管硬件管理的相关中断,只支持简单的开关中断等 。
FreeRTOS中的中断使用其实跟逻辑差不多,需要自己配置中断,并且使能中断,编写中断服务函数,在中断服务函数中使用内核IPC通信机制,一般建议使用信号量、消息或事件标志组等标志事件的发生,将事件发布给处理任务,等退出中断后再有相关处理任务具体处理中断。
ARM Cortex-M NICV支持中断嵌套功能:当一个中断触发并且系统进行响应时,处理器硬件会将当前运行的部分上下文寄存器自动压入中断栈中,这部分的寄存器包括PSR,R0,R1,R2,R3以及R12寄存器。
11.2、CubeMX工程创建
任务创建可参考前面第4章配置,这里注重讲一下定时器配置过程:
freertos.c中添加代码:
/* USER CODE END Header_StartTaskLED */
void StartTaskLED(void const * argument)
{
/* USER CODE BEGIN StartTaskLED */
/* Infinite loop */
for(;;)
{
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
osDelay(800);
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
osDelay(200);
}
/* USER CODE END StartTaskLED */
}
/* USER CODE END Header_StartTaskInterrupt */
void StartTaskInterrupt(void const * argument)
{
/* USER CODE BEGIN StartTaskInterrupt */
static uint32_t total_num = 0;
/* Infinite loop */
for(;;)
{
total_num += 1;
if(total_num == 5) //(2)
{
printf("关闭中断.............\r\n");
portDISABLE_INTERRUPTS(); //关闭中断 (3)
HAL_Delay(3000); //延时 5s,使用不影响任务调度的延时(4)
printf("打开中断.............\r\n"); //打开中断
portENABLE_INTERRUPTS(); //(5)
}
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
osDelay(1000);
}
/* USER CODE END StartTaskInterrupt */
}
main.c中以及定时器中断回调函数中添加代码如下:
/* USER CODE BEGIN 2 */
printf("中断管理实验\r\n");
HAL_TIM_Base_Start_IT(&htim2); //开启定时器中断
HAL_TIM_Base_Start_IT(&htim3);
/* USER CODE END 2 */
…………
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* USER CODE BEGIN Callback 0 */
/* USER CODE END Callback 0 */
if (htim->Instance == TIM1) {
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
if(htim->Instance == TIM2)
{
printf("TIM2 输出…………\r\n");
}
if(htim->Instance == TIM3)
{
printf("TIM3 输出…………\r\n");
}
/* USER CODE END Callback 1 */
}
串口打印结果:
十二、CPU利用率设计
12.1、内存管理简介
CPU的使用率其实就是系统运行程序占用的CPU资源,表示机器在某段时间程序运行的情况。CPU的利用率越高,说明机器在这个时间上运行了很多程序。
CPU的利用率是非空闲任务的时间与总时间之比。Freer提供测量各个任务占用CPU时间的函数接口,调试的时候很有必要得到当前系统的CPU利用率,但是产品发布后就可以将这个CPU利用率统计这个功能去掉。
FreeRTOS使用一个外部变量进行统计时间,并且消耗一个高精度的定时器,其用于定时的精度是系统时钟节拍的10-20倍,比如当前系统的时钟节拍是1000Hz,那么定时器的计数节拍就要是10000-20000Hz。
12.2、CubeMX工程创建
除了前面的基础配置,还应该添加:
FreeRTOSConfig.h中添加: extern volatile uint32_t CPU_RunTime;
freertos.c中添加代码:
/* USER CODE BEGIN 1 */
/* Functions needed when configGENERATE_RUN_TIME_STATS is on */
__weak void configureTimerForRunTimeStats(void)
{
CPU_RunTime = 0;
}
__weak unsigned long getRunTimeCounterValue(void)
{
return CPU_RunTime;
}
/* USER CODE END 1 */
………………
/* USER CODE END Header_StartTaskLED1 */
void StartTaskLED1(void const * argument)
{
/* USER CODE BEGIN StartTaskLED1 */
/* Infinite loop */
for(;;)
{
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
osDelay(800);
printf("LED1_Task Running, LED2_ON\r\n");
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
printf("LED1_Task Running, LED2_OFF\r\n");
osDelay(200);
}
/* USER CODE END StartTaskLED1 */
}
/* USER CODE END Header_StartTaskLED2 */
void StartTaskLED2(void const * argument)
{
/* USER CODE BEGIN StartTaskLED2 */
/* Infinite loop */
for(;;)
{
HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET);
osDelay(200);
printf("LED2_Task Running, LED2_ON\r\n");
HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET);
osDelay(800);
printf("LED2_Task Running, LED2_OFF\r\n");
}
/* USER CODE END StartTaskLED2 */
}
/* USER CODE END Header_StartTaskCPU */
void StartTaskCPU(void const * argument)
{
/* USER CODE BEGIN StartTaskCPU */
uint8_t CPU_RunInfo[400];
/* Infinite loop */
for(;;)
{
memset(CPU_RunInfo,0,400);//信息缓冲区清零
vTaskList((char *)&CPU_RunInfo); //获取任务运行时间信息
printf("---------------------------------------------\r\n");
printf("任务名 任务状态 优先级 剩余栈 任务序号\r\n");
printf("%s", CPU_RunInfo);
printf("---------------------------------------------\r\n");
memset(CPU_RunInfo,0,400); //信息缓冲区清零
vTaskGetRunTimeStats((char *)&CPU_RunInfo);
printf("任务名 运行计数 利用率\r\n");
printf("%s", CPU_RunInfo);
printf("---------------------------------------------\r\n\n");
osDelay(1000);
}
/* USER CODE END StartTaskCPU */
}
main.c中添加以下代码:
/* USER CODE BEGIN PV */
volatile uint32_t CPU_RunTime = 0;
/* USER CODE END PV */
…………
/* USER CODE BEGIN 2 */
printf("FreeRTOS CPU 利用率统计\r\n");
HAL_TIM_Base_Start_IT(&htim3);
/* USER CODE END 2 */
…………
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* USER CODE BEGIN Callback 0 */
/* USER CODE END Callback 0 */
if (htim->Instance == TIM1) {
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
if(htim->Instance == TIM3)
{
CPU_RunTime++;
}
/* USER CODE END Callback 1 */
}
串口打印结果如下: