低成本闭环步进方案:HK32F030C8T6+TB67H450+MT6816设计详解

低成本闭环步进方案:HK32F030C8T6+TB67H450+MT6816设计详解 简介面向嵌入式工程师、硬件爱好者与学生的航顺HK32F030C8T6步进电机控制板完整设计资料硬件采用40×40mm双层板布局覆盖主控、TB67H450驱动、MT6816编码器与TLP2168光耦隔离电路可用于步进电机驱动、编码器反馈及控制算法学习与二次开发。压缩包共642个文件容量29.01MB包括C语言源码、头文件、汇编启动文件、Keil工程配置.uvprojx/.uvoptx、原理图.schdoc、PCB.pcbdoc、BOM清单及hex烧写文件等从电路设计到软件工程均有完整呈现。板载两颗最高3.5A的TB67H450驱动芯片配合MT6816实现小于0.08度的控制精度支持4/8/16/32倍电子齿轮自由设置12-30V宽电压输入可适配42/57步进电机。目前已有1281人学习资料内还包含软件例程源码、编译中间文件与调试配置可直接用于参考设计、代码移植或开发排错是一份完整的步进电机控制方案参考资料。1. 这组“HK32F030C8T6TB67H450MT6816”组合为什么值得照抄前阵子做一款机器人关节用的闭环步进控制器原本选型是STM32F030C8T6加两颗A4988结果一算成本超预算两倍而且编码器走增量式ABZ上电必须找零位。后来换成航顺HK32F030C8T6做主控TB67H450做电机驱动MT6816做磁性角度传感器整套方案在硬件和软件上都接近“低成本闭环步进”的最终形态。这个方案能解决的实际问题很具体普通开环步进丢步后无法察觉而MT6816是14位绝对角度编码器上电即可得到电机轴绝对角度配合HK32的定时器输出两路PWM控制TB67H450就能构成低成本的位置环。适合做3D打印机挤出机、云台、小型AGV舵轮这类对成本敏感又要一定闭环精度的设备的工程师也适合想把“国产MCU独立驱动磁编码器”这套架构吃透的嵌入式开发者。整个链路里最容易被低估的是HK32F030C8T6和STM32F030之间的寄存器级差异以及TB67H450的电流斩波设置方式。网上很多例程直接把STM32的HAL库拿来编译结果定时器重映射引脚不对、ADC采样值偏大问题都出在细节。下面按原理图、PCB、软件例程源码这条线拆开讲重点写清楚哪里能平移、哪里必须重写。2. HK32F030C8T6最小系统与TB67H450驱动电路原理图设计要点2.1 HK32F030C8T6与STM32F030C8T6的引脚差异和最小系统HK32F030C8T6是LQFP48封装引脚排列大体兼容STM32F030C8T6所以大部分情况下可以直接替换PCB板。但有两处不能简单照抄一是NRST引脚上拉电容和复位电路的时序参数不同航顺官方建议复位电容用100nF有些STM32板卡习惯用1uF换成HK32后偶尔出现上电复位不彻底二是PA9/PA10、PB6/PB7这些引脚的复用功能分配有差异尤其是I2C1和USART1的映射需要仔细查HK32F030C8T6的数据手册里AFIO复用表。最小系统的设计我一般这么做VDDA和VSSA之间串一个10uH磁珠再加1uF和0.1uF去耦电容这是国产MCU ADC精度不翻车的常见做法。晶振用8MHz无源晶振两个22pF负载电容配合芯片内部的PLL倍频到72MHz。BOOT0接10kΩ下拉电阻到地避免意外进入Bootloader。烧录口保留SWD四针SWDIO、SWCLK、GND、3V3注意HK32F030C8T6的SWDIO和SWCLK默认复用PA13、PA14和STM32一致。电源方案上我建议用一颗LDO把5V降到3.3V给MCU和逻辑电路LDO前面再加一个SS14二极管防反接。不要用DC-DC给MCU供电因为TB67H450的PWM斩波噪声会通过DC-DC反馈到模拟电源上影响MT6816的角度读取稳定性。下面是一段HK32F030C8T6的GPIO和定时器初始化代码可以看到和STM32标准库非常接近// 基于航顺HK32F030C8T6标准库PB0/PB1输出PWM给TB67H450 #include hk32f030m_pwm.h void MotorPwm_Init(void) { GPIO_InitTypeDef gpioInit; TIM_OCInitTypeDef ocInit; TIM_TimeBaseInitTypeDef tbInit; // 使能GPIOB和TIM3时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // 复用推挽输出 gpioInit.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1; gpioInit.GPIO_Mode GPIO_Mode_AF_PP; gpioInit.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, gpioInit); // 72MHz / (71 1) / (999 1) 720kHz PWM频率 tbInit.TIM_Period 999; tbInit.TIM_Prescaler 71; tbInit.TIM_CounterMode TIM_CounterMode_Up; tbInit.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIM3, tbInit); // PWM模式1输出比较1、2初始占空比0 ocInit.TIM_OCMode TIM_OCMode_PWM1; ocInit.TIM_OutputState TIM_OutputState_Enable; ocInit.TIM_Pulse 0; ocInit.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM3, ocInit); TIM_OC2Init(TIM3, ocInit); TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_Cmd(TIM3, ENABLE); } void Motor_SetDuty(uint8_t ch, uint16_t duty) { if (ch 1) TIM_SetCompare1(TIM3, duty); else if (ch 2) TIM_SetCompare2(TIM3, duty); }这段代码里RCC_APB2Periph_GPIOB和RCC_APB1Periph_TIM3的宏名与STM32F0标准库完全一致但需要注意HK32库的启动文件里默认开启的时钟源是HSI而不是HSE如果你在SystemInit里没配置PLLTIM3的时钟频率可能不是预期的72MHz。排查方法很简单用Keil调试窗口读SystemCoreClock变量如果显示8MHz说明时钟树没切到PLL。2.2 TB67H450的外围电路和电流斩波设置TB67H450是一款双全桥电机驱动器既能驱动一路有刷直流电机也能桥接成一路步进电机控制接口是独立的IN1/IN2和PWM1/PWM2或者用PWM/EN模式。在这个方案里我使用两路PWM直接控制一个绕组的正反向另一相类似。驱动器的逻辑供电是3.3V和HK32F030C8T6共用一个电源域省去电平转换电路这是选这颗芯片的一个重要原因。电流斩波阈值是步进电机控制板最需要调好的一项。TB67H450在外围只需要一个VREF引脚的分压电阻网络和一个VM引脚的大电容不需要外置采样电阻。斩波电流和VREF的关系在数据手册里给出固定公式但实际芯片批次差异会带来约±10%的偏差。我一般的做法是先按手册公式算出理论VREF再用示波器电流探头或采样电阻实测相电流最后微调VREF分压电阻。例如目标是1.2A峰值相电流手册公式算下来VREF是0.6V那我会在VREF引脚用10kΩ电位器分压3.3V得到0.6V并串联一个1kΩ电阻限制电流。同时VM引脚一定加一个100uF电解电容和0.1uF陶瓷电容组合电容靠近VM引脚放置否则高速PWM时电压尖峰容易触发VM欠压保护电机表现为“咯噔”抖动。TB67H450还有两个关键引脚TOFF和MODE。TOFF引脚决定固定关断时间常见做法是接一个47kΩ电阻到地对应约20us关断时间MODE引脚选择衰减模式走步进模式时建议选混合衰减可以通过串联一个电阻到3.3V来配置。下面是完整的引脚连接参考表功能HK32F030C8T6引脚TB67H450引脚参数说明PWM_APB0 (TIM3_CH1)IN1电机A相正向PWM20kHzPWM_A-PB1 (TIM3_CH2)IN2电机A相反向PWMPWM_BPA6 (TIM1_CH1)IN3电机B相正向PWM20kHzPWM_B-PA7 (TIM1_CH2)IN4电机B相反向PWMVREF3.3V经分压电阻VREF斩波电流阈值0.2~2.5VTOFF47kΩ到地TOFF固定关断时间约20usMODE10kΩ到3.3VMODE混合衰减模式故障输出PA4 (外部中断)MO低电平表示过流/过热这里要特别强调TIM1是高级定时器输出PWM前必须调用TIM_CtrlPWMOutputs(TIM1, ENABLE)这是和TIM3最大的区别。很多从STM32F0转过来的例程没有这一句结果PA6和PA7一直输出低电平电机毫无反应。2.3 功率电源和逻辑电源的处理顺序整套板子的电源树建议采用“单点接地”方式24V输入经过防反接和保险丝后直接给TB67H450的VM引脚同时经过一个DC-DC降压到5V再经LDO到3.3V。DC-DC的开关节点铜皮要远离HK32F030C8T6的晶振引脚避免耦合噪声导致系统随机重启。MDI引脚在原理图里容易被漏掉。TB67H450的MDI是空脚数据手册要求必须接地不能悬空。上板后如果电机电流明显小于设置值第一件应检查的事就是MDI是否接地。另一个高频错误是VM和VCC接反VM是电机电源通常24VVCC是逻辑电源3.3V两者接反会直接烧毁芯片内部稳压器。我习惯在原理图里把这两类网络用不同颜色标注并加文字说明“VM24V”“VCC3.3V”。原理图设计完成后建议把这几个参数输入到AD设计Altium Designer的编译检查规则里所有TB67H450电源引脚附近的去耦电容必须小于5mm走线VM网络的铜皮最小宽度为1.5mmMDI网络必须连接到GND网络。AD的“Net Class”功能可以快速检查这些约束我在实际项目里就把VM、GND、3V3三个网络设为不同的Net Class每个Class单独设置线宽规则这样画PCB时不容易出现细线过大电流的情况。3. MT6816磁性编码器接口与角度解算从SPI时序到软件例程源码3.1 MT6816的SPI输出格式和硬件连接MT6816是MagnTek推出的14位磁性角度传感器内部集成了霍尔阵列和CORDIC算法直接输出0°~360°的绝对角度分辨率约0.02°。它支持SPI、I2C和PWM输出在步进电机控制板的典型接法是SPI从机模式只需挂到HK32F030C8T6的SPI1上。MT6816放在电机轴的磁铁正下方气隙建议控制在0.5mm~1.5mm之间装配时要用非磁性螺钉固定PCB否则磁铁被螺钉分流后角度非线性会明显变大。SPI模式下MT6816的关键时序是命令帧和数据帧长度。常见的读取方式是主机发送0x0000命令从机返回两个字节数据其中高14位是角度值低2位是状态和校验位。下面是具体的SPI连接和角度读取代码// 基于HK32F030C8T6的SPI1读取MT6816角度 #include hk32f030m_spi.h #include hk32f030m_gpio.h #define MT6816_CS_GPIO GPIOA #define MT6816_CS_PIN GPIO_Pin_3 void MT6816_SPI_Init(void) { GPIO_InitTypeDef gpioInit; SPI_InitTypeDef spiInit; // PA1SCK, PA2MISO, PA3CS, PA4MOSI未用到 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI1, ENABLE); gpioInit.GPIO_Pin GPIO_Pin_1 | GPIO_Pin_2; gpioInit.GPIO_Mode GPIO_Mode_AF_PP; gpioInit.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpioInit); gpioInit.GPIO_Pin MT6816_CS_PIN; gpioInit.GPIO_Mode GPIO_Mode_Out_PP; gpioInit.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(MT6816_CS_GPIO, gpioInit); GPIO_SetBits(MT6816_CS_GPIO, MT6816_CS_PIN); // 1MHz SPI模式0MSB先行 spiInit.SPI_Direction SPI_Direction_2Lines_FullDuplex; spiInit.SPI_Mode SPI_Mode_Master; spiInit.SPI_DataSize SPI_DataSize_8b; spiInit.SPI_CPOL SPI_CPOL_Low; spiInit.SPI_CPHA SPI_CPHA_1Edge; spiInit.SPI_NSS SPI_NSS_Soft; spiInit.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_72; spiInit.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI1, spiInit); SPI_Cmd(SPI1, ENABLE); } uint16_t MT6816_ReadRawAngle(void) { uint16_t raw 0; uint8_t hi, lo; GPIO_ResetBits(MT6816_CS_GPIO, MT6816_CS_PIN); // 发送两个字节的0x00命令同时接收角度数据 SPI_SendData8(SPI1, 0x00); while (SPI_GetFlagStatus(SPI1, SPI_FLAG_TXE) RESET); while (SPI_GetFlagStatus(SPI1, SPI_FLAG_RXNE) RESET); hi SPI_ReceiveData8(SPI1); SPI_SendData8(SPI1, 0x00); while (SPI_GetFlagStatus(SPI1, SPI_FLAG_TXE) RESET); while (SPI_GetFlagStatus(SPI1, SPI_FLAG_RXNE) RESET); lo SPI_ReceiveData8(SPI1); GPIO_SetBits(MT6816_CS_GPIO, MT6816_CS_PIN); raw ((uint16_t)hi 8) | lo; // 高14位是角度数据低2位丢弃 return (raw 2) 0x3FFF; }这段代码有几个细节需要注意。MT6816的SPI速率不需要很高1MHz足够因为角度更新率只有几百Hz提高速率反而容易受电机PWM噪声干扰。注意SPI_SendData8和SPI_ReceiveData8之间的标志位等待顺序HK32的SPI底层库和STM32F0标准库行为一致都是先查TXE再查RXNE。如果读回来的数据总是在某几个固定值附近跳变多半是CS拉低后SCK相位不对可以把SPI_CPHA改为SPI_CPHA_2Edge再试。3.2 角度零位校准和方向统一绝对角度编码器的原始值不能直接用于电机控制因为安装磁铁时不可能保证0度点与电机A相零位完全对齐。上电后需要做一次零位校准常见做法是让电机跑一个完整的电气360度开环动作采集不同位置的角度值再计算偏移量。更简单的做法是手动把电机轴转到机械零位读取当前MT6816角度值保存为offset。当板卡没有外部Flash时可以把这个offset写到HK32F030C8T6的Flash末页上电启动时读取。角度方向也需要统一。步进电机正转时MT6816角度值可能递增也可能递减根据磁铁安装决定。统一方向的做法是读取后判断方向标志如果反向则用0x3FFF - raw得到修正值。完整的角度解算函数如下typedef struct { int32_t raw_offset; // 零位偏移量 uint8_t dir; // 0正向1反向 uint16_t last_angle; // 上次角度用于多圈计数 int32_t angle_deg; // 输出角度单位0.1度范围-1800~1800 int32_t turns; // 累计圈数 } MT6816_t; int32_t MT6816_GetAngleDeg(MT6816_t *enc) { uint16_t raw MT6816_ReadRawAngle(); uint16_t adjusted; if (enc-dir 0) { adjusted (raw enc-raw_offset) 0x3FFF; } else { adjusted (0x3FFF - raw enc-raw_offset) 0x3FFF; } // 多圈连续计数注意跨0度点的跳变 int16_t delta (int16_t)(adjusted - enc-last_angle); if (delta 8192) { enc-turns--; // 超过半圈认为是负向跨越 } else if (delta -8192) { enc-turns; // 低于负半圈认为是正向跨越 } enc-last_angle adjusted; int32_t total (int32_t)enc-turns * 16384 adjusted; return (total * 3600) / 16384; // 输出0.1度 }这里每次跨0度点都可能出现跳变因为角度值从16383变到0差值接近-16383int16_t能正确表示这个负数所以判断阈值选8192能有效区分正常运动和大跳变。如果不用多圈计数位置环在连续旋转超过一圈后会出现角度跳变失步检测也没法做。在实际项目中我会在每次读取后顺便检查返回的状态位如果传感器自检出错直接置一个encoder_fault标志让控制逻辑立即停止PWM输出。3.3 软件例程源码里的控制循环结构完整软件例程源码通常包含三个层次底层硬件抽象、电机驱动中间层、应用控制层。底层就是上面这些GPIO、SPI、TIM初始化函数中间层把“角度值”转换成“位置偏差”应用层再根据偏差计算PWM占空比。一个典型的闭环控制循环如下// 位置环控制循环1kHz周期调用 void Motor_ControlLoop(void) { int32_t current_angle MT6816_GetAngleDeg(encoder); int32_t error target_position - current_angle; // 简单的P控制器输出限幅为±500 int32_t duty (error * kp) / 100; if (duty 500) duty 500; if (duty -500) duty -500; if (duty 0) { Motor_SetDuty(1, duty); Motor_SetDuty(2, 0); } else { Motor_SetDuty(1, 0); Motor_SetDuty(2, -duty); } }控制循环的周期用SysTick实现1kHz中断里刷新占空比。对于步进电机PWM频率我习惯设在20kHz左右超出人耳听觉范围。这个循环里的kp是角度差乘以比例系数比如目标角度和当前角度相差100度1000个0.1度kp取2那么输出占空比是20%对应电流约0.24A具体值需要根据电机扭矩系数标定。注意步进电机的矩频特性PWM占空比和电机扭矩不完全线性所以位置环后面必须加电流限制也就是TB67H450的VREF阈值否则堵转时占空比直接被拉满芯片温度快速上升。4. AD设计里的PCB布局与DRC步进电机控制板的主要矛盾4.1 大电流和敏感信号的物理隔离PCB设计上这套控制板的核心矛盾是电机大电流噪声和MT6816微小信号之间的隔离。AD设计Altium Designer里我习惯用禁止布线层画一条物理隔离带把板子分成功率区和逻辑区。功率区包含TB67H450、VM大电容、电机接线端子逻辑区包含HK32F030C8T6、晶振、MT6816、SWD座。隔离带下方不能有任何走线如果单面板空间紧张至少保证MT6816和其他敏感走线在顶层时底层整块铺地平面电机走线在顶层短而宽地绕过。TB67H450的散热焊盘必须打矩阵过孔到底层铺铜过孔规格0.4mm间距0.8mm数量不少于12个。这个散热焊盘同时作为散热和电流回路的一部分如果过孔不足满电流运行时芯片温度能到90°C以上过温保护频繁触发。在AD的Thermal Relief设置里给焊盘连接铺铜方式选择Direct Connect而不是菊花瓣连接否则大电流过孔会被热焊盘缩小电流承载能力。地平面分割是另一个关键决策。我的做法是不分割地平面所有GND统一为一个完整铺铜功率地和逻辑地通过在地平面上的单点过孔相连。因为TB67H450的电流是PWM斩波分割地平面会造成回流电流绕路形成更大的电流环路产生差模辐射。MT6816的GND引脚直接就近打过孔落到底层地平面不需要单独隔离地。这样在AD的DRC检查里只需增加一条规则禁止在隔离带区域放置任何铜皮。4.2 线宽、过孔和滤波参数的量化建议下表是我在AD设计里实际使用的线宽和过孔规则可以写到板厂的制板说明里信号/电源网络最小线宽推荐线宽过孔/电流能力VM (24V电机电源)1.5mm2.0mm4个0.6mm过孔/2AGND (功率回路)1.5mm2.0mm铺铜连续回流3V3 (逻辑电源)0.3mm0.5mm1个0.4mm过孔PWM信号0.2mm0.3mm并联33Ω电阻MT6816 SPI0.2mm0.3mm串联22Ω电阻PWM信号线上串联33Ω电阻可以抑制振铃减少对MT6816的数字噪声耦合。MT6816的SCK和MISO线尽量等长走线长度控制在10mm以内并且远离VM和电机A/B两相输出线至少在平面距离上保持5mm以上。如果PCB尺寸允许在MT6816的电源脚再加一个10uF钽电容和一个0.1uF陶瓷电容形成π型滤波能有效降低电机大电流时电源上的毛刺。4.3 与TB67H450相关的DRC规则和布局检查清单在AD的Design Rules里我一般配置三类规则来兜底。第一类线宽规则Net Class对应表第二类间距规则设置VM网络与其他网络间距不小于1.2mm因为VM上最高电压是30V虽然不算强电但电机堵转时反电动势叠加会把VM电压拉到接近35V间距太小容易击穿第三类是过孔规则禁止在HK32F030C8T6晶振下方和MT6816下方打地过孔避免引入噪声。布局顺序上先放TB67H450然后以它为中心展开。TB67H450的VM引脚旁放100uF电解电容和0.1uF陶瓷电容电解电容负责低频储能陶瓷电容负责高频噪声。然后放HK32F030C8T6晶振尽量靠近MCU晶振引脚。最后放MT6816时要注意MT6816是贴片封装底部中心有焊盘需要把传感器小板和主控板用排针连接或者作为独立模块放置时MT6816下方不要走电机A/B相线。在AD里可以给MT6816的Room Definition区域设置禁止布线保证这块区域只有信号线。5. 联调技巧用示波器和串口验证角度环的边沿参数5.1 先看PWM和相电流波形板卡焊接完成后先用示波器量HK32F030C8T6的PB0/PB1引脚确认PWM波形存在频率是设定值。此时TB67H450的VM先不接24V只接3.3V逻辑电源观察驱动器输出引脚是否有电压正常应为0V。接上24V后再量TB67H450的OUT1和OUT2对GND波形。空载下两个输出应互为反相占空比等于HK32输出的占空比。如果输出端始终为高而不翻转检查MODE引脚的配置电阻如果输出有缺失窄脉冲检查TOFF电阻值是否过大。相电流波形需要串入一个小阻值采样电阻或者用电流探头。我一般是把示波器探头套在电机的某根引线上观察电流波形是否符合“梯形”或“正弦”形状。如果电流波形出现严重的毛刺说明PCB上的VM去耦电容不够如果电流上升沿很缓说明VREF设置过低或电机电感太大。这里要记下一个关键边沿参数闭环位置环的PWM周期一般是50us20kHz超过这个时间后若电流还未上升到目标值说明VREF不够或CS电流检测异常。5.2 用串口打印角度值验证MT6816先跑一个最简单的例程只开MT6816 SPI读取电机关闭。用串口工具打印原始角度值和换算后的0.1度值手动转电机轴观察数据是否连续变化。如果数据从16383跳到0这是正常的配合多圈处理函数就没问题如果数据卡在某个范围或跳变无规律先用示波器看SPI时序重点检查CS低电平时SCK是否有漏脉冲以及MISO上是否有反射振铃。确认角度数据稳定后可以切换到闭环模式。把目标角度设为当前角度然后人为拉住电机轴观察串口打印的角度误差。再把目标角度设为当前加360度即0.1度单位下加3600电机应自动走到目标位置停止停止后角度误差在±3个LSB以内。如果电机振荡减小位置环kp如果电机缓慢爬行且最终停不准增大kp并检查是不是MT6816的角度读取速率太低导致控制周期内误差变化过快。5.3 失步检测的最终验证方法整套控制板的终极验证是做堵转失步测试。用上位机发送一个较大的位置阶跃在电机运动途中用手堵住电机轴串口监控当前角度和目标角度的误差。如果失步发生后误差超过设定阈值例如20度程序应触发错误状态立即禁用TIM1的PWM输出并点亮故障LED。这一步能证明MT6816作为绝对角度编码器的价值它不仅能做闭环还能在开环模式下起到“状态观测”的作用。联调时最值得调的一组参数是VREF和TOFF。VREF决定最大电流TOFF决定斩波频率两者联合影响电机运行时的发热和噪声。我的最终验证标准是位置命令为连续正弦运动频率2Hz幅值180度电机连续运行30分钟TB67H450表面温度不超过70°CMT6816角度数据无丢失串口打印的误差均方根小于0.5度。满足这个标准后这套硬件和软件例程源码就算达到可交付状态了。本文还有配套的精品资源点击获取