HC-SR04 超声波测距模块可提供 2cm-400cm 的非接触式距离感测功能,测距精度可达高到 3mm;
一、超声波工作原理
1、CH-SR04 相关电气参数 
二、计算公式(理解时参考时序图)
(1)采用 IO 触发测距,给至少 10us 的高电平信号;
(2)模块自动发送 8 个 40khz 的方波,自动检测是否有信号返回;
(3)有信号返回,通过 IO 输出一高电平,高电平持续的时间就是超声波从发射到返回的时间
(4)超声波从发射到返回的时间.测试距离=(高电平时间*声速(340M/S))/2;
在此只需要提供一个 10uS 以上脉冲触发信号,该模块内部将循环发出 8 个 40kHz 周期电平并检测回波。一旦检测到有回波信号则输出回响信号。 回响信号的脉冲宽度与所测的距离成正比。由此通过发射信号到收到的回响信号时间间隔可以计算得到距离。公式:uS/58=厘米或者 uS/148=英寸;或是:距离= 高电平时间 * 声速(340M/S)/2;建议测量周期为 60ms 以上,以防止发射信号对 回响信号的影响。
三、超声波时序图
四、定时器计时原理
● 计数器寄存器(TIMx_CNT)
● 预分频寄存器(TIMx_PSC)
● 自动重装载寄存器(TIMx_ARR)
- 时钟源(CK_INT)
定时器时钟 TIMxCLK,即内部时钟 CK_INT,经 APB1 预分频器后分频提供,如果 APB1 预分频系数等于 1,则频率不变,否则频率乘以 2,库函数中 APB1 预分频的系数是 2,即 PCLK1=36M,所以定时器时钟 TIMxCLK=36*2=72M - 预分频器(PSC)
PSC 是一个 16 位的预分频器,可以对定时器时钟 TIMxCLK 进行 1~65536 之间的任何一个数进行分频。具体计算方式为: CK_CNT=TIMxCLK/(PSC+1)
- 计数器(CNT)
计数器 CNT 是一个 16 位的计数器,只能往上计数,最大计数值为 65535。当计数达到自动重装载寄存器的时候产生更新事件,并清零从头开始计数。
- 自动重装载寄存器(ARR)
自动重装载寄存器 ARR 是一个 16 位的寄存器,这里面装着计数器能计数的最大数值。当计数到这个值的时候,如果使能了中断的话,定时器就产生溢出中断。
定时器预分频器设置
// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
TIM_TimeBaseStructure.TIM_Period = BASIC_TIM_Period;
// 时钟预分频数为
TIM_TimeBaseStructure.TIM_Prescaler= BASIC_TIM_Prescaler;
当内部时钟经过PSC预分频器分频后,1000 000Hz, 也就是1us ,ARR设置为1000,意味着,CNT计数器每1us记录一次 , 当1ms 时ARR寄存器清零。
五、公式推导
因为时钟以微秒为最小计算单位,所有方便理解,将所有的转换成微妙计算
distance = 定时器时间 (s)* 340(m/s) / 2
distance = 定时时间(us)* 34000 / 2 / 1000 000 = 定时时间(us)* 0.017
另一种算法:distance = 定时时间(us) / 58
六、相关注意事项:
1、此模块不宜带电连接,若要带电连接,则先让模块的 GND 端先连接,否则会影响 模块的正常工作。
2、测距时,被测物体的面积不少于 0.5 平方米且平面尽量要求平整,否则影响测量的结果
3、当传感器紧贴物体表面时,会出现数值错误,
七、参考文献
(19条消息) 超声波测距为什么除以58_超声波测距公式为什么除以58_总结所学的博客-优快云博客
相关代码
Ultrasonic.c
/**
* @brief 超声波相关引脚初始化
* @param NULL
* @retval NULL
*/
static void UltrasonicGpioModeConfig(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_TE_PeriphClockCmd(ECHO_TRIG_CLK_ENR,ENABLE); //引脚时钟使能
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin = TRIG_PIN;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(TRIG_PORT,&GPIO_InitStruct); //TRIG 引脚
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; //ECHO 设置为上拉输入,接受信号
GPIO_InitStruct.GPIO_Pin = ECHO_PIN;
// GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(ECHO_PORT,&GPIO_InitStruct); //ECHO 引脚
TRIG_LOW(); /* 让两引脚初始化时处于低电平 */
ECHO_LOW();
}
/**
* @brief 定时器中断配置
* @param NULL
* @retval NULL
*/
static void NVIC_TIME_Config(void)
{
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
NVIC_InitStruct.NVIC_IRQChannel = BASIC_TIM_IRQ;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
static void BASIC_TIM_Mode_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// 开启定时器时钟,即内部时钟CK_INT=72M
BASIC_TIM_APBxClock_FUN(BASIC_TIM_CLK, ENABLE);
// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
TIM_TimeBaseStructure.TIM_Period = BASIC_TIM_Period;
// 时钟预分频数为
TIM_TimeBaseStructure.TIM_Prescaler= BASIC_TIM_Prescaler;
// 时钟分频因子 ,基本定时器没有,不用管
//TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
// 计数器计数模式,基本定时器只能向上计数,没有计数模式的设置
//TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
// 重复计数器的值,基本定时器没有,不用管
//TIM_TimeBaseStructure.TIM_RepetitionCounter=0;
// 初始化定时器
TIM_TimeBaseInit(BASIC_TIM, &TIM_TimeBaseStructure);
// 清除计数器中断标志位
TIM_ClearFlag(BASIC_TIM, TIM_FLAG_Update);
// 开启计数器中断
TIM_ITConfig(BASIC_TIM, TIM_IT_Update,ENABLE);
// 使能计数器
// TIM_Cmd(BASIC_TIM, ENABLE);
}
/**
* @brief 超声波触发信号
* @param NULL
* @retval NULL
*/
void WAVE_Start(void)
{
TRIG_HIGH(); //TRIG设置为高电平
CPU_TS_Tmr_Delay_US(20); //延时大于10us
TRIG_LOW();
}
/**
* @brief 超声波相关初始化
* @param NULL
* @retval NULL
*/
void WAVE_Init(void)
{
UltrasonicGpioModeConfig();
NVIC_TIME_Config();
BASIC_TIM_Mode_Config();
}
Ultrasonic.h
#define TRIG_RCC_CLK_ENR RCC_APB2Periph_GPIOA
#define RCC_TE_PeriphClockCmd RCC_APB2PeriphClockCmd
#define TRIG_PORT GPIOA
#define TRIG_PIN GPIO_Pin_4
#define ECHO_RCC_CLK_ENR RCC_APB2Periph_GPIOA
#define RCC_ECHO_ClockCmd RCC_APB2PeriphClockCmd
#define ECHO_PORT GPIOA
#define ECHO_PIN GPIO_Pin_5
#define ECHO_TRIG_CLK_ENR (ECHO_RCC_CLK_ENR|TRIG_RCC_CLK_ENR)
#define BASIC_TIM6 // 如果使用TIM7,注释掉这个宏即可
#ifdef BASIC_TIM6 // 使用基本定时器TIM6
#define BASIC_TIM TIM6
#define BASIC_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define BASIC_TIM_CLK RCC_APB1Periph_TIM6
#define BASIC_TIM_Period (1000-1)
#define BASIC_TIM_Prescaler (72-1) // 72000000/72 *1000= 1000us = lms
#define BASIC_TIM_IRQ TIM6_IRQn
#define BASIC_TIM_IRQHandler TIM6_IRQHandler
#else // 使用基本定时器TIM7
#define BASIC_TIM TIM7
#define BASIC_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define BASIC_TIM_CLK RCC_APB1Periph_TIM7
#define BASIC_TIM_Period 1000-1
#define BASIC_TIM_Prescaler 71
#define BASIC_TIM_IRQ TIM7_IRQn
#define BASIC_TIM_IRQHandler TIM7_IRQHandler
#endif
#define TRIG_HIGH() GPIO_SetBits(TRIG_PORT,TRIG_PIN)
#define TRIG_LOW() GPIO_ResetBits(TRIG_PORT,TRIG_PIN)
#define ECHO_HIGH() GPIO_SetBits(ECHO_PORT,ECHO_PIN)
#define ECHO_LOW() GPIO_ResetBits(ECHO_PORT,ECHO_PIN)
void WAVE_Init(void);
void WAVE_Start(void);
main.c
float distance;
volatile uint32_t time=0;
uint32_t TIME = 0;
int main(void)
{
uint8_t i;
USART_Config();
SysTick_Init();
LED_GPIO_Config();
WAVE_Init();
while (1)
{
for (i = 0; i < 5; i++) //求均值,避免误差
{
WAVE_Start(); // 开启信号
while (GPIO_ReadInputDataBit(ECHO_PORT, ECHO_PIN) == RESET); // ECHO 为高电平
TIM_SetCounter(BASIC_TIM, 0);
time =0;
TIM_Cmd(BASIC_TIM, ENABLE);
while(GPIO_ReadInputDataBit(ECHO_PORT, ECHO_PIN) == SET); // 等待ECHO 低电平
TIM_Cmd(BASIC_TIM, DISABLE);
TIME += ((time*1000)+ TIM_GetCounter(BASIC_TIM));
printf("采集到的时间为 %d us\n", TIME);
}
printf("采集到的时间平均值 %d ms\n", (TIME/5));
distance = (TIME/5 * 0.017 ); //第一种算法
//distance = (((TIME) /58 )); //第二种算法
printf("Distance:%f cm\r\n", distance);
TIME=0;
Delay_ms(2000);
}
}
stm32f10x_it.h
extern volatile uint32_t time ;
void TIM6_IRQHandler(void)
{
if(TIM_GetITStatus(BASIC_TIM,TIM_IT_Update)!=RESET)
{
time++;
TIM_ClearITPendingBit(BASIC_TIM, TIM_IT_Update);
}
}