ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

STM32F103ZET6驱动57闭环步进电机:从接线到PWM控制完整指南

STM32F103ZET6驱动57闭环步进电机:从接线到PWM控制完整指南 1. 项目背景与整体方案这个项目是我在给手上的一台小CNC改造成闭环驱动的过程中攒出来的经验。整套硬件很典型STM32F103ZET6最小系统板一块、57闭环步进电机三台、配套闭环驱动器三台再加一个48V直流开关电源。标题虽然写的是“STM32F103ZET6驱动57闭环步进电机”但拆开来看其实要解决三个层面的问题电机与驱动器的连接驱动器和单片机的信号连接还有单片机端生成“脉冲方向”的控制代码。很多朋友第一次接触闭环步进会以为闭环控制是单片机这边要读编码器数据做PID算法。实际不是。市面上的57闭环步进电机是把编码器装在电机尾部编码器直接接在闭环驱动器上驱动器内部完成位置比较和误差补偿。对STM32来说它只需要按照普通开环步进的方式输出脉冲和方向信号剩下的闭环纠错由驱动器自己消化。这一点想明白项目难度立刻降了一半。这篇内容适合刚入门步进电机控制、手头有F103开发板又不想花大几百买专用运动控制卡的玩家。照着里面的接线表和代码结构先单轴跑通再扩展到三轴逻辑上是完全一致的。我会把从器件选型到最终调试的完整链路讲清楚尤其是那些容易踩坑的接口电平、共地、细分拨码和PWM频率换算问题。1.1 为什么选57闭环步进电机57步进电机也叫NEMA 23机身方形的边长是57mm步距角1.8°标准上一圈是200个整步。开环驱动时如果负载偏重或者加减速太猛电机就会出现丢步位置误差会一直累积。闭环版本在尾部加了一个编码器常见的配置有1000线、2000线或2500线驱动器会实时读取编码器位置并和指令位置做比较一旦发现实际位置落后于目标位置就自动加大电流把转子“追”上去。只要负载没有超过电机的输出转矩极限闭环系统基本能做到不丢步。和真正的伺服电机相比57闭环步进的优势是便宜、系统简单、抗过载能力强。伺服驱动器通常要220V供电调试参数也多。闭环步进只需要一个直流电源拨码开关设好细分和电流接上脉冲方向就能跑非常适合DIY设备、小型三轴平台、激光雕刻机和自动送料机构。选型时要注意额定电流和电源电压的匹配。57闭环电机常见额定电流在2.8A到4.2A之间驱动器供电电压一般推荐24V到50V直流。电压越高高速性能越好但也别超过驱动器上限比如CL57驱动器最高是50V我用48V正好合适。电源额定电流按所有电机总电流的1.5到2倍选三台4.2A电机建议配10A以上的电源免得重载时电压掉太多触发驱动器过压或者欠压报警。1.2 核心器件选型清单我再把这个项目用到的器件列个表方便你照着采购。表里列的不是唯一答案只是我自己跑通过的一套组合。器件数量选型建议备注主控板1STM32F103ZET6最小系统板大容量引脚多后续扩展方便闭环步进电机1-357机座2.8-4.2A带编码器三轴联动要多买闭环驱动器1-3支持脉冲/方向接口带编码器接口如雷赛CL57、研控YKC2609开关电源148V直流功率按电机功率3倍以上建议带输出指示灯和端子排5V辅助电源1给单片机和脉冲接口供电注意和驱动器信号共地控制线若干双绞屏蔽线线径0.3mm²以上脉冲、方向、使能各一对杜邦线若干杜邦端子对驱动器端子调试阶段方便换线选择STM32F103ZET6这块芯片的原因很直接它有一堆通用定时器可以输出多路独立PWM跑三轴甚至四轴脉冲绰绰有余。加上ZET6是100脚封装GPIO数量多放弃某个引脚定义随时可以改到别的脚上。标准库和HAL库资料都多遇到问题搜起来也容易。对这项目来说性能完全够用没必要上F4或F7。2. 硬件接线从驱动器到单片机的每一根线接线是最容易出错也最容易让新手崩溃的一环。我见过很多“代码没问题但电机就是不动”的情况最后查下来都是某个端子没接对或者电平不匹配。下面我把整条链路分成电源、电机主回路、控制信号三条线路来讲信号方向从驱动器往外面看一根一根理清楚。2.1 电源与电机主回路接线先做电源部分。48V开关电源的L、N接市电V和V-分别接驱动器的电源正负极。大多数闭环驱动器的电源输入端子旁边标着“DC”“DC-”或者直接标“POWER”。电源线用0.75mm²以上的铜线压好冷压端子再插进去每台驱动器并联接到电源输出端不要串接免得第一台驱动器承担全部后续电流导致端子发热。接下来是电机主回路。57闭环电机引出线通常是四根相线颜色一般是A红、A-绿、B黄、B-蓝对应的就是驱动器上的A、A-、B、B-端子。两相步进电机没有公共端很容易和五线四相的小步进弄混接之前一定要看驱动器外壳丝印和电机说明书确认绕组的相序。接错相序的后果是通电后电机会发出明显的嗡嗡声锁轴力矩小低速时来回抖动。编码器线接驱动器时更得小心翼翼。电机尾部的编码器一般是一根多芯线插头分得很细常见的有电源正、电源地、A、A-、B、B-。编码器接口必须在断电状态下插拔带电插拔极易烧编码器内部的差分芯片。插好后建议用扎带把插头固定在驱动器底板上防止震动让插头松动导致报警。2.2 控制信号线脉冲、方向、使能闭环驱动器的控制端子通常叫PUL脉冲、DIR方向、ENA使能和普通开环步距驱动器的端子定义几乎一样。每个信号一般都有正端和负端比如PUL、PUL-、DIR、DIR-、ENA、ENA-。这六个端子里面每一对正负端其实就是光耦隔离一侧的发光二极管两端电流方向定了光耦才能导通。这里有个关键知识点STM32的GPIO输出是3.3V而很多步进驱动器的光耦输入侧为了兼容PLC的24V默认设计的驱动电压是5V到24V。直接把3.3V引脚拉低到GND去点亮5V光耦电流通常不够会导致脉冲信号识别不了。解决办法最常用的是共阳极接法把PUL、DIR、ENA这三个正端全部接到一起统一接到外部5V电源的正极然后用单片机引脚去控制对应的负端输出低电平时回灌电流点亮光耦。共阳极接线时单片机引脚内部要配置成推挽输出引脚输出低电平时电流从5V经光耦LED流入引脚到GND。一个光偶LED的限流电阻驱动器厂商已经做好了内部电阻决定了这路的电流大小单片机推挽输出灌电流一般能到8-20mA驱动多数闭环驱动器都没问题。为了保险起见我习惯在单片机上的PUL-、DIR-、ENA-引脚外再并联一颗1kΩ上拉电阻到3.3V防止引脚悬空时信号抖动。连接方式汇总成表格就是驱动器端子单片机引脚接线说明PUL外部5V正极共阳极脉冲输入正端PUL-PA0定时器PWM输出低有效DIR外部5V正极方向选择正端DIR-PB1方向控制低电平/高电平切换ENA外部5V正极使能正端ENA-PB2使能控制低电平使能ALM3.3V经上拉驱动器报警输出可接PB3读取5V电源GNDGND必须和单片机GND共地否则光耦回路不通2.3 编码器反馈接线闭环驱动器和电机编码器是配套出厂的一般线上接好就能用。编码器线有五芯或八芯常见是屏蔽双绞线。接线时严格按照插头定义来特别别把编码器电源正接反否则编码器芯片瞬间就烧了。接好之后驱动器面板上通常会显示“位置反馈正常”或者编码器计数状态部分驱动器还有个报警灯编码器没接好就会亮红灯或者报ER-01之类的故障码。编码器的作用是让驱动器知道电机当前的真实位置。位置信号在驱动器内部通过差分方式接收抗干扰能力强所以编码器线和脉冲线最好分开走线槽不要和电机主回路电源线紧紧绑在一起。实际项目里数控设备走90米/分钟的脉冲频率还稳定就是从物理隔离开始的。2.4 引脚分配与接线总表我最终采用的引脚分配如下按“一个定时器输出脉冲、两个普通GPIO控制方向与使能”的经典方案方便三轴扩展功能驱动器端子STM32引脚通道/模式说明X轴脉冲PUL-PA0TIM2_CH1PWM模式输出步进脉冲X轴方向DIR-PB1GPIO输出推挽电平决定方向X轴使能ENA-PB2GPIO输出推挽低电平使能驱动器报警ALMPB3GPIO输入下拉选接可做保护Y轴脉冲PUL-PA1TIM2_CH2PWM模式三轴时用Z轴脉冲PUL-PA2TIM2_CH3PWM模式三轴时用实际上TIM2最多有四个通道可以输出四路PWM所以一块F103ZET6最多能同时带四个脉冲轴前提是每个轴的频率独立配置。这个方案不像用软件翻转IO那样占用CPU脉冲频率稳定驱动器也不会因为脉冲间隔抖动而发热。3. 代码实现定时器PWM加方向控制的两种写法代码这块我用的是HAL库。原因很简单ST官方现在主推HAL库网上能找到的现成例子最多即使你用的是标准库把寄存器映射看懂了迁移也很容易。整个控制逻辑分三层底层是定时器产生频率可调的PWM波中间层是脉冲频率和速度的换算最上层是方向和使能控制以及简单的加减速调度。3.1 初始化GPIO与定时器配置先处理GPIO。PA0用于PWM输出PB1、PB2作为方向、使能控制。方向、使能引脚都要配置成推挽输出速度设成高速。注意方向和使能引脚没事的时候给个确定电平方向引脚默认高电平或低电平根据电机模块安装方向来定使能引脚要确认驱动器的“有效电平”到底是高还是低。我用的闭环驱动器使能信号是低电平有效所以单片机这边初始化时设成GPIO_PIN_SET高电平让驱动器处于关闭输出状态防止一上电电机就锁轴。等程序跑起来需要运动时再把PB2拉低使能电机。定时器配置是核心。主频72MHz从TIM2出发做PWM。TIM2挂在APB1总线上时钟倍频后是72MHz。为了让输出脉冲频率可以精确计算我先把预分频器PSC设为8也就是除以9得到8MHz的计数时钟。然后根据目标速度计算ARR寄存器的值。比如我想让电机以300转/分钟的速度转细分为16也就是每转需要3200个脉冲。每秒需要的脉冲数是300除以60乘以3200等于16000Hz。计数时钟8MHz除以16000HzARR就是500。代码里ARR写500输出就是16kHz的PWM正好对应300转/分钟。初始化代码大概长这样static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); // PIN_DIR(PB1), PIN_ENA(PB2) GPIO_InitStruct.Pin PIN_DIR | PIN_ENA; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_PULLUP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); HAL_GPIO_WritePin(GPIOB, PIN_DIR, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, PIN_ENA, GPIO_PIN_SET); } static void MX_TIM2_Init(uint16_t psc, uint16_t arr) { TIM_OC_InitTypeDef sConfigOC {0}; htim2.Instance TIM2; htim2.Init.Prescaler psc - 1; // 72MHz / 9 8MHz htim2.Init.CounterMode TIM_COUNTERMODE_UP; htim2.Init.Period arr - 1; // 决定脉冲频率 htim2.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; htim2.Init.AutoReloadPreload TIM_AUTORELOAD_PRELOAD_ENABLE; HAL_TIM_PWM_Init(htim2); sConfigOC.OCMode TIM_OCMODE_PWM1; sConfigOC.Pulse (arr - 1) / 2; // 占空比50% sConfigOC.OCPolarity TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode TIM_OCFAST_DISABLE; HAL_TIM_PWM_ConfigChannel(htim2, sConfigOC, TIM_CHANNEL_1); HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_1); }ARR初始化可以根据目标速度算好再调用也可以在运行中随时修改。修改ARR的同时要调用__HAL_TIM_SET_AUTORELOAD(htim2, val)更新同时同步修改占空比比如__HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, val / 2)这样频率切换的过程中占空比不会跳到奇怪的数值脉冲宽度也更均匀。3.2 控制逻辑方向、使能与转速换算方向切换要在电机停稳之后操作。如果电机还在惯性转动中直接反向换向步进电机会瞬间受到一个很大的反向制动力矩可能直接导致驱动器过流报警甚至烧模块。更稳妥的控制顺序是先停止输出脉冲延时几十毫秒再翻转方向引脚然后再启动脉冲输出。使能控制的时机也很讲究。正常运行时使能引脚保持低电平电机处于锁轴状态此时用手拧电机转轴会感觉到明显的阻力。停机超过几秒钟想让电机掉电可以拉高使能引脚驱动器输出电流归零电机转轴恢复自由转动方便手动调位置。速度换算直接在代码里做成一个函数传转速进去就返回ARR值uint16_t rpm_to_arr(uint16_t rpm, uint16_t microstep, uint16_t psc) { uint32_t pulse_per_sec (rpm * microstep * 200) / 60; return (uint16_t)(8000000UL / pulse_per_sec); // 8MHz时钟 }这里的200是电机一圈的整步数microstep是驱动器拨码设定的细分数比如16。要注意的是ARR和频率是反比关系ARR越小红线越高电机转速越快。所以做加减速时不是线性改变ARR而是按速度线性的脉冲频率变化规律来改。3.3 加减速与多段运动示例步进电机最大的敌人是启动时瞬间高频脉冲。如果目标转速是600转/分钟一上电就直接输出20kHz脉冲电机会直接堵转或者丢步。闭环步进的容错能力强一点但物理转矩还是有上限。所以正规控灵动代码都要设计加减速曲线。最简单的实用做法是梯形加减速设定一个起始频率比如300Hz然后每隔一小段时间把频率提高一档直到目标频率减速过程反过来。频率步进量按加速时间均分比如0.5秒加速到16kHz分20步每一步间隔25毫秒每步频率增加约785Hz。ARR的变化就可以按照这个节奏来更新。实际控制中我用了两个函数motor_run_speed(uint16_t rpm)直接快速设置目标频率motor_move_smooth(uint16_t target_rpm, uint16_t accel_ms)做加减速。加速阶段里用一个for循环每次设置新的PWM频率并延时25毫秒直到跑到目标值。思路核心代码void motor_accel_to_speed(uint16_t target_rpm, uint16_t current_rpm) { uint16_t arr; while (current_rpm target_rpm) { current_rpm 50; arr rpm_to_arr(current_rpm, 16, 9); __HAL_TIM_SET_AUTORELOAD(htim2, arr - 1); __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, (arr - 1) / 2); HAL_Delay(25); } }实际跑起来这段逻辑够用。如果做更大负载的龙门设备建议直接用公式PID曲线或者S曲线那是另一个话题。对57闭环步进来说梯形加减速已经把绝大部分丢步风险解决了再加上闭环驱动器的纠错能力进程稳得很。3.4 完整运行示例代码整合成一个简单的main循环电机先以500转/分钟正转2秒然后减速到0换向反转2秒再回到正转形成一个往复运动。用来验证接线和代码是否正常绰绰有余。int main(void) { HAL_Init(); SystemClock_Config(); // 72MHz MX_GPIO_Init(); MX_TIM2_Init(9, 500); HAL_GPIO_WritePin(GPIOB, PIN_ENA, GPIO_PIN_RESET); // 使能电机 while (1) { HAL_GPIO_WritePin(GPIOB, PIN_DIR, GPIO_PIN_RESET); // 方向1 motor_accel_to_speed(500, 0); HAL_Delay(2000); motor_accel_to_speed(0, 500); HAL_GPIO_WritePin(GPIOB, PIN_DIR, GPIO_PIN_SET); // 方向2 motor_accel_to_speed(500, 0); HAL_Delay(2000); motor_accel_to_speed(0, 500); } }注意motor_accel_to_speed(0, 500)里目标转速为0循环会跳过但ARR要显示设置到某个安全的低速然后停止。这里我把0的情况单独处理了直接把PWM停止而不是把ARR改成极小值。否则ARR写成0或1会输出极高频率驱动器会报脉冲过快错误。4. 参数设定与现场调试代码部分跑通后剩下就是驱动器参数和系统调试。很多机器最后精度不行、发热严重甚至啸叫问题往往出在这几组参数上面。4.1 细分、电流与每转脉冲设定闭环驱动器面板或侧面的拨码开关第一组SW1-SW3控制细分数第二组SW4-SW6控制峰值电流。细分数决定每转需要的脉冲数同时也影响分辨率。常见的细分有400、800、1600、3200、6400、12800、25600。细分越高单脉冲对应的角度越小运转越平稳但单位时间内需要输出的脉冲频率也更高单片机负担会加重。我用16细分加上电机本身200步/圈相当于每转3200个脉冲对F103的80MHz定时器来说毫无压力而且换算方便。电流拨码要根据电机铭牌上的额定电流来设。很多57电机额定3A如果按驱动器最大的5.6A去设电机会发热严重表面烫手。保守一点设成额定的80%-100%比如额定3A就设3A闭环驱动本身会在过载时自动补偿电流不需要靠长期超额定电流来提供力矩。驱动器面板上一般还会显示“Pulse/rev”和“Motor current”设好之后上电的时候会亮一下参数状态用来看设置值是否生效。4.2 脉冲频率与速度换算单片机能产生的脉冲频率不是无限高的和定时器位数、CPU频率有关。F103ZET6主频72MHz用16位定时器输出PWM最小ARR可以做到1PWM频率最高可以到36MHz但这个频率对驱动器的光耦来说太高了信号会失真。实际项目里脉冲频率在几百Hz到几百kHz都是常见场景。我建议脉冲频率算好一个“安全上限”。比如细分为3200时目标最大转速600转/分钟对应32kHz脉冲频率。闭环驱动器接收能力远不止这个数但在32kHz级别单片机不用闯入中断就能稳定输出帕斯。我实测用示波器看PWM波形频率偏差在个位数Hz以内驱动器完全不报警。如果速度更高建议换差分输出方案或者用专用运动控制芯片比如TMC、TB66系列驱动器自带STEP/DIR接口信号输入还能接受差分形式抗干扰更好。4.3 用上位机和串口辅助调试做运动控制调试时没有上位机光靠烧录程序改参数效率太低了。我通常会预留一个USART1串口通过CH340模块接到电脑用简单的串口调试助手发指令控制电机。比如发送“S500”表示设速度500转/分钟发送“M1000”表示正转1000个脉冲。不需要重协议命令行字符串解析就够了。这样调加减速参数的时候不需要反复拔插ST-Link重新烧程序。ST-Link下载器在开发阶段必不可少。F103ZET6板子上的SWD接口四根线分别是SWDIO、SWCLK、GND、3.3V接好之后Keil里设置成ST-Link下载器能看到实时的寄存器变量和全局变量。如果单片机引脚特别紧张甚至可以只保留SWDIO和SWCLK两根线就能调试。调试时我会先开一个定时器中断在中断里翻转一个GPIO作为逻辑分析仪测试点通过测量这个GPIO的周期来估算代码阻塞时间。这个办法比单纯看HAL_Delay准得多也方便发现加减速曲线里有没有小的停顿。5. 常见故障排查与避坑指南项目做完后我回想了整个过程中出现的几个问题整理成下面的速查表。新手遇到问题先对表查一轮比盲改代码快得多。5.1 常见问题速查表现象可能原因排查方法上电电机不锁轴使能信号接反或电平不对测量ENA-引脚电平确认低电平有效电机通电但转不动A、A-、B、B-相序接错断电检查绕组组别重新接线电机抖动严重控制脉冲频率太低或太高示波器看PUL波形核对PWM频率偶发丢步电源线太长电压跌落加粗电源线加电解电容驱动器报警ER-02编码器线接触不良重新插拔编码器插头确认屏蔽层接地单片机程序跑着跑着复位电机电源和逻辑电源共用了同一个电源电机电源单独供电与5V共地驱动器接收不到脉冲共阳极接法不对PUL悬空PUL接5VMCU输出拉低PUL-5.2 实操心得与独家避坑经验第一共地是悬在所有步进控制项目上的一个隐形大坑。很多朋友接了信号线但忘记把开关电源的地和单片机开发板的GND连在一起光耦回路没有电流电机怎么配置都不动。实际连接时我会把48V电源的负极、5V电源负极、驱动器DC-端子、单片机GND都汇聚到同一个接地排上全部直接连接避免形成多个参考地的电位差。第二脉冲线一旦超过30厘米建议用双绞屏蔽线屏蔽层一端接到驱动器外壳。如果设备靠近变频器或开关电源这种强干扰源脉冲线上还要串一个1kΩ电阻并并联100pF电容做一个简单的低通滤波防止干扰脉冲引起乱走位。第三闭环驱动器的报警输出端很有用。ALM接单片机的外部中断引脚比如PB3配置成下降沿触发。当电机过载或者堵转时驱动器报警单片机立刻停止发送脉冲并关闭使能。这个保护逻辑在无人值守的设备上简直是救命稻草。我在调试初期就是靠这个报警输出来决定把加减速时间加长。第四换向时要留死区时间。步进电机从正转变为反转如果没有任何停顿驱动器内部的电流环要在极短时间内完成相位切换容易触发过流报警。程序里方向切换后至少要延时20毫秒再启动PWM输出虽然会让定位时间变长一点点但换来的是系统稳定性。6. 项目扩展从单轴到多轴的改造思路这个项目只是起点。把这套逻辑扩展成X、Y、Z三轴在硬件上只需要再分配两个定时器通道、两个GPIO引脚分别控制另外两台驱动器。代码结构里把“脉冲频率”抽象成一个数组每个轴一个定时器把“方向”和“使能”抽象成每个轴一组GPIO程序里就能很自然地加上三轴插补的逻辑。如果你后续准备跑更加复杂的轨迹运动比如画直线、画圆弧那就要考虑脉冲分配算法和加减速前瞻了。那时候F103ZET6还有潜力但建议把实时运动规划放到上位机或单独的运动控制芯片中。无论如何本文里这套“定时器PWM方向GPIO闭环驱动器”的组合都是整个系统里最基础也最关键的一环。先把这一步吃透后面扩展多少轴都不会有心虚的感觉。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进