揭阳市网站建设_网站建设公司_页面权重_seo优化
2025/12/26 23:10:59 网站建设 项目流程

灰尘补偿算法笔记

一、算法概述

1.1 解决的核心问题

设备长期运行后,传感器积灰导致 AD 采样基线(零刻度)漂移,若仍用出厂基准值(PsAD)会导致浓度测量不准(误报 / 漏报),算法通过动态统计 + 自校准抵消基线漂移。

1.2 核心思路

  1. 数据统计:实时记录采样值与初始基准值的偏差,按 “10 天滚动窗口” 统计偏差出现频次;
  2. 校准触发:每 2 分钟(测试)/1 天(实际)触发周期检查,累计 10 天数据后启动校准;
  3. 动态校零:找到出现频次最高的偏差(最频偏差),将设备 “零刻度索引” 调整到该位置;
  4. 阈值同步:更新报警阈值适配新零刻度,补偿值持久化到 EEPROM(掉电不丢失)。

二、核心配置与数据结构

2.1 宏定义

宏名 取值 / 说明 原对应值
INDEX_SCOPE 21U _Scope
ENABLE_DOWN_OFFSET 1U(启用)/0U(禁用) _EnableDown
STATISTIC_DAY_COUNT 10U _10DAY
CYCLE_TRIGGER_THRESHOLD 12U(测试,2 分钟) 12
CYCLE_TRIGGER_THRESHOLD_REAL 98181U(实际,1 天) 98181
AD_VALUE_COUNT_MAX 0xFFFEU(计数上限,防溢出) 0xfffe
DUST_VALUE_EEPROM_ADDR 0x00U(补偿值存储地址) DustValueAddr
EEPROM_WRITE_ERR_FLAG 1U(EEPROM 写入错误标记) -

2.2 数据结构

typedef struct
{unsigned int  sec_counter;        // 秒计数器(触发周期逻辑)unsigned char day_index;          // 当前统计天数索引(0~9)unsigned char calibration_flag;   // 校准触发标记(1=需要校准)unsigned char base_index;         // 动态零值基准索引(核心补偿参数)signed char   offset_value;       // 校准计算出的偏移量(补偿量)unsigned int  smoke_ad_count[STATISTIC_DAY_COUNT][INDEX_SCOPE]; // 偏差统计数组
} DustData_T;// 全局灰尘数据(可按需调整作用域)
static DustData_T g_dust_data = {0};// 全局阈值
static signed int g_dust_compensate_value = 0;  // 总补偿值
static unsigned int g_normal_threshold = 2000;  // 平时阈值
static unsigned int g_alarm_threshold = 3000;   // 报警阈值
static unsigned int g_early_alarm_threshold = 2800; // 提前报警阈值// 出厂基准阈值(常量,不随校准变化)
static const unsigned int BASE_NORMAL_THRESHOLD = 2000;
static const unsigned int BASE_ALARM_THRESHOLD = 3000;
static const unsigned int BASE_EARLY_ALARM_THRESHOLD = 2800;

三、算法核心流程

四、关键函数解析

4.1 函数总览

函数名 核心作用
DustDataClear 清空指定天数的偏差统计计数
AdjustBaseIndex 调整基准索引,清零旧索引区间计数(防历史数据干扰)
FindCalibrationOffset 查找最频索引,计算校准偏移量
UpdateAdValueCount 封装计数累加逻辑,防止溢出
CalculateOffsetIndex 计算采样值对应的偏移索引(核心映射逻辑)
UpdateAlarmThresholds 叠加补偿值,更新所有报警阈值
SaveAndVerifyDustValue 补偿值写入 EEPROM 并校验,失败触发故障处理
Dust_Calculate 核心主函数,整合周期判断、校准、实时统计逻辑

4.2 工具函数完整实现

4.2.1 DustDataClear - 清空指定天数统计数据

/*** @brief  清空指定天数的AD统计计数* @param  day: 要清空的天数索引(0~STATISTIC_DAY_COUNT-1)* @retval 无* @note   遍历指定天数的所有索引,将计数重置为0,为新统计周期准备*/
static void DustDataClear(unsigned char day)
{unsigned char index = 0;// 遍历所有索引,清零计数do{g_dust_data.smoke_ad_count[day][index] = 0U;index++;} while (index < INDEX_SCOPE);
}

4.2.2 AdjustBaseIndex - 调整基准索引并清理旧数据

/*** @brief  调整基准索引并清零旧索引区间的计数* @param  old_base_index: 调整前的基准索引* @param  shift_num: 偏移量(可正可负,正=正向偏移,负=反向偏移)* @retval 调整后的新基准索引* @note   1. +INDEX_SCOPE避免负数取模异常,保证索引在0~INDEX_SCOPE-1;*         2. 清零旧索引到新索引区间的计数,避免历史数据干扰校准后统计*/
static unsigned char AdjustBaseIndex(unsigned char old_base_index, signed char shift_num)
{unsigned char new_base_index = 0;unsigned char day = 0;unsigned char index = 0;// 计算新基准索引:+INDEX_SCOPE避免负数取模异常,保证结果在0~INDEX_SCOPE-1new_base_index = (old_base_index + INDEX_SCOPE + shift_num) % INDEX_SCOPE;// 清零旧索引到新索引之间的计数(避免历史数据干扰)if (shift_num > 0){// 正向偏移:旧索引→新索引,正序清零for (day = 0; day < STATISTIC_DAY_COUNT; day++){for (index = old_base_index; index != new_base_index; index++){if (index >= INDEX_SCOPE){index = 0U;}g_dust_data.smoke_ad_count[day][index] = 0U;}}}else if (shift_num < 0){// 反向偏移:旧索引→新索引,倒序清零for (day = 0; day < STATISTIC_DAY_COUNT; day++){for (index = old_base_index; index != new_base_index; index--){if (index >= INDEX_SCOPE)  // 无符号数减到0后再减会溢出,判断溢出{index = INDEX_SCOPE - 1U;}g_dust_data.smoke_ad_count[day][index] = 0U;}}}// 偏移量为0时无需清零return new_base_index;
}

4.2.3 FindCalibrationOffset - 查找校准偏移量

/*** @brief  查找统计周期内计数最多的索引,计算偏移量(核心校准逻辑)* @param  current_base_index: 当前基准索引* @retval 计算出的补偿偏移量(可正可负)* @note   1. 最频索引=计数最多的索引,代表灰尘导致的稳定基线偏移;*         2. 启用向下偏移时,以(INDEX_SCOPE-1)/2为中点计算偏移,否则直接计算索引差*/
static signed char FindCalibrationOffset(unsigned char current_base_index)
{signed char offset = 0;unsigned int max_count = 0U;unsigned char max_index = current_base_index;unsigned char day = 0;unsigned char index = 0;// 遍历10天的所有索引,找到计数最多的索引(最频索引)for (day = 0; day < STATISTIC_DAY_COUNT; day++){for (index = 0; index < INDEX_SCOPE; index++){if (g_dust_data.smoke_ad_count[day][index] > max_count){max_count = g_dust_data.smoke_ad_count[day][index];max_index = index;}}}// 计算最频索引与当前基准索引的偏移量if (ENABLE_DOWN_OFFSET){// 启用向下偏移时,以(INDEX_SCOPE-1)/2为中点计算偏移unsigned char mid_index = (INDEX_SCOPE - 1U) >> 1;offset = (max_index - mid_index) - (current_base_index - mid_index);}else{// 禁用向下偏移时,直接计算索引差offset = max_index - current_base_index;}return offset;
}

4.2.4 UpdateAdValueCount - 更新统计计数(防溢出)

/*** @brief  更新AD值统计计数(封装重复逻辑,防止溢出)* @param  day: 天数索引(0~STATISTIC_DAY_COUNT-1)* @param  index: 要累加的偏差索引(0~INDEX_SCOPE-1)* @retval 无* @note   计数上限为AD_VALUE_COUNT_MAX,避免溢出导致统计失真*/
static void UpdateAdValueCount(unsigned char day, unsigned char index)
{// 计数累加,超过最大值则限制为最大值(防止溢出)if (g_dust_data.smoke_ad_count[day][index] < AD_VALUE_COUNT_MAX){g_dust_data.smoke_ad_count[day][index]++;}// 已达最大值则不操作
}

4.2.5 CalculateOffsetIndex - 计算采样值偏移索引

/*** @brief  计算采样值与基准值的偏移索引* @param  smoke_ad: 实时AD采样值* @param  base_ad: 初始基准AD值(出厂零刻度)* @retval 计算后的偏移索引(0~INDEX_SCOPE-1)* @note   1. 差值>>4(÷16)过滤采样噪声,保留粗粒度偏差;*         2. 启用向下偏移时,正向偏差映射到中点右侧,反向映射到左侧;*         3. 禁用向下偏移时,反向偏差直接归基准索引*/
static unsigned char CalculateOffsetIndex(unsigned int smoke_ad, unsigned int base_ad)
{unsigned int diff = 0U;unsigned char offset_index = 0U;unsigned char mid_index = (INDEX_SCOPE - 1U) >> 1;  // 索引中点(10)if (smoke_ad > base_ad){// 正向偏差:采样值 > 基准值diff = smoke_ad - base_ad;diff = diff >> 4;  // 差值÷16,过滤噪声,保留粗粒度偏差// 限制偏移量上限if (ENABLE_DOWN_OFFSET){if (diff >= mid_index){diff = mid_index;}// 映射到中点右侧索引(正向偏差区)offset_index = (unsigned char)diff + mid_index;}else{if (diff >= (INDEX_SCOPE - 1U)){diff = INDEX_SCOPE - 1U;}offset_index = (unsigned char)diff;}}else{// 反向偏差:采样值 ≤ 基准值if (ENABLE_DOWN_OFFSET){diff = base_ad - smoke_ad;diff = diff >> 4;  // 差值÷16// 限制偏移量上限if (diff >= mid_index){diff = mid_index;}// 映射到中点左侧索引(反向偏差区)offset_index = mid_index - (unsigned char)diff;}else{// 禁用向下偏移时,反向偏差直接归基准索引offset_index = g_dust_data.base_index;}}// 计算最终索引:基准索引+偏移量,取模保证不越界offset_index = (offset_index + g_dust_data.base_index) % INDEX_SCOPE;return offset_index;
}

4.2.6 UpdateAlarmThresholds - 更新报警阈值

/*** @brief  更新报警阈值(叠加补偿值)* @param  无* @retval 无* @note   1. 偏移量×16还原原始AD偏差(反向抵消÷16操作);*         2. 所有阈值叠加总补偿值,适配新的零刻度基线*/
static void UpdateAlarmThresholds(void)
{// 补偿值还原为原始AD差值(偏移量×16,反向抵消÷16操作)g_dust_compensate_value += (signed int)(g_dust_data.offset_value * 16);// 同步更新所有阈值(叠加补偿值)g_normal_threshold = BASE_NORMAL_THRESHOLD + g_dust_compensate_value;g_alarm_threshold = BASE_ALARM_THRESHOLD + g_dust_compensate_value;g_early_alarm_threshold = BASE_EARLY_ALARM_THRESHOLD + g_dust_compensate_value;
}

4.2.7 SaveAndVerifyDustValue - 补偿值持久化并校验

/*** @brief  保存补偿值到EEPROM并校验* @retval 0=成功,1=失败* @note   1. 需确保eep_input/eep_get/Fault_handling函数已实现;*         2. 校验失败触发故障处理,保证补偿值可靠性*/
static unsigned char SaveAndVerifyDustValue(void)
{unsigned char err_flag = 0U;signed int saved_value = 0;// 写入补偿值到EEPROM(需替换为实际EEPROM写入函数)eep_input(DUST_VALUE_EEPROM_ADDR, g_dust_compensate_value);// 读取验证saved_value = eep_get(DUST_VALUE_EEPROM_ADDR);// 校验写入是否成功if (saved_value != g_dust_compensate_value){err_flag = EEPROM_WRITE_ERR_FLAG;Fault_handling();  // 执行故障处理(需保证该函数已定义)}return err_flag;
}

4.3 核心计算主函数 - Dust_Calculate

/*** @brief  灰尘补偿核心计算函数(主入口)* @param  smoke_ad: 实时AD采样值* @param  base_ad: 初始基准AD值(出厂零刻度)* @retval 无* @note   整合“时间周期判断→校准逻辑→实时统计”全流程,是算法的核心入口*/
void Dust_Calculate(unsigned int smoke_ad, unsigned int base_ad)
{unsigned char offset_index = 0U;/*************************** 阶段1:时间周期判断 ***************************/if (g_dust_data.sec_counter >= CYCLE_TRIGGER_THRESHOLD){// 重置秒计数器g_dust_data.sec_counter = 0U;// 切换天数索引(循环0~STATISTIC_DAY_COUNT-1)g_dust_data.day_index++;if (g_dust_data.day_index >= STATISTIC_DAY_COUNT){g_dust_data.day_index = 0U;// 10天周期到,触发校准标记g_dust_data.calibration_flag = 1U;}/*************************** 阶段2:校准逻辑 ***************************/if (g_dust_data.calibration_flag == 1U){// 1. 查找最频偏差对应的补偿偏移量g_dust_data.offset_value = FindCalibrationOffset(g_dust_data.base_index);// 2. 清空当前天数的统计数据,为新周期准备DustDataClear(g_dust_data.day_index);// 3. 偏移量非0时执行补偿if (g_dust_data.offset_value != 0){// 3.1 调整动态基准索引(核心补偿动作)g_dust_data.base_index = AdjustBaseIndex(g_dust_data.base_index, g_dust_data.offset_value);// 3.2 更新报警阈值(叠加补偿值)UpdateAlarmThresholds();// 3.3 持久化补偿值到EEPROM并校验(void)SaveAndVerifyDustValue();}}}// 秒计数器累加(需保证该函数被周期性调用,比如1秒1次)g_dust_data.sec_counter++;/*************************** 阶段3:实时AD偏差统计 ***************************/// 计算偏移索引offset_index = CalculateOffsetIndex(smoke_ad, base_ad);// 更新对应索引的计数UpdateAdValueCount(g_dust_data.day_index, offset_index);
}

五、优化亮点总结

优化方向 具体措施
命名语义化 Smokeadsmoke_ad_ScopeINDEX_SCOPE,见名知意
拆分大函数 Dust_Calculate拆分为 7 个工具函数,降低逻辑复杂度
消除魔法数 硬编码值(如0xfffe12)定义为宏,便于维护
逻辑分层 按 “时间周期→校准→实时统计” 分层,流程清晰
鲁棒性提升 无符号数溢出防护、计数先判断后累加、负数取模异常规避
注释增强 函数添加功能 / 参数注释,关键逻辑添加 “为什么这么做” 的说明

六、扩展建议

  1. 全局变量封装:将g_dust_data封装为单例结构体,避免全局变量污染;
  2. 抗干扰增强FindCalibrationOffset添加 “最小有效计数”(如计数 < 10 则不校准);
  3. 调试扩展:增加日志打印接口(如校准偏移量、阈值更新值),便于问题定位;
  4. 参数校验:添加输入参数合法性检查(如day_index超出范围时容错);
  5. 补偿回退:增加补偿值上限,超出合理范围则回退到 EEPROM 默认值。

七、关键知识点备注

7.1 负数取模异常规避

// 优化后写法:+INDEX_SCOPE保证被除数为正,取模结果0~INDEX_SCOPE-1
new_base_index = (old_base_index + INDEX_SCOPE + shift_num) % INDEX_SCOPE;
// 反例:直接计算可能得到负数,无符号转换后越界
new_base_index = (old_base_index + shift_num) % INDEX_SCOPE; // 错误

7.2 最频索引的意义

  • 最频索引 = 统计周期内计数最多的索引,代表 “灰尘导致的稳定基线偏移”;
  • 选最频值而非平均值:过滤偶然噪声(如单次大粉尘),匹配灰尘漂移 “慢变化、稳定” 的特性。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询