目录
- 现象
- 材料
- 如何连接
- 代码
- 重点代码块理解
- DHT11
- 数值放大与取整
- 四位数字拆分与小数点控制
- 总结
- 扩展:阻塞与非阻塞
- 阻塞(Blocking)
- 非阻塞
- 对比
- 总结2
现象
材料
-Arduino Uno控制器
– 1个四位数码管
– 1个DHT11传感器
– 1个面包版
如何连接
可以参考四位数码管原理图结合代码连接
代码
#include <DHT.h> // ====================== DHT11 配置(仅保留温湿度相关) ====================== #define DHTPIN A1 // DHT11数据引脚 #define DHTTYPE DHT11 // 传感器类型 DHT dht(DHTPIN, DHTTYPE); // 温湿度核心变量(控制显示切换和存储数值) float currentTemp = 0.0; // 当前温度 float currentHum = 0.0; // 当前湿度 bool showTemp = true; // true=显示温度,false=显示湿度 unsigned long displaySwitchTime = 0; // 显示切换时间戳 const long switchInterval = 2000; // 2秒切换一次 // DHT11 定时读取控制(非阻塞,避免卡顿显示) unsigned long previousDHTMillis = 0; const long dhtInterval = 2000; // 每2秒读取一次温湿度 // ====================== 四位数码管配置(确保温湿度正常显示) ====================== int ledCount = 8; // 段脚数量(a-g + 小数点) int segCount = 4; // 四位数码管位选数量 // 共阴数码管段码表(0-9):bit0=a, bit1=b, bit2=c, bit3=d, bit4=e, bit5=f, bit6=g, bit7=小数点 const unsigned char DuanMa[10] = { 0x3f, // 0 0x06, // 1 0x5b, // 2 0x4f, // 3 0x66, // 4 0x6d, // 5 0x7d, // 6 0x07, // 7 0x7f, // 8 0x6f // 9 }; // 数码管引脚映射(与你的接线一致,无需修改) int ledPins[] = {7,8,9,10,11,12,13,A0}; // 段脚:a,b,c,d,e,f,g,小数点 int segPins[] = {2,3,5,6}; // 位选:千、百、十、个 unsigned char displayBuffer[4]; // 四位显示缓冲区(对应千、百、十、个位) // ====================== 初始化函数 ====================== void setup() { // 串口初始化(用于查看温湿度数值,方便调试) Serial.begin(9600); while (!Serial); Serial.println("系统启动 - DHT11 + 四位数码管(温湿度显示)"); // DHT11 初始化 dht.begin(); // 数码管引脚设置为输出模式 for(int ledpin = 0; ledpin < ledCount; ledpin++) { pinMode(ledPins[ledpin], OUTPUT); } for(int segpin = 0; segpin < segCount; segpin++) { pinMode(segPins[segpin], OUTPUT); } // 初始读取温湿度并更新显示缓冲区(开机即显示) readDHT(); updateDisplayBuffer(); } // ====================== 主循环(核心:温湿度读取 + 切换 + 数码管显示) ====================== void loop() { unsigned long currentMillis = millis(); // 1. 定时非阻塞读取温湿度 if (currentMillis - previousDHTMillis >= dhtInterval) { previousDHTMillis = currentMillis; readDHT(); } // 2. 每2秒交替切换温度/湿度显示 if (currentMillis - displaySwitchTime >= switchInterval) { displaySwitchTime = currentMillis; showTemp = !showTemp; // 切换显示状态 updateDisplayBuffer(); // 切换后立即更新显示缓冲区 } // 3. 数码管动态扫描(核心:让温湿度稳定显示在四位数码管上) static unsigned long lastScanTime = 0; static int currentDigit = 0; // 当前扫描的数码管位(0=千位,1=百位,2=十位,3=个位) if (currentMillis - lastScanTime >= 3) { // 3ms扫描一次,避免闪烁 lastScanTime = currentMillis; // 消影处理:先关闭当前点亮的数码管,避免重影 digitalWrite(segPins[currentDigit], HIGH); deal(0x00); // 切换到下一位数码管(循环扫描四位) currentDigit = (currentDigit + 1) % 4; // 点亮当前位数码管,显示对应缓冲区的段码(温湿度数值) digitalWrite(segPins[currentDigit], LOW); deal(displayBuffer[currentDigit]); } } // ====================== 读取DHT11温湿度(确保数值准确,用于数码管显示) ====================== void readDHT() { float humidity = dht.readHumidity(); float temperature = dht.readTemperature(); // 数据有效性检查(避免无效数值显示) if (isnan(humidity) || isnan(temperature)) { Serial.println("读取DHT11失败!"); currentTemp = 0.0; currentHum = 0.0; } else { currentTemp = temperature; currentHum = humidity; // 串口打印数值,方便对比数码管显示是否准确 Serial.print("湿度: "); Serial.print(currentHum); Serial.print("% | 温度: "); Serial.print(currentTemp); Serial.println("℃"); } } // ====================== 更新显示缓冲区(核心:将温湿度转换为数码管可识别的段码) ====================== void updateDisplayBuffer() { // 获取当前需要显示的数值(温度或湿度) float displayValue = showTemp ? currentTemp : currentHum; // 转换为整数(放大100倍,保留两位小数:如25.68 → 2568) long intValue = round(displayValue * 100); // 拆分四位数字,存入显示缓冲区(对应四位数码管,百位带小数点) displayBuffer[0] = DuanMa[(intValue / 1000) % 10]; // 千位(整数部分高位) displayBuffer[1] = DuanMa[(intValue / 100) % 10] | 0x80; // 百位(整数部分低位)+ 小数点(0x80=小数点亮) displayBuffer[2] = DuanMa[(intValue / 10) % 10]; // 十位(小数部分高位) displayBuffer[3] = DuanMa[intValue % 10]; // 个位(小数部分低位) // 补0处理:确保四位数码管完整显示,避免高位空缺 if (intValue < 1000) { // 数值小于10(如5.28 → 0528),千位补0 displayBuffer[0] = DuanMa[0]; } if (intValue < 100) { // 数值小于1(如0.68 → 0068),百位补0 displayBuffer[1] = DuanMa[0] | 0x80; } } // ====================== 数码管段码输出(消影+段码写入,确保显示清晰) ====================== void deal(unsigned char value) { // 1. 先关闭所有段脚,消除重影 for(int i = 0; i < 8; i++) { digitalWrite(ledPins[i], LOW); } delayMicroseconds(100); // 短暂延时,增强消影效果 // 2. 逐位写入段码,控制数码管显示对应数字 for(int i = 0; i < 8; i++) { digitalWrite(ledPins[i], bitRead(value, i)); } }重点代码块理解
1、
DHT11
#define DHTPIN A1 // 温湿度传感器数据引脚(固定接线) #define DHTTYPE DHT11 // 传感器型号(不可随意修改,对应库函数逻辑) DHT dht(DHTPIN, DHTTYPE); // 初始化DHT对象(库函数要求,用于后续读取温湿度)DHT 是第三方库提供的类,必须通过 dht.begin()(初始化)和 dht.readHumidity()/dht.readTemperature()(读取数据)使用,引脚定义需与实际接线一致。
进行代码的导入时要先安装对应的库文件
温湿度显示
2、
bool showTemp = true; // 显示状态标志位(核心开关) const long switchInterval = 2000; // 2秒切换间隔(可自定义修改)howTemp 是 “温度 / 湿度” 显示的核心切换开关,true 显示温度、false 显示湿度,通过 !showTemp 实现每 2 秒交替翻转。
3、
数值放大与取整
long intValue = round(displayValue * 100); // 如25.68℃ → 2568(保留两位小数)温湿度是浮点型(带小数),而数码管只能显示整数,通过放大 100 倍并四舍五入,将 “两位小数” 转换为 “四位整数”,方便四位数码管逐位显示。
四位数字拆分与小数点控制
displayBuffer[0] = DuanMa[(intValue / 1000) % 10]; // 千位(2568→2) displayBuffer[1] = DuanMa[(intValue / 100) % 10] | 0x80; // 百位(2568→5)+ 小数点(0x80=小数点亮) displayBuffer[2] = DuanMa[(intValue / 10) % 10]; // 十位(2568→6) displayBuffer[3] = DuanMa[intValue % 10]; // 个位(2568→8)通过除法和取余运算,拆分四位整数的每一位;| 0x80 是给百位段码添加小数点(对应数码管的小数点引脚),实现 “25.68” 的显示格式(千位 2、百位 5.、十位 6、个位 8)。
4、
高位补0:
if (intValue < 1000) displayBuffer[0] = DuanMa[0]; // 数值<10(如5.28→0528),千位补0
if (intValue < 100) displayBuffer[1] = DuanMa[0] | 0x80; // 数值<1(如0.68→0068),百位补0
确保四位数码管始终满屏显示,避免高位空缺(如 5.2℃不会显示为 “5.2”,而是 “05.20”),视觉效果更整洁
总结
DHT11采集温湿度 → updateDisplayBuffer()转换为段码(存入displayBuffer) → 动态扫描逐位读取缓冲区,点亮数码管 → 每2秒切换温度/湿度显示
扩展:阻塞与非阻塞
阻塞(Blocking)
阻塞是指程序执行到某段代码时,会暂停在这里等待任务完成,在等待期间无法执行其他任何代码,直到该任务结束后,才会继续向下执行后续逻辑。
非阻塞
非阻塞是指程序执行到某段代码时,不会暂停等待,而是通过 “时间戳对比” 判断任务是否需要执行,若不需要执行则直接跳过,继续执行后续代码;若需要执行则执行该任务,执行完成后继续向下流转
对比
阻塞
void loop() { // 任务1:打印信息 Serial.println("执行任务1"); // 阻塞延时2秒:这2秒内,程序完全暂停,无法执行任何其他代码 delay(2000); // 任务2:仅在2秒延时结束后,才会执行 Serial.println("执行任务2(阻塞方式,2秒一次)"); }非阻塞
// 定义时间戳变量,用于记录上一次执行任务的时间 unsigned long previousMillis = 0; const long interval = 2000; // 2秒间隔 void loop() { // 获取当前系统时间(上电后的毫秒数,实时更新) unsigned long currentMillis = millis(); // 非阻塞判断:对比当前时间与上一次执行时间的差值,是否达到2秒 if (currentMillis - previousMillis >= interval) { // 记录本次执行的时间(更新时间戳) previousMillis = currentMillis; // 任务2:仅当满足2秒间隔时,才执行 Serial.println("执行任务2(非阻塞方式,2秒一次)"); } // 任务1:不受任务2的影响,会持续快速执行(无卡顿) Serial.println("执行任务1(持续执行,无阻塞)"); }总结2
阻塞:delay(ms) 主导,程序会 “暂停等待”,期间无法执行其他代码,适用于简单单任务,不适用于你的多功能并行场景;
非阻塞:millis() 主导,通过 “时间戳对比” 判断任务是否执行,无暂停、无卡顿,支持多任务并行(温湿度采集 + 数码管显示 + 显示切换),是你的代码能稳定运行的核心;
你的代码全程采用非阻塞逻辑,这是 Arduino 高级编程的关键,理解它就能编写更复杂、更流畅的嵌入式程序。