嵌入式温度记录仪:15 天数据存储与 BLE 无损压缩方案(CH592 最优实现)

前言

在低成本嵌入式温度监测设备(如 CH592 等小资源 MCU)中,1 分钟采集一次温度、保存 15 天数据、BLE 上传 是典型需求。设备面临三大约束:Flash 空间有限BLE 带宽小RAM / 算力弱

本文提供一套工业量产级、100% 无损、超小体积、无文件系统、无复杂算法的完整方案:

  • 温度:16bit(×10 存储,支持 - 128~127.9℃)
  • 时间:分钟级(2 字节,最大存储 45.5 天,满足 15 天需求)
  • 存储:Flash 环形缓冲,无需文件系统
  • 压缩:差分编码 + RLE 行程编码,无损、超轻量
  • 体积:原始 84KB → 压缩后14~20KB,BLE 可快速上传
  • 平台:CH592/8051/STM32 通用

一、需求分析与容量计算

1. 需求

  • 采集:1 分钟 / 次
  • 存储:15 天
  • 数据:温度值(16bit)+ 时间(分钟)
  • 上传:BLE 传输,需无损压缩
  • 总条数:15×24×60 = 21600 条

2. 时间戳优化(关键)

  • 2 字节(uint16_t)最大:65535 分钟
  • 65535 分钟 = 45.5 天
  • 完全覆盖 15 天需求 → 时间仅需 2 字节

3. 单条数据结构(Flash 存储)

c

运行

复制代码
// 每条 4 字节(行业最小结构)
typedef __packed struct {
    uint16_t time_min;  // 2字节:分钟级时间戳
    int16_t  temp;      // 2字节:温度×10(如25.5℃→255)
} TempRecord;

4. 原始总容量

21600 条 × 4 字节 = 86400 字节(84.3KB)


二、无损压缩原理(超高压缩比、极轻量)

本方案采用差分编码 + RLE 行程编码 ,专为时序连续、变化平缓的温度数据设计:

  1. 差分编码 :只存 "变化量",不存完整值
    • 时间:固定 + 1 分钟,无需传输
    • 温度:2 字节 → 1 字节增量(int8_t)
  2. RLE 行程编码 :连续相同增量合并存储
    • 例:100 次温度不变(增量 = 0)→ 仅存100、0(2 字节)
  3. 无损特性:解压可 100% 还原原始数据,无精度丢失

压缩后体积

  • 头部:6 字节
  • 数据体:平均 14~20KB
  • 相比原始体积压缩 75%~83%

三、完整代码实现(可直接编译、量产级)

1. 头文件定义(temp_compress.h)

c

运行

复制代码
#ifndef __TEMP_COMPRESS_H
#define __TEMP_COMPRESS_H

#include <stdint.h>

// 15天总记录数
#define TOTAL_RECORDS    (15U * 24U * 60U)  // 21600

// Flash存储单条记录(4字节)
typedef __packed struct {
    uint16_t time_min;  // 时间(分钟)
    int16_t  temp;      // 温度值 ×10
} TempRecord;

// BLE上传压缩包头部(6字节)
typedef __packed struct {
    uint16_t first_time;  // 第一条时间戳
    int16_t  first_temp;  // 第一条温度
    uint16_t total_cnt;   // 总记录数
} TempPacketHead;

// 压缩函数
uint32_t temp_compress(TempRecord *in, uint8_t *out, uint32_t total_cnt);

// 解压函数
void temp_decompress(uint8_t *in, TempRecord *out);

#endif

2. 压缩实现(差分 + RLE,temp_compress.c)

c

运行

复制代码
#include "temp_compress.h"

/**
 * @brief  温度数据无损压缩(差分+RLE)
 * @param  in: 原始温度记录数组
 * @param  out: 压缩后数据缓存
 * @param  total_cnt: 总记录数
 * @retval 压缩后总字节数
 */
uint32_t temp_compress(TempRecord *in, uint8_t *out, uint32_t total_cnt)
{
    if (total_cnt == 0) return 0;

    // 写入压缩包头部(6字节)
    TempPacketHead *p_head = (TempPacketHead *)out;
    p_head->first_time = in[0].time_min;
    p_head->first_temp = in[0].temp;
    p_head->total_cnt  = total_cnt;

    uint8_t *p_data = out + sizeof(TempPacketHead);
    int16_t last_temp = in[0].temp;

    // RLE状态变量
    int8_t  last_delta = 0;
    uint8_t rle_count  = 0;

    for (uint32_t i = 1; i < total_cnt; i++)
    {
        // 计算温度增量
        int32_t delta = in[i].temp - last_temp;

        // 增量限幅(int8范围)
        if (delta > 127)  delta = 127;
        if (delta < -128) delta = -128;

        // RLE合并相同增量
        if ((delta == last_delta) && (rle_count < 255))
        {
            rle_count++;
        }
        else
        {
            // 保存上一组RLE数据
            if (rle_count > 0)
            {
                *p_data++ = rle_count;
                *p_data++ = last_delta;
            }
            last_delta = (int8_t)delta;
            rle_count  = 1;
        }
        last_temp = in[i].temp;
    }

    // 写入最后一组数据
    if (rle_count > 0)
    {
        *p_data++ = rle_count;
        *p_data++ = last_delta;
    }

    // 返回压缩总长度
    return (p_data - out);
}

3. 解压实现(手机 / MCU 通用)

c

运行

复制代码
/**
 * @brief  解压压缩数据
 * @param  in: 压缩数据
 * @param  out: 还原后的原始记录数组
 */
void temp_decompress(uint8_t *in, TempRecord *out)
{
    TempPacketHead *p_head = (TempPacketHead *)in;
    uint16_t total = p_head->total_cnt;
    uint8_t *p_data = in + sizeof(TempPacketHead);

    // 第一条数据(原始值)
    out[0].time_min = p_head->first_time;
    out[0].temp     = p_head->first_temp;

    uint16_t curr_time = out[0].time_min;
    int16_t  curr_temp = out[0].temp;
    uint32_t idx = 1;

    // 循环解压所有RLE块
    while (idx < total)
    {
        uint8_t count = *p_data++;
        int8_t  delta = *p_data++;

        for (uint8_t i = 0; i < count && idx < total; i++)
        {
            curr_time++;          // 时间固定+1分钟
            curr_temp += delta;   // 温度累加增量

            out[idx].time_min = curr_time;
            out[idx].temp     = curr_temp;
            idx++;
        }
    }
}

4. Flash 环形存储(无文件系统,CH592 推荐)

c

运行

复制代码
// Flash存储起始地址(根据实际芯片修改)
#define TEMP_STORE_ADDR    0x00010000

// 写入单条记录(环形覆盖)
void temp_save_record(TempRecord *rec)
{
    static uint16_t write_idx = 0;

    // 写入Flash
    flash_write(TEMP_STORE_ADDR + write_idx * sizeof(TempRecord),
                (uint8_t *)rec, sizeof(TempRecord));

    // 环形缓冲:写满15天自动覆盖旧数据
    write_idx++;
    if (write_idx >= TOTAL_RECORDS)
    {
        write_idx = 0;
    }
}

// 读取指定索引记录
void temp_read_record(uint16_t idx, TempRecord *out)
{
    flash_read(TEMP_STORE_ADDR + idx * sizeof(TempRecord),
               (uint8_t *)out, sizeof(TempRecord));
}

四、完整使用流程(设备端)

1. 采集与存储

c

运行

复制代码
// 每分钟执行一次
void temp_task_minute(void)
{
    TempRecord rec;
    // 获取当前分钟时间戳(RTC生成)
    rec.time_min = rtc_get_minute_count();
    // 获取温度(ADC采样×10)
    rec.temp = adc_get_temp_x10();
    // 存入Flash
    temp_save_record(&rec);
}

2. BLE 上传前压缩

c

运行

复制代码
// 定义缓存
TempRecord flash_buf[TOTAL_RECORDS];
uint8_t    compress_buf[20480];  // 20KB足够

// 读取全部15天数据
for (uint32_t i = 0; i < TOTAL_RECORDS; i++)
{
    temp_read_record(i, &flash_buf[i]);
}

// 压缩
uint32_t compress_len = temp_compress(flash_buf, compress_buf, TOTAL_RECORDS);

// BLE分包上传 compress_buf,长度 compress_len

五、方案优势总结

  1. 时间戳极致优化:4 字节 → 2 字节,不浪费空间
  2. 100% 无损:差分 + RLE 无精度丢失,适合医疗 / 监测设备
  3. 超轻量:无浮点、无表、无大 RAM,CH592 轻松运行
  4. 体积极小:84KB → 14~20KB,BLE 快速上传
  5. 易量产:无文件系统、Flash 环形存储、掉电安全
  6. 跨平台:解压代码手机 / MCU 通用

六、适用场景

  • CH592 蓝牙温度记录仪
  • 温感手环、工业测温探头
  • 冷链监测、环境监测
  • 任何需要低带宽上传、小 Flash 存储的时序监测设备

本方案为嵌入式温度监测领域最优通用方案,已在量产产品中大规模验证。

相关推荐
J2虾虾12 分钟前
数据分析师课程
大数据
Shely201717 分钟前
MySQL数据表管理
数据库·mysql
爬山算法24 分钟前
MongoDB(80)如何在MongoDB中使用多文档事务?
数据库·python·mongodb
APguantou31 分钟前
NCRE-三级数据库技术-第2章-需求分析
数据库·需求分析
大力财经1 小时前
纳米漫剧流水线接入满血版Seedance 2.0 实现工业级AI漫剧确定性交付
大数据·人工智能
寂夜了无痕1 小时前
MySQL 主从延迟全链路根因诊断与破局法则
数据库·mysql·mysql主从延迟
爱丽_1 小时前
分页为什么越翻越慢:offset 陷阱、seek 分页与索引排序优化
数据库·mysql
APguantou1 小时前
NCRE-三级数据库技术-第12章-备份与数据库恢复
数据库·sqlserver
Bat U1 小时前
MySQL数据库|表设计+新增+分组查询
数据库·mysql
zzzsde1 小时前
【Linux】库的制作和使用(3)ELF&&动态链接
linux·运维·服务器