news 2026/9/16 2:38:37

STM32三相SPWM波输出:从定时器配置到死区调试的全流程指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32三相SPWM波输出:从定时器配置到死区调试的全流程指南

简介:基于STM32的三相SPWM波输出压缩包,面向电机控制与逆变器方向的嵌入式开发者,重点解决用STM32高级定时器生成相位差120度的三相正弦脉宽调制波。工程覆盖PWM模式配置、预分频、比较寄存器占空比、死区插入,用三角载波与正弦表比较产生互补输出,同时给出三相平衡调整、调制指数优化、中断连续更新等处理方法,适合有STM32基础、想深入电机驱动的工程师。包内共174个文件,以C源文件、H头文件为主,另有uvproj工程文件、hex烧录文件、map映射文件、axf调试文件及编译中间结果,压缩包约2.81MB,目前已有564人学习下载。通过阅读源码和对比示波器波形,可掌握从定时器初始化到三相SPWM输出的完整链路,并将载波生成、死区控制、换相逻辑迁移到电机驱动或逆变器项目中。

1. 三相SPWM输出到底在解决什么问题

三相逆变器第一次上电,最常见的坑不是炸管,而是三个桥臂的PWM看着都不太对,放到电机上却嗡嗡尖叫、电流波形畸变。根子往往不在功率级,而在STM32输出的三路SPWM:相位彼此差了多少、死区够不够、每个载波周期有没有按正弦规律更新。基于STM32的三相SPWM波输出,就是要把三路载波同步的PWM,按正弦函数调制成互差120°的占空比序列,再通过半桥逆变得到三相对称电压。这个调制层做好了,后续接电流环、速度环、编码器对位都不会被波形拖后腿。适合正在做变频器、UPS、伺服驱动或电机调速开发的工程师,也适合用标准库或HAL库把毕设基础打牢的在校读者。

2. 调制机理与定时器选型:为什么是STM32的TIM1/TIM8

2.1 三角载波与正弦调制波的数学关系

SPWM的本质是用“占空比按正弦变化”的方波去逼近正弦电压。把定时器的计数周期看作三角载波,正弦调制波与三角波比较得到输出翻转点,换算到每个开关周期内的平均电压就是:

D(t) = (1 + M·sin(ωt)) / 2

满占空比时输出接近母线电压的一半,M是调制比(0到1之间),ωt是当前正弦角度。由于PWM频率远高于输出正弦频率,电感的伏秒积分会把高频分量滤掉,桥臂中点电压的基波正比于D(t),谐波则集中在载波频率的整数倍附近。

注意这个公式是单相的表达。做三相SPWM,只需要把同一个公式套到三个相位上:A相用ωt,B相用ωt - 2π/3,C相用ωt + 2π/3。三路PWM的载波必须同频同相,这样三相线电压里才能抵消掉共模的载波分量。这也是为什么三个通道要用同一个定时器、同一个计数器,而不是随便开三个软定时器去拼。

2.2 为什么高级定时器TIM1/TIM8比通用定时器更适合

STM32F103里能直接用于三相半桥的定时器只有TIM1和TIM8,它们在引脚上引出CH1~CH3和互补输出CH1N~CH3N,还内置死区发生器和刹车输入。通用定时器TIM2/3/4/5虽然也有PWM模式,但要得到六路带死区的互补波,得自己搭延迟电路或用外部逻辑门,时序一致性很难保证。特点对比如表:

特性TIM1 / TIM8TIM2/3/4/5
三路独立CCR输出
互补输出 CHx/CHxN
可编程死区(DTG)
主输出使能 MOE
刹车输入有,可做硬件过流封波
更新事件挂DMA

工程上做三相逆变桥,只需要TIM1的CH1~CH3分别驱动A/B/C相的上桥臂,CH1N~CH3N驱动对应的下桥臂,死区由硬件在翻转沿自动插入。如果只是做三路独立的SPWM信号不带互补,TIM1同样能胜任。一个误区是以为三路PWM来自三个不同通道就能直接输出给全桥,实际上六个MOS管需要的是互补波,通道数不够时肯定要外扩,选型时建议一步到位选带CHxN的芯片。

2.3 最小可运行的TIM1初始化

用HAL库配置时核心是:中心对齐模式、PWM模式1、使能三个通道输出。以下按20kHz载波、72MHz定时器时钟计算配置,ARR等于1800。

TIM_MasterConfigTypeDef sMasterConfig = {0}; TIM_OC_InitTypeDef sConfigOC = {0}; TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0}; htim1.Instance = TIM1; htim1.Init.Prescaler = 0; htim1.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED1; htim1.Init.Period = 1800 - 1; htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter = 1; HAL_TIM_PWM_Init(&htim1); sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 900; sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET; sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET; HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1); HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2); HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3);

关键参数解释:

  • Prescaler为0,让TIM1直接工作在72MHz,CCR写入精度约13.9ns。
  • Period=1800-1,中心对齐模式下PWM频率 = 72MHz / (2 × 1800) = 20kHz。
  • RepetitionCounter=1,让更新事件在每个载波周期只发生一次,避免上下两个计数方向各触发一次。
  • Pulse=900是占空比50%的中点,运行后会被正弦表覆盖。
  • OCIdleState/OCNIdleState用于空闲态电平,如果这里配反,会出现停机时桥臂直通,上电前先确认。

GPIO要复用为TIM1:PA8/PA9/PA10对应CH1/CH2/CH3,PB13/PB14/PB15对应CH1N/CH2N/CH3N。接着配置死区和刹车,在主输出使能前所有输出都是关闭的,这能避免上电瞬间桥臂直通:

sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE; sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE; sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF; sBreakDeadTimeConfig.DeadTime = 72; /* 72MHz下对应1μs */ sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE; sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH; sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE; HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig); 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_TIM_MOE_ENABLE(&htim1);

DeadTime字段不是“微秒”也不是“纳秒”,它是BDTR寄存器里DTG[7:0]的原始值。72MHz下写成72,得到1μs死区;如果定时器时钟改成其他频率,这个数字必须重新算。BreakState先关闭,调试正常后再把它接到硬件过流信号上,配合MOE做快速封波,比软件中断封波快一个数量级。

3. 三相SPWM生成的三块积木:正弦表、查表索引与更新触发

3.1 正弦表怎么建:提前算好,绝不在中断里算三角函数

SPWM的常见实现是查表法:把正弦函数的一个完整周期量化成固定个数的点,存进ROM,每个载波周期按索引取出对应的CCR值。查表相比在中断里实时调用sinf(),速度快、执行时间确定,对72MHz的STM32F103来说能省出大量CPU。表长取2的幂,后续用位与代替取模,能让中断延迟更稳定。

表长选择参考:载波20kHz、工频50Hz时,每个工频周期正好有400个载波周期,所以512点足够;如果载波只有10kHz,256点就够。点数再往上加,谐波失真改善有限,但Flash占用翻倍。

#define TAB_LEN 512 static uint16_t sin3_table[TAB_LEN][3]; void build_sin3_table(uint16_t arr) { for (uint32_t i = 0; i < TAB_LEN; i++) { double w = 2.0 * 3.14159265358979 * i / TAB_LEN; float d_a = 0.5f + 0.45f * sinf((float)w); float d_b = 0.5f + 0.45f * sinf((float)(w - 2.0 * 3.14159265358979 / 3.0)); float d_c = 0.5f + 0.45f * sinf((float)(w + 2.0 * 3.14159265358979 / 3.0)); sin3_table[i][0] = (uint16_t)(d_a * (float)(arr - 1)); sin3_table[i][1] = (uint16_t)(d_b * (float)(arr - 1)); sin3_table[i][2] = (uint16_t)(d_c * (float)(arr - 1)); } }

这段代码做了什么:w是当前查表相位,d_a/d_b/d_c是三相调制波在一个点上的占空比。d的取值在0.05到0.95之间摆动,等效调制比0.9,不会出现100%占空比。乘(arr-1)把占空比换算成CCR比较值,范围0~1799。如果把0.45改成0.5,调制比到1,但满载时上下桥臂的最小导通时间趋近于零,IGBT/MOS管的驱动芯片可能来不及完全关断,建议保留余量。工程机上如果不想在启动时调sinf,可以直接把这个表预生成成const数组烧进Flash,连建表时间都省掉。

3.2 查表索引:一个载波周期走一步

系统上电后,每次更新事件把索引pos加1,再从表中取三列值写到CH1~CH3的CCR。输出正弦频率由索引步进决定:

f_sin = f_carrier × step / TAB_LEN

step=1、f_carrier=20kHz、TAB_LEN=512时,输出约39.06Hz,离常见的50Hz差一些。想精确到50Hz,可以改用DDS累加器:用一个32位相位累加器,每次更新加上一个步长,取高9位做表索引,步长为0x10000 × f_sin / f_carrier × TAB_LEN。这样频率调节精度比整数索引好得多,而且频率变化时不会相位跳变。

3.3 更新触发:用TIM1更新中断把三路CCR一次刷掉

最小可跑版本用更新中断,在中断里更新三个比较寄存器。20kHz的更新频率在72MHz主频下完全能承受,中断里只有三次写寄存器,实测占用CPU在5%以内。

static uint32_t pos = 0; void TIM1_UP_IRQHandler(void) { if (__HAL_TIM_GET_FLAG(&htim1, TIM_FLAG_UPDATE)) { __HAL_TIM_CLEAR_FLAG(&htim1, TIM_FLAG_UPDATE); uint32_t idx = (pos++) & (TAB_LEN - 1); htim1.Instance->CCR1 = sin3_table[idx][0]; htim1.Instance->CCR2 = sin3_table[idx][1]; htim1.Instance->CCR3 = sin3_table[idx][2]; } }

这里直接写寄存器而不是调用HAL_TIM_SET_COMPARE,能省掉函数进入退出的开销。TAB_LEN是2的幂,所以用与运算取模;如果表长改成非2的幂,必须改成取余运算,否则索引越界会读到垃圾数据。pos是无符号32位,跑几天也不会溢出导致驱动卡死。中断里如果需要调压,可以在更新事件之外动态改0.45这个系数,但要注意不要让d_a超出0.5±0.5的边界,否则会出现削顶。

3.4 进阶:把更新频率完全交给DMA

如果载波频率提到50kHz以上,或者主循环里要跑浮点运算,可以把三路CCR的更新交给DMA。做法是预先建立三个长度等于TAB_LEN的DMA缓冲,分别对应CH1~CH3,用TIM1的更新事件触发DMA搬运。HAL里启动的是HAL_TIM_PWM_Start_DMA,每次更新事件自动搬运一个word到CCR,搬运完一轮后可用循环模式自动回卷。相比中断版本,CPU完全不进ISR,波形不会和主循环抖动。

它的坑也在这:三个通道要同时更新,但三条DMA流各自搬运,如果其中一路被更高优先级的DMA抢占,那一周期里三个CCR的刷新时刻就不一致,相电压会出现一个载波周期的毛刺。稳健做法是把三个CCR值连续放在一个结构体里,用DMA的burst模式一次搬三个word,或者保留一条低优先级的TIM更新中断做三路值的一致性校验。先跑通中断版,再优化到DMA,是效率最高的路径。

4. 载波频率、死区与调制比:三相SPWM的三个必调参数

4.1 载波频率:先解决噪音和损耗的矛盾

载波频率直接决定三点:输出纹波大小、开关损耗、是否落在人耳可听区。电机驱动常用16kHz以上避开听觉敏感区,但功率管开关损耗线性上升;工业大功率变频器载波反而会降到2~4kHz,靠输出电抗器弥补谐波。参考选型:

应用场景载波频率主要考虑
小功率BLDC/交流伺服20kHz~50kHz低噪音、滤波体积小
中大功率变频器4kHz~10kHz温升与IGBT损耗平衡
大功率整流/逆变电源2kHz~8kHz散热、开关损耗
车载或精密电源20kHz~100kHz磁件体积、响应速度

同一个定时器配置,改变载波就是改Period。以TIM1时钟72MHz为例,中心对齐模式下载波20kHz对应ARR=1800,10kHz对应3600,调高到50kHz则是720。注意同时检查Period和实际应用的死区是否匹配;载波越高,死区占一个周期比例越大,输出基波损失也越明显。

4.2 死区时间:DTG不是微秒,是寄存器值

死区是给功率管上下桥臂换流留出的不导通时间,防止高侧还没完全关断低侧就导通。最小死区等于驱动芯片关断延迟减开启延迟,再加安全余量。IGBT一般取1~3μs,MOSFET几百纳秒。上文配置里DeadTime=72,在72MHz下等于72个tDTS,即1μs,这只适用于DTG值小于128的情况。

ST的BDTR寄存器对DTG[7:0]做了分段编码,不是线性的。DTG在0~127时,死区等于DTG × tDTS;DTG[7:5]为10x时,死区等于(64 + DTG[4:0]) × 2 × tDTS;DTG[7:5]为11x时按8或16倍增。工程上常犯的错是看到数据手册写“最大死区”,直接把App里应填的秒数当DTG用,导致死区比预期大几倍。设完死区后用示波器量一下实际宽度,比对着寄存器算可靠。死区过大的直接后果是输出电压基波下降,相电流畸变率升高,电机在低速轻载时容易振动。

4.3 调制比与查表点数:为什么不是越高越好

调制比M就是查表代码里乘的那个0.45对应的比例。M=1代表占空比在0~100%间摆满整个正弦波,理想情况下输出基波最大,但功率管的关断时间和栅极驱动都吃不满,实际工程至少要留5%~10%裕量,这就是代码里用0.9倍调制比的直接原因。

查表点数与载波比的匹配更重要。查表法在每个载波周期只更新一次CCR,如果表长小于载波比,输出阶梯就粗;如果表长远大于载波比,很多点根本走不到,白白占Flash。这里记住一个估算公式:

TAB_LEN ≥ f_carrier / f_sin_max

其中f_sin_max是最大输出正弦频率。20kHz载波、50Hz输出,400点起;要做到100Hz(比如同步电机弱磁区),表至少要200点,工程上取512已相当宽裕。到调制比接近1且要最大母线电压利用率时,三相SPWM线性区就到头了,再往上要么过调制,要么切SVPWM加零序分量注入。

5. 验证与排错:从方波瞪到三相实波

5.1 三步验证:从MCU到功率级

第一步,不接功率级,示波器探头接TIM1的CH1/CH2/CH3引脚,参考接GND,能看到三路占空比随时间张开、合拢的方波。把两个通道开加法或余晖模式,逐帧确认相位差120°。这一步可以筛掉查表索引错误和相位写反的问题。

第二步,看一对互补波:CH1和CH1N同时接两路探头,打开死区放大。死区如果在1μs左右且出现在上升沿,说明BDTR配置生效。如果两只管子存在同时导通的区间,波形上会看到一条直通的谷,这个必须在上电前排除。

第三步,接阻感负载或电机,测输出线电压Uab和相电流。线电压应为正负交替的SPWM脉冲,电流波形应接近正弦。若电流波形有削顶,多半是调制比过高,查母线电压与目标电压间的裕量。

5.2 常见故障与排查顺序

现象优先检查项
三路输出全是恒低或恒高MOE未使能、GPIO复用没配、通道没Start
只有上桥臂输出,下桥臂全关OCNPolarity或OCNIdleState配置错误
死区比预期大很多DTG编码非线性,用实测波形确认
每隔固定周期出现一次毛刺DMA回卷或索引溢出,先换回取余
电机啸叫但能转载波频率太低或死区占空比过大,先提载波
上电瞬间炸管死区过小或BreakState时序不对,加硬件过流刹车

最后一招是拉长观察尺度:把示波器时基放到10ms/div,占空比包络应当是一个完整的正弦形状;再缩回50ns/div量死区。硬件上建议在母线正负端各并一个0.1μF高频电容,减少功率管开关噪声对TIM1参考地的影响,这一条能省掉很多“波形明明看着对却偶然误触发”的定位时间。用示波器的数学通道做CH1减CH2,能直接量到两相线电压的SPWM脉冲序列,边沿处是否有重叠一目了然,这个方法比纯肉眼看死区更可靠。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 2:37:27

MATLAB实战短时交通流量预测:ARIMA+BP组合模型全解析

简介&#xff1a;这是一份面向交通工程、智能交通系统研究人员与MATLAB初学者的短时交通流量预测源码资源。压缩包内为单个MATLAB脚本文件&#xff0c;大小仅1KB&#xff0c;代码轻量紧凑&#xff0c;聚焦交通流量预测这一核心场景。该脚本围绕数据预处理、特征工程、模型构建、…

作者头像 李华
网站建设 2026/9/16 2:37:01

智慧路灯管理后台系统模板:物联网接入与GIS可视化运维实践

简介&#xff1a;面向城市智慧照明管理场景的后台系统模板&#xff0c;适合物联网平台开发人员、前端工程师及方案集成商快速搭建路灯运维管理界面。模板覆盖设备监控、能耗统计、报警管理、远程控制等核心功能模块&#xff0c;并提供地图可视化与图表展示的交互原型。资源以静…

作者头像 李华
网站建设 2026/9/16 2:36:13

Java网络编程实战:从TCP/IP到Socket与UDP通信全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 2:36:08

2026企业AI办公工具选型指南:从场景匹配到平台盘点

企业引入AI办公工具的过程中&#xff0c;很多管理者容易陷入单一指标判断的误区。部分团队评估工具时&#xff0c;只统计平台内置功能数量&#xff0c;或是单纯参考市场热度&#xff0c;以此作为采购决策依据。这类评估方式会忽略工具与企业业务流程的适配性&#xff0c;不少企…

作者头像 李华
网站建设 2026/9/16 2:35:49

系统设计笔记的本质:从概念堆砌到可执行的工程肌肉记忆

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 2:35:44

蒙特卡洛抽样在电动汽车充电负荷建模中的工程应用

简介&#xff1a;本资源是面向电力系统工程师、新能源规划研究人员及高校电气专业研究生的蒙特卡洛法电动汽车充电负荷建模实践包&#xff0c;聚焦城市配电网负荷预测与基础设施规划中的关键不确定性建模问题。压缩包共21个文件&#xff0c;含8个MATLAB源码&#xff08;.m&…

作者头像 李华