百度智能车竞赛丝绸之路1——智能车设计与编程实现控制
百度智能车竞赛丝绸之路2——手柄控制
一、项目简介
本项目现已基于鲸鱼机器人开发套件对其整体外形进行设计,并且对应于实习内容——以“丝绸之路”为题,对机器人各个功能与机器人结构部分进行相关设计与调整。主要可以实现“车道线巡检”“音乐交际”、“城堡检测”、“翻山越岭”。
本项目通过 Python的Serial 串口通讯库实现与机器人下位机的RS232通讯;通过Struct二进制解析库完成对于手柄IO数据口数据的解析,从而实现通过Xbox手柄控制机器人各个关节及其相应的功能;通过飞浆团队研发的轻量化深度学习推理框架——PaddleLite,实现对于车道拐弯线与机器人航偏值的预测、不同城堡与友谊交际数据的识别分类、不同地标数据和挡板的识别分类,进而通过前置摄像头作为机器人系统的感知器检测机器人前方的车道曲线、地标和挡板,通过左置摄像头感知器检测机器人左方的不同城堡等数据。进而使得本次实习通过上述三种框架实现了机器人的相关任务的完成。
相关资料
2022智能车百度创意组学习资料
链接:https://pan.baidu.com/s/1GjCP3fz_cikNNPMaSAWANQ
提取码:2022
二、实现原理
2.1RS-232协议
RS-232协议是美国电子工业协会EIA(Electronic IndustryAssociation)制定的一种异步串行通信物理接口标准,RS232协议的最新版本为RS-232-C。RS-232-C总线标准设有25 条信号线,包括一个主通道和一个辅助通道。在多数情况下主要使用主通道,对于一般双工通信,仅需三条信号线就可实现:发送线、接收线、地线。适合于数据传输速率在0~20kbps、传输距离小于20m的异步串行通信。
在异步串行通信中,通信协议重要包括:起始位、数据位、校验位、停止位、波特率。通信线上没有数据时处于逻辑1状态,当准备发送一个字符数据时,首先发送一个逻辑0,这个逻辑0就是起始位;在起始位后紧跟的是数据位,数据位的个数可以是5、6、7或8。校验一般采用奇偶检验,校验位可以不发送。校验位之后为停止位,停止位表示一个字符数据的结束,停止位可以是1位、1.5位或2位高电平。
与传统RS232接口不同的是,本次项目中的RS232采用的特制网口,需要接到MC601控制板,通过板子与上位机连接。
2.2 pyserial库
Python的serial库是用于串口通信的库,它提供了一组与串口设备通信的函数和类。在使用serial 库进行RS232通讯时,首先,需要创建一个Serial对象来表示串口连接。创建Serial对象时需要指定串口号、波特率、数据位、停止位等参数。使用bytes的fromhex方法将十六进制的字符串格式协议转换为计算机可以直接读的二进制数据,使用Serial对象的write()方法向串口设备发送数据。使用Serial对象的read()或readline()方法从串口设备接收数据。read()方法可以指定要接收的数据长度,而readline()方法则会读取一行数据。使用Serial对象的close()方法关闭串口连接。使用Serial对象的flush()方法将缓冲区的数据全部发送出去并清空缓冲区。最后在操作完毕后,需要及时关闭串口连接,以释放串口资源。
三、实现过程
3.1基础搭建
参考基础版教程对智能车底盘进行组装,并安装控制板,参考官方文档机械臂、夹爪,独立设计两个相机的安装、举旗结构、击打坏人结构,最终智能车模型如下:
智能车搭建完成以后,对控制板进行环境搭建,参考文档将paddle_install放到指定文件夹执行.sh文件,安装paddlelite。环境搭建好以后便是通过电脑进行控制,将智能车的网口经过网线转USB接到电脑上,在网络适配器中找到新增加的连接进行更改,选择IPv4,
改成下图所示:
然后便可以通过安装的软件对智能车进行控制
Xftp和winscp可以进行文件传输,shell可以进行终端指令控制,鲸鱼编程软件可以单独控制硬件和传感器,MobaXterm_Personal既可以进行文件传输又可以进行终端控制。
以MobaXterm_Personal软件为例,点击sesson,选择SFTP,输入IP192.168.1.254,username为root,进入后输入密码root。如下图
连接后将官方提供的功能包src上传到板子,进入到workspace下,再通过ssh连接,执行python3 walk_lane.py,即可使用官方模型进行巡线,加前缀nohup可以断开网线进行巡线,按2开始,按4结束。将手柄接收器接到EdgeBoard板子USB口上,进入collect文件夹下,执nohup python3 collect_road_data.py,按手柄上的select后,蜂鸣笛响一下,即可手柄控制智能车跑巡线采集数据集,跑完之后按select键退出。之后将EdgeBoard板子collect下的train文件夹传到电脑,将训练数据集程序train_lane在pycharm新建项目,将train文件夹放到train_lane/train_data下,安装paddlepaddle_gpu执行cnn_trainer.py进行模型训练,如下图
3.2 各硬件传感器控制
3.2.1 简单程序实例
ser = serial.Serial("com3",380400, timeout=0.5)
data= '77 68 06 00 02 0C 01 02 20 0a'
cmd_data = bytes.fromhex(data)
ser.write(cmd_data)
通过python的serial库控制传感器,com3是传感器端口号,380400为波特率,06表示数据长度,00协议版本,02设置动作,0C为编码电机的编号,即10进制的12,01为版号,02为端口,表示电机接在拨号为01的电机控制板的M2口,20表示速度为32。
3.2.2 底盘控制
ChassisController类中move()方法需要传入一个含四个参数的列表与底层进行协议通讯控制四个电机的速度,run()和stop()方法用于识别到友谊时停车到指定位置,turn_left()方法,以指定速度左转,左右边轮速度互为相反数,turn_left()方法同理。steer()方法用于巡线,angle为识别到的线路阈值,将angle乘0.85,在delta正负不同时以1为基准进行左右轮差速实现转弯。
class ChassisController:
def __init__(self, _serial: serial.Serial):
self.serial = _serial
self.speed = 25
self.kx = 0.85
self.min_speed = 20
self.comma_head_all_motor = bytes.fromhex('77 68 0c 00 02 7a 01')
self.comma_trail = bytes.fromhex('0A')
def steer(self, angle):
# print(angle)
speed = int(self.speed)
delta = angle * self.kx
left_wheel = speed
right_wheel = speed
if delta < 0:
left_wheel = int((1 + delta) * speed)
elif delta > 0:
right_wheel = int((1 - delta) * speed)
# print("left_speed:", left_wheel, " right_speed:", right_wheel)
self.Move([left_wheel, right_wheel, left_wheel, right_wheel])
def run(self, l_speed, r_speed):
self.Move([l_speed, r_speed, l_speed, r_speed])
def stop(self):
self.move([0, 0, 0, 0])
def move(self, speeds):
left_front = int(speeds[0])
right_front = -int(speeds[1])
left_rear = int(speeds[2])
right_rear = -int(speeds[3])
self.min_speed = int(min(speeds))
# print(speeds)
left_front_kl = bytes.fromhex('01') + left_front.to_bytes(1, byteorder='big', signed=True)
right_front_kl = bytes.fromhex('02') + right_front.to_bytes(1, byteorder='big', signed=True)
left_rear_kl = bytes.fromhex('03') + left_rear.to_bytes(1, byteorder='big', signed=True)
right_rear_kl = bytes.fromhex('04') + right_rear.to_bytes(1, byteorder='big', signed=True)
send_data_all_motor = (self.comma_head_all_motor + left_front_kl + right_front_kl + left_rear_kl + right_rear_kl
+ self.comma_trail)
self.serial.write(send_data_all_motor)
time.sleep(0.01)
def turn_left(self):
speed = self.speed
left_wheel = -speed
right_wheel = speed
self.move([left_wheel, right_wheel, left_wheel, right_wheel])
def turn_right(self):
speed = self.speed
left_wheel = speed
right_wheel = -speed
self.move([left_wheel, right_wheel, left_wheel, right_wheel])
def reverse(self):
speed = self.speed
self.move([-speed, -speed, -speed, -speed])
3.2.3 RGB灯光闪烁
rgb_light类定义了2维列表light_num,分别向第一个灯、第二个灯、第三个灯、第四个灯,写入白,红、绿、蓝灯光,实现四个灯依次亮,转完一圈后变颜色继续转的闪烁效果。之后定义线程,使程序运行时灯光一直闪烁。
class rgb_light:
def __init__(self, ser: serial.Serial):
self.stop_event = threading.Event() # 初始化 stop_event 属性
self.ser = ser
def light_control(self):
light_num =[['01 ff ff ff','02 ff ff ff','03 ff ff ff','04 ff ff ff'],
['01 00 ff ff','02 00 ff ff','03 00 ff ff','04 00 ff ff'],
['01 ff 00 ff','02 ff 00 ff','03 ff 00 ff','04 ff 00 ff'],
['01 ff ff 00','02 ff ff 00','03 ff ff 00','04 ff ff 00'],]
while not self.stop_event.is_set():
for j in range(len(light_num)):
for i in range(len(light_num[0])):
cmd_data = bytes.fromhex(data) + bytes.fromhex(light_num[j][i]) + bytes.fromhex('0A')
self.ser.write(cmd_data)
time.sleep(0.5)
self.ser.write(bytes.fromhex(data1))
if __name__ == '__main__':
ser = serial.Serial("com5", 380400, timeout=0.5)
data = '77 68 08 00 02 3B 04' # 灯光 04 00 00 00
rgb_light = rgb_light(ser)
thread = threading.Thread(target=rgb_light.light_control)
thread.start()
time.sleep(20)
rgb_light.stop_event.set() # 调用 stop_event 对象的 set 方法来停止线程
3.2.4 举旗
向RaiseFlag类的servo_control方法传入角度和速度两个参数,即可控制智能舵机转动的角度和速度,self.angle里面定义了几个常用角度。
class RaiseFlag:
def __init__(self, _serial: serial.Serial):
self.serial = _serial
self.raise_flag = bytes.fromhex('77 68 08 00 02 36 02')
self.comma_trail = bytes.fromhex('0A')
self.angle = [225, 135, 45, -45]
def servo_control(self, angle, speed):
angle = int(angle)
cmd_servo_data = self.raise_flag + speed.to_bytes(1, byteorder='big',
signed=True) + angle.to_bytes(3, byteorder='little',
signed=True) + bytes.fromhex(
'0A')
self.serial.write(cmd_servo_data)
self.serial.flush()
time.sleep(0.01)
3.2.5 数码管显示陀螺仪角度、蜂鸣器唱歌
SensorController类中Buzzers()方法向蜂鸣器写入列表buzzer_list中的每一组包含音高和音长的参数,根据1-9个等级的音高结合音长粗略模仿D0、Re、Mi、Fa、So、La、Ti七个基本音符实现小星星歌曲音律效果。Groy_Sensor()方法传入板号和陀螺仪连接端口号即可控制陀螺仪,获取当前机械臂倾斜角度值,并用if,else语句排除了有时可能出现的异常。Nixie_Tube()方法需要传入陀螺仪的检测角度值,对值进行处理实现机器人学坐标系下的机械臂倾斜角度显示,即机械臂水平向上角度从360递减,机械臂水平向下角度从0递增。最后析构函数调用Nixie_clear()方法清除数码管显示。
class SensorController:
def __init__(self, _serial: serial.Serial):
self.serial = _serial
self.nixie_tube = bytes.fromhex('77 68 06 00 02 38 02')
self.nixie_clear = bytes.fromhex('77 68 06 00 02 39 02')
self.buzzer = bytes.fromhex('77 68 06 00 02 3D 00 01 0A')
self.buzzer_list = [[1, 2], [1, 2], [5, 2], [5, 2], [6, 2], [6, 2], [4, 4], [4, 2], [4, 2], [3, 2], [3, 2],
[2, 2],
[2, 2], [1, 4]]
self.buzzer_thread = threading.Thread(target=self.Buzzers, daemon=True, name='Buzzer')
self.comma_trail = bytes.fromhex('0A')
self.done = True
def Buzzers(self):
for say in self.buzzer_list:
say_str = '{:02x}'.format(say[0])
tm_str = '{:02x}'.format(say[1])
# print(say_str, '--', tm_str)
cmd_data = bytes.fromhex('77 68 06 00 02 3D {} {} 0A'.format(say_str, tm_str))
lock.acquire()
self.serial.write(cmd_data)
self.serial.flush()
lock.release()
time.sleep(say[1] / 4)
def Groy_Sensor(self, value, port):
value_str = '{:02x}'.format(value)
port_str = '{:02x}'.format(port)
cmd_data = bytes.fromhex('77 68 06 00 01 DC {} {} 0A'.format(port_str, value_str))
self.serial.write(cmd_data)
self.serial.flush()
time.sleep(0.01)
read_data = self.serial.readline()
if len(read_data) < 11 or read_data[-3] != port or read_data[-4] != 0xDC:
return None
else:
data = read_data[3: (3 + 4)]
sensor = struct.unpack('<i', struct.pack('4B', *data))[0]
return sensor
def Nixie_Tube(self, value):
try:
if value < 0:
value = 360 + value
else:
value = value
value = (value & 0xff).to_bytes(1, byteorder='big', signed=True) + (value >> 8).to_bytes(1, byteorder='big',
signed=True)
except Exception as err:
print(err)
value = 0
value = (value & 0xff).to_bytes(1, byteorder='big', signed=True) + (value >> 8).to_bytes(1, byteorder='big',
signed=True)
cmd_data = self.nixie_tube + value + self.comma_trail
self.serial.write(cmd_data)
self.serial.flush()
time.sleep(0.01)
def Nixie_clear(self):
cmd_data = self.nixie_clear + bytes.fromhex('00 00') + self.comma_trail
self.serial.write(cmd_data)
self.serial.flush()
time.sleep(0.01)
def __del__(self):
self.Nixie_clear()
四、实现结果
最终巡航完成了“车道线巡检”、“翻山越岭”,举旗,在检测到friendship 播放歌曲小星星完成“音乐交际”,巡航依然有问题尚未解决。
手柄控制效果视频链接
点赞+收藏+关注后私信博主获取完整代码