大家好,我是惊觉。失踪了三个月,我回来了。给大家带来一个好消息和一个坏消息。坏消息是,我尚未满血复活,Ardupilot第四篇将继续延期。好消息是,公众号恢复更新,先出一系列提升编码能力的文章。 全国电赛在即,昨天母校老师联系我,想让我给学弟们做下赛前培训。我做过很多年的培训,很早就发现了一个问题:同学们在为比赛做准备时,往往只注重去学习使用各种各样的传感器,自动控制算法,各种驱动。同学们只关注如何去实现功能,而忽视了如何把代码写得更好,更健壮,更易扩展和维护。如果在比赛之前,先准备好高质量的代码框架,基础模块,熟练掌握调度技巧,将极大提高赛时的开发和调试效率。 所谓高质量,涉及到很多方面,比如:
笔者不打算一一讲解这些设计原则,而是介绍一些实际的基础模块,讲解它们的设计思路,注意事项和编程技巧,并在此过程中让大家理解相关的设计原则。 毫秒级定时模块作为系列开篇,本文先介绍一个非常基础的模块:毫秒级定时模块。 友情提醒,本模块比较基础,可能有的同学对此非常熟悉,不过文末还有一个重要的小技巧噢。有基础的同学,可直接往后翻,跳到“再看comm_delay”一节。 此模块提供基础的定时功能,可细分为两种:
可能有的同学会想,直接用单片机的定时器嘛,一个任务用一个定时器,有啥好讲的。其实不然。定时模块肯定要依赖于硬件定时器,但是一个任务用一个定时器的话,会有如下问题:
因此,我们需要一个统一的,可移植性强的定时模块。 我们再回头看下两个基础的功能,延时和定时。 延时示例,1秒打印1次hello。comm_delay实现毫秒极延时。 void comm_delay(uint32_t ms); 定时示例,1秒打印1次hello。comm_get_ms返回当前系统时间,即系统从启动到现在经过了多少毫秒。
可能有的同学觉得上述两项功能差不多,而定时比延时的代码要复杂。定时的代码确实多一些,不过它具有并发能力,即支持多个定时任务同时进行。 定时示例,1秒打印1次you,2秒打印1次me。 static void show_you(void) 小结:
即:
其实延时函数很简单,因为它也可以看成是一个定时任务: void comm_delay(uint32_t ms) 系统时间实现comm_get_ms,即记录系统时间,自然要靠硬件定时器啦。大家用的单片机,无论是TI,STM32,NXP等,大部分都是cortex-m的内核,该内核有一个专门干这事情的定时器:SysTick timer。其名称为系统滴答定时器,只有简单的定时功能。配置好reload计数并使能后,其由reload值递减至0,触发中断,再从reload递减。如果根据其时钟配置相应的reload值,实现每1ms触发1次中断,那就可以记录毫秒 级的系统时间。 其配置函数位于单片机驱动库的CMSIS组件中,一般的工程都包含了这个组件,比如笔者使用TRUEStudio创建的工程: 下面是stm32的示例。SystemCoreClock为单片机的主频,这也是SysTick的输入时钟。SystemCoreClock / 1000即为1ms的定时计数,将reload配置为此值即可实现每1ms触发1次定时中断。其他单片机方法类似。
用法非常简单,单片机启动时调用sys_tick_init配置并使能SysTick,每1ms触发1次SysTick_Handler,其内对当前时间sys_tick进行加1操作。应用层通过sys_tick_get获取当前时间。 SysTick_Handler在中断向量表中指定,大家根据具体的MCU对号入座。 comm_get_ms只需对sys_tick_get进行简单的封装: uint32_t comm_get_ms(void) 再看comm_delay我们再看一下毫秒极延时的实现,大家觉得它有问题吗?
对于只需要进行几分钟演示的电赛来说,它没有问题。不过,电赛只是同学们实践所学的一条途径。正儿八经的产品,需要具有足够的健壮性,可长期稳定地运行。上述代码能长期运行吗? static uint32_t sys_tick = 0; comm_get_ms是对sys_tick_get的简单封装,而sys_tick_get返回的是一个32位无符号整型变量,它记录的是系统从启动到现在所经过的毫秒数。32位无符号整型变量最大能表示多长的时间呢?
其可记录49天。在49天后,sys_tick将会溢出,从零重新开始累加。为了方便描述,要使用到一个宏UINT32_MAX。UINT32_MAX表示32位无符号整型变量的最大值,即0xffffffff。 假设我们要延时1分钟,即60000ms。当前时间为UINT32_MAX - 59999,下面计算timeout。 uint32_t timeout = comm_get_ms() + ms; timeout发生溢出,计算结果为0。
那么下面的等待循环将立刻退出,而不需要等待1分钟。 while(comm_get_ms() < timeout); 在接下来的1分钟内,comm_get_ms(60000)都是失效的,每1分钟执行1次的任务将不停地执行。还有其他溢出的场景,这里不再一一描述。我们只要明确一点就好:comm_delay不能长期运行。 健壮的comm_delay怎么修改comm_delay以解决溢出问题呢?其实很简单,直接给出答案:
我们简单地验证几个场景: 当前时间为10ms,延时2ms。先计算timeout: 下面看看从现在开始,什么时候
此种场景,成功实现延时2ms。 当前时间为(UINT32_MAX - 1)ms,延时2ms。之所以定成UINT32_MAX - 1,是想测试时间溢出的场景,2ms后时间溢出。 先计算timeout,timeout在加2时溢出,最终结果为0。 下面看看从现在开始,什么时候
此种场景,成功实现延时2ms。 总结经过两种情况的测试,我们发现,无论计算过程中时间有无溢出,改进后的comm_delay都圆满完成延时。 这种实现的原理是什么呢?原理很重要噢,否则大家在使用时,可能把大于和小于关系搞反,或者是把被减数与减数的关系搞反。至于原理是什么呢,今天来不及讲了,请待下回分解。剧透一下,下篇的名字叫:张三与李四谁跑的快。 |
|
来自: 西北望msm66g9f > 《培训》