SPI Uno R3 and ESP32-Cam AI Thinker

I have been searching everywhere to help me see why my project is not working. Where can I find out more information on using UNO R3 as a master and the ESP32-Cam and a 2-wire fan as slaves?

Is that you cannot do communication with ESP32-CAM using 2-wire (TWI/I2C) Bus?

What's the purpose of wanting I2C communications between a ESP32 and a UNO? What code have you tried so far? Where does your code fail?

If you have an ESP32, why would you want to use an Arduino UNO? :roll_eyes:

I could see where an Uno might be useful with a ESP32CAM but using another 3.3V device with the cam would settle some 5V/3.3V interface issues.

Vaguely possible, but very unlikely. :face_with_raised_eyebrow:

Thank you everyone. I am new to this and find it all fascinating. I am just getting stuck with my big ideas and my months of research has not yielded the results I am looking for.

Here is the sketch I am working on. It is a combination of sketches that work independent of each other. My problem is that the ESP32-Cam does not have enough pins to control the fan and the sensor. Someone said I could get additional sensors by using SPI mode. So, I have been researching how to do that as a newbie. I have not put the SPI code in yet.

I want to turn on the fan for 3 seconds then shut it off for 30 minutes. I want the sensor to pick up movement then turn the camera on so that I can see what the movement is from. I want to send an email picture of the movement or live stream the movement via Blynk.

// Fill-in information from your Blynk Template here
#define BLYNK_TEMPLATE_ID           ""
#define BLYNK_DEVICE_NAME           ""

#define BLYNK_FIRMWARE_VERSION        "0.1.0"

#define BLYNK_PRINT Serial
//#define BLYNK_DEBUG

#define APP_DEBUG

// Uncomment your board, or configure a custom board in Settings.h
//#define USE_WROVER_BOARD
//#define USE_TTGO_T7
//#define USE_CAMERA_MODEL_AI_THINKER

#include "BlynkEdgent.h"
// INCLUDES

#include <SPI.h>
#include "Arduino.h" // General functionality
#include "esp_camera.h" // Camera
#include <SD.h> // SD Card 
#include "FS.h" // File System
#include "soc/soc.h" // System settings (e.g. brownout)
#include "soc/rtc_cntl_reg.h"
#include "driver/rtc_io.h"
#include <EEPROM.h> // EEPROM flash memory
#include <WiFi.h> // WiFi
#include "time.h" // Time functions
// "ESP Mail Client" by Mobizt, tested with v1.6.4
#include "ESP_Mail_Client.h" // e-Mail

// DEFINES
//#define USE_INCREMENTAL_FILE_NUMBERING //Uses EEPROM to store latest file stored
#define USE_TIMESTAMP // Uses Wi-Fi to retrieve current time value
#define SEND_EMAIL // Uses Wi-Fi to email photo attachment
#define TRIGGER_MODE // Photo capture triggered by GPIO pin rising/falling
//#define TIMED_MODE // Photo capture automated according to regular delay

// Wi-Fi settings

#define SMTP_HOST "smtp.office365.com"
#define SMTP_PORT 25
#define AUTHOR_EMAIL ""
#define AUTHOR_PASSWORD ""

// CONSTANTS
// GPIO Pin 33 is small red LED near to RESET button on the back of the board
const byte ledPin = GPIO_NUM_33;
// GPIO Pin 4 is bright white front-facing LED 
const byte flashPin = GPIO_NUM_4;
// When using TRIGGER_MODE, this pin will be used to initiate photo capture
const byte triggerPin = GPIO_NUM_13;
// Flash strength (0=Off, 255=Max Brightness)
// Setting a low flash value can provide a useful visual indicator of when a photo is being taken
const byte flashPower = 1;
//#ifdef TIMED_MODE
//  const int timeLapseInterval = 30; // seconds between successive shots in TIMELAPSE mode
//#endif
const int startupDelayMillis = 3000; // time to wait after initialising  camera before taking photo

// constants won't change. They're used here to set pin numbers:
const int pb = 13;     // the number of the pushbutton pin this pin needs to be virtual
const int en = 3;      // the number of the LED pin
const int in1 = 2;
int speedPin=5;
int dir1=4;
int dir2=8;
int mSpeed=255;

// variables will change:
int buttonState = 0;         // variable for reading the pushbutton status



// GLOBALS
// Keep track of number of pictures taken for incremental file naming
int pictureNumber = 0;
// Full path of filename of the last photo saved
String path;
#ifdef SEND_EMAIL
  // SMTP session used for eMail sending
  SMTPSession smtp;
  // Function fired on email success/failure
  void smtpCallback(SMTP_Status status);

  // Callback function after eMail sending
void smtpCallback(SMTP_Status status) {
  // Print the current status
  Serial.println(status.info());
  // Show details of successful delivery
  if (status.success())   {
    Serial.println("----------------");
    Serial.printf("Message sent success: %d\n", status.completedCount());
    Serial.printf("Message sent failed: %d\n", status.failedCount());
    Serial.println("----------------\n");
    struct tm dt;
    for (size_t i=0; i<smtp.sendingResult.size(); i++) {
      SMTP_Result result = smtp.sendingResult.getItem(i);
      time_t ts = (time_t)result.timestamp;
      localtime_r(&ts, &dt);
      Serial.printf("Message No: %d\n", i + 1);
      Serial.printf("Status: %s\n", result.completed ? "success" : "failed");
      Serial.printf("Date/Time: %d/%d/%d %d:%d:%d\n", dt.tm_year + 1900, dt.tm_mon + 1, dt.tm_mday, dt.tm_hour, dt.tm_min, dt.tm_sec);
      Serial.printf("Recipient: %s\n", result.recipients);
      Serial.printf("Subject: %s\n", result.subject);
    }
    Serial.println("----------------");
  }
  
}
#endif

void sleep() {
  // IMPORTANT - we define pin mode for the trigger pin at the end of setup, because most pins on the ESP32-CAM
  // have dual functions, and may have previously been used by the camera or SD card access. So we overwrite them here
  pinMode(triggerPin, INPUT_PULLDOWN);
  // Ensure the flash stays off while we sleep
  rtc_gpio_hold_en(GPIO_NUM_4);
  // Turn off the LED
  digitalWrite(ledPin, HIGH);
  delay(1000);
  #ifdef TRIGGER_MODE
    // Use this to wakeup when trigger pin goes HIGH
    esp_sleep_enable_ext0_wakeup(GPIO_NUM_13, 1);
    // Use this to wakeup when trigger pin goes LOW
    //esp_sleep_enable_ext0_wakeup(GPIO_NUM_13, 0);
  //#elif defined(TIMED_MODE)
    // Or, use this to wakeup after a certain amount of time has elapsed (parameter specified in uS, so multiply secs by 1000000)
    //esp_sleep_enable_timer_wakeup(timeLapseInterval * 1000000);
  #endif
  Serial.println("Going to sleep now");
  esp_deep_sleep_start();
  Serial.println("This will never be printed");
}
void setup()
{
  // Light up the discrete red LED on the back of the board to show the device is active
  pinMode(ledPin, OUTPUT);
  // It's an active low pin, so we write a LOW value to turn it on
  digitalWrite(ledPin, LOW);

  // CAUTION - We'll disable the brownout detection
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);

  // Start serial connection for debugging purposes
  Serial.begin(115200);
   while (!Serial); delay(200);
 

  
  delay(100);
 Serial.println("Starting up...");
  // initialize the LED pin as an output:

{
  // any code you place here will execute when the virtual pin value changes
}
pinMode (speedPin, OUTPUT);
pinMode (dir1, OUTPUT);
pinMode (dir2, OUTPUT);
  pinMode(en, OUTPUT);
  pinMode(in1, OUTPUT);
  // initialize the pushbutton pin as an input:
  // Note: Pullup makes HIGH/LOW state backward from what we typically think.
  // So, HIGH = not pressed, LOW = pressed
  pinMode(pb, INPUT_PULLUP);  

  // Keep en pin on all the time:
  // If fanciness is needed, you can modulate this pin to control the speed of the fan.
  Serial.println("en pin set HIGH");
  digitalWrite(en, HIGH);
}
  BlynkEdgent.begin();
  // Light up the discrete red LED on the back of the board to show the device is active
  pinMode(ledPin, OUTPUT);
  // It's an active low pin, so we write a LOW value to turn it on
  digitalWrite(ledPin, LOW);

  // CAUTION - We'll disable the brownout detection
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);

  // Start serial connection for debugging purposes
  Serial.begin(115200);
  Serial.println(__FILE__ __DATE__);
  
  //Pin definition for CAMERA_MODEL_AI_THINKER
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = 5;
  config.pin_d1 = 18;
  config.pin_d2 = 19;
  config.pin_d3 = 21;
  config.pin_d4 = 36;
  config.pin_d5 = 39;
  config.pin_d6 = 34;
  config.pin_d7 = 35;
  config.pin_xclk = 0;
  config.pin_pclk = 22;
  config.pin_vsync = 25;
  config.pin_href = 23;
  config.pin_sscb_sda = 26;
  config.pin_sscb_scl = 27;
  config.pin_pwdn = 32;
  config.pin_reset = -1;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;

  // If the board has additional "pseudo RAM", we can create larger images
  if(psramFound()){
    config.frame_size = FRAMESIZE_UXGA; // UXGA=1600x1200. Alternative values: 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;
  }

  // Disable any hold on pin 4 that was placed before ESP32 went to sleep
  rtc_gpio_hold_dis(GPIO_NUM_4);
  // Use PWM channel 7 to control the white on-board LED (flash) connected to GPIO 4
  ledcSetup(7, 5000, 8);
  ledcAttachPin(4, 7);
  // Turn the LED on at specified power
  ledcWrite(7, flashPower);

  // Initialise the camera
  // Short pause helps to ensure the I2C interface has initialised properly before attempting to detect the camera
  delay(250);
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    sleep();
  }

  // Image settings
  sensor_t * s = esp_camera_sensor_get();
  // Gain
  s->set_gain_ctrl(s, 1);      // Auto-Gain Control 0 = disable , 1 = enable
  s->set_agc_gain(s, 0);       // Manual Gain 0 to 30
  s->set_gainceiling(s, (gainceiling_t)0);  // 0 to 6
  // Exposure
  s->set_exposure_ctrl(s, 1);  // Auto-Exposure Control 0 = disable , 1 = enable
  s->set_aec_value(s, 300);    // Manual Exposure 0 to 1200
  // Exposure Correction
  s->set_aec2(s, 0);           // Automatic Exposure Correction 0 = disable , 1 = enable
  s->set_ae_level(s, 0);       // Manual Exposure Correction -2 to 2
  // White Balance
  s->set_awb_gain(s, 1);       // Auto White Balance 0 = disable , 1 = enable
  s->set_wb_mode(s, 0);        // White Balance Mode 0 to 4 - if awb_gain enabled (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home)
  s->set_whitebal(s, 1);       // White Balance 0 = disable , 1 = enable
  s->set_bpc(s, 0);            // Black Pixel Correction 0 = disable , 1 = enable
  s->set_wpc(s, 1);            // White Pixel Correction 0 = disable , 1 = enable
  s->set_brightness(s, 0);     // Brightness -2 to 2
  s->set_contrast(s, 0);       // Contrast -2 to 2
  s->set_saturation(s, 0);     // Saturation -2 to 2
  s->set_special_effect(s, 0); // (0 - No Effect, 1 - Negative, 2 - Grayscale, 3 - Red Tint, 4 - Green Tint, 5 - Blue Tint, 6 - Sepia)
  // Additional settings
  s->set_lenc(s, 1);           // Lens correction 0 = disable , 1 = enable
  s->set_hmirror(s, 0);        // Horizontal flip image 0 = disable , 1 = enable
  s->set_vflip(s, 0);          // Vertical flip image 0 = disable , 1 = enable
  s->set_colorbar(s, 0);       // Colour Testbar 0 = disable , 1 = enable
  s->set_raw_gma(s, 1);        // 0 = disable , 1 = enable
  s->set_dcw(s, 1);            // 0 = disable , 1 = enable
  
  // We want to take the picture as soon as possible after the sensor has been triggered, so we'll do that first, before
  // setting up the SD card, Wifi etc.
  // Initialise a framebuffer 
  camera_fb_t *fb = NULL;
  // But... we still need to give the camera a few seconds to adjust the auto-exposure before taking the picture
  // Otherwise you get a green-tinged image as per https://github.com/espressif/esp32-camera/issues/55
  // Two seconds should be enough
  delay(startupDelayMillis);
  // Take picture
  fb = esp_camera_fb_get();
  // Check it was captured ok  
  if(!fb) {
    Serial.println("Camera capture failed");
    sleep();
  }

  // Turn flash off after taking picture
  ledcWrite(7, 0);

  // Build up the string of the filename we'll use to save the file
  path = "/pic";

  // Following section creates filename based on increment value saved in EEPROM
  #ifdef USE_INCREMENTAL_FILE_NUMBERING
    // We only need 2 bytes of EEPROM to hold a single int value, but according to
    // https://arduino-esp8266.readthedocs.io/en/latest/libraries.html#eeprom
    // Minimum reserved size is 4 bytes, so we'll use that
    EEPROM.begin(4);
    // Read the value from the EEPROM cache
    EEPROM.get(0, pictureNumber);
    pictureNumber += 1;
    // Path where new picture will be saved in SD Card
    path += String(pictureNumber) + "_";
    // Update the EEPROM cache
    EEPROM.put(0, pictureNumber);
    // And then actually write the modified cache values back to EEPROM
    EEPROM.commit();
  #endif

  // Connect to Wi-Fi if required
  #if defined(SEND_EMAIL) || defined(USE_TIMESTAMP)
    WiFi.mode(WIFI_STA);
    WiFi.setHostname("Pest Memo");
    int connAttempts = 0;
//        WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    while (WiFi.status() != WL_CONNECTED && connAttempts < 10) {
      Serial.print(".");
      delay(500);
      connAttempts++;
       }
    if(WiFi.isConnected()){
            Serial.println("");
      Serial.println("WiFi connected.");
      Serial.println("IP address: ");
      Serial.println(WiFi.localIP());
      Serial.print(" Signal Level: ");
      Serial.println(WiFi.RSSI());
      Serial.println();
    }
    else {
      Serial.println(F("Failed to connect to Wi-Fi"));
      sleep();
    }
  #endif

  #ifdef USE_TIMESTAMP
    // Following section creates filename based on timestamp
    const long gmtOffset_sec = 0;
    const int daylightOffset_sec = 0;
    // Synchronise time from specified NTP server - e.g. "pool.ntp.org", "time.windows.com", "time.nist.gov"
    // From https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/Time/SimpleTime/SimpleTime.ino
    configTime(gmtOffset_sec, daylightOffset_sec, "pool.ntp.org");
    struct tm timeinfo;
    if(!getLocalTime(&timeinfo)){
      Serial.println("Failed to obtain time");
      sleep();
    }
    else {
      Serial.print("Current time is ");
      Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
      char timeStringBuff[50]; //50 chars should be enough
      strftime(timeStringBuff, sizeof(timeStringBuff), "%Y%m%d_%H%M%S", &timeinfo);
      path += (String)timeStringBuff;
    }
  #endif

  // Add the file extension
  path += ".jpg";
  
  // Next, we need to start the SD card
  Serial.println("Starting SD Card");
  if(!MailClient.sdBegin(14, 2, 15, 13)) {
    Serial.println("SD Card Mount Failed");
    sleep();
  }

  // Access the file system on the SD card
  fs::FS &fs = SD;
  // Attempt to save the image to the specified path
  File file = fs.open(path.c_str(), FILE_WRITE);
  if(!file){
    Serial.printf("Failed to save to path: %s\n", path.c_str());
    sleep();
  }
  else {
    file.write(fb->buf, fb->len); // payload (image), payload length
    Serial.printf("Saved file to path: %s\n", path.c_str());
  }
  file.close();

  // Now that we've written the file to SD card, we can release the framebuffer memory of the camera
  esp_camera_fb_return(fb);
  // And breathe for a moment...
  delay(1000);

  #ifdef SEND_EMAIL
    // Get verbose output of emailing process
    smtp.debug(1); 
    // Assign the callback function called after sending
    smtp.callback(smtpCallback);
    // Define the session config data which used to store the TCP session configuration
    ESP_Mail_Session session;
    session.server.host_name = SMTP_HOST;
    session.server.port = SMTP_PORT;
    session.login.email = AUTHOR_EMAIL;
    session.login.password = AUTHOR_PASSWORD;
    session.login.user_domain = "mydomain.net";

    // Define the SMTP_Message class variable to hold the config of the eMail itself
    SMTP_Message message;
    //message.enable.chunking = true; // Enable chunked data transfer for large messages if server supported
    message.sender.name = "ESP32-CAM";
    message.sender.email = AUTHOR_EMAIL;
    message.subject = "Motion Detected - ESP32-CAM";
    message.addRecipient("", "");
    //message.addRecipient("name2", "email2");
    //message.addCc("email3");
    //message.addBcc("email4");
    // Set the message content
    message.text.content = "Motion has been detected in your Pest Memo.  Please take a look inside of it.";
    message.text.transfer_encoding = Content_Transfer_Encoding::enc_base64;

    // Now define the attachment properties
    SMTP_Attachment att;
    att.descr.filename = "photo.jpg";
    att.descr.mime = "application/octet-stream"; //binary data
    att.file.path = path.c_str();
    att.file.storage_type = esp_mail_file_storage_type_sd;
    att.descr.transfer_encoding = Content_Transfer_Encoding::enc_base64;
    // Add attachment to the message
    message.addAttachment(att);

    // Connect to server with the session config
    Serial.println("Connecting to SMTP");
    if(!smtp.connect(&session)) {
      Serial.println("Couldn't connect");
      sleep();
    }
    // Start sending Email and close the session
    Serial.println("Sending Mail");
    if(!MailClient.sendMail(&smtp, &message)) {
      Serial.println("Error sending Email, " + smtp.errorReason());
      sleep();
    }
  #endif

  // Now that email is sent, we can turn the Wi-Fi off
  WiFi.disconnect(true);
  WiFi.mode(WIFI_OFF);

  // And go to bed until the next time we are triggered to take a photo
  sleep();


}

void loop() {
  BlynkEdgent.run();
}

It has a few GPIO pins. At first glance, you only need 2 pins.

A single output pin connected to a suitable relay module should be able to do this.

A PIR sensor maybe? It usually has 1 pin that changes from low to high (or high to low) when motion is detected.

Surprising. As @markd833 points out, you only need two pins so far.

Is there in fact something you are not telling us? :thinking: How many other things are involved in your "multiple sketches"?

A "port expander" connects with two pins using I²C and gives you up to 16 I/O pins.

Easy to program and much simpler (and cheaper) than trying to code a UNO - which is a very poor choice anyway for an actual project such as this. :roll_eyes:

1 Like

Sorry, I was at work.

I am new. So, when I read that I need a pin for a push button, for the fan, for the sensor, for an LED, and for Blynk I feel I don't have enough pins.

Thank you. Will I need a pin for the push button, to reset the wi-fi provisioning, or to have an LED to show the device is getting power?

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.