Forum: Mikrocontroller und Digitale Elektronik Ringbuffer aus Codevision


von Thomas M. (Gast)


Lesenswert?

Hallo zusammen,

ich versuche gerade mich in das Ringbufferprinzip einzuarbeiten.
Ich benutze Codevision, und hieraus die Ringbufferfunktionen des
Programmwizzard.
Über Studio4 und JTAG-AVR progge ich einen mega128.

Meine Codeschnipsel:

Das was in main.h steht:

#define RXB8 1
#define TXB8 0
#define UPE 2
#define OVR 3
#define FE 4
#define UDRE 5
#define RXC 7

#define FRAMING_ERROR (1<<FE)
#define PARITY_ERROR (1<<UPE)
#define DATA_OVERRUN (1<<OVR)
#define DATA_REGISTER_EMPTY (1<<UDRE)
#define RX_COMPLETE (1<<RXC)

// USART0 Receiver buffer
#define RX_BUFFER_SIZE0 16
char rx_buffer0[RX_BUFFER_SIZE0];

char rx_wr_index0,rx_rd_index0,rx_counter0;
// This flag is set on USART0 Receiver buffer overflow
bit rx_buffer_overflow0;



Das was in main.c steht:

// USART0 Receiver interrupt service routine
interrupt [USART0_RXC] void usart0_rx_isr(void)
{
char status,data;
status=UCSR0A;
data=UDR0;
if ((status & (FRAMING_ERROR | PARITY_ERROR | DATA_OVERRUN))==0)
   {
   rx_buffer0[rx_wr_index0]=data;
   if (++rx_wr_index0 == RX_BUFFER_SIZE0) rx_wr_index0=0;
   if (++rx_counter0 == RX_BUFFER_SIZE0)
      {
      rx_counter0=0;
      rx_buffer_overflow0=1;
      };
   };
}


#ifndef DEBUG_TERMINAL_IO
// Get a character from the USART0 Receiver buffer
#define ALTERNATE_GETCHAR
#pragma used+
char getchar(void)
{
char data;
while (rx_counter0==0);
data=rx_buffer0[rx_rd_index0];
if (++rx_rd_index0 == RX_BUFFER_SIZE0) rx_rd_index0=0;
#asm("cli")
--rx_counter0;
#asm("sei")
return data;
}
#pragma used-
endif


Die einzelnen Arbeitsweisen von der Interruftroutine und von
getchar() sind mir klar.
Aber das Zusammenspiel versteh ich nicht.

Meine Frage ist:  getchar() wird zu einer alternativen Funktion, aber
was geschiet damit?
Wie komme ich an meine Daten die im Ringbuffer stehen?


Kann man mir das mit wenigen Worten erklären?

Vielen Dank   Thomas

von Aleksej (Gast)


Lesenswert?

Wenn du einen normalen Ringbuffer verwendest, sollst du mit dem Lesen
oder Schreiben immer am Anfang des Buffers starten und dann bis zum
Ende gehen. Das heisst, das du ganz am Ende des Buffers nur knappe
Bytes zur Verfuegung hast, obwohl fast der ganze Buffer schon nicht
mehr besetzt ist. Wenn du dagegen einen Ringbuffer nimmst, dann kannst
du irgendwo am Ende des Buffers lesen und gleichzeitig am Anfang was
schreiben. Natuerlich ist es ein gewaltiges Vorteil.
Nachteile: du brauchst schon mal nicht 2, sondern 3 Variablen - Index
fuer das Lesen, Index fuer das Schreiben und eine Variable fuer die
Anzahl der Bytes, die du noch nicht gelesen hast. Und da kommt noch
eine Verifizierung dazu - du sollst aufpassen, dass dein Buffer nicht
ueberfluttet wird.
Im Prinzip funktioniert es so - du schreibst bis zum Ende des Buffers
und liest gleichzeitig deine Daten aus, dann erreichst du mit dem
Schreiben das Ende, wenn es ein normaler Buffer waere, dann ist es
schon aus, aber jetzt hast du noch bisschen Platz am Anfang des
Buffers, weil du einige Daten schon mal gelesen hast.

von Stefan (Gast)


Lesenswert?

Ich habe noch eine zusätzliche Variable eingefügt.


Sowie etwas im Ringpuffer steht wird diese Variable gesetzt.
Anhand dieser Variable kann man feststellen ob man getchar() ausführen
muss.
Erst wenn der Buffer wieder leer ist wir die Variable zurück gesetzt.
1
// USART0 Receiver interrupt service routine
2
interrupt [USART0_RXC] void usart0_rx_isr(void)
3
{  
4
char status,data;
5
status=UCSR0A;
6
data=UDR0;
7
if ((status & (FRAMING_ERROR | PARITY_ERROR | DATA_OVERRUN))==0)
8
   {
9
   rx_buffer0[rx_wr_index0]=data;
10
   if (++rx_wr_index0 == RX_BUFFER_SIZE0) rx_wr_index0=0;
11
   if (++rx_counter0 == RX_BUFFER_SIZE0)
12
      {
13
      rx_counter0=0;
14
      rx_buffer_overflow0=1;
15
      };
16
17
  ZEICHEN_IM_BUFFER = 1;
18
19
 };
20
} 
21
22
#ifndef _DEBUG_TERMINAL_IO_
23
// Get a character from the USART0 Receiver buffer
24
#define _ALTERNATE_GETCHAR_
25
#pragma used+
26
char getchar(void)
27
{
28
char data;
29
while (rx_counter0==0);
30
data=rx_buffer0[rx_rd_index0];
31
if (++rx_rd_index0 == RX_BUFFER_SIZE0) rx_rd_index0=0;
32
#asm("cli")
33
--rx_counter0;
34
#asm("sei")
35
36
if (rx_counter==0)
37
  ZEICHEN_IM_BUFFER = 0;
38
39
return data;
40
}
41
#pragma used-
42
endif

Man hat dann zwar wieder nen Polling.
Allerdings hat man den Vorteil bei seltener Übertragung, dass man das
Bit nur recht selten abfragen muss.
Durch den Ringpuffer gehen die Daten ja erst mal nicht verloren.

von Thomas M. (Gast)


Lesenswert?

@Aleksej
Das was ich habe ist ja ein endlos Buffer mit den drei, von dir
beschriebenen Variablen.


@Stefan
Muss ich wirklich die neue Variable ZEICHEN_IM_BUFFER benutzen?
Reicht es nicht aus , wenn ich rx_counter abfrage?
Bei rx_counter != 0 sind noch ungelesene Zeichen im Buffer.

von Stefan K. (_sk_)


Lesenswert?

@Stefan:
Ganz verstehe ich Deine Variable ZEICHEN_IM_BUFFER nicht. Was bringt
Dir diese, was Dir die Abfrage von rx_counter0 nicht schon bringt? Um
auf Zeichen im Buffer zu testen, reicht doch

  if (rx_counter0){
  }

Übrigens hast Du in Deiner Abfrage einen kleinen Schönheitsfehler:

  if (rx_counter==0)
    ********************** wenn hier ein RX-Interrupt auftritt, gibt
    ********************** es Probleme!
    ZEICHEN_IM_BUFFER = 0;

Angenommen, ein Zeichen steht im Puffer. Das holst Du mit getchar ab
und machst --rx_counter0.
Dann fragst Du (rx_counter0 == 0) (== TRUE) und setzt ZEICHEN_IM_BUFFER
= 0.
Wenn zwischen diesen Zeilen ein RX_IR auftritt, wird ein Zeichen in den
Buffer geschrieben, rx_counter0 erhöht und ZEICHEN_IM_BUFFER = 1
gesetzt.

Merkst Du was? Danach steht
ZEICHEN_IM_BUFFER == 0 und
rx_counter0 == 1

Wenn Du wirklich ZEICHEN_IM_BUFFER verwenden willst, dann schreibe:

  #asm("cli")
  if (rx_counter==0)
    ZEICHEN_IM_BUFFER = 0;
  #asm("sei")

Gruß, Stefan

von Stefan (Gast)


Lesenswert?

Alles klar.

Sehe ich ein.
Hatte bisher zwar noch keine Probleme werde ich aber beim nächsten mal
berücksichtigen.

von peter dannegger (Gast)


Lesenswert?

Man braucht doch nur 2 Pointer, bzw. Indexe.
Sind sie gleich, ist nichts im Puffer.
1
char rx_buf[256];
2
unsigned char rx_tail, rx_head;
3
4
5
char kbhit(void)
6
{
7
  return rx_head != rx_tail;
8
}
9
10
11
char getchar(void)
12
{
13
  char c;
14
15
  while( !kbhit() );
16
17
  c = rx_buf[rx_tail];
18
  ++rx_tail;
19
  return c;
20
}

In diesem Beispiel ist der Index rx_tail ein unsigned char und der
Puffer genau 256 Byte groß. D.h. ganz ohne Überlauftest rollt der
Puffer über.


Peter

von peter dannegger (Gast)


Lesenswert?

P.S.:

Und wenn der Puffer nur 32Byte groß sein soll, auch kein Problem:
1
rx_tail = (rx_tail + 1) & 0x1F;

Ein Undierung ist codesparender und schneller als ein bedingter
Sprung.

Daher werden gerne 2-er Potenzen als Puffergrößen genommen.


Peter

von Mario G. (mario)


Lesenswert?

@peter:
ich interessiere mich auch gerade für eine Ringbuffer-Implementation.
kannst du bitte mal deinen Rountine kbhit() erläutern. rx_head ist
sicherlich der Lesezeiger, oder?


Mario

von Rahul (Gast)


Lesenswert?

@Mario: Falsch geraten...
rx_head ist der Schreibzeiger.
Kann man sich relativ leicht an einer Eselsbrücke mit einem Hund
merken:
Der Schwanz (tail) folgt dem Kopf (head).
Sobald der tail den head eingeholt hat, ist der Hund 0 Elemente
groß...
"rx_head" wird in der Empfangs-Interrupt-Routine geschrieben.

Irgendwo hat hier auch jemand ein paar Makros zu dem Thema gepostet...

@Peter: durch rx_tail++ % 100 könnte man einen 100 Elemente grossen
Ringspeicher produzieren, oder?

von Michael (Gast)


Lesenswert?

...durch rx_tail++ % 100 könnte man...

Könnte man machen, ist aber nicht schön, da eine Division notwendig
wird. Da ist ein if(x >= y) schneller.

Neben den Schreib- Lesezeigern auch noch die Anzahl der Zeichen im
Puffer mitzuführen hat dann Vorteile, wenn z.B. Xon-Xoff Protokoll
verwendet wird, welches erst bei fast vollem Puffer wirksam werden
soll. Ferner kann man vor der Ausgabe testen, ob die auszugebenden
Zeichen komplett in den Puffer passen oder nicht. Bei Zustandsautomaten
(state machine) kann es besser sein, die Ausgabe zunächst
zurückzustellen, bis der Puffer hinreichend entleert wurde. Anderfalls
hängt das Programm in der Ausgabe fest, bis alle Zeichen übergeben
werden konnten.

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.