Forum: Mikrocontroller und Digitale Elektronik ringpuffer... ich verstehe es irgendwie nicht...


von Dennis Brücke (Gast)


Lesenswert?

Hi @all mal wieder ;),

ich versuche mich gerade in eine art Konfigurationsoberfläche per UART
zusammen zu basteln. Leider komme ich mit dem "Ringpuffer" Prinzip
zeichen zu empfangen nicht wirklich klar... und diese dann evtl.
Auszuwerten...

in meiner RX_ISR mache ich derzeitig folgendes:
********************************************************************
Uart_RX_Complete:
          push temp1
          push temp2
          push temp3
          in Save_SREG, SREG

          sbr Flags, 1<<USART_RX

          in temp1, udr
          st x+,temp1
          inc rs_puffer
          cpi rs_puffer, 8
          breq rs_count

          out SREG, Save_SREG
          pop temp3
          pop temp2
          pop temp1
reti

rs_count:
          clr rs_puffer
          ldi xl, low(rs232_rx_puffer)
          ldi xh, high(rs232_rx_puffer)
reti
********************************************************************

Wenn ich jetzt davon ausgehe, das ich 8 Zeichen Empfangen habe (welche
ja nach und nach kommen) wie lese ich diese jetzt gescheit aus...
irgendwie bin ich durcheinander... bis jetzt hab ich das immer so
gemacht.

ISR setzt ein Flag das ein byte empfangen wurde, liest dieses aus und
stored es in den SRAM

Main schaut ob RX Flag gesetzt wurde und weis jetzt das ein Zeichen da
ist und springt zu einem RS232 Receive Handler welcher das eine Zeichen
verarbeitet...

nur komme ich im moment nicht mit dem kopf weiter als ein zeichen...

Gruß Dennis

P.S: @peter habe mir deinen Bootloader auch angeschaut nur verstanden
hab ich es immer noch nicht...

von Matthias (Gast)


Lesenswert?

Hi

du solltest, bei der Verwendung von mehreren reti's in einer ISR auch
bei beiden deine Register wieder vom Stack holen.

Matthias

von Dennis Brücke (Gast)


Lesenswert?

@Matthias,

uppss... hab ich noch gar nicht gesehen ;) Wird gleich gemacht ;)
aber verstanden hab ich es leider dennoch nicht... (mit dem auswerten)

Gruß Dennis

von Andi (Gast)


Lesenswert?

Oder einfach mal umstellen:

Uart_RX_Complete:
          push temp1
          push temp2
          push temp3
          in Save_SREG, SREG

          sbr Flags, 1<<USART_RX

          in temp1, udr
          st x+,temp1
          inc rs_puffer
          cpi rs_puffer, 8
          brne URXC1        ;Bei Zeichen < 8 zu URXC1

          clr rs_puffer
          ldi xl, low(rs232_rx_puffer)
          ldi xh, high(rs232_rx_puffer)

URXC1:    out SREG, Save_SREG
          pop temp3
          pop temp2
          pop temp1
          reti

Bei anderweitiger Verwendung von X XL und XH auch noch auf den Stack
pushen und poppen.
Dabei muß dann natürlich deren Inhalt im SRAM abgelegt werden.

Gruß
Andi

von Dennis Brücke (Gast)


Lesenswert?

@Andi,

ok auch danke für den Hinweis ;) spart ein wenig mehr Code ;)
nur auch dies ist noch nicht wirklich die beantwortung / hilfestellung
für meine Frage ;)

Wie werte ich den Ringpuffer den ich mit jedem ISR mit einem Zeichen
fülle wieder aus ?

Gruß Dennis

von Andi (Gast)


Lesenswert?

Ich schätze, Du benötigst 2 mal einen Pointer für den Ring-Buffer.
Zum einen einen für die ISR und zum anderen einen für das
"Abholprogramm" in der Main.
Das Abholprogramm vergleicht zuerst seine Zugriffsadresse mit der
Zugriffsadresse der ISR.
Sind beide Adressen identisch, sind keine neuen Bytes zum abholen da
sind sie unterschiedlich ist mindestens 1 Byte zum abholen da.
Genauso die ISR.
Die prüft vorher auch ihre Zugriffsadresse mit der vom Abholprogramm
als Kontrolle, ob vom vermeintlich noch vollem Buffer mindestens 1 Byte
abgeholt wurde oder nicht.
Ist die Zugriffsadresse von der ISR um 1 kleiner, mit Berücksichtigung
des 8-ter Sprunges, als die vom Abholprogramm, ist der Buffer noch voll
ansonsten kann die ISR ein weiteres Byte in den Buffer schreiben.
Ich würde an Deiner Stelle erst mal die Zugriffsadressen im SRAM
speichern, sonst hast Du irgend wann keinen X- und Y-Pointer mehr für
andere Dinge zur Verfügung.

Gruß
Andi

von Andi (Gast)


Lesenswert?

Ach ja, die Zugriffsadressen sind ja nur 1 Byte groß (0 - 7).
Mit 8Bit-Befehlen kannst Du einfache Vergleiche durchführen und wenn Du
die konkrete Adresse benötigts einfach die Zugriffsadresse auf den
Anfang des Buffers addieren:

 lds r16,ISRAdr
 ....
 ....
 ldi xl,low(RingBuffer)
 ldi xh,high(RingBuffer)
 add xl,r16
 clr r16
 adc xh,r16

.dseg
ISRAdr:     .byte 1
RingBuffer: .byte 8

Gruß
Andi

von Dennis Brücke (Gast)


Lesenswert?

@Andi,

klingt als währst Du ein wenig skeptisch ;) Hab ich vieleicht das
Prinzip allgemein mir selber "VerKompliziert"?

Oder ist die Herangehensweise mit dem Flag setzen und in Main
überprüfen schon der richtige weg ?

Gruß Dennis

von Andi (Gast)


Lesenswert?

Ich bin überhaupt nicht skeptisch!
Das mit dem Flag ist vielleicht nicht der falsche Weg.
Aber mit für Beide Routinen, ISR-Empfang und Abholprogramm im Main, ist
es glaube ich einfacher und effizienter es mit 2 Pointern zu machen.
Man kann es auch wie folgt machen:

Variablen:
1. Pointer für die ISR = BuffISRAdr (1 Byte)
2. Pointer für das Abholprogramm = BuffMainAdr (1 Byte)
3. Füllstandsanzeige für den Buffer = BuffFill (1 Byte)
4. Der Buffer = Buffer (8 Bytes)

Die ISR prüft vor dem ablegen eines Byte BuffFill.
Ist BuffFill kleiner als 8 kann das Byte an der Adresse BuffISRAdr +
Buffer abgelegt werden, BuffFill um 1 erhöht werden und BuffISRAdr um 1
erhöht werden (bei 8 auf 0 setzen).
Ansonsten ist der Buffer voll weil vom Abholprogramm noch nichts
abgeholt wurde und das Byte verschwindet dann.
Genauso überprüft das Abholprogramm BuffFill.
Ist BuffFill ungleich 0 kann mindestens ein Byte abgeholt
werden,BuffFill um 1  verringert werden  und BuffMainAdr um 1 erhöht
werden (bei 8 auf 0 setzen).
Bei BuffFill = 0 ist kein Byte zum abholen da.
Im Abholprogramm kann das dann in einer Schleife durchgeführt werden
bis eine Kopie von BuffFill für die Schleife 0 ist.

Die Variablen dafür kann man ja im SRAM speichern und mit LDS und STS
darauf zugreifen:
.dseg
BuffISRAdr:  .byte 1
BuffMainAdr: .byte 1
BuffFill:    .byte 1
Buffer:      .Byte 8

Gruß
Andi

von Dennis Brücke (Gast)


Lesenswert?

@Andi,

also das letzte hab ich nun wirklich nicht so ganz verstanden ... :(
habe jetzt erfolgreich geschaft Byte für Byte im SRAM abzulegen.

Mache in der RX_ISR jetzt folgendes:

    ldi xl, low(RingBuffer)      RingBuffer adresse laden
    ldi xh, high(RingBuffer)

    lds temp1, BufferAnzahl      Anzahl der Zeichen
    add xl, temp1                zur Adresse addieren
    clr temp2
    adc xh, temp2

    in temp2, udr                UDR einlesen
    st x, temp2                  und an aktuelle adr store
    inc temp1                    Dann +1 der Zeichen
    cpi temp1, 8                 vergleichen mit max
    brne nochok1                 Überlauf?
    clr temp1                    dann anzahl auf 0
    sts BufferAnzahl, temp1      und storen

nochok1:
    sts BufferAnzahl, temp1      ansonsten store akt. anz.

    sbr Flags, 1<<USART_RX       Flag setzen für Zeichen da


Das Speichern in den Ringpuffer funktioniert prinzipiell schon.
Ich habe mir mal Peter Danneger seinen Bootloader angeschaut, und finde
es sehr interessant wie er die empfangen "Strings" per Sprungtabelle
auswertet nur so richtig verstanden hab ich das ganze ganz und gar
nicht... war ne lange ratlose nacht gestern...

Warum ich das ganze immer noch nicht verstehe ? Keine Ahnung nach so
vielem Lesen und Schreiben meines Sources vollkommen durcheinander...
arggg... schrei

Gruß Dennis

P.S.: @Andi, vieleicht erbarmst Du dich noch einmal und versuchst es
mir anders zu erklären... thnks.

von Ralf (Gast)


Lesenswert?

Hi Dennis,

hast du allgemeine Verständnis-Probleme mit Ringspeichern? Guck dir mal
diesen Link an, vielleicht hilft er dir:

http://www.zeiner.at/c/ringbuffer.html

Ich habe diesen jetzt mal mit Abwandlungen für mich übernommen, und es
geht, soweit ich das beurteilen kann. Ich brauche ihn, genauso wie du,
für meine serielle Schnittstelle. Dort habe ich das Problem, dass ich
trotz Hardware-Handshake einige Bytes verliere (der PC reagiert nicht
schnell genug auf das Sperren der Handshake-Leitungen), daher mache ich
es nun über den Ringbuffer.

Gruß Ralf

von Dennis Brücke (Gast)


Lesenswert?

Hi Ralf,

wie ich den Ringpuffer aufbauen muss um zu speichern, hab ich soweit
verstanden... nur wie ich diesen dann auswerte, da hängt es bei mir...
ein einzelnes Byte auswerten ist ja nicht wirklich ein Problem nur
mehrere Bytes auswerten und vor allem den Puffer dann wieder richtig
auszulesen. Diese Kombination zwischen auslesen und auswerten, da hängt
es irgendwie, das ich einfach nicht weis wie ich es aufbauen soll.

Nehmen wir einfach mal an, das ich sage ich möchte die Uhrzeit in einem
DS1337 speichern. z.B. Byte C für Uhr einstellen und dann zeit
übergeben. (Ist aber nur ein Beispiel für mein Problem)

Gruß Dennis

von Andi (Gast)


Lesenswert?

Tja, das mit dem Auswerten ist halt so ne Sache.
Kommt auch darauf an, was Du vor hast.
Willst Du Deinem Kontroller Befehle in Klartext übergeben brauchst Du
komplette Vergleiche von Zeichenketten.
Willst Du Byte-Befehle übergeben ist das einfach mit Zahlenvergleichen
zu machen und dann halt noch die Parameter (Uhrzeit) in entsprechend
gleicher Reihenfolge zu übergeben.
Der Buffer ist nur dazu da, damit der Prozessor bei extremer
Beschäftigung anderswo möglichst keine Bytes verliert.
Und wenn er das doch tut, kann man den Buffer vergrößern.
Für die Auswertung von mehreren Bytes brauchst Du ein weiteres
Zwischenlager im SRAM mit x Bytes wo das Abholprogramm (GET) die Bytes
der Reihe nach hineinschreibt.
Diese Größe hängt auch von dem ab, was Du anstellen willst.

Gruß
Andi

von Ralf (Gast)


Lesenswert?

Hi Dennis,

jetzt wird mir dein Problem klarer.

Wenn ich es richtig verstanden habe, brauchst du das gleiche, was ich
für meinen ISP-Programmer realisiert habe:

Ich würde erstens mal das AUSLESEN vom AUSWERTEN komplett trennen.
Der Ringbuffer ist nur für deine Schnittstelle da, sonst für NIX.

Ich habe mir Gedanken zum Datensatz-Aufbau gemacht, den ich für den
Programmer verwende. Das sieht bei mir wie folgt aus:

Das erste Byte ist immer ein darstellbares Zeichen aus dem ASCII-Satz
und dient als "Befehlskennung"
Alle weiteren Zeichen des Datensatzes sind immer ASCII-Hex-Zeichen,
also "0" - "9" und "A" - "F" und somit Daten. Jeder Befehl wird
mit dem CR-Zeichen (Return) abgeschlossen.

Ich empfange die Daten und setze sie in einen Ringbuffer, der nur für
die serielle Schnittstelle da ist. Aus dem Ringbuffer lese ich die
Daten in den "Befehlsbuffer" bis zum CR-Zeichen ein, wobei ich alle
Zeichen bis auf das erste dahingehend prüfe, ob es wirklich
ASCII-Hex-Zeichen sind.

Dann wird aus dem Befehlsbuffer das erste Zeichen (-> Befehlszeichen)
gelesen und geprüft, ob der Befehl bekannt ist. Wenn ja, übergebe ich
den kompletten Befehls-Buffer der zum Befehl gehörenden Subroutine, die
ihrerseits die Auswertung der restlichen Daten vornimmt (passt die Menge
der Daten zum Befehl, sind die Daten unsinnig, usw.). Zum Beispiel eine
Routine für das Lesen, wobei der Aufbau wie folgt ist:

RAAAAMM

R als Klartext (ASCII-Zeichen "R") für Read
AAAA ASCII-Hex-Zeichen für die Adresse
MM ASCII-Hex-Zeichen für die Menge der auszulesenden Datenbytes

In deinem Fall heisst das dann wohl, das du beim Befehl "C" die
SetClock-Routine aufrufst, die die restlichen Daten im Befehlsbuffer
prüft, und wenn sie gültig sind, schreibst du die Uhrzeit in deinen
Uhren-IC.

Kannst du damit was anfangen?
(Nebenbei, wenn es sich einfacher lösen lässt, bin ich für Vorschläge
dankbar ;-))

Falls du den entsprechenden Code-Schnipsel haben möchtest, sag
Bescheid.

Gruß Ralf

von Peter D. (peda)


Lesenswert?

@Dennis,

der Trick bei meinem Empfangspuffer ist, daß er komplette 256 Byte
belegt.
Wenn man dann das High-Byte der Adresse konstant hält:

;                               Receive Interrupt
;------------------------------------------------
URXCint:
        ldi     xh, high(rx_buff)       ;set high address byte
        in      ia0, UDR
        st      x+, ia0
        reti

ergibt sich ein automatisches Umlaufen ohne irgendwelche Tests.
Der X-Pointer ist dabei für den Interrupt reserviert, da man ja im
Hauptprogramm bequem mit 2 Pointern (Y,Z) auskommt.

Und bei der Auswertung wird dann auch immer nur das Low-Byte
hochgezählt:

exec_command:
        ldi     zl, low( command_table * 2 - 3 )
        ldi     zh, high( command_table * 2 - 3 )
        mov     a1, yl                  ;save command start
_coi1:  adiw    zl, 3                   ;skip rjmp + odd zero byte
        andi    zl, 0xFE                ;adjust to next word
        mov     yl, a1
        clt
        rjmp    _coi3
_coi2:  ld      a0, y
        inc     yl                      ;increment low address only !
        eor     a0, r0
        andi    a0, 0x5F                ;ignore case
        breq    _coi3
        set                             ;not matching
_coi3:  lpm     r0, z+
        tst     r0
        brne    _coi2
        brts    _coi1                   ;test next command
        lsr     zh                      ;byte to word address
        ror     zl
        ijmp                            ;jump after command string


Somit ist kein extra Umkopieren notwendig.
In der command_table stehen die einzelnen Kommandos gefolgt von einem
RJMP zu der entsprechenden Funktion.

Frag ruhig, wenn Du etwas nicht verstehst.


Peter

von Andi (Gast)


Lesenswert?

Aber man könnte es auch noch mittels AND auf eine 2-er-Potenz
verkleinern (für andere Zwecke):
Z. B. für einen Buffer mit 64 Byte:
URXCint:
        ldi     xh, high(rx_buff)       ;set high address byte
        in      ia0, UDR
        st      x+, ia0
        andi    xl,0x3F   ;0b00111111
        reti

Das AND sorgt dafür, das sich der Pointer nur von 0 bis 63 (relative
Adresse) bewegt.
Bei 32 oder 16 Byte dann "and xl,0x1F" oder "and xl,0x0F"
Natürlich muß man auch da aufpassen, das der Buffer keine 256
Byte-Schwelle überschreitet was man mit .org Sicherstellen kann.

Gruß
Andi

von Peter D. (peda)


Lesenswert?

@Andi,

URXCint:
        ldi     xh, high(rx_buff)       ;set high address byte
        in      ia0, UDR
        st      x+, ia0
        andi    xl,0x3F   ;0b00111111
        reti

Oh oh oh oh, ganz böses Foul !

"andi    xl,0x3F" ändert das SREG, also schleunigst sichern !!!


Peter

von Andi (Gast)


Lesenswert?

Ups...
... ist schon klar.
War ja nur mal als Beispiel eingeworfen.
OK, kostet wieder Rechenzeit, und wie es bei Deiner Routine aussieht,
ändert kein Befehl das SREG was wiederum Zeit spart wenn man es nicht
sichern (und andere Register) muß.

Gruß
Andi

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.