esp32-cam wifi传输摄像头画面到另一个esp32设备并显示到tft上

原理

由于传输数据大小限制,无法一次性传输整张,所以采用分段传输的方法。

  • 摄像头端:wifi softAP(wifi服务器)
  • 显示端:   wifi STA    (wifi客户端)
┌───无限循环───
 |
 |   【显示端-> 摄像头端 "getCam"
 |   【摄像头端】拍照、缓存照片
 |   【摄像头端-> 显示端 数据长度:xxxx [ 0x00,  0xXX,  0xXX ]
 |                                                                           └─长度数据─┘
 |  ┌───循环直到全部读取完成───
 |   |
 |   |   【显示端-> 摄像头端 "getCam" + n
 |   |   【摄像头端-> 显示端 [0x01, n, 0xXX, 0xXX, 0xXX, ...... 0xXX]   (2+1024个字节)
 |   |
 |  └─────
 |
 |   【显示端】将获取的数据解码jpeg,然后显示
 |
└─────

代码

需要的库

TFT_eSPI

TFT_eSPI库配置icon-default.png?t=N5K3https://so.csdn.net/so/search?q=TFT_eSPI

JPEGDecotor

摄像头端

  • server.ino
#include "esp_camera.h"
#include <WiFi.h>

WiFiUDP Udp;
#define Port 1024

#define maxData 1024

#define index(a, b) a.substring(0, b.length()) == b
#define afterStr(a, b) a.substring(b.length())
#define stoi(a) atoi(a.c_str())
#define minimum(a, b) (((a) < (b)) ? (a) : (b))

const char *ssid = "esp32-cam";     //wifi名称
const char *password = "helloesp";  //wifi密码

//======== esp32-cam摄像头配置 ========

#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27

#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22

static camera_config_t camera_config = {
  .pin_pwdn = PWDN_GPIO_NUM,
  .pin_reset = RESET_GPIO_NUM,
  .pin_xclk = XCLK_GPIO_NUM,
  .pin_sscb_sda = SIOD_GPIO_NUM,
  .pin_sscb_scl = SIOC_GPIO_NUM,

  .pin_d7 = Y9_GPIO_NUM,
  .pin_d6 = Y8_GPIO_NUM,
  .pin_d5 = Y7_GPIO_NUM,
  .pin_d4 = Y6_GPIO_NUM,
  .pin_d3 = Y5_GPIO_NUM,
  .pin_d2 = Y4_GPIO_NUM,
  .pin_d1 = Y3_GPIO_NUM,
  .pin_d0 = Y2_GPIO_NUM,
  .pin_vsync = VSYNC_GPIO_NUM,
  .pin_href = HREF_GPIO_NUM,
  .pin_pclk = PCLK_GPIO_NUM,

  .xclk_freq_hz = 20000000,
  .ledc_timer = LEDC_TIMER_0,
  .ledc_channel = LEDC_CHANNEL_0,

  .pixel_format = PIXFORMAT_JPEG,
  .frame_size = FRAMESIZE_QVGA,
  .jpeg_quality = 10,
  .fb_count = 1,
};

esp_err_t camera_init() {
  esp_err_t err = esp_camera_init(&camera_config);
  if (err != ESP_OK) {
    Serial.print("摄像头加载失败:error:0x");
    Serial.println(err);
    return err;
  }
  sensor_t *s = esp_camera_sensor_get();
  Serial.println("摄像头加载成功");
  return ESP_OK;
}

bool hasfb = false;
camera_fb_t *fb;

//====================================

void wifi_init() {
  WiFi.mode(WIFI_AP);
  WiFi.softAP(ssid, password);
  Serial.println("AP创建完成");
  Serial.print("设备IP:");
  Serial.println(WiFi.softAPIP());
  if (Udp.begin(Port)) {
    Serial.println("UDP启动成功");
    Serial.print(WiFi.softAPIP());
    Serial.print(":");
    Serial.println(Port);
  }
}

void getCam() {
  if (hasfb) esp_camera_fb_return(fb);
  fb = esp_camera_fb_get();
  hasfb = true;

  Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
  Udp.write(0x00);  //命令:返回数据长度
  if (!fb) {
    Serial.print("Camera capture failed");
    Udp.write(0);  //返回错误数据长度 (1)
    Udp.write(0);  //返回错误数据长度 (2)
  } else {
    Udp.write(fb->len / 256);  //返回数据长度 (1)
    Udp.write(fb->len % 256);  //返回数据长度 (2)
  }
  Udp.endPacket();
}

void getCamData(int number) {
  Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
  Udp.write(0x01);                                                                        //命令:发送数据
  Udp.write(number);                                                                      //确认数据编号
  if (number * maxData <= fb->len) {                                                      //防止访问超出
    Udp.write(fb->buf + number * maxData, minimum(maxData, fb->len - number * maxData));  //发送数据
  }
  Udp.endPacket();
}

void setup() {
  Serial.begin(115200);
  while (camera_init() != ESP_OK) {
    delay(1000);
  }
  wifi_init();
}

void loop() {
  int len = Udp.parsePacket();
  if (len) {
    char incomingPacket[len + 1];
    Udp.read(incomingPacket, len);
    incomingPacket[len] = '\0';
    String cmd = incomingPacket;
    if (cmd == "getCam") getCam();                                                               //命令:缓存摄像头数据 + 返回数据长度
    else if (index(cmd, String("getCam "))) getCamData(stoi(afterStr(cmd, String("getCam "))));  //命令:发送指定数据
  }
}

显示端

  • client.ino
#include <TFT_eSPI.h>
#include <WiFi.h>

TFT_eSPI tft = TFT_eSPI();

#include "jpeg.h"

#define maxData 1024

#define serverIP IPAddress(192, 168, 4, 1)
#define serverPort 1024

WiFiUDP Udp;

const char *ssid = "esp32-cam";
const char *password = "helloesp";

void getImage();
void cmd_len();
void cmd_data();

void wifi_init() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  tft.fillRect(0, 0, 320, 240, TFT_WHITE);
  tft.setTextColor(TFT_BLACK);
  tft.setCursor(0, 0);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    tft.print(".");
  }
  Serial.println();
  Serial.println("Connected");
  Serial.print("IP Address:");
  Serial.println(WiFi.localIP());
  Udp.begin(2048);
  tft.fillRect(0, 0, 320, 240, TFT_BLACK);
  getImage();
}
int data_n = 0;
int data_read_n = 0;
uint8_t *u_data;

uint8_t *imagedata;
int image_len = 0;
int image_get_n = 0;

int image_get_time = 0;

void (*ondonefunc)();
void (*timeoutfunc)();
int cmdtime = 0;
void receive_data(int n, void (*donef)(), void (*timeoutf)()) {
  //接收指定长度数据
  ondonefunc = donef;
  timeoutfunc = timeoutf;
  cmdtime = millis();
  data_n = n;
  data_read_n = 0;
  if (u_data != nullptr) delete[] u_data;
  u_data = new uint8_t[n];
}

void getImage() {
  image_get_time = millis();
  //开始读取图片
  //发送拍照指令 + 获取数据长度
  Udp.beginPacket(serverIP, serverPort);
  Udp.print("getCam");
  Udp.endPacket();
  receive_data(3, cmd_len, getImage);
}
void cmd_len() {
  //收到数据 [0x00, 0xXX, 0xXX]
  //         命令  数据1  数据2
  image_len = (uint16_t)u_data[1] * 256 + u_data[2];
  image_get_n = 0;
  if (imagedata != nullptr) delete[] imagedata;
  imagedata = new uint8_t[image_len];
  Udp.beginPacket(serverIP, serverPort);
  Udp.print("getCam ");
  Udp.print(image_get_n);
  Udp.endPacket();
  receive_data(2 + minimum(image_len - image_get_n * maxData, maxData), cmd_data, getImage);
}
void cmd_data() {
  //收到数据 [0x01, 0xXX, 0xXX, 0xXX, ...... 0xXX]
  //         命令   编号  数据
  if (u_data[1] != image_get_n) {
    Udp.beginPacket(serverIP, serverPort);
    Udp.print("getCam ");
    Udp.print(image_get_n);
    Udp.endPacket();
    receive_data(2 + minimum(image_len - image_get_n * maxData, maxData), cmd_data, getImage);
    return;
  }
  for (int i = 0; i < data_n - 2; i++) {
    imagedata[image_get_n * maxData + i] = u_data[2 + i];  //将收到的数据读取到imagedata
  }
  image_get_n++;
  if (image_get_n * maxData >= image_len) {
    //数据读取完成
    drawJpeg(imagedata, image_len, 0, 0);
    Serial.print("读取完成 耗时");
    Serial.print(millis() - image_get_time);
    Serial.print("ms ( ");
    Serial.print((float)1000 / (float)(millis() - image_get_time));
    Serial.println(" fps)");
    getImage();  //继续读取图片
    return;
  }
  Udp.beginPacket(serverIP, serverPort);
  Udp.print("getCam ");
  Udp.print(image_get_n);
  Udp.endPacket();
  receive_data(2 + minimum(image_len - image_get_n * maxData, maxData), cmd_data, getImage);
}
void setup() {
  Serial.begin(115200);
  tft.init();
  tft.setRotation(1);
  pinMode(15, OUTPUT);
  digitalWrite(15, LOW);
}
void loop() {
  if (WiFi.status() != WL_CONNECTED) {
    wifi_init();
  }
  int len = Udp.parsePacket();
  if (len) {
    int readlen = minimum(data_n - data_read_n, len);
    Udp.read(u_data + data_read_n, readlen);
    data_read_n += readlen;
    if (data_read_n >= data_n && ondonefunc != nullptr) ondonefunc();
  }
  if (millis() > cmdtime + 2000) timeoutfunc();
}

  • jpeg.h
#include <JPEGDecoder.h>

#define minimum(a, b) (((a) < (b)) ? (a) : (b))

void renderJPEG(int xpos, int ypos) {
  uint16_t *pImg;
  uint16_t mcu_w = JpegDec.MCUWidth;
  uint16_t mcu_h = JpegDec.MCUHeight;
  uint32_t max_x = JpegDec.width;
  uint32_t max_y = JpegDec.height;
  uint32_t min_w = minimum(mcu_w, max_x % mcu_w);
  uint32_t min_h = minimum(mcu_h, max_y % mcu_h);
  uint32_t win_w = mcu_w;
  uint32_t win_h = mcu_h;
  max_x += xpos;
  max_y += ypos;
  while (JpegDec.readSwappedBytes()) {
    pImg = JpegDec.pImage;
    int mcu_x = JpegDec.MCUx * mcu_w + xpos;
    int mcu_y = JpegDec.MCUy * mcu_h + ypos;
    if (mcu_x + mcu_w <= max_x) win_w = mcu_w;
    else win_w = min_w;
    if (mcu_y + mcu_h <= max_y) win_h = mcu_h;
    else win_h = min_h;
    if (win_w != mcu_w) {
      uint16_t *cImg;
      int p = 0;
      cImg = pImg + win_w;
      for (int h = 1; h < win_h; h++) {
        p += mcu_w;
        for (int w = 0; w < win_w; w++) {
          *cImg = *(pImg + w + p);
          cImg++;
        }
      }
    }
    if ((mcu_x + win_w) <= tft.width() && (mcu_y + win_h) <= tft.height()) {
      tft.pushRect(mcu_x, mcu_y, win_w, win_h, pImg);
    } else if ((mcu_y + win_h) >= tft.height()) JpegDec.abort();
  }
}

void drawJpeg(const uint8_t arrayname[], uint32_t array_size, int xpos, int ypos) {
  int x = xpos;
  int y = ypos;
  JpegDec.decodeArray(arrayname, array_size);
  renderJPEG(x, y);
}

演示

QVGA(320x240)测试最多有5fps

  • 7
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
要通过ESP32-CAM控制ESP8266,你需要先建立它们之间的WiFi连接,然后在ESP32-CAM上运行服务器程序,接收来自ESP8266的命令,并执行相应的操作。 以下是一个示例代码,展示如何通过ESP32-CAM控制ESP8266: ESP8266代码: ```c++ #include <ESP8266WiFi.h> #include <WiFiClient.h> const char* ssid = "your_SSID"; const char* password = "your_PASSWORD"; IPAddress serverIP(192, 168, 1, 100); // ESP32-CAM的IP地址 WiFiClient client; void setup() { Serial.begin(115200); delay(1000); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi..."); } Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); } void loop() { if (!client.connect(serverIP, 80)) { Serial.println("Connection failed"); delay(1000); return; } client.println("GET /?command=on HTTP/1.1"); client.println("Host: 192.168.1.100"); // ESP32-CAM的IP地址 client.println("Connection: close"); client.println(); while (client.connected()) { if (client.available()) { String line = client.readStringUntil('\n'); Serial.println(line); } } client.stop(); delay(1000); } ``` ESP32-CAM代码: ```c++ #include <WiFi.h> #include <WebServer.h> const char* ssid = "your_SSID"; const char* password = "your_PASSWORD"; IPAddress localIP(192, 168, 1, 100); // ESP32-CAM的IP地址 WebServer server(80); void handleCommand() { String command = server.arg("command"); if (command == "on") { // 执行打开操作 Serial.println("Turning on..."); } else if (command == "off") { // 执行关闭操作 Serial.println("Turning off..."); } else { // 无效的命令 Serial.println("Invalid command"); } server.send(200, "text/plain", "OK"); } void setup() { Serial.begin(115200); delay(1000); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi..."); } Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); server.on("/command", handleCommand); server.begin(); } void loop() { server.handleClient(); } ``` 在ESP8266的代码中,我们首先定义了ESP32-CAM的IP地址,并在loop()函数中,使用WiFiClient库连接到ESP32-CAM的IP地址,并向其发送一个HTTP GET请求,请求的URL中包含了一个命令参数command,值为on,表示打开操作。在ESP32-CAM的代码中,我们首先定义了一个WebServer对象,并在setup()函数中,注册了一个handleCommand()函数来处理来自ESP8266的命令。在handleCommand()函数中,我们根据命令参数的值来执行相应的操作,并返回一个OK响应。 你可以根据需要修改命令的参数和相应的操作,以实现ESP32-CAM对ESP8266的控制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值