Showing posts with label ESP32. Show all posts
Showing posts with label ESP32. Show all posts

Tuesday, 26 August 2025

FreeRTOS on ESP32: Beginner's Guide with Features, Benefits & Practical Examples

Standard



Introduction

When developing embedded systems, managing tasks, timing, and resources efficiently becomes a challenge as the complexity of the application grows. This is where Real-Time Operating Systems (RTOS) come in.

FreeRTOS is one of the most popular open-source real-time operating systems for microcontrollers. It is small, fast, and easy to integrate into resource-constrained devices like the ESP32, making it ideal for IoT, automation, and robotics projects.

In this blog topic, we will cover:

  • What FreeRTOS is
  • Key features of FreeRTOS
  • Why FreeRTOS is a good choice for ESP32 projects
  • A hands-on example using ESP32

What is FreeRTOS?

FreeRTOS is a lightweight, real-time operating system kernel for embedded devices. It provides multitasking capabilities, letting you split your application into independent tasks (threads) that run seemingly in parallel.

For example, on ESP32, you can have:

  • One task reading sensors
  • Another handling Wi-Fi communication
  • A third controlling LEDs

All running at the same time without interfering with each other.

Key Features of FreeRTOS

1. Multitasking with Priorities

FreeRTOS allows multiple tasks to run with different priorities. The scheduler ensures high-priority tasks get CPU time first, making it suitable for real-time applications.

2. Lightweight and Portable

The kernel is very small (a few KBs), making it ideal for microcontrollers like ESP32 with limited resources.

3. Preemptive and Cooperative Scheduling

  • Preemptive: Higher priority tasks can interrupt lower ones.
  • Cooperative: Tasks voluntarily give up CPU control.

This provides flexibility depending on your project needs.

4. Task Synchronization

Features like semaphores, mutexes, and queues help coordinate tasks and prevent resource conflicts.

5. Software Timers

Timers allow tasks to be triggered at regular intervals without blocking the main code.

6. Memory Management

Multiple memory allocation schemes let you optimize for speed or minimal memory fragmentation.

7. Extensive Hardware Support

FreeRTOS runs on 40+ architectures, including ARM Cortex-M, AVR, RISC-V, and of course, ESP32 (via the ESP-IDF framework).

Why Use FreeRTOS on ESP32?

The ESP32 has:

  • Dual-core processor
  • Wi-Fi + Bluetooth
  • Plenty of GPIOs

With FreeRTOS, you can use these resources efficiently:

  • Run Wi-Fi tasks on Core 0
  • Handle sensor data on Core 1
  • Keep the system responsive and organized

Example: Blinking LED Using FreeRTOS on ESP32

Below is a simple FreeRTOS example using ESP-IDF or Arduino IDE with the ESP32.

Code Example

#include <Arduino.h>

// Task Handles
TaskHandle_t Task1;
TaskHandle_t Task2;

// Task 1: Blink LED every 1 second
void TaskBlink1(void *pvParameters) {
  pinMode(2, OUTPUT);  // Onboard LED
  while (1) {
    digitalWrite(2, HIGH);
    vTaskDelay(1000 / portTICK_PERIOD_MS); // 1 second delay
    digitalWrite(2, LOW);
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }
}

// Task 2: Print message every 2 seconds
void TaskPrint(void *pvParameters) {
  while (1) {
    Serial.println("Task 2 is running!");
    vTaskDelay(2000 / portTICK_PERIOD_MS);
  }
}

void setup() {
  Serial.begin(115200);
  
  // Create two FreeRTOS tasks
  xTaskCreate(TaskBlink1, "Blink Task", 1000, NULL, 1, &Task1);
  xTaskCreate(TaskPrint, "Print Task", 1000, NULL, 1, &Task2);
}

void loop() {
  // Nothing here - tasks handle everything
}

How the Code Works

  • xTaskCreate: Creates a FreeRTOS task. Each task runs independently.
  • vTaskDelay: Delays a task without blocking others.
  • Two tasks:
    • Task 1 blinks the LED every second.
    • Task 2 prints a message every two seconds.

Both tasks run in parallel on the ESP32.

In Diagramatically shown below:

The above diagram represents;

  • Groups tasks clearly by Core 0 (Network/IO) and Core 1 (Control/Timing).
  • Places shared Queue/Event Group in the center.
  • Shows ISR → Queue → Tasks data flow with minimal arrows for clarity.

Let’s level this up with practical FreeRTOS patterns on ESP32 (Arduino core or ESP-IDF style APIs). Each example is bite-sized and focused on one RTOS feature so you can mix-and-match in a real project.

More FreeRTOS Examples on ESP32

1) Pin Tasks to Cores + Precise Periodic Scheduling

Use xTaskCreatePinnedToCore to control where tasks run and vTaskDelayUntil for jitter-free loops.

#include <Arduino.h>

TaskHandle_t sensorTaskHandle, wifiTaskHandle;

void sensorTask(void *pv) {
  const TickType_t period = pdMS_TO_TICKS(10);  // 100 Hz
  TickType_t last = xTaskGetTickCount();
  for (;;) {
    // read sensor here
    // ...
    vTaskDelayUntil(&last, period);
  }
}

void wifiTask(void *pv) {
  for (;;) {
    // handle WiFi / MQTT here
    vTaskDelay(pdMS_TO_TICKS(50));
  }
}

void setup() {
  Serial.begin(115200);

  // Run time-critical sensor task on Core 1, comms on Core 0
  xTaskCreatePinnedToCore(sensorTask, "sensor", 2048, NULL, 3, &sensorTaskHandle, 1);
  xTaskCreatePinnedToCore(wifiTask,   "wifi",   4096, NULL, 2, &wifiTaskHandle,   0);
}

void loop() {}

Why it’s useful: keep deterministic work (sensors/control) isolated from network stacks.

2) Queues: From ISR to Task (Button → LED)

Move edge events out of the ISR using queues and process them safely in a task.

#include <Arduino.h>

static QueueHandle_t buttonQueue;
const int BTN_PIN = 0;      // adjust for your board
const int LED_PIN = 2;

void IRAM_ATTR onButtonISR() {
  uint32_t tick = millis();
  BaseType_t hpTaskWoken = pdFALSE;
  xQueueSendFromISR(buttonQueue, &tick, &hpTaskWoken);
  if (hpTaskWoken) portYIELD_FROM_ISR();
}

void ledTask(void *pv) {
  pinMode(LED_PIN, OUTPUT);
  uint32_t eventTime;
  for (;;) {
    if (xQueueReceive(buttonQueue, &eventTime, portMAX_DELAY) == pdPASS) {
      // simple action: blink LED on each press
      digitalWrite(LED_PIN, !digitalRead(LED_PIN));
      Serial.printf("Button @ %lu ms\n", eventTime);
    }
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(BTN_PIN, INPUT_PULLUP);

  buttonQueue = xQueueCreate(8, sizeof(uint32_t));
  attachInterrupt(digitalPinToInterrupt(BTN_PIN), onButtonISR, FALLING);

  xTaskCreate(ledTask, "ledTask", 2048, NULL, 2, NULL);
}

void loop() {}

Tip: keep ISRs tiny; send data to tasks via queues.

3) Mutex: Protect Shared Resources (Serial / I²C / SPI)

Avoid interleaved prints or bus collisions with a mutex.

#include <Arduino.h>

SemaphoreHandle_t ioMutex;

void chatterTask(void *pv) {
  const char *name = (const char*)pv;
  for (;;) {
    if (xSemaphoreTake(ioMutex, pdMS_TO_TICKS(50)) == pdTRUE) {
      Serial.printf("[%s] hello\n", name);
      xSemaphoreGive(ioMutex);
    }
    vTaskDelay(pdMS_TO_TICKS(200));
  }
}

void setup() {
  Serial.begin(115200);
  ioMutex = xSemaphoreCreateMutex();

  xTaskCreate(chatterTask, "chat1", 2048, (void*)"T1", 1, NULL);
  xTaskCreate(chatterTask, "chat2", 2048, (void*)"T2", 1, NULL);
}

void loop() {}

Why it’s useful: prevents priority inversion and corrupted I/O.

4) Binary Semaphore: Signal Readiness (Wi-Fi Connected → Start Task)

Use a binary semaphore to gate a task until some condition is met.

#include <Arduino.h>
SemaphoreHandle_t wifiReady;

void workerTask(void *pv) {
  // wait until Wi-Fi is ready
  xSemaphoreTake(wifiReady, portMAX_DELAY);
  Serial.println("WiFi ready, starting cloud sync…");
  for (;;) {
    // do cloud work
    vTaskDelay(pdMS_TO_TICKS(1000));
  }
}

void setup() {
  Serial.begin(115200);
  wifiReady = xSemaphoreCreateBinary();

  // simulate Wi-Fi connect on another task/timer
  xTaskCreate([](void*){
    vTaskDelay(pdMS_TO_TICKS(2000)); // pretend connect delay
    xSemaphoreGive(wifiReady);
    vTaskDelete(NULL);
  }, "wifiSim", 2048, NULL, 2, NULL);

  xTaskCreate(workerTask, "worker", 4096, NULL, 2, NULL);
}

void loop() {}

5) Event Groups: Wait for Multiple Conditions

Synchronize on multiple bits (e.g., Wi-Fi + Sensor) before proceeding.

#include <Arduino.h>
#include "freertos/event_groups.h"

EventGroupHandle_t appEvents;
const int WIFI_READY_BIT  = BIT0;
const int SENSOR_READY_BIT= BIT1;

void setup() {
  Serial.begin(115200);
  appEvents = xEventGroupCreate();

  // Simulate async readiness
  xTaskCreate([](void*){
    vTaskDelay(pdMS_TO_TICKS(1500));
    xEventGroupSetBits(appEvents, WIFI_READY_BIT);
    vTaskDelete(NULL);
  }, "wifi", 2048, NULL, 2, NULL);

  xTaskCreate([](void*){
    vTaskDelay(pdMS_TO_TICKS(800));
    xEventGroupSetBits(appEvents, SENSOR_READY_BIT);
    vTaskDelete(NULL);
  }, "sensor", 2048, NULL, 2, NULL);

  // Wait for both bits
  xTaskCreate([](void*){
    EventBits_t bits = xEventGroupWaitBits(
      appEvents, WIFI_READY_BIT | SENSOR_READY_BIT,
      pdFALSE,  /* don't clear */
      pdTRUE,   /* wait for all */
      portMAX_DELAY
    );
    Serial.printf("Ready! bits=0x%02x\n", bits);
    vTaskDelete(NULL);
  }, "gate", 2048, NULL, 3, NULL);
}

void loop() {}

6) Software Timers: Non-Blocking Periodic Work

Use xTimerCreate for periodic or one-shot jobs without dedicating a full task.

#include <Arduino.h>

TimerHandle_t blinkTimer;
const int LED = 2;

void blinkCb(TimerHandle_t) {
  digitalWrite(LED, !digitalRead(LED));
}

void setup() {
  pinMode(LED, OUTPUT);
  blinkTimer = xTimerCreate("blink", pdMS_TO_TICKS(250), pdTRUE, NULL, blinkCb);
  xTimerStart(blinkTimer, 0);
}

void loop() {}

Why it’s useful: frees CPU and stack compared to a dedicated blink task.

7) Task Notifications: Fast 1-to-1 Signal (Lighter than Queues)

Direct-to-task notifications are like super-light binary semaphores.

#include <Arduino.h>

TaskHandle_t workTaskHandle;

void IRAM_ATTR quickISR() {
  BaseType_t xHigher = pdFALSE;
  vTaskNotifyGiveFromISR(workTaskHandle, &xHigher);
  if (xHigher) portYIELD_FROM_ISR();
}

void workTask(void *pv) {
  for (;;) {
    ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // waits, clears on take
    // handle event fast
    Serial.println("Notified!");
  }
}

void setup() {
  Serial.begin(115200);
  xTaskCreate(workTask, "work", 2048, NULL, 3, &workTaskHandle);

  // simulate an interrupt source using a timer
  hw_timer_t *timer = timerBegin(0, 80, true); // 1 us tick
  timerAttachInterrupt(timer, &quickISR, true);
  timerAlarmWrite(timer, 500000, true); // 500ms
  timerAlarmEnable(timer);
}

void loop() {}

8) Producer–Consumer with Queue + Backpressure

Avoid overruns by letting the queue throttle the producer.

#include <Arduino.h>

QueueHandle_t dataQ;

void producer(void *pv) {
  uint16_t sample = 0;
  for (;;) {
    sample++;
    if (xQueueSend(dataQ, &sample, pdMS_TO_TICKS(10)) != pdPASS) {
      // queue full -> dropped (or handle differently)
    }
    vTaskDelay(pdMS_TO_TICKS(5)); // 200 Hz
  }
}

void consumer(void *pv) {
  uint16_t s;
  for (;;) {
    if (xQueueReceive(dataQ, &s, portMAX_DELAY) == pdPASS) {
      // heavy processing
      vTaskDelay(pdMS_TO_TICKS(20)); // slower than producer
      Serial.printf("Processed %u\n", s);
    }
  }
}

void setup() {
  Serial.begin(115200);
  dataQ = xQueueCreate(16, sizeof(uint16_t));
  xTaskCreatePinnedToCore(producer, "prod", 2048, NULL, 2, NULL, 1);
  xTaskCreatePinnedToCore(consumer, "cons", 4096, NULL, 2, NULL, 0);
}

void loop() {}

9) Watchdog-Friendly Yields in Busy Tasks

Long loops should yield to avoid soft WDT resets and keep the system responsive.

#include <Arduino.h>

void heavyTask(void *pv) {
  for (;;) {
    // do chunks of work…
    // ...
    vTaskDelay(1); // yield to scheduler (~1 tick)
  }
}

void setup() {
  xTaskCreate(heavyTask, "heavy", 4096, NULL, 1, NULL);
}

void loop() {}

10) Minimal ESP-IDF Style (for reference)

If you’re on ESP-IDF directly:

// C (ESP-IDF)
void app_main(void) {
  xTaskCreatePinnedToCore(taskA, "taskA", 2048, NULL, 3, NULL, 1);
  xTaskCreatePinnedToCore(taskB, "taskB", 4096, NULL, 2, NULL, 0);
}

APIs are the same FreeRTOS ones; you’ll use ESP-IDF drivers (I2C, ADC, Wi-Fi) instead of Arduino wrappers.

Practical Stack/Perf Tips

  • Start with 2 ~ 4 KB stack per task; raise if you see resets. Use uxTaskGetStackHighWaterMark(NULL) to check headroom.
  • Prefer task notifications over queues for single-bit triggers; they’re faster and lighter.
  • Keep ISRs tiny; do work in tasks.
  • Use vTaskDelayUntil for fixed-rate loops (control systems).
  • Group readiness with Event Groups; single readiness with binary semaphores.

Real-World Use Cases on ESP32

  • Home Automation: Sensor monitoring + Wi-Fi communication + relay control.
  • Industrial IoT: Data acquisition + edge processing + cloud integration.
  • Wearables: Health data collection + Bluetooth communication.

FreeRTOS turns your ESP32 into a powerful multitasking device capable of handling complex, real-time applications. Its lightweight nature, multitasking support, and rich feature set make it perfect for IoT, robotics, and industrial projects.

By starting with simple tasks like LED blinking, you can gradually build more complex systems involving sensors, communication, and user interfaces; all running smoothly on FreeRTOS.

Bibliography

Wednesday, 30 July 2025

The Role of Edge Computing in Building a Carbon-Neutral AI Future

Standard

Artificial Intelligence (AI) is advancing faster than ever, but the price we pay in energy consumption is becoming impossible to ignore. Most AI workloads today rely on massive data centers that consume gigawatts of electricity and release enormous amounts of CO₂. To achieve a truly carbon-neutral AI future, we need smarter solutions and edge computing is leading the way.

Instead of sending all data to the cloud, edge devices like ESP32 microcontrollers, Raspberry Pi Zero, and NVIDIA Jetson Nano process AI tasks locally. These devices use far less power, require minimal cooling, and can even run on renewable energy sources. This shift is not just technical it’s environmental.

Why Edge Computing Powers Green AI

Processing data locally means fewer transmissions, lower bandwidth use, and drastically reduced energy consumption. When combined with renewable energy sources, edge computing creates a carbon-light AI ecosystem.

  • Energy Efficiency: Runs AI models on milliwatts instead of kilowatts.
  • Lower Carbon Footprint: Cuts reliance on high-emission data centers.
  • Reduced E-Waste: Supports longer hardware lifespans.
  • Scalability: Can be deployed everywhere—from remote farms to urban grids.

Real Edge Devices Driving Sustainability

ESP32 – The Low-Power IoT AI Enabler

  • Use Case: Smart irrigation systems analyze soil and weather locally, activating pumps only when needed.
  • Impact: Up to 25% water savings and minimal energy use.

Raspberry Pi Zero 2 W – Affordable AI at the Edge

  • Use Case: Home energy management systems predict consumption and optimize appliance usage.
  • Impact: Reduced household energy waste, contributing to lower emissions.

NVIDIA Jetson Nano – AI Power for Industrial Efficiency

  • Use Case: Real-time defect detection in factories without cloud processing.
  • Impact: Avoids production errors, reduces waste, and cuts energy losses.

Arduino Portenta H7 – Sustainable Industrial IoT

  • Use Case: Water flow monitoring for irrigation and industry, processed directly on the device.
  • Impact: Conserves water while minimizing network and power consumption.

Practical AI Models That Support These Devices

These edge devices rely on optimized AI models that balance performance with power efficiency. Here are real-world models that make edge AI sustainable:

1. MobileNet (TensorFlow Lite)

  • Optimized For: Low-power image classification on ESP32 and Raspberry Pi.
  • Example: Used in smart cameras to detect plant diseases in fields without needing cloud support.

2. YOLOv5 Nano

  • Optimized For: Object detection on Jetson Nano and Raspberry Pi.
  • Example: AI-enabled cameras for waste sorting, improving recycling rates while saving energy.

3. TinyML Anomaly Detection Models

  • Optimized For: Real-time industrial monitoring on microcontrollers.
  • Example: Vibration sensors using TinyML detect machinery faults early, preventing energy waste from breakdowns.

4. SensiML Gesture Recognition

  • Optimized For: ESP32 and Arduino Portenta for local ML processing.
  • Example: Smart wearable devices for energy-efficient gesture control in smart homes.

5. Edge Impulse Environmental Monitoring Models

  • Optimized For: ESP32, Raspberry Pi, and Arduino boards.
  • Example: Tiny ML models track air quality, helping cities optimize pollution control without massive cloud data.

Edge Computing + Renewable Energy = Carbon-Neutral AI

Pairing these devices with solar panels or other renewable energy solutions creates an ecosystem where AI runs with almost zero emissions. Imagine solar-powered AI irrigation, wind-powered edge sensors for smart grids, or battery-efficient wildlife tracking cameras—all contributing to sustainability without burdening the planet.

Why This Approach Works

Unlike traditional AI systems that require huge centralized resources, edge computing keeps computation close to the source, minimizing energy and emissions. When scaled globally, this could cut AI’s carbon footprint dramatically while making AI accessible to communities everywhere.

Edge devices like ESP32, Raspberry Pi Zero, and Jetson Nano show us that we don’t need to sacrifice the planet for progress. When combined with efficient AI models and renewable power, these technologies can help us build a truly carbon-neutral AI future.

Real-World Edge AI Case Studies: Tiny Models Powering Green AI Applications

The combination of edge computing, TinyML, and optimized AI models is already delivering measurable benefits—energy savings, reduced emissions, and smarter automation. Here are five compelling examples that show how devices like ESP32, Raspberry Pi, Jetson Nano, and Arduino boards are driving sustainable AI in the field.

1. ESP32-CAM for Local Object Detection

Use Case: As described in Sensors (2025), an object‑detection model runs directly on an ESP32-CAM module, performing image classification locally over MQTT—for example, detecting people or objects in monitoring scenarios.
Impact: Compared to sending images to the cloud, this setup significantly reduces bandwidth, latency, and energy use—ideal for solar-powered, off-grid deployments.
(MDPI)

2. TinyML Soil Moisture Prediction for Smart Farming

Use Case: A TinyML pipeline on ESP32 predicts daily soil moisture locally using pruned/quantized models, enabling precise irrigation control without cloud reliance.
Impact: This edge-only approach lowers water usage and eliminates transmission energy, making micro-farming both efficient and sustainable.
(IET Research Journals)

3. Jetson Nano for Smart Recycling Bins

Use Case: Researchers built a smart recycling bin using YOLOv4/K210 deployed on Jetson Nano, classifying waste types with 95–96% accuracy while consuming just ~4.7 W.
Impact: Waste sorting efficiency rises, with low power consumption and reduced cloud dependency—helping cities optimize recycling programs.
(arXiv)


4. Leaf Disease Detection on Raspberry Pi

Use Case: In a thermal-imaging study, MobileNetV1/V2 and VGG‑based models were pruned and quantized to run on Raspberry Pi 4B, detecting leaf disease in real time for farmers.
Impact: On-device disease classification was up to 2× faster than GPU inference, with much lower energy use, making crop monitoring more accessible.
(arXiv)

5. Smart Voice Assistants with TinyML in Home Automation

Use Case: A Nature (2025) study showed that voice assistant models on low-power devices (ESPs, wearables, or microcontrollers) can interpret commands and adjust home systems—all without constant internet access.
Impact: This reduces cloud energy costs and supports privacy, while enabling assistive tech in off-grid or low-bandwidth areas.
(Nature)

Why These Case Studies Show Green AI in Action

Feature What It Delivers
Local Inference Reduces need for cloud uploads and data transfers
Low Power Consumption Uses watts or milliwatts, not kilowatts
Efficient Models Uses pruning, quantization, TinyML for edge viability
Real-World Accuracy Models maintain 80–96% accuracy, suitable for tasks
Sustainable Deployment Compatible with solar or battery-powered setups    
These real deployments prove that meaningful Green AI doesn’t need mega‑data centers—it can happen on tiny chips. From smart recycling in cities to sustainable farming systems and safe voice assistants, edge devices enable AI that respects planet and people. Their low energy demand, combined with optimized models, unlock sustainable AI adoption across remote, rural, and resource-constrained environments.

 Bibliography

  • Edge Impulse. (2024). TinyML for IoT and Edge Devices. Retrieved from https://www.edgeimpulse.com
  • Raspberry Pi Foundation. (2025). Raspberry Pi Zero 2 W Applications in AI and IoT. Retrieved from https://www.raspberrypi.com
  • NVIDIA. (2025). Jetson Nano for Edge AI. Retrieved from https://developer.nvidia.com/embedded/jetson-nano
  • Arduino. (2025). Portenta H7: Low Power AI for Industry 4.0. Retrieved from https://www.arduino.cc/pro
  • International Energy Agency. (2025). AI and the Energy Transition. Retrieved from https://www.iea.org
  • Chang, Y.-H., Wu, F.-C., & Lin, H.-W. (2025). Design and Implementation of ESP32-Based Edge Computing for Object Detection. Sensors, 25(6), 1656.(MDPI)
  • Anonymous. (2024). TinyML-based moisture prediction for micro-farming on edge devices.(arXiv)
  • Li, X., & Grammenos, R. (2022). Smart Recycling Bin Using Waste Image Classification at the Edge. arXiv.(arXiv)
  • Silva, P. E. C. da, & Almeida, J. (2024). Leaf Disease Classification via Edge Computing and Thermal Imaging. arXiv.(arXiv)
  • Chittepu, S., Martha, S., & Banik, D. (2025). Empowering Voice Assistants with TinyML for Real‑World Applications. Scientific Reports.(Nature)

Thursday, 17 July 2025

Run AI on ESP32: How to Deploy a Tiny LLM Using Arduino IDE & ESP-IDF (Step-by-Step Guide)

Standard

Introduction

What if I told you that your tiny ESP32 board the same one you use to blink LEDs or log sensor data could run a Language Model like a miniature version of ChatGPT? 

Sounds impossible, right? But it’s not.

Yes, you can run a Local Language Model (LLM) on a microcontroller!


Thanks to an amazing open-source project, you can now run a Tiny LLM (Language Learning Model) on an ESP32-S3 microcontroller. That means real AI inference text generation and storytelling running directly on a chip that costs less than a cup of coffee 

In this blog, I’ll show you how to make that magic happen using both the Arduino IDE (for quick prototyping) and ESP-IDF (for full control and performance). Whether you’re an embedded tinkerer, a hobbyist, or just curious about what’s next in edge AI this is for you.

Ready to bring AI to the edge? Let’s dive in!  

In this blog, you'll learn two ways to run a small LLM on ESP32:

  1. Using Arduino IDE
  2. Using ESP-IDF (Espressif’s official SDK)

Understanding the ESP32-S3 Architecture and Pinout

The ESP32-S3 is a powerful dual-core microcontroller from Espressif, designed for AIoT and edge computing applications. At its heart lies the Xtensa® LX7 dual-core processor running up to 240 MHz, backed by ample on-chip SRAM, cache, and support for external PSRAM—making it uniquely capable of running lightweight AI models like Tiny LLMs. It features integrated Wi-Fi and Bluetooth Low Energy (BLE) radios, multiple I/O peripherals (SPI, I2C, UART, I2S), and even native USB OTG support. The development board includes essential components such as a USB-to-UART bridge, 3.3V LDO regulator, RGB LED, and accessible GPIO pin headers. With buttons for boot and reset, and dual USB ports, the ESP32-S3 board makes flashing firmware and experimenting with peripherals effortless. Its advanced security features like secure boot, flash encryption, and cryptographic accelerators also ensure your edge AI applications stay safe and reliable. All of these capabilities together make the ESP32-S3 a perfect platform to explore and deploy tiny LLMs in real-time, even without the cloud.


What Is This Tiny LLM?

  • Based on the llama2.c model (a minimal C-based transformer).
  • Trained on TinyStories dataset (child-level English content).
  • Supports basic token generation at ~19 tokens/sec.
  • Model Size: ~1MB (fits in ESP32-S3 with 2MB PSRAM).

What You Need?

Item Details
Board ESP32-S3 with PSRAM (e.g., ESP32-S3FH4R2)
Toolchain Arduino IDE or ESP-IDF
Model tinyllama.bin (260K parameters)
Cable USB-C or micro-USB for flashing

Method 1: Using Arduino IDE

Step 1: Install Arduino Core for ESP32

  • Open Arduino IDE.
  • Go to Preferences > Additional Board URLs

Add:

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

  • Go to Board Manager, search and install ESP32 by Espressif.

Step 2: Download the Code

The current project is in ESP-IDF format. For Arduino IDE, you can adapt it or wait for an Arduino port (coming soon). Meanwhile, here's a simple structure.

  • Create a new sketch: esp32_llm_arduino.ino
  • Add this example logic:

#include <Arduino.h> #include "tinyllama.h" // Assume converted C array of model weights void setup() { Serial.begin(115200); delay(1000); Serial.println("Starting Tiny LLM..."); // Initialize model llama_init(); } void loop() { String prompt = "Once upon a time"; String result = llama_generate(prompt.c_str(), 100); Serial.println(result); delay(10000); // Wait before next run }
    

Note: You'll need to convert the model weights (tinyllama.bin) into a C header file or read from PSRAM/flash.

Step 3: Upload and Run

  • Select your ESP32 board.
  • Upload the code.
  • Open Serial Monitor at 115200 baud.
  • You’ll see the model generate a few simple tokens based on your prompt!

Method 2: Using ESP-IDF

Step 1: Install ESP-IDF

Follow the official guide: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/

Step 2: Clone the Repo


git clone https://github.com/DaveBben/esp32-llm.git cd esp32-llm

Step 3: Build the Project


idf.py set-target esp32s3 idf.py menuconfig # Optional: Set serial port or PSRAM settings idf.py build

Step 4: Flash to Board


idf.py -p /dev/ttyUSB0 flash idf.py monitor

Output:

You’ll see generated text like:


Example Prompts and Outputs

  1. Prompt: Once upon a time
    Once upon a time there was a man who loved to build robots in his tiny shed.

  2. Prompt: The sky turned orange and
    The sky turned orange and the birds flew home to tell stories of the wind.

  3. Prompt: In a small village, a girl
    In a small village, a girl found a talking Cow who knew the future.

  4. Prompt: He opened the old book and
    He opened the old book and saw a map that led to a secret forest.

  5. Prompt: Today is a good day to
    Today is a good day to dance, to smile, and to chase butterflies.

  6. Prompt: My robot friend told me
    My robot friend told me that humans dream of stars and pancakes.

  7. Prompt: The magic door appeared when
    The magic door appeared when the moon touched the lake.

  8. Prompt: Every night, the owl would
    Every night, the owl would tell bedtime stories to the trees.

  9. Prompt: Under the bed was
    Under the bed was a box full of laughter and forgotten dreams.

  10. Prompt: She looked into the mirror and
    She looked into the mirror and saw a future full of colors and songs.

Tips to Improve

  • Use ESP32-S3 with 2MB PSRAM.
  • Enable dual-core execution.
  • Use ESP-DSP for vector operations.
  • Optimize model size using quantization (optional).

Demo Video

See it in action:
YouTube: Tiny LLM Running on ESP32-S3

 Why Would You Do This?

While it's not practical for production AI, it proves:

  • AI inference can run on constrained hardware
  • Great for education, demos, and edge experiments
  • Future of embedded AI is exciting!


Link Description
esp32-llm Main GitHub repo
llama2.c Original LLM C implementation
ESP-IDF Official ESP32 SDK
TinyStories Dataset Dataset used for training

Running an LLM on an ESP32-S3 is no longer a fantasy, it’s here. Whether you're an embedded dev, AI enthusiast, or maker, this project shows what happens when edge meets intelligence.

Bibliography / References

DaveBben / esp32-llm (GitHub Repository)
A working implementation of a Tiny LLM on ESP32-S3 with ESP-IDF
URL: https://github.com/DaveBben/esp32-llm
Karpathy / llama2.c (GitHub Repository)
A minimal, educational C implementation of LLaMA2-style transformers
URL: https://github.com/karpathy/llama2.c
TinyStories Dataset – HuggingFace
A synthetic dataset used to train small LLMs for children’s story generation
URL: https://huggingface.co/datasets/roneneldan/TinyStories
Espressif ESP-IDF Official Documentation
The official SDK and development guide for ESP32, ESP32-S2, ESP32-S3 and ESP32-C3
URL: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/
Hackaday – Large Language Models on Small Computers
A blog exploring the feasibility and novelty of running LLMs on microcontrollers
URL: https://hackaday.com/2024/09/07/large-language-models-on-small-computers
YouTube – Running an LLM on ESP32 by DaveBben
A real-time demonstration of Tiny LLM inference running on the ESP32-S3 board
URL: https://www.youtube.com/watch?v=E6E_KrfyWFQ

Arduino ESP32 Board Support Package
Arduino core for ESP32 microcontrollers by Espressif
URL: https://github.com/espressif/arduino-esp32

Image Links:

https://www.elprocus.com/wp-content/uploads/ESP32-S3-Development-Board-Hardware.jpg

https://krishworkstech.com/wp-content/uploads/2024/11/Group-1000006441-1536x1156.jpg

https://www.electronics-lab.com/wp-content/uploads/2023/01/esp32-s3-block-diagram-1.png

Thursday, 1 July 2021

ESP32: The Ultimate Guide to Features, Applications, and Projects

Standard
 


ESP32

What is ESP32:

ESP32 is a system on a chip(SoC) that integrates the following features:

  • Wi-Fi (2.4 GHz band):
  • Bluetooth
  • Dual high performance cores
  • Ultra Low Power co-processor
  • Multiple peripherals

Powered by 40 nm technology, ESP32 provides a robust, highly integrated platform, which helps meet the continuous demands for efficient power usage, compact design, security, high performance, and reliability.

Espressif provides basic hardware and software resources to help application developers realize their ideas using the ESP32 series hardware. The software development framework by Espressif is intended for development of Internet-of-Things (IoT) applications with Wi-Fi, Bluetooth, power management and several other system features.

Features:

Following block diagram explains all inbuilt features of  ESP32
Ultra­Low­Power Solution ESP32 is designed for mobile, wearable electronics, and Internet-of-Things (IoT) applications. It features all the state-of-the-art characteristics of low-power chips, including fine-grained clock gating, multiple power modes, and dynamic power scaling. For instance, in a low-power IoT sensor hub application scenario, ESP32 is woken up periodically and only when a specified condition is detected. Low-duty cycle is used to minimize the amount of energy that the chip expends. The output of the power amplifier is also adjustable, thus contributing to an optimal trade-off between communication range, data rate and power consumption.

Wi­Fi Key Features :
  • 802.11 b/g/n
  • 802.11 n (2.4 GHz), up to 150 Mbps
  • WMM 
  • TX/RX A-MPDU, RX A-MSDU 
  • Defragmentation 
  • Automatic Beacon monitoring (hardware TSF) 
  • 4 × virtual Wi-Fi interfaces 
  • Simultaneous support for Infrastructure Station, SoftAP, and Promiscuous modes 
  • Antenna diversity

BT Key Features :
  • Compliant with Bluetooth v4.2 BR/EDR and BLE specifications 
  • Class-1, class-2 and class-3 transmitter without external power amplifier 
  • Enhanced Power Control 
  • +12 dBm transmitting power 
  • NZIF receiver with –94 dBm BLE sensitivity 
  • Adaptive Frequency Hopping (AFH) 
  • Standard HCI based on SDIO/SPI/UART 
  • High-speed UART HCI, up to 4 Mbps 
  • Bluetooth 4.2 BR/EDR BLE dual mode controller 
  • Synchronous Connection-Oriented/Extended (SCO/eSCO) 
  • CVSD and SBC for audio codec 
  • Bluetooth Piconet and Scatternet 
  • Multi-connections in Classic BT and BLE 
  • Simultaneous advertising and scanning
CPU and Memory :
  • Xtensa® single-/dual-core 32-bit LX6 microprocessor(s), up to 600 MIPS (200 MIPS for ESP32-S0WD/ESP32-U4WDH, 400 MIPS for ESP32-D2WD) 
  • 448 KB ROM 
  • 520 KB SRAM 
  • 16 KB SRAM in RTC 
  • QSPI supports multiple flash/SRAM chips
Clocks and Timers :

  • Internal 8 MHz oscillator with calibration 
  • Internal RC oscillator with calibration 
  • External 2 MHz ~ 60 MHz crystal oscillator (40 MHz only for Wi-Fi/BT functionality)
  • External 32 kHz crystal oscillator for RTC with calibration 
  • Two timer groups, including 2 × 64-bit timers and 1 × main watchdog in each group 
  • One RTC timer
  • RTC watchdog

Advanced Peripheral Interfaces :
  • 34 × programmable GPIOs 
  • 12-bit SAR ADC up to 18 channels 
  • 2 × 8-bit DAC 
  • 10 × touch sensors 
  • 4 × SPI 
  • 2 × I²S 
  • 2 × I²C 
  • 3 × UART 
  • 1 host (SD/eMMC/SDIO) 
  • 1 slave (SDIO/SPI) 
  • Ethernet MAC interface with dedicated DMA and IEEE 1588 support 
  • Two-Wire Automotive Interface (TWAI®, compatible with ISO11898-1) 
  • IR (TX/RX) 
  • Motor PWM 
  • LED PWM up to 16 channels 
  • Hall sensor

Secure boot :
  • Flash encryption 
  • 1024-bit OTP, up to 768-bit for customers 
  • Cryptographic hardware acceleration: – AES – Hash (SHA-2) 
  • RSA – ECC – Random Number Generator (RNG)


Applications (A Non­exhaustive List) :

  • Generic Low-power IoT Sensor Hub 
  • Generic Low-power IoT Data Loggers 
  • Cameras for Video Streaming
  • Over-the-top (OTT) Devices 
  • Speech Recognition
  • Image Recognition 
  • Mesh Network 
  • Home Automation – Light control – Smart plugs – Smart door locks 
  • Smart Building – Smart lighting – Energy monitoring 
  • Industrial Automation – Industrial wireless control – Industrial robotics 
  • Smart Agriculture – Smart greenhouses – Smart irrigation – Agriculture robotics 
  • Audio Applications – Internet music players – Live streaming devices – Internet radio players – Audio headsets 
  • Health Care Applications – Health monitoring – Baby monitors 
  • Wi-Fi-enabled Toys – Remote control toys – Proximity sensing toys – Educational toys 
  • Wearable Electronics – Smart watches – Smart bracelets 
  • Retail & Catering Applications – POS machines – Service robots

Types & Specifications:

The ESP32 series of chips includes follwing variants...

  • ESP32-D0WD-V3 
  • ESP32-D0WDQ6-V3 
  • ESP32-D0WD
  • ESP32-D0WDQ6 
  • ESP32-D2WD E
  • SP32-S0WD 
  • ESP32-U4WDH
ESP32 WROOM PINOUT:






For More Updates...Please Follow: 

For ESp32 based Development Board Variants and market price:

Firmware and Types:

ESP32 Supports many firmwares and Programming languages. It does matter for a developpper to choose right firmware before developming any product. Right firmware and supported libraries are the main building blocks for developing  a good product.

Following table represents the Firmware Types Programming Language support with Links..

Programming Language Supports:

  1. Embeddded C,C++
  2. Python
    For More Reference:

IDEs:

1. VS Code


 

2. Arduino IDE


RTOS:



Power Consumption and Modes:

 ESP32 offers 5 configurable power modes. As per the power requirement, the chip can switch between different power modes. The modes are:

  • Active Mode
  • Modem Sleep Mode
  • Light Sleep Mode

  • Deep Sleep Mode
  • Hibernation Mode




Sample Codes, Experiments and Example:

LED Blinking Code:

/*
 * ON Board LED GPIO 2
 */

#define LED 2

void setup() {
  // Set pin mode
  pinMode(LED,OUTPUT);
}

void loop() {
  delay(500);
  digitalWrite(LED,HIGH);
  delay(500);
  digitalWrite(LED,LOW);
}

O/P:



Wifi Station Code:


#include <WiFi.h>
#include <WebServer.h>

/* Put your SSID & Password */
const char* ssid = "ESP32";  // Enter SSID here
const char* password = "12345678";  //Enter Password here

/* Put IP Address details */
IPAddress local_ip(192,168,1,1);
IPAddress gateway(192,168,1,1);
IPAddress subnet(255,255,255,0);

WebServer server(80);

uint8_t LED1pin = 4;
bool LED1status = LOW;

uint8_t LED2pin = 5;
bool LED2status = LOW;

void setup() {
  Serial.begin(115200);
  pinMode(LED1pin, OUTPUT);
  pinMode(LED2pin, OUTPUT);

  WiFi.softAP(ssid, password);
  WiFi.softAPConfig(local_ip, gateway, subnet);
  delay(100);
  
  server.on("/", handle_OnConnect);
  server.on("/led1on", handle_led1on);
  server.on("/led1off", handle_led1off);
  server.on("/led2on", handle_led2on);
  server.on("/led2off", handle_led2off);
  server.onNotFound(handle_NotFound);
  
  server.begin();
  Serial.println("HTTP server started");
}
void loop() {
  server.handleClient();
  if(LED1status)
  {digitalWrite(LED1pin, HIGH);}
  else
  {digitalWrite(LED1pin, LOW);}
  
  if(LED2status)
  {digitalWrite(LED2pin, HIGH);}
  else
  {digitalWrite(LED2pin, LOW);}
}

void handle_OnConnect() {
  LED1status = LOW;
  LED2status = LOW;
  Serial.println("GPIO4 Status: OFF | GPIO5 Status: OFF");
  server.send(200, "text/html", SendHTML(LED1status,LED2status)); 
}

void handle_led1on() {
  LED1status = HIGH;
  Serial.println("GPIO4 Status: ON");
  server.send(200, "text/html", SendHTML(true,LED2status)); 
}

void handle_led1off() {
  LED1status = LOW;
  Serial.println("GPIO4 Status: OFF");
  server.send(200, "text/html", SendHTML(false,LED2status)); 
}

void handle_led2on() {
  LED2status = HIGH;
  Serial.println("GPIO5 Status: ON");
  server.send(200, "text/html", SendHTML(LED1status,true)); 
}

void handle_led2off() {
  LED2status = LOW;
  Serial.println("GPIO5 Status: OFF");
  server.send(200, "text/html", SendHTML(LED1status,false)); 
}

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

String SendHTML(uint8_t led1stat,uint8_t led2stat){
  String ptr = "<!DOCTYPE html> <html>\n";
  ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr +="<title>LED Control</title>\n";
  ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n";
  ptr +=".button {display: block;width: 80px;background-color: #3498db;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n";
  ptr +=".button-on {background-color: #3498db;}\n";
  ptr +=".button-on:active {background-color: #2980b9;}\n";
  ptr +=".button-off {background-color: #34495e;}\n";
  ptr +=".button-off:active {background-color: #2c3e50;}\n";
  ptr +="p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
  ptr +="</style>\n";
  ptr +="</head>\n";
  ptr +="<body>\n";
  ptr +="<h1>ESP32 Web Server</h1>\n";
  ptr +="<h3>Using Access Point(AP) Mode</h3>\n";
  
   if(led1stat)
  {ptr +="<p>LED1 Status: ON</p><a class=\"button button-off\" href=\"/led1off\">OFF</a>\n";}
  else
  {ptr +="<p>LED1 Status: OFF</p><a class=\"button button-on\" href=\"/led1on\">ON</a>\n";}

  if(led2stat)
  {ptr +="<p>LED2 Status: ON</p><a class=\"button button-off\" href=\"/led2off\">OFF</a>\n";}
  else
  {ptr +="<p>LED2 Status: OFF</p><a class=\"button button-on\" href=\"/led2on\">ON</a>\n";}

  ptr +="</body>\n";
  ptr +="</html>\n";
  return ptr;
}

O/P:



WiFi Client Code:


WiFi OLED DIsplay(0.96inch I2C):

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

Adafruit_SSD1306 display(-1);

void setup()   
{                
	// initialize with the I2C addr 0x3C
	display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  

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

	// Display Text
	display.setTextSize(1);
	display.setTextColor(WHITE);
	display.setCursor(0,28);
	display.println("Hello world!");
	display.display();
	delay(2000);
	display.clearDisplay();

	// Display Inverted Text
	display.setTextColor(BLACK, WHITE); // 'inverted' text
	display.setCursor(0,28);
	display.println("Hello world!");
	display.display();
	delay(2000);
	display.clearDisplay();

	// Changing Font Size
	display.setTextColor(WHITE);
	display.setCursor(0,24);
	display.setTextSize(2);
	display.println("Hello!");
	display.display();
	delay(2000);
	display.clearDisplay();

	// Display Numbers
	display.setTextSize(1);
	display.setCursor(0,28);
	display.println(123456789);
	display.display();
	delay(2000);
	display.clearDisplay();

	// Specifying Base For Numbers
	display.setCursor(0,28);
	display.print("0x"); display.print(0xFF, HEX); 
	display.print("(HEX) = ");
	display.print(0xFF, DEC);
	display.println("(DEC)"); 
	display.display();
	delay(2000);
	display.clearDisplay();

	// Display ASCII Characters
	display.setCursor(0,24);
	display.setTextSize(2);
	display.write(3);
	display.display();
	delay(2000);
	display.clearDisplay();

	// Scroll full screen
	display.setCursor(0,0);
	display.setTextSize(1);
	display.println("Full");
	display.println("screen");
	display.println("scrolling!");
	display.display();
	display.startscrollright(0x00, 0x07);
	delay(2000);
	display.stopscroll();
	delay(1000);
	display.startscrollleft(0x00, 0x07);
	delay(2000);
	display.stopscroll();
	delay(1000);    
	display.startscrolldiagright(0x00, 0x07);
	delay(2000);
	display.startscrolldiagleft(0x00, 0x07);
	delay(2000);
	display.stopscroll();
	display.clearDisplay();

	// Scroll part of the screen
	display.setCursor(0,0);
	display.setTextSize(1);
	display.println("Scroll");
	display.println("some part");
	display.println("of the screen.");
	display.display();
	display.startscrollright(0x00, 0x00);
}

void loop() {}


O/P:

.....Many More



BLE Code:


#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>


#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

void setup() {
  Serial.begin(115200);
  Serial.println("Starting BLE work!");

  BLEDevice::init("Long name works now");
  BLEServer *pServer = BLEDevice::createServer();
  BLEService *pService = pServer->createService(SERVICE_UUID);
  BLECharacteristic *pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE
                                       );

  pCharacteristic->setValue("Hello World says Neil");
  pService->start();
  // BLEAdvertising *pAdvertising = pServer->getAdvertising();  // this still is working for backward compatibility
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);  // functions that help with iPhone connections issue
  pAdvertising->setMinPreferred(0x12);
  BLEDevice::startAdvertising();
  Serial.println("Characteristic defined! Now you can read it in your phone!");
}

void loop() {
  // put your main code here, to run repeatedly:
  delay(2000);
}

OTA Update Code and Support:

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

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

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");

  // 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());
}

void loop() {
  ArduinoOTA.handle();
}

For More in Details: htpts://lastminuteengineers.com/esp32-ota-updates-arduino-ide/...


Bibliography:

https://circuits4you.com/2018/02/02/esp32-led-blink-example/

https://lastminuteengineers.com/creating-esp32-web-server-arduino-ide/

https://randomnerdtutorials.com/esp32-bluetooth-low-energy-ble-arduino-ide/

https://www.espressif.com/en/products/modules

https://www.youtube.com/

https://www.google.com/

https://www.instructables.com/

https://stackoverflow.com/

http://esp32.net/

https://en.wikipedia.org/wiki/ESP32

https://aws.amazon.com/