You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将Arduino与A*算法结合实现6×6区域机器人控制?

整合Python串口通信与A*算法控制Arduino机器人(6×6区域)

我需要将Arduino与A算法结合以控制机器人在6×6区域内移动,控制指令为前进(F)、左转(L)、右转(R),用Python编写代码。已有Arduino串口通信代码、A算法代码以及Arduino电机控制程序,如何在Python中整合这两部分?


核心整合思路

  1. 调整A*算法适配6×6地图,禁用斜向移动(机器人只能上下左右移动)
  2. 将A*输出的坐标路径转换为机器人可执行的F/L/R指令序列
  3. 整合串口通信逻辑,将指令序列按顺序发送给Arduino

步骤1:适配A*算法到6×6地图

原A*代码支持斜向移动,且地图是10×10,修改为仅允许上下左右移动,并适配6×6网格:

class Node():
    """A*路径规划节点类"""
    def __init__(self, parent=None, position=None):
        self.parent = parent
        self.position = position
        self.g = 0  # 起点到当前节点的代价
        self.h = 0  # 当前节点到终点的估计代价
        self.f = 0  # 总代价(g+h)

    def __eq__(self, other):
        return self.position == other.position


def astar(maze, start, end):
    """
    A*路径规划主函数
    返回从起点到终点的坐标路径列表,格式如[(x1,y1), (x2,y2), ...]
    """
    # 创建起点和终点节点
    start_node = Node(None, start)
    start_node.g = start_node.h = start_node.f = 0
    end_node = Node(None, end)
    end_node.g = end_node.h = end_node.f = 0

    # 初始化开放列表和关闭列表
    open_list = []
    closed_list = []

    open_list.append(start_node)

    # 循环直到找到终点
    while len(open_list) > 0:
        # 获取当前代价最低的节点
        current_node = open_list[0]
        current_index = 0
        for index, item in enumerate(open_list):
            if item.f < current_node.f:
                current_node = item
                current_index = index

        # 移动当前节点到关闭列表
        open_list.pop(current_index)
        closed_list.append(current_node)

        # 到达终点,回溯生成路径
        if current_node == end_node:
            path = []
            current = current_node
            while current is not None:
                path.append(current.position)
                current = current.parent
            return path[::-1]  # 反转路径,从起点到终点

        # 生成子节点(仅上下左右四个方向)
        children = []
        # 方向定义:右(x+1,y)、左(x-1,y)、下(x,y+1)、上(x,y-1)
        for new_position in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
            node_position = (current_node.position[0] + new_position[0], 
                            current_node.position[1] + new_position[1])

            # 检查是否在地图范围内
            if (node_position[0] < 0 or node_position[0] >= len(maze[0]) or
                node_position[1] < 0 or node_position[1] >= len(maze)):
                continue

            # 检查是否为障碍物(1表示障碍物,0表示可通行)
            if maze[node_position[1]][node_position[0]] != 0:
                continue

            # 创建新节点并加入子节点列表
            new_node = Node(current_node, node_position)
            children.append(new_node)

        # 处理每个子节点
        for child in children:
            # 如果子节点已在关闭列表,跳过
            if child in closed_list:
                continue

            # 计算代价
            child.g = current_node.g + 1
            # 使用曼哈顿距离作为启发函数(更适合网格机器人)
            child.h = abs(child.position[0] - end_node.position[0]) + abs(child.position[1] - end_node.position[1])
            child.f = child.g + child.h

            # 如果子节点已在开放列表且当前路径代价更高,跳过
            for open_node in open_list:
                if child == open_node and child.g >= open_node.g:
                    continue

            # 将子节点加入开放列表
            open_list.append(child)

步骤2:将坐标路径转换为机器人控制指令

跟踪机器人当前朝向,根据路径中相邻坐标的变化生成对应的转向或前进指令:

def path_to_commands(path, initial_direction="down"):
    """
    将A*生成的坐标路径转换为机器人控制指令
    参数:
        path: A*输出的坐标路径列表
        initial_direction: 机器人初始朝向,可选值:"up", "down", "left", "right"
    返回:指令序列列表,如["F", "R", "F", ...]
    """
    if len(path) < 2:
        return []

    # 定义朝向与坐标变化的映射
    direction_map = {
        "up": (0, -1),
        "down": (0, 1),
        "left": (-1, 0),
        "right": (1, 0)
    }
    # 定义方向转向关系:当前朝向 -> 目标朝向 需要的指令
    turn_map = {
        ("up", "right"): "R",
        ("up", "left"): "L",
        ("up", "down"): "R",  # 若支持180度转向可改为"RR"
        ("down", "right"): "L",
        ("down", "left"): "R",
        ("down", "up"): "R",
        ("left", "up"): "R",
        ("left", "down"): "L",
        ("left", "right"): "R",
        ("right", "up"): "L",
        ("right", "down"): "R",
        ("right", "left"): "R"
    }

    commands = []
    current_dir = initial_direction

    for i in range(len(path)-1):
        current_pos = path[i]
        next_pos = path[i+1]
        # 计算需要移动的方向向量
        delta = (next_pos[0] - current_pos[0], next_pos[1] - current_pos[1])
        # 找到对应的目标朝向
        target_dir = [k for k, v in direction_map.items() if v == delta][0]

        # 如果当前朝向与目标朝向不同,生成转向指令
        if current_dir != target_dir:
            turn_cmd = turn_map[(current_dir, target_dir)]
            commands.append(turn_cmd)
            current_dir = target_dir
        # 生成前进指令
        commands.append("F")

    return commands

步骤3:整合串口通信发送指令

将指令序列自动发送给Arduino,添加延时确保机器人完成动作:

import serial
import time

def send_commands_to_arduino(commands, port="COM3", baudrate=9600, delay=1.5):
    """
    将指令序列发送给Arduino
    参数:
        commands: 指令列表
        port: Arduino串口端口
        baudrate: 波特率(需与Arduino代码一致)
        delay: 每个指令执行后的等待时间(秒)
    """
    try:
        # 初始化串口连接
        arduino = serial.Serial(port, baudrate, timeout=1)
        time.sleep(2)  # 等待串口初始化完成
        print("已建立与Arduino的串口连接")

        # 逐发送指令
        for cmd in commands:
            arduino.write(cmd.encode('utf-8'))
            print(f"已发送指令: {cmd}")
            time.sleep(delay)  # 等待机器人完成动作

        # 关闭串口
        arduino.close()
        print("串口连接已关闭")
    except Exception as e:
        print(f"串口通信出错: {str(e)}")

完整整合代码示例

import serial
import time

class Node():
    """A*路径规划节点类"""
    def __init__(self, parent=None, position=None):
        self.parent = parent
        self.position = position
        self.g = 0  # 起点到当前节点的代价
        self.h = 0  # 当前节点到终点的估计代价
        self.f = 0  # 总代价(g+h)

    def __eq__(self, other):
        return self.position == other.position


def astar(maze, start, end):
    """
    A*路径规划主函数
    返回从起点到终点的坐标路径列表,格式如[(x1,y1), (x2,y2), ...]
    """
    # 创建起点和终点节点
    start_node = Node(None, start)
    start_node.g = start_node.h = start_node.f = 0
    end_node = Node(None, end)
    end_node.g = end_node.h = end_node.f = 0

    # 初始化开放列表和关闭列表
    open_list = []
    closed_list = []

    open_list.append(start_node)

    # 循环直到找到终点
    while len(open_list) > 0:
        # 获取当前代价最低的节点
        current_node = open_list[0]
        current_index = 0
        for index, item in enumerate(open_list):
            if item.f < current_node.f:
                current_node = item
                current_index = index

        # 移动当前节点到关闭列表
        open_list.pop(current_index)
        closed_list.append(current_node)

        # 到达终点,回溯生成路径
        if current_node == end_node:
            path = []
            current = current_node
            while current is not None:
                path.append(current.position)
                current = current.parent
            return path[::-1]  # 反转路径,从起点到终点

        # 生成子节点(仅上下左右四个方向)
        children = []
        # 方向定义:右(x+1,y)、左(x-1,y)、下(x,y+1)、上(x,y-1)
        for new_position in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
            node_position = (current_node.position[0] + new_position[0], 
                            current_node.position[1] + new_position[1])

            # 检查是否在地图范围内
            if (node_position[0] < 0 or node_position[0] >= len(maze[0]) or
                node_position[1] < 0 or node_position[1] >= len(maze)):
                continue

            # 检查是否为障碍物(1表示障碍物,0表示可通行)
            if maze[node_position[1]][node_position[0]] != 0:
                continue

            # 创建新节点并加入子节点列表
            new_node = Node(current_node, node_position)
            children.append(new_node)

        # 处理每个子节点
        for child in children:
            # 如果子节点已在关闭列表,跳过
            if child in closed_list:
                continue

            # 计算代价
            child.g = current_node.g + 1
            # 使用曼哈顿距离作为启发函数(更适合网格机器人)
            child.h = abs(child.position[0] - end_node.position[0]) + abs(child.position[1] - end_node.position[1])
            child.f = child.g + child.h

            # 如果子节点已在开放列表且当前路径代价更高,跳过
            for open_node in open_list:
                if child == open_node and child.g >= open_node.g:
                    continue

            # 将子节点加入开放列表
            open_list.append(child)


def path_to_commands(path, initial_direction="down"):
    """
    将A*生成的坐标路径转换为机器人控制指令
    参数:
        path: A*输出的坐标路径列表
        initial_direction: 机器人初始朝向,可选值:"up", "down", "left", "right"
    返回:指令序列列表,如["F", "R", "F", ...]
    """
    if len(path) < 2:
        return []

    # 定义朝向与坐标变化的映射
    direction_map = {
        "up": (0, -1),
        "down": (0, 1),
        "left": (-1, 0),
        "right": (1, 0)
    }
    # 定义方向转向关系:当前朝向 -> 目标朝向 需要的指令
    turn_map = {
        ("up", "right"): "R",
        ("up", "left"): "L",
        ("up", "down"): "R",  # 若支持180度转向可改为"RR"
        ("down", "right"): "L",
        ("down", "left"): "R",
        ("down", "up"): "R",
        ("left", "up"): "R",
        ("left", "down"): "L",
        ("left", "right"): "R",
        ("right", "up"): "L",
        ("right", "down"): "R",
        ("right", "left"): "R"
    }

    commands = []
    current_dir = initial_direction

    for i in range(len(path)-1):
        current_pos = path[i]
        next_pos = path[i+1]
        # 计算需要移动的方向向量
        delta = (next_pos[0] - current_pos[0], next_pos[1] - current_pos[1])
        # 找到对应的目标朝向
        target_dir = [k for k, v in direction_map.items() if v == delta][0]

        # 如果当前朝向与目标朝向不同,生成转向指令
        if current_dir != target_dir:
            turn_cmd = turn_map[(current_dir, target_dir)]
            commands.append(turn_cmd)
            current_dir = target_dir
        # 生成前进指令
        commands.append("F")

    return commands


def send_commands_to_arduino(commands, port="COM3", baudrate=9600, delay=1.5):
    """
    将指令序列发送给Arduino
    参数:
        commands: 指令列表
        port: Arduino串口端口
        baudrate: 波特率(需与Arduino代码一致)
        delay: 每个指令执行后的等待时间(秒)
    """
    try:
        # 初始化串口连接
        arduino = serial.Serial(port, baudrate, timeout=1)
        time.sleep(2)  # 等待串口初始化完成
        print("已建立与Arduino的串口连接")

        # 逐发送指令
        for cmd in commands:
            arduino.write(cmd.encode('utf-8'))
            print(f"已发送指令: {cmd}")
            time.sleep(delay)  # 等待机器人完成动作

        # 关闭串口
        arduino.close()
        print("串口连接已关闭")
    except Exception as e:
        print(f"串口通信出错: {str(e)}")


def main():
    # 定义6×6地图(0=可通行,1=障碍物)
    maze = [
        [0, 0, 0, 0, 0, 0],
        [0, 1, 1, 0, 0, 0],
        [0, 0, 0, 0, 1, 0],
        [0, 1, 0, 0, 0, 0],
        [0, 0, 0, 1, 0, 0],
        [0, 0, 0, 0, 0, 0]
    ]
    # 起点坐标(x,y):x为列(0-5),y为行(0-5)
    start = (0, 0)
    # 终点坐标
    end = (5, 5)

    # 生成路径
    path = astar(maze, start, end)
    if not path:
        print("未找到可行路径")
        return
    print(f"A*生成路径: {path}")

    # 转换为控制指令
    commands = path_to_commands(path, initial_direction="down")
    print(f"控制指令序列: {commands}")

    # 发送给Arduino
    send_commands_to_arduino(commands, port="COM3", baudrate=9600)


if __name__ == '__main__':
    main()

关键注意事项

  • 串口配置:确保port参数与Arduino实际连接的串口一致,波特率需与Arduino代码中的Serial.begin(9600)完全匹配
  • 坐标映射:需确保Python中的坐标系统与机器人实际移动的物理地图完全对应,避免路径与实际移动方向不符
  • 延时调整:delay参数需根据机器人实际移动速度调整,确保机器人完成当前动作后再接收下一个指令
  • Arduino端配合:Arduino代码需正确解析串口接收的字符('F'/'L'/'R'),并对应执行电机控制逻辑
  • 障碍物定义:地图中的1需对应实际环境中的障碍物位置,确保A*规划出可行路径

内容

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 23:45:38