
站在校园或者社区里你可能已经见过不少“旧衣回收箱”“纸箱回收柜”但真正针对旧书做自助回收的设备其实不多。这个项目——“基于STM32的自助旧书回收装置系统设计”——就是补上这块短板的尝试用一块常见的STM32单片机做主控配合称重传感器、显示屏、通信模块和简单的机械舱门做出一台能够自动称重、自动计费、自动上传数据的回收终端。用户把闲置旧书投进去系统按重量结算积分或者金额后台统一管理投放记录。这个项目最大的价值在于“开源低成本”整套硬件成本可以控制在两百元以内软件代码全部开放非常适合嵌入式方向的学生做毕业设计也适合环保回收类创业团队快速验证流程或者竞赛项目直接拿去改。接下来我会把整套系统的设计思路、硬件选型、软件架构、联调过程以及踩过的坑完整拆开来讲内容会偏向实操尽量让你看完之后能直接照着做出一台来。1. 项目整体设计与思路拆解1.1 自助旧书回收装置要解决的三个核心问题先别急着谈硬件我们先想清楚这台设备到底要干嘛。自助回收装置本质上是一个无人值守的收件终端它必须回答三个问题第一用户怎么把书交给你第二你怎么知道交了多少第三这笔交易怎么记录和结算。对应到设备功能上就变成了三个核心模块投递舱门控制模块负责“收书”称重模块负责“计量”通信与显示模块负责“告知用户结果并上传数据”。三块缺一不可顺序也不能乱——用户开门、放入旧书、关门、系统称重、屏幕显示重量与积分、数据上传到云端这一整套流程下来才算完成一次完整的回收动作。这是我做这个项目时最先想清楚的一点自助回收设备不是“智能垃圾桶”它本质上是一台“无人结算终端”。所以整个设计都围绕一个核心状态机运转任何时刻系统只在做一件事要么等待用户要么正在处理投递要么正在上传数据。这种“单线程”思路在嵌入式开发里非常宝贵它让程序逻辑变得极其清晰也极大减少了 bug 出现的概率。1.2 方案选型为什么最终落在 STM32 上设备的核心功能确定之后接下来就是主控选型。我当初在树莓派、ESP32、STM32 三者之间犹豫过一阵最终选了 STM32F103C8T6也就是大家俗称的“蓝丸”核心板。原因有三个。第一是成本。树莓派一块板子就要两三百元加上 SD 卡、电源、外壳单台设备光主控部分就要接近四百元。而 STM32F103C8T6 核心板在电商平台的散片价格不到十元批量做十台设备省下来的钱足够覆盖传感器和通信模块的费用。第二是稳定性。自助回收设备经常部署在户外或半户外的环境供电质量不稳定温度湿度变化大。树莓派这类完整 Linux 系统对这种环境比较敏感意外断电后系统文件容易损坏需要人工介入恢复。而 STM32 是单片机没有文件系统需要维护上电直接运行固件断电后重新上电也能在几百毫秒内恢复工作非常适合无人值守场景。第三是外设资源。STM32F103C8T6 虽然价格便宜但该有的接口一个不少多路 USART 可以接 WiFi 模块和调试串口I2C 和 SPI 可以接显示屏和存储芯片多路 ADC 可以留作备用传感器的扩展接口定时器资源也足够驱动蜂鸣器、舵机这类执行器件。对于称重、显示、通信、门控这四类功能来说它的资源可以说绰绰有余。1.3 系统架构设备端、云端、用户端三方怎么协同整台设备的系统架构其实可以分成“端-云-用”三层。最底层是设备端也就是 STM32 主控节点它负责感知和动作检测舱门状态、读取称重传感器数据、驱动显示屏、控制电磁锁同时通过串口与 WiFi 模块通信。中间层是云端负责设备接入、数据存储和业务逻辑处理我使用 MQTT 协议配合 EMQX 这类开源 Broker设备端数据上报后云端规则引擎自动计算积分数值并将结果推送到用户端。最上层是用户端通常是一个微信小程序或者 APP用户扫码后就能看到自己的投放记录和积分余额。这里有一个关键点值得展开设备端的 STM32 并不直接和用户端通信它只和云端保持长连接。这样做的好处是“前端设备只管采集和上报”业务逻辑全部由云端处理。比如结算规则是每公斤积分 100未来如果想调整为 150只需要改云端的规则配置不需要现场升级每一台设备的固件。这种解耦思维在嵌入式物联网项目里非常重要尤其是在设备已经部署到多个点位之后远程改业务和现场改固件的成本天差地别。2. 硬件设计细节与关键器件选型2.1 主控之外称重、显示、通信模块怎么搭配主控确定之后其余模块的选型就根据功能需求逐个展开。称重模块我选用的是电阻应变片式压力传感器配合 HX711 高精度 24 位 ADC 芯片。传感器量程选择 50kg这个量程适合旧书回收场景——常见的教材和课外书单本重量通常在 0.2kg 到 1.5kg 之间一次投递三四本也不会超过 10kg50kg 量程留足了余量又保证了小重量下的分辨率。HX711 内部自带了仪表放大器可以将传感器输出的毫伏级差分信号放大到 ADC 能够准确采样的范围同时它和 STM32 的接口只需要两个 GPIO——一个 SCK 时钟引脚和一个 DT 数据引脚接线极为简单。显示模块这个设计我做了一个折中。考虑到设备部署在户外就选用了 1.3 英寸 OLED 显示屏因为 OLED 自发光特性保证了在黑暗中也能清晰显示而且工作温度范围宽寿命也长。屏幕用来显示当前的称重数值、预估积分和操作提示配合一个蜂鸣器做声音反馈基本可以满足用户交互需求。通信模块采用 ESP8266 串口 WiFi 模块。它是这个项目里唯一需要认真对待供电的模块因为 WiFi 发射瞬间的电流峰值可以达到 300mA 以上如果供电不足会导致模块反复重启。所以我在电路板上单独给它留了一路 AMS1117-3.3 稳压芯片供电并且在电源输入端并联了 470 微法电解电容和 100 纳法陶瓷电容实测下来模块工作非常稳定。2.2 HX711 称重电路原理与 PCB 布线要点如果你只是做实验、搭一个开发板级别的原型那 HX711 模块直接插杜邦线就能工作。但如果你打算把它做成真正可以部署的设备PCB 布线有几点需要特别注意我在这里展开讲一下。第一传感器信号线必须使用差分走线并且远离电源线和控制线。电阻应变片传感器输出的是毫伏级信号任何一点干扰都会直接影响称重精度。我在打样时犯过一个错误为了追求板子尺寸小把 HX711 的模拟输入走线贴着 ESP8266 的电源走线放置结果 WiFi 模块一发射称重数据就跳得厉害。后来重新布线把模拟部分和数字部分做了物理隔离问题才解决。第二HX711 的 AVDD 模拟电源建议单独滤波。HX711 内部 ADC 的参考电压直接取自 AVDD如果这个电压不稳定ADC 的转换结果也会跟着波动。我在 AVDD 引脚附近放置了一颗 10 微法钽电容和一颗 0.1 微法陶瓷电容并联这个组合对抑制低频和高频噪声都很有效。第三称重传感器本身需要 5V 激励。HX711 模块通常自带稳压电路可以从模块的 VCC 引脚输入 5V再通过模块上的分压电路给传感器提供稳定的激励电压。我建议不要直接从 STM32 的 3.3V 给传感器供电因为传感器灵敏度与激励电压成正比3.3V 供电会损失一部分信号幅度不利于小重量工况下的测量精度。2.3 电磁锁、舵机与电源管理机械执行部件没那么简单设备的投递舱门和回收箱体内的挡板我分别设计了电磁锁和舵机来控制。电磁锁用于实现“用户按按钮→舱门解锁→投入旧书”这个动作舵机则用于投递完成后将书从过渡仓翻入回收箱体实现二次防误取功能。电磁锁是一个不太起眼但坑很多的器件。常见的小型电磁锁工作电压 12V通电瞬间电流可能达到 1A 以上保持吸合时电流稍小但也有几百毫安。如果直接由 STM32 的 GPIO 控制那是绝对不行的必须通过 MOSFET 或者达林顿管驱动。我使用的是 AO3400 N-MOS 管3.3V 的 GPIO 可以直接驱动它打开同时在电磁锁两端反向并联了一个 1N4007 二极管作为续流二极管用于吸收断电瞬间感性负载产生的反向电动势保护 MOSFET 不被击穿。舵机则要注意 PWM 信号和控制时序。我选用的 SG90 舵机工作电压 5V信号线由 STM32 的定时器输出 PWM 控制脉冲宽度从 0.5ms 到 2.5ms 对应 0 度到 180 度。这里有一个很容易忽略的点舵机在堵转时会持续大电流如果不能及时判断到位轻则烧舵机重则导致整个电源系统电压跌落。所以我在程序中加入了到位检测逻辑——舵机到达指定角度后立即停止输出 PWM 信号避免长时间堵转。电源管理这块是整个硬件设计的基石。系统包含 5V传感器、舵机、OLED、3.3VMCU、WiFi 模块逻辑部分、12V电磁锁多种电压轨我采用 12V 直流电源输入通过 MP1584 降压模块输出 5V再通过 AMS1117 线性稳压输出 3.3V。这里要特别提醒一句AMS1117 这种线性稳压芯片输入输出电压差乘电流就是热损耗如果用 12V 直接降到 3.3V 给 ESP8266 供电差 8.7VWiFi 发射电流一上来芯片就会烫得厉害。所以我坚持“两级降压”的方案先 DC-DC 降到 5V再线性稳压到 3.3V既保证了转换效率也保证了输出纹波足够小。3. 软件架构与核心功能模块实现3.1 固件分层驱动层、业务层、协议层互不干扰嵌入式开发的初学者最容易犯的错误是把所有代码堆在一个 main.c 文件里几百行代码写完调试时简直灾难。这个项目因为要同时处理称重、显示、通信、门控逻辑复杂度不算低所以我从一开始就做了分层设计。整体软件架构分成三层。最底层是驱动层封装了 HX711 读取、OLED 刷新、舵机 PWM 控制、ESP8266 串口收发等具体硬件的操作函数。中间层是业务层负责处理整个回收流程的状态机也就是判断当前该干什么、下一步该切到哪个状态。最上层是协议层负责构造和解析与云端通信的 MQTT 报文把称重结果封装成 JSON 格式的消息发布到指定主题。这样做的好处非常直接如果后来你换了 4G 模块只需要修改驱动层里和串口通信相关的函数业务层和协议层完全不用动。如果云端协议从 JSON 改成更紧凑的 TLV 格式只需要改协议层的打包代码底层硬件驱动不受影响。这种模块化的思想让项目从一开始就具备良好的可维护性。3.2 五大状态机从待机到投递完成的完整流程我的整个设备运行逻辑用一个状态机就可以完整描述。状态机的设计参考了自动售货机的经典模型一共分五个状态空闲待机、舱门解锁等待投递、称重稳定、数据上报、系统复位。空闲待机状态下屏幕显示“请将书籍放入舱门按下开始按钮”的提示系统功耗最低所有外设处于待命状态。当用户按下投递按钮系统切换到舱门解锁状态电磁锁打开屏幕显示“舱门已开请放入书籍”。这里有一个细节为了让用户有足够时间完成投递操作同时避免人为恶意的长时间占用我设置了一个 30 秒的开门超时时间超时后自动关闭舱门并回到初始状态。用户上门关门后系统进入称重状态。称重并不是读取一次传感器数值就算完事而是需要连续采集多组数据做滤波处理后才能得到稳定的重量。我这里是连续读取 10 次去掉最大值和最小值后取平均值然后判断两次平均值的差值是否小于一个阈值比如 5 克小于阈值则认为重量已稳定进入数据上报状态。这个“稳定判断”是整个流程里最容易被忽略的细节但它对用户体验的影响非常大——如果判断太早读到的是书还在晃动的瞬时值如果判断太晚用户会觉得设备反应迟钝。数据上报状态STM32 通过串口把称重数据发给 ESP8266ESP8266 以 MQTT 协议发布到云端主题。云端收到消息后根据预设的积分规则计算结果再把“重量多少”“积分多少”通过另一个主题推送给设备端设备在 OLED 屏幕上显示给用户。最后系统进入复位状态舵机翻转挡板把旧书拨入回收箱体舱门重新锁定所有状态变量清零回到空闲待机。用一个伪代码来描述主循环就是while (1) { switch (current_state) { case STATE_IDLE: show_guide(); if (button_pressed()) { open_door(); current_state STATE_WAIT_BOOK; } break; case STATE_WAIT_BOOK: if (door_closed()) { start_weight_measure(); current_state STATE_WEIGHING; } else if (timeout_30s()) { close_door(); current_state STATE_IDLE; } break; case STATE_WEIGHING: if (weight_stable()) { publish_result(); current_state STATE_UPLOADING; } break; case STATE_UPLOADING: if (upload_success()) { display_result(); current_state STATE_RESET; } break; case STATE_RESET: reset_device(); current_state STATE_IDLE; break; default: current_state STATE_IDLE; break; } }这个循环本质上是一个“事件驱动轮询”的模式虽然没有使用操作系统但从实时性角度来看完全足够。整个流程里面没有地方需要微秒级响应最严苛的也就是 WiFi 上报过程的几百毫秒超时检测这在单片机的主循环里用简单的计数器就能实现。3.3 MQTT 数据协议字段设计决定后续扩展空间通信协议的设计直接决定了这套系统后续的业务扩展能力。我采用 MQTT 作为设备与云端之间的通信协议原因很简单MQTT 基于发布/订阅模式设备端和云端解耦有现成的开源 Broker 可用同时在弱网环境下表现也很好。设备端上报数据格式我设计如下{ device_id: RECYCLE_001, type: weight_report, timestamp: 1710907200, weight_g: 2350, status: 0 }云端推送结算结果的格式如下{ device_id: RECYCLE_001, type: settlement, order_id: ORD202403201001, weight_g: 2350, points: 235, message: 投递成功获得积分 235 }字段设计上有几个小心思值得说明。device_id 是每台设备的唯一标识方便云端管理多台终端。type 字段用于区分消息类型后续即使要增加“故障上报”“设备心跳”等新消息类型也不需要改消息结构。timestamp 统一采用 Unix 时间戳与云端服务器的时区无关避免了时区转换的麻烦。weight_g 以克为单位而不是千克因为整数比浮点数更适合物联网设备传输和数据库存储也减少了计算误差。断线补传机制自助设备的网络环境不会一直稳定尤其部署在楼道、地下空间等位置时WiFi 信号可能时好时坏。我的做法是在 STM32 的 Flash 中划出一块区域做数据缓存每次称重完成后先写入缓存再尝试上报失败上报成功后清除缓存。每次设备启动时先检查缓存区是否有未上报的数据如果有优先补传补传成功后才开始接收新的投递任务。这个机制虽然实现简单却大大提升了系统的可靠性。我在实际测试中模拟过连续断网多次的情况恢复网络后设备能够自动把积压的数据按时间顺序补传完毕云端数据一条没丢。4. 实操过程与核心环节实现4.1 从零搭建最小系统验证与外设驱动调试整个联调过程我建议不要一次性把全部硬件都接上而是分阶段进行。第一步是搭最小系统——STM32 核心板通过 ST-Link 连接电脑用 STM32CubeMX 生成一个基础工程先让板载 LED 点亮。这一步的主要目的是验证开发环境、下载器、芯片本身都没问题。接下来是串口调试。STM32F103C8T6 有多个 USART我在设计上做了引脚分配USART1 用于和 ESP8266 通信USART2 用 USB 转 TTL 接电脑调试。调试串口非常重要因为它是你观察系统内部状态的唯一窗口。我在所有关键状态切换的地方都加了一行 printf 日志输出比如“door opened”“weight stable: 2350g”“mqtt published”等联调时就能清楚看到系统当前运行到哪一步、卡在哪一步。然后是逐个外设的驱动验证。先接 OLED确认屏幕能够正常显示再接 HX711读取传感器原始数据并换算成重量最后接电磁锁和舵机验证开锁和翻板动作。每个外设单独调试通过之后再合到一起跑整机流程这样一旦出问题定位范围会非常小。4.2 称重标定这步偷懒后面一定翻车我在这里专门把“称重标定”拿出来讲因为它看起来简单实际操作中却特别容易出问题。很多人以为把 HX711 读取到的原始数据直接乘一个固定系数就是重量了其实不然传感器、放大器和 ADC 的组合存在零点和增益误差必须经过标定才能得到准确的重量值。标定的方法不复杂但需要耐心。先用两个已知重量的砝码比如 1kg 和 5kg分别记录 HX711 读到的原始值。假设空载原始值为 Z1kg 砝码对应原始值为 A15kg 砝码对应原始值为 A2那么 1kg 对应的原始增量大约为(A2 - A1) / 4标定的比例系数就是1000 / ((A2 - A1) / 4)单位是克每原始单位。这个系数换算后写入程序称重时就用(current_raw - Z) * scale算出当前重量。实际操作中还有两个坑说出来你可能都不信。第一秤台的水平姿态会影响结果——传感器安装倾斜会导致受力不均匀造成读数偏差。第二接线端子接触电阻的变化也会导致零点的漂移。所以我在结构上加了三个可调高度的支撑脚并且在软件里实现了“开机自动去皮”功能——每次设备上电时会自动把当前传感器读数作为零点基准这样只要设备在两次投递之间没有被挪动过称重就能一直保持准确。4.3 整机联调现场从本地模拟到云端闭环等所有外设驱动都调通之后就进入了整机联调阶段。我建议先做“本地模拟”——不接 WiFi 模块在电脑端用串口助手模拟 ESP8266 的应答。设备上报数据时串口手动手动回一条构建好的 JSON 结算消息验证 STM32 这边能够正确解析并显示积分结果。这样可以把问题控制在单片机这一侧避免一开始就混合网络问题导致排查困难。本地模拟通过后再接入真实的 ESP8266 和 MQTT Broker。我使用的是腾讯云服务器上部署的 EMQX Broker设备端通过 WiFi 连接互联网。为了让设备在实验室连 WiFi 方便调试我留了一个预留接口允许通过串口命令动态配置 WiFi SSID 和密码而不是把 WiFi 信息写死在固件里。这样设备部署到现场后维护人员不需要重新刷固件直接用串口工具发几条 AT 指令就能修改网络配置。实际联调的时候我发现一个现象设备上报称重数据后云端返回结算结果需要 1 到 3 秒的时间。这个延迟其实是正常的因为中间经过了 WiFi 传输、Broker 转发、规则引擎处理等多个环节。但用户在现场会觉得“等太久”所以我做了两个优化一是在设备端上报成功后就立刻显示“称重成功正在结算...”给用户一个明确的反馈二是把云端结算规则的复杂度降低尽量用轻量级的脚本处理把端到端延迟压缩在 1 秒以内。4.4 开源项目的文档与二次开发交付既然是开源项目代码只是交付物的一部分更重要的还有文档和工程化准备。我到后期整理资料时专门花了和写代码一样多的时间来完善以下内容README 文档写明项目背景、硬件清单、接线图、编译烧录步骤硬件目录下放 PCB 的原理图、Gerber 文件和 BOM 清单方便别人直接去打样软件目录按模块建好文件夹并写了头文件注释另外还录了两段视频一段是演示整机投递流程一段是讲解如何从零开始编译烧录固件。我特别建议你在开源项目里加一个引脚映射表如下功能模块STM32 引脚说明HX711 SCKPB13时钟信号HX711 DTPB12数据输出OLED SCLPB10I2C 时钟OLED SDAPB11I2C 数据SG90 舵机PA8PWM 输出电磁锁 MOSFETPA9GPIO 高电平开锁投递按钮PA0外部中断输入蜂鸣器PA10GPIO 高电平触发ESP8266 TXPA2接 USART1 RXESP8266 RXPA3接 USART1 TX这个表格看起来简单但对其他人理解项目、复现项目帮助巨大。很多开源项目代码写得不错但缺少这种“接线速查表”导致别人想二次开发时还要对着原理图一个个查引脚体验很差这也是我踩过别人开源项目的坑之后总结出来的经验。5. 常见问题与排查技巧实录5.1 称重偏漂移排查了半天问题出在震动整机联调时我遇到的最头疼的问题是称重数值不稳定尤其是在设备空载静止的情况下显示重量会在 ±20 克之间来回跳。这绝对无法接受因为旧书回收的计费基础就是重量误差 20 克意味着用户积分结算不准确。排查思路是从硬件到软件逐层排除。首先我用万用表测量了 HX711 模块的电源电压5V 供电正常纹波在可接受范围。然后我用示波器观察了数据引脚 SCK 和 DT 的波形发现信号完整性没有问题。直到我把传感器从外壳上取下来悬空放在桌面上测试数值立刻稳定了——问题出在传感器的安装方式上。我原来的结构设计把传感器直接安装在箱体底部而箱体旁边就是舵机安装位置。舵机每次动作都会引起箱体共振共振传导到传感器上就形成了持续的低频干扰。解决办法是在传感器和箱体之间加了一层 5mm 厚的橡胶减震垫同时在软件上提升了滤波强度。经过这两项处理后空载漂移降到了 ±3 克以内完全满足使用需求。5.2 通信掉线ESP8266 的 TCP 连接到底怎么保持ESP8266 模块在长时间运行后偶尔会掉线而且掉线后不会自动重连。这是所有基于 ESP8266 的项目都会遇到的问题它的 TCP 长连接在路由器重启、AP 切换、长时间空闲后都有可能会断开。固定写法里我会周期性地发送一个心跳包我设置的是 30 秒云端收到心跳后回复 pong如果三次 pong 都没有收到STM32 认为连接已经断开主动发起重连。但这里有一个细节AT 指令模式的 ESP8266 在被动接收数据时单片机需要不断去读取串口缓冲区并解析数据。如果恰好在上报称重结果的当口 WiFi 断开了那这条消息就发不出去的。所以我在协议层加了消息确认机制——设备端上报数据后等待云端的 ACK 消息如果 10 秒内没有收到 ACK设备自动将这条消息存入待补传队列并在下次重连后重新发送。5.3 屏幕显示花屏与乱码OLED 也会有“电压门槛”OLED 屏幕在正常供电时显示效果很好但我在测试中发现一个特殊现象当设备处于低功耗待机模式时如果用户按下按钮触发开锁电磁锁瞬间工作导致 3.3V 电压轻微跌落OLED 屏幕偶尔会出现花屏甚至花白的情况。深入分析后我意识到这是因为 AMS1117 的输出端虽然有电容滤波但电磁锁工作时的大电流还是会瞬间拉低整个电源轨。虽然 STM32 因为工作电压范围宽2.0 到 3.6V不受影响但 OLED 模块里的驱动芯片对电压更敏感一旦低于工作阈值就会输出异常。解决方案有两个硬件上把电磁锁的地线单独走不经过 OLED 和 MCU 的地平面减少共地阻抗引起的电压波动软件上在 OLED 驱动程序中加入初始化状态检测每次异常后可以自动重新初始化屏幕。我两个方案都采用了双重保险之后花屏问题再没出现过。5.4 结构安全问题不要忽略防火、防水和防夹手最后聊一点和电路无关但和产品落地强相关的事情。自助回收设备一般放在公共区域安全是底线。我在项目中做了几项设计来保证安全。防夹手舱门内壁安装了光电对射传感器检测到有物体遮挡时电磁锁不会锁门防止夹伤用户手指。防水防尘在箱体接缝位置使用了橡胶密封条进风口和散热口都加装了不锈钢丝网避免灰尘和小动物进入影响电路安全。防火回收箱体内部加装了一个温度传感器检测到温度异常时设备会自动切断舱门电源并拨打报警电话通过云端通知管理员。这些东西虽然不属于 STM32 开发的范畴但做产品级项目时它们的重要性一点都不低于代码本身。我见过很多开源项目只关注“能跑起来”却忽略了“能安全地跑很久”这样的项目只能停留在实验室阶段无法真正走向市场。如果你打算在这个项目基础上继续扩展我建议可以考虑两个方向。一是给设备增加 RFID 读者证识别功能用户在校园场景可以直接刷一卡通完成身份验证省去手机扫码的步骤二是把回收数据接入到学校的二手教材流转平台让回收的旧书不只是被卖掉而是能够被循环利用真正实现绿色校园的闭环。当然这些扩展都是后话了先把这套基础系统做出来跑通整条链路你的收获会比想象中大很多。