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

標準庫3.5實現(xiàn):

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

5.1 PWM_輸出

5.1.1 PWM輸出的工作原理

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

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

每個定時器有四個通道,每一個通道都有一個捕獲比較寄存器,,將寄存器值和計數(shù)器值比較,通過比較結果輸出高低電平,便可以實現(xiàn)脈沖寬度調(diào)制模式(PWM信號)。

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

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

圖1向上計數(shù)模式

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

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

至此一個PWM周期完成。針對PWM重點關注兩個寄存器,<font color=#DD0000>TIMx_ARR寄存器確定PWM頻率,TIMx_CCRx寄存器確定占空比。</font>

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

  • PWM模式1(向上計數(shù)) :計數(shù)器從0計數(shù)加到自動重裝載值(TIMx_ARR),然后重新從0開始計數(shù),并且產(chǎn)生一個計數(shù)器溢出事。

  • PWM模式2(向下計數(shù)) :計數(shù)器從自動重裝載值(TIMx_ARR)減到0,然后重新從重裝載值(TIMx_ARR)開始遞減,并且產(chǎn)生一個計數(shù)器溢出事件。

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

STM32F1系列共有8個定時器:

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

5.1.2 STM32Cube生成工程

本文介紹在STM32CubeMX進行定時器的配置,這里我們僅利用 TIM3的 4路通道輸出,方便我們比較波形。具體不同定時器對應引腳在對應芯片數(shù)據(jù)手冊的引腳說明(pin description) 中查看。

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

1.設置RCC

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

圖2 RCC配置

2.時鐘配置

筆者的板子使用的外部晶振為8MHz,選擇外部時鐘HSE 8MHz ,PLL鎖相環(huán)9倍頻后為72MHz,系統(tǒng)時鐘來源選擇為PLL,設置APB1分頻器為 /2,這時候定時器的時鐘頻率為72Mhz。本文筆者使用的定時器是TIM3,TIM3掛在APB1上,不同的定時器掛在不同總線上的。

圖 3時鐘配置

3.Times配置

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

圖4使能TIM3時鐘源

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

選項1 :Internal Clock 內(nèi)部時鐘
選項2 : ETR2 外部觸發(fā)輸入(ETR)(僅適用TIM2,3,4)

本文要使用TIM3的四個通道,因此需要將其使能。每個通道有很多模式,這里選擇PWM輸出。當對應的通道打開后,對應的GPIO也會被使能。

圖5使能TIM3的通道

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

PWM參數(shù)配置如下:

  • Counter setting

Prtscaler (定時器分頻系數(shù)) : 0
Counter Mode(計數(shù)模式) :Up(向上計數(shù)模式)
Counter Period(自動重裝載值) : 999
CKD(時鐘分頻因子) : No Division 不分頻
選項: 可以選擇二分頻和四分頻
auto-reload-preload(自動重裝載) : Enable 使能

  • TRGO Output (TRGO) Parameters

Master/Slave Mode(MSM bit):Disable
TRGO:定時器的觸發(fā)信號輸出 在定時器的定時時間到達的時候輸出一個信號(如:定時器更新產(chǎn)生TRGO信號來觸發(fā)ADC的同步轉(zhuǎn)換,)

  • PWM Generation Channel (四個CH)

Mode(定時模式):PWM mode 1
Pulse(計數(shù)比較值):四個通道分別為500,375,250,125
CH Polarity(輸出極性):High

圖6 PWM輸出參數(shù)配置

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

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

這里我們arr=999, psc=0, Tclk=72Mhz ,

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

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

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

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

5.1.3 PWM輸出的具體代碼分析

我們先看看主函數(shù),其代碼如下:


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 */

}

在主循環(huán)前面,需要對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()函數(shù),這個函數(shù)包含了TIM3的PWM配置,具體再講。

5.1.4 PWM輸出的實驗現(xiàn)象

現(xiàn)在,TIM3 的通道 1(PA.06)、2(PA.07)、3(PB.00)、4(PB.01)就會輸出不同占空比的 PWM 信號了。PWM 信號可以通過示波器看到??紤]到并不是每個用戶手頭上都有示波器,我們在這里采用軟件仿真的方式來驗證我們的程序。

以前我們都是通過 J_LINK 直接將我們的代碼燒到開發(fā)板的 Flash 中去調(diào)試,現(xiàn)在要換成軟件仿真,得首先設置一下我們的開發(fā)環(huán)境,按照如下步驟所示。

1)點擊 Target Options 選項圖標,選中 Debug 選項卡,選中 Use Simulator 選項,按圖中所示進行設置,然后點擊“OK”按鈕,見圖7所示。

圖7模擬調(diào)試設置

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。

圖8 端口輸出配置

需要注意的是Display Range的設置,默認的設置是看不到波形的。

3)設置完信號源之后,點擊 RUN 按鈕,仿真信號即出來了,當信號出來之后,可點擊STOP 按鈕,讓信號不再變化,方便觀察。其中 In、Out、All 這三個按鈕可以調(diào)節(jié)顯示信號的疏密程度,見圖9。

圖9波形輸出

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

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

5.2呼吸燈

5.2.1呼吸燈的工作原理

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

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

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

圖10正弦曲線與指數(shù)曲線

相對來說,使用下凹函數(shù)曲線燈光處于暗的狀態(tài)更長,所以指數(shù)函數(shù)的曲線更符合我們呼吸燈的亮度變化要求。

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

5.2.2 STM32Cube生成工程

和上一節(jié)內(nèi)容差不多,稍微有些不同罷了。

1.設置RCC

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

圖11 RCC配置

2.時鐘配置

筆者的板子使用的外部晶振為8MHz,選擇外部時鐘HSE 8MHz ,PLL鎖相環(huán)9倍頻后為72MHz,系統(tǒng)時鐘來源選擇為PLL,設置APB1分頻器為 /2,這時候定時器的時鐘頻率為72Mhz。本文筆者使用的定時器是TIM3,TIM3掛在APB1上,不同的定時器掛在不同總線上的。

圖12時鐘配置

3.Times配置

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

圖13使能TIM3時鐘源

本節(jié)要使用TIM3的CH3通道,因此需要將其使能。這里選擇PWM輸出。當對應的通道打開后,對應的GPIO也會被使能。筆者的板子的LED接到了PB0上,這里要根據(jù)自己的板子來配置TIM和CH。

圖14使能TIM3的CH3通道

PWM參數(shù)配置如下:

  • Counter setting

Prtscaler (定時器分頻系數(shù)) : 1999
Counter Mode(計數(shù)模式) :Up(向上計數(shù)模式)
Counter Period(自動重裝載值) : 255
CKD(時鐘分頻因子) : No Division 不分頻
選項: 可以選擇二分頻和四分頻
auto-reload-preload(自動重裝載) : Enable 使能

  • TRGO Output (TRGO) Parameters

Master/Slave Mode(MSM bit):Disable
TRGO:定時器的觸發(fā)信號輸出 在定時器的定時時間到達的時候輸出一個信號(如:定時器更新產(chǎn)生TRGO信號來觸發(fā)ADC的同步轉(zhuǎn)換,)

  • PWM Generation Channel (四個CH)

Mode(定時模式):PWM mode 1
Pulse(計數(shù)比較值):0
CH Polarity(輸出極性):High

圖15 PWM輸出參數(shù)配置

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

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

這里我們 arr=255 , psc=1999, Tclk=72Mhz ,

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

這個要開啟中斷。

圖16 開啟TIM3 的中斷

另外將設置GPIO的速度為高速。

圖17設置GPIO速度

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

5.2.3呼吸燈的具體代碼分析

我們還是先看看主函數(shù)。


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 */

}

主函數(shù)和上一節(jié)的差不多,還好了幾句,這個只需初始化TIM3的CH3。那么是哪里不同呢,在回答這個問題之前,先看看呼吸燈的編程流程。

1)硬件初始化,系統(tǒng)時鐘初始化;

2)GPIO初始化,TIM3以及PWM初始化;

3)啟動定時器和PWM相應通道。

4)調(diào)用中斷回調(diào)函數(shù),不斷改變TIMx_CCR寄存器的值。

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

1.生成指數(shù)曲線 PWM 數(shù)據(jù)

要實現(xiàn) LED 亮度隨著指數(shù)曲線變化,我們需要使用占空比呈指數(shù)曲線變化的 PWM 信號,而這樣的信號由定時器經(jīng)過查表產(chǎn)生。這個表的數(shù)據(jù)存儲在程序中的數(shù)組 indexWave中。

圖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 個數(shù)字,從圖中可以看到這些數(shù)字呈指數(shù)上升再衰減,正好是呼吸燈的一個控制周期。數(shù)字的大小范圍是 0~ 255,即把 LED 的亮度分為了 0 ~ 255 個等級。

假如我們把定時器的脈沖計數(shù)器 TIMx_CNT 上限設置為 255,把這個表的數(shù)據(jù)一個一個地賦值到定時器的比較寄存器 TIMx_CCR 中,那么在每個 PWM 周期中,當 TIMx_CNT的計數(shù)值小于比較寄存器 TIMx_CCR 的值時, 就會在通道中輸出低電平,點亮 LED,而隨著 TIMx_CCR 的值由 LED 亮度表得來,所以 LED 點亮的時間就會呈圖中的曲線變化,實現(xiàn)呼吸燈的功能。

這個表的數(shù)據(jù)是使用 matlab 軟件生成的。該代碼運行后會生成一個“index_wave.c”的文件,用戶把該文件中的數(shù)據(jù)復制到工程中的數(shù)組中即可。


%本代碼用于產(chǎn)生呼吸燈使用的指數(shù)函數(shù)數(shù)據(jù)

clear;

x = [0 : 8/19 : 8];      %設置序列 ,指數(shù)上升

up = 2.^x ;              %求上升指數(shù)序列 

up = uint8(up);          %化為8位數(shù)據(jù)

y = [8: -8/19 :0];      %設置序列 ,指數(shù)下降

down = 2.^y ;            %求下降指數(shù)序列

down = uint8(down);      %化為8位數(shù)據(jù)

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和定時器

硬件初始化,系統(tǒng)時鐘初始化就不說了,我們看看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()函數(shù)有兩部分內(nèi)容,一部分TIM的Counter配置,這部分內(nèi)容和上一章是一樣的,我們重點關注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:選擇空閑狀態(tài)下的非工作狀態(tài)。

  • OCNIdleState:設置非空閑狀態(tài)下的非工作狀態(tài)。

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

3.PWM輸出中斷回調(diào)函數(shù)

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


void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)

{

  static uint8_t pwm_index = 1; /* 用于PWM查表 */

static uint8_t period_cnt = 0; /* 用于計算周期數(shù) */

  period_cnt++;

  /* 若輸出的周期數(shù)大于10,輸出下一種脈沖寬的PWM波 */

  if(period_cnt >= 10)

  {

    /* 根據(jù)PWM表修改定時器的比較寄存器值 */

    __HAL_TIM_SET_COMPARE(htim,TIM_CHANNEL_3,indexWave[pwm_index]);


    /* 標志PWM表的下一個元素 */

    pwm_index++;

    /* 若PWM脈沖表已經(jīng)輸出完成一遍,重置PWM查表標志 */

    if( pwm_index >=  40)

    {

      pwm_index=0;

    }

    /* 重置周期計數(shù)標志 */

    period_cnt=0;

  }

}

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

本代碼的目的是每 10 次定時器中斷更新一次 PWM 表中的數(shù)據(jù)到比較寄存器TIMx_CCR 中,當遍歷完 PWM 表的 40 個元素時,再重頭開始遍歷 PWM 表,周而復始,重復 LED 的呼吸過程。

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

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

所以定時器的時鐘頻率:fTIM = 72000000/(TIM_Prescaler+1) = 36000 Hz

即定時器的時鐘周期為:tTIM = 1/fTIM = 1/36000 s

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

所以定時器的中斷周期為:tint= tTIM * (TIM_Period+1) =0.0071 s

因為 PWM 表有 pwm_index = 40 個亮度占空比數(shù)據(jù),同種占空比信號輸出 period_cnt =10

所以一個呼吸周期 T = tint *40 *10 = 2.844 s

5.2.4呼吸燈的實驗現(xiàn)象

將程序編譯好下載到板子中,可一看到LED像呼吸一樣漸漸變明或者漸漸變暗。


代碼獲取方式
1.關注公眾號[嵌入式實驗樓]
2.在公眾號回復關鍵詞[STM32F1]獲取資料


歡迎訪問我的網(wǎng)站:

BruceOu的嗶哩嗶哩
BruceOu的主頁
BruceOu的博客
CSDN博客
簡書

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容