9.FreeRTOS_信号量

信号量概述

信号量的本质也是一个队列

信号量中传输的是一个数值,因此信号量只能表示资源可用的数量,而不能传输数据。具体的信号量模型如下图:

信号量有两种:计数型信号量、二进制信号量,它们之间的区别在于计数值的范围

  • 计数型信号量可以取值0~任意值,在创建时可以设置当前的计数值
  • 二进制信号量取值只能为0、1,在创建时自动设置为0

使用信号量的方法就如模型一样,只有3个步骤:

  • 创建信号量
  • 存入信号量,这就是让计数值+1
  • 获取信号量,这就是让计数值-1

相关配置

在使用信号量之前,需要打开宏开关,具体的步骤如下:

信号量相关函数

创建计数型信号量

函数声明如下:

/* 这是一个宏,创建计数型信号量 */
xSemaphoreCreateCounting( uxMaxCount, uxInitialCount )

/* 宏定义 */
#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount )  \              
        xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )

/* 实际调用函数 */
QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount,
                                             const UBaseType_t uxInitialCount )

返回值:信号量句柄

uxMaxCount:计数值的最大值

uxInitialCount :计数值的初始值

创建二进制信号量

函数声明如下:

/* 这是一个宏,创建二进制信号量 */
xSemaphoreCreateBinary();

/* 宏定义 */
#define xSemaphoreCreateBinary() \   
        xQueueGenericCreate( ( UBaseType_t ) 1, 
                             semSEMAPHORE_QUEUE_ITEM_LENGTH,
                             queueQUEUE_TYPE_BINARY_SEMAPHORE )

/* 实际调用函数 */
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,
                                   const UBaseType_t uxItemSize,
                                   const uint8_t ucQueueType )

返回值:信号量句柄

存入(释放)信号量

函数声明如下:

/* 这是一个宏,存入(释放)信号量 */
xSemaphoreGive( xSemaphore )

/* 宏定义 */
#define xSemaphoreGive( xSemaphore )   \ 
        xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ),
                             NULL, 
                             semGIVE_BLOCK_TIME, 
                             queueSEND_TO_BACK )

/* 实际调用函数 */
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
                              const void * const pvItemToQueue,
                              TickType_t xTicksToWait,
                              const BaseType_t xCopyPosition )

返回值:成功返回pdPASS,在当前计数值=最大计数值时,会存入失败

xSemaphore :信号量句柄

获取信号量

函数声明如下:

/* 这是一个宏,获取信号量 */
xSemaphoreTake( xSemaphore, xBlockTime )

/* 宏定义 */
#define xSemaphoreTake( xSemaphore, xBlockTime )  \
        xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ) )

/* 实际调用函数 */
BaseType_t xQueueSemaphoreTake( QueueHandle_t xQueue,
                                TickType_t xTicksToWait )

xSemaphore :信号量句柄

xBlockTime :阻塞等待时间,portMAX_DELAY为死等

验证实验

使用二进制信号量来确保串口的输出不被打断。

具体代码如下:

QueueHandle_t SemaphoreHandleTest;/* 信号量句柄 */
void PrintFunction(void *param){
	
	while(1){
		xSemaphoreTake(SemaphoreHandleTest,portMAX_DELAY);/* 获取串口资源 */
		
        printf("%s",(char*)param);

		xSemaphoreGive(SemaphoreHandleTest);/* 释放串口资源 */
		vTaskDelay(10);/* 必须释放CPU资源 */
	}
}

int main( void )
{
	
	TaskHandle_t xHandleTask1;
	TaskHandle_t xHandleTask2;
	
	prvSetupHardware();
	SerialPortInit();
	printf("UART TEST\r\n");
	
	/* 创建二进制信号量,初始值为0*/
	SemaphoreHandleTest = xSemaphoreCreateBinary();
	/* 让信号量为1,代表串口资源可用 */
	xSemaphoreGive(SemaphoreHandleTest);
	
	xTaskCreate(PrintFunction,"Task1",100,(void*)"this is task1\r\n",1,&xHandleTask1);
	xTaskCreate(PrintFunction,"Task2",100,(void*)"this is task2\r\n",1,&xHandleTask2);

	vTaskStartScheduler();
	
	return 0;
}

运行结果如下:

可以看到,这两句话都进行完整的打印,并没有出现打印到一半就被调度器切换到其他任务的情况。

FreeRTOS 中,**邮箱**(通常通过消息队列实现)和**信号量**是两种常用的任务间通信机制,但它们在功能和适用场景上有显著区别。 ### 功能区别 - **邮箱**: 邮箱用于在任务之间传递数据,可以是任意类型的数据,只要不超过消息队列定义的最大大小。消息队列支持多个任务发送和接收消息,且允许等待队列有空间或数据,支持超时机制[^1]。 邮箱的一个特例是使用任务通知替代的消息邮箱,它只能传递一个简单的通知值,不支持超时等待。 - **信号量**: 信号量是一种同步机制,主要用于控制对共享资源的访问或任务间的同步。FreeRTOS 支持三种类型的信号量:二值信号量、计数信号量和互斥信号量。 - **二值信号量**:只有两种状态,0 或 1,常用于任务间的简单同步。 - **计数信号量**:可以有多个状态值,用于管理多个相同资源的访问。 - **互斥信号量**:专为解决资源独占访问问题设计,支持优先级继承机制,防止优先级反转问题[^2]。 ### 使用场景 - **邮箱**: 当需要在任务之间传递较大的数据结构或多个不同类型的数据时,使用消息队列(即邮箱)更为合适。例如,一个任务收集传感器数据并将其放入队列,另一个任务从队列中取出数据进行处理。 邮箱也适用于需要缓冲数据的情况,特别是在生产者-消费者模型中。 - **信号量**: - **二值信号量**:适用于任务间的基本同步,比如一个任务完成某项工作后通知另一个任务继续执行。 - **计数信号量**:当有多个相同的资源需要管理时,例如多个缓冲区或多个设备实例。 - **互斥信号量**:当需要确保某个资源在同一时间只能被一个任务访问时,例如访问共享内存或外设寄存器[^2]。 ### 性能与资源消耗 - **邮箱**: 消息队列需要额外的内存来存储消息,并且在创建时需要指定队列的长度和每个消息的大小。因此,使用消息队列会占用更多的内存资源。 - **信号量**: 信号量相对轻量级,只需要一个计数值和一些内部状态信息。互斥信号量由于需要支持优先级继承机制,因此稍微复杂一些,但仍然比消息队列更节省资源。 ### 示例代码 #### 消息队列(邮箱) ```c #include "FreeRTOS.h" #include "queue.h" // 定义队列句柄 QueueHandle_t xQueue; void vTaskProducer(void *pvParameters) { int32_t lValueToSend = 100; for (;;) { // 向队列发送数据 if (xQueueSend(xQueue, &lValueToSend, portMAX_DELAY) != pdPASS) { // 发送失败处理 } vTaskDelay(pdMS_TO_TICKS(1000)); // 延迟1秒 } } void vTaskConsumer(void *pvParameters) { int32_t lReceivedValue; for (;;) { // 从队列接收数据 if (xQueueReceive(xQueue, &lReceivedValue, portMAX_DELAY) == pdPASS) { // 处理接收到的数据 } } } int main(void) { // 创建消息队列 xQueue = xQueueCreate(10, sizeof(int32_t)); if (xQueue != NULL) { // 创建任务 xTaskCreate(vTaskProducer, "Producer", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL); xTaskCreate(vTaskConsumer, "Consumer", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL); // 启动调度器 vTaskStartScheduler(); } else { // 队列创建失败处理 } for (;;); } ``` #### 信号量(二值信号量) ```c #include "FreeRTOS.h" #include "semphr.h" // 定义信号量句柄 SemaphoreHandle_t xBinarySemaphore; void vTaskA(void *pvParameters) { for (;;) { // 执行某些操作 // 释放信号量 xSemaphoreGive(xBinarySemaphore); vTaskDelay(pdMS_TO_TICKS(1000)); // 延迟1秒 } } void vTaskB(void *pvParameters) { for (;;) { // 等待信号量 if (xSemaphoreTake(xBinarySemaphore, portMAX_DELAY) == pdTRUE) { // 信号量获取成功,执行后续操作 } } } int main(void) { // 创建二值信号量 xBinarySemaphore = xSemaphoreCreateBinary(); if (xBinarySemaphore != NULL) { // 创建任务 xTaskCreate(vTaskA, "Task A", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL); xTaskCreate(vTaskB, "Task B", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL); // 启动调度器 vTaskStartScheduler(); } else { // 信号量创建失败处理 } for (;;); } ``` ### 总结 - **邮箱**适合用于传递数据,尤其是需要缓冲或多任务间共享数据的场景。 - **信号量**更适合用于任务同步和资源管理,特别是需要确保资源独占访问的场景。 根据具体的应用需求选择合适的机制,可以提高系统的效率和可靠性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值