简介:设计资料围绕基于STM32的猫狗宠物喂养系统展开,面向物联网嵌入式学习者、电子设计竞赛参赛者及智能宠物硬件开发者,完整呈现从需求分析到系统落地的全流程方案。内容基于STM32F103RCT6主控,结合ESP8266 WiFi模块、28BYJ4步进电机、称重传感器、DHT11温湿度模块等硬件,实现定时/手动/远程投喂、余粮监测、环境温湿度与光照检测、LED照明控制等功能;软件端则涵盖腾讯云物联网平台产品与设备开发、物模型配置,以及微信小程序交互界面和MQTT协议通信。文档还梳理了硬件连线、原理图、实物图、Keil工程核心代码及程序下载流程,并在最后给出STM32代码使用注意事项,便于读者按图索骥。整体为单个PDF文件,大小34.63MB,结构完整、步骤详细,已有166人学习下载,适合作为课程设计、毕业设计或竞赛项目的直接参考与二次开发底稿。
1. 基于STM32的猫狗宠物喂养系统,为什么微信小程序是比App更顺的选择
自动喂食器是个被反复做过的品类,早些年是机械定时转盘,后来变成摄像头、扬声器、储粮桶一体的商用设备。真想自己从零搭一套,方案其实不少:树莓派挂Python脚本控制舵机,ESP8266连继电器开电磁阀,甚至有人拿旧手机改远程投食。但真要落到「猫狗在家里,人不在旁边,还得稳定喂一年」的场景,我首选STM32当主控、微信小程序当界面。STM32的定时器、PWM、GPIO资源刚好覆盖舵机、称重、余量检测这些外设,实时性也有保证;微信小程序免安装、扫码即用,还顺势解决了多端适配。这篇文章按一条可复现的路径来走:先把系统拆成模块,再给STM32端写驱动和定时逻辑,然后打通小程序和设备的通信链路,最后聊联调排错和数据回溯。新手能照着抄,做过一段时间的人也能在参数和边界条件上找到可抠的细节。
2. 系统架构与硬件选型:从STM32最小系统到执行机构的完整拆解
2.1 先把系统拆成四个模块,再谈选型
自动喂养系统可以清晰分成四块:主控模块、感知模块、执行模块、通信模块。主控模块用STM32,感知模块负责余量检测和运行状态判断,执行模块驱动出粮机构,通信模块负责和微信小程序交换数据。理解这个结构比直接焊板子更重要,因为后面写程序时,每个模块会对应不同的外设初始化和中断逻辑。
感知模块这一块,最常见的设计是给储粮桶底部加一个称重传感器,用HX711这类24位ADC芯片读取重量变化。出粮一次,重量减少多少,就能反推出猫粮或狗粮的颗粒重量。执行模块通常是一个步进电机或舵机,带动螺旋出粮轴,也有用舵机直接翻斗倒粮的简易方案。通信模块的选择决定了你整个系统的形态。
2.2 主控选型:STM32F103C8T6还是更高型号
做这类系统,我一般先用STM32F103C8T6打样。这颗芯片有64KB Flash、20KB RAM,主频72MHz,定时器、USART、I2C、SPI一应俱全,单颗芯片价格低,淘宝和立创商城随时买得到,Keil5的芯片包也装得顺手。如果你需要同时驱动步进电机、多个传感器,还要跑RTOS,可以考虑STM32F407或STM32F429,但通常用不上。很多人一开始就追求高配,结果把精力耗在了不必要的外设配置上。F103C8T6跑裸机程序,配合定时器中断和状态机,完全能支撑这个项目的并发逻辑。
2.3 硬件选型参数表与关键引脚分配
下面这张表是我打样常用的一套方案,可以作为你自己的选型起点:
| 模块 | 型号/方案 | 接口 | 关键参数 | 备注 |
|---|---|---|---|---|
| 主控 | STM32F103C8T6 | 3.3V供电 | 72MHz主频,64KB Flash | 最小系统板即可 |
| 通信 | ESP8266-01S | USART1 TX/RX | 支持TCP/UDP/HTTP | 串口AT指令配网 |
| 出粮电机 | 28BYJ-48步进电机 + ULN2003驱动板 | 4个GPIO | 5V供电,减速比1/64 | 扭矩足够带动螺旋轴 |
| 称重 | HX711 + 1kg/5kg压力传感器 | 2个GPIO(SCK/DOUT) | 24位ADC | 传感器需做屏蔽和固定 |
| 外壳 | 亚克力或3D打印 | — | 预留出粮口和传感器孔位 | 注意密封防潮 |
STM32的GPIO分配也有讲究:步进电机的四相接到PA0到PA3,HX711的SCK和DOUT分别接PB0和PB1,ESP8266串口复用USART1的PA9和PA10,再留一个PA4接蜂鸣器做声音提示。要特别提醒的是,电源设计上步进电机和STM32必须分压供电,电机启动瞬间电流变化大,共用一路3.3V会直接把主控拉复位。我见过不止一次样机莫名其妙重启,最后查出来是电源压降问题。5V电源直接从USB口取,3.3V由AMS1117稳压后单独给MCU。
2.4 通信链路的选择:WiFi优先还是蓝牙优先
微信小程序支持蓝牙、WiFi、USB等硬件接口调用,但在宠物喂养场景里,远程控制是刚需。所谓远程,意味着人和设备不在同一个局域网,所以最常见也最稳妥的做法是:STM32通过串口连接ESP8266,ESP8266以STA模式连接家里路由器,然后通过MQTT或HTTP协议把数据送到云端中转,小程序再从云端拉取或发布指令。这套链路里,微信小程序本身不需要直接连设备,而是连云端,因此还要考虑设备ID和用户微信的绑定关系。
如果只想实现近场控制,BLE方案更简单:用STM32加一个BLE透传模块,小程序通过蓝牙接口直接读写特征值。BLE的好处是免配网、时延低、断网可用,缺点是一离开蓝牙范围就失去控制。我的建议是,如果做毕业设计或原型,先用BLE打通全链路,速度快很多;如果面向实际长期使用,用WiFi上云是更可靠的选择。
3. STM32端程序设计:GPIO、PWM、定时器的实战配置
3.1 使用标准外设库还是HAL库,取决于你的调试习惯
这是新手第一个会卡住的问题。STM32的开发库主要有标准外设库(Standard Peripheral Library)和HAL库,另外还有直接操作寄存器的方式。我的建议是:如果你参考的开发板例程以标准库为主,那就延续标准库,因为网上关于F103的例程九成都是标准库,遇到问题容易找到参照;如果你打算做低功耗、跑RTOS或做图形化配置,直接上HAL + CubeMX更省事。关键是不要中途换库,否则指针定义、回调机制、超时处理全都不一样,排查成本极高。
下面以标准库为例,给出步进电机驱动的初始化代码。28BYJ-48是四相八拍电机,它的一个完整旋转周期需要走4096个半步,出粮机构每转一圈约吐出10到15克粮。齿轮减速比大,转速慢,但扭矩足够推动猫粮。四根相线分别接MCU的四个GPIO,输出高电平脉冲控制电机转动。
void Motor_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); } void Motor_Step(uint8_t motor_pin_mask) { GPIOA->BSRR = motor_pin_mask; delay_us(800); } void Motor_Cycle(int steps) { uint8_t phase[8] = {0x01, 0x03, 0x02, 0x06, 0x04, 0x0C, 0x08, 0x09}; for (int i = 0; i < steps; i++) { Motor_Step(phase[i % 8] << 0); } }这段代码的核心是将四个GPIO引脚配置为推挽输出,然后在循环里按八拍顺序切换电平。Motor_Step函数的参数motor_pin_mask是GPIOA低四位的位掩码,例如0x01表示PA0输出高电平,0x0C表示PA2和PA3同时为高。delay_us(800)控制脉冲宽度,决定电机转速,实际调的时候根据出粮机构的负载情况在400到1200微秒之间调整。步数steps决定出粮量,喂食量可以通过标定实验来换算。
3.2 定时器与RTC配合:喂食时间表怎么执行
喂食系统最要命的问题是掉电后时间表丢失。纯靠STM32内部的SysTick计时,在主控复位后时间就乱掉了。常见做法有两种:一是给STM32外接一个DS3231硬件RTC模块,用I2C读取时间,精度高且自带电池备份;二是利用STM32内部的备份寄存器,在掉电时用纽扣电池维持VBAT供电,开机后从备份寄存器恢复时间戳。前者更省心,代码也更直观,我用这个方案比较多。
喂食时刻的判断放在时间基准中断里执行。用TIM2产生一个1秒的更新中断作为系统心跳,每过一秒把当前RTC时间与喂食计划表比对,匹配则触发出粮流程。喂食计划表建议直接声明成一个结构体数组,通过串口或无线协议可在小程序端远程改写。
typedef struct { uint8_t hour; uint8_t minute; uint8_t feed_amount; // 0-100,对应电机步数比例 uint8_t enabled; } FeedItem; FeedItem feed_table[4] = { {8, 0, 50, 1}, {18, 30, 50, 1}, {0, 0, 30, 0}, {0, 0, 30, 0} }; void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); time_t now = DS3231_GetTime(); for (int i = 0; i < 4; i++) { if (feed_table[i].enabled && feed_table[i].hour == now.hour && feed_table[i].minute == now.minute) { StartFeed(feed_table[i].feed_amount); feed_table[i].enabled = 0; // 防止同一分钟重复触发 } } } }如果用的是内部RTC或者外部RTC模块,这里DS3231_GetTime()返回的time_t是标准Unix时间戳,需要通过本地时区偏移转换成北京时间再取小时和分钟。需要注意的是启用标志位enabled,它在喂食完成后被置0,等到下次整点才重新置位,这个字段避免了中断里重复触发的问题。另一种做法是记录上次喂食的日期,如果日期变化才允许再次触发。
3.3 HX711称重代码与标定流程
称重模块连接HX711时,只需两根线:SCK和DOUT。数据读取时序是:先将SCK拉低,等待DOUT变低,然后连续读取24个时钟脉冲,从最高位到最低位依次采集DOUT电平,得到24位有符号数。这个过程不能简单用GPIO模拟,因为时序要求严格,建议在代码里加一个计时等待,或者用定时器微秒延时。下面给一个最简单的读取函数。
uint32_t HX711_Read(void) { uint32_t count = 0; while (HX711_DOUT_READ() == 1); // 等待DOUT拉低 for (uint8_t i = 0; i < 24; i++) { HX711_SCK_WRITE(1); delay_us(1); count = count << 1; if (HX711_DOUT_READ() == 1) { count++; } HX711_SCK_WRITE(0); delay_us(1); } return count; }HX711读取到的是未经标定的原始ADC值,范围在0到16777215之间。标定流程是:先空秤读取原始值,记录为zero_offset;放上100克砝码,再次读取原始值,记录为full_offset。那么当前重量就是(raw - zero_offset) * 100.0 / (full_offset - zero_offset)克。实际项目中,猫粮密度低、颗粒大,落料瞬间对传感器冲击明显,所以建议做滤波处理,取五次读数的中位数而非平均值,能有效消除毛刺。另外称重模块的固定方式很关键,传感器不能悬空安装,否则应变片受力不匀,零点漂移会非常明显。
3.4 卡粮检测、看门狗与状态上报
出粮机构卡住是宠物喂养系统里最频发的故障,原因通常是猫粮颗粒卡在螺旋槽或出粮口边缘。一般通过步进电机电流间接检测,也就是检测ULN2003驱动板输入端的电压变化,但精度不够。更好的做法是在出粮动作期间用HX711对比实时重量,如果启动电机后2秒内重量没有下降,则判断为卡粮,立即停止电机并让蜂鸣器鸣叫,同时向小程序上报异常状态。检测逻辑写成状态机,喂食流程有IDLE、MOTOR_RUNNING、WEIGHT_CHECK、FEED_DONE、FAULT五个状态,避免在中断里做耗时操作。
看门狗建议使用IWDG独立看门狗,1秒左右喂狗。主循环里要保证每次喂狗的时间间隔小于看门狗超时时间,但也不能在中断里喂,否则死循环检测就不生效了。正确做法是:主循环每轮执行完所有任务后刷新IWDG,如果某次出粮任务僵死在等待HX711的循环里,看门狗就会复位系统,把整机拉回正常运行状态。
4. 微信小程序端:从登录鉴权到远程下发喂食指令
4.1 小程序与设备的三条通信路径怎么选
小程序没法直接访问TCP端口,也不建议让它直连家里局域网设备,所以通信方案通常有三种。第一种:设备通过MQTT协议连接公有云,小程序后端订阅同一主题,这是最通用的方案,适合长期运行。第二种:设备接入像OneNET、阿里云IoT这类专门平台,平台提供HTTP API,小程序通过云函数调API,好处是免了自己搭MQTT broker,坏处是平台规则在变,调试时容易被鉴权接口卡住。第三种:蓝牙BLE直连,小程序通过wx.openBluetoothAdapter扫描并连接设备的BLE模块,简单直接。
对大部分开发周期不长的项目,我倾向于走「STM32 + ESP8266 + 云平台」的方式,因为小程序端只需要处理HTTP请求,逻辑非常干净。下面这段代码是小程序里用wx.login获取code,再换取后端返回的自定义登录态,也就是常说的"code换token"过程。这个token后续会加在每次请求的请求头里,后端通过它确认用户身份和设备绑定关系。
wx.login({ success: function(res) { if (res.code) { wx.request({ url: 'https://your-domain.com/api/login', data: { code: res.code }, method: 'POST', success: function(resp) { const token = resp.data.token; wx.setStorageSync('user_token', token); console.log('登录成功,token已保存'); } }); } } });这里的res.code是从微信服务器拿到的临时登录凭证,有效期只有五分钟,必须立刻发给后端。后端拿着code调用微信的jscode2session接口换取openid,再用openid组装成你自己的token返回给小程序。token建议用JWT,里面包含用户ID和过期时间。每次请求都带上这个token,后端判断token有效性和设备绑定关系后再放行指令,避免任何人拿到设备ID就能乱喂粮。
4.2 下发喂食指令:HTTP请求与设备状态回传
小程序给设备下发指令时,常见接口设计是:POST /api/device/{device_id}/feed,请求体包含amount和timestamp两个字段。后端收到请求后,通过MQTT向ESP8266发布一个JSON消息,STM32解析后执行出粮动作。设备执行完成后,再回传一条状态消息到云端,小程序轮询或通过WebSocket实时更新界面。看下面这个发布指令的代码片段。
const token = wx.getStorageSync('user_token'); wx.request({ url: 'https://your-domain.com/api/device/1001/feed', method: 'POST', header: { 'Authorization': 'Bearer ' + token }, data: { amount: 50, schedule: false }, success: function(res) { if (res.statusCode === 200) { wx.showToast({ title: '喂食指令已发送', icon: 'success' }); } else { wx.showToast({ title: '发送失败,请检查设备在线状态' }); } } });amount字段在服务端会被映射为电机的转动步数比例,比如50代表最大出粮量的50%。schedule字段用来区分是一次性手动喂食还是写入定时表,如果是定时任务,STM32会把时间和步数存入自身的Flash或RTC定时表。这里有个容易忽略的点:请求成功后闪现的Toast只是代表后端收到了指令,不代表设备真的执行了。设备离线时,后端应返回明确的错误码,小程序界面显示为设备离线。
4.3 设置每日喂食计划的界面逻辑
小程序端设置定时喂食的界面,常见实现是用时间和单选框组合。用户先选一天中的几个时间点,再为每个时间点设定克数。提交时把这些配置打包成一个数组,通过POST /api/device/1001/schedule上传。STM32端的ESP8266收到后,将数据帧解析并写入外挂Flash扇区或内部Flash,掉电也不丢。
单选框的典型处理方式是用wx.radio-group绑定选中的喂食时间段索引。要注意的是,radio-group的bindchange事件返回的是字符串类型的value,需要在比较时做类型转换,否则容易在条件判断时出错。定时配置在上传前还要做本地校验,喂食时间不能重叠,喂食量不能超过桶容量,这些逻辑放在小程序端校验比放在后端响应更及时,用户反馈也更好。
4.4 通过订阅消息做余量告警
余量告警是宠物喂养系统的小闭环能力,适合用微信小程序的订阅消息。小程序通过wx.requestSubscribeMessage向用户申请一次性订阅授权,注意一次性授权只能推送一条消息,之后需要用户再次点击触发授权按钮。所以常见做法是,在用户点击"开启余量提醒"按钮的瞬间去申请订阅,后端在余量降到阈值时,通过云调用subscribeMessage.send推送一条模板消息给用户。
这里要特别确认用户是否真正打开了订阅允许。调试阶段经常出现后端返回43101错误码,代表用户拒绝了订阅授权。解决办法是在页面中引导用户重新点击按钮并再次申请。如果想每周推送一次余量报告,那就得让用户每次点完授权后,后台把授权次数攒起来用,这也是很多开发者在做宠物App和小程序时容易踩的坑。模板ID建议配置在环境变量里,不要硬编码在小程序代码里,不然每次换模板都要重新发版。
5. 联调排错清单、日志回溯与喂食可靠性验证
5.1 联调时最先遇到的几个问题
把STM32端和小程序端接起来调试时,信号和数据链路的排查可以对照下表逐项检查:
| 现象 | 可能原因 | 排查步骤 |
|---|---|---|
| 小程序提示设备离线 | ESP8266未连接WiFi或MQTT连接断开 | 用AT指令查询模块IP,检查路由器设备列表 |
| 手动喂食点击后无反应 | 串口波特率不匹配或GPIO配置错误 | 用逻辑分析仪抓TX/RX,确认波特率一致 |
| 出粮重量每次偏差大 | 电机打滑或步数换算不精确 | 称重多次,重新标定每克对应步数 |
| 定时喂食不执行 | RTC时间未同步或时区偏移错误 | 先查串口打印出的RTC时间是否正确 |
| 订阅消息推送失败 | 用户未授权或模板ID配置错误 | 在云开发控制台查看调用日志和错误码 |
5.2 给STM32端加上串口日志,回溯问题更快
排查问题时我最依赖的还是串口日志,不要依赖LED灯和手指测电压。STM32程序里加一个简单的printf重定向,把USART1映射到fputc函数,这样就能在Keil5的串口终端里直接看到系统运行状态。建议在喂食指令收到、电机启动、重量变化、完成出粮这几个关键节点都打一条日志。
void USER_USART_IRQHandler(void) { // 接收中断中解析来自ESP8266的AT指令 // 每收满一帧JSON数据,调用FeedCommandHandler() }调试阶段还有一个高效操作,是把ESP8266的透传模式先关闭,用串口助手直接给STM32发AT指令和数据帧,验证MCU的解析逻辑是否正确。这样就拆开了通信链路,定位问题更准确。等MCU这边逻辑稳了,再让ESP8266全速运转。
5.3 验证喂食可靠性的三个具体做法
喂食系统不能只测功能,还要测可靠性。第一是做重复投喂测试:连续运行50次手动喂食,每次放一个空碗,记录实际出粮量,计算偏差和标准差。预期是同一档位的出粮量波动在±5%以内,如果超过这个范围,优先检查电机步数和螺旋轴间隙。第二是断电恢复测试:在喂食中途断电,重新上电后观察设备是回到初始状态还是能恢复喂食进度。这一条直接关系到宠物会不会吃到一半断粮。第三是做长时间待机测试:连续跑48小时,记录设备是否出现死机或时间漂移,尤其是看IWDG看门狗有没有意外复位。把这三项数据记录下来,这个系统才算真正能交付给用户使用。
本文还有配套的精品资源,点击获取