这是一个基于STM32F103C8T6和HC-SR04超声波模块的测距监测系统。项目在温湿度采集系统的代码框架基础上开发,复用了任务调度器、按键、数码管等底层驱动,重点实现了超声波测距和距离监控功能。从代码移植到功能调试,记录了完整的开发过程。
- 超声波测距:使用HC-SR04模块,测量范围2cm-400cm
- 实时显示:4位数码管显示当前距离/阈值(单位:厘米)
- 距离监控:支持距离上限/下限两个阈值设置
- 智能报警:距离超出阈值范围时蜂鸣器PWM报警
- 按键控制:单击切换显示模式,双击进入阈值设置
- LED指示:LED1/LED2分别指示距离模式和阈值设置模式
- 串口回显:每500ms输出当前距离和系统状态
| 硬件模块 | 型号/规格 | 说明 |
|---|---|---|
| 主控MCU | STM32F103C8T6 | 72MHz主频,64KB Flash |
| 超声波模块 | HC-SR04 | 测距范围2-400cm |
| 数码管 | 4位共阴极数码管 | 显示距离和阈值 |
| 按键 | 4个独立按键 | 菜单和参数调节 |
| 蜂鸣器 | 无源蜂鸣器 | PWM驱动报警 |
| LED指示灯 | 2个LED | 模式指示 |
| 串口模块 | USART1 | 调试和数据回显 |
// HC-SR04超声波模块
HC_SR04_TRIG -> PB8 // 触发引脚
HC_SR04_ECHO -> PB9 // 回响引脚
// 数码管
段选 A-G,DP -> PA0-PA7
位选 DIG1-4 -> PB0, PB1, PB10, PB11
// 按键
KEY1(模式) -> PB12
KEY2(切换) -> PB13
KEY3(增加) -> PB14
KEY4(减少) -> PB15
// LED指示灯
LED1 -> PB6 // 距离显示模式
LED2 -> PB7 // 阈值设置模式
// 蜂鸣器 (通过TIM3 PWM驱动)
BEEP -> PB5 (TIM3_CH2, 部分重映射)
// 定时器
TIM2 -> 超声波计时(10μs中断)
TIM4 -> 系统时基(1ms中断)
TIM3 -> 蜂鸣器PWM(2.7kHz)HC-SR04使用超声波测距,基本流程:
- 触发信号:主机给TRIG引脚发送≥10μs的高电平
- 发射超声波:模块自动发射8个40kHz超声波脉冲
- 等待回响:ECHO引脚输出高电平,持续时间即为超声波往返时间
- 计算距离:距离 = (回响时间 × 音速) / 2
音速(25°C):v = 346 m/s = 0.0346 cm/μs
距离(cm):Distance = (time × 0.0346) / 2
代码实现:
Distance = (time_end * 346) / 2; // time_end单位:10μs
Distance_mm = Distance / 100; // 转换为毫米
distance_cm = Distance_mm / 10; // 转换为厘米- 理论范围:2cm - 400cm
- 盲区:2cm以内(回响信号未分离)
- 精度:±3mm(受温度、湿度影响)
- 测量角度:15°(有效探测角)
本项目复用了温湿度采集系统的代码架构:
// 相同的模块(直接复用)
✅ 任务调度器 (sch_task)
✅ 按键驱动 (key.c/h) - 支持单击/双击
✅ 数码管驱动 (smg.c/h) - 动态扫描
✅ LED驱动 (led.c/h)
✅ 串口驱动 (usart.c/h)
✅ 延时函数 (delay.c/h)
// 需要修改的模块
🔧 蜂鸣器 (beep.c/h) - 引脚从PA6改为PB5
🔧 定时器 (timer.c/h) - 新增TIM2用于超声波计时
// 新增模块
➕ HC-SR04驱动 (HCSR04.c/h) - 超声波测距| 功能 | 原引脚(温湿度) | 新引脚(超声波) | 变更原因 |
|---|---|---|---|
| 传感器DATA | PB8 (DHT11) | - | 更换为HC-SR04 |
| 超声波TRIG | - | PB8 | 新增 |
| 超声波ECHO | - | PB9 | 新增 |
| 蜂鸣器 | PA6 (TIM3_CH1) | PB5 (TIM3_CH2) | PCB布线优化 |
// 温湿度项目
unsigned char temp; // 温度
unsigned char humi; // 湿度
unsigned char temp_high; // 温度上限
unsigned char temp_low; // 温度下限
unsigned char humi_high; // 湿度上限
unsigned char humi_low; // 湿度下限
// 超声波项目
unsigned int distance; // 当前距离(厘米)
unsigned int distance_high; // 距离上限(厘米)
unsigned int distance_low; // 距离下限(厘米)// 温湿度:6个菜单
menu = 0: 显示温度
menu = 1: 显示湿度
menu = 2: 温度上限
menu = 3: 温度下限
menu = 4: 湿度上限
menu = 5: 湿度下限
// 超声波:4个菜单
menu = 0: 显示距离
menu = 1: 阈值设置(预留,未使用)
menu = 2: 距离上限
menu = 3: 距离下限void HC_SR04_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
// TRIG - 推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; // PB8
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// ECHO - 下拉输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // PB9
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_WriteBit(GPIOB, GPIO_Pin_8, 0); // TRIG初始为低
delay_us(15);
}int16_t sonar_mm(void)
{
uint32_t Distance, Distance_mm = 0;
// 1. 发送10μs触发信号
GPIO_WriteBit(GPIOB, Trig, 1);
delay_us(15); // 至少10μs
GPIO_WriteBit(GPIOB, Trig, 0);
// 2. 等待ECHO变为高电平
while(GPIO_ReadInputDataBit(GPIOB, Echo) == 0);
// 3. 开始计时
time = 0;
// 4. 等待ECHO变为低电平(回响结束)
while(GPIO_ReadInputDataBit(GPIOB, Echo) == 1);
// 5. 记录时间
time_end = time;
// 6. 判断是否超时(38ms对应约650cm)
if(time_end / 100 < 38)
{
Distance = (time_end * 346) / 2;
Distance_mm = Distance / 100;
}
return Distance_mm;
}// 每10μs触发一次中断
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
time++; // 计时变量累加
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}由于引脚从PA6改为PB5,需要使用TIM3的部分重映射功能。
void Init_timer3(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 使能时钟和复用功能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB |
RCC_APB2Periph_AFIO, ENABLE);
// 关键:TIM3部分重映射,CH2 -> PB5
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
// 配置PB5为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 时基配置:72MHz / 72 / 370 ≈ 2.7kHz
TIM_TimeBaseStructure.TIM_Period = 369;
TIM_TimeBaseStructure.TIM_Prescaler = 71;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
// PWM配置
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0; // 初始占空比0
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
// 通道2初始化(不是CH1)
TIM_OC2Init(TIM3, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM3, ENABLE);
TIM_Cmd(TIM3, ENABLE);
}
// 开启蜂鸣器
void Open_Beep(void)
{
TIM_SetCompare2(TIM3, 180); // 注意是SetCompare2
}
// 关闭蜂鸣器
void Close_Beep(void)
{
TIM_SetCompare2(TIM3, 0);
}void alarm_proc(void)
{
// 只在距离显示模式(menu=0)下检测报警
if(menu == 0)
{
// 当距离超出上限或低于下限时报警
if(distance > distance_high || distance < distance_low)
Open_Beep();
else
Close_Beep();
}
else
{
// 阈值设置模式下,关闭报警
Close_Beep();
}
}void seg_proc(void)
{
unsigned int display_value = 0;
// 根据菜单选择显示内容
switch(menu)
{
case 0: display_value = distance; break;
case 2: display_value = distance_high; break;
case 3: display_value = distance_low; break;
default: display_value = 0; break;
}
// 三位数显示(支持0-400cm)
smg_buf[0] = display_value / 100; // 百位
smg_buf[1] = (display_value / 10) % 10; // 十位
smg_buf[2] = display_value % 10; // 个位
smg_buf[3] = 10; // 熄灭
}问题现象:蜂鸣器不响,PA6有PWM输出但PB5没有
排查过程:
- 使用示波器测量PA6,发现有PWM波形
- 测量PB5,没有输出
- 查阅STM32参考手册,发现TIM3可以部分重映射
解决方案:
// 添加重映射配置
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
// TIM3部分重映射后的引脚对应关系:
// CH1: PA6 -> PA7
// CH2: PA7 -> PB5 ← 我们使用这个
// CH3: PB0 -> PB0 (不变)
// CH4: PB1 -> PB1 (不变)经验教训:
- STM32的复用功能需要显式开启重映射
- 不同的重映射模式有不同的引脚对应关系
- 必须查阅芯片参考手册确认
问题现象:距离数据跳变严重,偶尔显示0或400+
排查过程:
- 使用逻辑分析仪抓取TRIG和ECHO波形
- 发现ECHO有时没有响应
- 检查HC-SR04供电,发现电压偏低(3.1V)
解决方案:
- 使用5V供电(HC-SR04推荐5V)
- 添加软件滤波:
void ultrasonic_task(void)
{
int16_t dist_mm = sonar_mm();
distance = dist_mm / 10;
// 限幅处理
if(distance > 400) distance = 400;
if(distance < 2) distance = 2;
}关键参数:
// 目标:10μs中断一次
// 公式:T = (Prescaler + 1) * (Period + 1) / 72MHz
TIM_TimeBaseStructure.TIM_Period = 9; // ARR = 9
TIM_TimeBaseStructure.TIM_Prescaler = 71; // PSC = 71
// 计算:T = 72 * 10 / 72,000,000 = 10μs ✓初版问题:
// while循环阻塞,导致其他任务无法执行
while(GPIO_ReadInputDataBit(GPIOB, Echo) == 0);
while(GPIO_ReadInputDataBit(GPIOB, Echo) == 1);改进方向: 可以使用外部中断+定时器方式实现非阻塞测距:
- ECHO上升沿:开始TIM2计时
- ECHO下降沿:停止TIM2计时,读取CNT寄存器
目前采用300ms采集周期,阻塞时间短(<30ms),暂未影响系统响应。
task_t sch_task[] = {
{key_proc, 10, 0}, // 按键扫描 - 10ms
{seg_proc, 20, 0}, // 数码管显示 - 20ms
{led_proc, 100, 0}, // LED指示 - 100ms
{ultrasonic_task, 300, 0}, // 超声波采集 - 300ms
{alarm_proc, 100, 0}, // 报警检测 - 100ms
{uart_proc, 500, 0} // 串口回显 - 500ms
};- 上电初始化:显示当前距离,LED1亮
- KEY1单击:切换到阈值设置模式(预留)
- KEY1双击:进入距离上限 ↔ 距离下限设置切换
- KEY3:阈值+1(设置模式下)
- KEY4:阈值-1(设置模式下)
- 自动监控:距离超出范围时蜂鸣器报警
========== 超声波测距系统 ==========
==== 距离监测 =====
== YYDS ==
Distance: 25 cm, Menu: 0, LED1: 1, LED2: 0, Alarm: OFF
Distance: 102 cm, Menu: 0, LED1: 1, LED2: 0, Alarm: ON
Distance: 50 cm, Menu: 2, LED1: 0, LED2: 1, Alarm: OFF
| 实际距离(cm) | 测量值(cm) | 误差(cm) | 误差率 |
|---|---|---|---|
| 5 | 5 | 0 | 0% |
| 10 | 10 | 0 | 0% |
| 20 | 20 | 0 | 0% |
| 50 | 51 | +1 | 2% |
| 100 | 101 | +1 | 1% |
| 200 | 203 | +3 | 1.5% |
| 300 | 295 | -5 | 1.7% |
结论:近距离(<100cm)精度较高,远距离(>200cm)误差略增大
- 采集周期:300ms
- 单次测距时间:约20-30ms(取决于距离)
- 报警延迟:<100ms(报警任务周期)
| 测试条件 | 测量稳定性 | 备注 |
|---|---|---|
| 室内静止 | 优秀 | 波动±1cm |
| 移动目标 | 良好 | 需要滤波 |
| 强光照射 | 无影响 | 超声波不受光线影响 |
| 软质表面 | 较差 | 吸收超声波,反射弱 |
| 倾斜角度>15° | 不稳定 | 回响信号弱 |
使用外部中断方式:
// EXTI9配置为ECHO引脚
void EXTI9_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line9) != RESET)
{
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_9))
{
// 上升沿:开始计时
TIM_SetCounter(TIM2, 0);
TIM_Cmd(TIM2, ENABLE);
}
else
{
// 下降沿:停止计时
time_end = TIM_GetCounter(TIM2);
TIM_Cmd(TIM2, DISABLE);
}
EXTI_ClearITPendingBit(EXTI_Line9);
}
}中值滤波或均值滤波:
#define FILTER_SIZE 5
uint16_t distance_buffer[FILTER_SIZE];
uint16_t median_filter(void)
{
// 冒泡排序
for(int i=0; i<FILTER_SIZE-1; i++)
for(int j=0; j<FILTER_SIZE-1-i; j++)
if(distance_buffer[j] > distance_buffer[j+1])
swap(&distance_buffer[j], &distance_buffer[j+1]);
return distance_buffer[FILTER_SIZE/2]; // 返回中位数
}音速随温度变化:
// 音速(m/s) = 331.5 + 0.6 * 温度(°C)
float sound_speed = 331.5 + 0.6 * temperature;
Distance = (time_end * sound_speed / 10000) / 2; // 单位转换- 模块化设计:复用代码框架,快速开发新功能
- 定时器应用:掌握TIM基本定时、PWM输出、输入捕获
- 引脚复用:学会使用GPIO重映射功能
- 协议通信:理解HC-SR04的时序要求
- 调试技巧:逻辑分析仪、示波器的使用
| 挑战 | 解决方案 | 经验 |
|---|---|---|
| 定时器重映射配置 | 查阅参考手册 | 不能想当然,必须看文档 |
| 测距数据跳变 | 滤波+限幅处理 | 传感器数据需要预处理 |
| 阻塞式测距影响响应 | 优化采集周期 | 可改用中断方式 |
| 蜂鸣器引脚冲突 | 更换引脚和通道 | 提前规划引脚分配 |
- 代码复用:90%的底层代码直接复用,大幅提高开发效率
- 任务调度:清晰的多任务架构,便于功能扩展
- 精确计时:10μs级定时器中断,满足测距精度要求
- 完善的报警:实时监控,超阈值自动报警
- 友好交互:数码管+LED+按键,操作简单直观
- 增加LCD显示距离变化曲线
- 实现非阻塞式测距(外部中断)
- 添加数字滤波算法
- 温度补偿提高精度
- 支持多目标检测
- 低功耗模式
距离上限:100 cm
距离下限:10 cm
采集周期:300 ms
PWM频率:2.7 kHz
计时精度:10 μs| 功能 | 引脚 | 复用功能 | 备注 |
|---|---|---|---|
| HC-SR04 TRIG | PB8 | GPIO | 输出 |
| HC-SR04 ECHO | PB9 | GPIO | 输入 |
| 蜂鸣器 | PB5 | TIM3_CH2 | 部分重映射 |
| 系统时基 | - | TIM4 | 1ms中断 |
| 超声波计时 | - | TIM2 | 10μs中断 |
项目作者:学电子他就能回来吗 完成时间:2025年12月 开源地址:[GitHub仓库链接]
如果这个项目对你有帮助,欢迎点赞收藏!有问题欢迎在评论区讨论~