普中FreeRTOS资料的学习

一、初识FreeRTOS

1、为何学习?

在裸机系统中,所有操作都是在一个无限的大循环中实现,但是对着产品要实现的功能越来越多,单纯的裸机系统已经不能完美滴解决问题,反而会使编程变得更加复杂。

2、FreeROTS

  1. RTOS的全称是 RealTime Operating System,实时操作系统
  2. RTOS 的任务调度器被设计为可预测的,而这正是嵌入式实时操作系统所需要的,实时环境中要求操作系统必须对某一个事件做出实时的响应,因此系统任务调度器的行为必须是可预测的。
  3. 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源码下载

提供两个下载链接:
官网:http://www.freertos.org/,
码托管网站:https://sourceforge.net/projects/freertos/files/FreeRTOS/。

4、FreeRTOS文件介绍

 

 

二、CubeMX 中配置FreeRTOS相关介绍

 

 

 

具体关于CubeMX中怎么配置FreeRTOS的详细教程见:

CubeMX使用FreeRTOS编程指南_TOP嵌入式

三、FreeRTOS启动流程

3.1、任务创建

在系统上电后第一个执行的是启动文件里面的由汇编编写的复位函数Reset_Handler

复位函数的最后会调用C库函数_main。_main函数的主要工作是初始化系统的堆和栈,最后调用C中的main函数。在main()函数中,可以直接进行创建任务操作,FreeRTOS会自动帮我们做初始化的事情,如初始化堆内存。

3.2、任务调度

在创建完任务后,需要开启调度器。调度器中实现任务的真正调度,空闲任务的实现,定时器任务的实现。

为什么要空闲任务?因为FreeRTOS 一旦启动,就必须要保证系统中每时每刻都有一个任务处于运行态 (Runing),并且空闲任务不可以被挂起与删除,空闲任务的优先级是最低的, 以便系统中其他任务能随时抢占空闲任务的 CPU 使用权。

在Cortex-M3 架构中,FreeRTOS为了任务启动和任务切换使用了三个异常:SVC、PendSV、SYSTick:

SVC(系统服务调用,亦简称系统调用)用于任务启动,有些操作系统不允许应用程序直接访问硬件,而是通过提供一些系统服务函数,用户程序使用 SVC 发出对系统服务函数的呼叫请求,以这种方法调用它们来间接访问硬件,它就会 产生一个 SVC 异常。

PendSV(可挂起系统调用)用于完成任务切换,它是可以像普通的中断一样被挂起的,它的最大特性是如果当前有优先级比它高的中断在运行,PendSV 会延迟执行,直到高优先级中断执行完毕,这样子产生的 PendSV 中断就不会打断 其他中断的运行。

SysTick 用于产生系统节拍时钟,提供一个时间片,如果多个任务共享同一 个优先级,则每次 SysTick 中断,下一个任务将获得一个时间片。关于详细的 SVC、PendSV 异常描述,推荐《Cortex-M3 权威指南》一书的“异常”部分

 这里将PendSV和SYSTick异常优先级设置为最低,这样任务切换不会打断某个中断服务程序,中断服务程序也不会被延迟,简化了设计,有利于系统稳定。

3.3、主函数

 main函数里面只是创建并启动一些任务或硬件初始化

在临界区创建任务,任务只能在退出临界区的时候才执行最高优先级任务

vTaskStartScheduler (),开启任务调度器函数

在启动任务调度器的时候,假如启动成功的话,任务就不会有返回了, 假如启动没有成功,则通过LR寄存器指定的地址退出,在创建任务的时候,任务栈对应的LR寄存器指向是任务退出函数prvTaskExitError(),该函数里面是一个死循环。

四、消息队列

4.1、消息队列简介

 队列是为了任务与任务、任务与中断之间的通信而准备的,可以存储有限的、大小固定的数据项目。

通常队里采用先进先出(FIFO)的存储缓冲机制,也就是往队列发送数据时候(入队)永远是发送到队列的尾部,而从队里提取数据的时候(出队)是从队列的头部提取的。

数据发送到队列中发导致数据拷贝,也就是将要发送到数据拷贝到队列中,即队列中存储的是数据的原始值,而不是数据的引用(即只传递数据的指针),这个也叫值传递。

采用值传递的话虽然会导致数据拷贝,会浪费一点时间,但是一旦将消息发送到队列中原始的数据缓冲区就可以删除掉或者覆写,这样的话这些缓冲区就可 以被重复的使用

 

 出队阻塞:

当任务尝试从一个队列中读取消息的时候可以指定一个阻塞时间,这个阻塞时间就是当任务从队列中读取消息无效的时候任务阻塞的时间,阻塞时间在0~portMAX_DELAY之间。

入队阻塞:

入队是向队列中发送消息,将消息加入到队列中。和出队阻塞一样,当一个任务向队列发送消息的话也可以设置阻塞时间。

消息队列操作示图
(1)创建队列
图中任务 A 要向任务 B 发送消息,这个消息是 x 变量的值。首先创建一个队列,并且指定队列的长度和每条消息的长度。这里我们创建了一个长度为 4 的队列,因为要传递的是 x 值,而 x 是个 int 类型的变量,所以每条消息的 长度就是 int 类型的长度,在 STM32 中是 4 字节,即每条消息是 4 个字节的。
(2)向队列发送第一个消息
图中任务 A 的变量 x 值为 10,将这个值发送到消息队列中。此时队列剩余长度就是 3 了。前面说了向队列中发送消息是采用拷贝的方式,所以一旦消 息发送完成变量 x 就可以再次被使用,赋其他的值。
(3)向队列发送第二个消息
图中任务 A 又向队列发送了一个消息,即新的 x 的值,这里是 20。此时队列剩余长度为 2。
(4)从队列中读取消息
图中任务 B 从队列中读取消息,并将读取到的消息值赋值给 y,这样 y 就等于 10 了。任务 B 从队列中读取消息完成以后可以选择清除掉这个消息或者 不清除。当选择清除这个消息的话其他任务或中断就不能获取这个消息了,而且 队列剩余大小就会加一,变成 3。如果不清除的话其他任务或中断也可以获取这 个消息,而队列剩余大小依旧是 2。

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
代码(1):uxQueueLength 为 1 表示创建的队列长度为 1,其实用作信号量就表示信号量的最大可用个数,从前面的知识点我们就知道,二值信号量的非空 即满,长度为 1 不正是这样子的表示吗。
代码(2):semSEMAPHORE_QUEUE_ITEM_LENGTH 其实是一个宏定义,其值为 0,它表示创建的消息空间(队列项)大小为 0
代码(3):ucQueueType 表示的是创建消息队列的类型,在 queue.h 中有定义,具体代码如下,现在创建的是二值信号量,其类型就是 queueQUEUE_TYPE_BINARY_SEMAPHORE
  • 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、硬件设计所使用到的资源:

(1)LED1、LED2 指示灯
(2)KEY3、KEY4 按键
(3)串口 2
LED1 指示灯用于标识任务 的运行过程,LED2 用于信号量接收指示,按键 KEY3 和 KEY4 键用于信号量释放, 串口 2 用于按键操作时信息输出。

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 */
}

 结果是一样的:

调用CubeMX 相关封装函数
直接调用FreeRTOS API函数

七、事件

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、软件定时器简介

定时器,是指从指定的时刻开始,经过一个指定时间,然后触发一个超时事件,用户可以自定义定时器的周期与频率。

定时器有硬件定时器与软件定时器之分:

硬件定时器是芯片本身提供的定时器功能。一般是由外部晶振提供给芯片输入时钟,芯片向软件模块提供一组配置寄存器,接受控制输入,到达设定时间值后芯片中断控制器产生时钟中断。硬件定时器的精度一般都很高,可以达到纳秒量级,并且是中断触发方式。

软件定时器,是有操作系统提供的一类系统接口,它构建在硬件定时器基础上,使系统能够提供不受硬件定时器资源限制的定时器服务。

FreeRTOS 操作系统提供软件定时器功能,软件定时器的使用相当于扩展了定时器的数量,允许创建更多的定时业务。FreeRTOS 软件定时器功能上支持:
●裁剪:能通过宏关闭软件定时器功能。
●软件定时器创建。
●软件定时器启动。
●软件定时器停止。
●软件定时器复位。
●软件定时器删除。
FreeRTOS 提供的软件定时器支持单次模式和周期模式,单次模式和周期模式的定时时间到之后都会调用软件定时器的回调函数,用户可以在回调函数中加 入要执行的工程代码。
单次模式:当用户创建了定时器并启动了定时器后,定时时间到了,只执行一次回调函数之后就将该定时器进入休眠状态,不再重新执行。
周期模式:这个定时器会按照设置的定时时间循环执行回调函数,直到用户将定时器删除,如下所示:

 

使用软件定时器时候要注意以下几点:
●软件定时器的回调函数中应快进快出,绝对不允许使用任何可能引软件定 时器起任务挂起或者阻塞的 API 接口,在回调函数中也绝对不允许出现死循环。
●软件定时器使用了系统的一个队列和一个任务资源,软件定时器任务的优先级默认为 configTIMER_TASK_PRIORITY,为了更好响应,该优先级应设置为所 有任务中最高的优先级。
●创建单次软件定时器,该定时器超时执行完回调函数后,系统会自动删除该软件定时器,并回收资源。
●定时器任务的堆栈大小默认为 configTIMER_TASK_STACK_DEPTH 个字节。

8.2、常用软件定时器API函数

  • 软件定时器创建函数xTimerCreate()
  • 软件定时器启动函数
        xTimerStart()
        xTimerStartFromISR()
  • 软件定时器停止函数
        xTimerStop()
        xTimerStopFromISR()
  • 软件定时器删除函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提供以下几种方式发送通知给任务:

  1. 发送通知给任务,如果有通知未读,不覆盖通知值
  2. 发送通知给任务,直接覆盖通知值
  3. 发送通知给任务,设置通知值的一个或者多个位,可以当做事件组来使用
  4. 发送通知给任务,递增通知值,可以当做计数信号量使用

消息通知的限制:

  1. 只能有一个任务接受通知消息,因为必须指定接受通知的任务
  2. 只有等待通知的任务可以被阻塞,发送通知的任务,在任务和情况下都不会因为发送失败而进入阻塞态

任务通知运作机制:

任务通知可以在任务中向指定任务发送通知,也可以在中断中向指定任务发送通知,FreeRTOS 的每个任务都有一个 32 位的通知值,任务控制块中的成员 变量 ulNotifiedValue 就是这个通知值。只有在任务中可以等待通知,而不允 许在中断中等待通知。如果任务在等待的通知暂时无效,任务会根据用户指定的 阻塞超时时间进入阻塞状态,我们可以将等待通知的任务看作是消费者;其它任 务和中断可以向等待通知的任务发送通知,发送通知的任务和中断服务函数可以 看作是生产者,当其他任务或者中断向这个任务发送任务通知,任务获得通知以 后,该任务就会从阻塞态中解除,这与 FreeRTOS 中内核的其他通信机制一致。

9.2、常见任务通知API函数

  •  发送任务通知函数 xTaskGenericNotify()
       xTaskNotifyGive()
       vTaskNotifyGiveFromISR()
       xTaskNotify()
       xTaskNotifyFromISR()
  • 中断中发送任务通知通用函数xTakeGenericNotifyFromISR()
       xTaskNotifyAndQuery()
       xTaskNotifyAndQueryFromISR()
  • 获取任务通知函数
       ulTaskNotifyTake()
       xTaskNotifyWait()

9.3、CubeMX工程创建

CubeMX中没有相关设置,直接调用FreeRTOS的官方相关的API函数

9.3.1 任务通知代替消息队列
实现的功能是:主要创建 3 个任务,其中两个任务是用于接收任务通知,另一个任务发送任务通知。三个任务独立运行,发送消息任务是通过检测按 键的按下情况来发送消息通知,另两个任务获取消息通知,在任务通知中没有可 用的通知之前就一直等待消息,一旦获取到消息通知就把消息打印在串口调试助 手显示。

 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 任务通知代替二值信号量
实现的功能是:主要创建 3 个任务,其中两个任务是用于接收任务通 知,另一个任务发送任务通知。三个任务独立运行,发送通知任务是通过检测按 键的按下情况来发送通知,另两个任务获取通知,在任务通知中没有可用的通知 之前就一直等待任务通知,获取到通知以后就将通知值清 0,这样子是为了代替 二值信号量,任务同步成功则继续执行,然后在串口调试助手里将运行信息打印 显示。
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_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 任务通知代替计数信号量
实现的功能是:主要创建 2 个任务,一个是获取任务通知,一个是发 送任务通知,两个任务独立运行,获取通知的任务是通过按下 KEY1 按键获取, 模拟停车场停车操作,其等待时间是 0;发送通知的任务则是通过检测 KEY2 按 键按下进行通知的发送,模拟停车场取车操作,并且在串口调试助手输出相应信 息。
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_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 任务通知代替事件组
要实现的功能是:主要创建 2 个任务,一个是发送事件通知任务,一个是等待事件通知任务,两个任务独立运行,发送事件通知任务通过检测按键的按 下情况设置不同的通知值位,等待事件通知任务则获取这任务通知值,并且根据 通知值判断两个事件是否都发生,如果是则输出相应信息,LED 进行翻转。等待 事件通知任务的等待时间是 portMAX_DELAY,一直在等待事件通知的发生,等待
获取到事件之后清除对应的任务通知值的位。
freertos.c中添加代码如下:
/* 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_1 特性如下:
1、适用于那些一旦创建好任务、信号量和队列就再也不会删除的应用,实际上大多数FreeRTOS 应用都是这样的。
2、具有可确定性(执行所花费的时间大多数都是一样的),而且不会导致内存碎片。
3、代码实现和内存分配过程都非常简单,内存是从一个静态数组中分配到的,也就是适合于那些不需要动态内存分配的应用。

  • heap_2 内存分配方法 

heap_2提供了内存释放函数

heap_2 的特性如下:
1、可以使用在那些可能会重复的删除任务、队列、信号量等的应用中,要 注意有内存碎片产生!
2、如果分配和释放的内存 n 大小是随机的,那么就要慎重使用了,比如下面的示例:
a. 如果一个应用动态的创建和删除任务,而且任务需要分配的堆栈大小都是一样的,那么 heap_2 就非常合适。如果任务所需的堆栈大小每次都是不同, 那么 heap_2 就不适合了,因为这样会导致内存碎片产生,最终导致任务分配不 到合适的堆栈!不过 heap_4 就很适合这种场景了。
b.如果一个应用中所使用的队列存储区域每次都不同,那么 heap_2 就不适合了,和上面一样,此时可以使用 heap_4。
c.应用需要调用 pvPortMalloc()和 vPortFree()来申请和释放内存,而不是通过 FreeRTOS 的其它 API 函数来间接的调用,这种情况下 heap_2 不适 合。
3、如果应用中的任务、队列、信号量和互斥信号量具有不可预料性(如所需的内存大小不能确定,每次所需的内存都不相同,或者说大多数情况下所需的内 存都是不同的)的话可能会导致内存碎片。虽然这是小概率事件,但是还是要引 起我们的注意!
4、具有不可确定性,但是也远比标准 C 中的 mallo()和 free()效率高!heap_2 基本上可以适用于大多数的需要动态分配内存的工程中,而 heap_4 更是具有将内存碎片合并成一个大的空闲内存块(就是内存碎片回收)的功能。
  •  heap_3 内存分配方法

heap_3只是简单的封装了标准C库中的malloc()和free()函数,并且能够满足常用的编译器。采用的封装方式是操作内存前挂起调度器、完成后再恢复调度器。

heap_3.c 具有以下特点:
1、需要链接器设置一个堆,malloc()和 free()函数由编译器提供。
2、具有不确定性。
3、很可能增大 RTOS 内核的代码大小。

  • heap_4 内存分配方法
heap_4提供了一个最优匹配算法,会将内存碎片合并成一个大的可用的内存块,提供了内存块合并算法。
heap_4 特性如下:
1、可以用在那些需要重复创建和删除任务、队列、信号量和互斥信号量等的应用中。
2、不会像 heap_2 那样产生严重的内存碎片,即使分配的内存大小是随机的。
3、具有不确定性,但是远比 C 标准库中的 malloc()和 free()效率高。heap_4 非常适合于那些需要直接调用函数 pvPortMalloc()和 vPortFree() 来申请和释放内存的应用,注意,我们移植FreeRTOS 的时候就选择的 heap_4
  • heap_5 内存分配方法

heap_5使用了和heap_4相同的合并算法,内存管理实现起来基本相同,但是heap_5允许内存堆跨越多个不连续的内存段。

使用 heap_5 的话,在调用 API 函数之前需要先调用函数vPortDefineHeapRegions ()来对内存堆做初始化处理,在 vPortDefineHeapRegions()未执行完之前禁止调用任何可能会调用
pvPortMalloc()的 API 函数!比如创建任务、信号量、队列等函数。

10.3、CubeMX工程创建

要实现的功能是:使用 heap_4.c 方案进行内存管理测试,创建了两个任务,分别是 LED 任务与内存管理测试任务,内存管理测试任务通过检测按键 是否按下来申请内存或释放内存,当申请内存成功就向该内存写入一些数据,如 当前系统的时间等信息,并且通过串口输出相关信息;LED 任务是将 LED 翻转, 表示系统处于运行状态。
freertos.c中添加代码:
/* 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中,临界段最常出现的就是对全局变量的操作。

那么什么情况下临界段会被打断?一个是系统调度,还有一个就是外部中断。在 FreeRTOS,系统调度,最终也是产生 PendSV 中断,在 PendSV Handler 里面实现任务的切换,所以还是可以归结为中断。既然这样,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 */
}

串口打印结果如下:

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值