User Tools

Site Tools

blog:2023-05-30_stm32_bldc_control_with_hall_sensor



2023-05-30 STM32 BLDC Control with HALL Sensor

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 they detect the rotorposition and give back a virtual Hall Signal, see the TMC product.
    • Allegro A4935
    • TMC 603
  • 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 PullUps, 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.
  • Only following Combinations are possible[1]
  • 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
  • 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
      // 126 => 3,5s till overflow ; 285,714kHz TimerClock [36MHz/Prescaler]
      TIM_TimeBaseStructure.TIM_Prescaler = 126; 
      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; 
      // listen to T1, the  HallSensorEvent 
      TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_TRC;
      // Div:1, every edge 
      TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
      // noise filter: 1111 => 72000kHz / factor (==1) / 32 / 8 -> 281kHz 
      // input noise filter (reference manual page 322) 
      TIM_ICInitStructure.TIM_ICFilter = 0xF; 
      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_IT_CC1 | TIM_IT_CC2 
      TIM_ITConfig(TIM4, TIM_IT_CC1 | TIM_IT_CC2, ENABLE);
     
      // 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
      // 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); 
    }
    
    void incCommutationDelay(void) { 
      TIM4->CCR2 = (TIM4->CCR2) + 1; 
    }
    
    void decCommutationDelay(void) { 
      TIM4->CCR2 = (TIM4->CCR2) - 1;
    }
    
    // ------------- 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 
      } 
    }

Code for BLDC motor control timer

  • 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
    // the 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
    #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,7µs 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;
    
      // highest priority
      NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;
    
      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);
    }
    
    // enable the connection between HallTimer and MotorTimer
    void enableHallCommutateSignal() {
       TIM_SelectCOM(TIM1, ENABLE);
    }
    
    // disable the connection between HallTimer and MotorTimer
    void disableHallCommutateSignal() {
       TIM_SelectCOM(TIM1, DISABLE);
    }
    
    // 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 const 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
    };
    
    // 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++;
    }
    
    /* 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);
        }
      }
    }

Some things good to know

// 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;

TAGS

  • 145 person(s) visited this page until now.

Permalink blog/2023-05-30_stm32_bldc_control_with_hall_sensor.txt · Last modified: 2023/05/30 09:06 by jethro

oeffentlich