Forum: Digitale Signalverarbeitung / DSP / Machine Learning PWM-Generierung auf TMS320F28335 (eZdsp)


von Bennet G. (bgedan)


Angehängte Dateien:

Lesenswert?

Hallo Zusammen,

während meiner Diplomarbeit beschäftige ich mich derzeit extensiv mit 
der TMS320F28335-Familie, derzeit auf dem zugehörigen eZdsp-Board.


Ich habe bezüglich der Initialisierung der Prozessoren eine, für 
Fachleute, recht einfache Frage: Ich möchte über die PWM-Module mehrere 
unabhängige PWM-Signale ausgeben. Leider bekomme ich es nur hin, dass 
ein PWM-Modul eine Ausgabe erzeugt (namentlich das zuerst konfigurierte, 
welches an GPIO-Pin 8 anliegt.)

Folgenderweise gehe ich vor um die PWM-Signale zu erzeugen:

Zunächst benutze ich die "Peripheral and Header-Files", die auf ihrer 
Website für den Prozessortyp verfügbar sind und die entsprechenden 
Initialisierungsroutinen. Außerdem wird der Prozessor über das eZdsp-Kit 
von Spectrum Digital betrieben.

Zwei GPIO-Signale werden werden zunächst als PWM-Ausgänge geschaltet:

   GpioCtrlRegs.GPAPUD.bit.GPIO8= 0; // Enable pullup
   GpioCtrlRegs.GPAMUX1.bit.GPIO8 = 1; // PWM-Out

   GpioCtrlRegs.GPAPUD.bit.GPIO6= 0; // Enable pullup
   GpioCtrlRegs.GPAMUX1.bit.GPIO6= 1; // PWM-Out

Beide Module werden folgendermaßen konfiguriert:

   EPwm4Regs.TBCTL.bit.CTRMODE = 0x3;        // Disable the timer

   EPwm4Regs.TBCTL.all) = 0xC033;            // Configure timer control 
register
   EPwm4Regs.TBCTR) = 0x0000;                // Clear timer counter
   EPwm4Regs.TBPRD) = 10000;        // Set timer period
   EPwm4Regs.TBPHS.half.TBPHS) = 0x0000;    // Set timer phase

   EPwm4Regs.CMPA.half.CMPA) = 28125;    // Set PWM duty cycle
   EPwm4Regs.CMPCTL.all) = 0x0002;            // Compare control 
register

   EPwm4Regs.AQCTLA.all) = 0x0060;        // Action-qualifier control 
register A
   EPwm4Regs.AQSFRC.all) = 0x0000;        // Action-qualifier s/w force 
register

   EPwm4Regs.AQCSFRC.all) = 0x0000;        // Action-qualifier 
continuous s/w force register

   EPwm4Regs.DBCTL.bit.OUT_MODE) = 0;    // Deadband disabled
   EPwm4Regs.PCCTL.bit.CHPEN) = 0;        // PWM chopper unit disabled
   EPwm4Regs.TZCTL.bit.TZA) = 0x3;        // Trip action disabled for 
output A

   EPwm4Regs.TBCTL.bit.CTRMODE) = 0x2;    // Enable the timer in count 
up/down mode

(das selbe für EPWM5)

Zur Laufzeit werden (per Debugger) die Duty-Cycles per

 EPwm4Regs.CMPA.half.CMPA = DutyCycle;
 EPwm5Regs.CMPA.half.CMPA = DutyCycle;

gesetzt. Auf dem Oszilloskop ist dabei auf GPIO-Pin 8 auch ein 
entsprechendes PWM-Signal zu sehen. GPIO-Pin 6 liegt konstant auf 3.3 V, 
kein PWM-Signal.

Der benutze Code stammt aus dem TI-One-Day-Workshop. Nach meinen 
Verständnis sollte er zwei unabhängige PWM-Signale erzeugen. Ich denke, 
dass ich irgendwo eine Kleinigkeit vergesse oder einem Mißverständnis 
unterliege.

Kann mir jemand auf die Sprünge helfen?

Schonmal vorab herzlichen Dank für's Lesen,

Bennet

P.S.:

Den verwendeten Code habe ich (in sinnvoll gekürzter Weise) zur 
Weitergabe und Ansicht an die Mail angehängt.

von Tim R. (mugen)


Lesenswert?

Komische Art und Weise wie du programmierst, aber der DSC wird es wohl 
ausführen. Der größter Schnitzer ist der Register Eintrag CMPA, dieser 
Wert darf nicht größer sein als TBPRD. Eventuell würde ich noch den 
CTRMODE mir genauer ansehen, ich benutze den UP- und DOWN-Zähler, also 
in HEX 0x2. Die .all Anweisung finde ich schwer zu lesen und inzwischen 
brösel ich alles einzeln auf.

von Micha (Gast)


Lesenswert?

@ Bennet Gedan (bgedan)
Welchen Sinn haben die Makros in UR_Macros.h? Damit hast du mehr zu 
schreiben, es ist (IMO) wesentlich unübersichtlicher und die 
Autovervollständigung funktioniert damit vermutlich auch nicht...

von Bennet G. (bgedan)


Lesenswert?

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

von Bennet G. (bgedan)


Lesenswert?

Hallo Zusammen,

das Problem hat sich gelöst. Die PWMs lassen sich so ansteuern und ich 
habe nun auch PWM-Ausgaben auf jedem PWM-Kanal.

Die Ursache ist auf ungenaues Lesen der eZdsp-Dokumentation 
zurückzuführen. Ich hatte den Switch SW2 auf dem Board so eingestellt
1
1= OFF // GPIO28, 29, 30, 31 as expansion
2
2= OFF // MUX U22 Disabled (!)
3
3= OFF // GPIO8, 9, 10, 11 as expansion
4
4= OFF // MUX U23 Disabled
5
5= OFF // I2C EEPROM write protected
6
6= OFF // I2C EEPROM lowest address = 0

Muxer U23 routet gerade die GPIO-Pins 8, 9, 10, 11. Wenn man ihn 
disabled, sollte man sich nicht wundern, dass keine Signale mehr 
durchkommen. Da hab ich nicht genau genug hingeschaut.

Danke für eure Unterstützung!

von Micha (Gast)


Lesenswert?

>> Damit hast du mehr zu schreiben
> Ja, aber nur einmalig
Du musst aber jedes Mal GLUE(...) schreiben, also hast du nicht nur 
einmalig mehr zu schreiben... :P

Ich verstehe jetzt zwar was du mit den Makros bezwecken willst, aber 
nicht warum du dann nicht einfach
1
#define TEST_REG  GpioMuxRegs
2
#define TEST_BIT  GPIOB14
3
4
TEST_REG.GPBDIR.bit.TEST_BIT = 1;
statt
1
GpioMuxRegs.GPBDIR.bit.GPIOB14 = 1;
schreibst.

Bzw. (um dein obiges Beispiel aufzugreifen)
1
#define GPIO_PUMP      GPIO8     // Option A-1
2
#define EPWMREGS_PUMP  Epwm4Regs // Option A
3
4
GpioCtrlRegs.GPAPUD.bit.GPIO_PUMP = 0;
5
GpioCtrlRegs.GPAMUX1.bit.GPIO_PUMP = 1;
6
EPWMREGS_PUMP.TBCTL.bit.CTRMODE = 0;

Der einzige Nachteil dieser Methode ist, dass auch damit die 
Autovervollständigung im CCS nicht mehr funktioniert, da du die aber 
sowieso nicht nutzt, spielt das keine Rolle.

von Bennet G. (bgedan)


Lesenswert?

> aber nicht warum du dann nicht einfach [...]
> EPWMREGS_PUMP.GPAPUD.bit.GPIO_PUMP = 1;
> schreibst.

Das war auch mein erster Ansatz, hatte aber bisweilen nicht 
funktinoiert, da der Präprozessor das nicht richtig interpretiert hatte 
und mir dann der Compiler "um die Ohren flog". Deshalb musste ich's mit 
diesem Kunstgriff machen.

Ich hab's jetzt gerade nochmal getestet und es funktioniert! Seltsam, 
heute ist wohl der Tag der Reinwaschung ;-)

> da du die aber sowieso nicht nutzt, spielt das keine Rolle.

richtig, dem Editor den ich benutze, kann ich diese Schnippchen 
beibringen. CCS benutze ich meist nur noch zum Debugging.

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.