Hallo, ich habe mir ein Makro locate geschrieben, das wiederum eine LCD-Routine lcd_locate aufruft. Damit wird der Cursor im LCD platziert. Ausschnitte aus meinem Code: .equ Zeile1 = $80 ; Adressen der Displayposition .equ Zeile2 = $C0 .equ Zeile3 = $90 .equ Zeile4 = $D0 ;----------------------------------------------------------------------- - ; locate: @0 (Zeile) Setzt die Ausgabeposition im LCD auf ; @1 (Spalte) Zeile(1...4) und Spalte(1...16) ;----------------------------------------------------------------------- - .macro locate push temp1 push temp2 ldi temp1,@0 ldi temp2,@1 rcall lcd_locate pop temp2 pop temp1 .endmacro ;--------------------------------------------------------------- lcd_locate: push temp1 ; Register sichern push temp2 ; --- Position 4.Zeile --------------------------------- cpi temp1,4 brne pc+5 ldi temp1, Zeile4 - 1 add temp1, temp2 rcall lcd_command rjmp end_lcd_locate ; --- Position 3. Zeile -------------------------------- cpi temp1,3 brne pc+5 ldi temp1, Zeile3 - 1 add temp1, temp2 rcall lcd_command rjmp end_lcd_locate ; --- Position 2. Zeile -------------------------------- cpi temp1,2 brne pc+5 ldi temp1, Zeile2 - 1 add temp1, temp2 rcall lcd_command rjmp end_lcd_locate ; --- Position 1. Zeile -------------------------------- ldi temp1, Zeile1 - 1 add temp1, temp2 rcall lcd_command end_lcd_locate: pop temp2 pop temp1 ; Register rücksichern ret ;--------------------------------------------------------------- Es funktioniert soweit tadelos. Nur habe ich ein ziemlich langes Bedienungsmenü für einen ATMEGA8 programiert und jeder Aufruf von locate Z, Sp verbraucht 14 byte Codespeicher (jetzt wirds knapp mit dem Speicher). Kann man obigen Code noch optimieren oder muss ich mir die "Bequemlichkeit" des Schreibens mit dem Makro "erkaufen"? Hat jemand einen Vorschlag? Vielen Dank Axel
Hi Warum so kompliziert
1 | locate: push ZL |
2 | push ZH |
3 | push temp2 |
4 | |
5 | clr temp2 ; Null |
6 | dec temp1 |
7 | ldi ZL, Low(ln_start<<1) |
8 | ldi ZH,High(ln_start<<1) |
9 | add ZL,temp1 |
10 | adc ZH,temp2 |
11 | lpm temp1,Z |
12 | |
13 | pop temp2 |
14 | dec temp2 |
15 | add temp1,temp2 |
16 | rcall lcd_command |
17 | |
18 | pop ZH |
19 | pop ZL |
20 | ret |
21 | |
22 | ln_start: .db Zeile1,Zeile2,Zeile3,Zeile4 |
Da du temp1 und temp2 schon im Macro sicherst brauchst du das nicht noch einmal im Unterprogramm. Und wenn du bei der Zeilen- und Spaltennumerierung mit 0 anfängst, lassen sich noch 2 Befehle einsparen (dec temp1 u.dec temp2). MfG Spess
Axel schrieb: > Hat jemand einen Vorschlag? Ich weiß aber nicht, ob er Dir gefällt, denn das Konzept ist anders: Ich schreibe vom Programm aus nicht direkt auf das LCD, sondern auf einen Bildschirmspeicher im AVR-SRAM. Dabei sind die Zeichen linear angeordnet, also Zeile 0 zuerst, dann Zeile 1, 2 und 3. Das Übertragen an das LCD erfolgt in einem Task der Mainloop, der alle etwa 1ms aufgerufen wird und nur 1 Zeichen an das LCD sendet. Dies spart Rechenzeit, da keinerlei Warteschleifen oder Busy-Abfragen nötig sind. Beim Übertragen an das LCD wird auch gleich der LCD-bedingte Zeilensalat korrigiert. Im Bildschirmspeicher (VLCD, virtuelles LCD) steht also Fließtext, das LCD erhält bei jedem Zeilenwechsel zusätzlich den Befehl zum Setzen der Ausgabeposition. Locate muss also in meinem Fall nur die Schreib-Position im Bildschirmspeicher fälschen und ist daher sehr einfach und klein. Im Falle eines LCDs 4x27 oder 4x40 sieht Locate so aus:
1 | ;============================================================================= |
2 | ; locate Zeile,Spalte ;Setzt die Ausgabeposition im VLCD auf |
3 | ;Zeile (0...3) und Spalte (0...26). |
4 | ;----------------------------------------------------------------------------- |
5 | .macro locate ;Zeile (0...3), Spalte (0...27) |
6 | ;Positionierung der Ausgabeposition |
7 | push wl ;Register sichern |
8 | ldi wl,((@0 & 3)*spn)+(@1 & 63)+low(vlcd) ;Position im VLCD errechnen |
9 | sts wpos,wl ;und einstellen |
10 | pop wl ;Register wiederherstellen |
11 | .endmacro |
Dazu gibt es auch gleich noch ein Blinkmakro, mit dem ein Teil des Textes blinkend dargestellt wird (Software, nicht die Blinkfunktion des LCDs). Dies ist sehr hilfreich bei Menüs, man kann den gerade änderbaren Parameter blinken lassen.
1 | ;============================================================================= |
2 | ; blink Zeile,Spalte,Anzahl ;Markiert die Blinkposition im VLCD auf |
3 | ;Zeile (0...3), Spalte (0...26) und Anzahl blinkender |
4 | ;Zeichen. |
5 | ;----------------------------------------------------------------------------- |
6 | .macro blink ;Zeile (0...3), Spalte (0...27), Anzahl Blinkzeichen |
7 | ;Positionierung der Blinkposition |
8 | push wl ;Register sichern |
9 | ldi wl,((@0 & 3)*spn)+(@1 & 63)+low(vlcd) ;Startposition im VLCD errechnen |
10 | sts blinka,wl ;und einstellen |
11 | subi wl,-@2 ;Endposition im VLCD |
12 | sts blinke,wl ;einstellen |
13 | pop wl ;Register wiederherstellen |
14 | .endmacro |
Falls es Dich interessiert, hänge ich zum besseren Verständnis mal die LCD-Routinen für 4x27 (2 Controller) und 2x16 (1 Controller) an. Für 4x20 habe ich sie noch nicht angepasst. ...
Hallo Spess, hat etwas gedauert, bis ichs gerafft hab. Wenn ich richtig verstanden habe: locate: push ZL push ZH push temp2 clr temp2 ; Null dec temp1 ; * ldi ZL, Low(ln_start<<1) ; Startadr. der Tabelle ldi ZH,High(ln_start<<1) ; in Z-Pointer add ZL,temp1 ; + Eintrag in Tabelle adc ZH,temp2 ; + auch in zh lpm temp1,Z ; Zeile in temp1 pop temp2 ; Spalte wieder in temp2 dec temp2 ; * add temp1,temp2 ; Zeile + Spalte rcall lcd_command ; -> Befehl -> LCD pop ZH pop ZL ret ln_start: .db Zeile1,Zeile2,Zeile3,Zeile4 *: Und wenn du bei der Zeilen- und Spaltennumerierung mit 0 anfängst, lassen sich noch 2 Befehle einsparen (dec temp1 u.dec temp2). Mich hat Low(ln_start<<1) verwirrt. Kann ich auch schreiben Low(ln_start)? - Funktioniert nämlich auch. Vielen Dank Axel
Hallo Hannes,
> Ich weiß aber nicht, ob er Dir gefällt, denn das Konzept ist anders:
Ursprünglich wollte ich es auch nach Deinem Konzept machen. Ich hab
sooft Deine Routinen duchgelesen, aber leider nicht die Funktionsweise
verstanden :(
Axel
Hi
> Kann ich auch schreiben Low(ln_start)? - Funktioniert nämlich auch.
Wirklich? Die Adresse ist dann nämlich falsch. 'lpm' erwartet eine
Adresse in Bytes. 'ln_start' representiert aber eine Adresse in Word
gerechnet und ist damt um die Hälfte zu klein.
Statt 'ln_start<<1' kannst du auch 'ln_start*2' benutzen. Ist für manche
anschaulicher.
Übrigens, so etwas ' brne pc+5' solltest du vermeiden. Ist nicht
sonderlich informativ und erschwert spätere Änderungen.
Die 'push/pop's' würde ich aus dem Macro herausnehmen und manuell
ergänzen. Ist nämlich oft nicht notwendig.
MfG Spess
Hallo Spess,
> Übrigens, so etwas ' brne pc+5' solltest du vermeiden.
Du hast recht. Wurde mir auch schon einige male zum Verhängnis. Wollte
halt labels einsparen.
Danke für die Tipps.
Übrigens: Wenn ich noch Fragen zu der Variante von Hannes habe (VLCD),
soll ich hier nochmal im Forum fragen? Genial wäre natürlich, wenn man
dieses Thema an das LCD-Tutorial anfügen könnte. So viel ich weiß
braucht man einen Puffer auch für Grafik-LCDs.
Axel
Hallo Hannes,
> Was genau verstehst Du nicht?
Erstmal die allgemeine, grobe Funktionsweise. Ich verstehe es im Moment
so:
Mit locate wird Zeile und Spalte im VLCD festgelegt (wpos).
Mit lcd_data ein Datenbyte ins VLCD (Position wpos)geschickt.
Anschliessend flag setzen.
In der Mainloop das Datenbyte (oder Stz) vom VLCD ins LCD schreiben
(Position rpos = wpos?)
Einige Fragen:
Zu Beginn wird in der Init jedes byte im VLCD mit ' ' beschrieben?
Danach können im VLCD mehrere Datenbytes an unteschiedlichen Stellen
stehen?
Welche bytes werden vom VLCD an das LCD geschickt? Das zuletzt
hinzugekommene oder immer alle?
Wofür ist das flag upsync? Es wird gesetzt und gelöscht aber ich sehe
keine Abhängigkeit? Oder muss ich das in der Main abfragen? wenn gesetzt
-> lcd_update aufrufen?
Danke für Deine Geduld
1. Die Positionen sind doch fast immer konstant. Es macht also keinen Sinn, sie umständlich zur Laufzeit zu berechnen. Schreib Dir ein Macro, was die Berechnung durchführt, dann reduziert sich das Setzen auf 2 Worte. 2. Laß diese Push/Pop-Arien. Definiere Dir einige Scratchpadregister, die nicht gesichert werden müssen. Du hast genug Register, da ist Sparen unnötig. 3. Wenn Du oft Text an festen Stellen ausgibst, schreib ne Unterfunktion, wo das erste Byte die Position ist und danach der Text kommt. Peter
Hallo Peter, > Laß diese Push/Pop-Arien. Ich mache das mit asm noch nicht so lange und im Tutorial oder in diesen Beitrag http://www.mikrocontroller.net/articles/AVR-Tutorial:_Stack#Weitere_Informationen_.28von_Lothar_M.C3.BCller.29: wird immer darauf hingewiesen, Register zu sichern. Aber du hast recht, wenn ich mir 2 Register definiere, die nie gesichert werden, kann ich mir einiges sparen. Obwohl es mit meinen Registern knapp ist: Ich verwende Deine Entprellroutine, Prescaler, 2 Reg. für Encoder-Zustände, und natürlich für mein Programm. > Wenn Du oft Text an festen Stellen ausgibst, schreib ne Unterfunktion, > wo das erste Byte die Position ist und danach der Text kommt. Das ist eine gute Idee. Das werd ich gleich mal versuchen. Ich habe auch das Gefühl, das mein Code ziemlich umständlich ist. Fast die Hälfte des Codespeichers von meinem ATMEGA8 geht auf Kosten der LCD-Anzeige (Menütexte oder Makos)!!! ;-) Axel
Hi >Obwohl es mit meinen Registern knapp ist: Ich verwende Deine >Entprellroutine, Prescaler, 2 Reg. für Encoder-Zustände, und natürlich >für mein Programm. Du hast auch noch RAM. Einfach für die Werte Speicherplätze festlegen und auslagern. Register nur für eine Funktion zu benutzen ist Blödsinn. MfG Spess
Hallo Peter, ich nochmal. > Wenn Du oft Text an festen Stellen ausgibst, schreib ne Unterfunktion, > wo das erste Byte die Position ist und danach der Text kommt. > Schreib Dir ein Macro, was die Berechnung durchführt, dann reduziert > sich das Setzen auf 2 Worte. Ich hab mir für mein 4-zeiliges LCD nun eine Unterfunktion geschrieben, die immer 4 Zeilen aus einer Menütabelle ausgibt. Im Makro steht jetzt nur noch die Übergabe des ersten Menüpunktes in der Tabelle und der Aufruf der Unterfunktion. Und jeder Aufruf des Makros 'kostet' nur noch 4byte(2 Worte) Vielen Dank nochmal
Das passt zwar nicht ganz zu Deinem Konzept und auch nicht zu Peters Rat, ich will Dir aber trotzdem antworten. Allerdings bin ich nicht traurig, wenn Du (oder Andere) meinen Code nicht benutzt. Ich habe ihn für mich geschrieben, weil ich (wie Du ja auch) nicht viel davon halte, fremden unverstandenen Code zu benutzen. Man kann sicher einiges daran besser machen, aber mir genügt es und ich passe es gelegentlich an neue Anforderungen an. Axel schrieb: > Hallo Hannes, > >> Was genau verstehst Du nicht? > > Erstmal die allgemeine, grobe Funktionsweise. Ich verstehe es im Moment > so: > > Mit locate wird Zeile und Spalte im VLCD festgelegt (wpos). Richtig. Dabei liegen die Speicherzellen der Zeilen linear hintereinander. Das Array VLCD hat exakt Zeilen mal Spalten Speicherzellen, bei 2x16 also 32 Bytes, bei 2x24 48 Bytes, bei 4x27 108 Bytes. Dazu gibt es noch den Schreibzeiger (wpos, WritePosition) und den Lesezeiger (rpos, ReadPosition). > Mit lcd_data ein Datenbyte Kein Datenbyte, sondern ein ASCII-Zeichen... > ins VLCD (Position wpos)geschickt. Richtig, dabei wird wpos automatisch erhöht und bei Überlauf (Ende des Arrays VLCD) auf den Anfang des Arrays gesetzt. > Anschliessend flag setzen. Das Flag sagt der Transfer-Routine (Array -> LCD), dass es mindestens ein neues Zeichen gibt und deshalb das LCD nicht mehr aktuell ist, also ein Update braucht. > In der Mainloop das Datenbyte (oder Stz) vom VLCD ins LCD schreiben Die Routine LCD_Data legt die ASCII-Zeichen einfach in das Array und erhöht den Index (Zeiger auf Schreibposition). Dies ist ein einfacher SRAM-Zugriff und kostet nur wenige Takte Rechenzeit. Würde man direkt auf das LCD schreiben, so müsste man mit Wartezeiten rechnen, entweder Hardware-Busywait oder eine Software-Warteschleife. Diese Wartezeiten treten bei Benutzung des Arrays als Bildschirmspeicher nun nicht mehr auf. > (Position rpos = wpos?) Wpos ist die Schreibposition, also die Adresse im Array, an die das nächste Zeichen geschrieben wird. Diese verkörpert exakt eine bestimmte Ausgabeposition am realen LCD. Nun müssen die Zeichen aber noch ins reale LCD gebracht werden, denn den Bildschirmspeicher kann ja niemand lesen. Dazu wird im Zeitraster von etwa 1ms ein Zeichen aus dem Array an das LCD gesendet. Das könnte man im Interrupt machen, ich bevorzuge aber dazu die Mainloop, und zwar einen Job der Mainloop, der alle 1ms von einem Merker (Flag, Semaphore, ...) namens "upsync" angeschubst wird, der von einem sowiso gebrauchten Timer-Interrupt nebenher mit gesetzt wird. Und hier wird der zweite Zeiger auf das Array gebraucht, nämlich RPOS. Auch dieser wird bei jedem Lesezugriff erhöht. Dabei wird geprüft, ob das Ende einer Zeile erreicht wurde. Ist das der Fall, wird dem LCD statt des ASCII-Zeichens ein Positionier-Kommando (Set DD-RAM-Adress) gesendet. Das Zeichen wird dann "in der nächsten Runde" nachgereicht, für dessen Verwaltung gibt es einen weiteren Merker (zeiwe, Zeilenwechsel). Damit die Routine nicht unnötig das LCD aktualisiert, wird mit einem weiteren Merker (update) der Ablauf blockiert, wenn keine neuen Schreibzugriffe auf das Bildschirmspeicher-Array mehr erfolgt sind. Steuerzeichen... Da ich es leid war, die Ausgabetexte an die Zeilenlänge anpassen zu müssen, habe ich erstmal chr$(13) (Return, Enter) implementiert. Tritt dieses Zeichen auf, so wird der Rest der Zeile mit Leerzeichen aufefüllt (gelöscht) und auf den Beginn der nächsten Zeile positioniert. Dann sind noch ein paar Steuerzeichen dazugekommen, die aber zum Teil schon von den Textausgaberoutinen abgefangen werden. Falls es Dich interessiert, hänge ich die mal an. Da gibt es auch eine Routine zur Ausgabe indizierter Texte (Menüpunkte, Wochentage, ...). > > Einige Fragen: > Zu Beginn wird in der Init jedes byte im VLCD mit ' ' beschrieben? Ja, damit wird das LCD gelöscht, es enthält dann einen Text aus Leerzeilen. > Danach können im VLCD mehrere Datenbytes ASCII-Zeichen, also Text oder Steuerzeichen. > an unteschiedlichen Stellen > stehen? Ja, im VLCD steht der anzuzeigende Text im ASCII-Format. > Welche bytes werden vom VLCD an das LCD geschickt? Das zuletzt > hinzugekommene oder immer alle? Immer alle reiherum, und zwar solange, wie während der "letzten Runde" kein neuer Schreibzugriff auf das Array erfolgte. Bei 4x27 (108 Zeichen) sind das "je Runde" 112 LCD-Zugriffe (108 ASCII-Zeichen und 4 Set-DD-RAM-Adress-Kommandos), also 112 ms. Es gibt also etwa 8 komplette Updates je Sekunde. So schnell kann ich nicht lesen. Bei 4x20 wären es 84 Zugriffe (ms) je Runde, also etwa 12 Updates je Sekunde. > Wofür ist das flag upsync? Es wird gesetzt und gelöscht aber ich sehe > keine Abhängigkeit? Oder muss ich das in der Main abfragen? wenn gesetzt > -> lcd_update aufrufen? Genau! Und da der Zeitabstand von 1 ms auch gut zur Drehgeberabfrage passt, kann man ohne weiteres beides kombinieren... ;-) > > > Danke für Deine Geduld Nix zu danken... ...
Axel schrieb: > http://www.mikrocontroller.net/articles/AVR-Tutorial:_Stack#Weitere_Informationen_.28von_Lothar_M.C3.BCller.29: > wird immer darauf hingewiesen, Register zu sichern. Das Tutorial ist ein "One size fits all" Machwerk. Dort wird dir zunächst die 100% sichere Variante vermittelt. Das Tutorial weiß ja nicht, in welchem Umfeld seine 'Weisheiten' benutzt werden, also muss es auf Nummer sicher gehen. Im Einzelfall kann man natürlich davon abweichen, solange man weiß was man tut.
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.