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.
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.
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.
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.
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?
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.
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()?
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.
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?
im Fehlerfall werden weniger Bytes ins eeprom geschrieben.
Du verwendest offensichtlich ein externes "grosses" EEPROM.
Dabei gibt es zwei wichtige mögliche Fehlerquellen:
deine Programmierung entspricht nicht (ganz) den
Anforderungen deines EEPROMs.
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.
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.
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.
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 ....
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.
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.
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.
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.
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.
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:
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
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.
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.
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!
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.
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.
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.
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?
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.
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.
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
11
Datenblatt steht) einzubauen. Nur zur Probe, ob dort der Knackpunkt
12
liegt. Du kannst zusätzlich die Baudrate herabsetzen, wenn nötig
13
(Pufferüberlauf).
14
15
Du hast eine leere Textdatei gezippt. Bitte reiche die richtige Datei
16
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.
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]]
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.
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).
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.
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.
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!
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.
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.
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:
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) !
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
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
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
}
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.
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.
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.
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.
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.
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.
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.
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.
die Anzahl der für die jeweilige Page erlaubten Bytes wird vor dem Aufruf von Page-Write berechnet. Dieser wurde unabhängig von der Datenübertragung mehrfach getestet und funktioniert.
Anbei das Ergebnis des page write:
Page write test 22000 181
EEPROM
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 16
10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 32
20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 48
30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 64
40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 80
50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 96
60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 112
70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 128
80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 144
90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F 160
A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF 176
B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF 192
C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF 208
D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF 224
E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF 240
F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF 256
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 272
10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 288
20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 304
30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 320
40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 336
50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 352
60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 368
70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 384
80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 400
90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F 416
A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF 432
B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF 448
C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF 464
D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF 480
E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF 496
F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF 512
Das im fliegenden Aufbau mit Arduino Mega verwendete EEPROM ist dieses:
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.
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.
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.
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?
Ich werde gleich ein Minimalprogramm erstellen, welches nur noch die
relevanten Programmteile enthält
Macht man das nicht als Allererstes? :-O
Gruss Chregu
Das Programm ist immer weiter gewachsen und mehr Komponenten sind hinzugekommen. Bisher konnte ich alle Probleme innerhalb von einigen Stunden lösen. Nachdem die auf dem Chip gespeicherte Datenmenge immer grösser wurden, entstand dann zuletzt mein Wunsch, diese Daten "mal eben" auf dem PC zu sichern. Das Sichern klappt auch prima.
Beim Rücklesen in den Chip fing dann mein Problem an:
leider ist da das Byte-Write viel zu langsam und ich musste auf Page-Write umschwenken - und da hänge ich seit mittlerweile mehreren Tagen....
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?
vielen Dank für diesen Hinweis - noch nicht, ist aber als nächster Schritt vorgesehen.
Mittlerweile ist das Minimalprogramm fertig (siehe Anhang). Es läuft auf einem Arduino Uno, sollte auch auf einem Mega laufen. Angeschlossen ist nur das I2C-EEPROM von Reichelt:
Am Anfang wird ein I2C-Scan durchgeführt, welcher die Adressen 0x50 und 0x58 findet.
Hier sind die Testdaten, welche in den seriellen Monitor kopiert werden müssen:
Am Anfang wird ein I2C-Scan durchgeführt,
welcher die Adressen 0x50 und 0x58 findet.
Das EEPROM hat keine 2 Adressen.
Da ist doch was faul.
Google meint zu dem Thema:
Übersicht mit KI
Ein I2C EEPROM kann tatsächlich zwei Adressen anzeigen, wenn es sich um ein Gerät mit konfigurierbaren Adresspins handelt, die über Lötpads oder Jumper eingestellt werden können. Die eine Adresse ist die, die für die normale Kommunikation mit dem Arduino verwendet wird, und die andere ist eine alternative Adresse, die durch die Einstellung der Adresspins aktiviert wird.
Übersicht mit KI
Ein I2C EEPROM kann tatsächlich zwei Adressen anzeigen, wenn es sich um
ein Gerät mit konfigurierbaren Adresspins handelt, die über Lötpads oder
Jumper eingestellt werden können. Die eine Adresse ist die, die für die
normale Kommunikation mit dem Arduino verwendet wird, und die andere ist
eine alternative Adresse, die durch die Einstellung der Adresspins
aktiviert wird.
Ist Käse. Aber wenn deine Adresspins am EEPROM nicht fest auf GND oder VCC liegen, wackelt die Adresse und dein EEPROM reagiert zufällig.
Am Anfang wird ein I2C-Scan durchgeführt,
welcher die Adressen 0x50 und 0x58 findet.
Das EEPROM hat keine 2 Adressen.
Da ist doch was faul.
Kann das nicht mit dem I2C High Speed Modus zusammenhängen? Mein FRAM
reagiert beim Scan auch mit einer 2. Adresse als Antwort.
Beim entwickeln meiner I2C EEPROM Klasse habe ich gefühlte 20 Datenblätter auswendig gelernt.
Auch das zu diesem konkreten EEPROM.
Da drin steht nichts von 2 Adressen.
Klar: Es gibt EEPROMs, welche mit mehreren Adressen erscheinen. Aber das ist dann auch wohl dokumentiert.
schalte einmal in der Arduino IDE alle Warnungen zum kompilieren ein. Korrigiere alle Warnungen. Es gibt mehrere unbenutzte und nicht initialisierte Variablen.
Kannst du bei Funktionsaufrufen fehlerhafte Parameterübergaben ausschließen?
Worauf ich hinaus möchte, sollte eine minimale Prüfung eingebaut werden bevor Werte weiterverarbeitet werden? Könnte im dümmsten Fall zur falschen Anzeige führen.
Was schlimmer ist.
Du hast in der Einlesefunktion
3x Serial.available()
2x Serial.read()
Warum das denn?
Warum erste Bedingung >=2 ?
Ich glaube dir fehlt ein Übertragungsprotokoll.
Solange einlesen bis ein definiertes Ende erkannt wurde. Standardmäßig Linefeed.
Dann eingelesene Daten verarbeiten. Bis dahin in einen Buffer einlesen.
Übersicht mit KI
Ein I2C EEPROM kann tatsächlich zwei Adressen anzeigen, wenn es sich um
ein Gerät mit konfigurierbaren Adresspins handelt, die über Lötpads oder
Jumper eingestellt werden können. Die eine Adresse ist die, die für die
normale Kommunikation mit dem Arduino verwendet wird, und die andere ist
eine alternative Adresse, die durch die Einstellung der Adresspins
aktiviert wird.
Ist Käse. Aber wenn deine Adresspins am EEPROM nicht fest auf GND oder
VCC liegen, wackelt die Adresse und dein EEPROM reagiert zufällig.
Am Anfang wird ein I2C-Scan durchgeführt,
welcher die Adressen 0x50 und 0x58 findet.
Das EEPROM hat keine 2 Adressen.
Da ist doch was faul.
Kann das nicht mit dem I2C High Speed Modus zusammenhängen? Mein FRAM
reagiert beim Scan auch mit einer 2. Adresse als Antwort.
Beim entwickeln meiner I2C EEPROM Klasse habe ich gefühlte 20
Datenblätter auswendig gelernt.
Auch das zu diesem konkreten EEPROM.
Da drin steht nichts von 2 Adressen.
Klar: Es gibt EEPROMs, welche mit mehreren Adressen erscheinen. Aber das
ist dann auch wohl dokumentiert.
Hallo,
ich ging davon aus das man alle I2C Decives für den High Speed Modus erst gezielt ansprechen muss um diesen Modus zu aktivieren. Ist bei dem EEprom nicht der Fall zu sein. Okay. Dann wundert es tatsächlich wo die 2. Adresse herkommt wenn nur der Uno mit dem EEprom verkabelt ist.
Kai:
Das Display ist nicht dran?
Nur das EEprom am Bus?
Was hast du für einen Uno?
Nicht das hier ein Sonderling verwendet wird.
Kann das nicht mit dem I2C High Speed Modus zusammenhängen? Mein FRAM
reagiert beim Scan auch mit einer 2. Adresse als Antwort.
Bei 400 kbit müssen die Pullup "stronger" sein, als bei 100 kbit
Ja ist klar. Reagiert dennoch mit einer Antwort auf seine 2. Adresse, was auch normal ist - wenn es wie mein FRAM so angesprochen werden möchte. Ich möchte aber den Thread damit nicht weiter ablenken. ;-)
schalte einmal in der Arduino IDE alle Warnungen zum kompilieren ein.
Korrigiere alle Warnungen. Es gibt mehrere unbenutzte und nicht
initialisierte Variablen.
Kannst du bei Funktionsaufrufen fehlerhafte Parameterübergaben
ausschließen?
Worauf ich hinaus möchte, sollte eine minimale Prüfung eingebaut werden
bevor Werte weiterverarbeitet werden? Könnte im dümmsten Fall zur
falschen Anzeige führen.
Was schlimmer ist.
Du hast in der Einlesefunktion
3x Serial.available()
2x Serial.read()
Warum das denn?
Warum erste Bedingung >=2 ?
Ich glaube dir fehlt ein Übertragungsprotokoll.
Solange einlesen bis ein definiertes Ende erkannt wurde. Standardmäßig
Linefeed.
Dann eingelesene Daten verarbeiten. Bis dahin in einen Buffer einlesen.
Alle Warnungen sind eingeschaltet.
Meine Überlegungen dazu waren folgende:
a) Der Wert von Serial.available kann sich während der Bearbeitung
vergrössern.
b) Das 1. Serial.available wird beim Eintritt abgefragt (0 = Ende)
Das 2. lässt die Bearbeitung nur zu, wenn mindestens 1 Byte.
Das 3. dient nur zur Ermittlung des max. Bufferwertes.
Ich benötige 2 x Serial.read, da ein Byte aus 2 ASCII-Zeichen besteht.
Die Bedingung >=2 lässt die Bearbeitung nur zu, wenn mindestens 1
ganzes Byte (=2 ASCII-Zeichen) vorhanden sind.
Die später einzulesenden Dateien sind so groß, dass sie nicht in das RAM des ATMEGA328P passen. Daher werden die Daten aus einem 30Byte-Puffer direkt ins EEPROM geschrieben.
Reagiert dennoch mit einer Antwort auf seine 2. Adresse,
was auch normal ist.
Normal ist das im Datenblatt beschriebene Verhalten.
Wenn das da drin beschrieben ist, ist alles ok.
Ist es nicht beschrieben, ist da ein Problem!
Wo siehst du den Fehler in dieser Logischen Schlussfolgerung?
Reagiert dennoch mit einer Antwort auf seine 2. Adresse,
was auch normal ist - wenn es wie mein FRAM so angesprochen werden
möchte.
Normal ist das im Datenblatt beschriebene Verhalten.
Wenn das da drin beschrieben ist, ist alles ok.
Ist es nicht beschrieben, ist da ein Problem!
Wo siehst du den Fehler in dieser Logischen Schlussfolgerung?
Und du verwendest keine guten Kabel/Steckverbinder! Das sind doch wahrscheinlich alles ausgeleierte Eisendrähte. Geh da mal mit einem Magneten ran, dann weisst du, ob es Kupfer oder Eisen ist. Löte lieber anständige Kabel dran.
"The Wire library implementation uses a 32 byte buffer, therefore any communication should be within this limit. Exceeding bytes in a single transmission will just be dropped."
Das Eprom kann auch keine 2 Adressen haben zumal die 0x58 außerhalb des EEprom Adressbereichs liegt.
Woher du deinen 30 Byte buffer hast erklährt sich mir nicht
Ich hab dein page_write mal etwas umgebaut.
"The Wire library implementation uses a 32 byte buffer, therefore any
communication should be within this limit. Exceeding bytes in a single
transmission will just be dropped."
Das Eprom kann auch keine 2 Adressen haben zumal die 0x58 außerhalb des
EEprom Adressbereichs liegt.
Woher du deinen 30 Byte buffer hast erklährt sich mir nicht
Ich hab dein page_write mal etwas umgebaut.byte page_write(unsigned int
addr, byte nBytes)
{
//todo check params
Wire.beginTransmission(EXT_EE_ADDR); // EEP address
Wire.write((byte)(addr>>8)); // Addr High
Wire.write((byte)(addr&0xFF)); // Addr low
if (Wire.endTransmission(false)==success)// send 3 bytes + repeated
Start
{
for(byte i=0; i<nBytes; i++) // Write all bytes
{
Wire.write((byte)(Ser_Arr[i])); // Byte from array
}
byte status = Wire.endTransmission(); // seend the buffer
delay(5); // Important
return status; // Status of page-write
}
return 0xff; //something went wrong
}
Vielen Dank für deine Version, welche ich später testen werde.
Die von mir verwendete Angabe eines 30 Byte-Puffers habe ich von:
[[https://wolles-elektronikkiste.de/eeprom-teil-2-externe-i2c-eeproms]]
wenn dein Scanner Adresse 0x50 und 0x58 findet und mein Code bis 0x77 nichts, sollte man stutzig werden.
Ändere meinen Code auf < 128 ab. Was zeigt er an?
Ändere deinen Code auf < 78 ab. Was zeigt er an?
Laut Spezifikation ist im 7 Bit Bereich an Adresse 77 Ende.
dein Scanner scannt folgende Adressen:
for(address = 1; address < 78; address++ )
Das ist dezimal und nicht 0x78.
Leider läuft der Sketch bei mir nicht, weil einige Klammern fehlen.
das kann gar nicht sein. Mein Code kompiliert ohne jede Warnung.
Das Einzigste was ich vermehrt habe ist der Range.
Das muss so lauten, in Hex Schreibweise - weil 78 != 0x78.
Haste richtig erkannt. ;-)
for (address = 0x08; address < 0x78; address++ )
Beide Scanner sollten das Gleiche anzeigen. Wir müssen uns auch nicht daran festbeißen. Nur bleibt dann leider unklar was mit dem Bus los ist. Wird ein Problem verschleppt oder nicht.
jedoch ist das vollständige Programm mittlerweile 32 kB groß
Verstehe ich das richtig: eine (kleine) Teilfunktion darin bringt sporadisch Fehler, seit 130 Beiträgen wird diese isoliert betrachtet, und wenn sie tatsächlich isoliert läuft (in einem Minimalprogramm), ist der Fehler verschwunden?
jedoch ist das vollständige Programm mittlerweile 32 kB groß
Verstehe ich das richtig: eine (kleine) Teilfunktion darin bringt
sporadisch Fehler, seit 130 Beiträgen wird diese isoliert betrachtet,
und wenn sie tatsächlich isoliert läuft (in einem Minimalprogramm), ist
der Fehler verschwunden?
das wird nicht seit 130 Beiträgen isoliert betrachtet. Das wird jetzt erst isoliert betrachtet. Alles davor war Spekulation, weil kein Testcode usw. vorhanden war. Mir fehlt das Mitmachverständnis. Es wird sich zu viel auf "im Internet gefunden" berufen.
Man kann dem Bus Adressfehler auf den Grund gehen.
Man kann das Programm erweitern bis der Fehler wieder da ist.
Man kann sich im Kreis drehen.
ich bin jetzt sprachlos. Du behauptest mein Code funktioniert nicht.
Leider läuft der Sketch bei mir nicht, weil einige Klammern fehlen.
Daraus schließt man - er funktioniert eben nicht.
Ich habe mir Gedanken gemacht warum er nicht laufen soll. Völlig umsonst. Derweile funktioniert er und es dreht sich nur darum das man den Syntax nicht versteht. Ich erwarte das eine Formulierung im Forum eindeutig ist. Wir haben nur die schriftliche Kommunikation zur Verfügung.
leider gab es in unserer Kommunikation mehrere Missverständnis - wahrscheinlich habe ich mich zu kurz gefasst.
Deinen Sketch kann ich auch fehlerfrei compilieren.
Er zeigt alle 5s: Scanning... No I2C devices found.
Und dies mit und ohne angeschlossenes EEPROM.
Daran hat sich nichts geändert.
Da ich dies verwendete Schreinweise noch nie gesehen
hatte, vermutete ich hier einen Fehler.
Mein Sketch liefert mit angeschlossenen EEPROM 0x50 und 0x58.
Ohne angeschlossenes EEPROM blieb dieser hängen. Ursache war
ein fehlendes Wire.Begin was vorher wahrscheinlich in einer
der RTC- oder I2C-Routinen enthalten war.
Nun liefert dieser ohne angeschlossenes EEPROM:
No I2C Address found
Ich hänge deine Version (aus deinem Post kopiert) und meine aktuelle Version an (mit Wire.Begin und EEPROM löschen am Ende).
Die zwei Adressen wurden auch hier beobachtet:
https://docs.buspirate.com/docs/devices/at24c256/
"
This AT24C256 EEPROM is a non-original variant and includes an additional address: 0x58 (0xB0/B1). This extra address can be utilized to store data, such as a serial number. While this feature is not supported in the official AT24C256 chip, it is commonly found in certain “AT24C256 boards” sold on platforms like AliExpress.
Just use the 0x50 (0xA0/A1) address for reading and writing data to the EEPROM and ignore the 0x58 (0xB0/B1) address.
"
Allgemein werden mir die Datentypen zu wild verwendet. Integer mit bool und bool mit Integer.
Diese unbenutzte Funktion ist mir nicht ganz klar.
1
bytebytes_in_page(unsignedintaddr){// Number of bytes left in page
2
if(addr<EXT_EE_SIZE){// Address is valid
3
intpage=addr/EXT_EE_PAGE;// Current page
4
intborder=(page+1)*EXT_EE_PAGE;// Next page address = border
5
intbytes=border-addr;// Number of bytes total
6
intpakets=(bytes/EXT_EE_LIMIT)+1;// Number pf pakets
7
intrest=bytes;intcount=0;// Rest of bytes
8
9
if(rest>=EXT_EE_LIMIT){// Enough space for 30 bytes
10
count=EXT_EE_LIMIT;
11
}//
12
elseif(rest<EXT_EE_LIMIT){// Less space than 30 bytes
13
count=rest;
14
}//
15
returncount;// Number of bytes
16
}
17
else{
18
Serial.print(F("Illegal Address"));
19
}
20
}
Sie soll wahrscheinlich das Page Limit prüfen. Nur wenn ein 'rest' größer dem Limit ist, kann kein Platz mehr sein.
1
Ser_Arr[]
soll der Einlesebuffer sein? Wo wird geprüft ob er noch Platz hat?
Dann steht in test_data_rec_() ein 10ms delay drin. Warum?
Das sind alles so kleine komische Dinge die im Sketch verstreut für Ärger sorgen wenn mehr gemacht werden soll wie nur lesen und schreiben alleine. Du brauchst eine saubere Einleseroutine mit sauberen Protokoll.
Andererseits hat Arduino einen Empfangspuffer von 32 Zeichen, und er hat laut log nie mehr als 3 Zeichen im Puffer.
Ich schrieb weiter oben, das Interrupts nicht das Beschreiben der Zählvariablen verhindern können. Aber Interrupts können geringer priorisierte Interrupts verzögern, und Software kann alle Interrupts blockieren. Wenn der (nicht gezeigte und inzwischen entfernte) Code das getan hatte, ist es schon denkbar, daß die Serielle Eingabe einzelne Zeichen verpasst.
Außerdem könnte ein Stack/Heap Überlauf passieren, wobei ich das hier für rekativ unwahrscheinlich halte. Denn der Fehler passiert auch ohne die ganzen Debug Ausgaben, für die eine Menge RAM verwendet wird.
Allerdings hat Kai mehrfach geschrieben, dass ganz sicher alle Zeichen "im RAM" empfangen wurden. Ich vermute, er meint damit das rx_test array. Wenn dem so ist, können wir sowohl die serielle Eingabe als auch die Ausgabe ins Eeprom als Fehlerquelle ausschließen. Es bleibt nur noch der Algorithmus (Counter und for-Schleife) übrig, den wir alle nicht durchblicken.
Doch der müsste im isolierten Test den selben Fehler zeigen. Was nun, haben wir doch einen Stack/Heap überlauf? Das sollte kontrolliert werden.
Der Algorithmus erfüllt für meinen Geschmack zu viele Aufgaben in einem Rutsch. Dadurch ist er kaum zu debuggen. Ich würde diesen Code so aufspalten, dass einzelne Teile separat testbar werden. Wenigstens die Eingabe und Ausgabe sollte abkoppeln können, damit ein Unit Test ermöglicht wird.
und vielen Dank für die vielen Antworten, welche ich gleich im Detail beantworten werde.
Wichtigste Frage an Veit:
hast du die erste oder zweite Version meines Sketches verwendet?
Die erste habe ich nur aus dem Reset geprüft.
Die zweite setzt am Ende cnt zurück und löscht das EEPROM. Mit dieser Version habe ich viele Male (20-30) hintereinander geprüft und immer korrekte Ergebnisse erhalten.
Wenn im EEprom Daten fehlen, sind das immer komplette Bytes, entsprechend 2 Zeichen vom seriellen Port. Wenn der Puffer vom seriellen Port überlaufen würde, oder wenn Interrupt zu lange blockiert/verzögert würden, dann müsste das doch zumindest ab und zu auch mal eine ungerade Anzahl von Zeichen betreffen, was bei den folgenden Bytes (im EEprom) zu ganz anderen Werten führen würde. Der Fall ist aber kein einziges mal beobachtet worden. Außerdem waren die Daten im RAM ja auch immer in Ordnung.
Beides bestärkt meine Annahme, dass der serielle Empfang korrekt funktioniert. Ich bin ziemlich sicher, dass der Fehler irgendwo unterhalb der Zeilen
1
nibble=text2hex(Serial.read());// read and convert 1. char = high nibble
2
hi=nibble;// High nibble
3
4
nibble=text2hex(Serial.read());// read and convert 2. char = low nibble
5
lo=nibble;
6
7
val=hi<<4|lo;// Combine hi + low = value
8
rx_test[ram_idx]=val;// DEBUG! store to RAM
9
ram_idx++;
liegt - wenn es nicht ein Stack/Heap Überlauf ist.
Der Arduino I2C Buffer hat Default 32Byte Größe. Kann man notfalls ändern, ist aber nicht notwendig. Dieser Buffer puffert die Daten nur solange bis man sie abholt beim lesen bzw. über die Hardwareschnittstelle rausschreiben lässt. Falls dieser Buffer volllaufen sollte, kommt das Abholen nicht hinterher.
Zum Einlesen von Daten für die eigenen Zwecke verwendet man zusätzliche seinen eigenen Buffer. Damit hat die Main-Loop Zeit Daten aus dem "Arduino I2C Buffer" zu lesen und plausibel vorgeprüft in den eigenen Buffer zu schreiben. Wenn das Übertragungsprotokoll seine Endekennung erreicht hat, ist diese eine Übertragung zu Ende. Erst dann wird der eigenen Buffer verarbeitet. Damit hat man alle Zeit der Welt die Daten ins EEprom zu schreiben oder anderweitig zu verarbeiten.
Man bekommt erst ein Zeitproblem wenn die Daten schneller reinkommen wie man wegschreiben kann. Wenn man jedoch ständig ein EEprom beschreiben muss, dann ist die Wahl auf ein EEprom falsch.
Allerdings hat Kai mehrfach geschrieben, dass ganz sicher alle Zeichen
"im RAM" empfangen wurden. Ich vermute, er meint damit das rx_test
array. Wenn dem so ist, können wir sowohl die serielle Eingabe als auch
die Ausgabe ins Eeprom als Fehlerquelle ausschließen.
Fehler ausschließen kann ich eben nicht, wenn man es durch einfache Wiederholung durcheinanderbringt. Man sieht doch da schon einen Versatz. Wo kommt die 30 am Anfang her? Es wird allgemein zu viel vertraut, dass es schon irgendwie funktionieren wird. Wenn es einen Versatz gibt, dann muss man alle Lese/Schreibroutinen prüfen. Das darf man nicht überspringen.
Wichtigste Frage an Veit:
hast du die erste oder zweite Version meines Sketches verwendet?
Ich habe deine letzte Version
Beitrag "Re: Arduino: page-write in I2C eeprom verliert sporadisch ein Byte"
verwendet mit cnt=0 und EEprom mit Wire rausgenommen.
Habe sie aktuell bezüglich Warnungen aufgeräumt. Statt der fehlerhaften 30 kommt jetzt 00 am Anfang doppelt.
Das einlesen ist unsauber.
Lies alles ein bis zur LineFeed Endeerkennung und verarbeite dann erst.
das vorherige Log ergab, dass im Fehlerfall das cur_byte++ am Ende nicht
ausgeführt wird
Das kann aber nicht sein. Der Mikrocontroller führt den Code exakt so aus, wie du ihn geschrieben hast. Interrupts verhindern dort nichts.
Wenn (was zu überprüfen wäre) diese Zeilen das Problem enthalten, sehe ich zwei mögliche Gründe:
Ein Speicher-Überlauf bewirkt, dass diese Variable überschrieben wird. Allerdings wären die anderen Variablen, die direkt davor oder dahinter liegen höchstwahrscheinlich ebenso betroffen. Beachte, daß der Compiler die Reihenfolge der Variablen im RAM ändern kann. Man kann sich ein MAP File erstellen, um das zu kontrollieren.
dein Scanner liefert mit angeschlossenem EEPROM nach Erhöhung des
Adresszählers auf <0x7F:
I2C Scanner
Scanning...
I2C device found at address 0x50
I2C device found at address 0x58
done
Alle 3 Scanner finden ohne EEPROM keine Adresse.
Viele Grüße
Kai
Danke. Das mit den Adressen passt dann soweit. Dank Erklärung von Helmuth bezüglich 2. undokumentierter Adresse. Die 0x58 darfst du damit für nichts anderes konfigurieren.
Außerdem könnte ein Stack/Heap Überlauf passieren, wobei ich das hier
für rekativ unwahrscheinlich halte. Denn der Fehler passiert auch ohne
die ganzen Debug Ausgaben, für die eine Menge RAM verwendet wird.
Ich schrieb weiter oben, das Interrupts nicht das Beschreiben der
Zählvariablen verhindern können. Aber Interrupts können geringer
priorisierte Interrupts verzögern, und Software kann alle Interrupts
blockieren. Wenn der (nicht gezeigte und inzwischen entfernte) Code das
getan hatte, ist es schon denkbar, daß die Serielle Eingabe einzelne
Zeichen verpasst.
Außerdem könnte ein Stack/Heap Überlauf passieren, wobei ich das hier
für rekativ unwahrscheinlich halte. Denn der Fehler passiert auch ohne
die ganzen Debug Ausgaben, für die eine Menge RAM verwendet wird.
Hallo Nemopuk,
der Fehler passiert sowohl auf dem Arduino Mega, welcher ohne Logging ca. 6 kB freies RAM hat als auch auf dem Arduino Uno, welcher 468 Bytes freies RAM hat.
Der Rest des Programmes arbeitet auch im Fehlerfall weiter stabil.
Konkret bedeutet dies, dass alle Funktionen gegeben sind und das LCD in allen Menüs fehlerfrei anzeigt.
Vor Erstellung des Minimalprogrammes im Gesamtprogramm hatte ich viele Programmteile bis auf das LCD auskommentiert und die Komponenten entfernt, was am Fehlerbild nichts geändert hat:
RTC (wird nur gelesen)
IR-Empfänger (wird zum Zeitpunkt des Fehlers nicht verwendet)
DOT-Matrix (hat wahrscheinlich leinen Einfluss)
IR-Sender (wird zurzeit nur bei Empfang eines Kommandos aktiviert)
Noch nicht auskommentiert oder abkeklemmt habe ich bisher:
DHT11 und Piezo
Ich würde jetzt trotzdem erst mal auf Stack Überlauf überprüfen, egal wie unwahrscheinlich das erscheinen mag. Denn wenn das passiert, läuft das Programm eben nicht mehr wie geschrieben ab. Dann wird RAM überschrieben und es kann sogar passieren, dass der Programmzähler der CPU wild hin und her springt, also Sachen macht, die nirgends im Quelltext stehen.
Freies RAM zum Zeitpunkt des Compilierens sagt noch nichts darüber aus, wie viel RAM zur Laufzeit frei ist. Durch ein Memory-Leck würde der freie Speicher zur Laufzeit schrittweise immer kleiner. Bei Arduino droht außerdem eine Fragmentierung des Heap, insbesondere wenn man die String Klasse verwendet. Fragmentierung bedeutet: Du hast zwar viele kleine Stücke im RAM frei, aber keinen großen Zusammenhängenden Block mehr. Auch dann können sich Stack und Heap überlappen, mit den oben genannten Konsequenzen.
Es gibt reichlich Artikel im Internet, die Methoden zur Kontrolle beschreiben.
Wenn der Test dann wie erwartet (hoffentlich) negativ ausfällt (kein Speicherüberlauf), dann würde ich den Algorithmus nochmal ganz genau anschauen. Ich würde damit beginnen, das paarweise Einsammeln der Zeichen vom Pageing zu trennen (nicht beides in einer For-Schleife kombiniert). Dazwischen kommt dann zum Debugging das RAM Array. Dann ist zumindest schon mal klar, ob der Fehler vor dem Pageing passiert, oder nicht.
Als nächsten Schritt könnte man den Schreibzugriff auf das EEprom durch ein weiteres Array im RAM ersetzen (den 5ms delay beibehalten). Dies würde die Diskussion um die I2C Kommunikation beenden (denke ich).
Wie gesagt: Ich vermute, dass die Problemursache eine andere ist. Aber mit Vermutungen kommen wir hier offenbar nicht weiter. Es ist Zeit, sich zu vergewissern.
Moin, na das ist doch einmal eine Top Antwort auf die Problematik. Das
erklärt alles. Vielen Dank.
Dann sollte jetzt die Einlesefunktion usw. aufgeräumt werden.
Was mir auch gerade auffällt ist sowas. Das return steht falsch.byte
read_ext_eeprom(unsigned int addr) { // Read external eeprom
byte byteToRead; // Define byte to read !
if (addr < EXT_EE_SIZE) { // Address is valid
Wire.beginTransmission(EXT_EE_ADDR); // Start transmission
Address of external eeprom
Wire.write((byte)(addr >> 8)); // Address high
Wire.write((byte)(addr & 0xFF)); // Address low
Wire.endTransmission(); // End transmission
Wire.requestFrom(EXT_EE_ADDR, 1); // Allow multiple
requests
byteToRead = Wire.read(); // Read byte
return byteToRead; // Return value
}
}
Allgemein werden mir die Datentypen zu wild verwendet. Integer mit bool
und bool mit Integer.
gemäß Arduino-Referenz ist es angeblich kein Problem, unterschiedliche Datentypen zu addieren.
Diese unbenutzte Funktion ist mir nicht ganz klar.
byte bytes_in_page(unsigned int addr) { // Number of bytes left in
page
if (addr < EXT_EE_SIZE) { // Address is valid
int page = addr / EXT_EE_PAGE; // Current page
int border = (page + 1) * EXT_EE_PAGE; // Next page address =
border
int bytes = border - addr; // Number of bytes total
int pakets = (bytes / EXT_EE_LIMIT) + 1; // Number pf pakets
int rest = bytes; int count = 0; // Rest of bytes
if (rest >= EXT_EE_LIMIT) { // Enough space for 30
bytes
count = EXT_EE_LIMIT;
} //
else if (rest < EXT_EE_LIMIT) { // Less space than 30
bytes
count = rest;
} //
return count; // Number of bytes
}
else {
Serial.print(F("Illegal Address"));
}
}
Sie soll wahrscheinlich das Page Limit prüfen. Nur wenn ein 'rest'
größer dem Limit ist, kann kein Platz mehr sein.
Diese Routine ermittelt die Anzahl der Erlaubten bytes für die Schleife:
1
bytes=bytes_in_page(addr+cnt);// Determinate max. number allowed in this page
2
3
if((cur_byte<x/2)&&// Not all data processed | -> collect data
4
(cur_byte<bytes)){// Below buffer or page-write border |
Ser_Arr[]
soll der Einlesebuffer sein? Wo wird geprüft ob er noch Platz hat?
Es werden durch cur_byte < bytes maximal 30 Zeichen eingelesen
Dann steht in test_data_rec_() ein 10ms delay drin. Warum?
Es trudeln entgegen meiner Erwartung viel zu wenig Zeichen von der seriellen Schnittstelle ein, so dass immer nur ein paar Bytes geschrieben werden. Das 10ms Delay ist ein Versuchsballon, welcher diese Anzahl erhöht.
Nächste Schritt ist, die Baudrate auf 19200 oder 38400 zu setzen.
Das aber erst, wenn der Fehler im Gesamtprogramm beseitigt ist.
Das sind alles so kleine komische Dinge die im Sketch verstreut für
Ärger sorgen wenn mehr gemacht werden soll wie nur lesen und schreiben
alleine. Du brauchst eine saubere Einleseroutine mit sauberen Protokoll.
OK
Ich hatte vorher eine Software-Flusskontrolle (XON/XOFF sowie Anfangs- und Endezeichen verwendet. Hier war immer mein Problem, dass ich die Daten nicht schnell genug abholen konnnte.
gemäß Arduino-Referenz ist es angeblich kein Problem, unterschiedliche
Datentypen zu addieren.
Ich würde mich da ehrlich gesagt lieber auf die Dokumentation des gcc verlassen. Die Arduino Doku verheimlicht häufig kleine gemeine Details, um einfach aus zu sehen.
Ein Beispiel von mir mit Mehrfachverwendbarkeit.
Man kann sich bei Bedarf noch überlegen 'status' in der Struktur zu halten, wenn man zu anderen Zeitpunkten seine Daten verarbeiten möchte.
1
structDaten{
2
staticconstbyteREAD_BUFFER_SIZE{20};
3
charrxBuffer[READ_BUFFER_SIZE+1]{'\0'};// Platz für 20 Zeichen + Null-Terminator
Ich hatte vorher eine Software-Flusskontrolle (XON/XOFF sowie Anfangs-
und Endezeichen verwendet. Hier war immer mein Problem, dass ich die
Daten nicht schnell genug abholen konnnte.
Du müsstest dazu eine eigene Klasse für den seriellen Port implementieren.
Da das EEprom die Pages ja deutlich schneller schreibt, als dein serieller Port empfängt, kannst du prinzipiell auch ohne Handshake arbeiten. Es wird stabil funktionieren, nachdem die eigentliche Problemursache gelöst ist.
Wieso überrascht dich das eigentlich? 9600 Baus sind 960 Zeichen (Nibbles) pro Sekunde, also 480 Bytes. Dein EEprom schreibt 64 Bytes in 5ms, das sind 1280 Bytes pro Sekunde. Es ist also viel schneller.
gemäß Arduino-Referenz ist es angeblich kein Problem, unterschiedliche
Datentypen zu addieren.
Ich würde mich da ehrlich gesagt lieber auf die Dokumentation des gcc
verlassen. Die Arduino Doku verheimlicht häufig kleine gemeine Details,
um einfach aus zu sehen.
Alles Falschaussagen. Wenn man so argumentiert, mit Heap und Stack ablenkt, die serielle Kommunikation als Gegeben 'OK' akzeptiert, dann wundert mich nichts mehr.
Ich hatte vorher eine Software-Flusskontrolle (XON/XOFF sowie Anfangs-
und Endezeichen verwendet. Hier war immer mein Problem, dass ich die
Daten nicht schnell genug abholen konnnte.
Da das EEprom die Pages ja deutlich schneller schreibt, als dein
serieller Port empfängt, kannst du prinzipiell auch ohne Handshake
arbeiten. Es wird stabil funktionieren, nachdem die eigentliche
Problemursache gelöst ist.
ja - deshalb habe ich die Flusskontrolle wieder entfernt - hatte auch den Nachteil, dass ich mit einem Terminalprogramm arbeiten musste und somit der Serielle Monitor nicht zur Verfügung stand.
Wieso überrascht dich das eigentlich? 9600 Baud sind 960 Zeichen
(Nibbles) pro Sekunde, also 480 Bytes. Dein EEprom schreibt 64 Bytes
in 5ms, das sind 1280 Bytes pro Sekunde. Es ist also viel schneller.
Ja, nachdem das Page-Write funktioniert. Mein Test ergab, dass das Schreiben von 512 Bytes 180 ms benötigt.
Die SW-Flusskontrolle hatte ich wie folgt implementiert:
Dass heißt, du hast reichlich Reserve eingeplant, dass die andere Seite verzögert darauf reagiert. Gut.
Aber: Wenn der Rx Interrupt der seriellen Schnittstelle länger als 2 Bytes blockiert oder verzögert wird, verpasst du trotzdem Empfangene Bytes. Das UDR Register wird dann vom Schieberegister überschrieben. Das kannst du mit so einem Software-Handshake nicht verhindern. Wenn hier der Hase im Pfeffer liegt, nützt dir Software-Handshake nichts. Dann wäre der Fehler aber deutlich in rx_test[] sichtbar.
gemäß Arduino-Referenz ist es angeblich kein Problem, unterschiedliche
Datentypen zu addieren.
Ich würde mich da ehrlich gesagt lieber auf die Dokumentation des gcc
verlassen. Die Arduino Doku verheimlicht häufig kleine gemeine Details,
um einfach aus zu sehen.
Alles Falschaussagen. Wenn man so argumentiert, mit Heap und Stack
ablenkt, die serielle Kommunikation als Gegeben 'OK' akzeptiert, dann
wundert mich nichts mehr.
Macht Euer Ding.
Ich werde versuchsweise alle beteiligten Variablen der Empfangsroutine auf den selben Typ ändern. Der Grund für die Verwendung unterschiedlicher Typen war, für den Arduino Uno RAM zu sparen.
Für ein 32k-Eprom wäre int gut, besser aber ein unsigned int.
Somit müssten alle Variablen, welche an Berechnungen beteiligt sind, vom typ unsigned int sein.
Die SW-Flusskontrolle hatte ich wie folgt implementiert:
Dass heißt, du hast reichlich Reserve eingeplant, dass die andere Seite
verzögert darauf reagiert. Gut.
Dar HW-Sendepuffer der UART hat 16 Zeichen - für evtl. Zeitverzug habe ich das XOFF großzügig früh gesendet.
Aber: Wenn der Rx Interrupt der seriellen Schnittstelle länger als 2
Bytes blockiert oder verzögert wird, verpasst du trotzdem Empfangene
Bytes. Das UDR Register wird dann vom Schieberegister überschrieben. Das
kannst du mit so einem Software-Handshake nicht verhindern. Wenn hier
der Hase im Pfeffer liegt, nützt dir Software-Handshake nichts. Dann
wäre der Fehler aber deutlich in rx_test[] sichtbar.
Richtig rx_test ist immer richtig - daher habe ich den SW-Handshakeals Sackgasse verworfen....
Mein Test sagt etwas komplett anderes. Warum übersieht man das?
Bei den vielen Test ist inzwischen auch nicht mehr richtig klar, wer was womit getestet hat. Der Kai hat damit angefangen: Log-Meldungen von einem angeblichem Programmfragment gepostet, das unmöglich diese Ausgabe hätte produzieren können.
Mein Test sagt etwas komplett anderes. Warum übersieht man das?
Bei den vielen Test ist inzwischen auch nicht mehr richtig klar, wer was
womit getestet hat. Der Kai hat damit angefangen: Log-Meldungen von
einem angeblichem Programmfragment gepostet, das unmöglich diese Ausgabe
hätte produzieren können.
Die Variante mit eingebauten log habe ich nicht gepostet, da sie mir zu undurchsichtig erschien - kann ich aber - falls gewünscht - gerne nachholen.
Fakt ist, je mehr Logging-Funktionen ich in das Originalprogramm einbaue, um so öfter erhalte ich falsche Werte von cnt. Aus meiner Sicht zeigt dies in Richtung Stack bzw. Heap. Die erste Übertragung nach Neustart ist zurzeit mit 160 Bytes immer richtig.
Das Umstellen auf gleiche Variablentypen (unsigned int) hat das Verhalten nicht geändert.
Hat noch jemand meinen Minimal-Sketch getestet?
Zur Beachtung: während das Timeout noch läuft, sollte man kein 2. Datenpaket senden - evtl. erklärt das die Fehlfunktion bei Veit?
Ich habe den 2. Minimal-Sketch mittlerweile bestimmt 50 x ohne Fehler laufen lassen.
Ich muss jetzt erst einmal wieder meinen Kopf freibekommen und kurz etwas anderes machen....
zum letzten Mal. Programmiere eine saubere Einlesefunktion und eine zum verarbeiten der empfangenen Daten. Ohne Timeout. Ich verstehe nicht warum dazu alles gesagte, geschriebene und gezeigte ignoriert wird, wenn man selbst merkt das es nicht rund läuft. Man kann sich vertun, aber man muss auch reagieren können.
Die Variante mit eingebauten log habe ich nicht gepostet, da sie mir zu
undurchsichtig erschien - kann ich aber - falls gewünscht - gerne
nachholen.
Das war offensichtlich, und die fehlenden/auskommentierten Zeilen konnte man sich denken. Ich wollte nur daran erinnern künftig, besser zu jedem Testergebnis auch den Input und das verwendete Programm zu liefern oder zu verlinken, um Verwirrung zu vermeiden.
Fakt ist, je mehr Logging-Funktionen ich in das Originalprogramm
einbaue, um so öfter erhalte ich falsche Werte von cnt.
Aus meiner Sicht zeigt dies in Richtung Stack bzw. Heap.
Hängt ein Fehler von der Größe des Projektes ab, dann passiert er selten dort, wo man ihn sucht. Gerne werden z.B. Arraygrenzen überschritten, d.h. man schreibt auf eine Variable, die hinter das Array gelinkt wird.
Man könnte dann Arrays etwas mehr Bytes reservieren, ob sich was ändert.
Auch Pointer sind gerne mal vergiftet, der Compiler kann deren Größe nicht mehr überwachen.
Abgesehen vom nicht gesicherten Übertragungsprotokoll (das würde ich zuerst anpacken) würde ich die Logik ganz extrem vereinfachen.
Also z.B. immer 32/64/WhateverMakesSense Bytes einlesen und dann ins Flash schreiben. Ich wette, das geht in weniger als der Hälfte der bisher benötigten Zeilen und funktioniert dann auch ohne Probleme.
zum letzten Mal. Programmiere eine saubere Einlesefunktion und eine zum
verarbeiten der empfangenen Daten. Ohne Timeout. Ich verstehe nicht
warum dazu alles gesagte, geschriebene und gezeigte ignoriert wird, wenn
man selbst merkt das es nicht rund läuft. Man kann sich vertun, aber man
muss auch reagieren können.
Vielen herzlichen Dank für alle Tipps!
Ich habe alle Vorschläge verstanden, die Vor- und Nachteile der Umsetzung dieser intensiv überdacht und bin bin keinesfalls lernresistent.
Ich hatte bereits angefangen, Veits Beispiel zu integrieren, aber gemerkt, dass dieses zu viel Speicher benötigt.
Daher bin ich auf mein Verfahren zurückgekommen.....
Mein Hauptgrund für das von mir verwendete Verfahren ist, dass ich nicht ausreichend RAM zum Abspeichern der der später einzulesenden Dateien (jetzt ca. 2kB - später können es durchaus auch 10 kB sein) habe. Daher muss das Einlesen und Abspeichern "on the fly" passsieren.
Ein Timeout ist deshalb nötig, da der Controller vorher nicht weis, wie viele Daten eintreffen werden.
Das Warten auf ein Ende-Zeichen bedeutet ebenfalls, dass ich die Datei bis zum Ende ins RAM packen müsste, um sie anschließend zu bearbeiten.
In dem ATMEGA 328P meiner Platine habe ich aber nur 2kB RAM, von welchen ich gerne 500 Bytes frei haben möchte.
Mittlerweile bin ich einen grossen Schritt weiter:
zunächst habe ich alle Variablen des Gesamtprogrammes initialisiert, aufgrund pedas Vorschlag die Arrays größer als benötigt angelegt und soweit möglich auch alle Warnings beseitigt. Das hat keine Verbesserung gebracht.
Gerade habe ich als letzte meiner Ideen die Reihenfolge der Definition der Variable cur_byte und des Arrays vertauscht und nun hat sich das Fehlerbild geändert:
Es wird jetzt zu 90% die richtige Anzahl von 160 Bytes eingelesen - einmal hatte ich einen Ausreißer mit 158 Bytes die Bytes fehlen jetzt am Ende des EEPROM-Inhaltes 9E, 9F).
das mit dem Timeout und nicht warten können ist schon Mist. Dann sollte man einen anderen µC mit mehr RAM verwenden.
Peters Vorschlag mit dem Array größer wie benötigt dient zum Debuggen. Man kann die Größe schon auf das Notwendige anpassen. Man muss bei Verwendung auf das Ende prüfen. So wie ich das in
Beitrag "Re: Arduino: page-write in I2C eeprom verliert sporadisch ein Byte"
bspw. zeige
Wenn du erneut mit Timeout anfängst, hast du erneut keine Kontrolle. Möchtest du die Daten aufwendiger mitzählen oder vertraust du erneut darauf das immer alles gut geht? Am Ende stehst du wieder da und hast ein Problem. Dann verzichte lieber auf Page Write und schreibe alle Bytes sofort einzeln. Wenn du dein Bus kurz hälst und sauber terminierst, kannste locker 400kHz einstellen.
Überdenke dein Konzept bitte nochmal gründlich.
Und ganz wichtig. Dokter nicht an deinem bestehenden Programm rum. Verwirf es und fang bei Null an.
sicher habt ihr euch schon gefragt, ob ich aufgegeben habe? Nein, habe ich nicht.
Vorab: Schreiben ins RAM mit nachträgliche Page-Write aus dem RAM funktioniert theoretisch, hat aber ein Problem am Ende (siehe Ursache 5).
Da ich immer aber gerne ganz genau weis, weshalb etwas nicht funktioniert, habe ich der Reihe nach folgende Punkte abgearbeitet.
Zunächst war es mir wichtig, alle Warnungen im realen Programm zu beseitigen, um alle möglichen hieraus resultierenden Probleme auszuschließen.
Das hat deshalb etwas länger gedauert, weil hier viele Schreib- und Lesevorgänge auf das - Achtung: !interne EEPROM! vorhanden sind,
deren Funktion immer einwandfrei war (Anmerkungen Es sind hier die Einstellungen und alle Kalenderdaten gespeichert).
Bei jedem hatte ich die Warnung: invalid conversion from 'int' to 'uint8_t*
Hier haben sich die Entwickler des Arduino-Framework ja etwas ganz besonderes einfallen lassen: es muss ein Pointer auf die Variable übergeben werden.
Egal - ich habe alle geändert und das Schreiben und Lesen scheint bisher immer noch fehlerfrei zu sein.
Ein vollständiger Test dieser Funktionen steht allerdings noch aus. Hat aber nichts mit dem aktuellen Problem zu tun.
Ich reiche an dieser Stelle noch nach, dass das Projekt nach Corona seit ca. 2022 ruhte, da ich nicht die Muße fand, eine Platine zu routen.
Ursprünglich wollte ich die Drähte schön auf einer Grundplatte verlegen und das Ganze an die Wand hängen.
Während dieser Zeit ist das Gerät in seinem unschönen Zustand (fliegender Aufbau - Foto kennt ihr ja) gelaufen und hat fehlerfrei funktioniert.
Alle Termine wurden richtig angezeigt und alle Schaltzeiten eingehalten.
Das aber das nur am Rande.....
Nachdem diese Warnungen beseitigt waren, kamen andere hinzu (zeigt die Ide oberhalb einer bestimmten Anzahl nicht mehr alle Fehler an)?
Dies war die Verwendung vieler unbenutzte Variablen - habe ich beseitigt, obwohl es bis auf den RAM-Verbrauch vermutlich irrelevant ist.
Dann kamen interessante Meldungen bezüglich des Vergleiches unterschiedlicher Variablentypen - ebenfalls beseitigt.
Ich bin dem Hinweis von Klaus (feelfree) auf ungerades x nachgegangen: ist nicht abgedeckt, wird aber dadurch abgefangen, dass immer nur gerade Anzahlen gelesen werden.
Das versuchsweise Abfangen hat am Problem nichts geändert. Es ist aber ein Teil des Problems - dazu später.
Dann habe ich die Ursachen für das unterschiedliche Verhalten des Minimalprogrammes und des realen Programmes gesucht.
a) Der Sketch von Veit arbeitet ohne externes EEPROM - daher ist das Timing völlig anders.... - habe ich daher nicht weiter untersucht.
b) Die Ermittlung der mittleren Verzugszeit zwischen dem Eintreffen der ersten Bytes im seriellen Puffer und dem Aufruf der Empfangsroutine ergab 20ms
c) Und ja: Nach Einfügen eines Delays von mehr als 15ms in die Loop kommt auch das Minimalprogramm durcheinander!
d) Zusätzlich habe ich nun die "echt" eingetroffenen Bytes gezählt: der Wert ist immer richtig, auch wenn cnt falsche Werte enthält.
Bei nur geringer Anzahl geschriebener Bytes macht sich die Wartezeit am Ende des Page-Write stärker bemerkbar - wäre auch noch zu verkraften.
Die eigentliche Ursache für den sporadischen Fehler ist, dass sich zur Zykluszeit im ungünstigsten Fall (nur 1 Zeichen eingetroffen) eine Weitere von 20ms addiert.
und genau dann läuft der Puffer über.
Es gibt nun mehrere Lösungsansätze: a) als Workaround die Empfangsroutine nicht mehr verlassen, bis das 2. Zeichen kommt.
b) Eine intelligente Warteroutine.
Schön dass Du nicht aufgibst.
Vielleicht schaust Du dir mal diese Arduino lib an: https://docs.arduino.cc/libraries/at24c/
Die kümmert sich um
I2C transfer buffer limit 32 byte
AT24 page size 64 byte
Aufruf mit beliebigem bytecount byc, z.B. beginned ab buffer[4] nach EEPROM adr:
Es gibt nun mehrere Lösungsansätze: a) als Workaround die
Empfangsroutine nicht mehr verlassen, bis das 2. Zeichen kommt.
b) Eine intelligente Warteroutine.
Richtig wäre
c) in der Empfangsroutine immer nur so viele Bytes aufsammeln wie halt da sind. Erst wenn sich z.B. 32 Bytes angesammelt haben werden diese weggeschrieben.
a) Der Sketch von Veit arbeitet ohne externes EEPROM - daher ist das
Timing völlig anders.... - habe ich daher nicht weiter untersucht.
Deine Argumentation verstehe ich nicht. Versteht bestimmt niemand. Ich weiß nicht wie du Fehlersuche betreibst wenn das Offensichtliche ignoriert wird. Ich habe gezeigt das deine Einleseroutine fehlerhaft ist. Wenn man das ohne EEprom nachvollziehen kann, dann umso besser. Es gibt dabei genau so einen Versatz wie Eingangs von dir festgestellt. Wie eine saubere Einleseroutine aussehen kann bzw. aussieht habe ich mit Link und Code gezeigt. Warum das alles ignoriert verstehe ich echt nicht mehr. Das Einlesen muss jederzeit fehlerfrei funktionieren. Dein festhalten am paarweisen einlesen ist jedenfalls Mist. Du suchst lieber an anderen Baustellen und versaust dir den Code mit Zwangswartezeiten u.ä.. Wenn ich dann Ideen lese wie "Empfangsroutine nicht mehr verlassen" oder "Warteroutine" ...
Ich denke du hast keine Zeit? Aber möchtest noch mehr blockieren?
Mein wiederholter Rat. Fang nochmal sauber von vorn an.
Die eigentliche Ursache für den sporadischen Fehler ist, dass sich
zur Zykluszeit im ungünstigsten Fall (nur 1 Zeichen eingetroffen) eine
Weitere von 20ms addiert.
und genau dann läuft der Puffer über.
Dann mach doch das EEPROM Zeugs einfach mit in die schnellen Tasks vor der 20ms Wartezeit:
als Workaround die
Empfangsroutine nicht mehr verlassen
Nein, das ist (fast) immer eine Sackgasse. Eine Task darf alle anderen nicht auf unbestimmte Zeit blockieren.
Wenn eine Task gerade auf etwas warten muß, dann verlasse sie einfach und setze sie beim nächsten Aufruf fort. Mit einem switch/case kann man sich merken, wo weitergemacht werden soll.
Wie oben schon gesagt, ich sammele alle Zeichen in einen Puffer und bearbeite sie erst nach dem CR.