简介:面向舵机转向小车DIY与嵌入式控制学习者的电路设计PDF,围绕STM32最小系统板、TB6612电机驱动与舵机转向控制展开,重点解决小车方向精准控制、模块复用及供电安全问题。文档按模块化控制板组织,涵盖晶振复位与滤波、按键/LED/OLED人机交互、USB串口一键下载、AMS1117-3.3稳压、TVS保护、电阻分压电量测量以及电机驱动选型与限流设置,并给出原理图与采集代码片段,便于对照搭建和排查故障。包内共1个PDF文件,约383KB,篇幅紧凑,适合作为方案速查与设计参考。已有237人学习,读者可从中获取模块划分、接口定义、电源与下载电路细节,以及将控制板复用于平衡小车、倒立摆等项目的思路。
1. 一块能拆开带走的小车控制板
舵机转向小车这个题目,玩过一次的人都知道坑不在算法,在硬件——车跑起来之后,主控、稳压、电量检测、驱动全焊死在一块板上,想拿其中一块去做下一个项目,只能拆了重焊。这份基于 STM32F103C8T6 的舵机小车电路设计,最值得看的地方就在这:整块控制板被切成了最小系统板、12V 转 5V 稳压模块、电池电量测量模块、电机驱动模块、舵机接口五块,每块都能单独拔下来复用到别的车上。
它适合两类人。一类是在洞洞板上搭过东西、想画自己第一块正式控制板的开发者,原理图不复杂,但该有的保护、下载、采样电路一个不缺,照着走一遍能把电源链路的思路理顺。另一类是手里已经有 2S/3S 航模电池和一堆散模块,想把它们整合成一台能遥控能跑的车的人。下面按电路分块拆,最后落到固件和改造上。
2. 模块化电路架构:五块板子怎么连成一台车
先把整机的关系摆清楚。整块控制板不是按功能画成一张大图,而是按"能不能复用"切块:最小系统板负责算,稳压模块负责把电池电压降到 5V,电量模块负责盯着电池别过放,电机驱动模块负责出力气,舵机接口负责转向。这种切法的直接好处是故障定位——车不动了,先量 5V 在不在,再看最小系统板的 LED 闪不闪,两步就能把范围缩到一块板内。
| 模块 | 输入 | 输出 | 复用到别的项目时的注意点 |
|---|---|---|---|
| 最小系统板 | 5V | 3.3V、下载口、IO | 排针定义固定,换底板要重新对针 |
| 稳压模块 | 6.4~12.6V | 5V / 2A 级 | 电感和大电容占面积,注意散热 |
| 电量测量 | 电池正极 | 0~1.2V 模拟量 | 分压比跟电池串数绑定,换 3S 要重算 |
| 电机驱动 | 5V 逻辑 + 电池 | 双路电机 | 大电流走线要单独铺铜,别和信号共地 |
| 舵机接口 | 5V + PWM | 50Hz 脉冲 | 舵机堵转电流大,最好单独一路供电 |
这张表是我建议你在画板前先填一遍的东西。很多人画第一版的时候把舵机电源和单片机 5V 挂在同一根细线上,舵机一抖单片机就复位,翻车翻得莫名其妙。表格里"复用注意点"那一列,本质上就是给自己留的后悔药。
2.1 最小系统板:STM32F103C8T6 的外围与可复用 IO
项目里这块板子有自己的一套命名(ForestS1),在平衡小车、倒立摆上都在用,说明它的引脚定义是稳定的,这对二次开发很重要——你换项目不用重新记一遍排针。
核心是 STM32F103C8T6 的最小系统:8MHz 主晶振加两个 20pF 负载电容,复位电路一个 10k 上拉加 100nF 电容到地,再加一个复位按键。每一对 VDD/VSS 引脚旁边都要放 100nF 去耦电容,VDDA 那一路额外串一个电感或者磁珠再并 1uF,模拟电源脏了 ADC 读数就会飘,这一点在后面电量采集那节会反复出现。
PC14、PC15 这两个脚在 C8T6 上是接 32.768kHz RTC 晶振用的,这块板子没外接,所以当普通 IO 用。但别高兴太早,这两个脚是低驱动能力的,官方手册里给的输出电流只有几毫安级别,接个 LED 都要串大电阻,接继电器或者蜂鸣器必然带不动。我自己一般把它们留给"读一个拨码开关"或者"检测一个到位信号"这种纯输入场景。
人机交互部分是三样东西:一个用户按键、一个 LED、一个电源指示灯,外加 OLED 接口。按键一端接地、一端接 IO 并配 10k 上拉到 3.3V,按下为低;LED 串 1k 限流电阻,3.3V 减去 LED 压降约 1.9V,落在电阻上 1.4V,电流 1.4mA 左右,亮度偏暗但够看状态,想亮一点改成 470R。OLED 用 I2C 接在 PB6/PB7 上最省事,硬件 I2C 或者软件模拟都行,调试的时候把电池电压、PWM 占空比、当前状态打到屏上,比串口打印直观得多,尤其是在车上跑起来的时候。
2.2 12V→5V→3.3V 两级稳压的参数怎么定
供电链路是两级:电池先经 LM2596S-5.0 降到 5V,再经 AMS1117-3.3 降到 3.3V 给单片机。
第一级用 LM2596 的理由很实际:2S 电池满电 8.4V、3S 满电 12.6V,压差大的时候线性稳压器(比如 7805)会把多余的电压全变成热,12V 降到 5V、电流 500mA,功耗就是 3.5W,TO-220 封装不加散热片撑不住。LM2596 是开关型,效率八十几个百分点,外围也简单:输入端 100uF 以上的电解电容、一个 SS34 肖特基续流二极管、一个 33uH 功率电感、输出端 220uF 电解加 100nF 瓷片。固定输出版本(-5.0)的反馈脚直接接地,不用再分压,这一点别画错,画成可调版本还得补两个电阻。
提示:LM2596 的续流二极管必须用肖特基,普通 1N4007 反向恢复慢,效率掉得厉害还会发热。SS34 的 3A/40V 规格对这种应用刚好。
第二级 AMS1117-3.3 是给单片机、OLED、以及各种数字传感器供电的。它压差只有 1.1V 左右,5V 降到 3.3V 完全够用,电流上限 1A,实际带 200mA 以内比较稳。输入端 10uF、输出端 22uF 钽电容或者瓷片,datasheet 上强调输出电容的 ESR 会影响稳定性,用普通电解在高低温下容易振荡,我一般直接上 22uF 瓷片,省心。
系统板自己还有一路 AMS1117-3.3,那是把 USB 的 5V 或者外部 5V 转成 3.3V 给板载器件用的,跟上面这级是并联关系,实际用的时候只保留一路就行,两路 3.3V 直接连一起会互相灌电流。板子上还放了 D1、D2 两个 TVS 管,一头接电源一头接地,作用是外部接线插错或者人体静电打进来的时候把浪涌钳住,TVS 选型看钳位电压要低于后级器件的耐压,SMAJ5.0A 这种就够。
2.3 电量检测:1/11 分压网络与 ADC 换算
航模电池没有电量计,只能靠电压估。2S 满电 8.4V,放到 6.4V(单节 3.2V)以下就是过放,过放一次电池基本就废了,鼓包、内阻变大,后面怎么充都回不来。所以哪怕不做低电报警,至少也要在 OLED 上把电压显示出来。
电路就是两个电阻分压:上臂 10k、下臂 1k,分压比 11:1。8.4V 进来变成 0.76V,12.6V 进来变成 1.15V,都远在 3.3V 参考电压之内,留了足够余量。下臂并联的 1k 对 ADC 来说源阻抗约 1k(10k 和 1k 并联再取近似),STM32 的 ADC 采样时间设长一点(比如 239.5 个周期)就完全够用,不用加运放跟随。
/* 电池电压采集:分压比 11:1,VREF = 3.3V,12 位 ADC */ #define BATTERY_CH ADC_Channel_1 int Get_battery_volt(void) { int Volt; /* Get_Adc 返回 0~4095 的原始码值,先乘后除减少截断误差 */ Volt = Get_Adc(BATTERY_CH) * 3.3 * 11 * 100 / 4096; /* 返回值单位 0.01V,840 表示 8.40V,用整数方便 OLED 显示 */ return Volt; }这段代码的关键在乘除顺序。如果先算Get_Adc()/4096,整数除法直接把小数部分抹掉,电压低于 3.3V 的时候读数永远是 0。先乘 3.3、乘 11、再乘 100 放大成整数,最后才除 4096,即使有浮点参与,误差也压在 0.01V 以内。分母用 4096 而不是 4095 是工程上的常见近似,因为 12 位 ADC 的满量程码值 4095 对应的是 VREF 略微偏下的位置,用 4096 算出来的曲线在高段更贴近实测,真要做到 ±0.05V 精度还是得用万用表逐个校准一个偏置。
光有这一句还不够,单次采样受电机干扰影响很大,经常一秒内跳 0.3V。实际做法是采 10 次,去掉最大最小各两个,剩下取平均:
int Get_battery_volt_filter(void) { int buf[10], i, j, tmp, sum = 0; for (i = 0; i < 10; i++) { buf[i] = Get_Adc(BATTERY_CH); delay_ms(2); } /* 冒泡排序后掐头去尾,抑制电机换向时的尖峰 */ for (i = 0; i < 9; i++) for (j = 0; j < 9 - i; j++) if (buf[j] > buf[j + 1]) { tmp = buf[j]; buf[j] = buf[j + 1]; buf[j + 1] = tmp; } for (i = 2; i < 8; i++) sum += buf[i]; return sum / 6 * 3.3 * 11 * 100 / 4096; }分压电阻的精度直接决定读数准不准,5% 的碳膜电阻两颗串起来误差能到 0.1V 量级,1% 金属膜是最低要求,想让显示值和万用表读数对得上,用 0.1% 的。这个成本差异只有几毛钱,但对"低电报警到底该不该信"这件事影响很大。
3. 一键下载电路:DTR/RTS 怎么把 BOOT0 和复位捏在一起
STM32F103 下载程序有三条路:SWD、串口 ISP、USB DFU。SWD 要 ST-Link,最方便但要额外硬件;串口 ISP 只靠一根 USB 线,成本最低,缺点是常规流程很烦——手动把 BOOT0 拨到高、按一下复位、点下载、等写完再把 BOOT0 拨回来、再复位一次,五次操作错一次就不成功。
这块板子把这一串手动动作做成了电路,上位机点一下"开始编程",软件自动控制 DTR 和 RTS 两根串口控制线,就能完成 BOOT0 拉高、复位、释放、进入 ISP 的全过程。
3.1 从 DTR/RTS 到 BOOT0 与 NRST 的时序拆解
先记住一个前提:芯片输出的DTR_N、RTS_N是DTR、RTS的反相,也就是上位机把 DTR 置低,DTR_N 输出高。这个是 RS232 电平定义决定的,不理解这一点,看原理图会完全绕晕。
时序走一遍:
- 上位机(McuISP 或 FlyMcu)把 DTR 拉低,DTR_N 变高;同时把 RTS 拉高,RTS_N 变低。
- RTS_N 为低,Q2 导通,BOOT0 被接到 3.3V,BOOT0 = 1。
- 同一时刻,DTR_N 为高使得 Q1 导通,NRST 被拉低,单片机处于复位状态。
- 延时约 100ms,等复位稳定。
- 上位机把 DTR 拉高,DTR_N 变低,Q1 截止,NRST 被 10k 上拉回高,复位释放;此时 RTS 仍为高,RTS_N 仍为低,BOOT0 保持为 1。
- 单片机复位释放时采样到 BOOT0 = 1,从系统存储器启动,也就是固化在芯片内部的 ISP 引导程序。
- 上位机发 0x7F 握手,收到 0x79 后开始擦除、写入、校验。
整个设计的巧妙之处在于:复位释放和 BOOT0 保持高这两个动作在时序上是错开的,靠的是 DTR 比 RTS 晚一步变化。如果两根线同时翻转,可能出现 BOOT0 已经拉低而复位还没释放的情况,那就直接跑用户程序了,下载自然失败。
3.2 三极管选型与接线里最容易画错的地方
两个三极管一个做高边一个做低边。Q1 把 NRST 拉到地,用 NPN(S8050 这类)就行,基极串 1k 限流电阻,集电极接 NRST,发射极接地。Q2 把 BOOT0 拉到 3.3V,得用 PNP(S8550),发射极接 3.3V,集电极接 BOOT0,基极串 1k 到 RTS_N——注意 PNP 是低电平导通,所以逻辑和 Q1 正好相反,这也是为什么两个信号一个叫 DTR_N 一个叫 RTS_N 却要交叉接。
BOOT0 自己还要一个 10k 下拉到地,保证没有下载动作时它老老实实是低电平,否则上电直接进 ISP,用户程序永远不跑。这个下拉电阻经常被漏掉,漏掉的现象就是"烧录完拔掉下载器,程序不动了"。
如果用 PC 端自己写脚本测试时序,可以这样复刻:
import serial, time # 打开与一键下载电路相连的串口 ser = serial.Serial('COM3', 115200, timeout=1) ser.dtr = False # DTR 拉低 -> DTR_N 为高 -> Q1 导通,NRST 被拉低 ser.rts = True # RTS 拉高 -> RTS_N 为低 -> Q2 导通,BOOT0 = 1 time.sleep(0.1) # 保持复位 100ms ser.dtr = True # 释放复位,BOOT0 仍为 1,进入 ISP time.sleep(0.05) ser.write(b'\x7f') # 发同步字节,正常应收到 0x79 print(ser.read(1).hex()) ser.close()这段脚本的重点不是让你真拿它烧程序,而是验证硬件时序通不通。收到79说明 BOOT0、复位、串口三件事都对上了;收不到就按顺序查:先量 BOOT0 在第二步是不是真的变成 3.3V,再量 NRST 有没有被拉低。这种从现象倒推的排查比反复点下载按钮高效得多。
注意:不同版本 pyserial 对 dtr/rts 属性的电平语义处理有差异,"True"到底对应线上高电平还是低电平,跟驱动和串口芯片都有关。第一次调试时用万用表量一下 RTS 引脚的实际电平,比对着文档猜可靠。
4. 动力与转向:TB6612 驱动和舵机 PWM 的配合
小车要跑,得有人出力气。这套设计标题里写的是 TB6612,正文里电机驱动那节讨论的是 A4950,两个方案都值得说,因为它们的适用场景不一样,选错了要么烧芯片要么带不动。
4.1 TB6612 的引脚、真值表与 PWM 配置
TB6612FNG 是双路 H 桥,一片能带两个直流电机,逻辑电源 VCC 2.7~5.5V,电机电源 VM 最高 15V,连续电流 1.2A、峰值 3.2A。对 2S/3S 电池、130 或者 260 小电机的小车来说完全够用。
关键引脚:VM 接电池正极(注意一定要并 10uF 以上去耦,否则换向瞬间的电压尖峰会打穿芯片)、VCC 接 3.3V 逻辑、GND 共地、STBY 拉高使能(拉低整个芯片待机,可以拿来做软件急停)、PWMA/AIN1/AIN2 控制 A 路、PWMB/BIN1/BIN2 控制 B 路,AO1/AO2 接电机。
真值表要背下来:AIN1=1、AIN2=0 正转;AIN1=0、AIN2=1 反转;两个都是 0 是滑行停止(电机两端悬空,车会溜);两个都是 1 是刹车(电机两端短接,停得干脆)。调速靠 PWM 打在 PWMA 上,频率 10~20kHz 比较合适,低于 1kHz 电机能听到啸叫,高于 20kHz 开关损耗上来。
/* TIM3_CH1 -> PA6 接 PWMA,PA4/PA5 接 AIN1/AIN2 * 72MHz / 72 = 1MHz 计数,ARR = 99 -> 10kHz PWM */ void Motor_PWM_Init(void) { GPIO_InitTypeDef gpio; TIM_TimeBaseInitTypeDef tim; TIM_OCInitTypeDef oc; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_TIM1, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); /* PWM 引脚复用推挽 */ gpio.GPIO_Pin = GPIO_Pin_6; gpio.GPIO_Mode = GPIO_Mode_AF_PP; gpio.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &gpio); /* 方向脚普通推挽输出 */ gpio.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5; gpio.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOA, &gpio); tim.TIM_Period = 99; /* ARR: 100 个计数一个周期 */ tim.TIM_Prescaler = 71; /* PSC: 72MHz / 72 = 1MHz */ tim.TIM_CounterMode = TIM_CounterMode_Up; tim.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInit(TIM3, &tim); oc.TIM_OCMode = TIM_OCMode_PWM1; oc.TIM_OutputState = TIM_OutputState_Enable; oc.TIM_Pulse = 0; /* 初始占空比 0 */ oc.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC1Init(TIM3, &oc); TIM_Cmd(TIM3, ENABLE); }参数这样定:预分频 71 让计数器以 1MHz 跑,自动重装值 99 让一个周期 100 个计数,PWM 频率 10kHz,占空比分辨率 1%。如果想要更细的调速手感,把 ARR 改成 999,频率降到 1kHz,分辨率变成 0.1%,代价是电机有轻微啸叫——这个取舍在小车上很典型,看你更在意噪声还是低速平顺性。
方向控制封装成一个函数,把"速度带符号"这个语义做进去,上层代码就不用关心 AIN1/AIN2 的高低电平:
/* speed 范围 -100 ~ 100,负数反转 */ void Motor_Set(int speed) { if (speed > 100) speed = 100; if (speed < -100) speed = -100; if (speed > 0) { GPIO_SetBits(GPIOA, GPIO_Pin_4); GPIO_ResetBits(GPIOA, GPIO_Pin_5); } else if (speed < 0) { GPIO_ResetBits(GPIOA, GPIO_Pin_4); GPIO_SetBits(GPIOA, GPIO_Pin_5); speed = -speed; } else { /* 两脚同低为滑行停止;同高则是刹车,按需切换 */ GPIO_ResetBits(GPIOA, GPIO_Pin_4 | GPIO_Pin_5); } TIM_SetCompare1(TIM3, speed); /* 占空比 0~100 对应 CCR 0~99 */ }这里有个细节要留意:从正转直接切到反转,如果不在speed == 0附近停留,H 桥会经历一个瞬间的电流冲击,轻则触发过流保护,重则烧桥。带载调试时我一般做成"先减速到 0,等 20ms,再反向加速",虽然响应慢了一点,但芯片活得久。
4.2 舵机转向:50Hz 脉冲和机械中位的标定
转向不是靠差速,是前面一个舵机拉着转向机构。舵机吃的是标准 50Hz PWM,脉宽 0.5ms 到 2.5ms 对应左右极限,1.5ms 是中位。但绝大多数模拟舵机全行程是 180°,直接给极限脉宽会撞到机械限位,舵机堵转,电流瞬间上到 1A 以上,然后开始抖或者烧齿轮。稳妥的做法是只用 1.0ms 到 2.0ms 这一段。
STM32 的定时器配 50Hz 很简单,1MHz 计数、ARR 设成 19999:
/* TIM2_CH1 -> PA0 接舵机信号线,50Hz,脉宽 500~2500us */ void Servo_Init(void) { /* 时基和 GPIO 初始化同上,PSC = 71, ARR = 19999 */ TIM_SetCompare1(TIM2, 1500); /* 中位,1.5ms */ } /* angle: -45 ~ +45 度,映射到 1000~2000us,避免打到机械限位 */ void Servo_SetAngle(int angle) { int pulse; if (angle > 45) angle = 45; if (angle < -45) angle = -45; pulse = 1500 + angle * (500 / 45); /* 每度约 11us */ TIM_SetCompare1(TIM2, pulse); }机械中位这件事必须标定。装配的时候舵机摆到 1.5ms 对应的位置,再把转向拉杆装上去,让两个前轮目测朝正前方。如果先装拉杆再上电,中位偏了几度,直行的时候车会一直往一边偏,只能靠软件在角度上加个补偿量硬掰——这不是不行,但下次换舵机又得重新标一次,属于把机械问题拖到软件里解决。
舵机电源单独走一路,不要和单片机共用一根细线。舵机启动瞬间的浪涌电流能到 1A,线阻稍微大一点,5V 就被拽下来 0.5V,单片机立刻复位。我的习惯是舵机直接从 5V 稳压输出端单独拉一对粗线,中间并一个 470uF 电解吸收浪涌,信号线串 100R 电阻抑制振铃。
4.3 A4950 的取舍:什么时候 TB6612 不够用
正文里电机驱动那节讨论的是 A4950,这不是矛盾,而是两个档位的选择。TB6612 连续 1.2A、峰值 3.2A,A4950 峰值能到 3.5A 而且靠外接采样电阻设定限流,抗堵转能力强。车轻、轮子小、地面平,TB6612 完全够;车重、轮子大、经常原地打转或者爬坡堵转,TB6612 会进过热保护,表现为"跑一会儿突然没劲,凉下来又能跑"。
A4950 的限流电阻算法是ItripMAX = VREF / (10 × Rs)。Rs 取 0.25Ω,要限到 2A,VREF 就得给 5V,也就是直接接到 5V 电源上。这个电阻的功率要算一下:2A 流过 0.25Ω 是 1W,必须用 2512 封装的合金电阻,0603 上去直接冒烟。
A4950 的缺点是控制方式不一样,一片需要两路 PWM 才能同时控制方向和调速,软件上比 TB6612 的"方向 + 单路 PWM"麻烦一点,而且需要额外占用两个定时器通道。所以我的建议是:先把 TB6612 调通,把整车逻辑、舵机中位、电量显示都验证完,如果发现确实动力不足,再换 A4950 的驱动板,接口留成一样的排针就行——这就是模块化设计带来的好处,换驱动不用动底板。
5. 避坑与排查:下载失败、复位重启、ADC 飘的五个现场
电路画完、板子打回来,真正的战斗才开始。下面五条都是这套设计里真实会撞上的,每条按"看到什么、为什么、怎么办"写。
5.1 下载与启动类问题
现象一:点了一键下载,软件提示"无法连接",或者连接成功但写完程序不跑。
原因是 BOOT0 的下拉电阻漏了,或者 Q2 的基极电阻接到了 DTR_N 而不是 RTS_N。BOOT0 没有下拉时是浮空的,上电瞬间可能被判断为高电平,芯片直接进 ISP 模式,用户程序永远不执行;而基极接错线的话,上位机的时序根本作用不到 BOOT0 上,ISP 握手自然失败。解决办法是先用万用表量:不接下载软件时 BOOT0 应该稳定在 0V,下载软件点开始后瞬间变成 3.3V。这两个状态有一个不对,就往回查 Q2 和三极管周边的电阻。
现象二:下载成功,程序也跑,但每次按复位就丢程序。
这不是电路问题,是 FLASH 的读保护或者写保护被误设了,也可能是下载软件里勾了"编程后执行"但没勾"校验"。换一个下载工具重新全片擦除一次,再下载就正常。另外注意 C8T6 的容量是 64KB,程序编译出来超过 64KB 也会出现"下载完成但不运行",编译输出窗口的 size 那一行每次都要扫一眼。
5.2 电源与干扰类问题
现象三:电机一转,OLED 花屏,偶尔单片机复位。
最典型的电源干扰。原因有两个层次:一是电机驱动和单片机共用一条 5V 走线,电机换向瞬间的大电流在走线电阻上产生压降,单片机供电跌破 2.0V 的复位阈值;二是地线处理不当,大电流回路和模拟地在一点上连,共模干扰直接灌进 ADC 和 I2C。解决分三步:电机电源从电池端单独拉线到驱动板,不经过 5V 稳压;电机两端各并一个 100nF 瓷片加一个 10uF 电解,就近吸收换向尖峰;整板地线改成星型单点接地,功率地和信号地在稳压输出电容的负极处汇合。
现象四:舵机一直轻微抖动,不动的时候也在嗡嗡响。
要不就是脉宽超出机械行程,舵机在限位上反复修正;要不就是供电纹波太大,舵机内部控制环在追一个抖动的目标。先软件上把角度限制在 ±45° 试一下,还抖就量 5V 上的纹波,超过 100mV 说明滤波不够,加 470uF 电解和 100nF 瓷片并联。还有一种情况是 PWM 信号线太长又没有串阻,边沿振铃被舵机当成多次脉冲,串 100R 电阻能解决大半。
5.3 采样与显示类问题
现象五:电池电压显示一会儿 8.3V 一会儿 7.6V,低电报警乱跳。
三个原因按概率排:分压电阻用了 5% 精度的碳膜,两颗电阻误差叠加能到 3%,8.4V 上就是 0.25V 的偏差;ADC 采样时间设得太短,1k 的源阻抗在快速采样下采不到真实值;没有做软件滤波,单次采样被电机噪声污染。解决办法:换 1% 金属膜电阻,ADC 采样周期设到 239.5 周期,采样结果做去极值平均,再给低电阈值加个 0.2V 的回差(低于 6.6V 报警,高于 6.8V 才解除),避免在阈值附近反复触发。
提示:如果要做电池串数自适应,分压电阻不要焊死,改成排针加跳线帽,2S 用 10k/1k,3S 换成 20k/1k,换电池不用拆板。
6. 二次开发:把模块拆走、把主控换掉的两条路
这套板子的价值在于"可拆",所以二次开发的第一条路不是改代码,是改装配方式。最小系统板、稳压模块、电量模块、驱动模块全部用排针加排母连接,而不是贴片焊死。代价是接触电阻和振动松脱的风险,收益是你今天做小车、明天做平衡车,除了底板其他全带走。我见过太多人第一块板子图省事全部贴片,第二个项目只能重新打样,钱和时间都翻倍。
如果只是想让车更好用,改动量最小、收益最明显的是这三处:
| 改造点 | 现状 | 改法 | 收益 |
|---|---|---|---|
| 速度闭环 | 开环给占空比 | 电机轴加霍尔或编码器,定时器编码器模式读脉冲 | 爬坡不减速,直线不跑偏 |
| 低电保护 | 只显示电压 | 电压低于阈值时限制最大占空比,再低则停 | 保护电池不过放 |
| 参数在线调 | 改代码重烧 | 按键 + OLED 做菜单,参数存内部 FLASH 最后一页 | 调 PID 不用带电脑 |
编码器接口用 TIM4 的编码器模式最省引脚,PA0/PA1 之外再挑两个脚就行;参数存 FLASH 要注意 C8T6 的页大小是 1KB,最后一页地址 0x0800FC00,写之前必须先擦除整页,且擦写寿命只有 1 万次,别在 1ms 定时器里存参数。
第二条路是换主控。这套板子的最小系统板引脚定义是固定的,如果你想换成主频更高、带硬件浮点的型号,最省事的做法是画一块引脚兼容的转接板,把新主控的 SWD、串口、电源引脚引到同样的排针位置上,底板一行不用改。换完之后第一件事不是写业务代码,是拿示波器把舵机 PWM 和电机 PWM 的周期、占空比范围重新量一遍——不同型号定时器的时钟树不一样,同样的 PSC 和 ARR 算出来的频率能差好几倍,这个坑我踩过一次,车一上电直接满舵打死,差点把齿轮崩了。从那以后我每次换主控,都强制先跑一遍"PWM 频率和占空比极限"的裸机测试,用示波器确认波形再往上堆逻辑。
希望这些能帮你少走点弯路。
本文还有配套的精品资源,点击获取