Skip to content

第 7 章:舵机 + 超声波避障

7.1 任务描述

本章实现自动避障:小车前进过程中持续测量前方距离;检测到障碍物时停车,由舵机带动超声波模块分别向左、向右测距,比较两侧距离后向空旷一侧转向,然后继续前进。全程无需人工干预。

7.2 避障策略

检测条件小车动作
前方距离大于 30 cm前进(调用第 4 章"前进"函数)
前方距离不大于 30 cm停车,舵机分别转向两侧测距
左侧距离大于右侧距离左平移(或原地左转)后恢复前进
右侧距离不小于左侧距离右平移(或原地右转)后恢复前进
两侧距离均不大于 15 cm后退 1 秒,再重新扫描

自动避障策略示意图

两个关键细节:

  • 舵机转到目标角度后须等待约 0.5 秒再读取距离,否则读到的是转动途中的数据;

  • 超声波模块存在约 2 cm 的测距盲区,且过近时回波异常,因此以 30 cm 作为减速判定线,为转向留出余地。

7.3 代码实现

程序结构:

  1. 循环开始,舵机回到 90° 中位,读取正前方距离;

  2. 若距离大于 30 cm,前进;

  3. 否则停车:舵机转到 135°,等待 0.5 秒,读取并记录左侧距离;舵机转到 45°,等待 0.5 秒,读取并记录右侧距离;

  4. 比较两侧距离,向空旷一侧平移或原地转向,回到第 1 步。

说明:每次循环先将舵机回到 90° 中位再测正前方距离,保证每次读数的探测方向一致,避免上一次扫描残留的角度影响判断。

本章无新知识点,Mind+ 图形化代码从略。重点在于通过算法锻炼编程思维。学员可根据下方 MicroPython 代码自行拖拽还原图形化程序,如需协助请联系编者微信。

python
# 自动避障主程序
# 直接调用前面章节的函数:servo_angle(第 5 章)、distance(第 5 章)、
# go_sp / stop_all(第 4 章),无需重复定义
while True:
    # 每次循环先把舵机回 90° 中位再测正前方,
    # 保证每次读数的探测方向一致,不受上次扫描残留角度影响
    servo_angle(90)
    time.sleep(0.5)          # 等舵机停稳再读数
    front = distance()
    if front > 30:
        # 前方空旷(>30cm):前进。以 30cm 为判定线,是因为超声波有约 2cm 盲区,
        # 且要为停车、转向留出反应距离
        go_sp(150, 1)
    else:
        stop_all()           # 先停车,再左右扫描
        servo_angle(135)     # 135° = 左侧(俯视逆时针数角度)
        time.sleep(0.5)      # 等舵机停稳再读,否则读到的是转动途中的数据
        left = distance()
        servo_angle(45)      # 45° = 右侧
        time.sleep(0.5)
        right = distance()
        if left <= 15 and right <= 15:
            # 两侧都很近(被围住了):后退 1 秒,下一轮重新扫描
            go_sp(-150, 1)
            time.sleep(1)
        elif left > right:
            # 左侧更空旷:向左平移 0.5 秒
            # (也可以换成原地左转:turn_angle(30),效果类似)
            go_sp(150, 2)
            time.sleep(0.5)
        else:
            # 右侧不窄于左侧:向右平移 0.5 秒(或 turn_angle(-30))
            go_sp(-150, 2)
            time.sleep(0.5)
    time.sleep(0.05)
cpp
// 自动避障主程序
// 直接调用前面章节的函数:servoAngle(第 5 章)、distance(第 5 章)、
// go_sp / stop_all(第 4 章),无需重复定义
void loop() {
  // 每次循环先把舵机回 90° 中位再测正前方,保证探测方向一致
  servo15.write(90);
  delay(500);                // 等舵机停稳再读数
  float front = distance();
  if (front > 30) {
    // 前方空旷(>30cm):前进。30cm 判定线为停车、转向留出反应距离
    go_sp(150, 1);
  } else {
    stop_all();              // 先停车,再左右扫描
    servo15.write(135);      // 135° = 左侧(俯视逆时针数角度)
    delay(500);              // 等舵机停稳再读,否则读到转动途中的数据
    float left = distance();
    servo15.write(45);       // 45° = 右侧
    delay(500);
    float right = distance();
    if (left <= 15 && right <= 15) {
      // 两侧都很近(被围住了):后退 1 秒,下一轮重新扫描
      go_sp(-150, 1);
      delay(1000);
    } else if (left > right) {
      // 左侧更空旷:向左平移 0.5 秒(也可换成原地左转 turn_angle(30))
      go_sp(150, 2);
      delay(500);
    } else {
      // 右侧不窄于左侧:向右平移 0.5 秒(或 turn_angle(-30))
      go_sp(-150, 2);
      delay(500);
    }
  }
  delay(50);
}

7.4 进阶练习:带扫描功能的跟随

当目标离开正前方探测范围时,小车会因读数异常而停止或误动作。改进方案:通过舵机(GP15)驱动超声波模块依次转向 45°、90°、135° 进行扫描,比较各方向的距离读数,判定目标方位后向该方向平移或原地转向,重新锁定目标。

本章无新知识点,Mind+ 图形化代码从略。重点在于通过算法锻炼编程思维。学员可根据下方 MicroPython 代码自行拖拽还原图形化程序,如需协助请联系编者微信。

python
# 进阶:带扫描功能的跟随
# 思路:舵机带超声波扫 45°/90°/135° 三个方向,
# 哪个方向距离最大,目标就在哪个方向,然后向该方向移动
while True:
    best_dir = 90            # 记录距离最大的方向
    best_dist = -1           # 记录最大距离(先设成不可能的小值)
    for a in (45, 90, 135):
        servo_angle(a)
        time.sleep(0.5)      # 等舵机停稳
        s = distance()
        if s > best_dist:
            best_dist = s
            best_dir = a
    # 扫描结束,按目标方位行动
    if best_dir == 90:
        # 目标在正前方:按磁吸跟随的死区逻辑控制距离(第 6 章)
        if best_dist > 30:
            go_sp(150, 1)    # 远了:前进
        elif best_dist < 15:
            go_sp(-150, 1)   # 太近:后退
        else:
            stop_all()       # 15~30cm:刚好,停车
    elif best_dir == 135:
        # 目标在左侧:向左平移 0.5 秒重新锁定(也可 turn_angle(30))
        go_sp(150, 2)
        time.sleep(0.5)
    else:
        # 目标在右侧:向右平移 0.5 秒(也可 turn_angle(-30))
        go_sp(-150, 2)
        time.sleep(0.5)
    time.sleep(0.05)
cpp
// 进阶:带扫描功能的跟随
// 思路:扫 45°/90°/135° 三个方向,距离最大的方向就是目标方向
void loop() {
  int best_dir = 90;         // 记录距离最大的方向
  float best_dist = -1;      // 记录最大距离(先设成不可能的小值)
  int angles[3] = {45, 90, 135};
  for (int i = 0; i < 3; i++) {
    servo15.write(angles[i]);
    delay(500);              // 等舵机停稳
    float s = distance();
    if (s > best_dist) {
      best_dist = s;
      best_dir = angles[i];
    }
  }
  // 扫描结束,按目标方位行动
  if (best_dir == 90) {
    // 目标在正前方:按磁吸跟随的死区逻辑控制距离(第 6 章)
    if (best_dist > 30) {
      go_sp(150, 1);         // 远了:前进
    } else if (best_dist < 15) {
      go_sp(-150, 1);        // 太近:后退
    } else {
      stop_all();            // 15~30cm:刚好,停车
    }
  } else if (best_dir == 135) {
    // 目标在左侧:向左平移 0.5 秒重新锁定(也可 turn_angle(30))
    go_sp(150, 2);
    delay(500);
  } else {
    // 目标在右侧:向右平移 0.5 秒(也可 turn_angle(-30))
    go_sp(-150, 2);
    delay(500);
  }
  delay(50);
}

7.5 拓展练习

  • 脱困能力:模拟三面被围的场景,验证"两侧均过近则后退重扫"分支能否让小车脱困;

  • 效率统计:用变量记录一次运行中的转向次数,比较不同判定阈值(20 cm / 30 cm / 40 cm)下的通行效率。

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