Forum: Projekte & Code TWI-Master für ATMEGA in C (ohne TWI-Start/Stop, ACK/NACK)


von Michael S. (Gast)


Angehängte Dateien:

Lesenswert?

TWI-Master für ATMEGA in C (ohne TWI-Start/Stop, ACK/NACK)

Ok, der Betreff soll provozieren.
Natürlich ist ein Betrieb des TWI-Busses ohne TWI-Start/Stop-Condition 
nicht möglich. Und auch das ACK/NACK muss ausgewertet werden.

Die Frage ist aber, ob der Anwender diesen Verwaltungskram wirklich 
selbst regeln muss.

Der Einsatz des TWI-Busses könnte benutzerfreundlicher sein als es in 
manchen Bibliotheken der Fall ist.
Selbst die Arduino-Fraktion quält die geneigte Anwenderschaft mit
- wire.beginn();
- wire.beginTransmission(0x40);
- wire:send('A');
- wire.send('B');
- wire:endTransmission();

Dabei kann man den Beispielcode aus der AVR315 von ATMEL nehmen, einige 
kleine Ergänzungen vornehmen - und die Arbeit läuft fast von selbst.

Als Beispiel:
Um ein "0x55" an einen PCF8574 mit der TWI-Adresse "0x40" zu senden:

twi_buffer[0] = 0x40;
twi_buffer[1] = 0x55;
TWI_MA_write(twi_buffer, 2);

Um den Status des PCF8574 anschließend zur Kontrolle wieder auszulesen:

TWI_MA_read(twi_buffer, 2);

In twi_buffer[1] steht dann der Status der PortPins des PCF8574.

Ein anderes Beispiel:
Um 16 Byte ab der 16-Bit Adresse "eeprom_adr" aus einem Eeprom mit der 
TWI-Adresse 162 auslesen:

twi_buffer[0] = 162;
twi_buffer[1] = (uint8_t) (eeprom_adr >> 8);
twi_buffer[2] = (uint8_t) (eeprom_adr);

TWI_MA_write(twi_buffer, 3);
TWI_MA_read(twi_buffer, 17);

Die Daten stehen nun in twi_buffer[1] .. [17].

Wie man sieht:
Das umständliche Hantieren mit TWI-Start, TWI-Stop, das Abfragen bzw. 
Setzen von ACK's und NACK's, das Setzen von Flags und von Read/Write 
Bits ist nicht wirklich notwendig - zumindest nicht durch den Anwender.

Er muss lediglich beachten:
- Einmalige zu Beginn des Programmablaufes muss das TWI-Modul
  initialisiert werden.
- Die zu sendenden/empfangenen Daten werden in einem Array übergeben.
- Das erste Byte des Arrays (Index 0) muss die Adresse des Slave
  beinhalten!
- Die zu sendenden/empfangenen Daten stehen im Array immer beginnend
  an Index 1!
- Bei der Anzahl der zu sendenden/empfangenden Daten muss die Adresse
  mitgezählt werden!

Dass die TWI-Adresse an Position 0 innerhalb des Arrays steht, das sieht 
im ersten Moment befremdlich aus.
Hat aber den enormen Vorteil, dass die TWI-Adresse den Aufruf eines 
Read/Write überlebt und mehrfach nacheinander verwendet werden kann:
Häufig schreibt man eine Adresse auf einen Slave und liest anschließend 
Daten aus - die TWI-Adresse in Byte[0] muss dabei nicht neu ins Array 
geschrieben werden.

Achtung, die 7-Bit TWI-Adresse wird links ausgerichtet erwartet!
Das Read/Write-Bit (das Bit.0) kann völlig ignoriert werden, das 
TWI-Modul wählt die erforderlichen Einstellungen ohne Zutun von aussen!

Ein weiterer Vorteil dieser "Bibliothek":
Das TWI-Modul arbeitet interruptgesteuert.
Sobald die zu sendenden Daten an das Modul übergeben sind, kann das 
Hauptprogramm weiterarbeiten, das Versenden der Daten erfolgt im 
Hintergrund.
Zu beachten ist, dass die Interrupts freigegeben sein müssen!

Ein verschmerzbarer Nachteil dieser Lösung:
Innerhalb des TWI-Moduls muss zur Pufferung der Daten ein zusätzliches, 
lokales Array zur Verfügung stehen, das so groß ist wie die maximal zu 
versendende Nachricht + 1 (für die TWI-Adresse).


mfg

Michael S.

von Horst H. (horha)


Lesenswert?

Hallo,

schoene Sache das!
Ein kleiner Vorschlag.
Waere es nicht einleuchtender, twi_buffer[0] direkt mit einer Funktion 
als TWI_Adress== TWI_MA_buf[0] zuzuweisen und dabei direkt passend zu 
schieben.
1
TWI_MA_SetAdress( uint8_t adr){
2
  TWI_MA_buf[0] = adr <<1;}
Den TWI-Puffer ab 1 wird ohnehin so umkopiert.
TWI_MA_write(twi_buffer, 2); statt ,3
TWI_MA_read(twi_buffer, 16);statt ,17 ist einfach weniger 
fehlertraechtig, wenn man 16 Byte abholen will.
Das genau passiert doch auch in Deinen read und write Prozeduren durch 
zwei leichte Aenderungen:
1
void TWI_MA_write( uint8_t *msg, uint8_t msgSize ){
2
  uint8_t i;
3
4
  while ( TWI_BUSY );                         // Wait until TWI is ready for next transmission.
5
6
->TWI_msgSize = msgSize+1;                     // Number of data to transmit.
7
->TWI_MA_buf[0] &= 0xFE;                       // Store slave address with Write-Bit setting.
8
                                                // Die Daten umkopieren nach TWI_MA_buf[]
9
  for ( i = 1; i < msgSize; i++ ) TWI_MA_buf[i] = msg[i];
10
11
  TWI_success = FALSE;
12
  TWI_state   = TWI_NO_STATE ;
13
  TWCR = (1<<TWEN)|                            // TWI Interface enabled.
14
         (1<<TWIE)|(1<<TWINT)|                 // Enable TWI Interupt and clear the flag.
15
         (0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)|      // Initiate a START condition.
16
         (0<<TWWC);}
Analog fuer den read Befehl.

von Michael S. (Gast)


Angehängte Dateien:

Lesenswert?

TWI-Slave für ATMEGA in C

Zur Vervollständigung folgt hier noch die Beschreibung zum Pendant des 
TWI-Masters, dem TWI-Slave.

Dieses Programm ist auf der Grundlage der AppNote AVR311 entstanden.

Das TWI-Slave-Modul arbeitet vollständig interruptgesteuert im 
Hintergrund - unabhängig und unsichtbar für die Hauptanwendung.

Es verfügt über 2 unabhängige Puffer - jeweils für Daten, die der Master 
sich abholen kann und für Daten, die der Master an den Slave sendet.

Wenn der Master Daten an den Slave sendet, dann beendet er die 
Transaktion mit einer Stop-Condition.
Wird diese vom Slave erkannt, dann schaltet er den Empfang ab (das Flag 
TWIE wird gelöscht).

Will main() wissen, ob Daten empfangen wurden, dann muss es eine Abfrage 
losschicken.

if (TWI_SLA_Rx_Cnt()) ....

Ist das Flag TWIE gelöscht, dann wird die Anzahl der empfangenen Bytes 
zurückgegeben.
Main() muss nun die Daten aus dem Empfangspuffer abholen, danach wird 
automatisch wieder die Empfangsbereitschaft des TWI-Moduls hergestellt.

Der schematische Programmablauf sieht so aus:

TWI_SLA_Init();
asm volatile("sei");

while(1)
{
  if ((i=TWI_SLA_Rx_Cnt()))
  {
    TWI_SLA_Get_Data(twi_buffer);
    /*
    Die empfangenen Daten stehen nun in twi_buffer
    es sind genau (i-1) Nutzbytes
    twi_buffer[0] ist die verwendete TWI-Slave-Adresse
    */
  }
  /*
  tu sonst noch was
  */
}

Wie beim TWI-Master liefert das erste Byte des Empfangsarrays die 
TWI-Adresse.
In diesem Falle die TWI-Adresse, unter der der Slave angesprochen wurde 
(Die ATMegas können so konfiguriert werden, dass sie auf mehreren 
Adressen lauschen).

Wenn der Master vom Slave Daten abholen will, müssen diese vorher auf 
dem Slave bereitgestellt werden.
Dazu werden die Daten in ein Array verpackt und auf das TWI-Modul 
kopiert:

TWI_SLA_Put_Data(twi_buffer, anzahl_der_bytes);

Alternativ kann der Pointer auf den Sendepuffer des TWI-Moduls auf eine 
beliebige Speicherstelle verbogen werden (sinnigerweise das erste 
Element eines Arrays).

TWI_SLA_Set_TxPtr(&test_buffer[0]);

Holt der Master nun Daten ab, dann werden die aus dem Array 
test_buffer[] geliefert.

Diese Methode erspart das Umkopieren und reduziert den Speicherbedarf 
(weil der Tx_Buffer im TWI_Slave-Modul reduziert werden kann).
Ausserdem kann man auf diesem Wege (zum Debuggen) Speicherinhalte des 
Slave auslesen - gelegentlich ist das ganz hilfreich.


mfg

Michael S.

von Michael S. (Gast)


Lesenswert?

Hallo Horst,

Dein Einwand ist richtig.
Aber über das Problem habe ich natürlich auch nachgedacht.

Am Ende habe ich mich entschieden, diese Eigenart bei der 
Hardware-TWI-Schnittstelle beizubehalten
(sie hat ja auch Einzug in den TWI-Slave gehalten).
Ich will aber gerne noch einmal prüfen, ob meine Entscheidung 
tatsächlich richtig war.

Bei der Software-TWI habe ich es übrigens so gelöst, wie von dir 
vorgeschlagen.

Da Du im Besitz des Source-Codes bist, kannst Du das Programm leicht auf 
Deine Denkweise umstricken.

Genauso das habe ich ja mit dem Programm-Code der AppNotes auch gemacht.

mfg

Michael S.

von Michael S. (Gast)


Lesenswert?

Hallo Horst,

über Deinen Hinweis habe ich noch einmal in Ruhe nachgedacht.

Angenommen, wir folgen Deinem Vorschlag.
Dann wird zwar die Parameterübergabe logisch, aber beim 
Beschreiben/Auslesen der Daten gibt's dafür ein neues Problem:

twi_buffer[0] = TWI_ADR;
twi_buffer[1] = data_A;
twi_buffer[2] = data_B;

TWI_MA_write(twi_buffer, 2);  // klingt ist das logisch nach
                              // der Variablenzuweisung davor ?
TWI_MA_read(twi_buffer, 2);

data_A = twi_buffer[1];      // ist das logisch ?
data_B = tei_buffer[2];

Also auch hier sind wieder reichlich Fettnäpfchen verteilt, durch die 
man waten kann.

Deswegen habe ich mich entschieden, die Parameter genau zur 
Datenstruktur passend zu wählen.
Das ist dann zumindest in der Sache logisch - und nachvollziehbar, 
sofern man die Datenstruktur versteht.

Es gäbe allerdings noch einen anderen Weg, den Funktionsaufruf 
plausibler zu machen.
Nämlich die TWI-Adr als zusätzlichen Parameter zu übergeben:

TWI_MA_write(twi_adr, twi_buffer, bytes);
TWI_MA_read(twi_adr, twi_buffer, bytes);

Dafür erkauft man sich diese Nachteile:
- die zusätzliche Parameterübergabe kostet einige Nanosekunden und
  einige Bytes,
- die TWI_Adresse geht verloren und muss bei jedem Funktionsaufruf
  neu gesetzt werden - das wäre unschön.

Da wir mit einem Microcontroller arbeiten, muss manchmal die 
Gratwanderung zwischen Ökonomie vor Bequemlichkeit gewagt werden.
Meine persönliche Entscheidung war hier die zugunsten der Ökonomie.


Michael S.

von Horst H. (horha)


Lesenswert?

Hallo,

ich kamm nur auf diese Idee, weil Du die Daten ohnehin umkopierst, um 
sie per Interrupt überhaupt unmanipuliert senden zu können.
Es wird vom Hauptprogramm nie direkt auf TWI_MA_buf zugegriffen, wenn 
man read oder write benutzt.
// Die Daten umkopieren nach TWI_MA_buf
Oh, falscher Fehler, ich habe mit main.c garnicht angesehen...
Du willst also den zweiten Puffer sparen
( OT , kopierst aber trotzdem? Gute Güte mir zerrinnen die Nanosekunden. 
OK, ich tippe langsamer. ;-) )

main.c
1
while(1)
2
   {
3
      twi_buffer[0] = 0x40;                 // Standardadresse PCF8574
4
      twi_buffer[1] = i++;
5
      TWI_MA_write(twi_buffer, 2);
6
      _delay_ms(100);
7
   }
Ohne Delay ausreichender Größe kann das schiefgehen.
Vielleicht lieber so in der Art:
1
while(1)
2
   {
3
      twi_buffer[0] = 0x40;                 // Standardadresse PCF8574
4
      twi_buffer[1] = i++;
5
      TWI_MA_write(twi_buffer, 2);
6
      while ( TWI_BUSY )
7
        {
8
        _delay_ms(1);
9
        }
10
   }

von Michael S. (Gast)


Lesenswert?

Horst Hahn schrieb:


> while(1)
>    {
>       twi_buffer[0] = 0x40;                 // Standardadresse PCF8574
>       twi_buffer[1] = i++;
>       TWI_MA_write(twi_buffer, 2);
>       while ( TWI_BUSY )
>         {
>         _delay_ms(1);
>         }
>    }


Die Warteschleife ist überflüssig, denn in TWI_MA_read() und in 
TWI_MA_write() steht am Anfang:

while ( TWI_BUSY );

Hier wird gewartet, bis die letzte Transaktion abgeschlossen ist.
Muss ja auch, ansonsten würde der Datenbereich ja überschrieben.

mfg

Michael S.

von Horst H. (horha)


Lesenswert?

Hallo,

Der Datenbereich wird doch überschrieben.
Vielleicht wird es besser sichtbar, wenn ich das delay weglasse.
Es ist twi_buffer[1] = 0 zu Beginn.

>twi_buffer[0] = 0x40;                 // Standardadresse PCF8574
>>                                      // Im Sinne des Erfinders nur einmal
>>                                         vor der Schleife
>while(1)
>    {
>       twi_buffer[1] = i++;
>       TWI_MA_write(twi_buffer, 2);
>    }
dann wäre beim ersten Durchhlauf twi_buffer[1] = 1, wenn 
TWI_MA_write(twi_buffer, 2) aufgerufen wird.

TWI beginnt mit der Ausgabe per Interupt, sozusagen nonblocking.

Die Schleife saust weiter und ändert twi_buffer[1] in 2, bevor noch die 
Adresse auf dem I2C Bus ausgeben ist.
Das bedeutet, die 1 ist nicht auf TWI-Bus gesendet worden wäre.
Es ist richtig, das jetzt TWI_MA_write geduldig auf den Abschluss der 
zuvor begonnenen Übertragung wartet, aber es hat nicht mitbekommen, das 
sich twi_buffer[1] inzwischen geändert hat.
Bei längeren Übertragungen mit einem I2C-Eeprom wäre das fatal.

Deshalb empfand ich den Anschein, twi_buffer praktisch gekapselt, also 
ohne äußeren Zugriff zu verwenden, indem man msg erzeugt und dann in 
write erst kopiert, als den richtigen Ansatz.

Es ist vielleicht eine Betrachtung, die in der Praxis gar nicht 
vorkommt, weil man seine Sende und Empfangsdaten erst ändert, wenn die 
Übertragung auch fertig ist, aber dann hätte man sich das eingebaute 
kopieren von msg sparen können und dies nur bei Bedarf selbst getan.

von Patrick N. (emerand)


Lesenswert?

Hallo michael!!

Ich habe nach dem TWI_Master Routinen von AVR315 geschaut als ich auf 
deine vereinfachte version gestoßen bin. sehr hilfreich was du da 
gemacht hast.

Aber ich hätte eine kleine frage dazu.
Ich benuzte ein Atmega8. Ich möchte Die daten (so 300 Bytes)  die ich 
vorher ins eeprom des Atmega8 gesichert habe über TWI weiterleiten.

kann ich dafür dieselbe main funktion wie du benutzen? so in der art:
1
unsigned char eeprom_read (unsigned short address) {
2
  while(EECR & (1<<EEWE));
3
    EEAR = address;
4
  EECR |= (1<<EERE);
5
  return EEDR;
6
}
7
8
int main(void)
9
{
10
   uint16_t address;
11
   uint8_t data = eeprom_read (address);
12
   uint8_t twi_buffer[300+1];     // 300 Bytes Daten + 1 Byte Adresse
13
14
   TWI_MA_init();                 // Die TWI_interface initialisieren
15
   sei();                         // Interrupts freigeben
16
17
   while(1)
18
   {
19
      twi_buffer[0] = 0x40;      // TWI slave adresse
20
      twi_buffer[1] = data++;
21
22
      TWI_MA_write(twi_buffer, 2);
23
      _delay_ms(100);              
24
   }
25
}

wenn nein!! wie soll dann mein code sonst aussehen?
Ich wäre euch für jede hilfe dankbar.
Viele Grüße

von Michael S. (Gast)


Lesenswert?

Hallo Patrick,

wenn ich Dein Problem richtig verstanden habe, dann möchtest Du 300 Byte 
(ob aus dem Eeprom oder woher sonst ist gleichgültig) via TWI an einen 
Slave senden.

Wenn Du das in einer einzigen Sendung realisieren willst, dann müssten 
auf allen beteiligten Geräten die Puffer entsprechend groß dimensioniert 
sein!

Du musst dann die 301 Byte in das Sendearray packen und an das TWI-Modul 
übertragen.
Benötigt also schon 602 Byte auf dem Master ( weil das TWI-Modul die 
Daten ja zwischenspeichert ).

Machbar sollte das ein, aber dieser Weg ist wenig (Speicher-) effizient.

Ich würde die Daten in kleine Häppchen aufteilen und nacheinander 
versenden.
Jedem Paket vorangestellt wird eine Packet-Nr.
Der Empfänger sortiert die Daten dann an das vorgesehene Ziel.

Aber viele Wege führen nach Rom ...

mfg

Michael S.

von Michael S. (Gast)


Lesenswert?

Hallo Horst,

das Problem, das Du beschreibst, kann (besser: sollte) nicht sein.

Denn die Routine TWI_MA_Read() schreibt zunächst die TWI-Adresse des 
Slave auf den Bus, wartet dann in einem while( TWI_BUSY ); bis die 
Übertragung abgeschlossen (TWIE gelöscht) und die STOP-Condition auf den 
Bus geschrieben ist (!(TWSTO)) und beginnt erst dann mit dem Lesen der 
Daten.

Genau dieselbe Abfrage steht auch vor einem TWI_MA_Write().

Möglicherweise beobachtest Du Seiteneffekte?
Vielleicht siehst Du die "1" gar nicht, weil sie bereits von der "2" 
überschrieben wurde ?

Sieh Dir mal die Daten mit einem Logicanalyser an, der offenbart den 
wahren Verlauf der Kommunikation.

mfg

Michael S.

von Michael S. (Gast)


Angehängte Dateien:

Lesenswert?

Hallo Horst,

um zu klären, ob womöglich doch noch irgendwo ein Denkfehler steckt, 
habe ich eine Schleife getestet:

for(i =0; i<5;i++)
   {
   twi_buffer[0] = 0x40;
   twi_buffer[1] = i;

   TWI_MA_Write( twi_buffer, 2);
   }

while(1);

Das Signal/die Kommunikation kann man im Screedump nachvollziehen.

Was meinst Du dazu ?


mfg

Michael S.

von Michael S. (Gast)


Angehängte Dateien:

Lesenswert?

USI-Slave für ATTINY in C

Nur die "größeren" Controller verfügen über eine 
Hardware-TWI-Schnittstelle.
Aber auch auf den ATtiny's lässt sich eine TWI-Schnittstelle einrichten.
Anstelle der TWI-Hardware wird hier das USI-Modul eingesetzt.

Die nachfolgenden Routinen für den USI-Slave funktionieren aus Sicht des 
Anwenders nach der gleichen Methode wie der TWI-Slave.

Das USI-Slave-Modul arbeitet wieder vollständig interruptgesteuert im 
Hintergrund - unabhängig und unsichtbar für die Hauptanwendung.

Es verfügt über 2 unabhängige Puffer - jeweils für Daten, die der Master 
sich abholen kann und für Daten, die der Master an den Slave sendet.

Wenn der Master Daten an den Slave sendet, dann beendet er die 
Transaktion mit einer Stop-Condition.
Diese vom USI-Slave als Zeichen für den Abschluss einer Übertragung 
genutzt.
(Im Gegensatz zum TWI-Slave bleibt der USI-Slave z.Z. weiterhin 
empfangsbereit.
Folgt eine neue Übertragung, bevor die Daten abgeholt sind, dann wird 
der Rx_Puffer überschrieben.)

Will main() wissen, ob Daten eingegangen sind, dann muss es eine Abfrage 
losschicken.

if (USI_SLA_Rx_Cnt()) ....

Diese Funktion liefert die Anzahl der empfangenen Bytes (+1) zurück
- oder NULL, wenn keine neuen Daten vorliegen.
Sofern Daten eingegangen sind, muss main() sie aus dem Empfangspuffer 
abholen.

Der schematische Programmablauf sieht so aus:

USI_SLA_Init();
asm volatile("sei");

while(1)
{
  if ((i=USI_SLA_Rx_Cnt()))
  {
    USI_SLA_Get_Data(twi_buffer);
    /*
    Die empfangenen Daten stehen nun in twi_buffer,
    es sind genau (i-1) Nutzbytes
    twi_buffer[0] ist die verwendete USI-Slave-Adresse
    */
  }
  /*
  tu sonst noch was
  */
}

Wie bei den TWI-Routinen liefert das erste Byte des Empfangsarrays die 
TWI-Adresse.
(Die Interrupt-Routine könnte man mit geringem Aufwand so ändern, dass 
auch der USI-Slave auf mehrere Adressen reagiert.)

Wenn der Master vom Slave Daten abholen will - oder soll, müssen diese 
vorher auf dem Slave bereitgestellt werden.
Dazu werden die Daten in ein Array verpackt (ohne dass eine TWI-Adresse 
vorangestellt wird !)  und auf das TWI-Modul kopiert :

USI_SLA_Put_Data(twi_buffer, anzahl_der_bytes);

Die Alterative (Pointer auf einen beliebigen Speicherbereich setzen), 
wie sie beim TWI-Slave realisiert ist, habe ich hier erst einmal 
entfallen lassen.

Die Funktionen sind aus dem Beispielcode zur AppNote AVR 312 entwickelt.


Jetzt fehlt nur noch der USI_Master ....


mfg

Michael S.

von Michael S. (Gast)


Angehängte Dateien:

Lesenswert?

USI-Master für ATTINY in C (ohne TWI-Start/Stop, ACK/NACK)

Wenngleich eher selten benötigt, hier zum Abschluss das letzte Teil im 
Puzzle: der USI-Master.

Das Handling ist aus Sicht des Anwenders identisch mit dem des 
TWI-Masters.

Als Beispiel:
Um ein "0x55" an einen PCF8574 mit der TWI-Adresse "0x40" zu senden:

twi_buffer[0] = 0x40;
twi_buffer[1] = 0x55;
USI_MA_write(twi_buffer, 2);

Um den Status des PCF8574 anschließend zur Kontrolle wieder auszulesen:

USI_MA_read(twi_buffer, 2);

In twi_buffer[1] steht dann der Status der PortPins des PCF8574.

Ein anderes Beispiel:
Um 16 Byte ab der 16-Bit Adresse "eeprom_adr" aus einem Eeprom mit der 
TWI-Adresse 162 auslesen:

twi_buffer[0] = 162;
twi_buffer[1] = (uint8_t) (eeprom_adr >> 8);
twi_buffer[2] = (uint8_t) (eeprom_adr);

USI_MA_write(twi_buffer, 3);
USI_MA_read(twi_buffer, 17);

Die Daten stehen nun in twi_buffer[1] .. [17].

Zu beachten ist wieder:
- Einmalige zu Beginn des Programmablaufes muss das USI-Modul
  initialisiert werden.
- Die zu sendenden/empfangenen Daten werden in einem Array übergeben.
- Das erste Byte des Arrays (Index 0) muss die Adresse des Slave
  beinhalten!
- Die zu sendenden/empfangenen Daten stehen im Array immer beginnend
  an Index 1!
- Bei der Anzahl der zu sendenden/empfangenden Daten muss die Adresse
  mitgezählt werden!

Dass die TWI-Adresse an Position 0 innerhalb des Arrays steht, das sieht 
im ersten Moment befremdlich aus.
Hat aber den enormen Vorteil, dass die TWI-Adresse den Aufruf eines 
Read/Write überlebt und mehrfach nacheinander verwendet werden kann:
Häufig schreibt man eine Adresse auf einen Slave und liest anschließend 
Daten aus - die TWI-Adresse in Byte[0] muss dabei nicht neu ins Array 
geschrieben werden.

Achtung, die 7-Bit TWI-Adresse wird links ausgerichtet erwartet!
Das Read/Write-Bit (das Bit.0) kann völlig ignoriert werden, das 
TWI-Modul wählt die erforderlichen Einstellungen ohne Zutun von selbst!

Programmintern gibt es wesentliche Unterschiede zwischen der 
Arbeitsweise des TWI-Masters und der des USI-Masters:

Der USI-Master arbeitet nicht interruptgesteuert und die zu sendenden 
Daten werden nicht ins USI-Modul kopiert.
Das Programm schiebt eigenhändig Bit für Bit auf die SDA-Leitung, 
erzeugt das Clocksignal auf SCL, setzt START- und STOP Conditions und 
prüft die ACKs und NACKs der Gegenstelle.

Aber all das wollen wir im Detail eigentlich alles garnicht wissen.


Der Programmcode ist mit nur geringfügigen Ergänzungen aus dem 
Beispielcode zur AppNote AVR312 von ATMEL übernommen.

Mit dem USI-Master ist das Quartett endlich komplett:
Es gibt den Master und den Slave jeweils auf Basis von Hardware-TWI für 
die ATMEGAs bzw. Software-USI für die ATTINYs - mit einheitlicher 
Bedienung und ohne die Notwendigkeit, eigenhändig Start- Stop- 
Bedingungen setzen oder ACKs und NACKs prüfen oder setzen zu müssen.

Die Bedienung beschränkt sich (nach der Initialisierung) auf folgende 
Funktionen:

Bei einem Master:
- MA_Write(Daten_Array, Anzahl_Bytes)   Daten auf den Slave schreiben
- MA_Read(Daten_Array, Anzahl_Bytes)    Daten vom Slave lesen

Bei einem Slave:
- SLA_Rx_Cnt()              Prüfen, ob Daten empfangen wurden
- SLA_Get_Data(Daten_Array) Wenn ja, dann die Daten abholen
                            Daten für den Master bereitstellen
- SLA_Put_Data(Daten_Array, Anzahl_Bytes)


TWI kann ja so einfach sein ...

mfg

Michael S.

von Patrick N. (emerand)


Lesenswert?

Hallo Stephan!

> wenn ich Dein Problem richtig verstanden habe, dann möchtest Du 300 Byte
> (ob aus dem Eeprom oder woher sonst ist gleichgültig) via TWI an einen
> Slave senden.

Ja genau das ist mein problem

> Du musst dann die 301 Byte in das Sendearray packen und an das TWI-Modul
> übertragen.

meinst du so in der art?
1
int main(void)
2
{
3
   uint16_t address;
4
   uint8_t data [200];
5
   uint8_t twi_buffer[300+1];     // 300 Bytes Daten + 1 Byte Adresse
6
7
   TWI_MA_init();                 // Die TWI_interface initialisieren
8
   sei();                         // Interrupts freigeben
9
10
   while(1)
11
   {
12
      twi_buffer[0] = 0x40;      // TWI slave adresse
13
      twi_buffer[1] = data[0];
14
      twi_buffer[2] = data[1];
15
      twi_buffer[3] = data[2];
16
           .
17
           .// kann natürlich auch in eine schleife zusammengefasst werden 
18
           .
19
      twi_buffer[301] = data[299];   
20
  
21
22
      TWI_MA_write(twi_buffer, 301);
23
      _delay_ms(100);              
24
   }
25
}
> Benötigt also schon 602 Byte auf dem Master ( weil das TWI-Modul die
> Daten ja zwischenspeichert ).

Ich glaube das Atmega8 hätte genug speicher oder ?

Danke
Mfg

von Michael S. (Gast)


Lesenswert?

Hallo Patrick,

grundsätzlich kann das so funktionieren.

Ein kleines (behebbares) Problem besteht aber dennoch.
Die Indizes sind nur als uint8_t definiert.

Da Du aber 300 Byte übertragen willst, reicht das nicht aus.

Und - wie schon gesagt - die Methode ist auch reichlich ineffizient (aus 
Sicht der Speichernutzung).

Folgender Weg benötigt nur 16 Byte für die Puffer:

twi_buffer[0] = twi_adr;

for( i = 0; i < 20; i++)
   {
   for(j = 1; j < 16; j++)
      {
      twi_buffer[j] == eeprom_data[i + j];
      }
   TWI_write(twi_buffer[j], 16);
   }

Hier werden 20 * 15 Byte = 300 Byte nacheinander verschickt.
Der empfangende Slave muss die Daten nur wieder richtig ans Ziel 
sortieren.

mfg

Michael S.

von Horst H. (horha)


Lesenswert?

Hallo,

ich beziehe mich auf meinen nonsens von oben.
Ich hatte im Text meine (falsche) Sichtweise erklaert.
Ich dachte, twi_buffer waere identisch mit TWI_MA_Buf, um Speicherplatz 
zu sparen.
Dem ist aber zum Glueck nicht so.

von Patrick N. (emerand)


Lesenswert?

Hallo Michael!

Erstmal danke für dein ansatz.
Aber ich glaube bei diesem Code werden jedesmal dieselben Datenpakets + 
ein neues weitergeleitet.

oder irre ich mich?

Mfg

von Horst H. (horha)


Lesenswert?

Hallo,

er meinte wohl, dass man 20x 15 Byte verschickt.
Dazu muss eeprom_data ein Feld von 0..301 sein.
1
twi_buffer[0] = twi_adr;
2
3
for( i = 0; i < 20; i++)
4
   {// Kopiere die naechsten 15 Datenbyte
5
   for(j = 1; j < 16; j++)
6
      {
7
      twi_buffer[j] = eeprom_data[15*int(i) + j];
8
      }
9
   TWI_write(twi_buffer[0], 16);
10
   }
Alternativ könnte man einen Zeiger auf ein ein uint_8 inkrementieren.
1
twi_buffer[0] = twi_adr;
2
uint_8 *pData 
3
pData = &eeprom_data;
4
5
for( i = 0; i < 20; i++)
6
   {// Kopiere die naechsten 15 Datenbyte
7
   for(j = 1; j < 16; j++)
8
      {
9
      twi_buffer[j] = *pData;
10
      pData++;
11
      }
12
   TWI_write(twi_buffer[0], 16);
13
   }

von Michael S. (Gast)


Lesenswert?

Patrick N. schrieb:

> oder irre ich mich?

Nein, Du hast mich ertappt.
Aber so könnte es möglicherweise funktionieren.

for( i = 0; i < 20; i++)
   {
   for(j = 1; j < 16; j++)
      {
      twi_buffer[j] == eeprom_data[(i * 15) + j];
      }
   TWI_write(twi_buffer[j], 16);
   }

mfg

Michael S.

von Patrick N. (emerand)


Lesenswert?

Hallo Horst!!

> er meinte wohl, dass man 20x 15 Byte verschickt.
> Dazu muss eeprom_data ein Feld von 0..301 sein.

So war es auch geplannt! ich habe ein Atmega8 mit einer EEPROM von 512 
Byte. Und eigentlich ist mein ziel die temperatur von einem sensor zu 
lesen, die daten in digital Werten umzuwalndeln (mit einer frequenz von 
1 Hz), und sie zuerst ins EEPROM spreichern bevor ich sie über TWI 
weiterleite.

Ich hätte gleich die 300Bytes weitergeleitet, aber ich werde eure rat 
folgen und die Daten in datenpateke zerteilen.
Danke schön.

Mfg

von Patrick N. (emerand)


Lesenswert?

Hallo!

@ Horst
> TWI_write(twi_buffer[0], 16);
Dadurch wird jedesmal nur die Slave addresse geschickt oder??

@ Michael
> TWI_write(twi_buffer[j], 16);
Bei dir wird die Slave adresse nicht geschickt oder ?

Ich hoffe ich gehe euch nicht zu sehr auf dem sack mit meinen blöden 
fragen.

Mfg

von Michael S. (Gast)


Lesenswert?

Patrick N. schrieb:

> Ich hoffe ich gehe euch nicht zu sehr auf dem sack mit meinen blöden
> fragen.

Nein, Du hast ja recht.
Da sind uns die üblichen Flüchtigkeitsfehler unterlaufen, wenn man mal 
eben so zwischen Tür und Angel prinzipiell etwas darstellen will ....

>> TWI_write(twi_buffer[0], 16);
> Dadurch wird jedesmal nur die Slave addresse geschickt oder??
Nein - der Compiler meldet sich vermutlich.

Der erste Parameter ist ein Pointer auf ein Array.

Deswegen muss er lauten:

- &twi_buffer[0]

oder

- twi_buffer

> @ Michael
>> TWI_write(twi_buffer[j], 16);
> Bei dir wird die Slave adresse nicht geschickt oder

Der gleiche Fehler wie oben.

Aber in beiden Fällen würden 15 Datenbytes und 1 Adresse verschickt.

mfg

Michael S.

von Michael S. (Gast)



Lesenswert?

Hallo allerseits,

bei meinem Versuch, TWI-Master und TWI-Slave einem TWI-Multi-Master zu 
verheiraten, sind mir einige sinnfreie Stellen im Programmcode des 
TWI-Master und des TWI-Slave aufgefallen.
Die sind nun entfallen.

TWI-Slave und USI-Slave konnten bislang keinen General Call verarbeiten, 
auch das ist behoben.

Der USI-Slave hat nun auch einen Pointer erhalten, mit dessen Hilfe 
unterschiedliche Variablenbereiche aus dem Slave ausgelesen werden 
können.

Zu(nicht)guterletzt waren auch einige Bezeichnungen bei Funktionen und 
Variablen inkonsistent.

Die Beschreibung für alle Module füge ich als
- TWI_USI_master_slave_120303.pdf bei,
  der Programmcode steckt in
- TWI_USI_master_slave_c_120303.zip.

Was ich bislang nicht erwähnt habe:
TWI-Master/Slave sind auf dem ATMega48 getestet, USI-Master/Slave auf 
einem ATTiny25.

mfg

Michael S.

von Patrick N. (emerand)


Lesenswert?

Hey nur mal so eine Frage zu der Deklaration von TWI_TWBR in der datei 
TWI_Master.h. Kann es sein dass die Zahlen 2 und 8 vertauscht hast?
ich statt
1
#define TWI_TWBR         ((F_CPU / (2 * TWI_CLOCK)) - 8)
soll es nicht
1
#define TWI_TWBR         ((F_CPU / (8 * TWI_CLOCK)) - 2)
sein.

Vielen Dank

von Michael S. (Gast)


Lesenswert?

Hallo,

im Manual zum ATMEGA 48/88/168 steht folgende Formel:

SCL frequency = CPU Clock frequency / (16 + 2* (TWBR) * Prescaler Value)

Bei einem Prescaler Value von 1 komme ich durch Umformen auf:

TWDR = (CPU Clock frequency / (SCL frequency) * 2)) - 8

Konkret für FCPU von 4MHz und TWI-Clock von 100.000Khz

TWBR = 4.000.000 / (100.000 * 2) - 8 = 20 - 8 = 12


Würde deine Annahme stimmen
#define TWI_TWBR         ((F_CPU / (8 * TWI_CLOCK)) - 2)

dann sähe die Rechnung so aus:
TWBR = (4.000.000 / (8 * 100.000)) - 2 = 5 - 2 = 3

In älteren Manuals stand der Hinweis, dass der Wert TWBR nicht kleiner 
als 10 sein sollte.

Alles klar ?

mfg

Michael S.

von Patrick N. (emerand)


Lesenswert?

>im Manual zum ATMEGA 48/88/168 steht folgende Formel:
alles Klar daher das Misverstandnis!!
Ich habe die Rechnung mit einem ATMEGA8 gemacht:
Da sieht die Formel ein bisschen aber entscheidend anders aus und zwar 
so:
 SCL_frequency = CPU Clock frequency / ( 16 + 2(TWBR)* 4^TWPS )

Mit TWPS = 1 kommt man auf meine Lösung.

>In älteren Manuals stand der Hinweis, dass der Wert TWBR nicht kleiner
>als 10 sein sollte.
Das gilt wahrscheinlich nicht für ATMEGA8.... hoffe ich zumindest.

Trotzdem danke für deine Antwort

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.