ich bastel derzeit an folgendem Projekt: die Nachbildung eines
historischen, programmierbaren Tischrechners. Das Original war auf dem
Intel 8008 basiert, ich verfüge über einen Speicherabzug der Firmware,
ohne diesen bis ins letzte Detail zu verstehen. Was ich verstehe ist
lediglich wie die I/O funktioniert.
Habe basierend auf dem Atmega 1284P in C eine i8008 Emulation
entwickelt, funktioniert auch soweit. Die i8008 Maschinenbefehle sind
jeweils C Funktionen, die Ausführung des emulierten Prozessors erfolgt
über ein Pointer-Array. Hier sind ein paar Code-Schnipsel um das Prinzip
zu zeigen:
*Befehlssatz laden:*
1
voidAssignInstructionSet()
2
{
3
iSet[0]=&HLT;
4
iSet[1]=&HLT;
5
iSet[2]=&RLC;
6
iSet[3]=&RNC;
7
iSet[4]=&ADI;
8
iSet[5]=&RST0;
9
iSet[6]=&MVI_A;
10
iSet[7]=&RET;
11
u.s.w.
12
}
*Hauptschleife:*
1
// i8008 processor loop:
2
while(1){
3
nxt=memory[PC];// next 8008 machine code
4
Mem=memory[Lreg|Hreg<<8];
5
if(Cflag)Cflag=0b00000001;// make sure only Bit 0 of Carry flag used
6
(*iSet[nxt])();// execute next 8008 instruction
7
if(moni)monitor();// invoke monitor if SER key was pressed
8
}// end.while
Jetzt habe ich an die Kiste eine RS232 Schnittstelle drangestrickt,
Reaktion auf gedrückte Taste per ISR. Die beinhaltet vorläufig nur ein
minimalen Funktionstest:
Mein Problem:
Wenn ich, entsprechend oben geposteter Hauptschleife, per Tastendruck
auf dem Atmel-Gerät nach monitor() verzweige, funktioniert die
Kommunikation mit dem seriellen Terminal sauber.
Aber wenn ich etwas auf dem seriellen Terminal tippe während die i8008
Emulation normal läuft, kackt die Emulation ab.
Vor lauter Verzweiflung hab ich schon alle irgendwie verdächtigen
Variablen als volatile benannt, hat aber nichts geholfen.
Hat eventuell jemand eine Idee, was hier verkehrt laufen könnte?
Nachtrag: ich lese grade meinen Beitrag noch mal. Als ich mit diesem
recht komplexen Projekt anfing hatte ich mich zum Thema C Präprozessor
überhaupt noch nicht belesen. Eventuell sind die gross geschriebenen
8008 Mnemonics ein Problem?
> iSet[0] = &HLT;>Eventuell sind die gross geschriebenen 8008 Mnemonics ein Problem?
Kann sein, kann nicht sein. Man sieht ja - da nur Codeschnippsel - die
Definition von z.B. HLT nicht. Wenn es ein Problem gibt, würde IMHO am
wahrscheinlichsten eine Meldung beim Kompilieren kommen.
> Aber wenn ich etwas auf dem seriellen Terminal tippe während die i8008> Emulation normal läuft, kackt die Emulation ab.
Bei so etwas ist mein erster Verdacht ein Bufferoverflow z.B. wegen
nicht erkanntem Zeilenende. Ich würde eine entsprechende Debugausgabe
einbauen, die anzeigt, in welchem Status mein Programm sich befindet. In
deinem Fall spielt das keine Rolle, weil du als Verarbeitung der
erhaltenen Zeichen nur ein Echo hast, aber keine Speicherung in Feldern.
> ISR(UART0_RECEIVE_INTERRUPT)
Den Vektornamen UART0_RECEIVE_INTERRUPT finde ich in meiner WinAVR
Installtion (20100110) nicht, d.h. die programmierte ISR passt nicht zum
freigegebenen IRQ. Meine Toolchain würde aber auch eine Warnung
ausgeben, wenn sie einen Vektornamen nicht kennt. Würde ich trotzdem das
Programm erzeugen, würde das beim ersten Aufruf der dann fehlenden ISR
abstürzen.
hab inzwischen viel ausprobiert aber bekomme das Problem nicht in den
Griff.
monitor() ist lediglich eine simple while Schleife die vorläufig
nichts macht und nur auf eine bestimmte Taste wartet. Wenn dort der
USART0_RX_vect Interrupt auftritt, läuft alles wie gewollt.
In der eigentlichen Hauptschleife lasse ich Funktionen vektoriell
ausführen mit
1
(*iSet[nxt])();// execute next 8008 instruction
Sobald der USART0_RX_vect Interrupt ausgelöst wird, verhaspelt sich die
gezeigerte Funktionsausführung, und rennt in die Funktion mit dem Index
0. Hab das inzwischen mittels blinkender LEDs ermittelt.
Hatte allerdings vor dem Atmel noch nie Kontakt mit einer
Harvard-Architektur. Ist die oben skizzierte vektorielle Ausführung
sauber oder muss man das irgendwie anders (PROGMEM?) machen?
nach tagelanger Rumprobiererei hab ich das Problem jetzt offenbar
gelöst:
die beiden für den USART0 genutzten PINS PD0 und PD1 waren per DDRD auf
Input gesetzt. Habe jetzt irgendwann mal im Rahmen diverser
Verzweiflungs-Versuche diese beiden Pins auf Output geschaltet. Seitdem
funktioniert alles stabil und genau so wie es sein soll.
Und ich versteh die Welt nicht mehr. Erstens hab ich nirgendwo in den
Beispielen oder im Datenblatt gelesen, dass die Einstellung im
Richtungs-Registers irgendwie mit der seriellen Schnittstelle
kollidieren könnte. Hatte das im Datenblatt so verstanden, dass die ser.
Schnittstelle die Pins okkupiert, egal was die sonst so machen. Ich hab
übrigens konkret an den beiden Pins nichts anderes dranhängen als den
MAX3232 Wandler.
Noch merkwürdiger: die komischen Effekte sind nur in bestimmten Teilen
des Codes aufgetreten.
Micha schrieb:> die beiden für den USART0 genutzten PINS PD0 und PD1 waren per DDRD auf> Input gesetzt. Habe jetzt irgendwann mal im Rahmen diverser> Verzweiflungs-Versuche diese beiden Pins auf Output geschaltet. Seitdem> funktioniert alles stabil und genau so wie es sein soll.>> Und ich versteh die Welt nicht mehr. Erstens hab ich nirgendwo in den> Beispielen oder im Datenblatt gelesen, dass die Einstellung im> Richtungs-Registers irgendwie mit der seriellen Schnittstelle> kollidieren könnte. Hatte das im Datenblatt so verstanden, dass die ser.> Schnittstelle die Pins okkupiert, egal was die sonst so machen.
So kenn ich das allerdings auch.
Die UART krallt sich ihre Pins. Denn eigentlich müsstest du ja einen auf
Output und einen auf Input stellen.
Hmm. Kann es sein, dass das DDR nur indirekt schuld ist, weil du Pullup
Widerstände eingeschaltet hast, die sich nur bei Datenrichtung Input
auswirken?
>Kann es sein, dass das DDR nur indirekt schuld ist, weil du Pullup
Widerstände eingeschaltet hast, die sich nur bei Datenrichtung Input
auswirken?
Stimmt, ich hatte die internen Pull-UPs für alle Input Ports aktiviert.
Normalerweise stelle ich unbenutzte Ports immer auf Input, mit dem
internen Pull-Up aktiv. Ist das ein Problem für die ser. Schnittstelle
(scheint ja so zu sein)?
Micha schrieb:> Stimmt, ich hatte die internen Pull-UPs für alle Input Ports aktiviert.> Normalerweise stelle ich unbenutzte Ports immer auf Input, mit dem> internen Pull-Up aktiv. Ist das ein Problem für die ser. Schnittstelle> (scheint ja so zu sein)?
Um ehrlich zu sein: Ich weiß es nicht.
Beim ADC bleiben die Pullups erhalten, auch wenn sich der ADC die
Portpins selbst auf Input stellt. Beim UART hab ich das noch nie
ausprobiert - wohl auch deshalb, weil ich mich relativ streng an die
Regel halte: Alle Initialisierungen am Anfang einmal machen und dann
nach Möglichkeit in Ruhe lassen. Auf Verdacht in irgendwelchen
Funktionen an den DDR Registern rumspielen wirst du bei mir nicht finden
- da verlier ich den Überblick, welche Funktion was umstellt.
@Joachim Drechsel:
ist mittlerweile ein recht umfangreiches und komplexes Projekt, das aus
je 12 Stück C und header Dateien besteht. Den ganzen Müll wird eh keiner
durchsehen wollen(?)
Aber ich muss eines zugeben: vor ca. 2 Monaten hab ich die Hardware,
also auch die Beschaltung der Atmega-Pins, radikal überarbeitet. Kann
daher nicht ganz ausschliessen dass irgendwo noch was von Port D gelesen
und verwertet wird. Muss ich mir am WE mal ganz genau ansehen...
Micha schrieb:> Aber ich muss eines zugeben: vor ca. 2 Monaten hab ich die Hardware,> also auch die Beschaltung der Atmega-Pins, radikal überarbeitet. Kann> daher nicht ganz ausschliessen dass irgendwo noch was von Port D gelesen> und verwertet wird. Muss ich mir am WE mal ganz genau ansehen...
Dann solltest du gleich mal hergehen und für die Funtkionalitäten
#defines vorsehen.
Auch wenn wir hier im Forum eine LED meistens so einschalten
1
while(1){
2
3
if(irgendwas)
4
PORTB|=(1<<PB0);
5
}
(bzw. sinngemäss für andere Hardware), so ist das bei einem realen,
größerem Projekt ein sicherere Weg ins Chaos.
1
#define LED_PORT PORTB
2
#define LED_DDR DDRB
3
#define LED_ERROR PB0
4
5
6
...
7
8
while(1){
9
10
if(irgendwas)
11
LED_PORT|=(1<<LED_ERROR);
12
}
(und natürlich mit der restlichen Hardware genauso). Weiters sind, mit
Ausnahme von ganz am Anfang, Komplettzugriffe auf die Ports verboten.
Auch hier wieder eine Ausnahme: wenn natürlich die Hardware den ganzen
Port belegt.
Immer nur Einzelbit Setzen/Löschen. Und immer nur über eigens dafür
eingeführte #define, die das Gerät identifizieren und an einen Pin
binden.
Denn dann hast du die Konfigurationen an einer Stelle im Code beisammen
und wenn die LED an einen anderen Pin umzieht, dann ändert man einfach
das #define und der restliche Code passt sich alleine an, so dass man
keine Stelle übersehen kann.
Abgesehen davon, ist dann natürlich auch
LED_PORT |= ( 1 << LED_ERROR );
eine viel bessere Selbst-Dokumentation als
PORTB |= ( 1 << PB0 );
Gute Programmierer überschauen nicht das Chaos, sondern sie schaffen
sich Mechanismen, dass es erst gleich gar nicht entsteht. Und sie
begreifen sich selbst als mögliche Fehlerquelle, die es auszuschalten
gilt.
Danke für die Hinweise!
Wohl wahr, in mindestens 99% aller Probleme ist der Mensch die Ursache.
Derzeit ist meine Projekt-Hardware noch auf Breadboard-Basis. Da hab ich
konsequent drauf geachtet die Ports logisch und in Reihenfolge
zusammenhängend für bestimmte Aufgaben zu verwenden. Aber ich weiss,
dass bei Leiterplatten-Schaltungen die Prioritäten oft anders gesetzt
werden, da werden dann Pin-Zuordnungen dem Layout untergeordnet, um
Überkreuzungen von Leiterbahnen zu minimieren.
Werd mich am WE mal hinsetzten und meinen Code aufräumen. Vielleicht hab
ich dabei dann ja noch den einen oder anderen Aha-Effekt ;)