跨文件数据共享模式:通过静态全局变量与访问函数结合

在嵌入式软件开发中,模块化设计是保证代码可维护性和可扩展性的关键。下面传感器这段代码展示了一种经典且实用的跨文件数据共享模式:通过静态全局变量与访问函数结合,实现模块内部数据的封装与外部只读访问

#ifndef __SENSOR_H

#define __SENSOR_H

#include "rtthread.h"

#define SENSOR_POLL_TIME (60 * 15)

struct temp_data{

float temp;

rt_uint32_t timestamp;

};

struct temp{

rt_uint8_t cnt;

struct temp_data temp_data[10];

};

struct humidity_data{

float humidity;

rt_uint32_t timestamp;

};

struct humidity{

rt_uint8_t cnt;

struct humidity_data humidity_data[10];

};

struct temp *temp_get_data(void);

void temp_clear_data(void);

struct humidity *humidity_get_data(void);

void humidity_clear_data(void);

void sensor_cache_for_gps(void);

const struct temp* sensor_get_gps_temp(void);

const struct humidity* sensor_get_gps_humidity(void);

void sensor_clear_gps_temp(void);

void sensor_clear_gps_humidity(void);

#endif

#include "sensor.h"

#include "rtthread.h"

#include "dev_sht4.h"

struct sensor{

struct rt_thread thread;

rt_uint8_t stack[0x200];

rt_device_t sht4;

struct temp temp;

struct humidity humidity;

};

static struct sensor app;

static rt_device_t sht4_dev = RT_NULL;

static struct temp gps_temp_cache = {0};

static struct humidity gps_humidity_cache = {0};

void sensor_cache_for_gps(void)

{

struct sht4_data data;

rt_device_t dev = rt_device_find("sht4");

if (dev == RT_NULL) {

gps_temp_cache.cnt = 0;

gps_humidity_cache.cnt = 0;

return;

}

rt_device_open(dev, RT_DEVICE_FLAG_RDWR);

rt_device_read(dev, 0, &data, sizeof(data));

rt_device_close(dev);

gps_temp_cache.cnt = 1;

gps_temp_cache.temp_data[0].temp = data.t_degC;

gps_temp_cache.temp_data[0].timestamp = time(RT_NULL);

gps_humidity_cache.cnt = 1;

gps_humidity_cache.humidity_data[0].humidity = data.rh_pRH;

gps_humidity_cache.humidity_data[0].timestamp = gps_temp_cache.temp_data[0].timestamp;

}

const struct temp* sensor_get_gps_temp(void)

{

return &gps_temp_cache;

}

const struct humidity* sensor_get_gps_humidity(void)

{

return &gps_humidity_cache;

}

void sensor_clear_gps_temp(void)

{

gps_temp_cache.cnt = 0;

}

void sensor_clear_gps_humidity(void)

{

gps_humidity_cache.cnt = 0;

}

struct temp *temp_get_data(void)

{

struct sht4_data data;

if(app.sht4 == RT_NULL)

{

app.sht4 = rt_device_find("sht4");

if(app.sht4 == RT_NULL)

{

rt_kprintf("find sht4 failed\r\n");

app.humidity.cnt = 0;

app.temp.cnt = 0;

return &app.temp;

}

rt_device_init(app.sht4);

}

rt_device_open(app.sht4, RT_DEVICE_FLAG_RDWR);

rt_device_read(app.sht4, 0, &data, sizeof(data));

rt_device_close(app.sht4);

app.temp.cnt = 1;

if(data.t_degC > -0.1 && data.t_degC < 0.1)

{

app.temp.temp_data[0].temp = data.t_degC;

}

app.temp.temp_data[0].temp = data.t_degC;

app.temp.temp_data[0].timestamp = time(RT_NULL);

app.humidity.cnt = 1;

app.humidity.humidity_data[0].humidity = data.rh_pRH;

app.humidity.humidity_data[0].timestamp = app.temp.temp_data[0].timestamp;

return &app.temp;

}

void temp_clear_data(void)

{

app.temp.cnt = 0;

}

struct humidity *humidity_get_data(void)

{

return &app.humidity;

}

void humidity_clear_data(void)

{

app.humidity.cnt = 0;

}

void sensor_thread(void *param)

{

while(1)

{

rt_thread_mdelay(10000);

struct temp *temp = temp_get_data();

if(temp != RT_NULL)

{

rt_kprintf("temp: %f, humidity: %f\r\n", temp->temp_data[0].temp, app.humidity.humidity_data[0].humidity);

}

}

}

int sensor_app_init(void)

{

app.sht4 = rt_device_find("sht4");

if (app.sht4 == RT_NULL)

{

rt_kprintf("sht4 device not found!\n");

return -RT_ERROR;

}

if (rt_device_init(app.sht4) != RT_EOK)

{

rt_kprintf("sht4 init failed!\n");

return -RT_ERROR;

}

rt_device_open(app.sht4, RT_DEVICE_FLAG_RDWR);

rt_device_close(app.sht4);

// rt_err_t ret = rt_thread_init(&app.thread,

// "sensor",

// sensor_thread,

// RT_NULL,

// app.stack,

// sizeof(app.stack),

// 21,

// 20);

// if(ret == RT_EOK)

// ret = rt_thread_startup(&app.thread);

rt_kprintf("sensor init success\r\n");

return RT_EOK;

}

MSH_CMD_EXPORT_ALIAS(sensor_app_init, sensor_app_init, sensor app init);

一、代码回顾

sensor.c 中:

复制代码
static struct temp gps_temp_cache = {0};
static struct humidity gps_humidity_cache = {0};

const struct temp* sensor_get_gps_temp(void)
{
    return &gps_temp_cache;
}

const struct humidity* sensor_get_gps_humidity(void)
{
    return &gps_humidity_cache;
}

msg.c 中(或其他需要数据的文件):

复制代码
#include "sensor.h"
const struct temp *temp_cache = sensor_get_gps_temp();
const struct humidity *humidity_cache = sensor_get_gps_humidity();

二、为什么要这样设计?

1. 需求背景

温湿度传感器数据需要在多个模块(如GPS上报、WiFi上报)中使用。

但由于GPS工作时发热会影响传感器读数,所以需要在开启GPS前主动读取并缓存数据,之后各模块直接使用这份缓存数据。

数据由 sensor.c 模块统一管理,其他模块不应直接操作缓存变量,而应通过接口获取。

2. 不使用全局变量的理由

如果直接定义全局变量(如在 sensor.h 中声明 extern struct temp gps_temp_cache;),会带来以下问题:

命名冲突:全局符号污染,容易与其他模块同名变量冲突。

耦合度高:任何地方都可以随意修改该变量,难以追踪数据变化,容易引入bug。

缺乏封装 :内部数据结构(如 struct temp 的成员)暴露给所有调用者,一旦结构体定义变化,所有使用的地方都需要修改。

无法控制访问权限:无法限制只读或只写,也无法在访问时添加额外逻辑(如数据有效性检查)。

三、本设计模式的优点

1. 信息隐藏与封装

使用 static 关键字将 gps_temp_cachegps_humidity_cache 限制在 sensor.c 文件作用域内,外部文件无法直接访问。

对外只提供获取指针的接口函数,且返回类型为 const 限定,强制调用者只能读取数据而不能修改,保证了缓存数据的一致性。

2. 接口稳定性

即使将来 struct temp 的内部成员发生变化(如增加字段、修改数据类型),只要接口函数的声明不变,调用方代码就无需修改。例如,若需要将温度值由 float 改为 int 表示,只需调整 sensor.c 内部实现,而 msg.c 的调用代码完全不受影响。

3. 延迟绑定与灵活性

可以在接口函数中添加额外的逻辑,例如检查数据是否有效、是否需要重新读取等。比如 sensor_get_gps_temp() 内部可以判断缓存是否过期,若过期则自动更新,调用方完全无感。

未来若需要从多个传感器获取数据,或改为通过消息队列传递,只需修改接口实现,无需改动调用方。

4. 符号隔离

静态全局变量不会出现在符号表中,避免了链接时的命名冲突。多个模块可以各自定义同名的静态变量而互不干扰。

四、深入理解:指针返回与数据生命周期

1. 返回指针 vs 返回值

返回指针(如 const struct temp*)避免了数据拷贝,效率高,尤其适用于结构体较大的情况。

但是需要注意生命周期问题:返回的指针指向的是模块内部的静态存储区,其生命周期与整个程序相同。调用方获取指针后,只要模块未重新初始化,指针始终有效。这比返回局部变量的指针安全得多。

若返回的是动态分配的内存,则调用方需负责释放,增加了复杂性。此处使用静态变量,简单可靠。

2. 常量限定 const

返回 const struct temp* 表明调用者不能修改指针指向的内容,符合"缓存数据只读"的语义。

如果确实需要修改数据(例如清空缓存),应提供专门的修改接口(如 sensor_clear_gps_temp()),而不是直接暴露指针。

3. 数据同步与线程安全

在多线程或RTOS环境中,如果多个任务同时调用这些接口,可能存在数据竞争。本例中的缓存由单个线程(GPS控制线程)在开启GPS前更新,其他线程(如网络上报线程)只读,因此是安全的。如果需要更复杂的并发控制,可以在接口内部加锁。

五、与其他数据共享方式的对比

方式 优点 缺点
直接全局变量 简单直接 耦合高,难维护,无法控制访问权限
静态全局变量 + 接口函数 封装性好,接口稳定,只读控制 需要编写额外接口函数,稍微增加代码量
动态分配 + 传递指针 灵活,可动态创建销毁 需手动管理内存,易泄漏
全局结构体 + 注册回调 松耦合,适合事件驱动 实现复杂,不适合简单数据共享

六、实际应用中的扩展

这种模式在嵌入式系统中非常普遍,例如:

驱动层提供传感器数据:static sensor_data_t s_data; const sensor_data_t* driver_get_data(void);

系统状态管理:static sys_status_t s_status; const sys_status_t* sys_get_status(void);

配置参数管理:static config_t s_config; const config_t* config_get(void);

甚至可以将接口进一步抽象,提供多个获取函数(如按索引获取、按名称获取),内部实现可以是数组、链表等,但对外隐藏细节。

七、总结

这段代码完美体现了模块化设计中的信息隐藏原则

数据(静态全局变量)归模块内部所有;

接口(函数)提供受控的访问途径;

调用者通过接口间接获取数据,无需关心数据来源和存储方式。

这种做法的核心价值在于降低模块间的耦合,提高代码的可维护性和可复用性。掌握这种设计模式,对于构建大型嵌入式软件系统至关重要。今后在需要跨文件共享数据时,可以优先考虑这种"静态变量 + 访问函数"的方式,而不是直接使用全局变量。

相关推荐
学嵌入式的小杨同学3 小时前
STM32 进阶封神之路(七):中断核心原理 + NVIC 深度解析 —— 从概念到寄存器配置(面试重点)
stm32·单片机·嵌入式硬件·mcu·硬件架构·pcb·嵌入式实时数据库
qq_402995754 小时前
RS485通信设计
stm32·单片机·mcu
电子科技圈4 小时前
IAR扩展嵌入式开发平台,推出面向安全关键型应用的长期支持(LTS)服务
嵌入式硬件·安全·设计模式·软件工程·代码规范·设计规范·代码复审
串口哑火达人4 小时前
(七)RT-Thread物联网实战--MQTT-cJSON-OneNET
c语言·单片机·嵌入式硬件·mcu·物联网
2501_943695334 小时前
2026高职物联网专业发展前景好吗?
物联网
普中科技4 小时前
【普中STM32F1xx开发攻略--标准库版】-- 第 34 章 RTC 实时时钟实验
stm32·单片机·嵌入式硬件·开发板·rtc·实时时钟·普中科技
深念Y4 小时前
eMMC芯片引脚定义详解:以KLM8G1GETF为例
linux·单片机·嵌入式硬件·ufs·emmc·闪存·颗粒
我在人间贩卖青春4 小时前
NVIC相关寄存器
单片机·嵌入式硬件·中断·nvic
萤丰信息4 小时前
物联网+AI技术落地:重构园区管理新模式,激活产业发展新动能
大数据·人工智能·科技·物联网·重构·智慧园区