PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Porterweiterung für ASURO über zweiten ATmega8 als I2C-Slave



Ronny10
13.02.2007, 20:00
In der letzten Zeit wird hier viel über den I2C-Bus und seine Einsatzmöglichkeiten gesprochen. Ich möchte euch als Beispiel eine Erweiterung mit einem als I2C-Slave programmierten ATmega8 vorstellen. Damit erweitert man den ASURO um 4 ADC-Kanäle und 16 digitale I/O's. Der Slave arbeitet mit dem Hardware-I2C-Interface (SDA an Pin27, SCL an Pin28 ).

Wie funktioniert das?
Als erstes muss man sich eine Art Befehlssatz ausdenken die der Master (ASURO) dem Slave schickt und die dieser dann ausführt.
Die header-Datei twi_register.h mit dem Befehlssatz:




#define sbi(port,bit) port|=(1<<(bit))
#define cbi(port,bit) port&=(~(1<<(bit)))
#define BIT(n) (1<<(n))

#define MAX_REG 255
#define FALSE 0
#define TRUE 1

/* ic2-slave-adresse */

#define TWI_ADR 0x80

/* set-befehle */

#define TWI_SET_DDRB 1 // definiere über das ddr eingänge und ausgänge von portb
#define TWI_SET_PORTB 2 // setze pull up widerstände an portb
#define TWI_SET_PINB 3 // setze ein einzelnes bit an portb
#define TWI_RES_PINB 4 // rücksetze ein einzelnes bit an portb

#define TWI_SET_DDRC 5 // wie portb
#define TWI_SET_PORTC 6
#define TWI_SET_PINC 7
#define TWI_RES_PINC 8

#define TWI_SET_DDRD 9 // wie portb
#define TWI_SET_PORTD 10
#define TWI_SET_PIND 11
#define TWI_RES_PIND 12

/* get-befehle */

#define TWI_GET_PINB 13 // lese das pinregister von portb
#define TWI_GET_PINC 14
#define TWI_GET_PIND 15


#define TWI_GET_ADC0 16 // lese adc0
#define TWI_GET_ADC1 17
#define TWI_GET_ADC2 18
#define TWI_GET_ADC3 19



Da gibt es Set- Res- und Get-Befehle. Set-Befehle zum setzen von Bits in den Datenrichtungs-Registern, von Ports oder einzelnen Port-Bits und natürlich auch zum zurücksetzen einzelner Bits (Res-Befehl). Mit den Get-Befehlen kann man den Zustand der Pinregister (digitale Eingänge) abfragen oder einen ADC-Kanal einlesen (2x8Bit) den man vorher mit einem Set-Befehl selektiert hat.
Ein Befehl besteht immer aus zwei Byte, dem Befehl und einem Parameter. Wenn ein Befehl keinen Parameter benötigt, z.B. TWI_GET_ADC0, müssen aber trotzdem zwei Byte gesendet werden. Dabei ist es egal welchen Wert der Parameter hat, da er ja nicht ausgewertet wird.

Das ist die I2C-Interrupt-Routine I2C_Slave.c für den ATmega8-Slave:



#include <avr/io.h>
#include <avr/interrupt.h>
#include <compat/twi.h>
#include "twi_register.h"

volatile unsigned char reg = MAX_REG;
volatile unsigned char twi_comm = FALSE;

void twi_init( unsigned char adr )
{
TWAR = adr;
TWCR = BIT(TWINT) | BIT(TWEN) | BIT(TWEA) | BIT(TWIE);
sei();
}

SIGNAL(SIG_2WIRE_SERIAL)
{
switch (TWSR) {

/* SLAVE RECEIVER */

case TW_SR_DATA_ACK:
case TW_SR_DATA_NACK:

if( reg < MAX_REG )
{
switch( reg )
{
case TWI_SET_DDRB: DDRB = TWDR;
break;
case TWI_SET_PORTB: PORTB = TWDR;
break;
case TWI_SET_PINB: sbi(PORTB, TWDR);
break;
case TWI_RES_PINB: cbi(PORTB, TWDR);
break;
case TWI_SET_DDRC: DDRC = TWDR;
break;
case TWI_SET_PORTC: PORTC = TWDR;
break;
case TWI_SET_PINC: sbi(PORTC, TWDR);
break;
case TWI_RES_PINC: cbi(PORTC, TWDR);
break;
case TWI_SET_DDRD: DDRD = TWDR;
break;
case TWI_SET_PORTD: PORTD = TWDR;
break;
case TWI_SET_PIND: sbi(PORTD, TWDR);
break;
case TWI_RES_PIND: cbi(PORTD, TWDR);
break;
case TWI_GET_ADC0: ADMUX = BIT(REFS0) + 0x00;
ADCSRA |= BIT(ADSC);
break;
case TWI_GET_ADC1: ADMUX = BIT(REFS0) + 0x01;
ADCSRA |= BIT(ADSC);
break;
case TWI_GET_ADC2: ADMUX = BIT(REFS0) + 0x02;
ADCSRA |= BIT(ADSC);
break;
case TWI_GET_ADC3: ADMUX = BIT(REFS0) + 0x03;
ADCSRA |= BIT(ADSC);
break;
}

reg = MAX_REG;
}
else
{
reg = TWDR;
twi_comm = reg;
}

TWCR = BIT(TWINT) | BIT(TWEN) | BIT(TWEA) | BIT(TWIE);
break;

/* SLAVE TRANSMITTER */

case TW_ST_SLA_ACK: // twi-adresse und read empfangen

switch( twi_comm )
{
case TWI_GET_PINB: TWDR = PINB;
break;
case TWI_GET_PINC: TWDR = PINC;
break;
case TWI_GET_PIND: TWDR = PIND;
break;
case TWI_GET_ADC0:
case TWI_GET_ADC1:
case TWI_GET_ADC2:
case TWI_GET_ADC3:
while (!(ADCSRA & BIT(ADIF)))
;
TWDR = ADCL;
break;
}

TWCR = BIT(TWINT) | BIT(TWEN) | BIT(TWEA) | BIT(TWIE); // transmit first byte
break;

case TW_ST_DATA_ACK: // erstes daten-byte des adc übertragen und ack empfangen
// sende jetzt das zweite daten-byte des adc

switch( twi_comm )
{
case TWI_GET_ADC0:
case TWI_GET_ADC1:
case TWI_GET_ADC2:
case TWI_GET_ADC3:
TWDR = ADCH;
}

TWCR = BIT(TWINT) | BIT(TWEN) | BIT(TWEA) | BIT(TWIE); // sende zweites byte
break;

default: TWCR = BIT(TWINT) | BIT(TWEN) | BIT(TWEA) | BIT(TWIE);
break;

}

}



Leider kommt das mit den Tabs beim kopieren nicht so 100%tig hin.

Das ist die Initialisierungsfunktion M8_I2C_Slave.c (main):


/*
autor: Peter Wilbert
version: 1.0
datum: 27.01.07

atmega8 als i2c-slave erweitert den asuro
um 4 adc's und 16 digitale i/o's
*/

#include <avr/io.h>
#include <avr/interrupt.h>
#include "twi_register.h"

extern void twi_init( unsigned char );

/* *** hauptprogramm ***

initialisiert den i2c-bus und verweilt dann in einer
endlosschleife. das eigentliche programm ist die twi-
interruptroutine in der die eingehenden slave-befehle
direkt ausgeführt werden
*/

int main(void)
{
/*
adc init
*/
ADCSRA = BIT(ADEN) + BIT(ADPS2) + BIT(ADPS1); // init adc prescale 64
ADMUX = BIT(REFS0) + 0x00; // adc einmal anstossen
ADCSRA |= BIT(ADSC); // start adc
while (!(ADCSRA & BIT(ADIF))) // warte bis ok
;
/*
twi initialisieren
*/
twi_init(TWI_ADR);

/*
for ever
*/
for(;;)
;

return(0);
}


Mit dieser Erweiterung sollte man erst einmal genügend I/O-Pins und ADC-Kanäle für Erweiterungen zur Verfügung haben! Den Befehlssatz kann man ja für andere Funktionen erweitern.

Peter (Ronny10)

raid_ox
13.02.2007, 20:04
Wow, danke für den Post!!!!

raid_ox
13.02.2007, 20:17
Hallo ROnny,

Was ist TWSR und TWDR?

Ronny10
14.02.2007, 07:01
Dazu solltest du dir unbedingt die Doku (PDF) zum ATmega8 von ATMEL durchlesen, da das TWI noch einige andere Register hat die man auch kennen muss!

TWDR = TWI Data Register
TWSR = TWI Status Register
...

Peter (Ronny10)

Ronny10
14.02.2007, 07:20
Ein Beispiel, wie die Master-Funktionen aussehen:




#include <avr/io.h>
#include "i2cmaster.h"
#include "twi_register.h"

/***************************************
sendet einen befehl plus parameter
zum m8-slave
****************************************/
void twi_send( unsigned char befehl, unsigned char parameter )
{
i2c_start_wait(TWI_ADR+I2C_WRITE); // verbindung aufbauen
i2c_write( befehl ); // befehl für den slave
i2c_write( parameter ); // parameter des befehls
i2c_stop(); // verbindung beenden
}

/**********************************************
liest ein port-pinregister vom m8-slave
***********************************************/
unsigned int twi_get( unsigned char befehl )
{
unsigned char c;

i2c_start_wait(TWI_ADR+I2C_WRITE); // verbindung aufbauen
i2c_write( befehl ); // befehl für den slave
i2c_write( 0 ); // befehl benötigt keinen parameter
i2c_rep_start(TWI_ADR+I2C_READ); // wiederhole start + read
c = i2c_readNak(); // read byte
i2c_stop(); // verbindung beenden
return(c);
}

/**********************************************
liest einen adc-kanal (2Byte) vom m8-slave
***********************************************/
unsigned int twi_get_adc( unsigned char befehl )
{
unsigned int i,j;

i2c_start_wait(TWI_ADR+I2C_WRITE); // verbindung aufbauen
i2c_write( befehl ); // befehl für den slave
i2c_write( 0 ); // befehl benötigt keinen parameter
i2c_rep_start(TWI_ADR+I2C_READ); // wiederhole start + read
i = (unsigned int)i2c_readAck(); // lower byte
j = (unsigned int)i2c_readNak(); // upper byte
i2c_stop(); // verbindung beenden

i += (j <<= 8);

return(i);
}



Für den Master benutze ich die Software i2cmaster.s von Peter Fleury's Homepage (man muss ja nicht immer das Rad neu erfinden).

http://jump.to/fleury

Peter (Ronny10)

raid_ox
14.02.2007, 07:24
Ok danke für den Tipps. Brauch man auch für den Slave ne 8MHz Resonator?

Danjo00
14.02.2007, 07:34
GEnau das was ich meinte zu meinem display und dem link den ich bei meinem thema gepostet habe (zimlich zum schluss).Befasse mich richtig damit heute mittag erst weil ich hatte nachtschicht und gehe jetzt schlafen.
THX Ronny

Ronny10
14.02.2007, 07:48
Ich betreibe meine ATmega8 Mikrocontroller immer mit 8MHz (auch beim ASURO) und dem internen Oszillator. Damit habe ich die beiden CLK-Pins frei und benötige keine externen Bauteile zur Takterzeugung. Um die Bits für die Configuration und Security zu verändern, benötigst du PonyProg2000 oder was ähnliches, ein ISP-Interface und ein entsprechendes Programmierkabel.

Der ATmega8 wird von ATMEL mit einer Werkseinstellung ausgeliefert: 1MHz CLk die mit dem internen Oszillator erzeugt wird! Wenn du einen "neuen" ATmega8 verwendest, muss du also erst die "Werkseinstellung" ändern, sonst arbeitet der ATmega8 nur mit 1MHz!

Peter (Ronny10)

raid_ox
14.02.2007, 07:50
Ja, ich benutze AVR dude mit myprogrammer. was soll man als füsebits setzen, damit man interne oszillator mit 8Mhz betreiben kann?

Ronny10
14.02.2007, 08:20
Bitte unbedingt zusätzlich die Doku dazu befragen! Wenn du eine falsche Einstellung vornimmst kann es sein, dass du deinen ATmega8 nicht mehr programmieren kannst!

Das ist die Werkseinstellung für die CLK-Bits des ATmega8 (1MHz + int. Osz.), wie er von ATMEL ausgeliefert wird:

CKSEL0 = 1
CKSEL1 = 0
CKSEL2 = 0
CKSEL3 = 0

Das ist die Einstellung für 8MHz CLK und internem Oszillator:

CKSEL0 = 0
CKSEL1 = 0
CKSEL2 = 1
CKSEL3 = 0

Peter (Ronny10)