Drexel University's Logo

Clayton McNeil

HUBO Lab Motor Controller Programming Guide


Home
About Me
Tutorials
Media Archive
Resources

Part 6: Programming for the Hall Effect Sensor and PWM Control


The complete source code for this tutorial can be downloaded here.

With the ability to send commands to the motor via CAN, the next task to address is making the motor move. This requires knowing which electromagnetic coils should receive power and generating the PWM signal that will be sent to them.

Inside the motor are three Hall effect sensors, just as there are three coils. For every position of the rotor as it spins, two of these sensors will be triggered. Each combination of two sensors corresponds to a conductive phase of the motor and dictates the PWM signal each coil should receive (active high, active low, forced high, or forced low) to keep the rotor moving.

Diagram of Hall Effect Sensor Sequence
Hall effect sensor sequence as shown in Maxon reference materials.

To generate the PWM signals that will be sent to the coils, the F2811’s Event Manager peripheral will be used. The Event Manager works by allowing timers to be configured such that they produce a carrier frequency for the PWM signal. As the timers tick away, the value of their count is compared to the value of a ‘compare’ register. If the two values match, there is a transition in the signal. Depending on configuration, the count will either restart from zero or counts backwards to zero once it reaches its defined end (set in the ‘period’ register). If the timer is setup to count backwards, a second compare will occur.

Take, for instance, a timer counting from zero to 5 and then back to zero. When the timer reaches 2 (the ‘compare’ value), an associated PWM signal will go from low to high. It will stay high until the timer hits 2 again while counting backwards. If each tick is considered an equal time segment of the period, the PWM signal would have a duty cycle of 50% because it was high for five ticks (3, 4, 5, 4, 3). Therefore, by changing the value of the compare registers, different duty cycles can be realized. This is a simplified example of how the Event Manager works, but the principles are the same.

HMC_CommonDefinitions.h
 
#define PWM_SIGNALMAX    1187
#define PWM_SAFTEYGAP    200
#define PWM_DEADZONE     25

The constants defined in the PWM section of HMC_CommonDefinitions.h are used when calculating the compare values that will ultimately control the duty cycle of the PWM signal.

HMC_HallSensor-PWM.c
 
extern struct mPARAMS { // Motor Parameters
    INT16         PWMSignal;        // Indicates PWM Signal (value for CMPRx registers)
    UINT16         Direction;       // Indicates Direction of motor

    unsigned     PWMRun:1;          // Indicate if PWM is enabled for direct running
    unsigned     PosControlRun:1;   // Indicate if position control is enabled
    unsigned     PosControlGo:1;    // Indicate if position control should goto DestRefPos

    sLWord        OrigRefPos;       // Origin Reference Position
    sLWord        DestRefPos;       // Destination Reference Position
    sLWord        DeltaRefPos;      // Delta between the OrigRefPos and DestRefPos
    sLWord        NextRefPos;       // Next, interpolated, Reference Position
    
    sLWord        PrevPos;          // A Previous Position recovered from encoder
    sLWord        Pos;              // Position recovered from encoder
    
    Word          KP;               // P gain of Position controller
    Word          KD;               // D gain of Position controller

    sLWord        IntpolStepSize;   // Interpolation Step Size
    sLWord        IntpolStepCount;  // Interpolation Step Count

} MotorParams;

extern void InitEVA(void);
extern void InitializeMotorParams(void);
extern void DriveMotor(void);

In addition to declarations for functions defined in HMC_HallSensor.c that will be called from other source files, HMC_HallSensor.h also declares the very important MotorParams structure. The purpose of this structure is to hold any data related to the motor or its control.

 
UINT16 hsOutputStates[16]= {0x0CCC,0x06F3,0x0F36,0x063F,0x036F,0x0F63,0x03F6,0x0CCC,
                            0x0CCC,0x03F6,0x0F63,0x036F,0x063F,0x0F36,0x06F3,0x0CCC};

void InitEVA(void)
{
    EvaRegs.T1PR = 0x04E2;         // Timer1 period : 20kHz (Carrier Freq.)
    EvaRegs.T1CMPR = 0x3C00;       // Timer1 compare
    EvaRegs.T1CNT = 0x0000;        // Timer1 counter
    
    // modify continuous up and down mode for deadband
    EvaRegs.T1CON.all = 0x1042;    // 0001 0000 0100 0010
    
  // Initalize EVA Timer2 as a encoder 
    EvaRegs.T2PR = 0xFFFF;         // Timer2 period
    EvaRegs.T2CNT = 0x0000;        // Timer2 counter
    EvaRegs.T2CON.all = 0xD870;    // 1101 1000 0111 0000
 
  // Enable compare for PWM1-PWM6
    EvaRegs.CMPR1 = 0;
    EvaRegs.CMPR2 = 0;
    EvaRegs.CMPR3 = 0;
    
  // Compare action control.  Action that takes place
  // on a cmpare event
  // output pin 1 CMPR1 - active high
  // output pin 2 CMPR1 - active low
  // output pin 3 CMPR2 - active high
  // output pin 4 CMPR2 - active low
  // output pin 5 CMPR3 - active high
  // output pin 6 CMPR3 - active low
    EvaRegs.ACTRA.all = 0x0AAA;

    EvaRegs.DBTCONA.all = 0x0000;  // no deadband
    EvaRegs.COMCONA.all = 0xA600;
}

Following the definition of hsOutputStates is the definition for InitEVA(). This function handles the configuration of the Event Manager. Although there are actually two Event Managers (EVA and EVB) on the F2811 DSP, only one is needed to control a single motor.

The first aspect of the Event Manager to be configured is GP (General Purpose) Timer 1. This will be the timer that is used to produce the PWM output signals. Using the T1CON register, it is setup for continuous up/down counting mode and enabled. The PWM carrier freuency and T1 compare value are also set, via register T1PR and T1CMPR, respectively.

Rather than being utilized for PWM generation, the second GP Timer is used to read the encoder. As explained earlier in the tutorial (Part 1, Getting Started with the HUBO Lab Motor Controller...), the encoder operates using a quadrature encoder pulse (QEP). By setting up the timer to use directional up/down counting and a QEP circuit as its external input, it will automatically recover a position value from the encoder.

After the timers, the PWM compare and the Compare Action Control (ACTRA) registers are initialized. The ACTRA register is what dictates how PWM signals transition when a compare match occurs (as in forced high, active low, etc). In order to keep the rotor spinning, this register must constantly be updated based on the position of the rotor as described by the Hall effect sensors. The mechanism for doing this explained below.

The final lines of InitEVA() disable the PWM deadbands and enable timer comparison for all three PWM compare registers.

 
void InitializeMotorParams(void)
{
    MotorParams.PWMSignal = 10;
    MotorParams.Direction = 0;

    MotorParams.PWMRun = DISABLE;
    MotorParams.PosControlRun = DISABLE;
    MotorParams.PosControlGo = DISABLE;

    MotorParams.OrigRefPos = 0;
    MotorParams.DestRefPos = 0;
    MotorParams.DeltaRefPos = 0;
    MotorParams.NextRefPos = 0;
    
    MotorParams.PrevPos = 0;
    MotorParams.Pos = 0;
    
    MotorParams.KP = DEFALT_POSCTRL_KP;
    MotorParams.KI = DEFALT_POSCTRL_KI;
    MotorParams.KD = DEFALT_POSCTRL_KD;

    MotorParams.IntpolStepSize = DEFAULT_INTPOL_SSIZE;
    MotorParams.IntpolStepCount = 0;
    EncoderValue = 0;
 
    EvaRegs.T2CNT=0;
}

The second initialization function, InitializeMotorParams() resets all the variables in the MotorParams structure. It also resets two variables relating to the encoder (EncoderValue and EvaRegs.T2CNT), which will be covered in the next part of the tutorial.

 
void HallSensorSequence(void)
{
    UINT16 hallSensorSeq;
    
    hallSensorSeq = 
        (GpioDataRegs.GPADAT.bit.GPIOA11) | (GpioDataRegs.GPADAT.bit.GPIOA12<<1) | 
        (GpioDataRegs.GPADAT.bit.GPIOA13<<2) | ((MotorParams.Direction<<3)&0x0008);

    EvaRegs.ACTRA.all = hsOutputStates[hallSensorSeq];
}

To determine what type of signal each coil should be sent, the function HallSensorSequence() starts by polling the GPIO pins connected the Hall effect sensors. In conjunction with the variable MotorParams.Direction, which indicates the direction the motor should be moving, bit shifting is used to form a 4-bit number. Each of the sixteen possible values for this number refer to a possible state of the motor and a corresponding value for the ACTRA register stored in the hsOutputStates array, to which the number serves as an index. Thus, based on the current state, PWM signals will be sent to the proper coils by updating the ACTRA register. (Note: The sixteen states from the six conductive phases plus the two exceptional states where the Hall effect sensors are either all triggered or all dormant, times the two directions.)

 
void ConfigurePWM(void)
{
    // Motor PWM
    EvaRegs.CMPR1 = MotorParams.PWMSignal;
    EvaRegs.CMPR2 = MotorParams.PWMSignal;
    EvaRegs.CMPR3 = MotorParams.PWMSignal;
}

Rather than directly set the PWM signal/compare values in the Event Manager registers throughout the code, the ConfigurePWM() function handles periodically loading them. As the three lines of code clearly show, each CMPR register receives the same value as it is the reasonability of HallSensorSequence()to control how whether the signal is seen by the coils.

 
void DriveMotor(void)
{
    HallSensorSequence();
    
    if (CpuTimer0.InterruptCount == 500) // 1kHz (500kHz / 500) 
    {
            FreqFlag1kHz = TRUE;
            CpuTimer0.InterruptCount = 0;
    }

    ConfigurePWM();

    CpuTimer0.InterruptCount++;

    // Light up LED indicator D10
    GpioDataRegs.GPGTOGGLE.bit.GPIOG5 = 1;
}

Last to be defined in HMC_HallEffect-PWM.c is the function DriveMotor(), which is called by an ISR attached to a CPU timer interrupt in HMC_Main.c. This particular timer is configured for a frequency of 500 kHz. Aside from calling HallSensorSequence() and ConfigurePWM(), the function also regulates the timing of a 1 kHz control loop inside of the program’s main() function. It does this by incrementing a count each time it is called. When the count reaches a value corresponding to the proper period for 1 kHz, a flag is set that enables action in the control loop.

HMC_Main.c
 
void main(void)
{
    .
    .
    .
    EALLOW;  // This is needed to write to EALLOW protected registers
    PieVectTable.TINT0 = &cpu_timer0_isr0;
    EDIS;    // This is needed to disable write to EALLOW protected registers
    .
    .
    .
    ConfigCpuTimer(&CpuTimer0, 150, 2);    //Interrupt Timer Freq. Set to 500kHz 
    StartCpuTimer0();
    .
    .
    .    
    // PDPINT interupt disabled
    EvaRegs.EVAIMRA.bit.PDPINTA=0;
    EvbRegs.EVBIMRA.bit.PDPINTB=0;
      .
    .
    .
    FreqFlag1kHz = 0;

    InitEVA();
    InitializeMotorParams();
    .
    .
    .

    for(;;)
    {
        if(FreqFlag1kHz)
        {
            .
            .
            .
        }
        .
        .
        .    
        FreqFlag1kHz = 0; 
    }
}
.
.
.
interrupt void cpu_timer0_isr0(void) 
{

    // Handle Hall Sensor and PWM for motor
    DriveMotor();

    // Acknowledge this interrupt to receive more interrupts from group 1
    PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
}

The main() function inside HMC_Main.c starts by adding cpu_timer0_isr0() to the PIE vector table, which will cause DriveMotor() to be called a regular intervals. The timer is then configured to operate at the predefined frequency and started. Following this, the power drive protection interrupts are disabled and the initialize functions for the Event Manager and MotorParams structure are called.

Once all initializations are complete in main(), the program enters an infinite loop. The action that occurs within this loop runs at a frequency of 1 kHz, based on the FreqFlag1kHz variable regulated by the CPU timer and DriveMotor() function.

HMC_CAN.c
 
HMC_CAN.c
void ParseCAN(void)
{    
    if(ReceiveCAN(CMD_MBOX, RxData, &RxDLC) == TRUE) // if any CMD is received
    {
        switch(RxData[0]) 
        {    

            .
            .
            .
            case CANCMD_PWMRUN:     // Enable/Disable PWM direct run mode
                if(RxData[1] == ENABLE){
                    MotorParams.PosControlRun = DISABLE;
                    MotorParams.PWMRun = ENABLE;
                    MotorParams.Direction = RxData[2] & 0x01;
                    MotorParams.PWMSignal = 
                       (int)((((float)RxData[3]) * PWM_SIGNALMAX) / 100) + PWM_DEADZONE;                        
                }
                else if(RxData[1] == DISABLE)
                {
                    MotorParams.PWMRun = DISABLE;
                    MotorParams.PWMSignal = 0;
                }
                break;            
            .
            .
            .
    }
}

The last section of code that needs to be reviewed is the actual CAN command to allow direct control of the PWM duty cycle. The second byte of data in this command controls whether the PWM signal is enabled or disabled. This state is stored in MotorParams.PWMRun and is checked at the start of each iteration of the control loop in main(). Thus, disabling the PWM signal will cause the motor to stop when in this mode.

If the command is sent to enable direct control of the PWM, the third and fourth bytes of data are used to set the motor’s direction and duty cycle, respectively. For the duty cycle, the value is to be expressed as a percentage.


Testing the Results:

To test the above code and make the motor spin, start by sending the motor enable command described in Part 5, Programming for CAN Communications. After the motor is enabled, send a CAN message containing the same message identifier and the following bytes: 0x0D (the command identifier for PWM control), 0x01 (enable/disable), 0x00 (direction), and 0x0A (duty-cycle, set here at 10%). This should cause the rotor to begin spinning. Send the same command again, this time with a value of 0x0F (15%) for the duty cycle. The rotor should start to spin even faster. To stop it, disable PWM control by sending a message with the value 0x00 in the second byte.

Continue to Part 7, Programming for the Encoder and Position Control...

Home | About Me | Tutorials | Media Archive | Resources