上位机(Nano)与单片机(32)通信指南
本篇主要介绍下上位机与下位机的通信,使用jetson nano(Ubuntu20.04)和32单片机的通信,使用ttl转usb,可以直接插在usb口上作为串口使用,本文只介绍上位机部分的通信代码
ttl转usb可以在不供电的情况下直接使用(如果你的代码涉及串口,可以放个假的骗一下代码),首先在插入usb的时候我们需要查看下串口名称,终端输入指令
ls -l /dev/tty*
出现的内容应该是这样的(一堆串口名)
通过插拔各输入一次判断下哪个是新出的就能看到串口的名称了(上图USB0就是我的串口名)
接下来写串口的初始化,波特率为115200:
import serial
import time
try:
# 初始化串口
serial_port = serial.Serial(
port="/dev/ttyUSB0",
baudrate=115200,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
)
time.sleep(1) # 等待串口连接稳定
代码中的ttyUSB0就是我的串口名称,根据实际情况修改,串口名称会遇到各种莫名其妙的原因产生改变,建议修改串口名称(就不多说了),修改后的串口是这样的(以/dev/ttyBT为例)
接下来是串口消息的发送,包括int char string,但是int很难使用,所以我们不涉及这个部分:
# 发送char数据
char_value = 'A'
serial_port.write(char_value.encode())
serial_port.write(';'.encode())
# 发送字符串数据
str = "ABCD"
serial_port.write(str.encode())
serial_port.write(';'.encode())
需要注意的是,串口的发送有的时候并不会直接发送,需要一个回车或者 ; 作为结束符以供下位机识别(上图使用;作为下位机处理的结束符)
我们甚至可以缩写一下(一般如果输出的内容不改变的话可以使用这个,便捷)
serial_port.write("ABCD;".encode())
串口接收内容(包括读取字节byte和字符char)相比来说字节的兼容性好:
# 接收char数据
if serial_port.in_waiting > 0:
char_received = serial_port.read().decode()
print(f"接收到的char数据: {char_received}")
# 接收string数据
if serial_port.inWaiting() > 0:
data = serial_port.readline().strip()
print(f"接收到的string数据: {data}")
这个使用的是字节接收的形式接收数据的,兼容性会比较好(大部分的内容都是用字节存储的)这个char_received和data都是字符的形式(二进制),但是可以正常print出来,需要注意下
read()表示读取一个字节的内容,encode()表示把字符转化为字符(如果不转换就是字节),readline()是读取一整行的字节,直到回车\n,strip可以去掉空格的内容然后转化为字符串,根据情况选择
如果你想使用字符的接收可以参考下面的
if serial_port.in_waiting > 0:
# char_received = serial_port.read().decode()
# print(f"接收到的char数据: {char_received}")
char_byte = serial_port.read() # 读取一个字节
char_str = char_byte.decode() # 将字节转换为字符
print(f"接收到的字符: {char_str}")
这个代码会把char_str直接改成字符的形式,单片机复位的时候也会发消息到串口,是UTF-8的格式会导致程序直接退出,兼容性较差,不推荐使用(如果使用这个,下面的b就不用写了)
下面可能会用到的还有对接收到的数据进行处理:
if serial_port.inWaiting() > 0:
data = serial_port.readline().strip()
# print(data)
if data == b"ABCD": # 注意这里是字节比较
print("已接收ABCD")
# serial_port.reset_input_buffer() # 清理输入缓冲区
这个是使用字节比较,b"ABCD"会把字符串 ABCD 直接转化为二进制的形式,如果没有b就不能被识别,最后一句清空缓冲区不是必须的,在单片机大量发送消息给上位机的时候可能会用到,比如在单片机复位的时候可能存在消息发送过于频繁,那么就需要清空操作,否则会导致接受的代码出现问题
完整代码如下:
import serial
import time
# import struct
try:
# 初始化串口
serial_port = serial.Serial(
port="/dev/ttyUSB0",
baudrate=115200,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
)
time.sleep(1) # 等待串口连接稳定
# # 发送int数据
# int_value = 1234
# int_bytes = struct.pack('>I', int_value) # 大端字节序
# serial_port.write(int_bytes)
# serial_port.write(';'.encode())
# 接收int数据
# if serial_port.in_waiting >= 4: # int数据大小为4字节
# int_received_bytes = serial_port.read(4)
# int_received = struct.unpack('>I', int_received_bytes)[0]
# print(f"接收到的int数据: {int_received}")
while 1:
# # 发送char数据
# char_value = 'A'
# serial_port.write(char_value.encode())
# serial_port.write(';'.encode())
# 发送字符串数据
str = "ABCD"
serial_port.write(str.encode())
serial_port.write(';'.encode())
# 接收char数据
if serial_port.in_waiting > 0:
# char_received = serial_port.read().decode()
# print(f"接收到的char数据: {char_received}")
char_byte = serial_port.read() # 读取一个字节
char_str = char_byte.decode() # 将字节转换为字符
print(f"接收到的字符: {char_str}")
if char_str == "A": # 字符比较 复位回消息可能会产生问题
print("已接收字符 A")
if serial_port.inWaiting() > 0:
data = serial_port.readline().strip()
print(data)
if data == b"ABCD": # 注意这里是字节比较
print("已接收ABCD")
except serial.SerialException as e:
print(f"串口错误: {e}")
except Exception as e:
print(f"其他错误: {e}")
finally:
if serial_port.is_open:
serial_port.close()
int发送的时候单片机需要重新写相应的代码,不推荐使用,char和string的话,单片机只需要同一套发送就能兼容了,str的话可以接收复位发来的消息的(需要清缓存)可以用来上位机那边程序的复位
部分情况总结:
推荐使用其他内容前先将串口配置确认无误,查看两边的波特率,串口名称是否改变,接线是否有问题,再进行代码的写入,否则会导致还没建立起联系后面的内容都白写的问题(而且很难检查到哪里的问题)
不要让串口在几秒内多次发送消息,会导致消息的吞并,比如说,我A和B一起发送,可能会导致A刚进缓存区就被B顶掉了,从而导致通信异常,下位机同理,
作者:墨ml