STM32F103C8T6 + HX711 + 0.96寸OLED:手把手教你做一个桌面电子秤(附完整代码)
STM32F103C8T6与HX711打造高精度电子秤:从硬件搭建到软件调优全指南
在创客圈子里,电子秤项目一直是个既能练手又实用的经典选题。这次我们要用STM32F103C8T6这颗性价比之王作为主控,搭配HX711高精度ADC和0.96寸OLED显示屏,打造一个精度可达0.1g的桌面电子秤。不同于简单的模块堆砌,我们将深入探讨传感器稳定性优化、显示界面设计等实战细节,让你真正掌握嵌入式系统开发的全流程。
1. 硬件选型与电路设计
1.1 核心器件选型要点
STM32F103C8T6(俗称"蓝莓派")的选择理由:
- 72MHz主频的Cortex-M3内核,足够处理称重数据
- 64KB Flash + 20KB RAM,轻松容纳我们的程序
- 丰富的GPIO和通信接口(2个I2C、3个USART等)
- 市面上开发板价格普遍低于30元
HX711模块的关键参数验证:
- 24位ADC分辨率(理论精度1/16777216)
- 内置可编程增益放大器(PGA),支持128倍放大
- 典型采样率:10Hz或80Hz可选
- 工作电压:2.6V-5.5V(与STM32完美兼容)
0.96寸OLED屏的选购陷阱:
- 确认是SSD1306驱动芯片的I2C版本(4针)
- 分辨率为128x64像素
- 注意区分3.3V和5V供电版本(我们选3.3V)
1.2 电路连接详解
完整接线方案:
| HX711引脚 | STM32连接 | 注意事项 |
|---|---|---|
| VCC | 5V | 避免使用3.3V,可能供电不足 |
| GND | GND | 确保共地 |
| DOUT | PA1 | 需配置为上拉输入 |
| SCK | PA3 | 推挽输出模式 |
OLED连接方案:
OLED_SCL -> PB6 (I2C1_SCL) OLED_SDA -> PB7 (I2C1_SDA)关键提示:I2C总线必须接上拉电阻!虽然部分OLED模块内置了,但建议在SCL/SDA线上各加一个4.7kΩ电阻到3.3V
称重传感器选型建议:
- 推荐使用5kg量程的铝合金悬臂梁传感器
- 注意灵敏度参数(通常2.0mV/V)
- 配套使用惠斯通电桥调理电路
2. 开发环境搭建与基础工程配置
2.1 工具链准备
推荐开发环境组合:
- Keil MDK-ARM V5(使用社区版即可)
- STM32CubeMX(用于初始化代码生成)
- ST-Link V2下载器(兼容性好)
必须安装的软件包:
- STM32F1xx_DFP(设备支持包)
- ARM::CMSIS(5.7.0或更高)
- Keil::STM32F1xx_StdPeriph_Lib(标准外设库)
2.2 工程创建步骤
使用CubeMX初始化流程:
- 选择STM32F103C8型号
- 配置时钟树(72MHz主频)
- 启用PA1(输入)、PA3(输出)
- 配置I2C1(标准模式,400kHz)
- 生成Keil工程
关键外设初始化代码片段:
// HX711 GPIO初始化 void HX711_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; __HAL_RCC_GPIOA_CLK_ENABLE(); // DOUT配置为上拉输入 GPIO_InitStruct.Pin = GPIO_PIN_1; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // SCK配置为推挽输出 GPIO_InitStruct.Pin = GPIO_PIN_3; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET); }3. HX711驱动开发与优化
3.1 基础数据读取实现
HX711工作时序关键点:
- DOUT变低后开始时钟脉冲
- 前24个脉冲读取数据(MSB优先)
- 第25-27个脉冲设置下次转换的通道和增益
原始数据读取函数:
int32_t HX711_Read(void) { int32_t data = 0; while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_SET); for(uint8_t i=0; i<24; i++) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_SET); delay_us(1); data <<= 1; HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET); delay_us(1); if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_SET) { data++; } } // 设置下次为通道A,128增益 for(uint8_t i=0; i<1; i++) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_SET); delay_us(1); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET); delay_us(1); } return data ^ 0x800000; // 转换补码 }3.2 数据稳定性优化策略
常见干扰来源及解决方案:
电源噪声:
- 在HX711的VCC和GND之间加100μF电解电容
- 并联0.1μF陶瓷电容去高频噪声
机械振动:
- 采用滑动平均滤波算法
- 代码实现示例:
#define SAMPLE_NUM 10 int32_t HX711_GetStableValue(void) { int64_t sum = 0; for(uint8_t i=0; i<SAMPLE_NUM; i++) { sum += HX711_Read(); delay_ms(5); } return (int32_t)(sum / SAMPLE_NUM); }- 温度漂移:
- 每次上电后自动去皮
- 设置定期自动校准功能
4. 称重算法与用户界面实现
4.1 重量换算算法
校准流程数学模型:
- 测量空载时的ADC值(Tare)
- 放置已知重量M的标准砝码,测得ADC值(Cal)
- 计算线性系数:k = (Cal - Tare)/M
- 实际重量 = (当前读数 - Tare)/k
校准函数实现:
float weight_g = 0; int32_t tare_value = 0; float calibration_factor = 0; void HX711_Calibrate(float known_weight) { int32_t raw = HX711_GetStableValue(); calibration_factor = (raw - tare_value) / known_weight; } float HX711_GetWeight(void) { int32_t raw = HX711_GetStableValue(); return (raw - tare_value) / calibration_factor; }4.2 OLED界面设计
显示内容规划:
- 主界面:当前重量(大字体)+单位
- 次级信息:电池电量、采样率
- 菜单界面:校准模式、单位切换
使用u8g2图形库的显示示例:
void OLED_ShowWeight(float weight) { char buf[16]; u8g2_ClearBuffer(&u8g2); u8g2_SetFont(&u8g2, u8g2_font_inb38_mr); sprintf(buf, "%.1f", weight); u8g2_DrawStr(&u8g2, 10, 50, buf); u8g2_SetFont(&u8g2, u8g2_font_6x10_mr); u8g2_DrawStr(&u8g2, 100, 50, "g"); u8g2_SendBuffer(&u8g2); }5. 系统集成与调试技巧
5.1 常见问题排查指南
问题1:读数不稳定
- 检查电源滤波电容是否足够
- 尝试降低HX711采样率到10Hz
- 确保传感器安装牢固无晃动
问题2:OLED显示异常
- 确认I2C地址正确(通常0x3C或0x78)
- 检查上拉电阻是否接好
- 降低I2C时钟频率测试
问题3:线性度差
- 检查传感器量程是否合适
- 确保校准砝码精度足够
- 验证机械结构无摩擦阻力
5.2 进阶优化方向
低功耗设计:
- 使用STM32的停机模式
- 定时唤醒采样
- 动态调整OLED刷新率
多单位切换:
- 实现g/oz/lb等单位换算
- 保存用户偏好到Flash
数据记录功能:
- 添加EEPROM存储历史数据
- 通过串口导出到PC
// 简易数据记录实现示例 void SaveToEEPROM(float weight) { static uint16_t addr = 0; uint32_t temp = (uint32_t)(weight * 10); HAL_FLASH_Unlock(); __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR); FLASH_ErasePage(0x0800FC00); HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, 0x0800FC00 + addr, temp); addr += 4; HAL_FLASH_Lock(); }6. 成品制作与外壳设计
6.1 结构设计建议
材料选择对比表:
| 材料类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 亚克力 | 美观、易加工 | 强度一般 | 桌面使用 |
| 铝合金 | 坚固耐用 | 加工难度大 | 工业环境 |
| 3D打印 | 造型自由 | 表面粗糙 | 原型开发 |
推荐布局方案:
- 上层:称重平台(尺寸建议8x8cm)
- 中层:传感器固定层
- 下层:电子仓(STM32+OLED)
6.2 电源管理优化
供电方案对比:
USB供电:
- 优点:稳定可靠
- 缺点:移动性差
锂电池方案:
- 推荐18650电池+充电模块
- 添加电量检测功能
- 代码示例:
float GetBatteryVoltage(void) { HAL_ADC_Start(&hadc1); HAL_ADC_PollForConversion(&hadc1, 10); uint16_t adc = HAL_ADC_GetValue(&hadc1); return (adc * 3.3 / 4095) * (R1 + R2) / R2; // 分压电阻计算 }- 低功耗提示:
- 电压低于3.3V时OLED显示警告
- 自动进入休眠模式
7. 项目扩展与进阶应用
7.1 蓝牙无线传输
添加HC-05模块实现:
- 串口连接STM32的USART1
- AT指令配置模块参数
- 实现重量数据无线传输
示例通信协议设计:
{ "device": "SmartScale", "weight": 125.3, "unit": "g", "timestamp": 1625097600 }7.2 云端数据对接
通过ESP8266实现:
- 连接WiFi网络
- 使用MQTT协议上传数据
- 阿里云IoT平台接入示例:
void UploadToCloud(float weight) { char cmd[128]; sprintf(cmd, "AT+MQTTPUB=0,\"/scale/data\",\"{\\\"weight\\\":%.1f}\",0,0\r\n", weight); HAL_UART_Transmit(&huart2, (uint8_t*)cmd, strlen(cmd), 1000); }7.3 商业级改进建议
生产优化:
- 改用SMT贴片元件
- 设计PCB整合所有模块
- 通过EMC测试
功能增强:
- 添加触摸按键
- 实现自动累加计重
- 支持多用户模式
认证考虑:
- 计量器具认证(OIML R76)
- CE/FCC电磁兼容认证
- RoHS环保认证
在完成这个项目后,我发现最影响精度的往往不是代码问题,而是机械结构和电源质量。有一次调试时读数总是漂移,最后发现是USB线质量太差导致供电不稳。改用锂电池供电后,精度立即稳定在0.2g以内。另一个实用建议是:在校准时,最好使用多个标准砝码分段校准(如100g、500g、1kg),这样能在全量程范围内获得更好的线性度。
