Hallo, ich soll ein 8052-Programm vom Keil C51-Compiler auf den SDCC portieren, mit dem ich bisher leider noch nicht gearbeitet hab. Als MCU kommt ein SiLabs Controller zum Einsatz, Single-Cycle-Core, 24.5MHz. Der Controller implementiert eine 10-Kanal Software-PWM mit 8-Bit Auflösung, 100Hz, gesteuert wird per Kommandos über UART. Folgende Änderungen an der Software habe ich für die Portierung vorgenommen: - Anpassungen der Speichertypen (also z.B. 'data' -> '__data') - der Keil C51 unterstützt im Gegensatz zum SDCC unions als Funktionsparameter und Rückgabewert -> passend geändert (die unions sind für den einfachen Zugriff auf High/Low-Byte bei 'unsigned int' gedacht) - Interrupt-Prototypen in der main-Funktion (lt. Handbuch SDCC nötig) - lokale Variablen in Interrupts 'vorsichtshalber' als 'static' deklariert (ist beim Keil C51 nicht nötig) Das Erstellen lief ohne Fehler durch. Soweit, so gut... Jedoch ist es nun so, dass der Timer-Interrupt für die PWM wesentlich länger braucht, und zwar so lange dass der nächste Timer-Interrupt bereits noch während der Abarbeitung des aktuellen auftritt. Mir ist klar, dass der SDCC nicht so gut optimiert wie der Keil Compiler, aber ich frage mich nun, ob bzw. wo ich da noch tunen kann. Liegt es eher an der Art, wie man programmiert (z.B. verschachtelte if-Abfragen) oder sollte ich prinzipiell eine andere Register-Bank für den Interrupt verwenden, oder oder oder... Mir fehlt jetzt einfach grad der richtige Ansatzpunkt, wo ich am besten versuchen kann, das Programm auch sauber mit SDCC ans Laufen zu bringen, wenn möglich eben so, dass die Sourcen auf beiden Compilern funktionieren. Wie optimiert man für den SDCC bzw. welche "Konstrukte" sollte man vermeiden? Ralf
Immer nach dem Motto "KISS" (keep it small and simple). Also keine ausgebufften Konstrukte, möglichst keine Pointeroperationen (die kommen bei so einem kleinen 8 Bitter nicht gut) und drauf achten, daß man keine Überläufe programmiert, das geht entweder schief oder bläht den Code auf. Ansonsten: Guck dir mal den jeweiligen Assemblercode an. W.S.
SDCC kann mit C51 Keil weder in Codegröße noch in Geschwindigkeit mithalten. Eventuell musst Du das ganze Interruptkonzept und die Wiederholraten der Timer neu durchdenken. Alternativ, sofern Dein 8051 das mitmacht, die Taktfrequenz erhöhen. Keil braucht fast kein 'volatile'. Der Compiler erkennt fast immer, wenn eine Variable im main und Interrupt verwendet wird. SDCC kann das nicht, was häufig zu Fehlverhalten im Programm führt.
Der Keil Compiler ist zweifelsfrei die bessere Wahl. Ich habe schon recht große Programme auf den SDCC portiert und eine Codegrößendifferenz von max 10% dabei beobachtet. Ich glaube kaum das dein Problem damit zu tun hat daher wäre es besser den Code herzuzeigen. Dein Programm sollte auch auf dem SDCC schnell genug laufen. Ohne deinen Code zu kennen kann man aber kaum helfen.
Hallo, danke für eure Antworten. @W.S.: > Immer nach dem Motto "KISS" (keep it small and simple). Eigentlich dachte ich, dass meine Implementierung KISS ist :) > Also keine ausgebufften Konstrukte, ... Hmmm... ich mache einige Zugriffe auf structs im Interrupt, weil ich die jeweiligen PWM-Kanäle mit "Eigenschaften" wie 'Enable', 'Polarity', etc. ausgestattet hab, dafür hab ich dann halt structs verwendet, um es "zusammen" zu halten. > ... möglichst keine Pointeroperationen (die kommen bei so einem kleinen > 8 Bitter nicht gut)... Guter Hinweis. Ein Teil der structs liegt im idata-Bereich, also geht der Zugriff nur über Pointer, aber generell würde bei einem struct ja sowieso über Pointer zugegriffen, selbst wenn die structs im data-Bereich liegen würden, oder? > und drauf achten, daß man keine Überläufe programmiert, das geht > entweder schief oder bläht den Code auf. Was genau meinst du mit Überläufen? > Ansonsten: Guck dir mal den jeweiligen Assemblercode an. Ja, n Blick hab ich schon riskiert, aber ich muss erstmal durchsteigen wie der SDCC was löst. Die ISR ist jedenfalls knapp 50% größer als die vom Keil. Ich vermute das Problem ist auch nicht direkt die Schleife in der ich die PWM-Kanäle durchlaufe, sondern der Inhalt, wobei das eigentlich nur if-Abfragen sind, in denen in Abhängigkeit der Einstellungen für die Kanäle eben das entsprechende Bit gesetzt wird. @Matthias K.: > SDCC kann mit C51 Keil weder in Codegröße noch in Geschwindigkeit > mithalten. Eventuell musst Du das ganze Interruptkonzept und die > Wiederholraten der Timer neu durchdenken. Alternativ, sofern Dein 8051 > das mitmacht, die Taktfrequenz erhöhen. Ich benutze den internen Oszillator mit 24.5MHz, mehr kann der Controller eh nicht und für einen externen wären auch keine Pins frei. Ich denke, ich werde tatsächlich versuchen, das Konzept zu überarbeiten. Der Timer läuft mit etwa 25,6kHz (100Hz * 8-Bit Auflösung), im IRQ wird der PWM-Zähler mit den Werten der Kanäle verglichen und entsprechend das Ausgangsbit gesetzt. "Mehr" ist das eigentlich nicht, also fraglich ob ich da was tun kann. Ich kann nur versuchen, das gleiche auf eine andere Art zu implementieren. > Keil braucht fast kein 'volatile'. Der Compiler erkennt fast immer, wenn > eine Variable im main und Interrupt verwendet wird. SDCC kann das nicht, > was häufig zu Fehlverhalten im Programm führt. Deswegen hab ich schon vorab bei Variablen im IRQ oder gemeinsam genutzten Variablen 'static' oder 'volatile' gesetzt. @Bernd N: > Der Keil Compiler ist zweifelsfrei die bessere Wahl. Sehe ich auch so :) Der Wunsch war eben Sourcen zu haben, die auf beiden laufen, ohne große Abweichungen zu haben. > Ich habe schon recht große Programme auf den SDCC portiert und eine > Codegrößendifferenz von max 10% dabei beobachtet. Ich glaube kaum das > dein Problem damit zu tun hat daher wäre es besser den Code herzuzeigen. Ich denke auch nicht, dass es die Größe an sich ist, nur eben die Optimierung bzw. die Laufzeit der Schleife. > Dein Programm sollte auch auf dem SDCC schnell genug laufen. Ohne deinen > Code zu kennen kann man aber kaum helfen. Ich werd den Code posten, bin nur grad nicht am Entwicklungsrechner. Ralf
Ralf schrieb: >> Also keine ausgebufften Konstrukte, ... > Hmmm... ich mache einige Zugriffe auf structs im Interrupt, weil ich die > jeweiligen PWM-Kanäle mit "Eigenschaften" wie 'Enable', 'Polarity', etc. > ausgestattet hab, dafür hab ich dann halt structs verwendet, um es > "zusammen" zu halten. Ist halt die Frage wie Du das konkret implementiert hast. Wenn Du z.B. für Enable- und Polarity- Bits jeweils eine if-Abfrage machst, dann bekommst Du natürlich auch ordentlich Overhead der sich vermeiden lässt. Angenommen Du fasst diese Kontrollbits für die 8 Kanäle jeweils in einem Byte zusammen, also 1 Byte für die Enable-Bits, eines für die Polarity. Dann hast Du ein Byte für die 8 PWM Ausgangskanäle. Zuerst setzt Du die Bits in dem Ausgabebyte abhängig vom PWM-Wert, ohne irgendwelche extra Tests. Anschließend machst Du ein XOR dieses Bytes mit dem Polarity-Byte. Somit hast Du dann ganz einfach die Polarität gesetzt. Dann machst Du ein AND mit dem Enable-Byte. So kann man das ganz einfach und effizient lösen ohne da irgendwelche if-Abragen zu benutzen. Je nachdem wie dein Port-Zuweisungen reicht es dann auch wenn das fertigy Byte ganz einfach zum Port geschickt wird. Sollte das auf mehrere Ports verteilt sein dann musst das halt wieder aufsplitten. Grüße, Chris
@Christian Klippel: > Ist halt die Frage wie Du das konkret implementiert hast. Wenn Du z.B. > für Enable- und Polarity- Bits jeweils eine if-Abfrage machst, dann > bekommst Du natürlich auch ordentlich Overhead der sich vermeiden lässt. Genauso ist es grad, jedes PWM-Kanal-struct wird geprüft, ob es freigeschaltet ist, dann der PWM-Wert, dann die Polarität und in Abhängigkeit davon das Bitmuster setzen, welches an die Ports soll. Nach dem Schleifendurchlauf wird das Bitmuster auf die Ports verteilt. > Angenommen ... Ich hab aber 10 Kanäle :) Ein Byte reicht also nicht. Trotzdem gefällt mir die Idee, ich könnte versuchen, in der Schleife anstatt der if-Abfragen tatsächlich die jeweiligen Enable/Polarity-Bytes der einzelnen Kanäle logisch zu verknüpfen und schauen, ob das beim SDCC effizienter ist. > Sollte das auf mehrere Ports verteilt sein dann musst das halt wieder > aufsplitten. Ja, das muss ich leider, da wird ein bisschen maskiert, geschoben, etc. Da komm ich nicht drumrum. Ich werd das bei nächster Gelegenheit mal ausprobieren, danke. Ralf
Ralf schrieb: > Ich hab aber 10 Kanäle :) Ein Byte reicht also nicht. Trotzdem gefällt > mir die Idee, ich könnte versuchen, in der Schleife anstatt der > if-Abfragen tatsächlich die jeweiligen Enable/Polarity-Bytes der > einzelnen Kanäle logisch zu verknüpfen und schauen, ob das beim SDCC > effizienter ist. > >> Sollte das auf mehrere Ports verteilt sein dann musst das halt wieder >> aufsplitten. > Ja, das muss ich leider, da wird ein bisschen maskiert, geschoben, etc. > Da komm ich nicht drumrum. > > Ich werd das bei nächster Gelegenheit mal ausprobieren, danke. > > Ralf Naja, dann nimmst Du halt jeweils zwei Bytes, ist ja nicht so dramatisch. Falls es der Speicher hergibt kannst Du auch mehrere nehmen und die Bits dort gleich passend zu den Ports verwenden. Am Ende machst dann eine Oder-Verknüpfung mit dem, was sonst noch so an den jeweiligen Ports ausgegeben werden soll. Ein paar logische Operatoren sind in der Regel immer effizienter als irgendwelche verschachtelten if-Abfragen. Ist halt immer die Sache. Man kann viel optimieren wenn man bereit ist dafür etwas Speicher zu opfern. Beispielsweise, wenn die PWM ausgänge auf zwei POrts verteilt sind, und Du das Ausgangs-Latch des jeweiligen Port's lesen kannst, könnte das als Pseudocode so aussehen:
1 | unsigned char pwmbits_1, pwmbits_2; |
2 | unsigned char pwmenable_1, pwmenable_2; |
3 | unsigned char pwmpol_1, pwmpol_2; |
4 | usingned char pwm_1, pwm_2, pwm_3, .... |
5 | unsigned char pwm_count; |
6 | unsigned char pwm_temp; |
7 | |
8 | interrupt() |
9 | { |
10 | // |
11 | pwmbits_1 = 0x00; |
12 | pwmbits_2 = 0x00; |
13 | |
14 | if(pwm_1 > pwm_count) |
15 | { |
16 | pwmbits_1 |= 0x01; |
17 | } |
18 | |
19 | if(pwm_2 > pwm_count) |
20 | { |
21 | pwmbits_1 |= 0x04; |
22 | } |
23 | |
24 | if(pwm_3 > pwm_count) |
25 | { |
26 | pwmbits_1 |= 0x20; |
27 | } |
28 | |
29 | .... |
30 | |
31 | if(pwm_10 > pwm_count) |
32 | { |
33 | pwmbits_2 |= 0x02; |
34 | } |
35 | |
36 | pwm_count++; |
37 | |
38 | // polarität anpassen |
39 | pwmbits_1 ^= pwmpol_1; |
40 | |
41 | // nur enabelte PWM bits nehmen |
42 | pwmbits_1 &= pwmenable_1; |
43 | |
44 | |
45 | pwmbits_2 ^= pwmpol_2; |
46 | pwmbits_2 &= pwmenable_2; |
47 | |
48 | // aktuellen status eines ports lesem |
49 | pwm_temp = PORTA_LATCH; |
50 | |
51 | // alle alten PWM bits ausmaskieren |
52 | pwm_temp &= ~pwmenable_1; |
53 | |
54 | // aktuelle PWM bits einfügen |
55 | pwm_temp |= pwmbits_1; |
56 | |
57 | // port aktualisieren |
58 | PORTA_LATCH = pwm_temp; |
59 | |
60 | pwm_temp = PORTB_LATCH; |
61 | pwm_temp &= ~pwmenable_2; |
62 | pwm_temp |= pwmbits_2; |
63 | PORTB_LATCH = pwm_temp; |
64 | } |
Hier habe ich die polarität halt vor dem Enable-Maskieren gemacht, damit wäre jeder nicht benutzte Kanal immer auf logisch 0, unabhängig der Polarität. Sollte die Polarität auch für ausgeschaltete PWM Kanäle gelten dann muss die Reihenfolge der beiden Anweisungen halt vertauscht werden. Wenn die PWM auf mehr als zwei Ports verteilt ist dann halt entsprechend mehr Bytes verwenden. Man opfert hier also etwas Speicher um sich dann aber eine Menge Code zu sparen. Keine Ahnung ob dein µC es erlaubt den aktuellen Ausgabezustand eines Ports zu lesen, ich habe das einfach mal angenommen. Falls nicht muss man halt eine andere Lösung dafür finden. Entweder man nimmt weitere Bytes und setzt dort die Bits entsprechend dem was man sonst im Code an Portbits setzt, oder man beisst in den sauren Apfel und testet halt die einzelnen Bits der fertigen PWM Bytes und setzt/löscht dann die Portbits entsprechend einzeln. Grüße, Chris
Achja, und falls auf einem 8-Bit Port alles für PWM verwendet wird dann kann man sich das vorherige auslesen, maskieren, verknüpfen und zurückschreiben natürlich sparen und gibt stattdessen direkt das PWM-Byte auf dem Port aus. Grüße, Chris
@Chris: Besten Dank! Ich werd das mal so umsetzen und Feedback geben, ob's funktioniert. Bin echt gespannt. Jedenfalls wär's interessant zu lernen, wo bzw. wie man den SDCC "manuell" optimieren kann :) Ralf
Ralf schrieb: > @Chris: > Besten Dank! Ich werd das mal so umsetzen und Feedback geben, ob's > funktioniert. Bin echt gespannt. Jedenfalls wär's interessant zu lernen, > wo bzw. wie man den SDCC "manuell" optimieren kann :) > > Ralf Es lohnt sich auf jeden Fall immer sich mit dem Assembler-Code, den der jeweilige Compiler produziert, auseinanderzusetzen. Das gilt ganz besonders für "Universalcompiler" die mehr als eine Architektur bedienen können. Nur zu oft kommt es da nämlich vor das viele Dinge einfach zu Allgemein gehalten sind, obwohl der jeweilige µC optimiertere Methoden anbietet. Auch haben Compiler manchmal Eigenheiten die einem die Bedienung zwar erleichtern, aber den Code dafür aufblähen. Genau da gibt es für den SDCC auch ein schöne Beispiel in Bezug auf die PIC's. Diese haben ja getrennten RAM und Flash Speicher. Bei Microchip's C18 ist es dann so das man angeben muss aus welchem Part was kommt, damit er den richtigen Code erzeugt. Wenn man z.B. eine Textausgabe-Routine hat, muss man die doppelt anlegen: einmal für Strings aus dem RAM und einmal für Strings aus dem Flash. Bei'm SDCC hingegen ist das nicht nötig. Sieht natürlich erstmal wie ein Vorteil aus, was es ja eigentlich auch ist. Aber wenn man sich den generierten Assembler-Code ansieht wird schnell klar wo das Problem dabei ist: Es wird ganz einfach für beide Speicher-Versionen generiert und aufgrund eines Bits im übergebenen Pointer dann entschieden ob der Teil für RAM- oder Flash-Zugriff ausgeführt werden soll. Nachher wundert man sich dann warum der Code so groß wird... Da haben spezialisierte Compiler natürlich oft einen Vorteil. Die müssen halt genau eine Architektur ansprechen, und "wissen" daher meistens wie gewisse Probleme am effizientesten gelöst werden können. Grüße, Chris
@Chris: So, habe nun diverse Versuche gemacht. Den Ansatz der logischen Verknüpfung habe ich wieder verwerfen müssen, das hat's z.T. noch schlimmer gemacht. Allerdings hab ich zu struct-Arrays eine Aussage von einem SDCC-Entwickler bekommen, dass das nicht optimal über den Index angesprochen wird, ich sollte besser einen Pointer auf das Array verwenden. Zusammen mit der Angabe des passenden Speichertyps, auf den der Pointer zeigt, hab ich jetzt eine Version, die in einem Schnelltest wesentlich besser reagiert hat. Ralf
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.