• Witaj na Forum Arduino Polska! Zapraszamy do rejestracji!
  • Znajdziesz tutaj wiele informacji na temat hardware / software.
Witaj! Logowanie Rejestracja


Ocena wątku:
  • 0 głosów - średnia: 0
  • 1
  • 2
  • 3
  • 4
  • 5
ESP8266EX + BL0397, czyli smart gniazdko i problem z pomiarem prądu
#1
Witam, 

zakupiłem z ciekawości budowy, działania, a przede wszystkim dokładności pomiarów gniazdko SmartDGM, made for Biedronka. Na pokładzie jest nic innego jak układ ESP8266EX, a za pomiar prądu odpowiada klon HLW8012, czyli BL0397.
No i właśnie w fakcie, że to jest klon, jest cały szkopuł.

Do obsługi tego, użyłem biblioteki od HLW8012 autorstwa Xose Pérez https://github.com/xoseperez/hlw8012
Dla BL0397 trzeba było nieco tą bibliotekę poprawić, ale to głównie kosmetyka, czyli mnożniki, napięcia wzorcowe, itp.

Mój problem nie dotyczy tyle dokładności pomiaru, co samego pomiaru.

Gdy wgrywam example, ustawiając rzecz jasna odpowiednie piny, w Serial Monitorze, mam odczyt, ale zawsze ten sam. 
Tzn. wykonując manualną kalibrację (jest w kodzie sketcha), np. na żarówce 150W, po ustawieniu multiplerów, restarcie płytki i właczeniu zasilania, widzę ładnie odczyt 230V, 150W. Zmieniam żarówkę na 40W i mam dokładnie ten sam odczyt. Kolejna zmiana na 10W i nadal w Serialu widzę 150W. Oczywiście rekalibracja na inną żarówkę daje tylko tyle że wynik z tej kalibracji mam w serialu.

Mój kod programu, odchudzony o części od wifi czy komunikacji z serwerem:
Kod:
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266HTTPClient.h>
#include "BL0397.h"


#define LED                             13
#define RELAY_PIN                       14
#define SEL_PIN                         12
#define CF1_PIN                         5
#define CF_PIN                          4
//int SWITCH =                         3;

#define UPDATE_TIME                     4000

#define CURRENT_MODE                    LOW

#define CURRENT_RESISTOR                0.001
#define VOLTAGE_RESISTOR_UPSTREAM       ( 3 * 599000 ) // Real: 2280k
#define VOLTAGE_RESISTOR_DOWNSTREAM     ( 1000 ) // Real 1.009k

BL0397 hlw8012;



void setup() {
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, HIGH);
  pinMode(LED, OUTPUT);
  digitalWrite(LED, LOW);

   hlw8012.begin(CF_PIN, CF1_PIN, SEL_PIN, CURRENT_MODE, false, 500000);

//   hlw8012.setResistors(CURRENT_RESISTOR, VOLTAGE_RESISTOR_UPSTREAM, VOLTAGE_RESISTOR_DOWNSTREAM);
    hlw8012.setCurrentMultiplier(45.65);
    hlw8012.setVoltageMultiplier(16100.45);
    hlw8012.setPowerMultiplier(12000.88);

}

void unblockingDelay(unsigned long mseconds) {
    unsigned long timeout = millis();
    while ((millis() - timeout) < mseconds) delay(1);
}

//=======================================================================
//                    Main Program Loop
//=======================================================================
void loop() {
//  button();
  server.handleClient();
  delay(500);
    static unsigned long last = millis();

    // This UPDATE_TIME should be at least twice the minimum time for the current or voltage
    // signals to stabilize. Experimentally that's about 1 second.
    if ((millis() - last) > UPDATE_TIME) {

        last = millis();
        Serial.print("[HLW] Active Power (W)    : "); Serial.println(hlw8012.getActivePower()); power = hlw8012.getActivePower();
        Serial.print("[HLW] Voltage (V)         : "); Serial.println(hlw8012.getVoltage()); volt=hlw8012.getVoltage();
        Serial.print("[HLW] Current (A)         : "); Serial.println(hlw8012.getCurrent()); ampere=hlw8012.getCurrent();
        Serial.print("[HLW] Apparent Power (VA) : "); Serial.println(hlw8012.getApparentPower());
        Serial.print("[HLW] Power Factor (%)    : "); Serial.println((int) (100 * hlw8012.getPowerFactor()));
        hlw8012.toggleMode();

    }
Serial.println(countdown);

//=======================================================================



BL0397.h:
Kod:
#ifndef BL0937_h
#define BL0937_h

#include <Arduino.h>

// Internal voltage reference value -- DO NOT CHANGE
#define V_REF               1.218

// The factor of a 1mOhm resistor as per reference circuit in datasheet
// A 1mOhm resistor allows a ~30A max measurement
#define R_CURRENT           0.001

// This is the factor of a voltage divider of 6x 330K upstream and 1k downstream as per reference circuit in datasheet
// e.g. 110mV * (R_VOLTAGE = ~2000) = 220V
#define R_VOLTAGE           (6 * 330) + 1

// Minimum delay between selecting a mode and reading a sample
#define READING_INTERVAL    3000

// Maximum pulse with in microseconds
#define PULSE_TIMEOUT       1000000

// CF1 mode
typedef enum {
    MODE_CURRENT,
    MODE_VOLTAGE
} BL0937_mode_t;

class BL0937 {

    public:

        void cf_interrupt();
        void cf1_interrupt();

        void begin(
            unsigned char cf_pin,
            unsigned char cf1_pin,
            unsigned char sel_pin,
            unsigned char currentWhen = HIGH,
            bool use_interrupts = true,
            unsigned long pulse_timeout = PULSE_TIMEOUT);

        void setMode(BL0937_mode_t mode);
        BL0937_mode_t getMode();
        BL0937_mode_t toggleMode();

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

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

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

        double getCurrentMultiplier() { return _current_multiplier; };
        double getVoltageMultiplier() { return _voltage_multiplier; };
        double 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 resetMultipliers();

    private:

        unsigned char _cf_pin;
        unsigned char _cf1_pin;
        unsigned char _sel_pin;

        double _current_resistor = R_CURRENT;
        double _voltage_resistor = R_VOLTAGE;

        double _current_multiplier; // Unit: us/A
        double _voltage_multiplier; // Unit: us/V
        double _power_multiplier;   // 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;

        unsigned char _current_mode = HIGH;
        volatile unsigned char _mode;

        bool _use_interrupts = true;
        volatile unsigned long _last_cf_interrupt = 0;
        volatile unsigned long _last_cf1_interrupt = 0;
        volatile unsigned long _first_cf1_interrupt = 0;

        void _checkCFSignal();
        void _checkCF1Signal();
        void _calculateDefaultMultipliers();

};

#endif

BL0307.cpp
Kod:
#include <Arduino.h>
#include "BL0937.h"

void BL0937::begin(
    unsigned char cf_pin,
    unsigned char cf1_pin,
    unsigned char sel_pin,
    unsigned char currentWhen,
    bool use_interrupts,
    unsigned long pulse_timeout
    ) {

    _cf_pin = cf_pin;
    _cf1_pin = cf1_pin;
    _sel_pin = sel_pin;

    _current_mode = currentWhen;
    _use_interrupts = use_interrupts;
    _pulse_timeout = pulse_timeout;

    pinMode(_cf_pin, INPUT_PULLUP);
    pinMode(_cf1_pin, INPUT_PULLUP);
    pinMode(_sel_pin, OUTPUT);

    _calculateDefaultMultipliers();

    _mode = _current_mode;
    digitalWrite(_sel_pin, _mode);
}

void BL0937::setMode(BL0937_mode_t mode) {
    _mode = (mode == MODE_CURRENT) ? _current_mode : 1 - _current_mode;
    digitalWrite(_sel_pin, _mode);

    if (_use_interrupts) {
        _last_cf1_interrupt = _first_cf1_interrupt = micros();
    }
}

BL0937_mode_t BL0937::getMode() {
    return (_mode == _current_mode) ? MODE_CURRENT : MODE_VOLTAGE;
}

BL0937_mode_t BL0937::toggleMode() {
    BL0937_mode_t new_mode = getMode() == MODE_CURRENT ? MODE_VOLTAGE : MODE_CURRENT;
    setMode(new_mode);
    return new_mode;
}

double BL0937::getCurrent() {

    // Power measurements are more sensitive to switch offs,
    // so we first check if power is 0 to set _current to 0 too
    if (_power == 0) {
        _current_pulse_width = 0;

    } else if (_use_interrupts) {
        _checkCF1Signal();

    } else if (_mode == _current_mode) {
        _current_pulse_width = pulseIn(_cf1_pin, HIGH, _pulse_timeout);

    }

    _current = (_current_pulse_width > 0) ? _current_multiplier / _current_pulse_width / 2 : 0;
    return _current;

}

unsigned int BL0937::getVoltage() {
    if (_use_interrupts) {
        _checkCF1Signal();
    } else if (_mode != _current_mode) {
        _voltage_pulse_width = pulseIn(_cf1_pin, HIGH, _pulse_timeout);
    }

    _voltage = (_voltage_pulse_width > 0) ? _voltage_multiplier / _voltage_pulse_width / 2 : 0;
    return _voltage;
}

unsigned int BL0937::getActivePower() {
    if (_use_interrupts) {
        _checkCFSignal();
    } else {
        _power_pulse_width = pulseIn(_cf_pin, HIGH, _pulse_timeout);
    }

    _power = (_power_pulse_width > 0) ? _power_multiplier / _power_pulse_width / 2 : 0;
    return _power;
}

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

unsigned int BL0937::getReactivePower() {
    unsigned int active = getActivePower();
    unsigned int apparent = getApparentPower();
    if (apparent > active) {
        return sqrt(apparent * apparent - active * active);
    } else {
        return 0;
    }
}

double BL0937::getPowerFactor() {
    unsigned int active = getActivePower();
    unsigned int apparent = getApparentPower();
    if (active > apparent) return 1;
    if (apparent == 0) return 0;

    return (double) active / apparent;
}

unsigned long BL0937::getEnergy() {

    // Counting pulses only works in IRQ mode
    if (!_use_interrupts) return 0;

    /*
        Pulse count is directly proportional to energy:
        P = m*f (m=power multiplier, f = Frequency)
        f = N/t (N=pulse count, t = time)
        E = P*t = m*N  (E=energy)
    */
    return _pulse_count * _power_multiplier / 1000000. / 2;

}

void BL0937::resetEnergy() {
    _pulse_count = 0;
}

void BL0937::expectedCurrent(double value) {
    if (_current == 0) getCurrent();
    if (_current > 0) _current_multiplier *= (value / _current);
}

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

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

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

void BL0937::setResistors(double current, double voltage_upstream, double voltage_downstream) {
    if (voltage_downstream > 0) {
        _current_resistor = current;
        _voltage_resistor = (voltage_upstream + voltage_downstream) / voltage_downstream;
        _calculateDefaultMultipliers();
    }
}

void ICACHE_RAM_ATTR BL0937::cf_interrupt() {
    unsigned long now = micros();
    _power_pulse_width = now - _last_cf_interrupt;
    _last_cf_interrupt = now;
    _pulse_count++;
}

void ICACHE_RAM_ATTR BL0937::cf1_interrupt() {

    unsigned long now = micros();
    unsigned long pulse_width;

    if ((now - _first_cf1_interrupt) > _pulse_timeout) {

        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);
        _first_cf1_interrupt = now;

    }

    _last_cf1_interrupt = now;

}

// 1) output pulse width is fixed at 38uS, The frequency is proportional to the power value
// 2) overcurrent indicator. outputs 6.78KHz pulses when overcurrent occurs
void BL0937::_checkCFSignal() {
    if ((micros() - _last_cf_interrupt) > _pulse_timeout) _power_pulse_width = 0;
}

// SEL=0 (MODE_CURRENT), the output current is rms, output pulse width is fixed at 38uS, The frequency is proportional to the current value
// SEL=1 (MODE_VOLTAGE), the output voltage is rms, output pulse width is fixed at 38uS. The frequency is proportional to the voltage value
void BL0937::_checkCF1Signal() {
    if ((micros() - _last_cf1_interrupt) > _pulse_timeout) {
        if (_mode == _current_mode) {
            _current_pulse_width = 0;
        } else {
            _voltage_pulse_width = 0;
        }
        toggleMode();
    }
}

// Active Power Freq = 1721506 * ((VOL_PIN * CUR_PIN) / (V_REF * V_REF))
// Voltage Freq = 15397 * (VOL_PIN / V_REF);
// Current Freq = 94638 * (CUR_PIN / V_REF);
//
void BL0937::_calculateDefaultMultipliers() {
    _power_multiplier   = 1000000.0 * (1721506 / (_voltage_resistor / _current_resistor * (V_REF * V_REF)));
    _voltage_multiplier = 1000000.0 * (15397 / (_voltage_resistor * V_REF));
    _current_multiplier = 1000000.0 * (94638 / (_current_resistor * V_REF));
}

Wskazówki z internetu, to SEL_PIN jest w tym gniazdku INVERTED i żeby pomiar był skuteczny, należy ustawić monitory na FALLING EDGEs. Mnie to za wiele nie rozjaśniło, ale liczę na Waszą pomoc z implementacją tego Smile
 
Odpowiedź
#2
Nikt nie jest w stanie pomóc?
 
Odpowiedź
#3
Moge pomóc.
 
Odpowiedź
#4
(22-11-2019, 10:10)semi napisał(a): Moge pomóc.

Jeżeli to nie problem, to ja po proszę Wink
 
Odpowiedź
#5
(22-11-2019, 16:48)Mad_Maxs napisał(a):
(22-11-2019, 10:10)semi napisał(a): Moge pomóc.

Jeżeli to nie problem, to ja po proszę Wink
Zacznij od pokazania schematu urządzenia a przynajmniej obwodów związanych z pomiarem prądu.
 
Odpowiedź
#6
Kostki HLW8012 zamieniają natężenie oraz napięcie prądu na częstotliwość. Są dwa wyjścia jedno od prądu, drugie od napięcia. Ta biblioteka nie robi nic innego jak mierzy długość impulsu. Więc jeśli coś nie działa, to może zacznij od początku, czyli sprawdź oscyloskopem czy zmienia się częstotliwość na wyjściu "prądowym" w zależności od obciążenia.
Czy używasz przerwań do pomiaru częstotliwości, czy jakiegoś pulseIn()?
Jeśli masz problem z kodem lub sprzętem, zadaj pytanie na forum. Nie odpowiadam na PW, jeśli nie dotyczą one spraw forum lub innych tematów prywatnych.

[Obrazek: SsIndaG.jpg]
 
Odpowiedź
#7
(23-11-2019, 15:50)Robson Kerman napisał(a): Kostki HLW8012 zamieniają natężenie oraz napięcie prądu na częstotliwość. Są dwa wyjścia jedno od prądu, drugie od napięcia. Ta biblioteka nie robi nic innego jak mierzy długość impulsu. Więc jeśli coś nie działa, to może zacznij od początku, czyli sprawdź oscyloskopem czy zmienia się częstotliwość na wyjściu "prądowym" w zależności od obciążenia.
Czy używasz przerwań do pomiaru częstotliwości, czy jakiegoś pulseIn()?

Niestety nie mam do dyspozycji oscyloskopu.
Dla wspomnianego gniazdka, jeden z użytkowników supli napisał firmware, z którym działa idealnie, a więc sam układ działa poprawnie. Dla mnie jednak supla to nie jest rozwiązanie, wolę mieć własny system, pracujący tylko w sieci wewnętrznej, oparty na kodzie, w którym większość linii znam
 
Odpowiedź
#8
(25-11-2019, 22:45)Mad_Maxs napisał(a): ... wolę mieć własny system ... oparty na kodzie, w którym większość linii znam.

No mi się zdaje, że nie znasz. Zadałem pytanie, czy używasz przerwań, czy jakiegoś pulseIn() i nie otrzymałem odpowiedzi. Pewnie dla tego, bo nie masz pojęcia jak działa ta biblioteka. Jaki jest problem zajrzeć tam i sprawdzić?
Proszę jedynie o minimum współpracy.
Czy w ogóle ESP8266 będzie pracowało z ową biblioteką?
Nie jestem ekspertem od ESP, ale na 99% jestem pewny, że dla ESP nie działa pulseIn().

Edit: Działają za to przerwania na prawie każdym pinie i bardzo łatwo jest napisać funkcję, która w przerwaniu będzie mierzyła częstotliwość, potem tylko miernik w dłoń aby skalibrować wyniki i będziesz miał swój monitoring gniazdka.
Podpowiem wszystko co i jak. Napisałem takową funkcję i działa, sprawdzałem z oscyloskopem, to błąd jest w okolicach 0,5%. Można go zmniejszyć ale kosztem częstotliwości próbkowania.
Jeśli masz problem z kodem lub sprzętem, zadaj pytanie na forum. Nie odpowiadam na PW, jeśli nie dotyczą one spraw forum lub innych tematów prywatnych.

[Obrazek: SsIndaG.jpg]
 
Odpowiedź
#9
(28-11-2019, 00:00)Robson Kerman napisał(a):
(25-11-2019, 22:45)Mad_Maxs napisał(a): ... wolę mieć własny system ... oparty na kodzie, w którym większość linii znam.

No mi się zdaje, że nie znasz. Zadałem pytanie, czy używasz przerwań, czy jakiegoś pulseIn() i nie otrzymałem odpowiedzi. Pewnie dla tego, bo nie masz pojęcia jak działa ta biblioteka. Jaki jest problem zajrzeć tam i sprawdzić?

Masz mnie.. wiem że pomiar jest przełączany pomiędzy dwiema wartościami, pulseIn() tam nie znalazłem, ale czy to było by właściwą odpowiedzią, nie wiedziałem.
Z własnym kodem, bardziej chodziło mi o to co mam naskrobane w innych płytkach których używam. Tej biblioteki nie ogarnąłem, dlatego postanowiłem napisać na forum.

Znalazłem w internecie informację, że ESP działa z biblioteką po korekcie, ale szczegółów już nie dostałem.

Jeżeli dobrze zrozumiałem, to napisałeś własną funkcję bez używania biblioteki? Tutaj też było napisane, że większa częstotliwość próbkowania, skutkuje mniejszą dokładnością pomiaru
 
Odpowiedź
#10
Biblioteka nie jest na przerywaniu. Na esp8266 działa pulsenIn(), nie działa tylko pulsenInlong()

te wycinki z biblioteki 
Kod:
double HLW8012::getCurrent() {

    // Power measurements are more sensitive to switch offs,
    // so we first check if power is 0 to set _current to 0 too
    if (_power == 0) {
        _current_pulse_width = 0;

    } else if (_use_interrupts) {
        _checkCF1Signal();

    } else if (_mode == _current_mode) {
        _current_pulse_width = pulseIn(_cf1_pin, HIGH, _pulse_timeout);
    }

    _current = (_current_pulse_width > 0) ? _current_multiplier / _current_pulse_width / 2 : 0;
    return _current;

}

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

unsigned int HLW8012::getActivePower() {
    if (_use_interrupts) {
        _checkCFSignal();
    } else {
        _power_pulse_width = pulseIn(_cf_pin, HIGH, _pulse_timeout);
    }
    _power = (_power_pulse_width > 0) ? _power_multiplier / _power_pulse_width / 2 : 0;
    return _power;
}

a tu biblioteka esp

Kod:
// max timeout is 27 seconds at 160MHz clock and 54 seconds at 80MHz clock
unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout)
{
    const uint32_t max_timeout_us = clockCyclesToMicroseconds(UINT_MAX);
    if (timeout > max_timeout_us) {
        timeout = max_timeout_us;
    }
    const uint32_t timeout_cycles = microsecondsToClockCycles(timeout);
    const uint32_t start_cycle_count = xthal_get_ccount();
    WAIT_FOR_PIN_STATE(!state);
    WAIT_FOR_PIN_STATE(state);
    const uint32_t pulse_start_cycle_count = xthal_get_ccount();
    WAIT_FOR_PIN_STATE(!state);
    return clockCyclesToMicroseconds(xthal_get_ccount() - pulse_start_cycle_count);
}
Arduino zostało wymyślone po to, by robić dobrze jedną prostą rzecz – migać diodą. 
 
Odpowiedź
  


Skocz do:


Przeglądający: 1 gości