16 канальный логгер измерений потребления переменного тока из сети 220В

Недавно ко мне обратился знакомый с просьбой сделать ему прототип устройства для измерения переменного тока. Я как раз был на больничном и искал чем бы заняться. Выслушав его требования, я согласился и взялся за работу.

Основными требованиями были:

  • неинвазивный способ измерения тока
  • использование датчика SCT-013-030
  • 16 каналов измерений
  • введение лога всех измерений вместе с датой и временем на SD карточку
  • питание от внешнего блока питания 12В
  • подключение датчиков с помощью пайки проводов на плате

После неких раздумий были оговорены и согласованы некоторые компоненты. Это микроконтроллер ATmega328, модуль RTC DS3231 и модуль для работы с microSD (ведь сейчас доступнее достать microSD, а разницы то нет).

Микросхемы DS3231 в нашем городе не нашлось, но зато Arduino модулей DS3231 просто навалом. Потому использовался такой вот модуль RTC:

Вроде бы схема должна уже складываться в голове сама, она не совсем то и сложная.

  • Питание у нас от внешнего блока питания на 12В, а значит нам нужен стабилизатор напряжения на 5В.
  • Контроллер с необходимой обвязкой(кварц, подтягивающие резисторы и т.д.)
  • RTC модуль
  • microSD модуль
  • А также интерфейс UART для передачи данных на ПК, и интерфейс для внутрисхемного программирования контроллера по SPI 

Всё чего не хватает в этом списке, это схема подключения датчиков SCT-013-030, и схема реализации 16 каналов измерений (ведь у ATmega328 всего несколько ножек для аналогового измерения, и то некоторые заняты интерфейсом I2C, 27 и 28 ножки).

Давайте по очереди. Сначала разберемся с датчиком тока.

По своей сути этот датчик является измерительным трансформатором тока. Датчик меряет силу переменного тока. Внутри датчика мы уже имеем встроенное сопротивление в 62 Ом, в результате чего на выходе мы имеем напряжение от 0 до 1 В в зависимости от измеряемого тока 0-30 А. Проблема в том что и на выходе датчика у нас получается переменное напряжение 0 — 1 В(если по простому то от -1 до +1 В). А это не подходит нам, потому что контроллер не поймет отрицательного напряжения. Простейшим способом избавиться от этого, это было сдвинуть это напряжение к средней точке с помощью делителя напряжения, как показано на части схемы ниже. С помощью делителя на 2 выводе датчика мы получим 2.5 В.

Таким образом на 1 выводе в точке «CH1» мы будем получать напряжение от 1.5 до 3.5 В (то-есть 2.5 В +-, те 0-1 В что выдает датчик).

Далее я стал думать, как бы организовать 16 каналов измерений. Тогда я вспомнил об одной микросхеме, которую я использовал на своей дипломной работе — CD4052, аналоговый 8-и канальный мультиплексор/демультиплексор.

В нашем случае данную микросхему разумно использовать как мультиплексор, то-есть справа мы имеем входы X0-X3 и Y0-Y3 и слева выходы X и Y. В результате при использовании 2-х таких микросхем мы бы сумели измерять 16 каналов с помощью 4 аналоговых ножек контроллера. Но добравшись до даташита такого чуда, я обнаружил более для меня удобного его брата — CD4051.

Где использовав две таких микросхемы, ми сможем измерить все 16 каналов лишь с помощью 2-х аналоговых ножек контроллера.

Выбор измеряемого канала осуществляется с помощью подачи двоичного кода номера канала на ножки A,B,C.

Так я и сделал, в результате чего мы получили итоговую схему нашего устройства:

Следующим этапом стало проектирование печатной платы, товарищ указал необходимые размеры и форму платы. И процесс начался. За один-два дня плата была разведена, вытравлена, спаяна и готова к работе. Файл печатной платы прикреплен в формате Sprint Layout к статье ниже.

Фото вытравленной и спаянной платы.

А также необходимо указать что и куда подключается на плате.

Ну что же, у нас на руках есть готовая плата со всей необходимой периферией. Пришло время пытаться получать данные с датчиков.

Для начала попробую продемонстрировать работу всего лишь с одним датчиком тока SCT-013-030:

#define AVRG_VOLT 2.50

float voltage0 = (analogRead(A0) * 5.0)/1024.0;
current = fabs(voltage0 — AVRG_VOLT) * 30;

Все очень просто, мы измеряем значение АЦП на ножке, и переводим это значение в вольты. После из этого значения получаем значение тока, с помощью вот такой вот пропорции:

, где 

, потому что нам не важно направление измеряемого тока.

А значит:

 

После запуска устройства, был снят лог и получен следующий график:

Ситуация не совсем та, которую хотелось бы видеть. Все дело в том, что сейчас мы видим график для мгновенных значений тока, то есть значений в определенный момент времени.

А нам нужно получать действующее значение, или как ещё его называют среднеквадратичным значением его мгновенных значений.

А если быть точными, то проще всего будет для расчета взять все мгновенные значения за один период. Так как частота сети у нас 50 Гц, то период будет равен:

 

Период будет равен 0.02 с, или же 20 мс. Потому нам достаточно на протяжении 20 мс измерять мгновенные значения, после чего найти их среднее квадратическое значение.

Потому был написан вот такой вот код реализующий простенький алгоритм уже для измерения действующего значения сразу всех каналов(весь код можно просмотреть ниже и во вложениях к статье):

unsigned char i = 0;
for(i=0; i<8; i++)
{
Set_CHANNELs(i);
f_50hz_time = millis();
unsigned long k_rms = 0;
float Irms_0=0.0f, Irms_1=0.0f;
while(f_50hz_time+20>=millis())
{
float voltage0 = (analogRead(A0) * 5.0)/1024.0;
float voltage1 = (analogRead(A1) * 5.0)/1024.0;
Irms_0 += sq((voltage0 — AVRG_VOLT) * 30);
Irms_1 += sq((voltage1 — AVRG_VOLT) * 30);
k_rms++;
}
ADC_data[i] = sqrt(Irms_0/k_rms);
ADC_data[i+8] = sqrt(Irms_1/k_rms);
}

 

Да, такая картина уже больше похожа на правду. Потому на этом можем и остановится. Давайте приведем весь листинг кода программы:

#include <SPI.h>
#include <SD.h>
#include <DS3231.h>

DS3231 rtc(SDA, SCL);
File myFile;

//CONSTANTs
#define AVRG_VOLT 2.50

//PINs
#define A 5
#define B 6
#define C 7
#define INH 8
#define SS_PIN 10

//MACROSes
#define GET_DATA() rtc.getDateStr(FORMAT_SHORT, FORMAT_LITTLEENDIAN, ‘-‘)
#define GET_TIME() rtc.getTimeStr(FORMAT_LONG)

float ADC_data[16] = {0};

unsigned long write_time;
unsigned long f_50hz_time;
unsigned long DelayTime = 1000;

byte UART_Time[6] = {0,0,0,0,0,0};
unsigned char index_time = 0;

void ADC_measure(void);
void WriteDataToSD(void);
void ReadAllFiles(void);
void WriteDataToUART(void);

void get_commands(void);

void setup() {
pinMode(A, OUTPUT);
pinMode(B, OUTPUT);
pinMode(C, OUTPUT);

pinMode(INH, OUTPUT);
digitalWrite(INH, HIGH);

pinMode(SS_PIN, OUTPUT);
write_time = millis();

Serial.begin(115200);
delay(100);
Serial.println(«Before RTC init»);
rtc.begin();
Serial.println(«After RTC init»);
if(!SD.begin(SS_PIN))
{
Serial.println(«SD card init fail.»);
}
}

void loop() {
get_commands();
ADC_measure();
WriteDataToSD();
}

void Set_CHANNELs(unsigned char number)
{
digitalWrite(INH, HIGH);
switch (number)
{
case 0:digitalWrite(C,0);
digitalWrite(B,0);
digitalWrite(A,0);
break;
case 1:digitalWrite(C,0);
digitalWrite(B,0);
digitalWrite(A,1);
break;
case 2:digitalWrite(C,0);
digitalWrite(B,1);
digitalWrite(A,0);
break;
case 3:digitalWrite(C,0);
digitalWrite(B,1);
digitalWrite(A,1);
break;
case 4:digitalWrite(C,1);
digitalWrite(B,0);
digitalWrite(A,0);
break;
case 5:digitalWrite(C,1);
digitalWrite(B,0);
digitalWrite(A,1);
break;
case 6:digitalWrite(C,1);
digitalWrite(B,1);
digitalWrite(A,0);
break;
case 7:digitalWrite(C,1);
digitalWrite(B,1);
digitalWrite(A,1);
break;
default: break;
}
digitalWrite(INH, LOW);
}

void ADC_measure(void)
{
unsigned char i = 0;
for(i=0; i<8; i++)
{
Set_CHANNELs(i);
f_50hz_time = millis();
unsigned long k_rms = 0;
float Irms_0=0.0f, Irms_1=0.0f;
while(f_50hz_time+20>=millis())
{
float voltage0 = (analogRead(A0) * 5.0)/1024.0;
float voltage1 = (analogRead(A1) * 5.0)/1024.0;
Irms_0 += sq((voltage0 — AVRG_VOLT) * 30);
Irms_1 += sq((voltage1 — AVRG_VOLT) * 30);
k_rms++;
}
ADC_data[i] = sqrt(Irms_0/k_rms);
ADC_data[i+8] = sqrt(Irms_1/k_rms);
}
}

void WriteDataToSD(void)
{
unsigned char i = 0;
myFile = SD.open(GET_DATA(), FILE_WRITE);
if((write_time+DelayTime) < millis())
{
if(myFile)
{
for(i=0; i<16; i++)
{
myFile.print(GET_DATA()); myFile.print(‘;’); myFile.print(GET_TIME()); myFile.print(‘;’); myFile.print(i+1); myFile.print(‘;’); myFile.print(ADC_data[i]); myFile.println(‘;’);
}
Serial.println(«Success writing data to FILE»);
}
else
{
Serial.println(«Error read file from SD card»);
}
write_time = millis();
}
myFile.close();
}

void ReadAllFiles(void)
{
File dir = SD.open(«/»);
dir.rewindDirectory();
File myFile = dir.openNextFile();
while(myFile)
{
Serial.print(«nr File name: «); Serial.println(myFile.name());
while(myFile.available())
{
Serial.write(myFile.read());
}
myFile.close();
myFile = dir.openNextFile();
}

Serial.println(«NO MORE FILES»);
}

void WriteDataToUART(void)
{
unsigned char i = 0;
if( (write_time+DelayTime) < millis())
{
Serial.println();
Serial.print(GET_DATA()); Serial.print(» «); Serial.println(GET_TIME());
for(i=0; i<16; i++)
{
Serial.print(«CH «); Serial.print(i); Serial.print(«: «); Serial.print(ADC_data[i]); Serial.print(» A»);Serial.print(«nr»);
}
write_time = millis();
}
}

void get_commands(void)
{
char cmd;
if(Serial.available())
{
cmd = Serial.read();
}

switch(cmd)
{
case ‘t’:
{
//DD MM YY — Day Month Year, HH MM SS — Hours Minutes Seconds
Serial.println(«Input date and time at this format -> DD MM YY HH MM SS «);
while(!Serial.available());
while((Serial.available()))
{
UART_Time[index_time] = byte(Serial.parseInt());
index_time++;
}
rtc.setDate(UART_Time[0], UART_Time[1], UART_Time[2]);
rtc.setTime(UART_Time[3], UART_Time[4], UART_Time[5]);
index_time = 0;
Serial.print(«Date and time set to «); Serial.print(GET_DATA()); Serial.print(» «); Serial.println(GET_TIME());
break;
}
case ‘r’:
{
ReadAllFiles();
break;
}
default:break;
}
}

Дополнительная информация.

Лог на карточку введется таким образом: каждый день создается новый файл с именем в виде даты(например «15-10-17») и в этот файл построчно пишутся данные в формате

ДАТА;ВРЕМЯ;№ КАНАЛА;ЗНАЧЕНИЕ

Также работать с устройством можно с помощью терминала по COM-порту.

К примеру для настройки часов достаточно отправить команду в виде символа ‘t’, после чего указать дату и время в формате DD MM YY HH MM SS. Все действия сопровождаются подсказками.

Или же можно считать все лог-файлы с помощью команды ‘r’.

Исходник прошивки и печатная плата прикреплены к статье.

Список радиоэлементовОбозначение
Тип
Номинал
Количество
ПримечаниеМагазинМой блокнот

U$1
МК AVR 8-битATmega328P Automotive1
U1
Линейный регуляторLM1117-N1
5VU$2, U$6
Мультиплексор/демультиплексорCD4051B2

DS3231 Arduino Module1

microSD Arduino Module1
U$11-U$26
Датчик токаSCT-013-03016
РезисторыR1, R5, R14-R47
Резистор10 кОм34
0805КонденсаторыC1
Электролитический конденсатор470 мкФ x 25 В1
C6
Электролитический конденсатор10 мкФ x 16 В1
C2, C3, C8, C9, C11, C14, C15, C21
Конденсатор100 нФ8
0805C24-C39
Электролитический конденсатор10 мкФ х 10 В16
С10
Конденсатор10 нФ1
0805С12,С13
Конденсатор22 пФ2
0805ДругоеL1
Катушка индуктивности10 мкГн1
0805Q1
Кварцевый резонатор16 МГц1
JP1, JP2
Штыревой разъем PLS1х101
Добавить все

Скачать список элементов (PDF)

Прикрепленные файлы:

Добавить комментарий

Ваш адрес email не будет опубликован.