【STM32F103筆記】7、定時器之PWM輸出——做個呼吸燈

這一篇來介紹STM32的定時器,STM32的定時器分爲三類:

  • 基本定時器Basic timers):從0計數到預設的值,並觸發中斷或DMA,沒有其它功能,其內部與DAC相連,可以用於觸發DAC;
  • 通用定時器General-purpose timers):可以升序或者降序計數,可以用於輸入捕捉、PWM輸入、比較輸出、PWM輸出、單脈衝輸出等等功能;
  • 高級定時器Advanced-control timers):擁有基本定時器和通用定時器的全部功能,並有輸出PWM中插入死區、編碼器解碼以及三相6步電機驅動功能。

對於STM32F103C8T6來說,用於3個通用定時器和1個高級定時器(手冊中)。這一篇使用通用定時器來輸出不同佔空比的PWM波控制LED,達到呼吸燈的效果。

原理設計

在系統板上,PB8用於控制LED,查閱STM32F103C8T6手冊,可知PB8爲定時器4的第3通道輸出引腳:
在這裏插入圖片描述
因此需要對定時器4進行分析。

TIM4定時器

定時器時鐘

定時器4爲STM32的通用定時器,其內部結構如圖所示:
在這裏插入圖片描述
上面爲定時器的時鐘信號選擇部分,在第2篇中分析過,定時器掛載在APB1總線上,而APB1時鐘信號在SystemInit()函數初始化後爲36MHz,然後經過APB1總線的一個倍頻器,在初始化後這個倍頻器使APB1時鐘信號2倍頻後進入定時器;

也就是說,雖然APB1總線上的時鐘信號爲36MHz,但由於這個2倍頻器,最終進入定時器的時鐘信號爲72MHz,也就是上圖中CK_PSC時鐘信號爲72MHz。

CK_PSC時鐘信號經過PSC Prescaler分頻後,進入計數器,作爲計數器的時鐘信號。

定時器庫函數

基礎初始化結構體TIM_TimeBaseInitTypeDef

和其它外設初始化一樣,定時器初始化也有其初始化結構體,可以對照函數手冊進行分析,這裏直接貼上代碼:

/** 
  * @brief  TIM Time Base Init structure definition
  * @note   This structure is used with all TIMx except for TIM6 and TIM7.    
  */

typedef struct
{
  uint16_t TIM_Prescaler;         /*!< Specifies the prescaler value used to divide the TIM clock.
                                       This parameter can be a number between 0x0000 and 0xFFFF */

  uint16_t TIM_CounterMode;       /*!< Specifies the counter mode.
                                       This parameter can be a value of @ref TIM_Counter_Mode */

  uint16_t TIM_Period;            /*!< Specifies the period value to be loaded into the active
                                       Auto-Reload Register at the next update event.
                                       This parameter must be a number between 0x0000 and 0xFFFF.  */ 

  uint16_t TIM_ClockDivision;     /*!< Specifies the clock division.
                                      This parameter can be a value of @ref TIM_Clock_Division_CKD */

  uint8_t TIM_RepetitionCounter;  /*!< Specifies the repetition counter value. Each time the RCR downcounter
                                       reaches zero, an update event is generated and counting restarts
                                       from the RCR value (N).
                                       This means in PWM mode that (N+1) corresponds to:
                                          - the number of PWM periods in edge-aligned mode
                                          - the number of half PWM period in center-aligned mode
                                       This parameter must be a number between 0x00 and 0xFF. 
                                       @note This parameter is valid only for TIM1 and TIM8. */
} TIM_TimeBaseInitTypeDef;  
  • TIM_Prescaler:時鐘信號分頻值,也就是設置上面PSC Prescaler對應的值,這個值可以設置爲0x0000-0xFFFF,也就是0-65535,對應寄存器爲TIMx prescaler (TIMx_PSC)
    在這裏插入圖片描述
    經過PSC預分頻器,進入計數器的時鐘信號頻率爲:
    fCK_PSC/(PSC+1) f_{CK\_PSC}/(PSC+1)
    比如設置PSC的值爲71,那麼最後進入計數器的時鐘信號頻率即爲1MHz;
  • TIM_CounterMode:計數器計數方向,自加或者自減等;
  • TIM_Period:計數週期,也就是計數器一共計數多少,比如設置爲999且爲向上計數,那麼計數器從0計數到999+1,共計1000us,也就是計數器最後輸出PWM的頻率爲1KHz;
  • 剩下的參數在這裏不使用,暫時不進行說明。
輸出比較結構體

若使用PWM直接控制引腳輸出,則還需要另一個初始化結構體:

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

  uint16_t TIM_OutputState;   /*!< Specifies the TIM Output Compare state.
                                   This parameter can be a value of @ref TIM_Output_Compare_state */

  uint16_t TIM_OutputNState;  /*!< Specifies the TIM complementary Output Compare state.
                                   This parameter can be a value of @ref TIM_Output_Compare_N_state
                                   @note This parameter is valid only for TIM1 and TIM8. */

  uint16_t TIM_Pulse;         /*!< Specifies the pulse value to be loaded into the Capture Compare Register. 
                                   This parameter can be a number between 0x0000 and 0xFFFF */

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

  uint16_t TIM_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 TIM1 and TIM8. */

  uint16_t TIM_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 TIM1 and TIM8. */

  uint16_t TIM_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 TIM1 and TIM8. */
} TIM_OCInitTypeDef;
  • TIM_OCMode:輸出模式設置,這裏設置成PWM1輸出模式,即當計數器中的值小於等於設定的值時,引腳輸出有效電平,大於時輸出無效電平;
  • TIM_OutputState:設置爲使能輸出模式;
  • TIM_Pulse:即設定的值(取名爲Pulse可能意思是PWM有效電平脈寬吧),當計數器中計數值小於等於設定的值時,輸出有效電平;
  • TIM_OCPolarity:設置有效電平爲高電平還是低電平;
  • 剩下的參數在這裏不使用,暫時不進行說明。

程序設計

改進延時函數

在上一篇中,利用SysTick的中斷進行延時,而SysTick中斷可能影響其他中斷操作(雖然本篇沒用到中斷)。

因此首先對延時函數進行改進,SysTick可以配置成不觸發中斷的形式,此時可以不斷查詢SysTick寄存器CTRL的Bit 16,當Bit 16置1時說明計數結束,即延時完成:

void delay_us(uint32_t us)
{
	SysTick->LOAD = us * 9;
	// 設置SysTick8分頻,即9MHz,並使能SysTick開始計數
	SysTick->CTRL = SysTick_CTRL_ENABLE_Msk;
	while(!(SysTick->CTRL&(1<<16)));
	SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}
void delay_ms(uint32_t ms)
{
	SysTick->LOAD = ms * 9000;
	SysTick->CTRL = SysTick_CTRL_ENABLE_Msk;
	while(!(SysTick->CTRL&(1<<16)));
	SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}

這裏注意在配置CTRL寄存器時,應保持Bit 2爲0,即使用8分頻時鐘,否則LOAD寄存器可能超出範圍(最大值爲0xFFFFFF)。

初始化TIM4

根據上述兩個初始化結構體,對TIM4進行初始化:

void Timer4PWMConfig(void)
{
	TIM_TimeBaseInitTypeDef TIM4InitStruct;
	TIM_OCInitTypeDef TIM4OCInitStruct;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
	
	TIM4InitStruct.TIM_Prescaler = 71;  // 1MHz
	TIM4InitStruct.TIM_Period = 999;  //999+1=1000 - 1KHz
	TIM4InitStruct.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM4, &TIM4InitStruct);
	
	TIM4OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
	TIM4OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
	TIM4OCInitStruct.TIM_Pulse = 0;
	TIM4OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_Low;
	TIM_OC3Init(TIM4, &TIM4OCInitStruct);
	// 使能TIM4的通道3比較寄存器預加載
	TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);
	// 使能自動重載寄存器ARR
	TIM_ARRPreloadConfig(TIM4, ENABLE);
	// 使能TIM4
	TIM_Cmd(TIM4, ENABLE);
}

初始化PB8

這裏注意將PB8初始化爲複用推輓輸出(用於TIM4的輸出引腳):

void PB8LEDConfig(void)
{
	GPIO_InitTypeDef GPIOInitStruct;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_8;
	GPIOInitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIOInitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIOInitStruct);
}

完整程序

在main函數的while(1)循環中,根據sigmoid函數計算當前PWM脈寬,並重新設置通道3的TIM_Pulse(即TIM4的CCR3寄存器),運行爲呼吸燈的效果:

/* Includes ------------------------------------------------------------------*/
#include "stm32f10x.h"
#include "delay.h"
#include <math.h>

/* Functions decleration -----------------------------------------------------*/
void Timer4PWMConfig(void);
void PB8LEDConfig(void);

int main(void)
{
	uint16_t i;
	double temp;
	
	PB8LEDConfig();
	Timer4PWMConfig();
	
	while(1)
	{
		for(i=0; i<101; i++)
		{
			temp = 1000 / (1 + pow(2.71828, 5-(double)i/10));
			// 調用庫函數TIM_SetCompare3或者直接對CCR3寄存器賦值
			//TIM_SetCompare3((uint16_t)temp);
			TIM4->CCR3 = (uint16_t)temp;
			delay_ms(10);
		}
		for(i=0; i<101; i++)
		{
			temp = 1000 / (1 + pow(2.71828, (double)i/10-5));
			//TIM_SetCompare3((uint16_t)temp);
			TIM4->CCR3 = (uint16_t)temp;
			delay_ms(10);
		}
	}
}

void Timer4PWMConfig(void)
{
	TIM_TimeBaseInitTypeDef TIM4InitStruct;
	TIM_OCInitTypeDef TIM4OCInitStruct;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
	
	TIM4InitStruct.TIM_Prescaler = 71;  // 1MHz
	TIM4InitStruct.TIM_Period = 999;  //999+1=1000 - 1KHz
	TIM4InitStruct.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM4, &TIM4InitStruct);
	
	TIM4OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
	TIM4OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
	TIM4OCInitStruct.TIM_Pulse = 0;
	TIM4OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_Low;
	TIM_OC3Init(TIM4, &TIM4OCInitStruct);
	TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);
	TIM_ARRPreloadConfig(TIM4, ENABLE);
	TIM_Cmd(TIM4, ENABLE);
}

void PB8LEDConfig(void)
{
	GPIO_InitTypeDef GPIOInitStruct;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_8;
	GPIOInitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIOInitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIOInitStruct);
}

運行結果

編譯下載並運行程序,LED以呼吸燈的模式閃爍:
在這裏插入圖片描述

完結撒花✿✿ヽ(°▽°)ノ✿

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章