Week 11: Networking and Communications

Planted April 14, 2026

Week 11: Networking and Communications

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 data
  • SCL (Serial Clock) synchronizes timing

I used a GME12864-7B OLED, the version I didn’t use in the production week

Group assignment

Link to our group assignment

Connections

  • VCC -> 5V
  • GND -> GND
  • SCL -> GPIO 6
  • SDA -> 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();
}

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:

  1. BCLK (bit clock) - the ESP32 toggles this line. Every rising or falling edge is one bit time on the data line.
  2. 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).
  3. DATA - the mic shifts out one bit per BCLK. The ESP32 only listens (I2S_MODE_RX); it never drives data_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)

LinePCB pinESP32 GPIORole
BCLK (bit clock)9GPIO 8Master bit clock from ESP32
LRCLK / WS (word select)10GPIO 9Frame clock; left channel when SEL = GND
DOUT (data out)8GPIO 20Serial PCM from mic to ESP32 data_in

How the firmware interprets the stream

SettingValueMeaning
SAMPLE_RATE44100CD-quality frame rate; LRCLK toggles 44.1k times per second per channel
BUFFER_LEN512Samples per DMA chunk (~11.6 ms of audio at 44.1 kHz)
dma_buf_count8Eight chunks in rotation so capture never stalls while USB serial drains
i2s_read(..., portMAX_DELAY)blockCPU waits until one full buffer is captured; no busy-polling GPIO
Serial.print("mic:")plotter tagArduino 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 module - red top with e-ink display, encoder cutout, and push button

Feyncorder clip - short demo of the module on the bench.

Overall it was an OK week!