正点原子 esp32s3通过网页查看摄像头实时监控

本人是新手,刚开始接触esp32板,希望能与大家交流。

开发板使用:正点原子ATK_DNESP32S3 V1.3

IDE: VSCODE + PLATFORMIO

摄像头模块:ov2640(非正点原子,正点原子的太贵了,买不起...)

在VS Code新建工程,工程命名为:camera-test1

主板选择对应主板,这里由于没有找到正点原子对应的主板,我自已导入了一个。

实验步骤:

1. 将买的其它厂家的ov2640接入到正点原子esp32s3板上,买的这个ov2640引脚布局正好与正点原子相同。

2. 当烧录好后,点击浏览器访问开发板的webserver。

实验结果:

通过不断捕获摄像头帧并将其作为JPEG图像发送到客户端来实现,当点击浏览器访问,可以在页面上实时看到监控画面。

这里吐槽一下,正点原子的摄像头模块是真的贵啊,这价格都可以买个高清摄像头了,咱只配用便宜的 !^_^

新建工程:

主板选择对应主板,这里由于没有找到正点原子对应的主板,我自已导入了一个。

代码:

#include "esp_camera.h"
#include <WiFi.h>
#include <WebServer.h>

#define OV_SCL_PIN 38
#define OV_SDA_PIN 39
#define OV_D0_PIN 4
#define OV_D1_PIN 5
#define OV_D2_PIN 6
#define OV_D3_PIN 7
#define OV_D4_PIN 15
#define OV_D5_PIN 16
#define OV_D6_PIN 17
#define OV_D7_PIN 18
#define OV_VSYNC_PIN 47
#define OV_HREF_PIN 48
#define OV_PCLK_PIN 45
#define OV_XCLK_PIN -1
#define OV_RESET_PIN -1
#define OV_PWDN_PIN -1

// WiFi配置
const char* ssid = "你家的wifi ssid";      // 替换为你的WiFi名称
const char* password = "你家wifi的密码";  // 替换为你的WiFi密码

WebServer server(80);

void startCameraServer();

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Serial.println("\n启动摄像头...");
  
  // 配置摄像头参数
  camera_config_t camera_config;
  camera_config.ledc_channel = LEDC_CHANNEL_0;
  camera_config.ledc_timer = LEDC_TIMER_0;
  camera_config.xclk_freq_hz = 20000000;  // 20MHz时钟
  
  // 数据引脚
  camera_config.pin_d7 = OV_D7_PIN;
  camera_config.pin_d6 = OV_D6_PIN;
  camera_config.pin_d5 = OV_D5_PIN;
  camera_config.pin_d4 = OV_D4_PIN;
  camera_config.pin_d3 = OV_D3_PIN;
  camera_config.pin_d2 = OV_D2_PIN;
  camera_config.pin_d1 = OV_D1_PIN;
  camera_config.pin_d0 = OV_D0_PIN;
  
  // 控制引脚
  camera_config.pin_xclk = OV_XCLK_PIN;
  camera_config.pin_pclk = OV_PCLK_PIN;
  camera_config.pin_vsync = OV_VSYNC_PIN;
  camera_config.pin_href = OV_HREF_PIN;
  
  // SCCB/I2C引脚
  camera_config.pin_sccb_sda = OV_SDA_PIN;
  camera_config.pin_sccb_scl = OV_SCL_PIN;
  
  // 电源控制引脚
  camera_config.pin_pwdn = OV_PWDN_PIN;
  camera_config.pin_reset = OV_RESET_PIN;
  
  // 图像参数
  camera_config.frame_size = FRAMESIZE_QVGA;     // 320x240分辨率
  camera_config.pixel_format = PIXFORMAT_JPEG;   // JPEG格式更适合网络传输
  camera_config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
  camera_config.fb_location = CAMERA_FB_IN_PSRAM; // 使用PSRAM
  
  // 图像质量
  camera_config.jpeg_quality = 12;  // 0-63,数值越小质量越高
  camera_config.fb_count = 1;       // 帧缓冲区数量

  // 初始化摄像头
  esp_err_t err = esp_camera_init(&camera_config);
  if (err != ESP_OK) {
    Serial.printf("摄像头初始化失败: 0x%x", err);
    
    // 尝试使用DRAM替代PSRAM
    camera_config.fb_location = CAMERA_FB_IN_DRAM;
    err = esp_camera_init(&camera_config);
    if (err != ESP_OK) {
      Serial.printf("第二次初始化尝试失败: 0x%x", err);
      while(1) delay(1000);
    }
  }

  // 连接WiFi
  WiFi.begin(ssid, password);
  Serial.print("连接WiFi...");
  
  int attempts = 0;
  while (WiFi.status() != WL_CONNECTED && attempts < 20) {
    delay(500);
    Serial.print(".");
    attempts++;
  }
  
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("\nWiFi连接失败! 请检查凭证");
    while(1) delay(1000);
  }
  
  Serial.println("\nWiFi已连接");
  Serial.print("IP地址: ");
  Serial.println(WiFi.localIP());

  // 设置网络服务器
  server.on("/", HTTP_GET, []() {
    server.send(200, "text/html; charset=utf-8", 
      "<html>"
        "<head>"
        "<meta charset='UTF-8'>"
          "<meta name='viewport' content='width=device-width, initial-scale=1'>"
          "<title>ESP32-S3摄像头</title>"
          "<style>"
            "body { margin: 0; background: #333; font-family: Arial, sans-serif; }"
            ".container { max-width: 800px; margin: 0 auto; padding: 20px; }"
            "h1 { color: #fff; text-align: center; margin-bottom: 20px; }"
            "img { display: block; max-width: 100%; height: auto; margin: 0 auto; }"
          "</style>"
        "</head>"
        "<body>"
          "<div class='container'>"
            "<h1>ESP32-S3摄像头监控</h1>"
            "<img src='/stream' alt='视频流'>"
          "</div>"
        "</body>"
      "</html>");
  });

  server.on("/stream", HTTP_GET, []() {
    server.sendHeader("Access-Control-Allow-Origin", "*");
    server.setContentLength(CONTENT_LENGTH_UNKNOWN);
    server.send(200, "multipart/x-mixed-replace; boundary=frame");
    
    while (true) {
      camera_fb_t *fb = esp_camera_fb_get();
      if (!fb) {
        Serial.println("获取帧失败");
        delay(100);
        continue;
      }
      
      server.sendContent("--frame\r\n");
      server.sendContent("Content-Type: image/jpeg\r\n\r\n");
      server.sendContent_P((char *)fb->buf, fb->len);
      server.sendContent("\r\n");
      
      esp_camera_fb_return(fb);
      delay(10); // 控制帧率
    }
  });

  server.begin();
  Serial.println("HTTP服务器已启动");
}

void loop() {
  server.handleClient();
  delay(1);
}

摄像头引脚定义:

#define OV_SCL_PIN 38     // I2C时钟线(SCL)引脚
#define OV_SDA_PIN 39     // I2C数据线(SDA)引脚
#define OV_D0_PIN 4       // 摄像头数据引脚D0
#define OV_D1_PIN 5       // 摄像头数据引脚D1
#define OV_D2_PIN 6       // 摄像头数据引脚D2
#define OV_D3_PIN 7       // 摄像头数据引脚D3
#define OV_D4_PIN 15      // 摄像头数据引脚D4
#define OV_D5_PIN 16      // 摄像头数据引脚D5
#define OV_D6_PIN 17      // 摄像头数据引脚D6
#define OV_D7_PIN 18      // 摄像头数据引脚D7
#define OV_VSYNC_PIN 47   // 垂直同步信号引脚
#define OV_HREF_PIN 48    // 水平参考信号引脚
#define OV_PCLK_PIN 45    // 像素时钟引脚
#define OV_XCLK_PIN -1    // 外部时钟引脚(未使用,设为-1)
#define OV_RESET_PIN -1   // 复位引脚(未使用)
#define OV_PWDN_PIN -1    // 断电引脚(未使用)

创建一个Web服务器对象,监听80端口:

WebServer server(80); 

启用调试输出:

Serial.setDebugOutput(true); 

摄像头配置:

camera_config_t camera_config;  // 定义摄像头配置结构体
// 配置LEDC(用于产生XCLK时钟)
camera_config.ledc_channel = LEDC_CHANNEL_0;  // 使用LEDC通道0
camera_config.ledc_timer = LEDC_TIMER_0;      // 使用LEDC定时器0
camera_config.xclk_freq_hz = 20000000;        // 设置XCLK时钟频率为20MHz

摄像头数据引脚配置:

camera_config.pin_d7 = OV_D7_PIN;
camera_config.pin_d6 = OV_D6_PIN;
camera_config.pin_d5 = OV_D5_PIN;
camera_config.pin_d4 = OV_D4_PIN;
camera_config.pin_d3 = OV_D3_PIN;
camera_config.pin_d2 = OV_D2_PIN;
camera_config.pin_d1 = OV_D1_PIN;
camera_config.pin_d0 = OV_D0_PIN;

摄像头控制引脚配置:

  camera_config.pin_xclk = OV_XCLK_PIN;   // 外部时钟引脚(未使用)
  camera_config.pin_pclk = OV_PCLK_PIN;   // 像素时钟引脚
  camera_config.pin_vsync = OV_VSYNC_PIN; // 垂直同步引脚
  camera_config.pin_href = OV_HREF_PIN;   // 水平参考引脚

I2C引脚配置:

  camera_config.pin_sccb_sda = OV_SDA_PIN;  // I2C数据引脚
  camera_config.pin_sccb_scl = OV_SCL_PIN;  // I2C时钟引脚

图像参数配置:

  camera_config.frame_size = FRAMESIZE_QVGA;     // 设置图像分辨率为QVGA(320x240)
  camera_config.pixel_format = PIXFORMAT_JPEG;   // 设置像素格式为JPEG(适合网络传输)
  camera_config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; // 设置抓取模式(当缓冲区为空时抓取)
  camera_config.fb_location = CAMERA_FB_IN_PSRAM;   // 设置帧缓冲区在PSRAM中(如果可用)

图像质量配置:

  camera_config.frame_size = FRAMESIZE_QVGA;     // 设置图像分辨率为QVGA(320x240)
  camera_config.pixel_format = PIXFORMAT_JPEG;   // 设置像素格式为JPEG(适合网络传输)
  camera_config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; // 设置抓取模式(当缓冲区为空时抓取)
  camera_config.fb_location = CAMERA_FB_IN_PSRAM;   // 设置帧缓冲区在PSRAM中(如果可用)

初始化摄像头,如果摄像头初始化失败,会尝试使用DRAM替代PSRAM,如果仍然失败则停止程序:

  esp_err_t err = esp_camera_init(&camera_config);  // 初始化摄像头
  if (err != ESP_OK) {  // 如果初始化失败
    Serial.printf("摄像头初始化失败: 0x%x", err);  // 打印错误信息
    
    // 尝试使用DRAM替代PSRAM
    camera_config.fb_location = CAMERA_FB_IN_DRAM;  // 将帧缓冲区设置在DRAM中
    err = esp_camera_init(&camera_config);  // 再次尝试初始化
    if (err != ESP_OK) {  // 如果再次失败
      Serial.printf("第二次初始化尝试失败: 0x%x", err);  // 打印错误信息
      while(1) delay(1000);  // 进入死循环,阻止程序继续执行
    }
  }

连接wifi:

  WiFi.begin(ssid, password);  // 连接WiFi
  Serial.print("连接WiFi...");  // 打印连接信息
  int attempts = 0;  // 尝试连接计数器
  // 循环等待WiFi连接,最多尝试20次
  while (WiFi.status() != WL_CONNECTED && attempts < 20) {
    delay(500);      // 等待500ms
    Serial.print("."); // 打印进度点
    attempts++;       // 增加尝试次数
  }
  
  // 检查是否连接成功
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("\nWiFi连接失败! 请检查凭证"); // 打印失败信息
    while(1) delay(1000);  // 进入死循环
  }
  
  // 连接成功
  Serial.println("\nWiFi已连接");
  Serial.print("IP地址: ");
  Serial.println(WiFi.localIP());  // 打印获取到的IP地址

设置web服务器路由:

  // 设置根路径("/")的处理函数
  server.on("/", HTTP_GET, []() {
    // 发送一个简单的HTML页面,包含一个图像元素,其源指向视频流
    server.send(200, "text/html; charset=utf-8", 
      "<html>"
        "<head>"
        "<meta charset='UTF-8'>"  // 设置字符集为UTF-8,避免中文乱码
          "<meta name='viewport' content='width=device-width, initial-scale=1'>"
          "<title>ESP32-S3摄像头</title>"
          "<style>"
            "body { margin: 0; background: #333; font-family: Arial, sans-serif; }"
            ".container { max-width: 800px; margin: 0 auto; padding: 20px; }"
            "h1 { color: #fff; text-align: center; margin-bottom: 20px; }"
            "img { display: block; max-width: 100%; height: auto; margin: 0 auto; }"
          "</style>"
        "</head>"
        "<body>"
          "<div class='container'>"
            "<h1>ESP32-S3摄像头监控</h1>"
            "<img src='/stream' alt='视频流'>"
          "</div>"
        "</body>"
      "</html>");
  });

设置视频路由,以MJPEG(Motion JPEG)格式发送实时视频流:

  server.on("/stream", HTTP_GET, []() {
    // 设置响应头,允许跨域请求(*表示任何来源)
    server.sendHeader("Access-Control-Allow-Origin", "*");
    // 设置内容长度为未知(因为视频流是连续的)
    server.setContentLength(CONTENT_LENGTH_UNKNOWN);
    // 发送响应状态码200和内容类型(多部分混合替换,边界为frame)
    server.send(200, "multipart/x-mixed-replace; boundary=frame");
    
    // 循环发送视频帧
    while (true) {
      camera_fb_t *fb = esp_camera_fb_get();  // 获取一帧图像
      if (!fb) {  // 如果获取失败
        Serial.println("获取帧失败");
        delay(100);
        continue;  // 继续尝试
      }
      
      // 发送帧边界和内容类型
      server.sendContent("--frame\r\n");
      server.sendContent("Content-Type: image/jpeg\r\n\r\n");
      // 发送图像数据(使用sendContent_P将数据从程序存储器发送)
      server.sendContent_P((char *)fb->buf, fb->len);
      server.sendContent("\r\n");  // 发送换行
      
      esp_camera_fb_return(fb);  // 释放帧缓冲区
      delay(10); // 控制帧率(通过延迟控制每帧之间的时间)
    }
  });

启动web服务器:

  server.begin();  // 启动Web服务器
  Serial.println("HTTP服务器已启动");

编译:

烧录:

通过浏览器访问:

可能的改进点

  1. 增加帧率控制:当前使用固定的10ms延迟,可以改为根据实际帧率动态调整

  2. 添加更多控制功能:如分辨率切换、亮度调整等

  3. 改进错误处理:添加更多错误信息,帮助诊断问题

  4. 添加身份验证:保护视频流访问

  5. 优化HTML界面:添加更多信息和控件,如帧率显示、快照功能等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FightingFreedom

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值