android 通過串列埠來控制pwm的輸出_探索者 STM32F407 開發板資料連載第十四章 PWM 輸出實驗...
1)實驗平臺:alientek 阿波羅 STM32F767 開發板
2)摘自《STM32F7 開發指南(HAL 庫版)》關注官方微訊號公眾號,獲取更多資料:正點原子
第十四章 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 所示:
圖 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
所示:
圖 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 所示:
圖 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 所示:
圖 14.1.4 暫存器 TIM14_ CCR1 各位描述
在輸出模式下,該暫存器的值與 CNT 的值比較,根據比較結果產生相應動作。利用這點,
我們通過修改這個暫存器的值,就可以控制 PWM 的輸出脈寬了。
如果是通用定時器,則配置以上三個暫存器就夠了,但是如果是高階定時器,則還需要配
置:剎車和死區暫存器(TIMx_BDTR),該暫存器各位描述如圖 14.1.5 所示:
圖 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 可以從資料手
冊表中檢視:
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 所示:
圖 14.4.1 PWM 控制 DS0 亮度