Forum: Mikrocontroller und Digitale Elektronik Mega32, Probleme mit RS232


von thomas (Gast)


Angehängte Dateien:

Lesenswert?

Hi!
Ich habe ein Problem mit dem Code im Anhang, geschrieben ist er für ein
ATMega32, Interrupt gesteuerte RS232 Kommunikation.
Das Programm sollte einfach nur das empfangene Zeichen wieder zurück
schicken und zwar ein Mal (Echo), aber wenn ich ein Zeichen zum
Controller schicke fängt er an andauernd das Zeichen zurück zu
schicken, bis ich ein neues Schicke, dann schickt er andauernd das neue
Zeichen.
Wo ist mein Fehler? Hat jemand vielleicht mehr Informationen zu
Interrupt gesteuerter RS232 Kommunikation, als im Tutorial oder im
Datenblatt? Vielleicht ein Programmablaufplan, fertiger Code oder so?

von Hannes L. (hannes)


Lesenswert?

Ich kann kein C...

Aber:
Der Sende-Interrupt tritt auf, wenn das Byte raus ist, also ein
weiteres Byte gesendet werden könnte.
Dies ist sehr vorteilhaft, wenn man Byres aus einem Ringbuffer in
"einem Rutsch" im Hintergrund (Interrupt) senden möchte.

Da du nach Empfang eines Bytes dieses Byte "nur einmal" zurücksenden
willst, solltest du auf den Sende-Interrupt verzichten.
Stattdessen einfach das UDRE-Bit prüfen bis UDR leer ist und dann das
Byte in UDR schreiben. Fertig...

Wenn du das nicht in der RX-ISR machen willst (ist ja nicht
"zukunftsfähig"...), dann solltest du dir ein Flag (Boolean)
einrichten, welches in der RX-ISR gesetzt wird. Die Mainloop pollt dann
(unter anderem, denn das Prog soll ja später sicherlich noch andere Jobs
erledigen) dieses Flag bis ein neues Byte eingetroffen ist (Flag
gesetzt). Ist dies der Fall, springt man zur TX-Routine (später dann
mal zu der Routine, die das Byte "verarbeiten" soll), in der man das
Byte sendet und das Flag löscht (der "Job" ist ja erledigt...). Mit
mehreren dieser Flags kann man mehrere verschiedene Jobs "anmelden",
kaum ein vernünftiges Programm kommt ohne diese Flags aus. Sollte man
sich also unbedingt angewöhnen.

...

von thomas (Gast)


Lesenswert?

@Hannes: Das mit dem Empfangen ist ja kein Problem, und dein Vorschlag
mit dem Flag habe ich auch für die Zukunft so vorgesehen, nur mit dem
Interrupt gesteuerten Senden hab ich noch Probleme.
Nachdem ich den TX-Interrupt auslöse (in der Funktion send()) wird
diese ja ausgeführt gleichzeitig müsste der Controller ja das UDRE-Flag
löschen da ich ja Daten in UDR einschreibe, somit dürfte der Interrupt
nicht wieder ausgeführt werden, oder verstehe ich das Datenblatt da
falsch.

"When the Data Register empty Interrupt Enable (UDRIE) bit in UCSRB is
written to one, the USART Data Register Empty Interrupt will be executed
as long as UDRE is set. UDRE is cleared by writing UDR. When
interrupt-driven data transmission is used, the DATA Register Empty
Interrupt routine must either write new data to UDR in order to clear
UDRE or disable the Data Register Empty Interrupt, otherwise a new
interrupt will occur once the interrupt routine terminates."

Ich hab ja sogar beides gemacht, so dass eigentlich kein weiterer
Interrupt auftreten dürfte.

von Hannes L. (hannes)


Lesenswert?

Der Interrupt tritt meiner Meinung nach auf, sobald UDR leer ist, also
das eben gesendete Byte abgearbeitet ist. Daher wird das Byte (oder das
Nächste, sofern eingetroffen) solange gesendet, bis der Interrupt wieder
deaktiviert wird.

Daher würde ich zum Senden eines einzelnen Bytes keinen Int nehmen.

Wenn man einen String senden möchte (z.B. Ringbuffer voller Bytes),
dann aktiviert die Routine, die den Ringbuffer füllt, den
UDRE-Interrupt.
Die UDRE-ISR prüft nun vor jedem Senden die Pointer des Ringbuffers und
deaktiviert den Interrupt, sobald der Lesepointer den Schreibpointer
eingeholt hat, also sobald der Ringbuffer leer ist.

Auf diese Art kann man ganze Strings senden (man füllt ja nur den
Ringbuffer, auch FIFO genannt), ohne auf das Timing der UART Rücksicht
zu nehmen. Das einentliche Senden geschieht dann in der ISR, bis bei
leerem Buffer der Int deaktiviert wird.

Verstanden?

Wenn du deine Tests (Echo-Betrieb) unbedingt mit Sende-Interrupt machen
möchtest, dann sollte dein Empfang den Sende-Int aktivieren und die
Senderoutine nach getaner Arbeit (schreiben in UDR) den Int wieder
deaktivieren, damit bei leerem UDR (Byte also fertig gesendet) nich
nochmal gesendet wird.

Nochmal anders betrachtet:
Ein Interrupt ist eine Reaktion auf ein Ereignis von außen.
Da das Senden eines Bytes kein äußeres Ereignis ist, sondern die
Entscheidung des laufenden Programms, ist hier ein Interrupt fehl am
Platze.
Anders beim Ringbuffer. Das äußere Ereignis ist das "Leersein" des
UDR, also die "Meldung", dass der "Transmitter" bereit ist, ein
neues Byte zu senden.
Man füllt nun den Ringbuffer mit Daten und gibt den UDRE-Interrupt
frei. Nun sorgt das Ereignis "UDR leer" dafür, das die ISR aufgerufen
wird. In dieser prüft das Programm, ob überhaupt noch Daten zum Senden
anliegen und entscheidet danach, ob es das nächste Byte ausgibt oder ob
es (weil nix mehr da ist) den Interrupt sperrt, um Doppelausgaben zu
vermeiden.

Ich hoffe, es war einigermaßen verständlich formuliert (etwas
überspitzt war es ja auch...).

Frohes Schaffen noch...

...

von thomas (Gast)


Lesenswert?

Ja hab dich verstanden, allerdings steht es dann wohl falsch im
Datenblatt. Es wundert mich dass es dann noch keiner bisher
gemerkt/korrigiert hat.
Vielleicht weiß jemand anderes noch etwas zu diesem Problem/Thema?
@Hannes: Aber trotzdem Danke!

von André F. (ratatatata)


Lesenswert?

>>Es wundert mich dass es dann noch keiner bisher
gemerkt/korrigiert hat.

ist ja auch kein fehler?

von Rahul D. (rahul)


Lesenswert?

Wenn du den Versand (eines Bytes) unbedingt mit einer ISR machen willst,
dann könntest du es so machen:

SIGNAL(SIG_UART_DATA){
  UCSRB &= ~(1<<UDRIE); // UDRIE-Bit löschen
  UDR = txData;         // Byte senden
}

Das ist aber nicht wirklich schön...

Mach es dann lieber so:

SIGNAL(SIG_UART_RECV){
  rxData = UDR;
  UDR = rxData;
}

Da du sowieso immer nur ein Byte als Echo verschicken willst, kann man
das auch gleich in der Empfangs-ISR machen. Die Wahrscheinlichkeit,
dass sich noch ein Byte im Sendepuffer befindet, ist sehr gering.

von Hannes L. (hannes)


Lesenswert?

> Da du sowieso immer nur ein Byte als Echo verschicken willst, kann
> man das auch gleich in der Empfangs-ISR machen.....

Ich nahm allerdings an, dass das Echo nur ein Provisorium ist und der
MC später mal einen intelligenteren Dialog mit dem PC führen soll
(nicht nur als Papagei). Deshalb auch meine Hinweise in Richtung
Ringbuffer zum Senden (ist zum Empfang natürlich auch sehr sinnvoll!).
AVR-U(S)ART hat nun leider mal kein FIFO.

...

von Rahul D. (rahul)


Lesenswert?

Hannes, da sein Programm so nicht optimal funktioniert, war das nur ein
Lösungsvorschlag.
Natürlich ist es sinnvoll, Ringpuffer etc. zu verwenden.
Ein Mini-FIFO haben AVR schon: Sie können 2 Bytes zwischenspeichern
(zwei Zugriffe auf UDR...)

Das einfachste wäre wohl erst mal ein Flag zu setzen und gleichzeitig
das UDR in ein Register zu schreiben. Das wäre ja die Vorstufe zum
Ringspeicher...

von Hannes L. (hannes)


Lesenswert?

Rahul, richtig...

Wenn ich das Blockschaltbild richtig interpretiere, hat RX neben dem
Shiftregister zwei Bytes Buffer, TX jedoch nur ein Byte. Dass sich
beide (physikalisch völlig getrennte) Buffer über eine Adresse (udr)
ansprechen lassen, ist ja klar, es sind schließlich Beides
"Einbahnstraßen"...

Ich ging bisher davon aus, dass RX auch nur ein Byte puffern kann,
danke für die Aufklärung...

Bit- & Bytebruch...
...HanneS...

von Karl H. (kbuchegg)


Lesenswert?

> "When the Data Register empty Interrupt Enable (UDRIE) bit in
> UCSRB is written to one, the USART Data Register Empty Interrupt
> will be executed as long as UDRE is set. UDRE is cleared by
> writing UDR. When interrupt-driven data transmission is used, the
> DATA Register Empty Interrupt routine must either write new data
> to UDR in order to clear UDRE or disable the Data Register Empty
> Interrupt, otherwise a new interrupt will occur once the interrupt
> routine terminates."
>
> Ich hab ja sogar beides gemacht, so dass eigentlich kein weiterer
> Interrupt auftreten dürfte.

Das hast Du vielleicht vor gehabt, aber gemacht hast Du es nicht :-)

>
> SIGNAL(SIG_UART_DATA){
>   UDR = txData;
>   UCSRA |= (0<<UDRE);
> }

So kann man kein Bit loeschen (mit bitweisen ODER).
ODER: Bit setzen
UND:  Bit loeschen

Um ein Bit zu loeschen, brauchst Du eine Maske, die an
allen Stellen, die nicht geaendert werden sollen eine 1
enthaelt und an allen Stellen die geloescht werden sollen
eine 0. Die kriegst Du mit dem ~ Operator, der alle Bits
umdreht:

   UCSRA &= ~( 0<< UDRE );

Jetzt wird das Bit geloescht.

Mit
  UCSRA |= (0<<UDRE);
hat sich UCSRA kein bischen veraendert. Denn

Alter Wert        Neuer Wert
*****************************
  1  ODER 0   ist    1
  0  ODER 0   ist    0

von Stefan Seegel (Gast)


Lesenswert?

Müsste aber heißen

UCSRA &= ~( 1<< UDRE );

Sonst passiert mit dem Register gar nix

Stefan

von thomas (Gast)


Angehängte Dateien:

Lesenswert?

So der Echo-Teil funktioniert jetzt, nachdem ich einige Fehler beseitigt
habe, Code ist im Anhang.
Mich würde noch interessieren wann eigentlich der Transmit Complete
Interrupt ausgelöst wird, wie stellt der µC fest wann ein ganzes Frame
übertragen wurde?

von A.K. (Gast)


Lesenswert?

"wann eigentlich der Transmit Complete Interrupt ausgelöst wird"

Wenn das Buffer-Register leer ist und das Shift-Register leer läuft.
Erfahrungsgemäss allerdings noch während des Stopbits.

Braucht mal normalerweise nur bei Halfduplex, z.B. RS485, wenn nach der
Übertragung der Transmitter abgeschaltet werden muss. Weshalb man da
erst noch ein bischen warten muss, sonst killt man das Stopbit.

von thomas (Gast)


Lesenswert?

So hatte ich das auch verstanden, ich hab dann auch mal meinen
Echo-Betrieb abgewandelt, indem ich in die
Transmit-Complete-Interrupt-Routine eine Endlosschleife eingebaut habe,
aber da ist er wohl nie reingesprungen (weg optimiert war die
Endlosschleife nicht). Im AVR-Studio-Simulator hab ich es auch noch
nicht geschafft, mmmmh mache wohl irgendwas falsch...

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.