STM32标准库PWM驱动直流电机:从硬件连接到代码实现 1. 项目缘起从“转起来”到“转得稳”最近在整理工作室的物料翻出来几个闲置的直流有刷电机和一块TB6612驱动模块。看着它们突然想起很多朋友在入门STM32控制电机时第一步往往卡在如何让电机“转起来”上。网上的教程要么是CubeMXHAL库的要么是寄存器版本的对于还在使用标准库Standard Peripheral Library的朋友来说总感觉隔了一层。特别是想用最基础的PWM实现一个简单的开环速度控制好像资料都挺零散的。所以我决定用最“原始”也最“经典”的STM32标准库搭配一个常见的电机驱动模块写一篇手把手的指南。目标很简单不扯复杂的闭环算法就聚焦于如何用定时器的PWM功能输出一个可调占空比的方波让电机听话地变速旋转。这个过程是理解更高级的FOC、PID等控制算法的基石。如果你手头正好有STM32F1系列比如流行的F103C8T6的开发板、一个电机驱动模块如TB6612、L298N和一个直流电机那么跟着做一遍你就能彻底掌握PWM开环控制的核心流程。2. 硬件选型与连接为什么是它们在动手写代码之前理清硬件连接和选型逻辑至关重要。盲目接线不仅可能导致电机不转更可能烧毁芯片或模块。2.1 核心控制器STM32与标准库我们选用STM32F103C8T6俗称“蓝桥杯”或“最小系统板”作为主控。选择它和标准库的原因有三资源丰富且经典F103系列拥有多个通用定时器TIM2, TIM3, TIM4等每个定时器都能产生4路PWM完全满足基础电机控制需求。其标准库资料浩如烟海社区支持极好遇到问题几乎都能找到答案。学习曲线平滑相比于直接操作寄存器标准库用结构体和函数封装了底层配置代码可读性更高相比于HAL库它又更贴近硬件能让你清晰地理解定时器、GPIO等外设是如何被配置起来的。这是从51单片机过渡到STM32的黄金学习路径。成本与普及度C8T6核心板价格低廉几乎是人手一块的“标配”保证了教程的复现性。2.2 电机驱动模块TB6612 vs L298N为什么我更推荐TB6612而不是更古老的L298N这里有一个简单的对比特性TB6612L298N工作电压2.5V-13.5V (VM)4.5V-46V输出电流1.2A连续/3.2A峰值2A连续/3A峰值控制逻辑电平3.3V/5V兼容通常需要5V对3.3V MCU可能不友好功耗与发热MOSFET桥效率高发热小双H桥导通压降大约2V发热严重待机模式有STBY引脚无尺寸与集成度小巧集成度高较大可能需要外接散热片核心优势TB6612采用MOSFET其导通内阻Rds(on)远小于L298N的双极型晶体管这意味着在驱动相同电机时TB6612自身的功耗和发热要小得多。对于小型直流电机比如玩具车电机、小型风扇电机TB6612是更优、更现代的选择。它只需要两个控制信号AIN1/AIN2或BIN1/BIN2和一个PWM信号即可控制一个电机的方向和速度逻辑清晰。2.3 接线图与原理剖析我们以驱动一个电机为例使用TB6612的A通道。接线如下电源部分VM接电机电源例如7.2V航模电池或6-12V的直流电源。务必与给STM32供电的3.3V/5V电源隔离这是保护MCU的关键。VCC接逻辑电源3.3V或5V与STM32共地。如果STM32是3.3V供电这里就接3.3V。GND至关重要TB6612的GND必须与STM32的GND、电机电源的GND连接在一起构成统一的参考地。控制信号部分AIN1连接STM32的某个GPIO引脚如PA1用于控制电机方向之一。AIN2连接STM32的另一个GPIO引脚如PA2用于控制电机方向之二。PWMA连接STM32的某个定时器通道引脚如PA8对应TIM1_CH1用于输入PWM速度信号。STBY使能引脚接高电平VCC模块才工作。可以接STM32的GPIO控制也可以直接接VCC。电机输出部分AO1和AO2分别接直流电机的两个电极。方向控制逻辑真值表STBYAIN1AIN2PWMA电机状态101PWM正转速度由PWM占空比决定110PWM反转速度由PWM占空比决定100PWM刹车电机快速停止111PWM刹车电机快速停止0XXX待机低功耗电机自由停止注意AIN1和AIN2决不能同时为1或同时为0并输入PWM这会导致电机短路刹车瞬间电流很大对驱动芯片和电源都是考验。常规调速我们只用(0,1)或(1,0)这两种状态。3. 软件架构定时器PWM生成全解析理解了硬件如何响应信号我们来看STM32如何产生这些信号。核心是利用通用定时器如TIM3的PWM输出模式。3.1 定时器PWM原理简述STM32的通用定时器有一个16位的自动重装载寄存器TIMx_ARR和一个16位的捕获/比较寄存器TIMx_CCRx。计数器TIMx_CNT从0开始向上计数。ARR决定了PWM的频率计数器计到ARR值后归零重新开始这个周期就是PWM波的周期。PWM频率 定时器时钟源 / (ARR 1)。CCRx决定了PWM的占空比我们设置定时器为“PWM模式1”。当计数器值小于CCRx时输出有效电平高或低可配置大于等于CCRx时输出无效电平。这样占空比 CCRx / (ARR 1)。通过修改CCRx的值我们就能动态改变输出PWM的占空比从而改变电机的平均电压实现调速。3.2 工程代码结构规划一个清晰的项目结构有助于管理和维护。建议在Keil或你使用的IDE中这样组织Motor_PWM_OpenLoop/ ├── User/ │ ├── main.c │ ├── stm32f10x_it.c │ └── (其他用户文件) ├── Hardware/ │ ├── motor.c │ └── motor.h ├── Libraries/ │ ├── CMSIS/ │ ├── STM32F10x_StdPeriph_Driver/ │ └── (标准库文件) └── (工程配置文件)我们把所有电机相关的操作封装在motor.c/h中实现高内聚、低耦合。3.3 motor.h 头文件设计头文件定义了接口和关键参数是代码的“使用说明书”。#ifndef __MOTOR_H #define __MOTOR_H #include stm32f10x.h // 电机编号定义 typedef enum { MOTOR_A 0, MOTOR_B } Motor_TypeDef; // 电机方向定义 typedef enum { MOTOR_FORWARD 0, MOTOR_BACKWARD, MOTOR_BRAKE } Motor_DirTypeDef; // 电机初始化结构体用于配置定时器和GPIO typedef struct { TIM_TypeDef* TIMx; // 定时器如TIM3 uint16_t TIM_Channel; // 定时器通道如TIM_Channel_1 uint32_t RCC_APBxPeriph_TIM; // 定时器时钟如RCC_APB1Periph_TIM3 GPIO_TypeDef* GPIOx; // PWM引脚所在GPIO组如GPIOA uint16_t GPIO_Pin; // PWM引脚如GPIO_Pin_6 uint32_t RCC_APB2Periph_GPIO; // GPIO时钟如RCC_APB2Periph_GPIOA uint8_t GPIO_PinSource; // 引脚源如GPIO_PinSource6 } Motor_PWMInitTypeDef; // 电机控制结构体包含方向控制引脚 typedef struct { Motor_PWMInitTypeDef PWM_Config; // PWM配置 GPIO_TypeDef* DIR_GPIOx1; // 方向引脚1的GPIO组 uint16_t DIR_Pin1; // 方向引脚1 GPIO_TypeDef* DIR_GPIOx2; // 方向引脚2的GPIO组 uint16_t DIR_Pin2; // 方向引脚2 } Motor_InitTypeDef; // 函数声明 void Motor_Init(const Motor_InitTypeDef* Motorx); void Motor_SetSpeed(Motor_TypeDef Motorx, int16_t speed); // speed范围-1000 ~ 1000 void Motor_Stop(Motor_TypeDef Motorx); #endif /* __MOTOR_H */设计思路这里我做了两层封装。Motor_PWMInitTypeDef专门管理PWM相关的硬件资源而Motor_InitTypeDef在此基础上加入了方向控制引脚形成一个完整的电机控制单元。使用int16_t类型的speed正负代表方向绝对值代表速度大小这样上层控制逻辑会非常直观。3.4 motor.c 源文件实现核心这是重头戏我们一步步拆解。3.4.1 初始化函数Motor_Init这个函数负责初始化控制一个电机所需的所有GPIO和定时器PWM通道。// 假设我们定义了两个电机控制结构体 static Motor_InitTypeDef MotorA_Structure; static Motor_InitTypeDef MotorB_Structure; void Motor_Init(const Motor_InitTypeDef* Motorx) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; // 1. 初始化方向控制GPIO引脚 RCC_APB2PeriphClockCmd(Motorx-DIR_GPIOx1 GPIOA ? RCC_APB2Periph_GPIOA : Motorx-DIR_GPIOx1 GPIOB ? RCC_APB2Periph_GPIOB : RCC_APB2Periph_GPIOC, ENABLE); // 实际项目中应对所有可能的GPIO组做判断此处简化 GPIO_InitStructure.GPIO_Pin Motorx-DIR_Pin1 | Motorx-DIR_Pin2; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; // 推挽输出 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(Motorx-DIR_GPIOx1, GPIO_InitStructure); // 注意DIR_GPIOx1和DIR_GPIOx2可能是同一组也可能是不同组。严谨的实现需要分别初始化。 // 初始化后将方向引脚设为刹车状态0,0或1,1 GPIO_ResetBits(Motorx-DIR_GPIOx1, Motorx-DIR_Pin1); GPIO_ResetBits(Motorx-DIR_GPIOx1, Motorx-DIR_Pin2); // 假设同组 // 2. 初始化PWM引脚 (复用推挽输出) RCC_APB2PeriphClockCmd(Motorx-PWM_Config.RCC_APB2Periph_GPIO, ENABLE); GPIO_InitStructure.GPIO_Pin Motorx-PWM_Config.GPIO_Pin; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(Motorx-PWM_Config.GPIOx, GPIO_InitStructure); // 3. 初始化定时器时基单元 // 使能定时器时钟需要区分APB1和APB2 if (Motorx-PWM_Config.TIMx TIM1 || Motorx-PWM_Config.TIMx TIM8) { RCC_APB2PeriphClockCmd(Motorx-PWM_Config.RCC_APBxPeriph_TIM, ENABLE); } else { RCC_APB1PeriphClockCmd(Motorx-PWM_Config.RCC_APBxPeriph_TIM, ENABLE); } // 计算PWM频率。假设系统时钟72MHz定时器预分频设为71则计数器时钟为1MHz。 // 若ARR设为999则PWM频率 1MHz / (9991) 1kHz。1kHz对于直流电机调速是常用频率。 TIM_TimeBaseStructure.TIM_Period 999; // ARR值决定周期 TIM_TimeBaseStructure.TIM_Prescaler 71; // 预分频值PSC TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; // 向上计数模式 TIM_TimeBaseInit(Motorx-PWM_Config.TIMx, TIM_TimeBaseStructure); // 4. 初始化PWM输出比较模式 TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; // 模式1 TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; // 使能输出 TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; // 输出极性高电平有效 TIM_OCInitStructure.TIM_Pulse 0; // 初始占空比为0CCR值 // 根据不同的通道调用不同的OC初始化函数 switch(Motorx-PWM_Config.TIM_Channel) { case TIM_Channel_1: TIM_OC1Init(Motorx-PWM_Config.TIMx, TIM_OCInitStructure); TIM_OC1PreloadConfig(Motorx-PWM_Config.TIMx, TIM_OCPreload_Enable); // 使能预装载 break; case TIM_Channel_2: TIM_OC2Init(Motorx-PWM_Config.TIMx, TIM_OCInitStructure); TIM_OC2PreloadConfig(Motorx-PWM_Config.TIMx, TIM_OCPreload_Enable); break; // ... 处理通道3和4 default: break; } // 5. 使能定时器的自动重装载预装载寄存器ARR TIM_ARRPreloadConfig(Motorx-PWM_Config.TIMx, ENABLE); // 6. 使能定时器 TIM_Cmd(Motorx-PWM_Config.TIMx, ENABLE); // 如果是高级定时器TIM1/TIM8还需要使能主输出 if (Motorx-PWM_Config.TIMx TIM1 || Motorx-PWM_Config.TIMx TIM8) { TIM_CtrlPWMOutputs(Motorx-PWM_Config.TIMx, ENABLE); } }关键点解析GPIO模式PWM引脚必须设置为GPIO_Mode_AF_PP复用推挽输出。这是很多新手容易忽略的地方如果设成普通的推挽输出定时器是无法控制该引脚输出的。时钟使能定时器时钟挂在不同的APB总线上。TIM1/TIM8在APB2其他通用定时器在APB1。使能时钟前需要判断。PWM频率计算PWM频率 TIMxCLK / ((PSC1)*(ARR1))。其中TIMxCLK是定时器的时钟源频率。如果APB1预分频器不为1定时器时钟会倍频需要查手册确认。对于F103当APB1分频系数不为1时挂在其上的定时器时钟是APB1时钟的2倍。一个稳妥的做法是在system_stm32f10x.c中查看SystemCoreClock或者直接计算。我们这里假设系统时钟72MHzAPB1分频系数为2则APB1时钟36MHzTIM2-7的时钟为72MHz。设置PSC71则计数器时钟为1MHz。ARR999得到1kHz PWM波。预装载寄存器TIM_OCxPreloadConfig和TIM_ARRPreloadConfig使能了预装载功能。这意味着我们对CCR或ARR的修改会在下一个更新事件时才生效防止在PWM周期中间修改寄存器导致输出毛刺。高级定时器TIM1和TIM8有刹车、互补输出等高级功能用于PWM输出时必须额外使能TIM_CtrlPWMOutputs。3.4.2 速度设置函数Motor_SetSpeed这是控制电机的核心API它将一个范围在-1000 ~ 1000的速度值转换为具体的方向信号和PWM占空比。void Motor_SetSpeed(Motor_TypeDef Motorx, int16_t speed) { Motor_InitTypeDef* pMotor; uint16_t dir_pin1_state, dir_pin2_state; uint16_t pwm_duty; // 1. 获取对应电机的控制结构体指针 if (Motorx MOTOR_A) { pMotor MotorA_Structure; } else { pMotor MotorB_Structure; } // 2. 边界限制 if (speed 1000) speed 1000; if (speed -1000) speed -1000; // 3. 判断方向并设置方向控制引脚 if (speed 0) { // 正转 dir_pin1_state 0; dir_pin2_state 1; pwm_duty speed; // 速度值为正直接作为占空比 } else if (speed 0) { // 反转 dir_pin1_state 1; dir_pin2_state 0; pwm_duty -speed; // 速度值为负取绝对值作为占空比 } else { // 速度为零刹车 dir_pin1_state 0; dir_pin2_state 0; pwm_duty 0; } // 4. 应用方向控制信号根据实际接线可能需要对不同GPIO组单独操作 if (dir_pin1_state) { GPIO_SetBits(pMotor-DIR_GPIOx1, pMotor-DIR_Pin1); } else { GPIO_ResetBits(pMotor-DIR_GPIOx1, pMotor-DIR_Pin1); } if (dir_pin2_state) { GPIO_SetBits(pMotor-DIR_GPIOx1, pMotor-DIR_Pin2); // 假设同组 } else { GPIO_ResetBits(pMotor-DIR_GPIOx1, pMotor-DIR_Pin2); } // 5. 设置PWM占空比 (CCR值) // 我们的ARR是999速度范围是0-1000所以需要映射CCR (pwm_duty * 999) / 1000 // 使用整数运算避免浮点数先乘后除减少精度损失 uint16_t ccr_value (pwm_duty * 999) / 1000; switch(pMotor-PWM_Config.TIM_Channel) { case TIM_Channel_1: TIM_SetCompare1(pMotor-PWM_Config.TIMx, ccr_value); break; case TIM_Channel_2: TIM_SetCompare2(pMotor-PWM_Config.TIMx, ccr_value); break; // ... 处理通道3和4 default: break; } }设计精妙之处统一的控制接口用一个speed参数同时控制方向和速度上层应用逻辑非常简洁。想正转全速就传1000反转半速就传-500停止传0。死区与刹车处理当speed为0时我们将方向引脚都置0这是TB6612的“刹车”模式之一能让电机快速停止而不是“滑行”。这是一种简单的开环制动。占空比映射将0-1000的线性速度值线性映射到0-999的CCR值。这里使用了整数运算(a * b) / c的模式在嵌入式系统中效率高于浮点数运算。注意当pwm_duty1000时ccr_value999对应100%占空比因为占空比CCR/(ARR1)。3.4.3 停止函数Motor_Stop这是一个便捷函数用于快速停止电机。void Motor_Stop(Motor_TypeDef Motorx) { Motor_SetSpeed(Motorx, 0); // 直接调用速度设置函数设置为0即可刹车 }3.5 main.c 主程序示例最后我们在主函数中将这些模块组装起来并实现一个简单的加速-匀速-减速-反转的测试程序。#include stm32f10x.h #include motor.h #include delay.h // 需要一个简单的延时函数可以用SysTick实现 // 定义电机A的硬件配置 const Motor_InitTypeDef MotorA_Config { .PWM_Config { .TIMx TIM3, .TIM_Channel TIM_Channel_1, .RCC_APBxPeriph_TIM RCC_APB1Periph_TIM3, .GPIOx GPIOA, .GPIO_Pin GPIO_Pin_6, .RCC_APB2Periph_GPIO RCC_APB2Periph_GPIOA, .GPIO_PinSource GPIO_PinSource6, }, .DIR_GPIOx1 GPIOA, .DIR_Pin1 GPIO_Pin_4, .DIR_GPIOx2 GPIOA, .DIR_Pin2 GPIO_Pin_5, }; int main(void) { // 初始化系统时钟通常由SystemInit()在启动文件调用这里假设已配置为72MHz // 初始化延时函数 Delay_Init(); // 初始化电机A Motor_Init(MotorA_Config); while(1) { // 测试1: 正转加速 for(int16_t i 0; i 1000; i 50) { Motor_SetSpeed(MOTOR_A, i); Delay_ms(100); // 每100ms增加5%速度 } Delay_ms(2000); // 全速正转2秒 // 测试2: 正转减速 for(int16_t i 1000; i 0; i - 50) { Motor_SetSpeed(MOTOR_A, i); Delay_ms(100); } Delay_ms(1000); // 停止1秒 // 测试3: 反转加速 for(int16_t i 0; i -1000; i - 50) { Motor_SetSpeed(MOTOR_A, i); Delay_ms(100); } Delay_ms(2000); // 全速反转2秒 // 测试4: 反转减速 for(int16_t i -1000; i 0; i 50) { Motor_SetSpeed(MOTOR_A, i); Delay_ms(100); } Delay_ms(1000); // 停止1秒 } }4. 调试与排坑让电机真正听话代码写完下载到板子电机可能不转或者行为异常。别急这是嵌入式开发的常态。按照以下步骤排查能解决99%的问题。4.1 电源与地线一切的基础这是最核心也最容易出错的一步。现象电机完全不转驱动芯片发热严重STM32可能复位或不工作。排查共地共地共地用万用表蜂鸣档确保STM32的GND、TB6612的GND、电机电源的GND是直接连通的。任何一点悬空都会导致信号参考电平混乱。电源隔离电机电源接VM和单片机逻辑电源接VCC最好使用两个独立的电源。如果必须共用务必在VM输入端加入一个大容量如1000uF的电解电容和一个104的瓷片电容进行滤波并确保电源功率足够。电机启动瞬间的电流冲击浪涌电流足以让稳压芯片崩溃导致单片机复位。电压匹配确认TB6612的VCC引脚接到了正确的逻辑电平3.3V或5VVM接到了电机额定电压范围内。4.2 信号测量用逻辑分析仪/示波器说话没有仪器一个简单的LED或万用表电压档也能做初步判断。现象电机不转或一直全速转。排查方向信号在程序运行中用万用表测量AIN1和AIN2引脚对地的电压。当你调用Motor_SetSpeed(MOTOR_A, 500)时应该测量到AIN10V,AIN23.3V假设逻辑电平3.3V。如果电压不对检查GPIO初始化代码和Motor_SetSpeed函数中设置引脚的部分。PWM信号这是关键。将示波器或逻辑分析仪探头接到PWMA引脚。是否有波形如果没有检查定时器时钟是否使能、GPIO是否配置为复用推挽输出、定时器是否被使能TIM_Cmd、对于TIM1/TIM8是否使能了主输出TIM_CtrlPWMOutputs。频率是否正确测量波形的周期是否接近我们计算的1ms1kHz如果差很多检查TIM_Period和TIM_Prescaler的设置以及系统主频是否正确。占空比是否变化在Motor_SetSpeed函数中设置断点或让速度循环变化观察示波器上脉冲宽度是否随之变化。如果占空比不变检查TIM_SetComparex函数是否被正确调用通道号是否匹配。4.3 软件逻辑与常见错误现象电机只能单向转或者调速不线性。排查方向逻辑错误对照TB6612的真值表确保你的(AIN1, AIN2)组合在正转、反转、刹车时是正确的。最常见的错误是把(1,0)和(0,1)搞反了。占空比映射错误在Motor_SetSpeed函数中打印或通过调试器观察计算出的ccr_value。当你传入speed500时ccr_value应该是(500*999)/1000499。如果这个值不对检查映射公式。ARR与CCR的关系记住有效占空比 CCR / (ARR 1)。如果你设置ARR999那么CCR999时占空比是999/100099.9%并非100%。如果你需要绝对的100%占空比一种方法是设置CCR大于ARR在PWM模式1下输出会持续为有效电平但更规范的做法是直接控制方向引脚让电机全速同时将PWM占空比设为100%即CCRARR1不应该是CCRARR然后使能输出比较快速模式。更简单的做法是在代码中判断如果speed的绝对值大于某个阈值如990就直接将方向引脚设为全速状态并关闭PWM输出或设CCRARR。不过对于开环控制99.9%和100%的差别几乎感觉不到。初始化顺序确保先配置好GPIO和定时器最后才使能定时器TIM_Cmd。如果顺序乱了可能会在初始化完成前就产生不正确的PWM边沿。4.4 进阶电机启动与低速“死区”现象电机在低速时比如speed100不启动或者抖动一旦超过某个阈值就突然转起来。分析与解决这是直流有刷电机的特性——静摩擦力矩。电机从静止到转动需要克服一定的静摩擦所需的启动电压即PWM占空比比维持转动所需的电压高。解决方案1软件在Motor_SetSpeed函数中对低速区进行非线性补偿。例如当目标速度绝对值小于阈值DEAD_ZONE比如150时实际输出的PWM占空比用一个更大的值。可以做一个简单的映射表实际CCR 补偿曲线(目标speed)。这被称为“启动助推”。解决方案2硬件选择启动扭矩更大的电机或者给电机轴加装减速箱降低启动负载。注意这不是你的代码或驱动有问题而是电机本身的物理特性。理解这一点就从“让电机转”进阶到了“让电机转得好”。5. 从开环到闭环下一步该想什么当你成功让电机随着你的代码指令平滑地加速、减速、正反转时你已经完成了开环控制。但开环控制有个致命问题它无法抵抗干扰。如果负载突然变重电机转速就会下降如果电池电压降低转速也会变慢。因为你只是“希望”电机以某个速度转并没有去“检查”它是否真的以那个速度在转。这就是闭环控制比如PID要解决的问题。你需要一个反馈传感器来告诉控制器电机的真实转速。常用的有编码器安装在电机轴上每转一圈输出固定数量的脉冲。通过定时器捕获这些脉冲的频率就能精确计算转速。这是最常用、精度最高的方案。霍尔传感器有些电机内部集成了霍尔元件每转输出几个脉冲如MG310电机有3个霍尔精度较低但成本也低。测速发电机输出与转速成正比的电压信号需要ADC采样。开环是闭环的基础。你现在已经搭建好了“执行机构”PWM驱动电机和“控制器”STM32。下一步就是为这个系统装上“眼睛”传感器并设计一个“大脑算法”PID控制器让系统能够根据“期望速度”和“实际速度”的偏差自动调整PWM占空比从而在负载变化时也能保持转速稳定。到那时你可以沿用本文的硬件框架和软件架构只需新增编码器读取的代码通常使用定时器的编码器接口模式并在主循环中增加一个PID计算函数用其输出结果来调用Motor_SetSpeed。你会发现有了清晰的模块化设计功能的叠加会变得非常顺畅。最后分享一个我自己的小习惯在调试电机这类大电流设备时我总是在电源线上串联一个保险丝比如2A并在VM和GND之间并联一个大功率的续流二极管如1N5822。保险丝能在短路时保护你的电源和芯片而续流二极管能吸收电机线圈在PWM关断时产生的反向电动势保护驱动芯片的MOSFET。这些小投入往往能避免整个实验板的“阵亡”。

本月热点