Hallo geehrte Experten,
ich suche jetzt schon einige Tage nach der Ursache für folgendes
Problem:
wie im Betreff geschrieben, geht bei der Speicherung sporadisch ein Byte
verloren. Die zu schreibenden Daten kommen zurzeit vom seriellen Monitor
- später werden es zwecks Datensicherung erstellte Dateien sein.
Nach erfolgreicher Überwindung des Problems, dass der serielle Puffer
nur 64 Zeichen aufnehmen kann, die Dateigröße aber mit mehr als 1k weit
oberhalb davon liegt, werden die Testdaten mittlerweile erfolgreich und
ohne Fehler in das RAM geschrieben.
Bei der Ausgabe des EEPROM-Inhaltes fehlt aber sporadisch ein Byte in
der aufsteigenden Reihenfolge (Zwecks Fehlereingrenzung wurde eine
Testdatei mit aufsteigenden Werten von 00 - 9F erstellt, welche über die
Eingabezeile des seriellen Monitors gesendet wird).
Konkret kommt es vor, dass 10 mal alle 160 Bytes korrekt eingelesen
werden, danach aber ein Byte fehlt.
Es wird keine Bibliothek für das Schreiben und Lesen des eeprom
verwendet.
Die zuässigen Werte für den Pufferspeicher und die Pagegrenzen werden
"zu Fuß" berechnet. Das versuchsweise Schreiben grösserer Datenmengen
mit beliebiger Startadresse wurde getestet und ist fehlerfrei.
hier ein Beispiel fehlerhafter Daten (es fehlt der Wert 65):
Im Anhang ein Auszug des Programms mit hoffentlich allen nötigen
Informationen.
Vielen Dank für jegliche Hilfe und viele Grüße
Kai
Da ich nur alle 30 Minuten antworten kann, reiche ich hier nach, dass
die Verarbeitung der Daten schneller geht, als sie bei 9600 BAUD
eintreffen (zum Debuggen hatte ich die empfangenen Daten auch ins RAM
geschrieben - der Inhalt war immer richtig).
Es war bereits ein Software-Handshake implementiert, welches das Problem
aber nicht beseitigte.
Offenbar fehlt ein Handshake. Dein Sender sendet die Daten schneller an
den Mikrocontroller, als dieser sie verarbeiten kann. Du brauchst eine
bidirektionale Kommunikation, damit der Mikrocontroller dem Sender ein
"mach mal pause" Signal senden kann.
Alternativ kannst du alle Dateien ins RAM empfangen, und erst danach ins
EEPROM schreiben - falls du dazu genug RAM frei hast.
Nemopuk schrieb:> Offenbar fehlt ein Handshake. Dein Sender sendet die Daten schneller an> den Mikrocontroller, als dieser sie verarbeiten kann. Du brauchst eine> bidirektionale Kommunikation, damit der Mikrocontroller dem Sender ein> "mach mal pause" Signal senden kann.
Oder genügend buffer um die Daten in RAM zwischenzuspeichern.
Hallo Nemopuk, hallo Andras,
Entschuldigung für das Einfügen des Quelltextes!
da ich nur alle 30 Minuten antworten kann, habe ich oben ergänzt, das
die Bearbeitung wesentlich schneller geht, als die Daten bei 9600 BAUD
eintreffen.
Der Maximalwert des Puffers erreicht maximal 30% (Zum Debuggen hatte ich
die Daten auch ins RAM geschrieben. Der Inhalt ist immer richtig).
Ich hatte vorher einen Software-Handshake eingebaut, welcher am Problem
aber nichts geändert hat.
Viele Grüße
Kai
Kai schrieb:> Dar Ram-Inhalt ist richtig aber die EEPROM-Schreibroutine verschluckt> ein Byte.
Das kann nicht sein, weil cnt für jedes empfangene Byte um 1 erhöht
wird. Wenn es am Schreibzugriff ins EEPROM läge, dann wäre an der
Position ein einzelnes falsches Byte, und danach kämen richtige Bytes.
Hier fehlt aber ein Byte und alle nachfolgenden sind danach an der
falschen Position.
Bei dir hat cnt am Ende weniger Bytes gezählt, als erwartet. Also wurde
ein Byte zu wenig empfangen (ist verloren gegangen). Füge eine Debug
Ausgabe vom cnt Wert hinzu, dann siehst du es.
Funktioniert es stets 10 Mal und beim 11. Mal nicht?
Ist es immer die 0x65, die fehlt?
Was sind die Werte der Debug-Ausgaben, wenn es funktioniert, und wenn
nicht?
Ich sehe gerade: Ich habe nicht geschnallt, das die beiden Nibbles als
einzelnes Byte zusammen gefasst werden. Mein Smartphone Display taugt
nicht gut zum Lesen von Quelltexten. Jetzt auf dem PC ist es mir klarer.
128 Zeichen Seriell ergeben 64 Bytes im EEprom
Eine Gegenfrage, bevor wir (ich) uns hier total verzetteln:
> byte bytes; byte x;
Was genau ist der Datentyp "byte"? Kann es sein, dass das ein Signed
Integer ist, der genau an dieser Stelle überläuft?
Hallo Nemopuk,
vielen Dank für diesen Hinweis - richtig - cnt zählt im Fehlerfall
weniger Bytes. Das Einfügen einer Debug-Ausgabe ist nicht ganz einfach,
da diese das Timing stark verändert. Versuche ich gleich auf jeden Fall
mal.
Hallo Michiii,
nein - es wird unterschiedlich oft richtig empfangen, dann wieder
unterschiedlich oft verkehrt und es ist nicht immer die 0x65.
Viele Grüße
Kai
Hier mal die 0x62:
Zähle mal, wie oft page_write() aufgerufen wird und gebe die Zahl tzum
Schluss aus. Wo ist die Dokumentation (oder besser noch der Quelltext
von page_write()?
Kai schrieb:> Dar Ram-Inhalt ist richtig aber die EEPROM-Schreibroutine verschluckt> ein Byte.
Dann solltest Du uns den dafür notwendigen Programmteil zeigen.
Hallo Nemopuk,
ja - meine Variablennamen sind an dieser Stelle nicht so toll:
Die Variable bytes enthält die errechneten für bytes_in_page(addr+cnt).
Die Variable x enthält die Anzahl der aktuell verfügbaren Zeichen für
die Auswertung.
byte ist gemäß Arduino-Referenz ein unsigned byte
[[https://docs.arduino.cc/language-reference/de/variablen/data-types/byte/]]
Die Zeichen 00 - 9F ergeben für cnt fehlerfrei gezählt einen Wert von
160.
und es werden 160 Bytes im eeprom geschrieben - im Fehlerfall werden
weniger Bytes ins eeprom geschrieben.
Viele Grüße
Kai
Mal neben bei bemerkt: Ich kriege einen Knoten im Kopf, wenn ich mir die
mathematischen Teile in deinem Quelltext anschaue. Das ist alles so von
hinten durch die Brust ins Auge, dann durch die Nase wieder rein und am
Popo raus. Hast du dir das selbst ausgedacht, oder kopiert?
Kai schrieb:> im Fehlerfall werden weniger Bytes ins eeprom geschrieben.
Du verwendest offensichtlich ein externes "grosses" EEPROM.
Dabei gibt es zwei wichtige mögliche Fehlerquellen:
1) deine Programmierung entspricht nicht (ganz) den
Anforderungen deines EEPROMs.
2) Dein Aufbau ist mangelhaft. Die Stromversorgung spielt
dabei eine grosse Rolle. Viele Leute vergessen ein
Abblock-C am EEPROM anzubringen oder halten es bewusst
für nicht erforderlich.
for(unsignedinti=0;i<number;i++){// Write all bytes allowed on this page
6
Wire.write((byte)(Ser_Arr[i]));}// Byte from array
7
Wire.endTransmission();// End transmission
8
delay(5);// Important
9
}
Hallo Nemopuk,
selbst ausgedacht :(
Hallo Wastl,
ja ein externes EEPROM mit Abblock-C. Der Fehler tritt sowohl bei einem
fliegenden Aufbau mit einem Arduino-Mega als auch auf einer fertigen
Platine mit einem 328P auf.
Viele Grüße Kai
PS: muss leider für ca. 1h weg
Da nach dem Fehler alle folgenden Bytes im EEprom um ein Byte verschoben
sind, vermute ich einen Fehler in der Berechnung der Adressen
(Positionen im EEprom).
Du könntest alle berechneten Positionen (mit denen du page_write()
aufrufst) mal in ein Array schreiben und zum Schluss zu Debug Zwecken
ausgeben. Dann sehen wir, wie oft page_write() aufgerufen wurde, und mit
welchen berechneten Adressen.
Kai schrieb:> void page_write(unsigned int addr, unsigned int number)
Das wird so nicht gehen, da die Wire-Klasse nicht weiss dass
sie auf die Programmierung des EEPROMs Rücksicht nehmen muss.
Nemopuk schrieb:> vermute ich einen Fehler in der Berechnung der Adressen> (Positionen im EEprom)
Nö. Einfach nicht lang genug gewartet sondern EEPROM "überfahren".
Wastl schrieb:> Einfach nicht lang genug gewartet sondern EEPROM "überfahren".
Ich habe gerade mal in ein zufällig gewähltes DB geschaut, da werden 5ms
als maximale Zeit genannt. Also das sollte man mal mit dem RICHTIGEN
Datenblatt abgleichen und sicherheitshalber ein bisschen länger warten.
Nemopuk schrieb:> da werden 5ms als maximale Zeit genannt
Ist richtig und steht im Code auch so drin. Wenn man es
wirklich gründlich timen will pollt man am Ende einer Page
so lange bis das EEPROM sich wirklich zurückmeldet dass es
wieder bereit ist.
Trotzdem sind die genannten Symptome Anzeichen genug dass
das EEPROM überfahren wird. Gleiches gilt für nicht abge-
blockte Versorgungsspannung ....
Wastl schrieb:> Ist richtig und steht im Code auch so drin. Wenn man es> wirklich gründlich timen will pollt man am Ende einer Page> so lange bis das EEPROM sich wirklich zurückmeldet dass es> wieder bereit ist.
Nein!
Nicht am Ende einer Page, sondern VOR jedem Schreibzugriff.
Das delay(5) ist auf jeden Fall gründlich daneben.
Dazu:
Kai, hast du bedacht dass Wire nur einen 32 Byte großen Buffer
verwaltet?
Überfüllungen werden abgeschnitten.
Wire.write() meldet den Misserfolg, aber das wird hier gründlich
ignoriert.
Arduino F. schrieb:> Nein!> Nicht am Ende einer Page, sondern VOR jedem Schreibzugriff.
Seltsam. In den Datenblättern steht davon nichts. Ich habe
schon sehr viele I2C EEPROMs mit meinen eigenen Page-Write
Routinen beschrieben und habe dabei nie vor dem Schreiben
gewartet, sondern immer auf das OK des EEPROMs.
Wastl schrieb:> Arduino F. schrieb:>> Nein!>> Nicht am Ende einer Page, sondern VOR jedem Schreibzugriff.>> Seltsam. In den Datenblättern steht davon nichts. Ich habe> schon sehr viele I2C EEPROMs mit meinen eigenen Page-Write> Routinen beschrieben und habe dabei nie vor dem Schreiben> gewartet, sondern immer auf das OK des EEPROMs.
Die Logik dahinter:
Wenn ich schreiben möchte, dann muss/kann ich doch vorher prüfen ob das
EEPROM bereit ist. Gegebenenfalls warten.
Wenn ich nach dem Schreiben prüfe/warte, ist das verschwendete Zeit.
OK, wenn viele Schreibzugriffe Zeitnah erfolgen, ist egal, wo gewartet
wird, aber das ist längst nicht immer der Fall.
Vielen Dank für alle Hinweise - Versorgungsspannung ist abgeblockt.
Pullups sind vorhanden. Am I2C-Bus hängen eine RTC, ein LCD und das
externe eeprom.
Die Wire-Schreibroutine wird ja nur mit der erlaubten Anzahl von Daten
aufgerufen. Die Zeit werde ich kontrollieren. Die verwendete Angabe habe
ich aus einem Beispiel übernommen.
3. Arduino F:
Die Schreibroutine habe ich aus einem Beispiel übernommen, wo nach dem
Schreiben gewartet wird. Die Puffergröße wird beachtet. In dem
beschriebenen Beispiel ist eine Puffergröße von 30 angegeben.
Viele Grüße
Kai
Kai schrieb:> Das Einfügen einer Debug-Ausgabe ist nicht ganz einfach,> da diese das Timing stark verändert.
Nicht, wenn du die Daten über SPI ausgibst und mit dem Logikanalyzer
mitliest. Die SPI-Ausgange kannst du deutlich schneller als die 9600Bd
laufen lassen.
Kai schrieb:> Das Einfügen einer Debug-Ausgabe ist nicht ganz einfach,> da diese das Timing stark verändert. Versuche ich gleich auf jeden Fall> mal.
Falsch. Man muss halt die Debugausgaben passend puffern und dann
ausgeben, wenn genügend Zeit ist.
Hallo Rainer, Nemopuk, Wastl, Arduino F und Falk,
zunächst habe ich den Tipp von Arduino F umgesetzt und am Ende der
Page-Write-Routine auf das ACK gewartet (beim ersten Aufruf läuft ja
noch kein Page-Write, daher am Ende):
for(unsignedinti=0;i<number;i++){// Write all bytes allowed on this page
6
Wire.write((byte)(Ser_Arr[i]));}// Byte from array
7
Wire.endTransmission();// End transmission
8
//delay(5); // Important
9
Wire.beginTransmission(EXT_EE_ADDR);// Start command and device Address queued
10
Wire.write(0x00);// Dummy data and direction mode (R-W/=0) bit queued
11
while(Wire.endTransmission()!=0x00);// Transmission success status code as per Wire Library
12
}
Das hat am Verhalten nichts geändert, gefällt mir aber auch besser, als
das delay(5).
Danach habe ich den Vorschlag von Nemopuk integriert und nach jedem
Aufruf von Page_Write die Variable cnt und cur_byte in ein Array
geschrieben und am Ende der Übertragung ausgegeben (siehe Anhang).
Das Ergebnis hat mich sehr überrascht - ich hätte nicht erwartet, dass
die Daten so schnell bearbeitet (in das eeprom geschrieben) werden, dass
sich der Puffer nur vor dem Start der Routine auf 11 Zeichen anwächst.
Leider gibt es neben fehlerfreien immer noch fehlerhafte Übertragungen,
was ja auch nicht anders zu erwarten war:
Kai schrieb:> Das hat am Verhalten nichts geändert
Sehe ich anders, jetzt ein neues Fehlerbild dazu gekommen:
Vorher:
> 60 61 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 112
62 fehlt, alle folgenden Bytes sind an der falschen Adresse.
Nachher:
> 20 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 48
Die 21 fehlt, alle folgenden Bytes sind an der falschen Adresse.
> 91 92 93 94 95 96 97 97 99 9A 9B 9C 9D 9E 9F FF 160
Eine der beiden 97 müsste den Wert 98 haben.
Ich würde immer noch versuchen, einen großzügigen Delay (mehr als im
Datenblatt steht) einzubauen. Nur zur Probe, ob dort der Knackpunkt
liegt. Du kannst zusätzlich die Baudrate herabsetzen, wenn nötig
(Pufferüberlauf).
Du hast eine leere Textdatei gezippt. Bitte reiche die richtige Datei
nach.
Wire.endTransmission() überträgt die Daten aus dem transmit buffer und
meldet Misserfolge, das muss ausgewertet werden:
0: success.
1: data too long to fit in transmit buffer.
2: received NACK on transmit of address.
3: received NACK on transmit of data.
4: other error.
5: timeout
Kai schrieb:>> while (Wire.endTransmission() != 0x00); // Transmission success>
So wird endTransmission() im Fehlerfall einfach nochmal aufgerufen,
keine Ahnung was dann passiert - macht nix und gibt 0 zurück ??
Wire.write() schreibt die Daten nur in den transmit buffer (und gibt
deshalb auch nur die Anzahl der geschriebenen Bytes zurück).
Wastl schrieb:> Ich habe> schon sehr viele I2C EEPROMs mit meinen eigenen Page-Write> Routinen beschrieben und habe dabei nie vor dem Schreiben> gewartet, sondern immer auf das OK des EEPROMs.
Nun, vor dem Schreiben oder Lesen mußt Du doch erstmal die Adresse
senden. Damit ist automatisch gesichert, daß der EEPROM ein vorheriges
Schreiben beendet hat.
Ein EEPROM der busy ist, stellt sich tot, d.h. antwortet mit NACK auf
seine Adresse. Ich versuche es dann einfach in einer Schleife nochmal,
die bei 400kHz I2C-Takt bis zu 10ms wartet.
Man muß natürlich vor jedem Byte abtesten, ob ein Pageüberlauf erfolgt
und dann erstmal die aktuelle Page wegschreiben.
Anbei mal meine I2C-EEPROM Lib.
Sie benutzt Bit-Banging I2C, da zumindest bei den ATmega8 usw. der
I2C-Controller recht störempfindlich ist. Sie ist dadurch auch sehr
leicht auf jede andere µC-Familie zu portieren.
Da ja keine Interrupts benutzt werden, hätte der I2C-Controller keinen
Laufzeitvorteil. Auch der Flashverbrauch ist dadurch minimal.
Peter D. schrieb:> Nun, vor dem Schreiben oder Lesen mußt Du doch erstmal die Adresse> senden.
Ich hatte nicht vor meine Routine (die einwandfrei funktioniert)
im Detail in einem Absatz zu erklären. Belehrungen zum
Algorithmus brauchst du mir nicht zu geben, immerhin beziehst
du deine Belehrung auf meine Aussage und schreibst "doch", als
ob bei mir ein grober Annahme-Fehler vorliegt.
Kai schrieb:> zunächst habe ich den Tipp von Arduino F umgesetzt und am Ende der> Page-Write-Routine auf das ACK gewartet
Das ist natürlich viel zu optimistisch.
Jedes Byte Schreiben kann fehlschlagen, daher muß auch jedesmal auf ACK
geprüft werden.
Auf ACK man auch nicht warten, sondern man kann es nur prüfen. Der Slave
legt es im 9. Takt vom Master auf den Bus oder eben nicht.
Und beim Lesen muß der Master auf das letzte Byte mit NACK antworten!
Wastl schrieb:> du deine Belehrung auf meine Aussage und schreibst "doch", als> ob bei mir ein grober Annahme-Fehler vorliegt.
Lies nochmal gründlicher, da erfolgt nirgends eine Belehrung, sondern
eine Erklärung. Und die Erklärung ist natürlich nicht nur für Dich
gedacht, sondern ganz allgemein.
Das ACK auf die I2C-Adresse impliziert den Abschluß aller vorherigen
Aktionen. Du kannst von mir aus gerne noch ein Delay anfügen.
Nemopuk schrieb:> Ich habe das Gefühl, dass einige hier keine Ahnung haben, wie die I2C> Implementierung von Arduino funktioniert.
Meine Erklärungen beziehen sich ausschließlich auf den I2C-Standard, wie
er von Philips/NXP festgelegt wurde. Die konkrete Realisierung in einem
µC war nie Punkt der Diskussion und somit erst recht nicht, wie es in
der Arduino-Welt erfolgt.
Es gibt nie nur einen einzigen Weg, der Implementierung. Wichtig ist
nur, daß der Philips/NXP I2C-Standard eingehalten wird.
Helmut H. schrieb:> So wird endTransmission() im Fehlerfall einfach nochmal aufgerufen,> keine Ahnung was dann passiert - macht nix und gibt 0 zurück ??
Damit hast Du voll recht, das ist ein grober Fehler.
I2C kennt kein Retry, wie z.B. der CAN-Bus. Man muß daher die
fehlerhafte Aktion komplett wiederholen.
Nemopuk schrieb:> Ein Logic Analyzer wäre auch am I2C Bus hilfreich.
I2C Kommunikation erfordert wegen ACK mehr Zusammenarbeit zwischen den
beiden Kommunikationsteilnehmern. Macht der Arduino des TO den Handshake
im Hintergrund per Hardware?
Peter D. schrieb:> Man muß daher die fehlerhafte Aktion komplett wiederholen.
Wen man endTransmission() erneut aufruft, passiert genau das.
Rainer W. schrieb:> Macht der Arduino des TO den Handshake im Hintergrund per Hardware?
Ja
Helmut H. schrieb:> Wire.write() schreibt die Daten nur in den transmit buffer
Korrekt!
Nemopuk schrieb:> Wen man endTransmission() erneut aufruft, passiert genau das.
Es wird also z.B. bei "2: received NACK on transmit of address" alles
wiederholt?
Wo ist das denn dokumentiert?
Es spricht prinzipiell ja nichts dagegen, wenn die Funktionen derart
komplex sind und hoffentlich auch ausgiebig getestet sind.
Obwohl ich nicht so richtig daran glaube, das die EEPROM Statemaschine
wirklich im Hintergrund laufen kann. Das mit dem Pageüberlauf ist ja
schon ziemlich speziell.
Auch müßte diese Superfunktion die Adreßlänge wissen, d.h. ist der
EEPROM <= 24C16 oder >=24C164.
Peter D. schrieb:> Es wird also z.B. bei "2: received NACK on transmit of address" alles> wiederholt.
Nicht automatisch durch das Framework. Der Programmierer der Anwendung
entscheidet, ob und wie er auf Fehler reagieren will. Helmut schlug vor
Helmut H. schrieb:> So wird endTransmission() im Fehlerfall einfach nochmal aufgerufen
Das war ein Vorschlag, keine Feststellung.
> Wo ist das denn dokumentiert?
Auf arduino.cc. Für den Anfang:
https://docs.arduino.cc/learn/communication/wire/https://docs.arduino.cc/language-reference/en/functions/communication/wire/https://github.com/arduino/ArduinoCore-avr/tree/master/libraries/Wire/src
Aber du diskutierst offenbar lieber über (meist gegen) Arduino, anstatt
dessen Doku zu lesen oder wenigstens mal einen Blick in dessen Quelltext
zu werfen. Alle sollen von deinen Codes lernen, was OK ist, denn sie
sind gut. Das ganze käme jedoch wesentlich überzeugender rüber, wenn du
dich auch mal mehr mit dem Code von Arduino befassen würdest.
Mag sein, dass Arduino für "Dummköpfe" designt wurde, aber nicht von
Dummköpfen. Die Macher von Arduino sind durchaus fachkundig.
Nemopuk schrieb:> Aber du diskutierst offenbar lieber über (meist gegen) Arduino
Da bist Du aber sowas von auf dem Holzweg. Ich benutze es nicht, das ist
aber auch schon alles. Kritisiert habe ich es bisher nirgends.
Ich kann allerdings nicht sämtliche Arduino Doku im Kopf haben. Daher
mußt Du mir schon zugestehen, daß ich manchmal auch dazu frage. Fragen
stellen darf man ja wohl noch und ist keine Kritik!
Ich würde immer noch versuchen, einen großzügigen Delay (mehr als im
9
Datenblatt steht) einzubauen. Nur zur Probe, ob dort der Knackpunkt
10
liegt. Du kannst zusätzlich die Baudrate herabsetzen, wenn nötig
11
(Pufferüberlauf).
12
13
Du hast eine leere Textdatei gezippt. Bitte reiche die richtige Datei
14
nach.
Die doppelt vorhandene 97 habe ich gestern Abend tatsächlich übersehen
und leider offenbar auch das Ergebnis des Logging nicht in die geöffnete
Datei kopiert. Die Daten sind leider weg - ich wiederhole es gleich.
Helmut:
1
Wire.endTransmission() überträgt die Daten aus dem transmit buffer und
2
meldet Misserfolge, das muss ausgewertet werden:
3
0: success.
4
1: data too long to fit in transmit buffer.
5
2: received NACK on transmit of address.
6
3: received NACK on transmit of data.
7
4: other error.
8
5: timeout
Vielen Dank für diese Information, welche mir bisher fehlte.
Peda:
1
Nun, vor dem Schreiben oder Lesen mußt Du doch erstmal die Adresse
2
senden. Damit ist automatisch gesichert, daß der EEPROM ein vorheriges
3
Schreiben beendet hat.
4
Ein EEPROM der busy ist, stellt sich tot, d.h. antwortet mit NACK auf
5
seine Adresse. Ich versuche es dann einfach in einer Schleife nochmal,
6
die bei 400kHz I2C-Takt bis zu 10ms wartet.
7
8
Man muß natürlich vor jedem Byte abtesten, ob ein Pageüberlauf erfolgt
9
und dann erstmal die aktuelle Page wegschreiben.
Deine I2C-Lib ist natürlich die Königsklasse - wahrscheinlich würde ich
mir bei der Umsetzung für meine Applikation aber viele neue Probleme
einhandeln (RTC, LCD)?
Bedeutet dies, dass das Warten auf ein ACK am Ende des Schreibvorganges
nicht erforderlich ist?
Zunächst werde ich versuchen, ein ähnliches Ergebnis wie gestern zu
loggen.
Eine Anmerkung noch:
a) zwecks möglichst schnellen Abholens der seriellen Daten und
b) damit mir auf dem I2C-Bus nichts dazwischenfunkt
wird während der gesamten Übertragung die while-Schleife nicht
verlassen.
Viele Grüße und einen schönen Sonntag
Kai
Peter D. schrieb:> Kai schrieb:>> byte Ser_Arr[31];>> Sicher, daß eine Page 31 Byte groß ist?>> Ich kenne nur EEPROMs mit 32 Bytes per Page.
Hallo peda,
bei diesen 30 Elementen handelt es sich um die Größe des Puffers der
Wire-Bibliothek. Diese Informatíon hatte ich von Wolles Elektronikkiste
übernommen:
[[https://wolles-elektronikkiste.de/eeprom-teil-2-externe-i2c-eeproms]]
Viele Grüße
Kai
Peter D. schrieb:> Kai schrieb:>> byte Ser_Arr[31];>> Sicher, daß eine Page 31 Byte groß ist?>> Ich kenne nur EEPROMs mit 32 Bytes per Page.
Das verwendete hat offensichtlich 64 Byte per Page.
Der Wire Buffer hat nur 32 Byte
Unser Klient möchte nur 30 pro Transfer übertragen.
Kai schrieb:> Bedeutet dies, dass das Warten auf ein ACK am Ende des Schreibvorganges> nicht erforderlich ist?
Nochmal, auf das ACK kann man nicht warten, der Master kann das ACK nur
abfragen (pollen).
Ich würde eine eigene Funktion zum Setzen der I2C-Adresse empfehlen.
Diese sendet in einer Schleife STA+Adresse+STO, bis entweder ein ACK
kommt oder ~10ms rum sind. Bei ACK entfällt natürlich das nachfolgende
STO.
Eine feste Wartezeit würde ich nicht empfehlen, da dann das Schreiben
deutlich länger braucht.
Arduino F. schrieb:> Unser Klient möchte nur 30 pro Transfer übertragen.
Dann muß aber "page_write" einen Pageüberlauf mit behandeln.
Schade, das "page_write" nicht gezeigt wird, das wäre schon sehr
wichtig.
Kai schrieb:> void page_write(unsigned int addr, unsigned int number){> Wire.beginTransmission(EXT_EE_ADDR); // Start transmission to> I2C address> Wire.write((byte)(addr>>8)); // Address eeprom high> Wire.write((byte)(addr&0xFF)); // Address eeprom low> for(unsigned int i=0; i<number; i++){ // Write all bytes> allowed on this page> Wire.write((byte)(Ser_Arr[i]));} // Byte from array> Wire.endTransmission(); // End transmission> delay(5); // Important> }
Hallo peda,
das war die ursprüngliche Version von page-write (hatte ich weiter oben
nachgereicht).
Kai schrieb:> das war die ursprüngliche Version von page-write
Wie ich befürchtet habe. Es wird kein einziges mal der Status überprüft.
Ein Pageüberlauf wird auch nicht behandelt. Bei einem Pageschreiben
werden nur die unteren Adreßbits hochgezählt. Ein Datenbyte wird also
wieder an die erste Adresse der Page geschrieben.
Hallo nemopuk,
anbei die Log-Datei einer erfolgreichen und einer fehlerhaften
Übertragung - an letzter Position ist das Ergebnis von
Wire.EndTransmission angehängt.
Mittlerweile bin ich aber ziemlich sicher, dass der Fehler - wie bereits
anfangs von dir vermutet - in der Berechnung der Variable cnt liegen
muss,
denn diese ist im Fehlerfall < 160. Diese Variable wird aber durch ein
fehlerhaftes Schreiben des eeproms überhaupt nicht beeinflusst.
Viele Grüße
Kai
Kai schrieb:> Mittlerweile bin ich aber ziemlich sicher, dass der Fehler - wie bereits> anfangs von dir vermutet - in der Berechnung der Variable cnt liegen> muss,> denn diese ist im Fehlerfall < 160. Diese Variable wird aber durch ein> fehlerhaftes Schreiben des eeproms überhaupt nicht beeinflusst.
Aha, jetzt kommen wir der Sache näher. Mir fällt da auf, dass page_write
viel zu oft aufgerufen wird. Du wolltest doch große Blöcke zum EEprom
übertragen (30 Bytes, nicht 2-3 Bytes), oder nicht?
Wie ich befürchtet habe. Es wird kein einziges mal der Status überprüft.
2
Ein Pageüberlauf wird auch nicht behandelt. Bei einem Pageschreiben
3
werden nur die unteren Adreßbits hochgezählt. Ein Datenbyte wird also
4
wieder an die erste Adresse der Page geschrieben.
Richtig - der Status wurde zu diesem Zeitpunkt nicht abgefragt - habe
ich ergänzt - page write kommt immer mit dem Status 0 zurück.
Ein Pageüberlauf wird dadurch verhindert, dass vor Aufruf des page-write
die Anzahl der auf dieser Page noch verfügbaren Bytes berechnet und nur
die zulässige Anzahl im Aufruf übergeben wird.
Hallo Nemopuk,
ja - das wollte ich - offenbar kommen aber die Daten von der seriellen
Schnittstelle viel langsamer als erwartet.
Übrigens ist mir leider ein Fehler bei der Erstellung der Testdaten
passiert: die doppelte 95 war auch bereits im ersten Post des
RAM-Inhaltes vorhanden - ich bitte dafür vielmals um Entschuldigung!
Viele Grüße
Kai
Der ganze Algorithmus ist mir zu komplex. Ich durchblicke ihn nicht, du
(Kai) offenbar auch nicht, und auch sonst hat noch keiner den Fehler
darin gefunden.
Der Haupt-Fehler ist also dein undurchsichtiger Algorithmus. Warum so
kompliziert, wenn es auch einfach geht? Schmeiß den Algorithmus weg und
fange nochmal von vorne an.
Es ist echt blöd, dass der I²C Puffer von Arduino kleiner als die page
size ist. Das macht die Sache komplizierter, als sie sein könnte.
Vielleicht magst du auf ein anderes EEprom wechseln, das maximal 32
Bytes pro Page hat. Oder du benutzt nicht Wire, um größere Puffer
benutzen zu können. Schau dir auch mal
https://github.com/sparkfun/SparkFun_External_EEPROM_Arduino_Library an.
Man muss nicht jedes Rad neu erfinden.
Nemopuk schrieb:> Der ganze Algorithmus ist mir zu komplex. Ich durchblicke ihn> nicht, du> (Kai) offenbar auch nicht, und auch sonst hat noch keiner den Fehler> darin gefunden.
Ich auch nicht!
Aber kann hier ja mal meine Arduino I2C EEPROM/FRAM Baustelle rein
werfen.
Vielleicht kann man sich ja da was abschauen, oder gar nutzen wie sie
ist, denn sie funktioniert.
Hallo nemopuk, hallo Arduino F,
vielen Dank für eure Antworten und das ihr euch so intensiv mit meinem
Problem auseinandersetzt.
@nemopuk:
ich komme noch einmal darauf zurück, dass du in deinem letzten Post
ursprünglich sinngemäß geschrieben hattest:
"weshalb speicherst du nicht einfach alle Daten für eine Page im RAM und
schreibst sie anschließend in einem Rutsch ins EEPROM".
Das war auch mein Ansatz. Ich schildere diesen noch mal, in der
Hoffnung, dass ich dann selbst auf meinen Fehler komme:
1. Die ersten Zeilen handeln den Empfang der seriellen Schnitstelle. Die
Routine darf bis zum Ende der Übertragung nicht verlassen werden,
da andernfalls während der Loop zu viele Daten im seriellen Puffer
eintreffen (Dazu dient ein Timer).
Es müssen mindestens 2 Zeichen (= 1Byte) eingetroffen sein.
1
while((Serial.available()>=2)||// As long as data are available
2
(receive_timer>0)){// Or receive timeout is running
3
4
if(!rx_run){receive_timer=399;rx_run=1;}// Set receive timeout counter (only once) !
2. Jeweils 2 Zeichen werden in eine Hexadezimalzahl gewandelt.
1
for(inti=0;i<x/2;i++){// Process total number of bytes (=2 nibbles) received 2 4 6 8
2
3
bytey=Serial.available();// Current buffer state = no of char!
4
if(y>serbuf){serbuf=y;}// Buffer max size
5
6
nibble=text2hex(Serial.read());// read and convert 1. char = high nibble
7
hi=nibble;// High nibble
8
9
nibble=text2hex(Serial.read());// read and convert 2. char = low nibble
10
lo=nibble;// Low nibble
11
12
val=hi<<4|lo;// Combine hi + low = value
3. Die maximal erlaubte Anzahl von Bytes (Puffer- oder Page-Grenze) wird
berechnet:
1
bytes=bytes_in_page(addr+cnt);// Determinate max. number allowed in this page
4. Solange keine der Grenzen (Wire-Puffer, Page) erreicht ist, werden
die Daten in ein Array geschrieben (x ist die Anzahl der verfügbaren
Zeichen:
1
bytes=bytes_in_page(addr+cnt);// Anzahl der max. erlaubten Bytes
2
3
if((cur_byte<x/2)&&// Not all data processed | -> collect data
4
(cur_byte<bytes)){// Below Buffer or page-write border |
5
Ser_Arr[cur_byte]=val;// Add data to page-write buffer |
6
cur_byte++;// Next index |
7
}
5. Sobald eine der Grenzen (Wire-Puffer, Page) erreicht ist, werden die
Daten ins EEPROM geschrieben
1
if((cur_byte==x/2)||// current count = received bytes | -> page write
2
(cur_byte==bytes)){// Buffer or page-write border reached |
3
page_write(addr+cnt,cur_byte);// Perform page-write to ext ee |
4
//debug_page_wr(addr); // DEBUG! show page write status |
5
cnt=cnt+cur_byte;// Increase address by bytes added | -> prepare next packet
6
cur_byte=0;// No Bytes left |
7
}
8
}
Beim Schreiben fallen mir nun folgende mögliche Fehlerursachen ein:
a) da der Timeout aktuell nur am Anfang der Übertragung gesetzt wird,
könnte dieser vor dem Ende der Übertragung ablaufen.
b) da die Abfrage 1 auf Werte >= 2 abfragt, könnte die Wandlung 2)
fehlschlagen.
c) Es könnte irgendein Interrupt die korrekte Adressberechnung stören.
Zurzeit arbeite ich daran, ein Logging der zu schreibenden und der
tatsächlich geschriebenen Daten zu implementieren.
Erstes Zwischenergebnis ist, dass das wahrscheinlich immer erste Byte
eines neuen Datenpaketes falsch geschrieben wird.
Zur Verifikation möchte ich aber erst einmal eine größere Anzahl von
Übertragungen analysieren.
Viele Grüße
Kai
Nemopuk schrieb:> https://docs.arduino.cc/language-reference/en/functions/communication/wire/
So wie ich das sehe, ist endTransmission() das eigentliche Arbeitspferd,
alles andere füllt nur einen SRAM-Puffer.
Allerdings verstehe ich die recht knappe Beschreibung nicht ganz. Was
bedeutet "sends a restart message". Wird bei einem Fehler das ganze
Paket nun nochmal gesendet oder was?
Die Lib verwendet das HW-I2C und Interrupts. Allerdings bleibt das
endTransmission() in einer Schleife hängen, bis Stop gesendet wurde. Es
ist also keinen Deut schneller, als meine Lib mit SW-I2C.
Ich hab nur mal grob unter die Oberfläche gestöbert. Es ist ja doch ein
riesen Klotz an Code.
Wenn ich das I2C noch für andere Sachen brauche, gebe ich dem EEPROM 2
separate Pins, d.h. er hat ein I2C nur für sich. EEPROM ist ja typisch
nicht zeitkritisch, kann also durch alles andere unterbrochen werden.
Auch spart es Speicher, weil keine Doppelpufferung benötigt wird.
Und wenn noch viele andere Leitungen mit am I2C hängen, kann der EEPROM
leicht gestört werden und falsche Daten schreiben. I2C kennt ja keine
CRC. Ich speichere daher über einzelne Datenblöcke auch immer eine CRC
mit im EEPROM.
Peter D. schrieb:> Was> bedeutet "sends a restart message". Wird bei einem Fehler das ganze> Paket nun nochmal gesendet oder was?
Nein, das Flag entscheidet ob Wire.endTransmission(flag) eine Stopp
oder Repeated-Start Condition auf dem Bus setzt.
Kai schrieb:> Zurzeit arbeite ich daran, ein Logging der zu schreibenden und der> tatsächlich geschriebenen Daten zu implementieren.> Erstes Zwischenergebnis ist, dass das wahrscheinlich immer erste Byte> eines neuen Datenpaketes falsch geschrieben wird.> Zur Verifikation möchte ich aber erst einmal eine größere Anzahl von> Übertragungen analysieren.
Was ist für dich ein neues Datenpaket? Ein neues Datenfile (0..9F), oder
wird dein vorbereitetes File vom PC in einzelnen Häppchen verschickt?
Wenn du wirklich Hilfe willst, dann erwarte ich, dass du ein
vollständiges und kompilierbares Programm (ino) zeigst, welches das
Problem aufweist.
Dein reduziertes "Test.ino" File ist nicht kompilierbar, und keinem hier
ist bekannt welchen Einfluss die nicht gezeigten Softwareteile ausüben
könnten. Was du bisher gezeigt hast sind immer nur einzelne Brocken und
nicht das Gesamtbild.
Peter D. schrieb:> Es ist also keinen Deut schneller, als meine Lib mit SW-I2C.
Das ist wohl so. Allerdings haben die Pins (soweit ich weiß)
Entstörfilter, von denen eine Software Implementierung mit Bitbanging
nicht profitieren kann. Was je nach Anwendung völlig egal ist.
Du schreibst was davon, das du I2C nicht nur fürs EEPROM, sondern auch
für RTC und LCD nutzt. Ist das alles auf einem Board, oder "irgendwie"
funktionierend an 2 Meter Kabel zum Display angebunden?
Man kann sich über Normen hinwegsetzen, oft funktioniert das auch. Wenn
dann plötzlich die Komplexität ansteigt, gibt es unvermittelt Probleme,
die "unerklärlich" sind.
Hast du schon mal mit den Pullups rumgespielt? (verkleinert)
Weil wenn du bsp. auf der Leitung Echos hast, kann es sein, das das
Display damit klar kommt, dein EEPROM aber nicht. Also testweise mal
entbehrliche Busteilmehmer abklemmen und im Code auskommentieren und
gucken, ob der Schreibzugriff dann funktioniert.
Hallo Loco M, hallo Gerald B,
@Loco:
Für mich ist diesem Fall ein neues Datenpaket ein neues Aufrufen des
Page-Write mit bis zu 30 Bytes.
Ich kann deine Erwartung vollkommen verstehen, jedoch ist das
vollständige Programm mittlerweile 32 kB groß.
Die Steuerung der Funktionen erfolgt über eine Menüstruktur.
Das Problem tritt in dem Programmteil auf, in welcher die serielle
Datenübertragung an das EEPROM erfolgt.
Um alle möglichen Nebeneffekte auszuschließen, wird diese Routine für
die Dauer eines Timeout nicht verlassen.
@ Gerald B:
1
Du schreibst was davon, das du I2C nicht nur fürs EEPROM, sondern auch
2
für RTC und LCD nutzt. Ist das alles auf einem Board, oder "irgendwie"
3
funktionierend an 2 Meter Kabel zum Display angebunden?
4
Man kann sich über Normen hinwegsetzen, oft funktioniert das auch. Wenn
5
dann plötzlich die Komplexität ansteigt, gibt es unvermittelt Probleme,
6
die "unerklärlich" sind.
7
Hast du schon mal mit den Pullups rumgespielt? (verkleinert)
8
Weil wenn du bsp. auf der Leitung Echos hast, kann es sein, das das
9
Display damit klar kommt, dein EEPROM aber nicht. Also testweise mal
10
entbehrliche Busteilmehmer abklemmen und im Code auskommentieren und
11
gucken, ob der Schreibzugriff dann funktioniert.
Es gibt 2 Varianten des Aufbaus:
a) eine fliegende mit Arduino Mega (Kabellänge ca. 25cm)
Der Arduino Mega hat gemäß Dokumentation 10k-Pullups.
b) eine doppelseitige Platine mit 328P (Gesamtlänge des I2C ca. 10cm)
Es befinden sich 10k-Pullup in der Mitte des Busses.
Der Fehler tritt identisch bei beiden Aufbauten auf.
Abklemmen des LCD ist nicht möglich, da das Gerät dann nicht mehr
bedienbar ist.
Code auskommentieren ist aus dem selben Grund nur eingeschränkt möglich.
@nemopuk:
genau - deshalb kämpfe ich noch mit dem Logging…..
Sobald das Logging läuft, weis ich mehr und werde berichten.
Viele Grüße
Kai
Kai schrieb:> Der Arduino Mega hat gemäß Dokumentation 10k-Pullups.Kai schrieb:> Es befinden sich 10k-Pullup in der Mitte des Busses.
Aha. Viel zu hoch! Und nimm Zwei!
Gruss Chregu
Christian M. schrieb:> Kai schrieb:>> Es befinden sich 10k-Pullup in der Mitte des Busses.>> Aha. Viel zu hoch! Und nimm Zwei!
Jain. Die meisten Sachen laufen auch mit 10k Pull Ups und 100kHz. Bei
allem was man hier so gelesen hat, liegt der Fehler zu 99% in der
Software.
I2C ist mit max. 3mA bei LOW definiert, also min. 1,6k bei 5V. 2-4,7k
sind OK.
Hallo Christian M und Falk B,
auch vielen Dank für eure Antworten.
1
Jain. Die meisten Sachen laufen auch mit 10k Pull Ups und 100kHz. Bei
2
allem was man hier so gelesen hat, liegt der Fehler zu 99% in der
3
Software.
Definitiv - ich war sicher, dass der Fehler in meiner Software liegt
daher auch meine Frage hier im Forum. Der Wert der Byte Zählvariable cnt
hat im Fehlerfall einen zu geringen Wert (z. B. 158 anstelle 160).
Manchmal sind 10 Übertragungen hintereinander in Ordnung, mal nicht.
Ich vermute den Fehler beim Sammeln der Daten - hier wird offenbar
manchmal die Variable cur_byte nicht erhöht:
1
if((cur_byte<x/2)&&// Not all data processed | collect data
2
(cur_byte<bytes)){// Below page-write border |
3
Ser_Arr[cur_byte]=val;// Add data to pagebuffer |
4
cur_byte++;// Next index |
5
}
Mittlerweile funktioniert das Logging auch (siehe Anhang).
Das Ergebnis ist, dass die fehlenden Daten auch nicht geschrieben
wurden.
Die Routine verschluckt manchmal ein von der Schnittstelle korrekt
gelesenes Byte (siehe RAM-Inhalt). Nach der Ursache suche ich gerade.
Viele Grüße
Kai
Hallo,
reduziere den Code soweit runter, dass du nur noch auf das EEprom
schreibst und liest. Erstelle eine Tabelle im RAM womit du auf das
EEprom Testdaten schreibst die dann wieder gelesen werden. Wenn es dann
noch den Fehler zeigt, zeige den testbaren Sketch und den
Hardwareaufbau. Schaltplan, Kabellängen usw. Zur Zeit macht das keinen
Spass hier durchblicken zu wollen.
Nochmal: Es scheitert nicht an den EEprom Zugriffen, sondern an dem
Algorithmus für die Zähler und Adressen. Der Fehler steckt in dem
undurchsichtigen Algorithmus.
Hallo,
letzter Schuss ins Blaue ohne Angabe zum EEprom, Sketch usw.
Beachtest du wirklich die Pagegrenzen korrekt?
Sieht so aus wenn du 30 Bytes schreibst aber mit fortlaufender Adresse.
Angenommen dein EEprom hat 64Byte Pagegröße.
Dann liegen die in den Bereichen
1
0 … 63 - Page 0
2
64 … 127 - Page 1
3
128 … 191 - Page 2
4
192 … 254 - Page 3
5
usw.
Wenn du das EEprom mit 0xFF füllst, dann müssten mit 30 Byte Daten pro
Page folgende Adressen 0xFF zeigen.
1
0 … 29 - deine Daten
2
30 … 63 - 0xFF
3
64 … 93 - deine Daten
4
94 … 127 - 0xFF
5
usw.
Ansonsten schreibst du über die Pagegrenzen hinweg, spätestens mit dem
3. Satz mit 30 Bytes.
Schreib das einmal ohne Page Write, jedes Byte einzeln.
Wenn ich das richtig sehe hat jedes der Module und der Mega 10k Pullup.
Das macht insgesamt 2k. Evtl. etwas wenig.
Ist auf dem RTC Board nicht noch ein EEPROM?
Im bitteren Fall gar mit der gleichen Adresse?
Hallo Kai,
greife den Gedanken von Arduino F. auf und baue zusätzlich alles
unnötige ab. Sodass du nur den Kern des Problem testest. Rüste ab, teste
und wenn immer noch nicht zeige den auf das Problem reduzierten Sketch.
Dann kann das Forum bestimmt helfen.
Arduino F. schrieb:> oder Repeated-Start Condition auf dem Bus setzt.
Das ist ja blöd, was soll das bringen?
Man muß doch im Fehlerfall das ganze Paket nochmal senden.
Es müßte also eine Funktion geben, die die Daten im Puffer wieder als
gültig markiert.
Ansonsten muß man ja von hinten durch die Brust ins Auge alles nochmal
präparieren.
Peter D. schrieb:> Arduino F. schrieb:>> oder Repeated-Start Condition auf dem Bus setzt.>> Das ist ja blöd, was soll das bringen?
Du hältst es also für blöd diese Bedingungen setzen zu können?
Da solltest du nochmal drüber nachdenken!
Bedenke:
Wire handelt den I2C Bus ab, es hat keine Ahnung von EEPROM oder
sonstigen I2C Dingern und ihre Bedürfnisse.
Mein Rat:
1. Wenn dir Wire nicht gefällt, dann nutze es nicht.
2. Bei Verbesserungsvorschlägen wende dich an das Arduino Team. z.B.
ändere Wire auf Github. Und Ja, dann hat jeder was davon.
Arduino F. schrieb:> Du hältst es also für blöd diese Bedingungen setzen zu können?
Ja.
Ich wüßte nicht, wie ein Repeated-Start bei einem Fehler weiter helfen
soll.
Es setzt einfach nur den Bus auf blockiert.
Das Repeated-Start wird nur benötigt, um nach der EEPROM-Adresse auf
Lesen umzuschalten, also wenn eben kein Fehler aufgetreten ist.
Peter D. schrieb:> Das Repeated-Start wird nur benötigt, um nach der EEPROM-Adresse auf> Lesen umzuschalten, also wenn eben kein Fehler aufgetreten ist.
Damit widersprichst du dir selber!
1. Du findest es blöd
2. Du hältst es für notwendig
Die irrsinnige Fantasie, dass das repteated in der Doku irgendwas mit
Fehlern zu tun hat, existiert nur in deinem Kopf.
Veit D. schrieb:> Hallo Kai,>> greife den Gedanken von Arduino F. auf und baue zusätzlich alles> unnötige ab. Sodass du nur den Kern des Problem testest. Rüste ab, teste> und wenn immer noch nicht zeige den auf das Problem reduzierten Sketch.> Dann kann das Forum bestimmt helfen.
Hallo Arduino F und Veit D.,
vielen Dank für eure Tipps.
Ich habe nun alle anderen Komponenten bis auf das I2C-LCD abgebaut und
im Programm auskommentiert. Die Page-Write-Routine liefert ohne EEPROM
erwartungsgemäß 2 (received NACK on transmit of address) zurück.
Der Fehler tritt immer noch auf: cnt zählt sporadisch zu wenig Bytes.
Ergebnisse I2C-Scan:
I2C-LCD 0x27
I2C-EEPROM 0x50 und 0x58
I2C-RTC 0x57 und 0x68
Ich werde gleich ein Minimalprogramm erstellen, welches nur noch die
relevanten Programmteile enthält und sobald es läuft anhängen.
Viele Grüße
Kai
Kai schrieb:> Ich werde gleich ein Minimalprogramm erstellen, welches nur noch die> relevanten Programmteile enthält
Macht man das nicht als Allererstes? :-O
Gruss Chregu
Kai schrieb:> while ((Serial.available() >= 2) ||> (receive_timer >0)) {
Ich hasse Protokolle mit Timeout. Das gibt eigentlich immer Ärger, wenn
nicht gleich, dann später.
Für Textnachrichten bietet es sich an, das <CR> als Endekennzeichen zu
benutzen. Man sammelt also einfach die Bytes und nach <CR> fängt man
erst an, die Nachricht zu parsen.
Hier mal ein Beispiel:
> x = Serial.available(); // Store total number of char
received (max. 64)
> for (int i=0; i < x/2; i++){ // Process total number of bytes
(=2 nibbles) received 2 4 6 8
Ohne jetzt genauer nachgedacht zu haben: Ist der Fall "ungerades x"
sauber abgedeckt?