вольтметр переменного напряжения
- Войдите на сайт для отправки комментариев
Решил поделится весьма скудными знаниями по созданию вольтметра переменного напряжения от 1 до 300 вольт. Заранее хочу выразить благодарность всем кто помог мне в этом нелегком деле!
материалы которые помогли разобратся привожу в виде ссылок:
1http://samou4ka.net/page/tajmer-schetchik-mikrokontrollerov-avr //тема о настройке таймеров
2http://samou4ka.net/page/analogo-cifrovoj-preobrazovatel-mk-atmega8 // настройка АЦП
3http://www.stepwood.com/avrsuite/2012/10/03/atmega-rezhimyi-rabotyi-taymerov/ еще немного о таймерах
4http://arv.radioliga.com/content/view/107/43/ про измерение напряжения теория
5http://cxem.net/izmer/izmer90.php полезная ссылочка
В этих статьях описанно как использовать Таймеры и АЦП управляя ими напрямую!
Итак сам вольтметр состоит из Аналоговой и цифровой части, В качестве АЦП применен встроенный преобразователь с разрешением в 10 бит микроконтроллера ATmega328 установленный на плату Arduino MIni. Я использовал на нем 2 аналоговых входа из восьми возможных!
Аналоговая часть состоит из2 ОУ схема достаточно простая и взята она от китайского стабилизатора, на 1 ОУ реализуется уменьшение 5 вольтового напряжения до уровня 2 вольта, второй через делитель напряжения получает входную величину и поднимает ее относительно 2 вольт. Очень важно выбрать хороший ОУ я применил MCP617 (По непонятной причине LM324 совершенно не подходит для этой схемы появляется двоение синусоиды) И желательно использовать сопративление ряда Е96 +-1%. Переменный резистор многооборотный 10кОм.

Можно было использовать схему приведенную ниже, но если захотите контролировать несколько фаз эта схема не пойдет и проблемы с ней обсуждались в этой теме.http://arduino.ru/forum/apparatnye-voprosy/kak-borotsya-s-dannym-yavleniem , некоторые люди пишут что применение диодов вносит погрешности при измерении чесно говоря я их не заметил, но это не говорит о том что их нет.

Цифровая часть написана в ArduinoIDE. Алгоритм измерения следеюший каждые 0.3 секунды запускается таймер с частотой прерывания 5000Гц(использован 8 битный таймер 2) следовательно если брать частоту 50 Гц то период равен 20мсек, за данный период в обработчике прерывания сохраняем 100 выборок напряжения в массив. после того как программа собрала 100 значений запрещаем прерывание и расчет переходит в основном цикле LOOP/
В цикле for происходит математические вычисления(вычитание подъема синусоиды на 2 вольта, возведение в квадрат и сложение этих значений) согласно вот этой теории :
по завершению цикла происходит вычисление реальной величины напряжения.
Переменная coef расчитывается с учетом напряжения делителя , я ее подбирал эксперементально, сравнивал измеренную величину и подаваемую источником переменного тока, и эту разницу выравнивал коэфициентом!
значение я выводил по сериал соединению!
* переменные работающие в обработчике прерывания */
volatile int Umass_A[101]; //масив переменных для хранения мгновенных напряжений фазы А
int Ucor = 0;
long Uism_A = 0; // переменная для хранения измеренного напряжения и квадрата фазы А
long Usumm_A = 0; // переменная для хранения сумм квадратов фазы А
volatile byte counter = 0; // счетчик в обработчике прерывания
int ADC0 = 0; // аналоговый вход 0 для переменной Ucor!
int ADC1 = 1;
#define ADC1 1
volatile byte flag = 0;
/* переменные для усреднения напряжений*/
float sqrtUsum_A = 0;
int real_U_A = 0;
float coff = 0.138;
unsigned long timeOut = 0;// переменная для хранения времени!!!
//массивы
// кнопки и светодиоды
void setup()
{
TIMSK2 = 0b00000000; // запрещение прерывания по совпадению таймера/счетчика Т2
TCCR2A = 0b00000010; // режим работы СТС
TCCR2B = 0b00000011; // предделитель на 32
ASSR &= ~(1<<AS2); // Выбор источника синхронизации таймера(от системного генератора
OCR2A = 98;
// срабатывание таймера 16000000/32/100=5000 раз в секунду 100 раз за секунду
ADMUX = (0<<REFS1)|(1<<REFS0)|(0<<ADLAR)|(0<<MUX3)|(0<<MUX2)|(0<<MUX1)|(0<<MUX0);
ADCSRA = 0b10000111;
Serial.begin(9600);
}
void loop()
{
if(millis()-timeOut > 400)
{
timeOut = millis();
metod();
}
if(flag == 3)
{
for ( int i = 1; i<101; i++)
{
Uism_A = Umass_A [i];
Uism_A -= Ucor;// убираем подьем синусоиды на 2 вольт
Uism_A *= Uism_A;
Usumm_A += Uism_A;
}
sqrtUsum_A = sqrt(Usumm_A); //вычисляем квадратный корень из суммы квадратов
real_U_A = coff * sqrtUsum_A; //вычисляем реальное напряжение для фазы А
Usumm_A = 0;
counter = 0;
flag = 0;
Serial.println(real_U_A);
}
}
void metod()
{
Ucor = analogRead(ADC0);
TIMSK2 |= (1<<OCIE2A); // разрешаем прерывание
while(flag<3);
TIMSK2 = 0b00000000; // останавливаем таймер
}
word read_adc(byte adc_input)
{
ADMUX = adc_input | (ADMUX & 0xF0);
//задержка для стабилизации входного напряжения
//начинаем преобразование (ADSC = 1)
ADCSRA |= (1<<ADSC);
while((ADCSRA & 0x10)==0); //ждем, пока АЦП закончит преобразование (ADIF = 0)
ADCSRA|=0x10;//устанавливаем ADIF
return ADCW;//ADCW - содержит ADCH и ADCL как нам нужно
}
//****************обработчик прерывания********************
ISR(TIMER2_COMPA_vect)
{
counter++;
if((counter<= 100)&&flag == 0)
{
Umass_A[counter] = read_adc(ADC1);
if(counter == 100)
{
flag = 3;
counter = 1;
}
}
}
вообщем получилось достаточно неплохо точность около 1%
Вот в принципе и все!
Интерес к измерениям переменного тока есть. Но,хотелось бы замерять значения не самим ацп Ардуино ,а внешним ацп . Например
в щитке ввода в дом ,а затем данные передавать в Ардуино. Тогда возникает вопрос как делать выборки.
И ещё ,почему для приведения к уровню вместо делителя не использовать трансформатор с выпрямителем. Всё таки развязка.
. Тогда возникает вопрос как делать выборки.
А что вам мешает в обработчике прерывания опрашивать не собственный АЦП, а внешний АЦП который будет подключен по SPI? Я думаю все вполне реализуемо.
А вот по поводу гальванической развязки это хороший вопрос!!!
http://www.microchip.su/showthread.php?t=2788&highlight=%C8%E7%EC%E5%F0%E5%ED%E8%E5+%EF%E5%F0%E5%EC%E5%ED%ED%EE%E3%EE
вот на этом форуме очень оживленно обсуждали данный вопрос, Один утверждал что смог сделать трансформатор из гайки М8 и первичную обмотку подключил через конденсатор! и это дало неплохие результаты, Но лично мои эксперементы с трансформатором дают странные рзультаты, изменяя напряжение с помощью ЛАТРа я заметил некоторую нелинейность т.е подаю с латра 100 вольт мой вольтметр показывает 110, увеличиваю до 200 показывает 197, и я таки не разобрался в чем было дело!
И ещё ,почему для приведения к уровню вместо делителя не использовать трансформатор с выпрямителем. Всё таки развязка.
а чем вам плечо в 3МОм не развязка... да и трасформаторы это увеличение размера устройства. А в таком смд исполнении датчик напряжения на ОУ ~2-3см2 на плате.
И ещё ,почему для приведения к уровню вместо делителя не использовать трансформатор с выпрямителем. Всё таки развязка.
а чем вам плечо в 3МОм не развязка... да и трасформаторы это увеличение размера устройства. А в таком смд исполнении датчик напряжения на ОУ ~2-3см2 на плате.
У меня даже меньше получилоссь, если еще и оу взять смд!
Хорошая статья о измерении переменного напряжения http://512volt.ru/s2.htm
Да статья то хорошая но есть одно но, цена LTC1968IMS8 примерно 500 рублей, а если захотим измерять 3 фазы это уже 1500 не считая остальной мелочевки и контроллера!
Лайнер обычно не дешев. Не полторы, конечно, тыщи (у китайцев за 850 руб можно пять штук взять и это включая стоимость доставки), но все равно ощутимо. Можно погуглить аналоги по сочетанию rms-to-dc -- там много чего в выдаче найти можно.
полторы тысячи это я посчитал для 3 фаз!! гуглил по теме самый дешевый лайнер без учета доставки стоит 8.9$ ну это по первой попавшейся ссылке.
Это в принципе не дорого!!
DimaP, спасибо за труды. Однако, у меня схемка вроде как не рабочая. По крайней мере, кручу я подстроечный резистор на 10К - и на DSO138 (типа осцилограф сделай сам) не наблюдаю никаких изменений. Вообще никаких. Замер делал между ADC1 и ADC0, и между ADC1 и GND и между ADC0 и GND. Может еще что-то забыли указать? Применил тоже, как и увас MCP617 - я так понял это два ОУ в одном корпусе.
DimaP, забыл я просто подать питание на ОУ, вот же... Теперь все заработало, еще раз спасибо! Единственное не могу понять, как лучше переделать схему под 3 фазы. Ведь получается, что ноль то общий.
возможно вам поможет, коментировал что мог по максимуму, измерение 3 фаз, + реакция при выходе из установленных пределов...
схема устройства не сохранилась, только микропрограмма. Наверное если сесть и вниматильно изуть код то можно понять как реализованна аппаратная часть, точно знаю что железная часть для считывание фаз напряженийреализованна с помощью картинки из поста №1 картинка 1...
#include <EEPROM.h> #include <TimeHelpers.h> //библиотека для работы со временем #include <LiquidCrystal_I2C.h> // Подключаем стандартную библиотеку LiquidCrystal #include <Wire.h> // библиотека дря работы I2C #include <DS1307.h> //Подключение библиотеки для DS1307 LiquidCrystal_I2C lcd(0x27,16,2); // set the LCD address to 0x27 for a 16 chars and 2 line display inline word read_adc(byte adc_input); //прототипы функций int matimatics(long Usumm, double coff); void EEPROMWriteInt(int p_address, word p_value); word EEPROMReadInt(int p_address); /* переменные работающие в обработчике прерывания */ volatile long Uism_L1; // измеренное напряжение для фазы 1 volatile long Usumm_L1; // суммированное напряжение для фазы 1 volatile long Uism_L2; // измеренное напряжение для фазы 2 volatile long Usumm_L2;// суммированное напряжение для фазы 2 volatile long Uism_L3; // измеренное напряжение для фазы 3 volatile long Usumm_L3;// суммированное напряжение для фазы 3 volatile byte flag = 0; // флаг для обрабтчика прерывания volatile byte counter = 0; // счетчик в обработчике прерывания //каналы для чтения функцией АЦП #define ADC1 0 #define ADC2 1 #define ADC3 2 #define ADC4 A3 #define ADC5 A6 #define ADC6 A7 //здесь задаютя значениявижержки времени для работы "TimeHelpers" #define TIME_OUT_INTERVAL 350UL//интервал измерения фаз напряжений #define TIME _SEC_ (1) //время равное 1 секунде #define TIME_1 300UL // выдержка 300 мсек #define TIME_SEVED 10000UL // выдержка 10 сек #define BLINK_INTERVAL 500UL // для мигалки int Ucor_L1 = 0; /*напряжения подъема синусоиды на 2 вольта*/ int Ucor_L2 = 0; /*напряжения подъема синусоиды на 2 вольта*/ int Ucor_L3 = 0; /*напряжения подъема синусоиды на 2 вольта*/ //переменные конечного значения напряжения по фазам 1. 2. 3 int real_U_L1 = 0; int real_U_L2 = 0; int real_U_L3 = 0; //коэффициенты для расчетов среднеквадратического значения напряжения double coff_L1 = 0; double coff_L2 = 0; double coff_L3 = 0; // переменные для работы с eeprom word ee_readL1 = 1400; word ee_readL2 = 1400; word ee_readL3 = 1400; //массивы DATA сохраненных значений byte mass_MIN[10]; byte mass_HR[10]; byte mass_DATE[10]; byte mass_MTH[10]; byte store_Fider_[10]; int store_U_L1[10]; int store_U_L2[10]; int store_U_L3[10]; //работа с lcd char stroka[16]; // кнопки и светодиоды const byte buttEnter = 13;// кнопка интер const byte buttDown = 12; // кнопка для прокрутки вниз const byte buttUp = 11;// кнопка для прокрутки вверх const byte light = 10;//кнопка подсветки const byte fider = 7;//индикация фидеров #define ledRed 9 //светодиод наличия данных в памяти #define ledGreen 8 // светодиод нормальной работы /*разного рода переменные*/ //boolean waitTime = false; // выдержка времени для того чтобы не сыпалось много значений boolean flag_red_stop = false;// данный флаг нужен для запрета горящего класного светодиода когда идет выжержка времени boolean downFlag = false; //flag кнопки интер boolean delFlag = false; //flag кнопки вниз boolean upFlag = false; //flag кнопки вверх boolean off_light_flag = false; boolean set_flag = false; boolean ee_write_flag = false; boolean flag_mig = false; boolean tti = false; byte value_delite = 0; //для выдержки времени при удалении byte timeSet = 0;// время для выключения реле byte off_light = 0;// выдержка на выключение подсветки LCD byte massVal = 0; // переменная для сохранения данных и определения индекса массивов byte scroll = 1; //переменная для прокручивания результатов byte value_switch = 0;//ДЛЯ ВЫДЕРЖКИ ВРЕМЕНИ НА ПОДАЧУ КОМАНДЫ ПЕРЕКЛЮЧИТЬ ФИДЕР byte Timer123 = 0; byte stateFider = 0; byte inter_sel = 0; byte inter_ON = 0; byte set_regim = 0;// для настройки режимов int enterRegim = 1; // переменная режима кнопки enter int Uset = 0; //напряжение в режиме setings word set_coef = 1400;//переменная для настройки коэффициента #define dell 10000UL //константа для деления напрядения и получения коэффициента //значения превышенного и заниженного напряжения #define Hi 242 #define Low 198 void setup() { pinMode(buttEnter, INPUT); pinMode(buttDown, INPUT); digitalWrite(buttDown, HIGH); pinMode(buttUp, INPUT); digitalWrite(buttUp, HIGH); pinMode(light, INPUT); digitalWrite(light, HIGH); pinMode(fider, INPUT); digitalWrite(fider, HIGH); pinMode(ledRed, OUTPUT); pinMode(ledGreen, OUTPUT); TIMSK2 = 0b00000000; // запрещение прерывания по совпадению таймера/счетчика Т2 TCCR2A = 0b00000010; // режим работы СТС TCCR2B = 0b00000011; // предделитель на 32 ASSR &= ~(1<<AS2); // Выбор источника синхронизации таймера(от системного генератора OCR2A = 99; // срабатывание таймера 16000000/32/100=5000 раз в секунду 100 раз за секунду ADMUX = (0<<REFS1)|(1<<REFS0)|(0<<ADLAR)|(0<<MUX3)|(0<<MUX2)|(0<<MUX1)|(0<<MUX0); ADCSRA = 0b10000111; lcd.init(); ee_readL1 = EEPROMReadInt(6);//переменные читаемые занимают 2 байта coff_L1 = float(ee_readL1)/dell;// записываемс коеф для 1 фазы ee_readL2 = EEPROMReadInt(8);// поэтому здесь читаем с 2 байта coff_L2 = float(ee_readL2)/dell;// записываемс коеф для 2 фазы ee_readL3 = EEPROMReadInt(10); // читаем из еепрома коэффициент coff_L3 = float(ee_readL3)/dell;// записываемс коеф для 3 фазы // Serial.begin(9600); } /*+++++++++++++++++++++++++++++++++++++++++*/ /* EEPROMWriteInt(0, 65535); //занимаються ячейки 0 и 1 // 65535 максмальное значение которое можно записать в 2 байта Serial.print("Read the following int at the eeprom address 0: "); Serial.println(EEPROMReadInt(0)); */ // функция для записи/ чтения памяти EEPROM //кушаем аж 2 байта EEPROM void EEPROMWriteInt(int p_address, word p_value) { byte lowByte = ((p_value >> 0) & 0xFF); byte highByte = ((p_value >> 8) & 0xFF); EEPROM.write(p_address, lowByte); EEPROM.write(p_address + 1, highByte); ee_write_flag = false; } // считаем нашы два байта word EEPROMReadInt(int p_address) { byte lowByte = EEPROM.read(p_address); byte highByte = EEPROM.read(p_address + 1); return ((lowByte << 0) & 0xFF) + ((highByte << 8) & 0xFF00); } void loop() { DO_EVERY(TIME_OUT_INTERVAL,{ Ucor_L1=analogRead(ADC4); Ucor_L2=analogRead(ADC5); Ucor_L3=analogRead(ADC6); TIMSK2 |= (1<<OCIE2A); }) // разрешаем прерывание через каждые 300 мСек и попутно читаем напряжение подъема синусоиды /*--------------------------------------------------------------------*/ if(flag == 3)// этоот флаг устанавливается в обработчике прерывания разрешая тем самым расчет реальныз величин U { real_U_L1 = matimatics(Usumm_L1, coff_L1);//расчет реальныз величин U1 real_U_L2 = matimatics(Usumm_L2, coff_L2);//расчет реальныз величин U2 real_U_L3 = matimatics(Usumm_L3, coff_L3);//расчет реальныз величин U3 counter = 0;//сбрасываем счетчик flag = 0;//сбрасываем флаг /* сбрасываем суммы напряжений*/ Usumm_L1 = 0; Usumm_L2 = 0; Usumm_L3 = 0; } /*-----------------------------------------------------------------------*/ //здесь мы прроверяем последовательно каждую фазу на предмет завышения или занижения напряжения if((real_U_L1 >= Hi || real_U_L1 <= Low) || (real_U_L2>= Hi || real_U_L2 <= Low) || (real_U_L3 >= Hi || real_U_L3 <= Low )) { digitalWrite(ledGreen, LOW); if(tti == false) { flag_red_stop = true; massVal++; mass_safe(); tti = true; if(massVal>10){massVal = 1;} } } /*--------------------------------------------------------------------*/ if(massVal >= 1 && flag_red_stop == false) { digitalWrite(ledRed, HIGH); } else if (massVal == 0) { digitalWrite(ledRed, LOW); digitalWrite(ledGreen, HIGH); } /*--------------------------------------------------------------------*/ /*--------------------------------------------------------------------*/ if (off_light_flag == true) { DO_EVERY(TIME, off_light++;) } if(off_light >= 20) { off_light_flag = false; lcd.noBacklight(); off_light = 0; } if(flag_red_stop == true) { LED_mig(); } /*--------------------------------------------------------------------*/ button_prog(); ee_write_flag = false; } void button_prog(void)//Работаем с кнопками и режимами управления, а также включением и выключением подсветки LCD { if(digitalRead(buttEnter) == LOW) //если нажата кнопка интер и не нажата кнопка прокрутки { DO_EVERY(TIME, inter_sel++;) DO_EVERY(TIME_1, inter_ON++;) } if(digitalRead(buttEnter) == HIGH) { inter_sel = 0; inter_ON = 0; } if(inter_ON >= 1){ee_write_flag = true; inter_ON =0; lcd.clear(); } if(inter_sel >= 2) { inter_sel = 0; enterRegim++; //увеличиваем переменную режимов на 1 lcd.clear(); if(enterRegim > 2){ enterRegim = 1; }//если больше 2 режимов переходим к первому } if(digitalRead(buttDown) == LOW && digitalRead(buttUp) == LOW) { DO_EVERY(TIME, timeSet++;) } else {timeSet = 0;} if(timeSet >= 5) { timeSet = 0; enterRegim = 0; } switch(enterRegim) { case -1: delite(); break; case 0: setings(); break; case 1: standBy(); break; case 2: data_save(); flag_red_stop = false; break; } if(digitalRead(light) == LOW) { lcd.backlight(); off_light_flag = true; DO_EVERY(TIME, value_delite++;) } else { value_delite = 0; } if(value_delite >=5) { enterRegim = -1; lcd.clear(); value_delite = 0; } switch(digitalRead(fider)){ case 0 : stateFider = 1; break; case 1 : stateFider = 2; break; } // выдержка времени для ожидания между сохранениями результатов if(tti == true) { DO_EVERY(TIME_SEVED, Timer123++;) //Serial.println(Timer123); } if(Timer123 >= 6) { tti = false; Timer123 = 0; } } /*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=*/ void delite (void) { if(digitalRead(buttDown) == LOW && downFlag == false)//нажали кнопку вниз { downFlag = true; delFlag = ~delFlag; lcd.clear(); } if(digitalRead(buttDown) == HIGH && downFlag == true){downFlag = false;}//отпустили кнопку вниз if(digitalRead(buttUp) == LOW && upFlag == false)//нажали кнопку вверх { upFlag = true; delFlag = ~delFlag; lcd.clear(); } if(digitalRead(buttUp) == HIGH && upFlag == true){upFlag = false;}//отпустили кнопку вверх lcd.setCursor(0, 0); lcd.print("Delete DATA Yes"); lcd.setCursor(0, 1); lcd.print(" No "); if(delFlag==false) { blink_simbol(0); if(ee_write_flag == true) { massVal = 0; value_delite = 0; scroll = 1; tti = false; enterRegim = 1; ee_write_flag = false; flag_red_stop = false; } } else{ blink_simbol(1); if(ee_write_flag == true) { enterRegim = 1; ee_write_flag = false; } } } void blink_simbol(byte x) { lcd.setCursor(15, x); DO_EVERY(BLINK_INTERVAL, flag_mig = ~flag_mig;) if(flag_mig == false){lcd.print("<");} else { lcd.print(" ");} } /*==============================================================================*/ void mass_safe(void)//сохраняем время и напряжение в масссив, пока сохраняем просто в RAM в дальнейшем будет во флеш { mass_MIN[massVal] = RTC.get(DS1307_MIN,false); mass_HR[massVal] = RTC.get(DS1307_HR,true); mass_DATE[massVal] = RTC.get(DS1307_DATE,false); mass_MTH[massVal] = RTC.get(DS1307_MTH,false); store_Fider_[massVal] = stateFider; store_U_L1[massVal] = real_U_L1; store_U_L2[massVal] = real_U_L2; store_U_L3[massVal] = real_U_L3; } /*функция вычисления напряжения*/ int matimatics(long Usumm, double coff) { int real_U_int; double real_U; double sqrtUsum; sqrtUsum = sqrt(Usumm); //вычисляем квадратный корень из суммы квадратов real_U = coff * sqrtUsum; //вычисляем реальное напряжение для фазы real_U_int = int(real_U); return real_U_int; } /*==============================================================================*/ void LED_mig(void) { DO_EVERY(BLINK_INTERVAL,digitalWrite(ledRed,!digitalRead(ledRed))); } /*==============================================================================*/ void standBy(void) { lcd.setCursor(0,0); sprintf (stroka, "%02d.%02d.%4d/%02d:%02d", RTC.get(DS1307_DATE,false), RTC.get(DS1307_MTH,false), RTC.get(DS1307_YR,false), RTC.get(DS1307_HR,true), RTC.get(DS1307_MIN,false)); lcd.print(stroka); /*выводим состояния напряжений пофазно*/ // печатаем вторую строку lcd.setCursor(0, 1); sprintf (stroka, "%3d|%3d|%3d|%1d%3s", real_U_L1, real_U_L2, real_U_L3 , stateFider, "Fid"); lcd.print(stroka); } void data_save(void) { if(digitalRead(buttDown) == LOW && downFlag == false) { downFlag = true; scroll++; lcd.clear(); if(scroll > massVal) { scroll = 1; } } if(digitalRead(buttDown) == HIGH && downFlag == true) { downFlag = false; } if(digitalRead(buttUp) == LOW && upFlag == false) { upFlag = true; scroll--; lcd.clear(); if(scroll < 1) { scroll = massVal; } } if(digitalRead(buttUp) == HIGH && upFlag == true) { upFlag = false; } if(massVal >= 1) { lcd.setCursor(0, 0); sprintf(stroka, "%02d. %02d.%02d %02d:%02d", scroll, mass_DATE[scroll], mass_MTH[scroll], mass_HR[scroll], mass_MIN[scroll]); lcd.print(stroka); lcd.setCursor(0, 1); sprintf(stroka, "%3d|%3d|%3d|%1d%3s", store_U_L1[scroll], store_U_L2[scroll], store_U_L3[scroll], store_Fider_[scroll], "Fid"); lcd.print(stroka); } else { lcd.setCursor(0, 0); lcd.print("No DATA,to press"); lcd.setCursor(0, 1); lcd.print( "the button ENTER"); } } /*__________________________________________*/ void setings (void) { if(digitalRead(buttDown) == LOW && downFlag == false)//нажали кнопку вниз { downFlag = true; set_coef--; } if(digitalRead(buttDown) == HIGH && downFlag == true)//отпустили кнопку вниз { downFlag = false; } if(digitalRead(buttUp) == LOW && upFlag == false)//нажали кнопку вверх { upFlag = true; set_coef++; } if(digitalRead(buttUp) == HIGH && upFlag == true)//отпустили кнопку вверх { upFlag = false; } if(digitalRead(light) == LOW&&set_flag==false) { set_regim++; set_flag=true; lcd.clear(); if(set_regim > 3) { enterRegim = 1; set_regim = 0; } } if(digitalRead(light) == HIGH && set_flag == true){set_flag = false;} switch(set_regim) { case 1: coff_L1 = float(set_coef)/dell; Uset = real_U_L1; if(ee_write_flag == true) { EEPROMWriteInt(6, set_coef); //занимаються ячейки 0 и 1 // 65535 максмальное значение которое можно записать в 2 байта } break; case 2: if(ee_write_flag == true) { EEPROMWriteInt(8, set_coef); //занимаються ячейки 0 и 1 // 65535 максмальное значение которое можно записать в 2 байта } coff_L2 = float(set_coef)/dell; Uset = real_U_L2; break; case 3: if(ee_write_flag == true) { EEPROMWriteInt(10, set_coef); //занимаються ячейки 0 и 1 // 65535 максмальное значение которое можно записать в 2 байта } coff_L3 = float(set_coef)/dell; Uset = real_U_L3; break; } lcd.setCursor(0, 0); lcd.print(" SETINGS "); lcd.setCursor(0, 1); sprintf(stroka, "%1s%1d=%03d %3s=%4d", "L", set_regim, Uset, "cof", set_coef); lcd.print( stroka); } inline word read_adc(byte adc_input) { ADMUX = adc_input | (ADMUX & 0xF0); delayMicroseconds(10);//задержка для стабилизации входного напряжения ADCSRA |= (1<<ADSC);//начинаем преобразование (ADSC = 1) while((ADCSRA & 0x10)==0); //ждем, пока АЦП закончит преобразование (ADIF = 0) ADCSRA|=0x10;//устанавливаем ADIF return ADCW;//ADCW - содержит ADCH и ADCL как нам нужно } //****************обработчик прерывания******************** ISR(TIMER2_COMPA_vect) { counter++; if((counter<= 100)&&flag == 0) { Uism_L1 = read_adc(ADC1); Uism_L1 -= Ucor_L1;// убираем подьем синусоиды на 2 вольт if(Uism_L1 > -5 && Uism_L1 < 5){Uism_L1=0;} Uism_L1 *= Uism_L1;// возводим значение в квадрат Usumm_L1 += Uism_L1;// склдываем квадраты измерений if(counter == 100) { flag = 1; counter = 1; } } if((counter<=100)&&flag == 1) { Uism_L2 = read_adc(ADC2); Uism_L2 -= Ucor_L2; // убираем подьем синусоиды на 2 вольт if(Uism_L2 > -5 && Uism_L2 < 5){Uism_L2=0;} Uism_L2 *= Uism_L2;// возводим значение в квадрат Usumm_L2 += Uism_L2; // склдываем квадраты измерений if(counter == 100) { flag = 2; counter = 1; } } if((counter<=100)&&flag ==2) { Uism_L3 = read_adc(ADC3); Uism_L3 -= Ucor_L3; // убираем подьем синусоиды на 2 вольт if(Uism_L3 > -5 && Uism_L3 < 5){Uism_L3=0;} Uism_L3 *= Uism_L3;// возводим значение в квадрат Usumm_L3 += Uism_L3; // склдываем квадраты измерений if(counter == 100) { flag = 3; TIMSK2 &= ~(1<<OCIE2A); // останавливаем таймер } } }Библиотеки использованные в коде, взяты с данного форума, с помощью поиска их легко найти.
вот еще несколько картинок печатные платы ну и передняя панель прибора ))
спустя 3 года XD
Спасибо, за схему и за код. Вижу что программа использует 6 аналоговых выходов для измерения (по 2 на фазу). Хочу переделать на 4 выхода. 3 фазы и 1 ноль. Вот не могу понять, зачем сигнал 2В (с делителя напряжения) повторяется через ОУ? Чем вызвана такая необходимость. Я знаю, что сигнал повторяют через ОУ, если надо на него подцепить нагрузку. Если кто-то пояснит, толково и незаумно, был бы рад.
А от чего Ардуино питается? И, надеюсь, с компьютером не соединяется?
Нельзя без трансформаторов или оптических развязок сетевые напряжения куда-либо пихать.
Хорошая статья о измерении переменного напряжения http://512volt.ru/s2.htm
Что за спам, конкретную ссылку дайте!!!
Подскажите, а что за номинал резистора стоит в самой первой схеме слева, обозначенный W3 LB-O?
Собрал первую предлагаему схему, залил код. Все работает отлично, кроме одного момента, связанного со скачками напряжения в пределах 2 вольт. То есть, напирмер, в сети 220В, а показания Arduino меняются от 219 до 221. Это нормальная работа или все же виной тому помехи, связанные с работой схемы?
Добрый день, можна ли переделать код под таймер1 ? (просто уже таймер2 занят функцией вывода звука tone(). )
гальваническая развязка обсуждалась в жж сообществе "рожденный с паяльником". там предложили использовать китайский трасформатор тока (стоит копейки) включенный через через токограничиващий резистор в 220в так чтоб ток был 60-90% от максимума. дальше математика.
вот нашел схему с пояснениями - http://easyelectronics.ru/zamer-setevogo-napryazheniya.html
Спасибо за публикацию, повторил Вашу разработку с выводом напряжения на не дорогой индикатор ТМ1637 за 48руб. https://aliexpress.ru/item/4000587257525.html
/*Подключаем библиотеку*/ #include "TM1637.h" /*Определяем пины подключения*/ #define CLK 3 #define DIO 2 TM1637 tm1637(CLK, DIO); /* переменные работающие в обработчике прерывания */ volatile int Umass_A[101]; //масив переменных для хранения мгновенных напряжений фазы А int Ucor = 0; long Uism_A = 0; // переменная для хранения измеренного напряжения и квадрата фазы А long Usumm_A = 0; // переменная для хранения сумм квадратов фазы А volatile byte counter = 0; // счетчик в обработчике прерывания int ADC0 = 0; // аналоговый вход 0 для переменной Ucor! int ADC1 = 1; #define ADC1 1 volatile byte flag = 0; /* переменные для усреднения напряжений*/ float sqrtUsum_A = 0; int real_U_A = 0; float coff = 0.138; unsigned long timeOut = 0;// переменная для хранения времени!!! void setup() { /*Инициализация дисплея*/ tm1637.init(); tm1637.set(BRIGHT_TYPICAL); TIMSK2 = 0b00000000; // запрещение прерывания по совпадению таймера/счетчика Т2 TCCR2A = 0b00000010; // режим работы СТС TCCR2B = 0b00000011; // предделитель на 32 ASSR &= ~(1<<AS2); // Выбор источника синхронизации таймера(от системного генератора OCR2A = 98; // срабатывание таймера 16000000/32/100=5000 раз в секунду 100 раз за секунду ADMUX = (0<<REFS1)|(1<<REFS0)|(0<<ADLAR)|(0<<MUX3)|(0<<MUX2)|(0<<MUX1)|(0<<MUX0); ADCSRA = 0b10000111; Serial.begin(9600); } void loop() { if(millis()-timeOut > 400) { timeOut = millis(); metod(); } if(flag == 3) { for ( int i = 1; i<101; i++) { Uism_A = Umass_A [i]; Uism_A -= Ucor;// убираем подьем синусоиды на 2 вольт Uism_A *= Uism_A; Usumm_A += Uism_A; } sqrtUsum_A = sqrt(Usumm_A); //вычисляем квадратный корень из суммы квадратов real_U_A = coff * sqrtUsum_A; //вычисляем реальное напряжение для фазы А Usumm_A = 0; counter = 0; flag = 0; Serial.println(real_U_A); /*Вывод чисел массива на дисплей*/ int digitV = real_U_A/1000; // После деления на 1000 остаётся разряд тысяч. int digitoneV = real_U_A/100%10; // После деления на 100 остаётся разряд сотен. int digittwoV = real_U_A/10%10; // Выделяем разряд. десятков. int digitfriV = real_U_A%10; // Выделяем разряд единиц. //Выводим массив на дисплей tm1637.display(0, digitV); tm1637.display(1, digitoneV); tm1637.display(2, digittwoV); tm1637.display(3, digitfriV); delay(1000); } } void metod() { Ucor = analogRead(ADC0); TIMSK2 |= (1<<OCIE2A); // разрешаем прерывание while(flag<3); TIMSK2 = 0b00000000; // останавливаем таймер } word read_adc(byte adc_input) { ADMUX = adc_input | (ADMUX & 0xF0);//задержка для стабилизации входного напряжения //начинаем преобразование (ADSC = 1) ADCSRA |= (1<<ADSC); while((ADCSRA & 0x10)==0); //ждем, пока АЦП закончит преобразование (ADIF = 0) ADCSRA|=0x10;//устанавливаем ADIF return ADCW;//ADCW - содержит ADCH и ADCL как нам нужно } //****************обработчик прерывания******************** ISR(TIMER2_COMPA_vect) { counter++; if((counter<= 100)&&flag == 0) { Umass_A[counter] = read_adc(ADC1); if(counter == 100) { flag = 3; counter = 1; } } }Доброго времени суток, может кто-то подсказать как организовать цикл в представленной разработке, чтоб выводилось среднее из допустим 10 значений напряжений? (да я глуп и у меня не получается, да только начал изучать этот огромный пласт и схемотехнику, мои жалкие попытки приводили к тому что в протеусе в серийный порт выводятся только нули или значения без усреднения, ссылку на википедию с определением среднего арифметического оценю как высшее проявление юмора).
И огромное спасибо за схему с пояснениями и код.
мои жалкие попытки приводили к тому что в протеусе в серийный порт выводятся только нули или значения без усреднения
показывайте свои "жалкие попытки". иначе разговор не о чем
//* переменные работающие в обработчике прерывания */ volatile int Umass_A[101]; //масив переменных для хранения мгновенных напряжений фазы А int Ucor = 0; long Uism_A = 0; // переменная для хранения измеренного напряжения и квадрата фазы А long Usumm_A = 0; // переменная для хранения сумм квадратов фазы А volatile byte counter = 0; // счетчик в обработчике прерывания int ADC0 = 0; // аналоговый вход 0 для переменной Ucor! int ADC1 = 1; #define ADC1 1 volatile byte flag = 0; /* переменные для усреднения напряжений*/ float sqrtUsum_A = 0; float real_U_A = 0; float coff = 0.0138; float U; unsigned long timeOut = 0;// переменная для хранения времени!!! //массивы // кнопки и светодиоды void setup() { TIMSK2 = 0b00000000; // запрещение прерывания по совпадению таймера/счетчика Т2 TCCR2A = 0b00000010; // режим работы СТС TCCR2B = 0b00000011; // предделитель на 32 ASSR &= ~(1<<AS2); // Выбор источника синхронизации таймера(от системного генератора OCR2A = 98; // срабатывание таймера 16000000/32/100=5000 раз в секунду 100 раз за секунду ADMUX = (0<<REFS1)|(1<<REFS0)|(0<<ADLAR)|(0<<MUX3)|(0<<MUX2)|(0<<MUX1)|(0<<MUX0); ADCSRA = 0b10000111; Serial.begin(9600); } void metod() { Ucor = analogRead(ADC0); TIMSK2 |= (1<<OCIE2A); // разрешаем прерывание while(flag<3); TIMSK2 = 0b00000000; // останавливаем таймер } void loop() { for (int j=1;j<11;j++)//цикл для получения среднего арифметического { if(millis()-timeOut > 400) { timeOut = millis(); metod(); } if(flag == 3) { for ( int i = 1; i<101; i++) { Uism_A = Umass_A [i]; Uism_A -= Ucor;// убираем подьем синусоиды на 2 вольт Uism_A *= Uism_A; Usumm_A += Uism_A; } sqrtUsum_A = sqrt(Usumm_A); //вычисляем квадратный корень из суммы квадратов real_U_A = coff * sqrtUsum_A; //вычисляем реальное напряжение для фазы А U+= real_U_A; // сложение результатов десяти измерений Usumm_A = 0; counter = 0; flag = 0; } } U=U/10; // вычисление среднего арифметического Serial.println(U,3); U=0; } word read_adc(byte adc_input) { ADMUX = adc_input | (ADMUX & 0xF0); //задержка для стабилизации входного напряжения //начинаем преобразование (ADSC = 1) ADCSRA |= (1<<ADSC); while((ADCSRA & 0x10)==0); //ждем, пока АЦП закончит преобразование (ADIF = 0) ADCSRA|=0x10;//устанавливаем ADIF return ADCW;//ADCW - содержит ADCH и ADCL как нам нужно } //****************обработчик прерывания******************** ISR(TIMER2_COMPA_vect) { counter++; if((counter<= 100)&&flag == 0) { Umass_A[counter] = read_adc(ADC1); if(counter == 100) { flag = 3; counter = 1; } } }Как итог получаю одни нули.
Вот тут нужно почитать:
http://www.psychiatry.ru/siteconst/userfiles/file/PDF/2606/Karpov.pdf
По шкале от ноля до Задорнова поставил бы Петросяна.
что-то слишком накручено... какой это хотя бы МК?
Arduino uno. Я "нагло позаимствовал" код автора этого проекта, схему повторил в протеусе и экспериментировал, в первоначальном виде в серийный порт выдаются значения с погрешностью +- 1,5 %, мне скорость измерения не важна, одного значения на секунду будет более чем достаточно, поэтому захотел усреднить полученные значения, но в виду того что в коде используется пока сложные для меня прерывания таймера добиться результата я не могу. Мне кажется ошибка в том что я для вычисления среднего беру не измеренные значения и обрабатываю, а беру из функций, но это не точно.
Для того, чтобы не возникало таких подозрений, код надо не "заимствовать", а писать самостоятельно. Да и Протеус в случае имитации случайных процессов - плохой помощник.
В общем, пишите код сами и проверяйте его на реальном "железе".