尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
树莓派4B+STM32+ROS:从零搭建能跑导航的智能小车
简介这份资源面向嵌入式开发初学者与ROS入门开发者提供一套基于树莓派4B与STM32F103C8T6协同控制的智能小车完整工程。上位机运行ROS下位机负责底层驱动两者通过异步串口通信构成分层控制架构可帮助读者理解机器人系统中感知、决策与执行模块的接口划分与数据流转。压缩包共236个文件约8.75MB涵盖C/C源码、头文件、ROS launch与msg定义、yaml参数、xacro与dae模型、rviz配置及shell脚本等覆盖从底层驱动到上层节点的完整链路。已有101人学习下载。读者可据此复现雷达驱动、串口通信、PID调试与RealSense接入等关键模块也可在现有框架上扩展功能或用于课程实践与竞赛项目是学习物联网设备开发与控制算法实现的完整实践案例。1. 从一堆散件到能跑 ROS 的小车这套树莓派4B加STM32的架构到底解决什么问题很多人第一次做智能小车都是拿一块 Arduino 或者 STM32 直接裸机写while(1)电机转、超声波测距、红外循迹全塞在一个主循环里。跑起来没问题但一旦想加激光雷达建图、想跑 ROS 的导航栈、想用 RViz 看实时位姿代码立刻变成一团乱麻。这套「树莓派4B STM32 ROS」的架构本质上是把决策层和执行层拆开树莓派4B跑 Ubuntu 和 ROS负责建图、定位、路径规划、视觉处理这些吃算力的活STM32 负责电机 PWM、编码器读数、IMU 采样、超声波测距这些硬实时任务。两者通过串口按固定协议通信。这样分工之后树莓派不用被电机中断打断STM32 也不用去理解代价地图是什么。适合谁适合已经会点 C 语言、想从「玩具小车」跨到「能跑 ROS 导航的移动平台」的嵌入式开发者也适合工创赛、智能物流小车这类需要完整感知-决策-控制链路的比赛场景。下面按硬件选型、通信协议、ROS 端节点、STM32 端固件、联调排坑、进阶验证的顺序把这条路走一遍。2. 硬件选型与上下位机分工树莓派4B 和 STM32 各自该扛什么2.1 为什么不是树莓派直接控电机树莓派4B 的 GPIO 能出 PWM但 Linux 不是硬实时系统。你写个 Python 脚本用RPi.GPIO输出 PWM系统一调度、一跑 ROS 节点、一读摄像头PWM 波形就抖。电机表现为低速时一顿一顿编码器计数和实际转速对不上PID 根本调不稳。更麻烦的是树莓派 GPIO 只有 3.3V 逻辑、驱动能力弱直接接电机驱动模块的使能脚遇到干扰容易误触发。常见做法是树莓派只发「目标速度」这种高层指令STM32 用硬件定时器产生 PWM用定时器编码器模式读正交编码器用 ADC 或 I2C 读 IMU。STM32F103ZET6 这类芯片有足够多的定时器和串口跑 FreeRTOS 之后任务优先级清晰电机控制周期能稳定在 1ms 甚至更快。分工边界可以这样划任务执行方理由激光雷达数据采集树莓派4B需要 USB 带宽和 ROS 驱动摄像头图像处理树莓派4B算力集中方便跑视觉节点全局/局部路径规划树莓派4BROS 导航栈依赖电机 PWM 输出STM32硬实时定时器资源编码器测速STM32定时器编码器模式不占 CPUIMU 姿态解算STM32高频采样滤波在本地做超声波/红外避障STM32中断响应快不依赖 Linux电池电压监测STM32ADC 直接读上报给上位机2.2 树莓派4B 引脚图里真正要用的几根线树莓派4B 的 40Pin 引脚图网上很多但在这套架构里树莓派和 STM32 之间通常只走串口不直接走 GPIO 控制电机。需要关注的引脚GPIO14 (TXD) / GPIO15 (RXD)接 STM32 的 USART RX/TX做上下位机通信。注意树莓派是 3.3V 电平STM32 也是 3.3V可以直接连但最好串 100Ω 电阻限流。5V 和 GND如果 STM32 板子从树莓派取电要算好电流。树莓派4B 的 5V 引脚能出的电流有限电机驱动最好单独供电共地即可。GPIO2/GPIO3 (I2C)如果 IMU 接在树莓派上而不是 STM32 上用这两根。但更推荐 IMU 接 STM32减少树莓派 I2C 总线被其他传感器抢占的概率。提示树莓派4B 的串口默认被蓝牙占用需要在/boot/config.txt里加dtoverlaydisable-bt否则/dev/ttyAMA0不可用。这一步很多人翻车表现为串口能打开但收不到数据。2.3 STM32 端外设分配与最小系统以 STM32F103ZET6 为例一个能跑的分配方案TIM1 CH1/CH2左电机 PWM 和方向控制TIM1 CH3/CH4右电机 PWM 和方向控制TIM2 编码器模式左编码器 A/B 相TIM3 编码器模式右编码器 A/B 相USART1接树莓派波特率 115200 或 460800USART2调试串口接 USB-TTL 看日志I2C1接 MPU6050 或 ICM20602ADC1 IN0电池电压分压检测TIM41ms 控制周期中断跑速度环 PID如果用的是五线四相步进电机STM32 端要用定时器输出四路相位或者用步进驱动器接脉冲方向。步进电机在 ROS 小车里不如直流编码电机常见因为步进低速力矩大但高速扭矩掉得快而且 ROS 导航需要的是连续速度控制步进更适合定点场景。2.4 供电与干扰最容易被低估的环节电机启动瞬间电流能到几安培如果和树莓派共用一路 5V树莓派会欠压重启。血泪经验是电机驱动用单独的 2S 或 3S 锂电池经降压模块供电树莓派用另一路 5V 3A 以上的降压模块两路共地但不共电源正极。STM32 可以从树莓派取 3.3V也可以单独从电机电池经降压取 5V 再转 3.3V。编码器线、电机线、串口线要分开走电机线尽量双绞。如果串口通信偶尔丢包先查是不是电机线干扰了串口线而不是急着改协议。3. 上下位机通信协议把速度指令和里程计数据说清楚3.1 帧格式设计定长还是变长上下位机通信最怕两件事粘包和丢帧。定长帧处理简单变长帧灵活。对于小车我一般用定长帧 帧头帧尾 校验帧头(2B) | 长度(1B) | 类型(1B) | 数据(N B) | 校验(1B) | 帧尾(2B) 0xAA 0x55 | N2 | 0x01 | ... | XOR | 0x0D 0x0A类型 0x01 是上位机发给下位机的速度指令数据区 4 字节int16 左轮速度 int16 右轮速度单位 mm/s。类型 0x02 是下位机上报的里程计数据区 8 字节int32 左编码器累计 int32 右编码器累计。类型 0x03 是 IMU 数据类型 0x04 是电池电压和超声波。校验用异或就够简单快。如果串口环境差可以上 CRC8。3.2 STM32 端解析代码// 串口接收状态机放在 USART1 中断或 DMA 空闲中断里 typedef enum { WAIT_HEAD1, WAIT_HEAD2, GET_LEN, GET_TYPE, GET_DATA, GET_CHECK, WAIT_TAIL1, WAIT_TAIL2 } RxState; RxState rx_state WAIT_HEAD1; uint8_t rx_buf[32]; uint8_t data_len 0, data_idx 0; uint8_t calc_check 0; void parse_byte(uint8_t b) { switch (rx_state) { case WAIT_HEAD1: if (b 0xAA) rx_state WAIT_HEAD2; break; case WAIT_HEAD2: rx_state (b 0x55) ? GET_LEN : WAIT_HEAD1; break; case GET_LEN: data_len b - 2; // 减去类型和校验 calc_check b; data_idx 0; rx_state GET_TYPE; break; case GET_TYPE: rx_buf[0] b; // 类型存第一个 calc_check ^ b; rx_state GET_DATA; break; case GET_DATA: rx_buf[1 data_idx] b; calc_check ^ b; if (data_idx data_len) rx_state GET_CHECK; break; case GET_CHECK: if (b calc_check) { handle_frame(rx_buf); // 校验通过处理 } rx_state WAIT_TAIL1; break; case WAIT_TAIL1: rx_state (b 0x0D) ? WAIT_TAIL2 : WAIT_HEAD1; break; case WAIT_TAIL2: rx_state (b 0x0A) ? WAIT_HEAD1 : WAIT_HEAD1; break; } }这段状态机逐字节推进不依赖超时粘包也能正确切分。data_len是数据区长度calc_check从长度字节开始异或和发送端保持一致。handle_frame里根据rx_buf[0]的类型分发0x01 解析左右轮目标速度写入全局变量供 PID 使用。3.3 树莓派端 ROS 串口节点树莓派上用 Python 写一个 ROS 节点订阅/cmd_vel把Twist转成左右轮速度打包发送同时读串口把编码器累计值转成nav_msgs/Odometry发布。#!/usr/bin/env python3 import rospy, serial, struct from geometry_msgs.msg import Twist from nav_msgs.msg import Odometry from tf.transformations import quaternion_from_euler WHEEL_BASE 0.18 # 两轮间距米 TICKS_PER_METER 1200 # 编码器每米脉冲数实测标定 ser serial.Serial(/dev/ttyAMA0, 115200, timeout0.01) def cmd_vel_cb(msg): v msg.linear.x w msg.angular.z v_left v - w * WHEEL_BASE / 2.0 v_right v w * WHEEL_BASE / 2.0 # 转成 mm/s 的 int16 l int(v_left * 1000) r int(v_right * 1000) frame struct.pack(BBBBhhB, 0xAA, 0x55, 6, 0x01, l, r, 0) # 校验长度^类型^数据各字节 check 6 ^ 0x01 for byte in struct.pack(hh, l, r): check ^ byte frame frame[:-1] bytes([check]) b\x0D\x0A ser.write(frame) def read_serial(): buf b while not rospy.is_shutdown(): buf ser.read(64) while len(buf) 9: if buf[0] ! 0xAA or buf[1] ! 0x55: buf buf[1:] continue length buf[2] if len(buf) length 5: break frame buf[:length 5] buf buf[length 5:] check 0 for b in frame[2:length 3]: check ^ b if check ! frame[length 3]: continue if frame[3] 0x02: left, right struct.unpack(ii, frame[4:12]) publish_odom(left, right) def publish_odom(left, right): odom Odometry() odom.header.stamp rospy.Time.now() odom.header.frame_id odom odom.child_frame_id base_link odom.pose.pose.position.x (left right) / 2.0 / TICKS_PER_METER odom.pose.pose.position.y 0 odom.twist.twist.linear.x (left right) / 2.0 / TICKS_PER_METER pub.publish(odom) rospy.init_node(base_serial) pub rospy.Publisher(/odom, Odometry, queue_size10) rospy.Subscriber(/cmd_vel, Twist, cmd_vel_cb) read_serial()WHEEL_BASE和TICKS_PER_METER必须实测标定不能抄。标定方法让小车直线走 1 米看编码器累计值反推TICKS_PER_METER原地转 360 度看左右轮差反推WHEEL_BASE。这两个参数不准后面导航建图会歪。3.4 波特率和实时性取舍115200 波特率下一帧 9 字节约 0.78ms1ms 控制周期够用。如果编码器线数高、上报频率要 100Hz 以上建议提到 460800 或 921600。但波特率越高线材和干扰影响越大。STM32 端用 DMA 接收 空闲中断树莓派端用serial.read非阻塞读避免阻塞 ROS 回调。4. ROS 端建图导航与 STM32 固件联调从能收到数据到能跑起来4.1 树莓派4B 上 ROS 环境怎么装才不折腾树莓派4B 跑 Ubuntu 20.04 ROS Noetic 或 Ubuntu 22.04 ROS2 Humble 都行。如果走 ROS1鱼香 ROS 一键安装确实省事但要注意它默认装的是桌面版还是基础版。树莓派上建议装ros-noetic-ros-base加需要的包不要装完整桌面版SD 卡空间和内存都吃紧。装完 ROS 后串口权限要处理sudo usermod -aG dialout $USER然后重新登录。否则每次都要sudo跑节点ROS 参数服务器会出权限问题。4.2 激光雷达和底盘 TF 树怎么配一个典型的 TF 树map - odom - base_link - lasermap - odom由 AMCL 或 SLAM 提供odom - base_link由底盘里程计提供base_link - laser是静态 TF用static_transform_publisher发布。很多人建图时地图重影就是odom - base_link的发布频率太低或者里程计跳变。里程计发布频率建议 30-50Hz和串口上报频率一致。如果 STM32 每 10ms 上报一次树莓派就每 10ms 发一次odom不要攒着一起发。4.3 STM32 端速度环 PID 实现// 1ms 定时器中断里调用 typedef struct { float kp, ki, kd; float integral; float prev_error; float output_max; } PID; float pid_calc(PID *pid, float target, float actual) { float error target - actual; pid-integral error * 0.001f; // dt 1ms // 积分限幅防止饱和 if (pid-integral 1000) pid-integral 1000; if (pid-integral -1000) pid-integral -1000; float derivative (error - pid-prev_error) / 0.001f; pid-prev_error error; float out pid-kp * error pid-ki * pid-integral pid-kd * derivative; if (out pid-output_max) out pid-output_max; if (out -pid-output_max) out -pid-output_max; return out; }kp先给 0.5 左右ki给 0.1kd先给 0。调的时候先只加kp让电机能响应但不振荡再加ki消除稳态误差kd最后加抑制超调。output_max对应 PWM 满占空比。积分限幅是必须的否则电机堵转时积分会积到天上松手瞬间猛冲。4.4 联调顺序不要一上来就跑导航正确的联调顺序STM32 单独跑用调试串口打印编码器值和 PWM 输出手动给目标速度看电机转不转、编码器数对不对。树莓派单独跑串口节点用rostopic pub /cmd_vel发速度看 STM32 是否收到、电机是否响应。树莓派订阅/odom在 RViz 里看小车位姿是否随手动推车变化。跑robot_pose_ekf或ekf_localization融合编码器和 IMU。最后才跑gmapping或cartographer建图再跑move_base导航。跳过第 3 步直接建图大概率地图是糊的回头查更费时间。4.5 多节点发布 cmd_vel 时底盘节点怎么取舍热词里有人问「ROS 多个节点发布移动指令话题时底盘节点如何取舍」。常见场景是遥控节点、导航节点、避障节点都在发/cmd_vel。做法有两种一是用mux节点按优先级选一路比如避障最高、导航次之、遥控最低二是底盘节点订阅一个/cmd_vel_mux话题由twist_mux包管理。不要多个节点直接发同一个/cmd_vel否则底盘会收到交错指令表现为小车抽搐。5. 避坑与排查串口丢包、编码器跳变、导航画龙这些坑怎么填5.1 串口能打开但收不到数据现象树莓派上/dev/ttyAMA0能open但read一直空。原因树莓派4B 默认把主串口分配给蓝牙/dev/ttyAMA0实际是 mini UART 或者被蓝牙占用。解决在/boot/config.txt加dtoverlaydisable-bt重启后/dev/ttyAMA0才是真正的 PL011 串口。另外检查enable_uart1是否打开。5.2 编码器计数偶尔跳变现象小车直线走/odom的 x 偶尔往回跳。原因编码器 A/B 相线受电机线干扰或者 STM32 定时器编码器模式没开滤波。解决编码器线双绞并远离电机线STM32 的TIM_EncoderInterfaceConfig里把输入滤波ICFilter设成 0x0F软件上对编码器差值做限幅单周期变化超过阈值就丢弃。5.3 建图时地图重影、墙壁变厚现象RViz 里地图墙壁有两层。原因odom - base_link的 TF 时间戳和激光数据时间戳不同步或者里程计标定不准。解决检查TICKS_PER_METER和WHEEL_BASE用卷尺实测确认odom发布频率和激光频率匹配在gmapping里把odom_frame和base_frame设对。5.4 导航时小车画龙、原地打转现象move_base发出速度后小车左右摆。原因局部代价地图参数太激进或者 PID 响应太慢。解决先调local_costmap的inflation_radius别设太大再降move_base的max_vel_theta最后查 STM32 速度环 PIDkp太大也会摆。用rqt_plot同时看/cmd_vel和/odom的角速度能看出是规划问题还是控制问题。5.5 STM32 程序跑一段时间就死机现象小车跑几分钟后电机停转串口无响应。原因串口中断里处理太多逻辑或者数组越界。解决中断里只做收字节和状态机解析后的处理放到主循环检查rx_buf索引有没有可能超过 32看门狗打开但喂狗周期要大于最坏任务执行时间。6. 进阶验证用 rosbag 回放和阶跃响应判断底盘到底调好没有底盘调没调好不看它能不能动看两个东西阶跃响应和rosbag 回放一致性。阶跃响应测试在树莓派上发一个从 0 到 0.3m/s 的阶跃速度指令同时录/odom的twist.linear.x。理想曲线是快速上升、轻微超调、稳定在 0.3 附近。如果上升很慢kp太小如果超调大然后振荡kp太大或kd不够如果稳态到不了 0.3ki不够或者电机力矩不足。用rqt_plot把/cmd_vel/linear/x和/odom/twist/twist/linear/x画在一起一眼能看出来。rosbag 回放一致性录一段手动遥控的/cmd_vel和/odom然后写个脚本把/cmd_vel按时间戳重新发一遍对比回放时/odom和原始/odom的偏差。如果偏差随时间累积越来越大说明里程计标定有问题如果偏差是随机的说明串口丢包或编码器干扰。# 录一段包含 cmd_vel 和 odom 的 bag rosbag record -O chassis_test.bag /cmd_vel /odom # 回放时只发 cmd_vel观察 odom rosbag play chassis_test.bag --topics /cmd_vel # 用 python 对比两条 odom 曲线 python3 compare_odom.py original.bag replay.bagcompare_odom.py的思路读两个 bag 的/odom按时间戳对齐算 x、y、yaw 的累计误差。如果 10 秒内 x 方向误差超过 5cm就得回去查TICKS_PER_METER。还有一个技巧在 STM32 端加一个「里程计原始值」上报不要只上报融合后的位姿。这样出问题时能分清是编码器读数错还是融合算法错。我一般会在协议里留一个调试帧把左右编码器原始计数、PID 输出、电池电压一起打包用调试串口打到电脑上看波形。这个习惯帮我省了很多「猜」的时间。最后说个习惯每次改完 PID 参数或标定参数先录一段 bag 存着命名带日期和参数值。过一周回头看能知道哪组参数是有效的不用重新试。这套树莓派4B 加 STM32 的架构硬件成本不高但调通需要耐心尤其是串口和里程计这两块。把这两块做扎实上面跑 SLAM 和导航就是水到渠成的事。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

AI投研工具的核心竞争力:不在模型数量,而在数据与框架

AI投研工具的核心竞争力:不在模型数量,而在数据与框架

去年下半年我深度参与了一个AI投研工具从0到1的搭建过程,当时碰到的第一个高频问题就是:“你们接了多少个大模型?”——问的人里有产品经理、有投资人,甚至内部研发也有这种倾向。后来工具上线跑了三个月,真实用户反馈…

📅 2026/10/28 7:24:33
GeoXCP:空间解释不确定性量化框架解析与应用

GeoXCP:空间解释不确定性量化框架解析与应用

1. GeoXCP项目概述GeoXCP是一个专注于解决可解释人工智能(XAI)中空间解释不确定性量化问题的创新框架。在医疗影像分析、自动驾驶、遥感监测等空间数据密集型领域,AI模型的决策过程往往需要结合地理空间特征进行解释。传统XAI方法虽然能生成热力图等可视化解释&…

📅 2026/10/28 7:24:33
Stable Diffusion 3.0企业级应用与品牌视觉模型实践

Stable Diffusion 3.0企业级应用与品牌视觉模型实践

1. Stable Diffusion 3.0与企业品牌视觉模型的融合机遇当Midjourney还在为个人创作者提供艺术灵感时,Stable Diffusion 3.0已经悄然打开了企业级市场的大门。作为首个支持企业私有化部署的开源扩散模型,其最大的突破在于允许企业用自有品牌资产训练专属视…

📅 2026/10/28 7:24:33
MORE NEWS

更多资讯

📰

神经网络核心原理与工程实践指南

1. 人工神经网络的前世今生第一次接触神经网络是在2012年的ImageNet竞赛上,当时AlexNet以压倒性优势夺冠的场景至今记忆犹新。那是我第一次意识到,这个起源于1943年McCulloch-Pitts神经元模型的古老概念,经过几十年的蛰伏后终于迎来了爆发。神…

📰

高通QCN文件解析与基带级IMEI/MEID修改技术指南

1. 项目概述:这不是刷机,是基带层的“外科手术”“安卓玩机工具推荐——资深安卓玩家修改基带qcn工具 修改串码 meid 序列号等操作解析”,这个标题里藏着安卓系统最硬核、也最容易被误解的一类操作。它不涉及Root权限的获取,不依赖…

📰

2026年前端NPM实战指南:从配置到企业级应用

1. 为什么前端开发者必须精通NPM?作为一名从2015年开始接触前端开发的老兵,我见证了NPM从单纯的包管理工具成长为如今前端生态基石的全过程。2026年的今天,即使出现了PNPM、Yarn等替代方案,NPM仍然是每个前端工程师的必修课。最近…

📰

Mac Studio与5090跑大模型怎么选?推理、微调与显存带宽全解析

1. 先把问题问对:你跑的到底是哪种"大模型"很多人一上来就问"Mac Studio 和 5090 哪个强",这个问题本身就问偏了。这两台机器的差异不在"谁跑分高",而在于它们服务的是完全不同的使用场景。我见过太多人花三万…

📰

CPU存储管理:虚拟内存与TLB优化实践

1. 为什么需要CPU存储管理?现代计算机系统中,CPU的处理速度与内存访问速度之间存在巨大差距。以典型的3GHz CPU为例,每个时钟周期仅0.33纳秒,而访问一次主存可能需要100纳秒以上。这种速度差异达到300倍之多,这就是著名…

📰

基于ABCD分块矩阵的Mie散射MATLAB实现:稳定求解大粒径粒子

简介:一份面向光学与大气科学领域研究者的Matlab资源包,围绕Mie散射理论提供计算系数an、bn的m脚本,并以txt说明梳理理论要点与参数设置。压缩包共两个文件,m文件为可直接调用的函数实现,txt文件对理论背景、散射系数定…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬