
头文件添加与类型说明
为了解决 uint8_t 不识别等问题,需要添加相关头文件。

类型定义说明
说明我们自定义的结构体类型。

文件包含
包含相关头文件,确保类型和接口可用。

句柄识别
为防止句柄未识别,需要包含对应头文件,但仅添加头文件可能会编译报错,还需注意结构体定义和引用的完整性。

相关文件包含
继续包含所需的其他文件。

文件列表
本项目主要包含如下两个文件:

句柄定义调整
需要根据实际情况修改句柄的定义。

数据定义调整
定义数据时要注意,避免重复包含导致冲突。

编译结果
经过上述调整,最终实现0错误0警告。

文件包含总结
以上为本项目相关的全部包含文件。

串口初始化
获取串口设备时,还需要对其进行初始化。

板子连接
将开发板连接好之后,进行后续调试。

\[DISABABLE_8f59d90e\]
##LE_8e0fbc01]]
串口数据发送
发送数据后能够
头文件正常接收。
[[DISABLE添加与类型识别
我们先添加头文件。因为不认识uint8_t。

<!-- 4 -_9dd43b04]]
U->
添加这个头文件,防止不认识。

ART 设备结构体头文件示例
#ifndef __我们的这个类型。

包含这个文件。
[[FY_UART_DEVICE_H
#define __FY_UDISABLE_16fa3979]]
防止不认识句柄。但是编译会报错,还不能只单纯的添加这个。

ART_DEVICE_H
#include <stdint.h
我们先包含。

这两个文件。
struct UART_Device{
/* 名
改一下我们的句柄。

要改一下我们的定义数据因为重复包含了。
[[name;
/* 初始化DISABLE_7c15163d]]
之后是0错误0警告。

函数为函数指针类型 哪个串口 b
这就是我们包含的文件。

我们得到串口的时候还要去初始化。

连接上我们的板子。

发现可以接收到了。
au:波特率,datas:数据位,parity:校验位,stop:停止位*/
int (*init)(struct UART_Device *p

发现我们发送可以接收到。
UART设备驱动.h文件示例
#ifndef __FY_UART_DEVICE_H
#define __FY_UART_DEVICE_H
Dev, int bau, int datas, char parity, int stop#include <stdint.h>
struct UART_Device{
/* 名字 */
char *name;
/* 初始化函数为函数指针类型 哪);
/* 发送函数为个串口 bau:波特率,datas:数据位,parity:校验位,stop:停止位*/
int (*init)(struct UART_Device *pDev, int bau, int datas, char parity, int s函数指针类型 which:哪个top);
/* 发送函数为函数指针类型 which:哪个串口 datas:数据位,len:数据长度,timeout_ms:等待时间*/
int (*send)(struct UART_Device *pDev, uint8_t串口 datas:数据位,len:数据长度,timeout_m *datas,int len, int timeout_ms);
/* 接收函数为函数指针类型 which:哪个串口,data:1字节,stops:等待时间*/
int (*send)(struct UART_Device *p:停止位*/
int (*recv)(struct UART_Device *pDev, uint8_t *data, int timeout_ms);
/* 私有数据 */
void* priv_data;
};
struct UART_DevicDev, uint8_t *datas,int len, inte *GetUARTDevice(char *name);
#endif
这是我们的.h文件。
<!-- timeout_ms);
32 -->
UART设备驱动.c文件示例
#include "FY_uart_device.h"
#include "stm32f1xx_hal.h" // Dev /* 接收函数为函数指针类型ice header
#include "stm32f1xx_hal_uart.h"
which:哪个串口,data#include "FreeRTOS.h"
#include "semphr.h"
#include "queue.h"
#include <string.h>
#define UART_RX_QUEUE_LEN 100
extern UART_HandleTypeDef huart1;
/* 声明一下 *:1字节,stop:停止位*//
struct UART_Device g_stm32_uart1;
/* 扩大私有数据 */
struct UART_Data{
/* 串口 */
UART_HandleTypeDef *handle;
SemaphoreHandle_t xTxsem;
QueueHa
int (*recv)(struct UART_Dndle_t xRxQueue;
uint8_t rxdata;
};
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
struct UART_Data *data;
/* 串口发送evice *pDev, uin完成回调函数 */
if(huart == &huart1)
{
data = g_stm32_uart1.priv_data;
/* 释放信号量 */
xSemaphoreGiveFrt8_t *data, int timeout_ms);
/* 私有数据 */
omISR(data->xTxsem, NULL);
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
struct UART_Data *data;
void* priv_data;
};
struct U /* 串口接收完成回调函数 */
if(huart == &huart1)
{
data = g_stm32_uart1.priv_data;
ART_Device *GetUARTDevice(char *name);
#endif /* 写队列 */
xQueueSendFromISR(data->
这是我们的 `.hxRxQueue, &data->rxdata, NULL);
/* 再次启动数据的接收 */
HAL_UART_Receive_IT(d` 文件。
<!-- 17 -ata->handle, &data->rxdata, 1);
}
}
/* 初始化->
UART 设备结构函数为函数指针类型 bau:波体实现文件示例
#include "FY_uart_device.h"
#inc特率,datas:数据位,parity:校验位,stop:停止位*/
static int stm32_uart_init(struct UART_Deviclude "stm32f1xx_hal.h" // Device header
#ince *pDev, int bau, int datas,char parity, int stop)
{
/* 得到私有数据 */
struct UART_Data* data = pDev->privlude "stm32f1xx_data;
/* 创建信号量 */
data->xTxsem = xSemaphoreCreateBinary();
/* 创建队列 */
data->xRxQueue = xQueueCreate(UART_RX_QUEUE_LEN, 1);_hal_uart.h"
/* 启动第一次数据的接收 */
HAL_UART_Receive_IT(data->handle, &data->rxdata, 1);
return 0;
}
/
#include "FreeRTOS.h"
#include "semphr.h"
#inclu* 发送函数为函数指针类型 dde "queue.h"
#iatas:数据位,len:数据长度,timeout_ms:等待时间*/
static int stm32_uart_send(struct UART_Device *pDev, uint8_t *datas,int len, int timeonclude <string.h>
#define UARTut_ms)
{
struct UART_Data* data = pDev->priv_data;
/* 仅仅是触发中断而已 */
HAL_UART_Transmit_IT(data->handle, datas, len);_RX_QUEUE_LEN 100
extern UART_HandleTypeDef hu
/* 等待发送完毕:等待某个信号量(因为有freertos) */
art1;
/* 声明一下 */
struct UART_D if(pdTRUE == xSemaphoreTake(data->xTxsem, timeout_ms))
return 0;/* 成功 */
else
return -1;
}
/* 接收函数为函数指针类型 ,data:1字节,stop:停止位*/
static int stm32_uart_recv(struct UART_Device *pDev, uint8_t *data, int timeout_ms)
{
struct Uevice g_stm32_uart1;
/* 扩大私有数据ART_Data *uart_data = pDev->priv_data;
/* 读取队列得到数据,问题:谁写队列? */
struct UART中断:写队列 */
i_Data{
/* 串f(pdPASS == xQueueReceive(uart_data->xRxQueue, data, timeout_ms))
return 0;
else
retur口 */
UART_Hn -1;
}
static struct UART_Data g_stm32_uart1andleTypeDef *handle;
SemaphoreHandle_t xTx_data = {
&huart1,
};
static struct UART_Device g_stm32_uart1 = {
"stm32_uart1",/* 名字 */
stm32_uart_init,
ssem;
QueueHandle_t xRxQueuetm32_uart_send,
stm32_uart_recv,
&g_stm32_uart1_data,
};
/* 对外提供的接口函数 */
struct UART;
uint8_t rxdata;
};
void_Device *g_uart_devs\[\] = {&g_stm32_uart1};
struct UART_Device *GetUARTDevice(char *name)
{
int i = 0;
HAL_UART_TxCpl for(i=0; i<(sizeof(g_uart_devs) / sizeof(gtCallback(UART_H_uart_devs0)); i++)
{
if(0 == strcmp(name, g_uart_devsi->name))
andleTypeDef *huart)
{
struc return g_uart_devsi;
}
return NULL;
}
//struct UART_Device g_stm32_uart2 = {
// "stm32_uart2",/* 名字 */
t UART_Data *data;
/* 串口发送// stm32_uart_init,
// stm32_uart_send,
// stm32_uart_recv,
// &huart2,
//};
完成回调函数 */
i# 一. 结构体变量创建与赋值的区别
问题1:结构体带等号是创建吗?带等号和不带的区别?
*f(huart == &huart1)
{
*终极结论(必记)**
- 带 = :是 创建 + 赋值(初始化)
- 不带 = :只创建,不赋值(内容为空/垃圾值)
1. 带 = 的结构体 → 创 data = g_stm32_uart1.priv_dat建 + 赋值
例如:
struct UART_Da;
/* 释放信号量 */
xevice g_stm32_uart1 = { ... };
- 创建结构体变量 g_stm32_uart1
- 立刻赋值,填入名字、函数、SemaphoreGiveFromISR(data->xTx指针等内容
- 结果:结构体完整、可用
2. 不带 = 的结构体 → 只创建sem, NULL);
}
}
void HAL_U,不赋值
struct UART_Device g_stm32_uart1;
- 只创建一个空盒子-ART_RxCpltCallback(UART_HandleTypeDef *huart){struct UART_Data *data;没有赋值,内容为空或垃圾值
- 结果:成员均为空,不能使用
3. 最关键区别
| 写法 | 状态 /* 串口接收完成回调函数 | 能否用 |
|---- /if(huar---------------|--------------|--------|| 带 = | 创建+赋值 | ✅能用t == &huart1){dat || 不带 = a = g_stm32_uart1.priv_data;/ 写队列 */
| 只创建空壳 | ❌不能用 |
4. 比喻理解
- 带 =:买手机+装卡、软件 xQueueSendFromISR(data->→直接能用
- 不带 =:只买手机壳→不能用
5. 代码对应
struct UART_DxRxQueue, &data->rxdata, NULL);evice g_stm32_uart1 = {
"stm32_uart1",
stm32_uart_init,
stm32_uart_send,
stm32_uart_recv,
&g_stm32_uart
1_data
};
- 创建完整的uart1设备并赋 /* 再次启动数据的接收 */HAL_UART_Receive_IT(d值
- 所以能用
ata->handle, &da## 6. 总结
- 结构体ta->rxdata, 1);}}
/* 初始化带 = :创建+赋值→能用
- 结构体不带 = :只创建空壳→不能用
- 串口设备必须带 =函数为函数指针类型 bau:波 才能工作
<!-- 34 -特率,datas:数据位,pa->
二. 函数指针成员定义与赋值

rity:校验位,stop:停止位*/
static int s
问题2:结构体成员是函数指针,不赋值也可以吗?
100%明确结论tm32_uart_init(struct UART_Device *pDev, int bau, int datas,char parity, int stop)
{
/* 得:
❌ 错误!必须赋值,否则函数指针是野指针,一调用就死机!
1到私有数据 */
str. 定义与赋值的区别
- uct UART_Data data = pDev->priv定义**:只是声明结构体里有_data; / 创建信号量 */dat一个函数指针成员(坑位),没有a->xTxsem = xSemaphoreCreateBi内容
- 赋值:创建结构体变量时,将函数地址填入坑位,nary();/* 创建队列 */data指针才有效
示例
定义:
int (*init)(->xRxQueue = xQustruct UART_Device *pDev, ...);
- 只声明成员,没有eueCreate(UART_RX_QUEUE_LEN, 1);赋值
赋值:
static struct UART_Device g_stm32_uart1 = {
/* 启动
"stm32_uart1",
stm32_uart_init, // 赋第一次数据的接收 */
HAL_UART_Receiv值函数指针
stm32_uart_send,
stm32_uart_recv,
&g_stm32e_IT(data->handle, &data->rxdata, 1);
_uart1_data
};
return 0;
}
/```
- 赋值后,指针有效,能正常调用
2. 必须赋值原因
-
不赋* 发送函数为函数指针类型 datas:数据位,len:数据值,函数指针为NULL或垃圾值
-
调用时会触发硬件错误(HardFault)
长度,timeout_ms:等待时间*/
static int## 3. 比喻理解
-
结构体设计图:按钮位置(坑位),没有功能
-
创建变量+赋值:绑定电路,按钮能用
stm32_uart_send(struct UART_Device *pDev, uin 4. 总结
- 结构体成员定义只是坑位,必须赋值才可用
t8_t *datas,int len, int timeo- 不赋值的函数指针不能用,一ut_ms)
{
struct UART_Data* d调用就死机
5. 你的代码是正确的
```c
static struct UART_Device gata = pDev->priv_data;
/* 仅_stm32_uart1 = {
"stm32_uart1",
stm32_uart_init,
stm32_uart_send,
stm32_uart_r仅是触发中断而已 */
ecv,
&g_stm32_uart1_data
};
- 正确赋值,函数指 HAL_UART_Transmit_IT(data->hand针能用
三. 结构体类型与变量的le, datas, len);
/* 等待关系
问题3:定义结构发送完毕:等待某个信号量(因为体类型,然后创建结构体变量就是赋值吗?
100%正确!
##有freertos) */
- 三步彻底理解
- 定义结构体类型 :画图纸 if(pdTRUE == xSemaphoreTake(data->xTxsem, tim- 创建结构体变量:造实物
- 赋值:填充内容
示例
定义类型:
struct UART_Device {
char *eout_ms))
return 0;/* 成功 */
else
return -1name;
int (*init)(...);
int (*send)(...);
void *pr;
}
/* 接收函数为函数指iv_data;
};
- 图纸,不能用
创建变量针类型 ,data:1字节,st+赋值:
struct UART_Device g_stm32_uart1 = { ... };
op:停止位*/
static int stm32_uart_recv(struct UAR- 实物,填内容
2. 总结
- 结构体类型=图纸
- 结构体变量=实物
- = {...} = 给T_Device *pDev, uint8_t *data, int timeout_ms){struct U实物赋值
- 赋值后才能用
3. 你的理解ART_Data *uart_data = pDev->priv_data;
/* 读取完全正确!
四. 结构体指针数组的作用

问题4:结构体指针数组队列得到数据,问题:谁写队列?存放结构体变量的地址吗?
100%正确!
1. 代码拆中断:写队列 */
if(pdPASS == xQu解
static struct UART_Device g_stm32_uart1 = { ... };
strueueReceive(uartct UART_Device *g_uart_devs\[\] = {&g_stm32_uart1};
- g_s_data->xRxQueuetm32_uart1:结构体变量(实物)
- &g_stm32_uart1:变量的地址, data, timeout_ms))return 0;els
- g_uart_devs\[\]:指针数组,存放结构体变量ereturn -1;}
static struct UART_Data g_stm32_uart1的地址
2. 比喻理解
- 结构体变量=人
- &结构体变量=门牌号_data = {&huart1,};
stat- 指针数组=通讯录
- 通讯录里存的是门牌号(地址)
3. 为什么要这样ic struct UART_Device g_stm32_u写?
- 节省内存
- 方便art1 = {"stm32_uart1",/* 名统一管理和查找
- GetUARTDevice函数可遍历数组,通过名字查找设备
字 */
stm32_uart_init,
s
4. 总结
- 指针tm32_uart_send,stm32_uart_r数组存的是结构体变量的地址
- 方便统一管理多个设备
--ecv,&g_stm32_uart1_data,
5. 扩展
- 增加UART2,只需创建变量并加};
/* 对外提供的接口函数到数组即可
<!-- 37 - */
struct UART_Device *g_uart->
五. 字符串比较与设备查找

_devs\[\] = {&g_stm32_uart1};
st
问题5:解释`if(0 == strcmp(name, g_uart_devs[iruct UART_Device *GetUARTDevice(char *name)
{
]->name))`这句代码
核心:用`st int i = 0;
rcmp`判断两个字符串是否完全相等,返回0代表完全一样
-- for(i=0; i<(sizeof(g_uart_d-
1. strcmp函数规则
| 比较结果 evs) / sizeof(g_uart_devs0)) | 返回值 | 含义 |
|----------------------|----; i++)
{
if(0 == s----|--------------|
| str1 == str2 | 0 | 完全相等 |
| str1 < str2 | trcmp(name, g_uart_devsi->name))
return g_uart_devsi;
}负数 | 字典序较小 |
| str1 > str2 | 正数 | 字典序较大 |
return NULL;
}
//struct UART_Device g
2. 代码含义
- name:要查找的设备名字
- g_uart_devsi->name:当前设备的名字
- 0 == strcmp_stm32_uart2 = { // "stm32_uart2",/* 名字 */ (...):判断两者是否完全一致
3. 为// stm32_uart_init,
// stm32_uart_send,
什么不用==比较字符串?
- 字符串不能用==直接// stm32_uart_recv,// &比较内容
- 必须用strcmp函数
4. 防错写法(Yoda表达式huart2,
//};
``)
- 写成0 == strcmp(...),防止误写成`
一、问题1:结构赋值语句
5. 总结
- strcmp用于字符串内容比较
- 返回体带等号与不带等号的区别
##0时,名字完全匹配
6. 比喻
- 查花名册,名字完全对上才算找到
终极结论(务必牢记)
- **带 = :创建 + 赋值(初始
六. 结构体指针在其他文件的使用
问题6:在别的文件中定义指针变量,解释这句
100%正确!用的就是以前只创建,不赋值(内容为空或为垃圾创建的结构体类型
1. 代码拆解
1c
struct UART_Device *pUARTDev. 带 = 的结构体 → 创建;
struct UART_Device *pUARTDev = GetUARTDevice + 赋值
```c
stru("stm32_uart1");
- 第一行:定义一个指向UART设备的指针变量ct UART_Device g_stm32_uart1 = {
- 第二行:调用查找函数,获取设备指针并赋值
2. 原理
- 头文件里定义 ... };
**含义:**
-
创建一个结构体结构体和查找函数
-
包含头文件变量 `g_stm32_uar后,任何文件都能用同一套类型和接口
-
指针变量指向已创建的设备结构体
t1`
- 立刻赋值,把大括号里的内容全部填进去
**结果3. 执行流程
- 查找函数遍历数组,返回对应设备指针
-:**
- 这个结构体变量是完整的、可用的、有内容的
指针变量指向真实设备,可直接
2. 不带 = 的结构操作
4. 比喻
- 拿空手柄(指针变量),查通讯录(GetUARTDevi体 → 只创建,不赋值
``ce),绑定到真实遥控器(设备结构体)
5. 总结
- 指针变量指向已`c
struct UART_D创建设备
- 查找函数返回设备evice g_stm32_uart1;
**含义指针
- 赋值后可直接用指针操作:**
- 只创建一个空的结构设备
6. 写法区别
| 写法 | 状态 体变量
- 不赋值,内容全是空 ||------|----------|| 只定义(未初始化)
结果: | 空指针 |
| 定义+赋值 | 指向设备 |
七. 函数
- 结构体成员全是空值或垃圾指针调用中的"自己"传递

问题值,不能直接使用,否则可能崩溃7:调用时为什么要传pUARTDev自己进去?
3. 最关键区别
- 带 =:创建 + 赋值,能直接用
- **
终极答案:传pUARTDev就是C语言模拟面向对象的this指针,让函数知道操作哪个设备
1. 结构体函数指针定义
int (*send)(struct UART_Device *pDev, uint8_t *datas, int len, int tim不带 =**:只创建空壳,不能直接用
4.eout_ms);
- 第一个参数pDev:设备自身指针
2 最简单比喻
- **带 =. 调用时传递
pUARTDev->Send(pUARTDev, "100ask\r\n", 8, 100);
- pUARTDev:设备自身
- 让函数内部**:买了手机+装好卡和软件,直接能用
- **不带 =**能找到私有数据、硬件句柄
3. 必须传自己的原因
- 函数是公用的,不传:只买了空手机壳,不能用
5. 代码对应
struct UART_Device g_stm32自己就不知道操作哪个设备
-
通过`pDev->priv_data`找到硬件句柄
-
C语言_uart1 = {
"stm32_uart1",
函数必须靠参数传递设备信息
stm32_uart_init,
stm32_uar---
4. 比喻
- 遥控器开机按钮,必须递交遥t_send,
stm32_uart_recv,
控器本身,函数才能知道操作哪个设备
5. 总 &g_stm32_uart1_data
};
**结
- 传设备自身指针,函数这是创建一个完整的 uart1才能找到硬件
- 不传就无法操作 设备并赋值,所以能直接用。**
6. 总结具体设备
6
- 结构体带 = :创建. 补充
- 所有结构体方法 + 赋值 → 能用
- 结构都需传自己,实现面向对象
八. 驱动核心链路与私有数据
即使查找到了设备指针,也只是去操作那三个函数(发送、初始化、接收)。而这些函数内部通过结构体指针找到私有数据,私有数据里再指向`hu体不带 = :只创建空壳 → 不能用
- 串口设备,必须带 = 才能正常工作
二、问题2:函数指针成员不赋值可以用吗?

art1`的句柄,这样才能确定操作哪个串口。
结论
绝对不可以!函数指针成员如果不赋值,调用时会崩溃。
终极驱动链条
- 查找设备:GetUARTDevice("uart1")
→ 得到设备指针 pUART# 1. 定义 vs 赋值
- **定义**:只是声明结构Dev
- 调用函数:pUARTDev->send(pUARTDev, ...)
→ 把自己传给函数
- 函数内部:
pDev = 自己
→ pDev->pr体里有一个函数指针成员(只是预iv_data 找到私有数据
→ priv_data->留一个位置)
- **赋值**handle 找到 huart1
→ 操作硬件 UART1
总结
- 函数是公用的,指针是设备自己的,私有数据是专属的,句柄是硬件的
- 不传自己,函数就找不到私有数据,也就无法:在创建结构体变量时,必须把函数地址赋值进去,否则指针为NULL或野指针
操作具体串口
##2. 代码举例
定 你现在已经完全吃透驱动逻辑!
九义结构体类型(只是预留坑位)
int (*init. 内存级别对比:结构体带 =)(struct UART_Device *pDev, ... 与不带 =
1. 不带 =:只定义,不赋值
```c
struct UART_Device dev;
);
创建
内存状态:
dev
├───────────┤
│ name = NULL│ 变量并赋值(必须赋值)
static struct UART_Device g ← 空
├───────────┤
│ init = NULL│ ← 空
├───────────┤
│ send =_stm32_uart1 = { NULL│ ← 空
├───────────┤
│recv = NULL │ ← 空
├───────────┤
│priv_data=NULL│
"stm32_uart1",
stm32_u ← 空
└───────────┘
- 全部空指针/art_init, // 这里0,不能用
2. 带 =:定义+赋值(初始化)
struct UART_Device dev才是赋值
stm32_uart_send,
stm32_uart_recv,
= {
"uart1",
stm32_uart_init,
stm32_uart_send,
stm32_uart_recv,
&uart1_d &g_stm32_uart1_data
};
ata
};
内存状态:
dev├───────────────────┤│ name = "uart1" │ ← 有名字├───────────────────┤│ init = 0x08001234 │ ← 指向真实函数├────
3. 错误───────────────┤
│ send = 0x08005678 │ ← 指向真实函数
├───────────────────┤
│ rec示例(不赋值会崩溃)
struct UART_Dv = 0x08009ABC │ ← 指向真实函数
├───────────────────┤
│priv_data=&evice g_stm32_uart1 = {
"stuart1_data│ ← 指向私有数据
└───────────────────┘
- 所有成员都有有效值,可m32_uart1",直接用
3. 总结对比
| 方式 | 内存状态 NULL, // 没有赋值
// ...
};
g | 能否用 |
|-----------|-------------------|--------|
| 不带 = _stm32_uart1.ini | 空盒子,全是NULL | ❌不能用 |
| 带 = t(&g_stm32_uart | 内容完整可用 | ✅能用 |
1, ...); // 直接死机
4. 最直白总结
- 不带 =:只造盒子,不放东西 4. 总结
- 定义函数
- 带 =:造盒子+放满东西
你现在这个理解,已经完全吃透结构体了!
指针成员只是预留位置,不赋值不能用
-
创建结构体变量时必须赋值,否则调用会崩溃
-
正因为是函数指针,才必须赋值,绝不能省略
三、问题3:结构体定义、变量创建和赋值的关系
你的理解完全正确!
1. 定义结构体 = 画图纸
struct UART_Device {
char *name;
int (*init)(...);
int (*send)(...);
void *priv_data;
};
- 只是图纸,没有内容,不能用
2. 创建变量 + 赋值 = 造实物 + 赋值
struct UART_Device g_stm32_uart1 = { ... };
- 用图纸造实物
- = { ... } 就是赋值,填充内容
3. 赋值后才能用
= {
"stm32_uart1",
stm32_uart_init,
stm32_uart_send,
&g_stm32_uart1_data
};
- 所有成员都被赋值,结构体变量可以直接使用
总结
- 结构体 = 图纸
- 结构体变量 = 实物
- = {} = 给实物填内容(赋值)
四、问题4:结构体指针数组存放结构体地址

你的理解完全正确!
- 用已创建的结构体变量,创建一个指针数组,数组里存放的是结构体变量的地址
代码示例
static struct UART_Device g_stm32_uart1 = { ... };
struct UART_Device *g_uart_devs\[\] = { &g_stm32_uart1, };
- g_stm32_uart1 是结构体变量(实物)
- &g_stm32_uart1 是它的地址
- g_uart_devs\[\] 是结构体指针数组(通讯录),存放结构体变量的地址
优势
- 节省内存
- 操作方便
- 统一管理所有设备
总结
- 数组存的是结构体地址,便于统一管理和查找
五、问题5:if(0 == strcmp(name, g_uart_devsi->name)) 作用详解

作用
- 用 strcmp 比较两个字符串是否完全相等
- 返回值为 0 时,表示两个字符串完全一致
代码解释
if(0 == strcmp(name, g_uart_devsi->name))
- name:要查找的设备名
- g_uart_devsi->name:当前数组元素的设备名
- strcmp 返回 0 表示完全匹配,if 条件成立
为什么不用 ==?
- 字符串内容不能直接用 == 比较,只能用 strcmp
总结
- strcmp 返回 0 代表字符串完全相等
- 该语句用于查找名字匹配的设备
六、问题6:在其他文件中定义结构体指针并赋值

代码还原
struct UART_Device *pUARTDev;
struct UART_Device *pUARTDev = GetUARTDevice("stm32_uart1");
解释
- struct UART_Device *pUARTDev; 定义了一个指向 UART 设备的指针变量
- pUARTDev = GetUARTDevice("stm32_uart1"); 通过查找函数获取 UART1 设备的指针,并赋值给 pUARTDev
- 这样 pUARTDev 就指向了之前创建的 UART1 结构体
原理
- 头文件定义了结构体类型和查找函数
- 其他文件通过包含头文件,实现设备的查找和使用
总结
- 在其他文件中定义指针,通过查找函数获取设备地址,实现跨文件调用和操作
七、问题7:pUARTDev->Send(pUARTDev, "100ask\r\n", 8, 100); 的含义

终极答案
- 传递 pUARTDev 本身作为参数,就是让 send 函数知道当前要操作的设备是哪一个(相当于 C 语言的 this 指针)
详细解释
- pUARTDev->Send:调用结构体中的 send 函数指针
- 第一个参数 pUARTDev:传递自己,函数内部通过它找到对应的私有数据和硬件句柄
- 后续参数是发送内容、长度、超时时间
为什么要传自己?
- send 函数是通用的,必须靠 pUARTDev 确定具体操作哪个设备
- 内部通过 pUARTDev->priv_data->handle 找到对应硬件
总结
- 传自己是为了让函数明确操作对象,实现面向对象的效果
八、问题8:结构体指针、私有数据和硬件句柄的关系
你的理解已经完全正确!
执行链路
- 通过名字查找,获得 UART1 的指针 pUARTDev
- 调用 pUARTDev->init / send / recv 等函数
- 每次调用都传入 pUARTDev,函数内部通过 pUARTDev->priv_data 找到私有数据
- 私有数据的 handle 指向 huart1,最终操作具体硬件
总结
- 公共函数 + 结构体指针 + 私有数据 + 句柄,形成完整的驱动调用链路
- 不传自己,函数无法定位要操作哪个硬件
九、问题9:结构体带 = 和不带 = 的内存级别对比
1. 不带 =:只定义,不赋值
struct UART_Device dev;
内存状态:
dev
├─────────────┤
│ name = NULL │
├─────────────┤
│ init = NULL │
├─────────────┤
│ send = NULL │
├─────────────┤
│ recv = NULL │
├─────────────┤
│ priv_data=0 │
└─────────────┘
- 全是空指针/0,不能用
2. 带 =:定义 + 赋值(初始化)
struct UART_Device dev = {
"uart1",
stm32_uart_init,
stm32_uart_send,
stm32_uart_recv,
&uart1_data
};
内存状态:
dev
├────────────────────┤
│ name = "uart1" │
├────────────────────┤
│ init = 0x08001234 │
├────────────────────┤
│ send = 0x08005678 │
├────────────────────┤
│ recv = 0x08009ABC │
├────────────────────┤
│ priv_data=&uart1_data │
└────────────────────┘
- 所有成员都有有效值,可以直接用
3. 总结对比表
|------|-------------|-------|
| 方式 | 内存状态 | 能否使用 |
| 不带 = | 空盒子,全是 NULL | ❌ 不能用 |
| 带 = | 内容齐全,可用 | ✅ 能用 |
4. 终极总结
- 不带 =:只造盒子,不放东西
- 带 =:造盒子 + 放满内容