当前位置:文档之家› 单片机人机界面设计

单片机人机界面设计

*************************************************************************
独立按键与菜单显示系统的设计
****************************************************************************
引言:
通过按键和LCD液晶显示组成的人机界面是电子产品设计的常用的人机交互方式,如果能够将复杂且耗时的按键驱动、液晶驱动、菜单维护等工作从系统中分离出来并提供完备的功能,对于减少资源占用提高系统实时性、简化系统设计具有重要的意义。

本文以设计一套包含按键置数、菜单滚动、动态显示变量、系统状态发送等功能的最小化的单片机系统,并且以此为契机探讨按键和显示程序设计中应当注意的种种问题和程序的优化设计。

一、独立设计按键和显示系统的意义
人机交互界面就是是人与计算机之间传递、交换信息的媒介和对话接口,是计算机系统的重要组成部分。

按键驱动和液晶显示部分是很多电子设计采取的交互手段,它的好处是接口简便、成本低。

实际上多数的设计中按键和液晶的响应控制不需要有很高的实时性,按键程序通常都包含100ms左右的消抖延时,液晶显示时只要刷屏速度大于150ms(60HZ)肉眼是看不出分别的,但这段时间相对于实时系统来说是一个很长的,如果和系统混合在一起编译运行将占用很大的存储和运行开销。

能够将按键和显示部分从应用系统中剥离出来对于提高开发效率完备程序设计都有很高的价值,特别的对于电子竞赛短时间内需要完成完整的功能设计的情况都有重要的意义。

从系统应用的角度看不同的系统对于按键的处理和显示的功能都是很类似的,比如按键输入一个数据、屏幕的特定位置显示一个变量、菜单上下滚动选中其中的某一行等等。

一个只有三个按键的系统输入一个变量时需要很多的中间变量,还要配合按键扫描、变量数据上下限判断、液晶屏显示当前值、标记显示当前正在输入的位等一系列的功能函数调用步骤才能完成,然而最后系统所关心的只是输入变量的值是多少。

不难看出将按键与显示部分分离出来不仅简化应用系统的设计而且可以大幅提高系统实时处理能力。

设计一个完整的按键菜单液晶模块对于初学者来说也并不是一件容易的事,通常在很熟悉编译环境和语言的前提下要完整编写这些代码也需要几个星期甚至更长的时间,重复编写这些功能不仅耗时而且没有必要。

本设计中带有一个简单的单片机系统完全实现按键和显
示的全部功能独立于系统外运行,可以简单快速的开发需要的人机界面系统,同时通过通讯总线系统通知发生的事件(比如:按键状态变化、用户通过菜单选中某行、用户输入了完整的变量数据等)发送到上位机,上位机接收后可以有选择的进行处理以减少系统开销。

本设计可将菜单字符和结构信息存储在片上eeprom 中通过指令改写,这样系统就具有了很强的移植的特性。

二、 按键驱动设计
按键驱动程序看似简单,但是要编写功能完备、响应灵敏、资源占用少的按键程序需要一些技巧才能实现的。

按键一般由开关构成,少量的按键可直接io 控制,数量较多时一般构成阵列形式,以下论述也适合于AD 器件扩展的按键形式。

按键驱动程序一般由扫描部分、按键获取、按键处理部分组成,逻辑功能见下图:
将扫描和获取分开设计是有必要的。

按键扫描程序负责检查按键状态,如果按键状态变化并且执行消抖延时后仍然保持就将按键状态存入全局量待取,实时性好的按键扫描应该定时触发的形式调用。

按键获取程序返回待取按键状态处理完后清除按键状态。

首先分开设计符合按键程序的基本思想,即扫描产生事件、处理与事件一一对应、不同的事件对应多种处理方式;另外分开设计有利于扩展按键功能,例如组合按键、长按键、按键对应字符等复杂功能的设计上如果没有清晰的程序逻辑结构很容易造成混乱产生不易察觉的错误。

下面的示例程序以100~200HZ 定时调用扫描程序即可驱动按键并且以调用计数消除了去抖延时。

uint8 Press_Key_ID=0; //按键扫描码
int8 Press_Key_Time=-KEY_DEJITTER_TIME;
//键盘扫描 按键扫描 获取键码 Y N 返回
按键处理后清除 Y N 置待取按键 按键状态变化? 有待取按键? 返回
void Key_Scan(void)
{
uint8 tmp=0;
//非消抖时间键盘扫描,置键值
if (Press_Key_Time==-KEY_DEJITTER_TIME || Press_Key_Time>=0) {
tmp = ~KEY_PORTX & KEY_CODE;
if (tmp==0)
{//无键按下
Press_Key_ID=0;
Press_Key_Time=-KEY_DEJITTER_TIME;
}
else
//有键按下
if (Press_Key_ID != tmp) //按键状态有改变
{
Press_Key_ID = tmp;
Press_Key_Time=-KEY_DEJITTER_TIME+1; //进入消抖期}
//常有状态保持不变Press_Key_Time=0待取
}
// 消抖期键码不变 keytime++
else
Press_Key_Time++;
}
//获取键值,如果有返回键码或长按返回-1
uint8 Key_Get(void)
{
uint8 ret=0;
if (Press_Key_ID && Press_Key_Time>=0)
if (Press_Key_Time==0) //有待取按键
{
ret=Press_Key_ID;
Press_Key_Time++;
}
return ret;
}
三、显示部分的菜单设计
菜单应用程序的特点在于分支重多,因此要特别设计好程序的逻辑结构和菜单存储的组织形式,否则当菜单项比较多或涉及的功能比较复杂时就很难实现了。

为了不影响菜单处理时其他功能应该将菜单处理函数以外的实时控制部分单独编写到一个函数中,菜单处理函数中调用此函数完成实时任务。

下面以ST7920控制器的汉字库液晶字符菜单为例,非字符菜单其逻辑结构上也是一样的。

菜单系统可以看作由若干个菜单项组成的树状结构,每一个菜单项包括显示字符和处理函数两部分组成,即每进入一个菜单项先显示若干字符然后进入处理函数。

由此我们可以定义基本的菜单项结构如下:
typedef void (*MENU_FUNCTION)(void);
typedef struct { //菜单项结构
uint8 * MenuString; //字符数组指针
uint8 StringSize; //大小
MENU_FUNCTION Dispose_Fun; //处理函数指针
}MENU_ITEM_STRUCT;
通过结构中的函数指针可以把一个菜单项完整的描述出来,程序中每一个菜单项的切换就可以用同一个函数来处理了。

函数指针的调用形式如:
(* (Menu_Current[MenuChoice].Dispose_Fun))(); //处理调用
为清晰程序设计的逻辑,建议菜单项的命名方式以菜单系统的树状结构自然命名(例如第一层第一个菜单项字符为MenuString_1_1同时其对应的处理函数命名为MenuDispose_1_1等等),然后定义一个菜单结构数组完全描述整个菜单系统,同时附加若干条件编译来简化程序的编写,例如编译条件如果定义了某个菜单字符或处理函数就添加此菜单项的定义等,具体实现不再赘述。

四、独立的系统设计
本次设计以ST7920串行模式为显示器件,其串行时序与SPI总线一致,主控MCU完全控制SPI总线,同时主机负责完成以下部分功能:
1:按键扫描和液晶驱动;
2:中文菜单状态显示、滚屏维护、按键置数等;
3:作为主控端与上位机通讯,传送按键、菜单选中、置数结束等消息,并接收上位机传来的指令(如:数据更新、显示切换、开关显示等)。

系统设计目标为带有按键与菜单维护的独立模块,为支持菜单动态显示变量的功能,需要上位机与主控系统通讯。

此部分功能借助与LCD统一的SPI总线来实现,通讯时主控机为主机,上位机为从机。

另外主控机需要向上位机发送按键状态变化、菜单选中事件通知、按键输入变量值等实时信息,因此SPI总线需要设计成双向通讯的收发模式。

当有上述事件发生时由主控机通知上位机事件类型和变量值等信息。

SPI总线时序逻辑和通讯协议参考相关手册。

对于SPI总线的控制主要考虑是否需要专门的SPI硬件的支持,通过分析硬件SPI总线可以提高主控机与上位机的通信的实时性,可以减少对液晶驱动控制的开销。

但液晶控制器每次发送的控制指令执行需要一段时间来完成(大概100us),由于串行模式无法实现判忙条件,因此硬件SPI总线的支持对于提高LCD驱动部分能够发挥得作用也要打折扣。

结合IO口需求和性价比因素的分析,单片机采用了STC15F100系列8脚封装的单片机,这个型号的单片机内置RC震荡和复位电路,可以省去外部的晶振和复位电路。

总共可提供6个管脚的IO口,三个用于按键,三个用于模拟SPI总线。

五、总结
通过实践本文提出的独立系统大大简化电子设计中的人机界面的开发步骤,同时独立运行的系统使得上位机摆脱耗时的底层驱动开销提高系统的实时性能。

清晰的结构才能实现复杂的处理,本文分析和总结的按键和菜单程序的一般结构具有一定的代表性和参考价值。

相关主题