良许Linux教程网 干货合集 C语言映射表在嵌入式串口解析、UI设计中的应用

C语言映射表在嵌入式串口解析、UI设计中的应用

一直以来,我都对在微控制器(MCU)开发中使用映射表进行各种应用感兴趣。在我的工作中,我经常使用映射表,因为它不仅能使程序更美观,而且具有极高的可扩展性。最近在群里看到一位网友对映射表的应用做了相应的总结,而且总结内容简单易懂。我觉得非常值得一看,下面我们一起来看看吧!

一、映射表在串口数据解析中的应用

1、数据结构

typedef struct 
{
    char CMD[CMDLen];
    unsigned char (*cmd_operate)(char *data);
}Usart_Tab;

2、指令、函数映射表

static const Usart_Tab InstructionList[CMDMax]=
{
    {"PWON",PowOn},
    {"PWOFF",PowOff},
    {"HDCP",HdcpOnOff},
    {"/V",QueryKaVersion},
    {"EDIDUpgrade",UpdataEDID},
    {"Psave",Psave},
    {"Precall",Precall},
    {"Pclear",Pclear},
};

3、串口解析函数实现

unsigned char DataAnalysis(char *buf)
{
  unsigned char i,Result;
  char *NEXT=NULL;
  for(i=0;iif(NEXT!=NULL)
    {
      usartfuncp=InstructionList[i].cmd_operate;
      Result=(*usartfuncp)(NEXT);
    }
  }
  return Result;
}

二、映射表在UI设计中的应用

1、数据结构

菜单枚举:

typedef enum
{
  stage1=0,
  stage2,
  stage3,
  stage4,
  stage5,
  stage6,
  stage7,
  stage8,
  stage9,
}SCENE;

数据结构:

typedef struct {
  void (*current_operate)(); //当前场景的处理函数
  SCENE Index;               //当前场景的标签
  SCENE Up;                  //按下Up键跳转的场景
  SCENE Down;                //按下Down键跳转的场景
  SCENE Right;               //按下Left键跳转的场景
  SCENE Left;                //按下Right键跳转的场景
}STAGE_TAB;

2、函数映射表

STAGE_TAB stage_tab[]={
  #.    operate       Index    Up     Down    Left   Right   
  {Stage1_Handler,  stage1,  stage4,  stage7,  stage3, stage2},
  {Stage2_Handler,  stage2,  stage5,  stage8,  stage1, stage3},
  {Stage3_Handler,  stage3,  stage6,  stage9,  stage2, stage1},
  {Stage4_Handler,  stage4,  stage7,  stage1,  stage6, stage5},
  {Stage5_Handler,  stage5,  stage8,  stage2, stage4,  stage6},
  {Stage6_Handler,  stage6,  stage9,  stage3, stage5,  stage4},
  {Stage7_Handler,  stage7,  stage1,  stage4, stage9,  stage8},
  {Stage8_Handler,  stage8,  stage2,  stage5, stage7,  stage9},
  {Stage9_Handler,  stage9,  stage3,  stage6, stage8,  stage7},
};

3、定义两个变量保存当前场景和上一个场景

char current_stage=stage1;
char prev_stage=current_stage;

4、按下Up按键 跳转到指定场景current_stage的值根据映射表改变

current_stage =stage_tab[current_stage].Up;

5、场景改变后 根据映射表执行相应的函数Handler

if(current_stage!=prev_stage)
{
  stage_tab[current_stage].current_operate();
  prev_stage=current_stage;
} 

以上文章转载自:

CSDN博客链接:https://blog.csdn.net/appleJanLinux

三、单片机实现屏幕界面,多层菜单(综合版)

1、数据结构

(1)行元素结构体

typedef struct{
 uint16_t enterViewIndex;//按下确定键跳转的界面
 char * text;            //当前行显示的文本
 HandlerFunc handler;    //显示函数
}RowListTypeDef;

HandlerFunc是函数指针,此函数即可作为行元素的显示函数,又可作为按键处理函数,其类型为:

typedef void(*HandlerFunc)(uint16_t index, char* p, uint8_t key); 

三个形参的作用分别是:

@param index: 指向此函数的RowListTypeDef在数组中的下标
@param p: 指向当前RowListTypeDef元素的text指针指向的字符串
@param key: 若按下按键的值大于等于6(KEY_ADD),则此形参会是非0值(KEY_NONE);若小于6,则传入0(KEY_NONE)

(2)界面结构体

typedef struct {
 const RowListTypeDef * const list;//指向当前层所指向的行元素
 uint16_t lengthOfList;            //指向的行元素的长度
 uint16_t parentViewIndex;         //本元素所属层的标号
 uint16_t startRow;                //记录在上一层时的开始行索引
 uint8_t currRow;                  //记录在上一层时的行索引
}ViewListTypeDef;

定义ViewListTypeDef型数组是可以使用VIEW_MEMBER_FORMAT(x)帮助编写;如:

ViewListTypeDef menu[] = {
 VIEW_MEMBER_FORMAT(rowListHome),
 VIEW_MEMBER_FORMAT(rowListSettingRoot),
 VIEW_MEMBER_FORMAT(rowListView1),
 VIEW_MEMBER_FORMAT(rowListView2),
 VIEW_MEMBER_FORMAT(rowListView3),
VIEW_MEMBER_FORMAT(rowListView1_1),
};

其中VIEW_MEMBER_FORMAT宏定义为

#define ROW_LENGTH(x)       ((uint16_t)(sizeof(x)/sizeof(RowListTypeDef)))
#define VIEW_MEMBER_FORMAT(x)       {x,ROW_LENGTH(x),0,0,0}

(3)游标结构体

//游标,只需要定义一个即可    ==> 8字节(byte)
typedef struct {
 uint8_t currRow;           //当前指向元素
 uint8_t keyval;            //记录按键
  uint16_t currViewIndex;   //当前指向层
 uint16_t startRow;         //屏幕第一行显示的行元素索引
 uint16_t rowNum;           //记录当前层的行元素数
}CursorTypeDef;

函数作用

本控件函数很少,只有两个,即:

void View_Init(ViewListTypeDef * v, CursorTypeDef * c)

此函数的作用是初始化界面控件:将用户定义好的ViewListTypeDef数组的地址和CursorTypeDef地址初始化到控件

void View_Loop(void)

此函数作用是在处理界面数据。注意:需要将此函数放入主循环中,每隔一段时间调用一次。

间隔时间典型值是100ms。

注意:并不是本控件消耗的时间多,而是屏幕驱动程序消耗的时间太多,本人的屏幕驱动是模拟的SPI时序而不是单片机硬件SPI,故屏幕驱动消耗的时间太多。控件每次需要不到1000个机器周期,而驱动程序是其上百倍。

若使用硬件外设驱动屏幕,则可以将间隔时间适当调小一点,同时注意不要低于屏幕刷新周期。

设计界面时只需要定义几个数组即可。

首先定义RowListTypeDef类型数组,根据界面数定义数组个数,根据每个界面包含的行元素数定义每个数组的长度。

然后定义ViewListTypeDef类型数组,定义一个即可,数组长度是界面数决定的。

例如,定义一个实现界面的大数组:

const RowListTypeDef rowListHome[] = {  
 //{.enterViewIndex  |  .x  |  .text  |  .handler},
   {1,"home",NULL},
};

const RowListTypeDef rowListSRoot[] = {  
 //{.enterViewIndex  |  .x  |  .text  |  .handler},
 {2,"Row 1",NULL},
 {3,"Row 2",NULL},
 {4,"Row 3",NULL},
};

const RowListTypeDef rowListView1[] = {  
 //{.enterViewIndex  |  .x  |  .text  |  .handler},
 {5,"Row 1-1",NULL}, 
 {VIEW_NONE,"Row 1-2",NULL},
 {VIEW_NONE,"Row 1-3",NULL},
 {VIEW_NONE,"Row 1-4",NULL},
 {VIEW_NONE,"Row 1-5",NULL},
 {VIEW_NONE,"Row 1-6",NULL},
 {VIEW_NONE,"Row 1-7",NULL},
 {VIEW_NONE,"Row 1-8",NULL},
 {VIEW_NONE,"Row 1-9",NULL},
};

const RowListTypeDef rowListView2[] = {  
 //{.enterViewIndex  |  .x  |  .text  |  .handler},
 {VIEW_NONE,"Row 2-1",NULL},
 {VIEW_NONE,"Row 2-2",NULL},
 {VIEW_NONE,"Row 2-3",NULL},
 {VIEW_NONE,"Row 2-4",NULL},
 {VIEW_NONE,"Row 2-5",NULL},
 {VIEW_NONE,"Row 2-6",NULL},
 {VIEW_NONE,"Row 2-7",NULL},
 {VIEW_NONE,"Row 2-8",NULL},
};

const RowListTypeDef rowListView3[] = {  
 //{.enterViewIndex  |  .x  |  .text  |  .handler},
 {VIEW_NONE,"Row 3-1",NULL},
 {VIEW_NONE,"Row 3-2",NULL},
 {VIEW_NONE,"Row 3-3",NULL},
 {VIEW_NONE,"Row 3-4",NULL},
 {VIEW_NONE,"Row 3-5",NULL},
 {VIEW_NONE,"Row 3-6",NULL},
 {VIEW_NONE,"Row 3-7",NULL},
 {VIEW_NONE,"Row 3-8",NULL},
 {VIEW_NONE,"Row 3-9",NULL},
 {VIEW_NONE,"Row 3-10",NULL},
 {VIEW_NONE,"Row 3-11",NULL},
 {VIEW_NONE,"Row 3-12",NULL},
 {VIEW_NONE,"Row 3-13",NULL},
 {VIEW_NONE,"Row 3-14",NULL},
 {VIEW_NONE,"Row 3-15",NULL},
};

const RowListTypeDef rowListView1_1[] = {  
 //{.enterViewIndex  |  .x  |  .text  |  .handler},
 {VIEW_NONE,"Row 1-1-1",NULL}, 
 {VIEW_NONE,"Row 1-1-2",NULL},
 {VIEW_NONE,"Row 1-1-3",NULL},
 {VIEW_NONE,"Row 1-1-4",NULL},
 {VIEW_NONE,"Row 1-1-5",NULL},
 {VIEW_NONE,"Row 1-1-6",NULL},
 {VIEW_NONE,"Row 1-1-7",NULL},
 {VIEW_NONE,"Row 1-1-8",NULL},
};

ViewListTypeDef menu[] = {
 //.currIndex | .parentViewIndex | .list | .lengthOfList | .display
 VIEW_MEMBER_FORMAT(rowListHome),
 VIEW_MEMBER_FORMAT(rowListSRoot),
 VIEW_MEMBER_FORMAT(rowListView1),
 VIEW_MEMBER_FORMAT(rowListView2),
 VIEW_MEMBER_FORMAT(rowListView3),
 VIEW_MEMBER_FORMAT(rowListView1_1),
};

程序格式

程序需要定义一个全局变量游标CursorTypeDef,如:

CursorTypeDef cursor;

然后在main函数中调用控件初始化程序View_Init

View_Init(menu,&cursor);

在程序主循环中每隔一段时间调用程序View_Loop.例如每隔100ms调用一次

当有按键按下时,只需要根据按键的不同给cursor.keyval变量赋不同的值即可.例如:

rotaryval = ReadRotaryEncoder();
if(rotaryval == ROTARY_LEFT)
{
    cursor.keyval = KEY_UP;
}else if(rotaryval == ROTARY_RIGHT)
{
    cursor.keyval = KEY_DOWN;
}

其中按键值有以下:

#define KEY_NONE    0      //没有按下按键
#define KEY_ENTER   1      //按下键
#define KEY_RETURN  2     //按下键(返回上一层)
#define KEY_HOME    3      //按下键
#define KEY_DOWN    4      //按下键
#define KEY_UP      5      //按下键
#define KEY_ADD     6      //按下键
#define KEY_SUB     7      //按下键

代码文件

gitee:https://gitee.com/figght/zBitsView.git
GitHub:https://github.com/figght/zBitsView.git

以上就是良许教程网为各位朋友分享的Linu系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你 !

137e00002230ad9f26e78-265x300
本文由 良许Linux教程网 发布,可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。
良许

作者: 良许

良许,世界500强企业Linux开发工程师,公众号【良许Linux】的作者,全网拥有超30W粉丝。个人标签:创业者,CSDN学院讲师,副业达人,流量玩家,摄影爱好者。
上一篇
下一篇

发表评论

联系我们

联系我们

公众号:良许Linux

在线咨询: QQ交谈

邮箱: yychuyu@163.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

关注微博
返回顶部