系列内容章节如下:
STM32并口屏应用实例:点亮你的显示世界之原理篇
STM32并口屏应用实例:点亮你的显示世界之程序篇
STM32并口屏应用实例:点亮你的显示世界之应用篇
在嵌入式系统开发中,显示设备是与用户交互的重要界面之一。STM32作为一款功能强大的微控制器,支持多种外设接口,其中并口屏的驱动应用非常广泛,其抗干扰性比其他接口更为出色。本文将通过一个实际案例,介绍如何使用STM32驱动并口屏,并展示其原理和实现过程。
PS:本文所涉及的显示框架设计不仅仅局限于并口通信,串行接口也是适用的。本文是在显示驱动之上的应用设计。文章名称只是为了承接系列文章的统一设计风格。
书接上文STM32并口屏应用实例:点亮你的显示世界之程序篇我们继续进行并口屏的应用程序设计。本文介绍一套适合于小屏幕的上下级菜单式切页显示的方案。同时我们在前面也有一篇关于旋转编码器应用的文章:STM32旋转编码器应用实例。我们结合旋转编码器来实现显示的切换和参数的修改。
一、程序设计思路
总体思路是面向页面对象编程,每一个页面有唯一ID,通过ID的索引把各个界面联系起来,可以从上级页面跳到下级页面,也可从下级页面返回上级页面。
二、页面操作设计
2.1 页面对象
我们引入面向对象编程的思想,以每一个页面作为一个对象,这个对象有如下操作:
1、进入当前页面,初始化当前页面数据,如果没有可以忽略;
2、周期刷新页面内容,这个一般是必备的;
3、退出页面,当通过按键或者旋钮开关调节切页的时候,执行退出操作,如果不需要刻意忽略。
通过以下宏和页面索引结构体来实现每一个页面的定义。
typedef enum
{
PAGE_INIT = 0,
PAGE_SCAN,
PAGE_EXIT,
} PageScanType_e;
typedef struct
{
uint8_t current;
uint8_t exit; //返回索引号
uint8_t enter; //确认索引号
void (*current_operation)(PageScanType_e opt); /* 当前扫描页面 */
void (*exit_operation)(PageScanType_e opt); /* 退出切换页面 */
void (*enter_operation)(PageScanType_e opt); /* 确认切换页面 */
} page_table;
current:当前页面的索引号
exit:按下“退出“按钮后要跳转到的页面索引号
enter:按下“确认“按钮后要跳转到的页面索引号
current_operation:当前页面的索引号要执行的显示函数,这是一个函数指针
exit_operation:退出页面的索引号要执行的显示函数,这是一个函数指针
enter_operation:确认页面的索引号要执行的显示函数,这是一个函数指针
注意:因为我们在这里结合了旋转编码器,所以不需要上下切换按键,旋转编码器数值变化时,通过页面刷新不同数据达到上下切换的效果(上下切属于同一级页面,甚至可以看作同一个页面,上下切换只是显示这个页面的各个局部内容)。
2.2 页面集合
我们以字母代替页面显示函数名称进行说明,实际应用时,可以自己编写页面显示函数名称。
#define FUN_1_BASE 1
#define FUN_A1_BASE 2
#define FUN_A21_BASE 3
#define FUN_F21_BASE 7
#define FUN_F31_BASE 8
#define FUN_F41_BASE 9
page_table table[]=
{
//第0层
{ 0, 2, 0,(*fun_0) ,(*fun_0) ,(*fun_a1)},
{ 1, 1, 1,(*fun_1) ,(*fun_1) ,NULL}, /* 告警弹窗:任意界面可弹窗,按任意键返回弹窗前的界面 */
//第1层
{ 2, 0, 2,(*fun_a1),(*fun_0) ,NULL}, /* 由于不使用单独页面 enter 需要 base + offset 再当前页面函数就行修改 */
//第2层
{ 3, 2, 3,(*fun_a21),(*fun_a1),NULL},
{ 4, 2, 4,(*fun_c21),(*fun_a1),NULL},
{ 5, 2, 5,(*fun_d21),(*fun_a1),NULL},
{ 6, 2, 6,(*fun_e21),(*fun_a1),NULL},
{ 7, 2, 7,(*fun_f21),(*fun_a1),NULL}, /* 密码验证成功才能进入下一层fun_f31,否则提示密码输入错误 */
//第3层:厂家参数设置界面(输入密码后)
{ 8, 7, 8,(*fun_f31),(*fun_f21),NULL},
//第4层
{ 9, 8, 9,(*fun_f41),(*fun_f31),NULL},
{10, 8,10,(*fun_f42),(*fun_f31),NULL},
{11, 8,11,(*fun_f43),(*fun_f31),NULL},
};
从上面的数组可以看出,程序设计将页面分为多级:
第0层是主界面:按“确认”可进入菜单界面,按其他键无效,可以填入本界面显示函数指针;
第1层是菜单界面:按“退出”可返回主界面,按其他键无效,可填本界面显示函数指针或者填`NULL`,执行时会判断;
第2层是各个菜单下的显示界面;操作不在赘述;
第3层是厂家菜单界面,在第二层输入密码后才能跳转到;
第4层是厂家餐点下的显示界面。
然后,还可以看到第0层还有一个告警弹窗界面,这个弹窗界面其实并不独属于某一个层级,咋任何层级,只要有告警触发时,从任意界面跳转到告警弹窗界面,所以我就将告警弹窗界面暂时罗列在第0层。因为告警跳转不符合数组中的跳转逻辑,所以我们在跳转告警界面时需要保留跳转上下文,退出告警弹窗界面时能够返回原来的界面(可以是任意层的界面)。如下,定义的宏#define FUN_1_BASE 1
就是告警弹窗界面的索引。
2.3 页面调用
页面调用通过维护一个页面控制结构体来完成:
typedef struct PageCtrl
{
uint16_t func_index;
uint16_t last_index;
uint16_t pop_up_index; /*!< 弹窗前界面 */
bool isAuth; /*!< 菜单选项是否确认鉴权 */
bool isAdmin; /*!< 菜单选项是否已经登录权限界面 */
void (*current_operation_index)(PageScanType_e opt); /*!< 当前周期扫描页面 */
} PageCtrl_t;
其中,
1、func_index为当前页面索引,页面触发切换后更新;
2、last_index上一个页面索引,页面切换完成后更新;
3、pop_up_index弹窗前的页面索引,产生告警弹窗时更新;(上一节提到的告警弹窗界面)
4、isAuth菜单选项是否确认鉴权(根据需要,如果需要秘钥才能进入的界面,可以通过该标志位设定)
5、isAdmin菜单选项是否已经登录权限界面
6、void (*current_operation_index)(PageScanType_e opt); /*!< 当前周期扫描页面 */
页面调用通过在while(1)循环周期中,判断页面索引是否发生改变,如果索引改变,就顺序执行:
1、退出上一个界面,执行操作函数,参数为PAGE_EXIT;(本示例不需要)
2、更新当前页面操作函数指针;
3、进入下一个界面,执行操作函数,参数为PAGE_INIT;
4、更新当前界面索引;
5、周期执行当前页面操作函数,刷新页面。
if (sPageCtrl.func_index != sPageCtrl.last_index)
{.....
//(*sPageCtrl.current_operation_index)(PAGE_EXIT); /*退出上一个页面操作函数*/
sPageCtrl.current_operation_index = table[sPageCtrl.func_index].current_operation;
(*sPageCtrl.current_operation_index)(PAGE_INIT); /*进入下一个页面操作函数*/
sPageCtrl.last_index = sPageCtrl.func_index;
}
(*sPageCtrl.current_operation_index)(PAGE_SCAN); /*执行当前页面操作函数*/
2.4 页面切换逻辑
在文章开头提到的STM32旋转编码器应用实例本实例是通过旋转编码器来更新页面显示区域,通过按键来进入和退出页面。
按键逻辑如下:
static void SetKeyFunCB(IoKeyType_e key, KeyAction_e action)
{
if (action == KEY_ACTION_PRESS)
{
printf("KEY_ACTION_PRESS key = %d\n", key);
if (key == IO_MENU) /* 返回 */
{
sPageCtrl.func_index = table[sPageCtrl.func_index].exit;
}
else if (key == IO_ENTER) /* 确认 */
{
sPageCtrl.func_index = table[sPageCtrl.func_index].enter;
}
else if (key == IO_EC11)
{
places_step++;
if(places_step>=4)
{
places_step = 0;
}
}
if(FUN_1_BASE == sPageCtrl.func_index) /* 弹窗后任意按键返回弹窗前界面 */
{
GW_INFO("func_index = pop_up_index = %d\n", sPageCtrl.pop_up_index);
sPageCtrl.func_index = sPageCtrl.pop_up_index;
}
}
}
通过IO_MENU
按键返回上一级界面,更新页面索引为当前页面的table[sPageCtrl.func_index].exit;
通过IO_ENTER
按键进入下一级界面,更新页面索引为当前页面的table[sPageCtrl.func_index].enter;
通过IO_EC11
按键切换数值更新位,这为旋转编码器的按键,配合此按键快速更改数据;
2.5 页面示例
static void fun_a1(PageScanType_e opt)
{
switch (opt)
{
case PAGE_INIT:
{
index = 0;
places_step = 0;
sPageCtrl.isSelected = false;
Api_LoadSetHmi(index);
break;
}
case PAGE_SCAN:
{
Api_Encoder_GetPara(&index, 0, 4, PLACE_ONES);
Api_LoadSetHmi(index);
table[FUN_A1_BASE].enter = FUN_A21_BASE+index; /* 直接修改下一个页面链接 */
}
case PAGE_EXIT:
{
break;
}
}
}
当前页面为设置菜单页面:
- 首次进入该页面是初始化状态
case PAGE_INIT:
初始化参数值index; - 周期刷新页面,调用
Api_Encoder_GetPara(&index, 0, 4, PLACE_ONES);
来获取当前选中的菜单,通过旋转编码器可更新index值;应用旋转获取0-4的数值,每次步进1;
详细使用方法见STM32旋转编码器应用实例 - 然后调用LCD显示API接口
Api_LoadSetHmi(index);
显示内容根据index值来确定;
显示内容参考:STM32并口屏应用实例:点亮你的显示世界之程序篇采用画布模式,更新画布后全屏刷新。
/****************************************************************************************************
函数名称: Api_LoadSetHmi
功能描述: 设置页面加载
输 入:
输 出: 无
********************************************************************************************************/
void Api_LoadSetHmi(int index)
{
LcdTxType_e type = LCD_CANVASBUFF;
memset(sg_arrFrameBuffer, 0x00, sizeof(sg_arrFrameBuffer));
Api_LoadSelectPoint(type, index, 4);
if(index<4)
{
Api_display_label_16x16(type, 20, 0, ContorlM16x16, sizeof(ContorlM16x16)/32);
Api_display_label_16x16(type, 20, 2, CommSetting16x16, sizeof(CommSetting16x16)/32);
Api_display_label_16x16(type, 20, 4, PowerPara16x16, sizeof(PowerPara16x16)/32);
Api_display_label_16x16(type, 20, 6, DisPara16x16, sizeof(ContorlM16x16)/32);
}
else
{
Api_display_label_16x16(type, 20, 0, ManufacturerPara16x16, sizeof(ManufacturerPara16x16)/32);
}
Api_disp_256x64(0, 1, sg_arrFrameBuffer[0]);
}
三、效果展示
通过上述代码,我们成功驱动了8位并口的液晶屏,实现了参数的显示。如下:
特殊符号显示
中英文字符串显示
十六进制数据显示
十进制数据显示
四、总结
本文通过一个实际案例,展示了如何使用STM32驱动8位并口屏。通过硬件连接和软件编程,我们实现了屏幕的初始化、数据传输和显示功能。并口屏以其快速的数据传输能力,在嵌入式显示领域具有广泛的应用前景。希望本文能为你的项目提供参考和启发。本文提及的并口屏显示应用是针对这一系列文章所取的题名,所涉及的显示框架设计不仅仅局限于并口屏,其他串行接口也是适用的。