Ich hatte neulich eine Idee um ganz einfach Texte auszugeben. In diesem
Fall waren es Debugmeldungen an eine Serielle Schnittstelle. Die wollte
ich nachträglich in einen Programmteil einsetzen. Der übliche Weg, die
Texte am Programmende mit Zeiger ablegen, Zeiger nach Z-Pointer
(vielleicht noch vorher auf den Stack sichern), Print-Routine aufrufen,
war mir da nach kurzer Zeit zu umständlich!
Ich bin dann auf die Idee gekommen die Addresse der Zeichen im Stack zu
übergeben, und wenn die Zeichen gleich nach dem call-Unterprogrammaufruf
stehen, schiebt der call automatisch die richtige Addresse auf den
Stack.
1
_StatusOut:
2
call _DCPrint
3
.db " *****************************",10,0
4
call _DCPrint
5
.db " Programm beendet, <a> Anwendersoftware, <b> Bootloader",10,0
6
_StatusLoop:
7
ldi TEMP0, (3<<p_StatusTWiOK) ;TWi Status prüfen
8
and TEMP0, StatusByte
Die Null ist das Zeichen für Textende. Die Printroutine holt sich die
Zeichenkettenaddresse vom Stack, gibt die Zeichen bis zur Null an die
Serielle Schnittstelle, schiebt die nächste Word Addresse nach der Null
auf den Stack und springt mit ret zurück. Die zwei Zeilen (Aufruf und
Text) können an (fast) jeder Stelle im Code eingefügt werden ohne zu
stören, nur r0:r1 werden verändert. Natürlich muß man bei zeitkritischen
Aktionen aufpassen und lange Texte führen schnell zu einem "relative
branch out of reach". Ich hatte auch ein Problem, nachdem ich die
Kommentare entfernt hatte mußte ich einen durch eine Warteschleife
ersetzen.
Hier ist das Unterprogramm, es ist für einen Atmega1284 geschrieben,
sollte sich aber gut anpassen lassen, bei den meisten (kleineren) fällt
dann auch das hantieren mit RAMPZ weg.
1
; *** Text nach dem call-Befehl (Rücksprungaddresse) ausgeben
2
; *** bis zur nächsten Null
3
; *** R0, R1 werden verändert
4
5
_DCPrint:
6
pop R1 ;Rücksprungaddresse vom Stack holen
7
pop R0 ;= Start der Zeichenkette
8
push TEMP0 ;benötigte Arbeitsregister sichern
9
push ZL
10
push ZH
11
in TEMP0, RAMPZ
12
push TEMP0
13
ldi TEMP0, 0
14
mov ZH, R1 ;nach Z verschieben
15
mov ZL, R0
16
lsl ZL ;und zu Byte Addresse machen
17
rol ZH
18
rol TEMP0
19
out RAMPZ, TEMP0
20
21
_DCPrintLoop:
22
elpm TEMP0, Z+ ;Zeichen holen
23
cpi TEMP0, 0 ;bei Null
24
breq _DCPrintEnde ;beenden
25
_DCPrintLoop2:
26
lds TEMP0, UCSR0A ;prüfen ob die UART frei ist
27
sbrs TEMP0, UDRE0
28
jmp _DCPrintLoop2 ;sonst warten
29
30
sts UDR0, TEMP0 ;Zeichen schicken
31
jmp _DCPrintLoop ;und nächstes
32
_DCPrintEnde:
33
34
in TEMP0, RAMPZ ;Neue Rücksprungaddresse
35
lsr TEMP0 ;zu Word Addresse
36
ror ZH ;machen
37
ror ZL
38
adc ZL, NULL ;bei "0,5" im Carry aufrunden
39
40
mov R1, ZH
41
mov R0, ZL ;sichern
42
pop TEMP0 ;veränderte Register wiederherstellen
43
out RAMPZ, TEMP0
44
pop ZH
45
pop ZL
46
pop TEMP0
47
push R0 ;und neue Rücksprungaddresse auf den Stack schieben
48
push R1
49
ret
TEMP0 ist R16 und NULL ist R15 und immer Null, habe noch nie alle
Register gebraucht und das ist oft praktisch.
In Assembler ist es sinnvoll, einige Scratchpadregister zu definieren.
D.h. jede Funktion darf sie benutzen, ohne ständig Push/Pop-Orgien
veranstalten zu müssen. Dadurch wird der Code erheblich kleiner.
Das machen sogar die C-Compiler.
Hier findest Du meine puts Version:
Beitrag "I2C (TWI) Sniffer mit AVR"
Maik Jahabeich schrieb:> Ich hatte neulich eine Idee um ganz einfach Texte auszugeben.
...
> Die Null ist das Zeichen für Textende. Die Printroutine holt sich die> Zeichenkettenaddresse vom Stack, gibt die Zeichen bis zur Null an die> Serielle Schnittstelle, schiebt die nächste Word Addresse nach der Null> auf den Stack und springt mit ret zurück.
Herzlichen Glühstrumpf!
Du hast die PRIMM Funktion (PRint IMMediate) wiederentdeckt. Die habe
ich schon auf dem U880 (Z80) verwendet. Und auf dem C64.
XL
Siehe auch: Beitrag "Re: Cortex-M: Stack Begriffserklärung bzw. Handhabung"
Vielen Dank,
ich habe mir das schon gedacht, und wollte auch kein Patend anmelden,
sondern die Idee nur mal vorstellen, damit nicht jeder gezwungen ist,
das Rad selber neu zu erfinden.
Ich würde das Ding auch in den AVR-Softwarepool einpflegen, finde aber
keine passende Rubrik, vielleicht sollte man so etwas wie
"Softwareschnipsel" oder "kleine Helfer" als Rurik einführen.
Hi
Diese Art Routine kenne ich auch schon aus Z80/U880-Zeiten.
Kleiner Hinweis: Das
> in TEMP0, RAMPZ> push TEMP0> ldi TEMP0, 0> ...
funktioniert nur bis 128k Flash. ATMega2560/2561 und eine Reihe ATXMegas
fallen da raus.
MfG Spess
spess53 schrieb:> funktioniert nur bis 128k Flash.
Die absolute Schmerzgrenze für Assembler wär bei mir max 8kB gewesen.
Inzwischen mache ich auch bei 1kB (ATtiny13) nur noch C.
Hi
>Die absolute Schmerzgrenze für Assembler wär bei mir max 8kB gewesen.>Inzwischen mache ich auch bei 1kB (ATtiny13) nur noch C.
Wenn ich das
>.db " Programm beendet, <a> Anwendersoftware, <b> Bootloader",10,0
^^^^^^^^^^
lese, kann es durchaus Programmteile geben, die am oberen Flashende
liegen.
MfG Spess
Peter Dannegger schrieb:> Die absolute Schmerzgrenze für Assembler wär bei mir max 8kB gewesen.
Ich habe mit Assembler keine Schmerzen und programmiere gerne damit, das
ist bei mir auch nicht von der Grösse abhängig, sondern mehr davon was
das Programm tuen soll.
> Inzwischen mache ich auch bei 1kB (ATtiny13) nur noch C.
Ja, man wird alt...
Ich habe auf dem C64 angefangen, bin vom Basic schnell zu Assembler
gekommen und irgendwie immer dabei geblieben; mit C bin ich nie warm
geworden.
spess53 schrieb:> funktioniert nur bis 128k Flash. ATMega2560/2561 und eine Reihe ATXMegas> fallen da raus.
Ist mir schon klar, bei mehr als 128kByte kommt noch drittes Byte mit
der Rücksprungaddresse vom Stack und muß mitbehandelt werden.