Michael Fetting
Published © MIT

Tinker Light Controller

Imagine the Asus Tinker Board controlling 64 relays via I2C to manage multiple strands of LED lights, synchronized to midi music.

IntermediateShowcase (no instructions)Over 3 days2,989
Tinker Light Controller

Things used in this project

Hardware components

Tinker Board
Tinker Board
×1
Relay (generic)
16 Channel 5V Relay Module Optocoupler
×4
IO Pi Plus
32 channel digital expansion board
×2
ULN BOB
16 Channel Darlington Driver IC Breakout Board
×4
Electrical Outlet, Tamper Resistant Duplex Receptacle, 15A
×32
Sring lever push fit reuseable cable 5 wire connector
×20
5v Power Supply
×1

Story

Read more

Schematics

IO PI PLUS

AB Electronics

Code

tinker light organ

C/C++
reads midi events and sets an appropriate i2c address to on or off.
#include <alsa/asoundlib.h>
#include <wiringPi.h>
#include <mcp23017.h>
#include <limits.h>
#include <unistd.h>
#include <math.h>

static snd_seq_t *seq_handle;
static int in_port;

////////////////////////////////////////////////////////////////////////////
//
// This is the i2c version, mcp23017s array contains an address 
// for each attached MCP23017. Each MCP23017 controls 16 pins. 
// The pinMapping array contains the addresses for the pins available
// for midi assignment (note/channel), 16 pins per address.
//
//////////////////////////////////////////////////////////////////

int test=FALSE;
int base = 120;
int pinMapping[] = {
0x20, //1-16
0x21  //16-32
};

int mcp23017s[] = {
0x20, 
0x21,
0x22, 
0x23
};

int base_pin[] = {
120,
136,
152,
168
};

//#define TOTAL_PINS (sizeof(pinMapping) / sizeof(int)) * 16
#define TOTAL_PINS 31
#define THRUPORTCLIENT 14
#define THRUPORTPORT 0

int history[64];

void midi_open(void)
{
    snd_seq_open(&seq_handle, "default", SND_SEQ_OPEN_INPUT, 0);

    snd_seq_set_client_name(seq_handle, "LightOrgan");
    in_port = snd_seq_create_simple_port(seq_handle, "listen:in",
                      SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE,
                      SND_SEQ_PORT_TYPE_APPLICATION);
 
    if( snd_seq_connect_from(seq_handle, in_port, THRUPORTCLIENT, THRUPORTPORT) == -1) {
       perror("Can't connect to thru port");
       exit(-1);
    } 

}


snd_seq_event_t *midi_read(void)
{
    snd_seq_event_t *ev = NULL;
    snd_seq_event_input(seq_handle, &ev);
    return ev;
}


//Currently playing note, by pin
int pinNotes[TOTAL_PINS];

//Currently playing channel, by pin
int pinChannels[TOTAL_PINS];

//Enabled channels
int playChannels[16];


void clearPinNotes() {
   printf("clearPinNots\n");
   int i;
   for(i=0; i< TOTAL_PINS; i++) {
      pinNotes[i] = -1;
   }
}

void i2cDigitalWrite(int pinIdx,int val) {
   history[pinIdx]++; 
   val ?  printf("%i (%i) on\n", base + pinIdx, history[pinIdx])  : printf("%i (%i) 0FF\n", base 
+ pinIdx, history[pinIdx]);
   digitalWrite (base + pinIdx, val );
} 
  
void clearPinChannels() {
   printf("clearPinChannls\n"); 
   int i;
   for(i=0; i< TOTAL_PINS; i++) {
      pinChannels[i] = INT_MAX;
   }
}

void clearPinsState() {
   clearPinNotes();
   clearPinChannels();
}

void pinsOn() {
   int i;
   for(i=0; i< TOTAL_PINS; i++) { 
      i2cDigitalWrite(i, 1);

   }
}

void pinsOff() {
   int i;
   for(i=0; i< TOTAL_PINS; i++) {
      i2cDigitalWrite(i, 0);
   }
}


void setChannelInstrument(int channel, int instr) {
  printf("setting channel %i to instrument %i\n", channel, instr);
  playChannels[channel] = instr;  
}


int isPercussion(int instrVal) {
  return instrVal >= 8 && instrVal <= 15;
}

int isPercussionChannel(int channel) {
  int instr = playChannels[channel];
  return isPercussion(instr);
}

int isBase(int instrVal) {
  return instrVal >= 32 && instrVal <= 39;
}
int isSynth(int instrVal) {
  return instrVal >= 88 && instrVal <= 103;
}

int choosePinIdx(int note, int channel) {
   //Return the note modulated by the number of melody pins
   printf("note: %i channel: %i\n", note, channel);
   int val = note  % (TOTAL_PINS * 2);
   return val / 2;
}

void midi_process(snd_seq_event_t *ev)
{
    
    //If this event is a PGMCHANGE type, it's a request to map a channel to an instrument
    if( ev->type == SND_SEQ_EVENT_PGMCHANGE )  {
       //Clear pins state, this is probably the beginning of a new song
       clearPinsState();
       setChannelInstrument(ev->data.control.channel, ev->data.control.value);
    }

    //Note on/off event
    else if ( ((ev->type == SND_SEQ_EVENT_NOTEON)||(ev->type == SND_SEQ_EVENT_NOTEOFF)) ) {
        
  
        //choose the output pin based on the pitch of the note
        int pinIdx = choosePinIdx(ev->data.note.note, ev->data.note.channel);


        if(!isPercussionChannel(ev->data.note.channel) ) { 
           int isOn = 1;
           //Note velocity == 0 means the same thing as a NOTEOFF type event
           if( ev->data.note.velocity == 0 || ev->type == SND_SEQ_EVENT_NOTEOFF) {
              isOn = 0;
           }


           //If pin is set to be turned on
           if( isOn ) {
              //If pin is currently available to play a note, or if currently playing channel can be overriden due to higher priority channel
              if( pinNotes[pinIdx] == -1 || pinChannels[pinIdx] > ev->data.note.channel )  {
                      
                 if( (pinChannels[pinIdx] > ev->data.note.channel ) && pinNotes[pinIdx] != -1)  {
                    //printf("OVERRIDING CHANNEL %i for %i\n", pinChannels[pinIdx], ev->data.note.channel);
                 }
                 //Write to the pin, save the note to pinNotes
                 i2cDigitalWrite(pinIdx, 1); 
                 pinNotes[pinIdx] = ev->data.note.note;
                 pinChannels[pinIdx] =  ev->data.note.channel;
              }
           }
           
           //Pin is to be turned off
           else {
              //If this is the note that turned the pin on..
              if( pinNotes[pinIdx] == ev->data.note.note && pinChannels[pinIdx] == ev->data.note.channel ) {
                 //Write to the pin, indicate that pin is available
                 i2cDigitalWrite(pinIdx, 0); 
                 pinNotes[pinIdx] = -1;
                 pinChannels[pinIdx] = INT_MAX;
              }
           }
       }

    }
    
    else {
       printf("Unhandled event %5d\n", ev->type);
   
    }

    snd_seq_free_event(ev);
}

void turnOffLights()
{
  int i=0;
  for (i=0;i<32;i++){
    i2cDigitalWrite(i,0);
  }
}

void leap(int direction)
{
// 0 - leap in
// 1 - leap out

  if(direction == 0) {
    int i=0;
    for (i=0;i<16;i++){
      i2cDigitalWrite(15-i,1);
      i2cDigitalWrite(16+i,1);
      if (i>0) {
        i2cDigitalWrite(0+(16-i),0);
        i2cDigitalWrite(16+(i-1),0);
      }
      usleep(125000);
    }
    i2cDigitalWrite(0,0);
    i2cDigitalWrite(31,0);
  } 
  if(direction == 1) {
    int i=0;
    for (i=0;i<16;i++) {
      i2cDigitalWrite(i, 1);
      i2cDigitalWrite(31 - i, 1);
      if (i>0) {
        i2cDigitalWrite(i - 1, 0);
        i2cDigitalWrite(31 - (i-1), 0);
      }
      usleep(125000);
    }
    i2cDigitalWrite(15,0);
    i2cDigitalWrite(16,0);
  } 
}

int main()
{

    int i = 0;
    printf("Total Pins: %i\n", TOTAL_PINS);
    
    //Setup wiringPi
    if( wiringPiSetup() == -1) {
      exit(1);
    }

    for(i=0; i < (sizeof(mcp23017s) / sizeof(int)); i++) {
      printf("%i %i\n",(120 + (16 * i)), mcp23017s[i]);
      mcp23017Setup(base_pin[i], mcp23017s[i]);
    }
   
    //Setup all the pins to use OUTPUT mode
    for(i=0; i< 64; i++) {
      pinMode( base+i, OUTPUT);
    }

    clearPinsState();
    turnOffLights();

    //Open a midi port, connect to thru port also
    midi_open();

    //Process events forever
     while (1) {
       midi_process(midi_read());
    }

    return -1;
}

Credits

Michael Fetting

Michael Fetting

2 projects • 2 followers
Object-Oriented Software Developer for over twenty years. Internet of Things (IoT) for home automation.

Comments