首先说明:这是一个失败的按键扫描程序。它的前身是机智云的STM32按键扫描代码。 不管是用什么芯片,按键扫描的延时去抖总是令人心痛,费CPU费电费时间,于是各种各样好玩有趣的按键扫描代码诞生了。就比如我曾经下载的这个机智云的代码,使用状态机+事件驱动,看起来非常漂亮。 使用状态机和一个定时器,就可以去除消抖延时,提高按键扫描效率,还可以在里面添加各种各样的功能判断,好处说不完呐。 而事件驱动,把按键和按键对应的功能隔离开,使用函数指针连接,这样分隔开了按键扫描和事件处理。回想起多级菜单,每一级菜单按键功能都不一样,处理起来简直就是噩梦。如果用事件机制来做的话,就可以通过函数指针动态改变按键功能,终于把重复混乱的变量和代码送进了回收站。 最近放假实在是心动,我想,如果能把它用在51上该多好呀,于是乎就动手了。但是当我兴冲冲地把它改了好半天,终于用在51上的时候,程序直接挂掉了——51速度太慢,消抖检测周期内不能跑完回调函数,于是就会死机。 实验环境是STC12C5A60S2 11.0592MHz 代码8级优化 对于51单片机来说,这个程序致命的缺点: 1. 51速度不够,如上所述。我试着把KeyHandle函数里后三个功能的检测注释掉,就不会死机了。但是这不是写这段程序的初衷,所以不能这么做。 2. 51内存不够。这段程序在定义4个按键3个事件的时候RAM占用量就过百了,看着51那可怜巴巴的128字节RAM,我都有点不忍了。至于STC自带的扩展的RAM,速度实在是不够,如果定义到xdata,回调函数里很简短的操作都会死机。
结果是失败了,芯片配置不行,但是代码还是很有趣的。虽然下面说的这些因为51的速度被限制了,但是我还是想说一说,都是实际问题。 以往的按键扫描,按键IO口要用sbit或者#define,离扫描函数隔得老远,16进制键码还得自己算,算错了就不响应,功能函数就更不忍直视了,和消抖搅和在一起…… 再看这个按键扫描: 1. 按键IO直接在初始化函数里用字符串输入,支持任意IO口连接的矩阵键盘和单线开关按键。 - void KeyInit(void) //按键扫描初始化
- {
- SingleKey[EnumKey_Left].IOPort1 = "P34"; SingleKey[EnumKey_Left].IOPort2 = "P30"; //注册按键 Port1必须是IO口 Port2是IO口或"GND"
- SingleKey[EnumKey_Right].IOPort1 = "P35"; SingleKey[EnumKey_Right].IOPort2 = "P30";
- SingleKey[EnumKey_Up].IOPort1 = "P36"; SingleKey[EnumKey_Up].IOPort2 = "P30";
- SingleKey[EnumKey_Down].IOPort1 = "P37"; SingleKey[EnumKey_Down].IOPort2 = "P30";
- ...
复制代码 矩阵键盘不一定要接在一个8 位的整组IO 上。对于40 脚直插的单片机来说,这反而复杂了些。但是呢,看看那些一不丢丢的小可怜单片机,比如STC15W408AS 的16脚封装,一组完整引出的IO 都没有,要是在这样的单片机上用传统的方式应用4x4 的矩阵键盘,那处理起来可难受死了……
2. 按键编码就是从0开始到最大按键支持数量-1 ,放在一个enum枚举里面,键码就是移位,不用算,直接复制粘贴就可以。还需要把下面#define的按键成员总数也一起改了,注意不要超过最大值,最大值在KeyScan.h里定义,如果有需要可以修改。 - enum EnumUserKey //按键编号和键值枚举 编号从0开始 不得超过(KEY_MAX_NUMBER-1)
- {
- EnumKey_Up = 0, EnumKey_Up_TriggerValue = 1<<EnumKey_Up,
- EnumKey_Down = 1, EnumKey_Down_TriggerValue = 1<<EnumKey_Down,
- EnumKey_Left = 2, EnumKey_Left_TriggerValue = 1<<EnumKey_Left,
- EnumKey_Right = 3, EnumKey_Right_TriggerValue = 1<<EnumKey_Right
- };
复制代码
3. 功能函数作为事件单独定义,我改进了一下原来的代码,让功能和按键互相独立,用户自定义触发方式或按键组合,再用函数指针连接想要触发的事件,逻辑简洁清晰。 还需要把上面#define的用户自定义的功能总数一起改了,就是KeyFuncs成员的数量,不一定和事件函数或者按键数量一致,只要内存够用,想要多少就要多少。
- void Key7ShortPressEvent(void)
- {
- static u8 i=0;
- i = (i+1)%CountOfArray(table);
- display(i);
- }
- void Key12ShortPressEvent(void)
- {
- static u8 i=0;
- i = (i-1)%CountOfArray(table);
- display(i);
- }
- void Key17_22ShortPressEvent(void)
- {
- Uart_SendString("Func2! \r\n");
- //这个太慢了 会死机! 放主函数里也不行 串口被打断了就会卡死
- }
复制代码- KeyFuncs[0].TriggerValue = EnumKey_Up_TriggerValue; //需要响应的键值 注意是键值! 不是键编号! 组合按键用或
- KeyFuncs[0].SingleClick = Key7ShortPressEvent; //注册回调函数
- KeyFuncs[1].TriggerValue = EnumKey_Down_TriggerValue; //需要响应的键值 注意是键值! 不是键编号! 组合按键用或
- KeyFuncs[1].SingleClick = Key12ShortPressEvent; //注册回调函数
- KeyFuncs[2].TriggerValue = EnumKey_Left_TriggerValue | EnumKey_Right_TriggerValue; //需要响应的键值 注意是键值! 不是键编号! 组合按键用或
- KeyFuncs[2].MultiPress = Key17_22ShortPressEvent; //注册回调函数
复制代码
到这里就算是完成了按键驱动的应用,这个思路是不是比传统的方式简单多了,嘿嘿。 除非要修改按键消抖的时间常数,或者删减按键功能(长按短按判定等),或者改变软件支持的按键数量,不然不需要修改KeyScan.c和KeyScan.h,这样就完全分离了驱动和应用,不管是移植维护还是调试,都非常方便。
说了这么多,结果不还是不能用吗?我用STC12C5A60S2 11.0592MHz速度确实不够,不过现在STC15W内部IRC时钟可以飙到30MHz,我没有试过,不知道够不够。就算51用不了,移植到别的单片机上也是不错的。状态机和事件驱动的思路可以放到很多应用里去,虽然这是一个失败的程序,但是到目前为止还没发现逻辑问题,只是受硬件配置限制,就当是一次学习的过程吧。 失败是成功之母。
GizwitsMCUSTM32F103C8x20170428114156281c5df12c.zip
(725.67 KB, 下载次数: 19)
状态机按键20181008bak.zip
(63.99 KB, 下载次数: 20)
|