Measure AC current and power factor with this non-contact sensor. It is a Seeed Grove I2C 50/60HZ AC current and power factor module.
For energy management projects, I’d love an easy way to measure AC current and power factor, ideally something non-contact that can be easily attached to equipment and removed without connecting directly to any high-voltage circuits.
I'd also like an I2C module that encapsulates all of the sensing complexity and lets my main MCU handle communications.
This sensor module was part of my entry to the Seeed Grove sensor co-invent campaign, it's a great opportunity for anyone to make your dreamed sensor a reality.
Similar Projectshttps://www.vizi.net/ - non-contact current and power factor sensor and wireless communications, well documented
https://iotawatt.com/ - open source AC power meter, measures voltage using a voltage transformer
Real and Apparent PowerFor AC power, the apparent power in watts is simply voltage times current. This is the power that is used to calculate how much a wire will heat when conducting current. The unit of apparent power is VA, or volt-amps.
The real power used by the load is usually less than the apparent power, as some power is reflected by capacitive or inductive circuit elements. When measuring AC inductive or capacitive loads like motors or computer power supplies, real power in watts is expressed by the formula P = V * I * cos theta. The unit of power is typically watts or kilowatts.
Power FactorCos θ is also known as power factor, in which θ represents the phase shift or delay of the current flow. The current lags or leads the applied voltage for inductive or capacitive loads. For purely resistive loads, θ is 0, and thus cos θ is 1. Power factor typically ranges 0.8 to 1.0 for inductive loads. That is, the real power [kW] is less than the applied voltage times the current [VA].
For this sensor, AC current can be sensed using a non-contact current transformer (CT). Thanks to an instructables article on a DIY Non-Contact AC Voltage Detector, I learned that the phase of an AC voltage signal can be measured in a non-contact way using a high impedance digital logic buffer. In audio terms, this means measuring the hum in the circuit. The difference in phase between the voltage and current can be measured to calculate power factor.
I2C sensorsI love using I2C sensors in my projects. They make measurements easy, they handle all of the physics, the signal conditioning, the analog to digital conversion, calibration, and present an interface that allows easy access to the digitized data. Any code that accesses the sensor remains free of all of that complexity. Additionally, you can put a number of I2C sensors on the same 2 wire bus.
I'm finding that the Microchip ATtiny tinyAVR 2 series is perfect for implementing an I2C sensor. It is a tiny 8-bit MCU that has an advanced differential ADC, programmable asynchronous logic, and can be run at very low power.
Simplified Schematic
With a differential ADC, measuring current is relatively easy, you just measure the voltage drop across a resistor. For AC current, you need to sample the current a number of times during a 50 or 60HZ cycle, and calculate the root mean squared average of the samples. The phase of the AC voltage signal can be measured with an antenna wire attached to a high impedance digital logic buffer that is part of the ATtiny configurable custom logic (CCL) peripheral.
ModuleConnect the 100A 50mA CT by clamping it around a current carrying wire. The CT has a 3.5mm plug. Connect it to the jack. Connect the jack wires to the B- and B+ terminals in parallel with a 20ohm burden resistor. If a 0-1Vac CT is used (which has a built-in burden resistor), do not connect the 20ohm burden resistor.
Tape the power line antenna wire to that same current carrying wire or cable. Connect it to pin RX1. Do not connect the wire’s conductor to anything, it merely is taped to the insulated current carrying wire or cable. This antenna wire senses the voltage (emf).
Plug in the Grove Connector and wire to your MCU, for instance an ESP32.
Sample Code#include <Arduino.h>
#include <Wire.h>
#include "I2C_AC_Current.h"
AC_Current hct20;
void setup()
{
Serial.begin(115200);
hct20.begin(21,22); // SDA, SCL. 21,22 for ESP32
}
void loop()
{
hct20.read();
Serial.print("Current: ");
Serial.print(hct20.getCurrent());
Serial.print(" PF: ");
Serial.println(hct20.getPF());
delay(1000);
}
Console
Current: 1.30 PF: 0.96
You can calculate power if you assume a voltage, which is usually constant, for example:
const float voltage = 240;
float real_power_watts = voltage * hct20.getCurrent() * hct20.getPF();
Sensor FirmwareThankfully Spence Konde has written an Arduino core for the ATTiny 2-series, this make writing the sensor code much easier. The core supplies features like the setup(), loop() functions, Serial object, I2C slave ISR functions, and libraries to access the many chip peripherals.
The sensor firmware code can be found on github.
The setup() function starts the ADC, I2C client, and installs the request and response callbacks.
void setup()
{
...
setupLogic();
ADC_init();
Wire.onReceive(receiveHandler);
Wire.onRequest(requestHandler);
Wire.begin(SHT2x_ADDRESS);
...
}
The Logic library sets up the chip's custom configurable logic to listen for a voltage signal on IN2, pin PA2.
void setupLogic()
{
...
Logic0.enable = true; // Enable logic block 0
Logic0.input0 = in::masked; // PA0 masked
Logic0.input1 = in::masked; // PA1 TX1 masked
Logic0.input2 = in::pin; // PA2 RX1 voltage sense
Logic0.output = out::disable; // Disable logic block 0 output pin PA4
Logic0.filter = filter::disable; // No output filter enabled
Logic0.truth = 0x01; // Set truth: HIGH only if input low
Logic0.edgedetect = edgedetect::enable; // Enable edge detection
Logic0.attachInterrupt(&voltageSenseISR,CHANGE);
Logic0.init();
// Start the AVR logic hardware
Logic::start();
}
It calls an interrupt service routine on each level change, which happens 50-60 times per second. This interrupt starts a loop timer when the ADC is sampling the current, and the number of iterations is stored when the current next crosses zero. This number, when calibrated, is proportional to the phase shift between the voltage and current signals.
The main loop() function samples the current signal oncer per second for 20ms, and calls add_sample() on each channel.
hardware_fast_sample_chABC(MAX_SAMPLES,chA,chB,chC); // samples for 20ms
The add_sample() function saves the voltage phase count when the next current zero cross is seen. It adds together the square of the current sample for each iteration in two bins, one bin for a 60HZ signal, and another bin for the remainder for a 50HZ signal. The result for the correct bin is chosen by examining which bin has a balanced signal.
void add_sample(int16_t diff) {
_crossguard--;
_phaseCounter++; // set to zero in ISR
if(((diff ^ _lastSample) & _crossguard) >> 15) {
// If crossed unambiguously (one but not both samples negative and crossGuard negative)
_crossguard = 10;
if (_phaseTriggered == 1) { // on first call after phase mark.
_phaseTriggered++;
_phaseCount = _phaseCounter;
}
}
uint32_t sq = ((uint32_t)diff*(uint32_t)diff);
if (_tickNum < SAMPLES_PER_60HZ_CYCLE) {
_sum2_60 += sq;
_n_60++;
if (diff >= 0) { _p++; }
if (diff < 0) { _n++; }
} else {
_sum2_e += sq;
_n_e++;
if (diff >= 0) { _pe++; }
if (diff < 0) { _ne++; }
}
_tickNum++;
_lastSample = diff;
return;
}
In the main loop, once per second, the RMS current (amps) is scaled and stored in a global variable to be read by the I2C slave ISR.
The power factor is also scaled and stored in a global variable.
When an I2C request is detected, the receiveHandler is called, and the request byte (command) is saved.
void receiveHandler(int numbytes)
{
if (numbytes > 0) {
// Called on a Write address, data
g_i2c_command = Wire.read();
}
}
Later, the requestHandler is called and the response is buffered for writing out onto the wire.
void requestHandler()
{
if ((g_i2c_command == SHT2x_GET_TEMPERATURE_NO_HOLD) ) {
Wire.write((uint8_t)(g_temperature >> 8));
Wire.write((uint8_t) g_temperature);
uint8_t buf[] = { (uint8_t)(g_temperature>>8), (uint8_t) g_temperature };
Wire.write(sht20_crc8(buf, 2));
} else if ((g_i2c_command == SHT2x_GET_HUMIDITY_NO_HOLD) ) {
Wire.write((uint8_t)(g_humidity >> 8));
Wire.write((uint8_t) g_humidity);
uint8_t buf[] = { (uint8_t)(g_humidity>>8), (uint8_t) g_humidity };
Wire.write(sht20_crc8(buf, 2));
}
}
PlansFuture plans are to support 3 channels, add channel detail to the I2C interface, and lower the sensor power use.
ConclusionI'm looking forward to using this module to help save energy, by monitoring various electrical loads.
Thanks for your interest and I'd be happy to hear any comments or if you find this useful.
Comments