02 Python 底盘运动控制

来自Waveshare Wiki
跳转至: 导航搜索

Python 底盘运动控制

在本章节中我们会写一个Python例程,用于控制机器人底盘运动,你也可以自行使用其它语言来进行机器人底盘的运动控制。


底盘控制原理

在本例程中,我们使用 JupyterLab 中的代码块,生成一串 JSON 指令,通过树莓派的 GPIO 串口(默认与下位机通信的波特率为115200),将这个 JSON 指令发送给下位机,下位机收到指令后开始执行动作。

你可以参考后续的章节来了解都可以给下位机发送什么样的指令,你也可以使用其它语言来实现这一功能,或者自己写一个上位机的应用。


这样设计的优点

我们使用上位机+下位机的架构可以充分解放上位机的宝贵资源,上位机(树莓派,Jetson 等 SBC)类似人类的大脑,ESP32作为下位机类似人类的小脑,上位机执行视觉处理/决策方面的高阶控制,下位机执行具体的运动控制/插值等低阶控制。这样可以做到大小脑分工合作,下位机负责高频PID控制可保证车轮转速准确,上位机也不需要在这类低复杂度高算力的工作上浪费资源。


主程序 app.py

项目文件夹中的 app.py,这个是产品的主程序,当你执行过 autorun.sh 后(产品默认出厂是已经配置好自动运行的了),app.py 会在未来每次开机时自动运行,它的运行会占用 GPIO串口 和 摄像头资源,如果你在交互式教程中或者其它程序中需要用到这些资源可能会引发冲突或其它错误,二次开发或学习前,务必关闭掉 app.py 的自动启动。

由于 app.py 中使用了多线程,且开机时它使用 crontab 来自动运行,所以通常不能使用 sudo killall python 这样的指令来关闭 app.py, 你需要在 crontab 中注释掉运行 app.py 的那一行然后再重启机器人产品。

crontab -e

首次使用该命令后,会询问你希望使用什么编辑器来打开这个 crontab 文件,推荐选择 nano,输入 nano 对应的序号即可,然后按回车键确认。

用 "#" 注释掉 ...... app.py 这一行

# @reboot ~/ugv_pt_rpi/ugv-env/bin/python ~/ugv_pt_rpi/app.py >> ~/ugv.log 2>&1
@reboot /bin/bash ~/ugv_pt_rpi/start_jupyter.sh >> ~/jupyter_log.log 2>&1

注意:千万不要注释掉 start_jupyter.sh 这一行,否则开机后你将不能使用 jupyterLab 使用交互式教程。

然后退出并保存变更,具体方法为,编辑 crontab 的内容后,按 ctrl + x,退出 nano,由于你编辑过 crontab 文件了,所以它会问你是否保存变更(Save modified buffer?),输入字母 Y,然后按回车退出,即可保存变更。

再次重启设备开机后产品主程序就不会自动运行了,你可以随意使用 JupyterLab 中的教程了,后续如果你需要再恢复主程序开机自动运行时,可以再使用上面的方法打开 crontab 问价,然后删除掉 @ 前面的 '# ' 符号,退出并保存变更,这样就能恢复主程序的开机自动运行了。


底盘控制例程

在下面的例程中,我们使用 is_raspberry_pi5() 函数来判断当前的树莓派型号,因为树莓派4B和树莓派5的 GPIO 串口的设备名称是不同的,你需要使用正确的 GPIO 设备名称,且使用与下位机相同的波特率(默认为115200)。

运行以下代码块之前你需要先将产品架高起量,保持驱动轮全部离地,调用以下代码块后机器人会开始走动,小心不要让机器人从桌面上掉落。

from base_ctrl import BaseController
import time

# 用于检测树莓派的函数
def is_raspberry_pi5():
    with open('/proc/cpuinfo', 'r') as file:
        for line in file:
            if 'Model' in line:
                if 'Raspberry Pi 5' in line:
                    return True
                else:
                    return False

# 根据树莓派的型号来确定 GPIO 串口设备名称
if is_raspberry_pi5():
    base = BaseController('/dev/ttyAMA0', 115200)
else:
    base = BaseController('/dev/serial0', 115200)

# 轮子以0.2m/s的速度转动2秒钟后停止
base.send_command({"T":1,"L":0.2,"R":0.2})
time.sleep(2)
base.send_command({"T":1,"L":0,"R":0})

通过调用上面的代码块,树莓派会首先发送 {"T":1,"L":0.2,"R":0.2} 这条指令(后面章节我们会再具体介绍指令的构成),车轮开始转动,间隔两秒钟后树莓派会发送 {"T":1,"L":0,"R":0} 这条指令,车轮会停止转动,这里需要注意的一点是,即使不发送后面的停止车轮转动的指令,如果你没有发送新的指令,车轮依然会停止转动,这是因为下位机内含有心跳函数,心跳函数的做用是在上位机长时间没有新的指令发送给下位机时,下位机自动停止目前的移动指令,改函数的目的是为了避免上位机由于某些原因死机而导致下位机继续运动。

如果你希望机器人一直持续不断地运动下去,上位机需要每隔2秒-4秒循环发送运动控制的指令。


底盘类型选择

你可能会发现当你输入上面的运动控制指令后,机器人车轮的转动方向或者转动速度并不符合预期,那时因为在控制底盘前,你需要设置底盘的类型(每次执行产品主程序 app.py 时都会自动配置底盘类型,配置参数被存储在 config.yaml 中),这样底盘才会按照正确的参数来进行电机控制(默认每次底盘重新上电都需要由上位机配置一次),你可以向底盘发送以下命令来设置底盘的类型:

其中“main”的值为底盘类型:

  • 1:RaspRover(四轮四驱底盘)
  • 2: UGV Rover(六轮四驱底盘)
  • 3:UGV Beast(履带底盘)

“module”的值为模块类型:

  • 0:没有安装模块和安装有云台模块都可以设置为该值
  • 1:机械臂模块(如果没有安装机械臂会导致底盘 Ping 不通关节报错)
  • 2:云台模块(如果没有安装云台会导致底盘Ping不同关节报错)

使用案例如下,例如你使用的是安装有 云台 的 UGV Rover 产品,你可以通过向下位机发送下面这条指令来设置底盘类型:

{"T":900,"main":2,"module":0} 或 {"T":900,"main":2,"module":2}
  • 上面这两条指令都可以用来配置带有云台的 UGV Rover 产品,如果你不需要底盘反馈云台的角度信息(仅针对用户的二次开发,例程中暂不包含这方面应用),推荐使用前者。

你可以根据自己手中产品的类型,更改下面代码块中的JSON参数,来配置底盘类型:

base.send_command({"T":900,"main":2,"module":0})

然后再执行下面的代码块控制底盘,轮子的转动方向和速度就是正确的了。

base.send_command({"T":1,"L":0.2,"R":0.2})
time.sleep(2)
base.send_command({"T":1,"L":0,"R":0})


底盘转向原理

上面的例程中,你可以控制机器人向前走两秒钟后停止,后续可以通过更改参数来对底盘进行转向控制,底盘采用差速转向原理进行运动控制。

当车辆转弯时,内侧轮(转向方向相同的那一侧)需要行进更短的距离,因此需要旋转得更慢,以保持车辆的稳定性。 差速器通过允许两个驱动轮以不同速度旋转来实现这一目标。通常情况下,外侧轮(转向方向相反的那一侧)旋转得更快,而内侧轮旋转得更慢。 这种不同的旋转速度导致车辆产生转向运动,从而使其沿着预期的方向转向。

你可以给两侧车轮不同的目标线速度来控制车辆的转向,并且可以轻松地调整转向半径。