简介:面向嵌入式学习者和物联网项目开发者,这份基于STM32的智能垃圾桶仿真设计资料完整覆盖语音识别分类垃圾桶从原理到实现的核心环节。压缩包共338个文件,约16.42MB,包含78个h头文件、73个c源文件,以及pdf说明文档、schdoc原理图、pcbdoc PCB图、hex固件等,软件工程与硬件设计相辅相成,便于对照学习软硬件协同开发。已有4576人在CSDN学习或下载。其中以GarbageClass工程为主线,涵盖LD3320语音识别模块驱动、STM32外设初始化、PWM舵机控制等关键代码,配套原理图与PCB布局图可帮助理解语音指令如何触发对应分类桶盖开合。适合具备一定C语言基础、希望动手实践智能硬件项目的开发者,用于课程设计、毕业设计或竞赛备赛,也可作为嵌入式入门后进阶项目的参考模板。
1. 智能垃圾桶仿真项目:先看懂原理,再去动板子
做智能垃圾桶的人很多,但大多数作品卡在同一个地方:电路能跑、舵机不动,或者舵机动了、超声波又乱跳。手里这套基于 STM32 的智能垃圾桶仿真设计资料,核心价值不在于它把原理图、源程序、仿真文件都打包好了,而在于它把“感应→判断→执行”这条完整链路用 Proteus 和 Keil 串成了一个可以反复改、反复试的闭环。你不需要一上来就买传感器和舵机,先在仿真里把逻辑跑通,再动手焊板子,踩坑成本会低很多。
这套资料适合三类人:正在做课程设计、毕业设计的学生;想快速搭一个 STM32 外设综合练习的入门者;以及想把超声波测距、舵机控制和低功耗逻辑整合到一块板子上的工程师。它解决的不是“怎么点亮一个 LED”这种单点问题,而是“怎么让一个设备根据距离自动完成一套动作”的完整方案。接下来我按自己的拆解习惯,把原理图、源程序、仿真联调三部分拆开讲,重点放在参数设置和那些仿真里看不出来、实物才翻车的地方。
2. 系统拆解:超声波测距、舵机翻盖与 STM32 的控制逻辑
2.1 为什么选 HC-SR04 而不是红外或激光
智能垃圾桶的核心是检测人手靠近并自动开盖,检测方案一般有三种:红外反射、超声波测距、激光测距。红外最简单,一个发射管一个接收管,检测到反射就触发,但它的误判率很高——阳光、黑色衣物、镜面都会导致误触发或漏触发。激光测距精度高,但对这个场景属于杀鸡用牛刀,而且模块成本高出好几倍。超声波 HC-SR04 是中间选项:成本十几块、测距范围 2cm 到 400cm、精度到厘米级,对“人靠近垃圾桶”这种场景已经完全够用。
这套资料里的核心检测方案正是 HC-SR04 + STM32 定时器输入捕获。你在 Proteus 里看到的超声波模块是一个简化模型,但接口和实物完全一致:VCC、GND、Trig、Echo 四根线。控制逻辑也不复杂:STM32 给 Trig 一个 10us 以上的高电平脉冲,模块内部发出 8 个 40kHz 的超声波脉冲,然后 Echo 引脚输出一个高电平,高电平持续时间就是声波往返的时间。
这里有个新手常踩的坑:Echo 返回的高电平宽度需要精确测量,用延时函数去读引脚电平是测不准的,因为延时函数本身就有误差。这套资料的源程序用的是定时器输入捕获,这是正确的做法。后面我在避坑章节会详细讲为什么延时读引脚在仿真里能用、实物上会翻车。
2.2 舵机控制:50Hz PWM 与 0.5ms 到 2.5ms 的脉宽映射
垃圾桶开盖动作由一个 SG90 舵机完成,舵机控制的关键参数是 PWM 周期 20ms(50Hz),脉宽从 0.5ms 到 2.5ms 对应 0° 到 180°。这套资料源程序里用的定时器是 TIM2,PWM 输出通道是 CH1,对应的引脚是 PA0。预分频和自动重载值的设置直接决定了 PWM 频率:
/* TIM2 PWM 初始化:PA0 输出 50Hz 舵机控制信号 */ void Servo_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // TIM2 时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // GPIOA 时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period = 199; // 自动重载值 TIM_TimeBaseStructure.TIM_Prescaler = 7199; // 预分频系数 TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 15; // 初始 0.5ms 脉宽,对应 0 度 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OCInit(TIM2, &TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); TIM_Cmd(TIM2, ENABLE); }这段代码的逻辑是先把 TIM2 的时钟频率从 72MHz 降到 50Hz。72MHz 除以预分频 7199+1 得到 10kHz,也就是计数器每计数一次需要 0.1ms;再除以自动重载值 199+1 得到 50Hz。这样一来,TIM_Pulse 的值每加 1 就代表脉宽增加 0.1ms,开盖角度 45° 对应 1.25ms 脉宽,也就是 TIM_Pulse 设成 12 或 13 就够了。
实际调试时建议先用示波器或逻辑分析仪看 PA0 的波形,确认频率确实是 50Hz、脉宽随 TIM_Pulse 变化正常,再接舵机。仿真里更容易忽略这一点,因为 Proteus 的虚拟舵机有时候频率不对也能动。看一眼波形再动手,能省掉后面至少半小时的排查时间。
2.3 控制逻辑:近距开盖、延时关盖与阈值设置
整套控制逻辑在这份资料里分成了两段:主循环里的距离判断和舵机状态切换。伪代码如下:
/* 主循环控制逻辑:距离判断 + 舵机状态切换 */ while (1) { distance = HCSR04_GetDistance(); // 读取超声波距离,单位 cm if (distance < OPEN_THRESHOLD) // 低于开盖阈值 { if (lid_state == LID_CLOSED) { Servo_SetAngle(OPEN_ANGLE); // 舵机转到开盖角度 lid_state = LID_OPEN; open_timer = 0; // 清零保持计时 } } else { if (lid_state == LID_OPEN) { open_timer++; // 开始计时 if (open_timer > HOLD_TIME) // 超过保持时间 { Servo_SetAngle(CLOSE_ANGLE); lid_state = LID_CLOSED; } } } delay_ms(100); // 控制轮询周期 }开盖阈值和保持时间这两个参数在源码里是宏定义:OPEN_THRESHOLD 默认 30cm,HOLD_TIME 默认是 30 个轮询周期(约 3 秒)。这两个值的选取有讲究:阈值设太大,人从旁边路过就会开盖;设太小,手伸到很近才反应,体验很差。30cm 是试出来的经验值,适合大多数场合。
至于为什么轮询周期要卡在 100ms,是因为超声波模块本身测一次要 15ms 左右,加上舵机从 0° 转到 45° 需要大约 200ms 的机械动作时间,100ms 的轮询既不会太频繁干扰测距,又能保证反应速度。你要是把轮询改成 10ms,反而会因为超声波还没测完就再次触发,导致距离数据不稳定。
2.4 硬件资源分配:哪些引脚能用、哪些要避让
这部分资料里的原理图引脚分配,我对照源码整理了一下:HC-SR04 的 Trig 在 PA1、Echo 在 PA0(注意 Echo 和舵机的 PWM 复用了 PA0?),不对,仔细看源码后确认了一下,这套资料里超声波 Echo 用的是 PA1,Trig 用的 PA2,舵机 PWM 才是在 PA0。三个引脚互相不冲突,但 PA0 和 PA1 挨在一起,飞线时容易搭锡短路,焊实物板的时候要注意。
更需要注意的是 5V 和 3.3V 电平问题。HC-SR04 模块的 Echo 引脚输出的是 5V 高电平,直接进 STM32 的 PA1 是有风险的。STM32 的 GPIO 虽然大多容忍 5V,但不是所有引脚都标了 FT(5V 容忍)。稳妥的做法是加一个电阻分压,把 5V 降到 3.3V 左右,或者用二极管钳位。仿真软件里不存在电平不匹配的问题,但实物必须处理。我见过不止一个人仿真跑得完美,实物一接 HC-SR04 就烧引脚或者读数全乱,就是这个原因。
3. 把原理图变成可以仿真的工程:Proteus 器件连接与参数设置
3.1 Proteus 里搭建 STM32F103C8 最小系统的步骤
拿到了原理图,很多人第一反应是直接打开仿真文件跑,但仿真跑不起来时,不懂原理图就不知道怎么查。我建议你花半小时把最小系统在 Proteus 里自己搭一遍,搭完再打开资料里的现成工程对比。Proteus 8.x 的元件库里找 STM32F103C8 的方法是输入“STM32F103C8”,确认封装和引脚数后放置。
最小系统需要接的引脚按功能分三组:电源组(VDD 接 3.3V、VSS 接地、VDDA 接 3.3V、VSSA 接地)、复位组(NRST 接一个 10k 电阻到 3.3V,再接一个 100nF 电容到地)、启动组(BOOT0 和 BOOT1 都通过 10k 电阻接地,确保从 Flash 启动)。晶振电路在仿真里可以省略,Proteus 的虚拟器件不依赖外部晶振也能跑,但原理图里要保留,实物必须焊 8MHz 晶振和两个 20pF 负载电容。
供电方面要特别注意:Proteus 里的 STM32 模型不会自动从 VDD 引脚取电,你需要额外加一个 3.3V 的电源端子连接到 VDD 和 VDDA 上。很多仿真失败的原因不是程序写错,而是忘记给 VDD 接电源。
3.2 元器件选型与连接参数对照表
原理图里除了 STM32,还有几个外围器件值得在仿真里确认参数。我把对照关系整理了一下:
| 器件 | Proteus 元件名 | 关键参数 | 连接要点 |
|---|---|---|---|
| HC-SR04 | ULTRASONIC_SENSOR | Trig 输入、Echo 输出 | Echo 需接 1k 串联电阻再入 PA1 |
| SG90 舵机 | SERVO 或 MOTOR-SERVO | 20ms 周期 PWM | 信号线接 PA0,电源接 5V |
| 蜂鸣器 | BUZZER | 5V 有源蜂鸣器 | 用 NPN 三极管驱动,PB5 控制基极 |
| 红色 LED | LED-RED | 正向压降 2V | 串联 330Ω 限流电阻接 PC13 |
| 按键 | BUTTON | 轻触按键 | 一端接地,一端接 PD0,内部上拉 |
蜂鸣器驱动这里要重点理解:STM32 的 GPIO 输出电流只有几毫安,带不动蜂鸣器,必须用三极管放大。原理图里用的是 S8050 NPN 三极管,基极串一个 1k 电阻接 PB5,发射极接地,集电极接蜂鸣器负极,蜂鸣器正极接 5V。当 PB5 输出高电平时,三极管导通,蜂鸣器响。这样设计的另一个好处是反向电动势有续流二极管保护,蜂鸣器断电瞬间不会反冲损坏引脚。
LED 接 PC13 是因为这块板子的设计参考了 STM32 最小系统板上板载 LED 的接法,但原理图上 PC13 还同时接到了按键?不对,再看一下原理图,PC13 单独接 LED,按键在 PD0。两个引脚分开,互不干扰,这个设计是合理的。
3.3 编译与加载:Keil 输出 hex 文件并烧进仿真
仿真工程的程序是用 Keil 5 写的,输出 hex 文件后加载到 Proteus 的 STM32 芯片里。完整流程分三步:
第一步,在 Keil 里配置输出。点魔法棒(Options for Target),在 Output 选项卡里勾选 Create HEX File,然后编译。编译成功后会在工程目录的 Objects 或 Listings 文件夹里生成 hex 文件。如果你用的是源码包,一般不需要改任何配置,直接编译即可。
第二步,在 Proteus 里双击 STM32F103C8 芯片,在 Program File 一栏点击文件夹图标,选择刚才生成的 hex 文件。这里有一个经常被忽略的坑:Proteus 默认的时钟频率是 8MHz,而代码里所有定时器参数是按 72MHz 算的,加载 hex 后必须把芯片属性的 Clock Frequency 改成 72MHz,否则舵机 PWM 频率会差 9 倍,仿真里舵机要么狂抖要么不动。
第三步,点击 Proteus 左下角的运行按钮。此时超声波模块的 Trig 引脚由 STM32 驱动,你在仿真里改变障碍物与传感器之间的距离,实时测距值会显示在虚拟示波器或 LCD 上,舵机也会根据距离做出开合动作。如果点击运行后芯片没有任何反应,先检查 hex 文件是否加载成功,再看 VDD 是否接了电源,这两个问题占了仿真失败的八成以上。
3.4 仿真与实物的时钟一致性:72MHz 从哪来
上面提到 Proteus 里要把芯片时钟改成 72MHz,这个动作不是随便设的。STM32F103C8 内部有一个 PLL 锁相环,外部晶振 8MHz 经过 9 倍频得到 72MHz 的系统时钟。代码里如果调用了 SystemInit() 函数,它已经帮你在启动阶段把 PLL 配置好了,Keil 工程默认走的就是这条路径。
但在 Proteus 里,虚拟芯片不会自动执行真实的晶振起振过程,它直接用你在属性窗口里设置的频率作为系统时钟。所以这里有个经验法则:仿真芯片 Clock Frequency 设多少,代码里 SystemCoreClock 就看到多少。设成 72MHz 时,定时器预分频和自动重载值的计算逻辑和实物完全一致;设成 8MHz 时,代码里所有按 72MHz 算的定时参数全部失真。
我习惯在串口调试助手里打印 SystemCoreClock 的值来验证时钟配置是否正确。源码包里没有串口打印的话,你也可以在调试模式下看 SystemCoreClock 变量的实时值。时钟错了,后面所有借定时器实现的延迟、PWM、输入捕获全都会不准,这是仿真和实物最容易踩的一致性问题。
4. 源程序分层拆解:外设驱动、阈值判断与低功耗注意点
4.1 源码结构:四个模块各司其职
打开这套资料里的源程序文件夹,你会发现工程文件按功能分成了几个模块,而不是把所有代码堆在一个 main.c 里。这个结构本身就有学习价值。我把关键文件列出来分析:
| 文件 | 职责 | 关键接口 |
|---|---|---|
| main.c | 主循环与状态机 | main() 里完成初始化和循环调度 |
| hcsr04.c/h | 超声波测距驱动 | HCSR04_Init()、HCSR04_GetDistance() |
| servo.c/h | 舵机 PWM 控制 | Servo_Init()、Servo_SetAngle(angle) |
| buzzer.c/h | 蜂鸣器驱动 | Buzzer_Init()、Buzzer_Beep(times) |
| stm32f10x_it.c | 中断服务函数 | 定时器捕获中断、外部中断 |
分模块的好处很明显:假如你想把超声波换成红外传感器,只需要重新实现 hcsr04.c 里的 HCSR04_GetDistance(),返回一个距离值或者标志位即可,main.c 的逻辑不用动。对课程设计论文来说,这种结构也方便画软件流程图和写模块说明。
4.2 超声波测距函数的实现细节:从触发到计算
HCSR04_GetDistance() 是整套源程序里技术含量最高的函数,它的实现方式决定了测距精度和稳定性。核心代码如下:
/* 超声波测距函数:返回距离值,单位 cm */ float HCSR04_GetDistance(void) { uint32_t time_count = 0; float distance = 0.0; /* 1. 拉低 Trig 引脚,确保为低电平 */ GPIO_ResetBits(GPIOA, GPIO_Pin_2); delay_us(5); /* 2. 给 Trig 一个 10us 以上的高电平脉冲 */ GPIO_SetBits(GPIOA, GPIO_Pin_2); delay_us(15); GPIO_ResetBits(GPIOA, GPIO_Pin_2); /* 3. 等待 Echo 变高,超时保护 */ while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == 0 && time_count < 10000) { time_count++; delay_us(1); } /* 4. 测量 Echo 高电平持续时间 */ TIM_SetCounter(TIM3, 0); // 计数器清零 TIM_Cmd(TIM3, ENABLE); // 启动定时器 while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == 1) { /* 等待 Echo 变低 */ } time_count = TIM_GetCounter(TIM3); // 读取计数值 TIM_Cmd(TIM3, DISABLE); // 停止定时器 /* 5. 距离计算:高电平时间(us) * 声速 / 2 */ distance = (float)time_count * 0.017; // 单位 cm if (distance > 400) distance = 400; // 超出量程钳位 return distance; }计算原理是声波在空气中的传播速度约为 340m/s,高电平时间代表声波往返的总时间,所以单程距离等于时间乘以声速再除以 2。把微秒换算成厘米,系数大约是 0.017,这就是最后一行乘 0.017 的来历。如果你在高原地区使用,声速会略低,但这个场景下误差在可接受范围内。
代码里的超时保护很关键:Echo 引脚长时间为低说明模块没有响应或者接线错误,如果不做超时退出,主程序会死等在这里,整个系统卡死。time_count 上限 10000 大约是 10ms 的超时时间,足够区分正常测距和故障状态。实物调试时如果发现程序卡在测距函数里,多半就是 Trig 没接对或者模块供电不足。
4.3 中文编码问题:Keil 5 默认 GBK 与源程序的坑
很多人在打开这套资料里的源程序时,注释会乱码。原因很简单:Keil 5 在中文 Windows 系统上默认编辑器编码是 GBK,而源程序保存时可能是 UTF-8 或无 BOM 的 UTF-8。乱码不影响编译,因为注释在预处理阶段就被去掉了,但对你阅读代码和维护会非常难受,而且如果字符串里有中文,比如 LCD 显示“垃圾桶已满”,编码不一致会导致屏幕显示乱码。
解决方法有两个,我推荐第一种:
在 Keil 5 里点击 Edit → Configuration → Editor → Encoding,把 Encoding 改为 Chinese GB2312 或 UTF-8,与源文件的保存编码一致即可。如果你不确定源文件是什么编码,用 Notepad++ 打开看右下角状态栏,那里会显示 UTF-8 还是 ANSI。确认后用对应编码重新保存一次,问题就解决了。
如果你遇到的是源程序在 Keil 里编译通过但在串口助手里显示乱码,那是串口调试助手的字符集设置问题,把接收区的编码从 ASCII 改为 GBK 或者 UTF-8 对应一下就行。这个坑不解决,后面想用串口打印距离值做标定都做不了。
4.4 低功耗设计:不需要时让外设休眠
智能垃圾桶如果靠电池供电,低功耗就是一个必须考虑的问题。资料里的源程序在主循环里没有做低功耗处理,但硬件设计上已经留了余地:蜂鸣器只在开盖动作触发时响一声,LCD 背光可以单独控制,HC-SR04 在两次测距之间可以进入停止状态。
如果你要在实物上做到低功耗,我建议这样改:在距离判断后,如果状态是关盖且没有触发,让 STM32 进入 STOP 模式,用外部中断唤醒——把 HC-SR04 的 Echo 引脚配置为外部中断源,或者用定时器周期性唤醒。实测下来,STM32 在 RUN 模式下功耗大约 50mA,STOP 模式下可以降到 5mA 以下。仿真里看不出功耗差异,但对电池供电的产品这是生死线。
还有一个小细节:LED 指示灯的限流电阻从 330Ω 改成 1kΩ,LED 亮度会略微下降,但静态功耗可以从 10mA 降到 3mA 左右。如果你做的是课程设计答辩,这一步可以写进论文的“优化与改进”章节,但注意别在实物上把亮度降得看不清了。
5. 避坑清单:仿真通过、实物翻车的五个典型问题
5.1 Echo 引脚直连 STM32 导致测距不稳定
现象:仿真里测距读数正常,实物接上 HC-SR04 后距离值偶尔跳变,严重时 STM32 发热甚至烧毁。
原因:HC-SR04 的 Echo 引脚在 5V 供电时输出高电平接近 5V,而 STM32 的 GPIO 耐压极限是 3.6V 左右。虽然部分引脚标注为 5V 容忍,但不是全部,且引脚间的瞬态过压也可能触发内部保护二极管导通,造成电流倒灌。
解决:在 Echo 和 PA1 之间串联一个 1kΩ 电阻,或者在 PA1 对地并联一个 3.3V 稳压二极管。如果手头没有稳压管,用两个 10kΩ 电阻分压也行,分压后高电平约为 2.5V,STM32 的逻辑阈值是 0.4 倍 VDD 即约 1.32V,2.5V 足够识别为高电平。从那以后我每次接 HC-SR04 实物,都会先在 Echo 串联电阻,这是不会省的元件。
5.2 舵机抖动或转动角度不对
现象:舵机上电后一直抖动,或者控制开盖时只转了 30° 而不是预期的 45°。
原因:最常见的是 PWM 频率不对。SG90 要求 50Hz,但初始化的预分频或重载值算错,频率变成 60Hz 或 40Hz,舵机就会抖动。另一个原因是电源问题,舵机启动瞬间电流可达 500mA,USB 供电带不动,电压跌落导致控制信号失真。
解决:先用逻辑分析仪或示波器实测 PA0 的波形,确认频率严格 50Hz,脉宽在 0.5ms 到 2.5ms 之间。如果频率正确还抖,检查舵机供电是否与 STM32 共地,且舵机电源是否独立供电。仿真里没有电流概念,舵机模型理想化,所以这一点只有实物才会暴露。
5.3 主循环卡死在超声波测距函数
现象:程序运行一段时间后,LED 不再闪烁,按键无响应,像是死机了。
原因:HCSR04_GetDistance() 里等待 Echo 变低的循环没有超时保护。一旦模块异常,比如超声波被遮挡、Trig 信号丢失,Echo 引脚可能一直保持高电平,while 循环永远跳不出去,整个程序卡死。如果 Echo 引脚悬空,电平不定也会导致类似问题。
解决:给等待 Echo 变低的循环也加一个超时退出,比如设置最大等待 50ms,超时直接返回上一次的测距值或者返回一个异常值(如 999)。资料里的源码加了第一步触发超时保护,但不少人复现时删掉了第二步的超时判断,就会遇到这个坑。
5.4 仿真中一切正常,实物上测距值整体偏移
现象:仿真里 30cm 处开盖,实物上 20cm 就开盖,误差固定且可重复。
原因:气温和声速有关。0°C 时声速约为 331m/s,30°C 时为 349m/s,差了 5%。代码里按 340m/s 计算,在低温环境下测距值会偏大,高温环境下偏小。另外,HC-SR04 模块本身的测距范围有盲区,2cm 以内测不准,近距离误差放大效应明显。
解决:最简单的方式是在代码里做标定。拿一个直尺,在 10cm、20cm、30cm 三个位置分别记录实际距离和测量距离,算出修正系数,在 distance 计算完后乘上这个系数。比如测量值一直是实际值的 0.95 倍,那就 distance = distance / 0.95。经验做法是修正系数只用一个,近距离和远距离各测一个点,取平均。
5.5 Keil 编译报错 undefined symbol:TIM_SetCounter
现象:编译时提示 TIM_SetCounter 未定义,或者 warning 提示该函数隐式声明。
原因:源程序使用了标准外设库(SPL),但工程没有添加对应的源文件 stm32f10x_tim.c,或者头文件 stm32f10x_tim.h 没有被包含。TIM_SetCounter 是 TIM 外设库函数,缺了这个文件就链接不上。
解决:在 Keil 的工程管理里展开 StdPeriph_Driver 组,检查是否包含 stm32f10x_tim.c。没有的话,右键选择 Add Existing Files,把库文件加进去。如果还是报错,检查头文件包含路径里有没有指向 Libraries 文件夹。这个坑在拿到任何现成的 STM32 源程序时都值得先检查一遍,比瞎猜编译错误要快得多。
6. 一个值得复现的细节:把测距函数改成不阻塞的中断方式
这套资料的源程序用的是阻塞式测距,也就是 HCSR04_GetDistance() 里用 while 等待 Echo 高电平结束。在智能垃圾桶这个场景里,阻塞 10ms 级别的时间完全够用,系统没那么强的实时性要求。但如果你想把这块板子改造成其他项目,比如做一个巡航避障小车,阻塞式测距就会成为瓶颈——小车需要同时处理电机调速、超声波测距、红外循迹等多个任务,任何一个传感器阻塞十几毫秒,整个控制周期都会被打乱。
我推荐的做法是把超声波测距改成定时器输入捕获加中断。具体思路是:Trig 引脚发送触发脉冲后,立刻把 Echo 引脚对应的定时器通道配置为上升沿捕获中断;Echo 一旦变为高电平,上升沿中断触发,记录当前计数值;同时把捕获通道切换为下降沿捕获;Echo 变为低电平时,下降沿中断触发,再次记录计数值。两个计数值相减,就得到了高电平持续时间。整个过程不需要主循环等待,测距在后台完成。
/* TIM3 输入捕获中断服务函数:捕获 Echo 上升沿和下降沿 */ void TIM3_IRQHandler(void) { if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET) { TIM_ClearITPendingBit(TIM3, TIM_IT_CC1); if (echo_level == 0) { /* 上升沿:记录起始时间 */ capture_start = TIM_GetCapture1(TIM3); echo_level = 1; /* 切换为下降沿捕获 */ TIM_OC1PolarityConfig(TIM3, TIM_OCPolarity_Low); } else { /* 下降沿:计算时长 */ capture_end = TIM_GetCapture1(TIM3); echo_level = 0; /* 切换回上升沿捕获 */ TIM_OC1PolarityConfig(TIM3, TIM_OCPolarity_High); distance = (capture_end - capture_start) * 0.017; } } }这段代码里有一个关键动作:第一次进入中断捕获到上升沿后,立刻把极性切换成下降沿。不切换的话,下一次仍然是上升沿中断,就永远等不到结束时刻。TIM_OC1PolarityConfig 这个函数就是干这个用的。另外要注意捕获计数器溢出问题:TIM3 是 16 位计数器,在 72MHz 时钟、预分频 71 时,每微秒计数一次,65535us 后溢出。超声波测距最大量程约 400cm,往返时间约 23500us,不会溢出,但如果你改了预分频,要自己算一下极限时间。
在主循环里,你只需要维护一个测距完成标志位,当标志位置位时去读 distance 变量,读完后清零。这样主循环可以保持非常短的控制周期,舵机响应也会更跟手。改造完成以后,如果测距值出现偶发跳变,回到阻塞式代码再看一遍时序,多数是中断里读取计数值的时机出了问题。从那以后我每次拿到涉及超声波的新工程,第一件事就是看测距函数是不是阻塞的,不是阻塞的直接用,是阻塞的先评估控制周期够不够,不够就改成中断捕获。这份资料本身按课件模板走,能带你跑通完整链路,而这一步改造恰好是把它从“课程设计作品”升级成“可以挪到别的项目复用”的小模块,希望帮到你。
本文还有配套的精品资源,点击获取