Halli Hallo!
Bei einer LED Steuerung brauche ich eine Soft-PWM und habe den
Soft-PWM-Artikel auf eurer Seite wiederverwertet. Vielen Dank an die
Leute die sich die Arbeit gemacht haben. Um eine möglichst hohe
Genauigkeit zu erreichen wollte ich das Ganze noch ein Wenig auf
Geschwindigkeit optimieren. Daraus ergeben sich folgende Änderungen:
- die Frequenz wird mit aus dem Systemtakt direkt abgeleitet
- die Output-Comopare-Values sind absolute Werte für den Timer1 (spart
rechnen)
- Möglichkeit nur einige der Port-Pins zu benutzen (zu Ungunsten der
Geschwindigkeit und deswegen hier nicht von Belang)
Insgesamt konnte ich die gesamte ISR inklusive Rücksprung auf 78 Takte
(statt 111 wie bei dem Beispiel auf der Seite) zusammendampfen. Das
reicht leider gerade für 9 Bit Genauigkeit (128 Takte für einen Wert).
Ich bin nun auf der Suche nach Leuten die mir sagen können, ob die ISR
noch einsparpotential auf Assemblerebene bietet um auf unter 60 Takte zu
kommen und 10 Bit Genauigkeit zu ermöglichen.
// set output compare value to next point in time when we have to change the ports
19
OCR1A=rgbpwm_ptrOcISR[rgbpwm_step];
20
}
Was der Compiler da ausspuckt sieht für einen Laien wie mich schon
ziemlich minimal aus. Aber ich gebe die Hoffnung nicht auf... Ich kenn
mich mit Assembler leider nicht aus und welche Fallstricke bei
inline-Sachen zu beachten sind konnte ich bisher nicht wirklich finden.
Da dachte ich: "Frag jemanden, der sich auskennt" :-)
Im prologue und epilogue stehen da jeweils 5 push/pops, die ja jeweils 2
Takte kosten. Am "reti" lässt sich nichts drehen.
Danke für eure Hilfe
Wenn's um Geschwindigkeit geht ist der Inline-Assembler dein Freund.
>Ich bin nun auf der Suche nach Leuten die mir sagen können, ob die ISR>noch einsparpotential auf Assemblerebene bietet um auf unter 60 Takte zu>kommen und 10 Bit Genauigkeit zu ermöglichen.
Geht in Assembler locker. Interrupt-Routinen verbraten pauschal 10-15
Zyklen (übern Daumen), den Rest hast du für ein Decrement und ein
Port-Update.
Wenn der Controller sonst nichts sinnvolles tun soll geht's auch ohne
Interrupts. Damit braucht eine Soft-PWM um die 10 Zyklen.
Warum überhaupt Soft-PWM? Hat der Controller (welcher Controller
überhaupt?) keinen 16-Bit-Timer, der eine Hardware-PWM erzeugen kann?
Wofür brauchst du eine Genauigkeit von 10 Bits?
Hallo Frank,
Deine gepackte (ZIP) Datei habe ich nicht geöffnet; es wäre schön
gewesen, Du hättest den Code einfach direkt in den Anhang gestellt. Mir
ist total rätselhaft, weshalb es Dir bei LED-PWM auf Genauigkeit im
us-Bereich ankommt. Trotzdem hier mal der Assembler Code und unten noch
ein paar Kommentare dazu:
#ifdef RGBPWM_PINMASK
RGBPWM_PORT = (RGBPWM_PORT & ~(RGBPWM_PINMASK)) |
rgbpwm_ptrPortISR[rgbpwm_step];
; rgbpwm_ptrOcISR[rgbpwm_step] => r0 !!!!!!!!!
; rgbpwm_step => r1 !!!!!!!!!
; rgbpwm_steps => r2 (oder ist das sogar eine Konstante??) !!!!!!
; rgbpwm_update => r17
in r16, RGBPWM_PORT
andi r16, ~(RGBPWM_PINMASK)
or r16, r1
out RGBPWM_PORT, r16
#else
RGBPWM_PORT = rgbpwm_ptrPortISR[rgbpwm_step];
out RGBPWM_PORT, r0
#endif
// next step
if (++rgbpwm_step == rgbpwm_steps)
{ rgbpwm_step = 0; rgbpwm_update = 1; }
inc r1
cp r1,r2
brne dest
clr r1
ldi r17, 1
OCR1A = rgbpwm_ptrOcISR[rgbpwm_step];
dest:
out OCR1A, r0
; rgbpwm_step <= r1 !!!!!!!!!
; rgbpwm_update <= r17 !!!!!!!
Falls rgbpwm_steps konstant ist, wird es noch einfacher. Etwas Zeit wird
sicher mit der Parameter-Übergabe (siehe Zeilen mit !) verbraten
(wahrschinlich sind Deine Variablen volatile) und die verwendeten
Register müssen gerettet werden. Falls die I/O-Register bei Deinem
Prozessor memory mapped sind, geht auch kein in/out. Es sieht schon
danach aus, als ob man das Compiler-Ergebnis (hast Du mit -Os
optimiert??) um ein paar Instruktionen schlagen könnte. Überleg Dir aber
genau, ob der Aufwand lohnt, um bei einer LED-PWM ein paar us zu sparen!
Gruß
Fred
P.S. Kai war schneller!
Danke für die Antworten!
> Interrupt-Routinen verbraten pauschal 10-15 Zyklen> (übern Daumen)
Damit meinst du jetzt den reinen Funktionsrumpf?
Folgende Takte müssen ja zusätzlich noch drinnen sein:
- Port-setzen: 1
- Output Compare setzen: 2
- Inkrementieren: 1
- if-Abfrage: 2
- "step" auf 0 und "update" auf 1: 2
Summe: 8 Takte, aber ohne Daten "holen" und Adressen berechnen?
> Wenn der Controller sonst nichts sinnvolles tun soll geht's auch> ohne Interrupts.
Doch der soll eigentlich schon alles machen (i2c, menu, ad-wandler,
tastenabfrage) und die pwn so "nebenbei"
> Warum überhaupt Soft-PWM? Hat der Controller (welcher Controller> überhaupt?) keinen 16-Bit-Timer, der eine Hardware-PWM erzeugen kann?
Es ist mein aller... aller erstes Controller Projekt. Richtig: mega88
:-)
Problem ist, dass ja nur zwei 16 Bit PWMs da sind. Ich hatte eigentlich
auch gedacht, dass die 8 Bit PWMs ja locker reichen müssten. Leider weit
gefehlt. Nach ersten Versuchen stellte sich natürlich raus, dass bei 8
Bit der Übergang von untersten Wert 1 auf 0 optisch schon als recht
heftiger Sprung bemerkbar macht - jedenfalls für meine Ansprüche :-)
Also will ich die unteren Stufen weiter auflösen und am besten für alle
Farben gleich. Ich will es vermeiden, einen zweiten Controller irgendwie
per i2c ansprechen zu müssen. Die Herausforderung sowas mal richtig zu
optimieren und seien es eben auch nur 10 Bit reizt mich einfach.
> Deine gepackte (ZIP) Datei habe ich nicht geöffnet; es wäre schön> gewesen, Du hättest den Code einfach direkt in den Anhang gestellt.
Ich habe bei meinen drei Wochen Recherchen hier irgendwo schon gelesen,
dass man es nicht so toll findet viele Zeilen Code direkt im Posting zu
haben. Deswegen dacht ich mich, ich halte mich daran.
> Falls rgbpwm_steps konstant ist, wird es noch einfacher.
Nein, leider nicht. Die Zahl schwankt zwischen mit der Anzahl
verschiedener PWM-Werte auf den Kanälen. 1 = alle Ausgänge immer 0 (der
Interrupt wird nur aufgerufen wenn der Timer1 0 wird), 9 wenn alle 8
Ausgänge verschiedene Umschaltzeiten haben. Wenn es nacher darum geht
vielleicht noch ein Bit mehr Genauigkeit herauszuschlagen könnte man
natürlich darüber nachdenken, hier die Konstante 9 reinzuschreiben und
beim Berechnen der Tabelle für die Umschaltzeitpunkte "dummies" ohne
Ausgangsänderung einzubauen, nur damit der Wert konstant bleiben kann.
Aber sollte man das wohl lassen...
> Falls die I/O-Register bei Deinem> Prozessor memory mapped sind, geht auch kein in/out.
Kannst du mir das näher erklären? Der gcc macht bei setzen des
OCA-Wertes immer ein out draus. Hier der bisherige erzeugte Code:
1
127 __vector_11:
2
128 .LFB5:
3
129 .LM21:
4
130 /* prologue: frame size=0 */
5
131 00be 1F92 push __zero_reg__
6
132 00c0 0F92 push __tmp_reg__
7
133 00c2 0FB6 in __tmp_reg__,__SREG__
8
134 00c4 0F92 push __tmp_reg__
9
135 00c6 1124 clr __zero_reg__
10
136 00c8 8F93 push r24
11
137 00ca 9F93 push r25
12
138 00cc EF93 push r30
13
139 00ce FF93 push r31
14
140 /* prologue end (size=9) */
15
141 .LM22:
16
142 00d0 9091 0000 lds r25,rgbpwm_step
17
143 00d4 E091 0000 lds r30,rgbpwm_ptrPortISR
18
144 00d8 F091 0000 lds r31,(rgbpwm_ptrPortISR)+1
19
145 00dc E90F add r30,r25
20
146 00de F11D adc r31,__zero_reg__
21
147 00e0 8081 ld r24,Z
22
148 00e2 8BB9 out 43-0x20,r24
23
149 .LM23:
24
150 00e4 9F5F subi r25,lo8(-(1))
25
151 00e6 9093 0000 sts rgbpwm_step,r25
26
152 00ea 8091 0000 lds r24,rgbpwm_steps
27
153 00ee 9817 cp r25,r24
28
154 00f0 01F4 brne .L10
29
155 .LM24:
30
156 00f2 1092 0000 sts rgbpwm_step,__zero_reg__
31
157 .LM25:
32
158 00f6 81E0 ldi r24,lo8(1)
33
159 00f8 8093 0000 sts rgbpwm_update,r24
34
160 .L10:
35
161 .LM26:
36
162 00fc E091 0000 lds r30,rgbpwm_step
37
163 0100 FF27 clr r31
38
164 0102 EE0F lsl r30
39
165 0104 FF1F rol r31
40
166 0106 8091 0000 lds r24,rgbpwm_ptrOcISR
41
167 010a 9091 0000 lds r25,(rgbpwm_ptrOcISR)+1
42
168 010e E80F add r30,r24
43
169 0110 F91F adc r31,r25
44
170 0112 8081 ld r24,Z
45
171 0114 9181 ldd r25,Z+1
46
172 0116 9093 8900 sts (136)+1,r25
47
173 011a 8093 8800 sts 136,r24
48
174 /* epilogue: frame size=0 */
49
175 011e FF91 pop r31
50
176 0120 EF91 pop r30
51
177 0122 9F91 pop r25
52
178 0124 8F91 pop r24
53
179 0126 0F90 pop __tmp_reg__
54
180 0128 0FBE out __SREG__,__tmp_reg__
55
181 012a 0F90 pop __tmp_reg__
56
182 012c 1F90 pop __zero_reg__
57
183 012e 1895 reti
58
184 /* epilogue end (size=9) */
59
185 /* function __vector_11 size 57 (39) */
> wahrschinlich sind Deine Variablen volatile
Ja genau, die gleichen wie beim Soft-PWM Artikel, also Ist ja eh fast
das gleiche im Prinzip.
1
volatileuint8_trgbpwm_steps;// total steps in cycle
2
volatileuint8_trgbpwm_update;// isr sets this to 1 after last step to signalize we can swap pointers
> die verwendeten Register müssen gerettet werden
Wie? Sind das die ganzen vom gcc ertzeugten push's und pop's um die
Werte, die davor dort lagen zu sichern?
> hast Du mit -Os optimiert??
Ja.
> Überleg Dir aber genau, ob der Aufwand lohnt, um bei einer LED-PWM> ein paar us zu sparen!
Wie gesagt geht es nicht um die us sondern um die "Geschmeidigkeit" beim
Faden in den unteren Helligkeitsstufen. Und darum zu lernen.
Ich habe nun noch Fragezeichen beim Einsatz der Instruktionen. Z.B.
gleich in der ersten Zeile: wie komme ich denn an die Adresse von
"rgbpwm_ptrOcISR[rgbpwm_step]", oder erzeugt der gcc das für mich? Kann
man einfach irgendwelche Register innerhalb seines inline Assembler
belegen und der gcc achtet darauf, dass er sich mit den im Assembler
explizit Registern nicht in die Quere kommt?
Irgendwo habe ich was gelesen, dass der Compiler sich bestimmte Register
für bestimmte Zwecke vorbehält?
Muss ich alles mit "asm volatile ("BEFEHL");" machen?
Super nett, dass man so schnell geholfen wird hier. Danke!
Frank
Hi Frank,
>> Deine gepackte (ZIP) Datei habe ich nicht geöffnet; es wäre schön>> gewesen, Du hättest den Code einfach direkt in den Anhang gestellt.> Ich habe bei meinen drei Wochen Recherchen hier irgendwo schon gelesen,> dass man es nicht so toll findet viele Zeilen Code direkt im Posting zu> haben. Deswegen dacht ich mich, ich halte mich daran.
genau deshalb habe ich auch geschrieben "in den Anhang (!!) gestellt"
(als *.c oder *.txt, nicht als ZIP)!
Gruß
Fred
Hallo Frank,
Bei der "Geschmeidigkeit" des Fading kommt es auf ein paar us bestimmt
nicht an! Als Lernübung ist Dein Projekt natürlich vollkommen
akzeptabel.
Aus Deinen Fragen geht allerdings hervor, dass Du Dich mit der
Dokumentation des Inline Assemblers noch nicht viel beschäftigt hast.
Vielleicht ist hier jemand bereit, Deine Fragen im einzelnen
beantworten. Ich möchte Dich eher auf die Dokumentation und diverse
Tutorials verweisen. Am instruktivsten finde ich
http://www.stanford.edu/class/ee281/projects/aut2002/yingzong-mouse/media/GCCAVRInlAsmCB.pdf
Auch in einem Parallel-Universum gibt es gute Informationen:
http://www.roboternetz.de/wissen/index.php/Inline-Assembler_in_avr-gcc
Zu Deiner "memory mapped" Frage: das ist gerade gestern in einem anderen
Thread ( Beitrag "mega164p: sbi UCSR0B,TXEN0; Operand 1 out of range" )diskutiert worden
und gilt nur für bestimmte Atmel-AVRs.
Viel Erfolg!
Fred
Danke für die Links! Bin da einiges durchgegangen und habe nun etwa 2,5
Stunden gebraucht um einfach mal zu versuchen die C-Portzuweisung in
Assembler zu übertragen. Ich bin schon verzweifelt, dass es sich einfach
nicht kompilieren lies. Das klappt nun. Aber Funktion = Null und
Fehlermeldungen gibts auch nicht.
Sonderlich "instruktiv" finde ich die leider nicht. Gerade weil die
eigentlich gar keine Ausgaben abdecken.
Vielleicht ist das ja gerade ein Problem mit dem "mapped memory". Keine
Ahnung, denn aus den Assembler Schnippseln kann ich nicht viel rauslesen
und der gcc benutzt ja auch "out".
Spaß macht das nicht. Auf den Inline-Assembler Seiten wird immer immer
"assumed", dass man sich mit dem AVR-Assembler schon auskennt. Ziemlich
realitätsfremd. Schade.
Trortzdem 'nen Tipp?
@ Frank (Gast)
>- die Frequenz wird mit aus dem Systemtakt direkt abgeleitet
???
Ist doch schon im Original so. Man kann den Vorteiler auf 1 setzen wenn
man will/kann.
>- die Output-Comopare-Values sind absolute Werte für den Timer1 (spart>rechnen)
Die zwei adds brauchen zwei Takte. Nicht nennenswertes an zeit
zugewinnen.
>Insgesamt konnte ich die gesamte ISR inklusive Rücksprung auf 78 Takte>(statt 111 wie bei dem Beispiel auf der Seite) zusammendampfen. Das>reicht leider gerade für 9 Bit Genauigkeit (128 Takte für einen Wert).
Du meinst Auflösung, das ist nicht unbedingt Genauigkeit. Siehe
Auflösung und Genauigkeit.
>noch einsparpotential auf Assemblerebene bietet um auf unter 60 Takte zu>kommen und 10 Bit Genauigkeit zu ermöglichen.
???
Bei 8 MHz sind ca. 312 Takte/ PWMTakt verfügbar. Die C-ISR braucht 111.
Bettreibe den Controller mit 16 MHz und 10 Bit Auflösung sind kein
Problem.
MfG
Falk
P.S. Viel mehr als ein paar "überflüsse" Push/pops kann man nicht
sparen, der GCC ist schon recht gut.
Hallo Frank:
Du kannst dein Problem vielleicht einfacher in den Griff bekommen:
Das Auge nimmt logaritmisch war (es passt sich der Gesamthelligkeit an),
deswegen "brauchst" du auch mehr als 10Bit Genauigkeit. Allerdings
benötigst du sie nur bei den niedrigen Werten. Alternativ kannst du auch
einfach eine konstante An-Zeit setzten und die Aus-Zeit variieren. Hier
gilt: je groeser die Aus-zeit, destoweniger Licht. Je groesser die
Aus-Zeit desto einfacher auch in C zu realisieren (da lansgam).
Deswegen mein Vorschlag:
Nimm einen 8-Bit-Timer und stell ihn auf CTC modus. Dann in den
Interrupt machst du die An-Zeit direkt rein (für kleine Werte), also
währen LED an, wird immer der interrupt ausgeführt, und die Aus-Zeit hat
dann dein Programm zur arbeit moeglich. Wenn das Verhältnis An-Aus
entsprechend "viel" An-Zeit verlangt, schaltest du einfach auf
"normalen" PWM um.
viel Erfolg!
> P.S. Viel mehr als ein paar "überflüsse" Push/pops kann man nicht> sparen, der GCC ist schon recht gut.
Das stimmt nicht immer. Gerade wenn viele einzelne (statische) Variablen
verwendet werden, nimmt der GCC liebend gerne die statisch
addressierbaren Lade-Speicher-Befehle für den SRAM. Das verwenden des
Pointer-Registers muss man ihm dann "per Hand" beibringen. Hier kann
sowohl viel Flashspeicher als auch Zeit gespart werden.
Hi Frank,
mit dem Inline Assembler haben wir Dich wohl auf Abwege gebracht. So
geht es einfacher: Verwende eine C-Assembler (*.S) Routine. Hier als
Beispiel für den Analog-Komparator-Interrupt:
Hauptprogramm in C:
#include <avr\interrupt.h>
#include <avr\io.h>
#include <stdlib.h>
int main(void) {
while (1) ;
}
(Interrupts also gar nicht erwähnt!)
*.S (Assembler für C) Programm:
#include <avr/io.h>
.global ANALOG_COMP_vect
ANALOG_COMP_vect:
push 16 ; r16 retten
in 16,_SFR_IO_ADDR(SREG) ; SREG retten
; HAUPTKRAM, wir dürfen jetzt natürlich r16 benutzen!
out _SFR_IO_ADDR(SREG),16 ; SREG wiederherstellen
pop 16 ; r16 wiederherstellen
reti
Alles schön von Jörg Wunsch hier dokumentiert:
http://www.nongnu.org/avr-libc/user-manual/group__asmdemo.html
Der Assembler für WinAVR/GCC (der die *.S Dateien assembliert) hat auch
ein paar Eigenheiten (z.B. einfach "16" statt "r16" schreiben!); aber
ich könnte mir vorstellen, dass Du damit besser zurechtkommst.
Wie ich schon ganz am Anfang gesagt habe, ist das nur als Übung
anzusehen, denn die Mikrosekunden-Genauigkeit, die Du anstrebst, macht
für LED-PWM wenig Sinn.
Gruß
Fred
Es gibt schon einiges was man in ASm noch sparen kann. Mann kann z.B.
das Ergniss vom Vergleich z.B. per ROL direkt aus dem carry Flag ins
ergebnis schieben. Auch kann man die innere Schleife für 8 Kanäle
auflösen, gibt etwas längeren Code wird aber deutlich schneller. Für 8
bit PWm sieht der zeitkritische Teil für 2 Kanäle dann so aus:
ld r1,x+
cp tstep,r1
rol data
ld r1,x+
cp tstep,r1
rol data
kann man auch ganz gut auf vielfache von 8 Kanäle oder mehr bits
erweitern.
Für 8 Bit PWM kommt man so auf etwa 6 Zyklen pro Kanal. Für mehr als 8
Bits sollten es ca. 9 Zyklen sein (ein extra ld und ein CPC).
Hallo Frank,
noch ein kurzer Nachtrag zu meinem obigen Vorschlag, den gcc-Assembler
zu benutzen.
1. Ich verwende den oft, da ich den Inline-Assembler nicht besonders
komfortabel finde.
2. Hier eine Mini-Einführung:
http://www.nongnu.org/avr-libc/user-manual/assembler.html
Viel Erfolg!
Fred
@ Falk Brunner
> Ist doch schon im Original so. Man kann den Vorteiler auf 1 setzen> wenn man will/kann.
Ich finde es so deutlich weniger aufwendig. Bis ich begriffen habe, dass
du (?) den Timer einfach überlaufen lässt und auf nen Wert setzt, der in
der "Vergangenheit" liegt - im Wissen im nächsten Timer-Durchlauf hats
wieder hin, hats ne ganze Weile gedauert.
> Du meinst Auflösung, das ist nicht unbedingt Genauigkeit.#> Siehe Auflösung und Genauigkeit.
Stimmt aber ich wüsste nicht warum meine Version weniger genau sein
sollte als die aus dem Artikel. Ansonsten liegt die Sensorik Vorlesung
bereits hinter mir, ja :-)
> Bei 8 MHz sind ca. 312 Takte/ PWMTakt verfügbar. Die C-ISR braucht 111.> Betreibe den Controller mit 16 MHz und 10 Bit Auflösung sind kein> Problem.
Du tauscht damit PWM-Frequenz gegen Genauigkeit. Schon mal vor 'nem 100
Hz Fernseheher gesessen und nicht still gesessen? Ich finde die
"Doppelbilder" grausig. Wenn ich mir überlege so einen Raum
auszuleuchten...
Ja ich leide noch an der Krankheit etwas so gut wie möglich statt so gut
wie (für andere vielleicht) nötig zu machen :-)
Gruß
Frank
@ .._
> Deswegen mein Vorschlag...
Ich weiß nicht ob ich das richtig verstanden habe... Ich habe mir auch
schon gedanke gemacht ob ich nicht die ersten 64 Schritte einfach
"festverdrahte" und in den Overflow-Interrupt packe. Dann muss man halt
ein paar mehr Bytes RAM opfern und beim Ändern der PWM-Einstellungen
viel mehr Werte berechnen, aber das passiert im Verhältnis ja eher
selten.
Ich glaube, das ist in etwas was du meintest?
Gruß
@ Fred:
> Alles schön von Jörg Wunsch hier dokumentiert
Das mit dem Variablen Binden ist ja schon mal eine sehr gute Idee für
die "volatile Dinger". Spart man sich schon mal 8 Takte für die push's
und pop's. Leider zeigt er aber nicht, wie abseits davon a C-Variablen
geschweige denn die Arrays rankommt. Oder habe ich was übersehen.
Bisher habe ich einfach die neue rgbpwm-isr.S in die Makefile
eingetragen und versuche mal die LEDs hardgecoded an und aus zu
schalten.
WinAVR kann ich nicht benutzen als Linux-Jünger :-)
Hallo!
Ich bin doch wieder auf den inline-Assembler umgeschwenkt, weil ich
einfach nicht herausfinde konnte, wie ich im GNU-Assembler an meine
anderen C-Variablen rankommen. Und ich war nun schlussendlich
erfolgreich. Nur ist's leider gerade so nicht soviel schneller geworden,
dass ich in meiner Version mit 10 Bit arbeiten kann.
Für den Fall, dass jemand mal 'nen Beispielcode sucht, will ich mein
Ergebnis hier mal vorstellen.
Ausgangspunkt war diese C-ISR:
1
ISR(TIMER1_COMPA_vect)
2
{
3
// set output
4
RGBPWM_PORT=rgbpwm_ptrPortISR[rgbpwm_step];
5
// next step
6
if(++rgbpwm_step==rgbpwm_steps)
7
{
8
rgbpwm_step=0;
9
rgbpwm_update=1;// allow update
10
}
11
// set output compare value to next point in time when we have to change the ports
12
OCR1A=rgbpwm_ptrOcISR[rgbpwm_step];
13
}
Ich habe nun folgendes draus gemacht:
1
// current step in pwm cycle
2
registeruint8_trgbpwm_stepasm("r2");
3
// total steps in pwm cycle
4
registervolatileuint8_trgbpwm_stepsasm("r3");
5
// isr sets this to 1 after last step to signalize we can swap pointers
6
registervolatileuint8_trgbpwm_updateasm("r4");
1
ISR(TIMER1_COMPA_vect)
2
{
3
asmvolatile(
4
// RGBPWM_PORT = rgbpwm_ptrPortISR[rgbpwm_step];
5
"lds r30,rgbpwm_ptrPortISR""\n\t"// load rgbpwm_ptrPortISR to pointer-register Z
6
"lds r31,(rgbpwm_ptrPortISR)+1""\n\t"
7
"add r30,r2""\n\t"// add rgbpwm_step to calculate correct address
8
"adc r31,__zero_reg__""\n\t"// if there was an overflow, add the carry flag to
9
// the next address-byte
10
"ld __tmp_reg__,Z""\n\t"// load the value the Z register points to
11
"out %[port],__tmp_reg__""\n\t"// set port to this value
12
// next step
13
"inc r2""\n\t"// rgbpwm_step++;
14
"cp r2,r3""\n\t"// if (rgbpwm_step == rgbpwm_steps) ...
15
"brne pwmNotLastStep""\n\t"// {
16
"clr r2""\n\t"// rgbpwm_step = 0;
17
"clr r4""\n\t"// rgbpwm_update = 0;
18
"inc r4""\n\t"// rgbpwm_update++;
19
"pwmNotLastStep:""\n\t"// }
20
// OCR1A = rgbpwm_ptrOcISR[rgbpwm_step];
21
"mov r30,r2""\n\t"
22
"clr r31""\n\t"
23
"lsl r30""\n\t"// z-low-byte *= 2 (we access a 16 bit variable)
24
"rol r31""\n\t"// ???
25
"lds r24,rgbpwm_ptrOcISR""\n\t"// load rgbpwm_ptrOcISR to z-reg low-byte
26
"lds __tmp_reg__,(rgbpwm_ptrOcISR)+1""\n\t"// load rgbpwm_ptrOcISR to z-reg low-byte
27
"add r30,r24""\n\t"// add 2*rgbpwm_step to calculate correct address
28
"adc r31,__tmp_reg__""\n\t"// add carry-flag from previous addition
29
"ld r24,Z""\n\t"
30
"ldd __tmp_reg__,Z+1""\n\t"
31
"sts %[ocr1a]+1, __tmp_reg__""\n\t"// set OCR1AH (first)
Sicherlich nicht perfekt, für das erste Mal? Leider ist da noch ein
kleiner Fehler drin, oder _SFR_IO_ADDR(OCR1A) liefert einen "falschen"
Wert. Wenn man statt '%[ocr1a]' '136' (beim C-Ergebnis abgeschaut)
hardcoded, funktioniert's einwandfrei.
Hier noch ein kleiner Vergleich:
Frank wrote:
> Für den Fall, dass jemand mal 'nen Beispielcode sucht, will ich mein> Ergebnis hier mal vorstellen.>> Ausgangspunkt war diese C-ISR:>
1
ISR(TIMER1_COMPA_vect)
2
>{
3
>// set output
4
>RGBPWM_PORT=rgbpwm_ptrPortISR[rgbpwm_step];
5
>// next step
6
>if(++rgbpwm_step==rgbpwm_steps)
7
>{
8
>rgbpwm_step=0;
9
>rgbpwm_update=1;// allow update
10
>}
11
>// set output compare value to next point in time when we have to
12
>changetheports
13
>OCR1A=rgbpwm_ptrOcISR[rgbpwm_step];
14
>}
Wie wäre es mit folgendem:
Die alte Regel 'Space for Time':
Durch einführen von 2 zusätzlichen Pointern ersparst du dem
Compiler die Array-Index-Arithmetik innerhalb der ISR
1
ISR(TIMER1_COMPA_vect)
2
{
3
// set output
4
RGBPWM_PORT=*p_rgbPort;
5
6
p_OcISR++;
7
8
// next step
9
if(++prgbPort==rgbpwm_ptrPortISR+rgbpwm_steps)
10
{
11
rgbpwm_update=1;// allow update
12
p_rgbPort=&rgbpwm_ptrPortISR[0];
13
p_OcISR=&rgbpwm_ptrOcISR[0];
14
}
15
16
// set output compare value to next point in time when we have to change the ports
Hallo Frank,
schön, dass Du eine Lösung gefunden hast! Leider habe ich Deine Frage
> ...., wie abseits davon a C-Variablen geschweige denn die Arrays> drankommt.
gestern nicht gesehen, sonst hätte ich ich sie gleich beantwortet. Der
erste Parameter (im Beispiel unten eine 16 bit Adresse) wird über
r24/25, der nächste über r22/23 übertragen. Beispiel (meine "private"
strcpy()):
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;* void strcpy_ (char* destination, char* source);
wr = 16
.global strcpy_
.func strcpy_
strcpy_:
push wr ; wr wird im hier nicht gezeigten Teil der Funktion benutzt
push 26 ; X
push 27
push 28 ; und Y werden (s.u.) benutzt
push 29
movw 28, 24 ; 1. Parameter => Y
movw 26, 22 ; 2. Parameter => X
; usw.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Wo ist das dokumentiert? Versuche ich gleich herauszufinden, dann melde
ich mich wieder.
Gruß
Fred