从传感器到屏幕:用STM32的ADC读取电子秤数据并显示(野火指南者实战)
从传感器到屏幕:STM32 ADC实战电子秤系统开发指南
当你第一次尝试将电子秤传感器接入STM32时,可能会被那一堆跳动的数字搞得一头雾水。我清楚地记得自己调试时,显示屏上的数值像过山车一样上下波动,而那个50g的标准砝码放上去居然显示87g——这显然不是我们想要的结果。本文将带你深入STM32的ADC世界,从信号调理到屏幕显示,构建一个稳定可靠的电子秤系统。
1. 电子秤传感器信号处理基础
电子秤传感器的核心是惠斯通电桥,它由四个应变电阻组成,当施加外力时,电阻值会发生变化,产生微弱的差分电压信号。这个信号通常在毫伏级别,典型值为0-10mV,需要经过放大才能被ADC有效采集。
信号调理电路的关键参数对比:
| 参数 | LM324方案 | OP07方案 | 理想值 |
|---|---|---|---|
| 输入失调电压 | ±2mV | ±60μV | 越小越好 |
| 温漂系数 | 7μV/°C | 0.2μV/°C | ≤1μV/°C |
| 增益带宽积 | 1MHz | 0.6MHz | ≥500kHz |
| 电源抑制比 | 65dB | 120dB | ≥100dB |
提示:OP07虽然性能优异,但需要双电源供电。如果系统只有单电源,可以考虑LTC1050等轨到轨运放。
在实际项目中,我推荐使用两级放大:
- 第一级用仪表放大器(如INA128)做高精度差分放大
- 第二级用普通运放做可调增益放大
// 典型的两点校准公式 float calibrated_weight = (raw_adc - offset) * scale_factor;2. STM32 ADC模块深度配置
STM32的ADC模块看似简单,实则暗藏玄机。ADC的精度不仅取决于芯片本身,更与配置参数密切相关。以下是经过多个项目验证的最佳配置组合:
关键配置步骤:
- 时钟分频:保证ADC时钟≤14MHz(STM32F1系列)
- 采样时间:根据信号源阻抗选择,电子秤建议55.5周期
- 触发方式:连续转换模式简化软件设计
- 对齐方式:右对齐更符合常规思维
void ADC_Init_Optimal(void) { ADC_InitTypeDef ADC_InitStructure; RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 12MHz时钟 ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = DISABLE; ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 1; ADC_Init(ADC1, &ADC_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 1, ADC_SampleTime_55Cycles5); ADC_Cmd(ADC1, ENABLE); // 启用内部校准 ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); }在调试中发现一个有趣现象:ADC的第一次读数往往不准确。解决方案是在初始化后丢弃前几次采样:
#define WARMUP_SAMPLES 5 void ADC_Warmup(void) { for(int i=0; i<WARMUP_SAMPLES; i++) { ADC_SoftwareStartConvCmd(ADC1, ENABLE); while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); ADC_GetConversionValue(ADC1); // 丢弃读数 } }3. 数字滤波与数据处理实战
原始ADC数据就像未经驯服的野马,我们需要用数字滤波技术让它变得温顺。经过多次实验,我发现组合滤波方案效果最佳:
- 滑动平均滤波:消除随机噪声
- 中值滤波:抑制突发干扰
- 一阶低通滤波:平滑数据变化
#define FILTER_WINDOW 10 uint16_t moving_avg_filter(uint16_t new_sample) { static uint16_t samples[FILTER_WINDOW] = {0}; static uint8_t index = 0; static uint32_t sum = 0; sum = sum - samples[index] + new_sample; samples[index] = new_sample; index = (index + 1) % FILTER_WINDOW; return sum / FILTER_WINDOW; }校准是另一个关键环节。我习惯使用两点校准法:
- 零点校准(空载)
- 满量程校准(已知重量)
校准数据建议存储在Flash或EEPROM中:
typedef struct { uint16_t zero_point; float scale_factor; uint32_t checksum; } CalibrationData; void save_calibration(const CalibrationData *data) { >void update_display(float weight) { static float last_weight = -1; static uint8_t tick = 0; // 仅当重量变化超过0.1g或定时刷新 if(fabs(weight - last_weight) > 0.1 || ++tick >= 10) { char buf[16]; sprintf(buf, "%6.1f g", weight); ILI9341_DispStringLine_EN(LINE(5), buf); last_weight = weight; tick = 0; } // 电池图标等每30次刷新一次 if(tick % 30 == 0) { update_battery_icon(); } }对于需要按键操作的场景,建议实现以下功能:
- 长按3秒进入校准模式
- 短按切换单位(g/kg/oz)
- 双击清零
void handle_buttons(void) { static uint32_t press_time = 0; if(BUTTON_PRESSED) { if(press_time == 0) press_time = HAL_GetTick(); } else { if(press_time > 0) { uint32_t duration = HAL_GetTick() - press_time; if(duration > 3000) { enter_calibration_mode(); } else { toggle_unit(); } press_time = 0; } } }5. 系统稳定性增强技巧
在工业现场应用中,我遇到了各种意想不到的问题,最终总结出这套稳定性方案:
电源噪声抑制:
- 在ADC参考电压引脚加10μF钽电容+0.1μF陶瓷电容
- 模拟部分使用LC滤波:22μH电感+100μF电容
- 数字与模拟地单点连接
温度补偿:
float temp_compensated_weight(float raw_weight, float temperature) { // 二阶温度补偿公式 static const float TC1 = -0.0005f; static const float TC2 = 0.0000002f; return raw_weight * (1 + TC1*temperature + TC2*temperature*temperature); }故障检测机制:
- ADC值超范围报警
- 传感器开路/短路检测
- 数据突变率监控
#define MAX_JUMP 100 void check_sensor_health(uint16_t new_sample) { static uint16_t last = 0; static uint8_t error_count = 0; if(abs(new_sample - last) > MAX_JUMP) { if(++error_count > 3) { trigger_error(ERR_SENSOR); } } else { error_count = 0; } last = new_sample; }在最近的一个项目中,这套系统实现了±0.2g的精度,即使在电机启停的干扰环境下也能稳定工作。调试过程中最耗时的不是代码编写,而是找到那个让ADC读数跳动的电源干扰源——原来是一个劣质的手机充电器在同一个插座上。
