资讯详情

MSP430G2553与DHT11温湿度采集及12864显示实现

📅 2026/10/28 19:49:33 | 华诺云谱 👁 阅读
MSP430G2553与DHT11温湿度采集及12864显示实现
简介一份围绕MSP430G2553驱动DHT11温湿度传感器并完成12864液晶显示的嵌入式项目文档面向正在学习单片机外设驱动、单总线通信与LCD显示的学生和电子设计初学者可用于课程设计、实验参考或项目复盘。文档以50KB的单个doc文件交付内容集中呈现主控初始化、DHT11启动与40位数据接收、定时器脉宽解码、温湿度校验及12864汉字与数字显示等实现思路并涉及微秒与毫秒延时、显示格式取舍。已有962人学习说明其在低功耗MCU入门实践中具有一定参考价值。文档结构紧凑围绕初始化、接收、显示与校验等环节展开便于快速定位关键实现。读者可据此理解MSP430的I/O操作、单总线时序判断、校验和排错方法以及将传感器数据落到液晶屏的完整组织方式适合按知识点查阅并对照自己的实验代码查漏补缺。1. 从一根数据线开始为什么这个温湿度采集方案值得做一遍DHT11 只有一根数据线看起来是入门级器件但真正把它接到 MSP430G2553 上、再把数据稳定显示到 12864 液晶上翻车点比想象中多时序抖动几十微秒就读回全 0校验和不过就整屏不刷新液晶串行时序差一点就花屏。这套方案要解决的就是“低成本温湿度采集终端”的完整链路问题——单总线时序解码、数据校验、液晶显示分层一条链走通。它适合正在做嵌入式课设、毕设的学生也适合需要快速搭一个环境监测原型的从业者。整套代码只有两个源文件加一个头文件但结构上的取舍值得逐层拆开讲。2. 需求与技术选型三段链路和三个不能省的约束先把需求摆清楚选型才有依据。这个项目的功能目标很朴素周期性地读取环境温度与湿度做数据有效性判断把结果连同单位提示一起显示在液晶上。但看似简单的三件事对应三条独立的技术链路任何一条断掉整个系统就是黑匣子。2.1 三段链路的职责边界第一段是传感链路MSP430G2553 作为主机通过 P2.1 单总线与 DHT11 通信负责发起起始信号、逐位采样 40 bit 数据、做校验和比对。第二段是显示链路12864 液晶走串行接口占用 P1.0 片选、P1.1 数据、P1.2 时钟三根线负责把汉字提示和两位数值画到指定 DDRAM 地址。第三段是时间基准链路所有单总线时序都建立在微秒级延时之上延时准不准直接决定解码对不对。三段的耦合点很明确——传感链路输出的temph / templ / humdh / humdl / check五个字节是显示链路的唯一输入而时间基准是前两段共同的底层依赖。做方案设计时我一般会把这三段画成三条并列的横向泳道中间只留一个数据交接点这样后面调 bug 时能立刻定位问题出在哪条泳道上而不是对着一个不亮的屏幕瞎猜。器件选型上有几个现实考量。DHT11 的精度是湿度 ±5%RH、温度 ±2℃分辨率只有 1℃/1%RH这决定了它不适合做精密测量但足够做室内环境提示。它出厂已校准、单总线、3.3V 至 5.5V 供电与 MSP430 的 3.3V 电平天然兼容不需要电平转换。12864 选串行模式而非并行是因为 MSP430G2553 的 IO 资源有限串行只吃三根线代价是刷新速率和时序容错度都下降必须靠软件延时兜住。2.2 时钟与延时的选型账整个项目最容易被低估的决策是时钟。代码里用DCOCTL CALDCO_1MHZ把 DCO 校准到 1MHz然后所有延时都通过__delay_cycles换算#define CPU (1000000) #define delay_us(x) (__delay_cycles((double)x*CPU/1000000.0)) #define delay_ms(x) (__delay_cycles((double)x*CPU/1000.0))这里CPU宏必须和实际 DCO 频率严格一致。__delay_cycles是编译器内建函数参数是 CPU 周期数如果宏写 1000000 而实际跑在 8MHz所有延时都会缩到八分之一DHT11 的 30us 采样窗口直接失效读回的数据会呈现“湿度正常、温度恒为 0”这类很有迷惑性的现象。反过来如果为了保险把延时放大while等待高电平结束的循环会拖长整体帧时间可能错过下一位的起始沿。DHT11 的时序窗口实际是很紧的主机拉低至少 18ms 作为起始信号然后拉高 20 到 40us 释放总线传感器响应时先拉低约 80us再拉高约 80us此后每一位以 50us 低电平开始高电平持续 26 到 28us 表示 0持续 70us 表示 1。代码里用delay_us(30)作为判决阈值正好卡在 0 和 1 的高电平宽度之间这个 30us 不是随便取的它需要大于 28us 且明显小于 70us留出两边各约 40us 的余量。提示DCO 的校准值来自芯片内部 CALDCO_1MHZ 常量不同批次的 G2553 校准精度有差异通常能到 ±3% 以内。时序临界场景下这个偏差足以让 30us 判决阈值偏向某一侧。选型上还有一个常见误区想用定时器捕获替代软件延时来解码。理论上更精确但 DHT11 单总线的位间隔没有标准时钟参考捕获模式下要频繁进出中断反而增加抖动风险。这个项目用纯软件延时加轮询代码短、可控、便于排查是课设场景下的合理取舍。3. 总体设计两个源文件的模块划分与数据流整套代码只有main.c、12864.c和12864.h文件少不代表没有架构恰恰相反正因为文件少模块边界必须靠函数职责和头文件约定来守住否则后期加一个功能就会变成一锅粥。3.1 分层结构与调用关系从调用关系看是三层的。最底层是12864.c里的 IO 驱动层Send负责把一个字节拆成三个字节按位串出delay_Nus / delay_1ms / delay_Nms提供液晶侧的粗延时。往上一层是显示服务层Ini_Lcd做初始化序列Clear_GDRAM清扩展显存Disp_HZ按地址写汉字点阵Disp_SZ把两位十进制数拆成字符码写入Draw_PM和Draw_TX负责整屏图片和 16×16 图形绘制。最上层是应用层main.creceive_init组织一次完整的采集时序receive解一个字节show_tishi / show_Wendu / show_Shidu决定显示什么、显示在哪。分层的价值在于隔离变化。比如液晶从串行改并行只需改12864.c的Send和端口宏应用层一行不动再比如 DHT11 换成 DHT22只改receive_init里的字节顺序和校验方式显示层不受影响。头文件12864.h就是这个边界的契约它只暴露函数原型不暴露内部端口宏调用方看不到cyCS / cySID / cyCLK这些实现细节。3.2 关键数据与显示地址规划数据流是这样的receive_init内依次调用五次receive()把结果落到全局变量humdh / humdl / temph / templ / check主循环算cal humdh humdl temph templ与check比对相等才调显示函数。这几个变量是unsigned char正好容纳 DHT11 的一字节输出。显示地址的规划需要对着 12864 的 DDRAM 映射来算。这个项目把提示信息固定在首行0x80起写“温度”0x86位置写“°”0x88起写“湿度”0x8e位置写“%”。数值则插在提示中间的空位温度值写到0x83小数占位写0x84湿度值写0x8b小数占位写0x8c。每个汉字占两个字节地址所以“温度”三个字占0x80到0x850x86恰好接单位符号地址计算必须精确到字节错一位整个版面就串行。显示内容起始地址占用字节数据来源温度提示0x806tishi_W三字点阵温度单位0x862tishi_Wd温度数值0x832temph十位与个位湿度提示0x886tishi_S湿度单位0x8e2tishi_Sd湿度数值0x8b2humdh这张表看起来琐碎但它是排版的唯一依据。我见过不少同类项目把数值地址和提示地址写反结果屏幕上数字和汉字叠在一起排查半天以为是液晶坏了。实际做法是先在纸上按 16 字节一行画出地址格标好每个元素的起止再回来写Disp_HZ和Disp_SZ的实参能省掉大量试错。4. 模块实现单总线时序、字节解码与液晶写入实现部分按数据流顺序拆三段采集时序怎么发起、每一位怎么判 0 和 1、显示怎么落到像素上。每段都给出关键代码和参数依据。4.1 起始信号与响应握手receive_init承担一次完整采集的握手和后续五次字节读取void receive_init() { P2DIR | BIT1; // P2.1 切为输出主机掌握总线 LOW; // 拉低总线发出起始信号 delay_ms(20); // 保持至少 18msDHT11 才会识别 HIGH; // 释放总线 delay_us(30); // 20~40us 高电平后交给传感器 P2DIR ~BIT1; // 切为输入准备读响应 if(!(P2IN BIT1)) // 传感器拉低回应 { while(!(P2IN BIT1)); // 等 80us 低电平响应结束 while((P2IN BIT1)); // 等 80us 高电平准备结束 humdh receive(); humdl receive(); temph receive(); templ receive(); check receive(); } }这段代码的顺序不能乱。先切输出再拉低是为了避免引脚处于输入态时拉低无效拉低 20ms 而不是刚好 18ms是留余量因为delay_ms本身受 DCO 精度影响释放总线后等 30us 再切输入是给传感器留出识别起始信号并开始响应的时间。切输入之后用if判断总线是否被拉低这一步是握手确认如果没等到低电平说明传感器没接好或者上电未稳定直接跳过整个读取避免后面的receive卡死在while等待里。while等待那两行是流程的分水岭。第一行等响应低电平结束第二行等响应高电平结束两个都走完才真正进入 40 位数据的采集窗口。省略任何一行都会导致位对齐错位读出来的字节整体偏移。注意如果 DHT11 未接或接触不良while(!(P2IN BIT1))在极端情况下可能长时间阻塞。稳妥做法是在等待循环里加一个计数上限超时即返回主循环下一轮再试。4.2 逐位采样与 30us 判决阈值receive负责解出一个字节的 8 位核心是那个 30us 延时判决char receive(void) { unsigned char num 0, tem, cnt; for(cnt 0; cnt 8; cnt) { tem 0; while(!(P2IN BIT1)); // 等 50us 低电平时隙结束 delay_us(30); // 关键判决点 if((P2IN BIT1)) // 30us 后仍为高判为 1 { tem 1; while((P2IN BIT1)); // 等高电平走完为下一位对齐 } num 1; // 先移位再加位保证高位在前 num | tem; } return num; }逐行解释while(!(P2IN BIT1))消化每位开头固定的 50us 低电平delay_us(30)是关键判决点此时如果高电平还在说明这一位的高电平宽度超过 30us只能是 70us 的“1”如果已经回低就是 26 到 28us 的“0”判决为 1 之后必须再用while((P2IN BIT1))等高电平结束否则下一位的低电平时隙会被吞掉造成位错位。num 1; num | tem;的顺序决定了字节的位序。DHT11 是高位先出所以先左移腾出最低位再或入当前位八次循环后得到的就是标准的 MSB 在前的字节。这个顺序写反的典型现象是读出的湿度值看起来“有数但不对”比如实际 60% 读成 3%校验和还恰好偶尔能过属于最难查的一类问题。30us 这个阈值需要单独说。DHT11 手册给出“0”的高电平是 26 到 28us“1”是 70us取 30us 左右作为分界能同时避开两者。但软件延时本身有误差加上__delay_cycles的取整实际判决点可能落在 30 到 32us 之间仍在安全区。真正危险的是把延时写成delay_us(50)那样“1”才勉强能判对而“0”有可能因为 DCO 偏差被误判成 1表现为湿度虚高。4.3 校验和与显示刷新主循环里的校验逻辑很短但它是数据可信度的唯一闸门while(1) { receive_init(); cal humdh humdl temph templ; if(cal check) { show_Wendu(); show_Shidu(); } }cal取四个数据字节的低八位之和与第五字节check比对相等才刷新显示。注意这里是unsigned char相加溢出自然丢弃高位正好符合 DHT11 的校验规则不需要额外取模运算。校验不过就不刷新屏幕保留上一次的有效值这比刷出乱码要好得多。显示侧的两个函数是纯映射void show_Wendu() { Disp_SZ(0x83, temph); // 整数部分写两位数字 Disp_HZ(0x84, tishi_dot, 2); // 占位符 .00 补足小数位 } void show_Shidu() { Disp_SZ(0x8b, humdh); Disp_HZ(0x8c, tishi_dot, 2); }Disp_SZ内部把数值拆成十位和个位查numtable取 ASCII 码写入void Disp_SZ(uchar addr, uchar shuzi) { uchar tmp0 shuzi / 10; uchar tmp1 shuzi % 10; Send(0, addr); Send(1, numtable[tmp0]); Send(1, numtable[tmp1]); }那个tishi_dot里装的是.00用Disp_HZ按汉字方式写两字节实际就是把小数位固定显示成.00。原始代码里Disp_SZ(0x84, templ)被注释掉了原因是 DHT11 的温度小数位在当前精度下恒为 0显示出来信息量为零反而占位置。这个取舍在课设答辩时经常被问回答口径应该是“传感器分辨率只有 1℃小数位无有效信息”而不是“懒得写”。4.4 液晶写字节与指令/数据分流Send是整条显示链路的地基它把一个字节变成三字节的串行帧void Send(uchar type, uchar transdata) { uchar firstbyte 0xf8; uchar temp; uchar i, j 3; if(type) firstbyte | 0x02; // 数据帧 vs 指令帧 cyPORT | BIT(cyCS); cyPORT ~BIT(cyCLK); while(j 0) { if(j 3) temp firstbyte; else if(j 2) temp transdata 0xf0; else temp (transdata 4) 0xf0; for(i 8; i 0; i--) { if(temp 0x80) cyPORT | BIT(cySID); else cyPORT ~BIT(cySID); cyPORT | BIT(cyCLK); temp 1; cyPORT ~BIT(cyCLK); } // 三个字节之间必须留足延时否则易出现时序问题 if(j 3) delay_Nus(600); else delay_Nus(200); j--; } cyPORT ~BIT(cySID); cyPORT ~BIT(cyCS); }第一字节是命令头0xf8为基础值type为 1 时或上0x02变成数据帧头这就是指令与数据的分流开关。第二、三字节是把一个 8 位数据拆成高 4 位和低 4 位分两次送因为 12864 串行协议按半字节对齐。每半字节内部再按位从高到低串出先置数据线再拉高时钟左移再拉低时钟形成完整时钟脉冲。延时那一行是血泪经验。代码作者在注释里特意标了“三个字节之间一定要有足够的延时”第一字节后 600us、后续 200us看起来奢侈但串行模式下液晶内部要解析命令头再准备接收数据间隔太短会直接丢字节表现为屏幕局部花屏或者汉字显示成半个。这类问题用逻辑分析仪抓波形能一眼看出字节间隔不足但纯靠肉眼调试极其折磨属于典型的“不写后悔药就吃药”的场景。初始化序列Ini_Lcd里的五条命令也要按序理解0x30设 8 位接口和基本指令集0x02地址归位0x0c开显示但不显示光标0x01清屏0x06设光标右移和地址加一。每条命令后跟delay_Nus(72)这是液晶内部执行命令所需时间的下限省掉会偶发命令未生效。5. 避坑与排查五类最容易翻车的问题这套方案代码量不大但排查成本很高因为出错现象往往不具备指向性。下面五条是这个项目里最常见的翻车点按“现象、原因、解决”拆开写。5.1 读数恒为 0 或恒为 255现象屏幕能正常显示提示文字和单位但温湿度数值固定不动或者直接显示 0。原因通常是握手没成功——P2DIR | BIT1之后忘记切回输入或者拉低保持时间不足 18ms导致 DHT11 根本没响应if(!(P2IN BIT1))分支整段被跳过全局变量保留上电默认值。解决先用示波器或逻辑分析仪看 P2.1 是否有那 80us 的响应低脉冲没有就查起始信号再把delay_ms(20)临时改成 25ms 加余量验证确认后回退。5.2 湿度正确、温度恒为 0现象湿度显示跟着环境变化温度却一直是 0 或者变化极不灵敏。原因基本锁定在位序。receive里如果写成先或位再移位字节内部的位序就反了读出的数值呈现“看着像数但完全不对”的状态另一种可能是receive_init里的字节顺序写错把humdh和temph的位置调换了。解决把五个字节原始值通过串口打印出来对照cal是否等于check判断位序是否自洽再看温湿度哪个跟着环境变反推字节顺序。5.3 校验和偶尔不过现象大部分时间显示正常但每隔几秒数值闪一下停住不更新。原因是单总线时序抖动导致某一位判错校验和自然对不上主循环的if(cal check)挡住了显示刷新。这其实是设计在正确工作问题是判错频率过高。常见诱因是 DCO 未校准到 1MHz或者主循环里加了其他耗时操作影响延时精度。解决确认DCOCTL CALDCO_1MHZ执行在位主循环保持精简不要把显示刷新之外的逻辑塞进来读取间隔保持 1 秒以上DHT11 自身采样率不低于 1Hz读太快会读到上次的缓存值。5.4 屏幕局部花屏或汉字缺笔画现象提示文字位置对但显示残缺或者整屏随机出现噪点。原因是Send里三个字节之间的延时不够或者Disp_HZ写汉字时字节数算错——一个汉字占两个字节地址num参数传的是汉字个数内部循环要跑到num * 2。解决先把delay_Nus(600)和delay_Nus(200)各放大一倍做对照实验如果花屏消失就是延时问题如果依旧检查Disp_HZ的调用实参是不是把字节数当成了汉字数。5.5 数据地址与提示地址重叠现象数值显示出来后和汉字叠在一起或者数字只显示一半。原因是地址规划没按 16 字节一行对齐0x83这种奇数地址落在汉字的两个字节之间写入时把汉字点阵的后半截覆盖了。解决回到第 3 章的地址规划表先确认提示和数值的起止地址在每个元素上互不侵占再检查Disp_SZ写两位数字时是否连续占用两个字节地址——如果数值只放一位第二位地址会被提示文字吃掉。6. 验收与答辩怎么证明这套采集链路真的可靠验收这一步很多人吃亏在“能显示就算完”但评审更想看到的是你对数据可信度和边界条件的掌控。我一般会用一套三段式验收流程从静态到动态逐步加压每段都留下可展示的证据。第一段是静态自检不动传感器只查显示。上电后先确认提示文字、单位符号、占位小数全部就位且无残缺这一步验证Ini_Lcd的初始化序列和Disp_HZ的地址计算。接着用固定值替换temph / humdh的赋值比如写死 25 和 60看Disp_SZ是否稳定输出两位数字确认显示链路与传感链路解耦。这一段过不了后面所有数据都是假的。第二段是动态比对把采集结果和独立仪表对照。用手持温湿度计放在传感器旁边记录 5 组环境状态下的读数和显示值填写下面这种比对表。注意 DHT11 的精度是 ±2℃ 和 ±5%RH比对时按这个容差判断合格与否而不是要求完全一致。测试场景参考仪表读数显示读数校验是否通过判定室温静置24.0℃ / 55%RH24℃ / 55%RH是合格手捂传感器30.0℃ / 70%RH29℃ / 68%RH是容差内空调出风口18.0℃ / 40%RH18℃ / 41%RH是合格加湿器旁22.0℃ / 85%RH22℃ / 82%RH是容差内断开数据线无保持上次值否符合预期第三段是故障注入专门验证异常处理。依次做三件事拔掉数据线确认屏幕保留上次有效值而非刷出乱码短接数据线到地一段时间确认程序不会卡死在while等待里把供电电压在 3.0V 到 3.6V 之间调确认 DCO 校准和时序仍然可靠。这三步对应第 5 章那几个坑的反向验证答辩时把现象和预期一一对上比讲十遍代码流程有说服力。答辩环节常被追问的两点提前想好口径。一是“为什么不用定时器捕获解码”回答要落在单总线没有参考时钟、中断开销反而增加抖动纯软件延时在 1MHz 下精度足够且代码可控。二是“为什么小数位不显示”回答传感器分辨率只有 1℃小数位无有效信息显示.00是排版占位而非真实读数这个取舍是为了版面清晰。回答时把精度、时序、取舍三件事串起来说比只报参数显得更懂设计。从那以后我每次做单总线类的东西都会在验收清单里强制加上一条先断开设备看程序会不会卡死再接回来确认能否自恢复。这个习惯是从一次答辩现场被要求“现在把线拔了”开始的当时程序死在while循环里屏幕和串口一起沉默。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑