Hallo Leute,
ich versuche mit dem STM32F407 die Hallsensoren eines BLDC abzufragen,
damit ich den dann ansteuern kann. PWM usw. habe ich schon
implementiert, die läuft auch. Was aber nicht geht, ist die Abfrage der
Hallsensoren.
Ich möchte das wie folgt tun:
die 3 Hallsensoren werden mit CH1, CH2 und CH3 des Timer 8 verbunden. Da
gibt es ja diesen XOR-Mode, wo angeblich die 3 Eingangssignale
XOR-Verknüpft werden. Diesen Mode benutze ich - Code siehe unten.
Der Gedanke dahinter ist, dass IMMER wenn sich an den Hallsensoren was
ändert, der Timerinterrupt aufgerufen wird. Das funktioniert auch, aber
der Hallsensor an CH1 wird interessanterweise gar nicht beachtet. Was
ist das Problem mit diesem Eingang? Ich sehe den Wald vor lauter Bäumen
nicht mehr, suche schon den ganzen Abend :-)
1
RCC_APB2ENR|=BIT_01;
2
TIM8_CCER=0;
3
4
/* configure timer 8 auto-reload buffer */
5
TIM8_CR1=BIT_07;
6
7
/* timer 8 input is xor of ch1, ch2 and ch3 */
8
TIM8_CR2=BIT_07;
9
10
/* enable counter mode */
11
TIM8_SMCR=(BIT_06|BIT_02|BIT_01|BIT_00);
12
TIM8_CR1|=BIT_00;
13
14
/* configure the channels 1 to 3 */
15
TIM8_CCMR1=(BIT_08|BIT_00);
16
TIM8_CCMR2=BIT_00;
17
18
/* enable timer 8 interrupts */
19
TIM8_DIER=BIT_06;
20
NVIC_ISER1|=BIT_13;
Bin mal gespannt.
Ach ja, was mich auch interessieren würde:
Die Kommutierung meines BLDC geschieht ja "von Hand", d.h. im
Timerinterrupt muss ich den gewünschten PWM-Dutycycle abhängig von der
aktuellen Rotorposition ins richtige Register setzen. Gibt es eine
Möglichkeit, das zu automatisieren, damit ich nicht bei jeder
Hallsensoränderung einen Interrupt zu verarbeiten haben? sonst wird ja
die Interruptlast recht hoch, denke ich.
Mit Gruss
Tobias
Tobias Plüss schrieb:> Das funktioniert auch, aber> der Hallsensor an CH1 wird interessanterweise gar nicht beachtet.
Zeig mal die Portkonfiguration. Es könnte auch nützlich sein, die
Pullups an den Halleingängen zu aktivieren.
Tobias Plüss schrieb:> Die Kommutierung meines BLDC geschieht ja "von Hand", d.h. im> Timerinterrupt muss ich den gewünschten PWM-Dutycycle abhängig von der> aktuellen Rotorposition ins richtige Register setzen. Gibt es eine> Möglichkeit, das zu automatisieren, damit ich nicht bei jeder> Hallsensoränderung einen Interrupt zu verarbeiten haben? sonst wird ja> die Interruptlast recht hoch, denke ich.
Nö, das kannste bei einem MC der STM32F4 Leistungsklasse vergessen. Die
paar Zyklen merkt der MC fast gar nicht. Eine kleine Tabelle mit den 6
möglichen Zuständen der Sensoren als Index wäre schon so ziemlich das
Äusserste an Optimierung, aber ein switch..case Konstrukt ist vermutlich
übersichlicher.
Übrigens muss das alles auch nicht im Timerinterupt geschehen, sondern
könnte auch ein Pinchange sein. Den Timerinterrupt könntest du dann
freier für die Geschwindigkeitsregelung und andere regelmässige Aufgaben
nehmen (PID Regler, ADCs usw. )
> Gibt es eine> Möglichkeit, das zu automatisieren
Du könntest einen DMA-Request auslösen, der dann aus einer Tabelle den
Wert ins Register schiebt. Das geht automatisch. Aber das ist wie
Matthias sagt nicht nötig.
Hallo,
ok, ich werde heute Abend dann mal meine Portinitialisierung hier noch
hochladen. Habe meinen Sourcecode sowieso noch ein wenig umgestellt.
Denn ich möchte nun 2 Motoren ansteuern; für den einen Motor benutze ich
TIM8, für den anderen TIM1.
Die Hallsensoren schliesse ich an TIM3 und TIM4 an, jeweils mit der
XOR-Funktion, und aktiviere bei beiden Timern den Trigger-Interrupt.
Wenn dann an den Hallsensoren was wackelt, dann wird der
Trigger-Interrupt angesprungen, und der Timer zählt 1 hoch - das ist
zumindest die Idee.
Kann man so machen, oder?
Ich überlege nur noch grade, ob ich das hochzählen des Timers für irgend
etwas nützliches noch gebrauchen kann. Speedmessung sollte ja möglich
sein....
Also, die Pullups sind auf alle Fälle aktiviert, auf dem Oszi sehe ich,
dass der Eingang tatsächlich auch mal High ist, und wenn man am Motor
dreht irgendwann mal auf Low springt.
Jo discovery schrieb:> Du könntest einen DMA-Request auslösen, der dann aus einer Tabelle den> Wert ins Register schiebt.
Hmm, ich muss glatt mal nachschauen, ob der DMA Controller auch ausm
Flash lesen kann. Bis jetzt hab ich nur RAM und Peripherie mit DMA
beschickt. Wäre ne nette kleine Möglichkeit, um z.B. Audio Samples auf
den DAC zu schreiben.
Tobias Plüss schrieb:> Die Hallsensoren schliesse ich an TIM3 und TIM4 an, jeweils mit der> XOR-Funktion, und aktiviere bei beiden Timern den Trigger-Interrupt.
Ich hatte für meine BLDC Projekte das mal probiert, aber der Aufwand mit
den Massen von Timern erschien mir viel zu hoch und kompliziert.
Mittlerweile hängen die Sensoren schlicht an den untersten 3 Bit eines
Ports und lösen einen EXTI aus, der sich dann um Kommutierung oder
Raumzeigersektor kümmert. Das war wesentlich einfacher, als sich durch
den Timer Dschungel zu wühlen.
Da der PWM Timer sowieso läuft, benutze ich diesen Interrupt für
Geschwindigkeitsmessung und regelmässige Aufgaben.
Hallo Matthias,
puh, endlich habe ich den Fehler in meinem Code gefunden.
Einer der GPIOs war falsch initialisiert. Nun läuft der Code aber. Ich
habe zwei BLDCs angeschlossen; die Hallsensoren sind jeweils an Timer 2
bzw. Timer 4. Mit dem XOR-Mode werden die Timer getaktet, wenn einer der
Hallsensoren "wackelt", der Timer zählt dann hoch und ein Interrupt wird
aufgerufen.
Im Interrupt stelle ich den Duty Cycle jeweils richtig ein. Geht jetzt
alles :-D
bei Interesse kann ich meinen Code hier hoch laden; es ist allerdings
keine grosse Hexerei, sich durch den Timer-Dschunel zu kämpfen (ich habe
auch nur relativ einfache Funktionen implementiert, keine "Verkettungen"
der Timer untereinander usw.)
Eigentlich würde ja jetzt nur noch ein Drehgeber fehlen, dann könnte man
eine Feldorientierte Regelung implementieren!
Wie misst du den Speed deiner Motoren? Das finde ich immer eine etwas
schwierige Messung; man muss z.B. einen Counter 1 Sekunde lang oder so
laufen lassen und die Pulse der Hallsensoren zählen oder irgend sowas.
Das gefällt mir nicht, schon nur weil es eine ziemliche Totzeit gibt,
die dann den Regler stört... ich möchte eigentlich später dann man die
Drehzahlen meiner BLDCs einigermassen gut regeln können; ich möchte vor
allem im unteren Drehzahlbereich fahren.
Tobias Plüss schrieb:> Eigentlich würde ja jetzt nur noch ein Drehgeber fehlen, dann könnte man> eine Feldorientierte Regelung implementieren!
Das benötigt keinen Drehgeber, abgesehen davon würde die Zeit um den
Drehgeber abzufragen (SSI oder EnDat) viel zu viel Zeit benötigen. Ich
benutze Sinustabellen und resynchronisiere die Pointer mit den
Hallsensor Events.
Das Prinzip ist einfach und wird in AppNote AVR447 gut besprochen und
umgesetzt. Bei AVR läuft es mit 3 Timern in einem 8-bitter
(ATMega88/168...), das Prinzip ist aber leicht umsetzbar auf einen der
Advanced Timer des STM32 mit 3 seiner 4 CC Register. (Ich nehme Timer8
auf einem STM32F103).
Am elegantesten alleridngs finde ich dafür allerdings AWEX auf einem
XMega.
Tobias Plüss schrieb:> Wie misst du den Speed deiner Motoren? Das finde ich immer eine etwas> schwierige Messung; man muss z.B. einen Counter 1 Sekunde lang oder so> laufen lassen und die Pulse der Hallsensoren zählen oder irgend sowas.> Das gefällt mir nicht, schon nur weil es eine ziemliche Totzeit gibt,> die dann den Regler stört
Die Geschwindigkeitsmessung fällt bei diesem Prinzip sozusagen ab. Der
Overflow IRQ des Timers triggert die Sinuserzeugung und inkrementiert
nebenbei was, unter anderem auch deswegen, um einen Motorstillstand zu
erkennen. Dieses Inkremetieren wird auch für die
Rotationsgeschwindigkeit benutzt.
Hallo,
ja also mit Drehgeber wäre es natürlich am bequemsten, da ich dann zu
jedem Zeitpunkt sofort die exakte Position weiss, und nicht "schätzen"
muss.
Du hast in deiner Anwendung also tatsächlich eine FOC implementiert?
sehr interessant!
Wie machst du das beim Starten des Motors, dort kannst du ja noch nicht
auf die Hallsensoren synchronisieren, weil du ja zu dem Zeitpunkt noch
nicht weisst, wie schnell der Motor dreht.
Avr447 kenne ich einigermassen; der Witz ist, soweit ich mich erinnere,
dass man die Pulsdauer der Hallsensoren misst, und dann dazwischen quasi
mit einer "Software-PLL" interpoliert. Oder so ähnlich, ja?
Tobias Plüss schrieb:> Wie machst du das beim Starten des Motors, dort kannst du ja noch nicht> auf die Hallsensoren synchronisieren, weil du ja zu dem Zeitpunkt noch> nicht weisst, wie schnell der Motor dreht.
Meine Anwendung ist aus AVR447 entwickelt. Dort wird der Motor in
Blockkommutierung gestartet und nach einigen 'richtigen' Sensorwechseln
auf Sinus umgeschaltet. Die 'Messuhr' für den Abstand zwischen den
Sensorwechseln läuft aber sofort mit.
Tobias Plüss schrieb:> der Witz ist, soweit ich mich erinnere,> dass man die Pulsdauer der Hallsensoren misst, und dann dazwischen quasi> mit einer "Software-PLL" interpoliert. Oder so ähnlich, ja?
So ähnlich - die Software misst die Zeit zwischen zwei Sensorwechseln
und interpoliert bei jedem PWM Timerüberlauf aus der 'Uhr' die Position
in die Sinustabelle. Die absolute Pulsdauer der Sensoren ist dabei
unwichtig, entscheidend sind nur die Flanken. Ein kompletter Zyklus
besteht also aus den 6 möglichen Sensorzuständen. Bei Sensorwechsel wird
der zum Sensormuster passende Sektorabschnitt in der Sinustabelle als
neuer aktueller Startwert benutzt.
Das ganze funktioniert sehr gut, ist schnell durch die Tabelle und
leicht portierbar, auch auf andere Motore. Bis jetzt läuft es hier auf
dem Orginal Mega88, dem XMega A3 und dem STM32F103. Als Motore benutze
ich hier einen Plattenspieler Pioneer Direct Drive zum Testen und ein
paar 48V/2000-4000W Radnabenmaschinen.
Hallo Matthias,
ich frage mal so direkt: darf ich mir deinen Code mal anschauen? Es
würde mich echt interessieren, wie du das mit dem STM32 machst. Wie das
Prinzip geht, habe ich schon verstanden, aber softwaremässig kriege ich
es irgendwie nicht so recht gebacken - irgendwie sehe ich den Timer-Wald
vor lauter Timern nicht mehr so recht ;-)
Ich lasse es jetzt erstmal mit Blockkommutierung und normal PWM laufen,
da weiss ich schon, dass es funktioniert und mache jetzt gar keine
grossen Tests. Was aber toll wäre, wenn ich die feldorientierte Regelung
gebacken bekäme. Ich möchte das gerne mal "zu Fuss" implementieren (mit
einer fertigen Library ist ja nicht so spassig ;-) ) und daher wäre ein
Denkanstoss ganz praktisch! Also, darf ich mal gucken? :D
Gruss
Tobias
Das ist sehr umfangreich. Insgesamt sind das ca. 100k Sourcecode,
verteilt auf einige Dateien. Da diese mehr oder weniger zusammenhängen ,
zeige ich mal nur die Timer und EXTI Init und die beiden ISR für Timer
und EXTI:
1
/* init the main PWM Timer 1 with 3 channels and dead time insertion
Ich weiss, das da jede Menge Dinge drin sind, die für dich keinen Sinn
ergeben, aber ich darf nicht den gesamten Code posten. Da das ein
Projekt in Fluss ist, sind da Parameter einstellbar und die
Fehlerbehandlung alleine sind massig Zeilen, da es um ein
sicherheitsrelevantes Teil geht.
Zwar dreht der Motor, aber die ganze Geschichte zeigt einige
Merkwürdigkeiten:
- der Motor dreht schneller, wenn der Duty cycle (Also TIM1_CCRx) klein
ist. Ist etwas unschön....
- irgendwie scheint es, als ob es regelmässig Kurzschlüsse in meiner
H-Brücke gäbe, denn der Motor "knattert" zwischendurch, und der
Stromverbrauch ist bei 50% duty cycle am grössten (?). Auf dem
Oszilloskop kann ich allerdings nichts erkennen, die Deadtime sieht da
völlig i.O. aus.
Meine Idee war eigentlich wie folgt:
beim BLDC ist ja immer eine Phase nicht bestromt (gehen wir mal von
gewöhnlicher Blockkommutierung aus). Ich wollte dann die beiden zu
bestromenden Phasen wie folgt bestromen: nehmen wir an, Phase 1 und 2
sollen bestromt werden, dann wollte ich folgendes implementieren:
Phase 1 low side _____----_____----_____----_____----
Phase 1 high side ---______---______---______---____
Phase 2 low side ---______---______---______---____
Phase 2 high side _____----_____----_____----_____----
kann man das nicht so machen? wie gesagt wird der Stromverbrauch
exorbitant, wenn der Duty cycle so um 50% ist. Bei 100% duty Cycle ist
der Stromverbrauch dann wesentlich kleiner.
Dann noch eine Frage:
du hast ja in deinem Gerät eine Feldorientierte Regelung implementiert.
Das wäre für meine Anwendung im Prinzip auch das richtige, denn ich muss
das Drehmoment genau im Griff haben und gut regeln können. Die Sache ist
die: für FOC musst du ja die Ströme in 2 Phasen kennen. Wie hast du das
gelöst? Hast du Stromsensoren in die Phasenzuleitungen zu deinem Motor
eingebaut?
Oder kann ich diese "floatende" Stormmmessung umgehen, indem ich einfach
immer an der lowside messe?
Ich stehe grad auf dem SChlauch. :-)
Gruss
Tobias Plüss schrieb:> - der Motor dreht schneller, wenn der Duty cycle (Also TIM1_CCRx) klein> ist. Ist etwas unschön....> - irgendwie scheint es, als ob es regelmässig Kurzschlüsse in meiner> H-Brücke gäbe, denn der Motor "knattert" zwischendurch, und der> Stromverbrauch ist bei 50% duty cycle am grössten (?).
Schau noch mal genau auf die Polarität der OC Ausgänge. Dein Code
(tschuldigung) ist etwas unübersichtlich, da du praktisch nur
irgendwelche Bits setzt, die ohne Datenblatt nicht zu verfolgen sind,
deswegen weiss ich jetzt nicht, ob da alles sauber ist. Das ist einer
der Gründe, warum ich zur Initialisierung meistens doch auf die Library
zurückgreife, warum auch nicht. Nur bei zeitkritischen Sachen fummele
ich dann direkt an den Registern.
Wenn du dir meine Timer Init nochmal anschaust - diese Polaritäten sind
für 3 Halbbrückentreiber vom Typ IR2110/2112/2113 mit positiver Logik.
Tobias Plüss schrieb:> Ich wollte dann die beiden zu> bestromenden Phasen wie folgt bestromen: nehmen wir an, Phase 1 und 2> sollen bestromt werden, dann wollte ich folgendes implementieren:
Ehrlich gesagt, löse ich das auch über eine Tabelle. Mein EXTI Interrupt
wird vom Sensor ausgelöst und liest dann den Sensorzustand aller drei
Sensoren. Dieser Wert dient als Index in eine Tabelle:
1
// PHASES to position in TIMx_CCER register
2
3
#define UH 0x0004
4
#define UL 0x0001
5
6
#define VH 0x0040
7
#define VL 0x0010
8
9
#define WH 0x0400
10
#define WL 0x0100
11
12
#define LOSIDEMASK (UL | VL | WL)
13
#define HISIDEMASK (UH | VH | WH)
14
#define PHASEMASK (LOSIDEMASK | HISIDEMASK)
15
// tables organized for enabling bits in TIMx_CCER
16
constuint16_tblockCommutationTableForward[8]=
17
{
18
0,// illegal value
19
WH|UL,// UL, WH
20
UH|VL,// VL, UH
21
WH|VL,// VL, WH
22
VH|WL,// WL, VH
23
VH|UL,// UL, VH
24
UH|WL,// WL, UH
25
0// illegal value
26
};
27
/*! \brief Block commutation port direction masks, reverse driving.
28
*/
29
constuint16_tblockCommutationTableReverse[8]=
30
{
31
0,
32
UH|VL,
33
VH|WL,
34
UH|WL,
35
WH|UL,
36
WH|VL,
37
VH|UL,
38
0
39
};
Wie du siehst, lasse ich Timer und CC Register einfach weiterlaufen und
kommutiere über enable/disable der CC Ausgänge. Das ist schweineschnell
und ich muss gar nicht an den CC Registern rumfummeln, die enthalten
nach wie vor nur den aktuellen PWM Wert (in allen drei der gleiche bei
Blockkommutierung). Wert 0 und Wert 7 gibts bei den Sensoren nie,
deswegen die 0 am Anfang und am Ende der Tabelle. Falls doch mal ein
solcher Wert auftauchen würde, werden sicherheitshalber alle Phasen
abgeschaltet.
Eine Tabelle ist für vorwärts und die andere für rückwärts.
Das ganze hat den Vorteil, das ich über ifdefs auch verschiedene Motore
anwählen kann, jeder mit seiner eigenen Tabelle. Nicht alle Motoren
benutzen ja das gleiche Schema.
Hallo,
so ich bins nochmal. Ich habe nochmal an meinem Code gebastelt.
Nun ergeben sich die Signale wie im angehängten Oszibild.
Channel A1 ist einer der Hallsensoren.
dann, 0 & 1 sind low und high side Phase 1
2 & 3 sind low und high side Phase 2
4 & 5 sind low und high side Phase 3
kann das so stimmen? Der Motor dreht jedenfalls ordentlich, allerdings
ist die Stromaufnahme ein wenig schlecht; im Bereich mittleren
PWM-Tastgrades ist sie am höchsten, und das obwohl ich eine Totzeit
eingebaut habe, siehe 2. Bild.
(Sorry für das Photo, das Oszi kann leider keine Bilder speichern)