STM32嵌入式学习 01:GPIO 输出与 LED 闪烁
1. 本课目标
- 理解
main()中 STM32 程序的基本执行顺序。 - 理解“打开外设时钟 -> 配置 GPIO -> 应用配置 -> 主循环”的固定结构。
- 使用 PA0 输出高、低电平控制 LED。
- 区分推挽输出、开漏输出。
- 理解
GPIO_Pin_0等宏的位掩码含义。 - 区分整体写入
GPIO_Write()和局部修改GPIO_SetBits()/GPIO_ResetBits()。
2. 知识主线
3. 最小 LED 程序
#include "stm32f10x.h"
#include "Delay.h"
int main(void)
{
// GPIOA 在使用前必须打开 APB2 外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 用结构体集中保存 GPIO 配置
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // PA0
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
// 将配置写入 GPIOA 的相关寄存器
GPIO_Init(GPIOA, &GPIO_InitStructure);
while (1)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_0); // PA0 输出低电平
Delay_ms(500);
GPIO_SetBits(GPIOA, GPIO_Pin_0); // PA0 输出高电平
Delay_ms(500);
}
}
4. 程序执行流程
一次完整循环的理论等待时间约为:
500 ms + 500 ms = 1000 ms
实际 LED 的亮灭相位取决于开发板 LED 的电路接法。低电平可能点亮 LED,也可能熄灭 LED,不能只根据 API 名称推断实际亮灭。
5. 四个关键步骤
5.1 打开时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
这是对 RCC 时钟控制器的调用。GPIOA 挂在 APB2 总线上,使用前需要开启对应时钟。漏掉这句时,GPIO 配置通常不会按预期工作。
5.2 配置结构体
GPIO_InitTypeDef GPIO_InitStructure;
它是一张配置表。结构体字段描述 GPIO 的模式、引脚和速度,GPIO_Init() 再根据这些字段配置底层寄存器。
5.3 推挽输出
GPIO_Mode_Out_PP
推挽输出可以主动输出高电平,也可以主动输出低电平,适合 LED 和普通数字控制信号。
开漏输出 GPIO_Mode_Out_OD 只能主动拉低;释放时需要外部上拉电阻形成高电平,常见于 I2C 和共享信号线。不能因为“都是输出模式”就把两者当作等价配置。
5.4 主循环
while (1)
单片机程序通常不会像桌面程序一样执行完后退出,而是在无限循环中持续执行控制逻辑。
6. 位掩码
源码中的宏本质上是不同的二进制位:
#define GPIO_Pin_0 ((uint16_t)0x0001)
#define GPIO_Pin_1 ((uint16_t)0x0002)
#define GPIO_Pin_2 ((uint16_t)0x0004)
因此可以用按位或选择多个引脚:
GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2
这表示同时选择 PA0、PA1、PA2。引脚选择使用 |,不要把“位集合”误写成普通加法。
7. 整体写入与局部修改
标准库实现中:
GPIO_Write(GPIOA, value); // GPIOA->ODR = value,整体覆盖
GPIO_SetBits(GPIOA, pins); // GPIOA->BSRR = pins,指定位置 1
GPIO_ResetBits(GPIOA, pins); // GPIOA->BRR = pins,指定位置 0
选择原则
| API | 影响范围 | 适用场景 |
|---|---|---|
GPIO_SetBits | 指定引脚置 1 | 单独打开 LED |
GPIO_ResetBits | 指定引脚置 0 | 单独关闭 LED |
GPIO_WriteBit | 一个指定引脚 | 根据条件设置单个引脚 |
GPIO_Write | 整个 GPIO 端口 | 独占端口的流水灯或整体刷新 |
如果 GPIOA 的其他引脚连接了别的功能,直接 GPIO_Write() 可能误改它们;此时优先用 SetBits、ResetBits 或先明确维护掩码。
8. 延时函数与前提
Delay.c 使用 Cortex-M3 的 SysTick:
SysTick->LOAD = 72 * xus;
SysTick->VAL = 0x00;
SysTick->CTRL = 0x00000005;
while (!(SysTick->CTRL & 0x00010000)) {}
其中 72 * xus 假设系统时钟为 72 MHz。若系统主频改变而延时计算没有同步修改,Delay_ms(500) 不一定仍然是 500 ms。这是本工程的时钟假设,不是所有 STM32 工程的通用常数。
该延时是阻塞式的:等待期间 CPU 主要在等待计数结束,暂时不能高效处理其他任务。复杂项目通常改用定时器中断、SysTick 节拍或 RTOS 调度。
9. 今日练习
练习 A:修改速度
把两处 Delay_ms(500) 改成 Delay_ms(100),预测完整循环约为 200 ms。
练习 B:双引脚交替
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_Init(GPIOA, &GPIO_InitStructure);
while (1)
{
GPIO_SetBits(GPIOA, GPIO_Pin_0);
GPIO_ResetBits(GPIOA, GPIO_Pin_1);
Delay_ms(500);
GPIO_ResetBits(GPIOA, GPIO_Pin_0);
GPIO_SetBits(GPIOA, GPIO_Pin_1);
Delay_ms(500);
}
练习 C:判断影响范围
回答:如果 PA1 当前为高电平,执行下面代码后 PA1 是否一定变低?
GPIO_Write(GPIOA, GPIO_Pin_0);
答案:是。因为 GPIO_Write() 整体写入 ODR,未设置的位会变为 0。
10. 事实、假设与验证边界
- 已知事实:工程目标设备为
STM32F103C8,使用 Keil ARMCC 5.06 和 STM32 标准外设库;源码配置了 GPIOA 的 PA0 推挽输出。 - 合理推断:若 LED 电路连接到 PA0 且供电、下载和接线正确,PA0 电平会周期性变化。
- 尚未确认:本次没有收到编译输出、下载结果或实际 LED 现象,因此不能把硬件闪烁记为已验证事实。
- 重要约束:LED 高低电平有效逻辑取决于开发板原理图;延时准确性依赖系统时钟为 72 MHz 的假设。
11. 下一课预告
下一章进入 GPIO 输入:按键读取。
知识链将变为:
读取输入 -> 判断按键状态 -> 修改 LED 输出
这会引入输入模式、上拉/下拉、电平有效逻辑和按键消抖。
更多推荐



所有评论(0)