首页 > 编程知识 正文

freertos timer,freertos时钟节拍最低多少

时间:2023-05-03 08:15:09 阅读:182150 作者:4122

FreeRTOS与RT-Thread和C/OS一样,支持时间片功能。 所谓时间片,是指可以以相同的优先顺序具有多个任务,按每个任务的顺序共享相同的CPU时间,将共享CPU时间称为时间片。

在RTOS中,最小的时间单位是SysTick的中断周期。 虽然RT-Thread和C/OS可以指定多个take的时间片大小,但是FreeRTOS不同,时间片只能是一个take。 与其说FreeRTOS支持时间片,不如说该时间片是常规的任务调度。

让我们先测试一下工时记录卡:

代码如下所示。

任务1和任务2都是绝对的死循环,没有手动切换任务,也没有堵塞延迟(有时在堵塞延迟中切换任务)。 当调度开始时,它首先运行具有最高优先级的任务3,然后任务3进入拥塞延迟并且开始任务1的执行。 在知道任务3的定时时间过期后执行任务3,如果延迟时间堵塞,则继续任务1。 我们好像只能以同样的优先顺序执行头部节点的任务呢。 任务2怎么执行?

/*软件延迟*/voiddelay(uint32_tcount ) {for; 出局了!=0; count--; }/*任务1*/voidtask1_entry(void*p_arg ) (for ); ({flag1=1; //vtaskdelay(1; 延迟(100; flag1=0; 延迟(100; //vtaskdelay(1; }/*任务2*/voidtask2_entry(void*p_arg ) (for ); ({flag2=1; //vtaskdelay(1; 延迟(100; flag2=0; 延迟(100; //vtaskdelay(1; }voidtask3_entry(void*p_arg ) for ); ({flag3=1; taskdelay(1;//延迟(100 ); flag3=0; taskdelay(1;//延迟(100 ); }为了解决这个问题,我们先来看看实验现象吧。 实验现象如下

可以看到,任务3的执行(以及延迟时间1 take的堵塞)立即切换到任务1,在下一个take切换到任务2,处于循环状态。 为什么? 是什么?

合理推断,在相同的优先级列表下,任务控制块的指针每次都指向下一个列表项目,从而可以遍历相同优先级下的多任务函数[执行顺序:任务1 -任务2-.]。

FreeRTOS时间片原理:

能够以相同的优先级执行多个任务最终要归功于名为taskRESET_READY_PRIORITY () task select _ highest _ priority _ task )的宏定义的实现方式

1 task select _ highest _ priority _ task ()查找就绪任务的优先级

# definetaskselect _ highest _ priority _ task (() _ubasetype_tuxtoppriority; /*查找包含就绪任务优先级的队列*/ portget _ highest _ priority (uxtoppriority,uxTopReadyPriority ); /*要获取具有最高优先级的仲裁任务的TCB并将其更新为pxCurrentTCB *//*,需要此宏listGET_OWNER_OF_NEXT_ENTRY获取链表中的第一个节点的OOK 这意味着,每次调用时,节点扫描指针pxIndex都会向后移动一次,以指向下一个节点*/ list get _ owner _ of _ next _ entry (pxcurrenttcb ) }/* task select _ highest _ priority _ task (*/list get _ owner _ of _ next _ entry ) )函数的精彩之处在于链表下的第一个怎么理解? 假设当前链表中有n个节点。 在第n次调用函数时,pxIndex指向第n个节点。 这意味着,每次调用时,节点扫描指针pxIndex都会向后移动一次以指向下一个节点。

/*获取链

表节点的OWNER,即TCB */#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ){List_t * const pxConstList = ( pxList ); /* 节点索引指向链表第一个节点调整节点索引指针,指向下一个节点, 如果当前链表有N个节点,当第N次调用该函数时,pxInedex则指向第N个节点 */( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;/* 当前链表为空 */ if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ){( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;}/* 获取节点的OWNER,即TCB */ ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; }

本实验中,优先级 2 下有两个任务,当系统第一次切换到优先级为 2 的任务(包含了任务 1 和任务 2,因为它们的优先级相同)时,pxIndex 指向任务 1,任务 1 得到执行。当任务 1 执行完毕,系统重新切换到优先级为 2 的任务时,这个时候 pxIndex指向任务 2,任务 2得到执行,任务 1 和任务 2 轮流执行,享有相同的 CPU时间,即所谓的时间片。

 

本实验中,任务1和任务2的主体都是无限循环,那如果任务1和任务2 都会调用将自己挂起的函数(实际运用中,任务体都不能是无限循环的,必须调用能将自己挂起的函数),比如 vTaskDelay()。调用能将任务挂起的函数中,都会先将任务从就绪列表删除,然后将任务在优先级位图表 uxTopReadyPriority 中 应的位清零,由taskRESET_READY_PRIORITY()函数来实现。

#define taskRESET_READY_PRIORITY( uxPriority ){if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ ( uxPriority ) ] ) ) == ( UBaseType_t ) 0 ){portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) );}}

taskRESET_READY_PRIORITY() 函数的妙处在于清除优先级位图表uxTopReadyPriority 中相应的位时候,会先判断当前优先级链表下是否还有其它任务,如果有则不清零。假设当前实验中,任务 1 会调用 vTaskDelay(),会将自己挂起,只能是将任
务 1从就绪列表删除,不能将任务 1 在优先级位图表 uxTopReadyPriority中对应的位清 0,因为该优先级下还有任务 2,否则任务 2 将得不到执行。

修改代码:

1 修改systick中断服务函数:

这是原来的中断服务函数,更新时基函数不带返回值:

/*************************************************************************** SysTick中断服务函数**************************************************************************/void xPortSysTickHandler( void ){/* 关中断 */ vPortRaiseBASEPRI(); /* 更新系统时基 */ xTaskIncrementTick();/* 开中断 */ vPortClearBASEPRIFromISR();}

这是新的中断服务函数,当xTaskIncrementTick()函数返回为真时才进行任务切换。区别在于,原来的 xTaskIncrementTick()是不带返回值的,执行到最后会调用 taskYIELD()执行任务切换。

void xPortSysTickHandler( void ){/* 关中断 */ vPortRaiseBASEPRI(); /* 更新系统时基 */ if( xTaskIncrementTick() != pdFALSE ){/* 任务切换,即触发PendSV *///portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;taskYIELD();}/* 开中断 */vPortClearBASEPRIFromISR();}

2 修改时基切换  xTaskIncrementTick()函数

因为之前的时基函数,每次tick都换切换任务,而现在是当xTaskIncrementTick()函数返回为真时才进行任务切换。

BaseType_t xTaskIncrementTick( void ){TCB_t * pxTCB;TickType_t xItemValue; BaseType_t xSwitchRequired = pdFALSE; const TickType_t xConstTickCount = xTickCount + 1;xTickCount = xConstTickCount;/* 如果xConstTickCount溢出,则切换延时列表 */if( xConstTickCount == ( TickType_t ) 0U ){taskSWITCH_DELAYED_LISTS();}/* 最近的延时任务延时到期 */if( xConstTickCount >= xNextTaskUnblockTime ){for( ;; ){if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE ){/* 延时列表为空,设置xNextTaskUnblockTime为可能的最大值 */xNextTaskUnblockTime = portMAX_DELAY;break;}else /* 延时列表不为空 */{pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );/* 直到将延时列表中所有延时到期的任务移除才跳出for循环 */ if( xConstTickCount < xItemValue ){xNextTaskUnblockTime = xItemValue;break;}/* 将任务从延时列表移除,消除等待状态 */( void ) uxListRemove( &( pxTCB->xStateListItem ) );/* 将解除等待的任务添加到就绪列表 */prvAddTaskToReadyList( pxTCB ); /* 表示有任务就绪且就绪任务的优先级比当前优先级高时,需要执行一次任务切换,即将 xSwitchRequired 的值置为 pdTRUE。而原来,我们是在执行完 xTaskIncrementTick()函数的时候, 不管是否有任务就绪,不管就绪的任务的优先级是否比当前任务优先级高都执行一次任务切换。如果就绪任务的优先级比当前优先级高,那么执行一次任务切换与加了这段代码实现的功能是一样的。如果没有任务就绪呢?就不需要执行任务切换,这样与之前的实现方法相比就省了一次任务切换的时间。虽然说没有更高优先级的任务就绪,执行任务切换的时候还是会运行原来的任务,但这是以多花一次任务切换的时间为代价的。*/// 简而言之,就是延时到期提取的任务优先级提当前运行的还要高,就切换任务// 否则,只是执行上面的添加就绪列表操作,灯带高优先级任务执行完成再自己切换#if ( configUSE_PREEMPTION == 1 ){if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ){xSwitchRequired = pdTRUE;}}#endif /* configUSE_PREEMPTION */}}}/* xConstTickCount >= xNextTaskUnblockTime */ /* 这部分代码与时间片功能相关当前优先级列表下不止一个任务时就执行一次任务切换,即将 xSwitchRequired 置为 pdTRUE 即可 */ #if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) { if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 ) { xSwitchRequired = pdTRUE; } } #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */ /* 任务切换 */ //portYIELD();}

增加的部分1,主要就是为了避免无意义的任务切换。也就是说,当有任务就绪时,就将任务添加到就绪列表,而只有当就绪列表的任务优先级很高时,才进行任务切换,否则不要去浪费切换任务的时间。

/* 表示有任务就绪且就绪任务的优先级比当前优先级高时,需要执行一次任务切换,即将 xSwitchRequired 的值置为 pdTRUE。而原来,我们是在执行完 xTaskIncrementTick()函数的时候,不管是否有任务就绪,不管就绪的任务的优先级是否比当前任务优先级高都执行一次任务切换。如果就绪任务的优先级比当前优先级高,那么执行一次任务切换与加了这段代码实现的功能是一样的。如果没有任务就绪呢?就不需要执行任务切换,这样与之前的实现方法相比就省了一次任务切换的时间。虽然说没有更高优先级的任务就绪,执行任务切换的时候还是会运行原来的任务,但这是以多花一次任务切换的时间为代价的。*/// 简而言之,就是延时到期提取的任务优先级提当前运行的还要高,就切换任务// 否则,只是执行上面的添加就绪列表操作,灯带高优先级任务执行完成再自己切换#if ( configUSE_PREEMPTION == 1 ){if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ){xSwitchRequired = pdTRUE;}} #endif

增加的部分2,时间片

/* 这部分代码与时间片功能相关当前优先级列表下不止一个任务时就执行一次任务切换,即将 xSwitchRequired 置为 pdTRUE 即可 */ #if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) { if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 ) { xSwitchRequired = pdTRUE; } } #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */

其实 FreeRTOS 的这种时间片功能不能说是真正意义的时间片,因为它不能随意的设置时间为多少个 tick,而是默认一个 tick,然后默认在每个 tick 中断周期中进行任务切换而已。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。