孔宪青
(威海职业学院,山东 威海264200)
用C 语言在Keil 软件上进行单片机程序的编译是比较有代表性的。并且随着单片机小系统控制模块越来越多,发展出了模块化程序结构。但不可避免的是,单片机控制的模块越多,模块间数据交换和函数的相互调用也越复杂。传统上的main 主函数所在文件是所以模块调用和数据汇总的集中区域,同时main 主函数的while(1)循环要对总线模块进行多任务的时序分配,本文通过附加一个模块化程序Assistant.c 文件使时序分配和模块管理分离,使之各自独立化,提高了程序的条理和可读性,并使改错和程序扩展进一步简化。
传统的KeilC51 模块化程序就是有一个main.c 文件和若干子模块构成。而其中子模块由一组*.c 和*.h 共同构成。Main.c 文件调用各自子模块的头文件,而子模块头文件里带有可外部调用的函数和数据。这样通过头文件建立和主函数文件的沟通桥梁,事实上也是唯一的一个桥梁。结构见图一,程序构成如下:
#include "h_a.h" ......
void main(void){ while(1){//模块时序分配} }
#ifndef _H_A_H_ #define _H_A_H_
//模块函数的外调函数
#endif
各子模块满足的条件是:
(1)硬件驱动模块,一种特定硬件对应一个模块(图中以h 开头的模块);
(2)软件功能模块,其模块的划分应满足低偶合、高内聚[1]的要求(图中以s 开头的模块)。
主函数和子模块头文件的结构类似于图一中右侧程序。
子函数的.c 文件的结构是和mian.c 主函数文件是一致的,注意要把自身头文件包含进去。除了外调的函数外,包括全局常变量部分,所有函数都要用static 静态关键字封闭函数本体。这样才能满足高内聚和独立化的要求,下面通过Ds1302 模块说明的声明形式:
可以看出需要外调的都是不带static 关键字的,内聚函数都是静态的,这样也可以避免不同模块的函数同名问题。
Main.c 文件内部while(1){……}循环体内要进行模块的时序分配,我个人的解决方式是通过节拍控制来管理。具体是采用一个定时器来产生节拍,例如AT89S52 的16 位T2 定时器,方式是设置T2 为自动重装,然后每50ms 产生一个节拍。这样在程序的前台[2]总有一个节拍来控制模块的启停。下面是程序段:
(1)定义节拍
char g_Beat[6]=0; //全局节拍的个数由并行模块的数量决定
(2)设置节拍
(3)中断方式激活节拍
(4)调用节拍控制并行模块
while(1)
{if(g_Beat[1]>=10){g_Beat[1]=0;NecKey(); } //模块一0.5 秒执行一次
if(g_Beat[2]>=20){g_Beat[2]=0;CalendarShow(); } //模块二1 秒执行一次
if(g_Beat[3]>=30){g_Beat[3]=0;TimeDot(); } //模块三1.5 秒执行一次
if(g_Beat[4]>=50){g_Beat[4]=0;Ds18b20TempN5110();} } //模 块 四2.5 秒执行一次
节拍控制是占用一个定时器且利用中断产生的节拍,因此把节拍设置的长一些比较好,例如50ms,同时定时器也设置为低优先级。通过节拍main 函数的并行模式不是一个真正的并行序列,因为单片机指令的执行微观上是串行的,只有FPGA 这种器件才能实现真正的并行。但是宏观上节拍的引入,使模块的发生仅仅出现在时间轴的若干点上,这样模块的执行就相似于并行序列,而且时钟越快,这种串行模拟越能逼近并行的工作模式。
优化是因为,main 主函数要把所有采集回来的数据进行处理,并进行时序分配,这些任务对于模块较少时候可以,但是模块多于6 个以上后,这种控制变得的极其繁琐。例如做一个万年历的小系统,内部使用了h_N5110.c(显示)、h_Ds18b20.c(温度)、h_Ds1302.c(时 间/日历)、h_Isd1420.c(放音)、s_IrNecRxd(设置通讯)、s_ChinaLunar.h (阳阴历转换)共6 个模块。其中软件模块和硬件模块都有,并且相互调用时候数据交互复杂,如N5110 模块中显示字符串这个函数就要被不同模块中的很多函数调用。
优化的方式是建立一个Assistant.c 模块,称为主函数的助手模块。这个模块的主要任务就是把所有子模块的外出数据和外调函数都集中到本模块内,在这里进行汇总材料和组成被主函数main 调用的纯函数(即输入输出都是void 的函数)。假设子模块是把原料合成半成品的加工厂,那么Assistant 模块就是把半成品组装成商品的组装厂,而main 函数就是使用这些商品的客户,而客户的唯一任务就是使用这些商品并分配利益即分配多任务下的总线时间,具体程序优化后的结构见图1。
Assistant 模块对于子模块来说其实是个管理者。它管理和接受各个子模块部门的数据和函数。这里注意一点就是子模块的外调函数也是纯函数,它们使用时不调用任何非自身模块的数据和函数,即使模块内部需要相同的某个函数例如延时,也应该设置成静态函数。总之,子模块是非常纯粹的数据和函数包体。
而Assistant 和main 主函数的关系更像主管和秘书,它最后形成的函数必须是输入输出都是void 的函数,这样才能断开main 和子模块的所有联系。Assistant 模块是一个承上启下的一个模块,通过它把子模块的若干问题解决后,提供个main 若干单一纯粹的函数。Assistant 的出现使并行模块的时间分配和数据处理分离开来。使时序的问题和函数的问题解耦并各自独立化。
子模块构建要求它不能外调其他模块的函数,完全靠自身的函数来实现基本驱动的功能。如果模块内部需要调用来自外部的值,应该设置成入口参数形式和返回值形式,不能把外部输入量直接写入函数内部。只有子模块内部有中断并且在中断中产生了数值才允许使用extern 输入输出中间值。Assistant 是个混合模块,事实上它是所有模块中最复杂的,在这个模块中有大量的全局变量和常量,它的目标就是组合子模块各种资源并形成纯void 函数给main 调用。换句话说,优化模块化程序设计KeilC51,集中的任务就是如何写Assistant。Main 函数的唯一任务就是通过节拍控制Assistant 提供的函数以实现多任务并行运行,高效率的完成总线周期。
本文从KeilC51 的模块化程序出发,介绍了在多模块下如何组织和优化程序结构方法。其中要点是建立Asssitant 模块使多种模块的功能单一化。这种组织形式为类似的程序处理提供了参考。
[1][美]Michael J.Pont.C 语言嵌入式系统开发[M].中国电力出版社,2003-12-01.
[2]侯殿有.基于八位单片机的C 语言程序设计[M].北京大学出版社,2012-09-01