尧图精选

STM32三相SPWM实现:定时器参数、死区与互补输出详解

🕒 发布时间:2026/9/16 2:22:49 📁 来源:尧图网络
简介围绕STM32实现三相SPWM波输出的工程资料包面向嵌入式开发和电机控制方向学习者适用于逆变器、交流电机驱动等电能转换场景。资料系统梳理了SPWM的关键环节高级定时器PWM模式配置、死区时间设定、三相相位互差120度的实现、三角载波与正弦参考波的比较逻辑并涉及中断处理与参数实时更新便于理解从原理到工程落地的完整链路。压缩包共174个文件以C语言源文件.c/.h为主同时包含编译中间文件.o/.d、Keil工程配置与链接脚本以及可烧录的hex文件总大小约2.81MB目录结构完整方便快速定位到核心代码与生成文件。目前已有564人学习下载。通过该资源使用者可获得一个可运行的三相SPWM输出工程范例结合代码与编译信息可快速对照学习定时器初始化、占空比调节等关键配置对于需要开展变频调速、并网逆变器等项目开发的工程师也是一份有参考价值的二次开发底座。1. 三相SPWM不是“算出来”的是配合定时器“排出来”的三相SPWM的核心不是sin()函数而是定时器。A、B、C三相各需要一个互差120°的正弦调制波三路PWM共用同一个三角载波STM32的高级定时器TIM1恰好能输出三对互补PWM直接驱动三相全桥的六个开关管。大多数从网上下载的“基于STM32的三相SPWM波输出.rar”工程包代码逻辑并不复杂真正容易翻车的是载波频率、死区时间、更新中断频率和引脚复用这几处定时器参数。与其逐行读代码不如先把定时器行为吃透再回头对照工程里的寄存器配置。这篇文章按载波参数、工程结构、关键代码、双极性驱动、验证移植的顺序展开让新手半小时跑起来也让熟手能定位波形畸变的根源。2. 三相SPWM的载波频率与STM32定时器参数先设对ARR和PSC2.1 载波频率决定波形质量10kHz是默认起点载波频率就是PWM频率SPWM本质上是用高频三角载波去切割正弦调制波。STM32用定时器计数来形成三角波计数溢出频率由预分频器PSC和自动重载值ARR共同决定。中心对齐模式下计数器从0加到ARR再从ARR减到0一个完整PWM周期对应两次计数所以PWM频率公式是f_pwm f_clk / (PSC 1) / (2 * (ARR 1))系统时钟72MHz时目标10kHz载波可以取PSC71、ARR99。这里有个经验值载波频率越高电流波形越平滑但开关管损耗线性上升。三相异步电机调速常用10kHz到16kHzIGBT模块用8kHz到10kHzMOSFET方案可以上20kHz。做毕业设计或初学者验证10kHz最稳妥因为一个50Hz正弦周期内正好有200个载波周期波形阶梯感小示波器上观察也直观。目标载波频率系统时钟PSCARR每个工频周期载波数8kHz72MHz894916010kHz72MHz714920012kHz72MHz594924016kHz72MHz4449320调制深度m的定义是正弦波峰值与载波幅值之比在定时器里体现为CCR的最大值与ARR的比值。m接近1.0时输出线电压利用率最高但会进入过调制区产生明显削顶。工程上一般把最大CCR限制在0.9倍ARR左右留出死区时间和母线电压波动的余量。2.2 中心对齐模式与PWM1模式三角载波怎么从定时器来STM32通用定时器和高级定时器都支持中心对齐模式。以标准外设库为例TIM_TimeBaseInitTypeDef里TIM_CounterMode选TIM_CounterMode_CenterAligned1或CenterAligned2对应CMS位01/10。需要注意CMS位只控制更新事件在向上计数还是向下计数时产生不影响比较输出中心对齐PWM波形的对称性始终存在。输出极性上SPWM工程最常用PWM1模式也就是计数值小于CCR时输出有效电平。配合TIM_OCPolarity_HighCCR值越大高电平脉宽越宽占空比与CCR值单调对应。如果选PWM2模式整个逻辑反向初学者套公式时会搞混建议固定用PWM1。提示中心对齐模式下若CMS11向上和向下计数都会产生更新事件中断频率是载波频率的两倍。要让中断频率恰好等于载波频率用CMS01或10即可具体见3.3节。2.3 死区时间与互补输出不设死区就是炸管三相全桥同一桥臂上下管不能同时导通否则电源直通。STM32高级定时器在互补输出时可以在两路信号之间插入死区时间这个值写在TIM1的BDTR寄存器里。死区时间太小会直通太大则波形畸变明显输出相电压在过零点附近出现平台。死区时间估算看功率器件的关断延迟。IGBT模块一般2us到3us低压MOSFET驱动电路0.5us到1us就够。以STM32F103在72MHz系统时钟下为例DeadTime字段单位是DTS周期约13.9ns写入144对应2us。标准库初始化代码如下TIM_BDTRInitTypeDef BDTR; BDTR.OSSRState TIM_OSSRState_Enable; BDTR.OSSIState TIM_OSSIState_Disable; BDTR.LOCKLevel TIM_LOCKLevel_OFF; BDTR.DeadTime 144; // 144 * (1/72MHz) 2us BDTR.Break TIM_Break_Disable; BDTR.BreakPolarity TIM_BreakPolarity_High; BDTR.AutomaticOutput TIM_AutomaticOutput_Enable; TIM_BDTRConfig(TIM1, BDTR);这段代码里OSSRState决定主输出在空闲状态时是否仍保持通常使能OSSIState关闭让空闲时输出高阻Break禁用不用刹车引脚。这里最关键的DeadTime值换到不同主频的芯片时必须重新算不能直接照抄。提示凡是用了TIM1或TIM8的任何输出都必须调用TIM_CtrlPWMOutputs(TIM1, ENABLE)打开主输出MOE位否则IO口上一路PWM都不会有。3. 解析.rar工程包文件结构、正弦表与更新中断3.1 先确认芯片型号与固件库再找main入口网上流传的“基于STM32的三相SPWM波输出.rar”通常包含Keil工程、标准外设库或者HAL库源码、说明文档。打开工程的第一件事不是看代码而是确认芯片型号。同样一份工程STM32F103ZET6和STM32F103C8T6的TIM1通道引脚一致但Flash容量和启动文件不同如果是F407的工程高级定时器映射引脚有变化直接烧到F103上不会有输出。Keil里芯片型号不匹配时Options for Target里改型号之后还要重新选择正确的启动文件并核对系统时钟初始化函数里的外部晶振频率。固件库风格也要先分清。标准外设库的初始化代码长这样GPIO_InitTypeDef、TIM_TimeBaseInitTypeDef配合RCC_APB2PeriphClockCmd。HAL库则完全不同MX_GPIO_Init和MX_TIM1_Init都由CubeMX生成回调函数HAL_TIM_PWM_PulseFinishedCallback刷新占空比。如果原来工程是标准库写的没必要强行改成HAL库维持原风格反而出错少。Keil打开工程后依次检查三处Target选项里的芯片型号、C/C选项里的Define宏STM32F10X_HD还是STM32F10X_MD、工程文件树里的启动文件。这三处不一致编译能过但运行诡异比如定时器不进中断。3.2 生成三相正弦表120°相位差在表索引里实现查表法是三相SPWM最常见的实现方式。载波10kHz、调制波50Hz时一个工频周期正好200个采样点。A、B、C三相的差异不是算出来的是同一张正弦表错开索引位置得到的。相位差120°对应表长的三分之一所以B相起点在表的第67个点C相在133个点取整后实际相位差略有偏差但对电机运行影响很小。生成正弦表可以在PC上用Python完成import math TABLE_SIZE 200 # 一个工频周期的采样点数对应10kHz载波 ARR 99 # 定时器自动重载值 MAX_CCR int(ARR * 0.9) # 调制度0.9防止过调制削顶 phases [0, 2*math.pi/3, 4*math.pi/3] for phase in phases: table [] for i in range(TABLE_SIZE): duty 0.5 0.5 * math.sin(2*math.pi*i/TABLE_SIZE phase) ccr int(duty * MAX_CCR) table.append(ccr) print(, .join(map(str, table)))这段脚本输出的数组直接粘贴到C源文件里即可。MAX_CCR按0.9倍ARR取值防止正弦波峰值处占空比达到100%否则出现过调制输出波形顶部被削平。假如死区时间较大还要把MAX_CCR再调小一点因为死区占用的是有效脉宽。有了表之后工程里一般会建三个数组或一个二维数组存放三相数据。二维数组在一级缓存较小的MCU上会多占一点RAM但代码更清晰const uint16_t spwm_table[3][TABLE_SIZE] { { 44, 46, 48, ... }, // A相 { 67, 69, 71, ... }, // B相索引起点偏移 { 89, 90, 92, ... } // C相 };3.3 在更新中断里刷新CCR中断频率与查表方式的取舍查表之后的问题是什么时候把表中的值写入比较寄存器。常用做法是在定时器更新中断里按顺序递增索引取出三相CCR值调用TIM_SetCompare1/2/3。下面是这个逻辑的核心volatile uint16_t pwm_index 0; void TIM1_UP_IRQHandler(void) { if (TIM_GetITStatus(TIM1, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM1, TIM_IT_Update); TIM_SetCompare1(TIM1, spwm_table[0][pwm_index]); TIM_SetCompare2(TIM1, spwm_table[1][pwm_index]); TIM_SetCompare3(TIM1, spwm_table[2][pwm_index]); pwm_index; if (pwm_index 200) pwm_index 0; } }注意中心对齐模式CMS01时更新事件只在向上计数结束产生中断频率等于载波频率10kHz每100us进入一次三次数组访问耗时极短对CPU占用率几乎无感。若把CMS配置成了11更新事件在上下计数都产生中断频率变成20kHz必须在TIM_TimeBaseInit之后用TIM1-RCR 1让每两次更新产生一次中断频率回到10kHz。中断刷新方式的极限在于如果载波频率继续提高比如电机控制用到20kHz同时系统还在跑编码器采样和通信协议中断内要做的事情变多CPU会吃紧。此时可以改用DMA把正弦表放到内存用定时器更新事件触发DMA搬运到TIM1的CCR寄存器三个通道可以由一条DMA流依次完成。不过DMA方式对表和重载值的排列有要求中小型项目直接用中断查表即可。固件库工程里还有一个细节TIM1是高级定时器NVIC配置要同时使能TIM1_UP_IRQn。有些工程里输出正常但频率不对多半是中断没开或者更新标志被其它代码清掉了。4. 三相互补输出接线与双极性SPWM的驱动差异4.1 三相全桥六个管脚CH1/CH1N/CH2/CH2N/CH3/CH3N分配三相SPWM最终要驱动三相全桥逆变器一共六个开关管。STM32F103的TIM1刚好提供三路输出和三路互补输出对应关系如下定时器通道STM32F103引脚连接对象TIM1_CH1PA8A相上桥臂TIM1_CH1NPB13A相下桥臂TIM1_CH2PA9B相上桥臂TIM1_CH2NPB14B相下桥臂TIM1_CH3PA10C相上桥臂TIM1_CH3NPB15C相下桥臂PCB上通常是上下桥臂分别接电源正和电源负电机三相绕组接在两个桥臂中点。有刷电机或无刷电机的驱动板走线不同但STM32这一端永远是六路PWM输出。接线之前先看原理图确认驱动芯片输入的信号极性有的驱动电路要求高电平关断需要把PWM极性反过来。GPIO配置里要注意复用推挽和重映射。F103上TIM1默认使用PA8/PA9/PA10和PB13/14/15部分板子做了重映射需要检查AFIO的重映射寄存器。GPIO模式设成AF_PP复用推挽之后IO方向交给定时器代码里不要再调用GPIO_SetBits操作这些引脚。4.2 为什么三相全桥只能用双极性SPWM单极性SPWM常见于单相逆变器正半周只有一组开关在动作另一组保持导通开关损耗低但输出含有较大的低频分量。三相全桥不能这样做因为三个桥臂必须共用同一条母线各相电位互相牵制每一相都需要在正负母线之间连续切换才能在电机绕组上得到对称的三相线电压。所以三相SPWM实际运行的必然是双极性方式。双极性SPWM的每个PWM周期里上管和下管各动作一次输出电压在正负母线电压之间来回翻转。载波频率越高输出电流波形越接近正弦但开关损耗和死区影响同步变大。死区导致输出电压误差与负载电流方向相关电流为正时误差使输出电压减小电流为负时误差使输出电压增大。低速工况下这种误差会引起电流过零点畸变电机噪音明显。想改善就得做死区补偿实时采样相电流方向在CCR值上叠加或减去死区对应的计数值。STM32内部没有专门的死区补偿硬件代码里要自己判断电流过零这也是SPWM项目进阶的常见方向。4.3 别忘了MOE主输出使能高级定时器和通用定时器的差别用TIM3写PWM例程的时候初始化完直接就有波形。换成TIM1很多新手会在这一步卡住代码一模一样示波器就是没有输出。原因是TIM1/TIM8这类高级定时器多了一个主输出使能位MOE位于BDTR寄存器中系统复位后默认关闭。标准库中必须调用TIM_CtrlPWMOutputs(TIM1, ENABLE);这一行放在TIM_BDTRConfig之后、使能定时器之前。工程中如果找不到这一行PWM是永远不会出来的。除此之外TIM1初始化顺序建议为先开RCC时钟再配GPIO复用然后初始化时基、输出比较、互补输出和死区最后使能CCx和CCxN输出通道。顺序颠倒会引起寄存器写入失效比如死区还没配置就先开了主输出死区值可能会被覆盖。提示MOE位还受刹车输入影响。工程里Break没有使能时对应引脚不能接地否则MOE被硬件清零PWM瞬间消失且不会自动恢复。顺带提一句SPWM是变频调速的基础但电压利用率只有约86%。同样一个三相全桥想提高直流母线利用率下一步值得研究SVPWM。SVPWM通过八个基本电压矢量合成期望电压矢量代码复杂度高一些但波形谐波更小是商业变频器的主流方案。这里先用SPWM把定时器、正弦表、死区这些底子打好后续切SVPWM时复用TIM1的配置框架即可。5. 三相SPWM怎么验证以及换板子时改哪些参数5.1 示波器三步验证频率、削顶、相序写完整段代码下载到板子之后建议按下面顺序验证波形。第一步看单路PWM频率。探头接PA8与GND观察PWM频率是否等于设计载波频率10kHz。如果频率偏离检查PSC和ARR如果完全没有波形先查MOE和互补输出使能。第二步看调制度是否削顶。把探头改为交流耦合观察PA8输出PWM脉冲宽度是否周期性变化。把时基拉到20ms左右看到正弦包络后测量包络峰值若顶部明显削平说明MAX_CCR取值过大或母线电压不足以支撑当前调制深度。减小MAX_CCR系数即可。第三步看三相相序。A、B、C三路分别接示波器CH1、CH2、CH3触发CH1测量CH2与CH3相对CH1的相位差应约为120°和240°。如果观察到的相序反了把B相和C相的索引起点交换即可。没有示波器时可以用Proteus仿真观察六路PWM不过Proteus对TIM1死区和更新中断的时序仿真与实际芯片有差异只能验证逻辑不能验证死区波形。另一个低价方案是用逻辑分析仪采样率在50MHz以上的型号能看清死区时间是否插入正确。用逻辑分析仪看PB13与PA8正常情况应看到两路波形互补且中间有一小段时间都是低电平这段低电平就是死区。5.2 换芯片、改载波频率时只需动这几个参数工程换到另一块STM32板子上不需要重写全部代码但有四个地方必须重新计算。第一个是系统时钟。外部晶振8MHz或25MHz会改变PLL配置进而影响APB2时钟和TIM1时钟PSC和ARR都依赖这个时钟数值。先用RCC_GetClocksFreq读取TIM1时钟频率再反推PSC和ARR。第二个是死区时间。换芯片后DTS可能不同DeadTime字段按新DTS重新计算。例如原来72MHz下填144得到2us换成84MHz的F407之后同一字段对应的时间是1.7us左右需要按84MHz重新算。第三个是正弦表点数。把载波频率从10kHz改成20kHz、调制波频率保持50Hz时一个周期采样点数变成400。此时要重新生成数组并且把更新中断里的TABLE_SIZE同步改掉否则索引溢出导致输出波形周期性错乱。第四个是引脚复用重映射。不同封装或不同板卡对TIM1_CH1N等引脚的引出位置不同GPIO初始化只改引脚号还不行部分型号还需要配置AFIO重映射寄存器。查芯片数据手册的Alternate Function Mapping表确认所有六路输出实际复用位置后再接线。调试工具推荐用STMStudio或Keil的Variable窗口监视pwm_index变量的变化范围。正常情况应从0轮转到199后清零若观察到跳变或回绕异常优先查中断里是否有其它代码修改了该变量。控制类项目里三相PWM输出往往要和ADC采样联动比如逐波限流需要每个载波周期采样一次电流此时建议把采样触发信号取自TIM1的更新事件或TRGO引脚确保采样点落在PWM中心而不是在主中断里用软件延时采样那样采到的电流时刻会随负载波动电流环PI参数很难调稳。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →