User Tools

Site Tools


projects:dmx:i2c_chainable_bridge:home

I2C to DMX chainable bridge

~~META:description abstract=I2C to DMX chainable bridge module~~

A simple but effective module that self-stream DMX frames. It acts as a I2C slave allowing master to change frame data and allow DMX chaining to simply merge controllers into a single universe.

Features

  • 400kHz I2C
  • Up to 256 master channels
  • Master channels then chain channels merge logic

Electronics

schematic.svg

Source code

main.c
/**
 * @defgroup I2C slave DMX TX node
 * @type firmware
 * @file main.c
 * @author Etienne Meleard <etienne@yent.eu>
 * @creation 2015-06-16
 * @tabsize 4
 * @mcu ATMEGA8A
 * @fcpu 16MHz
*/
 
 
//============================ 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 16MHz 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(PORTX).bitY
 
#define RX          Bit(PIND).bit0
#define RX_COMPLETE SIG_UART_RECV
#define RX_TIMER    TCNT0
 
#define TX          Bit(PORTD).bit1
#define TX_COMPLETE SIG_UART_TRANS
 
#define BREAK_LEN   22
#define MAB_LEN     2
 
#define I2C_EVENT   SIG_2WIRE_SERIAL
 
 
// ================ 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 MAX_TIME_WITHOUT_START  10000
 
// ================ Globals ================ //
 
volatile uint8_t master_channels = 0;
volatile uint8_t master_channel_idx = 0;
 
volatile uint8_t receiving = 0;
volatile uint16_t received = 0;
 
volatile uint8_t sending = 0;
volatile uint16_t sent = 0;
 
volatile uint8_t data[513];
 
#define F_NONE                  0x00
#define F_SET_CHANNELS_NUMBER   0x01
#define F_SET_CHANNELS          0x10
#define F_SET_CHANNELS_NEXT     0x11
#define F_SET_SINGLE_CHANNEL    0x20
#define F_SET_SINGLE_CHANNEL_V  0x21
volatile uint8_t function = F_NONE;
 
// ================ Functions ============== //
 
void init(void) {
    uint16_t i;
 
    // Led as output
    DDRX |= 0x0Y;
 
    // USART
    UCSRA = 0x00;   // No doubling of frequency
    UCSRB = 0xc0;   // Rx disabled, Rx irq enabled, Tx disabled, Tx 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 4us
 
    // I2C slave
    TWAR = 0xe0;    // Slave adress (0b1110000 = 0x70), no listening to general calls
    TWCR = 0x45;    // Enable I2C, Slave mode, auto ACK, irq enabled
    TWBR = 0x0d;    // 400kHz SCL
    TWSR = 0x00;    // frequency
 
    // Set whole frame to 0
    for(i=0; i<513; i++)
        data[i] = 0x00;
}
 
void startSending(void) {
    TX = 0;         // Break
    _delay_us(100);
 
    TX = 1;         // MAB
    _delay_us(15);
 
    UCSRB |= 0x08;  // Enable TX, reset counter
    sending = 1;
    sent = 0;
 
    sendNext();     // Start sending channels
}
 
void stopSending(void) {
    UCSRB &= 0xf7;  // Disable TX
    sending = 0;
    TX = 1;
 
    _delay_us(100); // Wait between frames
 
    startSending(); // Start next frame
}
 
void sendNext(void) {
    UDR = data[sent];
 
    sent++;
 
    if(sent > FRAME_LENGTH)
        stopSending();
}
 
uint8_t startReceiving(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
 
    UCSRB |= 0x10;  // Enable RX
    receiving = 1;
 
    received = 0;   // Reset counter
 
    return 1;
}
 
void stopReceiving(void) {
    UCSRB &= 0xef;  // Disable RX
    receiving = 0;
}
 
 
// =============== Interrupts ============== //
 
SIGNAL(RX_COMPLETE) {
    uint8_t value;
    value = UDR;
 
    if(!receiving || !master_channels) return;
 
    if(UCSRA & 0x10) return;
    if(UCSRA & 0x08) return;
 
    received++;
 
    if(received == 1) {
        if(value != 0x00) stopReceiving(); // Should be 0x00, not a setting packet otherwise
        return; // Ignore first slot
    }
 
    if(received > (512 - master_channels)) {
        // Do not relay more than we can handle
        stopReceiving();
        return;
    }
 
    data[received + master_channels + 1] = value;
}
 
SIGNAL(TX_COMPLETE) {
    sendNext();
}
 
SIGNAL(I2C_EVENT) {
    if(TWSR != 0x80) return; // Ignor anything that is not valid data
 
    switch(function) {
        case F_NONE: // Got function
            function = TWDR;
            break;
 
        case F_SET_CHANNELS_NUMBER: // Master gives his number of channels
            master_channels = TWDR;
            break;
 
        case F_SET_CHANNELS: // Set first channel of whole
            master_channel_idx = 0;
 
        case F_SET_CHANNELS_NEXT: // Write channel from whole
            data[master_channel_idx + 1] = TWDR;
            master_channel_idx++;
            if(master_channel_idx >= master_channels)
                function = F_NONE;
            break;
 
        case F_SET_SINGLE_CHANNEL: // Set single channel idx ...
            master_channel_idx = TWDR;
            function = F_SET_SINGLE_CHANNEL_V;
            break;
 
        case F_SET_SINGLE_CHANNEL_V: // ... and value
            data[master_channel_idx + 1] = TWDR;
            function = F_NONE;
            break;
    }
 
    TWCR |= 0x80;   // Clear flag
}
 
 
// ================== Main ================= //
 
int main(void) {
    uint16_t time_without_start = 0;
 
    cli();        // WatchDog disabled AT ALL TIMES !
    init();
    sei();
 
    BLINK;
 
    startSending(); // Start sending routine (autonomous, irq driven)
 
    while(!master_channels) // Continous rapid blinking while no master channels received
        BLINK;
 
    while(1) {
        if(getFrameStart()) {
            LED = 0;
            time_without_start = 0;
        }
        if(time_without_start > MAX_TIME_WITHOUT_START) {
            if(receiving) stopReceiving();
            BLINK;
        }
        time_without_start++;
 
        _delay_us(100);
    }
    return 0;
}

Sample master code

// Master running at 16MHz
 
// Init
TWCR = 0x04;    // I2C enable
TWBR = 0x0d;    // 400kHz SCL
TWSR = 0x00;    // frequency
 
uint8_t openLinkToDMXBridge(void) {
    TWCR |= 0xa0;                       // Send start
    while(!(TWCR & 0x80));              // Wait for data to be sent
    if((TWSR & 0xf8) != 0x08) return 0;  // Check status
    TWDR = 0xe1;                        // address + write
    TWCR |= 0x80;                       // Send
    while(!(TWCR & 0x80));              // Wait for data to be sent
    if((TWSR & 0xf8) != 0x18) return 0;  // Check status
    return 1;
}
 
uint8_t sendByteToDMXBridge(uint8_t value) {
    TWDR = value;                       // address + write
    TWCR |= 0x80;                       // Send
    while(!(TWCR & 0x80));              // Wait for data to be sent
    if((TWSR & 0xf8) != 0x28) return 0;  // Check status
    return 1;
}
 
uint8_t closeLinkToDMXBridge(void) {
    TWCR |= 0x90;                       // Send stop
}
 
void sendNumberOfChannelsToDMXBridge(uint8_t count) {
    openLinkToDMXBridge();
    sendByteToDMXBridge(0x01);
    sendByteToDMXBridge(count);
    closeLinkToDMXBridge();
}
 
void sendAllChannelsToDMXBridge(uint8_t *channels, uint8_t count) {
    openLinkToDMXBridge();
    sendByteToDMXBridge(0x10);
    do {
        sendByteToDMXBridge(channels++);
    } while(--count);
    closeLinkToDMXBridge();
}
 
void sendSingleChannelToDMXBridge(uint8_t channel, uint8_t value) {
    openLinkToDMXBridge();
    sendByteToDMXBridge(0x20);
    sendByteToDMXBridge(channel);
    sendByteToDMXBridge(value);
    closeLinkToDMXBridge();
}

dmx, microcontroller, lighting

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