I always wanted to make a decent controller for LED strips, so the most logical choice would be to get started with DMX. On this page I will describe this project of mine.
Step 1: design some PCB’s! Note that the final PCB will be 17*50mm

This morning the mailman delivered a cute box all the way from HongKong! My PCB’s arrived, and I was kind of surprised, see for yourself:
Just so you know: I ordered 10 of those, and i got 22! I even had 8 electrically checked. THANK YOU SEEEDSTUDIO!
These small boards look just great
Not a single remark I can make concerning the quality! The only design flaw (but thats my fault) is an interruption in the ground plane because of a via.
I immediately soldered one of these boards as a testing setup

That is:
PIC12F1822
MAX3485
Microchip 3.3V regulator
BC847B
In the end it will be attached to a smaller piece of an RGB led strip, but for the moment I use it entirely.

I finished developing the software for the micro controllers *jeej*!
I’m using Freestyler which is a Belgian freeware, so I’m happy to use it. I can control the three colours (Red, Green, Blue) of each strip separately via the (surprisingly easy) DMX protocol.
Enough Jibber Jabber; here are pics!

Each module is connected by a small pin header (12V, GND, D+ & D-)
DMX receiver module:

Power & terminating resistor module:

USB communication module (without the FTDI232RL breakout board):

I’ll try cleaning up the Assembly code, group schematic, PCB and BOM files and release it here for the public!
There are still ‘some’ PCBs left, so if you’re interested, send me an email (look on top of this website)
So you want to see some code, well here you have it!
In short: It’s in ASM, it uses software PWM (based on a timer interrupt) and the UART hardware firing interrupts.
I wouldn’t call it finished, but it’s in a working state 🙂 I hope comments explain the entire code, but I must admit that I have to reread my own code to figure out how I did it. If you have any questions, just post a reply (if the bots didn’t break it, otherwise just mail).
#include list p=12f1822, r=dec ; errorlevel -306 ; no page boundary warnings ; errorlevel -302 ; no bank 0 warnings ; errorlevel -202 ; no 'argument out of range' warnings ; LED pin locations #define PIN_R 0 #define PIN_G 2 #define PIN_B 1 ; Defines starting conditions of pins #define DUTY_R 0x20 #define DUTY_G 0xAA #define DUTY_B 0xFF ; Defines addresses #define R_ADDR_LOW 0x07 #define R_ADDR_HIGH 0x00 ; Defines addresses #define G_ADDR_LOW 0x08 #define G_ADDR_HIGH 0x00 ; Defines addresses #define B_ADDR_LOW 0x09 #define B_ADDR_HIGH 0x00 ; Defines end address (standard is 512) #define TOTAL_ADDR_LOW 0x00 #define TOTAL_ADDR_HIGH 0x02 __config _CONFIG1, (_FCMEN_OFF & _IESO_OFF & _CLKOUTEN_OFF & _BOREN_ON & _CPD_ON & _CP_OFF & _MCLRE_ON & _PWRTE_OFF & _WDTE_OFF & _FOSC_INTOSC) ; 0011 1111 1110 0100 __config _CONFIG2, (_WRT_OFF & _PLLEN_OFF & _STVREN_ON & _BORV_19 & _LVP_OFF) ; 0001 1110 1111 1111 #define BREAK 0 cblock 0x70 teller duty_r temp_duty_r duty_g temp_duty_g duty_b temp_duty_b addr_low addr_high temprc stat endc org 0x000 nop goto init org 0x004 ;--------------------------------------- ; Interrupt handler ;--------------------------------------- bcf INTCON, GIE movlb 0 btfsc PIR1, RCIF goto serialprocedure btfsc INTCON, TMR0IF goto timerprocedure serialprocedure movlb 3 ;btfsc RCSTA, FERR ; Test for framing error ;retfie ; This is the start sequence btfss RCSTA, FERR ; Test for framing error goto testandinc movf RCREG, W ; Mark register as read bsf stat, BREAK ; Break has passed, set bit movlw 0xFE movwf TXREG retfie testandinc btfss stat, BREAK ; Test if break has happened goto serialinc ; We're past the start sequence ; This is the start byte movf RCREG, W ; Mark register as read bcf stat, BREAK ; Clear break bit retfie serialinc ; Increment counter incf addr_low, F ; Increment the Low byte btfsc STATUS, Z ; Do we have Zero (Multiple of 256)? incf addr_high, F ; Increment High byte (if necessary) movf RCREG, W ; Move RCREG in a private variable movwf temprc call debugger testRedAddress movlw R_ADDR_LOW subwf addr_low, W ; substract values btfss STATUS, Z ; if zero detected, skip goto testGreenAddress movlw R_ADDR_HIGH subwf addr_high, W ; substract values btfss STATUS, Z ; if zero detected, skip goto testGreenAddress ; If this is reached, address is the same movf temprc, W movwf temp_duty_r testGreenAddress movlw G_ADDR_LOW subwf addr_low, W ; substract values btfss STATUS, Z ; if zero detected, skip goto testBlueAddress movlw G_ADDR_HIGH subwf addr_high, W ; substract values btfss STATUS, Z ; if zero detected, skip goto testBlueAddress ; If this is reached, address is the same movf temprc, W movwf temp_duty_g testBlueAddress movlw B_ADDR_LOW subwf addr_low, W ; substract values btfss STATUS, Z ; if zero detected, skip goto testReset movlw B_ADDR_HIGH subwf addr_high, W ; substract values btfss STATUS, Z ; if zero detected, skip goto testReset ; If this is reached, address is the same movf temprc, W movwf temp_duty_b testReset movlw TOTAL_ADDR_LOW subwf addr_low, W ; substract values btfss STATUS, Z ; if zero detected, skip retfie movlw TOTAL_ADDR_HIGH subwf addr_high, W ; substract values btfss STATUS, Z ; if zero detected, skip retfie ; Address counter has reached maximum clrf addr_low clrf addr_high retfie timerprocedure bcf INTCON, TMR0IF incfsz teller goto int_test ; End of period: ; Setting new duty cycles movf temp_duty_r, W movwf duty_r movf temp_duty_g, W movwf duty_g movf temp_duty_b, W movwf duty_b ; Setting LEDs movlb 2 movf duty_r, W bcf LATA, PIN_R ; Turn off first btfss STATUS, Z bsf LATA, PIN_R ; turn on led movf duty_g, W bcf LATA, PIN_G ; Turn off first btfss STATUS, Z bsf LATA, PIN_G ; turn on led movf duty_b, W bcf LATA, PIN_B ; Turn off first btfss STATUS, Z bsf LATA, PIN_B ; turn on led retfie ; Go back! int_test movlb 2 ; Test Red duty movf duty_r, W ; put duty in W subwf teller, W ; substract values btfsc STATUS, Z ; if zero detected, execute bcf LATA, PIN_R ; turn off led ; Test Green duty movf duty_g, W ; put duty in W subwf teller, W ; substract values btfsc STATUS, Z ; if zero detected, execute bcf LATA, PIN_G ; turn off led ; Test Blue duty movf duty_b, W ; put duty in W subwf teller, W ; substract values btfsc STATUS, Z ; if zero detected, execute bcf LATA, PIN_B ; turn off led retfie ; and return from interrupt ;--------------------------------------- init movlw 0x00 movwf teller ; Reset teller movlw DUTY_R movwf duty_r ; Load duty cycle movwf temp_duty_r movlw DUTY_G movwf duty_g ; Load duty cycle movwf temp_duty_g movlw DUTY_B movwf duty_b ; Load duty cycle movwf temp_duty_b clrf addr_low clrf addr_high ; Set clock speed ;movlw b'10100111' ; Internal clock at 62.5kHz movlw b'01111011' ; Internal clock at 16Mhz movlb 1 movwf OSCCON movlb 3 clrf ANSELA ; All pins digital movlb 1 clrf ADCON0 ; Disable ADC clrf ADCON1 ; Digital I/O clrf TRISA ; All pins output bsf TRISA, 5 ; RA5 is input movlb 2 ; clrf LATA ; turn off all LEDs clrf CM1CON0 ; Disable comparator clrf CM1CON1 ; ; bsf LATA, 5 bsf APFCON, RXDTSEL ; Set RX to RA5 bsf APFCON, TXCKSEL ; Set TX to RA4 movlb 3 bsf BAUDCON, BRG16 ; 16 bit baud generator bsf TXSTA, BRGH ; Set High baud rate ; Place 15 in SPBRG movlw 0x0F movwf SPBRGL clrf SPBRGH movlb 1 bsf PIE1, RCIE ; Enable receive intterrupt bcf PIE1, TXIE ; Disable transmit interrupts movlb 3 bsf TXSTA, TXEN ; Enable transmit bsf RCSTA, RX9 ; 9 bit reading bsf RCSTA, SPEN ; Enables serial port bsf RCSTA, CREN bcf TXSTA, SYNC ; Asynch mode movlb 1 bcf OPTION_REG, TMR0CS ; Timer op Fosc/4 bsf OPTION_REG, PSA ; DISABLE prescaler bcf OPTION_REG, PS0 ; Set timer prescaler to 1:2 bcf OPTION_REG, PS1 bcf OPTION_REG, PS2 bsf INTCON, PEIE ; Perpheral interrupts enabled bsf INTCON, TMR0IE ; Enable timer0 overflow interrupt bsf INTCON, GIE ; Interrupts on! main nop goto main debugger movlb 3 movf addr_high, W movwf TXREG movf addr_low, W movwf TXREG return end
If you might want to have these boards made by a PCB service (or make it yourself!), here are the files.
What’s in the archive?
* Eagle schematic and board files
* 2 libraries I made
* Gerber files for services like seeedstudio
Note to self: C1 is 1µF and C2 is 100nF
Have fun!
Last week we put the lights to the test for a small party. Because 3 little (9 LED) LED strips is a little lousy for a party, I made some more:
Two full size LED strips were also driven by the tiny DMX receiver boards, so they are capable of something.
Putting all of those strips next to each other is also quite stupid, so I also made 5 extension cables (in total 10m in length):
Here is an updated version of the firmware.
Changes:
* Added a ‘reset counter by break’ feature, so that less than 512 bytes could be received without breaking anything
* Increased PWM frequency to reduce flickering (with my LED strips, flickering is not visible any more)
Note: I think they are more or less DMX-512 compliant. I’d say they are more forgiving than the strict standard. But, if there are transmission errors in the data packages, a break will be detected and the entire set will be lost or even ‘sent’ to the wrong devices. Just so you know!
#include <p12F1822.inc> list p=12f1822, r=dec ; errorlevel -306 ; no page boundary warnings ; errorlevel -302 ; no bank 0 warnings ; errorlevel -202 ; no 'argument out of range' warnings ; Defines addresses #define R_ADDR_LOW 0x01 #define R_ADDR_HIGH 0x00 ; Defines addresses #define G_ADDR_LOW 0x02 #define G_ADDR_HIGH 0x00 ; Defines addresses #define B_ADDR_LOW 0x03 #define B_ADDR_HIGH 0x00 ; Defines starting conditions of pins (useful when no DMX signal is applied) #define DUTY_R 0x20 #define DUTY_G 0xAA #define DUTY_B 0xFF ; LED pin locations #define PIN_R 1 #define PIN_G 2 #define PIN_B 0 ; Defines end address (standard is 512) ; The counter is also reset by the break signal, this is an extra check. ; If you want to abuse the DMX-512 protocol (by using more than 512 values), ; Change it here #define TOTAL_ADDR_LOW 0x00 #define TOTAL_ADDR_HIGH 0x02 ; Timer preload that generates timebase for PWM ; This value reduces flickering #define TIMER_LOAD 0xD0 __config _CONFIG1, (_FCMEN_OFF & _IESO_OFF & _CLKOUTEN_OFF & _BOREN_ON & _CPD_OFF & _CP_OFF & _MCLRE_ON & _PWRTE_OFF & _WDTE_OFF & _FOSC_INTOSC) ; 0011 1111 1110 0100 __config _CONFIG2, (_WRT_OFF & _PLLEN_OFF & _STVREN_ON & _BORV_19 & _LVP_OFF) ; 0001 1110 1111 1111 #define BREAK 0 cblock 0x70 cntr ; 8 bit duty cycle counter duty_r temp_duty_r duty_g temp_duty_g duty_b temp_duty_b addr_low ; Current lower address addr_high ; Current higher address temprc ; Temporary received value stat ; Status register endc org 0x000 nop goto init org 0x004 ;--------------------------------------- ; Interrupt handler ;--------------------------------------- bcf INTCON, GIE movlb 0 btfsc PIR1, RCIF goto serialprocedure btfsc INTCON, TMR0IF goto timerprocedure serialprocedure movlb 3 btfss RCSTA, FERR ; Test for framing error goto testandinc ; A break (or part of the break) has been detected movf RCREG, W ; Mark register as read bsf stat, BREAK ; Break has passed, set bit movlw 0xFE movwf TXREG clrf addr_low ; Clear counters clrf addr_high retfie testandinc btfss stat, BREAK ; Test if break has happened goto serialinc ; We're past the start sequence ; This is the start byte, we don't care about it movf RCREG, W ; Mark register as read bcf stat, BREAK ; Clear break bit retfie serialinc ; Increment counter incf addr_low, F ; Increment the Low byte btfsc STATUS, Z ; Do we have Zero (Multiple of 256)? incf addr_high, F ; Increment High byte (if necessary) movf RCREG, W ; Move RCREG in a another variable movwf temprc call debugger testRedAddress movlw R_ADDR_LOW subwf addr_low, W ; substract values btfss STATUS, Z ; if zero detected, skip goto testGreenAddress movlw R_ADDR_HIGH subwf addr_high, W ; substract values btfss STATUS, Z ; if zero detected, skip goto testGreenAddress ; If this is reached, address is the same movf temprc, W movwf temp_duty_r testGreenAddress movlw G_ADDR_LOW subwf addr_low, W ; substract values btfss STATUS, Z ; if zero detected, skip goto testBlueAddress movlw G_ADDR_HIGH subwf addr_high, W ; substract values btfss STATUS, Z ; if zero detected, skip goto testBlueAddress ; If this is reached, address is the same movf temprc, W movwf temp_duty_g testBlueAddress movlw B_ADDR_LOW subwf addr_low, W ; substract values btfss STATUS, Z ; if zero detected, skip goto testReset movlw B_ADDR_HIGH subwf addr_high, W ; substract values btfss STATUS, Z ; if zero detected, skip goto testReset ; If this is reached, address is the same movf temprc, W movwf temp_duty_b testReset movlw TOTAL_ADDR_LOW subwf addr_low, W ; substract values btfss STATUS, Z ; if zero detected, skip retfie movlw TOTAL_ADDR_HIGH subwf addr_high, W ; substract values btfss STATUS, Z ; if zero detected, skip retfie ; Address counter has reached maximum ; This assures compliance with DMX-512 protocol clrf addr_low ; Reset counters clrf addr_high retfie timerprocedure bcf INTCON, TMR0IF ; Preloading timer value movf TIMER_LOAD, W movwf TMR0 incfsz cntr goto int_test ; End of period, setting new duty cycles movf temp_duty_r, W movwf duty_r movf temp_duty_g, W movwf duty_g movf temp_duty_b, W movwf duty_b ; Setting LEDs movlb 2 movf duty_r, W bcf LATA, PIN_R ; Turn off first btfss STATUS, Z bsf LATA, PIN_R ; turn on led movf duty_g, W bcf LATA, PIN_G ; Turn off first btfss STATUS, Z bsf LATA, PIN_G ; turn on led movf duty_b, W bcf LATA, PIN_B ; Turn off first btfss STATUS, Z bsf LATA, PIN_B ; turn on led retfie int_test movlb 2 ; Test Red duty movf duty_r, W ; put duty in W subwf cntr, W ; substract values btfsc STATUS, Z ; if zero detected, execute bcf LATA, PIN_R ; turn off led ; Test Green duty movf duty_g, W ; put duty in W subwf cntr, W ; substract values btfsc STATUS, Z ; if zero detected, execute bcf LATA, PIN_G ; turn off led ; Test Blue duty movf duty_b, W ; put duty in W subwf cntr, W ; substract values btfsc STATUS, Z ; if zero detected, execute bcf LATA, PIN_B ; turn off led retfie ; and return from interrupt ;-------------------------------------------- ; This is the intialisation and main routine ;-------------------------------------------- init movlw 0x00 movwf cntr ; Reset counter movlw DUTY_R movwf duty_r ; Load duty cycle movwf temp_duty_r movlw DUTY_G movwf duty_g ; Load duty cycle movwf temp_duty_g movlw DUTY_B movwf duty_b ; Load duty cycle movwf temp_duty_b clrf addr_low clrf addr_high ; Set clock speed movlw b'01111011' ; Internal clock at 16Mhz movlb 1 movwf OSCCON movlb 3 clrf ANSELA ; All pins digital movlb 1 clrf ADCON0 ; Disable ADC clrf ADCON1 ; Digital I/O clrf TRISA ; All pins output bsf TRISA, 5 ; RA5 is input (UART) movlb 2 clrf CM1CON0 ; Disable comparator clrf CM1CON1 bsf APFCON, RXDTSEL ; Set RX to RA5 bsf APFCON, TXCKSEL ; Set TX to RA4, only used for debugging purposes movlb 3 ; Configure UART module bsf BAUDCON, BRG16 ; 16 bit baud generator bsf TXSTA, BRGH ; Set High baud rate movlw 0x0F ; Place 15 in SPBRG<0:15> movwf SPBRGL clrf SPBRGH movlb 1 bsf PIE1, RCIE ; Enable receive intterrupt bcf PIE1, TXIE ; Disable transmit interrupts movlb 3 bsf TXSTA, TXEN ; Enable transmit bsf RCSTA, RX9 ; 9 bit reading bsf RCSTA, SPEN ; Enables serial port bsf RCSTA, CREN bcf TXSTA, SYNC ; Asynch mode ; Configure Timer0 module movlb 1 bcf OPTION_REG, TMR0CS ; Timer op Fosc/4 bsf OPTION_REG, PSA ; DISABLE prescaler bcf OPTION_REG, PS0 ; Set timer prescaler to 1:2 bcf OPTION_REG, PS1 bcf OPTION_REG, PS2 bsf INTCON, PEIE ; Perpheral interrupts enabled bsf INTCON, TMR0IE ; Enable timer0 overflow interrupt bsf INTCON, GIE ; Interrupts on! main nop goto main ; Writes the current counter values to the transmit UART debugger movlb 3 movf addr_high, W movwf TXREG movf addr_low, W movwf TXREG return end
This is the result:
https://www.youtube.com/watch?v=7OesQ3aEstE
The flicker you see in the center frame, is because the set value for the LEDs is not full brightness, and therefore it’s using PWM to change the intensity. It’s that flicker frequency that can still be detected by the camera, but no longer by the human eye
How do you set dmx start addres.
I assume that you mean the address for a device (or LED color)?
Check the source code. At lines 7 through 17, you’ll find the defines for the three colors. You simply ‘glue’ the low and high byte together, and then you know the address. So, they’re actually hard-coded. There are no switches to quickly change them.
Hey, cool project !
I was asking myself what site/service you used to make your pcb. For small quantity i only know eurocircuit and pcbpool but is it cheaper to do that in hong kong ?
Welcome here! Nice to see some more Belgian people 🙂
I have been using the PCB service from SeeedStudio in China. There is also iTeadStudio that delivers the same quality. They tend to compete with each other!
I haven’t come around to moving this article from my old site, so here is a post I made about getting PCB’s made in China: http://phalox.be/flux/viewtopic.php?id=16
Have fun! If you have any more questions, I’d love to hear them!
Thanks for your quick answer ! I can really see the point of doing those in china. I wonder how they do benefits which such low prices… Anyway, i’ll browse your site when time will be mine. I need to study right now but i’ve many projects in my head waiting to be done ! (including an 8 channels adressable dmx receiver) & Any answers to my questions would be appreciated of course !
Good luck with exams 🙂
This is my first year after my studies, meaning that I’m working!
Write your ideas down, it helps clearing your mind of them and you won’t forget them 😉
what about the USB Connectivity module details it isn’t given anywhere 🙁 i need it to make the USB Connectivity and also is it possible to connect multiple DMX Receiver with the help of IN n OUT DMX Pins
You’ve got a point, I never really discussed that.
In fact it’s quite simple: I am using a USB to UART converter board (the black one). Mine contains a FT232RL chip. You can find these kinds of boards on multiple places (check ebay!).
Next, we need to make a RS485 signal. The only difference with UART, is that this is a differential signal (one line is high, the other is low and vice versa) and it works on different voltages.
I am using a MAX485 (but there are cheaper version not from Maxim). This is actually a transceiver (can transmit and receive, but not at the same time), but I am using it as a transmitter only (DMX is one way traffic!). Have a look at the datasheet of this chip: http://www.usconverters.com/downloads/max491.pdf
For transmission function, you have to connect:
* /RE to 5V -> this DISABLES receive
* DE to 5V -> this ENABLES transmit
* RO can be left open. I think you can also connect to GND
* DI is the input of the chip
* Power stuff, can simply be powered by the 5V and GND coming from the FT232RL board.
Connect the TX line of the FT232RL to DI of MAX485, the A and B output of the MAX 485 are your DMX signal. Make sure you don’t swap them, because then all your information is inverted!
PS: you can also find USB->DMX modules on Ebay, but I don’t know how they work…
Your last question: Yes! DMX is a bus, so you can attach as many modules as you want (as long as your bus can handle it, and you don’t cross the 512 limit). Normally they connect a lot less nodes per bus.
Hi. I am interesting dmx 3ch rec. but I can’t convert hex file. PLease help me. Usually I am writing program with picbasicpro. Now your program asm. program. how can I build this. I downloaded mplab software. But give to me more error. I think 12lf1822.lib can’t see. Please share with me your experience. I writen dmx sender with pbp it is working good. But I can’t write receiver program. I think pbp commands take a very long time. start pulse sometimes lost. I want to try your program. Please help. Thanks
Nice little circuit 🙂
I wonder if you have considered adding gamma-correction for the LEDs (see https://learn.adafruit.com/led-tricks-gamma-correction/the-quick-fix).. I am no expert i PIC assembler myself, but I would consider it to be possible to implement with a reasonable knowledge about the PIC assembler syntax.
Thanks! I did however learn, that if you’re using it in “production” (a party or something), you really need better isolation (preferably on the DMX transmitter side) to work stable. Also, XLR connectors would be nice to have. Those pin headers are dead in no time!
I did not actually do any gamma correction, but should be possible! The 12F1822 (http://ww1.microchip.com/downloads/en/DeviceDoc/40001413E.pdf) has 256 bytes of data memory (eeprom) which could be used for this (that’s a bit of a waste though. Or somewhere in its 2k of flash. Having fun with banks is somewhat annoying on these tiny chips, so then the EEPROM memory doesn’t sound too bad 🙂
Many ways to achieve the result. If you feel like it, please take my code and dive into! the 12F pics you can sample for free at Mchip! I’ve just moved abroad and had to leave my electronics stuff at home 🙁
[…] did DMX receivers, we did DMX pool lights, but now it’s time for something slightly more professional: Buying […]