Skip to content

Commit

Permalink
Merge pull request #4328 from TD-er/bugfix/HLW8012_jitter
Browse files Browse the repository at this point in the history
[HLW8012] Improve stability and resolution Voltage/Current measurement
  • Loading branch information
TD-er authored Nov 3, 2022
2 parents cff4e44 + 1ef3714 commit d67c354
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 81 deletions.
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

0 comments on commit d67c354

Please sign in to comment.