首页 > 编程语言 >【花雕学编程】Arduino动手做(230)---ESP32 CAM 长时延时摄影:在拍摄之间使设备休眠并记住帧号

【花雕学编程】Arduino动手做(230)---ESP32 CAM 长时延时摄影:在拍摄之间使设备休眠并记住帧号

时间:2024-09-13 09:56:54浏览次数:3  
标签:动手做 pin Arduino 长时 GPIO Serial config EEPROM SD

在这里插入图片描述

37款传感器与执行器的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的,这里准备逐一动手尝试系列实验,不管成功(程序走通)与否,都会记录下来——小小的进步或是搞不掂的问题,希望能够抛砖引玉。

【Arduino】168种传感器模块系列实验(资料代码+仿真编程+图形编程)
实验二百三十:ESP32 CAM开发板 带OV2640摄像头模块 WIFI+蓝牙模块

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
ESP32-CAM 是一款非常小的摄像头模块,配备 ESP32-S 芯片。除了 OV2640 摄像头和几个用于连接外围设备的 GPIO 外,它还具有一个 microSD 卡插槽,可用于存储使用摄像头拍摄的图像或存储文件以提供给客户。包括乐鑫 ESP32-S Wifi + 蓝牙+BLE 芯片、2MP 摄像头模块 OV2640 和带有 CH340 UART 芯片的 USB 编程适配器。

ESP32-Cam 是一款运行在 ESP32-S 芯片上并使用 OV2640 摄像头的小型摄像头模块。ESP32_Cam 也可以 OV7670 摄像头,但 OV2640 更好(更高的分辨率和内置的 JPEG 编码,这消除了 ESP32-S 的处理任务)。

ESP-32 Cam 规格
ESP-32 系列
它支持 Wi-Fi (802.11b/g/n)
支持蓝牙 (4.2 带 BLE)
内置 LED 闪光灯
9 个 IO 端口
支持 UART、SPI、I2C 和 PWM
内置 micro SD 读卡器
输入电源:3.3V / 5V(据报道,5V 供电比 3.3V 更稳定)

OV2640 摄像头
2 百万像素
阵列尺寸:UXGA (1600 x 1200)
镜头尺寸:1/4 英寸(6.35 毫米)
最大图像传输速率:15 帧/秒

在这里插入图片描述

实验模块接线示意图

在这里插入图片描述
【Arduino】168种传感器模块系列实验(资料代码+仿真编程+图形编程)
实验二百三十:ESP32 CAM开发板 带OV2640摄像头模块 WIFI+蓝牙模块
项目实验之十二:ESP32 CAM 长时延时摄影:在拍摄之间使设备休眠并记住帧号

实验开源代码

/*
  【Arduino】168种传感器模块系列实验(资料代码+仿真编程+图形编程)
  实验二百三十:ESP32 CAM开发板 带OV2640摄像头模块 WIFI+蓝牙模块
  项目实验之十二:ESP32 CAM 长时延时摄影:在拍摄之间使设备休眠并记住帧号
*/

#include "esp_camera.h"
#include "SD_MMC.h"
#include "EEPROM.h"

#define EEPROM_SIZE_IN_BYTES 1 // 定义EEPROM的大小为1字节

// 选择摄像头型号
//#define CAMERA_MODEL_WROVER_KIT // 有PSRAM
//#define CAMERA_MODEL_ESP_EYE // 有PSRAM
//#define CAMERA_MODEL_M5STACK_PSRAM // 有PSRAM
//#define CAMERA_MODEL_M5STACK_V2_PSRAM // M5Camera版本B有PSRAM
//#define CAMERA_MODEL_M5STACK_WIDE // 有PSRAM
//#define CAMERA_MODEL_M5STACK_ESP32CAM // 无PSRAM
#define CAMERA_MODEL_AI_THINKER // 有PSRAM
//#define CAMERA_MODEL_TTGO_T_JOURNAL // 无PSRAM

#include "camera_pins.h"
const char* photoPrefix = "/photo_"; // 照片前缀
int photoNumber = 0; // 照片编号

#define MICROSECONDS_IN_SECONDS 1000000 // 每秒的微秒数
#define SLEEP_TIME_IN_SECONDS 120 // 睡眠时间(秒)
unsigned long sleepTime = MICROSECONDS_IN_SECONDS * SLEEP_TIME_IN_SECONDS; // 睡眠时间(微秒)

void setup() {
  Serial.begin(115200); // 初始化串口通信,波特率为115200
  //Serial.setDebugOutput(true);
  //Serial.println();

  Serial.println("ESP32正在唤醒..."); // 打印唤醒信息

  EEPROM.begin(EEPROM_SIZE_IN_BYTES); // 初始化EEPROM
  photoNumber = EEPROM.read(0); // 从EEPROM读取照片编号

  Serial.println("从偏好设置加载的下一个照片编号: " + String(photoNumber)); // 打印照片编号

  camera_config_t config; // 定义摄像头配置
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000; // XCLK频率
  config.pixel_format = PIXFORMAT_JPEG; // 像素格式

  // 如果存在PSRAM IC,使用UXGA分辨率和更高的JPEG质量进行初始化
  // 为更大的预分配帧缓冲区
  if (psramFound()) {
    config.frame_size = FRAMESIZE_UXGA;
    config.jpeg_quality = 10;
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 12;
    config.fb_count = 1;
  }

#if defined(CAMERA_MODEL_ESP_EYE)
  pinMode(13, INPUT_PULLUP);
  pinMode(14, INPUT_PULLUP);
#endif

  // 摄像头初始化
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("摄像头初始化失败,错误代码0x%x", err);
    return;
  }

  sensor_t * s = esp_camera_sensor_get();
  // 初始传感器垂直翻转,颜色有点饱和
  if (s->id.PID == OV3660_PID) {
    s->set_vflip(s, 1); // 翻转回来
    s->set_brightness(s, 1); // 提高亮度
    s->set_saturation(s, -2); // 降低饱和度
  }

  // 如果需要,微调图像
  s->set_brightness(s, -1); // 亮度范围-2到2
  //s->set_contrast(s, 1); // 对比度范围-2到2
  //s->set_saturation(s, 1); // 饱和度范围-2到2
  //s->set_wb_mode(s, 0); // 白平衡模式0到4
  //s->set_special_effect(s, 0); // 特效0: 无, 1: 负片, 2: 灰度, 3: 红色, 4: 绿色, 5: 蓝色, 6: 棕褐色
  //s->set_colorbar(s, 1); // 彩条1或0

  // 降低帧大小以提高初始帧率
  //s->set_framesize(s, FRAMESIZE_QVGA);
  s->set_framesize(s, FRAMESIZE_XGA);
  //s->set_framesize(s, FRAMESIZE_HD);

#if defined(CAMERA_MODEL_M5STACK_WIDE) || defined(CAMERA_MODEL_M5STACK_ESP32CAM)
  s->set_vflip(s, 1);
  s->set_hmirror(s, 1);
#endif

  Serial.println("初始化SD卡");
  if (!SD_MMC.begin()) {
    Serial.println("SD卡初始化失败!");
    return;
  }

  uint8_t cardType = SD_MMC.cardType();
  if (cardType == CARD_NONE) {
    Serial.println("SD卡槽似乎是空的!");
    return;
  }

  // 如果SD卡为空,则将EEPROM文件计数器重置为0
  ResetPhotoNumbering();

  // 拍摄第一张照片但不保存,因为它通常有绿色的色调
  TakePhoto(false);

  // 拍摄第二张照片并保存到SD卡
  TakePhoto(true);

  Serial.println("将进入深度睡眠,持续 " + String(sleepTime) + " 微秒...");
  Serial.flush();

  // 设置ESP32的睡眠时间间隔
  esp_sleep_enable_timer_wakeup(sleepTime);

  // 让ESP32进入深度睡眠
  esp_deep_sleep_start();

}

void loop() {
  // 本示例不需要循环代码
}

void TakePhoto(bool savePhoto) {

  camera_fb_t * fb = NULL;

  // 使用摄像头拍照
  fb = esp_camera_fb_get();
  if (!fb) {
    Serial.println("摄像头拍照失败");
    return;
  }

  if (!savePhoto) {
    return;
  }

  String photoFileName = photoPrefix + String(photoNumber) + ".jpg";

  fs::FS &fs = SD_MMC;
  Serial.printf("照片文件名: %s\n", photoFileName.c_str());

  File file = fs.open(photoFileName.c_str(), FILE_WRITE);
  if (!file) {
    Serial.println("打开文件写入模式失败");
  }
  else {
    file.write(fb->buf, fb->len); // 负载(图像),负载长度
    Serial.println("文件保存路径: " + String(photoFileName));
    ++photoNumber;
    if (photoNumber > 255) {
      photoNumber = 0;
    }

    EEPROM.write(0, photoNumber);
    EEPROM.commit();
    Serial.println("下一个照片编号已保存到偏好设置: " + String(photoNumber));
  }
  file.close();
  esp_camera_fb_return(fb);
}

void ResetPhotoNumbering() {

  fs::FS &fs = SD_MMC;
  File sdCardRoot = fs.open("/");

  if (!sdCardRoot) {
    Serial.println("打开SD卡根目录失败!");
    return;
  }

  if (!sdCardRoot.isDirectory()) {
    Serial.println("无法读取SD卡根目录!");
    return;
  }

  File file = sdCardRoot.openNextFile();
  if (file.available() > 0) {
    Serial.println("SD卡不为空");
  } else {
    Serial.println("SD卡为空");
    photoNumber = 0;
    EEPROM.write(0, photoNumber);
    EEPROM.commit();
    Serial.println("下一个照片编号重置为0");
  }
}

实验串口返回情况

在这里插入图片描述
代码解释:

1、引入库和定义常量

#include "esp_camera.h"
#include "SD_MMC.h"
#include "EEPROM.h"

#define EEPROM_SIZE_IN_BYTES 1 // 定义EEPROM的大小为1字节

esp_camera.h: 用于摄像头的初始化和操作。
SD_MMC.h: 用于SD卡的初始化和操作。
EEPROM.h: 用于EEPROM的读写操作。
EEPROM_SIZE_IN_BYTES: 定义EEPROM的大小为1字节,用于存储照片编号。

2、选择摄像头型号

#define CAMERA_MODEL_AI_THINKER // 有PSRAM
#include "camera_pins.h"

CAMERA_MODEL_AI_THINKER: 选择AI Thinker摄像头模块。
camera_pins.h: 包含摄像头引脚定义。

3、定义全局变量和常量

const char* photoPrefix = "/photo_"; // 照片前缀
int photoNumber = 0; // 照片编号

#define MICROSECONDS_IN_SECONDS 1000000 // 每秒的微秒数
#define SLEEP_TIME_IN_SECONDS 120 // 睡眠时间(秒)
unsigned long sleepTime = MICROSECONDS_IN_SECONDS * SLEEP_TIME_IN_SECONDS; // 睡眠时间(微秒)

photoPrefix: 照片文件名前缀。
photoNumber: 照片编号,从EEPROM读取。
MICROSECONDS_IN_SECONDS: 每秒的微秒数。
SLEEP_TIME_IN_SECONDS: 睡眠时间(秒)。
sleepTime: 睡眠时间(微秒)。

4、初始化设置

void setup() {
  Serial.begin(115200); // 初始化串口通信,波特率为115200
  Serial.println("ESP32正在唤醒...");

  EEPROM.begin(EEPROM_SIZE_IN_BYTES); // 初始化EEPROM
  photoNumber = EEPROM.read(0); // 从EEPROM读取照片编号
  Serial.println("从偏好设置加载的下一个照片编号: " + String(photoNumber));

Serial.begin(115200): 初始化串口通信,波特率为115200。
EEPROM.begin(EEPROM_SIZE_IN_BYTES): 初始化EEPROM。
photoNumber = EEPROM.read(0): 从EEPROM读取照片编号。

5、摄像头配置和初始化

 camera_config_t config; // 定义摄像头配置
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000; // XCLK频率
  config.pixel_format = PIXFORMAT_JPEG; // 像素格式

  if(psramFound()){
    config.frame_size = FRAMESIZE_UXGA;
    config.jpeg_quality = 10;
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 12;
    config.fb_count = 1;
  }

  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("摄像头初始化失败,错误代码0x%x", err);
    return;
  }

camera_config_t config: 定义摄像头配置结构体。
config.pin_xxx: 设置摄像头引脚。
config.xclk_freq_hz: 设置XCLK频率。
config.pixel_format: 设置像素格式为JPEG。
psramFound(): 检查是否存在PSRAM,调整帧大小和JPEG质量。
esp_camera_init(&config): 初始化摄像头。

6、传感器设置

  sensor_t * s = esp_camera_sensor_get();
  if (s->id.PID == OV3660_PID) {
    s->set_vflip(s, 1); // 翻转回来
    s->set_brightness(s, 1); // 提高亮度
    s->set_saturation(s, -2); // 降低饱和度
  }

  s->set_brightness(s, -1); // 亮度范围-2到2
  s->set_framesize(s, FRAMESIZE_XGA);

sensor_t * s = esp_camera_sensor_get(): 获取摄像头传感器。
s->set_vflip(s, 1): 垂直翻转图像。
s->set_brightness(s, 1): 设置亮度。
s->set_saturation(s, -2): 设置饱和度。
s->set_framesize(s, FRAMESIZE_XGA): 设置帧大小。

7、初始化SD卡

  Serial.println("初始化SD卡");
  if(!SD_MMC.begin()){
    Serial.println("SD卡初始化失败!");
    return;
  }

  uint8_t cardType = SD_MMC.cardType();
  if(cardType == CARD_NONE){
    Serial.println("SD卡槽似乎是空的!");
    return;
  }

  ResetPhotoNumbering();

SD_MMC.begin(): 初始化SD卡。
SD_MMC.cardType(): 检查SD卡类型。
ResetPhotoNumbering(): 重置照片编号。

8、拍照和保存照片

  TakePhoto(false); // 拍摄第一张照片但不保存
  TakePhoto(true); // 拍摄第二张照片并保存到SD卡

  Serial.println("将进入深度睡眠,持续 " + String(sleepTime) + " 微秒...");
  Serial.flush();

  esp_sleep_enable_timer_wakeup(sleepTime);
  esp_deep_sleep_start();
}

TakePhoto(false): 拍摄第一张照片但不保存。
TakePhoto(true): 拍摄第二张照片并保存到SD卡。
esp_sleep_enable_timer_wakeup(sleepTime): 设置ESP32的睡眠时间间隔。
esp_deep_sleep_start(): 让ESP32进入深度睡眠。

9、拍照函数

void TakePhoto(bool savePhoto) {
  if (!savePhoto) {
    return;
  }

  camera_fb_t * fb = esp_camera_fb_get();  
  if (!fb) {
    Serial.println("摄像头拍照失败");
    return;
  }

  String photoFileName = photoPrefix + String(photoNumber) + ".jpg";
  fs::FS &fs = SD_MMC; 
  Serial.printf("照片文件名: %s\n", photoFileName.c_str());

  File file = fs.open(photoFileName.c_str(), FILE_WRITE);
  if (!file) {
    Serial.println("打开文件写入模式失败");
  } else {
    file.write(fb->buf, fb->len); // 写入图像数据
    Serial.println("文件保存路径: " + String(photoFileName));
    ++photoNumber;
    if (photoNumber > 255) {
      photoNumber = 0;
    }

    EEPROM.write(0, photoNumber);
    EEPROM.commit();
    Serial.println("下一个照片编号已保存到偏好设置: " + String(photoNumber));
  }
  file.close();
  esp_camera_fb_return(fb);
}

TakePhoto(bool savePhoto): 拍照函数。
camera_fb_t * fb = esp_camera_fb_get(): 获取摄像头帧缓冲区。
String photoFileName = photoPrefix + String(photoNumber) + “.jpg”: 创建照片文件名。
File file = fs.open(photoFileName.c_str(), FILE_WRITE): 打开文件写入模式。
file.write(fb->buf, fb->len): 写入图像数据。
++photoNumber: 增加照片编号。
EEPROM.write(0, photoNumber): 更新EEPROM中的照片编号。
EEPROM.commit(): 提交EEPROM更改。
esp_camera_fb_return(fb): 释放帧缓冲区。

10、重置照片编号函数

void ResetPhotoNumbering() {
  fs::FS &fs = SD_MMC;
  File sdCardRoot = fs.open("/");

  if (!sdCardRoot) {
    Serial.println("打开SD卡根目录失败!");
    return;
  }

  if (!sdCardRoot.isDirectory()) {
    Serial.println("无法读取SD卡根目录!");
    return;
  }

  File file = sdCardRoot.openNextFile();
  if (file) {
    Serial.println("SD卡不为空");
  } else {
    Serial.println("SD卡为空");
    photoNumber = 0;
    EEPROM.write(0, photoNumber);
    EEPROM.commit();
    Serial.println("下一个照片编号重置为0");
  }
}

fs::FS &fs = SD_MMC: 使用SD_MMC文件系统。
File sdCardRoot = fs.open(“/”): 打开SD卡根目录。
if (!sdCardRoot): 检查是否成功打开根目录。
if (!sdCardRoot.isDirectory()): 检查根目录是否为目录。
File file = sdCardRoot.openNextFile(): 打开根目录中的下一个文件。
if (file): 检查SD卡是否不为空。
if (!file): 如果SD卡为空,重置照片编号为0,并更新EEPROM。

11、主循环函数

void loop() {
  // 本示例不需要循环代码
}

void loop(): 主循环函数。在这个示例中,不需要在循环中执行任何操作,因为所有逻辑都在setup()函数中完成。

总结
这个代码实现了以下功能:
1、初始化ESP32-CAM和SD卡。
2、从EEPROM读取照片编号。
3、配置摄像头参数并初始化摄像头。
4、拍摄照片并保存到SD卡。
5、更新EEPROM中的照片编号。
6、进入深度睡眠以节省电力。
7、在SD卡为空时重置照片编号。
这些功能使你的ESP32-CAM能够定期拍照并保存到SD卡,同时保持低功耗模式。

实验场景图

在这里插入图片描述

标签:动手做,pin,Arduino,长时,GPIO,Serial,config,EEPROM,SD
From: https://blog.csdn.net/weixin_41659040/article/details/142176752

相关文章

  • 使用Arduino Uno作为烧录器为Atmega328PB芯片直接烧录程序
    目录摘要烧录方式操作过程准备工作将Arduinouno设置为烧录器烧录器和目标板电路连接添加第三方库设置参数程序烧写使用ArduinoIDE进行烧写使用Ardudess进行烧写成果展示摘要通过将一块ArduinoUno设置成ISP模式作为烧录器,从而实现为ArduinoProMini空白的ATMega328PB芯片直接......
  • FastAPI 进阶:使用 BackgroundTasks 处理长时间运行的任务
    在FastAPI中,BackgroundTasks是一个功能,它允许你在发送响应给客户端之后执行后台任务。这些任务对于不需要客户端等待的操作非常有用,比如发送电子邮件通知或处理数据。然而,当服务器重启时,由于BackgroundTasks是与单个应用实例的生命周期相关联的,它们不会自动恢复执行。Backgrou......
  • Python和MATLAB(Java)及Arduino和Raspberry Pi(树莓派)点扩展函数导图
    ......
  • 【花雕学编程】Arduino FOC 之五自由度机械臂的逆运动学求解
    Arduino是一个开放源码的电子原型平台,它可以让你用简单的硬件和软件来创建各种互动的项目。Arduino的核心是一个微控制器板,它可以通过一系列的引脚来连接各种传感器、执行器、显示器等外部设备。Arduino的编程是基于C/C++语言的,你可以使用ArduinoIDE(集成开发环境)来编写、......
  • 【花雕学编程】Arduino动手做(230)---使用ESP32摄像头模块捕获图像并将其保存到SD卡上
    37款传感器与执行器的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的,这里准备逐一动手尝试系列实验,不管成功(程序走通)与否,都会记录下来——小小的......
  • SEO 时间线:SEO 需要多长时间才能见效?
    理解时间线并设定现实的期望在评估您的SEO努力的成功时至关重要。当为Google等搜索引擎优化您的网站时,您可能会问:什么时候我能看到SEO的结果?在本文中,我们将探讨:SEO需要多长时间?为什么SEO需要这么长时间?影响SEO结果的因素实际的SEO时间表如何更快获得SEO结果?SEO需要多长时间......
  • 【花雕学编程】Arduino FOC 之并联五连杆算法
    Arduino是一个开放源码的电子原型平台,它可以让你用简单的硬件和软件来创建各种互动的项目。Arduino的核心是一个微控制器板,它可以通过一系列的引脚来连接各种传感器、执行器、显示器等外部设备。Arduino的编程是基于C/C++语言的,你可以使用ArduinoIDE(集成开发环境)来编写、......
  • 【花雕学编程】Arduino FOC 之步进电机正反转驱动、AS5600编码器信息读取及速度检测
    Arduino是一个开放源码的电子原型平台,它可以让你用简单的硬件和软件来创建各种互动的项目。Arduino的核心是一个微控制器板,它可以通过一系列的引脚来连接各种传感器、执行器、显示器等外部设备。Arduino的编程是基于C/C++语言的,你可以使用ArduinoIDE(集成开发环境)来编写、......
  • Arduino_ESPC3学习笔记
    1、环境搭建(1)官网下载:Arduino-Home(2)配置首选项点灯科技(diandeng.tech)资源下载,点击下载文件——>首选项——>开发管理地址添加开发板地址:https://arduino.me/packages/esp32.json下载社区打包的esp32安装包,直接运行,程序会自动解压到相应位置。重启Arduino配置......
  • Arduino基础入门学习——使用DHT11温湿度传感器获取温湿度
    使用DHT11温湿度传感器获取温湿度一、前言二、DHT11介绍三、准备工作四、程序代码五、运行结果六、结束语一、前言老规矩,再来一句名言激励激励大家,当然,也激励自己(狗头):             读书百遍,其义自见。——晋·陈寿二、DHT11介绍DHT11采用单总线......