Close
0%
0%

Awesome Mix Vol. 1

Cassette Tape SAO with backlit LED spools and I2C-triggered visualizations animating Fast Forward, Rewind, Play, and Pause.

Public Chat
Similar projects worth following
This is an SAO project that is focused on demonstrating badge-to-SAO I2C communication, with a goal of providing a framework for how conference badges can activate SAO functionality and interact with SAOs without prior knowledge of their featureset.

Git Repo:

https://bitbucket.org/di0/fuff-over-i2c

BOM:

2 x WS2812B RGB LED Array:

-- Amazon Link:
https://www.amazon.com/gp/product/B0C7C9HP9R/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1

-- Digikey Part Link:
https://www.digikey.com/en/products/detail/adafruit-industries-llc/2226/5878434

-- Datasheet:
https://www.digikey.com/en/htmldatasheets/production/2371852/0/0/1/ws2812b-led

  • PoC Firmware Feature Complete

    Dustin Johnson11/03/2024 at 19:48 0 comments

    2 days of hacking at Supercon8 tables and what do we have?!

    ... PoC Feature-Complete!

    - RP2040 Slave PoC firmware feature-complete? Yes.

    - RP2040 Master PoC firmware feature-complete? Yes.

    - FUFF transport-layer protocol over-I2C implemented? Yes.
         - payloads (file xfer) working from RP2040 master to slaves? Yes.
         - RPC commands working from master to slave (activates SAO features)? Yes.

    - Circuit mounted on Supercon8 SAO protoboard, demoing on badge? In-progress...

    - Custom PCB? No. Focused on the comm stack, as is the theme of the SAO contest.

    Git repo (includes example code for RP2040 I2C Slave):

         - https://bitbucket.org/di0/fuff-over-i2c

    Video:

  • Porting from Arduino (C++) to RP2040 (MicroPython)

    Dustin Johnson10/25/2024 at 06:24 0 comments

    I needed to port my Arduino design onto the RP2040. I was going to use a boost converter, but I don't have one, so I just decided to use the 5V rail on the RP2040-One that gets "populated" on the RP2040 when powered over USB. If I take care of the 5V rail on the circuit later, the code should be the same either way.

    We can see I have another rp2040 "SAO" connected via a directly soldered I2C bus ;) I'm working on the I2C communication -- the disconnected rp2040-Matrix you see connected will be pretending to be the conference badge (the I2C master), for now.

    Pics of the updated prototype circuit:

    A video of the same animations playing, but now through micropython:

    The ported code is as follows:

    # Arduino code ported to RP2040 micropython
    # -----------------------------------------
    import machine
    import neopixel
    import time
    import random
    
    LED_PIN = 8
    LED_COUNT = 14
    LED_BRIGHTNESS = 0.05  # Start with 0.1 to check visibility; adjust to preferred level
    LED_CENTER_LEFT = 0
    LED_CENTER_RIGHT = 7
    LED_LEFT_MIN = 1  # Exclude center LED (left spool)
    LED_LEFT_MAX = 6  # Exclude center LED (left spool)
    LED_RIGHT_MIN = 8  # Exclude center LED (right spool)
    LED_RIGHT_MAX = 13  # Exclude center LED (right spool)
    
    # Initialize NeoPixel
    np = neopixel.NeoPixel(machine.Pin(LED_PIN), LED_COUNT)
    
    LED_LEFT_INDEX = random.randint(LED_LEFT_MIN, LED_LEFT_MAX)
    LED_RIGHT_INDEX = random.randint(LED_RIGHT_MIN, LED_RIGHT_MAX)
    
    # Original colors
    FOREGROUND_COLOR = (0, 0, 0)  # Black
    BACKGROUND_COLOR = (255, 165, 0)  # Orange
    CENTER_COLOR = (255, 0, 0)  # Red
    
    # Function to adjust brightness with minimum scaling effect
    def set_neopixel_color(color, brightness_factor):
        return tuple(max(int(c * brightness_factor), 1) for c in color)
    
    
    # Animation functions with brightness adjustment
    # ----------------------------------------------
    def animation_play(millis_delay=130):
        global LED_LEFT_INDEX, LED_RIGHT_INDEX
        
        # Play animation for left spool
        if LED_LEFT_INDEX < LED_LEFT_MAX:
            np[LED_LEFT_INDEX] = set_neopixel_color(BACKGROUND_COLOR, LED_BRIGHTNESS)
            np[LED_LEFT_INDEX + 1] = set_neopixel_color(FOREGROUND_COLOR, LED_BRIGHTNESS)
            LED_LEFT_INDEX += 1
        else:
            np[LED_LEFT_MAX] = set_neopixel_color(BACKGROUND_COLOR, LED_BRIGHTNESS)
            np[LED_LEFT_MIN] = set_neopixel_color(FOREGROUND_COLOR, LED_BRIGHTNESS)
            LED_LEFT_INDEX = LED_LEFT_MIN
    
        # Play animation for right spool
        if LED_RIGHT_INDEX < LED_RIGHT_MAX:
            np[LED_RIGHT_INDEX] = set_neopixel_color(BACKGROUND_COLOR, LED_BRIGHTNESS)
            np[LED_RIGHT_INDEX + 1] = set_neopixel_color(FOREGROUND_COLOR, LED_BRIGHTNESS)
            LED_RIGHT_INDEX += 1
        else:
            np[LED_RIGHT_MAX] = set_neopixel_color(BACKGROUND_COLOR, LED_BRIGHTNESS)
            np[LED_RIGHT_MIN] = set_neopixel_color(FOREGROUND_COLOR, LED_BRIGHTNESS)
            LED_RIGHT_INDEX = LED_RIGHT_MIN
    
        np.write()
        time.sleep_ms(millis_delay)
    
    
    def animation_rewind(millis_delay=50):
        global LED_LEFT_INDEX, LED_RIGHT_INDEX
        
        # Rewind animation for left spool
        if LED_LEFT_INDEX > LED_LEFT_MIN:
            np[LED_LEFT_INDEX] = set_neopixel_color(BACKGROUND_COLOR, LED_BRIGHTNESS)
            np[LED_LEFT_INDEX - 1] = set_neopixel_color(FOREGROUND_COLOR, LED_BRIGHTNESS)
            LED_LEFT_INDEX -= 1
        else:
            np[LED_LEFT_MIN] = set_neopixel_color(BACKGROUND_COLOR, LED_BRIGHTNESS)
            np[LED_LEFT_MAX] = set_neopixel_color(FOREGROUND_COLOR, LED_BRIGHTNESS)
            LED_LEFT_INDEX = LED_LEFT_MAX
    
        # Rewind animation for right spool
        if LED_RIGHT_INDEX > LED_RIGHT_MIN:
            np[LED_RIGHT_INDEX] = set_neopixel_color(BACKGROUND_COLOR, LED_BRIGHTNESS)
            np[LED_RIGHT_INDEX - 1] = set_neopixel_color(FOREGROUND_COLOR, LED_BRIGHTNESS)
            LED_RIGHT_INDEX -= 1
        else:
            np[LED_RIGHT_MIN] = set_neopixel_color(BACKGROUND_COLOR, LED_BRIGHTNESS)
            np[LED_RIGHT_MAX] = set_neopixel_color(FOREGROUND_COLOR, LED_BRIGHTNESS)
            LED_RIGHT_INDEX = LED_RIGHT_MAX
    
        np.write()
        time.sleep_ms(millis_delay)
    
    
    # Blink to indicate function triggered
    def animation_function_triggered(color):
        np[LED_CENTER_LEFT] = set_neopixel_color(color, LED_BRIGHTNESS)
     np[LED_CENTER_RIGHT] = set_neopixel_color(color,...
    Read more »

  • Coding The Animations

    Dustin Johnson10/16/2024 at 09:19 0 comments

    I enhanced the prototype code to implement animations for PLAY, PAUSE, REWIND, and FASTFORWARD -- video of latest functionality is at the bottom of this project log.

    I included an indexing offset to imitate the asymmetric spinning of the rotational indicator on tape spools, as seen here: 

    Here is the code:

    // neopixel code
    // --------------
    #include <FastLED.h>
    #define LED_PIN 5
    #define LED_COUNT 14
    #define LED_BRIGHTNESS 5 // max 255 which is blinding
    #define LED_CENTER_LEFT 0
    #define LED_CENTER_RIGHT 7
    CRGB leds[LED_COUNT]; // must be lowercase and this variable name or the compilation fails
    int LED_LEFT_CENTER = 0;
    int LED_RIGHT_CENTER = 7;
    int LED_LEFT_MIN = 1; // exclude center LED (left spool)
    int LED_LEFT_MAX = 6; // exclude center LED (left spool)
    int LED_RIGHT_MIN = 8; // exclude center LED (right spool)
    int LED_RIGHT_MAX = 13; // exclude center LED (right spool)
    int LED_LEFT_INDEX = random(LED_LEFT_MIN, LED_LEFT_MAX); // index for rotation animations (left spool)
    int LED_RIGHT_INDEX = random(LED_RIGHT_MIN, LED_RIGHT_MAX); // index for rotation animations (right spool)
    CRGB FOREGROUND_COLOR = CRGB::Black;
    CRGB BACKGROUND_COLOR = CRGB::Orange;
    CRGB CENTER_COLOR = CRGB::Red;
    
    
    // function prototypes
    // --------------
    void animation_play(int millisDelay = 130); // function prototype to accept optional argument
    void animation_rewind(int millisDelay = 50); // function prototype to accept optional argument
    
    // main setup
    // --------------
    void setup() {
      Serial.begin(9600);
      FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, LED_COUNT);
      FastLED.setBrightness(LED_BRIGHTNESS);
      animation_bootup();
    }
    
    // main loop
    // --------------
    void loop() { 
      
      animation_function_triggered(CRGB::White);
      for (int i=0; i < 20; i++) {
        animation_pause();
      }
    
      animation_function_triggered(CRGB::White);
      for (int i=0; i < 50; i++) {
        animation_play();
      }
    
      animation_function_triggered(CRGB::White);
      for (int i=0; i < 150; i++) {
        animation_fastforward();
      }
    
      animation_function_triggered(CRGB::White);
      for (int i=0; i < 50; i++) {
        animation_play();
      }
    
      animation_function_triggered(CRGB::White);
      for (int i=0; i < 50; i++) {
        animation_rewind();
      }
    }
    
    // blink to indicate function triggered
    void animation_function_triggered(CRGB color) {
      // on
      leds[LED_LEFT_CENTER] = color;
      leds[LED_RIGHT_CENTER] = color;
      FastLED.show();
      // sleep
      delay(100);
      // off
      leds[LED_LEFT_CENTER] = CRGB::Black;
      leds[LED_RIGHT_CENTER] = CRGB::Black;
      FastLED.show();
    }
    
    void animation_pause() {
      leds[LED_LEFT_INDEX] = BACKGROUND_COLOR;
      leds[LED_RIGHT_INDEX] = BACKGROUND_COLOR;
      FastLED.show();
      delay(100);
    }
    
    void animation_bootup() {
    
      // illuminate left spool
      for (int i = LED_LEFT_MIN; i <= LED_LEFT_MAX; i++) {
        if (i == LED_CENTER_LEFT) { continue; }
        leds[i] = BACKGROUND_COLOR;
        FastLED.show();
        delay(50);
      }
    
      // illuminate right spool
      for (int i = LED_RIGHT_MIN; i <= LED_RIGHT_MAX; i++) {
        if (i == LED_CENTER_RIGHT) { continue; }
        leds[i] = BACKGROUND_COLOR;
        FastLED.show();
        delay(50);
      }
    }
    
    
    void animation_fastforward() {
    
      animation_play(40);
    
    }
    
    // speed is delay, so more is slower
    void animation_play(int millisDelay) {
     
      // animation: PLAY (left spool)
      if (LED_LEFT_INDEX < LED_LEFT_MAX) {
        leds[LED_LEFT_INDEX] = BACKGROUND_COLOR;
        leds[LED_LEFT_INDEX+1] = FOREGROUND_COLOR;
        LED_LEFT_INDEX++;    
      } else {
        leds[LED_LEFT_MAX] = BACKGROUND_COLOR;
        leds[LED_LEFT_MIN] = FOREGROUND_COLOR;
        LED_LEFT_INDEX = LED_LEFT_MIN;
      }
    
      // animation: PLAY (right spool)
      if (LED_RIGHT_INDEX < LED_RIGHT_MAX) {
        leds[LED_RIGHT_INDEX] = BACKGROUND_COLOR;
        leds[LED_RIGHT_INDEX+1] = FOREGROUND_COLOR;
        LED_RIGHT_INDEX++;    
      } else {
        leds[LED_RIGHT_MAX] = BACKGROUND_COLOR;
        leds[LED_RIGHT_MIN] = FOREGROUND_COLOR;
        LED_RIGHT_INDEX = LED_RIGHT_MIN;
      }
     
      FastLED.show();  
      delay(millisDelay);
    }
    
    // speed is delay, so more is slower
    void animation_rewind(int millisDelay) {
     
      // animation: REWIND (left spool)
      if (LED_LEFT_INDEX > LED_LEFT_MIN) {
     leds[LED_LEFT_INDEX]...
    Read more »

  • Prototype Assembly: Part 3

    Dustin Johnson10/16/2024 at 04:15 0 comments

    Now I just need to code the demo:

    // neopixel code
    // --------------
    #include <FastLED.h>
    
    // neopixel code
    // --------------
    #define LED_PIN 5
    #define LED_COUNT 14
    #define LED_BRIGHTNESS 5
    #define LED_CENTER_LEFT 0
    #define LED_CENTER_RIGHT 7
    
    // neopixel code
    // --------------
    // must be lowercase and this variable name or the compilation fails
    CRGB leds[LED_COUNT];
    
    // di0_demo1 code
    // --------------
    // exclude center LED
    int LED_LEFT_MIN = 1;
    int LED_LEFT_MAX = 6;
    // exclude center LED
    int LED_RIGHT_MIN = 8;
    int LED_RIGHT_MAX = 13;
    // keep the indexes for which LEDs are off (CRGB::Black)
    int LED_LEFT_OFF_INDEX = LED_LEFT_MIN;
    int LED_RIGHT_OFF_INDEX = LED_RIGHT_MIN;
    
    // main setup
    // --------------
    void setup() {
      Serial.begin(9600);
      FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, LED_COUNT);
      FastLED.setBrightness(LED_BRIGHTNESS);
      di0_demo1_setup();
    }
    
    // main loop
    // --------------
    void loop() {
      //di0_demo0();
      di0_demo1_loop();
    }
    
    // di0_demo1 code
    // --------------
    void di0_demo1_setup() {
    
      // illuminate left spool
      for (int i = LED_LEFT_MIN; i <= LED_LEFT_MAX; i++) {
        if (i == LED_CENTER_LEFT) { continue; }
        leds[i] = CRGB::WhiteSmoke;
        FastLED.show();
        delay(50);
      }
    
      // illuminate right spool
      for (int i = LED_RIGHT_MIN; i <= LED_RIGHT_MAX; i++) {
        if (i == LED_CENTER_RIGHT) { continue; }
        leds[i] = CRGB::WhiteSmoke;
        FastLED.show();
        delay(50);
      }
    }
    
    // di0_demo1 code
    // --------------
    void di0_demo1_loop() {
    
      // animation: PLAY (left spool)
      if (LED_LEFT_OFF_INDEX < LED_LEFT_MAX) {
        leds[LED_LEFT_OFF_INDEX] = CRGB::Blue;
        leds[LED_LEFT_OFF_INDEX+1] = CRGB::WhiteSmoke;
        LED_LEFT_OFF_INDEX++;    
      } else {
        leds[LED_LEFT_MAX] = CRGB::Blue;
        leds[LED_LEFT_MIN] = CRGB::WhiteSmoke;
        LED_LEFT_OFF_INDEX = LED_LEFT_MIN;
      }
    
      // animation: PLAY (right spool)
      if (LED_RIGHT_OFF_INDEX < LED_RIGHT_MAX) {
        leds[LED_RIGHT_OFF_INDEX] = CRGB::Blue;
        leds[LED_RIGHT_OFF_INDEX+1] = CRGB::WhiteSmoke;
        LED_RIGHT_OFF_INDEX++;    
      } else {
        leds[LED_RIGHT_MAX] = CRGB::Blue;
        leds[LED_RIGHT_MIN] = CRGB::WhiteSmoke;
        LED_RIGHT_OFF_INDEX = LED_RIGHT_MIN;
      }
      FastLED.show();  
      delay(100);
    }

    ... and voila:

  • Prototype Assembly: Part 2

    Dustin Johnson10/16/2024 at 01:09 0 comments

    I've completed my test circuit as follows:

    Arduino 

    - Digital Pin 5 --> LED Module 1: DIN

    - 5V --> Breadboard Positive Rail

    - Gnd --> Breadboard Negative Rail

    LED Module 1:

    - DIN --> Arduino: Digital Pin 5

    - DOUT --> LED Module 2: DIN

    - PWR --> Breadboard Positive Rail

    - GND --> Breadboard Negative Rail

    LED Module 2:

    - DIN --> LED Module 1: DOUT

    - DOUT --> Disconnected

    - PWR --> Breadboard Positive Rail

    - GND --> Breadboard Negative Rail

  • Prototype Assembly: Part 1

    Dustin Johnson10/15/2024 at 22:57 0 comments

    Let's keep it stupid-simple with the big ole trusty Arduino Uno for the PoC / breadboarding, especially since these RGB arrays want 5V. I will port this prototype to an RP2040-based design with a boost converter on the custom-printed PCB for the con, as time / blockers allow.

    I broke apart and soldered some headers on the RGB LED arrays, so they'd fit in the breadboard. I arranged to represent the Cassette Tape Spools in the same way the PCB will have them reverse-mounted against the prepreg diffuser. 

    The pinouts on these WS2812B modules fit exactly across the breadboard gap. There is an extra middle pin for ground, but I don't think I need it.

    Adding this log on the GPD Win Max 2 (2024) and messing with my RP2040-based Thumby. Also, to the left we can see the RP2040-One connected to an RP2040-Matrix, which are my other silly SAO prototypes, which I'd like to use as placeholder SAOs to demo on the Jacana FUFF Mux (https://hackaday.io/project/198240-fuff-mux-sao).

View all 6 project logs

Enjoy this project?

Share

Discussions

Lutetium wrote 10/15/2024 at 14:19 point

Take your time: but that's awesome.  :)

BREAKING NEWS: 2024 SUPERCON SAO CONTEST DEADLINE EXTENDED

  Are you sure? yes | no

Dustin Johnson wrote 10/15/2024 at 23:45 point

Awesome! Thank you! Backfilling some worklogs now.

  Are you sure? yes | no

davedarko wrote 10/15/2024 at 07:00 point

the deadline was extended by a week (22nd) according to Elliot in discord and podcast btw.

Great idea btw, looking forward to see it!

  Are you sure? yes | no

Dustin Johnson wrote 10/15/2024 at 23:51 point

Lol, I can breathe again! Let's goooo! Thank you for your support!

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates