Forum: Mikrocontroller und Digitale Elektronik eeprom_write_block und Fleury urat lib Problem


von Karsten K. (karsten42)


Lesenswert?

Hallo liebe Wissende!

Ich verwende ein ATMEGA 88a-PU, WinAVR-20100110, urat lib von Peter 
Felury mit 38.000 Baud 8N1
Sobald ich eeprom_write_block() benutze kommt aus der seriellen 
Schnittstelle nur noch schrott. Die Funktion eeprom_read_block() 
funktioniert einwandfrei. Zum Test habe ich ein kleines simples Programm 
geschrieben damit das Problem nachvollziehbar ist.Das PRINT Macro 
benötige ich später für eine formatierte Ausgabe...

Hat jemand eine Idee was hier so furchtbar schief läuft?
1
#include <avr/interrupt.h>
2
#include <avr/io.h>
3
#include <stdio.h>
4
#include <avr/pgmspace.h>
5
#include <avr/eeprom.h>
6
7
#include "uart.h"
8
9
#define  PRINT(string, ...)    printf_P(PSTR(string), ##__VA_ARGS__)
10
11
uint8_t Vdata[3];
12
uint8_t eedata[] EEMEM = { 18, 3, 72 };
13
14
int putchar__(char c, FILE *stream) {
15
  uart_putc(c);
16
  return 0;
17
}
18
19
FILE mystdout = FDEV_SETUP_STREAM(putchar__, 0, _FDEV_SETUP_WRITE);
20
21
int
22
main (void)  {  
23
  
24
  uart_init(UART_BAUD_SELECT(38400UL, F_CPU));
25
  sei();
26
  stdout = &mystdout;
27
28
  // -------------------------------------------------------------------------------
29
  // overwrite Vdata array data with eeprom values
30
  Vdata[0] = 2;
31
  Vdata[1] = 4;
32
  Vdata[2] = 6;
33
34
  PRINT("Prev Data  D0:%d D1:%d D2:%d\n",Vdata[0], Vdata[1], Vdata[2]);
35
36
  eeprom_read_block(Vdata, eedata, sizeof(Vdata));
37
38
  PRINT("Actual read data  D0:%d D1:%d D2:%d\n\n",Vdata[0], Vdata[1], Vdata[2]);
39
40
  // -------------------------------------------------------------------------------
41
  // write Vdata values to eeprom
42
  Vdata[0] = 10;
43
  Vdata[1] = 20;
44
  Vdata[2] = 30;
45
46
  eeprom_write_block(Vdata, eedata, sizeof(Vdata));
47
48
  PRINT("Actual wrote data  D0:%d D1:%d D2:%d\n",Vdata[0], Vdata[1], Vdata[2]);
49
50
  while(1) {
51
52
  }
53
}

Ausgabe OHNE eeprom_write_block()
1
Prev Data  D0:2 D1:4 D2:6
2
    
3
Actual read data  D0:10 D1:20 D2:30
4
5
Actual wrote data  D0:10 D1:20 D2:30


Ausgabe MIT eeprom_write_block()
1
Prev Data  D0:2 D1:4 D2:6
2
                                                      
3
Actuaì ò¥¡¤€´±¼±¤˜ž™˜¢˜™˜¢™&¦¡¡¨,WWéɽѕ‘…Ñ…ÁéÅÁÅéÉÁÉéÍÁ)ÿ

von Der T. (Gast)


Lesenswert?

Block-Mode dauert zu lange -> RS232-Framing-Error?
Wie wird das Busy-Flag ausgewertet?
Als Warteschleife?

von Uwe (de0508)


Lesenswert?

Hallo,

vielleicht noch ein Tipp:

ich würde eeprom_update_block() verwenden.

# http://www.nongnu.org/avr-libc/user-manual/group__avr__eeprom.html

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Karsten K. schrieb:
>   eeprom_read_block(Vdata, eedata, sizeof(Vdata));

Hier fehlt ein
    eeprom_busy_wait ();

>   eeprom_write_block(Vdata, eedata, sizeof(Vdata));

Und hier erst recht.

auserdem vorher:

    uint8_t tmp_sreg = SREG;
    cli();

und nacher:

    SREG = tmp_sreg;

von chris (Gast)


Lesenswert?

Frank M. schrieb:
> Hier fehlt ein
>     eeprom_busy_wait ();
>
>>   eeprom_write_block(Vdata, eedata, sizeof(Vdata));
>
> Und hier erst recht.
>
> auserdem vorher:
>
>     uint8_t tmp_sreg = SREG;
>     cli();
>
> und nacher:
>
>     SREG = tmp_sreg;


Unsinn.
"All of the read/write functions first make sure the EEPROM is ready to 
be accessed."
busy_wait() ist also unnötig.
Und die EEPROM-Funktionen sperren selber die Interrupts wenn nötig, da 
muss man nichts selber dran rumfummeln.

Zum Thema:
seltsames Problem. Selbst wenn die EEPROm-Funktion ewig die Interrupts 
sperrt, kann da eigentlich nichts passieren, außer dass die UART-Ausgabe 
verzögert wird. Falsche Zeichen können da eigentlich nicht vorkommen.
Wie sieht deine RAM-Auslastung aus?

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

chris schrieb:
> Unsinn.
> "All of the read/write functions first make sure the EEPROM is ready to
> be accessed."
> busy_wait() ist also unnötig.

Stimmt, Du hast recht. Danke für den Hinweis.

> Und die EEPROM-Funktionen sperren selber die Interrupts wenn nötig, da
> muss man nichts selber dran rumfummeln.

Das kann ich allerdings nicht nachvollziehen, denn:

"As these functions modify IO registers, they are known to be 
non-reentrant. If any of these functions are used from both, standard 
and interrupt context, the applications must ensure proper protection 
(e.g. by disabling interrupts before accessing them)."

Danach muss man sich um die Sperrung der Interrupts schon selber 
kümmern.

: Bearbeitet durch Moderator
von Karsten K. (karsten42)


Lesenswert?

Hallo Ihr Besten!

Daaanke für die guten Tipps. Es ist nur das Testprogramm wie oben 
beschrieben geladen, es sollte also kein Problem mit RAM geben.

Ich habe den Vorschlag das SREG zu sicher und die INterrupts 
auszuschalten ausprobiert. Es hilft aber leider nicht. Zumal, als 
Unwissender, cli() und danach sei() sollte doch das gleiche machen wie 
das SREG manuell sichern, cli() und dann SREG zurückschreiben oder?

Ich bin komplett ratlos... :-(

von Karsten K. (karsten42)


Lesenswert?

Update:

Mit folgendem CodeTeil läuft es:
1
...
2
3
  tmp_sreg = SREG;
4
  cli();
5
  eeprom_write_block(Vdata, eedata, sizeof(Vdata));
6
  eeprom_busy_wait ();
7
  SREG = tmp_sreg;


Aber ehrlich: Ich möchte nicht etwas einsetzen, das "funktioniert und 
keiner weis warum". Es mag ja nun gerade jetzt eine Konstellation 
eingetreten sein, die das Funktionieren möglich macht. Später, im 
wirklichen Code, gibt´s dann wieder Probleme.

Entscheidend ist hier das eeprom_busy_wait(). Ohne diesem habe ich trotz 
des sperrens der Interrupts wieder "Müll" auf der seriellen 
schnittstelle...

Gruß
Karsten

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Karsten K. schrieb:
> Unwissender, cli() und danach sei() sollte doch das gleiche machen wie
> das SREG manuell sichern, cli() und dann SREG zurückschreiben oder?

Nein, ist nicht dasselbe. Die Sicherung und das Zurückschreiben von SREG 
ist universeller. Es macht nämlich keine Annahme darüber, ob Interrupts 
gerade eingeschaltet sind oder nicht. Das Zurückschreiben stellt einfach 
den alten Zustand wieder her.

Ein sei() hingegen..... schaltet die Interrupts ohne Wenn und Aber ein - 
egal, ob sie vorher aus oder an waren.

> Mit folgendem CodeTeil läuft es:

Gratulation! Offenbar stimmt da das libc-Manual nicht ganz. Ich 
jedenfalls benutze immer eeprom_busy_wait() - auch wenn andere das als 
Unsinn abtun.

: Bearbeitet durch Moderator
von Stefan E. (sternst)


Lesenswert?

Frank M. schrieb:
> Gratulation! Offenbar stimmt da das libc-Manual nicht ganz. Ich
> jedenfalls benutze immer eeprom_busy_wait() - auch wenn andere das als
> Unsinn abtun.

Du liegst relativ falsch.

1) Das Manual ist korrekt. Es verspricht ja nur, dass vor einem 
EEPROM-Zugriff geprüft wird, ob das EEPROM bereit ist. Hier geht es aber 
um ein Abwarten hinterher.

2) Auch bezüglich der Interrupt-Sperren liegst du falsch. Die absolut 
nötigen Sperren erledigt die Lib selber. Der von dir zitierte Teil 
bezieht sich auf Reentrace, die hier aber nicht vorliegt.


Ich wette auch, dass das Problem hier rein gar nichts mit Interrupts zu 
tun hat, und es auch nur mit dem eeprom_busy_wait() funktioniert. Der 
Knackpunkt wird nämlich das Benutzen von LPM während eines laufenden 
EEPROM-Schreibens sein.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Stefan Ernst schrieb:
> Du liegst relativ falsch.

Ich lerne gerne dazu.

> 1) Das Manual ist korrekt. Es verspricht ja nur, dass vor einem
> EEPROM-Zugriff geprüft wird, ob das EEPROM bereit ist. Hier geht es aber
> um ein Abwarten hinterher.

Gut, verstanden. Dann hatte ich das Manual missverstanden.

Das heißt dann aber auch, dass mein ursprünglicher Tipp, nach dem 
Schreiben eeprom_busy_wait() aufzurufen, korrekt und kein Unsinn war, 
wie hier behauptet wurde.

> Ich wette auch, dass das Problem hier rein gar nichts mit Interrupts zu
> tun hat, und es auch nur mit dem eeprom_busy_wait() funktioniert.

Das kann der OP ja leicht testen.

Ich werde die Wette aber nicht eingehen. Es kann gut sein, dass allein 
der wait ausreicht, das will ich nicht abstreiten. Trotzdem wird das 
meine Einstellung, bei solchen Sachen wie EEPROM lieber defensiv 
vorzugehen, nicht ändern. Verkehrt ist es jedenfalls nicht. Schaden tut 
es auch nicht.

: Bearbeitet durch Moderator
von Karsten K. (karsten42)


Lesenswert?

Hallo Frank, Hallo Stefan

@FRank,
Danke für die Ausführung zu sei()  und dem Sichern von SREG. Nun, 
zwischen Zuweisung von SREG an eine tmp variable und dem Aufruf von 
cli() kann ja noch gut und gern das SREG verändert werden. 
Korrekterweise müsste dann das Sichern und das Abschalten der Interrupts 
atomar erfolgen.

@Stefan,
Ahh, ich hatte es so verstanden, dass der Zugriff via eeprom_xx_yy() 
komplett "gesichert" ist. Also auch das warten auf den kompletten 
Vollzug des Schreib/Lesevorganges.
Wenn an dem nicht so ist, kann es in Bezug auf Interrupts ja dann doch 
noch Probleme geben, wenn z.B. ein Schreibvorgang noch nicht 
abgeschlossen ist, aber schon ein Interrupt aktiviert wird. Dann würde 
ein eeprom_busy_wait() auch nur "zufällig" funktionieren. Nämlich dann, 
wenn zwischen dem Schreib/lese Aufruf kein Interrupt erzeugt wird.
Es sei denn, ein Interrupt hat keinerlei Auswirkung auf den 
Schreib/Lesevorgang selbst...

Jungs: Ihr wart eine MEGA Hilfe! Leider gibt´s noch keine 
Mail-3D-Drucker für Bier; sonst würde es jetzt bei euch zu Hause rappeln 
und ein schönes kühles Flens "gedruckt" werden...

gruß
Karsten

von Peter D. (peda)


Lesenswert?

Vielleicht ist ja ein Bug in der Lib.

Probier mal meine Lib aus:
1
#include <util\atomic.h>
2
3
#define  EE_WRITE  1
4
#define  EE_READ    0
5
6
void eeprom( uint8_t *sram, uint16_t eep, uint16_t len, uint8_t write )
7
{
8
  uint8_t edat;
9
10
  do{
11
    while( EECR & 1<<EEPE );    // wait until previous write done
12
    EEAR = eep;
13
    EECR |= 1<<EERE;      // read pulse
14
    edat = EEDR;
15
    if( write ){      // if write flag
16
//      if( edat != *sram ){    // and not equal
17
  EEDR = *sram;      // write EEPROM
18
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE){
19
    EECR |= 1<<EEMPE;
20
    EECR |= 1<<EEPE;    // write pulse
21
  }
22
//      }
23
    }else{
24
      *sram = edat;      // read EEPROM
25
    }
26
    sram++;
27
    eep++;        // next address
28
  }while( --len );
29
}

Die beiden Kommentare am Zeilenanfang sind, damit das Schreiben auch 
wirklich erfolgt.
Später kann man sie rausnehmen.

von Stefan E. (sternst)


Lesenswert?

Frank M. schrieb:
> Das heißt dann aber auch, dass mein ursprünglicher Tipp, nach dem
> Schreiben eeprom_busy_wait() aufzurufen, korrekt und kein Unsinn war,
> wie hier behauptet wurde.

Generell nötig ist eeprom_busy_wait() nicht (und so hattest du es doch 
ursprünglich gemeint, oder?), aber im konkreten Fall ist es (vermutlich) 
die Lösung des Problems.

Deshalb das "relativ". ;-)

von Stefan E. (sternst)


Lesenswert?

Karsten K. schrieb:
> Wenn an dem nicht so ist, kann es in Bezug auf Interrupts ja dann doch
> noch Probleme geben, wenn z.B. ein Schreibvorgang noch nicht
> abgeschlossen ist, aber schon ein Interrupt aktiviert wird.

Welche Probleme?

von Peter D. (peda)


Lesenswert?

Karsten K. schrieb:
> Also auch das warten auf den kompletten
> Vollzug des Schreib/Lesevorganges.

Das hat keinen Einfluß.
Warten muß man nur, wenn man das nächste mal lesen oder schreiben will 
oder ein SPM ausführen.
Interrupts und LPM werden davon nicht beeinflußt.

Zeig dochmal den Schaltplan.
Ich hatte mal komische Effekte, als ich vergessen hatte, AVCC 
anzuschließen.

von Stefan E. (sternst)


Lesenswert?

Peter Dannegger schrieb:
> Interrupts und LPM werden davon nicht beeinflußt.

Ich kann bezüglich des LPM auch nichts im Datenblatt finden, aber die 
Symptome deuten schon recht deutlich in diese Richtung.

von Karsten K. (karsten42)


Lesenswert?

Hallo Peter,

Danke für die lib :-)
Ich bin zu doof den richtigen Cast auszuführen.
1
uint8_t Vdata[3];
2
uint8_t eedata[] EEMEM = { 18, 3, 72 };
3
4
...
5
eeprom(Vdata, eedata, sizeof(Vdata), EE_READ);
6
..

Natürlich meckert der Compiler bei Argument 2 da der Prototyp von 
eepron() ja ein uin16_t als zweites Argument fordert um EEAR in einem 
Rutsch zu füllen.

Ein Schaltplan gibt es noch nicht; Es ist nichts weiter als ein kleines 
Rs232 Modul mit einem MAX232 an TX/RX, einem Reset taster und die 
"normale" Reset beschaltung aus 4K7 und 100nF. AVcc habe ich mit +5V 
belegt.

Ich habe schon alles weggelassen was irgendwie nicht unbedingt notwendig 
ist ( SPI, Taster, LED´s, SIS command decoder usw... )

Gruß
Karsten

von Peter D. (peda)


Lesenswert?

Versuchs mal mit cast:

eeprom(Vdata, (uint16_t)eedata, sizeof(Vdata), EE_READ);

von Sebastian W. (wangnick)


Lesenswert?

Karsten K. schrieb:
> @FRank,
> Danke für die Ausführung zu sei()  und dem Sichern von SREG. Nun,
> zwischen Zuweisung von SREG an eine tmp variable und dem Aufruf von
> cli() kann ja noch gut und gern das SREG verändert werden.
> Korrekterweise müsste dann das Sichern und das Abschalten der Interrupts
> atomar erfolgen.

Nein.

ENTWEDER beim Zuweisen von SREG waren Interrupts deaktiviert. Dann KANN 
sich SREG zwischen der Zuweisung und dem cli() gar nicht verändern.

ODER beim Zuweisen von SREG waren Interrupts aktiv. Dann könnte 
tatsächlich ein Interrupt zwischen der Zuweisung und dem cli() 
stattfinden. Nur: Ein Interrupthandler endet normalerweise mit einem 
RETI, der alle Interrupts wieder aktiviert. Dieses Spielchen könnte 
sogar mehrmals vor dem cli() passieren. Es macht aber nichts. Nach dem 
schlussendlichen cli() läuft dann nur noch das Hauptprogramm. Am Ende 
des geschützten Bereichs wird SREG aus tmp zurückgeladen und die 
Interrupts werden wieder aktiviert. Der restliche Inhalt von SREG, und 
ob er zwischendurch geändert wurde, ist uns ja schnurzegal.

Insofern muss das Sichern von SREG und das anschliessende cli() nicht 
notwendigerweise atomar sein.

LG, Sebastian

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.