15.1 輸入捕獲概述
系統滴答定時器一般用來提供“心跳”作用,而STM32定時器最基本功能也是定時,可以設置不同時間長度的定時。定時器除了最基本的定時功能外,定時器與GPIO有掛鉤使得它可以發揮強大的作用,比如可以輸出不同頻率、不同佔空比的方波信號、PWM信號,同時做爲輸入捕獲功能時,可以測量脈衝寬度、實現電容按鍵檢測等等。
STM32的輸入捕獲,簡單的說就是通過檢測 TIMx_CHx(定時器X的通道X)上的邊沿信號,在邊沿信號發生跳變(比如上升沿/下降沿)的時候,將當前定時器的值(TIMx_CNT)存放到對應的通道的捕獲/比較寄存器(TIMx_CCRx)裏面,完成一次捕獲。同時還可以配置捕獲時是否觸發中斷/DMA 等。
輸入捕獲說的通俗一點就是用計數器(定時器)來記錄某一個脈衝高電平的時間,或者我們只捕獲脈衝的上升沿或者下降沿,這具體要根據具體事例進行分析。
15.2 輸入捕獲過程
輸入捕獲的基本工作過程就是先捕捉一次脈衝上升沿,然後計數器開始計時,等待着捕捉到脈衝下降沿,等到捕捉到下降沿的時候,計數器停止計數,計算計數器中的數值,這個數值就是高電平所持續的時間,然後再重現開始下一輪的捕捉。
例如,要配置向上計數器在TI2輸入端的上升沿計數,使用下列步驟:
1、配置TIMx_CCMR1寄存器CC2S=’01’,配置通道2檢測TI2輸入的上升沿
2、配置TIMx_CCMR1寄存器的IC2F[3:0],選擇輸入濾波器帶寬(如果不需要濾波器,保持 IC2F=0000即無濾波器,以fDTS 採樣)
3、配置TIMx_CCER寄存器的CC2P=’0’,選定上升沿極性
4、 配置TIMx_SMCR寄存器的SMS=’111’,選擇定時器外部時鐘模式1
5、 配置TIMx_SMCR寄存器中的TS=’110’,選定TI2作爲觸發輸入
6、 設置TIMx_CR1寄存器的CEN=’1’,啓動計數器
當上升沿出現在TI2,計數器計數一次,且TIF標誌被設置。 在TI2的上升沿和計數器實際時鐘之間的延時,取決於在TI2輸入端的重新同步電路。
15.3 輸入捕獲配置步驟
1. 新建兩個文件,cap.c 和 cap.h
2. 在頭文件 cap.h 添加下面代碼:
3. 把 cap.c 添加到工程中
4. 在 cap.c 中添加以下代碼:
#include "cap.h"
u8 TIM2_CAPTURE_STA=0; //輸入捕獲狀態
u16 TIM2_CAPTURE_VAL; //輸入捕獲值
void cap_init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure; //定義GPIO結構體
NVIC_InitTypeDef NVIC_InitStructure; //定義中斷優先級結構體
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //定義TIM2定時器結構體
TIM_ICInitTypeDef TIM_ICInitStructure; //定義輸入捕獲結構體
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置中斷優先級分組
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //使能TIM2時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能GPIOA時鐘
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PA0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //下拉輸入模式
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化GPIOA
GPIO_ResetBits(GPIOA,GPIO_Pin_0); //PA0下拉
TIM_TimeBaseStructure.TIM_Period = arr; //設置在下一個更新事件活動的自動裝載寄存器週期的值
TIM_TimeBaseStructure.TIM_Prescaler = psc; //設置用來作爲TIMx時鐘頻率除數的預分頻值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //設置時鐘分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上計數
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure); //根據指定的參數初始化TIMx的時間基數單位
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; //選擇輸入端IC1映射到TI1上
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕獲
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置輸入分頻,不分頻
TIM_ICInitStructure.TIM_ICFilter = 0x00; //IC1F=0000配置輸入濾波器 不濾波
TIM_ICInit(TIM2,&TIM_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寄存器
TIM_ITConfig(TIM2,TIM_IT_Update|TIM_IT_CC1,ENABLE); //允許更新中斷CC1IE捕獲中斷
TIM_Cmd(TIM2,ENABLE ); //使能定時器2
}
void TIM2_IRQHandler(void) //TIM2中斷服務函數
{
if((TIM2_CAPTURE_STA & 0X80) == 0) //還未成功捕獲
{
if((TIM_GetITStatus(TIM2,TIM_IT_Update) != RESET))
{
if(TIM2_CAPTURE_STA & 0X40) //已經捕獲到高電平了
{
if((TIM2_CAPTURE_STA & 0X3F) == 0X3F) //高電平太長了
{
TIM2_CAPTURE_STA |= 0X80; //標記成功捕獲了一次
TIM2_CAPTURE_VAL = 0XFFFF;
}
else TIM2_CAPTURE_STA++;
}
}
if(TIM_GetITStatus(TIM2,TIM_IT_CC1) != RESET) //捕獲 1 發生捕獲事件
{
if(TIM2_CAPTURE_STA & 0X40) //捕獲到一個下降沿
{
TIM2_CAPTURE_STA |= 0X80; //標記成功捕獲到一次上升沿
TIM2_CAPTURE_VAL = TIM_GetCapture1(TIM2);
TIM_OC1PolarityConfig(TIM2,TIM_ICPolarity_Rising);//CC1P=0 設置爲上升沿捕獲
}
else //還未開始,第一次捕獲上升沿
{
TIM2_CAPTURE_STA = 0; //清空
TIM2_CAPTURE_VAL = 0;
TIM_SetCounter(TIM2,0);
TIM2_CAPTURE_STA |= 0X40; //標記捕獲到了上升沿
TIM_OC1PolarityConfig(TIM2,TIM_ICPolarity_Falling); //CC1P=1 設置爲下降沿捕獲
}
}
}
TIM_ClearITPendingBit(TIM2,TIM_IT_Update|TIM_IT_CC1); //清除中斷標誌位
}
其中,TIM2_CAPTURE_VAL:就是用來記錄當前計數器中的數值;
TIM2_CAPTURE_STA:是用來記錄捕獲狀態,把它當成一個寄存器那樣來使用。 TIM2_CAPTURE_STA 各位描述下表 所示:
從圖中我們可以看到,TIM2_CAPTURE_STA的最高位是捕獲完成的標誌位,如果我們目前已經完成了一次高電平的捕獲(即捕捉到了一次上升沿和一次下降沿),那麼該位置1,否則置0;TIM2_CAPTURE_STA的次高位是捕獲到高電平標誌位,如果我們現在已經捕獲到了一次上升沿,那麼就把該位置1,然後再把捕獲的極性改爲下降沿,然後就等待着下一個中斷的發生(每捕捉到一次邊沿就發生一次中斷),我們就這樣一直依靠這兩個標誌位來進行上升沿和下降沿的捕獲。
然後還有一個問題就是,如果某一個脈衝高電平的時間太長了,已經超出了我們定時器(計數器)所能記錄的範圍,那麼計數器就會產生溢出,而這個溢出的次數需要記錄下來,後面計算高電平時間要用到這個溢出次數,所以我們就用TIM2_CAPTURE_STA的後六位來進行記錄計數器溢出的次數,如果高電平持續時間仍然提高,導致溢出的次數也超過了TIM2_CAPTURE_STA後六位所能表示的數值範圍,那麼這個時候,就讓程序強制結束此次高電平的捕獲,即認爲這個脈衝的高電平時間是計數器的最大值。
這個中斷服務函數裏面包含了兩個中斷,一個是捕獲中斷,一個是溢出中斷,在進入到這個中斷服務函數裏面,我們首先進行判斷是發生了哪種中斷,然後再進行相應的操作。
5. 實現 TIM2 通道 1 輸入捕獲功能
我們使用 TIM2 通道 1 輸入捕獲功能來捕獲 TIM3 通道 2 輸出的 PWM 波形。因此,需要把 PB05 接到 PA0 端子上,切記斷電接線!
#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
#include "tim.h"
#include "key.h"
#include "pwm.h"
#include "usart.h"
#include "cap.h"
extern u8 TIM2_CAPTURE_STA;
extern u16 TIM2_CAPTURE_VAL;
int main(void)
{
u32 temp=0;
delay_init();
usart_init(115200);
PWM_Init(900-1,0); //設置 80kHz PWM波形
cap_init(0XFFFF,72-1); //以 1MHz 的頻率計數
printf("SYSTEM Init Complete.\r\n");
while(1)
{
delay_ms(10);
TIM_SetCompare2(TIM3,TIM_GetCapture2(TIM3) + 1);
if(TIM_GetCapture2(TIM3)==300)TIM_SetCompare2(TIM3,0);
if(TIM2_CAPTURE_STA & 0X80) //成功捕獲到了一次上升沿
{
temp = TIM2_CAPTURE_STA&0X3F;
temp *= 65536; //溢出時間總和
temp += TIM2_CAPTURE_VAL; //得到總的高電平時間
printf("HIGH:%d us\r\n",temp); //打印總的高點平時間
TIM2_CAPTURE_STA = 0; //開啓下一次捕獲
}
}
}
在 main.c 文件中我們主要就是進行相關初始化函數的調用以及進行高電平時間的計算。其中主要部分就是高電平時間的計算,它就是計數器溢出次數乘以計數器的最大值,然後再加上當前計數器中的數值,最後把標誌位清零。
歡迎關注微信公衆號『OpenSSR』