Receive DMX-512 with an Arduino

Part I: ProloguePart II: Instructions

arduino

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:

  1. Part I: Prologue
  2. Part II: Instructions
  3. Part III: Known Limitations, Notes, and the Code
  4. View All

Pages: 1 2 3


77 Responses to “Receive DMX-512 with an Arduino”

  • Michael Says:

    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!

  • Max Says:

    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.

  • Michael Says:

    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.

  • Tobias Says:

    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!

  • Max Says:

    Thanks for letting me know that it works with the 328, I’ve updated the page accordingly.

  • Tobias Says:

    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?

  • Max Says:

    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.

  • Elliot Says:

    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.

  • Max Says:

    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?

  • Elliot Says:

    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.

  • Tobbe Says:

    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:)

  • Max Says:

    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.

  • Tom Says:

    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?

  • Max Says:

    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.

  • Max Says:

    @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.

  • Elliot Says:

    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.

  • genehed Says:

    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

  • Paul Says:

    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

  • Max Says:

    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:

      if(dmxcurrent > dmxaddress) {         //check if the current address is the one we want.
        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***

  • Tom Says:

    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?

  • Tom Says:

    Oops. Got it. Opened the file incorrectly.

  • Robert Says:

    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!

  • Max Says:

    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.

  • Paul Says:

    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

  • Paul Says:

    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

  • Max Says:

    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.

  • Paul Says:

    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

  • Robert Larsen Says:

    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)

  • Max Says:

    Robert,
    That sounds awesome! Definitely if you get some photos and information posted up, post up a link.

  • Danjel Says:

    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.

  • Danjel Says:

    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)

  • Max Says:

    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 :)

  • Danjel Says:

    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

  • Danjel Says:

    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 ?!?

  • Max Says:

    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.

  • Danjel Says:

    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

  • Danjel Says:

    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….

  • Danjel Says:

    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?

  • Danjel Says:

    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.

  • Danjel Says:

    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!

  • Max Says:

    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.

  • Danjel Says:

    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.

  • Danjel Says:

    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?

  • Matt Says:

    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.

  • Max Says:

    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.

  • Max Says:

    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.

  • Ben Says:

    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.

  • Max Says:

    Hey Ben,
    Sounds like a great project. Let me know how you make out.

  • Robert Larsen Says:

    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.

  • Joe Says:

    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!

Leave a Reply