上一篇文章介绍了定时器的输出函数,本文介绍了定时器的输入函数。
在单片机和嵌入式开发中,有些场景需要捕获传感器高电平(或低电平)信号的持续时间,例如红外解码信号、编码器输入信号等。
如下图所示,以单个高电平输入信号为例,如何测量这个高电平时间呢?
为了直观地理解,我们需要不断地检测这个信号。当信号从0变为1时,记录一个时间,当信号从1变为0时,记录另一个时间。两次之差就是高电平的持续时间。那么具体如何编程呢?这需要使用定时器。
上一篇文章介绍了定时器的输出功能。本文利用定时器的输入函数来计算脉冲持续时间。如下所示:
定时器的CNT计数器不断计数。
首先将定时器的输入通道配置为上升沿捕获,这样当检测到从0到1的跳变时,CCR1会先保存当前的CNT值,同时将CNT清零并重新开始计数。
然后捕获定时器输入通道的下降沿。当检测到从1到0的转变时,CCR2会首先保存当前的CNT值。
在此期间,CNT计数值可能会溢出。这不会影响计数。只需记录溢出次数并重新开始计数即可。
最后将N次溢出时间和CCR2节省的时间相加即可计算出t2-t1的高电平时间。
上一篇文章介绍了定时器输出PWM时用到的几个寄存器(CR、CCMR、CNT、PSC、ARR、CCR等)。这里介绍一下捕获信号时需要用到的几个寄存器:
CCMR寄存器在上一篇文章中已经介绍过,但上一篇文章只介绍了其在输出模式下的功能。本文将介绍其在输入模式下的功能:
这些通道可用于输入(捕获模式)或输出(比较模式)模式。通道方向通过配置相应的CCxS 位来定义。该寄存器的所有其他位在输入模式和输出模式下具有不同的功能。对于任何给定位置
OCxx 用于描述该通道配置为输出时该位对应的功能。
ICxx 用于描述通道配置为输入时该位对应的功能。
因此,需要注意的是,同一位在输入级和输出级具有不同的含义。
这里只介绍输入模式下的功能:
位15:12 IC2F:输入捕捉2 滤波器(输入捕捉2 滤波器)
Bit 11:10 IC2PSC[1:0]:输入捕捉2 预分频器(输入捕捉2 预分频器)
Bit 9:8 CC2S:捕捉/比较2 选择(Capture/compare 2 select) 用法请参考下面的CC1S 通道1
位7:4 IC1F:输入捕捉1 滤波器
数字滤波器由一个事件计数器组成,其中每N 个事件被视为有效边沿:
0000:无过滤器
0001~1111:其他频率的滤波器
Bit 3:2 IC1PSC:输入捕捉1 预分频器(Input capture 1 prescaler)
该位字段定义CC1 输入(IC1) 的预分频器。一旦CC1E=0(TIMx_CCER 寄存器),预分频器就会复位。
00:无预分频器,每次在捕捉输入上检测到边沿时执行捕捉
01~11: 每发生2 (4, 8) 个事件就执行一次捕获
位1:0 CC1S:捕捉/比较1 选择,该位字段定义通道方向(输入/输出)和使用的输入。
00:CC1通道配置为输出
01:CC1通道配置为输入,IC1映射到TI1
10:CC1通道配置为输入,IC1映射到TI2
11:CC1 通道配置为输入,IC1 映射到TRC。该模式仅在通过TS 位(TIMx_SMCR 寄存器)选择内部触发输入时有效
注:仅当通道关闭时(TIMx_CCER 中的CC1E=0),数据才能写入CC1S 位。
我们将使用该寄存器的最低2 位,CC1E 和CC1P。
位15、11、7、3 CCxNP:捕捉/比较输出极性。
CCx 通道配置为输出:CCxNP 必须保持清零。
CCx 通道配置为输入:该位与CCxP 结合使用来定义TI1FP1/TI2FP1 的极性。请参阅CCxP 说明。
位14、10、6、2 被保留并且必须保持其复位值。
位13、9、5、1 CCxP:捕捉/比较输出极性。
00:同相/上升沿触发电路对TIxFP1 上升沿敏感(复位模式、外部时钟模式或触发模式下的捕获或触发操作),TIxFP1 不反相(门控模式或编码器模式下的触发操作)。
01:反相/下降沿触发电路对TIxFP1 下降沿(复位模式、外部时钟模式或触发模式下的捕获或触发操作)、TIxFP1 反相(门控模式或编码器模式下的触发操作)敏感。
10:保留,不要使用该配置。
11:同相/上升沿和下降沿触发电路对TIxFP1 的上升沿和下降沿敏感(在复位模式、外部时钟模式或触发模式下执行捕获或触发操作),TIxFP1 不反相(门控模式时) 。此配置不得在编码器模式下使用。
0:OCx高电平有效
1:OCx低电平有效
CCx 通道配置为输出:
CCx 通道配置为输入:
CCxNP/CCxP 位选择用于触发或捕捉操作的TI1FP1 和TI2FP1 的极性。
位12、8、4、0 CCxE:捕捉/比较输出使能。
0:禁用捕获
1:启用捕捉
0:关闭——OCx未激活
1:On 在相应的输出引脚上输出OCx 信号
CCx 通道配置为输出:
CCx 通道配置为输入:
该位决定计数器值是否可以实际捕获到输入捕获/比较寄存器1 (TIMx_CCR1) 中。
我们需要使用中断来处理捕获的数据,所以必须使能通道1的捕获比较中断,即CC1IE设置为1。
位15、13、7、5 被保留并且必须保持其复位值。
位14 TDE:触发DMA 请求使能
Bit 12~Bit 9 CCxDE:捕捉/比较x DMA 请求使能(捕捉/比较1 DMA 请求使能)
位8 UDE:更新DMA 请求使能
Bit 6 TIE:触发信号(TRGI)中断使能(触发中断使能)
Bit 4~Bit 1 CCxIE:捕捉/比较x 中断使能(捕捉/比较1 中断使能)
Bit 0 UIE:更新中断使能(更新中断使能)
此处使用定时器5 的通道1。根据STM32F407数据手册“3 Pinouts及引脚描述”中的“表9.复用功能映射”复用引脚描述表,可以看到引脚A0对应定时器5的通道1,因此使用A0作为信号输入引脚。
因此,在程序中可以这样配置A0引脚。请务必配置引脚的复用功能:
GPIO_InitTypeDef GPIO_InitStructure; /*GPIO 结构*/RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //使能PORTA时钟/*GPIO输入信号初始化*/GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0; //GPIOA0GPIO_InitStructure.GPIO_Mode=GPIO _Mode_AF; /*复用函数*/GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz; //速度100MHzGPIO_InitStructure.GPIO_OType=GPIO_OType_PP; //推挽复用输出GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_DOWN; /*下拉*/GPIO_Init(GPIOA,GPIO_InitStructure); //初始化PA0GPIO_PinAFConfig(GPIOA, GPIO_PinSource0,GPIO_AF_TIM5); //PA0复用位定时器5
使用定时器时,时基初始化是必不可少的,这意味着设置一些计数频率和溢出值(自动重载值):
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; /*时基结构*//*时基初始化*/TIM_TimeBaseStructure.TIM_Period=arr; /* 自动重载值*/TIM_TimeBaseStructure.TIM_Prescaler=psc; /*定时器分频*/TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式TIM_TimeBaseInit(TIM5,TIM_TimeBaseStructure);
设置定时器通道1为输入捕捉模式:
/* 上升沿捕获*/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);TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE); /* 允许更新(溢出)中断,允许CC1IE捕获中断*/TIM_Cmd(TIM5,ENABLE); //使能定时器5关于配置CCMR1、CCER寄存器
CCMR1:
CCER:
TIM_ICInit函数对应输入通道的初始化,实际操作的是CCMR1和CCER寄存器:
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct){ if (TIM_ICInitStruct-TIM_Channel==TIM_Channel_1) { /* TI1 配置*/TI1_Config(TIMx, TIM_ICInitStruct-TIM_ICPolarity, TIM_ICInitStruct-TIM_ICSelection , TIM_ICInitStruct-TIM_ICFilter); /* 设置中断捕获预分频器值*/TIM_SetIC1Prescaler(TIMx, TIM_ICInitStruct-TIM_ICPrescaler); } else if (TIM_ICInitStruct-TIM_Channel==TIM_Channel_2) { /*省略.*/}}static void TI1_Config(TIM_TypeDef* TIMx, uint16_t TIM_ICPolarity, uint16_ tTIM_ICSelection ,uint16_t TIM_ICFilter){ uint16_t tmpccmr1=0, t mpccer=0; /* 关闭通道1: 并复位CC1E 位*/TIMx-CCER=(uint16_t)~TIM_CCER_CC1E; tmpccmr1=TIMx-CCMR1; tmpccer=TIMx-CCER; /* 通过设置选择CC1S 作为输入模式并配置滤波器*/tmpccmr1=((uint16_t)~TIM_CCMR1_CC1S) ((uint16_t)~TIM_CCMR1_IC1F); tmpccmr1 |=(uint16_t)(TIM_ICSelection | (uint16_t)(TIM_ICFilter (uint16_t)4)); /* 选择CC1P极性并设置CC1E位*/tmpccer=(uint16_t)~(TIM_CCER_CC1P | TIM_CCER_CC1NP); tmpccer |=(uint16_t)(TIM_ICPolarity | (uint16_t)TIM_CCER_CC1E); /* 向TIMx 的CCMR1 和CCER 寄存器写入数据*/TIMx-CCMR1=tmpccmr1; TIMx-CCER=tmpccer;}void TIM_SetIC1Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC){ TIMx-CCMR1=(uint16_t)~TIM_CCMR1_IC1PSC; /* 复位IC1PSC 位*/TIMx-CCMR1 |=TIM _ICPSC; /*设置IC1PSC值*/}关于配置DIER寄存器
TIM_ITConfig函数实际上操作DIER寄存器来使能中断:
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionState NewState){ if (NewState !=DISABLE) { /* 使能中断*/TIMx-DIER |=TIM_IT; } else { /* 禁止中断*/TIMx-DIER=(uint16_t)~TIM_IT; }}
定时器中断的使能设置已在上面的定时器配置中进行了设置。这里只是定时器中断优先级的常规配置:
/*定时器中断配置*/NVIC_InitStructure.NVIC_IRQChannel=TIM5_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2; //抢占优先级3NVIC_InitStructure.NVIC_IRQChannelSubPriority=0; //子优先级3NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; //IRQ通道使能NVIC_Init( NVIC_InitStructure); //根据指定参数初始化NVIC寄存器
这里使用了两个全局变量来辅助实现高级捕获。在:
TIM5CH1_CAPTURE_VAL用于记录下降沿捕获时TIM5_CNT的值。
TIM5CH1_CAPTURE_STA用于记录捕获状态,我们将其用作寄存器。这是他们的描述:
u8 TIM5CH1_CAPTURE_STA=0; //输入捕捉状态(使用自制寄存器之一,初始为0) u32 TIM5CH1_CAPTURE_VAL; //输入捕获值(TIM2/TIM5为32位)/*** @brief 定时器5中断服务程序*/void TIM5_IRQHandler(void){ if((TIM5CH1_CAPTURE_STA0X80)==0)//尚未成功捕获(1000 0000 ) { /*定时器溢出中断*/if(TIM_GetITStatus(TIM5, TIM_IT_Update) !=RESET) { if/* (强制)标记成功捕获一次(1000 0000) */TIM5CH1_CAPTURE_VAL=0XFFFFFFFF; /* 由于溢出次数N无法增加,所以将当前捕获值设置为32位最大值,相当于Nmax+1*/} else /* 一般情况下不会发生溢出,正确的最终获得高电平时间*/{ TIM5CH1_CAPTURE_STA++; /* 定时器溢出累计次数N */} } else { /* 当信号还没有被捕获到时,定时设备溢出后什么都不做,自己清零,继续计数*/} } /*捕获1次捕获事件发生*/if(TIM_GetITStatus(TIM5, TIM_IT_CC1) !=RESET) { /*捕获下降沿(结束信号)*/if(TIM5CH1_CAPTURE_STA0X40) /* 启动信号(0100 0000)之前已被标记*/{ TIM5CH1_CAPTURE_STA| 事件发生*/=0X80; /* 标记成功捕捉到高电平脉宽(1000 0000) */TIM5CH1_CAPTURE_VAL=TIM_GetCapture1(TIM5 ; Edge (启动信号) */else { TIM5CH1_CAPTURE_STA=0; /* 清除捕捉状态寄存器*/TIM5CH1_CAPTURE_VAL=0; /* 清除捕捉值*/TIM5CH1_CAPTURE_STA|=0X40; /* 标记上升沿(0100 0000) */TIM_Cmd(TIM5,DISABLE ); /* 关闭定时器5 */TIM_SetCounter(TIM5,0); /* 清除CNT 并重新从0 开始计数*/TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling); /* CC1P=1 设置为下降沿捕获*/TIM_Cmd(TIM5,ENABLE ); /* 使能定时器5 */} } } TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位}我们再对比一下这张图:
初始化期间设置为上升沿触发。触发后(启动信号),清除CNT,重新从0开始计数,设置为下降沿捕获
在后续过程中,可能会出现多次定时器计数溢出,即TIM5CH1_CAPTURE_STA++(使用低6位),即N的值
最后捕获下降沿(结束信号),TIM5CH1_CAPTURE_VAL获取当前CNT值,即CCRx2的值
再看一下main函数:
while(1) { /* 成功捕获高电平(1000 0000) */if(TIM5CH1_CAPTURE_STA0X80) { temp=TIM5CH1_CAPTURE_STA0X3F; /* 获取溢出次数N(0011 1111) */temp*=0XFFFFFFFF; /* 溢出时间总和=N*溢出计数值*/temp+=TIM5CH1_CAPTURE_VAL; /* 总高电平时间=溢出时间之和+ 下降沿计数值*/printf('HIGH:%lld us\r\n',temp) ; //打印总高电平时间TIM5CH1_CAPTURE_STA=0; //开始下一次捕获}} 检查TIM5CH1_CAPTURE_STA捕获1个高电平时,打印高电平的持续时间:
总高电平时间=N(TIM5CH1_CAPTURE_STA的低6位)* ARR(溢出计数值)+ CCRx2(下降沿计数值)
附:部分寄存器缩写全名
ARR:auto-reload register 自动重载寄存器
CCR:捕捉/比较寄存器捕捉/比较寄存器
PSC:预分频器预分频器
CNT:计数器计数器
SR:状态寄存器状态寄存器
CCMR:捕捉/比较模式寄存器捕捉/比较模式寄存器
CC1S:捕捉/比较1 选择捕捉/比较1 模式选择
OC1M:输出比较1 模式输出比较1 模式
OC1PE:输出比较1 预载使能输出比较1 预载使能
IC1F:输入捕捉1 滤波器输入捕捉1 滤波器
IC1PSC:输入捕捉1 预分频器输入捕捉1 预分频器
CCER:捕捉/比较使能寄存器捕捉/比较使能寄存器
CC1P:捕捉/比较输出极性捕捉/比较1 输出极性
CC1E:捕捉/比较输出使能捕捉/比较1 输出使能
SMCR:从机模式控制寄存器从机模式控制寄存器
DCR:DMA控制寄存器DMA控制寄存器
DIER:DMA/中断允许寄存器DMA/中断允许寄存器
DMAR:完整传输的DMA 地址完整传输的DMA 地址
OR: 选项寄存器选项寄存器