TL;DR: All I want is to send the camera's frame buffer to another ESP32CAM. That's it. No streaming, just one photo every few minutes or so.
If you think my approach could be different please tell me, I will gladly consider other options!
I am having a hard time figuring out how to do this, documentation is scarce presumably because most people don't use SPI. (I2C is way too convenient)
However, I found a forum thread which described a similar issue: link
I wish to create a script that would take a picture on both ESP32CAM modules, send the picture from the first one to the second one and then have that one send both pictures to an FTP server. However, I want to use ethernet, since Wi-Fi seems to be really unreliable for some reason (and would like to use PoE) and the W5500 Ethernet modules communicate through SPI.
So unless I can switch from I2C for the ESP to ESP communication to SPI for the Ethernet module... I must use SPI.
I am not very knowledgeable in serial communication so I was hoping someone could help me by giving me some guidance on how to do this. The thread I linked does help, but was written for I2C (correct me if I am wrong) and the code gets a little complicated and combined with the fact I can't find much documentation on this makes me a little uneasy when coding.
Thank you for any and all help!
Receiver code (very unfinished and not working, mostly a cut down example from mentioned thread)
/*Receives picture via serial and saves to ftp*/
#define DEBUG_ESP //comment out to deactivate debug console
#ifdef DEBUG_ESP
#define pDBGln(x) Serial.println(x)
#define pDBG(x) Serial.print(x)
#else
#define pDBG(...)
#define pDBGln(...)
#endif
#include "SPITransfer.h"
SPITransfer myTransfer;
struct img_meta_data{
uint16_t counter;
uint16_t imSize;
uint16_t numLoops;
uint16_t sizeLastLoop;
} ImgMetaData;
uint16_t packetCounter=1;
uint16_t bufferPointer=0;
char tempImageBuffer[32000];
// FTP Client Lib
#include "ESP32_FTPClient.h"
// FTP Server credentials
char ftp_server[] = "";
char ftp_user[] = "";
char ftp_pass[] = "";
// picture name
String picPrefix ="";
String pic_name;
// Connection timeout;
#define CON_TIMEOUT 10*1000 // milliseconds
ESP32_FTPClient ftp(ftp_server, ftp_user, ftp_pass);
void setup(){
//Serial.begin(Baud Rate, Data Protocol, Txd pin, Rxd pin);
Serial.begin(115200); // Define and start serial monitor
// 115200,256000,512000,962100
SPCR |= bit (SPE);
pinMode(MISO, OUTPUT);
SPI.attachInterrupt();
myTransfer.begin(SPI);
}
void loop(){
if(myTransfer.available()) {
myTransfer.rxObj(ImgMetaData, sizeof(ImgMetaData));
pDBG("Struct Data: ");
pDBG(ImgMetaData.counter);
pDBG(", ");
pDBG(ImgMetaData.imSize);
pDBG(", ");
pDBG(ImgMetaData.numLoops);
pDBG(", ");
pDBG(ImgMetaData.sizeLastLoop);
pDBG(", PacketCounter: ");
pDBGln(packetCounter);
if(ImgMetaData.counter==1){
copyToImageBuff(MAX_PACKET_SIZE-sizeof(ImgMetaData));
}else{
if(ImgMetaData.counter==packetCounter){
if(packetCounter<ImgMetaData.numLoops){
copyToImageBuff(MAX_PACKET_SIZE-sizeof(ImgMetaData));
}else if(ImgMetaData.counter==packetCounter){
copyToImageBuff(ImgMetaData.sizeLastLoop);
}
}
}
if(packetCounter>ImgMetaData.numLoops){
pic_name = picPrefix;
pic_name += ".jpg";
FTP_upload();
packetCounter=1;
bufferPointer=0;
delay(2000);
//while(1){}
}
}else if(myTransfer.status < 0) {
pDBG("ERROR: ");
if(myTransfer.status == -1)
pDBGln(F("CRC_ERROR"));
else if(myTransfer.status == -2)
pDBGln(F("PAYLOAD_ERROR"));
else if(myTransfer.status == -3)
pDBGln(F("STOP_BYTE_ERROR"));
}
}
void copyToImageBuff(uint16_t dataLenght){
for(int y=0;y<dataLenght;y++){
tempImageBuffer[bufferPointer+y] = myTransfer.rxBuff[y+sizeof(ImgMetaData)];
}
bufferPointer+=dataLenght;
packetCounter++;
pDBG("dataLenght: ");
pDBG(dataLenght);
pDBG(", bufferPointer: ");
pDBGln(bufferPointer);
}
void printBuf(char localBuff){
pDBG(F("Pixel Values: { "));
for (uint16_t k=0; k<sizeof(myTransfer.rxBuff); k++){
pDBG(myTransfer.rxBuff[k]);
if (k < (sizeof(myTransfer.rxBuff) - 1))
pDBG(F(", "));
else
pDBGln(F(" }"));
}
}
void FTP_upload(){
pDBG("Uploading via FTP: ");
ftp.OpenConnection();
//ftp.ChangeWorkDir("/public_html/zyro/");
//Create a file and write the image data to it;
ftp.InitFile("Type I");
const char *f_name = pic_name.c_str();
ftp.NewFile( f_name );
pDBGln( f_name );
ftp.WriteData((unsigned char*)tempImageBuffer, ImgMetaData.imSize);
ftp.CloseFile();
// Breath, withouth delay URL failed to update.
pDBGln("Done... Waiting next transfer...");
delay(100);
}
Sender (same issue as above):
#define DEBUG_ESP //comment out to deactivate debug console
#ifdef DEBUG_ESP
#define pDBGln(x) Serial.println(x)
#define pDBG(x) Serial.print(x)
#else
#define pDBG(...)
#define pDBGln(...)
#endif
#include "esp_camera.h"
#include "Arduino.h"
#include "soc/soc.h" // Disable brownout problems
#include "soc/rtc_cntl_reg.h" // Disable brownout problems
#include "driver/rtc_io.h"
#include "SPITransfer.h"
#include <SPI.h>
// Pin definition for CAMERA_MODEL_AI_THINKER
#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
SPITransfer myTransfer;
//HardwareSerial Comm(1);
struct img_meta_data{
uint16_t counter;
uint16_t imSize;
uint16_t numLoops;
uint16_t sizeLastLoop;
} ImgMetaData;
const uint16_t PIXELS_PER_PACKET = MAX_PACKET_SIZE - sizeof(ImgMetaData);
void setup(){
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
Serial.begin(115200); // Define and start serial monitor
// 115200,256000,512000,962100
//Comm.begin(962100, SERIAL_8N1,15,14); //, Comm_Txd_pin, Comm_Rxd_pin); // Define and start Comm serial port
//myTransfer.begin(Comm);
digitalWrite(15, HIGH);
SPI.begin(14,12,13,15);
SPI.setClockDivider(SPI_CLOCK_DIV8);
myTransfer.begin(SPI);
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;
config.pixel_format = PIXFORMAT_JPEG;
// if(psramFound()){
// config.frame_size = FRAMESIZE_UXGA; // FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA
// config.jpeg_quality = 10;
// config.fb_count = 2;
// } else {
// config.frame_size = FRAMESIZE_SVGA;
// config.jpeg_quality = 12;
// config.fb_count = 1;
// }
config.frame_size = FRAMESIZE_VGA;
config.jpeg_quality = 10;
config.fb_count = 1;
// Init Camera
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK){
Serial.printf("Camera init failed with error 0x%x", err);
return;
}
// Change extra settings if required
sensor_t * s = esp_camera_sensor_get();
//s->set_vflip(s, 0); //flip it back
//s->set_brightness(s, 1); //up the blightness just a bit
//s->set_saturation(s, -2); //lower the saturation
s->set_ae_level(s, -1); //exposure time -2 to 2
}
void loop(){
uint16_t startIndex = 0;
camera_fb_t * fb = NULL;
//Take Picture with Camera
fb = esp_camera_fb_get();
ImgMetaData.imSize = fb->len; //sizeof(myFb);
ImgMetaData.numLoops = (fb->len / PIXELS_PER_PACKET) + 1; //(sizeof(myFb)/PIXELS_PER_PACKET) + 1;
ImgMetaData.sizeLastLoop = fb->len % PIXELS_PER_PACKET; //(sizeof(myFb)%PIXELS_PER_PACKET);
for(ImgMetaData.counter=1; ImgMetaData.counter<=ImgMetaData.numLoops; ImgMetaData.counter++){
myTransfer.txObj(ImgMetaData, sizeof(ImgMetaData));
//printStructBuf();
stuffPixels(fb->buf, startIndex, sizeof(ImgMetaData), PIXELS_PER_PACKET);
//stuffPixels(myFb, startIndex, sizeof(ImgMetaData), PIXELS_PER_PACKET);
//printStructBuf();
myTransfer.sendData(MAX_PACKET_SIZE);
pDBGln(F("Sent:"));
pDBG(F("img.counter: ")); pDBGln((uint16_t)((myTransfer.packet.txBuff[1] << 8) | myTransfer.packet.txBuff[0]));
pDBG(F("img.imSize: ")); pDBGln((uint16_t)((myTransfer.packet.txBuff[3] << 8) | myTransfer.packet.txBuff[2]));
pDBG(F("img.numLoops: ")); pDBGln((uint16_t)((myTransfer.packet.txBuff[5] << 8) | myTransfer.packet.txBuff[4]));
pDBG(F("img.sizeLastLoop: ")); pDBGln((uint16_t)((myTransfer.packet.txBuff[7] << 8) | myTransfer.packet.txBuff[6]));
startIndex += PIXELS_PER_PACKET;
delay(100);
//printBuf();
}
esp_camera_fb_return(fb); //clear camera memory
delay(10000);
}
void printStructBuf(){
pDBG(F("Internal Struct: { "));
for (uint16_t k=0; k<sizeof(ImgMetaData); k++){
pDBG(myTransfer.packet.txBuff[k]);
if (k<(sizeof(ImgMetaData)-1))
pDBG(F(", "));
else
pDBGln(F(" }"));
}
}
void printBuf(){
pDBG(F("Pixel Values: { "));
for (uint16_t k=8; k<MAX_PACKET_SIZE; k++){
pDBG(myTransfer.packet.txBuff[k]);
if (k < (MAX_PACKET_SIZE - 1))
pDBG(F(", "));
else
pDBGln(F(" }"));
}
pDBGln();
}
void stuffPixels(const uint8_t * pixelBuff, const uint16_t &bufStartIndex, const uint16_t &txStartIndex, const uint16_t &len){
uint16_t txi = txStartIndex;
for (uint16_t i=bufStartIndex; i<(bufStartIndex + len); i++) {
myTransfer.packet.txBuff[txi] = pixelBuff[i];
txi++;
}
}