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:
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.
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?
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.
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.
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.
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.
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.
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.
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.
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.
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.
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:
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
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.
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).
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.
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.
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.
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.
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.
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.
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?
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.
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".
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.
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.
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.
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.
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.
Ich habe das gerade mal von der console aufgerufen mit
1
avrtest-s0x4000-d-mmcu=avr5lcd.elf
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.
Ich habe das gerade mal von der console aufgerufen mit
1
>avrtest-s0x4000-d-mmcu=avr5lcd.elf
2
>
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.
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.
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.
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.
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.
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
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.
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.
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:
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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.
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.
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:
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.
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.
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.
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.
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.
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.
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.
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:
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.