一溉卓、定時器簡介
STM32F1 系列中,除了互聯(lián)型的產(chǎn)品搬泥,共有 8
個定時器的诵,分為基本定時器,通用定時器和高級定時器佑钾。
基本定時器 TIM6
和 TIM7
是一個 16 位的只能向上計數(shù)的定時器西疤,只能定時,沒有外部 IO休溶。
通用定時器 TIM2/3/4/5
是一個 16 位的可以向上/下計數(shù)的定時器代赁,可以定時,可以輸出比較兽掰,可以輸入捕捉芭碍,每個定時器有四個外部 IO。
高級定時器 TIM1/8
是一個 16 位的可以向上/下計數(shù)的定時器孽尽,可以定時窖壕,可以輸出比較,可以輸入捕捉杉女,還可以有三相電機互補輸出信號瞻讽,每個定時器有 8 個外部 IO。
二熏挎、新建工程
1. 打開 STM32CubeMX 軟件速勇,點擊“新建工程”
2. 選擇 MCU 和封裝
3. 配置時鐘
RCC 設(shè)置,選擇 HSE(外部高速時鐘) 為 Crystal/Ceramic Resonator(晶振/陶瓷諧振器)
選擇 Clock Configuration坎拐,配置系統(tǒng)時鐘 SYSCLK 為 72MHz
修改 HCLK 的值為 72 后烦磁,輸入回車养匈,軟件會自動修改所有配置
4. 配置調(diào)試模式
非常重要的一步,否則會造成第一次燒錄程序后續(xù)無法識別調(diào)試器
SYS 設(shè)置都伪,選擇 Debug 為 Serial Wire
5. 配置GPIO
GPIO 設(shè)置呕乎,在右邊圖中找到 LED 燈對應引腳,選擇 GPIO_Output陨晶,輸出低電平點亮猬仁,可以添加自定義標簽
三、TIM6基本定時器
3.1 參數(shù)配置
在 Timers
中選擇 TIM6
設(shè)置珍逸,并勾選 Activated
激活
在 Parameter Settings
進行具體參數(shù)配置逐虚。
Tclk 即內(nèi)部時鐘CK_INT聋溜,經(jīng)過APB1預分頻器后分頻提供谆膳,如果APB1預分頻系數(shù)等于1,則頻率不變撮躁,否則頻率乘以2漱病,庫函數(shù)中APB1預分頻的系數(shù)是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(時鐘預分頻數(shù)):72-1
則驅(qū)動計數(shù)器的時鐘 CK_CNT = CK_INT(即72MHz)/(71+1) = 1MHz
-
Counter Mode(計數(shù)模式):Up(向上計數(shù)模式)
基本定時器只能是向上計數(shù)
-
Counter Period(自動重裝載值):1000-1
則定時時間 1/CK_CLK*(999+1) = 1ms
- auto-reload-preload(自動重裝載):Enable(使能)
-
TRGO Parameters(觸發(fā)輸出):不使能
在定時器的定時時間到達的時候輸出一個信號(如:定時器更新產(chǎn)生TRGO信號來觸發(fā)ADC的同步轉(zhuǎn)換)
3.2 配置NVIC
使能定時器中斷
3.3 生成代碼
輸入項目名和項目路徑
選擇應用的 IDE 開發(fā)環(huán)境 MDK-ARM V5
每個外設(shè)生成獨立的
’.c/.h’
文件不勾:所有初始化代碼都生成在 main.c
勾選:初始化代碼生成在對應的外設(shè)文件。 如 GPIO 初始化代碼生成在 gpio.c 中嗤军。
點擊 GENERATE CODE 生成代碼
3.4 修改中斷回調(diào)函數(shù)
打開 stm32f1xx_it.c
中斷服務(wù)函數(shù)文件注盈,找到 TIM6 中斷的服務(wù)函數(shù) TIM6_IRQHandler()
中斷服務(wù)函數(shù)里面就調(diào)用了定時器中斷處理函數(shù) HAL_TIM_IRQHandler()
打開 stm32f1xx_hal_tim.c
文件,找到定時器中斷處理函數(shù)原型 HAL_TIM_IRQHandler()
叙赚,其主要作用就是判斷是哪個定時器產(chǎn)生哪種事件中斷老客,清除中斷標識位,然后調(diào)用中斷回調(diào)函數(shù) 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
*/
這個函數(shù)不應該被改變胧砰,如果需要使用回調(diào)函數(shù),請重新在用戶文件中實現(xiàn)該函數(shù)苇瓣。
HAL_TIM_PeriodElapsedCallback()
按照官方提示我們應該再次定義該函數(shù)尉间,__weak
是一個弱化標識,帶有這個的函數(shù)就是一個弱化函數(shù)击罪,就是你可以在其他地方寫一個名稱和參數(shù)都一模一樣的函數(shù)哲嘲,編譯器就會忽略這一個函數(shù),而去執(zhí)行你寫的那個函數(shù)媳禁;而 UNUSED(htim)
撤蚊,這就是一個防報錯的定義,當傳進來的定時器號沒有做任何處理的時候损话,編譯器也不會報出警告侦啸。其實我們在開發(fā)的時候已經(jīng)不需要去理會中斷服務(wù)函數(shù)了槽唾,只需要找到這個中斷回調(diào)函數(shù)并將其重寫即可而這個回調(diào)函數(shù)還有一點非常便利的地方這里沒有體現(xiàn)出來,就是當同時有多個中斷使能的時候光涂,STM32CubeMX會自動地將幾個中斷的服務(wù)函數(shù)規(guī)整到一起并調(diào)用一個回調(diào)函數(shù)庞萍,也就是無論幾個中斷,我們只需要重寫一個回調(diào)函并判斷傳進來的定時器號即可忘闻。
接下來我們就在 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燈翻轉(zhuǎn)一次
{
HAL_GPIO_TogglePin(LED_G_GPIO_Port,LED_G_Pin);
time = 0;
}
}
}
/* USER CODE END 1 */
3.5 添加定時器啟動函數(shù)
現(xiàn)在進入 main 函數(shù)并在 while 循環(huán)前加入開啟定時器函數(shù) HAL_TIM_Base_Start_IT()
钝计,這里所傳入的 htim6 就是剛剛定時器初始化后的結(jié)構(gòu)體。
/**
* @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 */
}
現(xiàn)在實驗現(xiàn)象是每1秒LED燈翻轉(zhuǎn)一次
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總線齐佳。
*/
// 開啟定時器時鐘,即內(nèi)部時鐘 CK_INT=72M
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
/*
預分頻將輸入時鐘頻率按1~65536之間的值任意分頻,分頻值決定了計數(shù)頻率私恬。
計數(shù)值為計數(shù)的個數(shù),當計數(shù)寄存器的值達到計數(shù)值時,產(chǎn)生溢出,發(fā)生中斷。
如系統(tǒng)時鐘為72MHz,預分頻 TIM_Prescaler = 71,
計數(shù)值 TIM_Period = 1000,
則 TIM_Period * (TIM_Prescaler + 1) / 72000000 = 0.001,
即每1ms產(chǎn)生一次中斷炼吴。
*/
// 自動重裝載寄存器周的值(計數(shù)值)
TIM_TimeBaseStructure.TIM_Period = 1000;
// 累計 TIM_Period 個頻率后產(chǎn)生一個更新或者中斷
// 時鐘預分頻數(shù)為 71本鸣,
// 則驅(qū)動計數(shù)器的時鐘 CK_CNT = CK_INT / (71+1)=1M
TIM_TimeBaseStructure.TIM_Prescaler = 71;
// 時鐘分頻因子 ,基本定時器沒有硅蹦,不用管
//TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
// 計數(shù)器計數(shù)模式荣德,基本定時器只能向上計數(shù),沒有計數(shù)模式的設(shè)置
//TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
// 重復計數(shù)器的值童芹,基本定時器沒有涮瞻,不用管
//TIM_TimeBaseStructure.TIM_RepetitionCounter=0;
/*
完成時基設(shè)置
*/
// 初始化定時器
TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure);
/*
為了避免在設(shè)置時進入中斷,這里需要清除中斷標志位。
如果是向上計數(shù)模式(基本定時器采用向上計數(shù)),
則采用函數(shù) TIM_ClearFlag(TIM6, TIM_FLAG_Update),
清除向上溢出中斷標志假褪。
*/
// 清除計數(shù)器中斷標志位
TIM_ClearFlag(TIM6, TIM_FLAG_Update);
// 使能計數(shù)器
TIM_ITConfig(TIM6,TIM_IT_Update,ENABLE);
// 開啟計數(shù)器
TIM_Cmd(TIM6, ENABLE);
// 暫時關(guān)閉定時器的時鐘署咽,等待使用
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, DISABLE);
}
/**
@brief NVIC初始化(使用TIM6基本定時器)
@param 無
@return 無
*/
void BASIC_TIM_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
// 設(shè)置中斷組為 0
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
// 設(shè)置中斷來源
NVIC_InitStructure.NVIC_IRQChannel = TIM6_IRQn ;
// 設(shè)置主優(yōu)先級為 0
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
// 設(shè)置搶占優(yōu)先級為 3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
#define BASIC_TIM_IRQHandler TIM6_IRQHandler
// 1ms發(fā)生一次中斷,time 記錄中斷次數(shù)
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實戰(zhàn)教程(四)——基本定時器(還是點燈)
《嵌入式-STM32開發(fā)指南》第二部分 基礎(chǔ)篇 - 第4章 定時器(HAL庫)