一、GPIO 原理
GPIO——general purpose input output
端口基本结构

置位/复位寄存器 BSRR: 该寄存器分为高 16 位和低 16 位,高 16 位配置为 1 时将 GPIO 复位,低 16 位配置为 1 时将 GPIO 置位,两者配置为 0 的时候都无操作。
对于这个 BSRR 寄存器有一个疑问,就是为什么已经有 ODR 寄存器了,我直接对 ODR 进行操作不是就能输出想要的结果,为什么还需要 BSRR 寄存器来设置置位和复位?询问 DeepSeek 可知,这里涉及一个原子操作的问题,如果有程序读取了 GPIO,完事来了个中断把 GPIO 值改了,中断回来以后再对这个值进行修改,会把被中断修改后的值改回去,如果用 BSRR 会让修改更加原子操作一些。
二、GPIO 输出-点亮 LED
1、原理图翻阅
LED 对应 RGB 灯光的 GPIO 引脚为 PF6、PF7、PF8,即 GPIOF_6、GPIOF_7、GPIOF_8。通过原理图同样可以看出,当引脚设为低电平的时候可以点亮 LED。

2、程序执行顺序
3、查看数据手册相应引脚地址与寄存器配置
寄存器基地址

GPIO 模式配置

GPIO 数据输出

4、编写代码
案例:点亮红色 LED 灯
stm32F4xx.h 文件
#define GPIO_ODR *(unsigned int *)(0x40021400+0x14)
#define GPIO_MODER *(unsigned int *)(0x40021400)
#define RCC_AHB1ENR *(unsigned int *)(0x40023800+0x30)
main.c 文件
#include"stm32F4xx.h"
int main(void){
RCC_AHB1ENR |= (1<<5);
GPIO_MODER &= ~((0x03)<<12);
GPIO_MODER |= (1<<12);
GPIO_ODR |= (1<<6);
GPIO_ODR &= ~(1<<6);
}
void SystemInit(void){
}
bug 调试
代码烧录后发现无法点亮 LED 灯。反复核对代码与视频代码后,发现没有语法错误,代码功能也无异常,那这就奇怪了。
使用野火提供的官方例程,编译烧录后发现可以点亮 LED,对比代码后发现有很多不同的代码,因此将自己的代码修改成例程的代码,烧录后还是无法点亮 LED,这就更奇怪了。
不信邪,直接把官方例程的代码全封不动复制粘贴到自己的项目中,编译烧录,还是无法点亮 LED,这就奇怪了。
官方例程可以,我直接复制粘贴官方的到我的项目中编译烧录就不行,难道是项目配置问题?打开 keil5 的项目配置对比,发现有个 use MicroLIB没有打勾。难不成是这个问题?打上勾重新编译,卧槽 LED 亮了!

这 use MicroLIB真够坑的,为啥不勾选这个选项就不能用?问问 DeepSeek 吧:
microLIB 的作用和原因:
1. 什么是 microLIB?
microLIB 是 ARM 提供的一个精简版 C 标准库,专门为嵌入式系统设计,占用空间小,但功能相对完整标准库少。
2. 为什么勾选后才能运行?
关键原因:缺少 printf/scanf 等函数的重定向
你的代码中有
SystemInit()函数,但可能是空的。标准库需要这个函数来初始化一些东西:
- 标准 C 库:需要完整的系统初始化,包括堆栈初始化
- microLIB:更简单,对初始化要求更低
3. 具体机制:
// 当你使用标准库时,启动文件会调用: main() 之前会调用: 1. SystemInit() // 系统初始化 2. __main() // 标准库初始化(包含堆栈等) 3. main() // 你的主函数 // 如果使用microLIB: main() 之前调用: 1. SystemInit() // 系统初始化 2. main() // 直接进入主函数4. 为什么你的代码不勾选就不行?
你的
SystemInit()函数是空的,但标准库期望它完成:
- 时钟初始化
- 浮点单元初始化
- 其他系统设置
解决办法(三选一):
方法 1:勾选 Use microLIB(你用的这个)
最简单,但有些 C 库功能可能不支持。
方法 2:实现真正的 SystemInit()
void SystemInit(void) { // 设置系统时钟 // 初始化FPU // 其他必要的系统初始化 }方法 3:禁用标准库的半主机模式
在 main() 开始添加:
#pragma import(__use_no_semihosting) void _sys_exit(int x) { x = x; } struct __FILE { int handle; }; FILE __stdout;5. microLIB 的优缺点:
优点:
- 代码体积小(节省几 KB Flash)
- 启动更快
- 对系统初始化要求低
- 适合资源受限的嵌入式系统
缺点:
- 不支持所有 C 标准库功能
- 浮点运算性能可能较差
- 某些高级功能缺失
6. 实际开发建议:
- 简单项目用 microLIB:节省空间
- 复杂项目用标准库:需要完整功能时
- 保持习惯:建议在创建新工程时就勾选
- 注意差异:某些函数在 microLIB 中行为不同
作业 1+2:点亮其他两个颜色灯 + 三个灯闪烁
这里实现的是三个颜色灯交替点亮
#include"stm32F4xx.h"
void turnOnRed(){
GPIO_ODR |= (7<<6);
GPIO_ODR &= ~(1<<6);
}
void turnOnGreen(){
GPIO_ODR |= (7<<6);
GPIO_ODR &= ~(1<<7);
}
void turnOnBlue(){
GPIO_ODR |= (7<<6);
GPIO_ODR &= ~(1<<8);
}
int main(void){
RCC_AHB1ENR |= (1<<5);
// 这里可以稍微修改一下值,缩减到2行代码,懒得改了。
GPIO_MODER &= ~((0x03)<<12);
GPIO_MODER |= (1<<12);
GPIO_MODER &= ~((0x03)<<14);
GPIO_MODER |= (1<<14);
GPIO_MODER &= ~((0x03)<<16);
GPIO_MODER |= (1<<16);
unsigned int i = 0;
unsigned char c = 0;
while(1){
if(i>=1680000){
i = 0;
if(c == 0){
turnOnRed();
c++;
}else if(c == 1){
turnOnGreen();
c++;
}else if(c == 2){
turnOnBlue();
c=0;
}
}else{
i ++;
}
}
}
void SystemInit(void){
}
三、GPIO 输入-按键控制 LED
1、原理图
按键是 PA0 和 PC13,通过原理图可以看出,按键未被按下时,端口直接接地,当按键按下,IO 口会被配置为高电平。因此通过检测 PA0 或 PC13 是否是高电平即可确定是否按下了开关。

2、程序执行顺序
由于霸天虎 V2 开发板在硬件电路上配置了按键防抖,因此程序执行的时候不考虑软件的按键防抖。

3、代码实现
bsp_key.c
#include "bsp_key.h"
void init_key(void){
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
GPIO_InitTypeDef GPIO_CONFIG;
GPIO_CONFIG.GPIO_Pin = GPIO_Pin_0;
GPIO_CONFIG.GPIO_Mode = GPIO_Mode_IN;
GPIO_Init(GPIOA, &GPIO_CONFIG);
GPIO_CONFIG.GPIO_Pin = GPIO_Pin_13;
GPIO_CONFIG.GPIO_Mode = GPIO_Mode_IN;
GPIO_Init(GPIOC, &GPIO_CONFIG);
}
uint8_t read_key1(void){
return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);
}
uint8_t read_key2(void){
return GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_13);
}
bsp_key.h
#ifndef _BSP_KEY_H
#define _BSP_KEY_H
#include "stm32f4xx.h"
void init_key(void);
uint8_t read_key1(void);
uint8_t read_key2(void);
#endif //_BSP_KEY_H
main.c
#include "stm32f4xx.h"
#include "bsp_led.h"
#include "bsp_key.h"
void delay(uint32_t count){
for(;count >0; count --);
}
uint8_t scan_key1(){
if(read_key1() == 1){
while(read_key1() == 1);
return 1;
}else{
return 0;
}
}
uint8_t scan_key2(){
if(read_key2() == 1){
while(read_key2() == 1);
return 1;
}else{
return 0;
}
}
int main(void){
uint8_t led_red_status = 0;
uint8_t led_green_status = 0;
init_led();
turn_off_red();
turn_off_green();
turn_off_blue();
init_key();
while(1){
if(scan_key1()){
if(led_red_status == 0){
turn_on_red();
led_red_status = 1;
}else{
turn_off_red();
led_red_status = 0;
}
}
if(scan_key2()){
if(led_green_status == 0){
turn_on_green();
led_green_status = 1;
}else{
turn_off_green();
led_green_status = 0;
}
}
}
}