//***************************************************************************** // // bldc.c - Motor control application for a brushless DC motor. // // Copyright (c) 2005-2007 Luminary Micro, Inc. All rights reserved. // // Software License Agreement // // Luminary Micro, Inc. (LMI) is supplying this software for use solely and // exclusively on LMI's microcontroller products. // // The software is owned by LMI and/or its suppliers, and is protected under // applicable copyright laws. All rights are reserved. Any use in violation // of the foregoing restrictions may subject the user to criminal sanctions // under applicable laws, as well as to civil liability for the breach of the // terms and conditions of this license. // // THIS SOFTWARE IS PROVIDED "AS IS". NO WARRANTIES, WHETHER EXPRESS, IMPLIED // OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE. // LMI SHALL NOT, IN ANY CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL, OR // CONSEQUENTIAL DAMAGES, FOR ANY REASON WHATSOEVER. // // This is part of revision 461 of the BLDC application. // //***************************************************************************** #include #include "../../hw_ints.h" #include "../../hw_memmap.h" #include "../../hw_pwm.h" #include "../../hw_types.h" #include "../../src/debug.h" #include "../../src/gpio.h" #include "../../src/interrupt.h" #include "../../src/pwm.h" #include "../../src/qei.h" #include "../../src/sysctl.h" #include "../../src/timer.h" #include "pid.h" #include "ui.h" #include "bldc.h" //***************************************************************************** // // This application is designed to drive a Pittman N2341S001 brushless DC // motor. The commutation sequence and PID controller tuning may need to be // modified when using a different motor; the PID controller can be tuned for // different response behavior of the Pittman motor. // // The rotational speed of the motor is measured with either the quadrature // encoder module (if present in hardware) or a software interface to the // optical encoder. When requested, the measured speed is used to perform // closed loop speed control of the motor. // // A simple user interface is provided on UART0 operating at 115,200, 8-N-1. // The status is provided in the following format: // // Cr s:1000 a:0996 // ab cccc dddd // // Where 'a' is "C" for closed loop operation and "O" for open loop operation, // 'b' is 'r' for running and 's' for stopped, 'cccc' is the target motor // speed, and 'dddd' is the measured motor speed. // // The following commands are available: // // r Start/stop the motor. // l Toggle between open and closed loop operation. // p Set a new proportional gain factor for the motor speed PID // controller. // i Set a new integral gain factor for the motor speed PID controller. // d Set a new differential gain factor for the motor speed PID // controller. // z Display the current gain factors for the motor speed PID controller. // D Toggle display of each motor speed sample (every 1/100th of a // second). // // See the accompanying application note for full details of these commands. // //***************************************************************************** //***************************************************************************** // // One of the following must be defined to choose the system clock rate: // SYSTEM_CLOCK_6MHZ, SYSTEM_CLOCK_20MHZ, SYSTEM_CLOCK_25MHZ, // SYSTEM_CLOCK_50MHZ // //***************************************************************************** //#define SYSTEM_CLOCK_6MHZ #define SYSTEM_CLOCK_20MHZ //#define SYSTEM_CLOCK_25MHZ //#define SYSTEM_CLOCK_50MHZ //***************************************************************************** // // Clock and PWM dividers used depend on which system clock rate is chosen. // //***************************************************************************** #if defined(SYSTEM_CLOCK_6MHZ) #define SYSDIV SYSCTL_SYSDIV_1 #define PWMDIV SYSCTL_PWMDIV_1 #define CLKUSE SYSCTL_USE_OSC #elif defined(SYSTEM_CLOCK_20MHZ) #define SYSDIV SYSCTL_SYSDIV_10 #define PWMDIV SYSCTL_PWMDIV_2 #define CLKUSE SYSCTL_USE_PLL #elif defined(SYSTEM_CLOCK_25MHZ) #define SYSDIV SYSCTL_SYSDIV_8 #define PWMDIV SYSCTL_PWMDIV_2 #define CLKUSE SYSCTL_USE_PLL #elif defined(SYSTEM_CLOCK_50MHZ) #define SYSDIV SYSCTL_SYSDIV_4 #define PWMDIV SYSCTL_PWMDIV_2 #define CLKUSE SYSCTL_USE_PLL #else #error "System clock speed is not defined!" #endif //***************************************************************************** // // These define the pair of PWM signals that correspond to each phase of the // motor. // //***************************************************************************** #define PHASE_A (PWM_OUT_0_BIT | PWM_OUT_1_BIT) #define PHASE_B (PWM_OUT_2_BIT | PWM_OUT_3_BIT) #define PHASE_C (PWM_OUT_4_BIT | PWM_OUT_5_BIT) //***************************************************************************** // // These define the PWM signals that correspond to the high and low side FETs // for each phase of the motor. // //***************************************************************************** #define PHASE_A_PLUS PWM_OUT_0_BIT #define PHASE_A_MINUS PWM_OUT_1_BIT #define PHASE_B_PLUS PWM_OUT_2_BIT #define PHASE_B_MINUS PWM_OUT_3_BIT #define PHASE_C_PLUS PWM_OUT_4_BIT #define PHASE_C_MINUS PWM_OUT_5_BIT //***************************************************************************** // // These define the GPIO pins that are used for the Hall effect sensors. // //***************************************************************************** #define HALL_PORT GPIO_PORTB_BASE #define HALL_INT INT_GPIOB #define HALL_A GPIO_PIN_6 #define HALL_B GPIO_PIN_5 #define HALL_C GPIO_PIN_4 #define HALL_SHIFT 4 //***************************************************************************** // // The minimum allowed motor speed. This is the slowest speed at which the // motor will be allowed to be run by this application. // //***************************************************************************** #define MIN_APP_MOTOR_SPEED 200 //***************************************************************************** // // The maximum allowed motor speed. This is the fastest speed at which the // motor will be allowed to be run by this application. // //***************************************************************************** #define MAX_APP_MOTOR_SPEED 7000 //***************************************************************************** // // The number of lines in the quadrature encoder on the motor. // //***************************************************************************** #define ENCODER_LINES 1000 //***************************************************************************** // //! Mapping from Hall effect sensor state to PWM drive state. //! //! This array maps the state of the Hall effect sensors on the motor to the //! set of PWM signals that should be driving at that time. This mapping //! corresponds to the following table, taken from the data sheet for the //! motor: //! //! \verbatim //! Hall State Winding State //! 100 B+, A- //! 110 C+, A- //! 010 C+, B- //! 011 A+, B- //! 001 A+, C- //! 101 B+, C- //! \endverbatim // //***************************************************************************** static const unsigned long g_pulHallToPhase[] = { 0, PHASE_A_PLUS | PHASE_C_MINUS, PHASE_C_PLUS | PHASE_B_MINUS, PHASE_A_PLUS | PHASE_B_MINUS, PHASE_B_PLUS | PHASE_A_MINUS, PHASE_B_PLUS | PHASE_C_MINUS, PHASE_C_PLUS | PHASE_A_MINUS }; //***************************************************************************** // //! Determines if the motor is running. //! //! This variable contains the value of a state machine that handles //! transitioning the motor from a running to a non-running state. This can //! have any of the following values: //! //! \verbatim //! MOTOR_STOPPED The motor is stopped. //! MOTOR_RUNNING The motor is running. //! MOTOR_STOPPING The motor is stopping, and the high side switches //! have been turned off //! MOTOR_STOP_DELAY The motor is stopping, the high side switches have //! been turned off, and a delay time has expired (to //! prevent shoot-through on the inverter bridge). //! \endverbatim // //***************************************************************************** #define MOTOR_STOPPED 0 #define MOTOR_RUNNING 1 #define MOTOR_STOPPING 2 #define MOTOR_STOP_DELAY 3 static int g_iRunning = 0; //***************************************************************************** // //! Determines if the motor is rotating in the forward or reverse direction. //! //! This flag determines if the motor is rotating in the forward or reverse //! direction. This determines the direction of current flow through the motor //! windings for each Hall effect state; reversing the current flow reverses //! the direction of rotation. // //***************************************************************************** static tBoolean g_bReverse = false; //***************************************************************************** // //! Determines if the motor is run open or closed loop. //! //! This flag determines if the motor is run open loop (i.e. with no feedback) //! or closed loop (with feedback from the quadrature encoder being used to //! track the motor speed to the target speed). // //***************************************************************************** static tBoolean g_bOpenLoop = true; //***************************************************************************** // //! A flag to indicate debug mode. //! //! This flag determines if the closed loop debug mode is enabled. If it is, //! each speed measurement is printed out so that the performance of the PID //! gain factors can be evaluated. // //***************************************************************************** static volatile tBoolean g_bDebug = false; //***************************************************************************** // //! A flag to indicate the QEI type. //! //! This flag determines if software QEI is enabled. If it is, the number of //! edges on a single encoder channel is counted via GPIO interrupts. // //***************************************************************************** static tBoolean g_bSoftwareQEI = false; //***************************************************************************** // //! The target speed. //! //! This variable contains the desired speed of the motor. It is updated when //! the user requests a new speed for the motor. // //***************************************************************************** static unsigned long g_ulTarget = 0; //***************************************************************************** // //! The current speed. //! //! This variable contains the measured speed of the motor. It is updated //! every 100th of a second. // //***************************************************************************** static volatile unsigned long g_ulSpeed = 0; //***************************************************************************** // //! The number of speed measurements. //! //! This variables contains the number of times the motor speed has been //! measured. It is effectively a 100Hz counter. // //***************************************************************************** static volatile unsigned long g_ulSpeedCount = 0; //***************************************************************************** // //! The count of edges from the optical encoder. //! //! This variable contains the count of edges seen from the optical encoder //! during the current time period. The count is incremented for each edge //! interrupt and copied/reset every 1/100th of a second; the number of edges //! per 1/100th of a second is used to compute the speed of the motor in RPM. // //***************************************************************************** static unsigned long g_ulVelCount = 0; //***************************************************************************** // //! The base PWM duty cycle. //! //! This variable contains the base PWM duty cycle. This is the duty cycle //! that is used if the motor is being operated open-loop; this is the base //! duty cycle to which the error delta is added when operating closed-loop. // //***************************************************************************** static unsigned long g_ulBaseDutyCycle = 0; //***************************************************************************** // //! The period of the PWM generators. //! //! Calculated based on the system clock and the desired PWM frequency // //***************************************************************************** static unsigned long g_ulPwmPeriod = 0; //***************************************************************************** // //! PID algorithm state. //! //! This structure contains the state of the PID algorithm that is processing //! the speed feedback loop. // //***************************************************************************** static tPIDState g_sPID; //***************************************************************************** // //! Proportional gain factor. //! //! This variable contains the proportional gain factor for the PID algorithm. // //***************************************************************************** static long g_lPGain = 25; //***************************************************************************** // //! Integral gain factor. //! //! This variable contains the integral gain factor for the PID algorithm. // //***************************************************************************** static long g_lIGain = 100; //***************************************************************************** // //! Derivitive gain factor. //! //! This variable contains the derivitive gain factor for the PID algorithm. // //***************************************************************************** static long g_lDGain = 25; //***************************************************************************** // //! Linear speed mapping table. //! //! This table maps the desired motor speed to the PWM duty cycle required to //! achieve this speed. These duty cycles assume no load on the motor and //! nominal operating conditions; this will result in the motor going //! approximately the desired speed. The duty cycle is represented as a value //! ranging from 0 (0%) to 10000 (100%). // //***************************************************************************** typedef struct { unsigned long ulSpeed; unsigned long ulDutyCycle; } tSpeedMap; static const tSpeedMap g_psSpeedMap[] = { { 200, 2587 }, { 500, 3067 }, { 1000, 3733 }, { 1500, 4293 }, { 2000, 4773 }, { 2500, 5413 }, { 3000, 5820 }, { 3500, 6440 }, { 4000, 6940 }, { 4500, 7427 }, { 5000, 8057 }, { 5500, 8560 }, { 6000, 8933 }, { 6500, 9307 }, { 7000, 9627 }, }; #define SPEEDMAP_ENTRIES ((sizeof(g_psSpeedMap) / sizeof(tSpeedMap)) - 1) //***************************************************************************** // //! Storage for the main user interface display. //! //! This contains the contents of the main display on the user interface; //! updates to this array are reflected to the user interface. // //***************************************************************************** static char g_pcDisplay[20]; //***************************************************************************** // //! The user interface commands. //! //! This contains the list of commands supported by this application. // //***************************************************************************** static void DebugHandler(unsigned long ulValue); static void RunHandler(unsigned long ulValue); static void SpeedHandler(unsigned long ulValue); static void LoopHandler(unsigned long ulValue); static void PGainHandler(unsigned long ulValue); static void IGainHandler(unsigned long ulValue); static void DGainHandler(unsigned long ulValue); static void StatusHandler(unsigned long ulValue); static const tUICommandList g_psCommands[] = { { 'D', 0, 0, DebugHandler }, { 'r', 0, 0, RunHandler }, { 's', 1, "Speed: ", SpeedHandler }, { 'l', 0, 0, LoopHandler }, { 'p', 1, "PGain: ", PGainHandler }, { 'i', 1, "IGain: ", IGainHandler }, { 'd', 1, "DGain: ", DGainHandler }, { 'z', 0, 0, StatusHandler } }; //***************************************************************************** // //! The user interface description. //! //! This contains a description of the user interface. // //***************************************************************************** static const tUIDescription g_sUI = { g_pcDisplay, 8, g_psCommands }; //***************************************************************************** // // The error routine that is called if the driver library encounters an error. // //***************************************************************************** #ifdef DEBUG void __error__(char *pcFilename, unsigned long ulLine) { } #endif //***************************************************************************** // //! Interrupt handler for the Hall effect GPIO signals. //! //! Handles the interrupt generated by the GPIO block when one of the Hall //! effect sensors changes state. The set of PWM signals to be driven is //! changed based on the new state of the Hall effect sensors, performing //! commutation of the motor. //! //! \return None. // //***************************************************************************** void GPIOIntHandler(void) { unsigned long ulHall, ulPWM; // // Clear the GPIO pin interrupts. // GPIOPinIntClear(HALL_PORT, HALL_A | HALL_B | HALL_C); // // Perform commutation if the motor is running. // if(g_iRunning == MOTOR_RUNNING) { // // Get the current Hall effect sensor state. // ulHall = (GPIOPinRead(HALL_PORT, HALL_A | HALL_B | HALL_C) >> HALL_SHIFT); // // Get the set of PWM signals to be driven. // ulPWM = g_pulHallToPhase[ulHall]; // // If drive direction is reversed, then invert the PWM signals. This // will reverse the current direction through the motor. // if(g_bReverse) { if(ulPWM & PHASE_A) { ulPWM ^= PHASE_A; } if(ulPWM & PHASE_B) { ulPWM ^= PHASE_B; } if(ulPWM & PHASE_C) { ulPWM ^= PHASE_C; } } // // Set the PWM signals to be driven. // PWMOutputState(PWM_BASE, ulPWM ^ (PHASE_A | PHASE_B | PHASE_C), false); PWMOutputState(PWM_BASE, ulPWM, true); } } //***************************************************************************** // //! Configure the GPIO block to read from the Hall effect sensors. //! //! This function performs the setup on the GPIO block in order to properly //! interface with the Hall effect sensors on the motor. The GPIO pins are //! configured as inputs with both edge interrupt generation, and an interrupt //! handler is installed to handle the interrupts caused by a change in the //! Hall effect sensor state. //! //! \return None. // //***************************************************************************** static void SetupGPIO(void) { // // Configure the Hall effect GPIO pins as inputs with interrupt generation // on both rising and falling edges. // GPIODirModeSet(HALL_PORT, HALL_A | HALL_B | HALL_C, GPIO_DIR_MODE_IN); GPIOIntTypeSet(HALL_PORT, HALL_A | HALL_B | HALL_C, GPIO_BOTH_EDGES); // // Enable the Hall effect GPIO pin interrupts. // GPIOPinIntEnable(HALL_PORT, HALL_A | HALL_B | HALL_C); IntEnable(HALL_INT); // // Changes in the hall effect sensors are the highest priority, so set the // interrupt priority to the highest. // IntPrioritySet(HALL_INT, 0x00); } //***************************************************************************** // //! Configure the PWM block to drive the motor. //! //! This function performs the setup on the PWM block in order to properly //! driver the windings of the motor. The three generators are configured to //! run at the same rate and are synchronized. All six outputs are disabled, //! non-inverted, and set to disable on fault conditions. //! //! \return None. // //***************************************************************************** static void SetupPWM(void) { // // Enable the PWM peripheral. // SysCtlPeripheralEnable(SYSCTL_PERIPH_PWM); // // Set the PWM clock divider. // SysCtlPWMClockSet(PWMDIV); // // Make the appropriate GPIO pins be PWM outputs instead of GPIO. // GPIODirModeSet(GPIO_PORTB_BASE, GPIO_PIN_0 | GPIO_PIN_1, GPIO_DIR_MODE_HW); GPIODirModeSet(GPIO_PORTD_BASE, GPIO_PIN_0 | GPIO_PIN_1, GPIO_DIR_MODE_HW); GPIODirModeSet(GPIO_PORTE_BASE, GPIO_PIN_0 | GPIO_PIN_1, GPIO_DIR_MODE_HW); // // Set the PWM outputs to a non-inverted state with the signal disabled // during fault conditions. // PWMOutputInvert(PWM_BASE, PHASE_A | PHASE_B | PHASE_C, false); PWMOutputFault(PWM_BASE, PHASE_A | PHASE_B | PHASE_C, true); // // Disable the PWM outputs. // PWMOutputState(PWM_BASE, PHASE_A | PHASE_B | PHASE_C, false); // // Configure and enable the generators. // PWMGenConfigure(PWM_BASE, PWM_GEN_0, (PWM_GEN_MODE_UP_DOWN | PWM_GEN_MODE_SYNC | PWM_GEN_MODE_DBG_STOP)); PWMGenConfigure(PWM_BASE, PWM_GEN_1, (PWM_GEN_MODE_UP_DOWN | PWM_GEN_MODE_SYNC | PWM_GEN_MODE_DBG_STOP)); PWMGenConfigure(PWM_BASE, PWM_GEN_2, (PWM_GEN_MODE_UP_DOWN | PWM_GEN_MODE_SYNC | PWM_GEN_MODE_DBG_STOP)); PWMGenEnable(PWM_BASE, PWM_GEN_0); PWMGenEnable(PWM_BASE, PWM_GEN_1); PWMGenEnable(PWM_BASE, PWM_GEN_2); // // Set the period of each generator timer. // PWMGenPeriodSet(PWM_BASE, PWM_GEN_0, g_ulPwmPeriod); PWMGenPeriodSet(PWM_BASE, PWM_GEN_1, g_ulPwmPeriod); PWMGenPeriodSet(PWM_BASE, PWM_GEN_2, g_ulPwmPeriod); // // Set the initial pulse width of the PWM signals to zero. // PWMPulseWidthSet(PWM_BASE, PWM_OUT_0, 0); PWMPulseWidthSet(PWM_BASE, PWM_OUT_1, 0); PWMPulseWidthSet(PWM_BASE, PWM_OUT_2, 0); PWMPulseWidthSet(PWM_BASE, PWM_OUT_3, 0); PWMPulseWidthSet(PWM_BASE, PWM_OUT_4, 0); PWMPulseWidthSet(PWM_BASE, PWM_OUT_5, 0); // // Synchronize the timers. // PWMSyncUpdate(PWM_BASE, PWM_GEN_0_BIT | PWM_GEN_1_BIT | PWM_GEN_2_BIT); PWMSyncTimeBase(PWM_BASE, PWM_GEN_0_BIT | PWM_GEN_1_BIT | PWM_GEN_2_BIT); } //***************************************************************************** // //! Sets the duty cycle of the PWM outputs. //! //! \param ulDutyCycle is the duty cycle for the outputs, specified in clocks. //! //! This function will synchronously update the duty cycle of the PWM outputs //! to the specified value. //! //! \return None. // //***************************************************************************** static void SetPWMDutyCycle(unsigned long ulDutyCycle) { // // Set the PWM duty cycle for each of the PWM outputs. // PWMPulseWidthSet(PWM_BASE, PWM_OUT_0, ulDutyCycle); PWMPulseWidthSet(PWM_BASE, PWM_OUT_1, ulDutyCycle); PWMPulseWidthSet(PWM_BASE, PWM_OUT_2, ulDutyCycle); PWMPulseWidthSet(PWM_BASE, PWM_OUT_3, ulDutyCycle); PWMPulseWidthSet(PWM_BASE, PWM_OUT_4, ulDutyCycle); PWMPulseWidthSet(PWM_BASE, PWM_OUT_5, ulDutyCycle); // // Synchronously update them all now. // PWMSyncUpdate(PWM_BASE, PWM_GEN_0_BIT | PWM_GEN_1_BIT | PWM_GEN_2_BIT); } //***************************************************************************** // //! Interrupt handler for the QEI edge interrupt. //! //! This function is called whenever an edge is seen on the GPIO connected to //! the optical encoder. It simply counts the number of edges seen. //! //! \return None. // //***************************************************************************** void EdgeIntHandler(void) { // // Clear the GPIO interrupt. // GPIOPinIntClear(GPIO_PORTC_BASE, GPIO_PIN_4); // // Increment the count of edges. // g_ulVelCount++; } //***************************************************************************** // //! Interrupt handler for the QEI interrupt. //! //! This function is called whenever the QEI interrupt occurs. In open-loop //! operation, it takes care of spinning down the motor when a stop has been //! requested. In closed-loop operation, it adjusts the PWM duty cycle based //! on the measured speed of the motor by appyling a PID control algorithm. In //! either mode, it takes care of reversing the direction of rotation when //! requested by the user. //! //! \return None. // //***************************************************************************** void QEIIntHandler(void) { char pcBuffer[6]; long lDelta; // // Clear the QEI interrupt. // if(g_bSoftwareQEI) { TimerIntClear(TIMER0_BASE, TIMER_TIMA_TIMEOUT); } else { QEIIntClear(QEI_BASE, QEI_INTTIMER); } // // Capture the encoder edge count. // if(g_bSoftwareQEI) { lDelta = g_ulVelCount * 4; g_ulVelCount = 0; } else { lDelta = QEIVelocityGet(QEI_BASE); } // // Increment the count of QEI interrupts. // g_ulSpeedCount++; // // Get the speed of the motor and convert it from pulses per 1/100th of a // second into rpm. // g_ulSpeed = lDelta = (lDelta * 100 * 60) / (ENCODER_LINES * 4); // // Check the motor running state. // if(g_iRunning == MOTOR_STOPPING) { // // The motor is stopping, and the high side switches have been turned // off. A delay time has expired, so move to the next state. // g_iRunning = MOTOR_STOP_DELAY; // // There is nothing further to do. // return; } else if(g_iRunning == MOTOR_STOP_DELAY) { // // The motor is stopping, and the delay time has expired, so the motor // is now stopped. // g_iRunning = MOTOR_STOPPED; // // Turn on the low side switches. This will cause the motor to brake. // PWMOutputInvert(PWM_BASE, PWM_OUT_1_BIT | PWM_OUT_3_BIT | PWM_OUT_5_BIT, true); // // There is nothing further to do. // return; } // // Update the PWM duty cycle if the motor is being driven closed-loop. // if(!g_bOpenLoop) { // // Compute a new duty cycle delta. // lDelta = PIDUpdate(&g_sPID, lDelta, g_ulTarget - lDelta); // // Update the PWM duty cycle. // lDelta = g_ulBaseDutyCycle + (lDelta / 10000); if(lDelta < 5) { lDelta = 5; } if(lDelta > (g_ulPwmPeriod - 5)) { lDelta = g_ulPwmPeriod - 5; } SetPWMDutyCycle(lDelta); } // // See if debug mode is enabled. // if(g_bDebug) { // // Print the current speed measurement to the user interface. // lDelta = g_ulSpeed; pcBuffer[0] = '0' + ((lDelta / 1000) % 10); pcBuffer[1] = '0' + ((lDelta / 100) % 10); pcBuffer[2] = '0' + ((lDelta / 10) % 10); pcBuffer[3] = '0' + (lDelta % 10); pcBuffer[4] = ' '; pcBuffer[5] = '\0'; UIDisplay(pcBuffer); } } //***************************************************************************** // //! Configure the QEI block to read the motor speed. //! //! This function performs the setup on the QEI block in order to measure the //! speed of the motor. The positional information from the QEI block is not //! used, only the speed. //! //! \return None. // //***************************************************************************** static void SetupQEI(void) { // // See if this part has a QEI peripheral. // if(SysCtlPeripheralPresent(SYSCTL_PERIPH_QEI)) { // // Disable software QEI. // g_bSoftwareQEI = false; // // Enable the QEI peripheral. // SysCtlPeripheralEnable(SYSCTL_PERIPH_QEI); // // Make the appropriate GPIO pins be QEI inputs instead of GPIO. // GPIODirModeSet(GPIO_PORTC_BASE, GPIO_PIN_4 | GPIO_PIN_6, GPIO_DIR_MODE_HW); GPIODirModeSet(GPIO_PORTD_BASE, GPIO_PIN_7, GPIO_DIR_MODE_HW); // // Configure the QEI block to capture on both edges, use quadrature // signals, and to capture the speed. // QEIConfigure(QEI_BASE, (QEI_CONFIG_CAPTURE_A_B | QEI_CONFIG_NO_RESET | QEI_CONFIG_QUADRATURE | QEI_CONFIG_NO_SWAP), 0); QEIVelocityConfigure(QEI_BASE, QEI_VELDIV_1, SysCtlClockGet() / 100); // // Enable velocity timer expiration interrupt. // QEIIntEnable(QEI_BASE, QEI_INTTIMER); IntEnable(INT_QEI); // // Enable the QEI block. // QEIEnable(QEI_BASE); QEIVelocityEnable(QEI_BASE); // // Set the QEI interrupt to the second highest priority. It shouldn't // be as high as the hall effect sensors (i.e. it shouldn't interfere // with commutation of the motor) but it should be handled ahead of the // other interrupts. // IntPrioritySet(INT_QEI, 0x40); } else { // // Enable software QEI. // g_bSoftwareQEI = true; // // Enable the timer that will be used to provide the timer period // interrupt. // SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0); // // Configure and enable a timer to provide interrupt every 1/100th of a // second. // TimerConfigure(TIMER0_BASE, TIMER_CFG_32_BIT_PER); TimerLoadSet(TIMER0_BASE, TIMER_A, (SysCtlClockGet() / 100) - 1); TimerEnable(TIMER0_BASE, TIMER_A); // // Enable the interrupt handler for the timer. // TimerIntEnable(TIMER0_BASE, TIMER_TIMA_TIMEOUT); IntEnable(INT_TIMER0A); // // Configure the optical encoder GPIO pin as an input with interrupt // generation on the rising edge. // GPIODirModeSet(GPIO_PORTC_BASE, GPIO_PIN_4, GPIO_DIR_MODE_IN); GPIOIntTypeSet(GPIO_PORTC_BASE, GPIO_PIN_4, GPIO_RISING_EDGE); // // Enable the optical encoder GPIO pin interrupt. // GPIOPinIntEnable(GPIO_PORTC_BASE, GPIO_PIN_4); IntEnable(INT_GPIOC); // // Set the QEI interrupts to the second highest priority. It shouldn't // be as high as the hall effect sensors (i.e. it shouldn't interfere // with commutation of the motor) but it should be handled ahead of the // other interrupts. Both interrupts are set to the same priority as a // form of mutual exclusion between the two interrupt handlers (it is // guaranteed that one handler will not interrupt the other, so they // can share global variables without special consideration of being // interrupted by the other). // IntPrioritySet(INT_GPIOC, 0x40); IntPrioritySet(INT_TIMER0A, 0x40); } } //***************************************************************************** // //! Debug command callback function. //! //! \param ulValue is ignored. //! //! This function is called when the debug command is entered by the user. It //! toggles closed-loop debug mode, in which the measured motor speed is //! printed periodically in order to evaluate the performance of the PID gain //! factors. //! //! \return None. // //***************************************************************************** static void DebugHandler(unsigned long ulValue) { // // Is debug mode currently enabled or disabled? // if(g_bDebug) { // // Disable debug mode. // g_bDebug = false; // // Restore the user interface display. // UIDisplay("\n"); } else { // // Enable debug mode. // g_bDebug = true; // // Start on a fresh line for the user interface. // UIDisplay("\r\n"); } } //***************************************************************************** // //! Run command callback function. //! //! \param ulValue is ignored. //! //! This function is called when the run command is entered by the user. It //! toggles the running state of the motor. //! //! \return None. // //***************************************************************************** static void RunHandler(unsigned long ulValue) { // // Is the motor running? // if(g_iRunning == MOTOR_RUNNING) { // // Have the UI indicate that the motor is stopped. // g_pcDisplay[2] = 's'; // // Begin stopping the motor. This prevents the QEI interrupt from // changing the state of the PWM outputs. // g_iRunning = MOTOR_STOPPING; // // Disable the PWM outputs. // PWMOutputState(PWM_BASE, PHASE_A | PHASE_B | PHASE_C, false); } else { // // Have the UI indicate that the motor is running. // g_pcDisplay[2] = 'r'; // // Start the motor running. // g_bReverse = g_bReverse ? false : true; g_iRunning = MOTOR_RUNNING; // // Turn off the inversion of all the switches. // PWMOutputInvert(PWM_BASE, PHASE_A | PHASE_B | PHASE_C, false); // // Fake an interrupt from the Hall effect sensors to get the correct // PWM outputs enabled. // GPIOIntHandler(); } } //***************************************************************************** // //! Speed command callback function. //! //! \param ulValue is the new target speed. //! //! This function is called when the speed command is entered by the user. It //! causes the supplied speed to become the target speed for the motor. //! //! \return None. // //***************************************************************************** static void SpeedHandler(unsigned long ulValue) { unsigned long ulIdx; // // Enforce the upper and lower bound on the motor speed. // if(ulValue > MAX_APP_MOTOR_SPEED) { ulValue = MAX_APP_MOTOR_SPEED; } if(ulValue < MIN_APP_MOTOR_SPEED) { ulValue = MIN_APP_MOTOR_SPEED; } // // Set the target speed in the display. // g_pcDisplay[6] = '0' + ((ulValue / 1000) % 10); g_pcDisplay[7] = '0' + ((ulValue / 100) % 10); g_pcDisplay[8] = '0' + ((ulValue / 10) % 10); g_pcDisplay[9] = '0' + (ulValue % 10); // // Find range entry in speed mapping table. // for(ulIdx = 0; ulIdx < SPEEDMAP_ENTRIES; ulIdx++) { if(ulValue < g_psSpeedMap[ulIdx + 1].ulSpeed) { break; } } // // Perform a linear interpolation between the two selected entries of the // speed map. // g_ulBaseDutyCycle = (((g_psSpeedMap[ulIdx].ulDutyCycle + (((ulValue - g_psSpeedMap[ulIdx].ulSpeed) * (g_psSpeedMap[ulIdx + 1].ulDutyCycle - g_psSpeedMap[ulIdx].ulDutyCycle)) / (g_psSpeedMap[ulIdx + 1].ulSpeed - g_psSpeedMap[ulIdx].ulSpeed))) * g_ulPwmPeriod) / 10000); // // Save the new target speed. // g_ulTarget = ulValue; // // See if open loop operation is enabled. // if(g_bOpenLoop) { // // Set the PWM duty cycle based on the requested target speed. // SetPWMDutyCycle(g_ulBaseDutyCycle); } } //***************************************************************************** // //! Loop command callback function. //! //! \param ulValue is ignored. //! //! This function is called when the loop command is entered by the user. It //! causes the control of the motor to toggle between open-loop and closed-loop //! operation. //! //! \return None. // //***************************************************************************** static void LoopHandler(unsigned long ulValue) { // // Is the motor currently being driven open- or closed-loop? // if(g_bOpenLoop) { // // Set the UI indicator for closed-loop operation. // g_pcDisplay[1] = 'C'; // // Initialize the PID controller. // PIDInitialize(&g_sPID, 100000, -100000, g_lPGain, g_lIGain, g_lDGain); // // Indicate that the motor is now being driven in a closed-loop // fashion. Nothing further is done at this point; at the next QEI // interrupt the PID controller will be run and start adjusting the // duty cycle as it sees fit. // g_bOpenLoop = false; } else { // // Indicate that the motor is now being driven in an open-loop fashion. // g_bOpenLoop = true; // // Set the UI indicator for open-loop operation. // g_pcDisplay[1] = 'O'; // // Set the PWM duty cycle based to the base duty cycle. // SetPWMDutyCycle(g_ulBaseDutyCycle); } } //***************************************************************************** // //! Proportional gain callback function. //! //! \param ulValue is the new proportional gain factor. //! //! This function is called when the proportional feedback command is entered //! by the user. It causes the supplied gain factor to become the proportional //! gain for the PID algorithm. //! //! \return None. // //***************************************************************************** static void PGainHandler(unsigned long ulValue) { // // Save the new proportional gain factor. // g_lPGain = (long)ulValue; // // Adjust the settings of the PID algorithm. // PIDSetGains(&g_sPID, g_lPGain, g_lIGain, g_lDGain); } //***************************************************************************** // //! Integral gain callback function. //! //! \param ulValue is the new integral gain factor. //! //! This function is called when the integral feedback command is entered by //! the user. It causes the supplied gain factor to become the integral gain //! for the PID algorithm. //! //! \return None. // //***************************************************************************** static void IGainHandler(unsigned long ulValue) { // // Save the new integral gain factor. // g_lIGain = (long)ulValue; // // Adjust the settings of the PID algorithm. // PIDSetGains(&g_sPID, g_lPGain, g_lIGain, g_lDGain); } //***************************************************************************** // //! Derivitive gain callback function. //! //! \param ulValue is the new derivitive gain factor. //! //! This function is called when the derivitive feedback command is entered by //! the user. It causes the supplied gain factor to become the derivitive gain //! for the PID algorithm. //! //! \return None. // //***************************************************************************** static void DGainHandler(unsigned long ulValue) { // // Save the new derivitive gain factor. // g_lDGain = (long)ulValue; // // Adjust the settings of the PID algorithm. // PIDSetGains(&g_sPID, g_lPGain, g_lIGain, g_lDGain); } //***************************************************************************** // //! Displays the current PID gain factors. //! //! \param ulValue is ignored. //! //! This function is called when the status command is entered by the user. It //! displays the current PID gain factors. //! //! \return None. // //***************************************************************************** static void StatusHandler(unsigned long ulValue) { char pcBuffer[24]; // // Set the basic format of the string to be displayed. // strcpy(pcBuffer, "\r\nP=000 I=000 D=000\r\n"); // // Put the proportional gain factor into the string. // pcBuffer[4] = '0' + ((g_lPGain / 100) % 10); pcBuffer[5] = '0' + ((g_lPGain / 10) % 10); pcBuffer[6] = '0' + (g_lPGain % 10); // // Put the integral gain factor into the string. // pcBuffer[10] = '0' + ((g_lIGain / 100) % 10); pcBuffer[11] = '0' + ((g_lIGain / 10) % 10); pcBuffer[12] = '0' + (g_lIGain % 10); // // Put the derivitive gain factor into the string. // pcBuffer[16] = '0' + ((g_lDGain / 100) % 10); pcBuffer[17] = '0' + ((g_lDGain / 10) % 10); pcBuffer[18] = '0' + (g_lDGain % 10); // // Display the current gain factors for the user. // UIDisplay(pcBuffer); } //***************************************************************************** // //! Hard fault handler. //! //! This is the handler for hard faults, if they occur. Immediate action must //! be taken to shut off the PWM outputs to prevent any potential damage to the //! motor. //! //! \return None. // //***************************************************************************** void HardFault(void) { // // Disable all the PWM outputs. // PWMOutputState(PWM_BASE, PHASE_A | PHASE_B | PHASE_C, false); // // Indicate that a hard fault has occurred. // UIDisplay("\r\nHard Fault!"); // // Loop forever. // while(1) { } } //***************************************************************************** // //! Program entry point. //! //! This is the entry point for the brushless DC motor control application. It //! is responsible for getting the system setup and operational. This is //! called at the base activation level, so any exception will preempt this //! function. //! //! \return Never returns. // //***************************************************************************** int main(void) { // // Setup the device clocking. // SysCtlClockSet(SYSDIV | CLKUSE | SYSCTL_XTAL_6MHZ | SYSCTL_OSC_MAIN); // // Compute the PWM period based on the clock. // g_ulPwmPeriod = SysCtlClockGet() / 16000; #if (PWMDIV == SYSCTL_PWMDIV_2) g_ulPwmPeriod /= 2; #endif // // Enable the GPIO peripherals. // SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA); SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB); SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC); SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD); SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE); // // Set the static portion of the user interface display. // strcpy(g_pcDisplay, "\rOs s:0000 a:0000"); // // Enable processor interrupts. // IntMasterEnable(); // // Initialize the user interface, GPIO, PWM, and QEI drivers. // UIInitialize(&g_sUI); SetupGPIO(); SetupPWM(); SetupQEI(); // // Set the priority of the UART interrupt. This needs to be lower than // the hall effect sensors and the QEI interrupt since it is the least // critical operation in the system. // IntPrioritySet(INT_UART0, 0x80); // // The default motor speed is 1000rpm. // SpeedHandler(1000); // // Loop forever. // while(1) { // // Update the user interface every 32nd time through the loop. // if((g_ulSpeedCount & 63) == 0) { // // Set the current speed in the display. // g_pcDisplay[13] = '0' + ((g_ulSpeed / 1000) % 10); g_pcDisplay[14] = '0' + ((g_ulSpeed / 100) % 10); g_pcDisplay[15] = '0' + ((g_ulSpeed / 10) % 10); g_pcDisplay[16] = '0' + (g_ulSpeed % 10); // // Update the user interface if not in debug mode. // if(!g_bDebug) { UIUpdate(); } } // // Go to sleep. // // This makes it impossible to break into the processor via the // debugger so it is commented out for now. // //SysCtlSleep(); } }