欢迎光临
我们一直在努力

蓝桥杯嵌入式 按键控制LED

顺序调过来:先讲 CubeMX 怎么配,再讲代码怎么看。

一、先搞清楚要配哪些引脚

写代码之前,先要知道自己需要控制哪些引脚。在这里插入图片描述 根据原理图可知我们按键需要用到PB0,PB1,PB2,PA0,LED的引脚参考嵌入式蓝桥杯从零点亮第一个LED

引脚用途模式
PB0 按键 KEY1 输入,上拉
PB1 按键 KEY2 输入,上拉
PB2 按键 KEY3 输入,上拉
PA0 按键 KEY4 输入,上拉
PC8~PC15 LED LD1~LD8 输出
PD2 锁存器 LE 输出

为什么按键要上拉? 蓝桥杯板子按键一端接 GPIO,一端接 GND。不按的时候引脚悬空,电平随机跳,会误触发。配上内部上拉后,不按时被拉到高电平,按下时被拉到 GND 读到低电平,逻辑就确定了。

为什么 LED 要接锁存器? PC8~PC15 除了接 LED,还复用给 LCD、数码管等外设。用 74HC573 锁存器,可以把“写数据”和“输出保持”分开:写的时候 LE=1 透明通过,写完 LE=0 锁住,PC 口就能腾出来给别的外设用。

二、CubeMX 配置步骤

1. 新建工程

打开 CubeMX,File → New Project,搜索 STM32G431RBT6,选中后 Start Project。 在这里插入图片描述 在这里插入图片描述

⚠️ 蓝桥杯嵌入式板子是 STM32G431RBT6,别选错。

2. 配RCC和SYS

基础配置不会,可以看看嵌入式蓝桥杯从零点亮第一个LED 我这就不详细讲了。 在这里插入图片描述 在这里插入图片描述

3. 配按键引脚(PB0、PB1、PB2、PA0)

在芯片图上分别点击 PB0、PB1、PB2、PA0,每个都选 GPIO_Input。 在这里插入图片描述

然后进入左侧 System Core → GPIO,逐个点这四个引脚,右侧参数设成: 在这里插入图片描述

参数值
GPIO mode Input mode
GPIO Pull-up/Pull-down Pull-up
Maximum output speed Low

在这里插入图片描述

Pull-up 必须选,这是按键能稳定工作的关键。

4. 配 LED 引脚(PC8~PC15)

在芯片图上找到 PC8,按住 Shift 再点 PC15,可以一次选中 PC8~PC15 全部 8 个引脚,右键选择 GPIO_Output。

然后进 GPIO 面板,把这 8 个引脚设置成:

参数值
GPIO mode Output mode
GPIO Pull-up/Pull-down No pull-up and no pull-down
Maximum output speed Low
Default output level High(初始全灭)

初始电平设 High,因为 LED 是低电平点亮,High 表示一开始全灭,上电不会乱闪。

5. 配锁存器引脚(PD2)

点击 PD2,选 GPIO_Output,参数:

参数值
GPIO mode Output mode
GPIO Pull-up/Pull-down No pull-up and no pull-down
Maximum output speed Low
Default output level Low

初始电平设 Low,即 LE 默认拉低,锁存器保持关闭状态。

6. 把 GPIO 和 RCC 切成 LL 库

进 Project Manager → Advanced Settings,找到 GPIO 和 RCC 两行,把下拉框从 HAL 改成 LL。

这一步不做,生成的代码全是 HAL,跟你写的 LL_GPIO_IsInputPinSet 对不上。

7. 配置时钟

进 Clock Configuration 标签页,把 SYSCLK 拉到 80 MHz。

蓝桥杯板子标准频率就是 80MHz。频率不对,LL_mDelay(20) 的消抖时间会不准。

8. 生成代码

回到 Project Manager:

设置项推荐值
Project Name 自定义,如 KeyLedDemo
Project Location 不要含中文路径
Toolchain/IDE MDK-ARM
Generate peripheral initialization as a pair of .c/.h files ✅ 勾上

点 GENERATE CODE。

9. 检查生成的代码

打开 Core/Src/gpio.c,应该能看到类似:

void MX_GPIO_Init(void)
{
LL_GPIO_InitTypeDef GPIO_InitStruct = {0};

LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOA);
LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOB);
LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOC);
LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOD);

/* 按键 PA0, PB0, PB1, PB2 */
GPIO_InitStruct.Pin = LL_GPIO_PIN_0;
GPIO_InitStruct.Mode = LL_GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);

GPIO_InitStruct.Pin = LL_GPIO_PIN_0 | LL_GPIO_PIN_1 | LL_GPIO_PIN_2;
GPIO_InitStruct.Mode = LL_GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;
LL_GPIO_Init(GPIOB, &GPIO_InitStruct);

/* LED PC8~PC15 */
GPIO_InitStruct.Pin = LL_GPIO_PIN_8 | LL_GPIO_PIN_9 | LL_GPIO_PIN_10 |
LL_GPIO_PIN_11 | LL_GPIO_PIN_12 | LL_GPIO_PIN_13 |
LL_GPIO_PIN_14 | LL_GPIO_PIN_15;
GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
LL_GPIO_Init(GPIOC, &GPIO_InitStruct);

/* LE PD2 */
GPIO_InitStruct.Pin = LL_GPIO_PIN_2;
GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
LL_GPIO_Init(GPIOD, &GPIO_InitStruct);

LL_GPIO_SetOutputPin(GPIOC, LL_GPIO_PIN_8 | ... | LL_GPIO_PIN_15); // 初始全灭
LL_GPIO_ResetOutputPin(GPIOD, LL_GPIO_PIN_2); // LE = 0
}

看到这些,说明 CubeMX 配好了。你不需要手写这个函数,CubeMX 全帮你生成了。

三、再来看代码

配置讲完,回头看代码,就很好理解了。

1. 主循环里的按键扫描

if(LL_GPIO_IsInputPinSet(GPIOB, LL_GPIO_PIN_0) == 0)
{
LL_mDelay(20);
while(LL_GPIO_IsInputPinSet(GPIOB, LL_GPIO_PIN_0) == 0){;}
ucled = 1;
}

每个按键都是同一套 4 步:

  • 判断按下:== 0 说明读到低电平
  • 20ms 消抖:LL_mDelay(20) 躲过机械抖动
  • 等松手:while 死循环等到电平变高,防止一次按压触发多次
  • 赋值:给 ucled 赋值 1、2、4、8
  • 四个按键对应:

    按键引脚ucled 值
    KEY1 PB0 1
    KEY2 PB1 2
    KEY3 PB2 4
    KEY4 PA0 8

    2. 为什么是 1、2、4、8?

    板子有 8 个 LED,用一个字节的 8 个 bit 分别表示 8 个灯:

    ucled二进制点亮
    1 0000 0001 LD1
    2 0000 0010 LD2
    4 0000 0100 LD3
    8 0000 1000 LD4

    这就是位掩码。想要同时点几个灯,直接按位或:ucled = 1 | 4 = 5 就是 LD1+LD3 亮。

    3. LED_Disp 函数三步走

    void LED_Disp(uint8_t ucled)
    {
    LL_GPIO_SetOutputPin(GPIOD, LL_GPIO_PIN_2); // ① LE = 1,打开锁存器
    LL_GPIO_WriteOutputPort(GPIOC, (~ucled) << 8); // ② 取反 + 左移,写数据
    LL_GPIO_ResetOutputPin(GPIOD, LL_GPIO_PIN_2); // ③ LE = 0,锁存
    }

    第 ① 行:PD2 是锁存器的 LE。LE=1 时锁存器“透明”,输入什么输出什么。

    第 ② 行:拆成三步:

    • 取反 ~ucled:LED 低电平点亮,ucled=1 想点 LD1,硬件上要让 PC8 输出 0,所以要取反。
    • 左移 << 8:PC8 对应端口的 bit8,要把它对齐。
    • 整个端口写:LL_GPIO_WriteOutputPort 一次性写 GPIOC 所有 16 位。

    运算过程:

    ucled = 1 → 0000 0000 0000 0001
    ~ucled → 1111 1111 1111 1110
    (~ucled) << 8 → 1111 1110 0000 0000

    PC8 输出 0,LD1 亮

    第 ③ 行:LE 从 1 拉回 0,锁存器把当前输入“锁住”,PC 口就能腾出来做别的事了。

    四、完整可抄代码

    需要工程文档可下载 蓝桥杯LL库简单读取GPIO按键代码.zip

    #include "stm32g4xx_ll_gpio.h"
    #include "stm32g4xx_ll_bus.h"
    #include "stm32g4xx_ll_utils.h"

    uint8_t ucled = 0;

    void LED_Disp(uint8_t ucled)
    {
    LL_GPIO_SetOutputPin(GPIOD, LL_GPIO_PIN_2);
    LL_GPIO_WriteOutputPort(GPIOC, (~ucled) << 8);
    LL_GPIO_ResetOutputPin(GPIOD, LL_GPIO_PIN_2);
    }

    void KEY_Scan(void)
    {
    if(LL_GPIO_IsInputPinSet(GPIOB, LL_GPIO_PIN_0) == 0)
    {
    LL_mDelay(20);
    while(LL_GPIO_IsInputPinSet(GPIOB, LL_GPIO_PIN_0) == 0){;}
    ucled = 1;
    }
    if(LL_GPIO_IsInputPinSet(GPIOB, LL_GPIO_PIN_1) == 0)
    {
    LL_mDelay(20);
    while(LL_GPIO_IsInputPinSet(GPIOB, LL_GPIO_PIN_1) == 0){;}
    ucled = 2;
    }
    if(LL_GPIO_IsInputPinSet(GPIOB, LL_GPIO_PIN_2) == 0)
    {
    LL_mDelay(20);
    while(LL_GPIO_IsInputPinSet(GPIOB, LL_GPIO_PIN_2) == 0){;}
    ucled = 4;
    }
    if(LL_GPIO_IsInputPinSet(GPIOA, LL_GPIO_PIN_0) == 0)
    {
    LL_mDelay(20);
    while(LL_GPIO_IsInputPinSet(GPIOA, LL_GPIO_PIN_0) == 0){;}
    ucled = 8;
    }

    LED_Disp(ucled);
    }

    主循环调用:

    while(1)
    {
    KEY_Scan();
    }

    五、几个能少丢分的技巧

    1. 改成 else if 更快更稳

    四个键几乎不可能同时按,用 else if 逻辑清晰,也避免误判:

    if(PB0 == 0) { ... ucled = 1; }
    else if(PB1 == 0) { ... ucled = 2; }
    else if(PB2 == 0) { ... ucled = 4; }
    else if(PA0 == 0) { ... ucled = 8; }

    2. 想“按一次翻转一次”就用 ^=

    ucled ^= 1; // KEY1:LD1 亮灭切换
    ucled ^= 2;
    ucled ^= 4;
    ucled ^= 8;

    这样多个灯的状态可以同时保留,比单纯赋值灵活。

    3. 想单独点亮某个灯,用位运算

    ucled |= 1; // 点亮 LD1,不影响其他
    ucled &= ~2; // 熄灭 LD2
    ucled ^= 4; // 翻转 LD3

    4. 全端口写有风险

    LL_GPIO_WriteOutputPort(GPIOC, …) 会覆盖 PC0~PC7。蓝桥杯板子上 PC0~PC7 一般是空的,所以没事。 但如果你的工程里 PC0~PC7 还挂了别的外设,就得改成按位写:

    LL_GPIO_WriteOutputPort(GPIOC, ((~ucled) << 8) | (GPIOC->ODR & 0x00FF));

    5. 别在阻塞函数里扫描按键

    KEY_Scan 有 LL_mDelay(20) 和 while 等松手,整体是阻塞的。 放主循环没问题,但别塞到 LCD 刷屏、串口阻塞接收里,容易漏按键。

    六、流程总览

    CubeMX:新建工程 → STM32G431RBT6

    配按键 PB0/PB1/PB2/PA0 → GPIO_Input + Pull-up

    配 LED PC8~PC15 → GPIO_Output,初始 High

    配 LE PD2 → GPIO_Output,初始 Low

    Advanced Settings → GPIO 和 RCC 改 LL

    Clock Configuration → SYSCLK 80MHz

    生成代码 → gpio.c 自动生成 LL 初始化

    main.c 主循环调 KEY_Scan()

    记住三件事:

  • 按键:读低 → 20ms 消抖 → 等松手 → 赋值
  • 位掩码:1、2、4、8 对应 LD1~LD4,一字节管 8 灯
  • 锁存:LE=1 → 写 PC 高 8 位 → LE=0
  • 赞(0)
    未经允许不得转载:171主机测评 » 蓝桥杯嵌入式 按键控制LED
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址