【0基础嵌入式学习日志】Day02:函数封装、结构体指针与传感器阈值判断
一、前言
今天继续进行嵌入式 C 语言基础学习。Day01 主要完成了最基础的 C 工程结构搭建,包括 include、src、build 目录划分,头文件定义,GCC 编译和 GitHub 上传。
Day02 的学习重点是进一步把代码写得更像真实工程,包括函数封装、结构体指针传参、传感器数据模拟、故障阈值判断以及 Makefile 自动编译。
本次练习的目标不是写复杂功能,而是理解嵌入式软件中常见的基本流程:
系统初始化 → 传感器数据更新 → 故障判断 → 状态输出
二、Day02 工程结构
本次 Day02 工程目录如下:
day02
├── Makefile
├── include
│ ├── fault_code.h
│ └── system_type.h
├── src
│ └── main.c
└── build
└── day02_test
其中:
include:用于存放头文件;
src:用于存放源文件;
build:用于存放编译生成的可执行文件;
Makefile:用于自动执行 GCC 编译命令;
README.md:用于记录项目说明。
相比 Day01,Day02 不再只是简单地把所有代码都写在 main() 函数中,而是将不同功能拆分为多个函数,使代码结构更加清晰。
三、系统状态结构体 system_type.h
首先在 day02/include/system_type.h 中定义系统状态结构体:
#ifndefSYSTEM_TYPE_H#defineSYSTEM_TYPE_Htypedefstruct{intled_state;floatvoltage;floatcurrent;floattemperature;unsignedintfault_code;}SystemStatus;#endif这里定义了一个结构体 SystemStatus,用于统一管理系统状态。
结构体中包含:
led_state:LED 状态;
voltage:传感器电压;
current:传感器电流;
temperature:传感器温度;
fault_code:系统故障码。
使用结构体的好处是可以将系统相关数据集中管理,而不是分散定义多个变量。例如:
SystemStatus sys;
这样 sys 就代表当前系统的整体状态。
四、故障码定义 fault_code.h
然后在 day02/include/fault_code.h 中定义故障码:
#ifndefFAULT_CODE_H#defineFAULT_CODE_H#defineFAULT_NONE0x0000#defineFAULT_LOW_VOLTAGE0x0001#defineFAULT_OVER_CURRENT0x0002#defineFAULT_OVER_TEMP0x0004#defineFAULT_SENSOR_ERROR0x0008#endif这些故障码采用宏定义的方式表示。
其中:
FAULT_NONE 表示无故障;
FAULT_LOW_VOLTAGE 表示低电压故障;
FAULT_OVER_CURRENT 表示过电流故障;
FAULT_OVER_TEMP 表示过温故障;
FAULT_SENSOR_ERROR 表示传感器异常故障。
这里使用 0x0001、0x0002、0x0004 这类数值,是为了通过不同 bit 位表示不同故障状态。这样一个 fault_code 变量就可以同时记录多个故障。
例如:
fault_code = 0x0001 | 0x0002 | 0x0004;
最终结果为:
fault_code = 0x0007
表示同时存在低电压、过电流和过温故障。
五、主程序 main.c
Day02 的主程序放在 day02/src/main.c 中。
首先引入头文件:
#include<stdio.h>#include"system_type.h"#include"fault_code.h"其中:
stdio.h 用于使用 printf() 输出信息;
system_type.h 用于使用 SystemStatus 结构体;
fault_code.h 用于使用故障码宏定义。
六、系统初始化函数
第一个函数是系统初始化函数:
voidsystem_init(SystemStatus*sys){sys->led_state=0;sys->voltage=12.5f;sys->current=1.2f;sys->temperature=35.6f;sys->fault_code=FAULT_NONE;}该函数的作用是给系统状态赋初始值。
这里使用了结构体指针:
SystemStatus *sys
在函数内部通过:
sys->voltage
访问结构体成员。
需要注意的是:
结构体变量访问成员:使用 .
结构体指针访问成员:使用 ->
例如:
sys.voltage
用于普通结构体变量;
sys->voltage
用于结构体指针。
七、传感器数据更新函数
第二个函数是传感器数据更新函数:
voidsensor_update(SystemStatus*sys){sys->voltage=9.5f;sys->current=2.5f;sys->temperature=72.0f;}该函数用于模拟传感器采集到的新数据。
这里故意设置了三个异常值:
电压 9.5V,低于阈值 10.0V;
电流 2.5A,高于阈值 2.0A;
温度 72.0℃,高于阈值 60.0℃。
这样后续故障检测函数就可以触发对应故障。
八、故障检测函数
第三个函数是故障检测函数:
voidfault_check(SystemStatus*sys){sys->fault_code=FAULT_NONE;if(sys->voltage<10.0f){sys->fault_code|=FAULT_LOW_VOLTAGE;}if(sys->current>2.0f){sys->fault_code|=FAULT_OVER_CURRENT;}if(sys->temperature>60.0f){sys->fault_code|=FAULT_OVER_TEMP;}}该函数主要完成三个判断:
电压 < 10.0V → 低电压故障
电流 > 2.0A → 过电流故障
温度 > 60.0℃ → 过温故障
其中这句代码比较关键:
sys->fault_code |= FAULT_LOW_VOLTAGE;
|= 表示在原有故障码基础上增加一个故障位。
这样做的好处是可以让一个 fault_code 同时保存多个故障状态,而不是只能表示单一故障。
九、系统状态打印函数
第四个函数是系统状态打印函数:
voidsystem_print(constSystemStatus*sys){printf("LED state: %d\n",sys->led_state);printf("Voltage: %.2f V\n",sys->voltage);printf("current: %.2f A\n",sys->current);printf("Temperature: %.2f C\n",sys->temperature);printf("Fault code: 0x%04X\n",sys->fault_code);if(sys->fault_code&FAULT_LOW_VOLTAGE){printf("Fault: Low voltage\n");}if(sys->fault_code&FAULT_OVER_CURRENT){printf("Fault: Over current\n");}if(sys->fault_code&FAULT_OVER_TEMP){printf("Fault: Over temperature\n");}if(sys->fault_code==FAULT_NONE){printf("System normal\n");}}该函数主要用于打印系统状态和具体故障信息。
这里使用:
sys->fault_code & FAULT_LOW_VOLTAGE
判断故障码中是否包含低电压故障。
需要注意:
|= 用于设置故障位
& 用于判断故障位
这两个符号是本次练习中非常重要的内容。
十、主函数流程
最后在 main() 函数中调用前面定义好的函数:
intmain(void){SystemStatus sys;system_init(&sys);sensor_update(&sys);fault_check(&sys);system_print(&sys);return0;}整体流程如下:
- 定义系统状态变量 sys
- system_init(&sys):初始化系统状态
- sensor_update(&sys):模拟传感器数据更新
- fault_check(&sys):根据阈值判断故障
- system_print(&sys):打印系统状态和故障信息
这里的 &sys 表示取 sys 的地址,并传给函数。
十一、GCC 编译方式
一开始使用 GCC 手动编译:
gcc day02/src/main.c -Iday02/include-Wall-Wextra-oday02/build/day02_test其中:
gcc:调用 GCC 编译器;
day02/src/main.c:指定源文件;
-Iday02/include:指定头文件目录;
-Wall:开启常见警告;
-Wextra:开启更多警告;
-o day02/build/day02_test:指定输出文件。
运行程序:
./day02/build/day02_test十二、Makefile 自动编译
手动输入 GCC 命令比较长,因此进一步学习了 Makefile。
day02/Makefile 内容如下:
CC=gcc CFLAGS=-Wall-Wextra-Iinclude TARGET=build/day02_test SRC=src/main.c all:$(CC)$(SRC)$(CFLAGS)-o $(TARGET)run:./$(TARGET)clean:rm-f $(TARGET)这里:
CC = gcc 表示使用 GCC 编译器;
CFLAGS 表示编译参数;
TARGET 表示生成的可执行文件;
SRC 表示源文件;
all 表示默认编译任务;
run 表示运行程序;
clean 表示清理编译结果。
进入 day02 目录后,可以直接执行:
make完成编译。
执行:
makerun运行程序。
执行:
makeclean删除编译生成的可执行文件。
十三、运行结果
程序运行结果如下:
LED state:0Voltage:9.50V current:2.50A Temperature:72.00C Fault code: 0x0007 Fault: Low voltage Fault: Over current Fault: Over temperature可以看到,程序成功检测到了三个故障:
低电压故障
过电流故障
过温故障
最终故障码为:
0x0007
其含义为:
0x0001 | 0x0002 | 0x0004 = 0x0007
说明多个故障可以通过一个变量同时表示。
十四、遇到的问题和解决方法
- 变量名拼写错误
编译过程中遇到过类似错误:
SystemStatus has no member named ‘failt_code’
原因是把:
fault_code
误写成了:
failt_code
C 语言中变量名必须完全一致,一个字母写错都会导致编译失败。修改拼写后,程序可以正常编译。
- Makefile 找不到
一开始在工程根目录直接执行:
make
出现错误:
make: *** No targets specified and no makefile found. Stop.
原因是 Makefile 放在 day02 文件夹中,而当时终端所在位置是工程根目录。
解决方法是先进入 day02 目录:
cd /root/Embedded_14Days/day02
然后再执行:
makemakerun最终成功完成自动编译和运行。
十六、项目源码
本次 Day02 学习代码已上传至 GitHub:
https://github.com/jdai10590-afk/Embedded-C-Learning-Projects/tree/main/day02