Controller Code

Download the Ada-remoteFXTrigger_NeoTrellis_FastLED_TX code below and upload it to your controller box.

//Ada_remoteFXTrigger_NeoTrellis_TX
//Remote Effects Trigger Box Transmitter
//by John Park
// & Erin St. Blaine
//for Adafruit Industries

// General purpose button box
// for triggering remote effects
// using packet radio Feather boards
//
//
//MIT License

#include <SPI.h>
#include <RH_RF69.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_NeoTrellis.h>

#include <Encoder.h>
int m = 0; //variable to increment through menu list
int x = 17; //variable for referencing buttons -- setting X to a non-existent button
/********* Encoder Setup ***************/
#define PIN_ENCODER_SWITCH 11
Encoder knob(10, 12);
uint8_t activeRow = 0;
long pos = -999;
long newpos;
int prevButtonState = HIGH;
bool needsRefresh = true;
bool advanced = false;
unsigned long startTime;


/********* NeoTrellis Setup ***************/
Adafruit_NeoTrellis trellis;

#define MOMENTARY 0
#define LATCHING 1
#define MODE LATCHING //all Trellis buttons in latching mode
#define NUMTRELLIS 1
#define numKeys (NUMTRELLIS * 16)
#define INTPIN A2
int NUM_SCREENS = 3;  // change this number to add more screens


//define a callback for key presses
TrellisCallback blink(keyEvent evt){
  // Check is the pad pressed?
  if (evt.bit.EDGE == SEESAW_KEYPAD_EDGE_RISING) {
    //trellis.pixels.setPixelColor(evt.bit.NUM, 0xFFFFFF); //on rising
  } else if (evt.bit.EDGE == SEESAW_KEYPAD_EDGE_FALLING) {
  // or is the pad released?
  //trellis.pixels.setPixelColor(m, Wheel(map(m*2, 0, trellis.pixels.numPixels(), 0, 255))); //off falling
   x = evt.bit.NUM;
  }

  // Turn on/off the neopixels!
  trellis.pixels.show();

  return 0;
} 



/************ OLED Setup ***************/
Adafruit_SSD1306 oled = Adafruit_SSD1306();
#if defined(ESP8266)
  #define BUTTON_A 0
  #define BUTTON_B 16
  #define BUTTON_C 2
  #define LED      0
#elif defined(ESP32)
  #define BUTTON_A 15
  #define BUTTON_B 32
  #define BUTTON_C 14
  #define LED      13
#elif defined(ARDUINO_STM32F2_FEATHER)
  #define BUTTON_A PA15
  #define BUTTON_B PC7
  #define BUTTON_C PC5
  #define LED PB5
#elif defined(TEENSYDUINO)
  #define BUTTON_A 4
  #define BUTTON_B 3
  #define BUTTON_C 8
  #define LED 13
#elif defined(ARDUINO_FEATHER52)
  #define BUTTON_A 31
  #define BUTTON_B 30
  #define BUTTON_C 27
  #define LED 17
#else // 32u4, M0, and 328p
  #define BUTTON_A 9
  #define BUTTON_B 6
  #define BUTTON_C 5
  #define LED      13
#endif


/************ Radio Setup ***************/
// Can be changed to 434.0 or other frequency, must match RX's freq!
#define RF69_FREQ 915.0

#if defined (__AVR_ATmega32U4__) // Feather 32u4 w/Radio
  #define RFM69_CS      8
  #define RFM69_INT     7
  #define RFM69_RST     4
#endif

#if defined(ARDUINO_SAMD_FEATHER_M0) // Feather M0 w/Radio
  #define RFM69_CS      8
  #define RFM69_INT     3
  #define RFM69_RST     4
#endif

#if defined (__AVR_ATmega328P__)  // Feather 328P w/wing
  #define RFM69_INT     3  // 
  #define RFM69_CS      4  //
  #define RFM69_RST     2  // "A"
#endif

#if defined(ESP32)    // ESP32 feather w/wing
  #define RFM69_RST     13   // same as LED
  #define RFM69_CS      33   // "B"
  #define RFM69_INT     27   // "A"
#endif


// Singleton instance of the radio driver
RH_RF69 rf69(RFM69_CS, RFM69_INT);

int lastButton=17; //last button pressed for Trellis logic

int menuList[8]={1,2,3,4,5,6,7,8}; //for rotary encoder choices
//int m = 0; //variable to increment through menu list
int lastTB[8] = {16, 16, 16, 16, 16, 16, 16, 16}; //array to store per-menu Trellis button


/*******************SETUP************/
void setup() {
  delay(500);
  Serial.begin(115200);
  //while (!Serial) { delay(1); } // wait until serial console is open, 
  //remove if not tethered to computer

pinMode(PIN_ENCODER_SWITCH, INPUT_PULLUP);//set encoder push switch pin to input pullup
pinMode(INTPIN, INPUT);
  
 // digitalPinToInterrupt(10); //on M0, Encoder library doesn't auto set these as interrupts
 // digitalPinToInterrupt(12);
  
  // Initialize OLED display
  oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 128x32)
  oled.setTextWrap(false);
  oled.display();
  delay(500);
  oled.clearDisplay();
  oled.display();
  oled.setTextSize(2);
  oled.setTextColor(WHITE);
  pinMode(BUTTON_A, INPUT_PULLUP);
  pinMode(BUTTON_B, INPUT_PULLUP);
  pinMode(BUTTON_C, INPUT_PULLUP);
  pinMode(LED, OUTPUT);     
  pinMode(RFM69_RST, OUTPUT);
  digitalWrite(RFM69_RST, LOW);

  // manual reset
  digitalWrite(RFM69_RST, HIGH);
  delay(10);
  digitalWrite(RFM69_RST, LOW);
  delay(10);
  
  if (!rf69.init()) {
    Serial.println("RFM69 radio init failed");
    while (1);
  }
  Serial.println("RFM69 radio init OK!");
  
  // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM (for low power module)
  // No encryption
  if (!rf69.setFrequency(RF69_FREQ)) {
    Serial.println("setFrequency failed");
  }

  // If you are using a high power RF69 eg RFM69HW, you *must* set a Tx power with the
  // ishighpowermodule flag set like this:
  rf69.setTxPower(14, true);

  // The encryption key has to be the same as the one in the server
  uint8_t key[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
                    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
  rf69.setEncryptionKey(key);
  
  pinMode(LED, OUTPUT);

  Serial.print("RFM69 radio @");  Serial.print((int)RF69_FREQ);  Serial.println(" MHz");

  oled.setCursor(0,0);
  oled.println("RFM69 @ ");
  oled.print((int)RF69_FREQ);
  oled.println(" MHz");
  oled.display();
  delay(1200); //pause to let freq message be read by a human

  oled.clearDisplay();
  oled.setCursor(0,0);
  oled.println("REMOTE FX");
  oled.setCursor(0,16);
  oled.println("TRIGGER");  
  oled.display();

   if (!trellis.begin()) {
    Serial.println("Could not start trellis, check wiring?");
    while(1);
  } else {
    Serial.println("NeoPixel Trellis started");
  }

  //activate all NeoTrellis keys and set callbacks
  for(int i=0; i<NEO_TRELLIS_NUM_KEYS; i++){
    trellis.activateKey(i, SEESAW_KEYPAD_EDGE_RISING);
    trellis.activateKey(i, SEESAW_KEYPAD_EDGE_FALLING);
    trellis.registerCallback(i, blink);
  }

  //do a little animation to show we're on
  for (uint16_t i=0; i<trellis.pixels.numPixels(); i++) {
    trellis.pixels.setPixelColor(i, Wheel(map(i, 0, trellis.pixels.numPixels(), 0, 255)));
    trellis.pixels.show();
    delay(50);
  }
  for (uint16_t i=0; i<trellis.pixels.numPixels(); i++) {
    trellis.pixels.setPixelColor(i, 0x000000);
    trellis.pixels.show();
    delay(50);
  }

}

//////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////

void loop() {
   

/*************Rotary Encoder Menu***********/

    //check the encoder knob, set the current position as origin
    long newpos = knob.read() / 4;//divide for encoder detents
    
    /* // for debugging
     Serial.print("pos=");
     Serial.print(pos);
     Serial.print(", newpos=");
     Serial.println(newpos);
    */

    if(newpos != pos){
      int diff = newpos - pos;//check the different between old and new position
      if(diff>=1){
        m++; 
        m = (m+NUM_SCREENS) % NUM_SCREENS;//modulo to roll over the m variable through the list size
       }

      if(diff==-1){ //rotating backwards
         m--;
         m = (m+NUM_SCREENS) % NUM_SCREENS;
       }
      /* //uncomment for debugging or general curiosity
      Serial.print("Diff = ");
      Serial.print(diff);
      Serial.print("  pos= ");
      Serial.print(pos);
      Serial.print(", newpos=");
      Serial.println(newpos);
      Serial.println(menuList[m]);
      */

      pos = newpos;

      // Serial.print("m is: ");
      //Serial.println(m);
  //write to the display
      oled.setCursor(0,3);
      oled.clearDisplay();

      int p; //for drawing bullet point menu location pixels
      int q;
  
      if (m==0){
        for(p=4;p<8;p++){
          for(q=0;q<4;q++){
            oled.drawPixel(q,p,WHITE);
          }
        }
        oled.print(" Solids");
        clearPixels();
        trellis.pixels.setPixelColor(0, 50,0,0);
        trellis.pixels.setPixelColor(1, 50,50,0);
        trellis.pixels.setPixelColor(2, 0,50,0);
        trellis.pixels.setPixelColor(3, 0,0,50);
        trellis.pixels.setPixelColor(4, 20,0,50);
        trellis.pixels.setPixelColor(5, 50,0,20);
        trellis.pixels.setPixelColor(6, 100,100,100);
        trellis.pixels.setPixelColor(7, 10,10,10);
        trellis.pixels.show();
      }
      if (m==1){
        for(p=8;p<12;p++){
          for(q=0;q<4;q++){
            oled.drawPixel(q,p,WHITE);
          }
        }
        oled.print(" Gradients");
        clearPixels();
        trellis.pixels.setPixelColor(0, 50,0,0);
        trellis.pixels.setPixelColor(1, 50,50,0);
        trellis.pixels.setPixelColor(2, 0,50,0);
        trellis.pixels.setPixelColor(3, 0,0,50);
        trellis.pixels.setPixelColor(4, 20,0,50);
        trellis.pixels.setPixelColor(5, 50,0,20);
        trellis.pixels.setPixelColor(6, 100,100,100);
        trellis.pixels.setPixelColor(7, 10,10,10);
        trellis.pixels.show();
      }
      if (m==2){
        for(p=12;p<16;p++){
          for(q=0;q<4;q++){
            oled.drawPixel(q,p,WHITE);
          }
        }
        oled.print(" Rainbows");
        clearPixels();
        trellis.pixels.setPixelColor(0, 50,0,0);
        trellis.pixels.setPixelColor(1, 50,50,0);
        trellis.pixels.setPixelColor(2, 0,50,0);
        trellis.pixels.setPixelColor(7, 10,10,10);
        trellis.pixels.show();
      }
 
  
      
      oled.display();
    }

    char radiopacket[20];
   
   
/**************Solids**************/
       
        if(m==0){ //next menu item
       
            if (x==0){ //button 1 sends button A command
              radiopacket[0] = 'A';
              oled.clearDisplay();
              oled.setCursor(0,0);
              oled.print("Solid");
              oled.setCursor(50,16);
              oled.print("RED");
              oled.display(); 
            }
            if (x==1){ //button 2 sends button B command
              radiopacket[0] = 'B';
             oled.clearDisplay();
              oled.setCursor(0,0);
              oled.print("Solid");
              oled.setCursor(50,16);
              oled.print("GOLD");
              oled.display(); 
            }
            if (x==2){ //button 3 sends button C command
              radiopacket[0] = 'C';
             oled.clearDisplay();
              oled.setCursor(0,0);
              oled.print("Solid");
              oled.setCursor(50,16);
              oled.print("GREEN");
              oled.display();  
            } 

            if (x==3){ //button 4 sends button D command
              radiopacket[0] = 'D';
             oled.clearDisplay();
              oled.setCursor(0,0);
              oled.print("Solid");
              oled.setCursor(50,16);
              oled.print("BLUE");
              oled.display();  
            }

            if (x==4){ //button 5 sends button E command
              radiopacket[0] = 'E';
              oled.clearDisplay();
              oled.setCursor(0,0);
              oled.print("Solid");
              oled.setCursor(50,16);
              oled.print("PURPLE");
              oled.display(); 
            }
            if (x==5){ //button 6 sends button F command
              radiopacket[0] = 'F';
             oled.clearDisplay();
              oled.setCursor(0,0);
              oled.print("Solid");
              oled.setCursor(50,16);
              oled.print("PINK");
              oled.display(); 
            }
            if (x==6){ //button 7 sends button G command
              radiopacket[0] = 'G';
             oled.clearDisplay();
              oled.setCursor(0,0);
              oled.print("Solid");
              oled.setCursor(50,16);
              oled.print("WHITE");
              oled.display();  
            } 

            if (x==7){ //button 8 sends button H command
              radiopacket[0] = 'H';
             oled.clearDisplay();
              oled.setCursor(0,0);
              oled.print("Solid");
              oled.setCursor(50,16);
              oled.print("OFF");
              oled.display();  
            }
     

 trellis.pixels.show(); // tell the trellis to set the LEDs we requested
}

/**************Gradients**************/
       
        if(m==1){ //next menu item
       
            if (x==0){ //button 1 sends button A command
              radiopacket[0] = 'I';
              oled.clearDisplay();
              oled.setCursor(0,0);
              oled.print("Gradient");
              oled.setCursor(50,16);
              oled.print("RED");
              oled.display(); 
            }
            if (x==1){ //button 2 sends button B command
              radiopacket[0] = 'J';
             oled.clearDisplay();
              oled.setCursor(0,0);
              oled.print("Gradient");
              oled.setCursor(50,16);
              oled.print("GOLD");
              oled.display(); 
            }
            if (x==2){ //button 3 sends button C command
              radiopacket[0] = 'K';
             oled.clearDisplay();
              oled.setCursor(0,0);
              oled.print("Gradient");
              oled.setCursor(50,16);
              oled.print("GREEN");
              oled.display();  
            } 

            if (x==3){ //button 4 sends button D command
              radiopacket[0] = 'L';
             oled.clearDisplay();
              oled.setCursor(0,0);
              oled.print("Gradient");
              oled.setCursor(50,16);
              oled.print("BLUE");
              oled.display();  
            }

            if (x==4){ //button 5 sends button E command
              radiopacket[0] = 'M';
              oled.clearDisplay();
              oled.setCursor(0,0);
              oled.print("Gradient");
              oled.setCursor(50,16);
              oled.print("PURPLE");
              oled.display(); 
            }
            if (x==5){ //button 6 sends button F command
              radiopacket[0] = 'N';
             oled.clearDisplay();
              oled.setCursor(0,0);
              oled.print("Gradient");
              oled.setCursor(50,16);
              oled.print("PINK");
              oled.display(); 
            }
            if (x==6){ //button 7 sends button G command
              radiopacket[0] = 'O';
             oled.clearDisplay();
              oled.setCursor(0,0);
              oled.print("Gradient");
              oled.setCursor(50,16);
              oled.print("WHITE");
              oled.display();  
            } 

            if (x==7){ //button 8 sends button H command
              radiopacket[0] = 'H';
             oled.clearDisplay();
              oled.setCursor(0,0);
              oled.print("Gradient");
              oled.setCursor(50,16);
              oled.print("OFF");
              oled.display();  
            }
 trellis.pixels.show(); // tell the trellis to set the LEDs we requested
}


/**************Rainbows**************/
       
        if(m==2){ //next menu item
       
            if (x==0){ //button 1 sends button A command
              radiopacket[0] = 'P';
              oled.clearDisplay();
              oled.setCursor(0,0);
              oled.print("Rainbow");
              oled.setCursor(50,16);
              oled.print("FADE");
              oled.display(); 
            }
            if (x==1){ //button 2 sends button B command
              radiopacket[0] = 'Q';
             oled.clearDisplay();
              oled.setCursor(0,0);
              oled.print("Rainbow");
              oled.setCursor(50,16);
              oled.print("SOFT");
              oled.display(); 
            }
            if (x==2){ //button 3 sends button C command
              radiopacket[0] = 'R';
             oled.clearDisplay();
              oled.setCursor(0,0);
              oled.print("Rainbow");
              oled.setCursor(50,16);
              oled.print("TWINKLE");
              oled.display();  
            } 

            if (x==7){ //button 8 sends button H command
              radiopacket[0] = 'H';
             oled.clearDisplay();
              oled.setCursor(0,0);
              oled.print("Rainbow");
              oled.setCursor(50,16);
              oled.print("OFF");
              oled.display();  
            }
 trellis.pixels.show(); // tell the trellis to set the LEDs we requested
}



Serial.print("Sending "); 
          Serial.println(radiopacket[0]);

          rf69.send((uint8_t *)radiopacket, strlen(radiopacket));
          rf69.waitPacketSent(); 
          //reset packet so unassigned buttons don't send last command
          radiopacket[0]='Z'; //also being used to turn off NeoPixels 
          //from any unused button

          if (rf69.waitAvailableTimeout(1000)) { 
            // Should be a message for us now   
            uint8_t buf[RH_RF69_MAX_MESSAGE_LEN];
            uint8_t len = sizeof(buf);
            
            if (! rf69.recv(buf, &len)) {
              Serial.println("Receive failed");
              return;
            }
            digitalWrite(LED, HIGH);
            rf69.printBuffer("Received: ", buf, len);
            buf[len] = 0;
            
            //Serial.print("TX Got: "); 
            //Serial.println((char*)buf);
            Serial.print("RSSI: "); 
            Serial.println(rf69.lastRssi(), DEC);

            //delay(1000);//chill for a moment before returning the message to RX unit

            /*************Reply message from RX unit***********/
            //oled.clearDisplay();
            //oled.print((char*)buf[0]);
            //oled.print("RSSI: "); oled.print(rf69.lastRssi());
            //oled.display(); 
            
            
            digitalWrite(LED, LOW);
          }

          //lastButton=i;//set for next pass through to turn this one off
      

       trellis.read();
     delay(100); //the NeoTrellis has a resolution of around 60hz
    
 }


/******************************************/

// Input a value 0 to 255 to get a color value.
// The colors are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  if(WheelPos < 85) {
   return trellis.pixels.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  } else if(WheelPos < 170) {
   WheelPos -= 85;
   return trellis.pixels.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } else {
   WheelPos -= 170;
   return trellis.pixels.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  return 0;
}

void clearPixels() {
 for (uint16_t i=0; i<trellis.pixels.numPixels(); i++) {
    trellis.pixels.setPixelColor(i, 0x000000);
    trellis.pixels.show();
    delay(2);
  }  
  }

 

If you are not familiar with using Arduino to upload code, see this guide.

Customizing

This code has three different screens you can scroll through: Solids, Gradients, and Rainbows. Each screen lights up a few buttons on the NeoTrellis. The unlighted buttons are currently unassigned.

To add more screens, look for this line in the code under NeoTrellis Setup and change the number to reflect the total number of screens you want.

Download: file
int NUM_SCREENS = 3;  // change this number to add more screens

Then scroll down and find the "Rotary Encoder Menu" section. Here is where you can set the pixel colors on the NeoTrellis for each screen, and give each screen a title. You can also add or remove screens here (just remember to change NUM_SCREENS to match your total).

Download: file
    if (m==0){
        for(p=4;p<8;p++){
          for(q=0;q<4;q++){
            oled.drawPixel(q,p,WHITE);
          }
        }
        oled.print(" Solids");
        clearPixels();
        trellis.pixels.setPixelColor(0, 50,0,0);
        trellis.pixels.setPixelColor(1, 50,50,0);
        trellis.pixels.setPixelColor(2, 0,50,0);
        trellis.pixels.setPixelColor(3, 0,0,50);
        trellis.pixels.setPixelColor(4, 20,0,50);
        trellis.pixels.setPixelColor(5, 50,0,20);
        trellis.pixels.setPixelColor(6, 100,100,100);
        trellis.pixels.setPixelColor(7, 10,10,10);
        trellis.pixels.show();
      }

Scrolling some more, find the section titled "Solids". This is where you will tell the controller to send a specific command, like "A" or "B" when each button is pressed. The first button on the controller is 0, and the last one is 15. You can also customize the OLED display messages here.

Download: file
/**************Solids**************/
       
        if(m==0){ //next menu item
       
            if (x==0){ //button 1 sends button A command
              radiopacket[0] = 'A';
              oled.clearDisplay();
              oled.setCursor(0,0);
              oled.print("Solid");
              oled.setCursor(50,16);
              oled.print("RED");
              oled.display(); 
            }

That's all this box does: shows pretty colors and sends a letter when a button is pressed. All the rest is done on the receiver end.

This guide was first published on Jan 30, 2019. It was last updated on Jan 30, 2019. This page (Controller Code) was last updated on Aug 11, 2019.