文章目录
- 前言
- 串口数据接收
- 数据可视化
- 主函数
前言
书接上回,在上一篇中我们实现了arduino板控制超声波模块与舵机自动旋转并在串口打印距离与角度信息,这次我们来用python实现接收串口数据并可视化
所涉及的库:
import time
import numpy as np
import matplotlib.pyplot as plt
import serial
import serial.tools.list_ports
串口数据接收
这里选择写了个串口的类来进行处理
class COMM():
def __init__(self, baudrate, com_n=0):
self.port_list = serial.tools.list_ports.comports()
self.baundrate = baudrate
self.port_num = self.port_list.__len__()
self.device = self.port_list[com_n].device
self.comm = None
print("共有可用串口数量:", self.port_num)
for i in range(self.port_num):
print(i, ":", self.port_list[i])
# 用于打开串口
def open_serial(self):
flag = 0
self.comm = serial.Serial(self.device, self.baundrate, timeout=0.1)
if self.comm.isOpen():
print(self.device, "串口打开成功")
else:
print("串口打开失败")
flag = 255
return flag
# 用于接收
def recieve(self):
ret = ''
try:
rx_buf = self.comm.read()
if rx_buf != b'':
time.sleep(0.04)
rx_buf = rx_buf + self.comm.read_all()
# print("串口收到消息:", rx_buf)
flag = 0
time.sleep(0.01)
except:
pass
# 这里之所以这么切割数据是因为上一篇blog在串口打印的数据就是这个格式。你需要根据你串口实际打印的东西来修改下面的内容。
s = str(rx_buf)
ret = s.split(",")
ret[0] = ret[0][2:]
ret[1] = ret[1][:-5]
return ret
在init中,baudrate为串口的波特率,com_n为串口的序号,如果你不知道你用的串口是第几个,可以先空着,运行一遍后在命令行打印出的串口列表中看一下哪个是你用的串口。
数据可视化
为了模仿雷达的效果,我们用函数画一条扫描线
这里的center参数是考虑到:如果雷达是360度旋转的,那坐标原点可以放地图的中间;如果雷达是180度旋转的,坐标原点可以放在地图的中下方。但是后来偷懒了,就只写了一个360度旋转的情况,如果你的雷达是180度的,那绘制的图像会有一部分区域浪费掉
def get_line(theta, imshape, center="mid"):
# 获得地图尺寸
map_h = imshape[0]
map_w = imshape[1]
if center == "mid":
# 当地图的中心设置在正中心的情况
# 按角度范围分情况画图,防止出现扫描线不连续的情况
if (theta % np.pi < np.pi / 4) or (theta % np.pi > np.pi * 3 / 4):
line_x = np.asarray(range(map_w))
line_y = (line_x - map_w / 2) * np.tan(theta) + map_h / 2
line_y = line_y.astype(np.int64)
# 考虑数据溢出地图的问题
over_idx = (line_y < map_h) * (line_y > 0)
else:
if theta == 0:
return None, None
line_y = np.asarray(range(map_h))
line_x = (line_y - map_h / 2) / np.tan(theta) + map_w / 2
line_x = line_x.astype(np.int64)
# 考虑数据溢出地图的问题
over_idx = (line_x < map_w) * (line_x > 0)
return line_x[over_idx], line_y[over_idx]
除了扫描线,还需要根据串口得到的角度与距离信息换算出地图上扫描到的障碍物对应的位置
# 角度的起始以坐标轴的正半轴为起点,逆时针转动
def calc_location(imshape, center, scale, theta, dist):
map_h, map_w, _ = imshape
ret = [map_w - 1, map_h - 1]
x = center[1] + dist / scale * np.cos(theta)
y = center[0] + dist / scale * np.sin(theta)
x, y = x.astype(np.int64), y.astype(np.int64)
# 防止超出地图
if x > map_w or x < 0 or y > map_h or y < 0:
print("超出边界,对应坐标为:", x, ",", y)
else:
ret = [x, y]
return ret
主函数
以上内容加上主函数即可得到最终结果
if __name__ == '__main__':
# 地图尺寸
map_h = 128
map_w = 128
center = [map_h / 2, map_w / 2]
# 比例尺,如scale=10对应一格子代表10cm,scale=1对应一格子代表1cm
scale = 0.5
# 串口频率
baudrate = 115200
# 初始角度
init_theta = np.pi / 2
# 地图形状
imshape = (map_h, map_w, 3)
# 地图数据
imdata = np.zeros(imshape)
# Enable interactive mode.
plt.ion()
# Create a figure and a set of subplots.
figure, ax = plt.subplots()
# return AxesImage object for using.
im = ax.imshow(imdata)
COMM = COMM(baudrate=baudrate, com_n=1)
COMM.open_serial()
for n in range(600):
try:
dist, theta = COMM.recieve()
except:
continue
print(dist, theta)
dist, theta = float(dist), float(theta) / 180 * np.pi
# 获得扫描线
# line_x, line_y = get_line((init_theta + theta_vel * n) % (2 * np.pi), imshape)
line_x, line_y = get_line((init_theta + theta) % (2 * np.pi), imshape)
# 获得障碍点
# TODO:这里的theta还没有换算过
x, y = calc_location(imshape, center, scale, init_theta + theta, dist)
# 获得数据
imdata = imdata * 0.97
x, y = min(x, map_w - 1), min(y, map_h - 1)
imdata[x, y] = [1, 1, 1]
imdata_with_line = np.copy(imdata)
imdata_with_line[line_x, line_y] = [1, 0, 0]
# update image data
im.set_data(imdata_with_line)
# draw and flush the figure .
figure.canvas.draw()
figure.canvas.flush_events()
# time.sleep(0.005)