尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

国产MCU+SA85550驱动42步进电机:定速转动脉冲频率与PWM实现详解

国产MCU+SA85550驱动42步进电机:定速转动脉冲频率与PWM实现详解 简介本资源是一套基于国产国民技术N32G430单片机与矽塔SA85550驱动芯片的步进电机定速控制完整工程面向嵌入式初学者、国产芯片开发者及自动化控制实践者解决国产MCU国产驱动IC协同控制2相4线42步进电机的核心问题适用于工业定位、智能硬件原型开发等对成本与自主可控有要求的场景。压缩包共139个文件含51个头文件.h定义外设寄存器与接口32个C源文件.c实现GPIO、TIM、RCC、ADC等底层驱动及电机脉冲时序逻辑另有.o、.map、.bin等编译产物与Keil工程文件.uvprojx/.uvoptx整体大小为3.14MB。已有177人学习下载代码结构清晰模块化程度高涵盖定时器精准脉冲生成、SA85550细分使能配置、电机方向/使能/复位全流程控制并附带可直接烧录的LedBlink.bin验证固件便于快速上手与调试验证。 最近在折腾一套小设备主控用的是国民技术的N32系列驱动步进电机选了矽塔SA85550电机是最常见的42型两相四线步进电机。需求很朴素上电之后按一个固定转速持续转不搞复杂插补不做加减速曲线就是定速运行。本来以为这种“转起来就行”的活儿半小时搞定结果从接线、算频率到真正把代码跑稳还是踩了几个坑。这篇就把完整链路写清楚为什么用这套组合、SA85550和单片机之间到底该怎么接、定速运行的脉冲频率怎么算、例程源码怎么理解以及实测中容易翻车的几个典型问题。适合刚接触步进电机驱动、手头正好有国产单片机开发板想快速把电机转起来的人参考。1. 为什么是“国民技术SA8555042步进”这条国产链1.1 选型背景与这套方案解决的实际问题先说选型动机。很多做运动控制的工程师习惯了“单片机进口驱动芯片”的组合比如STM32配A4988或者DRV8825这套组合确实资料多、坑少。但这两年做产品选型客户和供应链都在推国产替代手头的MCU项目也从STM32换到了国民技术N32系列。国民技术的N32G45x系列芯片主频、定时器资源、ADC、通信接口都比较齐全标准外设库的写法和ST风格很接近原来写STM32的代码往N32上迁移学习成本不算高。驱动芯片这边矽塔SA85550是一颗国产双H桥步进电机驱动芯片从名字能看出来它做的事和传统STEP/DIR接口的步进驱动芯片一脉相承单片机只负责发脉冲STEP和方向DIR芯片内部负责把脉冲转换成两相四线步进电机所需要的电流时序。这就是整套系统的核心分工——MCU不直接驱动电机线圈它根本没那个电流能力真正出力的是SA85550内部的MOS管MCU只是给“命令”。这个方案解决的问题也很具体你需要让一台42步进电机按照某个固定转速持续运行同时希望主控能随时改变方向、启停甚至以后扩展成精确走固定圈数。如果用普通GPIO翻转来做脉冲转速一高CPU就被占死而且软件延时受中断影响速度会抖。用定时器硬件PWM输出来当STEP信号源频率稳定、不占用CPU、长期运行不漂移这是定速运行最省心的实现方式。1.2 SA85550到底是什么角色别把驱动芯片当放大器很多新手有个误解觉得驱动芯片就是把单片机信号“放大”一下给电机。这个理解不算全错但不够准确。SA85550内部是两路H桥每一路H桥可以控制一相线圈的电流方向。四线两相步进电机有两组绕组正好对应两组H桥。STEP脉冲每来一个上升沿芯片内部的逻辑就控制H桥切换到一个新的通电状态相当于电机走了一个“细分步”。这里有一个很重要的概念步进电机的“一整步”和“细分步”不是一回事。42步进电机典型的步距角是1.8°也就是说电机每整步转过1.8°转一圈需要200个整步。如果驱动芯片没有细分功能那MCU给一个脉冲电机就转1.8°。但SA85550这类芯片内置细分功能可以把1.8°再切成2等分、4等分、8等分甚至32等分。这样MCU每给一个脉冲电机实际只转1.8°的1/8也就是0.225°。细分的好处是低速振动小、噪音低代价是同样的转速需要的脉冲频率会成倍增加。这一点是后面所有频率计算的基础先记住脉冲频率、细分数、目标转速三者是绑定在一起的。2. 硬件接线与关键引脚从42电机四根线到定时器PWM输出脚2.1 两相四线步进电机的线色与相序确认标题里写的“2项4线”实际应该是“两相四线”。“2项”是误写但不影响理解电机内部有两组独立绕组引出四根线。42步进电机因为机座尺寸是42mm×42mm得名不是“42步”。这类电机的引出线线色不同厂家并不完全统一最常见的配色是红、绿、蓝、黑一般红色和绿色是一相蓝色和黑色是另一相但也有厂家用红、蓝、绿、黑的排列。所以拿到电机先别急着按颜色接先确认相序。确认相序有两个土办法都很实用。第一个是用万用表的电阻档两两测量四根线之间的阻值阻值较小的那一对就是同一相绕组。通常42步进电机每相绕组的直流电阻在几欧到几十欧之间如果测出阻值接近0或者无穷大说明测量对象不是一对绕组。第二个办法更直观把任意两根线短接在一起用手去拧电机轴。如果这两根线属于同一相你会明显感觉到电机轴有“顿挫感”转起来一股一股地发涩如果两根线不是同一相短接后电机轴基本没有阻尼感。这是利用步进电机绕组的电磁阻尼特性现场判断非常可靠。顺带说一个线上经常有人问的接线问题有一种说法是“三相步进线序可以任意交换两相”这个说法不能套到两相四线电机上。两相四线没有UVW那种三相概念。如果接好线之后电机反转你可以选择交换其中一相的两根线比如把A和A-对调也可以交换两相的先后顺序A相和B相对调都能让电机反转。但如果只是单纯为了“让它转”接反了最多是方向不对不会烧电机除非你把线接到了电源端。2.2 单片机、SA85550、电机之间的引脚映射下面是我这次实际使用的引脚映射关系。这里特别说一下STEP信号选的是定时器PWM输出引脚不是随便一个GPIO。我用的国民技术N32G45x系列TIM1_CH1默认映射到PA8所以STEP接PA8。TIM1是高级定时器输出PWM时需要额外使能主输出这个细节后面代码部分会讲。SA85550信号功能接单片机引脚说明STEP脉冲输入PA8TIM1_CH1一个脉冲走一个细分步DIR方向控制PB0高低电平决定正反转EN使能/脱机PB1按芯片手册确认有效电平A1/A2电机A相电机红线/绿线同组绕组两根线B1/B2电机B相电机蓝线/黑线另一组绕组两根线VM电机电源12V或24V视电机额定电压与驱动芯片范围VDD逻辑电源3.3V与单片机同电源GND逻辑地/电源地与单片机共地必须连通这里有个关键提醒单片机的地和SA85550的逻辑地必须连在一起。如果不共地STEP信号和DIR信号就没有参考地电平会乱跳轻则电机不转重则损坏IO口。我见过不少“电机抖动但不转”的故障最后查下来就是GND悬空。接线的顺序也建议先接逻辑部分再接VM电源不要带脉冲热插拔电机线不然驱动芯片容易受到反电动势冲击。2.3 使能脚的两种用法SA85550这类芯片通常都有ENEnable引脚作用是把驱动输出关断让电机处于“脱机”状态。实践中有两种用法一种是调试阶段直接把EN拉到有效电平让驱动一直工作相当于把电机一直锁住另一种是正常控制逻辑里用单片机GPIO控制EN需要电机转动时使能不需要时关断这样能省电也能减小电机发热。需要注意不同芯片的EN有效电平可能不一样有的低电平有效有的高电平有效。我在这里的代码示例里默认EN低电平有效但你在实际接线时必须去看SA85550的数据手册确认。如果不确定可以把EN悬空试一下——不少芯片EN悬空时驱动默认工作但养成查手册的习惯更稳妥。还有一个经验方向DIR的电平切换最好在电机停止状态或者STEP不发脉冲的时候切换不要一边高速转一边硬切方向否则容易丢步甚至损坏驱动。3. 定速运行的频率计算转速、细分、脉冲频率怎么换算3.1 核心公式与一个60RPM的算例定速运行的本质就是让定时器稳定输出某个频率的PWM。所以程序设计的第一步不是写代码而是先算出目标转速对应多少赫兹的STEP脉冲。公式很简单脉冲频率(Hz) 目标转速(r/min) ÷ 60 × 电机每圈步数 × 细分数42步进电机步距角是1.8°每圈整步数是360÷1.8200。如果SA85550设置为8细分目标转速是60转/分钟那么脉冲频率 60 ÷ 60 × 200 × 8 1600 Hz也就是说每秒需要给STEP引脚1600个脉冲。这个数算出来之后后面所有定时器配置都围绕这个频率展开。为了让你对“不同细分数下同样转速需要多少频率”有个直观感受我列个表目标转速60RPM每圈整步数细分数所需脉冲频率1细分2001200Hz2细分2002400Hz4细分2004800Hz8细分20081600Hz16细分200163200Hz32细分200326400Hz从表里能看出来一个现实问题细分越高同样的转速下定时器要输出越高的频率。如果你打算用32细分让电机跑到300RPM脉冲频率会到32kHz。这个频率对单片机定时器来说完全能输出但电机在这个速度下扭矩已经开始下降反而更容易失步。所以我个人建议42步进电机的定速运行细分8起步就够了既能让低速段震动小又不会把频率抬得太高。3.2 反向推算定时器分频和重载值频率算好了接下来就是把频率映射到定时器配置。以N32G45x的TIM1为例PWM频率公式是PWM频率 定时器时钟频率 ÷ (预分频PSC 1) ÷ (自动重载值ARR 1)我这次用的板子定时器时钟按72MHz来算具体你手上的板子要看系统时钟和总线分频配置。想让计数时钟变成1MHz方便计算就把PSC设为71这样72MHz除以711正好等于1MHz。然后要输出1600Hz就需要ARR 1 1MHz ÷ 1600Hz 625所以ARR 624。占空比设成50%也就是Pulse (ARR 1) ÷ 2 312。这里要提一个很典型的坑定时器时钟不是想当然等于主频。N32、GD32、STM32这些芯片定时器挂在APB1或APB2总线上如果AHB和APB的分频系数没配清楚定时器时钟可能比主频慢一半甚至更多。网上有人遇到“定时器慢了一倍”的问题多半就是总线分频的问题。判断方法很简单把配好的PWM实际频率用示波器测一下如果和理论值差一倍优先去查时钟树的APB分频配置。3.3 定速运行为什么优先用硬件PWM可能有人会问用定时器中断每次中断翻转一次GPIO不也能产生脉冲吗确实能而且很多人入门时这么干。但定速运行它的区别在于中断方式受主循环和中断嵌套影响频率会抖动而且频率越高CPU占用率越大。硬件PWM不一样定时器一旦启动波形就由硬件持续产生CPU完全不用管。你只负责配置好频率主循环里哪怕跑LCD刷新、按键扫描也不影响电机转速。所以我这次例程的核心思路就是STEP信号由TIM1_CH1的PWM硬件输出DIR和EN由普通GPIO控制。这个架构最稳定也是工业上步进电机控制最常用的做法。4. 例程源码逐段拆解GPIO、定时器PWM与主循环4.1 GPIO初始化把每个脚的模式选对先看工程里最基础的GPIO部分。整个源码基于国民技术N32G45x系列的标准外设库函数名可能和你手上的SDK版本有小差异但配置逻辑是通用的。#include n32g45x.h /* STEP引脚接TIM1_CH1默认映射到PA8 */ #define STEP_PORT GPIOA #define STEP_PIN GPIO_PIN_8 /* DIR方向引脚PB0 */ #define DIR_PORT GPIOB #define DIR_PIN GPIO_PIN_0 /* EN使能引脚PB1示例按低电平有效处理 */ #define EN_PORT GPIOB #define EN_PIN GPIO_PIN_1 static void MY_GPIO_Init(void) { GPIO_InitTypeDef gpioInit; /* 使能GPIOA和GPIOB时钟 */ RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA | RCC_APB2_PERIPH_GPIOB, ENABLE); /* DIR和EN配置为普通推挽输出 */ gpioInit.Pin DIR_PIN | EN_PIN; gpioInit.GPIO_Mode GPIO_Mode_Out_PP; gpioInit.GPIO_Speed GPIO_Speed_50MHz; GPIO_InitPeripheral(DIR_PORT, gpioInit); /* STEP引脚配置为复用推挽输出由TIM1_CH1驱动 */ gpioInit.Pin STEP_PIN; gpioInit.GPIO_Mode GPIO_Mode_AF_PP; gpioInit.GPIO_Speed GPIO_Speed_50MHz; GPIO_InitPeripheral(STEP_PORT, gpioInit); /* 初始状态方向先定为正转使能驱动 */ GPIO_WriteBit(DIR_PORT, DIR_PIN, Bit_RESET); GPIO_WriteBit(EN_PORT, EN_PIN, Bit_RESET); }一个细节STEP引脚的GPIO模式必须是复用推挽AF_PP不能配成普通推挽输出否则定时器的PWM信号不会从引脚送出去。很多新手第一版代码电机完全没反应查到最后就是这个模式没配对。DIR和EN用普通推挽输出就够因为这两个脚只是电平信号不需要硬件外设接管。4.2 定时器PWM配置高级定时器别忘了主输出TIM1是高级定时器配置过程和普通定时器TIM2、TIM3略有不同它有一个主输出使能MOE控制位需要额外打开否则PWM不会输出。这也是TIM1和TIM2在使用上最大的区别。static void MY_TIM1_PWM_Init(uint32_t freq_hz) { TIM_TimeBaseInitTypeDef timBase; TIM_OCInitTypeDef timOc; uint16_t arr; uint16_t psc; /* 使能TIM1时钟 */ RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_TIM1, ENABLE); /* * 假设定时器时钟72MHz。 * PSC71计数时钟72MHz/721MHz。 * ARR 1MHz/freq_hz - 1。 */ psc 71; arr (uint16_t)(1000000UL / freq_hz) - 1; timBase.Period arr; timBase.Prescaler psc; timBase.ClockDivision TIM_CLOCK_DIV_1; timBase.CounterMode TIM_COUNTER_MODE_UP; TIM_InitTimeBase(TIM1, timBase); /* 通道1输出PWM1占空比50% */ timOc.OcMode TIM_OC_MODE_PWM1; timOc.Pulse (arr 1) / 2; timOc.OcPolarity TIM_OC_POLARITY_HIGH; TIM_InitOc(TIM1, timOc); /* 高级定时器必须使能主输出 */ TIM_SelectOutputEnable(TIM1, ENABLE); /* 启动定时器开始输出PWM */ TIM_EnablePwm(TIM1, ENABLE); }主函数就非常简洁了int main(void) { /* 系统时钟初始化使用外部晶振或者内部RC具体按你的工程配置 */ /* SystemClock_Config(); */ MY_GPIO_Init(); MY_TIM1_PWM_Init(1600); /* 1600Hz对应60RPM8细分 */ while (1) { /* 定速运行PWM由硬件持续输出主循环做其他事即可 */ /* 如需动态调速可以在这里读按键或串口指令重新调用MY_TIM1_PWM_Init */ } }这段代码跑起来之后电机会以固定方向、固定转速持续运行。调速度只需要改MY_TIM1_PWM_Init的参数就行改成800就是4细分下60RPM对应的一半速度改成3200就是2倍速度。这种可读性很好的写法调试的时候非常方便我把目标频率定义成宏放在文件头部这样调参不用钻进函数里找。4.3 转向控制与使能控制怎么加只在主循环里空转显然太单调。实际项目里往往需要根据工况切换方向或者停止。方向控制很简单就是操作DIR引脚的电平/* 正转 */ GPIO_WriteBit(DIR_PORT, DIR_PIN, Bit_RESET); /* 反转 */ GPIO_WriteBit(DIR_PORT, DIR_PIN, Bit_SET);使能控制是控制EN引脚/* 使能驱动电机处于锁定状态能保持力矩 */ GPIO_WriteBit(EN_PORT, EN_PIN, Bit_RESET); /* 关闭驱动电机脱机可以手动转动电机轴 */ GPIO_WriteBit(EN_PORT, EN_PIN, Bit_SET);我在实际调试时习惯先开EN使能再启动PWM停止时先关PWM再考虑要不要关EN。不要让电机带着脉冲被直接脱机否则高速转动的电机突然失去保持力矩负载可能会带动电机反转造成意外。这个顺序对设备安全很重要。4.4 从“一直转”到“转固定圈数”的扩展定速运行是最基础的模式但很多设备实际需要的是“转几圈精确停下来”。在现有代码上扩展并不难思路是用定时器更新中断来数脉冲每来一个PWM周期脉冲数加一当累计值到达目标脉冲数时关闭PWM。目标脉冲数的计算公式目标脉冲数 电机每圈步数 × 细分数 × 目标圈数例如8细分下要转10圈就是200 × 8 × 10 16000个脉冲。中断里计数volatile uint32_t pulse_cnt 0; volatile uint32_t pulse_target 0; void TIM1_IRQHandler(void) { if (TIM_GetIntStatus(TIM1, TIM_IT_UPDATE) ! RESET) { TIM_ClrIntPendingBit(TIM1, TIM_IT_UPDATE); pulse_cnt; if (pulse_target 0 pulse_cnt pulse_target) { /* 到达目标脉冲数停止PWM输出 */ TIM_EnablePwm(TIM1, DISABLE); TIM_SelectOutputEnable(TIM1, DISABLE); } } }这种方式的特点是脉冲的产生依然由硬件定时器负责中断只负责计数所以不会引入时间抖动停止位置精度也比较高。如果要更精确可以配置另一个定时器工作在外部时钟模式直接由硬件统计STEP脉冲个数完全不用进中断那就是更进一步的做法了当前例程没必要上这个复杂度。需要提醒的是中断服务函数里尽量不要做耗时操作计数和判断脉冲量都是微秒级以内的操作没问题。如果要在到达目标后马上切换方向或者做别的动作最好置一个标志位放到主循环里去处理不要在中断里执行复杂逻辑。5. 实测中容易翻车的几个点失步、发热与电源处理5.1 为什么直接给高速脉冲会丢步刚把代码跑通的时候我习惯性把目标速度调高测试结果发现电机在低速起步阶段直接“咔咔”响甚至原地不动。这就是典型的失步。原因很简单步进电机的输出扭矩随转速升高而下降从静止状态直接施加高频脉冲转子来不及跟上定子的磁场切换就会丢步。对于定速运行需求如果你确认目标转速不高、负载不大直接从低速启动没有太大问题。但如果目标转速比较高比如超过200RPM或者负载有惯性最好做一个简单的软启动启动时先把PWM频率设成一个较低值比如200Hz然后每隔一小段时间提升一次频率逐步到目标值。这就是梯形加减速的雏形。最简单的软启动代码不需要很复杂可以在主循环里定时调整PWM频率#define START_FREQ_HZ 200 #define TARGET_FREQ_HZ 1600 static void Ramp_Up_Start(void) { uint32_t f START_FREQ_HZ; while (f TARGET_FREQ_HZ) { MY_TIM1_PWM_Init(f); Delay_Ms(20); f 100; } MY_TIM1_PWM_Init(TARGET_FREQ_HZ); }这个函数在启动时调用能让电机从低速平滑加速到目标速度实测下来能明显减少失步和噪音。当然这只是一个简化版本真正的加减速算法要考虑加速度曲线但对于定速运行的设备这种分级提速已经够用了。5.2 驱动芯片发热与电机电流设置SA85550驱动芯片在持续运行后发热是正常现象尤其是电机电流超过1A的时候芯片外壳会明显烫手。42步进电机的额定电流一般在0.4A到1.7A之间具体看型号。很多42电机标称1.5A但实际设备里不用顶格跑把电流适当调低一点发热和噪音都能明显下降扭矩可能只损失一点点。判断驱动电流是否合适的方法让电机静止锁住用手去拧电机轴如果感觉能轻松拧动说明电流偏小如果感觉完全锁死、很难转动说明电流足够。运行时摸电机外壳如果温度稳定在六七十度以内问题不大如果烫到无法长时间触摸就需要降低电流或者改善散热。电机本身作为执行元件转久了温热是物理常态不要误以为这是故障。5.3 电源纹波对运行稳定性的影响定速运行看似简单但电机的电流是脉冲式的瞬间电流变化很大。如果电源输出能力不足或者电源线太长VM电压在脉冲瞬间会明显跌落轻则电机力矩波动重则驱动芯片触发欠压保护直接停机。我的处理习惯是在驱动芯片的VM引脚附近并联一个大电解电容容量按驱动电流来选1A左右电流时至少用470µF到1000µF再并联一个0.1µF的陶瓷电容吸收高频尖峰。电解电容就近安装不要离VM脚太远否则走线电感会削弱电容的作用。另外逻辑电源3.3V和电机电源VM最好分开供电不要用同一个LDO强行带电机否则MCU容易不断复位。5.4 用示波器验证STEP波形而不是靠耳朵调试过程中最忌讳的是“听声音试速度”。听觉判断只能知道电机在转但转速是否准确、波形是否正常完全看不出来。最直接的办法是用示波器量PA8和GND之间的波形确认频率是否等于你计算的目标值。实测发现很多配置问题在示波器上一目了然频率偏一半是定时器时钟分频问题波形完全不出是GPIO复用模式没配对或高级定时器主输出没使能占空比异常是TIM_OC配置有误。没有示波器的话也可以用逻辑分析仪或者退一步用频率计实在都没有才用听感和手感受粗调。但我还是建议大家想办法搞一台示波器步进电机调试基本离不开它。5.5 一个让调试更顺手的小习惯最后分享一个我自己的小习惯把所有需要调参的量收敛到文件头部的宏定义而不是散落在函数里。比如#define MOTOR_RPM 60 #define MOTOR_MICRO_STEP 8 #define MOTOR_STEPS_PER_REV 200 #define STEP_FREQ_HZ ((MOTOR_RPM / 60.0) * MOTOR_STEPS_PER_REV * MOTOR_MICRO_STEP)这样改速度、改细分数就只需要动一行宏STEP_FREQ_HZ会自动重新计算。调参数的时候不用翻代码找函数里面的数字减少出错概率。这个习惯在项目代码稍微复杂之后节省的时间非常可观。等你以后从“转起来”做到“精确走位”这套代码的主干也都不用推翻只需要在现有基础上叠加中断计数和运动状态管理就行。本文还有配套的精品资源点击获取
返回列表