Receive DMX-512 with an Arduino

By Max
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.


You Will Need:

  • A copy of the latest release and the modified wiring_serial.c or HardwareSerial.cpp file.  See the instructions for what to do with the files.   *update*:  This post is now more than four years old.  Some people have reported success using the 1.0+ software, but I’d start with the older rev 0023 of the IDE and go from there once you get it working.  Download it here, under Previous IDE Versions.
  • An Arduino with an Atmega168 or Atmega368 processor.  Because of the timing-sensitive nature of DMX-512, some of the code had to be optimized by referring to particular registers on the Atmega168/368.  The code can easily be adapted to other processors, though.
  • An RS-485 to Serial Transceiver, such as the MAX485 or the TI 75176.  They’ll run you about $1, the MAX485 is a little more but it apparently has some kind of fancy overvoltage protection, so I used that.
  • A breadboard and some wires, also a 150Ω resistor to terminate the DMX line if necessary.

Instructions:

DMX uses a twisted pair of signal wires with opposite polarity to transmit information per RS-485.  However, your Arduino needs a serial signal, with a pin brought high for one and low for zero.  To convert between these protocols, you’ll need to wire up your MAX485 or 75176 in the following way:

Schematic Rev2

Here are some pictures which may help you:

The prototype from the left.

The prototype from the left.

 

The prototype from the right.

The prototype from the right.

Note 1: Starting with Rev11 of the software, I adjusted the pin layout slightly for better routing on the DMX I/O Shield.  The two gray wires in the above photos that are plugged into pins 3 and 4 should be plugged into pins 2 and 3, respectively, if you’re using the latest software.

Note 2: I’m using the Arduino protoshield here, which I highly recommend, they’re handy.  If you don’t have one, the pin layout is the same as if you ran directly into the Arduino board.

Note 3: if you want to retain the use of pins 1, 4, and 5, at the cost of being able to transmit as well as receive, you can connect the corresponding pins on the MAX485 to the ground on the Arduino board.  I’ve done it this way for possible future RDM functionality ;).

One Dumb Hack is necessary: rename your currently installed HardwareSerial.cpp file to HardwareSerial.cpp.backup, and put the modified HardwareSerial.cpp from this site in the same directory.  It’s located in:

(Arduino Install Directory)/hardware/cores/arduino/

The reason for this is that the Arduino software defines the USART_RX_vect serial reception interrupt, and includes it in your compiled code, even if you don’t use any serial functions.  This will, I hope, be fixed in a future release, but until it is this is the work-around.  You can read more about the issue here.  Once you’ve uploaded the sketch and it’s working to your satisfaction, you can undo this part to regain your normal serial library functionality.

Finally, fire up the Arduino software, and put what you want the Arduino to do with the received values in the action() loop. The received values are stored in the dmxvalue[] array. The downloaded sketch contains example code to print out each of the values to the serial port, and set PWM pins 5 and 6 to the first and second value in the array, respectively, but this can of course be changed to anything you want.

That’s it, let me know how it works for you!  You may want to keep reading for the Known Limitations, etc.

Known Limitations:

  • Atmega168 and Atmega328 based processors only (you will have to rename the registers and interrupt vectors if you want to use it for another processor).
  • I personally only test the software with a USB-DMX Pro controlled via Lightfactory, since that’s what I have laying around.  But it handles a variety of frame rates, break lengths, etc. just fine.  The consensus seems to be that it works just fine with other controllers.
  • Because I needed access to exact timing, I had to use the Timer2 functionality, so pins 3 and 11 cannot be used for PWM.  The Timer2 issue is also the most frequent cause of incompatibility problems when using community-developed libraries.  See the comments for remedies if you’re affected by this.
  • Will not detect bad addressing.  For example, with it set to receive the default 8 channels, “dmxaddress=510;” would give you two good channels and six channels of junk.  Or “dmxaddress=50;” when only 55 addresses are sent by the controller.  Edwin Dolby at Laser Productions had an elegant idea to address this, namely that you could use the constrain function to map out of bounds values to the correct 0-511 range.  However, I have decided not to implement this by default, as without some kind of numerical readout I think the values should just be set to what you set them.  But, easy to implement if you decide you want it!
  • When addressing, sometimes when you hit the 0 or 1 switch it doesn’t take.  I’ve programmed the LED to turn off briefly if the bit was successfully entered, so if you don’t see it go off, you’ll have to hit the switch again until it takes.  I don’t know why it’s doing this, if you have some time to wade through the logic let me know why and I’ll update the code. Ron Barber was good enough to figure out why it was doing this and show me how to fix it.  Thanks Ron!

Future Development:

  • Add a dip switch and code to allow in-the-field addressing.
  • Reduce the number of Atmega168-specific functions to improve code portability.
  • Design a shield for better durability, signal pass-through, termination, etc. Check here!
  • Add frame error and data overrun error handling routines.
  • Add RDM functionality.
  • Develop a separate DMX monitor application in Processing or the like.
  • Timer2 is currently used for break detection.  I’m thinking that I could re-write the code so that it records the configuration register values currently existing, sets them to what it needs, and then puts them back the way it found them.  That way, you would be able to use your other libraries that rely on this timer (of which there are apparently a lot), as long as they didn’t need to run during break detection.  Advantages and disadvantages to this, obviously.  But the #1 issue seems to be ‘doesn’t work with library X’, so for most of you this would be an improvement.  Or a polling loop might even be good enough, and then we could forgo the timer business altogether.
  • The documentation for this project has sprawled out onto so many posts and comments that it’s difficult for even me to find, I’m looking at trying to consolidate everything into a minisite or something.  I’d also like to add a big ‘how it works’ section, which would help those of you trying to customize the code.

Release History and Notes:

  • 9 October 2010: Rev15.
    • Tested and working with IDE version 0021.  I have not tested it with the new Arduino Uno hardware, but can’t think of any reason why it wouldn’t work.  If you have any success or otherwise with the new hardware, drop a line in the comments.
    • A bug has been fixed in the addressing routine caused by button debounce, and the addressing routine logic has been simplified and made clearer.  Big thanks to Ron Barber for pointing out the source of the bug and contributing new code.
    • There is now a check when the previously stored address is read from EEPROM to ensure that it is in the valid range of 1-511, to prevent a bad value being read in from uninitialized EEPROM.
    • Fixed a potential bug in the break detection routine that could cause the read values to be off by one in cases where the break from the microcontroller was exactly 88uS.
  • 23 June 2010: Rev14
    • Tested and working with IDE version 0018.
  • 9 July 2009: Rev13
    • Tested and working with IDE version 0016.
    • The number of channels to receive is now easily user-configurable.
    • Replaced static variables with #define statements for memory optimization (+48 bytes, woot!).
  • 12 May 2009: Rev12
    • In-the-field addressing 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 (which is why I didn’t use the more conventional dip switch setup).
    • Some of the variables were localized, since the sketch is now getting pretty complex.
  • 27 April 2009: Rev11
    • Adjusted pin layout for better routing on the DMX I/O Shield.  Pins 3 and 4 in Rev10 are now pins 2 and 3, respectively.
  • 1 April 2009: Rev10
    • Cleaned up and improved code commenting.
    • Adjusted HardwareSerial.cpp (included) so the code will compile on Arduino software release 0015.  If you’re still using 0014 or 0013, you’ll replace wiring_serial.c instead (also included).
    • Replaced manual register configuration of the USART with the Arduino function serial.Begin(250000), which apparently works just as well and reduces the number of Atmega168-specific register calls considerably.
    • Moved the action() loop (what you want the Arduino to do with the received values) to its own tab, to make the code easier to use.
  • 20 March 2009: Rev09
    • First release
  • Rev00-Rev08: Pre-release betas.

The Code: Here is the .pde sketch file.  It may be of general interest as well if you’re trying to write interrupt-based programs for the Arduino.  In the download, there is also a tab for the user code and another for in-the-field addressing module.

[cc]

/***********************************************************
* DMX-512 Reception                                        *
* Developed by Max Pierson                                 *
* Version Rev15 9 Oct 2010                                 *
* Released under the WTFPL license, although I would       *
* appreciate Attribution and Share-Alike                   *
* See blog.wingedvictorydesign.com for the latest version. *
************************************************************/

/******************************* Addressing variable declarations *****************************/

#include <EEPROM.h>
#define NUMBER_OF_CHANNELS 8
//the number of channels we want to receive (8 by default).

#define SWITCH_PIN_0 11 //the pin number of our "0" switch
#define SWITCH_PIN_1 12 //the pin number of our "1" switch
unsigned int dmxaddress = 1;
/* The dmx address we will be listening to.  The value of this will be set in the Addressing()
*  function and read from EEPROM addresses 510 and 511.

/******************************* MAX485 variable declarations *****************************/

#define RECEIVER_OUTPUT_ENABLE 2
/* receiver output enable (pin2) on the max485.
*  will be left low to set the max485 to receive data. */

#define DRIVER_OUTPUT_ENABLE 3
/* driver output enable (pin3) on the max485.
*  will left low to disable driver output. */

#define RX_PIN 0   // serial receive pin, which takes the incoming data from the MAX485.
#define TX_PIN 1   // serial transmission pin

/******************************* DMX variable declarations ********************************/

volatile byte i = 0;              //dummy variable for dmxvalue[]
volatile byte dmxreceived = 0;    //the latest received value
volatile unsigned int dmxcurrent = 0;     //counter variable that is incremented every time we receive a value.
volatile byte dmxvalue[NUMBER_OF_CHANNELS];
/*  stores the DMX values we're interested in using--
 *  keep in mind that this is 0-indexed. */
volatile boolean dmxnewvalue = false;
/*  set to 1 when updated dmx values are received
 *  (even if they are the same values as the last time). */

/******************************* Timer2 variable declarations *****************************/

volatile byte zerocounter = 0;
/* a counter to hold the number of zeros received in sequence on the serial receive pin.
*  When we've received a minimum of 11 zeros in a row, we must be in a break.  */

void setup() {

  /******************************* Max485 configuration ***********************************/

  pinMode(RECEIVER_OUTPUT_ENABLE, OUTPUT);
  pinMode(DRIVER_OUTPUT_ENABLE, OUTPUT);
  digitalWrite(RECEIVER_OUTPUT_ENABLE, LOW);
  digitalWrite(DRIVER_OUTPUT_ENABLE, LOW);    //sets pins 3 and 4 to low to enable reciever mode on the MAX485.

  pinMode(RX_PIN, INPUT);  //sets serial pin to receive data

  /******************************* Addressing subroutine *********************************/

  pinMode(SWITCH_PIN_0, INPUT);           //sets pin for '0' switch to input
  digitalWrite(SWITCH_PIN_0, HIGH);       //turns on the internal pull-up resistor for '0' switch pin
  pinMode(SWITCH_PIN_1, INPUT);           //sets pin for '1' switch to input
  digitalWrite(SWITCH_PIN_1, HIGH);       //turns on the internal pull-up resistor for '1' switch pin

  /* Call the addressing subroutine.  Three behaviors are possible:
  *  1. Neither switch is pressed, in which case the value previously stored in EEPROM
  *  510 and 511 is recalled,
  *  2. Both switches are pressed, in which case the address is reset to 1.
  *  3. Either switch is pressed (but not both), in which case the new address may
  *  be entered by the user.
  */
  //set this equal to a constant value if you just want to hardcode the address.
  dmxaddress = Addressing();

  /******************************* USART configuration ************************************/

  Serial.begin(250000);
  /* Each bit is 4uS long, hence 250Kbps baud rate */

  cli(); //disable interrupts while we're setting bits in registers

  bitClear(UCSR0B, RXCIE0);  //disable USART reception interrupt

  /******************************* Timer2 configuration ***********************************/

  //NOTE:  this will disable PWM on pins 3 and 11.
  bitClear(TCCR2A, COM2A1);
  bitClear(TCCR2A, COM2A0); //disable compare match output A mode
  bitClear(TCCR2A, COM2B1);
  bitClear(TCCR2A, COM2B0); //disable compare match output B mode
  bitSet(TCCR2A, WGM21);
  bitClear(TCCR2A, WGM20);  //set mode 2, CTC.  TOP will be set by OCRA.

  bitClear(TCCR2B, FOC2A);
  bitClear(TCCR2B, FOC2B);  //disable Force Output Compare A and B.
  bitClear(TCCR2B, WGM22);  //set mode 2, CTC.  TOP will be set by OCRA.
  bitClear(TCCR2B, CS22);
  bitClear(TCCR2B, CS21);
  bitSet(TCCR2B, CS20);   // no prescaler means the clock will increment every 62.5ns (assuming 16Mhz clock speed).

  OCR2A = 64;
  /* Set output compare register to 64, so that the Output Compare Interrupt will fire
  *  every 4uS.  */

  bitClear(TIMSK2, OCIE2B);  //Disable Timer/Counter2 Output Compare Match B Interrupt
  bitSet(TIMSK2, OCIE2A);    //Enable Timer/Counter2 Output Compare Match A Interrupt
  bitClear(TIMSK2, TOIE2);   //Disable Timer/Counter2 Overflow Interrupt Enable          

  sei();                     //reenable interrupts now that timer2 has been configured. 

}  //end setup()

void loop()  {
  // the processor gets parked here while the ISRs are doing their thing. 

  if (dmxnewvalue == 1) {    //when a new set of values are received, jump to action loop...
    action();
    dmxnewvalue = 0;
    dmxcurrent = 0;
    zerocounter = 0;      //and then when finished reset variables and enable timer2 interrupt
    i = 0;
    bitSet(TIMSK2, OCIE2A);    //Enable Timer/Counter2 Output Compare Match A Interrupt
  }
} //end loop()

//Timer2 compare match interrupt vector handler
ISR(TIMER2_COMPA_vect) {
  if (bitRead(PIND, PIND0)) {  // if a one is detected, we're not in a break, reset zerocounter.
    zerocounter = 0;
    }
  else {
    zerocounter++;             // increment zerocounter if a zero is received.
    if (zerocounter == 20)     // if 20 0's are received in a row (80uS break)
      {
      bitClear(TIMSK2, OCIE2A);    //disable this interrupt and enable reception interrupt now that we're in a break.
      bitSet(UCSR0B, RXCIE0);
      }
  }
} //end Timer2 ISR

ISR(USART_RX_vect){
  dmxreceived = UDR0;
  /* The receive buffer (UDR0) must be read during the reception ISR, or the ISR will just
  *  execute again immediately upon exiting. */

  dmxcurrent++;                        //increment address counter

  if(dmxcurrent > dmxaddress) {         //check if the current address is the one we want.
    dmxvalue[i] = dmxreceived;
    i++;
    if(i == NUMBER_OF_CHANNELS) {
      bitClear(UCSR0B, RXCIE0);
      dmxnewvalue = 1;                        //set newvalue, so that the main code can be executed.
    }
  }
} // end ISR
[/cc]

P.S.: There’s a really good comment thread for this post and it may answer your question, especially if it is of the form ‘will it work with this other Arduino library?’ So take a gander at that before writing a comment, if you would.


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

  • dmtry Says:

    Hi. Very interesting project thanks! can be used in various applications. I would like to add encoder and managed using several DMX channel setting motor speed and position

  • Daniel Kendell Says:

    Firstly, thanks and well done for cracking this, I’ve wanted to be able to add DMX to my lighting projects for a long time now but sussing it out myself is currently beyond my ability. I’m totally building one of these! :D

    Something I’m unsure about though, Rapid Electronics don’t have a “MAX485″, but they do have “MAX485CPA+” and “MAX485CSA+”. Are either of those suitable for this? I don’t know what the difference is.

    Thanks again for publishing your work,
    Dan

  • Max Says:

    Hi Daniel,
    The difference is the package. The CPA is a through hole package, whereas the CSA is the surface mount. You’ll want the CPA. Best of luck with your project!

  • DDD Says:

    Hi there,

    Any update on the code? Will there be a future version that does not need to modify the Arduino lib?

  • Ed Says:

    Incredible work!
    I am having trouble getting things to work for me.
    I am using Arduino 0017.
    Do you have any more recent builds of your project?
    Thanks so much!

  • Max Says:

    Hi All,
    I will put together an update for IDE 0017 or verify that the current version works. If you don’t want to wait, you could just take a look at what I’ve done to the hardware.cpp file and do the same to the IDE 0017 version.

    Note that the hack to the Arduino library files is necessary due to code on the Arduino side, not my software, so I have no control over when/if this will be fixed.

  • BlueFusion Says:

    For everyone else using 0017:
    The mod you need to make to the hardwareserial.cpp is this:

    Change line 115 from:
    uint8_t rxen, uint8_t txen, uint8_t rxcie, uint8_t udre)

    to:

    uint8_t rxen, uint8_t txen, uint8_t rxcie, uint8_t udre, uint8_t u2x)

    and it should compile.

  • David Says:

    The fix given by BlueFusion does work. One note for a little more clarity: you should do this as an additional modification in the already modified hardwareserial.cpp file (the one included in the download from this site).

    Thanks anyway for the work already done on this, I will be constructing a centerpiece for a disco with this which will have 8 pinspots, mirrors and servo’s! (and maybe we will abuse it every now and then with our band :))

  • James Corbett Says:

    Can your modifications send and receive or not?

  • Gregory Haley Says:

    Doing research.
    Looking for a solution.
    I Need to manipulate 2000+ 3w-5w LED’s for my art project.
    http://www.TheBallofLight.com
    Your project seems like it would allow for it. Each of the 2000 lights would need to be able to be driven by a central DMX console. As I read each of your units can manipulate 512 individual lights. That sounds astounding. That would mean I would need to make only 4 of these units. That would also mean 4 Universes I believe. I think I would also need 4 optical splitters to lead to these units. Then back to the Console.

    I have many questions on this topic and am just getting started on a two and half year project.

    If you are interested at all interested I would love to tell all and pick your brain some you seem extremely knowledgeable on this subject.

  • Max Says:

    I actually would not use DMX for this application– I would try to get your LEDs to take a video input. That way, you’ll be able to program it from a VJ software (such as Arkaos, there are dozens) rather than a theatrical lighting console, which will give you a more intuitive and flexible programming paradigm. Also, I would try to source your LED drivers from commercially available hardware such as the driver boards for LED billboards. By the time you buy the components for 2,000 DIY drivers, you won’t be saving any money, and the soldering involved gives me the creeping horrors. I mean, why spend all your time reinventing the wheel, when you could be focusing on the art? My $.02

  • Michael O'Keefe Says:

    This is amazing! I’m trying to build a DMX-controlled moving light, and your code should be very helpful! However, in Arduino 0018, the project won’t compile: it says “(location)/HardwareSerial.cpp:185: error: no matching function for call to ‘HardwareSerial::HardwareSerial((ring_buffer*, volatile uint8_t*, volatile uint8_t*, volatile uint8_t*, volatile uint8_t*, volatile uint8_t*, int, int, int, int)’”. I’ve tried the modification suggested by BlueFusion, which fixed another compiler error but not this one. Any suggestions for an Arduino 0018 user?

  • lampmaker Says:

    I’m having a hard time getting this code to work on my arduino mega, IDE 0018.
    Additional problem is that I want it to connect to serial3, so I have serial available to talk to the PC.
    With some simple code, I can detect (and copy) data received on serial3 to serial0, so all electronics work nicely. I just can’t get this DMX program to work, and I have no clue what all the timer configurations etc, hacks to harwareserial, etc mean…

  • Kieren Says:

    lampmaker, how did you get it to work on a mega? I have tried for ages bu no matter what i do all i get is errors

  • Grody Says:

    I am likewise unable to compile this on 0018. Game over.

  • Dave Cole Says:

    Hi: I want to build a DMX-driven 8*8 LED array and was hoping to use your code and a TLC5940 (using the http://students.washington.edu/acleone/codes/tlc5940arduino/html_r8/ library) but your library and their library use the same pins (specifically 3 and 11). Is there a way to move those functions to other pins?

    Also, I’m trying to compile under IDE 0018 and even with the Bluefusion mods to hardwareserial.cpp it comes up with errors (it’s still complaining about that line 115).

    Thanks.

    \dmc

  • Max Says:

    Hi Dave,
    That might be do-able. You’ll need to move either my DMX code or the tlc5940 clock timer over to timer1. See the discussion in earlier comments about timers and trying to output two data protocols at the same time. As I understand it, the TLC5940 has an asynchronous serial input, so it doesn’t need constant updates, all the time, right?

  • Dave Cole Says:

    Max: Thanks for your thoughts.

    In my application I won’t be needing to do RDM, so I won’t need the timer on Pin 3 … it’s just the Pin 11 timer I’ll need to move (and the guy who wrote the tlc5490 library isn’t interested in telling me how to move his pins around).

    I’m still learning about the 5940, so I can’t say yet whether it’s asynch or synch … when I get more eddiecated on the topic, I’ll be back.

    Also, I tried downloading IDE 0017 and moving in the new HardwareSerial.cpp with Bluefusion’s mods and the sketch still won’t compile …

    Thanks again.

    \dmc

  • Grody Says:

    I think I might’ve figured out this whole 0018 issue. All I did was comment out the section of code in the IDE-native HardwareSerial.cpp that was commented out in the version provided here, and now it at least appears able to compile. I haven’t tried getting it to run on an Arduino yet.

  • Dave Cole Says:

    Grody: Just ran across your theory … tried it out and got this:

    >>error: no matching function for call to ‘HardwareSerial::HardwareSerial(ring_buffer*, volatile uint8_t*, volatile uint8_t*, volatile uint8_t*, volatile uint8_t*, volatile uint8_t*, int, int, int, int)’

    Thanks for your thoughts, though.

    \dmc

  • aususer Says:

    Gregory Haley: I say the suggested “feeding video” into that 2000 nodes will be “interesting” (say the least).
    But you might want to look at ARTNET or E1.31 for your driving protocols (rather than basic DMX512).
    Your backend/desk will need to support it of course, but it will get over you 512/universe limitations.
    There are examples you can have a look at where they are using > 3000 lights in an awesome christmastree driven by madrix: http://www.response-box.com/rgblights/ (be prepared to sell your kids to get one, and own a nuclear powerstation to light it of course ;).
    good luck!

  • Jay Says:

    I’ve got an interesting anomaly someone may have encountered — the outputted DMX values don’t map correctly from my input values. Here’s how they map:
    0 –> 254

    64 –> 2
    65 –> 254

    128 –> 2
    129 –> 254

    191 –> 2
    192 –> 254

    255 –> 2

    I’m sure it has something to do with my home-made el cheapo FTDI-based DMX transmitter, but my professional DMX lights don’t seem to mind. Any ideas where I should look to see if I can adapt the code? I’m running Arduino 0018, using the standard action() loop.

    Great software! Thanks!

  • Dave Cole Says:

    Success!

    In recent weeks I hadn’t bothered to read the text of the blog, just the comments and so I didn’t notice that Rev14 was out. That has solved all my problems.

    I am successfully processing a DMX signal (which is from a USB converter designed by a friend of mine that is still in beta).

    In my first few minutes of fiddling with the action loop, I found the need to make the output of the serial monitor more meaningful and neater. You can see my rewritten action loop and its output at

    http://www.dmcole.net/wp-content/uploads/2010/08/Arduino-DMX-receiver-Action.png

    Thanks so much Max. Now, on to the not insignificant process of making the serial chips work …

    \dmc

  • Siliconsoul Says:

    Hi,

    I came across this the other day and would like to ask you if you still have a copy of the code lying around somewhere although it shouldn’t be too difficult to implement an LCD and a rotary encoder.

    Would be great if you can share the code with us.

    Thanks.

    Sincerely,
    Siliconsoul.

  • Seb Says:

    Hi,

    Thanks for posting all this so beautifully. I am a LD from NY and have been using Arduino for some small scale embedded systems projects / mock ups. My next quest is to try and make a small, maybe 1′, LED strip light that is driven by an arduino and controlled by a dmx console. I am also new to code writing and my question is… once you get the Arduino to receive DMX what is the best way to map the dmx channel to a digital output?

    -seb

  • Max Says:

    Hi Seb,
    If your power needs are pretty minimal, you could just drive the LEDs directly via the PWM outputs. Otherwise, you’ll need the Arduino to output whatever the control protocol is for your LED drivers– probably 0-10V. Although, it doesn’t seem to me that you save any time/money here by building your own strip– there are RGB strips available at dirt cheap prices, e.g.:
    http://www.enttec.com/?main_menu=Products&pn=72001&show=description
    (or even Ikea!)

    The very best and highest performing RGB strips will run you in the $250/ft range, e.g.:
    http://www.lumenpulse.com/en/products.php?model=324
    But you won’t match the performance/durability of these commercial products unless you have the tooling and engineering skills to make good optical components (miniature lenses and reflectors) and thermally manage the diodes so they don’t overheat and burn out.

    I don’t want to sound discouraging because it sounds like a really fun project, but it’s a way to go if you decide you’d like to outsource some of it.

  • Erick Nava Aldana Says:

    New to Arduino and DMX, but recently buyed shield parts from CuteDigi, three questions:

    I have 3 pin led bars, its compatible?
    How to connect 3 to 5 and viceversa?
    Need to make any change on code(for 3 pin compatibility and/or atmega 328 Duemilanove)?

    Mani thanks, nice project!!!

  • Motoi Ishibashi Says:

    Thanks for the great project!

    I have tested with arduino uno.
    But I didn’t work.
    Exactly same curcuit worked fine with arduino duemilanove ATmega328.
    IDE version is 0021.

  • Harald Bildsøe Says:

    nice work..

    hope some one will get it worikng with uno..

  • ewanuno Says:

    got it working on arduino21!

    it took me a bit of head scratching trying to figure out that if you only use rx on the driver chip then you have to disable tx by conecting tx_enable to ground.

    i built a servo controller for a video projector shutter, which had it’s debut last week.

    and i modifyed it to use dip switch adressing, because thats what i’m used to and didn’t need the extra pins.
    but i only had an 8 pin dipswitch so i’m stuck at adresses up to 128.

    tested with three diferent dmx desks, all working fine.

    i can share the code if anybody likes, but it’s really nothing you couldn’t do on a sunday afternoon.

    ewan

  • Tommy Says:

    Ewanamo!
    Please share the code!
    tommy.ptrson(aa)gmail.com

  • Danny Says:

    ewanamo I would like to get your code
    here is my e-mail:
    dani.feldman@gmail.com

    thanks in advance

  • Max Says:

    Thanks for doing this. I thought about getting into Arduino by making a DMX based project myself. Are you still maintaining the code?
    Did you get it working with the Arduino Uno?

    What other cool Arduino-DMX512 projects have you done since this post?

  • Shaun Says:

    What is the minimum number of pins this will use on the Arduino? Can simply use only RO and hardwire the other signals to highs/lows?

  • Siliconsoul Says:

    Thanks for the great code. Unfortunately I am having trouble with the last addresses. To be precise, I’m using 3 channels and if I set the addresses to 509 and above, the output oscillates. I’m using pins 5, 6 and 9 as PWM outputs and the LEDs attached flicker. This doesn’t happen on other addresses though.
    Thanks in advanced.

  • Max Says:

    I have no idea at all why PWM would be flaky when using the last addresses. The only thing I can suggest is that you insert an instruction to toggle a pin on and off in various places in the code and put an oscilloscope on the PWM pin and compare the two outputs to see if you can find the instruction that is causing the problem. Sorry I don’t have a better answer, let me know what you find out.

  • Quan Says:

    hey I got your code working with my Chauvet Obey10 DMX controller — only issue i’m having is the first 2 bytes of dmxvalue seem to be garbage… by setting dmxaddress = 1, channel 1 data seems to come from dmxvalue[3] instead of dmxvalue[0]. do you know the cause or reason for reading 2 extra bytes and putting them in dmxvalue[0] and dmxvalue[1]?

    one suggestion in your example code is to formate the printed values to DEC, it was quite confusing at first because printing the byte to screen would give me messed up characters.

    thanks a million!

  • Quan Says:

    i also noticed you didn’t set the USBS0 bit in the UCSR0C — which sets stop bits to 2, will that make any difference?

  • Quan Says:

    I tweaked your code a little bit to get rid of the Timer2 interrupt. If you add the following code to your main loop() — essentially I’m using micros() to keep track of the break time, and just polling to see if the time has been reached. It frees up the Timer2 interrupt but I don’t know if adding more code to the main loop() or action() will cause the timing to be off:

    if(bitRead(PIND, PIND0)){
    lastMark = micros();
    }
    else if((micros()-lastMark) >= 88){
    bitSet(UCSR0B, RXCIE0);
    }

    let me know if this works for you.

  • Pepe Says:

    Thanks! Works like a charm! Have been tinkering about starting with electronics again and finally made that decision. Your help is very much appreciated!

    @Quan: I figured, the two offset bytes might be vendor specific start codes (see: http://tsp.plasa.org/tsp/working_groups/CP/DMXAlternateCodes.php )
    Or am I wrong and isn’t that the cause of our two-byte “problem” and do others not find this behaviour?

  • BubbaJoe Says:

    Can I use this to modify the dimmer on a NON DMX light?

  • jimbojones Says:

    Great project!!!
    I’m going to try to port it to an arduinoMega.
    I’ll let you know how it works out.
    (maybe you’d like to post the code?)

    Great job dude!

  • jimbojones Says:

    Hi there,

    I’m working on porting this one to the arduino MEGA, so far the timer works, but the uart ISR doesn’t. I have posted the code and further explanation at
    http://arduino.cc/forum/index.php/topic,50696.0.html

    Any help is welcome, I keep working on it
    All praise goes to Max for his great work!

  • Ioull Says:

    Thx for this very good job … I port it to arduinoMega 1280 changing following lines :
    if (bitRead(PIND, PIND0)) {
    became
    #if defined(__AVR_ATmega1280__)
    if (bitRead(PINE, PINE0)) {
    #else
    if (bitRead(PIND, PIND0)) {
    #endif

    and
    ISR(USART_RX_vect){
    became
    #if defined(__AVR_ATmega1280__)
    ISR(SIG_USART0_RECV){
    #else
    ISR(USART_RX_vect){
    #endif

    With this modification code is compatible both for Mega1280 and 328 !

    The other part of code I change was to move the 3 offset for dmx address … because I lost time to understand why my example does not work … In my case I have no button for address configuration … so I squeeze Adressing part of the project without checking what was in it … this offset had to be in the main program in setup part in order to avoid some confusing ;-)

    Thanks and sorry for my poor english …

  • Ioull Says:

    Sorry I forgot to say I’d to patch HardwareSerial.cpp commenting this part of code too :

    /******************* COMMENTED OUT FOR DMX RECEPTION ************
    SIGNAL(SIG_USART0_RECV)
    {
    unsigned char c = UDR0;
    store_char(c, &rx_buffer);
    }
    ******************** END COMMENT ***************/

  • Ioull Says:

    And the last comment … for the moment … Using timer2 with Mega1280 disable PWM on pins 9 and 10 instead of 3 and 11 for 328
    (from http://softsolder.com/2010/09/05/arduino-mega-1280-pwm-to-timer-assignment/)
    In order to get compatibility between 1280 and 328. perhaps it could be interressant to use timer1 instead of timer2 in 328 case. So in both case (328 and 1280) PWM will be disabled for pin 9 and 10.

  • Nuno Says:

    Good night,

    you could creat a library like DMXsimple for receiving DMX?

    i am having some troubles on using your actual program because my knowlege of C is Low.

    thanks

  • Dave Says:

    Do you need to un-plug the shield from the Arduino before uploading the sketch? I had mine plugged in and it won’t upload. Un-plugging it uploads fine. Looks like the MAX481 is preventing the sketch upload.

    Is there any way the sketch can be upload while the shield is plugged in?

  • Max Says:

    Hey Dave,
    You shouldn’t have to unplug the shield to upload a sketch. I would maybe try a different computer, or to check and make sure that the MAX485 pins 2 and 3 are held low– I’m thinking that if the computer USART thought that the Arduino was about to transmit something, it wouldn’t be able to upload the sketch, maybe? Or check it on another Arduino if you’ve got one.

    *edit*: If you are using a Uno, I don’t know, I don’t have one. I know it’s possible, though.

    Max

  • James Says:

    @Dave-
    I’m using an Arduino Uno, and I found I need a 10k pullup resistor on the MAX485 pin 2 (Receive Enable) or else I have to disconnect the MAX485 output from pin D0 (serial Rx) before uploading a new sketch. I think when the Arduino reboots, the D2 pin (which is connected to the MAX485 pin 2) defaults to input (high-impedance) and floats, so the MAX485 continues to hog the serial line and prevents the sketch upload. The pull-up resistor ensures that the MAX485 output is disabled until the sketch intentionally enables it.

    @Quan-
    I was seeing extra bytes at the beginning of the DMX frame as well, which threw off the DMX addresses. Turns out the UART needed to be flushed. I could tell because the extraneous values were actually the values of the DMX channels right after the ones I was reading. Change the following function:

    //Timer2 compare match interrupt vector handler
    ISR(TIMER2_COMPA_vect) {
    if (bitRead(PIND, PIND0)) { // if a one is detected, we’re not in a break, reset zerocounter.
    zerocounter = 0;
    }
    else {
    zerocounter++; // increment zerocounter if a zero is received.
    if (zerocounter == 20) // if 20 0′s are received in a row (80uS break)
    {
    // Flush UART
    volatile unsigned char dummy; //<–NEW CODE
    while ( UCSR0A & (1<<RXC0) ) dummy = UDR0; //<–NEW CODE

    bitClear(TIMSK2, OCIE2A); //disable this interrupt and enable reception interrupt now that we're in a break.
    bitSet(UCSR0B, RXCIE0);
    }
    }
    } //end Timer2 ISR

Leave a Reply