
先說在開頭啊,我們學習定時器總感覺它是很難的,這里我就不說他的編程難度,而是對于它的理解難度。學習定時器你就必須了解他的來龍去脈。
比如說你現在要使用一個定時器,那么先要決定你要用哪一個定時器,是高級定時器還是通用定時器呢?假如你要用高級定時器(TIM1 、TIM8),那他和通用定時器有啥區別呢?在程序中我們如何來體現他們的區別呢?第二個你要關心的就是你使用的定時器是多少位的,一般有16位和32位區分。比如TIM1就是16位,那么他的最大計數個數就是2^16=65536-1, 也就是說你的TIM_TimeBaseStructure.TIM_Period = X ;其中X最大值為65535,你不能高于它。但是如果你使用TIM2就是32位,那么他的最大計數個數就是2^32=4294967296-1,也就是說你的TIM_TimeBaseStructure.TIM_Period = X ;其中X最大值為4294967295,你不能高于它。 第三就是你要清楚你使用的這個定時器的時鐘來源是哪里,一般來說我們高級定時器(TIM1、TIM8)的時鐘來源是來自AHB2,你可以這樣記:高級定時器的時鐘頻率肯定高于通用定時器的時鐘頻率對吧,又因為2>1,所以高級定時器的時鐘來源是來自AHB2對吧,那么通用定時器的時鐘源就來自AHB1了對吧。
在這里還要注意的是STM32中除非APB1的時鐘分頻數設置為1,否則通用定時器TIMx的時鐘是 APB1時鐘的2倍,當 APB1 的時鐘不分頻的時候,通用定時器 TIMx 的時鐘就等于APB1 的時鐘。這也就解釋了下面我使用TIM3是通用定時器,掛載在 APB1上,而 APB1是36MHz,通用定時器TIMx的時鐘是 APB1時鐘的2倍,所以TIM3的定時器是72MHZ 計算。只有把這些該清楚了才能續的下去,當然遠不止這一下。
上一章我們說到了定時器,這一章稱熱打鐵熱說一說脈沖寬度調制-PWM。 脈沖寬度調制(PWM),是英文“Pulse Width Modulation”的縮寫,簡稱脈寬調制,是利用微處理器的數字輸出來對模擬電路進行控制的一種非常有效的技術。簡單一點,就是對脈沖寬度進行控制。

看到這個圖,我先想聰明的你馬上會想到,這個玩意一定有兩個寄存器ARR和CRRx,而這兩個值一定是需要自己設置的,圖上都標出來了,對吧。 我們假定定時器工作在向上計數 PWM模式,且當 CNT<CCRx 時,輸出 0,當 CNT>=CCRx 時輸出 1。那么就可以得到如上的 PWM示意圖:
- 當 CNT 值小于 CCRx 的時候,IO 輸出低電平(0),
- 當 CNT 值大于等于 CCRx 的時候,IO 輸出高電平(1),
- 當 CNT 達到 ARR 值的時候,重新歸零,然后重新向上計數,依次循環。 改變 CCRx 的值,就可以改變 PWM 輸出的占空比,改變 ARR 的值,就可以改變 PWM 輸出的頻率,這就是 PWM 輸出的原理。所以說對于PWM我們只要在程序中修改這兩個值就可以了,其他的都一樣。
STM32F429 的定時器除了 TIM6 和 7。其他的定時器都可以用來產生 PWM 輸出。 直接開搞,來看一下定時器的分類,這是F4的定時器分配圖

這里還要知道一個概念就是通道:
通道是什么,我們換個說法,通道=路=引腳,這樣是不是好理解一點,也就是說TIM2有4路定時器。TIM5也有4路,TIM3也有4路,TIM4也有4路。為什么要這么多路呢? 比如我們要產生8路周期,占空比都不同的PWM信號輸出,那我們可以選TIM2的 CH1/CH2/CH3/CH4 還有TIM3 的CH1,CH2,CH3,CH4 這8路進行輸出,需要這么多路,就是為了可以輸出/輸入 更多的信號。比如你用小舵機做一個小機器人就需要很多的定時器對應很多的通道也就是對應很多引腳。輸出的信號分別是哪些管腳呢。很明顯對F4來說就是 TIM2的 CH1/CH2/CH3/CH4 對應 PA5 /PA1/PA2/PA3 這4個管腳。 TIM5的 CH1/CH2/CH3/CH4 對應 PH10/PH11/PH12/PIO 這4個管腳。
要使 STM32F429 的通用定時器 TIMx 產生 PWM 輸出,除了上一章介紹的寄存器外,我們還會用到4個寄存器,來控制 PWM 的。這三個寄存器分別是: 捕獲/比較模式寄存器(TIMx_CCMR1/2) 捕獲/比較使能寄存器(TIMx_CCER) 捕獲/比較寄存器(TIMx_CCR1~4) 剎車和死區寄存器(TIMx_BDTR)這個寄存器一般在高級定時器中使用,就是TIM1和TIM8。如果你沒用到這兩個定時器就不要管這個寄存器了。脈沖寬度調制模式可以生成一個信號,該信號頻率由 TIMx_ARR 寄存器值決定,其占空比則由 TIMx_CCRx 寄存器值決定。
寄存器講解
捕獲/比較模式寄存器(TIMx_CCMR1/2)
該寄存器一般有 2 個:TIMx _CCMR1和 TIMx _CCMR2。TIMx_CCMR1 控制 CH1 和CH2,而 TIMx_CCMR2 控制 CH3 和 CH4

這個寄存器我們關心的是 位14 13 12 和 位 9 8,其他的默認就可以了。 模式設置位 OC4M,此部分由 3位組成。總共可以配置成 7 種模式,我們使用的是 PWM 模式,所以這 3 位必須設置為 110/111。這兩種 PWM 模式的區別就是輸出電平的極性相反。另外 CC4S 用于設置通道的方向(輸入/輸出)默認設置為00,就是設置通道作為輸出使用。
捕獲/比較使能寄存器(TIM3_CCER)
使能寄存器見名知意,當然是使能TIM3定時器的啊。使CC4E置位為1就可以了。

捕獲/比較寄存器(TIMx_CCR1~4)
該寄存器總共有 4 個,對應 4 個通道 CH1~4。我們使用的是通道3。在輸出模式下,該寄存器的值與 CNT 的值比較,根據比較結果產生相應動作。利用這點,我們通過修改這個寄存器的值,就可以控制 PWM 的輸出脈寬了。

程序代碼配置
我們文本的例子是定時器3的通道3,對應PB0管腳。
1.開啟TIM3時鐘,配置 PB0
要使用 TIM3,我們必須先開啟 TIM3的時鐘,這點相信大家看了這么多代碼,應該明白了。這里我們還要配置 PB0 為復用輸出(當然還要時能 GPIOB的時鐘),這是因為 TIM3_CH3通道將使用 PB0的復用功能作為輸出,我們配置 PB0為復用輸出,才可以實現 TIM3_CH3的 PWM 經過 PB0輸出。
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能TIM外設時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE); //使能GPIO外設時鐘
//設置該引腳為復用輸出功能,輸出TIM3 CH3的PWM脈沖波形
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //TIM3_CH3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //復用推挽輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
2.設置 TIM3的ARR和PSC
在開啟了 TIM3的時鐘之后,我們要設置 ARR 和 PSC 兩個寄存器的值來控制輸出 PWM 的周期。這在庫函數是通過 TIM_TimeBaseInit 函數實現的,在上一節定時器中斷章節已經有講解過,這里就不詳細講解,調用的格式為:
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = 100;
TIM_TimeBaseStructure.TIM_Prescaler =360-1;
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //設置時鐘分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上計數模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根據TIM_TimeBaseInitStruct中指定的參數初始化TIMx的時間基數單位
對于上面的參數解釋一下: ①頻率:我們使用的APB1時鐘源是72MHz的,在此我們不做分頻,通過配置相關的參數來設置輸入頻率,計算方法:輸入頻率=APB1時鐘/(預分頻系數+1)=72 000 000Hz/360=200 000Hz =200KHZ。 ②TIM_TImeBaseStructure.TIM_Period參數決定了輸出PWM波形的頻率,輸出PWM波形的頻率=定時器的輸入頻率/TIM_TImeBaseStructure.TIM_Period,這里我們設置設置的周期為200 000Hz/100=2000Hz,即0.5ms一個周期。 如果你想修改你的輸出PWM波形的頻率按我上面自己配置即可。
3.設置TIM3_CH3的PWM模式及通道方向, 使能 TIM3的CH3輸出
接下來,我們要設置 TIM3_CH3為 PWM 模式,我們要通過配置 TIM3_CCMR2的相關位來控制 TIM3_CH3 的模式。在庫函數中,PWM 通道設置是通過函數 TIM_OC1Init()~TIM_OC4Init()來設置的,不同的通道的設置函數不一樣,這里我們使用的是通道 3,所以使用的函數是 TIM_OC3Init()。這里的結構體我就不多少了,自己看一下就可以明白,對于普通定時器我們只需要設置下面這四個參數就可以了。
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //選擇定時器模式:TIM脈沖寬度調制模式2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比較輸出使能
TIM_OCInitStructure.TIM_Pulse = 500;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //輸出極性:TIM輸出比較極性高
TIM_OC3Init(TIM3, &TIM_OCInitStructure); //根據TIM_OCInitStruct中指定的參數初始化外設TIMx
③配置占空比:占空比=配置占空比的值/ TIM_TImeBaseStructure.TIM_Period,這里的占空比為50/100=50% 。50%的占空比那么波形也一定是一半高一半低對吧,一會我們仿真看一看。
4使能TIM3
所有的都配置完了,最后能一下TIM3。
TIM_Cmd(TIM3, ENABLE);//使能TIM3
5.使能基本定時器3的預裝載值。
使能TIM3在CCR3上的預裝載寄存器
TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable); 使能TIM3在CCR3上的預裝載寄存器,即TIM3_CCR3的預裝載值在更新事件到來時才能被傳送至當前寄存器中。
TIM_ARRPreloadConfig(TIM3, ENABLE); //使能TIM3在ARR上的預裝載寄存器 (影子寄存器)
這里我們來講一下影子寄存器: 1.有影子寄存器的有3個:分頻寄存器PSC,自動重裝載寄存器 ARR,自動捕獲CCRx(x是對應的通道),注意,PSC,ARR,CCRx不是影子寄存器,而是它們對應的“預裝載寄存器”; 2、影子寄存器才是真正起作用的寄存器,但是ST沒有提供這個寄存器出來,只是提供出與之相對應的預裝載寄存器,分別為“PSC,ARR,CCRx” 3、我們用戶能接觸到,能修改或讀取的都是預裝載寄存器,ST只是把它們開放出來(影子寄存器并沒有開放給用戶),其實就是ARR寄存器,如:TIM3->ARR 4、從預裝載寄存器ARR傳送到影子寄存器,有兩種方式,一種是立刻更新,一種是等觸發事件之后更新;這兩種方式主要取決于寄存器TIMx->CR1中的“APRE”位;
- APRE=0,當ARR值被修改時,同時馬上更新影子寄存器的值;
- APRE=1,當ARR值被修改時,必須在下一次事件發生后才能更新影子寄存器的值; 5、怎么樣馬上立刻更改影子寄存器的值,而不是下一個事件;方法如下:
- 將ARPE=0,TIM_ARRPreloadConfig(TIM3, DISABLE );
- 在ARPE=1,TIM_ARRPreloadConfig(TIM3, ENABLE);

TIM_ARRPreloadConfig設置為DISABLE 和ENABLE的問題,他的作用只是允許或禁止在定時器工作時向ARR的緩沖器中寫入新值,以便在更新事件發生時載入覆蓋以前的值。在開始初始化的時候你已經把" TIM_TimeBaseStructure.TIM_Period=100; //ARR的值 ",后來也一直是這個值,原因是你沒有編寫中斷服務函數或者你在中斷服務函數中根本就沒有給ARR緩沖器重新寫入新值,所以設置為DISABLE 和ENABLE都沒有影響,但是最保險的方法就是是能一下。
匯總一下代碼:
void TIM3_PWM_Init()
{ GPIO_InitTypeDef GPIO_InitStructure;TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;TIM_OCInitTypeDef TIM_OCInitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能TIM時鐘 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);//使能GPIO外設時鐘 //設置該引腳為復用輸出功能,輸出TIM3 CH3的PWM脈沖波形GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //TIM3_CH3GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //復用推挽輸出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period = 100; //輸出PWM波形的頻率=定時器的輸入頻率/TIM_TImeBaseStructure.TIM_Period,200 000Hz/100=2000Hz,即0.5ms一個周期 TIM_TimeBaseStructure.TIM_Prescaler =360-1;//不分頻 輸入頻率=APB1時鐘/(預分頻系數+1)=72 000 000Hz/360=200 000Hz =200K TIM_TimeBaseStructure.TIM_ClockDivision = 0; //設置時鐘分割:TDTS = Tck_timTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上計數模式TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //選擇定時器模式:TIM脈沖寬度調制模式2TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比較輸出使能TIM_OCInitStructure.TIM_Pulse = 50; //占空比=配置占空比的值/TIM_TImeBaseStructure.TIM_Period,500/1000=50% TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //輸出極性:TIM輸出比較極性高TIM_OC3Init(TIM3, &TIM_OCInitStructure); TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable); //CH3預裝載使能 TIM_ARRPreloadConfig(TIM3, ENABLE); //使能TIM3在ARR上的預裝載寄存器 (影子寄存器) TIM_Cmd(TIM3, ENABLE); //使能TIM3
}
6.主函數的編寫
主函數就很簡單,一個while函數就可以解決。
int main(void){ SystemInit (); TIM3_PWM_Init();while(1){}
}
根據而上一章講到的方法keil中調試一下,可以清楚地看到周期是0.5ms,跟我們上面設置的一樣。占空比也是50%,就是1/2。

當然我們這里設置的PWM波是死的,也就是說是固定的50%。那么這實際項目中我們一般是要求可變的PWM,最典型的就是控制電機的轉速。舉一個例子,下面是電機的驅動芯片就是TB6612的管腳分配圖。
TB6612引腳分配:
VM PWMA--------->TIM3_CH3(PBO)
VCC AIN2--------->GPIOB_12
GND AIN1--------->GPIOB_13
AO1 STBY--------->GPIOB_14
AO2 BIN1--------->GPIOB_15
BO2 BIN2--------->GPIOA_12
BO1 PWMB--------->TIM3_CH4(PB1)
在這里我們用GPIOB_12和GPIOB_13的電平高低來控制電機的前進后退,具體的電平高低要看真值表。用TIM3_CH3(PB0)來控制電機的轉速。設置不同的值,電機的轉速就是有快有慢。那么用哪一個函數來控制呢?
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
理所當然,對于其他通道,分別有一個函數名字,函數格式為 TIM_SetComparex(x=1,2,3,4)。現在我們就可以控制 TIM3 的 CH3 輸出 PWM 波了。也就是一個輪子的轉速快慢了。這里我用一個小小的if語句來使定時器3的通道3的值發生變化。
int main(void){ u16 i=0; SystemInit (); TIM3_PWM_Init();while(1){i++;TIM_SetCompare3(TIM3,i); //定時器3的通道3 if(i==100) i=0;}
}
下面看一下效果:

可以明顯的看到PWM波在不斷地變化,那就對應電機轉速的變化或者小燈的明暗程度。
至此我們就明白是STM32是如何輸出PWM的。這里我們來總結一下: 我們輸出PWM用到了定時器3,用TIM_Period 和TIM_Prescaler來確定輸出PWM的頻率和周期.用TIM_Pulse來確定占空比。 如果你單純的使用定時器3做定時功能那么TIM_Period 和TIM_Prescaler來確定需要定時的時間,也就是多長時間進入一次中斷服務函數執行相應的指令。只是在不同的地方叫法不同,本質還是一樣的。 聰明的你會了嗎?
