WEEK 04
Embedded Programming
Group assignment:
- Demonstrate and compare the toolchains and development workflows for available embedded architectures.
- Document your work to the group work page and reflect on your individual page what you learned.
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
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. |
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:
- Browse through the datasheet for a microcontroller.
- Write and test a program for an embedded system using a microcontroller to interact (with local input &/or output devices) and communicate (with remote wired or wireless connections).
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).
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:
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.
/* 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.
/* 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.
/* 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
/* 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.
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.
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.
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... 🤞
Let's interact with inputs and outputs 💪
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.
FILES