Due to high demand, expect some shipping delays at this time - orders may not ship for up to 2-3 business days.

TLC59711 with DMX
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

TLC59711 with DMX

by TommyCo10 on Sat Jan 15, 2022 1:28 pm

Hi everyone, I'm very new to Arduino and programming in general so apologies...

I have a background in analog electronics but, I'm trying to get to grips with digital microcontrollers and programming in general, so I'm learning by looking at tutorials, watching videos and butchering my way through and adapting example code to see how things work.

I've been making some progress with the DMXSerial2 library to make a DMX/RDM LED driver by modifying the included example code in order to produce as many ways of 8 bit PWM as my Arduino Mega can handle.

This has been quite satisfying on its own, but I seek greater dimming resolution, so I bought one of your TLC59711 boards to see if I can incorporate it into my existing project to give me multiple 16bit PWM outputs too.

The board itself works fine on its own with the MEGA and the Adafruit example demo code does exactly what I'd expect with the LEDs I have connected to it, but as soon as I incorporate the DMXSerial2 element things start to get a bit crazy.

I've essentially butchered the DMXSerial2 library demo example and the ADAfruit TLC59711 example into each other (which I'm aware is probably a terrible idea, but I'm learning how things work by adapting/breaking things).

Alongside my 8 bit PWM channels coming out of my mega, I've created some unsigned ints as containers for a 16 bit value and then asked the Arduino to use DMXSerial2.readrelative(n) to read the dmx values relative to the start address, adding a 'course' channel (dmx val *256) to a 'fine' channel dmx val to create a PWM value of 0- 65535 to the unsigned int container which the tlc.setPWM references to get a value.

Everything works as expected as I add the two examples together until I add the final tlc.write(); command and then the RDM function stops working on the Arduino (my external lighting controller loses sight of it) and when I send DMX channel values from the external DMX controller, the LEDs on the 8bit Arduino PWM outputs and the 16 bit TLC59711 outputs strobe at approximately 1hz at the brightness value I send out, UNLESS I send a global DMX channel 1 to 512 value to 255, were everything comes up together, but at a stable, non strobing level.

I know the DMXserial2 library doesn't play well with the serial monitor so I'm doing this blind, but I'd hoped it wouldn't interfere with SPI... but perhaps that's something to do with it.

As I'm extremely new to all of this I've hit a dead end and I have absolutely no idea what to try next, but I'm hoping that with your collective expertise, you might be able to point me the right way!

I'm aware that I've probably gone around this in quite an inefficient way and the rest of the code is a mess of the two examples I have mashed together with lots of redundant elements I can take out...

I'll include the code so you can have a good laugh at it and I apologise profusely to the original authors in advance!

Code: Select all | TOGGLE FULL SIZE
// - - - - -
// DmxSerial2 - A hardware supported interface to DMX and RDM.
// RDMSerialRecv.ino: Sample RDM application.
// Copyright (c) 2011-2013 by Matthias Hertel, http://www.mathertel.de
// This work is licensed under a BSD style license. See http://www.mathertel.de/License.aspx
// This Arduino project is a sample application for the DMXSerial2 library that shows
// how a 12 channel receiving RDM client can be implemented.
// The 12 channels are used for PWM Output:
// address (startAddress) + 0  (ChanA1) -> PWM Port 44
// address (startAddress) + 1  (ChanB1) -> PWM Port  3
// address (startAddress) + 2  (ChanC1) -> PWM Port  4
// address (startAddress) + 3  (ChanD1) -> PWM Port  5
// address (startAddress) + 4  (ChanE1) -> PWM Port  6
// address (startAddress) + 5  (ChanF1) -> PWM Port  7
// address (startAddress) + 6  (ChanG1) -> PWM Port  8
// address (startAddress) + 7  (ChanH1) -> PWM Port  9

// This sample shows how Device specific RDM Commands are handled in the processCommand function.
// The following RDM commands are implemented here:
// More documentation and samples are available at http://www.mathertel.de/Arduino
// 06.12.2012 created from DMXSerialRecv sample.
// 09.12.2012 added first RDM response.
// 22.01.2013 first published version to support RDM
// 03.03.2013 Using DMXSerial2 as a library
// 15.05.2013 Arduino Leonard and Arduino MEGA compatibility
// 15.12.2013 ADD: output information on a LEONARDO board by using the #define SERIAL_DEBUG definition
//            If you have to save pgm space you can delete the inner lines of this "#if" blocks
// 24.01.2014 Peter Newman/Sean Sill: Get device specific PIDs returning properly in supportedParameters
// 24.01.2014 Peter Newman: Make the device specific PIDs compliant with the OLA RDM Tests. Add device model ID option
// 12.04.2015 change of using datatype boolean to bool8.
// 25.05.2017 Stefan Krupop: Add support for sensors
// 21.08.2018 improvements and typo by Peter Newman
// 31.10.2018 Remove unnecessary #include <EEPROM.h> by Graham Hanson
// - - - - -

#include <DMXSerial2.h>
#include "Adafruit_TLC59711.h"
#include <SPI.h>

// How many boards do you have chained?
#define NUM_TLC59711 2

#define data   27
#define clock  26

Adafruit_TLC59711 tlc = Adafruit_TLC59711(NUM_TLC59711, clock, data);
//Adafruit_TLC59711 tlc = Adafruit_TLC59711(NUM_TLC59711);

// uncomment this line for enabling information on a LEONARD board.
//#define SERIAL_DEBUG

// Constants for demo program

const int ChanA2OP =  0;  // 16Bit PWM output pin for ChanA.
const int ChanB2OP =  1;  // 16Bit PWM output pin for ChanB.
const int ChanC2OP =  2;  // 16Bit PWM output pin for ChanC.
const int ChanD2OP =  3;  // 16Bit PWM output pin for ChanD
const int ChanE2OP =  4;  // 16Bit PWM output pin for ChanE
const int ChanF2OP =  5;  // 16Bit PWM output pin for ChanF
const int ChanG2OP =  6;  // 16Bit PWM output pin for ChanG
const int ChanH2OP =  7;  // 16Bit PWM output pin for ChanH
const int ChanI2OP =  8;  // 16Bit PWM output pin for ChanI
const int ChanJ2OP =  9;  // 16Bit PWM output pin for ChanJ
const int ChanK2OP = 10;  // 16Bit PWM output pin for ChanK
const int ChanL2OP = 11;  // 16Bit PWM output pin for ChanL

const int ChanA1Pin = 44;  // PWM output pin for ChanA1
const int ChanB1Pin =  3;  // PWM output pin for ChanB1
const int ChanC1Pin =  4;  // PWM output pin for ChanC1
//const int ChanD1Pin =  5;  // PWM output pin for ChanD1
//const int ChanE1Pin =  6;  // PWM output pin for ChanE1
const int ChanF1Pin =  7;  // PWM output pin for ChanF1
const int ChanG1Pin =  8;  // PWM output pin for ChanG1
const int ChanH1Pin =  9;  // PWM output pin for ChanH1

unsigned int ChanA2VAL = 0; //Container for 16 bit Value
unsigned int ChanB2VAL = 0; //Container for 16 bit Value
unsigned int ChanC2VAL = 0; //Container for 16 bit Value
unsigned int ChanD2VAL = 0; //Container for 16 bit Value
unsigned int ChanE2VAL = 0; //Container for 16 bit Value
unsigned int ChanF2VAL = 0; //Container for 16 bit Value
unsigned int ChanG2VAL = 0; //Container for 16 bit Value
unsigned int ChanH2VAL = 0; //Container for 16 bit Value
unsigned int ChanI2VAL = 0; //Container for 16 bit Value
unsigned int ChanJ2VAL = 0; //Container for 16 bit Value
unsigned int ChanK2VAL = 0; //Container for 16 bit Value
unsigned int ChanL2VAL = 0; //Container for 16 bit Value

#define ChanA2VAL ((256* DMXSerial2.readRelative(0)) + DMXSerial2.readRelative(1))
#define ChanB2VAL ((256* DMXSerial2.readRelative(2)) + DMXSerial2.readRelative(3))
#define ChanC2VAL ((256* DMXSerial2.readRelative(4)) + DMXSerial2.readRelative(5))
#define ChanD2VAL ((256* DMXSerial2.readRelative(6)) + DMXSerial2.readRelative(7))
#define ChanE2VAL ((256* DMXSerial2.readRelative(8)) + DMXSerial2.readRelative(9))
#define ChanF2VAL ((256* DMXSerial2.readRelative(10)) + DMXSerial2.readRelative(11))
#define ChanG2VAL ((256* DMXSerial2.readRelative(12)) + DMXSerial2.readRelative(13))
#define ChanH2VAL ((256* DMXSerial2.readRelative(14)) + DMXSerial2.readRelative(15))
#define ChanI2VAL ((256* DMXSerial2.readRelative(16)) + DMXSerial2.readRelative(17))
#define ChanJ2VAL ((256* DMXSerial2.readRelative(18)) + DMXSerial2.readRelative(19))
#define ChanK2VAL ((256* DMXSerial2.readRelative(20)) + DMXSerial2.readRelative(21))
#define ChanL2VAL ((256* DMXSerial2.readRelative(22)) + DMXSerial2.readRelative(23))

// Default Output Levels
#define ChanA1DefaultLevel 0x00 * 2
#define ChanB1DefaultLevel 0x00 * 2
#define ChanC1DefaultLevel 0x00 * 2
#define ChanD1DefaultLevel 0x00 * 2
#define ChanE1DefaultLevel 0x00 * 2
#define ChanF1DefaultLevel 0x00 * 2
#define ChanG1DefaultLevel 0x00 * 2
#define ChanH1DefaultLevel 0x00 * 2

// define the abcdefgh output type
void abcdefgh (byte a, byte b, byte c, byte d, byte e, byte f, byte g, byte h)
  analogWrite(ChanA1Pin, a);
  analogWrite(ChanB1Pin, b);
  analogWrite(ChanC1Pin, c);
  //analogWrite(ChanD1Pin, d);
  //analogWrite(ChanE1Pin, e);
  analogWrite(ChanF1Pin, f);
  analogWrite(ChanG1Pin, g);
  analogWrite(ChanH1Pin, h);
} // abcdefgh()

// see DMXSerial2.h for the definition of the fields of this structure
const uint16_t my_pids[] = {E120_DEVICE_HOURS, E120_LAMP_HOURS};
struct RDMINIT rdmInit = {
  "TommyDMX", // Manufacturer Label
  1, // Device Model ID
  "12 Channel PWM Controller", // Device Model Label
  24, // footprint
  (sizeof(my_pids) / sizeof(uint16_t)), my_pids,
  0, NULL

void setup ()

#if defined(SERIAL_DEBUG)
  // The Serial port can be used on Arduino Leonard Boards for debugging purpose
  // because it is not mapped to the real serial port of the ATmega32U4 chip but to the USB port.
  // Don't use that on Arduino Uno, 2009,... boards based on ATmega328 or ATmega168 chips.
  while (!Serial) ;

  // initialize the Serial interface to be used as an RDM Device Node.
  // There are several constants that have to be passed to the library so it can reposonse to the
  // corresponding commands for itself.
  DMXSerial2.init(&rdmInit, processCommand);

  uint16_t start = DMXSerial2.getStartAddress();
#if defined(SERIAL_DEBUG)
  Serial.print("Listening on DMX address #"); Serial.println(start);

  // set default values to reduced output level
  // this color will be shown when no signal is present for the first 5 seconds.
  DMXSerial2.write(start + 0,  10);
  DMXSerial2.write(start + 1,  10);
  DMXSerial2.write(start + 2,  10);
  DMXSerial2.write(start + 3,  10);
  DMXSerial2.write(start + 4,  10);
  DMXSerial2.write(start + 5,  10);
  DMXSerial2.write(start + 6,  10);
  DMXSerial2.write(start + 7,  10);

  // enable pwm outputs
  pinMode(ChanA1Pin, OUTPUT); // sets the digital pin as output
  pinMode(ChanB1Pin, OUTPUT);
  pinMode(ChanC1Pin, OUTPUT);
//  pinMode(ChanD1Pin, OUTPUT);
//  pinMode(ChanE1Pin, OUTPUT);
  pinMode(ChanF1Pin, OUTPUT);
  pinMode(ChanG1Pin, OUTPUT);
  pinMode(ChanH1Pin, OUTPUT);

#if defined(SERIAL_DEBUG)
  // output the current DeviceID

  DEVICEID thisDevice;

  Serial.print("This Device is: ");
  if (thisDevice[0] < 0x10) Serial.print('0'); Serial.print(thisDevice[0], HEX);
  if (thisDevice[1] < 0x10) Serial.print('0'); Serial.print(thisDevice[1], HEX);
  if (thisDevice[2] < 0x10) Serial.print('0'); Serial.print(thisDevice[2], HEX);
  if (thisDevice[3] < 0x10) Serial.print('0'); Serial.print(thisDevice[3], HEX);
  if (thisDevice[4] < 0x10) Serial.print('0'); Serial.print(thisDevice[4], HEX);
  if (thisDevice[5] < 0x10) Serial.print('0'); Serial.print(thisDevice[5], HEX);

} // setup()

void loop() {
  // Calculate how long no data backet was received
  unsigned long lastPacket = DMXSerial2.noDataSince();

  if (DMXSerial2.isIdentifyMode()) {
    // RDM command for identification was sent.
    // Blink the device.
    unsigned long now = millis();
    if (now % 1000 < 500) {
      abcdefgh (200, 200, 200, 200, 200, 200, 200, 200);
    } else {
      abcdefgh (0, 0, 0, 0, 0, 0, 0, 0);
    } // if

  } else if (lastPacket < 30000) {
    // read recent DMX values and set pwm levels
    analogWrite(ChanA1Pin, DMXSerial2.readRelative(0));
    analogWrite(ChanB1Pin, DMXSerial2.readRelative(1));
    analogWrite(ChanC1Pin, DMXSerial2.readRelative(2));
   // analogWrite(ChanD1Pin, DMXSerial2.readRelative(3));
   // analogWrite(ChanE1Pin, DMXSerial2.readRelative(4));
    analogWrite(ChanF1Pin, DMXSerial2.readRelative(5));
    analogWrite(ChanG1Pin, DMXSerial2.readRelative(6));
    analogWrite(ChanH1Pin, DMXSerial2.readRelative(7));
  } else {
#if defined(SERIAL_DEBUG)
    Serial.println("no signal since 30 secs.");
    // Show default color, when no data was received since 30 seconds or more.
    analogWrite(ChanA1Pin, ChanA1DefaultLevel);
    analogWrite(ChanB1Pin, ChanB1DefaultLevel);
    analogWrite(ChanC1Pin, ChanC1DefaultLevel);
   // analogWrite(ChanD1Pin, ChanD1DefaultLevel);
    //analogWrite(ChanE1Pin, ChanE1DefaultLevel);
    analogWrite(ChanF1Pin, ChanF1DefaultLevel);
    analogWrite(ChanG1Pin, ChanG1DefaultLevel);
    analogWrite(ChanH1Pin, ChanH1DefaultLevel);

  } // if

  // check for unhandled RDM commands
} // loop()

// This function was registered to the DMXSerial2 library in the initRDM call.
// Here device specific RDM Commands are implemented.
bool8 processCommand(struct RDMDATA *rdm, uint16_t *nackReason)
  byte CmdClass       = rdm->CmdClass;     // command class
  uint16_t Parameter  = rdm->Parameter;      // parameter ID
  bool8 handled = false;

  // This is a sample of how to return some device specific data
  if (Parameter == SWAPINT(E120_DEVICE_HOURS)) {  //0x0400
    if (CmdClass == E120_GET_COMMAND) {
      if (rdm->DataLength > 0) {
        // Unexpected data
        *nackReason = E120_NR_FORMAT_ERROR;
      } else if (rdm->SubDev != 0) {
        // No sub-devices supported
        *nackReason = E120_NR_SUB_DEVICE_OUT_OF_RANGE;
      } else {
        rdm->DataLength = 4;
        rdm->Data[0] = 0;
        rdm->Data[1] = 0;
        rdm->Data[2] = 2;
        rdm->Data[3] = 0;
        handled = true;
    } else if (CmdClass == E120_SET_COMMAND) {
      // This device doesn't support set
      *nackReason = E120_NR_UNSUPPORTED_COMMAND_CLASS;

  } else if (Parameter == SWAPINT(E120_LAMP_HOURS)) { // 0x0401
    if (CmdClass == E120_GET_COMMAND) {
      if (rdm->DataLength > 0) {
        // Unexpected data
        *nackReason = E120_NR_FORMAT_ERROR;
      } else if (rdm->SubDev != 0) {
        // No sub-devices supported
        *nackReason = E120_NR_SUB_DEVICE_OUT_OF_RANGE;
      } else {
        rdm->DataLength = 4;
        rdm->Data[0] = 0;
        rdm->Data[1] = 0;
        rdm->Data[2] = 0;
        rdm->Data[3] = 1;
        handled = true;
    } else if (CmdClass == E120_SET_COMMAND) {
      // This device doesn't support set
      *nackReason = E120_NR_UNSUPPORTED_COMMAND_CLASS;
  } // if

  return handled;
} // processCommand

// End.

Posts: 3
Joined: Sat Jan 15, 2022 12:45 pm

Re: TLC59711 with DMX

by TommyCo10 on Sun Jan 16, 2022 3:31 am

Just to provide a small update, I’ve changed the line in the dmxserial2.cpp file to move dmx data to serial port 1 on the mega and used some short wires to take the tx/rx from the jumpers on the the dmx shield to the correct rx1/tx1 pins on the mega.

This hasn’t fixed the problem but going forward, it might free up the serial0 port for debugging with the serial monitor, so if you can think of anything to try using that, I have that as an option.

I wonder if someone more experienced can have a look at my #define methodology for taking two channels of DMX values (0-225), making one a ‘course’ channel by multiplying this value by 256 and adding the final 255 to this from the second dmx ‘fine’ channel so it’s possible to get a total value of 65535 to set the PWM.

Whilst this is a standard way of controlling a 16bit intensity channel on a dmx lighting desk, is my setting up of this calculation in a #define setup the cause of this pulsing problem?

I’ve noticed that if I remove the reference to the unsigned int ChanA2VAL in the tlc.setPWM command and just put a number from 0-65535, I get a steady level on that channel. I’m not sure how I test if it’s this bad coding or if something is interrupting the stream of DMX data instead which causes everything to pulse unless it’s at full.

Also if I give a value to a dmx channel which is outside the range of the footprint of the device, I get seemingly random flashing and strobing on seemingly random channels…

Posts: 3
Joined: Sat Jan 15, 2022 12:45 pm

Re: TLC59711 with DMX

by TommyCo10 on Sun Jan 16, 2022 2:42 pm

I just thought I'd post another update in case anyone sees this in the future.

I managed to solve the issue by using the TLC59711 library from Ulrich Stern instead of the Adafruit library. I'm able to get stable brightness levels with the SPI hardware clock divider set to (4). Anything faster causes the strobing/flashing problem again and knocks out the RDM.

I would like to see if this resolution can be further improved, but I think that will take some more research and fiddling. Im just pleased to get it working at all!

Posts: 3
Joined: Sat Jan 15, 2022 12:45 pm

Please be positive and constructive with your questions and comments.