基于状态机的程序架构
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
项目4
简易数字钟的设计(2)计算机专业有门必修课程叫“软件工程”,这门课程告诉软件学习者们如何系统性的、规范化的、可定量的过程化方法去开发和维护软件。我们在学习单片机编程的过程当中,也应该借鉴“软件工程”课程当中的讲述的方法和手段,去维护和规范我们的单片机程序。
在本单元当中,我们安排了4个任务。任务1介绍了一种基于状态机的程序框架,通过状态机的学习,初学者可以写出思路清晰、多任务运行流畅的程序。任务2介绍了程序的风格和可移植性,规范了变量和函数等的命名,并简单介绍了C51中提高程序可移植性的方法。任务3介绍了程序模块化的实现方法,让初学者学会合理的管理程序。任务4中运用本单元所讲的知识,结合前一单元,完成简易数字钟的设计。
【内容安排】
4.1基于状态机的程序框架
4.2程序的风格和可移植性
4.3程序的模块化
4.4简易数字钟的设计
任务4.1 基于状态机的程序框架
4.1.1任务介绍
上一单元中已经多次提到多任务运行时,延时函数(DelayMs())对程序的危害性,堵塞
CPU系统任务的实时性得不到有效的保证。在 3.4节中,提到中断可以提高任务的实时性,
但是单片机的中断数量是有限的,不可能每一个任务都有中断。在 3.5节中,通过定时器中
断服务函数提供的时标信号,定时扫描LED和数码管,可以消除延时函数,时标信号给我们
提供了一种新的思路来消除延时函数(本质上还是借助于中断)。但是LED闪烁和动态数码管
扫描都是属于状态时间分配均匀的(LED闪烁有两个状态,亮和灭分配时间相等;数码管每个
位扫描的时间也相等),程序易于实现。对于像按键检测这样的(时间分配不均匀的)任务,怎样来消除程序中的延时呢?
本节任务是:
利用本节所讲“状态机”,改写独立按键程序,并增加“长按”、“连击”等功能。
4.1.2知识准备
1、状态机的思想
网络上经常报道特级象棋大师车和多人一起下象棋,采用的方式是“车轮战”。车轮战有
两种方式:(1)象棋大师先和甲开始下象棋,直到有了结果,然后才轮到乙和象棋大师对阵,下完了之后,然后是丙……,一直到和最后一个人下完。(2)象棋大师先和A下一步棋,
然后再和B下一步棋,然后再和C,和……,和所有人下完一遍后,再回头从A开始,一个
人接一个人。
很显然“车轮战”的第1种方式效率不如第2种方式效率高,报道上的“车轮战”也是指的第2种方式。原因在于象棋大师的水平远远高于其他人,如果采用第一种方式,象棋大师下一步棋很快,甲需要考虑很长时间才能落子,象棋大师在和甲下棋的过程中,其他人只能等待。如果采用第二种方式,象棋大师和甲只下一步棋,然后再和乙也下一步棋,和所有人下完一步棋之后,再从甲开始,这样看起来是所有人都在下象棋,效率自然远高于第一种方式。
“车轮战”的第2种方式,实际上就是程序中状态机的基本原理。程序中的多个任务可以看成是其他棋手,CPU是象棋大师,CPU在执行多个任务时,不再是先执行任务1,执行完
任务1后,再执行任务2,而是把每个任务又划分出多个小任务(小任务中没有时间等待),CPU每次只执行每个任务中的小任务,执行完任务1中的一个小任务后,然后快速转向任务 2 中的小任务,按照这种模式轮询下去,由于CPU很快(象棋大师),整个程序中的任务都得到
了实时的执行。任务中的小任务是按照任务的状态来划分的,故称为“状态机”。
上一单元中,利用定时器的时标信号扫描动态数码管的程序实际上也是采用了状态机的
原理。时标信号到来后,CPU扫描1位数码管,然后去执行别的任务,下一个时标信号到来后,再扫描下一位数码管。6位数码管的扫描被分成了6个子任务,CPU每一次只执行一个子任务。
2、任务的划分
数码管扫描的任务划分非常简单,因为每一个子任务执行的时间时均匀的,而且任务很
相似。但是程序中大多数任务划分出来的子任务时间分布不均匀,而且划分出来的子任务不
相似。举个例子,2个LED,第一个LED按照亮1秒,灭2秒的方式闪烁,第2个LED按照亮2秒,灭1秒的方式闪烁,要求不用延时函实现。
我们先给出程序,通过程序学习状态机的实现方法。
整个程序的框架思路清晰,定时器0产生1ms时标信号供主函数使用,主函数每间隔1ms 执行任务1和任务2。任务1和任务2实现内容类似,下面我们分析任务1的内容。
任务1中的Led1Twinkle()函数是通过状态机完成的,状态机由switch-case 语句构造。
在程序中先定义状态机变量( Led1State,静态局部变量),并赋初值0,以及时间计数变量
(Led1Cnt )。LED1的状态为亮1秒,灭2秒,分2个状态,所以switch-case 语句中对应LED1 的两个状态有两个case分支,状态机变量(Led1State有2个值,分别为0和1。
系统上电运行,1ms时标到来,第一次执行任务1,由于Led1State初值为0,则进入到
第一个分支,在case语句中,CPU只执行两条语句:点亮LED1;让计数变量加1( Led1Cnt=1),并判断是否等于1000,如果不是则直接跳出switch-case 语句(break不可以忽略),任务1 执行完毕,然后再执行任务2。下一个1ms 再到来,再进入第一个分支,计数变量再加
1(Led1Cnt=2),再退出,时标信号到来,计数变量再加1, 一直循环下去…。直到计数变量满足了大于等于1000的条件,则计数变量置0,并把状态机变量置1( LedState=1),指向第二个分支,退出任务。下一个1ms再次到来时,进入任务1后,根据状态机变量的值,进入
到程序的第二个分支,熄灭LED1,重新对计数变量计数,接下来的程序运行模式和第一个分
支是一样的。
总结:“状态机”的思想实际上是把系统所有任务按照任务的状态划分出多个子任务,以时标信号为基准,主程序不停的轮询各任务中的子任务。子任务之间的跳转依靠计数变量计数或者某些状态量的改变。
3、基于状态机的按键检测
上一单元中的独立按键的扫描程序如下: