Forum: Mikrocontroller und Digitale Elektronik Makro locate (ASM)


von Axel (Gast)


Lesenswert?

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

von spess53 (Gast)


Lesenswert?

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

von Hannes L. (hannes)


Angehängte Dateien:

Lesenswert?

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.

...

von Axel (Gast)


Lesenswert?

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

von Axel (Gast)


Lesenswert?

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

von spess53 (Gast)


Lesenswert?

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

von Axel (Gast)


Lesenswert?

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

von Hannes L. (hannes)


Lesenswert?

Was genau verstehst Du nicht?

...

von Axel (Gast)


Lesenswert?

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

von Peter D. (peda)


Lesenswert?

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

von Axel (Gast)


Lesenswert?

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

von spess53 (Gast)


Lesenswert?

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

von Axel (Gast)


Lesenswert?

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

von Hannes L. (hannes)


Angehängte Dateien:

Lesenswert?

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...

...

von Karl H. (kbuchegg)


Lesenswert?

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
Noch kein Account? Hier anmelden.