Power Factor Measurement Circuit with Arduino (ACS712 & Zero Crossing Detector)

Help Needed: Power Factor Measurement Circuit with Arduino (ACS712 & Zero Crossing Detector)

Hello everyone,

I am working on a power factor measurement project using an Arduino, and I need help debugging my circuit and code. The setup includes: 220v voltage divider vout=3v , R1=40K,R2=390 take I=1.75mA
AC Source Voltage=220V
V
max
=√2×220=311V
Voltage across the bridge rectifier= 311-1.4= 310V
Choose I= 7.5mA

:white_check_mark: Voltage sensing via a voltage divider.
:white_check_mark: Current sensing using an ACS712 sensor (20A, 100mV/A).
:white_check_mark: Zero Crossing Detector using an LM358 comparator.
:white_check_mark: Display on a 20x4 LCD.
i shared the circuit design
can you create thec code of this circuit design please and share

I think you misunderstood; the forum is not a free software factory.

1 Like

Please tell me you have not connected that circuit to mains.

What is your level of electrical and electronic knowledge? The whole thing is live and if any part of it is connected to earth, for example by plugging in a USB cable from your PC, it will go BANG! , as will your PC.

How exactly are you intending to measure the power factor? Do you have anyway of varying the power factor for testing?

iam a 3 year student of electrical and elctronics engineeering
do not worry iam just using proteus to simulate
for pf calculation iam using this but i do not know much
Zero Crossing Detector
Zero crossing detector is used to detect sine wave zero
crossing from positive half cycle to negative cycle or negative
half cycle to positive half cycle. To measure time difference
between two waves is to detect zero crossing of two waves.
The 220V, AC is step down using step down voltage and a
current sensing unit is used to extract the waveforms of current.
The output of the voltage sensing circuit is proportional to the
voltage across the load and current sensing circuit is
proportional to the current through the load. These waveforms
are fed to voltage comparators constructed using operational
amplifier. It is zero crossing detector, and its output changes
during zero crossing of the voltage and current waveforms.
These outputs are fed to the controller unit which does the
further power factor calculations. Zero crossing detector using
LM358 as a comparator is shown in figure.

this is the code i tried

#include <LiquidCrystal.h>

// LCD pin configuration
LiquidCrystal lcd(4, 5, 6, 7, 8, 9);

// Pin assignments
#define VOLTAGE_SENSOR A0
#define CURRENT_SENSOR A1
#define ZCD_VOLTAGE 2
#define ZCD_CURRENT 3

// Constants
const float voltageConversionFactor = 310.0 / 1023.0;
const float currentConversionFactor = 0.1; // ACS712 sensitivity for 20A module
const float pi = 3.14159265358979323846;
const float frequency = 50.0;
const float timePerDegree = (10.0 / 180.0) * 1e6; // in microseconds

volatile unsigned long zeroCrossTimeVoltage = 0;
volatile unsigned long zeroCrossTimeCurrent = 0;
volatile bool voltageCrossed = false;
volatile bool currentCrossed = false;

void zeroCrossVoltage() {
    static unsigned long lastInterruptTime = 0;
    unsigned long interruptTime = micros();
    if (interruptTime - lastInterruptTime > 2000) {  // debounce time
        zeroCrossTimeVoltage = interruptTime;
        voltageCrossed = true;
    }
    lastInterruptTime = interruptTime;
}

void zeroCrossCurrent() {
    static unsigned long lastInterruptTime = 0;
    unsigned long interruptTime = micros();
    if (interruptTime - lastInterruptTime > 2000) {  // debounce time
        zeroCrossTimeCurrent = interruptTime;
        currentCrossed = true;
    }
    lastInterruptTime = interruptTime;
}

void setup() {
    lcd.begin(20, 4);
    lcd.print("Power Factor Meter");
    delay(2000);
    lcd.clear();

    pinMode(ZCD_VOLTAGE, INPUT);
    pinMode(ZCD_CURRENT, INPUT);
    attachInterrupt(digitalPinToInterrupt(ZCD_VOLTAGE), zeroCrossVoltage, RISING);
    attachInterrupt(digitalPinToInterrupt(ZCD_CURRENT), zeroCrossCurrent, RISING);
}

void loop() {
    float Vrms = 0.0, Irms = 0.0, phaseAngle = 0.0, powerFactor = 0;
    
    // Smooth voltage reading by averaging 10 samples
    int voltageReading = 0;
    for (int i = 0; i < 10; i++) {
        voltageReading += analogRead(VOLTAGE_SENSOR);
    }
    voltageReading /= 10;
    Vrms = (voltageReading * voltageConversionFactor) / sqrt(2);
    
    // Smooth current reading by averaging 10 samples
    int currentReading = 0;
    for (int i = 0; i < 10; i++) {
        currentReading += analogRead(CURRENT_SENSOR);
    }
    currentReading /= 10;
    float currentVoltage = (currentReading * 5.0) / 1023.0;
    Irms = (currentVoltage - 2.5) / currentConversionFactor; 
    
    if (voltageCrossed && currentCrossed) {
        unsigned long timeDifference = abs(zeroCrossTimeVoltage - zeroCrossTimeCurrent);
        phaseAngle = (timeDifference / timePerDegree) * (2 * pi / 360.0);
        powerFactor = cos(phaseAngle);
        voltageCrossed = false;
        currentCrossed = false;
    }

    // Update the LCD without clearing it each time
    lcd.setCursor(0, 0);
    lcd.print("Vrms: ");
    lcd.print(Vrms, 2);
    lcd.print("V");

    lcd.setCursor(0, 1);
    lcd.print("Irms: ");
    lcd.print(Irms, 2);
    lcd.print("A");

    lcd.setCursor(0, 2);
    lcd.print("Phase Angle: ");
    lcd.print(phaseAngle, 2);
    lcd.print(" rad");

    lcd.setCursor(0, 3);
    lcd.print("PF: ");
    lcd.print(powerFactor, 2);
    
    delay(1000); // Delay between updates
}


The circuit is "dangerous", though under fully autonomous operation, nothing should happen beyond potential electronic failure if faults occur. However, never connect it to anything else simultaneously, and of course, never connect it to a computer via USB.

To detect zero crossings, you need to monitor the signals arriving at each op-amp (operational amplifier).

For U1:A: The voltage input comes from a full-wave rectifier, so it will never have negative values. Adjust RV1 to a low enough value so that the op-amp output remains saturated most of the time and stays at zero for the shortest adjustable duration.
For U1:B: The voltage input comes from the ACS712 sensor. When it detects 0 amps, the output is Vcc/2, so adjust the negative input of U1:B via RV2 to this voltage.

Additional note: As a third-year engineering student, you should know that in real-world applications, the phase shift in zero crossings between voltage and current is not always indicative of real power factor; it’s only valid when the waveforms are fairly sinusoidal.

this is my circuit design and the code when i run the code i get pf=1.00 theta 0.00 always even i connect inductor 1H and lamp
i was expacting to get 0.63 when the load inductive is 1H
how can i solve this issue

#include <LiquidCrystal.h>

// LCD connected to: RS=4, E=5, D4=6, D5=7, D6=8, D7=9
LiquidCrystal lcd(4, 5, 6, 7, 8, 9);

// Pins
const int voltagePin = A0;
const int currentPin = A1;
const int zcdVoltagePin = 2; // INT0
const int zcdCurrentPin = 3; // INT1

// Constants
const float Vref = 5.0;
const float ACS712_sensitivity = 0.1; // 100mV/A
const float Voffset = 2.5;
const float voltageDividerRatio = (390.0 / (390.0 + 40000.0));
const float Vin = 310.0;
const float calibrationFactor = Vin * voltageDividerRatio;

// Variables
volatile unsigned long tVoltage = 0;
volatile unsigned long tCurrent = 0;
volatile unsigned long timeDiff = 0;
volatile bool voltageZCD = false;
volatile bool currentZCD = false;

void setup() {
  Serial.begin(9600);
  lcd.begin(20, 4);

  pinMode(zcdVoltagePin, INPUT);
  pinMode(zcdCurrentPin, INPUT);

  attachInterrupt(digitalPinToInterrupt(zcdVoltagePin), voltageZC, RISING);
  attachInterrupt(digitalPinToInterrupt(zcdCurrentPin), currentZC, RISING);

  lcd.setCursor(0, 0);
  lcd.print("AC Power Monitor");
  delay(1000);
}

void loop() {
  float Vrms = getVrms();
  float Irms = getIrms();
  float angleDeg = (timeDiff / 55.556);
  float angleRad = angleDeg * (PI / 180.0);
  float pf = cos(angleRad);
  pf = constrain(pf, 0.0, 1.0);

  float realPower = Vrms * Irms * pf;
  float apparentPower = Vrms * Irms;

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Vrms: "); lcd.print(Vrms, 1); lcd.print(" V");
  lcd.setCursor(0, 1);
  lcd.print("Irms: "); lcd.print(Irms, 2); lcd.print(" A");
  lcd.setCursor(0, 2);
  lcd.print("PF: "); lcd.print(pf, 2); lcd.print(" T:"); lcd.print(angleDeg, 1);
  lcd.setCursor(0, 3);
  lcd.print("P:"); lcd.print(realPower, 1); lcd.print(" W"); lcd.print(" S:"); lcd.print(apparentPower, 1); lcd.print(" VA");

  delay(500);
}

float getVrms() {
  float sumSq = 0;
  for (int i = 0; i < 100; i++) {
    int val = analogRead(voltagePin);
    float voltage = (val * Vref / 1023.0) / voltageDividerRatio;
    sumSq += voltage * voltage;
    delay(1);
  }
  return sqrt(sumSq / 100.0);
}

float getIrms() {
  float sumSq = 0;
  for (int i = 0; i < 100; i++) {
    int val = analogRead(currentPin);
    float currentVoltage = val * Vref / 1023.0;
    float current = (currentVoltage - Voffset) / ACS712_sensitivity;
    sumSq += current * current;
    delay(1);
  }
  return sqrt(sumSq / 100.0);
}

void voltageZC() {
  tVoltage = micros();
  voltageZCD = true;
  if (voltageZCD && currentZCD) {
    timeDiff = abs((long)(tCurrent - tVoltage));
    voltageZCD = currentZCD = false;
  }
}

void currentZC() {
  tCurrent = micros();
  currentZCD = true;
  if (voltageZCD && currentZCD) {
    timeDiff = abs((long)(tCurrent - tVoltage));
    voltageZCD = currentZCD = false;
  }
}

have a look at pzem-004t-v3 sensor and nodemcu-pzem004t-v3-and-8ch-relay-board