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

1、数据结构

  1. typedef struct
  2. {
  3. char CMD[CMDLen];
  4. unsigned char (*cmd_operate)(char *data);
  5. }Usart_Tab;

2、指令、函数映射表

  1. static const Usart_Tab InstructionList[CMDMax]=
  2. {
  3. {"PWON",PowOn},
  4. {"PWOFF",PowOff},
  5. {"HDCP",HdcpOnOff},
  6. {"/V",QueryKaVersion},
  7. {"EDIDUpgrade",UpdataEDID},
  8. {"Psave",Psave},
  9. {"Precall",Precall},
  10. {"Pclear",Pclear},
  11. };

3、串口解析函数实现

  1. unsigned char DataAnalysis(char *buf)
  2. {
  3. unsigned char i,Result;
  4. char *NEXT=NULL;
  5. for(i=0;i<CMDMax;i++)
  6. {
  7. NEXT=StrCmp(buf,(char*)InstructionList[i].CMD);
  8. if(NEXT!=NULL)
  9. {
  10. usartfuncp=InstructionList[i].cmd_operate;
  11. Result=(*usartfuncp)(NEXT);
  12. }
  13. }
  14. return Result;
  15. }

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

1、数据结构

菜单枚举:

  1. typedef enum
  2. {
  3. stage1=0,
  4. stage2,
  5. stage3,
  6. stage4,
  7. stage5,
  8. stage6,
  9. stage7,
  10. stage8,
  11. stage9,
  12. }SCENE;

数据结构:

  1. typedef struct {
  2. void (*current_operate)(); //当前场景的处理函数
  3. SCENE Index; //当前场景的标签
  4. SCENE Up; //按下Up键跳转的场景
  5. SCENE Down; //按下Down键跳转的场景
  6. SCENE Right; //按下Left键跳转的场景
  7. SCENE Left; //按下Right键跳转的场景
  8. }STAGE_TAB;

2、函数映射表

  1. STAGE_TAB stage_tab[]={
  2. #. operate Index Up Down Left Right
  3. {Stage1_Handler, stage1, stage4, stage7, stage3, stage2},
  4. {Stage2_Handler, stage2, stage5, stage8, stage1, stage3},
  5. {Stage3_Handler, stage3, stage6, stage9, stage2, stage1},
  6. {Stage4_Handler, stage4, stage7, stage1, stage6, stage5},
  7. {Stage5_Handler, stage5, stage8, stage2, stage4, stage6},
  8. {Stage6_Handler, stage6, stage9, stage3, stage5, stage4},
  9. {Stage7_Handler, stage7, stage1, stage4, stage9, stage8},
  10. {Stage8_Handler, stage8, stage2, stage5, stage7, stage9},
  11. {Stage9_Handler, stage9, stage3, stage6, stage8, stage7},
  12. };

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

char current_stage=stage1;<br />char prev_stage=current_stage;

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

current_stage =stage_tab[current_stage].Up;

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

  1. if(current_stage!=prev_stage)
  2. {
  3. stage_tab[current_stage].current_operate();
  4. prev_stage=current_stage;
  5. }

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

1、数据结构

(1)行元素结构体

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

HandlerFunc是函数指针,此函数即可作为行元素的显示函数,又可作为按键处理函数,其类型为:
typedef void(*HandlerFunc)(uint16_t index, char* p, uint8_t key);

三个形参的作用分别是:
@param index: 指向此函数的RowListTypeDef在数组中的下标<br />@param p: 指向当前RowListTypeDef元素的text指针指向的字符串<br />@param key: 若按下按键的值大于等于6(KEY_ADD),则此形参会是非0值(KEY_NONE);若小于6,则传入0(KEY_NONE)
(2)界面结构体

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

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

  1. ViewListTypeDef menu[] = {
  2. VIEW_MEMBER_FORMAT(rowListHome),
  3. VIEW_MEMBER_FORMAT(rowListSettingRoot),
  4. VIEW_MEMBER_FORMAT(rowListView1),
  5. VIEW_MEMBER_FORMAT(rowListView2),
  6. VIEW_MEMBER_FORMAT(rowListView3),
  7. VIEW_MEMBER_FORMAT(rowListView1_1),
  8. };

其中VIEWMEMBER_FORMAT宏定义为
`
#define ROWLENGTH(x) ((uint16_t)(sizeof(x)/sizeof(RowListTypeDef)))
#define VIEW_MEMBER_FORMAT(x) {x,ROW_LENGTH(x),0,0,0}`

(3)游标结构体
//游标,只需要定义一个即可 ==> 8字节(byte)

  1. typedef struct {
  2. uint8_t currRow; //当前指向元素
  3. uint8_t keyval; //记录按键
  4. uint16_t currViewIndex; //当前指向层
  5. uint16_t startRow; //屏幕第一行显示的行元素索引
  6. uint16_t rowNum; //记录当前层的行元素数
  7. }CursorTypeDef;

函数作用
本控件函数很少,只有两个,即:
void View_Init(ViewListTypeDef * v, CursorTypeDef * c)

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

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

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

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

设计界面时只需要定义几个数组即可。
首先定义RowListTypeDef类型数组,根据界面数定义数组个数,根据每个界面包含的行元素数定义每个数组的长度。
然后定义ViewListTypeDef类型数组,定义一个即可,数组长度是界面数决定的。
例如,定义一个实现界面的大数组:

  1. const RowListTypeDef rowListHome[] = {
  2. //{.enterViewIndex | .x | .text | .handler},
  3. {1,"home",NULL},
  4. };
  5. const RowListTypeDef rowListSRoot[] = {
  6. //{.enterViewIndex | .x | .text | .handler},
  7. {2,"Row 1",NULL},
  8. {3,"Row 2",NULL},
  9. {4,"Row 3",NULL},
  10. };
  11. const RowListTypeDef rowListView1[] = {
  12. //{.enterViewIndex | .x | .text | .handler},
  13. {5,"Row 1-1",NULL},
  14. {VIEW_NONE,"Row 1-2",NULL},
  15. {VIEW_NONE,"Row 1-3",NULL},
  16. {VIEW_NONE,"Row 1-4",NULL},
  17. {VIEW_NONE,"Row 1-5",NULL},
  18. {VIEW_NONE,"Row 1-6",NULL},
  19. {VIEW_NONE,"Row 1-7",NULL},
  20. {VIEW_NONE,"Row 1-8",NULL},
  21. {VIEW_NONE,"Row 1-9",NULL},
  22. };
  23. const RowListTypeDef rowListView2[] = {
  24. //{.enterViewIndex | .x | .text | .handler},
  25. {VIEW_NONE,"Row 2-1",NULL},
  26. {VIEW_NONE,"Row 2-2",NULL},
  27. {VIEW_NONE,"Row 2-3",NULL},
  28. {VIEW_NONE,"Row 2-4",NULL},
  29. {VIEW_NONE,"Row 2-5",NULL},
  30. {VIEW_NONE,"Row 2-6",NULL},
  31. {VIEW_NONE,"Row 2-7",NULL},
  32. {VIEW_NONE,"Row 2-8",NULL},
  33. };
  34. const RowListTypeDef rowListView3[] = {
  35. //{.enterViewIndex | .x | .text | .handler},
  36. {VIEW_NONE,"Row 3-1",NULL},
  37. {VIEW_NONE,"Row 3-2",NULL},
  38. {VIEW_NONE,"Row 3-3",NULL},
  39. {VIEW_NONE,"Row 3-4",NULL},
  40. {VIEW_NONE,"Row 3-5",NULL},
  41. {VIEW_NONE,"Row 3-6",NULL},
  42. {VIEW_NONE,"Row 3-7",NULL},
  43. {VIEW_NONE,"Row 3-8",NULL},
  44. {VIEW_NONE,"Row 3-9",NULL},
  45. {VIEW_NONE,"Row 3-10",NULL},
  46. {VIEW_NONE,"Row 3-11",NULL},
  47. {VIEW_NONE,"Row 3-12",NULL},
  48. {VIEW_NONE,"Row 3-13",NULL},
  49. {VIEW_NONE,"Row 3-14",NULL},
  50. {VIEW_NONE,"Row 3-15",NULL},
  51. };
  52. const RowListTypeDef rowListView1_1[] = {
  53. //{.enterViewIndex | .x | .text | .handler},
  54. {VIEW_NONE,"Row 1-1-1",NULL},
  55. {VIEW_NONE,"Row 1-1-2",NULL},
  56. {VIEW_NONE,"Row 1-1-3",NULL},
  57. {VIEW_NONE,"Row 1-1-4",NULL},
  58. {VIEW_NONE,"Row 1-1-5",NULL},
  59. {VIEW_NONE,"Row 1-1-6",NULL},
  60. {VIEW_NONE,"Row 1-1-7",NULL},
  61. {VIEW_NONE,"Row 1-1-8",NULL},
  62. };
  63. ViewListTypeDef menu[] = {
  64. //.currIndex | .parentViewIndex | .list | .lengthOfList | .display
  65. VIEW_MEMBER_FORMAT(rowListHome),
  66. VIEW_MEMBER_FORMAT(rowListSRoot),
  67. VIEW_MEMBER_FORMAT(rowListView1),
  68. VIEW_MEMBER_FORMAT(rowListView2),
  69. VIEW_MEMBER_FORMAT(rowListView3),
  70. VIEW_MEMBER_FORMAT(rowListView1_1),
  71. };

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

然后在main函数中调用控件初始化程序View_Init
View_Init(menu,&cursor);

在程序主循环中每隔一段时间调用程序View_Loop.例如每隔100ms调用一次
当有按键按下时,只需要根据按键的不同给cursor.keyval变量赋不同的值即可.例如:

  1. rotaryval = ReadRotaryEncoder();
  2. if(rotaryval == ROTARY_LEFT)
  3. {
  4. cursor.keyval = KEY_UP;
  5. }else if(rotaryval == ROTARY_RIGHT)
  6. {
  7. cursor.keyval = KEY_DOWN;
  8. }

其中按键值有以下:
_#define KEY_NONE 0 //没有按下按键_<br />_#define KEY_ENTER 1 //按下<确定>键_<br />_#define KEY_RETURN 2 //按下<返回>键(返回上一层)_<br />_#define KEY_HOME 3 //按下<首页>键_<br />_#define KEY_DOWN 4 //按下<下>键_<br />_#define KEY_UP 5 //按下<上>键_<br />_#define KEY_ADD 6 //按下<加>键_<br />_#define KEY_SUB 7 //按下<减>键_

代码文件
gitee:https://gitee.com/figght/zBitsView.git
GitHub:https://github.com/figght/zBitsView.git