ROS2系列教程:实体机器人硬件搭建(原理)

本文是 ROS2 系列教程的第 31 篇

本文是 ROS2 系列教程的第 31 篇:实体机器人硬件搭建(原理)。第 26-30 篇我们打通了硬件控制全链路:电机、舵机、传感器、MicroROS、ROS2-Control。但那些都是在"零件"层面。本篇开始进入实体机器人阶段(31-34):把前面所有的零件组装成一台能跑、能感知、能建图导航的真机器人。本篇聚焦硬件搭建的完整原理:总体架构、选型、电气连接、电源管理、车架结构,并给出计算与调试方法,为第 32-34 篇的软件开发打基础。

一、实体机器人总体架构

1.1 经典两轮差速小车的组成

一台能跑 ROS2 的最小实体机器人(两轮差速小车)由以下部分组成:

模块 组成 作用
车架 底盘板、连接件、轮子 承载所有部件
动力 直流减速电机 ×2、编码器、电机驱动(TB6612) 驱动车轮、测速
下位机 STM32 / ESP32 电机闭环控制、传感器采集
上位机 树莓派 / Jetson Nano 运行 ROS2、建图导航
电源 锂电池、稳压模块 供电
感知 激光雷达、IMU、超声波 环境感知
通信 USB 串口、Wi-Fi 上/下位机通信

经典数据流:

text 复制代码
激光雷达 ─┐
IMU ──────┼→ 上位机(ROS2) ──/cmd_vel──→ 下位机(STM32) ──PWM──→ 电机驱动 ──→ 电机
超声波 ───┘        ↑                                              │
                /odom ◄──── 编码器测速 ── 下位机闭环 ────────────────┘

1.2 为什么选两轮差速

两轮差速(2WD)是最适合入门和学习的底盘构型:

底盘类型 优点 缺点 适合
两轮差速 结构简单、成本低、控制成熟 只能原地旋转和前后走 入门、室内、教学
四轮差速 负载大、越野 转向需要滑移 室外
麦克纳姆轮 全向移动、可横移 贵、控制复杂、效率低 竞赛、仓储
阿克曼转向 接近真车 结构复杂、不能原地转 室外车模

两轮差速的运动学模型(第 13 篇讲过):给线速度 v 和角速度 ω,换算左右轮速:

text 复制代码
v_left  = (v - ω × L/2) / r
v_right = (v + ω × L/2) / r
其中 L=轮距,r=轮半径

1.3 上下位机分工再明确

  • 上位机(树莓派/Jetson):跑 ROS2、SLAM、Nav2。算力强但功耗高(5V 2-3A)。
  • 下位机(STM32):电机闭环、编码器、超声波等实时性要求高的任务。功耗低、实时性强。
  • 分界点:所有"控制周期 ≤ 10ms"的任务放下位机,其余放上位机。

二、硬件选型(原理与要点)

2.1 电机选型

机器人电机选择的核心参数:

参数 说明 选择要点
减速比 电机转速 vs 输出转速 小车常用 1:30 ~ 1:90
额定电压 常见 3.7V/6V/12V 与电池电压匹配
额定转速 输出轴 RPM 小车 100-300 RPM 合适
扭矩 决定爬坡/加速能力 越大越有力
编码器 每转脉冲数(PPR) 至少 11-13 PPR,配 4 倍频

选型计算示例:目标最高速度 0.5 m/s,轮径 65mm(半径 0.0325m):

text 复制代码
轮子转速 = v / (2πr) = 0.5 / (2 × 3.14 × 0.0325) ≈ 2.45 转/秒 ≈ 147 RPM

电机(减速前)转速 = 147 × 减速比
选 1:30 减速比 → 电机 ≈ 4410 RPM(合理,常见 3000-12000 RPM 电机)

所需扭矩估算(车重 1kg,摩擦系数 0.3,加速度 1m/s²):
F = m×a + μ×m×g = 1×1 + 0.3×1×9.8 ≈ 3.94 N
每轮扭矩 = F×r / 2 / 效率 ≈ 3.94×0.0325/2/0.6 ≈ 0.107 N·m

结论:选择 12V、减速比 1:30、额定转速 ~5000RPM、扭矩 ≥ 0.15 N·m、带编码器的直流减速电机。

2.2 上位机选型

平台 优点 缺点 建议
树莓派 4B/5 生态好、教程多、便宜 算力一般、散热 入门首选
Jetson Nano/Orin GPU 算力强 贵、功耗高 视觉项目
工控机 稳定、接口全 大、贵 工业

运行 ROS2 Humble 至少需要:4GB 内存、16GB 存储、支持 USB3.0(接激光雷达)。

2.3 激光雷达选型

型号 测距 频率 特点
RPLIDAR A1 12m 5.5Hz 最经典,性价比高
YDLIDAR X4 10m 6-7Hz 国产,性价比高
RPLIDAR A2/A3 12-25m 8-16Hz 精度更高
Livox Mid-360 40m 10Hz 3D 雷达,贵

入门选 RPLIDAR A1 或 YDLIDAR X4 即可满足室内 SLAM。

2.4 电源选型

电池 电压 优点 缺点
18650 锂电(2S/3S) 7.4/11.1V 便宜、容量大 需要保护板
航模锂电 LiPo 7.4-11.1V 放电猛 娇贵、怕过放
磷酸铁锂 6.4-9.6V 安全、寿命长 电压低

电源架构(关键):

text 复制代码
锂电池(11.1V)
    ├── 降压模块 → 12V/5A → 电机驱动(电机供电)
    ├── 降压模块 → 5V/3A → 树莓派(上位机)
    └── 降压模块 → 5V/1A → STM32 + 传感器

注意:电机启动电流大,电机供电必须与逻辑供电隔离(至少分开走线、电容滤波),否则电机一启动上位机就复位。

三、电气连接与布线(原理)

3.1 接线规范

实体机器人接线是"细节决定成败"的环节,规范如下:

  1. 电源线粗、信号线细:电机线用 20AWG 以上,信号线 26-28AWG。
  2. 共地:所有模块 GND 必须连在一起(否则通信不稳定)。
  3. 上下位机串口交叉:上位机 TX → 下位机 RX,上位机 RX → 下位机 TX,共地。
  4. 屏蔽与隔离:电机线与传感器线分开走,避免干扰;长线用双绞或屏蔽线。
  5. 防反接:电源入口加防反接二极管或防反接模块。

3.2 完整接线清单(示例)

text 复制代码
电源:
  11.1V 电池 → 电源开关 → 分线排
    ├─→ 5V/5A 降压 → 树莓派 5V/GND
    ├─→ 5V/2A 降压 → STM32 5V + 传感器 VCC
    └─→ 12V 直供 → TB6612 VM(电机电源)

下位机(STM32):
  PA0/PA1 → 左电机编码器 A/B
  PB6/PB7 → 右电机编码器 A/B(或用定时器编码器模式)
  PA6/PA7 → TB6612 PWMA/PWMB
  PA4/PA5 → TB6612 AIN1/AIN2
  PB0/PB1 → TB6612 BIN1/BIN2
  PA8     → TB6612 STBY
  PA9/PA10→ 串口1 → 树莓派 UART(115200)

上位机(树莓派):
  GPIO14/15 → 串口 → STM32
  USB0      → RPLIDAR A1
  I2C       → IMU(可选)

雷达:
  RPLIDAR → 5V/GND + USB

3.3 电流估算(防电源不足)

总电流估算(满载):

text 复制代码
树莓派 4B:       ~1.2A(峰值 2.5A)
STM32 + 传感器:  ~0.3A
激光雷达:        ~0.4A
电机(2 个堵转): 2 × 1.5A = 3A
合计:            ~5A(峰值)

电池容量建议:≥ 5000mAh(2S/3S),续航 1-2 小时
降压模块额定:电机路 ≥ 5A,逻辑路 ≥ 3A

四、示例 1:车架与结构设计(原理)

4.1 车架结构要点

  1. 重心低:电池放最底层,重心尽量贴近地面,转弯更稳。
  2. 重心居中:电池、上位机尽量居中对称,避免偏载导致跑偏。
  3. 轮距与轴距:轮距越宽越稳但转弯半径越大;两轮小车通常轮距 = 车宽的 60-70%。
  4. 驱动轮与万向轮:两轮差速需要配 1-2 个万向轮(牛眼轮/万向球)做支撑。
  5. 模块固定:减震(雷达、IMU 用硅胶垫),防松动(螺丝胶)。

4.2 建模辅助(可选)

可以用 FreeCAD/Onshape 建 3D 模型验证干涉,也可以用简单画图工具画布线图。实体搭建前先画一张"俯视布局图",标注每个模块位置和走线,能省大量返工。

五、示例 2:下位机固件框架(原理)

下位机 STM32 固件采用"定时中断 + 状态机"结构,主循环处理通信,定时中断处理控制:

c 复制代码
// main.c 框架:下位机固件主循环(伪代码 + 关键逻辑)
#include "main.h"
#include "motor_driver.h"
#include "encoder.h"
#include "pid.h"
#include "protocol.h"

// 全局变量
volatile float target_v = 0, target_w = 0;  // 目标线速度/角速度
volatile float current_v = 0, current_w = 0;

// 10ms 控制定时器中断:速度闭环
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM6) {
        // 1. 编码器测速 → 计算实际线/角速度
        float v_left = encoder_speed(ENC_LEFT);    // rad/s
        float v_right = encoder_speed(ENC_RIGHT);
        current_v = (v_left + v_right) * WHEEL_RADIUS / 2.0;
        current_w = (v_right - v_left) * WHEEL_RADIUS / WHEEL_BASE;

        // 2. 逆运动学:目标线/角速度 → 左右轮速
        float wl = (target_v - target_w * WHEEL_BASE / 2.0) / WHEEL_RADIUS;
        float wr = (target_v + target_w * WHEEL_BASE / 2.0) / WHEEL_RADIUS;

        // 3. PID 闭环 → PWM
        motor_left_set_speed((int)pid_update(&pid_l, wl, v_left));
        motor_right_set_speed((int)pid_update(&pid_r, wr, v_right));
    }
}

// 主循环:处理串口协议帧
int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_TIM6_Init();      // 10ms 控制定时器
    MX_USART1_UART_Init();
    motor_init();
    HAL_TIM_Base_Start_IT(&htim6);   // 启动控制定时器

    protocol_frame_t frame;
    while (1) {
        // 解析串口帧(协议见第 29 篇:帧头+命令+校验)
        if (protocol_parse(&frame) == PROTOCOL_OK) {
            switch (frame.cmd) {
                case CMD_SET_VEL:   // 设置目标速度
                    target_v = frame.f32[0];
                    target_w = frame.f32[1];
                    break;
                case CMD_GET_ODOM:  // 上位机查询里程计
                    protocol_send_odom(current_v, current_w);
                    break;
                case CMD_RESET:     // 复位
                    __NVIC_SystemReset();
                    break;
            }
        }
    }
}

这个框架体现了下位机的核心原则:控制(中断)+ 通信(主循环)分离,保证实时性。

六、示例 3:上位机串口通信节点(Python)

上位机用 ROS2 节点与下位机通信:订阅 /cmd_vel 打包发送,读取里程计回传发布 /odom:

python 复制代码
# serial_bridge.py:上位机-下位机串口桥(ROS2 节点)
import rclpy
import serial
import struct
from rclpy.node import Node
from geometry_msgs.msg import Twist
from nav_msgs.msg import Odometry
from std_msgs.msg import Header


class SerialBridge(Node):
    FRAME_HEAD = 0xAA55

    def __init__(self):
        super().__init__('serial_bridge')
        # 串口配置(与下位机一致)
        self.ser = serial.Serial('/dev/ttyUSB0', 115200, timeout=0.02)
        # 订阅速度指令
        self.cmd_sub = self.create_subscription(
            Twist, '/cmd_vel', self.on_cmd_vel, 10)
        # 发布里程计
        self.odom_pub = self.create_publisher(Odometry, '/odom', 10)
        self.timer = self.create_timer(0.02, self.read_odom)  # 50Hz

    def on_cmd_vel(self, msg: Twist):
        # 打包:帧头(2B) + 命令(1B) + 数据(8B float×2) + 校验(1B)
        data = struct.pack('>2ff', msg.linear.x, msg.angular.z)
        checksum = 0
        for b in data:
            checksum ^= b
        frame = struct.pack('>HB', self.FRAME_HEAD, 0x01) + data + bytes([checksum])
        self.ser.write(frame)

    def read_odom(self):
        # 读下位机回传帧(简化:固定格式)
        buf = self.ser.read(15)
        if len(buf) != 15:
            return
        head, cmd = struct.unpack('>HB', buf[:3])
        if head != self.FRAME_HEAD or cmd != 0x02:
            return
        v, w = struct.unpack('>2f', buf[3:11])
        # 发布里程计(简化:仅速度,实际工程应积分位姿)
        odom = Odometry()
        odom.header = Header()
        odom.header.stamp = self.get_clock().now().to_msg()
        odom.header.frame_id = 'odom'
        odom.child_frame_id = 'base_link'
        odom.twist.twist.linear.x = v
        odom.twist.twist.angular.z = w
        self.odom_pub.publish(odom)


def main(args=None):
    rclpy.init(args=args)
    node = SerialBridge()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

验证链路:

bash 复制代码
# 终端 1:启动串口桥
ros2 run robot_bringup serial_bridge.py

# 终端 2:发速度指令,观察电机是否转
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.1}, angular: {z: 0.0}}" --once

# 终端 3:查看里程计(轮子转起来 /odom 才有数据)
ros2 topic echo /odom

七、示例 4:整机启动流程与调试(launch)

实体机器人调试要"分层验证":先硬件后软件,先单模块后整机。

7.1 分层调试顺序

text 复制代码
第 1 层 电源:各模块电压正常,指示灯亮
第 2 层 下位机:串口打印正常,电机能转,编码器有读数
第 3 层 通信:上位机 serial_bridge 能收发,ros2 topic 有数据
第 4 层 传感器:雷达 /scan 正常,IMU /imu 正常
第 5 层 建图导航:SLAM 建图、Nav2 导航

7.2 一键启动 launch

python 复制代码
# robot_bringup.launch.py:整机启动(串口桥 + 雷达 + 底盘状态)
from launch import LaunchDescription
from launch_ros.actions import Node


def generate_launch_description():
    return LaunchDescription([
        # 串口桥(上/下位机通信)
        Node(package='robot_bringup', executable='serial_bridge.py',
             output='screen'),
        # 激光雷达驱动
        Node(package='rplidar_ros', executable='rplidar_composition',
             parameters=[{'serial_port': '/dev/ttyUSB1',
                          'frame_id': 'laser',
                          'angle_compensate': True}],
             output='screen'),
        # 发布 TF(odom → base_link → laser)
        Node(package='tf2_ros', executable='static_transform_publisher',
             arguments=['0', '0', '0.1', '0', '0', '0',
                        'base_link', 'laser']),
        # 底盘状态发布(/odom 已在串口桥里)
    ])

启动后检查:

bash 复制代码
ros2 launch robot_bringup robot_bringup.launch.py
# 验证话题
ros2 topic list
# 应看到:/cmd_vel /odom /scan /tf ...
# 验证 TF 树
ros2 run tf2_tools view_frames  # 生成 frames.pdf 检查坐标系

八、常见问题与排障

现象 原因 解决
上位机反复重启 电源不足/电机干扰 电机与逻辑供电隔离,加大电容,单独供电
电机不动但代码正常 STBY 没拉高/电源没接电机路 检查 TB6612 STBY 与 VM 电压
车轮方向相反 电机接线/编码器方向不一致 软件里反转 IN 引脚或统一方向
车跑偏(直行走弧线) 左右轮速不等/机械不对称 用 PID 速度闭环,检查轮胎气压/磨损
串口乱码 波特率不一致/共地缺失 统一 115200,检查 GND 连接
雷达扫描有盲区 雷达安装位置遮挡 雷达装在最高点,避开云台/支架遮挡
上电瞬间 USB 掉设备 电流冲击 加缓启动(软启动模块),或先上逻辑电再上动力电
WiFi 丢包 信号干扰/天线位置 天线垂直朝上,避开金属遮挡

九、小结与下一篇预告

本篇核心:

  1. 实体机器人 = 车架 + 动力(电机/编码器/驱动)+ 下位机 + 上位机 + 电源 + 感知。
  2. 选型三要素:电机(减速比/扭矩/编码器)、上位机(算力 vs 功耗)、电源(电流估算与隔离)。
  3. 电气规范:共地、电源隔离、信号线分离、防反接------细节决定稳定性。
  4. 调试方法论:分层验证(电源→下位机→通信→传感器→整机),用 launch 一键启动。

至此实体机器人的"骨架"搭好了。下一篇(第 32 篇)进入 嵌入式控制与通信开发(原理+仿真):把第 31 篇的硬件框架落实成完整代码------下位机协议栈实现、里程计发布、传感器数据融合,并在仿真环境里验证上/下位机通信的可靠性。敬请期待。

相关推荐
Williamtym10 小时前
PickGo Lab实践:用浏览器、Python和MCP串起机器人搬运仿真
开发语言·python·机器人
禁默11 小时前
Embodied VideoAgent 论文解读:机器人怎样持续记住动态场景
人工智能·机器人
szxinmai主板定制专家12 小时前
ZYNQ 数据采集方案踩坑总结:电力强电磁干扰、工业现场多协议适配优化
运维·人工智能·fpga开发·机器人·zynq
空堂与归13 小时前
英伟达开源ASPIRE:机器人自己攒Skill
人工智能·机器人·开源·agent
Gary Studio16 小时前
轮足机器人-IMU部分
机器人
禁默17 小时前
机器人的手,为什么拿个杯子都这么难?一文看懂“灵巧操作“
人工智能·microsoft·机器人
愤怒火龙果19 小时前
张小珺商业访谈笔记整理|对话 Dyna 创始人 Lindon Gao:机器人世界的迷雾、分歧与抉择
人工智能·机器人·人物访谈
具身AGI21 小时前
一块远程显卡服务一队机器人,瓶颈不在模型
人工智能·机器人
波力海苔夹心脆67521 小时前
VisionMaster 常用工具入门:图像采集、定位、测量、识别一篇打通
经验分享·机器人·视觉检测·工业相机·visionmaster·海康