天天看點

《嵌入式-STM32開發指南》第二部分 基礎篇 - 第5章 PWM(HAL庫)

标準庫3.5實作:

《嵌入式-STM32開發指南》第二部分 基礎篇 - 第5章 PWM

5.1 PWM_輸出

5.1.1 PWM輸出的工作原理

脈沖寬度調制(PWM),是英文“ Pulse Width Modulation” 的縮寫,簡稱脈寬調制,是利用微處理器的數字輸出來對模拟電路進行控制的一種非常有效的技術。 簡單一點,就是對脈沖寬度的控制。

STM32 的定時器除了 TIM6 和 7(基本定時器)。其他的定時器都可以用來産生 PWM 輸出。其中進階定時器 TIM1 和 TIM8 可以同時産生多達 7 路的 PWM 輸出。而通用定時器也能同時産生多達 4路的 PWM 輸出,這樣, STM32 最多可以同時産生 30 路 PWM 輸出。

每個定時器有四個通道,每一個通道都有一個捕獲比較寄存器,,将寄存器值和計數器值比較,通過比較結果輸出高低電平,便可以實作脈沖寬度調制模式(PWM信号)。

在上一節,講解了定時器的相關寄存器即基本原理,本節将不再贅述。下面談談如何使用定時器的寄存器進行PWM輸出的。若配置脈沖計數器TIMx_CNT為向上計數,而重載寄存器TIMx_ARR配置為N,即TIMx_CNT的目前計數值數值X在TIMxCLK時鐘源的驅動下不斷累加,當TIMx_CNT的數值X大于N時,會重置TIMx_CNT數值為0重新計數。而在TIMxCNT計數的同時,TIMxCNT的計數值X會與比較寄存器TIMx_CCR預先存儲了的數值A進行比較,當脈沖計數器TIMx_CNT的數值X小于比較寄存器TIMx_CCR的值A時,輸出高電平(或低電平),相反地,當脈沖計數器的數值X大于或等于比較寄存器的值A時,輸出低電平(或高電平)。如此循環,得到的輸出脈沖周期就為重載寄存器TIMx_ARR存儲的數值(N+1)乘以觸發脈沖的時鐘周期,其脈沖寬度則為比較寄存器TIMx_CCR的值A乘以觸發脈沖的時鐘周期,即輸出PWM的占空比為A/(N+1)。

估計很多初學者看了上面的一段話都很蒙圈,沒關系,下面以向上計數模式為例進行講解。

《嵌入式-STM32開發指南》第二部分 基礎篇 - 第5章 PWM(HAL庫)

圖1向上計數模式

在PWM輸出模式下,除了CNT(計數器目前值)、ARR(自動重裝載值)之外,還多了一個值CCRx(捕獲/比較寄存器值)。當CNT小于CCRx時,TIMx_CHx通道輸出低電平;當CNT等于或大于CCRx時,TIMx_CHx通道輸出高電平。是以得到PWM的一個周期如下:

1.定時器從0開始向上計數; 2.當0-t1段,定時器計數器TIMx_CNT值小于CCRx值,輸出低電平; 3.t1-t2段,定時器計數器TIMx_CNT值大于CCRx值,輸出高電平; 4.當TIMx_CNT值達到ARR時,定時器溢出,重新向上計數…循環此過程。

至此一個PWM周期完成。針對PWM重點關注兩個寄存器,TIMx_ARR寄存器确定PWM頻率,TIMx_CCRx寄存器确定占空比。

上文提到了PWM的輸出模式,下面講解PWM的工作模式:

  • PWM模式1(向上計數) :計數器從0計數加到自動重裝載值(TIMx_ARR),然後重新從0開始計數,并且産生一個計數器溢出事。
  • PWM模式2(向下計數) :計數器從自動重裝載值(TIMx_ARR)減到0,然後重新從重裝載值(TIMx_ARR)開始遞減,并且産生一個計數器溢出事件。

[ps] 本文以F1系列為例進行講解,ST不同系列其定時器個數不同

STM32F1系列共有8個定時器:

進階定時器(TIM1、TIM8);通用定時器(TIM2、TIM3、TIM4、TIM5);基本定時器(TIM6、TIM7)。

5.1.2 STM32Cube生成工程

本文介紹在STM32CubeMX進行定時器的配置,這裡我們僅利用 TIM3的 4路通道輸出,友善我們比較波形。具體不同定時器對應引腳在對應晶片資料手冊的引腳說明(pin description) 中檢視。

表1 STM32F1定時器輸出通道引腳

《嵌入式-STM32開發指南》第二部分 基礎篇 - 第5章 PWM(HAL庫)

1.設定RCC

設定高速外部時鐘HSE,選擇外部時鐘源。

《嵌入式-STM32開發指南》第二部分 基礎篇 - 第5章 PWM(HAL庫)

圖2 RCC配置

2.時鐘配置

筆者的闆子使用的外部晶振為8MHz,選擇外部時鐘HSE 8MHz ,PLL鎖相環9倍頻後為72MHz,系統時鐘來源選擇為PLL,設定APB1分頻器為 /2,這時候定時器的時鐘頻率為72Mhz。本文筆者使用的定時器是TIM3,TIM3挂在APB1上,不同的定時器挂在不同總線上的。

《嵌入式-STM32開發指南》第二部分 基礎篇 - 第5章 PWM(HAL庫)

圖 3時鐘配置

3.Times配置

選擇TIM,使能TIM3,指定時鐘源。

《嵌入式-STM32開發指南》第二部分 基礎篇 - 第5章 PWM(HAL庫)

圖4使能TIM3時鐘源

【注】TIM3的時鐘源有兩個選項

選項1 :Internal Clock 内部時鐘 選項2 : ETR2 外部觸發輸入(ETR)(僅适用TIM2,3,4)

本文要使用TIM3的四個通道,是以需要将其使能。每個通道有很多模式,這裡選擇PWM輸出。當對應的通道打開後,對應的GPIO也會被使能。

《嵌入式-STM32開發指南》第二部分 基礎篇 - 第5章 PWM(HAL庫)

圖5使能TIM3的通道

【注】如果使能通道前通道中GPIO使用過,STM32CubeMX會自動将GPIO配置為重映射的GPIO。舉個例子,當PB0被占用了,那麼四個GPIO會重映射到PC6-PC9。

PWM參數配置如下:

  • Counter setting
Prtscaler (定時器分頻系數) : 0 Counter Mode(計數模式) :Up(向上計數模式) Counter Period(自動重裝載值) : 999 CKD(時鐘分頻因子) : No Division 不分頻 選項: 可以選擇二分頻和四分頻 auto-reload-preload(自動重裝載) : Enable 使能
  • TRGO Output (TRGO) Parameters
Master/Slave Mode(MSM bit):Disable TRGO:定時器的觸發信号輸出 在定時器的定時時間到達的時候輸出一個信号(如:定時器更新産生TRGO信号來觸發ADC的同步轉換,)
  • PWM Generation Channel (四個CH)
Mode(定時模式):PWM mode 1 Pulse(計數比較值):四個通道分别為500,375,250,125 CH Polarity(輸出極性):High
《嵌入式-STM32開發指南》第二部分 基礎篇 - 第5章 PWM(HAL庫)

圖6 PWM輸出參數配置

根據前面的參數配置,我們可以算出PWM的輸出周期:

P

W

M

=

1

/

(

T

c

l

k

/

(

p

s

c

+

1

)

)

(

a

r

r

+

1

)

PWM=1/(Tclk/(psc+1))*(arr+1)

PWM=1/(Tclk/(psc+1))∗(arr+1)

這裡我們

a

r

r

=

999

,

p

s

c

=

,

T

c

l

k

=

72

M

h

z

arr=999, psc=0, Tclk=72Mhz

arr=999,psc=0,Tclk=72Mhz ,

P

W

M

=

1

/

(

72

M

h

z

/

(

1

)

)

(

999

+

1

)

=

72

m

s

PWM=1/(72Mhz/(1))*(999+1)=72ms

PWM=1/(72Mhz/(1))∗(999+1)=72ms

本文選擇的是PWM模式1,在向上計數時,一旦TIMx_CNT < TIMx_CCR1(計數比較值)。時通道1為有效電平,否則為無效電平;在向下計數時,一旦TIMx_CNT>TIMx_CCR1時通道1為無效電平(OC1REF=0),否則為有效電平(OC1REF=1)。輸出比較極性的指的是你在比較比對之後輸出口輸出的極性,也就是設定比較輸出的有效電平。你可以設定為高電平有效或者低電平有效。如果設定為高電平有效,那麼當定時器比較比對之後,輸出口輸出高電平,否則就反一下。

如果是PWM模式1,且向上計數,如果極性設定為低,那麼 TIMx_CNT < TIMx_CCR1 時,輸出低電平,更簡單就是占空比為1 -TIMx_CCR1/(ARR+1). 如果極性為高,占空比就是TIMx_CCR1/(ARR+1)。

好了,到這裡,配置就完成了,生成工程就行了。

5.1.3 PWM輸出的具體代碼分析

我們先看看主函數,其代碼如下:

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM3_Init();
  /* USER CODE BEGIN 2 */
/*使能定時3*/
  HAL_TIM_Base_Start_IT(&htim3);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_4);
//HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_ALL);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
    
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}      

在主循環前面,需要對TIM3進行初始化配置:

HAL_TIM_Base_Start_IT(&htim3);      

然後再開啟四路通道的PWM:

HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_4);      

如果全部開啟,可使用以下代碼:

HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_ALL);      

PWM輸出最重要就是MX_TIM3_Init()函數,這個函數包含了TIM3的PWM配置,具體再講。

5.1.4 PWM輸出的實驗現象

現在,TIM3 的通道 1(PA.06)、2(PA.07)、3(PB.00)、4(PB.01)就會輸出不同占空比的 PWM 信号了。PWM 信号可以通過示波器看到。考慮到并不是每個使用者手頭上都有示波器,我們在這裡采用軟體仿真的方式來驗證我們的程式。

以前我們都是通過 J_LINK 直接将我們的代碼燒到開發闆的 Flash 中去調試,現在要換成軟體仿真,得首先設定一下我們的開發環境,按照如下步驟所示。

1)點選 Target Options 選項圖示,選中 Debug 頁籤,選中 Use Simulator 選項,按圖中所示進行設定,然後點選“OK”按鈕,見圖7所示。

《嵌入式-STM32開發指南》第二部分 基礎篇 - 第5章 PWM(HAL庫)

圖7模拟調試設定

2)點選 Start/Stop Debug Session 選項圖示,點選System Analysis Windows的下拉選項的 Logic Analysis,彈出視窗後點選Setup…頁籤,在彈出的 Setup Logic Analysis 序列槽中點選 New(Insert)按鈕,然後在文本欄裡面分别輸入:PORTA.6 、PORTA.7、PORTB.0、PORTB.1,記住,是 New 一個就輸入一個信号的 IO,輸完之後需要再 New。對應的相關設定見圖8。

《嵌入式-STM32開發指南》第二部分 基礎篇 - 第5章 PWM(HAL庫)

圖8 端口輸出配置

需要注意的是Display Range的設定,預設的設定是看不到波形的。

3)設定完信号源之後,點選 RUN 按鈕,仿真信号即出來了,當信号出來之後,可點選STOP 按鈕,讓信号不再變化,友善觀察。其中 In、Out、All 這三個按鈕可以調節顯示信号的疏密程度,見圖9。

《嵌入式-STM32開發指南》第二部分 基礎篇 - 第5章 PWM(HAL庫)

圖9波形輸出

其中 Cursor 選項可以幫助我們測量信号的時間差,Amplitute 則可以幫助我們測量信号的幅值。

【注】有的STM32cudeMX生成的工程不一定能 ,如果實在沒法看結果,直接看呼吸燈實驗吧。

5.2呼吸燈

5.2.1呼吸燈的工作原理

呼吸燈,就是指燈光裝置的亮度随着時間由暗到亮逐漸增強,再由亮到暗逐漸衰減,很有節奏感地一起一伏,就像是在呼吸一樣,因而被廣泛應用于手機、電腦等電子裝置的訓示燈中。冰冷的電子裝置應用呼吸燈後,頓時增添了幾分溫暖。

要使用數字器件控制燈光的強弱,我們很自然就想到 PWM(脈沖寬度調制)技術。假如以LED 作為燈光裝置,且由控制器輸出的 PWM 信号可以直接驅動 LED,PWM 信号中的低電平可點亮 LED 燈。當 LED 以較高的頻率進行開關(亮滅)切換時,由于視覺暫留效應,人眼是看不到 LED 燈的閃爍現象的,反映到人眼中能感覺到的是亮度的差别。即以一定的時間長度為周期,LED 燈亮的平均時間越長,亮度就越高,反之越暗。是以,我們可以使用高頻率的 PWM 信号,通過調制信号的占空比,控制 LED 燈的亮度。

那麼具體我們應該控制 LED 燈以怎樣的亮度曲線變化能夠達到最好的效果呢?亮度随着時間逐漸變強再衰減,可以用兩種常見的數學函數表示,分别是半個周期的正弦函數與指數上升曲線及其對稱得到的下降曲線。

《嵌入式-STM32開發指南》第二部分 基礎篇 - 第5章 PWM(HAL庫)

圖10正弦曲線與指數曲線

相對來說,使用下凹函數曲線燈光處于暗的狀态更長,是以指數函數的曲線更符合我們呼吸燈的亮度變化要求。

接下來就要确定呼吸燈的呼吸頻率(即一個亮度起伏過程)。據統計,成人的一個呼吸周期為 3 秒鐘,即吸氣時間(亮度上升時間) 1.5 秒,呼氣時間(亮度衰減時間)1.5 秒。我們使用定時器即可精确控制它的呼吸頻率,當然,讀者想把呼吸燈的頻率調快或慢一點都是可以的,呼吸周期為 3 秒鐘隻是一個參考值。

5.2.2 STM32Cube生成工程

和上一節内容差不多,稍微有些不同罷了。

1.設定RCC

設定高速外部時鐘HSE,選擇外部時鐘源。

《嵌入式-STM32開發指南》第二部分 基礎篇 - 第5章 PWM(HAL庫)

圖11 RCC配置

2.時鐘配置

筆者的闆子使用的外部晶振為8MHz,選擇外部時鐘HSE 8MHz ,PLL鎖相環9倍頻後為72MHz,系統時鐘來源選擇為PLL,設定APB1分頻器為 /2,這時候定時器的時鐘頻率為72Mhz。本文筆者使用的定時器是TIM3,TIM3挂在APB1上,不同的定時器挂在不同總線上的。

《嵌入式-STM32開發指南》第二部分 基礎篇 - 第5章 PWM(HAL庫)

圖 12時鐘配置

3.Times配置

選擇TIM,使能TIM3,指定時鐘源。

《嵌入式-STM32開發指南》第二部分 基礎篇 - 第5章 PWM(HAL庫)

圖13使能TIM3時鐘源

本節要使用TIM3的CH3通道,是以需要将其使能。這裡選擇PWM輸出。當對應的通道打開後,對應的GPIO也會被使能。筆者的闆子的LED接到了PB0上,這裡要根據自己的闆子來配置TIM和CH。

《嵌入式-STM32開發指南》第二部分 基礎篇 - 第5章 PWM(HAL庫)

圖14使能TIM3的CH3通道

PWM參數配置如下:

  • Counter setting
Prtscaler (定時器分頻系數) : 1999 Counter Mode(計數模式) :Up(向上計數模式) Counter Period(自動重裝載值) : 255 CKD(時鐘分頻因子) : No Division 不分頻 選項: 可以選擇二分頻和四分頻 auto-reload-preload(自動重裝載) : Enable 使能
  • TRGO Output (TRGO) Parameters
Master/Slave Mode(MSM bit):Disable TRGO:定時器的觸發信号輸出 在定時器的定時時間到達的時候輸出一個信号(如:定時器更新産生TRGO信号來觸發ADC的同步轉換,)
  • PWM Generation Channel (四個CH)
Mode(定時模式):PWM mode 1 Pulse(計數比較值):0 CH Polarity(輸出極性):High
《嵌入式-STM32開發指南》第二部分 基礎篇 - 第5章 PWM(HAL庫)

圖15 PWM輸出參數配置

根據前面的參數配置,我們可以算出PWM的輸出周期:

P

W

M

=

1

/

(

T

c

l

k

/

(

p

s

c

+

1

)

)

(

a

r

r

+

1

)

PWM=1/(Tclk/(psc+1))*(arr+1)

PWM=1/(Tclk/(psc+1))∗(arr+1)

這裡我們

a

r

r

=

255

,

p

s

c

=

1999

,

T

c

l

k

=

72

M

h

z

arr=255 , psc=1999, Tclk=72Mhz

arr=255,psc=1999,Tclk=72Mhz ,

P

W

M

=

1

/

(

72

M

h

z

/

(

1

+

1999

)

)

(

255

+

1

)

PWM=1/(72Mhz/(1+1999))*(255+1)

PWM=1/(72Mhz/(1+1999))∗(255+1)

這個要開啟中斷。

《嵌入式-STM32開發指南》第二部分 基礎篇 - 第5章 PWM(HAL庫)

圖16 開啟TIM3 的中斷

另外将設定GPIO的速度為高速。

《嵌入式-STM32開發指南》第二部分 基礎篇 - 第5章 PWM(HAL庫)

圖17設定GPIO速度

好了,到這裡,就配置完成了。

5.2.3呼吸燈的具體代碼分析

我們還是先看看主函數。

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM3_Init();
  /* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim3);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}      

主函數和上一節的差不多,還好了幾句,這個隻需初始化TIM3的CH3。那麼是哪裡不同呢,在回答這個問題之前,先看看呼吸燈的程式設計流程。

1)硬體初始化,系統時鐘初始化; 2)GPIO初始化,TIM3以及PWM初始化; 3)啟動定時器和PWM相應通道。 4)調用中斷回調函數,不斷改變TIMx_CCR寄存器的值。

我們就根據這個思路來看看呼吸燈的具體代碼。

1.生成指數曲線 PWM 資料

要實作 LED 亮度随着指數曲線變化,我們需要使用占空比呈指數曲線變化的 PWM 信号,而這樣的信号由定時器經過查表産生。這個表的資料存儲在程式中的數組 indexWave中。

《嵌入式-STM32開發指南》第二部分 基礎篇 - 第5章 PWM(HAL庫)

圖18 LED 變化曲線

uint8_t indexWave[] = {1,1,2,2,3,4,6,8,10,14,19,25,33,44,59,80,
  107,143,191,255,255,191,143,107,80,59,44,33,25,19,14,10,8,6,4,3,2,2,1,1};      

這個表有 40 個數字,從圖中可以看到這些數字呈指數上升再衰減,正好是呼吸燈的一個控制周期。數字的大小範圍是 0~ 255,即把 LED 的亮度分為了 0 ~ 255 個等級。

假如我們把定時器的脈沖計數器 TIMx_CNT 上限設定為 255,把這個表的資料一個一個地指派到定時器的比較寄存器 TIMx_CCR 中,那麼在每個 PWM 周期中,當 TIMx_CNT的計數值小于比較寄存器 TIMx_CCR 的值時, 就會在通道中輸出低電平,點亮 LED,而随着 TIMx_CCR 的值由 LED 亮度表得來,是以 LED 點亮的時間就會呈圖中的曲線變化,實作呼吸燈的功能。

這個表的資料是使用 matlab 軟體生成的。該代碼運作後會生成一個“index_wave.c”的檔案,使用者把該檔案中的資料複制到工程中的數組中即可。

%本代碼用于産生呼吸燈使用的指數函數資料
clear;

x = [0 : 8/19 : 8];       %設定序列 ,指數上升
up = 2.^x ;               %求上升指數序列  
up = uint8(up);           %化為8位資料

y = [8: -8/19 :0];       %設定序列 ,指數下降
down = 2.^y ;            %求下降指數序列
down = uint8(down);      %化為8位資料

line = [[0:8/19:8],[8:8/19:16]]         %拼接序列
val = [up , down]                       %拼接輸出序列

dlmwrite('index_wave.c',val);       %輸出到檔案index_wave.c
plot(line,val,'.');                 %顯示波形圖      

2.初始化GPIO和定時器

硬體初始化,系統時鐘初始化就不說了,我們看看GPIO和定時器初始化。

/**
  * @brief TIM3 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM3_Init(void)
{

  /* USER CODE BEGIN TIM3_Init 0 */

  /* USER CODE END TIM3_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};

  /* USER CODE BEGIN TIM3_Init 1 */

  /* USER CODE END TIM3_Init 1 */
  htim3.Instance = TIM3;
  htim3.Init.Prescaler = 1999;
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = 255;
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM3_Init 2 */

  /* USER CODE END TIM3_Init 2 */
  HAL_TIM_MspPostInit(&htim3);

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOB_CLK_ENABLE();

}      

GPIO初始化說的,就配置了一個時鐘。這裡主要講講TIM3初始化,MX_TIM3_Init()函數有兩部分内容,一部分TIM的Counter配置,這部分内容和上一章是一樣的,我們重點關注TIM_OC_InitTypeDef結構體,這個結構體就是輸出比較配置的結構體,這個就是PWM的具體輸出的配置結構體。

typedef struct
{
  uint32_t OCMode;        /*!< Specifies the TIM mode.
                               This parameter can be a value of @ref TIM_Output_Compare_and_PWM_modes */

  uint32_t Pulse;         /*!< Specifies the pulse value to be loaded into the Capture Compare Register.
                               This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */

  uint32_t OCPolarity;    /*!< Specifies the output polarity.
                               This parameter can be a value of @ref TIM_Output_Compare_Polarity */

  uint32_t OCNPolarity;   /*!< Specifies the complementary output polarity.
                               This parameter can be a value of @ref TIM_Output_Compare_N_Polarity
                               @note This parameter is valid only for timer instances supporting break feature. */

  uint32_t OCFastMode;    /*!< Specifies the Fast mode state.
                               This parameter can be a value of @ref TIM_Output_Fast_State
                               @note This parameter is valid only in PWM1 and PWM2 mode. */

  uint32_t OCIdleState;   /*!< Specifies the TIM Output Compare pin state during Idle state.
                               This parameter can be a value of @ref TIM_Output_Compare_Idle_State
                               @note This parameter is valid only for timer instances supporting break feature. */

  uint32_t OCNIdleState;  /*!< Specifies the TIM Output Compare pin state during Idle state.
                               This parameter can be a value of @ref TIM_Output_Compare_N_Idle_State
                               @note This parameter is valid only for timer instances supporting break feature. */
} TIM_OC_InitTypeDef;      
  • OCMode:輸出比較模式的選擇,對應的是TIMx_CCMR1寄存器的OC1M位。
  • Pulse:設定電平跳變值,最小值為0x0000 ,最大值為0Xffff。
  • OCPolarity:設定輸出比較的極性。
  • OCNPolarity:設定互補輸出比較極性。
  • OCFastMode:輸出比較快速使能和失能。
  • OCIdleState:選擇空閑狀态下的非工作狀态。
  • OCNIdleState:設定非空閑狀态下的非工作狀态。

根據上述講解,也就能明白MX_TIM3_Init()的含義了。接下來重點來了,以上是STM32cudeMX自動生成的代碼,下面是我們自己實作的代碼,也就是呼吸燈的核心代碼。

3.PWM輸出中斷回調函數

關于如何配置中斷,這部分昂看上一章,下面講解如何編寫PWM中斷服務函數。

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  static uint8_t pwm_index = 1;     /* 用于PWM查表 */
  static uint8_t period_cnt = 0;    /* 用于計算周期數 */

  period_cnt++;
  /* 若輸出的周期數大于10,輸出下一種脈沖寬的PWM波 */
  if(period_cnt >= 10)                    
  {
    /* 根據PWM表修改定時器的比較寄存器值 */
    __HAL_TIM_SET_COMPARE(htim,TIM_CHANNEL_3,indexWave[pwm_index]);
    
    /* 标志PWM表的下一個元素 */
    pwm_index++;                        
    /* 若PWM脈沖表已經輸出完成一遍,重置PWM查表标志 */
    if( pwm_index >=  40)               
    {
      pwm_index=0;                
    }
    /* 重置周期計數标志 */
    period_cnt=0;                       
  }
}      

本中斷服務函數在每次定時器更新事件發生時執行一次(即 256 個定時器時鐘周期)。函數中使用了靜态變量 pwm_index 和 period_cnt,它們分别用來查找 PWM 表元素和記錄同樣占空比的脈沖輸出了多少次。

本代碼的目的是每 10 次定時器中斷更新一次 PWM 表中的資料到比較寄存器TIMx_CCR 中,當周遊完 PWM 表的 40 個元素時,再重頭開始周遊 PWM 表,周而複始,重複 LED 的呼吸過程。

整個呼吸過程的時間計算方法如下:

因為定時器的 TIM_Prescaler 設定為 1999;

是以定時器的時鐘頻率:

f

T

I

M

=

72000000

/

(

T

I

M

P

r

e

s

c

a

l

e

r

+

1

)

=

36000

H

z

f_{TIM} = 72000000/(TIM_Prescaler+1) = 36000 Hz

fTIM​=72000000/(TIMP​rescaler+1)=36000Hz

即定時器的時鐘周期為:

t

T

I

M

=

1

/

f

T

I

M

=

1

/

36000

s

t_{TIM} = 1/f_{TIM} = 1/36000 s

tTIM​=1/fTIM​=1/36000s

因為定時器的 TIM_Period 設定為 255;

是以定時器的中斷周期為:

t

i

n

t

=

t

T

I

M

(

T

I

M

_

P

e

r

i

o

d

+

1

)

=

0.0071

s

tint= t_{TIM} * (TIM\_Period+1) =0.0071 s

tint=tTIM​∗(TIM_Period+1)=0.0071s

因為 PWM 表有

p

w

m

_

i

n

d

e

x

=

40

pwm\_index = 40

pwm_index=40 個亮度占空比資料,同種占空比信号輸出

p

e

r

i

o

d

_

c

n

t

=

10

period\_cnt =10

period_cnt=10 次

是以一個呼吸周期

T

=

t

i

n

t

40

10

=

2.844

s

T = tint *40 *10 = 2.844 s

T=tint∗40∗10=2.844s

5.2.4呼吸燈的實驗現象

将程式編譯好下載下傳到闆子中,可一看到LED像呼吸一樣漸漸變明或者漸漸變暗。

代碼擷取方法

1.長按下面二維碼,關注公衆号[嵌入式實驗樓]

2.在公衆号回複關鍵詞[STM32F1]擷取資料

歡迎通路我的網站:

BruceOu的哔哩哔哩

BruceOu的首頁

繼續閱讀