標準庫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ù)模式為例進行講解。

在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.設置RCC
設置高速外部時鐘HSE,選擇外部時鐘源。

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

3.Times配置
選擇TIM,使能TIM3,指定時鐘源。

【注】TIM2的時鐘源有兩個選項
選項1 :Internal Clock 內(nèi)部時鐘
選項2 : ETR2 外部觸發(fā)輸入(ETR)(僅適用TIM2,3,4)
本文要使用TIM3的四個通道,因此需要將其使能。每個通道有很多模式,這里選擇PWM輸出。當對應的通道打開后,對應的GPIO也會被使能。

【注】如果使能通道前通道中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

根據(jù)前面的參數(shù)配置,我們可以算出PWM的輸出周期:
這里我們 ,
本文選擇的是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所示。

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。

需要注意的是Display Range的設置,默認的設置是看不到波形的。
3)設置完信號源之后,點擊 RUN 按鈕,仿真信號即出來了,當信號出來之后,可點擊STOP 按鈕,讓信號不再變化,方便觀察。其中 In、Out、All 這三個按鈕可以調(diào)節(jié)顯示信號的疏密程度,見圖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ù)上升曲線及其對稱得到的下降曲線。

相對來說,使用下凹函數(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,選擇外部時鐘源。

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

3.Times配置
選擇TIM,使能TIM3,指定時鐘源。

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

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

根據(jù)前面的參數(shù)配置,我們可以算出PWM的輸出周期:
這里我們 ,
這個要開啟中斷。

另外將設置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中。

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;
所以定時器的時鐘頻率:
即定時器的時鐘周期為:
因為定時器的 TIM_Period 設置為 255;
所以定時器的中斷周期為:
因為 PWM 表有 個亮度占空比數(shù)據(jù),同種占空比信號輸出
次
所以一個呼吸周期
5.2.4呼吸燈的實驗現(xiàn)象
將程序編譯好下載到板子中,可一看到LED像呼吸一樣漸漸變明或者漸漸變暗。
代碼獲取方式
1.關注公眾號[嵌入式實驗樓]
2.在公眾號回復關鍵詞[STM32F1]獲取資料
歡迎訪問我的網(wǎng)站: