ACS37800 help to calibrate

Hello everyone, first time in the forum.
I created a custom PCB with the ACS37800 IC to measure current consumption and voltage level on a 220VAC line. Using ESP32 as the main microcontroller of the board.

I managed to work out SPI communication using SparkFun Library as base, simply changing from i2c to spi. Thank you very much for that btw!

The IC circuit is as the example in the datasheet, with L line isolated via 2 x 1MOhm resistors, N line connected directly to GND in the IC, and the RSENSE with a value of 2.2KOhm. The device is powered via a Switching Mode Power Supply, which is why the N can’t be isolated from GND.
The IC is 3.3VDC powered, as is the ESP32.
Here is the schematic of the ACS37800 in my custom board, please note that R8 and R9 are actually shorted with 0Ohm resistors:


And here is the PCB layout of the board:

My problem is that the values read are not exactly correct. For example, it measures 194 VAC RMS, but the real value is 224VAC RMS; in current is similar, without load the value read is 0.088A and with a load of approximately 120W , the measured value is 0.680 A (it should be around 0.110A).
Here is an extract from the ESP32 log:

Voltage: 0.01, Current: 0.088, Active: 0.00, Img: 0.00, Apparent: 0.00
Voltage: 191.90, Current: 0.680, Active: 129.41, Img: 14.46, Apparent: 130.56
Voltage: 191.82, Current: 0.679, Active: 129.14, Img: 14.33, Apparent: 130.29

Note here the Apparent Power is very close to the real value but the Voltage and Current are weird.

In the library I see a lot of magic numbers that I can’t work out where they come from, even thought they say what they are. For example, here I don’t understant where the 55000 value comes from:

volts /= 55000.0; //Convert from codes to the fraction of ADC Full Scale (16-bit)
volts *= 250; //Convert to mV (Differential Input Range is +/- 250mV)
volts /= 1000; //Convert to Volts
//Correct for the voltage divider: (RISO1 + RISO2 + RSENSE) / RSENSE
//Or:  (RISO1 + RISO2 + RISO3 + RISO4 + RSENSE) / RSENSE
float resistorMultiplier = (_dividerResistance + _senseResistance) / _senseResistance;
volts *= resistorMultiplier;

Also, the datasheet is very confusing in this matter as the calculation for the real value is very different and I can’t make heads or tails of this explanation:

I tried changing averaging samples, offsets, gains, and others but the values just went crazier than before.

I hope someone can help me understand what I’m doing wrong.

Our calculations are based on our design specifically, but you can swap it out…Here’s some code that claude.ai suggested:

float calculateVoltageRMS(uint16_t rawValue) {
    // Step 1: Convert 16-bit fixed point to float (0-1 range)
    float normalizedValue = rawValue / 65536.0f;  // 2^16 = 65536
    
    // Step 2: Apply the input range scaling
    // ΔVIN(MAX) = 0.84V and additional 1.19 scaling factor from datasheet
    float voltageAtInput = normalizedValue * 0.84f * 1.19f;
    
    // Step 3: Calculate actual voltage divider ratio
    // Only R6 + R7 (2MΩ total) and R10 (2.2kΩ) are in the path
    float r_iso = 2000000.0f;  // R6 + R7 = 2MΩ
    float r_sense = 2200.0f;   // R10 = 2.2kΩ
    float dividerRatio = (r_iso + r_sense) / r_sense;  // ≈ 910.91
    
    // Final line voltage in VAC RMS
    float lineVoltage = voltageAtInput * dividerRatio;
    
    return lineVoltage;
}

// Usage with calibration factor
float calibrateVoltage(float measuredVoltage) {
    // Calibration factor based on your measurements (224V actual / 194V measured)
    const float calibrationFactor = 224.0f / 194.0f;  // ≈ 1.155
    return measuredVoltage * calibrationFactor;
}

The fact that you’re measuring 194V instead of 224V (a ratio of about 1.155) could be due to several factors:

  1. Resistor tolerances:
  • The 1MΩ resistors (R6, R7) typically have 5% or even 10% tolerance
  • At 5% tolerance, each 1MΩ resistor could be between 950kΩ and 1.05MΩ
  • This could significantly affect your divider ratio
  1. Additional factors:
  • PCB trace resistance
  • Contact resistance at the 0Ω resistor connections
  • Temperature effects on the resistors
  • Internal scaling factors in the ACS37800

To improve accuracy, you could:

  1. Measure the actual resistance of R6 + R7 with a multimeter
  2. Add a trimmer potentiometer in series with R10 for fine calibration
  3. Use calibration in software instead:
float readVoltage() {
    uint16_t rawValue = readACS37800Register(VRMS_REGISTER);
    float voltage = calculateVoltageRMS(rawValue);
    return calibrateVoltage(voltage);
}

Hi Iván ( @Ivan_Gerber ),

The ACS37800 datasheet is really not great. I struggled with it while I was writing the Library and code examples.

The 55000 value comes from the 16-bit ADC (0 - 65535) scaled by the valid range of 0.84. The true value should be 55049. 1.19 is simply 1 / 0.84. I don’t think claude.ai got it right there…!

I did all of my AC testing at low voltages, comparing the values with those from a True RMS meter. It is possible I made an error, but I don’t think I did. Please feel free to open an issue on the Library GitHub repo if you want to discuss anything about the library. (We can’t and won’t give you any advice on using ACS37800 hardware at power line voltages. That’s on you. Please be safe.)

Best wishes,
Paul

1 Like

Thank you both for the help, I’ll be sure to check the resistors actual values and implement them on the calculations. Also, I appreciate the explanation :smiley: