Week 10: Output Devices

Planted March 31, 2026

Week 10: Output Devices

This week was a week that I didn’t get to experiment much due to it coinciding with our schools exam week below. Below is some coding I did with output devices readily avaliable.

WS2812B

I started out with the RGB LED WS2812B just getting a R,G,B loop on it


#include <Adafruit_NeoPixel.h>

#define PIN        3
#define NUM_LEDS   1

Adafruit_NeoPixel strip(NUM_LEDS, PIN, NEO_GRB + NEO_KHZ800);

void setup() {
  strip.begin();
  strip.show(); 
}

void loop() {

  strip.setPixelColor(0, strip.Color(255, 0, 0));
  strip.show();
  delay(1000);

  // GREEN
  strip.setPixelColor(0, strip.Color(0, 255, 0));
  strip.show();
  delay(1000);


  strip.setPixelColor(0, strip.Color(0, 0, 255));
  strip.show();
  delay(1000);
}

WS2812B

Next it was onto programming the most commonplace output device for me, the servo. below is “hello world” spin code I ran on it

video

code

#include <Adafruit_NeoPixel.h>
#include <ESP32Servo.h>

#define LED_PIN 9
#define NUMPIXELS 1
#define SERVO_PIN 3


Servo myServo;

int angle = 0;
int direction = 1;
int colorState = 0;
unsigned long lastMove = 0;


void setup() {
  pixel.begin();
  pixel.setBrightness(50);
  myServo.attach(SERVO_PIN);
}

void loop() {

  if (now - lastMove > 15) {
    angle += direction;
    if (angle >= 180 || angle <= 0) direction *= -1;
    myServo.write(angle);
    lastMove = now;
  }
}

OLED

next it was onto the most fun part of them all i2c and oled.

I decided to re-create a couple of this I worked with back when I was learning / using OpenProcessing

First I created Conway’s game of life

video

code

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

#define SDA_PIN 6
#define SCL_PIN 7

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

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

#define W 128
#define H 64

uint8_t grid[W][H];
uint8_t newGrid[W][H];

void setup() {
  Wire.begin(SDA_PIN, SCL_PIN);

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    while (1);
  }

  display.clearDisplay();

  randomSeed(analogRead(0));

  for (int x = 0; x < W; x++) {
    for (int y = 0; y < H; y++) {
      grid[x][y] = random(2); // 0 or 1
    }
  }
}

int countNeighbors(int x, int y) {
  int count = 0;

  for (int dx = -1; dx <= 1; dx++) {
    for (int dy = -1; dy <= 1; dy++) {
      if (dx == 0 && dy == 0) continue;

      int nx = (x + dx + W) % W;
      int ny = (y + dy + H) % H;

      count += grid[nx][ny];
    }
  }

  return count;
}

void loop() {
  for (int x = 0; x < W; x++) {
    for (int y = 0; y < H; y++) {
      int neighbors = countNeighbors(x, y);

      if (grid[x][y] == 1) {
        if (neighbors < 2 || neighbors > 3)
          newGrid[x][y] = 0;
        else
          newGrid[x][y] = 1;
      } else {
        if (neighbors == 3)
          newGrid[x][y] = 1;
        else
          newGrid[x][y] = 0;
      }
    }
  }

  for (int x = 0; x < W; x++) {
    for (int y = 0; y < H; y++) {
      grid[x][y] = newGrid[x][y];
    }
  }

  display.clearDisplay();

  for (int x = 0; x < W; x++) {
    for (int y = 0; y < H; y++) {
      if (grid[x][y]) {
        display.drawPixel(x, y, SSD1306_WHITE);
      }
    }
  }

  display.display();
  delay(50);
}

Then I created a simple flocking simulation

video

code

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

#define SDA_PIN 6
#define SCL_PIN 7

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

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

#define NUM_BOIDS 25

struct Boid {
  float x, y;
  float vx, vy;
};

Boid boids[NUM_BOIDS];

float maxSpeed = 1.5;
float neighborDist = 12;
float separationDist = 6;

void setup() {
  Wire.begin(SDA_PIN, SCL_PIN);

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    while (1);
  }

  display.clearDisplay();
  randomSeed(analogRead(0));

  for (int i = 0; i < NUM_BOIDS; i++) {
    boids[i].x = random(SCREEN_WIDTH);
    boids[i].y = random(SCREEN_HEIGHT);
    boids[i].vx = random(-10, 10) / 10.0;
    boids[i].vy = random(-10, 10) / 10.0;
  }
}

void limitSpeed(Boid &b) {
  float speed = sqrt(b.vx * b.vx + b.vy * b.vy);
  if (speed > maxSpeed) {
    b.vx = (b.vx / speed) * maxSpeed;
    b.vy = (b.vy / speed) * maxSpeed;
  }
}

void loop() {
  for (int i = 0; i < NUM_BOIDS; i++) {
    float sepX = 0, sepY = 0;
    float alignX = 0, alignY = 0;
    float cohX = 0, cohY = 0;

    int count = 0;
    for (int j = 0; j < NUM_BOIDS; j++) {
      if (i == j) continue;
      float dx = boids[j].x - boids[i].x;
      float dy = boids[j].y - boids[i].y;
      float dist = sqrt(dx * dx + dy * dy);

      if (dist < neighborDist) {
        alignX += boids[j].vx;
        alignY += boids[j].vy;
        cohX += boids[j].x;
        cohY += boids[j].y;
        count++;

        if (dist < separationDist) {
          sepX -= dx;
          sepY -= dy;
        }
      }
    }

    if (count > 0) {
      alignX /= count;
      alignY /= count;
      cohX = (cohX / count) - boids[i].x;
      cohY = (cohY / count) - boids[i].y;
    }

    boids[i].vx += sepX * 0.05 + alignX * 0.05 + cohX * 0.01;
    boids[i].vy += sepY * 0.05 + alignY * 0.05 + cohY * 0.01;
    limitSpeed(boids[i]);
  }

  for (int i = 0; i < NUM_BOIDS; i++) {
    boids[i].x += boids[i].vx;
    boids[i].y += boids[i].vy;
    if (boids[i].x < 0) boids[i].x = SCREEN_WIDTH;
    if (boids[i].x >= SCREEN_WIDTH) boids[i].x = 0;
    if (boids[i].y < 0) boids[i].y = SCREEN_HEIGHT;
    if (boids[i].y >= SCREEN_HEIGHT) boids[i].y = 0;
  }

  display.clearDisplay();
  for (int i = 0; i < NUM_BOIDS; i++) {
    display.drawPixel((int)boids[i].x, (int)boids[i].y, SSD1306_WHITE);
  }
  display.display();
  delay(30);
}

then I moved onto a classic graphics challenge. 3D wireframe cube

video

code

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

#define SDA_PIN 6
#define SCL_PIN 7
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

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

float angleX = 0;
float angleY = 0;

struct Point3D { float x, y, z; };

Point3D cube[8] = {
  {-1, -1, -1}, { 1, -1, -1}, { 1,  1, -1}, {-1,  1, -1},
  {-1, -1,  1}, { 1, -1,  1}, { 1,  1,  1}, {-1,  1,  1}
};

int edges[12][2] = {
  {0,1},{1,2},{2,3},{3,0},
  {4,5},{5,6},{6,7},{7,4},
  {0,4},{1,5},{2,6},{3,7}
};

void project(Point3D p, int &x2d, int &y2d) {
  float scale = 50;
  float z = p.z + 4;
  x2d = (int)(p.x * scale / z) + 64;
  y2d = (int)(p.y * scale / z) + 32;
}

void setup() {
  Wire.begin(SDA_PIN, SCL_PIN);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
}

void loop() {
  display.clearDisplay();
  Point3D r[8];

  for (int i = 0; i < 8; i++) {
    float x = cube[i].x;
    float y = cube[i].y;
    float z = cube[i].z;

    float cosX = cos(angleX);
    float sinX = sin(angleX);
    float y1 = y * cosX - z * sinX;
    float z1 = y * sinX + z * cosX;

    float cosY = cos(angleY);
    float sinY = sin(angleY);
    float x2 = x * cosY + z1 * sinY;
    float z2 = -x * sinY + z1 * cosY;
    r[i] = {x2, y1, z2};
  }

  for (int i = 0; i < 12; i++) {
    int x0, y0, x1, y1;
    project(r[edges[i][0]], x0, y0);
    project(r[edges[i][1]], x1, y1);
    display.drawLine(x0, y0, x1, y1, SSD1306_WHITE);
  }

  display.display();
  angleX += 0.05;
  angleY += 0.04;
  delay(15);
}

Overall I was sad that I couldn’t get as much done or get to experiment with many things as I did in previous weeks, but I was able to atleast get the core stuff for this week done. It was an okay week.