Forum: Projekte & Code Einfaches "Print" in Assembler ohne Parameter


von Maik J. (caldrogyn)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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"

von Axel S. (a-za-z0-9)


Lesenswert?

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"

: Bearbeitet durch User
von Maik J. (caldrogyn)


Lesenswert?

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.

von spess53 (Gast)


Lesenswert?

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

von Peter D. (peda)


Lesenswert?

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.

von spess53 (Gast)


Lesenswert?

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

von Maik J. (caldrogyn)


Lesenswert?

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.

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.