拓冰建站拓冰建站
首页 / 资讯中心 / 正文

TM7711高精度称重滤波算法:嵌入式信号处理实战解析

简介本资源是一份面向嵌入式开发初学者与厨房秤类IoT设备开发者的技术实践包聚焦TM7711高精度称重传感器在0.1克级测量场景下的噪声抑制问题。针对传感器原始数据跳变大、内码波动超5 LSB等典型现象提供轻量、可移植的C语言滤波实现方案适用于MCU资源受限的8/16位平台。压缩包仅含2个核心文件1个.h头文件定义接口与参数1个.c源文件实现滤波逻辑总大小仅1KB结构简洁无依赖库便于快速集成到Keil、IAR或GCC嵌入式工程中。已有5022人学习下载读者可直接获取已验证的滤波算法框架包含滑动平均或加权平均等适配称重特性的稳定策略、参数调优注释及内码→克重转换参考逻辑显著降低高精度称重系统开发中的信号处理调试门槛。1. 项目概述从厨房秤到高精度信号处理的挑战最近在整理一个老项目翻出来一个关于TM7711高精度厨房秤滤波算法的C语言实现。这玩意儿乍一看就是个厨房秤但里面涉及到的信号处理、模数转换和软件滤波其实在很多嵌入式测量场景里都能用上比如电子天平、压力传感器、甚至是某些DIY的精密测量设备。TM7711这个芯片本质上是一个24位Σ-Δ模数转换器ADC专门为桥式传感器设计像称重传感器、压力传感器这些。它的核心卖点就是高分辨率能把传感器微弱的模拟信号变化转换成数字世界我们能处理的数值。但高分辨率不等于高精度。厨房秤放在那里数值可能自己在那跳一会儿多0.1克一会儿少0.1克。这背后的原因很复杂电源的纹波、环境的电磁干扰、传感器自身的微小形变和热噪声甚至你用手轻轻碰一下桌子都会在ADC的输出数据里引入噪声。我们的目标就是写一套C语言算法把这些乱七八糟的噪声尽可能地滤掉让秤稳定、准确地显示我们放上去的重量尤其是要稳住那最后一位小数0.1克的跳动。所以这个项目.zip文件里的核心绝不仅仅是读个ADC数据然后显示那么简单。它是一场与噪声的“战争”我们需要在有限的单片机资源比如常见的51、STM32、或者低功耗的MSP430里用巧妙的数学方法和程序逻辑从充满毛刺的原始数据流中提取出真实、稳定的重量信号。这涉及到对TM7711驱动、多种数字滤波器设计、以及称重特定逻辑如去皮、零点跟踪、稳定判断的深度整合。下面我就把这个项目的里里外外拆解一遍把思路、代码和踩过的坑都摊开来聊聊。2. 核心需求与方案选型背后的逻辑2.1 为什么是TM7711芯片选型考量在做低成本、高精度的称重方案时ADC的选择是关键。TM7711之所以常见于厨房秤、珠宝秤有几个硬核理由。首先它是24位Σ-Δ架构。传统的逐次逼近型ADC在达到高分辨率时对基准电压和模拟电路的要求极其苛刻成本飙升。而Σ-Δ ADC通过过采样和噪声整形把量化噪声推到高频段再用数字滤波器轻松滤掉从而用相对简单的模拟前端实现了高分辨率。TM7711在10Hz输出速率下有效位数能轻松超过20位这意味着对于满量程2mV/V的典型称重传感器它可以分辨出极其微小的电压变化对应到重量上就是0.1克甚至更小的分度值。其次它内置了可编程增益放大器PGA增益从1到128可调。称重传感器输出信号很小通常是毫伏级别。PGA可以在芯片内部先将信号放大再送给ADC进行转换这样就降低了对后续运放电路的要求简化了外围设计也减少了引入外部噪声的环节。最后它的接口是简单的SPI或类似的三线制单片机用几根GPIO就能驱动非常适合资源有限的嵌入式系统。注意TM7711有两个通道A和B厨房秤通常只用通道A接称重传感器。通道B有时会用来接温度传感器做补偿但在基础应用中往往悬空或接地编程时需注意正确配置通道选择寄存器。2.2 滤波算法目标稳定、快速、准确我们的滤波算法需要达成三个有时互相矛盾的目标稳定最终显示的重量值不能乱跳。在空秤或稳定负载时最后一位数字比如0.1g应该几乎不动或者只在正负1个数字内缓慢变化。快速放上物品后重量值应该快速收敛到真实值不能等好几秒才稳定。这关系到用户体验。准确滤除噪声的同时不能扭曲真实的重量变化。比如放上一个物品的瞬间算法要能快速响应而不是被当成噪声滤掉。基于这些目标单纯的一种滤波器往往难以胜任。比如一个很深的移动平均滤波可以让显示非常稳定但响应会变得极其迟钝放上东西后数字像蜗牛一样慢慢爬升用户体验很差。反之如果只用一次采样值响应最快但显示会跳得让人眼花缭乱。因此分层滤波或复合滤波策略是必然选择。我们需要将“抑噪”和“保真”分阶段处理。2.3 方案选型从基础到进阶的滤波策略在嵌入式称重领域经过实践检验的常见方案组合如下基础层硬件与驱动层抑噪均值滤波这是第一道防线。连续读取N次TM7711的原始ADC值比如N10然后取算术平均值。这能有效抑制高频随机噪声。N的大小需要在速度和稳定性间权衡。中值滤波在均值之前可以先对一组采样值进行中值滤波剔除偶然出现的、幅度巨大的脉冲干扰比如电磁干扰毛刺防止它们污染均值。核心层软件数字滤波器一阶滞后滤波低通滤波这是最常用、最有效的软件滤波器之一公式为Y(n) α * X(n) (1-α) * Y(n-1)。其中X(n)是当前输入经过基础层处理的值Y(n)是当前输出Y(n-1)是上一次输出α是滤波系数0α1。这个滤波器的物理意义很好理解当前输出是当前输入和上一次输出的加权平均。α越接近1响应越快但滤波效果弱α越接近0滤波效果越强响应越慢。它计算量极小非常适合单片机。滑动平均滤波维护一个固定长度的队列每次新的数据进来剔除最老的数据计算队列中所有数据的平均值。它对周期性干扰有良好抑制但会引入固定的延迟且需要占用存储队列的内存。应用层称重特定逻辑动态阈值与稳定判断这是让显示“不跳”的关键。算法需要实时计算最近一段时间内重量值的变化幅度比如标准差或最大值与最小值之差。当这个变化幅度低于一个预设的“稳定阈值”时才认为秤已经稳定此时显示的数字才可以被更新到最终显示值上。否则显示一个“正在稳定”的中间值或保持原值。零点跟踪空秤的零点会随着温度、时间漂移。算法需要能在长时间空秤且稳定的情况下缓慢地调整零点参考值防止因为零点漂移导致空秤不归零。去皮处理去皮操作不仅仅是显示置零在算法内部它意味着将当前的稳定重量值作为一个偏移量保存下来后续所有测量值都减去这个偏移量。滤波算法需要能正确处理去皮瞬间的数据跳变。在本项目中我最终采用的是一种“中值均值一阶滞后动态稳定判断”的组合拳方案。下面我们进入具体的实现细节。3. 核心模块实现与代码解析3.1 TM7711驱动编写要点驱动TM7711本质上就是模拟它的通信时序来读写寄存器。它有两种模式一种是简单的三线制数据输入DIN 数据输出DOUT 时钟SCK另一种是标准的SPI。这里以三线制为例讲解关键点。首先是初始化和配置。上电后TM7711需要一段时间稳定通常几十毫秒。然后我们要通过写入配置寄存器来设置增益、通道和输出速率。例如设置通道A、增益128、输出速率10Hz。// 假设引脚定义 #define TM7711_SCK_GPIO_PIN ... #define TM7711_DOUT_GPIO_PIN ... #define TM7711_DIN_GPIO_PIN ... // 向TM7711写入一个字节24位数据分3次写入8位 static void TM7711_WriteByte(uint8_t data) { for(uint8_t i 0; i 8; i) { if(data 0x80) { GPIO_SetBits(TM7711_DIN_GPIO_PORT, TM7711_DIN_GPIO_PIN); } else { GPIO_ResetBits(TM7711_DIN_GPIO_PORT, TM7711_DIN_GPIO_PIN); } // 产生一个时钟上升沿数据被锁存 GPIO_SetBits(TM7711_SCK_GPIO_PORT, TM7711_SCK_GPIO_PIN); Delay_us(1); // 短暂延时满足时序要求 GPIO_ResetBits(TM7711_SCK_GPIO_PORT, TM7711_SCK_GPIO_PIN); Delay_us(1); data 1; } } // 从TM7711读取24位数据 static int32_t TM7711_ReadData(void) { int32_t value 0; uint8_t i; // 等待DOUT引脚变低表示数据准备就绪 while(GPIO_ReadInputDataBit(TM7711_DOUT_GPIO_PORT, TM7711_DOUT_GPIO_PIN) 1); for(i 0; i 24; i) { GPIO_SetBits(TM7711_SCK_GPIO_PORT, TM7711_SCK_GPIO_PIN); Delay_us(1); value 1; // 先左移再读位 if(GPIO_ReadInputDataBit(TM7711_DOUT_GPIO_PORT, TM7711_DOUT_GPIO_PIN)) { value | 0x01; } GPIO_ResetBits(TM7711_SCK_GPIO_PORT, TM7711_SCK_GPIO_PIN); Delay_us(1); } // 再额外产生一个时钟上升沿为下一次转换启动做准备根据数据手册 GPIO_SetBits(TM7711_SCK_GPIO_PORT, TM7711_SCK_GPIO_PIN); Delay_us(1); GPIO_ResetBits(TM7711_SCK_GPIO_PORT, TM7711_SCK_GPIO_PIN); Delay_us(1); // TM7711输出的是24位有符号补码需要扩展到32位 if(value 0x00800000) { // 检查第24位符号位 value | 0xFF000000; // 如果是负数高8位补1 } return value; }实操心得Delay_us(1)的精度很重要。如果单片机主频不高或者有中断干扰这个延时可能不准确导致通信失败。一个更稳健的做法是用循环空指令实现精确延时或者直接使用硬件SPI接口如果芯片支持。另外一定要仔细看数据手册里关于“数据就绪”DOUT变低和“启动下一次转换”的时序要求不同厂家的7711芯片可能略有差异。3.2 分层滤波算法的C语言实现接下来是重头戏滤波算法的具体实现。我们定义一个结构体来管理滤波器的状态和参数。typedef struct { // 基础采样缓冲区用于中值/均值滤波 int32_t raw_sample_buf[RAW_SAMPLE_NUM]; // RAW_SAMPLE_NUM 例如 10 uint8_t raw_sample_index; // 一阶滞后滤波参数与状态 int32_t filtered_value; // Y(n-1) 也是最终的滤波输出 float alpha; // 滤波系数例如 0.2 // 稳定判断相关 int32_t stable_value; // 当前认定的稳定值用于显示 int32_t last_stable_value; int32_t variance_threshold; // 稳定判断阈值单位是ADC码值 uint32_t stable_counter; // 稳定计数器 } WeightFilter_t; // 初始化滤波器 void Filter_Init(WeightFilter_t *filter, float alpha, int32_t init_value, int32_t var_thresh) { memset(filter, 0, sizeof(WeightFilter_t)); for(int i0; iRAW_SAMPLE_NUM; i) { filter-raw_sample_buf[i] init_value; } filter-filtered_value init_value; filter-stable_value init_value; filter-alpha alpha; filter-variance_threshold var_thresh; } // 核心滤波与稳定判断函数 int32_t Filter_Process(WeightFilter_t *filter, int32_t new_raw_adc) { int32_t temp_value; int32_t base_value; // 1. 基础层中值滤波这里简化为去掉最大最小值后求平均替代严格中值以节省排序时间 filter-raw_sample_buf[filter-raw_sample_index] new_raw_adc; filter-raw_sample_index (filter-raw_sample_index 1) % RAW_SAMPLE_NUM; // 计算缓冲区中数据的简单均值可优化为去掉头尾极值 int64_t sum 0; for(int i0; iRAW_SAMPLE_NUM; i) { sum filter-raw_sample_buf[i]; } base_value (int32_t)(sum / RAW_SAMPLE_NUM); // 2. 核心层一阶滞后滤波 // Y(n) α * X(n) (1-α) * Y(n-1) // 为避免浮点运算使用定点数技巧alpha 0.2 20 / 100 temp_value (int32_t)(filter-alpha * base_value (1.0 - filter-alpha) * filter-filtered_value); // 或者使用整数运算temp_value (base_value * 2 filter-filtered_value * 8) / 10; (当alpha0.2时) filter-filtered_value temp_value; // 更新内部状态 // 3. 应用层动态稳定判断 int32_t diff abs(temp_value - filter-last_stable_value); if(diff filter-variance_threshold) { filter-stable_counter; if(filter-stable_counter STABLE_COUNT_LIMIT) { // 例如连续5次稳定 filter-stable_value temp_value; // 更新显示用的稳定值 filter-stable_counter STABLE_COUNT_LIMIT; // 防止溢出 } } else { // 变化超过阈值认为不稳定重置计数器 filter-stable_counter 0; filter-last_stable_value temp_value; // 更新为新的比较基准 } // 返回当前用于显示的值稳定值或上一次的稳定值 return filter-stable_value; }这个Filter_Process函数需要被周期性调用调用频率最好与TM7711的数据输出率匹配如10Hz。每次调用它都会吸入一个新的原始ADC值经过层层处理后返回一个趋于稳定的重量ADC码值。3.3 标定与重量转换滤波后的ADC码值还不是重量。我们需要通过标定建立ADC码值和实际重量的线性关系。通常采用两点标定法记录空秤时的ADC码值零点adc_zero和放置一个已知标准砝码如500克时的ADC码值adc_500g。// 标定参数 int32_t adc_zero 0; // 零点ADC值 int32_t adc_cal 0; // 标定点ADC值如500克时 float weight_per_adc 0.0; // 每个ADC码对应的重量克/码 // 标定函数在特定界面下调用 void Calibrate(WeightFilter_t *filter, float known_weight_grams) { // 1. 清空秤盘长按“去皮”进入零点标定模式 // 等待稳定后读取稳定的 filtered_value 作为 adc_zero adc_zero filter-stable_value; // 2. 放上已知重量的砝码按“确认” // 等待稳定后读取稳定的 filtered_value 作为 adc_cal adc_cal filter-stable_value; // 3. 计算系数 if(adc_cal ! adc_zero) { weight_per_adc known_weight_grams / (adc_cal - adc_zero); } } // 将滤波后的ADC值转换为重量克 float ConvertToWeight(int32_t filtered_adc) { float weight (filtered_adc - adc_zero) * weight_per_adc; // 可以在这里做四舍五入到0.1克的操作 weight ((int)(weight * 10 0.5)) / 10.0; return weight; }注意事项标定过程本身也需要在非常稳定的环境下进行。标定时使用的滤波稳定阈值可以设置得比日常使用更严格以确保标定数据的准确性。weight_per_adc这个系数最好保存在单片机的非易失性存储器如Flash或EEPROM中下次上电直接读取。4. 参数调优与性能平衡的艺术算法框架搭好了但里面的参数RAW_SAMPLE_NUM,alpha,variance_threshold,STABLE_COUNT_LIMIT直接决定了秤的“性格”。调参是个细致活需要反复实测。4.1 滤波系数α的选择响应速度 vs 平滑度alpha是一阶滞后滤波器的灵魂。它的取值需要在响应速度和平滑度之间做权衡。α较大如0.5系统对重量变化的响应非常迅速放上东西后显示值“嗖”一下就上去了。但副作用是在稳定状态下显示值的最后一位数字可能还会有可见的、缓慢的波动。这适合需要快速读数的场合但对极致稳定不友好。α较小如0.05滤波效果极强稳定时显示数字几乎像钉死一样不动。但放上物品后显示值会非常缓慢地逼近真实值用户会觉得“这秤反应好慢”。折中方案在实际项目中我常常采用“动态α”策略。当检测到重量发生剧烈变化diff很大时临时调高alpha比如到0.4让系统快速跟踪。当重量变化进入稳定阈值范围内时再将alpha切换回一个较小的值如0.1实现高平滑度。这相当于一个非线性滤波器。// 动态alpha的简单示例 float dynamic_alpha; int32_t diff abs(new_raw_adc - filter-filtered_value); // 计算新数据与当前滤波值的差 if(diff BIG_CHANGE_THRESHOLD) { dynamic_alpha 0.4f; // 快速跟踪模式 } else if(diff filter-variance_threshold) { dynamic_alpha 0.2f; // 常规跟踪模式 } else { dynamic_alpha 0.05f; // 高稳定模式 } // 使用 dynamic_alpha 进行一阶滞后滤波计算4.2 稳定阈值与计数器的设定variance_threshold稳定阈值和STABLE_COUNT_LIMIT稳定计数限制共同决定了“何时显示才算稳定”。阈值太小系统过于“敏感”很难达到稳定条件导致显示值迟迟不更新或者在不该更新的时候仍有微小波动更新造成最后一位数字偶尔跳动。阈值太大系统过于“迟钝”可能把一些真实的微小重量变化比如缓慢放置物品也误判为稳定导致显示值提前“定格”与实际重量有偏差。计数器的作用要求连续多次满足阈值条件才判定稳定这提供了抗干扰的冗余度。比如一个偶然的干扰毛刺导致一次diff超限但计数器会清零不会立即更新稳定值从而避免了显示跳动。一个实用的调试方法是让秤空载运行一段时间观察filtered_value波动的最大范围峰值。将variance_threshold设置为这个波动范围的2到3倍。STABLE_COUNT_LIMIT通常设置为3到10数值越大判定越严格稳定显示越“迟钝”。4.3 零点跟踪与防误触发零点跟踪是为了应对传感器的热漂移和蠕变。但实现时必须非常小心否则可能把用户缓慢放置的轻小物品也“跟踪”掉。安全的零点跟踪逻辑只有在重量值长时间处于“空秤区间”内比如绝对值小于10个ADC码值且系统判定为稳定时才允许进行零点跟踪。跟踪速度必须非常缓慢。例如每次采样周期只将零点adc_zero向当前filtered_value调整非常小的一个步长如1/100的差值或者每分钟只调整一次。一旦检测到重量明显超出“空秤区间”立即停止零点跟踪并锁定当前的零点值直到重量再次长时间归零。// 简易零点跟踪示例需在主循环中慢速调用 void SlowZeroTracking(WeightFilter_t *filter) { static uint32_t zero_track_timer 0; const uint32_t TRACK_INTERVAL_MS 60000; // 每分钟跟踪一次 const int32_t ZERO_DEAD_ZONE 10; // 空秤死区单位ADC码 if(GetSystemTick() - zero_track_timer TRACK_INTERVAL_MS) { zero_track_timer GetSystemTick(); int32_t current_stable_adc filter-stable_value; // 判断是否处于空秤且稳定状态 if(abs(current_stable_adc - adc_zero) ZERO_DEAD_ZONE filter-stable_counter STABLE_COUNT_LIMIT) { // 缓慢调整零点每次调整1% adc_zero adc_zero (int32_t)((current_stable_adc - adc_zero) * 0.01); // 保存新的adc_zero到EEPROM需要节拍操作 } } }5. 常见问题排查与调试技巧在实际制作和调试过程中你肯定会遇到各种问题。下面是一些典型问题及其排查思路。5.1 问题一显示值乱跳完全不稳定可能原因1电源噪声。这是最常见的问题。TM7711对电源非常敏感。使用线性稳压电源如LM1117-3.3而不是开关电源在芯片的电源引脚就近放置一个10μF的钽电容和一个0.1μF的陶瓷电容。排查用示波器观察TM7711的AVDD引脚模拟电源看纹波是否过大应小于几毫伏。如果没有示波器可以尝试用电池供电看是否改善。可能原因2传感器或连接问题。称重传感器的四根线激励、激励-、信号、信号-接触不良或者传感器本身损坏。排查测量传感器桥路的阻值通常每个桥臂350欧姆左右。检查接线是否牢固。尝试轻轻敲击传感器看ADC值是否有剧烈变化。可能原因3参考电压不稳。TM7711需要非常稳定的参考电压。如果使用芯片内部的参考源要确保其稳定。如果使用外部参考同样需要干净、低噪声的电源。可能原因4数字信号干扰。SCK和DOUT等数字信号线如果离传感器模拟信号线太近可能耦合噪声。尽量分开走线或使用屏蔽线。5.2 问题二响应速度太慢放上东西后数字爬升慢可能原因1滤波过重。alpha值太小或者RAW_SAMPLE_NUM太大STABLE_COUNT_LIMIT太大。排查与解决尝试逐步增大alpha如从0.05调到0.2减少RAW_SAMPLE_NUM如从10减到5。考虑引入前面提到的“动态α”策略。可能原因2TM7711输出速率设置过低。TM7711的输出速率是可配置的如10Hz, 40Hz, 640Hz。速率越低每次转换时间越长数据更新自然慢。排查检查初始化代码中配置寄存器是否将输出速率设为了较低的10Hz。在满足噪声要求的前提下可以尝试提高到40Hz。注意提高速率可能会增加噪声需要重新调整滤波参数。5.3 问题三去皮后空秤不回零或有微小底数可能原因1零点跟踪过于激进或逻辑有误。可能在去皮操作后零点跟踪逻辑立即开始工作错误地跟踪了一个非零值。排查检查零点跟踪的触发条件。确保去皮操作后立即将零点跟踪功能暂时禁用一段时间比如10秒或者将当前去皮后的状态设置为新的、临时的“零参考点”零点跟踪基于这个新参考点进行而不是原始的adc_zero。可能原因2机械结构应力。去皮后秤盘或传感器可能因为机械结构存在微小应力导致ADC码值并非绝对的零点。排查这是物理限制。可以在软件中设置一个“零区范围”当重量绝对值小于某个很小值时比如对应0.5克直接显示为0。这就是很多商用秤的“自动归零”功能。5.4 问题四标定后线性度不好不同重量段误差大可能原因1传感器非线性。廉价的称重传感器在满量程的两端可能存在非线性。解决两点标定只能保证零点和标定点的准确。如果要求高可以采用多点标定如0g, 100g, 500g, 满量程并采用查表法或线性插值法进行转换。对于厨房秤两点标定通常足够。可能原因2标定过程不稳定。标定时秤没有放稳或者环境有振动、气流。解决标定必须在绝对稳定的台面上进行避开风扇、空调出风口等待显示值完全稳定后再进行确认操作。5.5 调试技巧利用串口打印数据曲线最有效的调试手段之一就是把关键数据通过串口发送到电脑用串口绘图工具如Serial Plotter, CoolTerm或者自己写个Python脚本画出来看。你可以同时打印new_raw_adc原始ADC值观察噪声的原始形态。filtered_value一阶滤波后观察滤波器的平滑效果。stable_value稳定值观察稳定判断逻辑是否正确触发。diff变化量观察是否超过了设定的阈值。通过图形你可以直观地看到放上物品、取下物品、稳定、跳变等各个阶段数据是如何变化的从而精准地调整alpha、variance_threshold等参数。这比盲目修改代码、反复烧录测试要高效得多。最后分享一个我个人的体会高精度滤波没有“银弹”参数。一套参数在一台秤上表现完美换一个传感器、换一个电源、甚至换一个安装外壳可能就需要重新微调。核心在于理解每个参数和模块对系统动态特性的影响是影响响应速度还是影响稳定精度然后掌握基于数据观察的调试方法。这个TM7711的项目本质上是一个经典的嵌入式信号处理案例把这些思路吃透了以后做压力测量、温度采集、声音信号处理等等都会触类旁通。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门