Forum: Compiler & IDEs Problem mit UART ISR


von Micha (Gast)


Lesenswert?

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
void AssignInstructionSet()
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:
1
ISR(UART0_RECEIVE_INTERRUPT)
2
{
3
uint8_t ReceivedByte;
4
   ReceivedByte = UART0_DATA; // get a byte ;-)
5
   UART0_DATA = (uint8_t) toupper(ReceivedByte); // Echo pressed key uppercase
6
}

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?

von Micha (Gast)


Lesenswert?

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?

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

Ich vermute mal, die ISR kommen da durcheinander. Wie ist der UART
aufgesetzt ? Was passiert beim Senden ? Was macht monitor() ?

von Krapao (Gast)


Lesenswert?

>  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.

von Micha (Gast)


Lesenswert?

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?

von Micha (Gast)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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?

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

Ich würde auch den Zufall verdächtigen.

Vielleicht könnte er mal den kompletten Code posten ?

Wäre dann (im Erfolgsfall) auch für andere hilfreich.

von Micha (Gast)


Lesenswert?

>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)?

von Karl H. (kbuchegg)


Lesenswert?

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.

von Micha (Gast)


Lesenswert?

@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...

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

Ok, mein aktuelles besteht aus 84 C-Sourcen und 11 Headerfiles.
Sagt das etwas aus ? Bestenfalls, daß ich Dateien mag ;)

von Karl H. (kbuchegg)


Lesenswert?

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.

von Micha (Gast)


Lesenswert?

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 ;)

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.