当前位置: 首页 > article >正文

STM32输入捕获采集超声波模块HC-SR04响应的高电平

所用单片机:STM32F103C8T6

超声波测距模块链接(拼多多):https://mobile.yangkeduo.com/mall_page.html?ps=WIvk5gnEbW

一、模块使用说明

        该链接的超声波模块---HC-SR04支持3种接口模式:GPIO、UART、IIC。本文章仅对常用的GPIO口驱动模式进行说明,读者可自行验证其它接口的可行性。

1.1 特性说明

1.2 工作方式

        如果要用其它接口模式的话,需要对模块的电阻进行配置,如下图:

        GPIO口驱动的工作方式为,单片机给超声波模块Trig引脚发送一个大于10us的高电平,触发HC-SR04发出8个40kHz的方波,并自动检测是否有信号返回,如果有信号返回,就会通过Echo对单片机输出一个高电平,高电平的持续时间就是超声波从发射到返回的时间。 如下时序图;

        必须要注意的一点是,在下面的时序图中也可以看出,单片机给Trig引脚发送10us的高电平后,紧接着要给这个引脚发送低电平;周期最小是200ms!!!也就是说给Trig引脚发送高电平后,不能立刻就读取Echo引脚返回的高电平持续时间!!

         等待Trig一个周期完后,检测Echo引脚高电平的持续时间T,如前所说,高电平的持续时间T就是超声波从发射到返回的时间;那么只要知道声波的速度C,用T*C/2就能得到设备与障碍物的距离D。商家提供的声速计算如下:

        

    二、定时器输入捕获检测Echo高电平的原理    

        定时器输入捕获原理看B站这个up,几分钟就说明白了:【【STM32】动画讲解输入捕获 并实现超声波测距】https://www.bilibili.com/video/BV1HM4m1R75B/?share_source=copy_web&vd_source=91acb6de0cf2d9aa6a14eec0c8d82cdb


        假定定时器工作在向上计数模式,上图中的t1~t2时间段就是我们要测量的高电平持续时间。

        首先设置定时器输入通道X为上升沿捕获,这样t1时刻就会捕获到当前的cnt值。为了方便后续的计算,此时要将计数器的值清零,并且设置定时器输入通道X为下降沿捕获。等到t2时刻,又会触发下降沿捕获,此时的CNT值就是除去溢出时间外的高电平持续时间,记作CCR,再将输入通道X设为上升沿捕获,如此循环。。。。

        如果障碍物距离实在太远,可能导致Echo引脚返回的高电平时间实在太长,以至于超出了定时器重装载计数器的最大值0xffff,此时就会发生溢出。如果代码没有设计考虑溢出的话,就会导致误差过大(整个溢出时间的高电平)。

        因此,驱动这个模块,不仅要使用定时器输入捕获中断,还要使用定时器更新中断,记录溢出的次数。最终的脉宽时间 = 溢出次数 *(计数器重装载值+1)+ (CCR+1)

三、参考代码

3.1 硬件驱动实现

#include "stm32f10x.h"                  // Device header
#include "stdio.h"
#include "stdbool.h"
#include "delay.h"

#define TIM_PERIOD	0XFFFF

static void GpioCofig(void)
{
	/*GPIO时钟使能*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	/*GPIO引脚配置*/
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;	//超声波Trig触发引脚
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStruct);	
	GPIO_WriteBit(GPIOB, GPIO_Pin_13, Bit_RESET);//默认上电不触发
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;	//超声波Echo引脚,默认下拉输入0	
	GPIO_Init(GPIOA, &GPIO_InitStruct);	
}

static void TimerCofig(void)
{
	/*- - - - - - - -使能定时器时钟- - - - - - - - */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
	TIM_InternalClockConfig(TIM1);
	/*- - - - - - - -时基单元初始化- - - - - - - - */
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct);
	TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStruct.TIM_Prescaler = 72 - 1;
	TIM_TimeBaseInitStruct.TIM_Period = TIM_PERIOD;
	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStruct);
	
	/*- - - - - - - -输入捕获结构体初始化- - - - - - - - */
	TIM_ICInitTypeDef TIM_ICInitStruct;
	TIM_ICStructInit(&TIM_ICInitStruct);
	
	TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;
	TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;//上升沿捕获
	TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;//影子寄存器cc1映射到捕获通道TI1
	TIM_ICInit(TIM1, &TIM_ICInitStruct);
	
	/*设置中断*/
	TIM_ClearFlag(TIM1, TIM_FLAG_Update | TIM_IT_CC1);//清除更新和捕获中断标志位
	TIM_ITConfig(TIM1, TIM_IT_Update | TIM_IT_CC1, ENABLE);

	/*- - - - - - - -NVIC配置- - - - - - - - */
//	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//一个工程只分组一次
	NVIC_InitTypeDef  NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel = TIM1_CC_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
	NVIC_Init(&NVIC_InitStruct);

	NVIC_InitStruct.NVIC_IRQChannel = TIM1_UP_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 3;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 3;
	NVIC_Init(&NVIC_InitStruct);
	/*- - - - - - - -定时器使能,开始计数- - - - - - - - */
	TIM_Cmd(TIM1, ENABLE);
}



/**
***************************************************
* @brief	超声波测距驱动初始化
* @param
* @return 
***************************************************
*/
void UltrasoundDrvInit(void)
{
	GpioCofig();
	TimerCofig();
}

3.2 功能实现代码

        这里我没有使用温度与声速的关系式计算声速,而是直接采取了20℃下的典型值,只是为了方便验证,如果要求更准确可以加温度校准。

typedef struct 
{
	uint8_t capFinishFlag;		//捕获结束标志位
	uint8_t capStartFlag;		//捕获开始标志位
	uint16_t capCcrValue;		//捕获寄存器的值
	uint16_t capPeriods;		//自动重装载寄存器更新溢出次数
}TIM_ICUserValueType_t;

static TIM_ICUserValueType_t g_icValueType = 
{
	.capFinishFlag = 0,
	.capStartFlag = 0,
	.capCcrValue = 0,
	.capPeriods = 0,
};

/**
***************************************************
* @brief	触发超声波探测
* @param
* @return 
***************************************************
*/
void UltrasoundProc(void)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_13, Bit_SET);
	DelayNus(20);
	GPIO_WriteBit(GPIOB, GPIO_Pin_13, Bit_RESET);
	/*应当保证低电平时间符合周期要求,这里我有其它办法,就不用延时死等了*/
}

static bool CalPulseWidth(uint32_t* pulseWidth)//触发探测后,计算高电平的持续时间
{
	if (g_icValueType.capFinishFlag == 0)
	{
		return false;
	}
	*pulseWidth = g_icValueType.capPeriods * (TIM_PERIOD + 1) + (g_icValueType.capCcrValue + 1);
	g_icValueType.capFinishFlag = 0;//捕获结束标志位清零
	printf("capPeriods is %d\n",g_icValueType.capPeriods);
	return true;
}

/**
***************************************************
* @brief	获取最终和障碍物的距离,cm
* @param	
* @return 
***************************************************
*/
void GetDistance(float* distance)
{
	uint32_t pulseWidth = 0;
//	while ( CalPulseWidth(&pulseWidth) == false );
	(void)CalPulseWidth(&pulseWidth);
	*distance = ((float)pulseWidth*1.0f/10.0f*0.34f)/2.0f;
}
/**
***************************************************
* @brief	定时器1更新中断服务函数
* @param
* @return 
***************************************************
*/
void TIM1_UP_IRQHandler(void)
{
	if ( TIM_GetITStatus(TIM1, TIM_IT_Update) )
	{
		/*实现功能*/
		g_icValueType.capPeriods ++;
		TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
	}
	TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
}

/**
***************************************************
* @brief	定时器1输入捕获中断服务函数
* @param
* @return 
***************************************************
*/
void TIM1_CC_IRQHandler(void)
{
	if ( TIM_GetITStatus(TIM1, TIM_IT_CC1) != RESET )
	{
		//第一次捕获
		if ( g_icValueType.capStartFlag == 0 )
		{
			TIM_SetCounter(TIM1,0);//计数器清零
			g_icValueType.capPeriods = 0;//之前的自动重装载寄存器溢出次数清零
			g_icValueType.capCcrValue =0;//存放捕获比较寄存器值的变量清零
			TIM_OC1PolarityConfig(TIM1, TIM_ICPolarity_Falling);//第一次捕获到上升沿后就把捕获设置为下降沿
			g_icValueType.capStartFlag = 1;//捕获开始标志置一
		}
		//表示为下降沿捕获中断
		else
		{
			g_icValueType.capCcrValue = TIM_GetCapture1(TIM1);//获取捕获比较寄存器的数值,这就是除去溢出外的高电平时间
			TIM_OC1PolarityConfig(TIM1, TIM_ICPolarity_Rising);//把捕获设置为上升沿
			g_icValueType.capStartFlag = 0;//开始捕获标志清零
			g_icValueType.capFinishFlag = 1;//捕获结束标志置一
		}
	}
	TIM_ClearITPendingBit(TIM1, TIM_IT_CC1);//无条件,必须要
}

3.3 .h头文件

#ifndef _ULTRASOUND_DRV_H_
#define _ULTRASOUND_DRV_H_

#include "stdint.h"
/**
***************************************************
* @brief	超声波测距驱动初始化
* @param
* @return 
***************************************************
*/
void UltrasoundDrvInit(void);

/**
***************************************************
* @brief	触发超声波探测,两次触发之间最好间隔60ms,因此函数60ms调用一次
* @param
* @return 
***************************************************
*/
void UltrasoundProc(void);

/**
***************************************************
* @brief	获取最终和障碍物的距离,cm
* @param
* @return 
***************************************************
*/
void GetDistance(float* distance);
#endif

3.4 主函数测试代码

        能够显示数值,验证功能就行,下面的代码仅供参考,需要自己根据自己的工程进行修改。

注意,代码中有个60ms的延时一定是要放在UltrasoundProc();后不可改位置不可删除的,用于保证Trig的触发信号不会影响到Echo的回响信号,也就是之前提到的200ms周期!!

static void DrvInit(void)
{
	DelayInit();
	SystickInit();
	BleDrvInit();
	CarDrvInit();
	SteeringDrvInit();
	UltrasoundDrvInit();
}

static void AppInit(void)
{
	RegTaskScheduleCb(TaskScheduleCb);
}

int main(void)
{
	DrvInit();
	AppInit();
	float  distance = 1.0f;
	while(1)
	{
		TaskHandler();//这个不用管,忽略,你们不用写
		UltrasoundProc();
		DelayNms(60);//这个延时十分重要,不可省略,保证周期(虽然不是200ms,也能用)
		GetDistance(&distance);
		printf("DISTANCE:%.2fcm\r\n\n",distance);//需要会串口,不会可以搜文章、视频,或者用其它显示
		distance = 0;
		DelayNms(2000);
	}
}


http://www.kler.cn/a/589855.html

相关文章:

  • 自动化APP测试APPium的元素等待
  • Django Rest Framework 创建纯净版Django项目部署DRF
  • Android Fresco 框架缓存模块源码深度剖析(二)
  • 爬虫代码中需要设置哪些HTTP头部信息?
  • 在遇见— 再遇见
  • docker入门篇
  • Windows 图形显示驱动开发-WDDM 3.0功能- 硬件翻转队列(一)
  • WPF窗口读取、显示、修改、另存excel文件——CAD c#二次开发
  • wordpress导入mysql数据库文件的方法及注意事项
  • Python----计算机视觉处理(Opencv:图片颜色识别:RGB颜色空间,HSV颜色空间,掩膜)
  • 基于CPLD+MCU的3U机箱模拟量采样板(AIO-I),主要功能由模拟量采集,模拟量输出,PWM采集和输出
  • Qt SQL-1
  • 洛谷 P1182 数列分段 Section II 二分详细讲解
  • vue3vue-elementPlus-admin框架中form组件的upload写法
  • 人工智能辅助 3D 建模:Claude + Blender MCP 体验
  • Java高频面试之集合-13
  • vllm-openai多服务器集群部署AI模型
  • 365天之第P10周:Pytorch实现车牌识别
  • [HelloCTF]PHPinclude-labs超详细WP-Level 0
  • 本地部署 RAGFlow - 修改默认端口