一、定時器簡介
STM32F1 系列中,除了互聯型的產品,共有 8
個定時器,分為基本定時器,通用定時器和高級定時器。
基本定時器 TIM6
和 TIM7
是一個 16 位的只能向上計數的定時器,只能定時,沒有外部 IO。
通用定時器 TIM2/3/4/5
是一個 16 位的可以向上/下計數的定時器,可以定時,可以輸出比較,可以輸入捕捉,每個定時器有四個外部 IO。
高級定時器 TIM1/8
是一個 16 位的可以向上/下計數的定時器,可以定時,可以輸出比較,可以輸入捕捉,還可以有三相電機互補輸出信號,每個定時器有 8 個外部 IO。
二、新建工程
1. 打開 STM32CubeMX 軟件,點擊“新建工程”
2. 選擇 MCU 和封裝
3. 配置時鐘
RCC 設置,選擇 HSE(外部高速時鐘) 為 Crystal/Ceramic Resonator(晶振/陶瓷諧振器)
選擇 Clock Configuration,配置系統時鐘 SYSCLK 為 72MHz
修改 HCLK 的值為 72 后,輸入回車,軟件會自動修改所有配置
4. 配置調試模式
非常重要的一步,否則會造成第一次燒錄程序后續無法識別調試器
SYS 設置,選擇 Debug 為 Serial Wire
5. 配置GPIO
GPIO 設置,在右邊圖中找到 LED 燈對應引腳,選擇 GPIO_Output,輸出低電平點亮,可以添加自定義標簽
三、TIM6基本定時器
3.1 參數配置
在 Timers
中選擇 TIM6
設置,并勾選 Activated
激活
在 Parameter Settings
進行具體參數配置。
Tclk 即內部時鐘CK_INT,經過APB1預分頻器后分頻提供,如果APB1預分頻系數等于1,則頻率不變,否則頻率乘以2,庫函數中APB1預分頻的系數是2,即PCLK1=36M,如圖所以定時器時鐘Tclk=36*2=72M。
定時器溢出時間:
Tout = 1 / (Tclk / (psc + 1)) ? (arr + 1)
- 定時器時鐘Tclk:72MHz
- 預分頻器psc:71
- 自動重裝載寄存器arr:999
即 Tout = 1/(72MHz/(71+1))?(999+1) = 1ms
-
Prescaler(時鐘預分頻數):72-1
則驅動計數器的時鐘 CK_CNT = CK_INT(即72MHz)/(71+1) = 1MHz
-
Counter Mode(計數模式):Up(向上計數模式)
基本定時器只能是向上計數
-
Counter Period(自動重裝載值):1000-1
則定時時間 1/CK_CLK*(999+1) = 1ms
- auto-reload-preload(自動重裝載):Enable(使能)
-
TRGO Parameters(觸發輸出):不使能
在定時器的定時時間到達的時候輸出一個信號(如:定時器更新產生TRGO信號來觸發ADC的同步轉換)
3.2 配置NVIC
使能定時器中斷
3.3 生成代碼
輸入項目名和項目路徑
選擇應用的 IDE 開發環境 MDK-ARM V5
每個外設生成獨立的
’.c/.h’
文件不勾:所有初始化代碼都生成在 main.c
勾選:初始化代碼生成在對應的外設文件。 如 GPIO 初始化代碼生成在 gpio.c 中。
點擊 GENERATE CODE 生成代碼
3.4 修改中斷回調函數
打開 stm32f1xx_it.c
中斷服務函數文件,找到 TIM6 中斷的服務函數 TIM6_IRQHandler()
中斷服務函數里面就調用了定時器中斷處理函數 HAL_TIM_IRQHandler()
打開 stm32f1xx_hal_tim.c
文件,找到定時器中斷處理函數原型 HAL_TIM_IRQHandler()
,其主要作用就是判斷是哪個定時器產生哪種事件中斷,清除中斷標識位,然后調用中斷回調函數 HAL_TIM_PeriodElapsedCallback()
。
/* NOTE: This function Should not be modified, when the callback is needed,
the HAL_GPIO_EXTI_Callback could be implemented in the user file
*/
這個函數不應該被改變,如果需要使用回調函數,請重新在用戶文件中實現該函數。
HAL_TIM_PeriodElapsedCallback()
按照官方提示我們應該再次定義該函數,__weak
是一個弱化標識,帶有這個的函數就是一個弱化函數,就是你可以在其他地方寫一個名稱和參數都一模一樣的函數,編譯器就會忽略這一個函數,而去執行你寫的那個函數;而 UNUSED(htim)
,這就是一個防報錯的定義,當傳進來的定時器號沒有做任何處理的時候,編譯器也不會報出警告。其實我們在開發的時候已經不需要去理會中斷服務函數了,只需要找到這個中斷回調函數并將其重寫即可而這個回調函數還有一點非常便利的地方這里沒有體現出來,就是當同時有多個中斷使能的時候,STM32CubeMX會自動地將幾個中斷的服務函數規整到一起并調用一個回調函數,也就是無論幾個中斷,我們只需要重寫一個回調函并判斷傳進來的定時器號即可。
接下來我們就在 stm32f1xx_it.c
這個文件的最下面添加 HAL_TIM_PeriodElapsedCallback()
/* USER CODE BEGIN 1 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static uint32_t time = 0;
if(htim->Instance == TIM6) // 定時器6基地址
{
// 自定義應用程序
time++; // 每1ms進來1次
if(time == 1000) // 每1秒LED燈翻轉一次
{
HAL_GPIO_TogglePin(LED_G_GPIO_Port,LED_G_Pin);
time = 0;
}
}
}
/* USER CODE END 1 */
3.5 添加定時器啟動函數
現在進入 main 函數并在 while 循環前加入開啟定時器函數 HAL_TIM_Base_Start_IT()
,這里所傳入的 htim6 就是剛剛定時器初始化后的結構體。
/**
* @brief The application entry point.
* @retval int
*/
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_TIM6_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim6);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
現在實驗現象是每1秒LED燈翻轉一次
3.6 HAL庫與標準庫代碼比較
STM32CubeMX 使用 HAL 庫生成的代碼:
/**
* @brief TIM6 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM6_Init(void)
{
/* USER CODE BEGIN TIM6_Init 0 */
/* USER CODE END TIM6_Init 0 */
TIM_MasterConfigTypeDef sMasterConfig = {0};
/* USER CODE BEGIN TIM6_Init 1 */
/* USER CODE END TIM6_Init 1 */
htim6.Instance = TIM6;
htim6.Init.Prescaler = 72-1;
htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
htim6.Init.Period = 1000-1;
htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM6_Init 2 */
/* USER CODE END TIM6_Init 2 */
}
/**
* @brief This function handles TIM6 global interrupt.
*/
void TIM6_IRQHandler(void)
{
/* USER CODE BEGIN TIM6_IRQn 0 */
/* USER CODE END TIM6_IRQn 0 */
HAL_TIM_IRQHandler(&htim6);
/* USER CODE BEGIN TIM6_IRQn 1 */
/* USER CODE END TIM6_IRQn 1 */
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static uint32_t time = 0;
if(htim->Instance == TIM6)
{
// 自定義應用程序
time++;
if(time == 1000)
{
HAL_GPIO_TogglePin(LED_G_GPIO_Port,LED_G_Pin);
time = 0;
}
}
}
HAL_TIM_Base_Start_IT(&htim6);
使用 STM32 標準庫的代碼:
/**
@brief 定時器中斷配置(使用TIM6基本定時器)
@param 無
@return 無
*/
void BASIC_TIM_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
/*
可以從上圖看出基本定時器和通用定時器使用APB1總線,
高級定時器使用APB2總線。
*/
// 開啟定時器時鐘,即內部時鐘 CK_INT=72M
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
/*
預分頻將輸入時鐘頻率按1~65536之間的值任意分頻,分頻值決定了計數頻率。
計數值為計數的個數,當計數寄存器的值達到計數值時,產生溢出,發生中斷。
如系統時鐘為72MHz,預分頻 TIM_Prescaler = 71,
計數值 TIM_Period = 1000,
則 TIM_Period * (TIM_Prescaler + 1) / 72000000 = 0.001,
即每1ms產生一次中斷。
*/
// 自動重裝載寄存器周的值(計數值)
TIM_TimeBaseStructure.TIM_Period = 1000;
// 累計 TIM_Period 個頻率后產生一個更新或者中斷
// 時鐘預分頻數為 71,
// 則驅動計數器的時鐘 CK_CNT = CK_INT / (71+1)=1M
TIM_TimeBaseStructure.TIM_Prescaler = 71;
// 時鐘分頻因子 ,基本定時器沒有,不用管
//TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
// 計數器計數模式,基本定時器只能向上計數,沒有計數模式的設置
//TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
// 重復計數器的值,基本定時器沒有,不用管
//TIM_TimeBaseStructure.TIM_RepetitionCounter=0;
/*
完成時基設置
*/
// 初始化定時器
TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure);
/*
為了避免在設置時進入中斷,這里需要清除中斷標志位。
如果是向上計數模式(基本定時器采用向上計數),
則采用函數 TIM_ClearFlag(TIM6, TIM_FLAG_Update),
清除向上溢出中斷標志。
*/
// 清除計數器中斷標志位
TIM_ClearFlag(TIM6, TIM_FLAG_Update);
// 使能計數器
TIM_ITConfig(TIM6,TIM_IT_Update,ENABLE);
// 開啟計數器
TIM_Cmd(TIM6, ENABLE);
// 暫時關閉定時器的時鐘,等待使用
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, DISABLE);
}
/**
@brief NVIC初始化(使用TIM6基本定時器)
@param 無
@return 無
*/
void BASIC_TIM_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
// 設置中斷組為 0
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
// 設置中斷來源
NVIC_InitStructure.NVIC_IRQChannel = TIM6_IRQn ;
// 設置主優先級為 0
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
// 設置搶占優先級為 3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
#define BASIC_TIM_IRQHandler TIM6_IRQHandler
// 1ms發生一次中斷,time 記錄中斷次數
uint16_t time;
void BASIC_TIM_IRQHandler(void)
{
if(TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET)
{
time++;
TIM_ClearITPendingBit(TIM6, TIM_FLAG_Update);
}
}
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
MX_TIM6_Init();
對應 BASIC_TIM_Config();BASIC_TIM_NVIC_Config();
HAL_TIM_Base_Init(&htim6)
對應 TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure)
HAL_TIM_Base_Start_IT(&htim6);
對應 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
四、注意事項
用戶代碼要加在 USER CODE BEGIN N
和 USER CODE END N
之間,否則下次使用 STM32CubeMX 重新生成代碼后,會被刪除。
? 由 Leung 寫于 2021 年 1 月 14 日
? 參考:STM32CubeMX系列教程3:基本定時器
STM32CubeMX實戰教程(四)——基本定時器(還是點燈)
《嵌入式-STM32開發指南》第二部分 基礎篇 - 第4章 定時器(HAL庫)