STM32 BLDC Control with HALL Sensor: Unterschied zwischen den Versionen

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche
Zeile 10: Zeile 10:
BLDC Bridge FETs: TIM1
BLDC Bridge FETs: TIM1
HallSensor: TIM4
HallSensor: TIM4
Recommendation: For your first projects and to learn more i suggest to use a protection 3 phase motor driver. In there documents you find good information about shoot-through protection, on-time, off-time, dead-time, hall-steps, rpm and field frequency, cutoff filter frequency and many more ...…. Some of the chips offers also simplified usage of sensorless motor control because he detects the rotorposition and gives back a virtual Hall Signal, see the TMC product.
Allegro A4935 http://www.allegromicro.com/en/Products/Part_Numbers/4935/
TMC 603 http://www.trinamic.com/tmc/render.php?sess_pid=446
If you have a simple FET bridge driver then you must carfully calculate the dead time which is a prameter in the motortimer.
Active Freewheeling
Without active freewheeling the body diodes during PWM OFF time produce a huge amount of power loss. With active freewheeling this power loss can be reduced.


INFO: Check HallSensor Inputs they often needs PullUp`, use RC Filters to filter bad signals.
INFO: Check HallSensor Inputs they often needs PullUp`, use RC Filters to filter bad signals.

Version vom 9. April 2011, 21:11 Uhr

STM32 BLDC Control with HALL Sensor

The code is not a complete project, it`s to show you how to use the motor timer to control an BLDC Motor in combination with an HALL Sensor on another timer.

Intro

The system needs two timers. One to control PWM for the Bridge FETs and another Timer to identify the HallSensor singnals. The HallSensor timer triggers the Motor timer commutation event. The connection betwenn these two timers is done in the background with events. There is no direct need for interrupt handling to commutate the motor timer.

BLDC Bridge FETs: TIM1 HallSensor: TIM4

Recommendation: For your first projects and to learn more i suggest to use a protection 3 phase motor driver. In there documents you find good information about shoot-through protection, on-time, off-time, dead-time, hall-steps, rpm and field frequency, cutoff filter frequency and many more ...…. Some of the chips offers also simplified usage of sensorless motor control because he detects the rotorposition and gives back a virtual Hall Signal, see the TMC product.

Allegro A4935 http://www.allegromicro.com/en/Products/Part_Numbers/4935/ TMC 603 http://www.trinamic.com/tmc/render.php?sess_pid=446

If you have a simple FET bridge driver then you must carfully calculate the dead time which is a prameter in the motortimer.

Active Freewheeling Without active freewheeling the body diodes during PWM OFF time produce a huge amount of power loss. With active freewheeling this power loss can be reduced.

INFO: Check HallSensor Inputs they often needs PullUp`, use RC Filters to filter bad signals.

Code for the HallSensor timer

Internal Connection from Hall/Enc Timer to Motor Timer. The HALL Timer Output is direct connected to the Motor Timer Commutation Trigger. If the correct combination of Motor and Hall/Enc Timer is selected then this is done for you. If you can not use the internal Connection you have to do this in an Interrupt manually.

Info: Only following Combinations are possible !!

Check STM32 Reference Manual for Internal Trigger 1 to 4 (ITR1 to ITR4) see Table: "TIMx Internal trigger connection"

If Motor is on Timer1 this is possible
  Hall/Enc is Timer 2 --> Motor is Timer 1 ==> use TIM_TS_ITR1
  Hall/Enc is Timer 3 --> Motor is Timer 1 ==> use TIM_TS_ITR2
  Hall/Enc is Timer 4 --> Motor is Timer 1 ==> use TIM_TS_ITR3
  Hall/Enc is Timer 5 --> Motor is Timer 1 ==> use TIM_TS_ITR0
If Motor is on Timer8 this is possible
  Hall/Enc is Timer 1 --> Motor is Timer 1 ==> use TIM_TS_ITR0
  Hall/Enc is Timer 2 --> Motor is Timer 1 ==> use TIM_TS_ITR1
  Hall/Enc is Timer 4 --> Motor is Timer 1 ==> use TIM_TS_ITR2
  Hall/Enc is Timer 5 --> Motor is Timer 1 ==> use TIM_TS_ITR3

<c> void configHallSensorTimer(void) {

// Timer 3 decodes the 3 HallSensor input lines // see referenze manual page 305

// define timer clock // between two changes on the hall sensor lines on the lowest rotation speed (eg. 1/100 from max. speed) the timer must not overflow // define timer counter clock appropriate

…..…

// enable port pins for hall inputs RCC_APB2PeriphClockCmd(...); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_...; GPIO_InitStructure.GPIO_Mode = …. GPIO_Init(...., &GPIO_InitStructure);

...…..

RCC_APB1PeriphClockCmd(TIM4_CLK, ENABLE);

// timer base configuration 
 TIM_TimeBaseStructure.TIM_Prescaler = 126; // 126 => 3,5s till overflow ; 285,714kHz TimerClock [36MHz/Prescaler] ; 
 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; 
 TIM_TimeBaseStructure.TIM_Period = 65535; 
 TIM_TimeBaseStructure.TIM_ClockDivision = 0; 
 TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; 
 TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); 
   
 // enable hall sensor
// T1F_ED will be connected to  HallSensoren Imputs TIM4_CH1,TIM4_CH2,TIM4_CH3 
 TIM_SelectHallSensor(TIM4, ENABLE); 
 //  TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, uint16_t TIM_EncoderMode, 
 //        uint16_t TIM_IC1Polarity, uint16_t TIM_IC2Polarity) 
 

// HallSensor event is delivered with singnal TI1F_ED (this is XOR of the three hall sensor lines) // Signal TI1F_ED: falling and rising ddge of the inputs is used

 TIM_SelectInputTrigger(TIM4, TIM_TS_TI1F_ED); 
 
 // On every TI1F_ED event the counter is resetted and update is tiggered 
 TIM_SelectSlaveMode(TIM4, TIM_SlaveMode_Reset); 
 // Channel 1 in input capture mode 
 // on every TCR edge (build from TI1F_ED which is a HallSensor edge)  
 // the timervalue is copied into ccr register and a CCR1 Interrupt (TIM_IT_CC1)  is fired 
 TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; 
 TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; 
 TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_TRC; // listen to T1, the  HallSensorEvent 
 TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; // Div:1, every edge 
 // noise filter: 1111 => 72000kHz / factor (==1) / 32 / 8 -> 281kHz 
 TIM_ICInitStructure.TIM_ICFilter = 0xF; // input noise filter (reference manual page 322) 
 TIM_ICInit(TIM4, &TIM_ICInitStructure); 
 
 // channel 2 can be use for commution delay between hallsensor edge and switching the FET into the next step. if this delay time is over the channel 2 generates the commutation signal to the motor timer
 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; 
 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; 
 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; 
 TIM_OCInitStructure.TIM_Pulse = 1; // 1 is no delay; 2000 = 7ms
 TIM_OC2Init(TIM4, &TIM_OCInitStructure); 
 
 // clear interrupt flag
 TIM_ClearFlag(TIM4, TIM_FLAG_CC2); 
 
 //TIM_SelectMasterSlaveMode(TIM4, TIM_MasterSlaveMode_Enable); 
 // TIM_SelectOutputTrigger(TIM4, TIM_TRGOSource_OC1); 
 // timer2 output compate signal is connected to TRIGO 
 TIM_SelectOutputTrigger(TIM4, TIM_TRGOSource_OC2Ref); 
 
 // Enable channel 2 compate interrupt request 
 TIM_ITConfig(TIM4, TIM_IT_CC1 | TIM_IT_CC2, ENABLE);  // TIM_IT_CC1 | TIM_IT_CC2 

 // Enable output compare preload 
 //TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable); 
 
 // Enable ARR preload 
 //TIM_ARRPreloadConfig(TIM4, ENABLE); 
 
 // Enable update event 
 //TIM_ClearFlag(TIM4, TIM_FLAG_Update); 
 //TIM_ITConfig(TIM4, TIM_IT_Update, DISABLE); 

// we use preemption interrupts here, BLDC Bridge switching and Hall has highest priority

NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn; 
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00; 
 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; 
 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 
 NVIC_Init(&NVIC_InitStructure); 
 // -------------------
 // HallSensor is now configured, if BLDC Timer is also configured the after enabling timer 4 
 // the motor will start after next overflow of the hall timer because this generates the first startup
 // motor cummutation event
 TIM_Cmd(TIM4, ENABLE); 

} </c>


<c> void incCommutationDelay(void) {

 TIM4->CCR2 = (TIM4->CCR2) + 1; 
 ….

} </c>

<c> void decCommutationDelay(void) {

 TIM4->CCR2 = (TIM4->CCR2) - 1;
 ….

} </c>


<c> // ------------- HallSensor interrupt handler -----------------

// this handles TIM4 irqs (from HallSensor) void TIM4_IRQHandler(void) {

 if (TIM_GetITStatus(TIM4, TIM_IT_CC1) != RESET) 
 { 
   TIM_ClearITPendingBit(MOTOR_TMC603_HALLENC_TIM, TIM_IT_CC1); 
   // calculate motor  speed or else with CCR1 values
   hallccr1 = TIM4->CCR1;
   …...........
 } 
 else if (TIM_GetITStatus(TIM4, TIM_IT_CC2) != RESET) 
 { 
   TIM_ClearITPendingBit(MOTOR_TMC603_HALLENC_TIM, TIM_IT_CC2); 
   // this interrupt handler is called AFTER the motor commutaton event is done
   // after commutation the next motor step must be prepared
   // use inline functions in irq handlers static __INLINE funct(..) {..} 
   BLDCMotorPrepareCommutation(); 

} else {

   ; //.... this should not happen 
 } 

} </c>

Code for BLDC motor control timer

<c> void configMotorBridgeTimer(void) {

// define timer clock, motor timer can be TIM1 or TIM8 RCC_APB2PeriphClockCmd(...); ..….

// define the 6 output pins for the bridge, if needed define he input pin for emergeny stop …...

// Chopper Frequency (PWM for the FETs) // 18kHz was good in empiric tests // ARR = SystemCoreClock / ChopperFreq // ARR defines also the resolution of the Chopper PWM

  1. define BLDC_CHOPPER_PERIOD ((uint16_t)4000)
 // Time Base configuration
 TIM_TimeBaseStructure.TIM_Prescaler = 0;
 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
 TIM_TimeBaseStructure.TIM_Period = BLDC_CHOPPER_PERIOD;
 TIM_TimeBaseStructure.TIM_ClockDivision = 0;
 TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
 TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);


 // Channel 1, 2, 3 – set to PWM mode - all 6 outputs
 // per channel on output is  low side fet, the opposite is for high side fet
 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Timing;
 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
 TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
 TIM_OCInitStructure.TIM_Pulse = 0; // BLDC_ccr_val
 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
 TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
 TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
 TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Set;
 TIM_OC1Init(TIM1, &TIM_OCInitStructure);
 TIM_OC2Init(TIM1, &TIM_OCInitStructure);
 TIM_OC3Init(TIM1, &TIM_OCInitStructure);


 // activate preloading the CCR register
 TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); 
 TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable); 
 TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable); 


 /* automatic output enable, break off, dead time ca. 200ns and 
 // no lock of configuration */
 TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable;
 TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable;
 TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_OFF;
 // DeadTime value n=1 bis 31: from 14ns to 1,7us
 // DeadTime value n=129 bis 159: from 1,7us to 3,5ms
 // DeadTime value 7 => 98ns
 // ... see programmers reference manual
 // DeadTime[ns] = value * (1/SystemCoreFreq) (on 72MHz: 7 is 98ns)
 TIM_BDTRInitStructure.TIM_DeadTime = 7; // 98ns
 TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;


 // enabel this if you use emergency stop signal
 // TIM_BDTRInitStructure.TIM_Break = TIM_Break_Enable;
 // TIM_BDTRInitStructure.TIM_BreakPolarity = MOTOR_TMC603_EMSTOP_POLARITY;
 TIM_BDTRInitStructure.TIM_Break = TIM_Break_Disable;
 TIM_BDTRConfig(TIM1, &TIM_BDTRInitStructure);


 // preload ARR register
 TIM_CCPreloadControl(TIM1, ENABLE);


 // activate COM (Commutation) Event from Slave (HallSensor timer) through TRGI
 enableHallCommutateSignal();


 // Internal connection from Hall/Enc Timer to Motor Timer
 // eg. TIM1 (BLDC Motor Timer) is Slave of TIM3 (Hall Timer)


 // Internal connection from Hall/Enc Timer to Motor Timer
 // Choose carefully from the following possible combination 
 // check programmers reference manual
 // TIM_SelectInputTrigger(TIM1, TIM_TS_ITR0);
// MotorTimer = TIM1, HallTimer = TIM5
 // TIM_SelectInputTrigger(TIM1, TIM_TS_ITR1);
// MotorTimer = TIM1, HallTimer = TIM2
 // TIM_SelectInputTrigger(TIM1, TIM_TS_ITR2);
// MotorTimer = TIM1, HallTimer = TIM3
TIM_SelectInputTrigger(TIM1, TIM_TS_ITR3);
// MotorTimer = TIM1, HallTimer = TIM4
 // TIM_SelectInputTrigger(TIM8, TIM_TS_ITR0);
// MotorTimer = TIM8, HallTimer = TIM1
 // TIM_SelectInputTrigger(TIM8, TIM_TS_ITR1);
// MotorTimer = TIM8, HallTimer = TIM2
 // TIM_SelectInputTrigger(TIM8, TIM_TS_ITR2);
// MotorTimer = TIM8, HallTimer = TIM4
 // TIM_SelectInputTrigger(TIM8, TIM_TS_ITR3);
// MotorTimer = TIM8, HallTimer = TIM5
 // Enable interrupt, motor commutation has high piority and has a higher subpriority then the hall sensor
 NVIC_InitStructure.NVIC_IRQChannel = TIM1_TRG_COM_IRQn;
 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;  // highest priority
 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
// highest priority
 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
 NVIC_Init(&NVIC_InitStructure);


 // Interrupt for hardwired EmergencyStop
(if needed)
 // Timer 1 Motor Emergency Break Input
 // NVIC_InitStructure.NVIC_IRQChannel = TIM1_BRK_IRQn;
 // NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;
 // NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
 // NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
 // NVIC_Init(&NVIC_InitStructure);


 // --------- activate the bldc bridge ctrl. ----------
 // in a project this will be done late after complete 
 // configuration of other peripherie
 // enable COM (commutation) IRQ
 TIM_ITConfig(TIM1, TIM_IT_COM, ENABLE);
 // enable motor timer
 TIM_Cmd(TIM1, ENABLE);
 // enable motor timer main output (the bridge signals)
 TIM_CtrlPWMOutputs(TIM1, ENABLE);

} </c>

<c> // enable the connection between HallTimer and MotorTimer void enableHallCommutateSignal() {

  TIM_SelectCOM(TIM1, ENABLE);

} </c>


<c> // disable the connection between HallTimer and MotorTimer void disableHallCommutateSignal() {

  TIM_SelectCOM(TIM1, DISABLE);

} </c>


<c> // BLDC motor steps // every row from 1 to 6 is called by a hall state // every column a FET from 3-phase bridge // motor off is at row 0 BLDC_BRIDGE_STATE_VORWARD[0] // cw - rechtslauf - positiv // { 1H,1L , 2H,2L , 3H,3L } // BLDC motor steps // every row from 1 to 6 is one of the 6 motor vector state // every column a FET from 3-phase bridge // all FETs off at row 0 or 8 (this pattern should not come from the hallsensor) // cw - rechtslauf - positiv // { 1H,1L , 2H,2L , 3H,3L } static uint8_t BLDC_BRIDGE_STATE_VORWARD[8][6] = { // Motor step

  { FALSE,FALSE   ,   FALSE,FALSE   ,  FALSE,FALSE },  // 0
  { FALSE,TRUE    ,   TRUE ,FALSE   ,  FALSE,FALSE },  // 2
  { FALSE,FALSE   ,   FALSE,TRUE    ,  TRUE ,FALSE },  // 4
  { FALSE,TRUE    ,   FALSE,FALSE   ,  TRUE ,FALSE },  // 3
  { TRUE ,FALSE   ,   FALSE,FALSE   ,  FALSE,TRUE  }   // 6
  { FALSE,FALSE   ,   TRUE ,FALSE   ,  FALSE,TRUE  },  // 1
  { TRUE ,FALSE   ,   FALSE,TRUE    ,  FALSE,FALSE },  // 5
  { FALSE,FALSE   ,   FALSE,FALSE   ,  FALSE,FALSE },  // 0

}; </c>


<c> // This function handles motor timer trigger and commutation interrupts // can be used for calculation... void TIM1_TRG_COM_IRQHandler(void) {

 TIM_ClearITPendingBit(TIM1, TIM_IT_COM);
 // commutationCount++;

} </c>

<c> // This is called from HALL timer interrupt handler // remember: // if hall a hall edge is detected // first the motor commutation event is done // next this routine is called which has to prepare the next motor step // (which FET must be switched on or off) // active freewhelling is used to minimize power loss // // code should be easy to understand and to debug... for practical use // you should optimize it

// static __INLINE void BLDCMotorPrepareCommutation(void) {


 // next bridge step calculated by HallSensor inputs
 // if there was an hall event without changing the hall position, do nothing
 //
 // in principle on every hall event you can go to the next motor 
 // step but i had sometimes problems that the motor was running
 // on an harmonic wave (??) when the motor was without load
 uint16_t newhallpos = ((GPIO_ReadInputData(GPIOD) & 0x7000) >> 12);


 if (newhallpos == hallpos) return;
 lasthallpos = hallpos;
 hallpos = newhallpos;


 // this is only for motor direction forward  
 BH1 = BLDC_BRIDGE_STATE_VORWARD[hallpos][0];
 BL1 = BLDC_BRIDGE_STATE_VORWARD[hallpos][1];
 BH2 = BLDC_BRIDGE_STATE_VORWARD[hallpos][2];
 BL2 = BLDC_BRIDGE_STATE_VORWARD[hallpos][3];
 BH3 = BLDC_BRIDGE_STATE_VORWARD[hallpos][4];
 BL3 = BLDC_BRIDGE_STATE_VORWARD[hallpos][5];  



 // **** this is with active freewheeling ****  
 
 // Bridge FETs for Motor Phase U
 if (BH1) {
   // PWM at low side FET of bridge U
   // active freewheeling at high side FET of bridge U 
   // if low side FET is in PWM off mode then the hide side FET 
   // is ON for active freewheeling. This mode needs correct definition
   // of dead time otherwise we have shoot-through problems
   TIM_SelectOCxM(TIM1, TIM_Channel_1, TIM_OCMode_PWM1);
   TIM_CCxCmd(TIM1, TIM_Channel_1, TIM_CCx_Enable);
   TIM_CCxNCmd(TIM1, TIM_Channel_1, TIM_CCxN_Enable);
 } else {
   // Low side FET: OFF
   TIM_CCxCmd(TIM1, TIM_Channel_1, TIM_CCx_Disable);
   if (BL1){
    // High side FET: ON 
    TIM_SelectOCxM(TIM1, TIM_Channel_1, TIM_ForcedAction_Active);
     TIM_CCxNCmd(TIM1, TIM_Channel_1, TIM_CCxN_Enable);
   } else {
     // High side FET: OFF
     TIM_CCxNCmd(TIM1, TIM_Channel_1, TIM_CCxN_Disable);
   }
 }


 // Bridge FETs for Motor Phase V
 if (BH2) {
   TIM_SelectOCxM(TIM1, TIM_Channel_2, TIM_OCMode_PWM1);
   TIM_CCxCmd(TIM1, TIM_Channel_2, TIM_CCx_Enable);
   TIM_CCxNCmd(TIM1, TIM_Channel_2, TIM_CCxN_Enable);
 } else {
   TIM_CCxCmd(TIM1, TIM_Channel_2, TIM_CCx_Disable);
   if (BL2){
     TIM_SelectOCxM(TIM1, TIM_Channel_2, TIM_ForcedAction_Active);
     TIM_CCxNCmd(TIM1, TIM_Channel_2, TIM_CCxN_Enable);
   } else {
     TIM_CCxNCmd(TIM1, TIM_Channel_2, TIM_CCxN_Disable);
   }
 }


 // Bridge FETs for Motor Phase W
   if (BH3) {
   TIM_SelectOCxM(TIM1, TIM_Channel_3, TIM_OCMode_PWM1);
   TIM_CCxCmd(TIM1, TIM_Channel_3, TIM_CCx_Enable);
   TIM_CCxNCmd(TIM1, TIM_Channel_3, TIM_CCxN_Enable);
 } else {
   TIM_CCxCmd(TIM1, TIM_Channel_3, TIM_CCx_Disable);
   if (BL3){
     TIM_SelectOCxM(TIM1, TIM_Channel_3, TIM_ForcedAction_Active);
     TIM_CCxNCmd(TIM1, TIM_Channel_3, TIM_CCxN_Enable);
   } else {
     TIM_CCxNCmd(TIM1, TIM_Channel_3, TIM_CCxN_Disable);
   }
 }

} </c>

Some things good to know

<c> // You can manually generate an commutation event (like the hall sensor) TIM_GenerateEvent(TIM1, TIM_EventSource_COM);

// how to change the PWM value TIM1->CCR1 = new_ccr_val; TIM1->CCR2 = new_ccr_val; TIM1->CCR3 = new_ccr_val; </c>