Go Down

Topic: TLC5947 w/ Max MSP & Serial Freezing (Read 471 times) previous topic - next topic


Hi all,

I am currently working on an installation wherein I'll be programming 96 solenoids to play different percussive objects. The solenoids are split up into 12 8-solenoid groups. Each solenoid will also have a corresponding RGB LED, so 96 in total- which amounts to 288 individual LEDs.

The primary issue I seem to be having is with programming the LEDs.

Each group's 8 RGB LEDs will be controlled using a TLC5947 grayscale 24-channel 12-bit LED driver. I am currently trying to control brightness values for each LED via serial data sent from Max MSP. Because the data is 12-bit and SPI writes in 8-bits at a time, when new 12-bit data is received for each LED, it is first stored in an array which is then parsed (two LED values at a time) into 1.5 bytes. Each pair of LED values will have either their 8 most significant bits or 8 least significant bits stored into bytes, and will then share a third byte for their remaining 4 bits each. This parsed data is recorded into three different arrays which is then combined into a single 36-byte array in the order in which it will be shifted out to the TLC5947 via SPI.

This method works somewhat, but it tends to freeze very easily when I send too much serial data too quickly from Max. When it freezes, the Arduino does take any other commands and only works upon resetting it. Upon adding delays to many of the LED messages in Max, the freezing seems to be negated to a certain degree- but then there is some lag in the LED's response time.

Are there ways in which I could better optimize my code to stop the freezing? I've read the tagged introduction to serial communications post as well as a number of videos and tutorials on serial communication but I still don't quite grasp how to make it less apt to freezing... I've also tried using the TLC5947 library instead of writing the code myself, but I run into the same freezing issue.

The full code is below- any input or suggestions is much appreciated! Thanks y'all.  :)  Amina

Code: [Select]
#include <SPI.h>

//Node & Checksum Parameter
int NODE = 1;                                                                                                     //Header byte for 1 chandelier pillar.
byte TOTAL_BYTES = 3;                                                                                             //Serial data checksum # of bytes.

//LED Parameters
int LED_NUM[]= {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};    //LED header bytes
int L_LATCH = 10;
int LED_VAL[24];
byte SPI_BYTEMSB[12];                                                                                             //Used for parsing 12-bit LED[VAL] arrays into 8-bit bytes (1.5 bytes each)
byte SPI_BYTEMID[12];                                                                                             //Bytes are arranged MSBFIRST into SPI_WRITE[] to be transmitted
byte SPI_BYTELSB[12];
byte SPI_WRITE[36];
byte i = 0;                                                                                                       //For waking up TLC5947 shift register (takes 4 clock pulses to wake)

//Solenoid Parameters
int S_LATCH = 6;
int SOL_NUM[] = {0, 1, 2, 3, 4, 5, 6, 7};                                                                         //Solenoid header bytes
byte SHIFT_VAL = 0;                                                                                               //Used to record last on/off data for each of the 8 solenoids to compare with incoming data.
byte MULT[] = {1, 2, 4, 8, 16, 32, 64, 128};                                                                      //Used to write on/off data to corresponding bit of the byte shifted out to the solenoid shift register.

int RESET = 8;

int OUTPUT_PINS[] = {6, 8, 10};

void LED_CHECK(){
      for(int y = 0; y < 24; y ++){
      if(Serial.peek() == LED_NUM[y]){                              
        if(LED_VAL[y] != Serial.peek()){                                  //If the save LED_VAL[x] is different than the new data coming in
        LED_VAL[y] = Serial.read();                                           //Writes the new data to LED_VAL[y]
        LED_DATAPARSE();                                                      //Parses the 12-bit values into 1.5 bytes of SPI_WRITE[]
        LED_DATAWRITE();                                                      //And transfers new data out to the TLC5947
        else{Serial.read(); break;};                                      //Otherwise it removes the byte and ends the for().

  for(int x = 0; x < 24; x += 2){                                                        //Processes two LED_VAL[] 12-bit bytes at a time.
    SPI_BYTELSB[(x - 2) / 2] = ((LED_VAL[x + 1] & 4080) >> 4);                           //LED_VAL[x + 1]'s first 8 MSBits are parsed into SPI_BYTELSB
    SPI_BYTEMID[(x - 2) / 2] = ((LED_VAL[x] >> 8) + ((LED_VAL[x + 1] & 15) << 4));       //LED_VAL[x + 1]'s last 4 LSBits are added to LED_VAL[x]'s first 4 MSBits and parsed to SPI_BYTEMID
    SPI_BYTEMSB[(x - 2) / 2] = LED_VAL[x] & 255;                                         //LED_VAL[x]'s last 8 LSBits are parsed into SPI_BYTE[MSB].
  for(int h = 0; h < 36; h += 3){                                                        //SPI_BYTEMSB, SPI_BYTEMID & SPI_BYTELSB are stored into SPI_WRITE[36]
    SPI_WRITE[h] = SPI_BYTEMSB[(h / 3) - 1];                                             //in the order of how they are clocked to the TLC5947.
    SPI_WRITE[h + 1] = SPI_BYTEMID[(h / 3) - 1];
    SPI_WRITE[h + 2] = SPI_BYTELSB[(h / 3) - 1];

  SPI.transfer(i);                                                                       //Initializes the TLC5947.
  for(int g = 35; g > -1; g --){                                                         //Clocks SPI_WRITE[36] bytes to TLC5947.
  digitalWrite(L_LATCH, HIGH);
  digitalWrite(L_LATCH, LOW);

void SOL_CHECK(){                                            
    for(int v = 0; v < 8; v ++){
      if(Serial.peek() == SOL_NUM[v]){
        Serial.read();                                                    //Removes header byte.  
        if(SHIFT_VAL ^ ((Serial.peek() * MULT[v]))){                      //If the new serial data is different than the old data,
          if(Serial.peek() == 1){                                             //and if the new data is a 1,
           Serial.read();                                                       //removes data byte
           SHIFT_VAL = (SHIFT_VAL + MULT[v]);                                   //turns the corresponding bit in SHIFT_VAL from 0 to 1
         else if(Serial.peek() == 0){                                         //or if the new data is a 0,
           Serial.read();                                                       //removes data byte
           SHIFT_VAL = (SHIFT_VAL - MULT[v]);                                   //turns the corresponding bit in SHIFT_VAL from 1 to 0
       else{Serial.read(); break;};                                       //Or ends the for statement if data is redundant.

  SPI.transfer(i);                                                                       //Initializes the TLC5947.
  SPI.transfer(SHIFT_VAL);                                                               //Writes byte to solenoid shift register.
  digitalWrite(S_LATCH, HIGH);
  digitalWrite(S_LATCH, LOW);

  if(Serial.available() == 1){
  if(Serial.peek() == 'R'){
    Serial.read(); digitalWrite(RESET, HIGH);

void setup() {
SPISettings (15000000, MSBFIRST, SPI_MODE0);

  for(int x = 0; x < 3; x ++){
    pinMode(OUTPUT_PINS[x], OUTPUT);};

digitalWrite(RESET, LOW);
digitalWrite(S_LATCH, LOW);
digitalWrite(L_LATCH, LOW);


void loop() {
if(Serial.available() == TOTAL_BYTES){
  if(Serial.peek() == NODE){
    Serial.read();                                                        //Removes node header byte.  
  }else{Serial.read(); Serial.read(); Serial.read();};


Also a bit of additional information about how data is being sent/processed...

Data for each solenoid/LED group is received with two header bytes- one to specify the group in which the data is for, another to specify which solenoid or LED in the group the data is for, and lastly the on/off or brightness value to be updated. Each solenoid/LED group will have it's own Arduino Nano which will receive serial data via an ES-1 WiFi chip- a computer running a Max For Live patch in Ableton Live will be transmitting data for all the LEDs and solenoids.

The on/off messages for each group's 8 solenoids are being controlled with an 8-bit shift register. Any time new data is received, it is saved into a byte which is then shifted out to the register via SPI- or it is thrown out if the new data is redundant. This part of the design is working as intended thus far.


Have you got good decoupling on all these components? And diodes across the solenoids?

Try running it with just LEDs in the place of the solenoids, does it still freeze?

A schematic would help us see these things. 


Unfortunately I don't have a schematic handy, but there is a flyback diode on each solenoid. And yes, I've tried it without the solenoid portion of the code and it still has the same freezing issues. I didn't mention it above, but the solenoids are going through some beefy MOSFETs and are being powered by a separate power supply, so really all the shift register is doing is gating the MOSFETs.


Dec 08, 2019, 12:18 am Last Edit: Dec 08, 2019, 12:24 am by Grumpy_Mike
Yes but the shift registers also need decoupling capacitors. Erratic behaviour like this is almost always caused by insufficient decoupling, layout or missing ground connections.

It never ceases to amaze me that you can make something without a schematic, I would never attempt such a thing and I have been doing this game almost 50 years.

Looking at the code you are handling the serial input most oddly. Especially when you are looking to see if NODE has arrived which is a 1. Where is the serial coming from and is it likely to send a 1 when all the other times it seems to be handling ASCII data.

Go Up