Raspberry Pi 4B robot dog's serial communication driver library pyserial practice

pyserial is a serial communication driver library that can be installed on commonly used Windows, Linux, MacOS, etc. Here I am using Raspberry Pi 4B for testing. This board is still very powerful. We can use the pyserial library to operate based on this A device like a robot dog on a board.

1. Four-legged robot dog

My own is a four-legged robot dog. Each leg has three servos, so it has 3 degrees of freedom. The four legs have a total of 12 servos, which is 12 degrees of freedom. Each servo controls the corresponding joint, such as the root joint. Control the base joint, the hip joint controls the thigh, and the knee joint controls the calf, as shown below:

In the physical robot, the order of the joint matrices inside is: the first group is on the left side in front, the second group is on the right side, the third group is on the right side in the back, and the fourth group is in the left side in the back.

2. OS environment

Before installation, we usually familiarize ourselves with the local environment first, so that it is convenient to download and install the corresponding version.

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

Check the chip type: 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

It can be seen that it is a 64-bit architecture and a new ARMv8 architecture, and it also uses the new A64 instruction set. raspi is the alias of Raspberry Pi . This is the environment of my Raspberry Pi in the Ubuntu 20 version.

3. Install pyserial

Installation command: pip3 install pyserial
After installation, let’s test whether it is successful. The installation package is pyserial and the imported module is 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. Serial communication interface

4.1. list_ports interface list

First we list what interfaces are available

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

This '/dev/ttyAMA0' is the communication interface between Raspberry Pi 4B and the host computer. After getting the interface name, we can open this interface for communication.

4.2. Open the interface

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

It is recommended to specify the timeout period here to avoid the program waiting forever because the data cannot be read. 

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)

Here, the serial device name and port refer to the same thing: ser.name and ser.port 

Check what methods and properties it contains: 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. Byte array

Open the serial port interface and let's try to read a line: b_data=ser.readline()

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

Converting it to a list is in decimal form, which seems more intuitive.

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

Here you can see that what we actually read is an array of length 9. Let’s think about it. When we operate the robot, we should also write an array of length 9 to control the various states of the robot. Facts have proved that the idea is correct. of.

4.4. Serial parameter setting

Some common important parameters are as follows:

port : Device name or interface of the serial port (None)
baudrate : Baud rate, 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 : data bits, each byte Number of digits, usually 7 or 8, default is 8
parity : Check bits, PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE
stopbits : stop bits, 1 stop bit, 1.5 stop bit, 2 stop bits (STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO)
xonxoff : software flow control (True, False)
rtscts : Hardware (RTS/CTS) flow control (True, False)
dsr/dtr : Hardware (DSR/DTR) flow control (True, False)
timeout : Read timeout, recommended to be specified to avoid blocking
writeTimeout : Write timeout

The baud rate needs to pay attention to the following three points:

Transmission speed : The higher the baud rate, the faster the transmission speed, but it also increases the possibility of transmission errors.
Transmission distance : The higher the baud rate, the shorter the transmission distance, because high-speed transmission will cause signal attenuation.
Hardware support : The baud rate needs to match the hardware device. If the device does not support high-speed transmission, high baud rates cannot be used.

5. Robot dog

With the above knowledge, let's actually control the robot dog and let it run forward first. The source code has many functions. Here I only focus on the forward running of the robot dog. I will simplify the code so that we can have a clearer understanding of the communication of this serial port.

5.1. Code

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)

Test results:

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

In this way, the robot dog will run forward. Step is to set the pace width of the robot dog. You can see the device and baud rate of the initialization interface, as well as the recommended timeout period. We can write through the write() method. . mydog.stop() means that the sent step width is 0 to stop the robot dog.

5.2, ser.write writing

We can print the value of the tx variable so that we can see what is written:
when running

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

When stopped

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

From the above analysis, we only need to write a series of list values , so it can be simplified to the following three commands to make the robot dog run.
The simplified code is as follows:

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

Of course, the decimal here can also be replaced by hexadecimal:

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

5.3, ser.readline reading

Let’s check the received information again. Increase the timeout here, otherwise the data will not be obtained:

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

When running: b'U\x00\t\x01\x30\x99\x2c\x00\xaa'
When stopping: b'U\x00\t\x12\x01d\x7f\x00\xaa'

That’s it. The read and write operations of serial communication. The values ​​here are byte arrays, decimal arrays, hexadecimal arrays, etc. are all equivalent. That is to say, we write such a byte array ser.write (b'U\x00\t\x01\x30\x99\x2c\x00\xaa') You can also make the robot dog run, and the effect is the same.

6. Complete code and functions 

The above is to make the robot dog run by writing serial port data. If you are familiar with the use of the serial port library, the complete code is posted here. It has the common functions of operating the robot dog. The code is as follows:

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)

Some other functions are also explained as follows. First import DOGZILLA:

from DOGZILLALib import DOGZILLA
mydog = DOGZILLA()

Turn left: mydog.turn(50)
Turn right: mydog.turn(-50)
This is the setting of yaw angle VYAW speed

Left translation: mydog.left(10)
Right translation: mydog.right(10)
This is the VY speed setting of the Y axis

The robot dog's feet do not move and translate along the three axes: mydog.translation(['x','y','z'],[-30,-10,100])
The robot dog here will move forward - 30, -10 to the right, and 100 upwards.

The robot dog's feet do not move and rotate along three axes: mydog.attitude(['r','p','y'],[30,60,90])

The RPY here are ROLL roll angle, PITCH pitch angle, and YAW yaw angle. Those who are interested in this part can check out:
Euler angle (roll angle, pitch angle, yaw angle), rotation matrix, quaternion Conversion and Solving Universal Joint Deadlock

Default action: mydog.action , such as peeing action: mydog.action(11)

Stop movement and return all parameters to their initial state: mydog.reset()

Single-leg movement: mydog.leg(2,[-40,-30,40]) , here is the second leg (front right), 40 steps back along the X-axis, 30 steps to the right on the y-axis, and 40 steps down on the Z-axis

Single servo rotation: mydog.motor(22,60) , here is the second hip servo, controlling the number of the thigh

Periodic rotation: mydog.periodic_rot(['p','y'],[20,30]) , RPY, here the P and Y angles are selected for rotation, that is, the pitch angle and yaw angle

Periodic translation: mydog.periodic_tran(['x','y'],[20,30]) , here is the translation along the X, Y axis

Stand still: mydog.mark_time(1)
Step frequency: mydog.pace('slow') # 'normal', 'slow', 'high'

Robot dog gait: mydog.gait_type('walk') # 'trot', 'walk', 'high_walk'

Turn on/off the robot dog's self-stabilizing state: mydog.imu(0)

Turn on/off the robot dog to cycle through the action state: mydog.perform(1)

Servo speed (valid only when controlling the servo alone): mydog.motor_speed(10)

Read the angles of 12 servos: 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]

Here, the data will change in real time as the robot dog moves.

Read the roll angle, pitch angle, and yaw angle of the robot dog respectively. The same change of the steering gear will also cause real-time changes in the Euler angle.

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

Battery level: mydog.read_battery() # 81
Firmware version: mydog.read_version() # '3.1.7-Y'

It’s over here. To summarize, we mainly use the robot dog to get familiar with the pyserial serial port communication library. The entire code looks relatively simple and efficient. It only needs to connect to the specified serial port interface, and specify the baud rate and timeout time. , you can easily establish a communication connection, and then write the corresponding data according to the address to complete the operation of the robot dog.

Guess you like

Origin blog.csdn.net/weixin_41896770/article/details/134748636