基于STM32F103C8T6的智能垃圾桶设计与传感器抗干扰实践
简介这份 PDF 文档围绕基于 STM32F103C8T6 单片机的智能垃圾桶控制系统展开面向电子信息、自动化及相关专业学生与单片机开发者可作为课程设计、毕业设计和智能家居小项目开发的参考。内容从系统总体方案入手串联超声波测距、红外感应、电机驱动、LCD1602 显示、WT588D 语音播报与继电器消毒等模块讲清自动翻盖、延时关闭、满溢提醒和按键消毒的控制逻辑与软硬件配合方式对传感器选型、电路连接和程序流程均有较完整的论述。压缩包内共 1 个 PDF 文件约 1.94MB为完整论文式技术文档含摘要、系统设计、模块分析与总结等部分便于按章节查阅和摘录。该资源已有 7845 人学习适合需要快速理解智能垃圾桶实现思路、搭建实物原型或撰写相关设计文档的读者参考借鉴。1. 一个桶盖的响应速度决定这套东西能不能落地很多基于 STM32 的智能垃圾桶设计图纸上画得漂亮焊完板子一上电就原形毕露手伸过去盖板纹丝不动或者盖板开一半又往回缩像抽搐一样。这不是玄学是红外阈值、超声时序和电机供电三件事里至少有一样没处理好。手上这份基于 STM32F103C8T6 单片机的智能垃圾桶设计把红外感应翻盖、HC-SR04 测满溢、LCD1602 显示容量、WT588D 语音提醒、按键消毒这五条链路都跑通了它解决的是人不碰桶这个很具体的问题适合做课题设计、想练 STRM32 定时器捕获和比较器电路的从业者。它的难点从来不在算法而在传感器现场标定和干扰抑制后者才是决定成败的地方。2. 方案定型主控与外设怎么配才不打架选型这件事我见过太多人先囤一堆模块再回头硬塞程序。正确的顺序是先数引脚、再算电流、最后定接口顺序反了后面全是返工。2.1 主控为什么是 C8T6而不是更便宜的八位机这套设计要同时干四件事跑一路输入捕获测超声波回波宽度、扫一路按键并做消抖、刷 LCD1602、按需触发语音芯片。八位机也能做但输入捕获的精度和定时器数量会非常紧张超声测距的时间分辨率直接决定厘米级误差。STM32F103C8T6 是 Cortex-M3 内核主频 72MHz64KB Flash、20KB SRAMLQFP48 封装引出 37 个可用 GPIO带 3 个通用定时器和 2 个高级定时器其中 TIM2 做输入捕获、TIM3 做按键扫描节拍、TIM4 留给软件延时资源正好够分配不宽裕但也不捉襟见肘。时钟这块有个细节要说明外部高速晶振配 8MHz经过 PLL 九倍频得到 72MHz 系统时钟RTC 用的那颗是 32.768kHz不是 32.768MHz。原始文档里写成了 32.768MHz属于笔误照那个参数买晶振RTC 必然不走。血泪经验是BOM 表里的晶振一定要按频率和负载电容两个参数同时核对只对频率不对负载电容起振失败照样是黑匣子。引脚分配我一般写成表贴在工位上避免改代码时来回翻原理图。功能引脚模式备注HC-SR04 TrigPA0推挽输出触发脉冲 ≥10usHC-SR04 EchoPA1浮空输入复用 TIM2_CH2 输入捕获红外感应输入PB0上拉输入低电平检测到物体电机驱动 IN1~IN4PB4~PB7推挽输出接 ULN2003LCD1602 D4~D7PA4~PA7推挽输出四线模式省 IOLCD1602 RS/RW/ENPB10~PB12推挽输出RW 可接地省一根WT588D DATAPA9推挽输出一线串口WT588D BUSYPA10浮空输入忙状态指示消毒按键PC13上拉输入低电平有效消毒继电器PB1推挽输出高电平吸合这张表的用法很直接先按它接完线再照它写初始化最后照它排查。任何一处接反都会在第 5 章那些翻车点里体现出来。2.2 五个模块的取舍逻辑红外感应用 RPR220 还是成品避障模块。RPR220 是反射式光电传感器内部带可见光过滤体积小、便宜但它输出的是模拟量必须配 LM393 比较器把它整形成数字电平阈值靠一只滑动变阻器现场调。好处是阈值你能自己控制难点是每一台的阈值都不一样量产时这是负担单台打样时这是灵活性。如果是批量做我会换成带电位器的一体化避障模块省事如果是要讲清比较器原理RPR220 更合适。这套资料用的是两路 RPR220 并联再进 LM393属于后者。满溢检测用超声还是称重。称重传感器要装桶底结构改动大而且换袋、倒垃圾后零点漂移严重HC-SR04 装在桶盖内侧朝下打量的是垃圾堆顶到探头的高度安装简单、免标定零点代价是塑料袋、纸团这类吸声材质会让回波变弱。所以超声测满在垃圾桶场景是够用而不是精确后面 3.3 节会给一个防误判的判据。翻盖驱动用步进还是舵机。ULN2003 驱动 28BYJ-48 减速步进的方案成本低、扭矩够缺点是四相八拍占四个 IO且必须做加减速否则启停时整个桶身都在抖舵机方案只占一个 PWM 口但 180° 舵机扭力普遍偏小大盖板压不住。这套设计选了步进那就得在软件里老老实实补一个梯形加减速。提示音用 WT588D 还是蜂鸣器。蜂鸣器只能滴滴讲不清是满了还是消毒中WT588D 是可以烧录语音段的语音芯片一线串口发地址就能播指定句子交互体验提升明显代价是多一根控制线和一个烧录步骤。显示用 LCD1602 还是 OLED。1602 字符够用、强光下可读、驱动简单缺点是占 6 到 10 根 IO还得单独调对比度电位器。OLED 只占 I2C 两线但在走廊这种光照变化大的环境里镜面反射会让人看不清。这套设计用 1602是因为它要在户外场景下保持可读性。2.3 供电拓扑别让电机和单片机抢同一口饭整机从一路 5V 适配器进来分成三条支路5V 直供 ULN2003 和继电器线圈5V 经 AMS1117-3.3 稳压给 STM32 和 LCD1602 背光限流电阻HC-SR04 和 WT588D 吃 5V。关键点是电机和继电器的地必须单点汇到电源入口不能串在单片机的 GND 走线上否则电机换相的瞬间地弹会把 MCU 的复位脚拉低表现就是一开盖就死机。我一般会在 ULN2003 的 COM 端并一颗 1N4148 续流二极管在电机电源脚就近并 100uF 电解加 0.1uF 瓷片在 STM32 每个电源脚并 0.1uF。这三个动作加起来不到五毛钱但能省掉后面两小时的抓瞎。继电器那一路同理线圈两端必须并续流二极管触点侧如果驱动的是紫外灯管还要考虑启动瞬间的浪涌。提示调试阶段用可调电源限流到 300mA 再上电短路时电源会限流而不是冒烟这个习惯救过我不止一次。3. 两路感知落地把模拟量变成可信的数字量感知层是这套设计里最容易看起来正常、实际不可用的部分。红外决定盖板开不开超声决定语音叫不叫两路信号都必须先稳定再去谈逻辑。3.1 RPR220 加 LM393阈值不是一个固定数字RPR220 的接法是发射管串限流电阻接 5V接收管集电极接 5V、发射极经负载电阻接地中间取分压点送 LM393 的正输入端LM393 的负输入端接一只 10k 滑动变阻器分压出的参考电压。没有物体靠近时接收管收不到反射光内阻很大分压点电压高有手靠近时反射光让接收管导通分压点电压被拉低比较器翻转输出从高变低STM32 读到下降沿就知道有人来了。这里有两个容易忽略的点。第一是回差单阈值比较在临界距离上会疯狂抖动盖板就会反复开合。解决办法是在软件侧做时间滤波连续 N 次采样都是低电平才认定有人连续 M 次都是高电平才认定人走了N 取 3、M 取 8 是个不错的起点。第二是环境光RPR220 内部虽然有滤光片但正对窗户或正对射灯时红外分量依然会抬高底噪把阈值顶穿。实机上我一般会让探头略微朝下倾斜 15° 到 20°避开直射同时把参考电压调到手在 20cm 处刚好翻转的位置然后用胶固定电位器——不点胶的电位器运输途中自己就转走了。调试时别靠猜。拿万用表直流档量比较器输出脚手在 5cm、10cm、20cm、30cm 各停三秒记下翻转点再把这个距离对应的电位器位置锁死。这一步做完红外这块基本就不会再出问题。3.2 HC-SR04 的时序与换算所有误差都来自这 10 微秒HC-SR04 的工作流程是Trig 脚收到一个不小于 10us 的高电平模块内部自动发 8 个 40kHz 的脉冲串同时 Echo 脚拉高脉冲遇到障碍物返回被接收后Echo 拉低。Echo 高电平持续的时间就是声波往返时间距离按下面这个式子算测量距离cm Echo 高电平时间us× 0.034 ÷ 20.034 是声速 340m/s 换算成 cm/us 的结果除 2 是因为时间包含了往返。常温下声速其实在 331 到 340m/s 之间随温度变化20℃ 时约 343m/s用 0.034 会带来 1% 左右的系统误差对垃圾桶满溢判断来说完全无所谓但如果要做毫米级项目就得补温度补偿。参数典型值说明供电5V3.3V 下回波幅度不足容易被误判量程2cm ~ 400cm小于 2cm 是盲区回波和发射混叠测量角度约 15°安装时要让声锥基本正对垃圾表面精度约 3mm这是模块标称实测受表面材质影响更大最小测量周期60ms周期太短会收到上一次的残余回波最小周期这条必须重视。很多人把测距放在主循环里狂刷结果读到一堆乱跳的值原因就是上一个脉冲串还没衰减完下一个就发出去了。60ms 是安全线也就是说超声波每秒最多测 16 次对垃圾桶完全够用别贪快。3.3 安装位置与盲区把判满的规则定死桶深假设 36cm探头装在盖板内侧正中央朝下。第一次上电空桶状态下读到的距离接近 36cm减去盖板到探头的几厘米随着垃圾堆高读数逐渐变小。判满逻辑不能写成读数小于 X 就算满因为桶里扔了个斜插的纸盒回波可能从盒顶反弹回来读数突然变小语音就误报了。我一般用双条件连续 5 次采样读数都小于 12cm且 5 次之间的波动小于 3cm才判定满溢并触发语音。12cm 这个值对应垃圾离桶口还剩 12cm留出了套袋翻边的空间。波动门限是为了过滤掉单次异常回波。另外探头周围 2cm 内不能有遮挡物盖板上的加强筋如果正好在声锥里会形成固定回波读数永远是一个偏小的常数这个坑非常隐蔽。安装角度上探头轴线尽量垂直于桶底平面。倾斜会让声锥打在内壁上形成多次反射读数比实际偏小而且不同高度的垃圾偏小量还不一样标定都标不准。装配时我用一个直角垫块定位压紧后再打胶比目测靠谱得多。4. 软件骨架状态机、捕获中断与显示刷新软件部分的价值不在写多少行而在把硬件的不确定性挡在逻辑层外面。主循环只做三件事采数据、跑状态机、刷显示任何阻塞超过 20ms 的操作都挪进中断或分时任务。4.1 主程序用一个四状态机收口系统状态定义为待机、开盖、满溢报警、消毒。上电初始化后进待机红外有效沿触发进入开盖开盖持续 5 秒无人则自动关闭并回到待机待机期间每 60ms 采一次超声满足 3.3 节的双条件就进满溢报警播报语音并把状态锁存直到超声读数回升到 20cm 以上说明垃圾被取走才解锁任何状态下按下消毒键先强制关盖再吸合继电器 30 秒然后回到待机。这个状态机的关键约束是同一时刻只允许一个执行器动作。开盖过程中不接受消毒指令消毒过程中不响应红外否则步进电机和继电器同时动作电源纹波会把 MCU 拉复位。/* main.c —— 主循环状态机标准库Keil5 MDK */ typedef enum { ST_IDLE, ST_OPEN, ST_FULL, ST_STERILIZE } sys_state_t; int main(void) { uint16_t dist_cm; sys_state_t state ST_IDLE; SystemInit(); /* 72MHz */ Delay_Init(); HCSR04_Init(); IR_Init(); Motor_Init(); /* PB4~PB7 TIM4 节拍 */ LCD1602_Init(); Voice_Init(); Key_Init(); LCD1602_ShowStr(0, 0, Smart Bin Ready ); while (1) { switch (state) { case ST_IDLE: dist_cm HCSR04_ReadCm(); /* 内部自带 60ms 节流 */ LCD1602_ShowNum(1, 0, dist_cm); /* 第二行显示实时距离 */ if (IR_Detected()) state ST_OPEN; if (IsFull(dist_cm)) state ST_FULL; if (Key_Sterilize()) state ST_STERILIZE; break; case ST_OPEN: Motor_Open(); /* 梯形加减速开盖 */ if (WaitNoPerson(5000)) /* 5s 无人则关盖 */ { Motor_Close(); state ST_IDLE; } break; case ST_FULL: Voice_Play(ADDR_FULL); /* 播“垃圾已满请及时处理” */ LCD1602_ShowStr(1, 0, FULL! ); if (HCSR04_ReadCm() 20) state ST_IDLE; /* 取走垃圾后解锁 */ break; case ST_STERILIZE: Motor_Close(); Relay_On(); /* 继电器吸合启动消毒 */ Delay_ms(30000); Relay_Off(); state ST_IDLE; break; } } }这段代码的逻辑很朴素状态只在一处被修改执行器动作集中在分支里避免边开盖边判断满溢这种互相打架的写法。参数上要注意三点WaitNoPerson(5000)的 5000 是毫秒改这个值就是改关盖延时实测 4 到 6 秒体验最好太短人还没走开盖就关了太长则白耗电ADDR_FULL是语音芯片里烧录的地址必须和烧录时填的一致满溢解锁门限 20cm 要大于判满门限 12cm中间留 8cm 的迟滞防止在临界点反复进出报警。4.2 超声波捕获10 微秒触发与 1 微秒计数测距的精度上限由定时器分辨率决定。把 TIM2 预分频设成 7172MHz 分频后计数频率正好 1MHz一个计数就是 1us对应 0.017cm远高于模块本身的 3mm 精度够用。/* hcsr04.c —— PA0 触发PA1/TIM2_CH2 捕获回波高电平宽度 */ #include stm32f10x.h static volatile uint16_t s_t_rise 0; /* 上升沿计数值 */ static volatile uint16_t s_width 0; /* 高电平宽度单位 us */ static volatile uint8_t s_done 0; /* 一次测量完成标志 */ void HCSR04_Init(void) { GPIO_InitTypeDef gpio; TIM_TimeBaseInitTypeDef tb; TIM_ICInitTypeDef ic; NVIC_InitTypeDef nvic; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); gpio.GPIO_Pin GPIO_Pin_0; /* Trig 推挽输出 */ gpio.GPIO_Mode GPIO_Mode_Out_PP; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio); GPIO_ResetBits(GPIOA, GPIO_Pin_0); gpio.GPIO_Pin GPIO_Pin_1; /* Echo 浮空输入兼 TIM2_CH2 */ gpio.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, gpio); tb.TIM_Prescaler 71; /* 72MHz/72 1MHz1 计数 1us */ tb.TIM_Period 0xFFFF; /* 65.5ms 溢出足够测 11m */ tb.TIM_CounterMode TIM_CounterMode_Up; tb.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIM2, tb); ic.TIM_Channel TIM_Channel_2; ic.TIM_ICPolarity TIM_ICPolarity_Rising; ic.TIM_ICSelection TIM_ICSelection_DirectTI; ic.TIM_ICPrescaler TIM_ICPSC_DIV1; ic.TIM_ICFilter 0x03; /* 必须开滤波否则毛刺会误捕获 */ TIM_ICInit(TIM2, ic); TIM_ClearITPendingBit(TIM2, TIM_IT_CC2); TIM_ITConfig(TIM2, TIM_IT_CC2, ENABLE); nvic.NVIC_IRQChannel TIM2_IRQn; nvic.NVIC_IRQChannelPreemptionPriority 1; nvic.NVIC_IRQChannelSubPriority 1; nvic.NVIC_IRQChannelCmd ENABLE; NVIC_Init(nvic); TIM_Cmd(TIM2, ENABLE); } /* 捕获中断上升沿记起点并切下降沿下降沿算宽度并切回上升沿 */ void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_CC2) ! RESET) { if (s_t_rise 0) { s_t_rise TIM_GetCapture2(TIM2); TIM2-CCER | TIM_CCER_CC2P; /* 改为下降沿触发 */ } else { s_width TIM_GetCapture2(TIM2) - s_t_rise; s_t_rise 0; s_done 1; TIM2-CCER ~TIM_CCER_CC2P; /* 改回上升沿触发 */ } TIM_ClearITPendingBit(TIM2, TIM_IT_CC2); } } /* 对外接口返回厘米值0 表示超时无回波 */ uint16_t HCSR04_ReadCm(void) { uint32_t guard 0; s_done 0; GPIO_SetBits(GPIOA, GPIO_Pin_0); for (guard 0; guard 72; guard) __NOP(); /* 约 10us 高电平 */ GPIO_ResetBits(GPIOA, GPIO_Pin_0); guard 0; while (!s_done guard 20000) __NOP(); /* 约 8ms 超时等待 */ if (!s_done) return 0; return (uint16_t)((uint32_t)s_width * 17 / 1000); /* us*0.034/2 ≈ us*0.017 */ }代码里有三个参数决定了它能不能用。TIM_ICFilter 0x03是输入滤波不写这一行杜邦线一长就会捕获到几十纳秒的毛刺读出来的距离全是几十厘米的乱数。触发脉冲那段用__NOP()空转大约 10us这是至少 10us短了模块不认长了也没事但别用Delay_ms(1)这类毫秒级延时去凑容易把主循环拖慢。最后的超时等待 20000 次循环是防止桶里没东西、回波彻底丢失时程序卡死在 while 里返回 0 让上层自己决定怎么处理——状态机里对 0 值应当直接忽略而不是当成很近。4.3 语音播报与 1602 刷新两条慢速链路WT588D 的一线串口时序是这么个意思DATA 脚平时保持高电平先拉低 5ms 作为起始然后按位发送 8 位数据每位用不同的高电平宽度区分 0 和 1发完再把 DATA 拉高。具体脉宽各批次手册略有差异我一般先用示波器抓一次原厂遥控信号量出高电平宽度再写进延时表比照着手册猜靠谱。/* voice.c —— WT588D 一线串口发一个字节位宽需按手头模块手册标定 */ #define VOICE_DATA_H GPIO_SetBits(GPIOA, GPIO_Pin_9) #define VOICE_DATA_L GPIO_ResetBits(GPIOA, GPIO_Pin_9) #define W_ONE_US 1200 /* “1”的高电平宽度单位 us */ #define W_ZERO_US 400 /* “0”的高电平宽度单位 us */ #define W_LOW_US 800 /* 每位之间的低电平 */ void Voice_SendByte(uint8_t data) { uint8_t i; VOICE_DATA_L; /* 起始位低电平 5ms */ Delay_us(5000); for (i 0; i 8; i) /* 低位先发 */ { VOICE_DATA_H; Delay_us((data 0x01) ? W_ONE_US : W_ZERO_US); VOICE_DATA_L; Delay_us(W_LOW_US); data 1; } VOICE_DATA_H; /* 回到空闲高电平 */ } /* 播报指定地址的语音段 */ void Voice_Play(uint8_t addr) { if (Voice_IsBusy()) return; /* BUSY 为高说明正在播直接丢帧防止串音 */ Voice_SendByte(addr); }这段代码的关键不在逻辑而在三个延时宏。它们和模块主频、晶振批次强相关同型号换一批货可能需要微调 ±100us。BUSY 判断是必须的语音芯片在播放时收到新指令会打断当前语音表现就是垃圾已……已满……很难听。LCD1602 用四线模式只占 6 根 IO刷新频率控制在 200ms 一次就够。刷太快会让整屏闪烁而且每次写指令都要等忙标志主循环会被拖住。/* lcd1602.c —— 四线模式写一个字节RSPB10 RWPB11 ENPB12 */ static void LCD_WriteNibble(uint8_t nib, uint8_t rs) { GPIO_WriteBit(GPIOB, GPIO_Pin_10, rs ? Bit_SET : Bit_RESET); GPIO_ResetBits(GPIOB, GPIO_Pin_11); /* RW0 只写 */ GPIO_WriteBit(GPIOA, GPIO_Pin_4, (nib 0x01) ? Bit_SET : Bit_RESET); GPIO_WriteBit(GPIOA, GPIO_Pin_5, (nib 0x02) ? Bit_SET : Bit_RESET); GPIO_WriteBit(GPIOA, GPIO_Pin_6, (nib 0x04) ? Bit_SET : Bit_RESET); GPIO_WriteBit(GPIOA, GPIO_Pin_7, (nib 0x08) ? Bit_SET : Bit_RESET); GPIO_SetBits(GPIOB, GPIO_Pin_12); /* EN 上升沿锁存 */ Delay_us(2); GPIO_ResetBits(GPIOB, GPIO_Pin_12); Delay_us(50); /* 等内部处理不能省 */ } void LCD_WriteByte(uint8_t dat, uint8_t rs) { LCD_WriteNibble(dat 4, rs); /* 先高四位 */ LCD_WriteNibble(dat 0x0F, rs); /* 再低四位 */ }Delay_us(50)这一行是很多人省掉的地方省了就会出现偶尔丢字符上电花屏因为 1602 内部执行指令需要时间四线模式还要两次锁存。这种问题不会稳定复现最难查。5. 避坑与排查这些翻车点我替你踩过5.1 电气层复位、抖动与误触发现象一手一靠近盖板开到位的同时单片机重启屏幕回到初始界面。原因基本可以锁定在电机换相时的电流冲击。ULN2003 驱动步进电机四相轮流导通每次切换都会在电源线上产生尖峰如果电机和 MCU 共用一条细地线地电位抬升会把 MCU 的 NRST 拉到阈值以下。解决分三步电机电源单独从电源入口取地线单点汇合ULN2003 的 COM 端并 1N4148 续流MCU 的 NRST 脚并 0.1uF 到地。做完这三步还没好就量一下 5V 轨在开盖瞬间跌到多少跌到 4.3V 以下说明电源功率不够换 2A 的适配器。现象二盖板开合时咔咔响走一半停住或者丢步。28BYJ-48 是减速步进启动频率上不去。如果代码里直接连续发脉冲第一拍就丢步表现就是原地抖。正确做法是梯形加减速前 20 步每步间隔从 8ms 递减到 2ms匀速段保持 2ms末尾 20 步再递增回去。这个改动只多十几行代码但盖板的动作质感完全不同。另外步进电机的四相线序接错也会抖红线和各相的顺序要按说明书对接反了电机只会原地振动不转。现象三白天好好的晚上灯一开桶盖自己反复开合。红外阈值被环境光顶穿了。RPR220 虽然有滤光片但白炽灯和射灯的红外分量很足。解决方式是软件时间滤波加物理遮光连续 3 次采样为低才认定有人同时给探头加一个 3mm 长的黑色热缩管做遮光筒只留正向视场。这个改动成本几毛钱效果比调电位器明显得多。现象四继电器一吸合LCD1602 显示乱码。继电器线圈是感性负载断电瞬间的反向电动势会顺着控制线窜回 MCU 的 IO。除了线圈并续流二极管控制线最好走远离电机的路径必要时在继电器驱动三极管的基极串一个 1k 电阻限流。乱码还有一个可能原因是 1602 的 RW 脚悬空飞线时忘了接读操作时序错乱同样表现为随机花屏。5.2 逻辑层误报满、盖板弹跳与语音串音现象五桶是空的语音却一直喊垃圾已满。先看探头是不是被盖板加强筋挡住了这种情况读数会固定在一个偏小的常数上。用串口把每次测距值打出来如果一直是 9cm 左右不动就是固定回波。其次是量一下供电HC-SR04 在 3.3V 下工作回波脉宽会变窄甚至丢失HCSR04_ReadCm返回 0如果上层把 0 当成很近处理就会误判满溢。所以判满函数里必须显式排除 0 值和大于量程的值。现象六人站在桶前不动盖板开了关、关了开。红外输出在临界距离上抖动或者关盖延时太短。检查顺序是先把红外判定改成计数器滤波再看WaitNoPerson的延时值是不是只有 1 到 2 秒。我一般会把红外灵敏度调到手在 20cm 处触发比调到最灵敏要稳得多因为最灵敏意味着人走过走廊都会被误触发。现象七语音播报只播了半句就断或者干脆不响。两个原因。一是 BUSY 判断缺失主循环每 60ms 就发一次播放指令芯片被反复打断二是 DATA 线的位宽和模块不匹配发出去的数据被芯片判成无效帧。排查方法是先断开 MCU用按键手动触发一次语音确认模块本身和喇叭没问题再去查时序。这个先分离故障域的习惯能省掉大量时间。现象八消毒键按一下继电器吸合好几次。按键抖动加上消毒流程里没有做状态锁。机械按键的抖动时间通常在 5 到 20ms软件上要 20ms 消抖同时进入消毒状态后应当屏蔽按键输入直到流程结束。另外消毒用的紫外灯管不能在人靠近时点亮这一步必须在硬件上和软件上双重保证软件上消毒前强制关盖硬件上把灯管装在盖板内侧并有行程开关串联。6. 验证方法把能用变成几个能测的数到这一步功能都通了但能用和稳定之间还差一轮量化验证。我一般不看感觉还行只看下面这几个数。指标测试方法合格线翻盖响应时间手进入检测区到盖板完全抬起秒表或高速摄像≤1.2s关盖延时误差手离开到盖板启动关闭5s±0.5s满溢判定一致性桶内放不同形状垃圾 20 次误报 ≤1 次连续开合寿命自动循环 200 次无丢步、无复位待机电流万用表串在电源入口≤80mA含屏背光翻盖响应时间这项最容易暴露问题。如果测出来超过 1.5 秒八成是超声测距阻塞了主循环因为HCSR04_ReadCm里有最长 8ms 的等待加上 60ms 的节流本身不该有明显延迟真正的元凶往往是 LCD 刷新太频繁或者语音播报时用了死延时。把所有延时都改成状态机分时之后这个数会明显改善。满溢一致性测试有个土办法分别用纸团、塑料袋、平整的纸盒三种填充物各测若干次记录误报率。塑料袋最容易被漏检因为它吸声回波弱读数会偏大表现为明明满了却不报警。如果这一项不过关就把判满门限从 12cm 提高到 15cm用一点余量换稳定性这个取舍在实际产品里很常见。还有一项容易被忽略断电重启后的状态恢复。消毒进行到一半断电重新上电后继电器必须处于断开状态盖板必须处于关闭状态。这要求在初始化函数里先把继电器和电机引脚全部置为安全电平再去初始化外设。没有这一步上电瞬间继电器可能吸合紫外灯直接亮起来是很危险的事情。踩过最疼的一次坑是整机装配完之后才发现超声探头被盖板的塑料卡扣挡了两厘米空桶读数永远是 11cm语音从早叫到晚。拆开重新定位花了一晚上。从那以后我每次装探头都会先不装盖板单独跑一遍测距把空桶、半满、满溢三个状态的距离值全部记在纸上装完再复测一遍两次数据对得上才算过。这个习惯后来帮我拦下过好几次结构干涉的问题值得你也在自己的板子上强制走一遍。希望帮到你。本文还有配套的精品资源点击获取