📄 analog.c
字号:
if(64 == ctr)
return (signed int) (avg >> 6); //use this optimized version after the first 64 seconds
else
if(1 == ctr)
return (signed int) (avg);
else
return (signed int) (avg / ctr ); //this takes longer due to division.
}
/* ******************************************************************** *
* Coulomb Counter, Instantaneous conversion complete ISR
*
* This interrupt fires after every conversion, at 3.9ms intervals.
* Every 256 interrupts is therefore 1 second.
*
* Each count of the result is 53.7uV, which, into a 5mOhm sense R,
* indicates 10.74mA flowing. This is quick-responding and can therefore
* be used in conjunction with cell voltage measurements to calculate
* cell impedance by watching the delta-V on the cells when compared to
* any available delta-I. There obviously must be sufficient delta-I
* to permit a reasonably accurate division result, otherwise the
* calculation could be way off. Note that the CC ADC is separate
* hardware from the 12-bit general-purpose ADC and can therefore do a
* current measurement in parallel with one cell voltage measurement.
* There is a global variable, LatestCCI, that always contains the most
* recent value. The intent is that this value would be captured along
* with the individual cell voltage at a given instant.
*
* ( NOTE: if you use LatestCCI, remember that it is NOT SCALED TO mA! )
*
* We also use this as the basis of the required 1-minute averaged current.
* We play some tricks to get this value without doing ugly math.
* Since the values need to be available in milliamps, we must accumulate
* lots of samples to make up for the fact there's over a 10X scale delta.
* Specifically, we get 1 count for every 10.74mA. If we accumulate
* 10.74 x 16 = 172 samples, then we can simply divide by 16 to get a
* 1mA/step average result. We will thus take 172 samples per second.
* (Note: this value could be modified SLIGHTLY to effect a 'calibration'
* without adding additional math processing burden.)
*
* The next problem is to spread these samples out evenly over the sampling
* period (1 second). To do this, we use a simple first-order sigma-delta
* software modulator; if its 1-bit output is a '1', then we accumulate the
* sample that's available at this moment.
*
* ******************************************************************** */
#define CCI_CAL 172 /* # of samples we need to accumulate in a second */
#pragma vector = CCADC_vect
__interrupt void CC_Instantaneous_ISR(void)
{
static unsigned char timer = 0; //256 interrupts = 1 second
static signed long sl = 0; //averaging accumulator
static unsigned char mod_remainder; //used by sigma-delta modulator
signed int temp,temp2;
if(PowerMode == POWERMODE_IDLE) //do Regular Current mode instead.
{
temp = CADIC;
temp <<= 3; //note: CADIC is actually 13 bits incl. sign
RunningAcc += (signed long) temp; //update the charge state
RunningAcc += (signed long) temp; //update the charge state
RunningAcc += (signed long) temp; //update the charge state
RunningAcc += (signed long) temp; //must do it 4 times (Instantaneous * 32 = Accumulate)
//Note: it is POSSIBLE to overflow here, but not likely unless pushing lots of current.
temp2 = CADIC;
temp = temp2;
temp += temp2;
temp += temp2/2; //10.5x, a good approximation of 10.7x
temp -= CCIoffset; //subtract the offset calibration value
CCarray_AddSample(temp); //update the 64-second average
return;
}
//If get here, we're running in Active mode, so we use this only for 1-second avg current reading.
if(0 == ++timer) //one per second, pull out the accumulated
{ // result and update the 64-second list.
sl /= 16; //divide result by 16, per our math.
sl -= CCIoffset; //subtract the offset calibration value
CCarray_AddSample((signed int) sl); //extract the properly scaled current value.
sl = 0; //clear the accumulator
}
// Now, accumulate SOME of the available samples, specifically, (10.74 x 16) of them,
// to ultimately yield a 1mA-per-bit current measurement in the accumulator variable "sl".
temp = mod_remainder; //(this auto-clears the upper byte, by promotion.)
temp += CCI_CAL; //if this addition causes a CARRY, then USE this sample.
mod_remainder = (unsigned char) temp; //Re-save.
if(temp>>8) //was a CARRY generated?
{
temp = CADIC; //grab the result
// temp -= CCIoffset; // remove offset from each sample, different value than the one currently used/calibrated
LatestCCI = temp; //update the globally-available value
sl += temp;
}
else
LatestCCI = CADIC; //update the globally-available value
}
/* =====================================================================================
=====================================================================================
===================================================================================== */
/* ******************************************************************** *
* Coulomb Counter, Regular Current ISR
*
* This interrupt is tripped when the discharge current exceeds the amount
* specified in the CADRDC register (set up by the CCmode() function).
* When this fires, it indicates that the system needs to switch back to
* Active Mode.
*
* ******************************************************************** */
#pragma vector = CCADC_REG_CUR_vect
__interrupt void CC_RegularCurrent_ISR(void)
{
ChangePowerMode(POWERMODE_ACTIVE,0);
}
/* =====================================================================================
=====================================================================================
===================================================================================== */
/* ********************************************************************
* Coulomb Counter, Accumulator ISR
*
*! \todo
* This interrupt is the main mechanism for collecting charge estimates
* for the 'fuel gauge' function. Note that CCADC offset error must also
* be corrected here, and temperature should be taken into account in
* subsequent calculations as well.
*
* ******************************************************************** */
//unsigned char ccindex = 0;
//long ccarray[256];
#pragma vector = CCADC_ACC_vect
__interrupt void CC_Accumulator_ISR(void)
{
union {signed long acc; unsigned char byte[4];} lastCCreading;
//! \todo If using IAR3.20, the following lines need to be enabled and the one below disabled.
/*
lastCCreading.byte[0] = CADAC0; //grab the ACC value
lastCCreading.byte[1] = CADAC1; //grab the ACC value
lastCCreading.byte[2] = CADAC2; //grab the ACC value
lastCCreading.byte[3] = CADAC3; //grab the ACC value
*/
lastCCreading.acc = CADAC; // Only works with IAR 4.10 and later
lastCCreading.acc -= (signed long) CCoffset; //although the CC's offset is temperature-dependent
// we have NOT implemented thermal compensation.
if(CC_delay_acc)
{
CC_delay_acc--;
return;
}
RunningAcc += lastCCreading.acc; //merge this sample with the main accumulator.
}
/* =====================================================================================
=====================================================================================
===================================================================================== */
/* General description of ADC usage:
*
* The ADC handles 10 different inputs. We trigger a periodic
* scan through all the channels back-to-back by a timer.
* Then, when desired, the user can request a particular measurement,
* according to its function group, and will get back a properly
* scaled and calibrated value. For instance, reading a temperature
* will scale the ADC result including gain and offset; when reading
* a particular cell's voltage, it will be corrected according to the
* offset and gain for that particular channel.
*
* When reading cell voltages, we must ensure that cell balancing FETs
* are disabled.
*
* The time required for fully charging a cell's input bypass cap to
* within 1/4000th of full-scale (about 1mV error, max.) is approx.
* 8 time-constants (not 9, since the cap starts at cellV/2 rather
* than at 0.00V). In the configuration we use, we have a 0.1uF with
* a 500ohm resistor, giving a TC of 50uS. 400uS is therefore required
* before commencing a reading of any cell's ADC channel. We accomplish
* this delay without cost by reading the other channels first, THEN
* reading the cell inputs.
*
*/
void ADCinit(void)
{
// char i;
ReadFactoryCalibration();
// Note: SLOW_RC_CAL is used in calculations on the Wakeup timer to
// calculate the elapsed time, rather than to adjust its oscillator.
}
//This routine starts a scan of all 10 ADC channels back-to-back.
// The parameter specifies which Thermistor input to read (0-3).
void StartAdc(unsigned char select)
{
unsigned char temp = (1<<select);
//! \todo NOTE: If not using ADC0-3 exclusively as analog inputs, you MUST modify the following code.
DIDR0 = 0x0F; //disable ADC0-3 digital input buffer
PORTA &= 0xE0;
PORTA |= (1<<4); //drive PA4 high!
DDRA &= 0xE0;
DDRA |= (1<<4) | temp; //make PA4 and the selected signal be Outputs
VADMUX = 5; //set up for channel 5 first.
VADCSR = (1<<VADEN) | (1<<VADCCIF); //clear any pending int
VADCSR = (1<<VADEN) | (1<<VADSC) | (1<<VADCCIE); //Start the first conversion & ena int's
}
// Automatically scan through all ADC channels.
// When all 10 are done, this interrupt disables itself.
#pragma vector = VADC_vect
__interrupt void ADC_INT(void)
{
unsigned char temp;
static char counter; //added to handle Rev E silicon errata.
temp = VADMUX;
ADCbuffer[temp-1] = VADC; //save the result
if((temp <= 4) && (temp >= 1)) //just read a Cell?
cell_current[temp-1] = LatestCCI; //save its associated CCI reading for impedance.
if(temp == 5) { // init counters before getting there during scan.
counter = VPTAT_READINGS; // per errata for Rev E silicon
temp++;
}
if((temp == 6)||(temp == 7)) { //
if(--counter == 0) { // wait for VPTAT to stabilize, per errata for rev.E
temp++;
counter = ADC0_READINGS; // wait for VREF to stabilize after VPTAT readings
}
} else {
temp++;
if(10 == temp) { // earliest possible 8, latest 10, if earlier use 6/7 ifs above
DisableCellBalancing(); //if more than 519uS is needed, we can do this earlier than 10!
} else {
if(10 < temp) { // continue with cell1-4?
temp = 1;
} else {
if(5 == temp) { //have we scanned ALL channels now?
SetADCScanDone; //flag Mainline that we have new samples!
EnableCellBalancing();
VADCSR = 0; //disable this ADC and its Interrupt.
return;
}
}
}
}
VADMUX = temp;
VADCSR |= (1<<VADSC); //start next conversion now.
}
//This should be called from main().
//After an ADC scan, this will calculate new results.
//This needs to know the state from the thermistor state machine.
void CalculateADCresults(void)
{
unsigned long calc;
unsigned char index;
// unsigned int fixedV;
unsigned int thermV;
//Calculate new cell voltages
for(index = 0; index < PACKSTACK; index++)
{
calc = ADCbuffer[index]<<2;
calc = calc * ADCgain[index];
calc >>= 16;
CellV[index] = (unsigned int)(calc);
if((unsigned int)calc < CELL_TOOLITTLEV)
DoShutdown(SHUTDOWN_REASON_UNDERVOLTAGE); //go to Shutdown Mode if cell is drained!
if((unsigned int)calc > CELL_TOOMUCHV)
DoShutdown(SHUTDOWN_REASON_OVERVOLTAGE); //go to Shutdown Mode if cell is too full!
}
//! \todo Code could be added HERE to do something with the cell_current[] array,
//! for the purpose of determining cell impedances.
//Calculate new ADC4 (PA4) reading due to its scale factor of ~0.2X
calc = ADCbuffer[4];
calc = calc * 344;
VPA4 = (unsigned int) (calc >> 8);
//Calculate on-chip temperature. There may be ways to optimize this,
// such as pre-scaling VTgain up by 2.5X, then scaling ADCbuffer[6] by 4X,
// so that when multiplied you get 10X. Can then shift the result UP by 2,
// then grab the upper two bytes as the result. Not sure if VTgain can be
// scaled by 2.5X and still fit in an INT. Doing this optimization would save a long div.
calc = ADCbuffer[5];
calc = calc * VTgain; //this gives 'K * 2^14
OnChipTemp = (unsigned int) (calc / 1638); //divide by (2^14 / 10) to get 0.1'K
//Update the result for the thermistor that was just checked.
index = ThermistorSelect; //ThermistorSelect says which output was low.
index++; //Go to next channel & use that reading as midpoint.
index &= 0x03;
thermV = ADCbuffer[index+7];
Thermistor[ThermistorSelect] = thermV;
}
// Returns on-chip temperature in 0.1 degrees Kelvin.
// Returns Thermistor resistance reading directly in Ohms (change this as needed).
// channel: 0=on-chip; 1-4 = PA0-3 thermistors
unsigned int ReadTemperature(unsigned char channel)
{
if(0 == channel)
return OnChipTemp;
// temperature = ADCbuffer[6] * VTgain;
// temperature = temperature / 1638; // =16384/10
// temperature -= 2731; //convert from 'K to 'C (0'C = 273.15K)
// return (unsigned int) temperature;
if(channel > 5)
return 0; //choose what error value you want.
//! \todo Insert calculations here to convert voltage reading(s) to temperature(s).
return Thermistor[channel-1]; //for now, just return the VADC result
}
//Read the cell voltage in millivolts (always positive). (cell = 1-4)
unsigned int ReadCell(char cell)
{
if((cell == 0) || (cell > 4))
return 0;
return CellV[cell-1];
}
void DisableCellBalancing(void)
{
CBCR = 0;
}
void EnableCellBalancing(void) //if CellToBalance = 0, no balancing req'd.
{
if(CellToBalance)
{
CBCR = (1<<(CellToBalance-1));
}
else
CBCR = 0;
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -