Featured image of post 宇树A1电机折腾笔记

宇树A1电机折腾笔记

RM工程电控学习笔记

上图就是笨笨的宇树A1,这是我目前为止转过的最难转的电机。电机的说明书、SDK链接都来自MATH-286-Pro的视频提供:宇树A1相关资料宇树官方SDK仓库。这篇笔记分两部分,先使用SDK驱动电机,再迁移到STM32下位机上。

电脑SDK控制

变态的硬件接线

首先,驱动宇树A1需要的是RS-485格式的信号,这是一种类似CAN的差分信号,但和CAN不同的是并不采用总线方式,而是直接把TTL串口信号的方波复制一遍并镜像,因此一条线的串口信号就需要两条A/B线来传输,下图是我用示波器测出来的完美RS-485方波:

示波器

我们的大致思路是,电脑接一个USB转TTL模块,下游再接一个TTL转RS-485模块,下游再接电机,这是我这次选择的硬件(队里也只有这一套):

USB转TTL模块:FT232H

TTL转RS-485模块:MAX13488

由于宇树A1需要的波特率极高,4.8MBd,一般常用的CH340、CH341都不支持,FT232H是少有支持高波特率的USB转TTL/GPIO/I2C/SPI多功能芯片(最高支持12MBd),非常适合这一场景;MAX13488则最高支持16MBd,分别是下面两张图:

然后就是具体的接线:

FT232HMAX13488
AD0(TX)TX
AD1(RX)RX
5V5V
GNDGND

默认状态下,AD0就是FT232H的TX脚,AD1就是FT232H的RX脚。这里非常容易搞错的是,RX对RX,TX对TX,而不是交叉相接!可以这样理解,因为MAX13488并不是“对面的目标”,而是“我方的中转站”,接线正常情况下MAX13488上的TX灯会亮起,这个灯是红灯,红灯并不代表出错反而是成功。下面是宇树A1的接口线序:

上图方向插线的3pin口,从左至右分别是B、A、GND,左右两个3pin口线序完全一致,可以用于串联多个电机。直接B接B,A接A,GND接GND连到MAX13488另一端即可。

最早提到过,对于差分信号,一根线的数据应该需要一组A/B线才能传输,按照这样的逻辑,RX/TX应该需要两组A/B线,一共四条线,但我们看到MAX13488和电机侧只有一组A/B线。这是因为宇树A1仅支持半双工,而RX/TX TTL通信是全双工,MAX13488进行了转换,原先是发送接收可以同时进行,两条线互不干扰,但现在只有一条线(一组A/B只能表示一条线的信号),因此无法同时传输,这里MAX13488芯片自动处理了阻塞的逻辑。

环境配置

建议在Linux系统下使用SDK,更快捷方便。我在Ubuntu 22.04 LTS环境中测试通过,其他发行版可能出现各种版本问题无法编译(我使用的ArchLinux由于gcc太新就报错,其他发行版可以使用Distrobox不需要重装系统,这里不多赘述)。

1
2
3
4
5
6
7
git clone https://github.com/unitreerobotics/unitree_actuator_sdk
cd unitree_actuator_sdk
mkdir build
cd build
cmake ..
make
sudo ./changeID

七行命令就可以编译运行更改ID的程序。其中会要求你输入USB设备路径,一般情况下都是/dev/ttyUSB0,如果你不确认是不是这个设备,可以做一个简单的测试:

1
ls /dev | grep ttyUSB

运行这个命令,只有一个ttyUSB0,那就不用想了;如果有好几个,可以尝试拔掉FT232H再运行看看哪个设备消失了,消失的就是FT232H。changeID运行后,会输出:

1
2
3
4
5
6
[WARNING] SerialPort::recv, unblock version, wait time out
[WARNING] motor id=187 does not reply
Please turn the motor.
One time: id=0; Two times: id=1, Three times: id=2
ID can only be 0, 1, 2
Once finished, press 'a'

看到wait time outdoes not reply不需要慌张,因为接收电机信息的线路一直比较抽风,我们只要保证能给电机发送信息就能正常驱动电机,包括速控和位控。这里有一个用于检测电机是否接收到广播的奇技淫巧,就是将B、A、GND反接成GND、A、B重新上电,运行changeID,如果电机突然锁死,内部发出声音,再按a回车声音消失,就是接收到了信息。

如果一切正常,就可以开始设置ID,宇树A1的ID只有0、1、2。这里的ID设置方式极度硬核,人为外力把电机完整的转一圈再按a就是0,两圈就是1,三圈就是2,建议像本文第一张图那样加两个螺丝,这样外力更好拧(说明书也是这么建议的)。ID设置完,需要在example/example_a1_motor.cpp中修改serial()cmd.id,分别是USB设备路径和电机ID,再编译运行example_a1_motor,电机应该就能以默认参数速控开转。这里给一组位控的参数,n在这里是圈数:

1
2
3
4
5
cmd.kp    = 0.1;
cmd.kd    = 2;
cmd.q     = n*6.28*queryGearRatio(MotorType::A1);
cmd.dq    = -3.14*queryGearRatio(MotorType::A1);
cmd.tau   = 0.0;

下位机直接控制

这里使用STM32F405RGT6为例直接控制宇树A1电机。对于接线,和上面电脑连接的逻辑相同,现在不过变成MCU上的RX脚接MAX13488的RX,MCU上的TX脚接MAX13488的TX。这里需要注意的是,需要使用F4主控的USART1或USART6,因为这两个串口挂在APB2上,支持84MHz的时钟频率,在默认采样模式(OverSampling=16)下,是5.25MBd的最高波特率,而另外几个挂在APB1上,最高2.625MBd,不满足我们4.8MBd的条件。

将完整通信协议迁移到下位机的详细代码构建流程不多赘述,这里我可以给出两个资料参考,一个是电机接收报文格式(也摘取自之前开头的宇树A1相关资料 ,这里的markdown格式喂给AI更方便 ):

字节偏移变量名数据类型说明物理量换算公式 (发送前)
0start[0]uint8帧头,固定 0xFE-
1start[1]uint8帧头,固定 0xEE-
2motorIDuint8目标电机ID (0~2) 0xBB 为广播-
3reserveduint8预留,固定 0-
4modeuint8控制模式: 0: 停转 5: 开环缓转 10: 闭环伺服-
5ModifyBituint8参数修改位 (通常 0)-
6ReadBituint8读取参数位 (通常 0)-
7reserveduint8预留 (通常全 0)-
8-11Modifyuint32参数修改位 (通常全 0)-
12-13Tint16前馈力矩 (Nm)Target_T * 256
14-15Wint16目标角速度 (rad/s)Target_W * 128
16-19Posint32目标位置 (rad)Target_Pos * 16384/(2*PI)
20-21K_Pint16位置刚度 (系数)Target_Kp * 2048
22-23K_Wint16速度刚度 (系数)Target_Kw * 1024
24LowHzuint8低频控制索引-
25LowHzuint8低频控制值-
26-29Resuint8[4]预留-
30-33CRCuint32CRC32 校验码校验前 30 个字节

另外一个是我根据这个格式写 (vibe coding) 的下位机驱动代码,测试通过,已经推送到GitHub

You never know what will happen next.
使用 Hugo 构建
主题 StackJimmy 设计