本帖最后由 lilijin1995 于 2022-12-13 13:09 编辑
序:MultiTimer这样一个软件定时器拓展模块真的很方便,但是如果如果任务回调函数中执行了太耗时的任务的话,可能会导致其他任务来不及执行,相关的tick没法实现自加,现在就我们使用过程遇到的一些问题与大家分享一下,由于楼主水平有限,文档和视频中难免有出错和讲得不好的地方,欢迎各位读者和观众善意地提出意见和建议,谢谢!
关于MultiTimer
MultiTimer 是一个软件定时器扩展模块,作者是0x1abin,可无限扩展你所需的定时器任务,取代传统的标志位判断方式, 更优雅更便捷地管理程序的时间触发时序。
功能限制,这里给出MultiTimer GitHub上的开源链接,:
https://github.com/0x1abin/MultiTimer
1.定时器的时钟频率直接影响定时器的精确度,尽可能采用1ms/5ms/10ms这几个精度较高的tick; 2.定时器的回调函数内不应执行耗时操作,否则可能因占用过长的时间,导致其他定时器无法正常超时; 3.由于定时器的回调函数是在 MultiTimerYield 内执行的,需要注意栈空间的使用不能过大,否则可能会导致栈溢出。
诚如以上功能限制中的描述,第一个点要求我们是满足了,在CH32V003中移植了,使用的是Systick配置的1ms作为时钟基准。
由于项目保密性的原因,部分代码删减,但不会影响大家理解的。
首先是配置Systick 1Ms进入中断,产生1ms的tick。
- void SysTick_Handler(void) {
- uwTick += 1;
- SysTick->SR = 0;
- }
- uint64_t PlatformTicksGetFunc(void) {
- return uwTick;
- }
- /*********************************************************************
- * @fn main
- *
- * [url=home.php?mod=space&uid=247401]@brief[/url] Main program.
- *
- * [url=home.php?mod=space&uid=266161]@return[/url] none
- */
- int main(void)
- {
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
- Delay_Init();
- USART_Printf_Init(115200);
- printf("Demo:TM1650 Test!!!\r\n");
- printf("SystemClk:%d\r\n",SystemCoreClock);
- //systick
- NVIC_EnableIRQ(SysTicK_IRQn);
- SysTick->SR &= ~(1 << 0);
- SysTick->CMP = (SystemCoreClock/1000)-1;//1ms
- SysTick->CNT = 0;
- SysTick->CTLR = 0xF;
- PollSystemInit();
- while(1)
- {
- MultiTimerYield();
- }
- }
我们创建了一个定时任务,用于执行按键数码管驱动的扫描,这是一个专门的驱动IC方案,使用软件I2C方案,从数码管数据的更新,再加上按键的扫描,其中有多个软件us延迟:
- /*******************读按键命令************************/
- u8 TM1650_read(void) {
- u8 key;
- IIC_Start();
- IIC_Send_Byte(0x49); //读按键指令
- IIC_Wait_Ack();
- key = IIC_Read_Byte(0);
- IIC_Wait_Ack();
- IIC_Stop();
- return key;
- }
- /***显示1-99数据***/
- void TM1650Disp2Num(unsigned char snum) {
- Dig1_Data = Digital_Table[snum / 10 % 10];
- Dig2_Data = Digital_Table[snum % 10];
- DigitalScan();
- }
这样的话就违反了第二个限制,不能执行太耗时的操作。因为我们在第一个定时器任务里面执行了启动第二个定时器。如下:- void TM1650_Timer1Callback(MultiTimer* timer, void *userData) {
- switch (TM1650ReadKey(0)) {
- case ONOFF_KEY: //KI6+DIG1 21
- {
- PowerSta = !PowerSta;
- if (PowerSta == PowerON) {
- WorkingTime=15;
- TM1650Disp2Num(WorkingTime);
- MultiTimerStart(&timer2, 1000, Remaining_WorkingPeriodTimer2Callback, NULL);
- break;
- } else {
- Display_0F();
- }
- }
- break;
- default:
- break;
- }
- MultiTimerStart(timer, 50, TM1650_Timer1Callback, userData);
- }
在定时器1任务中,按下按键ON开启定时器2任务:
- void Remaining_WorkingPeriodTimer2Callback(MultiTimer* timer, void *userData) {
- if (PowerSta == PowerON)
- {
- if(++RunTick1S==60)
- {
- RunTick1S=0;
- if(++RunTick1m==15)
- {
- TM1650Disp2Num(0);
- }else{
- TM1650Disp2Num(WorkingTime-RunTick1m);
- }
- }
- printf("RunTick1S=%d,RunTick1m=%d\r\n",RunTick1S,RunTick1m);
- MultiTimerStart(timer, 60000, Remaining_WorkingPeriodTimer1Callback, userData);
- }
- }
在定时2中通过RunTick1S和RunTick1m计算1分钟,这是一个分钟计时器,然后我又想,Systick中断是不会被打断的,我可以1分钟执行一次定时器2的任务,如下:
- void Remaining_WorkingPeriodTimer2Callback(MultiTimer* timer, void *userData) {
- if (PowerSta == PowerON)
- {
- if(++RunTick1m==15)
- {
- TM1650Disp2Num(0);
- }else{
- TM1650Disp2Num(WorkingTime-RunTick1m);
- }
- printf("RunTick1S=%d,RunTick1m=%d\r\n",RunTick1S,RunTick1m);
- MultiTimerStart(timer, 60000, Remaining_WorkingPeriodTimer2Callback, userData);
- }
- }
现在是1min中进入一次定时器2回调,然后我们实测,2min,离晒大谱了。
最后猜测Systick的中断都不准,后来发现,我们用了HSE,而我们并没有外部晶振,这一切都说得通了
后来配置为HSI,完美,相差无几
总结
这是完全是粗心惹的祸,作为一名工程师,这种错误还是少犯才行,为避免这种错误,我觉得还是需要给自己安排一个软件设计流程,按流程来走,不然以后又出现这种笑掉诸位大牙的事件。
|