Forum: Mikrocontroller und Digitale Elektronik Timing Problem


von Carsten (Gast)


Lesenswert?

Hallo Forum...
im Laufe der Zeit hat sich mein Programm und die Anforderung an Dieses
doch erheblich erweitert.
Mittlerweile stehe ich vor einem erheblichen Timing-Problem.
Wie würdet Ihr die Folgenden Aufgaben lösen:

-Tasten entprellen (nach Peter Dannegger)(Timer0)
-bei gedrückter Taste Nachricht mit UART absetzen
-alle 30 Minuten diese Nachricht wiederholen (Timer0 mit Zähler)
-regelmässig UART-Nachrichten empfangen und auswerten (ca 80-120
Zeichen)
-Bei Bedarf Teile dieser Nachricht ins EEPROM schreiben.
Bis jetzt habe ich in der Timer-Schleife die Tasten entprellt und 30
Minuten hochgezählt. Schon da habe ich das Risiko, während des sendens
einer neuen Nachricht ausgerechnet im 30-Minutentakt unterbrochen zu
werden.
Jetzt kommt aber das Empfangen hinzu.
Da die Abfrage nach neuen Nachrichten öfter geschehen muss, kann ich
den UART-Interrupt nicht verwenden, weil der mir jedes Senden einer
Nachricht unterbricht.
Baue ich aber das Senden und das Empfangen hintereinander und schalte
so lange den Interrupt aus, ist jede Zeitzählung im Eimer.

Habe ich hier grundlegende Gedankenfehler gemacht ?

von crazy horse (Gast)


Lesenswert?

hm, sehe kein wirkliches Problem. Spendiere der UART je einen Sende- und
Empfangsbuffer (Ringspeicher) und lass sowohl Sender als auch Empfänger
im Interrupt laufen.

von Carsten (Gast)


Lesenswert?

...hört sich einfach an.
Wie geht das in ASM ?
Welchen Interrupt meinst Du? Timer0?

von Alex (Gast)


Lesenswert?

Den UART-Interrupt. Wie das geht erfährst du im Tutorial bzw.
Datenblatt.

von crazy horse (Gast)


Lesenswert?

nö - UART-Interrupt.
Damit entlastest du den Prozessor ganz erheblich, da er nicht
Ewigkeiten auf komplette Zeichen oder gar Zeichenketten warten muss.
Das macht dir natürlich jedes Timing kaputt.
Such mal hier im Forum wie das geht, ich und andere haben das schon
ausführlich beschrieben. Deine zu sendenden Zeichen werden nicht direkt
ins UART geschrieben, sondern in einen reservierten RAM-Bereich
(TX-Buffer), das geht ganz schnell, und damit ist für dich das Thema
Senden erledigt. Ist die UART bereit für das Senden eines neuen
Zeichens, holt sich das Interruptprogramm das Zeichen aus dem RAM und
sendet es, falls es was zu Senden gibt. Beim Empfangen genauso: kommt
ein Zeichen reingetröpfelt, schreibt das Int-Programm des Receivers
dies in den RAM. Du kannst dann nachschauen, ob was und wieviel
angekommen ist, wenn du Zeit hast.

von Carsten (Gast)


Lesenswert?

Hast Du zufällig einen Suchbegriff, mit dem ich zu Ziel finde ?
...letztenendes muss ich doch ohnehin jedes byte über's UART
schieben.
Wenn ich das tue habe ich immer noch das problem, dass beim Senden ein
Empfang dazwischen kommen kann oder ?

von crazy horse (Gast)


Lesenswert?

da z.B. http://www.mikrocontroller.net/forum/read-1-70900.html#70952
Du verwechselst da glaub ich ein paar grundlegende Sachen.
Das Senden eines Bytes löst man durch einen einfachen Schreibbefehl
out UDR, xxx aus, das geschieht in wenigen Maschinentakten. Dann kommt
der langsame Teil: das Byte wird tatsächlich rausgeschoben, und das
dauert. Nehmen wir mal 9600Baud, ein Bit dauert also 1/9600 dauert also
rund 104µs, das ganze Byte incl. Start-und Stoppbit also rund 1ms.
Wartest du nun diese Zeit nur ab, um das nächste Byte zu senden,
vertust du bei sagen wir mal 4MHz rund 4000 Takte für nichts pro Byte.
Die Zeit kann man sinnvollerweise besser nutzen, wenn die UART per
Interrupt Bescheid sagt, dass jetzt ein neues Zeichen gesendet werden
könnte. Im Prinzip kostet dich das Ganze so gut wie gar keine
Prozessorzeit - wenn man es richtig macht.

von Carsten (Gast)


Lesenswert?

Ja, das sehe ich ein.
Welcher Interrupt ist das denn dann ? TXCIE ?
Und was passiert, wenn eine Message rein kommt, bevor alles gesendet
ist ?

von Carsten (Gast)


Lesenswert?

...hat vielleicht mal jemand einen ASM Codeschnipsel zum Thema TX/RX
Buffer für mich ?

von ...HanneS... (Gast)


Lesenswert?

UART empfängt im Hintergrund (rein hardwaremäßig), auch wenn
gleichzeitig per UART gesendet wird, und meldet sich per Interrupt,
wenn ein weiteres Byte komplett empfangen wurde. Dieses ist dann in
der ISR auszulesen, um Platz für das nächste zu empfangende Byte zu
machen.
Mit UDR (als I/O-Adresse) wird bei IN und OUT auf verschiedene
I/O-Register (Empfangsregister, Senderegister) zugegriffen. Es sind
also zwei "Einwegregister" ("Einbahnstraßen") zu einem Namen (einer
Adresse) zusammengefasst.

...

von Carsten (Gast)


Lesenswert?

...ok, das habe ich verstanden.
Damit wäre Empfang und Senden kein Problem.
Jetzt kommt das nächste Problem:
Grundsätzlich setze ich Kurznachrichten über das UART ab.
Dazu muss ich zuerst den Befehl senden, einen Augenblick warten und
dann den Text hinterher schicken.
Jetzt kommts...
über den Timer möchte ich regelmässig nachschauen, ob neue SMS'en
eingetroffen sind. Also Befehl raus und per Interrupt das Ergebnis
empfangen.
So weit, so gut.
Da ich natürlich nicht sofort reagieren kann, muss das Ergebnis
zwischengespeichert werden.
Ich kapier das mit dem RX-Buffer nicht.
Hier im Forum finde ich immer nur Beispiele ala Tutorial, die
sinnvoller weise direkt ein Byte an die LED's rauspumpen oder irgend
welche kryptischen Beschreibungen.
Gibt's da nicht irgendwo ein Beispiel in ASM?
Wenn möglich, ohne wieder jede Menge Register zu verbraten.
Die werden nämlich langsam knapp.

von ...HanneS... (Gast)


Lesenswert?

Hi...

Es gibt SRAM.
Und es gibt Pointer, die darauf "zeigen" können.
Und es gibt ASM-Befehle, die das SRAM über Pointer, aber auch direkt
ansprechen können.

Man kann also (wegen Registermangel) einen Pointer (2 Bytes) im SRAM
(feste Adresse) "aufbewahren. Wenn man ihn braucht, holt man ihn mit 2
LDS-Befehlen ins X-, Y- oder Z-Registerpaar. Dann kann mit
dementsprechenden LD- oder ST- Befehlen auf (einen reservierten Teil
des) SRAM zugreifen, wobei bei Autoin/decrement sogar der Pointer
verändert wird. Durch Vergleich des Pointers mit den Grenzen des
reservierten SRAM-Bereiches (Adresse) kann der Pointer als Ring geführt
werden und je nach Bedarf 16...32 Bytes überspannen. Ist die Aktion mit
dem SRAM-Zugriff fertig (manchmal schon nach einem Byte), dann wird der
Pointer (für die nächste Aktion) wieder im SRAM "verwahrt". Dadurch
können andere Programmteile dieselben Pointerregister (aber mit anderen
Inhalten) nutzen.

...

von Carsten (Gast)


Lesenswert?

Ich denke mal die Adresse muss ich dann selbst festlegen und den
Adressbereich auch selber reservieren ?
Das SRAM ist doch der Teil, den ich im Studio unter Data finde ?
Gibt's da einen bestimmten Bereich, den man bevorzugen sollte ?
Wenn ich mein Programm im Debug-Modus starte, kann ich sehen, dass sich
am Ende des SRAMS was tut.

von ...HanneS... (Gast)


Lesenswert?

Das Ende ist Stack. Denn du setzt ja den Stackpointer nicht zum Spaß
nach "RAMEND". Der Stackpointer geht nach unten, also der Stack
wächst vom RAM-Ende nach unten. Wie groß der Stackbedarf ist, hängt von
deinem Programmierstil ab. Das SRAM vom Anfang (also hinter dem
I/O-Bereich, bei einigen Megas hinter dem extendet-I/O-Bereich) bis zum
max vermuteten Stack musst du selbst verwalten, Bill Gates macht das
nicht, denn hier läuft kein OS im Hintergrund. ;-)

...

von Carsten (Gast)


Lesenswert?

...das heisst, wenn ich am Anfang (0060) beginne, kommt mir nichts in
die quere, es sei denn ich hab es explizit dahin geschrieben ?
...außer der Stack natürlich, wenn's zu eng wird...
Wie mache ich das dann ?
Reset:
STS 00,0060  ;Lowbyte Pointer
sts 62,0061  ;Highbyte Pointer
...

Lesen:
;Pointer holen
LDS XL,0060  ;Lowbyte in X-Register laden
LDS XH,0061  ;Highbyte in X-Register laden

mov temp1,??? da weiß ich nicht weiter
adiw XH:XL,1
jetzt hier gucken ob Ende erreicht dann wieder Pointer reset
sonst
sts 00,XL
sts 62,XH

oder wie ?

von ...HanneS... (Gast)


Lesenswert?

Hi Carsten...

Vielleicht hättest du dich doch mal mit dem Quelltext beschäftigen
sollen, den ich dir vor längerer Zeit per Mail schickte...

SRAM kann nicht mit Konstanten ansprechen, sondern nur mit Registern.
Die SRAM-Adressen kann man zwar als Konstanten angeben, besser sind
aber Labels. Interpretiere sie einfach als "Variablennamen". Diese
müssen natürlich eingerichtet werden. Z.B. so:

.dseg                   ;SRAM-Variablen (im Datensegment)
sekunde:    .byte 1     ;aktuelle Sekunde (Addr $60)
minute:     .byte 1     ;aktuelle Minute  ($61)
stunde:     .byte 1     ;aktuelle Stunde  ($62)
wotag:      .byte 1     ;aktueller Wochentag ($63)
frequenz:   .byte 3     ;gemessene Frequenz (L-Byte zuerst) ($64-$66)
impulsl:    .byte 3     ;gemessene Impulspausendauer (L zuerst)
impulsh:    .byte 3     ;gemessene Impulsdauer (L zuerst) ($6a-$$6c)
batt0:      .byte 1     ;Batteriezustand, Nachkomma (Mittelwert)($6d)
batt1:      .byte 1     ;Batteriezustand, volle Prozentwerte ($6e)
astring:    .byte 27    ;Startadresse für LCD-Ausgabestring im S-RAM

(siehe auch den gesendeten Quelltext)

Willst du Variablen davon verwenden, dann musst du sie in ein Register
laden und nach der Veränderung wieder ins SRAM zurück spichern:

 lds wh,sekunde             ;Sekunde aus SRAM holen
 inc wh                     ;erhöhen
 sts sekunde,wh             ;erstmal zurück ins SRAM
 cpi wh,60                  ;Überlauf?
 brlo ausgabe               ;nein...
 clr wh                     ;ja, von 0 beginnen
 sts sekunde,wh             ;Korrektur zurück ins SRAM

Das Beispiel zeigt zwar nur eine billige Uhr, aber es geht ja um die
Schreibweise beim SRAM-Zugriff. Das Label "sekunde" steht hier für
die Speicherstelle $60 im SRAM, in der 1 Byte Platz hat. Adresse $60
deshalb, weil sie zuerst deklariert wurde. Die Adressen interessieren
aber im Programm nicht mehr, dafür haben wir ja die Labels.

Gruß...
...HanneS...

von Carsten (Gast)


Lesenswert?

...sorry HanneS...
Natürlich habe ich mir Deinen Quelltext angeschaut.
Hab da aber speziell zu diesem Thema nicht gesucht und muss gestehen,
dass ich bei diesem Problem nicht dran gedacht habe schäm.
>das Beispiel zeigt nur eine billige Uhr
Ich wäre froh, wenn ich so weit schon wäre.
Das mit dem .dseg hab ich für die Befehle als Text (hier eseg) schon
verwendet (s.Tutorial Speicher). Ich wäre allerdings nie drauf
gekommen, dass man so leicht auch den Speicherbereich "zuweisen"
kann.
Naja, Tutorials sind nunmal doch reduziert auf das Wesentliche. Die
Fragen kommen immer erst mit dem Programmieren.

...wie auch immer...
Vorerst 1000 Dank,
ich halt Dich auf dem Laufenden...

von ...HanneS... (Gast)


Lesenswert?

Carsten, dein Problem ist die abstrakte Programmier-Denkweise, die du
dir im Laufe der Zeit durch die Arbeit unter einem OS angewöhnt hast.
Bei AVR-ASM hat man kein OS und muss alles selbst machen (was aber auch
den Vorteil hat, dass einem Bill nicht dazwischen pfuscht!).

Da ich das nachvollziehen kann, greife ich manchmal (auch in anderen
Threads) zu überspitzen Vergleichen. Dies dient zum "Wachrütteln",
nicht etwa zum Beleidigen und mit Humor zu verstehen...

Gruß...
...HanneS...

von Carsten (Gast)


Lesenswert?

Ja, das passt schon.
Du hast ja Recht.
Man sollte erst mal lesen, was man hat.
Keine Sorge, bin nicht beleidigt.
Das Thema Speicherverwaltung ist natürlich absolutes Neuland für mich
und doch etwas abstrakt.
Im Moment sind Tips und Anregungen zwar immer willkommen aber ohne den
Code teilweise sehr schwer nach zu bauen.
Nachvollziehen kann ich eigentlich bis auf die Tastenentprellung bisher
alles.
Nur an der Umsetzung fehlt es in einigen Bereichen halt noch.
Aber mit Eurer Hilfe schaffe ich das auch.

von Carsten (Gast)


Lesenswert?

Ich bin grade dabei das aus zu probieren...
Definition:
.dseg               ;SRAM
RX_Pointer: .byte 2  ;SRAM Adresse 60/61
RX_Text:    .byte 32 ;SRAM Adressen 62-82

Reset:
  ldi temp1,$62               ;Pointer für RX-Buffer
  sts LOW(RX_Pointer),temp1   ;initialisieren
  ldi temp1,$00
  sts HIGH(RX_Pointer),temp1

wenn ich das so durchticker, wird aber $62 in Speicher $60
geschrieben.
Ich muss ihm aber schon einen Startwert vorgeben ?

von ...HanneS... (Gast)


Lesenswert?

Moin...

Reset:
  ldi temp1,$62               ;Pointer für RX-Buffer
  sts LOW(RX_Pointer),temp1   ;initialisieren
  ldi temp1,$00
  sts HIGH(RX_Pointer),temp1

Nööö...

 ldi temp1,low(RX_Text)     ;unteres Byte der Buffer-Adresse ($62)
 sts RX_Pointer, temp1      ;in unteres reserviertes Byte,
 ldi temp1,high(RX_Text)    ;oberes Adressbyte des Buffers (00)
 sts RX_Pointer+1,temp1     ;in das darüberliegende Byte

high(RX_Text) ergibt $00, denn das Highbyte der Adresse, die das Label
RX_Text verkörpert ist in diesem Falle ja 0...

Wenn es dann Qiuck&dirty sein soll und die Ringbufferzugriffe sehr
schnell erfolgen sollen, dann definiert man zuerst die Ringbuffer, so
dass alle benötigten Ringbuffer noch im Speicherbereich unter $100
liegen. Dann braucht man sich nur noch um das Low-Byte der Pointer zu
kümmern, das H-Byte ist immer 0 und brauch weder im SRAM gesichert
werden noch beim Inkrementieren beachtet werden. Das spart
Speicherplatz, macht die Bufferzugriffe (in ISR) schneller, hat aber
den Nachteil, dass es bei Portierung auf Megas mit extendet-I/O-Bereich
zu Problemen kommt. Alternativ kann man alle Ringpuffer in den Bereich
$100-$1ff legen, dann muss 1 ins High-Byte der Pointer. 0 wäre aber
besser, da man wegen der 16-Bit-Addiererei (Übertrag mittels Carry)
sowiso ein (meist unteres) Register namens "null" reserviert hat, das
immer den Wert 0 hat.

...

von Carsten (Gast)


Lesenswert?

...gibt es eigentlich eine Möglichkeit den seriellen Empfang zu
simulieren (AVR Studio) ?
Gibts da ne virtuelle Schnittstelle oder ein Eingabefenster ?
Online auf dem STK500 geht's ja glaub ich nicht.

von Carsten (Gast)


Lesenswert?

Jetzt hab ich endlich eine Routine für den SRAM-Buffer gefunden, bekomme
sie aber nicht zum laufen...

;Definitionen
.equ  RX_fifo_length =32
; SRAM-Section ---------------------------------------------
.dseg
Save_XReg:    .byte 2   ;Backupadresse für X-Register
RX_fifo_base: .byte RX_fifo_length ;Reserviert den Platz für
fifo_länge
RX_fifo_n:    .byte 1  ;Anzahl der im Puffer gespeicherten Bytes
RX_fifo_in:   .byte 2  ;Pointer geschriebenes Byte
RX_fifo_out:  .byte 2  ;Pointer gelesenes Byte
;-----------------------------------------------------------

;Initialisierungsteil
ldi temp1,0      ;Zähler für gespeicherte
sts rx_fifo_n,temp1    ;Bytes zurück setzen
ldi temp1,LOW(rx_fifo_base)  ;Pointer für zu schreibendes
sts rx_fifo_in,temp1    ;Byte auf Datenspeicheradresse setzen
ldi temp1,HIGH(rx_fifo_base)  ;
sts rx_fifo_in+1,temp1    ;
ldi temp1,LOW(rx_fifo_base)  ;Pointer für zu lesendes
sts rx_fifo_out,temp1    ;Byte auf Datenspeicheradresse setzen
ldi temp1,HIGH(rx_fifo_base)  ;
sts rx_fifo_out+1,temp1    ;

;------------------------------------------------------------
;Zeichen ins SRAM schreiben
Add_RX_Fifo:
  push   temp1
  push   temp2
  sts  Save_XReg,XL    ;X-Register sichern
  sts  Save_XReg+1,XH    ;
  in  temp1,UDR  ;Byte aus dem Empfangsregister holen
  lds  temp2,rx_fifo_n  ;Anzahl geschriebener Bytes holen
  cpi  temp2,rx_fifo_length  ;Mit maximaler Länge des
                                        ;Datenbereiches vergleichen
  breq  end_add_rxfifo  ;Wenn Buffer voll, nicht überschreiben
  lds  XL,rx_fifo_in  ;Schreibadresse holen
  lds  XH,rx_fifo_in+1  ;
  st  X+,temp1  ;Datenbyte in Adresse aus X-Register speichern
        ;und X-Register um eins erhöhen
  lds  temp1,rx_fifo_n  ;Anzahl geschriebene
  inc  temp1    ;Zeichen erhöhen
  sts  rx_fifo_n,temp1  ;und sichern
  cpi  XL, LOW(rx_fifo_base+rx_fifo_length);16-Bit Vergleich
                                ;Speicherposition mit X-Register
  ldi  temp2,HIGH(rx_fifo_base+rx_fifo_length)
  cpc  XH,temp2
  breq  add_rxfifo_rollover
End_add_rxfifo:
  sts  rx_fifo_in,XL  ;fifo_in speichern
  sts  rx_fifo_in+1,XH
  lds  XL,Save_XReg  ;X-Register restaurieren
  lds  XH,Save_XReg+1
  pop  temp2
  pop  temp1
  ret
Add_rxfifo_rollover:
  ldi  XL,low(rx_fifo_base)  ;niedrigste Adresse einstellen
  ldi  XH,High(rx_fifo_base)  ;um alte Daten zu überschreiben
  rjmp  End_add_rxfifo

; SRAM lesen -----------------------------------------------
Get_RX_fifo:
  cli
  push  temp1
  push  temp2
  sts  Save_XReg,XL  ;X-Register sichern
  sts  Save_XReg+1,XH  ;
  lds  temp1,rx_fifo_n  ;Anzahl geschriebener Bytes holen
  tst  temp1    ;Testen ob überhaupt was im Speicher ist
  breq  end_get_rxfifo  ;sonst gibt's nix zu tun
  lds  XL,rx_fifo_out  ;Lesepointer holen
  lds  XH,rx_fifo_out+1;
  ld  Text,X+    ;Text in Variable"Text" einlesen und
                                ;Pointer erhöhen
  lds  temp1,rx_fifo_n  ;Anzahl Zeichen laden
  dec  temp1    ;um 1 reduzieren
  sts  rx_fifo_n,temp1  ;Anzahl sichern
  cpi  XL,Low(rx_fifo_base+rx_fifo_length);16-Bit Vergleich
                                ;Speicherposition mit oberster Grenze
  ldi  temp2,High(rx_fifo_base+rx_fifo_length)
  cpc  XH,temp2
  breq  get_rxfifo_rollover
End_get_rxfifo:
  sts  rx_fifo_out,XL  ;Leseadresse sichern
  sts  rx_fifo_out+1,XH
  lds  XL,Save_XReg  ;X-Register wieder herstellen
  lds  XH,Save_XReg+1  ;
  pop  temp2
  pop  temp1
  sei
  ret
Get_rxfifo_rollover:
  ldi  XL,LOW(rx_fifo_base)
  ldi  XH,HIGH(rx_fifo_base)
  rjmp  end_get_rxfifo

Wenn ich diese Routinen im Simulator durchlaufe, scheint alles zu
klappen.
Mache ich das im Prozessor, hat er bestenfalls ein delay von einem
Zeichen.
Meistens kommt aber nur Mist raus.
Der Speichervorgang wird vom UART ausgelöst, gelesen wird zyklisch.
Die Quelle ist die DesignNote 029 von den AVR-Freaks.
Ich glaube das Programm verstanden zu habe aber komme nicht dahinter,
wo der Wurm steckt.
Wäre vielleicht jemand so nett und könnte sich das mal anschauen ?
Danke

von ...HanneS... (Gast)


Lesenswert?

Hi...

;Definitionen
.equ  RX_fifo_length =32
; SRAM-Section ---------------------------------------------
.dseg
Save_XReg:    .byte 2   ;Backupadresse für X-Register
RX_fifo_base: .byte RX_fifo_length ;Reserviert den Platz für
fifo_länge
RX_fifo_n:    .byte 1  ;Anzahl der im Puffer gespeicherten Bytes
RX_fifo_in:   .byte 2  ;Pointer geschriebenes Byte
RX_fifo_out:  .byte 2  ;Pointer gelesenes Byte
;-----------------------------------------------------------

Soweit ok, wobei ich das Pointerregister im Stack sichern würde.


;Initialisierungsteil
ldi temp1,0      ;Zähler für gespeicherte
sts rx_fifo_n,temp1    ;Bytes zurück setzen
ldi temp1,LOW(rx_fifo_base)  ;Pointer für zu schreibendes
sts rx_fifo_in,temp1    ;Byte auf Datenspeicheradresse setzen
ldi temp1,HIGH(rx_fifo_base)  ;
sts rx_fifo_in+1,temp1    ;
ldi temp1,LOW(rx_fifo_base)  ;Pointer für zu lesendes
sts rx_fifo_out,temp1    ;Byte auf Datenspeicheradresse setzen
ldi temp1,HIGH(rx_fifo_base)  ;
sts rx_fifo_out+1,temp1    ;

Nicht falsch, obwohl das kürzer machbar ist, denn beide Pointer haben
dieselbe Anfangsadresse, das doppelte LDI ist daher unnötig.


;------------------------------------------------------------
;Zeichen ins SRAM schreiben
Add_RX_Fifo:

;Das ist eine ISR, ausgelöst vom UART, ein neues Byte ist
;eingetroffen. Daher ist jetzt erstmal das SREG zu sichern!
;Dafür gibt es verschiedene Methoden, ich nehme meist ein
;Exklusiv-Register namens srsk:

 in srsk,sreg    ;SREG sichern, da ISR

  push   temp1
  push   temp2
  sts  Save_XReg,XL    ;X-Register sichern
  sts  Save_XReg+1,XH    ;
  in  temp1,UDR  ;Byte aus dem Empfangsregister holen
  lds  temp2,rx_fifo_n  ;Anzahl geschriebener Bytes holen
  cpi  temp2,rx_fifo_length  ;Mit maximaler Länge des
                                        ;Datenbereiches vergleichen

;  breq  end_add_rxfifo  ;Wenn Buffer voll, nicht überschreiben

;Und was passiert nun mit dem empfangenen Byte???
;Hier sollte man zumindest ein Error-Flag setzen, mit dem das
;Hauptprogramm über den Verlust informiert wird. Routine wird unten
;angehängt...

 breq add_rxfifo_error  ;Wenn Buffer voll, dann weg hier...

  lds  XL,rx_fifo_in  ;Schreibadresse holen
  lds  XH,rx_fifo_in+1  ;
  st  X+,temp1  ;Datenbyte in Adresse aus X-Register speichern
        ;und X-Register um eins erhöhen


  lds  temp1,rx_fifo_n  ;Anzahl geschriebene

;Nunja, der Wert von rx_fifo_n steht noch/schon in temp2...

  inc  temp1    ;Zeichen erhöhen
  sts  rx_fifo_n,temp1  ;und sichern

;oder eben:

  inc  temp2    ;Anzahl Zeichen erhöhen
  sts  rx_fifo_n,temp2  ;und sichern



  cpi  XL, LOW(rx_fifo_base+rx_fifo_length);16-Bit Vergleich
                                ;Speicherposition mit X-Register
  ldi  temp2,HIGH(rx_fifo_base+rx_fifo_length)
  cpc  XH,temp2
  breq  add_rxfifo_rollover
End_add_rxfifo:
  sts  rx_fifo_in,XL  ;fifo_in speichern
  sts  rx_fifo_in+1,XH
  lds  XL,Save_XReg  ;X-Register restaurieren
  lds  XH,Save_XReg+1
  pop  temp2
  pop  temp1

Das ist eine ISR, also SREG wiederherstellen!

 out sreg,srsk    ;SREG wiederherstellen

;  ret

;Das ist eine ISR, also RETI statt RET, um das i-Flag im SREG wieder
;zu setzen und den nächsten Int zu ermöglichen...

 reti

Add_rxfifo_rollover:
  ldi  XL,low(rx_fifo_base)  ;niedrigste Adresse einstellen
  ldi  XH,High(rx_fifo_base)  ;um alte Daten zu überschreiben
  rjmp  End_add_rxfifo

;Nun die Fehlerbehandlung. Es sei vorausgesetzt, es wurde ein
;oberes Register namens "flags" definiert und in diesem ein Bit
;namens "rx_error".

add_rxfifo_error:
 sbr flags,1<<rx_error  ;Error-Flag setzen
 rjmp End_add_rxfifo

;Das Hauptprogramm sollte nach Empfang des Strings das Error-Flag
;prüfen und bei Fehler den String verwerfen und das Flag wieder
;löschen...


; SRAM lesen -----------------------------------------------

;Dies ist also ein Teil der Hauptschleife.
;In den meisten Fällen wird kein neues Byte eingetroffen sein.
;Daher kann man den Durchlauf verkürzen, indem man erstmal nur
;die benötigten Register sichert (zum Benutzen frei macht).
;Es ist auch allgemein üblich, im Hauptprogramm einen anderen Pointer
;zu verwenden als in der ISR.

Get_RX_fifo:
 cli                  ;damit ISR nicht dazwischen funkt...
 push temp1           ;Platz für Anzahl der Zeichen im Puffer
 lds temp1,rx_fifo_n  ;Anzahl geschriebener Bytes holen
 tst temp1            ;Testen ob überhaupt was im Speicher ist

; breq end_get_rxfifo  ;sonst gibt's nix zu tun
 breq nix_get_rxfifo  ;sonst gibt's nix zu tun (Sprungziel geändert)

; push  temp2          ;temp2 wird nicht gebraucht

;  sts  Save_XReg,XL  ;X-Register sichern
;  sts  Save_XReg+1,XH  ;

 push yl               ;Y-Pointer
 push yh               ;freimachen

 lds yl,rx_fifo_out    ;Lesepointer holen
 lds yh,rx_fifo_out+1;
 ld Text,X+            ;Text in Variable"Text" einlesen und
                       ;Pointer erhöhen

; lds temp1,rx_fifo_n  ;Anzahl Zeichen laden (steht schon/noch drin)

 dec temp1             ;um 1 reduzieren
 sts rx_fifo_n,temp1   ;Anzahl sichern
 cpi XL,Low(rx_fifo_base+rx_fifo_length);16-Bit Vergleich
                       ;Speicherposition mit oberster Grenze
 ldi temp1,High(rx_fifo_base+rx_fifo_length)
 cpc XH,temp1          ;Vergleich mit Übertrag (Carry)
 brne End_get_rxfifo   ;weg hier, wenn Ende nicht erreicht wurde
 ldi XL,LOW(rx_fifo_base)  ;Pointer auf Bufferanfang
 ldi XH,HIGH(rx_fifo_base) ;setzen

End_get_rxfifo:
  sts  rx_fifo_out,XL      ;Leseadresse sichern
  sts  rx_fifo_out+1,XH

;  lds  XL,Save_XReg  ;X-Register wieder herstellen
;  lds  XH,Save_XReg+1  ;

;stattdessen pusch & pop...

 pop yh                    ;alten Wert im benutzten Pointer
 pop yl                    ;wiederherstellen

; pop  temp2               ;wurde nicht gebraucht...

nix_get_rxfifo:            ;Einsprung, wenn kein Zeichen da war...

  pop  temp1
  sei
  ret

;******************************************************************

Die Änderungen wurden nicht geprüft, denn ich habe momentan kein
Projekt mit U(S)ART in Bearbeitung. Ich hoffe, du kannst sie
nachvollziehen. Sollten Fehler drin sein, so bitte ich um Nachsicht und
Berichtigung...

...

von Carsten (Gast)


Lesenswert?

Guten Morgen HanneS...
>Soweit ok, wobei ich das Pointerregister im Stack sichern würde.
...hab ich mir auch schon gedacht.
>Nicht falsch, obwohl das kürzer machbar ist, denn beide Pointer haben
>dieselbe Anfangsadresse, das doppelte LDI ist daher unnötig.
...auch das hatte ich schon in Erwägung gezogen
Das Sreg sichere ich vor dem Absprung in die Sub, in der UART-ISR und
sichere es nach dem ret zurück.
Steht in einem einfachen Register (r0).

;-------------------- UART byte empfangen ----------------------------
USART_RXC:
    in  Save_SReg,SREG  ;Statusregister sichern
    rcall  Add_rx_fifo  ;Empfangenes Byte in SRAM speichern
    out  SREG,Save_SReg  ;Statusregister restaurieren
    reti

Das mit dem Buffer voll hette ich schon gesehen, mir aber wegen der
Probleme mit dem Rest noch keine weiteren Gedanken gemacht.
Da war noch ein anderer Bug drin. sts Save_XReg,XL... stand vorher
unter:
breq  end_get_rxfifo  ;sonst gibt's nix zu tun
...da hätte er mir irgend welchen alten Mist ins X-Register zurück
geschrieben.

>;Nunja, der Wert von rx_fifo_n steht noch/schon in temp2...
Stimmt, cpi lässt temp2 unverändert. Hab ich nicht gesehen.

...Gute Idee mit dem Error-Flag. Ich habe ohnehin ein Status-Register,
wo ich noch ein Bit frei machen kann.

Verkürzen des Durchlaufs find ich gut.

Dass Du plötzlich mit dem Y-Register angefangen hast war sicher nur ein
Versehen ?

Die Änderungen habe ich verstanden aber ein Fehler lässt sich nicht
finden oder ?
Ich hatte vermutet, dass die Initialisierung fehlerhaft ist aber
scheint nicht so zu sein...
Danke Dir erstmal.

von Carsten (Gast)


Lesenswert?

Ich habe jetzt mal alle Interrupts abgeschaltet, bis das Empfangene
Zeichen aus dem Speicher gelesen und ans UART gesendet ist.
Jetzt kommt zwar kein Zeichensalat aber dafür erscheint das gesendete
Zeichen im Terminal immer einen Tastendruck verspätet.
Ich drücke 1, nix. Drücke 2, Antwort 1...
Hier meine Senderoutine (Aus dem Tutorial)
; sendbyte: sendet das Byte aus Register Text über das UART
sendbyte:
        sbis   USR, UDRE        ; warten bis das UART bereit ist
        rjmp   sendbyte
        out   UDR, Text
        ret
Das heißt doch, dass rx_fifo_n offensichtlich nicht passt.
Im Simulationsmodus ist aber alles in Ordnung.
Den Timer-Interrupt habe ich vorsichtshalbe deaktiviert.

von Carsten (Gast)


Lesenswert?

...puuhh es läuft...
Ich hatte nicht bedacht, dass der UDR geleert wird, nachdem man ihn
einmal in ein Register übertragen hat.
Habe ihn vor der Routine einmal über ein Temp-Register auf das Display
gejagt. Dann ist natürlich nix mehr mit korrekt auslesen.
Wenn Interesse besteht, vor allem für die Anfänger, wie mich,
Poste ich gerne mal den gesamten Quelltext zum Thema UART-Empfang,
Ablage in einem FIFO-Speicher und Weiterverarbeitung...

von ...HanneS... (Gast)


Lesenswert?

Moin...

Modularisierung des Codes ist das Eine (da bin ich aber noch nicht ganz
angekommen).
Aber in der UART-RX-ISR eine weitere SUB aufrufen, die nur hier und
nirgendwoanders gebraucht wird, das halte ich für übertrieben. Denn
jedes rcall-ret kostet wertvolle Rechenzeit, besonders in der ISR.

Verkürzen des Durchlaufs halte ich für nötig, da es Teil der Mainloop
ist.

Dass ich den Y-Pointer nutzte, war kein Versehen. Ich habe mir einige
Tips (leider noch lange nicht alle) erfahrener Programmierer (wie Peter
D.) angenommen. Da ist es z.B. üblich, den X-Pointer ausschließlich im
Interrupt zu verwenden, im Hauptprogramm dann Y und Z.

Nächster inzwischen eingetroffener Beitrag...

RX_fifo_n hat nix mit dem Senden zu tun. Für das Senden benötigst du
einen zweiten Ringbuffer. Der muss so groß sein, dass deine längste
Nachricht rein passt. Das (rausnehmen und) Senden übernimmt dann die
ISR, die dann aufgerufen wird, wenn der UART-Buffer (das eine Byte in
UDR) leer ist.
Dann gibt es auch kein Warten, dann ist UART bereit.

Nächster inzwischen eingetroffener Beitrag...

Ja, UDR darf man nur einmal pro Byte auslesen.

...

von ...HanneS... (Gast)


Lesenswert?

Achja:

Das Tutorial will die ersten Schritte vermitteln und setzt daher auf
ein Programmkonzept, bei dem es keinen (steuernden) Timer-Int gibt und
bei Hardwarezugriffen teils gewartet werden muss, bis diese HW bereit
ist. Dies bedeutet Delay-Schleifen und Busywait-Schleifen.

Wenn man mit Timer-Int, User-Flags und Mainloop arbeitet, dann benötigt
man kaum noch Warteschleifen, das synchronisiert man mit dem Timer-Int.
Und Busywait-Schleifen gibt es gleich garnicht mehr, wenn ein I/O
(noch) nicht bereit ist, dann springt man zurück zur Mainloop und
versucht es bei der "nächsten Runde". So können andere Programmteile
abgearbeitet werden anstatt auf die Bereitschaft eines I/O-Teiles zu
warten.

...

von Carsten (Gast)


Lesenswert?

Das heisst, ich baue mir einen zweiten SRAM-Puffer und schreibe aus
diesem auf's UART ?
Das wäre dann vermutlich der USART_TXC-Interrupt ?
Was kann ich denn mit dem UDR-Empty-Handler machen ?
Werden ISR's durch andere ISR's unterbrochen ?

von ...HanneS... (Gast)


Lesenswert?

Moin...

> Das heisst, ich baue mir einen zweiten SRAM-Puffer und schreibe aus
diesem auf's UART ?

Das wirst du wohl müssen, wenn du mehr als ein Byte senden willst. Und
wegen der Bequemlichkeit sollte deine längste Nachricht darin Platz
finden.
Falls deine Nachrichten keine variablen Teile (Zahlen, Messwerte)
enthalten, sondern nur fixe Texte (wäre aber Quatsch, denn dann könnte
man ja die Nummer des Textes senden), dann könnte man diese auch direkt
aus dem Flash senden.

> Das wäre dann vermutlich der USART_TXC-Interrupt ?

Puhhh... Da musst du mal das Datenblatt befragen und evtl. testen. Es
gibt da nämlich auch noch USART_UDRE (UDR leer). Einer von Beiden wird
es wohl sein...

> Was kann ich denn mit dem UDR-Empty-Handler machen ?

Datenblatt fragen, Appnotes fragen, ausprobieren...

> Werden ISR's durch andere ISR's unterbrochen ?

Nein (es sei denn, du erlaubst das mit SEI in der ISR, dann sollte man
aber genau wissen was man tut).
Deshalb sollen ISRs ja auch so kurz wie möglich sein.

...

von Carsten (Gast)


Lesenswert?

...ich habe variable Texte, in denen ein Teil aber immer gleich ist.
Ich weiß auch noch nicht, wie ich das mache.
Ich dache den ersten Teil (immer gleich) aus dem Flash und den Rest aus
dem EEPROM. Diese werden per UART ins EEPROM gespeichert (sehr selten).

von ...HanneS... (Gast)


Lesenswert?

Mach die Texte nicht größer als unbedingt erforderlich. Dann den
Sendebuffer so groß, dass auch der längste Text reinpasst. Dann in
einer Schleife den ersten Teil Text in den Sendebuffer, danach die
Variablen als ASCII, dann evtl. weiteren Text. Dabei natürlich den
Pointer aktualisieren (und die Anzahl der Bytes).

Denkbar ist auch das Zusammenstellen eines mit $00 terminierten Strings
im SRAM (in einem Rutsch), der dann byteweise gesendet wird.

Im UART-Int Anzahl prüfen, und ggf. ein Byte senden (Pointer und Anzahl
korregieren).
Das erste Byte muss dann allerdings von Hand gesendet werden, da der
Int ja erst nach dem erfolgreichen Senden des ersten Bytes auftritt.

Alternativ kann das Senden eines Bytes vom Timer-Int angeschubst
werden, wenn das Timing so bemessen ist, dass es keine Überschneidung
gibt. Du würdest also zyklisch nachfragen, ob die Anzahl größer 0 ist
und dann ein Byte senden.

...

von Carsten (Gast)


Lesenswert?

Kann ich denn nicht den festen Teil direkt aus dem Flash abschicken und
direkt danach den Teil aus'm EEPROM ?
Abgeschlossen wird so eine Meldung ohnehin mit CR oder STRG+Z.

von ...HanneS... (Gast)


Lesenswert?

Sicher, wenn du bei der Verwaltung den Überblick behältst. Vergiss aber
nicht, dass du zu einem Zeitpunkt nur jeweils 1 Byte schicken kannst.
Hast du alles als String im SRAM liegen, dann lässt sich das Senden
leicht automatisieren. Musst du den String erst beim Senden
zusammensetzen, dann ist die Verwaltung schwieriger. Wenn du es kannst,
dann mach es so. Ich würde das aber vermeiden wollen. Ich kann mir aber
vorstellen, dass man das mittels Scheduler realisieren kann.

...

von Carsten (Gast)


Lesenswert?

...Du machst mir Mut...
Ich hab zu wenig Speicher für den gesamten Text.

By the way...
Wie kann ich am elegantesten ein einzelnes Bit in einem Register
toggeln ?

von ...HanneS... (Gast)


Lesenswert?

Zu toggelnde Bits in einem Hilfsregister setzen, dann mit EOR toggeln.
EORI gibt es leider nicht.

...

von Carsten (Gast)


Lesenswert?

Na, da hatte ich mal wieder Ladehämmungen...
Die genullten Bits sind dann ja auch egal :D

von ...HanneS... (Gast)


Lesenswert?

Du hast dafür also zuwenig RAM...

Dan analysiere doch erstmal, wie sich deine Sendestrings zusammen
setzen. Also wieviele "Etappen" nötig sind, von welchen Quellen
(Flash, EEPROM, SRAM) nötig sind.

Dann könntest du beim Zusammensetzen der Meldung in einem SRAM-Bereich
eine Liste (n Einträge a 16 Bit) erstellen, in der Quelle (obere 2 Bit)
und Startadresse (untere 13 Bits adressieren 8KB) der einzelnen Etappen
eingetragen werden. Quelle 0 kennzeichnet das Ende, Quellen 1, 2 und 3
stehen für Flash, EEPROM und SRAM. Das Ende des jeweiligen Strings in
allen Quellen wird mit 0 terminiert.

Deine Sende-ISR muss dann gemäß der Liste den jeweiligen String bis zur
nächsten 0 ausgeben (Verzweigung gemäß Quellentyp auf drei
unterschiedliche Programmteile), dann die nächste Quelle+Startadresse
einlesen und abarbeiten, bis die nächste Quelle 0 ist.

Das dürfte sogar recht übersichtlich machbar sein.

...

von Carsten (Gast)


Lesenswert?

Das hört sich gut an...
Du meinst aber dass dann alle Texte zuerst ins SRam geholt werden ?

von ...HanneS... (Gast)


Lesenswert?

Nööö...

Bei der zuletzt vorgeschlagenen Methode bleiben alle Ausgabefragmente
(nennen wir sie Etappen) dort wo sie sind. Das wären ASCII-Texte im
Flash, ASCII-Texte im EEPROM und in ASCII-Text umgewandelte Zahlen im
SRAM. Alle ASCII-Texte sind mit $00 terminiert (Ende-Kennung), damit
die Ausgaberoutine(n) keine Stringlängenzähler benötigen.

Dein Ausgabeprogrammteil (das, welches den Ausgabestring definiert)
legt dann im SRAM eine Sequenz ab, in der für jede "Etappe" zwei
Bytes benötigt werden. Die unteren 14 Bits repräsentieren die
Startadresse des auszugebenden Strings (also den Pointer auf den
Stringbeginn, am besten den Z-Pointer verwenden, da der auch auf Flash
zeigen kann). Die oberen 2 Bits geben an, wo sich der String befindet,
z.B.: 00xxxxxx=fertig, 01xxxxxx=Flash, 10xxxxxx=EEPROM, 11xxxxxx=SRAM.

Die Senderoutine (die jeweils 1 Byte an UDR schickt)
- holt den Datensatz aus der Liste im SRAM ins y-Register,
- übernimmt die oberen 2 Bits in YH in ein weiteres Register
  (global, zur Verzweigung auf die verschiedenen Senderoutinen),
- löscht die oberen 2 Bits in YH (jetzt steht in YH:YL nur noch die
  Startadresse des Etappenstrings)
- holt ein Byte von der jeweiligen Quelle
- überprüft das Byte auf $00
- wenn nicht $00, gibt das Byte an UDR aus,
- wenn $00, holt nächsten Eintrag aus "Etappenliste" und richtet
  Pointer und Quellzeiger neu ein, holt und sendet erstes Byte
- wenn Quelle $00 ist, dann Übertragung beenden...

Für die "Etappenliste" muss dann etwas SRAM deklariert werden (Größe
nach Anzahl der erforderlichen Etappen), dazu ein Pointer auf die
Etappenliste, weiterhin 2 Bytes für die Kopie des aktuellen Y-Pointers
und bei Registermangel 1 Byte für die Kopie der aktuellen Quelle (zum
Verzweigen).

Je meht ich darüber nachdenke, desto mehr komme ich zu der Einsicht,
dass ich dieses Prinzip auch irgendwann anwende...

...

von Carsten (Gast)


Lesenswert?

Das muss ich jetzt erst mal verdauen...
Wird etwas dauern.

von Carsten (Gast)


Lesenswert?

...verdammt, wie löse ich den TXC interrupt das erste mal aus?
Geht das denn nur, wenn ich in einer Schleife warte bis UART bereit und
dann vielleicht NULL sende ?
Das TXC-Bit manuell auf 1 setzen hat zumindest nicht gefunzt...

von ...HanneS... (Gast)


Lesenswert?

Schick doch das erste Zeichen von Hand und aktiviere den INT.
Ist deine MSG durch, dann deaktiviere den Int wieder (nach dem letzten
gesendeten Zeichen, also dann, wenn die Sende-ISR feststellt, dass nix
mehr zu senden da ist.

Vielleicht kannst du ja die Sende-ISR auch für das erste Zeichen als
Unterprogramm aufrufen (rcall), das reti am Ende wirkt doch wie ein
ret, setzt aber zusätzlich noch das I-Flag. Vielleicht auch vor dem
rcall das I-Flag löschen (cli)? Musst mal probieren.

...

von Carsten (Gast)


Lesenswert?

Ok, wenn reti genauso funktioniert...
Ich hab jetzt erstmal 0 übers UART geschickt.
Scheint auch hervorragend zu funktionieren.
Im Moment hab ich nur noch irgendwo ne Endlosschleife eingebaut aber
die bekomme ich auch noch weg...
Dein Vorschlag mit den Jobs läuft hervorragend.
Wenns rund läuft hänge ich mal nen Auszug hier rein.

von Carsten (Gast)


Lesenswert?

Also das direkte Starten hat irgendwelche vermurksten Zeichen
ausgespuckt.
Aber jetzt hänge ich wieder...
Manchmal verschluckt er einige Zeichen (immer am Ende der Teiltexte).

Hier meine Routinen:
USART_TXC:
    in  Save_SREG,SREG
    push  temp1
    ;Liegt überhaupt ein Job an ?
    lds  temp1,TX_Routine+1  ;High-byte laden
    andi  temp1,0b11000000  ;Job-Bits extrahieren
    tst  temp1    ;kein job ? -> weg hier
    breq  TX_NoJob
    push  ZL    ;Z-Register sichern
    push  ZH    ;
    push  text    ;
    lds  ZL,TX_Routine  ;gesamte Adresse holen
    lds  ZH,TX_Routine+1  ;
    andi  ZH,0b00111111  ;Speicherkennung löschen
    cpi  temp1,0b11000000;Wo liegen die Daten ?
    breq  SRAM_lesen  ;im SRAM
    cpi  temp1,0b10000000;im EEPROM
    breq  EEPROM_lesen  ;
    cpi  temp1,0b01000000;im Flash
    breq  FLASH_lesen  ;
    rjmp  TX_Ende  ;da ist was schief gegangen, weg hier
;------------------------SRAM---0b11000000---------------
SRAM_lesen:
    ld  text,Z+    ;Text-Byte holen
    tst  text    ;Testen ob Textende erreicht
    breq  Job_lesen_ende  ;sonst weg hier
    out  UDR,text  ;Byte ins UART schreiben
    or  ZH,temp1   ;Speicherkennung zurückschreiben
    sts  TX_Routine,ZL  ;neue Adresse sichern
    sts  TX_Routine+1,ZH  ;
    rjmp  TX_Ende

;------------------------EEPROM-0b10000000---------------
EEPROM_lesen:
    sbic  EECR,EEWE  ;warten, bis EEPROM bereit
    rjmp  EEPROM_lesen
    out  EEAR,ZL
    sbi  EECR,EERE  ;Lesevorgang einleiten
    in   Text,EEDR  ;Byte in text holen
    tst  text    ;Testen ob Textende erreicht
    breq  Job_lesen_ende  ;sonst weg hier
    out  UDR,text  ;Byte ins UART schreiben
    inc  ZL    ;Adresszähler um eins erhöhen
;Vielleicht muss hier der Überlauf nach 128 noch geprüft werden, da
EEPROM nur bis 127 geht
    sts  TX_Routine,ZL  ;und zurückspeichern
    rjmp  TX_Ende

;------------------------FLASH---0b01000000--------------
FLASH_lesen:
    lpm        ;Erstes Byte des Strings nach R0 lesen
                tst   R0               ;R0 auf 0 testen (Textende)
                breq   Job_lesen_ende  ;wenn 0, -> weg hier
                out   UDR,r0    ;Byte senden
    adiw   ZL, 1    ;Adresse des Z-Pointers um 1 erhöhen
                sts  TX_Routine,ZL  ;neue Adresse speichern
    rjmp   TX_Ende

;------------------------ENDE------------------
Job_lesen_ende:
    clr  temp1
    sts  TX_Routine+1,temp1 ;Job erledigt.
TX_Ende:
    pop  Text    ;Diverse Register restaurieren
    pop  ZH
    pop  ZL
TX_NoJob:
    pop  Temp1
    out  SREG,Save_SREG
    reti


...und hier werden die Jobs zugeteilt:

;Nachrichten selektieren
    lds   temp1,TX_Routine+1 ;High-Byte der Routine
    andi  temp1,0b11000000   ;Ist noch ein Job aktiv ?    tst  temp1
    brne  Msg_Select_noJob   ;Ja, weg hier

    sbrc  MsgToSend,MsgTotal ;wenn Total-Flag 0,
    rjmp   Msg_Total     ;nächste Meldung,
    sbrc  MsgToSend,MsgDruck ;wenn Druck-Flag 0,
    rjmp   Msg_Druck     ;nächste Meldung,
    sbrc  MsgToSend,MsgStrom ;wenn Strom-Flag 0,
    rjmp   Msg_Strom     ;nächste Meldung,
    sbrc  MsgToSend,MsgOK    ;Antwortflag für Bestätigung
    rjmp  Msg_OK
Msg_select_noJob:
    rjmp  Msg_Select_ende     ;Weiter

Msg_Druck:
;TX_Routine ist das Wort, dass den TX-Auftrag und die Adresse enthält

Msg_Druck_Job_0:;Sendebefehl aus dem Flash senden
  cpi  Senderoutine,0          ;Ist die Senderoutine <> 0?
  brne  Msg_Druck_Job_1          ;dann zum nächsten Job
  ldi  temp1,LOW(Sendbefehl*2)  ;Adresse holen
  sts  TX_Routine,temp1        ;LOW-Byte TX-Routine schreiben
  ldi  temp1,HIGH(Sendbefehl*2)
  ori  temp1,0b01000000    ;Adressbereich auf Flash festlegen
  sts  TX_Routine+1,temp1
  ldi  Senderoutine,1      ;nächsten Job markieren
Start_Druck_Job_0:
        sbis   USR, UDRE        ;warten bis das UART bereit ist
        rjmp   Start_Druck_Job_0
        ldi  temp1,13           ;<CR> um UART zu starten
  out   UDR, temp1
    rjmp  Msg_Select_ende

Msg_Druck_Job_1:;Rufnummer aus EEPROM anhängen
  cpi  SendeRoutine,1    ;Ist die Senderoutine <> 1?
  brne  Msg_Druck_Job_2    ;Dann zum nächsten Job
  ldi  temp1,0           ;Adresse erste Rufnummer Setzen
  sts  TX_Routine,temp1       ;LOW-Byte TX-Routine schreiben
  ldi  temp1,0b10000000     ;High-Byte + Adressbereich EEPROM
  sts  TX_Routine+1,temp1
  ldi  Senderoutine,2          ;nächsten Job markieren
Start_Druck_Job_1:
        sbis   USR, UDRE        ;warten bis das UART bereit ist
        rjmp   Start_Druck_Job_1
        ldi  temp1,34         ;<">schicken um UART zu starten
  out   UDR, temp1
  rjmp  Msg_Select_ende

Msg_Druck_Job_2:;Text aus Flash anhängen
  cpi  Senderoutine,2    ;Ist die Senderoutine <> 2?
  brne  Msg_Druck_Job_3    ;dann zum nächsten Job
  ldi  temp1,LOW(Text11*2)    ;Adresse von "Sendbefehl" holen
  sts  TX_Routine,temp1  ;LOW-Byte TX-Routine schreiben
  ldi  temp1,HIGH(Text11*2)
  ori  temp1,0b01000000    ;Adressbereich auf Flash festlegen
  sts  TX_Routine+1,temp1
  ldi  Senderoutine,0  ;Bei null ist dieser Job abgeschlossen
  rcall  delay
Start_Druck_Job_2:
        sbis   USR, UDRE              ;warten bis das UART bereit ist
        rjmp   Start_Druck_Job_2
        ldi  temp1,33      ;<!>  einfügen um UART zu starten
  out   UDR, temp1
  rjmp  Msg_Select_ende

Msg_Druck_Ende:
  eor  YH,YH  ;Y-Register reset für 30-Minuten-Meldung
  eor   YL,YL

  cli
  ldi  Senderoutine,0    ;Alles fertig
  cbr  MsgToSend,1<<MsgDruck  ;Message ist weg
  sei
  rjmp  Msg_Select_ende    ;Weiter
MSG_Strom:

  rjmp  Msg_Select_ende    ;Weiter
MSG_Total:

  rjmp  Msg_Select_ende    ;Weiter
Msg_OK:

  rjmp  Msg_Select_ende    ;Weiter
Msg_Select_Ende:

Ich bitte um Entschuldigung, für die schlechte Lesbarkeit aber das
Fenster hier ist ziemlich schmal.

@HanneS:
Kannst Du hier einen Bug entdecken ?

von ...HanneS... (Gast)


Lesenswert?

Ich glaube, wir reden hier aneinander vorbei.

Deine Routinen entsprechen nicht dem vorgeschlagenen Konzept. Auch
scheinen wir unterschiedliche Begriffe zu verwenden.

Unter Job verstehe ich die Reaktion auf ein Ereignis. Also ein
Programmteil, der ausgeführt wird, wenn ein Ereignis auftritt. Z.B. das
Ausgeben eines Textes als Reaktion auf Tastendruck oder Timerablauf.

Da es wegen RAM-Mangel notwendig erschien, die MSG aus verschiedenen
"Textbausteinen" zusammenzusetzen, die im Flash (Fixtexte), EEPROM
(zur Laufzeit geänderte Texte) oder SRAM (zu ASCII umgewandelte
temporäre Zahlenwerte) liegen können, habe ich den Begriff Etappe ins
Spiel gebracht. Damit meine ich einen Textbaustein (mit 0 terminiert),
der zusammen mit anderen Etappen versendet werden kann. Eine MSG
besteht dann aus mehreren Etappen, die nacheinander gesendet werden.

Dein Code unterstützt nur eine Etappe. Er enthält auch noch einige
Teile, die ich auf die Schnelle nicht nachvollziehen kann.

Du brauchst also erstmal ein Stück SRAM, in dem du die "Etappenliste"
und den Pointer darauf einrichten kannst. - Aber das habe ich ab hier:
http://www.mikrocontroller.net/forum/read-1-183199.html#184297
(und den folgenden Beiträgen) bereits ausführlich beschrieben.

Schade eigentlich auch, das deine Infos immer so dürftig und
scheibchenweise rüberkommen. Ich weiß immer noch nicht, wieviele
unterschiedliche MSGs das werden können, was diese MSGs enthalten
sollen usw. Denn danach richtet sich, wie man das programmiert.

Ich muss aber erstmal los.

...

von Carsten (Gast)


Lesenswert?

Ich glaube nicht, dass wir so weit aneinander vorbei geredet haben.
Alles was mit Druck-Job zu tun hat ist im Grunde eine Message.
Die Messages setzen sich aus dem AT-Befehl (Flash), der Rufnummer
(EEPROM) und dem Meldetext (Flash) zusammen.
Den Abschnitt SRAM habe ich eigentlich nur der Vollständigkeit-halber
mit eingebaut.
MSG_Strom etc. sind weitere Meldungen, die ich erst fertig mache, wenn
die erste Meldung funktioniert.
Im Grunde habe ich das nur anders genannt. Die drei Jobs (0-2) sind
eigentlich die Etappen und alle drei zusammen sind der Job.
Bei "Nachrichten selektieren" wird ausgewählt, welche Nachricht
geschickt wird (ich sehe grade, dass da das tst verrutscht ist).
Das Setzen der entsprechenden MsgToSend-Bits geschieht in der Timer
ISR.
Das erste mal ausgelöst von einer Taste und dann alle halbe Stunde
erneut.
Ich möchte das ganze Programm noch nicht posten, weil da noch elendige
Baustellen drin sind.
Beispielsweise arbeite ich noch an der Auswertung der eingegangenen
Meldungen und muss deswegen das EEPROM manuell in der Reset
beschreiben.

Ich melde mich nachher mal per Mailbei Dir.
Gruß Carsten

von ...HanneS... (Gast)


Lesenswert?

Moin...

Hier der Versuch, oben genanntes Konzept in Code auszudrücken:

Kernstück ist das Array 'etappen' im SRAM, in dem in jeweils 2 Bytes
die Startadresse des Strings und der Speichertyp, in dem der String
abgelegt ist, enthalten ist. Dabei repräsentieren die oberen 2 Bits des
H-Bytes den Speichertyp, die restlichen 14 Bits die Startadresse des
Strings.

Wenn kein String gesendet werden muss (also im Normalzustand des
Programmablaufs), sind TX und Sendeinterrupt deaktiviert.
Muss ein String gesendet werden, dann wird durch einen Job des
Hauptprogramms (Beispiel 'MSG1' weiter unten) eine Etappenliste (mit
Ende-Kennung) erstellt, in der die Startadressen und Speichertypen
mehrerer Strings enthalten sind.
Dann wird der Pointer 'etappenp', der die Position des zu sendenden
Bytes enthält, mit der Startadresse des Strings der ersten Etappe
geladen und die Variable 'etapquelle' mit der Kennung des
Speichertyps des ersten Strings geladen. Die Kennungen der
Speichertypen sind der Einfachheit halber als Konstanten definiert.
Erst wenn diese Daten vorbereitet sind, wird TX und der Sende-Interrupt
aktiviert. (Der Einfachheit halber sind die dazu benötigten Bits in der
Konstante 'tx_ein' definiert.) Danach wird das I-Flag gelöscht (also
Interrupts verboten) und die TX-ISR per RCALL aufgerufen. Dies sendet
das erste Byte. Das abschließende RETI erlaubt danach wieder alle
Interrupts.

Ist das Byte gesendet, löst UART einen weiteren TX-Int aus, in dem die
ISR das nächste Byte sendet. Wird dabei eine 0 (Stringende) erreicht,
so wird der nächste String aus der Etappenliste adressiert (Speichertyp
auch) und das erste Byte dieses Strings gesendet.

Wird als Speichertyp 0 erkannt (beide oberen Bits gelöscht), so wird
das als Etappenlistenende erkannt und TX und der TX-Interrupt werden
deaktiviert (das letzte zu sendende Byte ist ja durch, es wird ja zum
Senden des ersten Bytes des Strings der vermuteten neuen Etappe
aufgefordert, die es nicht mehr gibt).

Somit ist nach dem Senden der gesamten MSG der Normalzustand (TX und
TX-INT deaktiviert) wiederhergestellt und TX kann nur durch das Senden
einer weiteren MSG aktiviert werden.

Will man sicher gehen, dass man nicht in einen laufenden Sendevorgang
"reinplatzt", so kann man anhand des Zustands der entsprechenden Bits
in UCSRB feststellen, ob TX aktiviert ist.


Um das Zusammensetzen der MSG aus Etappen-Strings zu vereinfachen,
wurden 4 Macros definiert.

send_neu setzt den Z-Pointer auf die Basisadresse der Jobliste

send_f adresse platziert String aus Flash in der Etappenliste

send_e adresse platziert String aus EEPROM in der Etappenliste

send_s adresse platziert String aus SRAM in der Ereignisliste

Mit diesen Macros stellt man die Etappenliste zusammen, danach wird
mittels 'rjmp sendmsg' die Etappenliste terminiert und der Transfer
angestoßen.

;---------------------------------------------------------------
;Einige Konstanten:

;Speichertypen, in denen die Strings liegen können:
.equ qflash=0b01000000  ;Textquelle=Flash
.equ qeepr=0b10000000   ;Textquelle=EEPROM
.equ qsram=0b11000000   ;Textquelle=SRAM

;Bits im USART-Register UCSRB zum Aktivieren und Deaktivieren von
;TX und TX-Interrupt:
.equ tx_ein=(1<<udrie)|(1<<txen) ;TX und TX-INT (de)aktivieren

;---------------------------------------------------------------
;im SRAM-Bereich:
.dseg
; ... andere benötigte SRAM-Bereiche definieren...
etappen:    byte 16     ;max 7 Etappen (+ Ende-Kennung)
etappe:     byte 1      ;laufende Etappen-Nummer
etappenp:   byte 2      ;Pointer auf String in akt. Etappe
etapquelle: byte 1      ;Quelle der akt. Etappe

;---------------------------------------------------------------
;einige Macros zum Angeben von Textbausteinen:

.macro send_neu ; keine Parameter nötig
 ldi zl,low(etappen)    ;Z-Pointer auf Etappenlistenanfang
 ldi zh,high(etappen)   ;im SRAM setzen
.endmacro

.macro send_f ;Adresse der Strings (Label im cseg) als Parameter
 ldi temp1,low(@0*2)    ;Low-Teil der Adresse im Flash
 st z+,temp1            ;in Etappenliste als L
 ldi temp1,high(@0*2)   ;High-Teil der Adresse
 sbr temp1, qflash      ;Flash-Bit (Quelle) setzen
 st z+,temp1            ;in Etappenliste als H
.endmacro

.macro send_e ;Adresse des Strings (Label im eseg) als Parameter
 ldi temp1,low(@0)      ;Low-Teil der Adresse im EEPROM
 st z+,temp1            ;in Etappenliste als L
 ldi temp1,high(@0)     ;High-Teil der Adresse
 sbr temp1, qeepr       ;EEPROM-Bit (Quelle) setzen
 st z+,temp1            ;in Etappenliste als H
.endmacro

.macro send_s ;Adresse des Strings (Label im dseg) als Parameter
 ldi temp1,low(@0)      ;Low-Teil der Adresse im SRAM
 st z+,temp1            ;in Etappenliste als L
 ldi temp1,high(@0)     ;High-Teil der Adresse
 sbr temp1, qsram       ;SRAM-Bits (Quelle) setzen
 st z+,temp1            ;in Etappenliste als H
.endmacro

;---------------------------------------------------------------
;Unterprogramm zum Abschluss der Etappenliste und Start
;des Transfers...

sendmsg: ;Unterprogramm, vervollständigt Etappenliste und schubst
         ;das Senden an. Wird von MSG-Erstellung per rjmp
         ;aufgerufen...
 ldi temp1,0            ;Null als Ende-Kennung
 st z+,temp1            ;in beide bytes des letzten Eintrags
 st z+,temp1            ;der Etappenliste
 sts etappe,temp1       ;mit Etappe 0 beginnen
 lds temp1,etappen      ;Pointer (L) für String für Etappe0
 sts etappenp,temp1     ;aus Etappenliste in Pointer kopieren
 lds temp1,etappen+1    ;H-Byte aber
 push temp1             ;sichern,
 cbr temp1,qsram        ;Quell-Bits löschen (Adresse isolieren),
 sts etappenp+1,temp1   ;Adresse H speichern,
 pop temp1              ;gesichertes Byte zurückholen,
 andi temp1,qsram       ;Quelle maskieren (Adresse löschen),
 sts etapquelle,temp1   ;als akt. Textquelle ins SRAM
 cli                    ;Int sperren, da ISR als SUB missbraucht
                        ;wird (wird vom RETI der ISR wieder akt.)
 in temp1,ucsrb         ;USART-Control-Reg. einlesen,
 sbr temp1,tx_ein       ;TX und Int. einschalten
 out ucsrb,temp1        ;und zurückschreiben
 rjmp isr_tx            ;USART-TX-ISR aufrufen und zurück...

;---------------------------------------------------------------
;Beispiel MSG absetzen:

;Verwendete Register (Inhalte nicht gesichert):
;ZH und ZL als Pointer
;temp1 für diverse temporäre Zwecke

msg1:    ;Aufruf aus der Main per 'rcall msg1'
 send_neu               ;Z-Pointer auf Anfang Etappenliste
 send_f text1           ;1, Etappe aus Flash ab Label text1 im cseg
 send_e num1            ;2. Etappe aus EEP ab Label num1 in EEP
;oder:
 send_e $20             ;Etappe aus EEP ab Adresse $0020 (32)
 send_s zahl1           ;Etappe aus SRAM ab Label zahl1 im dseg
 send_f text2           ;Etappe aus Flash ab Label text2 im cseg
 rjmp sendmsg           ;Etappenliste abschließen und Transfer
                        ;anschubsen, ISR 1 mal als SUB missbrauchen
                        ;zurück geht es mit dem RETI der ISR

;Die Anzahl der in einem Rutsch absetzbaren Etappen richtet sich
;nach der Größe des für die Etappenliste reservierten SRAMs.
;Es werden pro Etappe 2 Bytes benötigt, zuzüglich 2 Bytes für
;die Ende-Kennung.

;---------------------------------------------------------------
;Durch Interrupt aufgerufene Routine zum Senden eines Bytes
;über USART. In welchem Int das geschieht, wird in der
;Konstante tx_ein bestimmt, und natürlich vom Int-Vektor...
;Beides sollte ünereinstimmen.

;Verwendete Register:
;zh und zl als Pointer auf String
;temp1 vorerst als Stringquelle, danach als Byte des Strings...

ISR_TX:             ;Int zum Senden eines Bytes...
 in save_sreg,sreg      ;SREG sichern
 push zl                ;Inhalte benutzter Register
 push zh                ;sichern
 push temp1
 lds zl,etappenp        ;aktuellen Pointer auf String der akt.
 lds zh,etappenp+1      ;Etappe holen
 lds temp1,etapquelle   ;Stringquelle dieser Etappe holen
ISR_TX0:            ;Einsprung bei Etappenwechsel...
 cpi temp1,qflash       ;String im Flash??
 brne ISR_TX1           ;nein, weg hier...
 lpm temp1,z+           ;ja, Byte holen
 rjmp ISR_TX_Byteda     ;und weg hier...
ISR_TX1:
 cpi temp1,qeepr        ;String im EEPROM??
 brne IST_TX2           ;nein, weg hier...
 out eearl,zl           ;Adresse
 out eearh,zh           ;setzen
 adiw zh:zl,1           ;Pointer erhöhen
 sbi eecr,eere          ;Lesen anschubsen
 in temp1,eedr          ;Byte holen
 rjmp ISR_TX_Byteda     ;und weg hier...
ISR_TX2:
 cpi temp1,qsram        ;String im SRAM??
 brne ISR_TX_err        ;nein, Error...
 ls temp1,z+            ;Byte holen
ISR_TX_Byteda:      ;nun ist das Byte in r0...
 tst temp1              ;Ende-Kennung 0??
 breq ISR_TX_nextetappe ;ja, Etappe wechseln...
 out udr,temp1          ;nein, Byte senden
 sts etappenp,zl        ;aktualisierten Pointer
 sts etappenp+1,zh      ;wieder ins SRAM sichern
ISR_TX_ende:        ;Einsprung zum Beenden...
 pop temp1              ;alte Registerinhalte
 pop zh                 ;wiederherstellen
 pop zl
 out sreg,Save_SREG     ;SREG wiederherstellen
 reti                   ;fertig...

ISR_TX_error:       ;dürfte eigentlich nie erreicht werden...
 sbr flags,txerror      ;Error-Flag setzen
 rjmp ISR_TX_ende       ;zum "sauberen" Ende der ISR...

ISR_TX_nextetappe:  ;Pointer auf nächste Etappe setzen...
 lds temp1,etappe       ;akt. Etappennummer holen
 subi temp1,-2          ;um 2 erhöhen (Offset in Etappenliste)
 sts etappe,temp1       ;und gleich wieder sichern
 send_neu               ;Z-Pointer auf Anfang Etappenliste
 add zl,temp1           ;Offset dazu addieren
 clr temp1              ;löschen wegen Übertrag
 adc zh,temp1           ;Übertrag (Carry) auch addieren
 ld temp1,z+            ;L-Byte Stringadresse holen
 push temp1             ;und für später sichern
 ld temp1,z             ;H-Byte holen
 mov zh,temp1           ;und in Z-Pointer
 pop zl                 ;L-Byte auch in Z-Pointer
 cbr zh,qsram           ;Quellen-Bits im Pointer löschen
 andi temp1,qsram       ;Adressbits in Quelle löschen
 breq ISR_TX_next1      ;Quelle 0? dann Ende...
 sts etapquelle,temp    ;nein, Quelle in SRAM sichern
 rjmp ISR_TX0           ;erstes Byte des neuen Strings senden
ISR_TX_next1        ;Ende der Etappenliste erreicht...
 in temp1,ucsrb         ;USART-Control-Reg. einlesen,
 cbr temp1,tx_ein       ;TX und Int. ausschalten
 out ucsrb,temp1        ;und zurückschreiben
 rjmp ISR_TX_ende       ;zum "sauberen" Ende der ISR...

;---------------------------------------------------------------

Dieser Code ist als Manuskript zu sehen, er ist im Texteditor
erstellt und weder im Simulator noch im AVR getestet worden.
Er soll lediglich das oben genannte Konzept darstellen.
Es können daher noch grobe Fehler enthalten sein.

von Carsten (Gast)


Lesenswert?

Also ich fand meine Vesion für einen dummy wie mich ganz gut. Mann war
ich stolz, das die sogar funktioniert hat (bis auf kleine Macken).
:-(

Aber ich gestehe, Deine Version ist wirklich extrem elegant und spart
beim Definieren der Messages jede Menge Code.
Das einzige, der SRAM-Platz wird doch erheblich knapper.
Das mit dem Array ist wirklich eine tolle Lösung.

Danke, für Deine Geduld !

von ...HanneS... (Gast)


Lesenswert?

Die Version ist nicht getestet, lobe sie also erst, wenn sie fehlerfrei
läuft.

Es kann sein (nicht weiter überprüft) dass der UDR-leer-Int statisch
ist (also nicht beim "leerwerden" sondern beim "leersein"
aufgerufen wird). Wenn dem so ist, wird die TX-ISR durch das Aktivieren
von TX und TX-Int automatisch aufgerufen (UDR ist ja leer). Dann ist die
ISR natürlich nicht "von Hand" aufzurufen.

Ich kann es leider nicht testen, da ich derzeit kein Projekt mit USART
und Baudratenquarz verfügbar habe, mit internem RC-Oszillator macht
sich das schlecht.

...

von Carsten (Gast)


Lesenswert?

Ja, das stimmt. UDR-leer wird tatsächlich ausgelöst, wenn udr leer ist
und nicht wenns leer wird.
Das blöde ist, das der MC dann jeden zweiten schritt da hin eiert und
so wertvolle Zeit vertrödelt.
Gut, das soll bei meiner Anwendung nich wirklich ne Rolle spielen aber
ich find es unschön.
Wenn man den Code simulieren will, wird man vor lauter Sprüngen völlig
bekloppt.
Aber ich finde den Code vom prinzip schon geil.
Der Ansatz ist selbst bei kleinen Fehlern, falls da überhaupt welche
sind, wirklich toll. Und das beste, ich hab's verstanden freu

von ...HanneS... (Gast)


Lesenswert?

Was eiert da rum???

TX ist Senden, also kein von außen auftretendes Ereignis, sondern ein
vom Programm angestoßener Vorgang. Daher kann TX und der UDR-Leer-Int
erstmal deaktiviert sein. Will das Programm senden, dann wird (nach
Zusammenstellen der Etappenliste) TX und der Int aktiviert. Ist die
letzte Etappe durch, wird TX und der Int. wieder deaktiviert. Damit
wird kein TX-Int mehr aufgerufen und da eiert dann auch nix rum.
Natürlich darfst du TX nicht schon in der allgemeinen USART-Init
aktivieren, sondern erst, wenn du eine MSG absetzt...

Ähnliches Prinzip kannst du zum Beschreiben des EEPROMs einsetzen. Dazu
setzt du eear(L+H) auf die gewünschte Startadresse im EEPROM, richtest
im SRAM einen Pointer auf den Empfangsbuffer ein (auf das erste Byte
des Strings, und schaltest den Int scharf, der EEPROM_ready meldet. In
diesem Int schaufelst du jeweils ein Byte ins EEPROM und prüfst es auf
0 (Stringende). Die 0 wird mitgeschaufelt, da sie auch im EEPROM als
Stringende benötigt wird. Beim Erkennen der 0 wird dann einfach der
EEP-Int deaktiviert, wodurch die ISR nicht wieder aufgerufen wird. Dein
Empfangsparser (der die empfangenen Zeichen(ketten) analysiert muss dann
so intelligent sein, dass er sich die Startadresse des Strings merkt,
der ins EEP soll.

So, Besuch eingetroffen, bin erstmal "grundig" (im "Hintergrund")

...

...

von Carsten (Gast)


Lesenswert?

Hups, da hab ich mal wieder zu schnell gelesen...
Stimmt, da eiert nix.
Werde mich bald dran setzen, das ganze auszuprobieren.

Gruss Carsten

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.