你是否遇到过这样的场景想做一个简单的环境监测项目比如监控花房温湿度、记录仓库环境数据或者只是学习嵌入式开发却发现网上教程要么过于简单只讲原理要么过于复杂直接上RTOS和云平台中间缺少一个能跑通、能理解、能复用的完整实践案例这正是很多单片机初学者和物联网爱好者的共同痛点知道DHT11能测温湿度知道OLED能显示也知道STM32很强大但如何将它们有机地组合成一个稳定可靠的“温湿度检测系统”并理解其中的每一个技术细节和潜在陷阱却往往缺少一份“手把手”的指南。本文将为你彻底解决这个问题。我们不只讲“是什么”更聚焦“为什么”和“怎么做错”。我将基于一个经典的STM32F103C8T6核心板 DHT11温湿度传感器 0.96寸OLED显示屏的组合带你从零构建一个完整的温湿度检测系统。更重要的是我会深入剖析那些教程里通常一笔带过的关键点DHT11单总线协议的严格时序如何用代码精准实现I2C驱动OLED时地址冲突怎么办如何用状态机思想让传感器读取更稳定以及如何利用Proteus进行无实物仿真验证读完本文你将获得一套可直接移植的、基于STM32 HAL库的完整工程代码包含模块化驱动和清晰注释。对DHT11通信协议和OLED显示原理的深度理解而不仅仅是复制代码。一套系统的调试和排查方法论当你的屏幕不亮、传感器读数全为0时知道第一步该查哪里。利用Proteus进行虚拟仿真的能力在焊接硬件前验证逻辑的正确性。这个项目麻雀虽小五脏俱全涵盖了嵌入式开发中GPIO控制、精密延时、单总线协议、I2C通信、数据格式化、模块化编程等核心技能是初学者夯实基础的绝佳练手项目也是开发者快速搭建环境监测原型的实用参考。1. 系统核心为什么是STM32DHT11OLED在开始动手前我们需要明确这个技术选型背后的逻辑。市面上温湿度传感器有DHT11、DHT22、SHT30、AM2302等显示模块有LCD1602、LCD12864、OLED等主控更有51、STM32、ESP32等多种选择。为什么我们这个项目采用这样的组合1.1 DHT11性价比与精度的平衡DHT11是一款经典的温湿度复合传感器采用单总线1-Wire协议通信。它的优势在于成本极低非常适合学习和低成本项目。接口简单仅需一个GPIO口即可完成数据读写节省单片机资源。集成度高内部已包含校准的数字化输出无需复杂的模拟电路处理。 其缺点是精度相对一般温度±2℃湿度±5%RH响应速度较慢约2秒一次。但对于大多数室内环境监测、农业大棚、仓库报警等场景这个精度完全足够。学习它的关键在于掌握其严格的单总线时序这是后续一切工作的基础。1.2 OLED显示屏清晰与低功耗的体现我们选用0.96寸I2C接口的OLED屏原因如下自发光高对比度在暗环境下显示效果远胜于需要背光的LCD。功耗低特别适合电池供电的便携设备。I2C接口节省引脚仅需两根线SCL SDA与DHT11一样能最大化利用主控有限的GPIO资源。丰富的图形库支持可以轻松显示文字、数字、图形甚至动画提升项目观感。需要注意I2C OLED通常有0x78和0x7A两种设备地址驱动前必须确认这是第一个常见的坑点。1.3 STM32F103C8T6蓝桥杯/最小系统板性能与生态的保障选择STM32F1系列特别是“蓝色药丸”核心板是因为性能足够72MHz主频20KB RAM64KB Flash处理传感器数据和刷新屏幕绰绰有余。生态强大STM32拥有最完善的HAL库、标准库支持资料和社区资源极其丰富。开发工具成熟ST-Link调试器价格亲民Keil、STM32CubeIDE等IDE支持良好。引脚丰富提供了足够的GPIO来连接我们的传感器和屏幕并为未来扩展如添加蜂鸣器报警、Wi-Fi模块留有余地。这个组合形成了一个高性价比、低功耗、易于学习且具备实用价值的经典方案。接下来我们将深入每个模块的核心原理。2. 核心原理深度剖析通信协议是成败关键很多项目失败不是因为代码逻辑错误而是对底层通信协议的理解浮于表面。本章节将深入DHT11的单总线协议和OLED的I2C协议让你知其然更知其所以然。2.1 DHT11单总线协议毫秒级时序的精准掌控DHT11的通信流程分为主机MCU发起和传感器响应两部分全部由一根数据线完成。时序要求非常严格误差超过几十微秒就可能导致读取失败。通信全过程解析主机发起起始信号MCU将数据线拉低至少18毫秒ms然后拉高20-40微秒µs随后释放总线设置为输入模式等待传感器响应。传感器响应信号DHT11检测到起始信号后会先将总线拉低80µs再拉高80µs表示它已准备好发送数据。数据传输随后传感器连续发送40位数据5个字节。每一位数据都以一个50µs的低电平起始位开始随后的高电平持续时间决定该位是0还是1。位‘0’高电平持续约26-28µs。位‘1’高电平持续约70µs。数据格式40位数据包含字节1湿度整数部分。字节2湿度小数部分DHT11为0。字节3温度整数部分。字节4温度小数部分DHT11为0。字节5校验和前4个字节相加的低8位。关键难点与解决方案难点1如何实现µs级精准延时在STM32中直接使用HAL_Delay()毫秒级是不行的。我们需要用系统滴答定时器SysTick或通用定时器TIM来实现微秒延时函数。难点2如何可靠读取每一位必须在起始低电平后等待高电平到来然后计时高电平的持续时间。使用循环查询方式并设置超时判断防止程序卡死。难点3如何提高读取稳定性单次读取失败是常见的。最佳实践是实现一个带错误处理和重试机制的读取函数例如连续读取5次取其中校验成功的3次结果的平均值。2.2 OLED的I2C协议地址与数据帧I2C是一种同步、半双工、多主多从的串行总线。驱动OLED我们主要关注主机MCU向从机OLED写数据。通信流程起始条件SCL为高时SDA由高变低。发送设备地址发送7位设备地址例如0x78和1位读写位0表示写。等待应答OLED会拉低SDA作为应答ACK。发送控制字节决定后续是命令0x00还是数据0x40。发送数据/命令每发送一个字节等待一个ACK。停止条件SCL为高时SDA由低变高。关键注意点地址确认使用I2C扫描代码确认你的OLED地址是0x78还是0x7A。初始化序列OLED上电后需要发送一系列初始化命令来设置对比度、扫描方式、显示开/关等。这部分代码通常由厂家提供需要完整执行。数据/命令区分每次发送数据前都需要先发送控制字节来指明类型这是驱动OLED的固定套路。理解了这些底层原理我们就能写出健壮、可靠的驱动代码而不是碰运气式的“复制-粘贴-祈祷它能工作”。3. 硬件连接与开发环境搭建3.1 硬件清单与连接图主控STM32F103C8T6 核心板 x1传感器DHT11 温湿度传感器模块 x1显示0.96寸 I2C OLED 显示屏 (SSD1306驱动) x1调试/供电ST-Link V2 调试器、USB数据线、杜邦线若干连接方式非常重要接错必失败STM32引脚连接至备注3.3VDHT11 VCC, OLED VCC务必接3.3V接5V可能烧毁OLED。GNDDHT11 GND, OLED GND共地。PB8 (可自定义)DHT11 DATA用于单总线通信。PB6OLED SCLI2C1时钟线。PB7OLED SDAI2C1数据线。为什么选择这些引脚DHT11的DATA引脚可以接任何具有中断或普通输入输出能力的GPIO这里选择PB8方便演示。PB6和PB7是STM32F103C8T6的I2C1接口的复用引脚使用硬件I2C比模拟I2C更稳定高效。3.2 软件开发环境准备IDE安装STM32CubeIDE免费集成CubeMX推荐或Keil MDK需要注册。STM32CubeMX用于图形化配置引脚、时钟、外设。如果使用CubeIDE它已内置。驱动安装ST-Link的USB驱动。源码准备OLED的驱动库通常为ssd1306.c/h和fonts.c/h和DHT11的驱动文件。3.3 使用STM32CubeMX创建工程这是避免底层配置错误的关键一步。打开CubeMX选择MCU型号STM32F103C8Tx。配置时钟在RCC中将HSE设置为Crystal/Ceramic Resonator。在Clock Configuration标签页将系统时钟源设置为HSE并配置系统时钟为72MHz。配置GPIO找到PB8将其设置为GPIO_Output初始输出高电平并重命名为DHT11_DATA。找到PB6和PB7它们会自动被I2C1占用。将I2C1的模式设置为I2C。配置调试接口在SYS中将Debug设置为Serial Wire否则烧录一次后可能无法再次调试。生成代码在Project Manager中设置工程名、路径、选择IDESTM32CubeIDE并将HAL库的时基源Timebase Source选为一个除了SysTick以外的定时器如TIM1避免与HAL_Delay和我们的微秒延时函数冲突。最后点击GENERATE CODE。至此一个基础工程框架就创建好了它帮我们正确配置了时钟树和引脚复用。接下来我们将进入核心的代码实现部分。4. 驱动层代码实现从微秒延时到数据读取我们将代码分为三层硬件抽象层延时、GPIO控制、设备驱动层DHT11、OLED、应用层主逻辑。首先从最底层的微秒延时开始。4.1 实现精准的微秒延时函数由于HAL库的HAL_Delay()精度是毫秒我们需要自己实现一个微秒延时函数。这里利用系统滴答定时器SysTick来实现。// 文件delay_us.c #include delay_us.h static uint32_t fac_us 0; // 微秒延时倍乘数 /** * brief 初始化延迟函数需在系统时钟设置完成后调用 * param sysclk: 系统时钟频率单位Hz (例如72000000) * retval None */ void delay_init(uint32_t sysclk) { // SysTick频率设置为系统时钟的1/8再除以1000000得到1us需要的计数值 HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK_DIV8); fac_us sysclk / 8000000; } /** * brief 微秒级延时 * param nus: 要延时的微秒数 * retval None */ void delay_us(uint32_t nus) { uint32_t ticks; uint32_t told, tnow, tcnt 0; uint32_t reload SysTick-LOAD; // LOAD的值 ticks nus * fac_us; // 需要计时的节拍数 told SysTick-VAL; // 刚进入时的计数器值 while (1) { tnow SysTick-VAL; if (tnow ! told) { if (tnow told) { tcnt told - tnow; // 注意SYSTICK是递减计数器 } else { tcnt reload - tnow told; } told tnow; if (tcnt ticks) { break; // 时间超过/等于要延迟的时间则退出 } } } }对应的头文件delay_us.h需要声明这两个函数。在main.c的SystemClock_Config()函数调用之后调用delay_init(72000000)进行初始化。4.2 DHT11驱动实现这是整个项目的核心难点。我们将严格按照协议时序编写代码并加入超时检测和校验。// 文件dht11.c #include dht11.h #include delay_us.h #define DHT11_DATA_PIN GPIO_PIN_8 #define DHT11_DATA_PORT GPIOB // 设置数据线为输出模式 static void DHT11_Set_Output(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin DHT11_DATA_PIN; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(DHT11_DATA_PORT, GPIO_InitStruct); } // 设置数据线为输入模式带上拉 static void DHT11_Set_Input(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin DHT11_DATA_PIN; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(DHT11_DATA_PORT, GPIO_InitStruct); } // 读取DHT11一个位的值 static uint8_t DHT11_Read_Bit(void) { uint8_t retry 0; // 等待低电平起始信号结束约50us while (HAL_GPIO_ReadPin(DHT11_DATA_PORT, DHT11_DATA_PIN) GPIO_PIN_RESET) { delay_us(1); if (retry 100) return 0xFF; // 超时返回错误 } delay_us(40); // 跳过起始信号后的高电平26-28us for 0, 70us for 1取中间值等待 // 判断高电平持续时间 if (HAL_GPIO_ReadPin(DHT11_DATA_PORT, DHT11_DATA_PIN) GPIO_PIN_SET) { // 高电平还在说明是‘1’ retry 0; while (HAL_GPIO_ReadPin(DHT11_DATA_PORT, DHT11_DATA_PIN) GPIO_PIN_SET) { delay_us(1); if (retry 100) break; } return 1; } else { // 高电平已结束说明是‘0’ return 0; } } // 读取DHT11一个字节的值 static uint8_t DHT11_Read_Byte(void) { uint8_t i, byte 0; for (i 0; i 8; i) { byte 1; byte | DHT11_Read_Bit(); } return byte; } /** * brief 读取DHT11的温湿度数据 * param temp: 温度值指针整数部分 * param humi: 湿度值指针整数部分 * retval 状态: 0成功1失败 */ uint8_t DHT11_Read_Data(uint8_t *temp, uint8_t *humi) { uint8_t buf[5] {0}; uint8_t i, checksum; // 1. 主机发出起始信号 DHT11_Set_Output(); HAL_GPIO_WritePin(DHT11_DATA_PORT, DHT11_DATA_PIN, GPIO_PIN_RESET); delay_us(18000); // 拉低至少18ms HAL_GPIO_WritePin(DHT11_DATA_PORT, DHT11_DATA_PIN, GPIO_PIN_SET); delay_us(30); // 拉高20-40us // 2. 释放总线等待传感器响应 DHT11_Set_Input(); // 3. 等待传感器响应低电平80us uint8_t retry 0; while (HAL_GPIO_ReadPin(DHT11_DATA_PORT, DHT11_DATA_PIN) GPIO_PIN_SET) { delay_us(1); if (retry 100) return 1; // 等待高电平超时 } retry 0; while (HAL_GPIO_ReadPin(DHT11_DATA_PORT, DHT11_DATA_PIN) GPIO_PIN_RESET) { delay_us(1); if (retry 100) return 1; // 等待低电平超时 } // 4. 等待传感器响应高电平80us retry 0; while (HAL_GPIO_ReadPin(DHT11_DATA_PORT, DHT11_DATA_PIN) GPIO_PIN_SET) { delay_us(1); if (retry 100) return 1; // 等待高电平超时 } // 5. 开始接收40位数据 for (i 0; i 5; i) { buf[i] DHT11_Read_Byte(); } // 6. 校验数据 checksum buf[0] buf[1] buf[2] buf[3]; if (checksum ! buf[4]) { return 1; // 校验失败 } *humi buf[0]; *temp buf[2]; return 0; // 读取成功 }头文件dht11.h需要声明DHT11_Read_Data函数。这段代码的关键在于DHT11_Read_Bit函数中对高电平持续时间的判断逻辑以及每一步的超时处理这极大地增强了程序的鲁棒性。4.3 OLED驱动集成与显示函数OLED驱动通常使用现成的库如SSD1306。我们需要将其适配到我们的HAL库I2C接口上。// 文件ssd1306.c (部分关键适配代码) #include ssd1306.h #include main.h // 包含hi2c1的定义 extern I2C_HandleTypeDef hi2c1; // 在main.c中定义的I2C句柄 // 向OLED发送命令I2C方式 void ssd1306_WriteCommand(uint8_t command) { uint8_t buf[2] {0x00, command}; // 控制字节0x00表示命令 HAL_I2C_Master_Transmit(hi2c1, SSD1306_I2C_ADDR, buf, 2, HAL_MAX_DELAY); } // 向OLED发送数据I2C方式 void ssd1306_WriteData(uint8_t* data, size_t length) { uint8_t *buf malloc(length 1); if (buf NULL) return; buf[0] 0x40; // 控制字节0x40表示数据 memcpy(buf[1], data, length); HAL_I2C_Master_Transmit(hi2c1, SSD1306_I2C_ADDR, buf, length 1, HAL_MAX_DELAY); free(buf); } // 初始化OLED void ssd1306_Init(void) { // 发送一系列初始化命令如关闭显示、设置时钟分频、复用率、对比度等 HAL_Delay(100); // 等待OLED上电稳定 ssd1306_WriteCommand(0xAE); // 关闭显示 ssd1306_WriteCommand(0xD5); ssd1306_WriteCommand(0x80); // 设置时钟分频 ssd1306_WriteCommand(0xA8); ssd1306_WriteCommand(0x3F); // 设置复用率 // ... 更多初始化命令 ssd1306_WriteCommand(0xAF); // 最终开启显示 ssd1306_Fill(Black); // 清屏 ssd1306_UpdateScreen(); // 更新显示 } // 在指定位置显示字符串 void ssd1306_ShowString(uint8_t x, uint8_t y, char* str, FontDef font, SSD1306_COLOR color) { while (*str) { if (x font.width SSD1306_WIDTH) { x 0; y font.height; } ssd1306_DrawChar(x, y, *str, font, color); x font.width; str; } ssd1306_UpdateScreen(); }你需要将完整的ssd1306.c/h和fonts.c/h文件添加到工程中并确保ssd1306.h中SSD1306_I2C_ADDR的定义与你的OLED地址匹配通常是(0x78 1)或(0x7A 1)HAL库要求左移一位。5. 应用层逻辑与主程序整合驱动层准备好后应用层的逻辑就非常清晰了循环读取传感器数据并刷新到屏幕上。// 文件main.c (部分关键代码) #include main.h #include dht11.h #include ssd1306.h #include fonts.h #include stdio.h I2C_HandleTypeDef hi2c1; // ... 其他外设句柄 // 用于格式化字符串的缓冲区 char display_str[32]; int main(void) { HAL_Init(); SystemClock_Config(); delay_init(72000000); // 初始化微秒延时 MX_GPIO_Init(); MX_I2C1_Init(); // I2C初始化由CubeMX生成 // 初始化OLED ssd1306_Init(); ssd1306_SetCursor(0, 0); ssd1306_ShowString(0, 0, DHT11 System, Font_11x18, White); ssd1306_ShowString(0, 20, Initializing..., Font_7x10, White); HAL_Delay(2000); ssd1306_Fill(Black); uint8_t temperature 0; uint8_t humidity 0; uint8_t read_status 0; while (1) { // 1. 读取DHT11数据 read_status DHT11_Read_Data(temperature, humidity); // 2. 清屏并显示数据 ssd1306_Fill(Black); if (read_status 0) { // 读取成功格式化字符串 snprintf(display_str, sizeof(display_str), Temp: %d C, temperature); ssd1306_ShowString(10, 10, display_str, Font_11x18, White); snprintf(display_str, sizeof(display_str), Humi: %d %%, humidity); ssd1306_ShowString(10, 35, display_str, Font_11x18, White); ssd1306_ShowString(20, 55, Status: OK, Font_7x10, White); } else { // 读取失败 ssd1306_ShowString(10, 25, Read Error!, Font_11x18, White); ssd1306_ShowString(15, 50, Check Sensor, Font_7x10, White); } // 3. 延时2秒DHT11两次读取间隔需大于1秒 HAL_Delay(2000); } }6. 系统运行、验证与Proteus仿真6.1 硬件运行与验证按照第3章的连接图用杜邦线连接好硬件。使用ST-Link连接电脑和STM32核心板。在IDE中编译工程无错误后下载程序到单片机。复位或上电后观察OLED屏幕。如果一切正常你将看到温度和湿度数据每隔2秒更新一次。用手触摸DHT11传感器温度值应有小幅上升向传感器哈气湿度值应显著上升。6.2 利用Proteus进行虚拟仿真无实物验证对于没有硬件的学习者Proteus仿真是一个极佳的选择。它可以在电脑上模拟整个系统运行。仿真步骤绘制原理图在Proteus ISIS中添加元件STM32F103C6C8在库中可能没有C6引脚兼容。DHT11在传感器分类中。OLED12864-I2C在显示器分类中。RES电阻、POWER、GROUND。连接电路DHT11的DATA脚接STM32的PB8。OLED的SCL、SDA分别接STM32的PB6、PB7。VCC接3.3VGND接地。加载程序双击STM32芯片在Program File一栏选择你在Keil或CubeIDE生成的HEX文件例如Project.hex。运行仿真点击左下角的运行按钮。Proteus会模拟运行你的程序你可以在虚拟的OLED屏幕上看到温湿度数据。DHT11元件上可以右键设置仿真的温湿度值用于测试。仿真的意义它让你在焊接硬件前就能验证GPIO控制逻辑、通信时序和显示逻辑是否正确极大降低了学习成本和调试难度。7. 常见问题与深度排查指南遇到问题不要慌按照以下清单系统性排查问题现象可能原因排查步骤解决方案OLED屏幕不亮1. 电源接错接了5V2. I2C地址不对3. 初始化序列错误4. 硬件I2C引脚配置错误1. 用万用表测量OLED VCC电压是否为3.3V。2. 编写I2C扫描程序确认设备地址。3. 检查ssd1306_Init()中的命令序列是否完整。4. 在CubeMX中确认PB6、PB7已正确配置为I2C1。1. 改接3.3V。2. 修改SSD1306_I2C_ADDR定义。3. 对照数据手册检查初始化命令。4. 重新生成CubeMX代码。OLED显示乱码或不全1. 字体文件未正确包含或调用2. 显示缓冲区未更新3. 显示坐标超出屏幕范围1. 检查fonts.h是否被包含字体数组是否完整。2. 确保在绘制后调用了ssd1306_UpdateScreen()。3. 检查ssd1306_ShowString函数中的x, y坐标计算。1. 确保字体文件被添加到工程并参与编译。2. 在每次绘制操作后调用更新函数。3. 添加坐标边界检查逻辑。DHT11读取始终失败返回255或01. 时序不精确2. 上拉电阻问题模块通常已集成3. 电源不稳定4. 传感器损坏1. 用逻辑分析仪或示波器抓取DATA引脚波形对比协议时序图。2. 检查DATA引脚是否已通过代码或外部电阻上拉。3. 测量DHT11 VCC电压确保在3.3V-5V且稳定。4. 更换传感器测试。1. 精细调整delay_us函数和DHT11_Read_Bit中的延时。2. 在DATA引脚和VCC之间连接一个4.7K-10K上拉电阻。3. 给MCU和传感器提供稳定的电源。4. 更换传感器。数据偶尔正确偶尔错误1. 未满足两次读取间隔1s的要求2. 中断干扰了时序3. 电源噪声1. 检查主循环中读取间隔是否足够。2. 在读取DHT11期间临时关闭全局中断。3. 在电源引脚增加滤波电容。1. 确保HAL_Delay(2000)执行。2. 在DHT11_Read_Data函数首尾加上__disable_irq()和__enable_irq()。3. 在VCC和GND之间并联一个100nF和10uF电容。Proteus仿真中DHT11数据不变Proteus中DHT11模型需要手动设置仿真值在仿真运行时右键点击DHT11元件选择Edit Properties修改Temperature和Humidity的值。在属性框中设置你想要的温湿度值。8. 最佳实践与项目扩展方向一个能跑通的demo只是开始要做出稳定、可维护、可扩展的项目还需要遵循一些最佳实践。8.1 代码与工程最佳实践模块化将DHT11和OLED的驱动完全分离成独立的.c/.h文件通过清晰的接口如DHT11_Read_Datassd1306_ShowString与主程序交互。错误处理像我们代码中那样为所有可能失败的操作如I2C传输、DHT11读取添加返回值检查。资源管理在OLED驱动中使用malloc后务必free避免内存泄漏。对于嵌入式系统也可以使用静态缓冲区。避免阻塞在主循环中长时间使用HAL_Delay会影响系统响应。对于更复杂的系统可以考虑使用定时器中断来周期性地触发传感器读取和显示刷新实现一个简单的调度器。8.2 项目扩展与进阶这个基础系统可以作为一个平台进行多种有趣的扩展添加报警功能当温度或湿度超过设定阈值时控制一个LED闪烁或蜂鸣器鸣叫。需要增加一个GPIO驱动蜂鸣器或LED。数据记录与查看增加一个SD卡模块将温湿度数据以CSV格式定期存储到SD卡中便于后期分析。无线传输增加一个ESP-01SESP8266Wi-Fi模块将数据上传到云平台如阿里云、OneNET或发送到手机APP。这需要学习AT指令或ESP8266的SDK编程。低功耗优化对于电池供电场景可以将STM32设置为休眠模式定时唤醒读取传感器和刷新显示极大延长续航。图形化界面利用OLED的绘图功能显示温湿度历史曲线图。多传感器融合增加一个光照强度传感器BH1750或大气压传感器BMP280构建一个综合环境监测站。通过这个“温湿度检测系统”项目你不仅学会了如何连接硬件、编写驱动、调试问题更重要的是掌握了一套嵌入式开发的方法论从需求分析、技术选型、原理研究、代码实现到调试验证的完整流程。这套方法可以迁移到任何其他的传感器、执行器或通信模块上。建议你将本工程代码作为模板保存在未来的项目中当你需要驱动一个新的I2C设备或单总线设备时回顾DHT11和OLED的驱动代码你会发现思路是相通的。嵌入式开发的世界很大但坚实的基础和清晰的思路能让你走得更稳、更远。