FreeRTOS — 事件标志组
以下内容转载自安富莱电子:http://forum.armfly.com/forum.php
前面的章节我们已经讲解了任务管理和时间管理,从这节开始讲解任务间的通信和同步机制。首先讲解任务间的通信和同步机制之一,事件标志组。
1 、 事 件 标 志 组
1.1 为什么要使用事件标志
事件标志组是实现多任务同步的有效机制之一。也许有不理解的初学者会问采用事件标志组多麻烦,搞个全局变量不是更简单?其实不然,在裸机编程时,使用全局变量的确比较方便,但是在加上 RTOS 后就是另一种情况了。使用全局变量相比事件标志组主要有如下三个问题:
使用事件标志组可以让 RTOS 内核有效地管理任务,而全局变量是无法做到的,任务的超时等机制需要用户自己去实现。
使用了全局变量就要防止多任务的访问冲突,而使用事件标志组则处理好了这个问题,用户无需担心。
使用事件标志组可以有效地解决中断服务程序和任务之间的同步问题。
1.2 FreeRTOS 任务间事件标志组的实现
任务间事件标志组的实现是指各个任务之间使用事件标志组实现任务的通信或者同步机制。
下面我们来说说 FreeRTOS 中事件标志的实现,根据用户在 FreeRTOSConfig.h 文件中的配置:
#define configUSE_16_BIT_TICKS 1
配置宏定义 configUSE_16_BIT_TICKS 为 1 时,每创建一个事件标志组,用户可以使用的事件标志是8 个。
#define configUSE_16_BIT_TICKS 0
配置宏定义 configUSE_16_BIT_TICKS 为 0 时,每创建一个事件标志组,用户可以使用的事件标志是24个。
上面说的 8 个和 24 个事件标志应该怎么理解呢?其实就是定义了一个 16 位变量,仅使用了低 8bit或者定义了一个 32 位变量,仅使用了低 24bit。每一个 bit 用 0 和 1 两种状态来代表事件标志。反映到FreeRTOS上就是将事件标志存储到了EventBits_t类型的变量中,这个变量又是怎么回事呢?定义如下:
进一步跟踪 TickType_t 的数据类型,定义如下:
由上面定义可以看出,TickType_t 数据类型可以是 16 位数或者 32 位数,这样就跟上面刚刚说的configUSE_16_BIT_TICKS 宏定义呼应上了。配套的例子都是配置宏定义 configUSE_16_BIT_TICKS为 0,即用户每创建一个事件标志组,有 24 个标志可以设置。如下图所示,这里仅使用 bit0,bit1 和 bit2。
注意:后面的讲解中,默认全是创建一个事件标志,支持 24 个事件标志设置。
下面我们通过如下的框图来说明一下 FreeRTOS 事件标志的实现,让大家有一个形象的认识。
上面就是一个简单的 FreeRTOS 任务间事件标志通信过程。
1.3 FreeRTOS 中断方式事件标志组的实现
FreeRTOS 中断方式事件标志组的实现是指中断函数和 FreeRTOS 任务之间使用事件标志。下面我们通过如下的框图来说明一下 FreeRTOS 事件标志的实现,让大家有一个形象的认识。
运行条件:
创建一个任务和一个串口接收中断
运行过程描述如下:
任务 Task1 运行过程中调用函数 xEventGroupWaitBits,等待事件标志位被设置,任务 Task1 由运行态进入到阻塞态。
Task1 阻塞的情况下,串口接收到数据进入到了串口中断服务程序,在串口中断服务程序中设置 Task1等待的事件标志,任务 Task1 由阻塞态进入到就绪态,在调度器的作用下由就绪态又进入到运行态。
上面就是一个简单的 FreeRTOS 中断方式事件标志通信过程。实际应用中,中断方式的消息机制要注意以下四个问题:
中断函数的执行时间越短越好,防止其它低于这个中断优先级的异常不能得到及时响应。
实际应用中,建议不要在中断中实现消息处理,用户可以在中断服务程序里面发送消息通知任务,在任务中实现消息处理,这样可以有效地保证中断服务程序的实时响应。同时此任务也需要设置为高优先级,以便退出中断函数后任务可以得到及时执行。
中断服务程序中一定要调用专用于中断的事件标志设置函数,即以 FromISR 结尾的函数。
在操作系统中实现中断服务程序与裸机编程的区别。
如果 FreeRTOS 工程的中断函数中没有调用 FreeRTOS 的事件标志组 API 函数,与裸机编程是一样的。
如果 FreeRTOS 工程的中断函数中调用了 FreeRTOS 的事件标志组的 API 函数,退出的时候要检测是否有高优先级任务就绪,如果有就绪的,需要在退出中断后进行任务切换,这点跟裸机编程稍有区别,详见 .4 小节实验例程说明(中断方式):
另外强烈推荐用户将 Cortex-M3 内核的 STM32F103 和 Cortex-M4 内核的 STM32F407,F429的 NVIC 优先级分组设置为 4,即:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);这样中断优先级的管理将非常方便。
用户要在 FreeRTOS 多任务开启前就设置好优先级分组,一旦设置好切记不可再修改。
2 、事件 标志 组 API 函数
使用如下 11 个函数可以实现 FreeRTOS 的事件标志组:
xEventGroupCreate()
xEventGroupCreateStatic()
vEventGroupDelete()
xEventGroupWaitBits()
xEventGroupSetBits()
xEventGroupSetBitsFromISR()
xEventGroupClearBits()
xEventGroupClearBitsFromISR()
xEventGroupGetBits()
xEventGroupGetBitsFromISR()
xEventGroupSync()
关于这 11 个函数的讲解及其使用方法可以看 FreeRTOS 在线版手册:
这里重点的说以下 4 个函数:
xEventGroupCreate()
xEventGroupWaitBits()
xEventGroupSetBits()
xEventGroupSetBitsFromISR()
因为本章节配套的例子使用的是这 4 个函数。
2.1 函数 xEventGroupCreate
2.2 函数 xEventGroupSetBits
函数原型:
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, /* 事件标志组句柄 */
const EventBits_t uxBitsToSet ); /* 事件标志位设置 */
函数描述:
函数 xEventGroupSetBits 用于设置指定的事件标志位为 1。
第 1 个参数是事件标志组句柄。
第 2 个参数表示 24 个可设置的事件标志位,EventBits_t 是定义的 32 位变量,低 24 位用于事件标志设置。变量 uxBitsToSet 的低 24 位的某个位设置为 1,那么被设置的事件标志组的相应位就设置为 1。变量 uxBitsToSet 设置为 0 的位对事件标志相应位没有影响。比如设置变量 uxBitsToSet = 0x0003 就表示将事件标志的位 0 和位 1 设置为 1,其余位没有变化。
返回当前的事件标志组数值。
使用这个函数要注意以下问题:
1. 使用前一定要保证事件标志组已经通过函数 xEventGroupCreate 创建了。
2. 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序中使用的是xEventGroupSetBitsFromISR
3. 用户通过参数 uxBitsToSet 设置的标志位并不一定会保留到此函数的返回值中,下面举两种情况:
a. 调用此函数的过程中,其它高优先级的任务就绪了,并且也修改了事件标志,此函数返回的事件标志位会发生变化。
b. 调用此函数的任务是一个低优先级任务,通过此函数设置了事件标志后,让一个等待此事件标志的高优先级任务就绪了,会立即切换到高优先级任务去执行,相应的事件标志位会被函数xEventGroupWaitBits 清除掉,等从高优先级任务返回到低优先级任务后,函数xEventGroupSetBits 的返回值已经被修改。
使用举例:
#define BIT_0 (1 << 0) #define BIT_1 (1 << 1) #define BIT_ALL (BIT_0 | BIT_1) static EventGroupHandle_t xCreatedEventGroup = NULL; /* ********************************************************************************************************* * 函 数 名: vTaskTaskUserIF * 功能说明: 接口消息处理。 * 形 参: pvParameters 是在创建该任务时传递的形参 * 返 回 值: 无 * 优 先 级: 1 (数值越小优先级越低,这个跟 uCOS 相反) ********************************************************************************************************* */ static void vTaskTaskUserIF(void *pvParameters) { uint8_t ucKeyCode; EventBits_t uxBits; while(1) { ucKeyCode = bsp_GetKey(); if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { /* K2 键按下,直接发送事件标志给任务 vTaskMsgPro,设置 bit0 */ case KEY_DOWN_K2: /* 设置事件标志组的 bit0 */ uxBits = xEventGroupSetBits(xCreatedEventGroup, BIT_0); if((uxBits & BIT_0) != 0) { printf("K2 键按下,事件标志的 bit0 被设置\r\n"); } else { printf("K2 键按下,事件标志的 bit0 被清除,说明任务 vTaskMsgPro 已经接受到 bit0 和 bit1 被设置的情况\r\n"); } break; /* K3 键按下,直接发送事件标志给任务 vTaskMsgPro,设置 bit1 */ case KEY_DOWN_K3: /* 设置事件标志组的 bit1 */ uxBits = xEventGroupSetBits(xCreatedEventGroup, BIT_1); if((uxBits & BIT_1) != 0) { printf("K3 键按下,事件标志的 bit1 被设置\r\n"); } else { printf("K3 键按下,事件标志的 bit1 被清除,说明任务 vTaskMsgPro 已经接受到 bit0 和 bit1 被设置的情况\r\n"); } break; /* 其他的键值不处理 */ default: break; } } vTaskDelay(20); }
}
2.3 函数 xEventGroupSetBitsFromISR
函数原型:
BaseType_t xEventGroupSetBitsFromISR(
EventGroupHandle_t xEventGroup, /* 事件标志组句柄 */
const EventBits_t uxBitsToSet, /* 事件标志位设置 */
BaseType_t *pxHigherPriorityTaskWoken ); /* 高优先级任务是否被唤醒的状态保存 */
函数描述:
函数 xEventGroupSetBits 用于设置指定的事件标志位为 1。
第 1 个参数是事件标志组句柄。
第 2 个参数表示 24 个可设置的事件标志位,EventBits_t 是定义的 32 位变量,低 24 位用于事件标志设置。变量 uxBitsToSet 的低 24 位的某个位设置为 1,那么被设置的事件标志组的相应位就设置为 1。变量 uxBitsToSet 设置为 0 的位对事件标志相应位没有影响。比如设置变量 uxBitsToSet = 0x0003 就表示将事件标志的位 0 和位 1 设置为 1,其余位没有变化。
第3个参数用于保存是否有高优先级任务准备就绪。如果函数执行完毕后,此参数的数值是pdTRUE,说明有高优先级任务要执行,否则没有。
返回值,如果消息成功发送给 daemon 任务(就是 FreeRTOS 的定时器任务)返回 pdPASS,否则返回 pdFAIL,另外 daemon 任务中的消息队列满了也会返回 pdFAIL。
使用这个函数要注意以下问题:
1. 使用前一定要保证事件标志已经通过函数 xEventGroupCreate 创建了。同时要在 FreeRTOSConfig.h文件中使能如下三个宏定义:
#define INCLUDE_xEventGroupSetBitFromISR 1
#define configUSE_TIMERS 1
#define INCLUDE_xTimerPendFunctionCall 1
2. 函数 xEventGroupSetBitsFromISR 是用于中断服务程序中调用的,故不可以在任务代码中调用此函数,任务代码中使用的是 xEventGroupSetBits。
3. 函数 xEventGroupSetBitsFromISR 对事件标志组的操作是不确定性操作,因为不知道当前有多少个任务在等待此事件标志。而 FreeRTOS 不允许在中断服务程序和临界段中执行不确定性操作。为了不在中断服务程序中执行,就通过此函数给 FreeRTOS 的 daemon 任务(就是 FreeRTOS 的定时器任务)发送消息,在 daemon 任务中执行事件标志的置位操作。同时也为了不在临界段中执行此不确定操作,将临界段改成由调度锁来完成。这样不确定性操作在中断服务程序和临界段中执行的问题就都得到解决了。
4. 由于函数 xEventGroupSetBitsFromISR 对事件标志的置位操作是在 daemon 任务里面执行的,如果想让置位操作立即生效,即让等此事件标志的任务能够得到及时执行,需要设置 daemon 任务的优先级高于使用此事件标志组的所有其它任务。
5. 通过下面的使用举例重点一下函数 xEventGroupSetBitsFromISR 第三个参数的规范用法,初学者务必要注意。
使用举例:
#define BIT_0 (1 << 0) #define BIT_1 (1 << 1) #define BIT_ALL (BIT_0 | BIT_1) static EventGroupHandle_t xCreatedEventGroup = NULL; /* ********************************************************************************************************* * 函 数 名: TIM2_IRQHandler * 功能说明: 定时器中断。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void TIM2_IRQHandler(void) { BaseType_t xResult; BaseType_t xHigherPriorityTaskWoken = pdFALSE; /* 中断消息处理,此处省略 */ …… /* 向任务 vTaskMsgPro 发送事件标志 */ xResult = xEventGroupSetBitsFromISR(xCreatedEventGroup, /* 事件标志组句柄 */ BIT_0 , /* 设置 bit0 */ &xHigherPriorityTaskWoken ); /* 消息被成功发出 */ if( xResult != pdFAIL ) { /* 如果 xHigherPriorityTaskWoken = pdTRUE,那么退出中断后切到当前最高优先级任务执行 */ portYIELD_FROM_ISR(xHigherPriorityTaskWoken); }
}
2.4 函数 xEventGroupWaitBits
函数原型:
EventBits_t xEventGroupWaitBits(
const EventGroupHandle_t xEventGroup, /* 事件标志组句柄 */
const EventBits_t uxBitsToWaitFor, /* 等待被设置的事件标志位 */
const BaseType_t xClearOnExit, /* 选择是否清零被置位的事件标志位 */
const BaseType_t xWaitForAllBits, /* 选择是否等待所有标志位都被设置 */
TickType_t xTicksToWait ); /* 设置等待时间 */
函数描述:
函数 xEventGroupWaitBits 等待事件标志被设置。
第 1 个参数是事件标志组句柄。
第 2 个参数表示等待 24 个事件标志位中的指定标志,EventBits_t 是定义的 32 位变量,低 24 位用于事件标志设置。比如设置变量 uxBitsToWaitFor = 0x0003 就表示等待事件标志的位 0 和位 1 设置为 1。此参数切不可设置为 0。
第 3 个参数选择是否清除已经被置位的事件标志,如果这个参数设置为 pdTRUE,且函数xEventGroupWaitBits 在参数 xTicksToWait 设置的溢出时间内返回,那么相应被设置的事件标志位会被清零。如果这个参数设置为 pdFALSE,对已经被设置的事件标志位没有影响。
第 4 个参数选择是否等待所有的标志位都被设置,如果这个参数设置为 pdTRUE,要等待第 2 个参数 uxBitsToWaitFor 所指定的标志位全部被置 1,函数才可以返回。当然,超出了在参数xTicksToWait 设置的溢出时间也是会返回的。如果这个参数设置为 pdFALSE,第 2 个参数uxBitsToWaitFor 所指定的任何标志位被置 1,函数都会返回,超出溢出时间也会返回。
第 5 个参数设置等待时间,单位时钟节拍周期。如果设置为 portMAX_DELAY,表示永久等待。
返回值,由于设置的时间超时或者指定的事件标志位被置 1,导致函数退出时返回的事件标志组数值。
使用这个函数要注意以下问题:
1. 此函数切不可在中断服务程序中调用。
2. 这里再着重说明下这个函数的返回值,通过返回值用户可以检测是哪个事件标志位被置 1 了。
如果由于设置的等待时间超时,函数的返回值可会有部分事件标志位被置 1。
如果由于指定的事件标志位被置1而返回,并且设置了这个函数的参数xClearOnExit为pdTRUE,那么此函数的返回值是清零前的事件标志组数值。
另外,调用此函数的任务在离开阻塞状态到退出函数 xEventGroupWaitBits 之间这段时间,如果一个高优先级的任务抢占执行了,并且修改了事件标志位,那么此函数的返回值会跟当前的事件标志组数值不同。
使用举例:
#define BIT_0 (1 << 0) #define BIT_1 (1 << 1) #define BIT_ALL (BIT_0 | BIT_1) static EventGroupHandle_t xCreatedEventGroup = NULL; /* ********************************************************************************************************* * 函 数 名: vTaskMsgPro * 功能说明: 消息处理,使用函数 xEventGroupWaitBits 接收任务 vTaskTaskUserIF 发送的事件标志 * 形 参: pvParameters 是在创建该任务时传递的形参 * 返 回 值: 无 * 优 先 级: 3 ********************************************************************************************************* */ static void vTaskMsgPro(void *pvParameters) { EventBits_t uxBits; const TickType_t xTicksToWait = 100 / portTICK_PERIOD_MS; /* 最大延迟 100ms */ while(1) { /* 等 K2 按键按下设置 bit0 和 K3 按键按下设置 bit1 */ uxBits = xEventGroupWaitBits(xCreatedEventGroup, /* 事件标志组句柄 */ BIT_ALL, /* 等待 bit0 和 bit1 被设置 */ pdTRUE, /* 退出前 bit0 和 bit1 被清除,这里是 bit0 和 bit1 都被设置才表示“退出”*/ pdTRUE, /* 设置为 pdTRUE表示等待 bit1和 bit0 都被设置*/ xTicksToWait); /* 等待延迟时间 */ if((uxBits & BIT_ALL) == BIT_ALL) { /* 接收到 bit1 和 bit0 都被设置的消息 */ printf("接收到 bit0 和 bit1 都被设置的消息\r\n"); } else { /* 超时,另外注意仅接收到一个按键按下的消息时,变量 uxBits 的相应 bit 也是被设置的 */ bsp_LedToggle(3); } } }
static void AppTaskCreate(void) { xTaskCreate(vTaskLed1, /* 任务函数名 */ "Task Led1", /* 任务名,字符串形式,方便调试 */ 512, /* 栈大小,单位为字,即4个字节 */ NULL, /* 任务形参 */ 4, /* 优先级,数值越大,优先级越高 */ &xHandleTaskLED1); /* 任务句柄 */ xTaskCreate( vTaskTaskUserIF, /* 任务函数 */ "vTaskUserIF", /* 任务名 */ 512, /* 任务栈大小,单位word,也就是4字节 */ NULL, /* 任务参数 */ 1, /* 任务优先级*/ &xHandleTaskUserIF ); /* 任务句柄 */ xTaskCreate( vTaskMsgPro, /* 任务函数 */ "vTaskMsgPro", /* 任务名 */ 512, /* 任务栈大小,单位word,也就是4字节 */ NULL, /* 任务参数 */ 2, /* 任务优先级*/ &xHandleTaskMsgPro ); /* 任务句柄 */ xTaskCreate(vTaskBeep,"Task Beep",512,NULL,3,&xHandleTaskBeep); }
static void vTaskTaskUserIF(void *pvParameters) { EventBits_t uxBits; while(1) { if(ucKeyCode != 0) { switch( ucKeyCode) { /* K1键按下 挂起任务VTaskLED */ case 1: /* 设置事件标志组的bit0 */ uxBits = xEventGroupSetBits(xCreatedEventGroup, BIT_0); if((uxBits & BIT_0) != 0) { printf("K1键按下,事件标志的bit0被设置\r\n"); } else { printf("K1键按下,事件标志的bit0被清除,说明任务vTaskMsgPro已经接受到bit0和bit1被设置的情况\r\n"); } ucKeyCode = 0; break; /* K2键按下 启动单次定时器中断,50ms后在定时器中断将任务vTaskLED恢复 */ case 2: /* 设置事件标志组的bit1 */ uxBits = xEventGroupSetBits(xCreatedEventGroup, BIT_1); if((uxBits & BIT_1) != 0) { printf("K2键按下,事件标志的bit1被设置\r\n"); } else { printf("K2键按下,事件标志的bit1被清除,说明任务vTaskMsgPro已经接受到bit0和bit1被设置的情况\r\n"); } ucKeyCode = 0; break; /* 其他的键值不处理 */ default: break; } vTaskDelay(20); } } }
先按下K1,再按K2,串口打印结果如下:
第一个输出毫无疑问,第二行,由于事件等待vTaskMsgPro优先级大于按键处理,所以当K2按下之后,调度器首先回到高优先级的任务vTaskMsgPro,打印出此时K1,K2都被按下以致bit0和bit1被置位的消息,在vTaskMsgPro任务中调用xEventGroupWaitBits函数后,这两个置为1的位bit1和bit0会被清零,此时,调度器再次回到低优先级的按键处理vTaskTaskUserIF任务时,xEventGroupSetBits的返回值已经更新成清零值,故第三行打印清除的消息。
现在,我们把按键处理的vTaskTaskUserIF优先级设置成为高于vTaskMsgPro任务的,打印输出如下:
第一个输出也毫无疑问,按下K1,bit0被置位,当我按下K2的时候,此时调度器 不会马上返回低优先级的vTaskMsgPro任务,而会继续执行自身(此实验设置按键处理最高优先级)直到被阻塞,所以会有第二行的打印,但是,注意,第二行按下K2的打印却依旧显示的是被清除了,因为在vTaskMsgPro任务中使用了事件等待,而K2按下的时候,freertos操作系统会知道等待两个按键按下的事件已经触发了,此时,在按键处理任务中,xEventGroupSetBits的返回值,也不是当前获取的置位值了,而是经过xEventGroupSetBits函数自动清零之后的值,所以第二行打印的是清零消息,第三行打印都被置位,为什么不是清零?因为此时的xEventGroupWaitBits返回值是清零前的事件标志组数值。
可能你觉得有点奇怪, xEventGroupSetBits函数本就是置位信息的功能,居然还要受xEventGroupWaitBits函数和调用形式影响,哪怕调用xEventGroupWaitBits函数的任务优先级还是低于我们的按键任务的,但是,正是因为这样,我们真正实时传递了事件信息啊。试想,要是我的两个按键事件都已经触发了,而我在按键处理任务中还不能立即知道,这样的实时性显然是不满足需求的。就连裸机中,我们通过中断改变一个元素的值,一定是中断改变之后,这个值在被任何使用的时候都已经更新,所以,作为实时操作系统,freertos这样的行为也就可以理解了。
中断方式:
这里设置两个按键中断,在按键中断中启动定时器,计时50ms,进入定时器中断,执行置位。
void KEY1_IRQHandler(void) { //确保是否产生了EXTI Line中断 if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET) { ucKeyCode = 1; temp = 1; //进定时器中断的标志位 // 使能定时器 TIM_Cmd(BASIC_TIMx, ENABLE); //清除中断标志位 EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE); } } void KEY2_IRQHandler(void) { //确保是否产生了EXTI Line中断 if(EXTI_GetITStatus(KEY2_INT_EXTI_LINE) != RESET) { ucKeyCode = 2; temp = 2; //进定时器中断的标志位 // 使能定时器 TIM_Cmd(BASIC_TIMx, ENABLE); EXTI_ClearITPendingBit(KEY2_INT_EXTI_LINE); } }
定时器中断:
void BASIC_TIMx_IRQHandler(void) { BaseType_t xResult; BaseType_t xHigherPriorityTaskWoken = pdFALSE; /* 中断消息处理,此处省略 */ if(1 == temp) { TIM_ClearITPendingBit(BASIC_TIMx , TIM_IT_Update); temp = 0; /* 向任务vTaskMsgPro发送事件标志 */ xResult = xEventGroupSetBitsFromISR(xCreatedEventGroup, /* 事件标志组句柄 */ BIT_0 , /* 设置bit0 */ &xHigherPriorityTaskWoken ); /* 消息被成功发出 */ if( xResult != pdFAIL ) { /* 如果 xHigherPriorityTaskWoken = pdTRUE,那么退出中断后切到当前最高优先级任务执行 */ portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } } if(2 == temp) { TIM_ClearITPendingBit(BASIC_TIMx , TIM_IT_Update); temp = 0; /* 向任务vTaskMsgPro发送事件标志 */ xResult = xEventGroupSetBitsFromISR(xCreatedEventGroup, /* 事件标志组句柄 */ BIT_1, /* 设置bit1 */ &xHigherPriorityTaskWoken ); /* 消息被成功发出 */ if( xResult != pdFAIL ) { /* 如果xHigherPriorityTaskWoken = pdTRUE,那么退出中断后切到当前最高优先级任务执行 */ portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } } /*不使能定时器*/ TIM_Cmd(BASIC_TIMx, DISABLE); }
由于自己设计思路不严谨,没有额外增加中断标志位;本想直接直接判断ucKeyCode == 1;或者ucKeyCode == 2;直接进定时器中断
各种测试不行,置位完全不起作用。我按K1,然后进K1中断,启动定时器定时50ms,进定时器中断,没完全没毛病。
但是,这是一个FreeRTOS的实时操作系统。系统不可能一直等着你计时到50ms进定时器中断,这50ms系统还会做很多事情。
按下K1进按键中断,启动定时器开始定时。但是定时期间,由于定时器没到50ms,此时还没触发定时器中断,系统开始执行最高优先级的vTaskTaskUserIF任务。
在此任务中,我们把ucKeyCode又赋值为0,假设定时器定时到50ms,进入定时器中断,但是此时ucKeyCode = 0,
向任务vTaskMsgPro发送事件标志,xEventGroupSetBitsFromISR函数根本不执行。
void BASIC_TIMx_IRQHandler(void) { BaseType_t xResult; BaseType_t xHigherPriorityTaskWoken = pdFALSE; /* 中断消息处理,此处省略 */ if(1 == ucKeyCode ) { TIM_ClearITPendingBit(BASIC_TIMx , TIM_IT_Update); temp = 0; /* 向任务vTaskMsgPro发送事件标志 */ xResult = xEventGroupSetBitsFromISR(xCreatedEventGroup, /* 事件标志组句柄 */ BIT_0 , /* 设置bit0 */ &xHigherPriorityTaskWoken ); /* 消息被成功发出 */ if( xResult != pdFAIL ) { /* 如果 xHigherPriorityTaskWoken = pdTRUE,那么退出中断后切到当前最高优先级任务执行 */ portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } } if(2 == ucKeyCode ) { TIM_ClearITPendingBit(BASIC_TIMx , TIM_IT_Update); temp = 0; /* 向任务vTaskMsgPro发送事件标志 */ xResult = xEventGroupSetBitsFromISR(xCreatedEventGroup, /* 事件标志组句柄 */ BIT_1, /* 设置bit1 */ &xHigherPriorityTaskWoken ); /* 消息被成功发出 */ if( xResult != pdFAIL ) { /* 如果xHigherPriorityTaskWoken = pdTRUE,那么退出中断后切到当前最高优先级任务执行 */ portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } } /*不使能定时器*/ TIM_Cmd(BASIC_TIMx, DISABLE); }
最终就变成了按下按键执行定时器定时50ms,50ms一到,里面没有做任何事情又把定时器不使能了。
故把代码改成
这样按下K1,进按键中断,启动定时器,执行按键处理任务,定时时间到就进入定时器中断,发送事件标志,
同理按下K2,进按键中断,启动定时器,执行按键处理任务,定时时间到就进入定时器中断,发送事件标志。
vTaskMsgPro任务等K1按键按下设置bit0和K2按键按下设置bit1 。成功就打印接收到bit0和bit1都被设置的消息。
打印情况如下:
tip:要以代码的运行状况来设计代码。自己的思路是否符合实际的运行情况。
欢迎加入作者的小圈子
扫描下方左边二维码加入QQ交流群,扫描下方右边二维码关注个人微信公众号并,获取更多隐藏干货,QQ交流群:859800032 微信公众号:Crystal软件学堂
作者:Liu_Jing bilibili视频教程地址:https://space.bilibili.com/5782182 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在转载文章页面给出原文连接。 如果你觉得文章对你有所帮助,烦请点个推荐,你的支持是我更文的动力。 文中若有错误,请您务必指出,感谢给予我建议并让我提高的你。 |