似乎软件架构,只有纯上位机软件才有,其实,嵌入式软件也有架构可言,只有好的架构,才能结构清晰,方便开发和让系统稳定的工作。在有嵌入式操作系统的情况下,可以利用多任务和信号量,事件等设计嵌入式软件。但是在没有操作系统的裸机中,更需要有好的架构。例如利用事件和状态机模拟实现多任务,或者利用定时器和消息队列,信号量等模拟实现多任务,有了多任务就能灵活的设计软件架构。
一种简单的信号量实现:
void sem_init( volatile U08 *Sem ) { (*Sem)=0; } void sem_post( volatile U08 *Sem ) { if( 0 == (*Sem) ) (*Sem)++; } U08 sem_wait( volatile U08 *Sem ) { if(0 == *Sem) return 1; (*Sem)--; return 0; }
在一个大的while(1)大循环中,利用信号量实现各个函数(任务)的同步。
void Task_SysTime( void ) { static int TaskInitFlg = 0; U32 Timer1sCount = 0; //时钟计数器个数 U32 disstat = 0; static int tmrid0 = 0, tmrid1 = 0, tmrid2 = 0, tmrid3 = 0; if( 0 == TaskInitFlg ) { OSTimeDlyHMSM( 0, 0, 0, 50 //主要等待任务删除后才创建卡任务 tmrid0 = TimerSet(20); //定时器0(毫秒定时器)用于键盘、寻卡、定时器中断服务程序,20ms tmrid1 = TimerSet(1000);//定时器1(毫秒定时器)用于背显、GPS、定时连接检测、空闲显示 tmrid2 = TimerSet(500); //定时器2(毫秒定时器)用于信号显示,500ms tmrid3 = TimerSet(500); //定时器3(毫秒定时器)用于电池显示,500ms sem_init( &gSem_EVT_CARDFLG_OK ); //初始化为没有卡 APP_DisIdle( 2 ); //显示一次时间 APP_DisVoice(); TaskInitFlg = 1; //任务初始化完成 } else { HW_IWDG_ReloadCounter(); //清看门狗 if( 0 == TimerCheck(tmrid0) ) { tmrid0 = TimerSet(20); //定时器0重新定时, 20ms Timer_ScanKeyboard(); //20MS键盘扫描 Timer_FindCard(); //20MS寻卡处理 TIM20MS_IRQHandler(); //20MS定时器中断服务程序 } } } void Task_Tick( void ) { Task_SysError(); Task_CardProc(); Task_SysTime(); Task_MenuProc(); Task_MtnLink(); Task_CommProc(); } int main( void ) { Sys_Init(); //系统初始化 while( 1 ) { Task_Tick(); //任务轮询 if( 0 == sem_wait( &gSem_EVT_QUIT_APP ) ) break; //应用退出 } }
以上为借助信号量和定时器实现的一种简单的模拟多任务,其实也算不上是多任务,因为如果一个函数执行时间很长,如何打断它?
以下为借住定时器和任务队列实现的一种模拟多任务:
#include <stdio.h> #include "timTask.h" #include "disp.h" /*===================================================== = 变量定义 =====================================================*/ //任务队列 typedef struct{ char flagState; //运行方式 0: 无任务 // 1: 运行 char flagRun; //完成状态 0: 正在计数 // 1: 计数完成 char flagType; //处理方式 0: 主任务处理 // 1: 中断处理 ulong cntRun; //运行计数器 ulong numCircle; //循环计数器 void (*pTaskFunc)(void); //任务 }TypeTimTask; TypeTimTask timTaskTab[TIM_TASK_NUMBER]; /************************************************************************* * 函数原型: * 功能描述: * 入口参数: * 出口参数: * 返 回 值: *************************************************************************/ void TimTaskInit(void) { int i; for (i=0; i<TIM_TASK_NUMBER; i++) { timTaskTab[i].pTaskFunc = 0; timTaskTab[i].cntRun = 0; timTaskTab[i].numCircle = 0; timTaskTab[i].flagRun = 0; timTaskTab[i].flagState = 0; } SPT_register_call_back(TimTaskUpdate); SPT_set(TIM_TASK_PERIOD *64 / 1000); } /************************************************************************* * 函数原型: * 功能描述: * 入口参数: * 出口参数: * 返 回 值: *************************************************************************/ short TimTaskAdd(ulong fsttim, ulong cirtim, void (*pTaskFunc)(void), uchar type) { int i; int pos = -1; //查找位置 for (i=0; i<TIM_TASK_NUMBER; i++) { if (timTaskTab[i].pTaskFunc == pTaskFunc) { pos = i; break; } if ((pos == -1) && (timTaskTab[i].flagState == 0)) { pos = i; } } //任务已满 if (pos == -1) { return -1; } // timTaskTab[pos].pTaskFunc = pTaskFunc; timTaskTab[pos].cntRun = fsttim / TIM_TASK_PERIOD; timTaskTab[pos].numCircle = cirtim / TIM_TASK_PERIOD; timTaskTab[pos].flagRun = 0; timTaskTab[pos].flagType = type; timTaskTab[pos].flagState = 1; return 0; } /************************************************************************* * 函数原型: * 功能描述: * 入口参数: * 出口参数: * 返 回 值: *************************************************************************/ void TimTaskDel(void (*pTaskFunc)(void)) { int i; for (i=0; i<TIM_TASK_NUMBER; i++) { if (timTaskTab[i].pTaskFunc == pTaskFunc) { timTaskTab[i].flagState = 0; timTaskTab[i].flagRun = 0; return; } } } /************************************************************************* * 函数原型: * 功能描述: * 入口参数: * 出口参数: * 返 回 值: *************************************************************************/ void TimTaskUpdate(void) { int i; SPT_set(TIM_TASK_PERIOD *64 / 1000); for (i=0; i<TIM_TASK_NUMBER; i++) { if (timTaskTab[i].flagState != 0) { if (timTaskTab[i].cntRun != 0) { timTaskTab[i].cntRun--; } else { //判断处理位置 if (timTaskTab[i].flagType != 0) (*timTaskTab[i].pTaskFunc)(); else timTaskTab[i].flagRun = 1; //判断重载 if (timTaskTab[i].numCircle) timTaskTab[i].cntRun = timTaskTab[i].numCircle; else timTaskTab[i].flagState = 0; } } } } /************************************************************************* * 函数原型: * 功能描述: * 入口参数: * 出口参数: * 返 回 值: *************************************************************************/ void TimTaskProc(void) { int i; for (i=0; i<TIM_TASK_NUMBER; i++) { if (timTaskTab[i].flagRun != 0) { timTaskTab[i].flagRun = 0; (*timTaskTab[i].pTaskFunc)(); } } }
更为巧妙的是,可以借住函数指针实现一种灵活的菜单和按键实时处理结构。类似于windows下win32的消息驱动机制,
通过中断等方式把实时事件封装成消息。以下为定义界面刷新显示和响应按键处理的结构:
#ifndef __PAGE_H_ #define __PAGE_H_ #include "heads.h" /*===================================================== = =====================================================*/ typedef struct{ void (* OnPaint)(void); void (* OnKey)(short); }TypePage; /*===================================================== = =====================================================*/ void WndPageSet(const TypePage *pg, int type = 0); TypePage * WndGetPage(void); void WndPageEsc(void); void WndOnKey(short key); void WndOnPaint(void); void WndMenuInit(const char *pmn, char mline); void WndMenuSelet(int m); char WndMenuGetSelet(void); long WndGetPaseword(int x, int y, char *psw, int len, long qevent);
#include "pageWnd.h" /*===================================================== = =====================================================*/ char flagPaint = 0; void (* pOnPaint)(void) = 0; void (* pOnKey)(short) = 0; const char *pMenuStr; uchar menuSelect = 0; uchar menuLine = 0; uchar menuTop; TypePage *pageCurrent; TypePage *pageTreeTab[10]; uchar pageIndex = 0; /*===================================================== = =====================================================*/ void WndDrawMenu(void); /************************************************************************* * 函数原型: * 功能描述: * 入口参数: * 出口参数: * 返 回 值: *************************************************************************/ void WndPageSet(const TypePage *pg, int type) { if (pg == &pageMain) //防止出错 { pageIndex = 0; } else if (type == 0) { pageTreeTab[pageIndex++] = pageCurrent; } pageCurrent = (TypePage *)pg; pOnPaint = pg->OnPaint; pOnKey = pg->OnKey; flagPaint = 1; } /************************************************************************* * 函数原型: * 功能描述: * 入口参数: * 出口参数: * 返 回 值: *************************************************************************/ TypePage * WndGetPage(void) { return pageCurrent; } /************************************************************************* * 函数原型: * 功能描述: * 入口参数: * 出口参数: * 返 回 值: *************************************************************************/ void WndPageEsc(void) { TypePage *pg; if (pageIndex != 0) { pageIndex--; pg = pageTreeTab[pageIndex]; } else { pg = (TypePage *)&pageMain; } pageCurrent = pg; pOnPaint = pg->OnPaint; pOnKey = pg->OnKey; flagPaint = 1; } /************************************************************************* * 函数原型: * 功能描述: * 入口参数: * 出口参数: * 返 回 值: *************************************************************************/ void WndOnPaint(void) { if (flagPaint != 0) { flagPaint = 0; (*pOnPaint)(); } } /************************************************************************* * 函数原型: * 功能描述: * 入口参数: * 出口参数: * 返 回 值: *************************************************************************/ void WndOnKey(short key) { if (pOnKey != 0) { (*pOnKey)(key); } } /************************************************************************* * 函数原型: * 功能描述: * 入口参数: * 出口参数: * 返 回 值: *************************************************************************/ void WndMenuInit(const char *pmn, char mline) { menuSelect = 0; pMenuStr = pmn; menuLine = mline; menuTop = 0; WndDrawMenu(); } /************************************************************************* * 函数原型: * 功能描述: * 入口参数: * 出口参数: * 返 回 值: *************************************************************************/ void WndMenuSelet(int m) { //光标滑动 if (m > 0) //下移 { menuSelect++; if (menuSelect == menuLine) menuSelect = 0; if (menuSelect > menuTop + 4) { if (menuLine < menuTop + 4) menuTop = menuLine - 4; else menuTop = menuSelect - 4; } } else if (m < 0) //上移 { if (menuSelect == 0) menuSelect = menuLine - 1; else menuSelect--; } //图框移动 if (menuSelect < menuTop) //上移 { menuTop = menuSelect; } else if (menuSelect >= menuTop + 4) //下移 { menuTop = menuSelect - 3; } WndDrawMenu(); } /************************************************************************* * 函数原型: * 功能描述: * 入口参数: * 出口参数: * 返 回 值: *************************************************************************/ char WndMenuGetSelet(void) { return menuSelect + 1; } /************************************************************************* * 函数原型: * 功能描述: * 入口参数: * 出口参数: * 返 回 值: *************************************************************************/ void WndDrawMenu(void) { int i; char buf[17]; const char *pmn = pMenuStr + menuTop * 16; DispClr(); for (i=0; i<4; i++) { if (menuTop + i == menuLine) break; memcpy(buf, pmn, 16); buf[16] = '\0'; if (menuSelect == menuTop + i) DispSetStyle(DISP_POSITION | DISP_REVERSE | DISP_7x9); else DispSetStyle(DISP_POSITION | DISP_NORMAL | DISP_7x9); DispString(0, i * 2, buf); pmn += 16; } } /************************************************************************* * 函数原型: * 功能描述: * 入口参数: * 出口参数: * 返 回 值: *************************************************************************/ long WndGetPaseword(int x, int y, char *psw, int len, long qevent) { int pin = 0; long keyevt; char key; char buf[20]; memset(buf, '_', len); buf[len] = '\0'; PSW_INPUT_LOOP: DispString(x, y, buf); keyevt = delay_and_wait_key(0, EXIT_KEY_ALL, 0); if (keyevt == qevent) { psw[0] = '\0'; return keyevt; } switch (keyevt) { case EXIT_KEY_0: key = '0'; break; case EXIT_KEY_1: key = '1'; break; case EXIT_KEY_2: key = '2'; break; case EXIT_KEY_3: key = '3'; break; case EXIT_KEY_4: key = '4'; break; case EXIT_KEY_5: key = '5'; break; case EXIT_KEY_6: key = '6'; break; case EXIT_KEY_7: key = '7'; break; case EXIT_KEY_8: key = '8'; break; case EXIT_KEY_9: key = '9'; break; case EXIT_KEY_COMM: if (pin != 0) { buf[--pin] = '_'; } goto PSW_INPUT_LOOP; break; case EXIT_KEY_ENTER: psw[pin] = 0; return 0; default: goto PSW_INPUT_LOOP; } if (pin != len) { psw[pin] = key; buf[pin] = '*'; pin++; } goto PSW_INPUT_LOOP; }
在软件设计时,如果添加界面和对应的按键处理,很灵活,只需要新添加一个文件就可以了,文件的内容,只需要实现OnPain和对应的OnKey
#include "PageMenu.h" /*===================================================== = =====================================================*/ const char mainMenuTab[] = /* 1234567890123456*/"\ 1. 现场采集 \ 2. 数据上传 \ 3. 存储状态查询 \ 4. 时间设置 \ 5. 对比度设置 \ 6. 恢复出厂设置 \ 7. 关于 "; /*===================================================== = =====================================================*/ void PageMenuOnPain(void); void WndMenuOnKey(short key); const TypePage pageMenu = {PageMenuOnPain, WndMenuOnKey}; /************************************************************************* * 函数原型: * 功能描述: * 入口参数: * 出口参数: * 返 回 值: *************************************************************************/ void PageMenuOnPain(void) { WndMenuInit(mainMenuTab, 7); } /************************************************************************* * 函数原型: * 功能描述: * 入口参数: * 出口参数: * 返 回 值: *************************************************************************/ void WndMenuOnKey(short key) { int res; switch (key) { case KEY_F1: case KEY_ENTER: res = WndMenuGetSelet(); switch (res) { case 1: WndPageSet(&pageSimp); break; case 2: WndPageSet(&pagePclink); break; case 3: WndPageSet(&pageInquire); break; case 4: WndPageSet(&pageRtc); break; case 5: WndPageSet(&pageGray); break; case 6: SPageInit(); WndPageSet(&pageMenu, 1); break; case 7: WndPageSet(&pageAbout); break; } break; case KEY_F2: case KEY_F3: WndPageSet(&pageMain); break; case KEY_1: WndPageSet(&pageSimp); break; case KEY_2: WndPageSet(&pagePclink); break; case KEY_3: WndPageSet(&pageInquire); break; case KEY_4: WndPageSet(&pageRtc); break; case KEY_5: WndPageSet(&pageGray); break; case KEY_6: SPageInit(); WndPageSet(&pageMenu, 1); break; case KEY_7: WndPageSet(&pageAbout); break; case KEY_UP: WndMenuSelet(-1); break; case KEY_DOWN: WndMenuSelet(1); break; case KEY_POWER: WndPageSet(&pagePower); break; } }
pageMain,pageAbout,pageRtc,pagePclink等文件,他们的结构很类似。都是实现了OnPaint和OnKey函数。
如:pagePclink.c文件内容:
实现了PagePclinkOnPaint和PagePclinOnKey函数.
CommPclink函数是自己想要实现的功能,可以自己定义。
#include "pagePclink.h" /*===================================================== = =====================================================*/ void PagePclinkOnPaint(void); void PagePclinOnKey(short key); const TypePage pagePclink = {PagePclinkOnPaint, PagePclinOnKey}; /************************************************************************* * 函数原型: * 功能描述: * 入口参数: * 出口参数: * 返 回 值: *************************************************************************/ void PagePclinkOnPaint(void) { DispClr(); DispSetStyle(DISP_CENTER | DISP_REVERSE | DISP_7x9); DispString(0, 0, " 数据上传 "); DispSetStyle(DISP_POSITION|DISP_NORMAL|DISP_7x9); DispString(0, 6, "[连接] [返回]"); } /************************************************************************* * 函数原型: * 功能描述: * 入口参数: * 出口参数: * 返 回 值: *************************************************************************/ void PagePclinOnKey(short key) { switch (key) { case KEY_F1: CommPclink(); break; case KEY_F3: WndPageEsc(); break; } }
#ifndef __PAGE_POWER_H_ #define __PAGE_POWER_H_ #include "pageWnd.h" /*===================================================== = =====================================================*/ extern const TypePage pagePower; #endif #include "PagePower.h" #include "disp.h" /*===================================================== = =====================================================*/ void PagePowerOnPaint(void); void PagePowerOnKey(short key); const TypePage pagePower = {PagePowerOnPaint, PagePowerOnKey}; /************************************************************************* * 函数原型: * 功能描述: * 入口参数: * 出口参数: * 返 回 值: *************************************************************************/ void PagePowerOnPaint(void) { DispClr(); DispSetStyle(DISP_CENTER | DISP_REVERSE | DISP_7x9); DispString(0, 0, " 电源管理 "); DispSetStyle(DISP_POSITION|DISP_NORMAL|DISP_7x9); DispString(0, 2, " [Enter] 关机 "); DispString(0, 4, " [F3 ] 返回 "); } /************************************************************************* * 函数原型: * 功能描述: * 入口参数: * 出口参数: * 返 回 值: *************************************************************************/ void PagePowerOnKey(short key) { switch (key) { case KEY_ENTER: case KEY_POWER: Halt_EH0218(4); SysInit(); break; case KEY_F3: WndPageEsc(); break; } }
这样的一种结构,很灵活,在主函数中只需要这样调用:
int main(void) { short key; typ_msg_word smw; SysInit(); for ( ; ; ) { /* 界面刷新 */ WndOnPaint(); /* 消息处理 */ smw.s_word = sys_msg(SM_STAY_AWAKE); //用SM_GOTO_SLEEP串口就不能用 //按键处理 if (smw.bits.key_available) { LcdOffDelay(LCD_OFF_DELAY); key = KEY_read(); if (key != -1) { WndOnKey(key); } } //插入充电电源 if (smw.bits.charger_on) { LcdOffDelay(LCD_OFF_DELAY); } //断开充电电源 if (smw.bits.charger_off) { LcdOffDelay(LCD_OFF_DELAY); RefreshBattery(); } //串口 if (smw.bits.comm_data) { CommReceive(); } //实时任务 if (smw.bits.time_out) { TimTaskProc(); } } }