This week, I worked on embedded programming by simulating a XIAO ESP32C6 and a Raspberry Pi Pico using Wokwi, an online microcontroller simulator. Although I also worked with the physical ESP32C6, this documentation focuses only on the simulation.
In our group assignment, we explored various microcontroller architectures available at FabLab Puebla. We examined their datasheets to understand their performance characteristics and development workflows. Our findings are documented on the group assignment page: Embedded Programming.
Below is a summary of the architectures we analyzed:
Architecture | Pros | Cons | Main Applications | Common Microcontrollers |
---|---|---|---|---|
AVR (8-bit) | Low power consumption, Simple to program, Good community support | Limited processing power, Fewer advanced peripherals | Hobbyist projects, Simple embedded systems | ATmega328, ATmega32u4, ATtiny45, ATtiny44, ATtiny412, ATtiny1614 |
ARM Cortex-M0+ | Energy efficient, Wide range of tools, High performance for 32-bit | More complex to program than AVR, Higher cost | Low power applications, IoT devices, Wearables | SAMD21, RP2040 |
RISC-V (32-bit) | Open source architecture, Scalable, Flexible | Less mature ecosystem, Fewer ready-to-use libraries | IoT, Educational purposes, Research projects | ESP-32C3 |
Xtensa (LX6/LX7) | Highly customizable, Good for DSP applications, Integrated Wi-Fi and Bluetooth | Proprietary, Requires licensing for commercial use | Connected devices, Smart home, Audio processing | ESP32-S3, ESP32-WROOM |
Tensilica L106 (32-bit) | Low cost, Integrated Wi-Fi, Good SDK support | Single core, Less powerful than newer ESP32 models | IoT, Budget Wi-Fi projects | ESP8266 |
ARM Cortex-M4F | High performance, Floating point unit, Energy efficient | More expensive than simpler cores, Complex for beginners | High-performance embedded systems, Industrial control, Advanced IoT devices | Nucleo-L432KC, nRF52840 |
The XIAO ESP32C6 is a compact, powerful microcontroller with Wi-Fi 6, Bluetooth 5, and Zigbee, making it ideal for IoT and embedded applications. More technical details can be found on the official Seeed Studio Wiki.
Feature | Description |
---|---|
Microcontroller | ESP32-C6 (RISC-V 160MHz CPU) - Efficient processing for embedded applications. |
Wireless Connectivity | Supports Wi-Fi 6 (802.11ax), Bluetooth 5 (LE), and Zigbee for IoT communication. |
Operating Voltage | 3.3V logic level, powered via USB-C. |
GPIO Pins | 8 usable GPIOs (D0-D7), including ADC, I2C, SPI, and UART. |
PWM & Timers | Capable of hardware PWM for LED control and precision timing. |
Onboard LED | One built-in user LED on pin D10 for debugging and testing. |
RGB Matrix Control | Uses GPIO2 with WS2812B LEDs for addressable color control. |
Programming Methods | Supports Arduino, MicroPython, and ESP-IDF for low-level embedded programming. |
The Raspberry Pi Pico RP2040 is a low-cost, high-performance microcontroller board with a dual-core ARM Cortex-M0+ processor. It is designed for embedded applications and supports MicroPython, C, and Arduino. More technical details can be found on the official Raspberry Pi Documentation.
Feature | Description |
---|---|
Microcontroller | RP2040 (Dual-core ARM Cortex-M0+, 133MHz) - Powerful and efficient for embedded tasks. |
Memory | 264KB SRAM, external 2MB Flash storage. |
Operating Voltage | 3.3V logic level, powered via USB micro-B. |
GPIO Pins | 26 multifunctional GPIOs, including ADC, PWM, SPI, I2C, and UART. |
PWM & Timers | 16 PWM channels for precise timing and LED control. |
Onboard LED | One built-in user LED on GPIO25 for debugging and testing. |
USB Support | Native USB 1.1 host/device functionality. |
Programming Methods | Supports C/C++, MicroPython, Arduino, and CircuitPython. |
Since Wokwi does not allow actual Wi-Fi connections, this program simulates a Wi-Fi connection process. The red LED blinks while attempting to "connect," and then either the green LED turns on (successful connection) or the red LED stays on (failed connection).
Below is the Arduino code used to simulate the Wi-Fi connection. The LEDs are controlled based on the simulated connection status.
#define RED_LED D2 // GPIO for red LED (Wi-Fi failed)
#define GREEN_LED D3 // GPIO for green LED (Wi-Fi connected)
void setup() {
Serial.begin(115200); // Start Serial Monitor for debugging
pinMode(RED_LED, OUTPUT); // Set the red LED as an output
pinMode(GREEN_LED, OUTPUT); // Set the green LED as an output
digitalWrite(RED_LED, LOW); // Ensure red LED starts OFF
digitalWrite(GREEN_LED, LOW); // Ensure green LED starts OFF
Serial.println("\nSimulating Wi-Fi Connection...");
// Simulating Wi-Fi connection attempts by blinking the red LED
for (int i = 0; i < 10; i++) {
digitalWrite(RED_LED, HIGH); // Turn ON red LED (trying to connect)
delay(300); // Wait 300ms
digitalWrite(RED_LED, LOW); // Turn OFF red LED
delay(300); // Wait 300ms
Serial.print("."); // Print progress dots in Serial Monitor
}
// Simulate a random connection success or failure
bool wifiConnected = random(0, 2); // Randomly pick 0 (fail) or 1 (success)
if (wifiConnected) {
Serial.println("\n Simulated Wi-Fi Connected!");
digitalWrite(GREEN_LED, HIGH); // Turn ON green LED
digitalWrite(RED_LED, LOW); // Turn OFF red LED
} else {
Serial.println("\n Simulated Wi-Fi Connection Failed.");
digitalWrite(GREEN_LED, LOW); // Turn OFF green LED
digitalWrite(RED_LED, HIGH); // Keep red LED ON
}
}
void loop() {
}
To simulate this in Wokwi, follow these steps:
The initial setup for this project was inspired by work from Uri Shaked (Urish), the creator of Wokwi. His Wokwi platform has made it easy to test embedded systems in a web-based environment. You can explore more of his amazing projects at Wokwi Project Library.
In this simulation, the Raspberry Pi Pico controls a NeoPixel ring (WS2812B) using MicroPython. The animation consists of the following effects:
Below is the MicroPython code used to create this effect. The animation is controlled based on timing and color logic.
import time
from neopixel import Neopixel
# Configuration for NeoPixel Ring
NUM_PIXELS = 16 # Number of LEDs in the ring
PIN = 6 # GPIO pin where the NeoPixel ring is connected
pixels = Neopixel(NUM_PIXELS, 0, PIN, "GRB")
# List of Colors (RGB Format)
colors = [
(182, 228, 48), # Yellow-Green
(66, 209, 224), # Cyan
(255, 50, 50), # Red
(255, 140, 0), # Orange
(128, 0, 255), # Purple
(50, 255, 50) # Green
]
special_color = (66, 209, 224) # Cyan for blink effect
fade_color = (255, 255, 255) # White before fading
# Initial Settings
pixel_index = 0
color_index = 0
direction = 1 # 1 for forward, -1 for backward
brightness = 50 # Adjust brightness (0-255)
cycle_count = 0 # Counts the forward-backward cycles
def fade_out():
""" Fades all LEDs to black smoothly """
for b in range(100, -1, -5): # Decrease brightness in steps of 5
pixels.brightness(b)
pixels.fill(fade_color)
pixels.show()
time.sleep(0.05) # Small delay for smooth fading
pixels.fill((0, 0, 0)) # Ensure all LEDs are off
pixels.show()
while True:
pixels.brightness(brightness)
# Set color for current pixel
pixels.set_pixel(pixel_index, colors[color_index])
pixels.show()
# Move pixel in forward or reverse direction
pixel_index += direction
# Change direction at the edges
if pixel_index == NUM_PIXELS - 1 or pixel_index == 0:
direction *= -1 # Reverse direction
color_index = (color_index + 1) % len(colors) # Cycle colors
cycle_count += 1 # Count cycles
# After 2 full back-and-forth cycles, trigger special effect
if cycle_count == 4:
cycle_count = 0 # Reset cycle counter
# Blink all LEDs quickly in cyan
for _ in range(6):
pixels.fill(special_color)
pixels.show()
time.sleep(0.1)
pixels.fill((0, 0, 0))
pixels.show()
time.sleep(0.1)
# Turn all LEDs solid white for 5 seconds
pixels.fill(fade_color)
pixels.show()
time.sleep(5)
# Fade to black
fade_out()
time.sleep(0.1) # Delay for smooth animation
To simulate this in Wokwi, follow these steps:
The initial setup for this project was inspired by work from Uri Shaked (Urish), the creator of Wokwi. His Wokwi platform has made it easy to test embedded systems in a web-based environment. You can explore more of his amazing projects at Wokwi Project Library.
In this section, we compare the MicroPython and Arduino (C++) approaches for programming microcontrollers. Both have unique advantages and limitations, depending on the application. Below is a table summarizing key differences followed by an explanation of how each language handles operations.
Feature | MicroPython | Arduino (C++) |
---|---|---|
Language | Python (MicroPython) | C++ (Arduino Framework) |
Ease of Use | More beginner-friendly, easier syntax | More complex, requires memory management |
Execution Speed | Slower (interpreted) | Faster (compiled directly to machine code) |
Memory Usage | Higher (due to Python interpreter overhead) | Lower (more optimized for embedded systems) |
Hardware Control | Higher-level, easier to implement | Lower-level, gives more control over hardware |
Libraries | Many built-in Python libraries (simple implementation) | Extensive Arduino library ecosystem (optimized for performance) |
Real-Time Applications | Not ideal for real-time processing (due to Python overhead) | Better for real-time and timing-critical tasks |
Best For | IoT, quick prototyping, beginner projects | Robotics, performance-critical applications |
Although both codes perform similar tasks (controlling LEDs), the way they handle operations is different. Let's look at key differences:
In MicroPython, variables are dynamically typed:
NUM_PIXELS = 16 # No need to declare type
brightness = 50 # Simple variable assignment
In Arduino (C++), variables need explicit data types:
#define RED_LED D2 // Defining constants
int brightness = 50; // Explicitly declaring an integer variable
MicroPython uses `while True:` to run an infinite loop:
while True:
pixels.brightness(brightness)
pixels.show()
time.sleep(0.1)
Arduino uses `loop()` for continuous execution:
void loop() {
digitalWrite(RED_LED, HIGH);
delay(500);
digitalWrite(RED_LED, LOW);
delay(500);
}
In MicroPython, controlling NeoPixels requires a library and is done with:
pixels.set_pixel(pixel_index, colors[color_index])
pixels.show()
In Arduino, controlling LEDs is done with:
digitalWrite(RED_LED, HIGH);
MicroPython is great for rapid prototyping, beginner-friendly development, and IoT projects. Arduino (C++) is better for real-time, performance-critical, and low-level hardware control. The choice depends on the project requirements!
Initially, I planned to create a real hardware mini-project, but after experimenting with Wokwi, I realized that a simulated environment provides a quick and efficient way to test embedded programs. This project uses an ESP32-C3 microcontroller and a LED Bar Graph to display live Air Quality Index (AQI) data from my weather station. Instead of physically wiring components, Wokwi allows me to prototype quickly in a virtual space.
The ESP32-C3 connects to Wi-Fi, fetches AQI data via an API request, prints the values to the serial monitor, and displays them using a color-coded LED bar graph.
Instead of Arduino (C++), I chose MicroPython because:
Component | Description |
---|---|
ESP32-C3 Mini | A compact, low-power microcontroller with Wi-Fi & Bluetooth, perfect for IoT projects. ESP32-C3 Datasheet |
Wokwi LED Bar Graph | A 10-segment LED display used to visualize AQI levels using color codes. Wokwi LED Bar Graph |
The ESP32-C3 connects to Wi-Fi using the network
module.
It automatically connects to the Wokwi-GUEST network.
import network
import time
SSID = "Wokwi-GUEST"
PASSWORD = ""
wifi = network.WLAN(network.STA_IF)
wifi.active(True)
wifi.connect(SSID, PASSWORD)
print("Connecting to Wi-Fi...")
while not wifi.isconnected():
time.sleep(0.5)
print("Connected! IP Address:", wifi.ifconfig()[0])
I have a weather station & air quality monitoring system on the roof. This project uses its API to retrieve real-time AQI (Air Quality Index).
The API returns a JSON response, which we parse to extract the latest AQI reading.
import urequests
import json
API_URL = "http://smXXxxXty.sixxXXcmx.com/SmXXxxXXtyAPI/GetData"
TOKEN = "XXXXxxxXXXxxxxxXXXxXXxxX"
SENSOR_ID = 1001
response = urequests.get(f"{API_URL}?token={TOKEN}&idSensor={SENSOR_ID}&dtStart=2025-02-17T08:00:00.000Z&dtEnd=2025-02-17T09:00:00.000Z")
data = response.json()
response.close()
# Extract AQI Value
last_record = data[-1]
nested_json = json.loads(last_record["Data"])
aqi_value = int(nested_json.get("Data", "0"))
print("Last AQI Value:", aqi_value)
After fetching the AQI data, we print it in the serial monitor.
print("Last AQI Value:", aqi_value)
The LED bar graph represents the AQI value using color-coded segments:
AQI Level | Color | LEDs Lit |
---|---|---|
0 - 50 | Green | LED 1 & 2 |
51 - 100 | Yellow | LED 3 & 4 |
101 - 150 | Orange | LED 5 & 6 |
151 - 200 | Red | LED 7 & 8 |
from machine import Pin
LED_PINS = [2, 3, 4, 5, 6, 7, 8, 9]
leds = [Pin(pin, Pin.OUT) for pin in LED_PINS]
def update_led_bar(aqi):
for led in leds:
led.value(0)
if 0 <= aqi <= 50:
leds[0].value(1)
leds[1].value(1)
elif 51 <= aqi <= 100:
leds[2].value(1)
leds[3].value(1)
elif 101 <= aqi <= 150:
leds[4].value(1)
leds[5].value(1)
elif 151 <= aqi <= 200:
leds[6].value(1)
leds[7].value(1)
update_led_bar(aqi_value)
This project successfully simulates an IoT air quality monitoring system in Wokwi. It demonstrates how an ESP32-C3 can connect to Wi-Fi, fetch real-world data via an API, and display the results using LEDs.
You can download the full MicroPython script used in this project:
import network
import time
import urequests
import json
from machine import Pin
# Wi-Fi Configuration
SSID = "Wokwi-GUEST"
PASSWORD = ""
# API Configuration
API_URL = "http://smaxxXXX.sidxxX.com/SmaxxxXXyAPI/GetData"
TOKEN = "XXXXxxxXXXXxxxxXXXXxxxx"
SENSOR_ID = 1001
# LED Bar Graph Pins
LED_PINS = [2, 3, 4, 5, 6, 7, 8, 9]
leds = [Pin(pin, Pin.OUT) for pin in LED_PINS]
# Connect to Wi-Fi
wifi = network.WLAN(network.STA_IF)
wifi.active(True)
wifi.connect(SSID, PASSWORD)
print("Connecting to Wi-Fi...")
while not wifi.isconnected():
time.sleep(0.5)
print("Connected! IP Address:", wifi.ifconfig()[0])
# Fetch AQI Data
response = urequests.get(f"{API_URL}?token={TOKEN}&idSensor={SENSOR_ID}&dtStart=2025-02-17T08:00:00.000Z&dtEnd=2025-02-17T09:00:00.000Z")
data = response.json()
response.close()
# Extract AQI Value
last_record = data[-1]
nested_json = json.loads(last_record["Data"])
aqi_value = int(nested_json.get("Data", "0"))
print("Last AQI Value:", aqi_value)
# Display AQI with LED Bar Graph
def update_led_bar(aqi):
for led in leds:
led.value(0)
if 0 <= aqi <= 50:
leds[0].value(1)
leds[1].value(1)
elif 51 <= aqi <= 100:
leds[2].value(1)
leds[3].value(1)
elif 101 <= aqi <= 150:
leds[4].value(1)
leds[5].value(1)
elif 151 <= aqi <= 200:
leds[6].value(1)
leds[7].value(1)
update_led_bar(aqi_value)
The goal of this project was to take embedded programming beyond simulation and implement it on real hardware. The XIAO ESP32C6 microcontroller and a 6x10 RGB LED matrix were used to visually indicate the Wi-Fi connection status. The LEDs blink red while the device attempts to connect and turn solid green once connected.
This project demonstrates the transition from software-based simulations, such as those in Wokwi, to real-world embedded systems. By working with physical components, challenges like wiring, power management, and timing accuracy become crucial factors.
Instead of writing the entire program manually, an AI-assisted approach was taken to streamline the development process. By providing a detailed prompt specifying the behavior of the system, AI was able to generate structured, optimized code. This approach allowed for multiple iterations and refinements, ensuring that the final result functioned as intended.
AI was particularly useful in structuring the logic for handling Wi-Fi connectivity and LED control efficiently. The generated code was further modified and tested to ensure smooth operation.
The following prompt was used to generate the final code using AI. It specifies the required behavior, logic, and optimization steps.
"Write an Arduino sketch for a XIAO ESP32C6 that connects to Wi-Fi and controls a 6x10 RGB LED matrix using the Adafruit NeoPixel library. Follow these requirements:
1 Wi-Fi Connection:
- Connect to Wi-Fi using SSID: "Primavera25"
- Password: "Ib3r02025ui@"
- Print Wi-Fi status in the serial monitor.
2 RGB LED Matrix (60 LEDs, WS2812B type):
- Uses GPIO2 (D0) for data input.
- Set brightness to 50% (127 on a scale of 0-255).
- Adafruit NeoPixel library must be used.
- Ensure the matrix initializes correctly before setting colors.
3 LED Behavior:
- While connecting to Wi-Fi:
- Blink RED every 300ms.
- Continue blinking even if the connection happens fast.
- Minimum 3 seconds of red blinking before any green LED appears.
- If connected successfully:
- Turn all LEDs solid GREEN after 3 seconds of blinking.
- If the connection fails:
- Keep blinking RED indefinitely.
4 Optimize the Code:
- Ensure proper delays to avoid flickering.
- Add clear comments explaining each step.
- Optimize loop logic to avoid unnecessary delays.
Generate a clean and optimized Arduino code snippet that follows these exact steps."
The following code connects the XIAO ESP32C6 to Wi-Fi and visually indicates its status using a 6x10 RGB matrix. The LED matrix blinks red while attempting to connect, guarantees at least 3 seconds of blinking even if Wi-Fi connects fast, and turns solid green when connected. If Wi-Fi fails, it continues blinking red indefinitely.
#include <WiFi.h>
#include <Adafruit_NeoPixel.h>
const char* ssid = "Primavera25";
const char* password = "Ib3r02025ui@";
#define MATRIX_PIN D0 // RGB Matrix Data Pin (D0 = GPIO2)
#define NUM_LEDS 60 // 6x10 Matrix = 60 LEDs
#define BRIGHTNESS 127 // 50% brightness (0-255)
Adafruit_NeoPixel matrix(NUM_LEDS, MATRIX_PIN, NEO_GRB + NEO_KHZ800);
void setup() {
Serial.begin(115200);
Serial.println("\nStarting...");
// Initialize NeoPixel and set brightness
matrix.begin();
matrix.setBrightness(BRIGHTNESS);
matrix.clear();
matrix.show();
delay(100);
Serial.println("Connecting to Wi-Fi...");
WiFi.begin(ssid, password);
int attempt = 0;
unsigned long startTime = millis();
while (WiFi.status() != WL_CONNECTED && attempt < 20) {
Serial.print(".");
// Blink RED while connecting
for (int i = 0; i < NUM_LEDS; i++) {
matrix.setPixelColor(i, matrix.Color(255, 0, 0));
}
matrix.show();
delay(300);
matrix.clear();
matrix.show();
delay(300);
attempt++;
}
// Ensure RED blinking lasts at least 3 seconds
while (millis() - startTime < 3000) {
for (int i = 0; i < NUM_LEDS; i++) {
matrix.setPixelColor(i, matrix.Color(255, 0, 0));
}
matrix.show();
delay(300);
matrix.clear();
matrix.show();
delay(300);
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nConnected to Wi-Fi!");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
// Solid GREEN when connected
for (int i = 0; i < NUM_LEDS; i++) {
matrix.setPixelColor(i, matrix.Color(0, 255, 0));
}
matrix.show();
} else {
Serial.println("\nFailed to connect.");
// Keep blinking RED if Wi-Fi fails
while (1) {
for (int i = 0; i < NUM_LEDS; i++) {
matrix.setPixelColor(i, matrix.Color(255, 0, 0));
}
matrix.show();
delay(300);
matrix.clear();
matrix.show();
delay(300);
}
}
}
void loop() {
// Nothing needed in loop
}
The videos below shows the RGB matrix transitioning from Wi-Fi Not Connected (blinking red) to Connected (solid green).