Suche TWI-Slave receive ohne Librarie

Hi,

da mir die Wire-Librarie zu viel SRAM "wegnimmt", habe den Buffer schon auf 1 verringert, suche ich für einen von 2 µc eine Möglichkeit, das TWI selbst zu machen.
Im Netz finde ich leider nichts brauchbares.
Bedingungen an TWI sind:
In einer TWI-ISR per Interrupt ein Byte abholen und auswerten. Evtl. später dann bis zu 4 weitere Bytes innerhalb der ISR ankommen lassen und auswerten, aber ohne die ISR zu verlassen.

Ich müsste also wissen, wie ich ohne Librarie einen Pointer auf eine ISR-Routine setzte und benötige eine TWI-ISR-Routine.

Kann mir dabei jemand helfen?

MfG
Andi

Nimm einen größeren Controller (zB Arduino MEGA)

Grüße Uwe

Ich möchte keinen Flamewar anzetteln oder jemanden auf die Füße treten, aber ich finde es etwas ‘seltsam’, dass hier oft als einzige Antwort immer gleich der nächst größere Controller vorgeschlagen wird.
Auch wenn hier viele Laien, wie ich auch, unterwegs sind, so erwartet man als Fragensteller doch häufig eher Schuppser in die richtige Richtung was den Code angeht.
Immer größere Controler sind nicht immer die Lösung.

Effizientes Programmieren sollte imho durchaus an Priorität gewinnen.

Ich bin an so ähnlichen Funktionen interessiert, weil ich die wire.h doch recht mächtig finde für “die paar Funktionen, die ich bräuchte”.

Nix für ungut und prügelt mich nicht gleich windelweich :kissing:

uwefed:
Nimm einen größeren Controller (zB Arduino MEGA)

Grüße Uwe

Okay, wenn du mir den MEGA dann noch auf 1" x 0,6" schrumpfen kannst, dann gerne ^^

Und ja, die Wire-Librarie finde ich auch für bestimmte Anwendungen einfach zu mächtig.
Ich brauche tatsächlich nur den Mode Slave/Receive in einem µC in der ISR für ein Byte innerhalb einer Transmission.
Und wenns mal mehr als ein Byte sein soll dann in mehreren Transmission immer nur ein Byte.
Es werden auch mal pro Sekunde viele Bytes (Steuercodes von einem anderen µC) gesendet aber imm nur ein Byte je Transmission.
Muss doch irgendwie per Selfmade machbar sein, ohne Wire.h diese zu empfangen.

MfG
Andi

Für den Empfang mehrerer Bytes benötigt man ein Array, und das kostet nun mal Speicher. Das Warten in der ISR auf weitere Bytes ist bei den üblichen Datenraten ganz schlechter Stil, vergleichbar mit delay() :frowning:

Unbenutzte Funktionen/Methoden belegen keinen Speicher, insbesondere kein RAM.

DrDiettrich:
Für den Empfang mehrerer Bytes benötigt man ein Array, und das kostet nun mal Speicher. Das Warten in der ISR auf weitere Bytes ist bei den üblichen Datenraten ganz schlechter Stil, vergleichbar mit delay() :frowning:

Unbenutzte Funktionen/Methoden belegen keinen Speicher, insbesondere kein RAM.

Dass das mit dem in einer ISR weitere Daten abfragen genereller Mist ist ist mir vollkommen klar.
Es ist ja auch nur eine evtl. spätere Option wo das dann auch beiden µC bekannt ist bzw. wenn ein bestimmter "Befehl" gesendet wird dann auch 1 bis 5 weitere Bytes folgen können je nach Befehl. Im endeffekt eine absolut synchrone Abhandlung wo der Slave-Controller genau weis was kommen wird.
Mir gehts jetzt erst mal darum wie man eine ISR selber, ohne TWI-Librarie, auf TWI-Empfang per Interrupt einstellt und dann da drin das offensichtlich schon im TWI-Datenregister vorliegende Byte abholt und dann bestätigt das der TWI-Bus wieder "frei" ist.
Und eben OHNE TWI-Libraries.

MfG
Andi

Du willst Daten empfangen ... also TWI Slave. Auf dieser Seite: TWI Slave mit avr-gcc – RN-Wissen.de findest Du unten ein Beispiel, wie man TWI in diesem Fall ganz ohne Library mit AVR GCC nutzt.

So, habe es jetzt doch noch geschafft:

#define TWIIntOn TWCR|=1<<TWIE
#define TWIIntOff TWCR&=~(1<<TWIE)

ISR (TWI_vect){
  uint16_t T = wireRead();  //Daten von TWI prüfen/holen
  if(T==0)return;  //Daten angekommen? Wenn nicht, raus aus der ISR.
  TWIIntOff;   //Zum verarbeiten TWI-Int ausschalten.
  uint8_t x=T;   //Da nur ein Byte spaart es beim vergleichen ne Menge Code.
   ... Your Code
   ...
  TWIIntOn;
}

uint16_t wireRead(){   //Lesen von TWI mit 16-Bit Rückgabe.
  uint16_t data=0;
start:
  switch(TW_STATUS){
    case TW_SR_SLA_ACK:   // addressed, returned ack
    case TW_SR_GCALL_ACK: // addressed generally, returned ack
    case TW_SR_ARB_LOST_SLA_ACK:   // lost arbitration, returned ack
    case TW_SR_ARB_LOST_GCALL_ACK: // lost arbitration, returned ack
      TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT) | _BV(TWEA);
      break;
    case TW_SR_DATA_ACK:       // data received, returned ack
    case TW_SR_GCALL_DATA_ACK: // data received generally, returned ack
      data=256|TWDR;
      TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT) | _BV(TWEA);
      break;
    case TW_SR_STOP: // stop or repeated start condition received
      // ack future responses and leave slave receiver state
      TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT);
      break;
    case TW_SR_DATA_NACK:       // data received, returned nack
    case TW_SR_GCALL_DATA_NACK: // data received generally, returned nack
      // nack back at master
      TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT);
      break;
    }
  return data;
}

//Initialisierung des TWI-Busses
void init_twi_slave(uint8_t adr){
  TWAR= adr<<1; //Adresse setzen
  TWCR &= ~(1<<TWSTA)|(1<<TWSTO);
  TWCR|= (1<<TWEA) | (1<<TWEN)|(1<<TWIE);   
  TWBR = ((16000000 / 800000) - 16) / 2;
  // deactivate internal pullups for twi.
  digitalWrite(SDA, 0);  //Sollten keine externen Pullups vorhanden sein, deaktivieren.
  digitalWrite(SCL, 0);  //Sollten keine externen Pullups vorhanden sein, deaktivieren.
}

Von der ISR (TWI_vect), welche immer bei einem TWI-Event aufgerufen wird, werden die empfangenen TWI-Daten gelesen und sofort ausgewertet, ohne Buffer etc.
Die Funktion wireread() gibt, wenn es Daten sind, einen 16-Bit-Wert zurück.
Sind Daten im 16-Bit-Wert wird das durch das High-Byte gekennzeichnet welches man nach einer Integritätsprüfung mit If(TWI==0) nicht mehr benötigt. Da es sich um Daten auch um “0”-Bytes handeln kann erschien mir das am sinnvollsten.
Es funktioniert für meine Zwecke tadellos und habe wieder knappe 60Bytes mehr SRAM zur verfügung. Sogar bei 800KHz TWI-Frequenz.

MfG
Andi

Wozu soll das Abschalten der Interrupts gut sein?

DrDiettrich:
Wozu soll das Abschalten der Interrupts gut sein?

Damit in der TWI-ISR beim sofortigen verarbeiten des TWI-Bytes kein weiterer TWI-Interrupt ausgelöst wird (Stack etc.).
Betrifft nur den TWI.

Welchen exotischen Controller benutzst Du denn, der Interrupts (und ISR) unterbrechbar macht?

FlyingEagle:
Auch wenn hier viele Laien, wie ich auch, unterwegs sind, so erwartet man als Fragensteller doch häufig eher Schuppser in die richtige Richtung was den Code angeht.

Ich erwarte von Haribo68, dass er wenigstens die Geräte nennt, die Haribo68 verwendet.

Hier haben wir eine Frage, wo die Antwort von der konkreten Hardwarekonfiguration abhängig ist.
Wird die genannt? Nein!

Also ist aus meiner Sicht KEINE direkt Zielführende Antwort möglich.
Von da her, finde ich die Antwort von Uwe gar nicht so schlecht.

Und wie man sieht, konnte Haribo68 sich auch selber gut helfen.
Haribo68 wollt sich hier nur mal ausheulen.
Die Seele frei blasen.

Alles gut!

Zusätzlich (bei Performance- und Optimierungsfragen):

von wahsaga:
Forenposting mit Performancefrage Bewertungsfaustregel:
Wer Fragen nach der Performance solcher Kinkerlitzchen stellt, der programmiert vermutlich noch nicht mal ansatzweise performant. Andernfalls, wenn er wirklich und zu Recht an einer Applikation arbeiten würde, bei der dieses Quentchen entscheidend ist, sollte er diese Frage gar nicht mehr stellen müssen.

:o :o :o :o :o :o

combie:
Und wie man sieht, konnte er sich auch selber gut helfen.
Er wollt sich hier nur mal ausheulen.
Die Seele frei blasen.

Alles gut!

Ey, lass das mit der 3. Person ^^

Aber TWI ist doch TWI? Wenn man eine Frage darüber stellt ob jemand da was spezielles hat, eben nur die Slave-Routinen ohne Buffer weil nur ein Byte je Transmit, dann kann es sich ja nur um einen AVR handeln da der Begriff von Atmel ist.
Es handelt sich um Steuercodes, rauf, runter, Mode+ etc., welche direkt in paar µ-Sekunden in der ISR verarbeitet werden können. Und dazu ca. 180 Byte weinger SRAM - ich weis, eig. lächerlich heutzutage - ? Ne.
Aber egal, es handelt sich um einen A-Star 32u4.

Vielleicht hilft das ja noch anderen weiter, zumindest wieder mal was gelernt ^^

MfG
Andi

Ey, lass das mit der 3. Person ^^

Korrigiert!

Besser so?

combie:
Also ist aus meiner Sicht KEINE direkt Zielführende Antwort möglich.
Von da her, finde ich die Antwort von Uwe gar nicht so schlecht.

Mir ging es weniger darum, dass eine zielführende Antowrt seitens Uwe fehlte, fiel mehr störte mich gleich die Richtung "größere CPU".

Wenn ich deine Beiträge zu meinen Fragen lese, dann sind die meistens schon in Richtung "so geht es besser" oder "so holt man mehr raus", aber wenn man sich viele Beiträge so anschaut, kommt diese Antwort auch sehr oft.

@Uwe: nimm es bitte nicht persönlich, ich habe das in meinem Thread(s) auch schon häufiger gesagt.

Mir geht es mehr darum besseren Code zu schreiben und auch entsprechende Hilfen zu bekommen.
Wenn etwas wirklich nicht geht, weil zu groß, dann sehe ich das gerne ein, aber erstmal nicht als ersten Kanonenschlag :slight_smile:

So, Freunde, danke fürs Zuhören. Dir, Haribo, Glühstrumpf das es geht :slight_smile:

Leider "spinnt mein Struct im EEPROM immer noch" ... zumindest ein wenig.

Hi,

habe nebenher noch ein bißchen an meiner Routine TWI-Slave-Receive gebastelt und wollte sie euch nicht vorenhalten.
Hier der Code:

#define uintB uint8_t
#define uintW uint16_t
#define uintL uint32_t

#define TWIbuffersize 4           //Größe des TWI-Buffers. Mögliche Größen sind: 1, 2, 4, 8, 16, 32, 64 oder 128 Byte
#define TWIring TWIbuffersize-1   //Als Ringbuffer die AND-Verknüpfung
uintB TWIinv=0,                   //Menge an Datenbytes im TWI-Buffer
      TWIbuffer[TWIbuffersize],   //Der TWI-Buffer als Array
      TWIptrin,                   //Pointer auf den TWI-Buffer für Dateneingang
      TWIptrout;                  //Pointer auf den TWI-Buffer für Datenausgang

void setup(){
  init_twi_slave(2);                //TWI initiallisieren mit Nr. 2

}

void loop(){

  if(TWIinv){                //Sind Daten im TWI-Buffer?
    byte x=TWIRead();        //ein Byte aus dem TWI-Buffer abholen
    ...                                 //und auswerten...

  }
}

byte TWIRead(){
  if(!TWIinv)return;
  byte x=TWIbuffer[TWIptrout++];
  TWIptrout&=TWIring;
  TWIinv--;
  return x;
}

void init_twi_slave(uintB adr){
  TWAR= adr<<1; //Adresse setzen
  TWCR &= ~(1<<TWSTA)|(1<<TWSTO);
  TWCR|= (1<<TWEA) | (1<<TWEN)|(1<<TWIE);   
  TWBR = ((16000000 / 100000) - 16) / 2;   //TWI-Frequenz. Möglich bis 800000!
  // deactivate internal pullups for twi.
  digitalWrite(SDA, 0);
  digitalWrite(SCL, 0);
}

ISR (TWI_vect){
  switch(TW_STATUS){
    case TW_SR_SLA_ACK:   // addressed, returned ack
    case TW_SR_GCALL_ACK: // addressed generally, returned ack
    case TW_SR_ARB_LOST_SLA_ACK:   // lost arbitration, returned ack
    case TW_SR_ARB_LOST_GCALL_ACK: // lost arbitration, returned ack
      TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT) | _BV(TWEA);
      break;
    case TW_SR_DATA_ACK:       // data received, returned ack
    case TW_SR_GCALL_DATA_ACK: // data received generally, returned ack
      if (TWIinv<TWIbuffersize){
        TWIbuffer[TWIptrin++&TWIring]=TWDR;
        TWIinv++;
        TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT) | _BV(TWEA);
      }else{
        TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT);
      }
      break;
    case TW_SR_STOP: // stop or repeated start condition received
      // ack future responses and leave slave receiver state
      TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT);
      break;
    case TW_SR_DATA_NACK:       // data received, returned nack
    case TW_SR_GCALL_DATA_NACK: // data received generally, returned nack
      // nack back at master
      TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT);
      break;
  }
}

Den TWI-Buffer habe ich als Ring-Buffer organisiert. Dadurch ist es auch möglich, schon bereits abgeholte Daten, je nach Größe des Buffers, im nachhinein noch mal auszuwerten. Aber das nur am Rande.
Es gibt zwei Pointer auf den TWI-Buffer. TWIptrin als Eingang des empfangenen Bytes und TWIptrout zum abholen. Der tatsächliche Inhalt an neuen Datenbytes bestimmt die Variable TWIinv für TWI Inventory und verhindert natürlich auch ein permanentes befüllen des Buffers ohne das was abgearbeitet wurde.
Durch eine AND-Verknüpfung werden die Pointer innerhalb des Buffers gehalten. Allerdings sind dann nur Größen von 1, 2, 4, 8, 16, 32, 64 und 128 Byte möglich. Möchte mann z. B. eine Größe von 20 muss man halt eine Zeile mit Bereichprüfung einbauen und die AND-Verknüpfung &TWIring weglassen.
Die eigentliche Empfangsroutine für TWI-Daten ist als ISR definiert und wird von der Arduino-IDE auch automatisch als ISR eingerichtet, läuft also ab Start des Controllers sofort los wenn Daten kommen.
Ob was im TWI-Buffer drin ist kann mit if(TWIinv) abgefragt werden und wenn das der Fall ist mit x=TWIRead(); dann auch abgeholt werden.

MfG
Andi

Von wegen Ring-Buffer, habe bereits vor einigen Woche das geändert aber hier kurz & bündig die aktuelle TWI-Slave-Funktion:

#define TWIbuffersize 6                  //Größe des TWI-Buffers von 1 bis 255 Bytes.
volatile byte TWIbuffer[TWIbuffersize];  //Der TWI-Buffer als Array
volatile byte TWIindex=0;                //Pointer auf den TWI-Buffer für Dateneingang
volatile byte TWIsize=0;                 //Anzahl der Datenbytes im TWI-Buffer

ISR (TWI_vect){
  switch(TW_STATUS){
    case TW_SR_SLA_ACK:   // addressed, returned ack
    case TW_SR_GCALL_ACK: // addressed generally, returned ack
    case TW_SR_ARB_LOST_SLA_ACK:   // lost arbitration, returned ack
    case TW_SR_ARB_LOST_GCALL_ACK: // lost arbitration, returned ack
        TWIindex=0;
        TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT) | _BV(TWEA);
      break;
    case TW_SR_DATA_ACK:       // data received, returned ack
    case TW_SR_GCALL_DATA_ACK: // data received generally, returned ack
      if(TWIindex<TWIbuffersize){
        TWIbuffer[TWIindex++]=TWDR;
        TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT) | _BV(TWEA);
      }else{
        TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT);
      }
      break;
    case TW_SR_STOP: // stop or repeated start condition received
      // ack future responses and leave slave receiver state
        TWIsize=TWIindex;
        TWIindex=255;
        TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT);
      break;
    case TW_SR_DATA_NACK:       // data received, returned nack
    case TW_SR_GCALL_DATA_NACK: // data received generally, returned nack
      // nack back at master
      TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT);
      break;
  }
}

Mittels if(TWIindex==255) wird ermittelt ob ein vollständiges Datenpaket empfangen wurde.
Z. B. in einer zyklisch aufrufenden Funktion zum abarbeiten der TWI-Daten einfach TWIindex auf 0 setzen um zu signalisieren das die Daten bearbeitet wurden:

if(TWIindex==255){
TWIindex=0;
byte a=TWIbuffer[0];
if(a==1)byte b=TWIbuffer[1]; //oder wie auch immer…

}

Sollten während des abarbeitens wieder neue Daten kommen macht es Sinn, die Daten aus dem TWIbuffer sofort abzuholen, ansonsten kann der Sender auch eine oder ein paar ms warten bevor dieser neue Daten sendet.
Wenn man Strings unbestimmter Länge empfangen möchte lässt sich über TWIsize die Größe des Datenpaketes ermitteln was man gleich z. B. in einer for-Schleife verwenden kann und man spart sich auch entsprechende Abschlußzeichen wie z. B. “\0”.
Als Ringbuffer kam es paar mal vor, das sich der Output-Pointer verschoben hatte, wahrscheinlich aus zeitlichen Gründen da bei meiner Anwendung oft die Interrupts gespeert sind, deshalb nun auf die klassische Art.

MfG
Andi