Week 02 — Electronics & Embedded Programming

Inputs → code → outputs. From a heartbeat "tick" to my own touch-controlled plant on an OLED.

How to Survive Soldering & Embedded Programming

Welcome to my crash course in being confused, clicking too many things, and eventually making something! This is my beginner-friendly guide to going from "I have no idea what's happening" to real, working code on hardware.
Note: We're all learning. It's new, and that's okay.

1) What is embedded programming

A microcontroller is a tiny computer that runs one focused program. The sketch has two parts: setup() runs once, and loop() runs forever. The loop is basically read → decide → act.

void setup() {  // runs once at power-up
  // pinMode(...), Serial.begin(...), etc.
}
void loop() {   // runs forever
  // read → map → output
}

Lecture notes (Anthony): stay at 3.3 V on RP2040 pins, prove life with a blink/serial test first, then add features gradually. Good soldering is part of debugging.

2) Soldering (intro + my process)

Goal: each castellated pad is fully wetted—joining the module edge pad and my PCB pad. I tinned the iron, used flux, tack-soldered corners, then ran a small bead across each edge. Final step: continuity checks.

Embed: Anthony's "Intro to Soldering"

Embed: my XIAO RP2040 soldering timelapse

Good wetting on castellated pads
Good wetting on the castellated edges (shiny, continuous).
Continuity test
Continuity test: probe from XIAO pad → matching PCB net (listen for the beep).
What I changed after feedback: I was under-soldering at first (too cautious). Adding enough solder to fully bridge pad↔pad fixed a touch pad that wasn't reading.

3) Initial sanity tests (thanks, Quentin)

I validated the hardware using Quentin's minimal Arduino sketches. These prove three layers quickly: microcontroller runs code (blink), I²C display works (hello text), and touch pads respond (cap sense + LED example).

Testing

3.1 Blink (is the microcontroller alive?)

// Blink — basic liveness test (XIAO RP2040)
void setup(){
  pinMode(LED_BUILTIN, OUTPUT);
}
void loop(){
  digitalWrite(LED_BUILTIN, HIGH); delay(500);
  digitalWrite(LED_BUILTIN, LOW);  delay(500);
}

If this blinks, the RP2040 is running code and GPIO works.

Video: my blink test.

3.2 Display (SSD1306 over I²C)

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

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define SCREEN_ADDRESS 0x3C  // sometimes 0x3D

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

void setup() {
  Serial.begin(0);           // RP2040 USB CDC (auto)
  delay(50);                 // let the screen power up
  display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
  display.clearDisplay();
  display.display();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
}

void loop() {
  display.clearDisplay();
  display.setCursor(28, 25);
  display.print("Hello world!");
  display.display();
}

Wiring used: SDA→D4, SCL→D5, VCC→3V3, GND→GND. If you see "Hello world!" the I²C bus and OLED are good.

Video: OLED "Hello world!"

3.3 Touch (capacitive pads + LED example)

#define PIN_RED   17
#define PIN_GREEN 16
#define PIN_BLUE  25

#define N_TOUCH   6
#define THRESHOLD 30

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]  = {0};
bool pin_touched_past[N_TOUCH] = {0};

void update_touch() {
  int t, t_max = 200, p;
  for (int i = 0; i < N_TOUCH; i++) {
    p = touch_pins[i];
    pinMode(p, OUTPUT); digitalWriteFast(p, LOW);
    delayMicroseconds(25);
    noInterrupts();
    pinMode(p, INPUT_PULLUP);
    t = 0;
    while (!digitalReadFast(p) && t < t_max) { t++; }
    touch_values[i] = t;
    interrupts();
    pin_touched_past[i] = pin_touched_now[i];
    pin_touched_now[i]  = touch_values[i] > THRESHOLD;
  }
}

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

void setup() {
  Serial.begin(0);  // RP2040 USB CDC
  pinMode(PIN_RED, OUTPUT); pinMode(PIN_GREEN, OUTPUT); pinMode(PIN_BLUE, OUTPUT);
  digitalWrite(PIN_RED, HIGH); digitalWrite(PIN_GREEN, HIGH); digitalWrite(PIN_BLUE, HIGH); // HIGH = off with this wiring
}

void loop() {
  update_touch();

  // example: button 0 press/release toggles GREEN LED
  if (pin_touched_now[0] && !pin_touched_past[0]) { digitalWrite(PIN_GREEN, LOW);  } // on
  if (!pin_touched_now[0] && pin_touched_past[0]) { digitalWrite(PIN_GREEN, HIGH); } // off

  print_touch();
  delay(50);
}

On touch, numbers jump in Serial Monitor. This proves the capacitive pads + GPIO are behaving.

Video: touch readings + LED reaction.

Source sketches and more examples: Quentin Bolsee — qpad-xiao / Arduino . Note: Serial.begin(0) is valid on RP2040's USB CDC (auto-baud). On other boards you usually use 115200.

4) Re-solder (robust) — the mistake that taught me fast

I powered the PCB while it rested on metal → OLED went blank. I swapped in a fresh XIAO and re-soldered carefully. Lesson: protect the back of the board, use a non-conductive mat, and sanity-test after rework.

After re-solder: clean, wetted joints
After re-solder: clean, continuous joints, no bridges.

5) My own testing phase (post re-solder)

With the fresh XIAO RP2040 soldered in, I began verifying that everything worked. I started with Quentin's base sketches to confirm the board was alive, then modified them myself. These tests show inputs, outputs, and communication all working.

5.1 Heartbeat → "tick" → "hello"

I combined Serial and LED in a heartbeat sketch. Originally it printed "tick" each second — I edited it to say "hello." This was my first authored modification to someone else's code.

// Heartbeat serial
static unsigned long t0 = millis();
if (millis() - t0 > 1000) {
  t0 = millis();
  Serial.println("hello"); // my edit
}

Microcontroller & Board (what I'm actually programming)

Microcontroller (chip): RP2040

  • Dual-core Arm Cortex-M0+ up to 133 MHz; 264 KB SRAM; external flash
  • USB device, GPIO (digital / PWM / analog), timers, I²C / SPI / UART
  • Logic level: 3.3 V (not 5 V tolerant)

Board (module): Seeed Studio XIAO RP2040

  • USB-C (power + programming), 3.3 V regulator, boot/reset (UF2), user LED
  • Castellated pins break out the RP2040's GPIO so I can solder/use them

My understanding is: the RP2040 is the engine; the XIAO board is the car around it (USB, power, pins).

Why both matter: the datasheet tells me what the chip can do; the board docs tell me how those pins are wired so I can actually use them.

Summary: I am programming the RP2040 microcontroller via the XIAO RP2040 board on my PCB.

Group Assignment — Toolchains & Workflows (Reference)

For group assignment we were asked to compare embedded architectures and development workflows (Arduino C/C++, MicroPython/CircuitPython, toolchains, and bootloaders). See our group page and a classmate's write-up for details:

My quick takeaway: XIAO RP2040's UF2 bootloader makes flashing easy (drag-and-drop), Arduino IDE gives fast iteration in C/C++, and Python firmwares (CircuitPython/MicroPython) trade raw performance for ease and REPL.

My program — touch-controlled plant on the OLED

I wanted something a bit poetic: a plant that "grows" and "sways" when I touch the pads. Touch values are normalized to 0..1 and mapped to parameters (stem height, sway angle, bloom size). OLED is SSD1306 128×64 over I²C: SDA → D4, SCL → D5, VCC → 3V3, GND → GND. Update TOUCH_PINS to match your board.

Touch-controlled plant growing and swaying on OLED display
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET   -1
#define I2C_ADDR     0x3C

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// === Update to YOUR touch pad pins ===
const int TOUCH_PINS[] = {2, 3, 4};
const int N = sizeof(TOUCH_PINS)/sizeof(TOUCH_PINS[0]);

uint32_t tMin[3] = {100000,100000,100000}, tMax[3] = {0,0,0};

// crude capacitive read: higher when touched
uint32_t readCapRaw(int pin, int samples=6){
  uint32_t total=0;
  for(int s=0; s<samples; s++){
    pinMode(pin, OUTPUT); digitalWrite(pin, LOW); delayMicroseconds(5);
    pinMode(pin, INPUT_PULLUP);
    uint32_t t0 = micros();
    while(digitalRead(pin)==LOW){ if(micros()-t0 > 3000) break; }
    total += (micros()-t0);
  }
  return total / samples;
}
float clamp01(float v){ return v<0?0:v>1?1:v; }
float norm(float v, float lo, float hi){ return (hi<=lo+1)?0.0f: (v-lo)/(hi-lo); }

void setup(){
  pinMode(LED_BUILTIN, OUTPUT);
  Wire.begin();
  if(!display.begin(SSD1306_SWITCHCAPVCC, I2C_ADDR)){
    while(true){ digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); delay(100); }
  }
  display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE);
  display.setCursor(0,0);
  display.println("Touch plant: D4/D5=I2C");
  display.println("Pad0=grow 1=sway 2=bloom");
  display.display();
  delay(600);
}

void loop(){
  uint32_t cap[3] = {0,0,0};
  for(int i=0; i<N && i<3; i++){
    cap[i] = readCapRaw(TOUCH_PINS[i], 10);
    if(cap[i] < tMin[i]) tMin[i] = cap[i];
    if(cap[i] > tMax[i]) tMax[i] = cap[i];
  }

  float n0 = (N>0) ? clamp01(norm(cap[0], tMin[0], tMax[0])) : 0.0f; // height
  float n1 = (N>1) ? clamp01(norm(cap[1], tMin[1], tMax[1])) : 0.5f; // sway
  float n2 = (N>2) ? clamp01(norm(cap[2], tMin[2], tMax[2])) : 0.3f; // bloom

  int   stemLen = (int)(20 + n0 * 42);
  float sway    = (n1 - 0.5f) * 0.8f;
  int   bloomR  = (int)(3 + n2 * 10);

  display.clearDisplay();

  int baseX=64, baseY=62;
  display.drawLine(0, baseY, 127, baseY, SSD1306_WHITE);

  int x0=baseX, y0=baseY;
  int x1=baseX, y1=baseY - stemLen/3;
  int x2=baseX + (int)(sway*18), y2=baseY - (2*stemLen)/3;
  int x3=baseX, y3=baseY - stemLen;

  display.drawLine(x0,y0, x1,y1, SSD1306_WHITE);
  display.drawLine(x1,y1, x2,y2, SSD1306_WHITE);
  display.drawLine(x2,y2, x3,y3, SSD1306_WHITE);

  auto leaf=[&](int x,int y,float ang,int len){
    int x2=x+(int)(cos(ang)*len), y2=y+(int)(sin(ang)*len);
    display.drawLine(x,y,x2,y2,SSD1306_WHITE);
    display.drawLine(x,y,x2,y2+3,SSD1306_WHITE);
    display.drawLine(x2,y2,x2,y2+3,SSD1306_WHITE);
  };
  leaf(baseX-8, baseY - stemLen*2/5, -1.9f + sway*0.6f, 10 + (int)(n0*8));
  leaf(baseX+8, baseY - stemLen*3/5,  1.9f + sway*0.6f, 10 + (int)(n0*8));

  display.drawCircle(x3, y3, bloomR, SSD1306_WHITE);
  display.fillCircle(x3, y3, bloomR/2, SSD1306_WHITE);

  display.setTextSize(1); display.setCursor(0,0);
  display.print("c0:"); display.print(cap[0]);
  display.print(" c1:"); display.print(cap[1]);
  display.print(" c2:"); display.print(cap[2]);
  display.display();

  delay(30);
}

Arduino Programming Cheat Card (XIAO RP2040)

void setup() { /* runs once at startup */ }
void loop()  { /* runs forever */ }

// Pins
pinMode(pin, OUTPUT);
pinMode(pin, INPUT_PULLUP);
digitalWrite(pin, HIGH);   // on
digitalWrite(pin, LOW);    // off
int d = digitalRead(pin);  // HIGH/LOW

// Analog & Serial
int a = analogRead(A0);    // 0..1023
Serial.begin(115200);
Serial.println("Hello");

// Timing
delay(500);                 // 500 ms = 0.5 s
unsigned long t = millis(); // ms since boot

Troubleshooting (what actually happened)

  • Serial gibberish → baud mismatch: set 115200.
  • Blank OLED → check I²C wiring (SDA=D4, SCL=D5), try 0x3C vs 0x3D, power cycle.
  • Touch flatline → wrong pins in TOUCH_PINS or a dry joint; reflow with flux.
  • After soldering → clean flux, continuity test, re-run "blink + hello" before complex code.

How this meets the assignment

  • Datasheet: I read the RP2040 + XIAO docs (see Sources) and summarized chip vs board, voltage limits, and I²C pins.
  • Program & interaction: I authored an Arduino sketch that reads local inputs (touch pads) and drives local outputs (OLED drawing, LED).
  • Communication: I used USB Serial for the heartbeat "tick → hello" and raw touch prints. I also used a Processing sketch on my laptop as a remote visualization (serial → animation). [I'll add screenshots here]
  • Extra credit (envs/languages): Primary in Arduino (C/C++). I experimented with Processing (host-side visualization toolchain). Next I may try CircuitPython to compare workflows.

Sources

  • In-class & recitations; electronics presentation by Anthony.
  • Quentin Bolsee — XIAO RP2040 sanity tests (touch/LED), notes & code references.
  • Seeed Studio XIAO RP2040 docs (pinout, I²C), RP2040 datasheet.
  • Group reference: EECS Week 03 page
  • Example group section: Saleem's week 2
  • ChatGPT (GPT-5 Thinking) — tutoring and code scaffolding. Verified on my hardware and adapted to my PCB.