Tim BishopVishnu Kumar
Published © GPL3+

Non-contact AC power meter, i2c

Track AC electrical power use with this non-contact I2C sensor. It measures AC current and power factor. A Seeed Studio Grove system module.

IntermediateWork in progress2 hours2,671
Non-contact AC power meter, i2c

Things used in this project

Hardware components

Seeed Studio - Grove I2C 50/60HZ AC current and power factor module
×1
Seeed Studio Non-invasive AC Current Sensor (100A max)
×1
Audio / Video Cable Assembly, 3.5mm Slim Stereo Plug to 3.5mm Slim Stereo Jack
Audio / Video Cable Assembly, 3.5mm Slim Stereo Plug to 3.5mm Slim Stereo Jack
Cut the cable in half and use the jack end
×1
Espressif ESP32 Development Board - Developer Edition
Espressif ESP32 Development Board - Developer Edition
Use any arduino-compatible ESP32 or other MCU that you wish to use as the I2C host.
×1
Through Hole Resistor, 20 ohm
Through Hole Resistor, 20 ohm
×1
Seeed Studio Grove - 4 pin Female Jumper to Grove 4 pin Conversion Cable (5 PCs per PAck)
×1

Software apps and online services

PlatformIO IDE
PlatformIO IDE

Story

Read more

Schematics

Schematic

Code

Example arduino code

C/C++
#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);

}

I2C_AC_Current.h

C/C++
#pragma once
//
//    FILE: I2C_AC_Current.h
//  AUTHOR: Rob Tillaart, Viktor Balint, Tim Bishop
// VERSION: 0.1.0
//    DATE: 2022-10-28
// PURPOSE: Arduino library for the Grove I2C AC Current Sensor
//     URL:
//    BASE: Based on SHT20 temperature sensor driver https://github.com/RobTillaart/SHT2x


#include <Arduino.h>
#include <Wire.h>

//  error codes
//  kept in sync with SHT31 library
#define SHT2x_OK                      0x00
#define SHT2x_ERR_WRITECMD            0x81
#define SHT2x_ERR_READBYTES           0x82
#define SHT2x_ERR_NOT_CONNECT         0x84
#define SHT2x_ERR_CRC_TEMP            0x85
#define SHT2x_ERR_CRC_HUM             0x86
#define SHT2x_ERR_CRC_STATUS          0x87 
#define SHT2x_ERR_RESOLUTION          0x8A

class AC_Current
{
public:
  AC_Current();

#if defined(ESP8266) || defined(ESP32)
  bool begin(const int dataPin, const int clockPin);
#endif
  bool begin(TwoWire *wire);

  //  check sensor is reachable over I2C
  bool isConnected();

  //  read must be called get getTemperature / getHumidity
  bool read();

  float    getCurrent();
  float    getPF();

    //  might take up to 15 milliseconds.
  bool reset();

  private:
  uint8_t   crc8(const uint8_t *data, uint8_t len);

  bool      writeCmd(uint8_t cmd);
  bool      writeCmd(uint8_t cmd, uint8_t value);
  bool      readBytes(uint8_t n, uint8_t *val, uint8_t maxDuration);
  TwoWire* _wire;

  uint16_t  _rawHumidity;
  uint16_t  _rawTemperature;

  uint8_t   _status;

  uint8_t   _error;
  uint8_t   _resolution;
};

I2C_AC_Current.cpp

C/C++
//
//    FILE: AC_Current.h
//  AUTHOR: Rob Tillaart, Viktor Balint, Tim Bishop
// VERSION: 0.1.0
//    DATE: 2022-10-28
// PURPOSE: Arduino library for the Grove I2C AC Current Sensor
//     URL:
//

#include "I2C_AC_Current.h"

//  SUPPORTED COMMANDS
#define SHT2x_GET_TEMPERATURE_NO_HOLD   0xF3
#define SHT2x_GET_HUMIDITY_NO_HOLD      0xF5
#define SHT2x_SOFT_RESET                0xFE
#define SHT2x_WRITE_USER_REGISTER       0xE6
#define SHT2x_READ_USER_REGISTER        0xE7

#define SHT2x_ADDRESS                   0x40

AC_Current::AC_Current()
{
  _rawTemperature = 0;
  _rawHumidity    = 0;
  _error          = SHT2x_OK;
  _status         = 0;
  _resolution     = 0;
}

#if defined(ESP8266) || defined(ESP32)
bool AC_Current::begin(const int dataPin, const int clockPin)
{
  _wire = &Wire;
  if ((dataPin < 255) && (clockPin < 255))
  {
    _wire->begin(dataPin, clockPin);
  } else {
    _wire->begin();
  }
  return reset();
}
#endif


bool AC_Current::begin(TwoWire *wire)
{
  _wire = wire;
  return reset();
}


bool AC_Current::isConnected()
{
  _wire->beginTransmission(SHT2x_ADDRESS);
  int rv = _wire->endTransmission();
  if (rv != 0) _error = SHT2x_ERR_NOT_CONNECT;
  return (rv == 0);
}


bool AC_Current::read()
{
  uint8_t buffer[3];

  //  TEMPERATURE
  writeCmd(SHT2x_GET_TEMPERATURE_NO_HOLD);
  delay(85); 

  if (readBytes(3, (uint8_t*) &buffer[0], 90) == false)
  {
    _error = SHT2x_ERR_READBYTES;
    return false;
  }
  if (crc8(buffer, 2) != buffer[2])
  {
    _error = SHT2x_ERR_CRC_TEMP;
  //  return false;  // do not fail yet
  }
  _rawTemperature  = buffer[0] << 8;
  _rawTemperature += buffer[1];
  _rawTemperature &= 0xFFFC;

  _status = buffer[1] & 0x0003;
  if (_status == 0xFF)  // TODO  != 0x01  (need HW to test)
  {
    _error = SHT2x_ERR_READBYTES;
    return false;
  }
  
  //  HUMIDITY
  writeCmd(SHT2x_GET_HUMIDITY_NO_HOLD);
  delay(29); 

  if (readBytes(3, (uint8_t*) &buffer[0], 30) == false)
  {
    return false;
  }
  if (crc8(buffer, 2) != buffer[2])
  {
    _error = SHT2x_ERR_CRC_HUM;
  //    return false;  // do not fail yet
  }
  _rawHumidity  = buffer[0] << 8;
  _rawHumidity += buffer[1];
  _rawHumidity &= 0xFFFC;     //  TODO is this mask OK? as humidity is max 12 bit..

  _status = buffer[1] & 0x0003;
  if (_status == 0xFF)        //  TODO  != 0x02  (need HW to test)
  {
    _error = SHT2x_ERR_READBYTES;
    return false;
  }

  _error = SHT2x_OK;
  return true;
}


float AC_Current::getCurrent()
{
  // par 6.2
  return -46.85 + (175.72 / 65536.0) * _rawTemperature;
}


float AC_Current::getPF()
{
  // par  6.1
  return -6.0 + (125.0 / 65536.0) * _rawHumidity;
}


bool AC_Current::reset()
{
  bool b = writeCmd(SHT2x_SOFT_RESET);
  return b;
}


//////////////////////////////////////////////////////////
//
//  PRIVATE
//
uint8_t AC_Current::crc8(const uint8_t *data, uint8_t len)
{
  // CRC-8 formula from page 14 of SHT spec pdf
  // Sensirion_Humidity_Sensors_SHT2x_CRC_Calculation.pdf
  const uint8_t POLY = 0x31;
  uint8_t crc = 0x00;

  for (uint8_t j = len; j; --j)
  {
    crc ^= *data++;

    for (uint8_t i = 8; i; --i)
    {
      crc = (crc & 0x80) ? (crc << 1) ^ POLY : (crc << 1);
    }
  }
  return crc;
}


bool AC_Current::writeCmd(uint8_t cmd)
{
  _wire->beginTransmission(SHT2x_ADDRESS);
  _wire->write(cmd);
  if (_wire->endTransmission() != 0)
  {
    _error = SHT2x_ERR_WRITECMD;
    return false;
  }
  return true;
}


bool AC_Current::writeCmd(uint8_t cmd, uint8_t value)
{
  _wire->beginTransmission(SHT2x_ADDRESS);
  _wire->write(cmd);
  _wire->write(value);
  if (_wire->endTransmission() != 0)
  {
    _error = SHT2x_ERR_WRITECMD;
    return false;
  }
  return true;
}


bool AC_Current::readBytes(uint8_t n, uint8_t *val, uint8_t maxDuration)
{
  _wire->requestFrom((uint8_t)SHT2x_ADDRESS, (uint8_t) n);
  uint32_t start = millis();
  while (_wire->available() < n)
  {
    if (millis() - start > maxDuration)
    {
      _error = SHT2x_ERR_READBYTES;
      return false;
    }
    yield();
  }

  for (uint8_t i = 0; i < n; i++)
  {
    val[i] = _wire->read();
  }
  _error = SHT2x_OK;
  return true;
}

Example arduino code

Module Firmware

Credits

Tim Bishop

Tim Bishop

3 projects • 1 follower
Electrical engineer, my first project was an apple cider press, since then I'm interested in electronics, sustainability, music, and hiking.
Vishnu Kumar

Vishnu Kumar

2 projects • 0 followers

Comments