Forum: Mikrocontroller und Digitale Elektronik AVR Assembler-Frage


von Tiny10Nutzer (Gast)


Lesenswert?

Hi Ihrz...

Ich habe mal eine schnelle Frage:

Die AVR-Assembler-Befehle (für Attiny) BST und BLD lassen sich auf die 
Register R0-R31 anwenden. Ich möchte die Befehle direkt auf das $REG 
anwenden. Kann ich das $REG in den Registern R0-R15 finden?

Befehl bisher:
cp r16,r17
in r18,$REG
bst r18,2
bld r19,0

Gewünscht:
cp r16,r17
bst $REG,2
bld R19,0

von spess53 (Gast)


Lesenswert?

Hi

>Kann ich das $REG in den Registern R0-R15 finden?

ATTiny 4/5/9/10 haben keine Register 0...15.

Was ist '$REG'?

MfG spess

von Peter II (Gast)


Lesenswert?

Tiny10Nutzer schrieb:
> Kann ich das $REG in den Registern R0-R15 finden?

nein.

wo sollte es denn das versteckt sein?

von Tiny10Nutzer (Gast)


Lesenswert?

@ spess53
Die Frage war für einen Attiny13A.

@ Peter II
Die Frage erübrigt sich etwas, da ich nach r0-r15 gefragt hatte...

von Knut B. (Firma: TravelRec.) (travelrec) Benutzerseite


Lesenswert?

Meinst Du das SREG? Das ist ein I/O-Register und hat einen separaten 
Platz im Controller-Adressraum. Es gibt spezielle Befehle, um einzelne 
Bits im SREG zu setzen und zu löschen.

SEC Set Carry
CLC Clear Carry
SEN Set Negative Flag
CLN Clear Negative Flag
SEZ Set Zero Flag
CLZ Clear Zero Flag
SEI Global Interrupt Enable
CLI Global Interrupt Disable
SES Set Signed Test Flag
CLS Clear Signed Test Flag
SEV Set Two’s Complement Overflow
CLV Clear Two’s Complement Overflow
SET Set T in SREG
CLT Clear T in SREG
SEH Set Half Carry Flag
CLH Clear Half Carry Flag

von Tiny10Nutzer (Gast)


Lesenswert?

@ Knut Ballhause

So weit war ich bereits. Trotzdem danke.

Was ich möchte ist statt

in R16,SREG
bst R16,2

direkt

bst SREG,2

schreiben können. Ich hatte jetzt die Hoffnung, dass das SREG in einen 
der Register von r0-r15 (Attiny13A) steht, da ich bst auf die Register 
r0-r31 anwenden kann.

von spess53 (Gast)


Lesenswert?

Hi

>Was ich möchte ist statt
>in R16,SREG
>bst R16,2

>direkt

>bst SREG,2

>schreiben können.

Und was ist der tiefere Grund für diesen Wunsch?

MfG Spess

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


Lesenswert?

Tiny10Nutzer schrieb:
> @ Knut Ballhause
>
> So weit war ich bereits. Trotzdem danke.

Warum schreibst du Dödel dann *$REG* statt SREG? Das war die Frage, 
die sich alle Vorposter gestellt haben.

> Was ich möchte ist statt
>
> in R16,SREG
> bst R16,2
>
> direkt
>
> bst SREG,2
>
> schreiben können.

Erstens ist das nicht das gleiche. Und zweitens ist das Leben kein 
Wunschkonzert.

> Ich hatte jetzt die Hoffnung, dass das SREG in einen
> der Register von r0-r15 (Attiny13A) steht

Welchen Teil von

Knut Ballhause schrieb:
> Das ist ein I/O-Register und hat einen separaten
> Platz im Controller-Adressraum.

hast du nicht verstanden?

Oder von

Knut Ballhause schrieb:
> Es gibt spezielle Befehle, um einzelne
> Bits im SREG zu setzen und zu löschen.

?

Bit 2 in SREG ist das N Flag. Dafür gibt es SEN und CLN.


XL

von Karl H. (kbuchegg)


Lesenswert?

spess53 schrieb:

> Und was ist der tiefere Grund für diesen Wunsch?

Hab ich auch eine Weile überlegt.
Er will sich offenbar das Ergebnis des COmpares (wenn ich jetzt nur 
wüsste, welche Bedeutung Bit 2 im SREG hat) im T-Bit speichern.

Beantwortet noch nicht die Frage nach dem 'wozu' im Detail, aber das 
scheint wohl die Absicht zu sein.

von Kaj (Gast)


Lesenswert?

spess53 schrieb:
> Und was ist der tiefere Grund für diesen Wunsch?
Wahrscheihlich moechte er sich die Takte sparen... oder eine 
Codezeile...

von Karl H. (kbuchegg)


Lesenswert?

Karl Heinz schrieb:
> spess53 schrieb:
>
>> Und was ist der tiefere Grund für diesen Wunsch?
>
> Hab ich auch eine Weile überlegt.
> Er will sich offenbar das Ergebnis des COmpares (wenn ich jetzt nur
> wüsste, welche Bedeutung Bit 2 im SREG hat) im T-Bit speichern.
>
> Beantwortet noch nicht die Frage nach dem 'wozu' im Detail, aber das
> scheint wohl die Absicht zu sein.


Langsam: der Code ging ja noch weiter
1
cp r16,r17
2
bst $REG,2
3
bld R19,0

er will offenbar das Negative Flag vom SREG nach R19 übertragen.
Wenn er sagen würde, ob ihm die restlichen Flags vom SREG in R19 egal 
sind oder nicht, dann würde ich ja sagen: übertrag halt einfach das 
komplette SREG ins R19.

von spess53 (Gast)


Lesenswert?

Hi

>Wahrscheihlich moechte er sich die Takte sparen... oder eine
>Codezeile...

Ist schon klar. Aber was will er mit dem N-Flag in einem Register? Es 
gibt Befehle, die direkt auf diese Flag reagieren: brpl und brmi. Oder 
SBIS SREG,2/SBIC SREG,2.

MfG Spess

von der alte Hanns (Gast)


Lesenswert?

"Gewöhnlich glaubt der Mensch, selbst wenn er Unsinn liest,
Es müsse sich dabei doch auch was denken lassen."

oder so ähnlich...

von Yalu X. (yalu) (Moderator)


Lesenswert?

Welches ist der Wertebereich von R16 und R17? Liegt er günstig, setzt
der CP-Befehl das C-Flag gleich wie das N-Flag, und folgender Code tut
das von dir Gewünschte mit der gleichen Zyklenzahl:
1
  lsr r19
2
  cp r16,r17
3
  rol r19

Musst du tatsächlich das N-Flag verwenden, geht das nicht so einfach.

von Tiny10Nutzer (Gast)


Lesenswert?

Der Teilabschnitt meines Code sieht etwa so aus:
1
cp r17,r18
2
in r16,SREG
3
bst r16,2
4
bld R24,0
5
cp r17,r19
6
in r16,SREG
7
bst r16,2
8
bld r24,1
9
cp r17,r20
10
in r16,SREG
11
bst r16,2
12
bld r24,2
13
cp r17,r21
14
in r16,SREG
15
bst r16,2
16
bld r24,3
17
cp r17,r22
18
in r16,SREG
19
bst r16,2
20
bld r24,4
21
cp r17,r23
22
in r16,SREG
23
bst r16,2
24
bld r24,5
25
out PortB,r24

Wer sich jetzt hier so schnell durchfindet wird merken, dass ich hier 
nicht nur eine Zeile (einen Befehl) einsparen möchte. Der Sinn des 
Ganzen ist hier Offtoppic (und sagt mir nur, dass die Diskutierenden 
darüber nicht die richtige Antwort haben). Ich suche hier nur einen Weg 
den Befehl bst etwas zweck zu entfremden.

Im Attiny13A ist das SREG in Adresse 63 (0x3F). Wenn ich jetzt wüsste 
wie die Register r0-r15 aufgerufen werden, könnte ich vieleicht einfach 
die Adressen austauschen (darüber nachdenkend).

von der alte Hanns (Gast)


Lesenswert?

Am Anfang

ldi r24,0
out PORTB,r24

und dann jeweils

brpl pc+2
 sbi PORTB,n

Spart Code, aber leider keine Zeit.

von der alte Hanns (Gast)


Lesenswert?

> Spart Code, aber leider keine Zeit.

Na, das ist datenabhängig.

von Robin (Gast)


Lesenswert?

Wie wäre es denn damit?
1
cp r17,r18
2
brpl PC+2
3
sbr R24,0
4
....

von Tiny10Nutzer (Gast)


Lesenswert?

@ Yalu X

Es muss das N-Flag sein. Ich brauche nach jeder Auswertung das N-Flag an 
einer bestimmten Position des PortB.

@ der alte

Der aktuelle Zustand des PortB ist nicht wichtig und darf aber auch 
nicht verändert werden. Der zeitliche Abstand mit dem auf PortB 
geschrieben wird muss zuverlässig konstant sein (weswegen ich nur einmal 
auf PortB schreibe). Hin- und Herspringen möchte ich aus dem gleichen 
Grund nicht. Das Ganze muss ohne Sprungbefehle gehen.

von der alte Hanns (Gast)


Lesenswert?

Okay, aber was spricht gegen die Lösung von Robin (welche eindeutig die 
bessere ist)? Warum keine Sprungbefehle?

von der alte Hanns (Gast)


Lesenswert?

Nur als Hinweis: Robins Lösung ist zeitlich konstant.

von (prx) A. K. (prx)


Lesenswert?

Robin schrieb:
> sbr R24,0

So wird das nix. SBR (=ORI) braucht Maske, nicht Bit.

von der alte Hanns (Gast)


Lesenswert?

Schon klar:
ori r24,(1<<n)

von Robin (Gast)


Lesenswert?

A. K. schrieb:
> Robin schrieb:
>> sbr R24,0
>
> So wird das nix. SBR (=ORI) braucht Maske, nicht Bit.

Ups, ist aber auch schon wieder ein weilchen her mit dem Assembler bei 
mir. War da auch schon mal fitter. Aber das Prinzip stimmt doch 
zumindest, der Rest ist feinschliff.

von Tiny10Nutzer (Gast)


Lesenswert?

@ Robin

Das kommt dem was ich mir vorstelle schon sehr nahe. Die Zyklenzahl ist 
mit N-Flag-Low und N-Flag-High die gleiche. Aber: Ich habe bisher noch 
nicht mit SBR arbeiten dürfen. Aber beeinflusst das nicht auch die Bits 
(in R24) die ich unberührt lassen möchte?

von Yalu X. (yalu) (Moderator)


Lesenswert?

Ich hätte jetzt auch gesagt, dass Robins Code mit der Modifikation von
A. K. und dem alten Hanns den Job tun sollte. Zu Beginn der ganzen
Befehlskette sollte noch ein CLR R24 stehen. Zusammen mit dem OUT am
Ende sind das konstant 20 Zyklen

Tiny10Nutzer schrieb:
> Der Teilabschnitt meines Code sieht etwa so aus:

Das sind 25 Zyklen, also bringt Robins Lösung doch eine ganze Menge :)

Tiny10Nutzer schrieb:
> Ich habe bisher noch nicht mit SBR arbeiten dürfen. Aber beeinflusst
> das nicht auch die Bits (in R24) die ich unberührt lassen möchte?

SBR ist einfach ein Synonym für ORI.

von der alte Hanns (Gast)


Lesenswert?

Die Verwendung von sbr und cbr hatte ich mir damals ganz schnell 
abgewöhnt! Es handelt sich, wie A.K. und Yalu schreiben, nur um eine 
andere Form von ori resp. andi, vermutlich um in der Werbung die 
Befehlsanzahl künstlich aufzublähen.

von spess53 (Gast)


Lesenswert?

Hi

>Ich habe bisher noch
>nicht mit SBR arbeiten dürfen. Aber beeinflusst das nicht auch die Bits
>(in R24) die ich unberührt lassen möchte?

Nein. Das ist eine andere Schreibweise für ORI. Es werden nur die Bits 
gesetzt, die Eins sind.

MfG Spess

von Tiny10Nutzer (Gast)


Lesenswert?

Sieht das so richtig aus?
1
ldi r24,0
2
cp r17,r18
3
brmi PC+2
4
ori r24,0b00000001
5
cp r17,r19
6
brmi PC+2
7
ori r24,0b00000010
8
cp r17,r20
9
brmi PC+2
10
ori r24,0b00000100
11
cp r17,r21
12
brmi PC+2
13
ori r24,0b00001000
14
cp r17,r22
15
brmi PC+2
16
ori r24,0b00010000
17
cp r17,r23
18
brmi PC+2
19
ori r24,0b00100000
20
out PortB,r24

von Yalu X. (yalu) (Moderator)


Lesenswert?

So werden die Bits in R24 aber invertiert gesetzt. Wenn jedes Bit dem 
N-Flag des jeweiligen Vergleichs entsprechen soll, musst du wie in 
Robins Vorschlag BRPL statt BRMI verwenden.

von der alte Hanns (Gast)


Lesenswert?

Und ein Tipp: das (1<<n) macht die Sache nicht nur übersichtlicher, 
sondern auch sicherer. (Wenn ich da an SEPA mit den vielen Nullen 
mittendrin denke)

von Tiny10Nutzer (Gast)


Lesenswert?

Ja das meinte ich. War mir grade nicht mehr sicher.
Diese Variante hat auch den enormen Vorteil, dass ich mir R16 einspare.

@ Robin
Hast mir grade riesig geholfen.

Danke euch allen für die Hilfe.

von spess53 (Gast)


Lesenswert?

Hi

Noch eine grundsätzlich Frage: Bist du sicher, das das N-Flag das 
richtige ist?

MfG Spess

von Yalu X. (yalu) (Moderator)


Lesenswert?

spess53 schrieb:
> Noch eine grundsätzlich Frage: Bist du sicher, das das N-Flag das
> richtige ist?

Anscheinend ja (ich hatte das weiter oben auch schon gefragt):

Tiny10Nutzer schrieb:
> @ Yalu X
>
> Es muss das N-Flag sein. Ich brauche nach jeder Auswertung das N-Flag an
> einer bestimmten Position des PortB.

Ginge auch das C-Flag könnte man noch einmal 6 Zyklen einsparen.
Deswegen hast du wahrscheinlich auch danach gefragt :)

von Tiny10Nutzer (Gast)


Lesenswert?

@ spess53

Ich bin mir absolut sicher. Ich habe die Routine ja auch schon mehrmals 
im Einsatz gehabt. Wie ich aber grade so mal meine Ausdrucke untersucht 
habe, hat mich die Länge des Programmabschnitts (Zyklen und Code) dann 
doch sehr gestört. Ich fand auch die Lösung über ein nichtgebrauchtes 
Register zu gehen nicht sonderlich elegant.

von spess53 (Gast)


Lesenswert?

Hi

>Ginge auch das C-Flag könnte man noch einmal 6 Zyklen einsparen.
>Deswegen hast du wahrscheinlich auch danach gefragt :)

Das N-Flag sagt nach einem CP nichts über das Verhältnit der Operatoren 
aus:

 0x01-0x03 = 0xFE -> N=1
 0xFF-0x01 = 0xFE -> N=1

MfG Spess

von der alte Hanns (Gast)


Lesenswert?

Nicht das Einzige, was mir hier etwas spanisch vorkommt.
Nichtsdestotrotz: den 'Unsinn' nehme ich mit Bedauern zurück.

von Tiny10Nutzer (Gast)


Lesenswert?

Jetzt bin ich doch nochmal neugierig geworden
Das Ziel der Teilfunktion ist:

Die Register die mit CP verglichen werden, können Werte von 0 bis 255 
annehmen. Ist das eine Register kleiner als das andere soll ein Bit (in 
einem dritten Register) gesetzt werden. So meine Routine bisher. Ich 
könne meine Routine aber auch etwas abändern, solange das Ergebnis das 
gleiche bleibt.

von spess53 (Gast)


Lesenswert?

Hi

>Ist das eine Register kleiner als das andere soll ein Bit (in
>einem dritten Register) gesetzt werden.

Dafür ist das Carry-Flag zuständig. Das N-Flag sagt lediglich aus, das 
beim Ergebnis Bit7 = 1 ist.

MfG Spess

von Yalu X. (yalu) (Moderator)


Lesenswert?

spess53 schrieb:
> Das N-Flag sagt nach einem CP nichts über das Verhältnit der Operatoren
> aus:
>
>  0x01-0x03 = 0xFE -> N=1
>  0xFF-0x01 = 0xFE -> N=1

Doch, manchmal schon. Das hängt davon ab, ob die Registerinhalte als
vorzeichenlose oder oder vorzeichenbehaftete Zahlen betrachtet werden.
Offensichtlich haben wir es hier mit vorzeichenbehafteten Zahlen zu tun,
weswegen das N-Flag die passendere Lösung ist.

Korrektur: Da habe ich etwas verwechselt. Nicht das N-Flag, sondern das
S-Flag ist für Vergleiche vorzeichenbehafteter Zahlen gedacht.

Ich bin mir aber fast sicher, dass man, wenn man den Rest des Programms
etwas anpassen würde, das Ganze auch mit vorzeichenlosen Zahlen rechnen
könnte, so dass man auch das C-Flag verwenden könnte ;-)

Edit:

Tiny10Nutzer schrieb:
> Die Register die mit CP verglichen werden, können Werte von 0 bis 255
> annehmen. Ist das eine Register kleiner als das andere soll ein Bit (in
> einem dritten Register) gesetzt werden.

Hmm, also doch vorzeichenlos? Dann ist aber das C-Flag richtig.

: Bearbeitet durch Moderator
von Peter D. (peda)


Lesenswert?

Tiny10Nutzer schrieb:
> Ich bin mir absolut sicher.

Ist denn die Anwendung so geheim, daß Du sie nicht nennen darfst?

Mit dem C-Flag würde ich denken, das ist ne Mehrkanal-PWM.
Mit dem N-Flag fällt mir keine praktische Anwendung ein. Vielleicht ein 
Mehrkanal-Phasenschieber.

: Bearbeitet durch User
von tiny10nutzer (Gast)


Lesenswert?

Hier der Code bis zur relevanten Stelle (ist selbsterklärend):
1
.include "tn13Adef.inc"
2
 
3
.def tmp = r16
4
.def output = r17
5
.def itmp = r18
6
.def isreg = r19
7
8
.def softpwmband = r20
9
.def softpwmcount = r21
10
.def channel0value = r22
11
.def channel1value = r23
12
.def channel2value = r24
13
.def channel3value = r25
14
.def channel4value = r26
15
.def channel5value = r27
16
.def pwmstatus = r28
17
.def delaycount = r29
18
19
.org 0x0000
20
        rjmp    reset_order        ; Reset Handler
21
.org 0x0006
22
        rjmp    timer0_match    ; Timer0A Match Handler
23
24
25
reset_order:
26
 ldi     tmp, low(RAMEND)    ; Stackpointer initialisieren
27
 out     SPL, tmp
28
29
 ldi     softpwmband, 250 ; PWM Auflösung 
30
 ldi     softpwmcount, 0
31
 ldi     channel0value, 0
32
 ldi     channel1value, 0
33
 ldi     channel2value, 0
34
 ldi     channel3value, 0
35
 ldi     channel4value, 0
36
 ldi     channel5value, 0
37
 ldi     delaycount, 0
38
        
39
 ldi     output, 0b00111111      ; Port B auf Ausgang
40
 out     DDRB, output
41
 out     PORTB, output    ; Port B auf High 
42
 
43
 ldi     tmp, 39          ; wenn timerzähler 40-1 erreich interruppt  auslösen
44
 out     OCR0A, tmp
45
 ldi     tmp, 1 << OCIE0A     ; OCIE0A: Interrupt bei Timer Vergleich A
46
 out     TIMSK0, tmp
47
 ldi     tmp, 1 << WGM01      ; nach OCIE0A Timerzähler zurücksetzen
48
 out     TCCR0A, tmp
49
; ldi     tmp, 1 << OCF0A  ; Dieses Bit lösst den Interrupt aus 
50
; out     TIFR0, tmp  ; und wird vom OCIE0A in TIMSK0 gesetzt
51
 ldi     tmp, 1 << CS00     ; CS00 setzen: für Timerzähler kein Vorteiler
52
 out     TCCR0B, tmp
53
 sei              ; Interrupt erlauben         
54
  rjmp mainloop  
55
       
56
timer0_match:          ; Timer0A Match Handler
57
 in isreg, SREG 
58
59
 cp softpwmcount,channel0value   ; Vergleiche Zähler mit PWM-Einstellung
60
 in itmp, SREG ; das SReg in Register laden
61
 bst itmp, 2   ; das Negativ Bit 2 aus Register in das T-Flag schreiben
62
 bld output,0 ; schreibt das T-Flag in das Zielbit von output
63
 cp softpwmcount,channel1value   ; Vergleiche Zähler mit PWM-Einstellung
64
 in itmp, SREG ; das SReg in Register laden
65
 bst itmp, 2   ; das Negativ Bit 2 aus Register in das T-Flag schreiben
66
 bld output,1 ; schreibt das T-Flag in das Zielbit von output
67
 cp softpwmcount,channel2value   ; Vergleiche Zähler mit PWM-Einstellung
68
 in itmp, SREG ; das SReg in Register laden
69
 bst itmp, 2   ; das Negativ Bit 2 aus Register in das T-Flag schreiben
70
 bld output,2 ; schreibt das T-Flag in das Zielbit von output
71
 cp softpwmcount,channe31value   ; Vergleiche Zähler mit PWM-Einstellung
72
 in itmp, SREG ; das SReg in Register laden
73
 bst itmp, 2   ; das Negativ Bit 2 aus Register in das T-Flag schreiben
74
 bld output,3 ; schreibt das T-Flag in das Zielbit von output
75
 cp softpwmcount,channel4value   ; Vergleiche Zähler mit PWM-Einstellung
76
 in itmp, SREG ; das SReg in Register laden
77
 bst itmp, 2   ; das Negativ Bit 2 aus Register in das T-Flag schreiben
78
 bld output,4 ; schreibt das T-Flag in das Zielbit von output
79
 cp softpwmcount,channel5value   ; Vergleiche Zähler mit PWM-Einstellung
80
 in itmp, SREG ; das SReg in Register laden
81
 bst itmp, 2   ; das Negativ Bit 2 aus Register in das T-Flag schreiben
82
 bld output,5 ; schreibt das T-Flag in das Zielbit von output
83
 out PORTB,output ; auf das PortB schreiben
84
 
85
 inc softpwmcount                ; Interner und Externer Zähler +1
86
87
 cp softpwmcount, softpwmband ; wenn PWM-Durchlauf nicht das Ende erreicht hat
88
 brne PC+3 ; nächste Befehle überspringen
89
 ldi softpwmcount,0x00 ; neuer PWM-Durchlauf
90
 inc delaycount                    
91
92
 out SREG,isreg
93
  reti ; Interrput braucht bis hier ?? CPU-Zyklen
94
95
mainloop:   
96
 cpi delaycount,2
97
 breq smoth
98
  rjmp mainloop ; mainloop braucht bis hier 3 CPU-Zyklen

Die Kommentare sind lange nicht mehr aktuell. Der restliche Code ist 
weder fertig noch wichtig. Ich kann nur sagen, dass das Programm bis 
hier richtig funktioniert hat (die logische Abfolge stimmt).

von Yalu X. (yalu) (Moderator)


Lesenswert?

Wenn ich das richtig sehe, ist das kein PWM-Generator, sondern, wie
Peter schon angedeutet hat, ein Phasenschieber, es sei denn, softpwmband
wird an anderer Stelle noch auf einen Wert kleiner 128 gesetzt.

: Bearbeitet durch Moderator
von Robin (Gast)


Lesenswert?

Für größer/kleiner Vergleiche gibt es doch schönere branch befehle:

BRSH/BRLO für vorzeichenlose Zahlen und BRGE/BRLT für 
vorzeichenbehaftete Zahlen.

Damit wäre dann das ganze noch schöner ;)

von Peter D. (peda)


Lesenswert?

tiny10nutzer schrieb:
> Ich kann nur sagen, dass das Programm bis
> hier richtig funktioniert hat (die logische Abfolge stimmt).

Sicher?

Wenn ein Wert durchzählt und der andere irgendeine Konstante ist, dann 
ist das Ergebnis immer zu 50% negativ, d.h. Deine PWM hat konstant 50%. 
Nur die Phase verschiebt sich.


Oft ist aber der scheinbar einfache Weg weder effektiv noch effizient.
Schau Dir mal die BAM an:
http://www.batsocks.co.uk/readme/art_bcm_3.htm

Mir ist keine bessere Software-PWM bekannt.

von tiny10nutzer (Gast)


Lesenswert?

Iggit C-Code...

von tiny10nutzer (Gast)


Lesenswert?

Die ganze Routine sieht jetzt so aus:
1
timer0_match:          ; Timer0A Match Handler
2
 in isreg, SREG 
3
4
 ldi output,0
5
 cp softpwmcount,channel0value   ; Vergleiche Zähler mit PWM-Einstellung
6
 brpl PC+2
7
 ori output,0b00000001
8
 cp softpwmcount,channel1value   ; Vergleiche Zähler mit PWM-Einstellung
9
 brpl PC+2
10
 ori output,0b00000010
11
 cp softpwmcount,channel2value   ; Vergleiche Zähler mit PWM-Einstellung
12
 brpl PC+2
13
 ori output,0b00000100
14
 cp softpwmcount,channel3value   ; Vergleiche Zähler mit PWM-Einstellung
15
 brpl PC+2
16
 ori output,0b00001000
17
 cp softpwmcount,channel4value   ; Vergleiche Zähler mit PWM-Einstellung
18
 brpl PC+2
19
 ori output,0b00010000
20
 cp softpwmcount,channel5value   ; Vergleiche Zähler mit PWM-Einstellung
21
 brpl PC+2
22
 ori output,0b00100000
23
 out PORTB,output ; auf das PortB schreiben
24
 
25
 inc softpwmcount                ; Interner und Externer Zähler +1
26
27
 cp softpwmcount, softpwmband ; wenn PWM-Durchlauf nicht das Ende erreicht hat
28
 brne PC+3 ; nächste Befehle überspringen
29
 ldi softpwmcount,0x00 ; neuer PWM-Durchlauf
30
 inc delaycount                    
31
32
 out SREG,isreg
33
  reti ; Interrput braucht bis hier ?? CPU-Zyklen

Wenn Ihr mir jetzt sagt "Das geht noch kürzer" hör ich gern zu.

von der alte Hanns (Gast)


Lesenswert?

Normalerweise lässt sich ein Befehl (das compare) sparen, wenn man, 
statt bis zum Grenzwert hochzuzählen, vom Grenzwert beginnend 
herunterzählt.

von der alte Hanns (Gast)


Lesenswert?

Und natürlich ist noch immer das offen, was Yalu X. anriss: wenn statt 
des N- das C-Flag abgefragt werden kann (warum eigentlich nicht?), dann 
ist noch mehr möglich.

von Joerg W. (joergwolfram)


Lesenswert?

Wenn man das Carry-Flag nutzt und dieses ins Zielregister reinschiebt, 
kann man noch etwas Code und auch Takte sparen:
1
    clr   output
2
    cp    softpwmcount,channel5value
3
    rol   output
4
    cp    softpwmcount,channel4value
5
    rol   output
6
    cp    softpwmcount,channel3value
7
    rol   output
8
    cp    softpwmcount,channel2value
9
    rol   output
10
    cp    softpwmcount,channel1value
11
    rol   output
12
    cp    softpwmcount,channel0value
13
    rol   output
14
    out   PORTB,output
15
    inc   softpwmcount
16
    cp    softpwmcount,softpwmband
17
    brne  pwmset_1
18
    clr   softpwmcount
19
pwmset_1:
Jörg

von Sascha W. (sascha-w)


Lesenswert?

tiny10nutzer schrieb:
> Wenn Ihr mir jetzt sagt "Das geht noch kürzer" hör ich gern zu.
Damit man später noch mal was erkennen kann solltest du Sprungmarken 
anstelle von PC+xx verwenden. Man muss ja nicht alles 'kopieren'.

Sascha

von der alte Hanns (Gast)


Lesenswert?

> Damit man später noch mal was erkennen kann solltest du Sprungmarken

Bei mir ist es genau umgekehrt, vor lauter (unnützen) Sprungmarken würde 
ich nichts mehr erkennen.

von Robin (Gast)


Lesenswert?

Falls es dir nur um eine Takteinsparung in der Timer_ISR geht, dann 
kannst du deine Daten auch vorsortieren und in einer Liste die OCCRA 
Werte und die PortB Werte speichern und dann nurnoch diese in der ISR 
laden.

Dann bekommst du die Timer ISR innerhalb von ca.10 Takten abgearbeitet. 
Allerdings brauchst du in deinem, Hauptcode dann einen Bubblesort, der 
einmal pro Timer Überlauf durchlaufen wird.

Dann wird das ganze aber noch schneller ;)

von c-hater (Gast)


Lesenswert?

der alte Hanns schrieb:

> Bei mir ist es genau umgekehrt, vor lauter (unnützen) Sprungmarken würde
> ich nichts mehr erkennen.

Huch?! Was schreibst du denn für Code?

Klar, es gibt durchaus Situationen, wo die Ziele kurzer Sprünge 
unwichtig sind. Vor allem dann, wenn diese kurzen Sprünge nur 
existieren, um über den wegen der Beschränkungen der Sprungweite der 
branch-Instruktionen sozusagen "ausgelagerten" weiten Sprung 
hinwegzuspringen.

Wenn das bei dir aber so häufig vorkommt, daß du es für die Regel 
hältst, dann schreibst du offensichtlich ziemlich ineffizienten Code...

Bei mir liegt die Quote solcher durch die Beschränkungen des 
Befehlssatzes erzwungenen "Sprung-Umleitungen" jedenfalls im Allgemeinen 
unter einem Prozent.

In allen anderen Fällen arbeite ich natürlich mit Sprungmarken. Deren 
Bezeichner sind natürlich sprechend und ersparen somit den Kommentar an 
der Verzweigung. Alles andere wäre doch ziemlich idiotisch!

von Peter D. (peda)


Angehängte Dateien:

Lesenswert?

tiny10nutzer schrieb:
> Iggit C-Code...

Ach komm, der Algorithmus ist doch so einfach, das hat man ruckzuck auch 
in Assembler hingefriemelt.
Hier mal der Interrupt:
1
        INTHAND OC0Aaddr
2
        in      isreg, SREG
3
        ld      iwr0, X+
4
        out     PORTD, iwr0             ;output pattern
5
        lsl     pwm_time                ;0, 2, 6, 14, 30, 62, 126, 254
6
        inc     pwm_time                ;1, 3, 7, 15, 31, 63, 127, 255
7
        out     OCR0A, pwm_time
8
        inc     pwm_time                ;2, 4, 8, 16, 32, 64, 128, 0
9
        breq    _oc01
10
        dec     pwm_time                ;1, 3, 7, 15, 31, 63, 127
11
        out     SREG, isreg
12
        reti
13
_oc01:                                  ;                          0
14
        ldi     xl, low(pwm_data)
15
        ldi     xh, high(pwm_data)
16
        out     SREG, isreg
17
        reti
Der Rest im Anhang.
Die PWM ist nur 244Hz, das kann man aber noch tunen.
Ich habs auf nem ATmega48 getestet.

von tiny10nutzer (Gast)


Lesenswert?

@  Peter Dannegger

Dein Code macht mir grade gewaltig Angst, da ich genaugenommen überhaupt 
nichts verstanden habe...

(Deutschland 1:0 Grins)

von Sam .. (sam1994)


Lesenswert?

Peter Dannegger schrieb:
> Die PWM ist nur 244Hz, das kann man aber noch tunen.

Siehe Soft-PWM
geht aber auch noch besser. Mit Jitterkorrektur kann man nocht bessere 
Ergebnisse erreichen. Auch Soft-PWM (nicht BAM) kann man durch 
Phasenverschiebung mit vollem Takt erzeugen, das ist aber deutlich 
aufwändiger als BAM.

tiny10nutzer schrieb:
> Dein Code macht mir grade gewaltig Angst, da ich genaugenommen überhaupt
> nichts verstanden habe...

Versuche erstmal das Prinzip von BAM zu verstehen. Dann verstehst du 
auch den Code.

: Bearbeitet durch User
von tiny10nutzer (Gast)


Lesenswert?

Robin schrieb:
> Falls es dir nur um eine Takteinsparung in der Timer_ISR geht, dann
> kannst du deine Daten auch vorsortieren und in einer Liste die OCCRA
> Werte und die PortB Werte speichern und dann nurnoch diese in der ISR
> laden.
>
> Dann bekommst du die Timer ISR innerhalb von ca.10 Takten abgearbeitet.
> Allerdings brauchst du in deinem, Hauptcode dann einen Bubblesort, der
> einmal pro Timer Überlauf durchlaufen wird.
>
> Dann wird das ganze aber noch schneller ;)

Kann ich dafür irgendwo ein Beispiel sehen?

von Peter D. (peda)


Lesenswert?

Robin schrieb:
> Dann bekommst du die Timer ISR innerhalb von ca.10 Takten abgearbeitet.

10 Zyklen dauert allein schon der Einsprung, Sprung zum Handler und das 
RETI.
Und wenn im Main gerade ein RET ausgeführt wird, nochmal 4 Zyklen.
Von atomic Zugriffen und anderen Interrupts ganz zu schweigen.

Bei der BAM besteht allerdings die Möglichkeit, für die ersten 2 oder 3 
Bits den Interrupt garnicht erst zu verlassen. Dann könnten 10 Zyklen 
Bitzeit durchaus möglich sein.

: Bearbeitet durch User
von Sam .. (sam1994)


Lesenswert?

tiny10nutzer schrieb:
> Kann ich dafür irgendwo ein Beispiel sehen?

Das gibt es leider (noch) nicht.  Ich habe vor ein paar Tagen es 
theoretisch getestet. Es ist auf jeden Fall möglich.

Peter Dannegger schrieb:
> Dann könnten 10 Zyklen
> Bitzeit durchaus möglich sein.

Ich wiederhole mich: Es sind Bitzeiten bis zum CPU-Takt möglich. Mit 
Jitterkorrektur ist das gar kein Problem mehr.

von der alte Hanns (Gast)


Lesenswert?

an c-hater:

 ldi output,0
 cp softpwmcount,channel0value   ; vergl. jeweils Zähler mit PWM-Einst.
 brpl pc+2
  ori output,(1<<0)
 cp softpwmcount,channel1value
 brpl pc+2
  ori output,(1<<1)
 cp softpwmcount,channel2value
 brpl pc+2
  ori output,(1<<2)
 cp softpwmcount,channel3value
 brpl pc+2
  ori output,(1<<3)
 cp softpwmcount,channel4value
 brpl pc+2
  ori output,(1<<4)
 cp softpwmcount,channel5value
 brpl pc+2
  ori output,(1<<5)
 out PORTB,output

gegenüber

 ldi output,0
 cp softpwmcount,channel0value   ; Vergleiche Zähler mit PWM-Einstellung
 brpl hopp_de_baese_0
 ori output,0b00000001
hopp_de_baese_0:
 cp softpwmcount,channel1value   ; Vergleiche Zähler mit PWM-Einstellung
 brpl hopp_de_baese_1
 ori output,0b00000010
hopp_de_baese_1:
 cp softpwmcount,channel2value   ; Vergleiche Zähler mit PWM-Einstellung
 brpl hopp_de_baese_2
 ori output,0b00000100
hopp_de_baese_2:
 cp softpwmcount,channel3value   ; Vergleiche Zähler mit PWM-Einstellung
 brpl hopp_de_baese_3
 ori output,0b00001000
hopp_de_baese_3:
 cp softpwmcount,channel4value   ; Vergleiche Zähler mit PWM-Einstellung
 brpl hopp_de_baese_4
 ori output,0b00010000
hopp_de_baese_4:
 cp softpwmcount,channel5value   ; Vergleiche Zähler mit PWM-Einstellung
 brpl hopp_de_baese_5
 ori output,0b00100000
hopp_de_baese_5:
 out PORTB,output ; auf das PortB schreiben

DAS meinte ich, und wenn es auch, zugegebenermaßen, Geschmackssache sein 
mag, so wiederhole ich mich gerne: ich kann das Erste schneller 
überblicken.
Mit einem macro gefiele es mir noch besser, aber dieses Umschreiben 
überlasse ich Ihnen.

Dieses jedoch
> dann schreibst du offensichtlich ziemlich ineffizienten Code...
zeigt leider nur wieder einmal Ihren üblichen Stil.

von der alte Hanns (Gast)


Lesenswert?

Oder war es, da andernorts gerade über 'Lesevermögen' diskutiert wird, 
nur ein Missverständnis?
Ich meinte natürlich nicht, dass jegliche Sprungmarken unnütz sind, 
sondern dass ich solche Sprungmarken durch pc+- ersetze, die ich für 
unnütz halte.

Trotzdem, Ihr Ton gefällt mir nicht.
> ziemlich idiotisch!

von tiny10nutzer (Gast)


Lesenswert?

Während Ihr Euch ein klein wenig gestritten habt, kahm mir noch ein 
Gedanke:

Im Beispiel oben hat das Software-PWM eine Auflösung von 250 
Abstufungen. D.h. bei 250 Interruptaufrufen finden nur 2 
High-Low-Wechsel am Pin statt. Sind also die anderen 248 
Interruptaufrufe sinnfrei (auf den einzellnen Pin bezogen). Die kann man 
doch bestimmt aussetzen.?

von Peter D. (peda)


Lesenswert?

Ich hab die BAM noch auf 24 Zyklen Bitzeit gepimpt, indem der nächste 
Timerwert auch aus einer Tabelle geladen wird.
Damit kommt man bei F_CPU = 8MHz auf 1,3kHz PWM, das sollte reichen.

von der alte Hanns (Gast)


Lesenswert?

> Während Ihr Euch ein klein wenig gestritten habt

Nun, junger Freund, alles will gelernt sein, auch zivilisiert zu 
streiten, und, wenn ich meinen Blick etwas weiter schweifen lasse, so 
möchte ich hinzufügen, dass es eine der (überlebens-) wichtigen 
Fähigkeiten ist.

von Tiny10Nutzer (Gast)


Lesenswert?

@ der alte Hanns

Korrekt...

@ Peter Dannegger

Ich habe mich jetzt in das BAM hineingelesen. So wie ich das System 
verstehe setzt es (die Realität) vorraus, dass das niedrigste Bit für 
die kleines Auflösung 1 steht, das Bit darüber für eine Auflösung 
doppelt so groß wie das kleinste, usw. Für das höchste Bit braucht also 
für 128 Durchläufe nur ein Interrupt ausgelösst werden, da dieses Bit 
ohnehin für diese Größe steht. Das Bit darunter steht für 64 Durchläufe, 
muss hier auch nur einmal aufgerufen werden und sparrt dabei 63 
Durchläufe (oder Interrupts). Der nette Nebeneffekt ist, dass die BAM 
während eines gesammten Durchlaufs die Flanke mehrmals auf High bzw. Low 
setzt und so in Durchschnitt eine höhere Frequenz erwirkt wird, während 
ein "nur" PWM während eines Durchlaufs die Flanke jeweils nur einmal auf 
High bzw. Low setzt.

Ist das so richtig?

(Es steht aber auch in der Beschreibung, dass BAM absolut 
Elektro-Motor-Ungeeignet ist.)

von Tiny10Nutzer (Gast)


Lesenswert?

Während der 128 Bit-Phase hätte ich mehr als genug Zeit alle 
channel?value Einstellungen umzusortieren, sodass ich im Interrupt 
selber nur noch "out PortB,Output - Reti" schreiben muss. Der Vorteil 
ist eine extrem höhere Frequenz, da ich für den Interrupt statt der 
jetzigen 24-25 Zyklen nur noch 5 Zyklen brauche. Der Nachteil ist, dass 
ich 7 zusätzliche Register brauche, da ist 8-mal Output für die Ausgabe 
vorbereiten muss.

von Tiny10Nutzer (Gast)


Lesenswert?

Ich rechne mal:

Mein PWM: Ich arbeite jetzt mit 1,2 MHz, rufe alle 30 Takte den 
Interrupt auf und habe eine Auflösung von 250, macht 160 Hz.
Das Umsetzbare BAM: Vorrausgesetzt es bleibt bei den 1,2 MHz, ist der 
kürzeste Interruptaufruf bei 5 Takten und bei einer Auflösung von 8 Bit 
(256) komme ich auf 937,5 Hz. Da die Flanke mehr als nur einmal wechselt 
habe ich eine durchschnittlich deutlich höhere Frequenz. Wie ich die 
durchschnittlich höhere Frequenz berechnen soll, da habe ich aber grade 
keinen Plan von.

Kommt das so hin, oder denke ich grade Falsch?

von Robin (Gast)


Lesenswert?

tiny10nutzer schrieb:
> Kann ich dafür irgendwo ein Beispiel sehen?

Beispiel Code hab ich leider gerade keinen Zur hand, aber ich versuch es 
mal schrittweise zu erklären.

Im RAM speicherst du eine Tabelle mit 3 Byte pro Kanal. bestehend aus 2 
Byte für das Compare Register und einem Byte für den Ausgangsport. (bei 
mehreren Ports müssen mehr Bytes gespeichert werden).
Dann brauchst du noch 2 Byte für den Z-Pointer, damit die ISR schneller 
abgearbeitet werden kann.

Im Hauptprogramm wird eine Flag abgefragt, die nach einem Timer 
Durchlauf gesetzt wird. Ist diese Flag gesetzt, werden die zu diesem 
Zeitpunkt aktuellen PWM Werte sortiert und eine Maske für den Ausgabe 
Port angelegt.
Bei gleichen Timerwerten für mehrere Kanäle, werden diese 
zusammengefasst und alle doppelten bis auf einen Kanal auf maximum 
gesezt, damit sie ans ende der Tabelle kommen.

Im Timer Overflow interrupt werden alle PWM-Pins auf High gesetzt, der 
Z-Pointer auf den Anfang der Liste initialisiert und der Erste Wert der 
Liste in das Timer Compare Register geladen. Und die Flag für das 
Hauptprogramm gesetzt.

Bei einem Timer Compare interrupt, wird der Z-Pointer geladen, die 
Portmaske am Ausgabe Port ausgegeben und der nächste wert in das Compare 
Register geladen. Anschliesend wird der Z-Pointer wieder gesichert, 
damit ein schnellerer Zugriff im nächsten Interrupt erfolgt, ohne lange 
herum zu rechnen.

---------------------

Das sind die Bestandteile dieser Methode, hat natürlich Vorteile und 
auch Nachteile.

Vorteile:
- maximal n Interrupt aufrufe pro Periode, (n = Anzahl der Kanäle)
- Wenn man etwas fehler in Kauf nimmt (mindest abstand zwischen zwei 
Zeiten) kann man eine hohe Auflösung erreichen.

Nachteile:
- Recht aufwändige Vorverarbeitung der Daten
- Recht viel Speicherplatz benötigt (3*n + 2 Byte RAM und ein 
Sortieralgorithmus im Programmcode)
- Eine Periode Verzögerung bei der Ausgabe der Werte (könnte aber 
optimiert werden)


Hoffe das ist einigermaßen verständlich geschrieben. Die Einfachere 
Möglichkeit zum Realisieren ist auf jedenfall die BAM. Die 
Vorverarbeitung der Daten ist einfacher, dafür wird die ISR häufiger 
aufgerufen. Speicherbedarf und Verzögerung sind bei beiden in etwa 
gleich.

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


Lesenswert?

Tiny10Nutzer schrieb:
> Während der 128 Bit-Phase hätte ich mehr als genug Zeit alle
> channel?value Einstellungen umzusortieren, sodass ich im Interrupt
> selber nur noch "out PortB,Output - Reti" schreiben muss.

Genauso würde man das machen. Tatsächlich würde man wohl die Bitmasken 
vorberechnen (mehr als einmal pro Zyklus kann man die PWM-Werte sowieso 
nicht ändern ohne Glitches zu verursachen). Die ISR muß dann nur die 
jeweils nächste Bitmaske aus dem Buffer lesen und auf die Ausgabepins 
schreiben.

Nicht vergessen: für LED will man die Intensitätswerte nicht direkt für 
die PWM verwenden. Das Auge hat eine antilogarithmische 
Empfindlichkeitskurve. Für 256 Helligkeitsstufen, die dem Auge 
gleichmäßig erscheinen, braucht man mindestens 11, besser 12 Bit. Am 
einfachsten macht man dafür eine Tabelle und schlägt auch die nur einmal 
im PWM-Zyklus nach, wenn man die Bitmasken neu berechnet.


XL

von Robin (Gast)


Lesenswert?

Tiny10Nutzer schrieb:
> Wie ich die
> durchschnittlich höhere Frequenz berechnen soll, da habe ich aber grade
> keinen Plan von.

Die Frequenz bei BAM richtet sich nach deiner geschwindigkeit der ISR 
und deiner Auflösung des PWMs.

lässt sich dann etwa so berechnen:

F_CPU/(takte_ISR * Auflösung als Zahlenwert)

Also als Beispiel:

1200000 / (30 * 250) = 160 Hz

Durch Optimierungen bei den schnellen Frequenzen lässt sich dann aber 
noch einiges mehr rausholen, denn eigentlich sind die ersten 3 Ausgaben 
nur kritisch, danach sind die Zeiten der Ausgaben weit genug 
auseinander.

Wenn man die beiden Compare Register des Timers verwendet. Könnte man 
die Kleinen Werte von einer ISR ausgeben lassen und in der Anderen die 
Restlichen.

Bei der getrennten Ausgabe hast du dann viel mehr Zeit zur Verfügung, 
bis deine ISR Abgearbeitet sein muss.
So ist wenn die ersten 3 Werte getrennt ausgegeben werden, eine 8 fach 
höhere PWM Frequenz möglich.

von Peter D. (peda)


Lesenswert?

Tiny10Nutzer schrieb:
> Ich arbeite jetzt mit 1,2 MHz

Gibt es einen Grund, warum Du nicht 9,6MHz benutzen möchtest?
Du mußt nur den Vorteiler auf 1 setzen (als Fusebit oder im Programm).

von tiny10nutzer (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Gibt es einen Grund, warum Du nicht 9,6MHz benutzen möchtest?

Antwort: Faulheit...

Ich habe jetzt hier mal was im Konzept zusammengeschustert:
1
timer0_match_bit-128:
2
 out PortB,output128 ; Portausgabe für Bit 128
3
 ldi tmp,0b01000000  ; CompareMatch auf 64 setzen
4
 out PortB,tmp       ; 64 auf OCR0A schreiben
5
 nop ; ich muss hier den nächsten Interrupt von .org 0x0006 auf
6
 nop ; timer0_match_bit-64 lenken
7
 reti
8
timer0_match_bit-64:
9
 out PortB,output64  ; Portausgabe für Bit 64
10
 ldi tmp,0b00100000  ; CompareMatch auf 32 setzen
11
 out PortB,tmp       ; 32 auf OCR0A schreiben
12
 nop ; ich muss hier den nächsten Interrupt von .org 0x0006 auf
13
 nop ; timer0_match_bit-32 lenken
14
 reti
15
timer0_match_bit-32:
16
 out PortB,output32  ; Portausgabe für Bit 32
17
 ldi tmp,0b00010000  ; CompareMatch auf 16 setzen
18
 out PortB,tmp       ; 16 auf OCR0A schreiben
19
 nop ; ich muss hier den nächsten Interrupt von .org 0x0006 auf
20
 nop ; timer0_match_bit-16-8-4-2-1+bit_sorter lenken
21
 reti
22
23
timer0_match_bit-16-8-4-2-1+bit_sorter:
24
  out PortB,output16  ; Portausgabe für Bit 16
25
 nop
26
 nop
27
 nop
28
 nop
29
 nop
30
 nop ; ich muss hier den nächsten Interrupt von .org 0x0006 auf
31
 nop ; timer0_match_bit-128 lenken
32
  out PortB,output8  ; Portausgabe für Bit 8
33
 ldi tmp,0b10000000 ; Compare_Match auf 128 setzen
34
 out OCR0A,tmp      ; 128 auf OCR0A schreiben
35
 ldi tmp,0          ; tmp löschen
36
  out PortB,output4  ; Portausgabe für Bit 4
37
 out TCNT0,tmp      ; Timerzähler auf 0 festlegen - Verzögerung sollte korrekt sein
38
  out PortB,output2  ; Portausgabe für Bit 2
39
  out PortB,output1  ; Portausgabe für Bit 1
40
bit_sorter:         ; nur für die lesbarkeit
41
 nop ; hier stehen die Sortierbefehle
42
 nop ; hier stehen die Sortierbefehle
43
 reti

Ich finde Systemtakt durch 256 (8-Bit) hört sich als Frequenz doch echt 
gut an. Aber: Wie ändere ich im laufenden Programm den Sprungvektor in 
.org 0x0006?

von why_me (Gast)


Lesenswert?

out PortB,tmp       ; 64 auf OCR0A schreiben

da stimmt was nicht ;)


tiny10nutzer schrieb:
> Aber: Wie ändere ich im laufenden Programm den Sprungvektor in
> .org 0x0006?

Warum machst du das nicht in der selben ISR und lädst die Werte über 
einen Pointer aus dem RAM?
1
Variablen im RAM
2
Z_Pointer: .Byte 2
3
PWM: .Byte 8
4
5
OCCRA_ISR:
6
push Zh                 // Verwendete Register Sichern
7
push ZL
8
push R16
9
in R16, SREG
10
Push R16
11
12
lds Zl, Z_Pointer       // Pointer auf PWM Werte Laden
13
lds Zh, Z_Pointer+1
14
15
ld R16,Z+               // Aktuellen PWM Wert laden und ausgeben
16
out PortB, R16
17
18
sts Zl, Z_Pointer       // Veränderten Pointer sichern
19
sts Zh, Z_Pointer+1
20
                      
21
in R16, OCCRA           // OCCRA*2
22
lsl R16
23
out OCCRA, R16
24
25
pop R16                 // Register wieder Herstellen
26
out SREG, R16
27
pop R16
28
pop Zl
29
pop Zh
30
reti

Im Timer_Overflow interrupt wird dann OCCRA und die Pointer Variable im 
RAM immer wieder zurückgesetzt. Eine Flag gesetzt, die durch die dann im 
Hauptprogramm die Nächsten PWM Werte verarbeitet werden.

Optimieren kann man das ganze aber noch, wie weiter oben geschrieben, 
OCCRB für die kleinen Werte nehmen, oder gleich im Timer Overflow 
Interrupt.

von tiny10nutzer (Gast)


Lesenswert?

@ why_me

Dein Beispiel ist nicht schlecht, braucht aber vom Interrupt bis zum 
"out PortB" satte 18 Zyklen. Das währe dann auch schon das KO-Kriterium 
für mich. Aber dein Ansatz für die kleinen Bits OCR0A und für die hohen 
Bits OCR0B zu verwenden gefällt mir. Wenn ich die Zyklen geschickt 
ausmanövriere, kann ich ein, zwei Register einsparen, indem ich den 
Sortiervorgang dazwischenschiebe.

Der Sortiervorgang selber würde grundlegend so aussehen:
1
ror Channel0Value
2
ror Output128
3
ror Channel1Value
4
ror Output128
5
ror Channel2Value
6
ror Output128
7
ror Channel3Value
8
ror Output128
9
ror Channel4Value
10
ror Output128
11
ror Channel5Value
12
ror Output128
13
ror Output128
14
ror Output128
15
16
ror Channel0Value
17
ror Output64
18
ror Channel1Value
19
ror Output64
20
ror Channel2Value
21
ror Output64
22
ror Channel3Value
23
ror Output64
24
ror Channel4Value
25
ror Output64
26
ror Channel5Value
27
ror Output64
28
ror Output64
29
ror Output64
30
31
ror Channel0Value
32
ror Output32
33
ror Channel1Value
34
ror Output32
35
ror Channel2Value
36
ror Output32
37
ror Channel3Value
38
ror Output32
39
ror Channel4Value
40
ror Output32
41
ror Channel5Value
42
ror Output32
43
ror Output32
44
ror Output32
45
46
ror Channel0Value
47
ror Output16
48
ror Channel1Value
49
ror Output16
50
ror Channel2Value
51
ror Output16
52
ror Channel3Value
53
ror Output16
54
ror Channel4Value
55
ror Output16
56
ror Channel5Value
57
ror Output16
58
ror Output16
59
ror Output16
60
61
ror Channel0Value
62
ror Output8
63
ror Channel1Value
64
ror Output8
65
ror Channel2Value
66
ror Output8
67
ror Channel3Value
68
ror Output8
69
ror Channel4Value
70
ror Output8
71
ror Channel5Value
72
ror Output8
73
ror Output8
74
ror Output8
75
76
ror Channel0Value
77
ror Output4
78
ror Channel1Value
79
ror Output4
80
ror Channel2Value
81
ror Output4
82
ror Channel3Value
83
ror Output4
84
ror Channel4Value
85
ror Output4
86
ror Channel5Value
87
ror Output4
88
ror Output4
89
ror Output4
90
91
ror Channel0Value
92
ror Output2
93
ror Channel1Value
94
ror Output2
95
ror Channel2Value
96
ror Output2
97
ror Channel3Value
98
ror Output2
99
ror Channel4Value
100
ror Output2
101
ror Channel5Value
102
ror Output2
103
ror Output2
104
ror Output2
105
106
ror Channel0Value
107
ror Output1
108
ror Channel1Value
109
ror Output1
110
ror Channel2Value
111
ror Output1
112
ror Channel3Value
113
ror Output1
114
ror Channel4Value
115
ror Output1
116
ror Channel5Value
117
ror Output1
118
ror Output1
119
ror Output1
Abgesehen davon, dass ich den Vorgang statt 8 Mal nur 1 Mal in einem 
Unterprogramm durchführen kann, gibt es möglichkeiten das sinnvoller, 
schneller und kürzer zu erledigen?

von why_me (Gast)


Lesenswert?

Be deinem Code fehlt ja auch noch etwas, pushs, pops, dann noch die 
unterschiedlichen Sprünge, die wohl am schnellsten über eine Programm 
Counter Manipulation.

Werden auch nochmal 10 Takte sein.

Schneller wird man die Verarbeitung der Werte nicht hinbekommen. Evtl in 
die andere Richtung schieben und die letzten beiden Schiebe Operationen 
weglassen, PB7 und 8 gibts beim Tiny eh nicht.

Und Kürze gehts nur über eine schleife, aber das dauert dann auch wieder 
etwas länger.

von tiny10nutzer (Gast)


Lesenswert?

Na ich richtung POP, PUSH und Sprünge habe ich jetzt nicht gedacht.

Ich habe mein Konzept jetzt mal in diese Richtung gelenkt:
1
Reset_Order:    ; ergänzende Befehle
2
 ldi tmp,128     ; Verzögerung für timer0_match_bit-128-64
3
 out OCR0A,tmp  ; ORC0B ist für timer0_match_bit-32-16-8-4-2-1+bit_sorter zuständig
4
 ldi tmp,32     ; Verzögerung für timer0_match_bit-32-16-8-4-2-1+bit_sorter
5
 out OCR0B,tmp  ; ORC0B ist für timer0_match_bit-32-16-8-4-2-1+bit_sorter zuständig
6
7
8
timer0_match_bit-128-64:
9
 out PortB,output_tmp4 ; Portausgabe für Schieberegister output_tmp4
10
11
 in OCR0A,tmp     ; OCR0A lesen
12
 cpi tmp,64       ; wenn OCR0A auf 64 steht
13
 breq PC+3        ; die folgenden 2 Befehle überspringen
14
 ldi tmp, ??      ; Wert für ORC0A aus und OCR0B an
15
 out ?? , tmp     ; in das zuständige Port schreiben
16
 ror tmp          ; OCR0A halbieren
17
 out OCR0A,tmp    ; neuen Wert auf OCR0A schreiben
18
19
 ; Sorter für Schieberegister output_tmp1, output_tmp2 und output_tmp3
20
 mov output_tmp3,output_tmp4 ; Schieberegister output_tmp3 zu output_tmp4 verschieben 
21
 mov output_tmp2,output_tmp3 ; Schieberegister output_tmp2 zu output_tmp3 verschieben 
22
 mov output_tmp1,output_tmp2 ; Schieberegister output_tmp1 zu output_tmp2 verschieben 
23
24
 ; jetzt wird output_tmp1 neu erzeugt
25
 rol Channel0Value
26
 rol Output_tmp1 ; bit sort
27
 rol Channel1Value
28
 rol Output_tmp1 ; bit sort
29
 rol Channel2Value
30
 rol Output_tmp1 ; bit sort
31
 rol Channel3Value
32
 rol Output_tmp1 ; bit sort
33
 rol Channel4Value
34
 rol Output_tmp1 ; bit sort
35
 rol Channel5Value
36
 rol Output_tmp1 ; bit sort
37
38
 reti
39
40
timer0_match_bit-32-16-8-4-2-1+bit_sorter:
41
  out PortB,output_tmp4  ; Portausgabe für Bit 32
42
 nop 
43
 nop
44
 rol Channel0Value
45
 rol Output_tmp4 ; bit-2 sort
46
 rol Channel1Value
47
 rol Output_tmp4 ; bit-2 sort
48
 rol Channel2Value
49
 rol Output_tmp4 ; bit-2 sort
50
 rol Channel3Value
51
 rol Output_tmp4 ; bit-2 sort
52
 rol Channel4Value
53
 rol Output_tmp4 ; bit-2 sort
54
 rol Channel5Value
55
 rol Output_tmp4 ; bit-2 sort
56
 rol Channel0Value
57
  out PortB,output_tmp3  ; Portausgabe für Bit 16
58
 rol Output_tmp3 ; bit-1 sort in allgemeinregister output
59
 rol Channel1Value
60
 rol Output_tmp3 ; bit-1 sort in allgemeinregister output
61
 rol Channel2Value
62
 rol Output_tmp3 ; bit-1 sort in allgemeinregister output
63
 rol Channel3Value
64
 rol Output_tmp3 ; bit-1 sort in allgemeinregister output
65
  out PortB,output_tmp2  ; Portausgabe für Bit 8
66
 rol Channel4Value
67
 rol Output_tmp3 ; bit-1 sort in allgemeinregister output
68
 rol Channel5Value
69
  out PortB,output_tmp1  ; Portausgabe für Bit 4
70
 rol Output_tmp3 ; bit-1 sort in allgemeinregister output
71
  out PortB,output_tmp4  ; Portausgabe für Bit 2
72
  out PortB,output_tmp3  ; Portausgabe für Bit 1
73
 ldi tmp,3          ; tmp auf 3 - der Timer währe vor 3 Zyklen auf 0 gewesen
74
 out TCNT0,tmp      ; Timerzähler auf 3 festlegen - Verzögerung sollte korrekt sein
75
76
bit_sorter:         ; nur für die lesbarkeit
77
78
 rol Channel0Value
79
 rol Output_tmp4 ; bit-128 sort
80
 rol Channel1Value
81
 rol Output_tmp4 ; bit-128 sort
82
 rol Channel2Value
83
 rol Output_tmp4 ; bit-128 sort
84
 rol Channel3Value
85
 rol Output_tmp4 ; bit-128 sort
86
 rol Channel4Value
87
 rol Output_tmp4 ; bit-128 sort
88
 rol Channel5Value
89
 rol Output_tmp4 ; bit-128 sort 
90
91
 rol Channel0Value
92
 rol Output_tmp3 ; bit-64 sort
93
 rol Channel1Value
94
 rol Output_tmp3 ; bit-64 sort
95
 rol Channel2Value
96
 rol Output_tmp3 ; bit-64 sort
97
 rol Channel3Value
98
 rol Output_tmp3 ; bit-64 sort
99
 rol Channel4Value
100
 rol Output_tmp3 ; bit-64 sort
101
 rol Channel5Value
102
 rol Output_tmp3 ; bit-64 sort 
103
104
 rol Channel0Value
105
 rol Output_tmp2 ; bit-32 sort
106
 rol Channel1Value
107
 rol Output_tmp2 ; bit-32 sort
108
 rol Channel2Value
109
 rol Output_tmp2 ; bit-32 sort
110
 rol Channel3Value
111
 rol Output_tmp2 ; bit-32 sort
112
 rol Channel4Value
113
 rol Output_tmp2 ; bit-32 sort
114
 rol Channel5Value
115
 rol Output_tmp2 ; bit-32 sort 
116
117
 rol Channel0Value
118
 rol Output_tmp1 ; bit-16 sort
119
 rol Channel1Value
120
 rol Output_tmp1 ; bit-16 sort
121
 rol Channel2Value
122
 rol Output_tmp1 ; bit-16 sort
123
 rol Channel3Value
124
 rol Output_tmp1 ; bit-16 sort
125
 rol Channel4Value
126
 rol Output_tmp1 ; bit-16 sort
127
 rol Channel5Value
128
 rol Output_tmp1 ; bit-16 sort 
129
130
 ldi tmp,128 ; Compare_Match auf 128 setzen
131
 out OCR0A,tmp      ; 128 auf OCR0B schreiben
132
 ldi tmp, ??        ; Wert für ORC0B aus und OCR0A an
133
 out ?? , tmp       ; in das zuständige Port schreiben
134
135
 reti

Dürften noch der eine oder andere Schreibfehler drin sein. Aber ich habe 
hier statt 8 Outputregister nur 4 verbraten, indem ich sie einfach 
verschoben habe. Registerzahl hier 5+4+1=10 bleiben noch 6 die ich 
weiter verwenden kann.

Sieht noch wer Optimierungsmöglichkeiten?

von tiny10nutzer (Gast)


Lesenswert?

Korrektur:

Channel?Value = 6 Register
Output?? = 4 Regiser
tmp = 1 Register

bleiben noch 5

von Ein (Gast)


Lesenswert?

tiny10nutzer schrieb:
> Der Sortiervorgang selber würde grundlegend so aussehen:ror
> Channel0Value

tiny10nutzer schrieb:
> Ich habe mein Konzept jetzt mal in diese Richtung gelenkt:Reset_Order:
> ; ergänzende Befehle

Bring's mal auf den Punkt oder verwende die Anhang-Funktion. Sonst 
bekommt man noch wunde Finger vom Scrollen.

von tiny10nutzer (Gast)


Lesenswert?

Es gibt Leute die halte die Taste "Pflei-Runter" fest. Außerdem ist 
dieser Code das Hauptthema grade.

von Peter D. (peda)


Angehängte Dateien:

Lesenswert?

Meinst Du, da sieht noch jemand durch?
Sende solche Monster wenigstens als Anhang.

Hier mal meine Routine:
1
        ISR     OC0Aaddr
2
        ldd     iwr0, Y+(pwm_time - pwm_data)
3
        out     OCR0A, iwr0                     ;compare time
4
        ld      iwr0, Y+
5
        out     PORTD, iwr0                     ;output pattern
6
        ldi     iwr0, low(pwm_data_end)
7
        cpse    yl, iwr0
8
        reti
9
        ldi     yl, low(pwm_data)
10
        ldi     yh, high(pwm_data)
11
        reti

Ein Bit habe ich auf 24 Zyklen festgelegt (3 * Vorteiler 8). Der 
Interrupt braucht 18 Zyklen, da ist also noch Luft für ein RET (4 
Zyklen) im Main.
Die letzte Bitzeit 384 paßt dann nicht mehr in 8 Bit.  Macht aber 
nichts, ich habe sie einfach auf 2 Interrupts aufgeteilt.
Im Anhang ein kleines Testprogramm.

Ich hab schon ewig nichts mehr in Assembler gemacht. Ich hab daher 
erstmal einige Macros geschrieben, damit der Code nicht gar zu 
unleserlich wird.
C ist doch wesentlich bequemer, die Bitzeit muß man dann aber etwas 
erhöhen.

von Jobst M. (jobstens-de)


Lesenswert?

Als Alternative zur PWM funktioniert PDM auch ganz gut.

Hier ein Beispiel für 8 D/A-Kanäle.

R16 - R23 = Zu wandelndelde Werte
R8 - R15 = Merkregister
R7 = Zwischenregister
1
TimerIRQ:
2
  ADD     R8, R16
3
  ROL     R7
4
  ADD     R9, R17
5
  ROL     R7
6
  ADD     R10, R18
7
  ROL     R7
8
  ADD     R11, R19
9
  ROL     R7
10
  ADD     R12, R20
11
  ROL     R7
12
  ADD     R13, R21
13
  ROL     R7
14
  ADD     R14, R22
15
  ROL     R7
16
  ADD     R15, R23
17
  ROL     R7
18
  OUT     PORTB, R7 ; Oder welcher Port auch immer ...
19
RETI


Gruß

Jobst

von der alte Hanns (Gast)


Lesenswert?

an Peter Dannegger

Sehe ich das richtig?
Ausgehend von Ihrem Programm könnte man auf 2*8 = 16 Takte Bitzeit 
kommen, wenn man statt Interrupt-handling ein Polling (kennt jemand 
griffige deutsche Ausdrücke?) durchführt, evtl. keine Schleife, sondern 
achtmal hintereinandergeschrieben, und allfällige sonstige 
Verarbeitungsblöcke in die größte, ggfs. noch zweitgrößte Zeitscheibe 
verlegt.
(Damit würde auch die größte Zeitscheibe gerade noch in OCR0A passen)

von der alte Hanns (Gast)


Lesenswert?

Mich deucht, es sollten sogar 1*8 möglich sein!  ?

von Falk B. (falk)


Lesenswert?

@der alte Hanns (Gast)

>Ausgehend von Ihrem Programm könnte man auf 2*8 = 16 Takte Bitzeit
>kommen, wenn man statt Interrupt-handling ein Polling (kennt jemand
>griffige deutsche Ausdrücke?)

Interrupt ist ein Fremdwort, die deutsche Bezeichung 
Unterbrechungsanforderung ist zu lang und zu staubig, als das es jemand 
verwenden will.

Abfrage ist eine brauchbare deutsche Bezeichnung für Polling.

von Sam .. (sam1994)


Lesenswert?

der alte Hanns schrieb:
> Mich deucht, es sollten sogar 1*8 möglich sein!

Bitlänge 1 Takt ist möglich, indem die kritischen Bitzeiten in einen 
Interrupt legt (hab ich schon 2 mal geschrieben).

: Bearbeitet durch User
von der alte Hanns (Gast)


Lesenswert?

an Falk Brunner
Danke. Bleibt es also bei den Anglizismen, hier wohl besser als 
Fachterminologie bezeichnet.

an Sam
Wie sieht denn Ihr Beispielprogramm aus, besonders die Jitterkorrektur 
(schon wieder, Falk Brunner) würde mich interessieren.

von der alte Hanns (Gast)


Lesenswert?

Ha - könnte man 'Unschärfekorrektur' dafür schreiben?

an Sam
Liegt nicht genau hier das Problem? Ohne vollständige Korrektur hat man 
doch keine 8-bit-Auflösung mehr, oder verstehe ich das falsch?

von Falk B. (falk)


Lesenswert?

@ der alte Hanns (Gast)

>Ha - könnte man 'Unschärfekorrektur' dafür schreiben?

Nein, das wäre zu unschaft ;-)

Jitter würde ich eher mit Zittern übersetzen, das ist es ja irgendwie 
auch. Ein zeitliches Zittern eines Signals.

von der alte Hanns (Gast)


Lesenswert?

> Nein, das wäre zu unschaft

Sie irritieren mich, bin ich nicht mal mehr der deutschen Sprache 
mächtig?

von der alte Hanns (Gast)


Lesenswert?

Aber Spaß beiseite, Zittern bei einer Modulation heißt doch eigentlich 
Unschärfe, d.h. reduzierte Auflösung?

von Sam .. (sam1994)


Lesenswert?

Hier ist meine Variante um den Jitter zu entfernen. Ich hab auch schon 
welche mit ijmp gesehen, bei denen in eine nop Tabelle gesprungen wird. 
Bei größeren auszugleichenden Latenzen braucht das aber unnötig viel 
Speicherplatz.
1
sync:  
2
  in sync_temp, _SFR_IO_ADDR(TCNT0)
3
  subi sync_temp, lo8(INT_LATENCY + CYCLES_TO_SYNC + 3 + 6 + 1)  // 3: sync call; 6: avr isr call
4
5
  bst sync_temp, 0
6
  brtc .+0            // 1 cycles delay
7
8
  bst sync_temp, 1
9
  brtc .+0            // 2 cycles delay
10
  brtc .+0  
11
0:
12
  nop
13
  subi sync_temp, -4        // 4 cycles delay routine
14
  brmi 0b
15
16
  ret

INT_LATENCY: maximale Latenz des Interrupts (mindestens 4 Zyklen)
CYCLES_TO_SYNC: Takte bis sync vom Interrupt aufgerufen wird.

Die Routine sorgt dafür das beim Rücksprung das TCNT0 Register immer den 
gleichen Wert hat (außer man überschreitet die maximale Latenz).

: Bearbeitet durch User
von der alte Hanns (Gast)


Lesenswert?

So weit, so klar. Unklar ist mir aber noch das genaue Zusammenspiel mit 
dem restlichen Programm, vielleicht bin ich weniger versiert als Sie. 
Wenn Sie also Ihr komplettes Programm hier einstellen könnten, so wie 
Herr Dannegger?
Dann könnte man sich das Ganze auch in praxi anschauen, d.h. einfach 
eine Überprüfung per Voltmeter und Oszilloskop vornehmen.

von der alte Hanns (Gast)


Lesenswert?

Bitte verstehen Sie mich recht, ich will Sie keineswegs ärgern, nur -
ich habe schon einige 'Trivialitäten' ("kein Problem, das lösen wir 
später") gesehen, die dann als k.o.-Kriterium - nun, das Projekt eben 
k.o. schlugen, zumindest in der ursprünglich geplanten Form.
Und eine 1-Takt-Auflösung erstaunt mich schon (und würde mich freuen, 
wenn es möglich ist).

von Peter D. (peda)


Lesenswert?

Sam .. schrieb:
> Bitlänge 1 Takt ist möglich, indem die kritischen Bitzeiten in einen
> Interrupt legt (hab ich schon 2 mal geschrieben).

Ja, aber nicht gezeigt.

Wird letzlich darauf hinauslaufen, daß die ersten 128 Bitzeiten komplett 
im Interrupt laufen und nur das höchstwertige Bit dem Main etwa CPU-Zeit 
abgibt. Im Prinzip sähe das so aus:
1
        ISR     OC0Aaddr
2
        ld      iwr0, Y+
3
        ld      iwr1, Y+
4
        ld      iwr2, Y+
5
        out     PORTD, iwr0
6
        out     PORTD, iwr1             ;1 cycle
7
        nop
8
        out     PORTD, iwr2             ;2 cycle
9
        ld      iwr0, Y+
10
        nop
11
        out     PORTD, iwr0             ;4 cycle
12
        ld      iwr0, Y+
13
        rjmp    pc+1
14
        rjmp    pc+1
15
        nop
16
        out     PORTD, iwr0             ;8 cycle
usw.
Für die Latenzkompensation gabs auch mal ne App-Note.

Interessant wäre aber die Frage, wozu überhaupt die PWM schneller als 
1,3kHz machen?
Selbst bei Erweiterung auf 10Bit sinds immer noch 325Hz.

von der alte Hanns (Gast)


Lesenswert?

> Interessant wäre aber die Frage, wozu überhaupt

Erkenntnisgewinn, vielleicht kann man es mal für etwas anderes 
gebrauchen (ich vielleicht nicht mehr, aber die Jüngeren).

von Yalu X. (yalu) (Moderator)


Lesenswert?

Peter Dannegger schrieb:
> Interessant wäre aber die Frage, wozu überhaupt die PWM schneller als
> 1,3kHz machen?

Motoren pfeifen bei 1,3kHz immer so herzerbärmlich :)

von der alte Hanns (Gast)


Lesenswert?

> und nur das höchstwertige Bit dem Main etwa CPU-Zeit abgibt.
Okay, wie schon beschrieben. Aber ist die Synchronisation mit dem Timer 
auf 1 Takt genau möglich? Ich habe da zu wenig Erfahrung und Gespür.

> Für die Latenzkompensation gabs auch mal ne App-Note.
Wo, bzw. von wem?

> Motoren pfeifen bei 1,3kHz immer so herzerbärmlich :)
Vielleicht stören sich irgendwelche Tiere an 325 Hz-Licht?

von Peter D. (peda)


Lesenswert?

der alte Hanns schrieb:
>> Für die Latenzkompensation gabs auch mal ne App-Note.
> Wo, bzw. von wem?

Ich kanns nicht mehr finden.
Es könnte so ähnlich gewesen sein:
1
        ISR     OVF0addr
2
        in      iwr0,   TCNT0
3
        sbrs    iwr0, 0
4
        rjmp    pc+1
5
        sbrc    iwr0, 1
6
        lpm     iwr0, z

Beim Eintritt ist der Timer 6, 7, 8 oder 9.
6 ergibt sich aus dem Interruptaufruf (4) + RJMP (2) zum Handler.

: Bearbeitet durch User
von der alte Hanns (Gast)


Lesenswert?

Danke, Herr Dannegger, so allmählich bekomme ich eine vage Vorstellung.

Allerdings hatte ich auf etwas Fertiges von Sam gehofft, denn
> Ich habe vor ein paar Tagen es theoretisch getestet. Es ist auf jeden Fall
> möglich.

Aber mit etwas Glück habe ich ab morgen Mittag Zeit, dann setze ich mich 
dran, denn es interessiert mich durchaus, auch wenn ich mit dieser 
ganzen LED-Geschichte ("Zaubern von Showeffekten", so hieß es vor kurzem 
andernorts) sonst nicht viel anfangen kann.

von Peter D. (peda)


Angehängte Dateien:

Lesenswert?

Hier mal jetzt die 8MHz / 256 = 31kHz PWM:
1
        ISR     OVF0addr
2
        in      iwr0,   TCNT0           ;compensate latency
3
        sbrs    iwr0, 0
4
        rjmp    pc+1                    ;add 1 cycle
5
        sbrc    iwr0, 1
6
        lpm     iwr0, z                 ;add 2 cycle
7
        ld      iwr0, Y
8
        ldd     iwr1, Y+1
9
        ldd     iwr2, Y+2
10
        out     PORTD, iwr0
11
        out     PORTD, iwr1             ;1 cycle
12
        in      isreg, SREG             ;delay macro change SREG !
13
        out     PORTD, iwr2             ;2 cycle
14
        nop
15
        ldd     iwr0, Y+3
16
        out     PORTD, iwr0             ;4 cycle
17
        delay   iwr0, 5
18
        ldd     iwr0, Y+4
19
        out     PORTD, iwr0             ;8 cycle
20
        delay   iwr0, 13
21
        ldd     iwr0, Y+5
22
        out     PORTD, iwr0             ;16 cycle
23
        delay   iwr0, 29
24
        ldd     iwr0, Y+6
25
        out     PORTD, iwr0             ;32 cycle
26
        delay   iwr0, 60
27
        out     SREG, isreg             ;restore SREG
28
        ldd     iwr0, Y+7
29
        out     PORTD, iwr0             ;64 cycle
30
        reti

Der Interupt dauert 152 Zyklen, d.h. belegt 60% CPU-Zeit.
Das Main läuft dann quasi mit 3MHz.

Ein Flackern ist auch beim schnellsten Schütteln des STK500 nicht mehr 
zu erkennen.

Der komplette Code ist im Anhang.

von Peter D. (peda)


Lesenswert?

Und hier noch der Interrupt Handler für 10Bit PWM mit 7,8kHz:
1
        ISR     OVF0addr
2
        in      iwr0,   TCNT0           ;TCNT0 = 6, 7, 8 or 9
3
        sbrs    iwr0, 0                 ;compensate latency
4
        rjmp    pc+1                    ;add 1 cycle
5
        sbrc    iwr0, 1
6
        lpm     iwr0, z                 ;add 2 cycle
7
        dec     ovf_cnt
8
        brne    _isrt0_1
9
        ld      iwr0, Y
10
        ldd     ilwr0, Y+1
11
        ldd     ilwr1, Y+2
12
        out     PORTD, iwr0
13
        out     PORTD, ilwr0            ;1 cycle
14
        nop
15
        out     PORTD, ilwr1            ;2 cycle
16
        in      isreg, SREG             ;delay macro change SREG !
17
        ldd     iwr0, Y+3
18
        out     PORTD, iwr0             ;4 cycle
19
        delay   iwr0, 5
20
        ldd     iwr0, Y+4
21
        out     PORTD, iwr0             ;8 cycle
22
        delay   iwr0, 13
23
        ldd     iwr0, Y+5
24
        out     PORTD, iwr0             ;16 cycle
25
        delay   iwr0, 29
26
        ldd     iwr0, Y+6
27
        out     PORTD, iwr0             ;32 cycle
28
        delay   iwr0, 59
29
        out     SREG, isreg             ;restore SREG
30
        ldi     ovf_cnt, 4              ;4 * 256 cycle
31
        ldd     iwr0, Y+7
32
        out     PORTD, iwr0             ;64 cycle
33
        reti
34
_isrt0_1:
35
        cpi     ovf_cnt, 2
36
        breq    _isrt0_2
37
        brcs    _isrt0_3
38
        ldd     iwr0, Y+8
39
        out     PORTD, iwr0             ;256 cycle
40
        reti
41
_isrt0_2:
42
        ldd     iwr0, Y+9
43
        out     PORTD, iwr0             ;512 cycle
44
_isrt0_3:
45
        reti

von Sam .. (sam1994)


Angehängte Dateien:

Lesenswert?

Peter Dannegger schrieb:
> Interessant wäre aber die Frage, wozu überhaupt die PWM schneller als
> 1,3kHz machen?
> Selbst bei Erweiterung auf 10Bit sinds immer noch 325Hz.

Für Led Fading sollte man mindestens 10-12 Bit zu Verfügung haben. Das 
reicht dann auch für die nicht lineare Kennlinie des Auges, sowie für 
Dot-Correction. Mehr schadet hier nicht. Wenn man dann noch eine Matrix 
ansteuert braucht man schon ~1kHz PWM Frequenz.

Im Anhang 8 Kanal Bam für 9-16 Bit mit CPU-Frequenz.

der alte Hanns schrieb:
> Allerdings hatte ich auf etwas Fertiges von Sam gehofft, denn
>> Ich habe vor ein paar Tagen es theoretisch getestet. Es ist auf jeden Fall
>> möglich.

Das bezog sich auf phasenverschobene PWM. Ich habe dazu nur Code 
geschrieben, der die berechnet wie die PWM-Signale verschoben werden 
müssen damit man es mit einer bestimmten Interrupt-Latenz schafft. Der 
Code braucht auf dem Avr ~50k Zyklen, hat aber noch 
Optimierungspotential (schätzungsweise Faktor ~3). Wenn da Interesse 
besteht kann ich das auch hochladen.

von der alte Hanns (Gast)


Lesenswert?

Erstmal herzlichen Dank an die Autoren (beide offenbar Nachtarbeiter)!

Wann ich zu Sams Code komme, weiß ich noch nicht.
Pedas Code läuft auf einem 644 auf Anhieb, prima! Bei einem Schnelltest 
sehe ich zwar ungleiche Sprünge bei 127-128-129, das mag aber an meinem 
Analogteil liegen; gegen Abend habe ich mehr Zeit.

von Peter D. (peda)


Lesenswert?

der alte Hanns schrieb:
> Bei einem Schnelltest
> sehe ich zwar ungleiche Sprünge bei 127-128-129

Naja, 8MHz Bitzeit sind 125ns kurz, da können sich Flankenzeiten 
durchaus auswirken.
Schalte dochmal den CPU-Teiler auf 1MHz oder 500kHz.

von der alte Hanns (Gast)


Lesenswert?

> gegen Abend habe ich mehr Zeit
April, April! (Ich hasse diese Terminabstimmungen im 10-Minuten-Takt, 
aber wir sind heutzutage ja so flexibel, flexibel bis zur 
Selbstverbiegung)

an Sam
Der Titel 'Assembler-Frage' suggerierte mir genau dieses; Ihr Programm 
ist in C, und in Sachen C fühle ich mich schlicht inkompetent. 
Vielleicht hat Herr Dannegger Zeit und Lust, sich Ihr Programm 
anzuschauen und ggfs. zu prüfen; es wäre auch etwas für c-hater (seien 
Sie allerdings schon vorab vor dessen Umgangsformen gewarnt).

an Peter Dannegger
Ich arbeite bereits mit 1 MHz, genauer, ich hatte nie umgestellt.
Mir scheint, als wäre Ihr macro 'delay' für die größte Zeitscheibe einen 
Takt zu lang.

Generell aber: feine Sache, ich bin wieder einmal erstaunt, was sich 
alles machen lässt.

DANKE !

von der alte Hanns (Gast)


Lesenswert?

Also die Sprungweite 127->128 ist bei mir doppelt so groß wie 128->129; 
aber am doch recht überschaubaren 'delay' liegt es wohl nicht.

von Peter D. (peda)


Lesenswert?

Du hast recht, das 8.Bit ist 129 lang.
Ist auch logisch, der Timer zählt ja 256 Zyklen.
Lösung: Clear on Compare bei 254

von Peter D. (peda)


Lesenswert?

Hallo Hanns,

hast Du das hingekriegt mit dem Compare Interrupt statt Overflow?
Im Simulator im AVRStudio kann man sich schön ansehen, wie lang die 
einzelnen Bits sind.

Ich bin grad an der 10Bit-PWM.
Allerdings wird mir das in Assembler langsam zu mühselig.
Ich werd daher nur die Interrupts in Assembler machen und die 
Mainroutinen bequem in C. Dann kann man auch schönere Lichteffekte 
machen (z.B. Knight Rider).
Also so ähnlich wie Sam.

Der Thread ist ja durch die riesen Listings leider total verbrannt, da 
guckt keiner mehr drauf.
Ich werd mal nen neuen aufmachen.

von der alte Hanns (Gast)


Lesenswert?

Ja, hat sofort problemlos geklappt, danke der Nachfrage.

Etwas schade finde ich, dass von Tiny10Nutzer so gar keine Reaktion mehr 
kommt. Aber ich habe einiges gelernt, und für Sie war es hoffentlich 
nicht nur
> mühselig
sondern auch mit etwas Spaß verbunden.

von Konrad S. (maybee)


Lesenswert?

Peter Dannegger schrieb:
> Der Thread ist ja durch die riesen Listings leider total verbrannt, da
> guckt keiner mehr drauf.

Ich guck' noch! Und ich lasse mich auch nicht abschrecken. ;-)
Ich bin mir sicher, dass da noch einige stille Mitleser sind. Danke für 
das spannende Thema.

von der alte Hanns (Gast)


Lesenswert?

Hallo Herr Dannegger,

jetzt komme ich doch noch wie die alte Fastnacht hinterher.

Stimmt Ihr Code
        sbrs    iwr0, 0                 ;compensate latency
        rjmp    pc+1                    ;add 1 cycle
        sbrc    iwr0, 1
        lpm     iwr0, z                 ;add 2 cycle
?
Ich hätte eher
        sbrs    iwr0, 1
erwartet.

von c-hater (Gast)


Lesenswert?

der alte Hanns schrieb:

> Stimmt Ihr Code
>         sbrs    iwr0, 0                 ;compensate latency
>         rjmp    pc+1                    ;add 1 cycle
>         sbrc    iwr0, 1
>         lpm     iwr0, z                 ;add 2 cycle
> ?

> Ich hätte eher
>         sbrs    iwr0, 1
> erwartet.


Das kommt darauf an, in welchem Moment des Zyklus man zuschlägt. Nur bei 
sehr speziellen Bedingungen, die Peter folgendermaßen formuliert hat:

>> Beim Eintritt ist der Timer 6, 7, 8 oder 9.

stimmt die Routine so wie gepostet. Das ist aber nur unter gewissen 
Randbedingungen der Fall, von denen die wichtigsten sind:

-keine konkurrierenden Interrupts
-keine cli/sei-Blöcke (>4 Takte) in main

Nur wenn mindestens diese Bedingungen gegeben sind, ist der gepostete 
Code die effizienteste denkbare Sync-Routine.

Die von Sam gepostete Variante ist deutlich flexibler (sie kann 
praktisch auf jede Maximallatenz allein durch die Änderung einer 
einzigen symbolischen Konstanten justiert werden), diese Flexibilität 
bezahlt man allerdings damit, daß die Sync-Routine selber für ihre 
Arbeit 6 Takte mehr verbraucht als die Minimalvariante.

Wenn man die beiden Routinen sinngemäß verschmilzt, kann man eine bauen, 
die zwar die Flexibilität von Sams Variante hat, aber nur 4 Takte mehr 
verbraucht als die von Peter, also gegenüber Sams Variante immerhin 2 
Takte spart. Allerdings braucht diese Bastard-Lösung wiederum ein 
Register mehr, lohnt also nur, wenn der eigentliche Job der ISR sowieso 
mindestens zwei Register erfordert oder ein "freies" Register zur 
Verfügung steht, ansonsten würde der Aufwand zum Sichern und 
Wiederherstellen des zusätzlichen Registers den den Gewinn in einen 
Verlust verwandeln.

von der alte Hanns (Gast)


Lesenswert?

Nun, ich bin habe ganz naiv die main-Schleife um einige rjmp pc+1 sowie 
rcall mit ret erweitert und in der ISR auf das erste out PORTD einen 
breakpoint gesetzt; dann war im Simulator ein jitter zu sehen.
Aber, und das war mein Fehlschluss: ich hatte vor drei Tagen die 
Interruptroutine direkt an die Adresse gesetzt (jetzt nicht mehr dran 
gedacht), in meiner Testversion fehlte also ein rjmp mit seinen 2 
Takten.

von der alte Hanns (Gast)


Lesenswert?

an C-Hasser

Und bevor Sie sich nun wieder mit einem 'Huch' über meine Arbeitsweise 
lustig machen, sag' ich's lieber gleich: ich halte mein Pseudonym in 
Ehren und arbeite mit einem entsprechenden AVR-Studio, das aber 
meckerte, dass '.org' in einem macro nicht zulässig sei. Also musste ich 
schieben.

von c-hater (Gast)


Lesenswert?

der alte Hanns schrieb:

> Interruptroutine direkt an die Adresse gesetzt

Du meinst sicher: direkt auf den Interruptvektor.

> in meiner Testversion fehlte also ein rjmp mit seinen 2
> Takten.

Jepp, und damit war die von Peter formulierte Bedingung halt schon nicht 
mehr gegeben und der Code paßte nicht mehr.

Bei der Sam-Lösung hätte man nur vom Wert des Symbols INT_LATENCY zwei 
abziehen müssen und alles wäre wieder in Butter gewesen. Das war, was 
ich mit Flexibilität meinte.

Es geht nämlich weiter: Wenn die ISR einfach nur weiter als +-2kWords 
vom Vektor entfernt ist und deswegen eines rjmp ein jmp zum Einsatz 
kommen muss, wird die einfache Routine auch wieder nicht funktionieren. 
Dann ändert sich die Latenz nämlich um +1 und diesmal muß zur Korrektur 
an der ersten sbrX-Instruktion geschraubt werden.

Und es endet spätestens, wenn ein AVR mit 22Bit-PC zum Einsatz kommt. 
Dann ist das Problem mit dieser Routine überhaupt nicht mehr lösbar, 
weil schon dann die variable Latenz >4 ist.

Und das ist sie auch dann, wenn konkurrierende Interrupts und/oder 
cli/sei-Konstrukte mit einer Länge von mehr als 4 Takten in's Spiel 
kommen, was in nahezu jeder praktisch nützlichen Anwendung der Fall sein 
wird.

Deswegen würde ich an deiner Stelle gleich auf die flexiblere 
Sam-Variante und/oder die Bastard-Lösung setzen. Insbesondere, wenn der 
Code als recyclebares Macro gedacht ist, wie ich dem zweiten Posting 
entnehmen konnte...

von der alte Hanns (Gast)


Lesenswert?

Vielen Dank für die ausführlichen Erläuterungen.
> war die von Peter formulierte Bedingung halt schon nicht mehr gegeben
Und diese steht auch noch direkt darüber! (Es war auch sonst kein guter 
Independence Day für mich) Entschuldigung, speziell an Peter Dannegger.

von Jobst M. (jobstens-de)


Lesenswert?

der alte Hanns schrieb:
> das aber
> meckerte, dass '.org' in einem macro nicht zulässig sei.

Das ist aber auch Versionsunabhängig völlig logisch ...


Gruß

Jobst

von Peter D. (peda)


Lesenswert?


von c-hater (Gast)


Lesenswert?

Jobst M. schrieb:

> der alte Hanns schrieb:
>> das aber
>> meckerte, dass '.org' in einem macro nicht zulässig sei.
>
> Das ist aber auch Versionsunabhängig völlig logisch ...

Wieso sollte das logisch sein?

Der AVR-Assembler2 jedenfalls schluckt auch .org in Macros (zumindest im 
.cseg). Und gelegentlich ist das sogar nützlich anwendbar.

von Jobst M. (jobstens-de)


Lesenswert?

c-hater schrieb:
> Wieso sollte das logisch sein?

Aus dem einfachen Grund, dass dann mehrfach .org mit der selben Adresse 
im Programmcode steht ...?

Oder habe ich da etwas völlig missverstanden?


Gruß

Jobst

von Carsten R. (kaffeetante)


Lesenswert?

Tiny10Nutzer schrieb:
> Der Sinn des
> Ganzen ist hier Offtoppic (und sagt mir nur, dass die Diskutierenden
> darüber nicht die richtige Antwort haben).

Das ist etwas bescheiden formuliert. Die korrekte Antwort zur Frage kam 
schon. Dir gefällt nur nicht wie sie lauet. Geht nicht so direkt. Darum 
ist die Frage nach den Sinn und Zweck (Kontext) um eine Alternative zu 
finden eben nicht Offtoppic.

Tiny10Nutzer schrieb:
> @ Yalu X
>
> Es muss das N-Flag sein. Ich brauche nach jeder Auswertung das N-Flag an
> einer bestimmten Position des PortB.
>
> @ der alte
>
> Der aktuelle Zustand des PortB ist nicht wichtig und darf aber auch
> nicht verändert werden. Der zeitliche Abstand mit dem auf PortB
> geschrieben wird muss zuverlässig konstant sein (weswegen ich nur einmal
> auf PortB schreibe). Hin- und Herspringen möchte ich aus dem gleichen
> Grund nicht. Das Ganze muss ohne Sprungbefehle gehen.

Das ist jetzt die wesentliche Information, die man auch noch genauer 
beschreiben könnte. Schön daß sie dann doch irgendwann ntsiegelt wird. 
Immer diese Salamitaktik. "Helft mir, aber ich sag euch nicht wobei." 
Wenn man etwas vergißt oder übersieht ist das eine Sache. Vorsätzlich 
auch auf Rückfrage hinterm Berg zu halten und dann noch über die "nicht 
passende Antwort" beklagen ist eine Andere...

: Bearbeitet durch User
von Axel S. (a-za-z0-9)


Lesenswert?

Jobst M. schrieb:
> c-hater schrieb:
>> Wieso sollte das logisch sein?
>
> Aus dem einfachen Grund, dass dann mehrfach .org mit der selben Adresse
> im Programmcode steht ...?
>
> Oder habe ich da etwas völlig missverstanden?

Du scheinst irrigerweise vorauszusetzen, daß das Makro

1. mehrfach aufgerufen wird und außerdem

2. die Adresse nach *.org* konstant ist


XL

von c-hater (Gast)


Lesenswert?

Jobst M. schrieb:

> Aus dem einfachen Grund, dass dann mehrfach .org mit der selben Adresse
> im Programmcode steht ...?
>
> Oder habe ich da etwas völlig missverstanden?

Ja.

1) Macros können ersetzbare Parameter haben.
2) In Macros können Berechnungen vorgenommen werden.
3) Hinter '.org' können nicht nur numerische Literale stehen.

Als Beispiel zwei Macros, welche einen Datenbereich an der 
nächstmöglichen 256Byte-Grenze ausrichten. Das ist z.B. nützlich, um 
schnelle Zugriffe auf diese Daten zu realisieren, bei denen nur der 
Low-Teil des Indexregisters angefaßt werden muss. Eine recht häufige 
Aufgabe bei der Performance-Optimierung.

;->@0: Label für den Beginn des ausgerichteten Speicherblocks
;Macro funktioniert im DSEG und im ESEG
.MACRO PAGEALIGN
unaligned:
  .SET aligned = ((unaligned+255)/256)*256
  .EQU @0 = aligned
  .ORG aligned
.ENDMACRO

;->@0: Label für den Beginn des ausgerichteten Speicherblocks
;Macro funktioniert im CSEG
.MACRO PM_PAGEALIGN
unaligned:
  .SET aligned = (((unaligned<<1)+255)/256)*256
  .EQU @0 = aligned
  .ORG aligned
.ENDMACRO


Als Beispiel für die Benutzung vielleicht mal eine Subroutine, die das 
nachste Sample aus einer Sinustabelle von 256 Byte Größe holt. (Würde 
man natürlich so niemals implementieren, aber soll ja nur ein Beispiel 
sein):

.CSEG

sintable:
.DB ...  ;insgesamt 256 Bytes

;<-[Z]: Zeiger auf erstes Sample
init_sampleptr:
  ldi ZL,Low(sintable<<1)
  ldi ZH,High(sintable<<1)
  ret

;->[Z]: Zeiger auf das nächste Sample
;<-R16: neues Sample
;  [Z]: Zeiger auf nächstes Sample für nächsten Aufruf
getsample:
  push tmpL                        ; 2
  push tmpH                        ; 2
  lpm R16,Z+                       ; 3
  ldi tmpL,Low((sintable<<1)+256)  ; 1
  ldi tmpH,High((sintable<<1)+256) ; 1
  cp ZL,tmpL                       ; 1
  cpc ZH,tmpH                      ; 1
  brcs gs_done                     ; 2 1
  subi ZH,1                        ;   1
gs_done:
  pop tmpH                         ; 2
  pop tmpL                         ; 2
  ret                              ;--
                                   ;17

Die Routine (ohne call-frame) braucht 17 Takte und davon tragen nur 
magere 3 zur Lösung der eigentlichen Aufgabe bei, der ganze Rest wird 
nur gebraucht, um den Pointer auf die Samples im Datenbereich zu halten.

Aligned man nun diese Daten, reduziert sich die Sache auf:

.CSEG

PM_PAGEALIGN sintable
.DB ...  ;insgesamt 256 Bytes

;<-[Z]: Zeiger auf erstes Sample
init_sampleptr:
  ldi ZL,Low(sintable)
  ldi ZH,High(sintable)
  ret

;->[Z]: Zeiger auf das nächste Sample
;<-R16: neues Sample
;  [Z]: Zeiger auf nächstes Sample für nächsten Aufruf
getsample:
  lpm R16,Z                      ; 3
  inc ZL                         ; 1
  ret                            ;--
                                 ; 4

Vorteil sichtbar? Nutzen des Macros klar?

von Jobst M. (jobstens-de)


Lesenswert?

Axel Schwenke schrieb:
> Du scheinst irrigerweise vorauszusetzen, daß das Makro
>
> 1. mehrfach aufgerufen wird und außerdem

Dann verdient es aber eigentlich auch nicht den Namen Makro.


> 2. die Adresse nach *.org* konstant ist

Okay, das ist eine Erklärung.


Gruß

Jobst

von Peter D. (peda)


Lesenswert?

Axel Schwenke schrieb:
> Du scheinst irrigerweise vorauszusetzen, daß das Makro
>
> 1. mehrfach aufgerufen wird und außerdem

Natürlich kann das Macro "ISR" mehrmals aufgerufen werden, nämlich so 
oft, wie der gewählte AVR Interruptvektoren hat.
Es versteht sich von selbst, daß jedem Vektor nur ein Handler zugewiesen 
werden kann.

von Tiny10Nutzer (Gast)


Lesenswert?

Okay...

Jetzt nach recht langer Zeit komme ich doch nochmal dazu dieses Thema 
aufzugreifen. Jetzt habe ich Urlaub und darf mal nicht in der 
Nachbarschaft einen Computer reparieren. Ich habe aber auch in wenigen 
Monaten die Prüfung für meine Umschulung.

Ich habe jetzt mal den Berg an Beiträgen seit dem 28.06. gelesen und 
kämpfe mich jetzt da durch die Codes zu verstehen. Jetzt nach einem Tag 
muss ich sagen, habe ich grade mal kapiert, dass der Code mit den 
Zugriffen auf das SRAM zu tun hat und das irgendwas addiert wird. Ich 
stell mich grade beim Verstehen aber recht dumm an.

Vielleicht kann mich mal wer beim Verstehen an die Hand nehmen...?

von der alte Hanns (Gast)


Lesenswert?

> seit dem 28.06. gelesen

Dann haben Sie sicher gesehen, dass peda einen Folge-Thread eröffnet 
hat.
Wenn wirklich BAM (bzw. BCM) zum Einsatz kommen soll, dann ist Sams Code 
sicher eine gute oder sogar die beste Wahl; vermutlich auch ohne 
tieferes Verständnis einsetzbar.

von Tiny10Nutzer (Gast)


Lesenswert?

Ich möchte es so sagen:

Für mein Auge sieht das hoch professionell und funktionstüchtig aus. Ich 
verstehe nur nicht was da im Programm überhaupt passiert. Ich würde es 
aber gern verstehen, da das BAM, PWM oder was sonst in Frage kommt nur 
ein Teil eines Mammut-Projektes ist. Da kommen noch Teile hinterher die 
weitaus schlimmer um zu setzen sind. Da möchte ich natürlich Konflikte 
vermeiden, bzw. sehen wo ich welche Möglichkeiten ausschöpfen kann.

von Tiny10Nutzer (Gast)


Lesenswert?

Ne... da kann ich so lange drauf schauen wie ich will. Dieses 
Mixed-C-ASM kann ich nicht lesen. Da bleibt mir grade nichts anderes 
übrig, als meine eigene kleine Geschichte weiterzuschreiben. Ich hätte 
jetzt im nächsten Schritt aber ohnehin auf das SRAM ausweichen müssen. 
Dies macht im weiteren Verlauf meines Projektes auch Sinn.

von Tiny10Nutzer (Gast)


Lesenswert?

Bugfix:

Ich lese mir den Komplettcode von Sam immer und immer wieder durch und 
verstehe langsam immer mehr. Ich finde schon, dass das eine kleine 
Meisterleistung ist. Trotzdem verstehe ich so die eine oder andere Zeile 
nicht so ganz. Z. B.:

brtc .+0 Ich habe herausgefunden, dass dies bedingter Sprung PC+1 
bedeutet. Gesehen habe ich das aber noch nie.
brmi 0b Soll wohl ein Bedingter Sprung PC-1 bedeuten.
0: 1: 2: Sollen wohl Labels sein. Habe ich so auch noch nie gesehen. Die 
werden aber auch nirgends aufgerufen.

Lange Rede kurzer Unsinn habe ich alle Eindrücke von den Posts oben 
mitgenommen und habe an meinem (kleinen) Programm weitergeschrieben. Ich 
bin da aber auch einen Bug gestoßen, aus dem ich nicht schlau werde. Der 
8-Bit-Timer 0 wird einmal gestartet und von da an in Ruhe gelassen. 
Stattdessen wird der Interruptpunkt für OCR0A immer weiter nach hinten 
geschoben. Im folgenden Code (unten) wird von Interrupt bis zur Rückkehr 
in das Hauptprogramm 15 CPU-Zyklen verbraucht. Verschiebe ich den 
aktuellen Interruptpunkt in OCR0A auf einen Punkt 16 Zyklen später, 
sollte das Programm genau einen Zyklus im Hauptprogramm verweilen und 
wieder in die ISR gehen. Aber das tut sie im Simulator erst 16+256 
Zyklen später.

Kann mir wer sagen warum?
1
timer0_matchA:
2
 ld itmp0,Z+       ; SRAM lesen - Zeiger erhöhen 
3
 out PortB,itmp0   ; Ausgabe der entsprechend wertigen Bits
4
5
 in itmp4,SREG   ; SREG sichern
6
 cpi ZL, LOW(Bam_Outputs)+LOW(BAM_Width)-4  ; Zeiger auf Position überprüfen
7
 breq Last_BAM_Cycle              ; letzte BAMs ausführen
8
9
; Simple_BAM_Cycle:
10
11
 lsr itmp6              ; logisches verschieben nach rechts bei überlauf bit-128 mitnehmen
12
 in itmp0,OCR0A         ; aktuellen Timerinterrupt-Punkt lesen
13
 add itmp0,itmp6
14
 ; bewirkt, dass der nächste iterrupt in der hälfte der Zeit ausgelösst wird
15
 out OCR0A,itmp0        ; Timerinterrupt-Punkt auf neue Position setzen
16
17
 out SREG,itmp4
18
 reti                   ; 15-cycle-finish BAM-Fragment fertig

von der alte Hanns (Gast)


Lesenswert?

An der Stelle timer0_matchA sind doch bereits mindestens 4, vermutlich 
aber 6 oder mehr Takte seit dem Interrupt (TCNT0 = OCR0A) vergangen.

(Auch wenn ich mich wiederhole: wäre Zeit und Energie nicht besser in 
die Integration von Sams Lösung investiert?)

von Tiny10Nutzer (Gast)


Lesenswert?

Nach Eintritt in den Interrupt wird OCR0A ja nicht absolut weiter nach 
hinten versetzt. Die 16 werden zu der alten bereits vergangenem Position 
dazu addiert. Die neue Position wird also in Relation zur alten Position 
neu gesetzt, unabhängig davon wo diese Befehle stehen.

In den Interrupts vorher wird eine Verzögerung von 64 (0b01000000) und 
eine mit 32  (0b0010000) dazu addiert. Die Interrupts werden auch mit 
der korrekten Verzögerung aufgerufen. Nur mit der 16 gibt es Probleme. 
Die daraus folgende Verzögerung von 16+256 zeigt ja auch, dass die 
Verzögerung übernommen wurde. Sie wird nur viel zu spät wirksam und ich 
habe keine Ahnung warum.

Sams Lösung würde ich wirklich gern übernehmen, bzw. für meine kleineren 
Bedürfnisse anpassen. Das Problem bleibt aber, dass ich die Essenz des 
Programms (noch) nicht erfassen kann und es mir daher schwer fällt die 
einzelnen Stellen des Programms richtig einzuschätzen. Dazu kommt, dass 
mich die vielen Verzögerungsschleifen stören, die an sich gut mit 
sinnvollen Befehlen befüllt werden könnten (es wird CPU-Leistung 
verbraucht ohne das was gemacht wird). Mein kleines Programm ist 
ungleich weniger professionell, aber dafür stärker an meine Bedürfnisse 
angepasst.

von der alte Hanns (Gast)


Lesenswert?

Wenn OCR0A mit dem Wert 'vorher+16' gesetzt wird, ist TCNT0 bereits 
einen oder einige Zähler weiter, als wird die Interruptbedingung TCNT0 = 
OCR0A erst einen Durchlauf später erfüllt.
Das sollte im Simulator eigentlich klar erkennbar sein!?

von der alte Hanns (Gast)


Lesenswert?

Hier kommt noch das fehlende 'o' fürs also.

Nochmal zu Sam: 'verbraten' wird ja nur in den kleinen Zeitscheiben (und 
ich bezweifle, dass Ihnen das wesentlich besser gelingen wird), in den 
großen bleibt viel zeitlicher Spielraum für sonstige Aktivitäten.

Allgemein habe ich jetzt aber ein Problem: mein Wunsch zu helfen geht 
allmählich gegen Null, und ein Eigeninteresse, sprich Erkenntnisgewinn, 
ist nicht in Sicht. Ich verabschiede mich.

von Peter D. (peda)


Lesenswert?

Tiny10Nutzer schrieb:
> brmi 0b Soll wohl ein Bedingter Sprung PC-1 bedeuten.

Nope.

http://tigcc.ticalc.org/doc/gnuasm.html#SEC48L

von Tiny10Nutzer (Gast)


Lesenswert?

Die wenigen Erkenntnisse die ich grade über Sams Komplettprogramm habe 
ist, dass es auf den Timer_Overflow reagiert (davon ausgelöst wird) und 
die Bits in der Reichen folge 1 2 4 8 16 32 64 128 ausgibt und erst nach 
der Ausgabe von 128 die CPU wieder für das Hauptprogramm frei gibt. Da 
ich noch eine andere sehr wichtige Aufgabe umsetzen möchte ist das so 
für mich nicht direkt verwendbar.

Selbstverständlich können meine Erkenntnisse auch falsch sein.

Der Vollständigkeit halber möchte ich aber sagen, dass ich hier 
niemanden auf den Schlipps treten möchte.

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.