哈尔滨工程大学本科生毕业论文
的。硬件驱动层的软件负责直接操作硬件,并且给上层的软件提供一定的接口,这样有助于上层的软件实现更复杂的功能,并且系统的硬件有所改动时也只需改动相应的驱动模块即可。
本系统中的LED显示模块、前轮转向模块、后轮驱动模块、AT24C08存取模块、寻光源转动机构都做成了一些独立的模块,并且给外部提供了一些接口函数,来实现对这些硬件或机械部件的高级操作。
分层结构如图2.2所示: 软件的分层结构是很多系统中普遍采用的一种软件
结构,比如TCP/IP协议就是一种典型的分层结构。WINDOWS、LINUX等系统中也几乎把所有的系统硬件进行了抽象,这样上层的软件就不必关心硬件的细节,可以调用相应的模块提供的服务即可,这样可把更多的精力放在高级功能的实现上。 2.4.2多任务结构
为了充分利用单片机的CPU,内存等资源,本系统中引入了多任务的软件结构,即从宏观上来看单片机同时在做多件事情。分析一般的多任务系统的软件结构,系统的核心是任务调度器,在适当的时候任务调度器将保存当前任务的现场,并且恢复将要运行的任务的现场,并让其投入运行。简单的说,一般的多任务系统是任务调度器循环的调用各个需要执行的任务,进而可以更有效的利用系统的各种资源。从这里得到启发,可以用定时器每隔一定的时间中断一次,在中断处理函数中依次调用一次各个任务所对应的函数,并且各个函数都
8
逻辑控制层 置、取全局变量 硬件驱动层 操作硬件 硬件设备 图2.2 软件分层结构示意图
哈尔滨工程大学本科生毕业论文
能在一个较短的时间内返回,这样在某段时间内,各个任务所对应的函数都能够被执行到,就好像多个任务同时运行了。还有一点需要说明,就是各个任务是由一些函数和一些静态变量组成。函数由定时器中断处理函数定期的调用一次,并且有个前提就是这个函数能够在较短的时间内返回,否则其他任务将不能及时的被调用到,也就达不到“实时”这一要求。静态变量保存该任务的各种状态,并且其他模块和该任务进行通信也是通过置取这些静态变量来实现的。
本系统中软件的多任务结构如图2.3所示:
可以说定时器中断处理函数就是本设计中多任务的核心,即任务调度器。以下是定时器0中断处理函数的程序清单:
/////////////////////////////////////////////////////
系统复位 任务1 定时器初始化 0中断处理函主任务设计成无限循环结构 数 任务2 任务3 任务n 图2.3 多任务结构示意图
//定时器0中断处理函数 //每4毫秒中断一次 //产生时钟节拍
9
哈尔滨工程大学本科生毕业论文
//负责维护一个系统时间变量和任务调度 /////////////////////////////////////////////////////
#define TIME_OVERLOAD 3960 //定时器计数初值 //记录时间的结构的定义 typedef struct {
uchar t_ms;
//毫秒数0-99
uchar t_100ms; //100毫秒数0-9 uchar t_sec;
//秒数0-59
uchar t_min; //分钟数,0-255 }TIME;
TIME time; //记录系统时间的全局变量 void timer0(void) interrupt 1 {
//重装定时器0的计数初值
TH0=(65536-TIME_OVERLOAD)/256; TL0=(65536-TIME_OVERLOAD)%6; //维护系统时间 time.t_ms+=4; if(time.t_ms>99) {
time.t_ms=0;
if(++time.t_100ms>9) {
time.t_100ms=0; if(++time.t_sec>59) {
10
哈尔滨工程大学本科生毕业论文
time.t_sec=0; time.t_min++; } } }
//大约每50毫秒调用一次蜂鸣器任务
//并且只有在需要发声时才调用 //n_beep全局变量表示需要发出几声
if((n_beep)&&(!(time.t_msR)))beep2();
//每4毫秒调用一次显示任务
led_disp();
//每16毫秒调用一次后轮直流电机驱动程序
if(!(time.t_ms&0x0f))dianji(); //每32毫秒调用一次里程检测任务 if(!(time.t_ms&0x1f))licheng();
//每16ms调用一次前轮转向电机驱动函数
if(!(time.t_ms&0x0f))gw_dianji(); }
以上的这段程序代码就是实现了任务的调度,和一般的多任务系统相比较有几点不同:
(1) 任务之间的切换是通过函数的调用与返回实现的,当以上的这几个任务全调用一次之后将进入主任务执行,主任务即main()函数所对应的任务。
(2) 相应的任务上、下文的保存与恢复也不是由任务调度器实现的,而是由C语言编译器在函数调用时自动保存与恢复了主任务的上、下文;其他的任务不用保存上、下文,每次进入执行都是从相应函数的第一行开始,寄存器值也不用保存。
11
哈尔滨工程大学本科生毕业论文
总之,采用以上这种软件结构,也可以实现多个任务并发运行,在timer0()函数中可以加进更多的任务,只要各个任务都能在一个较段的时间内执行一次并返回到timer0()函数中就可以。此外,关于RAM的分配是在编译的时候完成的,各个任务一般要用一些全局的静态变量来标识自身状态。
在主控单片机上有以下几个任务:
(1) 主任务:main()开始的任务,很多功能都要在该任务中完成; (2) 蜂鸣器发声任务:beep2(),全局变量n_beep表示需要发出几个“嘀”声;
(3) LED显示任务:led_disp(),LED数码管和LED发光二极管显示任务;
(4) 后轮电机驱动任务:dianji(),目前只实现了电机的正、反转和停止功能,以后可以在该任务中加入PWM调速功能;
(5) 前轮转向任务:gw_dianji(),控制步进电机让前轮转到指定的角度;
(6) 里程检测任务:licheng(),检测小车的行驶里程。 在从单片机上有四个任务:
(1) 寻找光源方向任务:该任务作为从单片机的背景任务,由main()函数调用,然后一直循环的执行;
(2) 前超声测障任务:chaosheng_qian(),由从单片机的定时器中断处理函数timer0()每隔64毫秒调用一次;
(3) 后超声测障任务:chaosheng_hou(),每64毫秒调用一次,和chaosheng_qian()的调用相差32毫秒;
(4) 旋转机构转动任务:driver_dianji(),将寻找光源方向的转动机构旋转到指定的角度。 2.4.3消息驱动结构
考虑到系统中无论是操作者按下一个按键,还是遇到障碍或是寻
12