一、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. 实际开发建议:

  1. 简单项目用 microLIB:节省空间
  2. 复杂项目用标准库:需要完整功能时
  3. 保持习惯:建议在创建新工程时就勾选
  4. 注意差异:某些函数在 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、原理图

按键是 PA0PC13,通过原理图可以看出,按键未被按下时,端口直接接地,当按键按下,IO 口会被配置为高电平。因此通过检测 PA0PC13 是否是高电平即可确定是否按下了开关。

2、程序执行顺序

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

未命名绘图.drawio

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;
			}
		}
	}
}