简单的梳理一下STM32中测距的思路,同时也是定时器中断的学习,文中的的代码是买的器件带的测距代码。
1.首先要了解定时器的分类及区别:
2.通用定时器的简介
下面以通用计时器为例进行梳理,逻辑上遵循手册对定时器的介绍,然后需要更细致的介绍的地方,我尽量解释清楚。(这里做一个简单的记录Markdown首行缩进为(& emsp;),就可以了)
2.1定时器组成
如下图是基本定时器的图,首先是红色部分(如果哪里理解不正确,评论区可以指出),每个定时器都有一个预分频器(PSC,计数器时钟频率系数在1~65535之间任何的数值)和一个在该分频器分频后的时钟下工作的计数器(CNT),以及自动重装载寄存器(ARR),每个定时器是独立的,我看图我理解的每个定时器中的PSC,CNT,和ARR应该是有一组,但是捕获/比较寄存器是有四个。
2.1.1 捕获/比较通道
蓝色部分是四个通道,四个独立的通道可以完成:(基本定时器不具有这四种功能)
- 输入捕获
- 输出比较
- PWM生成
- 单脉冲模式输出
其中如下事件发生时产生中断/DMA: - 更新:计数器向上溢出/向下溢出,计数器初始化
- 触发事件
- 输入捕获
- 输出比较
下图表示的捕获/比较寄存器前面的详细讲解,这里我觉得要注意三点: - TIx信号采样,滤波
- 带极性的边沿检测器
- 输入触发的选择
- 触发信号的分频
2.1.2 输入捕获模式
这部分是看手册14.3.5之后自己的理解,如果有人看这边博文,尽量多看上面的图:
1.由CCER寄存器配置选择使能四个通道,打开相应的使能信号CCxE,并且设置通道的捕获信号的极性CCxP,下图展示的通道1的输入部分以及该两个配置的寄存器
2.数字滤波的概念:边沿信号的跳变可能不是完美的。数字滤波器由一个事件计数器组成,它记录到N个事件后会产生一个输出的跳变。也就是说连续N次采样,如果都是高电平,则说明这是一个有效的触发,就会进入输入捕捉中断(如果设置了的话)。这样就可以滤除那些高电平脉宽低于8个采样周期的脉冲信号,从而达到滤波的作用。
1)通常选择不滤波则TIMx_CCMR1的ICF[3:0]为0000。
2)IC1PS[1:0]选择每个边沿都触发一次捕获
3)CC1S填入01多路选择器选择TI1FP1为输入脚
到这里文中开始蓝色框中的部分通道方向为输入情况下就讲的差不多了。
3.输入/捕获的功能是当检测到ICxPS(2.1.2图中的IC1PS)信号有相应的边沿后,计数器CNT的计数值被锁存到捕获/比较寄存器(TIMx_CCRx,每个计数器有四个)中 ,如果要产生相应的中断,需要使能DMA/中断使能寄存器(TIMx_DIER)中的CCC1IE位。
2.1.3 定时器的时钟计算方法
可以看到高级定时器是APB2,其他定时器连接的总线是APB1,所有的分频时钟都是72MHZ的基础上分频,解释如下图所示。
通用定时器的时钟源可以通过模式控制寄存器(TIMx_SMCR)来进行配置。默认是000由内部时钟CK_INT进行驱动,计数器的最终的频率还需要经过PSC预分频计算才能得到。
2.1.4 时钟的计算
时基单元包括:
- 计数器寄存器(TIMx_CNT)
- 预分频寄存器(TIMx_PSC)
- 自动转载寄存器(TIMx_ARR)
PSC中填入分频因子,下图所示填为(4-1)时,定时器的时钟CK_CNT就会按照倍频因子将原时钟分频为CK_CNT。
计数器由预分后的时钟输出CK_CNT来驱动,且只有设置TIMx_CR1的CEN为1时,才能使能计数器,CK_CNT才会有效,其中计数器的在CK_CNT的时钟下计数模式有如下几种,其中自动加载值ARR是提前加载好的,其中加载的值是想要在分频后时钟下的计数的计数值,可以看到在这几种模式下最后计数寄存器CNT的对标值都是ARR。
其中计数器的模式在控制寄存器(TIMx_CRx)配置DIR位。
看懂了原理,下面举一个例子来说明如何计算时钟。如果调用的时候参数为 Timer2_Init(1000-1,72-1); ,则重载值是1000-1;倍频因子是72,那么就是72MHZ变为1000 000HZ,在分频后的时钟下,在选定的计数模式下,当计数到0或者计数到ARR的值后,产生中断,也就代表着每1000 000/1000 = 1000触发一次中断,则得到的是(72*1000)/72000 000 =0.001s = 1ms的中断,或者不能够理解前一个公式可以理解为(1/(72000 000/72))*1000 = (1/1000 000)*1000 = 0.001s = 1ms。
void Timer2_Init(u16 arr,u16 psc)
{
RCC->APB1ENR|=1<<0;
TIM2->ARR=arr;
TIM2->PSC=psc;
TIM2->DIER|=1<<0;
TIM2->CR1|=0x01; MY_NVIC_Init(1,3,TIM2_IRQn,2);
}
2.1.3 输入捕获功能的应用-超声测距
关于超声波的介绍
超声波使用定时器3来计算测得的距离,其中选择CC3配置输入,IC3映射在TI3上,可以看到,PB0作为输入口连接ECHO引脚。
捕获思路如下:
通过监测捕获通道上也就是PB0的边沿信号,在信号发生跳变的时候,将当前的定时器CNT的值放入对应的比较寄存器中CCR,完成一次捕获,如下图所示中断要设置两个,一个是捕获完成中断一个是CNT溢出中断(65535),为了区分中断的类型,在代码中设置标志寄存器STA,其中STA每位的含义如下如图2所示:
捕获代码实现思路如下:
1.在中断处理函数中,先判断STA【7】是否为1,如果等于1,说明已经成功捕获到一次高电平,现在的电平为低,如果==1,说明当前还需要继续判断中断类型,然后分别处理,可能是溢出,可能是上升沿,可能是下降沿。
2.当捕获到高电平,标记STA[6] ==1,计数器全部清零,设置极性为下降沿捕捉,如果捕获到下降沿,则在这其中溢出次数保存在【5:0】中,然后保存当前计数器的值,这就得到了超声波测距信号的时钟数,最后只需要设置上升沿捕捉。
2.1.4 代码讲解
1.Timer3_Init(0XFFFF,72-1); //超声播初始化PB0,PB1,在这个计数范围(65535)内都可以,72000 000/72 = 1000 000
void Timer3_Init(u16 arr,u16 psc)
{
RCC->APB1ENR|=1<<1; //使能TIM3时钟
RCC->APB2ENR|=1<<3; //使能PORTB时钟
GPIOB->CRL&=0XFFFFFF00;
GPIOB->CRL|=0X00000028; //PB.0 PB.1
TIM3->ARR=arr; //0XFFFF ;在65535*72<72000 000
TIM3->PSC=psc; //72;预分频数
TIM3->CCMR2|=1<<0; //CC3配置输入,IC3映射在TI3上,也就是测量的是PB0口,对应关系如图所示,
然后触发的信号源也就是PB0-CH3
//PB0,PB1, 每一个定时器有四个捕获/比较寄存器,CCMR2配置比较3,4,
我们具体的要配置3
//PB0输入(Echo),PB1输出(Tri)
TIM3->CCMR2|=0<<4; //无滤波,直接采样
TIM3->CCMR2|=0<<2; //CC3输入无预分频,监测到每一个边沿都触发一次捕获
TIM3->CCER|=0<<9; //配置CC3P设置为高电平有效
TIM3->CCER|=1<<8; //捕获使能
TIM3->DIER|=1<<3; //CC31E捕获比较中断
TIM3->DIER|=1<<0; //允许更新中断
TIM3->CR1|=0x01; //使能计数器
MY_NVIC_Init(1,3,TIM3_IRQChannel,1);
}
2.TIM3的中断处理函数:
其中中断的类型在中状态寄存器TIMx_SR中可以辨别,CC3IF为制定的中断,而UIF为更新也就是溢出中断。
void TIM3_IRQHandler(void)
{
u16 tsr;
tsr=TIM3->SR; //状态寄存器SR 的UIF = 1
if((TIM3CH3_CAPTURE_STA&0X80)==0) //没有捕获到高电平时,可能有两种中断的发生,第一种是捕获信号的到来,第二种是溢出的出现。
{
if(tsr&0X01) //第一种是溢出,说明已经捕获到了第一个的触发信号**
{
if(TIM3CH3_CAPTURE_STA&0X40) //如果捕获到了上升沿
{
if((TIM3CH3_CAPTURE_STA&0X3F)==0X3F) //信号太长,溢出,强制接受完毕
{
TIM3CH3_CAPTURE_STA|=0X80;
TIM3CH3_CAPTURE_VAL=0XFFFF;
}
else
{
TIM3CH3_CAPTURE_STA++; //普遍情况是溢出次数加一
}
}
}
if(tsr&0x08) //CC31F捕获/比较3中断标记,表明发生了设定信号的触发**
{
if(TIM3CH3_CAPTURE_STA&0X40) //当需要捕捉的是下降沿
{
TIM3CH3_CAPTURE_STA|=0X80; //标记捕捉完成信号
TIM3CH3_CAPTURE_VAL=TIM3->CCR3; //将捕获/比较3寄存器中的内容加载到变量中去,CCRS3中包含的是上一次输入捕获时间传输的计数器的值
TIM3->CCER&=~(1<<9); //CC1P=0 设置高电平有效
}
else //否则就是上升沿,当捕捉到上升沿后将所有的相关状态和数据寄存器的历史状态都清零
{
TIM3CH3_CAPTURE_STA=0;
TIM3CH3_CAPTURE_VAL=0;
TIM3CH3_CAPTURE_STA|=0X40; //将捕获到上升沿信号的信息反映到状态位上
TIM3->CNT=0; //计数器清零
TIM3->CCER|=1<<9; //CC1P=1 设置下降沿有效
}
}
}
TIM3->SR=0; //清除中断标志位
}
- Read_Distance()距离的计算
TImer1_Init(499,7199);
//7200*500=36000 00, 50ms一测,每50ms读取一次距离,其中计时器Timer3_Init()完成超声波接受状态的标记,定时器的中断处理函数1每次中断的时候调用读写距离函数,TIM1中断处理函数调用Read_Distance()函数,发出15us的高电平,然后判断超声波接受的状态,如果确实PB0有信号,则测量距离。
void Read_Distane(void)
{
PBout(1)=1; //PB1-Ttig, 输出-持续输出15us的高电平信号,开始测距
delay_us(15);
PBout(1)=0;
if(TIM3CH3_CAPTURE_STA&0X80) //如果标志信号捕获完毕,开始计算信号带宽,因为每次捕获到开始信号后,重新计数,所以只需要算溢出次数+最后一次计数即可
{
JuLi=TIM3CH3_CAPTURE_STA&0X3F; //获取溢出次数,也就是65535计算的次数
JuLi*=65536; //相乘,这里要注意2的16减一为65535,所以JuLi要声明为32位变量
JuLi+=TIM3CH3_CAPTURE_VAL; //加最后一次计数值,这里得到的JuLI是以 us为单位的,
JuLi=JuLi*170/1000; 170 00cm/s,但我没搞明白这里为什么是除以1000,而不是除以10000
TIM3CH3_CAPTURE_STA=0; //在计算完毕后清零
}
}
今天突然想通为什么这个的计算公式是这样:
超声波在空气中的传播速度是344m/s,因为测得的距离为一个来回,所以速度折算为170m/s = 170 000mm/s ,前面在寄存中的计数单位为72/72000 000 = 0.000 001s = 1us,距离 = 速度*时间 = (170000mm/s × 计数)/1000 000 = (距离×170)/ 1000 ,所以说,这里的距离的单位是mm,所以判断距离时,比如判断是否为25cm,应该为if(juli > 250)才是正确的。