☰
STM32粮仓环境监测系统设计:从传感器驱动到状态机完整解析
2026/10/26 10:28:39 网站建设 项目流程

简介:面向嵌入式毕业设计的STM32粮仓环境监测系统完整方案,包含源码工程、电路原理图与元器件清单,适合单片机课程设计、毕业设计及物联网环境监测项目二次开发。系统覆盖温湿度、火焰、烟雾、人体红外及二氧化碳多路采集,支持OLED实时显示、按键切换手动/自动运行、三路阈值设定与防盗模式,并通过ESP8266接入机智云APP实现远程监控与控制,功能链完整。压缩包内共145个文件,以64个C头文件、63个C源文件为主,涵盖外设驱动、机智云协议移植及主控逻辑;另有PDF说明文档、元器件清单Excel及Keil工程文件,源码结构清晰、注释规范,便于直接编译调试。资源包仅976KB,轻量易用,目前已有44人学习下载,对梳理传感器驱动、状态机设计与云平台对接流程具有较高参考价值。

1. 粮仓环境监测这套STM32毕设工程,值不值得拆开重做

粮仓最怕的不是天气,而是密闭空间里温湿度悄悄越界,等闻到霉味基本已经晚了。这套利用STM32实现的粮仓环境监测毕业设计,把温度、湿度、烟雾浓度等环境量采集上来,在单片机上完成阈值判断、声光报警和通风设备控制,源码、电路图、元器件清单都打包在一起。赶毕设进度的同学能直接照着复现,在职工程师也可以拿它当环境监测原型的参考模板。下面按选型、驱动、排错、工程到验证一路拆开讲,把引脚配置和容易翻车的细节一次性标清楚。

2. 系统架构与硬件选型:为什么STM32F103C8T6是毕业设计最稳的答案

粮仓环境监测的功能边界其实很固定:采集温湿度、采集烟雾浓度(可选光照),在屏幕和串口上实时显示,超过阈值时启动继电器或蜂鸣器。想明白边界,选型就不会纠结。这里按「信号链怎么走」和「电路图怎么读」两个小节展开,把选型理由和硬件复现的关键引脚一次性说清楚。

2.1 整体系统框架:从传感器到执行机构,数据是怎么流起来的

整套系统的信号链可以拆成四层。感知层负责把物理量变成电信号:DHT11输出温度和湿度,MQ-2模块的AO引脚输出约0.3V到3.3V的烟雾电压,光敏电阻配合分压电路把光照变成ADC电压。粮食在密闭仓内呼吸作用明显,粮堆容易局部发热、水分上移,所以温湿度是必测项;烟雾浓度用来覆盖粮食粉尘或设备线路过热这类突发情况,产粮区的实际项目里基本是标配。

主控层用STM32F103C8T6,Cortex-M3内核,72MHz主频,64KB Flash和20KB RAM。这套系统的代码量按模块拆分后不到20KB,RAM占用也只有几KB,剩余资源很充足。选它而不是C6T6,是因为后者Flash少了32KB,在带OLED字库和串口调试打印时会有紧迫感;选它而不是RCT6,是因为没必要多花引脚和板面,C8T6的最小系统板便宜、参考例程遍地都是,答辩时内核和外设的关系也容易讲清楚。

交互与执行层包含OLED(SSD1306,I2C接口,0.96寸)或LCD1602,蜂鸣器、继电器和三个按键。显示模块我通常建议选I2C OLED,驱动代码短,只占SCL/SDA两个引脚,能把更多IO留给传感器。如果手头只有TFT彩屏,问题也不大,但ILI9341这类屏首次读ID时容易返回A1A1而不是0x9341,多半是8080时序里CS、RD引脚接错或MISO没接好,这在显示章节还会再提。

数据流在主循环里是这么走的:2秒定时触发一次采样,DHT11读一次大约20多毫秒,ADC双通道串行转换在几百微秒内完成,所有读数放进一个EnvState结构体,显示、报警、串口三个模块共享这份数据。这样做的好处是每个模块都不需要重复访问GPIO,也不会因为频繁读取传感器而拖慢主循环。

2.2 元器件清单和电路图怎么读:最小系统、电源和四个必须确认的引脚

资源包里一般拆成源码工程、电路图和元器件清单(BOM)。BOM按电源、主控、传感器、执行、显示、被动元件分类,核对数量时直接对照下面的表:

类别主要器件规格/封装备注
电源AMS1117-3.3SOT-2235V转3.3V,输入端加100μF电解+100nF瓷片
主控STM32F103C8T6LQFP48可选用最小系统板
时钟8MHz无源晶振+22pF×2HC-49S负载电容是起振关键
复位10KΩ+100nF0603接NRST,低电平复位
温湿度DHT11模块3Pin/4Pin模块自带4.7KΩ上拉
烟雾MQ-2模块双排5PinAO接ADC,DO可选接GPIO
显示OLED SSD1306 0.96寸I2C地址0x3C或0x3D
执行5V继电器模块低电平触发线圈要加续流二极管
交互轻触按键×36×6×5按电路图处理上下拉

电路图阅读建议按「电源→复位→时钟→下载→外设」的顺序过一遍。电源部分,适配器5V进来先到AMS1117-3.3,输入输出端都要有100nF瓷片电容,输入端再加一个100μF电解电容用于吸收继电器吸合瞬间的电压跌落。如果继电器和单片机共用同一个5V轨,吸合瞬间会把3.3V拉扯下来,轻则OLED闪白,重则整板复位,这是后面避坑章要专门讲的一条。

复位电路要确认NRST引脚有10KΩ上拉到VDD、100nF电容下拉到地。很多从网上抄的电路图缺了这组RC,程序有时候能跑、烧录偶尔失败,看起来像玄学,根子其实在复位信号毛刺。时钟部分重点看8MHz晶振两边的22pF负载电容,取值过大会导致不起振;程序卡死在SystemInit时先怀疑它,用内部HSI能启动但串口波特率会飘,不能当长期方案。

下载接口处有四个引脚最容易翻车:BOOT0通过10K下拉到地,SWDIO加10K上拉,SWCLK加10K下拉,NRST保持复位电路完整。BOOT0如果悬空,上电瞬间可能进入系统存储器模式,程序怎么烧都跑不起来;SWDIO和SWCLK的上下拉则直接影响ST-Link的识别成功率。这几个引脚在实物焊接时优先处理,犯一次错能把整个调试周期拖掉好几天。

外设引脚的分配在工程设计阶段就要固定下来,避免和下载接口、I2C默认引脚冲突。一套常见的分配法是:OLED用PB8/PB9(I2C1),DHT11接PB1,烟雾ADC接PB0(ADC1_IN8),光敏ADC接PA1(ADC1_IN1),继电器接PB4,蜂鸣器接PB5,三个按键接PA2/PA3/PA4。上电后第一件事先用万用表量3.3V和5V两条电压轨,确认实际电压分别落在3.2V~3.5V、4.8V~5.2V范围内再接传感器,这个习惯能省掉后面一半的排障时间。

3. 传感器驱动与采集:把DHT11的时序和ADC通道切换写成可靠代码

传感器代码是整个项目里最容易看着能用、其实埋雷的部分。DHT11看着简单,但单总线协议对时序的要求藏在细节里;ADC部分则要处理通道切换、采样时间和数据滤波。这一章直接给出可抄的驱动写法。

3.1 DHT11单总线时序:起始信号、响应信号和40位数据怎么抠出来

DHT11只有一根数据线,协议全靠主机拉低、释放、读电平宽度来交换信息。主机先拉低至少18ms作为起始信号,释放后DHT11会在几十微秒内把线拉低80μs响应,再拉高80μs表示准备发数据。之后每个bit由一低一高组成,高电平持续约26~28μs是0,约70μs是1。40位数据的排列是湿度整数、湿度小数、温度整数、温度小数、校验和,校验和等于前四个字节相加的低8位,这个校验是排查数据错乱的重要依据。

下面的驱动用标准库实现,数据引脚是PB1。注意读bit时用「延时40μs后读电平」的方式,而不是测量整个高电平宽度,这样对延时函数精度的要求低一些,更抗干扰:

// dht11.c —— DHT11 单总线读取,数据脚 PB1 #include "stm32f10x.h" #include "dht11.h" #define DHT11_PORT GPIOB #define DHT11_PIN GPIO_Pin_1 #define DHT11_RCC RCC_APB2Periph_GPIOB static void DHT11_SetOutput(void) { GPIO_InitTypeDef gpio; gpio.GPIO_Pin = DHT11_PIN; gpio.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出,拉低时低电平足够干净 gpio.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(DHT11_PORT, &gpio); } static void DHT11_SetInput(void) { GPIO_InitTypeDef gpio; gpio.GPIO_Pin = DHT11_PIN; gpio.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入,与外部 4.7KΩ 并联 GPIO_Init(DHT11_PORT, &gpio); } // 读 1 位数据:等待高电平开始,延时 40us 后采样 static uint8_t DHT11_ReadBit(void) { uint16_t timeout = 0; while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) == 0) { if (++timeout > 100) return 0xFF; // 低电平超时,说明协议错乱 delay_us(1); } delay_us(40); // 高电平中点采样 return GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) ? 1 : 0; } uint8_t DHT11_Read(uint8_t *temp, uint8_t *humi) { uint8_t data[5] = {0}; uint16_t timeout; // 1. 起始信号:拉低至少 18ms,然后释放 DHT11_SetOutput(); GPIO_ResetBits(DHT11_PORT, DHT11_PIN); delay_ms(20); GPIO_SetBits(DHT11_PORT, DHT11_PIN); delay_us(30); DHT11_SetInput(); // 2. 等待 DHT11 拉低响应(80us 低电平的起始沿) timeout = 200; while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) == 1) { if (--timeout == 0) return 1; // 未收到响应 delay_us(1); } // 3. 等待响应低电平结束 timeout = 200; while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) == 0) { if (--timeout == 0) return 1; delay_us(1); } // 4. 读取 5 个字节:湿度整+小、温度整+小、校验 for (int i = 0; i < 5; i++) { for (int j = 0; j < 8; j++) { uint8_t bit = DHT11_ReadBit(); if (bit == 0xFF) return 2; // 位读取超时 data[i] = (data[i] << 1) | bit; } } // 5. 校验:前四字节累加的低 8 位应与第五字节一致 if (((uint8_t)(data[0] + data[1] + data[2] + data[3])) != data[4]) { return 3; } *humi = data[0]; *temp = data[2]; return 0; }

这段代码里有两个参数值得按实际模块调整。一个是起始信号的20ms,DHT11手册要求低电平保持18ms以上,留2ms余量是为了覆盖不同批次模块的个体差异,短了会让部分模块不响应。另一个是读bit时的40μs延时,0和1的高电平宽度差有40μs左右,延时40μs后读电平,只要延时误差在±15μs以内就能稳定区分,比测量整个高电平宽度的做法容忍度高不少。

读数据失败时先不要怀疑模块坏了。最常见的原因是两次读取间隔太短,DHT11采样周期要求大于1秒,连续读取会读到上次缓存的旧值。主循环里明明写了延时,但OLED显示和串口打印占用了太多时间,实际采样周期不到1秒,这种就是典型的「看着有延时,其实没延时」。我一般会把采样周期放到2秒,并在读取失败时返回旧值而不是把0xFF显示到屏上,至少看起来不会像死机。

注意:DHT11 两次读取间隔必须大于 1 秒,否则会读到上一次缓存的旧数据,表现为读数一直不变。

3.2 多通道ADC采集:烟雾浓度和光敏电压的换算与滤波

烟雾传感器MQ-2模块的AO输出是模拟电压,光敏电阻经过分压后也是模拟电压,两个信号都进STM32的ADC。F103C8T6内置两个12位ADC,规则组同一时刻只能转换一个通道,多通道的做法是每次读取前切换通道、启动转换、等EOC标志后再取结果。ADC时钟要配置成不超过14MHz,72MHz主频分频后取12MHz是常用值。

// adc.c —— ADC1 双通道读取,PB0(烟雾 IN8)、PA1(光敏 IN1) #include "stm32f10x.h" #include "adc.h" void ADC1_Init(void) { GPIO_InitTypeDef gpio; ADC_InitTypeDef adc; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_ADC1, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 72MHz / 6 = 12MHz,小于 ADC 上限 14MHz gpio.GPIO_Mode = GPIO_Mode_AIN; // 模拟输入,必须禁用数字上下拉 gpio.GPIO_Pin = GPIO_Pin_0; // PB0 GPIO_Init(GPIOB, &gpio); gpio.GPIO_Pin = GPIO_Pin_1; // PA1 GPIO_Init(GPIOA, &gpio); adc.ADC_Mode = ADC_Mode_Independent; adc.ADC_DataAlign = ADC_DataAlign_Right; adc.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 软件触发 adc.ADC_NbrOfChannel = 1; // 规则组每次转一个通道 ADC_Init(ADC1, &adc); ADC_Cmd(ADC1, ENABLE); } uint16_t ADC_ReadChannel(uint8_t ch) { // 通道号对应:ADC1_IN8 -> ch=8,ADC1_IN1 -> ch=1 ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5); ADC_SoftwareStartConvCmd(ADC1, ENABLE); while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); return ADC_GetConversionValue(ADC1); }

采样时间这里我习惯直接用ADC_SampleTime_239Cycles5,在12MHz时钟下大约是20μs一个点。相比55.5周期的短采样,长采样时间能明显降低信号源内阻带来的采样误差,光敏电阻分压回路的内阻经常到几十千欧,短采样会导致读数整体偏低。用长采样后ADC值会稳不少,这是从数据手册里能读出来的细节,不用靠猜。

原始ADC值是0~4095,要换算成电压再判断阈值:

// 滤波 + 电压换算:去掉最大最小值再求平均,抗现场毛刺 uint16_t ADC_ReadAvg(uint8_t ch) { uint32_t sum = 0; uint16_t min = 4095, max = 0, val; for (uint8_t i = 0; i < 8; i++) { val = ADC_ReadChannel(ch); sum += val; if (val < min) min = val; if (val > max) max = val; } return (sum - min - max) / 6; } float smoke_voltage = 3.3f * ADC_ReadAvg(8) / 4096.0f; float light_voltage = 3.3f * ADC_ReadAvg(1) / 4096.0f;

MQ-2的AO电压和浓度不是线性关系,报警阈值直接用电压值就行。有一个现场标定的笨办法但很好用:先在新鲜空气下记录一个电压基线,再用打火机气体短暂靠近传感器记录峰值,报警阈值取两者中间偏上一点。光敏电阻那边同理,白天夜晚两个基线一测,阈值自然出来了。另外要注意,烟雾传感器上电后要预热几分钟,刚上电时AO电压会偏高,驱动初始化里最好把第一次读取延迟到上电后几分钟,否则开机就会误报。

4. 避坑与排查:五个让粮仓监测系统翻车的细节

毕业设计实物最容易出问题的不是逻辑设计,而是电源、时序、接地、引脚电平这类「电学玄学」。这一章列五条实际踩坑记录,现象、原因、解决三步写清楚,方便你在调试现场直接对上号。

4.1 DHT11读回数据全是0xFF

现象:温湿度显示255,或者连续很久读数不变。原因多数是数据引脚没上拉或上拉电阻过大、GPIO被配置成浮空输入、起始信号拉低时间不足18ms。DHT11对手册时序要求很敏感,延时函数如果用空循环猜出来的,误差超过几十微秒就会翻车。

解决:先在数据脚外面并一个4.7KΩ上拉,模块自带的也要确认焊上了;GPIO在上电后设为IPU上拉输入;起始信号拉低时间给到20ms;延时函数换SysTick或定时器实现的微秒级延时。做完这四步,0xFF基本消失。

排查定位时,如果手头有逻辑分析仪,直接抓数据线波形,看主机起始的低电平宽度是不是真的达到了20ms,再看响应沿是否完整。很多时候是杜邦线太长导致边沿变缓,换15cm以内的短线就好。

4.2 继电器一吸合,整板复位

现象:继电器动作的瞬间OLED闪白,或者程序直接从main开头重新跑。原因是继电器线圈是感性负载,断开瞬间会产生反向电动势,直接灌到电源轨上,把3.3V拉到复位门限以下。

解决:继电器模块线圈两端并联一个1N4148或1N4007续流二极管,负极接电源正;继电器单独用一路5V供电,不跟OLED、传感器共用;电源适配器选输出电流1.5A以上。验证方法是用万用表量5V电压在继电器吸合瞬间的跌落值,如果跌落超过300mV,就说明电源余量不足,必须换供电方案。我后来把继电器放到PCB远端、控制线单独走,复位问题几乎绝迹。

4.3 OLED白屏或乱码

现象:SSD1306 OLED上电后白屏,或者第一行有内容但明显错位。原因通常是I2C地址不匹配,0x3C和0x3D都有厂家在用;SCL时钟配置超过了400kHz;模块是5V电平而MCU是3.3V,信号被拉到不确定电平。

解决:先用I2C扫描程序打印实际地址,再把驱动宏改成对应地址;I2C时钟压到400kHz以下;确认模块额定电压,用万用表量VCC引脚。另一个隐蔽原因是OLED的RESET引脚悬空,SSD1306的复位脚要拉高或直接连到GPIO控制,悬空时上电时序不对就容易白屏。

4.4 ST-Link下载失败:No target connected

现象:Keil里点下载,提示找不到目标,或连接后立刻断开。原因可能是SWDIO没有上拉、SWCLK没有下拉,下载器识别不稳;也可能是复位电路缺RC导致NRST有毛刺;最隐蔽的一种是SWDIO和SWCLK被复用成了普通IO,并且程序里把它们拉低了,直接把下载时序钳死。

解决:按住复位键点击下载,看到开始后才松开,这是让下载器和芯片同步最直接的办法;检查SWDIO 10K上拉、SWCLK 10K下拉;用万用表确认3.3V和NRST电平。如果换过一根下载线就能连上,那大概率是杜邦线太长或接触不良,换短线和屏蔽线即可。

4.5 ADC数值乱跳

现象:光敏读数白天从几百跳到两千,烟雾电压波动大,报警乱触发。原因是引脚悬空、采样时间太短导致采样电容没充满、转换过程中被中断打断,以及VREF周围缺少去耦电容。

解决:先确认引脚确实接了传感器电压,再把GPIO配成AIN;采样时间从55.5Cycles改成239.5Cycles;读取函数里关闭可抢占中断或等转换完成再进中断;在VREF引脚加100nF和10μF电容。最后做一次8点去极值平均,读出来的数值肉眼可见地稳。如果光敏引脚直接悬空,读出来的值会在全范围乱跳,这是判断问题根源最直接的信号。

5. 应用层与工程结构:主循环状态机、阈值报警和显示刷新怎么组织

传感器驱动能用了之后,整个工程的骨架才真正决定毕业设计好不好演示。状态机怎么写、全局变量怎么收敛、显示怎么刷新,这些比驱动代码更容易在回答老师提问时讲清楚。

5.1 工程组织:标准库还是HAL、模块怎么切、全局变量怎么收敛

工程组织风格因人而异,但答辩更看重条理。用标准库时,建议按功能切成独立模块,驱动层只暴露「读温度湿度」「读ADC电压」这类接口,应用层只负责阈值判断、显示和报警,不直接碰寄存器。

project/ ├── Core/ │ ├── main.c // 初始化 + 主循环 │ ├── stm32f10x_it.c // 中断服务 │ └── delay.c / delay.h // SysTick 延时 ├── Drivers/ │ ├── dht11.c / dht11.h // 温湿度读取 │ ├── adc.c / adc.h // 烟雾/光敏 ADC │ ├── oled.c / oled.h // SSD1306 I2C 显示 │ ├── relay.c / relay.h // 继电器控制 │ ├── key.c / key.h // 按键扫描 │ └── beep.c / beep.h // 蜂鸣器 ├── App/ │ ├── app.c / app.h // 主状态机 │ └── env.h // 环境数据结构体 └── Hardware/ ├── 电路图.pdf └── 元器件清单.xlsx

全局变量是裸机工程后期维护的大敌。更稳的方案是定义一个环境状态结构体,把所有传感器读数、阈值、报警状态都放进去,由app模块统一更新:

// env.h —— 环境数据聚合 typedef struct { uint8_t temperature; // 温度,单位 ℃ uint8_t humidity; // 湿度,单位 %RH float smoke_volt; // 烟雾电压,单位 V uint8_t light_level; // 光照等级 0~3 uint8_t relay_on; // 继电器状态 uint8_t beep_on; // 蜂鸣器状态 } EnvState; extern EnvState g_env;

用结构体的好处是访问路径清晰,调试时在Keil的Watch窗口里直接展开g_env就能看到全部状态,不用满屏找全局变量。结构体成员更新都收敛在app模块内,驱动层只返回局部结果、不写g_env,数据流向单一,后期加阈值调整功能时不容易改挂。

5.2 主循环状态机:按键、显示、报警和控制逻辑不打架的关键

裸机主循环最常见的写法是在while(1)里依次调用显示、按键、传感器、报警,看起来每件事都做了,实际表现是按键偶尔失灵、显示偶尔闪烁。原因是传感器读取是阻塞的,一次20多毫秒的DHT11读取会把按键扫描和显示刷新全部卡住。我一般用一个10ms时基驱动的主状态机,把耗时操作限定在MEASURE状态内,让按键和显示在剩余时间里轮流执行:

// app.c —— 主状态机:每 10ms 调用一次 App_Tick #define MEASURE_PERIOD 200 // 10ms * 200 = 2s 采集一次 typedef enum { STATE_INIT = 0, // 上电初始化 STATE_IDLE, // 待机 STATE_MEASURE, // 读取传感器(阻塞) STATE_ALARM, // 阈值判断 + 控制 STATE_DISPLAY, // 刷新显示 + 串口 } AppState; static AppState s_state = STATE_INIT; static uint16_t s_tick = 0; void App_Tick(void) { s_tick++; switch (s_state) { case STATE_INIT: Sensor_Init(); Relay_Init(); OLED_Init(); s_state = STATE_IDLE; break; case STATE_IDLE: if (s_tick >= MEASURE_PERIOD) { s_tick = 0; s_state = STATE_MEASURE; } break; case STATE_MEASURE: // DHT11 读取 + ADC 双通道,整个状态耗时约 30ms env.temperature = DHT11_GetTemp(); env.humidity = DHT11_GetHumi(); env.smoke_volt = 3.3f * ADC_ReadAvg(8) / 4096.0f; env.light_level = VoltageToLevel(3.3f * ADC_ReadAvg(1) / 4096.0f); s_state = STATE_ALARM; break; case STATE_ALARM: // 温湿度越限吸合继电器,烟雾越限响蜂鸣器 if (env.temperature > g_thr.temp_max || env.humidity > g_thr.humi_max) { Relay_On(); env.relay_on = 1; } else { Relay_Off(); env.relay_on = 0; } env.beep_on = (env.smoke_volt > g_thr.smoke_max); Beep_Set(env.beep_on); s_state = STATE_DISPLAY; break; case STATE_DISPLAY: OLED_Refresh(&env); UART_SendEnv(&env); s_state = STATE_IDLE; break; } }

状态机的关键是MEASURE和DISPLAY这两个阻塞状态加起来也只有几十毫秒,其余时间都在IDLE里空转检查s_tick和按键。按键扫描即使放在主循环里,也不会出现按一下没反应的情况。阈值g_thr是全局配置,由按键调整:短按「设置」进入调整态,用加减键改当前项,长按退出并保存到Flash。这个交互逻辑独立于主状态机,是一个小的子流程。

显示刷新这里有个细节:OLED SSD1306在I2C下整屏刷新大约需要30ms,如果高频率刷新,屏幕会肉眼可见地闪,还占用不少CPU。做法是只在数据变化超过一个显示单位时才局部刷新对应行,温度、湿度、烟雾电压、状态四行分别用设置光标和显示字符串函数更新,没变化的行不重绘。I2C总线上不传整屏缓冲,显示稳定性和代码复杂度的平衡点就在这。

如果换到ILI9341这类TFT屏,首次读ID返回值经常是A1A1而不是预期的0x9341,多数是8080时序里RD、CS、WR引脚接错或者MISO没接好。毕业设计显示数字信息用OLED更划算,I2C两根线就解决,不用去调16位并口的信号时序。

6. 进阶与验证:从「能跑」到「可靠」的最后一公里

毕业设计最怕的不是功能没做完,而是演示现场传感器数据跳变、报警乱响。代码已经能跑之后,接下来要做的就是校准和可靠性验证。校准分三步:温度用水水混合物0℃和沸水100℃修正DHT11的偏差,湿度用饱和盐水75.3%RH当参照点,烟雾阈值用打火机气体实测。每项改完把修正量写进一个校准宏,硬件不变时不许随手改。

可靠性验证我通常跑三项:连续48小时数据记录,看温湿度曲线有没有规律跳变;快速插拔继电器负载100次,看会不会复位;电源电压从5V缓慢降到3.0V再回升,确认复位门限正常。这三项过了,演示基本不会翻车。

升级方向按需选。想在手机上看数据,可以考虑ESP8266或ESP-01通过串口AT指令接入云平台,比如巴法云这类IoT服务,STM32把温湿度打包成JSON上行,云平台下发阈值,STM32侧解析后更新g_thr。做中文显示时注意STM32发送的中文要约定好GBK或UTF-8编码,和云端不一致会出现乱码,排查起来要费不少时间。想进一步多任务,可以把DHT11读取放到FreeRTOS的传感器任务里,显示和报警各自独立,但一套裸机状态机对毕业设计已经足够。

从那以后,我每次做环境监测类的东西,都强制走一遍三件事:先拿万用表把每条电压轨量一遍,再跑一次传感器自检,最后空转48小时看曲线。三步做完才敢上演示。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询