输入检测代码部分

Tutorial: 电子电路 Category: 嵌入式 Published: 2026-08-29 08:09:36 Views: 0 Likes: 0 Comments: 0

034.3.2 输入检测代码部分

#include "reg52.h"
//#include <intrins.h>

sbit LED1 = P1^0;
sbit LED2 = P1^1;
sbit LED3 = P1^2;
sbit LED4 = P1^3;

sbit KEY1 = P3^2;
sbit KEY2 = P3^3;

sbit SUN = P3^7;

void Delay20ms(void) // @11.0592MHz
{
    unsigned char data i, j;

    i = 36;
    j = 217;
    do
    {
        while (--j);
    } while (--i);
}

void main()
{
    while (1)
    {
        if (KEY1 == 0)
        {
            Delay20ms();
            while (KEY1 == 0); // while(1);松手检查
            Delay20ms();
            LED1 = ~LED1;
        }

        if (KEY2 == 0)
        {
            Delay20ms();
            while (KEY2 == 0); // while(1);松手检查
            Delay20ms();
            LED2 = ~LED2;
        }

        if (SUN == 0)
        {
            LED3 = 1;
        }
        else
        {
            LED3 = 0;
        }
    }
}

一、输入检测代码部分

本节使用 3.2输入检测 工程,实现两个按键的单次触发和光敏模块的数字量检测。程序持续读取 P3.2P3.3P3.7,再分别控制 LED1、LED2 和 LED3。

flowchart LR
    A[KEY1 / P3.2] --> D[按下与松手检测]
    B[KEY2 / P3.3] --> D
    D --> E[每次完整按压只产生一个事件]
    E --> F[翻转 LED1 / LED2]
    C[光敏模块 DO / P3.7] --> G[持续读取数字电平]
    G --> H[控制 LED3 模拟智能路灯]

开发板上的输入和输出都是低电平有效,但两者的原因不同:

  • 按键按下时把 P3 端口接到 GND,所以按下读到 0。
  • LED 的阳极接 +5V,阴极经过限流电阻接 P1 端口,所以 P1 输出 0 时才能形成电流回路并点亮。

1.1 按键电平检测

开发板原理图中的 SW3、SW4 分别连接 P3.2P3.3,每个按键的另一组触点接 GND。P3 口作为准双向输入时,由内部弱上拉维持释放状态的高电平;按下按键后,端口被开关拉到地。

                  STC89C52
              ┌────────────────┐
内部弱上拉 ───┤ P3.2 / INT0    ├──── SW3 ──── GND
内部弱上拉 ───┤ P3.3 / INT1    ├──── SW4 ──── GND
              └────────────────┘

按键松开:开关断开,P3.x = 1
按键按下:开关闭合,P3.x = 0

由此得到最直接的电平控制写法:

if (KEY1 == 0)
{
    LED1 = 0;
}
else
{
    LED1 = 1;
}

这段代码让 LED1 直接跟随按键状态:按下亮,松开灭。开发板 LED1 的电路是:

+5V ── LED1 ── R8 10 kΩ ── P1.0

P1.0 = 0:+5V → LED1 → R8 → MCU 引脚 → GND,回路导通,LED1 亮
P1.0 = 1:LED 两端压差很小,电流近似为零,LED1 灭

“电平跟随”和“按一次切换一次”是两种不同需求:

控制方式代码核心按住时松开后
电平跟随KEY1==0 时亮,否则灭LED 一直亮LED 灭
状态切换每次有效按压执行 LED1=~LED1状态只应改变一次保持改变后的状态

如果只把状态切换写成下面这样,效果不会可靠:

if (KEY1 == 0)
{
    LED1 = ~LED1;
}

while(1) 主循环运行速度远快于人的动作。按键保持低电平的几十或几百毫秒内,上述语句会执行很多次,LED 被高速反复取反;最终停在亮还是灭,取决于执行次数的奇偶。同时,机械触点抖动还会产生额外的高低电平变化。因此,要实现“按一次只切换一次”,既要处理抖动,也要把持续的按下电平转换为一次完整的按压事件。

编译和烧录时应选择 3.2输入检测/Objects/mcu51.hex,并结合文件时间确认它由当前工程生成;还要核对 STC89C52RC/LE52RC 芯片型号、CH340 对应的 COM 口和开发板供电状态。烧录后先验证按下灯亮、松开灯灭的跟随效果,再验证加入松手检测后的单次切换效果。

1.2 优化按键检测

轻触按键内部依靠金属弹片接通两组触点。弹片在刚接触和刚分离时会快速振动,使电路在“通”和“断”之间跳变数次,这就是机械抖动。

理想波形和实际波形可表示为:

时间 ─────────────────────────────────────────────►

动作:       按下                         松开

理想电平:1 ────────┐_____________________┌──────── 1
                    │         0           │

实际电平:1 ──────── 0-1-0-1-0 ───────── 0-1-0-1 ── 1
                     按下抖动    稳定按住    松手抖动

抖动通常持续数毫秒到数十毫秒,具体时间取决于按键结构、磨损、按压力度和电路条件。本实验在按下和松开阶段各使用 20 ms 延时,这是常用的入门经验值,不代表所有按键的抖动时间都精确等于 20 ms;产品设计应参考器件资料或示波器实测。

按键检测需要解决两类问题:

  1. 抖动重复触发:一次机械动作中出现多次 0/1 跳变。
  2. 持续电平重复触发:即使没有抖动,按住按键时 KEY1 仍会长时间等于 0,快速轮询也会重复执行取反。

有效的处理流程是:

发现 KEY1=0
      │
      ▼
等待约 20 ms,避开按下抖动
      │
      ▼
确认按键仍然为低电平
      │
      ▼
等待 KEY1 变为 1,也就是等待松手
      │
      ▼
再等待约 20 ms,避开松手抖动
      │
      ▼
执行一次 LED1 状态翻转

上方完整源码使用了其中的延时、松手等待和状态翻转。为了进一步排除偶然出现的短低脉冲,更严谨的写法还会在第一次延时后再次确认按键仍然按下:

if (KEY1 == 0)
{
    Delay20ms();
    if (KEY1 == 0)
    {
        while (KEY1 == 0);
        Delay20ms();
        LED1 = ~LED1;
    }
}

这个二次确认并不会改变“低电平有效”的硬件事实,只是避免将很短的干扰脉冲当成真实按压。

1.3 消抖

消抖分为软件消抖和硬件消抖。

  • 软件消抖:程序检测到可能的按键动作后,延时、重复采样或通过状态机确认电平稳定。优点是不增加元件、调整方便;缺点是阻塞式延时会占用 CPU 时间。
  • 硬件消抖:使用 RC 低通网络、施密特触发器或专用消抖芯片,让送入 MCU 的电平先在硬件上变得平稳。优点是不依赖主程序响应;缺点是增加元件,并且 RC 参数需要结合输入门限设计。

常见的 RC 加施密特触发器结构如下:

                       R1(上拉)
VCC ──────────────────/\/\/\───●────────► 施密特输入 ───► MCU
                                  │
                                  ├── C ─── GND
                                  │
                                  └── R2 ─── 按键 ─── GND

释放时:C 通过 R1 充电,节点缓慢变为高电平
按下时:C 通过 R2 和按键放电,节点缓慢变为低电平
施密特输入:使用不同的上升、下降门限,避免缓慢变化的电压在门限附近反复翻转

RC 变化速度由时间常数决定,近似为 τ = R × C。例如 10 kΩ × 1 µF ≈ 10 ms,但实际的上升和下降时间还取决于 R1、R2、输入门限及器件误差。R2 还可以限制按键闭合时电容的瞬时放电电流。本开发板的 SW3、SW4 原理图没有画出专用 RC 消抖网络,因此本实验采用软件消抖。

1.3.1 例题: 按键消抖

配套程序对 KEY1 的处理顺序是:

if (KEY1 == 0)
{
    Delay20ms();
    while (KEY1 == 0);
    Delay20ms();
    LED1 = ~LED1;
}

逐句理解如下:

  1. if (KEY1 == 0):捕捉到可能的按下动作。
  2. Delay20ms();:跳过按下初期的抖动区间。
  3. while (KEY1 == 0);:只要按键仍为低电平,就停在这个空循环里等待松手。末尾分号非常关键,它表示循环体为空。
  4. 第二个 Delay20ms();:按键刚松开时先等待一段时间,避开松手抖动。
  5. LED1 = ~LED1;:完整按压结束后只翻转一次 LED1。

while (KEY1 == 0); 可以理解为一个带条件的“原地等待”:

KEY1 仍为 0(手没松) ──► 继续循环,不执行后续代码
KEY1 变为 1(已经松手) ──► 退出循环,继续执行

它虽然简单,但属于阻塞式写法。按住 KEY1 时,程序无法继续扫描 KEY2 和 SUN;若需要同时处理多个输入、显示、串口等任务,应改用定时扫描状态机。

Delay20ms() 是按 11.0592 MHz 时钟和相应 STC89 指令集生成的软件延时函数。生成延时时要同时核对晶振频率、时间单位、目标指令集和编译器设置;晶振或指令周期不一致时,函数名称仍叫 20 ms,实际延时却可能不同。该函数没有调用 _nop_(),所以本工程中的 <intrins.h> 可以保持注释状态。

KEY2 的处理方式相同,只需把输入和输出对应为 KEY2LED2

KEY1(P3.2)──► 消抖与松手检测 ──► 翻转 LED1(P1.0)
KEY2(P3.3)──► 消抖与松手检测 ──► 翻转 LED2(P1.1)

复制代码后必须同时检查条件、等待松手的变量和对应 LED,避免出现外层判断 KEY2、内层却仍等待 KEY1 的复制错误。

1.3.2 光敏电阻检测

光敏模块的数字输出 DOP3.7,所以代码定义为 sbit SUN = P3^7;。这里的 ^7 表示 P3 寄存器的第 7 位;在 Keil C51 的 sbit 语法中,符号 ^ 可通过 Shift+6 输入。

光敏模块与单片机的连接为:

光敏模块                         开发板
VCC  ────────────────────────── +5V
GND  ────────────────────────── GND
DO   ────────────────────────── P3.7 / SUN
AO   ────────────────────────── 本实验不连接

模块内部的关键电路是“光敏分压 + 可调参考电压 + LM393 比较器”:

                VCC                         VCC
                 │                           │
              [10 kΩ]                    [DO 上拉]
                 │                           │
       AO ───────●──────► LM393 (+)          ├──── DO ──► P3.7
                 │              \             │
              [光敏电阻]         ) 比较器 ─────┘ 开集电极输出
                 │              /
                GND     VREF ─►(-)
                           ▲
                    电位器在 VCC 与 GND
                    之间产生可调阈值

在这套模块电路中,光越强,光敏电阻阻值越小,AO 电压越低;当 AO 低于电位器设定的 VREF 时,LM393 把 DO 拉低。因此实测关系是:

光照状态光敏电阻DO / SUN源码写入 LED3LED3 实际状态
光线较强阻值较小01
光线较弱阻值较大10

LED3 之所以在写 0 时点亮,是因为它同样采用 +5V → LED3 → 10 kΩ → P1.2 的低电平有效电路。最终程序为:

if (SUN == 0)
{
    LED3 = 1; // 光强:熄灭路灯
}
else
{
    LED3 = 0; // 光弱:点亮路灯
}

判断条件和 LED 赋值是成对的:也可以判断 SUN == 1 后执行 LED3 = 0,效果相同。调试未知模块时,应先观察 DO 或模块状态灯在明暗环境下的变化,再决定软件极性,不能仅凭引脚名猜测。

光敏模块的 DO 与按键一样都能作为数字输入读取,但光敏电阻没有机械触点,因此通常不需要按键式“等待松手”消抖。若光照恰好位于比较阈值附近,DO 仍可能因光线噪声和比较器门限反复跳变;可以调节电位器使工作点远离临界位置,或在程序中采用多次采样确认、时间滤波和迟滞判断。