程序代码篇---Python串口
在 Python 里,serial
库(一般指pyserial
)是串口通信的常用工具。下面为你介绍其常用的读取和发送操作函数及使用示例:
1. 初始化串口
要进行串口通信,首先得对串口对象进行初始化,代码如下:
import serial# 初始化串口
ser = serial.Serial(port='/dev/ttyUSB0', # 串口设备,Windows系统可能是COM1、COM2等baudrate=9600, # 波特率timeout=1 # 超时时间(秒)
)# 判断串口是否已打开
if ser.is_open:print("串口已打开")
2. 发送数据
使用write()
函数可以向串口发送数据,示例如下:
# 发送字符串
ser.write(b'Hello, serial!\n') # 注意要使用字节类型# 发送字节数据
data = [0x01, 0x02, 0xFF]
ser.write(bytes(data))
3. 读取数据
读取数据有多种方式,以下是几种常见的:
# 读取单个字节
byte = ser.read() # 返回一个字节# 读取指定数量的字节
data = ser.read(size=10) # 最多读取10个字节# 读取一行数据(以换行符结尾)
line = ser.readline() # 需设置超时时间,避免程序阻塞# 循环读取所有可用数据
while ser.in_waiting:data = ser.read(ser.in_waiting)print(f"收到数据: {data}")
4. 其他常用函数
还有一些辅助函数能帮助你更好地进行串口通信:
# 获取待读取的字节数
bytes_waiting = ser.in_waiting# 清空输入/输出缓冲区
ser.flush() # 清空所有缓冲区
ser.flushInput() # 清空输入缓冲区
ser.flushOutput() # 清空输出缓冲区# 关闭串口
ser.close()
完整示例
下面是一个完整的串口通信示例,展示了数据的发送和读取过程:
import serialtry:# 初始化串口ser = serial.Serial(port='/dev/ttyUSB0',baudrate=9600,timeout=1)# 发送数据ser.write(b'Hello, Arduino!\n')# 读取响应if ser.in_waiting:response = ser.readline().decode('utf-8').strip()print(f"收到响应: {response}")except serial.SerialException as e:print(f"串口错误: {e}")
finally:# 确保串口最终会被关闭if 'ser' in locals() and ser.is_open:ser.close()
使用串口通信时,要特别留意以下几点:
- 要保证波特率、数据位、停止位等参数和设备设置一致。
- 为避免程序阻塞,建议设置超时时间。
- 数据传输时,要注意字节和字符串之间的转换。
- 要妥善处理异常情况,并及时关闭串口。
在 Python 的serial
库中,read()
、readline()
和readlines()
是用于从串口读取数据的三个常用方法,它们的功能和行为有明显区别。以下是对这三个方法的详细对比:
1. read(size=1)
- 功能:从串口读取指定数量的字节(默认读取 1 个字节)。
- 参数:
size
:要读取的最大字节数,默认值为 1。
- 返回值:返回一个包含读取字节的
bytes
对象。 - 特点:
- 非阻塞行为:如果没有数据可读,且设置了
timeout
,则最多等待timeout
秒后返回空字节b''
。 - 精确控制:适合按固定长度读取数据(如二进制协议)。
- 非阻塞行为:如果没有数据可读,且设置了
- 示例:
# 读取1个字节 byte = ser.read()# 读取10个字节 data = ser.read(size=10)
2. readline()
- 功能:从串口读取一行数据,直到遇到换行符
\n
(或超时)。 - 参数:无。
- 返回值:返回包含读取内容的
bytes
对象,包含换行符。 - 特点:
- 行缓冲机制:需要设置
timeout
,否则可能因等待换行符而导致程序阻塞。 - 文本协议适用:适合按行读取文本数据(如日志、命令响应)。
- 行缓冲机制:需要设置
-
# 读取一行数据(以\n结尾) line = ser.readline() # 返回 b'Hello\n' print(line.decode('utf-8')) # 转为字符串并打印
3. readlines()
- 功能:读取多行数据,直到超时,并返回一个包含所有行的列表。
- 参数:
hint
(可选):指定读取的最大字节数,默认值为 - 1(无限制)。
- 返回值:返回一个由
bytes
对象组成的列表,每个元素代表一行。 - 特点:
- 批量读取:一次性读取所有可用数据,并按行分割。
- 依赖超时:必须设置
timeout
,否则会一直等待直到无新数据。
- 示例:
# 读取所有行直到超时 lines = ser.readlines() # 返回 [b'Line 1\n', b'Line 2\n'] for line in lines:print(line.decode('utf-8').strip()) # 打印每行内容(去除换行符)
核心区别总结
方法 | 读取方式 | 终止条件 | 返回类型 | 适用场景 |
---|---|---|---|---|
read(size) | 按字节数读取 | 读取指定数量的字节或超时 | bytes | 二进制数据、固定长度协议 |
readline() | 按行读取 | 遇到换行符\n 或超时 | bytes (单行) | 文本协议、逐行处理 |
readlines() | 批量读取多行 | 超时(收集所有完整行) | list[bytes] | 一次性读取所有数据并按行分割 |
注意事项
-
超时设置:
- 所有方法都依赖
timeout
参数,否则可能导致程序阻塞(尤其是readline()
和readlines()
)。 - 建议在初始化串口时设置合理的超时值(如
timeout=1
)。
- 所有方法都依赖
-
编码问题:
- 串口返回的是
bytes
类型,若需转为字符串,需使用decode()
方法(如data.decode('utf-8')
)。
- 串口返回的是
-
性能考量:
readlines()
会一次性读取所有数据到内存,若数据量较大可能导致内存占用过高。
常见应用场景
- 读取传感器数据:使用
read(size)
按固定长度解析二进制数据。 - 命令行交互:使用
readline()
读取设备响应(如 AT 指令)。 - 日志收集:使用
readlines()
批量获取历史数据。
根据具体需求选择合适的方法,可以更高效地处理串口通信。
在计算机中,\r
(回车符)、\n
(换行符)和\r\n
(回车换行符)是用于表示文本行结束的不同方式,它们源于不同的历史背景和系统设计。以下是它们的核心区别和应用场景:
1. 基本定义
-
\r
(回车符,Carriage Return)- 对应 ASCII 码13(十进制),表示为
\r
或0x0D
(十六进制)。 - 作用:将光标移动到当前行的行首,不换行。
- 对应 ASCII 码13(十进制),表示为
-
\n
(换行符,Line Feed)- 对应 ASCII 码10(十进制),表示为
\n
或0x0A
(十六进制)。 - 作用:将光标移动到下一行,但不回到行首。
- 对应 ASCII 码10(十进制),表示为
-
\r\n
(回车换行符)- 组合使用
\r
和\n
,表示一个完整的行结束符。 - 常见于 Windows 系统和网络协议(如 HTTP、SMTP)。
- 组合使用
2. 历史起源
-
打字机时代:
\r
:将打印头移动到行首(Carriage Return)。\n
:将纸张向上滚动一行(Line Feed)。- 早期计算机沿用了这两个控制字符的组合。
-
不同系统的标准化:
- Unix/Linux/macOS(现代):使用
\n
作为行结束符。 - Windows:使用
\r\n
作为行结束符。 - 旧版 macOS(OS 9 及之前):使用
\r
作为行结束符。 - 网络协议(如 HTTP、SMTP):强制使用
\r\n
确保跨平台兼容性。
- Unix/Linux/macOS(现代):使用
3. 实际影响
文本处理差异
-
Unix 系统:
# Unix文本文件(每行以\n结尾) line1\nline2\nline3
-
Windows 系统:
# Windows文本文件(每行以\r\n结尾) line1\r\nline2\r\nline3
-
跨平台问题:
- 若在 Windows 中打开 Unix 文件,可能会看到
^M
符号(即\r
)。 - 若在 Unix 中打开 Windows 文件,每行末尾可能会多出一个不可见的
\r
。
- 若在 Windows 中打开 Unix 文件,可能会看到
编程语言处理
-
Python:
# 读取文件时自动处理不同换行符 with open('file.txt', 'r') as f:lines = f.readlines() # 自动识别\n、\r\n或\r# 手动处理字节数据时需注意 data = b'line1\r\nline2\nline3\r' lines = data.split(b'\r\n') # 需根据实际情况选择分隔符
-
正则表达式:
import re# 匹配任意换行符 re.split(r'\r\n|\r|\n', text)
4. 应用场景
场景 | 推荐使用的行结束符 | 示例说明 |
---|---|---|
Unix/Linux 脚本 | \n | Shell 脚本、Python 脚本等默认使用\n 。 |
Windows 程序 | \r\n | 批处理文件(.bat )、Windows 文本编辑器默认使用\r\n 。 |
网络协议 | \r\n | HTTP 请求 / 响应、SMTP 邮件协议等要求使用\r\n 分隔行。 |
串口通信 | 取决于设备 | - 某些设备(如 Arduino)默认使用\r\n 。- 需查阅设备文档确认。 |
编程处理 | 按需求选择 | - 若处理多平台文本,建议使用 Python 的universal newlines 模式。 |
5. 转换方法
-
文本编辑器:
- VS Code、Sublime Text 等支持在不同换行符格式(LF/CRLF)间切换。
-
命令行工具:
# Windows → Unix(将\r\n替换为\n) dos2unix file.txt# Unix → Windows(将\n替换为\r\n) unix2dos file.txt
-
Python 代码:
# 将\r\n转换为\n with open('windows.txt', 'r') as f:content = f.read().replace('\r\n', '\n') with open('unix.txt', 'w') as f:f.write(content)
总结
符号 | ASCII 值 | 名称 | 系统 | 示例场景 |
---|---|---|---|---|
\n | 10 | 换行符(LF) | Unix/Linux/macOS | 代码文件、Unix 配置文件 |
\r | 13 | 回车符(CR) | 旧版 macOS(OS 9-) | 极少使用 |
\r\n | 13+10 | 回车换行符(CRLF) | Windows、网络协议 | Windows 文本、HTTP 请求 / 响应 |
选择合适的行结束符时,需考虑目标系统和应用场景,避免因换行符不匹配导致的解析错误。
response = ser.readline().decode('utf-8').strip()
这行代码虽然简短,但涉及了串口通信、编码转换和字符串处理等多个关键知识点。以下是对其含义及背后知识点的详细解析:
1. ser.readline()
:从串口读取一行数据
-
功能:从串口缓冲区读取数据,直到遇到换行符
\n
(或超时)。 -
返回值:返回一个
bytes
类型的对象,包含读取的原始字节数据(包括换行符)。 -
示例:
若串口接收到Hello World!\r\n
,则ser.readline()
返回b'Hello World!\r\n'
。 -
注意事项:
- 需要设置串口超时时间(如
timeout=1
),否则可能因等待换行符而导致程序阻塞。 - 若数据中没有换行符,
readline()
会等待到超时,并返回已读取的部分数据。
- 需要设置串口超时时间(如
2. .decode('utf-8')
:字节解码为字符串
-
功能:将
bytes
类型的数据按指定编码格式转换为str
类型(即字符串)。 -
编码格式:
- UTF-8:一种通用的 Unicode 编码,支持全球大多数语言(如中文、英文、日文等)。
- 其他常见编码:
'gbk'
:中文国标码,兼容简体中文。'ascii'
:仅支持英文字母、数字和基本符号。'latin-1'
:西欧语言编码,直接映射字节值(0-255)。
-
示例:
b = b'Hello World!\r\n' s = b.decode('utf-8') # 转换为字符串 'Hello World!\r\n'
-
潜在问题:
- 若数据编码与指定编码不匹配,会抛出
UnicodeDecodeError
。
例如:# 假设数据实际为GBK编码 b = b'\xd6\xd0\xce\xc4' # GBK编码的"中文" b.decode('utf-8') # 报错:UnicodeDecodeError b.decode('gbk') # 正确:'中文'
- 解决方案:
try:s = data.decode('utf-8') except UnicodeDecodeError:s = data.decode('gbk', errors='replace') # 忽略无法解码的字符
- 若数据编码与指定编码不匹配,会抛出
3. .strip()
:去除字符串首尾空白字符
-
功能:移除字符串开头和结尾的空白字符(如
\n
、\r
、空格、制表符\t
等)。 -
示例:
s = ' Hello World!\r\n ' s.strip() # 返回 'Hello World!'
-
变体方法:
.lstrip()
:仅移除左侧空白字符。.rstrip()
:仅移除右侧空白字符。.strip(chars)
:移除指定字符(如.strip('\r\n')
仅移除换行符)。
4. 完整流程示例
假设串口发送的原始数据为 b'Hello World!\r\n'
,执行 response = ser.readline().decode('utf-8').strip()
后:
ser.readline()
返回b'Hello World!\r\n'
(bytes
类型)。.decode('utf-8')
将其转换为字符串'Hello World!\r\n'
(str
类型)。.strip()
移除首尾的\r\n
,最终response
的值为'Hello World!'
。
5. 关键知识点总结
操作 | 输入类型 | 输出类型 | 作用 |
---|---|---|---|
ser.readline() | 无 | bytes | 从串口读取一行数据(含换行符) |
.decode('utf-8') | bytes | str | 将字节按 UTF-8 编码转换为字符串 |
.strip() | str | str | 移除字符串首尾的空白字符 |
编码格式 | 适用范围 | 常见场景 |
---|---|---|
utf-8 | 全球通用编码,支持多语言 | 互联网数据、跨平台文本 |
gbk | 简体中文 | 中文 Windows 系统、早期中文软件 |
ascii | 仅英文和基本符号 | 简单文本协议(如 HTTP 头) |
6. 常见问题与建议
-
编码错误:
- 若数据包含非 UTF-8 字符(如中文),尝试使用
'gbk'
或'latin-1'
解码。 - 使用
errors='ignore'
或errors='replace'
忽略 / 替换无法解码的字符。
- 若数据包含非 UTF-8 字符(如中文),尝试使用
-
换行符处理:
- 若需要保留换行符(如处理多行文本),可省略
.strip()
。 - 若数据使用
\r
或\r\n
作为换行符,可使用.replace('\r', '')
统一处理。
- 若需要保留换行符(如处理多行文本),可省略
-
调试技巧:
# 打印原始字节数据,便于排查编码问题 data = ser.readline() print(f"原始字节: {data}") try:response = data.decode('utf-8').strip()print(f"解码后: {response}") except UnicodeDecodeError as e:print(f"解码错误: {e}")
总结
这行代码的核心逻辑是:
从串口读取原始字节数据 → 按 UTF-8 编码转换为字符串 → 去除首尾空白字符
理解字节与字符串的区别、编码转换机制以及字符串处理方法,是解决串口通信中文乱码、格式错误等问题的关键。