【0基础嵌入式学习日志】Day09:枚举 enum、系统工作模式与 switch-case 状态打印
一、前言
今天继续进行嵌入式 C 语言基础学习。
前面几天已经学习了 C 工程结构、多文件拆分、Makefile 自动化编译、头文件依赖追踪、DEBUG 调试日志以及 config.h 参数集中管理。
Day01:基础 C 工程结构
Day02:函数封装、结构体指针、故障判断
Day03:多文件拆分,.h / .c 配合
Day04:.c → .o → 可执行文件
Day05:Makefile 自动化、模式规则、增量编译
Day06:.d 依赖文件,头文件依赖追踪
Day07:条件编译、DEBUG 开关与日志打印模块
Day08:config.h 配置头文件、阈值宏管理与消除魔法数字
Day09 的重点是学习 枚举 enum,并给系统增加一个新的状态字段:
SystemMode mode;
这个字段用于表示系统当前处于哪种工作模式,例如:
初始化模式
正常运行模式
故障模式
在嵌入式项目中,系统状态管理非常常见。比如一个设备可能处于初始化、运行、待机、故障、低功耗等不同状态。相比直接使用 0、1、2 这样的数字,用 enum 可以让代码更加清晰。
二、Day09 学习目标
本次 Day09 主要学习以下内容:
- 理解
enum枚举的基本作用; - 学会用枚举表示系统固定状态;
- 在
SystemStatus结构体中新增系统工作模式字段; - 在系统初始化时设置默认模式;
- 根据故障码切换
RUN和FAULT模式; - 使用
switch-case打印不同系统模式; - 验证
FAULT和RUN两种状态是否正确切换。
三、Day09 工程结构
Day09 是在 Day08 基础上继续修改得到的。
工程结构如下:
day09
├── Makefile
├── include
│ ├── config.h
│ ├── debug.h
│ ├── fault_code.h
│ ├── fault.h
│ ├── sensor.h
│ ├── system.h
│ └── system_type.h
├── src
│ ├── fault.c
│ ├── main.c
│ ├── sensor.c
│ └── system.c
└── build
└── day09_test
其中:
system_type.h:定义系统状态结构体和系统模式枚举;system.c:系统初始化和状态打印;fault.c:故障检测和系统模式切换;config.h:保存传感器模拟值和故障阈值;Makefile:自动编译和运行工程。
四、为什么需要 enum?
如果不用枚举,系统模式可能会写成这样:
sys->mode = 0;
sys->mode = 1;
sys->mode = 2;
但是问题是,别人看到 0、1、2 时,并不知道它们分别代表什么。
例如:
0 是初始化?
1 是运行?
2 是故障?
这种代码可读性比较差。
使用 enum 后,可以写成:
sys->mode = SYS_MODE_INIT;
sys->mode = SYS_MODE_RUN;
sys->mode = SYS_MODE_FAULT;
这样代码含义就很清楚:
SYS_MODE_INIT 初始化模式
SYS_MODE_RUN 正常运行模式
SYS_MODE_FAULT 故障模式
所以,enum 的作用可以简单理解为:
用有意义的名字表示一组固定状态。
五、定义系统工作模式枚举
在 day09/include/system_type.h 中新增 SystemMode 枚举类型:
#ifndef SYSTEM_TYPE_H
#define SYSTEM_TYPE_H
typedef enum
{
SYS_MODE_INIT = 0,
SYS_MODE_RUN,
SYS_MODE_FAULT
} SystemMode;
typedef struct
{
int led_state;
SystemMode mode;
float voltage;
float current;
float temperature;
unsigned int fault_code;
} SystemStatus;
#endif
其中:
typedef enum
{
SYS_MODE_INIT = 0,
SYS_MODE_RUN,
SYS_MODE_FAULT
} SystemMode;
定义了一个新的类型:
SystemMode
它包含三个系统模式:
SYS_MODE_INIT 初始化模式
SYS_MODE_RUN 正常运行模式
SYS_MODE_FAULT 故障模式
这里手动给 SYS_MODE_INIT 赋值为 0:
SYS_MODE_INIT = 0
后面的枚举值如果没有手动赋值,会自动递增:
SYS_MODE_INIT = 0
SYS_MODE_RUN = 1
SYS_MODE_FAULT = 2
虽然它们本质上还是整数,但在代码中使用名字会比直接写数字更清楚。
六、SystemStatus 结构体新增 mode 字段
Day08 的 SystemStatus 结构体中已经有:
int led_state;
float voltage;
float current;
float temperature;
unsigned int fault_code;
这些字段用于保存 LED 状态、电压、电流、温度和故障码。
Day09 新增了一个字段:
SystemMode mode;
完整结构体如下:
typedef struct
{
int led_state;
SystemMode mode;
float voltage;
float current;
float temperature;
unsigned int fault_code;
} SystemStatus;
这个字段的作用是保存当前系统模式。
以后可以通过:
sys->mode = SYS_MODE_INIT;
表示系统处于初始化模式。
也可以通过:
sys->mode = SYS_MODE_FAULT;
表示系统处于故障模式。
七、系统初始化时设置 INIT 模式
在 day09/src/system.c 的 system_init() 函数中,需要初始化系统模式。
修改后的代码如下:
void system_init(SystemStatus *sys)
{
sys->led_state = SYS_INIT_LED_STATE;
sys->mode = SYS_MODE_INIT;
sys->voltage = SYS_INIT_VOLTAGE;
sys->current = SYS_INIT_CURRENT;
sys->temperature = SYS_INIT_TEMPERATURE;
sys->fault_code = FAULT_NONE;
DEBUG_PRINT("system init done\n");
}
新增的是这一句:
sys->mode = SYS_MODE_INIT;
意思是:
系统刚初始化时,默认处于初始化模式。
这里不要直接写:
sys->mode = 0;
虽然 SYS_MODE_INIT 的值就是 0,但是写成 SYS_MODE_INIT 更容易理解。
八、根据故障码切换系统模式
系统初始化后,传感器数据会更新,然后进入故障检测。
在 day09/src/fault.c 中,根据 fault_code 判断系统是正常运行还是故障状态:
void fault_check(SystemStatus *sys)
{
sys->fault_code = FAULT_NONE;
if(sys->voltage < FAULT_LOW_VOLTAGE_TH)
{
sys->fault_code |= FAULT_LOW_VOLTAGE;
}
if(sys->current > FAULT_OVER_CURRENT_TH)
{
sys->fault_code |= FAULT_OVER_CURRENT;
}
if(sys->temperature > FAULT_OVER_TEMP_TH)
{
sys->fault_code |= FAULT_OVER_TEMP;
}
if(sys->fault_code == FAULT_NONE)
{
sys->mode = SYS_MODE_RUN;
}
else
{
sys->mode = SYS_MODE_FAULT;
}
DEBUG_PRINT("fault check done\n");
}
核心新增代码是:
if(sys->fault_code == FAULT_NONE)
{
sys->mode = SYS_MODE_RUN;
}
else
{
sys->mode = SYS_MODE_FAULT;
}
意思是:
如果 fault_code 等于 FAULT_NONE,说明没有故障,系统模式为 RUN;
如果 fault_code 不等于 FAULT_NONE,说明存在故障,系统模式为 FAULT。
这样系统模式就和故障检测结果关联起来了。
九、使用 switch-case 打印系统模式
在 system_print() 中,使用 switch-case 根据 sys->mode 打印系统模式。
void system_print(const SystemStatus *sys)
{
printf("LED state: %d\n", sys->led_state);
switch(sys->mode)
{
case SYS_MODE_INIT:
printf("System mode: INIT\n");
break;
case SYS_MODE_RUN:
printf("System mode: RUN\n");
break;
case SYS_MODE_FAULT:
printf("System mode: FAULT\n");
break;
default:
printf("System mode: UNKNOWN\n");
break;
}
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");
}
}
其中:
switch(sys->mode)
表示根据 sys->mode 的值进入不同分支。
例如:
case SYS_MODE_FAULT:
printf("System mode: FAULT\n");
break;
表示如果当前系统模式是 SYS_MODE_FAULT,就打印:
System mode: FAULT
break 的作用是结束当前分支,跳出 switch。
如果没有 break,程序可能会继续执行后面的 case,导致输出混乱。
十、为什么这里用 switch-case?
当然,也可以用 if-else 实现:
if(sys->mode == SYS_MODE_INIT)
{
printf("System mode: INIT\n");
}
else if(sys->mode == SYS_MODE_RUN)
{
printf("System mode: RUN\n");
}
else if(sys->mode == SYS_MODE_FAULT)
{
printf("System mode: FAULT\n");
}
else
{
printf("System mode: UNKNOWN\n");
}
但是像系统模式这种“一个变量对应多个固定状态”的情况,用 switch-case 更清晰。
所以可以简单记忆:
状态分支多,适合 switch-case;
普通条件判断,适合 if-else。
十一、Makefile 修改
因为 Day09 是从 Day08 复制过来的,所以需要将目标文件名改成 Day09 对应名称。
TARGET = build/day09_test
完整 Makefile 主要内容如下:
CC = gcc
CFLAGS = -Wall -Wextra -Iinclude
DEPFLAGS = -MMD -MP
DEBUG ?= 1
ifeq ($(DEBUG),1)
CFLAGS += -DDEBUG
endif
TARGET = build/day09_test
SRCS = $(wildcard src/*.c)
OBJS = $(patsubst src/%.c, build/%.o, $(SRCS))
DEPS = $(patsubst src/%.c, build/%.d, $(SRCS))
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(OBJS) -o $(TARGET)
build/%.o: src/%.c
$(CC) $(CFLAGS) $(DEPFLAGS) -c $< -o $@
run: $(TARGET)
./$(TARGET)
clean:
rm -f build/*.o build/*.d $(TARGET)
-include $(DEPS)
这样最终生成的可执行文件就是:
build/day09_test
十二、FAULT 模式测试
默认情况下,config.h 中的传感器模拟值如下:
#define SENSOR_SIM_VOLTAGE 9.5f
#define SENSOR_SIM_CURRENT 2.5f
#define SENSOR_SIM_TEMPERATURE 75.0f
故障判断阈值为:
#define FAULT_LOW_VOLTAGE_TH 10.0f
#define FAULT_OVER_CURRENT_TH 2.0f
#define FAULT_OVER_TEMP_TH 60.0f
由于:
9.5 < 10.0
2.5 > 2.0
75.0 > 60.0
所以会同时触发低电压、过电流和过温故障。
执行:
cd /root/Embedded_14Days/day09
make clean
make
make run
运行结果:
[DEBUG] system init done
[DEBUG] sensor update done
[DEBUG] fault check done
LED state: 0
System mode: FAULT
Voltage: 9.50 V
Current: 2.50 A
temperature: 75.00 C
Fault code: 0x0007
Fault: Low voltage
Fault: Over current
Fault: Over temperature
可以看到:
System mode: FAULT
说明存在故障时,系统模式能够正确切换为故障模式。
十三、RUN 模式测试
为了验证正常运行模式,将 config.h 中的传感器模拟值临时改成正常范围:
#define SENSOR_SIM_VOLTAGE 12.0f
#define SENSOR_SIM_CURRENT 1.0f
#define SENSOR_SIM_TEMPERATURE 35.0f
重新执行:
make clean
make
make run
运行结果变为:
[DEBUG] system init done
[DEBUG] sensor update done
[DEBUG] fault check done
LED state: 0
System mode: RUN
Voltage: 12.00 V
Current: 1.00 A
temperature: 35.00 C
Fault code: 0x0000
System normal
可以看到:
System mode: RUN
Fault code: 0x0000
System normal
说明没有故障时,系统模式能够正确切换为正常运行模式。
验证完成后,将 config.h 恢复为默认故障模拟值:
#define SENSOR_SIM_VOLTAGE 9.5f
#define SENSOR_SIM_CURRENT 2.5f
#define SENSOR_SIM_TEMPERATURE 75.0f
十四、今天遇到的问题
1. enum 写法错误
一开始把 enum 写成了类似:
typedef enum
[
SYS_MODE_INIT = 0,
SYS_MODE_RUN,
SYS_MODE_FAULT
] SystemMode;
这是错误的,因为 C 语言中的 enum 要使用大括号 {},不能使用中括号 []。
正确写法是:
typedef enum
{
SYS_MODE_INIT = 0,
SYS_MODE_RUN,
SYS_MODE_FAULT
} SystemMode;
2. 结构体字段拼写错误
一开始把:
SystemMode mode;
误写成了类似:
SystemMode maode;
这样后面如果写:
sys->mode = SYS_MODE_INIT;
就会报错,因为结构体里并没有 mode 这个成员。
所以变量名必须前后一致。
3. 把 C 代码误输入到终端
在修改 system.c 时,曾经把:
sys->mode = SYS_MODE_INIT;
误输入到了终端里。
终端报错:
Command 'sys-' not found
这是因为终端是执行 Linux 命令的地方,不是写 C 代码的地方。
代码应该写在 .c 文件里,例如:
day09/src/system.c
4. VS Code WSL 卡住问题
今天还遇到了 VS Code 连接 WSL 卡住、扩展导致窗口崩溃的问题。
最后通过以下方式解决:
关闭 VS Code
wsl --shutdown
清理 VS Code Server
禁用部分扩展后重新打开
这个问题和代码本身无关,是 VS Code 与 WSL 连接环境的问题。
十五、今日总结
通过 Day09 的学习,主要掌握了以下内容:
- 理解了
enum枚举的基本作用; - 学会了用枚举表示系统固定状态;
- 在
SystemStatus结构体中新增了mode字段; - 学会了在系统初始化时设置默认模式;
- 学会了根据故障码切换
RUN和FAULT模式; - 学会了使用
switch-case打印不同系统模式; - 验证了
FAULT和RUN两种系统模式; - 进一步理解了嵌入式系统中的状态管理思想。
Day09 的核心可以总结为:
enum 用有意义的名字表示固定状态;
switch-case 用于根据状态执行不同分支逻辑。
这一步虽然代码量不大,但非常接近真实嵌入式工程中的状态管理方式。后续学习状态机、任务调度、故障保护和设备运行流程时,都会继续用到类似思想。
十六、项目源码
本次 Day09 学习代码已上传至 GitHub:
https://github.com/jdai10590-afk/Embedded-C-Learning-Projects/tree/main/day09

1056

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



