Skip to content

Week 4. Embedded Programming

Group Assignment

This week, our group assignment was very interesting. With the help of Maxime, we explored several microcontrollers:

Using two types of programming languages, C and MicroPython, we compared the speed of execution. We measured everything using an oscilloscope.

Zhirayr did an execution speed test on the XIAO RP2040, and I did it on the XIAO ESP32-S3.

Frequency of ESP32-S3 with MicroPython.

alt text

Frequency of ESP32-S3 with C++ Arduino framework.

alt text

I want to mention that it was very weird for us that the ESP32 runs MicroPython slower (76.8 kHz) than the RP2040 (82.8 kHz), even though the ESP32 runs C++ with the Arduino framework much faster (785.3 kHz) than the RP2040 (610.2 kHz).

I think the reason is that the RP2040 is more optimized for MicroPython than the ESP32.

Microcontroller MicroPython Speed C++ with Arduino Speed
ESP32 76.8 kHz 785.3 kHz
RP2040 82.8 kHz 610.2 kHz

Which Microcontroller did I use?

Since my submarine should have a camera, I decided to try programming on the ESP32 microcontroller, which already has a camera. We have ESP32-CAM (ESP32-S) and XIAO-ESP32-S3 in our lab, so I will try to program on both.

alt text

More about ESP32.

What is ESP32?

I asked Copilot to introduce me more about ESP32. Here is the prompt.

What is ESP32-S3 , specs

Here is the answer.

The ESP32 is a series of low-cost, low-power system-on-chip microcontrollers with integrated Wi-Fi and dual-mode Bluetooth. Developed by >>Espressif Systems, the ESP32 series is a successor to the ESP8266 microcontroller.

This is the specs for ESP32-S3:

Specification Description
Processor Xtensa dual-core 32-bit LX7 microprocessor, operating at up to 240 MHz
Memory 512 KiB SRAM, 384 KiB ROM
Wireless Connectivity Wi-Fi (802.11 b/g/n) and Bluetooth (v5.0 BR/EDR and BLE)
Peripheral Interfaces 45 programmable GPIOs, 14 touch sensors, 2 × 12-bit SAR ADCs, 2 × 8-bit DAC, and more
Security Secure boot, flash encryption, cryptographic hardware acceleration
Power Management Integrated low-dropout regulator, deep sleep current of 5 μA

Versions

Here are the main versions of the ESP32:

  • ESP32: The original version with a dual-core Xtensa LX6 microprocessor. It’s known for its robust features and versatility.

  • ESP32-S: A versatile microcontroller with a dual-core 32-bit Xtensa LX6 microprocessor, operating at 160 or 240 MHz. It supports Wi-Fi and Bluetooth, and has extensive peripheral interfaces, making it suitable for a wide range of applications from simple DIY projects to complex IoT developments.

  • ESP32-S2: This version features a single-core Xtensa LX7 microprocessor. It offers improved security and a higher number of GPIOs compared to the original ESP32. It was launched in 2020.

  • ESP32-C3: This version is equipped with a single-core RISC-V microprocessor, providing enhanced performance and efficiency. It was also launched in 2020 and is designed for low-power IoT applications.

  • ESP32-S3: Features a dual-core Xtensa LX7 microprocessor, offering higher processing power and more GPIOs. It is suitable for AI and machine learning applications.

  • ESP32-H2: This version integrates Thread and Zigbee, making it ideal for smart home applications and providing better connectivity options.

Datasheets and Pinouts of ESP32-S and XIAO ESP32-S3

I found the pinouts of ESP32-S and XIAO ESP32-S3 on the Last Minute Engineers website and there is a lot of very useful information.

ESP32-S alt text

ESP32-S3 alt text

Then I asked Copilot about pinouts and which pins I should use. Here is the prompt.

Tell me a about the pinouts of ESP32. What are GPIO, ADC, SPI, and Touch? I will be programming through Arduino Uno in Arduino IDE, which pinouts do I need to use?

Here is the answer:

Pinouts of ESP32

  1. GPIO (General Purpose Input/Output): These are versatile pins used for both input and output of signals. They can connect various peripherals and devices.

  2. ADC (Analog-to-Digital Converter): This converts analog signals into digital values. ADC pins allow you to read analog inputs, like voltage levels.

  3. SPI (Serial Peripheral Interface): An interface used for communication with peripherals such as sensors and displays. It uses multiple pins for data exchange.

  4. Touch: These pins can detect touch inputs or capacitive touch signals.

Programming ESP32 with Arduino Uno in Arduino IDE

To program the ESP32 using Arduino Uno in Arduino IDE, you’ll need to use specific pins for different functionalities:

  • GPIO Pins: For general-purpose input/output (e.g., GPIO 0, 1, 2, etc.)

  • ADC Pins: For analog-to-digital conversion (e.g., ADC1_CHANNEL_0 - GPIO 36, ADC1_CHANNEL_1 - GPIO 37, etc.)

  • SPI Pins: For SPI communication (e.g., GPIO 18 - SCK, GPIO 23 - MOSI, GPIO 19 - MISO, GPIO 5 - SS)

  • Touch Pins: For touch input (e.g., T0 - GPIO 4, T1 - GPIO 0, etc.)

For programming through Arduino UNO using Arduino IDE, you’ll need to connect the ESP32 to the Arduino UNO using the following pins:

VCC to 3.3V

GND to GND

TXD to RX

RXD to TX

EN to EN

Simulating on Wokwi

There are two ways to simulate it with Wokwi. One is on the Wokwi website, and the second option is to add an extension on VS Code. I prefer to do it on web site.

I watched a video on how to write code for a servo motor, and I modified it by adding servo.write to the loop so that it keeps repeating constantly. Here is the code:

#include <ESP32Servo.h> 
Servo myServo; 
const int servoPin = 2; 

void setup() {
  myServo.attach(servoPin); 
}

void loop() {
  myServo.write(90);
  delay(1000); 
  myServo.write(0); 
  delay(1000); 
}



For including library I need to go Library Manager and add it there.

alt text

After adding components and connecting wires in simulator I pressed the Start Simulation button and it runs good.

❗Note. Keep in mind that compilation on Wokwi is quite long, and you might see the message ‘servers are busy’ several times. I managed to compile and start the simulation on the 6th try.

alt text

Programming

Wiring the device

After Simulation I tried to program on real ESP32-S3 and Servo motor. For this I have used these components:

alt text

  1. ESP32-S3

  2. Servomotor CS-939MG

  3. Breadboard

  4. Jump wires

  5. Type-C cable

I found the outputs of servomotor and usually the output is the same with the same colors.

alt text

After I connected the servo motor to ESP32-S3 using breadboard.

alt text

I want to share with you how to conveniently connect a breadboard without soldering using jumper wires.

alt text

Coding

Running Servomotor

For coding I used Arduino IDE.
For using Servomotor I need to install library ESP32Servo.

alt text

I found it in search bar and installed.

alt text

I need also to install ESP32 board from Boards Manager.

alt text

Here is the right board.

alt text

After installing I put my code to Arduino IDE, then select the board and settings.

alt text

Then I uploaded the code to ESP32 using ctrl + u hotkey.

❗Note. After uploading the code I’ll need to reset the board to start the code execution by pressing b button on device. This will put the board into the programming mode. Once the upload is complete, release the “B” button. By Pressing the “R” (Reset) button to reset the board and start running the uploaded code.

alt text

After uploading i saw writing lines in log, which is good , and it means that code is uploading.

alt text

Then I pressed the R button on device and Servo start rotating.



Using Camera on ESP32CAM.

Since I don’t have many programming skills, I decided to use AI to help me with the code. I studied many websites and videos to achieve this, and after spending 3 DAYS, I finally got what I needed!

Uploading Example of CameraWeServer

In Autodesk Instructables site I found how to connect and program ESP32CAM with arduino Uno.

alt text

Here is the connection.

alt text

After I tried to upload example of WebServer for Streaming.

alt text

Before uploading I uncommented the camera model , and write the wifi SSID and password.

alt text

Here is the parameters for uploading data.

alt text

❗Note. Compilation is not fast! in my case, it takes about 2 minutes. After compilation, you must press the button on the ESP32-CAM to enter boot mode so the writing process can start.

If you see this red line logs(writing at....) it’s mean that everything is good.

alt text

After uploading I take off the jumper between GND and GPIO 0 , open Serial Monitor (ctrl + shift + m) and push the button on ESP32CAM.

alt text

ESP32CAM is connected to Network, I open the address in browser and Voilà!!

alt text

Coding with AI.

I really enjoyed experimenting with the camera, and ChatGPT showed me the possibilities of getting information using a Telegram bot. So, I started learning all about it.

I watched several video tutorials on Youtube how to do all this, but I needed something different…

So I started talking with ChatGPT from this prompt:

I need code for ESP32-CAM where the camera connects through Wi-Fi and sends its IP address to a Telegram bot for live streaming.It should also have commands Capture – takes a photo and saves it to the SD card and Record – records a video and saves it to the SD card.

And everything started from here

As you can see I have very long conversations with ChatGPT and finally alt text

So, I started sending code to Copilot, hoping it would try to fix it. Then DeepSeek), then DeepAI, I talked to all AIs that are available and free.

alt text

I thought this would never end, and I wouldn’t be able to generate the code. But in the end, I came across another AI, Claude, who finally gave me the code that worked the way I wanted. However, before that, I spent a very long time talking to it, trying to explain what I needed and sending the code that other AIs had generated..

Here is the working Final Code:

    #include "esp_camera.h"
#include <WiFi.h>
#include "esp_timer.h"
#include "img_converters.h"
#include "Arduino.h"
#include "fb_gfx.h"
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "esp_http_server.h"
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>
#include <ArduinoJson.h>
#include "SD_MMC.h"
#include "FS.h"

// Telegram settings
#define BOTtoken "#######################" //Bot token

// Array of chat IDs
const String CHAT_IDS[] = {
  "************",    // Chat IDs
  "*************",
  "THIRD_CHAT_ID",
  "FOURTH_CHAT_ID",
  "FIFTH_CHAT_ID"
};
const int NUM_CHAT_IDS = 5;

const char* ssid = "*************";     // wifi settings
const char* password = "***********";

#define FLASH_LED_PIN 4
#define PART_BOUNDARY "123456789000000000000987654321"

// Variables
bool isRecording = false;
File videoFile;

// Telegram
WiFiClientSecure clientTCP;
UniversalTelegramBot bot(BOTtoken, clientTCP);

// Camera pins
#define 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

static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";

httpd_handle_t stream_httpd = NULL;

// Function to broadcast message to all chat IDs
void broadcastMessage(const String& message) {
  for (int i = 0; i < NUM_CHAT_IDS; i++) {
    bot.sendMessage(CHAT_IDS[i], message, "");
    delay(100); // Small delay between messages to prevent flooding
  }
}

bool initSDCard() {
  if(!SD_MMC.begin("/sdcard", true)) {
    Serial.println("SD Card Mount Failed");
    return false;
  }

  uint8_t cardType = SD_MMC.cardType();
  if(cardType == CARD_NONE) {
    Serial.println("No SD Card attached");
    return false;
  }

  return true;
}

void showMenu(String chat_id) {
  String welcome = "ESP32-CAM Bot Commands:\n\n";
  welcome += "/start : Show this menu\n";
  welcome += "/capture : Capture and save an image\n"; // Новая команда
  welcome += "/record : Start recording (with flash)\n";
  welcome += "/stoprecord : Stop recording\n";

  bot.sendMessage(chat_id, welcome, "");
}

// Функция для захвата и сохранения снимка
void captureImage(String chat_id) {
  if (!initSDCard()) {
    broadcastMessage("Failed to initialize SD Card");
    return;
  }

  camera_fb_t * fb = esp_camera_fb_get(); // Получаем кадр с камеры
  if (!fb) {
    broadcastMessage("Camera capture failed");
    return;
  }

  String filename = "/capture_" + String(millis()) + ".jpg"; // Имя файла
  File file = SD_MMC.open(filename, FILE_WRITE);

  if (!file) {
    broadcastMessage("Failed to create file");
    esp_camera_fb_return(fb); // Освобождаем буфер камеры
    return;
  }

  file.write(fb->buf, fb->len); // Записываем данные в файл
  file.close();
  esp_camera_fb_return(fb); // Освобождаем буфер камеры

  broadcastMessage("Capture saved: " + filename);
}

void startRecording(String chat_id) {
  if (!initSDCard()) {
    broadcastMessage("Failed to initialize SD Card");
    return;
  }

  String filename = "/capture_" + String(millis()) + ".mjpeg";
  videoFile = SD_MMC.open(filename, FILE_WRITE);

  if (!videoFile) {
    broadcastMessage("Failed to create file");
    return;
  }

  isRecording = true;
  digitalWrite(FLASH_LED_PIN, HIGH); // Turn on flash
  broadcastMessage("Recording started with flash. File: " + filename);
}

void stopRecording(String chat_id) {
  if (isRecording) {
    videoFile.close();
    isRecording = false;
    digitalWrite(FLASH_LED_PIN, LOW); // Turn off flash
    broadcastMessage("Recording stopped and saved");
  }
}

static esp_err_t stream_handler(httpd_req_t *req) {
  camera_fb_t * fb = NULL;
  esp_err_t res = ESP_OK;
  size_t _jpg_buf_len = 0;
  uint8_t * _jpg_buf = NULL;
  char * part_buf[64];

  res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
  if(res != ESP_OK) {
    return res;
  }

  while(true) {
    fb = esp_camera_fb_get();
    if (!fb) {
      Serial.println("Camera capture failed");
      res = ESP_FAIL;
    } else {
      if(fb->width > 400) {
        if(fb->format != PIXFORMAT_JPEG) {
          bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);
          esp_camera_fb_return(fb);
          fb = NULL;
          if(!jpeg_converted) {
            Serial.println("JPEG compression failed");
            res = ESP_FAIL;
          }
        } else {
          _jpg_buf_len = fb->len;
          _jpg_buf = fb->buf;
        }
      }

      if (isRecording && videoFile) {
        videoFile.write(_jpg_buf, _jpg_buf_len);
      }
    }

    if(res == ESP_OK) {
      size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len);
      res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
    }
    if(res == ESP_OK) {
      res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
    }
    if(res == ESP_OK) {
      res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
    }
    if(fb) {
      esp_camera_fb_return(fb);
      fb = NULL;
      _jpg_buf = NULL;
    } else if(_jpg_buf) {
      free(_jpg_buf);
      _jpg_buf = NULL;
    }
    if(res != ESP_OK) {
      break;
    }
  }
  return res;
}

void startCameraServer() {
  httpd_config_t config = HTTPD_DEFAULT_CONFIG();
  config.server_port = 80;

  httpd_uri_t index_uri = {
    .uri       = "/",
    .method    = HTTP_GET,
    .handler   = stream_handler,
    .user_ctx  = NULL
  };

  if (httpd_start(&stream_httpd, &config) == ESP_OK) {
    httpd_register_uri_handler(stream_httpd, &index_uri);
  }
}

void handleNewMessages(int numNewMessages) {
  for (int i = 0; i < numNewMessages; i++) {
    String chat_id = bot.messages[i].chat_id;
    String text = bot.messages[i].text;

    // Check if the sender is authorized
    bool isAuthorized = false;
    for (int j = 0; j < NUM_CHAT_IDS; j++) {
      if (chat_id == CHAT_IDS[j]) {
        isAuthorized = true;
        break;
      }
    }

    if (!isAuthorized) {
      bot.sendMessage(chat_id, "Unauthorized user", "");
      continue;
    }

    if (text == "/start") {
      showMenu(chat_id);
      String ipAddress = WiFi.localIP().toString();
      bot.sendMessage(chat_id, "Stream available at: http://" + ipAddress, "");
    }
    else if (text == "/capture") { // Обработка команды /capture
      captureImage(chat_id);
    }
    else if (text == "/record") {
      if (!isRecording) {
        startRecording(chat_id);
      } else {
        bot.sendMessage(chat_id, "Already recording", "");
      }
    }
    else if (text == "/stoprecord") {
      if (isRecording) {
        stopRecording(chat_id);
      } else {
        bot.sendMessage(chat_id, "Not recording", "");
      }
    }
  }
}

void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);

  Serial.begin(115200);
  Serial.setDebugOutput(false);

  pinMode(FLASH_LED_PIN, OUTPUT);
  digitalWrite(FLASH_LED_PIN, LOW);

  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_sccb_sda = SIOD_GPIO_NUM;
  config.pin_sccb_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_VGA;
    config.jpeg_quality = 18;
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_VGA;
    config.jpeg_quality = 18;
    config.fb_count = 1;
  }

  // Camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }

  // Camera settings
  sensor_t * s = esp_camera_sensor_get();
  s->set_brightness(s, 0);
  s->set_contrast(s, 0);
  s->set_saturation(s, 0);
  s->set_special_effect(s, 0);
  s->set_whitebal(s, 1);
  s->set_awb_gain(s, 1);
  s->set_wb_mode(s, 0);
  s->set_exposure_ctrl(s, 1);
  s->set_aec2(s, 0);
  s->set_ae_level(s, 0);
  s->set_aec_value(s, 300);
  s->set_gain_ctrl(s, 1);
  s->set_agc_gain(s, 0);
  s->set_gainceiling(s, (gainceiling_t)0);
  s->set_bpc(s, 0);
  s->set_wpc(s, 1);
  s->set_raw_gma(s, 1);
  s->set_lenc(s, 1);
  s->set_hmirror(s, 0);
  s->set_vflip(s, 1);
  s->set_dcw(s, 1);
  s->set_colorbar(s, 0);

  // Connect to WiFi
  WiFi.begin(ssid, password);
  clientTCP.setCACert(TELEGRAM_CERTIFICATE_ROOT);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");

  // Start web server
  startCameraServer();

  // Send initial message with menu
  String ipAddress = WiFi.localIP().toString();
  String welcome = "ESP32-CAM Started!\n\n";
  welcome += "Stream available at: http://" + ipAddress + "\n\n";
  welcome += "Commands:\n";
  welcome += "/start : Show menu\n";
  welcome += "/capture : Capture and save an image\n";
  welcome += "/record : Start recording (with flash)\n";
  welcome += "/stoprecord : Stop recording";

  broadcastMessage(welcome);
}

void loop() {
  int numNewMessages = bot.getUpdates(bot.last_message_received + 1);
  if (numNewMessages > 0) {
    handleNewMessages(numNewMessages);
  }
  delay(100);
}

Here is the result:

alt text

Flash is turning on, when I’m sending /record command.

alt text

Here is Live Streaming.

alt text

Conclusion

This week, I learned a lot. Although I didn’t write the final code for the submarine myself, I realized that to get the code you need from AI, you have to talk to it for a long time. It’s very complex code for me, and I’m still not very good at programming devices, but I will keep learning programming because it’s really interesting. What amazed me the most was discovering that different programming languages have different execution speeds.

Files of this Week


Last update: February 21, 2025