重新认识结构体:为什么 STM32 库函数喜欢接收一大包参数

经典三问

Q: C 语言结构体是什么?

A: 它是把多个有名称的成员组合成一个数据对象的类型,可以同时保存一组相关配置。

Q: 为什么库函数需要接收结构体?

A: 外设配置往往有多个彼此相关的字段,结构体能让调用接口保持稳定,也让参数含义更清楚。

Q: 结构体常见使用场景?

A: GPIO、定时器、串口等外设初始化,以及保存传感器数据、菜单项和控制器参数。


上一篇里,我把 GPIO_InitTypeDef 留在了这里:

c 复制代码
GPIO_InitTypeDef gpio;

gpio.GPIO_Pin = GPIO_Pin_13;
gpio.GPIO_Mode = GPIO_Mode_Out_PP;
gpio.GPIO_Speed = GPIO_Speed_2MHz;

GPIO_Init(GPIOC, &gpio);

我第一次看到这几行,先冒出来的是一串问题:为什么会有这么多参数?每个参数到底管什么?它们应该放在哪里?为什么最后还要把 &gpio 交给另一个函数?

这次先不急着背 GPIO_InitTypeDef。我们退回普通 C,看看结构体到底在解决什么麻烦。

参数开始变多以后

假设要写一个函数,给一辆车设置速度、方向和刹车状态:

c 复制代码
void Car_Setup(uint8_t speed,
               uint8_t direction,
               uint8_t brake);

参数少时还能看懂。调用时,三个数字分别对应速度、方向和刹车:

c 复制代码
Car_Setup(100, 1, 0);

但如果后来又加上加速度限制、电机编号、是否允许反转,函数可能变成这样:

c 复制代码
void Car_Setup(uint8_t speed,
               uint8_t direction,
               uint8_t brake,
               uint8_t acceleration,
               uint8_t motor_id,
               uint8_t allow_reverse);

调用就会变成一串不容易读懂的数字:

c 复制代码
Car_Setup(100, 1, 0, 50, 2, 1);

只看这一行,很难立刻确认 50 是加速度,还是电机编号;最后两个 12 交换位置,编译器也不会提醒你,因为它们的类型都是 uint8_t。参数越多,函数调用就越像一张没有列名的表。

可以把属于"车辆配置"的东西放在一起:

c 复制代码
typedef struct
{
    uint8_t speed;
    uint8_t direction;
    uint8_t brake;
} CarConfig;

使用时先得到一个配置对象:

c 复制代码
CarConfig config;

config.speed = 40;
config.direction = 1;
config.brake = 0;

再把它交给处理配置的函数:

c 复制代码
Car_ApplyConfig(&config);

这里的结构体没有变成什么高级对象。它就是几块挨在一起的数据,只是这些数据现在有了一个共同名字:CarConfig。函数看到这张配置单,知道速度、方向和刹车属于同一件事。

typedef struct 到底写了什么

c 复制代码
typedef struct
{
    uint8_t speed;
    uint8_t direction;
    uint8_t brake;
} CarConfig;

struct { ... } 描述了一种结构,花括号里是它的成员;最后的 CarConfig 给这种结构起了一个更好用的类型名;typedef 让以后声明变量时可以直接写:

c 复制代码
CarConfig config;

不使用 typedef 时,声明变量会更长:

c 复制代码
struct CarConfigTag
{
    uint8_t speed;
    uint8_t direction;
    uint8_t brake;
};

struct CarConfigTag config;

STM32 标准库大量使用 typedef struct,只是因为外设配置成员很多,这样写起来短一些。看到一串很长的类型名时,先把它当成"某类配置对象",不必先把名字背下来。

回到 GPIO_InitTypeDef

c 复制代码
GPIO_InitTypeDef gpio;

gpio.GPIO_Pin = GPIO_Pin_13;
gpio.GPIO_Mode = GPIO_Mode_Out_PP;
gpio.GPIO_Speed = GPIO_Speed_2MHz;

这三行不是随便挑了三个宏。它们分别回答三个问题:要安排哪根引脚?这根引脚作为输入、输出还是复用功能?输出时需要多快的边沿?

GPIO_Pin_13 换成 GPIO_Pin_12,你换的是哪根引脚;把 GPIO_Mode_Out_PP 换成输入模式,你是在换引脚的工作方式;把 GPIO_Speed_2MHz 换成 GPIO_Speed_50MHz,你只是在改变输出边沿能力。每一项都应该能在芯片手册或开发板接线中找到对应的东西。

为什么函数要拿到 &gpio

c 复制代码
GPIO_Init(GPIOC, &gpio);

gpio 是一个结构体变量,里面放着刚刚填写好的配置。&gpio 取出这个变量的地址,传给 GPIO_Init()。函数拿着这个地址,就能读取 GPIO_PinGPIO_ModeGPIO_Speed,再去修改 GPIOC 的配置寄存器。

拿地址并不意味着函数获得了什么神秘能力。函数仍然只是在读一段内存;只是它读完之后,调用了标准库里对应的寄存器操作,于是 PC13 的工作方式发生了变化。

点号和箭头其实在访问同一组成员

手里是结构体变量时,用点号:

c 复制代码
gpio.GPIO_Pin = GPIO_Pin_13;

手里是指向结构体的指针时,用箭头:

c 复制代码
GPIO_InitTypeDef *config;

config = &gpio;
config->GPIO_Pin = GPIO_Pin_13;

config->GPIO_Pingpio.GPIO_Pin 访问的是同一个成员。箭头不是 STM32 专用语法,只是"先沿着指针找到结构体,再访问它的成员"的缩写写法。

配置单要不要先恢复默认值

GPIO 的配置成员不算多,逐项填写通常已经够清楚。定时器、串口和 I2C 的配置更长,漏掉一个成员时,问题可能要到下载后才露出来。标准库常提供恢复默认配置的函数:

c 复制代码
GPIO_InitTypeDef gpio;

GPIO_StructInit(&gpio);
gpio.GPIO_Pin = GPIO_Pin_13;
gpio.GPIO_Mode = GPIO_Mode_Out_PP;
gpio.GPIO_Speed = GPIO_Speed_2MHz;

GPIO_Init(GPIOC, &gpio);

我一般先把这张配置单擦干净,再填这次真正需要的内容。不是每个函数都必须先调用 StructInit,但当结构体成员很多、代码还在调整时,我更愿意先恢复默认值,少给旧数据留下机会。

到了定时器、串口和 I2C,你还会看到 TIM_TimeBaseInitTypeDefUSART_InitTypeDefI2C_InitTypeDef。名字换了,动作没有换:声明一个配置对象,填写成员,把它的地址交给初始化函数。

结构体能帮助你把"这一组参数属于同一件事"写得清楚。真正需要花时间的,是弄明白每个成员改变以后,哪一根线、哪一个计数器或哪一段通信会跟着变化。

这些类型和函数就存在于右键快捷添加的那个标准库中。顺着调用链往里走,会看见标准外设库正是把配置对象翻译成 GPIO、定时器和串口寄存器操作的那一层。后面我将先深入标准库带大家看看里面是什么牛鬼蛇神,不必句句理解,只需要留下大概印象就行。

相关推荐
单片机仿真设计2 小时前
【proteus仿真】基于 STM32 单片机太阳能双轴追踪系统 Proteus 仿真设计(仿真图+程序)
stm32·单片机·嵌入式硬件·proteus·课设
zhangzhangkeji2 小时前
stm32 3-4 : 按键控制 LED,光敏传感器控制蜂鸣器
stm32·arm
Lzh编程小栈3 小时前
STM32 DMA深度解析|原理、完整工作流程、寄存器、中断联动,嵌入式面试高频考点(F1系列)
c语言·stm32·单片机·mcu·面试
新晨单片机设计3 小时前
S016A-基于STM32单片机电子秤(LCD1602显示)【Proteus仿真+Keil程序+原理图】
stm32·单片机·proteus
wuyk5554 小时前
从零吃透 MQTT 通信|第 11 章 MQTT 项目调试验证、性能优化、常见疑难问题、OTA 升级基础
c语言·开发语言·stm32·学习·性能优化
辰域电子4 小时前
STM32项目开源:粮仓环境安防监测系统(代码 + 原理图 + 仿真)
stm32·单片机·嵌入式硬件·开源·毕业设计·proteus
zhangzhangkeji5 小时前
stm32, 4-1 : OLED 调试工具。 4-2: keil 调试
stm32
充哥单片机设计6 小时前
【STM32开源项目】智能窗户控制器
stm32·单片机·嵌入式硬件·毕业设计·智能窗户
Zw-awa17 小时前
标准输入输出与 MicroLIB:C 语言的终端去了哪里
stm32