4. 避障功能

在本项目中,PiCrawler 将使用超声波模块检测前方的障碍物。 当检测到障碍物时,PiCrawler 会发送信号,并寻找其他方向继续前进。

运行代码

cd ~/picrawler/examples
sudo python3 4_avoid.py

当程序启动时,PiCrawler 会站立起来。

它会持续使用超声波传感器测量距离, 并在终端中打印测量到的数值。

如果在 15 cm 范围内检测到障碍物: - 会播放警告声音。 - 机器人会进行一次小幅度左转。

如果前方路径是畅通的: - 机器人会向前移动。

机器人会持续自动避障运行,直到你按下 Ctrl+C。

在程序退出之前,机器人会安全地坐下。

代码

备注

你可以对下面的代码进行 Modify/Reset/Copy/Run/Stop 操作。但在此之前,需要先进入源码路径,如 picrawler\examples。修改代码后,可以直接运行查看效果。

from picrawler import Picrawler
from robot_hat import Music, Ultrasonic, Pin
import time
import signal

music = Music()
crawler = Picrawler()
sonar = Ultrasonic(Pin("D2"), Pin("D3"))  # Ultrasonic trigger/echo pins

music.music_set_volume(100)  # Set speaker volume

alert_distance = 15  # Obstacle warning distance (cm)
speed = 80           # Movement speed

# ----------------------------
# Add hardware timeout to sonar.read()
# Prevent program from freezing
# ----------------------------
class Timeout(Exception):
    pass

def _alarm_handler(signum, frame):
    raise Timeout()

signal.signal(signal.SIGALRM, _alarm_handler)

# Read distance once with timeout protection
def safe_read_once(timeout_s=1):
    try:
        signal.alarm(timeout_s)
        d = sonar.read()
        signal.alarm(0)
        return d
    except Timeout:
        signal.alarm(0)
        return None
    except Exception:
        signal.alarm(0)
        return None

# Read multiple times and return median value (anti-noise)
def read_distance_filtered(n=5, gap=0.03, timeout_s=1):
    vals = []
    for _ in range(n):
        d = safe_read_once(timeout_s=timeout_s)
        if d is not None and d > 0:
            vals.append(d)
        time.sleep(gap)

    if not vals:
        return None

    vals.sort()
    return vals[len(vals)//2]  # Median filter

def main():
    distance = read_distance_filtered(n=5, gap=0.03, timeout_s=1)
    print("distance:", distance)

    if distance is None:
        time.sleep(0.15)  # Wait if read failed
        return

    if distance <= alert_distance:
        # Obstacle detected → play sound and turn
        try:
            music.sound_play_threading('./sounds/sign.wav', volume=100)
        except Exception as e:
            print("sound error:", e)

        crawler.do_action('turn left angle', 1, speed)
        time.sleep(0.5)  # Quiet window after movement
    else:
        # Path clear → move forward
        crawler.do_action('forward', 1, speed)
        time.sleep(0.4)

if __name__ == "__main__":
    try:
        crawler.do_step('stand', 40)  # Stand before starting
        time.sleep(1.0)

        while True:
            main()

    except KeyboardInterrupt:
        print("\nStop.")
    finally:
        try:
            crawler.do_step('sit', 40)  # Sit before exit
            time.sleep(1.0)
        except Exception:
            pass

工作原理

  1. 初始化模块

    music = Music()
    crawler = Picrawler()
    sonar = Ultrasonic(Pin("D2"), Pin("D3"))
    
    music.music_set_volume(100)
    alert_distance = 15
    speed = 80
    

    该代码块初始化三个主要模块: - music:用于控制声音播放。 - crawler:用于控制 PiCrawler 的运动。 - sonar:通过超声波传感器读取距离。

    同时还设置了扬声器音量、障碍物检测阈值(单位:厘米), 以及机器人的运动速度。

  2. 超时保护模块(防止 sonar.read() 卡死)

    class Timeout(Exception):
        pass
    
    def _alarm_handler(signum, frame):
        raise Timeout()
    
    signal.signal(signal.SIGALRM, _alarm_handler)
    

    超声波驱动在等待回波信号时可能会阻塞程序。 该代码块安装了一个信号处理器,使程序可以中断 卡住的 sonar.read() 调用,从而保证程序继续运行。

  3. 函数:safe_read_once()

    def safe_read_once(timeout_s=1):
        try:
            signal.alarm(timeout_s)
            d = sonar.read()
            signal.alarm(0)
            return d
        except Timeout:
            signal.alarm(0)
            return None
        except Exception:
            signal.alarm(0)
            return None
    

    该函数在带有超时保护的情况下读取一次超声波距离。

    • 如果读取成功,则返回距离值。

    • 如果读取超时或发生错误,则返回 None,避免程序卡死。

  4. 函数:read_distance_filtered()

    def read_distance_filtered(n=5, gap=0.03, timeout_s=1):
        vals = []
        for _ in range(n):
            d = safe_read_once(timeout_s=timeout_s)
            if d is not None and d > 0:
                vals.append(d)
            time.sleep(gap)
    
        if not vals:
            return None
    
        vals.sort()
        return vals[len(vals)//2]
    

    该函数通过多次采样来提高测量可靠性:

    • 无效数据(None<= 0)会被忽略。

    • 将剩余的有效数据排序。

    • 返回中位数作为最终距离值,从而减少噪声影响。

  5. 函数:main()(核心决策与动作)

    def main():
        distance = read_distance_filtered(...)
        if distance is None:
            return
    
        if distance <= alert_distance:
            music.sound_play_threading(...)
            crawler.do_action('turn left angle', 1, speed)
        else:
            crawler.do_action('forward', 1, speed)
    

    这是主要的控制逻辑:

    • 读取经过过滤的距离值。

    • 如果读取失败,则跳过本次循环。

    • 如果检测到的障碍物距离小于 alert_distance, 则播放警告声音并向左转。

    • 否则机器人向前移动。

  6. 程序入口模块(循环运行 + 安全退出)

    if __name__ == "__main__":
        try:
            crawler.do_step('stand', 40)
            while True:
                main()
        except KeyboardInterrupt:
            print("\nStop.")
        finally:
            crawler.do_step('sit', 40)
    

    该代码块控制程序的整体运行流程:

    • 程序开始前,机器人先站立。

    • 在无限循环中不断执行 main()

    • 按下 Ctrl+C 可以终止程序。

    • 程序退出前,机器人会安全地坐下。