/////////////////////////////////////////////////////////////////
#define ADDE_DATA_FLOW_MAX 5 // 建立数据流最大数量
#define ADDE_FREQUENCY 0.5f // 允许运算系数
#define ADDE_BOUNCE_THRESH 2 // 允许小幅波动阈值(不更新数据,节省算力)
#define ADDE_UPDATE_THRESH 50 // 立刻更新数据的阈值
#define ADDE_CONT_INC_VALID 3 // 连续增加数据有效次数
typedef struct
{
uint16_t validData; // 当前有效值
uint16_t currentData; // 实时数据
uint8_t contIncrease; // 数据连续增加计数
uint8_t sign; // 数据记录标号(高位为数据有效标记位,1表示已填充一周)
uint16_t dataFlow[ADDE_DATA_FLOW_MAX]; // 记录相关数据
uint32_t sumDataADC; // 数据总和
uint16_t dataADC; // 数据有效值(平均值)
} SAD_DataFlow; // AD数据转换结果记录结构
/**
* @brief 快速记录数据流并计算有效值
* @param target 数据流结构体指针
* @param data 新的实时数据
*/
void DataFlowRecordFast(SAD_DataFlow* target, uint16_t data)
{
if (target == NULL) return; // 增加空指针检查,提高健壮性
uint8_t index;
uint16_t currentADC = target->dataADC;
uint16_t dataDiff;
uint16_t maxData, minData;
// 计算当前数据与历史数据的差值
if (currentADC > data)
{
maxData = currentADC;
minData = data;
}
else
{
maxData = data;
minData = currentADC;
}
dataDiff = maxData - minData;
// 根据数据差值更新有效值
if (dataDiff > ADDE_UPDATE_THRESH)
{
// 数据变化超过阈值,需要快速更新
if (target->contIncrease >= ADDE_CONT_INC_VALID)
{
target->validData = data; // 达到连续增加阈值,立即更新
target->contIncrease = 0; // 重置连续计数
}
else
{
target->contIncrease++; // 增加连续计数
}
}
else
{
// 数据变化在阈值范围内
target->contIncrease = 0; // 重置连续计数
if (dataDiff > ADDE_BOUNCE_THRESH)
{
// 数据有一定波动,进行平滑更新
if (currentADC > data)
{
target->validData = currentADC - (uint16_t)(dataDiff * ADDE_FREQUENCY);
}
else
{
target->validData = currentADC + (uint16_t)(dataDiff * ADDE_FREQUENCY);
}
}
// 数据波动小于等于小幅波动阈值,不更新validData
}
// 更新数据流循环缓冲区
index = target->sign & 0x7F; // 获取当前索引(低7位)
// 移除最旧数据,添加新数据
target->sumDataADC = target->sumDataADC - target->dataFlow[index] + target->validData;
target->dataFlow[index] = target->validData;
// 更新索引和标记
target->sign++;
index++;
if (index >= ADDE_DATA_FLOW_MAX)
{
target->sign |= 0x80; // 设置高位标记,表示数据已填充一周
}
// 计算新的平均值
target->dataADC = target->sumDataADC / ADDE_DATA_FLOW_MAX;
}
这段代码的主要功能是对实时采集的数据进行平滑处理和历史记录管理,常用于需要对连续采集的数据(如传感器、AD 转换结果等)进行滤波和有效值计算的场景。
具体来说,它实现了以下核心功能:
数据平滑处理
对输入的实时数据(data)进行滤波处理,避免因数据小幅波动导致的频繁变化:
当数据变化超过阈值(ADDE_UPDATE_THRESH)时,通过连续计数判断是否立即更新有效值
当数据变化在一定范围内(ADDE_BOUNCE_THRESH到ADDE_UPDATE_THRESH之间)时,进行渐进式平滑更新
当数据波动极小时(≤ADDE_BOUNCE_THRESH),不更新有效值以节省计算资源
历史数据记录
使用循环缓冲区(dataFlow数组)记录最近的ADDE_DATA_FLOW_MAX个有效值,通过以下方式管理:
每次更新时,移除最旧的数据,添加新的有效值
维护所有记录数据的总和(sumDataADC),用于快速计算平均值
用sign标记记录状态(是否已填满一轮缓冲区)
有效值计算
通过历史数据的平均值(dataADC)作为当前的参考基准,实现对实时数据的动态校准。
简单来说,这段代码就像一个 "数据过滤器",它能去掉原始数据中的噪声波动,保留真实的趋势变化,同时记录历史数据用于后续分析,非常适合嵌入式系统中的传感器数据处理场景。
离线
学习了,另外currentData这个参数好像没有使用到?
研究了下,确实是。看程序用的是target->dataADC
离线
index会溢出,超过5了,那么这里会写入不该写的地方:target->dataFlow[index] = target->validData;
离线
把这行: target->sign |= 0x80; // 设置高位标记,表示数据已填充一周
改成: target->sign = 0x80; // 设置高位标记,表示数据已填充一周
可以解决溢出问题,但是如果平均数太多,响应比较慢
要既快又稳,卡尔曼滤波好一点
离线
卡尔曼滤波算法(供参考),实测效果不错。
#include <math.h>
// 卡尔曼滤波结构体
typedef struct {
float Q; // 过程噪声协方差(系统噪声)
float R; // 测量噪声协方差(传感器噪声)
float x; // 状态估计值
float P; // 估计协方差
float K; // 卡尔曼增益
} KalmanFilter;// 初始化
void Kalman_Init(KalmanFilter *kf, float Q, float R, float initial_value) {
kf->Q = Q;
kf->R = R;
kf->x = initial_value;
kf->P = 1; // 初始协方差可设大一些
kf->K = 0;
}// 执行一次滤波
float Kalman_Update(KalmanFilter *kf, float measurement) {
// 预测
kf->P = kf->P + kf->Q;// 更新卡尔曼增益
kf->K = kf->P / (kf->P + kf->R);// 更新估计值
kf->x = kf->x + kf->K * (measurement - kf->x);// 更新协方差
kf->P = (1 - kf->K) * kf->P;return kf->x;
}
使用方法:
KalmanFilter kf;
Kalman_Init(&kf, 1e-5, 2.5e-5, 0.0); // Q, R, 初始值float raw_value, filtered_value;
// 在循环中调用
raw_value = get_sensor_value(); // 获取传感器数据
filtered_value = Kalman_Update(&kf, raw_value);
离线