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.