Подскажите, как правильно посчитать частоту?

gene
Offline
Зарегистрирован: 07.02.2020

Прошу совета по замеру частоты сигнала.  Нашел код, к которому потом попробовал добавить разные делители частоты. Чем больше делитель, тем дольше время считывания (очевидно). А значит разбег между разными delta_time будет меньше в процентном отношении. Но проблема в том, что в зависимости от делителя меняется посчитанная частота (по переднему фронту считаю). Причем там проблема не в большем разбросе параметров, а именно в изменении частоты. Которая от делителя зависеть вроде не должна.

  ADMUX=B11100111;
  delay_us(10); 

  time = micros();  
  i = 1500;
  while (i--)
  {
    ADCSRA=B11000010; // 010 - 1/4
    //ADCSRA=B11000011; // 010 - 1/8
    //ADCSRA=B11000100; // 100 - 1/16
    //ADCSRA=B11000101; // 101 - 1/32
    //ADCSRA=B11000110; // 110 - 1/64
    //ADCSRA=B11000111; // 111 - 1/128
    while (ADCSRA & (1 << ADSC)); 
    buffer[i] = ADCH;
  }

  delta_time = micros() - time; 

От увеличения делителя зависит "плотность" фронтов в получившемся массиве: она увеличивается при одинаковой частоте сигнала. А значит страдает точность. Но проблема именно в изменении результатов.

Я сделал массив на 32 элемента и сохраняю туда "адреса" фронтов. То есть количество замеров всегда одинаковое. Например для частоты 4.8 килогерц при делителе 1/4 как раз получается 31-32 импульса в массиве на 1500 замеров с расстоянием между ними ~47 замеров. Если частоту уменьшить в два раза, то импульсов на той же частоте будет помещаться в два раза больше, и время всех замеров тоже увеличится. Но я беру первые 32 фронта, вычисляю среднее значение и умножаю среднее значение на delta_time / 1500 (размер массива с замерами).

Можно так:

  pulse_length = 0;
  for (i=1;i<pulse_count;i++)
    pulse_length += rising_edge[ i ] - rising_edge[ i-1];
  pulse_length /= pulse_count;

или так:

  pulse_length = ( rising_edge[ pulse_count-1 ] - rising_edge[0] ) / pulse_count;

Независимо от способа: я пробовал брать сумму первых двух или четырех импульсов (расстояние между передними фронтами), я пробовал брать просто  длину одного первого импульса — частота плывет в зависимости от делителя.

Может алгоритм неправильный, а может micros() выдает что-то не то (это второй вопрос: почему замер в первом примере кода выполняется за разное время, это связано с работой прерываний по таймеру? есть ли способ не использовать прерывания и таймеры, а узнать количество тактов процессора на этой процедуре?).

Уже третий день голову ломаю. Пока получается, что изменение размера массива с фронтами и изменение делителя влияет на полученную частоту. Там расчет очень простой:

// 1500 - в оригинале это BUFFER_SIZE - размер байтового массива
pulse_duration = pulse_length * ( delta_time / 1500 );
hz = 1000000 / pulse_duration;

Мне кажется я просто туплю и где-то в формуле надо что-то поменять местами.

bwn
Offline
Зарегистрирован: 25.08.2014

Загляните в проекты, там есть "Генератор........", автор "Dimax", он там вроде давал ссылки на правильные частотомеры.  Ну и в самом генераторе есть функция частотомера.

gene
Offline
Зарегистрирован: 07.02.2020

bwn пишет:

Загляните в проекты, там есть "Генератор........", автор "Dimax", он там вроде давал ссылки на правильные частотомеры.  Ну и в самом генераторе есть функция частотомера.

Нашел тему "Генератор с регулируемоей частотой на ардуино" - http://arduino.ru/forum/proekty/generator-s-reguliruemoei-chastotoi-na-a...

Частотометр -измеряет  методом тактирования первого таймера от источника сигнала. Измеряемый диапазон: 1Гц ... 7,999 МГц*

Эммм. Не уверен, что правильно понял. Но здесь измеряется частота импульсов вкл/выкл? Он не сохраняет показания (которые идут на D5, то есть напряжение замерять не получается?), а просто считает количество импульсов. Я пробовал похожий код вот с полчаса назад, но с моим источником, почему-то ничего не увидел нормального. Но это может просто поторопился. Может не туда провод подключил. Вот такой, вроде похож на то, что Dimax написал:

volatile unsigned int int_tic=0;
volatile unsigned long tic;

void setup()
{
  Serial.begin(9600);
  TCCR1A=0; TIMSK1 = 1<<TOIE1; //прерывание по переполнению
}

ISR (TIMER1_OVF_vect){ int_tic++; }

void loop()
{
  pinMode (5,INPUT); // вход сигнала T1 (only для atmega328)
  TCCR1B = (1<<CS10)|(1<<CS11)|(1<<CS12);//тактировани от входа Т1
  delay(1000);
  TCCR1B=0;
  tic= ((uint32_t)int_tic<<16) | TCNT1; //сложить что натикало
  int_tic=0; TCNT1 = 0;

  Serial.print( tic );
  Serial.println( " Hz  " );
}

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

gene
Offline
Зарегистрирован: 07.02.2020

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

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

gene
Offline
Зарегистрирован: 07.02.2020

Оказывается у меня неправильно время считается.

void analyze(){
 byte buffer [BUFFER_SIZE];
  unsigned long time, delta_time;
  int i=BUFFER_SIZE;

  ADMUX=B11100111;
  delay_us(10);

    
  time = micros();  

  while (i--)
  {
    // B11000111-125kHz B11000110-250kHz 
    ADCSRA=B11000010; // 010 - 1/4 - 4000 kHz
    //ADCSRA=B11000011; // 010 - 1/8 - 2000 kHz
    //ADCSRA=B11000100; // 100 - 16 - 1000 kHz
    //ADCSRA=B11000101; // 101 - 32 - 500 kHz
    //ADCSRA=B11000110; // 110 - 64 - 250 kHz
    //ADCSRA=B11000111; // 111 - 128 - 125 kHz 
    while (ADCSRA & (1 << ADSC));   // Ожидание флага окончания преобразования
    buffer[i] = ADCH;
  }

  delta_time = micros() - time;

  Serial.print( "dt="  );Serial.print( delta_time ); Serial.println( "" );
}

я убрал все другие расчеты (которые идут ниже) и оставил только замеры, которые показаны в этом коде. По какой-то причине delta_time меньше, чем в основной программе с кучей расчетов. Еще похоже, что функция Serial.print() в основном цикле тоже влияет на delta_time внутри analyze(). Как такое может быть? Добавляю больше Serial.print() в любом месте после и до замеров, время delta_time увеличивается.

Zummer
Offline
Зарегистрирован: 25.11.2019

gene пишет:

Я сделал массив на 32 элемента и сохраняю туда "адреса" фронтов. То есть количество замеров всегда одинаковое. Например для частоты 4.8 килогерц при делителе 1/4 как раз получается 31-32 импульса в массиве на 1500 замеров с расстоянием между ними ~47 замеров. Если частоту уменьшить в два раза, то импульсов на той же частоте будет помещаться в два раза больше, и время всех замеров тоже увеличится. Но я беру первые 32 фронта, вычисляю среднее значение и умножаю среднее значение на delta_time / 1500 (размер массива с замерами).

Я делал подобную задачу но не на ардуино,суть-точно так же считывал в буфер 1024 замера(у вас 1500),(как выше измеряем время заполнения буфера и делим на 1024-время одного замера),  потом считал количество фронтов в этом буфере,потом номер в буфере первого фронта и последнего,их разницу делил на (количество фронтов-1) и умножал на время одного замера -это период

 

gene
Offline
Зарегистрирован: 07.02.2020

Zummer пишет:

Я делал подобную задачу но не на ардуино,суть-точно так же считывал в буфер 1024 замера(у вас 1500),(как выше измеряем время заполнения буфера и делим на 1024-время одного замера),  потом считал количество фронтов в этом буфере,потом номер в буфере первого фронта и последнего,их разницу делил на (количество фронтов-1) и умножал на время одного замера -это период

Да-да, такой же способ. Еще можно попытаться остаток от буфера посчитать, которые не попал между фронтами. Еще чуть-чуть точность повысить (возможно) получится. Но такая точность не сильно нужна, когда частота куда-то не туда плывет.

У меня со временем какая-то свистопляска. Одинаковое количество замеров выдают разное время в зависимости кода в других местах программы.

Я попробовал усреднять. Сделал массив на 1024 и делаю, ну например, 1024*16 замеров, в буфер кладу [i & 1023], чтобы буфер по кругу переписывался. Разброс по времени между разными замерами уменьшается (там ведь только кратно четырем, но даже такая точность сбивается и значения могут быть разными на тысяче замеров). С усреднением ситуация налаживается и финальная расчетная частота скачет уже меньше. Но оказалось, что на кусок кода между вызовами micros() влияют функции из совершенно других мест. Вы как данные выводили? Не было влияния других функций?

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

rkit
Offline
Зарегистрирован: 23.11.2016
Zummer
Offline
Зарегистрирован: 25.11.2019

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

Я взводил TIMER1 на счет тактовых импульсов,перед измерением сбрасывал его значение в 0 и запускал,по окончании заполнения буфера останавливал TIMER1,его значение и есть задержка

Сейчас занят приставкой к дешевому эхолоту,там у меня почти как у вас-сброс TIMER1,его запуск,255 замеров АЦП складываются в буфер,стоп TIMER1

Zummer
Offline
Зарегистрирован: 25.11.2019

Можно в теме пультоскопа посмотреть как сделан замер частоты-сразу не подумал...

gene
Offline
Зарегистрирован: 07.02.2020

rkit пишет:

cli();

sei();

Отключается таймер, который выдает значения для функции micros(). А я не знаю как иначе можно узнать время выполнения. По хорошему, там количество тактов определенное получается. Если на голом ассемблере (а я совсем-совсем этот ассемблер не знаю), то можно попробовать посчитать такты. А на Си там кучу всего компилятор складывает и черт знает сколько тактов там используется.

gene
Offline
Зарегистрирован: 07.02.2020

Zummer пишет:

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

Это я понял. Но пока замечаю, что мешает совсем-совсем мало. Я попробовал замедлить первый таймер и время меняется практически в кратное число раз. Там отличия в десятых и сотых микросекунды (что в районе 4-6 миллисекунд вообще влиять не должно).

Zummer пишет:

Я взводил TIMER1 на счет тактовых импульсов,перед измерением сбрасывал его значение в 0 и запускал,по окончании заполнения буфера останавливал TIMER1,его значение и есть задержка

Сейчас занят приставкой к дешевому эхолоту,там у меня почти как у вас-сброс TIMER1,его запуск,255 замеров АЦП складываются в буфер,стоп TIMER1

Это пока для меня непонятное, но звучит как что-то похожее на решение. Получается, что нулевой таймер надо полностью отключить когда этот замер производится и работать будет только TIMER1, который не успеет переполниться?

Вообще у меня все больше подозрений не на таймер. Другой код в других местах влияет. Я не понимаю как такое может быть. Может особенность микроконтроллеров. Но это странно. Вывод Serial очень сильно замедляет процесс, расчет значений после замеров тоже замедляет. Я уже начинаю думать, что у меня что-то с головой не в порядке. Но закомментировав строку ВНЕ цикла замера - совсем в другом месте программы, в другой функции - я вижу уменьшение времени замера.

gene
Offline
Зарегистрирован: 07.02.2020

Zummer пишет:

сброс TIMER1,его запуск,255 замеров АЦП складываются в буфер,стоп TIMER1

Он в самом первом режиме запускается и просто увеличивает счетчик, выполняя функцию ISR (TIMER1_OVF_vect) { int_tic++; } ... ?

Я пока описание читаю. Честно сказать, с трудом понимаю. Есть ли режим "просто выполнять функцию" так и не понял. Есть запуск по переполнению , запуск по внешнему прерыванию, запуск при достижении значения. И, кажется, есть запуск "вместе с тактом процессора" с определенным делителем. То есть нужно выбрать максимально медленный вариант, который подойдет для измеряемых значений? Чтобы достаточная точность была и максимально редко запускалось прерывание, чтобы не вносить дополнительных задержек? Или все вообще не так?

Zummer
Offline
Зарегистрирован: 25.11.2019

gene пишет:

Он в самом первом режиме запускается и просто увеличивает счетчик, выполняя функцию ISR (TIMER1_OVF_vect) { int_tic++; } ... ?

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

Предусмотреть чтоб таймер не переполнялся иначе будет прерывание-подбираем делитель тактовой

Жаль исходника нет,я б сам попробовал (мои исходники на Bascom AVR и здесь они не уместны)

Но все это в случае если виновник всему прерывание от таймера (любого,например millis и т п)

Zummer
Offline
Зарегистрирован: 25.11.2019

Точно не помню,но вроде надо сбрасывать  ADIF  а не  ADSC

Kakmyc
Offline
Зарегистрирован: 15.01.2018

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

gene
Offline
Зарегистрирован: 07.02.2020

Zummer пишет:

Точно не помню,но вроде надо сбрасывать  ADIF  а не  ADSC

Я видел примеры и с ADIF, но тут вариант был взят из библиотеки. Правда там один замер был, и все работать должно иначе. Надо попробовать поменять.

Нашел такое описание:

Bit 6 - ADSC: ADC Start Conversion - Запуск преобразования ADC
В режиме однократного преобразования для запуска каждого цикла преобразования необходимо устанавливать бит ADSC в состояние 1. В циклическом режиме бит ADSC устанавливается в состояние 1 только при запуске первого цикла преобразования. Каждый раз после первой установки бита ADSC, выполненной после разрешения ADC или одновременно с разрешением ADC, будет выполняться пустое преобразование, предшествующее активируемому преобразованию.. Это пустое преобразование активирует ADC. ADSC будет сохранять состояние 1 в течение всего цикла преобразования и сбрасывается по завершении преобразования. При выполнении пустого преобразования, предшествующего активируемому, бит ADSC остается установленным до завершения активируемого преобразования. Запись 0 в этот бит эффекта не оказывает.

Bit 5 - ADFR: ADC Free Run Select - Установка циклического режима работы ADC
При установленном в состояние 1 бите ADFR ADC будет работать в циклическом режиме. В этом режиме ADC производит выборки и обращения к регистрам непрерывно (одно за другим). Очистка бита приводит к прекращению циклического режима.

Bit 4 -ADIF: ADC Interrupt Flag - Флаг прерывания ADC
Данный бит устанавливается в состояние 1 по завершению преобразования и обновления регистров данных. Прерывание по завершению преобразования ADC выполняется если в состояние 1 установлены бит ADIE и I-бит регистра SREG. Бит ADIF сбрасывается аппаратно при выполнении подпрограммы обработки соответствующего вектора прерывания. Кроме того, бит ADIF может быть очищен записью во флаг логической 1. Этого необходимо остерегаться при чтении-модификации-записи ADCSR, поскольку может быть запрещено отложенное прерывание. Это применимо и в случаях использования команд SBI и CBI.

 

Кажется, что надо ADFR использовать :) Но явно, чтобы разобраться нужно читать все с самого начала. Просто кусок кода приколотить не выходит.

gene
Offline
Зарегистрирован: 07.02.2020

Zummer пишет:

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

Предусмотреть чтоб таймер не переполнялся иначе будет прерывание-подбираем делитель тактовой

Жаль исходника нет,я б сам попробовал (мои исходники на Bascom AVR и здесь они не уместны)

Но все это в случае если виновник всему прерывание от таймера (любого,например millis и т п)

Я пытался натыкать что-нибудь с функцией прерывания, но пока не могу правильно выбрать какие биты куда установить, так как знаний совершенно не хватает, а описания какие-то уж очень хитрые. Вот даже тут не понял, что значит "происходит прерывание,а надо просто обнулить таймер". Я думал, что таймер работает просто увеличивая значение счетчика на единицу с каждым срабатыванием. Но, я думаю, что надо самому эту функцию надо написать. То есть отключить таймер0 и запустить свой таймер1. Переполнения на 16-битном таймере избежать несложно, если выбрать правильный делитель (сам опрос довольно короткий, счетчик переполнится не успеет). Только я даже не понимаю про cli() и sei() - они отключают все прерывания и после cli() надо запускать свое, либо отключают только таймеры, сделанные "культурно" ардуино-библиотечными функциями.

А код не проблема. Через пару часов буду дома, закину. Там в нем почти ничего и нет, кроме этого опроса.

gene
Offline
Зарегистрирован: 07.02.2020

Kakmyc пишет:
Какой то подход в корне неверный. Зачем считать частоту аналоговым входом ? Компаратор добавить , выставить опорное напряжение и запустить в Ардуино через пин внешнего прерывания. Запустить таймер, считать тики между прерываниями. Никакой массив с замерами не нужен, по двум импульсам можно рассчитать частоту.

Есть пара моментов. Я не очень понимаю что мне делать с напряжением. У меня источник с плавающим напряжением, а для цифрового входа, как я понимаю, нужно иметь 5 вольт. Кроме этого я хочу посчитать скважность (в принципе алгоритм уже считает, но в программу не включен), но, самое сложное, хочу считать, даже не знаю как это называется, разницу между минимумом и максимумом напряжения. Там не всегда от нуля до 5 вольт, там может быть от 2 до 3 вольт, например. Как-то это надо будет в проценты еще перевести (и, вероятно, младшие биты придется тоже учитывать). Ну зато, вроде как никаких синусоид и пил не должно быть, только прямые углы.

Мне кажется, что частоту как-то можно считать через пин внешнего прерывания, если придумать схему, которая будет напряжение "разделять" на 0 и 5 вольт. У моего источника ноль как бы есть, максимум точно известен, но редко когда достигается (обычно где-то треть или половина от максимально возможного напряжения). Вот в таких плавающих условиях, как я понимаю (а понимаю совсем-совсем приблизительно) не получится к цифровой ноге зацепиться. Хотя да, наверное, точность частотомера будет намного лучше и более высокие частоты станут доступны, но мне интересно не более 30 кГц, а в основном до 10 примерно, а реально важно только видеть меньше или больше 1 кГц. Просто хочется максимум выжать. Это, практически первый проект на микроконтроллерах. До этого я такого даже в руках не держал.

-NMi-
Offline
Зарегистрирован: 20.08.2018

gene пишет:

 

Я не очень понимаю что мне делать с напряжением. У меня источник с плавающим напряжением, а для цифрового входа, как я понимаю, нужно иметь 5 вольт.

Кроме этого я хочу посчитать скважность (в принципе алгоритм уже считает, но в программу не включен), но, самое сложное, хочу считать, даже не знаю как это называется, разницу между минимумом и максимумом напряжения. Там не всегда от нуля до 5 вольт, там может быть от 2 до 3 вольт, например.

Мне кажется, что частоту как-то можно считать через пин внешнего прерывания, если придумать схему, которая будет напряжение "разделять" на 0 и 5 вольт. 

Это, практически первый проект на микроконтроллерах. До этого я такого даже в руках не держал.

Компаратор решает эту проблему.

АЦП и эту проблему легко решает.

Можно, и даже нужно. Есть "специальные" ноги у проца.

А вот это уже печально, но исправимо.

gene
Offline
Зарегистрирован: 07.02.2020

Тут в самом конце закомментированы две строки. На них отлично видно, что время меняется.

https://yadi.sk/d/8abKdmf_aZuk3A

-NMi-
Offline
Зарегистрирован: 20.08.2018

И что ты по этому поводу придлагаишЪ???

asam
asam аватар
Offline
Зарегистрирован: 12.12.2018

Цитата:

Есть пара моментов. Я не очень понимаю что мне делать с напряжением. У меня источник с плавающим напряжением, а для цифрового входа, как я понимаю, нужно иметь 5 вольт. Кроме этого я хочу посчитать скважность (в принципе алгоритм уже считает, но в программу не включен), но, самое сложное, хочу считать, даже не знаю как это называется, разницу между минимумом и максимумом напряжения. 

Ну для таких вещей надо сигнал оцивровывать с помощью АЦП и потом уже его обрабатывать для получени всех нужных данных (частоты, скважности, вышеупомянутой разницы итп). Atmega328 для этого медленновата. Для сигналов в 1КГц вполне потянет. 10КГц уже только с низкой точностю и всего 7 выборок на период. Но может для ваших целей и нормально будет. А то лучше на STМ32 перебираться. Там и памяти больше и АЦП получше и работает все быстрее

gene
Offline
Зарегистрирован: 07.02.2020

asam, я так и делаю. Просто предлагается использовать цифровую ногу, чтобы считать частоту. А у меня то ли знаний не хватает, то ли это сложно реализовать в аппаратном плане (а тут точно знаний нет). Я сейчас все считаю достаточно неплохо, но куда-то частота плывет из-за совершенно посторонних вещей в других местах программы. Кстати, вполне получается посчитать частоту около 21 кГц - мне этого достаточно.

gene
Offline
Зарегистрирован: 07.02.2020

Zummer пишет:

Точно не помню,но вроде надо сбрасывать  ADIF  а не  ADSC

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

Скорость считывания с "free run" заметно увеличилась (просто не надо перед каждым циклом задавать параметры считывания), но с точностью какие-то странные конвульсии (другие таймеры отключены через cli()). Может идти достаточно стабильно, а потом на одном из измерений куда-то резко прыгнуть. Причем там первый опрос "нулевой" или типа того, может это режим удаления шумов, но кажется нет. Приходится делать минимум два прогона, чтобы исключить первые нулевые значения. Самое противное, что подгонка работает только для одной частоты (например 4800), и не работает корректно с частотами ~15 кГц. А самое странное, что если вручную задавать время, то оно совсем не похоже на то, что должно быть, и его не нужно менять для разных делителей! От этого я вообще отойти не могу. Надо бы психиатру показаться.

asam
asam аватар
Offline
Зарегистрирован: 12.12.2018

gene пишет:

asam, я так и делаю. Просто предлагается использовать цифровую ногу, чтобы считать частоту. А у меня то ли знаний не хватает, то ли это сложно реализовать в аппаратном плане (а тут точно знаний нет). Я сейчас все считаю достаточно неплохо, но куда-то частота плывет из-за совершенно посторонних вещей в других местах программы. Кстати, вполне получается посчитать частоту около 21 кГц - мне этого достаточно.

 

Так получается посчитать или не получается? И зачем вам «цифровая нога» если уже есть оцифрованный сигнал? «Цифровая нога»  реагирует на импульсы с амплитудой выше порога срабатывания входного триггера шмитта.  При 5 вольтовом питании это около 2.1В. Если уровень вашего сигнала  ниже, то реакции не будет.

 

gene
Offline
Зарегистрирован: 07.02.2020

asam, мне цифровая нога не нужна. Тут в теме говорят, что очень высокие частоты очень точно можно замерить на цифровой ноге. Я эти темы почитал, и мне показалось, что там только частоту можно замерить (может и скважность еще), но источник должен быть очень стабильным, а у меня напряжение плавает довольно сильно. Сейчас уже все считается, до 21 кГц примерно (более скоростных источников не проверял). Но у меня проблема, что время замера, которое считается через micros() очень сильно может меняться в зависимости от функций в других местах программы. Я там чуть выше исходник короткий приложил, там видно, что время меняется. Поэтому частота рассчитывается очень примерно, хотя и достаточно для моих целей. Но всегда хочется, чтобы было лучше, да и полностью понять в чем проблема тоже надо, ведь может там вообще левые данные получаются. Эталонного измерителя нет и эталонного источника тоже.

sadman41
Offline
Зарегистрирован: 19.10.2016

Рассчитывай через захват таймера по прерыванию.

gene
Offline
Зарегистрирован: 07.02.2020

sadman41 пишет:

Рассчитывай через захват таймера по прерыванию.

А можно поподробней? Таймер ведь и есть один из видов прерывания? То есть посчитать сколько раз было вызвано прерывание таймера? Если я правильно понял. Получается, что надо свой вариант таймера вместо ардуиновского таймера для micros()/millis() сделать? Но у меня пока знаний маловато. Я даже описание таймеров понять не могу. Что-то переполняется, и он срабатывает. А что переполняется нигде не написано.

sadman41
Offline
Зарегистрирован: 19.10.2016

Счётчик переполняется. Вытяните руки, растопырьте пальцы. Начинайте загибать по одному. Когда все будут загнуты - наступит событие "переполнения". Если действие происходит в МК, то в определенном регистре будет выставлен флаг, а затем вызван обработчик.

Таймер - это не прерывание, это механизм, который загибает пальцы с определенным интервалом. В даташите он так и называется - Timer/Counter.

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

Kakmyc
Offline
Зарегистрирован: 15.01.2018

А зная период импульсов (максимальная точность таймера на атмеге328 62.5нс), можно легко посчитать частоту.

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

gene пишет:

Это, практически первый проект на микроконтроллерах. До этого я такого даже в руках не держал.

Ты не поверишь, все здешние в какой-то момент времени даже в руках не держали )))

-NMi-
Offline
Зарегистрирован: 20.08.2018

gene пишет:

Тут в теме говорят, что очень высокие частоты очень точно можно замерить на цифровой ноге. Я эти темы почитал, и мне показалось, что там только частоту можно замерить (может и скважность еще), но источник должен быть очень стабильным, а у меня напряжение плавает довольно сильно.

Уотт как до тебя всё даходит то долго, зовут случаем не "почта" ???

В "твоём" случае под термином "цифровые ноги" подразумеваюцца ноги типа: T0, T1 и далее, Tx и ноги INT0, INT1 и далее INTx. Суть первых ног - возможность ИМИ тактировать соответствующий таймер, Т0 для нулевого таймера, Т1 - для первого ну и так далее. Суть вторых ног - возможность генерации прерываниЙ по ТРЁМ основным событиям: фронт, спад, изменение. Есть ЧЕТВЁРТОЕ событие - Zero, тобишь логический НОЛЬ на входе, приводящий к генерации прерывания. Это ЦИФРОВЫЕ ноги при помощи которых наиболее ЛЕГКО что-то посчитать. На практике можно считать ЛЮБОЙ ногой,но, имея при этом несколько больший "гемор". Здесь всё!

По поводу уровня входного сигнала и несоответствия его с логическими уровнями AVR. Выше уже был мой пост и по поводу "цифровых" ног, и по поводу компаратора. Но, так как до тебя не доходит с первого раза - я повторюсь, мне не трудно. Компаратор - это такая ФИГНЯ, которая измеряет разницу напряжений между выводами. Есть ОПОРНЫЙ вывод, на него подаёццо опорное напряжение и оно может быть ЛЮБОЕ! Есть измерительный вывод, которым собственно и "измеряем".  Суть проста как трусы: напруга на измерительном выводе меньше опорного - на выходе НОЛЬ, и наоборот, соответственно. Так вот, вся "фишка" компаротора заключается в том, что ОПОРНОЕ напряжение можно (и нужно) РЕГУЛИРОВАТЬ!!!  Посему ты можешь измерять сигналы амплитудой от 0,01 до хоть до 100 Вольт, вот в чём суть!

Фсё, я устал печатать)))

gene
Offline
Зарегистрирован: 07.02.2020

sadman41 пишет:
Счётчик переполняется. Вытяните руки, растопырьте пальцы. Начинайте загибать по одному. Когда все будут загнуты - наступит событие "переполнения". Если действие происходит в МК, то в определенном регистре будет выставлен флаг, а затем вызван обработчик. Таймер - это не прерывание, это механизм, который загибает пальцы с определенным интервалом. В даташите он так и называется - Timer/Counter.

Я читал, что есть функция таймера "сравнение" и "переполнение". Сравнение с заданным числом - это понятно. Оно вводится в ХХХ регистр. А переполнение когда наступает? Какого значения должен достигнуть счетчик, чтобы переполнение случилось и что будет потом? То есть, если например счетчик 16-битный, с делителем, например, 1/256, то значит, что переполнение наступит через 65536*16 М / 256 тактов? В  этот момент будет запущен обработчик? В обработчике можно сделать еще один счетчик. А потом, таймер-счетчик с переполнением начинает считать от нуля или его снова нужно запускать?

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

А что за внешнее прерывание? Это то же самое, что переполнение? Если да, то значение всегда будет одинаковым - какой-то максимум для счетчика. Я думал, что счетчик вызывает обработчик на каждое срабатывание, а при переполнении что-то еще происходит... ну пусть остановка счетчика. Тогда можно запустить счетчик перед замерами (с достаточно большим делителем), а в конце замера посмотреть (не знаю как), сколько он насчитал. Если же переполнение происходит очень быстро, и надо будет считать сколько раз случилось переполнение, то эта функция будет мешать (замедлять) замеры АЦП. То есть именно то, что происходит, если брать micros().

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

gene
Offline
Зарегистрирован: 07.02.2020

ua6em пишет:

Ты не поверишь, все здешние в какой-то момент времени даже в руках не держали )))

Очень даже поверю. Ничего такого в этом нет. Мне, может быть, мешает, представление о таймере во времена DOS. Там все было намного понятней - каждые 17.2 мс срабатывает прерывание. Всё! Не надо включать, выключать, выбирать делитель.

gene
Offline
Зарегистрирован: 07.02.2020

-NMi- пишет:

В "твоём" случае под термином "цифровые ноги" подразумеваюцца ноги типа: T0, T1 и далее, Tx и ноги INT0, INT1 и далее INTx. Суть первых ног - возможность ИМИ тактировать соответствующий таймер, Т0 для нулевого таймера, Т1 - для первого ну и так далее. Суть вторых ног - возможность генерации прерываниЙ по ТРЁМ основным событиям: фронт, спад, изменение. Есть ЧЕТВЁРТОЕ событие - Zero, тобишь логический НОЛЬ на входе, приводящий к генерации прерывания. Это ЦИФРОВЫЕ ноги при помощи которых наиболее ЛЕГКО что-то посчитать. На практике можно считать ЛЮБОЙ ногой,но, имея при этом несколько больший "гемор". Здесь всё!

T0, T1 - это таймеры? А INT0, INT1 - пины D2, D3 в nano? Вы предлагаете запустить таймер (я с этим как раз буксую), считать его переполнения и одновременно считать сколько раз на аппаратном прерывании, которое возникает на ноге, возникнет либо фронт, либо спад, либо ноль? Это я понимаю :)) Надеюсь, что понимаю. А ниже написано, почему не могу сделать именно таким способом.

-NMi- пишет:

Компаратор - это такая ФИГНЯ, которая измеряет разницу напряжений между выводами. Есть ОПОРНЫЙ вывод, на него подаёццо опорное напряжение и оно может быть ЛЮБОЕ! Есть измерительный вывод, которым собственно и "измеряем".  Суть проста как трусы: напруга на измерительном выводе меньше опорного - на выходе НОЛЬ, и наоборот, соответственно. Так вот, вся "фишка" компаротора заключается в том, что ОПОРНОЕ напряжение можно (и нужно) РЕГУЛИРОВАТЬ!!!  Посему ты можешь измерять сигналы амплитудой от 0,01 до хоть до 100 Вольт, вот в чём суть!

Компаратор (лат. comparare — сравнивать) — сравнивающее устройство - логический электронный прибор с двумя входами и одним выходом. Компаратор выдает высокое напряжение (логическая 1) в случае, если напряжение на первом (прямом) входе выше, чем на втором (инвертирующем) и низкое выходное напряжение (логический 0) если напряжение первого входа ниже вольтажа второго.

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

Мне мозгов не хватает. И навыков. Надо будет какое-то устройство сделать, которое будет отслеживать напряжение с источника пару секунд, потом задавать компаратору опорное напряжение. Потом снова его менять (если у источника изменится). Ну, возможно, теоретически, можно это будет сделать с помощью той же ардуины, каким-нибудь шимом с каким-нибудь конденсатором (хз как это компаратор с шимом работает, может и не надо сглаживать). Это для меня пока очень сложно. Я же говорю, у источника напряжение может быть любым в определенных пределах. Какое будет в текущий момент - неизвестно, для этого АЦП и используется - чтобы получить минимум и максимум. Это не для средних умов, а у меня даже до среднего не дотягивает.

-NMi-
Offline
Зарегистрирован: 20.08.2018

Рекомендую найти какого-нибуть старого дядьку-радиолюбителя, поставитЪ ему среЦтво и пусть расскажет на пальцах. Так будет прощще.

gene
Offline
Зарегистрирован: 07.02.2020

нет. ничего не получается.

sadman41
Offline
Зарегистрирован: 19.10.2016

gene пишет:

Я читал, что есть функция таймера "сравнение" и "переполнение". Сравнение с заданным числом - это понятно. Оно вводится в ХХХ регистр. А переполнение когда наступает? Какого значения должен достигнуть счетчик, чтобы переполнение случилось и что будет потом? То есть, если например счетчик 16-битный, с делителем, например, 1/256, то значит, что переполнение наступит через 65536*16 М / 256 тактов? В  этот момент будет запущен обработчик? В обработчике можно сделать еще один счетчик. А потом, таймер-счетчик с переполнением начинает считать от нуля или его снова нужно запускать?

По другим постам я вижу, что Вы всё в кучу валите и не можете потом в этом разобраться.

1) Прерывание - это "реакция" МК на какое-либо событие (внешнее или внутреннее) определенное некоторыми правилами. При этом происходит остановка хода выполнения основной программы, выполнение обработчика и возврат обратно на "точку в коде", где процессор это прерывание застигло. Наличие обработчика - условие необязательное.

2) "Переполнение" - это такое же событие, как и "сравнение". Последнее наступает, когда счётчик достиг заданного числа, а переполнение - на следующем тике (вроде так) после максимально возможного значения. Ничего волшебного в "переполнении" нет.

3) Счётчик - это почти обычная переменная, инкрементируемая на 1 с каждым тиком прескалера. Тикает прескалер - переменная увеличивается, не тикает - не увеличивается. Если у счётчика задана разрядность, то есть и максимальное значение. Добавьте к byte i=255 ещё единицу - каково значение переменной будет? А ещё +1 если? Вот то же самое происходит и с восьмибитным счётчиком.

4) Внешнее прерывание - это реакция МК на изменение уровня напряжения на специальном контролируемом входе. Назначен этому прерыванию обработчик - он будет вызван. Не назначен... ну, ничего и не будет. Почти. Никакой прямой связи внешние прерывания со счётчиками не имеют. Ну, разве что "захват счётчика". Уровень на входе изменился - текущее значение счётчика откопировалось в другую переменную. Счётчик продолжил тикать. Воспользовались Вы этой копией, не воспользовались - МК не интересует. И это гораздо точнее micros(), так как всё происходит на аппаратном уровне.

Однако Вы поставили перед собой излишне сложную для начального уровня задачу - измерить неизвестную частоту сигнала с неизвестной амплитудой. И тут я не знаю, что посоветовать. Простого пути нет. Делайте для начала что-то одно.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

gene пишет:

Очень даже поверю. Ничего такого в этом нет. Мне, может быть, мешает, представление о таймере во времена DOS. Там все было намного понятней - каждые 17.2 мс срабатывает прерывание. Всё! Не надо включать, выключать, выбирать делитель.

Вы неправильно выражаетесь не "представление", а "отсутствие представления".

В IBM PC использовался таймер Intel 8253, который по способу программирования очень похож на таймеры Ардуино. Точнее, микросхема 8253 включала в себя 3 таймера, один из которых использовался для прерываний системных часов, другой - для генерации тона (по потребности), а третий - уже не помню.

В Ардуино все очень похоже, только прерывания происходят не раз в 55 мс (в Вашем сообщении ошибка: в DOS прерываний были с частотой 18.2 Гц или периодом 55 мс), а раз в 1 мс. И есть еще два таймера, которые можно использовать, в частности, и для генерации тона (по потребности).

Т.е. один таймер, отвечающий за системное время, в Ардуино уже включен и настроен, а остальные - программируются по потребности. В точности так же, как и на IBM PC (в DOS, если угодно).

Kakmyc
Offline
Зарегистрирован: 15.01.2018

Тс, не в ту ты сторону лезешь.
Не нужен тебе ADC тут от слова совсем.
Практически при любой частоте и амплитуде у тебя будет точка к которой можно привязатся компаратором.
Тупо говоря при установке опорного
напряжения в 2в, при входном <2в будет лог ноль, при выше лог еденица.
Этот лог0/1 подаём на внешнее прерывание INT0 или INT1 по желанию.
Внутри обработчика внешнего прерывания мы просто запоминаем состояние регистра таймера (ну это такая штука куда считает аппаратный счетчик сколько натикало с момента сброса.)и обнуляем сам регистр. Итого у нас получается переменная в которой указано сколько тиков у нас прошло между внешними прерываниями INT0. Ну и это прерывание нужно настроить RISING/FALLING , что бы за один период один раз запускать.

Zummer
Offline
Зарегистрирован: 25.11.2019

Вот накидал счет таймером,можете проверить

    byte buffer [1024];
  unsigned long time, delta_time;
  //float time, delta_time;
  int i=1000;
  void setup(){
    Serial.begin(9600);
    ADMUX=B11100111;//выбор внутреннего опорного 1,1В
    delayMicroseconds(10);
 }
 void loop()
{   
  TCCR1A=0;
  TCCR1B=0b00000011;//4 мксек
  TIMSK1=0; 
  i=1000;
  cli();
  TCNT1   =0; 
    while (i--)
  {
    ADCSRA=B11000111; // 111 - 128 - 125 kHz 
    while ((ADCSRA & 0x10)==0);   // Ожидание флага окончания преобразования
    ADCSRA|=0x10;
    buffer[i] = ADCH;
  }
  delta_time =TCNT1;sei();
  Serial.print( "dt="  );Serial.print( delta_time ); Serial.println( "  " );
  }
Kakmyc
Offline
Зарегистрирован: 15.01.2018

Фигня какая то.
Зачем таймер, если он не используется ?
Да ещё и каждый цикл его перезапускать. Бред

Zummer
Offline
Зарегистрирован: 25.11.2019

Kakmyc пишет:
Фигня какая то. Зачем таймер, если он не используется ? Да ещё и каждый цикл его перезапускать. Бред

То есть вы не видите в строке 13 что запускаем таймер на подсчет тактов микроконтроллера/64?

В строке 16 запрещаем все прерывания-всякие delayMicroseconds и millis отключаются и в процессе "наполнения" буфера никакие прерывания не вмешиваются,но таймер все равно будет считать,главное не допустить переполнения

Почитайте тему еще раз,чтоб понять что хочет автор темы

 

gene
Offline
Зарегистрирован: 07.02.2020

Zummer, я взял ваш вариант и попробовал поменять другие части программы. Вот влияние delay(), расчетов и вывода. Тут кажется, что дело почти только в delay() - https://youtu.be/ZKOWZ7dawgg

А потом я просто поменял переменные, которые выводятся через Serial.print и оказалось, что это тоже влияет - https://youtu.be/POH7Xa3eagY

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

Время меняется от чего-то вне функции замера. И не обязательно увеличивается, бывает, что уменьшаться. Может это только у меня так?

gene
Offline
Зарегистрирован: 07.02.2020

Пока не уверен полностью, но вроде бы я эту хреноту поборол. С помощью ADIE. В общем, вызывается прерывание, в котором в массив все складывается. Время вроде бы стабильно даже с micros(). Только теперь возникает ощущение, что точность АЦП действительно падает при делителях 1/4 и 1/8. И вообще теперь частоты хорошо ловить получается только на определенном делителе (который "лучше" подходит для этой частоты). Раньше можно было 4800 Гц ловить на 1/4, 1/8, 1/16 без проблем, а теперь четко получается только только на 1/16. Плохо то, что при других делителях частота не так чтобы сильно скачет, и определить, что надо перейти на другой делитель будет не слишком просто.

Kakmyc
Offline
Зарегистрирован: 15.01.2018

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

Zummer
Offline
Зарегистрирован: 25.11.2019

Kakmyc пишет:
Нафига таймер перезапускать каждый цикл ? Зачем отключать прерывания ? В чем смысл таймера, если мы имеем нестабильные значения на выходе, зависящие от того , что выводится в порт ?

Ну так блесни талантом-подскажи как правильнее измерить время заполнения буфера снятия ацп

Считать таймером частоту не тот случай-вам четко это объяснили

переменную delta time  сделать unsigned int,у меня работает боле-менее

Kakmyc
Offline
Зарегистрирован: 15.01.2018

Маразм крепчал...
Не нужен там буфер и АЦП.

Zummer
Offline
Зарегистрирован: 25.11.2019

Kakmyc пишет:
Маразм крепчал... Не нужен там буфер и АЦП.

Ну вы программист-еще скажите не нужна ардуина и скетч,все можно измерить линейкой)))

это у вас маразм

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

Если вам нечего предложить идите,мягко говоря,стороной либо предложите свой вариант как требуется автору поста,задачи бывают разные,вы не знаете какая сейчас у автора,кто знает-может все порты D заняты например дисплей с 8-ми битной шиной

 

Kakmyc
Offline
Зарегистрирован: 15.01.2018

Zummer пишет:

Kakmyc пишет:
Маразм крепчал... Не нужен там буфер и АЦП.

Ну вы программист-еще скажите не нужна ардуина и скетч,все можно измерить линейкой)))

это у вас маразм

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

Если вам нечего предложить идите,мягко говоря,стороной либо предложите свой вариант как требуется автору поста,задачи бывают разные,вы не знаете какая сейчас у автора,кто знает-может все порты D заняты например дисплей с 8-ми битной шиной

 

Что вам не понятно в задаче автора ?
Там русским по белому написано :"правильно посчитать частоту".
Так вот , считать частоту через ADC это нихера не правильно.
Если он не знает как это сделать правильно, то нужно ему подсказать , а не потакать ему в его заблуждениях.
Если , все же ему нужно что то другое, никак не относящееся к названию темы, значит нужно исправить название темы.