📄 main.c
字号:
// This file has been prepared for Doxygen automatic documentation generation.
/*! \file ********************************************************************
*
* Atmel Corporation
*
* - File : main.c
* - Compiler : IAR EWAAVR 2.28a/3.10c
*
* - Support mail : avr@atmel.com
*
* - Supported devices : ATmega48/88/168
*
* - AppNote : AVR444 - Sensorless control of three-phase brushless
* DC motors with ATmega48.
*
* - Description : Example of how to use the ATmega48 for sensorless
* control of a three phase brushless DC motor.
*
* $Revision: 1.1 $
* $Date: Monday, October 10, 2005 11:15:46 UTC $
*****************************************************************************/
#include "BLDC.h"
#include <ioavr.h>
#include <inavr.h>
//! Array of power stage enable signals for each commutation step.
unsigned char driveTable[6];
//! Array of ADC channel selections for each commutation step.
unsigned char ADMUXTable[6];
//! Array holding the intercommutation delays used during startup.
unsigned int startupDelays[STARTUP_NUM_COMMUTATIONS];
/*! \brief Filtered commutation timer variable and speed indicator.
* This value equals half the time of one commutation step. It is filtered
* through an IIR filter, so the value stored is not the most recent measuremnt.
* The variable is stored in registers R14-R15 for quicker access.
*/
__regvar __no_init volatile unsigned int filteredTimeSinceCommutation @14;
/*! \brief The power stage enable signals that will be output to the motor drivers
* at next commutation.
*
* This variable holds the pattern of enable signals that will be output to the
* power stage at next commutation. It is stored in register R13 for quick access.
*/
__regvar __no_init volatile unsigned char nextDrivePattern @13;
/*! \brief Polarity of the expected zero crossing.
*
* The polarity of the expected zero crossing.
* Could be eiter \ref EDGE_FALLING or \ref EDGE_RISING.
*/
__regvar __no_init volatile unsigned char zcPolarity @ 12;
/*! \brief The commutation step that starts at next commutation.
*
* The commutation step that starts at next commutation. This is used to keep
* track on where in the commutation cycle we are. Stored in register R11 for
* quick access
*/
__regvar __no_init volatile unsigned char nextCommutationStep @11;
//! ADC reading of external analog speed reference.
volatile unsigned char speedReferenceADC;
//! ADC reading of shunt voltage.
volatile unsigned char shuntVoltageADC = 0;
//! ADC reading of the known external reference voltage.
volatile unsigned char referenceVoltageADC;
//! Flag that specifies whether a new external speed reference and a motor speed measurement is available.
volatile unsigned char speedUpdated = FALSE;
//! Flag that specifies whether a new current measurement is available.
volatile unsigned char currentUpdated = FALSE;
/*! \brief Program entry point.
*
* Main initializes all peripheral units used and calls the startup procedure.
* All commutation control from that point is done in interrupt routines.
*/
void main(void)
{
// Initialize all sub-systems.
ResetHandler();
InitPorts();
InitTimers();
InitADC();
MakeTables();
InitAnalogComparator();
// Run startup procedure.
StartMotor();
// Turn on watchdog for stall-detection.
WatchdogTimerEnable();
__enable_interrupt();
for(;;)
{
PWMControl();
}
}
/*! \brief Examines the reset source and acts accordingly.
*
* This function is called early in the program to disable watchdog timer and
* determine the source of reset.
*
* Actions can be taken, based on the reset source. When the watchdog is used as
* stall protection, a stall can be detected here. It is possible to e.g. store
* a variable in EEPROM that counts the number of failed restart attempts. After a
* certain number of attempts, the motor could simply refuse to continue until
* an external action happens, indicating that the rotor lock situation could be
* fixed.
*/
static void ResetHandler(void)
{
__eeprom unsigned static int restartAttempts;
// Temporary variable to avoid unnecessary reading of volatile register MCUSR.
unsigned char tempMCUSR;
tempMCUSR = MCUSR;
MCUSR = tempMCUSR & ~((1 << WDRF) | (1 << BORF) | (1 << EXTRF) | (1 << PORF));
// Reset watchdog to avoid false stall detection before the motor has started.
__disable_interrupt();
__watchdog_reset();
WDTCSR |= (1 << WDCE) | (1 << WDE);
WDTCSR = 0x00;
// Examine the reset source and take action.
switch (tempMCUSR & ((1 << WDRF) | (1 << BORF) | (1 << EXTRF) | (1 << PORF)))
{
case (1 << WDRF):
restartAttempts++;
if (restartAttempts >= MAX_RESTART_ATTEMPTS)
{
// Do something here. E.g. wait for a button to be pressed.
for (;;)
{
}
}
// Put watchdog reset handler here.
break;
case (1 << BORF):
//Put brownout reset handler here.
break;
case (1 << EXTRF):
restartAttempts = 0;
// Put external reset handler here.
break;
case (1 << PORF):
restartAttempts = 0;
// Put power-on reset handler here.
break;
}
}
/*! \brief Initializes I/O ports.
*
* Initializes I/O ports.
*/
static void InitPorts(void)
{
// Init DRIVE_DDR for motor driving.
DRIVE_DDR = (1 << UL) | (1 << UH) | (1 << VL) | (1 << VH) | (1 << WL) | (1 << WH);
// Init PORTD for PWM on PD5.
DDRD = (1 << PD5);
// Disable digital input buffers on ADC channels.
DIDR0 = (1 << ADC4D) | (1 << ADC3D) | (1 << ADC2D) | (1 << ADC1D) | (1 << ADC0D);
}
/*! \brief Initializes timers (for commutation timing and PWM).
*
* This function initializes Timer/counter0 for PWM operation for motor speed control
* and Timer/counter1 for commutation timing.
*/
static void InitTimers(void)
{
// Set up Timer/counter0 for PWM, output on OCR0B, OCR0A as TOP value, prescaler = 1.
TCCR0A = (0 << COM0A1) | (0 << COM0A0) | (1 << COM0B1) | (0 << COM0B0) | (0 << WGM01) | (1 << WGM00);
TCCR0B = (1 << WGM02) | (0 << CS02) | (0 << CS01) | (1 << CS00);
OCR0A = PWM_TOP_VALUE;
TIFR0 = TIFR0;
TIMSK0 = (0 << TOIE0);
// Set up Timer/counter1 for commutation timing, prescaler = 8.
TCCR1B = (1 << CS11) | (0 << CS10);
}
/*! \brief Initializes the AD-converter.
*
* This function initializes the AD-converter and makes a reading of the external
* reference voltage.
*/
static void InitADC(void)
{
// First make a measurement of the external reference voltage.
ADMUX = ADMUX_REF_VOLTAGE;
ADCSRA = (1 << ADEN) | (1 << ADSC) | (1 << ADIF) | (ADC_PRESCALER_16);
while (ADCSRA & (1 << ADSC))
{
}
referenceVoltageADC = ADCH;
// Initialize the ADC for autotriggered operation on PWM timer overflow.
ADCSRA = (1 << ADEN) | (0 << ADSC) | (1 << ADATE) | (1 << ADIF) | (0 << ADIE) | ADC_PRESCALER_8;
ADCSRB = ADC_TRIGGER_SOURCE;
}
/*! \brief Initializes the analog comparator.
*
* This function initializes the analog comparator for overcurrent detection.
*/
static void InitAnalogComparator(void)
{
#ifdef ANALOG_COMPARATOR_ENABLE
// Enable analog comparator interrupt on rising edge.
ACSR = (0 << ACBG) | (1 << ACI) | (1 << ACIE) | (1 << ACIS1) | (1 << ACIS0);
#endif
}
/*! \brief Initializes the watchdog timer
*
* This function initializes the watchdog timer for stall restart.
*/
static void WatchdogTimerEnable(void)
{
__disable_interrupt();
__watchdog_reset();
WDTCSR |= (1 << WDCE) | (1 << WDE);
WDTCSR = (1 << WDIF) | (1 << WDIE) | (1 << WDE) | (1 << WDP2);
__enable_interrupt();
}
/*! \brief Initializes arrays for motor driving and AD channel selection.
*
* This function initializes the arrays used for motor driving and AD channel
* selection that changes for each commutation step.
*/
static void MakeTables(void)
{
#if DIRECTION_OF_ROTATION == CCW
driveTable[0] = DRIVE_PATTERN_STEP1_CCW;
driveTable[1] = DRIVE_PATTERN_STEP2_CCW;
driveTable[2] = DRIVE_PATTERN_STEP3_CCW;
driveTable[3] = DRIVE_PATTERN_STEP4_CCW;
driveTable[4] = DRIVE_PATTERN_STEP5_CCW;
driveTable[5] = DRIVE_PATTERN_STEP6_CCW;
ADMUXTable[0] = ADMUX_W;
ADMUXTable[1] = ADMUX_V;
ADMUXTable[2] = ADMUX_U;
ADMUXTable[3] = ADMUX_W;
ADMUXTable[4] = ADMUX_V;
ADMUXTable[5] = ADMUX_U;
#else
driveTable[0] = DRIVE_PATTERN_STEP1_CW;
driveTable[1] = DRIVE_PATTERN_STEP2_CW;
driveTable[2] = DRIVE_PATTERN_STEP3_CW;
driveTable[3] = DRIVE_PATTERN_STEP4_CW;
driveTable[4] = DRIVE_PATTERN_STEP5_CW;
driveTable[5] = DRIVE_PATTERN_STEP6_CW;
ADMUXTable[0] = ADMUX_U;
ADMUXTable[1] = ADMUX_V;
ADMUXTable[2] = ADMUX_W;
ADMUXTable[3] = ADMUX_U;
ADMUXTable[4] = ADMUX_V;
ADMUXTable[5] = ADMUX_W;
#endif
startupDelays[0] = 200;
startupDelays[1] = 150;
startupDelays[2] = 100;
startupDelays[3] = 80;
startupDelays[4] = 70;
startupDelays[5] = 65;
startupDelays[6] = 60;
startupDelays[7] = 55;
}
/*! \brief Executes the motor startup sequence.
*
* This function locks the motor into a known position and fires off a
* commutation sequence controlled by the Timer/counter1 overflow interrupt.
*/
static void StartMotor(void)
{
unsigned char i;
SET_PWM_COMPARE_VALUE(STARTUP_PWM_COMPARE_VALUE);
nextCommutationStep = 0;
//Preposition.
DRIVE_PORT = driveTable[nextCommutationStep];
StartupDelay(STARTUP_LOCK_DELAY);
nextCommutationStep++;
nextDrivePattern = driveTable[nextCommutationStep];
for (i = 0; i < STARTUP_NUM_COMMUTATIONS; i++)
{
DRIVE_PORT = nextDrivePattern;
StartupDelay(startupDelays[i]);
ADMUX = ADMUXTable[nextCommutationStep];
// Use LSB of nextCommutationStep to determine zero crossing polarity.
zcPolarity = nextCommutationStep & 0x01;
nextCommutationStep++;
if (nextCommutationStep >= 6)
{
nextCommutationStep = 0;
}
nextDrivePattern = driveTable[nextCommutationStep];
}
// Switch to sensorless commutation.
TCNT1 = 0;
TIMSK1 = (1 << OCIE1A);
// Set filteredTimeSinceCommutation to the time to the next commutation.
filteredTimeSinceCommutation = startupDelays[STARTUP_NUM_COMMUTATIONS - 1] * (STARTUP_DELAY_MULTIPLIER / 2);
}
/*! \brief Timer/counter0 bottom overflow. Used for zero-cross detection.
*
* This interrupt service routine is called every time the up/down counting
* PWM counter reaches bottom. An ADC reading on the active channel is
* automatically triggered at the same time as this interrupt is triggered.
* This is used to detect a zero crossing.
*
* In the event of a zero crossing, the time since last commutation is stored
* and Timer/counter1 compare A is set up to trigger at the next commutation
* instant.
*/
#pragma vector=TIMER0_OVF_vect
__interrupt void MotorPWMBottom()
{
unsigned char temp;
// Disable ADC auto-triggering. This must be done here to avoid wrong channel being sampled on manual samples later.
ADCSRA &= ~((1 << ADATE) | (1 << ADIE));
// Wait for auto-triggered ADC sample to complete.
while (!(ADCSRA & (1 << ADIF)))
{
}
temp = ADCH;
if (((zcPolarity == EDGE_RISING) && (temp > ADC_ZC_THRESHOLD)) || ((zcPolarity == EDGE_FALLING) && (temp < ADC_ZC_THRESHOLD)))
{
unsigned int timeSinceCommutation;
// Find time since last commutation
timeSinceCommutation = TCNT1;
TCNT1 = COMMUTATION_CORRECTION;
// Filter the current ZC detection with earlier measurements through an IIR filter.
filteredTimeSinceCommutation = (COMMUTATION_TIMING_IIR_COEFF_A * timeSinceCommutation
+ COMMUTATION_TIMING_IIR_COEFF_B * filteredTimeSinceCommutation)
/ (COMMUTATION_TIMING_IIR_COEFF_A + COMMUTATION_TIMING_IIR_COEFF_B);
OCR1A = filteredTimeSinceCommutation;
speedUpdated = TRUE;
SET_TIMER1_INT_COMMUTATION;
CLEAR_ALL_TIMER1_INT_FLAGS;
// Disable Timer/Counter0 overflow ISR.
DISABLE_ALL_TIMER0_INTS;
// Read speed reference.
// Make sure that a sample is not in progress.
while (ADCSRA & (1 << ADSC))
{
}
// Change channel
ADMUX = ADMUX_SPEED_REF;
// Start conversion manually.
ADCSRA |= (1 << ADSC);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -