User Tools

Site Tools


projects:dmx_led_wash:home

DMX led wash

~~META:description abstract=A DMX512 led wash receiver/driver~~

A DMX512 led wash receiver/driver, current source version (single power leds) and open drain version (led strips).

Takes 3 DMX channels (RGB).

Features

  • Up to 9A output (open drain version, with heatsink)
  • Uses 3 DMX channels
  • 7-30V input
  • Cheap ATMEGA8L based
  • Small footprint
  • Extension header with ADC and INT lines for sound sensor or such (standalone mode)

Electronics

Maximum ratings

Open drain

AP4800 on 1“2 clad has a junction-to-ambient thermal resistance of 50°C/W, on minimal clad it is 125°C/W, in our case it should be close to 100°C/W.

Average study

Maximum temperature will be 125°C, taking ambient temperature of 25°C we get at most 1W power dissipation per MOS, which means a 7.4A current.

Worse case study

Maximum temperature will be 100°C, taking ambient temperature of 50°C we get at most 0.5W power dissipation per MOS, which means a 5.2A current.

Maximum power study

Taking a current of 9.5A we get a power dissipation of 1.63W. To avoid going over 125°C from a 25°C ambient temperature it is required to use a 35°C/W heatsink (5.7°C/W in the worst case).

Current source

In this case and without heatsink we should avoid dissipation over 0.5W per MOS (by using series resistors).

Using heatsink will allow to go higher than that.

Schematic

schematic.svg

Source code

main.c
/**
 * @defgroup RGB leds DMX512 controller
 * @type firmware
 * @file main.c
 * @author Etienne Meleard <etienne@yent.eu>
 * @creation 2014-06-20
 * @tabsize 4
 * @mcu ATMEGA8L
 * @fcpu 8MHz
*/
 
 
//============================ WARNING ===============================//
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!//
//                                                                    //
// Fuses must be like SUT1 = 1, SUT0 = 1, CKSEL3 = 1, CKSEL2 = 1,     //
// CKSEL1 = 1 and CKSEL0 = 0 to ensure using the crystal as clock     //
// (otherwise it uses the 8MHz internal clock), full swing mode is    //
// mandatory to acheive 8MHz operation.                               //
//                                                                    //
// CKDIV8 fuse must not be set to avoid clock frequency divided by 8  //
//                                                                    //
// Fuse set or = 1 means checked in ponyprog                          //
//                                                                    //
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!//
//====================================================================//
 
 
// ================= Fuses ================= //
 
/*FUSES = { // No success writing fuse this way, command line below ...
    .low = (FUSE_SUT1 & FUSE_SUT0 & FUSE_CKSEL3),
    .high = HFUSE_DEFAULT,
    .extended = EFUSE_DEFAULT,
};*/
 
// Run
// avrdude -c usbasp -p m8 -U lfuse:w:0xfe:m -U hfuse:w:0xd1:m -B250
// at first (-F may be needed)
 
 
// ================ Includes =============== //
 
#include <avr/io.h>
#include <sup_avr/io2.h>
#include <avr/interrupt.h>
#include <util/delay.h>
 
 
// ================ Pinouts ================ //
 
#define LED         Bit(PORTD).bit5
 
#define RX          Bit(PIND).bit0
#define RX_COMPLETE SIG_UART_RECV
#define RX_INT      Bit(PORTD).bit2
#define RX_SIG      SIG_INTERRUPT0
#define RX_TIMER    TCNT0
#define BREAK_LEN   11
#define MAB_LEN     1
 
#define DIP_0       Bit(PINC).bit0
#define DIP_1       Bit(PINC).bit1
#define DIP_2       Bit(PINC).bit2
#define DIP_3       Bit(PINC).bit3
 
#define DIP_A       Bit(PORTC).bit4
#define DIP_B       Bit(PORTB).bit4
#define DIP_C       Bit(PORTB).bit5
 
#define EXT_ADC     6
#define EXT_INPUT   Bit(PIND).bit3
#define EXT_OUTPUT  Bit(PORTD).bit3
#define EXT_SIG     SIG_INTERRUPT1
 
#define OUT_RED     Bit(PORTB).bit1
#define RED_CH      OCR1A
#define OUT_GREEN   Bit(PORTB).bit2
#define GREEN_CH    OCR1B
#define OUT_BLUE    Bit(PORTB).bit3
#define BLUE_CH     OCR2
 
 
// ================ Macros ================= //
 
#define BLINK       LED=1; _delay_ms(150); LED=0; _delay_ms(150)
#define PULSE10     LED=1; _delay_us(10); LED=0; _delay_us(10)
#define PULSE5      LED=1; _delay_us(5); LED=0; _delay_us(5)
#define PULSE3      LED=1; _delay_us(3); LED=0; _delay_us(3)
#define PULSE1      LED=1; _delay_us(1); LED=0; _delay_us(1)
 
#define RX_START    UCSRB |= 0x10; receiving = 1
#define RX_STOP     UCSRB &= 0xef; receiving = 0
 
#define ADDR_4      ((uint8_t)((PINC ^ 0x0f) & 0x0f))
 
#define DIP_SETTIME 10    // us, allows diodes to set (due to diode capacitance)
 
#define RX_ERROR    ((UCSRA & 0x10) || (UCSRA & 0x08))
 
#define MAX_TIME_WITHOUT_START 10000
#define ADDRESS_REFRESH_PERIOD 3000
 
#define PATTERN_FIXED   0
#define PATTERN_RAINBOW 1
#define PATTERN_BREATH  2
 
 
// ================ Globals ================ //
 
volatile uint16_t address = 0x0000;
 
volatile uint8_t receiving = 0;
volatile uint16_t received = 0;
 
volatile const uint8_t colors[12][3] = {
    {0xff, 0x00, 0x00}, // Linear colors
    {0x7f, 0x3f, 0x00},
    {0x7f, 0x7f, 0x00},
    {0x3f, 0x7f, 0x00},
    {0x00, 0xff, 0x00},
    {0x00, 0x7f, 0x3f},
    {0x00, 0x7f, 0x7f},
    {0x00, 0x3f, 0x7f},
    {0x00, 0x00, 0xff},
    {0x3f, 0x00, 0x7f},
    {0x7f, 0x00, 0x7f},
    {0x7f, 0x00, 0x3f}
    /*{0xff, 0x00, 0x00}, // Not linear
    {0xff, 0x7f, 0x00},
    {0xff, 0xff, 0x00},
    {0x7f, 0xff, 0x00},
    {0x00, 0xff, 0x00},
    {0x00, 0xff, 0x7f},
    {0x00, 0xff, 0xff},
    {0x00, 0x7f, 0xff},
    {0x00, 0x00, 0xff},
    {0x7f, 0x00, 0xff},
    {0xff, 0x00, 0xff},
    {0xff, 0x00, 0x7f}*/
};
 
volatile const uint16_t speeds[2][8] = {
    { // Rainbow : 1536 steps for each cycle, measured speed : c = 6.26 T + 17.49
        31 /*5s*/, 63 /*10s*/, 186 /*30s*/, 373 /*60s*/,
        562 /*90s*/, 752 /*120s*/, 1864 /*300s*/, 3740 /*600s*/
    },
    { // Breath : 256 steps for each cycle, so speed = 10000 T / 256, min is ???
        19 /*1s*/, 37 /*2s*/, 94 /*5s*/, 187 /*10s*/,
        375 /*20s*/, 562 /*30s*/, 1125 /*60s*/, 2250 /*120s*/
    }
};
 
 
// ================ Functions ============== //
 
void init(void) {
    // Led as output
    DDRD |= 1 << 5;
 
    // USART
    UCSRA = 0x00;   // No doubling of frequency
    UCSRB = 0x80;   // Rx disabled, Rx irq enabled, 8bit mode
    UCSRC = 0x8e;   // Async, no parity, 2 stops, 8 bits
    UBRRL = 1;      // 250kbds
 
    // Detection timer (timer 0)
    TCCR0 = 0x03;   // 64 prescaler, increment every 8us
 
    // Dip inputs
    DDRC &= 0xf0;
    PORTC |= 0x0f;  // Pull-up
 
    // Dip outputs
    DDRC |= 0x10;
    DDRB |= 0x30;
    DIP_A = 1;
    DIP_B = 1;
    DIP_C = 1;
 
    // RGB outputs as outputs
    DDRB |= 0x0e;
 
    // R/G timer (timer 1)
    TCCR1A = 0xa1;      // 8 bits, fast, non-inverting PWM on OC1A and OC1B
    TCCR1B = 0x04;      // with 256 prescaler ~ 122Hz
    RED_CH = 0x00;      // Red channel to 0
    GREEN_CH = 0x00;    // Green channel to 0
 
    // B timer (timer 2)
    TCCR2 = 0x66;   // 8 bits, fast, non-inverting PWM on OC2
    BLUE_CH = 0x00; // Blue channel to 0
}
 
void getAddress(void) {
    uint16_t buffer = 0x0000;
 
    DIP_A = 1;
    DIP_B = 1;
    DIP_C = 1;
 
    DIP_C = 0;
    _delay_us(DIP_SETTIME);
    buffer += ADDR_4;
    DIP_C = 1;
    buffer <<= 4;
 
    DIP_B = 0;
    _delay_us(DIP_SETTIME);
    buffer += ADDR_4;
    DIP_B = 1;
    buffer <<= 4;
 
    DIP_A = 0;
    _delay_us(DIP_SETTIME);
    buffer += ADDR_4;
    DIP_A = 1;
 
    address = buffer;
}
 
uint8_t getFrameStart(void) {
    uint32_t t = 1500000; // 1.5s
    while(t && RX) {
        _delay_us(1);
        t--;
    }
    if(!t) return 0;
 
    RX_TIMER = 0;
    while(!RX && RX_TIMER < BREAK_LEN) _delay_us(1);
    if(RX_TIMER < BREAK_LEN) return 0; // Error, break too short
 
    while(!RX) _delay_us(1); // Wait for MAB
 
    RX_TIMER = 0;
    while(RX && RX_TIMER < MAB_LEN) _delay_us(1);
    if(RX_TIMER < MAB_LEN) return 0; // Error, break too short
 
    received = 0;
    RX_START;
 
    return 1;
}
 
 
// =============== Interrupts ============== //
 
SIGNAL(RX_COMPLETE) {
    uint8_t data;
    data = UDR;
 
    if(!receiving) return;
 
    if(UCSRA & 0x10) return;
    if(UCSRA & 0x08)return;
 
    received++;
 
    if(received == 1) {
        //if(data != 0x00) RX_STOP; // Should be 0x00, not a setting packet otherwise
        return; // Ignore first slot
    }
 
    if(received < address + 2) {
        return; // Address not reached
    }
 
    if(received > address + 4) { // Received enough packets, stop receiving and wait for next frame
        RX_STOP;
        return;
    }
 
    // Got channel, set it
    switch(received - address - 2) {
        case 0: RED_CH      = data; break;
        case 1: GREEN_CH    = data; break;
        case 2: BLUE_CH     = data; break;
    }
}
 
 
// ================== Main ================= //
 
int main(void) {
    uint16_t time_without_start = 0;
    uint16_t address_is_valid = 0;
 
    uint8_t auto_mode = 0;
    uint8_t pattern = 0;
    uint8_t speed = 0;
    uint8_t stepped = 0;
    uint8_t color = 0;
 
    uint8_t color_index = 0;
    uint16_t wait_step = 0;
    uint16_t step = 0;
 
    uint16_t level = 0;
 
    cli();        // WatchDog disabled AT ALL TIMES !
    init();
    sei();
 
    BLINK;
    BLINK;
 
    while(1) {
        if(!address_is_valid) {
            getAddress();
 
            auto_mode = address >> 9;
            pattern = (address >> 7) & 0x03;
 
            if(auto_mode) switch(pattern) {
                case PATTERN_FIXED :
                    color = address & 0x3f;
                    break;
 
                case PATTERN_RAINBOW :
                    speed = (address >> 4) & 0x07;
                    stepped = (address >> 3) & 0x01;
                    break;
 
                case PATTERN_BREATH :
                    speed = (address >> 4) & 0x07;
                    color = address & 0x0f;
                    //color_index = color;
                    break;
            }
 
            address_is_valid = ADDRESS_REFRESH_PERIOD;
        }
 
        if(auto_mode) {
            if(!wait_step) { // Update output
                switch(pattern) {
                    case PATTERN_FIXED :
                        RED_CH = (color << 6) & 0xc0;
                        GREEN_CH = (color << 4) & 0xc0;
                        BLUE_CH = (color << 2) & 0xc0;
                        wait_step = (uint16_t)3500; // 0.35s
                        break;
 
                    case PATTERN_RAINBOW :
                        if(color) {
                            color_index = color - 1;
                        }else color_index = 11;
 
                        if(stepped) { // 25% (x2) holding
                            if(step < 32) level = 0;
                            else if(step >= 96) level = 127;
                            else level = 2 * step - 64;
 
                        }else level = step;
 
                        RED_CH = ((127 - level) * colors[color_index][0] + level * colors[color][0]) >> 7;
                        GREEN_CH = ((127 - level) * colors[color_index][1] + level * colors[color][1]) >> 7;
                        BLUE_CH = ((127 - level) * colors[color_index][2] + level * colors[color][2]) >> 7;
 
                        step++;
                        if(step >= 128) {
                            step = 0;
                            color++;
                            if(color >= 12) {
                                color = 0;
                            }
                        }
                        wait_step = speeds[0][speed];// - (stepped ? 25 : 19); // stepped cycle code takes approx. 25 (TODO), fade cycle code takes approx. 19
                        break;
 
                    case PATTERN_BREATH :
                        if(step < 256) {
                            level = step;
                        }else{
                            level = 511 - step;
                        }
 
                        RED_CH = (level * colors[color_index][0]) >> 8;
                        GREEN_CH = (level * colors[color_index][1]) >> 8;
                        BLUE_CH = (level * colors[color_index][2]) >> 8;
 
                        step++;
                        if(step >= 512) {
                            step = 0;
                            if(color == 0) {
                                color_index += 2;
                                if(color_index >= 12) color_index = 0;
                            }else color_index = color - 1;
                        }
                        wait_step = speeds[1][speed]; // TODO modulate dep on cycle approx time
                        break;
                }
            }
 
            if(wait_step) wait_step--;
        }else{ // DMX
            if(getFrameStart()) {
                LED = 0;
                time_without_start = 0;
            }
            if(time_without_start > MAX_TIME_WITHOUT_START) {
                if(receiving) RX_STOP;
                BLINK;
            }
            time_without_start++;
        }
 
        if(address_is_valid) address_is_valid--;
        _delay_us(100);
    }
    return 0;
}
makefile
# Name: Makefile
# Tabsize: 4
 
DEVICE  = atmega8
F_CPU   = 8000000	# in Hz
FUSE_L  = # see below for fuse values for particular devices
FUSE_H  = 
AVRDUDE = avrdude -c usbasp -p m8 #$(DEVICE) # edit this line for your programmer
 
#CFLAGS  = -Iusbdrv -I. -DDEBUG_LEVEL=0
CFLAGS  = -I. -DDEBUG_LEVEL=0
#OBJECTS = usbdrv/usbdrv.o usbdrv/usbdrvasm.o usbdrv/oddebug.o main.o
OBJECTS = main.o
 
COMPILE = avr-gcc -Wall -Os -DF_CPU=$(F_CPU) $(CFLAGS) -mmcu=$(DEVICE)
 
# symbolic targets:
help:
	@echo "This Makefile has no default rule. Use one of the following:"
	@echo "make hex ....... to build main.hex"
	@echo "make flash ..... to flash the firmware (use this on metaboard)"
	@echo "make clean ..... to delete objects and hex file"
 
hex: main.hex
 
# rule for uploading firmware:
flash: main.hex
	$(AVRDUDE) -U flash:w:main.hex:i
 
# rule for deleting dependent files (those which can be built by Make):
clean:
	rm -f main.hex main.lst main.obj main.cof main.list main.map main.eep.hex main.elf *.o
#	rm -f main.hex main.lst main.obj main.cof main.list main.map main.eep.hex main.elf *.o usbdrv/*.o main.s usbdrv/oddebug.s usbdrv/usbdrv.s
 
# Generic rule for compiling C files:
.c.o:
	$(COMPILE) -c $< -o $@
 
# Generic rule for assembling Assembler source files:
.S.o:
	$(COMPILE) -x assembler-with-cpp -c $< -o $@
# "-x assembler-with-cpp" should not be necessary since this is the default
# file type for the .S (with capital S) extension. However, upper case
# characters are not always preserved on Windows. To ensure WinAVR
# compatibility define the file type manually.
 
# Generic rule for compiling C to assembler, used for debugging only.
.c.s:
	$(COMPILE) -S $< -o $@
 
# file targets:
 
main.elf: $(OBJECTS)	# usbdrv dependency only needed because we copy it
	$(COMPILE) -o main.elf $(OBJECTS)
 
main.hex: main.elf
	rm -f main.hex main.eep.hex
	avr-objcopy -j .text -j .data -O ihex main.elf main.hex
	avr-size main.hex
 
# debugging targets:
 
disasm:	main.elf
	avr-objdump -d main.elf
 
cpp:
	$(COMPILE) -E main.c

Downloads

How to

Manual

DMX mode

DMX mode is activated by setting the 10th dip switch to 0.

In this mode dip switches 1 to 9 set the base channel (1 to 512).

The wash uses 3 channels : Red, Green and Blue.

Auto mode

Auto mode is activated by setting the 10th dip switch to 1.

In this mode dip switches 8 and 9 select the pattern :

8-9 dip pattern configuration
dip range role
00 fixed color 1-2 red level, 0-3 (max)
3-4 green level, 0-3 (max)
5-6 blue level, 0-3 (max)
10 rainbow (12 colors, see breathing) 4 step mode in “continous change” (0) and “stay a bit on each color” (1)
5-7 speed in : 2s (000), 10s (100), 30s (010), 60s (110), 90s (001), 120s (101), 300s (011) and 600s (111)
01 color breathing 1-4 color, 0 means color rolling, 1-12 select color in : red (1000), orange (0100), yellow (1100), lime (0010), green (1010), green-blue (0110), cyan (1110), light-blue (0001), blue (1001), blue-magenta (0101), magenta (1101) and pink (0011)
5-7 speed in : 1s (000), 2s (100), 5s (010), 10s (110), 20s (001), 30s (101), 60s (011) and 120s (111)
11 Reserved for audio sensing extension

led microcontroller lighting dmx

projects/dmx_led_wash/home.txt · Last modified: 2022/06/30 21:13 by 127.0.0.1