Receive DMX-512 with an Arduino
Part I: ProloguePart II: Instructions

Shiny and new out of the box!
Prologue: For Christmas, I received an Arduino. If you’re not familiar with them, they’re like a little computer with a lot of pins to which you can connect outputs like LEDs, servos, relays, triacs, or anything you’d want to control, as well as photosensors, switches, anything you’d want to take an input from. You write your program in the easy-to-learn Arduino environment, upload it to the Arduino board, and it’ll run your program automagically. I’m not a programmer, but less than an hour after taking it out of the box I had it blinking an LED for me. Buy one, they’re perfect for all of us who are trying to create some Theater Magic with no money or hope of getting any.
Well, Almost Perfect. There’s been a way to send DMX with an Arduino for awhile, but when I started poking around for DMX reception code, I came up with zilch. If you’re already savvy with microcontrollers and assembly code and avrdude and whatever-the-fuck-else, you probably know about this solution. Me, I look at assembly code and I just hear a dull screaming in my head, nevermind all that other stuff that I don’t know how to do either.
So I figured that a great first project would be to remedy this situation, and write a program to receive DMX on the Arduino platform. In the way of all Works in this Vale of Tears, this ended up being much more difficult and taking much longer than I initially anticipated. But eventually I figured it all out, and so here it is!
Features:
- In-the-field addressing from 1 to 512 via two tact switches (works with the previously released I/O Shield, here).
- Address is stored in non-volatile EEPROM, so it is retained when power is lost to the Arduino.
- Addressing hardware allows full use of the pins.
- Number of addresses to receive is configurable.
- Works with controllers that send less than the full 512 address set.
- Break detection is done correctly by detecting a Low value of >88μS per ANSI E1.11-2008, rather than the frame error hack used by many devices.
- Uses interrupt-based subroutines to eliminate processor-load related timing problems.
- If the DMX data signal is lost, the Arduino will maintain the current state until new values are received.
- The reception and user code run sequentially rather than at the same time, so they won’t interfere with each others’ timing.
Continue to Page 2 for Download and Instructions…
Go to Section:

April 1st, 2009 at 10:42 pm
How difficult is it to get this working for the ATMega 328 Chip? It seems that all the new Arduino boards are now using them. Otherwise, Would it be possible for me to drop an ATMega 168 into the socket on the board and have it work with this code or would more powerful reflashing of the chip be required?
Thanks!
April 2nd, 2009 at 1:11 am
From a cursory glance at the Atmega328 datasheet, the register names are exactly the same between the 328 and the 168. So, it might work right out of the box, maybe? If there is some difference in register or interrupt names, you’ll have to adapt the code by finding the function in the 168 datasheet referred to by all those bitset(REGISTER, BIT); commands, then finding the corresponding register in the 328 datasheet, and changing the name in the code to match what you find there. If you’ve done this kind of thing before, this is a simple job, otherwise it might take you a few hours. Code that works with the Atmega328 is something I want to do someday, but I don’t have one to test with at present.
c
You could just plug an Atmega168 into the board, as long as it has already been burned with the Arduino bootloader. Chips from Mouser or whatever won’t do this, but there are a number of vendors that sell them pre-loaded (Adafruit, Maker Shed, etc.)
Best of luck, let me know how it turns out.
April 2nd, 2009 at 8:21 pm
Thanks, Thats exactly what I needed. Im looking at using the DMX to drive some stepper motors possibly using an Arduino Motor shield. From looking at the spec sheets for your schematic as well as the motor shield, it seems like none of the pin usages overlap.
April 26th, 2009 at 2:00 am
Thanks for this!! It works great on my Arduino with ATmega328P. no modifications needed at all. Now I am waiting for my samples (TI, TLC5940) so I can have more PWM. Good work!
April 29th, 2009 at 2:46 pm
Thanks for letting me know that it works with the 328, I’ve updated the page accordingly.
May 8th, 2009 at 12:55 pm
Hi again!
I am now trying this with a TLC5940. And it is not working :( I think it is because booth libraries uses TIMER2. Is there a way to make this work without TIMER2 (or TIMER1)?? What do you think?
May 13th, 2009 at 4:42 pm
Hmmm. Well, Timer2 is used by my software for break detection–it fires an interrupt every 4uS exactly which checks the state of the serial input pin, and when we have 22 zeros in a row we’re in a break.
But there’s no reason why that part of it couldn’t be run off Timer1. Timer1 is a 16bit timer, so the configuration might be a little more complex. All of those “bitClear(REGISTER, BIT);” that pertain to Timer2 are the parts that will have to be adapted to the Timer1 register names.
Sounds like a cool project, if you get it working post a link.
May 15th, 2009 at 2:42 pm
Hey
Firstly, thanks so much for getting this working! I have a couple of projects that this is going to go into :D
Secondly, if I’m not mistaken, switching to Timer 1 still wouldn’t help because the TLC library uses bot 1 and 2 for different things (I think). Unless some portion of the TLC library could share with the break detection, or something.
May 17th, 2009 at 12:23 am
Since you’re talking to the TLC5490 over serial you don’t have to constantly feed it data, right? Couldn’t you reconfigure the TLC routine so that it will set up timer2 every time during the action() loop, and then reset to the dmx setup at the end? Sequentially, like.
Alternatively, I think you can trigger an interrupt from some of the pins. So if you had a circuit that dropped the pin to LOW every 4uS, that would work.
Or, adapt it to the arduino mega?
*EDIT* I’ve thought about this some more, and I don’t think adapting it to the Mega is going to help you. The Mega still runs at a 16MHz clock speed, so each clock cycle is 62.5nS, if I’m remembering right. When I wrote the break detection ISR, I needed it to complete in less than 4uS = 4000nS, so in 64 clock cycles or less. I had to write my code pretty carefully to achieve this, and I don’t think there will be enough time to feed your TLC5490.
So maybe buy a second bare-bones arduino (cheap), and have it do the TLC5490 processing?
May 17th, 2009 at 10:50 pm
I’m pretty sure one of the timers has to be constantly in use by the TLC to actually get the steady PWM timing to work on the TLC. I don’t really know what the other one is used for, but I’m still trying to figure out all of this interrupt/timer stuff (heck, I still don’t get how the DMX receiver code can handle anything other than exactly 88us break. Looks like anything longer would be read as 0s in a data byte? O_O. I take your word though that it works).
But yeah, I was leaning toward porting them to the mega since it has all the extra timers. But whichever way I get to work, I’ll make sure to post back what changes are needed.
May 18th, 2009 at 8:27 am
I have also fought to adapt if to the arduino mega. but I don’t have one :(
I have found another DMX receiver scetch (http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1223117506/0) but I haven’t gotten that to work on my 328. And that code I dont understand at all. your code is quite clear, but that!
But I am still trying :) hopefully this will work some day. the goal with my project is to make a primitive moving head with pan/tilt and RGB-mixing. in a later stage mayby with real CMY-wheels:)
May 18th, 2009 at 5:38 pm
I took pictures of all my whiteboard flowcharts intending to write up the design process. Interrupt-based algorithms are incredibly handy, and there isn’t a lot out there to help people get started. I’ll try to get that off the back burner.
May 22nd, 2009 at 9:19 am
I know this may sound like a stupid question, but where do you guys get ICs like the max485 without having to buy a bunch? I got one off of jameco but apparently got the wrong one because it is much smaller. Its the csa and not the epa which is the one I am assuming you are using?
May 22nd, 2009 at 10:56 am
For the max485 in particular, I ordered a free sample from Maxim IC. In general, though, you can order them online through digikey.com or mouser.com. Since you’re posting from San Jose, CA I’m sure there’s also a local electronics supply store (a real one, not Radioshack or Fry’s) that will have that part or an equivalent in stock.
May 23rd, 2009 at 5:25 pm
@Elliot: I had an idea. Why not adapt the break detection routine to trigger off Timer0?
Ordinarily, messing with timer0 is a Bad Idea because it will break all of the default arduino routines like delay() and whatnot. But since you only need to borrow it for ~80uS to poll the serial pin, you might be able to configure it and then immediately set it back to default once the break detection is done.
May 24th, 2009 at 2:45 pm
Hmm, interesting. I’ll have to give that a look when I get a chance. Thanks for the suggestion.
Oh, and I finally understand how it is able to deal with larger breaks and such, though those white board pics would be interesting to look at.
May 27th, 2009 at 8:22 pm
Hi Max,
so i’ve just started my own dmx send receive project which borrows pretty heavily from your code.
(read takes entirely :) )
with a few modifications.
posted for help at
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1243480137/0
would love you to have a look and see what you think
May 30th, 2009 at 1:13 pm
Quick question, is the 10 channel limit due to a hardware limitation? I’m looking to control approximately 10 RGB LEDs with this. User code would be minimal, just setting BlinkM’s. How much higher do you think you could go?
Cheers
May 30th, 2009 at 1:35 pm
Good question, the number of channels received is set at 8 by default but entirely customizable. If you look at the USART_RX_vect ISR, you’ll see the code:
dmxvalue[i] = dmxreceived;
i++;
if(i == 8) {
bitClear(UCSR0B, RXCIE0);
dmxnewvalue = 1; //set newvalue, so that the main code can be executed.
increasing that the maximum value that i is allowed to run to from 8 will increase the number of channels that will be recorded (you’ll also have to increase the dmxvalue[] array size). You bring up a good point, which is that of course people will want to customize how many channels are received. I will try to make that a little more user-friendly in the next release of the software.
As far as maximum size, you’re only limited by the amount of RAM. I wouldn’t be surprised if you could receive the whole 512 frame, if you don’t have a lot of user code.
***Update: see Rev13 of the software***
May 31st, 2009 at 2:35 pm
Hey Max, I am a little unclear as what to do with the action loop part of the code. What am I telling the arduino to do at this point? I have looked through your code a couple of times and tried refering the arduino site, but I’m a coding noob and don’t know how to proceed. I am also getting void loops errors which may be part of the action loop problem?
June 1st, 2009 at 2:39 pm
Oops. Got it. Opened the file incorrectly.
June 6th, 2009 at 1:48 am
I’d love to hear of any experiences integrating this code on an Arduino which in turn controls I2C devices (Paul, how’s the BlinkM going?). I’m keen to use a PCA9552 16 bit LED driver, any reasons I2C could cause conflicts with timing or whatnot? Thanks for your work Max!
June 11th, 2009 at 12:20 pm
I don’t know much about I2C, but in general:
If the I2C routine uses timer2, you’ll have to adapt it or the DMX code to another timer.
If you have to constantly refresh the data you’re sending with no breaks or pauses, it will probably be difficult or impossible to combine with the DMX reception software.
Yeah, I was kind of imagining this software as being used to control relays or something, where you “set and forget,” whereas it seems like people want to use it to feed streaming data to other ICs. I’ve been trying to figure out a way to redesign the algorithm it so that it can be more flexible in timing, but ultimately there’s only one processor, so there are inherent limitations here. Anyway, let me know if you manage to get it working, I’d be interested to hear how you make out.
July 2nd, 2009 at 3:07 pm
Hi Max,
Minor problem I could do with some help on please!
I am using Arduino 0016 and replacing the HardwareSerial.cpp seems to disable the serial.read function.
Is this a ‘feature’ which means I need to copy the cpp in and out, or is there something different I can do with this version?
Also if I may, I am thinking of trying to use a MAX3232 and NewSoftSerial rather than the hardware serial. Do you think this would be achievable, or is speed/timing going to be an issue?
Thanks,
Paul
July 3rd, 2009 at 6:42 am
Me again!
I have this up-and-running on the hardware serial, on a Duemilanove 328.
It works, but the DMX values are not stable.
Displaying the first two DMX values on an LCD, I see the value 255 for instance, but then it jumps to another value, and then jumps back again.
Any ideas? Its also not consistent in how long it is stable befofe a glitch.
It seems other people have had some success with this on a 328, so far I can say it works, but not stable values??
Thanks,
Paul
July 5th, 2009 at 5:06 pm
Hey Paul,
Where is the other (incorrect) value coming from? If it’s after the correct value, then the receiver is missing/undercounting some channels, which is presumably a receiver processor load problem, so I’d look at what libraries and functions you have in the code. If the value is before the correct value, the only explanation I can think of is that extra imaginary channels during the break are getting counted, caused by noise in the DMX line.
In either case I would also try some variations along the lines of changing the channel address, and removing any other devices and plugging the receiver directly into the controller with a single ‘known good’ cable. And of course do your usual DMX due diligence with not running data and power in the same raceway, using good shielded twisted pair wire, proper termination, etc.
I have not tested the sketch on 0016 yet. I would try using the default .cpp files, and hit compile and see what conflicts it gives you, if any. Perhaps they’ve finally stopped including the Serial routines onboard whether you reference a serial function or not, and then it’ll just work. If not, the error message may still be illuminating.
The MAX3232 is a RS-232 chip, and DMX is RS-485?
Finally, w.r.t. NewSoftSerial, it may work, or not. You would have to write reasonably efficient code to keep up with a 250Kbps data rate on a 16Mhz processor without the hardware USART helper, but such a thing is possible, I think. I’ll bet dollars to donuts they’re using Timer2, though. So you’ll have to adapt either their code or mine to run on timer1, which shouldn’t be too difficult.
July 6th, 2009 at 3:30 pm
Hi Max,
Thanks for the reply.
The incorrect value appears after the correct one so to speak.
I [orginally] tested this with LED 13, the code in the loop was simply turn the LED on if DMX channel 1 was 255, otherwise turn the LED off.
The LED was turning itself on and off, despite the channel level being at 255.
To better understand what was happening, I am using an LCD ( http://www.nuelectronics.com/estore/index.php?main_page=product_info&cPath=1&products_id=2 )
to display the first two channels of DMX.
If I raise the DMX value to 255, the values go to that value, but will not permanetly stay at that value, they jump down a bit and then back again.
I am using a known good Enttec USB Pro and LightFactory as the test output controller, and the circuit is directly connected within 15cm or so.
The channel address is set to 1 at the moment.
I am using the 150 resister to terminate the DMX circuit.
I think it may be a timing issue with the 328 vs the 168 you originally wrote for?
Sorry, I release I have not been clear..
The idea in the addition of the MAX3232 is that I am trying to get a DMX to Serial control going, and if the DMX recieve uses the hardware serial, I need additional circuit to handle the serial output.
I think it is best to leave the DMX to the hardware serial, and use the NewSoftSerial with MAX3232 for the serial device. Not sure about the timer2 yet, that could be an inconvience, the other option is the softwareserial in the playground, but I cannot get a stable output with that…
More when I can get back in front of the circuit in a couple of days!
Thanks,
Paul
August 2nd, 2009 at 8:56 pm
Hey Max, I’d just like to say thanks again, I have successfully adapted and implemented your code for a rather ambitious project. I’ve just under a hundred 328′s working beautifully in a piece of interactive theatre. Over 1000 LEDs lighting and 40 panel meters moving synchronously. Control from a Strand 520i, with a splitter before each chain of <32 units. Using the TI 75176 chip and data over UTP cable. Next trick will be making nodes that can transmit, but that's considerably more difficult… wish me luck! r (p.s photos soon if you're interested)
August 3rd, 2009 at 12:18 am
Robert,
That sounds awesome! Definitely if you get some photos and information posted up, post up a link.
September 20th, 2009 at 9:43 am
Hi Paul,
I am little confused by some of your bad addressing examples:
[quote]Will not detect bad addressing, such as “dmxaddress=510;” or ”dmxaddress=50;” when only 55 slots are sent by the controller.[/quote]
so 510 is bad because you are only sending values on slots 1-55 so there is no data on slot 510?
and 50 is bad for the same reason; only sending data on slots 1-55 so if the start address is 50 you are only getting 5 slots worth of data read by the arduinoDMX?
Same goes for this statement:
[quote]
Add bad start address value detection, based on the number of frames received (so if you set the start address to 50, but only 55 DMX slots are being sent by the controller, the error will be indicated by the pin 13 LED).[/quote]
Why is the error based on number of frames received instead of the size of the frame?
I am new to DMX so it could be I am simply not understanding the protocol and lingo.
September 20th, 2009 at 9:51 am
little correction to my second questions:
I understand the frame counting (instead of frame size) so sending 55 slots/channels is only a bad thing if the number of slots/channels you are expecting is more than 5 (since 55-50 = 5). So I guess if this is the case I was confused because there was no reference to the internal arduino code value of number of channels expected (#define NUMBER_OF_CHANNELS 8)
September 21st, 2009 at 10:08 pm
Thanks for the catch Danjel. Originally, the number of addresses to receive could not be easily changed from 8. I’ve tightened up the language to reflect the new capability.
Also, I’ve removed the “I’m not too sure this is actually going to work in the real world” disclaimer, since it appears that it does :)
September 28th, 2009 at 1:28 pm
Under Arduino16 I get this error:
/var/folders/-C/-CrtojkWGECwwQB8ALrEbE+++TI/-Tmp-/build5196551365637218582.tmp/core.a(HardwareSerial.cpp.o): In function `__vector_18′:
/Applications/arduino-0016/hardware/cores/arduino/HardwareSerial.cpp:95: multiple definition of `__vector_18′
o:/var/folders/-C/-CrtojkWGECwwQB8ALrEbE+++TI/-Tmp-/build5196551365637218582.tmp/Temporary_9909_1512.cpp:174: first defined here
Couldn’t determine program size: hardware/tools/avr/bin/avr-size: ‘/var/folders/-C/-CrtojkWGECwwQB8ALrEbE+++TI/-Tmp-/build5196551365637218582.tmp/receiver_rev13.hex’: No such file
September 28th, 2009 at 1:44 pm
Without replacing HardwareSerial.cpp in Arduino 17 I get the following error:
/var/folders/-C/-CrtojkWGECwwQB8ALrEbE+++TI/-Tmp-/build2796911162286465517.tmp/core.a(HardwareSerial.cpp.o): In function `__vector_18′:
/Applications/Arduino.app/Contents/Resources/Java/hardware/cores/arduino/HardwareSerial.cpp:95: multiple definition of `__vector_18′
o:/var/folders/-C/-CrtojkWGECwwQB8ALrEbE+++TI/-Tmp-/build2796911162286465517.tmp/receiver_rev13.cpp:174: first defined here
o to 132 in /var/folders/-C/-CrtojkWGECwwQB8ALrEbE+++TI/-Tmp-/build2796911162286465517.tmp/core.a(HardwareSerial.cpp.o)
It seems like the problems keep coming back to whatever _vector_18 is ?!?
September 28th, 2009 at 1:59 pm
Hey Danjel,
So did you replace the HardwareSerial.cpp in the Arduino install directory with the one from the download, as described in page 2 of this post?
__vector_18 is the ISR for the serial input. It executes every time the serial buffer receives a byte. But, my code also uses that ISR, so that’s where your conflict is coming from.
If you’re still having problems after replacing the HardwareSerial.cpp file post back.
September 28th, 2009 at 3:05 pm
I should mention I am on a MAC running OS X leopard (if that makes a difference)… I have been trying every version of Arduino and keep getting an error related to _vector_18 …. it is driving me crazy! I need to get this code working asap for a demo :S
September 28th, 2009 at 3:26 pm
ok another update. Went onto a PC and it compiles fine. This means there is some issue with OSX version of Arduino and this code….
September 28th, 2009 at 7:34 pm
Hey Max,
I followed your instructions exactly from the beginning (replacing the HardwareSerial.cpp file). I did the same for the various versions of Arduino I tried (Arduino 11, 14, 16 and 17) but had the same issue with all of them on OSX.
On my PC it compiled the first time (using Arduino 16).
However I have a new problem:
I am using a Lanbox LCE as my DMX interface. I have tested it with a DMX rgblight set to start address 1 and can control all three colors.
With the circuit built (using a MAX485 chip) and two leds for status (connected to pins 5 and 6 so that the “action” function illuminates them based on the dmx value).
So far it is not working. :( Since the baudrate is set to 250000 I can’t monitor the status messages using the Arduino monitor (incompatible baudrate)… maybe there is another way?
Any tips for testing the functionality? Anything that Lanbox DMX format may cause incompatibility?
September 28th, 2009 at 10:38 pm
ok so now I have something working! But not exactly as expected….
Even though I am leaving the arduino dmx address as ’1′ it seems that sending a full set of DMX values it stores value 1 in location dmxvalue[2] in the arduino array. Not sure where this offset is occurring.
September 28th, 2009 at 11:52 pm
and now it has stopped working argh! I am not able to reproduce the same functionality again… it very difficult not to be able to see what the arduino is actually receiving (and only using two leds to monitor particular dmx values). Maybe I need to hook up an LCD screen or something?? So frustrating!
September 29th, 2009 at 2:19 pm
Hi Danjel,
As a general troubleshooting tip, I would download the free version of 232 Analyzer, which can receive 256K baud rates (close enough to 250K), unlike the Arduino IDE. That will allow you to test things over serial.
Next, I would modify the USART_RX_vect ISR routine in my code so that as it receives a DMX value, it immediately sends that value back out over the serial. You may need to access the Serial I/O buffer UDR0 directly to get it fast enough that you don’t miss values.
Then as the Arduino receives it’s 512 values, you’ll see them pop up in 232 Analyzer. Further troubleshooting should be easier at that point. Good Luck.
October 16th, 2009 at 1:24 pm
Generally this is working but I still have
some more issues:
-I see the note in your code where you add +3 to the dmx address but this means I have to take this into account when sending values from another DMX system since this creates an offset for the channels.
-When I manually set an address of anything greater than 255 I am unable to store values properly. e.g. if I set an address of 298 (which is actually 301 because of the +3 offset) and then I send a value for channel 1 from a desk it gets read as channel 2 by the receiver! (instead of 302) so somehow that 300 is being subtracted?
I really don’t understand how this is possible…
When I set the address below 255 I have no problems.
October 16th, 2009 at 2:21 pm
further tests show that the maximum channel it will deal with is 300 (i.e. if any of my values are between channels 1-300 it is ok, anything above does not work)…. seems very odd and I can’t find the bug in code. Arduino running out of memory maybe?
October 18th, 2009 at 7:10 pm
Just getting started with my own DMX project and found your site. I expect I’ll be in contact with you quite a bit in the future as I’m new to building circuits and have never worked with an Arduino before, but so far learning a lot from your experience. From a DMX hardware standpoint, I notice you mention a 150Ω resistor for DMX termination. I have no idea how important this is to your project, but DMX transmitted with an RS485 wants to see an infinite 120Ω resistance. When building hardware DMX terminators for the lighting company I work at, all we do is solder a 120Ω resistor between pins 2 and 3.
October 18th, 2009 at 9:11 pm
Hi Matt,
Best of luck with your project, post back if you get stuck. With respect to the 120ohm resistor, anything the same order of magnitude will work fine for a termination resistor.
Danjel,
On the channel 300 limit, there was a bug in early versions of the code that limited the maximum channel to 255, but I thought I had dealt with that. I will test it when I get a chance and see if I can reproduce it on my end. But 300 is an strange number for a maximum address, from a programming standpoint.
The +3 channel offset is to set the dimmer numbers of your control desk *equal* to the address of the receiver– setting the receiver to begin at address 5 should allow you to control it with dimmer 5 at your lighting desk. If you are seeing a consistent offset, e.g. dimmer 5 is received as address 8, there may be some quirk to your controller DMX configuration and you would adjust the code accordingly.
November 13th, 2009 at 12:21 pm
Hi Danjel,
I have tested the receiver extensively at higher addresses using IDE 0017 and it worked fine for me… unless anyone else is seeing this issue I’m going to say it’s a quirk of your controller. Sorry.
December 1st, 2009 at 2:37 pm
Stumbled upon your blog and love the work you’ve done. I’m interested in the idea of adding a dip switch and a 4 segment led to program and display dmx addresses. Using shift registers it would only cost 6 pins I believe, leaving 10 free for whatever. I will happily share any success I have on here. My end goal is an overglorified A/B switch that’s controlled via dmx.
December 1st, 2009 at 5:36 pm
Hey Ben,
Sounds like a great project. Let me know how you make out.
December 2nd, 2009 at 10:45 pm
Hey Max,
Sorry I couldn’t get around to this earlier. Just some photos of my boards (and the show that they are used in) that I adapted your code for earlier this year…
http://www.flickr.com/photos/flashtui/sets/72157622799873947/
All the best,
Rob.
December 7th, 2009 at 10:40 am
Thanks a lot, this takes off a lot of headache from what I was trying to do.
@Robert Larsen
That looks like an amazing production. Very innovative idea!