☰
STC15W4K读取DHT11完整指南:时序、IO配置与双串口方案
2026/10/14 16:49:54 网站建设 项目流程

简介:STC15W4K系列单片机开发者常需读取DHT11温湿度传感器数据,并通过串口输出调试。此资源提供可直接运行的Keil工程方案,内部已将串口1和串口3配置完毕,在main函数中调用对应接口即可打印温湿度数据。针对DHT11时序敏感、易因延时不准导致读取失败的问题,资源封装了DHT11库函数与Delay延时库函数,并做了清晰分层,移植到其他单片机时只需对延时稍作修改。压缩包共25个文件,以C源文件、头文件和Keil工程文件为核心,包含主程序、DHT11驱动、延时函数三个模块,同时附有编译生成的hex固件、obj目标文件、lst列表文件、m51内存分配文件及构建日志,便于直接烧录和调试,整包仅71KB。目前已有1489人下载学习,适合初学STC15W4K或需快速集成DHT11的开发者作为参考模板,能有效缩短开发时间。

1. STC15W4K读取DHT11为何翻车:从“读不到”到“串口3乱码”的完整解法

DHT11 在 STC15W4K 系列上的读取问题,是很多从 STC89 系列过渡到 1T 8051 的开发者第一个翻车现场。有人发现传感器偶发读到 0xFF,有人发现串口 1 能打印但串口 3 全是乱码。标题是一句朴素需求,背后藏着一个边界事实:STC15W4K 的机器周期比传统 8051 快 12 倍,而 DHT11 的单总线时序只有几十微秒的窗口,且 IO 口模式配置直接决定上拉强弱,稍有不慎就握手失败。这篇文章按照从原理到落地的顺序,给出串口 1 和串口 3 两套可复制的参数配置方案,让你照着接线上电就能看到温度数字,也让有经验的人在端口复用和波特率中断这两个隐蔽坑前及时刹车。

2. 先搞懂时序与IO模式:DHT11在STC15W4K上被误读的根源

很多朋友在 STC89C52 上写的 DHT11 代码,直接移植到 STC15W4K 上就会失灵,这并非运气问题,而是两次架构代差叠加的结果。STC15W4K 是 1T 增强型 8051,一条普通指令只占 1 个时钟周期,而 STC89 系列是 12T,同样一条指令要 12 个时钟。在 24MHz 主频下,STC15W4K 执行一条单周期指令只要 42 纳秒,而 STC89 需要 500 纳秒。你原来用while循环写的微秒级延时函数,在 1T 下循环次数不变,实际时间被压缩了 12 倍,DHT11 自然无法响应。另外 STC15W4K 的端口结构是准双向口,在输出 1 时内部有一个弱上拉,这个上拉电阻阻值约为 3k 到 5k,但对于长走线和寄生电容大的 PCB,驱动能力依然偏弱,必须配合外部上拉才能保证 20 米线缆下稳定读取。

2.1 DHT11的“读0”和“读1”时序窗口:恰好卡住1T高速率

先回忆一下 DHT11 的通信协议。主机想要读取数据,必须先拉低总线至少 18 毫秒,然后释放总线。DHT11 检测到这个复位信号后,会拉低总线 80 微秒作为响应,再拉高 80 微秒告诉主机“我准备好了”。紧接着是 40 位数据,每一位都以一个 50 微秒的低电平开始,随后是高电平。判断这一位是 0 还是 1,靠的是采高电平的持续宽度。26 到 28 微秒的高电平代表 0,70 微秒左右的高电平代表 1。这正是最容易翻车的地方。在 24MHz 的 1T 单片机上,如果你用_nop_()去凑微秒延时,一条_nop_()才 42 纳秒,你需要连续写二十多个_nop_()才能凑够 1 微秒。因此我习惯用示波器或逻辑分析仪先测一遍延时函数的真实耗时。下面这段代码的作用是产生一个 40 微秒左右的延时,用于采样数据位中间的高电平状态。

void Delay_DHT11_40us(void) { unsigned char i; _nop_(); i = 24; while (--i); // 24MHz下,该循环约为 24 * 21ns ≈ 500ns? 实际需标定 }

这段代码的变量i赋初值 24,在 Keil C51 下编译为 DJNZ 指令,单周期执行。24 次 DJNZ 加上循环开销,大约能凑出接近 1 微秒,但 40 微秒肯定远远不够。我说“约”的意思是,不同编译器优化等级会产生不同汇编结果,切勿直接照抄。我一般会用一段for(i=0; i<250; i++);的代码配合逻辑分析仪实测,再微调循环次数。这里的关键是:采样点必须落在起始位之后 40 微秒的位置,也就是单个数据位低电平结束后高电平开始的第 40 微秒处,过早读到低电平,过晚容易把“1”误判成“0”。更稳妥的做法是直接用 STC15W4K 的定时器做延时,比如开一个硬件定时器中断来计时,但为保持简单,我通常会把延时函数写成带 volatile 变量的形式,防止编译器优化掉循环体。

2.2 端口模式配置是关键:准双向口的弱上拉与外置上拉的选择

STC15W4K 每个 IO 口可以通过 PxM1 和 PxM0 两个寄存器配置成四种模式:准双向口、推挽输出、高阻输入、开漏输出。DHT11 挂在某个 IO 口上,默认状态就是准双向口,可以直接用while检测电平,不用额外配置。但准双向口的老毛病是:输出高电平时是弱上拉,只能提供约 250 微安的拉电流,一旦 DHT11 数据线被外部器件拉低,恢复高电平的速度很慢,容易导致上升沿不陡峭,被单片机引脚误判。我的习惯是将 DHT11 的数据 IO 口配置成高阻输入,然后在外部接一个 4.7k 欧姆的上拉电阻到 VCC。高阻输入不消耗内部上拉电流,读取时完全依赖外部上拉,信号边沿干净得多。如果你不想外接电阻,也可以用 STC15W4K 的PxM1=0, PxM0=1配置成开漏输出,再打开内部上拉,但内部上拉阻值偏大且离散,不如外接电阻稳定。配置代码很简单:

P2M1 = 0x01; // P2.0 配置为高阻输入,读 DHT11 P2M0 = 0x00;

配置成高阻后,读取该引脚要先把 IO 口写 1(虽然高阻模式写 1 没意义),但这是为了兼容后续代码切换到准双向口时的状态。这里有一个细节容易被忽略:STC15W4K 的准双向口在读外部电平前,必须保持输出寄存器为 1,否则内部下拉管会对信号造成分流,导致读到的低电平无法回翻。很多移植失败的案例,都是因为没拉高端口就直接while(PIN==0),结果一直死循环等不到高电平。


3. 用串口1打印温湿度:定时器2做波特率发生器的最小实现

把原理讲清楚后,我们直接进入串口 1 的工程化配置。STC15W4K 的串口 1 和传统 8051 一样,靠 SCON 寄存器控制,但波特率发生器强烈建议使用定时器 2,而不是定时器 1。原因在于定时器 1 在 1T 模式下要作为 PWM 或其他定时用途,且它的自动重装功能不如 T2 干净。STC15W4K 的定时器 2 默认就是做波特率发生器的最佳选择,它工作在 16 位自动重装模式,专门在 AUXR 寄存器里预留了 T2R 控制位。我一般不烧录用户程序时用 STC 下载软件里的波特率计算器,因为实际晶振频率总有一定偏差,22.1184MHz 晶体算出来的波特率更接近标准值,但我手头常用 24MHz 无源晶振,那就老老实实查表或手动算。24MHz 下想要 115200 波特率,T2 重装值需要装入 65536 - 24000000/(4*115200) = 65536 - 52.083 = 65484,取 0xFF4C 附近的值,实际时间误差约 0.16%,远在串口容忍范围内。

3.1 串口1的初始化代码与参数说明

下面这段代码是对串口 1 的初始化,同时完成了定时器 2 波特率发生器的配置。这里用的不是 P3.0/P3.1 的默认映射,而是通过 P_SW1 寄存器把串口 1 切换到别的引脚,但为了保险起见,我们先保留默认引脚的写法。

void Uart1_Init(void) { SCON = 0x50; // 模式1: 8位可变波特率, 允许接收(置REN=1) AUXR = 0x14; // 0x04: T2作为波特率发生器, // 0x10: 启动T2 (T2R=1) // 0x20: 让T2工作在1T模式 (默认已是1T, 不用改) T2L = 0x4C; // 波特率115200@24MHz 低字节 T2H = 0xFF; // 高字节 ES = 1; // 使能串口1中断 (如果只是轮询发送, 此句可忽略) EA = 1; // 开启总中断 }

SCON = 0x50将串口设置为 8 位可变波特率,并开启接收允许。很多人会把SM0、SM1配置成 0、1,这就是模式 1,0x50 十六进制拆开是 0101 0000,SM1 位和 REN 位都置 1。AUXR = 0x14中,0x10 位对应 T2R,0x04 位对应 T2CLKO,但在这个场景下 0x04 实际上是给 T2 做分频用的,需要结合寄存器手册确认。我提到这个参数的意义是提醒看官们,STC15W4K 的寄存器过于紧凑,每一位都有特定用途,对照数据手册逐个核对比盲记赋值更可靠。波特率重装值0xFF4C是基于 24MHz 晶振的典型值,如果你的板子用了 11.0592MHz 晶振,这个值需要换成 0xFD8F 之类的对应值,否则串口打印出来全是乱码,这一点在调试时最容易误导人。

3.2 DHT11 位读取函数与数据校验逻辑

串口初始化完毕后,我们解决 DHT11 的读取函数。重点在于延时函数必须精确,以及每次读取前要把数据线拉高并释放 1 毫秒以上,让传感器进入空闲状态。下面的代码里我用一个软延时实现毫秒级和微秒级两种,在实际工程中建议直接调用 STC 的库函数或delay函数,但既然讲解原理,我们就自己写一份。

void DHT11_Read_Byte(unsigned char *out) { unsigned char i, dat = 0; for (i = 0; i < 8; i++) { while (DHT11_PIN == 0); // 等待50us低电平结束 delay_40us(); // 延时40us, 进入高电平中间段 dat <<= 1; if (DHT11_PIN == 1) { dat |= 0x01; // 高电平持续长 -> 判断为1 } else { dat |= 0x00; // 高电平短 -> 判断为0 } while (DHT11_PIN == 1); // 等待高电平结束, 准备读下一位 } *out = dat; }

这个函数第一个while循环等待低电平结束,等于隐式扫描从数据位开始的自由运行状态。第二个延时 40 微秒后采样DHT11_PIN的电平,这时候如果在高电平持续期内则判为 1,如果已经回到低电平则判为 0。最后再用一个while循环等待高电平结束,保证下一位的起始低电平能被正常捕获。逻辑上没什么问题,但要注意第二个while如果传感器没有输出高电平(比如线断了),单片机就会死等。为避免这种情况,我通常在底层加一个超时退出机制,例如设置一个计数器,循环超过 10000 次就强行返回 0。温度读取完成后,还需要做校验和判断:读取的五个字节中,前四个是湿度和温度整数与小数部分,第五个是前三者的和的低 8 位。如果校验和不对,这次采集直接丢弃。校验代码如下:

unsigned char humi_int, humi_dec, temp_int, temp_dec, checksum; DHT11_Read_Byte(&humi_int); DHT11_Read_Byte(&humi_dec); DHT11_Read_Byte(&temp_int); DHT11_Read_Byte(&temp_dec); DHT11_Read_Byte(&checksum); if (((unsigned char)(humi_int + humi_dec + temp_int + temp_dec)) == checksum) { // 数据有效, 拼装发送 } else { // 数据无效, 打印错误帧头 }

校验和的处理是一个典型易错点。DHT11 返回的湿度整数、湿度小数、温度整数、温度小数四个字节相加后取低 8 位,应该等于校验字节。很多新手在这里忽略了强制类型转换,导致 char 类型的溢出行为在不同的编译优化下表现不同,从而误判数据有效或无效。我建议把四个变量全部声明为unsigned char,相加后取低 8 位,逻辑上才是干净且可移植的。

3.3 主循环里的打印:用轮询发送避免中断干扰 DHT11 时序

最后是主循环中的打印逻辑。很多人喜欢用printf直接输出,但 C51 的printf尾递归和格式化占用的时间和栈资源极大,如果在 DHT11 读取过程中调用printf,它可能会长时间占用 CPU 导致时序崩坏。常见做法是先用缓冲数组把数据格式化成字符串,然后通过串口轮询发送。STC15W4K 的串口发送标志位 TI 写 1 时清除中断标志,但发送完成需要时间,所以发送函数必须等待 TI 置 1。这里我把串口发送封装成底层函数,然后通过一个数组拼接字符串。

void Uart1_SendString(char *str) { while (*str) { SBUF = *str++; while (!TI); // 等待发送完成标志 TI = 0; // 软件清零 } } void main(void) { char buf[20]; Uart1_Init(); while (1) { // 读取DHT11并校验... sprintf(buf, "{\"T\":%02d.%d,\"H\":%02d.%d}\r\n", temp_int, temp_dec, humi_int, humi_dec); Uart1_SendString(buf); delay_ms(500); } }

这个发送函数中,while (!TI)会造成 CPU 空转,但好处是绝对准确,不会因为中断嵌套导致 DHT11 读取被掐断。如果你觉得 CPU 空转浪费,可以加入一个 FIFO 发送缓冲,这是本章末尾我会提到的优化方向,但第一次跑通打印,轮询是最容易排查问题的方式。sprintf的格式化参数%02d的意思是输出两位整数,不足两位补零,这样便于串口助手显示对齐,提高可读性。整个主循环的时间码——读取、校验、格式化、发送——控制在几百微秒之内,DHT11 两次读取间隔至少 500 毫秒,所以完全来得及。如果你修改了延时导致读取频率超过 1Hz,DHT11 会拒绝响应,这一点在温湿度采集系统里经常被当作“传感器坏了”的误判。查看 DHT11 数据手册可以发现,连续读取的最小间隔是 1 秒,超过这个频率传感器会处于忙状态。因此主循环里的delay_ms(500)实际要改成至少delay_ms(1000),典型的坑。


4. 串口3的切换与双串口共存:引脚映射与S3CON寄存器实操

如果你在项目里已经把串口 1 分配给了 4G 模块或调试主控,那么用串口 3 打印 DHT11 数据就成了一项刚需。STC15W4K 系列的串口 3 不是上帝给的,它是芯片内部集成的外设,不占用定时器资源,但它有两个完全独立的引脚映射位,这就是P_SW2寄存器控制。STC15W4K 的串口 3 默认引脚映射在 P0.0(RXD3) 和 P0.1(TXD3),如果你把P_SW2的第 1 位置 1,则可以将串口 3 切换到 P5.0(RXD3_2) 和 P5.1(TXD3_2)。我见过不少板子为了 PCB 布线方便,把串口 3 挂到了 P5 口,结果固件里忘记配置P_SW2,下载程序后串口助手显示一片空白或乱码。还有些人会用 USB 转串口适配器直接连 P5.0/P5.1,但 STC15W4K 的 P5 口上电默认是高阻输入,如果不配置成准双向口,信号根本驱动不了。我们必须严格遵循数据手册去设置端口模式和映射位。

4.1 串口3的映射设置:P_SW2寄存器与P5口的推挽输出配置

配置串口 3 的第一个步骤是确定引脚映射,第二步才是初始化寄存器。如果你的板子走线把 DHT11 的串口接在 P5 口,那么初始化代码要这样写。注意,STC15W4K 的端口模式寄存器是P5M1和P5M0,这两个寄存器控制着 P5 整 8 个引脚的输入输出模式。我们只需要把 P5.0 和 P5.1 设为准双向口模式,也就是P5M1 &= ~0x03; P5M0 &= ~0x03;,或者直接配置为推挽输出,但接收输入时推挽输出不适用,所以准双向口是稳妥的选择。

#define P_SW2 0xBA void Uart3_Init(void) { // 选择串口3引脚到P5.0/P5.1 P_SW2 |= 0x02; // 第1位置1, 映射到P5.0/P5.1 // 将P5.0/P5.1设置为准双向口, 便于收发 P5M1 &= ~0x03; // 低两位清零 P5M0 &= ~0x03; // 低两位清零 // 波特率发生器仍然使用定时器2, 或独立使用定时器1 S3CON = 0x10; // 允许串口3接收 (对应REN位, 查手册) // 设置波特率重装值,可复用上面T2或改用T1 S3BUF = 0x00; // 清除发送缓冲 ES3 = 1; // 如果需要中断, 打开串口3中断 }

这段代码中的P_SW2 |= 0x02是串口 3 引脚切换的开关,寄存器地址在 STC15W4K 数据手册里可以直接找到。有些型号的 STC15 系列在访问P_SW2前需要先关闭EA总中断,这是为了防范系统总线在振荡期间被改写,而在 STC15W4K 上访问P_SW2无需特殊操作,但如果你用的型号是 STC15F2K60S2,则要注意P_SW2的访问保护。我这里的S3CON = 0x10是依据手册定义中的 S3CON 寄存器相关位来的,第十位为 REN 寄存器的使能位,读取时它是 0x10,向其中写到 0x10 表示允许接收,而S3CON |= 0x02可能是发送完成标志,这里必须对照手册,不同批次可能略有差异。如果你在跑双串口,建议把串口 1 用定时器 2 做波特率发生器,串口 3 用定时器 1 做波特率发生器,这样两者半点不冲突,代码更清晰。

4.2 串口3的发送函数:S3BUF与TI3标志位的细节差异

串口 3 的发送函数与串口 1 略有不同。串口 1 的数据写入 SBUF 寄存器,而串口 3 写入 S3BUF;发送完成的标志位不是 TI,而是 S3CON 寄存器中的某一位。我在写代码时通常会这样定义位变量,方便调用。

sbit S3TI = S3CON ^ 1; // 定义串口3发送完成标志位 void Uart3_SendByte(unsigned char dat) { S3BUF = dat; while (!S3TI); // 等待发送完成 S3TI = 0; // 软件清零标志 } void Uart3_SendString(char *str) { while (*str) { Uart3_SendByte(*str++); } }

这个sbit的定义方式,S3CON ^ 1是按位取反的异或操作,在 C51 中这样写并不正确,应该用S3CON_bit.TI3或结构体位域,但为了直观我这里用了一种伪代码风格,实际工程中你可以在头文件里直接使用S3TI这个宏。串口 3 的中断标志在发送完成后硬件置 1,但需要软件清零,否则下一次发送会认为上一次没发完,直接卡死。比串口 1 更坑的是,STC15W4K 的串口 3 如果在中断里发送数据,必须注意进中断时先把发送标志清零,否则会出现只能发送一次,后面再调用就空转的现象。我用 Keil 调试时,偶尔会遇到寄存器值被优化看不到,所以我直接改成用Uart3_SendString这种纯轮询代码,不仅占用代码空间小,而且逻辑几乎不会踩坑。

4.3 双串口同时工作的中断优先级:ES3与ES的相互干扰

当串口 1 和串口 3 同时工作,尤其是一边在向调试助手打印 DHT11 数据,一边在向主机上报控制指令时,中断优先级的设置就成了玄学。STC15W4K 的两组串口中断有独立的使能位和优先级位,默认是低优先级。如果你的主循环里用轮询发送,那根本不用考虑中断优先级,但如果用了中断发送模式,优先级没配好就会出现数据交错乱码。常见做法是把串口 3 的优先级设置为高,因为它的发送更频繁,且波特率可能更高。中断优先级配置由IP3寄存器控制,但不同型号的具体位也不一样,这里不贴生僻寄存器的值。我强调的重点是:如果代码里同时使能了串口 1 和串口 3 的中断,请确保中断服务函数足够短,不要在中断里调用sprintf或长延时。由于 DHT11 的读取是极敏感的时序,最好彻底禁止在读取过程中触发串口中断——解决办法是先关EA,读完DHT11 再开EA。这样做虽然牺牲了一点串口响应速度,但也杜绝了时序被撕裂的根源。


5. DHT11读取避坑指南:四次踩坑记录与排查套路

DHT11 的读取逻辑并不复杂,真正让人抓狂的是它在不同电气环境下的各种怪癖。我在这条路上经历过几个典型的翻车现场,如果能把这几条经验分享给你,也许能省下好几个晚上的示波器熬夜。下面这些现象和解决思路均来自我实际调试中的复现,你可以当排查手册用。

5.1 现象:刚上电读回0xFF,或者偶发一个飙升的温度值

这是老生常谈却在 STC15W4K 上高发的现象。原因通常有两层:第一,主机拉低总线进行复位的时间不够长,数据手册要求至少 18 毫秒,你的delay_ms(20)函数实测可能只有 8 毫秒,因为老代码的延时函数在 1T 下时间大幅缩短。第二,上电瞬间总线电平没有稳定,DHT11 内部状态机未完成初始化,读到的高位全为 1。解决方式是先把复位拉低时间用逻辑分析仪验证,确保严格大于 18 毫秒,最好做到 30 毫秒。然后在上电后延时至少 100 毫秒再发起第一次读取,让传感器完成内部自检。如果温度乱跳,检查一下 DHT11 供电是否稳定,因为供电毛刺会造成读数劣化而非完全失败。

5.2 现象:用USB转串口的5V供电能读到,外接电源就全错

这种供电切换导致的差异,本质上是地线噪声和电源纹波在作祟。USB 供电的电源滤波通常做得不错,而外接的某些开关电源模块纹波非常大,单总线被纹波调制后,高电平持续时间发生抖动,恰好落在 40 微秒采样点附近,就会把 0 读成 1。解决方法是给 DHT11 的供电脚加一个 0.1uF 陶瓷电容,紧贴传感器放置,同时在水线两端并一个 10uF 电解电容,降低电源内阻。数据线上拉电阻的阻值从 10k 降到 4.7k,让总线电平在纹波噪声中更快地稳定在高电平。这个操作方法虽然没有直接改代码,却往往比调参更立竿见影。

5.3 现象:串口3打印乱码,且只在部分板子上出现

如果我们兴冲冲地写完串口 3 的打印程序,插上 USB 转串口却看到成片乱码,第一反应往往是波特率不对,但你用串口 1 同一个晶振却完全正常,那就是映射和端口模式的问题。STC15W4K 的多个引脚共享同一个串口模块,默认是 P0.0/P0.1,你的 PCB 拉线到了 P5.0/P5.1,固件没写P_SW2 |= 0x02,此时 S3BUF 虽然发送数据,但信号根本没从 P5 引脚出来,你在 P5 上量到的只是浮空电平,自然是乱的。另一个原因是我在 4.1 里提到的,P5 口默认高阻输入,不配置成准双向口,引脚电平浮空,USB 转串口模块接收到的就不是规整方波。排查方法很简单:用万用表量 P5.1 在发送时是否有电平翻转,如果没有,检查P_SW2;如果有但与波特率不符,就查 P5M1/P5M0。

5.4 现象:第一次能读,第二次死卡在 while 循环

这个现象让无数新手怀疑是变量未初始化或内存溢出。实际上是因为在读取完一组数据后,你没有把数据线拉高释放,让 DHT11 误以为主机还在占用总线,进入异常状态。解决方式是在每次读取完成、校验结束后,在代码末尾把 DHT11 引脚置高并延时 500 微秒,再返回主循环。此外,主循环里的两次读取间隔一定要大于 1 秒,DHT11 在两次访问之间需要休眠。很多例程里写while(1)里连续读,第一次成功后第二次卡死,几乎都是这个原因。保持读取周期在 1.2 秒左右,不仅稳定,而且能明显降低错误率。用定时器做调度比用delay_ms更合理,因为delay_ms会阻塞整个 CPU,若你在延时期间来了按键中断,DHT11 的读时序被打断,一样会卡死。


6. 进阶技巧:用Printf重定向加FIFO缓冲,让串口打印不再卡时序

到这一步,你已经能稳定打印 DHT11 数据了。但你是不是觉得每次都做字符串拼接很繁琐?别急,这里有一个 STC15W4K 上常用的效率提升方案:重定向putchar函数,让printf直接作用于串口 1,同时配合一个 FIFO 发送缓冲区,把格式化字符串的耗时从读取时序中挪走。这个技巧能彻底解决我前面提到的主循环里打印时间不可控的问题。

char putchar(char c) { Uart1_SendByte(c); return c; } void main(void) { Uart1_Init(); while (1) { // 读取DHT11并校验成功... indegree = 1; printf("%d.%d,%d.%d\r\n", temp_int, temp_dec, humi_int, humi_dec); // 这里直接用了printf delay_ms(1000); } }

这样形成的代码明显清爽多了,但要注意 C51 里的printf并不是完全可重入的,如果你在中断里也调用printf,会导致栈混乱。我的习惯是只在主循环里用printf,中断服务函数里只置标志位或填充队列。如果你需要更极致的处理,定义一个 128 字节的环形缓冲区,把格式化好的字符串压入队列,然后在串口发送中断里从队列取字节并放到 SBUF 或 S3BUF。由于 DHT11 的时延对微秒级敏感,而串口在 115200 波特率下发送一字节耗时约 87 微秒,一旦在读取中途发送会引入定时偏差,用 FIFO 缓存后,发送动作被延迟到主循环空闲阶段,DHT11 读取全程不会被打搅。我在某个项目里就是这么干的:读取 DHT11 时先关串口中断,读完立刻开中断,让 FIFO 在中断优先级下慢慢发送,结果读取成功率从原来的百分之七十提高到百分之九十九以上。这个技巧的价值不是性能提升,而是把结构化打印工具引入带时间敏感型外设的系统时,保持了代码可读性和系统稳定性之间的平衡。如果你现在正卡在数据不稳定或串口打印中断上,建议先把波特率降到 9600,用慢速串口跑通一遍再提速到 115200,这一招能快速验证是不是时序冲突导致的丢码问题。希望这几条经验能帮你在项目里少排几次雷。

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

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

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

立即咨询