В связи с усилением энергетического кризиса во всем мире сейчас все более острой становится проблема экономии электроэнергии. А чтобы разработать эффективные меры ее экономии необходимо знать сколько электроэнергии потребляет то или иное устройство. Для этой цели разработаны различные устройства, но хорошие из них стоят достаточно дорого. Поэтому в данной статье мы рассмотрим создание устройства на основе модуля ESP32, которое способно выполнять функции измерителя электроэнергии и ваттметра.
Ранее на нашем сайте мы уже рассматривали проект ваттметра на основе модуля ESP32, но рассматриваемый в данной статье проект особенно удобен для измерения мощности устройств, работающих от сети переменного тока.
Необходимые компоненты
- Модуль ESP32 (ESP32 WROOM 32D Module) (купить на AliExpress).
- Импульсный источник питания HI LINK 5V 3W.
- 0.96” OLED дисплей 128X64 с интерфейсом I2C (купить на AliExpress).
- Датчик напряжения ZMPT101B (купить на AliExpress).
- Датчик тока ACS712 (купить на AliExpress).
- 220V AC 3 Pin Socket MALE.
- 220V AC 3 Pin Socket FEMALE.
- Корпус устройства, напечатанный на 3D принтере.
Как будет работать наш ваттметр
Для измерения потребляемой мощности нам необходимо измерить значения напряжение и тока и сообщить их нашему микроконтроллеру (модулю) ESP32, который на их основе рассчитает мощность и выведет ее значение на экран OLED дисплея. Для питания нашего ваттметра мы будем использовать импульсный источник питания HI LINK 5V 3W, который будет преобразовывать 220 В переменного тока в 5 В постоянного тока.
Структурная схема нашего ваттметра приведена на следующем рисунке.
Теперь давайте рассмотрим датчики, которые мы будем использовать для измерения напряжения и тока.
Датчик напряжения ZMPT101B
ZMPT101B – это трансформаторный датчик напряжения, который линейно уменьшает входное напряжение и обладает аналоговым выходом. Также он обеспечивает изоляцию напряжения до 4000V и безопасную работу с напряжением до 1000V. Схема датчика ZMPT101B приведена на следующем рисунке.
Из анализа представленной схемы видно, что выход трансформатора датчика подключен к микросхеме операционного усилителя LM358N, который усиливает пики напряжения.
Датчик тока ACS712
ACS712 представляет собой микросхему датчика тока, принцип действия которой основан на эффекте Холла. Она обнаруживает изменения магнитного поля при протекании тока и преобразует его в пропорциональное значение напряжения на своем выходе. Схема датчика тока ACS712 приведена на следующем рисунке.
Микросхема ACS712 выпускается в 3-х вариантах исполнения – 5A, 20A и 30A. Для нашего проекта мы выбрали ее вариант на 20A.
Схема проекта
Схема измерителя электроэнергии на основе модуля ESP32 представлена на следующем рисунке.
На схеме показано что импульсный источник питания Hi-link и входной терминал датчика напряжения ZMPT101B подключены в параллель к фазе и сети переменного тока, а датчик тока ACS712 последовательно включен в фазовый провод сети.
OLED дисплей подключен к контактам I2C модуля ESP32, а выходные контакты датчика напряжения и датчика тока подключены к контактам 34 и 36 модуля соответственно. Эти контакты способны выполнять операции аналого-цифрового преобразования (АЦП).
Все компоненты схемы получают питание от выходного контакта 5v модуля Hi-Link (терминалы Vo+ и Vo-).
Объяснение кода программы для модуля ESP32
Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты.
Установка необходимых библиотек
Для нашего проекта нам необходимо будет установить библиотеки Adafruit GFX и Adafruit SSD 1306. Для этого откройте менеджер библиотек (library manager) в Arduino IDE.
Выполните в нем поиск библиотек Adafruit GFX и Adafruit SSD1306 и затем установите их, нажав на кнопку install.
Теперь нам нужно установить библиотеки для работы с датчиками ZMPT101B и ACS712, коды этих библиотек похожи друг на друга, их оригиналы можно скачать по этим ссылкам: ZMPT101B и ACS712. Также эти библиотеки есть в составе файлов, ссылка на скачивание которых приведена в конце статьи. Но мы немного модифицируем их коды в связи с изменением микроконтроллера и чтобы они были совместимы с 12-битным АЦП модуля ESP32.
Чтобы скачать эти библиотеки с репозитория GitHub нажмите на кнопку download в выпадающем меню.
После скачивания zip файла откройте Arduino IDE и выберите в ней пункт Sketch >> Include Library >> Add Zip Library.
Укажите местоположение zip файла на своем компьютере и нажмите open.
В результате Arduino IDE установит вашу библиотеку. Необходимо повторить этот процесс для обоих библиотек.
После этого можно подключить их заголовочные файлы в начале программы.
1 2 3 4 5 6 |
#include "ZMPT101B.h" #include "ACS712.h" #include <SPI.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> |
Библиотеки Wire.h и SPI.h уже по умолчанию присутствуют в Arduino IDE, их устанавливать не нужно.
Команды инициализации
Инициализируем объекты для работы с датчиком тока и с датчиком напряжения.
1 2 |
ZMPT101B voltageSensor(34); ACS712 currentSensor(ACS712_20A, 36); |
С помощью следующего фрагмента кода инициализируем OLED дисплей.
1 2 3 4 5 |
#define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 64 // OLED display height, in pixels #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) #define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); |
После этого объявим глобальные переменные, которые потребуются нам в коде программы.
1 2 3 4 5 6 7 8 9 10 |
float P=0; float U=0; float I=0; long dt=0; float CulmPwh=0; float units=0; long changeScreen=0; float lastSample=0; unsigned long lasttime=0; long ScreenSelect = 0; |
Код для функции void setup()
В функции void setup() мы установим точку нуля и чувствительность наших датчиков, а также начнем работу с OLED дисплеем.
Примечание: пока чувствительность датчиков в коде программы не будет меняться, возможно, вам необходимо будет изменить точку нуля. Для этого просто единожды раскомментируйте строчки кода, обозначенные как “Calibration Commands” и убедитесь что в это время на вход модуля не подается ни внешнего тока, ни напряжения. После этого код программы покажет вам точку нуля (zero point) и вы можете обновить ее значение в коде.
Процесс калибровки следует выполнять отдельно для датчика тока и для датчика напряжения.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
void setup() { Serial.begin(9600); delay(100); voltageSensor.setSensitivity(0.0025); voltageSensor.setZeroPoint(2621); currentSensor.setZeroPoint(2943); currentSensor.setSensitivity(0.15); if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1306 allocation failed")); for(;;); } // Clear the buffer display.clearDisplay(); display.display(); //Caliberation Commands Need To Be Run On First Upload. //CalibCurrent(); //CalibVoltage(); } |
Основной цикл программы
В основном цикле программы мы будем считывать с выходов АЦП значения с выходов датчиков и передавать их на дисплей с интервалом 500 мс.
Также мы будем производить расчет мгновенной потребляемой мощности и потребленной электроэнергии.
Поскольку на экране дисплея нам необходимо отображать достаточно много переменных (напряжение, ток, мгновенная мощность, потребленная электроэнергия) мы будем переключаться между 4 экранами чтобы отобразить все это.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
void loop() { // To measure voltage/current we need to know the frequency of voltage/current // By default 50Hz is used, but you can specify desired frequency // as first argument to getVoltageAC and getCurrentAC() method, if necessary U = voltageSensor.getVoltageAC(); if(U<55) { U=0; CulmPwh=0; } I = currentSensor.getCurrentAC(); dt = micros()- lastSample; if(I<0.15) { I=0; CulmPwh=0; } // To calculate the power we need voltage multiplied by current P = U * I; CulmPwh = CulmPwh + P*(dt/3600);///uWh units= CulmPwh/1000; if(millis()-changeScreen>5000) { ScreenSelect+=1; changeScreen=millis(); } if(millis()-lasttime>500) { if((ScreenSelect%4)==0) { displayVoltCurrent(); }//Volts and Current else if( (ScreenSelect%4)==1) { displayInstPower(); }//Instantaenous Power else if( (ScreenSelect%4)==2) { displayEnergy(); } //Energy else if( (ScreenSelect%4)==3) { displayUnits(); } //Units } lastSample=micros(); } |
Необходимые для проекта функции
В нашем проекте нам потребуются следующие функции, которые мы запрограммировали:
1 2 3 4 5 6 7 8 |
CalibCurrent(); CalibVoltage(); displayVoltCurrent(); displayInstPower(); displayEnergy(); displayUnits(); displayCenter(String,Position); |
Их коды можно записать в программе после функции void loop().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
void displayVoltCurrent() { display.clearDisplay(); display.setTextColor(WHITE); display.setTextSize(1); display.setCursor(0,0); void displayVoltCurrent() { display.clearDisplay(); display.setTextColor(WHITE); display.setTextSize(3); displayCenter(String(U)+"V",3); display.setTextSize(3); displayCenter(String(I)+"A",33); display.display(); lasttime=millis(); } void displayInstPower() { display.clearDisplay(); display.setTextColor(WHITE); display.setTextSize(2); display.setCursor(0,0); displayCenter("Power",3); display.setTextSize(3); if(P>1000) { displayCenter(String(P/1000)+"kW",30); } else { displayCenter(String(P)+"W",30); } display.display(); lasttime=millis(); } void displayEnergy() { display.clearDisplay(); display.setTextColor(WHITE); if(CulmPwh>1000000000) { display.setTextSize(2); displayCenter("Energy kWh",3); display.setTextSize(3); displayCenter(String(CulmPwh/1000000000),30); } else if(CulmPwh<1000000000 && CulmPwh>1000000) { display.setTextSize(2); displayCenter("Energy Wh",3); display.setTextSize(3); displayCenter(String(CulmPwh/1000000),30); } else if(CulmPwh<1000000 && CulmPwh>1000) { display.setTextSize(2) displayCenter("Energy mWh",3); display.setTextSize(3); displayCenter(String(CulmPwh/1000),30); } else { display.setTextSize(2); displayCenter("Energy uWh",3); display.setTextSize(3); displayCenter(String(CulmPwh),30); } display.display(); lasttime=millis(); } void displayUnits() { display.clearDisplay(); display.setTextColor(WHITE); if(units>1000000) { display.setTextSize(2); displayCenter("Units",3); display.setTextSize(3); displayCenter(String(units/1000000),30); } else if(units<1000000 && units>1000) { display.setTextSize(2); displayCenter("MilliUnits",3); display.setTextSize(3); displayCenter(String(units/1000),30); } else { display.setTextSize(2); displayCenter("MicroUnits",3); display.setTextSize(3); displayCenter(String(units),30); } display.display(); lasttime=millis(); } void CalibCurrent() { while(1) { currentSensor.calibrate(); Serial.print("Zero Point Current :"); Serial.println(currentSensor.getZeroPoint()); display.clearDisplay(); display.setTextColor(WHITE); display.setTextSize(1); display.setCursor(0,0); display.print("Current Zero Point :"); display.setCursor(0,20); display.setTextSize(2); display.print(currentSensor.getZeroPoint()); display.display(); delay(500); } } void CalibVoltage() { while(1) { voltageSensor.calibrate(); Serial.print("Zero Point Voltage :"); Serial.println(voltageSensor.getZeroPoint()); display.clearDisplay(); display.setTextColor(WHITE); display.setTextSize(1); display.setCursor(0,0); display.print("Voltage Zero Point :"); display.setCursor(0,20); display.setTextSize(2); display.print(voltageSensor.getZeroPoint()); display.display(); delay(500); } } void displayCenter(String text, int line) { int16_t x1; int16_t y1; uint16_t width; uint16_t height; display.getTextBounds(text, 0, 0, &x1, &y1, &width, &height); // display on horizontal center display.setCursor((SCREEN_WIDTH - width) / 2, line); display.println(text); // text to display display.display(); } |
Сборка корпуса для проекта
Корпус данного измерителя энергии можно напечатать на 3D принтере, необходимые для этого STL файлы можно скачать по ссылке ниже в статье. Корпус состоит из двух частей, которые легко соединяются вместе.
При сборке корпуса проекта постарайтесь сделать правильную изоляцию с помощью изоляционной ленты и термоусадки. Внешний вид корпуса проекта показан на следующем рисунке.
Примечание: конечно, гнездо для розетки у этого корпуса для наших российских условий вряд ли подойдет и тут придется немного "подшаманить".
После того как все будет готово, можете проверить работу устройства, подключив через него в сеть какой-нибудь потребитель переменного тока.
Исходный код программы
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 |
#include "ZMPT101B.h" #include "ACS712.h" #include <SPI.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define SCREEN_WIDTH 128 // ширина OLED дисплея в пикселах #define SCREEN_HEIGHT 64 // высота OLED дисплея в пикселах #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) #define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); ZMPT101B voltageSensor(34); ACS712 currentSensor(ACS712_20A, 36); float P=0; float U=0; float I=0; long dt=0; float CulmPwh=0; float units=0; long changeScreen=0; float lastSample=0; unsigned long lasttime=0; long ScreenSelect = 0; void setup() { Serial.begin(9600); delay(100); voltageSensor.setSensitivity(0.0025); voltageSensor.setZeroPoint(2621); currentSensor.setZeroPoint(2943); currentSensor.setSensitivity(0.15); if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1306 allocation failed")); for(;;); } // Очищаем буфер display.clearDisplay(); display.display(); //Caliberation Commands Need To Be Run On First Upload. (команды калибровки, необходимы для первого запуска проекта) //CalibCurrent(); //CalibVoltage(); } void loop() { // To measure voltage/current we need to know the frequency of voltage/current // By default 50Hz is used, but you can specify desired frequency // as first argument to getVoltageAC and getCurrentAC() method, if necessary U = voltageSensor.getVoltageAC(); if(U<55) { U=0; CulmPwh=0; } I = currentSensor.getCurrentAC(); dt = micros()- lastSample; if(I<0.15) { I=0; CulmPwh=0; } // To calculate the power we need voltage multiplied by current P = U * I; //формула для расчета мощности CulmPwh = CulmPwh + P*(dt/3600);///uWh, электроэнергия, в кВт/ч units= CulmPwh/1000; if(millis()-changeScreen>5000) { ScreenSelect+=1; changeScreen=millis(); } if(millis()-lasttime>500) { if((ScreenSelect%4)==0) { displayVoltCurrent(); }//напряжение и ток else if( (ScreenSelect%4)==1) { displayInstPower(); }//мгновенная мощность else if( (ScreenSelect%4)==2) { displayEnergy(); } //электроэнергия else if( (ScreenSelect%4)==3) { displayUnits(); } //Units } lastSample=micros(); } void displayVoltCurrent() { display.clearDisplay(); display.setTextColor(WHITE); display.setTextSize(3); displayCenter(String(U)+"V",3); display.setTextSize(3); displayCenter(String(I)+"A",33); display.display(); lasttime=millis(); } void displayInstPower() { display.clearDisplay(); display.setTextColor(WHITE); display.setTextSize(2); display.setCursor(0,0); displayCenter("Power",3); display.setTextSize(3); if(P>1000) { displayCenter(String(P/1000)+"kW",30); } else { displayCenter(String(P)+"W",30); } display.display(); lasttime=millis(); } void displayEnergy() { display.clearDisplay(); display.setTextColor(WHITE); if(CulmPwh>1000000000) { display.setTextSize(2); displayCenter("Energy kWh",3); display.setTextSize(3); displayCenter(String(CulmPwh/1000000000),30); } else if(CulmPwh<1000000000 && CulmPwh>1000000) { display.setTextSize(2); displayCenter("Energy Wh",3); display.setTextSize(3); displayCenter(String(CulmPwh/1000000),30); } else if(CulmPwh<1000000 && CulmPwh>1000) { display.setTextSize(2); displayCenter("Energy mWh",3); display.setTextSize(3); displayCenter(String(CulmPwh/1000),30); } else { display.setTextSize(2); displayCenter("Energy uWh",3); display.setTextSize(3); displayCenter(String(CulmPwh),30); } display.display(); lasttime=millis(); } void displayUnits() { display.clearDisplay(); display.setTextColor(WHITE); if(units>1000000) { display.setTextSize(2); displayCenter("Units",3); display.setTextSize(3); displayCenter(String(units/1000000),30); } else if(units<1000000 && units>1000) { display.setTextSize(2); displayCenter("MilliUnits",3); display.setTextSize(3); displayCenter(String(units/1000),30); } else { display.setTextSize(2); displayCenter("MicroUnits",3); display.setTextSize(3); displayCenter(String(units),30); } display.display(); lasttime=millis(); } void CalibCurrent() { while(1) { currentSensor.calibrate(); Serial.print("Zero Point Current :"); Serial.println(currentSensor.getZeroPoint()); display.clearDisplay(); display.setTextColor(WHITE); display.setTextSize(1); display.setCursor(0,0); display.print("Current Zero Point :"); display.setCursor(0,20); display.setTextSize(2); display.print(currentSensor.getZeroPoint()); display.display(); delay(500); } } void CalibVoltage() { while(1) { voltageSensor.calibrate(); Serial.print("Zero Point Voltage :"); Serial.println(voltageSensor.getZeroPoint()); display.clearDisplay(); display.setTextColor(WHITE); display.setTextSize(1); display.setCursor(0,0); display.print("Voltage Zero Point :"); display.setCursor(0,20); display.setTextSize(2); display.print(voltageSensor.getZeroPoint()); display.display(); delay(500); } } void displayCenter(String text, int line) { int16_t x1; int16_t y1; uint16_t width; uint16_t height; display.getTextBounds(text, 0, 0, &x1, &y1, &width, &height); // отображаем горизонтально по центру display.setCursor((SCREEN_WIDTH - width) / 2, line); display.println(text); // текст для отображения display.display(); } |
Все необходимые файлы для проекта можно скачать по следующей ссылке.
4 053 просмотров
Как отслеживать с помощью данного модуля последовательную дугу?
К сожалению, не знаю, не профессионал в этой тематике.
Скажите, в предыдущем измерителе использовали делитель напряжения. Логика esp32 3.3V а датчики как я понимаю 5V.
Да, не вижу проблемы. Там же у источника питания был выход 5V.
Не видите проблемы. А то что на аналоговый вход пойдет более 3.3 вольт это не проблема в измерении?
На какой аналоговый вход?
Разобрался, заработало!!!
И в чем оказалась ошибка? Не поделитесь секретом успеха?
что бы я не делал выходит такое сообщение на строке - ACS712 currentSensor(ACS712_20A, 36); -'ACS712_20A' was not declared in this scope
А вы точно скачали и установили в Arduino IDE библиотеки ZMPT101B и ACS712?
Да установлены с архива вот от сюда https://github.com/Circuit-Digest/ESP32_EnergyMonitor
Ну тогда сложно так сразу сказать в чем проблема. Попробуйте скомпилировать программу, убрав из нее все, что связано с датчиком тока, скомпилируется или нет, а в другую программу скопируйте только импорт библиотеки для работы с датчиком тока и инициализацию объекта для работы с ним, скомпилируется или нет. Тут придется разбивать сложную программу на части и искать проблему по частям
В этом коде автор специально сделал ошибки. чтоб другие помучились и показать какой он крутой программер. В данная строка не должна иметь не каких имен в скобках. В скобках только номер вывода.
ACS712 currentSensor(36);
Понятно. Спасибо за ценное уточнение. Не думаю что он специально это сделал, просто я часто замечаю что авторы проектов на сайте, с которого переведена данная статья, какие то немного рассеянные и достаточно часто допускают в коде какие то простейшие опечатки. Некоторые из них я сам исправляю, но, к сожалению, не все.
откуда значение currentSensor.setSensitivity(0.15)
и какое будет для 30 амперного датчика
Это значение, полученное автором проекта после процесса калибровки. Для 30 амперного датчика также сначала нужно запустить процесс калибровки, в результате которого вы получите нужное значение