FAB ACADEMY 2026

WEEK 04

Embedded Programming

Group assignment:

For the Embedded Programming week, Rocio and I teamed up via Zoom to compare various development workflows. We split the tasks by choosing different microcontrollers to work on. I took charge of the RP2040 and, after documenting my process, shared the data with her to evaluate the differences between our selected architectures

Work in progress Week 01

In this section, I’m presenting a head-to-head comparison between two powerhouse microcontrollers:the Seeed Studio XIAO RP2040 and the ESP32 - C3. Although they share a small form factor, their design approaches are worlds apart. After a Zoom session with my teammate Rocio, we organized the following structure to demonstrate and compare their development workflows:

1. Architecture Comparison

Feature Seeed Studio XIAO RP2040 Seeed Studio XIAO ESP32-C3
Microcontroller Raspberry Pi RP2040 Espressif ESP32-C3
Architecture Dual-core ARM Cortex-M0+ Single-core 32-bit RISC-V
Clock Speed 133 MHz 160 MHz
Connectivity None (Wired only) Wi-Fi + Bluetooth 5.0 (LE)
SRAM 264 KB 400 KB
Flash Memory 2 MB 4 MB
Core Strength PIO (Programmable I/O):
Hardware-level custom protocols.
IoT & Security:
Hardware encryption and native wireless.
Work in progress Week 01

2. Toolchains (Development Environment)

The toolchain is the set of tools (compiler, linker, debugger) used to turn your code into a binary the chip can understand.

Feature Seeed Studio XIAO RP2040 Seeed Studio XIAO ESP32-C3
ISA (Architecture) ARMv6-M (Proprietary) RISC-V (Open Standard)
Main Compiler arm-none-eabi-gcc riscv32-esp-elf-gcc
Official SDK Pico C/C++ SDK (Bare-metal) ESP-IDF (Built on FreeRTOS)
Build System CMake / Ninja CMake / Ninja (via idf.py)
Primary Frameworks Arduino Core, MicroPython, CircuitPython Arduino Core, ESP-IDF, MicroPython
Hardware Debugging SWD (Requires external probe) Built-in USB-JTAG (No probe needed)
Binary Format .uf2 (Universal Flashing Format) .bin (Standard ESP Binary)

Key Points:

  • The RP2040 Ecosystem is highly portable. Because it uses the ARM standard, many developers find the transition from other chips (like STM32) very easy. The UF2 bootloader is its "secret weapon" for group projects because it removes the need for special drivers to upload code.
  • The ESP32-C3 Ecosystem is more specialized but powerful. It is essentially a "system-on-a-chip" designed to run a multitasking OS (FreeRTOS) by default. This makes the toolchain slightly heavier but allows for complex features like background Wi-Fi management.

3. Development Workflows

Phase Seeed Studio XIAO RP2040 Seeed Studio XIAO ESP32-C3
Boot Mode Physical "BOOT" button + Plug USB. Auto-reset via DTR/RTS (usually).
Upload Method Drag & Drop (UF2): Mounts as a mass storage drive. Serial Flashing: Over a COM port (UART/USB-CDC).
File Handling Ideal for CircuitPython (edit files directly on the drive). Compiled binaries (.bin) managed by esptool.
I/O Control PIO State Machines: High-speed, independent hardware logic. Software-defined: Standard GPIO/Peripherals.
Memory Mgmt. Single flat memory space (Simple). Partition Tables: Separate NVS, OTA, and App data.
Remote Updates Limited/Complex (Needs external hardware). Native OTA (Over-the-Air): Update via Wi-Fi.

Workflow Analysis:

  • The RP2040 Workflow is superior for rapid prototyping and educational settings. The fact that it behaves like a USB flash drive removes the "driver hell" often associated with microcontrollers. If your group wants to show a quick demo of changing code on the fly, this is the better choice.
  • The ESP32-C3 Workflow is better for IoT Deployment. Its workflow assumes the device might not be plugged into a computer forever. The ability to manage memory partitions and perform OTA updates makes it the professional choice for connected devices.

Group conclusions:

  • Architecture & Power: While both share a compact footprint, the RP2040 offers a dual-core ARM Cortex-M0+ focused on high-speed hardware manipulation through its unique PIO (Programmable I/O). The ESP32-C3 uses a single-core RISC-V architecture designed specifically for the IoT era, integrating native Wi-Fi and Bluetooth.
  • Connectivity & Scope: The ESP32-C3 is the clear winner for wireless projects, cloud integration, and remote monitoring. The RP2040 is better suited for standalone applications, high-performance local control, or scenarios where emulating specific hardware protocols is required.
  • Toolchain & Debugging: The RP2040 follows traditional ARM standards, making it highly portable and easy to use with common compilers. However, the ESP32-C3 provides a more modern debugging experience with its built-in USB-JTAG, allowing for real-time code inspection without additional hardware.
  • Development Workflow: The RP2040 excels in ease of use for beginners and rapid prototyping thanks to its "drag and drop" UF2 bootloader. Conversely, the ESP32-C3 offers a more robust industrial workflow, supporting multitasking via FreeRTOS and professional features like Over-the-Air (OTA) updates.
  • Ecosystem & Community: The RP2040 benefits from the massive Raspberry Pi community and is the premier choice for Python-based embedded development. The ESP32-C3 leverages the mature Espressif ecosystem, which is the industry standard for low-cost, secure IoT deployment.

Individual assignment:


XIAO RP240

1. General Description

The Seeed Studio XIAO RP2040 is a high-performance, low-power microcontroller that maintains the ultra-small footprint (21 x 17.8 mm) of the XIAO series. It is powered by the Raspberry Pi RP2040 chip, designed for wearable devices and compact projects.

2. Hardware Specifications

  • Processor: Dual-core ARM Cortex M0+ with a flexible clock running up to 133 MHz.
  • Memory: 264 KB of internal SRAM and 2 MB of on-board Flash memory.
  • Interfaces:
    • 14 GPIO pins in total.
    • 11 Digital pins and 11 PWM pins.
    • 4 Analog pins (ADC).
    • Dedicated communication: 1x I2C, 1x UART, 1x SPI.
  • Debug Interface: SWD (Serial Wire Debug) bonding pads located on the underside.
  • On-board Components:
    • Physical Reset (R) and Boot (B) buttons.
    • 1x Programmable RGB LED (WS2812).
    • Power LED and user-controllable status LEDs (Red, Blue, Green).
Work in progress

3. Software & Workflow

  • Language Compatibility: Fully supports Arduino, MicroPython, CircuitPython, PlatformIO, Rust, and RTOS like Zephyr.
  • Bootloader Mode: Easily enter Bootloader mode by holding the "B" button while connecting via USB. The board mounts as a UF2 mass storage drive, allowing for simple "drag and drop" programming.
  • Electrical Safety: Operating voltage is 3.3V. Inputting higher voltages into general I/O pins will damage the chip.
  • Battery Support: Includes dedicated pads on the back for 3.7V Lithium battery power.

  • Wiki Pro Tip: On the XIAO RP2040, the built-in programmable LEDs are active-low, meaning the pin must be pulled low (0V) to turn them on—the opposite of standard Arduino logic.

Testing and writing for RP2040

First, I downloaded the Arduino IDE program, version 2.3.7, for my computer's Windows version. I liked this version because there were others with a slightly different interface. I'll leave the link here:

Work in progress

Next, you need to look in the preferences to include a larger IDE library. To do this, paste the following link: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
We will proceed to our RP2040 board, for which we will open the board manager and look for our microcontroller.

Now that our RP2040 is connected to the indicated COM port, we will proceed to upload a startup code to confirm communication with our microcontroller. Copy the code below and click first on the checkmark icon and then on the arrow icon to the right to upload it. Wait a moment and you will see the communication in the serial monitor at the bottom of the program.

Work in progress
Code Hello World! - Credits: Gemini

 /*
  XIAO RP2040 - Startup Test (Hello World)
  This code prints a message to the Serial Monitor and 
  blinks the built-in Green LED.
*/

void setup() {
  // Initialize serial communication at 115200 baud
  Serial.begin(115200);
  
  // Set the built-in LED pin as an output
  // On the XIAO RP2040, LED_BUILTIN usually maps to the green LED
  pinMode(LED_BUILTIN, OUTPUT);
  
  // Wait a moment for the Serial Monitor to open
  delay(2000);
  Serial.println("--- XIAO RP2040 Connected ---");
  Serial.println("Hello World! The code compiled and uploaded successfully.");
}

void loop() {
  // Turn the LED ON (Note: On RP2040, LOW usually turns it ON)
  digitalWrite(LED_BUILTIN, LOW); 
  Serial.println("LED status: ON");
  delay(1000);
  
  // Turn the LED OFF
  digitalWrite(LED_BUILTIN, HIGH);
  Serial.println("LED status: OFF");
  delay(1000);

Interacting with my devices

It's time to interact with more input/output devices and code. To do this, we'll assemble our circuit board on a breadboard, carefully noting the pin locations according to the datasheet. Then, we'll ask Gemini to generate interaction code for our buttons and buzzer.

Work in progress
Code: Buttons, Buzzer, and Internal RGB LED - Credits: Gemini

 /*
  XIAO RP2040 - Buttons, Buzzer, and Internal RGB LEDs
  - Button 1 (D2) -> High Tone + Red LED
  - Button 2 (D4) -> Low Tone + Green LED
*/

// Defining the internal LED pins for the XIAO RP2040
#define MY_RED_LED 17
#define MY_GREEN_LED 16
#define MY_BLUE_LED 25

const int btn1 = D2;
const int btn2 = D4;
const int buzzer = D3;

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

  // Setup buttons with internal pullups
  pinMode(btn1, INPUT_PULLUP);
  pinMode(btn2, INPUT_PULLUP);
  
  // Setup buzzer and internal LEDs as outputs
  pinMode(buzzer, OUTPUT);
  pinMode(MY_RED_LED, OUTPUT);
  pinMode(MY_GREEN_LED, OUTPUT);
  pinMode(MY_BLUE_LED, OUTPUT);

  // Turn all internal LEDs OFF at the start (HIGH = OFF for these LEDs)
  digitalWrite(MY_RED_LED, HIGH);
  digitalWrite(MY_GREEN_LED, HIGH);
  digitalWrite(MY_BLUE_LED, HIGH);
  
  Serial.println("--- System Ready (Pins Defined Manually) ---");
}

void loop() {
  int state1 = digitalRead(btn1);
  int state2 = digitalRead(btn2);

  if (state1 == LOW) { 
    Serial.println("D2 Pressed: Red LED + High Tone");
    digitalWrite(MY_RED_LED, LOW);   // Turn Red ON
    digitalWrite(MY_GREEN_LED, HIGH);
    tone(buzzer, 1000); 
  } 
  else if (state2 == LOW) { 
    Serial.println("D4 Pressed: Green LED + Low Tone");
    digitalWrite(MY_RED_LED, HIGH);
    digitalWrite(MY_GREEN_LED, LOW); // Turn Green ON
    tone(buzzer, 500);  
  } 
  else {
    // Turn everything OFF when no button is pressed
    noTone(buzzer); 
    digitalWrite(MY_RED_LED, HIGH);
    digitalWrite(MY_GREEN_LED, HIGH);
  }
  
  delay(20); 
};

We'll add some external LEDs to the free pins and configure Gemini to interact with the LEDs so that when one button is pressed, they light up red, and when the other is pressed, they light up green. You can copy the code below if your connection is the same as mine.

Work in progress
Code: Buttons, Buzzer, and External RGB LED - Credits: Gemini

/*
  XIAO RP2040 - Full Control Project
  - Button 1 (D2) -> External Red LED (D5) + Internal Red LED + High Tone (D3)
  - Button 2 (D4) -> External Green LED (D6) + Internal Green LED + Low Tone (D3)
*/

// Defining internal LED pins (Manual mapping to avoid scope errors)
#define INT_RED_LED 17
#define INT_GREEN_LED 16
#define INT_BLUE_LED 25

// Pins for external components
const int btn1 = D2;
const int btn2 = D4;
const int buzzer = D3;
const int extRedLed = D5;
const int extGreenLed = D6;

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

  // Configure Buttons
  pinMode(btn1, INPUT_PULLUP);
  pinMode(btn2, INPUT_PULLUP);
  
  // Configure Outputs (Buzzer & LEDs)
  pinMode(buzzer, OUTPUT);
  pinMode(extRedLed, OUTPUT);
  pinMode(extGreenLed, OUTPUT);
  pinMode(INT_RED_LED, OUTPUT);
  pinMode(INT_GREEN_LED, OUTPUT);
  pinMode(INT_BLUE_LED, OUTPUT);

  // Initialize: Turn everything OFF
  // Internal LEDs: HIGH is OFF
  digitalWrite(INT_RED_LED, HIGH);
  digitalWrite(INT_GREEN_LED, HIGH);
  digitalWrite(INT_BLUE_LED, HIGH);
  
  // External LEDs: LOW is OFF
  digitalWrite(extRedLed, LOW);
  digitalWrite(extGreenLed, LOW);
  
  Serial.println("--- Full System Ready ---");
}

void loop() {
  int state1 = digitalRead(btn1);
  int state2 = digitalRead(btn2);

  if (state1 == LOW) { 
    Serial.println("D2 Pressed: Red LEDs ON + High Tone");
    // Turn ON Red
    digitalWrite(extRedLed, HIGH);  // External ON
    digitalWrite(INT_RED_LED, LOW); // Internal ON
    // Ensure Green is OFF
    digitalWrite(extGreenLed, LOW);
    digitalWrite(INT_GREEN_LED, HIGH);
    
    tone(buzzer, 1000); 
  } 
  else if (state2 == LOW) { 
    Serial.println("D4 Pressed: Green LEDs ON + Low Tone");
    // Turn ON Green
    digitalWrite(extGreenLed, HIGH);  // External ON
    digitalWrite(INT_GREEN_LED, LOW); // Internal ON
    // Ensure Red is OFF
    digitalWrite(extRedLed, LOW);
    digitalWrite(INT_RED_LED, HIGH);
    
    tone(buzzer, 500);  
  } 
  else {
    // Silence and Turn everything OFF
    noTone(buzzer); 
    digitalWrite(extRedLed, LOW);
    digitalWrite(extGreenLed, LOW);
    digitalWrite(INT_RED_LED, HIGH);
    digitalWrite(INT_GREEN_LED, HIGH);
  }
  
  delay(20); 
}

Finally, we added a micro servo so that it could be synchronized when the buttons were pressed, thus solving the exercise,......but

Code: Full Control Project with Servo - Credits: Gemini

/*
  XIAO RP2040 - Full Control Project with Servo
  - Button 1 (D2) -> External Red LED (D5) + Internal Red LED + High Tone (D3) + Servo to 0°
  - Button 2 (D4) -> External Green LED (D6) + Internal Green LED + Low Tone (D3) + Servo to 180°
*/

#include 

// Defining internal LED pins (Manual mapping to avoid scope errors)
#define INT_RED_LED 17
#define INT_GREEN_LED 16
#define INT_BLUE_LED 25

// Pins for external components
const int btn1 = D2;
const int btn2 = D4;
const int buzzer = D3;
const int extRedLed = D5;
const int extGreenLed = D6;
const int servoPin = D1; // Servo connected to D1

Servo myServo;

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

  // Configure Buttons
  pinMode(btn1, INPUT_PULLUP);
  pinMode(btn2, INPUT_PULLUP);
  
  // Configure Outputs
  pinMode(buzzer, OUTPUT);
  pinMode(extRedLed, OUTPUT);
  pinMode(extGreenLed, OUTPUT);
  pinMode(INT_RED_LED, OUTPUT);
  pinMode(INT_GREEN_LED, OUTPUT);
  pinMode(INT_BLUE_LED, OUTPUT);

  // Attach Servo and set to middle position
  myServo.attach(servoPin);
  myServo.write(90); 

  // Initialize: Turn everything OFF
  digitalWrite(INT_RED_LED, HIGH);
  digitalWrite(INT_GREEN_LED, HIGH);
  digitalWrite(INT_BLUE_LED, HIGH);
  digitalWrite(extRedLed, LOW);
  digitalWrite(extGreenLed, LOW);
  
  Serial.println("--- Full System Ready with Servo ---");
}

void loop() {
  int state1 = digitalRead(btn1);
  int state2 = digitalRead(btn2);

  if (state1 == LOW) { 
    Serial.println("D2 Pressed: Red LEDs + High Tone + Servo to 0 deg");
    // Feedback Visual
    digitalWrite(extRedLed, HIGH);
    digitalWrite(INT_RED_LED, LOW);
    digitalWrite(extGreenLed, LOW);
    digitalWrite(INT_GREEN_LED, HIGH);
    
    // Feedback Sonoro
    tone(buzzer, 1000); 
    
    // Movimiento Servo
    myServo.write(0); 
  } 
  else if (state2 == LOW) { 
    Serial.println("D4 Pressed: Green LEDs + Low Tone + Servo to 180 deg");
    // Feedback Visual
    digitalWrite(extGreenLed, HIGH);
    digitalWrite(INT_GREEN_LED, LOW);
    digitalWrite(extRedLed, LOW);
    digitalWrite(INT_RED_LED, HIGH);
    
    // Feedback Sonoro
    tone(buzzer, 500);  
    
    // Movimiento Servo
    myServo.write(180);
  } 
  else {
    // Reset Everything
    noTone(buzzer); 
    digitalWrite(extRedLed, LOW);
    digitalWrite(extGreenLed, LOW);
    digitalWrite(INT_RED_LED, HIGH);
    digitalWrite(INT_GREEN_LED, HIGH);
  }
  
  delay(20); 
}


Wait… Neil Doesn’t Like Breadboards!

Well... let's build one with a laser XD!

To continue learning with the exercise, we will manufacture a plate designed by Adrian Torres. This pcb is called FAB-XIAO; you can find more info here.

I downloaded the Fab XIAO Traces .svg file from Adrian because it's something I find easy to edit. I enlarged the PCB a bit and thickened the connection lines because I wanted to make sure the laser could do it.

Work in progress

To fabricate the PCB, I used a 50W fiber optic galvo laser; this is a full-station laser. The software for this laser is EZCAD, and we imported our saved PCB file in .dxf format. The parameters that worked best for me were a speed of 500, a power of 70, and a frequency of 45, with cross-cut etching in two passes. It took me a long time to achieve a good finish.

Work in progress
Work in progress

It was a bit tricky learning how to engrave with a fiber laser since I'm not very familiar with the machine, so my friend let me test different engraving parameters to see which would give the best finish. I messed up a few because I didn't wait for the material to cool down before applying the final touches.

Work in progress
Work in progress

Now, this is the good one!

Fiber laser engraving is extremely detailed and fast; the challenge is avoiding burning the circuit board and preventing interference on the traces. I know that with more practice I'll get better results, but I haven't had much time to do any testing.

Time for the final assembly... let's do this right! XD

Uff, moving on to the assembly part was the hardest for me. I don't have much experience, but with patience I was able to do it. However, I realized I had made a mistake: one of the tracks was too wide and crossed with two others, so I decided to correct it with a Dremel.

It's time to program and test it... 🤞


Work in progress
Code:Arduino Hello Button  Edit: Fab Academy 2023 - Fab Lab León     Original code:Neil Gershenfeld 12/8/19

//Fab Academy 2023 - Fab Lab León
//Button + LED                           
//Fab-Xiao
//
//Original code:Neil Gershenfeld 12/8/19
// This work may be reproduced, modified, distributed,
// performed, and displayed for any purpose, but must
// acknowledge this project. Copyright is retained and
// must be preserved. The work is provided as is; no
// warranty is provided, and users accept all liability.
//


const int ledPin1 = 0;//first light  RP2040 pin 0 or ESP32-C3 pin D6 
const int buttonPin = 1;// button pin  RP2040 pin 1 or ESP32-C3 pin D7 
int buttonState = 0;//initial state of the button
int i = 0; //variable intensity led

void setup() { //declaration of inputs and outputs
  pinMode(ledPin1, OUTPUT);
  pinMode(buttonPin, INPUT);
}
void loop() {
  buttonState = digitalRead(buttonPin);// we read the state of the button
  if (buttonState == HIGH) { //if we press the button
  digitalWrite(ledPin1, HIGH);
  delay(500);                       
  digitalWrite(ledPin1, LOW);    
  delay(500);
  digitalWrite(ledPin1, HIGH);
  delay(500);                       
  digitalWrite(ledPin1, LOW);    
  delay(500);
  digitalWrite(ledPin1, HIGH);
  delay(2000);                        
  digitalWrite(ledPin1, LOW);    
  delay(1000);
  
}
  else {  //if we don't press the button
  digitalWrite(ledPin1, LOW);
  }
}

Let's interact with inputs and outputs 💪


Work in progress
Code: FABXIAO - Servo + boton   Credits: Gemini

/*
XIAO RP2040 - Direct Control
- Button: D7 (Press to activate)
- LED: D6 (On while pressed)
- Servo: D0 (Moves 180° while pressed, returns to 0° when released)
*/

#include 

const button intPin = D7;

const ledPin = D6;

const servoPin = D0;

myServo;

null configuration() {
pinMode(buttonPin, INPUT_PULLUP); // Button to GND
pinMode(ledPin, OUTPUT);

myServo.attach(servoPin);

myServo.write(0); // Initial position (rest)

digital write(ledPin, LOW);

}

empty loop() {
// Read the button
if (digitalRead(buttonPin) == LOW) {
// STATE: PRESSED
digital write(ledPin, HIGH); // Turns on LED
myServo.write(180); // The servo rotates and stays there
}
other {
// STATE: RELEASED
digital write(ledPin, LOW); // Turns off LED
myServo.write(0); // The servo returns to rest
}

delay(10); // Short pause for stability
}

Conclusions and Reflections

The development of embedded systems using microcontrollers like the XIAO RP2040 represents a complete engineering cycle where IDE programming translates algorithmic logic into physical actions, enabling precise interaction between input (sensors/buttons) and output (servos/LEDs) devices. This process begins with validation on a breadboard, a critical stage for rapid prototyping and troubleshooting, and culminates in the fabrication of a PCB using fiber laser technology, which transforms an experimental design into a robust, professional, and optimized product. Together, these tools integrate computational thinking with digital manufacturing, facilitating the creation of personalized, efficient, and scalable technological solutions.

Work in progress

FILES

PCB FIBER LASER Download DXF
PCB EZCAD Download EZD