原理
由于传输数据大小限制,无法一次性传输整张,所以采用分段传输的方法。
- 摄像头端: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库配置https://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