STM32 HAL库实现SPWM正弦波生成:从原理到工程实践

📅 2026/7/29 4:13:46 👁️ 阅读次数 📝 编程学习
STM32 HAL库实现SPWM正弦波生成:从原理到工程实践

1. 项目概述与核心价值

最近在做一个需要精密交流信号源的小项目,手头正好有块STM32F103的开发板,就琢磨着能不能直接用MCU生成一个干净的正弦波。直接上DAC输出阶梯波当然可以,但想要波形质量好、频率稳定,还得上SPWM(正弦脉宽调制)配合滤波的方案。这听起来有点复杂,但用上ST官方力推的HAL库和图形化配置工具CubeMX,整个过程其实可以变得相当清晰和高效。最终,我成功实现了一个基于定时器输出SPWM波,经过简单LC滤波后,在负载上得到1kHz、失真度较低的正弦波的方案。这套方法不仅适用于信号发生器,在逆变器、电机驱动、音频D/A转换等需要高质量正弦波或可变频交流输出的场景里,都是非常核心的技术。

如果你也在为如何用STM32产生“像样”的正弦波而头疼,或者对SPWM的原理和HAL库下的实现感到好奇,那么这篇从原理到配置、再到代码调试的完整记录,应该能给你提供一个可以直接“抄作业”的参考。我会重点拆解为什么选SPWM、CubeMX里那些关键参数到底该怎么设、以及如何用最少的代码让定时器“吐”出我们想要的波形。

2. 核心思路与方案选型:为什么是SPWM?

在决定用STM32产生正弦波时,我们通常有几个备选方案:直接DAC输出、PWM滤波、以及SPWM滤波。每种方案都有其适用的场景和优缺点。

直接DAC输出是最直观的。STM32内部的DAC可以设置成直接输出一个电压值,我们只需要在定时器中断里,按照正弦函数计算下一个点的电压并刷新DAC寄存器即可。这种方法简单,但输出频率受限于中断速度和DAC的建立时间。对于1kHz的正弦波,一个周期至少需要几十个点才能保证波形不太畸形,这意味着中断频率要达到几十kHz。在F103这类主频72MHz的芯片上,虽然能做到,但会消耗可观的CPU资源。更关键的是,DAC输出的信号是阶梯状的,包含了丰富的高次谐波,即使后面接滤波电路,也很难完全滤除靠近基频的谐波,导致波形在过零点附近有畸变。

PWM滤波是另一种思路。我们产生一个固定占空比的PWM方波,经过低通滤波器后,可以得到一个直流电压,其值等于PWM的占空比乘以电源电压。如果想得到交流信号,就需要动态地改变这个占空比。这就是SPWM(Sinusoidal Pulse Width Modulation)的核心思想:用一个正弦波作为调制波,一个高频三角波作为载波,通过比较两者的瞬时值来产生一系列脉冲宽度按正弦规律变化的PWM波。这个PWM波经过一个截止频率介于载波频率和正弦波频率之间的低通滤波器后,高频的PWM载波成分被滤除,剩下的就是幅值和频率与调制波一致的正弦信号。

选择SPWM方案,我主要基于以下几点考量:

  1. 波形质量高:SPWM的本质是“能量等效”,理论上,只要载波频率足够高,滤波后得到的正弦波谐波失真(THD)可以做得非常低。这对于要求信号纯净度的应用至关重要。
  2. 效率与驱动能力强:SPWM信号可以直接驱动半桥或全桥的功率开关管(如MOSFET、IGBT),经过LC滤波后给负载供电,效率远高于线性放大方案。这对于逆变器、电机驱动等功率应用是唯一可行的选择。
  3. 充分利用硬件:STM32的定时器高级功能(如PWM输出、互补输出、带死区插入等)就是为这类应用设计的。使用硬件定时器产生PWM,不占用CPU计算资源,CPU只需要在必要时(如需要改变频率或幅值时)更新一下比较寄存器(CCR)的值即可。
  4. 灵活性好:通过软件改变正弦表数据或调制比,可以轻松实现输出电压幅值、频率的调节,甚至实现变频调速(VFD)等功能。

因此,对于目标“输出1kHz正弦波”,并且考虑到可能的扩展性(如调压、变频),SPWM滤波方案是一个在性能、资源占用和灵活性上取得很好平衡的选择。

3. 硬件设计与关键参数计算

在动手配置软件之前,必须先明确硬件电路,因为滤波器的参数直接决定了载波频率(即PWM频率)的选择。

3.1 系统框图与电路原理

整个系统的信号链非常清晰:

STM32定时器 -> SPWM波 (PIN) -> 低通滤波器 -> 纯正弦波 (负载)
  1. STM32:使用一个高级定时器(如TIM1或TIM8)的一个通道产生中心对齐的PWM波。
  2. 滤波电路:采用二阶LC低通滤波器。相比一阶RC滤波器,二阶LC滤波器具有更陡峭的滚降特性,能更有效地滤除载波频率及其谐波。一个典型的电路是在PWM输出引脚后,串联一个电感L,再并联一个电容C到地,负载接在电容两端。
  3. 负载:可能是示波器、后续电路,或者一个电阻。负载阻抗会影响滤波器的实际截止频率。

3.2 关键参数计算:载波频率与滤波器设计

这是整个项目的核心计算部分,参数选择是否合理直接决定最终波形的质量。

1. 载波频率(PWM频率)的选择:载波频率Fc需要远高于我们想要得到的正弦波频率Fsin(这里是1kHz)。通常,Fc至少是Fsin的20倍以上,才能保证滤波后的波形平滑。对于1kHz正弦波,我选择Fc = 20kHz。这个频率对于STM32的定时器来说很容易实现,同时也能被常见的LC元件有效滤波。

注意:载波频率也不是越高越好。过高的Fc会导致开关损耗增加(在功率应用中),并且对定时器的时钟源和分频器设置提出更高要求。20kHz是一个在音频范围以上、又不会对硬件造成太大压力的折中选择。

2. 每个正弦波周期的采样点数(N):这决定了我们正弦表的长度。N = Fc / Fsin = 20000 / 1000 = 20。这意味着,一个1kHz的正弦波周期内,我们的PWM占空比需要变化20次。用20个点来描绘一个正弦波,略显粗糙,可能会导致一些谐波失真。为了提高波形质量,我们可以增加点数。一个常用的技巧是,让定时器的更新频率(即载波频率Fc)是正弦波频率Fsin的整数倍K,同时K也是我们正弦表长度N的整数倍。这样每个正弦波周期内,占空比会均匀地变化N次。 我选择N = 100K = 20,那么Fc = Fsin * K * (N / N) = 1000 * 20 = 20kHz。这里K=20可以理解为“每个正弦波点重复输出的PWM周期数”。实际上,我们是在每个PWM周期(载波周期)都更新一次占空比,所以更新频率就是Fc。我们准备一个包含100个点的正弦表,每输出一个PWM脉冲,就切换到表中的下一个点。这样,输出100个不同占空比的PWM脉冲后,正好对应正弦波的一个完整周期。因此,Fsin = Fc / N = 20000 / 100 = 200Hz?等等,这里逻辑错了。

让我们理清关系:

  • 目标:Fsin = 1kHz
  • 设定:正弦表点数N = 100
  • 那么,为了在1秒内输出1000个正弦波,我们需要输出1000 * 100 = 100,000个不同的占空比值。
  • 这意味着,占空比的更新频率F_update必须为 100kHz。
  • 而占空比的更新,我们计划在每一个PWM周期(载波周期)开始时进行。因此,载波频率Fc必须等于更新频率F_update
  • 所以,Fc = Fsin * N = 1000 * 100 = 100kHz

结论:为了用100点的正弦表产生1kHz正弦波,我们需要将PWM的载波频率设置为100kHz。20kHz的假设需要修正。100kHz对于STM32F103(72MHz主频)来说仍然可行,但需要精心计算定时器分频。

3. 低通滤波器截止频率计算:滤波器需要滤除100kHz的载波及其谐波,同时保留1kHz的基波。截止频率Fcut应设置在两者之间。通常选择FcutFsin的5-10倍,同时远小于Fc。例如,选择Fcut = 10kHz。 对于二阶LC低通滤波器,其截止频率公式为:Fcut = 1 / (2π * sqrt(L * C))我们需要选择合理的L和C值。如果负载电阻为R(例如1kΩ),为了达到较好的匹配和阻尼,通常让滤波器的特征阻抗Z0 = sqrt(L/C)约等于负载电阻R,以避免谐振峰。 假设R = 1kΩFcut = 10kHz。 由Fcut = 1 / (2π * sqrt(LC))sqrt(LC) = 1 / (2π * Fcut) ≈ 1.59e-5Z0 = sqrt(L/C) = R = 1000L/C = 1e6联立两式:sqrt(LC) * sqrt(L/C) = L = 1.59e-5 * 1000 = 0.0159 H = 15.9 mHC = L / 1e6 = 0.0159 / 1e6 = 1.59e-8 F = 0.0159 µF ≈ 0.016 µF

实际中,15.9mH的电感比较大,不易获得且可能有较高内阻。我们可以调整Fcut或接受更低的特征阻抗。例如,选用更常见的电感值,如L = 1mH。 则C = 1 / ( (2π * Fcut)^2 * L ) = 1 / ( (2*3.14*10000)^2 * 0.001 ) ≈ 2.53e-7 F = 0.253 µF。可以选择标称值0.22µF或0.33µF的电容。 此时特征阻抗Z0 = sqrt(0.001 / 2.5e-7) ≈ 63.2 Ω,与1kΩ负载不匹配,滤波效果会打折扣,但对于演示和轻负载情况可以接受。更好的方法是使用运放构成的有源滤波器,但这里为了简洁,我们暂用LC无源滤波。

实操心得:滤波器设计是SPWM成败的关键。如果发现输出正弦波上有明显的高频毛刺(载波泄漏),多半是滤波器截止频率过高或阻尼不够。可以尝试增大电感或电容值(降低截止频率),或者在电感上串联一个小电阻(增加阻尼)。用示波器的FFT功能观察输出频谱,能非常直观地看到载波频率处的残余分量。

4. CubeMX工程配置详解

有了理论计算,我们就可以在CubeMX中进行具体配置了。这里以STM32F103C8T6和定时器TIM1的通道1(PA8引脚)为例。

4.1 时钟树配置

系统时钟是定时器计时的基准,必须首先配置正确。

  1. 在“Clock Configuration”标签页。
  2. 选择外部晶振(HSE)作为时钟源。假设板载8MHz晶振。
  3. 配置PLL:将HSE输入倍频。对于F103,系统时钟最高72MHz。我们可以设置PLL倍频因子为9,这样PLLCLK = 8MHz * 9 = 72MHz
  4. 将系统时钟源(SYSCLK)选择为PLLCLK,此时HCLK (AHB总线时钟) = 72MHz
  5. 高级定时器TIM1挂载在APB2总线上。确保APB2的预分频器(APB2 Prescaler)为1,即PCLK2 = 72MHz。TIM1的时钟来源于PCLK2

4.2 定时器TIM1配置

这是产生SPWM的核心。

  1. 在“Pinout & Configuration”标签页,左侧找到“Timers” -> “TIM1”。
  2. Clock Source:选择“Internal Clock”。
  3. Parameter Settings标签:
    • Counter Settings:
      • Prescaler (PSC): 这是定时器时钟的分频系数。定时器的实际计数时钟CK_CNT = PCLK2 / (PSC + 1)。我们的目标是产生Fc = 100kHz的PWM波。对于中心对齐模式,PWM频率Fpwm = CK_CNT / (ARR + 1)。我们先设定ARR。
      • Counter Mode: 选择“Center Aligned Mode 1/2/3”中的任意一个。中心对齐模式产生的PWM信号关于计数器中心对称,其谐波特性比边沿对齐模式更好,更适合用于功率转换和滤波。
      • Counter Period (ARR): 自动重装载值。这个值决定了PWM的分辨率。ARR越大,我们可以设定的占空比精度越高。但同时,为了达到固定的PWM频率,就需要更高的CK_CNT。我们需要权衡。假设我们选择ARR = 999,那么PWM的占空比分辨率是1/1000 = 0.1%。
      • 现在计算PSC:Fpwm = CK_CNT / (ARR + 1) = (PCLK2 / (PSC+1)) / (ARR+1)。 代入Fpwm=100kHz,PCLK2=72MHz,ARR=999。 得PSC + 1 = PCLK2 / (Fpwm * (ARR+1)) = 72e6 / (100e3 * 1000) = 0.72。 这显然不合理,PSC必须为整数。我们需要调整ARR。
      • 重新计算:令ARR = 719,则ARR+1=720PSC + 1 = 72e6 / (100e3 * 720) = 1.0。所以PSC = 0。 此时CK_CNT = 72MHz / (0+1) = 72MHz。 实际Fpwm = 72MHz / 720 = 100kHz。完美匹配。
      • Auto-reload preload: 使能(Enable)。这允许在下一个更新事件时才更新ARR的值,防止在计数中途改变周期导致不可预测的行为。
    • PWM Generation Channel 1:
      • Mode: 选择“PWM mode 1”或“PWM mode 2”。两者的区别是输出极性。我们选“PWM mode 1”,表示当计数器小于CCR时输出有效高电平。
      • Pulse (CCR1): 初始占空比,可以先设为ARR / 2 = 360
      • CH Polarity: 根据后续驱动电路决定。如果直接驱动滤波电路,高电平有效即可。
      • CH Idle State: 保持默认。
      • Fast Mode: 禁用。
      • CH Output: 使能(Enable)。
  4. NVIC Settings标签:使能“TIM1 update interrupt”和“TIM1 CC interrupt”。更新中断用于在每次PWM周期结束时(中心对齐模式是周期中心)更新占空比(CCR值)。CC中断在这里不是必须的,但开启也无妨。

4.3 GPIO配置

TIM1通道1对应的引脚PA8会自动被CubeMX配置为复用推挽输出(Alternate Function Push Pull)。通常无需额外修改,但可以检查一下输出速度(Speed),对于100kHz信号,选择“High”即可。

4.4 生成工程代码

点击“Project Manager”标签,设置好工程名称、路径、IDE(如MDK-ARM V5),然后点击“GENERATE CODE”。CubeMX会生成包含HAL库初始化的完整工程。

5. 软件代码实现与SPWM算法

打开生成的工程,我们需要在main.cstm32f1xx_it.c中添加核心逻辑。

5.1 生成正弦表

首先,我们需要一个正弦函数值表。这个表存储了在一个正弦周期内,不同相位点所对应的比较寄存器(CCR)值。CCR值决定了PWM的占空比。

/* Private variables ---------------------------------------------------------*/ #define SIN_TABLE_LEN 100 // 正弦表长度,与之前计算一致 #define PWM_PERIOD 720 // ARR+1的值,注意我们的ARR是719,所以周期是720 uint16_t sin_table[SIN_TABLE_LEN]; uint8_t sin_index = 0; /* Private function prototypes -----------------------------------------------*/ void generate_sin_table(void);

main()函数初始化部分,调用函数生成正弦表:

generate_sin_table(); HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); // 启动PWM输出 HAL_TIM_Base_Start_IT(&htim1); // 启动定时器更新中断

generate_sin_table函数的实现:

void generate_sin_table(void) { for (int i = 0; i < SIN_TABLE_LEN; i++) { // 计算0到2π之间的角度 float angle = 2 * 3.1415926f * i / SIN_TABLE_LEN; // 计算正弦值,范围[-1, 1] float sin_val = sinf(angle); // 将正弦值映射到PWM的CCR值范围。 // 我们希望正弦波幅值最大时,占空比为50% ± 调制深度。 // 设置调制深度为90%,即CCR在 (0.5 ± 0.45) * PWM_PERIOD 之间变化。 // 防止过调制(占空比0%或100%)导致波形削顶。 float modulation_depth = 0.45f; // 计算占空比:0.5 + 0.45 * sin(angle) float duty_cycle = 0.5f + modulation_depth * sin_val; // 将占空比转换为CCR值,并限制在安全范围内 uint32_t ccr_val = (uint32_t)(duty_cycle * PWM_PERIOD); if (ccr_val > PWM_PERIOD - 1) ccr_val = PWM_PERIOD - 1; if (ccr_val < 1) ccr_val = 1; // 避免完全关闭或完全开启,有助于滤波 sin_table[i] = (uint16_t)ccr_val; } }

注意:这里使用了浮点运算sinf,对于F103这类没有FPU的芯片,在中断中频繁计算会消耗大量时间。因此,最佳实践是在初始化时预先计算好正弦表并存入数组,中断中只需查表。这正是上面代码所做的。如果追求极致的速度和确定性,可以预先用电脑计算好表,直接以常量数组形式写在代码里。

5.2 定时器更新中断处理

每次PWM周期结束(中心对齐模式的中心点或端点,取决于配置)都会产生更新中断。我们在中断服务程序(ISR)中更新CCR值,以输出下一个正弦点。 在stm32f1xx_it.c中找到定时器更新中断向量TIM1_UP_IRQHandler,并在其中调用HAL库的中断处理回调函数。但更关键的是,我们要实现自己的更新逻辑。 我们可以在main.c中重写HAL库的更新中断回调函数:

/* Private user code ---------------------------------------------------------*/ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM1) { // 更新下一个点的CCR值 __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, sin_table[sin_index]); // 更新索引,准备下一个点 sin_index++; if (sin_index >= SIN_TABLE_LEN) { sin_index = 0; } } }

关键点解释

  • HAL_TIM_PeriodElapsedCallback是HAL库定义的弱函数,当定时器更新中断发生时,如果用户没有重写,它什么都不做。我们重写它来加入我们的逻辑。
  • __HAL_TIM_SET_COMPARE是一个宏,用于安全地设置指定定时器通道的比较寄存器(CCR)。它会在下一个PWM周期生效。
  • 我们在每个PWM周期更新一次CCR值。因为Fpwm = 100kHzSIN_TABLE_LEN=100,所以正弦波输出频率Fsin = Fpwm / SIN_TABLE_LEN = 1kHz,与设计目标一致。

5.3 调试与验证代码

main函数的while(1)循环中,我们可以添加一些调试代码,例如通过串口打印当前的索引和CCR值,或者用另一个GPIO引脚翻转来观察中断频率。

while (1) { // 可以添加其他逻辑,如通过按键改变频率或幅值 // 改变调制深度即可改变输出正弦波幅值 // 改变查表步长(如每次索引+2)即可改变输出频率(但会引入失真) HAL_Delay(1000); }

实操心得:调试时,首先不要接滤波器,直接用示波器观察PA8引脚的PWM输出。你应该能看到一个占空比在不断变化的方波。用示波器的测量功能,检查其频率是否为100kHz。然后打开示波器的持久显示(Persistence)或单次触发,观察占空比的变化是否呈现出一个完整的正弦包络。这是验证SPWM算法是否正确工作的第一步。

6. 滤波器搭建、测试与波形优化

软件调通后,就可以搭建硬件滤波器了。

6.1 无源LC滤波器搭建

根据之前的计算,我们暂定L = 1mHC = 0.22µF(或0.33µF)。

  1. 将电感串联在STM32的PA8引脚和负载之间。
  2. 将电容连接在负载两端(即电感之后,负载对地)。
  3. 负载可以使用一个1kΩ的电阻。
  4. 务必注意:电感应选择功率和电流合适的类型,普通工字电感或磁环电感均可。电容应选择高频特性好的瓷片电容或C0G/NP0材质的贴片电容,避免使用电解电容。

用示波器探头连接在负载电阻两端,观察波形。

6.2 典型问题与排查技巧

  1. 问题:输出正弦波幅值太小。

    • 排查:检查PWM的调制深度。在generate_sin_table函数中,modulation_depth决定了输出幅值。理论上最大为0.5(占空比从0%到100%变化),但为了避免过调制失真,通常设为0.45-0.48。尝试增大这个值。
    • 排查:检查滤波器负载。如果负载阻抗太小,会从电容吸收过多电流,导致电压被拉低。尝试增大负载电阻(如10kΩ)观察。
    • 排查:测量PWM引脚输出高电平的电压。确保是3.3V(或5V,取决于电路)。如果电压不足,可能是驱动能力问题,可以考虑在PWM输出后加一个缓冲器(如74HC04非门,接成同相缓冲)或MOSFET驱动芯片。
  2. 问题:正弦波顶部或底部有平坦现象(削顶)。

    • 原因:过调制。即正弦表计算出的CCR值超过了PWM周期(ARR)或低于0。我们的代码中已经做了限制(ccr_val > PWM_PERIOD - 1ccr_val < 1),但如果modulation_depth设置得过大,限制后的波形就会在峰值处被削平。
    • 解决:降低modulation_depth值。
  3. 问题:正弦波上有明显的高频纹波(毛刺)。

    • 排查:这是载波(100kHz)没有被完全滤除的表现。首先用示波器FFT功能确认毛刺频率是否为100kHz或其倍频。
    • 解决
      • 调整滤波器参数:尝试增大电感或电容值,以降低滤波器的截止频率。例如,将电容增加到0.47µF或1µF(注意电容类型)。
      • 增加滤波器阶数:使用两级LC滤波器(π型滤波器),滤波效果会更好。
      • 检查布线:滤波电路应尽量靠近MCU引脚,引线要短,避免引入额外的寄生电感和电容。
      • 使用有源滤波器:如果对波形质量要求极高,可以考虑使用运放(如LM324、TL084)搭建二阶或多阶有源低通滤波器(如Sallen-Key或MFB结构),其性能优于无源滤波器,且不受负载影响。
  4. 问题:正弦波频率不准。

    • 排查:用示波器精确测量输出正弦波的周期。理论上应为1ms。
    • 原因1:系统时钟不准。检查外部晶振是否起振,CubeMX中时钟树配置是否正确。
    • 原因2:定时器分频计算错误。回顾第4.2节的计算,确认PSCARRSIN_TABLE_LENFsinFpwm的关系。
    • 原因3:中断处理时间过长。如果中断服务程序执行时间超过了PWM周期(10µs),会导致某些更新事件被错过,从而降低有效频率。确保中断函数尽可能短小,只做最基本的查表和写寄存器操作。避免在中断中使用HAL_Delay或进行复杂计算。
  5. 问题:波形失真,不是完美的正弦波。

    • 排查:除了削顶和纹波,还可能因为正弦表点数太少(SIN_TABLE_LEN太小),导致阶梯感明显。增加SIN_TABLE_LEN可以改善,但需要同步提高Fpwm以维持Fsin不变,否则频率会降低。这需要重新计算定时器参数,可能受限于定时器最大计数频率。
    • 高级技巧——对称规则采样法:我们目前使用的是自然采样法的查表实现,即每个PWM周期均匀地取正弦表中的一个点。在低载波比(Fc/Fsin)下,这种方法会产生低次谐波。一种改进方法是规则采样法。它不是在每个三角载波周期自然比较,而是以固定的时间间隔(通常是载波峰值或谷值时刻)对正弦调制波采样,并保持这个值在整个载波周期内不变。这种方法产生的谐波主要集中在载波频率整数倍附近,更容易被滤除,且计算量小。在中断中,我们可以不简单地顺序查表,而是根据一个累加的相位角(phase_accumulator)来查表,这样可以更灵活地实现频率变化。

7. 性能提升与扩展思路

基础功能实现后,可以考虑以下优化和扩展:

  1. 动态调频与调幅

    • 调频:不改变PWM载波频率Fc,而是改变查表的速度。例如,在中断中,索引不是固定+1,而是根据一个频率控制字phase_inc来增加:sin_index += phase_inc。当phase_inc=1时,输出基频Fsin;当phase_inc=2时,输出频率翻倍。但这样会跳过一些表项,可能引入失真。更好的方法是使用直接数字频率合成(DDS)的思想,使用一个32位的相位累加器,用其高几位作为正弦表的索引。通过改变相位增量值,可以实现高分辨率、无失真的频率调节。
    • 调幅:动态修改modulation_depth变量,并重新计算或缩放整个正弦表。更高效的方法是在中断中,将查表得到的CCR值乘以一个幅值系数(0.0~1.0),再写入CCR寄存器。注意结果不能超过ARR。
  2. 使用DMA自动更新CCR:为了进一步解放CPU,可以使用DMA(直接存储器访问)来搬运正弦表数据到定时器的CCR寄存器。可以配置DMA循环模式,在每次定时器更新事件时触发一次DMA传输,自动将数组中的下一个值送到CCR。这样CPU完全不用干预,可以处理其他任务。这在需要产生多路同步SPWM(如三相逆变器)时尤其有用。

  3. 死区时间插入:如果驱动的是半桥或全桥电路,上下管需要互补的PWM信号,并且中间必须插入死区时间(Dead Time)防止上下管直通短路。STM32的高级定时器(TIM1/TIM8)硬件支持带死区插入的互补PWM输出。在CubeMX中配置通道为“PWM Generation CHx and CHxN”,并设置“Dead Time”参数即可。

  4. 闭环控制:如果需要稳定的正弦波输出电压(如在线式UPS),可以在输出端加入电压采样电路(如互感器+运放调理+ADC),MCU通过ADC读取实际输出电压,与期望的正弦表参考值进行比较,通过PID算法动态调整调制深度(或直接调整CCR值),实现闭环稳压。这构成了一个数字式的正弦波逆变器核心。

这个项目从原理分析、参数计算、到CubeMX配置、代码编写、硬件调试,完整地走通了一个基于STM32 HAL库的SPWM正弦波发生器。它不仅仅是一个简单的信号源,其背后涉及的定时器高级应用、调制原理、滤波器设计和数字控制思想,是电力电子、电机驱动、音频处理等众多领域的通用基础。希望这份超详细的拆解,能帮你不仅做出这个波形,更能理解每一个环节背后的“为什么”。