I2C Master und Slave mit zwei ATMegas

Viele Module des GREETBoard Systems kommunizieren über den I2C-Bus (auch TWI genannt), jedoch gilt es zum aktuellen Zeitpunkt noch eine große Lücke zu schließen, da nicht alle GREETBoard-Module über eine solche Kommunikationsfähigkeit verfügen. Daher mache ich mir in diesem Artikel ein paar Gedanken, wie Bausteine ohne eigene I2C-Fähigkeit (beispielsweise der L6208) an den Bus angeschlossen werden können. Na ja, was heißt Gedanken, es wird eher praktisch als theoretisch 😀

Als Beispiel nehme ich mal das Modul GREETBoard L6208 Stepper, welches über einen Port des GEETBoard ATMega128 angesteuert wird. Da der L6208 selber nicht über die Fähigkeit der I2C-Bus-Kommunikation verfügt, muss ich den Umweg über den ATMega128 wählen. Es müssen also zwei Mikrokontroller über den Bus miteinander kommunizieren, wobei der eine die Master-Rolle übernimmt um dem Slave zu sagen, was er machen soll. In diesem Fall würde ich dem Slave mitteilen, dass der Stepper mit der Geschwindigkeit X in Richtung Y drehen soll. Es wäre aber auch möglich, dass der Slave den Stepper X Schritte in Richtung Y ansteuern soll. Der Slave empfängt diese Information und wandelt sie in Ansteuerungsimpulse an den L6208 um. Fertig ist die Kommunikation per I2C!

So gut wie es sich ließt, so Ahnungslos war ich zu Beginn dieser Aufgabe.  Dabei liegt die Betonung auf „gewesen“! 😉 Ich zeige nun, wie ich das Problem gelöst habe.

Pragmatischer Ansatz

Bei der Recherche nach Lösungen für meine Aufgabe, bin ich auf eine interessante Seite gestoßen. Diese zeigt, wie die Kommunikation mit nur einer zusätzlichen Bibliothek realisiert werden kann. Das hat mich sofort interessiert, da ich diesen Ansatz als sehr pragmatisch einschätze. Und das obwohl ich mich sträube fertige Lösungen zu nutzen und diese einfach als Bibliothek einzubinden. Ein bisschen Programmieren muss schon sein! Diesem Ansatz bin ich aber bereits bei der I2C-Bibliothek von Peter Fleury und der I2CLCD-Bibliothek untreu geworden. Scheinbar drücke ich mich um das Schreiben einer I2C-Kommunikation!!

Grundsätzliche Funktion

Die Bibliothek twislave.c/.h stellt die Kommunikation zwischen den beiden ATMegas her und erstellt eine Buffer-Variable mit dem Namen „i2cdata“ im Slave, in die empfangene Daten abgelegt werden. Damit funktioniert der Slave wie ein EEPROM der über I2C ansprechbar ist. Und es ist tatsächlich so einfach, wie es sich anhört.

Bei der Kommunikation wird zunächst die Bufferposition gesendet und dann die Daten, die dort geschrieben werden sollen. Mit dem Senden weiterer Daten wird die Bufferposition automatisch hochgezählt. Weitere Positionsangaben sind somit nicht nötig. Zumindest nicht, wenn es sich um direkt hintereinander liegende Bufferpositionen handelt.

Die twislave-Bibliothek (twislave.c und die twislave.h)

In den vorangegangenen Artikeln habe ich die Bibliothek twimaster.c von Peter Fleury benutzt um die I2C-Kommunikation zu ermöglichen. Daran wird sich auch diesmal nichts ändern, sodass ausschließlich der Master-ATMega Daten vom Slave sendet und empfängt.

Der Slave wiederum nutzt die Bibliotheks-Dateien twislave.c/.h um auf die Daten des Masters zu reagieren, diese zu empfangen und in seinem Buffer abzulegen. Auch die Anfrage von Daten durch den Master ist gewährleistet, indem die angeforderten Buffer-Daten über den Bus an den Master gesendet werden.

Dabei finde ich es sehr schön, dass die Bibliothek nur bei Bedarf aktiviert wird und das Hauptprogramm einfach den Buffer ausliest und auf Veränderungen reagiert. Im folgenden Bild versuche ich das darzustellen. Das Hauptprogramm des Slaves ließt die Buffervariable ein und schaltet diverse Ausgänge, je nach den entsprechenden Bit-Werten.

Kommunikation zwischen Master und Slave

Kommunikation zwischen Master und Slave

Der Ablauf des Versuchs

Um die Kommunikation, bzw. die Programme zu testen, nehme ich einfach zwei GREETBoard ATMega128 und verbinde die I2C-Anschlüsse mit einem 10-poligen Flachbandkabel. Die beiden Programme (im nächsten Kapitel werde ich diese erklären) sind jeweils aufgespielt. Dabei sind Master und Slave mit je drei Variablen (byte1-3 für den Master, i2cdata(0-2) für den Slave) „vorgeladen“. Durch LEDs soll der aktuell geladene Wert dargestellt werden. Das nächste Bild zeigt den Ablauf der Kommunikation. Im Falle, dass die Kommunikation nicht zu Stande kommt, dient die LED3 als Fehlermeldung.

Ablauf des Kommunikationstests

Ablauf des Kommunikationstests

Die Programme in kleinen Schritten

„I2C-Master“

Als Erstes widme ich mich dem Code für den Master und erkläre diesen Schritt für Schritt.

Den Beginn macht ein kleiner Kommentarbereich mit grundlegenden Hinweisen, gefolgt von der Angabe der benötigten Header-Dateien.

/* Name: GREETBoard ATMega128 - I2C Master
 Autor: Timo Gruß
 Quelle: http://timogruss.de

 I2C-Master-Routinen von Peter Fleury verwenden
 siehe: http://homepage.hispeed.ch/peterfleury/avr-software.html#libs */

// ######## HEADER DATEIEN
#include <avr/io.h>
#include "i2cmaster.h"

Nun wird die Adresse des Slaves und danach ein paar Beispieldaten (byte 1-3) definiert. Die Variable „byte2“ wird später im Hauptprogramm ausgewertet und verwendet um auf die vom Slave gelesenen Daten zu reagieren.

// ######## VARIABLEN & DEFINITIONEN
#define SLAVE_ADRESSE 0x50

uint8_t byte1 = 42;
uint8_t byte2 = 43;
uint8_t byte3 = 44;

Diese Routine startet die Kommunikation über die Schreib-Adresse des Slaves und kontrolliert ob dieser bereit ist. Wenn ja, dann folgt die Adresse an die in den Buffer des Slaves geschrieben werden soll und die drei zu schreibenden Bytes. Die Bufferadresse wird nicht jedesmal neu angegeben, da sie automatisch erhöht wird. Abschließend folgt der I2C-Stop-Befehl um die Kommunikation zu beenden.

Wichtig ist hier, dass die drei Bytes fest programmiert sind (Werte 42, 43 und 44). Natürlich können diese auch durch Variablen ersetzt werden, um die Daten, die an den Slave gesendet werden, flexibel zu halten. Für den Versuch reicht es aber, dass diese fest eingetragen sind.

// ######## FUNKTIONEN
// ========>> LESE SLAVE</pre>
<pre>void s_write (void){
  if(!(i2c_start(SLAVE_ADRESSE+I2C_WRITE))) // Slave ist bereit zum Schreiben
  {
    i2c_write(0x00);                        // Buffer Startadresse setzen
    i2c_write(42);                          // Drei Bytes schreiben...
    i2c_write(43);                          // ... 42-44
    i2c_write(44);
    i2c_stop();                             // Zugriff beenden
  }

Sollte der Slave nicht korrekt seine Bereitschaft melden, dann wird im ELSE-Bereich die LED 3 eingeschaltet und ein Fehler gemeldet.

  else                                      // Slave ist nicht bereit
  {
    PORTC &= ~( (1<<PC7));                  // Aktiviere Port C7 (LED 3)
  }
}

Die Lese-Routine ist ziemlich ähnlich aufgebaut und startet ebenfalls mit der Bereitschaftskontrolle des Slaves. Danach wird die Adresse gesendet ab der der Slave die Daten aus dem Buffer senden soll. Anschließend folgen die drei Lesevorgänge, gefolgt vom Stop-Befehl.

// ========>> BESCHREIBE SLAVE

void s_read (void) {
  if(!(i2c_start(SLAVE_ADRESSE+I2C_WRITE))) // Slave bereit zum Schreiben der Bufferadresse
  {
    i2c_write(0x00); // Buffer Startadresse zum Auslesen senden
    i2c_rep_start(SLAVE_ADRESSE+I2C_READ); // Lesen beginnen
    byte1= i2c_readAck(); // 1. Byte lesen...
    byte2= i2c_readAck(); // 2. Byte lesen und in "gelesen" ablegen
    byte3= i2c_readNak(); // letztes Byte lesen, darum kein ACK
    i2c_stop(); // Zugriff beenden
}

Und auch hier wird die LED 3 aktiviert, wenn die Lesebereitschaft nicht hergestellt werden konnte.

  else
  {
    PORTC &= ~( (1<<PC7));                  // Aktiviere Port C7 (LED 3)
  }
}

Das Hauptprogramm initiiert die I2C-Kommunikation und definiert die Pins C2-4 als Eingänge (inkl. Pull up-Aktivierung), sowie die Pins C5-7 als Ausgang.

// ######## HAUPTPROGRAMM
int main(void)
{
  i2c_init();                                 // Init des I2C interface

  DDRC &= ~(1 << DDC2);                       // PC2 = S1 = Eingang (+)
  PORTC |= _BV(2);                            // Pull Up an PC2 aktivieren
  DDRC &= ~(1 << DDC3);                       // PC3 = S2 = Eingang (+)
  PORTC |= _BV(3);                            // Pull Up an PC3 aktivieren
  DDRC &= ~(1 << DDC4);                       // PC4 = S3 = Eingang (+)
  PORTC |= _BV(4);                            // Pull Up an PC4 aktivieren

  DDRC = (1 << DDC5)|(1 << DDC6)|(1 << DDC7);  // PortC5-7 als Ausgang für LED1-3
  PORTC |= (1<<PC5)|(1<<PC6)|(1<<PC7);         // Deaktiviere Ports C5-7 (LEDs aus)

Schließlich wird in der Endlosschleife die Variable „byte2“ abgefragt. Um das Lesen und Schreiben zu simulieren wird dem Slave beim Start bereit der Wert 11 für die Variable „byte2“ zugewiesen (bzw. der Buffer-Position, die später die Daten liefert, die der Variable „byte2“ zugewiesen wird).  Der Slave wird daher den Wert 11 zurück geben und somit die LED 3 aktivieren. Das Lesen des Slaves wird mit der Taste S2 durchgeführt.

Mit einem Druck auf S1 schreibt der Master den Wert 43 an die Stelle der Variable „byte2“ und damit auch auf den Slave. Wird danach erneut der Lesevorgang ausgeführt, so wird der Master nun den Wert 43 aus dem Slave lesen und die LED 2 leuchtet auf.

  // ######## ENDLOSSCHLEIFE
  while(1){
    // ========>> AUF GELESENE DATEN REAGIEREN
    if (byte2== 43){           // Wenn Wert 43 gelesen wurde...
      PORTC &= ~( (1<<PC5));      // ...aktiviere Port C5 (LED 1)
    }

    if (byte2== 11){           // Wenn Wert 11 gelesen wurde...
      PORTC &= ~( (1<<PC7));      // ...aktiviere Port C7 (LED 3)
    }
    // ========>> TASTENEINGABEN
    if (! (PINC & (1<<PINC2)) ){  // Wenn S1 gedrückt...
      s_write();                  // ...Schreib-Funktion aufrufen
    }

    if (! (PINC & (1<<PINC3)) ){  // Wenn S2 gedrückt...
      s_read();                   // ...Lese-Funktion aufrufen
    }
  }
}

„I2C-Slave“

Auch das Programm des I2C-Sklaven beginnt mit ein paar Kommentaren und Hinweisen an die sich die Integration der Headerfiles anschließt. In Zeile 19 befindet sich der Eintrag für die „twislave.h“ Datei!

/* Name: GREETBoard ATMega128 - I2C Slave
Autor: Timo Gruß
Quelle: http://timogruss.de
Bemerkungen: Das Programm basiert auf dem RN-Wissen-Artikel "TWI Slave mit avr-gcc"

I2C-Master-Routinen von Peter Fleury
siehe: http://homepage.hispeed.ch/peterfleury/avr-software.html#libs

twislave-Header Dateien von Roboternetz
siehe: http://www.rn-wissen.de/index.php/TWI_Slave_mit_avr-gcc
Testprogramm für den Slave
Der Buffer wird mit Werten gefüllt, die vom I2C-Master verändert werden. Die Veränderung der
Daten wird über LEDs angezeigt. */

// ######## HEADER DATEIEN
#include <util/twi.h>       // enthält z.B. die Bezeichnungen für die Statuscodes in TWSR
#include <avr/interrupt.h>  // dient zur Behandlung der Interrupts
#include <stdint.h>         // Integer-Definitionen
#include "twislave.h"       // Einbinden der I2C-Slave-Kommunikation
#include <stdlib.h>         // nötig für Zahlumwandlung mit itoa

Wer sich bislang gefragt hat, wie die Slaveadresse definiert wird, findet hier die Antwort. Die Adresse 0x50 wird einfach als SLAVE_ADRESS definiert und in der twislave.h benutzt. Grundsätzlich ist die Adresse frei zwischen 0x00 und 0xFF wählbar, wobei aber darauf geachtet werden muss, dass es nicht zweimal die gleiche Adresse im Bus gibt. Das wird zu 100% Fehler verursachen.

// ######## VARIABLEN & DEFINITIONEN
#define SLAVE_ADRESSE 0x50  //Die Slave-Adresse

Das Hauptprogramm startet mit der Definition der LED-Ausgänge und initialisiert den Slave-Modus inklusive der vorab festgelegten Adresse. Anschließend wird der I2C-Buffer (i2cdata) mit Werten gefüllt. Dazu startet eine For…Next-Schleife, die von Position 0 bis zur maximalen I2C-Buffergröße hochzählt (i) und einen Wert von 10+i an die entsprechende Bufferadresse schreibt (Zeile 34). Damit sind die ersten drei Bufferadressen mit 10, 11 und 12 gefüllt. Die Buffergröße wird in der twislave.h eingestellt und hat den Standardwert von 254. Das heißt, dass 255 verschiedene Bytes in „i2cdata“ abgelegt werden können.

// ######## HAUPTPROGRAMM
int main (void)
{
  DDRC = (1 << DDC5)|(1 << DDC3)|(1 << DDC4);  // PortC5-7 als Ausgang für LEDs

  init_twi_slave(SLAVE_ADRESSE);               // TWI als Slave mit Adresse slaveadr starten

  for(uint8_t i=0;i<i2c_buffer_size;i++)       // i2cdata mit Werten füllen, die der Master
    {                                          // auslesen und ändern kann.
      i2cdata[i]=10+i;                         // Beschreiben von i2cdata
    }

Da die I2C-Kommunikation im Hintergrund über die twislave.h abläuft, ist die Endlosschleife frei für die Reaktion auf eine Änderung des I2C-Buffers. So wird die LED1 eingeschaltet, solange der Wert in i2cdata(1) = 11 ist – also den Startwert aufweist. Ändert sich dieser zu 43 (nach dem Schreiben des Masters) schaltet sich LED1 ab und LED2 leuchtet.

  // ######## ENDLOSSCHLEIFE
  while(1)
  {
    if (i2cdata[1] == 11) {                    // Wenn 11 in i2cdata[1]
      PORTC |= (1<<PC3);                       // Aktiviere LED1
      PORTC &= ~(1<<PC4);                      // Deaktiviere LED2
    }                                          // Zustand vor dem Beschreiben des Masters

    if (i2cdata[1] == 43) {                    // Wenn 43 in i2cdata[1]
      PORTC |= (1<<PC4);                       // Aktiviere LED2
      PORTC &= ~(1<<PC3);                      // Deaktiviere LED1
    }                                          // Zustand nach dem Beschreiben des Masters
  }
}

 

Die beiden Programme gibt es weiter unten als Download.

Die Programme zum Download

Download_ButtonBitte klickt auf das Icon um die Programme zu laden.

In der Zip-Datei sind die beiden benötigten C-Dateien hinterlegt.

Viel Spaß beim Anwenden 😉

 

Fazit

Durch die Nutzung der i2cslave-Bibliothek, ist es sehr einfach möglich eine Kommunikation zwischen verschiedenen Mikrocontroller zu ermöglichen.  Dabei übernimmt einer der Controller die Master-Rolle, alle anderen erhalten den Status eines Slaves. In diesem Artikel habe ich die grundsätzliche Vorgehensweise aufgezeigt und einfache Beispiele verwendet. Natürlich ist es möglich, weitaus komplexere Informationen, Byte für Byte, zu versenden um darauf zu reagieren.

Mit dieser neuen Erfahrung bin ich meinem Ziel, das GREETBoard L6208 Stepper I2C-tauglich zu machen, ein großes Stück näher gekommen. Da die ATMegas 32-128 recht viel Platz in Anspruch nehmen und nicht auf ein 1/4 Modul passen, werde ich mich als nächstes mit der I2C-Kommunikation mit einem ATTiny2313 auseinandersetzen. Der ist deutlich kleiner und beherrscht alle benötigten Funktionen. Doch jetzt freue ich mich erstmal, dass sich die beiden ATMegas gut miteinander vertragen 😉

Alles Gute, Timo

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.