PWM的說明
PWM有三個關鍵指標: PWM頻率, 占空比, 區分度
對於同一個時鍾頻率下工作的單片機, 區分度是和PWM工作頻率相關的, 因為總頻率是固定的, PWM工作頻率越高, 留下給區分度的部分就越低, 因此區分度就越低. 對於STM32, 如果時鍾是72MHz, 在PWM頻率為1KHz時, 區分度為16bit, 在281KHz時, 為8bit, 在4.5MHz時, 就是4bit了.
STM32F4 Timers
STM32的PWM功能是定時器功能的一部分, STM32F4系列完整的定時器是14個
Timer | Type | Resolution | Prescaler | Channels | MAX INTERFACE CLOCK | MAX TIMER CLOCK* | APB |
---|---|---|---|---|---|---|---|
TIM1, TIM8 | Advanced | 16bit | 16bit | 4 | SysClk/2 | SysClk | 2 |
TIM2, TIM5 | General purpose | 32bit | 16bit | 4 | SysClk/4 | SysClk, SysClk/2 | 1 |
TIM3, TIM4 | General purpose | 16bit | 16bit | 4 | SysClk/4 | SysClk, SysClk/2 | 1 |
TIM9 | General purpose | 16bit | 16bit | 2 | SysClk/2 | SysClk | 2 |
TIM10, TIM11 | General purpose | 16bit | 16bit | 1 | SysClk/2 | SysClk | 2 |
TIM12 | General purpose | 16bit | 16bit | 2 | SysClk/4 | SysClk, SysClk/2 | 1 |
TIM13, TIM14 | General purpose | 16bit | 16bit | 1 | SysClk/4 | SysClk, SysClk/2 | 1 |
TIM6, TIM7 | Basic | 16bit | 16bit | 0 | SysClk/4 | SysClk, SysClk/2 | 1 |
F401屬於低端系列, 定時器只有一部分, 內置的定時器為
- 1個高級定時器TIM1
- 三相 PWM 輸出, 4個獨立通道(如果正反算兩個的話有8個). It has complementary PWM outputs with programmable inserted dead times
- 7個通用定時器
- 全功能的: TM2&5, TIM3&4, 4個獨立通道 for input capture/output compare, PWM or one-pulse mode output.
- 普通的: TIM9, TIM10,11. TIM10和TIM11有1個獨立通道, TIM9有2個獨立通道 for input capture/output compare, PWM or one-pulse mode output.
- 2個watchdog timers
每個定時器都有對應的通道數, 一般都有CH1 - CH4, 對於TIM1, 還有CH1N - CH4N
關於CH1和CH1N
后者輸出相對於前者反相的PWM信號, CH1和CH1N兩個通道互補輸出. 在設置這兩個通道輸出的時候如果開啟了互補輸出, 那么這兩個引腳的輸出電平始終相反, 也就是一個引腳輸出低電平, 另一個引腳自動輸出高電平, 反之亦然. 這樣的輸出方式一般用於電機驅動控制.
STM32F4的TIMx PIN腳輸出映射關系
TIM1 | TIM2 | TIM3 | TIM9 | |
---|---|---|---|---|
CH1 | PA8 | PA0 PA5 PA15 | PA6 PB4 | PA2 |
CH2 | PA9 | PA1 PB3 | PA7 PB5 | PA3 |
CH3 | PA10 | PA2 PB10 | PB0 | |
CH4 | PA11 | PA3 PB11 | PB1 | |
CH1N | PB13 PA7 | |||
CH2N | PB14 PB0 | |||
CH3N | PB15 PB1 |
設置PWM輸出電平的模式
PWM輸出模式的配置主要有兩個
1. TIM_OCMode: TIM輸出比較和PWM模式
- TIM_OCMode_Timing 在比較成功后不在對應輸出管腳上產生輸出, TIM_OCMode_Timing does not produce output on the corresponding output pin after a successful comparison
- TIM_OCMode_Active
- TIM_OCMode_Inactive
- TIM_OCMode_Toggle 計數達到比較值時翻轉對應輸出管腳上的電平, TIM_OCMode_Toggle is to flip the level on the corresponding output pin after a successful comparison
- TIM_OCMode_PWM1 常用的模式, CNT < CRRx時為有效電平, CNT > CRRx為無效電平
- TIM_OCMode_PWM2 與PWM1相反, CNT小於時為無效電平, 高於時為有效電平, 配合TIM_OCPolarity可以做到和PWM1一樣的輸出
2. TIM_OCPolarity: PWM的有效電平
與TIM_OCMode_PWM1和TIM_OCMode_PWM2配合, TIM_OCPolarity_High表示有效電平是高電平, TIM_OCPolarity_Low是低電平. 一般使用PWM1+HIGH的組合.
上面兩個配置結合產生的效果
- TIM_OCMode_PWM1模式下
- 設置TIM_OCPolarity_High, TIMx_CNT > TIMx_CCR輸出高電平, TIMx_CNT < TIMx_CCR輸出低電平
- 設置TIM_OCPolarity_Low, TIMx_CNT > TIMx_CCR輸出低電平, TIMx_CNT < TIMx_CCR輸出高電平
- TIM_OCMode_PWM2模式下
- 設置TIM_OCPolarity_High, TIMx_CNT > TIMx_CCR輸出低電平, TIMx_CNT < TIMx_CCR輸出高電平
- 設置TIM_OCPolarity_Low, TIMx_CNT > TIMx_CCR輸出高電平, TIMx_CNT < TIMx_CCR輸出低電平
設置PWM頻率
設置PWM頻率, 即設置PWM完整周期的時鍾計數次數. 這個是通過TIM_BaseStruct.TIM_Period
(ARR寄存器)設置的, 要設置這個值, 首先你要知道這個值的上限, 即定時器的最大值, 例如 16bit 即 65535, 要計算出PWM頻率, 可以這樣計算
PWM_frequency = timer_tick_frequency / (TIM_Period + 1)
也可以通過PWM頻率倒推時鍾周期計數值
TIM_Period = timer_tick_frequency / PWM_frequency - 1
例如, 如果需要的PWM頻率為10KHz, 則時鍾的周期計數值為
TIM_Period = (84000000 / 10000) - 1; // 8399
如果需要17.57 Khz, 就是
TIM_Period = (SystemCoreClock / 17570 ) - 1;
如果通過這個式子算出來的計數值大於定時器長度(例如超過了65535), 你需要增大 prescaler, 降低系統時鍾頻率
如果需要在運行時修改, 可以使用函數TIM_PrescalerConfig(TIM2, 35999, TIM_PSCReloadMode_Immediate)
, 這個函數的作用就是在定時器工作時改變預分頻器的值.
設置PWM占空比
設置占空比, 需要通過設置 TIM_Pulse 參數, 這個值就是用於比較的觸發值CRR, TIM_OCInitStructure.TIM_Pulse = 100
表示觸發值為100, 這個值的計算要結合PWM周期總計數值TIM_Period和需要的占空比百分比, 例如
pulse_length = ((TIM_Period + 1) * DutyCycle) / 100 - 1
# 其中DutyCycle是一個百分比, 例如對於TIM_Period為8399, 如果需要25%占空比
pulse_length = (8399 + 1) * 0.25 - 1 = 2099
如果需要在運行時修改, 你可以:
- You just write the updated width to the TIMx_CCRy register. Changed the CCR value in the relevant timer register and this did the trick. In my case, the code used to change the duty cycle is 'TIM3 -> CCR4 = {required value}'
- 通過調用
TIM_SetCompare[x](TIMx, Compare1)
這個函數,修改CCR的值,改變輸出占空比, 例如TIM_SetCompare1函數名中的數字1代表的是TIMx的通道1, 參數TIMx可以是TIM1, TIM2等, 第二個參數 Compare1, 是用於與TIMx計數值比較的數, 在TIMx達到這個計數值時將根據當前的模式和極性, 進行電平變換.TIM_SetCompareX
這個函數有四個, 分別是TIM_SetCompare1, TIM_SetCompare2, TIM_SetCompare3, TIM_SetCompare4. 對應不同的CHx使用, 例如TIMx_CH1使用 TIM_SetCompare1, TIMx_CH2使用TIM_SetCompare2, 等等.
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1) {
/* Check the parameters */
assert_param(IS_TIM_LIST8_PERIPH(TIMx));
/* Set the Capture Compare1 Register value */
TIMx->CCR1 = Compare1;
}
控制直流馬達的方向
控制直流馬達的模塊有兩種, 一種是L293D這種三線輸入的模塊, AB線的電壓差確定轉向, E線輸入PWM確定轉速, 另一種是L298N和L9110s這種雙線輸入的模塊, 根據兩根線的電壓差決定方向, 根據線上的PWM決定轉速. 對於前者, 需要兩根GPIO加一根PWM輸出, 對於后者, 需要兩根PWM輸出, PWM加在正向的PIN腳上, 另一個PIN腳PWM設為0.
對於后者的控制代碼例子如下
/*
雙軸搖桿: PIN腳朝左, X軸左小右大, Y軸上小下大
前進: X中,Y小
后退: X中,Y大
左轉: X小,Y中
右轉: X大,Y中
*/
void AdjustChannelPuls(u8 axis_x, u8 axis_y) {
int8_t l, r;
calc(axis_x, axis_y, &l, &r);
printf("X:%d, Y:%d, L:%d, R:%d\r\n", axis_x, axis_y, l, r);
if (l >= 0) {
Channel1Pulse = CalcPuls(l);
Channel3Pulse = CalcPuls(0);
} else {
Channel1Pulse = CalcPuls(0);
Channel3Pulse = CalcPuls(-l);
}
if (r >= 0) {
Channel2Pulse = CalcPuls(r);
Channel4Pulse = CalcPuls(0);
} else {
Channel2Pulse = CalcPuls(0);
Channel4Pulse = CalcPuls(-r);
}
TIM_ResetCounter(TIM3);
}
void UpdatePWM(void) {
TIM_SetCompare1(TIM2, Channel1Pulse);
TIM_SetCompare2(TIM2, Channel2Pulse);
TIM_SetCompare3(TIM2, Channel3Pulse);
TIM_SetCompare4(TIM2, Channel4Pulse);
}
代碼
啟動對應輸出口的定時器, 這里是TIM4
void TM_TIMER_Init(void) {
TIM_TimeBaseInitTypeDef TIM_BaseStruct;
/* 開啟TIM4時鍾 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
/*
TIM4連接的是 APB1 總線, 在F407上時鍾是 42MHz, 但是有內部PLL, 將頻率翻倍為 84MHz. 注意: 也有定時器是接在 APB2 總線上的, 默認工作在 84MHz, 通過內部PLL翻倍至 168MHz
設置預分頻 timer prescaller
時鍾被設置為 timer_tick_frequency = Timer_default_frequency / (prescaller_set + 1)
在這個例子中, 我們希望使用最大頻率, 所以 prescaller 設置為 0, 所以時鍾與總線時鍾一致, 頻率為
timer_tick_frequency = 84000000 / (0 + 1) = 84000000
*/
TIM_BaseStruct.TIM_Prescaler = 0;
/* 使用上升沿計數 */
TIM_BaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
/*
設置一個PWM完整周期的時鍾計數次數, 首先你要知道定時器的最大值, 在這個例子中是16bit, 即 65535, 要計算出你的PWM頻率, 可以這樣計算
PWM_frequency = timer_tick_frequency / (TIM_Period + 1)
通過這個算式也可以通過PWM頻率倒推時鍾周期計數值
TIM_Period = timer_tick_frequency / PWM_frequency - 1
在這個例子中, 如果需要的PWM頻率為10KHz, 則時鍾的周期計數值為
TIM_Period = 84000000 / 10000 - 1 = 8399
如果通過這個式子算出來的計數值大於定時器長度(例如超過了65535), 你需要增大 prescaler 降低系統時鍾頻率
*/
TIM_BaseStruct.TIM_Period = 8399; /* 10kHz PWM */
/* TIM_ClockDivision的設置不影響PWM頻率 */
TIM_BaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_BaseStruct.TIM_RepetitionCounter = 0;
/* TIM4 初始化 */
TIM_TimeBaseInit(TIM4, &TIM_BaseStruct);
/* TIM4 開始計數 */
TIM_Cmd(TIM4, ENABLE);
}
初始化PWM 4個通道
void TM_PWM_Init(void) {
TIM_OCInitTypeDef TIM_OCStruct;
/* 通道的公用配置 */
/* PWM 模式 2 = Clear on compare match 達到預設值時拉低電平 */
/* PWM 模式 1 = Set on compare match 達到預設值時拉高電平 */
TIM_OCStruct.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCStruct.TIM_OCPolarity = TIM_OCPolarity_Low;
/*
要得到期望的占空比(DutyCycle, 一個百分比), 通過這個式子計算定時器觸發值
pulse_length = ((TIM_Period + 1) * DutyCycle) / 100 - 1
例如
25% 占空比: pulse_length = ((8399 + 1) * 25) / 100 - 1 = 2099
50% 占空比: pulse_length = ((8399 + 1) * 50) / 100 - 1 = 4199
75% 占空比: pulse_length = ((8399 + 1) * 75) / 100 - 1 = 6299
100% 占空比: pulse_length = ((8399 + 1) * 100) / 100 - 1 = 8399
注意: 如果觸發值大於時鍾周期總長度 TIM_Period, 這個PWM將一直輸出同樣的電平
*/
TIM_OCStruct.TIM_Pulse = 2099; /* 25% duty cycle */
TIM_OC1Init(TIM4, &TIM_OCStruct);
TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);
TIM_OCStruct.TIM_Pulse = 4199; /* 50% duty cycle */
TIM_OC2Init(TIM4, &TIM_OCStruct);
TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable);
TIM_OCStruct.TIM_Pulse = 6299; /* 75% duty cycle */
TIM_OC3Init(TIM4, &TIM_OCStruct);
TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);
TIM_OCStruct.TIM_Pulse = 8399; /* 100% duty cycle */
TIM_OC4Init(TIM4, &TIM_OCStruct);
TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable);
}
初始化GPIO輸出
void TM_LEDS_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct;
/* GPIOD 時鍾 */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
/* 設置這些PIN腳的功能復用 */
GPIO_PinAFConfig(GPIOD, GPIO_PinSource12, GPIO_AF_TIM4);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource13, GPIO_AF_TIM4);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource14, GPIO_AF_TIM4);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource15, GPIO_AF_TIM4);
/* 設置PIN腳 */
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOD, &GPIO_InitStruct);
}
完整的代碼
#include "defines.h"
#include "stm32f4xx.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_tim.h"
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
u16 TimerPeriod = 0;
u16 Channel1Pulse = 0, Channel2Pulse = 0, Channel3Pulse = 0, Channel4Pulse = 0;
void DecreasePuls() {
Channel1Pulse = (Channel1Pulse <= 10)? TimerPeriod : Channel1Pulse - 10;
Channel2Pulse = (Channel2Pulse <= 10)? TimerPeriod : Channel2Pulse - 10;
Channel3Pulse = (Channel3Pulse <= 10)? TimerPeriod : Channel3Pulse - 10;
Channel4Pulse = (Channel4Pulse <= 10)? TimerPeriod : Channel4Pulse - 10;
}
void TIM_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* GPIOA, GPIOB Clocks enable */
RCC_AHB1PeriphClockCmd( RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOB , ENABLE);
/* GPIOA Configuration: Channel 1, 2, 3, 4 as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource0, GPIO_AF_TIM2);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_TIM2);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_TIM2);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_TIM2);
}
int main(void)
{
Systick_Init();
USART1_Init();
TIM_Config();
/* Compute the value to be set in ARR register to generate signal frequency at 17.57 Khz */
TimerPeriod = (SystemCoreClock / 17570 ) - 1;
/* Compute CCR1 value to generate a duty cycle at 100% for channel 1 and 1N */
Channel1Pulse = (u16) (((u32) 10 * (TimerPeriod - 1)) / 10);
/* Compute CCR2 value to generate a duty cycle at 30% for channel 2 and 2N */
Channel2Pulse = (u16) (((u32) 300 * (TimerPeriod - 1)) / 1000);
/* Compute CCR3 value to generate a duty cycle at 20% for channel 3 and 3N */
Channel3Pulse = (u16) (((u32) 20 * (TimerPeriod - 1)) / 100);
/* Compute CCR4 value to generate a duty cycle at 10% for channel 4 */
Channel4Pulse = (u16) (((u32) 100 * (TimerPeriod- 1)) / 1000);
/* TIM2 clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
/* Time Base configuration */
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period = TimerPeriod;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
//TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
/* Channel 1, 2,3 and 4 Configuration in PWM mode */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // PWM: trigger from valid -> invalid
// TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
//TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
// Specifies the TIM Output Compare pin state during Idle state, ## valid only for TIM1 and TIM8 ##
//TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
//TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset;
//Specifies the TIM Output Compare state
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = Channel1Pulse;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
// TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);
TIM_OCInitStructure.TIM_Pulse = Channel2Pulse;
TIM_OC2Init(TIM2, &TIM_OCInitStructure);
// TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);
TIM_OCInitStructure.TIM_Pulse = Channel3Pulse;
TIM_OC3Init(TIM2, &TIM_OCInitStructure);
// Enables or disables the TIMx peripheral Preload register on CCR1
// TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Enable);
TIM_OCInitStructure.TIM_Pulse = Channel4Pulse;
TIM_OC4Init(TIM2, &TIM_OCInitStructure);
// TIM_OC4PreloadConfig(TIM2, TIM_OCPreload_Enable);
// TIM_ARRPreloadConfig(TIM2, ENABLE);
/* TIM2 counter enable */
TIM_Cmd(TIM2, ENABLE);
/* TIM2 Main Output Enable */
TIM_CtrlPWMOutputs(TIM2, ENABLE); // Enables or disables the TIM peripheral Main Outputs.
while (1)
{
printf("TP:%d, CH1:%d, CH2:%d, CH3:%d, CH4:%d\r\n", TimerPeriod, Channel1Pulse, Channel2Pulse, Channel3Pulse, Channel4Pulse);
DecreasePuls();
TIM_SetCompare1(TIM2, Channel1Pulse);
TIM_SetCompare2(TIM2, Channel2Pulse);
TIM_SetCompare3(TIM2, Channel3Pulse);
TIM_SetCompare4(TIM2, Channel4Pulse);
Systick_Delay_ms(100);
}
代碼中的幾個函數的說明
TIM_OC4PreloadConfig
TIM_OC4PreloadConfig(TIM2, TIM_OCPreload_Enable);
使能TIM2 在 CCR4 上的預裝載寄存器, 即 TIM2_CCR4 的預裝載值在更新事件到來時才能被傳送至當前寄存器中. 就是設置 CCR4 中的預裝載值何時被傳送到當前的CNT寄存器中, 設置為ENABLE, 表示僅當更新事件到來的時候才裝載, 追蹤寄存器的設置可知, 原來設置的是CCMR1的OC2PE, 其實還有一種方式是立即裝載. OC1PE:輸出比較1預裝載使能(Output compare 1 preload enable)位3
- 0:禁止TIMx_CCR1寄存器的預裝載功能, 可隨時寫入TIMx_CCR1寄存器, 並且新寫入的數值立即起作用
- 1:開啟TIMx_CCR1寄存器的預裝載功能, 讀寫操作僅對預裝載寄存器操作, TIMx_CCR1的預裝載值在更新事件到來時被傳送至當前寄存器中
TIM_ARRPreloadConfig
TIM_ARRPreloadConfig的作用, 是允許或禁止在定時器工作時向ARR的緩沖器中寫入新值, 以便在更新事件發生時載入覆蓋以前的值. 如果在初始化的時候設置了ARR的值TIM_TimeBaseStructure.TIM_Period=2000;
, 后來也沒更改(沒有編寫中斷服務函數或者在中斷服務函數中沒有給ARR緩沖器重新寫入新值), 那么設置為DISABLE 和ENABLE都沒有影響, 這個方法可以不寫.
參考
- 使用std periph庫實現的,包含代碼講解, 用的是TIM4, 可以和官方標准庫里的例子(TIM3)對比着看 https://stm32f4-discovery.net/2014/05/stm32f4-stm32f429-discovery-pwm-tutorial/
- 原理和實現機制, HAL庫 https://deepbluembedded.com/stm32-pwm-example-timer-pwm-mode-tutorial/
- 使用std periph庫實現的,包含代碼講解 https://thecodeprogram.com/stm32f4-timers-and-pwm-generation-with-std-periph
- 使用L293控制馬達轉速和方向, 用一個按鍵切換方向, 用三個按鍵輸出不同的PWM占空比 https://www.engineersgarage.com/dc-motor-control-with-stm32-microcontroller/