@TOC 在第七章咱們介紹了 STM32 的定時器做爲 PWM 輸出的使用方法,這一章,咱們將向你們介紹通用定時器做爲輸入捕獲的使用。這個輸入捕獲在哪一個地方會用到呢?若是你玩太小車就會知道小車的編碼器測速就用到了定時器的輸入捕獲對不對!!在本章中,咱們將用 TIM2 的通道 1(PA0)來作輸入捕獲,捕獲 PA0 上高電平的脈寬。markdown
輸入捕獲模式能夠用來測量脈衝寬度或者測量頻率。STM32的定時器,除了TIM6和TIM7,其餘定時器都有輸入捕獲功能。STM32 的輸入捕獲,簡單的說就是經過檢測 TIMx_CHx 上的邊沿信號,在邊沿信號發生跳變(好比上升沿/降低沿)的時候,將當前定時器的值(TIMx_CNT)存放到對應的通道的捕獲/比較寄存器(TIMx_CCRx)裏面,完成一次捕獲。 函數
當捕獲通道 TIx(如TIM2_CH1) 上出現上升沿時,發生第一次捕獲,計數器 CNT 的值會被鎖存到捕獲寄存器 CCR 中,並且還會進入捕獲中斷,在中斷服務程序中記錄一次捕獲(能夠用一個標誌變量來記錄),並把捕獲寄存器中的值讀取到 value1中。當出現第二次上升沿時,發生第二次捕獲,計數器 CNT 的值會再次被鎖存到捕獲寄存器 CCR 中,並再次進入捕獲中斷,在捕獲中斷中,把捕獲寄存器的值讀取到 value3 中,並清除捕獲記錄標誌。利用 value3和value1 的差值咱們就能夠算出信號的週期(頻率)。oop
當捕獲通道 TIx(如TIM2_CH1) 上出現上升沿時,發生第一次捕獲,計數器 CNT 的值會被鎖存到捕獲寄存器 CCR 中,並且還會進入捕獲中斷,在中斷服務程序中記錄一次捕獲(能夠用一個標誌變量來記錄),並把捕獲寄存器中的值讀取到 value1中。而後把捕獲邊沿改變爲降低沿捕獲,目的是捕獲後面的降低沿。當降低沿到來的時候,發生第二次捕獲,計數器 CNT的值會再次被鎖存到捕獲寄存器 CCR中,並再次進入捕獲中斷,在捕獲中斷中,把捕獲寄存器的值讀取到 value3中,並清除捕獲記錄標誌。而後把捕獲邊沿設置爲上升沿捕獲。 在測量脈寬過程當中須要來回的切換捕獲邊沿的極性,若是測量的脈寬時間比較長,定時器就會發生溢出,溢出的時候會產生更新中斷,咱們能夠在中斷裏面對溢出進行記錄處理。 本章咱們用到 TIM2_CH1 來捕獲高電平脈寬,也就是要先設置輸入捕獲爲上升沿檢測,記錄發生上升沿的時候 TIM2_CNT 的值。而後配置捕獲信號爲降低沿捕獲,當降低沿到來時,發生捕獲,並記錄此時的 TIM2_CNT 值。這樣,先後兩次 TIM2_CNT 之差,就是高電平的脈寬,同時 TIM2 的計數頻率咱們是知道的,從而能夠計算出高電平脈寬的準確時間。須要用到的寄存器有:TIM2_ARR、TIM2_PSC、TIM2_CCMR一、TIM2_CCER、TIM2_DIER、TIM2_CR一、TIM2_CCR1。 首先 TIMx_ARR 和 TIMx_PSC,這兩個寄存器用來設自動重裝載值和 TIMx 的時鐘分頻,用法我在前面的第6、第七章已經講過了,這裏就不作贅述了。ui
該寄存器通常有 2 個:TIMx _CCMR1和 TIMx _CCMR2。TIMx_CCMR1 控制 CH1(通道1) 和CH2(通道2),而 TIMx_CCMR2 控制 CH3(通道3) 和 CH4(通道4)。 一、CC1S[1:0],這兩個位用於 配置CCR1 的通道方向,這裏咱們設置 IC1S[1:0]=01,也就是配置爲輸入。 二、IC1PSC[1:0],設置輸入捕獲 1 預分頻器。咱們是 1 次邊沿就觸發 1 次捕獲,因此選擇 00 就是了。 三、IC1F[3:0],這個位用於 配置輸入採樣頻率和數字濾波器長度,這裏,咱們不作濾波處理,因此設置 IC1F[3:0]=0000,只要採集到上升沿,就觸發捕獲編碼
捕獲使能寄存器,顧名思義就是使能輸入捕獲的寄存器了。這個寄存器在輸入捕獲這裏只用到了CC1P和CCE1這兩個位。要使能輸入捕獲,必須設置 CC1E=1,而 CC1P 則根據本身的須要來配置。 spa
咱們須要用到中斷來處理捕獲數據,因此必須開啓通道 1 的捕獲比較中斷,即 CC1IE 設置爲 1,其餘的位不用管。 設計
控制寄存器TIMx_CR1,咱們只用到了它的最低位,也就是用來使能定時器的。 3d
捕獲/比較寄存器 1:TIMx_CCR1,該寄存器用來存儲捕獲發生時,TIMx_CNT的值,咱們從 TIMx_CCR1 就能夠讀出通道 1 捕獲發生時刻的 TIMx_CNT 值,經過兩次捕獲(一次上升沿捕獲,一次降低沿捕獲)的差值,就能夠計算出高電平脈衝的寬度。 code
咱們文本的例子是定時器2的通道1,對應PA0管腳。orm
要使用 TIM2,咱們必須先開啓 TIM2的時鐘,這點相信你們看了這麼多代碼,應該明白了。
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//使能定時器2的時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能PA端口時鐘
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PA0 清除以前設置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0 輸入
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_ResetBits(GPIOA,GPIO_Pin_0); //PA0 下拉
複製代碼
在開啓了 TIM2的時鐘以後,咱們要設置 ARR 和 PSC 兩個寄存器的值來控制輸入捕獲的週期。這在庫函數是經過 TIM_TimeBaseInit 函數實現的,在上一節定時器中斷章節已經有講解過,這裏就不詳細講解,調用的格式爲:
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Period = arr; //設定計數器自動重裝值
TIM_TimeBaseStructure.TIM_Prescaler =psc;//預分頻器
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //設置時鐘分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上計數模式
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
複製代碼
輸入比較參數的設置包括映射關係,濾波,分頻以及捕獲方式等。這裏咱們須要設置通道 1爲輸入模式,且 IC1 映射到 TI1(通道 1)上面,而且不使用濾波(提升響應速度)器,上升沿捕獲。庫函數是經過 IM_ICInit 函數來初始化輸入比較參數的:
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct); typedef struct {
uint16_t TIM_Channel;//咱們設置爲通道 1,爲 TIM_Channel_1。
uint16_t TIM_ICPolarity;//設置輸入信號的有效捕獲極性,這裏咱們設置爲TIM_ICPolarity_Rising,上升沿捕獲。
uint16_t TIM_ICSelection;//是用來設置映射關係,咱們配置 IC1 直接映射在TI1 上,選擇TIM_ICSelection_DirectTI。
uint16_t TIM_ICPrescaler;//用來設置輸入捕 獲分頻係數 ,咱們這裏不分頻 , 因此選中TIM_ICPSC_DIV1,還有 2,4,8 可選。
uint16_t TIM_ICFilter;//設置濾波器長度,這裏咱們不使用濾波器,因此設置爲 0。
} TIM_ICInitTypeDef;
複製代碼
具體的代碼爲
TIM_ICInitTypeDef TIM2_ICInitStructure;
TIM2_ICInitStructure.TIM_Channel = TIM_Channel_1; //選擇輸入端 IC1 映射到 TI1 上
TIM2_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕獲
TIM2_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到 TI1 上
TIM2_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置輸入分頻,不分頻
TIM2_ICInitStructure.TIM_ICFilter = 0;//IC1F=0000 配置輸入濾波器 不濾波
TIM_ICInit(TIM2, &TIM2_ICInitStructure);
複製代碼
爲啥要使能捕獲和更新中斷? 由於防止定時器溢出啊!! 爲啥會溢出啊? 由於咱們要捕獲的是高電平信號的脈寬,因此,第一次捕獲是上升沿,第二次捕獲時降低沿,必須在捕獲上升沿以後,設置捕獲邊沿爲降低沿,同時,若是脈寬比較長,那麼定時器就會溢出,對溢出必須作處理,不然結果就不許了。這兩件事,咱們都在中斷裏面作,因此必須開啓捕獲中斷和更新中斷。 這樣就知道了吧!!!咱們使用定時器的開中斷函數 TIM_ITConfig 便可使能捕獲和更新中斷:
TIM_ITConfig( TIM2,TIM_IT_Update|TIM_IT_CC1,ENABLE);//容許更新中斷和捕獲中斷
複製代碼
最後,必須打開定時器的計數器開關, 啓動 TIM2 的計數器,開始輸入捕獲。
TIM_Cmd(TIM2,ENABLE ); //使能定時器 2
複製代碼
既然開啓了中斷就要編寫中斷服務函數啦!既然有中斷就要開有中斷優先級分組啦!設置中斷分組的方法前面屢次提到這裏咱們不作講解,主要是經過函數 NVIC_Init()來完成。分組完成後,咱們還須要在中斷函數裏面完成數據處理和捕獲設置等關鍵操做,從而實現高電平脈寬統計。在中斷服務函數裏面,跟之前的外部中斷和定時器中斷實驗中同樣,咱們在中斷開始的時候要進行中斷類型判斷,在中斷結束的時候要清除中斷標誌位。分別爲 TIM_GetITStatus()函數和 TIM_ClearITPendingBit()函數。
void TIM2_IRQHandler(void) {
if(TIM_GetITStatus(TIM2,TIM_IT_Update)!=0)
{
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
複製代碼
//定時器2通道1輸入捕獲配置
void TIM2_Cap_Init(u16 arr,u16 psc) {
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM2_ICInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//使能定時器和GPIO的時鐘
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //使能TIM2時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA時鐘
//初始化IO口,模式爲輸入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PA0 清除以前設置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0 輸入
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_ResetBits(GPIOA,GPIO_Pin_0); //PA0 下拉
//初始化定時器2 TIM2
TIM_TimeBaseStructure.TIM_Period = arr; //設定計數器自動重裝值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //預分頻器
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //設置時鐘分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上計數模式
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
//初始化TIM2輸入捕獲參數
TIM2_ICInitStructure.TIM_Channel = TIM_Channel_1; //CC1S=01 選擇輸入端 IC1映射到TI1上
TIM2_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕獲
TIM2_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上
TIM2_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置輸入分頻,不分頻
TIM2_ICInitStructure.TIM_ICFilter = 0x00;//IC1F=0000 配置輸入濾波器 不濾波
TIM_ICInit(TIM2, &TIM2_ICInitStructure);
//中斷分組初始化
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //TIM2中斷
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //先佔優先級2級
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //從優先級0級
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //根據NVIC_InitStruct中指定的參數初始化外設NVIC寄存器
TIM_ITConfig(TIM2,TIM_IT_Update|TIM_IT_CC1,ENABLE);//容許更新中斷 ,容許CC1IE捕獲中斷
TIM_Cmd(TIM2,ENABLE ); //使能定時器2
}
複製代碼
u8 Capture_State=0; //輸入捕獲狀態
u16 Capture_Value; //輸入捕獲值
//定時器2中斷服務程序
void TIM2_IRQHandler(void) {
if((Capture_State&0X80)==0)//還未成功捕獲 1000 0000
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
if(Capture_State&0X40)//已經捕獲到高電平了0100 0000
{
if((Capture_State&0X3F)==0X3F)//高電平太長了 0011 1111
{
Capture_State|=0X80;//標記成功捕獲了一次 1000 0000
Capture_Value=0XFFFF; //1111 1111
}
else
Capture_State++;
}
}
if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET)//捕獲1發生捕獲事件
{
if(Capture_State&0X40)//捕獲到一個降低沿 0100 0000
{
Capture_State|=0X80;//標記成功捕獲到一次上升沿1000 0000
Capture_Value=TIM_GetCapture1(TIM2);
TIM_OC1PolarityConfig(TIM2,TIM_ICPolarity_Rising); //CC1P=0 設置爲上升沿捕獲
}else //還未開始,第一次捕獲上升沿
{
Capture_State=0;//清空
Capture_Value=0;
TIM_SetCounter(TIM2,0);
Capture_State|=0X40;//標記捕獲到了上升沿0100 0000
TIM_OC1PolarityConfig(TIM2,TIM_ICPolarity_Falling); //CC1P=1 設置爲降低沿捕獲
}
}
}
TIM_ClearITPendingBit(TIM2, TIM_IT_CC1|TIM_IT_Update); //清除中斷標誌位
}
複製代碼
下面解釋一下中斷服務函數的過程: 首先設置兩個變量Capture_State和Capture_Value。 其中 Capture_State,是用來記錄捕獲狀態,該變量相似一個寄存器(其實就是個變量,只是咱們把它當成一個寄存器那樣來使用)。 另一個變量 Capture_Value,則用來記錄捕獲到降低沿的時候,TIM2_CNT的值。如今咱們來介紹一下,捕獲高電平脈寬的思路:首先,設置 TIM2_CH1 捕獲上升沿(這在TIM2的初始化函數執行的時候就設置好了),而後等待上升沿中捕獲斷到來,當捕獲到上升沿中斷,此時若是 Capture_State的第 6 位爲 0,則表示尚未捕獲到新的上升沿,就先把 Capture_State、Capture_Value和 TIM2->CNT 等清零,而後再設置 Capture_State的第 6 位爲 1,標記捕獲到高電平,最後設置爲降低沿捕獲,等待降低沿到來。若是等待降低沿到來期間,定時器發生了溢出,就在Capture_State裏面對溢出次數進行計數,當最大溢出次數來到的時候,就強制標記 捕獲完成 (雖然此時尚未捕獲到降低沿 )。 當降低沿到來的時候,先設置Capture_State的第 7 位爲 1,標記成功捕獲一次高電平,而後讀取此時的定時器的捕獲值到 Capture_Value裏面,最後設置爲上升沿捕獲,回到初始狀態。 這樣,咱們就完成一次高電平捕獲了,只要 Capture_State的第 7 位一直爲 1,那麼就不會進行第二次捕獲,咱們在main函數處理完捕獲數據後,將Capture_State置零,就能夠開啓第二次捕獲。
extern u8 Capture_State; //輸入捕獲狀態
extern u16 Capture_Value; //輸入捕獲值
int main(void) {
u32 temp=0;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 設置中斷優先級分組2
delay_init(); //延時函數初始化
TIM2_Cap_Init(0XFFFF,72-1); //以1Mhz的頻率計數
while(1)
{
delay_ms(10);
if(Capture_State&0X80)//成功捕獲到了一次高電平
{
temp=Capture_State&0X3F;
temp*=65536; //溢出時間總和
temp+=Capture_Value; //獲得總的高電平時間
printf("HIGH:%d us\r\n",temp); //打印總的高點平時間
Capture_State=0; //開啓下一次捕獲
}
}
}
複製代碼
該 main 函數將 TIM2_CH1 的捕獲計數器設計爲 1us 計數一次,並設置重裝載值爲最大,因此咱們的捕獲時間精度爲 1us。主函數經過 Capture_State的第 7 位,來判斷有沒有成功捕獲到一次高電平,若是成功捕獲,則將高電平時間經過串口輸出到電腦。 至此,咱們的軟件設計就完成了。