最近被问得最多的一个问题:STM32的TIM1明明照着别的定时器例程抄了一遍,引脚上就是测不到PWM波形,是不是芯片坏了?每次遇到这种问题,我基本都能猜到原因——不是芯片坏了,是把TIM1当成普通定时器用了。TIM1和TIM8是高级控制定时器,多出来一堆普通定时器没有的控制逻辑,少配置一步,输出就是静悄悄的。
这篇文章我会用标准库把TIM1输出PWM的完整流程拆开讲,从时钟树、GPIO复用模式到时基配置,再到那个最容易漏掉的主输出使能(TIM_CtrlPWMOutputs),每一步都解释清楚为什么要这么写,最后把我在项目里踩过、以及帮别人排查过的各种坑整理成一篇避坑清单。代码是完整的,直接复制就能跑,适合刚入门的小白,也适合从HAL库转回标准库做电机控制的工程师。
1. 为什么要认真对待TIM1和标准库
1.1 TIM1不是普通的定时器
很多教程讲STM32的PWM输出,会拿TIM2、TIM3举例。这些通用定时器也能输出PWM,配置起来也确实简单。但如果你只会在通用定时器上输出PWM,碰到电机驱动、数字电源这类项目时会很吃力,因为这些应用真正需要的不是“一个方波”,而是一整套波形安全控制机制,那正是TIM1这种高级控制定时器的看家本领。
TIM1和TIM8在STM32里单独归为一类,叫高级控制定时器(Advanced-control timer)。它们比普通定时器多出三样核心硬件能力:
- 互补输出:同一个PWM信号可以同时输出到两个引脚,一路正相、一路反相,专门用来驱动H桥或者三相桥的上下桥臂。
- 死区插入:正反两路信号切换的间隙里,自动插入一段两路都为低电平的时间,防止上下桥臂瞬间直通烧管子。
- 刹车功能:外部故障信号一来,硬件立刻封锁PWM输出,不需要软件干预,响应速度是微秒级的。
普通定时器根本没有这些机制。你想用TIM3模拟互补PWM和死区,靠GPIO翻转加延时是做不出来的,中断延时的抖动足以让功率器件冒烟。所以只要涉及电机功率驱动,基本绕不开TIM1/TIM8。
1.2 这个场景为什么选标准库而不是HAL库
这些年HAL库越来越流行,CubeMX点点点就能生成工程,确实方便。但在TIM1这种高级定时器的配置场景下,我个人还是推荐标准库,原因很直接:
标准库的API基本和寄存器一一对应,每个函数配置哪个位、操作哪个寄存器,看代码一眼就能明白。比如TIM_CtrlPWMOutputs这个函数,操作的就是BDTR寄存器里的MOE(主输出使能)位。而HAL库里这层逻辑被封装得更靠上,很多人出了问题根本不知道往哪查。尤其当你需要精确操作死区时间、刹车极性、锁定级别这些寄存器细节时,标准库写起来更痛快——数据手册里说设置DTG位段,标准库代码就对应TIM_BDTRInitStructure.TIM_DeadTime,一行对一行,不容易绕晕。
另外一个现实原因是存量代码。手头大部分电机控制项目、竞赛代码、老工程师的参考例程,都是标准库写的。现在项目维护、代码移植,还是绕不开标准库。用HAL库重新写一遍,费时费力不说,还容易在移植过程中把时序细节改出错。
1.3 TIM1最常见的三大实际应用场景
TIM1的PWM输出在日常项目里主要分三类用法:
- 电机调速类:直流电机用单路PWM配合H桥驱动,舵机用50Hz周期PWM控制角度,无刷电机用三组互补PWM做六步换相或者FOC。这类应用占TIM1使用场景的大多数。
- 信号调制类:驱动无源蜂鸣器发声、LED调光、加热器功率控制。这类场景对占空比精度有一定要求,但对死区和刹车不敏感,属于TIM1的基础用法。
- 电源变换类:同步Buck、Boost、推挽、半桥/全桥等拓扑,需要两个互补PWM带死区,配合刹车保护,TIM1简直就是为这个设计的。
把这三种场景归纳一下,你会发现它们有一个共同点:不只是要“有波形”,还要波形可靠、可控制、可保护。这也正是TIM1比普通定时器值钱的地方。
2. 标准库配置TIM1 PWM输出,一步一步来
2.1 时钟树:TIM1的72MHz是怎么来的
配置PWM频率之前,先得搞清楚TIM1的输入时钟是多少。以最常见的STM32F103系列为例,系统时钟一般由外部8MHz晶振经过PLL倍频到72MHz。STM32内部有两条外设总线APB1和APB2,APB1最高36MHz,APB2最高72MHz。TIM1挂在APB2上。
这里有个非常容易理解错的地方:APB2预分频器对定时器时钟有自己的一套规则。当APB2分频系数为1(即不分频)时,TIM1的时钟就是APB2的时钟,也就是72MHz。当APB2分频系数大于1时,TIM1内部会自动把APB2时钟乘以2,所以即便APB2被分频到36MHz,TIM1最终拿到的时钟依然是72MHz。
简单记忆:在常见的72MHz主频配置下,不管APB2怎么分频,TIM1的输入时钟基本就是72MHz。F407略有不同,它的APB2最高84MHz,TIM1时钟在分频系数大于1时取APB2的两倍,也就是168MHz。写代码前先确认目标芯片的定时器时钟,后面所有PWM频率计算都基于这个数字。
2.2 GPIO:复用推挽不是推挽
定时器波形通过引脚输出时,必须把引脚配置成复用功能模式。对于F103来说,TIM1_CH1默认复用引脚是PA8,TIM1_CH1N是PB13,CH2是PA9,CH2N是PB14,CH3是PA10,CH3N是PB15,CH4是PA11。这些默认复用映射在绝大多数F103封装上都是固定的,不需要开启重映射。
GPIO的模式要选GPIO_Mode_AF_PP,也就是复用推挽输出,不是普通的GPIO_Mode_Out_PP。这两个模式在标准库里是两个完全不同的配置路径。普通推挽输出模式下,引脚状态由ODR寄存器控制,定时器外设根本接管不了引脚;而复用推挽模式下,引脚控制权才会交给片内外设。很多新手把定时器配置得毫无问题,但GPIO模式选错,引脚始终是死的,自然测不到波形。
另外别忘了打开GPIO的时钟。代码里用到的引脚如果涉及重映射,还要额外开启AFIO时钟。开时钟这件事放在所有配置的最前面,和TIM1时钟一起用RCC_APB2PeriphClockCmd打开。
典型GPIO配置代码段如下:
GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure);2.3 时基和输出通道:频率、占空比的计算公式
TIM1要输出PWM,需要配置两个核心单元:时基单元(Time Base)和输出比较通道(OC Channel)。
时基单元决定了计数器的计数周期,配置项包括自动重装载值ARR、预分频系数PSC、计数模式和时钟分割。PWM频率的计算公式是:
PWM频率 = TIM时钟 / (PSC + 1) / (ARR + 1)
占空比 = 通道比较值CCR / (ARR + 1)
这里的PSC和ARR都是16位寄存器,取值范围0到65535。举个例子,如果TIM时钟是72MHz,想要输出1kHz的PWM,可以设置PSC=71、ARR=999。计算过程:72MHz除以72再除以1000,正好1000Hz。如果此时CCR=500,占空比就是500/1000,也就是50%。
这里有个细节值得留意:ARR=999时,计数器计数值从0到999,一共1000个计数周期。占空比的分子是CCR的值,不是CCR加一。用公式时注意别把ARR+1和CCR+1混在一起,不然算出来的占空比会差一个计数脉冲,频率高的场景下误差被放大,波形看着总是不对劲。
时基单元的初始化代码如下:
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Period = arr; // ARR, 自动重装载值 TIM_TimeBaseStructure.TIM_Prescaler = psc; // PSC, 预分频系数 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 时钟分割, 一般填0 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数 TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);输出比较通道负责把计数器和CCR比较的结果映射成引脚电平。PWM模式1的定义是:计数器计数值小于CCR时输出有效电平,大于等于CCR时输出无效电平。如果选择PWM模式2,逻辑正好反过来。电机控制里绝大多数场景用PWM模式1,配合TIM_OCPolarity_High的极性设置,直观理解就是“CCR越大,高电平时间越长,占空比越大”。
2.4 主输出使能:高级定时器和普通定时器的分水岭
TIM1和普通定时器在配置流程上最大的区别就在这里。普通定时器初始化完时基和输出通道,调用TIM_Cmd使能定时器,引脚上就有PWM了。TIM1不行,它还有个叫主输出使能(MOE)的门控位,在BDTR寄存器里。MOE不置1,即使计数器在跑、CCR在变,四个通道的PWM也全都不会送到引脚上。
标准库把这一位封装成了TIM_CtrlPWMOutputs函数:
TIM_CtrlPWMOutputs(TIM1, ENABLE);这一行是TIM1配置里绝对不能漏的。我排查过的那么多“TIM1没输出”问题里,大概有一半就是漏了这一句。剩下的才是GPIO配置、时钟分频等问题。
和主输出使能一起提的还有预装载功能。TIM_OC1PreloadConfig和TIM_ARRPreloadConfig分别控制CCR1寄存器和ARR寄存器有没有预装载。使能预装载后,修改CCR或ARR不会立刻生效,而是等下一个更新事件(计数器溢出)统一加载。如果不使能预装载,运行中修改CCR可能造成PWM波形中出现一个异常宽或异常窄的脉冲,在电机控制这种场景里是致命的。
2.5 完整可复制代码:初始化加动态调占空比
把上面这些串起来,写一个完整的初始化函数。这个函数传两个参数:arr和psc,分别对应自动重装载值和预分频系数,方便在main里灵活设置频率。
void TIM1_PWM_Init(u16 arr, u16 psc) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; // 1. 开启TIM1和GPIOA的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 | RCC_APB2Periph_GPIOA, ENABLE); // 2. 配置PA8为复用推挽输出, 用于TIM1_CH1 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // 3. 配置时基单元 TIM_TimeBaseStructure.TIM_Period = arr; TIM_TimeBaseStructure.TIM_Prescaler = psc; TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); // 4. 配置输出比较通道为PWM模式1 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable; TIM_OCInitStructure.TIM_Pulse = 0; // 初始占空比为0% TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High; TIM_OC1Init(TIM1, &TIM_OCInitStructure); // 5. 使能预装载, 避免运行中改参数产生毛刺 TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM1, ENABLE); // 6. 高级定时器特有: 使能主输出 TIM_CtrlPWMOutputs(TIM1, ENABLE); // 7. 启动定时器 TIM_Cmd(TIM1, ENABLE); }main函数里可以这样动态调整占空比。下面这版是个呼吸灯的效果,占空比从0逐渐加到最大再降回来,实际跑的时候LED亮度会有明显的渐变过程,非常适合第一次验证PWM是否正常工作:
int main(void) { u16 duty = 0; s8 dir = 1; TIM1_PWM_Init(999, 71); // 初始化TIM1, PWM频率为1kHz while (1) { TIM_SetCompare1(TIM1, duty); duty += dir; if (duty >= 999) dir = -1; if (duty <= 0) dir = 1; Delay_ms(3); } }这里有几个点要提醒一下。TIM_SetCompare1函数实际修改的是TIM1->CCR1寄存器,参数类型是u16,最大值不要超过ARR的值。如果设置的duty大于ARR,输出就会变成常高电平,波形直接失真,别问我怎么知道的。另外,占空比到达0时我把方向标志切回来,但没让PWM彻底关闭,这是因为占空比0和PWM关闭在行为上有一点区别——前者引脚固定低电平,后者引脚可能处于高阻态。在驱动电机时,这两种状态对电路的影响完全不同,实际项目里要根据场景明确区分。
3. 避坑实录:TIM1 PWM高频翻车现场
3.1 没输出,先查这四处
TIM1配置完成后引脚没波形,这是最常见的故障现象。我按排查优先级列一下原因:
- MOE位没置1。这个前面强调过了,TIM_CtrlPWMOutputs(TIM1, ENABLE)漏掉的话,其他全对也没用。
- GPIO模式选错。复用推挽AF_PP写成了普通推挽Out_PP,定时器信号没法输出到引脚。
- 定时器没启动。TIM_Cmd(TIM1, ENABLE)漏掉,计数器根本不跑。
- 输出通道没使能。TIM_OCInitStructure里的TIM_OutputState没置ENABLE,通道被关着。
这四个问题分别对应BDTR寄存器、GPIO配置、CR1寄存器、CCER寄存器。排查的时候不用凭感觉猜,打开调试器直接看这几个寄存器的值,一目了然。
3.2 频率差一倍或者差很多:时钟倍频规则
现象:明明按公式算好了PSC和ARR,示波器测出来的频率却是理论值的两倍,或者完全对不上。
频率是理论值两倍,大概率是APB2分频器规则没吃透。有的工程里SystemInit被改动过,或者移植代码时把系统时钟配置换了一套,APB2分频系数不是1,定时器时钟自动乘2,频率自然翻倍。这种问题隐蔽性很强,因为从RCC配置代码上看,系统时钟依然是72MHz,但APB2分频路径已经不同。
频率差得离谱,还要检查外部晶振频率。很多开发板用的12MHz晶振,如果SystemInit里的PLL配置是按8MHz写的,实际主频会变成108MHz而不是72MHz,全部外设的时钟基准都会偏。遇到这类问题,先停一下,用示波器测一下MCO引脚输出的系统时钟,确认基准频率没问题,再往下查定时器。
3.3 占空比调了没反应或者波形毛刺多
原因一般出在预装载上。没有使能TIM_OC1PreloadConfig时,修改CCR1会立即生效。如果修改的时机不凑巧,正好赶上计数器跳到CCR附近,PWM脉冲宽度就会出现一个突变的毛刺。在示波器上看,波形整体占空比好像是变了,但某些周期明显异常。
还有一种情况是CCR写错通道了。比如初始化用TIM_OC1Init配的CH1,main里却用了TIM_SetCompare2去改CH2的CCR。这时CH1的占空比当然不会有任何变化。检查这种问题最快的办法,就是在调试器里查看TIM1->CCR1的实际值,确认它跟着你的设置走。
3.4 CH1N互补通道没有波形
TIM1_CN通道表示互补输出通道。很多人想用CH1N输出反向PWM,但只配置了CH1相关的参数,结果PB13引脚上静悄悄。
要输出互补波形,必须明确配置三个点:TIM_OCInitStructure里把TIM_OutputNState置为Enable;PB13这个引脚要初始化成复用推挽输出;MOE位同样要置1。缺任何一个,CH1N都没波形。另外注意,即使只用CH1N不用CH1,CH1对应的输出状态也不一定要关掉,实际项目中一路正相一路反相的互补输出,才是CH1N存在的意义。
初始化代码里需要额外加上GPIOB的时钟和PB13的配置,再把TIM_OutputNState打开。这样CH1N才有正常的互补PWM输出。
3.5 上电瞬间波形乱跳
在一些电机控制项目里,MCU刚上电那几十毫秒,引脚上偶尔会冒出不规则的脉冲,直接把功率管打穿。这种现象多半是初始化顺序问题:先启动了定时器,才去配置输出通道,或者CCR的初值是个垃圾值,还没被重置就已经输出了一拍异常PWM。
标准库推荐的做法是先配置好GPIO、时基、输出通道,把输出状态设为Enable,同时把TIM_Pulse初始化为0,最后再统一使能主输出和定时器。这样从第一拍开始,波形就是可控的。如果涉及功率电路,更稳妥的办法是硬件上加RC延迟,让MCU先稳定运行几百毫秒再让功率级上电。
3.6 调试三板斧:快速定位PWM问题
排查PWM输出,我有三个固定套路,基本能覆盖90%的情况:
- 先用示波器看引脚有没有波形。如果没有,把引脚临时配置成普通输入,读一下引脚电平是不是悬空或固定电平,能快速判断GPIO是否被正确复用。
- 用调试器看寄存器。重点看TIM1->CR1的CEN位是不是1,TIM1->BDTR的MOE位是不是1,TIM1->CCMR1的OC1M字段是不是110(PWM模式1)。这三个位置对了,配置基本没问题。
- 把参数调到最小。PSC设0,ARR设9,定时器时钟按72MHz算,输出就是7.2MHz的方波。这种频率示波器一抓一个准,能快速验证时基和通道链路通不通。等确认链路正常,再把ARR和PSC调到目标值。
4. 进阶玩法:互补输出、死区与刹车
4.1 死区时间是怎么算的
死区时间通过BDTR寄存器(Break and Dead-time register)的DTG位段配置。标准库里面对应的是TIM_BDTRInitStructure里的TIM_DeadTime字段。DTG一共8位,配置分成几种模式,最常用的是DTG[7:5]=000的情况,此时死区时间等于DTG[6:0]乘以tDTS。
tDTS等于定时器时钟周期的整数倍。以72MHz为例,tDTS大约是13.89ns。如果TIM_DeadTime填72,死区时间就是72乘以13.89ns,约等于1微秒。如果DTG的高三位不是000,那对应的是更长的死区范围,公式要查参考手册的表格,实际项目中基本用不到。
死区时间怎么选?太短会失效,太长会增加功耗。驱动IGBT时死区一般取1微秒到3微秒;驱动低压MOSFET的半桥驱动芯片,像IR2104这类,死区取0.5微秒到2微秒比较常见。具体数值还要看栅极电阻、开关速度、寄生电容等因素,不是拍脑袋定的。第一次调试时可以把死区设置得稍微保守一点,用示波器同时看上下桥的Vgs波形,确认没有直通后再逐步减小死区。
4.2 配置CH1和CH1N互补输出的完整步骤
要配置互补输出,需要在前面基础代码上扩展。GPIO方面,除了PA8,还需要初始化PB13为复用推挽输出。输出通道配置里,把TIM_OutputNState置为Enable。
补充的GPIO配置如下:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure);通道配置里增加互补输出使能:
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; // 使能互补输出然后在初始化过程中调用TIM_BDTRConfig配置死区参数:
TIM_BDTRInitTypeDef TIM_BDTRInitStructure; TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable; TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable; TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_OFF; TIM_BDTRInitStructure.TIM_DeadTime = 72; // 约1us死区(72MHz时钟) TIM_BDTRInitStructure.TIM_Break = TIM_Break_Disable; TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_High; TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable; TIM_BDTRConfig(TIM1, &TIM_BDTRInitStructure);配置顺序上有个讲究:BDTR相关的配置应该在主输出使能之前完成。如果把TIM_CtrlPWMOutputs放在TIM_BDTRConfig前面,某些寄存器位可能被后续配置覆盖,导致保护逻辑没有完全生效。
4.3 刹车保护:异常时自动封锁波形
电机控制里最担心的就是过流。软件上可以通过ADC采样电流,超过阈值就让PWM占空比归零,但软件路径有延迟,中断响应再快也得几十个微秒。TIM1的刹车功能是硬件级的,外部故障信号一到BKIN引脚,输出立刻被封锁,响应速度是纳秒级的,不需要软件参与。
标准库里配置刹车功能,同样在TIM_BDTRInitStructure里。把TIM_Break置为Enable,设置刹车信号的极性,再配置自动输出功能。刹车触发后,输出通道会进入一个由OSSI/OSSR位决定的安全状态,通常配置成输出低电平,让电机停转。
实际项目里,常用一个比较器电路检测母线电流,过流时比较器输出跳变信号接到TIM1_BKIN引脚。硬件上再配合DMA或中断记录故障状态,形成一个完整的保护链路。这就是TIM1区别于普通定时器最有价值的地方,没有刹车功能的话,功率电路的保护只能靠软件硬扛,风险大得多。
4.4 实际项目中的参数选择经验
带互补输出的TIM1 PWM配置,参数选择有一些通用经验可以参考。
频率选择上,直流有刷电机和BLDC的PWM频率一般取10kHz到20kHz,高于人耳可听范围又低于开关损耗爆炸区间。20kHz正好是人耳听觉阈值临界点,很多产品为了静音都选这个值,但这时开关损耗也会相应增加,需要平衡。舵机控制则固定在50Hz,周期20毫秒,脉宽0.5毫秒到2.5毫秒对应不同角度,这是舵机协议本身定义的。
占空比范围方面,驱动半桥芯片时一般不建议让占空比达到100%。很多自举式驱动芯片的自举电容需要靠下桥开通来充电,占空比长时间接近100%,下桥一直关断,自举电容电压会掉下去,上桥驱动就会失效。实际使用中会限制占空比上限在95%左右。这个是典型的实战经验,光看芯片手册不容易想到。
还有一个容易被忽略的参数是GPIO输出速度。PWM频率越高、边沿越陡,对GPIO速度的要求就越高。20kHz以下的PWM用GPIO_Speed_50MHz完全够用,但如果你在搞几百kHz的高频电源,GPIO速度等级设置不当会影响上升沿质量,甚至造成额外的开关损耗。
5. 写在最后的一点体会
用TIM1输出PWM这件事,原理本身并不复杂,难的是把每一个配置环节的因果关系理清楚。我刚开始接触高级定时器时也走了不少弯路,最头疼的就是“看起来都对了但就是没波形”。后来养成了一个习惯:每次配置完新外设,先用最简单的参数验证链路,再逐步叠加功能。比如先让PA8输出7.2MHz的裸PWM,确认没问题后再加死区、加刹车、加互补输出,每一步都验证过再往前走。
最后分享一个实用小技巧:调试PWM和电机控制时,可以把占空比、当前PWM频率、故障标志这些变量通过串口打印出来,配合上位机做实时曲线。尤其是做PID调参那会儿,光靠示波器看波形效率特别低,串口数据配合波形图才能快速看到控制环路的动态响应。这也是我后来在所有电机项目里都保留的一个调试接口。希望这篇东西能帮你少踩几个坑,早日跑出自己满意的PWM波形。