Forum: Mikrocontroller und Digitale Elektronik Mal wieder UART mit Ringpuffer.


von Dampfente (Gast)


Lesenswert?

Hallo!

Ich habe gemerkt, dass ein Ringpuffer am U(S)ART einiges bringen würde - 
ist wohl aber hinreichend bekannt ;)
Also habe ich mir überlegt: "Bau ich das doch in meine uartlib mit 
ein!". Gesagt, getan. Aber Essig, dat Ding lüppt nich. Leider sehe ich 
selbst nirgends einen Fehler, es müsste theoretisch klappen!

Hier meine Lib (auf das gekürzt, was auch kompiliert wird...:
1
#include <avr/interrupt.h>
2
#include "uartlib.h"
3
4
volatile char TXBuffer[UART_BUFFER_SIZE];
5
uint8_t tx_start = 0;
6
uint8_t tx_end = 1;
7
8
volatile char RXBuffer[UART_BUFFER_SIZE];
9
uint8_t rx_start = 0;
10
uint8_t rx_end = 1;
11
12
#define bufferEmpty(buffer, start, end) \
13
  (((start + 1) % UART_BUFFER_SIZE) == end)
14
15
#define bufferFull(buffer, start, end) \
16
  (((end + 1) % UART_BUFFER_SIZE) == start)
17
18
#define enQueue(value, buffer, end) \
19
  buffer[end++] = value; \
20
  end %= UART_BUFFER_SIZE;
21
22
#define deQueue(value, buffer, start) \
23
  value = buffer[++start]; \
24
  start %= UART_BUFFER_SIZE;
25
26
ISR(USART_RXC_vect) {
27
  // Store UDR value
28
  if(!bufferFull(RXBuffer, rx_start, rx_end))
29
    enQueue(UDR, RXBuffer, rx_end);
30
}
31
32
ISR(USART_UDRE_vect) {
33
  // Send a value
34
  if(!bufferEmpty(TXBuffer, tx_start, tx_end)) {
35
    deQueue(uint8_t txn, TXBuffer, tx_start);
36
    UDR = txn;
37
  }
38
}
39
40
void uart_init(uint32_t baud, uint8_t dataDirection) {
41
  uint16_t UBRR_VAL = ((F_CPU + baud * 8UL) / (baud * 16UL) - 1UL);
42
  UBRRH = (uint8_t) (UBRR_VAL >> 8);
43
  UBRRL = (uint8_t) UBRR_VAL;
44
  UCSRB = dataDirection;
45
  UCSRB |= (1 << RXCIE) || (1 << UDRIE);
46
}
47
48
inline void uart_transmit(uint8_t data) {
49
  while (!bufferFull(TXBuffer, tx_start, tx_end));
50
  enQueue(data, TXBuffer, tx_end);
51
}
52
53
inline uint8_t uart_receive() {
54
  while (!bufferEmpty(RXBuffer, rx_start, rx_end));
55
  deQueue(uint8_t rxn, RXBuffer, rx_start);
56
  return rxn;
57
}

so sieht gerade mein main-Prog aus... "Standard" ;)
1
#include "lib/uartlib.h"
2
#include <avr/interrupt.h>
3
4
int main() {
5
  uart_init(115200UL, UART_DD_BOTH);
6
  sei();
7
  uart_transmit(255);
8
}

Auch mit 9600bd kommt beim Empfänger(PC) nix an. Ohne 
Interrupts/Buffering hatte alles wunderhübsch geklappt :(

Wer weiß, wo's brennt?

gez: eine Ente

von Dampfente (Gast)


Lesenswert?

Noch ein add:
1
volatile char TXBuffer[UART_BUFFER_SIZE];
2
volatile uint8_t tx_start = 0;
3
volatile uint8_t tx_end = 1;
4
5
volatile char RXBuffer[UART_BUFFER_SIZE];
6
volatile uint8_t rx_start = 0;
7
volatile uint8_t rx_end = 1;

Sollte natürlich so sein.
Funktioniert aber genausowenig :(

Hilfe!

gez: eine Ente

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Dampfente schrieb:
> es müsste theoretisch klappen!
Und Praktisch? Was sagt der Debugger?

Dir ist schon klar, dass du das erste Zeichen ohne Interrupt senden 
musst? Oder was meinst du, wie oft die ISR(USART_UDRE_vect) aufgerufen 
wird?

von Jim M. (turboj)


Lesenswert?

Klarer Fall: Du startest das Aussenden nicht. Das ist hier das Problem, 
denn der TX-Intterupt (UDRE) kommt ja nur wenn man schon ein Zeichen 
verschickt hat.

Das heisst man muss das erste Zeichen direkt ins UDR Register schreiben 
- aber nur falls nicht schon ein Transfer läuft.

Alternativ kann man auch den TX-Interrupt via Software auslösen. Das 
Problem ist jedoch dasselbe: Auch dies darf nur dann stattfinden, wenn 
kein TX-Transfer aktiv ist, sonst gibts Datensalat.

von Dampfente (Gast)


Lesenswert?

Jim Meba schrieb:
> Klarer Fall: Du startest das Aussenden nicht. Das ist hier das Problem,
> denn der TX-Intterupt (UDRE) kommt ja nur wenn man schon ein Zeichen
> verschickt hat.

Klingt logisch, danke!

Angepasst:
1
void uart_init(uint32_t baud, uint8_t dataDirection) {
2
  uint16_t UBRR_VAL = ((F_CPU + baud * 8UL) / (baud * 16UL) - 1UL);
3
  UBRRH = (uint8_t) (UBRR_VAL >> 8);
4
  UBRRL = (uint8_t) UBRR_VAL;
5
  UCSRB = dataDirection;
6
  UCSRB |= (1 << RXCIE) || (1 << UDRIE);
7
  while ( !( UCSRA & (1<<UDRE)) );
8
  UDR = 255;
9
}
Bringt aber auch nix...?
Ja, diese 255 wird gesendet, aber die aus dem main() schon nicht mehr!

gez: eine Ente

von Dampfente (Gast)


Lesenswert?

Doch, da waren noch ein paar Fehler...entfernt ;)

Aktueller Status:
1
volatile char TXBuffer[UART_BUFFER_SIZE];
2
volatile uint8_t tx_start = 0;
3
volatile uint8_t tx_end = 1;
4
5
volatile char RXBuffer[UART_BUFFER_SIZE];
6
volatile uint8_t rx_start = 0;
7
volatile uint8_t rx_end = 1;
8
9
#define bufferEmpty(start, end) \
10
  (((start + 1) % UART_BUFFER_SIZE) == end)
11
12
#define bufferFull(start, end) \
13
  (((end + 1) % UART_BUFFER_SIZE) == start)
14
15
#define enQueue(value, buffer, end) \
16
  buffer[end++] = value; \
17
  end %= UART_BUFFER_SIZE;
18
19
#define deQueue(value, buffer, start) \
20
  value = buffer[++start]; \
21
  start %= UART_BUFFER_SIZE;
22
23
ISR(USART_RXC_vect) {
24
  // Store UDR value
25
  if(!bufferFull(rx_start, rx_end))
26
    enQueue(UDR, RXBuffer, rx_end);
27
}
28
29
ISR(USART_UDRE_vect) {
30
  // Send a value
31
  if(!bufferEmpty(tx_start, tx_end)) {
32
    deQueue(uint8_t txn, TXBuffer, tx_start);
33
    UDR = txn;
34
  }
35
}
36
37
void uart_init(uint32_t baud, uint8_t dataDirection) {
38
    uint16_t UBRR_VAL = ((F_CPU + baud * 8UL) / (baud * 16UL) - 1UL);
39
    UBRRH = (uint8_t) (UBRR_VAL >> 8);
40
    UBRRL = (uint8_t) UBRR_VAL;
41
    UCSRB = dataDirection;
42
    UCSRB |= (1 << RXCIE) || (1 << UDRIE);
43
  while ( !( UCSRA & (1<<UDRE)) );
44
  // die kommt an!
45
  UDR = 255;
46
}
47
48
inline void uart_transmit(uint8_t data) {
49
  // hier war die Schleifenlogik verdreht!
50
  while (bufferFull(tx_start, tx_end));
51
  enQueue(data, TXBuffer, tx_end);
52
}
53
54
inline uint8_t uart_receive() {
55
  // hier war die Schleifenlogik verdreht!
56
  while (bufferEmpty(rx_start, rx_end));
57
  deQueue(uint8_t rxn, RXBuffer, rx_start);
58
  return rxn;
59
}

[c]int main() {
  uart_init(115200UL, UART_DD_BOTH);
  sei();
  // die kommt NICHT an!
  uart_transmit(65);
  while ( !( UCSRA & (1<<UDRE)) );
  // die kommt an!
  UDR = 255;
}

Tja, die 65 (Ascii A) ist nirgends zu entdecken :(

Debugger hab ich gerade nicht hier, kann das EclipsePlugin nicht 
anständig (soweit ich weiß)

gez: eine Ente

von (prx) A. K. (prx)


Lesenswert?

Unabhängig von der angesprochenen Problematik: Wenn du in deQueue erst 
"start" inkrementierst, dann über diesen Index Daten rausholst und erst 
dann per Modulo korrigierst, dann kann ein zweifelhafter Zwischenzustand 
entstehen, in den ein Interrupt reinrutschen und die Daten durcheinander 
bringen kann. Hier wäre eine lokale Variable angebracht, die anfangs 
geladen und abschliessend zurück geschrieben wird.

von Dampfente (Gast)


Lesenswert?

A. K. schrieb:
> zweifelhafter Zwischenzustand

Oh, da waren meine Finger wieder schneller als das Hirn - anfangs hatte 
es noch Sinn ergeben, das so zu tun, dann wieder was geändert... joa, 
passiert. Angepasst und vereinheitlicht:
1
#define enQueue(value, buffer, end) \
2
  buffer[end] = value; \
3
  end = ((end + 1) % UART_BUFFER_SIZE);
4
5
#define deQueue(value, buffer, start) \
6
  start = ((start + 1) % UART_BUFFER_SIZE); \
7
  value = buffer[start];

gez: eine Ente

von (prx) A. K. (prx)


Lesenswert?

Ungeschütze Makros sind Falltüren.

Aus
1
#define macro(blabla) \
2
  zeile1; \
3
  zeile2;
4
5
ISR(USART_RXC_vect) {
6
  // Store UDR value
7
  if(!bufferFull(rx_start, rx_end))
8
    macro(blabla);
9
}
wird nämlich
1
ISR(USART_RXC_vect) {
2
  // Store UDR value
3
  if(!bufferFull(rx_start, rx_end))
4
    zeile1;
5
    zeile2;
6
}
also
1
ISR(USART_RXC_vect) {
2
  // Store UDR value
3
  if(!bufferFull(rx_start, rx_end)) {
4
    zeile1;
5
  }
6
  zeile2;
7
}

von Peter D. (peda)


Lesenswert?


von Dampfente (Gast)


Lesenswert?

A. K. schrieb:
> Ungeschütze Makros sind Falltüren.
Okay, das habe ich nun auch noch eingebaut, meine Macros sehen jetzt so 
aus:
1
#define enQueue(value, buffer, end) { \
2
  buffer[end] = value; \
3
  end = ((end + 1) % UART_BUFFER_SIZE); \
4
}
5
6
#define deQueue(value, buffer, start) { \
7
  start = ((start + 1) % UART_BUFFER_SIZE); \
8
  value = buffer[start]; \
9
}
Aber funktionieren tut es immer noch genauso: Nicht.

Peter Dannegger schrieb:
> Beitrag "AVR-GCC: UART mit FIFO"
>
> Peter
Bei diesen kommentarlosen Verweisen auf eigene Threads schellen mir zwar 
regelmäßig die Eigenlob-Alarmglocken, aber ich unterdrücke das mal ;)
Ich habe deine lib gesehen und ausprobiert - und sie macht ihre Arbeit 
nicht besser als meine - und damit ebenso gut wie die lib von Peter 
Fleury.
Vielleicht habe ich alles falsch benutzt? Möglich - aber sie ist sowieso 
nicht 'intuitiv' benutzbar - schon allein den Baudratenteiler muss man 
noch im Hauptprogramm berechnen... :|

Ja, ich habe auf mehreren AVRs (Mega32, Mega16) getestet und damit einen 
Hardwaredefekt ausgeschlossen.

gez: eine Ente.

von Dampfente (Gast)


Lesenswert?

Stelle ich mich hier zu dumm an, um eine Antwort bekommen?
Hat niemand mehr eine Idee?



...oder warum geht's hier scheinbar nicht weiter?
Ich verzweifel hier gerade, Leute :D

gez: eine Ente

von Uwe (de0508)


Lesenswert?

Hallo Dampfente (Gast),

auch wenn Peter kurz auf seinen Lib verwiesen hat, muss ich noch 
anmerken sie funktioniert hervorragend !

Ich habe sie und auch die "Software UART mit FIFO" hier

Beitrag "Re: Software UART mit FIFO"

zusammengetragen.

Über das Studium seiner Quellen kommst Du zu einer Problemlösung und 
Umstellung deiner Programmlogik. Da ist der Fehler.

.

von Krapao (Gast)


Lesenswert?

> ...oder warum geht's hier scheinbar nicht weiter?

Ich verstehe mit deiner Beschreibung nicht, was nicht funktioniert - 1) 
das Interruptbasierte Senden/Empfangen oder 2) der Ringpuffer. Beim "Es 
funktioniert" Fall hattest du ja weder 1) noch 2) benutzt.

1) ist die Voraussetzung und 2) ist die Kür. Daher würde ich zuerst 1) 
implementieren und debuggen (was auch das entsprechende Testprogramm 
liefert) und dann 2). 2) kann man auch mit Polling prüfen.

Deine Variante 2) mit Makros zu machen, erleichtert weder das Debuggen 
von 1) noch von 2). Einen wesentlichen Fallstrick hat A.K. bereits 
gezeigt. Es wäre fürs Debuggen wesentlich einfacher, wenn statt Makros 
Funktionen verwendet würden. Sollte es nötig sein das aus Platz (?) oder 
Performance (?) Gründen als Makros zu machen, kann das später erfolgen.

von Anselm (Gast)


Lesenswert?

Was waren deine Beweggründe, die Ringbuffer Funktion in die
UART lib einzubauen?

von Dampfente (Gast)


Angehängte Dateien:

Lesenswert?

Uwe S. schrieb:
> Hallo Dampfente (Gast),
>
> auch wenn Peter kurz auf seinen Lib verwiesen hat, muss ich noch
> anmerken sie funktioniert hervorragend !
Wie gesagt - vielleicht hab ich auch alles nur falsch benutzt. Ich 
bezweifle das gar nicht. Nur Feinheiten im Stil der lib sagen mir 
persönlich nicht zu; und dass ein "Plug and Play"-Versuch gescheitert 
ist. Mir erschien es da günstiger, meine lib etwas zu erweitern als nach 
einer fertigen Lösung zu suchen, die genau zu mir passt. Vielleicht ist 
das vorhin falsch rübergekommen.

Uwe S. schrieb:
> Über das Studium seiner Quellen kommst Du zu einer Problemlösung und
> Umstellung deiner Programmlogik. Da ist der Fehler.
Vielleicht ist dieser Teil beabsichtigt so schwammig. Ich kann 
jedenfalls versichern, den Code von P.D. eingesehen und zu 95% 
verstanden zu haben (nur AIL und NIL sind mir noch ein Rätsel). 
Insgesamt kann ich nur kleinere Unterschiede zu meinem Code finden.
Da wäre die Inter-AVR-Portierbarkeit, die bei mir nur im Hintergrund 
steht, bei Peter das automatische an/ausschalten der Sender-ISR nach 
Bedarf, und ein paar Speicheroptimierungen mehr, die ich vielleicht 
nachträglich einstricke.

Krapao schrieb:
> Ich verstehe mit deiner Beschreibung nicht, was nicht funktioniert - 1)
> das Interruptbasierte Senden/Empfangen oder 2) der Ringpuffer. Beim "Es
> funktioniert" Fall hattest du ja weder 1) noch 2) benutzt.
Ich hab mal einen kurzen Test gemacht und die UDRE_ISR durch eine 
Senderoutine ersetzt:
1
ISR(USART_UDRE_vect) {
2
  UDR = 65;
3
}
und dann wie gehabt per uart_init() (siehe hier: 
Beitrag "Re: Mal wieder UART mit Ringpuffer.") den Transfer 
angeworfen. AUch im Main-Programm noch per
1
while ( !( UCSRA & (1<<UDRE)) );
2
UDR = 255;
noch ein Zeichen gesendet. Die einzigen Bytes die ankommen, sind die aus 
der main (0xFF) und die aus der uart_init (auch 0xFF).
Offensichtlich ist das IRQ-gesteuerte Senden allein schon nicht möglich.

Krapao schrieb:
> Einen wesentlichen Fallstrick hat A.K. bereits
> gezeigt. Es wäre fürs Debuggen wesentlich einfacher, wenn statt Makros
> Funktionen verwendet würden.
Klingt vernünftig, habe ich getan; leider hat das noch keine Besserung 
gebracht.

Anselm schrieb:
> Was waren deine Beweggründe, die Ringbuffer Funktion in die
> UART lib einzubauen?
Übertragungsrate. Ohne den Puffer kann ich bei größeren Übertragungen 
nur sehr langsam an den µC senden, was bei Dateiübertragungen Richtung 
SD-Karte sehr nervtötend ist. Bsp: Ich will 40 KB abspeichern - ohne 
Puffer krieg ich das bisher in etwa 20min auf die Karte. Mit Puffer ist 
das (theoretisch) in zwei bis drei Sekunden zu schaffen.

gez: eine Ente

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Dampfente schrieb:
> ...oder warum geht's hier scheinbar nicht weiter?
Lothar Miller schrieb:
> Was sagt der Debugger?

Dampfente schrieb:
> Stelle ich mich hier zu dumm an, um eine Antwort bekommen?
Wenn du nur sagst: ich probier die und probier das, aber keine 
Ratschläge annimmst (wie startet man das Senden?) und nicht mal sagst, 
was du machst und was nicht geht, was ERWARTEST du dann?

> Offensichtlich ist das IRQ-gesteuerte Senden allein schon nicht möglich.
Doch. Andere können das. Und der Controller gibt das her. Nur: der Start 
muss händisch angepackt werden. Aber was sage ich. Das steht im 
DATENBLATT (und es wurde wie gesagt hier schon gesagt).

>> Es wäre fürs Debuggen wesentlich einfacher, wenn statt Makros
>> Funktionen verwendet würden.
> Klingt vernünftig, habe ich getan; leider hat das noch keine Besserung
> gebracht.
Und: was ist dabei herausgekommen? Oder ist das geheim?

> Bsp: Ich will 40 KB abspeichern - ohne Puffer krieg ich das bisher
> in etwa 20min auf die Karte. Mit Puffer ist das (theoretisch)
> in zwei bis drei Sekunden zu schaffen.
Warum glaube ich das nicht?
Was hast du so vermurkst, dass da deiner Meinung nach eine derartige 
Steigerung drin ist?

von Uwe (de0508)


Lesenswert?

Hallo,

ich habe Peters Code an wenigen Stellen erweitert und hier ist die 
Senderoutine
1
/*
2
 * UART0 TX
3
 */
4
ISR( USART0_UDRE_vect )
5
{
6
  if( tx_in == tx_out ){    // nothing to sent
7
    UTX0_IEN = 0;      // disable TX interrupt
8
    return;
9
  }
10
  UDR0 = tx_buff[tx_out];
11
  ROLLOVER( tx_out, TX0_SIZE );
12
}
13
14
15
uint8_t utx0_ready( void )
16
{
17
  uint8_t i = tx_in;
18
19
  ROLLOVER( i, TX0_SIZE );
20
  return vu8(tx_out) ^ i;    // 0 = busy
21
}
22
23
24
void uputchar0( uint8_t c )
25
{
26
  uint8_t i = tx_in;
27
28
  ROLLOVER( i, TX0_SIZE );
29
  tx_buff[tx_in] = c;
30
  while( i == vu8(tx_out));    // until at least one byte free
31
          // tx_out modified by interrupt !
32
  vu8(tx_in) = i;
33
  UTX0_IEN = 1;                         // enable TX interrupt
34
}

Der Unterschied zu deinem Code ist
1
UTX0_IEN = 0;      // disable TX interrupt
2
UTX0_IEN = 1;                         // enable TX interrupt

Mach dir bitte klar, warum das dort SO steht.

von Dampfente (Gast)


Lesenswert?

Hallo Lothar,

Sei es mir gegönnt, deinen Beitrag mal zu analysieren - im 
ursprünglichsten Wortsinne!
Analysieren kommt vom griechischen: αναλύσειν, und bedeutet "auflösen" 
;)

Mögen die Spiele beginnen.

Lothar Miller schrieb:
> Wenn du nur sagst: ich probier die und probier das, aber keine
> Ratschläge annimmst
Wann habe ich denn mal einen Ratschlag nicht angenommen? Mal sehen...
1
Lothar Miller schrieb im Beitrag #2400718:
2
> Dir ist schon klar, dass du das erste Zeichen ohne Interrupt senden
3
> musst? Oder was meinst du, wie oft die ISR(USART_UDRE_vect) aufgerufen
4
> wird?
5
Habe ich gemacht. Mehr noch - das Programm sendet ZWEI sinnlose Bytes - die eben auch sinnlos bleiben. Siehe dazu die schon vor gefühlten Jahren gepostete Main-Routine. Ratschlag angenommen.
6
A. K. schrieb im Beitrag #2400758:
7
> Wenn du in deQueue erst
8
> "start" inkrementierst, dann über diesen Index Daten rausholst und erst
9
> dann per Modulo korrigierst, dann kann ein zweifelhafter Zwischenzustand
10
> entstehen
11
Ist eingebaut. Die entsprechende Routine sieht nun so aus:
12
[c]inline uint8_t deQueue(volatile uint8_t buffer[], uint8_t start) {
13
  start = ((start + 1) % UART_BUFFER_SIZE);
14
  return buffer[start];
15
}[/c]
16
Damit habe ich übrigens gleich einen weiteren:
17
Krapao schrieb im Beitrag #2400942:
18
> Es wäre fürs Debuggen wesentlich einfacher, wenn statt Makros
19
> Funktionen verwendet würden. Sollte es nötig sein das aus Platz (?) oder
20
> Performance (?) Gründen als Makros zu machen, kann das später erfolgen.
21
Ratschlag angenommen!
22
A. K. schrieb im Beitrag #2400767:
23
> Ungeschütze Makros sind Falltüren.
24
Huch, ein weiterer Ratschlag, den ich angenommen hatte - das beweist der Zwischenzustand:
25
[c]#define enQueue(value, buffer, end) { \
26
  buffer[end] = value; \
27
  end = ((end + 1) % UART_BUFFER_SIZE); \
28
}
29
30
#define deQueue(value, buffer, start) { \
31
  start = ((start + 1) % UART_BUFFER_SIZE); \
32
  value = buffer[start]; \
33
}[/c]
Soll ich weitermachen? Genug? Fein. Bittebitte, komm mir verdammt noch 
mal nicht so!

Aber... wieder ruhig Blut. Wenn man schon eine Frage an das allwissende 
Internet richtet, muss man ja eigentlich so vorbereitet sein, dass man 
die Antwort eigentlich schon kennt. Also weiter.

Lothar Miller schrieb:
> Doch. Andere können das. Und der Controller gibt das her. Nur: der Start
> muss händisch angepackt werden.
Das ist mir klar. Andere können auch Raketen zum Mond schießen und da 
muss der Start auch "händisch angepackt" werden.
Nur wie, zum Teufel, wie?
Ich habe inzwischen den Code von Peter Dannegger, Peter Fleury und vom 
RN-Wissen gelesen. Nicht zu vergessen den hier im Wiki. Überall stehen 
die gleichen Dinge:
ISR zum Empfangen und zwischenspeichern
ISR zum Senden aus dem Zwischenspeicher
uart_init zum setzen der Baudrate und der Interrupt-Flags, und zum 
sonstigen Initialisieren (Frameformat, Datenrichtungen, blabla.)
Und Methoden für put/get aus dem Zwischenspeicher.

Und bei "Anderen" fügt sich das problemlos zu einem interruptgesteuerten 
USART zusammen. Nur bei mir nicht, da läuft einfach gar nix, und das 
reproduzierbar bei allen AVRs, die hier rumliegen.

Lothar Miller schrieb:
> Aber was sage ich. Das steht im DATENBLATT.
Das Datenblatt ist ja wohl die erste Anlaufstelle für sowas, aber danke 
für den Verweis. Ich habe die Seiten 140 bis 165 jetzt ein weiteres Mal 
gelesen, aber es war nicht so unterhaltsam wie ein gutes Buch. Spaß 
beiseite. Dort steht nix, was mir hilft. Trotzdem danke.

Lothar Miller schrieb:
> Und: was ist dabei herausgekommen? Oder ist das geheim?
Am Kopf eines jeden Postings ist ein kleiner Bereich für angehängte 
Dateien. Manchmal stehen da auch welche - wie hier: 
Beitrag "Re: Mal wieder UART mit Ringpuffer."
Wenn du den Debugger meinst - was der ausgespuckt hat? Nix: Die ISRs 
werden  nie durchlaufen. Wo hab ich das noch gleich erwähnt? Genau, 
hier: Beitrag "Re: Mal wieder UART mit Ringpuffer." - ab Absatz 3.

Lothar Miller schrieb:
> Warum glaube ich das nicht?
> Was hast du so vermurkst, dass da deiner Meinung nach eine derartige
> Steigerung drin ist?
Ich glaube, das interessiert hier niemanden. Wie wir schon überaus 
wissenschaftlich korrekt festgestellt haben, tu ich ja nix anderes, als 
dies und das zu probieren und bin vollkommen beratungsresistent.

Danke, Ente.
PS: Solche Beiträge wie Deiner sind nett, um Neulinge zu verscheuchen, 
die nicht in der Lage sind zu recherchieren. Aber überleg vorher bitte 
zweimal, ob die Zielperson auch in dieses Register passt.
Ich bin zwar kein Vollprofi, aber eben auch kein Vollpfosten.

von Dampfente (Gast)


Lesenswert?

Uwe S. schrieb:
> Mach dir bitte klar, warum das dort SO steht.

Ich bin Peters Code schon durchgegangen, und seine SBIS-Methode auf 
einzelne Bits lesend UND schreibend zuzugreifen finde ich syntaktisch 
sehr elegant. Bei Gelegenheit übernehme ich das mal und passe es meinen 
Paradigmen an.

Zur Semantik: Hier wird das Bit UDRIE im UCSRB-Register gelöscht bzw. 
gesetzt. Der TX-Interrupt wird ja nur gebraucht, wenn etwas im 
Sendepuffer ist, also kann er bei leerem Sendepuffer abgeschaltet 
werden. Soweit klar.

Meine uart_init schaltet den TX-Interrupt übrigens direkt an - sollte 
aber nach meinem Verständnis keinen Unterschied machen - wenn das 
aktuelle Primärziel ist: "Übertrage mit Interrupt ein Byte zum Rechner"
1
void uart_init(uint32_t baud, uint8_t dataDirection) {
2
  uint16_t UBRR_VAL = ((F_CPU + baud * 8UL) / (baud * 16UL) - 1UL);
3
  UBRRH = (uint8_t) (UBRR_VAL >> 8);
4
    UBRRL = (uint8_t) UBRR_VAL;
5
    UCSRB = dataDirection;
6
    UCSRB |= (1 << RXCIE) || (1 << UDRIE);
7
  while ( !( UCSRA & (1<<UDRE)) );
8
  UDR = 255;
9
}

gez: eine Ente

von Uwe (de0508)


Lesenswert?

Hallo Dampfente,

du hast es leide noch nicht gesehen, der Trick ist beim Senden immer das 
ISR-Bit zu setzen, so wird immer wieder ein stehende TX EMPTY ISR neu 
gestartet und sie läuft, bis der Puffer leer ist.
Das Erste Zeichen kommt so auch aus dem Puffer und wird nicht per HAND 
gesendet.

Nochmal:
Die ISR beendet sich selbst, wenn keine Zeichen mehr im Puffer sind.

Trigger:
Sie wird durch jedes Zeichen, das man in den Puffer schreibt 
angestossen.


Klar?

von Uwe (de0508)


Lesenswert?

Hier noch der Auszug aus der anderen Lib von Peter Fleury.
Er setzt die selben ISR BITs, also sollte es doch so gehen.
Die Programmlogik ist die Selbe!
1
/*
2
Title:    Interrupt UART library with receive/transmit circular buffers
3
Author:   Peter Fleury <pfleury@gmx.ch>   http://jump.to/fleury
4
*/
5
6
SIGNAL(UART0_TRANSMIT_INTERRUPT)
7
/*************************************************************************
8
Function: UART Data Register Empty interrupt
9
Purpose:  called when the UART is ready to transmit the next byte
10
**************************************************************************/
11
{
12
    unsigned char tmptail;
13
14
    
15
    if ( UART_TxHead != UART_TxTail) {
16
        /* calculate and store new buffer index */
17
        tmptail = (UART_TxTail + 1) & UART_TX_BUFFER_MASK;
18
        UART_TxTail = tmptail;
19
        /* get one byte from buffer and write it to UART */
20
        UART0_DATA = UART_TxBuf[tmptail];  /* start transmission */
21
    }else{
22
        /* tx buffer empty, disable UDRE interrupt */
23
        UART0_CONTROL &= ~_BV(UART0_UDRIE);
24
    }
25
}
26
27
/*************************************************************************
28
Function: uart_putc()
29
Purpose:  write byte to ringbuffer for transmitting via UART
30
Input:    byte to be transmitted
31
Returns:  none          
32
**************************************************************************/
33
void uart_putc(unsigned char data)
34
{
35
    unsigned char tmphead;
36
37
    
38
    tmphead  = (UART_TxHead + 1) & UART_TX_BUFFER_MASK;
39
    
40
    while ( tmphead == UART_TxTail ){
41
        ;/* wait for free space in buffer */
42
    }
43
    
44
    UART_TxBuf[tmphead] = data;
45
    UART_TxHead = tmphead;
46
47
    /* enable UDRE interrupt */
48
    UART0_CONTROL    |= _BV(UART0_UDRIE);
49
50
}/* uart_putc */

von (prx) A. K. (prx)


Lesenswert?

Dampfente schrieb:

> Meine uart_init schaltet den TX-Interrupt übrigens direkt an

Viele Interrupt-Flags beim AVR setzen sich mit Aufruf der ISR 
automatisch zurück. Nicht alle. Der UDRE-Interrupt nicht. Was also 
geschieht, wenn in der ISR der Puffer leer ist?

von Dampfente (Gast)


Lesenswert?

Uwe S. schrieb:
> der Trick ist beim Senden immer das
> ISR-Bit zu setzen, so wird immer wieder ein stehende TX EMPTY ISR neu
> gestartet und sie läuft, bis der Puffer leer ist.
Eigentlich ist das ja sachlich falsch, da in der ISR keine Schleife 
benutzt wird um aus dem Puffer zu senden, sondern zeichenweise gesendet 
wird, da die ISR aufgerufen wird, sobald der HW-Sendepuffer leer/bereit 
ist.
Im Endeffekt läuft sie aber natürlich so wie du es beschreibst ;)

Vielleicht bringts ja was...?
1
void uart_init(uint32_t baud, uint8_t dataDirection) {
2
  uint16_t UBRR_VAL = ((F_CPU + baud * 8UL) / (baud * 16UL) - 1UL);
3
  UBRRH = (uint8_t) (UBRR_VAL >> 8);
4
  UBRRL = (uint8_t) UBRR_VAL;
5
  UCSRB = dataDirection;
6
  UCSRB |= (1 << RXCIE);
7
  while ( !( UCSRA & (1<<UDRE)) );
8
  UDR = 255;
9
}
10
11
inline void uart_transmit(uint8_t data) {
12
  while (bufferFull(tx_start, tx_end));
13
  enQueue(data, TXBuffer, tx_end);
14
  UCSRB |= (1 << UDRIE);
15
}
Nein. Keine Änderung. Die manuell gesendeten Daten aus der init-Routine 
und dem Mainprogramm kommen an, aber ein uart_transmit(65); bleibt 
stumm.

Ich habe sehr wohl verstanden, wie das prinzipiell funktioniert: Der 
Interrupt wird solange ausgelöst, wie das UDR-Register bereit ist, Daten 
aufzunehmen. Voraussetzung: Interrupts sind per sei(); aktiviert und das 
UDRIE-Bit ist auf 1.
Wenn man dann in UDR schreibt, die ISR solange NICHT mehr ausgelöst, bis 
das UDR bereit ist, das nächste Byte aufzunehmen.

sei(); rufe ich im main auf, und das UDRIE-Bit ist definitiv gesetzt. 
Also muss wohl der HW-Sendepuffer schuld sein, aber auf den warte ich ja 
(mehrfach), indem ich Bytes manuell sende. Also muss der auch bereit 
sein, ergo MUSS meine ISR eigentlich auslösen - wenn man dem Datenblatt 
trauen darf.

Tut sie aber nicht. Warum? Das frage ich euch gerade, ich weiß es auch 
nicht.

A. K. schrieb:
> Viele Interrupt-Flags beim AVR setzen sich mit Aufruf der ISR
> automatisch zurück. Nicht alle. Der UDRE-Interrupt nicht. Was also
> geschieht, wenn in der ISR der Puffer leer ist?
Die ISR wird immer wieder aufgerufen, was sehr ineffizient wäre. 
Theoretisch. Praktisch passiert bei mir NICHTS.

Uwe S. schrieb:
> Er setzt die selben ISR BITs, also sollte es doch so gehen.
> Die Programmlogik ist die Selbe!
Jopp, das denke ich auch die ganze Zeit....

gez: eine Ente

von Uwe (de0508)


Lesenswert?

Hi,

es ist schon spät, hier ist noch ein Fehler:
1
inline void enQueue(uint8_t value, volatile uint8_t buffer[], uint8_t end) {
2
  buffer[end] = value;
3
  end = ((end + 1) % UART_BUFFER_SIZE);
4
}

end kann man so nicht ändern !
Der Sichtbarkeitsbereicht liegt nur innerhalb der KLAMMERN { }
Stichwort:
end wird der Wert CALL BY VALUE übergeben.

Das trifft natürlich auch auf alle anderen dieser inline .. zu.

Vieleicht magst du ein MACOO anstatt der Funktion ?

von (prx) A. K. (prx)


Lesenswert?

Uwe S. schrieb:

> Vieleicht magst du ein MACOO anstatt der Funktion ?

Ich könnte mir vorstellen, dass er sich nun etwas
veräppelt vorkommt. ;-)

von (prx) A. K. (prx)


Lesenswert?

Dampfente schrieb:

> Die ISR wird immer wieder aufgerufen, was sehr ineffizient wäre.
> Theoretisch. Praktisch passiert bei mir NICHTS.

Woran ist dieses "nichts" erkennbar?

von Uwe (de0508)


Lesenswert?

Guten Morgen A. K. - die Zeit vergeht...

Nun ich habe nichts falsches geschrieben, nur die Lese und Schreibzeiger 
lassen sich so nicht verändern..

Damit läuft der Puffer nicht und alles STEHT. Das hatten wir schon 
gelesen.

von (prx) A. K. (prx)


Lesenswert?

Uwe S. schrieb:

> Nun ich habe nichts falsches geschrieben, nur die Lese und Schreibzeiger
> lassen sich so nicht verändern..

Klar, nur hatte er anfangs Makros verwendet, dann wurde ihm oben der Tip 
gegeben, Funktionen statt Makros zu verwenden und nun rätst du ihm, 
Makros statt Funktionen zu verwenden. ;-)

von Uwe (de0508)


Lesenswert?

Aber nicht von mir, dann sollte man sich als Programmierer auch die 
Seiteneffekte bewust machen. In diesem Fall gibt es keine.

Mir ist das auch erste durch das DRÜBERSEHEN der Dateien im ZIP File 
aufgefallen.

Vielleicht sendet Dampfente noch mal alles und man könnte morgen alle 
weiteren Fehler finden.

Bei den Codestückchen schaue ich auch nicht durch, was nun verwendet 
wird oder wurde.
.

von Dampfente (Gast)


Lesenswert?

Uwe S. schrieb:
> Vieleicht magst du ein MACOO anstatt der Funktion ?

Danke, genau daher kommt der Fehler auch, es war bis vor Kurzem ein 
Makro ;)
Krapao schrieb:
> Es wäre fürs Debuggen wesentlich einfacher, wenn statt Makros
> Funktionen verwendet würden. Sollte es nötig sein das aus Platz (?) oder
> Performance (?) Gründen als Makros zu machen, kann das später erfolgen.

Ich hab jetzt mal einen Pointerzugriff draus gemacht... µC meldet: "A".

Yeah. Natürlich! Er hat den Buffer gefüllt, aber da sich die Indexe 
nicht verändert haben, glaubte er hinterher wieder der Buffer wäre leer. 
Das war der Fehler.

Danke an Uwe S.!
Die uartlib schreibe ich morgen so fertig, dass sie ordentlich ist und 
etwas mehr speicheroptimiert - Dann veröffentliche ich das Ding hier.

Gute Nacht an alle!

gez: eine Ente

von Peter D. (peda)


Lesenswert?

Dampfente schrieb:
> (nur AIL und NIL sind mir noch ein Rätsel).

Alwas-In-Line bzw. Never-In-Line.

Der AVR-GCC ist etwas störrisch, was das Inlining angeht.
Er möchte kleine Funktionen inlinen, auch wenn das für die Codegröße 
kontraproduktiv ist.
Andererseits ist bei größeren Funktionen das einfache "inline" nicht 
stark genug, wenn man wirklich inlinen will.

Daher habe ich AIL/NIL definiert, um ihm meinen Willen aufzuzwingen.
Man kann aber auch den Compilerschalter "-fno-inline-small-functions" 
benutzten, dann braucht man NIL nicht.


Peter

von Dampfente (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Er möchte kleine Funktionen inlinen, auch wenn das für die Codegröße
> kontraproduktiv ist.
> Andererseits ist bei größeren Funktionen das einfache "inline" nicht
> stark genug, wenn man wirklich inlinen will.
Aha. War mir gar nicht so klar - hab das nur mal irgendwo als Randnotiz 
gelesen...

Wie benutzt man diese Defines denn dann? Im Code sind sie nirgends 
verwendet, bloß definiert:
1
#define AIL(x)   static x __attribute__ ((always_inline)); static x
2
#define NIL(x)   x __attribute__ ((noinline)); x

Wenn ich jetzt ein void myFunc(void) { blabla(); } habe, wo kommmt dann 
AIL bzw. NIL hin?

gez: eine Ente

von Uwe (de0508)


Lesenswert?

Hallo Dampfente,

was Peter mit den beiden Macros zur Verfügung stellt, ist eine 
Möglichkeit, schnell eine bestimmte Funktion als Inline-Funktion zu 
definieren.

Wann man das eine oder andere Macro anwendet oder liegt deshalb bei dir.

Ich bin dazu übergegangen alle Funktionen, die man auch Inlinen könnte, 
als "static" zu deklarieren.

Des weiteren steht in meinen Makefile:

CFLAGS += -Wall -Wstrict-prototypes
CFLAGS += -fno-inline-small-functions
CFLAGS += -fno-move-loop-invariants

Bei kleinen AVR auch mal
## !!Danach hat man nur noch 256 Byte Stack !!
CFLAGS += -mtiny-stack

von theVoid (Gast)


Lesenswert?

Uwe S. schrieb:
> Hallo Dampfente,
>
> was Peter mit den beiden Macros zur Verfügung stellt, ist eine
> Möglichkeit, schnell eine bestimmte Funktion als Inline-Funktion zu
> definieren.

Jo, das hab ich so weit :)

Meine Frage bezog sich eher darauf, WIE man die Makros anwendet - nicht 
WANN :)

gez: eine Ente

von Uwe (de0508)


Lesenswert?

Wenn man das Macros expandiert, so folgt:
1
AIL(void func_ich_bin_inline(uint8_t para1, uint8_t para2 ) )
2
{
3
// code
4
// :
5
}

von Dampfente (Gast)


Lesenswert?

Uwe S. schrieb:
> Wenn man das Macros expandiert, so folgt:
> AIL(void func_ich_bin_inline(uint8_t para1, uint8_t para2 ) )
> {
> // code
> // :
> }

Danke, Uwe S.!

gez: eine Ente

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.