简介:面向舵机转向小车与STM32入门控制项目,这份基于STM32与TB6612的电路设计资料聚焦舵机精准转向、模块化控制板搭建及电机驱动外围设计,适合电子设计爱好者、高校课程设计学生和需要快速复现小车方案的开发者参考。资源以1个PDF文件交付,压缩包约383KB,篇幅集中,便于在电脑或移动端查阅。内容围绕ForestS1最小系统板展开,涵盖STM32F103C8T6外围晶振与复位电路、USB串口一键下载的BOOT0与复位控制逻辑、AMS1117-3.3稳压及TVS保护、电阻分压式电池电量采集与ADC换算代码,并讨论A4950电机驱动选型、PWM调速和限流电阻计算等关键细节。模块化思路贯穿控制板、稳压、电量测量与驱动模块,便于故障排查和后续复用。已有237人学习,适合作为舵机小车电路搭建、原理图理解和程序下载电路调试的参考底稿。
1. 一块能拆着用的控制板:舵机转向小车到底在解决什么问题
去年帮人改一台舵机转向小车,板子做得挺紧凑,主控、电源、驱动全焊在一块 PCB 上。等到要换个大点的底盘、给前轮舵机加个云台,才发现电池座和驱动芯片的位置全挡住了,最后只能整块板重画。这也是我把这套基于 STM32F103C8T6 的舵机小车电路设计翻出来重读的原因:它从第一版就把「模块可回收」写进了约束里。
整套方案把控制板拆成最小系统板、一键下载、LM2596 稳压、AMS1117 降压、电阻分压电量测量、电机驱动和人机交互几个独立单元,每个单元有自己的输入输出定义,跑完一个项目能直接拔下来塞进下一个。它适合正在画自己第一版小车底板的人,也适合手里有车、想换主控或换驱动却不想推倒重来的开发者。读完至少能判断:哪几个模块值得照抄,哪几个必须按自己的电池参数重算。
2. 模块化架构:六个模块怎么分工,信号和电源怎么走
这套设计的核心不是某一颗芯片选得多妙,而是把「一块板」拆成「一组板」。拆的依据是电源域和信号域:凡是电压不同、噪声等级不同、更换频率不同的部分,都单独成模块。这样做的直接好处是调试时能一段一段量电压,坏处是接插件多了,接触电阻和线损会变成新的变量,后面避坑那章会专门讲。
先看整体分工。电池进来分成两条路,一条直接进电机驱动,因为电机要的是扭矩不是稳压;另一条进 LM2596 降到 5V,供系统板、舵机和 OLED;系统板上的 AMS1117 再把 5V 降到 3.3V 给 MCU 和逻辑电路。电量测量挂在电池上,用高阻分压送 ADC。人机交互和一键下载都算系统板的附属电路,跟着最小系统板走。
2.1 供电树:从 2S/3S 电池到 5V、3.3V
航模电池的电压区间要先算清楚:2S 满电 8.4V、标称 7.4V、过放保护点 6.4V;3S 满电 12.6V、标称 11.1V、过放点 9.6V。电机驱动直接吃这个宽压,所以选型时 VM 耐压要按 12.6V 往上留余量,TB6612FNG 的 VM 上限是 15V,3S 满电时余量偏小,电机反电动势一冲就容易踩线,这也是后面我建议 3S 用户优先考虑 A4950 的原因。
| 模块 | 输入 | 输出 | 典型负载 |
|---|---|---|---|
| 电池输入 | — | 6.4~12.6V | 电机 + 降压 |
| LM2596S-5.0 | 6.4~12.6V | 5.0V / 3A | 系统板、舵机、OLED |
| AMS1117-3.3 | 5.0V | 3.3V / 800mA | MCU、OLED 逻辑、传感器 |
| 分压网络 | 电池 | 1/11 电池电压 | ADC 采样 |
| 电机驱动 | 电池 | 双路电机 | 两个直流减速电机 |
这张表里最容易被低估的是 5V 轨。舵机在堵转瞬间能拉到 1~2A,LM2596 标称 3A 看着够,但舵机和系统板共用一条 5V 时,舵机一动 5V 就会被拉出几百毫伏的凹陷,MCU 的串口和 OLED 立刻出问题。常见做法是舵机单独走一路 5V,或者在舵机供电脚旁边并 470uF 以上的电解电容做本地储能,再串一个磁珠隔开高频噪声。
2.2 模块接口定义与可回收设计
模块化不是把板子锯开就行,接口定义不清楚,回收回来照样接不上。这套设计里每个模块都用 2.54 排针或 XH2.54 端子引出,电源入口统一「红正黑负」,信号线按功能分组,关键信号旁边留一个 GND 针脚。这样做插拔的时候不容易反接,量波形时探头地也好夹。
| 接口 | 类型 | 电平 | 用途 |
|---|---|---|---|
| J1 | XH2.54-2P | 6.4~12.6V | 电池输入 |
| J2 | XH2.54-2P | 5.0V | 舵机供电 |
| J3 | 排针 2×4 | 3.3V | OLED I2C + 串口调试 |
| J4 | 排针 2×4 | 3.3V | 系统板与底板互连 |
| J5 | 螺钉端子 | 电池电压 | 电机驱动 VM |
| J6/J7 | 螺钉端子 | — | 左右电机 |
回收设计的关键动作是「隔离」。每个模块的电源入口串一个 0Ω 电阻或者跳线帽,调试阶段可以单独断开测电流;信号线串 22Ω 电阻,限流同时抑制振铃;电机驱动的地和逻辑地只在电池负极处单点相连,避免电机电流从信号地回流。这几招不花钱,但能让一块板在三个项目之间反复搬家。
2.3 最小系统板外围:晶振、复位、滤波、PC14/PC15
最小系统板最早是为平衡小车和倒立摆做的,后来直接挪到舵机小车上,说明它的外围设计足够通用。核心是 STM32F103C8T6 加 8MHz 无源晶振,配两颗 20pF 负载电容,晶振尽量靠近芯片,走线包地。复位脚 NRST 用 10k 上拉到 3.3V,并联 100nF 到地,按键按下直接拉低。
去耦是这块板子做得比较扎实的地方:每个 VDD 引脚旁边放一颗 100nF 瓷片电容,整板再放一颗 10uF 钽电容做储能。VDDA 单独处理,串一颗 10Ω 电阻或者磁珠,再配 1uF 加 100nF,ADC 采电量时才不会把电源纹波当成电压变化。BOOT0 用 10k 下拉到地,保证正常上电从 Flash 启动,只有一键下载电路动作时才被拉高。
PC14 和 PC15 这两个引脚比较特殊,它们在 F103 上默认接 32.768kHz 低速晶振,没外接晶振时能当普通 IO 用。但要注意它们的驱动能力很弱,数据手册里标注的输出电流只有 3mA 左右,翻转速度也上不去,直接推 LED 会偏暗甚至不亮。我一般只拿它们做输入或者驱动高阻节点,真要点灯还是走 PB 口或者加一级三极管。
2.4 人机交互:按键、LED 与 OLED 接口
调试阶段最怕的是「黑匣子」,程序跑飞了不知道卡在哪。这块板子留了一个用户按键、一个运行指示灯、一个电源指示灯和一个 4 针 I2C 的 OLED 接口,成本不到五块钱,但能把大部分状态可视化。按键用 10k 上拉到 3.3V,按下接地,低电平有效;LED 串 1k 限流电阻,3.3V 减去 LED 压降 2V,电流大约 1.3mA,亮度不高但足够在室内看清。
按键的处理不能只做电平判断,机械按键抖动一般 5~20ms,直接读会一次按下触发好几次。我用的是 10ms 周期扫描加长短按区分,短按切模式,长按进参数设置,这样一颗键能干两件事,省掉旋钮和拨码开关。
/* 每 10ms 调用一次;返回 0 无动作,1 短按,2 长按 */ uint8_t Key_Scan(void) { static uint8_t cnt = 0; static uint8_t last = 1; uint8_t cur = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12); /* 上拉,按下为 0 */ uint8_t ret = 0; if (last == 1 && cur == 0) { /* 下降沿:按键按下,重新计时 */ cnt = 0; } else if (cur == 0) { /* 持续按下:累加,2s 封顶 */ if (cnt < 200) cnt++; } else if (last == 0 && cur == 1) { /* 上升沿:松开,按时长判长短按 */ ret = (cnt > 30) ? 2 : 1; /* 30 * 10ms = 300ms 为分界 */ } last = cur; return ret; }cnt的单位是 10ms,所以 30 对应 300ms 的分界点,200 对应 2 秒封顶,防止长按一直累加溢出。last保存上一次的电平,用来识别边沿。返回值交给上层状态机,短按切换「待机 / 手动 / 循迹」,长按进入参数标定。OLED 每 100ms 刷一次,显示电池电压、当前模式和舵机中位值,调车时不用连电脑就能看关键数据,省了很多次插拔串口的麻烦。
3. 一键下载电路与调试入口:BOOT0、DTR/RTS 到底怎么配合
STM32F103C8T6 的下载方式有串口 ISP、SWD、DFU 几种。SWD 要 ST-Link,贵在稳定;串口 ISP 只要一根 USB 线,成本最低,适合量产板和给学生用。麻烦的是串口下载要手动改 BOOT0 电平再复位,一天烧二十次程序就是四十次拨动加按压,手都麻了。一键下载电路就是把这两个动作交给串口芯片的 DTR 和 RTS 引脚自动完成。
3.1 串口 ISP 的时序:先拉高 BOOT0 再复位
F103 上电或复位时会采样 BOOT0 和 BOOT1 两个引脚,决定从哪块存储区启动。BOOT0 为低时从主 Flash 启动,跑用户程序;BOOT0 为高、BOOT1 为低时从系统存储器启动,执行芯片出厂固化的 Bootloader,这时候串口 1 才能和上位机握手下载。所以进入 ISP 的条件是:BOOT0 保持高电平的同时,NRST 给一个复位脉冲,让芯片在复位释放的瞬间采到 BOOT0=1。
手动操作的顺序是先拨 BOOT0 到高,按一下复位,下载,再把 BOOT0 拨回低,再复位一次让程序跑起来。一键下载要自动化的就是「拉高 BOOT0、拉低 NRST、延时、释放 NRST、保持 BOOT0」这一串动作,下载完成后再自动把 BOOT0 放回低电平并复位。整个时序里最容易出错的是延时,复位拉低时间太短芯片没反应过来,太长又会让上位机以为掉线,一般取 100ms 左右比较稳。
3.2 一键下载电路的三极管组合拆解
电路的核心是两个三极管 Q1、Q2 加串口芯片的 DTR、RTS 两路输出。这里有个前提必须先记住:CH340 这类芯片的 DTR_N、RTS_N 引脚输出和软件设置的 DTR、RTS 是反相的,软件里设 DTR 为低,DTR_N 实际输出高电平。
进入 ISP 的过程是这样的:上位机把 DTR 设为低,DTR_N 变高;RTS 设为高,RTS_N 变低。这一组电平让 Q2 导通,BOOT0 被拉到高电平;同时 Q1 也导通,NRST 被拉低,芯片处于复位状态。延时 100ms 后,上位机把 DTR 拉高,DTR_N 变低,Q1 截止,NRST 通过上拉电阻回到高电平,复位结束;此时 RTS 仍为高,RTS_N 仍为低,Q2 继续导通,BOOT0 维持高电平,芯片在复位释放的瞬间采到 BOOT0=1,进入 ISP 模式。接下来握手、擦除、写入、校验都能正常走完。
| 阶段 | 软件 DTR | 软件 RTS | BOOT0 | NRST | 芯片状态 |
|---|---|---|---|---|---|
| 初始 | 高 | 低 | 0 | 1 | 跑用户程序 |
| 进 ISP | 低 | 高 | 1 | 0 | 复位中 |
| 释放复位 | 高 | 高 | 1 | 1 | 进 Bootloader |
| 下载中 | 高 | 高 | 1 | 1 | 串口握手 |
| 下载完成 | 高 | 低 | 0 | 1 | 从 Flash 启动 |
这张表建议打印出来贴在工位上。我见过不少板子一键下载不工作,量到最后就是某个阶段 RTS 没维持住,BOOT0 在复位释放前掉回低电平,芯片直接跑回旧程序,上位机自然同步失败。
3.3 上位机参数配置与手动验证
用 FlyMcu 或者 mcuisp 这类工具时,几个选项必须对上:串口选 CH340 对应的端口,波特率 115200 起步,勾选「编程后执行」,下面两个复选框选「DTR 的低电平复位,RTS 高电平进 Bootloader」。这个描述和前面讲的时序是一一对应的,选反了就会变成「复位时进不了 ISP」或者「下载完不运行」。
如果电路第一次上电就不通,先别怀疑代码,用万用表量 Q2 集电极在下载瞬间有没有跳到 3.3V。没有的话,要么三极管焊反,要么 CH340 的 DTR 极性被上位机设反。也可以绕开电路,用 Python 直接控 DTR 和 RTS 手动模拟一遍时序,确认芯片本身和串口链路是好的。
import serial import time # 端口号按自己系统改,CH340 在 Windows 上通常是 COM5 及以上 ser = serial.Serial("COM5", 115200, timeout=1) # 第一步:模拟上位机进入 ISP 的电平组合 ser.dtr = False # DTR 低 -> DTR_N 高 -> Q1 导通,NRST 被拉低 ser.rts = True # RTS 高 -> RTS_N 低 -> Q2 导通,BOOT0 被拉高 time.sleep(0.15) # 复位保持 150ms,比手册要求的 100ms 留点余量 # 第二步:释放复位,BOOT0 继续维持高电平 ser.dtr = True time.sleep(0.05) # 第三步:发同步字节 0x7F,Bootloader 正常会回 0x79 ser.write(b"\x7f") ack = ser.read(1) print("sync ack:", ack.hex()) # 输出 79 说明已经进了 ISP # 第四步:恢复成正常运行状态,BOOT0 回低 ser.rts = False ser.dtr = False ser.close()这段脚本的价值在于把「玄学」变成可观测的电平。ser.dtr = False在 pyserial 里对应 DTR 逻辑低,但不同 USB 转串口芯片的极性可能相反,实测时如果回的不是79,把 dtr 和 rts 的取值同时取反再试一次。time.sleep(0.15)是复位保持时间,短于 100ms 时部分批次的 F103 会采不到 BOOT0。最后一步恢复rts = False很重要,否则 BOOT0 一直被拉高,下次上电又進 ISP,用户会以为程序丢了。
4. 电源与电量计:稳压、分压采样与电机驱动选型
电源这块是整个设计里最不性感但最容易翻车的部分。小车跑起来电机是脉冲负载,电流从几十毫安跳到两三安只用几毫秒,稳压芯片的环路响应跟不上,输出电压就会塌。电量测量看着只是两个电阻分压,但阻值选错会把电池直接灌进 ADC 引脚。电机驱动选型则直接决定小车能不能爬坡、会不会一堵转就烧芯片。
4.1 12V 到 5V:LM2596S-5.0 的外围参数怎么定
LM2596 是降压型开关电源,固定 5.0V 版本内部已经做好了反馈分压,外部不需要再调。典型外围是输入电容 100uF 电解加 0.1uF 瓷片,输出电容 220uF 电解加 0.1uF 瓷片,续流二极管用 SS34 肖特基,电感取 33uH 且饱和电流要大于 3A。输入电容尽量靠近芯片的 IN 脚,续流二极管的回路面积要小,这两条决定了输出纹波能不能压到 50mV 以内。
有个细节值得说:LM2596 的开关频率只有 150kHz,环路带宽低,负载突变时输出会有明显的过冲和下陷。小车场景里电机启动就是一次大负载阶跃,所以输出端那 220uF 不是可选项。我一般在电机驱动的 VM 脚旁边再并一颗 470uF,让瞬态电流从本地电容取,不往稳压芯片那边要。散热也别忽略,3A 输出时芯片损耗大约 1.5W,TO-263 封装必须铺一块至少 2cm² 的铜皮,否则跑几分钟就烫到保护。
4.2 5V 到 3.3V:AMS1117 与 TVS 保护
AMS1117-3.3 是线性稳压,优点是外围只要两颗电容,纹波小,适合给 MCU 和 ADC 供电;缺点是压差全变成热。5V 降到 3.3V,压差 1.7V,输出 500mA 时功耗 0.85W,SOT-223 封装不加铜皮会明显发烫,输出 800mA 时就是 1.36W,基本必烫。好在系统板和 OLED 加起来的负载一般不超过 200mA,实际温升可以接受。
保护部分用了 D1、D2 两颗 TVS 管,接在 5V 和 3.3V 对地之间。TVS 的作用是吸收外部高压和静电,比如 USB 线插拔时的浪涌、电机反电动势窜回电源轨。选型时看钳位电压要低于后级芯片的绝对最大额定值,5V 轨一般选 SMAJ5.0A,钳位 9.2V 左右,3.3V 轨选 SMAJ3.3A。TVS 要尽量靠近接口放置,走线短而粗,否则浪涌还没到 TVS 就先打穿芯片了。
4.3 电量测量:1/11 分压与 ADC 换算
航模电池的电量和电压是强相关的,2S 满电 8.4V,放到 6.4V 以下就会永久过放,锂电池一次过放基本就报废。所以监控电压不是为了精确显示百分比,而是为了在临界点之前叫停。这套设计用两颗高精度电阻做 1/11 分压,12.6V 进来变成 1.15V,远低于 3.3V 的 ADC 量程,安全余量足够。
阻值选择上,上臂 10k、下臂 1k 是比较常见的组合,分压比 11:1,静态电流约 1.1mA,对电池的消耗可以忽略。电阻精度建议用 1%,5% 的电阻在 12V 处会带来 ±0.1V 以上的误差,电量显示会飘得没法看。
/* 读取电池电压,返回值单位:0.01V,例如 840 表示 8.40V */ int Get_battery_volt(void) { int volt; /* Get_Adc 返回 0~4095;3.3V 参考;分压比 11:1;乘 100 做定点放大 */ volt = Get_Adc(Battery_Ch) * 3.3 * 11 * 100 / 4096; return volt; }这段是原始实现,思路对但有两个隐患。一是3.3是浮点常量,整个表达式会被提升成 double 运算,F103 没有硬件 FPU,一次调用几百个周期,放在主循环里频繁调用会拖慢控制周期。二是只采一次,ADC 噪声直接进结果,电压显示会末位跳动。我一般改成整数运算加多次平均:
/* 改进版:定点运算 + 8 次平均,返回值单位 mV */ uint32_t Get_battery_mv(void) { uint32_t sum = 0; for (uint8_t i = 0; i < 8; i++) { sum += Get_Adc(Battery_Ch); /* 单次采样 0~4095 */ } uint32_t adc = sum / 8; /* 平均,压掉 ±3LSB 抖动 */ /* 3300mV * 11 / 4096 = 8.86mV/LSB,全整数运算避免浮点 */ return adc * 3300UL * 11 / 4096; }3300UL后面的 UL 不能省,adc * 3300 * 11最大约 1.49 亿,超过 16 位 int 的范围,不加 UL 会溢出成负数。8 次平均能把随机噪声压到原来的三分之一左右,代价是 8 次采样时间,按 55.5 周期的采样配置算大约 60us,对 10ms 的控制周期完全无感。算出来的毫伏值再和阈值比较,2S 低于 6800mV、3S 低于 10200mV 就点亮红灯并降速,给用户留出返航时间。
4.4 电机驱动:TB6612 与 A4950 的取舍与限流电阻
标题里写的是 TB6612,实际设计文档里详细展开的是 A4950,这两颗其实是同一位置上可以互换的两个方案,理解它们的差别比记型号有用。TB6612FNG 是双 H 桥,一片驱动两个电机,VM 最大 15V,每通道连续 1.2A、峰值 3.2A,待机电流微安级,PWM 频率能上到 100kHz,外围只要几颗滤波电容,非常适合 2S 电池的小车。它的短板是 15V 耐压,3S 满电 12.6V 再加电机反电动势,余量偏紧。
A4950 是单通道,峰值 3.5A,连续 2A 左右,耐压高得多,每片要两路 PWM 控制正反转和调速,双电机就得两片。它的卖点是大电流和内置限流,通过 VREF 引脚和外部检流电阻设定最大电流,公式是 I_TripMAX = VREF / (10 × R_S)。取 R_S = 0.25Ω、VREF = 5V,算下来限流点 2A,正好卡在电机堵转电流和芯片承受能力之间。
| 项目 | TB6612FNG | A4950 |
|---|---|---|
| 通道数 | 2 | 1 |
| 连续电流 | 1.2A / 通道 | 2A |
| 峰值电流 | 3.2A | 3.5A |
| VM 耐压 | 15V | 40V 级别 |
| 控制方式 | IN1/IN2 + PWM | IN1/IN2 + PWM |
| 限流设定 | 无 | VREF + R_S |
| 适合电池 | 2S | 2S~3S |
限流电阻的功率也要算,2A 电流下 0.25Ω 上的功耗是 1W,必须用 2512 封装的合金电阻,0603 那种小电阻上去就烧。实际调试时我会先把 VREF 调到 3.3V,限流点降到 1.3A 左右做初步测试,确认电机和驱动都正常再提到 5V,这样就算代码写错导致堵转,也不至于第一把就冒烟。
/* TB6612 双电机控制;left/right 范围 -1000~1000,负数表示反转 */ void Motor_Set(int16_t left, int16_t right) { /* 左电机:PA0/PA1 定方向,PA2 出 PWM(TIM2_CH3) */ if (left >= 0) { GPIO_SetBits(GPIOA, GPIO_Pin_0); GPIO_ResetBits(GPIOA, GPIO_Pin_1); TIM_SetCompare3(TIM2, left); } else { GPIO_ResetBits(GPIOA, GPIO_Pin_0); GPIO_SetBits(GPIOA, GPIO_Pin_1); TIM_SetCompare3(TIM2, -left); /* 取反,PWM 占空比始终为正 */ } /* 右电机:PA3/PA4 定方向,PA5 出 PWM(TIM2_CH1) */ if (right >= 0) { GPIO_SetBits(GPIOA, GPIO_Pin_3); GPIO_ResetBits(GPIOA, GPIO_Pin_4); TIM_SetCompare1(TIM2, right); } else { GPIO_ResetBits(GPIOA, GPIO_Pin_3); GPIO_SetBits(GPIOA, GPIO_Pin_4); TIM_SetCompare1(TIM2, -right); } }TIM_SetCompare的第二个参数上限由 ARR 决定,这里配成 1000,所以入参正好是千分比占空比。方向脚和 PWM 的配合顺序有讲究:先设方向再给 PWM,避免换向瞬间出现两路同时为高的短路状态。TB6612 的 STBY 引脚必须拉高芯片才工作,很多人第一次上电电机不转,量半天最后发现 STBY 悬空,这是最常见的翻车点之一。
舵机是另一套控制逻辑,它要的是 50Hz 的周期信号,高电平宽度 500~2500us 对应 -90 到 +90 度,跟电机的 PWM 完全不是一回事。
/* 舵机角度控制;angle 范围 -90~90 度 */ void Servo_SetAngle(int8_t angle) { if (angle > 90) angle = 90; if (angle < -90) angle = -90; /* 线性映射:-90 度 -> 500us,0 度 -> 1500us,+90 度 -> 2500us */ uint16_t pulse = 1500 + (int16_t)angle * 2000 / 180; /* TIM3 预分频到 1us 计数,ARR = 20000 对应 20ms 周期 */ TIM_SetCompare2(TIM3, pulse); }2000 / 180是整数除法,会有约 1us 的量化误差,对应角度误差不到 0.1 度,对转向控制完全够用。顺序上要先配好 TIM3 的预分频和 ARR 再调用这个函数,否则第一次输出的脉宽是随机值,舵机会猛地打到一边。上电初始化时我习惯先Servo_SetAngle(0)让舵机回中位再开电机,避免小车一上电就窜出去。
5. 避坑与排查:这块板子最容易翻车的五个地方
这块板子我前后焊过几版,原理图上看不出问题、一上电就出问题的情况几乎每次都有。下面五条是出现频率最高的,按「上电与下载」和「运行阶段」分成两组,每条都写清楚现象、原因和具体怎么查。新手照着顺序排一遍,大部分问题十分钟内能定位。
5.1 上电与下载阶段:三个卡住新手的点
第一条,上电后 3.3V 只有 1.x V,AMS1117 烫手。现象是电源指示灯很暗或者不亮,万用表量 3.3V 对地只有 1V 出头。原因通常是输出端短路,最常见的是 OLED 排线接反或者 I2C 座子焊连,LDO 进入限流保护,压差全变成热。解决方法是断开所有负载,只留 AMS1117 和两颗电容,量空载输出是不是 3.3V,确认正常后再一个一个插模块,插到哪个电压掉下来就是哪个模块的问题。别在带载状态下反复上电,LDO 长时间限流会热死。
第二条,一键下载一直提示同步失败。现象是 FlyMcu 点开始后进度条卡在「连接中」,最后报同步超时。原因有三个常见方向:BOOT0 在复位释放前没保持高电平、CH340 的 DTR/RTS 极性和电路设计相反、串口被其他软件占用。排查顺序是先拔掉所有其他串口工具,用前面那段 Python 脚本手动发 0x7F,回了 0x79 说明电路没问题,问题在上位机配置;不回就量 Q2 集电极,看在 DTR 拉低时有没有跳到 3.3V,没有就是三极管或者 CH340 那一侧的问题。
第三条,程序下载成功但板子不运行。现象是 FlyMcu 提示「成功」,但 OLED 不亮、串口没输出。原因基本是下载完成后 BOOT0 还停在高电平,芯片复位后又进了 Bootloader,用户程序根本没被执行。解决方法是勾选「编程后执行」,让上位机在下载结束后自动把 BOOT0 拉低并复位;如果工具不支持,就手动断电再上电一次,让电路恢复到 BOOT0 下拉的状态。还有一种可能是下载地址写错,把程序烧到了 0x08000000 以外的区域,检查工程的 IROM 设置。
5.2 运行阶段:电源与驱动的两类异常
第四条,电机一转 MCU 就复位,串口乱码。现象是空载调试一切正常,一给电机 PWM 单片机就重启,或者串口打印出一堆乱码。原因是电机启动瞬间的大电流把电池电压拉塌,同时电机电流从信号地回流,抬高了 MCU 的地电位。解决分三步:电机电源线和逻辑电源线在电池负极处单点共地,不要串着走;电机驱动 VM 脚旁边并 470uF 电解加 0.1uF 瓷片;PWM 加软启动,占空比从 0 用 200ms 线性加到目标值,不要一步到位。这三招做完基本能根治。
第五条,ADC 采到的电池电压跳动 ±0.3V。现象是 OLED 上电压显示在 8.1~8.5 之间乱跳,电量判断忽高忽低。原因通常是三个:分压电阻用了 5% 精度的碳膜电阻、ADC 采样时间设得太短(比如 1.5 周期)、没有做软件滤波。解决办法是把分压电阻换成 1% 金属膜,ADC 采样时间设到 55.5 周期,采样前先做一次空采丢弃,然后连续采 8 次取平均。另外 VDDA 的去耦也要检查,那颗 100nF 离引脚超过 5mm 效果就打折。
这五条里前三条属于「板子还没跑起来」,后两条属于「跑起来了但不稳」。我的习惯是焊完新板先不插电机和舵机,只上系统板量三路电压,再用串口打印一次芯片 ID 和 Flash 大小,确认最小系统活着,再往上加模块。这个顺序能把问题范围缩到最小,比一上来全插满然后挨个拔快得多。
6. 二次开发:把这块控制板改成你自己的底盘
模块化设计的价值在改造阶段才真正体现出来。我最近一次改这个平台,是把原来的两轮差速底盘换成带舵机转向的三轮结构,前后只动了三个地方,一天就调通了。第一个是舵机中位标定,换底盘后舵机臂的安装角度变了,原来的 1500us 中位会让车往一边偏。标定方法很简单:写一个串口命令,让舵机在 1400~1600us 之间以 10us 步进扫,肉眼看前轮回正,记下那个值。
/* 舵机标定参数,放在单独的头文件里,换底盘只改这里 */ #define SERVO_CENTER_US 1480 /* 实测回正脉宽,批量生产建议存 Flash */ #define SERVO_HALF_US 480 /* 单边行程,对应最大转角 */ void Servo_SetAngle_Cal(int8_t angle) /* angle: -100~100,百分比行程 */ { if (angle > 100) angle = 100; if (angle < -100) angle = -100; /* 用百分比而不是角度,换舵机或换连杆时只改两个宏 */ uint16_t pulse = SERVO_CENTER_US + (int16_t)angle * SERVO_HALF_US / 100; TIM_SetCompare2(TIM3, pulse); }把角度换成百分比行程是个小技巧。不同舵机的实际转角范围差别很大,标称 180 度的可能只有 160 度,用绝对角度写死在代码里,换一个型号就得重算所有转弯参数;用百分比之后,转弯逻辑只关心「打多少行程」,具体脉宽交给宏,移植时改两行就完事。
第二个改动是驱动模块替换。原设计用的是 A4950,我手上只有 TB6612 的小板,两者接口定义我统一成了 VM、GND、四路输出、四路方向、两路 PWM、STBY,换板子只要把排线插上,代码里改一个#define USE_TB6612的宏,Motor_Set内部走不同的引脚映射。第三个改动是加了一路 IMU 的 I2C 接口,为后面的航向闭环留口子,硬件上只是多引了两个针脚和一个 4.7k 上拉,软件里先只读不控。
| 改造项 | 改哪里 | 验证方法 |
|---|---|---|
| 换底盘 | SERVO_CENTER_US、SERVO_HALF_US | 串口扫脉宽,看前轮回正 |
| 换驱动 | 驱动小板 + 引脚宏 | 单轮悬空正反转各 3 秒 |
| 换电池 | 分压电阻、低压阈值 | 可调电源模拟 6.4V 看告警 |
| 加传感器 | 预留 I2C 排针 | 先读 ID 再读数据 |
低压阈值这块要特别提醒一句。2S 的告警点我设 6.8V,3S 要改成 10.2V,如果换了电池忘了改这个宏,2S 的告警逻辑跑在 3S 上永远不会触发,电池过放一次就报废了。后来我干脆把电池节数做成上电检测:读一次分压值,高于 9V 就按 3S 处理,低于 9V 按 2S,阈值跟着自动切,虽然多写十几行,但省掉了换电池时的后悔药。
舵机小车的调试顺序也有讲究。我现在的固定流程是:先只上系统板确认串口和 OLED,再接舵机单独测中位和行程,然后接电机悬空测正反转,最后落地跑直线看跑偏量再调差速补偿。跳过任何一步,后面出了问题就得重新拆开查。从那以后我每次画新板、焊新板,都强制走一遍这个顺序,宁可多花半小时,也不愿在赛道上抱着车猜是电源还是代码的问题。这套电路设计和模块拆分思路,拿去改自己的底盘应该能省不少事,希望帮到你。
本文还有配套的精品资源,点击获取