Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[HLW8012] Improve stability and resolution Voltage/Current measurement #4328

Merged
merged 3 commits into from
Nov 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 66 additions & 45 deletions lib/HLW8012_1.1.1/src/HLW8012.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <Arduino.h>
#include "HLW8012.h"

#include <GPIO_Direct_Access.h>

#ifndef CORE_POST_3_0_0
#ifdef ESP8266
#define IRAM_ATTR ICACHE_RAM_ATTR
Expand Down Expand Up @@ -74,7 +76,7 @@ hlw8012_mode_t HLW8012::toggleMode() {
return new_mode;
}

double HLW8012::getCurrent() {
float HLW8012::getCurrent() {

// Power measurements are more sensitive to switch offs,
// so we first check if power is 0 to set _current to 0 too
Expand All @@ -89,40 +91,40 @@ double HLW8012::getCurrent() {
}

const unsigned int current_pulse_width = _current_pulse_width;
_current = (current_pulse_width > 0) ? _current_multiplier / current_pulse_width / 2 : 0;
_current = (current_pulse_width > 0) ? _current_multiplier / static_cast<float>(current_pulse_width) / 2.0f : 0.0f;
return _current;

}

unsigned int HLW8012::getVoltage() {
float HLW8012::getVoltage() {
if (_use_interrupts) {
_checkCF1Signal();
} else if (_mode != _current_mode) {
_voltage_pulse_width = pulseIn(_cf1_pin, HIGH, _pulse_timeout);
}
const unsigned int voltage_pulse_width = _voltage_pulse_width;
_voltage = (voltage_pulse_width > 0) ? _voltage_multiplier / voltage_pulse_width / 2 : 0;
_voltage = (voltage_pulse_width > 0) ? _voltage_multiplier / static_cast<float>(voltage_pulse_width) / 2.0f : 0.0f;
return _voltage;
}

unsigned int HLW8012::getActivePower() {
float HLW8012::getActivePower() {
if (_use_interrupts) {
_checkCFSignal();
} else {
_power_pulse_width = pulseIn(_cf_pin, HIGH, _pulse_timeout);
}
const unsigned int power_pulse_width = _power_pulse_width;
_power = (power_pulse_width > 0) ? _power_multiplier / power_pulse_width / 2 : 0;
_power = (power_pulse_width > 0) ? _power_multiplier / static_cast<float>(power_pulse_width) / 2.0f : 0.0f;
return _power;
}

unsigned int HLW8012::getApparentPower() {
double current = getCurrent();
float HLW8012::getApparentPower() {
float current = getCurrent();
unsigned int voltage = getVoltage();
return voltage * current;
}

unsigned int HLW8012::getReactivePower() {
float HLW8012::getReactivePower() {
unsigned int active = getActivePower();
unsigned int apparent = getApparentPower();
if (apparent > active) {
Expand All @@ -132,15 +134,15 @@ unsigned int HLW8012::getReactivePower() {
}
}

double HLW8012::getPowerFactor() {
unsigned int active = getActivePower();
unsigned int apparent = getApparentPower();
if (active > apparent) return 1;
if (apparent == 0) return 0;
return (double) active / apparent;
float HLW8012::getPowerFactor() {
const float active = getActivePower();
const float apparent = getApparentPower();
if (active > apparent) return 1.0f;
if (apparent == 0) return 0.0f;
return active / apparent;
}

unsigned long HLW8012::getEnergy() {
float HLW8012::getEnergy() {

// Counting pulses only works in IRQ mode
if (!_use_interrupts) return 0;
Expand All @@ -151,34 +153,35 @@ unsigned long HLW8012::getEnergy() {
f = N/t (N=pulse count, t = time)
E = P*t = m*N (E=energy)
*/
return _pulse_count * _power_multiplier / 1000000. / 2;
const float pulse_count = _cf_pulse_count;
return pulse_count * _power_multiplier / 1000000.0f / 2.0f;

}

void HLW8012::resetEnergy() {
_pulse_count = 0;
_cf_pulse_count = 0;
}

void HLW8012::expectedCurrent(double value) {
if (_current == 0) getCurrent();
if (_current > 0) _current_multiplier *= (value / _current);
void HLW8012::expectedCurrent(float value) {
if (static_cast<int>(_current) == 0) getCurrent();
if (static_cast<int>(_current) > 0) _current_multiplier *= (value / _current);
}

void HLW8012::expectedVoltage(unsigned int value) {
if (_voltage == 0) getVoltage();
if (_voltage > 0) _voltage_multiplier *= ((double) value / _voltage);
void HLW8012::expectedVoltage(float value) {
if (static_cast<int>(_voltage) == 0) getVoltage();
if (static_cast<int>(_voltage) > 0) _voltage_multiplier *= (value / _voltage);
}

void HLW8012::expectedActivePower(unsigned int value) {
if (_power == 0) getActivePower();
if (_power > 0) _power_multiplier *= ((double) value / _power);
void HLW8012::expectedActivePower(float value) {
if (static_cast<int>(_power) == 0) getActivePower();
if (static_cast<int>(_power) > 0) _power_multiplier *= (value / _power);
}

void HLW8012::resetMultipliers() {
_calculateDefaultMultipliers();
}

void HLW8012::setResistors(double current, double voltage_upstream, double voltage_downstream) {
void HLW8012::setResistors(float current, float voltage_upstream, float voltage_downstream) {
if (voltage_downstream > 0) {
_current_resistor = current;
_voltage_resistor = (voltage_upstream + voltage_downstream) / voltage_downstream;
Expand All @@ -190,33 +193,44 @@ void IRAM_ATTR HLW8012::cf_interrupt() {
const unsigned long now = micros();
_power_pulse_width = now - _last_cf_interrupt;
_last_cf_interrupt = now;
_pulse_count++;
_cf_pulse_count++;
}

void IRAM_ATTR HLW8012::cf1_interrupt() {

const unsigned long now = micros();

if ((now - _first_cf1_interrupt) > _pulse_timeout) {

unsigned long pulse_width;
const unsigned long time_since_first = now - _first_cf1_interrupt;

// The first few pulses after switching will be unstable
// Collect pulses in this mode for some time
// On very few pulses, use the last one collected in this period.
// On many pulses, compute the average over a longer period to get a more stable reading.
// This may also increase resolution on higher frequencies.
if (time_since_first > _pulse_timeout) {
// Copy value first as it is volatile
const unsigned long last_cf1_interrupt = _last_cf1_interrupt;
const unsigned long pulse_width =
(last_cf1_interrupt == _first_cf1_interrupt)
? 0
: (_cf1_pulse_count < 10)
? (now - last_cf1_interrupt) // long pulses, use the last one as it is probably the most stable one
: (time_since_first / _cf1_pulse_count);

if (_last_cf1_interrupt == _first_cf1_interrupt) {
pulse_width = 0;
} else {
pulse_width = now - _last_cf1_interrupt;
}

if (_mode == _current_mode) {
_current_pulse_width = pulse_width;
} else {
_voltage_pulse_width = pulse_width;
}

_mode = 1 - _mode;
digitalWrite(_sel_pin, _mode);

// Copy value first as it is volatile
const unsigned char mode = 1 - _mode;
DIRECT_pinWrite_ISR(_sel_pin, mode);
_mode = mode;
// Keep track of when the SEL pin was switched.
_first_cf1_interrupt = now;

_cf1_pulse_count = 0;
} else {
++_cf1_pulse_count;
}

_last_cf1_interrupt = now;
Expand All @@ -228,13 +242,20 @@ void HLW8012::_checkCFSignal() {
}

void HLW8012::_checkCF1Signal() {
if ((micros() - _last_cf1_interrupt) > _pulse_timeout) {
const unsigned long now = micros();
if ((now - _last_cf1_interrupt) > _pulse_timeout) {
if (_mode == _current_mode) {
_current_pulse_width = 0;
} else {
_voltage_pulse_width = 0;
}
toggleMode();
// Copy value first as it is volatile
const unsigned char mode = 1 - _mode;
DIRECT_pinWrite(_sel_pin, mode);
_mode = mode;
if (_use_interrupts) {
_last_cf1_interrupt = _first_cf1_interrupt = now;
}
}
}

Expand Down
54 changes: 28 additions & 26 deletions lib/HLW8012_1.1.1/src/HLW8012.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,31 +79,32 @@ class HLW8012 {
unsigned long pulse_timeout = PULSE_TIMEOUT);

void setMode(hlw8012_mode_t mode);

hlw8012_mode_t getMode();
hlw8012_mode_t toggleMode();

double getCurrent();
unsigned int getVoltage();
unsigned int getActivePower();
unsigned int getApparentPower();
double getPowerFactor();
unsigned int getReactivePower();
unsigned long getEnergy(); //in Ws
float getCurrent();
float getVoltage();
float getActivePower();
float getApparentPower();
float getPowerFactor();
float getReactivePower();
float getEnergy(); //in Ws
void resetEnergy();

void setResistors(double current, double voltage_upstream, double voltage_downstream);
void setResistors(float current, float voltage_upstream, float voltage_downstream);

void expectedCurrent(double current);
void expectedVoltage(unsigned int current);
void expectedActivePower(unsigned int power);
void expectedCurrent(float current);
void expectedVoltage(float current);
void expectedActivePower(float power);

double getCurrentMultiplier() { return _current_multiplier; };
double getVoltageMultiplier() { return _voltage_multiplier; };
double getPowerMultiplier() { return _power_multiplier; };
float getCurrentMultiplier() { return _current_multiplier; };
float getVoltageMultiplier() { return _voltage_multiplier; };
float getPowerMultiplier() { return _power_multiplier; };

void setCurrentMultiplier(double current_multiplier) { _current_multiplier = current_multiplier; };
void setVoltageMultiplier(double voltage_multiplier) { _voltage_multiplier = voltage_multiplier; };
void setPowerMultiplier(double power_multiplier) { _power_multiplier = power_multiplier; };
void setCurrentMultiplier(float current_multiplier) { _current_multiplier = current_multiplier; };
void setVoltageMultiplier(float voltage_multiplier) { _voltage_multiplier = voltage_multiplier; };
void setPowerMultiplier(float power_multiplier) { _power_multiplier = power_multiplier; };
void resetMultipliers();

private:
Expand All @@ -112,22 +113,21 @@ class HLW8012 {
unsigned char _cf1_pin = 0;
unsigned char _sel_pin = 0;

double _current_resistor = R_CURRENT;
double _voltage_resistor = R_VOLTAGE;
float _current_resistor = R_CURRENT;
float _voltage_resistor = R_VOLTAGE;

double _current_multiplier = 0.0; // Unit: us/A
double _voltage_multiplier = 0.0; // Unit: us/V
double _power_multiplier = 0.0; // Unit: us/W
float _current_multiplier = 0.0; // Unit: us/A
float _voltage_multiplier = 0.0; // Unit: us/V
float _power_multiplier = 0.0; // Unit: us/W

unsigned long _pulse_timeout = PULSE_TIMEOUT; //Unit: us
volatile unsigned long _voltage_pulse_width = 0; //Unit: us
volatile unsigned long _current_pulse_width = 0; //Unit: us
volatile unsigned long _power_pulse_width = 0; //Unit: us
volatile unsigned long _pulse_count = 0;

double _current = 0;
unsigned int _voltage = 0;
unsigned int _power = 0;
float _current = 0;
float _voltage = 0;
float _power = 0;

unsigned char _current_mode = HIGH;
volatile unsigned char _mode = 0;
Expand All @@ -136,6 +136,8 @@ class HLW8012 {
volatile unsigned long _last_cf_interrupt = 0;
volatile unsigned long _last_cf1_interrupt = 0;
volatile unsigned long _first_cf1_interrupt = 0;
volatile unsigned long _cf_pulse_count = 0;
volatile unsigned long _cf1_pulse_count = 0;

void _checkCFSignal();
void _checkCF1Signal();
Expand Down
20 changes: 10 additions & 10 deletions src/_P076_HLW8012.ino
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ int StoredTaskIndex = -1;
uint8_t p076_read_stage = 0;
unsigned long p076_timer = 0;

double p076_hcurrent = 0.0f;
unsigned int p076_hvoltage = 0;
unsigned int p076_hpower = 0;
unsigned int p076_hpowfact = 0;
float p076_hcurrent = 0.0f;
float p076_hvoltage = 0;
float p076_hpower = 0;
float p076_hpowfact = 0;

#define P076_Custom 0

Expand Down Expand Up @@ -450,12 +450,12 @@ boolean Plugin_076(uint8_t function, struct EventStruct *event, String &string)
}

if (command.equalsIgnoreCase(F("hlwcalibrate"))) {
unsigned int CalibVolt = 0;
double CalibCurr = 0;
unsigned int CalibAcPwr = 0;
if (validUIntFromString(parseString(string, 2), CalibVolt)) {
if (validDoubleFromString(parseString(string, 3), CalibCurr)) {
validUIntFromString(parseString(string, 4), CalibAcPwr);
float CalibVolt = 0;
float CalibCurr = 0;
float CalibAcPwr = 0;
if (validFloatFromString(parseString(string, 2), CalibVolt)) {
if (validFloatFromString(parseString(string, 3), CalibCurr)) {
validFloatFromString(parseString(string, 4), CalibAcPwr);
}
}
#ifndef BUILD_NO_DEBUG
Expand Down