Таймер и вычисление среднеквадратичного значения сигнала

alchi
Offline
Зарегистрирован: 13.02.2012

Доброго всем времени суток!
Задача такая:
Имеется развертка синусоидального сигнала (с течением времени меняется частота и амплитуда) или широкополосный случайный сигнал. Необходимо вычислить его СКЗ (среднеквадротичное значение).

Этот синал смаштабировал и сместил, т.е. 0 бит = -Umаx, 512 бит = 0 В, 1024 бит = Umax
АЦП ардуино - максимальная частота дискретизации 10 кГц, т.е. 10000 измерений за секунду
Далее например в течение 1 секунды я вычисляю сумму (U1^2+U2^2+..U10000^2) - набираю статистику
Как бы мне организовать цикл с таймером где бы все это считалось? Как только посчитал за секунду, результат отдал и по новой считает, набросочек программы необходим?

СКЗ = КОРЕНЬ((U1^2+U2^2+..U10000^2)/10000)

Или есть какие-нибудь другие способы реализации этого дела?

PS формула взята такая для простоты, не стал с скользящем СКЗ заморачиваться.

maksim
Offline
Зарегистрирован: 12.02.2012

 Попробуйте так:

#define IN A0
unsigned long time_out, summ;

void setup() {
  Serial.begin(9600);
}

void loop() {
  summ = 0;
  time_out = micros() + 1000000;
  while(time_out > micros()){
    summ += sq(analogRead(IN)-512);
  }
  Serial.println(sqrt(summ));
}

 

AlexFisher
AlexFisher аватар
Offline
Зарегистрирован: 20.12.2011

С таймером действительно нет смысла заморачиваться. Если во время счета Вам что-нибудь еще делать надо, то придется переходить на расчет скользящего в прерывании от АЦП. Программку maksimа я бы подправил (не уверен, что ровно 10000 отсчетов пройдет):

#define IN A0
unsigned long time_out, summ, n;

сonst float Umax=24.0; //например


void setup() {
  Serial.begin(9600);
}

void loop() {
  summ = 0;
  n=0;
  time_out = micros() + 1000000;
  while(time_out > micros()){
    summ += sq(analogRead(IN)-512);
    n++;
  }
  Serial.println(Umax*sqrt(((float)summ)/n));
}

 

alchi
Offline
Зарегистрирован: 13.02.2012

Попробовал!

Если сделать по этому варианту, то реально будет около 4316 отчетов за 1 секунду. Операция возведения в кадрат занимает много времени, если например просто суммировать то будет порядка 8000 отчетов.

Вероятно заявленные 10000 отчетов в секунду - это теоретический максимум работы ацп без учета других операции.

Как бы сделать так, запрос на ацп обрабатывается, в это время я считаю. Ацп отработало, я забрал данные, ацп начинает отрабатывать новые данные, в это время, я считаю полученное предыдущее значение с ацп.

Для чего мне это надо, у меня на входе сигнал с частотой от 5 до 2500 Гц. Это может быть и синусоидальный сигнал, так и случайный. Как раз последнее и накладывает подобную формулу вычисления СКЗ (для любого сигнала). Например если частота на входе будет порядка 2500 Гц, то 4316 отчетов в секунду мало, будет большая ошибка.

Есть какие-нибудь предложения?

 

PS еще попробовал организовать массив, данное с АЦП сажаю в массив и так далее... предложенной программой . В этом случае программа загружается в контроллер, но с компьютером  я не могу установить связь (через TCP/IP). Может памяти не хватает ОЗУ... хз что там...

AlexFisher
AlexFisher аватар
Offline
Зарегистрирован: 20.12.2011

 Могу предложить такие варианты:

  1. Возмите функцию analogRead из hardware\arduino\cores\arduino\wiring_analog.c и разбейте ее на 2 функции - одна запускает преобразование преобразование, другая получает результаты преобразования. Штатная функция тупо ждет окончания преобразования. Алгоритм действий: получили данные, запустили следующее преобразование, считаем квадрат, получили данные (они к этому времени уже готовы, если не готовы, то будут готовы почти сразу - проверку готовности из функции получения результатов убирать нельзя), и т.д. То есть считать параллельно преобразованию. Полагаю, скорость поднимется примерно вдвое.
  2. Максимально ускорить АЦП - рецепт в этой статье. Общая скорость вырастет процентов на 40 наверно.
alchi
Offline
Зарегистрирован: 13.02.2012

Улучшаем AnalogRead()

#define FASTADC 1

// defines for setting and clearing register bits
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

void setup() {
int start ;
int i ;

#if FASTADC
// set prescale to 16
sbi(ADCSRA,ADPS2) ;
cbi(ADCSRA,ADPS1) ;
cbi(ADCSRA,ADPS0) ;
#endif
 
Serial.begin(9600) ;
Serial.print("ADCTEST: ") ;
start = millis() ;
for (i = 0 ; i < 30000 ; i++)
analogRead(0) ;
Serial.print(millis() - start) ;
Serial.println(" msec (30000 calls)") ;
 
}

void loop() {
}
 

Результат: скорость 18,2 мкс против бывших 110 мкс.
Кстати, максимальная скорость АЦП Атмеги как раз 16мкс. Как вариант — использовать другую микросхему, заточенную именно под АЦП, которая позволит уменьшить скорость до 0,2мкс (читать ниже, почему)
 
Я так понял, вот этот кусок я вставляю в свою программу и в основном цикле как обычно работаем?

То, что жирным курсивом выделено - выбрасываем?

Предложенный вами 1 вариант, я врятли осилю, в асемблере не очень разбираюсь;(

Умножение не как нельзя подускорить?

step962
Offline
Зарегистрирован: 23.05.2011

alchi пишет:

Есть какие-нибудь предложения?

 

Вектор прерывания 22 (известный также под именем ADC_vect) - ваше все. Это прерывание генерируется в момент завершения аналогово-цифрового преобразования. Естественно, если прерывание разрешено и все подготовительные операции выполнены надлежащим образом.

Рыба проекта - примерно такая.

alchi
Offline
Зарегистрирован: 13.02.2012

step962 пишет:

alchi пишет:

Есть какие-нибудь предложения?

 

Вектор прерывания 22 (известный также под именем ADC_vect) - ваше все.

ммм продлите мысль пожалуйста?

step962
Offline
Зарегистрирован: 23.05.2011

В ISR, которая будет вызываться в момент завершения АЦ-преобразования, т.е. когда новое значение готово для дальнейшей обработки, это только что полученное значение записывается в буфер (лучше взять массив и добавить в ISR проверку на его переполнение), а затем тут же запускается новое преобразование, после чего управление возвращается основной программе. Ну а та может непрерывно брать данные из буфера, возводить их в квадрат и т.д. и т.п. - совсем не заморачиваясь запуском АЦ-преобразования и ожиданием его завершения.

AlexFisher
AlexFisher аватар
Offline
Зарегистрирован: 20.12.2011

alchi пишет:

Предложенный вами 1 вариант, я врятли осилю, в асемблере не очень разбираюсь;(

Умножение не как нельзя подускорить?

Предложенный вариант 1 - это более краткое изложение Вашего же варианта из #3, а ассемблер тут непричем, там сишная функция, которую тупо нужно разрезать на две, но чтобы понять, где резать, нужно понять, что она делает.

Для организации прерывания по окончанию преобразования придется существенно заглубиться в даташит или в программирование проца на "С" без ардуино-IDE (точнее, без ее библиотечных функций). Но складывать квадраты отсчетов Вы все равно не сможете быстрее АЦП. Так что, если хотите что-то делать параллельно работе АЦП - придется поднапрячь мозг и разобраться, как это работает (я не про канал Дискавери :) )

Если хотите просто ускорить АЦП, Вам из приведенного текста потребуются только дефайны и секция "иф фастадц"

Ускорить умножение (точнее, вычисления, связанные с умножением) можно если перейти к целочисленным расчетам. Если устроит точность результата 10 бит (как у АЦП), то позаботтесь о том, чтобы все операнды расчетов имели тип "long int"

P.S. И не надо бояться ассемблера - он не сложнее программируемого микрокалькулятора... хотя в наш продвинутый компьютеризированный 21 век о них уже никто и не помнит, наверно :)

step962
Offline
Зарегистрирован: 23.05.2011

AlexFisher пишет:

 

Но складывать квадраты отсчетов Вы все равно не сможете быстрее АЦП.

Хотите верьте, хотите нет, но для 10000 операций возведения в квадрат и суммирования мне потребовалось 234 мсек, т.е. за секунду можно успеть выполнить более 40000 таких операций. А это раз в 5 больше скорости работы АЦП (8-10 тыс. считываний в сек).


#define N 10000
int i;
long S=0;
long T0,T1;

void Test() {
  T0=millis();
  Serial.print("Begin: ");
  Serial.println(T0);
  for(i=0;i<N;i++) {
    S=S+i*i;
    if(i%1000==0) Serial.println(i);
  }
  T1=millis();
  Serial.print("End: ");
  Serial.println(T1);

  Serial.print("Millis: ");
  Serial.println(T1-T0);
  Serial.print("  Summ: ");
  Serial.println(S);
}

void setup() {
  Serial.begin(9600);
  Test();
}

void loop() {
}

 

alchi
Offline
Зарегистрирован: 13.02.2012

step962 пишет:

Хотите верьте, хотите нет, но для 10000 операций возведения в квадрат и суммирования мне потребовалось 234 мсек, т.е. за секунду можно успеть выполнить более 40000 таких операций. А это раз в 5 больше скорости работы АЦП (8-10 тыс. считываний в сек).

Действительно, это я ошибся, операция умножения выполняется быстро.

Попробовал "оптимизацию АЦП" (по ссылке выше), АЦП действительно стало работать быстрее примерно в 6 раз.

unsigned long time_out, summ, n;

void setup() {
  Serial.begin(9600);
}

void loop() {
  summ1 = 0;
  summ2 = 0;
  n=0;
  time_out = micros() + 1000000;
  while(time_out > micros()){
    summ1 += sq(analogRead(0)-512);
    summ2 += sq(analogRead(1)-512);
    n++;
  }
...
}

При опросе 2-х АЦП за 1 секунду проходит более 13000 опросов каждого.  Это меня более чем устраивает.

Спасибо огромное за помощь!

step962
Offline
Зарегистрирован: 23.05.2011

Я тоже ошибся, точнее, перезаложился с накладными расходами (как-бэ потери времени на операциях в прерывании).
Стоит закомментировать проверку
if(i%1000==0) Serial.println(i);
как выполнение ускоряется в 10 (!) раз. 10000 умножений за 23 мсек!!!
Что лишний раз говорит о том, что деление зна-а-а-а-ачительно медленнее умножения и от него необходимо избавляться любыми способами.

alchi
Offline
Зарегистрирован: 13.02.2012

step962 пишет:
Я тоже ошибся, точнее, перезаложился с накладными расходами (как-бэ потери времени на операциях в прерывании). Стоит закомментировать проверку if(i%1000==0) Serial.println(i); как выполнение ускоряется в 10 (!) раз. 10000 умножений за 23 мсек!!! Что лишний раз говорит о том, что деление зна-а-а-а-ачительно медленнее умножения и от него необходимо избавляться любыми способами.

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

В реале проверю как все это работает, посмотрю там...

AlexFisher
AlexFisher аватар
Offline
Зарегистрирован: 20.12.2011

Какие проблеммы? Это сумму квадратов Вы накапливаете в цикле, а деление и извлечение корня проводите 1 раз по окончании цикла замеров. 

alchi
Offline
Зарегистрирован: 13.02.2012

Это не проблема, можно и в контроллере поделить можно и на компьюторе. Ничего страшного;)

alchi
Offline
Зарегистрирован: 13.02.2012

Всем привет, пропадал маленько... Дела были. Этот проект для меня снова стал актуален в новом ключе. В кратце: я завел таймер на 100 мкс, в прерывание обрабатываю ацп... считаю скз. Переменная аккумулятор накапливает сумму квадратов 10000 отсчетов. И возникла проблема - размер аккумулятора в 4 байта не хватает для всех значений... Как его можно увеличить или создать свой тип данных например 12 байт типа int? чтобы туда поместилось число равное произведению 1023*1023*10000.

Michal
Michal аватар
Offline
Зарегистрирован: 26.04.2013

вот тема, тут считали среднеквадратичное значение...

alchi
Offline
Зарегистрирован: 13.02.2012

Michal пишет:

вот тема, тут считали среднеквадратичное значение...


там састота 50 Гц. У меня развертка от 5 до 2500 Гц или случайный сигнал в диапазоне 20-2500 Гц. Для правильной, корректной оцифровки сигнала по теореме Котельникова-Найкаеста частота опроса должна быть не меньше 5000 Гц, чтобы хотябы приходилось два отчета на период наивысшей частоты. А лучше 4 :) или больше. Но больше уже врятли. Амплитуда сигнала и частота неизвестны ардуине. По этому используется формула вычисления скз. При сумме квадратов 10000 отчетов аккумулятор переполняется... И значение недостоверное. Поэтому мне нужна переменная размером 8 или 12 байт :D как ее получить, я хз:(

step962
Offline
Зарегистрирован: 23.05.2011

Суммируйте первые 2000 отсчетов в одном накопителе, вторые - во втором, и так далее до пятого.

Как только накопители "заполнятся" ("прилетело" 10000 отсчетов) делите каждую сумму на 10000 и складывайте результаты (ну или делайте это последовательно - по мере заполнения накопителей).

Не забывайте сбрасывать накопители после выполнения вычислений с находящимися в них суммами.

alchi
Offline
Зарегистрирован: 13.02.2012

Здравствуйте, всеравно результат некорректный получается, какая частота дискретизации ацп у ардуины, как ее можно поменять, боюсь что в прерывание таймера он не успевает получить корректное значение?!

Michal
Michal аватар
Offline
Зарегистрирован: 26.04.2013

ну вроде как у AVR на 1 перобразование уходит 13 тактов процессора, т.е при всех делителях частоты выборку в  десятки кГц сделать можно...

Что вам мешает результата оцифровки хранить в 4байтной переменной(long, float). В обработчике принимаем 2байта с АЦП и складываем в массив, а в основном теле программы считаем всю медленную математику.

На конец можно вообще термоэлектрический вольтметр замутить на термопарах :))

 

 

alchi
Offline
Зарегистрирован: 13.02.2012

Добрый день!

К каким результам я пришел:

Считать скз на 10 и более КГц с 2-х аналоговых каналов и при этом работать с ethernet - нереально.

Команды работы с ethernet выполняются приличное время (до 300 мкс)...

Поэтому скорее всего будет так: будет дополнительный контроллер, который будет считать СКЗ и передавать по UARTу инфу в основной контроллер который уже будет опрашивать переферию, а потом уже передавать по ethernet информацию в компьютер.

 

alchi
Offline
Зарегистрирован: 13.02.2012

Модуль вычисления СКЗ тока и напряжения (с двух АЦП)

Все в принципе получилось хорошо, интервал таймера 51 мкс, ~19600 отчетов/сек. В программе я беру сумму квадратов девяти тысяч восьмисот отчетов, и отправляю в последовательный порт... Взял 9800, т.к. переменная summU или summI ограничена 4 байтами.

Интересно, почему-то при интервале таймера 50 мкс и при n=10000 отчетов, ничего не видно в мониторе последовательного порта, такое ощущение что программа не работает.

//Блок вычисления СКЗ напряжения и тока
#include <TimerOne.h>

//Оптимизация АЦП
#define FASTADC 1
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

unsigned long summU, summI,t1,t2;  
long a;
int n;
void setup()

{  
  Timer1.initialize(51); 
  Timer1.attachInterrupt(Timer1_action);

  //Оптимизация АЦП
#if FASTADC 
  // set prescale to 16
  sbi(ADCSRA,ADPS2) ;
  cbi(ADCSRA,ADPS1) ;
  cbi(ADCSRA,ADPS0) ;
#endif

  Serial.begin(115200);  
}  

void Timer1_action()
{
  if (n==9800) 
  {
    Serial.println(summU);  
    Serial.println(summI);  
    n=1;
    summU=0;
    summI=0;
  }
  else
  {
    a=analogRead(0);
    a=a-511;
    a=sq(a);
    summU += a;

    a=analogRead(1);
    a=a-511;
    a=sq(a);
    summI += a;

    ++n;
  }
}

void loop()
{  

}