博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
XMOVE3.0手持终端——软件介绍(一):精简型嵌入式管理系统的菜单实现和任务切换...
阅读量:6258 次
发布时间:2019-06-22

本文共 8279 字,大约阅读时间需要 27 分钟。

编者注: X-MOVE是作者在业余时间于2010年6月份启动的以运动传感开发,算法和应用的平台,目前已经发展了三个版本,第四版的开发接近尾声。发布在博客园仅为交流技术,不存在商业目的,作者保留一切权利。

      

一. 综述和废话

  本系统是我的的嵌入式实现部分。

  一提到OS一般都会被人喷。OS是何等庞大的东西,区区小辈凭什么敢把自己的几百行代码称之为OS?叫做框架都不行! 

  有句话叫简单就是美。方便移植,使用简单的c语言框架,在单片机上再合适不过了。

  想象一下,一个嵌入式手持系统,在2KB内存的单片机上实现,硬件上有按键和图形界面,软件上有简单的任务调度和中断服务策略,一个还不错的菜单管理和用户GUI,输入输出接口和简单的无线通信协议,有小游戏,甚至还能听MP3,甚至还有中文输入法。给你这样的系统,你还想要什么?

   所以我们称之为嵌入式管理系统,目前在430和STM32上成功移植和运行,可以支持不同颜色和分辨率的显示器,我会专门用一篇文章介绍其GUI实现。但目前我仅介绍其中的一部分:在嵌入式系统中如何实现简单的菜单和任务切换功能。

    与XMOVE手持终端相关的介绍文章列表如下:

  

  

    

  

  

  

  

 

  下面是系统实际运行图

这是该系统的12864单色屏版本

12864单色屏版本主菜单——四宫格

   320*240彩屏版本,菜单提供了三种风格和不同的配色,可以在系统设置中调节

二. 系统总体框架   

  系统面向对实时性没有极端要求的应用,针对平台是内存10KB以内的嵌入式芯片,通常包含小型LCD屏幕和键盘的工控系统,通常系统会实现一些菜单和任务调度。为实现这个目标,搭建系统框架是非常必要的。必须满足以下几类要求:(1)可移植性,主控芯片和外围模块可变,满足硬件无关性。(2)采用占先式处理,形成任务队列。(3)低内存占用,将大型数据尽可能保存在FLASH中。

  我们如何实现菜单呢?初步思路是switch-case块,系统通过键盘选择进入不同的子菜单,但子菜单终归要跳到主菜单的,用户的操作可能非常繁复,最后用swich-case这样的选择性结构根本没法描述复杂的菜单管理 。必须用改进的数据结构来描述,我们想到了图。但这样的图结构怎样描述呢?

  系统状态分为两类,菜单状态和任务状态。任何菜单页都可能有父菜单或子菜单,任务也可以看成只有父菜单而没有子菜单的特殊“菜单页”。同时每个任务都应该给出它的父菜单和子菜单值。这样就给出了任务状态转移图。当需要返回时,返回父菜单。若该菜单含有子菜单,则显示当前子菜单。

  1. 数据定义

   我们对每个菜单项定义如下的数据结构,与操作系统原理中的任务控制块(PCB)很相似。

 

复制代码
struct TaskPCB                     //菜单结构{        unsigned char *Name;  //任务名称        u8  (* function)();   //指向的函数指针         unsigned char *Detail;  //对该任务的描述         u8 PicIndex;   //该任务的图片在图片数组中的ID         u8 SubTaskList[10];  //第0项是父菜单,从第1项开始,分别对应子菜单标号            };
复制代码

  

      我们将保存TaskPCB的结构体数组,由于它是不会改变的,因此加上const标示符,编译器会将其存储在FLASH中。每个任务定义在数组中的偏移量就是该任务的唯一ID, 注释给出了结构体中成员的具体作用。此处我们重点解释下函数指针,数指针是指向函数的指针。 因而“函数指针”本身首先应是,只不过该指针变量指向函数。这正如用指针变量可指向、字符型、一样,这里是指向函数。

  将一个包含相同返回值和形参表的函数赋值给函数指针,执行该指针即等效于执行该函数。 运行时可以动态改变该指针指向的内容,从而修改程序运行方向,这就是c语言的“动态性”。C#里的委托在本质上也是函数指针,只不过它是面向对象和安全的,整个面向对象大厦就建立在委托之上,可见“函数指针”所表现的深刻内涵。

  我们定义如下的TaskPCB数组:

  

复制代码
const struct  TaskPCB  myTaskPCB[SIZE_OF_Task]= //菜单定义{    {
"系统主菜单",MenuGUI,"全局功能显示",5,{
0,6,14,20,8,33,9,10}}, //0 {
"系统时间",time_show,"查看当前系统的时间",8,{
8,0}}, //1 {
"加速度监测",AccShow,"三轴加速度检测",24,{
8,0}}, //2 {
"五子棋",Five,"人机和无线对战",23,{
9,0}}, //3 {
"俄罗斯方块",TerisBrick,"经典游戏,支持横竖屏",8,{
9,0}}, //4 {
"气压和温度",PressureTest,"显示温度和气压状态",24,{
8,0}}, //5 {
"动作感应键盘",GyroKeyboard,"感受全新的字符动作输入",17,{
14,0}}, //6 {
"通信管理",WirelessControl,"管理通信方式和协议",11,{
10,0}}, //7 {
"传感器监测",MenuGUI,"检测当前环境状态",20,{
0,6,1,2,5,19,12,16}}, //8 {
"娱乐功能",MenuGUI,"您可使用该系统自带游戏",22,{
0,4,3,4,15,28}}, //9 {
"系统管理",MenuGUI,"您可对该系统设置和管理",11,{
0,4,7,11,13,17}}, //10 {
"运行配置",OSConfigSet,"对功耗和功能的设置",19,{
10,0}}, //11 ///为了方便,仅显示了一部分 }
复制代码

     用一张结构图解释会更清楚:  

 

  2. 实现菜单显示

   有了以上的数据结构定义以后,显示就变得很简单了。 对于所有的菜单,他们的函数指针都应该指向一个函数:菜单显示函数。 请注意,由于平台不同,编码者的意愿也有所区别,该函数的实现可以非常灵活,多种多样。

   若该页是菜单,那么它的函数指针地址将指向菜单显示,通过当前的index,它会绘制出该菜单的子菜单,并完成菜单的选取和管理操作。并等待用户输入:方向键光标发生移动,跳出则系统返回父菜单,点选确定则进入子菜单项。

   我仅仅提供不完整的函数实现示意:

   (PS:这些代码是我大四时候写的,现在看都不一定能看得懂了...大家凑乎看看,其实有第一部分的数据结构,实现菜单就不成问题了)

复制代码
/*函数:u8  MenuGUI()  功能:显示不同风格的菜单界面参数:(全局变量)MenuType指出当前显示的界面风格,参见界面编辑的相关说明返回值:固定为1*/u8  MenuGUI()    //图形化界面窗口函数{    switch(MenuType)    {        case 0:        MainMenuListGUI(1,3,200,64);        break;    case 1:        MainMenuListGUI(1,8,0,25);        break;    case 2:        MainMenuListGUI(3,2,100,90);        break;            }    return 1;}
复制代码

 

复制代码
函数:u8  MainMenuListGUI()功能:主菜单界面的函数,负责绘图和和获得用户选择参数:LRMaxMount菜单左右显示的最大数量,UDMaxMount:上下显示的最大数量, OneLRLength:任一项在界面中的最大像素宽度,OneUDLength:任一项的最大像素长度返回值:固定返回1*/u8  MainMenuListGUI(u8 LRMaxMount,u8 UDMaxMount,u8 OneLRLength,u8 OneUDLength)    {        if (myTaskPCB[OS_index_data].function!=MenuGUI)  //如果要执行的不是界面绘制,则返回    {        return 0;    }        u8 MaxMount=myTaskPCB[OS_index_data].SubTaskList[1];        u8 func_state=0,menu_flag=1,LastFlag,TotalFreshEN=1,flag=1,FreshEN=1;        if(func_state==0)    {                        TaskBoxGUI_P(X_Witch_cn,Y_Witch_cn,Dis_X_MAX-X_Witch_cn,Dis_Y_MAX-Y_Witch_cn-3,(u8 *)myTaskPCB[OS_index_data].Name,0);        func_state=1;            }    while(func_state==1)            {                     MenuDataRefreshGUI( menu_flag, MaxMount, flag, LastFlag, LRMaxMount,UDMaxMount, OneLRLength, OneUDLength,FreshEN,TotalFreshEN);        LastFlag=flag;        switch(UpdownListInputControl(&menu_flag,&flag,MaxMount,LRMaxMount,UDMaxMount,1,&FreshEN,&TotalFreshEN))  //系统会在此处接收用户输入        {        case 0:            OSTaskClose();     //返回到父菜单              func_state=2;            return 1;                    case 1:            func_state=2;            break;                                }                    }            OS_index_data= myTaskPCB[OS_index_data].SubTaskList[menu_flag+flag];  //核心:通过菜单项改变OS_index_data,从而实现任务切换,见第三节    return 1;        }
复制代码

    还有接收用户输入的函数

  

 

接收用户输入的函数
/*u8 UpdownListInputControl(u8 *Menuflag,u8 *ThisPageflag,u8 *TotolFlag,u8 *ThisPageMax)功能:菜单输入控制方法,用于上下类型的菜单参数:Menuflag,全页面标志计数器,ThisPageflag当前页面标志计数器,TotolFlag总页面条数,ThisPageMax当前页面最大数量,PromptEN:是否提示到目录头或者结尾返回值:0:退出 1, 确认,2:仅仅选择了移动位置*/u8 UpdownListInputControl(u8 *Menuflag,u8 *ThisPageflag,u8 TotolFlag,u8 ThisPageLRMax, u8 ThisPageUDMax,u8 PromptEN,u8 *FreshEN,u8* TotalFreshEN){    u8 LastMenuFlag=*Menuflag;    u8 PromptFlag=0;    u8 GyroKey=KEYNULL;    *FreshEN=0;    u8 myKey=KEYNULL;    if(GyroControlEN==1)        PromptEN=0; //当开启陀螺检测时,关闭提示    if(GyroControlEN==1&&back_light>1&&GyroMenuEN)    {                delay_ms(300-20*TotolFlag);        L3G4200DReadData();        L3G4200DShowData();                delay_ms(300-20*TotolFlag);    }        else        InputControl();     if(GyroMenuEN!=0)        GyroKey=GyroKeyBoardInputMethod(0,1,300-30*ThisPageLRMax,300-20*ThisPageUDMax);        if(GyroKey!=KEYNULL)        myKey=GyroKey;    else        myKey=key_data;    GyroKey=KEYNULL;        switch(myKey)    {      case KEYENTER_UP   :                return 1;        //break;            case KEYUP_UP  :        if(*ThisPageflag>ThisPageLRMax)            (*ThisPageflag)-=ThisPageLRMax;        else        {
if(*Menuflag>ThisPageLRMax) (*Menuflag)-=ThisPageLRMax; else if(*ThisPageflag==1&&PromptEN) { PromptFlag=1; MessageGui("提示信息","已到目录开头",2); } //else //*ThisPageflag=1; } break; case KEYDOWN_UP : if(*ThisPageflag<=ThisPageLRMax*(ThisPageUDMax-1)&&*ThisPageflag+ThisPageLRMax<=TotolFlag) (*ThisPageflag)+=ThisPageLRMax; else {
if(*Menuflag+*ThisPageflag-1<=TotolFlag-ThisPageLRMax) (*Menuflag)+=ThisPageLRMax; else { if(TotolFlag==*ThisPageflag&&PromptEN) { PromptFlag=1; MessageGui("提示信息","已到目录结尾",2); } //else //*ThisPageflag= TotolFlag-*Menuflag+1; } } break; case KEYLEFT_UP : if(*ThisPageflag>1) (*ThisPageflag)--; else {
if(*Menuflag>1) (*Menuflag)--; else if(PromptEN) { MessageGui("提示信息","已到目录开头",2); PromptFlag=1; } } break; case KEYRIGHT_UP : if(*ThisPageflag

    示意图如下:

  

  3. 实现任务调度

  

  我们介绍以下系统核心全局变量:

  OS_index_data 当前需求的任务ID

  OS_index_ago 执行的上一次任务ID

  void *OS_func()  指向当前任务的函数指针

   OS_func_state 控制任务内部状态的标记位,一旦该值赋值为0,则当前任务被强行退出。

  整个系统表现为一个while循环,若任务已经全部执行完毕,则进入休眠。  而中断系统可以根据需求修改OS_index_data,同时可以将休眠的CPU唤醒并执行新的任务,当主流程发现要执行的任务和当前任务标号不同时,重新对函数指针赋值,并执行新功能。

复制代码
while(1)    {        if(OS_index_ago!=OS_index_data)  //若发现需要执行的任务与当前执行不同         {                         OS_index_ago=OS_index_data;  //            OS_func_state=0;     //清空OS_func_state值            OS_func=myTaskPCB[OS_index_data].function;  //执行函数指针赋值        }           OS_func();  //执行函数功能                LPM3;  //休眠    }
复制代码

     亦即,系统的执行流向由OS_index_data变量决定。可以修改该值的一般是中断服务或菜单服务。

三.  总结和问题

   读者可能会发现,实现用户输入和菜单显示的函数实在是太复杂了,由于不同的屏幕尺寸和要求,会出现大量的常量定义,大量的临时变量和长长的形参表:在单片机上,我只能用纯c的结构完成代码,又不能实现太多的全局变量,因此只能通过大量的函数参数传递解决棘手的问题。所以可读性实在不高,请读者见谅,你可以只关心我的数据结构的实现。不过,看了一些嵌入式界面开发的公司写的实现代码,比我的可读性更差(晕。。。。)

  有任何问题,欢迎随时交流。

   

 

  

作者:
出处:
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

标签: ,
本文转自FerventDesert博客园博客,原文链接:http://www.cnblogs.com/buptzym/archive/2012/06/22/2557460.html,如需转载请自行联系原作者
你可能感兴趣的文章
Union-Find 检测无向图有无环路算法
查看>>
RDIFramework.NET ━ 9.4 角色管理 ━ Web部分
查看>>
[SAP ABAP开发技术总结]逻辑数据库
查看>>
unix ls命令
查看>>
Ajax核心技术之XMLHttpRequest
查看>>
使用T4模板生成不同部署环境下的配置文件
查看>>
如何把Json格式字符写进text文件中
查看>>
Linux: xclip,pbcopy,xsel用法 terminal 复制粘帖 (mac , ubuntu)
查看>>
[SVN(Ubuntu)] SVN 查看历史详细信息
查看>>
技术出身能做好管理吗?——能!
查看>>
抽象工厂模式
查看>>
如何折叠一段代码使整个代码看起来简洁
查看>>
Quartz2D绘制路径
查看>>
Java知多少(30)多态和动态绑定
查看>>
JDBC操作数据库
查看>>
Android中RelativeLayout的字符水平(垂直居中)对齐
查看>>
--@angularJS--独立作用域scope绑定策略之&符策略
查看>>
乾坤合一~Linux设备驱动之USB主机和设备驱动
查看>>
Python IDLE快捷键【转载合集】
查看>>
Bootstrap中glyphicons-halflings-regular.woff字体报404错notfound
查看>>