51单片机户外运动参数监测:DHT11与LCD1602仿真系统设计 简介面向单片机课程设计场景的户外运动参数监测系统项目资料以单片机为控制核心整合温湿度传感器、脉搏检测、LCD显示、按键和LED指示等模块实现户外环境温湿度与人体脉搏的实时采集显示并在脉搏异常时自动切换LED颜色告警。资源包共35个文件压缩后约2.23MB涵盖Keil工程C源程序与头文件含DHT11、LCD1602驱动、Proteus仿真工程、hex烧录文件及docx设计说明目录结构清晰便于对照学习和二次修改。已有245人浏览学习。通过该资源可快速搭建完整的系统软硬件框架既能直接作为单片机课程设计参考也能在实操中掌握传感器数据读取、液晶显示、按键处理与GPIO控制等典型开发技能。1. 从“代码能编译”到“仿真能跑通”这套户外运动参数监测课设的资源价值大多数人做 51 单片机课设停在改改流水灯、抄一份 LCD1602 代码的程度而户外运动参数监测这道题要求同时处理温湿度采集、脉搏脉冲计数、LED 状态指示和键盘输入属于把一个简单单片机系统拼成完整闭环的典型练习。这套资源把 Proteus 仿真工程和 Keil C 源码都整理好了51 单片机作为控制核心DHT11 采集温湿度LCD1602 实时显示脉搏信号通过外部脉冲模拟正常时绿色 LED 亮、超出阈值变红色 LED。适合单片机原理课设、课程大作业或者答辩前想快速复现整套系统的人省去从零画原理图、再调单总线时序的重复劳动。2. 总体架构与器件选型为什么是 51 DHT11 LCD16022.1 系统框架与信号流向整个系统可以拆成五块单片机最小系统、DHT11 温湿度采集、LCD1602 显示、脉搏信号输入、键盘与 LED 指示。单片机负责所有逻辑DHT11 通过单总线把 40 位数据送给单片机其中高 16 位是湿度、低 16 位是温度单片机解析后把温湿度值送 LCD 显示。脉搏信号在仿真里是一串方波脉冲每一分钟的脉冲个数对应心率单片机用外部中断统计脉冲数量再用定时器门控计时算出的每小时心率也显示在 LCD 上并驱动红绿 LED 指示当前状态。键盘的作用是调整心率报警阈值的上下限。选型理由要往课设场景靠资源里用的是 AT89C51 或 STC89C52 这类经典 51 单片机它们在 Proteus 里仿真模型非常成熟Keil 工程直接打开就能编译不需要额外配置芯片头文件。DHT11 是数字温湿度传感器单总线协议免校准比 DS18B20 多了一条湿度数据线正好符合“户外运动参数”这个题目。LCD1602 显示字符够用不用碰复杂的图形屏驱动。红绿 LED 加限流电阻即可所有元件都能在 Proteus 里仿真不会因为缺硬件而卡住进度。2.2 资源包里的文件应该怎么用解压后你会看到 Proteus 工程、Keil 工程、仿真备份和设计文档核心文件如下文件/路径类型用途打开方式户外运动.pdsprjProteus 仿真工程原理图与仿真加载 HEX 后运行Proteus 8 及以上程序.uvprojKeil C51 工程编译源码生成 程序.hexKeil uVision4/5程序.hex编译产物烧录到仿真单片机的固件Proteus 双击单片机加载LCD1602.c / .h模块驱动1602 初始化、写命令/写数据Keil 工程内DHT11.c / .h模块驱动单总线时序、温湿度数据解析Keil 工程内Delay.c / .h工具函数微秒/毫秒延时时序必需Keil 工程内户外运动.docx设计报告课设文档骨架Word建议先打开 Proteus 原理图确认器件引脚的端口映射再打开 Keil 工程编译。HEX 文件已经存在可以直接加载到 Proteus 里跑但如果你想改逻辑还是要在 Keil 里重新编译。注意 Proteus 里双击单片机Program File 一栏选 程序.hexClock Frequency 设成 12MHz和代码里的定时器初值保持一致否则串口和延时全偏。2.3 脉搏监测在仿真中的接法真实场景里户外运动监测会用到光电式脉搏传感器把手指血管搏动转成电压脉冲但在 Proteus 仿真阶段通常直接用信号发生器产生频率可调的方波接到 P3.2 口。单片机外部中断 INT0 配置为下降沿触发方波的每个下降沿触发一次中断相当于一次心跳。这样处理的好处是把前端调理电路暂时黑盒化把验证重点放在单片机侧的计数逻辑、阈值判断和 LED 控制上。如果后面要换真实模块做法是把传感器输出的脉冲端接到 INT0再去掉信号发生器即可。工程里没有单独的脉搏传感器驱动文件因为它本质上是外部脉冲输入不属于单片机总线设备。课设答辩时能把这个区分讲清楚就已经比多数只会照着仿真跑的人深入一层了。3. DHT11 温湿度采集与 LCD1602 显示驱动实战3.1 单总线时序与 DHT11 读数据DHT11 是单总线器件数据引脚接 P1.0必须外接 4.7kΩ 上拉电阻。读取流程分两步主机先拉低数据线至少 18ms 唤醒 DHT11再释放总线随后 DHT11 会拉低 80us 响应再拉高 80us之后连续输出 40 位数据。每一位数据都由低电平 50us 加高电平组成高电平持续 26~28us 代表逻辑 0持续 70us 左右代表逻辑 1。我一般这样写读取函数sbit DHT11_DATA P1^0; // 数据引脚 unsigned char dht11_buf[4] {0, 0, 0, 0}; // 湿度高/低、温度高/低 unsigned char DHT11_Read_Byte(void) { unsigned char i, dat 0; for (i 0; i 8; i) { while (!DHT11_DATA); // 等待当前数据位低电平结束 Delay_us(40); // 稳定后采集落在高电平中间 if (DHT11_DATA) { dat | (0x80 i); // 高电平时间长读为 1 } while (DHT11_DATA); // 等待高电平结束准备下一 bit } return dat; } void DHT11_Read(void) { unsigned char i; DHT11_DATA 0; Delay_ms(20); // 主机拉低 20ms启动信号 DHT11_DATA 1; Delay_us(10); // 释放总线等待设备响应 if (!DHT11_DATA) { while (!DHT11_DATA); // 跳过 80us 低电平响应 while (DHT11_DATA); // 跳过 80us 高电平响应 for (i 0; i 4; i) { dht11_buf[i] DHT11_Read_Byte(); } // 校验位 dht11_buf[4] 应与前四个字节和的低 8 位相等 } }这段代码的关键是用了固定延时 40us 后再判断电平而不是去精确测量高电平宽度。因为 51 单片机在 12MHz 下指令周期是 1us循环判断会引入不可忽略的误差固定点采样反而更稳定。注意0x80 i是按位写入第 i 位读到的第一个 bit 是最高位。3.2 8 位模式驱动 LCD1602LCD1602 在 Proteus 里常用 8 位模式P0 口接 D0~D7RS、RW、EN 分别接 P2 口的高位。由于 P0 口内部没有上拉电阻原理图里必须放一个 10kΩ 排阻否则数据线上的高电平拉不起来表现为白屏或花屏。初始化指令固定为 0x38、0x0C、0x01分别表示 8 位模式、显示开光标关、清屏。代码里几个基础函数这样写sbit LCD_RS P2^6; sbit LCD_RW P2^5; // 接线里 RW 常接地这里保留 sbit LCD_EN P2^7; void LCD_WriteCmd(unsigned char cmd) { LCD_RS 0; LCD_RW 0; P0 cmd; LCD_EN 1; Delay_us(20); LCD_EN 0; Delay_ms(2); // 命令执行时间大于 1.52ms } void LCD_WriteData(unsigned char dat) { LCD_RS 1; LCD_RW 0; P0 dat; LCD_EN 1; Delay_us(20); LCD_EN 0; Delay_us(50); } void LCD_ShowString(unsigned char line, unsigned char col, unsigned char *str) { if (line 0) { LCD_WriteCmd(0x80 col); // 第一行 DDRAM 基址 } else { LCD_WriteCmd(0xC0 col); // 第二行 DDRAM 基址 } while (*str) { LCD_WriteData(*str); } }RW 引脚在仿真里直接接地也不会报错但保留这个端口可以在接真实硬件时多一个控制信号。0x80 col是设定显示位置第一行地址从 0x80 开始第二行从 0xC0 开始这是 LCD1602 的固定寻址规则。3.3 主循环里的显示刷新策略DHT11 读取一次要 20ms 启动时间两次读取间隔至少要 1 秒否则传感器不会刷新数据。所以主循环里用软件延时控制读取频率每秒读一次温湿度刷新 LCD剩余时间去处理脉搏和按键不要让主循环一直死读 DHT11。void main(void) { unsigned char temp10, temp1, humi10, humi1; LCD_Init(); DHT11_Init(); while (1) { DHT11_Read(); humi10 dht11_buf[0] / 10; humi1 dht11_buf[0] % 10; temp10 dht11_buf[2] / 10; temp1 dht11_buf[2] % 10; LCD_ShowString(0, 0, Temp: ); LCD_WriteData(temp10 0); LCD_WriteData(temp1 0); LCD_WriteData(C); // 湿度显示同理放在第二行 Delay_ms(1000); } }这里用/10和%10把十位和个位拆开再转成 ASCII 字符送到 LCD。如果直接用整数值显示LCD1602 看到的是不可见字符。这个刷新策略能保证每次读到的 DHT11 数据都是完整帧不会因为中途插入其他耗时操作而丢失数据位。4. 脉搏计数、阈值判断与 LED 状态控制4.1 用外部中断和定时器测心率心率计算的本质是统计每分钟脉冲数。在 Proteus 里把信号发生器的方波接到 P3.2也就是 INT0 引脚配置为下降沿触发。每检测到一个下降沿中断服务程序里计数器加 1。再用定时器 T1 做 50ms 中断累计 20 次就是 1 秒。但直接用 1 秒计数乘 60 误差很大显示数字会跳得厉害。我一般采用 15 秒统计窗口脉冲数乘 4 得到每分钟心率这样即使有一两个脉冲抖动对最终结果的影响也只有 4~8 次/分表针不会抖得没法看。sbit INT0_PIN P3^2; unsigned int pulse_cnt 0; unsigned char timer_cnt 0; unsigned char second_cnt 0; unsigned int bpm 0; void Timer1_Init(void) { TMOD (TMOD 0x0F) | 0x10; // T1 方式 116 位定时 TH1 0x3C; // 12MHz 下 50ms 初值 TL1 0xB0; ET1 1; TR1 1; IT0 1; // INT0 下降沿触发 EX0 1; EA 1; } void Int0_ISR(void) interrupt 0 { pulse_cnt; } void Timer1_ISR(void) interrupt 3 { timer_cnt; if (timer_cnt 20) { // 50ms * 20 1 秒 timer_cnt 0; second_cnt; if (second_cnt 15) { // 15 秒窗口结束 bpm pulse_cnt * 4; // 换算成每分钟心率 pulse_cnt 0; second_cnt 0; } } }T1 初值 0x3CB0 是按照 12MHz 晶振计算的。如果你手上的仿真原理图用的是 11.0592MHz 晶振50ms 定时会有微小偏差但因为心率窗口是 15 秒相对计时短时误差不会导致数量级错误答辩时能说清楚这一点即可。注意中断服务程序里不要放 LCD 刷新这类耗时操作只做计数和标志位更新。4.2 阈值判断与 LED 红绿切换正常成年人静息心率约 60~100 次/分课设里直接定义上下限。LED 采用共阴接法高电平点亮红绿两灯分别接 P1.6 和 P1.7串 220Ω 限流电阻后接地。sbit LED_G P1^6; sbit LED_R P1^7; #define HR_LOW 60 #define HR_HIGH 100 void LED_Update(unsigned int hr) { if ((hr HR_LOW) (hr HR_HIGH)) { LED_G 1; LED_R 0; } else { LED_G 0; LED_R 1; } }这里把边界值 60 和 100 判定为正常状态避免心率在临界点附近来回抖动时 LED 频繁切换。如果要区分“过高”和“过低”可以再加两个分支红色 LED 闪烁频率分别表示异常类型但课程设计里通常只要求红绿指示不需要做细分。4.3 键盘调整阈值的实现键盘在文件里是独立按键通常接 P1 口或 P3 口。真实户外运动场景中心率本来就会随运动强度升高把阈值写死不太合理所以我习惯把高阈值做成可调。扫描代码加软件消抖sbit KEY_UP P1^3; // 阈值加 sbit KEY_DOWN P1^4; // 阈值减 unsigned int limit_low 60, limit_high 100; void Key_Scan(void) { if (!KEY_UP) { Delay_ms(10); if (!KEY_UP) { limit_high 5; if (limit_high 150) limit_high 150; while (!KEY_UP); // 等待松手防止连加 } } if (!KEY_DOWN) { Delay_ms(10); if (!KEY_DOWN) { limit_high - 5; if (limit_high limit_low 10) limit_high limit_low 10; while (!KEY_DOWN); } } }按键默认按下为低电平因为仿真里按键另一端接地。上限每次加减 5范围限制在 60~150这样不会出现下限大于上限的逻辑错误。主循环里先扫按键、再更新 LED就能实时看到阈值变化的效果。5. Proteus 联调、时序排错与波形验证技巧5.1 联调顺序与频率匹配先把系统分成三段验证最小系统、DHT11、LCD 与脉搏。单片机晶振频率必须和代码里的延时参数一致。代码按 12MHz 写仿真里双击单片机把 Clock Frequency 设成 12MHz。如果设成 24MHzDHT11 的 20ms 启动信号会变成 40ms虽然能读但后续 40us 采样点会偏移经常出现偶尔读到错误湿度的情况。5.2 用信号发生器设置脉搏输入Proteus 左侧工具栏选 Signal Generator输出类型设成 DSquare频率填 1.2Hz幅值 5V。1.2Hz 的方波相当于每分钟 72 次心跳属于正常范围绿色 LED 应该亮。把频率改成 2Hz相当于 120 次/分LED 应变红。接线时信号发生器正极接 P3.2负极接 GND不接 GND 会导致电平浮空中断永远不触发。5.3 常见现象与排错速查现象可能原因检查点温湿度不更新DHT11 上拉电阻缺失或启动延时不对数据线是否接 4.7k 上拉DHT11_Read 拉低是否够 20msLCD 白屏或花屏P0 上拉缺失、初始化指令顺序不对排阻是否接好初始化是否按 0x38、0x0C、0x01心率一直为 0信号发生器负极没接 GND、中断引脚接错用示波器看 P3.2 是否有下降沿确认幅值是否 5VLED 两个都不亮共阴/共阳方向搞反、限流电阻太大实测 LED 两端电平高电平亮应选共阴接法按按键无反应按键电平拉不到低、引脚配置错误确认按键另一端接 GNDP1 口是否启用上拉排错时不要只盯着代码直接在 Proteus 里挂示波器看波形。把 DHT11 数据引脚连到示波器 A 通道正常应该看到先一个低电平 20ms 的大坑随后 40 位数据的小脉冲如果只有持续高电平静态说明 DHT11 的上拉电阻漏了或启动信号没发出去。这个方法同样适用于脉搏信号把示波器接到 P3.2能清晰看到方波下降沿数量和信号发生器频率对不对得上一旦这点确认中断计数部分就不会出大问题。本文还有配套的精品资源点击获取