Перенос математических преобразований из ISR в LOOP
- Войдите на сайт для отправки комментариев
Втр, 16/07/2013 - 20:36
Здравствуйте прошу помощи!
подскажите как можно математические функции, в частности возведение в квадрат (строка 167) и суммирование квадратов перенести из обработчика прерывания в loop!
Это нужно для того, чтобы повысить точность измерения такой длинный код в ISR выполняется не коректно((
// Подключаем стандартную библиотеку LiquidCrystal #include <LiquidCrystal.h> #include <Wire.h> #include <DS1307.h> //Подключение библиотеки для DS1307 // Инициализируем объект-экран, передаём использованные // для подключения контакты на Arduino в порядке: // RS, E, DB5, DB6, DB7, DB8 LiquidCrystal lcd(4, 5, 10, 11, 12, 13); /* переменные работающие в обработчике прерывания */ volatile long Uism_A = 0; // переменная для хранения измеренного напряжения и квадрата фазы А volatile long Usumm_A = 0; // переменная для хранения сумм квадратов фазы А volatile long Uism_B = 0; // ------В----- volatile long Usumm_B = 0;//------В------ volatile long Uism_C = 0; // -------С----- volatile long Usumm_C = 0;// --------С------ volatile int cntr = 0; // счетчик в обработчике прерывания /* подключаем аналоговые входы */ int ADC0 = 0; // аналоговый вход 0 для переменной Ucor! int ADC1 = 1; // вход измерения фазы А int ADC2 = 2; // вход измерения фазы B int ADC3 = 3; // вход измерения фазы С int Ucor = 0; // переменная будет хранить напряжение корректировки!!! /*переменные для расчета реальных величин!!*/ int real_U_A = 0; //переменная расчета рельной величины для фазы А float sqrtUsum_A = 0.0; //переменная расчете квадратного корня для фазы A int real_U_B = 0; //переменная расчета рельной величины для фазы В float sqrtUsum_B = 0.0; //переменная расчете квадратного корня для фазы В int real_U_C = 0; //переменная расчета рельной величины для фазы С float sqrtUsum_C = 0.0; //переменная расчете квадратного корня для фазы С unsigned long timeOut = 0;// переменная для хранения времени!!! unsigned long time = 0; void setup() { TIMSK2 = 0b00000000; // запрещение прерывания по совпадению таймера/счетчика Т2 TCCR2A = 0b00000100; // режим работы СТС TCCR2B = 0b00000101; // предделитель на 32 ASSR &= ~(1<<AS2); // Выбор источника синхронизации таймера(от системного генератора OCR2A = 100; // срабатывание таймера 16000000/32/100=5000 раз в секунду 100 раз за секунду lcd.begin(16, 2); RTC.stop(); RTC.set(DS1307_SEC,1); //set the seconds RTC.set(DS1307_MIN,50); //set the minutes RTC.set(DS1307_HR,15); //set the hours1 RTC.set(DS1307_DOW,4); //set the day of the week RTC.set(DS1307_DATE,14); //set the date RTC.set(DS1307_MTH,07); //set the month RTC.set(DS1307_YR,13); //set the year RTC.start(); } void loop() { if(millis() - timeOut >400)// ждем 0,25 секунды и... { Ucor = analogRead (ADC0); // сохраняем напряжение коррекции cntr = 0; TCNT2 = 0x00; // перезарежаем таймер /* обнуляем суммы напряжений*/ Usumm_A = 0; Usumm_B = 0; Usumm_C = 0; TIMSK2 |= (1<<OCIE2A); // разрешаем прерывание по совпадению timeOut = millis(); } /* расчет реальных величин*/ if (cntr==100) { sqrtUsum_A = sqrt(Usumm_A); //вычисляем квадратный корень из суммы квадратов real_U_A = 0.104 * sqrtUsum_A; //вычисляем реальное напряжение для фазы А /*для фазы В */ sqrtUsum_B = sqrt(Usumm_B); //вычисляем квадратный корень из суммы квадратов real_U_B = 0.104 * sqrtUsum_B; //вычисляем реальное напряжение для фазы В /*для фазы С */ sqrtUsum_C = sqrt(Usumm_C); //вычисляем квадратный корень из суммы квадратов real_U_C = 0.104 * sqrtUsum_C; //вычисляем реальное напряжение для фазы С } if(millis() - time >50) { lcd.setCursor(0, 1); // печатаем вторую строку lcd.print("A="); lcd.print(real_U_A); lcd.setCursor(5, 1); lcd.print("B="); lcd.print(real_U_B); lcd.setCursor(10, 1); lcd.print("C="); lcd.print(real_U_C); time = millis(); } // вывод времени и даты! lcd.setCursor(8,0); lcd.print(RTC.get(DS1307_HR,true)); // печатаем час lcd.setCursor(10,0); lcd.print(":"); // печатаем разделитель lcd.setCursor(11,0); lcd.print(RTC.get(DS1307_MIN,false)); // печатаем минуты lcd.setCursor(13,0); lcd.print(":"); // печатаем разделитель lcd.setCursor(14,0); lcd.print(RTC.get(DS1307_SEC,false)); // печатаем секунды if (RTC.get(DS1307_DATE,false)<=9)//если едициц дней меньше-равно 9, то выводим на 1 шаг правее { lcd.setCursor(0, 0); lcd.print(" "); lcd.setCursor(1, 0); lcd.print (RTC.get(DS1307_DATE,false));} else // иначе (т.е. если единиц дней больше 9, то выводим на 1 шаг левее { lcd.setCursor(0, 0); lcd.print (RTC.get(DS1307_DATE,false));} //Отображение текущего месяца lcd.setCursor(2, 0); //указываем место печати названия месяца switch (RTC.get(DS1307_MTH,false)) // в зависимости от значения месяца печатаем название { case 1: lcd.print("-01"); break; case 2: lcd.print("-02"); break; case 3: lcd.print("-03"); break; case 4: lcd.print("-04"); break; case 5: lcd.print("-05"); break; case 6: lcd.print("-06"); break; case 7: lcd.print("-07"); break; case 8: lcd.print("-08"); break; case 9: lcd.print("-09"); break; case 10: lcd.print("-10"); break; case 11: lcd.print("-11"); break; case 12: lcd.print("-12"); break; } } //****************обработчик прерывания******************** ISR(TIMER2_COMPA_vect) { if (cntr<=99) { /*для фазы A */ Uism_A = analogRead(ADC1); // считываем значения с аналогового порта 1 Uism_A -= Ucor; // убираем подьем синусоиды на 2 вольт Uism_A *= Uism_A;// возводим значение в квадрат Usumm_A += Uism_A; // склдываем квадраты измерений /* для фазы B */ Uism_B = analogRead(ADC2); // считываем значения с аналогового порта 2 Uism_B -= Ucor; // убираем подьем синусоиды на 2 вольт Uism_B *= Uism_B;// возводим значение в квадрат Usumm_B += Uism_B; // склдываем квадраты измерений /* для фазы С */ Uism_C = analogRead(ADC3); // считываем значения с аналогового порта 3 Uism_C -= Ucor; // убираем подьем синусоиды на 2 вольт Uism_C *= Uism_C;// возводим значение в квадрат Usumm_C += Uism_C; // склдываем квадраты измерений cntr++; // увеличиваем счетчик на 1 с каждым тактом! } else { TIMSK2 = 0b00000000; // останавливаем таймер } }
ну сделайте массив для каждой фазы и заносите измерения в п/прог. прерывания в массив, а потом с 89 строки спокойно все посчитайте
только память подсчитайте чтоб хватило
переписал я прогу немного вот что получилось
но погрешность все равно выше чем в примере когда вычисления производятся в обработчике ISR
самое интересное меняю настроики таймера, замедляю его и точность вырастает, до 1 процента!!
сумму квадратов только для фазы А находите, это для отладки или забыли остальные фазы дощитать(101 строка)
это пока только для отладки
наверное это из за функции analogRead которая тратит время на перключение каналов ацп
ну тогда вместо аналогреад попробуйте напрямую с АЦП считывать
помните axill писал
Кстати если и время преобразования через analogRead будет дольше, если почитать даташит, то там различается время первого преобразования и время последующего. analogread каждый раз переключает канал на ADC, а это значит, что она каждый раз инициирует первое измерение - самое длинное. Разница почти в два раза - 25 циклов против 13. Когда мы измеряем всего один канал у нас нет потребности в переключении. Но конечно не всегда эта разница в скорости критична, но стоит все просчитать. Что не маловажно - по даташиту требуется, чтобы для 10 битного режима ADC если мы хотим качественное преобразование то частота работы ADC не должна превышать 200кГц, а это как раз уже может наложить ограничения на скорость измерений!
Вот как раз то этого я еще не научился делать!!
там ведь число разбивается на 8 разрядов и 2 ADCH и ADCL соответственно а как из них получить число как при использовании analogRead не понимаю!!да и настройки для новичка в программировании сложноватые!
ну у вас изначально стояла задача просто вольтметр сделать...Как вариант увеличить до макс частоту и если можно точность(если можно), ну и попробовать напрямую к АЦП обращаться без библиотеки
вот например как
p - это указатель на элемент массива
т.е я явно не задавал массив, а задавал его начала base
объявлениеe указателя: int *p;
чтобы указатель стал на первый элемент пишем p = base // реальный адрес в памяти, с которого начнется массив
записать в первый элемент: *p = ADCL; *(p+1) = ADCH
в следующий: p++; \\ если р=100, то станет 102... у нас указатель на 2 байтное число
*p = ADCL; *(p+1) = ADCH
ну и т.д.
Посмотрите на строки 11-15 своего нового скетча.
Потом посмотрите на строку 100.
Потом - на строку 146.
После этого ответьте на вопрос: с каким количеством измеренных значений вы работаете - 98, 101 или 100...
в этой строке я указал массив переменных от 0 до 99 итого 100!
в этой строке накасячил соглассен цикл будет выводить значения от 0 до 100 а это 101 значение
ну а здесь вроде бы тоже логично(вроде, если что попправте меня) запускаем таймер и первое значение записанное в массив будет с индексом 0 а последнее 99!
в этой строке я указал массив переменных от 0 до 99 итого 100!
Вы, может быть, и указали 100 элементов, но любой компилятор воспримет это как объявление массива с 99 элементами (имеющими номера от 0 до 98). Шилдт вам в помощь:
Одномерные массивы
Шилдт вам в помощь:
Одномерные массивы
и в правду ведь помогло, проблема то была в том что накосячил я с объявлением массива , а потом и с записью данных, и выводом массива тоже косяка упорол, сейчас подкоректировал код, точность выросла до 0.7 % Теперь все четко!
на 3 канала хватает времени в ISR c аналогреад?
Над 2 остальными какналами буду работать сегодня надо ведь еще аппаратную часть доработать!
Проверка показала что с analogRead функцией вычисления по фазе А дают плавающие значения в диапазоне 1 вольта, а вот фаза B дает уже погрешность в 3-4 вольта, фазу С не проверял, но наверняка такой же результат!
вот например как
p - это указатель на элемент массива
т.е я явно не задавал массив, а задавал его начала base
объявлениеe указателя: int *p;
чтобы указатель стал на первый элемент пишем p = base // реальный адрес в памяти, с которого начнется массив
записать в первый элемент: *p = ADCL; *(p+1) = ADCH
в следующий: p++; \\ если р=100, то станет 102... у нас указатель на 2 байтное число
*p = ADCL; *(p+1) = ADCH
ну и т.д.
Просто дело в том что программа Arduino IDE не хочет кущать такой код!!!
Я не понимаю
вот у нас есть 8 битное число записанное в ADCH. A так же 2 битное записанное ADCL. причем при считывании мы должны писать его первым!!
Т.е когда АЦП измерил напряжение мы в этих регистрах получаем 2 числа, а что дальще с ними они так же и остаются 2 разными числами, мы их должны сложить или умножить??
И еще вопрос когда мы устанавливаем вход с которого мы будим считывать в регистре ADMUX мы же можем только 1 канал установить как в настройках включить другие каналы, чтобы измерять 3 фазы?
про АЦП меги статья собранная с даташита там более менее описан ацп
про результат преобразования: в ADMUX есть бит ADLAR. Смысл - выравнивает данные влево или вправо(в моем примере данные вправо выравнены), т.е получаем обычное число: в ADCL - младшая часть, ADCH - старшие 2 бита.
на счет чо делать с данными: я под меги в ардуиновском иде не пешу и тут я не советчик, но в С (как я привел пример), да и др. языках, можно адресоваться к ячейкам памяти через указатели. Т.е. в указателе хранится адрес на то( в нашем примере на число типа int) что мы хотим записать или считать с памяти. Значение указателя командой p++ увеличивается на число равное размеру данных на которые он указывает(у нас на 2).
Т.о. для заполенния массива нужно:
int *p; - это указатель в С на число типа int
1. задать начало его - конкретное значение адреса: p = 0x100;
2. пишем в текущую ячейку памяти:
*p = ADCL; - здесь пишем младшую часть
*(p+1) = ADCH - старшую
3. меняем адрес на следующий елемент массива: p++, т.е. если было 0х100, станет 0х102
4. проверяем на конец или кол-во элементов массива, если все нормально идем на П.2
К примеру, 3 значения с АЦП:
для считывания 2 байт просто пишем Uizm = *p; Uizm должно быть определено как 2 байтное
как менять канал АЦП: см. статью. Просто в Admux меняете соответствующее поле и со следующего перобразования подключается выбранный канал
Пробовал я использовать аналоговые входы на прямую вот что получилось
опыт показал что функция которая должна была работать не работает!!!
подскажите пожауйста где я ошибся??
попробуйте без п/п препывания своей ф-ей почитать значения