Skip to content

第 4 章:控制整车四向移动、转弯与原地旋转

4.1 模块原理:麦克纳姆轮

普通车轮只能沿滚动方向前进或后退。麦克纳姆轮的轮毂上安装有一圈与轮轴呈约 45° 夹角的从动小滚轮,轮子转动时,滚轮会将部分驱动力分解到斜向。四个轮子按不同的转向组合,各轮的斜向分力相互抵消或叠加,整车即可在不改变车头方向的前提下实现左右平移、45° 斜移及原地旋转。

麦克纳姆轮结构特写

4.2 全向移动轮态速查表(八向平移 + 原地旋转)

下表为本章核心。"正转"指轮子向前滚动(前进管脚高电平、后退管脚低电平),"反转"与之相反,"停止"指两管脚电平相同。

整车动作左前轮右前轮左后轮右后轮
前进正转正转正转正转
后退反转反转反转反转
左平移反转正转正转反转
右平移正转反转反转正转
左前 45° 平移停止正转正转停止
右前 45° 平移正转停止停止正转
左后 45° 平移反转停止停止反转
右后 45° 平移停止反转反转停止
原地左转反转正转反转正转
原地右转正转反转正转反转

全向移动轮态示意图

**比亚迪仰望U8的原地掉头,正是麦克纳姆轮四驱车的基本功能。**四轮全向小车共十种运动状态,上图中后四种斜移(左前、右前、左后、右后)因轮地摩擦导致定位精度不足,本教程不予采用。编程大赛的高精度场景下,建议以"平移+直行"组合替代,例如"左平移+前进"即可等效左前斜移,且定位更精准。

规律总结:

  • 前进 / 后退:四轮同向转动,详细代码已在3.5代码实现中已示例;

  • **左右转弯:**前进时,左右轮转速一致则直行;左轮转速高于右轮则右转;右轮转速高于左轮则左转。

  • 平移:两条对角线上的轮子各为一组(左前 + 右后为一组,右前 + 左后为一组),两组反向转动;左平移时"右前、左后"正转,右平移与之相反;

  • 原地旋转:左侧两轮与右侧两轮反向转动;旋转方向一侧的轮子反转。

注意:个别小车因电机安装方向或接线顺序不同,表中"正转 / 反转"可能整体对调。处理方法:先运行 3.4 节的前进后退程序,对方向不正确的轮子,互换其两根信号线,或在程序中对调该轮两个管脚的电平。以实测结果为准。

4.3 代码实现:方向函数封装

4.3.1 刹车函数

任何车辆,先能刹停,方能安全调试。

整体刹车函数积木

python
def stop_all():
    # 八个管脚全部输出 0:每个轮子两脚电势差都为 0,四轮同时停转
    # 这里不用 move(0),因为 move 只走四个轮函数,stop_all 把 8 个脚显式清零,
    # 即使程序其他部分改乱了脚位状态,也能一次刹干净
    pwm(GP2, 0)
    pwm(GP3, 0)
    pwm(GP4, 0)
    pwm(GP5, 0)
    pwm(GP6, 0)
    pwm(GP7, 0)
    pwm(GP8, 0)
    pwm(GP9, 0)
cpp
void stop_all() {
  // 八个管脚全部输出 0:每个轮子两脚电势差都为 0,四轮同时停转
  // 这里不用 move(0),stop_all 把 8 个脚显式清零,刹得更彻底
  analogWrite(2, 0);
  analogWrite(3, 0);
  analogWrite(4, 0);
  analogWrite(5, 0);
  analogWrite(6, 0);
  analogWrite(7, 0);
  analogWrite(8, 0);
  analogWrite(9, 0);
}

4.3.2 转弯封装函数

任何车辆,开起来的时候不能只走直线,要能左右转弯。

转弯函数积木

python
def turn1(e):
    # 差速转弯:左、右两侧轮速不一样,车就向慢的一侧偏转
    # e =  1 右转弯;e = -1 左转弯;e = 0 直行(两侧同速)
    # e 也可以取小数,比如 0.5 就是"半弯",弯道更平缓
    sp = 150            # 基础速度:两侧轮速都围绕它上下浮动
    p = 80              # 差速幅度:e 每变化 1,两侧速度一增一减各 80
    sp_left = sp + p * e    # 左侧速度 = 基础速度 + 差速
    sp_right = sp - p * e   # 右侧速度 = 基础速度 - 差速
    # 以 e = 1(右转)为例:左轮 230、右轮 70,
    # 左快右慢 → 车头向右偏,符合直觉
    wheel_left_1(sp_left)
    wheel_left_2(sp_left)
    wheel_right_1(sp_right)
    wheel_right_2(sp_right)
cpp
void turn1(float e) {
  // 差速转弯:左、右两侧轮速不一样,车就向慢的一侧偏转
  // e =  1 右转弯;e = -1 左转弯;e = 0 直行(两侧同速)
  // e 也可以取小数,比如 0.5 就是"半弯",弯道更平缓
  int sp = 150;               // 基础速度:两侧轮速都围绕它上下浮动
  int p = 80;                 // 差速幅度:e 每变化 1,两侧速度一增一减各 80
  int sp_left  = sp + p * e;  // 左侧速度 = 基础速度 + 差速
  int sp_right = sp - p * e;  // 右侧速度 = 基础速度 - 差速
  // 以 e = 1(右转)为例:左轮 230、右轮 70,左快右慢 → 车头向右偏
  wheel_left_1(sp_left);
  wheel_left_2(sp_left);
  wheel_right_1(sp_right);
  wheel_right_2(sp_right);
}

以上函数封装了3.5中四个轮子的基础代码:wheel_left_1,wheel_left_2,wheel_right_1,wheel_right_2,该四个函数的详细代码请移步到3.5中查看。

4.3.3 四向移动封装函数

四向移动函数积木

python
def go_sp(sp, dir):
    if dir == 1:
        # dir = 1:前后移动。四轮同速同向
        # sp > 0 前进;sp < 0 后退
        wheel_left_1(sp)
        wheel_left_2(sp)
        wheel_right_1(sp)
        wheel_right_2(sp)
    elif dir == 2:
        # dir = 2:左右平移(麦克纳姆轮独有的动作)
        # 左前轮与右后轮一组、左后轮与右前轮一组,两组反向转动:
        # 每个轮子产生的都是斜向力,对角两组斜向力相互抵消前后分量,
        # 只合成出纯横向的力,车身不转、车头不偏,整体横着走
        # sp > 0 向左平移;sp < 0 向右平移
        wheel_left_1(-1 * sp)
        wheel_left_2(sp)
        wheel_right_1(sp)
        wheel_right_2(-1 * sp)
cpp
void go_sp(int sp, int dir) {
  if (dir == 1) {
    // dir = 1:前后移动。四轮同速同向
    // sp > 0 前进;sp < 0 后退
    wheel_left_1(sp);
    wheel_left_2(sp);
    wheel_right_1(sp);
    wheel_right_2(sp);
  } else if (dir == 2) {
    // dir = 2:左右平移(麦克纳姆轮独有的动作)
    // 左前轮与右后轮一组、左后轮与右前轮一组,两组反向转动:
    // 对角两组斜向力相互抵消前后分量,只合成纯横向的力,车身整体横移
    // sp > 0 向左平移;sp < 0 向右平移
    wheel_left_1(-1 * sp);
    wheel_left_2(sp);
    wheel_right_1(sp);
    wheel_right_2(-1 * sp);
  }
}

4.3.4 原地左右旋转封装函数

原地旋转函数积木

python
def turn_angle(angle):
    # 原地旋转指定角度:angle > 0 原地左转;angle < 0 原地右转
    # 思路:左、右两侧轮速大小相等、方向相反,车就绕自身中心打转
    angle_n = angle / abs(angle)   # 取符号:只保留方向(+1 或 -1),数值大小后面单独算
    start_time = time.ticks_ms()   # 记下开始转动的系统时间(毫秒)
    # 时间换角度:实测标定,速度 200 时小车约每 10 毫秒转过 1°
    # 所以要转 |angle| 度,就需要 |angle| × 10 毫秒
    # 用 ticks_diff 相减而不是直接相减,可以正确处理毫秒计数器回绕
    while time.ticks_diff(time.ticks_ms(), start_time) < abs(angle) * 10:
        # 以 angle > 0(左转)为例:angle_n = 1
        # 左侧两轮 = -200(反转向后),右侧两轮 = +200(正转向前)
        # 右前左后 → 车身俯视逆时针原地打转 = 左转
        wheel_left_1(angle_n * -200)
        wheel_left_2(angle_n * -200)
        wheel_right_1(angle_n * 200)
        wheel_right_2(angle_n * 200)
    # 时间到,四轮归零停车
    wheel_left_1(0)
    wheel_left_2(0)
    wheel_right_1(0)
    wheel_right_2(0)
cpp
void turn_angle(int angle) {
  // 原地旋转指定角度:angle > 0 原地左转;angle < 0 原地右转
  // 思路:左、右两侧轮速大小相等、方向相反,车就绕自身中心打转
  int angle_n = angle / abs(angle);   // 取符号:只保留方向(+1 或 -1)
  unsigned long start_time = millis(); // 记下开始转动的系统时间(毫秒)
  // 时间换角度:实测标定,速度 200 时约每 10 毫秒转过 1°
  // 所以要转 |angle| 度,就需要 |angle| × 10 毫秒
  while (millis() - start_time < (unsigned long)abs(angle) * 10) {
    // 以 angle > 0(左转)为例:左侧两轮反转向后,右侧两轮正转向前
    wheel_left_1(angle_n * -200);
    wheel_left_2(angle_n * -200);
    wheel_right_1(angle_n * 200);
    wheel_right_2(angle_n * 200);
  }
  // 时间到,四轮归零停车
  wheel_left_1(0);
  wheel_left_2(0);
  wheel_right_1(0);
  wheel_right_2(0);
}

掌握本章各向运动控制,即可根据传感器数据,让车身在任意任务中自由移动。

不积跬步,无以至千里;万里之行,始于当下。