Forum: Mikrocontroller und Digitale Elektronik UART senden über udrie0


von PCB (Gast)


Lesenswert?

Hallo an Alle!

Ich habe einen Atmega 2560. Wenn ich über UART0 sende kann ich die Daten 
über serielle Schnittstelle mit einem hyperterminal empfangen.
Das klappt allerdings nur, wenn ich die Daten direkt in das udre0 
schreibe mittels

sts UDR0, r16 zum Beispiel in einem Timer.

Jetzt wollte ich die Daten testweise indirekt über das 
DataRegisterEmptyInterrupt senden.
Also (1<<udrie0) DataRegisterEmptyInterruptEnable in ucsr0b setzen, dann 
wird das Interrupt anschließend auch ausgelöst und dann im Interrupt 
selber sts UDR0, r16 ausführen und (0<<udrie0) das 
DataRegisterEmptyInterruptEnable wieder zurücksetzen.

Im nächsten Timer wird das ganze wiederholt. Timerintervall 1 Sekunde.

Klappt leider nicht. Es kommen keine Daten an am Pc an.

(1<<rxen0)|(1<<txen0)|(1<<rxcie0)|(1<<txcie0) sind gesetzt.

Kann mir jemand sagen, wo mein Denkfehler ist??
Danke!!!!!!

von Stefan F. (Gast)


Lesenswert?

Zu viel unklare Prosa. Zeige deinen Quelltext.

von c-hater (Gast)


Lesenswert?

PCB schrieb:

> Kann mir jemand sagen, wo mein Denkfehler ist??

Du hast nicht begriffen, dass das UDR-Interruptflag in deinem Fall 
IMMER gesetzt ist.

Das wird erst gelöscht, wenn du kurz hintereinander zwei Bytes in die 
Hardware geschrieben hast.

Die Software muss das wissen und entsprechend agieren.

Senden ist was anderes als Empfangen. Dementsprechend funktionieren die 
Interrupts vollkommen anders.

Da bedeutet in deinem Fall (wenn man nur alle Jubeljahre mal ein 
verschissenes Byte loswerden will): Die Nutzung des UDRE-Interrupts ist 
vollkommen hyperfluid...

von PCB (Gast)


Lesenswert?

Hallo c-hater,

danke für deine Antwort.
Das hilft schon mal weiter.
Wie müsste ich dann bei einer interruptgesteuerten Datenübertragung 
vorgehen ???

Danke

von c-hater (Gast)


Lesenswert?

PCB schrieb:

> Wie müsste ich dann bei einer interruptgesteuerten Datenübertragung
> vorgehen ???

Einfach nur: richtig. Der Interrupt darf erst "scharf geschaltet" 
werden, wenn die synchrone Befriedigung der Ausgabe-Anforderung nicht 
mehr möglich ist.

Es läuft darauf hinaus, eine synchrone Ausgaberoutine zu bauen, die bei 
entsprechender Last automatisch asynchron wird. Hört sich vielleicht 
kompliziert an, ist aber in Wirklichkeit mit wenigen Zeilen Code 
abgegessen. Jedenfalls, wenn man erstmal eine Ausgabe-Queue hat. Das ist 
die Grundlage, genau wie bei einer interruptgesteuerten Eingabe. Nur das 
Handling der Queue weicht ab.

Ich bin aber ziemlich sicher, dass in den Annalen dieses Forums auch die 
interruptgesteuerte Ausgabe schonmal irgendwo vorkam. Wie wäre es also, 
wenn du einfach mal selber suchen würdest?

von PCB (Gast)


Lesenswert?

c-hater schrieb:
> wenn die synchrone Befriedigung der Ausgabe-Anforderung nicht
> mehr möglich ist.

Geht das auch verständlich?

von c-hater (Gast)


Lesenswert?

PCB schrieb:

> Geht das auch verständlich?

Sicher. Wieviel bezahlst du für Nachhilfe-Lehrer pro Stunde?

von S. Landolt (Gast)


Lesenswert?

Vorschlag, so ganz grob:
Ringpuffer - Ausgaberoutine - UDR-ISR

Die Ausgaberoutine schreibt das Zeichen (ein Byte) in den Ringpuffer; 
ist der UDR-Interrupt gesperrt, wird er freigegeben.

Die UDR-ISR holt ein Byte aus dem Ringpuffer und übergibt es der 
Hardware (schreibt es in UDR). War es das letzte Byte im Ringpuffer, 
d.h. ist dieser leergelaufen, wird der UDR-Interrupt gesperrt.

von c-hater (Gast)


Lesenswert?

S. Landolt schrieb:

> Vorschlag, so ganz grob:
> Ringpuffer - Ausgaberoutine - UDR-ISR
>
> Die Ausgaberoutine schreibt das Zeichen (ein Byte) in den Ringpuffer;
> ist der UDR-Interrupt gesperrt, wird er freigegeben.

Das Konzept funktioniert auch, ist allerdings (relativ geringfügig) 
suboptimal.

> Die UDR-ISR holt ein Byte aus dem Ringpuffer und übergibt es der
> Hardware (schreibt es in UDR). War es das letzte Byte im Ringpuffer,
> d.h. ist dieser leergelaufen, wird der UDR-Interrupt gesperrt.

Hier wäre noch die TXC-Geschichte zu ergänzen, um die Sache wirklich 
"rund" zu machen.

von PCB (Gast)


Lesenswert?

Mein Problem scheint ja zu sein, dass das UDR-Interruptflag in meinem 
Fall
IMMER gesetzt ist und ich das DataReggoster in der 
DataRegisterEmptyInterrupt-Routine daher nicht beschreiben kann oder??

von S. Landolt (Gast)


Lesenswert?

Ja schon, aber zu Beginn ist der UDR-Interrupt ja nicht freigegeben. Die 
Freigabe erfolgt beim Aufruf der Ausgaberoutine mit dem Schreiben in den 
Ringpuffer.

von S. Landolt (Gast)


Lesenswert?

an c-hater:

Dann können Sie ja einen besseren Vorschlag machen, möglichst konkret.

Was mich betrifft, so halte ich es mit E.C. Bliss: The pursuit of 
excellence is gratifying and healthy. The pursuit of perfection is 
neurotic, frustrating and a terrible waste of time.
  Damit verabschiede ich mich.

von c-hater (Gast)


Lesenswert?

PCB schrieb:

> Mein Problem scheint ja zu sein, dass das UDR-Interruptflag in meinem
> Fall
> IMMER gesetzt ist

Ja.

> und ich das DataReggoster in der
> DataRegisterEmptyInterrupt-Routine daher nicht beschreiben kann oder??

Nein. Solange das Flag gesetzt ist,kannst du das Register natürlich 
beschreiben. Egal ob aus einer ISR oder aus main(). Genau den 
Sachverhalt, dass dieses Register (sinnvoll) beschreibbar ist, zeigt ja 
das gesetzte UDRE-Flag an.

von PCB (Gast)


Lesenswert?

Und warum werden die Daten dann nicht automatisch gesendet, wenn ich das 
Register beschreiben kann ??

von c-hater (Gast)


Lesenswert?

PCB schrieb:
> Und warum werden die Daten dann nicht automatisch gesendet, wenn ich das
> Register beschreiben kann ??

Werden sie doch, sobald du was in das Register schreibst. Zumindest so 
bald als möglich. Genau das ist, was die Hardware tut, wofür sie 
geschaffen wurde.

von Stefan F. (Gast)


Lesenswert?

Offenbar hat der c-hater dein Problem nicht verstanden. Wundert mich 
nicht, bei der unklaren Problembeschreibung. Ich traue mich auch nicht, 
dir zu antworten ohne vorher den Quelltext zu sehen.

von PCB (Gast)


Lesenswert?

Hallo und danke für die Nachfrage.

Wenn ich wie folgt vorgehe:

[......]
sei  ; interrupts global zulassen
reset_loop:; endlosschleife
sts UDR0, r16
sts UDR0, r16
rjmp reset_loop

Dann kann ich beobachten im Simulator, dass das UDRE0 auf 0 gesetzt 
wird.

Wenn ich das Gleiche aber im DataRegisterEmptyInterrupt mache passiert 
nix:

uartdre0:
  sts UDR0, r16
  sts UDR0, r16
reti

Das scheint der Kausus knacktus zu sein

von Stefan F. (Gast)


Lesenswert?

Schreibe mal ein (im Debugger) ausführbares Testprogramm, damit man das 
nachvollziehen kann.

Weil: Den von dir beschriebenen Effekt kenne ich so nicht. Ich schätze 
da passiert noch etwas anderes relevantes in Code, den du nicht gezeugt 
hast.

von PCB (Gast)


Lesenswert?

1
.include "m2560def.inc"
2
.listmac
3
4
.org 0x000    rjmp hreset            ; 01 - External Pin, Power-on Reset, Brown-out Reset, Watchdog Reset and JTAG AVR Reset
5
.org 0x0034    rjmp huartdre0           ; 27 - USART0 Data Register Empty
6
7
8
9
10
11
hreset:
12
13
  ; init stack ptr
14
  ldi r16, high(ramend)
15
  out sph, r16
16
  ldi r16, low(ramend)
17
  out spl, r16
18
19
20
  ; uart0 setup
21
  ldi r16, 0
22
  sts ubrr0h, r16
23
24
  ldi r16, 59
25
  sts ubrr0l, r16
26
27
  ldi r16, (1<<rxen0)|(1<<txen0)|(1<<rxcie0)|(1<<txcie0)
28
  sts ucsr0b, r16
29
30
  ldi xh, high(sram_start)
31
  ldi xl, low(sram_start)
32
  clr r16
33
hreset_memclr:
34
  st x+, r16
35
  cpi xl, low(ramend)
36
  brne hreset_memclr
37
  cpi xh, high(ramend)
38
  brne hreset_memclr
39
40
sei  ; interrupts global zulassen
41
42
hreset_loop:; infinitive loop
43
ldi r16, (1<<udrie0) ; initiate transmittion
44
sts ucsr0b, r16
45
  rjmp hreset_loop
46
47
48
  
49
  reti
50
  ;--------------
51
huartdre0:
52
  ldi r16, (0<<udrie0) ; stop transmittion
53
  sts ucsr0b, r16
54
55
  sts UDR0, r16
56
  sts UDR0, r16
57
58
  reti

und einmal das andere Beispiel:
1
.include "m2560def.inc"
2
.listmac
3
4
.org 0x000    rjmp hreset            ; 01 - External Pin, Power-on Reset, Brown-out Reset, Watchdog Reset and JTAG AVR Reset
5
6
hreset:
7
8
  ; init stack ptr
9
  ldi r16, high(ramend)
10
  out sph, r16
11
  ldi r16, low(ramend)
12
  out spl, r16
13
14
15
  ; uart0 setup
16
  ldi r16, 0
17
  sts ubrr0h, r16
18
19
  ldi r16, 59
20
  sts ubrr0l, r16
21
22
  ldi r16, (1<<rxen0)|(1<<txen0)|(1<<rxcie0)|(1<<txcie0)
23
  sts ucsr0b, r16
24
25
  ldi xh, high(sram_start)
26
  ldi xl, low(sram_start)
27
  clr r16
28
hreset_memclr:
29
  st x+, r16
30
  cpi xl, low(ramend)
31
  brne hreset_memclr
32
  cpi xh, high(ramend)
33
  brne hreset_memclr
34
35
sei  ; interrupts global zulassen
36
37
hreset_loop:; infinitive loop
38
sts UDR0, r16
39
sts UDR0, r16
40
  rjmp hreset_loop

von W.S. (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> Offenbar hat der c-hater dein Problem nicht verstanden.

Naja, hier geht es auch einigermaßen wirr zu.

OK, mit den AVR befasse ich mich nicht, aber bei den üblichen 
U(S)ART-Cores ist es im Algemeinen so:

Die Sendeseite:
Vor dem Schieberegister, wo das zu sendende Byte herausgeschoben wird, 
sitzt ein weiteres Register, in das man schreiben kann und wo (wenn das 
Schieberegister leer ist) das Byte sogleich ins Schieberegiter 
durchgewinkt wird. Ist grad noch was im Schieberegister, dann wird das 
Byte erst mal in o.g. Register zwischengespeichert, bis das 
Schieberegister leer ist.
So, und nun hat man zumeist 2 Interruptbits: eines was beim Leersein des 
Schieberegisters gesetzt ist und eines, was immer dann gesetzt ist, wenn 
Platz frei ist (im Schieberegister oder dem Register davor). Kann aber 
auch sein, daß bei den beiden eines oder beide invertiert sind, macht 
aber vom Prinzip nix.

Was also muß der Lowlevel-Treiber tun?
a) Wenn es etwas zu senden gibt und Platz frei ist (s.o.), dann soll er 
das nächste Byte in den Sender (also die Kombi aus Schieberegister und 
dem Register davor) schreiben. Normalerweise verschwindet dann das o.g. 
Interruptbit (das für "es ist noch Platz frei") und die zugehörige 
Interrupt-Anforderung. Kann aber auch sein, daß beim AVR so etwas 
flankenabhängig ist, also die o.g. Bedingung des Leerseins bzw. Platz 
leer nicht statisch die Bits steuert, sondern das Leerwerden oder das 
Platz leer Werden zum Setzen der Bits führt. Das liest man besser im 
Manual nach.

b) wenn es nix zu senden gibt, muß man die Interrupt-Anforderung 
sperren, denn ohne dies würde der U(S)ART ständig nach Nachschub 
brüllen. Logischerweise sollte man immer dann, wenn ein Byte gesendet 
werden soll und deshalb zuerst in den treiberinternen Ringpuffer 
gesteckt worden ist, diese Interrupt-Sperre aufheben. Dann kommt ein 
Interrupt, der das Byte abholt und in den Sender steckt. Wenn 
anschließend nix zu senden ist, dann siehe a)

Bei einigen Cortexen (LPC...) hab ich allerdings schon gesehen, daß dort 
der UART erst dann tatsächlich zu senden und Interrups anzufordern 
beginnt, wenn wenigstens 2 Bytes ab Initialisierung in den Sender 
gestopft wurden. Aber bei den AVR würde ich so eine Sonderlocke nicht 
erwarten.

W.S.

von Stefan F. (Gast)


Lesenswert?

diese Zeilen finde ich merkwürdig:
1
ldi r16, (1<<udrie0) ; initiate transmittion
2
sts ucsr0b, r16

Der Kommentar stimmt nicht mit dem überein, was du da wirklich machst. 
Und zwar erlaubst du Interrupts, aber zugleich schaltest zu alle anderen 
Bits in udrie0 aus.

Im Simulator bleibt TXEN0 aber trotzdem auf 1, das wundert mich, ist 
vielleicht ein Bug im Simulator (?).

Gleicher Fehler in der ISR:
1
ldi r16, (0<<udrie0) ; stop transmittion
2
sts ucsr0b, r16

Da stoppst du nicht nur die Übertragung sondern alles. Du setzt alle 
Bits auf 0.

Außerdem enabelst du in der Initialisierung rxcie0 und txcie0, aber es 
gibt keine zugehörigen Interrupt-Handler.

Ich habe es mal korrigiert:
1
.include "m2560def.inc"
2
.listmac
3
.org 0x000    rjmp hreset            ; 01 - External Pin, Power-on Reset, Brown-out Reset, Watchdog Reset and JTAG AVR Reset
4
.org 0x0034    rjmp huartdre0           ; 27 - USART0 Data Register Empty
5
hreset:
6
  ; init stack ptr
7
  ldi r16, high(ramend)
8
  out sph, r16
9
  ldi r16, low(ramend)
10
  out spl, r16
11
  ; uart0 setup
12
  ldi r16, 0
13
  sts ubrr0h, r16
14
  ldi r16, 59
15
  sts ubrr0l, r16
16
  ldi r16, (1<<rxen0)|(1<<txen0)|(1<<udrie0)
17
  sts ucsr0b, r16
18
  ldi xh, high(sram_start)
19
  ldi xl, low(sram_start)
20
  clr r16
21
hreset_memclr:
22
  st x+, r16
23
  cpi xl, low(ramend)
24
  brne hreset_memclr
25
  cpi xh, high(ramend)
26
  brne hreset_memclr
27
  sei  ; interrupts global zulassen
28
29
hreset_loop:; infinitive loop
30
  rjmp hreset_loop
31
  
32
huartdre0:
33
  sts UDR0, r16
34
  sts UDR0, r16
35
  reti

Sobald das Programm in der finalen "hreset_loop" landet wird die ISR 
aufgerufen. Nachdem zwei Bytes in UDR0 abgelegt wurden, geht das UDRE0 
Flag brav auf 0.

Das kann man so im Debugger/Simulator ganz bequem durch steppen. Habe 
ich mit dem AVR Studio 4.19 getestet.

von PCB (Gast)


Lesenswert?

Hallo Stefan,

Tolle Arbeit.

Wenn ich die Zeile durch

ldi r16, (1<<rxen0)|(1<<txen0)|(1<<rxcie0)|(1<<txcie0)|(1<<udrie0)
sts ucsr0b, r16 ; starte übertragung
dann klappt es.

Habe wie du richtig gesagt hast alle Bits auf 0 gesetzt.


Danke Dir!!!

von Stefan F. (Gast)


Lesenswert?

Wenn die Zeile wirklich nur "starte übertragung" machen soll, dann 
solltest du zuerst das Register lesen, nur das eine relevante Bit ändern 
und es dann wieder zurück schreiben.

Auf viele Register kann man auch Bit Operationen (SBI und CBI) anwenden. 
Ich habe nicht im Kopf, ob ucsr0b dazu zählt.

von PCB (Gast)


Lesenswert?

"... For I/O registers located in extended I/O map, "IN", "OUT", "SBIS",
"SBIC", "CBI", and "SBI" instructions
must be replaced with instructions that allow access to extended I/O.
Typically "LDS" and "STS" combined with "SBRS", "SBRC", "SBR", and
"CBR"."

Leider nein.

von c-hater (Gast)


Lesenswert?

W.S. schrieb:

> Bei einigen Cortexen (LPC...) hab ich allerdings schon gesehen, daß dort
> der UART erst dann tatsächlich zu senden und Interrups anzufordern
> beginnt, wenn wenigstens 2 Bytes ab Initialisierung in den Sender
> gestopft wurden. Aber bei den AVR würde ich so eine Sonderlocke nicht
> erwarten.

Also, so ist es weder beim AVR8 noch bei den Cortexen. Gesendet wird 
natürlich sofort. Aber der Interrupt für die Sendeanforderung wird erst 
dann gelöscht, wenn die Hardware "voll" ist. Auch das ist identisch und 
logisch.

Der Ablauf ist so:

1) Initial gibt es nichts zu versenden, deswegen ist natürlich der 
Interrupt für die Sendeanforderung aktiv. Denn der zeigt an, dass aus 
Sicht der Hardware Daten zum Versenden benötigt werden. Weil man aber 
hier aus Sicht des Treibers noch keine Daten zum Versenden hat, darf 
also die Interruptanforderung noch nicht zum Interrupt führen, denn die 
ISR hätte nix, womit sie die Hardware bedienen könnte.

2) Man schreibt ein Byte zum Treiber. Der kann das direkt in die 
Hardware schreiben. Es wird dort unmittelbar zum Ausgabe-Shiftregister 
durchgeleitet und seine Ausgabe beginnt.
Die Interruptanforderung der Hardware bleibt aber aktiv, denn das 
Buffer-Register der Hardware bleibt leer, sie "benötigt" aus ihrer Sicht 
also weiterhin Ausgabe-Daten. Wenn aus Sicht des Treiber aber keine 
weiteren Bytes zur Ausgabe vorliegen, darf der Interrupt in dieser 
Situation immer noch nicht freigeschaltet werden.

3) Noch ein Byte kommt zur Ausgabe. Der Treiber kann auch dies 
unmittelbar an die Hardware durchreichen, es landet in deren 
Buffer-Register. Jetzt geht erstmalig das Interrupflag aus, weil die 
Hardware jetzt "voll" ist, mehr kann sie nicht buffern. Aber der Treiber 
darf immer noch nicht den Interrupt scharf schalten, denn es liegen ja 
keine weiteren Daten zum Versenden vor, die an eine ISR an die Hardware 
verfüttern könnte.

Ab hier wird es "komplizierter", denn es kommt darauf an, was wann an 
Daten nachkommt. Nehmen wir erst einmal den Fall: keine. Dann ist der 
Drops hier für den Treiber gelutscht, denn alles, was zu versenden war, 
ist schon in der Hardware gelandet und die wird es ausgeben, zuerst das 
Shift-Register, dann wird das erneut aus dem Buffer-Register gefüllt 
(dabei geht dann das UDRE-Flag wieder auf aktiv) und letztlich wird auch 
das zweite Byte komplett ausgegeben (was dann zum Schluss das 
TXC-Interruptflag setzt).

Kommt aber ein drittes Byte zur Ausgabe beim Treiber an, bevor das erste 
komplett ausgegeben ist, dann tritt erstmalig die Situation ein, dass 
entweder der Treiber "stallen" muss (bei synchroner Ausgabe) oder halt 
der Interrupt erlaubt werden muss und die Daten in einen Softwarebuffer 
geschrieben werden müssen, aus dem sie später durch die ISR an die 
Hardware verfüttert werden (asynchrone Ausgabe).

Bei den Cortexen (und überhaupt bei jeder UART mit Double-Buffering) 
geht das im Prinzip ganz genau so, bloß die Bits und Interrupts heißen 
halt immer irgendwie anders.

von c-hater (Gast)


Lesenswert?

c-hater schrieb:

> oder halt
> der Interrupt erlaubt werden muss und die Daten in einen Softwarebuffer
> geschrieben werden müssen, aus dem sie später durch die ISR an die
> Hardware verfüttert werden (asynchrone Ausgabe).

Nach diesem Abschnitt fehlt natürlich noch ein sehr wichtiger, nämlich 
der, in dem die UDRE-ISR auf die Situation trifft, dass ihr 
Software-Buffer gerade leergelaufen ist. In dieser Situation muss die 
ISR natürlich dann den UDRE-Interrupt wieder "entschärfen".

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.