首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
基于DSP28335的DHT11温湿度采集与UART回传实战解析
📅 2026/9/9 4:25:45
✍️ 爱科研究院
👁 阅读 3,247
简介一份基于DSP28335与DHT11的温湿度采集工程包面向嵌入式开发学习者与DSP入门者解决在C2000系列平台上实现单总线传感器读取及UART数据回传的问题。工程基于CCS开发环境完整包含DSP2833x标准库、主程序main_dht11.c、链接命令文件与调试配置可直接导入CCS编译运行。包内共95个文件以C源文件、头文件、汇编程序及mk构建脚本为主同时包含cmd链接脚本、map映射表、out可执行文件及ccxml仿真配置整体约309KB目录结构规范便于按模块查阅。已有877人学习下载。读者可根据工程源码理解DHT11单总线时序的精确控制方法掌握UART波特率及帧格式配置熟悉SCI模块在DSP28335上的使用流程并借助配套的阅读说明与库文件快速迁移到其他温度采集或环境监测项目中。 去年做环境监测项目时我用手头的DSP28335开发板接了一个DHT11温湿度传感器数据通过UART回传到上位机就这么个“小活儿”前前后后折腾了将近一周才跑稳。DSP28335这片子平时都用来干FOC电机控制、并网逆变这种重活拿来读一个单总线温湿度传感器确实有点“杀鸡用牛刀”的意思但也正因为DSP的体系结构和单片机差异很大这个看似简单的需求踩出来的坑一点都不少。这篇文章把整个实现过程完整记录下来从硬件连接、协议分析、时序实现到UART回传、上位机调试再到我实际遇到的故障排查案例希望能帮到正在用TI C2000系列做类似传感器的朋友。无论你是刚接触DSP28335的新手还是想快速验证传感器采集方案的老手这套东西都可以直接照着抄。1. 项目整体设计与选型思路1.1 为什么用DSP28335来做“读温度”这种小事C2000系列DSP主打电机控制、数字电源、并网逆变等领域主频150MHz、32位定点运算、内置ADC和PWM这些资源用来读DHT11确实大材小用。但如果你的项目本身就以DSP28335为主控需要顺便采集环境温湿度做补偿或保护那就没必要再外挂一颗STM32或ESP32直接用GPIO模拟单总线协议即可。省一颗MCU就省一套电源、省一路晶振、省一堆耦合电容在成本敏感的产品上这很有意义。另外DSP28335的GPIO翻转速度极快理论上可以输出纳秒级的脉冲反而需要刻意“降速”才能满足DHT11的时序要求这也是个有趣的体验。1.2 硬件连接三根线的事别接出五根线的麻烦DHT11是四针封装但实际只用三根VCC、GND、DATA。DSP28335开发板大部分IO是3.3V逻辑DHT11供电范围3.3V到5.5V我建议直接3.3V供电这样数据线不用做电平转换GPIO配置成普通的推挽输入输出就能干活。数据线上必须接一个4.7kΩ到10kΩ的上拉电阻到VCC。DHT11是开漏输出没有上拉电阻它根本拉不高电平。这个电阻不是可选项是必选项——我见过有人没接上拉用万用表量数据线有电压但就是读不到数据其实就是悬空状态下的干扰毛刺让时序判断完全乱掉。连接清单如下信号DHT11引脚DSP28335引脚电源VCC(1)3.3V地GND(4)GND数据DATA(2)任意GPIO我这里用GPIO12上拉DATA与VCC之间4.7kΩ注意DHT11的NC脚3脚悬空即可别接地也别接电源。2. DHT11协议深度拆解40个bit的时序每一个都不能含糊2.1 单总线协议到底在说什么DHT11用的是单总线协议一根线上既有主机DSP发起的请求信号也有从机DHT11返回的响应和数据。通信流程分两段第一段是主机拉低总线持续时间至少18ms然后释放并拉高20到40us这是给DHT11的“起床信号”。第二段是DHT11回应一个80us的低电平表示“我醒了”再拉高80us表示“准备发数据”紧接着输出40bit数据。40bit的数据结构是8bit湿度整数 8bit湿度小数 8bit温度整数 8bit温度小数 8bit校验和。校验和等于前四个字节之和的低8位。每一位数据在总线上的表达方式很原始先是50us低电平然后拉高。高电平持续26到28us表示“0”持续70us表示“1”。2.2 用DSP的GPIO模拟这套时序难在哪难点不在于逻辑而在于时间精度的把控。DHT11对时序的容忍范围其实还算宽裕但DSP跑在150MHz一条指令才几纳秒如果你直接写空循环延时很容易延过头或者延不够。我的做法是起始信号的低电平用定时器延时函数保证18ms以上不要用空循环因为编译器优化级别一变循环次数对应的实际时间就变了。读取数据位时用GPIO的边沿变化结合循环等待来判定电平持续时间而不是用固定延时采样。在整个读时序过程中必须关中断。DSP28335的中断响应虽然快但PIE中断里如果有其他外设服务程序在跑哪怕延迟几个微秒都会让采样点落在错误的位置上。读取每一位的推荐流程是先等待总线变高然后测量高电平持续的时间。高电平持续超过40us判为“1”低于40us判为“0”。这个阈值比DHT11手册里的28/70us区间更安全因为实际测量时总线上会有电容充放电的影响波形边沿并不像示波器上看起来那样笔直。2.3 完整可用的读取代码这是我在项目中实际验证过的DHT11驱动代码基于TI的controlSUITE库函数写的// dht11.h #ifndef DHT11_H #define DHT11_H #include DSP2833x_Device.h #define DHT11_GPIO 12 #define DHT11_HIGH GpioDataRegs.GPBSET.bit.GPIO12 1 #define DHT11_LOW GpioDataRegs.GPBCLEAR.bit.GPIO12 1 #define DHT11_READ GpioDataRegs.GPBDAT.bit.GPIO12 void DHT11_Init(void); int DHT11_ReadData(unsigned char *humidity, unsigned char *temperature); #endif// dht11.c #include dht11.h #include DSP2833x_Examples.h // 延时函数基于CPU定时器0单位us void DHT11_Delay_us(Uint16 us) { Uint16 i; for(i 0; i us; i) { DELAY_US(1); // 使用DSP2833x的DELAY_US宏 } } void DHT11_Init(void) { EALLOW; GpioMuxRegs.GPBMUX1.bit.GPIO12 0; // 配置为GPIO功能 GpioMuxRegs.GPBDIR.bit.GPIO12 1; // 先设为输出 GpioMuxRegs.GPBQUALSEL1.bit.GPIO12 0; // 不使能输入限定 EDIS; DHT11_HIGH; } // 读取单个bit返回1或0 static Uint8 DHT11_ReadBit(void) { Uint16 timeout 0; // 等待低电平结束即等待总线变高 while(DHT11_READ 0) { if(timeout 1000) return 0; // 超时保护 } // 延时40us后采样 DHT11_Delay_us(40); if(DHT11_READ) return 1; else return 0; } // 读取完整40bit数据 static int DHT11_ReadRaw(Uint8 *data) { Uint8 i, j; Uint8 bit; Uint16 timeout 0; // 主机发送起始信号 EALLOW; GpioMuxRegs.GPBDIR.bit.GPIO12 1; // 设为输出 EDIS; DHT11_LOW; DHT11_Delay_us(20000); // 拉低至少18ms DHT11_HIGH; DHT11_Delay_us(30); // 释放并拉高20-40us EALLOW; GpioMuxRegs.GPBDIR.bit.GPIO12 0; // 切换为输入 EDIS; // 等待DHT11响应信号低电平80us timeout 0; while(DHT11_READ) { if(timeout 10000) return -1; // 无响应 } // 等待响应结束高电平80us timeout 0; while(DHT11_READ 0) { if(timeout 10000) return -1; } // 读取40bit数据 for(j 0; j 5; j) { data[j] 0; for(i 0; i 8; i) { bit DHT11_ReadBit(); data[j] 1; data[j] | bit; } } // 校验前4字节之和的低8位等于第5字节 if((Uint8)(data[0] data[1] data[2] data[3]) ! data[4]) return -2; // 校验失败 return 0; } // 对外的读取接口读取成功返回0 int DHT11_ReadData(unsigned char *humidity, unsigned char *temperature) { Uint8 raw[5]; int ret; DINT; // 关中断保证时序稳定 ret DHT11_ReadRaw(raw); EINT; // 恢复中断 if(ret 0) { *humidity raw[0]; // 湿度整数部分 *temperature raw[2]; // 温度整数部分 } return ret; }这段代码有几个关键配置必须说明GPBQUALSEL1.bit.GPIO12必须设为0否则输入限定器会引入最多几个系统时钟的滤波延迟虽然对单总线影响不大但没必要冒这个险。每次读取前要把GPIO切换成输出拉低读完再切回输入这个切换必须在EALLOW/EDIS保护下完成。从实测来看一次完整读取过程大约耗时25ms其中起始信号就占了20ms。也就是说DHT11的采样率上限大概在40Hz左右实际用1Hz到2Hz的刷新率就够用了毕竟温湿度本身变化很缓慢。3. UART回传把数据送到上位机3.1 SCI模块配置与波特率计算DSP28335的UART模块叫SCISerial Communications Interface有两路SCIA和SCIB。我用了SCIA对应引脚是GPIO28SCIRXDA和GPIO29SCITXDA。波特率计算的公式是波特率 LSPCLK / (8 × BRR) 或者 /16取决于BRR寄存器是否小于3。28335的LSPCLK默认是系统时钟的1/4系统时钟150MHz时LSPCLK37.5MHz。我用的波特率是9600bps代入公式BRR 37500000 / (8 × 9600) 488.28取整为488实际波特率 37500000 / (8 × 488) 9607.34。误差只有0.076%完全可以接受。初始化代码如下void SCI_Init(void) { EALLOW; GpioMuxRegs.GPGMUX1.bit.SCIRXDA_GPIO28 1; // 复用为SCI RX GpioMuxRegs.GPGMUX1.bit.SCITXDA_GPIO29 1; // 复用为SCI TX GpioMuxRegs.GPGMUX1.bit.SCIRXDA_GPIO28 1; GpioMuxRegs.GPGMUX1.bit.SCITXDA_GPIO29 1; EDIS; SciaRegs.SCICCR.all 0x0007; // 1停止位8位数据无校验 SciaRegs.SCICTL1.all 0x0003; // 使能TX和RX SciaRegs.SCICTL2.all 0x0003; SciaRegs.SCIHBAUD 0x0001; // BRR 488 0x01E8 SciaRegs.SCILBAUD 0x00E8; SciaRegs.SCICTL1.all 0x0023; // 释放SCI复位 }注意SCIHBAUD和SCILBAUD这两个寄存器要分开写高字节写高16位低字节写低16位不能直接写一个32位值。3.2 数据帧格式设计别用裸数据加个帧头帧尾回传数据时我最开始直接发四个字节的原始数据结果上位机经常出现错位——串口调试助手把字节流切开后根本不知道哪个字节是湿度、哪个是温度。后来加了帧头帧尾和简单的校验这个问题彻底解决。我设计的帧格式是0xAA 0x55 湿度值 温度值 校验和 0x0D 0x0A。帧头固定为AA 55校验和是湿度与温度之和的低8位帧尾是换行符。发送代码void SendDataToHost(unsigned char humi, unsigned char temp) { unsigned char buf[7]; buf[0] 0xAA; buf[1] 0x55; buf[2] humi; buf[3] temp; buf[4] (unsigned char)(humi temp); buf[5] 0x0D; buf[6] 0x0A; for(int i 0; i 7; i) { while(SciaRegs.SCICTL2.bit.TXRDY 0); // 等待发送缓冲区空 SciaRegs.SCITXBUF buf[i]; } }提示发送前一定要查TXRDY标志位否则往SCITXBUF里写数据时如果上一字节还没移出移位寄存器数据会直接丢失。3.3 上位机调试串口助手与VOFA调试阶段我用的是两种工具刚开始用串口调试助手只能看到十六进制数据需要自己对照帧格式解析还要在纸上算温度多少度效率很低。后来换成VOFA它支持自定义通信协议配上我上面的帧格式直接在界面上就能看到温度和湿度实时曲线。如果你只是验证功能串口调试助手就够用了。如果要做长时间数据记录或者趋势观察强烈建议上VOFA。这个软件我现在一直在用做电机控制时的波形观察和数据记录也靠它省去了自己写上位机的麻烦。不过有一点要注意VOFA的默认协议是JustFloat格式字节序是float的小端存储我这里用的是自定义帧格式需要在协议设置里选“RawData”自己写解析规则。具体配置方法添加协议选择帧头AA 55长度7字节校验方式选Sum偏移量对应湿度/温度的位置。4. 实际调试中踩过的坑与排查实录4.1 读到的数据始终是0xFF或者0x00这个现象最典型的两个原因第一是GPIO方向切换出了问题。DHT11_Init里把GPIO设置成了输出但ReadRaw函数中切回输入的感觉很隐蔽如果你漏了GpioMuxRegs.GPBDIR.bit.GPIO12 0这一句GPIO会一直保持输出模式DHT11拉低总线时DSP这边还在强输出高电平直接怼上了。第二是上下拉问题。检查一下数据线上是否有4.7kΩ到10kΩ的上拉电阻如果没有DHT11根本拉不高总线读回来的全是0。如果有万用表可以测一下数据线静态电压正常应该在3.3V附近。4.2 数据偶发错误校验和经常不过这个问题让我排查了很久最后定位到两个原因读取时没有关中断。项目中如果用到了定时器中断、SCI接收中断那么在DHT11_ReadData期间有中断进来哪怕是一个只有几十微秒的中断服务函数也会让位判断的延时变长导致误判。解决方案已经写在代码里了DINT和EINT包住整个读取过程。上一次读取还没有完全结束就开始了下一次。DHT11两次读取的间隔至少要1秒但我一开始在主循环里连续调用ReadData中间只间隔了几毫秒DHT11还没准备好响应信号就乱了。后来在每次读取之后加了一个500ms以上的延时问题就消失了。4.3 用仿真器跑没问题烧进Flash后就不动了这是TI C2000系列的老问题了不只是DHT11驱动会碰到所有外设初始化都可能遇到。原因是仿真器连接时程序运行在RAM中初始化代码正常执行烧进Flash后DSP上电先跑Boot ROM然后跳转到Flash中的_c_int00但Flash的等待状态没有配置CPU以0等待状态去读Flash而Flash本身需要至少几个等待周期结果就是程序跑飞。解决办法是在main函数最开始加上Flash初始化extern void InitFlash(void); void main(void) { InitSysCtrl(); InitFlash(); // 关键必须在任何外设初始化之前调用 DINT; InitPieCtrl(); IER 0x0000; IFR 0x0000; InitPieVectTable(); // ... 后续初始化 }InitFlash函数在TI的DSP2833x_Examples.h里声明实现是在DSP2833x_SysCtrl.c里它会设置Flash的等待状态寄存器。忘了这步程序在Flash里运行就像人在泥潭里跑步时序判断全乱。4.4 串口收到的数据乱码波特率不对或者电平不匹配最常见。波特率我上面算过了用的9600如果上位机设置成115200收到的必然全是乱码。另外确认一下USB转串口模块是否支持3.3V电平很多便宜的USB转TTL模块是5V电平的接到DSP的3.3V引脚上短时间内能用长时间会损坏GPIO。推荐用FT232R或FT231X方案的USB转串口模块支持3.3V电平Windows下驱动安装也比较省心。FT232R是老经典FT231X更便宜两者驱动通用都是FTDI官方的VCP驱动。5. 还有一些掏心窝的话5.1 关于时序实现方式的反思DHT11这种传感器用GPIO模拟协议读取最核心的其实是时间基准的选择。我一开始用DELAY_US宏做延时这个宏在RAM中运行还算靠谱但一旦优化等级开高或者代码被放到Flash中执行延时的实际时间就不可控了。后来我把关键时序切换成基于CPU定时器的延时函数稳定多了。如果你在别的项目里也要用DHT11建议优先考虑带硬件单总线接口的MCU或者用定时器输入捕获来测量高电平脉宽比纯软件延时可靠。5.2 扩展思路往ESP32或更高性能平台迁移如果你手头的项目正好也在评估ESP32方案DHT11的数据解析部分是通用的协议逻辑可以直接搬过去。ESP32的GPIO翻转速度更快而且有FreeRTOS需要注意的就是任务调度导致的时序抖动所以更推荐用ESP32的RMT外设来驱动DHT11RMT是专门为红外遥控设计的但用来做单总线时序采样非常合适能彻底解放CPU。最后再分享一个调试小技巧如果你手头有逻辑分析仪读取DHT11时一定要抓一下波形。这个传感器的工作状态直观反映在波形上比如起始信号宽度够不够、响应信号出现没有、每一位的高电平持续时间是否符合预期一眼就能看出来。我那次排查“偶发校验失败”的问题最后就是靠逻辑分析仪抓波形发现每次失败都恰好是中断服务程序抢占了CPU后来加上了关中断保护问题就彻底消失了连波形都变得非常干净。本文还有配套的精品资源点击获取
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/9/9 4:25:45
企业AI合规治理落地指南:从一刀切到全链路管控
2026/9/9 4:25:45
开源加固方案实战:为什么比商业加固基础版更强
2026/9/9 4:25:44
CTF从入门到实战:隐写术与漏洞利用的解题思路全解析
2026/9/9 5:15:47
Rust周刊2026W36 | rustup 1.29.1 更名 target tuple、Wasmi 2.0 提速 2.2 倍、Box map 稳定化、aarch64 FFI 传参修复…
2026/9/9 5:15:47
用定时IC替代单片机:小家电固定档位定时的低成本方案
2026/9/9 5:15:47
商用电子秤选购指南:预算有限也能买到精准耐用的秤
2026/9/9 5:15:47
CUDA Graphs、原子队列与NVSHMEM:实现GPU自主调度的三种机制
2026/9/9 5:15:47
TDengine高吞吐写入存储配置调优:WAL、buffer与磁盘选型实践
2026/9/9 5:10:47
Zephyr中断模型深度解析:上下文管理与硬件抽象
2026/9/9 0:00:26
MHS模型硬件标准:让大模型像调用软件一样控制物理设备
2026/9/9 0:00:27
AI五大核心方向详解:从机器学习到大模型,零基础转行选哪条?
2026/9/9 0:00:27
从50行最小循环到生产级AI引擎:工程化改造全解析
2026/9/9 2:07:00
超人会飞不算本事:系统稳定依赖清晰规则与边界设计
2026/9/9 1:41:51
超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论
2026/9/8 2:18:22
基于CNN的调制信号识别:MATLAB实现时频图分类实战