51单片机人机交互实战:从按键消抖到菜单系统设计

1次阅读
没有评论

共计 1713 个字符,预计需要花费 5 分钟才能阅读完成。

image.webp

背景痛点

刚开始玩 51 单片机做交互项目时,踩过不少坑。最头疼的就是按键——明明只按了一下,单片机却识别成多次触发。后来才知道这是机械按键的 触点抖动 问题,通常持续 5 -10ms。用示波器看波形时,会发现按下瞬间信号线像抽风一样高频跳变。

另一个痛点是 LCD1602 显示。直接刷新整个屏幕会导致肉眼可见的 字符残影,尤其在切换菜单时,上一屏的内容像幽灵一样短暂残留。后来发现这是由更新速度不匹配造成的。

当项目需要 多级菜单 时,新手常会用一堆 if-else 硬编码。我就干过这种事:第二周再读代码时,自己都理不清逻辑了。更糟的是,添加新功能时牵一发而动全身。

硬件设计

按键消抖电路

51 单片机人机交互实战:从按键消抖到菜单系统设计

  • RC 参数计算:要过滤 10ms 的抖动,时间常数 τ =RC 应大于抖动时长。取典型值 R =10kΩ,C=1μF,实际延迟约 10ms(τ=10×10³×1×10⁻⁶=10ms)
  • 注意事项
  • 电容建议选用陶瓷电容(如 104)
  • 上拉电阻值需与 GPIO 输入阻抗匹配

LCD 驱动扩展

使用 74HC595 芯片扩展 IO,仅用 3 个引脚就能驱动 LCD:

  1. 595 的 SER 接 P3.4(数据线)
  2. SRCLK 接 P3.5(时钟线)
  3. RCLK 接 P3.6(锁存线)

核心代码实现

状态机按键扫描

// 状态枚举(MISRA- C 兼容写法)typedef enum {
    KEY_IDLE,
    KEY_DEBOUNCE,
    KEY_CONFIRMED
} KeyState;

KeyState keyScan(uint8_t pin) {
    static KeyState state = KEY_IDLE;
    static uint16_t timer = 0;

    switch(state) {
        case KEY_IDLE:
            if(!PIN_READ(pin)) { // 检测到按下
                state = KEY_DEBOUNCE;
                timer = 10; // 10ms 计时
            }
            break;

        case KEY_DEBOUNCE:
            if(--timer == 0) {state = PIN_READ(pin) ? KEY_IDLE : KEY_CONFIRMED;
            }
            break;

        case KEY_CONFIRMED:
            if(PIN_READ(pin)) { // 检测释放
                state = KEY_IDLE;
                return KEY_PRESSED; // 返回有效按键事件
            }
            break;
    }
    return KEY_NONE;
}

菜单系统架构

typedef struct {
    const __code char *title; // 菜单标题存 ROM
    void (*action)(void);     // 回调函数指针
    struct MenuItem *parent;  // 父级菜单
    struct MenuItem *children[5]; // 子菜单数组
} MenuItem;

// 示例菜单定义
const __code MenuItem mainMenu = {"主菜单", NULL, NULL, {&subMenu1, &subMenu2}
};

性能优化

定时器中断方案

替换常见的 delay()函数:

  1. 配置定时器 0 为 1ms 中断
  2. 在中断服务程序 (ISR) 中维护全局计时变量
  3. 主循环通过检查计时变量实现非阻塞延时
volatile uint32_t systemTicks = 0;

void Timer0_ISR() interrupt 1 {
    TH0 = 0xFC; // 重装初值(1ms@11.0592MHz)systemTicks++;
}

void delayNonBlock(uint16_t ms) {
    uint32_t start = systemTicks;
    while((systemTicks - start) < ms);
}

避坑指南

长按处理技巧

  • 设置 两级阈值
  • 按下持续 500ms 触发长按事件
  • 之后每 200ms 触发一次重复事件
  • 在状态机中添加 KEY_REPEAT 状态

显示残影消除

  1. 先清除旧内容:LCD_Clear()
  2. 短暂延时 2ms
  3. 再写入新内容
  4. 使用局部刷新(只更新变化的部分)

中断共享变量

  • 对跨中断 / 主程序共享的变量:
  • 添加 volatile 关键字
  • 操作时临时关闭中断:
    EA = 0;
    criticalVar++;
    EA = 1;

思考题

如何用 PWM 驱动蜂鸣器实现以下交互反馈?
1. 短按发出 ” 滴 ” 声(100ms 4kHz)
2. 长按发出 ” 滴滴 ” 声(双脉冲)
3. 菜单切换时播放上行 / 下行音调

仿真工程下载:Proteus 项目包

正文完
 0
评论(没有评论)