单片机程序构架 单片机程序构架

转自 https://blog.csdn.net/qq8864/article/details/17961375

单片机程序构架

一种简单的信号量实现:

  1. void sem_init( volatile U08 *Sem )
  2. {
  3. (*Sem)= 0;
  4. }
  5. void sem_post( volatile U08 *Sem )
  6. {
  7. if( 0 == (*Sem) )
  8. (*Sem)++;
  9. }
  10. U08 sem_wait( volatile U08 *Sem )
  11. {
  12. if( 0 == *Sem)
  13. return 1;
  14. (*Sem)--;
  15. return 0;
  16. }

在一个大的while(1)大循环中,利用信号量实现各个函数(任务)的同步。

  1. void Task_SysTime( void )
  2. {
  3. static int TaskInitFlg = 0;
  4. U32 Timer1sCount = 0; //时钟计数器个数
  5. U32 disstat = 0;
  6. static int tmrid0 = 0, tmrid1 = 0, tmrid2 = 0, tmrid3 = 0;
  7. if( 0 == TaskInitFlg )
  8. {
  9. OSTimeDlyHMSM( 0, 0, 0, 50 //主要等待任务删除后才创建卡任务
  10. tmrid0 = TimerSet( 20); //定时器0(毫秒定时器)用于键盘、寻卡、定时器中断服务程序,20ms
  11. tmrid1 = TimerSet( 1000); //定时器1(毫秒定时器)用于背显、GPS、定时连接检测、空闲显示
  12. tmrid2 = TimerSet( 500); //定时器2(毫秒定时器)用于信号显示,500ms
  13. tmrid3 = TimerSet( 500); //定时器3(毫秒定时器)用于电池显示,500ms
  14. sem_init( &gSem_EVT_CARDFLG_OK ); //初始化为没有卡
  15. APP_DisIdle( 2 ); //显示一次时间
  16. APP_DisVoice();
  17. TaskInitFlg = 1; //任务初始化完成
  18. }
  19. else
  20. {
  21. HW_IWDG_ReloadCounter(); //清看门狗
  22. if( 0 == TimerCheck(tmrid0) )
  23. {
  24. tmrid0 = TimerSet( 20); //定时器0重新定时, 20ms
  25. Timer_ScanKeyboard(); //20MS键盘扫描
  26. Timer_FindCard(); //20MS寻卡处理
  27. TIM20MS_IRQHandler(); //20MS定时器中断服务程序
  28. }
  29. }
  30. }
  31. void Task_Tick( void )
  32. {
  33. Task_SysError();
  34. Task_CardProc();
  35. Task_SysTime();
  36. Task_MenuProc();
  37. Task_MtnLink();
  38. Task_CommProc();
  39. }
  40. int main( void )
  41. {
  42. Sys_Init(); //系统初始化
  43. while( 1 )
  44. {
  45. Task_Tick(); //任务轮询
  46. if( 0 == sem_wait( &gSem_EVT_QUIT_APP ) )
  47. break; //应用退出
  48. }
  49. }

以上为借助信号量和定时器实现的一种简单的模拟多任务,其实也算不上是多任务,因为如果一个函数执行时间很长,如何打断它?

以下为借住定时器和任务队列实现的一种模拟多任务:

  1. #include <stdio.h>
  2. #include "timTask.h"
  3. #include "disp.h"
  4. /*=====================================================
  5. = 变量定义
  6. =====================================================*/
  7. //任务队列
  8. typedef struct{
  9. char flagState; //运行方式 0: 无任务
  10. // 1: 运行
  11. char flagRun; //完成状态 0: 正在计数
  12. // 1: 计数完成
  13. char flagType; //处理方式 0: 主任务处理
  14. // 1: 中断处理
  15. ulong cntRun; //运行计数器
  16. ulong numCircle; //循环计数器
  17. void (*pTaskFunc)( void); //任务
  18. }TypeTimTask;
  19. TypeTimTask timTaskTab[TIM_TASK_NUMBER];
  20. /*************************************************************************
  21. * 函数原型:
  22. * 功能描述:
  23. * 入口参数:
  24. * 出口参数:
  25. * 返 回 值:
  26. *************************************************************************/
  27. void TimTaskInit(void)
  28. {
  29. int i;
  30. for (i= 0; i<TIM_TASK_NUMBER; i++)
  31. {
  32. timTaskTab[i].pTaskFunc = 0;
  33. timTaskTab[i].cntRun = 0;
  34. timTaskTab[i].numCircle = 0;
  35. timTaskTab[i].flagRun = 0;
  36. timTaskTab[i].flagState = 0;
  37. }
  38. SPT_register_call_back(TimTaskUpdate);
  39. SPT_set(TIM_TASK_PERIOD * 64 / 1000);
  40. }
  41. /*************************************************************************
  42. * 函数原型:
  43. * 功能描述:
  44. * 入口参数:
  45. * 出口参数:
  46. * 返 回 值:
  47. *************************************************************************/
  48. short TimTaskAdd(ulong fsttim, ulong cirtim, void (*pTaskFunc)(void), uchar type)
  49. {
  50. int i;
  51. int pos = -1;
  52. //查找位置
  53. for (i= 0; i<TIM_TASK_NUMBER; i++)
  54. {
  55. if (timTaskTab[i].pTaskFunc == pTaskFunc)
  56. {
  57. pos = i;
  58. break;
  59. }
  60. if ((pos == -1) && (timTaskTab[i].flagState == 0))
  61. {
  62. pos = i;
  63. }
  64. }
  65. //任务已满
  66. if (pos == -1)
  67. {
  68. return -1;
  69. }
  70. //
  71. timTaskTab[pos].pTaskFunc = pTaskFunc;
  72. timTaskTab[pos].cntRun = fsttim / TIM_TASK_PERIOD;
  73. timTaskTab[pos].numCircle = cirtim / TIM_TASK_PERIOD;
  74. timTaskTab[pos].flagRun = 0;
  75. timTaskTab[pos].flagType = type;
  76. timTaskTab[pos].flagState = 1;
  77. return 0;
  78. }
  79. /*************************************************************************
  80. * 函数原型:
  81. * 功能描述:
  82. * 入口参数:
  83. * 出口参数:
  84. * 返 回 值:
  85. *************************************************************************/
  86. void TimTaskDel(void (*pTaskFunc)(void))
  87. {
  88. int i;
  89. for (i= 0; i<TIM_TASK_NUMBER; i++)
  90. {
  91. if (timTaskTab[i].pTaskFunc == pTaskFunc)
  92. {
  93. timTaskTab[i].flagState = 0;
  94. timTaskTab[i].flagRun = 0;
  95. return;
  96. }
  97. }
  98. }
  99. /*************************************************************************
  100. * 函数原型:
  101. * 功能描述:
  102. * 入口参数:
  103. * 出口参数:
  104. * 返 回 值:
  105. *************************************************************************/
  106. void TimTaskUpdate(void)
  107. {
  108. int i;
  109. SPT_set(TIM_TASK_PERIOD * 64 / 1000);
  110. for (i= 0; i<TIM_TASK_NUMBER; i++)
  111. {
  112. if (timTaskTab[i].flagState != 0)
  113. {
  114. if (timTaskTab[i].cntRun != 0)
  115. {
  116. timTaskTab[i].cntRun--;
  117. }
  118. else
  119. {
  120. //判断处理位置
  121. if (timTaskTab[i].flagType != 0)
  122. (*timTaskTab[i].pTaskFunc)();
  123. else
  124. timTaskTab[i].flagRun = 1;
  125. //判断重载
  126. if (timTaskTab[i].numCircle)
  127. timTaskTab[i].cntRun = timTaskTab[i].numCircle;
  128. else
  129. timTaskTab[i].flagState = 0;
  130. }
  131. }
  132. }
  133. }
  134. /*************************************************************************
  135. * 函数原型:
  136. * 功能描述:
  137. * 入口参数:
  138. * 出口参数:
  139. * 返 回 值:
  140. *************************************************************************/
  141. void TimTaskProc(void)
  142. {
  143. int i;
  144. for (i= 0; i<TIM_TASK_NUMBER; i++)
  145. {
  146. if (timTaskTab[i].flagRun != 0)
  147. {
  148. timTaskTab[i].flagRun = 0;
  149. (*timTaskTab[i].pTaskFunc)();
  150. }
  151. }
  152. }
更为巧妙的是,可以借住函数指针实现一种灵活的菜单和按键实时处理结构。类似于windows下win32的消息驱动机制,
通过中断等方式把实时事件封装成消息。以下为定义界面刷新显示和响应按键处理的结构:

  1. #ifndef __PAGE_H_
  2. #define __PAGE_H_
  3. #include "heads.h"
  4. /*=====================================================
  5. =
  6. =====================================================*/
  7. typedef struct{
  8. void (* OnPaint)( void);
  9. void (* OnKey)( short);
  10. }TypePage;
  11. /*=====================================================
  12. =
  13. =====================================================*/
  14. void WndPageSet(const TypePage *pg, int type = 0);
  15. TypePage * WndGetPage(void);
  16. void WndPageEsc(void);
  17. void WndOnKey(short key);
  18. void WndOnPaint(void);
  19. void WndMenuInit(const char *pmn, char mline);
  20. void WndMenuSelet(int m);
  21. char WndMenuGetSelet(void);
  22. long WndGetPaseword(int x, int y, char *psw, int len, long qevent);

  1. #include "pageWnd.h"
  2. /*=====================================================
  3. =
  4. =====================================================*/
  5. char flagPaint = 0;
  6. void (* pOnPaint)( void) = 0;
  7. void (* pOnKey)( short) = 0;
  8. const char *pMenuStr;
  9. uchar menuSelect = 0;
  10. uchar menuLine = 0;
  11. uchar menuTop;
  12. TypePage *pageCurrent;
  13. TypePage *pageTreeTab[ 10];
  14. uchar pageIndex = 0;
  15. /*=====================================================
  16. =
  17. =====================================================*/
  18. void WndDrawMenu(void);
  19. /*************************************************************************
  20. * 函数原型:
  21. * 功能描述:
  22. * 入口参数:
  23. * 出口参数:
  24. * 返 回 值:
  25. *************************************************************************/
  26. void WndPageSet(const TypePage *pg, int type)
  27. {
  28. if (pg == &pageMain) //防止出错
  29. {
  30. pageIndex = 0;
  31. }
  32. else if (type == 0)
  33. {
  34. pageTreeTab[pageIndex++] = pageCurrent;
  35. }
  36. pageCurrent = (TypePage *)pg;
  37. pOnPaint = pg->OnPaint;
  38. pOnKey = pg->OnKey;
  39. flagPaint = 1;
  40. }
  41. /*************************************************************************
  42. * 函数原型:
  43. * 功能描述:
  44. * 入口参数:
  45. * 出口参数:
  46. * 返 回 值:
  47. *************************************************************************/
  48. TypePage * WndGetPage(void)
  49. {
  50. return pageCurrent;
  51. }
  52. /*************************************************************************
  53. * 函数原型:
  54. * 功能描述:
  55. * 入口参数:
  56. * 出口参数:
  57. * 返 回 值:
  58. *************************************************************************/
  59. void WndPageEsc(void)
  60. {
  61. TypePage *pg;
  62. if (pageIndex != 0)
  63. {
  64. pageIndex--;
  65. pg = pageTreeTab[pageIndex];
  66. }
  67. else
  68. {
  69. pg = (TypePage *)&pageMain;
  70. }
  71. pageCurrent = pg;
  72. pOnPaint = pg->OnPaint;
  73. pOnKey = pg->OnKey;
  74. flagPaint = 1;
  75. }
  76. /*************************************************************************
  77. * 函数原型:
  78. * 功能描述:
  79. * 入口参数:
  80. * 出口参数:
  81. * 返 回 值:
  82. *************************************************************************/
  83. void WndOnPaint(void)
  84. {
  85. if (flagPaint != 0)
  86. {
  87. flagPaint = 0;
  88. (*pOnPaint)();
  89. }
  90. }
  91. /*************************************************************************
  92. * 函数原型:
  93. * 功能描述:
  94. * 入口参数:
  95. * 出口参数:
  96. * 返 回 值:
  97. *************************************************************************/
  98. void WndOnKey(short key)
  99. {
  100. if (pOnKey != 0)
  101. {
  102. (*pOnKey)(key);
  103. }
  104. }
  105. /*************************************************************************
  106. * 函数原型:
  107. * 功能描述:
  108. * 入口参数:
  109. * 出口参数:
  110. * 返 回 值:
  111. *************************************************************************/
  112. void WndMenuInit(const char *pmn, char mline)
  113. {
  114. menuSelect = 0;
  115. pMenuStr = pmn;
  116. menuLine = mline;
  117. menuTop = 0;
  118. WndDrawMenu();
  119. }
  120. /*************************************************************************
  121. * 函数原型:
  122. * 功能描述:
  123. * 入口参数:
  124. * 出口参数:
  125. * 返 回 值:
  126. *************************************************************************/
  127. void WndMenuSelet(int m)
  128. {
  129. //光标滑动
  130. if (m > 0) //下移
  131. {
  132. menuSelect++;
  133. if (menuSelect == menuLine)
  134. menuSelect = 0;
  135. if (menuSelect > menuTop + 4)
  136. {
  137. if (menuLine < menuTop + 4)
  138. menuTop = menuLine - 4;
  139. else
  140. menuTop = menuSelect - 4;
  141. }
  142. }
  143. else if (m < 0) //上移
  144. {
  145. if (menuSelect == 0)
  146. menuSelect = menuLine - 1;
  147. else
  148. menuSelect--;
  149. }
  150. //图框移动
  151. if (menuSelect < menuTop) //上移
  152. {
  153. menuTop = menuSelect;
  154. }
  155. else if (menuSelect >= menuTop + 4) //下移
  156. {
  157. menuTop = menuSelect - 3;
  158. }
  159. WndDrawMenu();
  160. }
  161. /*************************************************************************
  162. * 函数原型:
  163. * 功能描述:
  164. * 入口参数:
  165. * 出口参数:
  166. * 返 回 值:
  167. *************************************************************************/
  168. char WndMenuGetSelet(void)
  169. {
  170. return menuSelect + 1;
  171. }
  172. /*************************************************************************
  173. * 函数原型:
  174. * 功能描述:
  175. * 入口参数:
  176. * 出口参数:
  177. * 返 回 值:
  178. *************************************************************************/
  179. void WndDrawMenu(void)
  180. {
  181. int i;
  182. char buf[ 17];
  183. const char *pmn = pMenuStr + menuTop * 16;
  184. DispClr();
  185. for (i= 0; i< 4; i++)
  186. {
  187. if (menuTop + i == menuLine)
  188. break;
  189. memcpy(buf, pmn, 16);
  190. buf[ 16] = '\0';
  191. if (menuSelect == menuTop + i)
  192. DispSetStyle(DISP_POSITION | DISP_REVERSE | DISP_7x9);
  193. else
  194. DispSetStyle(DISP_POSITION | DISP_NORMAL | DISP_7x9);
  195. DispString( 0, i * 2, buf);
  196. pmn += 16;
  197. }
  198. }
  199. /*************************************************************************
  200. * 函数原型:
  201. * 功能描述:
  202. * 入口参数:
  203. * 出口参数:
  204. * 返 回 值:
  205. *************************************************************************/
  206. long WndGetPaseword(int x, int y, char *psw, int len, long qevent)
  207. {
  208. int pin = 0;
  209. long keyevt;
  210. char key;
  211. char buf[ 20];
  212. memset(buf, '_', len);
  213. buf[len] = '\0';
  214. PSW_INPUT_LOOP:
  215. DispString(x, y, buf);
  216. keyevt = delay_and_wait_key( 0, EXIT_KEY_ALL, 0);
  217. if (keyevt == qevent)
  218. {
  219. psw[ 0] = '\0';
  220. return keyevt;
  221. }
  222. switch (keyevt)
  223. {
  224. case EXIT_KEY_0:
  225. key = '0';
  226. break;
  227. case EXIT_KEY_1:
  228. key = '1';
  229. break;
  230. case EXIT_KEY_2:
  231. key = '2';
  232. break;
  233. case EXIT_KEY_3:
  234. key = '3';
  235. break;
  236. case EXIT_KEY_4:
  237. key = '4';
  238. break;
  239. case EXIT_KEY_5:
  240. key = '5';
  241. break;
  242. case EXIT_KEY_6:
  243. key = '6';
  244. break;
  245. case EXIT_KEY_7:
  246. key = '7';
  247. break;
  248. case EXIT_KEY_8:
  249. key = '8';
  250. break;
  251. case EXIT_KEY_9:
  252. key = '9';
  253. break;
  254. case EXIT_KEY_COMM:
  255. if (pin != 0)
  256. {
  257. buf[--pin] = '_';
  258. }
  259. goto PSW_INPUT_LOOP;
  260. break;
  261. case EXIT_KEY_ENTER:
  262. psw[pin] = 0;
  263. return 0;
  264. default:
  265. goto PSW_INPUT_LOOP;
  266. }
  267. if (pin != len)
  268. {
  269. psw[pin] = key;
  270. buf[pin] = '*';
  271. pin++;
  272. }
  273. goto PSW_INPUT_LOOP;
  274. }

在软件设计时,如果添加界面和对应的按键处理,很灵活,只需要新添加一个文件就可以了,文件的内容,只需要实现OnPain和对应的OnKey

  1. #include "PageMenu.h"
  2. /*=====================================================
  3. =
  4. =====================================================*/
  5. const char mainMenuTab[] = /*
  6. 1234567890123456*/ "\
  7. 1. 现场采集 \
  8. 2. 数据上传 \
  9. 3. 存储状态查询 \
  10. 4. 时间设置 \
  11. 5. 对比度设置 \
  12. 6. 恢复出厂设置 \
  13. 7. 关于 ";
  14. /*=====================================================
  15. =
  16. =====================================================*/
  17. void PageMenuOnPain(void);
  18. void WndMenuOnKey(short key);
  19. const TypePage pageMenu = {PageMenuOnPain, WndMenuOnKey};
  20. /*************************************************************************
  21. * 函数原型:
  22. * 功能描述:
  23. * 入口参数:
  24. * 出口参数:
  25. * 返 回 值:
  26. *************************************************************************/
  27. void PageMenuOnPain(void)
  28. {
  29. WndMenuInit(mainMenuTab, 7);
  30. }
  31. /*************************************************************************
  32. * 函数原型:
  33. * 功能描述:
  34. * 入口参数:
  35. * 出口参数:
  36. * 返 回 值:
  37. *************************************************************************/
  38. void WndMenuOnKey(short key)
  39. {
  40. int res;
  41. switch (key)
  42. {
  43. case KEY_F1:
  44. case KEY_ENTER:
  45. res = WndMenuGetSelet();
  46. switch (res)
  47. {
  48. case 1:
  49. WndPageSet(&pageSimp);
  50. break;
  51. case 2:
  52. WndPageSet(&pagePclink);
  53. break;
  54. case 3:
  55. WndPageSet(&pageInquire);
  56. break;
  57. case 4:
  58. WndPageSet(&pageRtc);
  59. break;
  60. case 5:
  61. WndPageSet(&pageGray);
  62. break;
  63. case 6:
  64. SPageInit();
  65. WndPageSet(&pageMenu, 1);
  66. break;
  67. case 7:
  68. WndPageSet(&pageAbout);
  69. break;
  70. }
  71. break;
  72. case KEY_F2:
  73. case KEY_F3:
  74. WndPageSet(&pageMain);
  75. break;
  76. case KEY_1:
  77. WndPageSet(&pageSimp);
  78. break;
  79. case KEY_2:
  80. WndPageSet(&pagePclink);
  81. break;
  82. case KEY_3:
  83. WndPageSet(&pageInquire);
  84. break;
  85. case KEY_4:
  86. WndPageSet(&pageRtc);
  87. break;
  88. case KEY_5:
  89. WndPageSet(&pageGray);
  90. break;
  91. case KEY_6:
  92. SPageInit();
  93. WndPageSet(&pageMenu, 1);
  94. break;
  95. case KEY_7:
  96. WndPageSet(&pageAbout);
  97. break;
  98. case KEY_UP:
  99. WndMenuSelet( -1);
  100. break;
  101. case KEY_DOWN:
  102. WndMenuSelet( 1);
  103. break;
  104. case KEY_POWER:
  105. WndPageSet(&pagePower);
  106. break;
  107. }
  108. }

pageMain,pageAbout,pageRtc,pagePclink等文件,他们的结构很类似。都是实现了OnPaint和OnKey函数。
如:pagePclink.c文件内容:
实现了PagePclinkOnPaint和PagePclinOnKey函数.

CommPclink函数是自己想要实现的功能,可以自己定义。

  1. #include "pagePclink.h"
  2. /*=====================================================
  3. =
  4. =====================================================*/
  5. void PagePclinkOnPaint(void);
  6. void PagePclinOnKey(short key);
  7. const TypePage pagePclink = {PagePclinkOnPaint, PagePclinOnKey};
  8. /*************************************************************************
  9. * 函数原型:
  10. * 功能描述:
  11. * 入口参数:
  12. * 出口参数:
  13. * 返 回 值:
  14. *************************************************************************/
  15. void PagePclinkOnPaint(void)
  16. {
  17. DispClr();
  18. DispSetStyle(DISP_CENTER | DISP_REVERSE | DISP_7x9);
  19. DispString( 0, 0, " 数据上传 ");
  20. DispSetStyle(DISP_POSITION|DISP_NORMAL|DISP_7x9);
  21. DispString( 0, 6, "[连接] [返回]");
  22. }
  23. /*************************************************************************
  24. * 函数原型:
  25. * 功能描述:
  26. * 入口参数:
  27. * 出口参数:
  28. * 返 回 值:
  29. *************************************************************************/
  30. void PagePclinOnKey(short key)
  31. {
  32. switch (key)
  33. {
  34. case KEY_F1:
  35. CommPclink();
  36. break;
  37. case KEY_F3:
  38. WndPageEsc();
  39. break;
  40. }
  41. }

  1. #ifndef __PAGE_POWER_H_
  2. #define __PAGE_POWER_H_
  3. #include "pageWnd.h"
  4. /*=====================================================
  5. =
  6. =====================================================*/
  7. extern const TypePage pagePower;
  8. #endif
  9. #include "PagePower.h"
  10. #include "disp.h"
  11. /*=====================================================
  12. =
  13. =====================================================*/
  14. void PagePowerOnPaint(void);
  15. void PagePowerOnKey(short key);
  16. const TypePage pagePower = {PagePowerOnPaint, PagePowerOnKey};
  17. /*************************************************************************
  18. * 函数原型:
  19. * 功能描述:
  20. * 入口参数:
  21. * 出口参数:
  22. * 返 回 值:
  23. *************************************************************************/
  24. void PagePowerOnPaint(void)
  25. {
  26. DispClr();
  27. DispSetStyle(DISP_CENTER | DISP_REVERSE | DISP_7x9);
  28. DispString( 0, 0, " 电源管理 ");
  29. DispSetStyle(DISP_POSITION|DISP_NORMAL|DISP_7x9);
  30. DispString( 0, 2, " [Enter] 关机 ");
  31. DispString( 0, 4, " [F3 ] 返回 ");
  32. }
  33. /*************************************************************************
  34. * 函数原型:
  35. * 功能描述:
  36. * 入口参数:
  37. * 出口参数:
  38. * 返 回 值:
  39. *************************************************************************/
  40. void PagePowerOnKey(short key)
  41. {
  42. switch (key)
  43. {
  44. case KEY_ENTER:
  45. case KEY_POWER:
  46. Halt_EH0218( 4);
  47. SysInit();
  48. break;
  49. case KEY_F3:
  50. WndPageEsc();
  51. break;
  52. }
  53. }

这样的一种结构,很灵活,在主函数中只需要这样调用:

  1. int main(void)
  2. {
  3. short key;
  4. typ_msg_word smw;
  5. SysInit();
  6. for ( ; ; )
  7. {
  8. /*
  9. 界面刷新
  10. */
  11. WndOnPaint();
  12. /*
  13. 消息处理
  14. */
  15. smw.s_word = sys_msg(SM_STAY_AWAKE); //用SM_GOTO_SLEEP串口就不能用
  16. //按键处理
  17. if (smw.bits.key_available)
  18. {
  19. LcdOffDelay(LCD_OFF_DELAY);
  20. key = KEY_read();
  21. if (key != -1)
  22. {
  23. WndOnKey(key);
  24. }
  25. }
  26. //插入充电电源
  27. if (smw.bits.charger_on)
  28. {
  29. LcdOffDelay(LCD_OFF_DELAY);
  30. }
  31. //断开充电电源
  32. if (smw.bits.charger_off)
  33. {
  34. LcdOffDelay(LCD_OFF_DELAY);
  35. RefreshBattery();
  36. }
  37. //串口
  38. if (smw.bits.comm_data)
  39. {
  40. CommReceive();
  41. }
  42. //实时任务
  43. if (smw.bits.time_out)
  44. {
  45. TimTaskProc();
  46. }
  47. }
  48. }

一种简单的信号量实现:

  1. void sem_init( volatile U08 *Sem )
  2. {
  3. (*Sem)= 0;
  4. }
  5. void sem_post( volatile U08 *Sem )
  6. {
  7. if( 0 == (*Sem) )
  8. (*Sem)++;
  9. }
  10. U08 sem_wait( volatile U08 *Sem )
  11. {
  12. if( 0 == *Sem)
  13. return 1;
  14. (*Sem)--;
  15. return 0;
  16. }

在一个大的while(1)大循环中,利用信号量实现各个函数(任务)的同步。

  1. void Task_SysTime( void )
  2. {
  3. static int TaskInitFlg = 0;
  4. U32 Timer1sCount = 0; //时钟计数器个数
  5. U32 disstat = 0;
  6. static int tmrid0 = 0, tmrid1 = 0, tmrid2 = 0, tmrid3 = 0;
  7. if( 0 == TaskInitFlg )
  8. {
  9. OSTimeDlyHMSM( 0, 0, 0, 50 //主要等待任务删除后才创建卡任务
  10. tmrid0 = TimerSet( 20); //定时器0(毫秒定时器)用于键盘、寻卡、定时器中断服务程序,20ms
  11. tmrid1 = TimerSet( 1000); //定时器1(毫秒定时器)用于背显、GPS、定时连接检测、空闲显示
  12. tmrid2 = TimerSet( 500); //定时器2(毫秒定时器)用于信号显示,500ms
  13. tmrid3 = TimerSet( 500); //定时器3(毫秒定时器)用于电池显示,500ms
  14. sem_init( &gSem_EVT_CARDFLG_OK ); //初始化为没有卡
  15. APP_DisIdle( 2 ); //显示一次时间
  16. APP_DisVoice();
  17. TaskInitFlg = 1; //任务初始化完成
  18. }
  19. else
  20. {
  21. HW_IWDG_ReloadCounter(); //清看门狗
  22. if( 0 == TimerCheck(tmrid0) )
  23. {
  24. tmrid0 = TimerSet( 20); //定时器0重新定时, 20ms
  25. Timer_ScanKeyboard(); //20MS键盘扫描
  26. Timer_FindCard(); //20MS寻卡处理
  27. TIM20MS_IRQHandler(); //20MS定时器中断服务程序
  28. }
  29. }
  30. }
  31. void Task_Tick( void )
  32. {
  33. Task_SysError();
  34. Task_CardProc();
  35. Task_SysTime();
  36. Task_MenuProc();
  37. Task_MtnLink();
  38. Task_CommProc();
  39. }
  40. int main( void )
  41. {
  42. Sys_Init(); //系统初始化
  43. while( 1 )
  44. {
  45. Task_Tick(); //任务轮询
  46. if( 0 == sem_wait( &gSem_EVT_QUIT_APP ) )
  47. break; //应用退出
  48. }
  49. }

以上为借助信号量和定时器实现的一种简单的模拟多任务,其实也算不上是多任务,因为如果一个函数执行时间很长,如何打断它?

以下为借住定时器和任务队列实现的一种模拟多任务:

  1. #include <stdio.h>
  2. #include "timTask.h"
  3. #include "disp.h"
  4. /*=====================================================
  5. = 变量定义
  6. =====================================================*/
  7. //任务队列
  8. typedef struct{
  9. char flagState; //运行方式 0: 无任务
  10. // 1: 运行
  11. char flagRun; //完成状态 0: 正在计数
  12. // 1: 计数完成
  13. char flagType; //处理方式 0: 主任务处理
  14. // 1: 中断处理
  15. ulong cntRun; //运行计数器
  16. ulong numCircle; //循环计数器
  17. void (*pTaskFunc)( void); //任务
  18. }TypeTimTask;
  19. TypeTimTask timTaskTab[TIM_TASK_NUMBER];
  20. /*************************************************************************
  21. * 函数原型:
  22. * 功能描述:
  23. * 入口参数:
  24. * 出口参数:
  25. * 返 回 值:
  26. *************************************************************************/
  27. void TimTaskInit(void)
  28. {
  29. int i;
  30. for (i= 0; i<TIM_TASK_NUMBER; i++)
  31. {
  32. timTaskTab[i].pTaskFunc = 0;
  33. timTaskTab[i].cntRun = 0;
  34. timTaskTab[i].numCircle = 0;
  35. timTaskTab[i].flagRun = 0;
  36. timTaskTab[i].flagState = 0;
  37. }
  38. SPT_register_call_back(TimTaskUpdate);
  39. SPT_set(TIM_TASK_PERIOD * 64 / 1000);
  40. }
  41. /*************************************************************************
  42. * 函数原型:
  43. * 功能描述:
  44. * 入口参数:
  45. * 出口参数:
  46. * 返 回 值:
  47. *************************************************************************/
  48. short TimTaskAdd(ulong fsttim, ulong cirtim, void (*pTaskFunc)(void), uchar type)
  49. {
  50. int i;
  51. int pos = -1;
  52. //查找位置
  53. for (i= 0; i<TIM_TASK_NUMBER; i++)
  54. {
  55. if (timTaskTab[i].pTaskFunc == pTaskFunc)
  56. {
  57. pos = i;
  58. break;
  59. }
  60. if ((pos == -1) && (timTaskTab[i].flagState == 0))
  61. {
  62. pos = i;
  63. }
  64. }
  65. //任务已满
  66. if (pos == -1)
  67. {
  68. return -1;
  69. }
  70. //
  71. timTaskTab[pos].pTaskFunc = pTaskFunc;
  72. timTaskTab[pos].cntRun = fsttim / TIM_TASK_PERIOD;
  73. timTaskTab[pos].numCircle = cirtim / TIM_TASK_PERIOD;
  74. timTaskTab[pos].flagRun = 0;
  75. timTaskTab[pos].flagType = type;
  76. timTaskTab[pos].flagState = 1;
  77. return 0;
  78. }
  79. /*************************************************************************
  80. * 函数原型:
  81. * 功能描述:
  82. * 入口参数:
  83. * 出口参数:
  84. * 返 回 值:
  85. *************************************************************************/
  86. void TimTaskDel(void (*pTaskFunc)(void))
  87. {
  88. int i;
  89. for (i= 0; i<TIM_TASK_NUMBER; i++)
  90. {
  91. if (timTaskTab[i].pTaskFunc == pTaskFunc)
  92. {
  93. timTaskTab[i].flagState = 0;
  94. timTaskTab[i].flagRun = 0;
  95. return;
  96. }
  97. }
  98. }
  99. /*************************************************************************
  100. * 函数原型:
  101. * 功能描述:
  102. * 入口参数:
  103. * 出口参数:
  104. * 返 回 值:
  105. *************************************************************************/
  106. void TimTaskUpdate(void)
  107. {
  108. int i;
  109. SPT_set(TIM_TASK_PERIOD * 64 / 1000);
  110. for (i= 0; i<TIM_TASK_NUMBER; i++)
  111. {
  112. if (timTaskTab[i].flagState != 0)
  113. {
  114. if (timTaskTab[i].cntRun != 0)
  115. {
  116. timTaskTab[i].cntRun--;
  117. }
  118. else
  119. {
  120. //判断处理位置
  121. if (timTaskTab[i].flagType != 0)
  122. (*timTaskTab[i].pTaskFunc)();
  123. else
  124. timTaskTab[i].flagRun = 1;
  125. //判断重载
  126. if (timTaskTab[i].numCircle)
  127. timTaskTab[i].cntRun = timTaskTab[i].numCircle;
  128. else
  129. timTaskTab[i].flagState = 0;
  130. }
  131. }
  132. }
  133. }
  134. /*************************************************************************
  135. * 函数原型:
  136. * 功能描述:
  137. * 入口参数:
  138. * 出口参数:
  139. * 返 回 值:
  140. *************************************************************************/
  141. void TimTaskProc(void)
  142. {
  143. int i;
  144. for (i= 0; i<TIM_TASK_NUMBER; i++)
  145. {
  146. if (timTaskTab[i].flagRun != 0)
  147. {
  148. timTaskTab[i].flagRun = 0;
  149. (*timTaskTab[i].pTaskFunc)();
  150. }
  151. }
  152. }
更为巧妙的是,可以借住函数指针实现一种灵活的菜单和按键实时处理结构。类似于windows下win32的消息驱动机制,
通过中断等方式把实时事件封装成消息。以下为定义界面刷新显示和响应按键处理的结构:

  1. #ifndef __PAGE_H_
  2. #define __PAGE_H_
  3. #include "heads.h"
  4. /*=====================================================
  5. =
  6. =====================================================*/
  7. typedef struct{
  8. void (* OnPaint)( void);
  9. void (* OnKey)( short);
  10. }TypePage;
  11. /*=====================================================
  12. =
  13. =====================================================*/
  14. void WndPageSet(const TypePage *pg, int type = 0);
  15. TypePage * WndGetPage(void);
  16. void WndPageEsc(void);
  17. void WndOnKey(short key);
  18. void WndOnPaint(void);
  19. void WndMenuInit(const char *pmn, char mline);
  20. void WndMenuSelet(int m);
  21. char WndMenuGetSelet(void);
  22. long WndGetPaseword(int x, int y, char *psw, int len, long qevent);

  1. #include "pageWnd.h"
  2. /*=====================================================
  3. =
  4. =====================================================*/
  5. char flagPaint = 0;
  6. void (* pOnPaint)( void) = 0;
  7. void (* pOnKey)( short) = 0;
  8. const char *pMenuStr;
  9. uchar menuSelect = 0;
  10. uchar menuLine = 0;
  11. uchar menuTop;
  12. TypePage *pageCurrent;
  13. TypePage *pageTreeTab[ 10];
  14. uchar pageIndex = 0;
  15. /*=====================================================
  16. =
  17. =====================================================*/
  18. void WndDrawMenu(void);
  19. /*************************************************************************
  20. * 函数原型:
  21. * 功能描述:
  22. * 入口参数:
  23. * 出口参数:
  24. * 返 回 值:
  25. *************************************************************************/
  26. void WndPageSet(const TypePage *pg, int type)
  27. {
  28. if (pg == &pageMain) //防止出错
  29. {
  30. pageIndex = 0;
  31. }
  32. else if (type == 0)
  33. {
  34. pageTreeTab[pageIndex++] = pageCurrent;
  35. }
  36. pageCurrent = (TypePage *)pg;
  37. pOnPaint = pg->OnPaint;
  38. pOnKey = pg->OnKey;
  39. flagPaint = 1;
  40. }
  41. /*************************************************************************
  42. * 函数原型:
  43. * 功能描述:
  44. * 入口参数:
  45. * 出口参数:
  46. * 返 回 值:
  47. *************************************************************************/
  48. TypePage * WndGetPage(void)
  49. {
  50. return pageCurrent;
  51. }
  52. /*************************************************************************
  53. * 函数原型:
  54. * 功能描述:
  55. * 入口参数:
  56. * 出口参数:
  57. * 返 回 值:
  58. *************************************************************************/
  59. void WndPageEsc(void)
  60. {
  61. TypePage *pg;
  62. if (pageIndex != 0)
  63. {
  64. pageIndex--;
  65. pg = pageTreeTab[pageIndex];
  66. }
  67. else
  68. {
  69. pg = (TypePage *)&pageMain;
  70. }
  71. pageCurrent = pg;
  72. pOnPaint = pg->OnPaint;
  73. pOnKey = pg->OnKey;
  74. flagPaint = 1;
  75. }
  76. /*************************************************************************
  77. * 函数原型:
  78. * 功能描述:
  79. * 入口参数:
  80. * 出口参数:
  81. * 返 回 值:
  82. *************************************************************************/
  83. void WndOnPaint(void)
  84. {
  85. if (flagPaint != 0)
  86. {
  87. flagPaint = 0;
  88. (*pOnPaint)();
  89. }
  90. }
  91. /*************************************************************************
  92. * 函数原型:
  93. * 功能描述:
  94. * 入口参数:
  95. * 出口参数:
  96. * 返 回 值:
  97. *************************************************************************/
  98. void WndOnKey(short key)
  99. {
  100. if (pOnKey != 0)
  101. {
  102. (*pOnKey)(key);
  103. }
  104. }
  105. /*************************************************************************
  106. * 函数原型:
  107. * 功能描述:
  108. * 入口参数:
  109. * 出口参数:
  110. * 返 回 值:
  111. *************************************************************************/
  112. void WndMenuInit(const char *pmn, char mline)
  113. {
  114. menuSelect = 0;
  115. pMenuStr = pmn;
  116. menuLine = mline;
  117. menuTop = 0;
  118. WndDrawMenu();
  119. }
  120. /*************************************************************************
  121. * 函数原型:
  122. * 功能描述:
  123. * 入口参数:
  124. * 出口参数:
  125. * 返 回 值:
  126. *************************************************************************/
  127. void WndMenuSelet(int m)
  128. {
  129. //光标滑动
  130. if (m > 0) //下移
  131. {
  132. menuSelect++;
  133. if (menuSelect == menuLine)
  134. menuSelect = 0;
  135. if (menuSelect > menuTop + 4)
  136. {
  137. if (menuLine < menuTop + 4)
  138. menuTop = menuLine - 4;
  139. else
  140. menuTop = menuSelect - 4;
  141. }
  142. }
  143. else if (m < 0) //上移
  144. {
  145. if (menuSelect == 0)
  146. menuSelect = menuLine - 1;
  147. else
  148. menuSelect--;
  149. }
  150. //图框移动
  151. if (menuSelect < menuTop) //上移
  152. {
  153. menuTop = menuSelect;
  154. }
  155. else if (menuSelect >= menuTop + 4) //下移
  156. {
  157. menuTop = menuSelect - 3;
  158. }
  159. WndDrawMenu();
  160. }
  161. /*************************************************************************
  162. * 函数原型:
  163. * 功能描述:
  164. * 入口参数:
  165. * 出口参数:
  166. * 返 回 值:
  167. *************************************************************************/
  168. char WndMenuGetSelet(void)
  169. {
  170. return menuSelect + 1;
  171. }
  172. /*************************************************************************
  173. * 函数原型:
  174. * 功能描述:
  175. * 入口参数:
  176. * 出口参数:
  177. * 返 回 值:
  178. *************************************************************************/
  179. void WndDrawMenu(void)
  180. {
  181. int i;
  182. char buf[ 17];
  183. const char *pmn = pMenuStr + menuTop * 16;
  184. DispClr();
  185. for (i= 0; i< 4; i++)
  186. {
  187. if (menuTop + i == menuLine)
  188. break;
  189. memcpy(buf, pmn, 16);
  190. buf[ 16] = '\0';
  191. if (menuSelect == menuTop + i)
  192. DispSetStyle(DISP_POSITION | DISP_REVERSE | DISP_7x9);
  193. else
  194. DispSetStyle(DISP_POSITION | DISP_NORMAL | DISP_7x9);
  195. DispString( 0, i * 2, buf);
  196. pmn += 16;
  197. }
  198. }
  199. /*************************************************************************
  200. * 函数原型:
  201. * 功能描述:
  202. * 入口参数:
  203. * 出口参数:
  204. * 返 回 值:
  205. *************************************************************************/
  206. long WndGetPaseword(int x, int y, char *psw, int len, long qevent)
  207. {
  208. int pin = 0;
  209. long keyevt;
  210. char key;
  211. char buf[ 20];
  212. memset(buf, '_', len);
  213. buf[len] = '\0';
  214. PSW_INPUT_LOOP:
  215. DispString(x, y, buf);
  216. keyevt = delay_and_wait_key( 0, EXIT_KEY_ALL, 0);
  217. if (keyevt == qevent)
  218. {
  219. psw[ 0] = '\0';
  220. return keyevt;
  221. }
  222. switch (keyevt)
  223. {
  224. case EXIT_KEY_0:
  225. key = '0';
  226. break;
  227. case EXIT_KEY_1:
  228. key = '1';
  229. break;
  230. case EXIT_KEY_2:
  231. key = '2';
  232. break;
  233. case EXIT_KEY_3:
  234. key = '3';
  235. break;
  236. case EXIT_KEY_4:
  237. key = '4';
  238. break;
  239. case EXIT_KEY_5:
  240. key = '5';
  241. break;
  242. case EXIT_KEY_6:
  243. key = '6';
  244. break;
  245. case EXIT_KEY_7:
  246. key = '7';
  247. break;
  248. case EXIT_KEY_8:
  249. key = '8';
  250. break;
  251. case EXIT_KEY_9:
  252. key = '9';
  253. break;
  254. case EXIT_KEY_COMM:
  255. if (pin != 0)
  256. {
  257. buf[--pin] = '_';
  258. }
  259. goto PSW_INPUT_LOOP;
  260. break;
  261. case EXIT_KEY_ENTER:
  262. psw[pin] = 0;
  263. return 0;
  264. default:
  265. goto PSW_INPUT_LOOP;
  266. }
  267. if (pin != len)
  268. {
  269. psw[pin] = key;
  270. buf[pin] = '*';
  271. pin++;
  272. }
  273. goto PSW_INPUT_LOOP;
  274. }

在软件设计时,如果添加界面和对应的按键处理,很灵活,只需要新添加一个文件就可以了,文件的内容,只需要实现OnPain和对应的OnKey

  1. #include "PageMenu.h"
  2. /*=====================================================
  3. =
  4. =====================================================*/
  5. const char mainMenuTab[] = /*
  6. 1234567890123456*/ "\
  7. 1. 现场采集 \
  8. 2. 数据上传 \
  9. 3. 存储状态查询 \
  10. 4. 时间设置 \
  11. 5. 对比度设置 \
  12. 6. 恢复出厂设置 \
  13. 7. 关于 ";
  14. /*=====================================================
  15. =
  16. =====================================================*/
  17. void PageMenuOnPain(void);
  18. void WndMenuOnKey(short key);
  19. const TypePage pageMenu = {PageMenuOnPain, WndMenuOnKey};
  20. /*************************************************************************
  21. * 函数原型:
  22. * 功能描述:
  23. * 入口参数:
  24. * 出口参数:
  25. * 返 回 值:
  26. *************************************************************************/
  27. void PageMenuOnPain(void)
  28. {
  29. WndMenuInit(mainMenuTab, 7);
  30. }
  31. /*************************************************************************
  32. * 函数原型:
  33. * 功能描述:
  34. * 入口参数:
  35. * 出口参数:
  36. * 返 回 值:
  37. *************************************************************************/
  38. void WndMenuOnKey(short key)
  39. {
  40. int res;
  41. switch (key)
  42. {
  43. case KEY_F1:
  44. case KEY_ENTER:
  45. res = WndMenuGetSelet();
  46. switch (res)
  47. {
  48. case 1:
  49. WndPageSet(&pageSimp);
  50. break;
  51. case 2:
  52. WndPageSet(&pagePclink);
  53. break;
  54. case 3:
  55. WndPageSet(&pageInquire);
  56. break;
  57. case 4:
  58. WndPageSet(&pageRtc);
  59. break;
  60. case 5:
  61. WndPageSet(&pageGray);
  62. break;
  63. case 6:
  64. SPageInit();
  65. WndPageSet(&pageMenu, 1);
  66. break;
  67. case 7:
  68. WndPageSet(&pageAbout);
  69. break;
  70. }
  71. break;
  72. case KEY_F2:
  73. case KEY_F3:
  74. WndPageSet(&pageMain);
  75. break;
  76. case KEY_1:
  77. WndPageSet(&pageSimp);
  78. break;
  79. case KEY_2:
  80. WndPageSet(&pagePclink);
  81. break;
  82. case KEY_3:
  83. WndPageSet(&pageInquire);
  84. break;
  85. case KEY_4:
  86. WndPageSet(&pageRtc);
  87. break;
  88. case KEY_5:
  89. WndPageSet(&pageGray);
  90. break;
  91. case KEY_6:
  92. SPageInit();
  93. WndPageSet(&pageMenu, 1);
  94. break;
  95. case KEY_7:
  96. WndPageSet(&pageAbout);
  97. break;
  98. case KEY_UP:
  99. WndMenuSelet( -1);
  100. break;
  101. case KEY_DOWN:
  102. WndMenuSelet( 1);
  103. break;
  104. case KEY_POWER:
  105. WndPageSet(&pagePower);
  106. break;
  107. }
  108. }

pageMain,pageAbout,pageRtc,pagePclink等文件,他们的结构很类似。都是实现了OnPaint和OnKey函数。
如:pagePclink.c文件内容:
实现了PagePclinkOnPaint和PagePclinOnKey函数.

CommPclink函数是自己想要实现的功能,可以自己定义。

  1. #include "pagePclink.h"
  2. /*=====================================================
  3. =
  4. =====================================================*/
  5. void PagePclinkOnPaint(void);
  6. void PagePclinOnKey(short key);
  7. const TypePage pagePclink = {PagePclinkOnPaint, PagePclinOnKey};
  8. /*************************************************************************
  9. * 函数原型:
  10. * 功能描述:
  11. * 入口参数:
  12. * 出口参数:
  13. * 返 回 值:
  14. *************************************************************************/
  15. void PagePclinkOnPaint(void)
  16. {
  17. DispClr();
  18. DispSetStyle(DISP_CENTER | DISP_REVERSE | DISP_7x9);
  19. DispString( 0, 0, " 数据上传 ");
  20. DispSetStyle(DISP_POSITION|DISP_NORMAL|DISP_7x9);
  21. DispString( 0, 6, "[连接] [返回]");
  22. }
  23. /*************************************************************************
  24. * 函数原型:
  25. * 功能描述:
  26. * 入口参数:
  27. * 出口参数:
  28. * 返 回 值:
  29. *************************************************************************/
  30. void PagePclinOnKey(short key)
  31. {
  32. switch (key)
  33. {
  34. case KEY_F1:
  35. CommPclink();
  36. break;
  37. case KEY_F3:
  38. WndPageEsc();
  39. break;
  40. }
  41. }

  1. #ifndef __PAGE_POWER_H_
  2. #define __PAGE_POWER_H_
  3. #include "pageWnd.h"
  4. /*=====================================================
  5. =
  6. =====================================================*/
  7. extern const TypePage pagePower;
  8. #endif
  9. #include "PagePower.h"
  10. #include "disp.h"
  11. /*=====================================================
  12. =
  13. =====================================================*/
  14. void PagePowerOnPaint(void);
  15. void PagePowerOnKey(short key);
  16. const TypePage pagePower = {PagePowerOnPaint, PagePowerOnKey};
  17. /*************************************************************************
  18. * 函数原型:
  19. * 功能描述:
  20. * 入口参数:
  21. * 出口参数:
  22. * 返 回 值:
  23. *************************************************************************/
  24. void PagePowerOnPaint(void)
  25. {
  26. DispClr();
  27. DispSetStyle(DISP_CENTER | DISP_REVERSE | DISP_7x9);
  28. DispString( 0, 0, " 电源管理 ");
  29. DispSetStyle(DISP_POSITION|DISP_NORMAL|DISP_7x9);
  30. DispString( 0, 2, " [Enter] 关机 ");
  31. DispString( 0, 4, " [F3 ] 返回 ");
  32. }
  33. /*************************************************************************
  34. * 函数原型:
  35. * 功能描述:
  36. * 入口参数:
  37. * 出口参数:
  38. * 返 回 值:
  39. *************************************************************************/
  40. void PagePowerOnKey(short key)
  41. {
  42. switch (key)
  43. {
  44. case KEY_ENTER:
  45. case KEY_POWER:
  46. Halt_EH0218( 4);
  47. SysInit();
  48. break;
  49. case KEY_F3:
  50. WndPageEsc();
  51. break;
  52. }
  53. }

这样的一种结构,很灵活,在主函数中只需要这样调用:

  1. int main(void)
  2. {
  3. short key;
  4. typ_msg_word smw;
  5. SysInit();
  6. for ( ; ; )
  7. {
  8. /*
  9. 界面刷新
  10. */
  11. WndOnPaint();
  12. /*
  13. 消息处理
  14. */
  15. smw.s_word = sys_msg(SM_STAY_AWAKE); //用SM_GOTO_SLEEP串口就不能用
  16. //按键处理
  17. if (smw.bits.key_available)
  18. {
  19. LcdOffDelay(LCD_OFF_DELAY);
  20. key = KEY_read();
  21. if (key != -1)
  22. {
  23. WndOnKey(key);
  24. }
  25. }
  26. //插入充电电源
  27. if (smw.bits.charger_on)
  28. {
  29. LcdOffDelay(LCD_OFF_DELAY);
  30. }
  31. //断开充电电源
  32. if (smw.bits.charger_off)
  33. {
  34. LcdOffDelay(LCD_OFF_DELAY);
  35. RefreshBattery();
  36. }
  37. //串口
  38. if (smw.bits.comm_data)
  39. {
  40. CommReceive();
  41. }
  42. //实时任务
  43. if (smw.bits.time_out)
  44. {
  45. TimTaskProc();
  46. }
  47. }
  48. }

猜你喜欢

转载自blog.csdn.net/lizhibing115/article/details/80953037