ESP32 Cam – OTA Uploading With Setup and Troubleshooting

In this tutorial I’ll show you how to setup and troubleshoot OTA or Over The Air updating using Wifi and an ESP32 Cam.

The ESP32 does not have a serial port so this makes it difficult to update.  I bought boards that have the FTDI Usb adapter built in, but they use all of the boards pins.  So if I want to add connections such as Analog read and digital output, then I lose the ability to upload a sketch. Removing the board and re-attaching it to the shield is a cumbersome process that should be avoided if you can.

The solution is to use OTA updating and just send new code over Wifi.

Let’s see how this is done.

2 Methods To Use

There are two methods we can setup.  

OTA Basic

OTA Web Updater 

OTA Basic

Here we just create a wifi network and then a new network port is available right in the Arduino IDE.  It’s quick and simple

OTA Web Updater

This method creates a web page that you can log onto.  Once logged in, you upload a compiled binary file.

Setting Up The OTA Basic

Since this method is so quick and easy, we’ll do this first.  Now the first time I tried this it failed so I’ll go over the issue and how you can solve this.

For this tutorial I’ll use the ESP32 Cam as discussed in tutorial Getting Started With Esp32 Cam

These are the boards I bought.  Each as it’s own USB adapter that can be removed once you have setup the OTA Software.

In the Arduino IDE, setup your board and options as follows:

Make sure that you choose the correct partition scheme or else the uploading will fail

Board – AI Thinker ESP32-CAM

Partition Scheme – Minimal with OTA 

 

Upload The Sketch

Navigate to ESP32 Examples and select the Basic OTA

Find these two lines in the sketch and enter in your ssid and password.

const char* ssid = “*******”;
const char* password = “*******”;

Chose the serial port and upload the sketch to your ESP32

You can also make your life easier by creating a unique name for your Network Port.  I uncommented the second line and named mine myesp32AI-Thinker.

// Hostname defaults to esp3232-[MAC]
ArduinoOTA.setHostname(“myesp32AI-Thinker”);

Now open up the Tools and look down at the bottom under Network Ports.  You should see something like this.

Uploading A New Sketch Using Wifi

Now you are ready to disconnect the board from your USB port.  You can remove the FTDI adapter or the shield if you have one.  

Supply power to the board and then go back to the Arduino IDE

Select the correct network port.

Upload the sketch.  

For this example, I will just re-upload the original but you can add additional code as needed.  If successful you should see something like this.

How To Add Custom Code

The whole point of this is to be able to add your own custom code.  You might think you can simply open a new sketch and upload what you need, but that will only work the first time.  The reason is that it will whipe out the OTA programming and you will have to reconnect it back to the USB in order to fix it.

Error uploading
Here is what happens if you fail to add the OTA code during WIFI uploading.

The solution is to re-use the basic OTA code and add in your custom code as needed.

In the example below I will modify the Basic OTA code and add in a blink sketch.

 

/**
 * My Engineering Projects
 * 
 * madscientistguy.com
 *
 * This code extends the example Basic OTA code to add a blinking LED capability
 */

#include <WiFi.h>
#include <ESPmDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>


const char* ssid = "*****";
const char* password = "***";

//add our custom definition
#define LED_PIN 2

unsigned long previousMillis = 0;  // will store last time LED was updated
const long interval = 500;  // interval at which to blink (milliseconds)
int ledState = LOW;  // ledState used to set the LED

void setup() {
  Serial.begin(115200);
  Serial.println("Booting");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("Connection Failed! Rebooting...");
    delay(5000);
    ESP.restart();

  }


   
  // Port defaults to 3232
  // ArduinoOTA.setPort(3232);

  // Hostname defaults to esp3232-[MAC]
   ArduinoOTA.setHostname("myesp32-AI-Thinker");

  // No authentication by default
  // ArduinoOTA.setPassword("admin");

  // Password can be set with it's md5 value as well
  // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
  // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");

  ArduinoOTA
    .onStart([]() {
      String type;
      if (ArduinoOTA.getCommand() == U_FLASH)
        type = "sketch";
      else // U_SPIFFS
        type = "filesystem";

      // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
      Serial.println("Start updating " + type);
    })
    .onEnd([]() {
      Serial.println("\nEnd");
    })
    .onProgress([](unsigned int progress, unsigned int total) {
      Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
    })
    .onError([](ota_error_t error) {
      Serial.printf("Error[%u]: ", error);
      if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
      else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
      else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
      else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
      else if (error == OTA_END_ERROR) Serial.println("End Failed");
    });

  ArduinoOTA.begin();

  Serial.println("Ready");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

    /** Custom Code */
    pinMode(LED_PIN, OUTPUT);
}

void loop() {
  ArduinoOTA.handle();
  
  /**
   * Custom Code
   * 
   */

  //blink an led
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
  // save the last time you blinked the LED
  previousMillis = currentMillis;
  // if the LED is off turn it on and vice-versa:
  ledState = not(ledState);
  // set the LED with the ledState of the variable:
  digitalWrite(LED_PIN,  ledState);
  }
}

Troubleshooting

Network Unavailable

Notice in the example sketch that I did not use the delay() function.  Delay pauses the program and this can cause issues when you want to update the code.  If you happen to be uploading during the pause, you will get an error saying that the network in unavailable.

Try to avoid delay() and use some other method such as the time method that I used.

 

General Upload Error (just brackets with no response)

This error happened to me and it was difficult to find the solution.  The error occurred when the partition scheme was incorrect. 

Be sure to use the Minimal OTA scheme

Trinket Microcontroller – Getting Started

The trinket is a small microcontroller that costs just a few bucks.  It it an ideal microcontroller for those smaller projects.

Connect To Arduino IDE

If you have not already downloaded the Trinket libraries, you’ll need to do that first. Go to the boards manager and add the trinket libraries.

When done, you can select the appropriate board under Adafruit Boards.  Select Adafruit Trinket ATtiny 8MHz

No Serial Port

Now you may be surprised to learn that there is no serial port that you can select.  That’s normal as it’s a virtual serial port.  

Programmer

Select the USB TinyISP programmer.

Upload A Sketch

You are almost done.  To upload the sketch, this is a bit different than most Arduinos.  You can only upload sketches if the bootloader is active.  Now how do you do that?

Red Pulsing LED – Active Bootloader

The bootloader is active when the red led is slowly pulsing.  This is active when you first plug it in but it times out.  Therefore, to re-activated it, press the reset button and upload the sketch while it’s pulsing.

Check out the video here

 

Errors During Uploading

Here is a typical error you might receive if you attempt to upload when the bootloader is not active.  Simply press the reset button and try again.

Arduino: 1.8.19 (Mac OS X), Board: “Adafruit Trinket (ATtiny85 @ 8MHz)”

Sketch uses 686 bytes (12%) of program storage space. Maximum is 5310 bytes.
Global variables use 9 bytes of dynamic memory.

avrdude: error: usbtiny_transmit: Broken pipe
avrdude: initialization failed, rc=-1
Double check connections and try again, or use -F to override
this check.

avrdude: error: usbtiny_transmit: Broken pipe
avrdude: error: usbtiny_transmit: Broken pipe

This report would have more information with
“Show verbose output during compilation”
option enabled in File -> Preferences.

Upload a test blink sketch and if all goes well the led will flash as you have specified in the sketch.

/*
  Blink

  Turns an LED on for one second, then off for one second, repeatedly.

  Most Arduinos have an on-board LED you can control. On the UNO, MEGA and ZERO
  it is attached to digital pin 13, on MKR1000 on pin 6. LED_BUILTIN is set to
  the correct LED pin independent of which board is used.
  If you want to know what pin the on-board LED is connected to on your Arduino
  model, check the Technical Specs of your board at:
  https://www.arduino.cc/en/Main/Products

  modified 8 May 2014
  by Scott Fitzgerald
  modified 2 Sep 2016
  by Arturo Guadalupi
  modified 8 Sep 2016
  by Colby Newman

  This example code is in the public domain.

  https://www.arduino.cc/en/Tutorial/BuiltInExamples/Blink
*/

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(100);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(100);                       // wait for a second
}

ESP32 Cam – Getting Started

The ESP32 Cam combines a microcontroller with a 2MP camera.  Not only that but it has built in WIFI and Bluetooth with a micro SD card for storage.  If you were to try this with an Arduino, it would be a lot more expensive and complicated.  Likewise a Raspberry Pi would also be more expensive.  Their webcam alone is around $60.

The camera modules is inexpensive, around $10 so it’s the perfect hardware if you need a camera.

Unlike an Arduino board, there is no USB connector.  You have 2 options available.

  1. Use an FTDI board and connect 4 wires to the ESP32 Cam
  2. Buy the modules with a pluggable shield.

The FTDI board is a small board that you connect to temporarily allow a USB connection.  I suppose you could leave it plugged in, but most buy a couple of these and use them just for programming multiple ESP32s.  They are also used if you are making your own microcontroller board using the ATMEGA328-PU chip.

FTDI board

2 Pack with removable usb shield

For this tutorial, I’m going to use the second option.  I bought 2 of these for cheap and each has it’s own USB connection.  You are free to remove it after programming. I chose this option because I thought it would be easier than manually jumping wires.

 

Connecting With Arduino IDE

There are a few steps required in order to get the board connected to the Arduino IDE.

  1. Install required libraries
  2. Select correct baud rate
  3. Select correct board

Install Libraries

The Arduino IDE boards manager will show all available libraries.  If you type in ESP32 and nothing shows up, then that means you need to tell the IDE where it can find new libraries.  This is simple.  All that is required is that you add a comma separated list of URLs.

The URL for the ESP32 libraries is located at:

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

I am using MAC OS so the instructions here are based on that.  Your’s may be different but the general procedure is the same.

Navigate to Arduino->Preferences and then look for the Additional Boards Manager URLS input field.

Additional Boards Manager
Here is where all additional URLS are located. Separate each URL by a “,”

Here you can see that I have already added a URL for Adafruit.  To add the URL for the ESP32, just insert a comma and the URL above and press save.

Once complete, go back to the Boards Manager and add the ESP32 library Tools->Manage Libraries

ESP Library

Select Your Board

For this tutorial we are running the demo WebCamera Server sketch.  If you open the sketch you will  notice that the default board is the AI Thinker

This is defined by this line of code.

#define CAMERA_MODEL_AI_THINKER // Has PSRAM

This is the board I selected under the tools manager.

Run Test Sketch

We need to try a test sketch to ensure everything is working right.  I chose to setup a Camera Webserver so that I could test both WIFI and the camera.

After you have selected the right board (Node32 S) and baud rate, navigate to File->Examples->Examples for Node32S->ESP32->Camera->CameraWebServer

Webserver Sketch

Locate these two lines

const char* ssid = “**********”;
const char* password = “**********”;

replace the *****’s with your SSID and Wifi password for your network.

Upload the sketch and check the serial monitor.  You should see this.

Webserver confirmation details
Note that you local IP address may be different

 

 

Webserver camera options

Webserver Optons

Open a web browser on any device using the same WIFI network. Navigate to the ip address shown. Mine was 192.168.50.55 Press Start Stream and the web camera window will open below if everything is working correctly.

You should see a window at the bottom for the webcam video.  You can change the size of the output by scrolling near the top of the options and setting the resolution.

Troubleshooting

Won’t Compile

Check to see if the baud rate is correct (115200).  This happened to me so I was surprised that it was the baud rate.

Check to see if correct board is selected (Node32 S for this tutorial)

Check to see if all libraries are installed

Garbage in Serial Monitor

Garbage in Serial Monitor
The baud rate is incorrect

This can happen if you have not set the serial monitor to the correct baud rate.  Check to make sure it is set to 115200

OLED Display With An Arduino

OLED Displays

Adding a display to your Arduino projects is a great way to show information in real time.  You can buy a display for under $10 and they are easy to use. I bought a 128×96 pixel monochrome display for around $6.  The screen size is 1.3″ and big enough for small projects where I just need to display some basic information.

Getting Started

When you buy your display, take note of the library needed to use the board.  We will be using this information in our sketch.  I bought mine in 2/pack and the displays used the SH1106 library.

Install the Library

Open up the Arduino program and navigate to Tools->Manage Libraries.

Enter in the library needed for your particular display board.  In my case I typed in 1106 and the suggested library was SH110X.  This library includes the 1106 needed for my board.  If you have not already installed it, press “Install”

Route Wires To Your Arduino

There are 2 protocols for OLED display boards I2C and SPI.  I bought the I2C protocol since it has fewer inputs than the SPI.

I2C – this has 4 wires

  • Ground
  • VCC 3 to 5V
  • SCL – Clock
  • SDA – Data

Note that the order I have shown here may differ on the board that you have.  Route your hookup wires carefully and always use the actual pins shown on your board.

Note the pin order. Always route to your microcontroller based on the actual pins shown on the board.

After you soldier on the header pins, run the wires to your Arduino.

  • Ground – Arduino Ground
  • VCC – 5V Pin
  • SCL – A5
  • SDA – A4

Upload A Test Sketch

The easiest way to do this is to use an example sketch.  Typically, the OLED libraries will include example code and that it what I used for testing purposes.  I navigated to File->Examples->Adafruit SH110X (or the library you used) and then pick an example that fits your particular board.

Troubleshooting

I2C Address

If nothing appears, make sure that you have the correct I2C address.

In the example sketch, you will see something like this.  

/* Uncomment the initialize the I2C address , uncomment only one, If you get a totally blank screen try the other*/
#define i2c_Address 0x3c //initialize with the I2C addr 0x3C Typically eBay OLED’s
//#define i2c_Address 0x3d //initialize with the I2C addr 0x3D Typically Adafruit OLED’s

For my board, the correct address is the 0X3C.  Yours may be different.

Pins Incorrect

Display issues can also be a results of incorrect pins.  Be sure to reference your particular board and install according to the layout described above.

Wrong Library

The first time I tried the test code, the entire screen was garbled.  The supplier I bought from apparently changed the library needed.  Originally it was the 1306 library, but my version needed the 1106.  Check your specifications and make sure you are using the right libary

Quick Reference – 1106 Library

Here are some basic commands that you will frequently use.

Setup Section

/*********************************************************************
  This is an example for our Monochrome OLEDs based on SH110X drivers

  This example is for a 128x64 size display using I2C to communicate
  3 pins are required to interface (2 I2C and one reset)

  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada  for Adafruit Industries.
  BSD license, check license.txt for more information
  All text above, and the splash screen must be included in any redistribution

  i2c SH1106 modified by Rupert Hirst  12/09/21
*********************************************************************/


#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>

/* Uncomment the initialize the I2C address , uncomment only one, If you get a totally blank screen try the other*/
#define i2c_Address 0x3c //initialize with the I2C addr 0x3C Typically eBay OLED's
//#define i2c_Address 0x3d //initialize with the I2C addr 0x3D Typically Adafruit OLED's

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1   //   QT-PY / XIAO
Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);


#define NUMFLAKES 10 //used in the example sketch
#define XPOS 0  //cursor position
#define YPOS 1  //cursor position
#define DELTAY 2

//adafruit logo 
static const unsigned char PROGMEM logo16_glcd_bmp[] =
{ B00000000, B11000000,
  B00000001, B11000000,
  B00000001, B11000000,
  B00000011, B11100000,
  B11110011, B11100000,
  B11111110, B11111000,
  B01111110, B11111111,
  B00110011, B10011111,
  B00011111, B11111100,
  B00001101, B01110000,
  B00011011, B10100000,
  B00111111, B11100000,
  B00111111, B11110000,
  B01111100, B11110000,
  B01110000, B01110000,
  B00000000, B00110000
};


void setup()   {

  Serial.begin(9600);

  // Show image buffer on the display hardware.
  // Since the buffer is intialized with an Adafruit splashscreen
  // internally, this will display the splashscreen.

  delay(250); // wait for the OLED to power up
  display.begin(i2c_Address, true); // Address 0x3C default
  display.setContrast (0); // dim display
 
  display.display();
  delay(2000);

  // Clear the buffer.
  display.clearDisplay();

 
  }

Print Commands

void loop() {
 //small font    
  display.setTextSize(1); //size
  display.setTextColor(SH110X_WHITE); //text color
  display.setCursor(0, 0); //position the cursor
  display.println("This is text that will be displayed");
  
  //show font with a background - this is the "inverted" option where the background is highlighted and the text is shown as black inside the highlighted section
  display.setTextColor(SH110X_BLACK, SH110X_WHITE); // 'inverted' text
  display.println(3.141592); //print a number
  
  //larger font
  display.setTextSize(2);
  display.setTextColor(SH110X_WHITE);
  display.print("0x"); display.println(0xDEADBEEF, HEX);
  display.display();
  delay(2000);
  display.clearDisplay();  
}