实时追踪带DC漂移的光电探测器信号最小值的优化方案问询
光电信号DC漂移下的最值追踪与触发优化方案
问题背景
- 经硬件低通+高通滤波放大后的光电探测器模拟信号存在DC漂移(DC walk),导致实时最小值追踪困难
- 当前每50次ADC读数将最小值上调1%的方案引入过多噪声,干扰触发逻辑(触发需满足:1. 信号处于负斜率;2. 数据低于平均值;3. 数据为最小值上方5%)
- 信号周期范围0.6Hz-2Hz,周期波动±20%,无法通过历史周期预判触发时机;阀门动作延迟较长,需在脉冲波负斜率段确定稳定触发点
- 调整窗口大小等尝试后,最值追踪效果仍不理想
现有核心代码
最值追踪核心片段
//Determine is value is outside max or min if(data > max) max = data; if(data < min) min = data; //Reset function to bring the bounds in every 50 cycles if(rstCntr>=50){ rstCntr=0; max = max/1.01; min = min*1.01; if(min <= 1200) min = 1200; if(max >= 1900) max = 1900; }
完整主函数
int main(void) { while (1) { //****************************************************************************** //** Process analog sensor data, calculate HR, and trigger solenoids //** At some point this should probably be moved to a function call in System.c, //** but I don't want to mess with it right now since it works (Adam 11/23/2022) //****************************************************************************** //Read Analog Data for Sensor data = ADC1_ReadChannel(7); //Buffer the sensor data for peak/valley detection for(int buf=3;buf>0;buf--){ dataBuffer[buf] = dataBuffer[buf-1]; } dataBuffer[0] = data; //Look for a valley //Considered a valley is the 3 most recent data points are increasing //This helps avoid noise in the signal uint8_t count = 0; for(int buf=0;buf<3;buf++) { if(dataBuffer[buf]>dataBuffer[buf+1]) count++; } if(count >= 3) currentSlope = true; //if the last 3 points are increasing, we just passed a valley else currentSlope = false; //not a valley // Track the data stream max and min to calculate a signal average // The signal average is used to determine when we are on the bottom end of the waveform. if(data > max) max = data; if(data < min) min = data; if(rstCntr>=50){ //Make sure we are tracking the signal by moving min and max in every 200 samples rstCntr=0; max = max/1.01; min = min*1.01; if(min <= 1200) min = 1200; //average*.5; //Probably finger was removed from sensor, move back up if(max >= 1900) max = 1900; //Need to see if this really works consistently } rstCntr++; average = ((uint16_t)min+(uint16_t)max)/2; trigger = min; //Variable is only used for debug output, resetting each time around if(data < average && currentSlope == false && //falling edge of signal data <= (((average-min)*.03)+min) && //Threshold above the min { FireSolenoids(); } } return 1; }
优化方案
1. 自适应滑动窗口最值追踪
针对信号周期波动的特点,用滑动窗口限定最值追踪的时间范围,既覆盖完整信号周期,又避免DC漂移的长期影响:
- 先通过最近的峰谷间隔估算当前周期,将窗口大小设为
周期×ADC采样率×0.8(确保覆盖大部分有效信号段) - 窗口内维护环形队列,每次新数据入队时移除最旧数据,实时更新窗口内的max和min;若移除的是当前max/min,则重新遍历窗口获取新的最值
- 代码示例:
#define MAX_WINDOW_SIZE 200 // 根据ADC采样率调整,比如100Hz采样时,2Hz信号周期为50点,窗口设80-100点足够 uint16_t windowBuffer[MAX_WINDOW_SIZE]; uint8_t windowHead = 0; uint16_t currentWindowMax = 0; uint16_t currentWindowMin = 4095; // 假设为12位ADC,满量程4095 void updateWindow(uint16_t newData) { uint16_t oldData = windowBuffer[windowHead]; windowBuffer[windowHead] = newData; windowHead = (windowHead + 1) % MAX_WINDOW_SIZE; // 更新窗口最大值 if (newData > currentWindowMax) { currentWindowMax = newData; } else if (oldData == currentWindowMax) { // 移除了旧最大值,重新遍历窗口获取新最大值 currentWindowMax = 0; for (int i = 0; i < MAX_WINDOW_SIZE; i++) { if (windowBuffer[i] > currentWindowMax) currentWindowMax = windowBuffer[i]; } } // 更新窗口最小值 if (newData < currentWindowMin) { currentWindowMin = newData; } else if (oldData == currentWindowMin) { // 移除了旧最小值,重新遍历窗口获取新最小值 currentWindowMin = 4095; for (int i = 0; i < MAX_WINDOW_SIZE; i++) { if (windowBuffer[i] < currentWindowMin) currentWindowMin = windowBuffer[i]; } } }
2. 指数平滑最值追踪
通过指数平滑算法对max和min进行滤波,兼顾DC漂移跟踪与噪声抑制:
- 对新数据的峰值/谷值进行加权更新,非峰值/谷值时缓慢调整最值以跟踪漂移
- alpha参数建议取0.01-0.05,可根据实际信号调试调整;初始化时用前20个数据的均值作为初始值
- 代码示例:
float alpha = 0.02; // 可调参数,平衡漂移跟踪速度与噪声抑制 float smoothedMax = 0.0; float smoothedMin = 4095.0; // 初始化平滑最值 void initSmoothing() { uint32_t sum = 0; for (int i = 0; i < 20; i++) { uint16_t data = ADC1_ReadChannel(7); sum += data; } smoothedMax = sum / 20.0; smoothedMin = sum / 20.0; } // 更新平滑最值 void updateSmoothedMaxMin(uint16_t data) { if (data > smoothedMax) { smoothedMax = alpha * data + (1 - alpha) * smoothedMax; } else { // 缓慢衰减最大值,跟踪DC漂移 smoothedMax = (1 - alpha/2) * smoothedMax; } if (data < smoothedMin) { smoothedMin = alpha * data + (1 - alpha) * smoothedMin; } else { // 缓慢抬升最小值,跟踪DC漂移 smoothedMin = (1 + alpha/2) * smoothedMin; } // 保留原有边界限制 if (smoothedMin <= 1200) smoothedMin = 1200; if (smoothedMax >= 1900) smoothedMax = 1900; }
3. 触发逻辑优化
减少噪声误触发,确保在负斜率段稳定触发:
- 扩展斜率判断的数据点数量(从3点增至5点),只有连续4次下降才判定为负斜率
- 增加触发计数机制,连续3次满足所有触发条件才执行阀门动作,避免单次噪声干扰
- 代码示例:
uint8_t triggerCount = 0; #define TRIGGER_THRESHOLD_COUNT 3 // 连续满足次数阈值 // 优化斜率判断 uint8_t count = 0; for (int buf = 0; buf < 4; buf++) { // 5个数据点,4次比较 if (dataBuffer[buf] > dataBuffer[buf+1]) count++; } currentSlope = (count >= 4); // 连续4次下降,判定为负斜率 // 计算触发阈值与判断 float average = (smoothedMin + smoothedMax) / 2.0; float triggerLevel = smoothedMin + (average - smoothedMin) * 0.05; // 最小值上方5% if (data < average && !currentSlope && data <= triggerLevel) { if (triggerCount < TRIGGER_THRESHOLD_COUNT) { triggerCount++; } else { FireSolenoids(); triggerCount = 0; // 触发后重置计数 } } else { triggerCount = 0; // 不满足条件时重置计数 }
内容的提问来源于stack exchange,提问作者Adam
相关产品推荐
相关产品推荐

