Moin Tim, Moin Micha,
zunächst recht herzlichen Dank für das Feedback!
> Komische Art und Weise wie du programmierst
Nunja, jeder hat seinen eigenen Stil und Herangehensweise... ich hätte
die Präprozessor-Sachen mal für's Beispiel außen vor lassen sollen, die
sind ja doch sehr speziell.
> Der größter Schnitzer ist der Register Eintrag CMPA
Stimmt da hatte ich die Periodendauer verkleinert und vergessen den
Compare-Wert anzupassen, danke für den Hinweis! Ist aber nicht
entscheidend, da ich den Comparewert ja zur Laufzeit per Debugger
einstelle. Dies funktioniert auch für PWM-Modul 4, jedoch nicht für
PWM-Modul 5.
Was sind die kleineren Schnitzer? (Stell Dir die Präprozessormakros
expandiert vor; um die Systematik geht es mir hier nicht, sondern darum
ob der Ablauf eine PWM zu konfigurieren und anzusteuern so richtig ist.)
> ich benutze den UP- und DOWN-Zähler, also in HEX 0x2
Ja, mache ich doch auch (wobei ich später wohl schnellere, asymmetrische
PWMS benötigen werde):
1 | //Zunächst
|
2 | EPwm4Regs.TBCTL.bit.CTRMODE = 0x3; //Stop-Freeze während des Setups
|
3 |
|
4 | // ...
|
5 | // PWM-Einstellungen: Period, Compare, Action-Qualifier, Deadband, Chopper,
|
6 | // Tripzones
|
7 | // ...
|
8 |
|
9 | //Dann
|
10 | EPWM4Regs.TBCTL.bit.CTRMODE = 0x2; //UP-DWN-Count nach Abschluss des Setups
|
(Diese Vorgehensweise entspringt dem TI One-Day Workshop und ist IMHO
absolut sinnvoll.)
> Die .all Anweisung finde ich schwer zu lesen
Es geht, sie sind alles andere als selbsterklärend; man kann sie aber
kommentieren und dann darauf beharren, das ein Zweiter der den Quellcode
liest, die Einstellungen einfach glauben muss, oder im Handbuch
nachschlagen muss ;-)
Nein, ernsthaft: Ich geb' Dir aber recht, die struct unions aus den
Peripheral-Headern sind viel selbsterklärender. Der Code stammt von TI
(One-Day Workshop) und ich hatte ihn zum Testen nach dem Nachvollziehen
der Einstellungen erstmal so übernommen. (Insbesondere an Stellen, wo
man das ganze Register auf 0x0 setzt, ist es einfacher dies per
.all-bitmask zu erledigen.)
> Damit hast du mehr zu schreiben
Ja, aber nur einmalig
> Welchen Sinn haben die Makros in UR_Macros.h? [...E]s ist (IMO)
> wesentlich unübersichtlicher
Das mag am Anfang so erscheinen, die Systematik erkläre ich gleich.
> und die Autovervollständigung funktioniert damit vermutlich auch nicht
Die Autovervollständigung ist bei mir extrem(!) langsam, funktioniert
mal und mal nicht: Regelmäßig schlage ich nach, wie die Schreibweise
denn jetzt nochmal war und welche unions es so gab. Wenn die
Autovervollständigung bei mir sinnvoll funktionieren würde, wäre es ein
echter Gewinn, so ist es aber einfach nur umständlich. Wenn ich mir die
häufig benutzten unions als Makro definiere kann ich das so machen, dass
ich mir die Sachen einprägen kann und nicht immer wieder die selben
Dinge schreiben muss und effektiv auch weniger schreibe. (Mittlerwele
benutze ich auch einen externen Editor und nicht den CCS-internen, da
habe ich ganz andere Möglichkeiten.)
Zu der Motivation der Macros:
Motivation
- absolut (bezogen auf dem F28335) generische PWM-Funktionen
- in dem vorliegenden Beispiel eine "Pumpen-PWM" und eine
"Lüfter-PWM"
- die geschriebene Funktion ist völlig unabhängig davon, auf
welchem PWM-Modul die Pumpen-PWM und auf welchem PWM-Modul die
Lüfter-PWM irgendwann mal liegen wird
- es ist ihr auch egal auf welchen GPIO-Pin diese Module ihre
Ausgänge "mappen"
- die Funktionen bleiben (wenn sie erstmal funktionieren) für alle
Zeiten so, wie sie sind
- die Anpassungen, welches PWM-Modul ich mit welcher Funktion
ansteuere und welcher GPIO-Pin damit bespaßt wird, regel ich
unabhängig in einer Header-Datei, die funktional und inhaltlich von
der "Wrapper"-Funktion getrennt ist
Die Register-Sätze und GPIO-Pins lege ich dazu in einer Header-Datei
fest
1 | UR_Constants_IO.h:
|
2 |
|
3 | #define EPWMREGS_PUMP EPwm4Regs
|
4 | #define EPWMREGS_FAN EPwm5Regs
|
5 | #define GPIO_PUMP GPIO8 //EPWM4A
|
6 | #define GPIO_FAN GPIO6 //EPWM5A
|
Beispielsweise aus den Makros EPWMREGS_PUMP und GPIO_PUMP, möchte ich
mir nun einen entsprechenden PWM-Code zusammenbasteln.
Damit das funktioniert brauche ich im wesentlichen ein
Präprozessor-Makro, das
mir flexibel die Ausdrücke der Art
1 | GpioCtrlRegs.GPAPUD.bit.GPIO8 = 0;
|
2 | GpioCtrlRegs.GPAMUX1.bit.GPIO8 = 1;
|
3 | EPwm4Regs.TBCTL.bit.CTRMODE = 0x3;
|
erzeugt.
Das Makro
1 | #define GLUE(A,B) A##.##B
|
erzeugt diese Paarungen. So wird aus
1 | myname = GLUE(Bennet.Gedan) z.B. myname = Bennet.Gedan
|
Ich kann dann also schreiben:
1 | GLUE(GpioCtrlRegs.GPAPUD.bit,GPIO_PUMP) = 0;
|
2 | GLUE(GpioCtrlRegs.GPAMUX1.bit,GPIO_PUMP) = 1;
|
3 | GLUE(EPWMREGS_PUMP,TBCTL.bit.CTRMODE) = 0;
|
und dann an einer einzigen Stelle anpassen, welches PWM-Register ich
damit ansteuern möchte:
1 | #define EPWMREGS_PUMP Epwm4Regs // Option A
|
2 | //#define EPWMREGS_PUMP Epwm5Regs // Option B
|
3 |
|
4 | #define GPIO_PUMP GPIO8 // Option A-1
|
5 | //#define GPIO_PUMP GPIO32 // Option A-2 (Fantasiewert, hab die Doku gerade nicht vor Augen)
|
6 | #define GPIO_PUMP GPIO6 // Option B-1
|
7 | //#define GPIO_PUMP GPIO50 // Option B-2 (Fantasiewert, hab die Doku nicht vor Augen)
|
Von den Optionen wähle ich dann diejenige aus, die für meine Hardware
gerade passend ist. Wenn ich also den selben Code auf dem selben
Prozessor für andere Peripherie-Konfiguration compilieren möchte, habe
ich eine zentrale Stelle, an der ich einmal die Peripherie einstelle und
meine generischen Funktionen bleiben davon unberührt. (Vorteil 1)
Darüberhinaus finde ich
1 | GLUE(GpioCtrlRegs.GPAPUD.bit,GPIO_PUMP) = 0;
|
2 | GLUE(GpioCtrlRegs.GPAMUX1.bit,GPIO_PUMP) = 1;
|
3 | GLUE(EPWMREGS_PUMP,TBCTL.bit.CTRMODE) = 0
|
viel selbsterklärender (vorausgesetzt, man kapiert, was GLUE macht) als
1 | GpioCtrlRegs.GPAPUD.bit.GPIO8 = 0;
|
2 | GpioCtrlRegs.GPAMUX1.bit.GPIO8 = 1;
|
3 | EPwm4Regs.TBCTL.bit.CTRMODE = 0x3;
|
Im oberen Beispiel sehe ich sofort, dass ich hier eine Pumpe ansteuern
möchte (das verstehe ich auch nach zehn Jahren noch), im unteren
Beispiel weiß ich nach zehn Jahren nicht mehr, das GPIO8 ein
Pumpenanschluss war und das ich diesen über das PWM-Modul 4 anspreche.
(Vorteil 2)
Idealerweise muss ich dann den PWM-Code gar nicht mehr anpassen und kann
entsprechende PWMs flexibel nach Bedarf auf IOs mappen, wie es mir
gerade passt (also im Rahmen dessen, was die GPIOs erlauben ;-) Da muss
ich mich natürlich selber schlau machen, bevor ich es "irgendwie"
einstelle.)
Ich sage idealerweise, da ich dann ein Problem bekomme, wenn GPAPUD gar
kein GPIO33 mehr definiert hat und ich es aber so einstelle. Dann
bekomme ich einen Compiler-Fehler und muss meinen Code doch anpassen.
Das sind dann aber nur marginale Anpassungen, vielleicht kann man aber
sogar dies noch umgehen ;-)
Dem ganzen sei hinterhergeworfen, dass jeder seine eigene Methodik hat
zu programmieren, auch gebe ich gerne zu, dass es zunächst ein
ziemlicher Aufwand ist; hoffe aber, dass es später durchaus der
Flexibilität dient und zu einem gewissen Maße selbst-dokumentierend ist.
Mich hat die Idee überzeugt, auch wenn man es sicher eleganter gestalten
kann.
Zurück zum Thema:
Evtl. habe ich bei den PWMs noch Probleme mit externer Hardware, da ich
noch externe Peripherie an's eZdsp angeschlossen habe. Ich werde einfach
mal das eZdsp im Alleinbetrieb testen und ausschließen, das mir hier
irgendwas dazwischen funkt. Außerdem kann ich dann alle PWM-Module mal
der Reihe nach durchprobieren.
Kann mir jemand vielleicht spontan einen Code zur Verfügung stellen, der
bei ihm zwei (oder mehr) unabhängige PWM-Signale herausstellt? Dann
sollte recht schnell zu sehen sein, welche Dinge ich vergessen habe.
Nochmals Danke und Entschuldigung für die lange Erklärung; ich hoffe sie
war nachvollziehbar und spannend ;-)
Grüße,
Bennet