简介:本资源是一套完整的STM32智能垃圾桶毕业设计/课程设计实战项目,面向电子信息、自动化、嵌入式等专业本科生及实践开发者,解决垃圾分类终端设备的软硬件协同开发痛点。压缩包共含程序源码工程(Keil MDK)、原理图与PCB设计文件(Altium Designer格式)、任务书、开题报告、答辩PPT、硬件采购清单、功能说明文档及参考资料,总计608.57MB,文件结构清晰,覆盖从需求分析、电路设计、传感器驱动、多任务调度到人机交互的全链路实现。已有609人学习下载,适合用于课程设计答辩、毕业设计参考或嵌入式综合实训。读者可直接部署运行:支持语音唤醒+垃圾名称识别自动开盖、四色垃圾桶颜色识别联动步进电机、红外感应自动启闭、光敏LED照明、HX711称重显示、MQ-2烟雾超标联动水泵与蜂鸣报警等八大核心功能,配套文档详述各模块接线逻辑与关键代码注释,大幅降低二次开发门槛。
1. 项目概述与核心价值
最近在整理过往的嵌入式项目时,翻出了一个挺有意思的“老伙计”——一个基于STM32F103C8T6设计的智能垃圾桶。这可不是一个简单的感应开盖垃圾桶,它集成了语音控制、颜色识别、称重、环境光强检测、烟雾监测、人体感应,并且通过步进电机控制开盖,甚至还有一个水泵模块用于可能的消毒或除臭功能。这个项目代号“资料编号25”,算是我早期玩转STM32多传感器融合的一个典型练手作。虽然现在回头看,代码架构和硬件选型上有些可以优化的地方,但它的功能完整性和实现思路,对于想从点灯、按键跳到综合项目实战的嵌入式爱好者来说,依然有很高的参考价值。
这个项目的核心,在于如何让一块经典的“蓝桥杯”大赛常用芯片——STM32F103C8T6,这颗只有72MHz主频、64KB Flash、20KB RAM的Cortex-M3内核MCU,去协调管理七八个不同类型的传感器和外设,并实现一些初步的“智能”决策。它解决的不是单一问题,而是一个小型嵌入式系统在资源受限下的多任务调度、实时数据采集与简单逻辑判断的综合挑战。无论是学生做毕业设计、电子爱好者DIY,还是工程师寻找传感器集成方案,都能从这个项目中找到对应的模块代码和连接方法。接下来,我就把这个项目的设计思路、关键实现细节、踩过的坑以及完整的工程文件梳理逻辑分享给大家,你可以把它看作一个可裁剪的“智能硬件功能库”。
2. 整体系统架构与硬件选型解析
2.1 核心控制器与最小系统
项目的“大脑”是STM32F103C8T6,选择它原因很直接:性价比高、资料丰富、生态成熟。对于这样一个多外设的项目,其拥有的3个USART、2个I2C、2个SPI以及多个定时器和ADC通道,基本够用。我使用的是市面上最常见的“核心板”形态,自带3.3V LDO、复位电路和8MHz晶振,通过2.54mm排针引出所有IO,极大方便了在洞洞板或自制PCB上搭建系统。
注意:STM32F103C8T6的IO口是5V容忍的,但ADC输入引脚只能承受3.3V。在连接一些输出为5V电平的传感器模块(如某些型号的HC-SR501人体感应模块)时,需要确认模块输出是否是3.3V兼容,或者使用电平转换电路,避免损坏ADC。
2.2 传感器与执行器模块清单
整个系统的感知和执行层由以下模块构成,它们的选型直接决定了项目的成本和功能上限:
- 语音识别模块:选用的是LD3320非特定人语音识别芯片模块。它内置DSP和优化算法,能离线识别数十条预设指令(如“打开垃圾桶”、“关闭”),通过SPI或并口与MCU通信。选择它而非更强大的在线语音方案,是出于离线、实时性和隐私的考虑。
- 颜色识别传感器:采用TCS34725。这是一款数字RGB颜色光传感器,通过I2C通信,能直接输出RGB三原色和光强的数字值。用它来识别投入垃圾的包装颜色,辅助进行简单的垃圾分类提示(例如,识别到蓝色包装可能对应可回收物)。
- 称重模块:基于HX711压力传感器ADC芯片。连接一个标准称重传感器(如5kg量程),24位高精度ADC,通过简单的两线(SCK, DT)串行通信实现重量测量。这是实现“桶满报警”功能的关键。
- 光强传感器:使用BH1750FVI数字环境光强度传感器。同样是I2C器件,分辨率高,直接输出lux值。用于实现“光线暗时开盖自动补光”或调节指示灯亮度。
- 烟雾传感器:选用MQ-2模拟气体传感器。它检测可燃气体、烟雾的浓度,输出模拟电压信号。通过STM32的ADC通道读取,用于厨房等环境的安全监测,浓度超标时报警。
- 人体感应模块:最常用的HC-SR501红外热释电传感器。检测人体移动,输出高/低电平信号。用于实现“人来开盖,人走延时关闭”的自动感应功能。
- 步进电机与驱动:控制桶盖开合。选用28BYJ-48五线四相步进电机,搭配ULN2003驱动板。这种电机扭矩适中,价格低廉,通过脉冲序列控制,适合这种慢速、精确角度控制(如开盖90度)的场景。
- 水泵模块:一个小型12V直流水泵,通过一个MOS管或继电器模块由STM32的GPIO控制通断。初衷是连接一个微型喷雾装置,用于喷洒空气清新剂或消毒液,实现定时或触发式除臭。
2.3 供电与电路设计要点
系统供电是一个需要仔细规划的部分。由于存在步进电机(5V)和水泵(12V)这类功率器件,不能直接从STM32的3.3V或USB 5V取电。我的方案是:
- 采用一个12V/2A的直流电源适配器作为总输入。
- 12V直接供给水泵(通过MOS管开关)。
- 12V通过一个DC-DC降压模块(如LM2596)降到5V,为步进电机驱动板、语音模块、传感器等供电。
- 5V再通过AMS1117-3.3线性稳压芯片,为STM32核心板、BH1750、TCS34725等3.3V器件供电。
实操心得:务必在电机和水泵的电源入口处并联一个大容量电解电容(如470uF~1000uF)进行储能和滤波,防止电机启动时的瞬间大电流导致整个系统电压跌落,引发STM32复位。同时,控制电机和水泵的GPIO口,一定要用三极管或MOS管驱动进行隔离,切勿直接驱动!
3. 核心功能模块的软件实现与代码剖析
3.1 多传感器数据采集与任务调度
在资源有限的Cortex-M3上管理这么多传感器,裸机编程采用“前后台(超级循环)+中断”的方式是务实的选择。我构建了一个简单的协作式调度器雏形。
主循环框架大致如下:
int main(void) { // 初始化所有硬件:时钟、GPIO、定时器、ADC、I2C、SPI、USART等 All_Init(); // 初始化各传感器模块 Sensor_Init(); // 初始化功能状态机 StateMachine_Init(); while (1) { // 任务1:读取ADC值(烟雾、电池电压) - 优先级低,慢速采样 if (timer_flag.adc_read) { timer_flag.adc_read = 0; Read_ADC_Values(); } // 任务2:查询I2C传感器(颜色、光强) - 中等速度 if (timer_flag.i2c_read) { timer_flag.i2c_read = 0; Read_Color_Sensor(); Read_Light_Sensor(); } // 任务3:查询语音模块状态 if (timer_flag.voice_check) { timer_flag.voice_check = 0; Voice_Process(); } // 任务4:处理称重数据(HX711需连续时钟,单独处理) Weight_Process(); // 任务5:扫描人体感应等数字输入 Check_Digital_Inputs(); // 任务6:执行主状态机,根据传感器数据决策 Main_StateMachine_Run(); // 任务7:处理显示和通信(如OLED, 串口调试) Display_Update(); } }这里的timer_flag是一个结构体,由SysTick定时器中断服务函数周期性置位,从而实现不同任务以不同频率执行。例如,ADC每100ms读一次,I2C传感器每200ms读一次,语音查询每50ms一次。
3.2 语音识别模块的集成与指令处理
LD3320的集成是重点也是难点。它有两种模式:寄存器模式和FIFO模式。我使用的是更常见的寄存器模式。
关键步骤:
- 硬件连接:模块的SPI接口(MISO, MOSI, SCK, CS)连接STM32的SPI1,另需一个GPIO连接模块的复位引脚,一个GPIO连接中断输出引脚。
- 初始化与ASR配置:上电后,通过SPI写入一系列寄存器,配置芯片的时钟、麦克风增益、识别列表等。其中,识别列表的写入是关键。需要将你希望识别的拼音串(如“da kai la ji tong”)转换成LD3320特定的编码,通过SPI写入其指定RAM区域。
// 示例:添加识别条目“打开垃圾桶” LD3320_AddOneCode("da kai la ji tong", CMD_OPEN); // CMD_OPEN是自定义的指令编号 - 中断驱动:将模块的中断输出引脚连接到STM32的外部中断引脚。当LD3320识别到有效指令后,会触发中断。在中断服务函数中,读取结果寄存器,获取识别到的指令编号。
- 指令处理:在主循环或中断中根据指令编号执行相应操作,如设置状态机标志位。
踩坑记录:LD3320对供电噪声敏感,SPI通信速率不能太高(建议<1MHz)。识别列表的拼音串要尽可能符合日常发音,且不宜过长。初期调试时,最好先用串口打印出芯片的各个状态寄存器值,确保初始化流程正确。
3.3 颜色识别与简单分类逻辑
TCS34725的使用相对标准。通过I2C初始化后,使其开始积分光强,然后读取R,G,B,C(清晰)四个通道的16位数据。
颜色判断不是直接看RGB值,而是计算归一化的色度坐标,以消除光强影响:
uint32_t sum = r + g + b; if (sum > 0) { float r_norm = (float)r / sum; float g_norm = (float)g / sum; // float b_norm = (float)b / sum; // 实际上 r_norm + g_norm + b_norm = 1 if (r_norm > 0.4 && g_norm < 0.3) { // 红色占主导,可能是红色包装 color_type = COLOR_RED; } else if (g_norm > 0.4 && r_norm < 0.3) { // 绿色占主导 color_type = COLOR_GREEN; } else if (r_norm > 0.3 && g_norm > 0.3) { // 红绿混合,可能是黄色或橙色 color_type = COLOR_YELLOW; } else { // 其他或无法判断 color_type = COLOR_UNKNOWN; } }得到颜色类型后,可以通过RGB LED或OLED屏幕给出提示,例如“检测到红色包装,建议投入有害垃圾箱”。这只是非常基础的演示,真实的垃圾分类需要更复杂的图像识别。
3.4 称重模块的校准与滤波
HX711是称重的核心。其驱动简单,但要做好需要两点:校准和滤波。
校准:
- 在桶内无物时,读取HX711的值作为
offset(皮重)。 - 放置一个已知重量的标准砝码(如500g),读取此时的HX711值
raw_weight_with_known。 - 计算比例系数
scale = (raw_weight_with_known - offset) / 500.0。 - 后续测量中,
实际重量 = (当前读数 - offset) / scale。
- 在桶内无物时,读取HX711的值作为
软件滤波:HX711的24位输出仍有噪声。我采用了一阶互补滤波(低通滤波)结合中值滤波。
// 一阶低通滤波:new_weight = a * current_raw + (1-a) * last_weight; // 中值滤波:连续采样5次,排序后取中间值作为current_raw long median_filter(short nums, ...) { /* 中值滤波实现 */ } float low_pass_filter(float current, float last, float a) { /* 低通滤波实现 */ }经过滤波后的重量值稳定很多,用于判断桶是否将满(例如,重量持续超过设定阈值的80%)。
3.5 步进电机控制与桶盖动作
28BYJ-48是单极步进电机,采用8拍模式(A-AB-B-BC-C-CD-D-DA)控制时步距角最小,运行最平稳。我使用一个定时器(如TIM2)产生固定频率的PWM脉冲,在中断中根据预设的步数序列表切换GPIO输出,控制ULN2003的四个输入引脚。
关键点在于动作规划:桶盖的开启和关闭不是瞬间完成的,需要加速-匀速-减速的过程,否则会失步或产生噪音。我实现了一个简单的梯形加减速算法:
typedef struct { uint32_t total_steps; // 总步数(如开盖需500步) uint32_t accel_steps; // 加速阶段步数 uint32_t decel_steps; // 减速阶段步数 uint32_t current_step; // 当前已走步数 uint16_t current_delay; // 当前步进间隔(决定速度) uint16_t min_delay; // 匀速段速度(对应最快频率) uint16_t max_delay; // 启动速度(对应最慢频率) } MotorCtrl_t; // 在定时器中断中调用 void Motor_Step_Handler(MotorCtrl_t *motor) { if (motor->current_step < motor->total_steps) { // 1. 根据当前步数计算应处的阶段(加速、匀速、减速) // 2. 根据阶段,计算或查表得到本次的步进间隔 current_delay // 3. 重设定时器ARR为 current_delay, 以改变速度 // 4. 输出下一拍相位 // 5. motor->current_step++; } else { // 动作完成,停止定时器 HAL_TIM_Base_Stop_IT(&htim2); } }这样,桶盖的开启和关闭动作就显得平滑且可靠。
4. 系统状态机设计与多模态交互逻辑
4.1 主状态机定义
所有传感器数据最终汇聚到一个主状态机,决定垃圾桶当前的行为模式。我定义了以下几个主要状态:
- IDLE:空闲状态。桶盖关闭,持续监测人体、语音和重量。
- AUTO_OPENING:自动开盖状态。检测到人体接近,启动电机开盖。
- AUTO_OPEN:自动开盖保持状态。盖已打开,等待人体离开或超时。
- AUTO_CLOSING:自动关盖状态。人体离开或超时,启动电机关盖。
- VOICE_CTRL:语音控制状态。响应语音指令进行开盖或关盖。
- FULL_ALARM:桶满报警状态。重量超阈值,声光报警,拒绝新垃圾(盖不自动开)。
- SMOKE_ALARM:烟雾报警状态。烟雾浓度超阈值,声光报警,可能联动水泵喷雾(如果用于消防演示)。
状态之间的转换由事件触发,例如EVENT_HUMAN_DETECTED,EVENT_VOICE_OPEN,EVENT_WEIGHT_FULL,EVENT_TIMEOUT等。
4.2 多传感器数据融合与冲突处理
当多个传感器同时触发时,需要有优先级策略。例如:
- 烟雾报警优先级最高:任何状态下,一旦检测到烟雾超标,立即进入
SMOKE_ALARM状态,并打断当前动作(如正在关盖)。 - 桶满报警次之:在
IDLE或AUTO_OPEN状态下检测到桶满,进入FULL_ALARM,自动感应和语音开盖功能暂时禁用。 - 人体感应 vs 语音控制:在
IDLE状态下,两者均可触发开盖。但如果正在语音控制开盖过程中(VOICE_CTRL),人体感应事件会被忽略,避免冲突。
这些逻辑都在Main_StateMachine_Run()函数中,通过switch-case语句实现。
4.3 用户反馈与交互设计
一个好的产品离不开清晰的反馈。这个项目通过多种方式与用户交互:
- 视觉反馈:一个0.96寸OLED屏幕,显示状态(如“正常”、“桶满”)、重量、检测到的颜色提示。另外,RGB LED用不同颜色指示状态(蓝色-正常, 红色-报警, 绿色-识别到可回收色)。
- 听觉反馈:蜂鸣器。短促“嘀”一声表示操作成功(如语音识别成功),急促“嘀嘀”声表示报警,播放一段旋律表示欢迎(上电自检完成)。
- 触觉反馈:步进电机平稳的转动声本身也是一种反馈。在
FULL_ALARM状态下,可以控制电机轻微震动提示。
5. 工程文件结构解析与移植指南
“资料编号25.zip”解压后,工程结构清晰,基于标准库或HAL库构建(我后期用HAL库重构了一版)。核心目录如下:
Project_25/ ├── Core/ │ ├── Inc/ │ │ ├── main.h │ │ ├── gpio.h, adc.h, tim.h, i2c.h, spi.h, usart.h // 硬件抽象层配置 │ │ ├── bsp_voice.h, bsp_color_sensor.h, bsp_hx711.h ... // 各模块驱动头文件 │ │ └── state_machine.h, data_filter.h // 应用层头文件 │ └── Src/ │ ├── main.c // 主循环、初始化 │ ├── 各外设的MSP回调函数 │ ├── bsp_voice.c, bsp_color_sensor.c ... // 各模块驱动源文件 │ └── state_machine.c, data_filter.c // 应用层源文件 ├── Drivers/ │ ├── CMSIS/ │ └── STM32F1xx_HAL_Driver/ ├── MDK-ARM/ // Keil工程文件 │ └── Project_25.uvprojx └── README.txt // 硬件连接表、使用说明移植到你的硬件平台,需要修改的地方:
Core/Inc/main.h和Core/Src/main.c:根据你的核心板修改SystemClock_Config时钟配置。Core/Inc/下的外设头文件:检查并修改#define的引脚定义,确保与你实际的接线一致。例如:// bsp_hx711.h #define HX711_SCK_PIN GPIO_PIN_0 #define HX711_SCK_PORT GPIOA #define HX711_DT_PIN GPIO_PIN_1 #define HX711_DT_PORT GPIOA- 模块驱动初始化函数:部分模块(如LD3320)的初始化序列可能因模块批次略有差异,如果遇到问题,需对照最新数据手册调整。
- 参数调整:在
state_machine.c或各模块驱动中,有大量可调参数,如重量满阈值、光强阈值、烟雾报警阈值、电机加减速曲线、语音识别列表等,需要根据你的实际硬件和应用场景重新校准和设定。
6. 常见问题排查与调试心得
6.1 电源与噪声问题
- 现象:系统运行不稳定,偶尔复位,传感器读数跳动大。
- 排查:
- 首先用万用表测量3.3V和5V电源轨的电压,在电机启停时观察是否有大幅跌落。
- 在电源入口和各IC的电源引脚附近增加去耦电容(104瓷片电容并联10uF电解电容)。
- 将模拟部分(如HX711, MQ-2的ADC输入)和数字部分(MCU, 电机驱动)的电源走线在物理上分开,单点接地。
- 心得:嵌入式系统,电源是基石。多电机、多传感器的项目,电源设计必须留足余量并做好滤波。
6.2 I2C通信失败
- 现象:TCS34725或BH1750读不出数据。
- 排查:
- 确认上拉电阻(通常4.7kΩ)已正确连接到SDA和SCL线。
- 用逻辑分析仪或示波器抓取I2C波形,看起始信号、地址、ACK是否正常。STM32的I2C时钟频率不宜过高,初期可设为100kHz。
- 检查从设备地址是否正确。TCS34725的地址是
0x29(7位), BH1750默认是0x23。 - 注意I2C总线上的电平是3.3V还是5V,确保所有设备电平兼容。
6.3 语音识别率低
- 现象:LD3320经常识别错误或不识别。
- 排查与优化:
- 环境噪声:在安静环境下测试。可以尝试在麦克风输入端增加简单的RC低通滤波电路。
- 供电质量:确保LD3320的3.3V供电干净,最好单独用一颗LDO供电。
- 识别列表优化:拼音串尽量用常见的、发音差异大的词。例如,“打开”比“开启”识别率可能更高。可以适当增加列表容量,但不要超过芯片限制。
- 软件优化:在识别到指令后,增加一个简单的“防抖”处理,例如连续两次查询到相同结果才确认,避免误触发。
6.4 步进电机抖动或失步
- 现象:电机转动不顺畅,有噪音,或者实际转动角度与预期不符。
- 排查:
- 驱动电流不足:ULN2003驱动28BYJ-48是足够的,但确保驱动板的输入电压(5V)稳定。电机堵转会消耗大电流,检查电源能否承受。
- 时序问题:检查步进脉冲序列的时序是否正确,8拍顺序不能错。在切换相位之间加入微秒级的延时(
HAL_Delay_us())有时能改善。 - 机械负载:检查桶盖的机械结构是否顺滑,有无卡滞。电机轴与负载的连接是否同心。
- 加减速曲线:如果直接从静止跳到高速,极易失步。务必使用前面提到的梯形加减速算法,并调整
accel_steps和decel_steps参数,找到最适合你机械结构的曲线。
6.5 称重数据漂移
- 现象:放上同一物体,每次读数有微小差异,或者长时间放置零点漂移。
- 优化:
- 硬件:称重传感器要安装稳固,避免侧向力。使用质量好的HX711模块,其基准电压稳定是关键。
- 软件滤波:结合中值滤波和低通滤波已能解决大部分随机噪声。对于缓慢的温漂,可以定期在确认桶内无物时进行“自动清零”(更新
offset),但需谨慎使用,避免误清零。 - 校准:使用多点校准(如空载、半量程、满量程)并存储校准参数到STM32的Flash中,可以提高线性度。
这个项目就像一个小型的“嵌入式技术博览会”,涵盖了MCU基础外设、多种通信协议、传感器应用、电机控制、状态机设计、电源管理和简单的数据融合。虽然每个模块单独看都不复杂,但将它们有机整合,并稳定可靠地运行,才是真正的挑战所在。希望这份详细的拆解,能为你实现自己的智能硬件创意提供扎实的参考。工程文件中的代码注释比较详细,结合本文的说明,你应该能较快地上手和修改。
本文还有配套的精品资源,点击获取