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 ?
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.
...hört sich einfach an. Wie geht das in ASM ? Welchen Interrupt meinst Du? Timer0?
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.
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 ?
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.
Ja, das sehe ich ein. Welcher Interrupt ist das denn dann ? TXCIE ? Und was passiert, wenn eine Message rein kommt, bevor alles gesendet ist ?
...hat vielleicht mal jemand einen ASM Codeschnipsel zum Thema TX/RX Buffer für mich ?
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. ...
...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.
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. ...
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.
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. ;-) ...
...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 ?
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...
...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...
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...
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.
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 ?
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. ...
...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.
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
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... ...
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.
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.
...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...
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. ...
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. ...
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 ?
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. ...
...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).
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. ...
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.
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. ...
...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 ?
Zu toggelnde Bits in einem Hilfsregister setzen, dann mit EOR toggeln. EORI gibt es leider nicht. ...
Na, da hatte ich mal wieder Ladehämmungen... Die genullten Bits sind dann ja auch egal :D
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. ...
Das hört sich gut an... Du meinst aber dass dann alle Texte zuerst ins SRam geholt werden ?
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... ...
...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...
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. ...
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.
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 ?
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. ...
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
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.
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 !
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. ...
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
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") ... ...
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.