做了几年嵌入式项目,手上攒了不少STM32的开发板和各种外设模块,前阵子朋友养鸽子遇到个麻烦事,说每天定时喂食、清理饮水这些琐碎工作占用时间太多,出差几天更是没人管,问我能不能搞个自动化的东西。于是就有了这个基于STM32的智能鸽子驯养系统。这个项目从需求分析、电路设计到实物搭建、软件调试,前后花了两周多,我把设计思路和实操过程整理成这篇博客,希望能给打算用STM32做类似小型自动化设备的朋友一些参考。
1. 项目概述与设计思路
1.1 这个项目解决了什么问题
鸽子饲养的核心工作可以归纳成四件事:定时定量喂食、保证饮水充足、观察鸽子状态、记录训练数据。纯人工做这些事,短期没问题,但一旦遇到出差、上班时间不固定、或者鸽子数量比较多,就容易出现漏喂、断水的状况。鸽子的进食规律被打乱,直接影响训练效果和健康状态。
这个系统的定位就是把这四件事自动化。主控用STM32,核心功能包括: 定时定量自动投喂、水槽水位实时监测、料仓余量检测、 环境温湿度采集、鸽子的进出感应记录,以及异常状态的本地告警。数据可以存储在本地,也可以通过串口或USB导出到电脑,方便做长期训练记录。
在设计之初我就明确了一点:这不是一个实验室里的演示板,而是要在实际鸽舍环境里长期运行的设备。所以整个系统的设计都围绕"可靠、省电、易维护"这三个关键词展开。鸽舍环境粉尘大、湿度高,还有老鼠和虫子可能咬线,这些因素在选型和结构设计时都必须考虑进去。
1.2 为什么选STM32做主控
选STM32而不是51单片机、Arduino或者ESP32,我主要基于这几个考虑。
先看算力和资源。STM32F103C8T6这个型号很有代表性:72MHz主频、64KB Flash、20KB RAM,跑一个多任务状态机绰绰有余。相比51单片机,它能轻松处理超声波测距的时间测量、HX711的24位数据解码、步进电机的脉冲控制同时进行,不需要频繁用中断去抢占CPU,程序结构可以写得更干净。
再看外设资源。这个系统需要的接口类型很杂:超声波模块要定时器输入捕获,步进电机要定时器输出比较,称重模块要软件模拟I2C或GPIO时序,温湿度传感器可以用单总线或I2C,RS485通信要UART加方向控制。STM32的定时器通道、USART、I2C、SPI、ADC这些外设数量刚好能满足需求,不用额外扩展芯片。
然后是开发生态。STM32的文档、库函数、示例代码非常丰富,遇到问题基本都能在社区找到答案,CubeMX配置工具也能把初始化代码的重复劳动降到最低。对于需要长期维护的项目来说,选择生态成熟的主控,等于给自己减少后续隐患。
另外还有一点,STM32的抗干扰能力在同价位芯片里表现算好的,供电范围够宽,IO口驱动能力也够,配合外部看门狗电路能实现真正的无人值守。这些特性对于鸽舍这种非理想环境非常重要。
1.3 系统总体架构
整个系统按功能拆成五个模块,各模块之间通过明确的接口交互:
第一是主控模块,负责逻辑调度、传感器数据采集、执行机构控制和人机交互,是整个系统的大脑。
第二是电源模块,支持12V适配器供电和4.2V锂电池备份供电,内部通过BUCK电路和多路LDO输出5V、3.3V,另有电池充电管理电路。
第三是传感模块,包括超声波测距传感器用于料仓余量检测和水位检测,称重传感器用于鸽子体重和进食量记录,温湿度传感器用于鸽舍环境监测,红外对管用于鸽子进出鸽舍的计数。
第四是执行模块,主要是步进电机驱动的喂食器,以及备用的风机组用于鸽舍通风。
第五是通信模块,包含RS485总线和USB接口。RS485用于扩展连接其他设备,USB用于数据导出和固件升级。
软件层面用状态机做总体调度,不同的工作状态(待机、喂食、清扫、告警)之间切换,每个状态内部再细化成子状态。这个设计在后面第3章会详细展开。
2. 硬件电路设计与实现要点
2.1 电源系统的分层设计
电源是整个系统可靠性的基础,我在这个项目里花了最多心思的电路就是电源。
首先是输入部分。主路采用12V/2A适配器,经过自恢复保险丝和TVS二极管防护后,进入BUCK降压电路,把电压降到5V给步进电机驱动、传感器模块供电,再经过LDO降到3.3V给STM32最小系统供电。
BUCK电路我用了MP1584EN这个芯片,它的输入范围是4.5V到28V,开关频率最高1.5MHz,最关键的在于它体积小、外围元件少、效率能做到90%以上,适合这种空间有限的场景。反馈电阻的取值决定了输出电压:Vout = Vref × (1 + R1/R2),MP1584的参考电压Vref是0.6V,要得到5V电压时,R1取47kΩ、R2取6.8kΩ,算出来大约5.02V,基本符合需求。电感选22μH,输出电容用两个22μF的陶瓷电容并联,实测纹波能控制在30mV以内。
然后是给STM32供电的3.3V部分。我用了一片AMS1117-3.3 LDO,输入接BUCK输出的5V,输出端接10μF钽电容和0.1μF陶瓷电容并联滤波。LDO虽然在效率上不如BUCK,但它纹波低、电路简单,给MCU这种对电源质量敏感的负载用更稳妥。
电池备份电路是我重点考虑的部分。如果12V适配器断电,系统需要切换到锂电池继续工作一段时间。锂电池标称3.7V,最高充电电压4.2V,这个电压直接给STM32不够,所以我用了一个升压电路,把电池电压升到5V,再加到AMS1117上。升压芯片用了MT3608,电路接法也是照数据手册推荐的来,反馈电阻用10kΩ和100kΩ组合,输出5V,实测效率在85%左右。
防倒灌电路一定要提。之前我做过一个项目,用USB和电池双路供电,结果USB断开后电流从电池倒灌到USB口,把USB设备烧了。这次加了PMOS管隔离方案:PMOS的源极接电池输出,漏极接负载,栅极通过电阻接到系统主供电上。当主供电存在时,栅极为高电平,PMOS关断电池通路;主供电消失时,栅极被下拉电阻拉低,PMOS导通,电池开始供电。这样逻辑上就保证了主电源和电池不会同时导通。
充电管理用的是TP4056,这是最常见的4.2V单节锂电池充电芯片。外围很简单:第2脚接VBAT,第3脚接VCC,第4脚和第5脚接USB输入,第6脚和第7脚作为充电指示灯驱动。关键是充电电流设置电阻PROG,1.2kΩ对应1A充电电流,2kΩ对应580mA,我实际用了2.2kΩ把电流限制在500mA左右,可以降低芯片发热。TP4056工作时会有一定热量,PCB设计时要在芯片底部铺一块大铜皮帮助散热。
2.2 最小系统与可靠性电路
STM32F103C8T6的最小系统由电源、晶振、复位电路、BOOT设置和去耦电容这几块组成。
晶振用的是8MHz无源晶振,两个负载电容选22pF。PCB布线时晶振要尽量靠近MCU的OSC_IN和OSC_OUT引脚,走线越短越好,包地处理,这样可以减少信号辐射和外部干扰。复位电路走的是经典接法:NRST引脚经10kΩ电阻上拉到3.3V,加0.1μF电容对地,外接一个复位按键。这个RC复位电路保证上电时NRST引脚维持低电平时间足够长,直到电源稳定后才拉高。
BOOT0引脚要下拉10kΩ到地,让芯片从Flash启动。BOOT1虽然是复用引脚,但为了调试方便我还是拉了出来,接了一个跳线帽。
去耦电容是很多新手容易忽略的点。我的做法是:在每个电源引脚附近放一个100nF陶瓷电容,就近接地过孔,在芯片的电源输入端再放一个4.7μF的钽电容。这些电容的高频去耦特性直接决定了MCU在电机、继电器这种大功率负载启停时的稳定性,绝不能省。
看门狗电路是这个项目可靠性设计的重头戏。STM32芯片内置了IWDG(独立看门狗),它由一个独立的40kHz内部RC振荡器驱动,不依赖主时钟。我在系统里配置了IWDG,预分频系数64,重装载值625,这样看门狗超时时间大约是1秒。主程序每500ms喂狗一次,如果程序跑飞或者卡在某个死循环里超过1秒,芯片自动复位,设备恢复正常运行。
但内部看门狗有个局限:如果程序卡死的同时主循环还能碰巧执行到喂狗的那行代码,那狗就没意义了。为了保险,我在电源输入端加了一个外部看门狗芯片MAX809,它监控3.3V电压,低于阈值2.93V时输出复位信号。这套电路的价值在于:即使MCU完全失控,只要电压异常,外部看门狗也能强制复位。
按键的防抖电路也是可靠性设计的一环。机械按键在按下和释放的瞬间会产生几十毫秒的抖动,如果不处理,一次按键可能被识别成多次。硬件上我用了RC低通滤波加施密特触发器的方式:按键串联一个10kΩ电阻,节点对地接一个100nF电容,信号再经过74HC14施密特触发器整形。这样抖动脉冲被RC电路滤除,施密特触发器负责把缓慢变化的信号边沿变成陡峭的数字信号,单片机读到的基本就是干净的单次脉冲了。软件上我还是保留了20ms消抖逻辑做双保险,实测按键响应非常稳定。
2.3 喂食执行机构与步进电机驱动
喂食器是整个系统最核心的执行机构。我设计了一个螺旋下料器:步进电机带动螺旋轴,螺旋轴旋转时把饲料从料仓底部推出,落入食槽。电机转过的圈数对应投喂量,通过控制电机步数就能实现定量喂食。
电机选的是五线四相步进电机28BYJ-48,配合ULN2003驱动板。选这个电机的理由很实在:便宜、体积小、扭矩足够推动小型螺旋轴,五线四相的结构调试起来也直观。
28BYJ-48内部有减速齿轮,减速比1/64,步距角5.625°/64,也就是每个半步对应的输出轴转动角度是0.0879°。要做到定时定量并不难,把脉冲数和出料量标定换算一下就成。我实测下来,喂食器每出1克饲料大约需要转8圈输出轴,折算成半步脉冲数是8×4096=32768个脉冲,程序里按这个倍数来算投喂量。
ULN2003内部是达林顿管阵列,集电极开路输出,灌电流驱动电机绕组。每个绕组线圈电阻约50Ω,5V供电时额定电流约100mA,ULN2003单路输出最大500mA,带这个电机没问题。第9脚COM要接到电机电源正极,给感性负载提供续流回路,否则关断瞬间的反电动势可能击穿驱动芯片,这是我调试早期踩过的坑。
电机驱动的软件层我用定时器输出比较功能产生脉冲。TIM2配置成PWM模式,输出比较通道接PF6或者通过IB连接ULN2003的IN1到IN4。四路输入按半步节拍的顺序依次输出高电平,即A→AB→B→BC→C→CD→D→DA这样一个周期。每一个节拍持续1.8ms,对应一个半步。这个节奏下电机运行安静且扭矩足,实测速度稳定,也没出现失步。
还有一个细节,电机驱动完成后要记得关闭输出,把所有控制引脚拉低,这样电机绕组没有电流流过,也就不发热。否则电机在不通电但处于某一相锁定的状态时,长时间会发热,影响饲料品质。
2.4 传感与采集电路
传感系统里最常用也最容易出问题的就是超声波测距模块,我用它来做两件事:检测料仓饲料余量、检测水槽水位。
HC-SR04模块的测距原理是:TRIG引脚收到10μs以上高电平后,模块内部发出8个40kHz的超声波脉冲,同时ECHO引脚输出高电平,高电平持续时间等于超声波从发射到遇到障碍物返回的时间。距离等于高电平时间×声速÷2,声速取340m/s,所以距离(厘米)= 高电平时间(微秒)×0.017。
关键是要精确测量ECHO高电平的时间宽度,这里用定时器输入捕获是最合适的方案。我在STM32上配置TIM3的CH1作为输入捕获通道,上升沿触发启动计时,下降沿触发读取当前计数值并停止,两次计数值之差就是高电平时间。72MHz的计数频率,每个计数是13.9ns,测距精度可以做到毫米级。
实际使用中要注意一个问题:HC-SR04的有效测距范围是2cm到400cm,在料仓这种空间比较有限、内壁还可能反射声波的场合,经常会有杂散回波导致读数乱跳。我的处理办法是在程序里做多重采样滤波:连续采集5次,去掉最大值和最小值,取中间3次的平均值。同时设置超时判断,如果ECHO高电平超过30ms,判定为数据无效,保留上一次有效读数。
称重模块用的HX711 + 压力传感器组合,用来实现鸽子的体重测量。HX711是24位高精度ADC芯片,专门为秤重桥式传感器设计,内部集成稳压电源和运算放大器。它和MCU的接口只需要两根线:PD_SCK和DOUT。读取时MCU给PD_SCK发25个以上负脉冲:前24个脉冲移出24位转换结果,发到第25个脉冲时可以选择下一次转换的输入通道和增益。我配置成通道A、增益128,后面接CS脚拉低,这样每次读取的就是满量程数据。
HX711的模拟电源AVDD要接2.6V到5V之间的电压,参考电压从AVDD通过内部稳压器产生,接一个0.1μF的去耦电容到VFB引脚。它的信号输入AIN1+和AIN1-分别接电桥传感器的两个差分输出端。差分输入的好处是能抑制共模干扰,比如电源波动、温度漂移这些对两个输入都有的噪声,会被差分放大器直接消除掉。
温湿度传感器选了DHT22,单总线协议,一根数据线上既传输数据又接收时钟。数字量直接通过总线读取,不用额外的ADC电路。DHT22每2秒采集一次,分辨率0.1℃,湿度精度±2%RH,对于鸽舍环境监测来说够用了。单总线对时序要求比较敏感,DHT22要求主机起始信号拉低至少18μs,然后释放总线等待响应。我实测用的延时函数是空循环实现的,不同编译优化等级下延时时间会有差异,所以调试时要把 -O0 和 -O2 下的表现都验证一遍,否则容易读不到数据或者数据校验错误。
鸽子的进出感应我用了E3F-DS30C4红外光电传感器,它是一个集成的开关量输出型传感器,输出NPN常开信号。鸽子经过传感器窗口时输出低电平,平时输出高电平。MCU这边直接用外部中断检测下降沿,配合软件里的方向判断来实现进出计数。这个模块的好处是自带反极性保护和电源滤波,抗干扰能力强,接线也简单,VCC接5V、OUT接MCU的PB0、GND共地。
2.5 通信与接口电路
RS485模块用的是MAX3485,它是3.3V供电的RS485收发器,和STM32的IO电平直接兼容。RS485是差分传输,A、B两线之间的电压差决定总线状态,抗共模干扰能力很强,适合鸽舍这种有电机、有220V供电线的现场环境。
这里有一个非常实用的设计:自动收发电路。传统做法是使用一个GPIO单独控制DE和RE引脚,发送数据时拉高DE、拉低RE,接收时反过来。但这样存在一个隐患:如果程序里切换方向的时机不对,会出现数据头丢失或总线冲突。我做了一个无方向控制的简化版本,把DE和RE引脚连到一起,通过一个三极管自动控制:TXD为低电平时,三极管截止,DE/RE被上拉到高电平,收发器处于接收状态;TXD为高电平时,三极管导通,DE/RE被拉低,收发器处于发送状态。这个电路能让RS485收发自动切换,省去程序里控制方向的麻烦,实测几百字节的数据连续收发没有错帧。
RS485总线末端要接120Ω匹配电阻,这是为了解决信号反射问题。当总线上的信号传输到线路末端时,如果阻抗不匹配,一部分能量会被反射回源端,导致波形扭曲、数据错误。120Ω正好等于双绞线的特性阻抗。这个电阻只接在总线最远端的两台设备上,中间设备不要接,否则会过度加重总线负载。
USB接口部分,STM32F103的USB外设是Device模式,我实现了一个虚拟串口(CDC类)功能。这样电脑插上USB线就能枚举出一个COM口,直接通过串口工具读取设备数据,不用额外插一个USB转串口模块。硬件上只需要MCU的USB_DP和USB_DN引脚连接USB座的对应信号线,再加一个1.5kΩ上拉电阻到USB的VBUS引脚,用来告诉主机这是全速设备。STM32的USB时钟要求48MHz,通过USB PLL或者主时钟分频得到,CubeMX里配置一下时钟树就能生成。
USB和RS485共存的场景下,系统就有了两条对外数据通路:本地调试用USB虚拟串口,远距离传输或多设备组网用RS485。我在软件里做了统一的串口数据帧协议,不管从哪个口进来,都走同一套协议解析逻辑。
TVS二极管保护电路也不能少。在USB的VBUS、D+、D-线上,以及RS485的A、B线上,我分别接了SMBJ6.0CA(双向TVS管)。TVS管在正常工作电压下呈高阻态,一旦线上出现超过击穿电压的浪涌,它立刻导通,把电压钳位到安全范围。雷电感应、继电器开关产生的浪涌尖峰就靠它们来扛。TVS管的响应时间是皮秒级的,比气体放电管快得多,对付ESD脉冲足够了。
3. 软件层面:如何把硬件跑起来
3.1 开发环境搭建与工程组织
软件部分我用的VS Code搭配PlatformIO插件来开发,没有用传统的Keil MDK。原因很直接:VS Code补全功能强大、Git集成方便、代码搜索和重构体验好,插件生态也能覆盖调试和烧录需求。PlatformIO专门支持STM32全系列,通过platformio.ini配置文件管理工程处理器和环境依赖,换库、换板子只需要改一行配置。
工程结构上我按模块拆分目录,每个模块有对应的.c和.h文件:
sys ├── main.c ├── bsp │ ├── bsp_uart.c │ ├── bsp_timer.c │ ├── bsp_iwdg.c │ └── bsp_adc.c ├── driver │ ├── drv_hcsr04.c │ ├── drv_hx711.c │ ├── drv_28byj48.c │ ├── drv_dht22.c │ └── drv_rs485.c ├── app │ ├── app_feeder.c │ ├── app_sensor.c │ ├── app_comm.c │ └── app_state_machine.c └── config └── board_config.hbsp层负责芯片外设的初始化和底层操作,driver层负责具体传感器或模块的协议解析,app层实现业务逻辑和状态机,这样的分层让我在调试某一个模块时不需要牵动其他代码。
烧录和调试用的ST-Link V2,通过4线SWD接口连接目标板,占用资源少、速度也够用。PlatformIO里烧录配置只需要在platformio.ini里指定upload_protocol = stlink,然后执行pio run -t upload就行。
3.2 核心模块的代码实现逻辑
系统的主循环框架用状态机来实现,对比裸机的前后台架构,它的优势在逻辑清晰、易于扩展和维护。
整个系统的工作流程我用三个状态来概括:IDLE(待机)、FEED(喂食)、ALERT(告警)。IDLE状态是默认状态,系统处于低功耗等待模式,定时器周期唤醒,检查当前时间是否到了喂食时刻、传感器数据是否正常。FEED状态在检测到需要喂食时进入,电机按预设脉冲数旋转,完成后自动回到IDLE。ALERT状态在传感器检测到异常(比如水位过低、料仓空、温湿度超出范围)时进入,蜂鸣器间歇报警,直到手动确认或异常解除。
喂食任务的具体逻辑是这样的:系统通过RTC记录当前时间,用户预先设定多个喂食时间点,比如早上7点、下午2点、傍晚6点。每个时间点对应一个投喂量参数,存储在外部的EEPROM里。到了设定时间,主状态机切到FEED,读取投喂量参数,计算出脉冲数,然后驱动电机运行。
步进电机的脉冲生成我放在了定时器中断里。TIM2的更新中断里执行一次IO状态切换,按照查表的方式把四个控制引脚置成对应的电平组合。一个节拍执行完后,计数器减1,直到计数归零说明投喂完成,退出中断函数时关闭PWM输出。这个方案的实时性好,CPU不需要高频占用,有剩余时间去处理传感器和其他事件。
我摘了一段驱动喂食电机的核心代码逻辑作为示意:
// 半步节拍表,对应A/AB/B/BC/C/CD/D/DA const uint8_t step_phase[8] = { 0b0001, 0b0011, 0b0010, 0b0110, 0b0100, 0b1100, 0b1000, 0b1001 }; void motor_step_once(uint8_t phase) { GPIO_WriteBit(GPIOA, GPIO_Pin_0, (phase & 0x01) ? Bit_SET : Bit_RESET); GPIO_WriteBit(GPIOA, GPIO_Pin_1, (phase & 0x02) ? Bit_SET : Bit_RESET); GPIO_WriteBit(GPIOA, GPIO_Pin_2, (phase & 0x04) ? Bit_SET : Bit_RESET); GPIO_WriteBit(GPIOA, GPIO_Pin_3, (phase & 0x08) ? Bit_SET : Bit_RESET); }实际驱动函数用定时器中断查这个表来跑,每次中断调用一次,就不在此展开整个文件了。
超声波的测距代码要注意一个细节:TRIG引脚必须先输出低电平至少2μs,再拉高10μs,最后拉低。这个时序如果不对,模块不会正常触发。我实测过TRIG高电平小于8μs时模块完全不响应,所以代码里延时必须保守一点。
3.3 看门狗与系统可靠性设计
看门狗的初始化代码我放在系统上电后的早期阶段,确保单片机上电后第一时间启动防护。
void bsp_iwdg_init(void) { // 解除写保护 IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); // 预分频系数64,40kHz/64=625Hz IWDG_SetPrescaler(IWDG_Prescaler_64); // 重装载值625,超时时间约1秒 IWDG_SetReload(625); // 重载计数值 IWDG_ReloadCounter(); // 启动看门狗 IWDG_Enable(); }喂狗的位置也有讲究。不能在整个主循环开始和结束的时候喂一次就算完事,因为主循环可能被某段长时间的传感器等待阻塞。我的做法是在每个状态机的核心处理函数内部周期性喂狗,比如传感器轮询、电机控制这些关键路径上。这样即便某个分支卡死,看门狗也能及时复位。
但在实际调试过程中,看门狗也会误伤:当我在线调试时,如果进入断点暂停超过1秒,看门狗就会复位芯片,导致调试被迫中断。调试阶段可以把看门狗用一个宏开关控制,发布版本再打开。
系统里还做了一个"软"看门狗的概念,来配合硬看门狗使用。主循环里维护一个任务计数器,要求在固定周期内每个高频任务至少执行一次,否则认为有任务异常,主动进入复位模式。这个设计弥补了硬件看门狗只能判断"卡死"、不能判断"任务丢失"的盲区,对解决传感器偶发阻塞问题特别有效。
3.4 USB虚拟串口实现
USB虚拟串口(CDC类)是这套系统里人机交互最方便的一环。电脑插上USB线,设备枚举成一个串口,任何串口终端工具都能直接访问,不需要专门驱动,也不需要第三方USB转串口芯片。
CubeMX里配置USB_DEVICE为Communication Device Class,生成底层USB库代码。在usbd_cdc_if.c的CDC_Receive_FS函数里,把接收到的数据放入环形缓冲区,由主循环去解析。CDC_Transmit_FS函数负责发送数据,要注意它的返回值:如果返回USBD_BUSY,说明上一次发送还没完成,需要稍后再重试。
USB枚举失败是经常遇到的问题。排查顺序是:先确认DP引脚上的1.5kΩ上拉电阻是否连接正确,这个电阻缺失时主机根本不会识别到设备;再看时钟树里USB时钟是否48MHz,不对也会枚举失败;最后检查USB线的数据线是否接对,DP和DN不能交换。
另外,GND必须共地,但VUSB和VDD最好在稍远的位置连接,并用磁珠或者小电感隔离,减少USB数据传输时的高频串扰对MCU电源的影响。
4. 调试实录:那些踩过的坑
4.1 电机不转或异响的排查
第一次上电测试喂食电机时,电机完全不转,只有细微的嗡嗡声。查了一圈发现是相序问题:28BYJ-48的五线四相电机红、蓝、绿、黄、橙五根线,橙色是公共电源线,红、蓝、绿、黄分别接四个相位。我的接线把红色和蓝色弄反了,导致驱动时序和绕组顺序不匹配,电机就无法连续旋转,只会抖动。
换回来之后又碰到一个新问题:电机转起来噪声很大,而且有明显的顿挫感。用示波器看驱动引脚的波形,发现每个节拍的保持时间不一致,有的节拍只有1.2ms,有的却到2.5ms。原因是定时器中断里除了查表赋值之外,还夹杂了其他代码的执行,导致中断响应时间抖动。解决方法是把节拍切换操作放在定时器中断的最前端,先赋值再处理其他事情;同时把中断优先级设置为最高级,避免被其他中断抢占。
另一个容易忽略的点是电源电流。28BYJ-48启动瞬间电流超过200mA,如果供电质量不好,电压跌落会导致MCU复位。我在电机电源输入处并联了一个470μF电解电容,专门应对启动瞬间的大电流抽取,这个问题就解决了。
4.2 传感器读数跳变
HC-SR04在料仓内的测距数值时不时的跳变,有时候直接从80cm跳到15cm,让我一度怀疑是模块坏了。后来发现是料仓内壁的光滑表面产生了镜面反射,声波经过多次反射后形成干扰回波。换了个安装角度,让传感器探头稍微倾斜,不直接正对仓壁,数据就稳定多了。
HX711称重模块的读数漂移比较麻烦。第一天标定好好的,第二天上电发现零位偏了好几克。检查后确认是桥式传感器的激励电压不稳定导致的。HX711的AVDD我用的是5V直供,但5V电源上有电机启停带来的纹波,导致ADC参考电压波动。改进措施是给HX711单独加了一个LC滤波,使用一个10μH电感和一个10μF电容组成低通滤波网络,放在AVDD前面,纹波被泡掉后读数明显稳定。
DHT22读不到数据的坑也值得记录。第一次调试时返回的CRC校验总是不通过,数据全为0xFF。排查半天发现是GPIO开漏输出配置有问题:单总线协议要求主机释放总线后,必须把IO配置为浮空输入,才能正确读到从机拉低的信号。之前我把引脚设成了推挽输出,等于一直驱动着总线,DHT22的信号根本拉不下去。
4.3 通信中断与恢复
RS485调通之后,运行了大约半小时,通讯突然中断,怎么重启现象也不恢复。排查发现发送端的数据能从TXD引脚看到,但AB总线上一片寂静。量了收发器MAX3485的VCC引脚,电压只有2.8V,明显偏低。原因是供电线压降:RS485节点离主控板比较远,我用了一段2米长的杜邦线供电,电机运行时线上的电压降大,拉到电流不够,收发器进入欠压状态。
后来我把通信节点的电源线换成了较粗的硅胶线,并在收发器旁边加了一个100μF的电解电容储能用,供电电压波动的问题立刻消失。
CAN通信的掉线问题在这个项目里虽然没有直接遇到,但我之前在另一个项目里碰到过,排查经验是通用的:先检查波特率配置是否完全一致,CAN总线的波特率误差要求很严格,两端哪怕差1%都会导致大量错误帧;再看总线收发器TJA1050的第8脚(S引脚)是否正常接高电平,没有正常进入高速模式时收发器输出波形畸变,也会造成通讯失败。
4.4 电源干扰与抗干扰处理
系统稳定运行之后还有一次偶发复位,查了很久发现是继电器动作瞬间的电源毛刺造成的。继电器线圈在通电和断电时会产生很大的反向电动势,虽然线圈两端并联了续流二极管,但动作瞬间的电流突变还是会在公共电源线上产生尖峰。
我做了三个层面的处理。第一层是在继电器的电源引脚和MCU电源引脚之间,用一颗磁珠做了电源隔离,把高频噪声限制在继电器这一侧。第二层是在PCB上把大电流路径和小信号路径的分开布置,模拟地、数字地、电源地单点汇接,不形成大面积的地环路。第三层是在主控板的电源输入位置增加EMI滤波电路,一个共模电感加两个Y电容的结构。
这三层处理之后,我用示波器监测了MCU的复位引脚和3.3V电源,在继电器反复动作时刻已经没有超过200mV的尖峰,系统彻底稳定下来。抗干扰这块没有明确的做完标准,只能是通过反复测试来逼近"怎么折腾都不复位"的状态。
做个收尾总结一下我在这个项目里最深的几个体会。第一点,做嵌入式系统,电源永远是第一优先级,电源不稳,其他模块再完美也会随机出错。第二点,硬件方案宁可保守,给软件的容错留下冗余;软件宁可多写几行防御性代码,给硬件的误差留出空间。第三点是流程问题:整个项目从需求分析、硬件设计、模块测试到系统联调,每一步都有明确的里程碑验证,最后组装在一起时才不会出现"哪里都有问题、不知道从哪里开始查"的窘境。
这个智能鸽子驯养系统的设计思路不局限于鸽子,把它替换成宠物自动喂食器、小型养殖场的环境监控终端,或者家庭阳台的自动浇灌设备,整个系统骨架都是可以直接迁移的。如果你准备做类似的项目,建议从最小系统开始搭,先把电源和串口调通,再一个模块一个模块地往系统里加。这样每一步都有可验证的成果,真正出了问题也知道问题出在哪个环节。