1. 程式人生 > 其它 >android 通過串列埠來控制pwm的輸出_探索者 STM32F407 開發板資料連載第十四章 PWM 輸出實驗...

android 通過串列埠來控制pwm的輸出_探索者 STM32F407 開發板資料連載第十四章 PWM 輸出實驗...

技術標籤:android 通過串列埠來控制pwm的輸出

1)實驗平臺:alientek 阿波羅 STM32F767 開發板

2)摘自《STM32F7 開發指南(HAL 庫版)》關注官方微訊號公眾號,獲取更多資料:正點原子

0af8d8412acb3d4e70db6ea52d0cd823.png

第十四章 PWM 輸出實驗

上一章,我們介紹了STM32F4的通用定時器TIM3,用該定時器的中斷來控制DS1的閃爍,

這一章,我們將向大家介紹如何使用 STM32F4 的 TIM3 來產生 PWM 輸出。在本章中,我們將

使用 TIM14 的通道 1 來產生 PWM 來控制 DS0 的亮度。本章分為如下幾個部分:

14.1 PWM 簡介

14.2 硬體設計

14.3 軟體設計

14.4 下載驗證

14.1 PWM 簡介

脈衝寬度調製(PWM),是英文“Pulse Width Modulation”的縮寫,簡稱脈寬調製,是利用

微處理器的數字輸出來對類比電路進行控制的一種非常有效的技術。簡單一點,就是對脈衝寬

度的控制,PWM 原理如圖 14.1.1 所示:

8118ca5569ec989b39a6351ef1209809.png

圖 14.1.1 PWM 原理示意圖

圖 14.1.1 就是一個簡單的 PWM 原理示意圖。圖中,我們假定定時器工作在向上計數 PWM

模式,且當 CNT=CCRx 時輸出 1。那麼就可以得到如上的 PWM

示意圖:當 CNT 值小於 CCRx 的時候,IO 輸出低電平(0),當 CNT 值大於等於 CCRx 的時候,

IO 輸出高電平(1),當 CNT 達到 ARR 值的時候,重新歸零,然後重新向上計數,依次迴圈。

改變 CCRx 的值,就可以改變 PWM 輸出的佔空比,改變 ARR 的值,就可以改變 PWM 輸出的

頻率,這就是 PWM 輸出的原理。

STM32F4 的定時器除了 TIM6 和 7。其他的定時器都可以用來產生 PWM 輸出。其中高階

定時器 TIM1 和 TIM8 可以同時產生多達 7 路的 PWM 輸出。而通用定時器也能同時產生多達 4

路的 PWM 輸出!這裡我們僅使用 TIM14 的 CH1 產生一路 PWM 輸出。

要使 STM32F4 的通用定時器 TIMx 產生 PWM 輸出,除了上一章介紹的暫存器外,我們還

會用到 3 個暫存器,來控制 PWM 的。這三個暫存器分別是:捕獲/比較模式暫存器

(TIMx_CCMR1/2)、捕獲/比較使能暫存器(TIMx_CCER)、捕獲/比較暫存器(TIMx_CCR1~4)。

接下來我們簡單介紹一下這三個暫存器。

首先是捕獲/比較模式暫存器(TIMx_CCMR1/2),該暫存器一般有 2 個:TIMx _CCMR1

和 TIMx _CCMR2,不過 TIM14 只有一個。TIMx_CCMR1 控制 CH1 和 2,而 TIMx_CCMR2

控制 CH3 和 4。以下我們將以 TIM14 為例進行介紹。TIM14_CCMR1 暫存器各位描述如圖 14.1.2

所示:

bff566356a887ab1fa1158376e93583a.png

圖 14.1.2 TIM14_CCMR1 暫存器各位描述

該暫存器的有些位在不同模式下,功能不一樣,所以在圖 14.1.2 中,我們把暫存器分了 2

層,上面一層對應輸出而下面的則對應輸入。關於該暫存器的詳細說明,請參考《STM32F4xx

中文參考手冊》第 476 頁,16.6.4 節。這裡我們需要說明的是模式設定位 OC1M,此部分由 3

位組成。總共可以配置成 7 種模式,我們使用的是 PWM 模式,所以這 3 位必須設定為 110/111。

這兩種 PWM 模式的區別就是輸出電平的極性相反。另外 CC1S 用於設定通道的方向(輸入/輸

出)預設設定為 0,就是設定通道作為輸出使用。注意:這裡是因為我們的 TIM14 只有 1 個通

道,所以才只有第八位有效,高八位無效,其他有多個通道的定時器,高八位也是有效的,具

體請參考《STM32F4xx 中文參考手冊》對應定時器的暫存器描述。

接下來,我們介紹 TIM14 的捕獲/比較使能暫存器(TIM14_CCER),該暫存器控制著各個

輸入輸出通道的開關。該暫存器的各位描述如圖 14.1.3 所示:

15698d9b55492b2a52eb87c29d96c6ff.png

圖 14.1.3 TIM14_ CCER 暫存器各位描述

該暫存器比較簡單,我們這裡只用到了 CC1E 位,該位是輸入/捕獲 1 輸出使能位,要想

PWM 從 IO 口輸出,這個位必須設定為 1,所以我們需要設定該位為 1。該暫存器更詳細的介

紹了,請參考《STM32F4xx 中文參考手冊》第 478 頁,16.6.5 這一節。同樣,因為 TIM14 只有

1 個通道,所以才只有低四位有效,如果是其他定時器,該暫存器的其他位也可能有效。

最後,我們介紹一下捕獲/比較暫存器(TIMx_CCR1~4),該暫存器總共有 4 個,對應 4 個

通道 CH1~4。不過 TIM14 只有一個,即:TIM14_CCR1,該暫存器的各位描述如圖 14.1.4 所示:

7698e2b101570f7f11f4f56acd616ba1.png

圖 14.1.4 暫存器 TIM14_ CCR1 各位描述

在輸出模式下,該暫存器的值與 CNT 的值比較,根據比較結果產生相應動作。利用這點,

我們通過修改這個暫存器的值,就可以控制 PWM 的輸出脈寬了。

如果是通用定時器,則配置以上三個暫存器就夠了,但是如果是高階定時器,則還需要配

置:剎車和死區暫存器(TIMx_BDTR),該暫存器各位描述如圖 14.1.5 所示:

f3142e8ea9765fdea28f051ddef15737.png

圖 14.1.5 暫存器 TIMx_ BDTR 各位描述

該暫存器,我們只需要關注最高位:MOE 位,要想高階定時器的 PWM 正常輸出,則必須

設定 MOE 位為 1,否則不會有輸出。注意:通用定時器不需要配置這個。其他位我們這裡就不

詳細介紹了,請參考《STM32F4xx 中文參考手冊》第 386 頁,14.4.18 這一節。

本章,我們使用的是 TIM14 的通道 1,所以我們需要修改 TIM14_CCR1 以實現脈寬控制

DS0 的亮度。至此,我們把本章要用的幾個相關暫存器都介紹完了,本章要實現通過 TIM14_CH1

輸出 PWM 來控制 DS0 的亮度。下面我們介紹通過庫函式來配置該功能的步驟。

首先要提到的是,PWM 實際跟上一章節一樣使用的是定時器的功能,所以相關的函式設

置同樣在庫函式檔案 stm32f4xx_hal_tim.h 和 stm32f4xx_hal_tim.c 檔案中。

1)開啟 TIM14 和 GPIO 時鐘,配置 PF9 選擇複用功能 AF9(TIM14)輸出。

要使用 TIM14,我們必須先開啟 TIM14 的時鐘,這點相信大家看了這麼多程式碼,應該明白

了。這裡我們還要配置 PF9 為複用(AF9)輸出,才可以實現 TIM14_CH1 的 PWM 經過 PF9

輸出。 HAL 庫使能 TIM14 時鐘和 GPIO 時鐘方法是:

__HAL_RCC_TIM14_CLK_ENABLE();//使能定時器 14__HAL_RCC_GPIOF_CLK_ENABLE();//開啟 GPIOB 時鐘

接下來便是要配置 PF9 複用對映為 TIM3 的 PWM 輸出引腳。關於 IO 口複用對映,在串列埠

通訊實驗中有詳細講解,主要是通過函式 HAL_GPIO_Init 來實現的:

GPIO_InitTypeDef GPIO_Initure;__HAL_RCC_TIM14_CLK_ENABLE();//使能定時器 14__HAL_RCC_GPIOF_CLK_ENABLE();//開啟 GPIOF 時鐘GPIO_Initure.Pin=GPIO_PIN_9;//PF9GPIO_Initure.Mode=GPIO_MODE_AF_PP;//複用推輓輸出GPIO_Initure.Pull=GPIO_PULLUP;//上拉GPIO_Initure.Speed=GPIO_SPEED_HIGH;//高速GPIO_Initure.Alternate= GPIO_AF9_TIM14;//PF9 複用為 TIM14_CH1HAL_GPIO_Init(GPIOF,&GPIO_Initure);

這裡還需要說明一下,對於定時器通道的引腳關係,大家可以檢視 STM32F4 對應的資料

手冊,比如我們 PWM 實驗,我們使用的是定時器 14 的通道 1,對應的引腳 PF9 可以從資料手

冊表中檢視:

fce3ce1fcc0f47d9713a3f29b83b5771.png

2)初始化 TIM14,設定 TIM14 的 ARR 和 PSC 等引數。

根據前面的講解,初始化定時器的 ARR 和 PSC 等引數是通過函式 HAL_TIM_Base_Init 來

實現的,但是這裡大家要注意,對於我們使用定時器的 PWM 輸出功能時,HAL 庫為我們提供

了一個獨立的定時器初始化函式 HAL_TIM_PWM_Init,該函式宣告為:

HAL_StatusTypeDef HAL_TIM_PWM_Init(TIM_HandleTypeDef *htim);

該函式實現的功能以及使用方法和 HAL_TIM_Base_Init 都是類似的,作用都是初始化定時

器 的 ARR 和 PSC 等參 數 。 為 什 麼 HAL 庫要 提 供 這 個 函 數 而 不直 接 讓 我 們 使 用

HAL_TIM_Base_Init 函式呢?

這 是 因 為 HAL 庫 為 定 時 器 的 PWM 輸 出 定 義 了 單 獨 的 MSP 回 調 函 數

HAL_TIM_PWM_MspInit,也就是說,當我們呼叫HAL_TIM_PWM_Init進行PWM初始化之後,

該函式內部會呼叫 MSP 回撥函式 HAL_TIM_PWM_MspInit。而當我們使用 HAL_TIM_Base_Init

初始化定時器引數的時候,它內部呼叫的回撥函式為 HAL_TIM_Base_MspInit,這裡大家注意

區分。

所以大家一定要注意,使用 HAL_TIM_PWM_Init 初始化定時器時,回撥函式為:

HAL_TIM_PWM_MspInit,該函式宣告為:

void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim);

一般情況下,上面步驟 1 的時鐘使能和 IO 口初始化對映都編寫在回撥函式內部。

3)設定 TIM14_CH1 的 PWM 模式,使能 TIM14 的 CH1 輸出。

接下來,我們要設定 TIM14_CH1 為 PWM 模式(預設是凍結的),因為我們的 DS0 是低電

平亮,而我們希望當 CCR1 的值小的時候,DS0 就暗,CCR1 值大的時候,DS0 就亮,所以我

們要通過配置 TIM14_CCMR1 的相關位來控制 TIM14_CH1 的模式。

在 HAL 庫中,PWM 通道設定是通過函式 HAL_TIM_PWM_ConfigChannel 來設定的:

HAL_StatusTypeDef HAL_TIM_PWM_ConfigChannel(TIM_HandleTypeDef *htim,TIM_OC_InitTypeDef* sConfig, uint32_t Channel);

第一個引數 htim 是定時器初始化控制代碼,也就是 TIM_HandleTypeDef 結構體指標型別,這

和 HAL_TIM_PWM_Init 函式呼叫時候引數儲存一致即可。

第二個引數 sConfig 是 TIM_OC_InitTypeDef 結構體指標型別,這也是該函式最重要的引數。

該引數用來設定 PWM 輸出模式,極性,比較值等重要引數。首先我們來看看結構體定義:

typedef struct{ uint32_t OCMode;//PWM 模式 uint32_t Pulse;//捕獲比較值 uint32_t OCPolarity;//極性 uint32_t OCNPolarity; uint32_t OCFastMode;//快速模式 uint32_t OCIdleState; uint32_t OCNIdleState;} TIM_OC_InitTypeDef;

該結構體成員我們重點關注前三個。成員變數 OCMode 用來設定模式,也就是我們前面講解的

7 種模式,這裡我們設定為 PWM 模式 1。成員變數 Pulse 用來設定捕獲比較值。成員變數

TIM_OCPolarity 用 來 設 置 輸 出 極 性 是 高 還 是 低 。 其 他 的 參 數 TIM_OutputNState ,

TIM_OCNPolarity,TIM_OCIdleState 和 TIM_OCNIdleState 是高階定時器才用到的。

第 三 個 參 數 Channel 用 來 選 擇 定 時 器 的 通 道 , 取 值 範 圍 為 TIM_CHANNEL_1~

TIM_CHANNEL_4。這裡我們使用的是定時器 14 的通道 1,所以取值為 TIM_CHANNEL_1 即

可。

例如我們要初始化定時器 14 的通道 1 為 PWM 模式 1,輸出極性為低,那麼例項程式碼為:

TIM_OC_InitTypeDef TIM14_CH1Handler; //定時器 14 通道 1 控制代碼TIM3_CH4Handler.OCMode=TIM_OCMODE_PWM1; //模式選擇 PWM1TIM3_CH4Handler.Pulse=arr/2; //設定比較值,此值用來確定佔空比TIM3_CH4Handler.OCPolarity=TIM_OCPOLARITY_LOW; //輸出比較極性為低HAL_TIM_PWM_ConfigChannel(&TIM3_Handler,&TIM3_CH4Handler,TIM_CHANNEL_4);

4)使能 TIM14。

在完成以上設定了之後,我們需要使能 TIM14。使能 TIM14 的方法前面已經講解過:

HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel);

該函式第二個入口引數 Channel 是用來設定要使能輸出的通道號。

對於單獨使能定時器的方法,在上一章定時器實驗我們已經講解。實際上,HAL 庫也同樣

提供了單獨使能定時器的輸出通道函式,函式為:

void TIM_CCxChannelCmd(TIM_TypeDef* TIMx, uint32_t Channel, uint32_t ChannelState);

5)修改 TIM14_CCR1 來控制佔空比。

最後,在經過以上設定之後,PWM 其實已經開始輸出了,只是其佔空比和頻率都是固定

的,而我們通過修改比較值 TIM14_CCR1 則可以控制 CH1 的輸出佔空比。繼而控制 DS0 的亮

度。HAL 庫中並沒有提供獨立的修改佔空比函式,這裡我們可以編寫這樣一個函式如下:

//設定 TIM 通道 4 的佔空比//compare:比較值void TIM_SetTIM14Compare1(u32 compare){TIM14->CCR1=compare;}

實際上,因為呼叫函式 HAL_TIM_PWM_ConfigChanne 進行 PWM 配置的時候可以設定比

較值,所以我們也可以直接使用該函式來達到修改佔空比的目的:

void TIM_SetCompare1(TIM_TypeDef *TIMx,u32 compare){TIM14_CH1Handler.Pulse=compare;HAL_TIM_PWM_ConfigChannel(&TIM14_Handler,&TIM14_CH1Handler,TIM_CHANNEL_1);}

這種方法因為要呼叫 HAL_TIM_PWM_ConfigChannel 函式對各種初始化引數進行重新設

置,所以大家在使用中一定要注意,例如在實時系統中如果多個執行緒同時修改初始化結構體相

關引數,可能導致結果混亂。

14.2 硬體設計

本實驗用到的硬體資源有:

1) 指示燈 DS0

2) 定時器 TIM14

這兩個我們前面都已經介紹了,因為 TIM14_CH1 可以通過 PF9 輸出 PWM,而 DS0 就是

直接節在 PF9 上面的,所以電路上並沒有任何變化。

14.3 軟體設計

開啟 PWM 輸出實驗程式碼可以看到,我們相比上一節,並沒有新增其他任何 HAL 庫檔案,

而是添加了幾個函式

Timer.c 原始檔程式碼如下://TIM14 PWM 部分初始化//arr:自動重灌值。//psc:時鐘預分頻數//定時器溢位時間計算方法:Tout=((arr+1)*(psc+1))/Ft us.//Ft=定時器工作頻率,單位:Mhzvoid TIM14_PWM_Init(u16 arr,u16 psc){ TIM14_Handler.Instance=TIM14;//定時器 14 TIM14_Handler.Init.Prescaler=psc;//定時器分頻 TIM14_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;//向上計數模式 TIM14_Handler.Init.Period=arr;//自動重灌載值 TIM14_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1; HAL_TIM_PWM_Init(&TIM14_Handler);//初始化 PWM TIM14_CH1Handler.OCMode=TIM_OCMODE_PWM1;//模式選擇 PWM1 TIM14_CH1Handler.Pulse=arr/2;//設定比較值,此值用來確定佔空比,預設比較值為自動重灌載值//的一半,即佔空比為 50% TIM14_CH1Handler.OCPolarity=TIM_OCPOLARITY_LOW; //輸出比較極性為低 HAL_TIM_PWM_ConfigChannel(&TIM14_Handler,&TIM14_CH1Handler,TIM_CHANNEL_1);//配置 TIM14 通道 1 HAL_TIM_PWM_Start(&TIM14_Handler,TIM_CHANNEL_1);//開啟 PWM 通道 1}//定時器底層驅動,時鐘使能,引腳配置//此函式會被 HAL_TIM_PWM_Init()呼叫//htim:定時器控制代碼void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim){GPIO_InitTypeDef GPIO_Initure;__HAL_RCC_TIM14_CLK_ENABLE();//使能定時器 14__HAL_RCC_GPIOF_CLK_ENABLE();//開啟 GPIOF 時鐘GPIO_Initure.Pin=GPIO_PIN_9; //PF9GPIO_Initure.Mode=GPIO_MODE_AF_PP; //複用推輓輸出GPIO_Initure.Pull=GPIO_PULLUP; //上拉GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速GPIO_Initure.Alternate= GPIO_AF9_TIM14;//PF9 複用為 TIM14_CH1HAL_GPIO_Init(GPIOF,&GPIO_Initure);}//設定 TIM 通道 4 的佔空比//compare:比較值void TIM_SetTIM14Compare1(u32 compare){TIM14->CCR1=compare;}

此部分程式碼包含了上面介紹的 PWM 輸出設定的前 5 個步驟。這裡我們關於 TIM14 的設定

就不再說了。

接下來,我們看看主程式裡面的 main 函式如下:

int main(void){u8 dir=1; u16 led0pwmval=0; HAL_Init(); //初始化 HAL 庫 Stm32_Clock_Init(336,8,2,7); //設定時鐘,168Mhzdelay_init(168); //初始化延時函式uart_init(115200); //初始化 USARTLED_Init();//初始化 LEDTIM3_Init(5000-1,8400-1); //定時器 3 初始化,週期為 500msTIM14_PWM_Init(500-1,84-1); //84M/84=1M 的計數頻率,自動重灌載為 500,//那麼 PWM 頻率為 1M/500=2kHZ while(1) {delay_ms(10);if(dir)led0pwmval++;//dir==1 led0pwmval 遞增else led0pwmval--;//dir==0 led0pwmval 遞減if(led0pwmval>300)dir=0;//led0pwmval 到達 300 後,方向為遞減if(led0pwmval==0)dir=1;//led0pwmval 遞減到 0 後,方向改為遞增TIM_SetTIM14Compare1(led0pwmval);//修改比較值,修改佔空比 }}

這裡,我們從死迴圈函式可以看出,我們將 led0pwmval 這個值設定為 PWM 比較值,也就

是通過 led0pwmval 來控制 PWM 的佔空比,然後控制 led0pwmval 的值從 0 變到 300,然後又

從 300 變到 0,如此迴圈,因此 DS0 的亮度也會跟著訊號的佔空比變化從暗變到亮,然後又從

亮變到暗。至於這裡的值,我們為什麼取 300,是因為 PWM 的輸出佔空比達到這個值的時候,

我們的 LED 亮度變化就不大了(雖然最大值可以設定到 499),因此設計過大的值在這裡是沒

必要的。至此,我們的軟體設計就完成了。

14.4 下載驗證

在完成軟體設計之後,將我們將編譯好的檔案下載到探索者 STM32F4 開發板上,觀看其

執行結果是否與我們編寫的一致。如果沒有錯誤,我們將看 DS0 不停的由暗變到亮,然後又從

亮變到暗。每個過程持續時間大概為 3 秒鐘左右。

實際執行結果如下圖 14.4.1 所示:

237b183252f4d24910509ef4dffe32a9.png

圖 14.4.1 PWM 控制 DS0 亮度