单片机的状态机框架编写_续
之前大致写过一篇状态机比较粗糙的博文,写了:状态机的方式比较适合裸机编程,但是不够深入,这里在深入一点,起因是美敦力medtronic公开了PB560呼吸机完整设计资料,里面用到了ST10F276的芯片,就是用的状态机的思想,所以状态机的思想肯定是比较好的,不然大公司不会使用这个框架来做呼吸机啊,这可是医疗产品,质量必须过关的。整个产品都要经过医疗认证的,无论是电气、软件、结构等等。
因此中小型裸机项目,使用状态机来编程应该是毫无压力的。但是要用好,还是要好好规划一下。
状态机的使用离不开定时器,通常是使用定时中断来触发一个时间来切换状态机的状态的,比如按键,10ms就去处理一下按键处理子程序,理想情况下,10ms基本上能够轮询到这个按键子程序,但是主程序通常不止一个处理子函数啊,还有其他的程序如下面所示,那请问,10ms到了,主程序能否执行完其他的函数呢,即能否在10ms左右再次进入按键子程序?不一定,要看1、单片机的主频,2、看外部设备的处理速度,3、单片机和外部芯片的通信速度,4、单片机其他函数的数学、逻辑计算量。
1、单片机主频越高,那么相应的数学计算,逻辑运算都会提升不少。
2、与外部芯片的通信速度,这个稍稍复杂,涉及到硬件的布线,是否允许高速通信,涉及到抗干扰,高频电路等;还有涉及到通信协议,SPI(MHZ)的速度比IIC(KHZ)快上一个数量级,而且spi还有qspi,dspi等高速通信的方式,这个速度就更加快了,当然单片机的总线速度也要比较高才行,不支持高速,那么只能低速了。
3、外部芯片的处理速度,有的芯片如果处理的很慢,比如ds18b20,开启转换到转换结束,最慢700多ms,那么就干脆把两个状态使用状态机来处理,不用死等,基本上耗费的时间就是单片机对于单总线的数据访问的时间了。看datesheet,一般都是us级别的,几ms的时间肯定是通信完成的。但是尽量在2ms以内处理完毕。而有的芯片,处理速度比较快,10ms以内,那么直接等待处理完毕即可,不用再设计一个状态机了,比如ad1259模数转换芯片。但是ad7793这个芯片,是∑-Δ行模数转换芯片,那么转换时间是120ms,有点长了,可以设计一个状态机,spi通信时间基本上us级别,可以忽略。
那么再来看看按键处理程序能否10ms左右来处理了,分析后,有点悬,那么解决方案有两种:是把按键处理的时间延长一倍:20ms轮询一次;要么使用rtos的方式来处理,但是ds18b20的时序比较严格,有一个时间必须延时几us之内,那么和其他的延时就有所区别(其他的延时都是至少延时几us,这种延时适合rtos系统,多延时几us无关紧要,即便被中断了几us也可以接受)。
如果保留10ms的处理间隔时间,那么实际上,按键处理间隔时间是长于10ms,比如2ms的时候处理轮询了按键程序,定时器在12ms重新置位了按键标志位,但是main函数还没处理完其他子程序,导致按键程序可能在15ms的时候处理了。如果在长一点,23ms的时候再来处理了,那么干脆把按键的时间间隔延长到20ms算了。
其实,while(1)的大循环,本身时序性就不够准确的,没有rtos的实时性高,想想,很多商业的rtos都是硬实时rtos,实时性当然比裸机要好了。裸机本身在的一定程度上就是软实时的,对于按键来说,10-20ms其实对人来说,差别不大的,真正使用起来不会影响用户体验的,多按一段时间和少按一段时间,对于呼吸机这个产品来说都是可以接受的。
这边文章是吃饭的时候无意中想到,想深入剖析一下,把微观的部分放大,来看看单片机到底是怎么执行状态机的,如有写的不好,还望指点。
int main(int argc, char const *argv[])
{
while(1)
{
ds18b20_discope();
key_discope();
adc_discope();
}
return 0;
}
void ds18b20_discope(void)
{
switch (ds18b20的状态机的全局变量)
{
case 发送命令:
发送转换命令
赋值到等待装态
break;
case 等待装态:
判断是否有超时,
如果有超时,则:读取,计数器清零,并回到发送命令状态
否则,do nothing
break;
default:
break;
}
}