Forum: Mikrocontroller und Digitale Elektronik STM32F4 und Hallsensoren


von Tobias P. (hubertus)


Lesenswert?

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

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

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. )

von Jo D. (Firma: Jo) (discovery)


Lesenswert?

> 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.

von Tobias P. (hubertus)


Lesenswert?

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.

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

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.

von Tobias P. (hubertus)


Lesenswert?

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.

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

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.

von Tobias P. (hubertus)


Lesenswert?

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?

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

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.

von Tobias P. (hubertus)


Lesenswert?

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

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

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.

von Tobias P. (hubertus)


Lesenswert?

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

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

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.

von Tobias P. (hubertus)


Angehängte Dateien:

Lesenswert?

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.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.