树莓派4B机器狗的串口通信驱动库pyserial实践

pyserial是一个串口通信驱动库,常用的Windows、Linux、MacOS等都可以安装,这里我使用的是树莓派4B来测试,这块板子还是很强大的,我们可以通过pyserial这个库来操作基于这块板子上的机器狗之类的设备。

1、四足机器狗

本人的是四足机器狗,每条腿有三个舵机,所以是3个自由度,四条腿总共12个舵机也就是12自由度,每个舵机分别控制着对应的关节,比如根关节控制基节,髋关节控制大腿,膝关节控制小腿,如下图:

实物机器人,里面的关节矩阵的顺序是,前面左边为第一组,右边为第二组,后面的右边为第三组,后面的左边为第四组。

2、OS环境

在安装之前我们一般都先熟悉下本地环境,这样方便下载安装对应的版本。

cat /etc/os-release

NAME="Ubuntu"
VERSION="20.04.4 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.4 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal

查看芯片的类型:uname -a

Linux yahboom 5.4.0-1069-raspi #79-Ubuntu SMP PREEMPT Thu Aug 18 18:15:22 UTC 2022 aarch64 aarch64 aarch64 GNU/Linux

可以看到是64位的架构而且是全新的ARMv8架构,使用的也是全新的A64指令集,其中 raspi 就是Raspberry Pi树莓派的别名,这个就是本人的树莓派在Ubuntu 20版本的环境情况。

3、安装pyserial

安装命令:pip3 install pyserial
安装好了之后,我们来测试下是否成功,安装包是pyserial,导入模块是serial

import serial
print(serial.VERSION)
#'3.5'
#dir(serial)
['CR', 'EIGHTBITS', 'FIVEBITS', 'LF', 'PARITY_EVEN', 'PARITY_MARK', 'PARITY_NAMES', 'PARITY_NONE', 'PARITY_ODD', 'PARITY_SPACE', 'PortNotOpenError', 'PosixPollSerial', 'SEVENBITS', 'SIXBITS', 'STOPBITS_ONE', 'STOPBITS_ONE_POINT_FIVE', 'STOPBITS_TWO', 'Serial', 'SerialBase', 'SerialException', 'SerialTimeoutException', 'Timeout', 'VERSION', 'VTIMESerial', 'XOFF', 'XON', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', 'absolute_import', 'basestring', 'importlib', 'io', 'iterbytes', 'os', 'protocol_handler_packages', 'serial_for_url', 'serialposix', 'serialutil', 'sys', 'time', 'to_bytes', 'unicode']

4、串口通信接口

4.1、list_ports接口列表

首先我们列出有哪些可用的接口

from serial.tools import list_ports
plist = list_ports.comports()
print(plist[0][0])
#'/dev/ttyAMA0'

这个'/dev/ttyAMA0'就是树莓派4B与上位机的通讯接口,得到了接口名称之后,我们就可以打开这个接口进行通信了。

4.2、打开接口

ser = serial.Serial('/dev/ttyAMA0',115200,timeout=5)

这里的超时时间推荐指定,避免因为读取不到数据,程序会一直等待。 

print(ser)
#Serial<id=0xffff87addc40, open=True>(port='/dev/ttyAMA0', baudrate=115200, bytesize=8, parity='N', stopbits=1, timeout=None, xonxoff=False, rtscts=False, dsrdtr=False)

这里在串口设备名称与端口指的是同一个东西:ser.nameser.port 

查看里面包含哪些方法与属性: dir(ser)

['BAUDRATES', 'BAUDRATE_CONSTANTS', 'BYTESIZES', 'PARITIES', 'STOPBITS', '_SAVED_SETTINGS', '__abstractmethods__', '__class__', '__del__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_abc_impl', '_baudrate', '_break_state', '_bytesize', '_checkClosed', '_checkReadable', '_checkSeekable', '_checkWritable', '_dsrdtr', '_dtr_state', '_exclusive', '_inter_byte_timeout', '_parity', '_port', '_reconfigure_port', '_rs485_mode', '_rts_state', '_rtscts', '_set_rs485_mode', '_set_special_baudrate', '_stopbits', '_timeout', '_update_break_state', '_update_dtr_state', '_update_rts_state', '_write_timeout', '_xonxoff', 'applySettingsDict', 'apply_settings', 'baudrate', 'break_condition', 'bytesize', 'cancel_read', 'cancel_write', 'cd', 'close', 'closed', 'cts', 'dsr', 'dsrdtr', 'dtr', 'exclusive', 'fd', 'fileno', 'flush', 'flushInput', 'flushOutput', 'getCD', 'getCTS', 'getDSR', 'getRI', 'getSettingsDict', 'get_settings', 'inWaiting', 'in_waiting', 'interCharTimeout', 'inter_byte_timeout', 'iread_until', 'isOpen', 'is_open', 'isatty', 'name', 'nonblocking', 'open', 'out_waiting', 'parity', 'pipe_abort_read_r', 'pipe_abort_read_w', 'pipe_abort_write_r', 'pipe_abort_write_w', 'port', 'portstr', 'read', 'read_all', 'read_until', 'readable', 'readall', 'readinto', 'readline', 'readlines', 'reset_input_buffer', 'reset_output_buffer', 'ri', 'rs485_mode', 'rts', 'rtscts', 'seek', 'seekable', 'sendBreak', 'send_break', 'setDTR', 'setPort', 'setRTS', 'set_input_flow_control', 'set_output_flow_control', 'stopbits', 'tell', 'timeout', 'truncate', 'writable', 'write', 'writeTimeout', 'write_timeout', 'writelines', 'xonxoff']

4.3、字节数组

打开串口接口,我们试着来读取一行:b_data=ser.readline()

#b'U\x00\t\x12\x01\x14\xcf\x00\xaa'

转成列表就是十进制的形式,看起来就比较直观点

list(b'U\x00\t\x12\x01\x14\xcf\x00\xaa')
#[85, 0, 9, 18, 1, 20, 207, 0, 170]

这里可以看到我们实际读取的是长度为9的一个数组,我们思考下,我们操作机器人,应该也是写入长度为9的数组就可以控制机器人的各种状态了,事实证明,想法是正确的。

4.4、serial参数设置

一些常见的重要参数如下:

port:串口的设备名或接口(None)
baudrate:波特率,50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 500000, 576000, 921600, 1000000, 1152000, 1500000, 2000000, 2500000, 3000000, 3500000, 4000000
bytesize:数据位,每个字节的位数,一般是7或8,默认是8
parity:校验位,PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE
stopbits:停止位,1位停止位、1.5位停止位、2位停止位(STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO)
xonxoff:软件流控(True, False)
rtscts:硬件(RTS/CTS)流控(True, False)
dsr/dtr:硬件(DSR/DTR)流控( True, False)
timeout:读超时,推荐指定,避免堵塞
writeTimeout:写超时

其中波特率需要注意以下三点

传输速度:波特率越高,传输速度越快,但也会增加传输错误的可能性。
传输距离:波特率越高,传输距离越短,因为高速传输会导致信号衰减。
硬件支持:波特率需要与硬件设备匹配,如果设备不支持高速传输,则无法使用高波特率。

5、机器狗

有了上面的知识铺垫,我们来实际操控机器狗,先让机器狗往前跑动起来。源码有比较多的功能,这里我只针对机器狗的向前跑动,,将代码精简出来,让我们对这个串口的通信有一个更加清晰的了解

5.1、代码

gedit MYDOG.py

import serial

def conver2u8(data, limit):
    return int(128 + 128 * data / limit)

class DOG():
    def __init__(self,port="/dev/ttyAMA0"):
        self.ser = serial.Serial(port, 115200, timeout=0.5)
    #往前跑动
    def moveX(self,step):
        u8v = conver2u8(step, 25)
        mode = 0x01
        order = 0x30
        value = []
        value_sum = 0
        value.append(u8v)
        value_sum = value_sum + u8v
        sum_data = ((1 + 0x08) + mode + order + value_sum) % 256
        sum_data = 255 - sum_data
        tx = [0x55, 0x00, (1 + 0x08), mode, order]
        tx.extend(value)
        tx.extend([sum_data, 0x00, 0xAA])
        print(tx)
        self.ser.write(tx)
    # 停止
    def stop(self):
        self.moveX(0)

测试下结果:

import MYDOG
mydog = MYDOG.DOG()
mydog.moveX(5)

这样机器狗就会往前跑动了,step是设置机器狗的步伐宽度,可以看到初始化接口的设备和波特率以及推荐加上超时时间,我们就通过write()方法进行写入即可。mydog.stop()也就是发送的步伐宽度为0就可以让机器狗停止了。

5.2、ser.write写入

我们可以打印tx变量的值,这样就可以查看写入了一些什么内容:
跑动时

[85, 0, 9, 1, 48, 153, 44, 0, 170]
b'U\x00\t\x01\x30\x99\x2c\x00\xaa'

停止时

[85, 0, 9, 1, 48, 128, 69, 0, 170]
b'U\x00\t\x01\x30\x80\x45\x00\xaa'

那么从上面分析来看,我们只需要写入一串列表数值即可,所以精简为如下三条命令就可以让机器狗跑动起来。
精简代码如下:

import serial
ser=serial.Serial("/dev/ttyAMA0", 115200, timeout=0.5)
ser.write([85, 0, 9, 1, 48, 153, 44, 0, 170])

当然这里的十进制也可以使用十六进制的形式代替:

ser.write([0x55, 0x0, 0x9, 0x1, 0x30, 0x99, 0x2c, 0x0, 0xaa])

5.3、ser.readline读取

再次来查看下接收到的信息,这里将超时时间调大点不然获取不到数据:

import serial
ser=serial.Serial("/dev/ttyAMA0", 115200, timeout=5)
ser.readline()

跑动时:b'U\x00\t\x01\x30\x99\x2c\x00\xaa'
停止时:b'U\x00\t\x12\x01d\x7f\x00\xaa'

到这里就完成了,串口通讯的读写操作,这里的值是字节数组、十进制数组、十六进制数组等都是等价的,也就是说我们写入这样的字节数组ser.write(b'U\x00\t\x01\x30\x99\x2c\x00\xaa')同样也是可以让机器狗跑起来,效果是一样的。

6、完整代码与功能 

上述是通过写入串口数据来让机器狗跑动起来,熟悉串口库的使用,这里贴出完整代码,里面常见的操作机器狗的功能都有,代码如下:

import serial
import struct
import time

__version__ = '2.0.7'
__last_modified__ = '2022/11/18'

"""
ORDER 用来存放命令地址和对应数据
ORDER is used to store the command address and corresponding data
"""
ORDER = {
    "BATTERY": [0x01, 100],
    "PERFORM": [0x03, 0],
    "CALIBRATION": [0x04, 0],
    "UPGRADE": [0x05, 0],
    "MOVE_TEST": [0x06, 1],
    "FIRMWARE_VERSION": [0x07],
    "GAIT_TYPE": [0x09, 0x00],
    "BT_NAME": [0x13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    "UNLOAD_MOTOR": [0x20, 0],
    "LOAD_MOTOR": [0x20, 0],
    "VX": [0x30, 128],
    "VY": [0x31, 128],
    "VYAW": [0x32, 128],
    "TRANSLATION": [0x33, 0, 0, 0],
    "ATTITUDE": [0x36, 0, 0, 0],
    "PERIODIC_ROT": [0x39, 0, 0, 0],
    "MarkTime": [0x3C, 0],
    "MOVE_MODE": [0x3D, 0],
    "ACTION": [0x3E, 0],
    "PERIODIC_TRAN": [0x80, 0, 0, 0],
    "MOTOR_ANGLE": [0x50, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128],
    "MOTOR_SPEED": [0x5C, 1],
    "LEG_POS": [0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    "IMU": [0x61, 0],
    "ROLL": [0x62, 0],
    "PITCH": [0x63, 0],
    "YAW": [0x64, 0]
}

"""
PARAM 用来存放机器狗的参数限制范围
PARAM is used to store the parameter limit range of the robot dog
"""

PARAM = {
    "TRANSLATION_LIMIT": [35, 18, [75, 115]], # X Y Z 平移范围 Scope of translation
    "ATTITUDE_LIMIT": [20, 15, 11],           # Roll Pitch Yaw 姿态范围 Scope of posture
    "LEG_LIMIT": [35, 18, [75, 115]],         # 腿长范围 Scope of the leg
    "MOTOR_LIMIT": [[-73, 57], [-66, 93], [-31, 31]], # 下 中 上 舵机范围 Lower, middle and upper steering gear range
    "PERIOD_LIMIT": [[1.5, 8]],
    "MARK_TIME_LIMIT": [10, 35],  # 原地踏步高度范围 Stationary height range
    "VX_LIMIT": 25,    # X速度范围 X velocity range
    "VY_LIMIT": 18,    # Y速度范围 Y velocity range
    "VYAW_LIMIT": 100  # 旋转速度范围 Rotation speed range
}


def search(data, list):
    for i in range(len(list)):
        if data == list[i]:
            return i + 1
    return -1


def conver2u8(data, limit, mode=0):
    """
    将实际参数转化为0到255的单字节数据
    Convert the actual parameters to single byte data from 0 to 255
    """
    max = 0xff
    if mode == 0:
        min = 0x00
    else:
        min = 0x01

    if not isinstance(limit, list):
        if data >= limit:
            return max
        elif data <= -limit:
            return min
        else:
            return int(128 + 128 * data / limit)
    else:
        limitmin = limit[0]
        limitmax = limit[1]
        if data >= limitmax:
            return max
        elif data <= limitmin:
            return min
        else:
            return int(255 / (limitmax - limitmin) * (data - limitmin))


def conver2float(data, limit):
    if not isinstance(limit, list):
        return (data - 128.0) / 255.0 * limit
    else:
        limitmin = limit[0]
        limitmax = limit[1]
        return data / 255.0 * (limitmax - limitmin) + limitmin


def Byte2Float(rawdata):
    a = bytearray()
    a.append(rawdata[3])
    a.append(rawdata[2])
    a.append(rawdata[1])
    a.append(rawdata[0])
    return struct.unpack("!f", a)[0]


class DOGZILLA():
    """
    在实例化DOGZILLA时需要指定上位机与机器狗的串口通讯接口
    When instantiating DOGZILLA, you need to specify the serial
    communication interface between the upper computer and the machine dog
    """

    def __init__(self, port="/dev/ttyAMA0"):
        self.ser = serial.Serial(port, 115200, timeout=0.5)
        self.rx_FLAG = 0
        self.rx_COUNT = 0
        self.rx_ADDR = 0
        self.rx_LEN = 0
        self.rx_data = bytearray(50)
        self.__delay = 0.05
        pass

    def __send(self, key, index=1, len=1):
        mode = 0x01
        order = ORDER[key][0] + index - 1
        value = []
        value_sum = 0
        for i in range(0, len):
            value.append(ORDER[key][index + i])
            value_sum = value_sum + ORDER[key][index + i]
        sum_data = ((len + 0x08) + mode + order + value_sum) % 256
        sum_data = 255 - sum_data
        tx = [0x55, 0x00, (len + 0x08), mode, order]
        tx.extend(value)
        tx.extend([sum_data, 0x00, 0xAA])
        self.ser.write(tx)

    def __read(self, addr, read_len=1):
        mode = 0x02
        sum_data = (0x09 + mode + addr + read_len) % 256
        sum_data = 255 - sum_data
        tx = [0x55, 0x00, 0x09, mode, addr, read_len, sum_data, 0x00, 0xAA]
        # time.sleep(0.1)
        self.ser.flushInput()
        self.ser.write(tx)

    def stop(self):
        self.move_x(0)
        self.move_y(0)
        self.mark_time(0)
        self.turn(0)

    def move(self, direction, step):
        if direction in ['x', 'X']:
            self.move_x(step)
        elif direction in ['y', 'Y']:
            self.move_y(step)
        else:
            print("ERROR!Invalid direction!")

    def move_x(self, step):
        if step > 20:
            step = 20
        elif step < -20:
            step = -20
        ORDER["VX"][1] = conver2u8(step, PARAM["VX_LIMIT"])
        self.__send("VX")

    def move_y(self, step):
        if step > 18:
            step = 18
        elif step < -18:
            step = -18
        ORDER["VY"][1] = conver2u8(step, PARAM["VY_LIMIT"])
        self.__send("VY")

    def turn(self, step):
        if step > 70:
            step = 70
        elif step < -70:
            step = -70
        elif 0 < step < 30:
            step = 30
        elif -30 < step < 0:
            step = -30
        ORDER["VYAW"][1] = conver2u8(step, PARAM["VYAW_LIMIT"])
        self.__send("VYAW")

    def forward(self, step):
        self.move_x(abs(step))

    def back(self, step):
        self.move_x(-abs(step))

    def left(self, step):
        self.move_y(abs(step))

    def right(self, step):
        self.move_y(-abs(step))

    def turnleft(self, step):
        self.turn(abs(step))

    def turnright(self, step):
        self.turn(-abs(step))

    def __translation(self, direction, data):
        index = search(direction, ['x', 'y', 'z'])
        if index == -1:
            print("ERROR!Direction must be 'x', 'y' or 'z'")
            return
        ORDER["TRANSLATION"][index] = conver2u8(data, PARAM["TRANSLATION_LIMIT"][index - 1])
        self.__send("TRANSLATION", index)

    def translation(self, direction, data):
        """
        使机器狗足端不动,身体进行三轴平动
        Keep the robot's feet stationary and the body makes three-axis translation
        """
        if (isinstance(direction, list)):
            if (len(direction) != len(data)):
                print("ERROR!The length of direction and data don't match!")
                return
            for i in range(len(data)):
                self.__translation(direction[i], data[i])
        else:
            self.__translation(direction, data)

    def __attitude(self, direction, data):
        index = search(direction, ['r', 'p', 'y'])
        if index == -1:
            print("ERROR!Direction must be 'r', 'p' or 'y'")
            return
        ORDER["ATTITUDE"][index] = conver2u8(data, PARAM["ATTITUDE_LIMIT"][index - 1])
        self.__send("ATTITUDE", index)

    def attitude(self, direction, data):
        """
        使机器狗足端不动,身体进行三轴转动
        Keep the robot's feet stationary and the body makes three-axis rotation
        """
        if (isinstance(direction, list)):
            if (len(direction) != len(data)):
                print("ERROR!The length of direction and data don't match!")
                return
            for i in range(len(data)):
                self.__attitude(direction[i], data[i])
        else:
            self.__attitude(direction, data)

    def action(self, action_id):
        """
        使机器狗狗指定的预设动作
        Make the robot do the specified preset action
        """
        if action_id <= 0 or action_id > 255:
            print("ERROR!Illegal Action ID!")
            return
        ORDER["ACTION"][1] = action_id
        self.__send("ACTION")

    def reset(self):
        """
        机器狗停止运动,所有参数恢复到初始状态
        The robot dog stops moving and all parameters return to the initial state
        """
        self.action(255)
        time.sleep(0.2)

    def leg(self, leg_id, data):
        """
        控制机器狗的单腿的三轴移动
        Control the three-axis movement of a single leg of the robot
        """
        value = [0, 0, 0]
        if leg_id not in [1, 2, 3, 4]:
            print("Error!Illegal Index!")
            return
        if len(data) != 3:
            message = "Error!Illegal Value!"
            return
        for i in range(3):
            try:
                value[i] = conver2u8(data[i], PARAM["LEG_LIMIT"][i])
            except:
                print("Error!Illegal Value!")
        for i in range(3):
            index = 3 * (leg_id - 1) + i + 1
            ORDER["LEG_POS"][index] = value[i]
            self.__send("LEG_POS", index)

    def __motor(self, index, data):
        ORDER["MOTOR_ANGLE"][index] = conver2u8(data, PARAM["MOTOR_LIMIT"][index % 3 - 1])
        self.__send("MOTOR_ANGLE", index)

    def motor(self, motor_id, data):
        """
        控制机器狗单个舵机转动
        Control the rotation of a single steering gear of the robot
        """
        MOTOR_ID = [11, 12, 13, 21, 22, 23, 31, 32, 33, 41, 42, 43]
        if isinstance(motor_id, list):
            if len(motor_id) != len(data):
                print("Error!Length Mismatching!")
                return
            index = []
            for i in range(len(motor_id)):
                temp_index = search(motor_id[i], MOTOR_ID)
                if temp_index == -1:
                    print("Error!Illegal Index!")
                    return
                index.append(temp_index)
            for i in range(len(index)):
                self.__motor(index[i], data[i])
        else:
            index = search(motor_id, MOTOR_ID)
            self.__motor(index, data)

    def unload_motor(self, leg_id):
        if leg_id not in [1, 2, 3, 4]:
            print('ERROR!leg_id must be 1, 2, 3 or 4')
            return
        ORDER["UNLOAD_MOTOR"][1] = 0x10 + leg_id
        self.__send("UNLOAD_MOTOR")

    def unload_allmotor(self):
        ORDER["UNLOAD_MOTOR"][1] = 0x01
        self.__send("UNLOAD_MOTOR")

    def load_motor(self, leg_id):
        if leg_id not in [1, 2, 3, 4]:
            print('ERROR!leg_id must be 1, 2, 3 or 4')
            return
        ORDER["LOAD_MOTOR"][1] = 0x20 + leg_id
        self.__send("LOAD_MOTOR")

    def load_allmotor(self):
        ORDER["LOAD_MOTOR"][1] = 0x00
        self.__send("LOAD_MOTOR")

    def __periodic_rot(self, direction, period):
        index = search(direction, ['r', 'p', 'y'])
        if index == -1:
            print("ERROR!Direction must be 'r', 'p' or 'y'")
            return
        if period == 0:
            ORDER["PERIODIC_ROT"][index] = 0
        else:
            ORDER["PERIODIC_ROT"][index] = conver2u8(period, PARAM["PERIOD_LIMIT"][0], mode=1)
        self.__send("PERIODIC_ROT", index)

    def periodic_rot(self, direction, period):
        """
        使机器狗周期性转动
        Make the robot rotate periodically
        """
        if (isinstance(direction, list)):
            if (len(direction) != len(period)):
                print("ERROR!The length of direction and data don't match!")
                return
            for i in range(len(period)):
                self.__periodic_rot(direction[i], period[i])
        else:
            self.__periodic_rot(direction, period)

    def __periodic_tran(self, direction, period):
        index = search(direction, ['x', 'y', 'z'])
        if index == -1:
            print("ERROR!Direction must be 'x', 'y' or 'z'")
            return
        if period == 0:
            ORDER["PERIODIC_TRAN"][index] = 0
        else:
            ORDER["PERIODIC_TRAN"][index] = conver2u8(period, PARAM["PERIOD_LIMIT"][0], mode=1)
        self.__send("PERIODIC_TRAN", index)

    def periodic_tran(self, direction, period):
        """
        使机器狗周期性平动
        Make the robot translate periodically
        """
        if (isinstance(direction, list)):
            if (len(direction) != len(period)):
                print("ERROR!The length of direction and data don't match!")
                return
            for i in range(len(period)):
                self.__periodic_tran(direction[i], period[i])
        else:
            self.__periodic_tran(direction, period)

    def mark_time(self, data):
        """
        使机器狗原地踏步
        Make the robot marks time
        """
        if data == 0:
            ORDER["MarkTime"][1] = 0
        else:
            ORDER["MarkTime"][1] = conver2u8(data, PARAM["MARK_TIME_LIMIT"], mode=1)
        self.__send("MarkTime")

    def pace(self, mode):
        """
        改变机器狗的踏步频率
        Change the step frequency of the robot
        """
        if mode == "normal":
            value = 0x00
        elif mode == "slow":
            value = 0x01
        elif mode == "high":
            value = 0x02
        else:
            print("ERROR!Illegal Value!")
            return
        ORDER["MOVE_MODE"][1] = value
        self.__send("MOVE_MODE")

    def gait_type(self, mode):
        """
        改变机器狗的步态
        Change the gait of the robot
        """
        if mode == "trot":
            value = 0x00
        elif mode == "walk":
            value = 0x01
        elif mode == "high_walk":
            value = 0x02
        ORDER["GAIT_TYPE"][1] = value
        self.__send("GAIT_TYPE")

    def imu(self, mode):
        """
        开启/关闭机器狗自稳状态
        Turn on / off the self stable state of the robot dog
        """
        if mode != 0 and mode != 1:
            print("ERROR!Illegal Value!")
            return
        ORDER["IMU"][1] = mode
        self.__send("IMU")

    def perform(self, mode):
        """
        开启/关闭机器狗循环做动作状态
        Turn on / off the action status of the robot dog cycle
        """
        if mode != 0 and mode != 1:
            print("ERROR!Illegal Value!")
            return
        ORDER["PERFORM"][1] = mode
        self.__send("PERFORM")

    def motor_speed(self, speed):
        """
        调节舵机转动速度,只在单独控制舵机的情况下有效
        Adjust the steering gear rotation speed,
        only effective when control the steering gear separately
        """
        if speed < 0 or speed > 255:
            print("ERROR!Illegal Value!The speed parameter needs to be between 0 and 255!")
            return
        if speed == 0:
            speed = 1
        ORDER["MOTOR_SPEED"][1] = speed
        self.__send("MOTOR_SPEED")

    def read_motor(self, out_int=False):
        """
        读取12个舵机的角度 Read the angles of the 12 steering gear
        """
        self.__read(ORDER["MOTOR_ANGLE"][0], 12)
        time.sleep(self.__delay)
        angle = []
        if self.__unpack():
            for i in range(12):
                index = round(conver2float(self.rx_data[i], PARAM["MOTOR_LIMIT"][i % 3]), 2)
                if out_int:
                    if index > 0:
                        angle.append(int(index+0.5))
                    elif index < 0:
                        angle.append(int(index-0.5))
                    else:
                        angle.append(int(index))
                else:
                    angle.append(index)
        return angle

    def read_battery(self):
        self.__read(ORDER["BATTERY"][0], 1)
        time.sleep(self.__delay)
        battery = 0
        if self.__unpack():
            battery = int(self.rx_data[0])
        return battery

    def read_version(self):
        self.__read(ORDER["FIRMWARE_VERSION"][0], 10)
        time.sleep(self.__delay)
        firmware_version = 'Null'
        if self.__unpack():
            # data = self.rx_data[0:10]
            data = self.rx_data[2:10]
            firmware_version = data.decode("utf-8").strip('\0')
        return firmware_version

    def read_roll(self, out_int=False):
        self.__read(ORDER["ROLL"][0], 4)
        time.sleep(self.__delay)
        roll = 0
        if self.__unpack():
            roll = Byte2Float(self.rx_data)
        if out_int:
            tmp = int(roll)
            return tmp
        return round(roll, 2)

    def read_pitch(self, out_int=False):
        self.__read(ORDER["PITCH"][0], 4)
        time.sleep(self.__delay)
        pitch = 0
        if self.__unpack():
            pitch = Byte2Float(self.rx_data)
        if out_int:
            tmp = int(pitch)
            return tmp
        return round(pitch, 2)

    def read_yaw(self, out_int=False):
        self.__read(ORDER["YAW"][0], 4)
        time.sleep(self.__delay)
        yaw = 0
        if self.__unpack():
            yaw = Byte2Float(self.rx_data)
        if out_int:
            tmp = int(yaw)
            return tmp
        return round(yaw, 2)

    def __unpack(self):
        n = self.ser.inWaiting()
        rx_CHECK = 0
        if n:
            data = self.ser.read(n)
            for num in data:
                if self.rx_FLAG == 0:
                    if num == 0x55:
                        self.rx_FLAG = 1
                    else:
                        self.rx_FLAG = 0

                elif self.rx_FLAG == 1:
                    if num == 0x00:
                        self.rx_FLAG = 2
                    else:
                        self.rx_FLAG = 0

                elif self.rx_FLAG == 2:
                    self.rx_LEN = num
                    self.rx_FLAG = 3

                elif self.rx_FLAG == 3:
                    self.rx_TYPE = num
                    self.rx_FLAG = 4

                elif self.rx_FLAG == 4:
                    self.rx_ADDR = num
                    self.rx_FLAG = 5

                elif self.rx_FLAG == 5:
                    if self.rx_COUNT == (self.rx_LEN - 9):
                        self.rx_data[self.rx_COUNT] = num
                        self.rx_COUNT = 0
                        self.rx_FLAG = 6
                    elif self.rx_COUNT < self.rx_LEN - 9:
                        self.rx_data[self.rx_COUNT] = num
                        self.rx_COUNT = self.rx_COUNT + 1

                elif self.rx_FLAG == 6:
                    for i in self.rx_data[0:(self.rx_LEN - 8)]:
                        rx_CHECK = rx_CHECK + i
                    rx_CHECK = 255 - (self.rx_LEN + self.rx_TYPE + self.rx_ADDR + rx_CHECK) % 256
                    if num == rx_CHECK:
                        self.rx_FLAG = 7
                    else:
                        self.rx_FLAG = 0
                        self.rx_COUNT = 0
                        self.rx_ADDR = 0
                        self.rx_LEN = 0

                elif self.rx_FLAG == 7:
                    if num == 0x00:
                        self.rx_FLAG = 8
                    else:
                        self.rx_FLAG = 0
                        self.rx_COUNT = 0
                        self.rx_ADDR = 0
                        self.rx_LEN = 0

                elif self.rx_FLAG == 8:
                    if num == 0xAA:
                        self.rx_FLAG = 0
                        self.rx_COUNT = 0
                        return True
                    else:
                        self.rx_FLAG = 0
                        self.rx_COUNT = 0
                        self.rx_ADDR = 0
                        self.rx_LEN = 0
        return False

    def calibration(self, state):
        """
        用于软件标定,请谨慎使用!!! For software calibration, please use with caution!!!
        """
        if state:
            ORDER["CALIBRATION"][1] = 1
        else:
            ORDER["CALIBRATION"][1] = 0
        self.__send("CALIBRATION")


if __name__ == '__main__':
    g_dog = DOGZILLA()
    version = g_dog.read_version()
    print("version:", version)

其余的一些功能也都解释如下,先导入DOGZILLA:

from DOGZILLALib import DOGZILLA
mydog = DOGZILLA()

左转弯:mydog.turn(50)
右转弯:mydog.turn(-50)
这个是偏航角VYAW速度的设置

左平移:mydog.left(10)
右平移:mydog.right(10)
这个是Y轴的VY的速度设置

机器狗足端不动,沿着三轴分别平移:mydog.translation(['x','y','z'],[-30,-10,100])
这里的机器狗将分别,往后-30,往右-10,往上100这样的运动

机器狗足端不动,沿着三轴分别转动:mydog.attitude(['r','p','y'],[30,60,90])

这里的RPY分别是ROLL横滚角、PITCH俯仰角、YAW偏航角,对这部分感兴趣的可以查阅:
欧拉角(横滚角、俯仰角、偏航角)、旋转矩阵、四元数的转换与解决万向节死锁

预设的动作:mydog.action,比如撒尿动作:mydog.action(11)

停止运动,所有参数恢复到初始状态:mydog.reset()

单腿运动:mydog.leg(2,[-40,-30,40]),这里是第二条腿(前面右边),沿着X轴后退40,y轴往右30,Z轴往下40

单个舵机转动:mydog.motor(22,60),这里是第二条的髋关节舵机,控制大腿的编号

周期性转动:mydog.periodic_rot(['p','y'],[20,30]),RPY,这里是选P,Y角做转动,也就是俯仰角和偏航角

周期性平动:mydog.periodic_tran(['x','y'],[20,30]),这里是沿着X,Y轴的平移

原地踏步:mydog.mark_time(1)
踏步频率:mydog.pace('slow') # 'normal'、'slow'、'high'

机器狗步态:mydog.gait_type('walk') # 'trot'、'walk'、'high_walk'

开启/关闭机器狗自稳状态:mydog.imu(0)

开启/关闭机器狗循环做动作状态:mydog.perform(1)

舵机转速(只在单独控制舵机的情况下有效):mydog.motor_speed(10)

读取12个舵机的角度:mydog.read_motor()

#[15.2, 40.62, 0.61, 14.18, 41.25, 0.85, 14.69, 51.22, 0.61, 15.2, 50.6, 0.36]

这里会随着机器狗的运动,数据会实时变化。

分别读取机器狗的横滚角、俯仰角、偏航角,同样的舵机的变化,也将导致欧拉角的实时变化。

mydog.read_roll() # 0.37
mydog.read_pitch() # 0.17
mydog.read_yaw() # -21.3

电池电量:mydog.read_battery() # 81
固件版本:mydog.read_version() # '3.1.7-Y'

到这里就结束了,最后总结下,主要是通过机器狗来熟悉这个pyserial串口通讯库,整个代码看下来是比较简洁和高效,只需要连接到指定的串口接口,以及指定波特率与超时时间,就能够轻松建立起通讯连接,然后根据地址写入对应的数据就可以完成对机器狗的操作了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/213064.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

初识Java 18-6 泛型

目录 潜在类型机制 支持潜在类型机制的语言 Python的潜在类型机制 C的潜在类型机制 Java中的直接潜在类型机制 潜在类型机制的替代方案 反射 将方法应用于序列中的每个元素 Java 8的潜在类型机制&#xff08;间接实现&#xff09; 潜在类型机制的使用例&#xff08;S…

条款2:不要滥用宏

文章目录 优先选择编译器而不是预编译器两种特殊情况使用宏替代函数调用总结 优先选择编译器而不是预编译器 假设我们预定义了一个宏#define ASPECT_RATIO 1.653&#xff0c;当我们的程序在这个地方出现错误的时候。可能会出现以下的问题&#xff1a; 符号名称ASPECT_RATIO可能…

MQTT客户端、代理(broker)和连接建立

在前篇文章&#xff08;http://t.csdnimg.cn/IamPz&#xff09;中&#xff0c;介绍了发布/订阅架构和MQTT如何据此交换信息&#xff0c;其中的关键概念是&#xff1a; 发布/订阅架构触耦了负责发布信息的客户端&#xff08;发布者&#xff09;和负责接收信息的客户端&#xff…

C语言-联合和枚举

------------------------------------ --------------- ------ 最慢的步伐不是跬步&#xff0c;而是徘徊&#xff1b; 最快的脚步不是冲刺&#xff0c;而是坚持。 今天来到我们的联合和枚举类型的讲解&#xff1a; 目录 联合体类型 联合体类型的声明 联合体类型的特点 …

Wireshark抓包分析RTMP协议时,出现Unknown问题

进行rtmp推流时&#xff0c;使用wireshark抓包&#xff0c;发现部分包显示Unknown 解决方法&#xff1a; 编辑 -> 首选项 -> Protocols -> RTMPT&#xff0c;这里Maximum packet size默认是32768 将该值调大&#xff0c;比如调成1048576&#xff0c;即可解决该问题。…

ChatGPT 的 18 种玩法,你还不会用吗?

你确定&#xff0c;你会使用 ChatGPT 了吗&#xff1f; 今天给大家整理了 18 种 ChatGPT 的用法&#xff0c;看看有哪些方法是你能得上的。 用之前我们可以打开R5Ai平台&#xff0c;可以免费使用目前所有的大模型 地址&#xff1a;R5Ai.com 语法更正 用途&#xff1a;文章…

改进LiteOS中物理内存分配算法(详细实验步骤+相关源码解读)

一、实验要求 优化TLSF算法&#xff0c;将Best-fit策略优化为Good-fit策略&#xff0c;进一步降低时间复杂度至O(1)。 优化思路&#xff1a; 1.初始化时预先为每个索引中的内存块挂上若干空闲块&#xff0c;在实际分配时避免分割&#xff08;split&#xff09;操作&#xff…

[原创]C++98升级到C++20的复习旅途-从汇编及逆向角度去分析“constexpr“关键字

[简介] 常用网名: 猪头三 出生日期: 1981.XX.XXQQ: 643439947 个人网站: 80x86汇编小站 https://www.x86asm.org 编程生涯: 2001年~至今[共22年] 职业生涯: 20年 开发语言: C/C、80x86ASM、PHP、Perl、Objective-C、Object Pascal、C#、Python 开发工具: Visual Studio、Delphi…

AtCoder Beginner Contest 331 题解 A-E

目录 A - TomorrowB - Buy One Carton of MilkC - Sum of Numbers Greater Than MeD - Tile PatternE - Set Meal A - Tomorrow 原题链接 题目描述 已知一年有M个月D天&#xff0c;求出第y年m月d天的后一天是哪一天。 思路&#xff1a;分类讨论 分别讨论m和d的是否是最后一个月…

基于SpringBoot的旅游信息网【源码好优多】

简介 旅游信息网是一款介绍旅游相关内容的网站&#xff0c;分为前台和后台部分&#xff0c;其中前台用户注册以后可以浏览景点、景点详情、预订景点、酒店、车票、保险、以及浏览旅游攻略、个人信息修改、在线留言等&#xff0c;管理员在后台对景点、攻略、订单信息、酒店信息、…

oj赛氪练习题

数组调整 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scanner new Scanner(System.in);int n scanner.nextInt();int k scanner.nextInt();int[] arr new int[n];for (int i 0; i < n; i) {arr[i] scanner.nextIn…

java源码-类与对象

1、面向对象与面向过程 在了解类和对象之前我们先了解一下什么是面向过程和面向对象。 1&#xff09;面向过程编程&#xff1a; C语言就是面向过程编程的&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&#xff0c;通过函数调用逐步解决问题。 2&#xff09;面向对…

Redis 发布订阅机制深入探索

Redis 的发布订阅&#xff08;pub/sub&#xff09;机制是一种消息传递模式&#xff0c;允许消息的发送者&#xff08;发布者&#xff09;和消息的接收者&#xff08;订阅者&#xff09;通过一个中介层&#xff08;频道&#xff09;进行通信&#xff0c;而无需彼此直接交互。以下…

半导体工艺发展概述

集成电路发展到今天&#xff0c;经历从1940年的PN结发现&#xff0c;到1950年BJT三极管发明&#xff0c;再到1963年CMOS电路发明。从单纯基于Si的半导体电路&#xff0c;再到GaAs, GaN&#xff0c;SiGe, InP等化合物半导体集成电路。不断的通过化学材料配比&#xff0c;基本单元…

TinyVue 组件库助力赛意信息获得工业软件种子奖

首先恭喜广州赛意信息科技股份有限公司荣获工业软件种子奖&#xff01;在本次大赛中&#xff0c;凭借“数据驱动智造&#xff0c;基于 iDME 的赛意新一代 SMOM 赋能电子行业制造运营管理解决方案”这一作品脱颖而出~ 大赛简介 10月30日至10月31日&#xff0c;由广东省工业和信…

圆通速递查询入口,以表格的形式导出单号的每一条物流信息

批量查询圆通速递单号的物流信息&#xff0c;以表格的形式导出单号的每一条物流信息。 所需工具&#xff1a; 一个【快递批量查询高手】软件 圆通速递单号若干 操作步骤&#xff1a; 步骤1&#xff1a;运行【快递批量查询高手】软件&#xff0c;并登录 步骤2&#xff1a;点击…

Hadoop——分布式计算MapReduce和资源调度Yarn

分布式计算 MapReduce YARN架构 YARN集群部署 一、Hadoop安装目录下/etc/hadoop修改mapred-env配置文件&#xff0c;mapred-site.xml文件 二、etc/hadoop文件内&#xff0c;修改yarn-env.sh&#xff0c;yarn-site.xml 三、将配置好的文件分发到其他服务节点 start-dfs.…

SLAM ORB-SLAM2(10)轨迹跟踪过程

SLAM ORB-SLAM2(10)轨迹跟踪过程 1. 总体过程2. ORB 特征点提取2.1. 相机数据处理2.1.1. 单目相机图像处理2.1.2. 双目相机图像处理2.1.3. RGBD相机图像处理2.2. ORB 特征点3. 地图初始化3.1. 坐标形式3.2. 坐标原点3.3. 地图尺度4. 相机位姿初始估计4.1. 关键帧4.2. 运动模型…

文件搜索神器—Everything,结合内网穿透秒变在线搜索神器!

Everythingcpolar搭建在线资料库&#xff0c;实现随时随地访问 文章目录 Everythingcpolar搭建在线资料库&#xff0c;实现随时随地访问前言1.软件安装完成后&#xff0c;打开Everything2.登录cpolar官网 设置空白数据隧道3.将空白数据隧道与本地Everything软件结合起来总结 前…

【每日一题】1423. 可获得的最大点数-2023.12.3

题目&#xff1a; 1423. 可获得的最大点数 几张卡牌 排成一行&#xff0c;每张卡牌都有一个对应的点数。点数由整数数组 cardPoints 给出。 每次行动&#xff0c;你可以从行的开头或者末尾拿一张卡牌&#xff0c;最终你必须正好拿 k 张卡牌。 你的点数就是你拿到手中的所有…