Forum: Mikrocontroller und Digitale Elektronik Weigu AVR-Assemblerkurs Modul A


von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Lesenswert?

Hallo,

leider wurde der Anfangs-Thread hierzu geschlossen.
Die Anfänge und die Gründe dafür, kann man dort nachlesen. Da es beim 
genannten Thread keinen Verweis zu diesem gibt, entsteht, wenn man den 
genannten Thread zuerst aufruft, der Eindruck, daß es sich mehr oder 
weniger um eine Datenleiche handelt. Deshalb wäre es gut, wenn einer der 
Moderatoren am Ende des geschlossenen Threads einen Link hier her machen 
könnte.

Es geht um den Mikrocontrollertechnik-Kursus Modul A von Guy Weiler.

http://www.weigu.lu/tutorials/avr_assembler/pdf/MICEL_MODUL_A.pdf

http://www.weigu.lu/tutorials/avr_assembler/index.html



Ich bin nun auf der Seite 82 bei der Aufgabe A405. Das Programm habe ich 
mit den bis dahin vorgestellen Befehlen geschrieben und möchte nun mal 
ein Programm sehen, das die Aufgabe Geschwindigkeitsoptimiert erledigen 
würde.
Diese Version benötigt 4644 Zyklen für die 778 Daten bzw. 'B'.

1
 ldi    r16,'B'
2
 ldi    yl,$60
3
 ldi    yh,$00
4
_NEXT:
5
 st     y+,r16
6
 cpi    yh,$04
7
 brne   _NEXT
8
 cpi    yl,$00
9
 brne   _NEXT
10
 nop
11
12
 .EXIT


Beitrag "AVR-Mikrocontrollertechnik-Kursus in Assembler besprechung"


Bernd_Stein

von Rene Z. (renezimmermann)


Lesenswert?

Bei mir ist A405 auf Seite 72.
1
 cpi    yl,$00
2
 brne   _NEXT

kannst du dir sparen. Wenn YH das erste mal 0x04 wird muss YL = 0 sein.

: Bearbeitet durch User
von c-hater (Gast)


Lesenswert?

Bernd S. schrieb:

> Diese Version benötigt 4644 Zyklen für die 778 Daten bzw. 'B'.

Es sind ganz sicher deutlich weniger.

Hab' keine Lust genau nachzurechnen, aber schon ein grober Überschlag 
sagt: du bist bei der Beherrschung von Assembler immer noch keinen 
Schritt weiter. Du kannst immer noch nicht einmal den 
Rechenzeitverbrauch einer gegebenen Routine korrekt ermitteln...

Damit du wenigstens kapierst, wie man sowas überschlägig ermittelt:

Die innere (immer ausgeführte) Schleife braucht 5 Takte, 778 mal 
durchlaufen=3890.

Durchlaufen auch der äußeren Schleife erfordert einen Mehraufwand von 2 
Takten, wird aber hier allerhöchstens 4 mal passieren->kann man in den 
Skat drücken, genauso wie den "statischen" Teil ausserhalb der 
Schleifen. Zusammen macht das maximal 12 Takte.

Überschlägig also insgesamt ca. 4000 Takte. Das ist ein ordentliches 
Stück von dem weg, was du (wie auch immer) "ermittelt" hast...

> Ich bin nun auf der Seite 82 bei der Aufgabe A405. Das Programm habe ich
> mit den bis dahin vorgestellen Befehlen geschrieben und möchte nun mal
> ein Programm sehen, das die Aufgabe Geschwindigkeitsoptimiert erledigen
> würde.

Du kannst nicht erwarten, dass ein Programm optimiert ist, wenn die 
Aufgabe direkt vorgibt, suboptimal zu sein (weil hier der Plan nicht 
war, ein optimales Programm zu schreiben, sondern dem Lehrling die 
Verwendung einer bestimmten Adressierungsart zu vermitteln).

Für optimalen Code gibt es viele Strategien.

Bei sehr engen Schleifen ist natürlich "loop unrolling" immer eine sehr 
attraktive Strategie. Die Idee dahinter ist, den Verwaltungsaufwand für 
die Schleifensteuerung im Verhältnis zum Nutzcode im Schleifenrumpf zu 
reduzieren. Im konkreten Fall benötigt der Nutzcode der inneren Schleife 
2 Takte, die Schleifensteuerung aber 3. Das ist ein sehr schlechtes 
Verhältnis, nur in 2/5 der Zeit macht der Code, was eigentlich sein 
Job ist. Da kann man ganz sicher einiges dran verbessern...

Richtig Assembler programmieren können bedeutet: selber herausfinden, 
was genau (ohne Beschränkung durch eine lehrhafte Aufgabenstellung) und 
dies dann auch fehlerfrei umzusetzen. Dabei hilft dir kein Tutorial, 
sondern nur eigenes Denken. Das Tutorial soll nur dazu dienen, dir die 
nötigen Grundlagen zu vermitteln, also die vollständige Beherrschung des 
Befehlssatzes.

Denn nur, wenn du weisst, was geht, kannst du aus dem, was geht, das 
wählen, was sinnvoll ist,um die konkrete Aufgabe bezüglich der konkreten 
Anforderungen optimal umzusetzen. Für die konkrete Aufgabe würde ein 
richtiger Assemblerprogrammierer z.B. mit an Sicherheit grenzender 
Wahrscheinlichkeit eher nicht "indirect with postincrement" als 
Adressierungsart wählen, sondern "indirect with predecrement". Aber wie 
schon gesagt: es handelt sich um eine Lehraufgabe für Dummies...

Du musst vor allem auch Lernen, dass Optimierung nahezu immer ein 
Kompromiss zwischen Codegröße und Rechenzeit ist. Doofe Compiler können 
nur immer in eine Richtung optimieren. Richtige Assemblerprogrammierer 
hingegen kennen das Gesamtproblem und können von Fall zu Fall 
entscheiden, wie stark an welcher Stelle des Codes in welche Richtung zu 
optimieren ist. Vor allem deswegen sind sie cleverer als Compiler und 
können besseren Code erzeugen als diese.

Das "loop unrolling" ist übrigens ein recht einfach überschaubares 
Beispiel, um sich diese Sachverhalte grundlegend klar zu machen. 
Interessant ist da auch der Vergleich mit einem echten Compiler im 
Kontext eines realen Programmes. Nur sehr selten finden diese 
unsäglichen Dinger die optimale Größe für das Unrolling. Nur zu deutlich 
sieht man, dass nach 50 Jahren im Wesentlichen immer noch nur ziemlich 
dumme Macros den Übersetzer-Job machen, die nach wie vor keinerlei 
Ahnung vom Gesamtproblem haben...

von Jim M. (turboj)


Lesenswert?

c-hater schrieb:
> Interessant ist da auch der Vergleich mit einem echten Compiler im
> Kontext eines realen Programmes. Nur sehr selten finden diese
> unsäglichen Dinger die optimale Größe für das Unrolling.

Bei den 8-bittern spielt eher die Integer-Promotion eine Rolle, die oft 
überflüssige Instruktionen einfügt.

Für größeres Loop Unrolling hat ein AVR üblicherweise zuwenig Flash. 
Dafür kann aber der Compiler nix. Bei AVR-GCC kann man das mit "-O3" mal 
versuchen.

Moderne C-Compiler sind oftmals schneller und effizienter als 
manueller Assembler, insbesondere bei 32-bittigen  Plattformen.

Man sollte Effizenz auch nicht überbewerten. Wenn ich den Code bis auf 
die letzte Assembler Instruktion optimieren muss habe ich in der Auswahl 
der Hardware was falsch gemacht. Für zeitkritische Sachen haben moderne 
µC normalerweise dedizierte Peripherie eingebaut.

von S. Landolt (Gast)


Lesenswert?

$400-$60 = 928
928*5 -1 +5 = 4644 (ohne das nop)
So viel zur "Beherrschung von Assembler".
Im Übrigen sehe ich keine große Einsparmöglichkeit, solange die 
Fragestellung so allgemein ist; von den erwähnten zwei Zeilen mal 
abgesehen, aber das sind ja nur 2 Takte.

von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Lesenswert?

Rene Z. schrieb:
> Bei mir ist A405 auf Seite 72.
>  cpi    yl,$00
>  brne   _NEXT
>
> kannst du dir sparen. Wenn YH das erste mal 0x04 wird muss YL = 0 sein.
>
Danke, gleich der erste Beitrag, der wirklich auf die Fragestellung 
eingeht. Sind zwar nur 2 Takte, wobei wir nun bei insgesamt 4642 sind, 
aber das war ja das Ziel für mich, so wenig Takte wie möglich für diese 
Aufgabe zu benötigen.
Du hast schon recht, aber wenn mann in dem Acrobat Reader die 82 angibt, 
kommt man zu der Seite, die ich in diesem Dokument meine.

Zu diesen Zeitpunkt hatte ich gar nicht weiter überlegt, weil ich die 
Aufgabe mit diesen Pre-Dekrement und Post-Inkrement nicht so richtig 
wahrgenommen hatte und dadruch erst zwei Varianten mit Pre-Dekrement 
durchgespielt habe, wo auf die " Nullabfrage " nicht verzichtet werden 
kann.
1
 ldi    r16,'B'
2
 ldi    yh,$04
3
 ldi    yl,$00
4
_NEXT:
5
 st     -y,r16
6
 cpi    yl,$60
7
 brne   _NEXT
8
 cpi    yh,$00
9
 brne   _NEXT
10
 nop
11
 
12
 .EXIT

4650 Zyklen bzw. Takte
1
 ldi    r16,'B'
2
 ldi    yh,$04
3
 ldi    yl,$00
4
_NEXT:
5
 st     -y,r16
6
 cpi    yh,$00
7
 brne   _NEXT
8
 cpi    yl,$60
9
 brne   _NEXT
10
 nop
11
 
12
 .EXIT

4962 Takte

Hatte irgendwie noch aus alten C64-Zeiten im Sinn, das es von Vorteil 
ist Schleifen Abwärts auf Null hin laufen zu lassen, wegen der einfachen 
Auswertung des Zero-Flags.

Schon interessant wie man durch vorher richtiges Überlegen, mal eben für 
die gleiche Aufgabe bis zu 320 Takte einsparen kann.

c-hater schrieb:
> ...->kann man in den
> Skat drücken,....
>
> Bei sehr engen Schleifen ist natürlich "loop unrolling" immer eine sehr
> attraktive Strategie...
>
Erst einmal danke für deinen ausführlichen Beitrag. Jedoch bitte ich 
dich zu bedenken, das ich mir das ASM-Programmieren selber beibringe und 
daher so eingefleischte Profiausdrücke ( Skat; loop unrolling ) gar 
nicht interpretieren kann.
>
> Für die konkrete Aufgabe würde ein
> richtiger Assemblerprogrammierer z.B. mit an Sicherheit grenzender
> Wahrscheinlichkeit eher nicht "indirect with postincrement" als
> Adressierungsart wählen, sondern "indirect with predecrement".
>
In diesem Fall, den ich ich genaustens vorgebe hast du allerdings 
unrecht.

S. Landolt schrieb:
> $400-$60 = 928
> 928*5 -1 +5 = 4644 (ohne das nop)
>
Ich habe mich bei der Taktangabe einfach auf den Simulator verlassen, 
aber natürlich einen groben Fehler gemacht $03A0 ist eben nicht 3x256 + 
10, sondern 3x256 + 10x16 = 928. Schön, das du zwischendurch auch mal 
C-hater von seinem hohen Ross geholt hast ;-)


Bernd_Stein

von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Lesenswert?

Habe die Aufgabe A406 auf der Seite 83 so umgesetzt. Es kommen ja 168 x 
8 = 1344 Daten vor ( 1344 = 7 Tage x 24 Stunden x Datensatz (8) ).
An Adresse $0100 wird der Mittelwert von Sensor 1 abgelegt usw.,
so daß der letzte Sensorwert ( Sensor 8 ) der Woche in Adresse $063F 
abgelegt wurde. Der ATmega8 hat hierfür zu wenig internen SRAM, so daß 
der ATmega32 im Simulator zu verwenden ist.

Nun dachte ich mir bei solchen Konstrukten ( adiw y,8 ) ist es 
vielleicht besser, bei der Lowbyte-Adresse nicht genau die Endadresse zu 
überprüfen, sondern lieber auf größer oder gleich, falls man sich mal 
beim ausrechnen der Endadresse vertut.
Die " Arbeitsregisterverschwendung " dient des bessern Überblicks beim 
Simulatordurchlauf.

Was haltet ihr davon ?
1
 ser    r16             ;Alle Pins von...
2
 out    DDRD,r16        ;...PortD als Ausgang konfigurieren
3
 ldi    yh,$01          ;Zeiger fuer die indirekte und...
4
 ldi    yl,$00          ;...indirekte Adresssierung mit Offset konfigurieren
5
_NEXT: 
6
 ldd    r3,y+2          ;Sensor 3 Wert einlesen
7
 ld     r1,y            ;Sensor 1 Wert einlesen
8
 ldd    r5,y+4          ;Sensor 5 Wert einlesen
9
 out    PORTD,r3        ;Sensor 3 Wert ausgeben
10
 out    PORTD,r1        ;Sensor 1 Wert ausgeben
11
 out    PORTD,r5        ;Sensor 5 Wert ausgeben
12
 adiw   y,8             ;Zeiger auf den naechsten Datensatz setzen
13
 cpi    yh,$06          ;Pruefen, ob der Datenbereich...
14
 brne   _NEXT           ;...$0100 bis...    
15
 cpi    yl,$40          ;...$063F...
16
 brlo  _NEXT            ;...abgearbeitet ist
17
 nop
18
19
 .exit


Bernd_Stein

: Bearbeitet durch User
von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Angehängte Dateien:

Lesenswert?

Mich wundert das sich niemand an meinem Text zum BRLO-Befehl stört.
Ich meine aus einem anderen Blickwinkel passt es ja schon, aber zu BRLO 
-> Branch if Lower = If Rd < Rr bzw. C=1 ( BRCS -> Branch if Carry-Flag 
ist Set ), geht ja  eigentlich hervor, das so oft gesprungen werden 
soll, so lange Rd kleiner ist als K. Ja, das ist schon alles ziemlich 
verwirrend mit den Sprungbefehlen.

Seht den Anhang. Was wollen die mir mit NOTE : sagen ?

Meinen die nun Sternchen oder (1) ?

Bernd_Stein

von spess53 (Gast)


Lesenswert?

Hi

Ich programmiere seit mitte der 80er in Assembler. Und in dieser Zeit 
bin ich zu >95% der Fälle mit Sprüngen in Abhängigkeit von Z oder C 
ausgekommen. Deine permanente Krümelkackerei hilft dir also auch nicht 
wesentlich weiter.

>Meinen die nun Sternchen oder (1) ?

Das Sternchen steht in der gleichen Zeile wie (1). Du kannst es dir also 
aussuchen.

MfG spess

von E.Ehrhart (Gast)


Lesenswert?

Bernd S. schrieb:
> geht ja  eigentlich hervor, das so oft gesprungen werden
> soll, so lange Rd kleiner ist als K.

Nein, aus brlo geht nur hervor daß solange gesprungen wird solange C=1. 
Welche Ursache C=1 nun hat ist eine ganz andere Frage. Das kann z.B. 
eine Vergleichsoperation sein. Dabei wird der 2.Registerwert schlicht 
vom ersten abgezogen (insofern gilt es also auf die richtige Reihenfolge 
zu achten). Wird das Ergebnis dabei negativ ist C gesetzt und brlo 
springt. Fertig und aus.

> Ja, das ist schon alles ziemlich
> verwirrend mit den Sprungbefehlen.

Eigentlich nicht. Zu jedem Sprungbefehl ist doch klar definiert welches 
Flag den Sprung auslöst.

von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Lesenswert?

spess53 schrieb:
> Das Sternchen steht in der gleichen Zeile wie (1). Du kannst es dir also
> aussuchen.
>
Aha, das ist also so etwas wie eine ungeschriebene Regel oder was ?

>
> Deine permanente Krümelkackerei hilft dir also auch nicht
> wesentlich weiter.
>
Die kommt nicht von mir sondern von ATMEL ;-)
Also ich will nochmal genauer erklären, was mich verwirrt.
In der gezeigten Tabelle steht bei Unsigned BRLO => Rd > Rr ; C+Z=0.
Oh, erkenne gerade meinen ersten Denkfehler + bedeutet nicht UND sondern 
ODER.
Ok, die andere Sache ist, dass bei der Befehlsbeschreibung aber
If Rd < Rr ( C=1 ) steht und man dann erkennen muss, das dies das Selbe 
bedeutet. Hinzu kommt bei mir, das man beim CPI-Befehl Rd und K nicht 
tauschen kann.

Außerdem impliziert in der Tablle Branch if lower, das Rr bzw. K kleiner 
sein müsste als Rd, was in der Befehlsbeschreibung aber genau umgekehrt, 
aber " richtiger " ( Rd < Rr ) ist.

Zum anderen ist BRLO, genau dass Selbe wie BRCS, weshalb du auch ganz 
gut zu 95% der Fälle mit dem Z oder C-Flag zurechtkommst !

Wen das alles als Anfänger nicht verwirrt, der ist für mich wirklich ein 
Naturtalent.

Aber nun zur ursprünglichen Frage - würdest du die abschließende Prüfung 
in meinem Programmbeispiel nun mit BRCS oder BRNE machen und warum ?

E.Ehrhart schrieb:
> Nein, aus brlo geht nur hervor daß solange gesprungen wird solange C=1.
>
> Zu jedem Sprungbefehl ist doch klar definiert welches
> Flag den Sprung auslöst.
>
Nein, aus dieser Scheißtabelle eben nicht siehe C+Z=0. Klar ist dies nur 
in der Befehlsbeschreibung und die habe ich ja gar nicht hier hoch 
geladen.

Vielleicht, sollte ich versuchen diese Tabelle aus meinem Kopf zu 
streichen, genauso wie die Befehle BRLO und BRSH.

Bernd_Stein.

von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Lesenswert?

Kommen wir zur Aufgabe A407. Hier geht es um Cut&Paste. Also der 
SRAM-Bereich $0060 bis $01FF soll gelesen und in den SRAM-Bereich $0300 
bis $049F kopiert werden. Anschließend wird der erste Bereich $0060 - 
$01FF mit dem Nullbyte ( $7F ) überschrieben.

Geht das schneller als mit 5315 Takten ?

1
 ldi    xh,$00      ;X-Zeiger... 
2
 ldi    xl,$60      ;...einrichten
3
 ldi    yh,$03      ;Y-Zeiger...
4
 ldi    yl,$00      ;...einrichten
5
_NEXT: 
6
 ld     r16,x+      ;Erst Wert laden, dann X = X+1
7
 st     y+,r16      ;Erst Wert schreiben 
8
 cpi    xh,$02      ;Adressbereich $0060 - $01FF bzw....
9
 brne   _NEXT       ;...$0300 - $041F abgearbeitet ?
10
 ldi    r16,$7F     ;Ja, Nullbyte laden und in den Bereich...
11
_NULLBYTE:
12
 st     -x,r16      ;...$01FF - $0060 schreiben
13
 cpi    xh,$00      ;Bereich...
14
 brne  _NULLBYTE    ;...
15
 cpi    xl,$60      ;...abgearbeitet ?
16
 brne  _NULLBYTE    ;Nein, dann weiter machen
17
 nop                ;Marke fuer den Breakpoint im Simulator

Bernd_Stein

von E.Ehrhart (Gast)


Lesenswert?

Bernd S. schrieb:
> Zum anderen ist BRLO, genau dass Selbe wie BRCS, weshalb du auch ganz
> gut zu 95% der Fälle mit dem Z oder C-Flag zurechtkommst !

Was hat das eine mit dem anderen zu tun? Für ein und dieselbe 
MC-Sprunginstruktion (hier: zur Prüfung aufs C-Flag) kann man in 
AVR-Assembler halt verschiedene Schreibweisen verwenden.

> Wen das alles als Anfänger nicht verwirrt, der ist für mich wirklich ein
> Naturtalent.

Du solltest als Anfänger

> versuchen diese Tabelle aus meinem Kopf zu
> streichen

und Dir statt dessen die einfachen Befehlsbeschreibungen zu Gemüte 
führen. Das ist wirklich kein Hexenwerk, es werden immer nur einzelne 
Flags abgefragt die irgendwelche Instruktionen zuvor verändert haben.

> genauso wie die Befehle BRLO und BRSH

die ja eigentlich nur und ganz unschuldig die Vergleichsoperation 
hervorheben sollen.

> Hinzu kommt bei mir, das man beim CPI-Befehl Rd und K nicht tauschen kann.

Weil Vergleiche eben eine Subtraktion Wert2-Wert1 sind und die 
Reihenfolge hier nicht egal ist!

> Aber nun zur ursprünglichen Frage - würdest du die abschließende Prüfung
> in meinem Programmbeispiel nun mit BRCS oder BRNE machen und warum ?

Das kann man mit beiden. Wie immer führen bei Asm viele Wege zum Ziel! 
Die Schreibweise
1
 cpi    yh,$06          ;Pruefen, ob der Datenbereich...
2
 brne   _NEXT           ;...$0100 bis...    
3
 cpi    yl,$40          ;...$063F...
4
 brlo  _NEXT            ;...abgearbeitet ist

ist aber insofern schlauer als daß brlo hier definitiv alle Fälle 
unter Low-Adr. $40 erfasst (nach Erreichen von HighAdr 06 natürlich) und 
nicht nur einen bestimmten wie es mit der Prüfung aufs Z-Flag der Fall 
wäre = dem Erreichen von Punkt $40, was vielleicht infolge des 
Additions-Intervalls (und zuviel Denkfäulnis :) nie geschieht. Eine 
andere, übersichtlichere Variante des Tests auf Erreichen von $640 wäre 
z.B. der 16bittige Vergleich
1
ldi xl,$06
2
cpi yl,$40
3
cpc yh,xl
4
brlo next

mit dem Nachteil, daß hier noch ein weiteres Register zum Vergleichen 
eingespannt werden müsste. Zuweilen bietet es sich an, den Pointer mit 
sbiw über den Bereich rückwärts wandern zu lassen, damit erschlägst Du 
nämlich zwei Fliegen: Der Pointer bewegt sich und Du kannst gleich das 
sbiw C-Flag-Ergebnis für den Vergleich bemühen...
Wie könnte der zugehörige Code ausschauen?

von Patrick J. (ho-bit-hun-ter)


Lesenswert?

Hi

Bernd S. schrieb:
> Geht das schneller als mit 5315 Takten ?

Bernd S. schrieb:
> _NULLBYTE:
>  st     -x,r16      ;...$01FF - $0060 schreiben
>  cpi    xh,$00      ;Bereich...
>  brne  _NULLBYTE    ;...
>  cpi    xl,$60      ;...abgearbeitet ?
>  brne  _NULLBYTE    ;Nein, dann weiter machen
>  nop                ;Marke fuer den Breakpoint im Simulator

Denke, Du kommst außer in zwei Fällen mit EINER Prüfung aus.
XH ist im letzten Durchgang immer 00, somit brauchst Du in Deinem Code 
immer die Prüfung auf XL.
Wenn Du als Erstes auf XL prüfst, hast Du pro XH nur einen Fall, wo XL 
'passt' und XH als zweite Prüfung hinzu genommen werden muß.

Also so:
1
 _NULLBYTE:
2
  st     -x,r16      ;...$01FF - $0060 schreiben
3
  cpi    xl,$60      ;Bereich...
4
  brne  _NULLBYTE    ;...
5
  cpi    xh,$00      ;...abgearbeitet ?
6
  brne  _NULLBYTE    ;Nein, dann weiter machen
7
  nop                ;Marke fuer den Breakpoint im Simulator
... ungetestet ...


MfG

von S. Landolt (Gast)


Lesenswert?

"Entscheidend ist, was hinten rauskommt"
Warum schreibt man nicht gleich in der ersten Schleife dieses 
"Nullbyte", also:
1
 .
2
 .
3
 ldi    r17,$7F
4
_NEXT: 
5
 ld     r16,x       ;Erst Wert laden, dann X = X+1
6
 st     x+,r17
7
 .
8
 .

von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Lesenswert?

E.Ehrhart schrieb:
> Eine
> andere, übersichtlichere Variante des Tests auf Erreichen von $640 wäre
> z.B. der 16bittige Vergleich
> ldi xl,$06
> cpi yl,$40
> cpc yh,xl
> brlo next
>
Mir ist bei der Assemblerprogrammierung die Geschwindigkeit am 
wichtigsten, bei anderen Sprachen ist dies sicherlich auch die 
Übersichtlichkeit, deshalb kommt diese Variante in Assembler für mich 
nicht in Frage.


Bernd_Stein

von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Lesenswert?

Patrick J. schrieb:
> Also so: _NULLBYTE:
>   st     -x,r16      ;...$01FF - $0060 schreiben
>   cpi    xl,$60      ;Bereich...
>   brne  _NULLBYTE    ;...
>   cpi    xh,$00      ;...abgearbeitet ?
>   brne  _NULLBYTE    ;Nein, dann weiter machen
>   nop                ;Marke fuer den Breakpoint im Simulator
> ... ungetestet ...
>
Danke, besonders für den zusätzlich erleuternden Text. Spart mal eben 
316 Takte, also insgesamt nur noch 4999 und ehemals 5315.

S. Landolt schrieb:
> Warum schreibt man nicht gleich in der ersten Schleife dieses
> "Nullbyte", also: .
>  .
>  ldi    r17,$7F
> _NEXT:
>  ld     r16,x       ;Erst Wert laden, dann X = X+1
>  st     x+,r17
>
Weil die ursprünglichen Daten in diesem Bereich zuerst in einem anderen 
Bereich verschoben, kopiert, gesichert, oder wie auch immer, werden 
sollen. Und dann erst soll dieser Bereich mit dem Nullbyte überschrieben 
werden.
Außerdem kommt man so auch nur mit einem Arbeitsregister (r16) aus und 
braucht kein weiteres.

Du hast das Plus geklaut, aber den Text nicht geändert ;-)


Bernd_Stein

von Peter D. (peda)


Lesenswert?

Bevor man sich unnütz Zeit für Optimieren ans Bein bindet, sollte man 
erstmal prüfen, wieviel % CPU-Last die Routine in der gesamten 
Applikation wirklich kostet.

4644 Zyklen bei 16MHz sind nichtmal 0,3ms. Dagegen ist ein Wimpernschlag 
schon eine Ewigkeit.

Ich verwende für analoge Steuerungen und Regelungen schon länger nur 
noch float, kostet ja nichts. Es macht aber die Programmierung deutlich 
komfortabler, übersichtlicher und weniger fehleranfällig.
Und für den Benutzer ist kein zeitlicher Unterschied gegenüber int 
bemerkbar.

von S. Landolt (Gast)


Lesenswert?

Deshalb mein einleitendes Zitat - am Ende sieht man keinen Unterschied. 
Und was das zusätzliche Register betrifft, nun, wenn das tatsächlich 
nicht frei sein sollte, dann macht man eben am Beginn/Ende ein push/pop, 
die 4 Takte bekommt man vielfach zurück.

von S. Landolt (Gast)


Lesenswert?

an Peter Dannegger
Ich bezog mich auf Bernd Stein, pardon, ich hätte zitieren sollen. Im 
übrigen handelt es sich ja um reine Theorie, Sandkastenspiele, oder 
besser Fingerübungen - es hört sich ja auch kein Mensch mit Vergnügen 
Tonleitern auf dem Klavier an.

von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Lesenswert?

Peter D. schrieb:
> Bevor man sich unnütz Zeit für Optimieren ans Bein bindet, sollte man
> erstmal prüfen, wieviel % CPU-Last die Routine in der gesamten
> Applikation wirklich kostet.
>
Ja schon, aber das ist nun mal ein Tick von mir. Seit dem ersten 
Programm auf  dem C64 in Assembler, bin ich halt von der Geschwindigkeit 
dieser Sprache fasziniert und sehe auch da den einzigartigen Vorteil 
dieser Programmiersprache. Natürlich ist das alles heute nicht mehr so 
extrem wie zu Commodore C64-Basic-Zeiten.

Also, lass mir meinen Fetisch und stör dich nicht weiter drann ;-)


Bernd_Stein

von E.Ehrhart (Gast)


Lesenswert?

Bernd S. schrieb:
> Mir ist bei der Assemblerprogrammierung die Geschwindigkeit am
> wichtigsten, bei anderen Sprachen ist dies sicherlich auch die
> Übersichtlichkeit, deshalb kommt diese Variante in Assembler für mich
> nicht in Frage.

Dann hast Du vermutlich noch kein größeres Projekt in Asm erstellt und 
beschränkst Dich hier wirklich nur auf einen Speed-

Bernd S. schrieb:
> Fetisch

Besonders Asm ist bei der Kleinteiligkeit seiner Bausteine, gerade bei 
größeren Projekten, auf eine ausreichende Übersichtlichkeit angewiesen, 
die sich durchaus auch herstellen lässt. Der eigentliche konträre 
Gegenpart zur Geschwindigkeit heißt aber sehr oft: Codegröße! Für viele 
Projekte ist die erzielte Geschwindigkeit mehr als genug, es hilft dann 
aber alles nichts wenn der tolle hochperformante Code nicht mehr in den 
niedlichen AVR-Speicher passt. Was in der Praxis wirklich von 
Bedeutung ist hat Peter D. auf den Punkt gebracht. Dem tragen letztlich 
auch die vorhandenen Hochsprachen Rechnung.

von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Angehängte Dateien:

Lesenswert?

Ich bin jetzt auf Seite 90 => Aufgabe A408.
Kann mir jemand schreiben, warum in R02 der Wert $4D für 'M' nicht 
auftaucht ?
1
;.ORG INT_VECTORS_SIZE       ;Interrupt Vektortabelle ueberspringen
2
3
 ldi    zl, LOW(TXTSTR*2)   ;Z-Zeiger mit der Texttabellenadresse des...
4
 ldi    zh,HIGH(TXTSTR*2)   ;...Programmspeichers laden
5
 ser    r16                 ;PortD komplett als Ausgang.... konfigurieren
6
 out    DDRD,r16            ;...konfigurieren                
7
_NEXT:                      ;Zeichen aus Texttabelle lesen und Z-Zeiger..
8
 ld     r2,z+               ;...ein Zeichen weiter setzen
9
 out    PORTD,r2            ;Zeichen auf PortD ausgeben
10
 tst    r2                  ;Zeichenende ereicht ( NUll ) ?
11
 brne   _NEXT               ;Nein, naechstes Zeichen 
12
 nop                        ;Breakpointmarke fuer den Simulator
13
14
.ORG $0300
15
16
TXTSTR:                    ;Texttabelle als...
17
.DB "MICEL",0              ;...Bytetabelle anlegen
18
19
.EXIT

Bernd_Stein

von E.Ehrhart (Gast)


Lesenswert?

Bernd S. schrieb:
> Kann mir jemand schreiben, warum in R02 der Wert $4D für 'M' nicht
> auftaucht ?

Klassischer Anfängerfehler.
Überlege nochmal, wie man Daten aus dem Flash ausliest!

von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Angehängte Dateien:

Lesenswert?

E.Ehrhart schrieb:
> Überlege nochmal, wie man Daten aus dem Flash ausliest!
>
Oh, Mann - Danke. Nach diesem Tipp hab ich es natürlich sofort gesehen.
1
.ORG INT_VECTORS_SIZE       ;Interrupt Vektortabelle ueberspringen
2
3
 ldi    zl, LOW(TXTSTR*2)   ;Z-Zeiger mit der Texttabellenadresse des...
4
 ldi    zh,HIGH(TXTSTR*2)   ;...Programmspeichers laden
5
 ser    r16                 ;PortD komplett als Ausgang.... konfigurieren
6
 out    DDRD,r16            ;...konfigurieren                
7
_NEXT:                      ;Zeichen aus Texttabelle lesen und Z-Zeiger..
8
 lpm    r2,z+               ;...ein Zeichen weiter setzen
9
 out    PORTD,r2            ;Zeichen auf PortD ausgeben
10
 tst    r2                  ;Zeichenende ereicht ( NUll ) ?
11
 brne   _NEXT               ;Nein, naechstes Zeichen 
12
 nop                        ;Breakpointmarke fuer den Simulator
13
14
.ORG $0033
15
16
TXTSTR:                    ;Texttabelle als...
17
.DB "MICEL",0              ;...Bytetabelle anlegen
18
19
.EXIT

Jetzt hab ich noch zwei andere Verständnisprobleme. Ich wollte halt 
sehen, ab welcher Adresse das Programm endet und ich die Tabelle 
einfügen kann.
Dies kann man ja schön in der .lss Datei sehen.

1. Warum ist im Programmspeicher der erste Befehl an Adresse $0054,
   aber im Listingfile (.lss) richtigerweise an $002A ?

2. Wieso bekomme ich auf einmal beim simulieren, solch eine Ansicht,
   wenn ich die Zeile mit .ORG INT_VECTORS_SIZE einfüge ?

Bernd_Stein

von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Lesenswert?

Bernd S. schrieb:
> 1. Warum ist im Programmspeicher der erste Befehl an Adresse $0054,
>    aber im Listingfile (.lss) richtigerweise an $002A ?
>
Da bin ich gerade selbst drauf gekommen. Weil er nicht in der 
Word-Ansicht dargestellt wird sondern in der Byte-Ansicht ( Adresse*2 ).

Bernd_Stein

von Rudi (Gast)


Lesenswert?

Bernd S. schrieb:
> Ich wollte halt
> sehen, ab welcher Adresse das Programm endet und ich die Tabelle
> einfügen kann

Da gibts nix notwendigerweise zu "sehen". Mach Dein .DB Statement an 
beliebiger Code-Stelle (aber hinter der Resetmarke) und gut ist. Die 
Datenbyteanzahl sollte wegen der Flash-Wortlänge allerdings geradzahlig 
sein (notfalls noch eine unnütze Null dran).

von Rudi (Gast)


Lesenswert?

Rudi schrieb:
> aber hinter der Resetmarke

... und natürlich auch hinter der verwendeten Interrupttabelle :)

von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Lesenswert?

Rudi schrieb:
> Die
> Datenbyteanzahl sollte wegen der Flash-Wortlänge allerdings geradzahlig
> sein (notfalls noch eine unnütze Null dran).
>.
Sowas macht glücklicherweise der Assembler. Schade das niemand Punkt 2 
erklären konnte.

Bei der Aufgabe A500:
b) Wieso werden beim Sprungbefehl wohl zwei Werte angegeben?

Weil gesprungen wird und einmal nicht. Jetzt Frage ich mich jedoch, 
warum gibt es da Taktmäßig eine Unterscheidung ? Befehlsholphase und 
Befehlsausführungsphase werden doch zu diesem Zeitpunkt in einem Takt 
erledigt und es ist ein Ein-Word-Befehl.
1
.CSEG                       ;was ab hier folgt kommt in den FLASH-Speicher
2
.ORG $0000        ;Programm beginnt an der FLASH-Adresse 0x0000.CSEG 
3
  
4
 ldi    r16,0
5
_LOOP:
6
 dec    r16
7
 brne   _LOOP
8
9
_endlos:
10
 rjmp   _endlos

This instruction branches relatively to PC in either
direction (PC - 63 ≤ destination ≤ PC + 64). Parameter k is the offset 
from PC and is represented in two’s
complement form. (Equivalent to instruction BRBC 1,k.)
Operation:
(i) If Rd ≠ Rr (Z = 0) then PC ← PC + k + 1, else PC ← PC + 1

Dachte erst es liegt daran das einmal PC+1 ( 1Takt ) und beim Sprung 
PC+k+1 zu bearbeiten ist. Dann habe ich mir den RJMP-Befehl angesehen 
und da gibt es ja nur in die Rückwärtssprungrichtung +1, aber trotzdem 
braucht er jedesmal 2 Takte.

Relative jump to an address within PC - 2K +1 and PC + 2K (words).

Bernd_Stein

von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Lesenswert?

Desweiteren habe ich noch ein Problem mit der Formelumstellung Seite 94:

d) Stelle die Formel nach G1 um.

Ich habe für t = 3tT + ( G1-1 )*3. Ldi (1T); dec (1*1T); Schleife 
(255-1)*3; brne (1T weil jetzt nicht mehr gesprungen wird ) 
herausbekommen.

G1 = ?
1
.CSEG                       ;was ab hier folgt kommt in den FLASH-Speicher
2
.ORG $0000              ;Programm beginnt an der FLASH-Adresse 0x0000.CSEG 
3
     
4
 ldi    r16,255
5
_LOOP:
6
 dec    r16
7
 brne   _LOOP
8
9
_endlos:
10
 rjmp   _endlos
11
 
12
.EXIT                       ;Ende des Quelltextes

Bernd_Stein

von Rudi (Gast)


Lesenswert?

Bernd S. schrieb:
> Rudi schrieb:
>> Die
>> Datenbyteanzahl sollte wegen der Flash-Wortlänge allerdings geradzahlig
>> sein (notfalls noch eine unnütze Null dran).
>>.
> Sowas macht glücklicherweise der Assembler. Schade das niemand Punkt 2
> erklären konnte.

Ja da hast Du Recht. Passieren tut nix. Es ist aber immer gut schon im 
Programmtext zu sehen was nun wirklich im Speicher Sache ist. Von den 
unschönen Assemblerwarnungen ganz abgesehen.

> Jetzt Frage ich mich jedoch, warum gibt es da Taktmäßig eine Unterscheidung ?

> Dachte erst es liegt daran das einmal PC+1 ( 1Takt ) und beim Sprung
> PC+k+1 zu bearbeiten ist. Dann habe ich mir den RJMP-Befehl angesehen
> und da gibt es ja nur in die Rückwärtssprungrichtung +1, aber trotzdem
> braucht er jedesmal 2 Takte.

Es wird wohl daran liegen daß relative Sprünge generell 2 Takte 
benötigen.
Die reine Condition-Prüfung (mit dem Ergebnis false = kein Sprung) 
dagegen nur einen. Warum das so ist kann dem Programmierer doch egal 
sein- er nimmt es als gegeben hin.

von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Lesenswert?

d) Stelle die Formel nach G1 um.

Ich habe für t = 3tT + ( G1-1 )*3. Ldi (1T); dec (1*1T); Schleife
(255-1)*3; brne (1T weil jetzt nicht mehr gesprungen wird )
herausbekommen.

G1 = ?
1
.CSEG                       ;was ab hier folgt kommt in den FLASH-Speicher
2
.ORG $0000              ;Programm beginnt an der FLASH-Adresse 0x0000.CSEG 
3
     
4
 ldi    r16,255
5
_LOOP:
6
 dec    r16
7
 brne   _LOOP
8
9
_endlos:
10
 rjmp   _endlos
11
 
12
.EXIT                       ;Ende des Quelltextes

t = Verbrauchte Zeit ; tT = Zeitdauer für einen Takt ; G1 = Anfangswert

t = 3tT + ( G1-1 )*3 => Klammer auflösen
t = 3tT + G1*3 - 3tT => Hinsehen und überlegen
t = G1*3             => |:3

*G1=t/3*

Verbrauchte Zeit also 255*3 = 765 Takte. G1=0 ist als 256 anzusehen.
t=G1*3 finde ich ist eine gut zu merkende Grundlage für die 
AVR-Assemblerprogrammierung, wenn es um 8-Bit-Zeitschleifen geht.
Das ist nicht aus meinen Überlegungen entstanden, sondern hierher :

http://www.avr-asm-tutorial.net/avr_de/zeitschleifen/delay8.html

Schade, fetthinterlegen mit ** geht leider nicht.


Bernd_Stein

: Bearbeitet durch User
von Rudi_H (Gast)


Lesenswert?

Deine Klammer ist falsch aufgelöst...nicht -3tT, sondern -3.

von Rudi (Gast)


Lesenswert?

Bernd S. schrieb:
> wenn es um 8-Bit-Zeitschleifen geht

sollte man immer eine timerinterrupt-basierte Lösung bevorzugen. Nicht 
nur, weil diese sich sehr weitläufig justieren lässt und keine 
Taktorgien zu berechnen sind. Vor allem, weil sie nicht unnütz 
Prozessorzeit verbrät und das geschmeidige Fließen des Hauptprogramms 
ohne jede Not ins Stocken bringt.

von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Lesenswert?

Rudi_H schrieb:
> Deine Klammer ist falsch aufgelöst...nicht -3tT, sondern -3.
>
Vielleicht sollte ich dann die Formel lieber so aufstellen :

t = 3tT + ( G1-1tT )*3

Rudi schrieb:
>> Bernd_Stein. schrieb:
>> wenn es um 8-Bit-Zeitschleifen geht
>
> sollte man immer eine timerinterrupt-basierte Lösung bevorzugen.
>
Da gebe ich dir Grundsätzlich schon recht, aber falls die 
Interruptbasierte Zeitbasis mal zu groß sein sollte, kann man sich 
sicherlich auch hiermit mal begnügen. Es kommt halt immer darauf an. Ich 
selbst sehe das ja auch so, das solche Zeitschleifen oder auf Ereignisse 
warten in Assembler Perlen vor die Säue schmeissen ist. Der Vorteil von 
Assembler ist und bleibt nun mal seine einzigartige Schnelligkeit.

Bernd_Stein

: Bearbeitet durch User
von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Lesenswert?

t = Verbrauchte Zeit ; tT = Zeitdauer für einen Takt ; G1 = Anfangswert

t = 3tT + ( G1-1 )*3tT  => Klammer auflösen
t = 3tT + G1*3tT - 3tT  => Hinsehen und überlegen

t = G1*3tT              => |:3tT

G1 = t/3tT

Habe die Formel nochmal durchdacht. Es ist sinnvoller sie so, für eine 
8-Bit Zeitschleife aufzustellen, da die Schleife ja einmal weniger 
durchlaufen wird ( -1*3tT ) als mit G1 angegeben wird.

Also in Worten :

Die verbrauchte Zeit entspricht dem Anfangswert der Zählschleife ( G1 ) 
mal drei für die Zeit, die ein Takt ( tT ) benötigt. Ganz schön 
verwirrend nicht wahr.

Als Beispiel :
Der µC läuft mit einem Megahertz, dann braucht ein Takt eine 
Mikrosekunde.
Wenn der Anfangswert des Schleifenzähler 255 beträgt, dauert das 
ausführen dieser Sequenz 255*3*1µs, also 765µs. Es können also nur 
Vielfache von drei Mikrosekunden als Zeiten entstehen.
1
.CSEG                   ;was ab hier folgt kommt in den FLASH-Speicher
2
.ORG $0000              ;Programm beginnt an der FLASH-Adresse 0x0000.CSEG 
3
     
4
 ldi    r16,255
5
_LOOP:
6
 dec    r16
7
 brne   _LOOP


Bernd_Stein

: Bearbeitet durch User
von Rudi (Gast)


Lesenswert?

Bernd S. schrieb:
> Da gebe ich dir Grundsätzlich schon recht, aber falls die
> Interruptbasierte Zeitbasis mal zu groß sein sollte, kann man sich
> sicherlich auch hiermit mal begnügen. Es kommt halt immer darauf an. Ich
> selbst sehe das ja auch so, das solche Zeitschleifen oder auf Ereignisse
> warten in Assembler Perlen vor die Säue schmeissen ist.

Betreibst Du das MC-Handwerk eigentlich als Hobby der grauen Theorie 
oder schreibst Du auch tatsächlich (für Dich) nützliche und größere 
ASM-Programme? Man gewinnt nämlich den Eindruck, es würde an Erfahrung 
mit letzterem mangeln bzw. an den richtigen Prioritäten.

von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Lesenswert?

Bernd S. schrieb:
> Bei der Aufgabe A500:
> b) Wieso werden beim Sprungbefehl BRNE wohl zwei Werte angegeben?
>
> Weil gesprungen wird und einmal nicht. Jetzt Frage ich mich jedoch,
> warum gibt es da Taktmäßig eine Unterscheidung ? Befehlsholphase und
> Befehlsausführungsphase werden doch zu diesem Zeitpunkt in einem Takt
> erledigt und es ist ein Ein-Word-Befehl.
>
Ich erkläre mir dies so:" In einem Systemtakt holt sich die CPU zwei 
Register in die ALU und führt mit ihnen entsprechend dem Befehl eine 
ALU-Operation aus ".
Beim bedingtem Sprung ( BRNE ) wird also der PC ( 2 Byte -> entsprechend 
2 Registern ) geholt und 1 hinzuaddiert ( 1 Takt ), wenn der Sprung 
nicht erfolgt.

Wenn der Sprung erfolgt, kommt obiges Szenario zustande und nun wird zu 
dem jetzt gebildeten PC noch k ( +-64 ) addiert, was ein weiteren Takt 
braucht.

Der unbedingte Sprung ( RJMP ) braucht immer 2 Takte, da er im ersten 
Takt den PC-LOW und k-LOW holt und diese addiert, dann im zweiten Takt 
PC-High und k-High holt und diese addiert. ( k = +- 2048 )

So nun zu meinen Lösungen zur Aufgabe A501.

a) 1Mhz = 765µs ; 16Mhz = 47,8125µs
b) 1/10 Sekunde = 100ms, also Nein
c) 256 durchläufe -> 768µs bzw. 48µs

Bernd_Stein

von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Lesenswert?

Hier meine Lösungen zu A502:

a)
1
.CSEG                 ;was ab hier folgt kommt in den FLASH-Speicher
2
.ORG $0000            ;Programm beginnt an der FLASH-Adresse 0x0000.CSEG 
3
  
4
 ldi  xl,LOW (65535)
5
 ldi  xh,HIGH(65535)
6
_LOOP:
7
 sbiw xh:xl,1
8
 brne _LOOP
9
10
_endlos:
11
 rjmp   _endlos

b) t = 1T + 4T * G1 ( T = Takt, G1 = X-Zeiger )

c) Ja, 1 Takt wenn mit 4T * G1 gerechnet wird.

d) Ja, wenn es auf den einen Takt nicht ankommt

e) G1 = t / 4T

f) 1Mhz = 262.140 bzw. 262.14µs ; 16Mhz = 16.383,7500 bzw. 16.383,8125µs

g) 100ms -> G1 = 100.000µS / 4µS --> 25.000 f. 1Mhz ; 16Mhz = 400.000, 
also
   nicht in 16Bit machbar.


Bernd_Stein

von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Angehängte Dateien:

Lesenswert?

Bin jetzt bei A507b angekommen und habe ein Problem die 
Schalterabfrage im Sekundenrythmus einzufügen.

Nach dem Umschalten bleibt die vorherige Frequenz bestehen.

b)Für Fleißige:
Bei hohen Frequenzen kann die zur Schalterabfrage benötigte Zeit die 
Genauigkeit der Frequenz beeinflussen. Ändere das Programm so um, dass 
die   Schalter nur ein mal pro Sekunde abgefragt werden. Speicheredas 
Programm als "A507_frequency_generator_3.asm".

Der PAP für 1Hz ist stellvertretend für die anderen Frequenzen, da sich 
nur die Werte für den Faktor der Grundzeit ändert.


Bernd_Stein

von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Angehängte Dateien:

Lesenswert?

Hier der PAP ( Programmablaufplan ) als png, da ja "noch" nicht jeder 
Programmierer dieses einfach zu bedienende Programm hat ;-)

https://www.heise.de/download/product/papdesigner-51889


Bernd_Stein

: Bearbeitet durch User
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.