Forum: Mikrocontroller und Digitale Elektronik Ringpuffer im EEPROM anlegen


von Christian T. (shuzz)


Lesenswert?

Hallo zusammen!

Ich möchte gerne für ein kleines Projekt (RGB-Beleuchtung) mehrere 
Presets im EEPROM des AVR ablegen.
Das ist soweit auch kein Problem, die Presets werden einfach an feste 
Stellen im EEPROM geschrieben (EEMEM-Variablen und fertig).
Da die Presets zwar vom Benutzer per Fernbedienung konfigurierbar sind 
aber vermutlich eher selten geändert werden sollten da die garantierten 
100.000 Schreib-/Löschzyklen des AVR ausreichen.

Mein Problem besteht nun darin, dass ich auch speichern will welches 
Preset als letztes aktiv war.
Und da könnte es dann schon irgendwann mal eng werden mit den 100.000...

Daher nun meine Frage: Wie implementiert man am elegantesten einen 
Ringpuffer im EEPROM?

Mein Ansatz sieht etwa so aus: Ich lege einen Block von z.B. 100 
Byte-Werten im EEPROM ab (wieder als EEMEM-Variable).
Nach dem Programmieren des Chips (bzw. Chip-Erase) sollten alle Werte in 
dem Array auf 0xFF stehen.
Nun kann ich das Array per eeprom_read_block auslesen und der letzte 
Wert im Array der NICHT 0xff ist sollte dann meiner sein.

Soweit so gut...

Wie schreibe ich das Ding aber nun weg? Soweit ich das verstanden habe 
würde eeprom__write_block den kompletten Block ins EEPROM schreiben, 
d.h. jede Zelle innerhalb des Blocks würde neu beschrieben oder?
(Das würde den Vorteil des Ringpuffers ja wieder zunichte machen.)

Die einzige Lösung die mir einfällt wäre: Im Array nach dem letzten Wert 
<0xFF suchen und nur diesen Wert per eeprom_write_byte schreiben.
Geht das nicht auch "einfacher"? (Nicht dass es wirklich schwer wäre, 
ich habe nur das Gefühl das Rad ein zweites Mal zu erfinden...)

z.B. so:
1
uint8_t selectedPreset[100] EEMEM;
2
3
uint8_t getSelectedPreset(){
4
  uint8_t presets[100];
5
  eeprom_read_block(&presets, &selectedPresets, sizeof(presets));
6
  uint8_t retVal = 0, j = 0;
7
  retVal = presets[0];
8
  for(i = 1; i < 100; i++){
9
    if(presets[i] != 0xFF){
10
      retVal = presets[i];
11
    } else {
12
      return retVal;
13
    }
14
  }
15
}
16
17
18
void setPreset(uint8_t preset){
19
  uint8_t presets[100];
20
  eeprom_read_block(&presets, &selectedPresets, sizeof(presets));
21
  for(i = 0; i < 100; i++){
22
    if(presets[i] == 0xFF){
23
      eeprom_write_byte(&selectedPresets + i, preset);
24
      return;
25
    }
26
  }
27
  for(i = 1; i < 100; i++){
28
    presets[i] = 0xFF;
29
  }
30
  presets[0] = preset;
31
  eeprom_write_block(&presets, &selectedPresets, sizeof(presets));
32
  return;
33
}

Könnte das so funktionieren?
Geht es auch einfacher?

Ich freue mich auf eure Kommentare! :)

von Fabian B. (fabs)


Angehängte Dateien:

Lesenswert?

Da gibts ne ganz schicke Appnote von Atmel dazu.
AVR010 High endurance EEPROM.

Gruß
Fabian

von Hagen R. (hagen)


Lesenswert?

Hm, dann könnte man um den gleichen Effekt zu erzielen wie in der 
AppNote auch gleich einen doppelt so großen Ringbuffer nehmen. Jedes 
Byte im Buffer besteht aus 7 Bit Index des letzten Presets und das 7'te 
Bit ist das Flag das anzeigt das dies das "Ende/Anfang" des Ringbuffers 
ist. Wird ein neuer Preset geschrieben suchst du den Eintrag mit 
gesetztem 7'ten Bit und löscht dieses Bit durch überscheiben. Das Byte 
danach wird mit dem neuen Presetindex geschrieben und dabei das 7'te Bit 
gesetzt.
Somit werden pro Schreibvorgang 2 EEPROM Bytes geschrieben. Der 
Gesamt-EEPROM Verbrauch und die Häufigkeit des Schreibens ist gleichhoch 
wie in dem Verfahren aus der AppNote. Nur ist der Unterschied das das 
Verfahren aus der AppNote viel kompliziter/umständlicher ist.
Vorteil mit der Bitkodierung ist das beim Überschreiben von zb. 0xFF 
nach 0x7F nur 1 Bit sich geändert hat und im EEPROM geschrieben wird.

Alternativ kannst du auch gleich das letzte Presetindex-byte mit 0xFF 
überschreiben und danach den neuen Presetindex (< 255) schreiben. Dh. 
dein EEPROM Buffer besteht im Grunde immer aus 0xFF gefüllt und nur 1 
Byte enthält den Presetindex. In jedem Falle wirst du doppelt soviel 
EEPROM verbrauchen um auf deinen berechneten Faktor zu kommen.

Gruß Hagen

von Christian T. (shuzz)


Lesenswert?

@fabs: Danke für den Hinweis, das sieht interessant aus. Den Pointer 
muss ich mir ja nur einmal am Anfang rauskramen, das eine Byte SRAM zum 
Speichern davon wird noch übrig sein... ;)
Allerdings ist der doppelte Speicherverbrauch irgendwie nicht so prall, 
aber ich werde die Appnote mal im Hinterkopf behalten.

@Hagen: Ich muss gestehen, ich kann Deinen Ausführungen spontan nicht so 
richtig folgen, Du verwirrst micht etwas... ^^
Soweit ich Dich verstehe, habe ich aber trotz allem noch den doppelten 
Speicherverbrauch und auch zwei Schreibzugriffe pro geschriebenem Byte, 
richtig?
D.h. die Anzahl der möglichen Lösch-/Schreibzyklen beträgt sowohl bei 
Dir als auch bei der Atmel-Appnote dann "nur" "50.000 * verbrauchtes 
EEPROM in Byte"?

Wäre da nicht meine Methode (s.o.) vorzuziehen?
Es würde zwar theoretisch länger dauern da die letzte Speicherstelle 
jedesmal neu rausgesucht wird, aber da kann man ja Abhilfe schaffen.
(Letzten Index einmal am Anfang laden und gut ist's...

Und ich bemerke gerade, dass ich euch eine wichtige Information 
vorenthalten habe: Es wird in der Applikation maximal 10 
unterschiedliche Presets geben, mehr ist sowieso nicht drin. Eure 
Methoden würden mir ja einen Wertebereich von 0..255 ermöglichen, nur 
brauche ich den ja gar nicht, 0..9 reicht bei mir aus...

Eine grundsätzliche Verständnisfrage hätte ich noch: Wenn ich eine 
Stelle im EEPROM mit 0xFF "beschreibe" entspricht das ja im Prinzip dem 
Löschen des entsprechenden Bytes im EEPROM. Beschreibe ich diese Stelle 
hinterher nochmal mit einem anderen Wert, wird die dann direkt nochmal 
gelöscht? Falls das so wäre hätte ich ja auch wieder "nur" 50.000 
nutzbare Schreibzugriffe pro Byte im EEPROM, richtig?

Ich danke euch auf jeden Fall schonmal für die Hilfe! :)

von Karl H. (kbuchegg)


Lesenswert?

Christian T. schrieb:

> Mein Problem besteht nun darin, dass ich auch speichern will welches
> Preset als letztes aktiv war.
> Und da könnte es dann schon irgendwann mal eng werden mit den 100.000...

Ehe dein Benutzer 100000 mal den Preset umgeschaltet hat, ist er längst 
eines natürlichen Todes gestorben, wenn wir eine einigermasse normale 
Benutzung voraussetzen.

ZUm anderen ist es ja nicht so, dass Atmel da einen Counter eingebaut 
hat, der nach exakt 100000 Schreibzyklen die Zelle abschaltet. Die 
garantierte Zahl an Zyklen lautet 100000. In der Praxis hält das Teil 
aber länger.

von Peter D. (peda)


Lesenswert?

Die neueren AVRs erlauben getrenntes Löschen und Schreiben.
Dann kannst Du hochzählen, den neuen Wert schreiben ohne Löschen und 
dann den vorherigen Wert nur löschen.


Peter

von Christian T. (shuzz)


Lesenswert?

Karl heinz Buchegger schrieb:
> Christian T. schrieb:
>
>> Mein Problem besteht nun darin, dass ich auch speichern will welches
>> Preset als letztes aktiv war.
>> Und da könnte es dann schon irgendwann mal eng werden mit den 100.000...
>
> Ehe dein Benutzer 100000 mal den Preset umgeschaltet hat, ist er längst
> eines natürlichen Todes gestorben, wenn wir eine einigermasse normale
> Benutzung voraussetzen.

Da könntest Du Recht haben... ^^
Mir geht es da eher um's Prinzip, ich find's halt schöner wenn ich dem 
Kumpel der das Teil kriegt sagen kann "darfst aber maximal 10.000.000 
umschalten!" ;-)

> ZUm anderen ist es ja nicht so, dass Atmel da einen Counter eingebaut
> hat, der nach exakt 100000 Schreibzyklen die Zelle abschaltet. Die
> garantierte Zahl an Zyklen lautet 100000. In der Praxis hält das Teil
> aber länger.

Das ist klar. Aber es ist eben nicht garantiert. ^^

Edit: Frage beantwortet von Peda, vielen Dank! :)


Grüße,

Christian

von Karl H. (kbuchegg)


Lesenswert?

Christian T. schrieb:
> Karl heinz Buchegger schrieb:
>> Christian T. schrieb:
>>
>>> Mein Problem besteht nun darin, dass ich auch speichern will welches
>>> Preset als letztes aktiv war.
>>> Und da könnte es dann schon irgendwann mal eng werden mit den 100.000...
>>
>> Ehe dein Benutzer 100000 mal den Preset umgeschaltet hat, ist er längst
>> eines natürlichen Todes gestorben, wenn wir eine einigermasse normale
>> Benutzung voraussetzen.
>
> Da könntest Du Recht haben... ^^
> Mir geht es da eher um's Prinzip,

Ist schon ok.

> ich find's halt schöner wenn ich dem
> Kumpel der das Teil kriegt sagen kann "darfst aber maximal 10.000.000
> umschalten!" ;-)

Vor kurzem haben wir hier im Forum eine ähnliche Rechnung gemacht.
Wenn dein Benutzer alle 10 Minuten den Preset wechselt und dass 24 
Stunden am Tag, 7 Tage die Woche, 52 Wochen im Jahr ... kurz gesagt, 
wenn er nichts anderes tut als alle 10 Minuten den Preset zu wechseln, 
dann hält das EEPROM gute 2 Jahre.

Nun kann ich mir nicht vorstellen, dass dein Kumpel jeden Tag nichts 
anderes tut als mit dem RGB Licht zu spielen. Tagsüber wird es wohl aus 
lassen, was die Lebensdauer schon mal auf ~4 Jahre erhöht. In der Nacht 
wird er wohl die halbe Zeit schlafen und wir sind bei 8 Jahren. Und dass 
er seine Abende damit verbringt, alle 10 Minuten den Preset zu wechseln, 
kann wohl getrost als illusorisch abgetan werden. Zumindest dann, wenn 
er irgendwann in den nächsten 8 Jahren Familie hat. Und seine Freundin 
wird ihm was husten, wenn ihm abends nichts besseres einfällt als mit 
der Lichtsteuerung zu spielen.

Fazit: Viel Lärm um nichts. Du brauchst deinem Kumpel überhaupt nicht 
sagen, dass es da eine Beschränkung der Lebensdauer gibt, weil diese 
Beschränkung ganz einfach nicht relevant ist.

> Kannst Du vllt. noch was zu meiner Frage bzgl. dem Löschen und
> Wiederbeschreiben des EEPROMs sagen? (s. Post vor Deinem, letzter
> Absatz)

Wenns denn sein muss :-)
Du könntest zb im EEPROM 3 oder 4 Zähler/PresetId-Kombinationen 
benutzen, die erst mal alle auf 0 gesetzt werden. Ist ein Zähler bei 
0xFFFF angelangt, benutzt du einfach den nächsten Zähler/PresetId.

von Hagen R. (hagen)


Lesenswert?

>Soweit ich Dich verstehe, habe ich aber trotz allem noch den doppelten
>Speicherverbrauch und auch zwei Schreibzugriffe pro geschriebenem Byte,
>richtig?

Ja, wirst du in jedem Falle immer haben.

>D.h. die Anzahl der möglichen Lösch-/Schreibzyklen beträgt sowohl bei
>Dir als auch bei der Atmel-Appnote dann "nur" "50.000 * verbrauchtes
>EEPROM in Byte"?

Ja.

>Wäre da nicht meine Methode (s.o.) vorzuziehen?

Wenn du nur einmalig 100 Presetsindizes speichern möchtes dann ja. Dein 
Buffer mit 100 Bytes ist am Anfang mit 0xFF gefüllt. Nach dem ersten 
Preset steht an Adresse 0x00 des Buffers also dein letzter Presets. Dein 
Buffer hat noch 99 Werte mit 0xFF gefüllt. Was machst du nach 100 
gespeicherten Prestes ? Wenn alle 100 zellen <> 0xFF sind wie möchtest 
du dann den aktuell letzten Preset finden ?

Du musst immer zu den eigentlichen Daten auch eine 
Verwaltungstabelle/buffer speichern. Ergo minimal doppelt soviel EEPROM 
wie dein Ziel der Anzahl der Schreibzugriffe dir vorgibt. Wichtig ist 
eben das diese Verwaltungsinformationen (Zeiger, Indizes) ebenfalls im 
EEPROM gespeichert werden müssen und dann den gleichen Bedingungen 
unterliegen wie deine eigentlichen Daten. Ergo: auch dafür eine 
Ringbuffer benutzen.

>Methoden würden mir ja einen Wertebereich von 0..255 ermöglichen, nur
>brauche ich den ja gar nicht, 0..9 reicht bei mir aus...

Ändert nichts wesentlich an der Sache. Du musst denoch minimal 2 EEPROM 
Bytes schreiben. Das neue Byte und das alte mit dem aktuellen Preset 
musst du verändern, oder eben das Datenbyte und das Indexbyte so wie in 
der AppNote.

Du könntest mit dem Benutzten der 2 Nibbles eines Bytes das Waerout um 
Faktor 1.6 verbesseren. Denn wenn du in einem Byte 2 Nibbles = 2 Presets 
hast so musst du nur jedes zweite Mal 2 Bytes und ansonsten nur 1 Byte 
neu schreiben.

Also Buffer besteht aus zb. 16 Bytes = 32 Presets. Alles mit 0xFF 
gefüllt. Beim ersten Schreiben in Byte 0 des Buffers steht also dann zB. 
0xF1 also Presetindex #1, und 1 Bytes wurde geschrieben. Beim zweiten 
Schreiben wieder im Byte 0 und drinnen steht zb. 0x21, also Preset #2. 
Wieder nur 1 Bytes geschrieben. Beim 3'ten Scheiben wird nun Byte 0 
gelöscht = 0xFF und Byte 1 mit zb. 0xF3, also Preset #3 geschrieben. 
Diesesmal also 2 Schreibzugriffe. Auf 4 Presets also 5 Bytes 
geschrieben. Normalerweise würdest du für 4 Presets 8 Bytes statt 5 neu 
schreiben. Faktor 1.6 besser.

Damit also daraus ein Ringbuffer wird musst du den vorherigen Preset mit 
0xFF überschreiben ansonsten weist du ja nach 100 Presets bei 100 Bytes 
im Buffer nicht mehr wo der Anfang ist und du kannst den Buffer nur 
einmalig mit 100 Presets befüllen. Das wäre dann kein Ringbuffer.

Gruß Hagen

von Christian T. (shuzz)


Lesenswert?

Hagen Re schrieb:
>>Soweit ich Dich verstehe, habe ich aber trotz allem noch den doppelten
>>Speicherverbrauch und auch zwei Schreibzugriffe pro geschriebenem Byte,
>>richtig?
>
> Ja, wirst du in jedem Falle immer haben.
>
>>D.h. die Anzahl der möglichen Lösch-/Schreibzyklen beträgt sowohl bei
>>Dir als auch bei der Atmel-Appnote dann "nur" "50.000 * verbrauchtes
>>EEPROM in Byte"?
>
> Ja.
>
>>Wäre da nicht meine Methode (s.o.) vorzuziehen?
>
> Wenn du nur einmalig 100 Presetsindizes speichern möchtes dann ja. Dein
> Buffer mit 100 Bytes ist am Anfang mit 0xFF gefüllt. Nach dem ersten
> Preset steht an Adresse 0x00 des Buffers also dein letzter Presets. Dein
> Buffer hat noch 99 Werte mit 0xFF gefüllt. Was machst du nach 100
> gespeicherten Prestes ? Wenn alle 100 zellen <> 0xFF sind wie möchtest
> du dann den aktuell letzten Preset finden ?

Wenn alle 100 Werte != 0xFF sind dann ist mein gesuchtes Preset die 
#100, beim nächsten Schreiben würde ich das Feld dann komplett wieder 
löschen und Zelle #1 beschreiben.


>>Methoden würden mir ja einen Wertebereich von 0..255 ermöglichen, nur
>>brauche ich den ja gar nicht, 0..9 reicht bei mir aus...
>
> Ändert nichts wesentlich an der Sache. Du musst denoch minimal 2 EEPROM
> Bytes schreiben. Das neue Byte und das alte mit dem aktuellen Preset
> musst du verändern, oder eben das Datenbyte und das Indexbyte so wie in
> der AppNote.

Deshalb fragte ich ja nach dem getrennten Löschen und Beschreiben eines 
EEPROM-Bytes. Aber so wie's aussieht wird das von der avr-libc nicht 
unterstützt. Zumindest in der Online-Hilfe steht da nix drüber. Ich 
könnte höchstens mal in den Code schauen ob die sowas getrennt behandeln 
oder nicht.
(Ich hege allerdings leise Zweifel ob ich da durchsteigen würde... ^^)

Da ich eine Assembler-Niete bin werde ich einfach mal kbuchegg's 
Argumentation folgen und mich darauf verlassen, dass der Kumpel 
vermutlich vor dem EEPROM das Zeitliche segnen wird... ;)

Ich danke euch allen für die Diskussion/Hinweise.


Grüße,

Christian

von Karl H. (kbuchegg)


Lesenswert?

Christian T. schrieb:

> Da ich eine Assembler-Niete bin werde ich einfach mal kbuchegg's
> Argumentation folgen und mich darauf verlassen, dass der Kumpel
> vermutlich vor dem EEPROM das Zeitliche segnen wird... ;)

Ja, ja. Dann bin ich wieder schuld :-)

> Ich danke euch allen für die Diskussion/Hinweise.

Diesmal mit runterzählen, da ja das EEPROM jungfräulich mit 0xFF besetzt 
ist :-)
1
#define NR_EEPROM_PRESET 4
2
3
uint16_t UsageCounter[NR_EEPROM_PRESET] EEMEM;
4
uint8_t  actPreset[NR_EEPROM_PRESET] EEMEM;
5
6
.....
7
8
9
uint8_t actPreset;
10
11
....
12
13
uint8_t GetPreset( void )
14
{
15
  uint8_t i;
16
17
  for( i = 0; i < NR_EEPROM_PRESET; ++i ) {
18
    if( eeprom_read_word( &UsageCounter[i] ) != 0 )
19
      return eeprom_read_byte( &actPreset[i] );
20
  }
21
22
  // all NR_EEPROM_PRESET*100000 EEprom Write Cycles have been used :-(
23
  return 0;
24
}
25
26
void PutPreset( uint8_t preset )
27
{
28
  uint8_t i;
29
30
  for( i = 0; i < NR_EEPROM_PRESET; ++i ) {
31
    uint16_t counter = eeprom_read_word( &UsageCounter[i] );
32
    if( counter != 0 ) {
33
      counter--;
34
      eeprom_write_word( &UsageCounter[i], counter );
35
      if( counter != 0 ) {
36
        eeprom_write_byte( &actPreset[i], preset );
37
        return;
38
      }
39
    }
40
  }
41
}

von Christian T. (shuzz)


Lesenswert?

Karl heinz Buchegger schrieb:
> Christian T. schrieb:
>
>> Da ich eine Assembler-Niete bin werde ich einfach mal kbuchegg's
>> Argumentation folgen und mich darauf verlassen, dass der Kumpel
>> vermutlich vor dem EEPROM das Zeitliche segnen wird... ;)
>
> Ja, ja. Dann bin ich wieder schuld :-)

Das Los des Moderators... 8D


> Diesmal mit runterzählen, da ja das EEPROM jungfräulich mit 0xFF besetzt
> ist :-)
>
>
1
> #define NR_EEPROM_PRESET 4
2
> 
3
> uint16_t UsageCounter[NR_EEPROM_PRESET] EEMEM;
4
> uint8_t  actPreset[NR_EEPROM_PRESET] EEMEM;
5
> 
6
> .....
7
> 
8
> 
9
> uint8_t actPreset;
10
> 
11
> ....
12
> 
13
> uint8_t GetPreset( void )
14
> {
15
>   uint8_t i;
16
> 
17
>   for( i = 0; i < NR_EEPROM_PRESET; ++i ) {
18
>     if( eeprom_read_word( &UsageCounter[i] ) != 0 )
19
>       return eeprom_read_byte( &actPreset[i] );
20
>   }
21
> 
22
>   // all NR_EEPROM_PRESET*100000 EEprom Write Cycles have been used :-(
23
24
    // Naja, eher NR_EEPROM_PRESET*65536 oder? ;-P
25
26
>   return 0;
27
> }
28
> 
29
> void PutPreset( uint8_t preset )
30
> {
31
>   uint8_t i;
32
> 
33
>   for( i = 0; i < NR_EEPROM_PRESET; ++i ) {
34
>     uint16_t counter = eeprom_read_word( &UsageCounter[i] );
35
>     if( counter != 0 ) {
36
>       counter--;
37
>       eeprom_write_word( &UsageCounter[i], counter );
38
        // kbuchegg hat gesagt er ist Schuld wenn hier Daten verloren gehen... ^^
39
>       if( counter != 0 ) {
40
>         eeprom_write_byte( &actPreset[i], preset );
41
>         return;
42
>       }
43
>     }
44
>   }
45
> }
46
>

Aber vielen Dank für die Grundidee, ich denke ich werde das so (ähnlich) 
umsetzen. Ringpuffer ist an der Stelle dann doch eher die berühmte 
Kanone für die Spatzen...


Grüße,

Christian

von Michael L. (michaelx)


Lesenswert?

Wenn du maximal 255 Presets hast (0-254), ist es doch ganz einfach. Du 
schreibst einfach das Byte in den nächsten Platz des Ringbuffers, und 
löschst danach den vorherigen (0xff = 255).

von Christian T. (shuzz)


Lesenswert?

Michael L. schrieb:
> Wenn du maximal 255 Presets hast (0-254), ist es doch ganz einfach. Du
> schreibst einfach das Byte in den nächsten Platz des Ringbuffers, und
> löschst danach den vorherigen (0xff = 255).

Jup, so ähnlich hatte ich das anfangs ja auch geplant.
Nur hätte ich den kompletten Buffer bei jedem "Überlauf" gelöscht und 
nicht bei jedem Schreibzugriff...

Mittlerweile ist mir aber klar geworden, dass ein Ringpuffer bei der 
geplanten Anwendung überdimensioniert wäre...
Daher bleibt's dann wohl eher bei 1-10 diskreten Speicherstellen im 
EEPROM die der Reihe nach 65535x beschrieben werden... ;)

von Hagen R. (hagen)


Lesenswert?

>Wenn alle 100 Werte != 0xFF sind dann ist mein gesuchtes Preset die
>#100, beim nächsten Schreiben würde ich das Feld dann komplett wieder
>löschen und Zelle #1 beschreiben.

Ja und, also doch wieder 2 Schreibvorgänge pro Zelle. 100x Preset und 
dann 1x alle 100 Preset alle 100 Zellen löschen. Macht bei mir 2x 
Schreiben pro Zelle und Preset.

Einziger Unterschied ist das vom Timing her bei der kontinuierlichen 
Methode jeweils 2x Schreibzeit des EEPROMs gleichmäßig verteilt entsteht 
und bei deiner alle 100 Preset ein langandauender Schreibzyklus von 100 
Bytes.

Gruß Hagen

von Christian T. (shuzz)


Lesenswert?

@Hagen: Jein. :)
Ich dachte, es sei möglich die Lösch- und Schreibzugriffe getrennt 
vorzunehmen. Das wäre dann nur ein kompletter Lösch-/Schreibzyklus. Und 
obwohl das bei neueren(?) AVRs laut Peter wohl machbar ist wird es 
anscheinend von der avr-libc nicht unterstützt.

(Bei der Gelegenheit mal die Frage an Peter: Was genau sind denn 
diesbezüglich die "neueren" AVRs?!?)

Da mir diese Möglichkeit also nicht offen steht (ich programmiere in C) 
hast Du also Recht, es werden 2 Schreibzugriffe sein. Und ja, mein 
Timing wäre da ungeschickt gewesen... :D

von Peter D. (peda)


Lesenswert?

Christian T. schrieb:
> (Bei der Gelegenheit mal die Frage an Peter: Was genau sind denn
> diesbezüglich die "neueren" AVRs?!?)

Z.B. ATtiny25, ATmega48 und neuer.


> Da mir diese Möglichkeit also nicht offen steht (ich programmiere in C)

Ach Quatsch.
EEPROM schreiben ist kein Hexenwerk, im Datenblatt ist sogar ein 
C-Beispiel drin.
Ich dachte, C-Programmierer können auch programmieren und nicht nur 
fertige Legosteine zusammenschieben.


Peter

von Christian T. (shuzz)


Lesenswert?

Peter Dannegger schrieb:

@Peter: Hast recht, im Datenblatt ist es gut erklärt.
(C-Beispiel hab ich für den Mega48 jetzt nicht gefunden, aber das sollte 
kein Problem sein.)


> Ich dachte, C-Programmierer können auch programmieren und nicht nur
> fertige Legosteine zusammenschieben.

Nun muss ich nur erstmal nen Mega48 bzw. 88 ordern, momentan werkelt 
noch ein "alter" Mega8 in dem Teil und der kann soweit ich das DB 
verstehe nicht getrennt löschen und schreiben.
Dann nehme ich Deine "Herausforderung" aber gerne mal an... ^^

von Hagen R. (hagen)


Lesenswert?

>@Hagen: Jein. :)
>Ich dachte, es sei möglich die Lösch- und Schreibzugriffe getrennt
>vorzunehmen. Das wäre dann nur ein kompletter Lösch-/Schreibzyklus. Und
>obwohl das bei neueren(?)

Falsch. Das spielt beim Vergleich beider Methoden keine Rolle. Wenn ich 
mich in meinem Postings auf "Scheiben" bezog dann ist damit natürlich 
Schreiben, Löschen oder beides in Einem gemeint. Aus Sicht eines 
Vergleiches beider Methoden ist das aber egal da beide Methoden die 
gleichen Operationen benötigen. Der einzige Unterschied ist wie gesagt 
die zeitmäßige Verteilung. Wenn du 100 Zellen auf einmal 
löschst/scheibst dauert das länger als nur 1 oder 2 zellen zu 
bearbeiten. Beim EEPROM ist das schon eine merkliche Zeitspanne.

Ich stimme aber mit der Meinung der Anderen fast überein. 2 Jahre alle 
10 Minuten wird keiner den Preset ändern. Ich vertrete aber auch immer 
die Aufassung das ein guter Programmierer alle Eventualitäten mit 
einkalkuliert. Wenn es nur wenig Mehraufwand in Software kostet, dann 
schadet es sicherlich nicht wenn der Anwender alle 10 Sekunden 10 Jahre 
lang die Presets ändern könnte ohne das ein Problem entsteht.

Davon abgesehen ist es auch eine gute Übung für einen Programmierer wenn 
er sowas mal programmiert hat.

Gruß Hagen

von Michael L. (michaelx)


Lesenswert?

Mit getrenntem Löschen und Schreiben kannst du die Lebensdauer mit 
meinem Vorschlag sogar voll ausnutzen, hat also bei einem 10-Byte-Puffer 
1 Mio. Schreibzugriffe, statt nur 0,5 Mio. wenn Löschen+Schreiben in 
einem Zyklus erfolgen.

Wobei ich denke, dass beim Schreiben von 0xff nur gelöscht, und nicht 
wirklich was geschrieben wird, es der Speicherzelle also praktisch kaum 
schadet.

von asdf (Gast)


Lesenswert?

Würde nicht 1x schreiben (ohne separates Löschen) pro neuem Wert 
ausreichen, wenn 7 Bit für die Kodierung des Zustands ausreichen?

Vorgehen wie folgt, Speicherbereich beliebiger Größe und Lage im EEPROM:

Initialisieren (einmalig):
1. Speicher löschen (0xFF)
2. an letzte Speicherstelle 7 Datenbits (0-6) mit gesetztem Bit 7 
Schreiben

Schreiben:
1.  erste Speicherzelle auslesen,
    Zustand von Bit 7 merken
2.  im gewählten Speicherbereich die
    letzte Speicherzelle suchen, deren
    Bit 7 dem gespeicherten Wert entspricht
3a. das Datum mit identischem Bit 7 an der
    Folgeadresse eintragen falls das Ende
    des Bereichs nicht überschritten wird
3b. andernfalls das Datum mit invertiertem
    Bit 7 am Anfang des Speicherbereichs
    eintragen

Lesen:
1.  erste Speicherzelle auslesen,
    Zustand von Bit 7 merken
2.  im gewählten Speicherbereich die
    letzte Speicherzelle suchen, deren
    Bit 7 dem gespeicherten Wert entspricht,
    die Bits 0-6 enthalten das letzte Datum

D.h. das Bit 7 wird bei jedem Umlauf des Puffers gewechselt und dient 
dem Erkennen des zuletzt eingetragenen Werts.

von Hannes L. (hannes)


Lesenswert?

Sozusagen ein "Toggelbit"...

...

von spess53 (Gast)


Lesenswert?

HI

Anderer Vorschlag: Abschalterkennung einbauen. Beim Einschalten alles in 
den Ram einlesen und damit arbeiten. Erst beim Abschalten den EEProm 
bemühen.

MfG Spess

von Hagen R. (hagen)


Lesenswert?

>Würde nicht 1x schreiben (ohne separates Löschen) pro neuem Wert
>ausreichen, wenn 7 Bit für die Kodierung des Zustands ausreichen?

Wenn du einmal den Ringbuffer gefüllt hast dann stellt sich die Frage 
nach welchem Bit 7 Zustand du entscheiden sollst. Also der Ringbuffer 
enthält die Hälfte an Zellen mit Bit 7 = 0 und die andere mit Bit 7 = 1. 
Die Frage ist nun: muß man das letzte Byte mit gesetztem oder gelöschtem 
Bit 7 lesen ?
Dein System benutzt also nur relative Informationen und keine 
Absolutaussagen. Beim nächsten Start kann der Controller auf Basis von 
nur relativen Infomationen keine Absolutaddresse der nächsten 
Speicherzelle berechnen.
Angenommen du entscheidest dich für Bit 7 = 1. Nach nochmal eine Runde 
den Ringbuffer gefüllt kippt der Zustand von Bit 7 = 1 auf 0. Nun muß 
der Controller also nach Bit 7 = 0 suchen. Somit toggelt das Bit 7 immer 
zwischen 0 und 1 nach jedem Ringbufferdurchgang. Der Controller muß nun 
wissen nach welchem Zustand für Bit 7 er suchen muß. Denn dieser 
Suchzustand muß ja auch toggeln. Du musst also den Suchzustand für Bit 7 
ebenfalls im EPPROM abspeichern und das am besten in einem Ringbuffer. 
Du könntest damit nur das Ratio verändern, also den Anteil an Indexbits 
in Relation zu Datenbits. Schreiben/Löschen müsstest du dann doch wieder 
maximal 2 Bytes pro Preset. In deinem Falle zb. ein Datenringbuffer mit 
256 Bytes. Zugehörig ein Suchbit-Ringbuffer mit 256/8= 32 Bytes. Dieser 
Ringbuffer speichert den aktuellen Suchbiut-Zustand der ja nur alle 256 
Preset toggelt. Also musst du auch nur alle 256 Presets diesen 
Suchbitringbuffer aktualisieren. Nach 256 Presets im Datenringbuffer 
toggelt Bit 7. Diesen neuen Bit 7 Zustand musst du im anderen 32Bytes 
Ringbuffer bitcodiert speichern. Das Ratio sinkt auf 256 zu 1, also 256 
1x Datenschreiben + 1 x Bitzustand schreiben.

Gruß Hagen

von Hagen R. (hagen)


Lesenswert?

Besse wäre es dann so vorzugehen: ein 256 Bytes Ringbuffer. Die obersten 
2 Bits eines jeden Bytes enthält einen Zähler. Dieser geht immer nur von 
0 bis 2, also 3 Elemente. Nun haben wir folgende Konstellation: 256 
Bytes / 3 = teilerfremd. Es muß also in einem gefüllten Ringbuffer immer 
eine Stelle geben bei der die 3 aufeinandrfolgenden Bytes in deren 
obersten 2 Bits eine Indexfolge haben die nicht {0,1,2} mod 3 ist. Nach 
dieser Stelle suchen wir beim Laden. Beim Speichern vervollständigen wir 
diese Stelle mit der richtigen Indexreihenfolge, also {0,1,2} mod 3. Zb. 
ist unsere "Bruchstelle" mit 0,1,1 befüllft. Die dritte Stelle enthält 1 
müsste aber 2 in den Indexbits stehen haben. Ergo der vorherige 
Datenwert ist unser letzter Preset. Der nächste zu scheibende Preset muß 
Indexbits = 2 bekommen. Es entsteht folgedes Speicherbilf der Indexbits:

Aktueller Zustand = 0,1,2,0,1,*1,2,0,1,2,
Mit * unsere Stelle in der die Indixbits unterbrochen sind und somit 
exakt die Speihcerstelle in der der nächste Preset gespeichert werden 
muß. Die Speicherzelle davor ist unser letzter benutzter Preset.

Neuer Zustand = 0,1,2,0,1,2,*2,0,1,2,..

Die Stelle mit * ist die nächste zu beschreibende Zelle die davor 
enthält den gerade geschriebenen Preset.

Das funktioniert weil wir 256 Zellen im Ringbuffer haben aber nur 3 
verschiedene Indizes. Damit ist unser Indexzähler immer teilerfremd zur 
Ringbuffergröße.

Vereinfacht man dies so gilt auch: Ringbuffergröße zb. 7 = Primzahl, 
oder jede ungerade Zahl. Index nur 1 Bit groß, also 0 oder 1.

Unsere Indexbits sehen so aus 0 1 0 1 0 0 1.
Wir suchen nach der Stelle in den Indexbits die nicht abwechselnd 
toggelt. Also hier die unterstrichene 0. Das ist die Speicherstelle in 
der wir den nächsten Preset speichern müssen. Davor die Zelle in der der 
letzte Preset steht. Da 7 teilerfremd zu 2 ist, also 7 Bytes im 
Ringbuffer ist teilerfremd zu 2 Indexbits geht das. Wir finden immer 
eine Stelle deren Indexbits zwei gleiche Werte haben müssen, entweder 00 
oder 11. Es wird nach der Intialisierung immer nur eine eineindeutige 
Stelle geben in der dieser Zustand auftaucht alle anderen sind immer 
abwechseln 0 1 oder 1 0.

Diese Methode verbraucht 1 Indexbit pro Datenbyte und wirklich nur 1 
Schreib/Löschzyklus pro Datenbyte.

Gruß Hagen

von Christian T. (shuzz)


Lesenswert?

Zunächst mal eins vorweg: Ich hab die Geschichte jetzt nach Peter's 
Vorschlag implementiert, tut wunderbar.
Es werden 30 Byte im EEPROM für das "Merken" des zuletzt verwendeten 
Presets verwendet, davon zehn für die tatsächliche Id und 20 als 
"Zugriffscounter".

Bei einem angenommenen "Umschaltintervall" von 10min sollte dieses 
System für die nächsten 125 Jahre funktionieren... ;)
Die Lösung ist für mich vollkommen zufriedenstellend und ausreichend.

Die Idee mit dem Togglebit finde ich trotzdem klasse!
Hagen, ich bin übrigens der Meinung dass es funktionieren sollte, es ist 
imho nicht notwendig den letzten "Toggle-Zustand" zu merken.

Begründung: Der Ringbuffer wird immer in der gleichen Richtung 
beschrieben, von "vorne" nach "hinten". Physikalisch ist es ja nunmal 
ein linearer Speicherbereich.

Beim Neustart des Systems ist es daher ein leichtes, den letzten 
geschriebenen Wert zu finden: Dieser Wert muss das gleiche Togglebit wie 
der erste Wert im Puffer haben. Haben alle Werte im Puffer das gleiche 
Togglebit dann ist der letzte Wert im Puffer auch der aktuell gültige 
(bzw. zuletzt geschriebene).

Somit kriegt man problemlos den Index des letzten aktuellen Wertes raus, 
damit sind dann auch weitere Schreiboperationen kein Problem.

P.S.: Ich möchte auch nochmal allen Beteiligten für die rege Diskussion 
danken. Ihr liefert mir ne Menge prima Ideen für die Zukunft!

von Hagen R. (hagen)


Lesenswert?

>Die Idee mit dem Togglebit finde ich trotzdem klasse!
>Hagen, ich bin übrigens der Meinung dass es funktionieren sollte, es ist
>imho nicht notwendig den letzten "Toggle-Zustand" zu merken.

Korrekt, unter der Annahme: Ringbuffergröße = 2x+1 ist teilerfremd zu 
Indexgröße = 2^y. Das habe ich oben schon erläutert. Bei zb. Indexgröße 
= 2 also 1 Bit groß und 2 Zustände 0 oder 1 muß der Ringbuffer nur eine 
ungerade Anzahl von Elementen haben. Im Grunde simpelste modulare 
Arithmetik.

Gruß Hagen

von Christian T. (shuzz)


Lesenswert?

@Hagen: Imo ist die Puffergröße egal, siehe mein letzter Post.

>Begründung: Der Ringbuffer wird immer in der gleichen Richtung
>beschrieben, von "vorne" nach "hinten". Physikalisch ist es ja nunmal
>ein linearer Speicherbereich.
>
>Beim Neustart des Systems ist es daher ein leichtes, den letzten
>geschriebenen Wert zu finden: Dieser Wert muss das gleiche Togglebit wie
>der erste Wert im Puffer haben. Haben alle Werte im Puffer das gleiche
>Togglebit dann ist der letzte Wert im Puffer auch der aktuell gültige
>(bzw. zuletzt geschriebene).
>
>Somit kriegt man problemlos den Index des letzten aktuellen Wertes raus,
>damit sind dann auch weitere Schreiboperationen kein Problem.

Du gehst von einem "echten" Ringpuffer aus, d.h. der Beginn des Puffers 
ist unbekannt, man kann im Prinzip immer nur das "nächste" Element aus 
dem Puffer ziehen.

Dem ist ja aber nicht so, wir haben es mit einem linearen 
Speicherbereich zu tun. Somit haben wir ja eine absolute Information von 
der wir ausgehen können: Den Beginn des Speicherbereichs des 
Ringpuffers.
Und das aktuell gültige Togglebit (d.h. das Bit mit dem auch der letzte 
Wert geschrieben wurde) ist immer das Togglebit des ersten Eintrags im 
Puffer, d.h. des Eintrags bei Index 0.

von Hagen R. (hagen)


Lesenswert?

@Christian: da hast du natürlich Recht und ich habe das übersehen ;) 
Also ist dein Vorschlag noch einfacher und universeller.

Gruß Hagen

von Christian T. (shuzz)


Lesenswert?

Danke für die Blumen, aber Ehre wem Ehre gebührt: Der Vorschlag war von 
asdf... ;)

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