rtthread:rt_timer定时器
1 systick中断处理函数
对于单片机而言,定时器的时钟节拍由systick提供,所以我们在此先记录一下systick中断处理函数;
在systick中断处理函数中对时钟节拍进行自加加,调用rt_timer_check( )对定时器进行扫描;
//board.c 使能systick定时器;系统晶振25MHz,RT_TICK_PER_SECOND分频1000;tick定时1ms; SysTick_Config( SystemCoreClock / RT_TICK_PER_SECOND ); //board.c 不知道为什么老是有建议中断函数放在stm32f10x_it.c中写,但我就没见过bsp中断写在stm32f10x_it.c中的; void SysTick_Handler(void){ rt_interrupt_enter(); rt_tick_increase(); rt_interrupt_leave(); } //clock.c void rt_tick_increase(void) { struct rt_thread *thread; /* increase the global tick */ ++ rt_tick; /* check time slice */ thread = rt_thread_self(); -- thread->remaining_tick; if (thread->remaining_tick == 0) { /* change to initialized tick */ thread->remaining_tick = thread->init_tick; /* yield 时间片用完之后让出处理器,但线程还是READY态;*/ rt_thread_yield(); } /* check timer */ rt_timer_check(); }
SysTick_Config( )
/***core_cm3.h systick不仅在core_cm3.h中提供了使用函数,还在misc.c中提供了使用函数; 正常外设不仅需要使能自身的中断,还要使能nvic中断才能进入中断函数; systick只要使能自身中断就可以使用中断函数了,但是呢它又配置了nvic优先级。。。 总之这个内核的SysTick_Config函数搭配SystemCoreClock宏还挺好用的,以后要是用到了就用它了; ***/ static __INLINE uint32_t SysTick_Config(uint32_t ticks) { if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */ SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set reload register */ NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* set Priority for Cortex-M0 System Interrupts */ SysTick->VAL = 0; /* Load the SysTick Counter Value */ SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */ return (0); /* Function successful */ }
2 定时器
大概rtthread觉得直接等待的阻塞延时效率不够高,逻辑不够优美;所以它给每个thread都配置了一个rt_timer类型的thread_timer定时器;
所有定时器由定时器链表统一管理,通过对thread_timer定时器统一管理多个线程的延时,效率高,逻辑优美;
2.1 定时器结构体
//rtdef.h #define RT_TIMER_FLAG_DEACTIVATED 0x0 /**< timer is deactive */ #define RT_TIMER_FLAG_ACTIVATED 0x1 /**< timer is active */ #define RT_TIMER_FLAG_ONE_SHOT 0x0 /**< one shot timer */ #define RT_TIMER_FLAG_PERIODIC 0x2 /**< periodic timer */ #define RT_TIMER_FLAG_HARD_TIMER 0x0 /**< hard timer,the timer's callback function will be called in tick isr. */ #define RT_TIMER_FLAG_SOFT_TIMER 0x4 /**< soft timer,the timer's callback function will be called in timer thread. */ #define RT_TIMER_CTRL_SET_TIME 0x0 /**< set timer control command */ #define RT_TIMER_CTRL_GET_TIME 0x1 /**< get timer control command */ #define RT_TIMER_CTRL_SET_ONESHOT 0x2 /**< change timer to one shot */ #define RT_TIMER_CTRL_SET_PERIODIC 0x3 /**< change timer to periodic */ #ifndef RT_TIMER_SKIP_LIST_LEVEL #define RT_TIMER_SKIP_LIST_LEVEL 1 #endif /* 1 or 3 */ #ifndef RT_TIMER_SKIP_LIST_MASK #define RT_TIMER_SKIP_LIST_MASK 0x3 #endif struct rt_object { char name[RT_NAME_MAX]; /**< name of kernel object */ rt_uint8_t type; /**< type of kernel object */ rt_uint8_t flag; /**< 上面的flag define */ #ifdef RT_USING_MODULE void *module_id; /**< id of application module */ #endif rt_list_t list; /**< list node of kernel object */ }; typedef struct rt_object *rt_object_t; /**< Type for kernel objects. */ struct rt_timer { struct rt_object parent; //还搞了个继承 rt_list_t row[RT_TIMER_SKIP_LIST_LEVEL]; //挂载到rt_timer_list链表 void (*timeout_func)(void *parameter); //超时函数地址,将线程重新挂载到优先级表、组 void *parameter; //所在thread的结构体地址 rt_tick_t init_tick; //延时时间 rt_tick_t timeout_tick; //延时时间 + rt_tick全局变量数值 }; typedef struct rt_timer *rt_timer_t;
2.2 定时器链表
定时器开始:将所在延时线程的优先级从优先级组和表中移除,然后把自己挂载到定时器链表上;
定时器关闭:将所在延时线程的优先级挂回优先级组和表中,然后把自己从定时器链表上移除;
//timer.c /* hard timer list */ static rt_list_t rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL]; #ifdef RT_USING_TIMER_SOFT #ifndef RT_TIMER_THREAD_STACK_SIZE #define RT_TIMER_THREAD_STACK_SIZE 512 #endif #ifndef RT_TIMER_THREAD_PRIO #define RT_TIMER_THREAD_PRIO 0 #endif /* soft timer list */ static rt_list_t rt_soft_timer_list[RT_TIMER_SKIP_LIST_LEVEL]; static struct rt_thread timer_thread; ALIGN(RT_ALIGN_SIZE) static rt_uint8_t timer_thread_stack[RT_TIMER_THREAD_STACK_SIZE]; #endif
2.3 rt_timer_init( )
每个线程结构体都有一个自己的定时器结构体,在对thread结构体初始化的时候也初始化了定时器结构体;
//thread.c _rt_thread_init() rt_timer_init(&(thread->thread_timer), thread->name, rt_thread_timeout, //超时函数 thread, 0, RT_TIMER_FLAG_ONE_SHOT); //默认定时器启动一次; //timer.c void rt_timer_init(rt_timer_t timer, const char *name, void (*timeout)(void *parameter), void *parameter, rt_tick_t time, rt_uint8_t flag) { rt_object_init((rt_object_t)timer, RT_Object_Class_Timer, name);//初始化对象变量,并将其节点list插入对应容器中; _rt_timer_init(timer, timeout, parameter, time, flag); //初始化剩下部分的定时器参数 } static void _rt_timer_init(rt_timer_t timer, void (*timeout)(void *parameter), void *parameter, rt_tick_t time, rt_uint8_t flag) { int i; timer->parent.flag = flag; timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED; timer->timeout_func = timeout; timer->parameter = parameter; timer->timeout_tick = 0; timer->init_tick = time; //定时器节点初始化成自身,用来在需要延时的时候将自己挂载到定时器链表上; for (i = 0; i < RT_TIMER_SKIP_LIST_LEVEL; i++) { rt_list_init(&(timer->row[i])); } }
3 定时器函数
3.1 rt_timer_start( )
定时器在线程结构体初始化的时候也一并初始化了,那么定时器又是在什么时候启动的呢?定时器是在线程函数需要延时的时候启动的;
当线程需要延时的时候,就会rt_list_remove( ) &tlist和&timer->row[0],suspend后的线程不会执行rt_schedule;
在rt_timer_start中设置timer->timeout_tick数值,然后将 &timer->row[0]插入定时器链表rt_timer_list[0]之后,rt_schedule()其他线程;
直到通过超时函数重新挂回优先级表和组后才可以调度执行;
//thread.c //当线程需要延时的时候调用;作用是把线程优先级挂起,开启定时器 //挂起的意思是将节点从所在链表中移除,并且将节点初始化为自身地址; rt_err_t rt_thread_sleep(rt_tick_t tick) { register rt_base_t temp; struct rt_thread *thread; temp = rt_hw_interrupt_disable(); thread = rt_current_thread; rt_thread_suspend(thread); //rt_list_remove() &tlist,&timer->row[0];如果相同优先级没有其他线程则清零优先级组; rt_timer_control(&(thread->thread_timer), RT_TIMER_CTRL_SET_TIME, &tick); rt_timer_start(&(thread->thread_timer)); //把定时器按延时时间排序挂载到定时器链表中; rt_hw_interrupt_enable(temp); rt_schedule(); //当前线程优先级在前面被挂起了,调度执行其他函数去; return RT_EOK; }
//timer.c //static rt_list_t rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL]; 定时器链表; //这个if else蛮新颖的,乍一看匪夷所思,多看几遍构思很巧,熟能生巧的"巧",以我的水平写不出来;20240122:重新看一遍还是需要细看才理解; //如果前面的数都要小于,那么找到一个大于的,然后插入到这个大于的数前面就可以了; rt_err_t rt_timer_start(rt_timer_t timer) { unsigned int row_lvl = 0; rt_list_t *timer_list; //定时器链表,用来比较的; register rt_base_t level; rt_list_t *row_head[RT_TIMER_SKIP_LIST_LEVEL]; //定时器链表头,用来处理定时器节点的; unsigned int tst_nr; static unsigned int random_nr; //前面rt_thread_suspend中设置过了,这里又设置了一遍; level = rt_hw_interrupt_disable(); _rt_timer_remove(timer); timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED; rt_hw_interrupt_enable(level); //rt_thread规定 timeout tick < (RT_TICK_MAX/2);这样的话比较的时候大于部分就作为负数的掩码使用; timer->timeout_tick = rt_tick_get() + timer->init_tick; level = rt_hw_interrupt_disable(); timer_list = rt_timer_list; row_head[0] = &timer_list[0]; //这个最外层的for循环在系统定时器只有一个时并没有实质性作用,那什么时候会有多个系统定时器呢? for (row_lvl = 0; row_lvl < RT_TIMER_SKIP_LIST_LEVEL; row_lvl++) { //第一个定时器插入时,自身地址就是.prev地址,不执行该for循环;timer_list[row_lvl].prev为链表尾巴地址,延时最长; //第二个定时器插入开始,才满足下面for循环条件,执行for循环; for (; row_head[row_lvl] != timer_list[row_lvl].prev; row_head[row_lvl] = row_head[row_lvl]->next) { struct rt_timer *t; rt_list_t *p = row_head[row_lvl]->next;//第一个插入的定时器节点地址; //找出 定时器链表中第一个链表节点所在的定时器结构体首地址; //#define rt_list_entry(ptr, type, member) (type *) { (char *)ptr - (unsigned long)[&(type *0)->member] } t = rt_list_entry(p, struct rt_timer, row[row_lvl]); //相等的话也继续,直到下一个定时器是大于当前定时器的,然后插入到它前面的后面; if ((t->timeout_tick - timer->timeout_tick) == 0) continue; //下面判断等价于(定时器t->timeout_tick > 待插入timer->timeout_tick) //row_head[row_lvl]->next的时间大于timer->timeout_tick,那么row_head[row_lvl]节点的时间小于timer->timeout_tick //break之后,把timer插入到row_head[row_lvl]之后; //如果timer->timeout_tick比较大,前面的数都符合小于但是后面的数不一定符合全部大于,这里的问题在于负数掩码是极大值; else if ((t->timeout_tick - timer->timeout_tick) < RT_TICK_MAX / 2) break; //else 如果timer->timeout_tick最大,因为比较的p是row_head[0]->next,退出的时候执行了for循坏语句,row_head[0]就变成了最后一个节点; //这样的话t timer被遍历了一遍之后,row_head[row_lvl] == timer_list[row_lvl].prev,退出for循坏了;这是逻辑小坑,要细心; } //始终不执行,也不知道干啥用,不管了; if (row_lvl != RT_TIMER_SKIP_LIST_LEVEL - 1) row_head[row_lvl + 1] = row_head[row_lvl] + 1; } random_nr++; //静态变量,用于记录启动了多少定时器; tst_nr = random_nr; rt_list_insert_after(row_head[RT_TIMER_SKIP_LIST_LEVEL - 1], &(timer->row[RT_TIMER_SKIP_LIST_LEVEL - 1])); //这个for不会执行,有多个系统定时器链表的时候才会用到,先放着; for (row_lvl = 2; row_lvl <= RT_TIMER_SKIP_LIST_LEVEL; row_lvl++) { if (!(tst_nr & RT_TIMER_SKIP_LIST_MASK)) rt_list_insert_after(row_head[RT_TIMER_SKIP_LIST_LEVEL - row_lvl], &(timer->row[RT_TIMER_SKIP_LIST_LEVEL - row_lvl])); else break; tst_nr >>= (RT_TIMER_SKIP_LIST_MASK + 1) >> 1; } timer->parent.flag |= RT_TIMER_FLAG_ACTIVATED; rt_hw_interrupt_enable(level); return -RT_EOK; }
3.2 rt_timer_check( )
systick作为定时器的时间节拍,在systick中断函数中会对定时器进行扫描;
如果第一个定时器的延时时间到了,就rt_list_remove(&timer->row[0]),然后调用rt_thread_timeout( );根据flag决定是否重新开启定时器;
//因为定时器链表是按延时时间升序排列的,所以第一个定时器时间没到,后面的定时器也没到; void rt_timer_check(void) { struct rt_timer *t; rt_tick_t current_tick; register rt_base_t level; current_tick = rt_tick_get(); level = rt_hw_interrupt_disable(); while (!rt_list_isempty(&rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1])) { t = rt_list_entry(rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1].next,struct rt_timer,row[RT_TIMER_SKIP_LIST_LEVEL - 1]); if ((current_tick - t->timeout_tick) < RT_TICK_MAX / 2) { _rt_timer_remove(t); t->timeout_func(t->parameter); //这个是调用超时函数的方式,第一次见; current_tick = rt_tick_get(); RT_DEBUG_LOG(RT_DEBUG_TIMER, ("current tick: %d\n", current_tick)); if ( (t->parent.flag & RT_TIMER_FLAG_PERIODIC) && (t->parent.flag & RT_TIMER_FLAG_ACTIVATED) ){ //如果线程定时器是周期循环,则清除状态位之后、重新启动定时器; t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED; rt_timer_start(t); } else //如果线程定时器只是定时一次,则清除状态位之后、不重新启动定时器; t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED; } else break; //如果第一个定时器时间没到,那就退出扫描; } rt_hw_interrupt_enable(level); }
3.3 rt_thread_timeout( )
rt_timer结构体中存放了超时函数的地址,以及将所在rt_thread的首地址作为参数传递给超时函数;
当定时器超时之后,就会调用超时函数,在超时函数中将定时器所在线程重新挂回优先级组和优先级表中;
//thread.c void rt_thread_timeout(void *parameter) { struct rt_thread *thread; thread = (struct rt_thread *)parameter; /* thread check */ RT_ASSERT(thread != RT_NULL); RT_ASSERT((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_SUSPEND); /* set error number */ thread->error = -RT_ETIMEOUT; //负号的功能类似return -1; /* remove from suspend list */ rt_list_remove(&(thread->tlist)); //在rt_timer_check中已经remove了一遍,这里又remove了一遍; /* insert to schedule ready list */ rt_schedule_insert_thread(thread); //将函数重新插入优先级表和优先级组,stat改成ready; /* do schedule */ rt_schedule(); //找出优先级组中年优先级最高的线程,然后切换到该线程; }
3.4 ipc定时器使用举例
ipc中有等待时间时,悬起线程后,用来开启定时器的操作;开启定时器之后要是到期了回来,线程状态码此时为-RT_TIMEROUT;
/* suspend current thread */ rt_ipc_list_suspend(&(mutex->parent.suspend_thread), thread, mutex->parent.parent.flag); if (time > 0) { RT_DEBUG_LOG(RT_DEBUG_IPC, ("set thread:%s to timer list\n", thread->name)); /* reset the timeout of thread timer and start it */ rt_timer_control(&(thread->thread_timer), RT_TIMER_CTRL_SET_TIME, &time); rt_timer_start(&(thread->thread_timer)); } /* enable interrupt */ rt_hw_interrupt_enable(temp); /* do schedule */ rt_schedule(); if (thread->error != RT_EOK) { return thread->error;//在定时器超时函数中,修改了这个thread->error; }
4 时间片
rtos中高优先级的线程可以将低优先级的线程suspend,然后让芯片执行高优先级的线程;那么对于优先级相同的线程rtos又该如何调度执行呢?
当线程的优先级相同时,就为每个相同优先级的线程配置一个时间片,每个线程都依次执行时间片长度的时间;为了确保效率需要合理安排每个线程的时间片长度;
相同优先级下,一个线程执行完自己的时间片之后,就将处理器交由下一个线程执行完下一个线程的时间片;通过时间片切换之前的线程态仍是READY态;
目的是确保相同优先级下的线程都能够合理均匀地分配到处理器资源;
4.1 定义
//在rt_thread结构体中定义的时间片参数; struct rt_thread { //... rt_ubase_t init_tick; /* 初始时间片 */ rt_ubase_t remaining_tick; /* 剩余时间片 */ //... }; typedef struct rt_thread *rt_thread_t;
4.2 初始化
//在rt_thread_init()中线程初始化的时候,对线程的两个时间片参数初始化; rt_err_t rt_thread_init(/*...其他参数*/ ,rt_uint32_t tick) { /*...其他参数初始化*/ thread->init_tick = tick; thread->remaining_tick = tick; }
4.3 使用函数
//添加时间片功能,只需要在定时器的基础上添加部分时间片函数代码就可以了,内容也不多,主要就两个函数; //clock.c void rt_tick_increase(void) { ++ rt_tick; struct rt_thread *thread; thread = rt_thread_self(); -- thread->remaining_tick; //数的自减减可以这么写; if (thread->remaining_tick == 0) { thread->remaining_tick = thread->init_tick; rt_thread_yield(); //时间片用光之后,将线程重新插入当前优先级表,然后执行rt_schedule; } rt_timer_check(); } //thread.c //将&thread->tlist remove之后重新insert_before当前优先级表,当前线程还是READY态;然后rt_schedule; rt_err_t rt_thread_yield(void) { register rt_base_t level; struct rt_thread *thread; /* disable interrupt */ level = rt_hw_interrupt_disable(); /* set to current thread */ thread = rt_current_thread; /* if the thread stat is READY and on ready queue list */ if ((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_READY && thread->tlist.next != thread->tlist.prev) { /* remove thread from thread list */ rt_list_remove(&(thread->tlist)); /* put thread to end of ready queue */ rt_list_insert_before(&(rt_thread_priority_table[thread->current_priority]), &(thread->tlist)); /* enable interrupt */ rt_hw_interrupt_enable(level); rt_schedule(); return RT_EOK; } /* enable interrupt */ rt_hw_interrupt_enable(level); return RT_EOK; }
5小结
主要在于rt_timer_start函数的逻辑,写的蛮好;
如果结构体成员是函数指针,那么它是怎么通过结构体成员调用函数指针的?
那个systick_config()函数搭配SystemCoreClock使用,systick使能中断函数不需要配置nvic中断,但是又需要配置nvic中断优先级;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通