Week 11: Networking and Communications
Planted April 14, 2026

Overview
For this week, I focused on I2C communication using a small OLED display. This week was generally about networking 101 and was easy going since I had the fundamentals down
I2C is a two-wire communication protocol used to connect low-speed devices (sensors, displays, etc.) to microcontrollers:
SDA(Serial Data) carries dataSCL(Serial Clock) synchronizes timing
I used a GME12864-7B OLED, the version I didn’t use in the production week
Group assignment
Connections
VCC -> 5VGND -> GNDSCL -> GPIO 6SDA -> GPIO 7
Problem
I had trouble getting the screen to work at first. That was mainly caused by the library I used which was SSD1306’s library. Hence I decided to switch to the U8g2 library which is more universal for i2c oled’s and the problem was fixed.
Connection test image
First I ran a connection test! It worked

#include <Wire.h>
#include <U8g2lib.h>
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0);
static const unsigned char starfleet_bmp[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ... trimmed for readability ... */ 0x00, 0x00
};
void setup() {
Wire.begin(6, 7);
u8g2.begin();
}
void loop() {
u8g2.clearBuffer();
u8g2.drawXBMP(0, 0, 128, 64, starfleet_bmp);
u8g2.sendBuffer();
}
Node usage, displaying a star trek logo
Next to make captain Picard proud I displayed the Star Trek next generation logo on the node.

#include <Wire.h>
#include <U8g2lib.h>
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0);
#define LOGO_W 16
#define LOGO_H 16
static const unsigned char logo_bmp[] PROGMEM = {
0x10, 0x00, 0x30, 0x00, 0x28, 0x00, 0x7c, 0x00,
0x54, 0x00, 0xd6, 0x00, 0xbe, 0x00, 0xba, 0x01,
0x82, 0x01, 0xf3, 0x01, 0x9b, 0x01, 0x0d, 0x01,
0x07, 0x03, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00
};
#define NUM_LOGOS 5
struct Logo {
float x, y;
float dx, dy;
};
Logo logos[NUM_LOGOS];
void setup() {
Wire.begin(6, 7);
u8g2.begin();
randomSeed(analogRead(0));
for (int i = 0; i < NUM_LOGOS; i++) {
logos[i].x = random(0, 128 - LOGO_W);
logos[i].y = random(0, 64 - LOGO_H);
logos[i].dx = (random(10, 25) / 10.0) * (random(2) ? 1 : -1);
logos[i].dy = (random(8, 20) / 10.0) * (random(2) ? 1 : -1);
}
}
void loop() {
u8g2.clearBuffer();
for (int i = 0; i < NUM_LOGOS; i++) {
u8g2.drawXBMP((int)logos[i].x, (int)logos[i].y, LOGO_W, LOGO_H, logo_bmp);
logos[i].x += logos[i].dx;
logos[i].y += logos[i].dy;
if (logos[i].x <= 0 || logos[i].x >= 128 - LOGO_W) {
logos[i].dx = -logos[i].dx;
logos[i].x = constrain(logos[i].x, 0, 128 - LOGO_W);
}
if (logos[i].y <= 0 || logos[i].y >= 64 - LOGO_H) {
logos[i].dy = -logos[i].dy;
logos[i].y = constrain(logos[i].y, 0, 64 - LOGO_H);
}
}
u8g2.sendBuffer();
delay(30);
}
Floating star trek logos
Finally, to have some more fun and to make amends with vibe coding version one I made some floating star trek logo’s. Live Long and Prosper!
#include <Wire.h>
#include <U8g2lib.h>
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0);
static const unsigned char starfleet_bmp[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbc, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9c, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x67, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x63, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x63, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x61, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x61, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xe0, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xe0, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xf0, 0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xf0, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0xf0, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0xf0, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0xf0, 0x81, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0xf0, 0x01, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xf0, 0x01, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xfe, 0x07, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xce, 0xff, 0x3f, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0xff, 0x1f, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xfc, 0x07, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xf8, 0x01, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x98, 0x03, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x0c, 0x03, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x03, 0x0c, 0x03, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x10, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x7e, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x80, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0xc0, 0xff, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0xf0, 0xe7, 0xe3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x01, 0xf8, 0xc1, 0xe3, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0xfc, 0x80, 0xe7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x3e, 0x00, 0xef, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x1f, 0x00, 0xdf, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xc0, 0x0f, 0x00, 0xde, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xe0, 0x07, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0x01, 0x00, 0xf8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xf8, 0x00, 0x00, 0xf8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x7c, 0x00, 0x00, 0xf0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x3c, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x1e, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x1f, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x0f, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
void setup() {
Wire.begin(6, 7);
u8g2.begin();
}
void loop() {
u8g2.clearBuffer();
u8g2.drawXBMP(0, 0, 128, 64, starfleet_bmp);
u8g2.sendBuffer();
}
Feyncorder (final project)
Magnetic encoder daughterboard for Pockety - assembled module with e-ink face, encoder pocket, and button. More on molding and PCB: project development.
I2S microphone bring-up (ICS43434)
The clip below uses an ICS43434 digital MEMS microphone on the Feyncorder PCB. Earlier in this week I used I2C for the OLED (two wires, register reads, shared bus). The mic is different: it streams PCM audio on a dedicated I2S bus. I2C is fine for slow sensors and small displays; I2S is built for continuous, clocked audio samples.
What I2S is
I2S (Inter-IC Sound) is a 3-wire serial link for digital audio:
- BCLK (bit clock) - the ESP32 toggles this line. Every rising or falling edge is one bit time on the data line.
- LRCLK / WS (word select) - also called frame clock. It flips once per audio sample. When WS is low, the current frame is the left channel; when high, it is the right channel (stereo pairs share one data wire).
- DATA - the mic shifts out one bit per BCLK. The ESP32 only listens (
I2S_MODE_RX); it never drivesdata_out.
The ESP32-C3 is I2S master: it generates BCLK and LRCLK. The ICS43434 is slave: it follows those clocks and pushes samples on DOUT. That matches I2S_MODE_MASTER | I2S_MODE_RX in the sketch.
How one sample fits on the wire
At 44.1 kHz, LRCLK completes 44,100 left/right frames per second. With I2S_CHANNEL_FMT_ONLY_LEFT and SEL tied to GND on the PCB, we only care about the left half of each frame (the mic is wired for left-channel output).
The ICS43434 outputs 24-bit PCM, but the ESP32 driver is set to I2S_BITS_PER_SAMPLE_32BIT. Each sample arrives in a 32-bit slot: the real audio sits in the top 24 bits, and the lower 8 bits are padding. That is why the loop does samples[i] >> 8 before printing: it strips the unused low byte and leaves a signed value you can plot.
I2S_COMM_FORMAT_STAND_I2S means default Philips I2S framing (MSB first, WS defines channel, one sample per WS half-period). No custom TDM or left-justified override is needed for this mic.
Pin map (PCB to MCU)
| Line | PCB pin | ESP32 GPIO | Role |
|---|---|---|---|
| BCLK (bit clock) | 9 | GPIO 8 | Master bit clock from ESP32 |
| LRCLK / WS (word select) | 10 | GPIO 9 | Frame clock; left channel when SEL = GND |
| DOUT (data out) | 8 | GPIO 20 | Serial PCM from mic to ESP32 data_in |
How the firmware interprets the stream
| Setting | Value | Meaning |
|---|---|---|
SAMPLE_RATE | 44100 | CD-quality frame rate; LRCLK toggles 44.1k times per second per channel |
BUFFER_LEN | 512 | Samples per DMA chunk (~11.6 ms of audio at 44.1 kHz) |
dma_buf_count | 8 | Eight chunks in rotation so capture never stalls while USB serial drains |
i2s_read(..., portMAX_DELAY) | block | CPU waits until one full buffer is captured; no busy-polling GPIO |
Serial.print("mic:") | plotter tag | Arduino Serial Plotter graphs the mic channel as a live waveform |
Data path (end to end)
ICS43434 (MEMS + ADC) -> I2S DOUT -> ESP32 I2S peripheral + DMA -> samples[] -> >> 8 -> USB Serial Plotter
Sound pressure moves the MEMS diaphragm; the chip digitizes it and clocks out bits on DOUT synchronized to BCLK and LRCLK. The ESP32 peripheral fills samples[] in the background. The main loop only formats and prints, which is enough to confirm the bus is alive and to watch level changes when you tap the board or speak near the mic.
#include <driver/i2s.h>
// --- Pin mapping from PCB ---
// PCB pin 9 = BCLK = D8 = GPIO8
// PCB pin 10 = LRCL = D9 = GPIO9
// PCB pin 8 = DOUT = D7 = GPIO20
#define I2S_SCK_PIN 8 // BCLK
#define I2S_WS_PIN 9 // LRCLK (Word Select)
#define I2S_DATA_PIN 20 // DOUT (mic data -> ESP32)
#define SAMPLE_RATE 44100
#define BUFFER_LEN 512
int32_t samples[BUFFER_LEN];
void setup() {
Serial.begin(115200);
while (!Serial);
i2s_config_t config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = SAMPLE_RATE,
.bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // SEL tied to GND
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 8,
.dma_buf_len = BUFFER_LEN,
.use_apll = false,
};
i2s_pin_config_t pins = {
.bck_io_num = I2S_SCK_PIN,
.ws_io_num = I2S_WS_PIN,
.data_out_num = I2S_PIN_NO_CHANGE,
.data_in_num = I2S_DATA_PIN,
};
i2s_driver_install(I2S_NUM_0, &config, 0, NULL);
i2s_set_pin(I2S_NUM_0, &pins);
i2s_zero_dma_buffer(I2S_NUM_0);
Serial.println("ICS43434 ready - constant recording started.");
}
void loop() {
size_t bytes_read = 0;
// Blocks until BUFFER_LEN samples are captured - no polling needed
i2s_read(I2S_NUM_0, samples, sizeof(samples), &bytes_read, portMAX_DELAY);
int count = bytes_read / sizeof(int32_t);
for (int i = 0; i < count; i++) {
// Shift right 8 bits to extract the 24-bit audio value
int32_t s = samples[i] >> 8;
// Format for Arduino Serial Plotter
Serial.print("mic:");
Serial.println(s);
}
}

Feyncorder clip - short demo of the module on the bench.
Overall it was an OK week!