简介:基于STC8A8K64S4A12-LQFP44单片机的DHT11温湿度传感器串口助手显示实验,是一份面向单片机学习者和嵌入式开发者的完整软件例程。资源围绕DHT11驱动、串口1初始化及数据帧格式化输出展开,主函数清晰展示了温度湿度数组清零、传感器数据读取与上传流程,适合用于快速验证最小系统硬件或作为传感器驱动移植模板。压缩包共13个文件,包含DHT11.C/H驱动源码、uart.c串口配置、main.c主程序、Keil工程文件(uvproj/uvopt)以及编译生成的hex与m51文件,整体仅226KB,结构精炼,便于直接打开工程查看或烧录到开发板测试。资源已吸引685人学习,使用者可通过例程掌握单总线时序读取、准双向口与推挽输出配置、串口中断收发等关键知识点,从而缩短温湿度采集类项目的开发周期。
1. 串口助手里显示温湿度,代码能过编译只是第一步
把“基于STC8A8K64S4A12-LQFP44单片机DHT11温湿度传感器串口助手显示实验软件例程源码.rar”这个标题拆开看,真正值钱的不是DHT11驱动,而是从IO口配置、单总线时序、串口初始化到上位机显示这一整条链路的串法。很多人拿到类似的例程,下载到板子上发现串口助手里全是乱码或者一直显示ERR,问题往往不在DHT11本身,而在主频配置、引脚模式或者读取节奏上。这篇文章不做源码逐行注释,而是讲清楚这类例程背后必须想明白的硬件规划、时序判据和调试方法。适合正在用STC8A系列做传感器实验、或者想把网上零散代码整理成自己能改的工程的人。
2. 先定硬件:STC8A8K64S4A12的引脚、上拉与时钟
2.1 LQFP44封装下这颗芯片的资源边界
STC8A8K64S4A12是典型的增强型8051,1T指令周期,主频比传统51快得多,片上有64KB Flash、8KB SRAM,ADC、PWM、多个串口和定时器都集成好了。LQFP44封装的好处是引脚数量充足,P0到P3基本全部引出,串口1的P3.0/P3.1、DHT11想接的P2口都不需要跟其他功能抢线,实验时用杜邦线直接飞线非常方便。
要特别注意一点:STC8A系列是1T内核,同样一段延时循环,放在传统STC89C52上能用,放到这颗芯片上实际时间会差一个数量级。所以例程里的延时函数、波特率重装值都必须按照这颗芯片的主频重新算,这一点是很多从老51转过来的人最容易翻车的环节。
2.2 DHT11接哪个引脚,上拉电阻怎么配
DHT11是单总线器件,数据线空闲时必须被拉高,通信时主机和DHT11轮流拉低总线。STC8A的IO口可以通过PxM0、PxM1两个寄存器配置成准双向、推挽、高阻输入和开漏四种模式。对于DHT11的数据脚,我一般会选用准双向模式,这种模式下IO内部有弱上拉,既能输出低电平作为起始信号,也能直接读取外部电平,省去了模式切换的麻烦。
外部再并一个4.7kΩ上拉电阻到VCC,是为了提高总线抗干扰能力,特别是杜邦线超过20厘米时,没有这个电阻很容易出现偶发校验错误。DHT11的供电范围是3.3V到5.5V,实验板如果用5V系统就整体供5V,如果用3.3V单片机就统一供3.3V,DHT11的模拟输出精度不会因供电电压有本质变化。
| 信号 | 单片机引脚 | IO模式 | 说明 |
|---|---|---|---|
| DATA | P2.0 | 准双向 | DHT11数据线,外部接4.7kΩ上拉到VCC |
| VCC | 3.3V或5V | - | DHT11供电引脚 |
| GND | GND | - | 与单片机共地 |
| TXD | P3.1 | 准双向 | 串口1发送,接USB-TTL的RXD |
| RXD | P3.0 | 高阻输入 | 串口1接收,接USB-TTL的TXD |
2.3 波特率为什么总用定时器1的0xFFFD
串口助手里显示温湿度,前提是串口波特率算准。STC8A8K64S4A12的串口1波特率可以由定时器1或定时器2产生,常见例程用定时器1,配置成16位自动重装模式。以11.0592MHz主频、9600bps为例,12T分频下重装值计算公式为:
65536 - 11059200 / (12 × 32 × 9600) = 65536 - 3 = 65533
也就是TH1=0xFF,TL1=0xFD。这个组合在11.0592MHz下波特率误差为0,所以绝大多数实验例程都采用这个主频和这个重装值。初始化代码通常长这样:
void UART1_Init(void) { SCON = 0x50; // 串口1模式1,8位UART,使能接收REN AUXR &= 0xBF; // T1x12=0,定时器1按12T分频 AUXR &= 0xEF; // S1BRT=0,串口1波特率发生器选择定时器1 TMOD = (TMOD & 0x0F) | 0x00; // 定时器1模式0:16位自动重装 TH1 = 0xFF; TL1 = 0xFD; // 11.0592MHz @ 9600bps TR1 = 1; // 启动定时器1 ES = 1; // 使能串口1中断 EA = 1; // 打开总中断 }代码里SCON=0x50是串口1模式1的标准配置,8位数据、可变波特率、允许接收。AUXR的两个位是STC8A特有的:第6位控制定时器1的分频,第4位选择串口1的波特率发生器。如果这两步漏了,后面串口助手收到的数据大概率是乱码。
主频来源也值得确认:STC8A8K64S4A12支持内部IRC和外部晶振,下载程序时可以通过STC-ISP软件把IRC频率设置成11.0592MHz,这样整板不需要外接晶振。但要记住,下载器里选的频率必须和代码里延时所依据的主频一致,否则DHT11时序和串口波特率会同时出问题。
3. DHT11单总线时序是例程的核心:应答、0/1与校验
3.1 一次完整握手:主机拉低18ms后释放
DHT11的通信由主机发起,流程固定:主机先把总线拉低至少18ms,然后释放总线并等待20到40us,再读DHT11的响应。DHT11收到起始信号后会先拉低总线80us左右,再拉高80us左右,这个低-高组合就是“我准备好了”的应答信号。应答结束后,DHT11开始连续输出40bit数据,高位先出。
uint8_t DHT11_Start(void) { uint8_t cnt = 0; DHT_PIN = 0; // 主机拉低总线 Delay_ms(18); // 起始信号持续18ms DHT_PIN = 1; // 释放总线,准双向模式下写1即释放 Delay_us(30); // 等待DHT11应答 if (DHT_PIN) return 1; // 总线为高,说明DHT11没有拉低应答 while (!DHT_PIN && cnt++ < 100); // 等待应答低电平结束 cnt = 0; while (DHT_PIN && cnt++ < 100); // 等待应答高电平结束 if (!DHT_PIN) return 1; // 应为高电平却仍为低,信号异常 return 0; // 握手成功 }这段代码的关键在于,主机释放总线后必须立刻检测电平变化,不能加一个长延时再读。DHT11的应答低电平只有80us左右,在1T主频下稍不留神就过去了。第二个while循环里用cnt做超时保护,防止总线卡死在某个电平上导致程序死等。
3.2 位判定:高电平宽度超过40us就是1
DHT11输出的每一位都由一段约50us的低电平起始,然后是一段高电平。数据0的高电平持续时间约23到27us,数据1的高电平约68到74us。所以判0还是判1,看的不是低电平,而是低电平之后那段高电平有多宽。
uint8_t DHT11_ReadBit(void) { uint8_t cnt = 0; while (!DHT_PIN && cnt++ < 200); // 等待位起始低电平结束 cnt = 0; while (DHT_PIN && cnt++ < 200); // 统计高电平持续时间 return (cnt > 40) ? 1 : 0; // 超过40次循环判为1 }这个函数的cnt不是精确的微秒数,而是循环次数。在11.0592MHz下约等于每次循环1us,所以阈值40对应40us左右,正好落在数据0和数据1的高电平宽度中间。main函数里的程序主循环延时也要注意,不能低于1秒。
while (1) { if (rx_cmd == 0xA1) // 串口助手发0xA1触发一次读取 { if (DHT11_Start() == 0) { hum_i = DHT11_ReadByte(); hum_d = DHT11_ReadByte(); tem_i = DHT11_ReadByte(); tem_d = DHT11_ReadByte(); checksum = DHT11_ReadByte(); if ((uint8_t)(hum_i + hum_d + tem_i + tem_d) == checksum) { UART1_SendString("T:"); UART1_SendU8(tem_i); UART1_SendByte('.'); UART1_SendU8(tem_d); UART1_SendString(" RH:"); UART1_SendU8(hum_i); UART1_SendByte('.'); UART1_SendU8(hum_d); UART1_SendString("\r\n"); } else { UART1_SendString("CRC ERR\r\n"); } } else { UART1_SendString("DHT11 NO RESPONSE\r\n"); } rx_cmd = 0; // 命令执行完清空 } // 主循环里不额外做长延时,等串口命令触发 }这个主循环和前面那种“上电后无限循环读”的做法不太一样,每次读取由串口命令触发,更适合观察DHT11在不同环境下的实时响应。发送字符串时注意,小数字段如果为0,会显示成T:25.0 RH:60.0,这种格式在串口助手里最容易对齐查看。
4.5 这些代码里的常见误用
很多人在写DHT11例程时会把读取函数放在一个大循环里,不加节流直接反复读。DHT11手册和实际表现都要求两次读取间隔不低于1秒,高频读取时器件会不应答或者返回固定值。另外,位读取的阈值40是跟主频强相关的,如果换成24MHz主频还不改参数,数据0很容易被误判成数据1。最后,准双向模式下写0是强拉低,写1是释放总线,不能像推挽输出那样理解成“输出高电平”,理清这一点才能看懂DHT11驱动里的电平操作。
5. 串口助手调试技巧与逻辑分析仪快速验证
5.1 串口命令触发读取的小协议
实验例程里除了上电自动发数据,更灵活的做法是让串口助手发命令触发读取。常见约定是0xA1单次读取、0xA2开始自动周期上报、0xA3停止上报,这样在调试时不用反复按复位键。
void UART1_ISR(void) interrupt 4 { if (RI) { RI = 0; rx_cmd = SBUF; // 只缓存最新一条命令 } }这个中断函数只处理接收,不处理发送,发送路径由查询方式完成。要注意的是,如果上位机连续发多条命令,MCU来不及处理,后面的命令会覆盖前面的,所以实际使用中每次发完命令等结果返回再发下一条。
5.2 优先用逻辑分析仪看DHT11波形
遇到DHT11返回ERR或者数据跳变,不要先怀疑代码,把逻辑分析仪接在P2.0上,采样率设8MHz以上,抓一次握手和40bit数据。看波形时重点确认三件事:起始低电平是否接近18ms,应答信号是否存在,每位的高电平宽度是否落在对应区间。波形不对,问题在硬件;波形对但数据错,问题在读取逻辑。
5.3 串口助手常见表现对照
| 串口助手现象 | 可能原因 | 排查方向 |
|---|---|---|
| 全部是0x00或0xFF | 波特率不匹配,或USB-TTL接线反了 | 检查TXD/RXD是否交叉,确认波特率9600 |
| 一直输出NO RESPONSE | 上拉电阻缺失或DHT11供电不足 | 检查DATA线上拉、VCC电压 |
| 数据偶尔CRC ERR | 读取间隔太短或杜邦线过长 | 主循环延时加到2秒,缩短杜邦线 |
| 温度和湿度固定不变 | DHT11损坏或总线被其他外设占用 | 换一颗DHT11,确认P2.0没有复用 |
最后一个调试技巧:在DHT11_Start返回失败时,用串口输出失败码而不是简单的字符串,比如把握手失败的阶段用数字带出来,配合逻辑分析仪的波形就能快速定位是起始信号没发出去,还是应答没读回来。
本文还有配套的精品资源,点击获取