摘要:现在,在单片机开发领域比较流行使用C语言来开发单片机软件,本文是介绍一种比C语言更高一个层次的开发方法 单片机的多任务操作系统RTOS,并且结合了一个设计范例来说明RTOS的原理和使用方法. It's poluar to develop the embed system software with C language in field of the development of embed system.. I will introduce a way of development ----RTOS multitask's operation system of embed system that is higher than C language. 关键字:C语言,C51,操作系统,RTOS,单片机,蓄电池监控
我们的客户向我们提出需要一种可以监控通讯机房蓄电池组的电池监控器,需要采集无人值守机房蓄电池单体电压,总电压,电流,温度等数据,可以输出告警信号,可以通过modem向远端提供数据和在机房中通过RS232接口配置数据.图1是该电路的原理框图. 图1 由于该系统中各个子任务需要及时响应,所以一般的程序块顺序执行的方式不适合于本系统.(如图2),程序块顺序执行方式的最大难点在于需要控制每个程序段的执行时间,否则对一些事件的响应将丧失实时性.一些任务,如和PC机超级终端通讯,如果每次都只执行一个小时间段后都要退出,将导致软件结构复杂化,而且一个程序段在输入不同数据的情况下,执行时间往往是不同的,这样的情况下,也要保证程序都能够定时退出则更加困难. 图2 基于以上原因,我们考虑使用keil C51的单片机实时操作系统RTOS,它不象unix,windows系统那样包含内存管理,任务管理,网络管理,文件管理等模块,它是一个非常简化的操作系统,只有任务管理和简单的内存管理,其它的模块都没有.该操作系统最多可以支持16个任务同时运转,而对于绝大多数单片机应用来说16个任务已经绰绰有余.以下是该操作系统提供的主要系统函数:
isr_send_signal从一个中断服务程序中发送一个信号给一个任务 os_clear_signal删除一个已经发送的信号 os_create_task建立任务,把任务加入执行序列 os_delete_task删除任务,把任务从执行序列中删除 os_running_task_id返回正在运行的任务的任务号 os_send_signal从一个任务发送信号给另一个任务 os_wait等待特定的事件
本系统使用5个任务来构建,(如图3).其中主任务完成4个子任务的管理和协调;PC232菜单任务完成和PC机上超级终端通讯的人机接口(同时还可以完成和modem的通讯),通过该人机接口,可以让用户完成数据设置,也可以在生产的过程中配置,调试机器,增加生产和维护的方便性;数据采集任务完成电池总体电压,电流和电池单体电压的数据采集;告警任务根据采集来的数据计算出数据是否超标,并且输出相应的告警信号;实时时间任务完成管理I2C总线的实时时钟芯片X1203的访问,并且实时刷新单片机RAM中的时间缓冲区,这个时间缓冲区的数据可以让别的任务共享. 图3 keil C51的单片机实时操作系统RTOS其实是一个准并行的多任务环境,其根本原理和windows的多任务类似,就是将CPU的机时等分切割成为一些时间片,然后分配给每个任务,分配给某个任务的时间耗尽后,如果该任务还没有计算完毕,则强行收回CPU控制权,保护现场后将CPU控制权交给下一个任务,如此循环往复,周而复始.在微观上每次只有一个任务占用CPU,但是由于时间片时间很短(CPU主频为11MHz时,大约每个时间片为20mS),所以从宏观上看,各个任务是并行运转的. 当一个程序段按照多任务的方式来设计后,可以将每个任务都设计成无限循环,可以反反复复地查询自己所管理的的那部分硬件.也可以反反复复地计算数据,不用担心自己独占CPU后,别的任务会因为不能得到CPU机时而丧失实时性.而且当一个任务数据处理完毕后,不需要再占用CPU的时候,可以执行一个OS_WAIT( )来释放CPU,以便别的任务可以更及时地得到处理. 以下是2个任务的范例代码,其中任务0是上述的主任务,它负责建立和删除每个子任务.任务1是上述的和PC机上超级终端通讯的人机接口的任务,任务0利用了RTOS的建立和删除任务的功能,时刻监视菜单任务,可以实现菜单很久没有人操作后自动从菜单树中的任意节点返回到菜单根节点.而这种操作不使用多任务来实现的话需要在菜单任务中插入许多代码,导致软件结构复杂化. void job_main (void) _task_ 0 { unsigned int menu_time_out; long MLN_moni; //监视MLN是否发生变化的变量 beep_lamp_test(); //测试指示灯和蜂鸣器 system_init(); // 系统上电初始化 os_create_task(1); //建立菜单任务 os_create_task(2); //建立ADC任务 menu_time_out=0; MLN_moni=MLN; while (1) { os_wait2(K_TMO, 20); //20个时间片周期才运行一次 if(MLN==MLN_moni) menu_time_out++; else {menu_time_out=0; MLN_moni=MLN; } if (menu_time_out>menusystem_timeout_time) //如果菜单任务超时 { os_delete_task(1); //删除菜单任务 os_create_task(1); //再次建立 menu_time_out=0; } } } void job_menu (void) _task_ 1 { init_comm_mode1(); //初始化串行通讯口 show_copyright(); //显示版权,版本 while (1) { proc_menu_talk(); //处理菜单 } } 在使用RTOS的时候,要注意和一般的单片机软件系统不同.以前的单片机系统是单任务系统,每个子任务是顺序执行的,一个正在运行的程序段可以拥有和控制单片机内外所有的资源,可以任意安排使用.而在多任务环境下就不同了,资源在访问时如果处置不当,可能会发生冲突或其它不确定性,这表现为一些以前在单任务下可以良好运行的代码段到了RTOS环境下就不能得到正确结果.以下以本系统中2个访问I2C总线的任务为例子来说明这个问题. 有2个任务,一个是前述人机接口中访问I2C总线上的EEPROM器件24C32的,另一个是实时时钟任务访问I2C总线上的实时时钟X1203的,这两个器件都挂接在SDA和SCL两个信号线上.请看图4. 图4 访问它们的时候,需要按照器件的时序要求来送出脉冲信号,才能完成CPU和器件之间的通讯 图5 图5是I2C总线的SDA数据线上的波形图,如果按照本图上半部分的时序,I2C总线可以正常访问,但是假设在本例中任务1运行到黑竖线的地方,RTOS发生了任务切换,任务2同样需要访问I2C总线,于是它也向I2C总线发送一串脉冲,虽然每个任务访问I2C总线的时序都没有错误,但这时SDA数据线上的逻辑状态被破坏却是实实在在的.在这种情况下任务1和任务2谁也不能得到正确的数据.这就是多任务下资源冲突的一种表现形式.其实解决这个问题也不难,我们可以设置一个信号量来表示I2C总线是否被占用,如果被占用就一直等待到I2C总线被释放,同时自己占用I2C总线也置I2C总线忙标志,访问结束后清除.如下是它的代码:
bit IIC_bus_busy // I2C总线忙标志,1有效 while(IIC_bus_busy) //如果I2C总线被占用,则等待I2C总线不被占用 { delay( ); } IIC_bus_busy=1; //锁定I2C总线 accessing_IIC_bus( ); //访问I2C总线 IIC_bus_busy=0; //解锁I2C总线 Page 6-6 2001-6-2
RTOS的另外一个问题就是重入,所谓重入就是说一个任务正在运行一个函数,当RTOS发生了任务切换后,如果也调用这个函数,就会破坏上一个任务调用此函数的现场,导致上一个任务的计算错误.看以下的例子,这是本系统中用到的一个计算一个整数是10的多少次方的函数.(例如9=9×100 返回0, 789=7.89×102 返回2).该函数定义了3个内部变量,假设任务1运行到for (i=0;i=0 && num<=9) return(0); for (i=0;i<8;i++) { s=num/n; if(s==0) return(i-1); else n=n*10; } return(-1); } 解决了多任务下的资源冲突和重入后,RTOS就比较完美了,相信大家在熟练使用RTOS后一定能够感受到它带来的稳定和高效.
参考文献 1. 《RTOS-51 Tiny user guide》 http://www.keil.com 2. 《ATMEL 24C32 datasheet》 http://www.atmel.com
|