顺序调过来:先讲 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 步:
四个按键对应:
| KEY1 | PB0 | 1 |
| KEY2 | PB1 | 2 |
| KEY3 | PB2 | 4 |
| KEY4 | PA0 | 8 |
2. 为什么是 1、2、4、8?
板子有 8 个 LED,用一个字节的 8 个 bit 分别表示 8 个灯:
| 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()
记住三件事:


