前言在全国大学生电子设计竞赛的最后冲刺阶段当所有队伍的硬件都能动起来时比拼的就是**“数据的艺术”**。为什么你的超声波/ToF测距总是偶尔跳出一个极其离谱的值为什么你的传感器读数和实际物理量总是呈“非线性”偏差怎么乘比例系数都不对为什么你的小车/仪器在测评现场遇到一点静电或者电机火花屏幕就卡死不再刷新本文将为你揭秘电赛国一队伍压箱底的**“三大软件法宝”一维卡尔曼滤波神级平滑、最小二乘法曲线拟合神级校准、以及看门狗自愈系统免死金牌**。把这些算法塞进你的代码库你的系统将百毒不侵TOC一、 神级数据平滑一维卡尔曼滤波Kalman Filter痛点你在读取 ADC 电压、超声波距离、或者电机转速时数据总是在真实值上下剧烈抖动。如果你用简单的均值滤波会导致系统响应极其迟钝比如电压突变了你的数据过了半秒才跟上去。卡尔曼滤波的降维解释它本质上是一个“动态权重分配器”。它通过结合**“上一时刻的预测值”和“这一时刻的传感器测量值”**来得出一个最优结果。如果传感器噪声大它就更相信预测。如果系统发生突变它能迅速调整权重去相信传感器。结果波形极其平滑且几乎没有滞后 C语言极简实现一维卡尔曼直接抄作业不需要学矩阵运算对于大多数单变量传感器如电压、距离一维卡尔曼滤波的代码只有短短几行codeCtypedef struct { float LastP; // 上次估算协方差 float Now_P; // 当前估算协方差 float out; // 滤波输出值 float Kg; // 卡尔曼增益 float Q; // 过程噪声协方差 (系统自身的波动越小越平滑但响应越慢) float R; // 测量噪声协方差 (传感器的噪声越大越平滑但响应越慢) } Kalman_TypeDef; /** * brief 初始化卡尔曼滤波器参数 */ void Kalman_Init(Kalman_TypeDef *kf, float q, float r) { kf-LastP 1.0f; // 初始协方差随便给个非零值 kf-Now_P 0.0f; kf-out 0.0f; kf-Kg 0.0f; kf-Q q; // 推荐值0.001 ~ 0.01 kf-R r; // 推荐值0.1 ~ 1.0 (根据传感器噪声大小调节) } /** * brief 执行一次一维卡尔曼滤波 * param kf: 滤波器结构体指针 * param input: 传感器刚刚读取到的原始脏数据 * retval 滤波后的丝滑数据 */ float Kalman_Filter(Kalman_TypeDef *kf, float input) { // 1. 预测协方差方程当前的先验协方差 上次的协方差 过程噪声 kf-Now_P kf-LastP kf-Q; // 2. 卡尔曼增益方程Kg P / (P R) kf-Kg kf-Now_P / (kf-Now_P kf-R); // 3. 更新最优估计值方程输出 上次输出 Kg * (测量值 - 上次输出) kf-out kf-out kf-Kg * (input - kf-out); // 4. 更新协方差方程P (1 - Kg) * P kf-LastP (1 - kf-Kg) * kf-Now_P; return kf-out; }实战用法对于一个跳动在 10.0 到 10.8 之间的带噪电压送入该函数输出的波形将稳如死狗般停在 10.4。如果电压突然变到 15.0它能在几个循环内瞬间跟上绝不拖泥带水二、 拒绝离群点中值去极值平均滤波法痛点超声波或者串口通信时由于电磁干扰偶尔会跳出一个极其离谱的“飞点”比如正常一直是 50cm突然来了一个 400cm。卡尔曼滤波虽然强但遇到这种瞬间的巨大突变也会被带偏一下。防飞点最强利器去极值平均滤波。核心逻辑连续采集 N 个数据放进数组 - 用冒泡排序从小到大排列 - 掐头去尾扔掉最大和最小的极端值 - 剩下的取平均。codeC#define FILTER_N 9 // 采样点数建议用奇数 float Median_Average_Filter(float new_data) { static float buf[FILTER_N]; static uint8_t count 0; float temp; float sum 0; // 1. 滑动窗口存入新数据 buf[count] new_data; if(count FILTER_N) count 0; // 拷贝一份数组用于排序不破坏原时间序列 float sort_buf[FILTER_N]; for(int i0; iFILTER_N; i) sort_buf[i] buf[i]; // 2. 冒泡排序 (从小到大) for(int i 0; i FILTER_N - 1; i) { for(int j 0; j FILTER_N - 1 - i; j) { if(sort_buf[j] sort_buf[j1]) { temp sort_buf[j]; sort_buf[j] sort_buf[j1]; sort_buf[j1] temp; } } } // 3. 掐头去尾去掉 2 个最大值去掉 2 个最小值 for(int i 2; i FILTER_N - 2; i) { sum sort_buf[i]; } // 返回剩余 5 个元素的平均值 return (sum / (FILTER_N - 4)); }三、 终极校准神器最小二乘法Least Squares Fitting痛点仪器仪表题你测出来的 ADC 值是 X真实的物理量是 Y。如果你直接用 Y K*X B 两点定标你会发现传感器往往是非线性的。两端准了中间就不准中间准了两端又差很多。降维打击多点多项式拟合。在赛前先测量 10 组 (X, Y) 的对应关系比如测 10 个不同距离下的 ADC 电压值。然后使用最小二乘法算出一条完美的曲线方程。虽然在单片机里跑复杂的矩阵求逆很费力但电赛是开卷的我们完全可以借助电脑MATLAB 或 Python算好系数再塞进 STM32。比赛实战流程超级提分秘籍写一段测试代码在 OLED 上显示当前的 ADC 原码值 X。拿尺子/万用表作为标准件记录 10 组真实值 Y 和对应的 X。打开电脑用 Python 跑以下代码获取拟合公式codePythonimport numpy as np # 你在赛场上测到的 10 组数据 X_ADC np.array([200, 510, 820, 1105, 1450, 1780, 2010, 2400, 2800, 3100]) Y_Real np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]) # 使用多项式拟合这里使用 二次多项式拟合 (Y aX^2 bX c) # 如果非线性很强可以改成 3 阶 coefficients np.polyfit(X_ADC, Y_Real, 2) print(f生成的公式: Y {coefficients[0]} * X^2 {coefficients[1]} * X {coefficients[2]})将算出来的三个常数直接放进 STM32 的代码里codeC// STM32 测量代码 float Calculate_Real_Value(float adc_val) { float a 0.00000045; // 电脑算出的二次项系数 float b 0.002315; // 电脑算出的一次项系数 float c 0.125; // 电脑算出的常数项 // Y a*X^2 b*X c return (a * adc_val * adc_val) (b * adc_val) c; }效果你的系统将拥有全量程极其恐怖的精度评委随便拿个东西一测误差不到 1%直接满分四、 赛场免死金牌IWDG 看门狗与软件自愈机制真实惨案某队伍做电机控制现场测评时电机启动的瞬间产生了一个巨大的火花EMI 强电磁干扰。单片机的 IIC 总线瞬间锁死整个程序卡在 while 循环里等 IIC 响应屏幕定格。评委站在旁边摇头痛失国一。为什么不用看门狗Watchdog看门狗是单片机内部的一个独立倒计时硬件通常使用独立的低频内部时钟 LSI极其可靠。如果你不按时“喂狗重置倒计时”计时器一旦归零硬件会强制重启整个单片机STM32 独立看门狗IWDG标准配置与避坑配置在 CubeMX 中开启 IWDG分频和重装载值设置倒计时为1 秒钟对于通常控制系统1秒不刷新就算死机了。喂狗位置千万别喂错❌错误做法把喂狗函数 HAL_IWDG_Refresh(hiwdg); 放在定时器中断里。原因如果你的 main 循环卡死在 IIC 或者 delay 里定时器中断可能依然在正常运行此时狗被正常喂食但系统其实已经瘫痪了。✅正确做法必须把喂狗放在 main 函数的主循环 while(1) 的最末尾进阶玩法死机恢复后保留状态极客操作系统重启确实救命了但如果重启后小车的状态归零了评委一样算你失败。单片机重启RAM 里的数据默认会丢失但我们可以利用RTC 的备份寄存器Backup Registers。它在单片机复位时不会被清空codeCint main(void) { HAL_Init(); // ... 初始化硬件 ... // 1. 读取备份寄存器判断是否是死机重启的 uint32_t last_state HAL_RTCEx_BKUPRead(hrtc, RTC_BKP_DR1); if (last_state 0) { // 第一次正常开机一切从头开始 System_State STATE_INIT; } else { // 啊哦我们经历了看门狗复位恢复死机前的状态 System_State last_state; } while(1) { // 执行当前状态任务 Run_System_Tasks(); // 2. 随时把当前核心状态写入备份寄存器 HAL_RTCEx_BKUPWrite(hrtc, RTC_BKP_DR1, System_State); // 3. 在主循环最后喂狗 HAL_IWDG_Refresh(hiwdg); } }评委视角电磁干扰导致你的小车屏幕闪了一下但它立刻原地恢复了继续跑。评委会认为这不仅不是失误反而体现了你们队伍极其强大的系统鲁棒性设计能力直接拉满印象分结语硬件决定了电赛的下限而软件算法与系统鲁棒性决定了电赛的上限。把卡尔曼滤波用于那些跳动的脏数据用去极值平均拦截突发的强干扰用多项式拟合驯服非线性的传感器最后用一只不知疲倦的看门狗守护整座堡垒。在电赛的考场上从来没有奇迹只有无数次抗干扰设计带来的必然。预祝各位电赛开发者数据美如画波形稳如狗干扰不宕机顺势斩国一觉得这套“防翻车算法库”硬核的话千万别白嫖点赞 ⭐收藏遇到数据跳动或现场死机时赶紧把这套代码粘进去续命你在调传感器数据或者打比赛时遇到过什么“诡异”的数据飘逸或死机现象欢迎在评论区分享你的“炸机经历”博主在线为你写代码破局
【电赛保姆级教程】别让噪点毁了你的国一!电赛高阶算法库:卡尔曼滤波、最小二乘法与系统免死金牌(附C源码)
发布时间:2026/6/3 21:15:51
前言在全国大学生电子设计竞赛的最后冲刺阶段当所有队伍的硬件都能动起来时比拼的就是**“数据的艺术”**。为什么你的超声波/ToF测距总是偶尔跳出一个极其离谱的值为什么你的传感器读数和实际物理量总是呈“非线性”偏差怎么乘比例系数都不对为什么你的小车/仪器在测评现场遇到一点静电或者电机火花屏幕就卡死不再刷新本文将为你揭秘电赛国一队伍压箱底的**“三大软件法宝”一维卡尔曼滤波神级平滑、最小二乘法曲线拟合神级校准、以及看门狗自愈系统免死金牌**。把这些算法塞进你的代码库你的系统将百毒不侵TOC一、 神级数据平滑一维卡尔曼滤波Kalman Filter痛点你在读取 ADC 电压、超声波距离、或者电机转速时数据总是在真实值上下剧烈抖动。如果你用简单的均值滤波会导致系统响应极其迟钝比如电压突变了你的数据过了半秒才跟上去。卡尔曼滤波的降维解释它本质上是一个“动态权重分配器”。它通过结合**“上一时刻的预测值”和“这一时刻的传感器测量值”**来得出一个最优结果。如果传感器噪声大它就更相信预测。如果系统发生突变它能迅速调整权重去相信传感器。结果波形极其平滑且几乎没有滞后 C语言极简实现一维卡尔曼直接抄作业不需要学矩阵运算对于大多数单变量传感器如电压、距离一维卡尔曼滤波的代码只有短短几行codeCtypedef struct { float LastP; // 上次估算协方差 float Now_P; // 当前估算协方差 float out; // 滤波输出值 float Kg; // 卡尔曼增益 float Q; // 过程噪声协方差 (系统自身的波动越小越平滑但响应越慢) float R; // 测量噪声协方差 (传感器的噪声越大越平滑但响应越慢) } Kalman_TypeDef; /** * brief 初始化卡尔曼滤波器参数 */ void Kalman_Init(Kalman_TypeDef *kf, float q, float r) { kf-LastP 1.0f; // 初始协方差随便给个非零值 kf-Now_P 0.0f; kf-out 0.0f; kf-Kg 0.0f; kf-Q q; // 推荐值0.001 ~ 0.01 kf-R r; // 推荐值0.1 ~ 1.0 (根据传感器噪声大小调节) } /** * brief 执行一次一维卡尔曼滤波 * param kf: 滤波器结构体指针 * param input: 传感器刚刚读取到的原始脏数据 * retval 滤波后的丝滑数据 */ float Kalman_Filter(Kalman_TypeDef *kf, float input) { // 1. 预测协方差方程当前的先验协方差 上次的协方差 过程噪声 kf-Now_P kf-LastP kf-Q; // 2. 卡尔曼增益方程Kg P / (P R) kf-Kg kf-Now_P / (kf-Now_P kf-R); // 3. 更新最优估计值方程输出 上次输出 Kg * (测量值 - 上次输出) kf-out kf-out kf-Kg * (input - kf-out); // 4. 更新协方差方程P (1 - Kg) * P kf-LastP (1 - kf-Kg) * kf-Now_P; return kf-out; }实战用法对于一个跳动在 10.0 到 10.8 之间的带噪电压送入该函数输出的波形将稳如死狗般停在 10.4。如果电压突然变到 15.0它能在几个循环内瞬间跟上绝不拖泥带水二、 拒绝离群点中值去极值平均滤波法痛点超声波或者串口通信时由于电磁干扰偶尔会跳出一个极其离谱的“飞点”比如正常一直是 50cm突然来了一个 400cm。卡尔曼滤波虽然强但遇到这种瞬间的巨大突变也会被带偏一下。防飞点最强利器去极值平均滤波。核心逻辑连续采集 N 个数据放进数组 - 用冒泡排序从小到大排列 - 掐头去尾扔掉最大和最小的极端值 - 剩下的取平均。codeC#define FILTER_N 9 // 采样点数建议用奇数 float Median_Average_Filter(float new_data) { static float buf[FILTER_N]; static uint8_t count 0; float temp; float sum 0; // 1. 滑动窗口存入新数据 buf[count] new_data; if(count FILTER_N) count 0; // 拷贝一份数组用于排序不破坏原时间序列 float sort_buf[FILTER_N]; for(int i0; iFILTER_N; i) sort_buf[i] buf[i]; // 2. 冒泡排序 (从小到大) for(int i 0; i FILTER_N - 1; i) { for(int j 0; j FILTER_N - 1 - i; j) { if(sort_buf[j] sort_buf[j1]) { temp sort_buf[j]; sort_buf[j] sort_buf[j1]; sort_buf[j1] temp; } } } // 3. 掐头去尾去掉 2 个最大值去掉 2 个最小值 for(int i 2; i FILTER_N - 2; i) { sum sort_buf[i]; } // 返回剩余 5 个元素的平均值 return (sum / (FILTER_N - 4)); }三、 终极校准神器最小二乘法Least Squares Fitting痛点仪器仪表题你测出来的 ADC 值是 X真实的物理量是 Y。如果你直接用 Y K*X B 两点定标你会发现传感器往往是非线性的。两端准了中间就不准中间准了两端又差很多。降维打击多点多项式拟合。在赛前先测量 10 组 (X, Y) 的对应关系比如测 10 个不同距离下的 ADC 电压值。然后使用最小二乘法算出一条完美的曲线方程。虽然在单片机里跑复杂的矩阵求逆很费力但电赛是开卷的我们完全可以借助电脑MATLAB 或 Python算好系数再塞进 STM32。比赛实战流程超级提分秘籍写一段测试代码在 OLED 上显示当前的 ADC 原码值 X。拿尺子/万用表作为标准件记录 10 组真实值 Y 和对应的 X。打开电脑用 Python 跑以下代码获取拟合公式codePythonimport numpy as np # 你在赛场上测到的 10 组数据 X_ADC np.array([200, 510, 820, 1105, 1450, 1780, 2010, 2400, 2800, 3100]) Y_Real np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]) # 使用多项式拟合这里使用 二次多项式拟合 (Y aX^2 bX c) # 如果非线性很强可以改成 3 阶 coefficients np.polyfit(X_ADC, Y_Real, 2) print(f生成的公式: Y {coefficients[0]} * X^2 {coefficients[1]} * X {coefficients[2]})将算出来的三个常数直接放进 STM32 的代码里codeC// STM32 测量代码 float Calculate_Real_Value(float adc_val) { float a 0.00000045; // 电脑算出的二次项系数 float b 0.002315; // 电脑算出的一次项系数 float c 0.125; // 电脑算出的常数项 // Y a*X^2 b*X c return (a * adc_val * adc_val) (b * adc_val) c; }效果你的系统将拥有全量程极其恐怖的精度评委随便拿个东西一测误差不到 1%直接满分四、 赛场免死金牌IWDG 看门狗与软件自愈机制真实惨案某队伍做电机控制现场测评时电机启动的瞬间产生了一个巨大的火花EMI 强电磁干扰。单片机的 IIC 总线瞬间锁死整个程序卡在 while 循环里等 IIC 响应屏幕定格。评委站在旁边摇头痛失国一。为什么不用看门狗Watchdog看门狗是单片机内部的一个独立倒计时硬件通常使用独立的低频内部时钟 LSI极其可靠。如果你不按时“喂狗重置倒计时”计时器一旦归零硬件会强制重启整个单片机STM32 独立看门狗IWDG标准配置与避坑配置在 CubeMX 中开启 IWDG分频和重装载值设置倒计时为1 秒钟对于通常控制系统1秒不刷新就算死机了。喂狗位置千万别喂错❌错误做法把喂狗函数 HAL_IWDG_Refresh(hiwdg); 放在定时器中断里。原因如果你的 main 循环卡死在 IIC 或者 delay 里定时器中断可能依然在正常运行此时狗被正常喂食但系统其实已经瘫痪了。✅正确做法必须把喂狗放在 main 函数的主循环 while(1) 的最末尾进阶玩法死机恢复后保留状态极客操作系统重启确实救命了但如果重启后小车的状态归零了评委一样算你失败。单片机重启RAM 里的数据默认会丢失但我们可以利用RTC 的备份寄存器Backup Registers。它在单片机复位时不会被清空codeCint main(void) { HAL_Init(); // ... 初始化硬件 ... // 1. 读取备份寄存器判断是否是死机重启的 uint32_t last_state HAL_RTCEx_BKUPRead(hrtc, RTC_BKP_DR1); if (last_state 0) { // 第一次正常开机一切从头开始 System_State STATE_INIT; } else { // 啊哦我们经历了看门狗复位恢复死机前的状态 System_State last_state; } while(1) { // 执行当前状态任务 Run_System_Tasks(); // 2. 随时把当前核心状态写入备份寄存器 HAL_RTCEx_BKUPWrite(hrtc, RTC_BKP_DR1, System_State); // 3. 在主循环最后喂狗 HAL_IWDG_Refresh(hiwdg); } }评委视角电磁干扰导致你的小车屏幕闪了一下但它立刻原地恢复了继续跑。评委会认为这不仅不是失误反而体现了你们队伍极其强大的系统鲁棒性设计能力直接拉满印象分结语硬件决定了电赛的下限而软件算法与系统鲁棒性决定了电赛的上限。把卡尔曼滤波用于那些跳动的脏数据用去极值平均拦截突发的强干扰用多项式拟合驯服非线性的传感器最后用一只不知疲倦的看门狗守护整座堡垒。在电赛的考场上从来没有奇迹只有无数次抗干扰设计带来的必然。预祝各位电赛开发者数据美如画波形稳如狗干扰不宕机顺势斩国一觉得这套“防翻车算法库”硬核的话千万别白嫖点赞 ⭐收藏遇到数据跳动或现场死机时赶紧把这套代码粘进去续命你在调传感器数据或者打比赛时遇到过什么“诡异”的数据飘逸或死机现象欢迎在评论区分享你的“炸机经历”博主在线为你写代码破局