Forum: Mikrocontroller und Digitale Elektronik Optimierung der Soft-PWM mit Assembler ISR


von Frank (Gast)


Angehängte Dateien:

Lesenswert?

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.
1
// Timer 1 Output Compare A-Interrupt
2
ISR(TIMER1_COMPA_vect)
3
{
4
    // set output
5
#ifdef RGBPWM_PINMASK
6
    // 91 clock ticks for the whole ISR
7
    RGBPWM_PORT = (RGBPWM_PORT & ~(RGBPWM_PINMASK)) | rgbpwm_ptrPortISR[rgbpwm_step];
8
#else
9
    // 78 clock ticks for the whole ISR
10
    RGBPWM_PORT = rgbpwm_ptrPortISR[rgbpwm_step];
11
#endif
12
    // next step
13
    if (++rgbpwm_step == rgbpwm_steps)
14
    {
15
        rgbpwm_step = 0;
16
        rgbpwm_update = 1; // allow update
17
    }
18
    // 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

von Kai G. (runtimeterror)


Lesenswert?

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?

von Fred S. (Gast)


Lesenswert?

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!

von Frank (Gast)


Lesenswert?

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
volatile uint8_t rgbpwm_steps;  // total steps in cycle
2
volatile uint8_t rgbpwm_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

von Fred S. (Gast)


Lesenswert?

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

von Fred S. (Gast)


Lesenswert?

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

von Frank (Gast)


Lesenswert?

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.
1
    asm volatile ("ld r16, %a0" : : "e" (rgbpwm_ptrPortISR[rgbpwm_step]) : "r16");
2
    asm volatile ("out %[port], r16" : : [port] "M" (_SFR_IO_ADDR(RGBPWM_PORT)));

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?

von Falk B. (falk)


Lesenswert?

@ 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.

von .._ (Gast)


Lesenswert?

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!

von .._ (Gast)


Lesenswert?

> 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.

von Fred S. (Gast)


Lesenswert?

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

von Ulrich (Gast)


Lesenswert?

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).

von Fred S. (Gast)


Lesenswert?

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

von Frank (Gast)


Lesenswert?

@ 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

von Frank (Gast)


Lesenswert?

@ .._

> 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ß

von Frank (Gast)


Lesenswert?

@ 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 :-)

von Frank (Gast)


Lesenswert?

Hey, die LEDs habe ich anbekommen freu
1
#include <avr/io.h>
2
#include "rgbpwm.h"
3
4
#ifdef RGBPWM_USE_ASM
5
6
.global TIMER1_COMPA_vect
7
TIMER1_COMPA_vect:
8
    push 16
9
    ldi 16, 0xff
10
    out _SFR_IO_ADDR(RGBPWM_PORT), 16
11
    pop 16
12
    reti
13
14
#endif

*ich-schaffs,-ich-schaffs*

von Frank (Gast)


Lesenswert?

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
register uint8_t rgbpwm_step asm("r2");
3
// total steps in pwm cycle
4
register volatile uint8_t rgbpwm_steps  asm("r3");
5
// isr sets this to 1 after last step to signalize we can swap pointers
6
register volatile uint8_t rgbpwm_update asm("r4");
1
ISR(TIMER1_COMPA_vect)
2
{
3
    asm volatile (
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)
32
        "sts %[ocr1a], r24" "\n\t"              // set OCR1AL
33
        :
34
        : [port] "M" (_SFR_IO_ADDR(RGBPWM_PORT)), [ocr1a] "M" (_SFR_IO_ADDR(OCR1A))
35
        : "r2", "r4", "r24", "r30", "r31"
36
        );
37
}
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:
1
            ASM     C   C (mit Pin-Maske)   Original
2
=====================================================
3
prologue:   14      16          18
4
function:   34      43          52
5
epilogue:   17      19          21
6
-----------------------------------------------------
7
SUMME:      65      78          91              111

Bis dann!
Frank

von Karl H. (kbuchegg)


Lesenswert?

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
> change the ports
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
17
    OCR1A = *p_OcISR++;
18
}

von Fred S. (Gast)


Lesenswert?

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

von Fred S. (Gast)


Lesenswert?

Hi,

hier die versprochenen URLs:

http://www.cs.mun.ca/~paul/cs4723/material/atmel/avr-libc-user-manual-1.6.1.pdf
  (Parameter-Übergabe auf S. 322 erläutert)

http://cs.uakron.edu/~margush/306/ppt/c_4_avr.ppt


Gruß

Fred

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.