2010-10-14 | #1 (permalink) |
论坛管理员
注册日期: 2009-06-30
帖子: 861
|
状态机在嵌入式前后台系统中的应用
状态机在嵌入式前后台系统中的应用 冯地明 在嵌入式前后台系统中,外部的异步事件通过中断来捕获,运行在后台。而其它的任务则运行于前台。提高系统中的任务处理能力,是嵌入式前后台系统设计的重点。本文描述了利用状态机来提高嵌入式前后台系统的任务处理能力的实现方法。 关键字: 前后台系统 状态机 队列 软件定时器 为了便于研究和描述状态机在嵌入式前后台软件系统中的应用,本文将以移动2G光纤直放站近端机的监控软件案例来阐述和说明。 1.移动2G光纤直放站近端机控单元简介 针对研究的目的,对于移动2G光纤直放站近端机监控单元只介绍与本文有关的部分的原理框图,如图1所示。 图1:移动2G光纤直放站近端机监控单元 在移动2G光纤直放站近端机中通过串口1发送到GSM Modem的数据不仅仅是短消息,还包括下行功率查询、信源信息查询、读取/删除短消息等。 因此,针对移动2G光纤直放站近端机监控单元的要求和软件系统为前后台系统的特点,移动2G光纤直放站近端机监控单元的监控软件设计采用了状态机和队列。 2.软件的设计思路 根据前后台软件系统的特点,结合移动2G光纤直放站近端机的硬件结构,以移动2G光纤直放站近端机的监控软件中的短消息收发子系统来阐述,软件的设计思路。 GSM Modem的短消息接收采用软件主动读取的方式,即软件被动(中断方式)接收Modem接收到短消息在Modem中存储的序号,然后软件主动读取短消息和删除已读取的短消息。短消息的收发处理数据流向如下图2所示。 图2:直放站短信收发处理结构 如图2,GSM Modem主动上报的信息将存储到串口1接收缓冲区中,软件从串口1接收缓冲区的数据中介析出短信序号(Modem收到的短消息在Modem中的存储序号)存储到短信序号队列(短信序号缓冲区1~n)中,然后软件通过短信序号队列的状态来决定是否需要向Modem发送读短信或者删除短信命令。 当软件发送读短消息命令后,GSM Modem将对应序号的短信息送出,数据将存储到串口1接收缓冲区中,软件再从串口1接收缓冲区的数据中解析出短消存储到短信队列(短信缓冲区1~m)中。这样需要软件处理的短消息就存储到了短信队列中,而处理的事情则交由软件的其它部分区处理。 2.2.短消息的发送 对所有需要发送到GSM Modem的数据,则通过UART1发送缓冲区来完成。具体发送那些数据(读/删除短信、下行功率查询、信源信息查询、未读短信查询)或者缓冲区的数据(短消息发送缓冲区、告警上报发送缓冲区)由软件根据相应的状态来选择确定。 3.设计思路的实现 3.1.串口1数据的发送 3.1.1.串口1发送缓冲区的数据结构 串口1是否有数据需要发送,由串口1的发送缓冲区的状态来决定,其串口1发送缓冲区的数据结构定义如下所示。 typedef struct{ unsigned char bStBuf; // bStBuf = Uart1_TxBuf_Rdy或者 // = Uart1_TxBuf_Wait或者 // = Uart1_TxBuf_Empty unsigned short Index; unsigned short Len; char Buf[270]; } Uart1Buf_t; Ø bStBuf成员:串口1发送缓存的状态。 Ø Index成员:串口1发送数据缓存索引。 Ø Len成员:串口1发送数据缓存中有效数据长度。 Ø Buf成员:串口1发送数据缓冲区。 3.1.2.串口1发送缓冲区软件定时器 由于GSM Modem的特性致使串口1不能不间断地发送数据,因此,对串口1的数据发送设定一个软件定时器,软件定时器用于控制GSM Modem是否可以接收来之串口1的新数据。软件定时器的结构定义如下所示。 typedef struct{ unsigned char bTimerSt; // 软件定时器的状态: Timer_START或者 // Timer_STOP unsigned int TimerCtn; // 软件定时器的计数器 void (*func)(void); // 超时后的相应的处理功能函数指针 } SoftTimer_t; Ø bTimerSt成员:用于描述软件定时器的状态。它有2种状态: l Timer_START:开始软件定时器。 l Timer_STOP:停止软件定时器。 Ø TimerCtn成员:用于描述软件定时器的定时时间。它是一个32位的计数器,硬件定时的基准时间为20ms(建议设置在前后台系统主程序循环一次需要的时间)。因此,最大的定时时间20ms*2^32= 85,899,345.92秒。 Ø func成员:用于描述软件定时器超时需要去处理相应事情的函数。该函数是在定时器中断服务程序下运行,因此为了减少中断服务程序占用CPU的时间,函数在只做简单的状态设置或者清除工作。如函数Clear_Uart1TxbStBuf。 void Clear_Uart1TxbStBuf(void){ Uart1Tx.bStBuf = Uart1_TxBuf_Empty; // 设置串口1发送缓冲区为空 …… } 3.1.3.串口1数据发送状态机 串口1发送缓冲区的成员bStBuf有3种状态: Ø Uart1_TxBuf_Rdy:串口1发送缓冲区数据准备好。 Ø Uart1_TxBuf_Wait:串口1发送缓冲区数据等待。 Ø Uart1_TxBuf_Empty:串口1发送缓冲区空。 其3种状态的转移情况如图3所示。 图3:串口1的发送缓冲区状态机 在将数据需要发送的数据拷贝到串口1发送缓冲区后,开启串口1的发送中断,软件将进入串口1的发送中断服务程序。这个中断服务程序将检测串口1发送缓冲区的状态,如果状态为Uart1_TxBuf_Rdy,则说明串口1发送缓冲区中有数据需要发送,这时串口1缓冲区的数据通过串口1的发送中断把所有的数据发送给GSM Modem。当数据发送完毕后,串口1发送缓冲区的状态将转移到Uart1_TxBuf_Wait状态,否则,将维持当前的状态。如函数Uart1_Tx。 void Uart1_Tx(void){ if(Uart1Tx.bStBuf == Uart1_TxBuf_Rdy){ // 发送缓冲区准备好了数据 *AT91C_US1_RHR = Uart1Tx.Buf[Uart1Tx.Index++]; if(Uart1Tx.Index == Uart1Tx.Len){ // 判断数据是否发送完 Uart1Tx.bStBuf = Uart1_TxBuf_Wait; // 改变串口1发送缓冲区 // 的状态 Uart1TxIntDis(); // 关闭串口1发送中断 Uart1TxTimer.bTimerSt = Timer_START; // 开启串口1软件定时器 } } } 当串口1发送缓冲区的状态在Uart1_TxBuf_Wait状态时,它可以有两条路径让串口1发送缓冲区的状态转移到Uart1_TxBuf_Empty。 其一,就是串口1软件定时器超时,如下面的程序代码所示。 #define Uart1TxTimer (Timer[0]) // 便于阅读 void Clear_Uart1TxbStBuf(void){ // 串口1软件定时器超时处理函数 Uart1Tx.bStBuf = Uart1_TxBuf_Empty; // 设置串口1发送缓冲区为空 …… } void SoftTimer_Init(void){ // 软件定时器初始化函数 Uart1TxTimer. bTimerSt = Timer_STOP; …… Uart1TxTimer.func = Clear_Uart1TxbStBuf; …… } void SoftTimer_Handler(void){ // 软件定时器计数处理函数 int i; for(i=0; i<SoftTimer_NUM; i++){ if(Timer[i].bTimerSt == Timer_START){ if(Timer[i].TimerCtn-- == 0){ // 定时器超时? Timer[i].bTimerSt = Timer_STOP; // 停止定时计数器 Timer[i].func(); // 该定时器对应的相关操作 } } } } 其二,就是相应的条件成立。如发送端消息,当软件从串口1的接收缓冲区中解析出“+CMGS n(1≤n≤255)”信息或者发送失败的信息时,串口1发送缓冲区的状态将转移到Uart1_TxBuf_Empty状态,同时停止串口1软件定时器;读短消息收到“+CMGR ……”信息。 3.2.短信数据的发送 如图2所示,需要通过串口1发送的数据包括:读/删除短信数据、下行功率查询数据、信源信息查询数据、未读短信查询数据、短消息发送缓冲区数据、告警上报发送缓冲区数据。其中对读/删除短信数据、下行功率查询数据、信源信息查询数据、未读短信查询数据,它们直接由GSM Modem处理,并做出处理结果应答。因此,这类数据直接通过串口1发送缓冲发送。 而短信数据(短消息发送缓冲区数据、告警上报发送缓冲区数据)发送需要两步操作(先发送短信的目的电话号码,再发送短信消息内容),发送是否完成,与GSM Modem和GSM网络有关。因此,这类数据的发送,先将发送操作的所有数据存储到短信数据缓冲区中,然后由软件通过短信数据缓冲区的状态,将数据通过串口1发送缓冲区发送给GSM Modem。 3.1.1.短信数据数据结构 短信数据包括短消息发送缓冲区数据和告警上报发送缓冲区数据。根据短信发送操作的两步,短信数据缓冲区的数据结构定义如下: typedef struct{ unsigned char bStBuf; // bStBuf = SmsTx_Emty或者 // = SmsTx_CmdRdy或者 // = SmsTx_Dly1 或者 // = SmsTx_DatRdy或者 // = SmsTx_Dly2或者 // = SmsTx _Wait unsigned char cmd_len; char cmd_buf[32]; unsigned short dat_len; char dat_buf[SMS_LEN+1]; unsigned char retry_time; // 重传次数 } SmsTx_t; Ø bStBuf成员:用于描述短信数据缓冲区的状态。 Ø cmd_len成员:用于描述cmd_buf中数据的长度。 Ø cmd_buf成员:用于存储短消息发送中的控制命令,如AT+CMGS=13583823789。 Ø dat_len成员:用于描述储短消息发送中的信息体长度。 Ø dat_buf成员:用于存储短消息发送中的信息体。 Ø retry_time成员:用于描述短消息在发送失败时,重传的次数。 3.1.2.短信数据发送状态机 短信数据缓冲区的状态有4种状态: Ø SmsTx_Empty:短信数据缓冲区空。 Ø SmsTx_CmdRdy:短信数据缓冲区控制命令准备好。 Ø SmsTx_Dly1:短信数据缓冲区延时1。 Ø SmsTx_DatRdy:短信数据缓冲区消息体准备好。 Ø SmsTx_Dly2:短信数据缓冲区延时2。 Ø SmsTx_Wait:短信数据缓冲区等待。 其状态的转移情况如下图4所示。 图4:短消数据发送状态机 void Uart1TxDatDeal(void){ (1) if(ReportTx.bStBuf == SmsTx_CmdRdy){ (2) if(Uart1Tx.bStBuf == Uart1_TxBuf_Empty){ (3) sprintf(Uart1Tx.Buf, ReportTx.cmd_buf); (4) Uart1Tx.Len = ReportTx.cmd_len; (5) Uart1Tx.Index = 0; (6) Uart1Tx.bStBuf = Uart1_TxBuf_RDY; (7) Uart1TxTimer.TimerCtn = 5*T100ms; (8) ReportTx.bStBuf = SmsTx_Dly1; (9) UartTxDatType = ReportType; (10) Uart1TxIntEn(); (11) } (12) } (13) slse if(ReportTx.bStBuf == SmsTx_DatRdy){ (14) if(Uart1Tx.bStBuf == Uart1_TxBuf_Empty){ (15) sprintf(Uart1Tx.Buf, ReportTx.dat_buf); (16) Uart1Tx.Len = ReportTx.dat_len; (17) Uart1Tx.Index = 0; (18) Uart1Tx.bStBuf = Uart1_TxBuf_RDY; (19) Uart1TxTimer.TimerCtn = 7*T1s; (20) ReportTx.bStBuf = SmsTx_Dly2; (21) Uart1TxIntEn(); (22) } (23) } (24) slse if(ReportTx.bStBuf == SmsTx_Wait){ (25) if(ReportTx.retry_time-- == 0) (26) ReportTx.bStBuf = SmsTx_Empty; (27) else (28) ReportTx.bStBuf = SmsTx_CmdRdy; (29) } (30) …… } void Clear_Uart1TxbStBuf(void){ (1) …… (2) if(bReportPrevSt == SmsTx_Dly1) (4) ReportTx.bStBuf = SmsTx_DatRdy; (5) else if(bReportPrevSt == SmsTx_Dly2) (6) ReportTx.bStBuf = SmsTx_Wait; (7) …… } void Uart1_Rx(unsigned char rx_dat){ (1) Uart1Rx.Buf[Uart1Rx.Index] = rx_dat; (2) …… (3) if(strcomp(&Uart1Rx.Buf,"+CMGS") == 0){ (4) if(UartTxDatType == ReportType){ (5) ReportTx.bStBuf = SmsTx_Empty; (6) UartTxDatType = NoType; (7) Uart1TxTimer.bTimerSt = Timer_STOP; (8) } (9) …… } (10) else if(strcomp(&Uart1Rx.Buf,"ERROR") == 0){ (11) if(UartTxDatType == ReportType){ (12) if(ReportTx.retry_time != 0){ (13) ReportTx.retry_time--; (14) ReportTx.bStBuf = SmsTx_CmdRdy; (15) } (16) UartTxDatType = NoType; (17) Uart1TxTimer.bTimerSt = Timer_STOP; (18) } (19) …… …… } 短消息发送缓冲区在SmsTx_Empty状态时,软件向短消息发送缓冲区中写入数据,短消息发送缓冲区的状态转移到SmsTx_CmdRdy。 当软件检查到短信数据缓冲区的状态在SmsTx_CmdRdy状态时,如Uart1TxDatDeal的(2)处,说明缓冲区的数据已经准备好了,可以向串口1发送缓冲区中转移。这时如果串口1发送缓冲区为空如Uart1TxDatDeal的(3)处,则将短信数据缓冲区的cmd_buf中的有效数据写入到串口1发送缓冲区中,设置串口1定时器等待时间为500毫秒,状态转移到SmsTx_Dly1;否则,状态维持不变。 当串口1软件定时器超时时,将运行Clear_Uart1TxbStBuf函数,函数将检测短消息发送缓冲区的状态,如果状态为SmsTx_Dly1,则将状态转移到SmsTx_DatRdy,如函数Clear_Uart1TxbStBuf的(4)~(5)处。否则状态将维持。 在SmsTx_DatRdy状态下,其操作过程与SmsTx_CmdRdy状态基本一致,只是等待的时间为7秒。如果状态可以转移,则转移到SmsTx_Dly2。 在SmsTx_Dly2状态,如果串口1软件定时器超时,其状态的转移于SmsTx_Dly1状态下一致,如Clear_Uart1TxbStBuf函数的(6)~(7)处;如果在这个状态收到短消息发成功的信息,则状态将转移到SmsTx_Empty,停止串口1软件定时器,如函数Uart1_Rx的(4)~(9)处;如果发送短信失败,则状态转移到SmsTx_CmdRdy状态,重传计数器减1,停止串口1软件定时器,如函数(11)~(19)处。 在SmsTx_Wait状态,当重传计数器为0时,状态转移到SmsTx_Emty。如果在重传计数器不为0时,则状态转移到SmsTx_CmdRdy状态。 4.结束语 在整个移动2G光纤直放站近端机的监控软件中,除了短消息收发处理、还包括实时采样、实时告警上报等任务,其所有的软件设计都采用类似于短信收发处理的状态机、队列和软件定时器的设计思路。极大地提高移动2G光纤直放站近端机监控软件的效率。 这种在前后台系统中使用状态机、队列和软件定时器的设计思路,可以应用到其它的嵌入式前后台系统,是一种值得学习、借鉴的日嵌入式软件设计思路。 参考文献 1.麻志毅. C语言解析教程(第4版). 机械工业出版社. 2.WAVECOM. AT command interface guide. 3.John D.Carpinelli. 计算机系统组成与体系结构. 人民邮电出版社. 4.邵贝贝. 嵌入式实时操作系统uC/OS-II. 北京航空航天大学
__________________
让世界倾听我们的笛声 |