简单的串口通讯 基于 PySerial

串行通讯是只用两条线直接进行数据传输的通讯方式,数据将会按照位的形式传输,虽然传输速度没有以字节传输数据的并行传输快,但是对于通讯量不大而且需要轻量级传输方案的时候串口通讯就是很好的选择。一般的串口通讯会使用到三条线,一条接收线一条发送线和一条接地线。

串口通讯是异步的,所以能够做到在一根线上传送数据的同时在另一根线上接受数据。除此以外,完成串口通讯还需要数据发送方与数据接收方波特率、数据位、停止位和奇偶的校验匹配。

通讯参数

波特率

波特率是指数据信号对载波的调制速率,它用单位时间内载波调制状态改变的次数来表示

每秒钟通过信号传输的码元数称为码元 的传输速率,简称波特率,常用符号 “Baud” 表示,其单位为 “波特每秒(Bps)”。串口常见的波特率有 4800、9600、115200 等。

波特率为 9600bps;代表的就是每秒中传输 9600bit,也就是相当于每一秒中划分成了 9600 等份。

因此,那么每 1bit 的时间就是 1/9600 秒=104.1666...us。约 0.1ms。既然是 9600 等份,即每 1bit 紧接着下一个比特,不存在额外的间隔。两台设备要想实现串口通讯,这收发端设置的波特率必须相同。

数据结构

串口数据的发送与接收是基于帧结构,即一帧一帧的发送与接收数据。

传输数据结构图示例
  1. 起始位为 0 点评,标志一个字符的开始
  2. 数据位,紧跟起始位,是本次通讯发送的数据内容,发送的位数又通讯协议约定,传输的时候先传输低位,再传输高位。
  3. 校验位,分为奇校验和偶校验,由发送和接受双方协定,并不是必须要发的。如果是奇校验,那么必须保证数据位加上校验位 “1” 数量为奇数,若偶校验则为偶数。
  4. 停止位,代表传输字符的结束,可以是 1 位、1.5 位或者 2 位,但是一定是逻辑高电平。
  5. 空闲位:一个自负停止位结束到下个自负起始位开始,表示线路空闲,必须是高电平

单双工通讯

  1. 单工:只能往一个方向传
  2. 半双工:两边都可以传,但是一个时刻只能往一个方向传
  3. 全双工:可以一边传一边收

上位机与 STM32 的串口通讯

笔者本次使用的是树莓派 4B 和 STM32 进行通讯,如果和你是使用 Jetson nano 或者其他上位机的话可以查一查相应的通讯端口。

STM32 串口通信接口有两种,分别是:UART(通用异步收发器)、USART(通用同步异步收发器)

串口连接示意图 TXD:数据发送引脚;RXD:数据输入引脚

如上面图所示,如果你要使用三根线完成串口通讯,要注意 TXD 和 TXD 需要交叉连接。

如果要使用电脑调试串口,可以购买 CH340,一个 USB 转串口的工具,然后电脑上下载一个串口调试助手调试,Windows 上推荐使用 VOFA+,一个使用 Qt 编写的跨平台串口调试软件。在 Mac 上可以在 Appstore 下载串口调试助手,那个更加好使。

关于树莓派串口的提示

树莓派的 gpio 串口默认分配给了蓝牙,如果要使用的话要单独开启,而且使用树莓派的魔改 debian 系统和使用 ubuntu 系统的开启方式不太一样。如果你还没有给树莓派开启串口,需要先开启串口才能进行接下来的调试。你可以上网搜索相关文章,笔者之后也会写一篇教程聊聊树莓派在 ubuntu 系统上使用串口的一些经验。

程序部分

安装与引入 PySerial

PySerial 模块封装了对串行端口的访问。它为在 Windows、OSX、Linux、BSD(可能是任何符合 POSIX 的系统)和 IronPython 上运行的 Python 提供后端。名为 “Serial” 的模块会自动选择适当的后端。使用以下命令安装:

pip install pyserial
或者
conda install pyserial

通过以下代码引入 Pyserial

import serial

PySerial 常用函数汇总

ser = ser.Serial("/dev/ttyAMA0", 115200)  # 打开串口并设置波特率为 115200
print(ser.portstr)  # 能看到第一个串口的标识
ser.write("hello")  # 往串口里面写数据
ser.close()  # 关闭 serial 表示的串口
ser.open()  # 打开串口
data = ser.read(num)  # 读 num 个字符
data = ser.readline()  # 读一行数据,以/n 结束,要是没有/n 就一直读,阻塞。
ser.baudrate = 9600  # 设置波特率
print(ser)  # 可查看当前串口的状态信息
ser.isOpen()  # 当前串口是否已经打开
ser.inWaiting()  # 判断当前接收的数据
ser.flushInput()  # 清除输入缓冲区数据
ser.flushOutput()  # 中止当前输出并清除输出缓冲区数据

打开串口

serial.Serial(port=None, baudrate=9600, bytesize=EIGHTBITS, parity=PARITY_NONE, stopbits=STOPBITS_ONE, timeout=None, xonxoff=False, rtscts=False, write_timeout=None, dsrdtr=False, inter_byte_timeout=None, exclusive=None)

# example
ser = serial.Serial('/dev/ttyAMA0', 115200, timeout=0.01)

常用的可接受参数:

  • port:端口
  • baudrate:波特率
  • timeout:读超时时间(这个参数设置为 None 则为等待永久,也就是用不超时;设置为 0,非阻塞模式,马上返回数据,如果没有的话就是返回 0;设置为 x 就是当请求的字节数可用时,马上返回,否则就等待 x 秒,然后返回在此之前接收到的所有字节)

检查串口是否正常打开

一般如果需要对串口进行进一步的操作,需要先检查串口是否正常打开,否则万一串口出现了问题的时候不好排查,也许你可以尝试这样写?额,看了下官方文档,说 isOpen() 方法在 3.0 已经被抛弃了,新的 api 是 is_open(),版本不同的话需要留意一下。

if ser.isOpen():
    print("[sys] Serial port is open")
else:
    print("[sys] Serial port is not open")

串口发送

可以使用`write()` 方法发送数据,但是 write 方法只接受二进制数据的数据类型,所以我们发送数据有两种方式,一个是发送 bytes,另一个是 bytearray。

bytes 和 bytesarray

bytes() 是一个内置函数,用于创建一个不可变的 bytes 对象。bytes() 方法可以接受多种不同的参数类型,并将其转换为 bytes 对象。下面是一些创建和使用的例子:

# 创建一个空的 bytes 对象
empty_bytes = bytes()
print(empty_bytes)  # b''

# 通过字符串创建 bytes 对象
str_bytes = bytes("hello world", encoding="utf-8")
print(str_bytes)  # b'hello world'

# 通过字节串创建 bytes 对象
bytearray_bytes = bytes(bytearray(b'\x01\x02\x03\x04'))
print(bytearray_bytes)  # b'\x01\x02\x03\x04'

# 通过整数列表创建 bytes 对象
int_list_bytes = bytes([65, 66, 67])
print(int_list_bytes)  # b'ABC'

bytearray()函数其实和 bytes()差不多,但是 bytearray()是可以变化的。

# 创建一个空的 bytearray 对象
empty_bytearray = bytearray()
print(empty_bytearray)  # bytearray(b'')

# 通过字符串创建 bytearray 对象
str_bytearray = bytearray("hello world", encoding="utf-8")
print(str_bytearray)  # bytearray(b'hello world')

# 通过字节串创建 bytearray 对象
bytes_bytearray = bytearray(b'\x01\x02\x03\x04')
print(bytes_bytearray)  # bytearray(b'\x01\x02\x03\x04')

# 通过整数列表创建 bytearray 对象
int_list_bytearray = bytearray([65, 66, 67])
print(int_list_bytearray)  # bytearray(b'ABC')

上面的例子可以看到其实两个在使用上其实区别不大,除了可变性以外,还需要注意的是由于 bytes 对象是不可变的,因此在内存中只需要存储一份数据,多个 bytes 对象可以共享同一份数据。而 bytearray 对象是可变的,必须在内存中存储整个数据的副本,因此在内存占用方面可能会更高。

# 可变与不可变性的例子
# 创建一个 bytes 对象
b = b'hello'

# 创建一个 bytearray 对象
ba = bytearray(b'hello')

# bytes 对象是不可变的,无法修改
# b[0] = 72  # TypeError: 'bytes' object does not support item assignment

# bytearray 对象是可变的,可以修改
ba[0] = 72
print(ba)  # bytearray(b'Hello')

所以在选择的时候可以考虑这个数据是不是真的需要进行频繁的修改,如果需要进行频繁的修改操作,或者需要支持原地修改,那么应该选择 bytearray 对象;如果数据不需要修改,或者需要共享数据,那么应该选择 bytes 对象。

通过串口发送二进制数据

在知道了上面的两个对象以后,我们就可以创建他们来发送了,下面是一个例子:

        if ser.isOpen():
            send_data = 'something to send'.encode('utf-8') #传输字符串
            send_data = bytearray([0x01]) #传输一个十六进制的字节
            ser.write(send_data)

最后是传输过去了一个十六进制的字节。

这边遇到了一个坑,发送字符串或者 bytes 对象到单片机上的时候发现经常出错,也会有收不到信息的情况出现,但是 bytearray 就没遇到,比较稳定,如果在使用

读取数据

通过 read() 方法可以读取数据,也可以用 readline(),read() 默认是每次读取一个字节,可以通过传入参数来改变每次读取的数量,所以写读取代码的时候可以和负责写单片机的同学协定下每次传多少。

# 读取数据
while True:
    try:
        data = ser.readline()
        if data:
            print(data)
    except serial.SerialTimeoutException:
        # 没有数据可读,继续执行其他操作
        pass

上面的例子使用了 readline 函数完成读取,需要需要注意的是, 由于 readline() 方法会在读取到一行完整的数据时才返回,因此需要在串口数据流中确保每行数据都以回车符或换行符结尾。

# 读取数据
while True:
    data = ser.read(8)
    if data:
        print(data)
    else:
        # 没有数据可读,继续执行其他操作
        pass

上面的例子一次读取了 8 个字节,可以和发送方协商一个,自由调整。如果读取到数据,则打印数据;如果没有数据可读,则继续执行其他操作。

更多探讨

一个应用肯定不只有串口通讯一个功能,由于串口通讯经常会有阻塞情况出现,所以的话也可以使用 python 多线程来处理通讯问题,把主线程用来处理主要的任务。如果是用的 opencv 的话记得如果使用了 imshow 的话不要把 cv 任务放在子线程处理,会出现卡住的情况……原因未知。另外还需要处理树莓派开机串口的权限问题,查了很多的资料,目前还没得到解决,如果有解决方案请一定要告诉我😭

参考

pySerial API — pySerial 3.0 documentation (pythonhosted.org)

串口通信-1:RS232、RS485 通信和 python 实现 - 知乎 (zhihu.com)

看完这篇,不要说不懂串口通信!-面包板社区 (eet-china.com)

5.20 与串行端口的数据通信 — python3-cookbook 3.0.0 文档

用 Python 玩转串口(基于 pySerial)_python 打开串口_程序员仓库的博客-CSDN 博客

python - serial communication(串口通信)- 简书 (jianshu.com)

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇