Forum: Mikrocontroller und Digitale Elektronik Atmega8 Assembler Timerproblem


von Matze (Gast)


Angehängte Dateien:

Lesenswert?

Hallo,

ich versuche im Moment einen Timer zu realisieren. Bin noch ziemlich neu 
auf dem Gebiet und hab moch noch nicht an den internen Timer des 
Controllers herangetraut. Wollte einfach mal mit Taktschritten die 
abgezählt werden einen Timer zusammenbasteln, der von 0-9999ms zählt.

Ich habe den Controller schon so weit, dass ich in einem Speicher (r21) 
die 1er Schritte bis 99 und in einem anderen (r23) die 100er Schritte 
bis 9900 habe.

Nun habe ich versucht über die Taktzeiten einen Timer aufzubauen, der 
entsprechend den Speicherwerten 0-9999ms Herunterzählt bis die Blitz-LED 
leuchtet. Den Abschnitt habe ich mal in den Anhang gepackt.

Ansich macht er schonmal das richtige. Ich habe mal einen Zähler 
angeschlossen und folgende Probleme festgestellt: Zum einen nimmt die 
Zeit immer mehr ab, je höher sie wird. Also bei 10ms kommen noch 10ms 
raus, aber bei 50 sinds nur noch 49, bei 150 dann 145 und bei 300 
nurnoch 260. Ich glaube ich hab da irgendeine verschachtelung noch 
vergessen.

Zum anderen und das ist das größte problem: wenn ich den vorgang 
mehrmals wiederhole zählt er ab und an einfach mal genau 100ms drauf. 
also statt 50 dann 150ms. Ich kann mir das überhaupt nicht erklären da 
ich auch kein Muster erkennen kann. Was kann ich hier übersehen haben?


Vielen Dank für die Mühe schonmal

Mit freundlichen Grüßen

Matze

von 3348 (Gast)


Lesenswert?

Zum einen enthaelt das AVRStudio einen Simulator, zum Anderen weshalb 
zaehlst du nicht mit einer 16bit variablen ?

von Matze (Gast)


Lesenswert?

hmm ich dachte ich habe bei einem 8bit controller auch nur 8bit 
speicherplätze. finde auch im datenblatt keine 16bit plätze.

von Jochen M. (taschenbuch)


Lesenswert?

Matze,

Du MUST die Datenblätter einfach SORGFÄLTIGER lesen, sonst macht die 
Beschäftigung mit einem MikroController überhaupt keinen Sinn!. Keinen.

Die Registerpaare X,Y,Z (xh:xl, yh:yl, zh:zl) sind 16Bit Register die 
für Pointer und 16-Bit Operationen z.B. ADIW ZH:ZL,2 verwendet werden 
können.

Aber das steht MEHR ALS DEUTLICH im Datenblatt bzw. im Instruction-Set.

Jochen Müller

von Philipp B. (philipp_burch)


Lesenswert?

Jochen Müller wrote:
> Matze,
>
> Du MUST die Datenblätter einfach SORGFÄLTIGER lesen, sonst macht die
> Beschäftigung mit einem MikroController überhaupt keinen Sinn!. Keinen.
>
> Die Registerpaare X,Y,Z (xh:xl, yh:yl, zh:zl) sind 16Bit Register die
> für Pointer und 16-Bit Operationen z.B. ADIW ZH:ZL,2 verwendet werden
> können.

Es sind keine 16-Bit-Register. Zusammen mit r25:r24 sind das die acht 
höchstwertigsten Register, auf die sich die adiw- und sbiw-Befehle 
anwenden lassen. Die sechst obersten Register haben zusätzlich die 
Besonderheit, dass sie bei einigen Befehlen als Pointer zur indirekten 
Adressierung verwendet werden können.
16-Bit-Operationen kannst du aber auch mit beliebigen anderen Registern 
machen, nur brauchst du dann zwei einzelne Befehle:
1
;r17:r16
2
clr r2
3
ldi r18, 5
4
5
add r16, r18
6
adc r17, r2
Damit wird das Registerpaar r17:r16 um 5 erhöht.

> Aber das steht MEHR ALS DEUTLICH im Datenblatt bzw. im Instruction-Set.

Korrekt.

von Matze (Gast)


Lesenswert?

wie gesagt bin da erst noch am anfang und versteh es mag sein dass ich 
das im datenblatt gelesen hab aber das heißt nicht dass ich es auch 
verstanden habe.

kann ich dann einfach meine beiden register mit den 1er und 100er 
stellen auf dieses kombinierte 16bit register überspielen und dieses 
dann über dec immer einen runterzählen lassen?

von Karl H. (kbuchegg)


Lesenswert?

Matze wrote:
> wie gesagt bin da erst noch am anfang und versteh es mag sein dass ich
> das im datenblatt gelesen hab aber das heißt nicht dass ich es auch
> verstanden habe.
>
> kann ich dann einfach meine beiden register mit den 1er und 100er
> stellen auf dieses kombinierte 16bit register überspielen und dieses
> dann über dec immer einen runterzählen lassen?

Nein.
dec ist eine 8 Bit Operation.
Du musst die sub, bzw. sbc Instruktionen benutzen. Auch wenn
der AVR ein 8 Bit Porzessor ist, bedeutet das nicht, dass man
keine 16 Bit Operationen machen kann. Im Grunde hast du das Prinzip
ja schon durchschaut, du teilst deine Zahl auf auf 2 Register.
Nichts anderes hast du mit deinen jetzigen Einern/Zehnern und
Hunderter gemacht. Nur ist so eine Aufteilung zwar für Anzeigezwecke
wunderbar, aber zum Rechnen hast du einiges an Mehraufwand.
Daher mcht man die Aufteilung anders: Ein Register enthält
alle 256-er (in Analogie zu den Einern/Hunderter) und das
andere alle Vielfachen von 256. Zusammen ergeben die beiden
Register dann
eine 16 Bit Zahl.
Und natürlich gibt es dann auch spezielle Befehle, mit denen man
über mehrere Register verteilte Zahlen bearbeiten kann. Speziell
für Addition und Subtraktion. Eine besondere Rolle spielt dabei
das Carry Bit im Statusregister, über welches der Übertrag von
einem Register in das nächste abgehandelt wird. Auch das hast du
im Grunde schon gemacht, immer dann wenn dein Einer/Zehner bei
99 angelangt ist, musstest du spezielle Aktionen machen, damit ein
Übertrag in die Hunderter stattfindet.
Machst du die Aufteilung der 16 Bit Zahl in 2 Register aber nach
dem HighByte/LowByte Prinzip (also: die Trennung bei 256), dann
kann dir die Hardware dabei unter die Arme greifen und diesen
Übertrag in Form des Carry Bits einfach zur Verfügung stellen bzw.
mit eigenen Befehlen kann dieser Übertrag einfach in der nächst
höheren Stelle berücksichtigt werden.

Von einer 16 Bit Zahl (die in 2 Registern, zb r16 und r17) gespeichert
ist, kann dann zb so aussehen:
1
    subi  r16, 1     ; von r16 1 abziehen. Wenn dabei ein Unterlauf
2
                     ; entsteht, wird das Carry auf 1 gesetzt, ansonsten
3
                     ; auf 0
4
    sbci  r17, 0     ; von r17 0 und das Carry Bit abziehen. 0 abziehen
5
                     ; mag etwas seltsam anmuten. Aber der Sinn des
6
                     ; Befehls besteht eigentlich darin, dass genau dann
7
                     ; 1 abgezogen wird, wenn das Carry Bit von der
8
                     ; vorhergehenden Subtraktion auf 1 gesetzt wurde.
9
                     ; es also in der kleineren Stelle einen Unterlauf
10
                     ; gab.

von Matze (Gast)


Lesenswert?

Vielen Dank, das hat mich schonmal einen Schritt weiter gebracht. Jetzt 
scheiter ich aber schon daran das Gegenstück zu den Befehlen subi und 
sbci zu finden. was bedeutet das immediate in den befehlen. denn der add 
und adc befehl machen ja das gleiche, nur dass sie das immediate nicht 
mit drin haben und einen register hinzuzählen und keine konstante.

Ich würde jetzt vom Prizip her so aufbauen: Erst zähle ich das Register 
das die 1er enthält in das 16bit Aegisterpaar. Dann zähle ich so oft die 
Konstante 100 hinzu bis das 100er Register auf Null ist. D.h. im Prinzip 
reicht mir dafür dir operationen die dein Vorredner Philipp Burch 
gemacht hat. nur kann ich da statt der 5 auch einfach 260 eingeben? oder 
bekommt der dann ein Problem. Außerdem verstehe ich da die Rolle des 
Registers r2 nicht ganz. Was machen die Befehle genau?

Ich finde ind em instruction manual immer nur ein einzelnes Beispiel zu 
den Befehlen. Wirklich verstehen tue ich dadurch nicht was da passiert.


Vielen Dank mal wieder für eure Mühe.

Gruß

Matze

von Matze (Gast)


Lesenswert?

Ich bin wieder einen Schritt weiter. Habe noch ein wenig auf der Seite 
hier herumgesucht - Respekt kann einem wirklich weiterhelfen in einigen 
Punkten. Auf jeden Fall habe ich da zummindest die das Gegenstück 
gefunden bzw. gesehen dass es das soi nicht gibt. Jetzt noch eine 
Verständnisfrage.

Wenn ich die zweite zahl mit adc hochzähle, und quasi immer 0 hinzuzähle 
(war das auch der sinn des registers r2?) dann wird da immer einer 
hinzugezählt wenn in der operation vorher das register übergelaufen ist. 
Wird dann der rest entsprechend in dem ersten register gespeichert?

verstehe ich das richtig: meine zahl teilt sich dann so auf dass ich 
erst schaue wie gros das zweite register ist und den wert dann mit 256 
multiplizieren muss und dazu dann den inhalt des ersten registers zähle?

von Philipp B. (philipp_burch)


Lesenswert?

Matze wrote:
> Ich bin wieder einen Schritt weiter. Habe noch ein wenig auf der Seite
> hier herumgesucht - Respekt kann einem wirklich weiterhelfen in einigen
> Punkten. Auf jeden Fall habe ich da zummindest die das Gegenstück
> gefunden bzw. gesehen dass es das soi nicht gibt. Jetzt noch eine
> Verständnisfrage.

Ein Gegenstück brauchst du eigentlich nicht, da Addition und Subtraktion 
im Grunde die gleiche Operation ist. Anstatt "addi r16, 5" (Was es nicht 
gibt), kannst du einfach schreiben
1
subi r16, -5
, du subtrahierst also -5.

> Wenn ich die zweite zahl mit adc hochzähle, und quasi immer 0 hinzuzähle
> (war das auch der sinn des registers r2?) dann wird da immer einer

Ja.

> hinzugezählt wenn in der operation vorher das register übergelaufen ist.
> Wird dann der rest entsprechend in dem ersten register gespeichert?

Hm, was meinst du mit "Rest"? Beim ersten Aufruf von "add" wird ja nur 
das Low-Byte erhöht, was einen Überlauf verursachen kann. Im Falle eines 
Überlaufs ist das Carry-Bit anschliessend 1 und wird beim folgenden 
Aufruf von "adc" einfach zum Operanden addiert. Da der Summand r2 dort 
aber ohnehin 0 enthält, wird einfach noch 0 oder 1 (Je nach Carry-Bit) 
dazugerechnet. Der Aufruf von "adc" verändert das Low-Byte aber nicht 
mehr.

> verstehe ich das richtig: meine zahl teilt sich dann so auf dass ich
> erst schaue wie gros das zweite register ist und den wert dann mit 256
> multiplizieren muss und dazu dann den inhalt des ersten registers zähle?

Ohne Gewähr:
1
;Zahl zum Aufteilen befindet sich in r17:r16, die 10'000er liegen
2
;anschliessend in r18, die Tausender in r19 usw.
3
;r2 muss 0 enthalten.
4
;Verändert werden zusätzlich noch r23 und r24.
5
Split16:
6
  clr r18
7
  clr r19
8
  clr r20
9
  clr r21
10
  clr r22
11
  ldi r23, LOW(10000)
12
  ldi r24, HIGH(10000)
13
  Split16_loop1:
14
    inc r18
15
    sub r16, r23
16
    sbc r17, r24
17
  brcc Split16_loop1
18
  dec r18
19
  add r16, r23
20
  adc r17, r24
21
  ldi r23, LOW(1000)
22
  ldi r24, HIGH(1000)
23
  Split16_loop2:
24
    inc r19
25
    sub r16, r23
26
    sbc r17, r24
27
  brcc Split16_loop2
28
  dec r19
29
  add r16, r23
30
  adc r17, r24
31
  ldi r23, 100
32
  Split16_loop3:
33
    inc r20
34
    sub r16, r23
35
    sbc r17, r2   ;r2 = 0
36
  brcc Split16_loop3
37
  dec r20
38
  add r16, r23
39
  adc r17, r2
40
  ldi r23, 10
41
  Split16_loop4:
42
    inc r21
43
    sub r16, r23
44
  brcc Split16_loop4  ;Die Zahl ist nun auf jeden Fall kleiner als 100
45
  dec r21
46
  add r16, r23
47
  mov r22, r16        ;Die Einer befinden sich jetzt bereits in r16 (Rest)
48
ret
Ist ungetestet, sollte aber irgendwie in der Art funktionieren. r2 
liesse sich unter Verwendung von "sbci" wohl auch noch einsparen, ein 
globales Nullregister zu haben hat sich aber in meinen Augen bewährt.

von Matze (Gast)


Lesenswert?

Habe das jetzt mal versucht soweit. Kann das son funktionieren? 
Ausgangspunkt sind die Register r21 und r23 mit den 1ern und 100ern. Am 
ende soll die dadurch gespeicherte Zahl in ein 16bit Register r21 Low 
und r20 High gespeichert werden.
1
             ldi    r20, 0        ;r20 als High Register leer machen
2
             ldi    r29, 100      ;100 laden zum draufzählen
3
             ldi    r30, 0        ;0 ladem zum draufzählen
4
verteilung:  cpi    r23, 0        ;prüfen ob 100er vorhanden sind
5
             breq  ende_Tneu      ;abbrechen wenn keine 100er da sind (r21 ist Low Register und bereits gefüllt)
6
             add    r21, r29      ;zum Low Register r21 100 hinzuzählen
7
             adc    r20, r30      ;zum High-Register r20 0 plus dem Carrybite hinzuzählen
8
             dec    r23          ;einmal 100 sind nun aus r23 übertragen
9
             rjmp  verteilung      ;das Ganze von vorne

von Karl H. (kbuchegg)


Lesenswert?

Matze wrote:
> Habe das jetzt mal versucht soweit. Kann das son funktionieren?

Du musst lernen dir selbst zu helfen.
Im AVR Studio gibt es einen Simulator, der deinen Prozessor
simulieren kann.
Also: Klopf dein Codestück hinein, assembliere es und gehe es
im Simulator Schritt für Schritt durch. Dabei schaust du dir
nach jedem Schritt die CPU Register an und entscheidest ob
der tatsächliche Registerinhalt deinen Vorstellungen entspricht.

> Ausgangspunkt sind die Register r21 und r23 mit den 1ern und 100ern.

Wozu brauchst du die noch?
Ich hätte jetzt eher mal das Gegenteil erwartet:
Ein Codestück, welches eine Zahl in Hunderter, Zehner und Einer
zerlegt, weil du das für die Anzeige brauchst. Bzw überhaupt
eine Anzeigeroutine, die während des Aufbereitens der Anzeige
diese Zerlegung durchführt.

> Am
> ende soll die dadurch gespeicherte Zahl in ein 16bit Register r21 Low
> und r20 High gespeichert werden.
>
>
1
> 
2
>              ldi    r20, 0        ;r20 als High Register leer machen
3
>              ldi    r29, 100      ;100 laden zum draufzählen
4
>              ldi    r30, 0        ;0 ladem zum draufzählen
5
> verteilung:  cpi    r23, 0        ;prüfen ob 100er vorhanden sind
6
>              breq  ende_Tneu      ;abbrechen wenn keine 100er da sind
7
> (r21 ist Low Register und bereits gefüllt)
8
>              add    r21, r29      ;zum Low Register r21 100 hinzuzählen
9
>              adc    r20, r30      ;zum High-Register r20 0 plus dem
10
> Carrybite hinzuzählen
11
>              dec    r23          ;einmal 100 sind nun aus r23 übertragen
12
>              rjmp  verteilung      ;das Ganze von vorne
13
> 
14
>

Sieht erst mal an sich gut aus. Aber noch mal der Hinweis: Eigentlich
brauchst du die umgekehrte Funktionalität.
Du musst schon konsequent sein. Dein bisheriges Schema mit Einer/Zehner
in einem Register und den Hundertern in einem anderen Register gibt
es nicht mehr.
Stattdessen hast du ein Registerpärchen in dem der Zähler als 16 Bit
Zahl realisiert ist. Bei Tastendruck wird der Zähler um 1 erhöht,
per Timer wird dieser Zähler um 1 erniedrigt.

von 3349 (Gast)


Lesenswert?


von Matze (Gast)


Lesenswert?

Danke für die Antwort.

Bei mir siehts so aus. Das Programm ist mittlerweile riesig weil es halt 
mein "Lernprogramm" ist und da an jeder Ecke neue Dinge hinzugekommen 
sind.

Ich habe halt zunächst eine Zählroutine, wobei man drei tasten hat - 
eine zum hochzählen, eine runter und eine bestätigen. dann wird das 
ganze im display angezeigt. desshalb habe ich die zählroutine von 
vornherein auf zwei register verteilt wobei das eine bis 99 zählt und 
das zweite ab da an die 100er stellen zählt. damit lässt sich das im 
display schön auflösen.

nun möchte ich mit dem vorher eingestellten wert einen timer ablaufen 
lassen und genau treten halt die unstimmigkeiten auf die ich eingangs 
genannt habe. desshalb wollte ich das gerne mal in 16bit zählweise 
machen, sodass ich nicht manuell 100er und 1er bzw. 10er stellen 
runterzähle sondern das über die 16bit registerkombination löse.

dafür muss ich die 100er und 1er stellen halt auf zwei register 
aufteilen wie mein codebeispiel es hoffentlich macht.

habe bislang nicht das avr studio genutzt. werde mir das mal 
installieren.

aber ich denke ich hab die grundlagen für die 16bit register und das 
Carrybit gelernt - das war mir bislang noch etwas unklar.

Vielen Dank soweit!

Gruß

Matze

von Philipp B. (philipp_burch)


Lesenswert?

> Ich habe halt zunächst eine Zählroutine, wobei man drei tasten hat -
> eine zum hochzählen, eine runter und eine bestätigen. dann wird das
> ganze im display angezeigt. desshalb habe ich die zählroutine von
> vornherein auf zwei register verteilt wobei das eine bis 99 zählt und
> das zweite ab da an die 100er stellen zählt. damit lässt sich das im
> display schön auflösen.

So würde ich da nicht rangehen. Besser wär's, du würdest immer mit einer 
normalen 16-Bit-Zahl arbeiten und diese erst zur Anzeige in einzelne 
Ziffern (10'000er, 1'000er, 100er, 10er und 1er) zerlegen. Wenn du nicht 
den ganzen Zahlenbereich (0 - 65'535 bei 16 Bit) brauchst, kannst du 
natürlich die 1'000er und die 10'000er auch weglassen. Eine Möglichkeit 
für sowas habe ich ja oben bereits gepostet.

> nun möchte ich mit dem vorher eingestellten wert einen timer ablaufen
> lassen und genau treten halt die unstimmigkeiten auf die ich eingangs
> genannt habe. desshalb wollte ich das gerne mal in 16bit zählweise
> machen, sodass ich nicht manuell 100er und 1er bzw. 10er stellen
> runterzähle sondern das über die 16bit registerkombination löse.

Ja, das ist ja auch richtig. So arbeitest du immer mit einer normalen 
16-Bit-Zahl und extrahierst die Dezimalziffern erst bei der Anzeige.

> dafür muss ich die 100er und 1er stellen halt auf zwei register
> aufteilen wie mein codebeispiel es hoffentlich macht.

Dein Code erstellt aus drei Ziffern eine 16-Bit-Zahl, das ist eher der 
umgekehrte Weg, den man z.B. bei der Eingabe von Zahlen über eine 
Tastatur beschreiten sollte. Je nach verwendetem Controller wäre dann 
die Benutzung des Hardware-Multipliers angebracht.

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.