一、引言
在现代嵌入式系统开发中,传感器技术起着至关重要的作用。超声波测距模块作为一种常用的距离测量传感器,因其成本低、精度较高、使用方便等优点,被广泛应用于机器人避障、液位检测、工业自动化等领域。HC - SR04 超声波测距模块是一款常见的超声波测距传感器,它可以方便地与微控制器进行连接,实现距离的测量。本文将详细介绍如何基于 STM32F407 的 HAL 库来驱动 HC - SR04 超声波测距模块。
二、HC - SR04 超声波测距模块概述
2.1 工作原理
HC - SR04 超声波测距模块的工作原理基于超声波在空气中的传播速度是已知的(在常温下约为 340m/s),通过测量超声波从发射到接收的时间差,就可以计算出模块与目标物体之间的距离。具体工作过程如下:
微控制器向 HC - SR04 的 Trig 引脚发送一个至少 10μs 的高电平脉冲,触发模块发射超声波。
HC - SR04 接收到触发信号后,会自动发射 8 个 40kHz 的超声波脉冲,并将 Echo 引脚置为高电平,开始计时。
当超声波遇到目标物体后反射回来,HC - SR04 接收到反射波,将 Echo 引脚置为低电平,停止计时。
微控制器通过测量 Echo 引脚高电平的持续时间 t,根据公式d=v×t/2(其中v为超声波在空气中的传播速度,d为模块与目标物体之间的距离)计算出距离。
2.2 引脚说明
HC - SR04 超声波测距模块有 4 个引脚,分别是:
VCC:电源正极,一般接 5V。
GND:电源负极,接地。
Trig:触发引脚,用于接收微控制器的触发信号。
Echo:回响引脚,用于输出超声波从发射到接收的时间信号。
2.3 性能参数
工作电压:5V
工作电流:15mA
探测距离:2cm - 400cm
测量精度:±3mm
探测角度:<15°
超声波频率:40kHz
三、STM32F407 简介
3.1 主要特性
高性能:采用 ARM Cortex - M4 内核,最高主频可达 168MHz,具有丰富的外设资源。
低功耗:支持多种低功耗模式,适用于对功耗要求较高的应用场景。
丰富的外设:包含多个串口、SPI、I2C 等通信接口,以及定时器、ADC、DAC 等功能模块。
3.2 HAL 库
HAL(Hardware Abstraction Layer)库是 ST 公司为 STM32 系列微控制器提供的硬件抽象层库,它简化了开发人员对硬件外设的操作,提高了代码的可移植性和开发效率。
四、硬件连接
将 HC - SR04 超声波测距模块与 STM32F407 开发板进行连接,连接方式如下:
需要注意的是,由于 HC - SR04 的 VCC 为 5V,而 STM32F407 的 GPIO 引脚一般为 3.3V,为了避免损坏 STM32F407 的 GPIO 引脚,需要在 Echo 引脚与 STM32F407 的 GPIO 输入引脚之间添加一个电平转换电路,将 5V 信号转换为 3.3V 信号。可以使用一个简单的分压电路来实现电平转换,例如使用两个电阻R1和R2,将R1连接到 Echo 引脚,R2接地,R1和R2的中间节点连接到 STM32F407 的 GPIO 输入引脚,选择合适的电阻值使得中间节点的电压为 3.3V。
五、开发环境搭建
5.1 安装开发工具
Keil MDK:用于编写、编译和调试 STM32 的代码。
STM32CubeMX:用于配置 STM32 的外设和生成初始化代码。
5.2 配置 STM32CubeMX
打开 STM32CubeMX,选择 STM32F407 芯片。
配置系统时钟,将主频设置为 168MHz。
配置 GPIO 引脚:
将 Trig 引脚配置为推挽输出模式。
将 Echo 引脚配置为浮空输入模式。
配置定时器:为了精确测量 Echo 引脚高电平的持续时间,需要使用定时器。选择一个合适的定时器(例如 TIM2),配置定时器的时钟源、预分频器和自动重载值,使得定时器的计数周期为 1μs。
生成代码,选择 Keil MDK 作为开发工具,点击 “Generate Code” 生成初始化代码。
六、代码实现
6.1 初始化 GPIO 和定时器
在生成的初始化代码中,已经包含了 GPIO 和定时器的初始化函数。以下是一个简单的示例代码,用于初始化 GPIO 和定时器:
#include "stm32f4xx_hal.h"
TIM_HandleTypeDef htim2;
GPIO_InitTypeDef GPIO_InitStruct = {0};
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM2_Init(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM2_Init();
while (1)
{
// 主循环
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** 初始化RCC振荡器
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = 8;
RCC_OscInitStruct.PLL.PLLN = 336;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 7;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** 初始化RCC时钟
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
}
static void MX_TIM2_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim2.Instance = TIM2;
htim2.Init.Prescaler = 168 - 1; // 定时器时钟频率为1MHz,计数周期为1μs
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 0xFFFF;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
}
static void MX_GPIO_Init(void)
{
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);
/*Configure GPIO pin : PA0 */
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pin : PA1 */
GPIO_InitStruct.Pin = GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
void Error_Handler(void)
{
while(1)
{
}
}
6.2 触发 HC - SR04 发射超声波
为了触发 HC - SR04 发射超声波,需要向 Trig 引脚发送一个至少 10μs 的高电平脉冲。以下是一个触发 HC - SR04 发射超声波的函数:
void Trigger_HC_SR04(void)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET); // 将Trig引脚置为高电平
HAL_Delay(1); // 延时1ms,确保高电平持续时间足够长
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET); // 将Trig引脚置为低电平
}
6.3 测量 Echo 引脚高电平的持续时间
使用定时器来测量 Echo 引脚高电平的持续时间。以下是一个测量 Echo 引脚高电平持续时间的函数:
uint32_t Measure_Echo_Time(void)
{
uint32_t start_time, end_time;
// 等待Echo引脚变为高电平
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET);
// 启动定时器
HAL_TIM_Base_Start(&htim2);
start_time = __HAL_TIM_GET_COUNTER(&htim2);
// 等待Echo引脚变为低电平
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_SET);
// 停止定时器
HAL_TIM_Base_Stop(&htim2);
end_time = __HAL_TIM_GET_COUNTER(&htim2);
return end_time - start_time; // 返回高电平持续时间(单位:μs)
}
6.4 计算距离
根据测量得到的 Echo 引脚高电平持续时间,使用公式d=v×t/2计算出距离。以下是一个计算距离的函数:
float Calculate_Distance(uint32_t time)
{
float distance;
distance = (float)time * 0.034 / 2; // 超声波传播速度为340m/s,转换为cm/μs为0.034cm/μs
return distance; // 返回距离(单位:cm)
}
6.5 主函数
在主函数中,调用上述函数完成超声波测距的操作。以下是一个完整的主函数示例:
#include "stm32f4xx_hal.h"
TIM_HandleTypeDef htim2;
GPIO_InitTypeDef GPIO_InitStruct = {0};
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM2_Init(void);
void Trigger_HC_SR04(void);
uint32_t Measure_Echo_Time(void);
float Calculate_Distance(uint32_t time);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM2_Init();
float distance;
uint32_t echo_time;
while (1)
{
Trigger_HC_SR04(); // 触发HC - SR04发射超声波
echo_time = Measure_Echo_Time(); // 测量Echo引脚高电平的持续时间
distance = Calculate_Distance(echo_time); // 计算距离
// 可以在这里将距离值通过串口等方式输出
HAL_Delay(1000); // 延时1s,避免频繁测量
}
}
// 其他函数定义保持不变
七、调试与测试
7.1 硬件连接检查
在进行调试之前,需要检查硬件连接是否正确,确保 HC - SR04 超声波测距模块的电源、Trig 引脚和 Echo 引脚连接无误,同时检查电平转换电路是否正常工作。
7.2 串口调试
可以通过串口将测量得到的距离值输出到串口调试助手中,方便观察和调试。在代码中添加串口初始化和数据发送的代码,例如使用 USART1 进行串口通信:
#include "stm32f4xx_hal.h"
UART_HandleTypeDef huart1;
TIM_HandleTypeDef htim2;
GPIO_InitTypeDef GPIO_InitStruct = {0};
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM2_Init(void);
static void MX_USART1_UART_Init(void);
void Trigger_HC_SR04(void);
uint32_t Measure_Echo_Time(void);
float Calculate_Distance(uint32_t time);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM2_Init();
MX_USART1_UART_Init();
float distance;
uint32_t echo_time;
char buffer[50];
while (1)
{
Trigger_HC_SR04(); // 触发HC - SR04发射超声波
echo_time = Measure_Echo_Time(); // 测量Echo引脚高电平的持续时间
distance = Calculate_Distance(echo_time); // 计算距离
// 将距离值转换为字符串
sprintf(buffer, "Distance: %.2f cm\r\n", distance);
// 通过串口发送距离值
HAL_UART_Transmit(&huart1, (uint8_t *)buffer, strlen(buffer), HAL_MAX_DELAY);
HAL_Delay(1000); // 延时1s,避免频繁测量
}
}
void SystemClock_Config(void)
{
// 系统时钟配置函数,保持不变
}
static void MX_TIM2_Init(void)
{
// 定时器初始化函数,保持不变
}
static void MX_GPIO_Init(void)
{
// GPIO初始化函数,保持不变
}
static void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 9600;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
}
void Trigger_HC_SR04(void)
{
// 触发HC - SR04发射超声波函数,保持不变
}
uint32_t Measure_Echo_Time(void)
{
// 测量Echo引脚高电平持续时间函数,保持不变
}
float Calculate_Distance(uint32_t time)
{
// 计算距离函数,保持不变
}
void Error_Handler(void)
{
while(1)
{
}
}
7.3 实际测试
将 STM32F407 开发板和 HC - SR04 超声波测距模块放置在合适的位置,使用不同距离的目标物体进行测试,观察串口调试助手中输出的距离值是否准确。
八、常见问题及解决方法
8.1 测量结果不准确
原因:
超声波传播过程中受到干扰,例如周围有其他超声波源或障碍物。
定时器精度不够,导致测量的 Echo 引脚高电平持续时间不准确。
电平转换电路存在问题,导致 Echo 引脚信号异常。
解决方法:
尽量避免在有干扰的环境下使用 HC - SR04 超声波测距模块,或者采取屏蔽措施减少干扰。
检查定时器的配置,确保定时器的计数周期准确,同时可以考虑使用更高精度的定时器。
检查电平转换电路的连接和电阻值,确保电平转换正常。
8.2 无法触发 HC - SR04 发射超声波
原因:
Trig 引脚连接不正确,或者没有输出正确的触发信号。
HC - SR04 模块本身存在故障。
解决方法:
检查 Trig 引脚的连接,确保其与 STM32F407 的 GPIO 输出引脚连接正确,同时检查触发信号的持续时间是否满足要求。
更换 HC - SR04 模块,检查是否是模块本身的问题。
8.3 无法测量 Echo 引脚高电平的持续时间
原因:
Echo 引脚连接不正确,或者没有接收到正确的回响信号。
定时器没有正常工作。
解决方法:
检查 Echo 引脚的连接,确保其与 STM32F407 的 GPIO 输入引脚连接正确,同时检查电平转换电路是否正常工作。
检查定时器的初始化和启动、停止操作,确保定时器能够正常工作。
九、总结
通过本文的介绍,你已经了解了如何基于 STM32F407 的 HAL 库来驱动 HC - SR04 超声波测距模块。从硬件连接、开发环境搭建到代码实现和调试,详细介绍了整个开发过程。希望本文能帮助你顺利完成基于 HC - SR04 超声波测距模块的嵌入式系统开发。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/m0_75187370/article/details/147188596
|