STM32F103C8T6三相SPWM六路互补输出:调频调压与时序约束详解

发布时间:2026/10/26 8:49:06
STM32F103C8T6三相SPWM六路互补输出:调频调压与时序约束详解

简介:面向嵌入式初学者与电机控制入门者的STM32实战资料,围绕STM32F103C8T6的高级定时器TIM1,用查表法生成三相六路互补SPWM信号,相位互差120°,并加入死区控制与调频调压功能,采用恒压频比方式驱动三相电机,可用于课程设计、电机驱动实验与波形观测练习。压缩包共149个文件、约46.67MB,以c与h源码、Keil工程文件、hex与axf固件、pdf原理文档为主,另附SPWM正弦表生成工具、死区计算器及仿真相关配置,工程带详细注解,可在KEIL5中在线仿真观察输出波形。该资源已有8068人学习下载,读者可据此理解SPWM查表生成、互补输出与死区设置、恒压频比调压调频的实现思路,并借助配套工具自行生成正弦表、核算死区参数,对照源码与文档完成从原理到波形的完整验证。

1. 三相 SPWM 六路互补:为什么“调频调压”四个字背后藏着一整套时序约束

很多人第一次看到“STM32F103C8T6 输出三相六路互补 SPWM”这个描述,会下意识觉得这就是个定时器 PWM 的事——配好 ARR、配好 CCR、开互补通道,波形就出来了。真上手才发现,波形是出来了,但一调频就乱、一调压就偏、死区一加就畸变,示波器上那六根线怎么看怎么别扭。问题不在“会不会配寄存器”,而在于三相六路互补输出本质上是一组强耦合的时序约束:三相互差 120°、每相上下桥臂严格互补、互补之间必须插入死区、调频时载波比要跟着变、调压时调制比要跟着变,任何一个参数动一下,其余五个通道的相位关系都得重新对齐。这份资源围绕的就是把这套约束在 STM32F103C8T6 上用 KEIL5 可仿真的方式完整落地,把“调频调压”从一个口号拆成可验证的时序机制。搞懂它,你才明白为什么同样是 PWM,有的能直接驱动三相桥,有的上电就炸管。

2. 三相六路互补的时序骨架:120° 移相和死区到底在约束什么

2.1 三相互差 120° 不是“错开一点”,而是载波与调制波的双重对齐

三相 SPWM 的核心不是三个独立的 PWM,而是三路调制波共享同一个载波。常见的实现思路是:用一个定时器产生三角载波(或锯齿载波),三路正弦调制值分别相差 2π/3,各自与载波比较得到占空比。关键在于“共享载波”这四个字——如果三路各用各的定时器、各起各的计数,哪怕初值只差几个 tick,长期运行下相位漂移会累积,电机上表现为转矩脉动、电流谐波变差。

在 STM32F103C8T6 上,合格的做法是把三相互补六路全部压到同一个高级定时器(TIM1)上。TIM1 有 CH1/CH1N、CH2/CH2N、CH3/CH3N 三对互补通道,天然满足“同一时基、同一载波”的前提。三路调制值的 120° 关系不靠硬件移相,而是靠软件在每个载波周期更新三个 CCR 值:

// 每个载波周期(更新事件)重算三相占空比 // mod_index: 调制比 0~1,f_out: 输出频率,Ts: 载波周期 for (int phase = 0; phase < 3; phase++) { float theta = 2.0f * PI * f_out * t + phase * (2.0f * PI / 3.0f); float duty = 0.5f + 0.5f * mod_index * sinf(theta); // 归一化到 0~1 ccr[phase] = (uint16_t)(duty * (ARR + 1)); // 映射到比较寄存器 } // 三路 CCR 在同一更新事件写入,保证相位关系不被打断

这段逻辑里有两个容易翻车的点。第一,phase * (2π/3)的相位偏移必须作用在调制波上,而不是在载波上做延迟;第二,三个 CCR 必须在同一个更新事件里一起生效,否则会出现某一相已经跳到新周期、另一相还停在旧周期的“半拍”现象,示波器上就是三相交点不干净。常见的实现思路是用 TIM1 的更新中断统一重算并写入,或者用 DMA 搬运预计算表,避免中断延迟抖动。

2.2 互补输出的“互补”是硬件保证的,但死区必须由你显式配置

互补两个字听起来简单:上桥导通时下桥关断。但真实功率桥的开关管关断有延迟,如果上下管在切换瞬间同时导通,就是直通短路,血泪经验里烧管子的场景多半出在这。所以“互补”在工程上从来不是严格的非此即彼,而是互补 + 死区:上管关断后,延迟一段死区时间,下管才允许导通。

STM32 的高级定时器把这件事做进了硬件:BDTR 寄存器里的 DTG 位域配置死区时长,CHxN 输出自动带上死区。这意味着死区不是软件在中断里“手动错开”,而是硬件在比较匹配点附近自动插入。常见的实现思路是:

// TIM1 死区配置示例(BDTR) // DTG[7:0] 决定死区时长,具体换算与 tDTS 相关 TIM1->BDTR |= (uint32_t)(DEAD_TIME_TICKS & 0xFF); // 低 8 位死区 TIM1->BDTR |= TIM_BDTR_MOE; // 主输出使能,缺了它六路全哑

这里最容易被忽略的是 MOE 位。很多人配完通道、配完死区,波形死活不出来,排查半天发现是主输出使能没开——这是黑匣子式的翻车,因为寄存器手册里 MOE 和通道使能是分开描述的。另一个坑是死区时长和载波周期的关系:死区占载波周期的比例过大,会明显压缩有效占空比范围,低压时波形畸变;过小则起不到防直通作用。一般按开关管关断时间留 1.5~2 倍裕量,再反推 DTG 值。

2.3 调频调压为什么必须联动载波比

“调频调压”四个字放在一起不是凑数。SPWM 的输出频率由调制波频率决定,输出幅值由调制比决定,但这两者都挂在同一个载波上。当输出频率变化时,如果载波频率不变,载波比(载波频率/调制波频率)就变了。载波比太低,一个调制波周期里采样点太少,波形阶梯化严重、谐波往低频跑;载波比太高,开关损耗上去,而且 F103 的 72MHz 主频和定时器分辨率未必撑得住。

所以调频时通常要同步调整 ARR(载波周期),让载波比维持在一个合理区间。调压则只动调制比,不动 ARR。这两条路径分开走,是这套机制能稳定工作的前提:

调节目标改动对象不改动对象原因
调频调制波频率 + ARR(载波周期)调制比维持载波比,控制谐波分布
调压调制比(CCR 缩放系数)ARR、调制波频率只改幅值,不动时序骨架

把这张表记住,后面调参时就不会出现“调频把幅值带偏了”的玄学现象——那多半是把调制比和载波周期混在一个变量里改了。

3. 从正弦表到 CCR:SPWM 在 F103 上的数据通路与实时性账本

3.1 查表法 vs 实时计算:为什么资源里更倾向预计算正弦表

在 72MHz 的 Cortex-M3 上,每个载波周期算三次sinf()并不是不行,但代价要算清楚。假设载波 10kHz,每个周期三次浮点正弦加缩放,中断里还要写寄存器,浮点运算在 M3 上没有 FPU,sinf走的是软件库,单次几十到上百周期不等。载波频率一高,中断占用率就上去了,主循环被挤得没空间做调频调压的指令解析。

常见的实现思路是预计算一张正弦表,按相位索引取值,中断里只做乘加和写 CCR:

#define SIN_TABLE_SIZE 256 static const uint16_t sin_table[SIN_TABLE_SIZE] = { /* 0~2π 归一化正弦,中值偏置 */ }; // 中断内:相位累加 + 查表 + 缩放 phase_acc[0] += phase_step; // 每相独立相位累加器 uint16_t idx = (phase_acc[0] >> 16) & (SIN_TABLE_SIZE - 1); ccr_val = center + ((int32_t)(sin_table[idx] - center) * mod_index_q15 >> 15);

这里用定点相位累加器(高 16 位做整数部分、低 16 位做小数部分)替代浮点,是为了让相位步进在中断里只做一次加法。phase_step由输出频率和载波周期共同决定,调频时改的就是它。查表法把三角函数开销从“每周期三次”压到“每周期三次数组访问”,实时性账本立刻好看很多。代价是表长带来的量化误差,256 点对一般电机驱动够用,对波形纯度要求高的场合可以加到 512 或 1024,但要权衡 Flash 占用和查表缓存。

3.2 更新事件、中断与 DMA:谁负责把数据送进 CCR

数据通路的设计决定了波形干不干净。最直接的做法是更新中断里写三个 CCR,但中断响应有延迟抖动,载波频率高时抖动占比不可忽略。更稳的做法是用 DMA:把预计算好的 CCR 序列放在内存里,定时器更新事件触发 DMA 搬运,CPU 完全不参与逐周期写入。

// DMA 搬运 CCR 的典型配置思路(伪代码,突出机制) // 源:ccr_buffer[3 * N],目标:TIM1->CCR1 起始地址 // 每次更新事件搬 3 个半字,循环模式 DMA1_Channel5->CPAR = (uint32_t)&TIM1->CCR1; DMA1_Channel5->CMAR = (uint32_t)ccr_buffer; DMA1_Channel5->CNDTR = 3 * N; DMA1_Channel5->CCR |= DMA_CCR_CIRC | DMA_CCR_DIR; TIM1->DIER |= TIM_DIER_UDE; // 更新事件触发 DMA

DMA 方案的好处是时序确定性:搬运由硬件事件触发,不受中断延迟影响。代价是灵活性下降——调频调压时要么重建缓冲区,要么用双缓冲切换。资源里 KEIL5 可仿真的价值就在这里:你可以在仿真器里单步看 DMA 搬运和 CCR 更新的对应关系,确认每个更新事件确实搬了三个值、相位没有错位,而不是靠示波器猜。仿真环境下还能直接观察 BDTR、CCER、ARR 这些寄存器的实际值,把“配置了但没生效”这类问题在下载前就摁掉。

3.3 调频调压的实时路径:参数从哪来、在哪生效

调频调压不是两个孤立旋钮,它们最终都要落到“下一个载波周期用什么参数”。常见的实现思路是维护一组目标参数(目标频率、目标调制比),在更新中断或主循环里做斜坡过渡,避免参数突变导致电流冲击:

// 主循环里做参数斜坡,中断里只读当前生效值 target_freq = cmd_freq; // 来自上位机/按键 target_mod = cmd_mod; // 每个控制周期逼近目标,限制变化率 cur_freq += clamp(target_freq - cur_freq, -FREQ_SLEW, FREQ_SLEW); cur_mod += clamp(target_mod - cur_mod, -MOD_SLEW, MOD_SLEW); // 由 cur_freq 重算 phase_step,由 cur_mod 重算缩放系数

这条路径的关键是“生效点”统一:频率和调制比都在更新事件边界生效,保证一个载波周期内参数不变。如果在中途改 CCR,会出现同一周期内三相占空比基于不同参数计算的错位,波形上就是毛刺。调频时还要注意 ARR 改动本身也应在更新事件生效(F103 的 ARR 有预装载机制),否则计数中途改周期会丢拍或重装异常。

4. 避坑与排查:六路波形不对时先看哪几个寄存器

4.1 六路全无输出,通道配置看着都对

现象:CCER 里通道使能都开了,CCR 也有值,示波器六路全平。原因多半是 BDTR 的 MOE 主输出使能没置位,高级定时器的互补输出受 MOE 总闸控制,通道使能只是分闸。解决:确认TIM1->BDTR |= TIM_BDTR_MOE,并检查是否有刹车输入(BRK)被误触发把 MOE 清掉了。

4.2 互补两路同时为高,死区像没生效

现象:上管和下管驱动信号在切换沿重叠。原因通常是死区值配了但 DTG 换算错,或者死区时长小于驱动电路的实际关断延迟。解决:按 tDTS 反推 DTG 实际纳秒数,用示波器量互补沿的重叠时间,确保死区大于开关管关断时间加驱动延迟;同时确认没有把死区配成 0。

4.3 调频后三相相位关系漂移

现象:低频时三相交点正常,频率调高后交点错位、电流谐波变大。原因多为三路相位累加器用了不同的步进更新时机,或者 CCR 写入不在同一更新事件。解决:三路相位累加器在同一中断里同步推进,CCR 写入放在更新事件回调里统一执行,避免某一路提前半拍。

4.4 调压时波形削顶或畸变

现象:调制比调大后正弦顶部变平。原因是调制比超过线性区上限,或者死区占比过大压缩了有效占空比。解决:把调制比限制在线性调制区内(一般留 5%~10% 裕量),并核算死区时间占载波周期的比例,必要时提高载波频率或缩短死区。

4.5 仿真通过、下载后波形异常

现象:KEIL5 仿真里寄存器值正确,实际板子上波形不对。原因常见于仿真时外设时钟配置和实际不一致,或实际硬件的驱动延时、死区需求与仿真假设不同。解决:仿真重点验证时序逻辑和寄存器写入顺序,死区和驱动匹配必须上板实测,别把仿真当最终验收。

5. 设计取舍:为什么这套方案把“可仿真”当成一等公民

把三相六路互补 SPWM 做出来不难,难的是让它可解释、可复现、可排查。这套资源在 F103C8T6 上选择“单定时器 + 硬件死区 + 查表/定点 + KEIL5 可仿真”的组合,本质上是在几个取舍点上做了明确站队。

第一个取舍是单定时器对多定时器。多定时器方案灵活,但三相共享载波的前提被打破,相位对齐要靠软件同步,长期漂移难控。单定时器把时基统一这件事交给硬件,代价是通道资源被一个定时器吃满,但对三相桥来说正好够用。

第二个取舍是硬件死区对软件死区。软件在中断里错开上下管,逻辑上能做,但中断抖动会让死区时长不确定,高载波频率下尤其明显。硬件死区由比较匹配点触发,时长确定,代价是要理解 DTG 换算和 MOE 总闸这套寄存器语义——这也是为什么很多人第一次配高级定时器会卡在“波形不出来”。

第三个取舍是可仿真对纯实测。仿真不能替代上板,但它能把“寄存器写没写对、更新事件有没有触发、DMA 搬没搬”这些确定性问题提前暴露,把上板调试的注意力留给死区匹配、驱动延时这些必须实测的部分。我一般会这么理解:仿真负责证明逻辑自洽,实测负责证明物理可行,两者缺一,排查就会变成玄学。

如果你正在做三相逆变、电机驱动或者电力电子相关的课程设计,这套东西的价值不在于“给了你一份能跑的工程”,而在于它把调频调压、死区、互补输出这几件事的耦合关系摊开给你看,配合 KEIL5 仿真能一步步验证每个机制是否真的生效。资源里的工程和配置可以直接拿来对照自己的实现,把每个寄存器、每条数据通路都对一遍,比对着手册空想快得多。从那以后我每次配高级定时器,都会先把 MOE、DTG、ARR 预装载这三处强制走一遍检查清单,再去看波形——希望帮到你。

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