简介:本资源是一套完整的STM32智能垃圾桶毕业设计/课程设计实战项目包,面向电子信息、自动化、嵌入式相关专业本科生及实践开发者,解决多传感器融合、语音交互与机电协同控制等综合工程问题。压缩包共含程序源码工程(Keil MDK)、原理图与PCB设计文件(Altium Designer格式)、任务书、开题报告、答辩PPT、硬件采购清单及功能说明文档等核心资料,总大小608.57MB,结构清晰、模块对应明确,便于从硬件搭建到软件调试全流程学习。已有609人下载学习,覆盖从原理理解、代码移植到实物联调的完整链路。读者可直接复现语音唤醒分类投放、红外自动感应开盖、TCS34725颜色识别联动步进电机、MQ-2烟雾报警触发水泵、HX711称重与OLED实时显示等八大功能,配套详细硬件描述与参考资料,显著降低嵌入式项目落地门槛。
1. 项目缘起:一个“全能型”智能垃圾桶的诞生
最近在整理工作室的旧项目,翻到了一个挺有意思的玩意儿——一个基于STM32的“六边形战士”智能垃圾桶。说它是六边形战士,是因为它集成的功能实在有点多:语音控制、颜色识别、称重、光强检测、烟雾报警、人体感应,还驱动着步进电机和水泵。这听起来不像个垃圾桶,倒像个小型的智能机器人。这个项目的工程文件编号是25,当初做它,一方面是出于对嵌入式系统综合应用的好奇,另一方面也是想挑战一下,看看在STM32F103C8T6这颗经典的“入门神U”上,到底能塞下多少种传感器和外设,并让它们协同工作。
现在回头看,这个项目虽然功能堆叠,但恰恰是学习嵌入式开发的绝佳案例。它不像一个单纯的教程项目那样只教你点灯、串口通信,而是把多个实际应用场景中的模块(感知、决策、执行)串联了起来。对于已经掌握了STM32基础,想从“会调库”进阶到“会做系统”的开发者来说,这种多传感器融合的项目非常有价值。它能让你深刻理解中断管理、任务调度、外设冲突、电源管理这些在复杂系统中才会遇到的问题。今天,我就把这个项目的核心设计思路、关键实现细节,以及那些调试过程中踩过的“坑”和“雷”,系统地梳理分享出来。无论你是想复现一个类似的作品,还是想从中汲取多模块集成的经验,相信都能有所收获。
2. 核心架构设计:如何让STM32F103C8T6“一心多用”
拿到STM32F103C8T6这颗芯片,第一件事就是评估资源。它拥有72MHz的主频、64KB Flash、20KB RAM,对于裸机开发或轻量级RTOS来说,资源不算富裕,但规划得当也完全够用。这个智能垃圾桶项目的核心挑战在于,如何让多个需要实时或准实时响应的传感器和外设,在一个单核MCU上和谐共处。
2.1 外设资源分配与引脚规划
引脚规划是硬件设计的第一步,也是软件架构的基础。必须在一开始就厘清每个模块的通信方式、功耗和对实时性的要求。我为这个项目绘制了一张详细的引脚分配表,这是避免后续硬件冲突和软件混乱的关键。
| 模块名称 | 功能描述 | 通信接口/控制方式 | 占用STM32引脚 | 备注 |
|---|---|---|---|---|
| 语音识别模块 | 接收语音指令,如“打开”、“关闭” | UART (串口) | PA9 (TX), PA10 (RX) | 通常为串口1,模块返回识别结果字符串。 |
| 颜色识别传感器 | 识别投放垃圾的颜色(用于分类) | I2C | PB6 (SCL), PB7 (SDA) | 如TCS34725,需注意I2C上拉电阻。 |
| 称重传感器+HX711 | 测量垃圾桶内垃圾重量 | GPIO (模拟SPI) | PA0 (DT), PA1 (SCK) | HX711为24位ADC,采用准SPI通信。 |
| 光敏电阻+ADC | 检测环境光照强度 | ADC | PA2 (ADC_IN2) | 需串联分压电阻,软件滤波。 |
| MQ-2烟雾传感器 | 检测可燃气体/烟雾浓度 | ADC | PA3 (ADC_IN3) | 模拟量输出,需设置阈值判断。 |
| HC-SR501人体红外传感器 | 检测是否有人靠近 | GPIO 输入 | PA4 | 输出高/低电平,使用外部中断或轮询。 |
| 步进电机驱动器 | 控制桶盖开合 | GPIO (脉冲+方向) | PB8 (PUL), PB9 (DIR) | 使用定时器PWM模式生成脉冲,GPIO控制方向。 |
| 5V微型水泵 | 模拟消毒/喷淋(需谨慎) | GPIO 输出 | PA8 | 通过三极管或MOS管驱动,注意防水与安全。 |
| OLED显示屏 | 显示状态信息(重量、模式等) | I2C 或 SPI | I2C: PB10 (SCL), PB11 (SDA) | 与颜色传感器共用I2C,注意地址区分。 |
| 用户按键 | 手动切换模式、校准等 | GPIO 输入 | PC13 (或其他) | 启用内部上拉,软件消抖。 |
| LED指示灯 | 系统状态指示 | GPIO 输出 | PC14, PC15 | 如电源灯、工作状态灯、报警灯。 |
注意:上表是一个典型规划。实际设计中,PA13、PA14、PA15、PB3、PB4这几个引脚默认有调试(SWD/JTAG)功能,如果要用作普通GPIO,必须在代码中禁用调试功能,否则无法正常控制。我最初就栽在这里,步进电机死活不转,查了半天才发现是PB3、PB4被JTAG占用了。
2.2 软件架构选择:裸机状态机 vs. RTOS
面对这么多任务,软件怎么组织?主要有两种思路:超级循环+状态机和实时操作系统(RTOS)。
对于STM32F103C8T6,我最初尝试了FreeRTOS。创建了几个任务:VoiceTask(语音处理)、SensorPollTask(轮询传感器)、MotorCtrlTask(电机控制)、DisplayTask(刷新屏幕)。理论上很清晰,但实际跑起来问题不少。首先是RAM紧张,每个任务栈、各种队列和信号量一分配,20KB RAM很快见底,容易出现堆栈溢出,系统莫名死机。其次是任务间同步和通信变得复杂,比如称重数据需要同时给显示任务和逻辑判断任务使用,用队列还是全局变量?全局变量要考虑互斥,增加了复杂度。
对于这个具体项目,我最终回归了更轻量、更可控的“裸机+状态机”架构。主程序在一个while(1)超级循环中运行,但循环内部不是简单轮询,而是基于时间片和事件驱动的状态机。
// 伪代码示例:主循环框架 int main(void) { // 硬件初始化(时钟、GPIO、定时器、ADC、串口、I2C等) All_Init(); while(1) { // 1. 检查定时器标志,实现“时间片” if (sys_tick_10ms_flag) { sys_tick_10ms_flag = 0; // 执行需要10ms周期执行的任务 Key_Scan(); // 按键扫描(消抖在此处理) Sensor_Data_Update(); // 更新传感器数据(非阻塞式) } if (sys_tick_100ms_flag) { sys_tick_100ms_flag = 0; // 执行需要100ms周期执行的任务 Logic_Process(); // 核心业务逻辑判断 OLED_Refresh(); // 刷新显示,避免刷新过快闪烁 } // 2. 处理异步事件(中断置位标志) if (voice_cmd_flag) { voice_cmd_flag = 0; Voice_Cmd_Process(); // 处理语音指令 } if (human_detect_flag) { // 人体传感器中断触发 human_detect_flag = 0; Human_Approach_Process(); } // 3. 后台常驻任务(如步进电机脉冲生成,用状态机控制) Stepper_Motor_StateMachine(); // 系统空闲时可进入睡眠模式省电(如果设计了的话) // __WFI(); } }这种架构的优势在于资源消耗极小,所有变量和缓冲区尽在掌握。通过精心设计的时间片(利用SysTick定时器中断),让不同执行周期的任务有条不紊地运行。中断服务程序(ISR)只做最紧急的事(如设置标志位、读取数据),耗时的处理(如语音指令解析、复杂的逻辑判断)放到主循环中,保证了系统的实时性和响应性。对于从单片机学习过渡而来的开发者,这种模式也更容易理解和调试。
3. 关键模块实现细节与避坑指南
有了整体架构,接下来就是逐个攻克模块。每个模块单独调试可能都很顺利,但集成在一起时,各种“坑”才会浮现出来。
3.1 语音识别模块的稳定通信
我使用的是市面上常见的串口语音识别模块,如LD3320或更智能的型号。它们通常通过串口发送固定的字符串指令,如“AA BB 03 00 01 0A”代表识别到“打开”指令。
关键点1:串口接收不定长数据。语音模块返回的数据包长度不固定。最可靠的方法是使用串口空闲中断(IDLE Interrupt)。STM32的UART在检测到一帧数据接收完成后,如果总线空闲时间超过一个字节的传输时间,就会产生IDLE中断。结合DMA,可以实现高效、准确的不定长数据接收。
// 启用串口接收DMA和空闲中断 HAL_UART_Receive_DMA(&huart1, voice_rx_buffer, VOICE_BUF_SIZE); __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); // 在串口中断服务函数中处理IDLE中断 void USART1_IRQHandler(void) { if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET) { __HAL_UART_CLEAR_IDLEFLAG(&huart1); // 清除空闲中断标志 // 计算本次接收到的数据长度 voice_data_len = VOICE_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx); if(voice_data_len > 0) { voice_cmd_flag = 1; // 通知主循环处理 } // 重启DMA接收,准备下一帧 HAL_UART_Receive_DMA(&huart1, voice_rx_buffer, VOICE_BUF_SIZE); } }关键点2:指令解析与容错。不要认为每次收到的数据都是完美的。需要在解析函数中加入校验(如和校验、固定头尾判断),并设计简单的状态机来解析有效指令,丢弃错误数据。同时,要处理“误唤醒”问题,比如环境噪音可能触发模块,可以在逻辑层设置一个“唤醒词”前置,或者只有垃圾桶在待机状态时才开启语音识别。
3.2 颜色识别与称重传感器的数据可靠性
颜色传感器(如TCS34725)和称重模块(HX711)都对环境干扰敏感。
颜色识别:I2C通信本身要稳定。确保上拉电阻(通常4.7KΩ)已正确连接。更重要的是集成时间(Integration Time)和增益(Gain)的设置。在昏暗环境下,需要更长的积分时间和更高的增益,但这会降低测量速度。我的经验是,在垃圾桶内部安装一个小的补光灯(由光敏电阻控制,暗时自动开启),为颜色传感器提供稳定的光照条件,这样就能使用固定的、较短的积分时间,提高响应速度。读取到的RGB值需要经过白平衡校准,通常是在空桶状态下,放入一张标准白纸,读取其RGB值作为基准。
称重传感器:HX711是一款24位ADC,精度很高,但极易受干扰。避坑重点如下:
- 电源隔离:HX711和应变片传感器的供电必须非常干净。最好使用独立的LDO(低压差线性稳压器)为其供电,并与MCU的数字电源隔离。模拟地和数字地之间用0欧电阻或磁珠单点连接。
- 机械安装:传感器必须安装稳固,不能有除了垂直方向以外的应力。垃圾桶的机械结构要保证重量完全、垂直地作用在传感器上。
- 软件滤波:原始数据跳动是正常的。必须采用软件滤波。我推荐滑动平均滤波结合突变值剔除。连续采样10-20次,排序后去掉最大最小的几个值,再取平均。同时,只有当连续多次(如5次)测量值稳定在一个小范围内时,才更新显示的重量值,这能有效避免因桶内垃圾晃动导致的数值跳变。
- 去皮与校准:上电后,必须自动执行一次“去皮”(Tare),减去桶身自重。校准则需要已知重量的砝码。在代码中保存校准系数到STM32的Flash中,避免每次上电重新校准。
3.3 烟雾传感与人体感应的抗干扰设计
MQ-2传感器和HC-SR501都是非常“活泼”的传感器,容易误报。
MQ-2烟雾传感器:它输出的是模拟电压,浓度越高,电压越高。但它的值会随时间漂移(预热效应),且对酒精、香烟烟雾甚至炒菜油烟都可能敏感。直接用一个固定阈值判断“着火”非常不可靠。我的做法是:
- 建立基线:系统上电稳定运行5分钟后,连续采样1分钟,取平均值作为当前环境的“洁净空气基线值”。
- 动态阈值:报警阈值 = 基线值 + 固定偏移量(通过实验确定)。这样能适应不同的室内空气环境。
- 持续判断:触发报警不是一次超标就报警,而是“连续10次采样中有8次超过阈值”才触发。同时,在OLED上显示实时的浓度曲线或数值,让用户心中有数,而不是一个简单的“报警/正常”状态。
HC-SR501人体红外传感器:它的输出是数字电平(有人高电平,无人低电平)。问题在于其触发方式。有些模块在感应到人体后会输出一个固定宽度(如几秒)的高电平脉冲,然后恢复低电平(不可重复触发模式);有些则是在感应期间持续高电平,人离开后恢复低电平(可重复触发模式)。务必根据你的模块型号和跳线设置,在代码中采用对应的检测逻辑。我建议使用可重复触发模式,并连接到MCU的外部中断引脚(上升沿和下降沿触发)。在中断服务函数中只改变一个状态标志,在主循环中根据这个标志的持续时间来判断是“持续有人”还是“短暂经过”,从而决定是否开盖以及开盖后保持多久。
3.4 步进电机与水泵的驱动与保护
步进电机驱动(控制桶盖):我使用常见的A4988或DRV8825驱动模块。MCU只需要提供“脉冲PUL”和“方向DIR”两个信号。
- 脉冲生成:使用STM32的定时器(如TIM3)的PWM模式来产生脉冲,通过改变ARR(自动重装载值)来灵活调整电机速度。绝对不要用
delay函数来生成脉冲,这会阻塞整个系统。 - 开合控制:编写一个步进电机状态机函数,在
Stepper_Motor_StateMachine()中调用。状态包括STOP、ACCEL(加速)、RUN(匀速)、DECEL(减速)。通过计算总步数(对应开盖角度)和加减速曲线,可以让开盖动作平滑、安静、快速。 - 限位与保护:必须在桶盖完全打开和完全关闭的位置安装微动开关作为限位传感器。当电机运行到限位时,MCU检测到开关信号,立即停止发送脉冲,并切断电机使能(ENABLE引脚),防止电机堵转烧坏驱动芯片或电机。
5V微型水泵控制:水泵是感性负载,且工作电流较大(可能几百mA)。切记:不能直接用STM32的GPIO引脚驱动!
- 必须使用驱动电路:最简单可靠的是用MOS管(如IRLZ34N)或三极管(如S8050)+继电器方案。GPIO通过一个电阻控制MOS管栅极,水泵接在电源和MOS管漏极之间。
- 续流二极管:如果水泵是直流有刷电机,必须在水泵两端反向并联一个续流二极管(1N4007),用于吸收电机停止时产生的反向电动势,保护MOS管。
- 防水与安全:这是一个模拟消毒/喷淋的功能,实际使用时必须极度谨慎。电路部分必须做好防水绝缘处理,并且这个功能最好仅作为演示,或者要有物理隔离措施,避免水汽损坏电子设备或引发安全问题。
4. 多传感器数据融合与业务逻辑设计
当所有传感器数据都能稳定获取后,真正的“智能”体现在业务逻辑上。如何让这些数据协同工作,而不是各自为政?
我设计了一个简单的主状态机来管理垃圾桶的整体行为,状态包括:SLEEP_MODE(休眠,关闭大部分传感器)、STANDBY_MODE(待机,检测人体和语音)、OPENING_MODE(正在开盖)、OPENED_MODE(盖已打开,正在检测投放)、CLOSING_MODE(正在关盖)、ALERT_MODE(烟雾报警)。
逻辑判断的核心函数Logic_Process()在主循环中周期执行(如每100ms),它根据当前状态和各传感器标志来决定状态迁移。
举例:自动开盖逻辑
- 条件1:人体传感器触发(
human_detect_flag=1),且持续时间超过500ms(避免小动物或风吹窗帘误触发)。 - 条件2:系统当前处于
STANDBY_MODE。 - 条件3(可选):环境光强低于阈值(晚上),自动打开桶内补光灯,辅助颜色识别。
- 动作:状态切换到
OPENING_MODE,启动步进电机状态机,同时播放一个提示音(可通过PWM驱动蜂鸣器实现)。 - 开盖后:进入
OPENED_MODE,启动一个“关盖倒计时”(如10秒)。在此期间,如果称重传感器检测到重量增加(投放了垃圾),则重置倒计时。倒计时结束,自动关盖。
举例:语音控制逻辑
- 语音指令解析线程收到“打开”指令。
- 无论当前处于何种状态(除了
ALERT_MODE),都强制中断当前动作,切换到OPENING_MODE。这实现了语音控制的最高优先级。
举例:烟雾报警逻辑
Sensor_Data_Update()周期读取MQ-2 ADC值,并通过上述的动态阈值法判断是否报警。- 如果触发报警,系统立即进入
ALERT_MODE。 - 动作:立即关闭桶盖(如果开着),停止一切其他非必要操作(如语音识别),让蜂鸣器急促鸣响,OLED屏幕显示红色警报信息和浓度值,水泵可能启动(如果是设计用于喷淋降温的,但需极端谨慎)。
- 报警状态需要手动(通过按键)确认后才能解除,恢复到
STANDBY_MODE。
这种基于状态机的逻辑设计清晰明了,易于调试和扩展。每个状态该做什么,由什么事件触发转移,都一目了然。
5. 电源管理与系统稳定性优化
一个由电池或USB供电的嵌入式设备,功耗和稳定性是终极考验。
功耗优化:
- 分时供电:对于大功耗且非持续工作的模块,如水泵、补光灯、甚至语音识别模块,可以通过MOS管开关电路控制其电源通断。在不需要时彻底断电,而非仅仅软件休眠。
- MCU睡眠:当系统处于
SLEEP_MODE时(如深夜无人时段),可以通过__WFI()指令让STM32进入睡眠模式(Sleep Mode),此时CPU停止,但外设和中断仍可工作。当人体传感器的中断(或其定时唤醒)发生时,再唤醒MCU。这能大幅降低待机功耗。 - 外设时钟管理:在初始化后,关闭所有不必要的外设时钟(如ADC、I2C2等)。在需要使用时再开启。
稳定性加固:
- 看门狗(IWDG):必须启用独立看门狗!这是防止程序跑飞的最后防线。根据最耗时任务的周期,合理设置看门狗超时时间(如1秒),在主循环的合适位置喂狗。
- 软件复位:在关键错误处理(如传感器持续无响应、内存校验失败)时,主动调用
NVIC_SystemReset()进行软件复位,好过系统死机。 - 参数存储:校准参数、系统设置等需要掉电保存的数据,应存储到STM32的内部Flash中。注意Flash的写入寿命(约1万次),避免频繁写入。可以采用“写平衡”策略,或者只在参数确实改变时才写入。
6. 调试心得与项目总结
回顾整个项目,从原理图绘制、PCB打样(或洞洞板焊接)、到一步步调试每个模块,最后整合联调,是一个充满挑战也收获巨大的过程。
最大的教训是“先分后合,逐步集成”。千万不要把所有模块焊好,然后一口气写所有代码。我的流程是:
- 最小系统:先确保STM32能正常下载程序、跑通点灯和串口打印。
- 逐个击破:每焊接或连接一个模块,就单独为它编写测试代码,验证其基本功能。比如单独测试步进电机正反转,单独测试HX711读数,单独测试语音模块识别。
- 模块化代码:为每个外设编写独立的
.c/.h文件,提供清晰的初始化、读/写接口函数。这步至关重要。 - 两两联调:开始将关联度高的模块组合测试。例如,测试人体感应触发开盖;测试语音控制开盖;测试称重变化触发关盖倒计时重置。
- 系统整合:最后将所有模块的驱动和业务逻辑状态机整合到主工程中。此时大部分问题已经在前面暴露和解决了。
关于资源管理:STM32F103C8T6的20KB RAM是硬约束。要善用static局部变量、减小全局数组大小、避免在栈上分配大数组。使用-ffunction-sections -fdata-sections链接选项配合-Wl,--gc-sections,可以有效地移除未使用的代码和数据,节省Flash空间。
这个“智能垃圾桶”项目,虽然功能上有些“炫技”,但其技术内核是普适的。它锻炼了多外设管理能力、实时性设计思维、模拟数字信号处理经验以及系统稳定性设计意识。当你成功地将这一大堆传感器和执行器协调起来,稳定地完成预设功能时,你对嵌入式系统的理解会上一个坚实的台阶。希望这份详细的复盘,能为你自己的综合项目实践提供一份有价值的参考地图。
本文还有配套的精品资源,点击获取