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

资讯详情

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

STM32 FOC六路互补PWM与死区配置实战:从CubeMX到示波器验证

STM32 FOC六路互补PWM与死区配置实战:从CubeMX到示波器验证 我正在调试一个无刷电机的FOC项目第一次上电就烧了一路MOS管。后来反复复盘问题就出在六路PWM的互补时序没处理干净、死区设置又太小上下管在切换瞬间直接短路。很多人找STM32无刷电机FOC驱动代码代码本身并不难找难的是底层PWM和死区机制没吃透跑起来照样炸。这篇文章我就把STM32 CubeMX配置六路互补PWM的完整过程捋一遍重点讲清楚死区时间怎么算、怎么配、怎么用示波器验证顺便把引脚冲突、HAL库启动顺序这些坑也填上给准备上手FOC的朋友一份可以直接照做的参考。1. FOC控制为什么绕不开六路互补PWM从桥臂结构说起1.1 三相全桥的物理结构决定了必须有六路驱动信号FOC最终是通过三相全桥逆变器来驱动电机绕组的。所谓三相全桥就是三对半桥、六个功率开关管一般是MOSFET或IGBT。每一对半桥对应电机的一相绕组上管接直流母线正极下管接直流母线负极两个管子中间的连接点就是电机的相线输出。这样一看就明白了STM32要控制这六个功率管至少要输出六路PWM信号。其中每一对上下管在逻辑上必须“互补”——上管导通时下管必须关断下管导通时上管必须关断。绝不能出现同一个桥臂上下管同时导通的情况否则直流母线正负极直接短路这就是所谓的“直通”或者“桥臂穿通”瞬间大电流足以把MOSFET的管芯击穿。FOC算法里Clarke变换和Park变换算出的是电压矢量最终要转换成三个桥臂上每个功率管的开关时间也就是六路PWM各自的占空比。这一整条链路里PWM配置是物理层的地基地基没打稳后面电机跑得再顺也是假象。1.2 死区时间不是可有可无它是功率级的保命设置现实中的功率管不是理想开关。MOSFET有开通延迟、上升时间、关断延迟、下降时间栅极驱动芯片也有传播延迟。如果上管从导通变成关断的同时下管立刻从关断变成导通而上管此时还没完全关断、其漏源电压还没建立起来下管就已经开始导通两个管子之间就会形成短暂的低阻通路也就是直通。直通哪怕只持续几百纳秒在母线电压几十伏甚至上百伏时都会产生很尖锐的电流浪涌烧管子往往就是这一下的事。死区就是在这个上下管切换的瞬间故意插入一段“两个管都不导通”的空白时间。典型做法是先让上管关断等一小段时间再允许下管开通反向切换时同样处理。STM32的高级定时器内部带有死区插入器可以在互补PWM切换边沿自动插入这个延迟不需要外置逻辑电路也不需要软件去精细控制关断时序。但死区不能无限大。死区期间电机相电流没有正常通路只能靠MOSFET体二极管或外部续流二极管续流这会造成电压利用率下降、电流波形畸变、电机噪声变大。所以死区是可靠性不炸管和波形质量控制精度之间的平衡点这正是“死区优化”这句话的本质。1.3 为什么非要用TIM1和TIM8这类高级定时器STM32家族里的定时器分三类基本定时器、通用定时器、高级控制定时器。TIM1和TIM8属于高级控制定时器和通用定时器相比最大的区别就是多了几样电机控制专用的功能互补PWM输出CHx和CHxN成对输出硬件上天然适合半桥驱动。可编程死区插入器通过DTG寄存器直接配置死区时间。刹车输入Break Input外部故障信号可以硬性关断所有PWM输出。重复计数器可以让更新事件周期性地延迟发生配合PWM控制更方便。与ADC的同步触发机制用定时器事件直接触发电流采样减少软件延迟。假如用通用定时器我只能输出六路独立的PWM无法从硬件上保证上下管的互补关系。要么在软件里做关断时序但中断延迟不可控电机运行中分分钟出危险要么在外面加一堆逻辑电路成本和复杂度全上去了。所以做FOC首选就是TIM1或TIM8。2. CubeMX配置前必算时钟、PWM频率、死区时间的选择逻辑2.1 定时器时钟到底是多少别再被APB2绕晕很多人把CubeMX时钟树配完了却不清楚定时器实际跑在多少频率。STM32F103的TIM1挂在APB2总线上TIM2到TIM4挂在APB1总线上。这里有一个非常容易踩的规律如果APBx预分频器系数为1定时器时钟就等于PCLKx如果APBx预分频器系数大于1定时器时钟等于PCLKx的两倍。以STM32F103外部8MHz晶振、系统主频72MHz为例。标准配置里APB1预分频一般为2所以PCLK1是36MHzTIM2到TIM4的时钟是36MHz乘以2也就是72MHzAPB2预分频为1所以PCLK2就是72MHzTIM1的时钟同样是72MHz。我的建议是在CubeMX时钟树页面把APB2这条支路看清楚确认定时器时钟没有被压到很低的数值。只要定时器时钟是72MHz后面所有死区计算都按72MHz来算就基本不会出错。2.2 PWM频率、ARR和PSC怎么搭配更合理FOC常用的PWM频率在10kHz到20kHz之间再高能进一步降低电机噪音但开关损耗也会相应增加。我以20kHz为例定时器时钟72MHz向上计数模式公式是这样的fPWM TIM_CLK / ((PSC1) × (ARR1))代入20kHz20kHz 72MHz / ((PSC1) × (ARR1))为了得到最大的占空比分辨率让PSC保持为0于是ARR等于3599也就是Counter Period写3599。为什么优先让PSC保持0因为PSC越大ARR越小同一个PWM周期内可以细分的占空比级数就越少。比如PSC设71ARR只有50那么占空比最小步进是1/50也就是2%对FOC来说这个分辨率完全不够看。FOC对占空比的精细度要求很高所以一开始就压小PSC给后面的调节留足余量。2.3 死区时间到底选多大从器件手册反推死区时间不是一个拍脑袋就能定的数主要看这几个因素功率MOSFET的开通延迟、关断延迟、上升时间、下降时间。栅极驱动芯片的传播延迟。母线电压和电流等级。驱动电阻大小栅极电阻越大开关过程越慢死区需求越大。工程上常见的经验范围我列一个参考表实际数值一定要以你的器件手册为准应用场景典型死区时间小型无人机电机几瓦到几十瓦300ns ~ 800ns中小功率伺服/机器人关节几十瓦到几百瓦500ns ~ 1.5μs电动车/工业驱动器几百瓦到数十千瓦1μs ~ 3μs提示写死区参数之前先查MOSFET数据手册里的td(on)、tr、td(off)、tf这些开关时间参数再叠加驱动芯片延迟并留出至少20%到50%的安全裕量。网上很多人说“用1μs就行”千万别照抄。不同MOSFET开关速度差很多同一个值在这块板子上没问题是运气在另一块板子上炸管是常态。2.4 死区分辨率与CKD时钟分割的隐藏关系CubeMX的TIM1参数里有一项“Internal Clock Division”也就是CKD它直接影响死区时钟步进。很多人忽略这个选项导致死区配置出来后实际值和预期对不上。当CKD设为No Division时死区时钟步进Tdtg等于定时器时钟周期tCK_INT。在72MHz定时器时钟下Tdtg大约是13.89ns。如果CKD设为Division 2Tdtg就是27.78ns如果设为Division 4Tdtg是55.56ns。也就是说CKD选得越大死区调节粒度越粗。F103的TIMx_BDTR寄存器中死区时间可以由DTG[7:0]位配置具体分四档计算DTG[7:5] 0xxDT DTG[7:0] × TdtgDTG[7:5] 10xDT (64 DTG[4:0]) × 2 × TdtgDTG[7:5] 110DT (32 DTG[4:0]) × 8 × TdtgDTG[7:5] 111DT (32 DTG[4:0]) × 16 × Tdtg举个实际例子我想在72MHz下配置约1μs死区。Tdtg为13.89ns用第一档计算DTG约等于72写成十六进制就是0x48。CubeMX图形界面里其实可以之间填时间值不需要手算DTG但你自己懂了这套映射关系用示波器验证时心里才有底。3. CubeMX六路互补PWM实操一步步配好TIM1并避免引脚冲突3.1 新建工程时的基础设置打开CubeMX新建工程芯片选择STM32F103C8T6或者你实际用的型号。先到System Core里把RCC的HSE设为Crystal/Ceramic Resonator也就是外部高速晶振模式。再进SYS把Debug设为Serial Wire否则后面下载调试时SWD引脚可能会被重新映射导致连不上调试器。时钟树页面里把HCLK直接拉到72MHz。CubeMX会自动分配APB1和APB2的预分频值。这时候重点看一眼APB1外设时钟是36MHz但定时器时钟是72MHzAPB2外设时钟和定时器时钟都是72MHz。确认无误后进入TIM1配置。这些基础步骤决定了整个工程的根基虽然不起眼但很多人最后发现PWM频率不对、死区时间不对回头排查才发现时钟树里某个分频系数被调过。3.2 启用TIM1的PWM生成通道与互补通道在Pinout Configuration里选中TIM1激活内部时钟然后在“PWM Generation”这一栏里把六个输出全部勾上PWM Generation CH1PWM Generation CH1NPWM Generation CH2PWM Generation CH2NPWM Generation CH3PWM Generation CH3N勾选之后CubeMX会自动分配引脚。默认情况下PA8对应TIM1_CH1PB13对应TIM1_CH1NPA9对应TIM1_CH2PB14对应TIM1_CH2NPA10对应TIM1_CH3PB15对应TIM1_CH3N。这个映射关系在后续焊接驱动板、设计PCB时一定要提前确认避免信号交叉。参数面板里我常用的典型配置是Prescaler 0Counter Mode UpCounter Period 3599Auto-reload preload EnableCH1、CH2、CH3的Pulse都先设为1800也就是50%占空比的初始值Break and Dead Time settings里面Dead Time填写1000ns这样六路互补PWM的硬件底层就算配好了后续生成代码直接就能跑出波形。3.3 引脚分配与重映射的坑默认映射看起来挺方便但PA9和PA10同时是USART1的TX和RX。如果开发板板载串口占了这两个引脚或者你的项目中要用串口打印调试信息就要提前规划好。CubeMX如果在Pinout页面检测到冲突会在工程里报错别硬生成。F103的TIM1支持部分重映射和完全重映射。部分重映射可以把互补输出通道换到PA7、PB0、PB1等引脚完全重映射则搬到PE8到PE15。小封装如C8T6没有那么多PE引脚基本只能走默认映射或部分重映射。如果六个引脚都挤在一起和你的驱动板布局冲突要么换大封装芯片要么把驱动板设计提前和引脚分配一起做别等画完板子再回来改。还有一个细节配置TIM1的PWM时如果同时启用了TIM1的刹车功能BKIN引脚会被占用。BKIN默认在PB12也可能重映射到PA6。没用到外部刹车输入时我建议在CubeMX里不要随便开启Break功能不然初始化后MOE位被刹车信号锁死六路PWM根本没输出排查起来很耽误时间。3.4 输出空闲状态与BKIN刹车输入别乱配在TIM1的Break and Dead Time settings里有两项叫做“Output Idle State”和“Output N Idle State”。它们的作用是当MOE主输出使能被关闭时比如刹车触发或初始化阶段CHx和CHxN引脚输出什么电平。默认值一般是Reset也就是低电平。但如果有人把它配置成了高电平在PWM还没启动的时候引脚就已经输出高接到驱动电路上就可能导致功率管误导通。这个问题非常隐蔽很多人在上电瞬间炸管查来查去最后发现就是空闲状态配置错了。我建议保持低电平并且在驱动板硬件设计上给每个功率管栅极加一个10k到100k的下拉电阻保证MCU初始化完成之前栅极被可靠拉低。软件电平状态和硬件上电默认状态一致才能把误触发风险降到最低。4. 工程生成后的代码级细节启动顺序、占空比更新与死区动态调整4.1 HAL库启动顺序不对就会出现“有主输出没互补输出”CubeMX生成工程后最常规的启动代码是这样HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_1); HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_2); HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_3); HAL_TIMEx_PWMN_Start(htim1, TIM_CHANNEL_1); HAL_TIMEx_PWMN_Start(htim1, TIM_CHANNEL_2); HAL_TIMEx_PWMN_Start(htim1, TIM_CHANNEL_3);一定要把主输出和互补输出分别启动。HAL_TIMEx_PWMN_Start内部会打开对应通道的互补输出使能位同时确保MOE主输出使能。如果只启动了主输出互补输出引脚会一直没波形如果只启动了互补输出主输出同样没有波形。两套都要调。启动时还有一种常见现象引脚输出恒高或者恒低。这时候先检查MOE位是否被关闭直接读一下TIM1的BDTR寄存器看MOE位就行。同时要检查有没有发生刹车事件也就是BIF标志。很多驱动板上接了故障反馈到BKIN脚如果反馈信号一直处于触发状态MOE会被强制关闭PWM自然全无输出。4.2 运行中如何安全地更新占空比FOC算法每个PWM周期都要更新三相占空比最常用的宏是__HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, value); __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_2, value); __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_3, value);value的取值范围是0到ARR。如果你用的是SVPWM最终给到每个通道的比较值是SVPWM算法算出来的三相占空比映射结果。PWM外设本身不关心这个值是怎么算出来的它只负责每一个计数周期把比较器值和计数器当前值做比较从而翻转输出电平。占空比的更新最好放在PWM周期中断里做避免在任意时刻写寄存器导致输出波形出现毛刺void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM1) { // 在这里计算下一周期的三相占空比并更新 } }还需要用__HAL_TIM_ENABLE_IT(htim1, TIM_IT_UPDATE)使能更新中断并且调用HAL_TIM_IRQHandler来驱动HAL库的回调机制。这套链路不复杂但初始化遗漏一个环节中断就永远进不去。4.3 运行时调整死区可行吗什么时候不可行理论上可以在运行中直接修改BDTR寄存器的DTG位来改变死区时间TIM1-BDTR (TIM1-BDTR ~TIM_BDTR_DTG_Msk) | (new_dtg TIM_BDTR_DTG_Pos);但我不建议在PWM正常输出时频繁修改死区。原因是对DTG的改动会立刻作用于后续边沿如果写寄存器的时机恰好落在切换边沿附近可能产生一个不完整的异常脉冲。这个脉冲出现在功率级上就是不可控的开关动作风险很大。更稳妥的做法是先停机在PWM停止输出的状态下修改死区再重新启动。死区本身是设计阶段就该定好的参数不是FOC运行中需要实时调整的量。硬件上如果实在想灵活调可以选带可编程死区时间的外部栅极驱动芯片通过SPI或引脚去设置但这样又增加了系统的复杂度和成本。5. 死区优化实测示波器测量方法与常见问题速查5.1 示波器上如何准确测量死区死区优化必须有依据不能靠猜。最直接的方法是拿双通道示波器一路接CHx一路接CHxN同时看两路波形之间的间隔。测量时有一个非常重要的细节探头的地线夹要用短地线不要用那根又细又长的地线夹否则测量噪声和振铃会大得离谱甚至测出错误的死区宽度。把时间轴缩放观察两路波形从一路变低到另一路变高的间隔这段两个输出都为低的时间就是死区。实际死区时间和配置值之间如果有偏差优先检查CKD设置因为死区步进取决于它。如果没有双通道示波器只有单通道也可以通过半桥输出点波形做一个粗测但信息量少很多很难判断死区到底是偏大还是偏小。预算允许的话300MHz以上带宽的双通道示波器是做电机驱动调试的底线配置。有一个实测技巧把PWM频率临时调低到1kHz占空比调到50%死区在示波器上会显示得更宽、更容易测量。测完再恢复实际运行频率这样测量误差更小。5.2 死区过大和过小分别是什么表现死区参数设置不合适故障现象往往很典型症状大概率原因验证手段上电瞬间或切换瞬间MOSFET发烫、冒烟死区太小存在直通示波器看互补输出交叉点调大死区相电流波形畸变、过零平台明显死区太大续流二极管长时间导通调小死区对比相电流波形电机低速时噪声大、振动明显死区太大导致电压误差被放大结合相电流波形和FFT分析逐步减小死区空载正常、带载一高就炸管死区裕量不足负载切换瞬间出现直通提高死区时间检查母线电压尖峰死区优化不是让死区越小越好。减小死区可以改善电流波形质量但如果功率管本身的开关参数不够好、驱动电路布局又比较差那一丁点时间裕量根本不够用。我的习惯是先按器件手册算出一个偏保守的值再用示波器逐步把死区往小调直到观察到相电流波形开始出现明显畸变再回调一点留出余量。5.3 常见问题排查速查表与踩坑实录我整理了一张在实际调试中反复用到的排查表现象可能原因排查方法六路引脚全部无输出MOE未使能 / 发生刹车事件读BDTR的MOE、BIF检查BKIN引脚电平只有CHx输出CHxN没有没调用HAL_TIMEx_PWMN_Start补上互补输出启动CHx和CHxN同时为高死区未生效或配置错误检查DTG寄存器值和死区时间引脚输出恒高输出空闲状态被设为高在CubeMX中把Idle State改为低电平上电瞬间误触发引脚悬空期间电平不定驱动板栅极加下拉电阻烧MOSFET但波形看起来正常死区不够或栅极尖峰过大用短地线探头看开关瞬态检查死区这些坑我基本都踩过。最惨的一次是驱动一个500W电机自认为配置完全没问题上电后一启动就炸了两路MOS管。后来拆下板子仔细测发现死区只设了300ns而那只MOSFET的关断时间在1μs以上直通电流瞬间把管子击穿。所以无论算法多漂亮第一步永远是验证功率级。还有一个在样机阶段特别实用的经验在直流母线回路串一个限流电阻或者白炽灯能极大保护功率级。如果死区配置有问题现象就是灯发亮、电流偏大逐步排查排除问题后再把限流电阻调到正常水平。这个方法看起来土但在FOC联调初期能帮你省下一堆器件成本。6. 从六路PWM到完整FOC系统的衔接要点把六路互补PWM和死区配置讲透之后很多朋友下一步就是在线搜“FOC代码”或者说想立刻把算法跑起来。别急这里有三个衔接点我建议一次过完否则下一个环节照样会卡壳。6.1 PWM只是执行层电流采样才是FOC的下一个门槛FOC闭环控制必须要知道电机相电流。电流采样一般用采样电阻或者霍尔电流传感器读ADC的时机非常关键。简单可靠的方案是让ADC由TIM1的TRGO触发在PWM周期的固定时刻自动采样这样不用在软件里纠结到底哪一行读取ADC最合适。在CubeMX中把TIM1的Trigger Output设置为Update Event再把ADC触发源选成TIM1即可。采样点要尽量避开关管的开关时刻否则尖峰干扰会被采进去电流环反馈乱跳。6.2 开环跑起来再进闭环转子初始定位别跳过六路PWM配好之后先写一个最简单的程序让三相依次通电验证电机能不能步进转动、方向对不对。接着做转子初始位置检测这是无感FOC启动前必须做的一步。很多无感方案用高频注入或者矢量脉冲法来判定转子初始角度而不是盲目给定一个角度直接启动。先把PWM和电流采样这两个硬件基础验证好再去跑复杂算法效率反而更高。6.3 从“能转”到“控制好”的调试路径我的建议是这样先用开环VF或者六步换向让电机转起来确认驱动方向、相序都没问题再上FOC开环给定Iq、Id目标观察电流是否跟随最后才能切入闭环速度环和位置环。每一步都是一个独立的小验证节点这样即使出问题也不会把故障点扩散到整个系统里。调试工具方面CubeMX只负责生成初始化代码真正调FOC算法还需要调试器实时看变量J-Link或ST-Link都行。我习惯在串口上打印Iq、Id反馈这些关键变量实时性要求高的时候就上DAC或者虚拟示波器。这些属于后话但提前把调试链路搭好后面会顺手很多。最后再分享一个我的习惯每次改完高级定时器的配置都先不要急着接电机。用示波器记录一段CH1和CH1N的波形确认实际死区时间和配置值之间的误差在可接受范围内再接电机通电。项目稳定之后把死区参数、PWM参数单独抽到一个配置头文件里方便以后换器件时快速调整。死区优化本质上是和功率管的开关速度、驱动电路、母线电压做匹配这个参数一旦调好后续FOC控制算法才能在一个可靠的功率地基上跑起来。别嫌这一步麻烦它值得被认真对待。
返回列表