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
| 2 | *
| 3 | */
| 4 | void TimerInit(u8 deadTime) {
| 5 |
| 6 | RCC_APB2PeriphClockCmd(TIM1_CLK, ENABLE);
| 7 | /* Initialize basic structures to default */
| 8 | TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
| 9 | TIM_OCStructInit(&TIM_OCInitStructure);
| 10 | /* Time base configuration */
| 11 |
| 12 | TIM_TimeBaseStructure.TIM_Prescaler = 256;
| 13 | TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
| 14 | TIM_TimeBaseStructure.TIM_Period = 0x00FF;
| 15 | TIM_TimeBaseStructure.TIM_ClockDivision = 0;
| 16 | TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
| 17 |
| 18 | TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
| 19 | TIM_UpdateRequestConfig(TIM1, TIM_UpdateSource_Global);
| 20 | TIM_UpdateDisableConfig(TIM1,DISABLE);
| 21 |
| 22 | /* Channel 1, 2 and 3 Configuration in PWM mode */
| 23 | TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
| 24 | TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
| 25 | TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
| 26 | TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
| 27 | TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
| 28 | TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
| 29 | TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Set;
| 30 |
| 31 | TIM_OCInitStructure.TIM_Pulse = 0x0000;
| 32 | TIM_OC1Init(TIM1, &TIM_OCInitStructure);
| 33 | TIM_OCInitStructure.TIM_Pulse = 0x0000;
| 34 | TIM_OC2Init(TIM1, &TIM_OCInitStructure);
| 35 | TIM_OCInitStructure.TIM_Pulse = 0x0000;
| 36 | TIM_OC3Init(TIM1, &TIM_OCInitStructure);
| 37 | }
| 38 | /* Setup Hall inputs */
| 39 | RCC_APB2PeriphClockCmd(HALL_GPIO_CLK | RCC_APB2Periph_AFIO, ENABLE);
| 40 | GPIO_InitStructure.GPIO_Pin = H1_GPIO_PIN | H2_GPIO_PIN | H3_GPIO_PIN;
| 41 | GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
| 42 | GPIO_Init(HALL_GPIO_PORT, &GPIO_InitStructure);
| 43 | /* Setup Hall Interrupt */
| 44 | /* Connect EXTI0 Line to Hall1 GPIO Pin */
| 45 |
| 46 | GPIO_EXTILineConfig(HALL_EXTI_PORT_SOURCE, HALL1_EXTI_PIN_SOURCE);
| 47 | GPIO_EXTILineConfig(HALL_EXTI_PORT_SOURCE, HALL2_EXTI_PIN_SOURCE);
| 48 | GPIO_EXTILineConfig(HALL_EXTI_PORT_SOURCE, HALL3_EXTI_PIN_SOURCE);
| 49 |
| 50 | /* Configure Hall EXTI line */
| 51 |
| 52 | EXTI_InitStructure.EXTI_Line = (HALL1_EXTI_LINE | HALL2_EXTI_LINE | HALL3_EXTI_LINE);
| 53 | EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
| 54 | EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
| 55 | EXTI_InitStructure.EXTI_LineCmd = ENABLE;
| 56 | EXTI_Init(&EXTI_InitStructure);
| 57 | EXTI_ClearITPendingBit(HALL1_EXTI_LINE | HALL2_EXTI_LINE | HALL3_EXTI_LINE);
| 58 | /* Enable and set EXTI0 Interrupt to the lowest priority */
| 59 | NVIC_InitStructure.NVIC_IRQChannel = HALL1_EXTI_IRQn;
| 60 | NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
| 61 | NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;
| 62 | NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
| 63 | NVIC_Init(&NVIC_InitStructure);
| 64 | NVIC_InitStructure.NVIC_IRQChannel = HALL2_EXTI_IRQn;
| 65 | NVIC_Init(&NVIC_InitStructure);
| 66 | NVIC_InitStructure.NVIC_IRQChannel = HALL3_EXTI_IRQn;
| 67 | NVIC_Init(&NVIC_InitStructure);
| 68 | }
|
Und hier die beiden ISRs: 1 | /* return the number of the current hallsensors
| 2 | * which should be between 1 and 6
| 3 | * values of 0 or 7 mean an error and should set the ERR_HALLSENSOR bit in 'error'
| 4 | */
| 5 | uint16_t GetHall(void) {
| 6 | volatile uint8_t n = 0;
| 7 | volatile uint16_t hall;
| 8 | // Delay(HALLDELAY);
| 9 | hall = HALL_GPIO_PORT->IDR & HALLMASK;
| 10 | while(n<6){
| 11 | if (hall == (HALL_GPIO_PORT->IDR & HALLMASK)) {
| 12 | n++;
| 13 | // Delay(HALLDELAY);
| 14 | }
| 15 | else {
| 16 | n = 0;
| 17 | hall = (HALL_GPIO_PORT->IDR & HALLMASK);
| 18 | }
| 19 | }
| 20 | if ( (hall < 1) | (hall > 6)) hallerrcount++;
| 21 | if (hallerrcount > 3 ) error |= ERR_HALLSENSORS;
| 22 | halls = hall; // debug
| 23 | return hall;
| 24 | }
| 25 | void HallIRQ() {
| 26 | EXTI_ClearFlag(EXTI_Line0 | EXTI_Line1 | EXTI_Line2);
| 27 | volatile uint16_t hall = GetHall();
| 28 | // spurious trap - this routine should only be fired if we have a
| 29 | // valid sensor change
| 30 | if (lastHall != hall ) {
| 31 | MotorSynchronizedUpdate();
| 32 | if (fastFlags.driveWaveform == WAVEFORM_BLOCK_COMMUTATION) {
| 33 | BlockCommutate(GetDesiredDirection(), hall);
| 34 | }
| 35 | ActualDirectionUpdate(lastHall, hall);
| 36 | lastHall = hall;
| 37 | }
| 38 | }
| 39 | /**
| 40 | * @brief This function handles External line0 interrupt request.
| 41 | * @param None
| 42 | * @retval None
| 43 | * Hall Sensor 1 IRQ
| 44 | */
| 45 | void EXTI0_IRQHandler(void)
| 46 | {
| 47 | if(EXTI_GetITStatus(HALL1_EXTI_LINE) != RESET)
| 48 | {
| 49 | /* Clear the EXTI line pending bit */
| 50 | EXTI_ClearITPendingBit(HALL1_EXTI_LINE);
| 51 | HallIRQ();
| 52 | }
| 53 | }
| 54 | /*
| 55 | * Hall Sensor 2 IRQ
| 56 | */
| 57 | void EXTI1_IRQHandler(void)
| 58 | {
| 59 | if(EXTI_GetITStatus(HALL2_EXTI_LINE) != RESET)
| 60 | {
| 61 | /* Clear the EXTI line pending bit */
| 62 | EXTI_ClearITPendingBit(HALL2_EXTI_LINE);
| 63 | HallIRQ();
| 64 | }
| 65 | }
| 66 | /*
| 67 | * Hall Sensor 3 IRQ
| 68 | */
| 69 | void EXTI2_IRQHandler(void)
| 70 | {
| 71 | if(EXTI_GetITStatus(HALL3_EXTI_LINE) != RESET)
| 72 | {
| 73 | /* Clear the EXTI line pending bit */
| 74 | EXTI_ClearITPendingBit(HALL3_EXTI_LINE);
| 75 | HallIRQ();
| 76 | }
| 77 | }
| 78 | /* the TIM1 Interrupt is fired by an overflow of the main PWM timer
| 79 | * it calculates a new Sine Table Pointer and writes the values to the OC registers
| 80 | * In Block Commutation it only writes an updated value of amplitudes by calling
| 81 | * BlockCommutationDuty()
| 82 | * regular tasks include firing the PID controller and reading the ADC.
| 83 | */
| 84 | void TIM1_UP_TIM16_IRQHandler(void){
| 85 | //uint8_t speedRegTicks = 0;
| 86 | // microcontroller.net : Clear the IT pending bit before doing any GPIO stuff
| 87 | TIM_ClearITPendingBit(TIM1,(TIM_IT_Update | TIM_IT_CC1 | TIM_IT_CC2 | TIM_IT_CC3));
| 88 | LED_GPIO_PORT->BSRR = ERROR_LED_PIN; // debug
| 89 | CommutationTicksUpdate();
| 90 | if (fastFlags.driveWaveform == WAVEFORM_SINUSOIDAL)
| 91 | {
| 92 | uint8_t tempU, tempV, tempW;
| 93 | {
| 94 | uint8_t sineTablePtr ;
| 95 | AdjustSineTableIndex(sineTableIncrement);
| 96 | //Add sine table offset to pointer. Must be multiplied by 3, since one
| 97 | //value for each phase is stored in the table.
| 98 | sineTablePtr =(uint8_t)(sineTableIndex >> 8) * 3;
| 99 | tempU = sineTable[sineTablePtr++];
| 100 | if (GetDesiredDirection() == DIRECTION_REVERSE)
| 101 | {
| 102 | tempV = sineTable[sineTablePtr++];
| 103 | tempW = sineTable[sineTablePtr];
| 104 | }
| 105 | else
| 106 | {
| 107 | tempW = sineTable[sineTablePtr++];
| 108 | tempV = sineTable[sineTablePtr];
| 109 | }
| 110 | }
| 111 |
| 112 | /* Scale sine modulation values to the current amplitude
| 113 | * and poke them into the Compare registers
| 114 | */
| 115 |
| 116 | TIM_SetCompare1(TIM1,(uint16_t)(amplitude * tempU) >> 8);
| 117 | TIM_SetCompare2(TIM1,(uint16_t)(amplitude * tempV) >> 8);
| 118 | TIM_SetCompare3(TIM1,(uint16_t)(amplitude * tempW) >> 8);
| 119 | }
| 120 | else if (fastFlags.driveWaveform == WAVEFORM_BLOCK_COMMUTATION)
| 121 | {
| 122 | blockCommutationDuty = amplitude * BLOCK_COMMUTATION_DUTY_MULTIPLIER;
| 123 | if (blockCommutationDuty > 255)
| 124 | {
| 125 | blockCommutationDuty = 255;
| 126 | }
| 127 | BlockCommutationSetDuty((uint8_t)blockCommutationDuty);
| 128 | }
| 129 |
| 130 | // regular tasks call the SpeedController
| 131 | speedRegTicks++;
| 132 | if (speedRegTicks >= SPEED_CONTROLLER_TIME_BASE)
| 133 | {
| 134 | SpeedController();
| 135 | speedRegTicks = 0;
| 136 | }
| 137 | LED_GPIO_PORT->BRR = ERROR_LED_PIN;
| 138 | }
|
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.
Hallo Matthias,
vielen Dank für deinen Code!
Meinen habe ich gestern nach langer Bastelei zum Laufen gebracht. Er ist
wie folgt:
1 | /*============================================================================*/
| 2 | static void init_pwm(void)
| 3 | /*------------------------------------------------------------------------------
| 4 | Function:
| 5 | initialise timer 1 for pwm generation; pwm outputs are pa8, pe11, pa10.
| 6 | initialise timer 8 for pwm generation; pwm outputs are pc6, pc7, pc8.
| 7 | in: none
| 8 | out: none
| 9 | ==============================================================================*/
| 10 | {
| 11 | duty_right = 0u;
| 12 | duty_left = 0u;
| 13 |
| 14 | /* enable and reset timer 1 */
| 15 | RCC_APB2ENR |= BIT_00;
| 16 | TIM1_CCER = 0u;
| 17 |
| 18 | /* configure timer 1 auto-reload buffer */
| 19 | TIM1_CR1 = BIT_07;
| 20 |
| 21 | /* set the autoreload value and the prescaler */
| 22 | TIM1_ARR = ARR_VALUE;
| 23 | TIM1_PSC = PSC_VALUE;
| 24 |
| 25 | /* configure the channels 1 to 3 */
| 26 | TIM1_CCMR1 = (BIT_14 | BIT_13 | BIT_11 | BIT_06 | BIT_05 | BIT_03);
| 27 | TIM1_CCMR2 = (BIT_06 | BIT_05 | BIT_03);
| 28 |
| 29 | /* set all duty cycles to 0 */
| 30 | TIM1_CCR1 = 0u;
| 31 | TIM1_CCR2 = 0u;
| 32 | TIM1_CCR3 = 0u;
| 33 |
| 34 | /* force register update */
| 35 | TIM1_EGR = BIT_00;
| 36 |
| 37 | /* enable timer 1 channels 1 to 3 */
| 38 | TIM1_CCER = (BIT_10 | BIT_08 | BIT_06 | BIT_04 | BIT_02 | BIT_00);
| 39 |
| 40 | /* set deadtime and enable output */
| 41 | TIM1_BDTR = (BIT_15 | PWM_DEADTIME);
| 42 |
| 43 | /* start timer 1 */
| 44 | TIM1_CR1 |= BIT_00;
| 45 | } /* end init_pwm */
| 46 |
| 47 | /*============================================================================*/
| 48 | static void gpio_init(void)
| 49 | /*------------------------------------------------------------------------------
| 50 | Function:
| 51 | initialise all gpio pins.
| 52 | in: none
| 53 | out: none
| 54 | ==============================================================================*/
| 55 | {
| 56 | /* enable gpio port a */
| 57 | RCC_AHB1ENR |= BIT_00;
| 58 |
| 59 | /* switch to alternate function on port a pins */
| 60 | GPIOA_MODER |= (BIT_21 | BIT_17 | BIT_15 | BIT_05 | BIT_03 | BIT_01);
| 61 |
| 62 | /* enable pullups for the hall sensors */
| 63 | GPIOA_PUPDR |= (BIT_04 | BIT_02 | BIT_00);
| 64 |
| 65 | /* enable alternate function 1 for port a: timer channels for timer 2 */
| 66 | GPIOA_AFRL |= (BIT_00 | BIT_04 | BIT_08);
| 67 |
| 68 | /* enable alternate function 1 for port a: timer channels for timer 1 */
| 69 | GPIOA_AFRH |= (BIT_00 | BIT_08);
| 70 |
| 71 | /* enable alternate function 3 for pa7: timer 8 inverted channel 1 */
| 72 | GPIOA_AFRL |= (BIT_29 | BIT_28);
| 73 |
| 74 | /* enable gpio port b */
| 75 | RCC_AHB1ENR |= BIT_01;
| 76 |
| 77 | /* switch to alternate function on port b pins */
| 78 | GPIOB_MODER |= (BIT_31 | BIT_29 | BIT_27 | BIT_17 |
| 79 | BIT_15 | BIT_13 | BIT_03 | BIT_01);
| 80 |
| 81 | /* enable pullups for the hall sensors */
| 82 | GPIOB_PUPDR |= (BIT_16 | BIT_14 | BIT_12);
| 83 |
| 84 | /* enable alternate function 2 for port b: timer 4 channels 1 to 3 */
| 85 | GPIOB_AFRL |= (BIT_29 | BIT_25);
| 86 | GPIOB_AFRH |= BIT_01;
| 87 |
| 88 | /* enable alternate function 1 for port b: timer 1 inverted channels 1 to 3 */
| 89 | GPIOB_AFRH |= (BIT_28 | BIT_24 | BIT_20);
| 90 |
| 91 | /* enable alternate function 3 for port b: timer 8 inverted channels 2 & 3 */
| 92 | GPIOB_AFRL |= (BIT_05 | BIT_04 | BIT_01 | BIT_00);
| 93 |
| 94 | /* enable gpio port c */
| 95 | RCC_AHB1ENR |= BIT_02;
| 96 |
| 97 | /* switch to alternate function for port c pins */
| 98 | GPIOC_MODER |= (BIT_23 | BIT_21 | BIT_17 | BIT_15 | BIT_13);
| 99 |
| 100 | /* enable alternate function 3 - timer 8 channels */
| 101 | GPIOC_AFRL |= (BIT_29 | BIT_28 | BIT_25 | BIT_24);
| 102 | GPIOC_AFRH |= (BIT_01 | BIT_00);
| 103 |
| 104 | /* switch to the alternate function 7 - usart 3 */
| 105 | GPIOC_AFRH |= (BIT_14 | BIT_13 | BIT_12 | BIT_10 | BIT_09 | BIT_08);
| 106 |
| 107 | /* enable gpio port d */
| 108 | RCC_AHB1ENR |= BIT_03;
| 109 |
| 110 | /* enable output pin and set it to low */
| 111 | GPIOD_MODER |= BIT_30;
| 112 | GPIOD_BSRR = BIT_31;
| 113 |
| 114 | /* enable gpio port e */
| 115 | RCC_AHB1ENR |= BIT_04;
| 116 |
| 117 | /* switch to alternate function on port e pin */
| 118 | GPIOE_MODER |= BIT_23;
| 119 |
| 120 | /* enable alternate function 1 for pin pe11 */
| 121 | GPIOE_AFRH |= BIT_12;
| 122 | } /* end gpio_init */
| 123 |
| 124 | /*============================================================================*/
| 125 | static void init_timers(void)
| 126 | /*------------------------------------------------------------------------------
| 127 | Function:
| 128 | initialise the timers for the hall sensors
| 129 | in: none
| 130 | out: none
| 131 | ==============================================================================*/
| 132 | {
| 133 | /* enable timer 2 and 4 */
| 134 | RCC_APB1ENR |= (BIT_02 | BIT_00);
| 135 |
| 136 | /* reset timer 2 */
| 137 | TIM2_CCER = 0u;
| 138 |
| 139 | /* enable autoreload buffer */
| 140 | TIM2_CR1 = BIT_07;
| 141 |
| 142 | /* timer 2 input is xor of ch1, ch2 and ch3 */
| 143 | TIM2_CR2 = BIT_07;
| 144 |
| 145 | /* enable counter mode */
| 146 | TIM2_SMCR = (BIT_06 | BIT_02 | BIT_01 | BIT_00);
| 147 |
| 148 | /* configure the channels 1 to 3 */
| 149 | TIM2_CCMR1 = (BIT_08 | BIT_00);
| 150 | TIM2_CCMR2 = BIT_00;
| 151 |
| 152 | /* enable timer 2 interrupts */
| 153 | TIM2_DIER = BIT_06;
| 154 | NVIC_ISER0 |= BIT_28;
| 155 |
| 156 | /* enable timer 2 */
| 157 | TIM2_CR1 |= BIT_00;
| 158 |
| 159 | /* reset timer 4 */
| 160 | TIM4_CCER = 0u;
| 161 |
| 162 | /* enable autoreload buffer */
| 163 | TIM4_CR1 = BIT_07;
| 164 |
| 165 | /* timer 4 input is xor of ch1, ch2 and ch3 */
| 166 | TIM4_CR2 = BIT_07;
| 167 |
| 168 | /* enable counter mode */
| 169 | TIM4_SMCR = (BIT_06 | BIT_02 | BIT_01 | BIT_00);
| 170 |
| 171 | /* configure the channels 1 to 3 */
| 172 | TIM4_CCMR1 = (BIT_08 | BIT_00);
| 173 | TIM4_CCMR2 = BIT_00;
| 174 |
| 175 | /* enable timer 4 interrupts */
| 176 | TIM4_DIER = BIT_06;
| 177 | NVIC_ISER0 |= BIT_30;
| 178 |
| 179 | /* enable timer 4 */
| 180 | TIM4_CR1 |= BIT_00;
| 181 | } /* end init_timers */
| 182 |
| 183 |
| 184 | /*============================================================================*/
| 185 | static void timer4_irq(void)
| 186 | /*------------------------------------------------------------------------------
| 187 | Function:
| 188 | called by timer 2 interrupts
| 189 | in: none
| 190 | out: none
| 191 | ==============================================================================*/
| 192 | {
| 193 | uint32_t hall;
| 194 |
| 195 | /* tell the os that interrupts are processed */
| 196 | os_irq_enter();
| 197 |
| 198 | /* get current state of the hall sensors */
| 199 | hall = ((GPIOB_IDR & (BIT_06 | BIT_07 | BIT_08)) >> 6);
| 200 |
| 201 | /* set appropriate pins for commutation */
| 202 | switch(hall)
| 203 | {
| 204 | /* hallsensor 1 */
| 205 | case (BIT_00):
| 206 | {
| 207 | /* enable pwm channels 1 and 3 */
| 208 | // TIM1_CCER = (BIT_11 | BIT_10 | BIT_09 | BIT_08 | BIT_02 | BIT_00);
| 209 | TIM1_CCER = (BIT_10 | BIT_09 | BIT_08 | BIT_03 | BIT_02 | BIT_00);
| 210 | TIM1_CCER |= (BIT_04 | BIT_05);
| 211 |
| 212 | /* set duty cycles */
| 213 | TIM1_CCR1 = duty_left;
| 214 | TIM1_CCR2 = 0u;
| 215 | TIM1_CCR3 = duty_left;
| 216 | }
| 217 | break;
| 218 |
| 219 | /* hallsensor 1 and 2 */
| 220 | case (BIT_01 | BIT_00):
| 221 | {
| 222 | /* enable pwm channels 2 and 3 */
| 223 | // TIM1_CCER = (BIT_11 | BIT_10 | BIT_09 | BIT_08 | BIT_06 | BIT_04);
| 224 | TIM1_CCER = (BIT_10 | BIT_09 | BIT_08 | BIT_07 | BIT_06 | BIT_04);
| 225 | TIM1_CCER |= (BIT_01 | BIT_00);
| 226 |
| 227 | /* set duty cycles */
| 228 | TIM1_CCR1 = 0u;
| 229 | TIM1_CCR2 = duty_left;
| 230 | TIM1_CCR3 = duty_left;
| 231 | }
| 232 | break;
| 233 |
| 234 | /* hallsensor 2 */
| 235 | case (BIT_01):
| 236 | {
| 237 | /* enable pwm channels 1 and 2 */
| 238 | // TIM1_CCER = (BIT_06 | BIT_04 | BIT_03 | BIT_02 | BIT_01 | BIT_00);
| 239 | TIM1_CCER = (BIT_07 | BIT_06 | BIT_04 | BIT_02 | BIT_01 | BIT_00);
| 240 | TIM1_CCER |= (BIT_09 | BIT_08);
| 241 |
| 242 | /* set duty cycles */
| 243 | TIM1_CCR1 = duty_left;
| 244 | TIM1_CCR2 = duty_left;
| 245 | TIM1_CCR3 = 0u;
| 246 | }
| 247 | break;
| 248 |
| 249 | /* hallsensor 2 and 3 */
| 250 | case (BIT_02 | BIT_01):
| 251 | {
| 252 | /* enable pwm channels 1 and 3 */
| 253 | // TIM1_CCER = (BIT_10 | BIT_08 | BIT_03 | BIT_02 | BIT_01 | BIT_00);
| 254 | TIM1_CCER = (BIT_11 | BIT_10 | BIT_08 | BIT_02 | BIT_01 | BIT_00);
| 255 | TIM1_CCER |= (BIT_04 | BIT_05);
| 256 |
| 257 | /* set duty cycles */
| 258 | TIM1_CCR1 = duty_left;
| 259 | TIM1_CCR2 = 0u;
| 260 | TIM1_CCR3 = duty_left;
| 261 | }
| 262 | break;
| 263 |
| 264 | /* hallsensor 3 */
| 265 | case (BIT_02):
| 266 | {
| 267 | /* enable pwm channels 2 and 3 */
| 268 | // TIM1_CCER = (BIT_10 | BIT_08 | BIT_07 | BIT_06 | BIT_05 | BIT_04);
| 269 | TIM1_CCER = (BIT_11 | BIT_10 | BIT_08 | BIT_06 | BIT_05 | BIT_04);
| 270 | TIM1_CCER |= (BIT_00 | BIT_01);
| 271 |
| 272 | /* set duty cycles */
| 273 | TIM1_CCR1 = 0u;
| 274 | TIM1_CCR2 = duty_left;
| 275 | TIM1_CCR3 = duty_left;
| 276 | }
| 277 | break;
| 278 |
| 279 | /* hallsensor 1 and 3 */
| 280 | case (BIT_02 | BIT_00):
| 281 | {
| 282 | /* enable pwm channels 1 and 2 */
| 283 | // TIM1_CCER = (BIT_07 | BIT_06 | BIT_05 | BIT_04 | BIT_02 | BIT_00);
| 284 | TIM1_CCER = (BIT_06 | BIT_05 | BIT_04 | BIT_03 | BIT_02 | BIT_00);
| 285 | TIM1_CCER |= (BIT_08 | BIT_09);
| 286 |
| 287 | /* set duty cycles */
| 288 | TIM1_CCR1 = duty_left;
| 289 | TIM1_CCR2 = duty_left;
| 290 | TIM1_CCR3 = 0u;
| 291 | }
| 292 | break;
| 293 |
| 294 | /* undefined hallsensor state */
| 295 | default:
| 296 | {
| 297 | /* disable all pwm channels */
| 298 | TIM1_CCER = 0u;
| 299 |
| 300 | /* set all duty cycles to 0 */
| 301 | TIM1_CCR1 = 0u;
| 302 | TIM1_CCR2 = 0u;
| 303 | TIM1_CCR3 = 0u;
| 304 |
| 305 | /* this should NEVER happen */
| 306 | assert(0);
| 307 | }
| 308 | break;
| 309 | }
| 310 |
| 311 | /* clear the interrupt flag */
| 312 | TIM4_SR = 0u;
| 313 |
| 314 | /* leave interrupt state */
| 315 | os_irq_exit();
| 316 | } /* end timer4_irq */
|
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 | const uint16_t blockCommutationTableForward[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 | const uint16_t blockCommutationTableReverse[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)
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
|