
我一直觉得STM32入门这件事最大的坎儿不是寄存器、不是HAL库而是手里没有一样能实实在在动起来的东西。流水灯点得再花哨那也只是在“证明单片机会喘气”。直到你接上一个TT马达让轮子转起来让小车跑起来那种“我真的在做一个系统”的感觉才算是到位了。这个教程我拖了很久没写主要是觉得TT马达太基础随便搜搜到处都是资料。但真到自己上手带项目才发现越是这种“人人都讲”的东西越容易在细节上翻车。供电不足导致复位、PWM频率不对导致电机嗡嗡叫但不转、驱动芯片选错直接烧IO——这些坑网上教程很少给你讲透。今天这篇就针对TT马达从硬件原理到手写驱动代码再到实际调试中会踩的坑一次性说完。1. 项目概述与核心思路拆解1.1 我们到底在做一件什么事TT马达也叫TT减速直流电机是各种智能小车、扫地机器人、机械臂底座项目里最常用的执行机构。它的核心结构是“直流电机本体 齿轮减速箱”通过减速箱把电机的高转速降下来同时增大输出扭矩让小车能真正“爬”起来而不是原地打滑空转。这次项目的目标很明确用STM32控制一个TT马达实现三件事——让马达转起来、控制转速、控制方向。听起来简单但背后涉及的知识点其实覆盖了STM32非常核心的几个外设GPIO的输出模式配置、定时器的PWM输出、以及外部中断或者编码器接口如果选带编码器的TT马达的速度反馈。我用的开发环境是STM32CubeMX Keil MDK芯片型号选了STM32F103C8T6也就是大家常说的“蓝丸”核心板。选这颗芯片没什么特别理由就是资料多、便宜、皮实新手折腾坏了也不心疼。如果你用的是F407或者G031思路完全一样配置界面稍作调整即可。1.2 为什么用TT马达而不是其他电机很多初学者会纠结直流电机种类那么多有刷的、无刷的、步进的、舵机为什么教程都选TT马达无刷直流电机的转速方向和有刷直流电机其实是两个方向的门槛。无刷电机控制精度高、寿命长但是需要专门的驱动电路也就是电调还要处理换相逻辑学习曲线陡。步进电机定位精准但又重又慢驱动模块也贵而且对新手来说“转多少度”这种控制方式反而不如“转多快”直观。TT马达属于有刷直流电机控制逻辑最简单给正电压就正转给负电压就反转电压大小决定转速。配合一颗几块钱的TB6612或者L298N驱动模块再加上STM32的PWM就能实现“电压可调 方向可换”的全功能控制。这也是为什么几乎所有智能小车教程都拿它当入门首选。1.3 系统整体方案设计整个系统的硬件链路是这样的STM32的GPIO输出方向控制信号决定电机正转还是反转STM32的定时器通道输出PWM信号决定电机转速PWM信号和方向信号共同进入电机驱动芯片TB6612TB6612根据输入信号输出对应大小和方向的电压给TT马达TT马达带动减速齿轮输出扭矩驱动车轮。软件层面我用HAL库来做整体流程是CubeMX初始化时钟和GPIO配置一个定时器的PWM通道然后在主循环里通过改变比较寄存器CCR的值来改变占空比再通过两个普通IO的电平组合来切换方向。这个方案的好处是把“逻辑控制”和“功率驱动”完全分离。STM32的IO口只能输出3.3V、最大20mA左右的电流根本带不动TT马达启动时可能达到几百毫安的电流。中间加了一级驱动芯片既保护了MCU又让功率路径更干净。2. TT马达硬件原理解读与驱动方案对比2.1 TT马达的电气特性与引脚定义先搞清楚TT马达本身是什么脾气后面接线才不会懵。标准的TT马达工作电压通常有两个规格3V-6V和6V-12V。智能小车套件里最常见的其实是3-6V的版本使用两节或者四节AA电池供电空载转速一般在200-300RPM左右取决于减速比常见1:48。堵转电流可能达到1A以上正常负载运行时电流大约在100-300mA之间。TT马达只有两个引脚没有正负之分因为直流电机本身就是对称的——你给它正向电压它就正转反过来接就反转。这也是它有刷电机做方向控制的原理通过H桥电路切换供电极性。人手一个的TT马达其实分两种不带编码器的纯动力马达和带编码器的带AB相霍尔编码器一般在电机尾部多出一块小PCB。编码器版本虽然贵几块钱但如果你后面想做闭环速度控制比如让小车走直线那编码器就是必需品。本次教程先讲不带编码器的版本把PWM调速和方向切换讲透编码器的使用放到下一篇详说。2.2 电机驱动的核心H桥到底在干什么方向控制靠的就是H桥电路。这个名字很形象——四个开关管组成一个“H”形状电机接在中间横杠上。具体工作原理不复杂当左上和右下两个开关管导通时电流从左边流向右边电机正转当右上和左下两个开关管导通时电流从右边流向左边电机反转。注意绝对不能把同一侧的两个开关管同时导通那就直接短路了业界称之为“直通shoot-through”轻则烧管子重则起火。我们自己用分立元件搭H桥不是不行但性能必然拉胯尤其是死区控制、过流保护这些细节根本做不到位。所以项目里一律推荐直接用集成驱动芯片——目前市面主流的型号有两个驱动芯片工作电压峰值电流导通压降接口逻辑适用场景L298N5V-35V2A高约2V左右5V TTL大功率电机、双电机小车TB66122.7V-5.5V1.2A低约0.3V3.3V/5V均可TT马达、小功率直流电机L298N是经典老将缺点是导通压降太大——2V左右的压降意味着明明给了6V电源到了电机端可能只有4V白白浪费电能。而且它的逻辑电平要求5V跟STM32的3.3V IO直接对接不太友好通常还得加电平转换或者用5V容忍引脚硬怼。TB6612就是当前TT马达项目的首选供电范围覆盖3V-6V正好匹配TT马达工作电压。导通压降只有0.3V左右发热小。逻辑输入兼容3.3VSTM32的IO可以直连不用任何电平转换电路。模块体积也很小面包板和洞洞板都能轻松放得下。2.3 PWM调速的原理与选择电压高低决定电机转速这个道理大家都懂但STM32并没有“直接输出可变电压”的功能只能输出数字信号——要么高电平要么低电平。调速就需要靠PWM脉冲宽度调制来模拟。PWM的精髓在一个“占空比”上一个周期里高电平所占的比例就是占空比。在一定频率下虽然IO口输出的是0到3.3V跳动方波但电机的机械惯性会滤掉这种快速波动等效于接收了一个平均电压平均电压 3.3V × 占空比。我举个具体数字帮助理解假设频率是10kHz一个周期就是100微秒如果高电平占30微秒那占空比就是30%等效电压就是3.3V × 30% ≈ 1V。换到电机上即使系统供电是6V驱动器输出的等效电压也会相应调整速度自然就降下来了。这里就引出了PWM频率选择的学问。如果频率太低比如50Hz电机会一冲一冲地转伴随明显的“滋滋”或者“嗡嗡”声响这就是典型的脉动转矩。但频率也不能太高因为驱动芯片有开关损耗频率高了芯片发热增加电机电感也会对高频信号产生阻抗导致实际电流反而上不去。TT马达这种小电机的甜点频率在10kHz到20kHz之间。我用的是10kHz驱动芯片不热电机声音也小。不要听网上有些人说“频率随便设个1kHz就行”那只能让电机转转起来的噪音会让你怀疑人生。3. 实操过程从CubeMX配置到代码实现3.1 CubeMX引脚分配与定时器选择硬件连接先定下来。我用的引脚分配如下不一定是最优解但你照着接一定没问题TB6612的PWMA接STM32的PA8TIM1_CH1TB6612的AIN1接PB0TB6612的AIN2接PB1TB6612的STBY接3.3V一直使能TB6612的VM接6V4节AA电池或2节锂电池TB6612的VCC接3.3V逻辑电源TB6612的GND跟STM32共地非常重要不共地一切白搭。打开STM32CubeMX芯片选STM32F103C8T6时钟配置为外部8MHz晶振然后PLL倍频到72MHz主频。这一步很关键因为PWM输出的实际频率跟定时器的时钟源直接相关。PA8对应的TIM1_CH1TIM1是挂在APB2总线上的高级定时器时钟频率在72MHz主频下就是72MHz。设置PWM频率10kHz换算预分频器Prescaler和自动重装载值Counter Period的过程是这样的预分频值PSC 72MHz / 10kHz / ARR - 1为了让占空比调节有细腻度我期望ARR值在100-1000之间取整后选择PSC 72 - 1ARR 100 - 1。这样算下来实际PWM频率 72MHz / 72 / 100 10kHz完美。占空比的分辨率就是1%也就是100档对TT马达调速完全够用。生成代码前记得在TIM1的Configuration页面里把Channel1设置为PWM Generation CH1然后在Parameter Settings里把Pulse设置为50初始占空比50%。3.2 HAL库PWM代码实现生成工程后代码修改量其实非常小。先看PWM的启动部分这个必须加在初始化完成之后/* 启动PWM输出 */ HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_1);注意HAL_TIM_PWM_Start只是启动了PWM波形输出但此时比较寄存器里的值还是CubeMX里设置的初始值。这个时候电机应该是维持在一个固定速度转还不能调速我们需要在程序运行过程中不断改变比较值。调节占空比的核心代码就一行/* 设置占空比第二个参数取值范围0-100 */ __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, duty_cycle);这个宏的本质就是修改TIM1-CCR1寄存器的值。PWM频率是固定的每个周期内高电平时间的长短完全由CCR1决定所以把CCR1从0改成100就等于占空比从0%变成100%电机从停止到全速。方向控制就更简单了就是两路GPIO的配合/* 正转 */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET); /* 反转 */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET); /* 刹停两端同时拉低/拉高等效于短路电机 */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);这里有个细节值得多说一句。很多人以为停转就是把PWM占空比设为0这其实不对。占空比为0等于电机两端没有电压电机靠惯性继续转这叫“滑行”。真正要做到快速停止需要把AIN1和AIN2同时拉高也就是刹车。H桥两侧下管导通电机两端短路转子的动能被绕组电阻快速消耗电机很快就停了。如果你做的是小车项目想让它精准停在某条线前或者某个地点附近用滑行大概率会冲过头必须做刹停处理。3.3 一个完整的电机测试Demo理论说再多不如跑起来看效果。写一个简单的测试程序电机先正转占空比从0平滑加到100再降回来然后反转重复一遍。这个过程能看到转速的平滑变化也能验证PWM通道和方向控制是否同时正常工作。while (1) { /* 正转加速 */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET); for (uint16_t duty 0; duty 100; duty) { __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, duty); HAL_Delay(10); } /* 正转减速 */ for (uint16_t duty 100; duty 0; duty--) { __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, duty); HAL_Delay(10); } /* 刹车1秒 */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET); __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, 0); HAL_Delay(1000); /* 反转加速 */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET); for (uint16_t duty 0; duty 100; duty) { __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, duty); HAL_Delay(10); } HAL_Delay(2000); }这个Demo跑起来后你用手捏住电机轴应该能感觉到扭矩在持续变化松开后转速随占空比线性起伏。如果一切正常那恭喜你STM32控制直流电机最核心的部分已经拿下了。4. 常见问题与调试心得实录4.1 电机不转排查顺序很重要但凡新手做这个项目十个有九个卡在“电机不转”上然后就开始胡乱换线、乱试。我的建议是不要急按照顺序一步步排查。首先量一下TB6612的VM和GND之间的电压。很多新手用电脑USB口或者直接从STM32的5V引脚给驱动模块供电这是最典型的坑。TT马达启动瞬间的电流可以飙到1A以上而USB口的输出能力一般只有500mA电压直接掉到4V以下电机当然不转。供电一定要用独立的电池组或者质量靠谱的DC-DC模块。接着看逻辑部分。TB6612的VCC引脚接3.3V了吗STBY有没有拉到高电平我见过太多人把这个使能脚悬空然后电机死活不转。STBY是待机控制脚低电平会让芯片进入待机状态所有输出都不工作必须接高电平才正常。然后是控制信号有没有真的送进芯片。用示波器或者万用表测一下AIN1和AIN2的电压跟代码逻辑对不对得上。如果没示波器万用表也能看个大概正转时AIN1应该是3.3VAIN2是0V反转就反过来。如果这两个信号都不对就别赖驱动芯片问题出在STM32的GPIO配置上回去看看CubeMX是不是把引脚模式配成ADC或者复用功能了。4.2 电机转起来之后噪音大得像拖拉机跑通之后第二个常见的坑就是电机振动和噪音。这个时候先不要怀疑电机坏了大概率是PWM频率太低了。我试过用1kHz的频率驱动电机转是转但那个“嗡嗡”声跟电锯差不多用手摸电机外壳都能感觉到明显的振动。把频率提升到10kHz以上声音立刻细腻很多听起来只是电机换向的轻微“沙沙”声。如果提高频率后噪音还压不下去那就要检查PWM和电源之间的滤波电容了。TT马达这种带换向器的有刷电机电刷在换向瞬间会产生大量火花和反向电动势噪声不仅干扰自身运行还会沿着电源线往STM32那边窜。解决方法是电机引脚两端直接并联一个100nF的陶瓷电容再并一个10uF-100uF的电解电容。这个操作能极大改善系统稳定性很多诡异的重启、死机问题都是这个噪声引起的。4.3 电机能转但速度不受控制或者完全跑飞如果你发现代码里改了占空比电机速度根本不变或者ICU直接跑飞多半是PWM初始化没生效。HAL库有几个坑是新手常踩的。比如CubeMX里明明开启了TIM1的PWM通道但生成的代码中漏掉了自动启动的代码。解决方法是手动调用HAL_TIM_PWM_Start。还有更隐蔽的用了错误的定时器句柄。比如你配置的是TIM2结果代码里操作的是htim1系统不会报错但PWM就是出不来。追查这种问题非常痛苦建议从一开始就养成习惯——每次改动后在初始化代码周围加注释标明用的是哪个定时器哪个通道。4.4 一个容易被忽略的问题共地这是嵌入式开发里最常见也最容易被忽视的细节——所有模块的地必须连在一起才能以同一个电压参考点为基准工作。如果你发现一个奇怪的现象STM32单独下载程序正常跟电脑通信正常但一接上电机电源整个系统就乱套——要么随机复位要么串口数据乱码。先别去查代码逻辑拿万用表量一下驱动模块的GND和STM32的GND之间是不是0V。如果不是把它们接上90%的妖魔鬼怪瞬间消失。共地问题还会引发一个更隐蔽的症状电机转动时STM32偶尔复位。这是因为电机电流经“地回路”倒灌到了单片机的地线上造成地电位被抬高。用独立的地线把功率地和逻辑地分开走回到电源端再单点汇接能有效避免这个问题。4.5 实测下来的电源选择和速度感受项目调试了一段时间最后说说实际使用的电源方案。我测试了三种供电方式2节AA电池串联3V能转但堵转时速度掉得厉害明显供不上劲4节AA电池串联6V最理想的工作状态加速有力调速线性度也好3.7V锂电池单节能用但转速偏低驱动力不太够适合做玩具级别的小车。所以如果你做的是双马达后驱小车建议直接上4节AA电池或者两节3.7V锂电池串联再通过降压模块稳压。记住电源的额定电流最好在2A以上否则电机同时启动时电压跌落会非常明显。最终的项目实测结论是这样的STBY接3.3V常开AIN1、AIN2控制方向PWMA输入10kHz的PWM信号控制等效电压TT马达整套系统运行稳定调速响应非常灵敏——从最低转速到最高转速目测响应时间不到100毫秒足够满足绝大多数小车项目的需求。5. 从驱动到闭环下一步可以怎么玩如果这篇文章你已经完全消化了TT马达在你手里应该已经能顺畅地正反转调速了。接下来值得往三个方向延伸一下。第一个方向是做双电机差速控制。小车有两个后轮、两个TT马达你需要用第二个定时器的另一个通道输出PWM再配合两路GPIO控制另一个驱动芯片。差速转向的核心逻辑是两个轮子转速不同车身自然转弯。这里你会遇到一个问题——两个电机的机械特性不可能完全一致同样的占空比转速也不一样所以要加入编码器反馈来统一转速。第二个方向就是带编码器的TT马达。电机尾部那个多出来的霍尔编码器每转一圈会产生固定数量的脉冲常见1圈约11或20个脉冲经过减速箱之后轮子转一圈大约对应几百个脉冲。用STM32的定时器输入捕获模式去测脉冲频率就能反算出车轮转速从而做PID闭环控制。第三个方向是把控制搬到无线端。用ESP8266或者蓝牙模块接收手机指令STM32解析指令后控制电机转速和方向。这一步做完一台可以手机遥控的智能小车就成型了。按我个人的经验从“让电机转起来”到“根据指令稳定地转到指定转速”中间需要经历的概念跨度其实很大涉及定时器捕获、中断服务函数、PID整定、串口协议解析等一整套知识链。但你把这篇文章的基础打扎实了后面的每块砖都能一块块垒上去。别急慢慢来。