Monday, April 21, 2014

Arduino DMX Tester - Inexpensive Tester for Sending DMX-512



Hello All,

I work part-time (more of a hobby) in the lighting industry and use DMX since it is the industry standard for communicating or controlling devices (lighting fixtures, controllers, consoles, etc..)  I have seen commercial DMX testers on the market but I wanted to create my own.

I have been working on an idea to create a low cost (<$50), Arduino based DMX tester.

The tester would provide the following functionality:
  1. Simple input protocol for entering commands using 4x4, 16 button keypad.
  2. Support LCD display, 4 line x 20 character
  3. Output DMX for single channel or a range of channels at a set intensity level.
  4.  
The Hardware:

I started with the following:
  1. An Arduino UNO board
  2. A 4x4, 16 button keypad (button matrix) 
  3. A 4 line by 20 Character LCD display w I2C (Serial) Interface
  4. A low cost DMX / RDM Shield purchased from EBay (model: CTC-DRA-10-1, low cost, non-isolated)
The Input Commands:

I wanted to use a 4 x 4 (16) button key pad to input all the commands with a simple / easy to remember protocol (format).

Here is the basic command format:

Channel@Intensity
Start Channel-End Channel@ Intensity

Here is the actual input protocol using only a 4 x 4 - 16 button key pad:
 
Command Description
XXX@III# Single Channel at a Specified Intensity
XXX-XXX@III# Range of Channels at a Specified Intensity
*@*# All Channels at Full Intensity
XXX@*# Single Channel at Full Intensity
XXX-*@III# Start Channel to Max Channel at a Specified Intensity
XXX-*@*# Start Channel to Max Channel at Full Intensity

Legend:
XXX = 1 to 512 Channel Number
III = 1 to 256 Intensity Level

Alpha-Numeric Key Mappings:
A = @ (at sign)
B = Bump (not implemented)
D = - (dash)
C = Clear
* = Wildcard value: 512 for channel and 256 for Full intensity
# = Execute

Code Development / Testing:

I developed / tested the code in several stages:
  1. Keypad input - 4 x 4 (16) button key pad (or switch array)
  2. LCD display - 4 x 20 Character LCD w I2C interface
  3. Verify/test the input commands (protocol) were working correctly using a state machine
  4. Add DMX master (sending) code
The Keypad
Hardware:

I had 3 types of key pads that I played with (switches on a PC board, membrane switch, soft-touch) See the pictures below.
2 different 4x4 -16 button keypads

Another 4x4 - 16 button keypad

Software:

I started with the Keypad library for easy matrix style keypad mapping.  See http://playground.arduino.cc/code/Keypad for more information.

I had to play around with the Row and Column mapping to get my Key Pad switch matrix to work. The pin-out in the documentation wasn't correct on any of the keypads.  So once I determined the correct pin-out, the code worked perfectly.

Here is code snippet showing how to use the Keypad library:

(This is for the 16 switches on PC board)


#include <Keypad.h>

const byte ROWS = 4; // define four rows
const byte COLS = 4; // define four columns
char keys [ROWS] [COLS] = {
{'1', '2', '3','@'},
{'4', '5', '6','B'},
{'7', '8', '9','C'},
{'*', '0', '#','-'}
};

// Pin  R/C 
// 8    C4
// 7    C3
// 6    C2
// 5    C1
// 4    R1
// 3    R2
// 2    R3
// 1    R4

// Connect 4 * 4 keypad row-bit port, the corresponding digital IO ports panel
byte rowPins [ROWS] = {6,7,8,9};

// Connect 4 * 4 buttons faithfully port, the corresponding digital IO ports panel
byte colPins [COLS] = {10,11,12,13};

// Call the function library function Keypad
Keypad keypad = Keypad (makeKeymap (keys), rowPins, colPins, ROWS, COLS);

void loop () {
  
  char key = keypad.getKey ();
  if (key != NO_KEY) {
    
    // Clear
    if(key == 'C') {
      state = CLEAR;
    }
}

The LCD display

Hardware:

I used a standard 4x20 character display with I2C (serial) interface which can be purchased from Ebay for about $10.00.

4 line by 20 character LCD display with I2C (serial) interface


Software:

I started with a I2C LCD display library. See LiquidCrystal_I2C Library 
for more information.

I really didn't have any problems getting the display to work. The 2x16 character display included in my Arduino kit didn't work so I ordered a 4x20 character display and it worked the first time I tried it.

Here is a code snippet showing how to use the LiquidCrystal_I2C library:

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x3F,20,4);

void setup () {
  lcd.init();
  lcd.backlight();
  lcd.setCursor(0,0);
  lcd.print("DMX Tester ");
  lcd.print(VERSION);
  lcd.setCursor(0,1);
  lcd.print("Enter Cmd:");
}



The Input Protocol (Commands)

At first I was going to use a state machine logic to handle the input from the keypad but I decided to try to just code it with normal logic.  After writing the initial version, I spent about 4 or 5 hours debugging the code.  I soon realized I should have created a state diagram and used state driven logic. to parse the input properly.

So I deleted most of the code and wrote a test plan and state diagram to match the input protocol.

Here is the test plan with all the input protocols defined in spreadsheet format: DMX Tester Test Plan

Here is the state diagram in spreadsheet format: DMX Tester State Diagram
Here is the state diagram in diagram format:

State Diagram in Diagram Format


Once I have the state diagram completed, I coded the input part of the projects in about 1.5 hours while I was on a sitting on plane.  After I tested and was satisfied the input logic was working I attached the DMX shield and added the DMX library.

Introduction to State Machine Logic

If you aren't familiar with or haven't used state machine logic in programming, it is the easiest way to to break complex problems into manageable states and state transitions.  One of the easiest ways to implement a state machine is to use a switch statement.  In my opinion it is the only way to implement serial input commands.

Example of a state machine using a switch statement:

switch(state) {
   case INITIAL:
     // process INITIAL state  
   break;
   case STATE1:
     // process STATE1 state       
   break;
   case CLEAR:
     clearAll();
     state = INITIAL;
   break;
   default:
   break;
}

Depending on the state, there are only a few valid keys.  Lets take a look at the first state called INITIAL.  As you can see from the state diagram the INITIAL state can only have the following keys: 0-9 and '*' (asterisk), else it is a format error.


State Keys Next State Comment
INITIAL 0-9 START1 X
INITIAL * WILDCARD1 *@
INITIAL Anything Else INITIAL Format Error

Here is a code snippet showing how I implemented the INITIAL state:

char key = keypad.getKey ();
  if (key != NO_KEY) {

    switch(state) {
      case INITIAL:
        // X
        if(validKeys0to9(key)) {
          pos = displayKey(key, pos);
          storeKey(key);
          state = START1;
        } else if (validWildCard(key)) {
          // *@
          pos = displayKey(key, pos);
          state = WILDCARD1;          
        } else {
          invalidFormat();
          clearAll();
        }
      break;

As you can see I created some helper functions to make the code easy to read.

// Validate key *
// return true if valid, else false
int validWildCard(int key) {
  int valid=0;
  switch(key) {
    case '*':
      valid=1;
    break;
  }
  return valid;
}

// Validate key 0-9
// return true if valid, else false
int validKeys0to9(int key) {
  int valid=0;
  switch(key) {
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
      valid=1;
    break;
  }
  return valid;
}

The DMX Shield

Hardware:

I used a DMX Shield - Model: CTC-DRA-10-1, low cost, non-isolated which can be purchased from Ebay for about $15.00

DMX Shield - Model: CTC-DRA-10-1


Software:

I used the Conceptinetics DMX Library to handle the DMX master (sending).   See Conceptinetics DMX Library and this DMX Shield Blog more information.
The library is very simple to use.  Initialize and use 2 different commands to send either a single channel or a range of channels.

Here is a code snippet showing how to initialize and use the Conceptinetics DMX Library:

#define DMX_MASTER_CHANNELS  512

// Pin number to change read or write mode on the shield
#define RXEN_PIN                2

// Configure a DMX master controller, the master controller
// will use the RXEN_PIN to control its write operation 
// on the bus
DMX_Master        dmx_master ( DMX_MASTER_CHANNELS, RXEN_PIN );

void setup () {
  dmx_master.enable ();
}

void sendDMX(int start, int end, unsigned char intensity) {
  if(start == end) {
      dmx_master.setChannelValue(start, intensity);    
  } else {
    dmx_master.setChannelRange(start, end, intensity );
  }
}

Development Issues:

During the development of this project I ran into a few issues.  I will describe the issues and how I resolved them.
  1. The serial port and the DMX library have a conflict with the interrupt handler.  This means you can't debug using the serial terminal and have the DMX shield working at the same time.

    Here is what I did to resolve this issue:
    1. I defined SERIAL_DEBUG_ENABLED
    2. I used #ifdef to conditionally compile in/out the serial/DMX functions.
    3. For some unknown reason I couldn't #ifdef out the #include <conceptinetics.h> header file so I have to comment it in/out to make it work. See examples below.
FOR DMX ENABLED
// Comment out for Serial but not DMX
//#define SERIAL_DEBUG_ENABLED

// Comment out for Serial - include for DMX
#include <conceptinetics.h>

// Serial or DMX but not both
#ifdef SERIAL_DEBUG_ENABLED
  Serial.begin (9600);
#else
  dmx_master.enable ();
#endif

FOR SERIAL ENABLED
// Comment out for Serial but not DMX
#define SERIAL_DEBUG_ENABLED

// Comment out for Serial - include for DMX
//#include <conceptinetics.h>

// Serial or DMX but not both
#ifdef SERIAL_DEBUG_ENABLED
  Serial.begin (9600);
#else
  dmx_master.enable ();
#endif

Packaging:

Here is the complete project without a case and no battery pack.

Working project without a case

I'm still working on putting the project into a case (packaging).  More information to come...

Completed Project:

I will post some pictures of the completed DMX tester and a short video showing how it works.

  •  Currently waiting on the electronic project case to arrive so I can mount and package everything in a nice case.


Source Code:

The complete DMX Tester source code is available here.


And on Github: https://github.com/onewithhammer/DMX_Tester


Parts List:
  1. Arduino Uno
  2. LCD display, 4 line by 20 character with I2C (serial) interface
  3. Keypad 4x4 (16 button)
  4. DMX shield - model: CTC-DRA-10-1, low cost, non-isolated
  5. Plastic enclosure case
  6. Battery holder/case
  7. XLR 5 pin connectors - chassis mount (I use 5 pin instead of 3 pin on the DMX shield)

Future Enhancements / Ideas:
  1. Allow selectable percent (1 to 100) or value (1 to 256) for intensity
  2. Support channel bump operation.  Allow channels to be selected in a range by pressing the 'B'ump button.
  3. Add menu for additional features.
  4. Option to receive a channel and display the value.
  5. Support multifunction  keys (keys can have multiple meaning depending on mode)
  6. Convert the keypad from 8 lines to 2 lines using ADC or I2C
Other Projects:

 I'm hoping to use this project to spin off into several other projects.  Here is a brief description of the other projects:
  1. A proximity detector that sends a DMX trigger signal (message).  I want to use this for a Halloween project.  When a person walks by an area, the proximity detector will trigger and send a DMX message to a lighting console configured to accept this channel.  The message will trigger a pre-programmed lighting scene.

Questions?

If you have any questions please feel free to ask.

I hope this blog is helpful and useful to someone interested in DMX in the lighting field.

Thanks,
Tony 


4 comments:

  1. Hi Tony
    Have you done a schematic for this project?

    Regards
    Tim

    ReplyDelete
  2. Tim,

    Sorry for the delay I was traveling. I didn't create a schematic but I can draw a block diagram schematic for you very easily. The pin outs are listed in the comments.

    Tony

    ReplyDelete
  3. hello, this is a really great project. Have you tried it with the MAX485 module? so it is cheaper but my test wont work with it.

    ReplyDelete
  4. Lovely, thanks for posting and for keeping it up...! I'm also having trouble debugging a DMX master and your serial port workaround has saved me, thanks.

    ReplyDelete