玩点嵌入式
本公众号主要分享嵌入式硬件、嵌入式软件等相关内容,帮助学习者实现嵌入式技术入门到进阶。
0篇原创内容
公众号
▲点击上方公众号名片关注了解更多▲
整理、修改和排版:嵌入式云IOT技术圈
一直很想写关于映射表在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;i
{
NEXT=StrCmp(buf,(char*)InstructionList[i].CMD);
if(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、场景改变后 根据映射表执行相应的函数Handlerif(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_InitView_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
以上文章转载自:CSDN博客链接:https://blog.csdn.net/weixin_42103223
个人之前也写过不少跟以上大佬一样的映射表demo,还有传感器读取框架的,菜单框架的,可以看看我以前分享的文章:
表驱动+状态机法AD传感器驱动检测框架
基于事件型表驱动法菜单框架之小熊派简易气体探测器实战项目开发(上)
一起融合起来,便可以做出一个万能的通用产品软件框架。
本文编辑转载自嵌入式云IOT技术圈,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。版权归原作者所有,如涉及作品内容、版权和其它问题,请在30日内与本公众号联系,我们将在第一时间删除内容!▼点击下方链接关注推荐公众号▼
关注公众号『玩点嵌入式』,专注于运营嵌入式技术交流学习社群,倾力引入打造嵌入式实战课程,旨在赋能在读大学生、应届毕业生、初入社会就业者、初中级嵌入式工程师及电子爱好者的高端电子技术学习生态圈。
●【技术文章】掌握了这7个规则,单片机开发连大神都跪了
●【技术文章】用于单片机的几种C语言算法
●【技术文章】工作这几年,同事对我的称呼肉眼可见的在变化着......
●【技术文章】EMI与EMS之测试软件的来龙去脉
●【资料分享】计划招生200名学员,免费学高速PCB