基于 STM32L431 的 HC-SR04 超声波与 ST188 红外光电传感器实验总结
文章目录
- 基于 STM32L431 的 HC-SR04 超声波与 ST188 红外光电传感器实验总结
- 一、前言
- 二、HC-SR04 超声波测距实验
- 2.1 HC-SR04 工作原理
- 三、超声波距离公式推导
- 四、HC-SR04 程序实现过程
- 五、HC-SR04 实验中的重要认识
- 六、HC-SR04 实验中容易出现的问题
- 七、ST188 红外光电传感器实验
- 八、ST188 GPIO 初始化
- 九、读取 ST188 状态
- 十、ST118_Handle() 处理函数
- 十一、HAL_GetTick() 和 HAL_Delay() 的区别
- 十二、两个实验中发现的 STM32 程序规律
- 十三、输入、处理、输出思想
- 十四、目前常用的 STM32 HAL 函数总结
- 1. HAL_Init()
- 2. HAL_GPIO_Init()
- 3. HAL_GPIO_ReadPin()
- 4. HAL_GPIO_WritePin()
- 5. HAL_GetTick()
- 6. HAL_Delay()
- 7. printf()
- 十五、串口调试的重要性
- 十六、STM32 调试时总结的排查规律
- 十七、不要完全相信代码注释
- 十八、轮询、中断和定时器的理解
- 十九、HC-SR04 的进一步改进
- 二十、ST188 的进一步扩展
- 二十一、实验心得
- 二十二、对 STM32 学习规律的理解
- 二十三、两个实验的共同程序模型
- HC-SR04
- ST188
- 二十四、总结
一、前言
本次实验主要完成了两个传感器实验:
- HC-SR04 超声波测距传感器实验
- ST188 红外光电传感器实验
虽然两个传感器的工作方式不同,但是完成实验以后可以发现,它们在 STM32 程序设计中存在非常明显的共同规律:
传感器产生信号 → STM32 采集信号 → 程序进行处理 → LCD、串口、LED、蜂鸣器等输出结果。
HC-SR04 更偏向于“时间测量 + 物理量计算”,而 ST188 更偏向于“数字状态读取 + 条件判断”。
通过这两个实验,我对 STM32 的 GPIO、定时器、HAL 库、串口调试以及传感器程序的分层设计有了更加清晰的认识。
二、HC-SR04 超声波测距实验
2.1 HC-SR04 工作原理
HC-SR04 是一种常见的超声波测距模块。
它的基本工作流程为:
STM32 ↓ TRIG 发送触发信号 ↓ HC-SR04 发射超声波 ↓ 超声波碰到物体 ↓ 产生反射 ↓ HC-SR04 接收到回波 ↓ ECHO 输出高电平脉冲 ↓ STM32 测量高电平持续时间 ↓ 根据时间计算距离HC-SR04 并不会直接向 STM32 输出距离。
STM32 真正获得的是:
超声波从发射到反射回来所经历的时间。
因此程序首先要测出 ECHO 高电平脉冲的持续时间,然后利用声速和传播时间计算距离。
三、超声波距离公式推导
设:
- 被测物体与超声波模块之间的距离为
d - 空气中的声速为
v - 超声波往返传播时间为
t
超声波实际传播过程为:
传感器 → 被测物体 → 传感器因此超声波实际传播的总路程为:
2 d 2d2d
根据速度公式:
s = v t s=vts=vt
因此有:
2 d = v t 2d=vt2d=vt
所以:
d = v t 2 d=\frac{vt}{2}d=2vt
在室温约 20℃ 的情况下,空气中的声速大约为:
v = 343 m / s v=343m/sv=343m/s
将单位换算为厘米:
343 m / s = 34300 c m / s 343m/s=34300cm/s343m/s=34300cm/s
又因为:
1 s = 1000000 μ s 1s=1000000\mu s1s=1000000μs
所以:
v = 0.0343 c m / μ s v=0.0343cm/\mu sv=0.0343cm/μs
代入距离公式:
d = 0.0343 t 2 d=\frac{0.0343t}{2}d=20.0343t
得到:
d = 0.01715 t d=0.01715td=0.01715t
也可以写成:
d ( c m ) ≈ t ( μ s ) 58.3 d(cm)\approx \frac{t(\mu s)}{58.3}d(cm)≈58.3t(μs)
因此在程序中经常可以看到下面两种写法:
distance=time_us/58.3;或者:
distance=time_us*0.0343/2;两种方式本质上完全相同。
例如,当 ECHO 高电平持续时间为:
1166 μs则距离为:
d = 1166 58.3 d=\frac{1166}{58.3}d=58.31166
约为:
20 c m 20cm20cm
四、HC-SR04 程序实现过程
HC-SR04 测距的第一步是让 STM32 向 TRIG 引脚发送一个触发脉冲。
核心代码类似:
HAL_GPIO_WritePin(TRIG_GPIO_Port,TRIG_Pin,GPIO_PIN_RESET);delay_us(2);HAL_GPIO_WritePin(TRIG_GPIO_Port,TRIG_Pin,GPIO_PIN_SET);delay_us(10);HAL_GPIO_WritePin(TRIG_GPIO_Port,TRIG_Pin,GPIO_PIN_RESET);其中需要注意:
TRIG 高电平持续时间需要不小于 10 μsHC-SR04 收到触发信号以后开始发射超声波。
接下来程序等待 ECHO 引脚变成高电平。
while(HAL_GPIO_ReadPin(ECHO_GPIO_Port,ECHO_Pin)==GPIO_PIN_RESET){}当 ECHO 变为高电平以后开始计时。
然后继续等待 ECHO 变为低电平。
while(HAL_GPIO_ReadPin(ECHO_GPIO_Port,ECHO_Pin)==GPIO_PIN_SET){}最后获得 ECHO 高电平持续时间:
t_us=timer_us_get();再计算距离:
distance=t_us*0.0343f/2.0f;完整过程可以概括为:
产生 TRIG 触发脉冲 ↓ 等待 ECHO 上升沿 ↓ 开始计时 ↓ 等待 ECHO 下降沿 ↓ 停止计时 ↓ 得到超声波往返时间 ↓ 利用 d = vt / 2 ↓ 计算距离 ↓ LCD / 串口显示五、HC-SR04 实验中的重要认识
通过 HC-SR04 实验,我认识到一个很重要的问题:
传感器输出的数据,不一定就是最终需要的物理量。
HC-SR04 输出的是:
时间而我们真正想得到的是:
距离因此中间需要进行转换:
ECHO 脉冲 ↓ 时间 ↓ 物理公式 ↓ 单位换算 ↓ 距离其实很多传感器都是类似的。
例如温度传感器可能是:
电压 → 温度光敏传感器可能是:
ADC 数值 → 光照强弱土壤湿度传感器可能是:
ADC 数值 → 土壤湿润程度压力传感器可能是:
电压 → 压力因此学习传感器时,不能只会调用函数读取数据,还必须知道:
STM32 读取到的原始信号到底代表什么。
六、HC-SR04 实验中容易出现的问题
HC-SR04 实验中最容易出现的问题之一是计时。
程序可能认为:
1 个计数 = 1 μs但是如果 STM32 的系统时钟或者定时器分频配置错误,那么实际一个计数可能并不是 1 μs。
这时最终距离会出现明显误差。
因此遇到测距结果明显偏大或者偏小时,可以按照下面的顺序检查:
系统时钟 ↓ 定时器时钟 ↓ Prescaler 分频 ↓ 计数频率 ↓ 一个计数对应多少时间另一个需要注意的问题是电平。
HC-SR04 通常使用 5V 供电,ECHO 输出电平可能接近 5V。
而很多 STM32 GPIO 工作电压为 3.3V,因此连接时应该确认对应 GPIO 是否能够承受该电平。
必要时可以增加:
分压电路或者:
电平转换电路此外,超声波测距还会受到目标物体角度、材质、环境温度以及其他反射物体的影响。
七、ST188 红外光电传感器实验
ST188 属于反射式红外光电传感器。
其基本原理为:
红外发射管 ↓ 发射红外光 ↓ 目标物体 ↓ 反射红外光 ↓ 光电接收器 ↓ 电信号 ↓ 比较 / 整形 ↓ 数字高低电平 ↓ STM32 GPIO因此,与 HC-SR04 相比,ST188 的处理过程更加简单。
STM32 不需要测量传播时间,只需要判断传感器输出是:
0还是:
1然后根据结果进行相应处理。
本次实验工程中,ST188 实际连接在 STM32L431 的:
PD14也就是:
GPIOD GPIO_PIN_14八、ST188 GPIO 初始化
ST188 需要把对应 GPIO 配置为输入模式。
代码如下:
#defineST118_PinGPIO_PIN_14#defineST118_GPIO_PortGPIODvoidST118_Init(void){GPIO_InitTypeDef GPIO_InitStruct;__HAL_RCC_GPIOD_CLK_ENABLE();GPIO_InitStruct.Pin=ST118_Pin;GPIO_InitStruct.Mode=GPIO_MODE_INPUT;GPIO_InitStruct.Pull=GPIO_NOPULL;HAL_GPIO_Init(ST118_GPIO_Port,&GPIO_InitStruct);}通过这段代码,可以总结出 STM32 GPIO 初始化的基本规律:
开启 GPIO 时钟 ↓ 选择 GPIO 引脚 ↓ 设置 GPIO 模式 ↓ 设置上下拉 ↓ HAL_GPIO_Init()例如普通输入可以使用:
GPIO_MODE_INPUT普通推挽输出:
GPIO_MODE_OUTPUT_PP如果要使用外部中断,则可能使用:
GPIO_MODE_IT_RISING或者:
GPIO_MODE_IT_FALLING因此 STM32 的很多 GPIO 程序其实具有非常相似的结构。
九、读取 ST188 状态
ST188 状态读取函数比较简单:
uint8_tST118_Read_State(void){returnHAL_GPIO_ReadPin(ST118_GPIO_Port,ST118_Pin);}真正完成输入读取的函数就是:
HAL_GPIO_ReadPin();这个实验中比较值得学习的是:
程序没有把初始化、读取、判断、显示和蜂鸣器控制全部写在 main() 中。
而是进行了分层。
ST118_Init() ↓ 负责初始化 ST118_Read_State() ↓ 负责读取传感器状态 ST118_Handle() ↓ 负责状态判断和输出处理这样的程序结构比把所有代码都写在 main() 中更加清晰。
十、ST118_Handle() 处理函数
主程序中可以不断调用:
while(1){ST118_Handle();}具体的状态读取和处理放在:
ST118_Handle();中完成。
核心逻辑类似:
voidST118_Handle(void){uint8_tst118_state;staticuint32_ttickstart;if((HAL_GetTick()-tickstart)>=1000){tickstart=HAL_GetTick();st118_state=ST118_Read_State();if(st118_state==1){beep_open();}else{beep_close();}}}这里使用了:
HAL_GetTick();HAL_GetTick()可以获得 STM32 从系统启动到当前时刻所经过的大致毫秒数。
因此:
HAL_GetTick()-tickstart>=1000表示:
距离上一次执行已经过去约 1000 ms也就是:
1 秒十一、HAL_GetTick() 和 HAL_Delay() 的区别
以前写程序时最容易想到的是:
HAL_Delay(1000);它确实可以延时 1 秒。
但是它有一个问题:
程序在这 1 秒期间基本处于等待状态例如:
while(1){ST118_Read_State();HAL_Delay(1000);}程序执行到:
HAL_Delay(1000);以后就会等待。
而使用:
HAL_GetTick();则可以写成:
if(HAL_GetTick()-tickstart>=1000){tickstart=HAL_GetTick();ST118_Read_State();}这样程序不会长时间阻塞。
主循环还可以同时处理:
LCD 刷新 串口通信 其他传感器 按键 LED 网络通信因此这种写法属于一种比较基础的:
非阻塞定时思想。
这也是以后学习状态机、FreeRTOS 等内容的重要基础。
十二、两个实验中发现的 STM32 程序规律
完成这两个实验以后,我发现 STM32 传感器程序通常可以抽象为:
main() │ ├─ HAL_Init() │ ├─ SystemClock_Config() │ ├─ GPIO 初始化 │ ├─ UART 初始化 │ ├─ LCD 初始化 │ ├─ Sensor_Init() │ └─ while(1) │ └─ Sensor_Handle() │ ├─ 获取传感器数据 ├─ 数据处理 ├─ 状态判断 └─ 显示 / 报警 / 通信因此以后拿到一个新的传感器工程,我不会再从几百个文件开始看。
而是首先寻找三个内容:
Sensor_Init()Sensor_Read()Sensor_Handle()也就是:
初始化 ↓ 读取 ↓ 处理通过这三个函数通常就可以比较快地理解整个传感器程序。
十三、输入、处理、输出思想
两个实验虽然传感器完全不同,但是都符合:
输入 ↓ 处理 ↓ 输出HC-SR04:
ECHO 脉冲 ↓ 测量时间 ↓ d = vt / 2 ↓ 得到距离 ↓ LCD / UARTST188:
红外反射 ↓ 数字电平 ↓ GPIO 读取 ↓ if 判断 ↓ LCD / 蜂鸣器 / UART以后即使学习其他传感器,例如:
温度传感器 湿度传感器 光照传感器 土壤湿度传感器 气体传感器 RFID整体程序思路通常依然是:
初始化 ↓ 采集 ↓ 处理 ↓ 输出十四、目前常用的 STM32 HAL 函数总结
通过这两个实验,目前经常使用的 HAL 函数主要有以下几个。
1. HAL_Init()
HAL_Init();用于初始化 HAL 库以及系统相关功能。
2. HAL_GPIO_Init()
HAL_GPIO_Init(GPIOx,&GPIO_InitStruct);用于配置 GPIO。
3. HAL_GPIO_ReadPin()
HAL_GPIO_ReadPin(GPIOx,GPIO_PIN_x);用于读取 GPIO 当前高低电平。
例如 ST188:
HAL_GPIO_ReadPin(GPIOD,GPIO_PIN_14);4. HAL_GPIO_WritePin()
HAL_GPIO_WritePin(GPIOx,GPIO_PIN_x,GPIO_PIN_SET);或者:
HAL_GPIO_WritePin(GPIOx,GPIO_PIN_x,GPIO_PIN_RESET);用于控制 GPIO 输出高低电平。
HC-SR04 的 TRIG 就需要使用这个函数。
5. HAL_GetTick()
HAL_GetTick();获取系统运行时间,单位通常为毫秒。
适合实现:
周期任务 非阻塞延时 简单软件定时6. HAL_Delay()
HAL_Delay(1000);用于阻塞式延时。
例如:
HAL_Delay(1000);就是延时约 1 秒。
7. printf()
printf("state = %d\r\n",state);或者:
printf("distance = %.2f cm\r\n",distance);虽然printf()并不是 HAL 专用函数,但是在 STM32 实验中非常常用。
一般可以通过串口重定向,将输出发送到电脑串口助手。
十五、串口调试的重要性
通过实验发现,串口是 STM32 调试中非常重要的工具。
例如:
printf("state = %d\r\n",state);可以观察 ST188 当前状态。
而:
printf("distance = %.2f cm\r\n",distance);则可以观察 HC-SR04 当前距离。
假设 LCD 没有正确显示,但是串口能够正常输出:
distance = 20.03 cm那么可以判断:
传感器读取正常 距离计算正常问题更可能出现在:
LCD 显示程序这样就可以快速缩小故障范围。
因此我认为串口调试最大的价值就是:
可以看到程序内部正在发生什么。
十六、STM32 调试时总结的排查规律
刚开始做 STM32 实验时,如果传感器没有工作,很容易首先怀疑:
代码是不是写错了?但是实际上问题可能来自多个方面。
现在我更倾向于按照以下顺序排查:
电源是否正常 ↓ GND 是否共地 ↓ 传感器引脚是否接对 ↓ GPIO 端口是否正确 ↓ GPIO 模式是否正确 ↓ 传感器是否真的产生输出 ↓ STM32 是否读取到信号 ↓ 数据处理是否正确 ↓ LCD / 串口是否正确输出这种方式比直接修改代码更加有效。
十七、不要完全相信代码注释
ST188 实验中有一个很值得注意的问题。
工程中实验名称使用:
ST188但是部分函数和文件仍然采用:
ST118例如:
ST118_Init();ST118_Read_State();ST118_Handle();另外旧代码注释中可能出现其他 GPIO 描述,但是实际宏定义使用:
#defineST118_PinGPIO_PIN_14#defineST118_GPIO_PortGPIOD因此真正运行的是:
PD14这让我认识到:
注释只是辅助说明,最终应该相信真正执行的代码、宏定义、原理图以及实际测试结果。
调试 STM32 时应该重点检查:
#define 宏定义 GPIO 初始化代码 函数调用关系 实际硬件接线 实际运行现象十八、轮询、中断和定时器的理解
ST188 当前实验主要采用的是:
周期轮询也就是隔一段时间执行:
ST118_Read_State();这种方式的优点是:
程序简单 容易理解 方便调试但是如果以后需要更快响应,可以使用:
EXTI 外部中断例如当 ST188 状态发生:
0 → 1或者:
1 → 0时直接触发中断。
这样 STM32 不需要一直询问传感器状态。
HC-SR04 也可以进一步优化。
目前常见程序可能采用:
软件等待 ECHO更进一步可以使用:
定时器输入捕获分别捕获:
ECHO 上升沿和:
ECHO 下降沿通过硬件直接计算脉冲宽度。
这样可以减少 CPU 占用,并提高时间测量精度。
十九、HC-SR04 的进一步改进
HC-SR04 可以进一步增加温度补偿。
空气中的声速会受到温度影响。
近似公式为:
v = 331.3 + 0.606 T v=331.3+0.606Tv=331.3+0.606T
其中:
T = 环境温度单位为:
℃因此距离公式可以写成:
d = v t 2 d=\frac{vt}{2}d=2vt
即:
d = ( 331.3 + 0.606 T ) t 2 d=\frac{(331.3+0.606T)t}{2}d=2(331.3+0.606T)t
实际系统中可以增加:
DS18B20或者:
SHT31读取环境温度,然后动态修正声速。
除此之外还可以加入:
中值滤波 多次平均 定时器输入捕获 距离报警 非阻塞状态机 物联网数据上传二十、ST188 的进一步扩展
ST188 虽然只能输出简单的有无遮挡状态,但是通过增加程序和传感器数量,可以实现很多功能。
例如可以使用两个 ST188。
设两个传感器之间距离为:
L LL
物体通过两个传感器的时间差为:
Δ t \Delta tΔt
那么物体速度可以近似计算为:
v = L Δ t v=\frac{L}{\Delta t}v=ΔtL
因此可以进一步实现:
物体计数 车辆检测 简单测速 方向判断 闯红灯检测 流水线物料检测如果再连接 Wi-Fi 或 NB-IoT 模块,还可以实现:
ST188 ↓ STM32 ↓ 串口 ↓ Wi-Fi / NB-IoT ↓ 服务器 ↓ 网页 / 手机端这样就可以从一个简单的传感器实验扩展成一个物联网系统。
二十一、实验心得
通过这两个实验,我最大的收获并不是记住了某一个函数或者某一段代码,而是逐渐理解了 STM32 控制传感器的整体思路。
以前第一次打开 STM32 工程时,看到:
main.c gpio.c usart.c sensor.c handle.c stm32l4xx_hal_xxx.c会感觉文件很多,不知道应该先看哪个。
现在我一般会先找到:
main()看看整个系统初始化了哪些模块。
然后再寻找:
Sensor_Init()了解传感器怎样初始化。
接着寻找:
Sensor_Read()看看 STM32 怎样获得传感器数据。
最后寻找:
Sensor_Handle()了解程序怎样处理数据并控制其他设备。
这种阅读程序的方法明显比从头到尾逐行看代码效率高很多。
二十二、对 STM32 学习规律的理解
通过实验,我发现学习 STM32 不能只背 HAL 函数。
真正重要的是理解:
硬件是什么 ↓ 产生什么信号 ↓ STM32 使用哪个外设采集 ↓ 程序怎样处理 ↓ 最后怎样输出例如 HC-SR04:
超声波 ↓ ECHO 脉冲宽度 ↓ GPIO + 定时器 ↓ 距离公式 ↓ 距离ST188:
红外反射 ↓ 数字高低电平 ↓ GPIO ↓ if 判断 ↓ 有无遮挡以后学习 I2C 传感器时可能变成:
传感器 ↓ I2C ↓ 读取寄存器 ↓ 原始数据 ↓ 数据换算 ↓ 物理量SPI 传感器也是类似。
所以学习 STM32 的核心并不是:
每一种传感器都重新学习一套程序。
而应该理解:
不同传感器只是在输入方式、通信方式和数据处理方式上有所不同。
二十三、两个实验的共同程序模型
最后可以把两个实验统一抽象为:
硬件初始化 ↓ 传感器初始化 ↓ 获取原始信号 ↓ 数据处理 ↓ 状态 / 数值判断 ↓ LCD / UART / LED / 蜂鸣器其中:
HC-SR04
输入: ECHO 脉冲 处理: 测量时间 计算距离 输出: LCD / 串口ST188
输入: GPIO 高低电平 处理: 判断有无遮挡 输出: LCD / 蜂鸣器 / 串口 / LED二十四、总结
HC-SR04 和 ST188 虽然属于完全不同的两种传感器,但是通过这两个实验可以比较系统地认识 STM32 传感器开发的基本方法。
HC-SR04 让我进一步掌握了:
GPIO 输出 GPIO 输入 微秒级延时 定时测量 超声波测距 物理公式换算ST188 则让我进一步掌握了:
GPIO 输入 状态判断 HAL_GetTick() 非阻塞定时 驱动层和业务层分离 LCD / 蜂鸣器 / 串口联动通过实验我逐渐认识到:
STM32 学习最重要的不是死记代码,而是理解硬件信号与软件处理之间的关系。
以后面对一个新的传感器,可以首先思考:
它输出的是什么? ↓ STM32 应该使用什么外设读取? ↓ 读到的原始数据代表什么? ↓ 需要怎样换算或判断? ↓ 结果应该如何显示或上传?只要把这条思路理清楚,大多数传感器实验都可以按照:
初始化 → 采集 → 处理 → 输出的规律进行分析和编程。
这也是这两次 STM32 传感器实验给我最大的收获。@TOC