标签:real value timer --- Canfestival sleep Timer time row
1 前言
最近刚接触Canopen开源且免费的协议栈软件Canfestival,它功能较为完备,支持主站和从站,还带有对象字典编辑工具,属于应用较广的Canopen协议栈。结合自己之前也为产品写过Canopen协议的经验,总结下学习Canfestial的心得。
Canopen对于timer需求在SDO的超时判断,周期的心跳,PDO的发送接收都有体现,所以需要对timer做统一设计。
最简单、好理解的做法是在1ms(单位时间)任务里对每个计数器进行计数,当到达给定时间时,回调相应的处理操作。
Canfestival的Timer机制却有所不同,它也是利用一个定时器任务,通过设置不同的定时时间来产生定时任务,实现不同时间异步事件的管理。
1.1 源码获取
这里我下载的是Canfestival-3版本
源码下载地址:https://canfestival.org/code.html.en
2. 原理介绍
大致原理如下图所示,假设Ta、Tb是2个周期定时器(如PDO),Tc是随机启动的定时器(如SDO超时判断),通过设置不同的定时器目标值来生成周期任务和非周期任务,就成了该原理的关键。
3 源码解析
源码中src/timer.c中定义了用户的timer使用接口
SetAlarm | 用户设置申请并设置一个定时器 |
DelAlarm | 用户取消一个定时器 |
TimeDispatch | timer超时任务回调 |
此外,和系统相关的接口还有这么几个
getElapsedTime | 获取自上一次timer超时到现在的时间 |
setTimer | 重置timer计时相对时间 |
一个关键全局的定义
total_sleep_time | 指向timer当前目标延时时间 |
用户调用SetAlarm设置定时器的过程
TIMER_HANDLE SetAlarm(CO_Data* d, UNS32 id, TimerCallback_t callback, TIMEVAL value, TIMEVAL period) { TIMER_HANDLE row_number; s_timer_entry *row; /* in order to decide new timer setting we have to run over all timer rows */ for(row_number=0, row=timers; row_number <= last_timer_raw + 1 && row_number < MAX_NB_TIMER; row_number++, row++) { if (callback && /* if something to store */ row->state == TIMER_FREE) /* and empty row */ { /* just store */ TIMEVAL real_timer_value; TIMEVAL elapsed_time; if (row_number == last_timer_raw + 1) last_timer_raw++; elapsed_time = getElapsedTime(); /* set next wakeup alarm if new entry is sooner than others, or if it is alone */ real_timer_value = value; real_timer_value = min_val(real_timer_value, TIMEVAL_MAX); if (total_sleep_time > elapsed_time && total_sleep_time - elapsed_time > real_timer_value) { total_sleep_time = elapsed_time + real_timer_value; setTimer(real_timer_value); } row->callback = callback; row->d = d; row->id = id; row->val = value + elapsed_time; row->interval = period; row->state = TIMER_ARMED; return row_number; } } return TIMER_NONE; }
该函数让用户设置了要定时的时长value,如果是周期定时器,还需要设置周期period,然后给了自己的callback。函数申请并激活了id最小的一个定时器,把用户的参数填入,最关键的是这段
elapsed_time = getElapsedTime(); /* set next wakeup alarm if new entry is sooner than others, or if it is alone */ real_timer_value = value; real_timer_value = min_val(real_timer_value, TIMEVAL_MAX);if (total_sleep_time > elapsed_time && total_sleep_time - elapsed_time > real_timer_value) { total_sleep_time = elapsed_time + real_timer_value; setTimer(real_timer_value); }
从原理图中Tc的加入就很好理解,如果用户要定的时长+已经运行的时长 < Timer原本要延的时长(total_sleep_time),则可以重置Timer,更新目标时长total_sleep_time;如果要定的时长+已经运行的时长 > Timer原本要延的时长 ,则本次不重置Timer了,等到超时处理时来设置。而Tc定时器自身的时长修改为
value + elapsed_time
也是为了统一把val值统一以上次Timer超时时刻为基本的定时时长。重置Timer的原则:保证所有定时器超时时长谁越短谁优先的原则,而所有定时器超时时长也必须有相同的时间基准,也就是上一时刻Timer超时时刻。这是这个原理能实现的关键。
也正是如此,TimeDispatch这个超时处理过程就好理解了
void TimeDispatch(void) { TIMER_HANDLE i; TIMEVAL next_wakeup = TIMEVAL_MAX; /* used to compute when should normaly occur next wakeup */ /* First run : change timer state depending on time */ /* Get time since timer signal */ UNS32 overrun = (UNS32)getElapsedTime(); TIMEVAL real_total_sleep_time = total_sleep_time + overrun; s_timer_entry *row; for(i=0, row = timers; i <= last_timer_raw; i++, row++) { if (row->state & TIMER_ARMED) /* if row is active */ { if (row->val <= real_total_sleep_time) /* to be trigged */ { if (!row->interval) /* if simply outdated */ { row->state = TIMER_TRIG; /* ask for trig */ } else /* or period have expired */ { /* set val as interval, with 32 bit overrun correction, */ /* modulo for 64 bit not available on all platforms */ row->val = row->interval - (overrun % (UNS32)row->interval); row->state = TIMER_TRIG_PERIOD; /* ask for trig, periodic */ /* Check if this new timer value is the soonest */ if(row->val < next_wakeup) next_wakeup = row->val; } } else { /* Each armed timer value in decremented. */ row->val -= real_total_sleep_time; /* Check if this new timer value is the soonest */ if(row->val < next_wakeup) next_wakeup = row->val; } } } /* Remember how much time we should sleep. */ total_sleep_time = next_wakeup; /* Set timer to soonest occurence */ setTimer(next_wakeup); /* Then trig them or not. */ for(i=0, row = timers; i<=last_timer_raw; i++, row++) { if (row->state & TIMER_TRIG) { row->state &= ~TIMER_TRIG; /* reset trig state (will be free if not periodic) */ if(row->callback) (*row->callback)(row->d, row->id); /* trig ! */ } } }
首先计算当前实际距离上一次超时花的时间real_total_sleep_time,然后根据每个定时器设定的超时时间val来判断,如果val值小于real_total_sleep_time,则说明该定时的超时时间已经满足,可以触发调用callback条件,否则不满足,则修改val值,将val值重新以当前时间基准点,到下一次超时时再去判断,这就是 row->val -= real_total_sleep_time 的由来。最后根据每个定时器的val值取 最小值
next_wakeup来重置Timer,实现下一次任务的调用。同时把 total_sleep_time更新为next_wakeup。 就是这样的设计,就保证了一个Timer通过计算不同的定时值来完成周期任务和非周期任务的功能。 4 心得 同样是定时器设计,我们通常使用循环任务查询方式来实现,通过学习也可以像Canfestival一样,设计成事件任务中断来实现。
标签:real,value,timer,---,Canfestival,sleep,Timer,time,row 来源: https://www.cnblogs.com/wrddc/p/16413226.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。