找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 4010|回复: 4
打印 上一主题 下一主题
收起左侧

[失败]状态机+事件驱动 按键扫描

[复制链接]
跳转到指定楼层
楼主
首先说明:这是一个失败的按键扫描程序。它的前身是机智云的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口连接的矩阵键盘和单线开关按键。
  1. void KeyInit(void)                //按键扫描初始化
  2. {
  3. SingleKey[EnumKey_Left].IOPort1 = "P34"; SingleKey[EnumKey_Left].IOPort2 = "P30";                //注册按键 Port1必须是IO口 Port2是IO口或"GND"
  4.         SingleKey[EnumKey_Right].IOPort1 = "P35"; SingleKey[EnumKey_Right].IOPort2 = "P30";
  5.         SingleKey[EnumKey_Up].IOPort1 = "P36"; SingleKey[EnumKey_Up].IOPort2 = "P30";
  6.         SingleKey[EnumKey_Down].IOPort1 = "P37"; SingleKey[EnumKey_Down].IOPort2 = "P30";
  7.         ...
复制代码
矩阵键盘不一定要接在一个8位的整组IO上。对于40脚直插的单片机来说,这反而复杂了些。但是呢,看看那些一不丢丢的小可怜单片机,比如STC15W408AS的16脚封装,一组完整引出的IO都没有,要是在这样的单片机上用传统的方式应用4x4的矩阵键盘,那处理起来可难受死了……


2.      按键编码就是从0开始到最大按键支持数量-1 ,放在一个enum枚举里面,键码就是移位,不用算,直接复制粘贴就可以。还需要把下面#define的按键成员总数也一起改了,注意不要超过最大值,最大值在KeyScan.h里定义,如果有需要可以修改。
  1. enum EnumUserKey                                 //按键编号和键值枚举 编号从0开始 不得超过(KEY_MAX_NUMBER-1)
  2. {
  3.         EnumKey_Up  = 0, EnumKey_Up_TriggerValue = 1<<EnumKey_Up,
  4.         EnumKey_Down  = 1, EnumKey_Down_TriggerValue = 1<<EnumKey_Down,
  5.         EnumKey_Left  = 2, EnumKey_Left_TriggerValue = 1<<EnumKey_Left,
  6.         EnumKey_Right  = 3, EnumKey_Right_TriggerValue = 1<<EnumKey_Right
  7. };
复制代码

3.      功能函数作为事件单独定义,我改进了一下原来的代码,让功能和按键互相独立,用户自定义触发方式或按键组合,再用函数指针连接想要触发的事件,逻辑简洁清晰。
还需要把上面#define的用户自定义的功能总数一起改了,就是KeyFuncs成员的数量,不一定和事件函数或者按键数量一致,只要内存够用,想要多少就要多少。

  1. void Key7ShortPressEvent(void)
  2. {
  3.         static u8 i=0;
  4.         i = (i+1)%CountOfArray(table);
  5.         display(i);
  6. }
  7. void Key12ShortPressEvent(void)
  8. {
  9.         static u8 i=0;
  10.         i = (i-1)%CountOfArray(table);
  11.         display(i);
  12. }
  13. void Key17_22ShortPressEvent(void)
  14. {
  15.         Uart_SendString("Func2! \r\n");
  16.         //这个太慢了 会死机! 放主函数里也不行 串口被打断了就会卡死
  17. }
复制代码
  1.         KeyFuncs[0].TriggerValue = EnumKey_Up_TriggerValue;                        //需要响应的键值 注意是键值! 不是键编号! 组合按键用或
  2.         KeyFuncs[0].SingleClick = Key7ShortPressEvent;                //注册回调函数
  3.         KeyFuncs[1].TriggerValue = EnumKey_Down_TriggerValue;                //需要响应的键值 注意是键值! 不是键编号! 组合按键用或
  4.         KeyFuncs[1].SingleClick = Key12ShortPressEvent;                //注册回调函数
  5.         KeyFuncs[2].TriggerValue = EnumKey_Left_TriggerValue | EnumKey_Right_TriggerValue;                //需要响应的键值 注意是键值! 不是键编号! 组合按键用或
  6.         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)

评分

参与人数 1黑币 +15 收起 理由
凌净清河 + 15 绝世好帖!

查看全部评分

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏1 分享淘帖 顶 踩
回复

使用道具 举报

沙发
ID:127533 发表于 2018-11-25 08:59 | 只看该作者
驱动和应用分离的思想真不错
回复

使用道具 举报

板凳
ID:84652 发表于 2020-2-22 20:50 | 只看该作者
我最近突发奇想,如果定时器处理不了耗时长的任务,那就让主函数处理。大概的想法是搞一个任务队列,定时器只负责把任务添加到任务队列里,主函数检查任务队列,如果有任务就依次执行。最近忙,有时间的话我就试试。不知道看到这里的坛友有没有什么好主意……
回复

使用道具 举报

地板
ID:535242 发表于 2020-2-24 12:16 | 只看该作者
刚开始学啊 一脸懵逼  现在用3个按键 LCD1602做的不断电时钟,逻辑没弄清楚,可以指导下不
回复

使用道具 举报

5#
ID:84652 发表于 2020-3-3 00:10 | 只看该作者
pull1121 发表于 2020-2-24 12:16
刚开始学啊 一脸懵逼  现在用3个按键 LCD1602做的不断电时钟,逻辑没弄清楚,可以指导下不

有什么问题?
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表