第2章:Python与硬件通信基础:认识串口(RS232/RS485)、Python的serial库安装与基础用法

好,咱们正式开始动手了。

上一章我们聊了光谱仪自动进样系统的整体架构。你可能已经跃跃欲试,想赶紧写代码控制硬件。别急,在写代码之前,有一道绕不开的坎——串口通信

说白了,你电脑跟光谱仪、进样器、注射泵这些设备怎么说话?就是靠串口。我做了这么多年自动化,90%的仪器控制项目,底层都是串口在干活。今天咱们就把串口这层窗户纸捅破。

2.1 串口是什么?RS232和RS485的区别

串口,全称串行通信接口。它一次只传一个比特,像排队过独木桥。听起来慢,但胜在简单可靠。

工业现场最常见的两种串口标准:RS232RS485

特性 RS232 RS485
传输方式 单端(不平衡) 差分(平衡)
最大距离 约15米 约1200米
最大节点数 1对1 最多256个节点
电压范围 ±3V ~ ±15V ±1.5V ~ ±6V
抗干扰能力
常见应用 老式PC、调试口 工业总线、仪器网络

我个人习惯:短距离、一对一、调试阶段,用RS232。比如你桌上那台光谱仪,多半就是RS232接口。长距离、多设备、现场布线,用RS485。比如进样器、注射泵、阀门组挂在一条总线上。

小提示: 现在很多笔记本没有原生串口了。别慌,买个USB转串口线就行。我推荐FTDI芯片的,稳定,不丢数据。淘宝几十块钱,别买太便宜的,我曾经被劣质线坑过,数据乱码查了一整天。

2.2 串口通信的三要素

串口通信要想通,三个参数必须对得上。就像对暗号,错一个都不行。

  1. 波特率(Baud Rate):每秒传输的符号数。常见的有9600、19200、115200。光谱仪一般用9600或115200。我建议你直接看设备手册,别猜。
  2. 数据位(Data Bits):一般是8位。老设备可能有7位,但很少见了。
  3. 停止位(Stop Bits):1位或2位。绝大多数设备用1位。

还有一个经常被忽略的——校验位(Parity)。None、Even、Odd三种。None最常见。如果通信不稳定,可以试试Even。

注意: 我曾经在调试一台进口光谱仪时,手册上写的是“9600,8,N,1”,结果死活连不上。折腾了两小时,最后发现实际是“9600,8,E,1”。嗯,手册也会骗人。遇到这种情况,用串口监听工具抓一下原厂软件的通信参数,最靠谱。

2.3 Python的serial库安装

Python操作串口,靠的是 pyserial 这个库。它封装了底层的系统调用,你只需要几行代码就能收发数据。

安装很简单,打开终端或命令行,敲一行命令:

pip install pyserial

如果你用的是Anaconda环境,也可以:

conda install -c conda-forge pyserial

装完之后,验证一下:

python -c "import serial; print(serial.__version__)"

如果能打印出版本号,恭喜你,环境搭好了。

避坑指南: 我曾经在Windows上遇到过安装成功但导入报错的情况。原因是系统里同时装了多个Python版本,pip装到了A环境,但运行用的是B环境。解决办法:用 python -m pip install pyserial 确保安装到当前Python解释器。

2.4 serial库基础用法:打开、读写、关闭

好,现在咱们写第一段串口代码。目标:打开串口,发一条命令,收一条回复。

import serial

# 打开串口
ser = serial.Serial(
    port='COM3',        # Windows下是COM口,Linux下是/dev/ttyUSB0
    baudrate=9600,
    bytesize=8,
    parity='N',
    stopbits=1,
    timeout=1           # 超时时间,单位秒
)

# 检查是否打开成功
if ser.is_open:
    print(f"串口 {ser.port} 已打开")

# 发送数据(需要编码成bytes)
ser.write(b'*IDN?\n')   # 很多仪器用 *IDN? 查询身份

# 读取数据
response = ser.readline()  # 读取一行,直到换行符
print(f"收到: {response.decode().strip()}")

# 关闭串口
ser.close()
print("串口已关闭")

这段代码麻雀虽小,五脏俱全。我解释几个关键点:

  • port:串口号。Windows去设备管理器里看,Linux用 ls /dev/ttyUSB*ls /dev/ttyACM* 找。
  • timeout:这个参数很重要。不设的话,readline() 会一直等,程序就卡死了。我一般设1秒,够用。
  • 编码:串口传的是字节,不是字符串。所以发送前要 .encode() 或直接写 b'...',收到后要 .decode()
重点: 串口通信的本质就是“发字节、收字节”。别想复杂了。你发的是命令,收的是响应。至于命令格式是什么,那是设备协议的事,下一章我们会详细讲。

2.5 常用方法速查

除了上面用的 writereadline,还有几个方法你迟早会用上:

方法 说明 示例
ser.read(n) 读取n个字节 data = ser.read(10)
ser.read_until(expected) 读到指定字节为止 data = ser.read_until(b'\n')
ser.in_waiting 缓冲区中等待读取的字节数 if ser.in_waiting > 0: ...
ser.flush() 清空输入输出缓冲区 ser.flush()
ser.close() 关闭串口 ser.close()

我个人最常用的是 read_until。为什么?因为很多仪器的响应以换行符 \n 或回车换行 \r\n 结尾。用 read_until 可以完整地拿到一条消息,不会半截断开。

2.6 一个完整的实战例子:查询光谱仪状态

假设你面前有一台光谱仪,串口参数是:COM5、115200、8、N、1。我们写一段代码,查询它的状态。

import serial
import time

def query_spectrometer(command):
    """发送命令并返回响应"""
    try:
        with serial.Serial('COM5', 115200, timeout=2) as ser:
            ser.write(command.encode() + b'\r\n')
            time.sleep(0.1)  # 给设备一点反应时间
            response = ser.read_until(b'\n')
            return response.decode().strip()
    except serial.SerialException as e:
        print(f"串口错误: {e}")
        return None

# 查询设备型号
model = query_spectrometer("*IDN?")
print(f"设备型号: {model}")

# 查询当前波长
wavelength = query_spectrometer(":SENSE:WAVELENGTH?")
print(f"当前波长: {wavelength} nm")

你想想看,这段代码里我用了 with 语句。好处是:不管代码是否报错,串口都会自动关闭。省得你忘了关串口,下次运行时报“端口被占用”。

注意: 串口是独占资源。一个程序打开了,另一个程序就打不开。调试时如果程序崩溃了,串口可能没释放。这时候要么重启电脑,要么用串口工具(如Putty、串口调试助手)先关掉占用。我习惯在代码里加个 try...finally 确保关闭。

2.7 小结

这一章我们干了三件事:

  • 搞懂了RS232和RS485的区别——一个短距离一对一,一个长距离多节点。
  • 装好了pyserial库——一行pip命令搞定。
  • 写了第一段串口通信代码——打开、发送、接收、关闭。

下一章,我们会深入设备协议,教你如何解析光谱仪返回的数据。说白了,串口只是管道,真正的内容在协议里。到时候你会看到,原来仪器返回的是一串有规律的字符串,我们要做的就是把它变成能用的数值。

嗯,今天就到这儿。动手试试吧,把代码跑起来,你会觉得串口这东西,其实没那么神秘。