STM32F401的PWM輸出


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

如果需要在運行時修改, 你可以:

  1. 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}'
  2. 通過調用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都沒有影響, 這個方法可以不寫.

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM