嵌入式全局变量管理
摘自嵌入式老鸟都在用的全局变量管理术你还在裸奔链接https://mp.weixin.qq.com/s/gB–Bgd3YGq0k1vmvNKQrw裸奔/* global.h */ extern uint8_t g_system_mode; extern int16_t g_temperature; extern uint32_t g_error_code; /* main.c */ uint8_t g_system_mode 0; int16_t g_temperature 25; uint32_t g_error_code 0;任何模块只要 #include “global.h”就能随意读写这些变量。耦合度爆表所有模块都直接依赖全局变量改名字就是灾难竞态风险中断和主循环同时读写数据可能撕裂调试困难变量被意外篡改你根本打不了写断点很多MCU不支持数据断点无法做合法性校验谁都能写入一个越界值系统直接跑飞static get/set最常见的进阶方案/* temperature.c */ #include temperature.h static int16_t s_temperature 25; /* 藏起来外部看不到 */ int16_t temperature_get(void) { return s_temperature; } void temperature_set(int16_t temp) { /* 可以加校验 */ if (temp -40 || temp 125) { return; /* 非法值拒绝写入 */ } s_temperature temp; }/* temperature.h */ #ifndef __TEMPERATURE_H__ #define __TEMPERATURE_H__ #include stdint.h int16_t temperature_get(void); void temperature_set(int16_t temp); #endif对比项裸奔全局变量static get/set访问控制无有门卫把关合法性校验无set 里可加检查命名冲突容易冲突有static 限制作用域调用方只能通过函数来操作而且变量本身被static限制在了文件作用域内(在C语言中static将全局变量/函数的作用域限制在声明它的翻译单元通常是单个.c文件内// module_a.c static int internal_var 100; // 静态全局变量仅本文件可见 int public_var 200; // 普通全局变量外部可访问通过extern // module_b.c extern int internal_var; // ❌ 编译错误找不到符号 extern int public_var; // ✅ 可以访问值为200 void test() { // printf(%d, internal_var); // 链接失败undefined reference printf(%d, public_var); // 正常输出200 }get/set 可能带来/未解决的问题接口爆炸一个模块管理 5 个变量就要写 10 个函数。系统里有 20 个模块那就是 200 个 get/set 函数。你的头文件会变成一本电话簿。主动轮询模块 B 想知道温度有没有变化只能不停地调用 temperature_get() 去轮询。这在裸机系统里意味着浪费 CPU 时间在 RTOS 里则可能需要专门开个任务来做这事。多线程/中断安全static 只是限制了访问路径并没有解决并发问题。中断里调 temperature_set()主循环里调 temperature_get()在 32 位 MCU 上操作 16 位变量可能没事但如果是个结构体呢/* 这样就不安全了 */ static SensorData_t s_sensor_data; /* 包含多个字段 */ SensorData_t sensor_get_data(void) { return s_sensor_data; /* 读到一半被中断打断数据撕裂 */ }结构体封装 模块化管理与其给每个变量都配一对 get/set不如把同一模块的数据打包成结构体统一管理/* sensor_mgr.h */ typedef struct { int16_t temperature; uint16_t humidity; int16_t pressure; uint32_t timestamp; } SensorData_t; /* 只暴露两个接口干净利落 */ const SensorData_t* sensor_get_data(void); void sensor_update(int16_t temp, uint16_t humi, int16_t pres);/* sensor_mgr.c */ #include sensor_mgr.h static SensorData_t s_sensor {0}; const SensorData_t* sensor_get_data(void) { return s_sensor; /* 返回只读指针外部改不了 */ } void sensor_update(int16_t temp, uint16_t humi, int16_t pres) { /* 集中校验 */ if (temp -40 || temp 125) return; if (humi 100) return; __disable_irq(); /* 关中断防止数据撕裂 */ s_sensor.temperature temp; s_sensor.humidity humi; s_sensor.pressure pres; s_sensor.timestamp get_sys_tick(); __enable_irq(); }关键点 返回 const 指针外部只能读不能写。想修改数据必须调用 sensor_update()其中包含了校验和临界区保护。回调通知机制告别轮询/* sensor_mgr.h 增加回调支持 */ typedef void (*SensorCallback_t)(const SensorData_t *data); void sensor_register_callback(SensorCallback_t cb);/* sensor_mgr.c */ #define MAX_CALLBACKS 4 static SensorCallback_t s_callbacks[MAX_CALLBACKS]; static uint8_t s_cb_count 0; void sensor_register_callback(SensorCallback_t cb) { if (cb ! NULL s_cb_count MAX_CALLBACKS) { s_callbacks[s_cb_count] cb; } } void sensor_update(int16_t temp, uint16_t humi, int16_t pres) { /* ... 校验和赋值同上 ... */ /* 数据更新后主动通知所有注册者 */ for (uint8_t i 0; i s_cb_count; i) { if (s_callbacks[i]) { s_callbacks[i](s_sensor); } } }/* display.c —— 显示模块 */ static void on_sensor_changed(const SensorData_t *data) { lcd_show_temperature(data-temperature); lcd_show_humidity(data-humidity); } void display_init(void) { sensor_register_callback(on_sensor_changed); /* 注册一次就行 */ }上述也即观察者模式的朴素实现。