Hallo,
benötige leider mal wieder Eure Hilfe, da ich selber nicht weiterkomme
und mir googeln auch nichts gebracht hat.
Ich habe eine Software für den Atmega162 geschrieben. An dem Controller
hängt ein LCD-Display und externer Ram. Das LCD-Display wird im
memory-mapped betrieben. Bis gestern lief auch alles wunderbar.
Als Compiler hatte ich den avr-gcc.exe (GCC) 8.5.1 20241123.
Heute habe ich noch eine weitere Datei erstellt. Das Compilieren klappt
einwandfrei und mir wurde kein Fehler gemeldet.
Wenn ich das Programm auf den Controller übertrage, stürzt das ab. Am
Anfang des Programms habe ich zur Kontrolle 2 LEDs geschaltet. Die
kommen auch noch. Mein Verdacht war, wenn auf den RAM zugegriffen wird,
dass es dabei hakt.
Vorab zur Information. Auf derselben Hardware läuft eine Software, die
erheblich mehr Ram benötigt. Auch das LCD-Display funktioniert. An der
Hardware liegt es nicht.
Um das Problem näher einzugrenzen, habe ich sämtliche Dateien nach Atmel
Studio kopiert. Dort lassen die sich nicht compilieren. Ich erhalte die
im Betreff angegebene Fehlermeldung. Exakt ist die Fehlermeldung: "Error
2 (near initialization for 'eingabenmenu[0].text')". Allerdings auch für
alle anderen Elemente von eingabenmenu. Dazu noch "Error 1 initializer
element is not constant" für alle Zeilen. Ich vermute, dass mit meiner
Initialsierung was nicht stimmt.
Hier mal, wie das Ganze aussieht.
1
typedefstructdisplay
2
{
3
constchar*text_zeile[LCD_DISP_LENGTH];
4
}t_display;
5
6
typedefstruct{
7
t_displaytext;
8
constint8_tprevious;
9
constint8_tnext;
10
int8_t(*fp)(void);
11
}t_menu;
12
13
constt_displayeingabe_datum_display={
14
{" Eingabe Datum",
15
"Bestätigen mit Enter",
16
"Weiter mit Down",
17
"Zurück mit Up"},
18
};
19
20
constt_displayeingabe_zeit_display={
21
{" Eingabe Zeit",
22
"Bestätigen mit Enter",
23
"Weiter mit Down",
24
"Zurück mit Up"},
25
};
26
27
staticconstt_menueingabenmenu[]={
28
{eingabe_datum_display,-1,1,eingabe_datum},
29
{eingabe_zeit_display,1,2,eingabe_zeit},
30
};
Jetzt meine Fragen:
1. Was mache ich falsch bei der Initialsierung? Ich nehme mal an, dass
es was mit den Arrays bzw. Strukturen zu tun hat und mir das Programm
deshalb abstürzt.
2. Warum hat mir der Compiler von Atmel Studio diesen Fehler gemeldet,
aber nicht der 8.5.1? Bzw. welche Einstellung muss ich angeben, damit
der mir das auch als Fehler meldet?
3. Ich habe in einer Datei "#include <time.h>" stehen. Der Compiler von
Atmel Studio findet die nicht. Beim 8.5.1 kein Problem. Was müsste ich
angeben, damit der Fehler bei Atmel Studio nicht kommt? Dies ist aber
zweitrangig, da Atmel Studio von mir eigentlich nicht verwendet wird.
Vorab schon mal vielen Dank.
Viele Grüße
Andreas
Andreas schrieb:> Als Compiler hatte ich den avr-gcc.exe (GCC) 8.5.1 20241123.
Und wie/womit rufst du den auf?
Zeig doch mal den Aufruf.
time.h ist Teil der avrlibc. Das sollte auch beim Studio dabei sein.
Atmel Studio klingt allerdings deprecated. Welche Version ist das?
Oliver
in t_menu verwenden, und in der Initialisierung den Adressoperator &
verwenden.
Also statt
1
{eingabe_datum_display,-1,1,eingabe_datum},
das hier:
1
{&eingabe_datum_display,-1,1,eingabe_datum},
Andreas schrieb:> Wenn ich das Programm auf den Controller übertrage, stürzt das ab.
Auch wenn hier im Forum manche Zeitgenossen unterwegs sind, die meinen,
daß man einen Debugger nie brauchen würde, hier hilft er.
Lass' das Programm im Debugger laufen und Du siehst, was schiefläuft.
Debuginterfaces sind auch für AVRs kein überteuertes Hexenwerk mehr.
Oliver S. schrieb:> time.h ist Teil der avrlibc. Das sollte auch beim Studio dabei sein.> Atmel Studio klingt allerdings deprecated. Welche Version ist das?
Das ist 6.2. Die aktuelle Version läuft bei mir nicht.
Harald K. schrieb:> Statt t_display text;>> würde ich t_display *text;
Danke, werde ich alles morgen ausprobieren und berichten.
Harald K. schrieb:> Auch wenn hier im Forum manche Zeitgenossen unterwegs sind, die meinen,> daß man einen Debugger nie brauchen würde, hier hilft er.
Das war meine Überlegung, das im Simulator von Atmel Studio laufen zu
lassen.
Das Problem ist aber, wenn ich das im Debugger laufen lasse, dann sehe
ich zwar, dass er bei dem Zugriff auf die Variablen abstürzt. Aber mir
ist dann immer noch nicht klar, wie ich die korrekt initialisieren muss.
Erstmal danke
Ich gehe mal davon aus dass du in C (und nicht in C++) programmierst.
Vorab: Warum dein Programm crasht, weiß ich auch nicht. aber für die
Fehlermeldung, die nur in bestimmten GCC-Versionen erscheint, gibt es
eine Erklärung:
Du benutzt Variablen (eingabe_datum_display und eingabe_zeit_display)
als constant expressions, was durch den C-Standard nicht abgedeckt ist
und deswegen bei älteren GCCs zu Fehlermeldungen führt (dass die
Variablen als const deklariert sind, ändert nichts daran, dass es
Variablen sind).
Offensichtlich betrachten neuere GCCs const-deklarierte Variablen
dennoch als constant expressions. Lt. Standard ist das eine legale
Spracherweiterung:
"An implementation may accept other forms of constant expressions."
Dein Code ist insofern nicht falsch, aber auch nicht gut, da die Daten
in eingabe_datum_display und eingabe_zeit_display zweimal im Speicher
auftreten, nämlich einmal in den Variablen selbst und zusätzlich als
Kopie in eingabenmenu.
Besser ist es, die entsprechenden Daten nicht in eigene Variablen,
sondern direkt in den Initialisierer von eingabenmenu zu schreiben.
Du kannst auch Haralds Vorschlag folgen, dann werden werden in
eingabenmenu statt der Kopien von eingabe_datum_display und
eingabe_zeit_display nur jeweils ein Pointer (2 Bytes) angelegt. Aber
auch 2×2 Bytes sind etwas verschenkter Speicherplatz. Zudem werden durch
die Indirektion über die Pointer die Zugriffe auf die Daten aufwendiger
(sowohl was die Codegröße als auch die Ausführungszeit betrifft).
Das alles sollte aber das Programm nicht crashen lassen, es sei denn,
dass durch die doppelte Speicherbelegung der RAM-Platz am Ende doch
etwas knapp wird.
Andreas schrieb:> 2. Warum hat mir der Compiler von Atmel Studio diesen Fehler gemeldet,> aber nicht der 8.5.1?
Weil du im Atmel Studio mit dem C-Compiler kompilierst, vorher aber
offenbar mit C++.
Andreas schrieb:> static const t_menu eingabenmenu[] = {> {eingabe_datum_display ,-1 ,1 ,eingabe_datum},
Hier kopierst du ja eingabe_datum_display nach eingabenmenu hinein. In C
kann man structs nicht so direkt zuweisen, in C++ schon.
Yalu X. schrieb:> Besser ist es, die entsprechenden Daten nicht in eigene Variablen,> sondern direkt in den Initialisierer von eingabenmenu zu schreiben.
Wie würde das dann aussehen? So:
1
staticconstt_menueingabenmenu[]={
2
{{" Eingabe Datum",
3
"Bestätigen mit Enter",
4
"Weiter mit Down",
5
"Zurück mit Up"},-1,1,eingabe_datum},
6
{{" Eingabe Zeit",
7
"Bestätigen mit Enter",
8
"Weiter mit Down",
9
"Zurück mit Up"},1,2,eingabe_zeit},
10
};
Yalu X. schrieb:> Das alles sollte aber das Programm nicht crashen lassen, es sei denn,> dass durch die doppelte Speicherbelegung der RAM-Platz am Ende doch> etwas knapp wird.
Das war definitiv nicht. Es wurde mir angezeigt, dass 110% Ram belegt
sind. Dies war aber bezogen auf den internen SRAM des Atmega162. Bei dem
anderen Programm, wass erheblich mehr Ram benötigte, wurde mir 500%
angezeigt. Als Extenen RAM hatte ich 32K. Ich habe auch einen Ramtest
über den gesamten Rambereich laufen lassen, es wurde mir kein Fehler
angezeigt.
Niklas G. schrieb:> Weil du im Atmel Studio mit dem C-Compiler kompilierst, vorher aber> offenbar mit C++.
Nein. Es war beides definitiv der C-Compiler.
Andreas schrieb:> Es wurde mir angezeigt, dass 110% Ram belegt sind.
Das ist schlecht und sollte keinesfalls ignoriert werden.
> Dies war aber bezogen auf den internen SRAM des Atmega162. […] Als> Extenen RAM hatte ich 32K.
Das solltest du den Linker aber auch wissen lassen. Sonst legt er dir
den Stack mitten in den Datenbereich. Wenn dadurch bspw. einer der
Funktionszeiger in eingabenmenu überschrieben wird, ist der Crash schon
sicher.
Ändere also das Linkerskript entsprechend, dann wird als netter
Nebeneffekt auch die prozentuale RAM-Belegung korrekt angezeigt.
Yalu X. schrieb:> Das solltest du den Linker aber auch wissen lassen. Sonst legt er dir> den Stack mitten in den Datenbereich. Wenn dadurch bspw. einer der> Funktionszeiger in eingabenmenu überschrieben wird, ist der Crash schon> sicher.
Meinst Du das hier:
Das ist drin.
Niklas G. schrieb:> Beim Initialisieren ein struct zu kopieren ist kein standardkonformes C,> aber der GCC kann es ab Version 8 als eine Spracherweiterung. Dein Atmel> Studio wird wohl eine ältere Version verwenden.
Danke für die Info.
Ich habe es gerade so:
1
staticconstt_menueingabenmenu[]={
2
{{" Eingabe Datum",
3
"Bestätigen mit Enter",
4
"Weiter mit Down",
5
"Zurück mit Up"},-1,1,eingabe_datum},
6
7
{{" Eingabe Zeit",
8
"Bestätigen mit Enter",
9
"Weiter mit Down",
10
"Zurück mit Up"},1,2,eingabe_zeit},
11
};
ausprobiert mit dem gcc 8.5.1. Zuerst kam die Warnung, dass um den Text
noch geschweifte Klammern fehlen. Als ich die drum gemacht hatte, liess
sich das Programm einwandfrei compilieren.
Kommentiere ich die Funktionen, in denen auf diese structs zugegriffen
wird, aus, dann startet mein Programm. Sobald ich die Kommentierung
rausnehme, läuft die Software erst gar nicht an.
Ich werde es morgen mal ausprobieren, wie Harald geschrieben hat, aber
ich glaube im Moment nicht, dass das mein Problem löst.
Ich habe diese Mimik mit den structs noch in einem anderen Programmteil.
Da funktionierte das bis gestern einwandfrei. Erst als ich heute noch
eine weitere C-Datei hinzugefügt hatte, die auch diese structs, nur mit
anderem Text und Funktionen verwendet, hat es nicht mehr funktioniert.
Ich bin dam Verzweifeln.
Andreas schrieb:> Meinst Du das hier:> -Wl,-Tdata=0x800500 -Wl,--defsym,__DATA_REGION_ORIGIN__=0x800500
-Wl,--defsym=__DATA_REGION_LENGTH__=32k
> Das ist drin.
Ja, damit sollte es eigentlich funktionieren (wenn ich nicht etwas
übersehen habe). Der Stack liegt damit zwar unverändert am oberen Ende
des internen RAM, da die Daten nun aber komplett im externen RAM liegen,
sollte es keine Konflikte geben. Ganz im Gegenteil, denn der Stack darf
jetzt auf bis zu 1 KiB (die Größe des internen RAM) anwachsen, ohne dass
er mit irgendetwas kollidiert.
Andreas schrieb:> Das war meine Überlegung, das im Simulator von Atmel Studio laufen zu> lassen.
Debugger, nicht Simulator.
> Das Problem ist aber, wenn ich das im Debugger laufen lasse, dann sehe> ich zwar, dass er bei dem Zugriff auf die Variablen abstürzt.
Das hat sich ja wohl mittlerweile geklärt.
Harald K. schrieb:> Andreas schrieb:>> Das war meine Überlegung, das im Simulator von Atmel Studio laufen zu>> lassen.>> Debugger, nicht Simulator.
Laufen tut es da im Simulator. Mit dem Debugger kann man das dann
debuggen.
Oliver
> Das ist drin.
Schau mal im MAP File, ob das auch Effekt hat. Ob die Symbole verwendet
werden, hängt nämlich an der Version der Binutils. Das ld Script muss
dazu so beginnen:
Johann L. schrieb:> Schau mal im MAP File, ob das auch Effekt hat. Ob die Symbole verwendet> werden, hängt nämlich an der Version der Binutils. Das ld Script muss> dazu so beginnen:
Das finde ich da überhaupt nicht drin. Ich habe folgendes darin
gefunden:
1
NameOriginLengthAttributes
2
text0x000000000x00004000xr
3
data0x008005000x00008000rw!x
4
eeprom0x008100000x00000200rw!x
5
fuse0x008200000x00000003rw!x
6
lock0x008300000x00000400rw!x
7
signature0x008400000x00000400rw!x
8
user_signatures0x008500000x00000400rw!x
9
*default*0x000000000xffffffff
Das Map-File habe ich mal angehangen. Allerdings ist es nicht die
Version, in der ich das so gemacht habe, wie Harald vorgeschlagen hat
sondern, so
1
staticconstt_menueingabenmenu[]={
2
{{" Eingabe Datum",
3
"Bestätigen mit Enter",
4
"Weiter mit Down",
5
"Zurück mit Up"},-1,1,eingabe_datum},
6
{{" Eingabe Zeit",
7
"Bestätigen mit Enter",
8
"Weiter mit Down",
9
"Zurück mit Up"},1,2,eingabe_zeit},
10
};
Harald K. schrieb:> Debugger, nicht Simulator.
Das mit dem Debugger habe ich noch nicht verstanden. Zum Debuggen müsste
ich doch die Fuses für JTAG setzen. Da ich den Controller aber im
memory-mapped betreibe, würde das doch nicht funktionieren.
Ich tendiere im Moment dazu den ganzen Kram in den Flash zu verlagern,
in der Hoffnung, dass ich dann die Probleme nicht habe.
Andreas schrieb:> Ich tendiere im Moment dazu den ganzen Kram in den Flash zu verlagern,> in der Hoffnung, dass ich dann die Probleme nicht habe.
Würde ich an deiner Stelle auch machen. Welchen Sinn sollte es haben,
konstante Strings im RAM vorzuhalten, die man eh nie ändern möchte?
Dafür ist der RAM eines AVR einfach zu teuer (im Vergleich zum Flash).
Oliver S. schrieb:> Laufen tut es da im Simulator. Mit dem Debugger kann man das dann> debuggen.
Das ist schön für den Simulator, nur ist es hilfreicher, den Code auf
dem eigentlichen Controller laufen zu lassen. Der sieht nämlich dann
auch die realen In- und Outputs, auch wenn das bei diesem
Initialisierungsproblem nicht wirklich relevant ist.
Der Simulator ist ein Relikt aus der Zeit, als man nichts anderes hatte
bzw. man mit sündhaft teuren ICEs hantieren musste. Sowas ist Schnee von
gestern.
Andreas schrieb:> Zum Debuggen müsste> ich doch die Fuses für JTAG setzen.
Oh, damit schießt man sich natürlich in den Fuß, insbesondere, wenn das
auch noch ein µC ist, der nichts anderes kann. Neuere AVRs können u.a.
debugWIRE, was nur einen Pin belegt (der, da sonst /RESET, sonst auch
nicht oft verwendet wird). UPDI, das andere AVRs haben, braucht auch nur
einen Pin.
Nun gut; wenn Du drauf angewiesen bist, diesen alten µC verwenden zu
müssen, und das JTAG-Interface nicht nutzen kannst, dann bist Du auf so
etwas wie den Simulator oder vollständige Intuition angewiesen.
Jörg W. schrieb:> Welchen Sinn sollte es haben, konstante Strings im RAM vorzuhalten, die> man eh nie ändern möchte?
Der ursprüngliche Gedanke war, da ich ausreichend RAM zur Verfügung
habe, aber der Flash eventuell zu klein werden könnte, das direkt in den
RAM zu legen. Da ich mich aber in den letzten Tagen dazu entschlossen
habe einen anderen Controller zu nehmen, der ausreichend Flash hat, kann
das jetzt auch dahin.
Harald K. schrieb:> Nun gut; wenn Du drauf angewiesen bist, diesen alten µC verwenden zu> müssen,
Angewiesen bin ich nicht. Aber das ist einer derjenigen, mit denen ich
seit Jahren spiele, den also kenne.
Andreas schrieb:> Der ursprüngliche Gedanke war, da ich ausreichend RAM zur Verfügung> habe, aber der Flash eventuell zu klein werden könnte, das direkt in den> RAM zu legen.
Trugschluss. Alle Konstanten im RAM brauchen eh gleich viel Flash noch
dazu – woher sollte denn sonst der RAM beim Start initialisiert werden?
Vermutlich hat der Crash überhaupt nichts mit der Initialisierung der
Menüdatenstruktur zu tun. Du hattest diese ja nur deswegen im Verdacht,
weil der alte GCC des Atmel Studios dort wegen der Nutzung einer neueren
Spracherweiterung einen Fehler gemeldet hat.
Andreas schrieb:> Ich tendiere im Moment dazu den ganzen Kram in den Flash zu verlagern,> in der Hoffnung, dass ich dann die Probleme nicht habe.
Das ist sicher sinnvoll, aber ich würde damit noch ein wenig warten, bis
der eigentliche Fehler gefunden ist. Sonst läufst du Gefahr, den Fehler
zu kaschieren, ohne ihn behoben zu haben. Irgendwann wird er dann erneut
zuschlagen.
Ich würde eher hier ansetzen:
Andreas schrieb:> Heute habe ich noch eine weitere Datei erstellt.
Möglicherweise hast du den Fehler damit eingeschleppt. Sicher ist das
aber nicht, denn der Fehler könnte auch schon vorher existiert haben,
ist aber erst durch die Einführung des neuen Moduls (und der damit
verbundenen Verschiebung von Code- und Datenfragmenten) zu Tage
getreten.
Andreas schrieb:> Zum Debuggen müsste> ich doch die Fuses für JTAG setzen. Da ich den Controller aber im> memory-mapped betreibe, würde das doch nicht funktionieren.
Dann versuch es mit LED-Debugging (was tu ja teilweise bereits getan
hast) und/oder Printf-Debugging.
Yalu X. schrieb:> Dann versuch es mit LED-Debugging (was tu ja teilweise bereits getan> hast) und/oder Printf-Debugging.
Das ist Debugging nach Gehör. Da ist der Simulator doch besser, bei dem
kann man Breakpoints setzen, Adressen ansehen etc.
Andreas schrieb:> Ich tendiere im Moment dazu den ganzen Kram in den Flash zu verlagern,> in der Hoffnung, dass ich dann die Probleme nicht habe.
Irgendwie passen die Typen doch nicht zusammen. Das mit __flash zu
dekorieren ändert doch nix daran.
> Johann L. schrieb:>> Schau mal im MAP File, ob das auch Effekt hat. Ob die Symbole verwendet>> werden, hängt nämlich an der Version der Binutils. Das ld Script muss>> dazu so beginnen:> Das finde ich da überhaupt nicht drin.
Das Map-File enthält auch kein ld Skript, sondern Werte von Symbolen,
deren Lokatierung etc. Default ld Skripte werden zu Referenz
installiert in $prefix/avr/lib/ldscripts.
> Ich habe folgendes darin gefunden:> [c]> Name Origin Length Attributes> text 0x00000000 0x00004000 xr> data 0x00800500 0x00008000 rw!x
Sieht doch gut aus.
Wie wird eigentlich SP initialisiert? Default für __stack ist RAMEND
aus <avr/io.h>, und das wird durch -Tdata oder __DATA_REGION_ORIGIN__
nicht beeinflusst. Evtl. ist also noch Symbol __stack zu definieren
damit der Startup-Code SP entsprechend setzt.
... SP scheint auch zu passen. Interner RAM ovn ATmega162 geht von 0x100
bis 0x4ff. Brauch XRAM extra Wait-States?
Jörg W. schrieb:> Trugschluss. Alle Konstanten im RAM brauchen eh gleich viel Flash noch> dazu – woher sollte denn sonst der RAM beim Start initialisiert werden?
Ok, das war mir nicht bekannt. Dann hätte ich das direkt machen sollen.
Yalu X. schrieb:> Das ist sicher sinnvoll, aber ich würde damit noch ein wenig warten, bis> der eigentliche Fehler gefunden ist. Sonst läufst du Gefahr, den Fehler> zu kaschieren, ohne ihn behoben zu haben. Irgendwann wird er dann erneut> zuschlagen.
Das ist auch meine Befürchtung. Insbesondere, da hier jetzt mehrfach
angedeutet wurde, dass die Initialisierung korrekt war.
Yalu X. schrieb:> Möglicherweise hast du den Fehler damit eingeschleppt.
Glaube ich nicht. Es gibt zwei Dateien, die exakt das identische machen.
Nur mit anderen Variablen, aber für die Anzeige dieselben Funktionen
aufrufen. Bis die zweite Datei dazugekommen ist, hat es "funktioniert".
Ich hatte gestern schon die Vermutung:
Yalu X. schrieb:> denn der Fehler könnte auch schon vorher existiert haben,> ist aber erst durch die Einführung des neuen Moduls (und der damit> verbundenen Verschiebung von Code- und Datenfragmenten) zu Tage> getreten.
Ich zeige hier mal die Funktionen, die auf die entsprechenden Strings
und Arrays zugreifen. Vielleicht sieht ja jemand den Fehler. Aber der
Compiler läuft ohne Fehlermeldung durch:
Kurz zur Erklärung: KEY_UP_CODE, KEY_DOWN_CODE sind von mir im RAM des
LCD-Display abgelegte Zeichen, wo ich Pfeil-hoch bzw. runter darstelle.
Hier ist im String ein okatler Wert vorhanden. Damit es nicht zu
verwirrend wird, habe ich die Strings gestern ein wenig abgeändert.
Tatsächlich sieht das dann so aus:
1
#define KEY_UP_CODE 0x7D
2
#define KEY_DOWN_CODE 0x7E
3
#define KEY_ENTER_CODE 0x7F
4
5
constt_displayeingabe_datum_display={
6
// 12345678901234567890
7
{" Eingabe Datum",
8
"Best\341tigen mit \177",
9
"Weiter mit \176",
10
"Zur\365ck mit \175"},
11
};
Mit "\341" stelle ich die Umlaute im String dar. Wenn ich direkt 'ä'
schreibe, stellt das Display irgendwelche merkwürdigen Zeichen dar.
Yalu X. schrieb:> Dann versuch es mit LED-Debugging (was tu ja teilweise bereits getan> hast) und/oder Printf-Debugging.
Nach dem Schalten der LEDS befindet sich ein printf("gestartet\n\r");.
Selbst das sehe ich schon nicht mehr. Zu diesem Zeitpunkt ist der
Controller aber noch nicht im memory-mapped geschaltet. Das LCD-Display
also noch gar nicht initialisiert.
Ich habe auch gerade versucht das so umzuschreiben, dass die Texte und
Strukturen im Flash angelegt werden. Aber da bin ich bisher an meiner
eigenen Unfähigkeit gescheitert. Liegt daran, dass ich das bisher noch
nie gemacht hatte und daher damit zu kämpfen habe. Allerdings habe ich
damit auch sowieso momentan Bauchschmerzen, da ich schon den Verdacht
hatte, dass der Fehler waonders liegt.
Johann L. schrieb:> Brauch XRAM extra Wait-States?
Es steht das: "MCUCR = (1<<SRE) | (1<<SRW10);" drin. Das wurde erst bei
der Initialsierung des LCDs gemacht. Das habe ich gerade mal direkt am
Anfang des Programms gesetzt. Leider ändert aber nichts.
Wie sieht's denn mit Debugging / Simulation aus?
Soweit ich sehe ist der Code ja recht simpel bzw. braucht keine Hardware
außer dem LCD (das man einfach simulieren kann). Zumindest bei AVRtest
könnte ich dich da unterstützen.
https://github.com/sprintersb/atest
Johann L. schrieb:> Wie sieht's denn mit Debugging / Simulation aus?> Soweit ich sehe ist der Code ja recht simpel bzw. braucht keine Hardware> außer dem LCD (das man einfach simulieren kann). Zumindest bei AVRtest> könnte ich dich da unterstützen.> https://github.com/sprintersb/atest
Danke. Ich sehe mir das später mal an und versuche das ans laufen zu
bringen. Muss jetzt erstmal weg.
Da tut sich gar nichts. Das Programm wird zwar aufgerufen, aber bleibt
hängen. Es wird auch nichts ausgegeben/angezeigt.
Sind meine Parameter falsch? Oder was muss ich eingeben, damit mir auch
irgendwas angezeigt wird. Damit ich mir etwas Tipperei erparen kann,
habe ich die elf Datei nur in "LCD.elf" umbenannt. Aber das wird es ja
wohl nicht sein.
Ich bin jetzt auch erstmal weg und komme erst am Abend dazu wieder
reinzusehen.
> Da tut sich gar nichts. Das Programm wird zwar aufgerufen, aber bleibt> hängen.
Ist ja auch klar. AVRtest simuliert keine I/O -- und selbst wenn, würde
kein LCD simuliert werden und die Kommunikation nicht funktionieren.
1) Zunächst wird ein exit-atmega162.o generiert. Dazu in den avrtest
Ordner gehen und
1
make exit-atmega162.o
ausführen.
Das exit-atmega162.o Modul definiert Kommunikation mit dem Simulator so
dass printf() etc. funktioniert wie auf einem normalen PC. Es wird bei
jedem Build mit hinzugelinkt.
2) Dann wird der Code so angepasst, dass er auch auf einem PC laufen
würde (keine Ports einlesen, keine ISRs, etc). Ports schreiben (wie zum
Beispiel Wait-States setzen oder LED blinken) können drin bleiben, haben
aber außer dem Setzen des SFRs keinen Effekt.
Der Code wird mit -DAVRTEST_H übersetzt. Oder mit -include
.../avrtest.h was AVRTEST_H ebenfalls setzt. avrtest.h wird nur
gebraucht wenn Syscalls direkt genutzt werden sollen.
2a) LCD-Initialisierung wird nicht gebraucht, also zum Beispiel
1
#ifndef AVRTEST_H
2
lcd_init();
3
#endif
oder
1
voidlcd_init(void)
2
{
3
#ifndef AVRTEST_H
4
// LCD init.
5
#endif
6
}
Die LCD-Routinen werden so erweitert:
1
voidlcd_gotoxy(intx,inty)
2
{
3
#ifdef AVRTEST_H
4
printf("lcd_gotoxy(%d,%d)\n",x,y);
5
#else
6
// normaler lcd_gotoxy code
7
#endif
8
}
9
10
voidlcd_puts(constchar*text)
11
{
12
#ifdef AVRTEST_H
13
printf("lcd_puts(\"%s\")",text);// falls text im RAM
14
printf("lcd_puts(\"%S\")",text);// falls text im PROGMEM / __flash
15
#else
16
// normaler lcd_puts code
17
#endif
18
}
AVRtest ist wie gesagt low-level; Debugging ist damit nicht möglich.
Man kann sich aber die simulierten Instruktionen anzeigen lassen, etwa:
1
avrtest_log -mmcu=avr5 lcd.elf -m 10000 -v
-m begrenzt die Anzahl simulierter Instruktionen. Hilfreich wenn sich
ein Programm aufhängt.
-v zeigt welche ELF Program Header geladen werden.
-d ist nicht notwendig. RAM wird durch den Startup-Code initialisiert.
Beispiel:
Danke für Deine Erklärungen.
Johann L. schrieb:> 1) Zunächst wird ein exit-atmega162.o generiert. Dazu in den avrtest> Ordner gehen und
make exit-atmega162.o
> ausführen.
Das hat schon mal funktioniert.
Mit dem Rest werde ich mich jetzt beschäftigen und mich wieder melden.
Nach einigen leichten Schwierigkeiten, die aber an mir lagen, ging das
Ganze erstaunlich gut. Danke Dir nochmal.
Ich bekomme immer den Exit Status: Timeout. Aufgerufen habe ich das mit:
1
avrtest_log-mmcu=avr5main.elf-m10000-v>inh
Die gesamte Datei, wo ich mir die Ausgabe umgelenkt habe, ist jetzt der
Einfachheit halber nicht beigefügt.
Meine erste Vermutung war, dass die Anzahl der "-m 10000" zu klein war.
Daraufhin habe ich das um Faktor 10 erhöht. Dasselbe Ergebnis. Nochmal
um Faktor 10 erhöht. Ebenfalls dasselbe Ergebnis.
In dem Programm warte ich auch auf Tasten. Aber daran liegt der Timeout
nicht. Ich bin das ganze Programm durchgegangen. Dort, wo auf Tasten
gewartet wird, habe ich es entweder auskommentiert oder einen festen
Wert zurückgegeben:
1
#ifdef AVRTEST_H
2
3
keys=(1<<BUTTON_PIN_DOWN);
4
printf("keys in wait_key: %d\n",keys);
5
returnkeys;
6
#else
7
......
8
#endif
Ich bekomme von den ganzen printf im Programm auch nichts angezeigt.
Folgendes habe ich z.B. abgeändert:
1
#ifndef AVRTEST_H
2
lcd_init(LCD_DISP_ON);
3
#else
4
printf("Statt lcd_init\n\r");
5
#endif
So, wie ich das verstanden habe, müsste ich das doch sehen. Daraufhin
habe ich in der main.c "#include "avrtest.h" eingefügt. Damit war aber
das Problem, dass in den anderen Dateien dann "AVRTEST_H" nicht mehr
bekannt war. Daher habe ich in jeder Datei das "#include "avrtest.h""
eingefügt. Es ist aber trotzdem nichts zu sehen.
Andreas schrieb:> Ich bekomme von den ganzen printf im Programm auch nichts angezeigt.
Kann mehrere Gründe haben:
- exit-atmega162.o wurde nicht zugelinkt.
- AVRTEST_H is nicht #define'd.
- Mit aktiviertem Logging werden Strings von printf nicht als Ganzes
ausgegeben, sondern von "Hallo" zuerst ein H, dann hundert Befehle
später ein a, dann hundert Befehle später ein l, ...
printf() Ausgaben sieht man besser mit -no-log, also Logging
deaktiviert.
> Meine erste Vermutung war, dass die Anzahl der "-m 10000" zu klein war.> Daraufhin habe ich das um Faktor 10 erhöht. Dasselbe Ergebnis. Nochmal> um Faktor 10 erhöht. Ebenfalls dasselbe Ergebnis.
Das Programm IST vermutlich in eine Endlosschleife wie alle Embedded
Anwendungen :-)
Rufe einfach mal exit(wert) auf oder return wert in main() an einer
Stelle wo die Ausführung aus jeden Fall hinkommen soll(te),
Falls das Programm in einer unerwarteten oo-Schleife ist, dann siehtst
du am Ende des Logs wo das Programm so rumwuselt.
Johann L. schrieb:> Das Programm IST vermutlich in eine Endlosschleife wie alle Embedded> Anwendungen :-)
Das stimmt natürlich.
Johann L. schrieb:> Rufe einfach mal exit(wert) auf oder return wert in main() an einer> Stelle wo die Ausführung aus jeden Fall hinkommen soll(te),
Mache ich morgen und gebe Bescheid.
Ich habe mich doch noch dran gesetzt und das getestet.
Um zu gucken, ob das Programm in einer Endlosschleife läuft, habe ich an
diversen stellen returns eingegeben. Das funktionierte auch soweit.
Direkt in der main wird eine Funktion (lcd_generatechar_for_buttons();)
aufgerufen, bei der Zeichen im Ram des LCD-Displays erzeugt werden. Wenn
ich die Funktion nicht aufrufe, sondern so wie unten auskommentiere
1
// Enter/Up und Down-Taste als Character im CG-RAM des LCDs speichern
Es wird bei "lcd_data(" schon abgebrochen.
Ich hatte mich schon gefreut und das Programm ohne die object-datei für
avrtest wieder auf den Controller geladen, dabei diese Funktion
auskommentiert. Es ist aber dasselbe Ergebnis. Die Software läuft nicht.
Ich kann in der Funktion aber auch keinen Fehler sehen. Hier mal, wie
die Funktion aussieht.
1
// Anzahl der Buttons
2
#define CTRL_CODES 3
3
4
#define KEY_UP 0
5
#define KEY_DOWN 1
6
#define KEY_ENTER 2
7
8
uint8_tctrl_key[][8]={
9
// Taste hoch
10
{0b00000100,// X
11
0b00001110,// XXX
12
0b00010101,// X X X
13
0b00100100,// X
14
0b00000100,// X
15
0b00000100,// X
16
0b00000100,
17
0b00000100},
18
// Taste runter
19
{0b00000100,
20
0b00000100,
21
0b00100100,// X
22
0b00000100,// X
23
0b00000100,// X
24
0b00010101,// X X X
25
0b00001110,// XXX
26
0b00000100},// X
27
// Enter-Taste, Möglichkeit 1
28
{0b00000001,// X
29
0b00000101,// X
30
0b00001001,// 1 X
31
0b00010001,// 1 X
32
0b00011111,// XXXX
33
0b00010000,// 1 X
34
0b00001000,// 1 X
35
0b00000100},// X
36
// Enter-Taste, Möglichkeit 2
37
{0b00000001,// X
38
0b00000001,// X
39
0b00000101,// 1 X
40
0b00001001,// 1 X
41
0b00011111,// XXXX
42
0b00001000,// 1 X
43
0b00000100,// 1 X
44
0b00000000},
45
};
46
47
voidlcd_generatechar_for_buttons()
48
{
49
// Startposition des 1. Zeichens (KEY_UP) einstellen
50
lcd_command(1<<LCD_CGRAM);// set CG RAM start address 0
51
for(inti=0;i<CTRL_CODES;i++)
52
for(uint8_tj=0;j<8;j++)
53
lcd_data(ctrl_key[i][j]);// Bitmuster übertragen
54
}
An lcd_data und lcd_command kann es nicht liegen, die sehen momentan so
aus:
1
voidlcd_command(uint8_tcmd)
2
{
3
#ifdef AVRTEST_H
4
5
printf("lcd_command(%d)\n",cmd);
6
7
#else
8
9
lcd_waitbusy();
10
lcd_write(cmd,0);
11
#endif
12
}
13
14
voidlcd_data(uint8_tdata)
15
{
16
#ifdef AVRTEST_H
17
18
printf("lcd_data(%d)\n",data);
19
20
#else
21
22
lcd_waitbusy();
23
lcd_write(data,1);
24
#endif
25
}
Um auszuschliessen, dass es am Atmega liegt, habe ich einen anderen
Atmega162 genommen. Dasselbe Ergebnis.
Andreas schrieb:> lcd_data(36)> lcd_data(> exit status: TIMEOUT> reason: instruction count limit reached> program: LCD_Display_Gaerbox.elf> exit address: 000472> total cycles: 15968> total instr.: 10000[/c]> Es wird bei "lcd_data(" schon abgebrochen.
Offenbar weil -m 10000 Instruktionen erreicht wurden. Also -m erhöhen
oder weglassen (-m 0 simuliert ohne Obergrenze an Instruktionen).
printf ist ja sehr aufwändig. Alternativ kann man die LOG Funktionen
aus avrtest.h verwenden, die fast ohne Overhead Daten an den PC ausgeben
können.
Und oben habe ich AVRtest erwähnt weil ich dich da unterstützen kann bei
der Verwendung; das war aber nicht als Empfehlung zu verstehen AVRtest
zu verwenden. Wie du siehst ist das recht mühsam.
Keiner kennt dein Programm so gut wie du, und bevor du zu AVRtest
greifst würde ich erst mal Standard-Techniken verwenden:
Du hast vermutlich schon eine recht konkrete Vorstellung wo es hakt im
Programm, also
- Nochmals den Code checken. Insbesondere Warnings aktivieren und
beheben.
- Checken dasss der erzeugte Code das tut was die C-Quelle ausdrückt
(bzw. was du ausdrücken willst). Etwa: -save-temps -fverbose-asm -g0
und dann s-Files checken.
- Code debuggen. Debugger gibt's ja einige, etwa Simulavr mit
gdbserver Interface oder in XYZ Studio.
AVRtest ist da eher last resort und eigentlich dafür konzipiert,
(Regression)Tests möglichts schnall abzuarbeiten. Mit AVRtest zu
"debuggen" ist mühsam, und ohne Vorstellung wo es ungefähr hakt ist's
noch 10x mühsamer...
Mir ist heute eine andere Idee gekommen. Ich bin mir allerdings nicht
sicher, ob mein Gedankengang richtig ist.
Als ich beim Linker noch nicht angegeben hatte, dass der RAM extern ist,
hat mir avr-size irgendwas an RAM von 110% angezeigt bezogen auf den
Atmega162.
Sämtliche Texte habe ich noch im RAM. Ich gehe mal davon aus, dass die
in den externen RAM geladen werden, bevor mein Programm überhaupt
anfängt zu laufen. Wenn der Compiler die beim Programmstart in den
externen RAM laden will, ist der in meinem Programm noch gar nicht
initialisiert. Soll heißen der Controller kann den externen RAM noch gar
nicht ansprechen.
Kann das was mit dem Problem zu tun haben?
Andreas schrieb:> Ich gehe mal davon aus, dass die> in den externen RAM geladen werden, bevor mein Programm überhaupt> anfängt zu laufen.
Ja, das macht der Startupcode, der zu Deinem Programm dazugelinkt wird.
Der wird vor Aufruf der Funktion main() ausgeführt.
Andreas schrieb:> Sämtliche Texte habe ich noch im RAM. Ich gehe mal davon aus, dass die> in den externen RAM geladen werden, bevor mein Programm überhaupt> anfängt zu laufen. Wenn der Compiler die beim Programmstart in den> externen RAM laden will, ist der in meinem Programm noch gar nicht> initialisiert. Soll heißen der Controller kann den externen RAM noch gar> nicht ansprechen.>> Kann das was mit dem Problem zu tun haben?
Falls sowas wie Wait-States o.ä. gesetzt werden müssen, dann ja.
libgcc kopiert .data LMA nach .data VMA in .init4; XRAM muss also davor
konfiguriert / ansprechbar gemacht weden:
Harald K. schrieb:> Ja, das macht der Startupcode, der zu Deinem Programm dazugelinkt wird.> Der wird vor Aufruf der Funktion main() ausgeführt.Johann L. schrieb:> Falls sowas wie Wait-States o.ä. gesetzt werden müssen, dann ja.
Eure beiden Hinweise haben mir zur Lösung des Problems weitergeholfen.
Ich habe jetzt folgendes in die Software eingefügt.
Jetzt läuft es. Die Zeile mit EMCUCR ist nicht erforderlich. Das ganze
ist auch reproduzierbar. Kommentiere ich das wieder aus, dann tut sich
gar nichts mehr.
Ganz lieben Dank nochmal an alle, die sich hier Gedanken und mir
Vorschläge gemacht haben.
Andreas schrieb:> Johann L. schrieb:>> Falls sowas wie Wait-States o.ä. gesetzt werden müssen, dann ja.>> Eure beiden Hinweise haben mir zur Lösung des Problems weitergeholfen.
Womit sich folgendes bewahrheitet:
> Andreas schrieb:>> Ich tendiere im Moment dazu den ganzen Kram in den Flash zu verlagern,>> in der Hoffnung, dass ich dann die Probleme nicht habe.>> Das ist sicher sinnvoll, aber ich würde damit noch ein wenig warten, bis> der eigentliche Fehler gefunden ist. Sonst läufst du Gefahr, den Fehler> zu kaschieren, ohne ihn behoben zu haben.
Mit Strings im Flash wäre das Problem erstmal weg, aber sobald andere
Variablen im Static Storage wären käme das Problem wieder zurück ... an
anderer Stelle und mit anderen Artefakten.
Johann L. schrieb:> Mit Strings im Flash wäre das Problem erstmal weg, aber sobald andere> Variablen im Static Storage wären käme das Problem wieder zurück ... an> anderer Stelle und mit anderen Artefakten.
Vollkommen richtig.
Nachdem ich das untenstehende gelesen habe,
Jörg W. schrieb:> Trugschluss. Alle Konstanten im RAM brauchen eh gleich viel Flash noch> dazu – woher sollte denn sonst der RAM beim Start initialisiert werden?
habe ich mich auch geärgert. Allerdings nicht über den Hinweis von Jörg
sondern über meine Blödheit. Wenn ich vorher mal mein Gehirn
eingeschaltet hätte, hätte mir eigentlich selber klar sein müssen, dass
mein Gedankengang, das in den RAM zu legen um Flash zu sparen,
vollkommen Banane war, da die sowieso erstmal im Flash vorhanden sein
müssen.
Ich lasse die Initialisierung über xram auch sicherheitshalber drin.
Ich werde das in den nächsten Tagen ändern und das nicht mehr in den RAM
legen. Hier werde ich mich dann nochmal melden, da ich das bisher noch
nie gemacht hatte.
Um die Strings in den Ram zu legen, habe ich mich weitestgehend hier an
das Tutorial gehalten. Aber so richtig funktioniert es nicht. Ich hätte
mal die Bitte, dass mir jemand einen Tipp gibt, was ich falsch mache
bzw. wie ich es anders machen soll.
Wie ich es befürchtet habe, breche ich mir daran einen ab, die Strings
sauber in den flash zu legen und auch korrekt anzuzeigen. Ich sitze seit
gestern daran und habe es heute morgen erst geschafft, das ins Ram zu
legen. Aber ich komme nicht weiter.
Hier mal, wie es bisher war, als das ganze noch im Ram war.
Ich konnte in der Funktion show_all_display_lines mit
lcdtextzeile.text_zeile[i] die Strings ansprechen und auch anzeigen.
So ist der neue Stand im Moment:
Die Texte werden auch sauber auf dem LCD-Display angezeigt. Da die Texte
in einem Array sind, muss ich die über den Pointer "lcd++" hochzählen.
Hier bin ich mir nicht sicher, ob das jetzt nur zufällig funktioniert
und ich Glück habe. Oder ich mir möglicherweise irgendwann ein Problem
hole, weil ich hier voraussetze, dass die Zeiger auf die Strings alle
hintereinander liegen.
Am liebsten wäre mir, wenn ich die weiterhin als array über den index
ansprechen könnte. Sofern ich mir mit dem Hochzählen des Pointers keine
neuen Probleme einhole, könnte ich damit auch leben.
Das kann nich sein. lcd ist entweder ein Typ oder ein Objekt.
PGM_P ist veraltet, wirf das raus.
pgm_read_word braucht es mit __flash auch nicht mehr. Einfach:
Johann L. schrieb:> Das kann nich sein. lcd ist entweder ein Typ oder ein Objekt.
Ich verstehe nicht was Du meinst. Habe jetzt die ganze Zeit rumprobiert.
Die einzige Lösung, die funktioniert ist die:
Andreas schrieb:> Johann L. schrieb:>> Das kann nich sein. lcd ist entweder ein Typ oder ein Objekt.> Ich verstehe nicht was Du meinst.
Vergiss es war'n Glitch in meinem Brain v0.9beta.
> Aber ich muss (PGM_P) und pgm_read_word nehmen, ansonsten bekomme ich> entweder Fehler beim compilieren
Dann fehlen irgendwo __flash Qualifier. -Waddr-space-convert sollte
sagen,. wo.
Eigentlich sollte doch
Ich komme nicht weiter, glaube aber auch, dass ich mittlerweile den Wald
vor lauter Bäumen nicht mehr sehe.
Es wird mir zwar die Fehlermeldung angezeigt, aber das sagt mir nichts.
Jörg W. schrieb:> Kannst du mal ein minimales compilierbares Beispiel> zusammenkopieren?
Hatte etwas gedauert. Meine eigentliche Planung war heute das Ganze neu
aufzusetzen. Daher hatte ich meine ganzen Versuche gelöscht und auch
nicht gesichert.
Damit das einfacher ist, habe ich nur eine Datei daraus gemacht und
sämtliche überflüssigen Dateien weggelassen. Die Funktionen für das LCD
habe ich abgeändert und nur einzelne Funktionen eingefügt. Das Programm
läuft natürlich nicht auf der realen Hardware. Aber ich habe über
mehrere Arten versucht was vernünftiges hinzubekommen. Bei sämtlichen
Funktionen habe ich als Kommentar die Auswirkungen in der tatsächlichen
Hardware hingeschrieben und welche Warnungen ich beim Compilieren
bekomme.
Am Anfang der Datei ist auch als Kommentar eingefügt, mit welchen
Parametern Compiler und Linker aufgerufen werden.
Gefährlich, das so als Makro zu schreiben. Das verhält sich hinter einem
"if" nicht mehr sinnvoll wie eine Anweisung. Der übliche Trick, das zu
umgehen, ist der auf den ersten Blick etwas schräg anmutende Konstrukt
1
…do{…}while(0)
Aber: das würde ich an deiner Stelle eh nicht als Makro schreiben,
sondern als inline-Funktion:
1
staticvoidinlinelcd_write(chard,boolrs){
2
if(rs)
3
*(volatileuint8_t*)(LCD_IO_DATA)=d;
4
else
5
*(volatileuint8_t*)(LCD_IO_FUNCTION)=d;
6
}
(Das "inline" wird es nicht brauchen, das erkennt der Compiler allein,
solange es "static" ist.)
Selbst als Makro sollte man das durch Zeilenumbrüche übersichtlicher
schreiben:
1
#define lcd_write(d,rs) \
2
do { \
3
if (rs) \
4
*(volatile uint8_t*)(LCD_IO_DATA) = d; \
5
else \
6
*(volatile uint8_t*)(LCD_IO_FUNCTION) = d; \
7
} while (0)
1
#define FSTR(X) ((const __flash char[]) { X } )
2
3
constt_menumain_menu[]__attribute__((progmem))={
Wenn du __flash benutzt, dann doch überall, und nicht plötzlich wieder
mit Attributen mischen.
1
voidlcd_puts_p(constchar*progmem_s)
2
// print string from program memory on lcd (no auto linefeed)
3
{
4
registercharc;
5
6
while((c=pgm_read_byte(progmem_s++))){
7
lcd_putc(c);
8
}
9
}
Davon abgesehen, dass "register" ein unsinniges Schlüsselwort von vor 50
Jahren ist, bleib doch auch hier bei __flash:
1
voidlcd_puts_p(const__flashchar*s)
2
{
3
charc;
4
5
while((c=*s++)!='\0')
6
lcd_putc(c);
7
}
… dann erübrigen sich auch solche Typecasts wie:
1
lcd_puts_p((PGM_P)lcd++);
OK, das sind jetzt nur meine Anmerkungen vom ersten Draufgucken.
Bestimmt hat Johann noch mehr …
Danke erstmal. Das mit dem write habe ich jetzt nur aus der lib von
Peter Fleury zur LCD-Ansteuerung kopiert, damit ich einigermaßen zeigen
konnte, wie der Code ist.
ist mir auch nicht klar.
Erstens: warum willst du da jeweils zwanzig Zeiger drin haben? Belegen
tust du davon dann vier. Müsste also LCD_LINES heißen. Zweitens, wofür
braucht das noch eine eigene struct?
Probier mal, das alles auf __flash umzubauen und schau, ob das Compilat
dann Sinn hat. Gern auch mal die erzeugte Assemblerdatei ansehen.
Jörg W. schrieb:> Das hier:#define LCD_LINES 4> #define LCD_DISP_LENGTH 20> …> typedef struct display> {> const char __flash *text_zeile[LCD_DISP_LENGTH];> } t_display;> ist mir auch nicht klar.
Stimmt. Jetzt wo Du es schreibst, fällt es mir auch auf. Gedacht war
immer LCD_Lines. Warum ich da LCD_DISP_LENGTH reingeschrieben habe,
weiss ich auch nicht. Bei der gesamten Programmierung habe ich in den
Schleifen auch immer LCD_LINES genommen.
Jörg W. schrieb:> Zweitens, wofür> braucht das noch eine eigene struct?
Meine ersten Versuche waren, dass ich da jeweils Zeile1, Zeile2 usw.
drin stehen hatten. Nachdem das funktioniert hatte, wollte ich das etwas
verbessern und habe daraus ein Array gemacht und dabei ist es geblieben.
Wenn ich mir da jetzt Gedanken drüber mache, ist das natürlich Quatsch
und ich kann das Array direkt hierein:
1
typedefstructmenu{
2
constt_displaytext;
3
constint8_tprevious;
4
constint8_tnext;
5
int8_t(*fp)(void);
6
}t_menu;
packen.
Heute oder morgen werde ich das ändern und auch mit LCD-Display testen
und wieder berichten.
Danke Dir erstmal für die Hinweise.
Johann L. schrieb:> Habs mal etwas aufgeräumt.
Wahnsinn. Läuft alles einwandfrei. Das hätte ich nie und nimmer
hinbekommen. Ganz lieben Dank und schöne Ostern.
Jörg W. schrieb:> Gefährlich, das so als Makro zu schreiben. Das verhält sich hinter einem> "if" nicht mehr sinnvoll wie eine Anweisung. Der übliche Trick, das zu> umgehen, ist der auf den ersten Blick etwas schräg anmutende Konstrukt
Hierzu habe ich noch zwei Fragen.
In der Library von Peter Fleury stand auch
1. Ist diese Änderung aus dem gleichen Grund angebracht, wie Jörg
geschrieben hat?
2. Kann ich mir dadurch irgendwas reinholen? Das Display funktiniert
damit aber.
Andreas schrieb:> Ist diese Änderung aus dem gleichen Grund angebracht, wie Jörg> geschrieben hat?
Ja.
> Kann ich mir dadurch irgendwas reinholen? Das Display funktiniert damit> aber.
Mehr Klarheit im Code. :-)
Denk dran, sowas wie die Peter-Fleury-Bibliotheken stammt noch aus dem
vorigen Jahrtausend. Da war gerade die Unterstützung eher kleiner
Architekturen im GCC noch lange nicht so ausgefeilt wie heute. Da konnte
man mit einem Makro schon mal etwas „Mikro-Optimierung“ machen –
heutzutage ist das aufgrund des schlechter wartbaren Sourcecodes eher
hinderlich.
Andreas schrieb:> static inline uint8_t lcd_read (bool rs)> {> uint8_t d;> if (rs)> d = *(volatile uint8_t*)(LCD_IO_DATA);> else> d = *(volatile uint8_t*)(LCD_IO_FUNCTION);> return d;> }
Das ist grundsätzlich korrekt, aber mir gefällt dieser Stil nicht so
gut: Das if-else macht nichts anderes als Daten raus zu liefern. Die
werden aber erst umständlich auf die "d" Variable zugewiesen, also als
Seiteneffekt. Außerdem ist die Variable am Anfang uninitialisiert, d.h.
es steht irgendwas zufälliges drin. Klar könnte man erstmal mit "=0;"
initialisieren, aber das macht's nicht besser weil das kein sinnvoller
Wert ist.
Man könnte versehentlich die Variable auslesen bevor dann später ein
Wert zugewiesen wird. Bei so einem einfachen Beispiel ist das zwar nicht
so wahrscheinlich, aber man sollte sich angewöhnen so etwas
grundsätzlich "sauber" umzusetzen (also: jede lokale Variable hat immer
einen sinnvollen Wert), damit man das bei komplexeren Situationen
reflexhaft richtig macht.
In anderen Sprachen hat if-else einen Rückgabewert, und das letzte
Statement einer Funktion ist automatisch der Rückgabewert der Funktion,
dort kann man das prinzipiell so in der Art umsetzen:
1
uint8_tlcd_read(boolrs)
2
{
3
if(rs)
4
*(volatileuint8_t*)(LCD_IO_DATA);
5
else
6
*(volatileuint8_t*)(LCD_IO_FUNCTION);
7
}
Dort gibt es keine uninitialisierten Variablen, und der Datenfluss ist
direkt ersichtlich. Da C aber eine byte-Verarbeitungssprache aus den
1970ern ist, geht das hier leider nicht. Man kann sich mit dem ternären
?: Operator behelfen, es also letztendlich genau wie im Makro machen:
1
staticinlineuint8_tlcd_read(boolrs)
2
{
3
returnrs?
4
*(volatileuint8_t*)LCD_IO_DATA
5
:
6
*(volatileuint8_t*)LCD_IO_FUNCTION;
7
}
Das sieht zwar nicht ganz so schön aus, aber dafür vermeidet man
ebenfalls die uninitialisierten Variablen und braucht keine
Seiteneffekte, sondern hat den direkten Datenfluss.
Jörg W. schrieb:> Mehr Klarheit im Code. :-)
Ok, Hauptsache keine Nachteile. :-)
Niklas G. schrieb:> In anderen Sprachen hat if-else einen Rückgabewert, und das letzte> Statement einer Funktion ist automatisch der Rückgabewert der Funktion,> dort kann man das prinzipiell so in der Art umsetzen:> uint8_t lcd_read (bool rs)> {> if (rs)> *(volatile uint8_t*)(LCD_IO_DATA);> else> *(volatile uint8_t*)(LCD_IO_FUNCTION);> }
Das so zu machen, war meine erste Idee. Eine innere Eingebung hat mir
aber gesagt, dass mit meiner Version zu machen und daher hatte ich das
gar nicht ausprobiert. Habe Deine Version mal gerade getestet. Da kommt:
Niklas G. schrieb:> Außerdem ist die Variable am Anfang uninitialisiert, d.h.> es steht irgendwas zufälliges drin.
Praktisch existiert sie in diesem Moment noch gar nicht.
> Klar könnte man erstmal mit "=0;"> initialisieren, aber das macht's nicht besser weil das kein sinnvoller> Wert ist.
Richtig, daher bin ich auch kein großer Freund dieser "schreib überall
einen Initializer hin"-Praxis (wie sie bspw. MISRA fordert). Sie
verhindert nämlich zielsicher, dass einen der Compiler warnt, wenn man
irgendwo dann vergessen hat, einen sinnvollen Wert zuzuweisen – und
diese Warnungen beherrschen Compiler mittlerweile sehr sicher, so man
sie warnen lässt (und nicht etwa die Codeflussanalyse durch -O0
unterdrückt). Die Warnung hat mir schon längeres Debugging erspart.
> Da C aber eine byte-Verarbeitungssprache aus den> 1970ern ist, geht das hier leider nicht. Man kann sich mit dem ternären> ?: Operator behelfen, es also letztendlich genau wie im Makro machen:
Nö.
Natürlich kannst du in der inline-Funktion mehrere return haben. Ist
übersichtlicher als der ternäre Operator.
Jörg W. schrieb:> Ist übersichtlicher als der ternäre Operator.
Finde ich überhaupt nicht. Und die vielen Leute die funktionale
Programmiersprachen nutzen auch nicht.
Jörg W. schrieb:> Praktisch existiert sie in diesem Moment noch gar nicht.
Spielt für die Überlegungen keine Rolle.
Andreas schrieb:> Habe Deine Version mal gerade getestet. Da kommt:
Ja so geht es in C ja nicht, wie gesagt..
Niklas G. schrieb:>> Ist übersichtlicher als der ternäre Operator.>> Finde ich überhaupt nicht.
Gut, Geschmackssache dann. Darüber kann man sich streiten. ;-)
Niklas G. schrieb:> Ja so geht es in C ja nicht, wie gesagt..
GCC könnte aus einem brace block einen Rückkehrwert bilden, aber das ist
a) compilerspezifisch und b) werden sich andere C-Programmierer fragen,
was das soll, wenn sie den Code später lesen müssen. Da ist die
inline-Funktion der einfachere Weg, ob nun mit ternärem Operator oder
mit zwei return.
Da es noch ein weiteres array gibt, was auch nie verändert wird, wollte
ich dies auch noch in den flash legen und nicht in den Ram.
Bisher sah die Definition so aus:
1
uint8_tctrl_key[][8]={
Heute habe ich dann versucht in den Flash zu legen mit:
1
staticconst__flashuint8_tctrl_key[][8]={
2
// Taste hoch
3
{0b00000100,// X
4
0b00001110,// XXX
5
0b00010101,// X X X
6
0b00100100,// X
7
0b00000100,// X
8
0b00000100,// X
9
0b00000100,
10
0b00000100},
11
// Taste runter
12
{0b00000100,
13
0b00000100,
14
0b00100100,// X
15
0b00000100,// X
16
0b00000100,// X
17
0b00010101,// X X X
18
0b00001110,// XXX
19
0b00000100},// X
20
// Enter-Taste, Möglichkeit 1
21
{0b00000001,// X
22
0b00000101,// X
23
0b00001001,// 1 X
24
0b00010001,// 1 X
25
0b00011111,// XXXX
26
0b00010000,// 1 X
27
0b00001000,// 1 X
28
0b00000100},// X
29
// Enter-Taste, Möglichkeit 2
30
{0b00000001,// X
31
0b00000001,// X
32
0b00000101,// 1 X
33
0b00001001,// 1 X
34
0b00011111,// XXXX
35
0b00001000,// 1 X
36
0b00000100,// 1 X
37
0b00000000},
38
};
Aufgerufen wird das dann folgendermassen:
1
voidlcd_data(uint8_tdata);
2
3
voidlcd_generate_char_for_buttons()
4
{
5
// Startposition des 1. Zeichens (KEY_UP) einstellen
Es ist auch vollkommen egal, ob ich das auf eine der untenstehenden
Möglichkeiten mache:
1
staticconst__flashuint8_tctrl_key[][8]={
2
3
staticconstuint8_t__flashctrl_key[][8]={
Das Ganze funktioniert auch einwandfrei. Eigentlich hatte ich erwartet,
dass der Compiler mindestens eine Warnung raus gibt. Aber nichts
dergleichen. Meine Frage ist jetzt, liegt das tatsächlich im Flash oder
ist das immer noch im Ram?
Dann habe ich noch zwei weitere Fragen.
1. Wo finde ich eine vernünftige Erklärung, wo der Unterschied zwischen
__flash und PROGMEM ist? PROGMEM habe ich bisher auch noch nie genutzt,
aber interessieren würde mich das schon.
2. Wo finde ich eine gute Erklärung, wie genau die Syntax für __flash
bei der Definition ist? Ich fand/finde die Unterstützung hier zwar
superklasse, aber ich würde das gerne mal verstehen, damit ich beim
nächsten Mal (hoffentlich) keinen neuen Thread aufmachen muss.
Andreas schrieb:> Das Ganze funktioniert auch einwandfrei. Eigentlich hatte ich erwartet,> dass der Compiler mindestens eine Warnung raus gibt. Aber nichts> dergleichen.
Falls du -Waddr-space-convert erwartest: Das ist aus per default und
nicht in -Wall, muss also händisch angeschaltet werden.
> Meine Frage ist jetzt, liegt das tatsächlich im Flash oder> ist das immer noch im Ram?
Blick ins Map-File. Wie man das erzeugen lässt siehst du bei den
Befehlen, die ich oben mit angegeben hatte.
> Dann habe ich noch zwei weitere Fragen.> 1. Wo finde ich eine vernünftige Erklärung, wo der Unterschied zwischen> __flash und PROGMEM ist?
Flash ist ein Qualifier gemäß ISO/IEC TR 18037 "Embedded C".
"progmem" ist nur ein Attribut, ist also nicht Teil einer Adresse. Es
wirkt i.W wie Attribut "section" mit paar extra Checks. Zum Lesen wird
Inline Asm gebraucht (pgm_read_xxx) während __flash mit normalem C-Code
(bzw. GNU-C) zugegriffen werden kann.
> 2. Wo finde ich eine gute Erklärung, wie genau die Syntax für __flash> bei der Definition ist?
Wie gesagt syntaktisch analog zu const bzw. volatile, allerdings nur
erlaubt für const (bei Zeigern / Adressen) bzw. const + Static Storage
(bei Objekt-Definition und -Deklaration).
> Ich fand/finde die Unterstützung hier zwar> superklasse, aber ich würde das gerne mal verstehen, damit ich beim> nächsten Mal (hoffentlich) keinen neuen Thread aufmachen muss.
Einfach mal schauen, was avr-gcc fürn Code erzeugt:
Danke für Deine Erklärung. Map-File lasse ich sowieso immer erstellen.
Habe gerade mal reingesehen. Dort steht:
1
.progmem.data.ctrl_key
2
0x000001a60x20./display_functions.o
Ich verstehe das so, dass das im Flash ist. Von der Länge würde es
passen.
Johann L. schrieb:> Falls du -Waddr-space-convert erwartest:
Das habe ich eingestellt, als Du mir das hier geschrieben hattest.
Trotzdem gibt es keine warnings.
Johann L. schrieb:> Einfach mal schauen, was avr-gcc fürn Code erzeugt:> int fun (const __flash int const const __flash *p)> {> return ***p;> }
Hab ich. Aber so richtig hilft mir das jetzt nicht weiter.
Andreas schrieb:> Aber so richtig hilft mir das jetzt nicht weiter.
Dreifache Dereferenzierung halt, wobei die Adresse jedesmal als
Flash-Adresse bewertet wird, der dann mit LPM gelesen wird.
Jörg W. schrieb:> Dreifache Dereferenzierung halt, wobei die Adresse jedesmal als> Flash-Adresse bewertet wird, der dann mit LPM gelesen wird.
Nur die 1te und 3te Dereferenzierung. Die mttlere liest per LD vom RAM
weil da kein __flash steht. 3x flash wäre:
Jörg W. schrieb:> der dann mit LPM gelesen wird
Daran, dass dort LPM steht, hatte ich mir schon gedacht, dass auf den
Flash zugegriffen wird.
Johann L. schrieb:> 3x flash wäre:> int fun (const __flash int const __flash const __flash *p) ...
Das heisst, vor jedem, was ich im Flash haben will, schreibe ich einfach
"__flash"?
Andreas schrieb:> Das heisst, vor jedem, was ich im Flash haben will, schreibe ich> einfach "__flash"?
Prinzipiell ja, kann aber etwas komplizierter werden wie zum Beispiel
1
const__flashchar*p="Hallo";
Hier ist p ein Flash-Zeiger, aber "Hallo" steht gemäß TR18037 im Generic
Address-Space. Man braucht also Verrekungen wie FLIT von oben:
1
const__flashchar*p=FLIT("Hallo");
Und dann geht es nicht nur um Lokatierung, sondern auch um Zeiger wie
z.B. in Funcktionsargumenten. Da muss __flash (so wie const und
volatile auch) an der richtigen Seite vom "*" stehen:
1
// Zeiger auf RAM-Position, die Zeiger auf __flash enthält.
2
const__flashchar*[const]*p;
3
4
// Zeiger auf Flash-Position, die Zeiger auf RAM enthält.
5
[const]char*const__flash*p;
6
7
// Zeiger auf Flash-Position, die Zeiger auf Flash enthält.
Johann L. schrieb:> Jörg W. schrieb:>> Dreifache Dereferenzierung halt, wobei die Adresse jedesmal als>> Flash-Adresse bewertet wird, der dann mit LPM gelesen wird.>> Nur die 1te und 3te Dereferenzierung. Die mttlere liest per LD vom RAM> weil da kein __flash steht.
Stimmt.
Wobei es sicher nur selten sinnvoll ist, Zeiger auf den RAM im Flash zu
haben, andersrum dagegen schon eher.