Week 11: Networking and Communications

Overview

This week I will be testing how to connect a keyboard to a PCB. My final project will have a handheld console that will require the use of a keyboard. I'm going to connect a wired Keyboard to my RP2040 console from weeks 4, 8, 9, and 10. I'm kind of nervous because the board is aready FULL and I did not leave myself ANY room to add components. So I am preparing myself for a battle.

The keyboard has its own ESP32-C61HF4 microcontroller and a fixed I2C bus address, which makes this a genuine two-node network rather than a simple peripheral wired to a GPIO pin.

This week I proved out the keyboard integration and communication layer on my existing test bench before committing to milling the final console PCB.

Group Assignment

Again, I am a remote student so I will be doing this week between my own two projects.

Individual Assignment

My keyboard is an Inter Integrated Circuit

An inter integrated circuit is a two wire serial communication protocol that allows multiple devices to share the same bus. One device acts as the controller and everything else is a peripheral (they listen and only respond when adressed by the controller directly.

The two wires are Serial Data and Serial Clock

  1. Serial Data: carries the actual data.
  2. Serial Clock: a clock signal generated by the controller that keeps everything in sync

Everytime my RP2040 calls a wire request it is specifically asking the keyboard (and only the keyboard) to send one byte. If a key has been pressed, that byte is the ASCII characer code. If nothing was pressed, it returns zeros and the sketch ignores it.

Why inter integrated circuits for this week?

My keyboard has a favtory default mode that is set for an inter integrated circuit so there does not need to be any firmware changes or mode switching. This was important to me because I value simplicity. It also only requires two data lines and if you've already seen my week 10 you know I'm STRESSED when it comes to available PCB real estate. My only alternative would have been a point to point system and doesn't have addressing or ESP now wich would have required going wireless. Again, simplicity was my biggest concern this week so going with the I2C allowed me to demostrate a real network with addresses.

What is Communicating:

Mode Role MCU Bus Address Local input and output
Keyboard Inter Integrated Circuit Peripheral ESP32-C61HF4 0X5F 42 Key keyboard input, RGB status LED
My custom PCB ft a RP2040 Inter Integrated Circuit Controller Seeed XIAO RP2040 Controller (no addy) My touchscreen, UV sensor, textile button, haptic feedback device

A note about addressing: If a non-zero byte comes backm a key was pressed. This is how the controller selects which node to communicate with. I hope this piece satisfys this week's addressing requirement.

Wiring

Bill of Mateirals - This week's addition

Component Part Interface
Keyboard ESP32-C61HF4 Via grove connector
My custom PCB ft a RP2040 Custom milled (weeks 4 and 8 of my documentation) Plugging via D4 and D5

Connections

Keyboard Grove Pin Signal Pin on RP2040 Note
GND GND GND Common Ground
5V Power 3V3 DO NOT use the 5V on the RP2040
G26 SDA D4 In week 10 I kept this pen for the keyboard
G25 SCL D5 In week 10 I kept this pen for the keyboard

Bring Up Method

I relied on my tape method again mentioned in previous weeks to make sure I can reuse my main components witout accidently damaging them. Once all four wires were positioned I ran a continuity check with my multimeter: each wire rang through to its intended pad, and I confirmed no adjacent wires were bridged to each other. Only after that check passed did I solder. I also verified that the keyboard's wires were fully isolated on the keyboard side before connecting

Wires wrapped and tested before plugging in Everything connected, Screen showing correctly

Programming

Toolchain

Same toolchain as weeks 8–10: Arduino IDE with the RP2040 core, board set to Seeed XIAO RP2040, COM6, serial at 115200. No new libraries were needed this week.

How the code works

In setup() I initialise the I2C bus by calling Wire.begin(D4, D5), which assigns SDA to pin D4 (GP6) and SCL to pin D5 (GP7). Everything else (the button, UV sensor, display, and haptic feedback) are unchanged from week 10.

In loop() I added a keyboard poll. Each cycle I call Wire.requestFrom(0x5F, 1) to ask the CardKB2 for one byte, then read it with Wire.read().

The UV footer, the textile button prompt advance, and motor timeout from weeks 9 and 10 all continue running in the same loop unchanged.

// ───────────────────────────────────────────────────────────── // Fab Academy Week 11 — Networking & Communications // Heaven Whitby — handheld journaling console (test bench) // Board: Seeed Studio XIAO RP2040 (custom milled PCB, Week 8) // // NETWORK: I2C bus // Node 1 (peripheral): M5Stack CardKB2 at address 0x5F // Node 2 (controller): This RP2040 — polls 0x5F each loop for keystrokes // // OUTPUT devices (preserved from Week 10): // - 2.8" ILI9341 SPI TFT (240x320) — displays prompt + typed response // - DC vibration motor via MOSFET on D2 (reminder buzz) // Inputs preserved from Week 9: // - Velostat fabric button on A0/D0 (press = clear response + next prompt) // - GUVA-S12SD analog UV sensor on A3 (shown in footer) // // NEW this week: // - Wire.h I2C init on D4 (SDA) / D5 (SCL) // - CardKB2 polled at 0x5F each loop — keystrokes appended to userInput string // - Backspace (0x08) removes last character // - userInput rendered live in the response area below the prompt // // Libraries: Adafruit GFX, Adafruit ILI9341, Wire (built into RP2040 core) // Toolchain: Arduino IDE + Earle Philhower RP2040 core, board = XIAO RP2040 // ───────────────────────────────────────────────────────────── #include #include #include #include // ── Display pins (hardware SPI on D8 SCK / D10 MOSI / D9 MISO) ── #define TFT_CS D1 // GP27 #define TFT_DC D6 // GP0 #define TFT_RST D7 // GP1 Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST); // ── I2C keyboard ── #define KB_ADDR 0x5F #define SDA_PIN D4 #define SCL_PIN D5 // ── Other I/O ── const int velostatPin = A0; const int uvPin = A3; const int motorPin = D2; // ── Velostat threshold ── int velostatBaseline = 0; const float PRESS_FRACTION = 0.5; int pressThreshold = 200; // ── Prompts ── const char* prompts[] = { "What pulled your attention today?", "Where did you feel most like yourself?", "Name one thing you're carrying that isn't yours.", "What would this morning's you want to hear right now?" }; const int promptCount = sizeof(prompts) / sizeof(prompts[0]); int currentPrompt = 0; // ── Timing ── const unsigned long PROMPT_WINDOW_MS = 20000; unsigned long promptStart = 0; bool reminded = false; bool wasPressed = false; // ── Typed response ── String userInput = ""; const int MAX_CHARS = 120; // ── Screen layout ── const int RESPONSE_Y = 160; const int RESPONSE_H = 120; const int REMINDER_Y = 268; const int STATUS_Y = 300; // ───────────────────────────────────────────────────────────── void setup() { Serial.begin(115200); analogReadResolution(12); pinMode(motorPin, OUTPUT); digitalWrite(motorPin, LOW); Wire.setSDA(SDA_PIN); Wire.setSCL(SCL_PIN); Wire.begin(); Serial.println("I2C initialised on D4/D5"); Wire.beginTransmission(KB_ADDR); byte err = Wire.endTransmission(); if (err == 0) { Serial.println("CardKB2 found at 0x5F"); } else { Serial.print("CardKB2 not found at 0x5F — error: "); Serial.println(err); } long sum = 0; for (int i = 0; i < 32; i++) { sum += analogRead(velostatPin); delay(5); } velostatBaseline = sum / 32; pressThreshold = (int)(velostatBaseline * PRESS_FRACTION); Serial.print("Velostat baseline="); Serial.print(velostatBaseline); Serial.print(" press threshold="); Serial.println(pressThreshold); tft.begin(); tft.setRotation(0); showPrompt(); Serial.println("Week 11 ready — I2C keyboard + LCD + velostat + UV + motor"); } // ───────────────────────────────────────────────────────────── void loop() { // 1. Poll CardKB2 Wire.requestFrom(KB_ADDR, 1); if (Wire.available()) { char key = Wire.read(); if (key != 0) { Serial.print("key: "); Serial.print((int)key); Serial.print(" ("); Serial.print(key);

Problems

Display blank and motor buzzing continuously on first power-up.

Lesson Learned:

When I first powered up the board with the CardKB2 connected, the display came on but showed nothing and the motor buzzed continuously.

I powered it off and started combing through the physical board. I didn't notice anything and tried plugging it back in. Unfortunately the buzzing continued with a blank screen.

Based on the buzzing continuing, I knew that it had to be something I'd done during soldering. I decided to pull out a magnifying glass to see if maybe there was something going on that I couldn't easily see. I then saw that there were some soldering issues related to my original fear of my board not being initally designed for a FILLED PCB.

Solder bridge between CS and motor resistor.

Lesson Learned:

I was really happy to find out why the motor was continuously buzzing was as "simple" as the resistor being interrupted. Then I was worried because the trace is SO tiny there and I had huge globs of flux all over that area. I had no idea what to do to get that area clear. All attempts to reheat and remove the flux was unsuccessful.

I don't have any solder wick so I used the flick method. The flick method worked good enough but I also grabbed my dull Xacto knife to try to "cut" the cold flux to separate it and destroy the bridge.

Ground Wire

Lesson Learned:

Even after clearing the bridge the display was still blank. I grabbed the magnifying glass again and noticed that the GRD from my touchscreen had come loose as well. Without a common ground the touchscreen didn't have a reference and everything was floating. It took a while but I was able to get it reconnected.

RST Wrie

Lesson Learned:

After fixing the gnd joint the serial monitor confirmed the sketch was running and I could see pressure and UV index values printing correctly but the display remained blank. I glanced over to my setup and noticed that in my rush to reconnect the gnd wire I somehow knocked the reset wire loose. A floating RST pin leaves the display in an undefined state on startup and prevents initialiation. I resolderd RST back to D7 and the display came back immmediatley.

Gallery

Wires wrapped and tested before plugging in Wires wrapped and tested before plugging in Wires wrapped and tested before plugging in

Videos

From Vimeo

Sound Waves from George Gally (Radarboy) on Vimeo.


From Youtube

3D Models