十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Arduino+Python电子秤上位机:串口通信与标定实战解析

Arduino+Python电子秤上位机:串口通信与标定实战解析 简介2018年山西省电子设计大赛“电子秤”赛题的完整实战源码包面向计划参加全国大学生电子设计竞赛或省级电赛的学生提供可直接运行的上位机与下位机联调方案。压缩包共5个文件包括2个Python程序分别承担主逻辑与界面展示、1个Arduino .ino下位机代码、1个一键启动bat脚本和1张上位机界面效果图整体仅9KB轻量易用。已有89人学习参考适合从零接触电赛流程、需要参考传感采集与上位机通信实现的中低年级同学。资源内含赛题原始方案与调试代码可帮助理解Python串口通信、Tkinter/GUI设计以及Arduino端数据采集的整体配合结合启动脚本与界面截图可快速复现项目并在此基础上扩展功能。1. 电子秤上位机项目为什么值得逐行拆解2018 年山西省电子设计大赛里电子秤是常驻题型现场搭出称重硬件把重量实时显示到电脑还要做去皮、标定、超限报警。放到现在看这套「Arduino 下位机 Python 上位机」组合仍是入门串口仪表最快的路径——下位机用 HX711 读传感器原始值上位机用 pyserial 收数据、tkinter 画界面恰好把嵌入式采集、串口协议、GUI 线程三个硬知识点串在一起。标题的价值不在称重本身而在它是可复用的串口仪器模板传感器换成温湿度或测距模块骨架基本不动。适合竞赛学生、刚接触上位机的 Python 工程师、想把 Arduino 数据接进电脑的 maker。下文先立住 HX711 的读数与标定原理再给出可运行的 Arduino、Python 代码最后收在联调阶段最易翻车的串口问题上。2. Arduino 称重下位机HX711 接线、读数与标定全流程2.1 HX711 与称重传感器怎么接、增益怎么选电子秤的核心是电阻应变式称重传感器内部是惠斯通电桥受力时电阻变化产生毫伏级差分电压。Arduino 自带的 ADC 只有 10 位、参考电压 5V分辨率根本不够看所以中间必须加一颗 24 位 Δ-Σ ADCHX711 就是这类芯片里最常见的一颗内部还带可编程增益放大器增益可选 128、64、32。传感器这边一般是四根线红 E、黑 E-、白 A-S-、绿 AS。E/E- 接 HX711 的稳压输出给传感器供电A/A- 接传感器的信号输出。HX711 与 Arduino 之间只要两根数据线DT数据输出和 SCK时钟典型接法是 DT 接 D3、SCK 接 D2。增益 128 对应约 20mV 满量程对常见的 1kg、5kg 悬臂梁传感器都够用所以默认就用 128采集速率保持芯片默认的 10SPS。注意HX711 的供电纹波对精度影响明显现场读数乱跳时先把电源换成独立稳压输出再检查传感器信号线是否绞合、屏蔽层是否接地。2.2 最小可用的 Arduino 串口输出程序Arduino 侧不需要自己写底层时序Arduino IDE 的库管理器里搜 HX711 直接安装。库对象建立之后核心动作就两个set_scale设标定系数tare去皮。下面这个程序把重量按一行一条的类 JSON 帧发到串口Python 端解析最省事。#include HX711.h #define LOADCELL_DOUT_PIN 3 #define LOADCELL_SCK_PIN 2 HX711 scale; float calibration_factor -2250.0; // 标定系数正负由接线方向决定 float offset 0.0; void setup() { Serial.begin(9600); scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN); scale.set_scale(calibration_factor); scale.tare(); // 上电自动去皮 } void loop() { if (scale.is_ready()) { float weight scale.get_units(10); // 连续 10 次读数取平均再换算 Serial.print({\w\:); Serial.print(weight, 2); Serial.print(,\n\:1}); Serial.println(); } delay(100); }几个参数值得说明。get_units(10)表示连续取 10 次原始值求平均后再乘标定系数这一步低通能压掉一部分高频抖动is_ready()是库提供的就绪检查只有 HX711 数据准备好才读避免取到 ADC 的中间态波特率 9600 已经足够一帧只有十几个字节10Hz 采样算下来约 1.5Kbps完全跑得动。帧以换行结尾Python 端readline()一次就能拿到完整一行。2.3 用已知砝码完成一次标定标定的目标是找到 ADC 原始值和克的换算关系先在空载状态记下原始值放上已知重量的砝码再记一个原始值两者差值除以砝码重量就是标定系数。calibration_factor (raw_with_weight - raw_empty) / known_weight实际操作时我会先刷一段辅助程序直接从串口打印原始值不用每次调系数都重烧固件#include HX711.h const int DOUT_PIN 3; const int SCK_PIN 2; HX711 scale; void setup() { Serial.begin(9600); scale.begin(DOUT_PIN, SCK_PIN); scale.tare(); } void loop() { if (scale.is_ready()) { Serial.print(raw:); Serial.print(scale.get_value()); // 未乘系数的去皮后原始差值 Serial.print( units:); Serial.println(scale.get_units(1)); } delay(200); }标定过程建议做成台账空载原始值、加载原始值、差值、砝码重量、算出的系数表格里留一列备注传感器编号。竞赛现场换传感器是常事有台账能快速判断是传感器漂了还是系数失效。下位机参数的常用取值如下表参数推荐值说明传感器激励电压5V悬臂梁传感器标准供电HX711 增益128默认即可约 20mV 满量程串口波特率9600帧短量小稳定优先采样率10 SPSHX711 默认输出约 10Hz发送周期100ms与人眼刷新率匹配标定之后下位机就不动了。不要在 Arduino 里做复杂滤波把去皮后的重量以固定周期丢给上位机滤波策略放到 Python 端调改参数不用重新烧板联调效率会高很多。3. Python 上位机串口通信pyserial 收数、帧解析与断线重连3.1 通信帧协议给裸数据套一层外壳上位机和下位机通信最容易犯的错是直接发123.45这种裸数值。串口是字节流没有消息边界123.45和-0.01长度不同接收端根本不知道从哪切分。所以项目里约定一帧必须有三样东西固定起始标记、数据字段、行结束符。Arduino 端输出的是类 JSON 的{w:123.45,n:1}加换行Python 端用json.loads解析省去手写字符串切分。这里有个取舍要讲清楚为什么不用带校验和的二进制帧二进制帧效率高、抗干扰适合数据量大或控制指令多的场景。电子秤一秒钟才 10 帧每帧不到 20 字节ASCII 行帧完全够用而且串口监视器能直接看到内容排查问题少一道解码步骤。两种帧的差异总结如下对比项ASCII 行帧二进制帧可读性串口监视器直接看需要转换工具帧长约 20 字节约 8 字节校验方式靠解析失败跳过通常带 CRC适用场景低频仪表数据高吞吐或控制指令竞赛现场时间紧可读性优先选 ASCII 行帧是稳妥的默认项。3.2 pyserial 最小读法readline、超时与解码Python 端先装 pyserial环境装好 Python 后执行pip install pyserialWindows 和 Linux 通用。读取用readline()配合超时参数防止程序卡死在等待数据上。下面是最小可用的读取脚本import serial import json ser serial.Serial( portCOM3, # Linux 下通常是 /dev/ttyUSB0 baudrate9600, bytesizeserial.EIGHTBITS, parityserial.PARITY_NONE, stopbitsserial.STOPBITS_ONE, timeout1, # 读超时 1 秒避免 readline 无限等待 ) while True: line ser.readline() # 读到换行或超时返回 if not line: continue try: payload json.loads(line.decode(ascii).strip()) weight payload[w] print(f{weight:.2f} g) except (json.JSONDecodeError, KeyError): # 半包或干扰数据丢弃这一帧 continue三个参数建议说透timeout1让 readline 在没数据时最多等 1 秒返回空值主循环不会卡死decode(ascii)配合 Arduino 端只发 ASCII 字符比 utf-8 少踩边界问题json.loads解析失败直接 continue串口上偶尔出现乱码是常态一帧垃圾数据不值得让程序崩溃。提示Windows 上首次运行找不到串口先在设备管理器确认 Arduino 的 COM 号Linux 上权限不够时把当前用户加入 dialout 组执行sudo usermod -a -G dialout $USER后重新登录生效。3.3 串口热插拔与自动重连竞赛现场有个现实问题Arduino 掉电重启、USB 线被碰松、换了一台电脑COM 号全变固件里写死端口是灾难。pyserial 自带的serial.tools.list_ports可以枚举串口配合异常捕获做自动重连import serial import serial.tools.list_ports import time def find_arduino(): for port, desc, hwid in serial.tools.list_ports.comports(): # Arduino 的 USB 转串口芯片常见描述CH340 / CP210x if CH340 in desc or USB in desc: return port return None def connect(): while True: port find_arduino() if port: try: ser serial.Serial(port, 9600, timeout1) return ser except serial.SerialException: time.sleep(1) time.sleep(1) ser connect()这段逻辑里comports()返回的元组包含端口名、设备描述和硬件 ID按描述关键词匹配而不是按序号匹配换个 USB 口也能找到设备。SerialException捕获的是端口被占用或设备已拔出外层 while 循环把重连频率压到 1 秒一次不会在系统里反复开关端口导致驱动异常。自动重连写好之后演示中途换 USB 口都不用重启程序。4. tkinter 上位机界面线程分工、去皮与 CSV 记录4.1 为什么串口读取必须放线程tkinter 的mainloop()是阻塞式的如果在回调里直接调ser.readline()界面会整个卡死。标准做法是把串口读取放进后台线程数据通过queue.Queue传到主线程主线程用after()定时从队列取数据刷新界面。import tkinter as tk import queue import threading import serial import json class ScaleApp: def __init__(self, root, ser): self.root root self.ser ser self.q queue.Queue() self.weight_var tk.StringVar(value--) tk.Label(root, text重量:, font(Helvetica, 20)).pack() tk.Label(root, textvariableself.weight_var, font(Helvetica, 32)).pack() t threading.Thread(targetself.read_serial, daemonTrue) t.start() self.poll_queue() def read_serial(self): while True: line self.ser.readline() if line: self.q.put(line) def poll_queue(self): try: while True: line self.q.get_nowait() payload json.loads(line.decode(ascii).strip()) self.weight_var.set(f{payload[w]:.2f} g) except (queue.Empty, json.JSONDecodeError, KeyError): pass self.root.after(50, self.poll_queue) # 每 50ms 检查一次队列 root tk.Tk() ser connect() # 复用上一章的自动重连函数 app ScaleApp(root, ser) root.mainloop()两个容易写错的点。第一串口线程必须设成 daemon 线程否则关窗口时线程不会退出程序会挂住第二poll_queue用after(50)而不是 while True 死循环每次刷新都回到 tkinter 事件循环界面不会和取数逻辑抢 CPU。刷新间隔 50ms 对应 20Hz高于下位机的 10Hz 输出不会丢帧。4.2 去皮和标定入口怎么做电子秤必须有去皮按钮。去皮有两种实现Python 端记录当前重量做偏移显示时减去或者向下位机发一条命令让 Arduino 重新 tare。我倾向第二种偏移保存在下位机Python 端重启后行为一致测量的原始基准也统一。Arduino 端在 loop 里轮询串口输入收到T就执行 tareif (Serial.available()) { char cmd Serial.read(); if (cmd T) { scale.tare(); Serial.println({\w\:0.00,\n\:2}); } }Python 端发命令只需要一行ser.write(bT)按钮回调里调用即可。标定入口同理界面放一个输入框让操作员填已知砝码重量Python 读取当前稳定值和对应原始值算出系数后写进配置文件重启自动加载不用每次重刷固件。配置用 JSON 文件存calibration_factor和port两个键足够代码里json.dump一次写完注意写配置前把串口读数线程暂停一帧避免边读边写的竞态。4.3 数据落盘CSV 记录与回放评委看重数据可追溯性把每次称重结果连同时间戳写进 CSV成本极低用处很大。在 4.1 的poll_queue解析出 weight 之后追加写入import csv import datetime f open(weights.csv, a, newline) writer csv.writer(f) # 解析获得 weight 后调用 ts datetime.datetime.now().strftime(%Y-%m-%d %H:%M:%S) writer.writerow([ts, f{weight:.2f}]) f.flush() # 及时刷盘进程崩溃也不丢数据两个细节必须强调flush()一定要加否则数据积在 Python 缓冲区里断电就没了文件用追加模式a打开连续多次运行不覆盖历史。CSV 的字段结构很简单后续回放和画图都靠它字段示例说明timestamp2024-05-01 10:00:00记录时刻weight_g123.45去皮后重量回放时把 CSV 读进来用 matplotlib 画重量-时间曲线两行代码就能出图。这里不展开绘图细节但评审现场一张稳定曲线比十句口头说明都有说服力。5. 电子秤联调排错粘包、滤波与现场稳定性5.1 半包、粘包与脏数据怎么兜住Arduino 每 100ms 发一行Python 用 readline 按换行切分消息边界被框死粘包基本不会发生。真正会踩的是两种情况下位机复位的瞬间发出半行残留以及 USB 转串口驱动在系统睡眠恢复后重放旧字节。应对策略只有一条——解析失败就当噪音跳过绝不中断。这要求上位机把读串口和解析数据彻底分开读完一行先做合法性检查字段缺失直接丢弃下一帧自然恢复。不要尝试用固定字节数去 read复位后字节流会错位按行读加异常兜底是容错性最高的组合。5.2 滑动平均与中值滤波怎么选HX711 的输出在震动环境下会有几克抖动下位机get_units(10)只做了一次低通上位机再加一级滑动平均显示会平稳很多class MovingAverage: def __init__(self, window5): self.window window self.buf [] def update(self, value): self.buf.append(value) if len(self.buf) self.window: self.buf.pop(0) return sum(self.buf) / len(self.buf)窗口越大越平滑滞后也越明显称重场景窗口取 5 到 10 合适。如果抖动是偶发尖峰而不是随机噪声滑动平均反而会被尖峰带偏这种情况改用中值滤波statistics.median(self.buf)一行就能算。竞赛现场两种方法各跑一遍选视觉上最稳的不必纠结理论优劣。5.3 现场稳定性验证清单联调时按顺序过一遍下面的清单能排除大多数现场问题。冷启动后先空载等 30 秒让 HX711 内部参考电压稳定用秒表记录 1 分钟零漂放 100g、500g、1000g 三档砝码逐点验证线性度用手轻敲桌面试验抗震动能力。检查项通过标准失败时先查零漂空载 60s 漂移 ≤ ±1g供电纹波、屏蔽、传感器紧固线性度三档砝码误差 ≤ ±2g标定系数、传感器量程抗震动敲桌后 2s 内回稳滤波窗口、机械固定串口稳定性30 分钟无断帧USB 线、驱动版本最后补一个现场提分技巧把标定系数写进 Arduino 的 EEPROM上电时用EEPROM.get(0, calibration_factor)恢复换传感器重新标定后系数也能掉电保存裁判重启设备数据依然是准的。EEPROM 有约十万次擦写寿命只在标定完成时执行一次EEPROM.put(0, calibration_factor)绝不能在 loop 里反复写。行帧校验、上位机滤波、系数持久化这三件事都落地后整套系统从冷启动到演示基本不需要人工干预留出时间处理场上突发情况才是拿分关键。本文还有配套的精品资源点击获取
返回列表