STM32-UART抽象层封装调试

头文件添加与类型说明

为了解决 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. 带 = :是 创建 + 赋值(初始化)
  2. 不带 = :只创建,不赋值(内容为空/垃圾值)

1. 带 = 的结构体 → 创 data = g_stm32_uart1.priv_dat建 + 赋值

例如:

struct UART_Da;

/* 释放信号量 */

xevice g_stm32_uart1 = { ... };

  1. 创建结构体变量 g_stm32_uart1
  2. 立刻赋值,填入名字、函数、SemaphoreGiveFromISR(data->xTx指针等内容
  3. 结果:结构体完整、可用

2. 不带 = 的结构体 → 只创建sem, NULL);

}

}

void HAL_U,不赋值

struct UART_Device g_stm32_uart1;

  1. 只创建一个空盒子-ART_RxCpltCallback(UART_HandleTypeDef *huart){struct UART_Data *data;没有赋值,内容为空或垃圾值
  2. 结果:成员均为空,不能使用

3. 最关键区别

| 写法 | 状态 /* 串口接收完成回调函数 | 能否用 |

|---- /if(huar---------------|--------------|--------|| 带 = | 创建+赋值 | ✅能用t == &huart1){dat || 不带 = a = g_stm32_uart1.priv_data;/ 写队列 */

| 只创建空壳 | ❌不能用 |

4. 比喻理解

  1. 带 =:买手机+装卡、软件 xQueueSendFromISR(data->→直接能用
  2. 不带 =:只买手机壳→不能用

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

};

  1. 创建完整的uart1设备并赋 /* 再次启动数据的接收 */HAL_UART_Receive_IT(d值
  2. 所以能用

ata->handle, &da## 6. 总结

  1. 结构体ta->rxdata, 1);}}

/* 初始化带 = :创建+赋值→能用

  1. 结构体不带 = :只创建空壳→不能用
  2. 串口设备必须带 =函数为函数指针类型 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. 定义与赋值的区别

  1. uct UART_Data data = pDev->priv定义**:只是声明结构体里有_data; / 创建信号量 */dat一个函数指针成员(坑位),没有a->xTxsem = xSemaphoreCreateBi内容
  2. 赋值:创建结构体变量时,将函数地址填入坑位,nary();/* 创建队列 */data指针才有效

示例

定义:

int (*init)(->xRxQueue = xQustruct UART_Device *pDev, ...);

  1. 只声明成员,没有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

};

  1. 正确赋值,函数指 HAL_UART_Transmit_IT(data->hand针能用

三. 结构体类型与变量的le, datas, len);

/* 等待关系

问题3:定义结构发送完毕:等待某个信号量(因为体类型,然后创建结构体变量就是赋值吗?

100%正确!

##有freertos) */

  1. 三步彻底理解
  1. 定义结构体类型 :画图纸 if(pdTRUE == xSemaphoreTake(data->xTxsem, tim- 创建结构体变量:造实物
  2. 赋值:填充内容

示例

定义类型:

struct UART_Device {

char *eout_ms))

return 0;/* 成功 */

else

return -1name;

int (*init)(...);

int (*send)(...);

void *pr;

}

/* 接收函数为函数指iv_data;

};

  1. 图纸,不能用

创建变量针类型 ,data:1字节,st+赋值:

struct UART_Device g_stm32_uart1 = { ... };

op:停止位*/

static int stm32_uart_recv(struct UAR- 实物,填内容

2. 总结

  1. 结构体类型=图纸
  2. 结构体变量=实物
  3. = {...} = 给T_Device *pDev, uint8_t *data, int timeout_ms){struct U实物赋值
  4. 赋值后才能用

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

  1. g_s_data->xRxQueuetm32_uart1:结构体变量(实物)
  2. &g_stm32_uart1:变量的地址, data, timeout_ms))return 0;els
  3. g_uart_devs\[\]:指针数组,存放结构体变量ereturn -1;}

static struct UART_Data g_stm32_uart1的地址

2. 比喻理解

  1. 结构体变量=人
  2. &结构体变量=门牌号_data = {&huart1,};

stat- 指针数组=通讯录

  1. 通讯录里存的是门牌号(地址)

3. 为什么要这样ic struct UART_Device g_stm32_u写?

  1. 节省内存
  2. 方便art1 = {"stm32_uart1",/* 名统一管理和查找
  3. GetUARTDevice函数可遍历数组,通过名字查找设备

字 */

stm32_uart_init,

s

4. 总结

  1. 指针tm32_uart_send,stm32_uart_r数组存的是结构体变量的地址
  2. 方便统一管理多个设备

--ecv,&g_stm32_uart1_data,

5. 扩展

  1. 增加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. 代码含义

  1. name:要查找的设备名字
  2. g_uart_devsi->name:当前设备的名字
  3. 0 == strcmp_stm32_uart2 = { // "stm32_uart2",/* 名字 */ (...):判断两者是否完全一致

3. 为// stm32_uart_init,

// stm32_uart_send,

什么不用==比较字符串?

  1. 字符串不能用==直接// stm32_uart_recv,// &比较内容
  2. 必须用strcmp函数

4. 防错写法(Yoda表达式huart2,

//};

``)

  1. 写成0 == strcmp(...),防止误写成`

一、问题1:结构赋值语句

5. 总结

  1. strcmp用于字符串内容比较
  2. 返回体带等号与不带等号的区别

##0时,名字完全匹配

6. 比喻

  1. 查花名册,名字完全对上才算找到

终极结论(务必牢记)

  1. **带 = :创建 + 赋值(初始

六. 结构体指针在其他文件的使用

问题6:在别的文件中定义指针变量,解释这句

100%正确!用的就是以前只创建,不赋值(内容为空或为垃圾创建的结构体类型

1. 代码拆解


1c

struct UART_Device *pUARTDev. 带 = 的结构体 → 创建;

struct UART_Device *pUARTDev = GetUARTDevice + 赋值

```c

stru("stm32_uart1");

  1. 第一行:定义一个指向UART设备的指针变量ct UART_Device g_stm32_uart1 = {
  2. 第二行:调用查找函数,获取设备指针并赋值

2. 原理

  1. 头文件里定义 ... };

**含义:**

  • 创建一个结构体结构体和查找函数

  • 包含头文件变量 `g_stm32_uar后,任何文件都能用同一套类型和接口

  • 指针变量指向已创建的设备结构体


t1`

  • 立刻赋值,把大括号里的内容全部填进去

**结果3. 执行流程

  • 查找函数遍历数组,返回对应设备指针

-:**

  • 这个结构体变量是完整的、可用的、有内容的

指针变量指向真实设备,可直接

2. 不带 = 的结构操作


4. 比喻

  • 拿空手柄(指针变量),查通讯录(GetUARTDevi体 → 只创建,不赋值

``ce),绑定到真实遥控器(设备结构体)


5. 总结

  • 指针变量指向已`c

struct UART_D创建设备

  • 查找函数返回设备evice g_stm32_uart1;

**含义指针

  1. 赋值后可直接用指针操作:**
  2. 只创建一个空的结构设备

6. 写法区别

| 写法 | 状态 体变量

  1. 不赋值,内容全是空 ||------|----------|| 只定义(未初始化)

结果: | 空指针 |

| 定义+赋值 | 指向设备 |

七. 函数

  1. 结构体成员全是空值或垃圾指针调用中的"自己"传递

问题值,不能直接使用,否则可能崩溃7:调用时为什么要传pUARTDev自己进去?

3. 最关键区别

  1. 带 =:创建 + 赋值,能直接用
  2. **

终极答案:传pUARTDev就是C语言模拟面向对象的this指针,让函数知道操作哪个设备

1. 结构体函数指针定义

int (*send)(struct UART_Device *pDev, uint8_t *datas, int len, int tim不带 =**:只创建空壳,不能直接用


4.eout_ms);

  1. 第一个参数pDev:设备自身指针

2 最简单比喻

  1. **带 =. 调用时传递

pUARTDev->Send(pUARTDev, "100ask\r\n", 8, 100);

  1. pUARTDev:设备自身
  2. 让函数内部**:买了手机+装好卡和软件,直接能用
  3. **不带 =**能找到私有数据、硬件句柄

3. 必须传自己的原因

  1. 函数是公用的,不传:只买了空手机壳,不能用

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

};

**结

  1. 传设备自身指针,函数这是创建一个完整的 uart1才能找到硬件
  2. 不传就无法操作 设备并赋值,所以能直接用。**

6. 总结具体设备

6

  1. 结构体带 = :创建. 补充
  1. 所有结构体方法 + 赋值 → 能用
  1. 结构都需传自己,实现面向对象

八. 驱动核心链路与私有数据

即使查找到了设备指针,也只是去操作那三个函数(发送、初始化、接收)。而这些函数内部通过结构体指针找到私有数据,私有数据里再指向`hu体不带 = :只创建空壳 → 不能用

  1. 串口设备,必须带 = 才能正常工作

二、问题2:函数指针成员不赋值可以用吗?

art1`的句柄,这样才能确定操作哪个串口。

结论

绝对不可以!函数指针成员如果不赋值,调用时会崩溃。

终极驱动链条

  1. 查找设备:GetUARTDevice("uart1")

→ 得到设备指针 pUART# 1. 定义 vs 赋值

  • **定义**:只是声明结构Dev
  1. 调用函数:pUARTDev->send(pUARTDev, ...)

→ 把自己传给函数

  1. 函数内部:

pDev = 自己

→ pDev->pr体里有一个函数指针成员(只是预iv_data 找到私有数据

→ priv_data->留一个位置)

  • **赋值**handle 找到 huart1

→ 操作硬件 UART1

总结

  1. 函数是公用的,指针是设备自己的,私有数据是专属的,句柄是硬件的
  2. 不传自己,函数就找不到私有数据,也就无法:在创建结构体变量时,必须把函数地址赋值进去,否则指针为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 ← 空

└───────────┘

  1. 全部空指针/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│ ← 指向私有数据

└───────────────────┘

  1. 所有成员都有有效值,可m32_uart1",直接用

3. 总结对比

| 方式 | 内存状态 NULL, // 没有赋值

// ...

};

g | 能否用 |

|-----------|-------------------|--------|

| 不带 = _stm32_uart1.ini | 空盒子,全是NULL | ❌不能用 |

| 带 = t(&g_stm32_uart | 内容完整可用 | ✅能用 |

1, ...); // 直接死机


4. 最直白总结

  • 不带 =:只造盒子,不放东西 4. 总结
  1. 定义函数
  • 带 =:造盒子+放满东西

你现在这个理解,已经完全吃透结构体了!

指针成员只是预留位置,不赋值不能用

  1. 创建结构体变量时必须赋值,否则调用会崩溃

  2. 正因为是函数指针,才必须赋值,绝不能省略

三、问题3:结构体定义、变量创建和赋值的关系

你的理解完全正确!

1. 定义结构体 = 画图纸

struct UART_Device {

char *name;

int (*init)(...);

int (*send)(...);

void *priv_data;

};

  1. 只是图纸,没有内容,不能用

2. 创建变量 + 赋值 = 造实物 + 赋值

struct UART_Device g_stm32_uart1 = { ... };

  1. 用图纸造实物
  2. = { ... } 就是赋值,填充内容

3. 赋值后才能用

= {

"stm32_uart1",

stm32_uart_init,

stm32_uart_send,

&g_stm32_uart1_data

};

  1. 所有成员都被赋值,结构体变量可以直接使用

总结

  1. 结构体 = 图纸
  2. 结构体变量 = 实物
  3. = {} = 给实物填内容(赋值)

四、问题4:结构体指针数组存放结构体地址

你的理解完全正确!

  1. 用已创建的结构体变量,创建一个指针数组,数组里存放的是结构体变量的地址

代码示例

static struct UART_Device g_stm32_uart1 = { ... };

struct UART_Device *g_uart_devs\[\] = { &g_stm32_uart1, };

  1. g_stm32_uart1 是结构体变量(实物)
  2. &g_stm32_uart1 是它的地址
  3. g_uart_devs\[\] 是结构体指针数组(通讯录),存放结构体变量的地址

优势

  1. 节省内存
  2. 操作方便
  3. 统一管理所有设备

总结

  1. 数组存的是结构体地址,便于统一管理和查找

五、问题5:if(0 == strcmp(name, g_uart_devsi->name)) 作用详解

作用

  1. 用 strcmp 比较两个字符串是否完全相等
  2. 返回值为 0 时,表示两个字符串完全一致

代码解释

if(0 == strcmp(name, g_uart_devsi->name))

  1. name:要查找的设备名
  2. g_uart_devsi->name:当前数组元素的设备名
  3. strcmp 返回 0 表示完全匹配,if 条件成立

为什么不用 ==?

  1. 字符串内容不能直接用 == 比较,只能用 strcmp

总结

  1. strcmp 返回 0 代表字符串完全相等
  2. 该语句用于查找名字匹配的设备

六、问题6:在其他文件中定义结构体指针并赋值

代码还原

struct UART_Device *pUARTDev;

struct UART_Device *pUARTDev = GetUARTDevice("stm32_uart1");

解释

  1. struct UART_Device *pUARTDev; 定义了一个指向 UART 设备的指针变量
  2. pUARTDev = GetUARTDevice("stm32_uart1"); 通过查找函数获取 UART1 设备的指针,并赋值给 pUARTDev
  3. 这样 pUARTDev 就指向了之前创建的 UART1 结构体

原理

  1. 头文件定义了结构体类型和查找函数
  2. 其他文件通过包含头文件,实现设备的查找和使用

总结

  1. 在其他文件中定义指针,通过查找函数获取设备地址,实现跨文件调用和操作

七、问题7:pUARTDev->Send(pUARTDev, "100ask\r\n", 8, 100); 的含义

终极答案

  1. 传递 pUARTDev 本身作为参数,就是让 send 函数知道当前要操作的设备是哪一个(相当于 C 语言的 this 指针)

详细解释

  1. pUARTDev->Send:调用结构体中的 send 函数指针
  2. 第一个参数 pUARTDev:传递自己,函数内部通过它找到对应的私有数据和硬件句柄
  3. 后续参数是发送内容、长度、超时时间

为什么要传自己?

  1. send 函数是通用的,必须靠 pUARTDev 确定具体操作哪个设备
  2. 内部通过 pUARTDev->priv_data->handle 找到对应硬件

总结

  1. 传自己是为了让函数明确操作对象,实现面向对象的效果

八、问题8:结构体指针、私有数据和硬件句柄的关系

你的理解已经完全正确!

执行链路

  1. 通过名字查找,获得 UART1 的指针 pUARTDev
  2. 调用 pUARTDev->init / send / recv 等函数
  3. 每次调用都传入 pUARTDev,函数内部通过 pUARTDev->priv_data 找到私有数据
  4. 私有数据的 handle 指向 huart1,最终操作具体硬件

总结

  1. 公共函数 + 结构体指针 + 私有数据 + 句柄,形成完整的驱动调用链路
  2. 不传自己,函数无法定位要操作哪个硬件

九、问题9:结构体带 = 和不带 = 的内存级别对比

1. 不带 =:只定义,不赋值

struct UART_Device dev;

内存状态:

dev

├─────────────┤

│ name = NULL │

├─────────────┤

│ init = NULL │

├─────────────┤

│ send = NULL │

├─────────────┤

│ recv = NULL │

├─────────────┤

│ priv_data=0 │

└─────────────┘

  1. 全是空指针/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 │

└────────────────────┘

  1. 所有成员都有有效值,可以直接用

3. 总结对比表

|------|-------------|-------|
| 方式 | 内存状态 | 能否使用 |
| 不带 = | 空盒子,全是 NULL | ❌ 不能用 |
| 带 = | 内容齐全,可用 | ✅ 能用 |

4. 终极总结

  1. 不带 =:只造盒子,不放东西
  2. 带 =:造盒子 + 放满内容
相关推荐
xian_wwq6 小时前
【学习笔记】「大模型安全:攻击面演化史」第 07 篇-安全左移
人工智能·笔记·学习
秋雨梧桐叶落莳6 小时前
iOS——NSUserDefaults学习
学习·macos·ios·objective-c·cocoa
易小染7 小时前
AI-Agent学习-LangChain-01
学习·langchain
ACP广源盛139246256737 小时前
GSV2221 显示转换芯片@ACP#赋能 RTX Spark 端侧 AI 设备,构建多屏全模态视觉交互新生态
大数据·人工智能·嵌入式硬件·gpt·spark·电脑·音视频
Szime8 小时前
TJA1044T/1现货查询与汽车CAN通信应用采购注意事项
嵌入式硬件·汽车
rhythm-ring8 小时前
《汽车智能高边开关PROFET:电流检测与标定实战》
嵌入式硬件·汽车
nnsix8 小时前
Unity 贴图压缩格式 笔记
笔记·unity·贴图
xian_wwq9 小时前
【学习笔记】「大模型安全:攻击面演化史」第 03 篇-数据投毒
笔记·学习·ai安全
sheeta19989 小时前
LeetCode 每日一题笔记 日期:2026.06.06 题目:2196. 根据描述创建二叉树
笔记·算法·leetcode
.千余10 小时前
【C++】手写双向链表:list容器模拟实现
开发语言·c++·笔记·学习·其他