Hi!
An einen Mega 32 habe ich 512kB external SRAM über 3 Latches
angeschlossen (1 Portbetrieb plus Steuerleitungen). Zum Zugriff darauf
schrieb ich die Funktionen RemMem() und WriteMem(). Diese funtkionieren
auch OHNE eingeschaltete Optierung, aber nicht mit.
Es ist mir klar, dass die Optimierung auch die Reihenfolge der Befehle,
also den Programmfluss verändern kann. Das würde natürlich dazu führen,
dass die strickt nacheinander auszuführenden Schritte in einer falschen
oder zusammengefassten Folge ausgeführt werden, was den SRAM
durcheinander bringt.
Frage:
1. Wie kann die Reihenfolge so genau festlegen?
2. Muss man sowas inlinen?
3. Kann man die Optimierung für bestimmte Programmteile abschalten?
4. Gibt es noch andere Möglichkeiten?
Ich bin übrigens kein Informatiker, schade eigentlich, aber ich muss es
trotzdem hinbekommen.
Alle Variablen sind volatile, also daran sollte es nicht liegen.
Danke schon mal,
Gruß Gock
Die Reihenfolge kann der Optimierer nicht einfach so ändern.
Aber zeig doch einfach mal den Code oder Compilierfähige Ausschnitte die
dein Problem noch beinhalten.
>1. Wie kann die Reihenfolge so genau festlegen?
In Assembler. In C geht das wohl per Sprach-Definition gar nicht, aber
der gcc hält sich zumindest bei Zugriffen auf volatile-Variablen an die
vorgegebene Reihenfolge.
>2. Muss man sowas inlinen?
Nein.
3. Kann man die Optimierung für bestimmte Programmteile abschalten?
Nein.
4. Gibt es noch andere Möglichkeiten?
Ja ;-)
Aber schau doch erst einmal im generierten Assemblercode nach, was der
Compiler aus deinem Code macht. Typische Ursachen für "funktioniert nur
ohne Optimierung" sind fehlendes volatile, selbstgestrickte
Delayschleifen, und natürlich Probleme durch verändertes Timing.
>Ich bin übrigens kein Informatiker,
Einer zu sein, würde dabei kaum helfen...
Oliver
Ok, Assemblercode...
Ich glaube ich habe eine der Stellen im Assemblercode finden können:
Aus diesem nichtoptimierten Code
1
PORTC |= (MemLowAdr << 4); // AdressLowByte an Memory
2
15bc: a5 e3 ldi r26, 0x35 ; 53
3
15be: b0 e0 ldi r27, 0x00 ; 0
4
15c0: e5 e3 ldi r30, 0x35 ; 53
5
15c2: f0 e0 ldi r31, 0x00 ; 0
6
15c4: 80 81 ld r24, Z
7
15c6: 80 61 ori r24, 0x10 ; 16
8
15c8: 8c 93 st X, r24
9
PORTC &= 0b00001111;
10
15ca: a5 e3 ldi r26, 0x35 ; 53
11
15cc: b0 e0 ldi r27, 0x00 ; 0
12
15ce: e5 e3 ldi r30, 0x35 ; 53
13
15d0: f0 e0 ldi r31, 0x00 ; 0
14
15d2: 80 81 ld r24, Z
15
15d4: 8f 70 andi r24, 0x0F ; 15
16
15d6: 8c 93 st X, r24
17
asm volatile ("nop");
wird das hier:
1
PORTC |= (MemLowAdr << 4); // AdressLowByte an Memory
2
4e6: ac 9a sbi 0x15, 4 ; 21
3
PORTC &= 0b00001111;
4
4e8: 85 b3 in r24, 0x15 ; 21
5
4ea: 8f 70 andi r24, 0x0F ; 15
6
4ec: 85 bb out 0x15, r24 ; 21
7
asm volatile ("nop");
Wie man sieht, gibt er nach Optimierung die MemLowAdress an PORTC nicht
aus (kein out 0x15...), sondern wartet auf die nächste Veränderung des
Werts um diesen dann auszugeben. Dadurch fehlt aber die Adresse am
Speicher.
Das passiert übrigens noch öfters...
Ich habe auch schon ein "asm volatile ("nop");" eingefügt, aber das hat
es auch nicht gebracht.
Wie bekomm ich ihn dazu, die Daten jedesmal am Port auszugeben?
Der Code ist korrekt und mit und ohne Optimierung exakt äquivalent, aber
deine Reihenfolge ist falsch. Erst setzt du die Adresse (|), nur um sie
im nächsten Befehl wieder zu löschen (&). Andersrum ist besser.
Hut Schugh schrieb:
> Wie man sieht, gibt er nach Optimierung die MemLowAdress an PORTC nicht> aus (kein out 0x15...),
doch tut er.
Diese Sequenz
1
15bc: a5 e3 ldi r26, 0x35 ; 53
2
15be: b0 e0 ldi r27, 0x00 ; 0
3
15c0: e5 e3 ldi r30, 0x35 ; 53
4
15c2: f0 e0 ldi r31, 0x00 ; 0
5
15c4: 80 81 ld r24, Z
6
15c6: 80 61 ori r24, 0x10 ; 16
7
15c8: 8c 93 st X, r24
holt über den Z-Pointer den momentanen Inhalt vom PORTC, setzt Bit
Nummer 4 (0x10, 16) und gibt den neuen Wert am PORTC über den X Pointer
wieder aus.
Das hier
1
4e6: ac 9a sbi 0x15, 4 ; 21
macht genau dasselbe: Setze Bit Nr 4 am PORTC
Nur 2 Gänge schneller :-)
> Werts um diesen dann auszugeben. Dadurch fehlt aber die Adresse am> Speicher.
Mir ist sowieso nicht ganz klar, was du da vor hast.
Du setzt Bit Nr 4 um es gleich darauf mit dem
1
PORTC&=0b00001111;
wieder zu löschen.
Ich denke auch, dass du in Wirklichkeit ein Timing/Logik Problem hast
und dein ursprünglicher Code mehr zufällig funktioniert hat.
Verdammt, Du hast Recht (sbi 0x15 usw...). Vielleicht ist er dann doch
einfach zu schnel für den Speicher...
1
#define MemLowAdr 1
2
#define MemHighAdr 2
3
#define MemKonfigAdr 3
4
#define MemoryAdress 4
5
[...]
6
voidWriteMem(volatileuint16_tDataAdress,volatileuint8_tMemQuart,volatileuint16_tMemData){// Schribt MemData an DataAdress, wobei MemQuart (Quartal) ein Quartal definiert
7
cli();
8
MemQuart=MemQuart<<1;// MemQuart x 2, weil Low und HighByte getrennt gespeichert werden müssen (64k Bytes *2 *4)
9
// LowAdress anlegen
10
PORTD=DataAdress;
11
PORTC|=(MemLowAdr<<4);// AdressLowByte an Memory
12
PORTC&=0b00001111;
13
// HighAdress anlegen
14
PORTD=(DataAdress>>8);// AdressHighbyte an Memory
15
PORTC|=(MemHighAdr<<4);
16
PORTC&=0b00001111;
17
// KonfigByte anlegen
18
PORTD=((MemQuart|(1<<CUSPtoDRDY)));//Wahl des Quartals, CUSP aus, WRITE = Low
19
PORTD&=~(1<<PD3);
20
PORTC|=(MemKonfigAdr<<4);
21
PORTC&=0b00001111;
22
// Chipselect Memory
23
PORTC|=(MemoryAdress<<4);
24
// DatanLowByte ausgeben
25
PORTD=MemData;// DataLow auf Bus
26
asmvolatile("nop");// Speicher benötigt Zeit WICHTIG !!!!!!!!!!!!!!!!!!
27
// CS-Adresse für Memory wegnehmen
28
PORTC&=0b00001111;
29
// KonfigByte ändern um HighByte in 2.Teil des Quartals zu schreiben
asmvolatile("nop");// Speicher benötigt Zeit ??? WICHTIG !!!!!!!!!!!!!!!!!!
37
// Chipselect Memory
38
PORTC|=(MemoryAdress<<4);
39
asmvolatile("nop");// Speicher benötigt Zeit ??? WICHTIG !!!!!!!!!!!!!!!!!!
40
// CS-Adresse für Memory wegnehmen
41
PORTC&=0b00001111;
42
// WE wieder High (active low) -> Read
43
PORTD|=(1<<CUSPtoDRDY)|(1<<PD3);
44
PORTC|=(MemKonfigAdr<<4);
45
PORTC&=0b00001111;
46
MemQuart=MemQuart>>1;
47
sei();
48
}
49
50
uint16_tReadMem(volatileuint16_tDataAdress,volatileuint8_tMemQuart){// bisher nur 8Bit Adresse
51
cli();
52
uint16_tMemData;
53
uint8_ttemp2;
54
MemQuart=MemQuart<<1;
55
// Output Enable von Memory = low (active LOW)
56
PORTB&=~(1<<PB4);
57
// KonfigByte ausgeben: Quartal wählen, CUSP aus, READ
58
PORTD=(MemQuart|(1<<CUSPtoDRDY)|(1<<PD3));// WE auf High (active low) -> Read und CUSP aus
59
PORTC|=(MemKonfigAdr<<4);
60
PORTC&=0b00001111;
61
// AdressLowByte an Memory
62
PORTD=DataAdress;
63
PORTC|=(MemLowAdr<<4);
64
PORTC&=0b00001111;
65
// AdressHighbyte an Memory
66
PORTD=(DataAdress>>8);
67
PORTC|=(MemHighAdr<<4);
68
PORTC&=0b00001111;
69
// Port konfigurieren
70
DDRD=0;// PortD ist EIngang
71
PORTD=0;// keine PullUps
72
// Chipselect Memory
73
PORTC|=(MemoryAdress<<4);
74
asmvolatile("nop");// Speicher benötigt Zeit WICHTIG !!!!!!!!!!!!!!!!!!
75
asmvolatile("nop");// Speicher benötigt Zeit WICHTIG !!!!!!!!!!!!!!!!!!
76
// DatenLowByte einlesen
77
MemData=PIND;
78
PORTC&=0b00001111;// Chipselect wegnehmen
79
// Port konfigurieren
80
DDRD=255;//Port als Ausgang
81
// WE auf High (active low) -> Read und CUSP aus
82
PORTD=((MemQuart+1)|(1<<CUSPtoDRDY)|(1<<PD3));
83
PORTC|=(MemKonfigAdr<<4);
84
PORTC&=0b00001111;// Chipselect wegnehmen
85
// Port konfigurieren
86
DDRD=0;// PortD ist EIngang
87
PORTD=0;// keine PullUps
88
// Chipselect Memory
89
PORTC|=(MemoryAdress<<4);
90
//DatenHighByte einlesen
91
asmvolatile("nop");// Speicher benötigt Zeit WICHTIG !!!!!!!!!!!!!!!!!!
92
asmvolatile("nop");// Speicher benötigt Zeit WICHTIG !!!!!!!!!!!!!!!!!!
93
temp2=PIND;
94
PORTC&=0b00001111;// Chipselect wegnehmen
95
// Output Enable von Memory = high
96
PORTB|=(1<<PB4);
97
DDRD=255;// PORTD ist wieder AUsgang
98
MemData=MemData|(temp2<<8);
99
MemQuart=MemQuart>>1;
100
returnMemData;
101
sei();
102
}
Der Speicher ist eine 55n Version und keins seiner timings liegt unter
55ns. Der µCTakt ist 16MHz, also 62,5ns. Trotzdem war es nötig
(möglicherseise wegen Siganllaufweg, einige "nop" einzufügen, damit
alles reibungsfrei läuft.
Was ist da sonst noch falsch???
Die PORT befehle kann ich nicht vertauschen:
PORTC |= (MemLowAdr << 4); schaltet über einen 4 zu 16 DeMux den
Chipselect vom LowAdressLatch frei, nachdem vorher die Adresse angelegt
wurde.
PORTC &= 0b00001111; nimmt den CS wieder weg und schaltet auf 0 (frei)
Daher ist die Reihenfolge so wichtig...
Karl heinz Buchegger schrieb:
> Ich denke auch, dass du in Wirklichkeit ein Timing/Logik Problem hast> und dein ursprünglicher Code mehr zufällig funktioniert hat.
Da die Inputs bei den AVRs zur Vermeidung metastabiler Zustände einen
Takt verzögert werden, ist durchaus denkbar, dass ein IN kurz nach dem
löschenden OUT noch den richtigen SRAM Wert für die richtige Adresse
liefert. Sofern kein Interrupt und kein störendes(!) NOP dazwischen
kommt.
Hut Schugh schrieb:
> PORTC &= 0b00001111; nimmt den CS wieder weg und schaltet auf 0 (frei)> Daher ist die Reihenfolge so wichtig...
Bei inaktivem CS lesen ist aber auch nicht der Brüller. Erst Adresse
raus, dann CS aktivieren, dann lesen, dann CS deaktivieren.
Und bei 16 Bit auf 8 Bit fehlt halt die Hälfe. Die obere,
A. K. schrieb:
> Bei inaktivem CS lesen ist aber auch nicht der Brüller. Erst Adresse> raus, dann CS aktivieren, dann lesen, dann CS deaktivieren.>> Und bei 16 Bit auf 8 Bit fehlt halt die Hälfe. Die obere,
Ja, so soll es doch auch sein: Adresse LOW anlegen -> CS schalten ->
Latch hält Adresse -> nächste (HIGH) Adresse anlegen -> CS High Adresse
usw. ... Lesen lassen.
Das Latch hat timings von max 50ns.
Aber ich glaube mittlerweile tatsächlich, dass es vorher zufällig
funktioniert hat und werde die gesamte Kette nochmal überprüfen.
> PORTC |= (MemLowAdr << 4); schaltet über einen 4 zu 16 DeMux> den Chipselect vom LowAdressLatch frei, nachdem vorher die> Adresse angelegt wurde.> PORTC &= 0b00001111; nimmt den CS wieder weg und schaltet auf 0 (frei)> Daher ist die Reihenfolge so wichtig...
machs mal so
Hut Schugh schrieb:
> Ja, so soll es doch auch sein: Adresse LOW anlegen -> CS schalten ->> Latch hält Adresse -> nächste (HIGH) Adresse anlegen -> CS High Adresse> usw. ... Lesen lassen.
Mein Text basierte noch auf den anfänglichen 2 Zeilen statt dem gesamten
Code, und da meine Glaskugel ein paar Sprünge hat, kann es bei Raten
auch mal zu Fehlern kommen.
Eins ist jedenfalls Mist: Beim Lesezyklus CS und OE aktivieren und dann
den Port D gegen den Datenbus vom SRAM Ausgang gegen Ausgang kämpfen zu
lassen. Du darft CS erst aktivieren, wenn der ganze Adresszirkus durch
ist und DDRD auf 0 steht.
Also die Stelle im ReadMem, wo das Output enable nicht ausgeschaltet
wird habe ich gefunden und behoben, danke. Ich habe jetzt erst den Port
auf Eingang geschaltet, dann CS aktiviert und dann OE eingeschaltet. Das
hat eine Änderung in der Zahl gebracht die ich schreibe und anschließend
wieder lese. Aber sie ist noch nicht identisch.
Das fehlende CS im WriteMem kann ich nicht finden, besser gesagt, da ist
doch eins, oder?
Es funktioniert ! ! !
In allen 3 Optimierungsstufen!
Fehler behoben:
- Output Enable am Speicher aktiviert und Port auf Ausgang
- Reihenfolge von OE und CS Aktivierung vertauscht (jetzt erst OE und
dann CS ein, beim Ausschalten umgekehrt)
- "Ruhezeiten" zT vergrößert und an anderer Stelle eingelegt
Ergebnis:
Ohne Optimierung benötigte ich eine "nop"s, weil der DeMux nicht der
schnellste ist. Durch die Optimierung hat sich der Codeaufwand
verschoben und nun brauche ich mehr "nop"s und zT an anderer Stelle.
Zur Sicherheit werde ich noch ein paar nops einbauen, wegen
Temperaturschwankung oder Alterung.
Vielen Dank an Euch alle, Ihr habt sehr geholfen und ich kann endlich
wieder ruhig schlafen!!!
Hier nochmal der funktionierende Code:
1
voidWriteMem(uint16_tDataAdress,uint8_tMemQuart,uint16_tMemData){// Schribt MemData an DataAdress, wobei MemQuart (Quartal) ein Quartal (4x(2x64k)) (16kx16Bit = 128k) des Speichers bedeutet, da nur 16BitAdressen verwendet werden können
2
cli();
3
MemQuart=MemQuart<<1;// MemQuart x 2, weil Low und HighByte getrennt gespeichert werden müssen (64k Bytes *2 *4)
4
// LowAdress anlegen
5
PORTD=DataAdress;
6
PORTC|=(MemLowAdr<<4);// AdressLowByte an Memory
7
PORTC&=0b00001111;
8
// HighAdress anlegen
9
PORTD=(DataAdress>>8);// AdressHighbyte an Memory
10
PORTC|=(MemHighAdr<<4);
11
PORTC&=0b00001111;
12
// KonfigByte anlegen
13
PORTD=((MemQuart|(1<<CUSPtoDRDY)));//Wahl des Quartals, CUSP aus, WRITE = Low
14
PORTD&=~(1<<PD3);
15
PORTC|=(MemKonfigAdr<<4);
16
PORTC&=0b00001111;
17
// DatanLowByte ausgeben
18
PORTD=MemData;// DataLow auf Bus// Chipselect Memory
19
PORTC|=(MemoryAdress<<4);
20
// CS-Adresse für Memory wegnehmen
21
PORTC&=0b00001111;
22
// KonfigByte ändern um HighByte in 2.Teil des Quartals zu schreiben