上篇介绍了定时器的输出功能,本篇介绍定时器的输入功能。
1 问题引出 在单片机与嵌入式开发中,某些场景需要捕获传感器的高电平(或低电平)信号的持续时间,如红外解码信号、编码器输入信号等。
如下图,以单一的一段高电平输入信号为例,如何测量这段高电平的时间呢?
从直观上理解,就是要不断的检测这个信号,当信号从0变到1时,记录一个时间,再从1变到0时,记录另一个时间,两个时间差就是高电平的持续时间了。那具体要怎么编程呢?这就要用到定时器了。
2 定时器的捕获原理 上篇介绍了定时器的输出功能,本篇是利用定时器的输入功能,来计算脉冲时长。如下图:
定时器的cnt计数器在不停的计数
首先配置定时器的输入通道为上升沿捕获,这样当检测到从0到1的跳变时,ccr1就会先保存当前的cnt值,同时cnt会清零重新开始计数
然后将定时器的输入通道为下降沿捕获,当检测从1到0的跳变时,ccr2就会先保存当前的cnt值
在这期间,cnt的计数值可能会溢出,这不影响,记录下溢出的次数,并重新开始计数即可
最终,t2-t1的高电平时间,就可以通过n次的溢出时间加ccr2保存的时间来计算获得了
3 定时器常用的寄存器 上篇介绍了定时器输出pwm时用到的几个寄存器(cr、ccmr、cnt、psc、arr、ccr等),这里再介绍几个捕获信号时需要用到的几个寄存器:
3.1 捕获/比较模式寄存器ccmr1 ccmr寄存器上篇已有介绍,只是上篇仅介绍了输出模式下的功能,本篇再介绍一下它在输入模式下的功能:
这些通道可用于输入(捕获模式)或输出(比较模式)模式。通道方向通过配置相应的 ccxs 位进行定义。此寄存器的所有其它位在输入模式和输出模式下的功能均不同。对于任一给定位
ocxx 用于说明通道配置为输出时该位对应的功能
icxx 则用于说明通道配置为输入时 该位对应的功能
因此,必须注意同一个位在输入阶段和输出阶段具有不同的含义。
这里仅先介绍输入模式下的功能:
位 15:12 ic2f:输入捕获 2 滤波器 (input capture 2 filter)
位 11:10 ic2psc[1:0]:输入捕获 2 预分频器 (input capture 2 prescaler)
位 9:8 cc2s:捕获/比较 2 选择 (capture/compare 2 selection) 用法参照下面的cc1s通道1
位 7:4 ic1f:输入捕获 1 滤波器 (input capture 1 filter)
数字滤波器由事件计数器组成,每 n 个事件才视为一个有效边沿:
0000:无滤波器
0001~1111:其它频率的滤波器
位 3:2 ic1psc:输入捕获 1 预分频器 (input capture 1 prescaler)
此位域定义 cc1 输入 (ic1) 的预分频比。只要 cc1e=0(timx_ccer 寄存器),预分频器便立即复位。
00:无预分频器,捕获输入上每检测到一个边沿便执行捕获
01~11:每发生 2 (4、8)个事件便执行一次捕获
位 1:0 cc1s:捕获/比较 1 选择 (capture/compare 1 selection),此位域定义通道方向(输入/输出)以及所使用的输入。
00:cc1 通道配置为输出
01:cc1 通道配置为输入,ic1 映射到 ti1 上
10:cc1 通道配置为输入,ic1 映射到 ti2 上
11:cc1 通道配置为输入,ic1 映射到 trc 上。此模式仅在通过 ts 位(timx_smcr 寄存器)选择内部触发输入时有效
注: 仅当通道关闭时(timx_ccer 中的 cc1e = 0),才可向 cc1s 位写入数据。
3.2 捕获/比较使能寄存器ccer 我们要用到这个寄存器的最低 2 位, cc1e 和 cc1p。
位 15、11、7、3 ccxnp:捕获 /比较x 输出极性 (capture/comparex output polarity)。
ccx 通道配置为输出: ccxnp 必须保持清零。
ccx 通道配置为输入:此位与 ccxp 配合使用,用以定义 ti1fp1/ti2fp1 的极性。请参见 ccxp 说明。
位 14、10、6、2 保留,必须保持复位值。
位 13、9、5、1 ccxp:捕获 /比较x 输出极性 (capture/comparex output polarity)。
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:捕获 /比较 x 输出使能 (capture/comparex output enable)。
0:禁止捕获
1:使能捕获
0:关闭––ocx 未激活
1:开启––在相应输出引脚上输出 ocx信号
ccx 通道配置为输出:
ccx 通道配置为输入:
此位决定了是否可以实际将计数器值捕获到输入捕获/比较寄存器 1 (timx_ccr1) 中。
3.3 dma/中断使能寄存器dier 我们需要用到中断来处理捕获数据,所以必须开启通道 1 的捕获比较中断,即 cc1ie 设置为 1 。
位 15、13、7、5 保留,必须保持复位值。
位 14 tde:触发 dma 请求使能 (trigger dma request enable)
位 12~位9 ccxde:捕获/比较x dma 请求使能 (capture/compare 1 dma request enable)
位 8 ude:更新 dma 请求使能 (update dma request enable)
位 6 tie:触发信号(trgi)中断使能 (trigger interrupt enable)
位 4~位1 ccxie:捕获/比较x 中断使能 (capture/compare 1 interrupt enable)
位 0 uie:更新中断使能 (update interrupt enable)
4 编程 4.1 定时器初始化 4.1.1 gpio初始化 这里用到的是定时器5的通道1,根据stm32f407的数据手册“3 pinouts and pin description”中的“table 9. alternate function mapping”复用引脚说明表,可以看到定时器5通道1对应的引脚位a0,所以使用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
4.1.2 时基初始化 使用定时器,时基初始化是必不可少的,就是要设置一些计数的频率与溢出值(自动重装载值):
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);
4.1.3 输入通道初始化 将定时器的通道1设置为输入捕获模式:
tim_icinittypedef tim5_icinitstructure; /*输入通道 结构体*//*输入通道初始化,初始化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 = 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_t tim_icselection,uint16_t tim_icfilter){ uint16_t tmpccmr1 = 0, tmpccer = 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
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, functionalstate newstate){ if (newstate != disable) { /* 使能中断 */ timx->dier |= tim_it; } else { /* 失能中断 */ timx->dier &= (uint16_t)~tim_it; }}
4.1.4 定时器中断初始化 定时器中断的使能设置已在上面的定时器配置中设置,这里只是进行常规的配置定时器中断的优先级:
/*定时器中断配置*/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寄存器
4.2 定时器中断服务函数 此处用到了两个全局变量,用于辅助实现高电平捕获。其中:
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_sta&0x80)==0)//还未成功捕获 (1000 0000) { /*定时器溢出中断*/ if(tim_getitstatus(tim5, tim_it_update) != reset) { if(tim5ch1_capture_sta&0x40)/* 之前标记了开始信号(0100 0000) */ { if((tim5ch1_capture_sta&0x3f)==0x3f) /* 高电平太长了,计数溢出了 (0011 1111) */ { tim5ch1_capture_sta|=0x80; /* (强制)标记成功捕获了一次 (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_sta&0x40) /* 之前标记了开始信号(0100 0000) */ { tim5ch1_capture_sta|=0x80; /* 标记成功捕获到一次高电平脉宽 (1000 0000) */ tim5ch1_capture_val=tim_getcapture1(tim5); /* 获取当前的捕获值 */ tim_oc1polarityconfig(tim5,tim_icpolarity_rising); /* cc1p=0 重新设置为上升沿捕获,用于下次捕捉信号 */ } /*还未开始,第一次捕获 上升沿(起始信号) */ 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的值
再看主函数中:
while(1) { /* 成功捕获到了一次高电平 (1000 0000) */ if(tim5ch1_capture_sta&0x80) { temp=tim5ch1_capture_sta&0x3f; /* 获取溢出的次数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:capture/compare register 捕获/比较寄存器
psc:prescaler 预分频器
cnt:counter 计数器
sr:status register 状态寄存器
ccmr:capture/compare mode register 捕获/比较模式寄存器
cc1s:capture/compare 1 selection 捕获/比较1模式选择
oc1m: output compare 1 mode 输出比较1模式
oc1pe:output compare 1 preload enable 输出比较1预装载使能
ic1f:input capture 1 filter 输入捕获1滤波器
ic1psc:input capture 1 prescaler 输入捕获1预分频器
ccer:capture/compare enable register 捕获/比较使能寄存器
cc1p:capture/comparex output polarity 捕获 /比较1输出极性
cc1e:capture/comparex output enable 捕获 /比较1输出使能
smcr:slave mode control register 从模式控制寄存器
dcr:dma control register dma 控制寄存器
dier:dma/interrupt enable register dma/中断使能寄存器
dmar:dma address for full transfer 全传输 dma 地址
or:option register 选项寄存器
Stable Diffusion采样速度翻倍!仅需10到25步的扩散模型采样算法
C51单片机interrupt和using的使用
苹果新款 AirPods Max 耳机未配备用于超宽带的 U1 芯片
美国航空公司决定将波音737 MAX航班延期至11月初复飞
2022世界人工智能大会|构建AR平台优势,释放产业元宇宙价值
电机控制基础2——定时器捕获单输入脉冲原理
2018年印度智能手机出货量达到1.423亿台,同比增长14.5%
网络分析仪软件对无源器件的测试
骁龙笔记本千兆级LTE为5G笔记本铺平道路
如何使用超级电容器设计简单、不间断的电源
MHD在无针血糖监测中的潜在价值和局限性讨论
卫星电话只有电信有吗
射频环行器的工作原理及射频环行器应用
哪款蓝牙耳机性价比比较高,佩戴舒适的蓝牙耳机推荐
远离家庭卫生清洁困扰,扫地机器人哪个牌子好?
运放电路之反相放大器
基于飞思卡尔MCU的血糖监测仪应用
最终榜上有名的十个公式,你都知道吗?
linux系统如何登录到远程linux服务器
“小鹏G3上市发布会”在广州亚运城综合体育馆举办