Forum: Mikrocontroller und Digitale Elektronik Konzept serieller Datenempfang


von Heinz B. (hez)


Lesenswert?

hello,

ich programmiere mir gerade eine serielle Schnittstelle.

Habe einen 80C517A und verwende die S1. Den Interrupthandler habe ich 
schon grundsätzlich. Ich programmiere im uVision in C.
Jetzt geht es darum, wie die Daten gebuffert werden sollen.

Habe mir folgendes vorgestellt:

Ich habe im Programm ein Array data.

Werden im uC Daten empfangen, wird ein Array line angelegt.
Von SBUF werden Byte für Byte eingelesen und in line abgelegt.

z. B. übertragen a b c \n
in line sind dann:
[0]=a [1]=b [2]=c
\n wird nicht abgespeichert

Sobald \n gelesen wird, kommt das array line in das array data.

z. B.
data[0]:
[0]=a [1]=b [2]=c

Kommen dann wieder Daten rein, passiert das gleiche wieder

z. b. übertragen x y z \n
in line:
[0]=x [1]=y [2]=z

und in data:
data[0]:
[0]=a [1]=b [2]=c
data[1]:
[0]=x [1]=y [2]=z

Das Hauptprogramm läuft in einer Endlosschleife und schaut regelmäßig 
nach, ob in data etwas steht.
Immer den ältesten Eintrag nimmt es dann zuerst heraus und verarbeitet 
ihn. Wäre also ein FIFO.

Funktioniert das? Macht man das so richtig?

von Falk B. (falk)


Lesenswert?

@ Heinz B. (hez)

Funktioniert das? Macht man das so richtig?

Jain. Einige Detail scheinen noch nicht ganz stimmig zu sein. Ein gutes 
Beispiel wie man es machen könnte findest du im Artikel Interrupt.

MFG
Falk

von Olaf (Gast)


Lesenswert?

> Funktioniert das? Macht man das so richtig?

Ja, so im groben funktioniert das. Ich mache es auch immer
mit einer Fifo wenn die Daten das erlauben, man also nicht
unmittelbar auf ein Byte sofort reagieren muss.

Du benoetigst im Prinzip vier Funktionen.

fifo_init
fifo_put
fifo_get
fifo_anzahl

Klugerweise implementiert man den Fifo als Ringbuffer. Das hat
den Vorteil das nur Daten verloren gehen wenn du mal keine Zeit
hast, es aber nicht zum Supergau kommt.

Dabei gibt es etwas zu beachten. Die Funktion zum schreiben von
Daten rufst du nur im IRQ auf, und die zum lesen nur ausserhalb!
Es kann dir aber passieren das du bereits neue Daten reinschreibst
und den Zeiger inkrementierst waerend du ausserhalb gerade mittem
im Lesezugriff bist. Du musst dann beim lesen und dekrementieren
der Daten kurz den IRQ fuer die RS232 sperren.


Olaf

von Heinz B. (hez)


Lesenswert?

http://www.mikrocontroller.net/articles/Interrupt

Habe mir das mal schnell angeschaut. Kenn mich eigentlich vorn und 
hinten nicht aus, aber ich glaube, ich konnte so ungefähr das Prinzip 
herauslesen.
Wenn ich das richtig verstanden habe, gibt es in diesem Beispiel nur 
einen einzigen Buffer. Dieser Buffer kann aus einer bestimmten Anzahl 
von chars bestehen. Wird eine Übertragung mit \r abgeschlossen, gilt 
dieser eine Buffer als voll.

>>Klugerweise implementiert man den Fifo als Ringbuffer

So etwas finde ich besser. Allerdings habe ich null Ahnung, wie man so 
etwas programmieren könnte. :-/

>> Es kann dir aber passieren das du bereits neue Daten reinschreibst
>> und den Zeiger inkrementierst waerend du ausserhalb gerade mittem
>> im Lesezugriff bist.

Verstehe das Problem nicht. Ich hätte mir so etwas vorgestellt:

Ich bilde ein Array. Kommen Daten rein, lege ich Zeichen für Zeichen in 
das Array ab. Erst bei einem \n kommt dieses Array in den Ringbuffer. 
Das Hauptprogramm greift nur auf den Ringbuffer zu. Das sollte dann 
keine Probleme verursachen, oder???

von Karl H. (kbuchegg)


Lesenswert?

Heinz B. schrieb:
> http://www.mikrocontroller.net/articles/Interrupt
>
> Habe mir das mal schnell angeschaut. Kenn mich eigentlich vorn und
> hinten nicht aus, aber ich glaube, ich konnte so ungefähr das Prinzip
> herauslesen.
> Wenn ich das richtig verstanden habe, gibt es in diesem Beispiel nur
> einen einzigen Buffer. Dieser Buffer kann aus einer bestimmten Anzahl
> von chars bestehen. Wird eine Übertragung mit \r abgeschlossen, gilt
> dieser eine Buffer als voll.

Das hast du richtig herausgelesen.
Aber das im Artikel beschriebene ist auch nicht wirklich ein Ringbuffer.

>>>Klugerweise implementiert man den Fifo als Ringbuffer
>
> So etwas finde ich besser. Allerdings habe ich null Ahnung, wie man so
> etwas programmieren könnte. :-/

unsigned char Buffer[20];
unsigned char WriteIndex;

void PutIntoBuffer( unsigned char Byte )
{
  Buffer[WriteIndex++] = Byte;
  if( WriteIndex == 20 )
    WriteIndex = 0;
}

Stell dir das Buffer Array als zum geschlossenen Kreis gebogen vor. Du 
hast dann die Eigenschaft: Bist du am Ende angelangt, fängst du einfach 
wieder von vorne an.

Analogie:
Ein 'Zifferblatt', wo du an jeder Minutenposition einen Wert speichern 
kannst. Dazu einen 'Zeiger', der dir sagt, wo der nächste Wert 
gespeichert werden muss. Kommt ein Wert schreibst du den Wert an die 
Position auf die der Zeiger weist und drehst den Zeiger um 1 Position 
weiter.

Natürlich brauchst du auch noch einen 2-ten Zeiger, der dir sagt von wo 
gelesen werden kann. Liest du einen Wert, so wird auch dieser Zeiger um 
1 Position weitergedreht.

Und jetzt kann man sich natürlich überlegen, was einem die 
Zeigerpositionen mitteilen in Bezug auf die Fragestellungen
* Ist noch Platz im Buffer oder überschreibe ich mir noch nicht gelesene 
Daten
* Ist überhaupt noch etwas im Buffer enthalten, was noch nicht gelesen 
wurde

>>> Es kann dir aber passieren das du bereits neue Daten reinschreibst
>>> und den Zeiger inkrementierst waerend du ausserhalb gerade mittem
>>> im Lesezugriff bist.
>
> Verstehe das Problem nicht.

Wenn du die Reihenfolge des schreibens und Zeiger weiterstellens richtig 
machst und das Zeiger weiterstellen atomar von statten gehen könnte, 
wäre es auch kein Problem.

> Ich bilde ein Array. Kommen Daten rein, lege ich Zeichen für Zeichen in
> das Array ab. Erst bei einem \n kommt dieses Array in den Ringbuffer.
> Das Hauptprogramm greift nur auf den Ringbuffer zu. Das sollte dann
> keine Probleme verursachen, oder???

Zu kompliziert und es löst das Problem des atomaren Weiterschaltens des 
Zeigers nicht.

von Heinz B. (hez)


Lesenswert?

>> unsigned char Buffer[20];
>> unsigned char WriteIndex;
>> void PutIntoBuffer( unsigned char Byte )
>> {
>>   Buffer[WriteIndex++] = Byte;
>>   if( WriteIndex == 20 )
>>     WriteIndex = 0;
>> }
Coole Sache. Schaut gut aus. Habe schon befürchtet, da muss man was mit 
Zeigern zaubern.

>> Zu kompliziert
Ich will jetzt nicht so klingen, dass das ein leichtes wäre ... habe ja 
fast 0 Ahnung vom C ... aber wie gehts denn noch einfacher??

>> und es löst das Problem des atomaren Weiterschaltens des
>> Zeigers nicht.
Das habe ich leider immer noch nicht verstanden. Ich sehe das Problem 
nicht.

von Falk B. (falk)


Lesenswert?

@Heinz B. (hez)

>Das habe ich leider immer noch nicht verstanden. Ich sehe das Problem
>nicht.

Dann lies mal das.

http://www.mikrocontroller.net/articles/Interrupt#Atomarer_Datenzugriff

MFG
Falk

von Heinz B. (hez)


Lesenswert?

Mir ist schon klar, was "atomar" bedeutet. Nur finde ich kein Problem 
darin, wenn ich - wie schon beschrieben - folgendermaßen vorgehe:
Zuerst alle seriellen Daten einer Übertragung (bis zu einem \n) in einem 
Array line sammeln und dann beim Empfang eines \n in einem Schlag dem 
Array data übergeben.

Wenn ich sage:
data[xy}=line
Ist das nicht atomar?

von Grrrr (Gast)


Lesenswert?

Heinz B. schrieb:
> Wenn ich sage:
> data[xy}=line
> Ist das nicht atomar?

Nein.
Zitat:
Von einem atomaren (engl. atomic) Datenzugriff spricht man, wenn der 
Zugriff innerhalb einer nicht unterbrechbaren Maschinenanweisung 
abgearbeitet wird.

von Karl H. (kbuchegg)


Lesenswert?

Heinz B. schrieb:

> einem Schlag dem
> Array data übergeben.

Genau da liegt der Pferdefuss.
Du kannst das eben nicht 'in einem Schlag' machen. Da sind auf 
Assemblerebene viele Operationen beteiligt. Und zwischen je 2 dieser 
Operationen kann dir ein Interrupt reinknallen.

> Wenn ich sage:
> data[xy}=line
> Ist das nicht atomar?

Wenn wir hier von strings reden: Nein das ist nicht atomar. Ausserdem 
ist das ja noch gar nicht die vollständige Operation

   data[xy] = line;
   xy++;

Aber schreiben ist eigentlich nicht das Problem. Das geschieht ja in der 
ISR und kann daher (bei normalen ISR Aufbau) nicht von einem anderen 
Interrupt unterbrochen werden. Lesen ist blöder. Denn das passiert 
ausserhalb und KANN daher von einem Interrupt mitten im Vorgang 
unterbrochen werden.

von Heinz B. (hez)


Lesenswert?

Hört sich alles kompliziert an. Aber Interrupts vorübergehend zu 
deaktivieren, finde ich auch irgendwie doof. Dann muss ich damit 
rechnen, dass irgendwann Daten verloren gehen, weil kein Interrupt 
ausgelöst wurde, oder?

von Grrrr (Gast)


Lesenswert?

Heinz B. schrieb:
> Dann muss ich damit
> rechnen, dass irgendwann Daten verloren gehen, weil kein Interrupt
> ausgelöst wurde, oder?

Wenn du nebenbei noch ne OCR auf ein Telefonbuch machst, schon. Sonst 
nicht.

von Olaf (Gast)


Lesenswert?

> Ich will jetzt nicht so klingen, dass das ein leichtes wäre ... habe ja
> fast 0 Ahnung vom C ... aber wie gehts denn noch einfacher??

Wenn ich mal gnadenlos ehrlich sein soll, das hier vermittelte
Konzept ist sehr einfach. Und noch besser es ist sehr gut geeignet
wenn man mal ein bisschen C lernen will. Du kannst dir dann naemlich
mal so einen Ringbuffer programmieren, den mit Testdatenfuellen
und ausprobieren was da wann passiert.

Es empfiehlt sich im uebrigen auch einen C Compiler auf dem PC zu haben. 
Das muss nicht mal so eine fette Kiste mit allem drum und dran sein. 
Selbst ein alter kostenloser TurboC reicht da aus. Dann kann man 
naemlich solche Funktionen erstmal auf einem PC testen bevor man sie 
einsetzt.

> Das habe ich leider immer noch nicht verstanden. Ich sehe das Problem
> nicht.

Nehmen wir mal an du hast eine Variable von 16Bit breite. Wenn du jetzt 
einen 16Bit Microcontroller hast so kann der bei der zuweisung eines 
anderen Wertes das in einem einzigen Maschienenbefehl machen. Hast du 
jetzt aber einen 8Bit Microcontroller so macht der daraus mehrere 
Befehle. Und dann kann es passieren das dein Programm vom Interrupt in 
der Mitte dieser Befehle unterbrochen wird. Wenn der Interrupt aber auch 
mit derselben Variable rumspielt dann kann es passieren das die dabei 
einen falschen Inhalt bekommt.
Nebenbei das ist auch ein gutes Beispiel warum man auf die Nase fallen 
kann wenn man einfach Code von anderen Leuten uebernimmt ohne den zu 
verstehen. .-)

Aber es kommt noch schlimmer. Nimm mal diese Zeile als Beispiel:

> if( WriteIndex == 20 ) WriteIndex = 0;

Selbst wenn dieser Variablen vom Type char sind, so kann es ein Problem 
sein. Einfach weil hier zweimal auf die Variable zugegriffen wird und 
dazwischen auch ein IRQ liegen koennte.

Das sind im uebrigen Probleme die nichts mit dem Verstaendnis von C 
zutun haben, sondern von der Nutzung der Sprache auf einem 
Microcontroller herruehren. Deshalb ist es IMHO nicht so optimal wenn 
man C auf einem Controller lernt.

> Ich bilde ein Array. Kommen Daten rein, lege ich Zeichen für Zeichen in
> das Array ab. Erst bei einem \n kommt dieses Array in den Ringbuffer.

Wenn moeglich sollte man das auch vermeiden. Man haelt seinen IRQ so 
kurz wie moeglich weil er dein Programm unterbricht und dem Rechenzeit 
klaut. Und fast noch schlimmer je nach Datenlage in deinem IRQ wird der 
IRQ auch noch verschieden lange dauern. Das kann je nach Anwendung egal 
oder total schlimm sein.

Mach lieber soetwas:

 1. Im IRQ nur Zeichen in deinen FIFO werfen. Ohne nachdenken einfach
    rein damit.

 2. Jetzt weisst du das ein Datensatz von dir sagen wir mal immer 10Byte
    gross ist. Also machst du dein Buffer mindestens 20 oder 30Byte 
gross.

 3. In deinem Hauptprogramm schaust du nach ob bereits 10Byte da sind.
    Wenn nicht machst du irgendwas anderes.

 4. Wenn genug Daten eingetroffen sind dann wertest du sie aus.
    Klugerweise machst du das in einer Statemaschine. Es ist wichtig
    du bei Fehlern wieder sicher aufsetzt. Viele Programmierer haben das
    nicht verstanden. Das sind dann die Produkte die erstmal problemlos
    funktionieren, wo man aber alle paar Wochen oder Monate mal den
    Stecker ziehen muss um sie zu resetten.

Olaf

von Heinz B. (hez)


Lesenswert?

Ich habe mir jetzt angeschaut, was uVision aus der Zuweisung
data[xy] = line;
macht. Es stimmt, das sind tatsächlich mehrere Mnemonic-Befehle.

>> Wenn du nebenbei noch ne OCR auf ein Telefonbuch machst, schon.
OCR? Telefonbuch? Du schreibst mit einem Laien ;)

von Grrrr (Gast)


Lesenswert?

OCR = Optical Character Recognition = optische Zeichenerkennung
Telefonbuch = Viiieeeele Zeichen

von Karl H. (kbuchegg)


Lesenswert?

Heinz B. schrieb:

>>> Wenn du nebenbei noch ne OCR auf ein Telefonbuch machst, schon.
> OCR? Telefonbuch? Du schreibst mit einem Laien ;)


Was er sagen will

> Aber Interrupts vorübergehend zu deaktivieren, finde ich auch
> irgendwie doof. Dann muss ich damit rechnen, dass irgendwann Daten
> verloren gehen,

Deine Interrupts sind für ein paar µ-Sekunden gesperrt. Trifft 
tatsächlich in dieser Zeit ein Zeichen ein, dann muss es eben ein paar 
µ-Sekunden warten. Bis das nächste Zeichen von der UART kommt vergeht 
aber ... eine halbe Ewigkeit für den µC. Daher geht da auch nichts 
verloren. Über die UART kommen die Zeichen ganz einfach nicht schnell 
genug, als dass es etwas ausmachen würde, wenn die Interrupts ganz kurz 
gesperrt werden. Was anderes wäre es, wenn während der Interruptsperre 
etwas wirklich lang andauerndes passiert, wie eben eine OCR (optical 
Character Recognition, also kurz gesagt das Ratespiel welche Pixel in 
einem Bild einen Buchstaben ergeben und wenn ja welchen)

von Heinz B. (hez)


Lesenswert?

Achso. Ich wusste nicht, dass Interrupts auch in einer Art Warteschlange 
abgelegt werden.

Also ich habe jetzt mal was gebastelt. Das ist mein 2tes C-Programm für 
einen uC. Also haut mich bitte nicht, falls hier nur Blödsinn steht (was 
warhrscheinlich der Fall sein wird) :) Getestet habe ich noch gar 
nichts. Ich weiß noch gar nicht, wie ich das wirklich testen kann. Das 
hier ist nur ein Ringbuffer für den Empfang von Daten.

1
#pragma iv(0x8000)    // Einsprungadresse fur Interrupts
2
#include <reg517a.h>  // special function register declarations
3
#include <stdio.h>    // prototype declarations for I/O function
4
#include <string.h>
5
6
// Ringbuffer für den Empfang serieller Daten:
7
// -----------------  -----------------
8
// | 3 |x |x |x |x |--| 0 |x |x |x |x |
9
// |   |12|13|14|15|  |   | 0| 1| 2| 3|
10
// -----------------  -----------------
11
//         |                  |
12
// -----------------  -----------------
13
// | 2 |x |x |x |x |--| 1 |x |x |x |x |
14
// |   |11|10| 9| 8|  |   | 7| 6| 5| 4|
15
// -----------------  -----------------
16
// In dem Ringbuffer können 4 Wörter gebuffert werden.
17
// Jedes Wort (Bufferelement) kann max 4 Chars aufnehmen.
18
19
// --- Definitionen:
20
int iBufferMax = 4; // max 4 Wörter im Ringbuffer
21
int iCharMax   = 4; // max 4 Chars je Wort
22
char aBuffer[16];   // Ringbuffer [iBufferMax*iCharMax]
23
24
// --- globale Variablen (auch in Interrupt-Routinen benötigt):
25
int iBufferRead     = 0; // Zeiger auf Buffer - Lesen     
26
int iBufferWrite    = 0; // Zeiger auf Buffer - Schreiben 
27
                         // Zeigt auf jenes Bufferelement, in das:
28
                         //  - aktuell reingeschrieben wird oder
29
                         //  - als nächstes reingeschrieben wird, sobald wieder Daten reinkommen.
30
                         // Ist der iBufferWrite 0, darf iBufferRead nur aus den Bufferelementen
31
                         // 1, 2 oder 3 lesen.
32
int iBufferChar     = 0; // Zähler für die einzelnen Chars je Wort (0 ... iCharMax-1)
33
bit bOverflowChar   = 0; // ob ein Overflow bei der Anzahl der erlaubten Chars je Wort
34
bit bOverflowBuffer = 0; // ob im Buffer ein Overflow aufgetreten ist
35
36
// Receive-Interrupt:
37
void com_isr (void) interrupt 16  // Interrupt-Routing für serielle Schnittstelle 1
38
{
39
   if ((S1CON & 0x01) > 0) // wenn Receive-Interrupt-Bit der seriellen Schnittstelle 1 gesetzt
40
   {
41
      // Sollte beim Empfang von Daten ein Overflow auftreten, werden alle Daten
42
      // ignoriert, bis wieder ein \n empfangen wurde.
43
44
      // Daten atomar übernehmen:
45
      int iRead = iBufferRead; // Kopie
46
47
      // S1BUF übernehmen:
48
      if (iRead != (iBufferWrite+1) % iBufferMax) // z. B. verboten: write=0 und read=1 => Bufferüberlauf
49
      {
50
         char cS1BUF=S1BUF;  // das über die serielle Schnittstelle 1 empfangene Zeichen übernehmen
51
         if (cS1BUF == '\n')
52
         {
53
            if (bOverflowChar == 0 && bOverflowBuffer == 0)
54
            {
55
               // \n selbst wird nicht im aBuffer abgespeichert
56
               // es wird nur iBufferWrite um 1 erhöht
57
               iBufferWrite = (iBufferWrite + 1) % 4; // nur die Interrupt-Routine darf diese Variable verändern
58
            }
59
60
            // Overflows und Char-Zähler zurücksetzen:
61
            iBufferChar     = 0;
62
            bOverflowChar   = 0;
63
            bOverflowBuffer = 0;
64
         }
65
         else
66
         {
67
            if (iBufferChar < iCharMax)   // nur max. iCharMax Zeichen je Wort erlaubt
68
            {
69
               if (bOverflowChar == 0 && bOverflowBuffer == 0)
70
               {
71
                  aBuffer[iBufferWrite + iBufferChar++] = cS1BUF; // Zeichen übernehmen
72
               }
73
            }
74
            else
75
            {
76
               // Sollte der Zähler iBufferChar überschritten werden,
77
               // wird iBufferWrite nicht um 1 erhöht.
78
               // Es wird nur ein Error-Flag gesetzt.
79
               bOverflowChar = 1;
80
            }
81
         }
82
      }
83
      else
84
      {
85
         // ansonst liegt ein Buffer-Overflow vor
86
         bOverflowBuffer = 1;
87
      }
88
89
      // Interrupt zurücksetzen:
90
      S1CON = S1CON & 0xFE; // Interrupt-Bit muss per Software zuruckgesetzt werden
91
   }
92
93
   if((S1CON & 0x02) > 0) // wenn Transmit-Interrupt-Bit der seriellen Schnittstelle 1 gesetzt
94
   {
95
      // Interrupt zurücksetzen:
96
      S1CON = S1CON & 0xFD; // Interrupt-Bit muss per Software zuruckgesetzt werden
97
   }
98
}
99
100
// --- Hauptprogramm:
101
void main (void)
102
{
103
   // Initialisierung serielle Schnittstelle 1:
104
   S1CON  = 0x90;        // Modus 1 (8 Datenbits + 1 Stopbit), Receive enable
105
   S1RELH = 0x03;        // 9600 Baud
106
   S1RELL = 0xD9;
107
   PCON   = PCON | 0x80;
108
   IEN2   = IEN2 | 0x01; // Interrupts aktivieren
109
110
   while (1)
111
   {
112
      // Daten atomar übernehmen:
113
      int iWrite = iBufferWrite; // Kopie
114
115
      // Kontrolle, ob Daten empfangen wurden und ausgelesen werden müssen:
116
      while (iBufferRead != iWrite)
117
      {
118
         // Wort aus Ringbuffer auslesen:
119
         char aChar[4]; // ein Wort aus dem Ringbuffer [iCharMax]
120
         int iI    = iBufferRead * iCharMax;
121
         int iIEnd = iI + iCharMax;
122
         for (; iI < iIEnd; ++iI)
123
         {
124
            aChar[iI] = aBuffer[iI];
125
         }
126
127
         // atomar ändern:
128
         iBufferRead = (iBufferRead + 1) % 4; // nur das Hauptprogramm darf diese Variable verändern
129
130
         // irgendetwas mit aChar machen ...
131
      }
132
   }
133
}

Was sagt ihr dazu? Müll? Gleich wieder wegwerfen?

von Zacc (Gast)


Lesenswert?

Ein Detail. Folgender code ist unguenstig :

void PutIntoBuffer( unsigned char Byte )
{
  Buffer[WriteIndex++] = Byte;
  if( WriteIndex == 20 )
    WriteIndex = 0;
}

Dies weil 20 eine unguensige Zahl ist. Man verwendet besser 32.

 Buffer[WriteIndex++] = Byte;
 Writeindex &= 0x1F;

Macht schon alles.

von oldmax (Gast)


Lesenswert?

Hi
Ich kenne mich mit C nicht aus, habe aber in Assembler solche 
Empfangsroutinen umgesetzt. Daher versuch ich's mal zu erklären, wie ich 
sowas mache.
Ich habe z.B. 4 Bereiche, wo meine Nutzdaten drin stehen
Satz_1: Byte10
Satz_2: Byte 10
Satz_3:  etc....
Dann brauch ich einen Puffer, da meine Zeichen irgendwann einmal 
eintrudeln und ich nicht weiß, wo sie hingehören...
USART_Buf: Byte 40 (hat aber nix mit den 40 Bytes der Datensätze zu tun, 
ist hier reinzufällig)
An dieser Stelle mußt du selbst entscheiden, wie groß du diesen Puffer 
brauchst. Hier stehen die Daten nur temporär, bis das Hauptprogramm sie 
weggeschafft hat.
Um nun diesen Buffer zu beschreiben und auszulesen benötige ich 2 
weitere Variablen
Buf_Read: Byte 1
Buf_Write: Byte 1
Diese sind meine Zeiger auf den Ringpuffer. Ist ein Zeiger so groß, wie 
der Puffer ( in diesem Fall 40) wird er auf 0 gesetzt und zeigt auf den 
Anfang.
Folgendes passiert in der ISR des UART, wenn ein Zeichen eintrifft:
Zuerst erhöhe ich Buf_Write, Kontrolle, ob Puffergrenze erreicht und 
wenn ja, dann Buf_Write auf 0
dann schreib ich den Zeiger zurück.
Anschließend hole ich die Adresse der Variablen USART_Buf und addiere 
Buf_Write. Somit kann ich das nächste Feld im Puffer beschreiben. Damit 
ist die ISR fertig.
Im Hauptprogramm frage ich einfach bei jedem Durchlauf ab, ob Buf_Read 
und Buf_Write gleich sind. Ist das der Fall, gibt's nix neues, ansonsten 
wird Buf_Read erhöht, geprüft auf Puffergrenze und evtl. auf 0 gesetzt, 
wieder abgelegt und dann zur Adresse des Puffers addiert und das Zeichen 
zur Weiterbehandlung gelesen. Entweder man hat irgendwann ein 
Steuerzeichen empfangen, was aussagt, welcher Datensatz grad eintrudelt, 
oder man prüft auf ein entsprechendes Steuerzeichen. Wie auch immer, der 
Ablauf ist nicht von der ISR abhängig und blockiert diese auch in 
keinster Weise. Empfängt man mit hoher Baudrate große Datenmengen, muß 
der Puffer entsprechend groß ausgelegt werden, damit er nicht seinen 
eigenen Schwanz überschreibt. Aber in der Regel sind die Controller 
schon sehr schnell, das ein Pufferüberlauf (oder besser gesagt, das 
Überschreiben der unbewerteten Daten) nicht vorkommt. Natürlich ist auch 
der Fall denkbar, zuerst die Addition von Pufferadresse und Zeiger und 
nach dem Beschreiben / Lesen die Zeiger zu erhöhen. Auch da ist 
Gleichheit, wenn kein neues Zeichen eingetrudelt und alles abgearbeitet 
ist.
Ich hoffe du kannst nach meiner Beschreibung das in C umsetzen.
Gruß oldmax

von Karl H. (kbuchegg)


Lesenswert?

Heinz B. schrieb:

> Ich weiß noch gar nicht, wie ich das wirklich testen kann. Das
> hier ist nur ein Ringbuffer für den Empfang von Daten.

Mein Tip:
Ehe du jetzt auf deinem µC im Nebel rumstocherst, installier dir auf 
deinem PC eine Entwicklungsumgebung, mit der du am PC auch C-Programme 
schreiben kannst. Und dann testest du deine Ringbuffer-Funktionen am PC. 
Anstelle der UART, schreibt dann eben dein Hauptprogramm über einen 
Funktionsaufruf einen Character in den Ringbuffer.

Aber: Auf dem PC hast du wesentlich bessere Möglichkeiten, dem Programm 
bei der Arbeit zuzusehen. Du hast einen Schirm, auf dem du mit printf 
zwischendurch Werte ausgeben lassen kannst. Du hast einen Debugger, der 
es dir ermöglicht, das Programm in Einzelschritten durchzugehen.

All das ist Grund genug, einzelne Funktionalitäten erst mal auf dem PC 
zu entwickeln und zu testen und erst dann, wenn alles funktioniert, 
diese Funktionalität in das µC Programm zu übernehmen.

Das spart dir eine Menge Kopfzerbrechen und auch Zeit.

von Olaf (Gast)


Lesenswert?

> Ein Detail. Folgender code ist unguenstig :

Das denke ich zwar auch....

> void PutIntoBuffer( unsigned char Byte )
> {
>   Buffer[WriteIndex++] = Byte;
>   if( WriteIndex == 20 )
>    WriteIndex = 0;
> }

> Dies weil 20 eine unguensige Zahl ist. Man verwendet besser 32.

Aber nicht aus diesem Grunde. Natuerlich hast du recht, man kann
durch solche Zahlen die sich binaer gut abbilden lassen, manches
etwas schneller machen oder vereinfachen.
Allerdings kommt es meist nicht so auf Geschwindigkeit an. Worauf
es aber bei kleinen Controllern mehr ankommt das ist Rambedarf. Und
wenn eine Aufgabe mit 20Byte buffer loesbar ist, dann sind 32 bereits
eine ganz schoene Verschwendung.

Besser ist folgendes:

#define MAXFIFO 20

char Buffer[MAXFIFO];
if( WriteIndex >= (MAXFIFO-1) )

Zum einen kann man dann seine Fifofunktionen in eine Datei auslagern
die man bei bedarf einbindet und dann einfach die groesse der Fifo
dem aktuellen Problem anpassen, und zum anderen hat die Verwendung
von '>=' den Vorteil das ein Programm nicht garsoviel Unsinn macht
wenn da doch aus einem bestimmten Grund ein zu grosser Wert drin steht.

Olaf

von Bernd (Gast)


Lesenswert?

Schau dir mal das Beispiel von Victoria Welch an... ist Code für einen 
8x51 und sollte leicht zu übernehmen sein.

http://sdccokr.dl9sec.de/resources.htm#Topic2

von Heinz B. (hez)


Lesenswert?

@Bernd
#define kannte ich noch gar nicht. Habe ich gleich bei mir eingebaut! 
Danke für den Tip.

Wann braucht man eigentlich volatile und static? Das sehe ich auch das 
erste mal in einem C-Programm. static kenne ich eigentlich nur im 
Zusammenhang mit OOP.

von Karl H. (kbuchegg)


Lesenswert?

Heinz B. schrieb:
> @Bernd
> #define kannte ich noch gar nicht. Habe ich gleich bei mir eingebaut!
> Danke für den Tip.
>
> Wann braucht man eigentlich volatile und static? Das sehe ich auch das
> erste mal in einem C-Programm. static kenne ich eigentlich nur im
> Zusammenhang mit OOP.


http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Datenaustausch_mit_Interrupt-Routinen

Bitte, bitte, bitte.
Geh das avr-gcc-Tutorial durch!

Da findet sich noch so manches, was du nicht kennst.

Und bei deinem nächsten Ausflug in die Stadt schaust du auch noch in 
eine Buchhandlung rein und fragst ob sie einen "Kernighan&Ritchie 
Programmieren in C" haben und kaufst das Buch.

von Bernd (Gast)


Lesenswert?

Beitrag "static volatile und Interrupts"

Oder suche in anderen Threads, ist oft diskutiert, als C Anfäner aber 
ok. Ich habe dir das Beispiel gegeben weil es sich bei deiner 
Aufgabenstellung um viele Details dreht und ein Beispielcode ist dann 
hilfreich... insbesondere wenn du allen daraus resultierenden Fragen 
nachgehst :-)

von Heinz B. (hez)


Lesenswert?

>> ist oft diskutiert
Verstehe ich. Ich kämpfe auch damit herum.

Versuch einer Definition zu volatile:

Soll man bei Variablen verwenden, deren Werte sich ohne eigenes Zutun 
verändern können und man immer mit dem aktuellen Wert arbeiten möchte.
Z. B. bei einem Port. Möchte ich in meinem Script immer den aktuellen 
Wert vom Eingabeport P4, muss ich den Wert von P4 einer 
volatile-Variable übergeben. Dann hat diese Variable immer den 
Letztstand.

Stimmt das? Habe ich das richtig verstanden?

von Karl H. (kbuchegg)


Lesenswert?

Heinz B. schrieb:
>>> ist oft diskutiert
> Verstehe ich. Ich kämpfe auch damit herum.
>
> Versuch einer Definition zu volatile:
>
> Soll man bei Variablen verwenden, deren Werte sich ohne eigenes Zutun
> verändern können und man immer mit dem aktuellen Wert arbeiten möchte.
> Z. B. bei einem Port. Möchte ich in meinem Script immer den aktuellen
> Wert vom Eingabeport P4, muss ich den Wert von P4 einer
> volatile-Variable übergeben. Dann hat diese Variable immer den
> Letztstand.
>
> Stimmt das? Habe ich das richtig verstanden?

Ich denke: im Prinzip ja

voltatile teilt dem Compiler mit, jegliche Optimierungen, gleich welcher 
Art, auf dieser Variablen zu unterlassen, weil sich ihr Wert auf Wegen 
ändern kann, die für den Compiler nicht einsehbar sind. Seine Annahmen, 
die sich aus irgendwelchen Anaylsen ergeben, über die zeitliche 
Entwicklung dieser Variablen, sind daher falsch und er möge doch bitte 
zu 100% genau das tun, was im Code steht. Und wenn im Code steht, dass 
von dieser Variablen gelesen werden soll und der Compiler denkt, dass 
genau dieser Wert eigentlich noch irgendwo in einem Prozessor-Register 
herumlungert, dann soll er das ignorieren und den Wert tatsächlich 
erneut aus dem Speicher lesen, auch wenn das vermeintlich länger dauert.
Selbiges gilt sinngemäss auch fürs schreiben auf eine volatile Variable.

> Möchte ich in meinem Script immer den aktuellen
> Wert vom Eingabeport P4, muss ich den Wert von P4 einer
> volatile-Variable übergeben

Das ist Unsinn. In dem Fall ist es der Port, der volatile sein muss. 
Sobald du den Wert vom Port in einer Variablen hast, besteht ja sowieso 
keine Verbindung der Variablen mehr mit dem Port. Die Variable kann 
daher ihren Wert nicht mehr 'auf für den Compiler nicht mehr einsehbaren 
Wegen' ändern. Und damit kann der Compiler dann auf dieser Variablen 
weiteroptimieren, wie es ihm gerade einfällt.


volatile heißt im Englischen soviel wie: flüchtig, unberechenbar
Und genau das bedeutet es auch für den Compiler: Das zeitliche Verhalten 
einer Variablen ist für den Compiler nicht greifbar und entzieht sich 
seinen Berechnungen. Daher wäre es gut, wenn er keine Annahmen darüber 
treffen würde.

von Bernd (Gast)


Lesenswert?

Jein, es betrifft den Porgrammfluß. Besteht die Möglichkeit das eine 
Variable außerhalb des Programmfluß geändert werden kann, z.b. durch 
einen INTERRUPT dann sollte man sie volatile deklarieren.

Der Compiler kann so etwas nicht wissen also sagt man ihm das mit 
"volatile".

Bei einem Port ist das nicht notwendig denn er wird ja immer aktuell 
eingelesen und dann ausgewertet.

von Grrrr (Gast)


Lesenswert?

Bernd schrieb:
> Bei einem Port ist das nicht notwendig denn er wird ja immer aktuell
> eingelesen und dann ausgewertet.

Evtl. muss diese Aussage eingeschränkt werden wenn der IO-Port "memory 
mapped" ist.

von Heinz B. (hez)


Lesenswert?

Zum Thema volatile.
Also ich habe mir jetzt so ein kleines Testscript gebastelt:
1
#pragma iv(0x8000)    // Einsprungadresse fur Interrupts
2
#include <reg517a.h>  // special function register declarations
3
#include <stdio.h>    // prototype declarations for I/O function
4
#include <string.h>
5
6
//volatile 
7
int i=0x01; // funktioniert auch ohne volatile!!!!!!!!!!!!!!!!!!!!
8
void com_isr (void) interrupt 16  // Interrupt-Routing für serielle Schnittstelle 1
9
{
10
   if ((S1CON & 0x01) > 0) // wenn Receive-Interrupt-Bit der seriellen Schnittstelle 1 gesetzt
11
   {
12
      P4=0x02;
13
      i=0x03; // <- HIER WIRD i AUF 3 GESETZT
14
      // Interrupt zurücksetzen:
15
      S1CON = S1CON & 0xFE; // Interrupt-Bit muss per Software zuruckgesetzt werden
16
   }
17
   if((S1CON & 0x02) > 0) // wenn Transmit-Interrupt-Bit der seriellen Schnittstelle 1 gesetzt
18
   {
19
      // Interrupt zurücksetzen:
20
      S1CON = S1CON & 0xFD; // Interrupt-Bit muss per Software zuruckgesetzt werden
21
   }
22
}
23
void main (void)
24
{
25
   // Initialisierung serielle Schnittstelle 1:
26
   S1CON  = 0x90;        // Modus 1 (8 Datenbits + 1 Stopbit), Receive enable
27
   S1RELH = 0x03;        // 9600 Baud
28
   S1RELL = 0xD9;
29
   PCON   = PCON | 0x80;
30
   EAL    = 1;
31
   IEN2   = IEN2 | 0x01; // Interrupts aktivieren
32
33
   while (1)
34
   {
35
      P4=i; // HIER WIRD i AUSGEGEBEN
36
   }
37
}

Eigentlich hätte ich mir erwartet, dass er 1 und dann 2 ausgibt, jedoch 
kein 3 mehr.?.?.? Er gibt aber 3 aus ....

von Grrrr (Gast)


Lesenswert?

Heinz B. schrieb:
> // funktioniert auch ohne volatile!!!!!!!!!!!!!!!!!!!!

NNNNNNNNNNNNNNNNEEEEEEEEEEEEEEEEEEEEIIIIIIIIIIIIIIIIIIIIIINNNNNNNNNNNNNN 
NNN!!!!!!!!!!!!!!!!!

Man hat es Dir doch nun extra gesagt und ausführlich erklärt.
Das man auch mit geschlossenenen Augen lebend über die A81 laufen kann 
ist wahrscheinlich nur ein Sonderfall, wenn derjenige, der empfiehlt, es 
überhaupt nicht oder wenn, dann mit offenen Augen zu tun, irgendwie 
lebendig aussieht.

von Heinz B. (hez)


Lesenswert?

Habe ich den Bernd falsch verstanden???

>>Jein, es betrifft den Porgrammfluß. Besteht die Möglichkeit das eine
>>Variable außerhalb des Programmfluß geändert werden kann, z.b. durch
>>einen INTERRUPT dann sollte man sie volatile deklarieren.
>>Der Compiler kann so etwas nicht wissen also sagt man ihm das mit
>>"volatile".

von Grrrr (Gast)


Lesenswert?

Heinz B. schrieb:
> Habe ich den Bernd falsch verstanden???

Nein.

Guck mal, wo i geändert wird. Ist das innerhalb eines INterrupts?

von Heinz B. (hez)


Lesenswert?

Öhm ... ja.?
Hier:
i=0x03;
Ist im Interrupt-Handler.

von Grrrr (Gast)


Lesenswert?

Heinz B. schrieb:
> Ist im Interrupt-Handler.

Ergo?

von Heinz B. (hez)


Lesenswert?

Ähm ... ergo sollte das nur mit volatile funktionieren, dass er dann im 
Hauptprogramm die 3 ausgibt, oder?

von Karl H. (kbuchegg)


Lesenswert?

Der springende Punkt ist, dass du dich ohne volatile dem Compiler 
auslieferst.

Das kann funktionieren, muss es aber nicht.
Mit einer anderen Compilerversion, einer anderen Einstellung des 
Optimizers, aufsplitten des Codes in 2 getrennte *.c Files, kann sich 
das alles umdrehen und den heissgeliebtes "funktioniert auch ohne 
volatile" dreht sich um in "Mist, ich habe einen Fehler und weiß nicht 
wo er steckt"

Und noch was: "Kann" ist nicht dasselbe wie "Muss"

von Karl H. (kbuchegg)


Lesenswert?

Heinz B. schrieb:
> Ähm ... ergo sollte das nur mit volatile funktionieren, dass er dann im
> Hauptprogramm die 3 ausgibt, oder?

Das sagt kein Mensch.
Umgekehrt wird ein Schuh draus.

ohne volatile kann es funktionieren oder es kann auch nicht 
funktionieren. Je nach Wasserstand, Mondphase und wie der Optimizer 
aufgelegt ist.

mit volatile muss es funktionieren.

von Heinz B. (hez)


Lesenswert?

>> Das kann funktionieren, muss es aber nicht.
Achso ... das funktioniert also nur zufällig gerade bei meinem Compiler 
und bei dieser uC.

Aber grundsätzlich habe ich das richtig verstanden? Man sollte dann in 
diesem Beispiel beim i MIT volatile arbeiten?

von Karl H. (kbuchegg)


Lesenswert?

Heinz B. schrieb:

> Aber grundsätzlich habe ich das richtig verstanden? Man sollte dann in
> diesem Beispiel beim i MIT volatile arbeiten?

Ja.

von Grrrr (Gast)


Lesenswert?

Heinz B. schrieb:
> Ähm ... ergo sollte das nur mit volatile funktionieren, dass er dann im
> Hauptprogramm die 3 ausgibt, oder?

Nicht ganz. Da muss man wohl nochmal weiter ausholen.

Dein Programm ist doch wahrscheinlich nur eine Testversion. Also ein 
Sonderfall.
Denn nach dem i einmal auf 0x03 gesetzt wurde ändert es seinen Wert 
nicht mehr. Ausserdem gibt es nur eine Reaktion die wahrscheinlich 
visuell schwer zu erkennen ist. Unter bestimmten Umständen wird es aber 
merkbar sein, das die Änderung von i nicht in main ankommt.

Wiegesagt, einmal lebendig über die Autobahn könnte auch heissen, das 
sie es einmal geschafft haben, Dich zu reanimieren. Keine Garantie für 
das zweite Mal.

Hier lernst Du aber auch was für Deine zukünftigen Projekte.

von Heinz B. (hez)


Lesenswert?

Wieso hat mich dann der Grrr so angegrrrrt? ;)

EDIT
@ Grrrr: Ja klar, ich experimentiere momentan nur herum.

von Karl H. (kbuchegg)


Lesenswert?

Heinz B. schrieb:

> Aber grundsätzlich habe ich das richtig verstanden? Man sollte dann in
> diesem Beispiel beim i MIT volatile arbeiten?


Sieh dir deine main(). Sieh dir nur die main() an
1
void main (void)
2
{
3
   // Initialisierung serielle Schnittstelle 1:
4
   S1CON  = 0x90;        // Modus 1 (8 Datenbits + 1 Stopbit), Receive enable
5
   S1RELH = 0x03;        // 9600 Baud
6
   S1RELL = 0xD9;
7
   PCON   = PCON | 0x80;
8
   EAL    = 1;
9
   IEN2   = IEN2 | 0x01; // Interrupts aktivieren
10
11
   while (1)
12
   {
13
      P4=i; // HIER WIRD i AUSGEGEBEN
14
   }
15
}

gibt es da irgendeine Möglichkeit, wie i jemals seinen Wert ändern 
könnte? Es gibt keine Zuweisung an i, es gibt keinen Funktionsaufruf. 
Also kann nach menschlichem (ähm compilerischem) Ermessen i niemals 
seinen Wert ändern. Nichts und niemand würde den Compiler daran hindern 
anstelle von

     P4 = i;

sich den Wert für i zu bestimmen, zb von hier

int i=0x01;

und die Zuweisung durch

      P4 = 0x01;

zu ersetzen. Das ist eine völlig legale Optimierung, die der Compiler 
machen darf.

von Grrrr (Gast)


Lesenswert?

Heinz B. schrieb:
> Wieso hat mich dann der Grrr so angegrrrrt? ;)

Ach das ist so meine Art.

Bin auf die "triumphierenden" Ausrufezeichen nach "funktioniert auch 
ohne volatile" angesprungen. Deswegen mein Autobahnbeispiel. Einmal 
geht's vielleicht, vielleicht auch nochmal. Aber ich gehe davon aus, das 
Du noch mehr Programme schreiben willst.

volatile ist halt ein oft gemachter und vieldiskutierter Fehler. 
Deswegen... meine Aufmerksamkeit erregende Art. Nichts für ungut.

von Heinz B. (hez)


Lesenswert?

Jetzt habe ich volatile auch noch eingebaut:

1
#pragma iv(0x8000)    // Einsprungadresse fur Interrupts
2
#include <reg517a.h>  // special function register declarations
3
#include <stdio.h>    // prototype declarations for I/O function
4
#include <string.h>
5
6
// Ringbuffer für den Empfang serieller Daten:
7
// -----------------  -----------------
8
// | 3 |x |x |x |x |--| 0 |x |x |x |x |
9
// |   |12|13|14|15|  |   | 0| 1| 2| 3|
10
// -----------------  -----------------
11
//         |                  |
12
// -----------------  -----------------
13
// | 2 |x |x |x |x |--| 1 |x |x |x |x |
14
// |   |11|10| 9| 8|  |   | 7| 6| 5| 4|
15
// -----------------  -----------------
16
// In dem Ringbuffer können 4 Wörter gebuffert werden.
17
// Jedes Wort (Bufferelement) kann max 4 Chars aufnehmen.
18
19
// --- Konstanten:
20
#define true       1
21
#define false      0
22
#define iBufferMax 4 // max 4 Wörter im Ringbuffer
23
#define iCharMax   4 // max 4 Chars je Wort
24
25
// --- globale Variablen (Im Hauptprogramm und im Interrupt-Handler benötigt):
26
volatile char aBuffer[iBufferMax*iCharMax]; // Ringbuffer [iBufferMax*iCharMax]
27
volatile int iBufferRead  = 0; // Zeiger auf Buffer - Lesen     
28
volatile int iBufferWrite = 0; // Zeiger auf Buffer - Schreiben 
29
                               // Zeigt auf jenes Bufferelement, in das:
30
                               //  - aktuell reingeschrieben wird oder
31
                               //  - als nächstes reingeschrieben wird, sobald wieder Daten reinkommen.
32
                               // Ist der iBufferWrite 0, darf iBufferRead nur aus den Bufferelementen
33
                               // 1, 2 oder 3 lesen.
34
35
// --- globale Variablen (nur im Interrrupt-Handler benötigt):
36
int iBufferChar     = 0;     // Zähler für die einzelnen Chars je Wort (0 ... iCharMax-1)
37
bit bOverflowChar   = false; // ob ein Overflow bei der Anzahl der erlaubten Chars je Wort
38
bit bOverflowBuffer = false; // ob im Buffer ein Overflow aufgetreten ist
39
40
// Receive-Interrupt:
41
void com_isr (void) interrupt 16  // Interrupt-Routing für serielle Schnittstelle 1
42
{
43
   if ((S1CON & 0x01) > 0) // wenn Receive-Interrupt-Bit der seriellen Schnittstelle 1 gesetzt
44
   {
45
      // Sollte beim Empfang von Daten ein Overflow auftreten, werden alle Daten
46
      // ignoriert, bis wieder ein \n empfangen wurde.
47
48
      // Daten atomar übernehmen:
49
      int iRead = iBufferRead; // Kopie
50
51
      // S1BUF übernehmen:
52
      if (iRead != (iBufferWrite+1) % iBufferMax) // z. B. verboten: write=0 und read=1 => Bufferüberlauf
53
      {
54
         char cS1BUF=S1BUF;  // das über die serielle Schnittstelle 1 empfangene Zeichen übernehmen
55
         if (cS1BUF == '\n')
56
         {
57
            if (!bOverflowChar && !bOverflowBuffer)
58
            {
59
               // \n selbst wird nicht im aBuffer abgespeichert
60
               // es wird nur iBufferWrite um 1 erhöht
61
               iBufferWrite = (iBufferWrite + 1) % iBufferMax; // nur die Interrupt-Routine darf diese Variable verändern
62
            }
63
64
            // Overflows und Char-Zähler zurücksetzen:
65
            iBufferChar     = 0;
66
            bOverflowChar   = false;
67
            bOverflowBuffer = false;
68
         }
69
         else
70
         {
71
            if (iBufferChar < iCharMax)   // nur max. iCharMax Zeichen je Wort erlaubt
72
            {
73
               if (!bOverflowChar && !bOverflowBuffer)
74
               {
75
                  aBuffer[iBufferWrite + iBufferChar++] = cS1BUF; // Zeichen übernehmen
76
               }
77
            }
78
            else
79
            {
80
               // Sollte der Zähler iBufferChar überschritten werden,
81
               // wird iBufferWrite nicht um 1 erhöht.
82
               // Es wird nur ein Error-Flag gesetzt.
83
               bOverflowChar = true;
84
            }
85
         }
86
      }
87
      else
88
      {
89
         // ansonst liegt ein Buffer-Overflow vor
90
         bOverflowBuffer = true;
91
      }
92
93
      // Interrupt zurücksetzen:
94
      S1CON = S1CON & 0xFE; // Interrupt-Bit muss per Software zuruckgesetzt werden
95
   }
96
97
   if((S1CON & 0x02) > 0) // wenn Transmit-Interrupt-Bit der seriellen Schnittstelle 1 gesetzt
98
   {
99
      // Interrupt zurücksetzen:
100
      S1CON = S1CON & 0xFD; // Interrupt-Bit muss per Software zuruckgesetzt werden
101
   }
102
}
103
104
// --- Hauptprogramm:
105
void main (void)
106
{
107
   // Initialisierung serielle Schnittstelle 1:
108
   S1CON  = 0x90;        // Modus 1 (8 Datenbits + 1 Stopbit), Receive enable
109
   S1RELH = 0x03;        // 9600 Baud
110
   S1RELL = 0xD9;
111
   PCON   = PCON | 0x80;
112
   EAL    = 1;
113
   IEN2   = IEN2 | 0x01; // Interrupts aktivieren
114
115
   while (1)
116
   {
117
      // Daten atomar übernehmen:
118
      int iWrite = iBufferWrite; // Kopie
119
120
      // Kontrolle, ob Daten empfangen wurden und ausgelesen werden müssen:
121
      while (iBufferRead != iWrite)
122
      {
123
         // Wort aus Ringbuffer auslesen:
124
         char aChar[iCharMax]; // ein Wort aus dem Ringbuffer
125
         int iI    = iBufferRead * iCharMax;
126
         int iIEnd = iI + iCharMax;
127
         for (; iI < iIEnd; ++iI)
128
         {
129
            aChar[iI] = aBuffer[iI];
130
         }
131
132
         // atomar ändern:
133
         iBufferRead = (iBufferRead + 1) % iBufferMax; // nur das Hauptprogramm darf diese Variable verändern
134
135
         // irgendetwas mit aChar machen ...
136
      }
137
   }
138
}

Wie schaut das aus, liebe Profis? Ist das grundsätzlich OK?
Ist alles so schön bunt :)

von Karl H. (kbuchegg)


Lesenswert?

Heinz B. schrieb:

> Wie schaut das aus, liebe Profis? Ist das grundsätzlich OK?
1
                 aBuffer[iBufferWrite + iBufferChar++] = cS1BUF; // Zeichen übernehmen
Das ist Murks.
Du hast keine Gewähr, dass iBufferWrite + iBufferChar einen gültigen 
Index in das Array ergibt.

Grundsätzlich denke ich, dass du im Ringbuffer viel zu viel machst. Du 
versuchst dort schon eine Zerlegung in 'Datensätze' einzubauen. Würde 
ich nicht machen. Der Ringbuffer soll nur dazu dienen, die Zeichen von 
der UART aufzunehmen, wenn der µC gerade anderwertig beschäftigt ist. 
Ansonsten soll er sich aus allem anderen raushalten. Insbesondere ist es 
nicht Aufgabe des Ringbuffers irgendwelche Interpretiationen der Daten 
vorzunehmen. Das macht dann derjenige, der sich die Character aus dem 
Buffer holt.
1
// atomar ändern:
2
         iBufferRead = (iBufferRead + 1) % iBufferMax;
auf einem 8-Bit System ist diese Operation höchst wahrscheinlich nicht 
atomar. iBufferRead wird mit dem neuen Wert beschrieben, indem HighByte 
und LowByte getrennt in den Speicher geschrieben werden. Kommt der 
Interrupt genau zwischen den beiden Operationen, dann findet die ISR 
einen nur halb geschriebenen neuen Wert in iBufferRead vor. Bei deinen 
Werten hast du insofern Glück, weil iBufferRead niemals größer als 16 
werden wird und damit ist das HighByte immer 0. Auf der anderen Seite 
erhebt sich dann natürlich sofort die Frage: Warum ist das dann 
eigentlich ein int?


Hier
1
      // Daten atomar übernehmen:
2
      int iWrite = iBufferWrite; // Kopie
3
4
      // Kontrolle, ob Daten empfangen wurden und ausgelesen werden müssen:
5
      while (iBufferRead != iWrite)
6
      {
hast du auch etwas grundlegend Misverstanden. Erstens ist das keine 
atomar durchgeführte Kopie. Zweitens: was hilft es dir, wenn iWrite 
einmal seinen Wert bekommt und dann nie wieder geändert wird.


Noch mal der Tip:
Probier und Teste die Einzelfunktionalität auf einem PC!

von Karl H. (kbuchegg)


Lesenswert?

Karl heinz Buchegger schrieb:

>
1
>                  aBuffer[iBufferWrite + iBufferChar++] = cS1BUF; //
2
> Zeichen übernehmen
3
>
> Das ist Murks.
> Du hast keine Gewähr, dass iBufferWrite + iBufferChar einen gültigen
> Index in das Array ergibt.

Mein Fehler
1
              iBufferWrite = (iBufferWrite + 1) % iBufferMax; // nur die Interrupt-Routine darf diese Variable verändern

Du wirst zwar das Array nicht überlaufen, aber du überschreibst dir 
unter Umständen noch nicht gelesene Daten.

Daher:
> Noch mal der Tip:
> Probier und Teste die Einzelfunktionalität auf einem PC!

Doppelt unterstreich!

von Heinz B. (hez)


Lesenswert?

>> Du hast keine Gewähr, dass iBufferWrite + iBufferChar einen gültigen
>> Index in das Array ergibt.
iBufferRead und iBufferWrite habe ich (hoffentlich) so programmiert, 
dass sie nur Werte von 0 bis 3 annehmen können.
Und bei iBufferChar mache ich noch so eine Kontrolle:
if(iBufferChar < iCharMax)

>> Du versuchst dort schon eine Zerlegung in 'Datensätze' einzubauen.
Ich will halt je Datenübertragung nur eine gewisse Anzahl an Zeichen 
erlauben. In meinem Fall immer nur 4 Chars.

>> Interpretiationen der Daten
Da meinst du wahrscheinlich den Vergleich mit '\n'
Ich dachte mir, ich mach es so ähnlich wie die Funktion PUTCHAR. Dort 
wird ja auch auf \n kontrolliert.
Siehe http://www.humerboard.at/doku/sb8/uc3.pdf

>> // atomar ändern:
Fehler von mir. Der Kommentar gehört weg. Muss ja nicht atomar sein.

>> Warum ist das dann eigentlich ein int
Ich habe mir das Leben jetzt mal leicht gemacht und alles Zahlenmäßige 
ins int gegeben :)

>> Probier und Teste die Einzelfunktionalität auf einem PC!
Muss ich mir noch überlegen, wie ich das jetzt anstelle.

>> int iWrite = iBufferWrite; // Kopie
>> keine atomar durchgeführte Kopie
Echt nicht???? Verdammt!!
Aber wie soll ich das denn sonst lösen?

von Heinz B. (hez)


Lesenswert?

Ich habe jetzt alle "int" umgeändert auf "unsigned char".

Wenn ich nur "char" angebe, ist das dann "signed char" oder "unsinged 
char"?

Ist das in C auch so der Fall, dass "signed char" schneller ist als 
"unsigned char"? Sollte ich dann statt dem "int" ein "singned char" 
nehmen? In meinem Fall wäre es ja eigentlich egal. Zähle ja immer nur 
bis max. 4.

von Grrrr (Gast)


Lesenswert?

Heinz B. schrieb:
> Ich will halt je Datenübertragung nur eine gewisse Anzahl an Zeichen
> erlauben. In meinem Fall immer nur 4 Chars.

Das kannst Du weder "erwzingen", noch "erlauben".
Ist, als wenn Du dem Wind erlaubst in Nord-West-Richtung zu wehen. Er 
macht's oder auch nicht.

Es ist schon richtig so, wie Karl Heinz, das vorschlägt. Erstmal ist es 
effizienter, weil der Interrupt schneller läuft und dann sauberer, weil 
Zeichenempfang und Verarbeitung getrennt sind.

von Heinz B. (hez)


Lesenswert?

>> weil Zeichenempfang und Verarbeitung getrennt sind.
Wie soll ich denn beim Empfang erkennen, ob die Daten vollständig 
übertragen wurden? Mit \n kann ich so eine Kontrolle machen. \n ist dann 
auch das Zeichen dafür, dass ein Wort (Datensatz ... oder wie auch immer 
man das nennen mag ... eben die max. 4 Zeichen) zu ende übertragen 
wurde.

von Grrrr (Gast)


Lesenswert?

Heinz B. schrieb:
> Wie soll ich denn beim Empfang erkennen, ob die Daten vollständig
> übertragen wurden?

"beim Empfang", also im Interrupt, garnicht. Du empfängst einfach alles 
was reinkommt.
Erst in der übergeordneten Ebene prüfst Du Protokoll und reagierst 
darauf.

von Grrrr (Gast)


Lesenswert?

Heinz B. schrieb:
> Mit \n kann ich so eine Kontrolle machen. \n ist dann
> auch das Zeichen dafür, dass ein Wort (Datensatz ... oder wie auch immer
> man das nennen mag ... eben die max. 4 Zeichen) zu ende übertragen
> wurde.

Der Punkt, um den es geht, ist ja nicht ob Du das Ende erkennst, 
sondern wo im Programm.

von Heinz B. (hez)


Lesenswert?

Also wäre die andere Variante, dass ich im Interrup-Handler für die 
empfangenen Daten überhaupt nichts prüfe (außer, dass natürlich mein 
kompletter Ring nicht überlaufen darf), dann aber das \n im Ringbuffer 
mit abspeichere.

Dann muss ich im Hauptprogramm immer alle Zeichen bis zum \n zählen. Und 
solltes es zu viele sein, habe ich einen Fehler.

So meinst du das, richtig?

von Karl H. (kbuchegg)


Lesenswert?

Heinz B. schrieb:
> Also wäre die andere Variante, dass ich im Interrup-Handler für die
> empfangenen Daten überhaupt nichts prüfe (außer, dass natürlich mein
> kompletter Ring nicht überlaufen darf), dann aber das \n im Ringbuffer
> mit abspeichere.
>
> Dann muss ich im Hauptprogramm immer alle Zeichen bis zum \n zählen. Und
> solltes es zu viele sein, habe ich einen Fehler.
>
> So meinst du das, richtig?

Ganz genau.
Und damit ist dann auch dein Fehler, den du mit den beiden 
Indexvariablen in der ISR hast, hinfällig :_)

>>> Du hast keine Gewähr, dass iBufferWrite + iBufferChar einen gültigen
>>> Index in das Array ergibt.
> iBufferRead und iBufferWrite habe ich (hoffentlich) so programmiert,
> dass sie nur Werte von 0 bis 3 annehmen können.
> Und bei iBufferChar mache ich noch so eine Kontrolle:
> if(iBufferChar < iCharMax)

Schön.
Und zum abspeichern benutzt du
                 aBuffer[iBufferWrite + iBufferChar++] = cS1BUF;

d.h. die Bytes vom Paket mit iBufferWrite gleich 0 werden im Array an 
den Indexpositionen 0, 1, 2, 3 abgelegt.
Die Bytes für iBufferWrite gleich 1 an den Arraypositionen 1, 2, 3, 4

Moment ich mach dir eine Tabelle

      iBufferWrite         Arrayindices an denen gespeichert wird

          0                0, 1, 2, 3
          1                1, 2, 3, 4
          2                2, 3, 4, 5
          3                3, 4, 5, 6

merkst du was?

Genau deshalb rachte ich dir noch einmal: Teste solche Dinge zuerst am 
PC, wo du dir einfach vernünftige Zwischenresultate mittels printf 
ausgeben lassen kannst.

von Heinz B. (hez)


Lesenswert?

>> aBuffer[iBufferWrite + iBufferChar++] = cS1BUF;
So einfach geht das leider nicht.

Angenommen mein Ringbuffer kann 16 Chars aufnehmen und 14 
Ringbuffer-Elemente sind bereits belegt. Das letzte dieser 14 ist ein 
\n.

Jetzt kommen wieder Daten rein.
Zuerst ein a ... passt noch rein in den Ringbuffer.
Dann ein b ... passt auch noch rein
Dann ein c ... passt nicht mehr rein, jetzt habe ich einen 
Bufferoverflow

Jetzt dürfen abc aber nicht mehr im Ringbuffer aufscheinen. Jetzt habe 
ich 2 Möglichkeiten:
 - entweder darf ich Daten nur dann in den Ringbuffer eintragen, wenn 
sie inkl. \n auch wirklich reinpassen oder
 - ich lösche die eingetragenen Daten der letzten Übertragung wieder aus 
dem Ringbuffer raus (also bis exklusive des \n der vorhergehenden 
Übertragung)

von Karl H. (kbuchegg)


Lesenswert?

Heinz B. schrieb:
>>> aBuffer[iBufferWrite + iBufferChar++] = cS1BUF;
> So einfach geht das leider nicht.

Eben.

> Angenommen mein Ringbuffer kann 16 Chars aufnehmen und 14
> Ringbuffer-Elemente sind bereits belegt. Das letzte dieser 14 ist ein
> \n.
>
> Jetzt kommen wieder Daten rein.
> Zuerst ein a ... passt noch rein in den Ringbuffer.
> Dann ein b ... passt auch noch rein
> Dann ein c ... passt nicht mehr rein, jetzt habe ich einen
> Bufferoverflow

Dein Buffer ist voll. Das sollte tunlichst nie passieren. Wenn die 
Gefahr besteht, dann musst du der Gegenstelle signalisieren: Nicht mehr 
weiter senden.

> Jetzt dürfen abc aber nicht mehr im Ringbuffer aufscheinen.

Falsche Strategie.

> Jetzt habe
> ich 2 Möglichkeiten:
>  - entweder darf ich Daten nur dann in den Ringbuffer eintragen, wenn
> sie inkl. \n auch wirklich reinpassen oder
>  - ich lösche die eingetragenen Daten der letzten Übertragung wieder aus
> dem Ringbuffer raus (also bis exklusive des \n der vorhergehenden
> Übertragung)

Die richtige Strategie besteht darin, den Ringbuffer gross genug zu 
machen, dass der verarbeitende Programmteil sich die 
zwischengespeicherten Zeichen rechtzeitig aus dem Buffer holt und so 
Platz für die nächsten Zeichen freimacht. Der Ringbuffer sollte sowieso 
nie mehr als ca. 80% gefüllt sein (in Ausnahmefällen). Wenn du 
regelmässig drüber kommst, den Buffer vergrößern. Wenn das nicht geht 
oder nicht praktikabel ist, dann musst du in der Datenübertragung ein 
Handshake einbauen und so dem Sender mitteilen: Ich kann nicht mehr, 
warte mal ein wenig.
Alternativ kann man auch je nach Anwendung auch einfach mal einen 
Datensatz aus dem Buffer rausholen (also lesen bis zum \n) und die Daten 
ohne Verarbeitung verwerfen.

Aber grundsätzlich sollte es dir nie passieren, dass der Buffer voll 
wird. Denn dann muss irgendetwas auf der Strecke bleiben. Oder anders 
ausgedrückt: Wenn der Wasserhahn deiner Badewanne 2 Liter in der Sekunde 
zulaufen lässt, durch den Ausguss aber nur 1 Liter pro Sekunde 
abfliessen kann, dann wird die Wanne irgendwann übergehen, egal wieviele 
Buffer du zwischen Zulauf und Ablauf dazwischenschaltest.

Umgemünzt auf deinen Fall heißt das:
Wenn der Sender schneller sendet als du verarbeiten kannst, rettet dich 
auch ein Ringbuffer nicht.
Ein Ringbuffer hat einzig únd alleine die Aufgabe Prozesse zu 
entkoppeln. In deinem Fall macht er die Verarbeitung unabhängig vom 
Prozess des Empfanges. Aber ein Ringbuffer kann nicht ein 
grundsätzliches zeitliches Problem einer prinzipiell zu langsamen 
Verarbeitung lösen.

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.