📄 main.c
字号:
// Wait for conversion to complete.
while((ADCSRA & (1 << ADSC)))
{
}
speedReferenceADC = ADCH;
// Read voltage reference.
// Change ADC channel.
ADMUX = ADMUX_REF_VOLTAGE;
// Start conversion manually.
ADCSRA |= (1 << ADSC);
// Wait for conversion to complete.
while((ADCSRA & (1 << ADSC)))
{
}
referenceVoltageADC = ADCH;
// Enable current measurements in ADC ISR.
ADMUX = ADMUX_CURRENT;
ADCSRA |= (1 << ADATE) | (1 << ADIE) | ADC_PRESCALER;
}
else
{
unsigned char tempADMUX;
tempADMUX = ADMUX;
// Read current
// Make sure that a sample is not in progress
while (ADCSRA & (1 << ADSC))
{
}
// Change channel
ADMUX = ADMUX_CURRENT;
// Start conversion manually.
ADCSRA |= (1 << ADSC);
// Wait for conversion to complete.
while((ADCSRA & (1 << ADSC)))
{
}
shuntVoltageADC = ADCH;
currentUpdated = TRUE;
// Restore ADC channel.
ADMUX = tempADMUX;
ADCSRA |= (1 << ADATE) | (1 << ADIE) | ADC_PRESCALER;
}
}
/*! \brief Commutates and prepares for new zero-cross detection.
*
* This interrupt service routine is triggered exactly when a commutation
* is scheduled. The commutation is performed instantly and Timer/counter0
* is reset to measure the delay between commutation and zero-cross detection.
*
* Commutation causes large transients on all phases for a short while that could
* cause false zero-cross detections. A zero cross detection hold-off period is
* therefore used to avoid any false readings. This is performed by using Timer/counter1
* compare B. The compare is set to happen after the specified hold-off period.
* Timer/counter1 compare B interrupt handler then enables the zero-cross detection.
*/
#pragma vector=TIMER1_COMPA_vect
__interrupt void Commutate()
{
// Commutate and clear commutation timer.
DRIVE_PORT = nextDrivePattern;
TCNT1 = 0;
zcPolarity = nextCommutationStep & 0x01;
// Set zero-cross detection holdoff time.
CLEAR_ALL_TIMER1_INT_FLAGS;
OCR1B = ZC_DETECTION_HOLDOFF_TIME_US;
SET_TIMER1_INT_HOLDOFF;
__watchdog_reset();
}
/*! \brief Enables zero-cross detection.
*
* This interrupt service routine is triggered when the zero cross detection
* hold-off time after commutation is over. All Timer/counter1 interrupts are
* disabled and Timer/counter0 (PWM) overflow interrupt is enabled to allow
* the ADC readings to be used for zero-cross detection.
*/
#pragma vector=TIMER1_COMPB_vect
__interrupt void EnableZCDetection()
{
// Enable TCNT0 overflow ISR.
CLEAR_ALL_TIMER0_INT_FLAGS;
CLEAR_ALL_TIMER1_INT_FLAGS;
SET_TIMER0_INT_ZC_DETECTION;
DISABLE_ALL_TIMER1_INTS;
// Set up ADC for zero-cross detection
ADMUX = ADMUXTable[nextCommutationStep];
// Wait for ADC to complete
while (!(ADCSRA & (1 << ADIF)))
{
}
ADCSRA &= ~(1 << ADIE);
ADCSRA |= (1 << ADSC) | (1 << ADATE);
// Rotate commutation step counter.
nextCommutationStep++;
if (nextCommutationStep >= 6)
{
nextCommutationStep = 0;
}
nextDrivePattern = driveTable[nextCommutationStep];
}
/* \brief ADC complete interrupt service routine, used for current measurements.
*
* This interrupt service routine is only enabled when current measurements are
* auto-triggered by the PWM counter overflow. The measured value is simply
* copied to \ref shuntVoltageADC, the \ref currentUpdated flag is set and
* Timer0 (PWM timer) interrupt flags are cleared.
*/
#pragma vector=ADC_vect
__interrupt void CurrentMeasurementComplete()
{
shuntVoltageADC = ADCH;
currentUpdated = TRUE;
CLEAR_ALL_TIMER0_INT_FLAGS;
}
/*! \brief Watchdog interrupt
*
* This ISR is called before the watchdog timer resets the device because of a stall.
* It simply disables driving, but other tasks that must be done before a watchdog reset,
* such as storage of variables in non-volatile memory can be done here.
*/
#pragma vector=WDT_vect
__interrupt void WatchdogISR()
{
DISABLE_DRIVING;
for(;;)
{
;
}
}
/*! \brief Overcurrent interrupt
*
* This interrupt service routine cuts power to the motor when an overcurrent situation
* is detected.
*/
#ifdef ANALOG_COMPARATOR_ENABLE
#pragma vector=ANA_COMP_vect
__interrupt void OverCurrentISR()
{
DISABLE_DRIVING;
for(;;)
{
;
}
}
#endif
/*! \brief Generates a delay used during startup
*
* This functions is used to generate a delay during the startup procedure.
* The length of the delay equals delay * STARTUP_DELAY_MULTIPLIER microseconds.
* Since Timer/Counter1 is used in this function, it must never be called when
* sensorless operation is running.
*/
void StartupDelay(unsigned int delay)
{
CLEAR_ALL_TIMER1_INT_FLAGS;
do
{
TCNT1 = 0xffff - STARTUP_DELAY_MULTIPLIER;
// Wait for timer to overflow.
while (!(TIFR1 & (1 << TOV1)))
{
}
CLEAR_ALL_TIMER1_INT_FLAGS;
delay--;
} while (delay);
}
#ifdef SPEED_CONTROL_CLOSED_LOOP
/*! \brief Controls the PWM duty cycle based on speed set-point and current consumption.
*
* This function controls the PWM duty cycle by calling a speed controller and a
* current controller. The speed controller signal is directly applied to the duty
* cycle. The current controller signal is used to limit the maximum duty cycle.
*/
static void PWMControl(void)
{
signed int speedCompensation;
static unsigned char currentCompensation = 0;
static signed int duty = STARTUP_PWM_COMPARE_VALUE;
// Run speed control only if a new speed measurement is available.
if (speedUpdated)
{
speedCompensation = SpeedControl();
speedUpdated = FALSE;
duty += speedCompensation;
}
// Run current control only if a new current measurement is available.
if (currentUpdated)
{
currentCompensation = CurrentControl();
currentUpdated = FALSE;
}
// Keep duty cycle within limits.
if (duty < MIN_PWM_COMPARE_VALUE)
{
duty = MIN_PWM_COMPARE_VALUE;
}
if (duty > (MAX_PWM_COMPARE_VALUE - currentCompensation))
{
duty = MAX_PWM_COMPARE_VALUE - currentCompensation;
}
SET_PWM_COMPARE_VALUE((unsigned char)duty);
}
#endif
#ifdef SPEED_CONTROL_OPEN_LOOP
static void PWMControl(void)
{
// Only update duty cycle if a new speed reference measurement has been made. (Done right after speed measurement is ready)
if (speedUpdated)
{
// Calculate duty cycle from speed reference value.
SET_PWM_COMPARE_VALUE(MIN_PWM_COMPARE_VALUE + speedReferenceADC * (MAX_PWM_COMPARE_VALUE - MIN_PWM_COMPARE_VALUE) / ADC_RESOLUTION);
}
}
#endif
/*! \brief Calculates the current speed in electrical RPM.
*
* This function calculates the current speed in electrical rotations per
* minute from the global variable \ref filteredTimeSinceCommutation.
*/
static unsigned long CalculateSpeed()
{
// Copy used to minimize period where interrupts are disabled.
unsigned int filteredTimeSinceCommutationCopy;
unsigned long rotationPeriod;
unsigned long speed;
/*
Disable interrupts to ensure that \ref filteredTimeSinceCommutation is accessed in
an atomic operation.
*/
__disable_interrupt();
filteredTimeSinceCommutationCopy = filteredTimeSinceCommutation;
__enable_interrupt();
/*
filteredTimeSinceCommutation is one half commutation time. Must be multiplied by 12 to get
one full rotation.
*/
rotationPeriod = (unsigned long)filteredTimeSinceCommutationCopy * 12;
speed = (TICKS_PER_MINUTE / rotationPeriod);
return speed;
}
/*! \brief Calculates the speed set-point in electrical RPM.
*
* This function calculates the speed set-point from the global variable
* speedReferenceADC.
*
* In this implementation, the speed reference values from 0x00 to 0xff are
* linearly mapped into the allowable speed range, set by \ref MIN_SPEED and
* \ref MAX_SPEED.
*/
static unsigned long CalculateSpeedSetpoint()
{
return (MIN_SPEED + ((MAX_SPEED - MIN_SPEED) * (unsigned int)speedReferenceADC) / ADC_RESOLUTION);
}
/*! \brief Calculates current consumption.
*
* This function calculates the current consumption in milliAmperes from the
* global variable \ref shuntVoltageADC. The external know reference voltage
* is used to compensate for varying AREF voltage.
*/
static unsigned int CalculateCurrent()
{
unsigned long ADCref;
unsigned int current;
// Calculate the voltage at AREF pin (scaled down motor supply voltage),
// using the known reference voltage. (In milliVolts)
ADCref = EXTERNAL_REF_VOLTAGE * 256UL / referenceVoltageADC;
// Calculate the current through the shunt. (In milliAmperes)
current = (unsigned int)((shuntVoltageADC * ADCref * 1000UL / 256UL) / SHUNT_RESISTANCE);
return current;
}
/*! \brief Speed control loop
*
* This function runs a simple P-regulator speed control loop. The duty cycle
* is only updated each time a new speed measurement is ready (on each zero-crossing).
* The time step is thus variable, so the P-gain of the P-regulator corresponds to
* a speed-varying P-gain for the continous system.
*/
static signed int SpeedControl(void)
{
unsigned long speedSetpoint;
unsigned long currentSpeed;
signed long speedError;
signed long dutyChange;
speedSetpoint = CalculateSpeedSetpoint();
currentSpeed = CalculateSpeed();
speedError = (speedSetpoint - currentSpeed);
dutyChange = speedError * P_REG_K_P / P_REG_SCALING;
return dutyChange;
}
/*! \brief Current control loop
*
* This function is called after the speed control loop. The desired duty cycle
* calculated by the speed control loop is available, and this function is
* responsible for adjusting the duty cycle to ensure that the current stays
* within limits.
*/
static unsigned char CurrentControl(void)
{
unsigned int current;
unsigned int overCurrentCorrection = 0;
current = CalculateCurrent();
// Cut power to motor if current is critically high.
if (current > CURRENT_LIMITER_CRITICAL)
{
DRIVE_PORT = 0x00;
for (;;)
{
// Stop and let watchdog timer reset part.
}
}
if (current > CURRENT_LIMITER_START)
{
overCurrentCorrection = (current - CURRENT_LIMITER_START) * CURRENT_LIMITER_FACTOR;
}
if (overCurrentCorrection > 255)
{
return 255;
}
return overCurrentCorrection;
}
/*! \mainpage
* \section Intro Introduction
* This documents data structures, functions, variables, defines, enums, and
* typedefs in the software for application note AVR444.
*
* \section CI Compilation Info
* This software was written for the IAR Embedded Workbench 4.11A.
*
* To make project:
* <ol>
* <li> Add the file main.c to project.
* <li> Under processor configuration, select device ATmega48, ATmega88
* or ATmega168
* <li> Enable bit definitions in I/O include files
* <li> Under "C/C++ compiler->Code->Register utilization", select 5
* registers (R11..R15) locked for global variables.
* <li> High optimization on speed is recommended for best performance
* </ol>
*
* \section DI Device Info
* The included source code is written for ATmega48/88/168.
*/
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -