2、硬件通信基础:串口通信原理、RS232与USB转串口、万用表通信协议(如DTM0660协议)、使用PySerial库进行串口通信
好,咱们进入第二章。这一章是硬核基础,也是整个万用表数据记录系统的地基。说白了,你要是不懂串口怎么工作的,后面写再多代码也是白搭。我当年刚入行时,就吃过这个亏——代码写得飞起,结果硬件死活不通,最后发现是串口参数配错了。嗯,这种坑咱们今天一次性填平。
2.1 串口通信原理:其实没那么玄乎
串口通信,全称叫「串行通信接口」。你想想看,数据在电脑内部是并行跑的,8位、16位、32位同时传输。但到了外部设备,线太多不现实,成本也高。所以串口就干了一件事:把并行数据变成一串0和1,按顺序一根线发出去。
我个人习惯把串口通信比作「两个人打电话」:
- 发送方:对着话筒(TX)说话
- 接收方:用耳朵(RX)听
- 地线:就是共用的「地面」,保证两个人站在同一电位上
串口通信有几个关键参数,一个都不能错:
| 参数 | 说明 | 常见值 |
|---|---|---|
| 波特率 | 每秒传输的比特数 | 9600、115200 |
| 数据位 | 一次传输多少位数据 | 7、8(常用8) |
| 停止位 | 数据结束的标志位 | 1、1.5、2 |
| 校验位 | 简单的错误检测 | 无、奇校验、偶校验 |
核心要点:通信双方必须使用完全相同的参数配置,否则收到的全是乱码。我在项目中遇到过好几次,明明线接对了,数据就是不对,最后发现是波特率设成了9600而设备用的是19200。
2.2 RS232与USB转串口:老将与新兵
RS232是串口通信的经典标准,诞生于上世纪60年代。它的电气特性是这样的:
- 逻辑1:-3V ~ -15V
- 逻辑0:+3V ~ +15V
你想想看,现在的单片机都是3.3V或5V供电,RS232这种±12V的电压根本没法直接对接。所以就有了电平转换芯片,比如MAX232。我早期做项目时,还自己焊过MAX232的电路,现在想想真是折腾。
到了今天,大部分电脑已经没有原生RS232接口了。怎么办?USB转串口方案应运而生。常见的芯片有:
- CH340:国产芯片,便宜好用,很多万用表数据线都用它
- FT232:进口芯片,稳定可靠,工业级产品首选
- CP2102:Silicon Labs出品,集成度高
我的建议:做课程项目用CH340就够了,几块钱一根线。但如果是做产品,我建议用FT232,稳定性不是一个量级。我曾经在产线上被CH340的驱动兼容性问题坑过,换了FT232后问题全消。
2.3 万用表通信协议:以DTM0660为例
DTM0660是国产万用表里非常常见的主控芯片。它的通信协议其实很简单,说白了就是:万用表每隔一定时间(通常是200ms~500ms)主动往外发一帧数据。
数据帧格式是这样的:
帧头(1字节) + 数据(14字节) + 校验(1字节) + 帧尾(1字节)
具体来说:
- 帧头:固定为0xAA
- 数据段:14个字节,包含测量值、单位、量程、功能模式等信息
- 校验:前面所有字节的异或和
- 帧尾:固定为0x55
举个例子,假设收到这样一帧数据:
AA 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 55
其中0x0E就是校验字节,计算方法是:0xAA ^ 0x01 ^ 0x02 ^ ... ^ 0x0D = 0x0E。如果算出来不对,这帧数据就得扔掉。
注意:不同品牌的万用表协议可能略有差异。比如胜利的某些型号帧头是0x55,帧尾是0xAA。拿到新设备时,第一件事就是用串口助手抓原始数据,分析协议格式。我曾经因为想当然地套用DTM0660协议,结果浪费了整整一天。
2.4 使用PySerial库进行串口通信
好了,理论讲完了,咱们来点实际的。Python操作串口,首选就是PySerial库。安装很简单:
pip install pyserial
下面是一个完整的串口通信示例,我加了详细的注释:
import serial
import serial.tools.list_ports
# 第一步:列出所有可用串口
def list_serial_ports():
ports = serial.tools.list_ports.comports()
for port in ports:
print(f"端口: {port.device}, 描述: {port.description}")
return ports
# 第二步:打开串口
def open_serial_port(port_name, baudrate=9600):
try:
ser = serial.Serial(
port=port_name,
baudrate=baudrate,
bytesize=serial.EIGHTBITS, # 8位数据位
parity=serial.PARITY_NONE, # 无校验
stopbits=serial.STOPBITS_ONE, # 1位停止位
timeout=1 # 超时1秒
)
print(f"成功打开串口: {port_name}")
return ser
except serial.SerialException as e:
print(f"打开串口失败: {e}")
return None
# 第三步:读取数据
def read_multimeter_data(ser):
if ser is None or not ser.is_open:
return None
# 读取一帧数据(假设帧长17字节)
raw_data = ser.read(17)
if len(raw_data) != 17:
return None
# 校验帧头和帧尾
if raw_data[0] != 0xAA or raw_data[-1] != 0x55:
print("帧头或帧尾错误")
return None
# 校验和验证
checksum = 0
for byte in raw_data[:-2]: # 不包含校验字节和帧尾
checksum ^= byte
if checksum != raw_data[-2]:
print("校验和错误")
return None
return raw_data
# 主程序
if __name__ == "__main__":
ports = list_serial_ports()
if not ports:
print("没有找到串口设备")
exit()
# 假设第一个串口是万用表
ser = open_serial_port(ports[0].device, 9600)
if ser:
try:
while True:
data = read_multimeter_data(ser)
if data:
# 这里可以解析数据,提取测量值
print(f"收到数据: {data.hex()}")
except KeyboardInterrupt:
print("用户中断")
finally:
ser.close()
关键点:
- 串口参数必须与万用表完全一致,尤其是波特率
- 读取数据时一定要做校验,否则可能拿到错误数据
- 记得设置超时,防止程序卡死
2.5 实战中的坑与避坑指南
做串口通信,有几个坑我几乎每次都会遇到:
- 乱码问题:八成是波特率不对,或者数据位/停止位不匹配
- 数据断断续续:检查USB线,有些便宜线屏蔽做得差,干扰大
- 驱动装不上:CH340在Win10以上系统有时需要手动安装驱动
- 串口被占用:比如串口助手没关,或者另一个Python进程占着端口
避坑指南:我曾经在调试一个工业项目时,串口数据总是丢包。查了两天,最后发现是USB延长线太长,信号衰减了。换成1米以内的线,问题解决。所以,能用短线就别用长线,能用屏蔽线就别用普通线。
好了,这一章的内容就到这儿。串口通信是嵌入式开发的必修课,你把它吃透了,后面写数据记录系统就会顺手很多。下一章咱们开始讲数据解析,把万用表发过来的原始字节变成人能看懂的电压、电流值。