Author Topic: New lighting control system - i2c question rpi and avr micro  (Read 1089 times)

0 Members and 1 Guest are viewing this topic.

Offline jmibkTopic starter

  • Regular Contributor
  • *
  • Posts: 68
  • Country: at
New lighting control system - i2c question rpi and avr micro
« on: November 12, 2016, 06:49:29 pm »
Hello,

I'am designing a new lighting control software that runs on a rasperry pi and also on windows or linux based platforms. It communicates mainly via Art-Net and on a raspberry pi via I2C to an external micro (atmel atmega328p).

If you interested in that software, visit https://www.facebook.com/GearBoxLC1. There is also a alpha preview version of it for download. It is freeware.

Now to the problem: This i2c connection is very slow, 3 - 4 updates of the dmx universe (512x 8Bit) per second. It is used to output dmx data on the rpi hardware - so it is neccessary to outsource this task to an external micro, that can generate the neccessary pulses for the break in the dmx protocol (the rpi can''t do that itself).

The data are transmit by the raspberry pi (python script) in blocks of 32 bytes per i2c connection.

Here is the software of the pi, that sends the data (dmx data stored in __dmx_data):

Code: [Select]
try:
    from smbus import SMBus
except ImportError:
    #raise ImportError('This platform has no SMBUS')
    pass
import time
import threading

class I2C(threading.Thread):
    __address = 0x10
    __dmx_data = [0] * 512
    __send_state = False          #True, wenn I2C gesentet wird. Reset ueber userio 
    def __init__(self,dmxio):
        threading.Thread.__init__(self)
        self.__dmxio = dmxio
        try: 
            self.__i2c = SMBus(1)
        except:
            print "[ERROR] I2C.init: no I2C interface found on this system"
        for channel in range(0,512):
            self.__dmx_data[channel] = channel     #fill with data
           
    def run(self):
        print "[INFO] I2C thread start"
        while self.__dmxio.is_running():
            for block in range(0,16):
                data = self.get_i2c_dmx_block_data(block)
                #print data
                try:
                    self.__i2c.write_i2c_block_data(self.__address, block, data)
                    self.__send_state = True
                except:
                    pass
                #print data
                time.sleep(0.01)
            #print self.dmx_data   
        print "[INFO] I2C thread exit"

    '''
    CONTROL FUNCTIONS
    '''   
    #send state       
    def get_send_state(self):
        return self.__send_state
    def set_send_state(self, state):
        self.__send_state = state
             
    def get_i2c_dmx_block_data(self, block_number):
        data = []
        start = block_number*32
        end = start + 32
        for channel in range(start, end):
            data.append(self.__dmx_data[channel])
        return data

    def set_dmx_data(self,data):
        self.__dmx_data = data

and the Atmel code (it is clocked 16MHz) and operates at 3,3V :

Code: [Select]
#include <avr/interrupt.h> 
#include <stdint.h>    
#include <util/delay.h>
#include <util/twi.h>
#include <avr/io.h>

//common vars
volatile uint8_t dmx_data[512];
//twi vars
volatile uint16_t dmx_channel; //Listenindex
//dmx vars
uint16_t gCurDmxCh; //current DMX channel
uint8_t gDmxState;

enum {BREAK, STARTB, DATA};


#define TWCR_ACK TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|(0<<TWWC);  //ACK nach empfangenen Daten senden/ ACK nach gesendeten Daten erwarten
#define TWCR_NACK TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|(0<<TWWC); //NACK nach empfangenen Daten senden/ NACK nach gesendeten Daten erwarten
#define TWCR_RESET TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWEA)|(0<<TWSTA)|(1<<TWSTO)|(0<<TWWC);  //switch to the non adressed slave mode...

#define TWI_ADDRESS 0x20 //twi Die Slave-Adresse
#define F_OSC 16000 //oscillator freq. in kHz (typical 8MHz or 16MHz)
#define IBG    10   //interbyte gap [us]

ISR (TWI_vect){
uint8_t data=0;
switch (TW_STATUS){ //TWI-Statusregister prüfen und nötige Aktion bestimmen
case TW_SR_SLA_ACK: //0x60 Slave Receiver, Slave wurde adressiert
TWCR_ACK; //nächstes Datenbyte empfangen, ACK danach senden
dmx_channel=0xFF; //Bufferposition ist undefiniert
break;
case TW_SR_DATA_ACK: //0x80 Slave Receiver, ein Datenbyte wurde empfangen
data=TWDR; //Empfangene Daten auslesen
if (dmx_channel == 0xFF){ //erster Zugriff, Bufferposition setzen
dmx_channel = data * 32; //Bufferposition wie adressiert setzen
TCNT0 = 0;
PORTB |= (1<<PB2);
TWCR_ACK; //nächstes Datenbyte empfangen, ACK danach, um nächstes Byte anzufordern
}
else{ //weiterer Zugriff, nachdem die Position im Buffer gesetzt wurde. NUn die Daten empfangen und speichern
if(dmx_channel<512){
dmx_data[dmx_channel++]=data; //Daten in Buffer schreibe
}
TWCR_ACK;
}
break;
case TW_ST_SLA_ACK: //0xA8 Slave wurde im Lesemodus adressiert und hat ein ACK zurückgegeben.
case TW_ST_DATA_ACK: //0xB8 Slave Transmitter, Daten wurden angefordert
if (dmx_channel == 0xFF){ //zuvor keine Leseadresse angegeben!
dmx_channel=0;
}
if(dmx_channel<256) {
TWDR = dmx_data[dmx_channel++]; //Datenbyte senden
}
else{
TWDR=0; //Kein Daten mehr im Buffer
}
TWCR_ACK;
break;
case TW_SR_STOP:
TWCR_ACK;
    break;
case TW_ST_DATA_NACK: //0xC0 Keine Daten mehr gefordert
case TW_SR_DATA_NACK: //0x88
case TW_ST_LAST_DATA: //0xC8  Last data byte in TWDR has been transmitted (TWEA = “0”); ACK has been received
default:
    TWCR_RESET;
break;
}
}

ISR (USART_TX_vect){
uint8_t DmxState= gDmxState;
if (DmxState == BREAK){
UBRR0H  = 0;
UBRR0L  = (F_OSC/1600); //90.9kbaud
UDR0    = 0; //send break
gDmxState= STARTB;
}
else if (DmxState == STARTB){
UBRR0H  = 0;
UBRR0L  = ((F_OSC/4000)-1); //250kbaud
UDR0    = 0; //send start byte
gDmxState= DATA;
gCurDmxCh= 0;
}
else{
PORTB | (1<<PB1);
_delay_us(IBG);
uint16_t CurDmxCh= gCurDmxCh;
UDR0 = dmx_data[CurDmxCh++]; //send data
if (CurDmxCh == sizeof(dmx_data)) gDmxState= BREAK; //new break if all ch sent
else gCurDmxCh= CurDmxCh;
}
}

ISR(TIMER0_OVF_vect){
PORTB &= ~( (1<<PB2) | (1<<PB1) );
}

//MAIN
int main (void){
cli();
//TWI
PORTC |= (3<<PC4);
  TWAR = TWI_ADDRESS;
TWCR &= ~(1<<TWSTA)|(1<<TWSTO);
TWCR|= (1<<TWEA) | (1<<TWEN)|(1<<TWIE);
//DMX
DDRD |= (1<<2)|(1<<1);
PORTD |= (1<<2)|(1<<1); //enable transmission
UBRR0H  = 0;
UBRR0L  = ((F_OSC/4000)-1); //250kbaud, 8N2
UCSR0C |= (3<<UCSZ00)|(1<<USBS0);
UCSR0B |= (1<<TXEN0)|(1<<TXCIE0);
UDR0    = 0; //start USART
gDmxState= BREAK; //start with break
//TIMER0
TCCR0B = (1<<CS02) | (1<<CS00);
TIMSK0 |= (1<<TOIE0);
//TIMER1
TCCR1C = (1<<CS11);
  TIMSK1 |= (1<<TOIE1);
DDRB |= (1<<PB1) | (1<<PB0);
//TCNT1H , TCNT1L
//
sei();

while(1){
}
}

I know - the datasheet of that micro says that 16MHz operation not possible with 3,3V but it works absolutely the same as with lower clock rates.
Anyone an idea?
« Last Edit: November 12, 2016, 06:51:33 pm by jmibk »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf