对于初学者,掌握了单片机的结构,背熟了指令并不代表就会写程序了。好多初学者都有这种体验,看书上的示例程序感觉都看懂了,但是一旦自己需要去写一个类似的程序时,往往无从下手。其实也很好理解这种情形,就好像我们认识好多文字,但不一定能写出好文章。那么如何来解决复杂程序的设计思路问题呢?
1.复杂程序的一般流程
尽管所有程序都是由顺序、分支和循环三种基本结构组成,但针对不同的程序功能,单片机程序的设计思路也不尽相同。事实上,大多数程序可以套用如图5-24所示的基本流程。
(1)初始化程序
初始化程序,用来对I/O接口、RAM(变量)、堆栈、定时器、中断等功能进行初始化。初始化程序一般只需要执行一遍。
(2)主程序(功能循环)
初始化程序结束后,程序运行的工作环境已经建立起来,此时可以进入主程序。主程序一般是一个循环体。这里运行的程序是程序要实现的各种功能,如输入检测、输出控制及人机界面等。一般把这些功能写成子程序的形式。
图5-24 复杂程序基本流程图
2.功能的模块化
所谓功能的模块化,就是将程序要完成的功能化整为零,逐步将复杂程序子程序化的过程。
1)功能的模块化:将一个复杂程序的功能进行分解,尽量分解成多个互不影响的子功能。
2)子功能的模块化:将子功能继续分解成一个个没有直接联系,但可以通过变量或地址联系起来的子程序。
在这种模块化的程序结构中,主程序仅仅是执行调度功能,负责轮流调用功能模块程序。主程序每循环一圈,所有的功能模块都被调用一次(见图5-25)。
3.模块的事件驱动
如图5-26所示,主程序按顺序依次调用各个功能模块,但有些模块可能在当次循环中并不具备运行条件。模块的事件驱动机制就用来解决此类问题。所谓事件驱动机制,就是给每个模块设置一个“使能标志”,通过使能标志来触发该模块代表的事件。也就是说,在每次进入功能模块(子程序)时,先判断该模块是否满足运行条件(功能模块使能标志等于1?),如果满足则运行(同时将使能标志清零),否则直接返回即可(见图5-26)。这样做加快了主程序的循环速度,提升整个系统的实时性。
图5-25 模块化程序结构
我们举例说明,比如说,有一个按键功能模块,用于扫描按键。实际上,我们没有必要在主程序的每次循环中都去扫描按键。我们只要为该按键模块定义一个扫描使能标志。在计时程序中定期(比如说是10ms)把该标志置为1。然后在显示程序开始处先判断一下这个标志。如果时间没到,则不必进行按键扫描。某个功能模块的使能标志,可以由其他模块进行设置。就像前面说到的按键扫描使能标志一样。其按键功能由按键扫描程序模块实现,而标志却是由计时程序或定时程序来进行设置。也就是说,虽然按键程序的运行与否受到计时程序的控制,但这两个模块之间并不存在互相调用的情况。它们之间仅仅是通过标志位进行联系。
这个按键扫描使能标志就是一个按键扫描事件的触发条件。同时也相当于其他程序对按键模块的控制条件。在整个系统的任何其他程序模块中,当我们觉得有必要扫描按键时,都可以通过这个标志去通知按键模块。有时,可能一个标志不足以传递所有的控制信息,我们可以用更多的标志或寄存器来实现命令和参数的传递。(www.xing528.com)
图5-26 功能模块的程序结构
4.顺序调度机制与优先调度机制
前面讲的是在进入功能模块时,先查询该功能模块的使能标志。我们也可以把这种查询的动作放在主程序中,并因此延伸出两种不同的主程序调度机制:
1)顺序调度机制
如果各个模块之间没有优先级区别,我们可以采取顺序调度机制(见图5-27)。这种调度机制的特征是,主程序按照一定的顺序,轮流查询各个功能模块的使能标志。如果标志有效,就执行相应的模块,并清除该标志。一个模块查询或执行结束后,继续对下一个模块进行操作。全部模块操作结束后,回到主程序开始处,如此循环不止,周而复始。采取顺序调度机制的程序结构的优点是,可以保证所有的功能模块都得到执行的机会,并且这种机会是均等的。顺序调度机制的缺点是,某些重要的模块无法得到及时的响应。
2)优先调度机制
如果各个功能模块有优先级的区别,我们可以采取优先调度机制(见图5-28)。这种调度机制的特征是,主程序按照一定的优先级次序,去查询各个标志。如果高优先级功能模块的使能标志有效,则在执行完该模块(并清除该标志)后,不再执行后续模块的查询操作,而是跳转到主程序开始处,重新开始新一轮操作。采取优先调度机制的程序结构的优点是,可以让排在前面的优先级高的功能模块获得更多更及时的执行机会。优先调度机制的缺点是,那些排在末位的模块有可能被堵塞。
图5-27 顺序调度机制
图5-28 优先调度机制
5.中断与前后台的程序结构
前面讲的在主程序中进行事件轮询的调度机制,应付一般的任务可以游刃有余了。但是一旦遇到紧急突发事件,还是无法保证即时响应。因此,单片机中引入了中断的概念。
我们把实时性要求更高的事件(比如,外部触发信号,或者通信)放在中断中(前台)响应,把实时性要求较低的任务(比如,按键扫描、显示刷新)交给主程序(后台)去调度。这样,就形成了前后台的程序结构模型(见图5-29)。那么,哪些任务应该放在中断中处理,哪些任务应该放在后台程序中处理呢?其实这是没有绝对的准则的。有些任务(比如前面提到的按键定时扫描)既可以放在前台(定时中断)执行,也可以放在后台执行。这取决于项目的具体情况,以及个人的编程习惯。前后台任务的配置,有两个比较极端的例子。一种情况就是,对于有些单片机来说,根本就没有中断源(如PIC的一些低端芯片)。所有的任务都要依靠主程序去合理调度。另一种情况是,现在有些单片机的中断资源极大丰富,几乎所有任务都可以通过中断实现。有时,人们干脆就让中断承担了全部的工作。后台程序除了上电时的初始化动作外,平时什么都不干,干脆呼呼大睡(进Sleep模式,或待机模式),以降低系统功耗,避免干扰。在大多数情况下,任务是由前台程序和后台程序分工合作完成的。为了避免前台程序和后台程序互相抢夺CPU的控制权,发生竞争,建议尽可能减少中断的执行时间。我们可以在中断服务程序中设置一些标志,然后回到主程序中来查询这些标志并做进一步的处理。
关于复杂程序的设计思路,除了以上介绍的常用调度机制,还有时间片轮转分时调度机制、基于状态机的调度机制等,在此不再一一赘述。
图5-29 前后台程序结构
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。