5 BEMF Acquisition Demo

This example shows how to setup the Timer/Counter type E (TCE) and Waveform Extension (WEX) peripherals to generate six complementary PWM signals to spin a three-phase Brushless Direct Current Motor (BLDC) or a Permanent Magnet Synchronous Motor (PMSM) with Sinusoidal Drive. This example also configures the Analog Comparator (AC) peripheral to measure the Back Electromotive Force (BEMF) when the motor is spinning.

The measurement window is one of the two dead time periods during a PWM cycle. Increasing the dead time period to 2 μs gives a big enough acquisition window to detect the current Zero-Cross, uninfluenced by the driving signals. The other dead time period is 500 ns. A complete PWM cycle has 50 µs (the classic 20 kHz frequency of MOSFET switching, commonly used in motor control applications).

In this example, the motor spins using forced commutation without synchronization or closed loop algorithms. This code example highlights the new BEMF measurement method during dead time. The motor is spinning at a constant speed and amplitude that is not modified at run time as it is not the main objective of this application.

The image below shows the setup used in this application:

Figure 5-1. Hardware Setup Needed for the Application
To generate this project using MPLAB® Code Configurator (MCC) Melody, MCC Melody (MCC Classic is not supported), follow the next steps:
  1. Create a new MPLAB® X IDE project for AVR16EB32.
  2. Open MCC from the toolbar, find more information on installing the MCC plug-in here.
  3. In MCC Content Manager Wizard, select MCC Melody, then click Finish.
  4. Click Project Resources, go to System, select CLKCTRL, and disable the Prescaler enable button
  5. From the Device Resources, go to the Drivers and Timer window, add the TCE module, then do the following configuration:

    – Module Enable: Must be enabled by default. If not, toggle the button (it turns blue if enabled)

    – Clock Selection: System clock (by default, the divider must be 1 - System clock)

    - Counter Direction: UP

    – Waveform Generation Mode: Single-Slope PWM mode with overflow on TOP

    – Requested Period [s]: 0.00005

    – Duty Cycle 0 [%]: 0

    – Duty Cycle 1 [%]: 0

    – Duty Cycle 2 [%]: 0

    – Duty Cycle 3 [%]: 98.5

    – Waveform Output n: Check the boxes from the Enable column for Waveform Output 0, 1, 2, 3

    – Scale mode: CMP values are scaled from the Center, 50% DC

    – Scaled Writing to registers: Normal

    – Amplitude Control Enable: Toggle the button (it turns blue if enabled)

    – Amplitude Value: 0.1

    – Generate ISR: Toggle the button (it turns blue if enabled)

    – Compare 3 Interrupt Enable: Toggle the button (it turns blue if enabled)

  6. From the Device Resources, go to Drivers and add the WEX module, then do the following configuration:

    – Input Matrix: Direct

    – Update Source: TCE (the update condition for the output signals will be dictated by TCE)

    - Override Settings: Check all the boxes from the Output Enable column for the Waveform Output [0-5]

    – Dead-time Insertion Channel 0 Enable: Toggle the button (it turns blue if enabled)

    – Dead-time Insertion Channel 1 Enable: Toggle the button (it turns blue if enabled)

    – Dead-time Insertion Channel 2 Enable: Toggle the button (it turns blue if enabled)

    – Requested Dead-time Low Side (μs): 2

    – Requested Dead-time High Side (μs): 0.5

  7. From the Device Resources, go to Drivers and add the AC0 module, then do the following configuration:

    – Enable: Toggle the button (it turns blue if enabled)

    – Positive Input MUX Selection: Positive Pin 5

    – Negative Input MUX Selection: Negative Pin 1

    – Output Pad Enable: Toggle the button (it turns blue if enabled)

  8. In the Pin Grid View window - check if the WEX_WO[0-5] pins are locked as outputs on PORTA. The pins are locked when checking the boxes from the Enable column from Waveform Output n. To change the PORT, click on a pin from another PORT in Pin Grid View. Check if PA7 pin is set as AC output. Check if PD4 and PD0 pins are set as inputs for AC. Also, another two pins are necessary as inputs for AC for the other two motor phases. Click on PD5 and PD6 and set them as inputs from Pins -> GPIO. Last, the pins that show the sampled BEMF and the sampling moment must be set as outputs from Pins -> GPIO. These pins are PF1, PF2, PF3 and PF4.
  9. In the Project Resources window, click the Generate button so that MCC will generate all the specified drivers and configurations.
  10. After the MCC Melody generates the project files with the configuration explained above, overwrite the content from the main.c file with the following:

    Add macro definitions, variables and Look-up Table (LUT) used to drive the motor:
    /* Number of pole pairs of a BLDC motor */
    #define MOTOR_PAIR_POLES                4
    
    /* MOSFET switching frequency in Hz */
    #define F_SAMPLING                      20000.0
    
    /* uint16_t range mapping: 0 - 359.99 electrical degrees -> 0 - 65535 */
    #define DEGREES_TO_U16(DEG)             (uint16_t)( (float)(DEG) * 65536.0 / 360.0 + 0.5)
    
    /* Speed conversion from RPM to LUT scrolling speed */
    #define RPM_TO_U16(RPM)                 (uint16_t)(((float)(RPM) * 65536.0 * (float)(MOTOR_PAIR_POLES)) / ((float)(F_SAMPLING) * 60.0) + 0.5)
    
    /* Sets the amplitude of the sine wave signals, and thus the scaling values of duty cycle 
     * in U.Q.1.15 format, ranging from 0 to 1.00. Duty cycle scaling is done in hardware
     * using the hardware accelerator of TCE.*/
    #define AMP_TO_U16(X)                   (uint16_t)(32768.0*(X) + 0.5)
    
    /* Speed of the motor - 120 RPM */
    #define SPEED                           RPM_TO_U16(120)
    
    /* Amplitude of the sine wave - 10% */
    #define AMPLITUDE                       AMP_TO_U16(0.1)
    
    
    #include "mcc_generated_files/system/system.h"
    
    typedef enum
    {
        MUX_PHASE_A = (AC_MUXPOS_AINP5_gc | AC_MUXNEG_AINN1_gc),
        MUX_PHASE_B = (AC_MUXPOS_AINP6_gc | AC_MUXNEG_AINN1_gc),
        MUX_PHASE_C = (AC_MUXPOS_AINP3_gc | AC_MUXNEG_AINN1_gc),
    } mux_t;
    
    /* LUT that is used to generate a sinusoidal drive */
    static const uint16_t sine_lookup_table[] = 
    {
      16384, 16786, 17187, 17589, 17989, 18389, 18788, 19185, 19580, 19973, 20364, 
      20753, 21140, 21523, 21903, 22280, 22653, 23023, 23389, 23750, 24107, 24459, 24807, 
      25149, 25486, 25818, 26143, 26463, 26777, 27085, 27386, 27681, 27969, 28250, 28523, 
      28790, 29049, 29300, 29543, 29779, 30006, 30226, 30437, 30639, 30833, 31018, 31194, 
      31362, 31520, 31670, 31810, 31941, 32062, 32174, 32276, 32369, 32453, 32526, 32590, 
      32644, 32689, 32723, 32748, 32763, 32768, 32763, 32748, 32723, 32689, 32644, 32590, 
      32526, 32453, 32369, 32276, 32174, 32062, 31941, 31810, 31670, 31520, 31362, 31194, 
      31018, 30833, 30639, 30437, 30226, 30006, 29779, 29543, 29300, 29049, 28790, 28523, 
      28250, 27969, 27681, 27386, 27085, 26777, 26463, 26143, 25818, 25486, 25149, 24807, 
      24459, 24107, 23750, 23389, 23023, 22653, 22280, 21903, 21523, 21140, 20753, 20364, 
      19973, 19580, 19185, 18788, 18389, 17989, 17589, 17187, 16786, 16384, 15981, 15580, 
      15178, 14778, 14378, 13979, 13582, 13187, 12794, 12403, 12014, 11627, 11244, 10864, 
      10487, 10114, 9744, 9378, 9017, 8660, 8308, 7960, 7618, 7281, 6949, 6624, 6304, 5990, 
      5682, 5381, 5086, 4798, 4517, 4244, 3977, 3718, 3467, 3224, 2988, 2761, 2541, 2330, 
      2128, 1934, 1749, 1573, 1405, 1247, 1097, 957, 826, 705, 593, 491, 398, 314, 241, 
      177, 123, 78, 44, 19, 4, 0, 4, 19, 44, 78, 123, 177, 241, 314, 398, 491, 593, 705, 
      826, 957, 1097, 1247, 1405, 1573, 1749, 1934, 2128, 2330, 2541, 2761, 2988, 3224, 
      3467, 3718, 3977, 4244, 4517, 4798, 5086, 5381, 5682, 5990, 6304, 6624, 6949, 7281, 
      7618, 7960, 8308, 8660, 9017, 9378, 9744, 10114, 10487, 10864, 11244, 11627, 12014, 
      12403, 12794, 13187, 13582, 13979, 14378, 14778, 15178, 15580, 15981
    };
    

    Add the Motor_Drive function. This function updates the driving signals and generates the sinusoidal drive at a given speed.

    /* Function that is called every 50us to update the drive */
    void Motor_Drive(void)
    {
        /* Counters that scroll through the LUT at runtime. These counter are used to create 
         * the 120 degrees phase shift between each of the motor's phases */
        static uint16_t     phase_a = DEGREES_TO_U16(0.0);
        static uint16_t     phase_b = DEGREES_TO_U16(120.0);
        static uint16_t     phase_c = DEGREES_TO_U16(240.0);
        static const uint16_t speed = SPEED;
    
        /* Values that will be written in the CMP channels of TCE */
        uint16_t drive_a, drive_b, drive_c;
        
        /* Updating the counters */
        phase_a += speed;
        phase_b += speed;
        phase_c += speed;
        
        /* Select new variables from the LUT for each CMP channel */
        drive_a = sine_lookup_table[(phase_a >> 8)];
        drive_b = sine_lookup_table[(phase_b >> 8)];
        drive_c = sine_lookup_table[(phase_c >> 8)];
        
        /* Update the values from CMP channels with new ones */
        TCE0_CompareChannels012BufferedSet(drive_a, drive_b, drive_c);
    }
    

    Add the Mux_Set function. This function switches the BEMF monitor from one phase to another because this example uses only one Analog Comparator.

    /* Functions that switches the MUX to monitor all 3 phases at runtime */
    void Mux_Set(uint8_t mode)
    {
        uint8_t temp;
        temp = AC0.MUXCTRL;
        temp &= ~(AC_MUXPOS_gm | AC_MUXNEG_gm);
        temp |= mode;
        AC0.MUXCTRL = temp;
    }
    

    Add the BEMF_Read function. This function samples the BEMF signals and displays them on General Purpose Input/Output (GPIO) pins. This function also calls the Mux_Set and Motor_Drive functions.

    /* Function that is called during the enlarged dead time to read the BEMF state */
    void BEMF_Read(void)
    {
        bool  bemf_state;
        static mux_t mux = MUX_PHASE_A;
    
        /* BEMF sampling point marked by an IO toggling */
        IO_PF4_SetHigh();
        bemf_state = ((AC0.STATUS & AC_CMPSTATE_bm) != 0 );
        IO_PF4_SetLow();
        
        /* Switching AC0 MUX from one phase to another one */
        switch(mux)
        {
            case MUX_PHASE_A: if(bemf_state) {IO_PF1_SetHigh();} else {IO_PF1_SetLow();} 
                                              mux = MUX_PHASE_B; break;
            case MUX_PHASE_B: if(bemf_state) {IO_PF2_SetHigh();} else {IO_PF2_SetLow();} 
                                              mux = MUX_PHASE_C; break;
            case MUX_PHASE_C: if(bemf_state) {IO_PF3_SetHigh();} else {IO_PF3_SetLow();} 
                                              mux = MUX_PHASE_A; break;
            default: mux = MUX_PHASE_A; break;
        } 
        
        /* Update Analog Comparator MUX to monitor another phase of the motor */
        Mux_Set(mux);
        
        /* Update drive to keep the motor spinning */
        Motor_Drive();
    }
    

    Edit the main.c file. Register the BEMF_Read function as a callback for the TCE CMP3 channel, enable the TCE hardware scaling accelerator, and set the desired amplitude level.

    int main(void)
    {
        SYSTEM_Initialize();
        
        /* Register the BEMF sampling function as a callback */
        TCE0_Compare3CallbackRegister(BEMF_Read);
        
        /* Enable hardware scaling accelerator after initialization to avoid messing up 
         * the timing for reading BEMF with CMP3 channel of TCE */
        TCE0_ScaleEnable(true);
        
        TCE0_AmplitudeSet(AMPLITUDE);
        
        while(1)
        {
        }    
    }
  11. Now, the project can be built and run from MPLAB X IDE. At run time, while the motor is spinning, the BEMF from each motor phase is measured with the AC and shown using some output GPIO pins. It can be observed that the measured BEMF follows the sinusoidal driving signals of the motor's phases.