简介面向STM32F103C8T6三相电机驱动与变频控制场景该资源提供基于高级定时器TIM1查表法生成三相六路互补SPWM的完整工程。代码生成相位互差120°的六路PWM可配置死区时间并通过恒压频比策略实现调频调压适用于逆变器控制、变频调速、电机驱动等教学与工程验证。工程基于KEIL5编写注释详尽支持在线仿真直接观察输出波形源码中spwm.c、timer.c等模块划分清晰便于理解正弦查表、死区插入、压频调节的具体实现同时附带的PDF文档可辅助原理梳理。资源包共148个文件包含18个C源文件、26个头文件、KEIL工程配置、链接脚本、PDF参考文档、hex可执行文件等压缩包大小46.65MB。目前已有1676人学习下载对嵌入式及电机控制方向的开发者而言可作为学习样例和移植基础快速获取可运行的工程并对照调试。1. SPWM三相六路互补输出死区和调频调压是怎么凑到一起的做三相电机驱动或三相逆变电源开发的人迟早会撞上「SPWM三相六路互补输出」这个组合。它的本质是把三对桥臂的开关逻辑映射成六路PWM每一相的上管和下管逻辑上必须反相这就是“互补”但物理器件关断需要时间所以要在切换沿插入一小段死区防止上下管直通烧模块而输出频率和电压幅值又必须按恒压频比的规律一起动这就是标题里“调频调压”要做的事。这篇文章适合手上已经有逆变主电路或驱动板、正准备写固件的人我按自己调板子的顺序来讲SPWM怎么生成、六路互补怎么配死区、调频调压的频率和电压从哪里算出来以及几个最容易翻车的排查点。新手可以照着搭参数熟手可以直接跳去第5章看坑。2. SPWM生成原理规则采样、定时器比较与载波比选择2.1 自然采样与规则采样数字控制器里跑的其实是规则采样SPWM的数学原型是正弦波与三角波求交点正弦瞬时值高于三角波时输出高电平反之输出低电平输出脉冲宽度按正弦规律变化。这种实时求交点的做法叫自然采样硬件上由比较器电路直接完成模拟SPWM发生器就是干这个的。数字MCU里没有廉价的正弦-三角波比较器硬件所以几乎所有固件方案走的是规则采样在三角波的顶点或底点时刻把正弦函数当时的数值采进来保持一个载波周期不变得到的是一串阶梯状的占空比指令。规则采样和自然采样的差别在于脉冲边沿位置有一个周期量级的量化误差但载波比载波频率与输出频率之比超过100之后两者输出波形的THD差异已经小到工程上可以忽略。我一般直接查正弦表因为查表法只做一次乘法就把占空比算出来了中断里省时间。查表法的精度损失来自表长度和定点量化256点表配合Q15格式的相位步进对绝大多数电机驱动场景足够。2.2 定时器比较通道怎么合成一路SPWM预装载是容易忽略的一环定时器生成PWM的核心机制是计数器数值与比较寄存器CCR相等时输出引脚发生翻转。边沿对齐模式下计数器从0递增到ARR匹配一次输出翻转一次中心对齐模式下计数器先增后减输出在周期中点对称所以脉冲波形对称性更好谐波里偶次分量更小。中心对齐模式还有另一个好处上下桥臂的互补切换时刻在时间轴上对称死区插入后每半个周期引入的误差方向一致便于后期做死区补偿。我在电机驱动项目里默认选中心对齐只有对开关损耗极其敏感的高频小功率场合才切回边沿对齐。有个配置项特别容易踩坑比较寄存器的预装载。如果关闭预装载软件在计数周期中间某个时刻写入新CCR值该值可能在一个周期内被比较器匹配多次输出波形上就会出现不该有的毛刺。正确做法是使能预装载让新值只在更新事件周期顶点或底点才被搬运到实际比较寄存器。中心对齐模式下更新事件一个周期出现两次分别在顶点和底点利用两次更新事件刷新三相CCR软件上每个PWM周期有两次写入机会时序余量比边沿对齐更宽松。2.3 载波频率与载波比先定这两个数再谈波形质量载波频率也就是开关频率它直接决定四个相互制约的量输出电流纹波、开关损耗、死区占比和音频噪声。开关频率越高电流越平滑、波形越接近正弦但IGBT或MOSFET的开关损耗线性上升死区时间占一个周期的比例也在变大——死区占比过大时死区引起的电压误差会比想象中明显得多。常见做法是MOSFET方案选10kHz到20kHzIGBT方案选8kHz到12kHz这也是通用变频器的典型区间。载波比是载波频率除以输出频率。它决定一个正弦周期内能放下多少个PWM脉冲载波比太低输出波形就退化成少数几个阶梯电流纹波和转矩脉动都很严重。我调参数时有一个经验下限最低载波比不小于15最好能到20以上。于是选择顺序就变成了先按开关器件定好载波频率再算最高输出频率下载波比是否够不够就提高载波频率或者限制最高运行频率。下表是我惯用的参数起点不同功率段直接套用参数低压MOSFET方案三相380V IGBT方案载波频率16kHz8kHz最高输出频率400Hz100Hz最低载波比4080死区时间300ns–800ns2μs–3μs调制比范围0–100%0–100%载波比这个数字还需要和正弦表长度配合考虑。假如固定载波频率10kHz、输出频率50Hz那么一个正弦周期正好有200个载波周期相位步进就是256/2001.28个表项每周期这个小数必须用定点格式保存否则频率会漂。这个计算在第4章展开。3. 三相六路互补输出桥臂映射、死区插入与寄存器配置3.1 三对桥臂与六路PWM的映射相位序错一根线就是事故三相逆变器主电路是三个桥臂并联在母线上每个桥臂上下两个开关管。六路PWM的命名通常是A/A-、B/B-、C/C-其中“”表示上管“-”表示下管A、B、C三相相位互差120°。在带高级定时器的MCU里定时器的三个比较通道各有互补输出典型映射是CH1和CH1N驱动A相上下管CH2和CH2N驱动B相CH3和CH3N驱动C相。这个映射关系要在硬件设计阶段就定死画板的人和写固件的人必须共用同一张表格。每相占空比的计算公式是dutyA 0.5 0.5 * M * sin(theta); // A相0.5为静止电平占空比 dutyB 0.5 0.5 * M * sin(theta - 2.094); // 2.094 rad 120° dutyC 0.5 0.5 * M * sin(theta 2.094);M是调制比范围0到1。当M0时三相占空比全是50%桥臂输出的是等宽方波线电压为零M1时占空比在0到100%之间正弦摆动此时SPWM线性区达到上限。这里有一个新手容易搞混的点SPWM的相电压幅值最大是母线电压的一半M1时线电压有效值约等于0.612倍母线电压想继续提高电压利用率靠的是SVPWM或三次谐波注入而不是把M调到1以上。3.2 为什么死区必须存在从器件关断延迟算直通风险逻辑上互补输出保证了上下管指令不会同时为高但物理器件不是瞬间完成开关的。MOSFET有关断延迟IGBT有拖尾电流上管还没完全关断下管就已经开始导通母线就被直接短路这就是直通。直通电流上升速率由母线电压和回路杂散电感决定往往在几百纳秒内就超过器件承受能力炸模块通常就发生在这种时刻。死区的做法是在互补信号切换时让目标管延迟一段时间再开通。这段时间内两个管子都处于关断状态电流靠桥臂续流二极管续流。死区时间的选择要从器件数据手册的关断延迟出发常见做法是取关断延迟最大值的1.5到2倍同时留出驱动芯片的传播延迟余量。参考经验值如下开关器件类型典型死区范围说明低压MOSFET60V-100V200ns–500ns开关速度快死区可以很小高压MOSFET600V以上500ns–1.5μs米勒平台拉长了关断时间IGBT模块600V/1200V2μs–3μs拖尾电流明显死区不能太短注意死区不是越大越安全。死区期间相电压由电流续流方向决定不受PWM指令控制所以死区越长实际输出电压与指令电压的偏差越大。这个偏差在低频段尤其明显轻则电机噪声变大重则波形削顶。把死区压到器件允许的下限配合死区补偿才是正路。3.3 硬件死区配置最小代码与死区时间换算多数带高级定时器的MCU在硬件层面直接支持互补输出和死区插入配置正确后CPU不需要在中断里人为延迟死区由定时器硬件自动插入。这比软件死区可靠得多因为软件延时依赖中断响应时间一旦中断被抢占死区时间就跑偏了。我在所有正式方案里都用硬件死区。硬件死区的核心配置有三项输出极性、死区长度、空闲状态。下面是基于通用定时器外设的初始化伪代码具体寄存器名称以实际芯片手册为准/* 定时器初始化中心对齐三通道互补PWM硬件死区 */ #define TIM_CLK 90000000UL // 定时器时钟 90MHz #define PWM_FREQ 9000 // 载波频率 9kHz #define DEAD_TIME_NS 1200 // 目标死区 1200ns // 中心对齐模式下ARR对应半个载波周期 uint16_t arr TIM_CLK / PWM_FREQ / 2 - 1; timer_set_period(arr); // 死区计数值 时钟频率 × 时间 uint16_t dt_cnt (uint16_t)(TIM_CLK / 1000000UL * DEAD_TIME_NS / 1000); // 使能三通道PWM输出与互补输出 timer_ch_enable(CH1, PWM_CENTER_ALIGNED, OUTPUT_ACTIVE_HIGH); timer_ch_enable(CH1N, OUTPUT_ACTIVE_HIGH, dt_cnt); // 互补通道带死区 timer_ch_enable(CH2, PWM_CENTER_ALIGNED, OUTPUT_ACTIVE_HIGH); timer_ch_enable(CH2N, OUTPUT_ACTIVE_HIGH, dt_cnt); timer_ch_enable(CH3, PWM_CENTER_ALIGNED, OUTPUT_ACTIVE_HIGH); timer_ch_enable(CH3N, OUTPUT_ACTIVE_HIGH, dt_cnt);这段代码的关键点是中心对齐模式下ARR要按半个载波周期计算死区时间要换算为定时器计数个数。不同MCU的死区寄存器编码方式差异很大有的直接填计数值有的是非线性编码写驱动前务必查对应芯片的参考手册别对着别家代码硬套。输出极性也要和驱动电路匹配有的驱动芯片低电平有效有的高电平有效配反了会表现为一上电就炸管或电机抖动。我习惯把互补通道的极性和对应驱动芯片的输入逻辑列成一张表画板、写固件各留一份硬件和软件按同一张表核对能省掉大量联调时互相甩锅的时间。4. 调频调压恒压频比、正弦步进法与低频补偿4.1 恒压频比为什么调频必须同时调压交流异步电机的绕组阻抗随频率变化如果只改变输出频率而不改变电压低频时感抗很小同样电压下电流会失控磁路饱和电机很快过热。恒压频比控制的本质是保持气隙磁通近似恒定磁通与电压和频率的比值成正比因此让电压幅值跟随频率按比例变化电机在基频以下的任意频率都能输出额定转矩。V/F曲线的基准工作点是额定频率和额定电压。在额定频率以下调制比M按比例缩放M (Fout / Frated) * 100%; // 例50Hz额定25Hz时M50%这个公式只适合纯理论情况。实际系统中还有两个电压损失项死区时间带来的误差电压以及开关管和续流二极管的导通压降。低频时这两项占的比重很大如果不补偿电机会出现低频抖动、带载无力。所以工程上的V/F曲线在低频段会加入一段起始电压boost让频率接近0时电压不是0而是某个小值。4.2 正弦步进法用一个相位累加器实现平滑调频在整个调频调压方案里调频的核心机构是相位累加器。载波频率固定不变每个PWM周期更新一次三相占空比更新时相位累加器前进一个步进值步进大小决定输出频率。一个正弦周期由表长度N个点组成每PWM周期走过step个点那么输出频率为Fout step * Fpwm / N所以调频只需要改step这一个变量非常优雅。step通常不是整数以256点表、10kHz载波、50Hz输出为例每个PWM周期需要走1.28个点这个0.28的小数必须保留否则输出频率会偏差很大。我的做法是把step按Q15格式存储相位累加器用32位无符号整数查表时取高8位作为正弦表索引。如果输出频率从0.1Hz往上调步进值跨度很大但相位累加器可以连续工作不会因为频率切换产生相位跳变这对电机平滑启动很重要。启动瞬间从0Hz直接加速时相位是从累加器当前值继续走的不存在“从正弦表中间硬切”造成的冲击。4.3 调压实现V/F曲线查表、低频boost与占空比写入下面是每个PWM周期更新中断里的完整逻辑。这段代码同时完成相位累加、查表、调压、三相占空比输出中断里只做整数运算不做浮点除法/* PWM更新中断每个载波周期执行一次 */ void pwm_isr(void) { static uint32_t phase_acc 0; // 1. 相位累加step_q15 Fout * N * 32768 / Fpwm phase_acc step_q15; // 2. 查正弦表取高8位作为256点表索引 uint16_t idx (uint16_t)(phase_acc 8) 0xFF; int16_t sa sin_tbl[idx]; int16_t sb sin_tbl[(idx 85) 0xFF]; // 120度对应256/3≈85.3 int16_t sc sin_tbl[(idx 171) 0xFF]; // 240度 // 3. 从V/F曲线得到调制比单位千分比500表示0.5 uint16_t mod vf_get_mod(fout_hz); // 4. 计算占空比CENTERARR/2对应50%占空比 int32_t duty_a CENTER ((int32_t)sa * mod) / 2000; int32_t duty_b CENTER ((int32_t)sb * mod) / 2000; int32_t duty_c CENTER ((int32_t)sc * mod) / 2000; // 5. 写入比较寄存器预装载保证下一次更新生效 timer_write_ccr(CH1, (uint16_t)duty_a); timer_write_ccr(CH2, (uint16_t)duty_b); timer_write_ccr(CH3, (uint16_t)duty_c); }代码里几个常数要解释清楚。sin_tbl表项若用int16_t表示正弦值范围为0到32767但这里存的是有符号值sa的正负代表当前相位在半波的位置。调制比mod单位是千分比1000对应M1duty_a的计算里除以2000是因为sa的满幅是32768乘以mod/1000后要映射到占空比的半幅范围即0.5M对应到ARR的一半所以系数是2×1000。这样算出来的duty在0到ARR之间对应占空比0%到100%。偏移量85和171对应120度和240度这是256点表的整数近似实际相位误差约0.47度对电机驱动没有影响。vf_get_mod函数是V/F曲线的核心它按分段线性实现。我的典型配置是启动频率2Hz时输出电压为额定电压的3%到5%从2Hz到额定频率之间按线性比例额定频率以上保持最大调制比。低频boost的数值要谨慎给多了电机在低频段会过励磁、电流偏大给少了带载启动无力。这个值是在空载和满载两个极端下实测折中的没有通用最优解。5. 调频调压避坑死区、过调制与启动冲击的排查手册5.1 死区太大波形削顶、电机噪声变大现象空载运行时光听声音就觉得不对电机有持续的低频嗡嗡声用示波器看相电压波形发现正弦顶部不是光滑的圆弧而是有台阶状缺口母线电流波形上有毛刺。原因死区期间桥臂输出电压不受PWM指令控制而是由续流电流方向决定这相当于在每个开关周期注入一个误差电压脉冲。死区越长误差电压越大叠加到正弦波上就表现为顶部被削掉一块。调制比越接近1这种削顶越明显。解决把死区时间压到器件允许的下限前提是用示波器确认不会直通。我在MOSFET方案里曾经从1μs一路降到300ns噪声问题基本消失温升也没有明显变化。如果死区已经压到很低还是有削顶就要考虑做第6章的死区补偿了。5.2 死区太小桥臂直通炸管往往只在一瞬间现象上电瞬间或第一次给使能时母线电流检测立刻报过流驱动芯片的故障输出被拉低拆开功率模块检查上下管都短路了甚至驱动电阻都烧黑了。原因死区时间小于开关管的关断延迟。IGBT的拖尾电流会导致实际关断时间比数据手册标称值更长或者驱动电路本身存在不对称的传播延迟上管驱动关断慢了几百纳秒下管已经导通直通电流瞬间形成。解决先把死区设到器件关断延迟最大值的2倍以上跑通功能后再逐步缩小。每次缩小后带满载跑一段时间的温升和波形确认没有异常再继续降。驱动芯片的传播延迟不对称问题容易被忽略尤其使用光耦驱动的场合上下通道的光耦延迟可能差几百纳秒这部分也要算进死区余量里。我踩过一次把死区从2μs降到1μs后满载运行一切正常但把母线电压调高后就炸了后来查出来是光耦开关延迟在高电压下变差从此以后都会把死区余量多留一点。5.3 过调制把调制比超过1当成提压手段现象输出电压加到某个点就上不去了继续提高调制比输出电压几乎不变但波形顶部被削平输出电流变成尖顶波谐波剧增电机温升很快。原因SPWM线性区最大调制比就是1M超过1后正弦波峰值部分进入削平状态脉冲宽度饱和基波电压不再随M线性增长此时进入过调制区。过调制不是不能用但电压谐波会急剧增大电流畸变严重对电机和滤波器都不友好。解决调制比限幅到100%。想提高电压利用率正确的办法是换SVPWM算法它的线性区调制比可以到1.15比SPWM高出约15%。在不改主电路的前提下SVPWM是提高母线电压利用率最直接的手段代价是算法复杂度高一些需要重新做扇区判断和矢量作用时间计算。如果只是想做简单验证也可以先注入三次谐波把正弦波峰值压下来等效提高调制比上限但电机零序电流会变大需要谨慎评估。5.4 启动瞬间过流0Hz附近V/F曲线电压太低现象设了0Hz启动一给使能就报过流跳闸尤其是带载启动时几乎必跳空载时能转起来但电机在启动瞬间有明显抖动。原因0Hz时V/F曲线给出的是0V或极低电压电机无法建立足够的转矩转子不动定子电流迅速增大同时死区电压在低频段占比大实际输出电压和指令电压方向不一致电流波形畸变严重。解决设置启动频率常见做法是2Hz到5Hz起调V/F曲线在启动频率处给一个起始电压通常为额定电压的3%到5%。启动相位从0开始相位累加器在使能前清零让电机从一开始就按确定的方向受力避免初始相位不确定造成的冲击。如果是大惯量负载还需要配合S曲线加减速而不是直接设一个固定启动频率就完事。我的习惯是启动频率和boost电压做成两个可调参数在调试软件里留出接口上电试机时先空载调好再带载微调。5.5 载波比过小高频段波形明显变差现象低频段电流波形很干净把输出频率调到额定以上后相电流变成明显的锯齿状电机噪音变大高速时转矩不足。原因载波频率固定输出频率升高后载波比下降一个正弦周期里只有十几个PWM脉冲谐波分量大幅上升。这本质上是采样点数不足导致的波形失真不是控制参数的问题。解决三种思路。一是限制最高运行频率让载波比始终高于20二是提高载波频率但要注意死区占比和开关损耗同步上升需要重新评估温升三是分段切换载波频率低速用低载波频率减少开关损耗高速用高载波频率保证波形质量。第三种方案最灵活但切换载波频率的瞬间要重新计算ARR和死区计数值处理不好会产生频率跳变建议在验证充分后再上。6. 验证与进阶示波器查死区、频压比校验与补偿方向6.1 示波器看六路波形死区用延迟放大来看写完固件上电之前先用示波器确认六路PWM的逻辑这一步能避免炸管。把示波器两个通道分别接同一相的上管和下管驱动信号用上管的关断沿做触发时基放到200ns/div左右观察下管开通沿是否比上管关断沿晚了一个死区时间。如果下管提前开通说明死区配置没生效立即断电检查寄存器。测量时注意探头安全上下管的驱动信号都以各自源极或发射极为参考不能用普通探头跨接在母线之间测最好用差分探头或隔离通道。我见过有人用两个探头分别接地的方式测上下管波形结果探头地线把桥臂短路了直接放炮。另外测出来的死区时间要包含驱动芯片的传播延迟实测值和寄存器设定值差几百纳秒是正常的只要下管开通沿确实在上管关断沿之后就行。6.2 频压比校验从空载到满载的三段式调整调频调压参数是否合理最终要看输出电压和输出频率的关系是否符合预期。用电压表或功率分析仪测相电压有效值记录不同频率点下的实测值与理论V/F曲线对比。我习惯从空载开始按5Hz、10Hz、25Hz、50Hz四个点记录然后带额定负载再测一遍。低频段偏差大就调boost电压高频段偏差大就查死区补偿和母线电压。测试点空载实测满载实测理论值偏差处理5Hz记录记录额定×10%偏差大调低频boost25Hz记录记录额定×50%偏差大查死区补偿50Hz记录记录额定值偏差大查母线电压满载下如果5Hz电压偏低电机容易堵转要加boost但boost加多了空载电流会偏大所以这个值要在两个极端之间折中。调好后用热成像仪看功率模块的温度确认没有局部过热点。6.3 方向软件死区补偿在什么阶段值得做死区补偿的原理是检测电流方向在PWM占空比上反向叠加一个误差电压抵消死区造成的电压损失。代码逻辑很简洁/* 死区补偿按电流方向叠加补偿电压comp单位为占空比计数 */ if (phase_current 0) { duty_a comp; // 电流为正实际电压偏低补上去 } else { duty_a - comp; }但工程实现有几个前提必须有相电流方向信息而且方向判断不能错错了反而会加剧畸变电流传感器带低通滤波时相位延迟会导致过零附近判断方向滞后需要做滞回三相都要独立补偿计算量不大但调试量不小。我的观点是先把死区压到器件允许下限再评估要不要做软件补偿。如果电流波形仍然不理想再上补偿不迟。硬件死区压缩能解决80%的问题剩下20%才是死区补偿的发挥空间。更高的方向是换SVPWM。同一套调频调压框架把SPWM的占空比计算换成SVPWM的矢量作用时间计算电压利用率提高15%死区影响也会因为矢量合成方式的不同而减小。这个迁移不影响相位累加和V/F曲线的逻辑只改占空比计算部分。最后说一个习惯每次初始化定时器我都会把预装载、极性、死区、空闲电平四个配置项逐行核对一遍。之前有一次漏了预装载波形上出现周期性的毛刺电机抖动到怀疑人生查了两天才发现是CCR在周期中间被改写导致的。从那以后这四项成了我的固定检查清单。希望帮到你。本文还有配套的精品资源点击获取