1. 硬件选型与电路连接第一次接触称重系统开发时最让我头疼的就是硬件选型。市面上各种型号的称重传感器和ADC芯片让人眼花缭乱经过多次踩坑后我发现STM32F103C8T6HX711这个组合特别适合新手入门。STM32F103C8T6作为经典的Cortex-M3内核MCU72MHz主频完全够用而且价格只要十几块钱。HX711则是专为电子秤设计的24位ADC内置可编程放大器省去了复杂的外围电路设计。具体到硬件连接我建议直接购买现成的HX711模块某宝上5块钱左右这样能避免很多麻烦。模块上通常会有明确的标识VCC接3.3V或5V实测两种电压都可用GND接地DOUT接MCU的任意GPIO如PA11SCK接另一个GPIO如PA12称重传感器一般有四根线红、黑、白、绿对应接在HX711模块的E、E-、A、A-端子。这里有个小技巧如果发现重量显示为负数把白绿两根线对调就能解决。我第一次调试时就遇到这个问题折腾了半天才发现是线序接反了。2. 驱动代码编写实战硬件连接好后接下来就是最关键的代码部分。HX711的通信协议其实很简单就是通过GPIO模拟时序来读取数据。我整理了一个经过实战检验的驱动方案包含三个关键函数首先是初始化函数需要配置两个GPIO的工作模式void HX711_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // SCK引脚配置为推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_12; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // DOUT引脚配置为浮空输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_11; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); }数据读取函数是核心需要注意严格的时序控制uint32_t Read_HX711(void) { uint32_t value 0; GPIO_SetBits(GPIOA, GPIO_Pin_12); // SCK高电平 while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_11)); // 等待DOUT变低 for(uint8_t i0; i24; i) { // 读取24位数据 GPIO_ResetBits(GPIOA, GPIO_Pin_12); delay_us(1); GPIO_SetBits(GPIOA, GPIO_Pin_12); value 1; if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_11)) value; delay_us(1); } // 第25个脉冲选择通道和增益 GPIO_ResetBits(GPIOA, GPIO_Pin_12); delay_us(1); GPIO_SetBits(GPIOA, GPIO_Pin_12); return value ^ 0x800000; // 补码转换 }3. 数据滤波处理技巧原始数据往往存在抖动直接使用会导致显示值不停跳变。经过多次实验我发现中值滤波移动平均的组合效果最好。具体实现如下先定义一个滤波缓冲区#define FILTER_LEN 5 uint32_t filterBuf[FILTER_LEN]; uint8_t filterIndex 0;然后实现滤波函数uint32_t Filter_Value(uint32_t rawValue) { // 更新缓冲区 filterBuf[filterIndex] rawValue; if(filterIndex FILTER_LEN) filterIndex 0; // 中值滤波 uint32_t tempBuf[FILTER_LEN]; memcpy(tempBuf, filterBuf, sizeof(filterBuf)); // 冒泡排序 for(uint8_t i0; iFILTER_LEN-1; i) { for(uint8_t j0; jFILTER_LEN-i-1; j) { if(tempBuf[j] tempBuf[j1]) { uint32_t temp tempBuf[j]; tempBuf[j] tempBuf[j1]; tempBuf[j1] temp; } } } // 取中值 return tempBuf[FILTER_LEN/2]; }实际测试中这种滤波方式能让5kg量程的称重系统稳定在±1g以内。如果环境干扰较大可以适当增加FILTER_LEN的值但要注意这会降低响应速度。4. 校准方法与实战经验校准是称重系统最关键的环节我总结了一套简单有效的三步校准法第一步去皮操作放上空载的称重盘执行去皮void Tare(void) { uint32_t sum 0; for(uint8_t i0; i10; i) { sum Read_HX711(); delay_ms(100); } offset sum / 10; // 记录零点偏移 }第二步线性校准准备标准砝码建议用满量程的50%和100%两个点计算校准系数void Calibration(void) { float knownWeight 500.0; // 已知重量(g) uint32_t rawValue 0; for(uint8_t i0; i10; i) { rawValue Read_HX711(); delay_ms(100); } rawValue / 10; scale knownWeight / (rawValue - offset); }第三步温度补偿可选 如果使用环境温度变化大可以增加温度传感器建立温度-误差补偿表float TempCompensation(float weight, float temp) { // 根据实测数据建立的补偿曲线 return weight * (1.0 0.0005*(temp - 25.0)); }在实际项目中我发现不同批次的传感器特性会有差异建议每个产品都单独校准。校准数据可以保存在STM32的Flash中避免每次上电重新校准。5. 完整系统集成将各个模块整合起来一个完整的称重系统需要处理以下任务定时采样建议使用定时器中断触发采样避免主循环阻塞void TIM2_IRQHandler(void) { if(TIM_GetITStatus(TIM2, TIM_IT_Update) ! RESET) { rawData Read_HX711(); TIM_ClearITPendingBit(TIM2, TIM_IT_Update); } }重量计算将ADC值转换为实际重量float GetWeight(void) { float temp (Filter_Value(rawData) - offset) * scale; return TempCompensation(temp, currentTemp); }显示输出可以通过OLED或串口输出void Display_Update(void) { char buf[16]; sprintf(buf, %.1fg, currentWeight); OLED_ShowString(0, 0, buf); }用户交互按键处理去皮、校准等功能void KEY_Handler(void) { if(KEY_Pressed()) { Tare(); OLED_ShowString(0, 2, Tare Complete); } }在电源处理上有个重要经验称重传感器对电源噪声非常敏感建议给HX711单独加一个LDO稳压并且电源走线要尽量短。如果测量值出现规律性跳动大概率是电源问题。
基于STM32F103C8T6与HX711的称重系统实战:从零搭建到数据校准
1. 硬件选型与电路连接第一次接触称重系统开发时最让我头疼的就是硬件选型。市面上各种型号的称重传感器和ADC芯片让人眼花缭乱经过多次踩坑后我发现STM32F103C8T6HX711这个组合特别适合新手入门。STM32F103C8T6作为经典的Cortex-M3内核MCU72MHz主频完全够用而且价格只要十几块钱。HX711则是专为电子秤设计的24位ADC内置可编程放大器省去了复杂的外围电路设计。具体到硬件连接我建议直接购买现成的HX711模块某宝上5块钱左右这样能避免很多麻烦。模块上通常会有明确的标识VCC接3.3V或5V实测两种电压都可用GND接地DOUT接MCU的任意GPIO如PA11SCK接另一个GPIO如PA12称重传感器一般有四根线红、黑、白、绿对应接在HX711模块的E、E-、A、A-端子。这里有个小技巧如果发现重量显示为负数把白绿两根线对调就能解决。我第一次调试时就遇到这个问题折腾了半天才发现是线序接反了。2. 驱动代码编写实战硬件连接好后接下来就是最关键的代码部分。HX711的通信协议其实很简单就是通过GPIO模拟时序来读取数据。我整理了一个经过实战检验的驱动方案包含三个关键函数首先是初始化函数需要配置两个GPIO的工作模式void HX711_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // SCK引脚配置为推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_12; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // DOUT引脚配置为浮空输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_11; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); }数据读取函数是核心需要注意严格的时序控制uint32_t Read_HX711(void) { uint32_t value 0; GPIO_SetBits(GPIOA, GPIO_Pin_12); // SCK高电平 while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_11)); // 等待DOUT变低 for(uint8_t i0; i24; i) { // 读取24位数据 GPIO_ResetBits(GPIOA, GPIO_Pin_12); delay_us(1); GPIO_SetBits(GPIOA, GPIO_Pin_12); value 1; if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_11)) value; delay_us(1); } // 第25个脉冲选择通道和增益 GPIO_ResetBits(GPIOA, GPIO_Pin_12); delay_us(1); GPIO_SetBits(GPIOA, GPIO_Pin_12); return value ^ 0x800000; // 补码转换 }3. 数据滤波处理技巧原始数据往往存在抖动直接使用会导致显示值不停跳变。经过多次实验我发现中值滤波移动平均的组合效果最好。具体实现如下先定义一个滤波缓冲区#define FILTER_LEN 5 uint32_t filterBuf[FILTER_LEN]; uint8_t filterIndex 0;然后实现滤波函数uint32_t Filter_Value(uint32_t rawValue) { // 更新缓冲区 filterBuf[filterIndex] rawValue; if(filterIndex FILTER_LEN) filterIndex 0; // 中值滤波 uint32_t tempBuf[FILTER_LEN]; memcpy(tempBuf, filterBuf, sizeof(filterBuf)); // 冒泡排序 for(uint8_t i0; iFILTER_LEN-1; i) { for(uint8_t j0; jFILTER_LEN-i-1; j) { if(tempBuf[j] tempBuf[j1]) { uint32_t temp tempBuf[j]; tempBuf[j] tempBuf[j1]; tempBuf[j1] temp; } } } // 取中值 return tempBuf[FILTER_LEN/2]; }实际测试中这种滤波方式能让5kg量程的称重系统稳定在±1g以内。如果环境干扰较大可以适当增加FILTER_LEN的值但要注意这会降低响应速度。4. 校准方法与实战经验校准是称重系统最关键的环节我总结了一套简单有效的三步校准法第一步去皮操作放上空载的称重盘执行去皮void Tare(void) { uint32_t sum 0; for(uint8_t i0; i10; i) { sum Read_HX711(); delay_ms(100); } offset sum / 10; // 记录零点偏移 }第二步线性校准准备标准砝码建议用满量程的50%和100%两个点计算校准系数void Calibration(void) { float knownWeight 500.0; // 已知重量(g) uint32_t rawValue 0; for(uint8_t i0; i10; i) { rawValue Read_HX711(); delay_ms(100); } rawValue / 10; scale knownWeight / (rawValue - offset); }第三步温度补偿可选 如果使用环境温度变化大可以增加温度传感器建立温度-误差补偿表float TempCompensation(float weight, float temp) { // 根据实测数据建立的补偿曲线 return weight * (1.0 0.0005*(temp - 25.0)); }在实际项目中我发现不同批次的传感器特性会有差异建议每个产品都单独校准。校准数据可以保存在STM32的Flash中避免每次上电重新校准。5. 完整系统集成将各个模块整合起来一个完整的称重系统需要处理以下任务定时采样建议使用定时器中断触发采样避免主循环阻塞void TIM2_IRQHandler(void) { if(TIM_GetITStatus(TIM2, TIM_IT_Update) ! RESET) { rawData Read_HX711(); TIM_ClearITPendingBit(TIM2, TIM_IT_Update); } }重量计算将ADC值转换为实际重量float GetWeight(void) { float temp (Filter_Value(rawData) - offset) * scale; return TempCompensation(temp, currentTemp); }显示输出可以通过OLED或串口输出void Display_Update(void) { char buf[16]; sprintf(buf, %.1fg, currentWeight); OLED_ShowString(0, 0, buf); }用户交互按键处理去皮、校准等功能void KEY_Handler(void) { if(KEY_Pressed()) { Tare(); OLED_ShowString(0, 2, Tare Complete); } }在电源处理上有个重要经验称重传感器对电源噪声非常敏感建议给HX711单独加一个LDO稳压并且电源走线要尽量短。如果测量值出现规律性跳动大概率是电源问题。