ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32控制TT马达:PWM调速、H桥驱动与PID闭环实战

STM32控制TT马达:PWM调速、H桥驱动与PID闭环实战 做小车、做机械臂、做自动浇花装置只要是“带得动”的活儿绕不开的第一个执行元件基本都是直流电机而TT马达又是直流电机里最接地气的那一类。物美价廉、自带减速箱、装上轮子就能跑所以从课程设计到各种竞赛小车几乎到处都有它的身影。这篇内容我打算把STM32、TT马达、直流电机这一整条驱动链路完整拆开讲一遍从H桥驱动原理、硬件接线、HAL库代码到PID调速和常见问题排查一次说透。适合刚接触电机控制的同学也适合想快速把电机驱动模块化抄走的开发者。1. TT马达与控制方案的底层逻辑1.1 TT马达到底是什么选型时别看错减速比TT马达这个名字其实是业内叫法全称是TT Motor因为圆柱形外壳加一颗塑料齿轮箱大家干脆都叫它TT马达。它本质上不是一个单独的电机而是“直流电机本体 减速齿轮箱 输出轴”的组合体。你拿到手的那颗东西拆开里面就是一颗普通的空心杯或者永磁直流电机输出轴经过齿轮减速后转速降下来、扭矩提上去这样才带得动轮子或者机械臂。选型时最容易看走眼的参数是减速比。市面上的TT马达减速比常见有1:20、1:48、1:90甚至1:120数字越大输出转速越慢扭矩越大。做小车底盘常用1:48在6V供电下空载转速差不多在150到200转每分钟配合直径65mm的轮子跑起来速度适中既不会慢到让人着急也不至于快到失控。减速比选小了扭矩不够小车起步会抖甚至原地不动选大了极速太低跑起来像蜗牛。所以拿到模块第一件事看减速比别只问“这是不是TT马达”。此外还有一种带编码器的TT马达尾部多了一块小PCB上面有两个霍尔传感器输出A、B两路方波信号用于测速和判断旋转方向。普通版就只能靠PWM开环控制带编码器版才能做闭环调速。做两轮差速小车或者需要精确控速的项目建议一步到位买带编码器的版本差价不大后面能省很多事。1.2 为什么控制TT马达要用PWM加H桥直流电机要调速本质是调节它两端的平均电压要反转本质是改变电流方向。这两件事落到STM32的控制方案上就对应两个概念PWM和H桥。PWM调速的原理一句话就能讲清楚通过高频开关电压让电机在一个周期内部分时间通电、部分时间断电由于电机转子和绕组有惯性它感受到的平均电压等于“占空比 × 供电电压”。占空比是50%时等效电压大概就是一半。这个逻辑和生活里的电风扇很像你拧低档开关风扇不会一会儿转一会儿停而是整体转速变慢只不过电风扇是靠绕组抽头改变电压PWM是靠高速开关模拟出不同的平均电压。TT马达本身是一个电感负载不像灯珠那样直接吃平均电压就能稳定工作所以PWM频率也不能太低。频率太低电流纹波大电机会一冲一冲还会发出滋滋的啸叫。后面我会专门讲频率选多少合适。反转为什么必须要H桥因为电机只有两根线你给A高B低它正转想要反转就得让A低B高也就是在电机两端切换电源极性。H桥由四个开关管组成对角导通控制电流方向正好完成这个切换。你用分立元件搭H桥当然也可以但还得考虑续流、死区、过流保护这些问题对新手太不友好。所以实际项目里基本都是买一颗集成电机驱动芯片里面的H桥、逻辑电路和保护电路都已经做好你只需要告诉它方向和占空比就行。1.3 L298N、TB6612FNG、DRV8833驱动芯片怎么选TT马达常用的驱动方案有三类我先把对比摆出来驱动方式类型管压降逻辑电压适用场景主要短板L298N双H桥三极管方案约2V5V教学演示、大电流电机发热严重低压场景效率低TB6612FNG双H桥MOSFET方案约0.5V2.7V~5.5V小型直流电机、TT马达单路峰值电流有限约1.2ADRV8833双H桥MOSFET方案低2.7V~5.5V逻辑VM可到10.8V小型电机、电池供电设备小封装接线需要转接板个人首选TB6612FNG原因很简单STM32的IO电平是3.3VTB6612的逻辑电源和支持的电平范围都在3.3V附近不需要额外做电平转换管压降只有零点几伏6V电池进去电机端还能有接近5.5V扭矩不容易打折。L298N虽然便宜又经典但它是老一代三极管方案管压降能吃掉将近2VTT马达这种低压电机本来就电压不高再被吃掉一段转速和扭矩都会明显缩水而且芯片烫得很。DRV8833性能也不错就是封装小手工焊接或者用转接板都稍微麻烦一些新手用TB6612最省心。如果你手里只有L298N也不是不能用但心里要有数6V供电下电机实际分到的电压可能只有4V左右在实际项目中如果你发现小车动力明显弱优先怀疑压降问题而不是PWM配置错了。2. 硬件接线与驱动电路焊错一个电容都可能翻车2.1 TB6612FNG引脚功能与STM32接线规划TB6612FNG模块上的引脚看起来有点多其实功能非常清晰。它内部有两路独立H桥分别叫A路和B路可以同时驱动两个电机正好对应两轮差速小车的左右轮。每个电机需要三个控制信号两路方向输入AIN1/AIN2和一路PWM输入PWMA电机输出是AO1/AO2两个端子。另外模块上有VM、VCC、GND三个电源引脚这里最容易犯错VM是电机电源接电池正极或者可调电源的电机端VCC是逻辑电源接STM32的3.3VGND一定要和STM32的GND连在一起。再有一个STBY引脚这是芯片的使能脚必须拉高电平芯片的PWM输出才会真正生效悬空的话电机死活不转这是新手翻车率最高的原因之一。我实际项目里常用的引脚分配如下功能引脚说明左电机PWMPA6TIM3_CH1右电机PWMPA7TIM3_CH2左电机方向1PB0AIN1左电机方向2PB1AIN2右电机方向1PB10BIN1右电机方向2PB11BIN2左电机编码器A/BPB6/PB7TIM4_CH1/CH2右电机编码器A/BPA0/PA1TIM2_CH1/CH2这里特别说明一下如果你只是单电机测试不用把左右轮都接上PWM占空比和方向组合的逻辑是一样的。如果你准备做两轮差速闭环那左右电机编码器都需要独立定时器避免和PWM定时器冲突。2.2 电源共地与滤波电机一启动MCU就复位的根源TT马达工作时电流动态变化非常大。空载电流可能只有几十毫安但启动瞬间或者堵转时电流能飙升到几百毫安甚至接近1A。如果你把STM32的USB供电和电机供电混在同一个电源上电机一启动电压被拉低STM32瞬间掉电复位表现就是小车一动单片机就重启程序永远跑不完。解决办法就是分开供电STM32用USB或者单独的5V供电电机电源用独立的电池组或者开关电源比如两节18650串联约7.4V或者4节5号电池约6V。两套电源之间只需要共地也就是把STM32的GND和驱动板的GND接在一起给控制信号提供一个共同的参考电位。这个共地不是可选项是必选项不共地的话PWM信号的电平参考点不对电机可能会出现各种诡异的小抖动或者不转。经验上我会在电机电源两端并联一颗100uF到470uF的电解电容再并一颗0.1uF瓷片电容尽量靠近驱动芯片的VM和GND引脚。因为电机换向的瞬间线路电感会产生尖峰毛刺电容能把这些毛刺吸收掉一部分。如果发现驱动芯片旁边的电容摸起来烫手多半是电容耐压不够或者极性焊反了换一颗立刻就好。2.3 方向控制引脚与PWM引脚的组合逻辑TB6612的输入逻辑控制电机工作状态这一点用表格记最方便AIN1AIN2PWMA电机状态10正占空比正转01正占空比反转00任意滑行惯性停止11任意刹车强停看到没有如果两个方向引脚同时为高不管PWM占空比多大电机都是处于刹车状态转速上不去。很多同学“PWM加到100%电机还是没力”先查一下IN1/IN2是不是写成了同为1。还有一点电机的两根输出线AO1/AO2怎么接都行正反只影响旋转方向。如果程序里设定正转但电机转反了不用改代码把两根输出线对调一下就行或者把方向引脚的电平逻辑对调。但要注意一旦确定了对线方式后面编码器的方向判断也要跟着一致不然闭环调试时会出现“目标正转反馈却是负数”的乌龙。3. 基于HAL库的驱动代码实现从CubeMX到函数封装3.1 CubeMX里的定时器PWM配置现在主流做法是用STM32CubeMX生成工程这里以STM32F103C8T6加HAL库为例。打开CubeMX后把系统时钟配置到72MHz然后找到TIM3这个定时器。在TIM3的配置页面里做两件事第一把Clock Source设置为Internal Clock第二把Channel1和Channel2的模式都选成PWM Generation CHn。这样TIM3_CH1和TIM3_CH2就会分别映射到PA6和PA7。引脚映射可以在芯片视图上确认鼠标点一下引脚就能看到复用功能。接下来要设置两个关键参数Prescaler和Counter Period它们决定PWM频率和占空比分辨率。假设定时器输入时钟是72MHz要让PWM频率为10kHz计算方法如下PWM频率 定时器时钟 / ((Prescaler 1) × (Counter Period 1))我常用的组合是Prescaler 71Counter Period 99。算一下72MHz / ((71 1) × (99 1)) 72MHz / 7200 10kHz刚好。占空比分辨率就是Counter Period的档位从0到99一共100档对应0%到100%的占空比对TT马达来说足够细腻了。如果你想让占空比更精细可以保持10kHz不变把Prescaler设为71Counter Period设为999这样占空比有1000档不过对这个小电机来说有点浪费100档完全够用。CubeMX配置完成后直接生成代码PWM初始化函数已经帮你生成好了不需要手写定时器初始化。3.2 电机控制函数封装正反转和调速一套搞定CubeMX生成的代码只是把PWM通道初始化了实际控制还需要自己封装函数。我习惯用一个简单的结构体把电机相关的资源绑在一起这样左右电机可以复用一套代码。typedef struct { TIM_HandleTypeDef *htim; // PWM定时器 uint32_t channel; // PWM通道 GPIO_TypeDef *in1_port; // 方向引脚1 uint16_t in1_pin; GPIO_TypeDef *in2_port; // 方向引脚2 uint16_t in2_pin; } Motor_t;初始化时把引脚资源填进去然后写核心的调速函数void Motor_SetSpeed(Motor_t *motor, int speed) { // speed范围-100 ~ 100 if (speed 100) speed 100; if (speed -100) speed -100; if (speed 0) { // 正转 HAL_GPIO_WritePin(motor-in1_port, motor-in1_pin, GPIO_PIN_SET); HAL_GPIO_WritePin(motor-in2_port, motor-in2_pin, GPIO_PIN_RESET); __HAL_TIM_SET_COMPARE(motor-htim, motor-channel, speed); } else { // 反转占空比取绝对值 HAL_GPIO_WritePin(motor-in1_port, motor-in1_pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(motor-in2_port, motor-in2_pin, GPIO_PIN_SET); __HAL_TIM_SET_COMPARE(motor-htim, motor-channel, -speed); } }这里有个小细节Counter Period配置的是99所以占空比数值范围就是0到99直接把speed当成CCR寄存器值就行不用再做比例换算。有人习惯把Period设成999那speed就要乘上10再写入写代码前先看下自己的配置不然会出现“写入100%实际只有10%”的错误。主程序里初始化完定时器记得调用HAL_TIM_PWM_Start启动PWM输出然后就可以Motor_SetSpeed(motor_left, 60); // 左轮正转60% Motor_SetSpeed(motor_right, -40); // 右轮反转40%这样左右轮就按你要的方向和速度转起来了。3.3 定时器编码器模式测速转速和方向都能读出来带编码器的TT马达尾部有两路霍尔信号称为A相和B相。电机转动时A、B两路输出相位差90度的方波正转时A相超前B相反转时B相超前A相。STM32的定时器编码器模式可以同时捕获这两路信号自动完成方向判断和计数累加不需要你在中断里手动处理非常方便。CubeMX里配置编码器模式以TIM4为例把TIM4的Channel1和Channel2都设为Encoder Mode然后在参数里把Encoder Mode选为TI1 and TI2也就是同时使用两路信号这种方式叫4倍频计数精度最高。PB6和PB7会自动被复用为TIM4的编码器输入引脚。代码里只需要初始化并启动编码器模式TIM_Encoder_InitTypeDef encoder_cfg {0}; encoder_cfg.EncoderMode TIM_ENCODERMODE_TI12; encoder_cfg.IC1Polarity TIM_ICPOLARITY_RISING; encoder_cfg.IC1Selection TIM_ICSELECTION_DIRECTTI; encoder_cfg.IC1Prescaler TIM_ICPSC_DIV1; encoder_cfg.IC1Filter 0; encoder_cfg.IC2Polarity TIM_ICPOLARITY_RISING; encoder_cfg.IC2Selection TIM_ICSELECTION_DIRECTTI; encoder_cfg.IC2Prescaler TIM_ICPSC_DIV1; encoder_cfg.IC2Filter 0; HAL_TIM_Encoder_Init(htim4, encoder_cfg); HAL_TIM_Encoder_Start(htim4, TIM_CHANNEL_ALL);然后在定时器中断里或者主循环的定时间隔内读取计数器值int16_t count (int16_t)__HAL_TIM_GET_COUNTER(htim4); __HAL_TIM_SET_COUNTER(htim4, 0);这里用int16_t类型特别重要因为计数器是16位有符号数正转时递增反转时递减符号正好表示方向。如果用uint16_t读反转时会读出一个很大的正数后面算速度就会乱套。速度换算公式也不复杂。一般TT马达编码器电机端每转一圈输出11个脉冲经过1:48减速后输出轴转一圈对应 11 × 48 528 个原始脉冲4倍频后就是2112个计数。如果每100ms读一次计数器值那一秒钟的计数就是 count × 10转每秒就是 count × 10 / 2112再乘以60就得到每分钟转速RPM。实际编码器参数请以你买的模块标成为准有些是13线公式里的数要对应改。3.4 PWM频率到底选多少1kHz和10kHz差别在哪很多教程用1kHz驱动电机也能转但我建议你直接上10kHz理由有三点。第一1kHz时PWM周期是1ms电机绕组的感性滤波不够平滑电流纹波明显转速波动会比较大尤其低速时能感觉到一顿一顿的。第二1kHz落在人耳听觉范围内电机和齿轮箱会发出刺耳的滋滋声在教室里演示特别明显。10kHz已经接近听觉上限听起来安静很多。第三TB6612这类MOSFET驱动芯片的开关损耗在10kHz级别非常小完全扛得住不需要担心发热问题。如果你用的是L298N这种三极管方案超过20kHz开关损耗会明显增大所以也不建议盲目把频率调高。综合下来TT马达配TB6612用10kHz是最省心的选择。4. 从转动到闭环串口监控、PID调速与差速控制4.1 串口打印转速先看得见才能调得准电机能转了只是第一步你还需要知道它到底转多快。最直接的办法是串口把转速数据打出来。STM32的USART1接USB转串口模块通过printf把目标速度、当前实际转速、PWM输出值这些关键信息实时发到电脑上看。使用HAL库时printf需要重定向简单做法是在usart.c里加一个fputc函数int fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, 10); return ch; }然后在主循环里定时打印printf(target%d, rpm%d, count%d\r\n, target_speed, rpm, encoder_count);串口数据能让你直观看到两个现象一是电机实际的转速和目标转速差多少二是转速的波动有多大。如果rpm在目标值附近来回晃说明电机摩擦力不均匀或者PWM频率太低。有了这个数据基础再去调PID就有依据了。串口还可以顺便做一个命令解析功能比如通过串口发送“S120”设置目标转速120RPM“P1.0 0.1 0”临时设置一组PID参数。这样调参不用反复编译下载程序效率高很多。4.2 一个能用的PID速度环代码和调参顺序PID速度反馈的流程不复杂编码器测出实际转速和目标转速求误差误差经过PID运算得到一个PWM占空比输出。我用一个结构体把PID状态保存起来方便左右轮各维护一套参数。typedef struct { float kp, ki, kd; int target; // 目标转速 int error; // 当前误差 int last_error; // 上次误差 float integral; // 积分累计 int output; // 输出限幅后的PWM } Pid_t; void Pid_Calc(Pid_t *pid, int feedback) { pid-error pid-target - feedback; pid-integral pid-error; // 积分限幅防止积分饱和 if (pid-integral 5000) pid-integral 5000; if (pid-integral -5000) pid-integral -5000; int derivative pid-error - pid-last_error; pid-output (int)(pid-kp * pid-error pid-ki * pid-integral pid-kd * derivative); // 输出限幅到占空比范围 if (pid-output 100) pid-output 100; if (pid-output -100) pid-output -100; pid-last_error pid-error; }调参顺序是有讲究的别上来就三个参数一起试。先把Ki和Kd设成0只留Kp。从小往大加比如从0.1开始观察转速响应如果实际转速一直在目标值附近振荡说明Kp太大如果响应很慢、差值始终消不掉说明Kp太小。TT马达经过减速箱后齿隙和静摩擦力比较明显Kp太小的时候电机根本拉不动转速会一直差一截这是正常现象继续加大Kp到轻微振荡再回调一点就行。Kp调到一个能让转速基本接近目标、但有静差的值后再加Ki消除静差。Ki从很小的值开始比如0.02大了转速会来回冲过头甚至形成等幅振荡。最后加Kd能抑制超调但如果编码器计数本身波动大Kd反而会把噪声放大实测下来Kd很多时候设成0就够用了。4.3 两轮差速小车怎么走直线和转向两轮差速小车的底盘本质是左右轮独立驱动。直线行驶不是让左右轮PWM相同而是让左右轮转速闭环后相同。开环给同样占空比看起来公平但两个电机的机械特性、摩擦、齿轮磨损都不一样实际速度会有偏差小车会慢慢偏到一边。做法是左右各维护一套PID分别读取左右编码器转速然后给同一个目标转速让两个闭环各自去追。走直线的质量很大程度取决于左右编码器计数的准确性和两个PID的调参一致性。转弯的原理也不复杂就是给左右轮不同的目标转速。设基础速度为base偏量为delta那么左转弯时left_target base - deltaright_target base delta原地旋转时left_target baseright_target -base。代码里只需要在目标分配时修改target值底下的PID每个周期正常跑就行。我踩过的坑是转弯偏移量给太大小车转弯特别猛导致编码器计数跟不上PID错误地把PWM拉满然后电机堵转、电流飙升。后来我把delta做成和当前速度相关的比例值低速时小转弯、高速时也只能温和转向整体就稳很多。做差速小车的人建议先用串口把左右轮实际转速打出来确认两个轮子在同目标下的偏差再上整车的运动控制。5. 常见问题与排查技巧实录5.1 电机不转先查这三个地方电机完全不转时别急着怀疑代码按这个顺序排查下来九成问题能解决。第一步查STBY。TB6612模块的STBY引脚如果没有拉高芯片内部所有输出都是高阻态电机端量不到电压。有些模块已经加上拉电阻有些没有稳妥做法是直接接一个GPIO拉高或者在硬件上跳线到3.3V。第二步查电源。用万用表量VM和VCC引脚VM应该有电池电压VCC应该有3.3V或者5V。如果VM有电但电机不转再量AO1和AO2两端电压正转且PWM 100%时这两端电压应该接近VM电压。第三步查方向逻辑。方向引脚的组合决定了电机是转、滑行还是刹车。如果AIN1和AIN2同为高电机处于刹车状态就会“有电但转不动”。另外PWM引脚要确认是否有波形最简单的方式是用示波器没有示波器的话可以临时把PWM引脚接一个LED亮度随占空比变化就说明PWM确实输出了只是后续接线有问题。5.2 芯片发烫、电机一启动就复位多半是电源问题芯片发烫通常有几个原因电源接反、VM和VCC电压过高、PWM频率过高导致开关损耗大或者电机长时间堵转。TB6612正常工作温度是温热如果摸上去烫到不敢碰立刻断开电源先检查VM和VCC是否接反再看电机是否被机械卡死。电机启动瞬间MCU复位的现象基本可以确定是电源功率不足或者没有把电机电源和逻辑电源分开。解决办法前面已经提过分开供电并共地同时在电机电源两端加滤波电容。如果使用的电池内阻太大也会在启动瞬间被拉低电压这时候换一颗高倍率电池或者把电池容量做大一点会有明显改善。还有一个容易忽略的点就是杜邦线接触不良。电机电流大时劣质杜邦线压降很严重甚至发热导致VM实际到芯片的电压远低于电池端电压。如果你发现电机空载和堵转的力度都不对把杜邦线换成短粗的线或者直接焊接问题立竿见影。5.3 编码器读数乱跳或者方向反了怎么处理编码器计数乱跳先检查共地。编码器信号和MCU的GND如果不共地信号电平参考点不一致读数肯定乱。然后检查接线长度TT马达的编码器信号线如果和电机电源线并行长距离走线电机换向的干扰会耦合进编码器信号造成误计数。处理办法是让编码器信号线尽量远离电机电源线或者给每一路编码器输出加一个10nF左右的滤波电容到GND。计数器数值一直增大、但电机实际方向和程序判断的方向相反这是AB相接反了。解决办法可以交换编码器的A、B两根线或者在代码里把读到的计数值取负再使用哪个方便用哪个。但要记得左右电机都要检查一遍不要只调一边否则后面左右轮方向会不一致小车直接原地打转。TIM编码器模式里的ICFilter参数也可以适当加大比如设成0x0F这是硬件层面的输入滤波能过滤掉部分毛刺比软件滤波效率更高。不过滤波加到太大会损失高频信号的响应对TT马达这种转速不高的场景影响不大。5.4 下载程序失败报“no stm32 target found”意味着什么烧录程序遇到过“error: no stm32 target found”的人应该不少这个问题是ST-Link或J-Link调试器没有和目标芯片建立连接而不是程序本身写错了。排查方向先看硬件连接SWDIO、SWCLK、GND三根线是否都接到位调试器和目标板是否共地杜邦线是否过长。连接线超过20cm时信号质量会明显下降解决办法是把线剪短或者在MDK的Debug设置里把SW时钟频率降低比如从4MHz降到1MHz。另一个常见原因是程序把SWD引脚复用成了普通GPIO。比如你在代码里把PA13和PA14配置成普通IO输出程序下载进去后调试接口就被占用了第二次再下载就找不到芯片。这种情况下按住目标板复位键不放点击下载按钮在开始下载的瞬间松开复位成功率会高一些再不行就用串口ISP方式擦除芯片。所以做项目时SWD引脚如果还有其他用途一定要在读代码前想清楚是否冲突。5.5 转速上不去、PWM满格但电机无力转速上不去先检查占空比和CCR寄存器的关系。Counter Period设成99时占空比范围是0到99写入100会超出范围相当于溢出行为不可控。如果你在代码里看到__HAL_TIM_SET_COMPARE的第二个参数写的是100而Period配置是99这就是问题所在改成99或者把Period改成999再匹配写入。还有一个被我忽略过的原因是PWM和方向引脚的配合。有些代码里在每次调速时把IN1/IN2都设置了一遍但如果GPIO配置成开漏且没有外部上拉输出高电平实际是悬空状态方向引脚拉不起来逻辑电压不明确电机自然无力。GPIO模式记得用推挽输出尤其是STM32驱动外部芯片的方向脚开漏模式不是不能用但必须加正确电平的上拉电阻没事别给自己挖这个坑。电机电源电压不足也会导致无力特别是电池电量快耗尽时转速明显下降。TT马达额定电压6V用两节锂电池变成7.4V虽然转速会高一点但长时间超压会加速齿轮箱磨损和电刷损耗不建议长期用超过额定电压太多的电源。最后再分享一点实操心得TT马达虽然便宜但它把电机控制里的核心知识串起来了PWM调速、H桥换向、编码器测速、PID闭环、差速控制每一步都能迁移到更复杂的电机系统。我个人的建议是拿到模块后先做最小化验证一个电机、一块驱动板、一颗STM32最小系统板把正反转和调速跑通再往车架上装。直接裸奔调试比装到车架后带着一堆线去查问题容易得多。调试节奏上一次只改一个变量。先固定PWM频率再调占空比先开环测转速再闭环调PID先单轮跑直再双轮差速。每次改动后用串口记录数据有了数据才能判断到底是谁的问题。踩坑多了你对电流、电压、PWM频率这些抽象概念会慢慢建立直觉之后再做各种小车就会顺很多。
返回列表