РЕШЕНО Подсчёт поступающих импульсов с помощью аппаратного таймера.

Тема в разделе "Arduino & Shields", создана пользователем MaksOne, 5 май 2023.

Статус темы:
Закрыта.
  1. MaksOne

    MaksOne Нерд

    Доброго времени суток, уважаемые форумчане! К сожалению под рукой нет ардуины, чтобы протестировать свои мысли на практике, поэтому могу лишь проработать вопрос в теории со сторонней помощью более опытных товарищей. А вопрос, собственно, следующий.
    Имеется необходимость подсчитать за определённый промежуток времени количество поступающих коротких импульсов, которые следуют со случайным периодом, хаотично. Причём их частота может варьироваться произвольно от единиц герц до десятков килогерц. Использовать для этого внешние прерывания и инкрементировать переменную путём прибавления единицы не хочется, ибо быстродействие такого кода оставляет желать лучшего, что чревато для точности, да и объем скетча тоже решает. Поэтому хочу использовать таймер в режиме счётчика. Изучив кучу статей и форумов, я пришёл к выводу, что для этого необходимо (без кода, вкратце, на примере таймера №1):
    1. Запретить все прерывания
    2. Проинициализировать таймер, выставив его управляющие регистры А и В в ноль
    3. Разрешить прерывание по переполнению, чтобы знать, когда регистр счётчика переполнится (необязательно)
    4. Настроить регистры таймера для работы от внешнего источника тактирования по переднему фронту
    5. Выставить значения TCNT1 = 0
    6. Разрешить прерывания
    После этого:
    1. Подаем на вход внешнего тактирования таймера 1 импульсы, которые считаем
    2. С помощью другого таймера (например 0) отсчитываем время и вызываем прерывание
    3. В этом прерывании запрещаем прерывания, вытаскиваем значение счётчика таймера 1 - это и будет количество импульсов, после чего обнуляем счётчик и разрешаем прерывания дальше.
    Правильно ли я понимаю логику измерения количества импульсов (считай, частоты) с помощью таймера?
    Если правильно, то следующий вопрос: Как именно мне получить значение подсчитанного количества импульсов из счётчика? Это значение хранится в переменной TCNT1? Если да, то могу ли я поместить значение счётчика TCNT1 в какую-нибудь гипотетическую переменную для дальнейшей работы, например, так:
    Код (C++):
    word count1 = TCNT1;
    Или же не всё так просто и придётся городить нечто более сложное?
    Возможно я что-то не так понимаю, прошу просветить. Заранее благодарю откликнувшихся.
     
  2. parovoZZ

    parovoZZ Гуру

    после ресета они и так запрещены.

    то же, что и выше

    а МК какой? У восьмибиток АВР вход в прерывание занимает от 4 до 6-ти тактов в зависимости от выполняемой команды. Инкремент - это ещё пара тактов. Выход из прерывания - 2 или 4 такта (не помню, если честно))) Ну и где здесь теряется быстродействие?

    всё очень просто. В книге Евстифеева таймеры описаны очень хорошо.
    Если вкратце: счётчик таймера переводится в режим capture и устанавливается прерывание по переполнению этого счётчика (в прерывании просто фиксируем тот факт, что счётчик переполнился. Если это нужно в дальнейшем). Считывать значение этого счётчика можно в любой момент. Играться с прерываниями и прочей чепухой не нужно. Есть нюансы при записи значения в счётчик 16-ти разрядного таймера, но речь сейчас не про это.

    работу с таймерами я показываю в одном из ответов на breadboard.ru
     
  3. MaksOne

    MaksOne Нерд

    Конечно же, я не акцентировал внимание на том, что изначальные установки битов производить в setup, конечно изначально прерывания запрещены, и управляющие биты в регистрах по нулям. Это понятно. Я привел алгоритм действий, если это всё производится гипотетически где-то в теле программы за пределами сетапа во время уже работающих прерываний.

    Неважно, какой МК. Абстрактный АВР мк с парой таймеров, имеющих все необходимые режимы, и один из которых 8 разрядный, а другой 16. Ну, к примеру, пусть Мега328. А по поводу потери быстродействия - ваши оценки затраченных тактов сильно разнятся с мнением других программистов, которые считают себя гуру. Приведу пример с одного из известных сайтов по электронике: " Вход в прерывание, это тактов 10-20. + накладные расходы. + задержка входа от того, что мы сейчас сидим в другом прерывании. Ну и нафига такая аппаратная капча, которая даёт не прогнозируемую и довольно приличную СОФТОВУЮ ошибку? Это надругательство над самой идеей капчи! Особенно если у нас предделитель 1 и каждый такт имеет значение."

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

    Т.е. мы настраиваем таймер на внешнее тактирование и режим Capture, подаём на тактовый вход измеряемые импульсы, и через необходимое время копируем содержимое регистра TCNT в регистр ICR, откуда уже в последующем и достаём измеренное кол-во импульсов?
     
    Последнее редактирование: 6 май 2023
  4. parovoZZ

    parovoZZ Гуру

    У 8-ми битной авр вход в прерывание 20 тактов??? Ну-ну. Что он будет делать все эти двадцать тактов? У STM32 с его конвеерами всего 12 тактов. Что при тактовой 70 МГц уже раза в два быстрее, чем у АВР с ее 6-ю тактами на 20 МГц.

    пусть специалист озвучит эти расходы.

    Начинать изучать МК надо не с форумов, а с книжек.

    если программирование идёт на сях, то в тулчейне уже всё предусмотрено. Достаточно написать запись присваивания 16-ти битного числа регистру счётчика. Если на ассемблере, то действовать надо так, как написано в даташите. Ну и на время чтения или записи 16-ти битного счётчика необходимо запретить глобально прерывания с помощью команд CLI и SEI
    Код (C++):
    cli();
    count1 = TCNT1;
    sei();
     
  5. a1000

    a1000 Гуру

    Не знаю о каком МК вам писали на форуме, но для
    в прерывание попадают по такому алгоритму
    - завершение текущей операции
    - сохранение в стек адреса текущей операции
    - переход в соответствующую строку таблицы векторов прерываний
    - переход в обработчик прерываний.
    Где тут может набраться10- 20 тактов, не знаю.
    Ну а
    Это очевидно сохранение в стек регистров используемых в обработчике прерываний. Ну так не надо километровые обработчики писать.
     
    ИгорьК и Airbus нравится это.
  6. Airbus

    Airbus Радиохулиган Модератор

    Все четко и ясно—сразу видно ассемблерщика!
     
  7. User248

    User248 Гик

    Вот, буквально недавно делал для замера частоты вращения двигателя.
    Код (C++):

    #define SENSOR_PIN 2 // контакт для замера частоты

    #define MEAS_TIME 1e3 // время подсчёта импульсов, в миллисекундах

    void setup()
    {
      Serial.begin(9600);
      pinMode(SENSOR_PIN, INPUT_PULLUP);
    }

    uint32_t freqMeter()
    {
      uint32_t counter = 0;
      static bool swOn = false;
      uint32_t freqTimer = millis();
      while (uint32_t(millis() - freqTimer) < MEAS_TIME) {
        bool butt = !digitalRead (SENSOR_PIN);
        // trigger
        if (butt && !swOn)
        {
          swOn = true;
          counter++;
        }
        if (!butt && swOn) {
          swOn = false;
        }
      }
      return counter;
    }

    void loop()
    {
      uint32_t freq = freqMeter();
      Serial.println(String(freq) + " Hz");
      delay (1000);
    }
     
     
  8. parovoZZ

    parovoZZ Гуру

    если писанина на сях, то у компилятора используемые регистры разнесены. Поэтому он их в стек не кладёт
    А есть ещё свободно используемые регистры. GPIO1, GPIO2 и GPIO3. Вроде так они зовутся. Лень качать даташит. Интернет в сибириях не очень.
     
  9. MaksOne

    MaksOne Нерд

    Конечно же на сях. Это ведь тема "Ардуино и Шилды", или же я что-то путаю? Кто занимается программированием Ардуино на ассемблере?
    Т.е. я правильно обозначил конструкцию присвоения значения счётчика TCNT переменной?
    Всё о том же, об Ардуино речь шла. А значит Мега328
    Ассемблерщики программируют голые камни, а не извращаются с Ардуинами. А это раздел тем об Ардуино, вроде как. Нет, конечно же, и ардуину можно заставить пахать с помощью ассемблера, но это уже, извините, азарт, граничащий с извращеньем.
    Опять же, это просто инкрементирование переменной. Да ещё и с помощью DigitalRead. Я недавно собирал для отца автомобильный пробник, одна из функций которого была частотомер. Измерял частоту таким же самым способом. Дак вот при частоте свыше 240 герц показания становились неадекватными. Я не спорю, что программа, помимо инкрементирования, занималась кучей других задач, типа организации динамической индикации, опроса кнопок и прочее, но сам факт того, что подобный способ подсчёта импульсов весьма ограниченный. Именно поэтому я хочу организовать подсчёт хаотично поступающих с частотой от единиц герц до десятков килогерц импульсов с помощью таймера/счётчика. А это другое.
     
  10. parovoZZ

    parovoZZ Гуру

    очень плохая идея, когда речь про дефицит процессорного времени. ДигиталРид - это обёртка, причём очень жирная, над банальным чтением регистра PIN, которое занимает всего 2 такта. Также мне не понятна идея эксплуатации МК на 16МГц, когда есть возможность увеличить его частоту до 20Мгц.
     
  11. User248

    User248 Гик

    Вы сначала проверьте, потом говорите. В представленном коде нет никаких посторонних задач. Только подсчёт импульсов в цикле. До 10 кГц должно хватить. Если мало, тогда воспользуйтесь этой библиотекой. Мегагерцы можно замерять на уно/нано ардуинах, если не ошибаюсь. Сигнал подаётся на контакт D5.

    https://github.com/PaulStoffregen/FreqCount

    Ещё проект был на сайте create.arduino.cc - частотомер до 6,5 мГц.


    Код (C++):

    #include <LedControl.h> // https://github.com/wayoda/LedControl/archive/master.zip
    #include <FreqCount.h> // https://github.com/PaulStoffregen/FreqCount/archive/master.zip
    LedControl lc = LedControl(12, 11, 10, 1); // DIN(12), CLK(11), CS(10)

    unsigned long f;
    byte fq[7], pd = false;

    void setup() {
      lc.shutdown(0, false);
      FreqCount.begin(1000);
      lc.clearDisplay(0);
      lc.setIntensity(0, 8);
    }

    void loop() {
      if (FreqCount.available()) {
        f = FreqCount.read();
      }
      lc.setRow(0, 7, 0x47);
      fq[6] = f / 1000000 % 10;
      fq[5] = f / 100000 % 10;
      fq[4] = f / 10000 % 10;
      fq[3] = f / 1000 % 10;
      fq[2] = f / 100 % 10;
      fq[1] = f / 10 % 10;
      fq[0] = f % 10 % 10;
      for (int i = 0; i < 7; i++) {
        lc.setDigit(0, i, fq[i], pd);
      }
    }
     
    schematic_WK38r2s5Av.jpg
     
    Последнее редактирование: 9 май 2023
  12. MaksOne

    MaksOne Нерд

    Вот именно, когда МК занят чисто этим кодом. А если есть куча другого кода, который необходимо выполнять постоянно? Если бы задача состояла чисто считать импульсы и всё, я бы даже без МК обошёлся бы ) отсюда и потребность разгрузить его.
    Я ознакомился с кодом библиотеки. Она тоже использует счётчики/таймеры ))) Зачем подключать целую библиотеку, когда можно обойтись несколькими строками C-кода без неё? Мне лишь нужно узнать синтаксис единственной строки, а именно присвоения значения счётного регистра TCNT какой-либо абстрактной переменной, и всё.
    Именно. Но поскольку мне нужно было замерять частоты до 99 Гц максимум, мне это тогда показалось достаточным.

    И всё же... Уважаемый parovoZZ, я искренне преклоняюсь перед вашими знаниями в области архитектуры МК и приёмами работы с регистрами напрямую, но единственный вопрос, ради которого я открыл эту тему - как можно вытащить значение регистра TCNT в отдельную переменную? Верна ли конструкция следующего плана: ?
    Код (C++):
    Count = TCNT1;
    Или же так нельзя?
    Если вы ответите мне на этот вопрос, я буду очень благодарен, вы сэкономите мне кучу времени, которую я потрачу на дальнейшие поиски правильного ответа.
     
  13. User248

    User248 Гик

    Две ардуины в помощь, если так всё серьёзно. Одна меряет частоту, другая занимается своими задачами. Связываются между собой по UART.
     
    Последнее редактирование: 9 май 2023
  14. parovoZZ

    parovoZZ Гуру

    Верно. Даже разверну мысль - зачем ардуина, если можно обойтись без неё?
     
    Последнее редактирование: 9 май 2023
  15. parovoZZ

    parovoZZ Гуру

    Так я же вроде написал. Если есть прерывания и обработка происходит вне их тела, то необходимо эти прерывания запрещать на время чтения/записи регистра счетчика 16-ти битного таймера. Если внутри прерывания все происходит, то в этом нет необходимости - ядро авр запрещает прерывания автоматически при входе в них (но их может разрешить программист).
     
  16. parovoZZ

    parovoZZ Гуру

    Одна дурина может считать ровно столько сигналов, сколько есть свободных таймеров на борту. Поэтому не надо делать программно то, что можно сделать аппаратно.
     
  17. parovoZZ

    parovoZZ Гуру

    Ты реально думаешь, что связь по уарту отнимет меньше процессорного времени, чем подсчёт импульсов? Могу сказать на 150 процентов, что это не так. И уж тем более это не так, если обмен организован на ардуино обертке. Там только и будешь заниматься, что обменом. На другие задачи времени не останется.
     
  18. User248

    User248 Гик

    Не больше, чем подсчёт импульсов за секунду. Можно проверить в рамках эксперимента.
     
  19. MaksOne

    MaksOne Нерд

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

    Да я то, собственно, и ардуинами не пользуюсь. Я c помощью arduino IDE пишу скетчи преимущественно на чистом С (насколько это возможно, чтобы сжать программу и повысить быстродействие) и заливаю в голый камень через программатор.

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

    User248 Гик

    Провёл эксперимент. Если верить симулятору Tinkercad, то на чтение по UART уходит несколько миллисекунд. И то по тому, что я добавил дополнительно delay(1) для надёжности считывания. При этом, на замер частоты в первой ардуине уходит ровно одна секунда.

    Безымянный56756756756.png
     
Статус темы:
Закрыта.