简介:基于STM32的宠物智能饲养盒Proteus仿真项目,面向单片机初学者、电子竞赛备赛者及宠物智能硬件爱好者,重点解决定时喂食与温湿度监控的联动实训需求。系统以STM32为主控,集成定时器、LCD1602显示、继电器投食控制与DHT11温湿度采集,可实现智能定时喂食、环境温湿度监测和超限控制。资料包共223个文件,主要包括C源文件、H头文件、编译中间文件、Proteus仿真工程、HEX固件,以及两段演示讲解视频和补拍视频,压缩包整体约32.88MB,目录结构清晰,便于按代码、仿真、文档分模块查阅。目前已有184人学习浏览。通过该资源,用户可获得可直接编译的完整源代码、配套Proteus仿真图、操作演示视频和固件烧录文件,适合课程设计、毕业设计或项目复现时边看边做、对照排错,能有效缩短开发调试周期。
1. 宠物智能饲养盒为什么先放在Proteus里做仿真再动手焊板
接到“基于STM32的宠物智能饲养盒”这类题目时,大部分人的第一反应是打开购物网站把DHT11、舵机、LCD1602、面包板一次性买齐。真正拖慢进度的往往不是代码,而是“假设外设行为都正确”这件事:温湿度传感器时序差一个微秒、舵机PWM占空比算错一档、晶振频率与库函数配置不一致,每一个问题在实物调试时都要靠示波器一根根线去查。Proteus仿真设计把传感器和执行器的行为模型化,逻辑错误在编译前就能暴露出来,硬件连接、状态机、时序参数都可以在电脑上先跑通一轮。这套流程对课程设计、毕业设计和刚开始接触嵌入式开发的人尤其合适:先在这个环境里验证“这么写到底对不对”,再决定要不要投板。下文按硬件搭建、控制逻辑、烧录调试、验收验证四层展开,所有代码按STM32F103系列标准外设库编写。
2. Proteus仿真元件选型与饲养盒硬件连接设计
2.1 按功能块拆出主控、感知、执行、交互四部分再选型
智能饲养盒的常见功能配置包括:环境温湿度检测、定时或手动喂食、缺水告警、LCD状态显示,以及串口调试输出。把这些功能映射到硬件上,就得到四个功能块:主控、感知、执行、交互。主控负责状态调度和信号处理,感知负责采集温湿度和模拟量水位,执行负责舵机开仓和指示灯/加热指示灯,交互负责LCD显示和按键输入。
选型时优先考虑Proteus中是否有对应的行为模型,而不只看数据手册。DHT11模型在Proteus中带完整单总线时序模拟,行为与实物基本一致;舵机SERVO模型能根据PWM高电平时间输出角度,配合示波器可以直观验证。LCD1602用LM016L模型,虚拟终端VIRTUAL TERMINAL用来替代串口调试助手。这些模型都位于Proteus元件库根目录下,导入后直接用导线连接即可,不需要额外安装第三方库。
2.2 Proteus元件库搜索与参数设置
2.2.1 STM32F103R6主控模型与时钟引脚
在Proteus 8 Professional中点击Pick Devices,搜索“STM32F103R6”可以找到主控模型。R6与C8T6同属F103中密度系列,内核和外设一致,只是封装与Flash大小不同,仿真中不存在实际容量限制,适合作为F103系列的统一仿真载体。放置后双击元件,弹窗中的Clock Frequency字段要与代码内部晶振配置保持相同,通常填写8MHz。
晶振电路在仿真中可以简化:真实硬件需要在OSC_IN和OSC_OUT之间接8MHz晶振并在两端分别对地接22pF电容,Proteus里若不接晶振也能运行,但PLL倍频路径依赖时钟配置,直接把频率参数填在元件属性里更稳妥。复位电路使用RST引脚接10k上拉电阻到3.3V,按键一端接地即可实现低电平复位;BOOT0引脚接地,让芯片从Flash启动。这一套接法与F103最小系统一致,后续移植到立创EDA画板时可以直接复用。
2.2.2 DHT11、LM016L、SERVO和电位器的接线差异
DHT11在Proteus中搜索后放在原理图,四脚模型从左到右为VCC、DATA、NC、GND。DATA引脚建议通过4.7k上拉电阻接3.3V,与实物要求一致。数据线引出到STM32的PB12。LM016L即1602字符液晶,标准16脚,RS接PB0、RW接PB1、EN接PB2,D4到D7接PB3到PB6,VL脚接1k电位器中间抽头调节对比度。舵机SERVO模型三根线,PWM输入接PA8,VCC接5V,GND共地。
水位检测在Proteus中没有现成的液位传感器模型,常见做法是用电位器POT-HG模拟水位变化,电位器中间抽头接PA1做ADC采集,通过分压值近似反映水位高低。这种方式适合仿真阶段验证阈值判断逻辑,制作实物时再替换为电极式水位传感器。
下表汇总了本设计用到的仿真元件:
| 功能块 | Proteus搜索关键字 | 放置后名称 | 关键参数或接法 |
|---|---|---|---|
| 主控 | STM32F103R6 | U1 | 8MHz外部时钟 |
| 温湿度 | DHT11 | U2 | DATA经4.7k上拉接PB12 |
| 显示 | LM016L | U3 | 4位模式,RS=PB0,EN=PB2 |
| 舵机 | SERVO | SV1 | PWM接PA8 |
| 水位模拟 | POT-HG | RV1 | 中间抽头接PA1,做ADC输入 |
| 调试串口 | VIRTUAL TERMINAL | —— | RX接PA9,TX接PA10 |
| 上拉电阻 | RES | R1 | 4.7k,DHT11数据线上拉 |
2.3 仿真观测点:串口虚拟终端与逻辑探针
仿真环境里最容易犯的错误是“不知道中间量是否对”,解决办法在原理图上预留观测点。USART1的PA9和PA10接入VIRTUAL TERMINAL,代码里用printf输出温湿度原始值和喂食触发事件,Proteus虚拟终端的波特率设置为9600、8位数据、无校验、1停止位,就能直接看到带时间戳的日志。对于DHT11数据线这种关键GPIO,可以用Logic Probe逻辑探针直接挂在PB12上,运行时探针颜色变化表示电平翻转。舵机PWM输出还可以接虚拟示波器,这比看代码里的TIMPulse更直观,第五章会专门讲示波器验收方法。
3. STM32饲养盒控制逻辑:状态机、DHT11时序与舵机喂食
3.1 用状态机划分饲养盒行为,避免主循环里堆if
饲养盒的行为可以从时间维度拆成四类:空闲等待、周期检测、投喂动作、环境告警。把这四类行为做成状态机,代码可读性比在主循环里连续if else判断标志位高得多。定义如下枚举:
typedef enum { SM_IDLE = 0, // 空闲,等待定时或按键触发 SM_DETECT, // 读取DHT11温湿度 SM_FEED, // 舵机开仓再关闭,完成一次投喂 SM_ALERT // 温度或湿度越限,LCD告警 } SysState; volatile SysState g_state = SM_IDLE; volatile uint32_t g_tick_1s = 0; // 秒计数,由SysTick中断累加 volatile uint8_t g_feed_req = 0; // 按键触发投喂主循环按状态分发:
while (1) { switch (g_state) { case SM_DETECT: dht11_read(&g_temp, &g_humi); if (g_temp > TEMP_MAX || g_humi < HUMI_MIN) g_state = SM_ALERT; else g_state = SM_IDLE; break; case SM_FEED: feed_action(); g_feed_times++; // 喂食次数累计,用于显示和验收 g_state = SM_IDLE; break; case SM_ALERT: lcd_show_alert(); g_state = SM_IDLE; break; default: break; } }状态迁移的触发条件放在中断和按键扫描里,主循环本身只做状态分发,不阻塞等待外设。Code中g_feed_times是喂食计数,调试时可以打印到串口,验证定时投喂是否按预定周期执行。
3.2 DHT11单总线读取:40us采样点怎么来的
DHT11一次通信包含主机起始信号、从机80us低电平响应、40bit数据、最后50us结束。数据位的区分方式是:每个数据位以50us低电平开始,随后高电平持续26到28us表示逻辑0,持续70us表示逻辑1。因此读取一个bit时,等待低电平结束,再延时40us后采样引脚电平,就能稳定落在两个区间的分界处。
static uint8_t dht11_read_bit(void) { uint8_t bitval = 0; // 等待50us低电平结束,保证采样点对齐到数据位的高电平段 while (!GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN)); delay_us(40); if (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN)) bitval = 1; while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN)); // 等待高电平结束 return bitval; }delay_us(40)是软延时,基于72MHz系统时钟做循环递减。需要说明的是,Proteus仿真运行速度受宿主机性能影响,软延时循环次数在仿真环境与实际硬件上不一定等长,调试时应以虚拟示波器测量到的DHT11响应时序为准,必要时微调循环次数。读取频率控制在1s一次,过高频率会导致DHT11模型返回错误数据。
3.3 舵机喂食:TIM1高级定时器输出PWM
舵机控制采用20ms周期性PWM,高电平时间决定转角:0.5ms对应0度,1.5ms对应90度,2.5ms对应180度。这部分使用TIM1的通道1输出PWM,TIM1属于高级定时器,除了常规的时基和输出比较配置,还必须使能主输出MOE,否则PA8上没有信号。
void servo_init(void) { GPIO_InitTypeDef gpio; TIM_TimeBaseInitTypeDef tim; TIM_OCInitTypeDef oc; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 | RCC_APB2Periph_GPIOA, ENABLE); gpio.GPIO_Pin = GPIO_Pin_8; gpio.GPIO_Mode = GPIO_Mode_AF_PP; gpio.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &gpio); tim.TIM_Prescaler = 71; // 72MHz分频到1MHz,计数周期1us tim.TIM_Period = 19999; // 20000us = 20ms,即50Hz tim.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM1, &tim); oc.TIM_OCMode = TIM_OCMode_PWM1; oc.TIM_Pulse = 50; // 占空比计数50 = 0.5ms => 0度 oc.TIM_OutputState = TIM_OutputState_Enable; TIM_OC1Init(TIM1, &oc); TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_CtrlPWMOutputs(TIM1, ENABLE); // 高级定时器主输出使能,容易遗漏 TIM_Cmd(TIM1, ENABLE); }TIM_Prescaler取71,把72MHz时钟分频成1MHz,此时TIM_Pulse的值直接等于高电平微秒数。喂食动作可以开环控制:先把仓门开到120度保持一段时间,让饲料落下,再关闭。由于舵机旋转需要时间,直接连续写占空比会跳过中间过程,稳妥做法是分步更新。
| TIM_Pulse计数值 | 高电平时间 | 舵机角度 | 饲养盒动作 |
|---|---|---|---|
| 50 | 0.5ms | 0° | 仓门完全关闭 |
| 75 | 1.5ms | 90° | 仓门半开 |
| 100 | 2.0ms | 120° | 仓门近全开,下料 |
| 125 | 2.5ms | 180° | 仓门全开 |
void feed_action(void) { // 开仓:0度到120度,每步间隔10ms,步长4us对应约10度 for (uint16_t pulse = 50; pulse <= 100; pulse += 4) { TIM_SetCompare1(TIM1, pulse); delay_ms(10); } delay_ms(300); // 保持开仓,让饲料下落 // 关仓:120度回到0度 for (uint16_t pulse = 100; pulse >= 50; pulse -= 4) { TIM_SetCompare1(TIM1, pulse); delay_ms(10); } }喂食期间对DHT11的读取会被阻塞约500ms,这在1s级控制周期内可以接受。若后续设计增加多路舵机或伺服电机,建议把分步旋转放进SysTick中断服务里,用非阻塞方式切换角度。
3.4 显示、按键与喂食定时器的时间片划分
状态机之外还需要一个秒级定时源。用SysTick配置1ms中断,SysTick_Handler里累加g_tick_1s,每满1000置检测标志,按键扫描则放在主循环中执行,配合10ms消抖窗口:
if (g_tick_1s >= 1000) { g_tick_1s = 0; g_state = SM_DETECT; // 每秒检测一次温湿度 } if (KEY_Scan() == KEY_FEED) { g_state = SM_FEED; // 手动触发一次投喂 }LCD1610的刷新不需要每一轮主循环都执行,温湿度变化缓慢,每秒刷新一次足够。显示函数这样组织:
void lcd_show_env(void) { char line[17]; snprintf(line, sizeof(line), "T:%dC H:%d%%", (int)g_temp, (int)g_humi); LCD1602_SetCursor(0, 0); LCD1602_WriteString(line); snprintf(line, sizeof(line), "Feed:%d", g_feed_times); LCD1602_SetCursor(0, 1); LCD1602_WriteString(line); }g_temp和g_humi定义为全局变量,DHT11读取函数更新它们,显示函数只读取,避免在中断服务函数里调用LCD操作,保持显示功能独占一个时间片。
4. 从Keil到Proteus:hex烧录、时钟配置与三个必调仿真参数
4.1 Keil5侧配置:芯片包装好后生成hex的四个选项
代码编辑与编译在Keil5完成。新建项目选择芯片型号时如果找不到STM32F103系列,说明芯片支持包没有安装,Pack Installer中搜索“STM32F1xx_DFP”进行安装。同时安装过C51和STM32包的环境互不影响,C51项目不会出现在Device列表中。工程建好后按先后顺序检查四个编译选项:
第一,Options for Target → Device确认目标芯片为STM32F103R6或同系列中密度型号。第二,Output选项卡勾选Create HEX File,这样编译后才会生成Proteus需要的.hex文件。第三,C/C++选项卡Defines中填写STM32F10X_MD, USE_STDPERIPH_DRIVER,MD对应中密度产品,漏掉这个宏会在编译时报外设寄存器未定义。第四,Debug选项卡选择“Use Simulator”或任意调试器都行,Proteus只用最终hex文件,不依赖调试器设置。
编译报错时优先检查Target选项卡里是否勾选了Use MicroLIB。标准库的printf默认走半主机模式,重定向fputc到USART1后若还在半主机状态下运行,会卡死在BKPT指令,勾选MicroLIB能直接绕过这个问题。
4.2 Proteus里加载hex并核对晶振参数
打开Proteus的原理图,双击U1进入Edit Component对话框。Program File字段选择刚刚编译生成的hex文件,路径通常在工程目录Objects文件夹下。Crystal Frequency字段填写8000000,这必须与代码中的HSE_VALUE保持一致。代码中RCC_PLLConfig的倍频系数若设置为9倍,系统时钟就是72MHz,这个值只影响代码运行速度,Proteus中的频率字段是给仿真模型提供时钟源的。
点击左下角运行按钮,绿色三角进入运行状态。如果LCD没有反应,先不要改代码,检查是否所有元件GND网络都连通,Proteus的电源端子VCC和VSS需要显式放置,网格连线不会自动连接电源符号。DHT11读取失败时,在DHT11模型上右键选择“Edit Properties”,确认Delay响应时间参数在模型默认值范围内。
4.3 三个必调的仿真参数与故障排查表
Proteus仿真环境有三个参数会影响运行结果:晶振频率、动画帧率、虚拟终端波特率。晶振频率必须与代码匹配,否则定时器时基计算全错;动画帧率默认比较高,仿真速度慢时在System → Set Animation Options里把帧率调低,同时关闭“Real Time Simulation”避免宿主机CPU占用过高;虚拟终端波特率要与USART1初始化一致,代码里配置的9600对应终端中的9600 Baud。
| 故障现象 | 可能原因 | 修改位置 |
|---|---|---|
| 仿真一开始就跑飞,LED乱闪 | 晶振频率与HSE_VALUE不一致 | 双击STM32模型改Crystal Frequency |
| LCD无字符但有背光 | LM016L的VL对比度引脚悬空 | VL脚接10k电位器中间抽头调电压 |
| 舵机一直停在0度不动 | TIM1主输出MOE未使能 | 代码中加TIM_CtrlPWMOutputs(TIM1, ENABLE) |
| 第一次读温湿度为0 | DHT11模型上电后需要时间稳定 | 读前延时500ms,或读到0时重试两次 |
| 虚拟终端显示乱码 | 波特率或数据格式不一致 | 终端设为9600, 8, N, 1 |
| 仿真速度明显变慢 | 动画面帧率过高 | System → Set Animation Options调低帧率 |
DHT11仿真模型有一个特殊行为:上电后第一次读取往往返回0xFF或0x00,因为模型内部的定时器还没有准备好。代码层面只要加一个重试机制,连续读失败两次后返回上一次有效值,就能规避这个坑。
5. 用示波器卡舵机脉宽,把仿真验收做成秒级流程
5.1 用虚拟示波器看PA8的脉宽和周期
Proteus左侧工具栏的虚拟仪器面板里选择OSCILLOSCOPE,放置后把A通道的探针连接到PA8引脚。运行时示波器会显示PWM波形,按以下三个指标逐项验收:周期是否稳定在20ms左右,高电平时间是否随角度分步变化,喂食动作期间波形是否连续无抖动。
周期偏差超过5%时,检查TIM_Prescaler和TIM_Period的值。72MHz输入下,Prescaler=71得到1MHz计数频率,Period=19999时计数器从0数到19999共20000个时钟周期,正好20ms。若代码把Prescaler配成7199,计数频率变成10kHz,同样的Period对应的周期就变成2秒,舵机角度更新会肉眼可见地一卡一卡。
验证角度分步变化可以打开舵机模型自带的Angle属性面板,运行喂食动作时,角度直接从0跳到120度,说明PWM从0.5ms直接跳到2.0ms,中间缺少过渡步,这时候看feed_action里TIM_SetCompare1的调用间隔,通常是把Delay_ms写成了Delay_us导致过渡时间过短。
5.2 按秒级时间轴记录喂食触发与温湿度刷新
最终验收阶段,将喂食定时从代码里的一个条件编译宏控制,例如#define FEED_INTERVAL_SEC 30,仿真运行时用手机计时,观察30秒整数倍时舵机是否动作、LCD计数行是否加一。连续验证三个周期,确认每次喂食时间差不大于一个秒计数。温湿度刷新则把DHT11读取周期改为2秒,观察虚拟终端串口日志中温湿度值更新的间隔。
这套流程把仿真验证收敛成了可量化的动作:示波器确认PWM参数正确、秒表确认逻辑周期正确、串口日志确认采集链路通畅。三个指标都通过后,再考虑把原理图网络表导出做PCB打样,仿真阶段积累的晶振配置、上拉电阻位置和串口接线方式可以直接迁移到实物设计。宠物饲养盒本身的低成本特性决定了它适合用这种“仿真先行”的开发路径,硬件改版一次的成本和时间远高于在Proteus里多跑几个小时的仿真。
本文还有配套的精品资源,点击获取