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米以内的线,问题解决。所以,能用短线就别用长线,能用屏蔽线就别用普通线。

好了,这一章的内容就到这儿。串口通信是嵌入式开发的必修课,你把它吃透了,后面写数据记录系统就会顺手很多。下一章咱们开始讲数据解析,把万用表发过来的原始字节变成人能看懂的电压、电流值。