工程文件链接: https://pan.baidu.com/s/1-LqQIOwF77NHi4bdWfKIwg?pwd=f4mc

一、设计方案

img

本超声波测距系统主要由四个模块组成,分别为:

DHT22(AM2302)温、湿度采集模块,为本系统自动校正部分的核心,负责温、湿度补偿中的温、湿度数据采集;

HC-SR04超声波测距模块,为本系统测距部分的核心,负责发射、接收超声波用于测量;

0.96寸OLED显示模块,为本系统数据可视化部分的核心,负责各项数据的显示;

STMF103微控制器,为本系统核心,负责控制操作各个外设。

另有三个按键分别作为“+”键、“-”键与重置键,用于补偿值的调整,负责测量值的手动校正。(可根据需要改为报警值,并加上led或者蜂鸣器报警)

二、基本电路连接

img

三、各模块代码

1.main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#include "stm32f10x.h"
#include "OLED.h"
#include "AM2302.h"
#include "HC_SR04.h"
#include "Key.h"

uint32_t Distance; // 存储距离值(单位:厘米)
uint16_t Humidity, Temperature; // 存储温湿度数据
float sound_speed_M_S; // 声速(单位:米/秒)
uint8_t KeyNum = 0; // 按键编号
int16_t compensation_value = 0; // 距离补偿值(单位:毫米)

void System_Init(void); // 系统初始化函数声明
void getData(void); // 获取传感器数据函数声明
void Data_Show(void); // 显示数据函数声明

int main(void)
{
// 初始化系统
System_Init();

while (1)
{
KeyNum = Key_GetNum(); // 获取按键输入

// 根据按键控制补偿值的变化
if(KeyNum == 1){
compensation_value += 1; // 增加补偿值
}
if(KeyNum == 2){
compensation_value -= 1; // 减小补偿值
}
if(KeyNum == 3){
compensation_value = 0; // 重置补偿值
}

// 获取并计算传感器数据
getData();

// 显示计算结果
Data_Show();
}
}

// 系统初始化函数
void System_Init(void){
// 初始化OLED显示屏
OLED_Init();
OLED_ShowString(2, 4, "Loading..."); // 显示加载信息

// 初始化其他外设
Key_Init(); // 初始化按键
AM2302_Init(); // 初始化AM2302温湿度传感器
Drv_Hcsr04_Init(); // 初始化HC-SR04超声波传感器

OLED_Clear(); // 清屏
}

// 获取数据函数
void getData(){
// 获取温湿度传感器数据
AM2302(&Humidity, &Temperature);

// 根据温湿度计算距离
Distance = getDistance(Humidity, Temperature, &sound_speed_M_S);

// 应用补偿值
Distance += compensation_value;

// 化整数方便拆分
sound_speed_M_S *= 1000;
}

// 显示数据函数
void Data_Show(){
uint8_t D_Line = 1, V_Line = 2, H_Line = 3, T_Line = 4;
int16_t abs_cp_value = (compensation_value < 0) ? -compensation_value : compensation_value; // 计算补偿值的绝对值

// 显示距离
OLED_ShowString(D_Line, 1, "D: ");
OLED_ShowNum(D_Line, 4, Distance / 10, 4); // 显示距离(整数部分)
OLED_ShowString(D_Line, 8, "."); // 小数点
OLED_ShowNum(D_Line, 9, Distance % 10, 1); // 显示距离(小数部分)
OLED_ShowString(D_Line, 11, "CM"); // 单位(厘米)

// 显示声速
OLED_ShowString(V_Line, 1, "V: ");
OLED_ShowNum(V_Line, 4, sound_speed_M_S / 1000, 4); // 显示声速(整数部分)
OLED_ShowString(V_Line, 8, "."); // 小数点
OLED_ShowNum(V_Line, 9, (int)sound_speed_M_S % 1000, 3); // 显示声速(小数部分)
OLED_ShowString(V_Line, 12, "M/S"); // 单位(米/秒)

// 显示湿度
OLED_ShowString(H_Line, 1, "H: ");
OLED_ShowNum(H_Line, 4, Humidity / 10, 2); // 显示湿度(整数部分)
OLED_ShowString(H_Line, 6, "."); // 小数点
OLED_ShowNum(H_Line, 7, Humidity % 10, 1); // 显示湿度(小数部分)
OLED_ShowString(H_Line, 8, "%"); // 单位(百分比)

// 显示温度
OLED_ShowString(T_Line, 1, "T: ");
OLED_ShowNum(T_Line, 4, Temperature / 10, 2); // 显示温度(整数部分)
OLED_ShowString(T_Line, 6, "."); // 小数点
OLED_ShowNum(T_Line, 7, Temperature % 10, 1); // 显示温度(小数部分)
OLED_ShowString(T_Line, 8, "C"); // 单位(摄氏度)

// 显示补偿值
OLED_ShowString(T_Line, 10, "B:");
if(compensation_value < 0){ // 判断补偿值是否为负
OLED_ShowString(T_Line, 12, "-"); // 显示负号
}
else{
OLED_ShowString(T_Line, 12, "+"); // 显示正号或空格
}
OLED_ShowNum(T_Line, 13, abs_cp_value, 2); // 显示补偿值的绝对值
OLED_ShowString(T_Line, 15, "mm"); // 单位(毫米)
}

点击并拖拽以移动

注:以下其它模块代码均来源于其它博文,只在其基础上做了些修改。

2.HC_SR04(原博文链接:【STM32F103】HC-SR04超声波测距模块详解(附工程文件)_hcsro4超声波测距模块-CSDN博客

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#include "HC_SR04.h"

// 定义全局变量
uint8_t flag = 0; // 用于记录中断信号是上升沿还是下降沿
uint32_t number = 0; // 记录定时器中断的次数
uint32_t times = 0; // 记录回响信号的持续时间

// 初始化HC-SR04超声波模块
void Drv_Hcsr04_Init(void) {
// 初始化GPIO口,Trig使用推挽输出,Echo使用浮空输入
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA的外设时钟
GPIO_InitTypeDef itd;

// 配置Trig引脚为推挽输出模式
itd.GPIO_Mode = GPIO_Mode_Out_PP; // 选择推挽输出模式
itd.GPIO_Pin = GPIO_Pin_6; // 配置GPIO_Pin_6
itd.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚速度为50MHz
GPIO_Init(GPIOA, &itd); // 初始化GPIOA引脚

// 配置Echo引脚为浮空输入模式
itd.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 选择浮空输入模式
itd.GPIO_Pin = GPIO_Pin_7; // 配置GPIO_Pin_7
GPIO_Init(GPIOA, &itd); // 初始化GPIOA引脚

// 配置外部中断源
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // 使能AFIO的外设时钟
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource7); // 配置外部中断源和中断通道

// 配置EXTI中断
EXTI_InitTypeDef itd1;
itd1.EXTI_Line = EXTI_Line7; // Echo信号使用端口7, 选择7号中断线
itd1.EXTI_LineCmd = ENABLE; // 使能该中断线
itd1.EXTI_Mode = EXTI_Mode_Interrupt; // 配置为中断模式
itd1.EXTI_Trigger = EXTI_Trigger_Rising_Falling; // 上升沿和下降沿都触发中断
EXTI_Init(&itd1); // 初始化外部中断配置

// 配置NVIC优先级
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 设置中断分组
NVIC_InitTypeDef itd2;
itd2.NVIC_IRQChannel = EXTI9_5_IRQn; // 配置使用EXTI9_5_IRQn中断通道
itd2.NVIC_IRQChannelCmd = ENABLE; // 使能该中断通道
itd2.NVIC_IRQChannelPreemptionPriority = 2; // 设置抢占优先级
itd2.NVIC_IRQChannelSubPriority = 2; // 设置响应优先级
NVIC_Init(&itd2); // 初始化中断优先级配置

// 配置定时器 100us
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 使能定时器2时钟
TIM_TimeBaseInitTypeDef itd3;
itd3.TIM_ClockDivision = TIM_CKD_DIV1; // 定时器时钟分频1
itd3.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数模式
itd3.TIM_Period = 72 - 1; // 定时器计数周期
itd3.TIM_Prescaler = 100 - 1; // 设置预分频器
itd3.TIM_RepetitionCounter = 0; // 高级定时器特有参数
TIM_TimeBaseInit(TIM2, &itd3); // 初始化定时器2

// 使能定时器2的中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 使能定时器更新中断
TIM_InternalClockConfig(TIM2); // 选择内部时钟作为定时器时钟源

// 配置定时器的NVIC中断优先级
NVIC_InitTypeDef itd4;
itd4.NVIC_IRQChannel = TIM2_IRQn; // 配置定时器2的中断通道
itd4.NVIC_IRQChannelCmd = ENABLE; // 使能该中断
itd4.NVIC_IRQChannelPreemptionPriority = 1; // 设置抢占优先级
itd4.NVIC_IRQChannelSubPriority = 1; // 设置响应优先级
NVIC_Init(&itd4); // 初始化中断优先级配置
}

// 定时器中断处理函数
void TIM2_IRQHandler(void) {
if (SET == TIM_GetITStatus(TIM2, TIM_FLAG_Update)) {
number++; // 每次定时器中断时,将计数次数增加1
TIM_ClearITPendingBit(TIM2, TIM_FLAG_Update); // 清除中断标志位
}
}

// 外部中断处理函数
void EXTI9_5_IRQHandler(void) {
if (SET == EXTI_GetITStatus(EXTI_Line7)) {
if (flag == 0) {
// 上升沿,回响电平开始,启动定时器
number = 0;
flag = 1; // 标记为上升沿
TIM_SetCounter(TIM2, 0); // 重置定时器计数
TIM_Cmd(TIM2, ENABLE); // 启动定时器
} else {
// 下降沿,回响电平结束,停止计时
TIM_Cmd(TIM2, DISABLE); // 停止定时器
flag = 0; // 标记为下降沿
times = number * 100 + TIM_GetCounter(TIM2); // 计算回响电平的持续时间(us)
}
EXTI_ClearITPendingBit(EXTI_Line7); // 清除中断挂起标志
}
}

// 计算声速(单位:mm/us)
float getSoundSpeed(uint16_t Humidity, uint16_t Temperature) {
float K1 = 0.606, K2 = 0.0124; // 声速公式的常数
float Humidity_Actual = Humidity / 10.0; // 将湿度转换为实际值(单位:%)
float Temperature_Actual = Temperature / 10.0; // 将温度转换为实际值(单位:°C)

// 根据湿度和温度计算声速
float sound_speed = (331.3 + K1 * Temperature_Actual + K2 * Humidity_Actual) / 1000.0; // 单位为mm/us
return sound_speed;
}

// 获取距离(单位:mm),返回声速(单位:m/s)
uint32_t getDistance(uint16_t Humidity, uint16_t Temperature, float* sound_speed_M_S) {
uint32_t distance = 0; // 存储测量的距离
float sound_speed = getSoundSpeed(Humidity, Temperature); // 获取声速
*sound_speed_M_S = sound_speed * 1000; // 转换为米每秒(m/s)

uint32_t measurements[11]; // 存储11次测距结果
uint32_t N = 11; // 采样次数

for (int i = 0; i < N; ++i) {
GPIO_SetBits(GPIOA, GPIO_Pin_6); // 发送Trig信号
Delay_us(15); // 持续15us的高电平
GPIO_ResetBits(GPIOA, GPIO_Pin_6); // 拉低Trig信号
Delay_ms(65); // 等待Echo信号返回
measurements[i] = times * sound_speed / 2; // 计算并存储距离(单位:mm)
}

// 使用冒泡排序对测量结果进行排序
for (int i = 0; i < N - 1; ++i) { // 外层循环
for (int j = 0; j < N - 1 - i; ++j) { // 内层循环
if (measurements[j] > measurements[j + 1]) {
uint32_t temp = measurements[j];
measurements[j] = measurements[j + 1];
measurements[j + 1] = temp; // 交换
}
}
}

// 去掉最大值和最小值,计算剩余数据的平均值
for (int i = 1; i < N-1; ++i) {
distance += measurements[i];
}
distance /= (N - 2); // 返回去除异常值后的平均距离

return distance;
}

点击并拖拽以移动

3.DHT22/AM2302(原博文链接:SRM32fx103驱动AM2302温湿度传感器_am2302引脚-CSDN博客

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#include "AM2302.h"

// 定义湿度、温度相关的变量
uint8_t Hum_H, Hum_L, Temp_H, Temp_L, Check;

// 输出模式下,设置引脚高电平或低电平
void AM2302_DATA_OUT(uint8_t a)
{
if(a == 1)
{
GPIO_SetBits(GPIOA, GPIO_Pin_9); // 设置引脚为高电平
}
else if(a == 0)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_9); // 设置引脚为低电平
}
}

// 输入模式下,读取引脚是高电平还是低电平
uint8_t AM2302_DATA_IN(void)
{
uint8_t a;
a = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_9); // 读取引脚电平
return a;
}

// 设置引脚为推挽输出模式
void AM2302_OUT(void)
{
GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA时钟

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // 配置引脚9
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚速度
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化GPIOA引脚
}

// 设置引脚为上拉输入模式
void AM2302_IN(void)
{
GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA时钟

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // 配置引脚9
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚速度
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化GPIOA引脚
}

// 读取一个字节的数据
uint8_t Read_Byte(void)
{
uint8_t i, temp = 0;
for(i = 0; i < 8; i++) // 循环8次读取一个字节的数据
{
while(AM2302_DATA_IN() == 0); // 等待引脚为高电平
Delay_us(50); // 延时50us

if(AM2302_DATA_IN() == 1) // 如果引脚保持高电平
{
while(AM2302_DATA_IN() == 1); // 等待高电平结束

temp |= (uint8_t)(0x01 << (7 - i)); // 将当前位设置为1
}
else
{
temp &= (uint8_t)~(0x01 << (7 - i)); // 将当前位设置为0
}
}
return temp;
}

// 初始化AM2302传感器,设置2秒的启动延时
void AM2302_Init(void)
{
AM2302_OUT(); // 设置为输出模式
AM2302_DATA_OUT(1); // 输出高电平

Delay_ms(2000); // 延时2秒等待传感器启动
}

// 读取AM2302传感器的数据
void Read_AM2302(void)
{
AM2302_OUT(); // 设置为输出模式
AM2302_DATA_OUT(0); // 拉低引脚,开始通信
Delay_us(1800); // 延时1.8ms
AM2302_DATA_OUT(1); // 拉高引脚,准备接收数据
Delay_us(30); // 延时30us

AM2302_IN(); // 设置为输入模式
if(AM2302_DATA_IN() == 0) // 判断AM2302是否返回响应信号
{
while(AM2302_DATA_IN() == 0); // 等待80ms的低电平响应
while(AM2302_DATA_IN() == 1); // 等待80ms的高电平响应

Hum_H = Read_Byte(); // 读取湿度高字节
Hum_L = Read_Byte(); // 读取湿度低字节
Temp_H = Read_Byte(); // 读取温度高字节
Temp_L = Read_Byte(); // 读取温度低字节
Check = Read_Byte(); // 读取校验字节

AM2302_OUT(); // 设置为输出模式
AM2302_DATA_OUT(1); // 拉高引脚
Delay_us(50); // 延时50us
}
else
{
OLED_Clear(); // 清除OLED显示屏
OLED_ShowString(2, 1, "Error!"); // 显示错误信息
}
}

// 获取温湿度数据并返回
void AM2302(uint16_t* Humidity, uint16_t* Temperature)
{
// 读取 DHT22 温湿度数据
Read_AM2302(); // 读取传感器数据

*Humidity = (Hum_H * 256 + Hum_L); // 湿度数据
*Temperature = Temp_H * 256 + Temp_L; // 温度数据,为实际10倍
}

点击并拖拽以移动

OLED模块与Key的代码使用了B站江科协的。

四、实物展示

img