
基于 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-SR04ST188二十四、总结一、前言本次实验主要完成了两个传感器实验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 svtsvt因此有2 d v t 2dvt2dvt所以d v t 2 d\frac{vt}{2}d2vt在室温约 20℃ 的情况下空气中的声速大约为v 343 m / s v343m/sv343m/s将单位换算为厘米343 m / s 34300 c m / s 343m/s34300cm/s343m/s34300cm/s又因为1 s 1000000 μ s 1s1000000\mu s1s1000000μs所以v 0.0343 c m / μ s v0.0343cm/\mu sv0.0343cm/μs代入距离公式d 0.0343 t 2 d\frac{0.0343t}{2}d20.0343t得到d 0.01715 t d0.01715td0.01715t也可以写成d ( c m ) ≈ t ( μ s ) 58.3 d(cm)\approx \frac{t(\mu s)}{58.3}d(cm)≈58.3t(μs)因此在程序中经常可以看到下面两种写法distancetime_us/58.3;或者distancetime_us*0.0343/2;两种方式本质上完全相同。例如当 ECHO 高电平持续时间为1166 μs则距离为d 1166 58.3 d\frac{1166}{58.3}d58.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_ustimer_us_get();再计算距离distancet_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.PinST118_Pin;GPIO_InitStruct.ModeGPIO_MODE_INPUT;GPIO_InitStruct.PullGPIO_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){tickstartHAL_GetTick();st118_stateST118_Read_State();if(st118_state1){beep_open();}else{beep_close();}}}这里使用了HAL_GetTick();HAL_GetTick()可以获得 STM32 从系统启动到当前时刻所经过的大致毫秒数。因此HAL_GetTick()-tickstart1000表示距离上一次执行已经过去约 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()-tickstart1000){tickstartHAL_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-SR04ECHO 脉冲 ↓ 测量时间 ↓ 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 当前高低电平。例如 ST188HAL_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 v331.30.606Tv331.30.606T其中T 环境温度单位为℃因此距离公式可以写成d v t 2 d\frac{vt}{2}d2vt即d ( 331.3 0.606 T ) t 2 d\frac{(331.30.606T)t}{2}d2(331.30.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