我的小车调试进度之:实现超声波测距并显示
小车调试进度六更~~
利用定时器的输入捕获功能得到ECHO口高电平持续的时间,进而得到距离。
-
关于超声波模块HC-SR04
超声波测距模块工作原理:
(1)采用IO口TRIG触发测距,给至少10us的高电平信号;
(2)模块自动发送8个40khz的方波,自动检测是否有信号返回;
(3)有信号返回,通过IO口ECHO输出一个高电平,高电平持续的时间就是超声波从发射到返回的时间。测试距离=(高电平时间*声速(340M/S))/2;
(4)本模块使用方法简单,一个控制口(Trig)发一个10US以上的高电平,就可以在接收口(Echo)等待高电平输出。 -
关于定时器的输入捕获功能
- 通用定时器以及高级定时器都具有输入捕获功能,且每个定时器都有四路输入捕获通道;
- 利用定时器的输入捕获功能捕获Echo口的高电平(先捕获一个上升沿,再捕获一个下降沿,即可判断捕获到高电平),进而可得到高电平的持续时间;
- 超声波模块的Echo引脚需要接入定时器的一路输入捕获通道(也就是和定时器的某个通道的GPIO口相连),如图:


我是将Echo接到了PB0口上,也就是定时器三的通道三上,所以我们在配置定时器时,需要把通道三设置为捕获模式,且初始化设置为上升沿捕获,这样一旦捕获到一个上升沿就会进入中断,我们在中断里再将捕获方式设置为下降沿捕获,并将计数器清零,那么下一次捕获到下降沿时才会再进入中断,则此时的计数值即为高电平的计数值,再经过处理后可转换为实际的高电平持续时间,进而可得到距离,思路就是这样,里面还有一些处理的细节,先看程序叭。
- TIM3_CH3(通道三)输入捕获初始化
/*TIM3_CH3(通道三)输入捕获初始化*/
TIM_ICInitTypeDef TIM3_ICInitStructure;
void TIM3_Cap_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能TIM3时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能GPIOB时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PB0 输入(ECHO)
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //PB1输出(Trig)
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; //2M
GPIO_Init(GPIOB, &GPIO_InitStructure);
//初始化定时器3 TIM3
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(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
//初始化TIM3输入捕获参数
TIM3_ICInitStructure.TIM_Channel = TIM_Channel_3; //CC1S=01 选择输入端 IC3映射到TI3上
TIM3_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
TIM3_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;//映射关系,这里必须映射到TI寄存器上
TIM3_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
TIM3_ICInitStructure.TIM_ICFilter = 0x00;//配置输入滤波器 不滤波
TIM_ICInit(TIM3, &TIM3_ICInitStructure);
//中断分组初始化
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //先占优先级2级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
TIM_ITConfig(TIM3,TIM_IT_Update|TIM_IT_CC3,ENABLE);//允许更新中断 ,允许CC3IE捕获中断
TIM_Cmd(TIM3,ENABLE ); //使能定时器3
}
- 中断处理函数(在
stm32f10x_it.c文件中写)
extern u16 TIM3CH3_CAPTURE_STA,TIM3CH3_CAPTURE_VAL;//输入捕获状态、输入捕获值
/*超声波回波脉宽读取中断*/
void TIM3_IRQHandler(void)
{
u16 tsr;
tsr=TIM3->SR;//将TIM3的状态寄存器值赋给tsr
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)//捕获3发生捕获事件
{
if(TIM3CH3_CAPTURE_STA&0X40) //捕获到一个下降沿
{
TIM3CH3_CAPTURE_STA|=0X80; //标记成功捕获到一次高电平脉宽
TIM3CH3_CAPTURE_VAL=TIM3->CCR3; //获取当前的捕获值.
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;//清除中断标志位
}
这里做几个说明:
- 关于
tsr=TIM3->SR;:
这里需要知道TIMX的SR寄存器(状态寄存器)的相关知识,具体可在《stm32中文参考手册》中找,这里我们只需要知道第一位和第四位分别是更新中断标志位和捕获/比较3中断标志位就可以:


要注意: 我们在TIM3_CH3(通道三)输入捕获初始化函数中的设置会产生两种进入中断的方式:向上计数溢出和捕获到上升沿,所以在中断处理函数中就需要做一个判断,那么就需要用到TIMX的状态寄存器SR了,如果位0为1,那么就意味着是一个溢出中断,如果位3为1,就意味着是一个捕获中断。
也就是if(tsr&0X01)//溢出和if(tsr&0x08)//捕获3发生捕获事件这两行代码的作用了。 - 关于
TIM3CH3_CAPTURE_STA:
这个是我们设置的变量,但是实际中我们把它作为一个自定义的寄存器来用,也就是说我们把每一位都自定义上不同的功能,这样可以减少变量的定义(这一思想来自于《STM开发指南-库函数版》)

- 超声波距离转换显示函数(
main.c文件中写,主函数while(1)内每200ms获取一次转换距离,不宜一直获取!!!)
/*超声波回波接收函数*/
void Read_Distance(void)
{
PBout(1)=1;
delay_us(15);
PBout(1)=0;
if(TIM3CH3_CAPTURE_STA&0X80)//成功捕获到了一次高电平
{
Distance = TIM3CH3_CAPTURE_STA&0X3F;
Distance *= 65536; //溢出时间总和
Distance += TIM3CH3_CAPTURE_VAL; //得到总的高电平时间(单位为us)
Distance = Distance*170/1000;//距离转换为mm(超声波测距范围为2cm到450cm)
OLEDShowString(0, 10, "当前Distance:");
OLED_ShowNumber(20,40,Distance/10,3,16);
OLED_ShowChar(45,40,'.',16,1);//人为加入小数点
OLED_ShowNumber(50,40,Distance%10,1,16);
OLEDShowString(80, 40, "cm");
OLED_Refresh_Gram();
TIM3CH3_CAPTURE_STA=0; //开启下一次捕获
}
}
- 显示效果

注意一点:用TIM3的输入捕获功能之后在同一程序内不能再用TIM3的其它功能,因为用的是同一个计数器!!!