Firmware Part 2
Feb 22, 2015
Doing the measurements
Last year I promised to give a deeper look in the calculations. So to learn about the start of a measurement go back to Firmware Part 1.
ADC base values
Getting the XMEGA datasheet Analog Digital Converter part, it says that if running in single ended unsigned mode, the
measurement value for GND is 205. This is to be able to measure a 0V voltage without problems.
To get the exact value was a problem. By measurement it was around 160. So I implemented
a self calibration (triggered by the button) to get the exact value at 0V. This variable
is named adc_usig_base
and read from EEPROM.
The next needed value is the resolution for each bit (called Least Significant Bit).
For 12bit resolution this is 0.4899mV/LSB (constant ADC_MV_12RES
).
Measuring the voltage
Is easy and just uses a voltage divider of 15.24 ((4.7K + 330) / 330) (constant MAIN_RES_DIV
)
to get the voltage from up to 26V to the measurement range of the XMEGA up to Vcc/1.6 = 2.06V
in the single ended mode with 12bit resolution.
The calculation looks like the following:
1 uint16_t calc_main_mV(uint16_t res) {
2 if( res <= adc_usig_base ) {
3 return 0; //clipping if the input is less than adc_usig_base
4 }
5 else {
6 double _v = (double)((double)(res - adc_usig_base) * (double)ADC_MV_12RES * (double)MAIN_RES_DIV / 10);
7 return (uint16_t)_v;
8 }
We substract the adc_usig_base
to get the correct range and multiply the result with the 12bit
resolution to get mV. Then we need to multiply with the voltage divider to get the correct voltage.
In this case we divide by 10 to get 10mV as the result unit as needed for the Powerbox voltage
sensor.
Measuring the current
The main task of the sensor is to read and transmit the current of the main battery pack. Depending on the current running through the ACS758 hall sensor it outputs a voltage. This voltage depends on the operating voltage Vcc and the current. The ACS758 can be operated up to 5V and this is the voltage base in the datasheet. So we need to adjust all datasheet values be a factor of 3.3V/5V = 0.66. Many values differ by the device, so all constants are defined in the conf_board.h file (eg. to be able to change to a 200A version).
ACS758 base values
We use the 100U version with Vcc 3.3V. So we get the following values:
- Voltage at Ip = 0A (no current) = 0.6V * 0.66 (for Vcc = 3.3V) = 0.396V (constant
ACS758_BASE
) - Sensitivity for 1A = 40mV at Vcc = 5V
- Sensitivity for 1A at Vcc = 3.3V = 26.4mV (by measuring its only 24mV) (constant
ACS758_RATE
) - Voltage at Ip = 100A = 0.0264V * 100 + 0.396V = 3.036V
That is close to the upper power rail of Vcc = 3.3V and to much for the XMEGA ADC. This can measure up to Vcc/1.6 = 2.06V in the single ended mode with 12bit resolution. So we need to apply a voltage divider to bring the Vout from the ACS758 to under 2.06V.
A voltage divider by 1.64 will do. I used a pair of 3K and 4.7K. This is calculated by
(3K + 4.7K) / 4.7K = 1.638. This looks pretty exact, but don’t forget the resistors have
tolerances by 1-2%. This is the constant CUR_RES_DIV
.
To get the measurement at 0A we can use the self calibration procedure as well. The variable
is called adc_b
and stored in the EEPROM.
Calculate the current
The formular looks like this: RES is the 12 bit result from the measurement.
I = (((RES - ADC_USIG_BASE * ADC_MV_12RES * CUR_RES_DIV) - ACS758_BASE) / ACS758_RATE
We substract the ADC base value for 0V and mutliply this with the resolution and voltage divider. From this voltage we substract the base voltage for 0A from the sensor and multiply with the sensitivity to get the current. Then with some mathematic transformations:
I * ACS758_RATE = ((RES - ADC_USIG_BASE) * ADC_MV_12RES * CUR_RES_DIV) - ACS758_BASE
I * ACS758_RATE / ADC_MV_12RES / CUR_RES_DIV = (RES - ADC_USIG_BASE) - (ACS758_BASE / ADC_MV_12RES / CUR_RES_DIV)
I = ((RES - ADC_USIG_BASE) - (ACS758_BASE / ADC_MV_12RES / CUR_RES_DIV)) * (ADC_MV_12RES * CUR_RES_DIV / ACS758_RATE)
var: ACS_B = (ACS758_BASE / ADC_MV_12RES / CUR_RES_DIV)
var: ACS_R = (ADC_MV_12RES / ACS758_RATE * CUR_RES_DIV)
I = (RES - ADC_USIG_BASE - ACS_B) * ACS_R
var: ADC_B = (ADC_USIG_BASE + ACS_B)
I = ((RES - ADC_B) * ACS_R
ACS_B
and ACS_R
can be precalculated to save time. ADC_B
can be measured through self
calibration and is the same as adc_b
.
ACS758_RATE
will result in A. But for the telemetrie we want the result in 10mA or mA for
the capacity measurement. This is done with _M1
for 10mA and _M
for mA.
Problems with the calculation
The first problem is, that I use unsigned integer. But this is bad, if the there is some
drift and the measurement goes under ADC_B
. This can be solved by clipping all results
below this value and simply return 0A.
The next problem is, that ACS_R
will be a floating point number like 0.0312. If we
switch to 32bit integer and multiply all numbers by a factor x, so the 32bit field will
not be exceeded, this will lead to greater precision of the calculation.
At the end of the calculation we then divide again and get the correct value. If we
multiply by an exponent of 2, we can divide with a simple and fast shift operation.
To calculate the maximum factor in the 32bit limit I used an Excel sheet, you will
find in the Github repository. The calculation the looks like:
I = (((RES - ADC_B) * ACS_R_O) >> ACS_SHIFT
ACS_SHIFT
= 5 divides by 32 for example.
In the calc_measure.c the whole thing looks like this:
1 uint16_t calc_mA(uint16_t res) {
2 if( adc_b >= res ) {
3 return 0; //clipping to zero
4 }
5 uint32_t _c = (( (uint32_t)( ((uint32_t)(res - adc_b)) * ((uint32_t)ACS_R_M1_O) ) ) >> ACS_SHIFT_M1);
6 return ((uint16_t)_c);
7 }
In this case the function returns the current in 10mA like required for the Spektrum telemetry Current sensor.
In the next round I will explain the capacity measurement. Then we need to add the time component to the current measurement.
Share