在输入捕获模式下,当检测到ICx信号上升/下降边沿时,计数器的当前值被存储在捕获比较寄存器TIMx_CCRx中。 当捕获事件发生时,相应的CCxIF 标志(TIMx_SR 寄存器) 被置1。如果中断或者DMA功能被使能,就会产生中断或者DMA请求。如果捕获发生时,CCxIF标志已经被置位,这时过采样标志CCxOF就会被置位。向CCxIF写0或者读去TIM_CCRx中的数据,将清除捕获标志。CCxOF位只能通过手动写入0进行清除。
假如我设置为上升沿捕获,那么当一个上升沿到来的时候,定时器当前的计数值(TIMx_CNT)就会写入TIMx_CCRx中。我们读取这个数据。等到下一个上升沿到了时,就会有另一个计数器值TIMx_CNT记录。根据这两个数据值差,我们能算出来输入数据的周期。当然,我们还有处理定时器溢出这个问题,定时器溢出了就不准了。
如果要测占空比,就需要同时捕获上升沿和下降沿。相邻两个上升沿之间的计数是输入的周期,相邻两次捕获(一个上升沿一个下降沿)之间的时间是占或者空的时间。根据这个可以计算占空比或者PPM之类的东西。 【实验内容】
本次实验,使用TIM4产生一个1K的频率输出,用TIM1进行捕获。并测出频率计算
TIM1 的时基单元配置:关于TIM1的时基设置问题前文已经讨论过了。这里只有一点需要明确的,就是为了尽量减少更新事件,将TIM_Period设置到最大即0xFFFF。定时器时钟设置成2M,这样定时器的更新频率就是30Hz,不会造成两次捕获之间产生多次更新。 TIM1的完整配置代码如下: void TIM1_ICConfig(void) {
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE); // //时基初始化
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //死区控制用。 TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器方向
TIM_TimeBaseInitStructure.TIM_Prescaler = 84-1; //Timer clock = sysclock /(TIM_Prescaler+1) = 2M TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInitStructure.TIM_Period = 0xFFFF; //Period = (TIM counter clock / TIM output clock) - 1 = 40Hz
TIM_TimeBaseInit(TIM1,&TIM_TimeBaseInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; TIM_ICInitStructure.TIM_ICFilter = 0;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInit(TIM1,&TIM_ICInitStructure); TIM_Cmd(TIM1,ENABLE); }
TIM4的配置就是基本的输出配置,没什么可说的。代码如下: void TIM4_OCConfig(void) {
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE); TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInitStructure.TIM_Prescaler = 0; TIM_TimeBaseInitStructure.TIM_RepetitionCounter =0;
TIM_TimeBaseInitStructure.TIM_Period = 42000-1; //周期:42M/(42000)= 1K
TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 10000;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
TIM_OC1Init(TIM4,&TIM_OCInitStructure); TIM_Cmd(TIM4,ENABLE); }
这个输出频率,在示波器上可以看到的:
之后就是在主函数里边进行测量了: CaptureNumber = 0; while(1) {
if(TIM_GetFlagStatus(TIM1,TIM_FLAG_CC1)==SET) {
TIM_ClearFlag(TIM1,TIM_FLAG_CC1); if(CaptureNumber == 0) {
counter = TIM_GetCapture1(TIM1); //第一次捕获 CaptureNumber = 1; }
else if(CaptureNumber == 1) //处理第二次捕获 {
if(TIM_GetFlagStatus(TIM1,TIM_FLAG_Update) != SET)//两次捕获间没有发生溢出的处理 {
Time = TIM_GetCapture1(TIM1); Time = Time - counter; } else {
TIM_ClearFlag(TIM1,TIM_FLAG_Update); //产生了更新事件
Time = 0xFFFF - counter + TIM_GetCapture1(TIM1)+1; //如果有更新事件产生时候的计算方式
}
CaptureNumber = 0; if(Time!=0) {
freq= 2000000/Time; //计算频率 }
freq = freq; //避免变量freq被编译器优化掉 } } }
通用定时器作为输入捕获的使用。我们将用 TIM5 的通道 1 (PA0)来做输入捕获,捕获 PA0 上高电平的脉宽(用 WK_UP 按键输入高电平),通过串口打印高电平脉宽时间 输入捕获简介
输入捕获模式可以用来测量脉冲宽度或者测量频率。 STM32 的定时器,除了 TIM6 和 TIM7,其他定时器都有输入捕获功能。STM32 的输入捕获,简单的说就是通过检测 TIMx_CHx 上的边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候,将当前定时器的值(TIMx_CNT)存放到对应的通道的捕获/比较寄存器(TIMx_CCRx)里面,完成一次捕获。同时还可以配置捕获时是否触发中断/DMA 等。
我们用到 TIM5_CH1 来捕获高电平脉宽,也就是要先设置输入捕获为上升沿检测,记录发生上升沿的时
候 TIM5_CNT 的值。然后配置捕获信号为下降沿捕获,当下降沿到来时,发生捕获,并记录此时的 TIM5_CNT 值。这样,前后两次 TIM5_CNT 之差,就是高电平的脉宽,同时 TIM5 的计数频率我们是知道的,从而可以计算出高电平脉宽的准确时间。
输入捕获的配置步骤:
1)开启 TIM5 时钟和 GPIOA 时钟,配置 P A0 为下拉输入。
要使用 TIM5,我们必须先开启 TIM5 的时钟。这里我们还要配置 P A0 为下拉输入,因为我们要捕获 TIM5_CH1 上面的高电平脉宽,而 TIM5_CH1 是连接在 PA0 上面的。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //使能 TIM5 时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能 GPIOA 时钟 2)初始化 TIM5,设置 TIM5 的 ARR 和 PSC。
在开启了 TIM5 的时钟之后,我们要设置 ARR 和 PSC 两个寄存器的值来设置输入捕获的自动重装载值和计数
频率。 这在库函数中是通过 TIM_TimeBaseInit 函数实现的
点击(此处)折叠或打开 1.
2. 3. 4. 5. 6.
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(TIM5, &TIM_TimeBaseStructure); 3)设置 TIM5 的输入比较参数,开启输入捕获
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
输入比较参数的设置包括映射关系,滤波,分频以及捕获方式等。这里我们需要设置通道 1为输入模式,且 IC1 映
射到 TI1(通道 1)上面,并且不使用滤波(提高响应速度)器,上升沿捕获。库函数是通过 TIM_ICInit 函数来初始化输入比较参数的:
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct); 同样,我们来看看参数设置结构体 TIM_ICInitTypeDef 的定义:
点击(此处)折叠或打开 1.
2. 3. 4. 5. 6. 7. 8.
{
uint16_t TIM_Channel; //设置通道
uint16_t TIM_ICPolarity; //设 置 输 入 信 号 的 有效 捕获 极性 uint16_t TIM_ICSelection; //设置映射关系
uint16_t TIM_ICPrescaler; //设置 输入捕获分频系数 uint16_t TIM_ICFilter; //设置滤波器长度 } TIM_ICInitTypeDef; 配置代码是:
点击(此处)折叠或打开 1.
2. 3. 4. 5. 6. 7.
TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择输入端 IC1 映射到 TI1 上 TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获 TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到 TI1 上 TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频 TIM5_ICInitStructure.TIM_ICFilter = 0x00;//IC1F=0000 配置输入滤波器 不滤波 TIM_ICInit(TIM5, &TIM5_ICInitStructure); 4)使能捕获和更新中断(设置 TIM5 的 DIER 寄存器)
因为我们要捕获的是高电平信号的脉宽,所以,第一次捕获是上升沿,第二次捕获时下降沿,必须在捕获上升沿之后,设置捕获边沿为下降沿,同时,如果脉宽比较长,那么定时器就会溢出,对溢出必须做处理,否则结果就不准了。这两件事,我们都在中断里面做,所以必须开启捕获中断和更新中断。
这里我们使用定时器的开中断函数 TIM_ITConfig 即可使能捕获和更新中断: TIM_ITConfig( TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);//允许更新中断和捕获中断 5)设置中断分组,编写中断服务函数
设置中断分组主要是通过函数 NVIC_Init()来完成。分组完成后,我们还需要在中断函数里面完成数据处理和捕获设置等关键操作,从而实现高电平脉宽统计。在中断服务函数里面,跟以前的外部中断和定时器中断实验中一样,我们在中断开始的时候要进行中断类型判断,在中断结束的时候要清除中断标志位。使用到的函数分别为 TIM_GetITStatus()函数和 TIM_ClearITPendingBit()函数。
if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET){}//判断是否为更新中断 if (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET){}//判断是否发生捕获事件 TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update);//清除中断和捕获标志位 6)使能定时器(设置 TIM5 的 CR1 寄存器)
TIM_ICInitTypeDef TIM5_ICInitStructure; typedef struct
最后,必须打开定时器的计数器开关, 启动 TIM5 的计数器,开始输入捕获。 TIM_Cmd(TIM5,ENABLE ); //使能定时器 5 例程:
点击(此处)折叠或打开 1.
2. 3. 4. 5.
6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36.
* 定时器5通道1输入捕获配置 */
void TIM5_Cap_Init(u16 arr,u16 psc) {
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM5_ICInitStructure; NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE); /*使能TIM5时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); /*使能GPIOA时钟*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; /**/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; /*PA0 输入*/ GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_ResetBits(GPIOA,GPIO_Pin_0); /*PA0 下拉*/
/*初始化定时器5 TIM5*/
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(TIM5,&TIM_TimeBaseStructure); /*根据TIM_TimeBaseInitStruct中指定的参数初始
化TIMx的时间基数单位*/
/* 初始化TIM5输入捕获参数 */
TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1; /*CC1S=01 选择输入端 IC1映射到TI1上*/ TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; /*上升沿捕获*/ TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; /*映射到TI1上*/ TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; /*配置输入分频,不分频*/ TIM5_ICInitStructure.TIM_ICFilter = 0; /*IC1F=0000 配置输入滤波器 不滤波*/ TIM_ICInit(TIM5,&TIM5_ICInitStructure); #include \#include \ /**
#include \