Skip to content

Commit

Permalink
Merge pull request letscontrolit#5224 from TD-er/bugfix/HLW8012_stabi…
Browse files Browse the repository at this point in the history
…lity_high_load

[HWL8012] Fix stability issues on ESP8266 with high current load
  • Loading branch information
TD-er authored Jan 16, 2025
2 parents ae15760 + 083aa24 commit 91054fe
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 69 deletions.
77 changes: 41 additions & 36 deletions lib/HLW8012_1.1.1/src/HLW8012.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,58 +97,62 @@ float HLW8012::getCurrent(bool &valid) {
float HLW8012::getCF1Current(bool &valid) {
_checkCF1Signal();

float pulsefreq{};
const auto res = _current_sample.getPulseFreq(pulsefreq);
valid = true;
if (!_current_sample.updated(valid)) {
return _cf1_current;
}

if (_current_sample.getPulseFreq(pulsefreq) == HLW8012_sample::result_e::Enough) {
float pulsefreq{};
if (_current_sample.getPulseFreq(pulsefreq)) {
_cf1_current = pulsefreq * _current_multiplier / 2.0f;
} else if (res == HLW8012_sample::result_e::Cleared ||
res == HLW8012_sample::result_e::Expired) {

// Add limit for CF1 current
// as it is highly unlikely any cos-phi will ever be worse than 0.25
bool tmpvalid{};
const float current = getCurrent(tmpvalid);
const float upperLimit = 4.0f * current;
if (upperLimit < _cf1_current) {
_cf1_current = upperLimit;
} else if (current > _cf1_current) {
// Cos-phi will never be > 1.0, so set it to the same
// as current computed from the CF pin and voltage
_cf1_current = current;
}
} else {
_cf1_current = 0.0f;
valid = res == HLW8012_sample::result_e::Expired;
}
// Add limit for CF1 current
// as it is highly unlikely any cos-phi will ever be worse than 0.25
bool tmpvalid{};
const float upperLimit = 4.0f * getCurrent(tmpvalid);
valid |= tmpvalid;
if (upperLimit < _cf1_current) {
_cf1_current = upperLimit;
// Consider always valid, as the current will be 0
// when we received no pulse for a while
_current_sample.setValid(true);
}

return _cf1_current;
}


float HLW8012::getVoltage(bool &valid) {
_checkCF1Signal();
float pulsefreq{};
const auto res = _voltage_sample.getPulseFreq(pulsefreq);
valid = true;
if (res == HLW8012_sample::result_e::Enough) {
_voltage = pulsefreq * _voltage_multiplier / 2.0f;
} else if (res == HLW8012_sample::result_e::Cleared ||
res == HLW8012_sample::result_e::Expired) {
_voltage = 0.0f;
// Do not claim voltage is valid as it is highly unlikely the voltage will ever be 0.
// The device will then not be powered.
valid = false;
if (!_voltage_sample.updated(valid)) {
return _voltage;
}
float pulsefreq{};
// Do not automatically claim voltage is valid as it is highly unlikely the voltage will ever be 0.
// The device will then not be powered.
valid = _voltage_sample.getPulseFreq(pulsefreq);
_voltage = valid
? pulsefreq * _voltage_multiplier / 2.0f
: 0.0;
return _voltage;
}

float HLW8012::getActivePower(bool &valid) {
_checkCFSignal();
float pulsefreq{};
const auto res = _power_sample.getPulseFreq(pulsefreq);
valid = true;
if (res == HLW8012_sample::result_e::Enough) {
_power = pulsefreq * _power_multiplier / 2.0f;
} else if (res == HLW8012_sample::result_e::Cleared ||
res == HLW8012_sample::result_e::Expired) {
_power = 0.0f;
valid = res == HLW8012_sample::result_e::Expired;
if (!_power_sample.updated(valid)) {
return _power;
}
float pulsefreq{};
valid = _power_sample.getPulseFreq(pulsefreq);
_power = valid
? pulsefreq * _power_multiplier / 2.0f
: 0.0f;
return _power;
}

Expand Down Expand Up @@ -251,7 +255,8 @@ void IRAM_ATTR HLW8012::cf_interrupt() {
++_cf_pulse_count_total;

const auto res = _power_sample.add();
if (res != HLW8012_sample::result_e::NotEnough) {
if (res == HLW8012_sample::result_e::Enough ||
res == HLW8012_sample::result_e::Expired) {
_power_sample.reset();
}
}
Expand Down
65 changes: 35 additions & 30 deletions lib/HLW8012_1.1.1/src/HLW8012_sample.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,16 @@
#endif
#endif

bool HLW8012_finished_sample::getPulseFreq(float &pulseFreq) const
bool HLW8012_finished_sample::getPulseFreq(float &pulseFreq)
{
// Copy volatile values first before checking
const int32_t dur = duration_usec;
const uint32_t cnt = count;
if (dur == 0 || cnt == 0)
{
pulseFreq = 0.0f;
return false;
}
pulseFreq = cnt;
pulseFreq /= dur;
return true;
valid = (dur != 0 && cnt != 0);
pulseFreq = valid
? static_cast<float>(cnt) / static_cast<float>(dur)
: 0.0f;
return valid;
}

void HLW8012_sample::reset()
Expand All @@ -34,6 +31,7 @@ void HLW8012_sample::reset()
{
finished.duration_usec = timeDiff(prev, next);
finished.count = cnt;
finished.updated = true;
}
else
{
Expand All @@ -48,29 +46,29 @@ void HLW8012_sample::reset()
HLW8012_sample::result_e HLW8012_sample::add()
{
const uint32_t now = micros();
const int32_t duration_since_start_usec(timeDiff(start_usec, now));
if (duration_since_start_usec < HLW8012_UNSTABLE_SAMPLE_DURATION_USEC) {
return HLW8012_sample::result_e::NotEnough;
}
if (first_pulse_usec == 0) {
first_pulse_usec = now;
return HLW8012_sample::result_e::NotEnough;
}
++count;
last_pulse_usec = now;

const auto res = getState();
if (res == HLW8012_sample::result_e::NoisePeriod ||
first_pulse_usec == 0)
if (timeDiff(first_pulse_usec, now) > HLW8012_MINIMUM_SAMPLE_DURATION_USEC)
{
count = 0;
first_pulse_usec =
(res == HLW8012_sample::result_e::NoisePeriod)
? 0
: now;
return HLW8012_sample::result_e::NotEnough;
return HLW8012_sample::result_e::Enough;
}
return res;
if (duration_since_start_usec >= HLW8012_MAXIMUM_SAMPLE_DURATION_USEC)
{
return HLW8012_sample::result_e::Expired;
}
return HLW8012_sample::result_e::NotEnough;
}

HLW8012_sample::result_e HLW8012_sample::getState() const
{
if (last_pulse_usec == 0)
{
return HLW8012_sample::result_e::Cleared;
}
const int32_t duration_since_start_usec(timeDiff(start_usec, last_pulse_usec));
if (duration_since_start_usec < HLW8012_UNSTABLE_SAMPLE_DURATION_USEC && count == 0)
{
Expand All @@ -88,11 +86,18 @@ HLW8012_sample::result_e HLW8012_sample::getState() const
return HLW8012_sample::result_e::NotEnough;
}

HLW8012_sample::result_e HLW8012_sample::getPulseFreq(float &pulsefreq) const
bool HLW8012_sample::getPulseFreq(float &pulsefreq)
{
if (finished.getPulseFreq(pulsefreq))
{
return HLW8012_sample::result_e::Enough;
}
return HLW8012_sample::result_e::Expired;
return finished.getPulseFreq(pulsefreq);
}

bool HLW8012_sample::updated(bool& valid)
{
const bool res = finished.updated == 1;
valid = finished.valid;
if (res) {
// Do not try to clear the volatile value if it was not set
finished.updated = 0;
}
return res;
}
21 changes: 18 additions & 3 deletions lib/HLW8012_1.1.1/src/HLW8012_sample.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,19 @@ typedef volatile unsigned char HLW8012_VOLATILE_UCHAR;

struct HLW8012_finished_sample
{
bool getPulseFreq(float &pulseFreq) const;
bool getPulseFreq(float &pulseFreq);
void clear()
{
duration_usec = 0;
count = 0;
updated = false;
valid = false;
}

int32_t duration_usec{};
uint32_t count{};
HLW8012_VOLATILE_UCHAR updated{};
bool valid{};
};

struct HLW8012_sample
Expand All @@ -42,7 +46,6 @@ struct HLW8012_sample
enum class result_e
{
NotEnough = 0,
Cleared,
NoisePeriod,
Enough,
Expired
Expand All @@ -59,7 +62,19 @@ struct HLW8012_sample
// Check to make sure we have long enough duration and at least 1 sample
result_e getState() const HLW8012_IRAM;

result_e getPulseFreq(float &pulsefreq) const;

// Use float calculations to compute frequency.
// @retval true: Computed value is usable
// @retval false: Computed value is 0.
bool getPulseFreq(float &pulsefreq);

// Check to see if there has been a new measurement.
// Calling this also clears the updated flag.
bool updated(bool& valid);

void setValid(bool valid) { finished.valid = valid ? 1 : 0; }

bool isValid() const { return finished.valid == 1; }

private:
static inline int32_t timeDiff(const unsigned long prev, const unsigned long next)
Expand Down

0 comments on commit 91054fe

Please sign in to comment.