GPIO Synchronization

Here’s a post Halloween, slightly scary hardware trick…

Two Pixelblazes, with GPIO 4 and 5 connected w/a couple of meters of CAT5 cable and we have GPIO pattern sync! Code below and (with lengthier explanation) in gpiosynchronizer.js in my pattern repository.

The three colored pixels from @jeff’s “An Intro to Pixelblaze Code”:

@ChrisNZ’s nifty spiral pattern:

(Yes, Pixel Teleporter supports multiple Pixelblazes. :slight_smile: I just figured nobody would ever need that feature so I didn’t document it. Coming in the next version.)

The code uses a state machine and a special, synchronizable version of the time() function to quickly sync two patterns. It syncs very quickly, is robust and doesn’t interfere with normal operation. If you change patterns on one of the PBs, or restart it, synchronization will automatically be re-established when both are running a synchronizable pattern again.

/*
 Utility: GPIO Synchronization Demo
 
 Uses two GPIO pins to synchronize patterns between
 a pair of connected Pixelblazes. Caution:  Connecting
 Pixelblazes this way requires soldering and basic electronics
 knowledge. See the README.md or the Pixelblaze forums for
 additional guidance.
 
 To use: 
 - Call initGPIO(inPin,outPin) to set up your GPIO pins
 - Call synchronize(delta) from your beforeRender() function
 - Call syncTime() instead of time() for things that need to
   be synchronized.
 - To manually initiate synchronization, call requestSync()
 
 Version  Author        Date        Comment
 1.0.0    JEM(ZRanger1) 11/1/2020 
*/ 

// Global state variables for synchronizer
var _syncState = 1;   // state of sync state machine
var _holdTime = 0;    // how long we've been in a given state
var _inPin = 4;       // input GPIO pin
var _outPin = 5;      // output GPIO pin 
var _tcount = 0;      // accumulator for sync'd timebase

// Configure the two GPIO pins needed for synchronization
function initGPIO(inPin,outPin) {
  _inPin = inPin;
  _outPin = outPin;

  pinMode(_outPin,OUTPUT)  
  pinMode(_inPin,INPUT)    
  digitalWrite(_outPin,LOW);  
}

// Just like built-in time(), but uses our synchronized timebase to
// keep patterns running together.
function syncTime(rate) {
  return (_tcount * 0.57375 / rate) % 1;
}

// Reset base time for syncTime().  If two Pixelblazes call this at
// almost exactly the same time, their patterns will be synchronized.
function resetTime() {
  _tcount = 0;
}

// start the pattern synchronization process the next time 
// synchronize() is called.
function requestSync() {
  _syncState = 1;
}

// GPIO Pattern synchronization state machine.  Must be called in
// beforeRender(), before any calls to syncTime()
function synchronize(delta) {
  _holdTime += delta;
  _tcount = (_tcount + (delta >> 15)) % 1;  
  
  if (_syncState == 0) {            // synchronized -- normal operation
    _syncState = digitalRead(_inPin);
  }
  else if (_syncState == 1 ) { 
    _holdTime = 0;
    digitalWrite(_outPin,HIGH);    // set pin HIGH to request sync 
    _syncState = 2;
  }
  else if (_syncState == 2) {
    if (digitalRead(_inPin)) {    // wait for other side to respond 
      _holdTime = 0;
      _syncState = 3;
    }
    else {
      if (_holdTime > 1000) _syncState = 1;
    }    
  }
  else if (_syncState == 3) {
    digitalWrite(_outPin,LOW);    // clear our req pin to ack other side's request    
    if (!digitalRead(_inPin)) {   // wait for other side to respond
      _syncState = 0;
      resetTime();
    }
    else {
      if (_holdTime > 1000) _syncState = 1;
    }
  }  
}

// GPIO Setup - set input and output pins.
initGPIO(4,5);

// 3 colored pixel demo from @jeff's "An Intro to Pixelblaze Code"
var leadPosition = 0;

export function beforeRender(delta) {
  synchronize(delta);  
  leadPosition = pixelCount * syncTime(.1);    
}

export function render(index) {
  red   = abs(leadPosition - index) < 1
  green = abs(leadPosition - index - 4) < 1
  blue  = abs(leadPosition - index - 8) < 1
    
  rgb(red, green, blue)  
}
3 Likes

Wow, here I was nudging you, figuring that an example would be difficult and Bam, you knock it out of the park. Nice job!

Question, does this scale? (A to b, b to c, etc?), And I’m wondering if a non-firestorm solution that is wireless could be crafted using this as a start.

Thanks @Scruffynerf! Since I’d recommended this approach without explaining how to make it work, I thought I should be at least a little bit responsible and offer some code. Plus, it was just a fun thing to try.

The algorithm is scalable. This implementation is built for two Pixelblazes though. If you’ve got a bigger project in mind, let me know and I can help build a scaled-up version. For several PBs over wireless, I’m still thinking Firestorm is the better option.

1 Like