3. 串口通信基础:UART协议简介、Python的pySerial库安装与基本使用、打开与关闭串口
好,咱们进入第三章。这一章聊的是串口通信,也就是UART。说实话,做嵌入式固件开发,串口就是你的耳朵和嘴巴。没有它,你连芯片在干什么都不知道。
我记得刚入行那会儿,有一次调试一块新板子,焊好电源、接好JTAG,结果程序死活跑不起来。折腾了两天,最后发现是串口没配置对,打印信息全丢了。从那以后,我养成了一个习惯:任何板子到手,第一件事就是先把串口调通。
3.1 UART协议简介
UART,全称是Universal Asynchronous Receiver/Transmitter,通用异步收发器。说白了,就是一种串行通信协议,用一根线发数据,另一根线收数据。
为什么叫异步?因为它没有单独的时钟线。收发双方靠约定好的波特率来同步。你想想看,两个人约好每分钟说多少个字,那就不用看表也能对上节奏。
UART的数据帧结构是这样的:
| 起始位 | 数据位 | 校验位 | 停止位 |
|---|---|---|---|
| 1位(固定为0) | 5~9位(常用8位) | 可选(奇/偶/无) | 1~2位(固定为1) |
常见的配置是:115200波特率、8位数据位、无校验、1位停止位,简写为115200-8-N-1。我在项目中几乎都用这个配置,除非遇到老旧的设备才用9600。
关键参数:
- 波特率:每秒传输的符号数。常见值:9600、19200、38400、115200、921600
- 数据位:一帧中实际数据的位数。ASCII字符用7位,二进制数据用8位
- 停止位:帧结束的标志。1位最常用,2位用于慢速设备
- 校验位:简单的错误检测。实际项目中我很少用,因为上层协议通常有更可靠的校验
为什么会用115200?因为它是标准晶振频率的分频结果,误差最小。我曾经试过用非标准波特率,结果在高温下通信就丢包了。嗯,这里要注意,波特率误差超过2%就容易出问题。
3.2 Python的pySerial库安装
Python操作串口,最常用的库就是pySerial。它跨平台,Windows、Linux、macOS都能用。
安装很简单,一行命令搞定:
pip install pyserial
如果你用的是Python 3,记得用pip3:
pip3 install pyserial
我个人习惯在虚拟环境里装,避免污染全局环境。你可以这样:
python -m venv serial_env
source serial_env/bin/activate # Linux/macOS
# 或者 serial_env\Scripts\activate # Windows
pip install pyserial
小提示:安装完成后,可以用 python -c "import serial; print(serial.__version__)" 验证是否安装成功。如果没报错,就说明装好了。
3.3 pySerial基本使用
先来看一个最简单的例子:打开串口,发一条数据,然后关闭。
import serial
# 打开串口
ser = serial.Serial(
port='COM3', # Windows下用COM口,Linux下用/dev/ttyUSB0或/dev/ttyAMA0
baudrate=115200,
bytesize=8,
parity='N', # N:无校验, E:偶校验, O:奇校验
stopbits=1,
timeout=1 # 超时时间,单位秒
)
# 发送数据
ser.write(b'Hello, UART!\n')
# 读取数据
data = ser.read(10) # 读取10个字节
print(data)
# 关闭串口
ser.close()
这段代码看起来简单,但有几个坑我得提醒你。
避坑指南:
- 端口号问题:Windows上COM10以上要用
\\\\.\\COM10这种写法,否则打不开。我曾经被这个坑过一整个下午。 - 超时设置:timeout=None表示一直等,timeout=0表示非阻塞。我建议设个1秒,既不会卡死,也不会漏数据。
- 写数据要加b:pySerial要求发送bytes类型,字符串要加b前缀或者用.encode()转换。
3.4 打开与关闭串口的正确姿势
很多新手会犯一个错误:打开串口后忘记关闭。结果下次再打开就报错"端口被占用"。
正确的做法是用 with 语句,它会自动管理资源:
import serial
with serial.Serial('COM3', 115200, timeout=1) as ser:
ser.write(b'Hello\n')
response = ser.readline()
print(response)
# 退出with块后,串口自动关闭
如果你需要手动控制,那就用try-finally:
ser = serial.Serial('COM3', 115200, timeout=1)
try:
ser.write(b'AT\r\n')
data = ser.read(100)
finally:
ser.close()
我个人更推荐with语句,代码更简洁,也不容易漏掉close。
3.5 检测可用串口
有时候你不知道设备插在了哪个端口上。pySerial提供了一个工具函数:
import serial.tools.list_ports
ports = serial.tools.list_ports.comports()
for port in ports:
print(f"端口: {port.device}, 描述: {port.description}")
输出大概是这样:
端口: COM3, 描述: USB Serial Port (COM3)
端口: COM5, 描述: Arduino Uno (COM5)
这样你就能找到正确的端口号了。我在项目中经常用这个来写自动检测脚本,省得每次手动改代码。
3.6 实战小技巧
最后分享几个我常用的技巧:
- 清空缓冲区:打开串口后,先调用
ser.reset_input_buffer()和ser.reset_output_buffer(),把之前残留的数据清掉。不然一读就是一堆乱码。 - 读取一行:用
ser.readline()读取直到换行符。注意设置timeout,否则可能卡死。 - 读取所有可用数据:用
ser.in_waiting获取缓冲区字节数,然后ser.read(ser.in_waiting)一次性读完。
# 清空缓冲区
ser.reset_input_buffer()
ser.reset_output_buffer()
# 读取一行
line = ser.readline().decode('utf-8').strip()
# 读取所有可用数据
if ser.in_waiting > 0:
data = ser.read(ser.in_waiting)
好了,这一章就到这里。串口通信是固件调试的基础,一定要熟练掌握。下一章我们会讲如何用pySerial实现自动烧录,那才是真正的实战环节。