Week 04 – Embedded Programming

This week is focused on Embedded Programming: comparing development workflows/toolchains for different embedded architectures, browsing microcontroller datasheets, and writing/testing programs that interact with local inputs/outputs and communicate remotely (wired or wireless).

On this page I document:

Assignment and Learning Outcomes

The weekly assignment is:

The learning outcomes are:

Checklist

In this page I answer the required questions:

You can see the group documentation here:

Group Assignment – Toolchains and Workflows Comparison

The Fab Academy FAQ clarifies that this group assignment is not theoretical: we must physically program devices from at least two different MCU families using different toolchains.

Boards used in our group

Toolchains used

We intentionally used different development environments to compare workflows:

Individual Assignment 1 – Microcontroller XIAO RP2040

Workflow A – XIAO RP2040 with Arduino IDE

  • Install Arduino IDE and add RP2040 board support (Seeed / RP2040 core).
  • Select board: Seeed XIAO RP2040 and correct USB port.
  • Write sketch (.ino) using Arduino APIs.
  • Compile & Upload (UF2/bootloader workflow handled by core).
  • Debug/monitor using Serial Monitor (USB CDC serial).

XIAO RP2040 – Development Process

The first step was to collect the official documentation from the manufacturer. I used the Seeed Studio product wiki to download the datasheet-related information, understand the board features, and review the recommended programming workflow:

After that, I analyzed the board specifications and the connection diagram (pinout / schematic) to identify the available GPIOs, power rails, and the pins required for my project. Here I include a reference image of the board connections:

As a starting point, I downloaded an initial test program from Quentin Bolsee’s repository. This example helped me validate my toolchain and confirm that the board was correctly detected and programmable.

Finally, I modified the original program to adapt it to my own setup and requirements. I changed the code structure and parameters and now we can view all buttons, for more thingsI add UART comunication for the button to send the data other devices

Source Code

You can download the Arduino source code used in this assignment here:

Code card
Arduino · test_touch_RP2040_V3.ino Show code
		
#include 
#include 
#include 
#include 

// ---------- OLED ----------
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define SCREEN_ADDRESS 0x3C

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1, 1700000UL, 1700000UL);

// ---------- RGB LED (según tu diseño) ----------
#define PIN_RED 17
#define PIN_GREEN 16
#define PIN_BLUE 25

// ---------- Touch config ----------
#define N_TOUCH 6
#define THRESHOLD 6

int touch_pins[N_TOUCH] = {3, 4, 2, 27, 1, 26};
int touch_values[N_TOUCH] = {0, 0, 0, 0, 0, 0};

bool pin_touched_now[N_TOUCH]  = {false, false, false, false, false, false};
bool pin_touched_past[N_TOUCH] = {false, false, false, false, false, false};

// ---------- UART ----------
#define UART_PORT Serial1
const unsigned long UART_BAUD = 115200;

// Envía el estado de los 6 botones como "B:010101\n"
void sendButtonsUART() {
  UART_PORT.print("B:");
  for (int i = 0; i < N_TOUCH; i++) {
    UART_PORT.print(pin_touched_now[i] ? '1' : '0');
  }
  UART_PORT.print('\n');
}

// ---------- Touch reading ----------
void update_touch() {
  int t;
  int t_max = 200;
  int p;

  for (int i = 0; i < N_TOUCH; i++) {
    p = touch_pins[i];

    pinMode(p, OUTPUT);
    digitalWriteFast(p, LOW);

    delayMicroseconds(25);

    pinMode(p, INPUT_PULLUP);

    t = 0;
    while (!digitalReadFast(p) && t < t_max) {
      t++;
    }
    touch_values[i] = t;

    pin_touched_past[i] = pin_touched_now[i];
    pin_touched_now[i]  = (touch_values[i] > THRESHOLD);
  }
}

void print_touch() {
  char print_buffer[30];
  for (int i = 0; i < N_TOUCH; i++) {
    sprintf(print_buffer, "%4d ", touch_values[i]);
    Serial.print(print_buffer);
  }
  Serial.println();
}

// ---------- Simple UI helpers ----------
const int TITLE_Y = 0;      // zona amarilla (bicolor físico)
const int STATE_Y = 16;     // debajo (zona azul)
const int COL_W   = 21;     // 6 columnas en 128px
const int X0      = 0;
const int LINE_H  = 8;

void drawTitles() {
  // limpia solo la banda superior
  display.fillRect(0, 0, 128, 16, SSD1306_BLACK);

  for (int i = 0; i < N_TOUCH; i++) {
    int x = X0 + i * COL_W;
    display.setCursor(x, TITLE_Y);
    display.print("B-");
    display.print(i + 1);
  }
}

void drawState(int idx, bool on) {
  int x = X0 + idx * COL_W;

  // limpia SOLO el hueco del estado
  display.fillRect(x, STATE_Y, COL_W, LINE_H, SSD1306_BLACK);

  display.setCursor(x, STATE_Y);
  display.print(on ? "ON" : "OFF");
}

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

  // OLED
  display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);

  // LED
  pinMode(PIN_RED, OUTPUT);
  pinMode(PIN_GREEN, OUTPUT);
  pinMode(PIN_BLUE, OUTPUT);
  digitalWrite(PIN_RED, HIGH);
  digitalWrite(PIN_GREEN, HIGH);
  digitalWrite(PIN_BLUE, HIGH);

  // Splash
  display.setCursor(28, 25);
  display.print("Hello Oscar");
  display.display();
  delay(1500);

  display.clearDisplay();
  display.setCursor(20, 25);
  display.print("button control");
  display.display();
  delay(800);

  // UART
  UART_PORT.begin(UART_BAUD);

  // UI inicial
  display.clearDisplay();
  drawTitles();
  for (int i = 0; i < N_TOUCH; i++) {
    drawState(i, false);
  }
  display.display();
}

void loop() {
  update_touch();

  bool changed = false;

  // si algún botón cambió, actualiza OLED + manda UART
  for (int i = 0; i < N_TOUCH; i++) {
    if (pin_touched_now[i] != pin_touched_past[i]) {
      changed = true;
      drawState(i, pin_touched_now[i]);
    }
  }

  if (changed) {
    display.display();
    sendButtonsUART();

    // feedback LED (opcional): enciende verde si hay al menos uno ON
    bool anyOn = false;
    for (int i = 0; i < N_TOUCH; i++) anyOn |= pin_touched_now[i];
    digitalWrite(PIN_GREEN, anyOn ? LOW : HIGH);
  }

  // Debug opcional
  // print_touch();

  delay(20);
}
		

Final Result – Video Demonstration

The following video shows the final working system: XIAO RP2040 reading the touch buttons and sending the state via UART to the ESP32, which processes the received data.

Individual assignment 2 M5Stack ATOM (ESP32-PICO) – Development Process

To begin working with the ATOM board, I first consulted the official manufacturer documentation in order to obtain technical specifications, pin configuration details, and recommended programming workflows.

The official documentation can be found at the following link:

From this page, I reviewed the hardware specifications, including the ESP32-PICO-D4 microcontroller architecture, integrated WiFi and Bluetooth capabilities, available GPIOs, power characteristics, and communication interfaces.

I also analyzed the board pinout to correctly identify the pins required for UART communication and external connections. The following image shows the ATOM pin configuration used in this project:

In addition to using the Arduino framework during development, I decided to explore a different programming environment by switching to UiFlow2. This allowed me to compare multiple development platforms and better understand how different toolchains interact with the ESP32 architecture.

Working with UiFlow2 provided a more visual and structured programming approach, while still allowing low-level control of the ESP32 hardware. This comparison helped me better understand the flexibility of the ESP32 ecosystem and the differences between Arduino-based and alternative development workflows. Additionally, this programming environment translates into MicroPython, allowing users to easily observe and compare both programming systems.

I used the tutorials from UiFlow2 Web IDE: Instaling the M5Burning for windows Register in Register an M5Stack Community account

  • M5Stack ATOM Lite – Official Documentation

    Fab Academy — M5Stack ATOM Matrix + MQTT + App Inventor

    M5Stack ATOM Matrix — MicroPython + MQTT + MIT App Inventor

    Fab Academy Documentation

    Overview ATOM

    For this assignment I worked with the M5Stack ATOM Matrix (ESP32 + 5x5 RGB LED matrix). The goal was to create a small IoT device that connects to WiFi, establishes an MQTT connection, and can be controlled remotely from a mobile application.

    The device provides visual feedback using the LED matrix:

    • Red face → Device powered and program started
    • White vertical bar → Successful MQTT connection
    • Color changes via mobile app → Remote control confirmed
    ---

    Firmware Installation (UIFlow2)

    Initially, the ATOM Matrix was flashed using M5Burner with the official UIFlow2 firmware for ATOM devices.

    This step is required because the UIFlow2 firmware provides the M5Stack-specific MicroPython modules needed to control the LED matrix and hardware.

    Steps followed:

    1. Download and install M5Burner
    2. Select ATOM Matrix device
    3. Choose UIFlow2 firmware
    4. Flash the firmware via USB
    5. Reboot and verify MicroPython environment
    ---

    MicroPython Development

    After flashing UIFlow2, I developed a MicroPython program that:

    • Initializes the ATOM Matrix
    • Displays a red face on boot
    • Connects to my phone’s WiFi hotspot
    • Connects to my MQTT server
    • Displays a white bar once MQTT connection is successful
    • Subscribes to a topic to receive color commands

    The LED matrix acts as a real-time status indicator, eliminating the need for constant serial debugging.

    Source Code

    The complete MicroPython source code can be downloaded here:

    Download MicroPython Program (main.py)

    For security reasons, MQTT credentials are stored separately and are not included in the public repository.
    Code card
    micropyton · micropyton.py Show code
    		
    
    import os, sys, io
    import M5
    from M5 import *
    from umqtt import MQTTClient
    from hardware import RGB
    from hardware import Button
    
    
    
    mqtt_client = None
    rgb = None
    Btn1 = None
    
    
    def mqtt_fablab_week4_blanco_event(data):
      global mqtt_client, rgb, Btn1
      rgb.set_screen([0, 0, 0, 0, 0, 0, 0xffffff, 0, 0xffffff, 0, 0, 0, 0, 0, 0, 0, 0xffffff, 0xffffff, 0xffffff, 0, 0, 0, 0, 0, 0])
    
    
    def mqtt_fablab_week4_verde_event(data):
      global mqtt_client, rgb, Btn1
      rgb.set_screen([0, 0, 0, 0, 0, 0, 0x46d133, 0, 0x46d133, 0, 0, 0, 0, 0, 0, 0, 0x46d133, 0x46d133, 0x46d133, 0, 0, 0, 0, 0, 0])
    
    
    def mqtt_fablab_week4_azul_event(data):
      global mqtt_client, rgb, Btn1
      rgb.set_screen([0, 0, 0, 0, 0, 0, 0x4633d1, 0, 0x4633d1, 0, 0, 0, 0, 0, 0, 0, 0x4633d1, 0x4633d1, 0x4633d1, 0, 0, 0, 0, 0, 0])
    
    
    def setup():
      global mqtt_client, rgb, Btn1
    
      M5.begin()
      Widgets.fillScreen(0x000000)
    
      rgb = RGB()
      rgb.set_screen([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
      rgb.set_brightness(20)
      rgb.set_screen([0, 0, 0, 0, 0, 0, 0xcd3232, 0, 0xcd3232, 0, 0, 0, 0, 0, 0, 0, 0xcd3232, 0xcd3232, 0xcd3232, 0, 0, 0, 0, 0, 0])
      Btn1 = Button(1, active_low=True, pullup_active=False)
      mqtt_client = MQTTClient('oscar', 'xxxxxxxx', port=1883, user='xxxxxxxx', password='xxxxxxxx', keepalive=0)
      mqtt_client.connect(clean_session=True)
      mqtt_client.subscribe('fablab/week4/blanco', mqtt_fablab_week4_blanco_event, qos=0)
      mqtt_client.subscribe('fablab/week4/verde', mqtt_fablab_week4_verde_event, qos=0)
      mqtt_client.subscribe('fablab/week4/azul', mqtt_fablab_week4_azul_event, qos=0)
      if mqtt_client.isconnected():
        rgb.set_screen([0, 0, 0xffffff, 0, 0, 0, 0, 0xffffff, 0, 0, 0, 0, 0xffffff, 0, 0, 0, 0, 0xffffff, 0, 0, 0, 0, 0xffffff, 0, 0])
    
    
    def loop():
      global mqtt_client, rgb, Btn1
      M5.update()
      mqtt_client.check_msg()
    
    
    if __name__ == '__main__':
      try:
        setup()
        while True:
          loop()
      except (Exception, KeyboardInterrupt) as e:
        try:
          from utility import print_error_msg
          print_error_msg(e)
        except ImportError:
          print("please update to latest firmware")
    
    		
    ---

    MQTT Communication

    The ATOM Matrix connects to my MQTT broker running on a remote server. The communication flow is:

    • Device connects to WiFi hotspot
    • Device connects to MQTT broker (port 1883)
    • Device subscribes to a command topic
    • Mobile app publishes color commands
    • Device updates LED face color accordingly
    ---

    MIT App Inventor Mobile Application

    To remotely control the device, I created a simple Android application using MIT App Inventor.

    The app includes:

    • Two buttons
    • Each button publishes a different color command via MQTT

    App Blocks

    App Inventor Blocks Screenshot

    When a button is pressed, the app publishes a text message to the MQTT topic. The ATOM Matrix receives the message and updates the face color.

    ---

    Results

    • Boot → Red face displayed
    • WiFi connection → automatic via hotspot
    • MQTT connection → white bar displayed
    • App control → face color changes instantly
    ---

    7. Demonstration Video

    ---

    Conclusions

    This project demonstrates:

    • Firmware flashing using M5Burner
    • MicroPython development on ESP32-based hardware
    • WiFi and MQTT connectivity
    • Mobile IoT control via MIT App Inventor

    The ATOM Matrix provides an excellent compact platform for IoT experimentation, combining visual feedback, WiFi connectivity, and simple user interaction.

    Fab Academy Documentation — ATOM Matrix MQTT Project
  • Architectural and Workflow Comparison

    Category Seeed Studio XIAO RP2040 M5Stack ATOM ESP32-PICO
    Microcontroller Family Raspberry Pi RP2040 Espressif ESP32-PICO-D4
    CPU Architecture Dual-core ARM Cortex-M0+ (ARMv6-M) Dual-core Xtensa LX6
    Wireless Connectivity Not integrated (requires external module) Integrated WiFi 802.11 b/g/n + Bluetooth
    Development Environment Arduino IDE (RP2040 core) ESP-IDF / PlatformIO / Arduino core
    Development Workflow Sketch-based, simplified abstraction layer Project-based structure with configurable build system
    Debugging Approach USB Serial (CDC) monitoring Serial logging, FreeRTOS logs, optional JTAG debugging
    Typical Use Case Deterministic embedded control and fast prototyping Connected IoT applications and network-enabled systems

    Group reflection (what I learned)

    The Arduino workflow on RP2040 is very fast for prototyping and simple I/O tests. On the other hand, ESP-IDF/PlatformIO for ESP32 feels more “professional” and scalable, especially when the project needs wireless connectivity and more advanced configuration. Both approaches are useful depending on the project requirements.

    Individual Assignment – Datasheet Browsing

    To support my programming decisions, I browsed the datasheets of the two microcontrollers used this week: RP2040 and ESP32-PICO. I focused on sections related to GPIO, electrical limits, communication peripherals, and boot/programming modes.

    RP2040 – key datasheet items I used

    • GPIO / Pin functions: mapping pins to GPIO and alternate functions (UART, I2C, SPI, PWM).
    • USB: native USB device support (useful for Serial over USB).
    • PIO (Programmable I/O): special feature of RP2040 for custom protocols (not required here, but important to know).
    • Clocks: default clocking and implications for timing/baud rate.
    • Memory: SRAM size and external flash usage.
    • Boot / UF2: BOOTSEL behavior and firmware upload workflow.

    ESP32-PICO – key datasheet items I used

    • WiFi/Bluetooth: integrated radio and typical usage scenarios for wireless communication.
    • GPIO matrix: flexible assignment of peripheral signals to pins.
    • Power / RF notes: stable power supply considerations when using WiFi.
    • Boot mode: strapping pins and flashing via USB-serial interface.

    Individual Assignment – Program: Local I/O + Remote Communication

    For the individual program I created a small embedded system using both boards: the XIAO RP2040 handles the local interaction (button + LED), ATOM ESP32-PICO uses WiFI and MQTT comunicactions.

    This demonstrates:

    • Local input/output: button toggles LED on the RP2040.
    • Remote wireless communication: ESP32 recibe data from app

    Hardware setup

    • XIAO RP2040: reads button + controls LED.
    • ATOM ESP32-PICO: receives MQTT message
    • Note: RP2040 GPIO is 3.3V and ESP32 GPIO is also 3.3V, so UART level shifting is typically not needed.

      Programming process

      1. Flashed a simple Serial test on each board to verify toolchains and ports.
      2. Implemented button + LED logic on RP2040 with debounce.
      3. On ESP32, read MQTT

      Results

      • Pressing the button toggles the LED on the RP2040.
      • RP2040 sends LED=1 / LED=0 through UART.
      • ESP32 connects to WiFi and MQTT and change color from a APPINVENTOR

    Summary and Reflection

    This week connected datasheets, toolchains, and real hardware testing. Comparing two MCU families (RP2040 vs ESP32) made it clear how architecture and integrated peripherals change the workflow: RP2040 is excellent for deterministic microcontroller tasks and fast prototyping, while ESP32 is extremely convenient for connected applications because WiFi/BLE and FreeRTOS are built-in.

    My individual system demonstrates a complete embedded pipeline: sense (button) → act (LED) → communicate (UART + WiFi+ MQTT). Debugging with serial logs on both boards helped me verify behavior step by step and avoid “black-box” errors.