
拿到STM32C542R之后很多人的下一步不是点灯而是输出PWM。原因很简单LED亮度调节、蜂鸣器发声、电机调速、舵机控制、Buck电源稳压甚至D类功放背后都是PWM。PWM不像串口那样要配协议也不像ADC那样要担心采样时序它就是把高低电平按一定规律输出看似简单实际调的时候却经常出问题。我看过不少初学者的调试现场波形是有但频率和预期差一个数量级占空比调了没反应或者程序一跑引脚却没有输出。问题大多不是芯片坏了而是ARR、PSC、CCR这三个寄存器的关系没有真正理解。这篇文章就用STM32C542R跑通PWM输出重点说明如何修改频率与占空比。这套思路在STM32全系列都通用C542R的具体时钟和外设映射以STM32CubeMX实际选项为准。文章会从这几个角度展开先讲清楚PWM的核心原理和三个寄存器的关系然后带你在STM32CubeMX里完成定时器配置并生成HAL库工程接着给出完整的PWM输出、动态修改频率和占空比的代码最后补充波形验证方法、常见问题排查表以及工程最佳实践。1. 这篇文章真正要解决的问题PWM本身不难难的是“想要的波形”和“寄存器配置”之间的对应关系。很多初学者是这样配置的在STM32CubeMX里打开某个定时器看到PWM Generation Channel把Prescaler填一个数把Counter Period填一个数生成代码后一跑发现引脚确实有波形了。但频率对不对占空比是不是自己想要的心里并没有底。更麻烦的是想在程序运行中修改占空比或频率就不知道该改哪个寄存器也不知道改了之后会不会产生异常波形。这篇文章要解决的正是这几个典型问题PWM频率、占空比分别由谁决定它们之间的公式是什么。在STM32CubeMX中如何配置一个PWM输出通道参数怎么填。用HAL库启动PWM后如何在main函数中动态修改占空比。如何在运行中修改PWM频率同时不破坏当前的占空比比例。如何用示波器或逻辑分析仪验证波形判断配置是否真的正确。适合看这篇文章的读者主要有两类。第一类是刚接触STM32、之前只在开发板上点过灯现在需要输出可调波形的人第二类是从STM32F1或F4迁移到STM32C5系列想快速确认HAL库和CubeMX配置思路有没有变化的人。如果你已经在做电机控制或数字电源这篇文章也可以帮你把PWM配置部分的基础打牢方便后面扩展死区、互补输出、故障保护等功能。一个额外的判断是STM32C5系列虽然属于新一代产品内核换成了Cortex-M33但从定时器PWM的使用方式看和传统STM32非常一致。这意味着你以前积累的PWM调试经验在C542R上依然有效不需要重新学习一套定时器体系。这对迁移用户来说是比较友好的。2. PWM基础概念与STM32定时器原理2.1 什么是PWMPWMPulse Width Modulation脉冲宽度调制叫“脉宽调制”本质上是一串周期性的高低电平信号。每个周期内高电平持续的时间称为脉宽高电平时间占整个周期的比例就是占空比。比如一秒钟重复1000次的方波如果高电平时间为0.5ms周期为1ms那占空比就是50%。PWM有两个核心参数频率和占空比。频率决定了控制对象响应多快占空比决定了输出平均能量的大小。用一个生活中的类比你用手电筒快速反复开关频率很低时人眼能感觉到闪烁频率足够高时人眼只感觉到亮度变化亮度高低就取决于“亮的时间占比”也就是占空比。2.2 为什么修改占空比能改变输出电压在功率电子领域PWM最典型的应用就是Buck降压电路。Buck电路在连续导通模式下输出电压约等于输入电压乘以占空比即 Vout ≈ Vin × D。也就是说只要改变占空比就能改变输出平均电压。这也是很多热词中关于“为什么修改占空比可以改变输出电压”的答案。占空比从20%升到80%输出电压平均值也随之升高。实际电路中会有开关管导通压降和电感电阻带来的损耗所以不是精确的线性关系但整体趋势就是如此。理解这一点你就能明白PWM绝不只是“让LED呼吸一下”的小功能它直接决定了电机转速、电源输出、加热器功率这些生产级控制对象。2.3 STM32定时器如何产生PWMSTM32的定时器产生PWM靠的是“计数比较”机制。定时器内部有一个计数器从0向上计数到ARRAuto-Reload Register自动重载寄存器然后归零重新开始。在计数过程中如果计数值到达CCRCapture/Compare Register捕获比较寄存器输出引脚就会翻转或保持指定电平。具体到PWM Generation模式引脚在高电平或低电平期间的变化方式取决于输出极性和模式配置。大多数场景使用向上计数模式计数器从0增长小于CCR时输出有效电平达到CCR后输出无效电平计到ARR后归零进入下一个周期。因此有三个寄存器在起作用PSCPrescaler预分频器对定时器输入时钟分频决定计数一次需要多长时间。ARRAuto-Reload Register决定计数周期也就是PWM的频率。CCRCompare Register决定比较点也就是PWM的占空比。公式如下计数时钟频率 定时器时钟 / (PSC 1)PWM频率 计数时钟频率 / (ARR 1)占空比 CCR / (ARR 1) × 100%注意ARR寄存器的实际值是“计数值”而完整的计数周期是ARR 1。很多资料为了书写方便习惯直接写作周期 ARR实际严格计算时要加1。当ARR比较大时误差很小但初学时最好把公式记准确。2.4 三个寄存器的作用对比参数作用对PWM的影响调整方式PSC定时器时钟分频频率粗调启动前配置运行中较少修改ARR计数周期频率精调同时影响占空比分辨率运行中可修改建议开预装载CCR比较匹配值直接决定占空比运行中可随时修改占空比的分辨率由ARR决定。如果ARR是99那么占空比有100档最小步进1%如果ARR是999那就有1000档最小步进0.1%。所以需要精细调节占空比时ARR要适当大一些代价是同样频率下需要更高的定时器时钟支持。2.5 预装载机制STM32定时器在很多寄存器上引入了预装载机制也就是“影子寄存器”。直接操作PSC、ARR、CCR这些寄存器时写入的新值可能不会立刻生效而是在特定的更新事件到来时才被加载。CubeMX中对应Auto-reload preload和Pulse预装载的选项。实际项目里建议始终开启ARR和CCR的预装载。原因很直接如果不开启运行中修改ARR或CCR计数器可能在一个周期的任意位置突然跳到目标频率产生一个不完整的脉冲。在电机驱动或电源控制中这种异常脉冲可能引起电流尖峰。开启预装载后参数在下一个周期边界统一更新波形切换更平滑。3. STM32C542R开发环境与准备工作3.1 硬件准备本文以STM32C542R开发板为例可以用带ST-Link的NUCLEO开发板也可以用独立ST-Link配合最小系统板。准备以下硬件STM32C542R开发板一块本文示例假设板载ST-Link调试器。示波器或逻辑分析仪用于观察PWM波形。没有示波器时可以用逻辑分析仪查看频率和脉宽但电压幅度观察不如示波器直观。一个LED灯或蜂鸣器方便在没有示波器的情况下验证PWM确实有输出。如果使用LED验证占空比频率不要太高。1kHz以下能明显看到亮度变化10kHz以上人眼几乎看不出差异。蜂鸣器则相反需要几kHz的频率才能听到声音这时反而更适合听频率变化。3.2 软件准备软件工具链如下STM32CubeMX用于图形化配置引脚、时钟、定时器并生成初始化代码。版本以你安装的为准本文重点演示通用配置思路。集成开发环境KEIL MDK、IAR Embedded Workbench或STM32CubeIDE均可。不同IDE生成的代码框架略有差异但main.c中的HAL库代码逻辑一致。STM32CubeProgrammer备用用于烧录和查看芯片状态。本文代码基于STM32 HAL库。如果你用的是LL库底层API不同但定时器PWM原理一样。3.3 定时器时钟来源在开始配置之前先建立一个关键认知CubeMX生成的时钟树中定时器输入的时钟频率不一定等于系统主频。SYSCLK经过AHB分频后得到APBx时钟APBx再经过预分频后得到定时器时钟。在STM32CubeMX中可以直接看到每个定时器所挂的时钟频率不需要手动计算倍频关系但你需要记住这个频率是多少因为后面计算PSC、ARR时要用到。本文后续的代码示例假设定时器时钟为80MHz。如果你的工程里定时器时钟不是80MHz按公式反算参数即可。4. 使用STM32CubeMX配置PWM输出4.1 新建工程并选择芯片打开STM32CubeMX在MCU Selector中搜索STM32C542R选中对应型号新建工程。如果开发板自带示例工程也可以直接在示例基础上修改能少配置一部分时钟树。4.2 配置RCC和时钟树在System Core中选择RCC将HSE设置为Crystal/Ceramic Resonator如果开发板有外部晶振。然后进入Clock Configuration页面将系统时钟配置到适合工作的频率。具体主频以当前芯片手册为准一般STM32C5系列支持较高主频但配置时不要超过CubeMX给出的上限。时钟树配置完成后建议在左侧或者右侧Info栏中查看TIMx的时钟频率并把这个数值记录下来。4.3 选择定时器并开启PWM在左侧Categories中选择Timers打开某个定时器例如TIM3。选择Channel1或其它通道在Mode下拉框中选择PWM Generation CHx。此时右侧会出现对应的引脚映射。如果引脚被其他外设占用CubeMX会提示冲突可以换一个定时器或通道。在定时器配置面板中按以下方式填写Prescaler (PSC)预分频填入79。Counter Mode选择Up。Counter Period (ARR)自动重载值填入999。Auto-reload preload选择Enable。PulseCCR填入500。CH Polarity选择High。CH Mode通常选择PWM mode 1。按照前面公式定时器时钟为80MHzPSC79那么计数时钟为80MHz / (791) 1MHz。ARR999计数周期为1000PWM频率为1MHz / 1000 1kHz。CCR500占空比为500 / (9991) 50%。如果计时器时钟不是80MHz请根据实际频率反算PSC和ARR。关键是先把PWM频率定在1kHz附近方便后续验证。4.4 查看GPIO配置定时器通道一旦配置CubeMX会自动把对应引脚设置为复用模式。你可以在GPIO设置里确认引脚的复用功能是否正确。一般情况下不需要手动修改GPIO配置CubeMX生成的代码已经完整。4.5 生成工程在Project Manager中设置工程名称和路径选择工具链为MDK-ARM或STM32CubeIDE生成代码。生成的工程中定时器初始化函数通常命名为MX_TIM3_Init并已在main函数开头调用。此时工程里已经包含PWM输出所需的底层初始化但PWM并没有真正启动。下一步要在用户代码中调用启动函数并加入修改频率和占空比的逻辑。5. HAL库PWM输出完整代码实现5.1 启动PWM输出生成代码后在main函数的while循环之前调用定时器PWM启动函数/* 文件路径Core/Src/main.c */ HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1);这一步的作用是启动定时器的PWM通道让引脚开始输出波形。很多初学者忘记调用这一步结果应用代码写了半天引脚上始终没有信号。如果确认GPIO配置无误但引脚没有波形第一件事就是检查是否调用了HAL_TIM_PWM_Start。启动函数可以放在while循环之前。如果你希望定时器开启后立即输出波形就放在初始化完成后如果需要按键或其他事件触发后再输出也可以放在事件处理函数中。5.2 最小验证代码生成代码后main函数里增加如下代码让LED或蜂鸣器立刻体现PWM输出效果。/* 文件路径Core/Src/main.c */ int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM3_Init(); /* 启动PWM输出通道1 */ HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1); while (1) { /* 占空比 250 / 1000 25% */ __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, 250); HAL_Delay(500); /* 占空比 750 / 1000 75% */ __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, 750); HAL_Delay(500); } }这段代码每500ms改变一次占空比。如果你接的是LED会看到亮度交替变化如果接的是蜂鸣器音量和响度会改变如果用示波器观察能看到脉宽在25%和75%之间切换。__HAL_TIM_SET_COMPARE是一个HAL库宏用于直接设置CCR寄存器。它等价于写TIMx-CCR1但比直接寄存器操作更可读也避免在不同系列之间移植时修改寄存器地址。5.3 动态修改PWM频率修改PWM频率的常见做法是修改ARR寄存器。但是直接改ARR有一个容易踩的坑如果你只改ARR而不改CCR占空比的百分比会变化。举个例子原配置ARR999CCR500占空比50%。现在把ARR改成499CCR仍然是500占空比就超过100%了从真实物理波形看引脚会一直输出高电平或者被硬件钳制到100%占空比。这样频率确实变了但输出状态已经完全不是你预期的。更好的做法是修改ARR时按比例同步修改CCR保持占空比不变。代码如下/* 文件路径Core/Src/main.c */ #define PWM_TIMER_CLK 80000000UL /* 定时器时钟按实际工程修改 */ #define PWM_PSC 79U /* 与CubeMX配置保持一致 */ static uint16_t current_duty_x1000 500U; /* 当前占空比单位0.1% */ void pwm_set_freq_hz(uint32_t freq_hz) { uint32_t new_arr; uint32_t old_arr; uint32_t new_ccr; if (freq_hz 0U) { return; } /* 计算新的ARR */ new_arr PWM_TIMER_CLK / ((PWM_PSC 1U) * freq_hz) - 1U; /* 保存旧ARR用于按比例调整CCR */ old_arr __HAL_TIM_GET_AUTORELOAD(htim3); /* 先限制一下避免频率设置过高导致ARR过小 */ if (new_arr 10U) { new_arr 10U; } /* 按占空比千分比重新计算CCR */ new_ccr (uint32_t)((uint64_t)new_arr * current_duty_x1000 / 1000U); __HAL_TIM_SET_AUTORELOAD(htim3, new_arr); __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, new_ccr); }这段代码里current_duty_x1000表示占空比的千分比数值。比如500表示50%250表示25%。这种做法比直接用百分比精度更高而且避免浮点运算。__HAL_TIM_GET_AUTORELOAD用于读取当前ARR值__uint64_t强制转换是为了防止乘法溢出。5.4 修改占空比并保持频率不变修改占空比只需要修改CCR。为了保证占空比不超过100%建议先读取当前ARR然后按比例计算CCR。/* 文件路径Core/Src/main.c */ void pwm_set_duty_percent(uint32_t percent) { uint32_t arr; uint32_t ccr; if (percent 100U) { percent 100U; } arr __HAL_TIM_GET_AUTORELOAD(htim3); ccr (uint32_t)((uint64_t)arr * percent / 100U); __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, ccr); }这个函数的思路很直接先算出当前周期值ARR然后乘以目标百分比得到CCR。相比手动指定一个固定CCR这种方式在ARR改变后依然能正确设置占空比不会出现占空比超范围的问题。5.5 完整演示轮换多个频率和占空比把上面的函数组合起来可以做一个更直观的演示。程序每隔800ms切换一组PWM参数示波器上能看到不同的频率和占空比组合。/* 文件路径Core/Src/main.c */ #include stdint.h /* 定时器时钟和预分频定义 */ #define PWM_TIMER_CLK 80000000UL #define PWM_PSC 79U static uint16_t current_duty_x1000 500U; void pwm_set_freq_hz(uint32_t freq_hz) { uint32_t new_arr; uint32_t old_arr; uint32_t new_ccr; if (freq_hz 0U) { return; } new_arr PWM_TIMER_CLK / ((PWM_PSC 1U) * freq_hz) - 1U; old_arr __HAL_TIM_GET_AUTORELOAD(htim3); if (new_arr 10U) { new_arr 10U; } new_ccr (uint32_t)((uint64_t)new_arr * current_duty_x1000 / 1000U); __HAL_TIM_SET_AUTORELOAD(htim3, new_arr); __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, new_ccr); } void pwm_set_duty_percent(uint32_t percent) { uint32_t arr; uint32_t ccr; if (percent 100U) { percent 100U; } current_duty_x1000 percent * 10U; arr __HAL_TIM_GET_AUTORELOAD(htim3); ccr (uint32_t)((uint64_t)arr * percent / 100U); __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, ccr); } int main(void) { uint32_t freq_tab[] {1000U, 2000U, 5000U, 10000U}; uint32_t duty_tab[] {20U, 40U, 60U, 80U}; uint8_t idx 0U; HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM3_Init(); HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1); while (1) { pwm_set_freq_hz(freq_tab[idx]); pwm_set_duty_percent(duty_tab[idx]); idx; if (idx 4U) { idx 0U; } HAL_Delay(800); } }这段代码在运行中不断修改ARR和CCR覆盖了1kHz到10kHz的频率范围以及20%到80%的占空比范围。在示波器上观察每800ms波形就会改变一次。有一点需要说明这里的pwm_set_duty_percent里将current_duty_x1000更新为percent*10是因为当前设计以千分比存储而percent是百分比。这样pwm_set_freq_hz在计算CCR时能保持当前占空比。实际项目中如果并不需要交叉修改频率和占空比可以简化设计只用一个全局变量保存当前占空比。5.6 代码运行逻辑说明这段演示代码的核心不是单个函数而是“修改ARR时必须同步修改CCR”这个工程意识。频率变化本质上是改变计数周期而占空比的“百分比”必须相对新的周期重新计算否则波形就会超出预期。这里真正容易踩坑的地方是如果你的ARR预装载没有开启修改ARR的瞬间计数器可能正处于一个周期中间此时新ARR会立即生效产生一个不完整的周期脉冲。CubeMX中Auto-reload preload选项务必保持Enable。同理CCR的预装载也建议开启这样占空比的变化在下一个周期才开始生效避免产生异常窄脉冲。6. 运行结果与波形验证6.1 使用示波器验证把示波器探头接到PWM输出引脚接地夹子接开发板GND。启动程序后如果配置正确示波器上会显示稳定方波。验证步骤如下先按一下示波器的Auto Set让波形自动居中。读取频率读数对比程序里设置的频率。测量高电平脉宽计算占空比。切换程序中的参数观察示波器读数是否同步变化。以初始配置为例定时器时钟80MHzPSC79ARR999PWM频率应该是1kHz。占空比初始值由CCR决定如果CCR500占空比为50%。6.2 逻辑分析仪验证如果没有示波器逻辑分析仪也是可选的验证工具。逻辑分析仪通常能准确测量频率和脉宽但采样率要足够高。一般用20MHz以上采样率的逻辑分析仪观察1kHz到10kHz的PWM没有问题。连接逻辑分析仪的通道到PWM引脚打开逻辑分析仪软件设置合适采样率抓取波形后测量高电平时间和周期同样可以计算频率和占空比。6.3 判断成功的标准判断PWM输出是否正常可以从几个角度确认频率读数是否符合预期误差通常在1%以内。占空比读数是否接近目标值比如50%的占空比实测在49%到51%之间都是正常的。切换参数后波形是否平滑变化没有出现明显乱码或周期突然跳变。如果输出频率和预期差很多先检查定时器时钟频率是否和你计算时假设的一致。CubeMX时钟树里看到的TIMx时钟才是公式里的TIM_CLK。不要直接用系统主频去算除非CubeMX显示定时器时钟就等于主频。7. STM32C542R PWM常见问题与排查方法下面整理一份常见问题排查表这些问题在初学者项目中出现频率很高。问题现象可能原因排查方式解决方案引脚没有任何波形没有调用HAL_TIM_PWM_Start检查main函数中是否启动PWM在while循环前调用启动函数引脚没有任何波形定时器通道选择错误检查CubeMX中PWM配置的通道确认启动函数和CubeMX配置的是同一通道引脚没有任何波形GPIO被其他外设占用检查CubeMX资源配置换用其他定时器或通道有波形但频率不对定时器时钟和预期不一致检查CubeMX时钟树中TIMx时钟按实际TIM时钟重新计算PSC和ARR修改占空比没反应CCR修改位置错误或未执行查看代码执行路径确认__HAL_TIM_SET_COMPARE被调用修改占空比没反应CCR预装载未开启修改被忽略检查CubeMX中的Pulse预装载开启预装载或重新初始化修改ARR后波形异常ARR预装载未开启检查CubeMX中的Auto-reload preload开启预装载修改ARR后占空比异常修改ARR后未同步修改CCR检查CCR计算逻辑按比例重新计算CCR高占空比时LED闪烁PWM频率太低检查PWM频率提高PWM频率到1kHz以上额外补充一个场景如果你在调试时修改了CubeMX配置记得重新生成代码。有时候手动改了main.c里的代码又重新生成工程新代码会覆盖main.c中的用户代码区域之外的内容。CubeMX对用户代码的保护区域有特殊标记自定义函数最好放在USER CODE BEGIN和USER CODE END之间或者另建一个.c文件管理。否则下次重新生成工程时你的函数可能丢失。8. 工程最佳实践与进阶方向8.1 参数宏定义与模块化PWM参数不要散落在main.c的各个角落。建议把定时器时钟、预分频值、默认频率、默认占空比统一收拢成宏定义放到头文件或专门的pwm_config.h中。这样当硬件频率变化时只需要改一个头文件而不是满工程搜索数字。更进一步可以把PWM操作封装成独立模块对外只暴露pwm_init、pwm_set_freq_hz、pwm_set_duty_percent几个接口。这样底层用的是TIM3还是TIM4上层应用完全不用关心测试和维护都方便很多。8.2 合理设计占空比分辨率不要一上来就把ARR设得很大。ARR越大占空比档位越多但PWM频率受限于定时器时钟。设计时先确定PWM频率需求再反推PSC和ARR的组合。比如需要20kHz PWM定时器时钟80MHz那么计数脉冲周期是4us。如果PSC0计数时钟80MHzARR3999占空比分辨率是1/4000也就是0.025%。如果这个精度够用就选这个组合如果不够就需要考虑更高的定时器时钟或降低PWM频率。8.3 修改频率时保持占空比稳定的必要性在电机控制或Buck电源中运行中切换PWM频率并不常见更常见的需求是切换占空比来调速调压。但如果你确实需要修改频率建议先计算新频率下的ARR再按当前占空比重新计算CCR最后同时写入两个寄存器。不要先改ARR再改CCR中间那个短暂状态会输出错误的占空比。8.4 高级定时器的互补PWM与死区如果你做的是电机驱动或半桥电路通常需要高级定时器输出互补PWM。比如STM32的TIM1、TIM8这类高级定时器支持互补输出和死区插入功能。死区的作用是防止上下桥臂同时导通避免直通短路。在CubeMX中配置PWM Generation CHx和CHxN后可以在Break and Dead Time设置中调整死区时间。死区时间一般根据MOS管或IGBT的关断延迟来确定典型值从几百纳秒到几微秒不等。8.5 PWM触发ADC采样在电源控制中经常需要让ADC在特定时刻采样避免开关噪声。标准做法是用定时器的PWM输出比较事件或更新事件触发ADC采样。这样采样点相对PWM信号的位置是固定的可以避开开关瞬间的电压尖峰。CubeMX中可以在ADC配置的Trigger处选择某个定时器的事件实现硬件自动联动不需要软件干预。8.6 故障保护与刹车输入电机驱动或电源电路中PWM故障保护很重要。STM32高级定时器支持刹车输入功能外部故障信号到来时硬件可以立即将PWM输出置为安全状态比如高电平、低电平或高阻态不需要软件参与。这个机制反应速度远快于中断也不依赖CPU是否在执行其他任务。实际项目中建议在调试阶段先验证故障保护逻辑再接入真实负载。8.7 测试环境注意事项不要一开始就把PWM直接接到电机或电源上测试。先用示波器确认波形再接入负载并且尽量使用限流电源。PWM频率和占空比配置不当可能让电机啸叫、过流或者让电源输出异常电压。调试阶段先空载确认波形无误后再逐步加大负载这套习惯能省下很多时间。9. 总结与后续学习方向这篇文章从PWM的频率和占空比公式讲起落到STM32C542R上的实际配置和代码核心是把PSC、ARR、CCR三个寄存器的分工以及运行中修改参数的方法讲清楚。你只要掌握了“PSC决定计数时钟、ARR决定频率、CCR决定占空比修改ARR时必须同步修改CCR”这个主线PWM的大部分坑都能避开。下一步的实践建议是先跑通文章里的最简单示例用示波器或逻辑分析仪验证1kHz、50%占空比波形然后把演示代码改成轮换频率和占空比的版本观察波形变化最后再把PWM接到LED或蜂鸣器上体会频率和占空比分别影响什么。到这一步PWM基础就算扎实了。如果想继续深入可以按这个顺序学习先掌握高级定时器的互补PWM和死区配置然后研究PWM触发ADC采样最后实现一个简单的电机调速或数字电源控制。这些扩展都建立在PWM基础之上有了这篇文章的配置基础和代码框架再去看参考手册和具体例程就不会摸不着头脑了。