2026/9/9 8:55:14

STM32F103 PWM控制步进电机:从硬件接线到加减速调试全解析

STM32F103 PWM控制步进电机:从硬件接线到加减速调试全解析 简介面向STM32F103单片机初学者的PWM控制42步进电机工程资料基于定时器生成PWM波形涵盖定时器初始化、PWM通道映射、占空比调节以及步进电机四相八拍驱动逻辑适合正在学习嵌入式电机控制或准备课程设计的读者。压缩包共一百三十六个文件以C语言源文件为主并包含工程配置文件、编译中间文件、可烧录的HEX固件、汇编列表和说明文档整体大小约二点四五兆字节目录结构规整可直接使用MDK打开学习。资源已有两千四百八十七人学习下载通过学习源码和烧录固件可以快速复现实验过程深入理解PWM频率、占空比与电机转速之间的关系同时掌握定时器预分频、自动重装载、比较寄存器配置、中断服务程序编写及GPIO复用功能的实际用法为后续步进电机精准控制项目提供可复用模板。 刚接触STM32F103的人十个里有八个都想过用它控制步进电机但很多人卡在“PWM输出后电机根本不转”这个阶段。我这两年帮人调试过好几块雕刻机、写字机、云台的控制板发现一个最常见的误区以为把PWM引脚接到驱动器上就能转。其实STM32F103在这个场景里真正输出的是一串脉冲——脉冲个数决定电机转多少角度脉冲频率决定转速而方向由另一个普通GPIO控制。这篇文章就围绕“STM32F103输出PWM控制步进电机”这条主线从硬件接线、驱动板选型、标准库初始化代码到加减速、占空比异常、回原方向这些实际坑一次讲清楚。适合刚入门步进电机控制的单片机开发者和DIY设备爱好者参考。1. 先理清思路PWM在步进电机控制里到底扮演什么角色1.1 步进电机的控制本质步进电机是一个把电脉冲变成角位移的执行元件。以最常见的1.8°步距角电机为例转子每收到一个脉冲旋转1.8°走一圈正好需要200个脉冲。如果驱动器开启了细分比如8细分那么每个脉冲对应的角度变成0.225°走一圈需要1600个脉冲。这个模型一旦建立起来控制逻辑就很清楚了输出固定数量的脉冲就能精确走位调整脉冲频率就能控制速度。所以STM32F103的PWM输出在这里并不是用来“调功率”的而是用来产生稳定的脉冲序列。这也是它与PWM驱动LED、PWM控制舵机最大的区别——占空比反而是次要参数。1.2 为什么用定时器PWM而不是GPIO翻转可能有朋友说我用GPIO加delay翻转引脚也能模拟脉冲把电机转起来。没错低频低速下确实能转但问题出在高频和稳定性上。脉冲频率一旦到几千赫兹CPU要一直忙在翻转和延时上主程序其他任务几乎没法跑而且主循环里的任何中断都可能让脉冲间隔抖动电机就会出现异响甚至失步。使用定时器硬件PWM的优势是波形由外设自己产生CPU不参与翻转频率由预分频器和自动重装载寄存器精确决定同一个定时器的多个通道还能分别输出方便以后扩展多轴控制。这也是正规驱动方案几乎都采用STM32定时器PWM输出作为STEP信号源的原因。1.3 两种主流的“PWM控制步进电机”路线市面上能看到的方案大致分两类。一是本文重点讲的脉冲驱动方式控制器输出PUL/DIR/ENA三路信号给步进驱动器驱动器负责电流放大和细分控制典型硬件是A4988、DRV8825、TB6600配合42步进电机或57电机这是雕刻机、3D打印机、写字机的主流方案。二是直接用PWM斩波驱动电机绕组最典型的就是ULN2003加五线四相28BYJ-48减速步进电机常见于玩具云台、小转台。前一种精度高、力矩大、抗干扰好后一种结构简单、成本极低但速度和力矩都有明显上限。下面按主流方案展开先讲硬件连接再讲代码实现。2. 硬件连接驱动板选型、电机线判断和共地问题2.1 步进驱动板怎么选这一块直接影响项目成败别随手拿一块板就往上怼。如果电机是28BYJ-48这种五线四相小电机用ULN2003驱动模块就够了它内部就是达林顿管阵列直接把GPIO的脉冲信号放大到电机绕组能转能停但力矩小、转速一高就丢步。如果是42步进电机最常见的选择是A4988或DRV8825模块它们体积小、细分可调直接插在单片机扩展板上非常方便。A4988默认输出电流约1ADRV8825可以到1.5A到2A推荐根据电机额定电流留20%到30%余量。再到57、86电机或者三轴雕刻机整机就要用TB6600、DM542这类带光耦隔离的独立型驱动器供电能到24V到36V甚至更高信号线隔离后抗干扰能力强很多。驱动方案适用电机特点典型场景ULN200328BYJ-48五线四相成本低、驱动简单、力矩小玩具云台、小转台A498842步进电机集成细分、体积小、电流约1A小型雕刻机、3D打印机DRV882542步进电机电流更大、最高细分更高3D打印机、小型CNCTB6600/DM54257/86步进电机光耦隔离、抗干扰、电流大三轴雕刻机、工业设备2.2 脉冲、方向、使能信号怎么接A4988这类模块引脚通常是STEP、DIR、ENSTEP接STM32的PWM输出脚DIR接普通GPIOEN接普通GPIO。需要特别注意使能电平A4988的EN是低电平有效也就是说把EN引脚拉低驱动器才输出电流给电机如果你把EN悬空模块内部上拉会让电机始终处于使能状态也能转但你会失去通过程序关断电流的能力。对于TB6600这种光耦隔离驱动器引脚是PUL、PUL-、DIR、DIR-、ENA、ENA-采用共阳极接法时把PUL、DIR、ENA统一接正电源PUL-、DIR-、ENA-分别接单片机引脚采用共阴极接法则反过来。新手最容易在接线阶段翻车翻来翻去没输出最后发现只是信号负端和电源地没共好。2.3 步进电机线怎么区分很多朋友拿到二手电机或者国产杂牌电机线色不按套路出牌这时候不要靠猜直接用万用表电阻挡判断。四线电机比较好办两两测量电阻很小、基本为零点几欧到几欧的是一组线圈另外两根是另一组两组之间测量应为开路。六线和八线电机要复杂些通常要先找出中心抽头同一绕组的两根端线对中心抽头电阻相等且等于两个半绕组串联时电阻的一半。实际操作时我习惯先把四根主线和中心抽头分离出来用标签贴好A、A-、B、B-再接驱动器。还有一种快速验证方向反没反的方法接好一组A相和B相后用手捏住电机轴慢慢旋转如果感受到一顿一顿的阻力说明接线正确否则交换其中一相的两根线再试。2.4 电源共地与电流调节这是我调试时踩过最多坑的地方。步进驱动器需要两路供电一是逻辑电源给芯片内部电路用A4988的VDD一般接3.3V或5V二是电机电源VMOT接到电机额定电压常见12V或24V。两路电源的GND必须接到一起否则STEP信号没有参考地驱动器收到的脉冲是乱七八糟的悬浮信号电机要么不动要么乱跳。A4988模块上有一个精密电位器用来设置电机电流参考电压Vref与输出电流I的关系大约是IVref/0.8。我一般把电流设定为电机额定电流的70%左右既保证力矩又控制发热。如果你发现电机运转一段时间后外壳烫手大概率是电流调高了先降电流试。3. 软件实现标准库V3.5下用TIM3输出PWM驱动步进电机3.1 频率和占空比的计算方法STM32F103的定时器时钟最大为72MHz。PWM输出频率公式是f 72MHz / ((PSC1) * (ARR1))。注意PSC和ARR都要加1因为这两个寄存器都是从0开始计数的。以控制42步进电机为例我经常把目标频率设成20kHz这个频率高于人耳听觉敏感区电机运行时声音小而且脉冲宽度充裕。计算过程如下设PSC71则计数时钟为72MHz/721MHz那么ARR11MHz/20kHz50所以ARR49。占空比设为50%CCR(ARR1)/225。有些朋友直接把PWM式子写成“PSC71-1ARR50-1”结果频率差了一截这是因为概念上混淆了寄存器和实际分频值的对应关系。3.2 标准库V3.5初始化代码以TIM3通道3、默认引脚PB0为例。标准库V3.5是STM32F103最经典、资料最多的固件库版本配置流程很固定开GPIOB时钟开TIM3时钟配置PB0为复用推挽输出再配置定时器时基和输出比较单元。void TIM3_PWM_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Prescaler 71; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_Period 49; TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIM3, TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse 25; TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC3Init(TIM3, TIM_OCInitStructure); TIM_Cmd(TIM3, ENABLE); }这段代码里TIM_Period就是ARRTIM_Pulse就是CCR。如果希望PWM参数在运行中修改时能平滑更新可以再加上TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable)和TIM_ARRPreloadConfig(TIM3, ENABLE)。预装载的作用是让寄存器的修改等到当前计数周期结束才真正生效避免一个周期内波形被截断。对于步进电机这种对脉冲完整性敏感的场景建议加上。3.3 方向与使能GPIO的配合方向信号DIR只是一个普通GPIO电平正转时拉高反转时拉低。但在生产实际中不能直接按下拉就发脉冲需要注意时序电机启动前先让EN引脚生效使驱动器输出电流到绕组建立定位力矩再输出脉冲方向切换时最好让脉冲停止几个毫秒再翻转DIR电平最后再恢复脉冲输出。否则电机会在方向切换瞬间因为混入额外脉冲而抖动。代码上就是初始化几个GPIO外部引脚然后按这个顺序操作。#define DIR_PORT GPIOB #define DIR_PIN GPIO_Pin_1 #define EN_PORT GPIOB #define EN_PIN GPIO_Pin_2 void Motor_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin DIR_PIN | EN_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); GPIO_SetBits(GPIOB, EN_PIN); GPIO_ResetBits(GPIOB, DIR_PIN); }3.4 动态调速改ARR还是改PSC很多朋友问步进电机调速时应该改定时器的哪个寄存器。答案是改ARR不改PSC。因为PSC一旦改动计数时钟变化可能连带影响同一定时器其他通道的频率而ARR只影响PWM频率不会破坏PSC已经确定好的时基关系。动态调速函数可以这样写先根据目标频率计算ARR值再更新ARR并同步把CCR设置成新周期的一半保持50%占空比。void Motor_SetFrequency(uint32_t freq) { uint32_t arr; if (freq 0) { TIM_Cmd(TIM3, DISABLE); return; } arr 72000000UL / ((71 1) * freq) - 1; if (arr 1) arr 1; TIM_SetAutoreload(TIM3, arr); TIM_SetCompare3(TIM3, (arr 1) / 2); }这里有一个容易忽略的问题当目标频率很高时ARR值会变得很小例如输出100kHz时ARR9此时CCR如果按(ARR1)/2计算是5高电平宽度还有5μs仍然满足绝大多数驱动器的最小脉宽要求。但如果ARR小于等于1梯形波的占空比调节就会开始失真所以代码里加了下限保护。实际应用时步进驱动器一般不建议超过200kHz的脉冲频率超过后驱动器内部光耦或整形电路可能来不及响应。3.5 加减速控制为什么必须做以及怎么做步进电机有一个很关键的参数叫启动频率超过这个频率直接起步电机会丢步甚至堵转。解决办法是加减速控制也就是频率从低到高渐进停止时再从高到低渐进。最简单实用的算法是梯形加减速用定时器中断每隔固定时间把当前频率往目标频率逼近一步。下面是一个基本的加速逻辑片段void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); if (current_freq target_freq) { current_freq accel_step; if (current_freq target_freq) current_freq target_freq; Motor_SetFrequency(current_freq); } } }这段代码只能在速度低于目标值时逐步累加实际项目中通常还需要把目标值附近的减速过程也加进去。更完整的做法是维护一个速度状态机分别处理加速、匀速、减速三个区间。如果对运动平滑度要求高比如写字机、激光雕刻头就需要S形加减速启动和结束阶段用曲线过渡。只要在频率变化率上做线性插值再配合一个小数组存加减速曲线就能实现。4. 调试与排查PWM波形、占空比异常和运动方向这些坑4.1 怎么确认PWM波形真的出来了这一步千万别省。我把这个环节当成调试的第一道关卡先把驱动板不接电机用逻辑分析仪或者示波器测STEP引脚看有没有稳定脉冲输出。没有仪器的时候可以用KEIL仿真里的逻辑分析仪进入调试界面后打开Analysis Windows菜单下的Logic Analyzer添加需要观察的引脚运行程序就能看到波形。但仿真毕竟不是真实电平它只能证明定时器配置逻辑正确到了实际板子上电平、干扰、接线都会影响结果。我个人的习惯是调试盒里常备一款几十块钱的逻辑分析仪采样率十几兆就够看PWM了比来回开关电源快太多。4.2 占空比到不了100%的奇怪现象有人在网上问“STM32F103定时器输出PWM占空比到不了100%”这其实是个容易混淆的概念。在PWM1模式下CCR大于等于ARR时输出确实可以保持全程高电平但在PWM2模式下恰好相反CCR设为0才是100%高电平。如果你在极性配置里又把输出极性设置为低那CCR和占空比的关系又会反转。所以遇到输出异常先检查TIM_OCMode和TIM_OCPolarity两个配置项。另一个高频问题是用TIM1或TIM8输出PWM时没有输出因为这两个是高级定时器初始化后还要调用TIM_CtrlPWMOutputs(TIM1, ENABLE)开启主输出也就是刹车和死区功能里的主输出使能。对于步进电机控制我们不强制要求用高级定时器TIM3和TIM4这类通用定时器配置更简单。4.3 电机不转、抖动、失步速查现象可能原因解决办法电机完全不动EN使能没拉低或信号没共地先拉低EN再发脉冲检查电源共地电机有嗡鸣声但不转脉冲频率超过电机启动频率降低目标频率增加加减速电机抖动并发出咔哒声频率落在共振区或电流不足避开共振频率调大驱动电流跑一段时间后丢步加速度过大或电流偏低减小加速度提高驱动电流电机发热严重电流设置过高降到额定电流70%左右只有一个方向转动DIR信号极性反了交换DIR接线或软件取反排查顺序我建议是先看电源再看信号最后怀疑代码。很多时候电机不转并不是PWM没输出而是驱动板的EN引脚被默认电平使能了或者电机电源没有真正送电。先用手摸一下电机轴如果能感受到静力矩说明绕组已经通电再去找脉冲问题。4.4 回原方向反了怎么调雕刻机、写字机这些设备回原点时经常发现电机越走越远。这个先判断是软件方向反了还是限位信号发错。最粗暴的测试方式把DIR的两根接线对调一下或者把代码里的GPIO_SetBits换成GPIO_ResetBits如果方向反过来说明控制逻辑没问题只是极性定义反了。还有几个容易忽略的细节限位开关如果用常闭触点程序里应该先读到低电平再触发停止如果写成常开逻辑一上电可能就直接误触发机械限位和光电限位的滤波时间也要区分建议在中断或扫描代码里加10ms软件消抖。回原点时通常会采用“高速寻找限位低速脱开限位再低速碰到精确位”的三段式流程这里的方向判断更要仔细。5. 再说几句个人体会做了这么多步进电机控制的小项目我发现最容易让人卡住的不是代码而是建立“频率决定转速、脉冲数量决定位置”这个基本概念。很多人一上来就纠结PWM占空比实际上在STEP/DIR方案里占空比只需要保持一个稳定值即可真正的功夫在频率计算、加减速设计和硬件接线细节上。每次帮别人排查问题最后查到根因时十有八九是使能没拉对、电源没共地、电流没调好这三个硬件问题之一。所以我的建议是拿到板子先别急着抄代码按顺序检查驱动板供电、信号共地、使能和电机接线然后用逻辑分析仪看STEP引脚确认波形后再加上位机联调。如果后续想深入可以从定时器中断输出指定数量脉冲、闭环步进编码器反馈、多轴联动这几个方向扩展STM32F103的定时器资源已经足够支撑三轴雕刻机这类应用。本文还有配套的精品资源点击获取