STM32F103C8T6三相SPWM电机驱动设计:死区配置与调频调压实战 📅 发布时间:2026/9/10 1:31:44 👁 浏览次数: 简介面向电机控制或嵌入式电力电子学习者的STM32 SPWM实现方案基于STM32F103C8T6高级定时器TIM1采用查表法生成三相六路带死区的互补SPWM波形相位互差120度支持恒压频比调频调压适合驱动三相异步电机或永磁同步电机也可用于逆变器与变频调速实验。资源包内含完整KEIL5工程代码注释详细可在线仿真直接观察输出波形另有SPWM原理讲解文档、正弦表生成工具与死区计算器方便理解参数计算与二次开发。压缩包共149个文件约46.67MB主要包含C源码与头文件、KEIL工程配置文件、hex/axf/map等编译产物、PDF学习文档以及独立的Excel正弦表工具与可执行计算器。已有8053人学习下载适合正在学习STM32高级定时器、SPWM调制或电机调速的开发者参考。 玩电机驱动的兄弟应该都有这种体会SPWM三相逆变的资料网上铺天盖地原理图一抓一大把但真正到了自己调板子的阶段死区设多少、互补输出怎么配、调频调压怎么联动每一步都是坑。最近我把STM32F103C8T6这颗经典芯片拉出来做了一版三相SPWM驱动方案六路互补PWM输出死区可调频率和电压都能在线调实测驱动小型三相异步电机和BLDC运行都很稳。这篇文章就完整复盘一下整个项目的设计和调试过程尤其是死区配置和调频调压这两个容易翻车的点给打算入坑或者正在调板子的朋友做个参考。先说下这个项目能做什么用一颗F103C8T6实现三相互补SPWM输出直接驱动MOSFET或IGBT组成的逆变桥配合简单的隔离驱动电路就能带电机。说白了就是单片机生成三相正弦波调制信号 硬件死区保护 软件调频调压这套东西做出来后稍微改改参数就能用在变频器、伺服驱动器前级、UPS、逆变电源这些场景里。适合正在学电机控制、搞电源设计、或者想用STM32做变频实验的工程师和学生。1. 项目整体设计思路为什么选F103C8T6做三相SPWM1.1 三个核心需求拆解标题里写了三个关键词三相六路互补输出、死区、调频调压。这三件事其实分别对应了硬件资源、安全保护和软件算法三个维度拆分来看思路就清晰了。先说三相六路互补输出。一个三相逆变桥需要6个功率管上桥臂和下桥臂分别由一对互补的PWM信号控制。STM32的高级定时器TIM1和TIM8天生就是干这个的它们支持三对互补PWM输出CH1/CH1N、CH2/CH2N、CH3/CH3N正好对应U、V、W三相。F103C8T6虽然是小容量型号但TIM1这个高级定时器一点没缩水该有的功能全都在这就是选它的底气。再说死区。上下桥臂如果同时导通电源直接短路轻则炸管子重则烧板子。死区就是让上下桥臂在切换时留出一段同时关断的时间这段空白期由硬件定时器自动插入不需要CPU干预。F103的TIM1有专门的死区寄存器可以精确到纳秒级这对电机驱动来说至关重要。最后是调频调压。电机调速本质上就是改变定子供电频率但要保持磁通恒定电压也得跟着频率走这就是经典的V/F曲线。软件层面需要让SPWM的调制比随频率联动变化载波频率和输出频率之间的约束关系也要处理好。1.2 定时器资源选型的取舍F103C8T6有TIM1、TIM2、TIM3、TIM4四个定时器其中只有TIM1是高级定时器支持互补输出和死区。所以方案基本没有悬念TIM1做三相SPWM主输出TIM2做调频调压的时基或者编码器计数剩下TIM3、TIM4留作他用或者干脆不用。有朋友可能会问为啥不选TMS320F28335那种DSP确实28335做电机控制是行业标准但F103C8T6的优势在于成本极低、开发环境成熟、资料海量用来做前期算法验证和教学演示完全够用。等算法跑通了再移植到DSP或者更高端的MCU上逻辑代码基本可以平搬只是寄存器配置要重写。1.3 硬件架构概览整机硬件链路是F103C8T6最小系统板 - TIM1的六路PWM输出 - 光耦隔离 - IR2103半桥驱动 - 三相全桥MOSFET - 电机。PWM信号经过光耦后给到IR2103的自举电路驱动上桥臂下桥臂直接驱动。这样既做了强弱电隔离又解决了上桥臂驱动电压不够的问题。电源部分我用了DC 24V供电给电机主回路控制板单独5V供电两路地在光耦处汇合。这里有个经验控制板和功率板的共地处理一定要干净地线走线粗一点不然后面调试示波器夹上去会看到一堆毛刺。2. SPWM波形生成从数学原理到代码落地2.1 波形生成方式的选型SPWM核心思路是面积等效用一系列脉宽按正弦规律变化的方波去等效一个正弦波。具体实现有硬件比较器方案用STM32的DAC输出正弦波作为比较基准和软件查表方案两种。查表法是绝对的主流做法原因很简单不用额外硬件精度高改起来方便。实现方式也直观预先算好一张正弦表或者在线计算正弦值然后映射到定时器的比较寄存器CCR上。STM32的定时器工作在PWM模式1或者PWM模式2当计数器值小于CCR时输出高电平大于CCR时输出低电平。这样就得到了脉宽按正弦变化的PWM波。2.2 正弦表计算与调制比控制正弦表的粒度决定波形质量。我用了512点的正弦表这意味着一个完整工频周期分成512步来逼近。每一步的脉宽计算公式是Duty (0.5 0.5 × M × sin(θ)) × ARR其中M是调制比0到1之间θ是当前角度ARR是自动重装载值。注意这里加了0.5的直流偏置因为SPWM需要有正有负的半周期但功率管的开关信号只有高和低两个状态所以要把正弦波抬升到正半轴最后由逆变桥的差分输出还原出正负交替的交流波形。调制比M直接控制输出电压幅值。M越大输出电压越高M越小输出电压越低。这就是调压的基础。我在代码里把M定义为一个全局变量和频率联动更新后面调压部分会详细讲。2.3 载波频率与分辨率权衡载波频率就是定时器的PWM频率我设置了20kHz。这个值是怎么定出来的呢高于人耳听觉上限20kHz电机运行安静同时又不会太高导致MOSFET开关损耗过大。20kHz载波下定时器时钟72MHzARR 72MHz / 20kHz 3600。这意味着CCR寄存器从0到3600之间变动按512点正弦表来算每个载波周期都有一次占空比更新。这个分辨率足够细腻输出波形不会出现可见台阶。这里有个精度问题需要注意如果载波频率再提高比如到50kHzARR就变成1440正弦幅值的分辨率下降到大约1/1440波形会粗糙一些同时开关损耗翻倍还多。所以20kHz是实用性和波形质量的黄金平衡点。// SPWM参数定义 #define SPWM_FREQ 20000 // 载波频率20kHz #define TIM1_PRESCALER 0 // 预分频1分频 #define TIM1_PERIOD 3600 // 72MHz / 20kHz #define SINE_TABLE_SIZE 512 // 正弦点数 #define MODULATION_INDEX 0.85f // 默认调制比3. 三相六路互补输出的核心死区配置3.1 为什么必须加死区先看一个危险场景上桥臂正在导通下桥臂关断。现在要切换成下桥臂导通、上桥臂关断。如果上桥臂立刻关断下桥臂立刻导通由于MOSFET不是理想开关关断需要时间导通也需要时间就存在一个短暂的时间窗口两个管子同时处于导通状态母线直接短路。这个短路电流是毁灭性的。所以必须要在这段切换窗口内让两个管子都处于关断状态这就是死区时间。F103C8T6的高级定时器内置死区功能只需要设置一个寄存器硬件就会自动在互补PWM的边沿插入死区不需要软件干预。3.2 死区寄存器DTR的详细计算TIM1的死区配置是很多人第一次接触时犯迷糊的地方。死区时间值写在BDTR寄存器的DTG[7:0]位段但这个位段不是简单的线性关系而是分段编码的。根据参考手册死区时间标准如下DTG[7:5] 0xx死区时间 DTG[7:0] × Tdtg其中Tdtg 1 / 72MHz ≈ 13.89nsDTG[7:5] 10x死区时间 (64 DTG[5:0]) × Tdtg × 2DTG[7:5] 110死区时间 (32 DTG[4:0]) × Tdtg × 8DTG[7:5] 111死区时间 (32 DTG[4:0]) × Tdtg × 16我设定的目标是1.5μs死区这是中小功率IGBT和MOSFET常见的死区值。算一下先用第一段公式死区 DTG × 13.89ns 1500nsDTG 1500 / 13.89 ≈ 108大于127不在第一段范围内需要换段。用第二段公式死区 (64 DTG[5:0]) × 13.89 × 2 1500算得64 DTG[5:0] ≈ 54小于64直接溢出说明第二段也不够用。换成第三段(32 DTG[4:0]) × 13.89 × 8 150032 DTG[4:0] ≈ 13.5还是不够。最后用第四段(32 DTG[4:0]) × 13.89 × 16 150032 DTG[4:0] ≈ 6.75取7DTG[4:0] 7。所以配置值就是 0b11100111写入BDTR寄存器的DTG位段即可写成十六进制就是0xE7。这个计算过程很绕实际开发中谁会天天手算我的建议是直接用STM32CubeMX配置或者写个简单的小函数自动分段计算但作为开发者至少要理解背后的分段逻辑因为不同单片机、不同定时器时钟下这个值变化很大不能盲目复用。3.3 互补输出引脚分配与初始化流程F103C8T6的引脚资源有限TIM1的互补输出引脚固定为PA8/PA7、PA9/PA8这里以手册为准具体来说CH1PA8CH1NPB13CH2PA9CH2NPB14CH3PA10CH3NPB15初始化流程分三步走第一步配置引脚为复用推挽输出注意互补输出引脚要设置为AFIO复用模式第二步配置定时器时基和PWM模式包括溢出频率20kHz第三步配置死区寄存器使能主输出MOE位最后启动定时器。有个细节需要注意高级定时器TIM1在使能PWM输出时必须把BDTR寄存器的MOEMain Output Enable位置1否则引脚上不会有波形输出。很多新手搞了半天没有输出就是漏了这个位。// 死区配置示例约1.5μs 72MHz TIM1-BDTR (TIM_BDTR_DTG_2 | TIM_BDTR_DTG_1 | TIM_BDTR_DTG_0) | (TIM_BDTR_DTG_4 | TIM_BDTR_DTG_3 | TIM_BDTR_DTG_2 | TIM_BDTR_DTG_1 | TIM_BDTR_DTG_0) | TIM_BDTR_MOE; // 实际值 0xE7D5其中DTG11100111MOE14. 调频调压V/F曲线的软件落地4.1 同步调制与异步调制的边界电机调速要变频率最简单粗暴的做法是直接改定时器的ARR值让PWM周期跟随输出频率变化。但ARR一旦改变载波频率也跟着变了这就是异步调制载波频率固定输出频率变化。异步调制的优点是实现简单缺点是低频时波形离散化严重高频时载波比太低导致谐波大。所以更讲究的做法是同步调制载波频率和输出频率保持固定比例比如载波比N 400输出频率50Hz时载波20kHz输出频率25Hz时载波10kHz。这样每个输出周期内的脉冲数恒定波形对称性好。我在代码里做了一个折中30Hz以下用同步调制30Hz以上切换到异步调制载波频率恒定20kHz。切换点要加滞回判断防止在切换点附近来回跳变导致频率抖动。4.2 调压逻辑与调制比的联动调频的同时必须调压这就是V/F曲线。对于三相异步电机理想情况是电压和频率成正比保持气隙磁通恒定。如果只升频不升压电机在低频段力矩严重不足转不起来如果只升压不升频磁通过饱和电流飙升电机发热严重。我的实现方法是定义一个单独的调频函数输入目标频率输出两个变量ARR的新值和调制比M的新值。频率与ARR的关系是线性的ARR 72000000 / (载波比 × 频率)载波比N根据当前频率动态计算调制比M按V/F曲线比例计算从0Hz的0V起步到50Hz时达到额定电压。低频补偿也要考虑。电机在低速时需要额外加一点电压补偿定子电阻压降。我的做法是频率低于10Hz时调制比M 0.15 频率 × 0.017相当于起始给了15%的电压作为补偿10Hz以上按线性V/F走。4.3 频率切换时的平滑过渡技巧直接改ARR会导致PWM周期突跳轻则电流冲击重则过流保护。我踩过这个坑一开始调频就是直接改ARR值结果电机在频率切换的瞬间嗡的一声电流波形有明显尖峰。解决方案是斜坡限幅频率不是一步到位而是按设定的加速度逐步逼近目标值。我在代码里用了一个定时器中断TIM21ms周期每1ms比较当前频率和目标频率差值大于步长就按步长增减然后重新计算ARR和M值。这样频率切换过程是平滑的斜坡电机电流平稳。斜坡加速度要配合负载惯量调整。负载惯量大加速度设小一点防止过流空载或者小惯量加速度可以大一些。我调试时以重载启动不炸管、不跳闸为基准逐渐加大加速度得到一个余量充足的临界值。// TIM2中断中执行1ms周期 void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update)) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 斜坡限幅调频 if (current_freq target_freq) { current_freq freq_step; if (current_freq target_freq) current_freq target_freq; } else if (current_freq target_freq) { current_freq - freq_step; if (current_freq target_freq) current_freq target_freq; } // 根据当前频率更新ARR和调制比M update_spwm_params(current_freq); } }5. 调试实录踩过的坑和排查思路5.1 上下桥臂直通瞬间炸管的教训第一次上电我就差点把板子送走。情况是这样的程序里死区配置已经写好了逻辑分析仪看波形也正常但一接上功率板MOSFET直接冒烟。排查了一整天才发现问题出在初始化顺序上。TIM1的MOE位主输出使能在定时器初始化后就被置位了但此时GPIO的AFIO复用配置还没完成引脚处于不确定状态偏偏在死区还没生效的那个窗口期上下桥臂同时导通了。这个坑的教训是初始化顺序必须严格保证先配置GPIO、再配置TIM参数含死区、最后才使能MOE输出。更稳妥的做法是加一个软件开关先让PWM配置就绪但MOE不使能等系统完全启动、母线电压稳定后再打开MOE。我把这个逻辑做成一个延时启动所有初始化完成后延时500ms再开主输出这500ms内如果检测到异常就直接不进启动流程。5.2 电机运行噪音大、电流波形不对称第二个典型问题是电机低速运行时有明显电磁噪音示波器看电流波形一个周期内正半波和负半波的面积不一样。查了半天问题出在死区时间不均匀。SPWM死区不只是防止直通那么简单它还会造成输出电压误差。死区时间内由于电流换向输出端电压会被钳位到母线电压这个钳位方向取决于电流方向导致实际输出电压偏离理想正弦波这就是死区效应。死区时间越大低次谐波越严重电机噪音越大低速时甚至会有明显的转矩脉动。解决方向有两条路。第一条是死区补偿软件里根据电流极性对CCR值做修正电流为正时增加占空比电流为负时减小占空比。但做死区补偿需要过零检测和电流方向判断F103C8T6只有一个ADC采集三相电流还要做同步复杂度上来了。第二条路是权衡死区时间在不炸管的前提下尽量缩小死区同时提高载波频率让死区占整个周期的比例缩小。我在这个项目中通过优化MOSFET驱动电路和光耦延时把死区从1.5μs压到了800ns噪音明显改善。对于大多数应用先做第二条路就能解决80%的问题。5.3 频率突变导致过流保护误触发还有一个调试经历值得分享。某次测试中我从20Hz直接跳变到45Hz过流保护立刻触发。之前明明做了斜坡限幅怎么会突变定位发现是TIM2斜坡中断被打断了——调试时开了多个中断TIM2优先级设置太低被USART中断长时间抢占导致斜坡更新不及时实际是阶梯式跳频触发过流。解决方法是调整中断优先级分配TIM2作为调频时基优先级提到最高通信中断可以容忍延迟降到最低。同时把斜坡更新频率从1ms提高到0.5ms频率变化更平滑。这个问题本质上是嵌入式系统典型的中断优先级设计问题电机控制这种硬实时任务必须优先保障。6. 项目扩展方向从F103到更高平台的迁移思路这套代码验证完成后往高端平台迁移是迟早的事。我的做法是把SPWM算法抽象成平台无关的C函数硬件的差异完全封装在底层驱动里。迁移到TMS320F28335这类DSP时最大的不同在于PWM模块的架构差异。C2000系列有专门的ePWM模块每个模块可以配置两路互补输出三个ePWM模块配合比STM32的TIM1还要灵活。死区配置支持上升沿延时、下降沿延时分别设定还有主源选择配合硬件死区补偿逻辑更丰富。算法层正弦表、V/F曲线、斜坡限幅代码可以平搬只需要换底层接口函数。如果继续停留在STM32家族F103升级到F334或者G4系列也有明显收益G4系列集成CORDIC加速器和更高精度的HRTIM高分辨率定时器做高频SPWM或者PFC控制都有优势。唯一要重新适应的是HAL库和LL库的接口风格但核心思路是相通的。实际项目里还有一个隐藏选择改用国产平替芯片。现在STM32的供货和价格波动确实让人头疼但我一直的观点是芯片选型不是简单看替换芯片能不能跑起来而是看整个生态开发工具、例程、社区资料能不能接得住。如果你对芯片底层的定时器结构理解得足够深换芯片其实只是重新写200行底层代码的事算法和调试经验才是核心价值所在。最后分享一个实用技巧在开发阶段给SPWM波形加一个最小占空比限制比如不低于2%这样可以避免在调制比接近0的时候出现极窄脉冲窄脉冲在隔离驱动电路里容易被吃掉或者导致MOSFET工作在线性区发热。这个限制在上限也要有不超过98%保证上下桥臂HSD/LSD驱动电路有足够的恢复时间。这个小细节在我几次试验中避免了至少两次MOSFET异常发热的情况建议做逆变的朋友都加上。本文还有配套的精品资源点击获取