简介:面向无人船自主导航与编队控制场景,提供完整的STM32嵌入式工程参考,内容覆盖电机舵机控制、GPS/IMU定位、ZigBee无线通信及单片机固件设计等关键环节。压缩包共588个文件、约16.63MB,以C源码(.c/.h)、编译输出文件(.o/.d/.crf)和Keil工程配置(.uvprojx/.uvoptx)为主,并附带链接映射表(.map)与十六进制烧录文件(.hex),可支撑代码研读、重新编译及板级烧录。已有3301人学习下载,适合嵌入式、机器人及物联网方向学生或工程师深入掌握无人船底层控制逻辑。借助源码能够理解PID调速、舵机PWM输出、GPS坐标解算、IMU姿态更新与卡尔曼滤波融合等实现细节,ZigBee部分则展示了多船间低功耗组网通信框架。整体目录结构清晰,文件分类明确,既可作为课程设计、毕业设计的基础代码,也能为无人船编队等工程项目的二次开发提供直接参考。 刷到这篇的朋友,大概率手里也有一条“半成品”无人船:船壳、电机、遥控器都齐了,但船上的控制器还得自己写;或者你正在做控制类课程设计、毕业设计,想找一个从零到跑通的参照系。我最近刚好把一套无人船控制系统(部分)完整地过了一遍——所谓“部分”,是因为整套系统还包括岸基上位机、云平台、图传这些外围,这次只聊船上的核心控制回路:主控逻辑、感知融合、动力执行和航向闭环。别小看“部分”这两个字,恰恰是它决定了一条船能不能稳定地、按预期地跑起来。
说实话,刚开始我也有点没底。以前做温度控制系统、步进电机控制那套思路,在这里只能算基本功。无人船最大的区别在于:被控对象在水里,模型不确定、干扰大、通信还会掉链子。这篇不讲那种堆模块的清单式教程,我会把每一步的关键判断和依据讲清楚——为什么选这个芯片、为什么参数要设死区、为什么必须先仿真再下水。适合三类人看:准备做无人船课设/毕设的学生、想把手头遥控船改成自主航行的爱好者、以及刚接触船用控制系统的嵌入式工程师。
1. 下手前先摸清无人船控制系统的模块边界
很多人拿到一个“控制系统”项目就急着写代码,结果越写越乱。正确的做法是先站在船体角度看全局:哪些归我管、哪些不归我管、接口在哪。
1.1 一套完整系统通常包含什么
抛开具体型号,一条能自主航行的无人船,从上到下可以拆成四层:
- 决策层:跑路径规划、任务逻辑,通常由高性能处理器或岸基计算机承担。
- 控制层:航向控制、速度控制、状态机切换,这是“控制系统”的核心。
- 感知层:姿态传感器(IMU)、磁罗盘、GPS/北斗定位、避障雷达或视觉。
- 执行层:无刷电调、推进电机、舵机或者差速转向机构。
我这次接手的“部分”,正好是中间两层加执行层的接缝:控制层到感知层的数据读取、控制层到执行层的驱动输出、以及控制层自身的闭环算法。决策层的复杂任务逻辑不在这次范围内,但我要留好接口,比如接收目标航向的串口协议。
提示:做项目前一定先把“我的边界”写出来。哪些模块别人做、哪些自己做、通信协议是什么,否则联调阶段就是无穷无尽的扯皮。
1.2 控制部分的核心子模块清单
确定边界之后,我把控制部分拆成下面几个子模块,每个子模块都是能独立测试的最小单元:
- 主控制器最小系统:MCU、时钟、调试接口、看门狗。
- 电源变换单元:电池电压转多路稳定电源。
- 姿态与航向解算:IMU数据采集、滤波、姿态角输出。
- 定位与速度估计:GNSS数据解析、经纬度转局部坐标。
- 遥控与自主切换逻辑:遥控优先、失联保护。
- 双电机差速驱动与PWM输出。
- 航向PID闭环控制器。
每完成一个模块,先在桌面或测试架上验证,再往船体上装。这个习惯帮我省了大量的排查时间,因为一旦出现问题,你能确定是哪个模块的事,而不至于全船拆开找毛病。
1.3 为什么先从“倒推接口”开始
我踩过最深的坑是:算法写完了,才发现执行机构响应不过来;或者传感器数据流没考虑时序,导致控制周期抖动。所以在写第一行业务代码之前,我用一张表格把接口定义清楚:
| 数据流方向 | 接口 | 速率/频率 | 协议 |
|---|---|---|---|
| IMU → MCU | I2C/SPI | 100Hz~200Hz | 寄存器读取 |
| 磁罗盘 → MCU | I2C | 50Hz | 寄存器读取 |
| GPS → MCU | UART | 5Hz~10Hz | NMEA-0183 |
| 遥控接收机 → MCU | PPM/PWM | 50Hz | 脉宽捕捉 |
| MCU → 电调 | PWM | 50Hz~400Hz | 脉宽输出 |
| MCU → 岸基 | 无线串口/数传 | 10Hz | 自定义帧 |
先定频率和协议,后面写代码时,每个外设驱动都是独立小文件,主循环只做同步和调度。这条经验在我后来做快速原型时非常管用。
2. 主控和动力选型:先把执行层钉死,算法才有意义
控制算法跑得再好,执行机构跟不上,一切都是纸上谈兵。所以我在选型上花的时间比写算法还多。
2.1 主控选型:我为什么没选树莓派
对于“控制系统”这种对实时性有硬要求的任务,我优先考虑的是:中断响应确定、外设丰富、功耗低、能长期稳定运行。市面上常见的方案对比下来:
| 方案 | 实时性 | 外设资源 | 功耗 | 上手难度 | 适用场景 |
|---|---|---|---|---|---|
| STM32F103 | 强 | 够用 | 低 | 低 | 基础航向控制 |
| STM32F407 | 强 | 丰富,带FPU | 低 | 低 | 带传感器融合+控制 |
| ESP32 | 中等 | WiFi/蓝牙方便 | 中 | 低 | 需要无线调试,但实时性一般 |
| 树莓派Zero 2W | 弱 | 强,跑Linux | 高 | 中 | 视觉、路径规划等上层任务 |
我最终选了STM32F407。原因很简单:它在做姿态解算时可以用硬件浮点,跑200Hz的PID循环毫无压力;同时I2C、SPI、UART、定时器PWM输出应有尽有;而且工业级芯片比Linux板子皮实得多,野外水边环境里不用担心系统崩溃。
注意:不是说树莓派不能用,而是别让它干控制这种“硬实时”的活。常见的分工是:树莓派跑路径规划,把航向指令通过串口发给STM32,STM32做底层控制。这也是很多成熟无人船产品的架构。
2.2 双差速驱动:船为什么不需要转向舵
小型无人船很少用“舵+单桨”,因为低速状态下舵效极差,船根本拐不过弯来。我采用的是双电机差速转向:左右各一个无刷电机,通过改变两侧转速差实现转向。
差速控制的好处有两个:
- 低速机动性极好,可以在水面原地掉头。
- 执行逻辑简单,控制量直接映射到左右PWM占空比。
具体到执行部件,无刷电调(ESC)用的是PWM控制。大多数船用电调支持50Hz~400Hz的PWM频率,我最终设为50Hz,脉宽范围1000us~2000us,中值1500us对应电机停止。这个设定和常见RC遥控器一致,后面做遥控切换特别方便。
这里有一个关键细节:死区处理。由于电调和电机的机械特性,PWM在1500us附近有一段区域,电机并不转。如果不处理这段死区,PID输出的小修正量会被执行机构完全吞掉,表现出来就是“船总往一边偏,打了方向没反应”。我在代码里加了如下逻辑:
// 死区补偿,输入为航向修正量 [-200, 200],单位us脉宽修正 int32_t deadzone = 15; int32_t left_pwm = 1500 + left_cmd + rudder_cmd; int32_t right_pwm = 1500 + right_cmd - rudder_cmd; if (left_pwm > 1500 - deadzone && left_pwm < 1500 + deadzone) { left_pwm = 1500; } if (right_pwm > 1500 - deadzone && right_pwm < 1500 + deadzone) { right_pwm = 1500; }2.3 电源树:最容易忽略的隐性坑
船用的动力电池动辄3S、4S锂电池(11.1V/14.8V),而MCU、传感器需要3.3V或5V。如果直接用一个DC-DC统一降压,电机启动瞬间的大电流会把电压拉垮,MCU直接复位。这是很多人第一次下水遇到“一推油门就重启”的元凶。
我的电源方案是分级供电:
| 电压轨 | 来源 | 负载 |
|---|---|---|
| 11.1V(电池直供) | 3S锂电池 | 无刷电调、电机 |
| 5V | 独立BEC降压模块 | 接收机、GPS、数传 |
| 3.3V | MCU板载LDO | 主控、IMU、磁罗盘 |
注意5V这一路必须和动力电隔离,至少也要用独立的DC-DC,不要跟电调的BEC共用。我在实际测试中发现,电调BEC在电机急加速时纹波很大,会直接影响GPS和罗盘的供电质量,导致定位跳变、航向漂移。把5V独立出来之后,问题明显改善。
3. 感知层数据链:姿态、航向与避障的工程实现
控制系统闭环的第一步,是知道自己“现在朝哪、状态如何”。感知层做不好,后面算法再高级也没用。
3.1 IMU姿态解算:从MPU6050到航向角
姿态解算我用的是MPU6050六轴(三轴加速度计+三轴陀螺仪),在常规应用中已经完全够用。核心是把陀螺仪积分出来的姿态和加速度计测出来的重力方向做融合,消除积分漂移。
初学者最容易做错的一步是:直接拿原始加速度计数据算角度,不做滤波。船在水面上晃来晃去,加速度计里混着船体运动的线加速度,算出来的角度全是噪声。我用了互补滤波,原理非常直观:
- 角度短期变化看陀螺仪积分(动态响应快,但会漂移)。
- 角度长期稳定靠加速度计修正(静态准,但噪声大)。
- 两者按权重相加,代码就几行:
// 互补滤波,dt为采样周期,alpha为权重系数 angle = alpha * (angle + gyro_rate * dt) + (1.0f - alpha) * accel_angle;alpha典型值取0.95~0.98,表示更信任陀螺仪的短期积分,加速度计只是慢慢拉回长期零漂。对于磁罗盘航向,也可以用同样的思路做一阶互补,或者用Mahony算法把磁力计、加速度计、陀螺仪一并解算成欧拉角。建议有精力的话直接上Mahony,代码网上很多,测试下来比单独互补滤波更稳。
磁航向这里有一个180度跳变问题,后面PID部分会专门讲。
3.2 GPS与磁罗盘的配合使用
GPS在无人船上的作用有两个:定位和测速。测速比定位更可靠,因为GPS的速度是直接由多普勒频移解算的,不受定位漂移影响。
我用的模块是ATGM336H,输出NMEA-0183协议,5Hz刷新率,完全够用。解析GNRMC帧就能同时拿到经纬度、对地速度和航迹向。这里要提醒一个点:GPS航迹向(COG)只有在船有速度时才有意义。船静止或者速度低于0.2m/s时,航迹向会乱跳,这时候航向控制应该继续用磁罗盘而不是GPS。
磁罗盘我用的是QMC5883L,安装在船体中心线、远离电机的位置。安装位置一旦挪动,必须重新做硬磁校准:让船在原地转两圈,采集最大最小磁场值,得到偏移量写入代码。不做这个校准,航向角可能会偏20度以上,直接影响控制效果。
经验:磁罗盘和电机之间至少保持15cm以上距离。我第一版把罗盘装在电机边上,结果转数一高,航向角跟着转,完全没法用。后来把罗盘移到船头方向的一个高支架上,才稳定下来。
3.3 避障与传感器安装位置
避障这次我只做了一层轻量级方案:船头装一个超声波测距模块测前向障碍物,距离小于设定值就减速停车或转入避让状态。水面环境超声波衰减大、多径干扰明显,实测有效距离比标称短很多,我把阈值设成2米,只做紧急刹车用。
传感器安装的位置,我总结出三条硬性规则:
- IMU尽量靠近船体重心,减小转动时的离心效应。
- GPS天线尽量高,减少水面多径反射。
- 磁罗盘远离铁磁物质和动态磁场源(电机、大电流导线)。
线缆走线也要讲究:PWM、I2C信号线用双绞线或屏蔽线,和动力线分开走,避免耦合噪声。这个问题在实验台上不容易暴露,到了船上空间狭小、线束捆在一起时特别明显。
4. 航向PID:从数字仿真到水面实测的完整调参路径
这一节是整个控制系统的灵魂。没有闭环控制的无人船,本质上就是个遥控船;有了航向闭环,才算真正“无人”。
4.1 数字仿真先行:船上跑的不是玄学
很多课设项目最缺的一步,就是控制系统的数字仿真。直接在真船上调PID,不仅效率低,还有撞船风险。我的做法是用MATLAB/Simulink先搭一个简化的船舶运动模型:一阶惯性环节描述船的转向响应,加一个纯延迟项描述电机和水的响应滞后。
被控对象传递函数近似为:
psi_dot / rudder_cmd = K / (T * s + 1)对于一条1米左右的小型无人船,实测响应特性大概是:K=1.2~2.0(°/s每单位PWM修正),T=0.5~1.5秒。不同船长、船型差异很大,建议下水前先做几个阶跃响应实验,把K和T大致估出来。
仿真模型的好处是:你能在几分钟内几百次地试参数,看超调、看稳态误差、看抗干扰能力。我最终在Simulink里把参数范围缩到很小,下水只微调了三次就稳了,大大减少了下水次数和风险。
4.2 航向PID的关键细节
航向控制和普通温度控制的本质区别在于:被控量是周期性的角度,目标航向和当前航向的差必须做归一化,直接相减会出大问题。
比如目标航向是10度,当前航向是350度,直观上的误差应该是20度(朝前转一点就到),但如果代码直接算“10 - 350”,得到的是-340度,PID会觉得误差巨大,疯狂朝反方向打。归一化处理很简单:
// 航向误差归一化到 [-180, 180] 度区间 float wrap_180(float err) { while (err > 180.0f) err -= 360.0f; while (err < -180.0f) err += 360.0f; return err; }另一个容易被忽视的是积分限幅与抗积分饱和。船在水中阻力大,期望航向和目标偏差长时间存在时,积分项会一直累积,导致输出饱和,等船转到目标方向后,积分项还要“放”很久,产生明显超调。我的做法是积分项限幅,通常限制在最大输出量的20%以内;同时当输出饱和时暂停积分累加,这就是抗积分饱和。
控制周期我固定为20ms(50Hz),和传感器数据同步。别小看这个周期选择——周期短了,电机和船体的响应跟不上,反而放大噪声;周期长了,控制滞后明显,船会走“S”形航线。
4.3 参数整定顺序与实测数据
PID参数整定,我的顺序是先P、再I、最后D,每一步都在仿真里验证过之后再上船。
- 只加P:把P从小到大试,找临界振荡点。船开始等幅摆动时,记下Pc和振荡周期Tc,然后取P=0.5Pc作为初始值。
- 加I:消除静差。水面对船的阻力不均匀,或者双电机转速有差异时,只靠P会有固定的稳态误差,I就是用来慢慢消除这个偏差。
- 加D:抑制超调。水面波动带来的高频干扰很多,D太大会把噪声放大成剧烈抖舵,所以D通常要给一点低通滤波或者直接取很小值。
我这条船的最终参数范围供参考:P在1.5~2.5之间,I在0.05~0.15之间,D在0.2~0.5之间(控制输出映射到PWM修正量单位)。实测在有风浪的水面,航向稳定在±5度以内,直线航行没有明显的“S形”摆动。
提示:千万记住,船在水里不像电机在台架上那么“听话”。风、浪、流都是随机干扰,追求无超调往往比追求快速更实际。我最后故意把P调得偏保守,换来了整个航行过程的平滑。
5. 联调阶段最容易翻车的几个环节和我的防护方案
从仿真到实船,中间隔着一个“现实”。以下这些问题,我每一条都真实踩过,写出来算是替你省钱省时间。
5.1 供电与地线噪声:实测中最隐蔽的元凶
第一章提过动力电和逻辑电要分开,这里再补充一个实战现象:即使分开了,如果地线共地处理不好,噪声照样会串进传感器。我第一版把所有模块的地线直接接到电池负极,结果GPS定位偶发跳变,IMU数据也有毛刺。后来改成星型接地——所有逻辑模块的地线单独拉到电源模块的同一个接地点,而不是“手拉手”串联接地,噪声明显下降。
还有一个我没想到的情况:电机启动瞬间,电池电压会被拉低几百毫伏,这时候如果MCU的ADC参考电压不稳,磁罗盘的读数就会跳。解决方法是:传感器供电不用MCU板载LDO,而是用一个精度更高的3.3V LDO独立供电;并且在软件里对磁罗盘数据做中值滤波,把单点毛刺削掉。
5.2 通信丢包与失控保护:任何时候都要有手动兜底
水面测试时,2.4G无线数传很容易因为距离远、水面反射、天线遮挡而丢包。我的设计原则是:控制指令永不超时。
具体实现是一个状态机:
- 状态A:遥控模式。接收机PPM信号正常,操作员直接控制。
- 状态B:自动模式。数传每200ms收到岸基指令,执行航向闭环。
- 状态C:失联保护。连续1秒没收到有效指令,自动切换到“停船”状态,电机归零;再持续3秒,启动返航或原地待命。
代码里用看门狗和软件计时器双重保障。核心逻辑很简单:
if (time_now - last_cmd_time > 1000) { // 1秒未收到指令,进入安全模式,执行机构归零 left_motor_stop(); right_motor_stop(); mode = SAFE_MODE; }遥控优先级最高,任何时候只要遥控信号有效,立刻接管控制权。这是安全底线,千万别省。
5.3 下水前的测试清单:系留、低速、急停
我每次下水前都会走一遍固定的检查流程,宁可在岸上多花十分钟,也绝不在水里捞船:
- 第一步,系留测试:把船用绳子拴在岸边固定物上,先低速推油门,确认动力方向正确、控制器能正常响应。
- 第二步,传感器验证:拿起船体左右晃动,在岸基软件上看姿态角是否跟随,磁罗盘方向是否合理。
- 第三步,失控保护模拟:直接在岸上关掉遥控器或拔掉数传,确认船在1秒内停车。
- 第四步,开阔水面低速试航:先用手动模式跑一圈,确认直线性;再切自动模式,从低速短距离开始。
这套流程跑下来,基本能排除90%以上的初期问题。另外,随身带一根打捞杆或者救生绳,谁用谁知道。
最后再说两句
做完这套无人船控制系统(部分),我最大的体会是:控制系统的工程量不在算法本身,而在于把传感器、执行器、电源、通信这些“脏活累活”理顺。很多时候你觉得是控制效果不好,追到底其实是某个数据噪声没滤干净、某个线缆干扰没排除。
如果你打算从零复刻这套系统,我建议第一个版本别贪多:先用手动遥控模式把船跑顺,再加入自动航向保持,最后才考虑路径规划。每加一层功能,都要把上一个功能够稳定作为前提。
最后再分享一个小技巧:所有传感器数据和控制输出,统一打成带时间戳的串口日志。水面测试回来后,把日志导出来和视频对应着分析,很多“玄学问题”的答案其实就藏在时间轴里。愿你的船早日下水,别像我一样第一次把船开进芦苇荡里捞了半小时。
本文还有配套的精品资源,点击获取