完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
本章节为大家讲解FreeRTOS消息队列(消息队列长度固定为1)的另一种实现方式----基于任务通知(Task Notifications)的消息队列,这里我们将这种方式实现的消息队列(消息队列长度固定为1)称之为任务消息邮箱。这种方式实现的消息队列效率更高,需要的RAM空间更小。当然,缺点也是有的,它没有第20章介绍的消息队列实现的功能全面(消息邮箱就是将消息队列长度设置为1的情况)。
本章教程配套的例子含Cortex-M3内核的STM32F103和Cortex-M4内核的STM32F407以及F429。 27.1 任务通知(TaskNotifications)介绍 27.2 任务消息邮箱 27.3 任务消息邮箱API函数 27.4 实验例程说明(任务间通信) 27.5 实验例程说明(中断方式) 27.6 总结 |
|
相关推荐
|
|
27.1 任务通知(TaskNotifications)介绍
(说明:第24,25,26和27章这部分基础知识是相同的) FreeRTOS每个已经创建的任务都有一个任务控制块(task control block),任务控制块就是一个结构体变量,用于记录任务的相关信息。结构体变量中有一个32位的变量成员ulNotifiedValue是专门用于任务通知的。 通过任务通知方式可以实现计数信号量,二值信号量,事件标志组和消息邮箱(消息邮箱就是消息队列长度为1的情况)。使用方法与前面章节讲解的事件标志组和信号量基本相同,只是换了不同的函数来实现。任务通知方式实现的计数信号量,二值信号量,事件标志组和消息邮箱是通过修改变量ulNotifiedValue实现的: (1)设置接收任务控制块中的变量ulNotifiedValue可以实现消息邮箱。 (2)如果接收任务控制块中的变量ulNotifiedValue还没有被其接收到,也可以用新数据覆盖原有数据,这就是覆盖方式的消息邮箱。 (3)设置接收任务控制块中的变量ulNotifiedValue的bit0-bit31数值可以实现事件标志组。 (4)设置接收任务控制块中的变量ulNotifiedValue数值进行加一或者减一操作可以实现计数信号量和二值信号量。 介绍了这么多,那么问题来了,采用这种方式有什么优势呢?根据官方的测试数据,唤醒由于信号量和事件标志组而处于阻塞态的任务,速度提升了45%,而且这种方式需要的RAM空间更小。但这种方式实现的信号量和事件标志组也有它的局限性,主要表现在以下两个方面: (1)任务通知方式仅可以用在只有一个任务等待信号量,消息邮箱或者事件标志组的情况,不过实际项目项目中这种情况也是最多的。 (2)使用任务通知方式实现的消息邮箱替代前面章节讲解的消息队列时,发送消息的任务不支持超时等待,即消息队列中的数据已经满了,可以等待消息队列有空间可以存新的数据,而任务通知方式实现的消息邮箱不支持超时等待。 |
|
|
|
|
|
27.2 任务消息邮箱
前面第20章,我们对消息队列进行了讲解,而消息邮箱就是将消息队列的长度设置为1的情况。本章节讲解的任务消息邮箱与第20章讲解的消息队列长度是1时要实现的功能是一样的,不同的是调用的函数和消息存储的位置: (1)任务消息邮箱是通过任务控制块中的一个32位变量ulNotifiedValue对数据进行存取。第20章讲解的消息队列创建后会有自己可以存取数据的空间。 (2)任务消息邮箱是通过函数xTaskNotifyWait()替代第20章讲解的函数xQueueReceive()实现从消息邮箱获取数据。 (3)任务消息邮箱是通过函数xTaskNotify() 和 xQueueSendFromISR ()替代第20章讲解的函数xQueueSend () 和 xEventGroupSetBitsFromISR实现向消息邮箱存入数据。 (4)任务消息邮箱通过函数xTaskNotify向消息邮箱中发送数据时,如果消息邮箱中上次的数据还没有处理,不支持超时等待,而消息队列的函数xQueueSend是支持超时等待的。 具体任务间任务消息邮箱的实现过程和中断方式任务消息邮箱的实现过程可以参看第20.1.2小节和20.1.3小节即可。实际项目中,如果使用任务消息邮箱和消息队列都能实现相应功能,强烈建议使用任务消息邮箱。 |
|
|
|
|
|
27.3 任务消息邮箱API函数
使用如下9个函数可以实现FreeRTOS的任务消息邮箱: (1) xTaskNotifyGive() (2) vTaskNotifyGiveFromISR() (3) ulTaskNotifyTake() (4) xTaskNotify() (5) xTaskNotifyAndQuery() (6) xTaskNotifyAndQueryFromISR() (7) xTaskNotifyFromISR() (8) xTaskNotifyWait() (9) xTaskNotifyStateClear() |
|
|
|
|
|
关于这9个函数的讲解及其使用方法可以看FreeRTOS在线版手册:
这里我们重点的说以下3个函数: (1) xTaskNotify (2) xTaskNotifyFromISR (3) xTaskNotifyWait 因为本章节配套的例子使用的是这3个函数。 |
|
|
|
27.3.1 函数xTaskNotify
函数原型: [url=]复制代码[/url]
函数描述: 函数xTaskNotify通过设置任务控制块中的变量ulNotifiedValue可以在任务代码中实现任务事件标志组,任务计数信号量,任务消息邮箱和任务二值信号量四种方式的消息通知(见27.1说明)。 (1)第1个参数是任务句柄。 (2)第2个参数是用来更新任务控制块中的32位变量ulNotifiedValue。 (3)第3个参数是任务通知模式设置,支持以下5个参数: (4)返回值,根据上面第3个参数的说明,将其设置为eSetValueWithoutOverwrite,有可能返回pdFALSE,其余所有情况都返回值pdPASS。 |
|
|
|
|
|
27.3.2 函数xTaskNotifyFromISR
函数原型: 复制代码 BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify, /* 任务句柄 */ uint32_t ulValue, /* 更新任务控制块中的变量ulNotifiedValue */ eNotifyAction eAction, /* 任务通知模式设置 */ BaseType_t *pxHigherPriorityTaskWoken ); /* 高优先级任务是否被唤醒的状态保存 */ |
|
|
|
|
|
函数描述:
函数xTaskNotifyFromISR通过设置任务控制块中的变量ulNotifiedValue可以在中断服务程序中实现任务事件标志组,任务计数信号量,任务消息邮箱和任务二值信号量四种方式的消息通知(见27.1说明)。 (1)第1个参数是任务句柄。 (2)第2个参数是用来更新任务控制块中的32位变量ulNotifiedValue。 (3)第3个参数是任务通知模式设置,支持以下5个参数: (4)第4个参数用于保存是否有高优先级任务准备就绪。如果函数执行完毕后,此参数的数值是pdTRUE,说明有高优先级任务要执行,否则没有。 (5)返回值,根据上面第3个参数的说明,将其设置为eSetValueWithoutOverwrite,有可能返回pdFALSE,其余所有情况都返回值pdPASS。 |
|
|
|
|
|
使用这个函数要注意以下问题:
1. 任务创建后,任务控制块中的变量ulNotifiedValue初始计数值是0。 2. 默认配置此函数可以使用的的宏定义已经在FreeRTOS.h文件中使能: #define configUSE_TASK_NOTIFICATIONS 1 当然,如果用户不需要使用任务通知功能相关的函数,可以在FreeRTOSConfig.h文件中配置此宏定义为0来禁止,这样创建的每个任务可以节省8个字节的需求。 3. 此函数是用于中断服务程序中调用的,故不可以在任务代码中调用此函数,任务代码中使用的是xTaskNotify。 4. 根据FreeRTOS的建议,实现二值信号量和计数信号量时使用函数vTaskNotifyGiveFromISR ()替代此函数xTaskNotifyFromISR()。 使用举例: 复制代码 static TaskHandle_t xHandleTaskMsgPro = NULL;/* 任务句柄,操作此句柄前一定要保证相应任务已经创建 */ /* ********************************************************************************************************* * 函 数 名: TIM1_IRQHandler * 功能说明: 定时器中断。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static uint32_t g_uiCount = 0; /* 设置为静态变量,方便查看数据更新 */ static void TIM1_IRQHandler (void) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; /* 中断消息处理,此处省略 */ …… xTaskNotifyFromISR(xHandleTaskMsgPro, /* 目标任务 */ g_uiCount++, /* 发送数据 */ eSetValueWithOverwrite,/* 如果目标任务上次的数据还没有处理,上次的数据会被覆盖 */ &xHigherPriorityTaskWoken); /* 如果xHigherPriorityTaskWoken = pdTRUE,那么退出中断后切到当前最高优先级任务执行 */ portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } /* ********************************************************************************************************* * 函 数 名: TIM2_IRQHandler * 功能说明: 定时器中断。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void TIM2_IRQHandler (void) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; /* 中断消息处理,此处省略 */ …… /* 通过此函数的返回值可以判断是否发送成功,这里没有做判断 */ xTaskNotifyFromISR(xHandleTaskMsgPro, /* 目标任务 */ g_uiCount++, /* 发送数据 */ eSetValueWithoutOverwrite,/* 如果目标任务上次的数据还没有处理,上次的数据不会被覆盖 */ &xHigherPriorityTaskWoken); /* 如果xHigherPriorityTaskWoken = pdTRUE,那么退出中断后切到当前最高优先级任务执行 */ portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } |
|
|
|
|
|
27.3.3 函数xTaskNotifyWait
函数原型: 复制代码 BaseType_t xTaskNotifyWait( /* 设置函数执行前清零任务控制块中变量ulNotifiedValue那些位 */ uint32_t ulBitsToClearOnEntry, /*设置函数退出前清零任务控制块中变量ulNotifiedValue那些位 */ uint32_t ulBitsToClearOnExit, /* 保存任务控制块中的变量ulNotifiedValue到指针变量pulNotifiedValue所指向的存储单元 */ uint32_t *pulNotificationValue, /* 等待消息通知的最大等待时间 */ TickType_t xTicksToWait ); |
|
|
|
|
|
函数描述:
函数xTaskNotifyWait可以在任务代码中实现任务事件标志组,任务计数信号量,任务消息邮箱和任务二值信号量四种方式的消息获取(见27.1说明)。 (1)第1个参数ulBitsToClearOnEntry用于函数执行之前,将任务控制块中的变量ulNotifiedValue进行如下操作 : ulNotifiedValue&= ~ulBitsToClearOnEntry 简单的说就是参数ulBitsToClearOnEntry哪个位是1,那么变量ulNotifiedValue的那个位就会被清零。比如ulBitsToClearOnEntry = 0x01表示将变量ulNotifiedValue的bit0清零,又比如ulBitsToClearOnEntry = 0xffffffff表示将变量ulNotifiedValue的所有位清零。 (2)第2个参数 ulBitsToClearOnExit用于函数退出前,将任务控制块中的变量ulNotifiedValue进行如下操作 : ulNotifiedValue&= ~ ulBitsToClearOnExit 简单的说就是参数ulBitsToClearOnExit哪个位是1,那么变量ulNotifiedValue的那个位就会被清零。比如ulBitsToClearOnExit= 0x01表示将变量ulNotifiedValue的bit0清零,又比如ulBitsToClearOnExit= 0xffffffff表示将变量ulNotifiedValue的所有位清零。 (3)第3个参数用于将任务控制块中的变量ulNotifiedValue保存到此参数指针所指向的存储单元。如果此参数没有用上,可以将其设置为NULL。 (4)第4个参数是没有消息时,等待消息的最大等待时间,单位系统时钟节拍。 (5)返回值,如果成功接收到消息返回pdTRUE,否则返回pdFALSE,比如在设置的超时时间内没有收到消息。 |
|
|
|
|
|
使用这个函数要注意以下问题:
1. 任务创建后,任务控制块中的变量ulNotifiedValue初始计数值是0。 2. 默认配置此函数可以使用的的宏定义已经在FreeRTOS.h文件中使能: #define configUSE_TASK_NOTIFICATIONS 1 当然,如果用户不需要使用任务通知功能相关的函数,可以在FreeRTOSConfig.h文件中配置此宏定义为0来禁止,这样创建的每个任务可以节省8个字节的需求。 3. 如果用户将FreeRTOSConfig.h文件中的宏定义INCLUDE_vTaskSuspend配置为1且第2个参数配置为portMAX_DELAY,那么此函数会永久等待直到消息可用。 4. 根据FreeRTOS的建议,实现二值信号量和计数信号量时使用函数ulTaskNotifyTake ()替代此函数xTaskNotifyWait ()。 |
|
|
|
|
|
使用举例:
复制代码 /* ********************************************************************************************************* * 函 数 名: vTaskMsgPro * 功能说明: 使用函数xTaskNotifyWait接收任务vTaskTaskUserIF发送的消息邮箱数据 * 形 参: pvParameters 是在创建该任务时传递的形参 * 返 回 值: 无 * 优 先 级: 3 ********************************************************************************************************* */ static void vTaskMsgPro(void *pvParameters) { BaseType_t xResult; const TickType_t xMaxBlockTime = pdMS_TO_TICKS(500); /* 设置最大等待时间为500ms */ uint32_t ulValue; while(1) { /* 第一个参数 ulBitsToClearOnEntry的作用(函数执行前): ulNotifiedValue &= ~ulBitsToClearOnEntry 简单的说就是参数ulBitsToClearOnEntry那个位是1,那么ulNotifiedValue 的那个位就会被清零。 这里ulBitsToClearOnEntry = 0x00000000就是函数执行前保留所有位。 第二个参数 ulBitsToClearOnExit的作用(函数退出前): ulNotifiedValue &= ~ulBitsToClearOnExit 简单的说就是参数ulBitsToClearOnEntry那个位是1,那么ulNotifiedValue 的那个位就会被清零。 这里ulBitsToClearOnExit = 0xFFFFFFFF就是函数退出前清楚所有位。 注:ulNotifiedValue表示任务vTaskMsgPro的任务控制块里面的变量,用来做消息邮箱数据的存取。 */ xResult = xTaskNotifyWait(0x00000000, 0xFFFFFFFF, &ulValue, /* 保存ulNotifiedValue到变量ulValue中 */ xMaxBlockTime); /* 最大允许延迟时间 */ if( xResult == pdPASS ) { printf("接收到消息邮箱数据ulValue = %drn", ulValue); } else { /* 超时 */ bsp_LedToggle(1); bsp_LedToggle(4); } } } |
|
|
|
|
|
27.4 实验例程说明(任务间通信)
27.4.1 STM32F103开发板实验 配套例子: V4-331_FreeRTOS实验_任务消息邮箱 实验目的: 1. 学习FreeRTOS的任务通知实现消息邮箱。 实验内容: 1. K1按键按下,串口打印任务执行情况(波特率115200,数据位8,奇偶校验位无,停止位1)。 2. K2键按下,发送消息邮箱数据给任务vTaskMsgPro,覆盖方式。 3. K3键按下,发送消息邮箱数据给任务vTaskMsgPro,非覆盖方式。 4. 本实验通过函数xTaskNotify和xTaskNotifyWait实现消息邮箱的功能。 5. 各个任务实现的功能如下: vTaskUserIF任务 :按键消息处理。 vTaskLED任务 :LED闪烁。 vTaskMsgPro任务:使用函数xTaskNotifyWait接收任务vTaskTaskUserIF发送的消息邮箱数据。 vTaskStart任务 :启动任务,也是最高优先级任务,这里实现按键扫描。 |
|
|
|
|
|
FreeRTOS的配置:
FreeRTOSConfig.h文件中的配置如下: 复制代码 /* Ensure stdint is only used by the compiler, and not the assembler. */ #if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__) #include extern volatile uint32_t ulHighFrequencyTimerTicks; #endif #define configUSE_PREEMPTION 1 #define configUSE_IDLE_HOOK 0 #define configUSE_TICK_HOOK 0 #define configCPU_CLOCK_HZ ( ( unsigned long ) 72000000 ) #define configTICK_RATE_HZ ( ( TickType_t ) 1000 ) #define configMAX_PRIORITIES ( 5 ) #define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 ) #define configTOTAL_HEAP_SIZE ( ( size_t ) ( 17 * 1024 ) ) #define configMAX_TASK_NAME_LEN ( 16 ) #define configUSE_TRACE_FACILITY 1 #define configUSE_16_BIT_TICKS 0 #define configIDLE_SHOULD_YIELD 1 /* Run time and task stats gathering related definitions. */ #define configGENERATE_RUN_TIME_STATS 1 #define configUSE_STATS_FORMATTING_FUNCTIONS 1 #define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() (ulHighFrequencyTimerTicks = 0ul) #define portGET_RUN_TIME_COUNTER_VALUE() ulHighFrequencyTimerTicks //#define portALT_GET_RUN_TIME_COUNTER_VALUE 1 /* Co-routine definitions. */ #define configUSE_CO_ROUTINES 0 #define configMAX_CO_ROUTINE_PRIORITIES ( 2 ) /* Set the following definitions to 1 to include the API function, or zero to exclude the API function. */ #define INCLUDE_vTaskPrioritySet 1 #define INCLUDE_uxTaskPriorityGet 1 #define INCLUDE_vTaskDelete 1 #define INCLUDE_vTaskCleanUpResources 0 #define INCLUDE_vTaskSuspend 1 #define INCLUDE_vTaskDelayUntil 1 #define INCLUDE_vTaskDelay 1 /* Cortex-M specific definitions. */ #ifdef __NVIC_PRIO_BITS /* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */ #define configPRIO_BITS __NVIC_PRIO_BITS #else #define configPRIO_BITS 4 /* 15 priority levels */ #endif /* The lowest interrupt priority that can be used in a call to a "set priority" function. */ #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0x0f /* The highest interrupt priority that can be used by any interrupt service routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER PRIORITY THAN THIS! (higher priorities are lower numeric values. */ #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 0x01 |
|
|
|
|
|
几个重要选项说明:
1、#define configUSE_PREEMPTION 1 使能抢占式调度器 2、#define configCPU_CLOCK_HZ ( ( unsigned long ) 72000000 ) 系统主频72MHz。 3、#define configTICK_RATE_HZ ( ( TickType_t ) 1000 ) 系统时钟节拍1KHz,即1ms。 4、#define configMAX_PRIORITIES ( 5 ) 定义可供用户使用的最大优先级数,如果这个定义的是5,那么用户可以使用的优先级号是0,1,2,3,4,不包含5,对于这一点,初学者要特别的注意。 5、#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 17 * 1024 ) ) 定义堆大小,FreeRTOS内核,用户动态内存申请,任务栈等都需要用这个空间。 6、configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 0x01 定义受FreeRTOS管理的最高优先级中断。简单的说就是允许用户在这个中断服务程序里面调用FreeRTOS的API的最高优先级。为了进一步说明这个宏定义的的作用,解释如下: (1)使用CM内核的MCU,官方强烈建议将NVIC的优先级分组配置为全抢占式优先级,全部配置为抢占式优先级的好处就是方便管理。 (2)对于STM32来说,设置NVIC的优先级分组为4时,NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4)就是全部配置为抢占式优先级。又因为STM32的优先级设置仅使用CM内核8bit中的高4bit,即只能区分2^4 = 16种优先级。因此当优先级分组设置为4的时候可供用户选择抢占式优先级为0到15,共16个优先级,配置为0表示最高优先级,配置为15表示最低优先级,不存在子优先级。 (3)这里配置configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY为0x01表示用户可以在抢占式优先级为1到15的中断里面调用FreeRTOS的API函数,抢占式优先级为0的中断里面是不允许调用的。 |
|
|
|
|
|
FreeRTOS任务调试信息(按K1按键,串口打印):
上面截图中打印出来的任务状态字母B, R, D, S对应如下含义: #definetskBLOCKED_CHAR ( 'B' ) 任务阻塞 #definetskREADY_CHAR ( 'R' ) 任务就绪 #definetskDELETED_CHAR ( 'D' ) 任务删除 #definetskSUSPENDED_CHAR ( 'S' ) 任务挂起 |
|
|
|
|
|
程序设计:
任务栈大小分配: vTaskUserIF任务 :2048字节 vTaskLED任务 :2048字节 vTaskMsgPro任务 :2048字节 vTaskStart任务 :2048字节 任务栈空间是在任务创建的时候从FreeRTOSConfig.h文件中定义的heap空间中申请的 #defineconfigTOTAL_HEAP_SIZE ( ( size_t )( 17 * 1024 ) ) 系统栈大小分配: |
|
|
|
|
|
FreeROTS初始化:
复制代码 /* ********************************************************************************************************* * 函 数 名: main * 功能说明: 标准c程序入口。 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ int main(void) { /* 在启动调度前,为了防止初始化STM32外设时有中断服务程序执行,这里禁止全局中断(除了NMI和HardFault)。 这样做的好处是: 1. 防止执行的中断服务程序中有FreeRTOS的API函数。 2. 保证系统正常启动,不受别的中断影响。 3. 关于是否关闭全局中断,大家根据自己的实际情况设置即可。 在移植文件port.c中的函数prvStartFirstTask中会重新开启全局中断。通过指令cpsie i开启,__set_PRIMASK(1) 和cpsie i是等效的。 */ __set_PRIMASK(1); /* 硬件初始化 */ bsp_Init(); /* 1. 初始化一个定时器中断,精度高于滴答定时器中断,这样才可以获得准确的系统信息 仅供调试目的,实际项 目中不要使用,因为这个功能比较影响系统实时性。 2. 为了正确获取FreeRTOS的调试信息,可以考虑将上面的关闭中断指令__set_PRIMASK(1); 注释掉。 */ vSetupSysInfoTest(); /* 创建任务 */ AppTaskCreate(); /* 启动调度,开始执行任务 */ vTaskStartScheduler(); /* 如果系统正常启动是不会运行到这里的,运行到这里极有可能是用于定时器任务或者空闲任务的 heap空间不足造成创建失败,此要加大FreeRTOSConfig.h文件中定义的heap大小: #define configTOTAL_HEAP_SIZE ( ( size_t ) ( 17 * 1024 ) ) */ while(1); } |
|
|
|
|
|
644 浏览 0 评论
求助一下关于51系列单片机的Timer0的计时问题,TH0、TL0+1的时间是怎么算的?
1819 浏览 1 评论
【RA-Eco-RA4E2-64PIN-V1.0开发板试用】开箱+Keil环境搭建+点灯+点亮OLED
1262 浏览 0 评论
【敏矽微ME32G070开发板免费体验】使用coremark测试敏矽微ME32G070 跑分
1136 浏览 0 评论
【敏矽微ME32G070开发板免费体验】开箱+点灯+点亮OLED
1357 浏览 2 评论
【youyeetoo X1 windows 开发板体验】少儿AI智能STEAM积木平台
12026 浏览 31 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-24 10:55 , Processed in 1.294122 second(s), Total 107, Slave 89 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号