第2周第2讲

51 单片机按键控制 LED 笔记(独立键 + 矩阵键)

1. 独立按键:单个按键控制 LED

先搞懂 “4 个独立按键” 怎么用

1.1 硬件咋连接?

看原理图就能明白:

  • 独立按键(比如 S2-S5)的一端接 “地(GND)”另一端接单片机的 P3 口(P3_4~P3_7)
  • 单片机 P3 口默认是 “高电平(可以理解为‘有电’)”,按下按键时,对应 P3 引脚会被 “拉到地”,变成 “低电平(没电)”。

大白话总结:

→ 按键按下 → 对应 P3 引脚变 “0”;

→ 按键松开 → 对应 P3 引脚变 “1”。

1.2 先写 “读按键” 的函数

我们需要一个函数(叫Key_Read),帮我们 “看当前哪个按键被按下了”,代码长这样:

// 先定义几个按键相关的变量(全局的,哪里都能用)
unsigned char Key_Val, Key_Down, Key_Up, Key_Old;

// 按键读取函数:返回“当前按下的按键编号(1-4)”
unsigned char Key_Read()
{
    unsigned char temp = 0;  // 存按键编号,默认0(没按键)
    // 检测P3_4(对应按键1)是否按下(变0)
    if(P3_4 == 0) temp = 1;
    // 检测P3_5(对应按键2)
    if(P3_5 == 0) temp = 2;
    // 检测P3_6(对应按键3)
    if(P3_6 == 0) temp = 3;
    // 检测P3_7(对应按键4)
    if(P3_7 == 0) temp = 4;
    return temp;  // 把按键编号返回出去
}

解释:这个函数会挨个查 P3_4 到 P3_7,哪个引脚是 0(按键按下),就返回对应的数字(1-4);没按键就返回 0。

1.3 关键:区分 “按下瞬间” 和 “松开瞬间”

有 4 行 “死记” 的代码,但得知道它是干嘛的:

Key_Val = Key_Read();  // 步骤1:读“现在哪个键按了”
Key_Down = Key_Val & (Key_Val ^ Key_Old);  // 步骤2:找“刚按下”的瞬间
Key_Up = ~Key_Val & (Key_Val ^ Key_Old);   // 步骤3:找“刚松开”的瞬间
Key_Old = Key_Val;     // 步骤4:把当前按键值存起来,下次对比用

用大白话翻译:

  • Key_Down:只有 “手指刚按下去” 的时候,这个变量才等于按键编号(比如按了按键 1,Key_Down=1);
  • Key_Up:只有 “手指刚松开” 的时候,这个变量才等于按键编号;
  • 这俩变量能帮我们实现 “按一下就触发,不是一直触发”(比如按一下亮灯,不是按住一直闪)。

1.4 用独立键控制 LED

举 2 个常用例子:

例子 1:按键 1 点亮 LED1,按键 2 松开熄灭 LED1

代码片段:

// 按键1“刚按下”→ P1_0=0(LED1点亮)
if(Key_Down == 1)
    P1_0 = 0;
// 按键2“刚松开”→ P1_0=1(LED1熄灭)
if(Key_Up == 2)
    P1_0 = 1;

例子 2:按键控制流水灯 “启动 / 停止”

先定义一个 “开关标志”(System_Flag):1 = 流水灯跑,0 = 流水灯停。

代码片段:

unsigned char System_Flag = 0;  // 流水灯状态:默认“停”
unsigned char ucLed = 0xfe;     // 流水灯初始值(第一个LED亮)

void main()
{
    while(1)  // 死循环,一直运行
    {
        // 先执行“读按键+判断按下/松开”的4行代码
        Key_Val = Key_Read();
        Key_Down = Key_Val & (Key_Val ^ Key_Old);
        Key_Up = ~Key_Val & (Key_Val ^ Key_Old);
        Key_Old = Key_Val;

        // 1. 流水灯运行(只有System_Flag=1时才跑)
        if(System_Flag == 1)
        {
            ucLed = __crol__(ucLed, 1);  // 循环左移(LED依次亮)
            P1 = ucLed;                  // 把值给P1口,控制LED
            Delay(100);                  // 延时100ms,让流水灯能看清
        }

        // 2. 按键控制“启停”
        switch(Key_Down)
        {
            case 1: System_Flag = 1; break;  // 按按键1→流水灯启动
            case 2: System_Flag = 0; break;  // 按按键2→流水灯停止
        }
    }
}

效果:按按键 1,流水灯开始跑;按按键 2,流水灯立刻停下。

2. 矩阵按键:16 个按键控制更多功能

独立键只能接 4 个,想接更多按键(比如 16 个),就用 “矩阵按键”(4 行 4 列)。

2.1 矩阵键的硬件 & 原理

看原理图:image-20251223200551484

  • 矩阵键是4 行 + 4 列(比如 S6-S21),行接 P3_0~P3_3,列接 P3_4~P3_7;
  • 原理是 “逐行扫描”:先把某一行设为 0(其他行设 1),然后看 “列” 哪个是 0,就能知道 “哪一行哪一列的按键按下了”。

比如:

→ 先把第 1 行(P3_0)设 0,其他行设 1 → 看列 P3_4~P3_7 哪个是 0 → 对应第 1 行的按键;

→ 再把第 2 行(P3_1)设 0,其他行设 1 → 看列哪个是 0 → 对应第 2 行的按键;

→ 扫完 4 行,就能识别 16 个按键。

2.2 矩阵键的 “读按键” 函数

代码逻辑就是 “逐行扫”,返回按键编号(1-16):

unsigned char Key_Read()
{
    unsigned char temp = 0;
    // 扫第1行:P3_0=0,其他行=1
    P3_0 = 0; P3_1 = 1; P3_2 = 1; P3_3 = 1;
    if(P3_4 == 0) temp = 1;
    if(P3_5 == 0) temp = 2;
    if(P3_6 == 0) temp = 3;
    if(P3_7 == 0) temp = 4;

    // 扫第2行:P3_1=0,其他行=1
    P3_0 = 1; P3_1 = 0; P3_2 = 1; P3_3 = 1;
    if(P3_4 == 0) temp = 5;
    if(P3_5 == 0) temp = 6;
    if(P3_6 == 0) temp = 7;
    if(P3_7 == 0) temp = 8;

    // 扫第3行:P3_2=0,其他行=1
    P3_0 = 1; P3_1 = 1; P3_2 = 0; P3_3 = 1;
    if(P3_4 == 0) temp = 9;
    if(P3_5 == 0) temp = 10;
    if(P3_6 == 0) temp = 11;
    if(P3_7 == 0) temp = 12;

    // 扫第4行:P3_3=0,其他行=1
    P3_0 = 1; P3_1 = 1; P3_2 = 1; P3_3 = 0;
    if(P3_4 == 0) temp = 13;
    if(P3_5 == 0) temp = 14;
    if(P3_6 == 0) temp = 15;
    if(P3_7 == 0) temp = 16;

    return temp;
}

2.3 矩阵键控制流水灯 “速度”

加个 “延时时间变量(Time)”:Time越大,流水灯越慢;Time越小,流水灯越快。

代码片段:

unsigned char Time = 100;  // 初始延时100ms(流水灯速度中等)

void main()
{
    while(1)
    {
        // 先执行“读按键+判断按下/松开”的4行代码(和之前一样)
        Key_Val = Key_Read();
        Key_Down = Key_Val & (Key_Val ^ Key_Old);
        Key_Up = ~Key_Val & (Key_Val ^ Key_Old);
        Key_Old = Key_Val;

        // 1. 流水灯运行(用Time控制速度)
        if(System_Flag == 1)
        {
            ucLed = __crol__(ucLed, 1);
            P1 = ucLed;
            Delay(Time);  // 延时时间是Time,控制流水灯快慢
        }

        // 2. 按键控制:启停+速度
        switch(Key_Down)
        {
            case 1: System_Flag = 1; break;   // 按键1→启动流水灯
            case 2: System_Flag = 0; break;   // 按键2→停止流水灯
            case 3: Time += 100; break;       // 按键3→Time+100(流水灯变慢)
            case 4: Time -= 100; break;       // 按键4→Time-100(流水灯变快)
        }
    }
}

效果:

→ 按按键 3:流水灯 “顿一下” 再变,跑得更慢;

→ 按按键 4:流水灯 “唰唰唰” 地跑,速度变快。