简介:一套基于STM32与Proteus的智能家居环境监测系统完整设计方案,适合嵌入式、自动化、电子信息等专业学生用于课程设计或毕业设计参考。系统围绕温湿度、光照和气体浓度检测展开,通过四个按键切换监测任务,数据经数码管实时显示并借助串口输出,温度、湿度异常时触发蜂鸣器报警。资源共137个文件,压缩包大小8.94MB,主要包含C语言源码、头文件、Keil工程文件、Proteus仿真工程与备份文件,以及hex、axf等编译产物和avi演示视频,可直观了解工程结构与运行效果。目前已有71人学习下载。代码经过严格测试,功能稳定,特别在气体检测模块中使用滑动变阻器模拟传感器,提供了纯仿真环境下的完整实现思路。资源内还附有项目文档和演示视频,便于对照学习STM32外设驱动、数码管显示、串口通信、按键交互及报警逻辑等关键技术。
1. STM32 与 Proteus 联动的智能家居环境监测:仿真跑通比焊板子更重要
这套资源的主干是一个基于 STM32F103 的环境监测系统,在 Proteus 里完成仿真,再把温湿度、光照、气体检测三个传感器数据合并成一个完整的数据采集与显示链路。说句实在话,很多同学卡在这类项目上的第一道坎,不是不会写驱动,而是仿真环境搭不起来,元器件找不到,编译出来的 hex 烧进去黑屏无反应。你能在这里看到怎么把 DHT11、光敏电阻分压电路、MQ 系列气体传感器模块在 Proteus 里接出来,怎么用 STM32 的 ADC 多通道扫描和 GPIO 模拟时序把它们读进来,再通过 I2C 接口的 OLED 或者 1602 把数据刷出来。适合正在做课程设计、毕业设计,或者想快速验证传感器方案但手头没有实物套件的人。用仿真先把逻辑跑通,再去画 PCB,是省时间最狠的一条路。
2. Proteus 仿真环境搭建与 STM32 最小系统:先解决能不能跑起来的问题
2.1 Proteus 版本选择与 STM32 元件库确认
Proteus 8.x 之后对 STM32 的支持才算真正能打,8.9 以下版本建 STM32 工程经常遇到元件库不完整的情况。你在元件搜索框里敲 STM32F103C6 或者 STM32F103R6,能搜到带 ARM Cortex-M3 内核的模型,才说明库是完整的。如果只搜到 STM32F103R8 或者只看到 LQFP48 封装没有内部程序存储器模型,那基本是库没装全,先换版本再继续。
另一个常见问题是搜索框输 STM32 能出结果,但双击放置后没有 VDD、VSS 引脚自动连接电源网络。STM32 的 Proteus 模型特殊,虚拟电源和接地引脚不会像普通 51 那样露在外面,你要从隐藏引脚属性里手动确认 PC13、PC14、OSC_IN、OSC_OUT 这些默认配置。我建议放置元件后第一件事是右键查看属性,确认 PD0、PD1 没有和 BOOT 相关电路冲突。
器件选型这块,仿真里优先用 STM32F103C6,因为它的 Flash 是 32KB,RAM 是 10KB,资源占用少,Proteus 仿真速度也快。你如果硬要用 F103ZET6 这种大容量型号,仿真运行时会明显变慢,数据刷新频率超过 5Hz 之后 CPU 占用率甚至会到 90% 以上,基本没法看。
2.2 最小系统电路搭建与时钟配置
Proteus 里搭 STM32 最小系统,不需要像实物那样接 8MHz 晶振,但建议还是放上晶振电路,因为部分 Proteus 版本里 RCC 初始化配置会选择 HSE 外部时钟源,没有晶振模型仿真直接卡死。
如果你图省事,干脆在代码里改用内部时钟 HSI,把 SystemInit 里的时钟源改掉。用标准外设库的时候,在 system_stm32f10x.c 里把 #define SYSCLK_FREQ_72MHz 换成 8MHz 或者直接用 72MHz 但符号改成 SYSCLK_FREQ_HSI。这个改动直接影响后面串口波特率计算,如果时钟源变了波特率不重算,串口打印出来全是乱码。
实际接线方面,我给出一份可直接照抄的引脚分配表。
| 功能模块 | 引脚 | 连接说明 |
|---|---|---|
| DHT11 数据 | PA0 | 开漏输出,接 4.7kΩ 上拉到 3.3V |
| 光敏电阻模拟量 | PA1 | ADC1 通道 1,分压电阻 10kΩ |
| 气体传感器模拟量 | PA2 | ADC1 通道 2,需要加 100nF 滤波电容 |
| OLED 的 SCL | PB6 | I2C1 时钟 |
| OLED 的 SDA | PB7 | I2C1 数据 |
| 有源蜂鸣器 | PB1 | 三极管驱动,低电平触发 |
| 按键 S1 | PB12 | 内部下拉,按下为高电平 |
分压电路有个细节:光敏电阻和固定电阻的位置直接决定 ADC 读数方向。我把光敏电阻放上面接 3.3V,固定电阻放下面接地,光越强 ADC 值越高。如果你反过来接,程序里阈值判断就要取反,否则晚上灯光亮起的时候系统以为光照很强,自动关灯逻辑全反了。
2.3 程序编译与 hex 烧录路径
仿真要跑起来,程序必须先编译出 hex 或者 elf 文件。Proteus 的 STM32 模型支持直接加载 .hex,也可以加载 .elf。我用 Keil MDK 编译的时候,在 Options for Target 里勾选 Create HEX File,然后从 Output 选项卡里找到生成的 .hex 路径。打开 Proteus 后双击 STM32 芯片,在 Program File 一栏选择这个 hex 文件,再把 System Clock 设置为 72MHz 或者 8MHz,跟你在代码里配置的时钟一致,不然串口和定时器时序全乱。
注意:Proteus 8.13 以上版本对 STM32 的调试支持有所调整,如果加载 hex 后运行报错说 Debug port 被禁用,请在芯片属性里把 Debug 模式选为 Serial Wire,并给 PA13、PA14 保留上拉电阻。
3. 传感器驱动与 ADC 多通道采集:数据从模拟世界进到寄存器
3.1 DHT11 的 GPIO 模拟时序读取
DHT11 是单总线协议,数据线直接挂在 PA0 上。Proteus 里有 DHT11 模型,可以直接用,但有个坑:Proteus 的 DHT11 模型对时序要求比较宽松,实物读不到的时序在仿真里也能读出来,所以你在仿真里跑通的代码,直接烧到实物上大概率读失败。我一般会用延时循环替代精确微秒延时,然后做超时判断,这样两边都能兼容。
核心代码逻辑是这样:
uint8_t DHT11_Read_Byte(void) { uint8_t i, data = 0; for (i = 0; i < 8; i++) { while (DHT11_PIN == 0); // 等待 50us 低电平结束 delay_us(30); // 延时 30us if (DHT11_PIN == 1) // 超过 30us 还在高电平,说明这一位是 1 { data = (data << 1) | 0x01; while (DHT11_PIN == 1); // 等待高电平结束 } else { data = (data << 1) | 0x00; // 否则这一位是 0 } } return data; }这段代码里最关键的是 30us 这个采样点。DHT11 的时序规定,50us 低电平之后,如果数据位是 0,高电平持续 26-28us,如果是 1,高电平持续 70us。你在 30us 处采样,既能避开临界区,又能稳定区分 0 和 1。delay_us 在标准外设库下可以用 SysTick 做延时,如果用的是 HAL,建议直接调 HAL_Delay 的微秒版本,或者用 DWT 计数器。
读取温湿度的完整流程是主机拉低 18ms 释放,然后读 40bit 数据。前 16bit 是湿度整数和小数,后 16bit 是温度整数和小数,最后 8bit 是校验和。校验和的算法是前四个字节相加,低 8 位等于第五个字节就认为数据有效。我在代码里把温度和湿度存在全局变量里,三秒读一次,超过五次连续校验失败就置一个传感器错误标志位,OLED 上显示 ERROR,而不是显示一个乱跳的假数据。
3.2 ADC 的连续扫描模式与通道切换
光照和气体检测走的是 ADC1 的通道 1 和通道 2。这里有个常见翻车点:很多人用 ADC 规则组连续转换模式,但只初始化了一个通道,另一个通道的数据永远是同一个值。ADC 初始化必须把两个通道都加入规则组,并且设置扫描模式。
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 开启扫描模式 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 连续转换 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 2; // 参与转换的通道数量 ADC_Init(ADC1, &ADC_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 2, ADC_SampleTime_239Cycles5);ADC_SampleTime_239Cycles5 这是采样时间参数,单位是 ADC 时钟周期,这里是最慢档,测光敏电阻这种变化缓慢的模拟量足够用。如果是测电机电流这种快速变化的信号,才需要缩短采样时间。两个通道转换完成后,转换结果会依次存入 ADC_DR 寄存器,用 DMA 搬运或者直接在转换完成中断里读取。
读取结果后,光照强度需要一个换算关系,这就是血泪经验了。光敏电阻的阻值和光照强度近似成反比,但 Proteus 里的光敏电阻模型默认参数不一定匹配你手里的实物。我在仿真里直接读 ADC 原始值做阈值判断,不做实际 lux 换算,因为仿真环境里没有校准光源,强行换算只会得到一个毫无意义的数字。只要原始值和光线变化趋势一致,阈值逻辑就能验证。
气体检测那边,MQ 系列传感器在实物里需要一个加热预热过程,但 Proteus 模型简化了这个环节。如果是用电压输出型模型,读到的就是完整的模拟电压。如果选的是电阻型模型,需要配合比较器电路或者再做一级分压。建议选电压输出型 MQ 模型,省掉一整套外围电路。
3.3 数据滤波:滑动平均消除脉冲干扰
ADC 原始数据直接拿来用,会在阈值边界附近抖动,刚超过阈值就响一下蜂鸣器,降下来又停,反复触发。我在这个项目里做了一个滑动平均滤波,窗口长度设成 10。
#define FILTER_LEN 10 uint16_t filter_buf[FILTER_LEN]; uint8_t filter_index = 0; uint16_t Get_Filtered_ADC(uint8_t channel) { uint16_t sum = 0; uint16_t raw = Read_ADC(channel); filter_buf[filter_index] = raw; filter_index = (filter_index + 1) % FILTER_LEN; for (uint8_t i = 0; i < FILTER_LEN; i++) { sum += filter_buf[i]; } return sum / FILTER_LEN; }窗口长度是这里的关键参数。10 的窗口长度对 10ms 级别的采样周期来说,延迟只有 100ms,点亮 LED 和触发蜂鸣器都感觉不到迟滞。如果你采的是烟雾浓度变化这种需要更平滑的场景,窗口可以加到 20,但代价是响应速度变慢。窗口长度在 5 到 20 之间调,我有一次在实物上用了 5 的窗口,结果蜂鸣器还是轻微抖动,换到 12 才稳定。
4. 数据展示与阈值联动:OLED 显示、按键设参和报警输出
4.1 I2C OLED 驱动与数据刷新策略
Proteus 里用的 OLED 12864 模型,I2C 地址默认是 0x3C。我遇到过地址是 0x3D 的模型,如果屏幕始终没反应,先把地址扫描一遍。I2C 驱动用标准外设库的 I2C1,或者可以软件模拟 I2C,Proteus 都能识别。硬件的 I2C 外设和软件模拟相比,代码量少一半,但时序配置比较复杂。我一般用软件模拟 I2C,逻辑清晰,移植也快。
刷新策略和刷新频率相关。OLED 是静态显示,不需要像数码管那样动态扫描,所以只在数据变化超过阈值时才刷新。温度变化小于 0.5 度,湿度变化小于 2%,光照小于 20 个 ADC 值时,直接跳过刷新,这样可以延长 OLED 使用时间,也让 Proteus 仿真运行更快。
显示内容布局我分成三行:第一行显示温度和湿度,第二行显示光照百分比,第三行显示气体浓度状态。OLED 屏 128x64 像素,一屏 21 个中文字符,16x16 点阵的话一行最多显示 8 个字。第三行的气体状态用文字 MODE 显示,不显示具体浓度值,因为 MQ 传感器的绝对浓度没有校准意义,只有阈值和开关量有价值。
4.2 按键参数设置与阈值存储
按键 S1 接 PB12,用来切换阈值调整模式。长按 2 秒进入设置状态,然后短按切换阈值项,再配合另一个按键调整数值。Proteus 仿真里按键按下时容易抖动,消抖延时至少给 20ms。我用状态机做按键扫描,区分短按、长按和释放事件,比单纯在主循环里判断电平要可靠得多。
阈值数值存储在结构体里,初始化给出默认值,温度上限 28 度,湿度下限 30%,光照下限 100(ADC 原始值),气体浓度上限 200。每个阈值上下边界有保护逻辑,温度上限不允许低于下限,防止用户把阈值设混乱后出现永远报警的状态。
4.3 联动控制逻辑与蜂鸣器驱动
报警逻辑不算复杂,但优先级要安排好。我设了四档优先:气体浓度超标最优先,立即触发蜂鸣器且无视其他条件;温度超次之;湿度不达标再次;光照触发只开灯不响蜂鸣器。这个设计思路是模拟真实场景:燃气泄漏不管温度多正常都要响,而光照不足只需要自动开灯,不需要把人吵醒。
蜂鸣器驱动加了一个 500ms 延时翻转逻辑,避免持续高电平驱动导致 Proteus 模型里蜂鸣器声音一直响。更合理的做法是把报警做成两个状态:持续报警和间歇报警。气体泄漏用持续报警,温度超限用间歇报警,间隔 200ms,这样从听觉上就能分辨故障类型。
5. Proteus 仿真常见问题排查:从黑屏到数据错乱的完整排障记录
5.1 Hex 文件能加载但仿真不运行
现象:点击运行按钮,STM32 芯片上没有任何电压变化探测点显示,程序不执行。
原因:最常见的是芯片属性里加载路径错误,或者 hex 文件是旧的,甚至加载了别的项目的 hex。第二常见的是芯片的电源引脚没有连接到虚拟 VDD/VSS 网络,Proteus 里 STM32 默认不显示电源引脚,但部分版本元件放置后引脚网络标签确实能联通,部分版本则需要手动添加 POWER 引脚。
解决:右键芯片选择 Edit Properties,确认 Program File 路径正确。接着在 Design 菜单里选择 Configure Power Rails,检查 VDD 和 VSS 是否映射到了 3.3V 和 0V。如果还是没有反应,把系统运行速度调低到 100%,别用实时加速模式,仿真的 STM32 在加速模式下容易不执行。
5.2 OLED 显示花屏或者完全不亮
现象:I2C 通信的 OLED 模块在 Proteus 里显示乱码,或者一片黑。
原因:地址配置错误,换成 0x3D。时序初始化不完整,OLED 上电后需要先发送初始化序列,包括关闭显示、设置显示时钟、设置对比度、打开显示。还有可能是因为 I2C 速率设置太高,Proteus 模型在 400kHz 模式下响应不稳定。
解决:先把 I2C 速率降到 200kHz 以下。初始化序列必须完整执行,这个序列里的每个命令都缺一不可。如果你在实物上能正常显示,仿真里花屏,检查 Proteus 模型里 OLED 的 I2C 地址是否被映射过,有些模型默认是 0x3C,但部分修改版模型是 0x3D。
5.3 ADC 通道切换后数据不变
现象:两个 ADC 通道都使能了,但切换通道后读到的值永远是同一个。
原因:规则组通道配置后没有调用 ADC_SoftwareStartConvCmd 重新触发转换,或者扫描模式没有正确启用。如果你用了 ADC_SoftwareStartConvCmd(ADC1, ENABLE) 只触发一次,数据只刷新一遍,后面读的都是上次的缓存值。
解决:确认 ADC_ScanConvMode 设置为 ENABLE,然后在主循环里每次读取前先调用一次 ADC_SoftwareStartConvCmd,并且等待 EOC 标志位置位。还有一种写法是使用 DMA,连续转换完成之后由 DMA 自动搬运,这个效率更高,但要确保 DMA 缓冲区大小和 ADC 通道数一致。
5.4 DHT11 数据一直显示为零
现象:OLED 上温湿度显示 0,或者显示固定的 255。
原因:GPIO 模式配置错误。DHT11 的数据引脚需要先设为推挽输出模式发送起始信号,发送完成后立刻切换为输入模式读取数据。如果一直输出模式,读到的是自己拉低的电平。另外,上拉电阻不能省略,模拟时序读取时浮空输入很容易读到随机电平。
解决:在起始信号发送完毕之后,用 GPIO_Config 重新把 PA0 设置为上拉输入模式,而且要确保设置过程中没有多余的延时,否则会错过 DHT11 的响应信号。
5.5 光线检测逻辑完全反了
现象:光照变强时系统报警,光照变弱时反而正常。
原因:分压电路里光敏电阻位置放反了。光敏电阻在上、固定电阻在下,光照增强时光敏电阻阻值下降,ADC 电压上升。如果你接成固定电阻在上、光敏电阻在下,光照增强时分压电压反而降低。
解决:要么重新调整电路连接,要么把程序里的阈值判断逻辑反向。实物上焊接完成后发现接反,改电路成本高,直接在程序里把大于号改成小于号最快。
6. 仿真数据流验证:三个办法确认你的系统真的在正常工作
仿真跑通只是第一步,真正让人放心的是你能证明每一个传感器数据路径都是对的。我会用三个办法来做数据流验证,顺序固定,从简单到复杂,哪个环节出问题会直接暴露在数据上。
第一个办法是串口虚拟终端打印。在 Proteus 里添加 Virtual Terminal 元件,把 STM32 的 USART1 输出引脚接到 RXD 端,程序里每隔 1 秒打印一次所有原始数据。打印格式用纯文本,每行包含温度、湿度、光照 ADC 值、气体 ADC 值和报警状态。这样做的好处是,你修改光敏电阻旁边光照强度,能立刻看到 ADC 数值变化的方向和幅度。
第二个办法是改变传感器输入看响应。Proteus 的 DHT11 模型可以直接修改当前温度和湿度数值,光敏电阻的电阻值也可以直接改,MQ 传感器模型有的版本支持调节输出电压。我会把温度从 25 度调到 35 度,观察 OLED 数据和报警状态是否同步变化。如果 OLED 显示数值变了但报警阈值没触发,检查阈值比较逻辑里变量类型是不是被截断了,我遇到过 ADC 值用 uint16_t 存储但比较时被强制转换成 uint8_t 的情况。
第三个办法是验证程序里的滤波效果。把光敏电阻的阻值以非常快的速度在 5kΩ 和 15kΩ 之间来回切换,模拟光线突变。然后观察滤波后的数据变化。如果你发现滤波输出剧烈跳变,说明滑动平均窗口没有真正累加,或者 filter_index 没有更新,常见的 bug 是在中断里和主循环里同时操作了滤波缓冲区。这个问题在实际项目中很隐蔽,两个地方共同修改同一组数据,proteus 仿真时可能看不出问题,因为执行速度慢,冲突概率低,实物跑起来就会随机出故障。
我后来每个仿真项目结束前都会强制走一遍这三个验证步骤,尤其是第一条串口打印,基本能覆盖八成隐蔽问题。那之后我再没遇到过交付给别人的仿真工程里藏着数据路径错误的情况。希望这套排查习惯能帮到你,毕竟仿真图纸看着再漂亮,能稳定跑出真实数据才是真本事。
本文还有配套的精品资源,点击获取