写在前面:
由于时间的不足与学习的碎片化,写博客变得有些奢侈。
但是对于记录学习(忘了以后能快速复习)的渴望一天天变得强烈。
既然如此
不如以天为单位,以时间为顺序,仅仅将博客当做一个知识学习的目录,记录笔者认为最通俗、最有帮助的资料,并尽量总结几句话指明本质,以便于日后搜索起来更加容易。
标题的结构如下:"类型":"知识点"------"简短的解释"
部分内容由于保密协议无法上传。
2024.03.13
- 二十九、UCOSIII:基于时基列表的时延操作
 - 
- 1、配置时钟中断时间
 - 2、创建任务
 - 3、任务放置到就绪列表中,并优先级排队
 - 4、将任务插入时基列表
 - 
- [1. 确认时延](#1. 确认时延)
 - 2.对任务进行排序
 - [3. 确认插入时基列表哪个成员](#3. 确认插入时基列表哪个成员)
 - [4. 对就绪列表的操作](#4. 对就绪列表的操作)
 
 
 
二十九、UCOSIII:基于时基列表的时延操作
1、配置时钟中断时间
            
            
              c
              
              
            
          
          /* 配置SysTick 10ms 中断一次 */
	OS_CPU_SysTickInit (10);
        在中断触发时运行OSTimeTick()函数
            
            
              c
              
              
            
          
          /* SysTick 中断服务函数 */
void SysTick_Handler(void)
{
	OSTimeTick();
}
        OSTimeTick()函数定义如下:
            
            
              c
              
              
            
          
          void  OSTimeTick (void)
{
    /* 更新时基列表 */
    OS_TickListUpdate();
    /* 任务调度 */
    OSSched();
}
        很明显,系统需要10ms一个时钟周期,每一个时钟周期更新一次时基列表
2、创建任务
创建任务需要使用 OSTaskCreate()函数,这部分和之前相同,不再概述。
任务指针格式如下:
            
            
              c
              
              
            
          
          struct os_tcb {
    CPU_STK         *StkPtr;
    CPU_STK_SIZE    StkSize;
    /* 任务延时周期个数 */
    OS_TICK         TaskDelayTicks;
    /* 任务优先级 */
    OS_PRIO         Prio;
    /* 就绪列表双向链表的下一个指针 */
    OS_TCB          *NextPtr;
    /* 就绪列表双向链表的前一个指针 */
    OS_TCB          *PrevPtr;
    /* 时基列表相关字段 */
    OS_TCB          *TickNextPtr;
    OS_TCB          *TickPrevPtr;	
    OS_TICK_SPOKE   *TickSpokePtr;	
    OS_TICK         TickCtrMatch;	
    OS_TICK         TickRemain;		
};
        3、任务放置到就绪列表中,并优先级排队
任务创建好之后,会放到就绪列表中,并在优先级列表对应值中设为1。
上述在之前章节已整理,本次不再概述。
4、将任务插入时基列表
当任务需要延时时,使用OS_TickListInsert()函数将任务插入时基列表。
            
            
              c
              
              
            
          
          /* 将一个任务插入时基列表,根据延时时间的大小升序排列 */
void  OS_TickListInsert (OS_TCB *p_tcb,OS_TICK time)
        
时基列表OSCfg_TickWheel[]有OS_CFG_TICK_WHEEL_SIZE个成员。
OS_CFG_TICK_WHEEL_SIZE的推 荐值为任务数/4,不推荐使用偶数。如果算出来是偶数,则加1变成质数,实际上质数是一个很好的选择。
时基列表OSCfg_TickWheel[]每个成员有三个值。
            
            
              c
              
              
            
          
          typedefstruct  os_tick_spoke       OS_TICK_SPOKE;
//在μC/OS-III中,内核对象的数据类型都会用大写字母重新定义。
struct  os_tick_spoke {
    OS_TCB              *FirstPtr;
    //每个成员都包含一条单向链表, 被插入该条链表的TCB会按照延时时间做升序排列。
    //FirstPtr用于指向这条单向链表的第一个节点。
    
    OS_OBJ_QTY           NbrEntries;
    //NbrEntries表示该条单向链表当前有多少个节点。
    
    OS_OBJ_QTY           NbrEntriesMax;
    //NbrEntriesMax记录该条单向链表最多的时候有多少个节点, 在增加节点的时候会刷新,在删除节点的时候不刷新。
};
        
1. 确认时延
当任务需要插入到时基列表中时,首先需要确认需要时延几个周期,即TickRemain的值。
然后确认OSTickCtr的值,将TickRemain和OSTickCtr相加得到TickCtrMatch。
OSTickCtr是一个全局变量, 记录的是系统自启动以来或者自上次复位以来经过了多少个SysTick周期。
OSTickCtr的值每经过一个SysTick周期其值就加一
2.对任务进行排序
众所周知,任务随时都有可能加入到时基列表中,那么怎么能高效的将各个任务按时延长短进行排序,并快速取用呢?
答案就在TickCtrMatch这个变量上,TickCtrMatch变量是系统设计时的一个巧思妙想。

当一个任务需要插入时基列表中时,我们先获得TickCtrMatch值。
因为TickCtrMatch值是TickRemain和OSTickCtr的和,即当前时间+时延时间,得到的值就是时延结束的绝对时间。
这样就能将所有任务按照 实验结束的绝对时间 进行排序。
3. 确认插入时基列表哪个成员
用TickCtrMatch对OS_CFG_TICK_WHEEL_SIZE进行求余,即对成员总数进行求余,得到的数就是存放任务的下标。
这样做是为了对任务进行分类。
假如成员总数为10(但实际上应该是个质数),当前系统时间为502。
502对10求余得2,那么在任务时延结束绝对时间TickCtrMatch为502,513,515,522的几个任务里只有502,522余数为2。我们不需要跟其他数比,只需要在余数为2的任务里找就行,这样就可以极大的减少寻找时间
同时将余数相同的任务按顺序排列,那么当系统时间为502,任务时延结束的绝对时间为522,那么之后的任务就不需要再找了,因为一定会比522大。

4. 对就绪列表的操作
当任务加入到时基列表中后,就需要从就绪列表中删除。
时基列表OSCfg_TickWheel[]该成员的NbrEntries加1。
当任务任务时延结束绝对时间TickCtrMatch等于系统时间OSTickCtr时就把任务加入到就绪列表,并从时基列表中删除。
时基列表OSCfg_TickWheel[]该成员的NbrEntries减1。
每个时间循环都要确定NbrEntriesMax大于等于NbrEntries