====== 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>schematic.svg}}
===== Source code =====
/**
* @defgroup I2C slave DMX TX node
* @type firmware
* @file main.c
* @author Etienne Meleard
* @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
#include
#include
#include
// ================ 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();
}
{{tag>dmx, microcontroller, lighting}}