基于STM32F103C8T6与NRF24L01的船模遥控系统设计与实战 📅 发布时间:2026/9/3 18:56:44 👁 浏览次数: 简介这是一套基于STM32F103C8T6最小系统板与nRF24L01无线模块的船模设计比赛项目源码适合电子竞赛、嵌入式入门及船模爱好者学习参考。工程以标准外设库编写涉及PWM电机调速、ADC数据采集、无线遥控通信等关键环节并涵盖定时器、RCC、I2C等底层外设初始化代码代码结构清晰可直接用Keil打开编译便于在此基础上修改和创新。压缩包共238个文件约6.38MB其中包含54个H头文件、53个C源文件以及Keil工程配置、hex/axf固件和编译过程文件便于对照源码学习或直接烧录验证。目前已有1081人学习下载作者为e_xiaoan。同时资源描述中附带配套博客说明与更新链接可帮助读者更快理解整体设计思路和硬件接线但代码仅供交流参考实际使用时需结合自身船模结构、供电与机械部分调试。 三年前参加大学生船模设计比赛那次我的船下水不到半分钟就失联了遥控器按键按到冒汗船在水面上一动不动最后靠着捞杆把它勾回来成绩自然也没好看。后来我把整套遥控方案推倒重做换成 STM32F103C8T6 最小系统板加 NRF24L01 无线模块才真正把遥控稳定性和响应速度做住了。这篇文章就把这套 stm32f103c8t6nrf24l01船模设计比赛项目源码 的核心思路和关键代码拆开讲清楚包括硬件怎么选、管脚怎么分配、通信帧怎么设计、收发两端的程序怎么写以及我在实船调试中踩过的几个大坑。不管你是要备战船模比赛、自组遥控船还是单纯想在 STM32 上做一套无线遥控项目都值得花十分钟把这套方案过一遍。1. 硬件底座选择为什么这套组合能抗住比赛1.1 比赛场景下的需求拆解船模比赛最怕的不是做不快而是遥控失灵。比赛场景要求整条船在几十米范围内操控可靠转向指令延迟不能明显油门响应要跟手而且整套系统成本要压得住。拆开来看核心需求就三块一块能稳定采集中位信号的控制板一个能实时收发控制指令的无线通道以及一套能驱动电机和舵机动作的执行机构。当时我对比过 Arduino、51 单板机最后选 STM32F103C8T6理由是它的资源在同类板子里足够“溢出”72MHz 主频、64KB Flash、20KB SRAMADC、定时器、SPI、USART 全都有还有大量网络资料和现成例程兜底。最重要的蓝色药丸形态的最小系统板只要几块钱即便比赛中烧坏一块也不会心疼。现在市面上还有不少 GD32、APM32 这类国产兼容替代型号引脚基本一致采购也不慌。1.2 为什么要选 NRF24L01 而不是蓝牙或 WiFi无线方案的选型我纠结了很久。蓝牙方案手机配对麻烦比赛中快速掉线重连是致命伤WiFi 延迟和功耗都不占优而且组网过程徒增复杂度433MHz 遥控模块天线大比赛现场干扰源多。NRF24L01 的定位恰好卡在“低延迟、低成本、点对点”这个点上工作在 2.4GHz 频段有 125 个可选信道能现场避开拥挤的 WiFi 频段Enhanced ShockBurst 硬件协议在模块内部自动完成帧头、CRC 校验等打包工作MCU 只负责从 SPI 口搬运几个字节学习成本很低。实测在 1Mbps 速率下一发一收延迟基本在微秒到毫秒级比手机蓝牙方案跟手得多。整套无线链路也就十几块成本完全符合学生项目预算。提示这个项目用裸机加状态机就足够了不需要上 FreeRTOS也更不需要 LVGL 这类图形框架。遥控端最多加一块 OLED 显示参数就是很舒服的状态。2. 系统架构建法与管脚分配2.1 双端硬件清单整套系统拆成发射端遥控器和接收端船载两边各用一块 STM32F103C8T6 最小系统板和一块 NRF24L01 模块。发射端还用了两个 10K 电位器模拟摇杆分别控制油门和转向接收端则接 L9110S 电机驱动板、一个直流电机带螺旋桨以及一个 SG90 舵机负责船舵转向。电源方面发射端用锂电池通过 AMS1117-3.3 降压给板子和无线模块供电船载部分我特意把电机供电和逻辑供电分开电机直接吃电池电压STM32 和 NRF24L01 走独立的 3.3V 稳压避免电机大电流把数字电路拖垮。这个供电隔离在船上很重要后面调试章节会详细讲。2.2 管脚分配表发射端和接收端我把 NRF24L01 的接线保持完全一致这样调试时两块板子可以互换排查问题非常方便。具体的管脚分配如下功能发射端管脚接收端管脚NRF24L01 SCKPA5SPI1_SCKPA5SPI1_SCKNRF24L01 MISOPA6SPI1_MISOPA6SPI1_MISONRF24L01 MOSIPA7SPI1_MOSIPA7SPI1_MOSINRF24L01 CEPB0PB0NRF24L01 CSNPB1PB1NRF24L01 IRQPB10PB10油门摇杆PA0ADC1_CH0-转向摇杆PA1ADC1_CH1-校准按键PA2-电机 PWM-PA8TIM1_CH1电机方向-PA1舵机 PWM-PA0TIM2_CH1调试串口PA9/PA10PA9/PA10注意 PA8 是 TIM1 的通道 1PA0 是 TIM2 的通道 1。用这两个定时器输出 PWM正好覆盖了电机高频率调速和舵机 50Hz 标准周期控制两种需求。如果你手里只有其他型号的板子只要保证 NRF24L01 挂在同一个 SPI 总线上CE/CSN 任选普通 GPIO 就行。2.3 供电和布线的几个细节NRF24L01 对供电电压挺敏感模块上电瞬间电流会拉到十几毫安以上发射瞬间更高如果供电线太细或者稳压器余量不足通信距离会肉眼可见地缩水。我给无线模块的 3.3V 引脚旁边并联了 10uF 电解电容和 0.1uF 陶瓷电容位置尽量靠近供电脚实测对通信稳定性帮助很大。另外船上的天线别贴着水面的金属件NRF24L01 模块用排针架高一点天线朝上减少水面对 2.4GHz 信号的吸收。这些都是后来实测摸索出来的细节。3. NRF24L01 通信链路数据帧、初始化与收发流程3.1 通信参数的确定NRF24L01 属于 SPI 从设备所有寄存器操作都走 SPI 总线。它内部有发送 FIFO 和接收 FIFO数据包最大 32 字节Enhanced ShockBurst 模式会自动补上帧头、地址和 CRC。在这个船模项目里我手动把自动应答auto-ACK关掉了因为每一帧遥控指令本就是一发一收不需要应答重传ACK 功能反而会增加空中的往返次数拉高延迟。通信速率选 1Mbps 而不是 2Mbps距离优先实测 2Mbps 下丢包率明显升高比赛现场环境复杂稳定压倒一切。信道我固定在第 10 信道因为常见 WiFi 路由集中在 1、6、11 信道第 10 信道相对干净到了比赛现场还可以通过按键切信道避开瞬时干扰。3.2 数据帧结构NRF24L01 本身是一个数据包承载工具我在它的 payload 之上又加了一层极简协议用来区分指令类型和校验完整性。实际发送 5 个字节结构如下字节内容说明byte[0]0xAA帧头1byte[1]0x55帧头2byte[2]油门值0~255byte[3]舵量0~180byte[4]校验值throttle ^ steer帧头加两字节不是为了防 NRF 数据包自身错位而是为了过滤旧包。当遥控器连续发送时如果某一包丢了接收端下一次收到的可能是 FIFO 里排队的旧指令帧头加校验可以快速识别并丢弃不完整的指令。校验方式没有用累加和因为指令就两个数据字节异或足够MCU 解析起来也快。3.3 核心初始化与收发函数初始化阶段需要配置 SPI 时钟、设置 CE/CSN 为输出、把 IRQ 引脚设为输入然后按顺序写入寄存器。这里给出精简版初始化代码能跑通收发链路void NRF24L01_Init(void) { NRF_CE_LOW(); NRF_CSN_HIGH(); // 先关自动应答使能数据通道0 NRF_WriteReg(0x01, 0x00); // EN_AA 0 NRF_WriteReg(0x02, 0x01); // EN_RXADDR 启动管道0 // 设置发送和接收地址一致5字节 const uint8_t addr[5] {0x34, 0x43, 0x10, 0x10, 0x01}; NRF_WriteReg(0x10, addr, 5); // TX_ADDR NRF_WriteReg(0x0A, addr, 5); // RX_ADDR_P0 NRF_WriteReg(0x11, 5); // RX_PW_P0 5字节payload NRF_WriteReg(0x05, 10); // RF_CH 10 NRF_WriteReg(0x06, 0x06); // RF_SETUP1Mbps发射功率0dBm NRF_WriteReg(0x00, 0x0E); // CONFIG使能CRC2字节CRC }发送函数逻辑是拉低 CE 进入待机清空发送 FIFO写入 payload拉高 CE 至少 130us 进入发送模式然后查询状态寄存器确认发送完成。发送失败一定要清掉 TX FIFO否则模块会反复重发旧包后面的新指令根本排不进去。void NRF24L01_TxPacket(uint8_t *buf, uint8_t len) { NRF_CE_LOW(); NRF_CSN_LOW(); SPI_WriteByte(0xE0); // FLUSH_TX NRF_CSN_HIGH(); NRF_CSN_LOW(); SPI_WriteByte(0xA0); // W_TX_PAYLOAD for (uint8_t i 0; i len; i) { SPI_WriteByte(buf[i]); } NRF_CSN_HIGH(); NRF_CE_HIGH(); delay_us(150); NRF_CE_LOW(); }接收函数一般通过 IRQ 引脚或者轮询状态寄存器判断是否有新数据。我建议项目早期用轮询方式逻辑简单后面再切到外部中断。接收时要留意读 payload 后清零 RX_DR 标志否则中断会反复触发。4. 遥控端代码摇杆采样、映射与发送状态机4.1 ADC 采样与摇杆映射电位器摇杆本身输出的是模拟电压经过 STM32 的 ADC1 采样后变成 0~4095 的数值。ADC 采样要加滤波否则手指轻微抖动就会变成舵量和油门的毛刺。我用最朴素的办法单次读取连续采样 10 次排序取中值。这个滤波代码写起来简单但对电位器接触噪声的抑制效果很好。原始值映射到 0~255 和 0~180 时留一点死区避免摇杆回中后因电阻误差导致舵机轻微摆动。更讲究一点的做法是上电校准时按住按键把当前摇杆物理位置记录为基准中位之后所有映射都相对这个基准展开大大减少电位器个体差异的影响。4.2 发送状态机遥控端主循环就是“采样—组帧—发送—刷新指示灯”的循环周期大概 20ms也就是 50Hz 发送频率指令跟手程度和信道占用之间平衡得比较好。代码如下while (1) { uint16_t adc_throttle ADC_ReadFilter(ADC_CH0); uint16_t adc_steer ADC_ReadFilter(ADC_CH1); uint8_t throttle map(adc_throttle, 0, 4095, 0, 255); uint8_t steer map(adc_steer, 0, 4095, 0, 180); uint8_t tx_buf[5]; tx_buf[0] 0xAA; tx_buf[1] 0x55; tx_buf[2] throttle; tx_buf[3] steer; tx_buf[4] throttle ^ steer; NRF24L01_TxPacket(tx_buf, 5); delay_ms(20); }如果 NRF24L01 连续出现发送失败遥控端不能傻等着要在主循环里及时发现并重置发送 FIFO。具体做法是读 STATUS 寄存器的 MAX_RT 位如果置位就写 1 清掉该标志并执行 FLUSH_TX防止无线链路卡死。比赛中遇到干扰造成的瞬时失败很正常只要做到“失败后迅速恢复”就不会影响操控。4.3 遥控端的实测提醒发射端的电池电压下降之后AMS1117 输出如果不够稳NRF24L01 的发射功率也会跟着掉遥控距离明显变短。所以遥控器低电量报警不是一个可选功能我后来把 ADC 的采样通道分一路给电池电压液晶屏上直接显示电压值低于 6V 就闪烁提醒。另一个提醒是发射端和接收端的信道必须一致否则两边都在“空忙”。我给遥控端加了一个按键循环切信道OLED 上显示当前信道号下水前先确认两端信道一致再封仓。5. 船体端代码指令解析、PWM 输出与失控保护5.1 接收与解析船体端上电后NRF24L01 切到接收模式通过外部中断或者主循环轮询 IRQ 引脚来获取新包。我建议收到数据后先丢进一个 buffer解析放在主循环里做不要在中断服务函数里执行 PWM 更新这种重操作否则容易造成定时器计数毛刺。解析逻辑就是对帧头和校验值做判断uint8_t Parse_Command(uint8_t *buf) { if (buf[0] 0xAA buf[1] 0x55) { if ((buf[2] ^ buf[3]) buf[4]) { return 1; } } return 0; }只有校验通过的指令才认为有效否则直接丢弃不更新 PWM 输出。因为 NRF24L01 本身有硬件 CRC出现帧内单字节错误的概率很低这里的异或校验只是双保险防止极端情况下接收到了错误数据。5.2 电机和舵机的 PWM 输出船体端有两路 PWM一路电机调速一路舵机转向。电机 PWM 挂在 TIM1_CH1 也就是 PA8 上输出频率 20kHz这样电机运转噪声小、调速线性度好。注意 TIM1 是高级定时器初始化时除了配置 ARR 和 CCR还必须使能主输出否则 PWM 引脚永远是低电平TIM1-BDTR | TIM_BDTR_MOE;这一行不加PA8 不会输出任何波形很多第一次用 STM32 的人都会在这个坑里卡半天。舵机 PWM 挂在 TIM2_CH1 也就是 PA0 上周期 50Hz对应 20ms 周期脉宽 0.5ms 到 2.5ms 映射到 0 到 180 度。如果定时器配置为 1MHz 计数频率那 CCR 寄存器的值直接写脉宽微秒数就行0 度时写 50090 度时写 1500180 度时写 2500非常直观。船模如果不要求倒车L9110S 的 PWM 方向控制直接接高电平只通过 PWM 占空比控制转速即可。如果比赛规则允许倒车那 PA1 作为方向脚控制 H 桥反向占空比在 0~255 之间双向映射。5.3 失控保护是船模的命根子遥控船在水面上突然失控最怕的是电机保持全速往前冲船直接跑出视线范围。所以我专门加了一套“看门狗”逻辑每收到一包合法指令就记录当前时间主循环里用 SysTick 检查如果超过 500ms 没有新指令就强制把油门设为 0舵机打回中位。这个保护逻辑在岸上测试时感觉不到但下水之后它就是最后一道安全网。同时船载板上的 LED 可以指示状态正常接收时 LED 闪烁失联超过 500ms 后 LED 常亮方便岸上人员在远处判断通信状态。6. 实船调试三次翻车后的排查链路与经验沉淀6.1 通信距离只剩三米第一次下水测试我就碰到通信距离暴跌船在架子上测试时能走二十米一放进水池就三米失联。我先怀疑天线位置把模块架空之后有所好转但没根治。后来拿万用表去量 NRF24L01 的供电电压发现舵机一动作电压就掉到 2.8V 左右。问题根源是船载电源方案太粗糙电机、舵机、无线模块共用一个稳压大电流冲击直接拉低逻辑电压。解决方法是把电机驱动供电独立出来走电池直供STM32 和 NRF 单独用 AMS1117 供电再给无线模块附近补电容。同时我把 RF 速率从 2Mbps 降到 1MbpsSPI 时钟降到一个保守值通信距离一下子就上去了。6.2 TIM1 高级定时器没有 PWM 输出调试舵机时遇到过一个很隐蔽的问题代码从网上抄来舵机就是纹丝不动。用示波器量 PA8 引脚波形始终是平的。排查了很久最后翻手册才发现 TIM1 作为高级定时器有刹车和主输出使能机制默认状态下主输出是关断的。普通定时器不存在这个问题所以网上很多例程不会特意写启用 MOE 的代码。加上那一行TIM1-BDTR | TIM_BDTR_MOE;之后PWM 立刻出现。这个坑特别典型如果你用 TIM1、TIM8 这类高级定时器输出 PWM一定要先确认主输出使能寄存器。6.3 电机一开就丢包下水测试的另一个经典现象是船静止时遥控一切正常电机一转就丢包。我用串口打印接收端的丢包计数电机转动时丢包率直接飙到接近 100%。用示波器看 3.3V 和 GND 之间电机启动瞬间有大量毛刺这是典型的传导干扰。解决办法有几个方向电机两端并联 104 陶瓷电容和 100uF 电解电容信号线绞合后远离电机电源线NRF24L01 模块用排针抬起来不贴主板。最有用的一步是让电机电源地线和逻辑地线单点相连两个地不要在板上大面积共铜。做完这些电机全速转动时丢包率立刻降到零。船下水后还要注意天线尽量垂直指向上方不要让船壳遮蔽天线因为水面本身对 2.4GHz 信号吸收很严重。6.4 下载器连接不上新拉回来的 STM32F103C8T6 核心板ST-Link 经常出现连接不上。我踩过一次之后养成了习惯遇到连不上就先检查 BOOT0 跳线按下复位键重试再不行就把 SWDIO 和 SWCLK 两根线重新插一遍。国内很多蓝色药丸核心板自带 CH340 串口转 USB 电路也可以用串口 ISP 方式下载把 BOOT0 拉高再上电配合串口工具就能烧录。两种方式配合使用几乎不会出现板子变砖的情况。如果你发现下载器怎么都识别不到芯片先别怀疑芯片坏了多半是驱动或者线序问题。6.5 下水前的自检清单比赛前我在房间里贴了一张自检清单每次装船前按顺序过一遍省掉了很多现场问题的排查时间发射端和接收端信道一致OLED 显示的信道号相同电池电压都在安全范围之内遥控端摇杆回中后舵机初始力臂处于中立位置电机空转时油门从低到高逐步推确认 PWM 输出平滑无跳变不装螺旋桨的状态下把船放在装满水的塑料桶里测试 30 分钟确认不会突然失联用透明胶带把 NRF24L01 模块固定好避免船体震动导致排针接触不良比赛场地如果信道拥挤先用电台扫描功能找到干扰小的信道再下水船模遥控项目做到这里其实已经不是单纯写代码的问题而是无线链路、电源、机械、水面环境等多个环节的统筹。每个人下水前都觉得自己没问题但真正跑起来问题往往出在你最没预料到的地方。如果非要说一个最重要的经验那就是一定把“可观测性”做足——遥控器上显示信道、丢包率、电池电压船载端用 LED 指示通信状态。你能在岸上看到的东西越多需要在水里捞船的次数就越少。准备比赛的朋友不妨从这套方案开始先跑通一对 NRF24L01 的收发再往板子上接电机和舵机一步一步来稳定性自然就有了。本文还有配套的精品资源点击获取