STM32F103驱动HC-SR04超声波测距:GPIO、定时器输入捕获与中断实战
简介本资源是基于STM32F103RCT6微控制器与HC-SR04超声波传感器实现高精度实时测距的完整嵌入式项目面向嵌入式初学者、课程设计学生及智能硬件开发者解决非接触式距离检测在机器人避障、智能家居与自动化场景中的基础应用需求。压缩包共77个文件36个.h头文件定义外设与模块接口33个.c源文件含主控逻辑、HCSR04驱动、定时器捕获、串口通信及LCD/OLED显示功能辅以Keil工程配置uvprojx/uvoptx、调试配置dbgconf、启动代码startup_stm32f10x_hd.s及可执行hex文件整体体积仅327KB结构清晰、模块解耦度高便于理解底层寄存器操作与中断时序控制。已有1069人学习下载提供开箱即用的Keil工程含完整HAL或标准外设库风格代码、详细注释、多级滤波算法实现及温度补偿预留接口助读者快速掌握超声波测距原理、STM32定时器输入捕获配置及嵌入式系统软硬件协同调试方法。1. 项目概述从零到一用STM32F103RCT6驱动HCSR04超声波模块最近在整理以前做过的嵌入式小项目翻出来一个基于STM32F103RCT6和HCSR04超声波模块的测距系统。这个项目虽然看起来简单就是一个“发波-收波-计时-计算”的过程但里面涉及到的GPIO操作、定时器精准计时、中断处理以及环境因素补偿每一个环节都藏着不少细节。很多新手朋友在复现时要么测不准要么干脆没数据问题往往就出在这些细节上。今天我就把这个项目的完整实现过程、核心代码逻辑以及我踩过的坑系统地梳理一遍目标是让你看完就能自己动手搭起来并且理解每一步为什么要这么做。这个项目非常适合刚学完STM32基础想找一个综合性小项目练手的朋友。你将用到STM32的GPIO输出触发信号、输入捕获回波信号以及定时器的高精度计时功能。最终实现的效果是通过串口将测得的距离数据实时打印出来精度可以做到厘米级响应速度也很快。我会基于最常用的标准库虽然现在HAL库更流行但标准库对于理解底层时序更有帮助来讲解开发环境是Keil MDK。当然如果你用的是CubeMXHAL库核心思路是完全相通的我会在关键地方指出差异。2. 核心硬件与原理深度解析2.1 主角介绍STM32F103RCT6与HC-SR04先来看看我们用的两个核心硬件。STM32F103RCT6属于STM32F1系列的“增强型”产品72MHz主频256KB Flash48KB RAM自带3个高级定时器、4个通用定时器还有2个看门狗和丰富的通信接口。对于我们这个超声波测距项目它的性能是绰绰有余的。我选择它一是因为资源丰富二是因为资料多社区生态好出了问题容易找到解决方案。HC-SR04超声波模块可以说是电子爱好者入门测距的“老朋友”了。它的工作原理是声纳测距模块的Trig引脚收到一个至少10us的高电平脉冲后会自动发射8个40kHz的超声波脉冲然后Echo引脚会输出一个高电平其持续时间与超声波从发射到遇到障碍物返回的时间成正比。我们只需要在Echo引脚变高时启动计时变低时停止计时就能得到声波往返的时间t。那么距离S怎么算公式很简单S (t * 340) / 2。这里340是声波在空气中传播的速度单位是米/秒。t的单位是秒计算出来的S单位是米。实际操作中我们计时单位常用微秒us声速用34000厘米/秒来换算会更直观距离(厘米) (时间(微秒) * 0.034) / 2或者简化成距离(厘米) 时间(微秒) / 58.0。这个58.0是怎么来的因为声速340米/秒 34000厘米/秒 0.034厘米/微秒。距离是往返路程的一半所以系数是0.034 / 2 0.017厘米/微秒取其倒数就是1 / 0.017 ≈ 58.8通常取58或59进行近似计算。我实测下来用58.0这个系数在常温下是比较准的。注意这个58.0的系数是在标准大气压、温度20℃、干燥空气的条件下得出的。声速受温度影响很大温度每升高1℃声速大约增加0.6米/秒。如果对精度要求极高比如工业级应用必须加入温度补偿。一个简单的修正公式是V 331.4 0.6 * T其中T是摄氏温度。对于大多数室内创客项目用固定系数问题不大。2.2 系统连接与引脚规划接线非常简单但接错了肯定没反应。HC-SR04模块通常有4个引脚VCC、Trig、Echo、GND。VCC接STM32的5V或3.3V这里有个关键点HC-SR04的老版本模块工作电压是5VEcho引脚输出的高电平也是5V。而STM32F103RCT6的GPIO引脚耐受电压一般是3.3V直接接入5V信号有烧毁IO口的风险。所以如果你的模块是5V电平的Echo引脚必须串联一个1k-2k的电阻进行分压或者使用电平转换电路。较新的模块有些已经支持3.3V逻辑电平了购买时一定要看清楚规格。我的模块是5V的所以Trig我用3.3V驱动模块能识别Echo回传信号通过1k电阻接到STM32的IO口。Trig触发引脚接STM32的任何一个GPIO配置为推挽输出即可。我用的是PA8。Echo回波引脚接STM32的GPIO并且这个GPIO必须支持输入捕获功能因为我们后续要用定时器来测量高电平持续时间。我选择PA0它对应定时器2的通道1TIM2_CH1方便做输入捕获。GND共地这个绝对不能忘。我的连接方案是HC-SR04.VCC - 5V开发板的5V引脚HC-SR04.Trig - STM32.PA8 (GPIO输出)HC-SR04.Echo - STM32.PA0 (GPIO输入串联1k电阻)HC-SR04.GND - GND为什么选PA0和PA8一方面是我的开发板这两个引脚引出来了方便另一方面PA0作为定时器2通道1其输入捕获功能非常强大可以直接用硬件记录Echo上升沿和下降沿的时刻比用外部中断软件计时要精准和稳定得多。3. 软件设计与核心代码实现3.1 开发环境搭建与工程配置我使用的是Keil MDK 5芯片包选择STM32F1系列。新建工程时需要添加标准外设库StdPeriph Lib。工程结构建议清晰一点User/文件夹放主程序main.c、stm32f10x_it.c中断服务函数等。Libraries/文件夹放STM32标准外设库的CMSIS和StdPeriph_Driver。HARDWARE/文件夹放自己写的硬件驱动比如ultrasonic.c/h。在ultrasonic.h里我们定义引脚和定时器#ifndef __ULTRASONIC_H #define __ULTRASONIC_H #include stm32f10x.h // 引脚定义 #define TRIG_GPIO_PORT GPIOA #define TRIG_GPIO_PIN GPIO_Pin_8 #define TRIG_GPIO_CLK RCC_APB2Periph_GPIOA #define ECHO_GPIO_PORT GPIOA #define ECHO_GPIO_PIN GPIO_Pin_0 #define ECHO_GPIO_CLK RCC_APB2Periph_GPIOA // 定时器定义用于输入捕获 #define ECHO_TIMx TIM2 #define ECHO_TIM_APBx_CLK RCC_APB1Periph_TIM2 #define ECHO_TIM_IRQn TIM2_IRQn #define ECHO_TIM_IRQHandler TIM2_IRQHandler #define ECHO_TIM_CHANNEL TIM_Channel_1 // 声速换算系数 (厘米/微秒) 的倒数简化计算 #define ULTRASONIC_SPEED_COEF 58.0f // 单位: us/cm // 最大测量距离和超时时间 (对应约4米) #define MEASURE_TIMEOUT (400 * 2 * ULTRASONIC_SPEED_COEF) // 单位: us void Ultrasonic_Init(void); float Ultrasonic_MeasureDistance(void); #endif这里我定义了一个超时时间MEASURE_TIMEOUT。为什么需要这个因为如果超声波前方没有障碍物Echo引脚可能一直不会变低程序就会死等。设置一个超时比如对应4米距离的时间超过这个时间就认为测量失败返回一个错误值比如0避免程序卡死。3.2 驱动初始化GPIO与定时器初始化的核心有两部分一是Trig引脚的输出初始化二是Echo引脚对应的定时器输入捕获初始化。我们看Ultrasonic_Init函数void Ultrasonic_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; NVIC_InitTypeDef NVIC_InitStructure; // 1. 初始化Trig引脚为推挽输出 RCC_APB2PeriphClockCmd(TRIG_GPIO_CLK, ENABLE); GPIO_InitStructure.GPIO_Pin TRIG_GPIO_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; // 推挽输出 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(TRIG_GPIO_PORT, GPIO_InitStructure); GPIO_ResetBits(TRIG_GPIO_PORT, TRIG_GPIO_PIN); // 初始化为低电平 // 2. 初始化Echo引脚为浮空输入因为外部有上拉或下拉 RCC_APB2PeriphClockCmd(ECHO_GPIO_CLK, ENABLE); GPIO_InitStructure.GPIO_Pin ECHO_GPIO_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; // 浮空输入 GPIO_Init(ECHO_GPIO_PORT, GPIO_InitStructure); // 3. 初始化定时器2用于输入捕获 RCC_APB1PeriphClockCmd(ECHO_TIM_APBx_CLK, ENABLE); // 时基单元配置1MHz计数频率即1个计数1us TIM_TimeBaseStructure.TIM_Period 0xFFFF; // 自动重装载值16位最大值 TIM_TimeBaseStructure.TIM_Prescaler 72 - 1; // 72MHz / 72 1MHz TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(ECHO_TIMx, TIM_TimeBaseStructure); // 输入捕获配置通道1上升沿捕获 TIM_ICInitStructure.TIM_Channel ECHO_TIM_CHANNEL; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; // 先捕获上升沿 TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI; // 直接映射到TI1 TIM_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1; // 不分频每个边沿都捕获 TIM_ICInitStructure.TIM_ICFilter 0x0; // 不滤波 TIM_ICInitStructure.TIM_ICMode TIM_ICMode_Capture; TIM_ICInit(ECHO_TIMx, TIM_ICInitStructure); // 使能捕获/比较1中断和更新中断 TIM_ITConfig(ECHO_TIMx, TIM_IT_CC1 | TIM_IT_Update, ENABLE); // 配置NVIC NVIC_InitStructure.NVIC_IRQChannel ECHO_TIM_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); // 使能定时器 TIM_Cmd(ECHO_TIMx, ENABLE); }关键点解析定时器分频STM32F103的APB1定时器时钟最高是72MHz。我们设置预分频器为71即72-1这样定时器的计数频率就是72MHz / (711) 1MHz。计数器每计一个数时间过去1微秒us这非常方便我们直接读取时间值。输入捕获极性初始设置为上升沿捕获对应Echo引脚从低变高的瞬间也就是超声波发射的时刻。滤波器这里设置为0即不滤波。如果你的环境干扰大Echo信号有毛刺可以适当增加滤波值比如0x8但要注意滤波会引入微小延迟。中断我们使能了捕获/比较1中断CC1IE和更新中断UIE。CC1IE用于处理边沿捕获事件UIE用于处理计数器溢出超过65535us即65.535ms。3.3 测距流程与状态机实现测距不是一个简单的顺序函数而是一个由“触发-等待回波-计算”构成的异步过程。我使用一个简单的状态机在中断和主循环中配合完成。首先定义几个全局变量static volatile uint32_t s_rise_time 0; // 上升沿捕获时间 static volatile uint32_t s_fall_time 0; // 下降沿捕获时间 static volatile uint8_t s_measure_status 0; // 测量状态: 0-空闲, 1-等待上升沿, 2-等待下降沿 static volatile uint8_t s_measure_done 0; // 测量完成标志 static volatile uint32_t s_timeout_counter 0; // 超时计数器然后在Ultrasonic_MeasureDistance函数中启动一次测量float Ultrasonic_MeasureDistance(void) { float distance_cm 0.0f; // 1. 发送触发脉冲 GPIO_SetBits(TRIG_GPIO_PORT, TRIG_GPIO_PIN); Delay_us(12); // 保持高电平至少10us这里给12us留有余量 GPIO_ResetBits(TRIG_GPIO_PORT, TRIG_GPIO_PIN); // 2. 重置状态启动测量 s_measure_status 1; // 进入“等待上升沿”状态 s_measure_done 0; s_timeout_counter 0; // 3. 切换输入捕获为上升沿触发确保初始状态 TIM_ICInitTypeDef TIM_ICInitStructure; TIM_ICInitStructure.TIM_Channel ECHO_TIM_CHANNEL; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter 0x0; TIM_ICInitStructure.TIM_ICMode TIM_ICMode_Capture; TIM_ICInit(ECHO_TIMx, TIM_ICInitStructure); // 4. 等待测量完成或超时 while (!s_measure_done) { // 这里可以加入一个超时判断防止死循环 // 简单延时并计数如果超过预期时间如100ms则退出 Delay_ms(1); if (s_timeout_counter 100) { // 约100ms超时 s_measure_status 0; // 重置状态 return 0.0f; // 返回0表示测量失败 } } // 5. 计算距离 if (s_fall_time s_rise_time) { uint32_t time_diff s_fall_time - s_rise_time; // 单位: us // 简单的超时判断时间过长认为无效 if (time_diff MEASURE_TIMEOUT) { distance_cm (float)time_diff / ULTRASONIC_SPEED_COEF; } } // 6. 重置标志准备下一次测量 s_measure_done 0; s_measure_status 0; return distance_cm; }注意这里的Delay_us和Delay_ms需要你自己实现或者使用系统滴答定时器SysTick。注意Delay_us在72MHz下用循环实现时需要仔细校准。一个简单的实现方法是使用SysTick定时器或一个基本定时器如TIM6来产生精确的微秒级延时。3.4 中断服务函数精准捕获时间戳整个系统的精度核心就在定时器中断服务函数里。它负责在Echo信号的边沿到来时记录下定时器计数器的值。void ECHO_TIM_IRQHandler(void) { if (TIM_GetITStatus(ECHO_TIMx, TIM_IT_Update) ! RESET) { // 定时器溢出中断计数器超过65535 // 如果我们在等待下降沿时发生了溢出需要处理时间累加 if (s_measure_status 2) { // 状态2是已捕获上升沿在等待下降沿 s_timeout_counter 65536; // 溢出一次增加65536us } TIM_ClearITPendingBit(ECHO_TIMx, TIM_IT_Update); } if (TIM_GetITStatus(ECHO_TIMx, TIM_IT_CC1) ! RESET) { // 捕获/比较1中断即Echo边沿触发 if (s_measure_status 1) { // 状态1等待上升沿即Echo变高超声波发射 s_rise_time TIM_GetCapture1(ECHO_TIMx) (s_timeout_counter * 65536); s_measure_status 2; // 切换到等待下降沿状态 // 改为下降沿捕获 TIM_OC1PolarityConfig(ECHO_TIMx, TIM_ICPolarity_Falling); } else if (s_measure_status 2) { // 状态2等待下降沿即Echo变低超声波返回 s_fall_time TIM_GetCapture1(ECHO_TIMx) (s_timeout_counter * 65536); s_measure_done 1; // 设置测量完成标志 s_measure_status 0; // 回到空闲状态 // 改回上升沿捕获为下一次测量做准备 TIM_OC1PolarityConfig(ECHO_TIMx, TIM_ICPolarity_Rising); } TIM_ClearITPendingBit(ECHO_TIMx, TIM_IT_CC1); } }这里是整个项目的精髓有几个极易出错的点计数器溢出处理定时器是16位的最大值65535。如果测量距离超过约3.7米对应约22000us * 2等一下这里我算错了。应该是65535us / 58 us/cm ≈ 1130cm 11.3米的往返时间不对58 us/cm是单程时间我们测量的是往返时间t距离S t / 58。如果t 65535us则S 65535/58 ≈ 1130cm 11.3米。但这是单次计数不溢出的最大距离。实际上我们通过s_timeout_counter记录溢出次数可以测量更远的距离。s_timeout_counter * 65536就是溢出部分的总时间。时间戳计算最终的时间戳 当前捕获值 溢出次数 * 65536。这样才能在计数器溢出后得到正确的时间。捕获极性切换在捕获到上升沿后要立即将极性改为下降沿这样才能在Echo变低时再次触发中断。测量完成后再改回上升沿为下一次测量做准备。中断标志清除必须在中断服务函数结束前清除相应的中断标志位否则会连续进入中断导致系统崩溃。4. 主程序逻辑与数据输出驱动写好了主程序就非常简单了。主要就是初始化系统然后在一个循环里不断测量并输出结果。#include stm32f10x.h #include ultrasonic.h #include stdio.h // 用于printf #include usart.h // 串口初始化需要你自己实现 // 简单重定向printf到串口1可选 int fputc(int ch, FILE *f) { USART_SendData(USART1, (uint8_t)ch); while (USART_GetFlagStatus(USART1, USART_FLAG_TC) RESET); return ch; } int main(void) { // 系统时钟初始化默认72MHz SystemInit(); // 初始化串口1用于打印数据波特率115200 USART1_Init(115200); // 初始化超声波模块 Ultrasonic_Init(); printf(STM32F103 HC-SR04 Ultrasonic Ranging System Ready.\r\n); while (1) { float dist Ultrasonic_MeasureDistance(); if (dist 0.1f dist 400.0f) { // 过滤无效值0-400cm printf(Distance: %.2f cm\r\n, dist); } else { printf(Measurement Error or Out of Range.\r\n); } // 每次测量间隔至少60ms给传感器留出“安静”时间防止上次回波干扰 Delay_ms(100); // 这里延时100ms让数据输出不要太快 } }主循环里的几个技巧数据过滤超声波模块在近距离2cm和远距离400cm时测量结果可能不稳定或无效。我加了一个简单的范围判断只输出0.1cm到400cm之间的数据。你也可以根据实际应用调整。测量间隔HC-SR04模块两次测量之间需要一定的间隔数据手册建议至少60ms否则前一次测量的回波可能会干扰下一次。我这里延时了100ms既保证了模块稳定工作也让串口输出不至于刷屏太快。错误处理当测量超时或计算出的距离异常时打印错误信息便于调试。5. 系统调试与精度优化实战代码写完了烧录进去可能一次成功也可能遇到各种问题。下面是我在调试过程中总结的几个常见问题和优化方法。5.1 常见问题排查清单现象可能原因排查步骤与解决方案完全没数据串口无输出1. 电源或GND未接好。2. Trig/Echo线接反或接触不良。3. 串口配置错误波特率、引脚。4. 程序未运行Boot引脚配置。1. 用万用表检查VCC和GND电压。2. 交换Trig和Echo线试试或检查杜邦线。3. 用PC串口助手查看是否有任何数据确认波特率。4. 检查BOOT0/BOOT1引脚是否为正常启动模式通常都接地。串口有输出但一直是“Measurement Error”1. Echo信号未返回模块损坏或前方无障碍物。2. Echo引脚电平不兼容5V模块直接接3.3V IO。3. 定时器输入捕获配置错误通道、极性。4. 中断未正确进入。1. 用手放在模块前方近距离遮挡看是否有数据。2.重点检查用示波器或逻辑分析仪看Echo引脚波形。如果没有示波器可以尝试在Echo和STM32 IO之间串联一个1k-2.2k电阻到地做一个简易分压。3. 检查TIM_ICInit函数参数特别是通道和极性设置。4. 在中断服务函数里设置一个断点或翻转一个LED看是否能进入。测量数据跳动很大不稳定1. 电源噪声。2. 环境声波干扰其他超声波源、噪音。3. 测量物体表面不平或吸声。4. 软件滤波不足。1. 给模块的VCC和GND之间并联一个10uF电解电容和一个0.1uF瓷片电容紧贴模块引脚。2. 避开其他超声波设备如另一个HC-SR04。尝试在安静的室内测试。3. 对准平整、坚硬的物体如墙壁测试。4. 在软件中做滑动平均滤波连续采样N次如5次去掉最大最小值求平均。测量值整体偏大或偏小1. 声速系数不准确受温度影响。2. 定时器时钟频率计算错误。3. 触发脉冲宽度不足。1. 用尺子实际测量一个固定距离如50.0cm根据测得的时间反算实际系数替换ULTRASONIC_SPEED_COEF。2. 确认系统时钟和定时器分频设置。用示波器测量一个GPIO翻转的时间来验证1us延时是否准确。3. 确保Trig高电平脉冲在10us-20us之间可以用Delay_us(12)。近距离5cm测不准或报错1. HC-SR04本身有盲区约2cm。2. 回波信号与触发信号太近可能被模块内部电路抑制。1. 这是模块硬件限制避免测量小于2-3cm的距离。2. 在软件中如果计算出的时间差极小如对应距离2cm直接作为无效数据处理。5.2 软件滤波算法提升稳定性原始数据难免有毛刺加入一个简单的滑动平均滤波效果会好很多。我们在驱动层之上做一个应用层的滤波处理。#define FILTER_SIZE 5 typedef struct { float buffer[FILTER_SIZE]; uint8_t index; float sum; } UltrasonicFilter_t; static UltrasonicFilter_t s_filter; void Filter_Init(UltrasonicFilter_t *filter) { for (int i 0; i FILTER_SIZE; i) { filter-buffer[i] 0.0f; } filter-index 0; filter-sum 0.0f; } float Filter_Update(UltrasonicFilter_t *filter, float new_value) { // 减去最早的值加上最新的值 filter-sum - filter-buffer[filter-index]; filter-sum new_value; filter-buffer[filter-index] new_value; filter-index; if (filter-index FILTER_SIZE) { filter-index 0; } // 返回平均值 return filter-sum / FILTER_SIZE; }在主循环中这样使用// 初始化滤波器 UltrasonicFilter_t dist_filter; Filter_Init(dist_filter); while (1) { float raw_dist Ultrasonic_MeasureDistance(); if (raw_dist 2.0f raw_dist 400.0f) { // 初步过滤 float filtered_dist Filter_Update(dist_filter, raw_dist); printf(Raw: %.2f cm, Filtered: %.2f cm\r\n, raw_dist, filtered_dist); } Delay_ms(100); }5.3 进阶优化温度补偿与自动校准如果项目用于室外或温差大的环境温度补偿就很有必要了。可以增加一个DS18B20之类的温度传感器实时获取环境温度动态计算声速。float Get_SpeedOfSound_Correction(float temperature_c) { // 声速 V 331.4 0.6 * T (m/s) return 331.4f 0.6f * temperature_c; } float Calculate_Distance_With_Temp(uint32_t time_us, float temperature_c) { float speed_ms Get_SpeedOfSound_Correction(temperature_c); // m/s float speed_cm_per_us speed_ms * 100.0f / 1e6f; // 转换为 cm/us float distance_cm (speed_cm_per_us * time_us) / 2.0f; return distance_cm; }在Ultrasonic_MeasureDistance函数中获取温度值temp然后用Calculate_Distance_With_Temp(time_diff, temp)代替原来的简单除法。6. 项目扩展与进阶思路这个基础项目做好后你可以在此基础上玩出很多花样多路超声波如果需要同时测量多个方向的距离比如机器人避障可以复用同一个定时器的不同通道如TIM2的CH1, CH2, CH3, CH4来捕获多个Echo信号或者使用多个定时器。注意Trig触发要分时进行避免相互干扰。与显示设备结合将距离数据实时显示在OLED屏幕上SSD1306或者用LCD屏显示一个简单的雷达扫描图。报警功能设置一个距离阈值比如小于20cm当物体靠近时让STM32控制一个蜂鸣器响或者LED亮做一个简易的倒车雷达。数据上传通过串口将数据发送到上位机PC进行图形化显示或者通过ESP8266/ESP32模块上传到物联网平台实现远程监控。改用HAL库如果你习惯用CubeMX和HAL库思路完全一样。用CubeMX配置好定时器的输入捕获通道生成代码后重点实现HAL_TIM_IC_CaptureCallback这个回调函数在里面处理上升沿和下降沿的逻辑。最后关于开发环境Keil MDK是经典选择但也可以尝试VSCode PlatformIO或者STM32CubeIDE后者是ST官方推出的免费IDE基于Eclipse集成CubeMX用HAL库开发非常方便。选择哪个取决于你的习惯和项目需求。对于学习底层寄存器操作和理解原理标准库和Keil的组合非常直接对于快速原型开发CubeMXHAL库效率更高。这个项目的所有代码和工程文件我都整理好了。如果你在复现过程中遇到任何问题或者有更好的实现思路欢迎一起交流。嵌入式开发就是这样从一个个小模块入手把每个细节搞明白积累起来就能做出更复杂的系统了。本文还有配套的精品资源点击获取