news 2026/9/2 8:04:12

STM32驱动DHT11与OLED显示实战:从时序到I2C的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32驱动DHT11与OLED显示实战:从时序到I2C的完整指南

简介:这是一套面向STM32入门学习与嵌入式课程设计的温湿度实时监测工程。以STM32F103C8为主控,通过DHT11传感器采集环境温湿度,并驱动OLED屏完成数据展示,搭配Keil uVision5开发环境,源码组织清晰,适合用来理解GPIO、定时器延时、单总线通信及OLED显示驱动等基础外设的协作流程;既可作为智慧农业、小型气象站、家居环境监测等场景的原型,也适合初学者由零开始跟进一个完整的小型显示项目。压缩包共75个文件,以C源码与头文件为主体,包含硬件驱动、标准外设库、启动文件、Keil工程配置及辅助清理脚本,整体仅306KB;目录按Hardware、System、User、Library等功能模块划分,可快速定位DHT11与OLED驱动、延时函数和main主逻辑,便于二次开发与移植。目前已有7934人学习下载,无论是完成课程设计还是积累STM32嵌入式项目经验,都是一份轻量而完整的参考资料。 自己买过DHT11模块,照着网上代码抄了一遍,结果屏幕乱码、温度不更新、传感器读数卡死——最后查出来是I2C地址搞错了、DHT11时序不满足、OLED复位脚没拉高。这几乎是每个STM32新手都会踩的三连坑。

所以当看到“基于STM32的温湿度传感器+OLED屏显示项目文件压缩包”这个压缩包的名字时,我第一反应是:这玩意儿要是里面代码能直接编译通过、连上硬件就能跑,那真的省掉一大半的排查时间。但光能用还不够,如果不懂背后原理,换个引脚、换个芯片型号,项目又得从头折腾。

这篇文章我就以这个压缩包项目为线索,把从硬件选型、环境搭建、DHT11驱动时序、OLED取模显示到工程管理踩过的坑,一条龙讲清楚。适合刚入门STM32、想做一个完整小项目的同学,也适合那些手上有板子但不知道从哪儿下手的自学者。

1. 项目整体思路与硬件选型

1.1 为什么是STM32+DHT11+OLED这个组合

很多新手一上来就想搞复杂的,比如加WiFi、上云、搞APP控制。但坦白说,能把手头这块STM32F103C8T6用明白,把传感器数据采集准确、把屏幕显示调通,这个基本功比什么都重要。这个项目的核心链路很简单:STM32通过GPIO读取DHT11的温湿度数据,然后通过I2C接口把数据发送给OLED屏幕显示出来。

这个组合经典到什么程度呢?江科大、正点原子、野火,几乎所有STM32开发板的例程里都有这个Demo。原因有三:

  • DHT11便宜、易买、单总线协议,逻辑简单,适合理解时序概念。
  • OLED(通常是0.96寸,SSD1306控制器)功耗低、显示清晰、I2C接口只占两根线,接线方便。
  • STM32F103C8T6性能和外设足够,几十块钱一块板子,坏了不心疼。

所以哪怕你以后要去做更复杂的项目,比如智能家居中控、小型气象站、大棚环境监测,这套框架都能直接复用,只需要把DHT11换成DHT22、SHT30,或者把OLED换成TFT彩屏而已。

1.2 温湿度传感器的选型差异:DHT11、DHT22、SHT30、485工业传感器

很多人看到热搜词里有“恒智微鑫485温湿度传感器原理图”,会疑惑:为什么我的项目用了DHT11,还要扯485工业传感器?因为这是两种完全不同的应用场景。

DHT11是消费级、低成本、单总线数字传感器,精度是±2℃和±5%RH,测量范围0-50℃、20-90%RH,适合室内环境、桌面小玩意、课程设计。它的优势就一个字:便宜,几块钱一个。

DHT22(也叫AM2302)精度高一些,是±0.5℃和±2%RH,测量范围也更宽(-40~80℃),但价格翻好几倍。

SHT30是I2C接口的传感器,精度更高、稳定性更好,而且不用自己写单总线时序,直接用I2C读寄存器就行。缺点是价格更高。

至于485输出的工业传感器,那一般是24V供电、RS485总线传输、Modbus协议,用在工厂、机房、粮库这种长距离、多节点、强干扰的场合。一块钱和一百块钱的传感器,应用场景完全不同。我这个压缩包项目用的DHT11,核心价值在于让你理解时序和协议,而不是追求精度。

1.3 OLED屏的选型:I2C还是SPI,0.96寸还是1.3寸

OLED这块,0.96寸的SSD1306是最常见的,有I2C接口和SPI接口两个版本。我强烈建议新手选I2C版本,因为只接4根线:VCC、GND、SCL、SDA,代码也好写。SPI版本虽然刷新速度快,但要多接好几根线,而且SPI时序比I2C复杂一些,对新手不友好。

这里插一句热搜词里提到的“oled屏的像素点由几层组成”这个问题。其实这个问题挺有意思的,OLED屏幕的像素点从物理结构来说主要是基板、阳极(ITO透明导电层)、有机发光层(空穴传输层、发光层、电子传输层这几层有机薄膜)、阴极(金属反射层),加上封装层。不过从单片机驱动角度来说,你只需要关心屏幕的分辨率和颜色就行了。0.96寸OLED是128x64像素,单色,每个像素只能亮或不亮,通过SSD1306控制器的GRAM来控制。你往GRAM里写1,这个像素就亮,写0就灭。屏幕刷新时控制器会自动把GRAM映射到物理像素上,这个映射关系、扫描方向是可以通过命令配置的,不同的库实现可能会因此出现镜像、反色之类的差异。

2. 开发环境准备与工程搭建

2.1 CubeMX配置:引脚分配与时钟树

拿到这个压缩包项目,第一步不是打开代码看,而是确认你的开发板是不是STM32F103C8T6,以及代码里引脚配置是否和你手上的板子一致。我用STM32CubeMX重新生成一份工程,这是最稳妥的路径。

打开STM32CubeMX,芯片选择STM32F103C8Tx,然后按下面的方式配置:

  • RCC:HSE选择Crystal/Ceramic Resonator(外部晶振),这个是8MHz无源晶振常用的配置方式。
  • SYS:Debug选择Serial Wire,这一步非常关键,不然你烧录一次程序之后,第二次就连接不上芯片了,因为默认的JTAG引脚被程序占用了,SWD也被关了。这就是为什么热搜词里有“stm32禁用jtag”,踩过坑的都懂。
  • I2C1:选择I2C,默认PB6是SCL、PB7是SDA。
  • GPIO:DHT11数据脚我一般接PA0,配置为Output Push Pull,速度Low就行。读时序时再手动切换为输入模式。如果使用开漏输出+外部上拉也可以,但DHT11的时序要求比较严格,用推挽输出+切换输入方向这个方式更可控。
  • USART1:可选,用于调试打印,接PA9(TX)和PA10(RX)。

时钟树要注意:STM32F103系列最高主频72MHz,一般我们在Clock Configuration里把PLL倍频调到x9,系统时钟选PLLCLK,最终得到72MHz。我见过有人直接在CubeMX里默认配置就跑了,结果系统时钟才8MHz(直接用HSI),I2C通信速率不对,OLED显示就会出问题。

有一点要特别说明:I2C的速率配置为100KHz(Standard Mode)就行,OLED的SSD1306控制器虽然支持400KHz(Fast Mode),但实际走线、上拉电阻和代码实现都可能影响稳定性。我调试的时候遇到过100KHz稳定、400KHz偶发乱码的情况,后来干脆统一用100KHz。这个项目显示内容不多,100KHz完全够用。

2.2 KEIL5工程配置与Pack安装

CubeMX生成代码后,用KEIL5打开MDK-ARM目录下的工程文件。但在这之前,你要确认KEIL5的Pack装了没有。很多新手上来就打开工程,然后报一堆“device not found”或者“cannot open source file xxx.h”,大部分原因就是Pack没装好。

STM32F103C8T6对应的是Keil.STM32F1xx_DFP这个Pack。打开KEIL的Pack Installer,搜索STM32F1xx,安装对应版本就行。这个Pack里面包含了芯片的SVD描述文件、Flash算法、启动文件,没有它KEIL根本不知道你要烧录的芯片是什么。

编译前还有几个设置我要提醒一下:

  • C/C++选项卡里,Define一栏加上USE_HAL_DRIVER,STM32F103C8Tx,这是CubeMX生成代码的标配,如果你用的是现成工程,看看有没有漏掉。
  • 勾选Use MicroLIB,这样可以省不少Flash空间,因为标准的C库printf会引入很多浮点格式化代码,MicroLIB是精简版,对单片机完全够用。
  • Debug选项卡里,选择ST-Link Debugger(或者其他你手上的下载器,比如DAP-Link),然后在Settings里确认能识别到芯片ID。

如果你看到的是“No Target Connected”或者“Cannot access Target”,先检查接线:SWDIO、SWCLK、GND三根线必须接对,VCC也要接,但不要接下载器的目标供电(除非你确定板子没有独立供电)。还有一个常见坑:下载器质量不好或线太长,导致SWD时钟频率太高连不上,把Flash Download里的下载速度调到100kHz甚至10kHz试试。

2.3 工程文件结构拆解

打开这个压缩包项目,我猜里面大概有这几个文件夹:

  • Core:包含main.c、stm32f1xx_it.c、system_stm32f1xx.c
  • Drivers:STM32官方HAL库的源码和头文件
  • HAL_LIB或Middlewares:一些额外的库文件
  • BSP或User:我们自己写的DHT11驱动、OLED驱动
  • MDK-ARM:KEIL工程文件

这种分层思路是对的:HAL库是ST官方的,不要动;BSP层是板级支持包,每个外设一个文件;User层是主逻辑。如果你拿到手的压缩包没有这种结构,也没关系,只要main.c是完整的,你完全可以自己把DHT11和OLED的驱动文件整理出来,这也是我下面要重点讲的。

3. 核心实现拆解:DHT11驱动与OLED显示

3.1 DHT11单总线时序:从波形到代码

DHT11的通信协议叫单总线(1-Wire),一根线既做电源又做数据(严格说是数据线,电源是独立的),核心逻辑就是靠不同长度的低电平脉冲来表示0和1。

完整的一次通信流程是这样:主机(STM32)先把数据线拉低至少18ms,然后释放(拉高),DHT11检测到这个起始信号后,会先拉低80us再拉高80us作为响应信号,紧接着发送40bit的数据:8bit湿度整数部分、8bit湿度小数部分、8bit温度整数部分、8bit温度小数部分、8bit校验和。

40bit的数据里面,每一位的编码规则是:先拉低50us,然后拉高,拉高持续26~28us表示0,拉高持续70us表示1。注意,这个时序不是绝对的,DHT11的手册上给的是范围值,不同的批次的传感器可能会有细微差别。所以读时序的代码里一般会有超时判断,防止死等。

下面这段代码是我整理过的DHT11读取逻辑,使用HAL库的微秒延时函数:

// 注意需要自己实现微秒延时,HAL_Delay只支持毫秒 static void DHT11_Delay_US(uint16_t us) { // 使用DWT计数器实现精确延时,或者用SysTick重载 DWT_Delay_Init(); DWT_Delay_US(us); } uint8_t DHT11_Read_Data(uint8_t *humidity, uint8_t *temperature) { uint8_t buf[5] = {0}; uint8_t i, j, temp = 0; // 主机拉低起始信号,至少18ms GPIO_Set_Pin(DHT11_PIN, 0); HAL_Delay(20); // 释放总线,拉高,等待DHT11响应 GPIO_Set_Pin(DHT11_PIN, 1); DHT11_Delay_US(30); // 切换为输入模式,读取数据 GPIO_Set_Mode_Input(DHT11_PIN); // 等待低电平响应信号(80us低+80us高) // 超时保护,防止死循环 uint16_t timeout = 10000; while (GPIO_Read_Pin(DHT11_PIN) == 1) { if (--timeout == 0) return 1; // 超时,传感器未响应 } timeout = 10000; while (GPIO_Read_Pin(DHT11_PIN) == 0) { if (--timeout == 0) return 1; } timeout = 10000; while (GPIO_Read_Pin(DHT11_PIN) == 1) { if (--timeout == 0) return 1; } // 读取40bit数据 for (j = 0; j < 5; j++) { for (i = 0; i < 8; i++) { timeout = 10000; while (GPIO_Read_Pin(DHT11_PIN) == 0) { if (--timeout == 0) return 1; } DHT11_Delay_US(40); // 40us后采样 if (GPIO_Read_Pin(DHT11_PIN) == 1) { temp |= 0x01; // 高电平持续较久,说明是1 } while (GPIO_Read_Pin(DHT11_PIN) == 1) { if (--timeout == 0) return 1; } if (i < 7) temp <<= 1; } buf[j] = temp; temp = 0; } // 校验 if ((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4]) { *humidity = buf[0]; *temperature = buf[2]; return 0; } return 2; // 校验失败 }

这里最关键的是那个DHT11_Delay_US(40),在第28~30us的时候采样电平,如果你延时不准确、偏长,读出来的数据会全部是错误的。这也是为什么我强调要用DWT或者SysTick做微秒延时,而不是靠空循环。空循环在不同优化等级下时间变化很大,Debug模式下能跑通,Release模式下就挂了。

另外每次读取完DHT11之后,建议间隔1秒以上再读下一次,DHT11手册上写的最高采样频率是1Hz,也就是每秒最多一次。你如果在一个while循环里连续读取,中间不加延时,传感器会反应不过来,读到的数据永远是旧的或者直接超时。

3.2 SSD1306 OLED驱动:I2C通信与初始化序列

OLED用的SSD1306控制器支持6800/8080并行接口、SPI、I2C三种方式,我们用的是I2C。SSD1306的I2C地址一般是0x3C,如果你的模块是0x3D,多半是因为地址引脚(SA0)被拉高了。这个地址在初始化代码里写死,如果你发现屏幕没反应,先查地址,不要急着改代码。

I2C通信的流程是:主机发送起始信号→发送设备地址+写位(0x78或0x7A)→发送控制字节(0x00表示后续是命令,0x40表示后续是数据)→发送命令或数据。

SSD1306初始化是一长串命令序列,设置显示关闭、电荷泵开启、对比度、扫描方向、行地址模式等。这里我强烈建议用现成的、验证过的初始化序列,不要自己改,除非你仔细读过SSD1306的数据手册。下面是常用的一段:

uint8_t oled_init_cmds[] = { 0xAE, // 关闭显示 0x20, 0x00, // 设置内存寻址模式为水平寻址 0xB0, // 设置页地址(第0页) 0xC8, // 设置扫描方向(从右上到左下,和常见屏幕方向一致) 0x00, 0x10, // 设置列地址低/高四位 0x40, // 设置显示起始行 0x81, 0x7F, // 设置对比度 0xA1, // 设置段重映射(避免镜像) 0xA6, // 正常显示(非反色) 0xA8, 0x3F, // 设置多路复用比 0xD3, 0x00, // 设置显示偏移 0xD5, 0x80, // 设置时钟分频 0xD9, 0xF1, // 设置预充电周期 0xDA, 0x12, // 设置引脚配置 0xDB, 0x40, // 设置VCOMH 0x8D, 0x14, // 开启电荷泵 0xAF, // 开启显示 };

有些初始化序列里如果有0xA1(段重映射),而你的屏幕又是镜像的,那就改成0xA0试试。同理,扫描方向0xC80xC0互为镜像。OLED这行就是个靠命令调镜像的事情,别怀疑芯片坏了。

I2C读写用HAL库的HAL_I2C_Mem_Write就很方便,因为它能自动处理设备地址、寄存器地址(这里就是控制字节)和数据缓冲区。写一个命令:

void OLED_Write_Cmd(uint8_t cmd) { HAL_I2C_Mem_Write(&hi2c1, OLED_ADDR, 0x00, I2C_MEMADD_SIZE_8BIT, &cmd, 1, 100); } void OLED_Write_Data(uint8_t data) { HAL_I2C_Mem_Write(&hi2c1, OLED_ADDR, 0x40, I2C_MEMADD_SIZE_8BIT, &data, 1, 100); }

这里有个小坑:HAL_I2C_Mem_Write的第二个参数是设备地址,SSD1306的7位地址是0x3C,但HAL库内部会在发送时自动左移一位并拼接读写位,所以你传的就是0x3C,不用自己移位成0x78。我见过有人在代码里写0x78,结果I2C总线上的地址变成了0xF0,屏幕完全不响应。

3.3 OLED汉字取模与显示逻辑

OLED显示汉字和字符的本质是往GRAM里填像素数据。128x64分辨率,一列8个像素为一个字节,16x16的汉字就是32字节数据。取模工具我用的是PCtoLCD2002,设置如下:

  • 取模方式:阴码,点阵内为1表示点亮
  • 逐行式取模
  • 每行16个点,共16行

取出来的效果是0x00,0x00,0xFC,0x04, ...这种代码,可以直接定义成const数组,节省RAM。

显示时要注意,SSD1306在页寻址模式下,一次可以从某个页(page)的某列开始连续写数据。页地址范围是0~7,每页8行像素,所以64行像素被分成8页。你往某个页写一个字节,这个字节的8个bit就代表该页这8行对应列的亮灭。这在显示16x16汉字时,需要跨两个页写数据:上半部分16行像素,位于第0页和第1页(从page p开始),写两轮,每轮16字节。

显示函数的核心逻辑大概这样:

void OLED_Show_CN(uint8_t x, uint8_t page, const uint8_t *font_data) { // 先写汉字上半部分(前16字节) OLED_Set_Cursor(x, page); for (uint8_t i = 0; i < 16; i++) { OLED_Write_Data(font_data[i]); } // 再写下半部分(后16字节) OLED_Set_Cursor(x, page + 1); for (uint8_t i = 16; i < 32; i++) { OLED_Write_Data(font_data[i]); } }

这里有个注意点:汉字库通常是16x16像素,但“温”、“湿”、“度”这些字有些笔画比较复杂,取模出来效果可能差强人意。如果显示效果不好,可以把取模的字号改成16x16标准,或者换成12x12的字体减少空间占用。

还有一个很实用的技巧:显示变量。DHT11读出来的是整数,比如湿度45%、温度25度。我们可以在OLED上显示“Temp: 25 C”和“Hum: 45 %”。但注意DHT11的精度,整数显示就够了。如果你想显示小数,做浮点格式化会很消耗Flash和RAM,建议整数和小数分开算,比如25.3度,就分别取25和3,手动拼字符。

3.4 main.c主循环逻辑设计

主程序逻辑非常简单,但有一些细节值得讲究。我的main函数里,外设初始化完成后,主循环大概是这样:

int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_I2C1_Init(); MX_USART1_UART_Init(); OLED_Init(); OLED_Clear(); OLED_Show_String(16, 0, "Temp:", 16); OLED_Show_String(16, 2, "Hum:", 16); uint8_t hum, temp; char buf[20]; while (1) { if (DHT11_Read_Data(&hum, &temp) == 0) { // 成功读取,更新显示 sprintf(buf, "%d C", temp); OLED_Show_String(64, 0, buf, 16); // 显示温度 sprintf(buf, "%d %%", hum); OLED_Show_String(64, 2, buf, 16); // 显示湿度 } else { // 读取失败,可以显示错误标记 OLED_Show_String(0, 7, "DHT11 ERR", 8); } HAL_Delay(1000); // 每秒读一次 } }

这里有个细节:每一轮更新前不用清屏,因为你要更新的区域只是数字部分,而且数字是用固定位置写上去的。如果数字从“25”变成“5”,之前十位的“2”如果不擦掉,显示就成了“52”。最简单的办法是每次先在这个区域填充空白字符(比如写三个空格),再写新数据。

还有一点,OLED_Init之后要做一次OLED_Clear,不然上电屏幕可能是花屏,因为GRAM里的初始数据是不确定的。

4. 常见问题与排查技巧实录

4.1 I2C通信异常的排查思路

I2C通信异常是最让人头痛的问题,因为看起来代码没错、接线没错,但就是没反应。我总结了几个排查方向,按优先级排列:

查硬件接线:SCL和SDA有没有接反?VCC和GND有没有接对?OLED模块的VCC虽然标称3.3V~5V都能工作,但STM32的I2C引脚是3.3V电平,所以最好统一用3.3V供电。如果你把OLED的VCC接到了5V,而SCL/SDA是3.3V的IO口,长期运行还是有风险的。

查I2C地址:前面已经提过,0x3C和0x3D的问题。你可以在初始化OLED之前,用HAL_I2C_IsDeviceReady检测一下设备是否在线:

if (HAL_I2C_IsDeviceReady(&hi2c1, 0x3C, 5, 100) != HAL_OK) { // 设备未响应,这里可以点一个LED提示 }

查外部上拉电阻:STM32的I2C引脚是开漏输出,必须要有外部上拉电阻才能输出高电平。大多数开发板和OLED模块上已经带了4.7k或10k上拉电阻,但如果你的OLED是那种裸屏模块,或者你用了杜邦线外接,没有上拉电阻,I2C通信是绝对不工作的。

查I2C速率:如果通信不稳定,把速率从400KHz降到100KHz试试。这个问题在长线连接时特别明显,杜邦线一长,寄生电容就会影响信号边沿,速率越高越容易出错。

4.2 DHT11读取失败和数据恒为0的调试方法

DHT11读取失败最常见的三个原因:引脚配置不对、微秒延时不准、总线竞争(多个设备共用引脚)。调试方法有一个很好用的:用逻辑分析仪抓波形,没有逻辑分析仪也可以用printf打印每个阶段的电平状态。

我分享一段调试思路:把DHT11数据引脚配置为输入模式后,在主循环里只打印引脚电平的翻转时间。如果你看到的是大约20ms低电平→80us低→80us高→数据段,说明时序基本正常。如果你看到引脚一直高电平,说明传感器没供电,或者数据线没接对。如果一上电就是低电平,可能是传感器坏了,或者数据线短路到地。

数据恒为0还有一个可能性:读取成功后,你打印的是buf[0]和buf[2],但实际接收的湿度值是整数部分(buf[0]),如果你看错位了,把buf[1](小数部分)当成湿度打印,那确实经常是0,因为DHT11的湿度小数部分大多数时候就是0。

关于微秒延时,我再补充一下DWT的实现。DWT(Data Watchpoint and Trace)是Cortex-M3内核里的一个调试组件,它的CYCCNT寄存器可以计数CPU周期。通过它实现微秒延时非常精准,而且不受优化等级影响:

void DWT_Delay_Init(void) { CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; } void DWT_Delay_US(uint32_t us) { uint32_t start = DWT->CYCCNT; uint32_t ticks = us * (SystemCoreClock / 1000000); while ((DWT->CYCCNT - start) < ticks); }

这个方案比SysTick更省事,因为SysTick可能被HAL库的HAL_Delay占用。

4.3 OLED显示乱码、花屏和镜像问题

OLED显示乱码的排查:

  • 如果屏幕上全是雪花点或者满屏亮块,大概率是初始化序列不对,或者GRAM写入方式不匹配。检查一下你是不是用了SPI接口的OLED,但代码是按I2C写的——这两种接口的屏长得几乎一样,容易拿错模块。
  • 如果字符显示出来是上下左右颠倒的,那基本就是段重映射和扫描方向的问题。把初始化序列里的0xA1改成0xA00xC8改成0xC0,排列组合试一遍就能找到正着的方向。
  • 如果字符显示正常,但位置不对,比如从中间开始显示,看看页地址和列地址的设置。OLED_Set_Cursor这个函数必须同时设页地址和列地址,有些人只设了列地址没设页地址,所有内容都写在第一页上,就会重叠成一坨。

花屏还有一个特殊原因:I2C通信本身没问题,但OLED的电源电压不稳。OLED的电荷泵开启后电流会增大,如果你用的是电脑USB口供电,电压可能被拉低,导致屏闪或者花屏。换独立5V/3.3V电源,或者在电源引脚旁边加一个10uF的电解电容和一个100nF的瓷片电容,能缓解这个问题。

4.4 STM32烧录失败:SWD连不上和JTAG禁用问题

烧录失败这个坑我必须单独说,因为它太常见了。有两种典型情况:

第一种:第一次烧录没问题,烧完之后第二次就报“No target connected”。这种基本都是程序里配置了GPIO,把PA13/PA14/PA15/PB3/PB4这些调试脚给重新定义了,或者把SWD功能关了。CubeMX里在SYS选项卡设置Debug为Serial Wire,这样SWD引脚就不会被重映射,下载器还能连上。如果已经连不上了,解决办法是把BOOT0引脚拉高,重新上电,让芯片进入ISP模式,这时候SWD是恢复的,擦除一下Flash再重新下载。

第二种:下载器本身驱动有问题。ST-Link/V2在Win10/Win11上有时会因为驱动冲突,导致电脑识别不到。解决办法是在ST官方工具STM32CubeProgrammer里重新安装驱动。如果你用的是盗版ST-Link或者那种9块9的DAP-Link,固件不稳定的情况更多,建议先换一根数据线试试——没错,数据线问题导致下载失败的案例我见过不止一次。

我在实际使用中最推荐下载方式的是ST-Link Utility(STM32 ST-LINK Utility),它可以直接擦除、编程、校验整个Flash,对于排查程序运行异常非常有用。程序跑飞了、芯片锁死了,用它一键全片擦除,干干净净。

4.5 这个压缩包项目的可扩展方向

把这个基础项目吃透之后,后续扩展的空间很大,这里列几个我实际做过的方向:

  • 换成DHT22或者SHT30传感器,只需要改驱动层,显示层完全不用动。
  • 增加历史温度曲线显示,OLED刷新改成局部刷新,每秒画出温度变化的像素点,就能做一个微型趋势图。
  • 增加按键切换到不同显示页面,比如一页显示温湿度,一页显示最大值最小值。
  • 把串口打印加上,通过USART把数据发到电脑的串口助手,方便调试和分析。
  • 加一个ESP8266或者ESP32模块,通过串口把温湿度数据传到云端——这就是智能家居的雏形了。
  • 把OLED换成1.3寸的I2C OLED(SH1106驱动),代码大部分可以复用,只要改初始化序列和窗口设置函数就行。

从底层驱动的角度,你可以深入研究一下CubeMX的HAL库源码,看看I2C的状态机是怎么实现的、DMA模式怎么处理、中断回调里能不能触发数据读取。这些内容才是从一个抄代码的新手变成能独立做项目的人的分水岭。

5. 最后想说的话

这个压缩包项目,说大不大,说小也不小。它没有用到复杂的外设,没有RTOS,没有复杂的算法,但它把嵌入式开发最基本的几个环节全部串起来了:芯片配置、GPIO操作、外设通信协议、时序控制、显示驱动、以及最重要的——排查问题的思路。

我的建议是,拿到任何项目压缩包,不要急着烧录看效果,先用CubeMX打开工程文件(或者自己重建一份),把每一行代码都过一遍,搞清楚时钟怎么配的、引脚怎么选的、I2C速率设了多少、DHT11时序怎么实现的、OLED初始化序列里每一条命令干嘛用的。全部搞清楚之后,再烧录到板子上。这样就算出问题,你也知道往哪儿查。

如果你在调试过程中遇到和我当年一样的坑——OLED不亮、DHT11读不出来、烧录第二次就失败——欢迎回来对照这篇文章,一条条排查。实在解决不了,可以看看板子型号、引脚接线、报错信息这三个信息,很多时候问题就藏在最基础的细节里。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 8:04:00

网络安全自学路线图:从零构建实战技能体系

网络安全&#xff0c;一个听起来既神秘又充满挑战的领域。很多人被电影里的“黑客”形象所吸引&#xff0c;以为敲几下键盘就能掌控一切&#xff0c;但真正踏入其中&#xff0c;却发现面对的是海量的术语、繁杂的工具和陡峭的学习曲线。网上教程虽多&#xff0c;却常常是零散的…

作者头像 李华
网站建设 2026/9/2 8:03:52

内容创作者如何系统化重启创作并产出标杆作品

1. 先搞清楚“停更后回归”到底在解决什么创作问题 “停更了5天&#xff0c;我又回来更新了&#xff01;这应该是我目前拍的最好的了”——这个标题背后&#xff0c;不是一个简单的复更声明&#xff0c;而是一个创作者在内容生产、质量控制和持续输出压力下&#xff0c;一次典型…

作者头像 李华
网站建设 2026/9/2 8:02:17

Scratch斜向移动速度问题解析与向量归一化架构设计

你有没有遇到过这种情况&#xff1a;在 Scratch 里做了一个角色移动&#xff0c;按下“上”和“右”键&#xff0c;角色斜着走&#xff0c;结果发现它跑得飞快&#xff0c;比只按一个方向键快得多&#xff1f;这感觉就像游戏里开了加速挂&#xff0c;角色“嗖”地一下就冲出去了…

作者头像 李华
网站建设 2026/9/2 8:00:24

2026年7月池州市新房价格深度分析报告

一、报告背景与数据说明本报告基于2026年7月池州市新房实际成交案例&#xff0c;结合区域分布、楼盘类型、户型结构与成交价格等维度&#xff0c;对当前池州新房市场进行深度分析。报告数据来源于公开成交备案信息与典型楼盘样本&#xff0c;旨在为购房者、投资者及行业从业者提…

作者头像 李华
网站建设 2026/9/2 7:58:58

政务场景高可靠电子合同系统适配选型全攻略

政务场景电子合同系统适配选型核心结论政务场景数字化签约需求目前可划分为三类核心场景&#xff0c;不同场景的合规要求、数据安全标准差异较大&#xff0c;选型逻辑存在明显区分。第一类为普通办公场景&#xff0c;主要用于内部非敏感通知、常规行政文件签署&#xff0c;对数…

作者头像 李华
网站建设 2026/9/2 7:57:02

全开源低成本具身智能机械臂my_ai_town:从零搭建到视觉抓取实战

想用机械臂实现智能抓取&#xff0c;但被动辄数万甚至数十万的商业方案劝退&#xff1f;想研究具身智能&#xff0c;却发现开源项目要么停留在仿真&#xff0c;要么硬件成本高不可攀&#xff1f;最近&#xff0c;一个名为my_ai_town的全开源、低成本具身智能机械臂项目在 GitHu…

作者头像 李华