Forum: Mikrocontroller und Digitale Elektronik 16-Bit-Schleifen mit 8051


von H-G S. (haenschen)


Lesenswert?

Hallo zusammen!

Ich benötige zum Auslesen eines Speicherbereichs eine 16-Bit-Schleife, 
die aus den Registern R0 (Highbyte) und R1 (Lowbyte) bestehen soll.

Der Befehl "DJNZ Loop" scheint gewisse Probleme mit dem Überlauf zu 
haben, wenn der Startwert "00h" ist. Da wird dann 00h zu FFh und die 
Schleife wird 255 mal durchlaufen obwohl eigentlich Null mal durchlaufen 
werden müsste.

Und wie sieht es eigentlich aus mit den Werten 255/256 ?
Wenn der 16-Bit-Zähler auf 0100h steht, müsste der da die Schleife 256 
mal durchlaufen oder 255 mal ? Wie würde ich die 256 erreichen wenn das 
Lowbyte der Schleife nur maximal 255 mal durchlaufen wird ?

Weiss da jemand etwas dazu ?

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

H-G S. schrieb:
> Wenn der 16-Bit-Zähler auf 0100h steht, müsste der da die Schleife 256
> mal durchlaufen oder 255 mal ?
Reine Definitionssache.
Gegenfrage dazu: wie oft müsste dein Zähler die Schleife durchlaufen, 
wenn da z.B. der Wert 0004h drin steht?

Mit der Antwort auf diese Frage lässt sich dann leicht sagen, wie oft er 
die Schleife durchlaufen muss, wenn 256 = 0100h drin steht...

> Weiss da jemand etwas dazu ?
Bitte nicht Plenken!

: Bearbeitet durch Moderator
von Ralf G. (ralg)


Lesenswert?

H-G S. schrieb:
> Der Befehl "DJNZ Loop" scheint gewisse Probleme mit dem Überlauf zu
> haben, wenn der Startwert "00h" ist.

Das ist kein Problem, das ist genauso (wie der Name schon sagt) 
dokumentiert!
http://www.self8051.de/djnz_____%3Cbyte%3E.%3Crel%3E,13474.html?PHPSESSID=b781dc8aa3e09a9ce83ee08e47b3a1a4

von Peter D. (peda)


Lesenswert?

Es gibt nen Haufen Lösungen, z.B.:
1
weiter:
2
        nop             ; Code
3
anfang:                 ; hier Einsprung in die Funktion
4
        dec     r0
5
        cjne    r0, #0ffh, weiter
6
        dec     r1
7
        cjne    r1, #0ffh, weiter
8
        ret
Man könnte aber auch DPTR auf die Endadresse testen.

von Georg (Gast)


Lesenswert?

Ralf G. schrieb:
> Das ist kein Problem, das ist genauso (wie der Name schon sagt)
> dokumentiert!

Und noch dazu ist es logisch:
Decrement: 00h -> ffh
Jump not zero: ja, ist ja nicht 0

Das Missverständnis liegt nicht daran, dass der Prozessor nicht logisch 
denken kann, sondern der Programmierer.

H-G S. schrieb:
> obwohl eigentlich Null mal durchlaufen
> werden müsste.

Noch ein kapitaler Denkfehler: 1mal wird die Schleife IN JEDEM FALL 
ausgeführt, DJNZ kommt ja erst am Schluss.

Es gibt noch sooo viel zu lernen...

Georg

von H-G S. (haenschen)


Lesenswert?

Ich werde die alte Holzhammer-Methode verwenden:    :-)

- Den 16-Bit-Zähler laden mit Wertebereich 0001h-XXXXh.
- Am Schleifenende eine volle 16-Bit-Subtraktion (minus 0001h) 
durchführen.
- Wenn beide Bytes des Zählers 0 sind dann Schleife verlassen.




Das Beispielprogramm von Peter Danegger zählt scheinbar durch Null, das 
ist auch nicht so günstig scheint mir - da kommt wieder das 
255/256-Problem hoch. Doch die Vergleichs-Befehle scheinen nützlich - 
die habe ich komplett vergessen!


Und hört auf mich als dumm hinzustellen  :-)
Ich weiss genau was der DJNZ-Befehl macht und dass die Schleife 
mindestens einmal durchlaufen wird.

von Peter D. (peda)


Lesenswert?

H-G S. schrieb:
> da kommt wieder das
> 255/256-Problem hoch.

Nö.

von Georg (Gast)


Lesenswert?

H-G S. schrieb:
> Ich weiss genau was der DJNZ-Befehl macht und dass die Schleife
> mindestens einmal durchlaufen wird.

H-G S. schrieb:
> obwohl eigentlich Null mal durchlaufen
> werden müsste.

Ganz offensichtlich weisst du das nicht. Als Anfänger dumm sein ist 
durchaus möglich, dumm bleiben wollen und jeden anmeckern, der auf 
Fehler aufmerksam macht, ist aber keine gute Idee.

Georg

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Lese doch bitte endlich mal das original Intel Handbuch zur MCS51 
Familie:
http://datasheets.chipdb.org/Intel/MCS51/MANUALS/27238302.PDF

Dort wird jeder Befehl haarklein beschrieben (bis auf 0A5h :-P). Dann 
wüsstest du auch, das bei DJNZ erst dekrementiert und dann auf Null 
geprüft wird - wie der Befehl ja auch aussagt.

: Bearbeitet durch User
von A. S. (Gast)


Lesenswert?

Sinnvoll ist der Befehl (djnz) ja auch am ENDE eines Stücks Code. Wo 
zum Anfang gesprungen wird, wenn noch nicht auf 0 dekrementiert ist.

Wenn er einmal durchlaufen werden soll, stellt man zu Anfang 1 ein.
Wenn er zweimal durchlaufen werden soll, 2.


Bei DJNZ ist die Variable auch kaum als Indexzähler geeignet, so dass 
ein Decrement am Anfang blöd wäre.

Und wenn man mehr als 255 Bytes kopieren will, man also ggf. eine äußere 
Schleife braucht, ist es auch schöner, wenn man mit Startwert 0 dann 
auch 256 Durchläufe am Stück hinkriegt. Bei 0 Durchläufen wird der ganze 
kladeradatsch garnicht aufgerufen.

von Eberhard H. (sepic) Benutzerseite


Lesenswert?

H-G S. schrieb:
> Hallo zusammen!
>
> Ich benötige zum Auslesen eines Speicherbereichs eine 16-Bit-Schleife,
> die aus den Registern R0 (Highbyte) und R1 (Lowbyte) bestehen soll.
>
> Der Befehl "DJNZ Loop" scheint gewisse Probleme mit dem Überlauf zu
> haben, wenn der Startwert "00h" ist. Da wird dann 00h zu FFh und die
> Schleife wird 255 mal durchlaufen obwohl eigentlich Null mal durchlaufen
> werden müsste.
>
> Und wie sieht es eigentlich aus mit den Werten 255/256 ?
> Wenn der 16-Bit-Zähler auf 0100h steht, müsste der da die Schleife 256
> mal durchlaufen oder 255 mal ? Wie würde ich die 256 erreichen wenn das
> Lowbyte der Schleife nur maximal 255 mal durchlaufen wird ?
>
> Weiss da jemand etwas dazu ?

Lass dich nicht drausbringen.

Wenn ein Register R1 mit dem Wert s1 = 1...255 oder 0 initialisiert ist, 
kannst du beim 8051 mit DJNZ zunächst eine Schleife genau 1...256 mal 
durchlaufen. Das gilt auch für andere 8-bit-µC mit DJNZ.

Also:
1
    MOV  R1,#s1
2
loop1:
3
    Befehlsfolge
4
    DJNZ R1,loop1
Der Wert Null ist bei DJNZ also äqivalent mit 256 Schleifendurchgängen.
Bei anderen µC nimmt man statt DJNZ einfach zwei Befehle, nämlich DEC 
und BRNE (je nach µC).

Falls man mehr als 256 Schleifendurchgänge für eine Befehlsfolge 
benötigt, nimmt man entweder zwei verschachtelte Schleifen, also:
1
    MOV  R0,#s2
2
loop2:
3
    MOV  R1,#s1
4
loop1:
5
    Befehlsfolge
6
    DJNZ R1,loop1
7
    DJNZ R0,loop2
Die Zahl der Schleifendurchgänge ist dann s1 * s2, reicht also von 1 bis 
2^16.

Oder falls sich die Zahl der Schleifendurchgänge nicht als Produkt von 
zwei 8-Bit-Werten (jeweils 1...256) darstellen lässt, kommt man an einer 
16-Bit-Arithmetik für den Schleifenzähler nicht vorbei, die aber kaum 
aufwendiger ist.

von Route_66 H. (route_66)


Lesenswert?

Eberhard H. schrieb:
> Oder falls sich die Zahl der Schleifendurchgänge nicht als Produkt von
> zwei 8-Bit-Werten (jeweils 1...256) darstellen lässt, kommt man an einer
> 16-Bit-Arithmetik für den Schleifenzähler nicht vorbei,

Das stimmt nicht ganz!
Wenn man dein Beispiel etwas umstellt:

Eberhard H. schrieb:
>    MOV  R0,#s2
>    MOV  R1,#s1
> loop1:
>     Befehlsfolge
>     DJNZ R1,loop1
>     DJNZ R0,loop1

Jetzt wird zuerst die "Befehlsfolge" s1-mal durchlaufen und danach (s2 
mal 256).
So kann man jeden Wert von 1 bis 65536 erreichen.
Du hast aber Recht, es läuft auf eine einfache 16-Bit Arithmetik hinaus.

: Bearbeitet durch User
von Eberhard H. (sepic) Benutzerseite


Lesenswert?

Route 6. schrieb:
> Eberhard H. schrieb:
>> Oder falls sich die Zahl der Schleifendurchgänge nicht als Produkt von
>> zwei 8-Bit-Werten (jeweils 1...256) darstellen lässt, kommt man an einer
>> 16-Bit-Arithmetik für den Schleifenzähler nicht vorbei,
>
> Das stimmt nicht ganz!
> Wenn man dein Beispiel etwas umstellt:
>
> Eberhard H. schrieb:
>>    MOV  R0,#s2
>>    MOV  R1,#s1
>> loop1:
>>     Befehlsfolge
>>     DJNZ R1,loop1
>>     DJNZ R0,loop1
>
> Jetzt wird zuerst die "Befehlsfolge" s1-mal durchlaufen und danach (s2
> mal 256).
> So kann man jeden Wert von 1 bis 65536 erreichen.

Sehr wohl stimmt mein Programm, und zwar in Unterschied zu deiner 
Korrektur ganz allgemein für alle s1, s2 = 1 bis 255 und Null:
1
    MOV  R0,#s2
2
loop2:
3
    MOV  R1,#s1
4
loop1:
5
    Befehlsfolge
6
    DJNZ R1,loop1
7
    DJNZ R0,loop2

Dein Sonderfall gilt nur für s1 = 0, denn er verwendet die Tatsache, 
dass R1 nach DJNZ R0,loop1 bereits Null ist.

Dieser Sonderfall s1 = 0 ist bei meiner allgemeinen Version wegen MOV 
R1,#s1 enthalten und somit lassen sich mit s1 = s2 = 0 auch 2^16 = 65536 
Durchläufe erreichen.

Falls s1 <> 0, wird bei dir die innere Schleife mit DJNZ R1,loop1 nur 
einmal richtig durchlaufen (nämlich s1 mal, danach immer 256 mal), da R1 
nicht neu geladen wird.

Die Gesamtzahl der Durchläufe ist bei deiner Version also (s2-1)*256 + 
s1, was vielleicht für andere Schleifenzahlen praktisch ist, die nicht 
per s1*s2 dargestellt werden können.

Beitrag #5133496 wurde vom Autor gelöscht.
von H-G S. (haenschen)


Angehängte Dateien:

Lesenswert?

Die aktuelle Schleifenversion ist auf dem angehängten Scan - den 
Kontrast konnte ich nicht verbessern ...


Es wäre natürlich gut, wenn Eberhard H.´s  Schleife funktionieren würde.
Das würde ein paar Programmzeilen sparen.


Edit: aber vielleicht gibt es Probleme mit dem kurzen/relativen 
Rücksprung, da in der Schleife recht viele Befehle stehen werden.

: Bearbeitet durch User
von Eberhard H. (sepic) Benutzerseite


Lesenswert?

H-G S. schrieb:
> Die aktuelle Schleifenversion ist auf dem angehängten Scan - den
> Kontrast konnte ich nicht verbessern ...

Du musst nach der Befehlsfolge erst 1 subtrahieren und dann 
abfragen/springen (sinngemäß wie bei DJNZ) und nicht umgekehrt, denn 
sonst stimmt die Zahl der Durchläufe nicht wie erwartet.

>
> Es wäre natürlich gut, wenn Eberhard H.´s  Schleife funktionieren würde.

Sie funktioniert wie beschrieben.

>
> Edit: aber vielleicht gibt es Probleme mit dem kurzen/relativen
> Rücksprung, da in der Schleife recht viele Befehle stehen werden.

Das ist dann ein 8051-Problem.

von Peter D. (peda)


Lesenswert?

H-G S. schrieb:
> Die aktuelle Schleifenversion ist auf dem angehängten Scan

Warum so umständlich?
Ist Dir diese Lösung zu einfach:
Beitrag "Re: 16-Bit-Schleifen mit 8051"

von Georg (Gast)


Lesenswert?

H-G S. schrieb:
> aber vielleicht gibt es Probleme mit dem kurzen/relativen
> Rücksprung, da in der Schleife recht viele Befehle stehen werden.

1. Es gibt auch lange Sprünge
2. Es gibt Unterprogramme

Vielleicht wäre es doch ganz gut, erst mal die einfachsten Grundlagen zu 
lernen.

Georg

von H-G S. (haenschen)


Lesenswert?

Eberhard H. schrieb:
> Du musst nach der Befehlsfolge erst 1 subtrahieren und dann
> abfragen/springen (sinngemäß wie bei DJNZ) und nicht umgekehrt, denn
> sonst stimmt die Zahl der Durchläufe nicht wie erwartet.

Stimmt!
Erst kommt die Subtraktion von 0001h, dann die Abfrage ob der Zähler auf 
0000h steht.
Danke dir!

Edit: Der Wertebereich der Schleife geht ja von 0001h bis xxxxh, also 
größer 0000h als Starwert. Daher kann sofort 0001h subtrahiert werden 
ohne einen Überlauf zu provozieren.

Peter D. schrieb:
> Warum so umständlich?
> Ist Dir diese Lösung zu einfach:
> Beitrag "Re: 16-Bit-Schleifen mit 8051"

Die ist mit im Moment zu relativ wegen der kurzen Sprungdistanz ... mal 
schauen wieviele Byte in der Schleifenmitte stehen dann kann ich sie mir 
nochmal anschauen.

: Bearbeitet durch User
von Eberhard H. (sepic) Benutzerseite


Lesenswert?

Peter D. schrieb:
> H-G S. schrieb:
>> Die aktuelle Schleifenversion ist auf dem angehängten Scan
>
> Warum so umständlich?
> Ist Dir diese Lösung zu einfach:
> Beitrag "Re: 16-Bit-Schleifen mit 8051"

Ich bin zwar kein 8051-Programmierer, aber wie willst du damit 2^16 
Durchläufe von "Code" erreichen?
1
weiter:
2
        nop             ; Code
3
anfang:                 ; hier Einsprung in die Funktion
4
        dec     r0
5
        cjne    r0, #0ffh, weiter
6
        dec     r1
7
        cjne    r1, #0ffh, weiter
8
        ret

2^16 Durchläufe klappen damit nur, wenn man in "weiter" statt in 
"anfang" einspringt und r0, r1 vorher mit 0ffh lädt. Ansonsten hat man 
wenigstens einen Durchlauf vergeigt.

Oder man vergleicht mit 0 statt mit 0ffh und löscht r0 und r1 vor dem 
Einsprung in "weiter".

Das wäre dann die "lange" Version von Route 66, die man mit zwei 
verschiedenen Rücksprungadressen und erneutem Laden von r0 wiederum 
verallgemeinern kann.

Der geringste Aufwand sind vermutlich zwei verschachtelte DJNZ-Schleifen 
mit den Anfangswerten Null und bei Bedarf eine zu lange Befehlsfolge 
("Code") in ein Unterprogramm zu verlagern.

Edit: Sehe gerade, dass CJNE auch nur einen relativen Sprung mit 8 Bit 
schafft, also ist es keine "lange" Version und benötigt sogar noch 2 
Bytes mehr (2x DEC) als mit DJNZ.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Eberhard H. schrieb:
> Ich bin zwar kein 8051-Programmierer, aber wie willst du damit 2^16
> Durchläufe von "Code" erreichen?

Wo steht, daß er das will?
Entweder 0..65535 oder 1..65536, einen Tod muß man sterben.
Ich hatte 0..65535 angenommen, ein Umstellen ist aber leicht möglich.
Oder man nimmt 3 Bytes, dann geht auch 0..65536.

von H-G S. (haenschen)


Lesenswert?

Streitet euch nicht  :-)

Ich muss eh mit Registern als Zähler arbeiten - die CJNE-Befehle 
unterstützen aber nur direkte Wert-Angaben im Opcode (soweit ich sah).

von Peter D. (peda)


Lesenswert?

H-G S. schrieb:
> Ich muss eh mit Registern als Zähler arbeiten - die CJNE-Befehle
> unterstützen aber nur direkte Wert-Angaben im Opcode (soweit ich sah).

Hä?
R0, R1 sind doch Register. Der Abbruchtest ist immer gleich, auch bei 
Deinem Code.
Laß einfach mal den Code im Simulator laufen mit verschiedenen 
Startwerten für R0, R1.

von H-G S. (haenschen)


Lesenswert?

Peter D. schrieb:
> Hä?
> R0, R1 sind doch Register. Der Abbruchtest ist immer gleich, auch bei
> Deinem Code.

Ups ... ich dachte die ffh seien Platzhalter für den Start-/Zählwert.
Doch es sind Überlauf-Testwerte. Der Zählwert steht ja in R0/R1.

Sieht sehr kompakt aus die Methode - sollte man sich merken und mal 
durchrechnen.

Die 256 als Wert scheint ja wichtig weil die 1. Bitstelle im High-Wort 
den Wert 256 hat und somit muss wohl die innere Schleife wirklich 256 
mal durchlaufen 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.