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:
- Simple input protocol for entering commands using 4x4, 16 button keypad.
- Support LCD display, 4 line x 20 character
- Output DMX for single channel or a range of channels at a set intensity level.
I started with the following:
- An Arduino UNO board
- A 4x4, 16 button keypad (button matrix)
- A 4 line by 20 Character LCD display w I2C (Serial) Interface
- A low cost DMX / RDM Shield purchased from EBay (model: CTC-DRA-10-1, low cost, non-isolated)
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:
- Keypad input - 4 x 4 (16) button key pad (or switch array)
- LCD display - 4 x 20 Character LCD w I2C interface
- Verify/test the input commands (protocol) were working correctly using a state machine
- Add DMX master (sending) code
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 |
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.
- 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: - I defined SERIAL_DEBUG_ENABLED
- I used #ifdef to conditionally compile in/out the serial/DMX functions.
- 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.
// 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:
- Arduino Uno
- LCD display, 4 line by 20 character with I2C (serial) interface
- Keypad 4x4 (16 button)
- DMX shield - model: CTC-DRA-10-1, low cost, non-isolated
- Plastic enclosure case
- Battery holder/case
- XLR 5 pin connectors - chassis mount (I use 5 pin instead of 3 pin on the DMX shield)
Future Enhancements / Ideas:
- Allow selectable percent (1 to 100) or value (1 to 256) for intensity
- Support channel bump operation. Allow channels to be selected in a range by pressing the 'B'ump button.
- Add menu for additional features.
- Option to receive a channel and display the value.
- Support multifunction keys (keys can have multiple meaning depending on mode)
- Convert the keypad from 8 lines to 2 lines using ADC or I2C
I'm hoping to use this project to spin off into several other projects. Here is a brief description of the other projects:
- 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