FreeRTOS入门笔记(任务管理,消息队列,信号量)

2023-05-16

FreeRTOS

一、任务

FreeRTOS操作系统支持多任务并发执行,可以看成每个任务可以写一个‘main’函数,在死循环里执行。

1.任务创建与删除

创建

(1)任务可以在CubeMx中创建,设置任务名称、优先级、堆栈大小。

(2)在程序中:

MX_FREERTOS_Init();

调用 osThreadCreate来创建一个任务,函数返回一个任务句柄。

defaultTask:任务名称

StartDefaultTask:任务的启动函数,逻辑代码在这里面写,。

osPriorityBelowNormal:任务优先级

osThreadId defaultTaskHandle;

osThreadDef(defaultTask, StartDefaultTask, osPriorityBelowNormal, 0, 128);
  defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);

删除

vTaskDelete(LEDTaskHandle);

2.任务调度

(1)osKernelStart():开启任务调度,之后程序就交给操作系统了,总是在中断和任务中来回切换。

(2)在每个任务的启动函数中编写逻辑代码,注意每个启动函数中都要有osDelay,因为多任务的实现机制就是在就绪态中取优先级最高的执行,当每个任务处于osDelay的时候进入阻塞,使其他低优先级的任务得以调度。不可使用HAL_Delay,这个不行。osDelay可以让任务定时进入阻塞态。

void StartDefaultTask(void const * argument)
{
  for(;;)
  {
		printf("test 1 running\r\n");
    	osDelay(1000);
  }
}

3.任务挂起与解挂

任务挂起后就不会处于就绪态和运行态了,相当于暂停该任务,该任务的所有信息都还保存着。

(1)任务挂起:

vTaskSuspend(LEDTaskHandle);
osThreadSuspendAll();//全部挂起

(2)任务解挂:

vTaskResume(LEDTaskHandle);
vTaskResumeFromISR();//中断中使用
osThreadResumeAll();//全部继续

说明:

单个任务不管挂起几次,解挂一次就可以了;传递NULL挂起自身。

全部挂起几次就需要解挂几次,本质是挂起任务调度器,导致不能切换上下文,中间会有一个变量记录被挂起了几次,但是中断还能执行,不过如果是进入需要进行上下文切换的中断也会被挂起,要是全部挂起了我应该在哪里全部解挂,是中断中吗?

ISR是用于中断中的,具体为什么中断中要用不一样的,我还没有考究。

4.代码测试

uint8_t task2 = 0;

void StartLEDTask(void const * argument)
{
  for(;;)
  {
		printf("test 2 running\r\n");
		task2++;
    osDelay(1000);
  }
}

void StartHC_SR04Task(void const * argument)
{
  for(;;)
  {
		if(task2>5)
			{
				task2=0;
				vTaskSuspend(LEDTaskHandle);
				printf("任务2已挂起\r\n");
				osDelay(2200);
				
				vTaskResume(LEDTaskHandle);
				printf("任务2继续\r\n");
				osDelay(2200);
				
				vTaskDelete(LEDTaskHandle);
				printf("任务2已删除\r\n");
				osDelay(2200);
				
				osThreadDef(LEDTask, StartLEDTask, osPriorityIdle, 0, 128);
				LEDTaskHandle = osThreadCreate(osThread(LEDTask), NULL);
				printf("任务2已创建\r\n");
				
			}
    osDelay(1);
  }
}

二、消息队列

消息队列是一个队列,它不属于任何一个任务,支持各任务间的异步通信。比如说定义了一个消息队列,那么每个任务都可以向它里面发数据,也可以从里面读数据,一般常用于多个任务发,一个任务收。

1.FreeRTOS消息队列特点

(1)支持先入先出和后入先出两种模式,不过我们一般用先入先出。

(2)读写队列均支持超时机制。如果读时队列为空或者写时队列为满,可以设置超时时间,在时间之内任务阻塞等待,超时后进入就绪并且可能有一个标志或者出发一个错误提醒。

(3)入队和出队是拷贝赋值的方式,不是引用。如果数据过大,可以选择传递指针,不过应注意发送方和接收方对这段地址的读写访问冲突,即保证收发只有一方在操作。

(4)注意在中断中调用的函数要用其专门的ISR函数(下面就不说ISR了,自己可以对应这搜一下),原因是大概是因为中断嵌套是中断优先级导致出错,挺复杂的,这个有说一些,但是我没太看明白https://www.cnblogs.com/w-smile/p/11333950.html。

2.消息队列使用

(1)创建消息队列

QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );

uxQueueLength:队列长度

uxItemSize:队列中每个元素的大小

返回一个消息队列句柄

技巧:一般使用时传递指针,可以节省空间,即uxItemSize=sizeof(uint32_t)。

(2)发送消息

BaseType_t xQueueSend(QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait);

xQueue:消息队列句柄

pvItemToQueue:发送数据地址

xTicksToWait:等待时间

返回状态信息

其他常用函数

xQueueSendToFront():一般用于紧急消息,需要立即处理.

xQueueOverwrite():可在队列满时,自动覆盖最旧的消息。

(3)接受消息

BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait); 

(对应于发送)

BaseType_t xQueuePeek(QueueHandle_t xQueue, void * pvBuffer, TickType_t xTicksToWait);//用于从队列中接收一个消息,但读取之后消息还保留在队列中

3.应用示例

简单拷贝赋值消息队列的创建与收发:

#include "queue.h"
#include "usart.h"

QueueHandle_t xQueue;  //消息队列

void StartDefaultTask(void const * argument)
{
	xQueue = xQueueCreate(10, sizeof(uint32_t));//这个函数需要放到一开始可以执行的程序地方,我这里放到默认的任务起始执行,并设置默认任务的优先级高于其他任务,保证这句话先执行。 也可以放到其他合适的地方,后来发现放到MX_FREERTOS_Init()函数的结尾比较合适
  	for(;;)
  	{
        printf("task one running\r\n");
  		osDelay(1000);
 	}
}

//该任务发送消息到队列
void StartLEDTask(void const * argument)
{
	uint32_t ulValueToSend = 0;
  	for(;;)
  	{
		xQueueSend(xQueue, &ulValueToSend, 1000);  //向队列里发送数据
		ulValueToSend++;
    	osDelay(1000);
  	}
}

//接受消息队列
void StartHC_SR04Task(void const * argument)
{
	uint32_t ulReceivedValue;
  	for(;;)
  	{
		 if (xQueueReceive(xQueue, &ulReceivedValue, portMAX_DELAY) == pdTRUE)
		printf("Q:%d\r\n", ulReceivedValue);
    	osDelay(100);
  }
}

一般的消息队列元素可以封装到结构体里,用一个变量标记发送方。https://blog.csdn.net/weixin_44333597/article/details/107725480有讲一点。

三、信号量

信号量是消息队列的一种应用,内部都是调用的消息队列的函数,可以用于进程同步、对临界资源互斥访问等功能。

信号量是一个队列,这个队列的长度根据类型设置有所不同,队列中的元素大小都是NULL。也就是说,入队的元素都是NULL,那么其实就是不会将在队列中存东西,但是队列句柄中有一个变量uxMessagesWaiting 表示队列中有几个元素,它的值是会在每次入队的时候+1,出队的时候-1。这样就可以通uxMessagesWaiting 与uxLength队列长度进行比较就可以知道是否可以出队入队。

其实就和队列一样,在队空和堆满的时候任务会进入阻塞状态,有一个等待时长,通过返回值判断是否成功。就是因为有这个阻塞在,所以可以达到进程同步或者共享资源访问控制的目的。

参考:http://t.csdn.cn/tiJG4(这个针对源码分析)

​ http://t.csdn.cn/Y3u5X(这个适合学习使用)

1.创建信号量

(1)二值信号量

#define semSEMAPHORE_QUEUE_ITEM_LENGTH		( ( uint8_t ) 0U )

#define xSemaphoreCreateBinary()       xQueueGenericCreate( ( UBaseType_t ) 1,                                                semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )

可以看到,创建二值信号量就是创建了一个长度为1,元素大小为0的消息队列,传入标记是一个二值信号量,在队列操作中会根据类型来自己处理。

创建好的二值信号量默认是空的,也就是说使用xSemaphoreTake()是获取不到的。

二值信号量的uxMessagesWaiting值只能在0或1之间转变。适用于任务同步。

xSemaphoreCreateBinary()

返回:信号量句柄,失败返回NULL。

示例:

SemaphoreHandle_t xSemaphore = NULL;

xSemaphore = xSemaphoreCreateBinary();

if (NULL == xSemaphore)
{
	printf("xSemaphoreCreateBinary error!\r\n");
}

(2)互斥信号量

互斥信号量的队列设置和二值信号量是类似的,只不过因为二值信号量在应用时可能会发生优先级翻转的情况,实时性有所损失,互斥信号量在其后执行了prvInitialiseMutex()进行初始化。互斥信号量具有优先级继承机制,在发生获取信号量的时候,会将发送信号量的任务的优先级设置为和自己一样,直到任务发送信号量之后才会将优先级恢复,避免了优先级翻转。不过最好不要用互斥信号量的ISR。适用于互斥访问。

#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )

xQueueCreateMutex()

返回值:

NULL :创建失败

其他值:创建成功返回的信号量句柄

示例:

 SemaphoreHandle_t xSemaphore;

xSemaphore = xSemaphoreCreateMutex();

if (NULL != xSemaphore)  //成功

(3)计数信号量

计数信号量创建的消息队列的长度可以不为1,则计数可以在0到这个数之间。

#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )

xSemaphoreCreateCounting()

参数:

uxMaxCount:计数信号量最大计数值,当信号量值等于此值的时候释放信号量就会失败。

uxInitialCount :计数信号量初始值

返回值:

NULL:创建失败

其他值,创建成功,返回信号量句柄。

示例:

SemaphoreHandle_t xSemaphore;

xSemaphore = xSemaphoreCreateCounting( 10, 0 );

2.发送信号量

#define xSemaphoreGive( xSemaphore ) xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )

xSemaphoreGive( xSemaphore )

参数:

xSemaphore :要释放的信号量句柄

返回值:

pdPASS:释放成功

errQUEUE_FULL:释放失败。

示例:

if( xSemaphoreGive( xSemaphore ) != pdTRUE ) 
{
	printf("xSemaphoreGive error!\r\n");
}

3.接收信号量

#define xSemaphoreTake( xSemaphore, xBlockTime ) xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ) )

xSemaphoreTake( xSemaphore, xBlockTime ) 

参数:

xSemaphore:要获取的信号量句柄

xBlockTime :阻塞时间

返回值:

pdTRUE:成功

pdFALSE:失败

示例:

if( xSemaphoreTake( xSemaphore, ( TickType_t ) 10 ) == pdTRUE )
{
    //成功
}

4.应用示例

二值信号量优先级翻转测试:创建一个二值信号量,初始时give。设置任务在执行前必须要take到才可以执行,执行完后give释放,这样的话就符合实际中的进程同步,例如高优先级的任务会打断低优先级的任务,如果想让低优先级的任务执行不被打断,那么就用二值信号量,当低优先级任务执行时锁住,让高优先级任务阻塞。任务1与任务3进行信号量访问,任务1可以设置任务时间较长一点,用一个for循环占用时间,以此来测试。任务2正常运行。

SemaphoreHandle_t Binary_Handler = NULL;  //二值信号量句柄

//这一段放到初始化MX_FREERTOS_Init()里
{
    Binary_Handler = xSemaphoreCreateBinary();	  //创建二值信号量if (Binary_Handler == NULL)
    {
        printf("Semaphore Binary Creat Failed!!!\r\n");
    }
    xSemaphoreGive(Binary_Handler);  //先给一个
}



void StartDefaultTask(void const * argument)
{
	long i;
    for(;;)
    {
          if (xSemaphoreTake(Binary_Handler,portMAX_DELAY) == pdTRUE)
          {
              printf("low task running\r\n");
              for (i=0; i<22222222; i++);  //让任务1执行时间长
              printf("low task give\r\n"); //执行完毕释放
              xSemaphoreGive(Binary_Handler);
          }
          else 
              printf("low task xSemaphoreTake error!\r\n");
          osDelay(1000);
    }
}

void StartLEDTask(void const * argument)
{
  for(;;)
  {
	printf("middle task running\r\n");
    osDelay(1000);
  }
}

void StartHC_SR04Task(void const * argument)
{
  for(;;)
  {
		printf("high task ask the semaphore\r\n");
		if (xSemaphoreTake(Binary_Handler,portMAX_DELAY) == pdTRUE)
		{
			printf("high task running\r\n");
			printf("high task give\r\n");
			xSemaphoreGive(Binary_Handler);
		}
		else 
			printf("high task xSemaphoreTake error!\r\n");
    osDelay(1000);
  }
}


在这里插入图片描述

现象说明:

①高优先级先执行,执行完后osDelay(1000)进入阻塞;

②中优先级执行,阻塞;

③低优先级执行,一直在for,时间较长,还未结束;

④高优先级阻塞完毕,进入就绪,抢占了低优先级使用权,但是由于低优先级还没有执行完毕give,所以高优先级在take处阻塞;

⑤低优先级继续执行for;

⑥中优先级阻塞结束,进入就绪,抢占低优先级执行,执行完后阻塞;

…(低优先级与中优先级交替进行,低优先级在中优先级阻塞的时候继续执行)

⑦低优先级执行结束,give,此时高优先级立马抢占执行。

发现中间高优先级因为take不到被阻塞了,导致优先级更低的中优先级先于它执行,出现优先级翻转现象。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FreeRTOS入门笔记(任务管理,消息队列,信号量) 的相关文章

随机推荐