由於近期在準備海洋航行器比賽,正好趁此機會學習一下ARM,看到周圍很多同學都在使用32,所以我也買了一塊STM32F103ZET6,準備好好地學習一下。
STM32的時鐘系統相當的複雜,包含了5個時鐘源,分別是HSI HSE LSI LSE PLL,HSI是高速內部時鐘、RC振盪器,頻率爲8M,HSE是高速外部時鐘,即晶振,我的核心板上晶振爲8M。LSI爲低速內部時鐘、RC振盪器,頻率40k,LSE爲低速外部時鐘,接32.768kHz晶振,作爲RTC時鐘源。PLL爲鎖相環倍頻輸出,最大不超過72M。
我在學習定時器時先看的是TIM3,它掛載在APB1分頻器上,APB1上面掛載的是低速外設,APB2上掛載高速外設。
在system_stm32f10x.c文件下,有默認定義SYSCLK_FREQ_72MHz,同時在SystemInit()函數下調用了SetSysClock(),根據宏定義將時鐘設爲72M。
讀取SystemCoreClock變量即可獲得系統時鐘頻率。
在默認情況下,系統的各個時鐘頻率如下:
SYSCLK:72M
AHB:72M
APB1(PCLK1):36M
APB2(PCLK2):72M
PLL:72M
詳細的定時器設定如下:
①首先要搞清楚定時器的計數時鐘頻率,在預分頻係數≠1的時候,TIM2~7的時鐘頻率爲APB1的2倍,即72MHz,預分頻係數的默認值不是1,但我並未查到該如何設置該值。
②定時器的設置主要包括定時器的初始化和中斷的初始化。
2.1 定時器初始化:
首先定義TIM_TimeBaseInitTypeDef類型的結構體,它包含了如下的內容:
typedef struct { uint16_t TIM_Prescaler; uint16_t TIM_CounterMode; uint16_t TIM_Period; uint16_t TIM_ClockDivision; uint8_t TIM_RepetitionCounter; } TIM_TimeBaseInitTypeDef;
第一項TIM_Prescaler是預分頻值,它與TIM_Period(重載週期值)的乘積即爲計數的總值。
第二項TIM_CounterMode爲計數模式,它的內容如下:
#define TIM_CounterMode_Up ((uint16_t)0x0000) #define TIM_CounterMode_Down ((uint16_t)0x0010) #define TIM_CounterMode_CenterAligned1 ((uint16_t)0x0020) #define TIM_CounterMode_CenterAligned2 ((uint16_t)0x0040) #define TIM_CounterMode_CenterAligned3 ((uint16_t)0x0060)
後面三項爲中心對齊模式,指的是計數到一定的值,產生溢出事件,再向下計數到0。常用的爲向上計數模式,即TIM_CounterMode_Up
第四項TIM_ClockDivision爲時鐘分割,對於時鐘分割沒有查到太多的描述,一般設定爲TIM_CKD_DIV1,或者直接填入0x0000。
第五項TIM_RepetitionCounter爲PWM模式的一些設定,一般的定時器不用設置。
除此之外還要設置中斷的類型,一般的定時器爲更新中斷,即由溢出事件產生的中斷,設置的方式爲:TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE),其中第一項是定時器代號,第二項爲類型,這裏設定爲更新方式,第三項爲使能。
根據上述內容我們知道,初始化的過程如下:
3
TIM_TimeBaseInitTypeDef TIM_STR; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//TIM3掛載在APB1上 //由於預分頻係數默認不是1,所以TIM3的時鐘爲2*APB1=72M TIM_STR.TIM_Period=arr; TIM_STR.TIM_Prescaler=psc; //(arr+1)*(psc+1)/TIM時鐘=定時器溢出中斷觸發週期 TIM_STR.TIM_ClockDivision=TIM_CKD_DIV1; TIM_STR.TIM_CounterMode=TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3,&TIM_STR); TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
2.2 中斷初始化:
中斷初始化爲操作NVIC(嵌套向量中斷控制器)函數。 設置方式如下:
NVIC_STR.NVIC_IRQChannel=TIM3_IRQn;//設定爲TIM3中斷 NVIC_STR.NVIC_IRQChannelPreemptionPriority=0;//先佔優先級0級 NVIC_STR.NVIC_IRQChannelSubPriority=3;//從優先級3級 NVIC_STR.NVIC_IRQChannelCmd=ENABLE;//IRQ通道時能 NVIC_Init(&NVIC_STR);//中斷初始化 TIM_Cmd(TIM3,ENABLE);//TIM3定時器使能
2.3 把這些都封裝成一個函數,既可作爲TIM3的初始化函數。如下:
void TIM3_Init(u16 arr,u16 psc) { //定時時間=(arr+1)*(psc+1)/72 單位爲us TIM_TimeBaseInitTypeDef TIM_STR; NVIC_InitTypeDef NVIC_STR; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //初始化定時器 TIM_STR.TIM_Period=arr; TIM_STR.TIM_Prescaler=psc; TIM_STR.TIM_ClockDivision=TIM_CKD_DIV1; TIM_STR.TIM_CounterMode=TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3,&TIM_STR); TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //初始化中斷 NVIC_STR.NVIC_IRQChannel=TIM3_IRQn; NVIC_STR.NVIC_IRQChannelPreemptionPriority=0; NVIC_STR.NVIC_IRQChannelSubPriority=3; NVIC_STR.NVIC_IRQChannelCmd=ENABLE; NVIC_Init(&NVIC_STR); //使能定時器 TIM_Cmd(TIM3,ENABLE); }
③中斷服務函數:
中斷函數的名字,TIM3的爲TIM3_IRQHandler
中斷服務函數內包含了:判斷是否發生中斷、中斷髮生後執行的內容、清除標誌位三部分。
首先是判斷是否發生了更新中斷,利用庫函數TIM_GetITStatus(P1,P2),它的參數P1爲代號,這裏是TIM3,P2爲中斷類型,這裏爲更新中斷TIM_IT_Update,當它爲1時即發生了更新中斷,這裏爲了增強可讀性,採用一個RESET代表0,當函數返回值不是RESET的時候,即發生了置位(中斷)。
清除標誌位採用的是庫函數TIM_ClearITPendingBit(P1,P2),參數與判斷的函數一樣。
具體函數如下:
void TIM3_IRQHandler(void) { if(TIM_GetITStatus(TIM3,TIM_IT_Update)!=RESET) { TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //在這裏執行中斷內容 } }
④完成調用
只要在main函數裏調用TIM3_Init()函數,並填入適當的參數,即可實現精確的定時中斷,例如獲得一秒,即72M個數字中斷一次,可分解爲10000*7200,配置如下:
TIM3_Init(9999,7199);