核心目标:掌握结构体(打包硬件相关数据 / 配置)和函数(复用驱动代码),完成 2 个嵌入式场景实操,理解两者在 “简化代码、统一管理” 中的核心作用 —— 这是后续写外设驱动、系统初始化的基础。
一、结构体:嵌入式中 “打包相关数据” 的工具(50 分钟)
嵌入式开发中,硬件的 “配置参数”“采集数据” 往往是相关联的(比如传感器的采样率、地址、采集值),结构体就是把这些 “相关数据” 打包成一个整体,避免变量混乱。
1. 结构体的定义与命名(嵌入式习惯)
- 定义格式:
struct 结构体名 { 成员1类型 成员1名; 成员2类型 成员2名; ... }; - 嵌入式命名习惯:结构体名首字母大写,成员名延续 “见名知意” 缩写(比如传感器配置结构体叫
TempSensorCfg,Cfg是Configuration的缩写)。 - 示例(温度传感器配置 + 数据结构体):
c
// 定义“温度传感器”结构体:打包配置参数和采集数据 struct TempSensorCfg { int cfg_samplerate; // 采样率(cfg=配置,samplerate=采样率) int cfg_addr; // 传感器I2C地址(addr=地址) int raw_data; // 单次采集原始数据 float real_data; // 单次校准后温度 };
2. 结构体的使用(3 个核心步骤)
- 声明结构体变量:
struct TempSensorCfg sensor1;(创建一个名为 sensor1 的传感器实例) - 给成员赋值(2 种方式):
- 逐个赋值:
sensor1.cfg_samplerate = 10;(采样率 10 次 / 秒)、sensor1.cfg_addr = 0x48;(I2C 地址 0x48) - 初始化赋值:
struct TempSensorCfg sensor1 = {10, 0x48, 256, 27.6};(按成员顺序赋值)
- 逐个赋值:
- 访问成员:用
.运算符,sensor1.raw_data就是访问 sensor1 的原始数据成员。
3. 嵌入式场景意义
- 替代多个独立变量:比如不用定义
samplerate1、addr1、raw1、real1,用一个结构体sensor1统一管理,代码更清晰。 - 方便批量处理:如果有多个传感器(sensor1、sensor2),每个都包含完整配置和数据,后续用数组或指针管理更高效。
二、实操 1:用结构体管理传感器数据(30 分钟)
模拟 “单个温度传感器” 的配置、数据采集和校准,用结构体打包所有相关信息:
c
#include <stdio.h>
// 定义温度传感器结构体(打包配置+数据)
struct TempSensorCfg {
int cfg_samplerate; // 采样率(次/秒)
int cfg_addr; // I2C地址(嵌入式中传感器地址常用十六进制)
int raw_data; // 原始采样值
float real_data; // 校准后温度
};
int main(void)
{
// 声明并初始化结构体变量(传感器1)
struct TempSensorCfg sensor1 = {
.cfg_samplerate = 10, // 显式赋值(推荐,顺序可打乱)
.cfg_addr = 0x48,
.raw_data = 259
};
// 计算校准后温度(沿用之前的公式)
sensor1.real_data = (sensor1.raw_data / 10.0) + 2;
// 打印传感器信息(嵌入式中可通过串口输出)
printf("传感器I2C地址:0x%X\n", sensor1.cfg_addr); // 0x%X以十六进制显示地址
printf("采样率:%d次/秒\n", sensor1.cfg_samplerate);
printf("原始数据:%d\n", sensor1.raw_data);
printf("校准后温度:%.1f℃\n", sensor1.real_data);
while(1);
return 0;
}
- 运行结果:会依次输出传感器的地址、采样率、原始数据和校准温度。
- 重点:
.cfg_samplerate = 10是嵌入式常用的 “显式初始化”,即使成员顺序变了,赋值也不会错,比按顺序赋值更稳健。
三、函数:嵌入式中 “复用代码” 的核心(50 分钟)
嵌入式开发中,很多逻辑会重复使用(比如温度校准、传感器初始化),把这些逻辑写成函数,每次用的时候直接调用,不用重复写代码 —— 既节省内存,又方便修改(比如校准公式变了,只改函数即可)。
1. 函数的定义与嵌入式常用格式
- 定义格式:
返回值类型 函数名(参数类型 参数名) { 函数体; return 返回值; } - 嵌入式核心原则:
- 函数名用 “动词 + 名词”(比如
calc_real_temp:计算真实温度),见名知意。 - 优先用 “指针参数” 传递大数据(比如结构体),避免拷贝浪费内存。
- 避免复杂逻辑:函数内部尽量简洁,一个函数只做一件事(比如校准函数只负责计算,不负责打印)。
- 函数名用 “动词 + 名词”(比如
2. 嵌入式常用函数类型(2 种核心)
- 有参数、有返回值(比如温度校准函数):
c
// 功能:输入原始数据,返回校准后温度 float calc_real_temp(int raw) { return (raw / 10.0) + 2; // 校准逻辑封装 } - 指针参数(传递结构体,修改内部数据):
c
// 功能:初始化传感器配置(通过指针修改结构体成员) void init_sensor(struct TempSensorCfg *sensor) { sensor->cfg_samplerate = 10; // 指针访问结构体成员用“->”,而非“.” sensor->cfg_addr = 0x48; sensor->raw_data = 0; // 初始化原始数据为0 }
- 关键细节:指针访问结构体成员时,用
->替代.(比如sensor->cfg_addr等价于(*sensor).cfg_addr),是嵌入式高频写法。
四、实操 2:用函数封装传感器初始化 + 温度校准(40 分钟)
把实操 1 的逻辑拆分成 “初始化函数” 和 “校准函数”,模拟嵌入式中 “代码复用” 的场景:
c
#include <stdio.h>
// 1. 定义结构体
struct TempSensorCfg {
int cfg_samplerate;
int cfg_addr;
int raw_data;
float real_data;
};
// 2. 函数1:初始化传感器(指针参数,修改结构体)
void init_sensor(struct TempSensorCfg *sensor) {
sensor->cfg_samplerate = 10;
sensor->cfg_addr = 0x48;
sensor->raw_data = 0; // 初始化为0,后续由硬件采集填充
}
// 3. 函数2:校准温度(有参数、有返回值)
float calc_real_temp(int raw) {
return (raw / 10.0) + 2; // 校准逻辑封装,后续修改只需改这里
}
int main(void)
{
struct TempSensorCfg sensor1;
// 调用初始化函数:传入结构体地址(&sensor1)
init_sensor(&sensor1);
// 模拟硬件采集数据(实际开发中由ADC接口读取)
sensor1.raw_data = 262;
// 调用校准函数:传入原始数据,返回校准后温度
sensor1.real_data = calc_real_temp(sensor1.raw_data);
// 打印结果
printf("传感器初始化完成,地址:0x%X\n", sensor1.cfg_addr);
printf("采集原始数据:%d\n", sensor1.raw_data);
printf("校准后温度:%.1f℃\n", sensor1.real_data);
// 复用函数:如果有第二个传感器,直接调用即可
struct TempSensorCfg sensor2;
init_sensor(&sensor2);
sensor2.raw_data = 253;
sensor2.real_data = calc_real_temp(sensor2.raw_data);
printf("\n传感器2校准后温度:%.1f℃\n", sensor2.real_data);
while(1);
return 0;
}
- 运行结果:会输出 sensor1 和 sensor2 的校准温度,两个传感器共用一套初始化和校准逻辑,体现函数复用价值。
- 重点:
init_sensor(&sensor1)传入的是结构体地址,函数内部通过指针修改成员 —— 嵌入式中传递结构体时,优先用指针(避免拷贝整个结构体,节省内存)。
五、第三天必掌握的 3 个关键知识点(10 分钟自测)
- 结构体的核心作用是 “打包相关数据”,访问成员用
.(普通变量)或->(指针变量)。 - 函数的核心是 “代码复用”,嵌入式中优先用 “指针参数” 传递结构体等大数据。
- 嵌入式函数命名原则:“动词 + 名词”,一个函数只做一件事(比如初始化函数只负责赋值,校准函数只负责计算)。


被折叠的 条评论
为什么被折叠?



