Как установить таймер?

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

, 👍0

Обсуждение

Как я предложил в комментариях к вашему другому вопросу, вы также можете использовать millis() здесь для измерения разницы во времени (используйте micros(), если измеряемое время меньше нескольких миллисекунд)., @chrisl


3 ответа


1

Даже если вы не используете настоящую (сложную) операционную систему, вы должны придерживаться общепринятых правил. Для Arduino во многих случаях следует избегать прямого управления аппаратным обеспечением, чтобы обеспечить совместимость с максимально возможным количеством существующих библиотек для вашей конкретной платформы Arduino.

Прямая установка таймера (если вы используете официальный Arduino Uno, который содержит процессор Atmel328P, таймеры процессора описаны в разделе 14 Спецификации Atmel328P) может привести к непредвиденным результатам, если вы используете библиотеку, которая ожидает, что таймер будет работать без изменений.

Вместо этого рассмотрите возможность использования функцииmillis(), встроенной в среду разработки Arduino IDE. . Функция возвращает текущее количество миллисекунд с момента включения Arduino. Запишите это значение в свой код. Затем, если вы хотите узнать, истекла ли одна секунда, получите новое значение в миллисекундах и вычтите из него это сохраненное значение и посмотрите, больше ли оно 1000. Если это так, то прошла одна секунда.

,

Что касается «_consider добавил код для обнаружения этого опрокидывания_»: лучше просто написать код, безопасный при опрокидывании, например if (millis() - start_time >= продолжительность). «проверить, возвращает ли millis() число, превышающее это значение» _не_ безопасно при опрокидывании., @Edgar Bonet

Настоящий Эдгар. @Damon Swart новичок на сайте, и мне нравится пробовать, тестировать и проверять по одной идее за раз (используя millis()), а затем, после того, как первая идея сработает, добавляя к ней (пролонгировать). Кроме того, я предпочитаю не включать код. Публикация ответов с кодом занимает больше времени, и код может быть неверным или неполным. Кроме того, описание решения заставляет оригинального плаката задуматься над проблемой., @st2000

@ Деймон Сварт, если вы попробуете ответ, и он сработает для вас, примите ответ как правильный, чтобы другие также могли найти и использовать ответ. Если это не работает для вас, оставьте комментарий здесь, и мы можем попытаться изменить ответ, чтобы он работал для вас., @st2000

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

Пожалуйста, смотрите мое предложенное редактирование., @Delta_G


1

Используйте micros() (справочная страница ) для получения метки времени в микросекундах и millis() (справочная страница ), чтобы получить отметку времени в миллисекундах.

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

Пример 1: простое линейное (синхронное) измерение времени программного обеспечения, включая низкое разрешение (micros()) и высокое разрешение (timer2.get_count())< /h1>

Проще говоря, давайте измерим, сколько времени требуется, чтобы установить выходной контакт в ВЫСОКИЙ, а затем снова в НИЗКИЙ уровень:

void setup() 
{
    Serial.begin(115200);
    // я просто оставлю контакт 9 в качестве входа; но раскомментируйте строку ниже, чтобы сделать ее выводом
    // pinMode(9, OUTPUT);
}

void loop()
{
    // Измеряем и печатаем, сколько микросекунд требуется только для того, чтобы установить выходной контакт в ВЫСОКОЕ состояние, а затем
    // снова НИЗКИЙ.
    uint32_t time_start_us = micros();    // <=== ВРЕМЯ-ОТМЕТКА НАЧАЛА
    digitalWrite(9, HIGH);
    digitalWrite(9, LOW);
    uint32_t time_end_us = micros();      // <=== КОНЕЦ ВРЕМЕННОЙ МЕТКИ
    uint32_t time_elapsed_us = time_end_us - time_start_us; 
    Serial.print("time_elapsed_us = ");
    Serial.println(time_elapsed_us);
    delay(100);
}

Действительно классный скетч ShowInfo для профилирования скорости Arduino показывает, что функция Arduino digitalWrite() занимает около 5 мкс каждый, поэтому ожидайте, что приведенный выше код напечатает ~ 10 мкс. Посмотрим, правильно ли это. Я запустил это на Arduino Nano и получил следующий результат:

time_elapsed_us = 8
time_elapsed_us = 12
time_elapsed_us = 12
time_elapsed_us = 12
time_elapsed_us = 12
time_elapsed_us = 12
time_elapsed_us = 12
time_elapsed_us = 8
time_elapsed_us = 12
time_elapsed_us = 12
time_elapsed_us = 8
time_elapsed_us = 12
time_elapsed_us = 8
time_elapsed_us = 12
time_elapsed_us = 12
time_elapsed_us = 12
time_elapsed_us = 12
time_elapsed_us = 8

Это странно. Почему только 8 или 12 нас? Почему не 10? Или 9? Или что-то другое? Оказывается, функция Arduino micros() имеет разрешение всего 4 мкс, поэтому на самом деле она выводит либо 8, либо 12 мкс. так как они кратны 4 нас. Чтобы получить лучшее разрешение, вам придется изменить регистры аппаратного таймера, как я сделал в своей библиотеке eRCaGuy_Timer2_Counter с разрешением 0,5 мкс. Полное раскрытие: я написал и поддерживаю эту библиотеку. Это бесплатное приложение с открытым исходным кодом, но оно размещено на моем личном веб-сайте с рекламой, и я собираю пожертвования для загрузки. Полнофункциональный фрагмент также доступен в коде внизу этой веб-страницы, ничего не загружая.

Вот как сделать приведенный выше код с моей библиотекой:

#include <eRCaGuy_Timer2_Counter.h>

// Преобразование счетчиков часов timer2, каждый из которых равен 0,5 мкс, в мкс.
float counts_to_us(uint32_t time_counts)
{
    float time_us = (float)time_counts/2.0; 
    return time_us;
}

void setup() 
{
    Serial.begin(115200);
    // я просто оставлю контакт 9 в качестве входа; но раскомментируйте строку ниже, чтобы сделать ее выводом
    // pinMode(9, OUTPUT);

    // Настроить Таймер2. Это ДОЛЖНО быть сделано до того, как заработают другие функции Timer2_Counter.
    // Примечание: так как это искажает выходы ШИМ на контактах 3 & 11, а так же мешает тону()
    // библиотеку (http: arduino.cc/en/reference/tone), вы всегда можете вернуть Timer2 в нормальное состояние,
    // вызов `timer2.unsetup()`
    timer2.setup(); 
}

void loop()
{
    // Измеряем и печатаем, сколько микросекунд требуется только для того, чтобы установить выходной контакт в ВЫСОКОЕ состояние, а затем
    // снова НИЗКИЙ.
    uint32_t time_start_counts = timer2.get_count();    // <=== ВРЕМЯ-ОТМЕТКА НАЧАЛА
    digitalWrite(9, HIGH);
    digitalWrite(9, LOW);
    uint32_t time_end_counts = timer2.get_count();      // <=== КОНЕЦ ВРЕМЕННОЙ МЕТКИ
    uint32_t time_elapsed_counts = time_end_counts - time_start_counts; 
    float time_elapsed_us = counts_to_us(time_elapsed_counts);
    Serial.print("time_elapsed_us = ");
    Serial.println(time_elapsed_us);
    delay(100);
}

Теперь посмотрите на результат. Вот более точные результаты с моей библиотекой eRCaGuy_Timer2_Counter. Намного лучше! Но почему эти ложные значения 14,50 мкс, которые я пометил <===? Почему они выключены на 4us? Я объясню ниже.

time_elapsed_us = 10.00
time_elapsed_us = 10.50
time_elapsed_us = 10.00
time_elapsed_us = 10.00
time_elapsed_us = 14.50  <===
time_elapsed_us = 10.50
time_elapsed_us = 10.50
time_elapsed_us = 10.50
time_elapsed_us = 10.00
time_elapsed_us = 10.00
time_elapsed_us = 14.50  <===
time_elapsed_us = 10.00
time_elapsed_us = 10.00
time_elapsed_us = 10.50
time_elapsed_us = 10.50
time_elapsed_us = 10.50
time_elapsed_us = 10.50
time_elapsed_us = 10.00

Недостатком того, что я делаю, является то, что вы будете чаще получать джиттер в 4 мкс. Каждый раз, когда 8-битный счетчик timer2 переполняется, вызывается ISR (процедура обслуживания прерываний). Это подсчитывает переполнения, чтобы отслеживать 32-битный программный таймер с 8-битного аппаратного счетчика. Вход в этот ISR занимает около 4 мкс, а это означает, что если вы попытаетесь получить метку времени, но затем будет вызвана ISR, вам придется подождать 4+ мкс, чтобы получить эту метку времени, поэтому она сильно отличается. Ник Гэммон, один из нескольких экспертов по Arduino, на которых я действительно равняюсь, упоминает об этом здесь в своих Прерываниях. статью, в которой он говорит: «Есть подстраиваемая цифра 4 мкСм...». Таким образом, этот 8-битный счетчик считает 1 тик за 0,5 мкс, что означает, что он пересчитывается каждые 256 тиков * 0,5 мкс/такт = 128 мкс. Таким образом, каждые 128 мкс у вас будет как минимум ошибка задержки 4 мкс, если вы попытаетесь вызвать timer2.get_count() точно при вызове ISR. Если вам действительно не повезет, вы можете даже получить этот эффект дважды и ошибиться на целых 8us. При использовании стандартной функции micros(), поскольку она обновляется только каждые 256 тиков * 4 мкс/тик = 1024 мкс, вы получаете этот эффект ошибки 4 мкс в 8 раз реже. Это компромисс лучшего разрешения: вы также получаете более частый джиттер 4+us.

И просто для удовольствия, вот очень плохой. Обратите внимание на 20,50мкс — на 10,50us меньше!

time_elapsed_us = 15.00 <===
time_elapsed_us = 10.50
time_elapsed_us = 10.00
time_elapsed_us = 10.00
time_elapsed_us = 10.00
time_elapsed_us = 10.00
time_elapsed_us = 15.00 <===
time_elapsed_us = 10.50
time_elapsed_us = 10.00
time_elapsed_us = 20.50 <======
time_elapsed_us = 10.00
time_elapsed_us = 10.00
time_elapsed_us = 10.00
time_elapsed_us = 10.50
time_elapsed_us = 10.00
time_elapsed_us = 10.50
time_elapsed_us = 10.50
time_elapsed_us = 10.00
time_elapsed_us = 10.00
time_elapsed_us = 10.00

Используя модовые фильтры, медианные фильтры или другие фильтры, эти ложные результаты можно удалить, конечно, за счет снижения частотной характеристики измеряемого объекта (все это означает, что на самом деле требуется несколько измерений, чтобы узнать истинное значение, так же как нам нужно увидеть своими глазами несколько измерений выше, чтобы сделать вывод, что 10,0 мкс кажется правильным ответом).

Пример 2: неблокирующее (асинхронное) измерение времени внешнего события

Более сложный пример: измерьте, как долго на контакте INPUT 9 находится ВЫСОКИЙ уровень, и распечатайте время ожидания ВЫСОКОГО уровня каждый раз, когда он снова становится НИЗКИМ.

Как правило, используйте этот подход для любых входных событий, которые необходимо измерить с разрешением от 100 до ~200 мкс или выше. Вы можете использовать это на каждом отдельном выводе одновременно и получать хорошие результаты с разрешением примерно на этом уровне, в зависимости от того, сколько времени ваш основной цикл занимает для выполнения каждой итерации.

constexpr uint8_t PIN = 9;

void setup()
{
    Serial.begin(115200);
    pinMode(PIN, INPUT);
}

void loop()
{
    // Это будет измерять, как долго `SOME_PIN` находится в состоянии HIGH, в микросекундах.

    static uint32_t time_start_us = micros();
    bool time_just_acquired = false; // true, если только что было измерено новое значение времени
    uint32_t time_elapsed_us = 0;

    bool pin_state = digitalRead(PIN);
    static bool pin_state_old = LOW;
    
    if (pin_state == HIGH && pin_state_old == LOW)
    {
        // Пин едва достиг ВЫСОКОГО уровня, поэтому "запустите таймер" путем получения временной метки
        // время начала
        time_start_us = micros();
        pin_state_old = pin_state; // Обновить
    }
    else if (pin_state == LOW && pin_state_old == HIGH)
    {
        // Пин только что стал НИЗКИМ, поэтому "остановить таймер" путем получения временной метки
        // время окончания
        uint32_t time_end_us = micros();
        pin_state_old = pin_state; // Обновить
        time_elapsed_us = time_end_us - time_start_us;
        time_just_acquired = true;
    }

    // В другом месте позже по коду, где вам нужно это значение,
    // вы можете использовать это так. Здесь я просто печатаю значение.
    if (time_just_acquired)
    {
        time_just_acquired = false; // перезагрузить
        Serial.print("time_elapsed_us = "); 
        Serial.println(time_elapsed_us);
    }
}

ВАЖНО: обратите внимание, что во всех приведенных выше примерах я использую ТОЛЬКО переменные UNSIGNED INTEGER для меток времени. Это НЕОБХОДИМО. Использование целых чисел со знаком для меток времени таким же образом, как я написал их здесь, было бы нарушением стандарта C, потому что это приведет к неопределенному поведению, когда вы выполняете вычитание, которое приводит к потере значимости, или когда целое число имеет переполнение. Однако использование целых чисел без знака вполне допустимо. Пример: (uint8_t)0 - (uint8_t)1 = 255, потому что это 8-битное целое число без знака, которое безопасно понижается от наименьшего значения обратно к наибольшему значению. . Точно так же (uint8_t)255 + (uint8_t)1 = 0, потому что это 8-битное целое число без знака, которое безопасно переполняется от самого высокого значения обратно к самому низкому значению. Вот как работает time_elapsed_us = time_end_us - time_start_us в обоих моих примерах. Когда 32-битный микросекундный счетчик переполняется, что происходит каждые 70 минут или около того, он возвращается к 0. Это означает, что иногда time_end_us будет МЕНЬШЕ, чем time_start_us, и вы можете получить такое измерение: time_elapsed_us = 124 - 4294967295, что равно 125.

Пример 3: использование внешних прерываний для обнаружения изменений на выводах для измерения внешних событий

Используйте этот подход, когда вам нужно измерять внешние события с разрешением 4–10 мкс или выше на максимум 2 контактах одновременно.

Это действительно хороший подход для измерения внешних событий, но вы получаете только 2 вывода на Arduino Uno или Nano или аналогичный, которые могут это сделать. Это контакты 2 или 3. См. таблицу здесь: https://www.arduino .cc/reference/en/language/functions/external-interrupts/attachinterrupt/.

Для демонстрации см. ответ Эдгара Боне здесь.

Пример 4: использование прерываний смены контакта для измерения внешних событий

Используйте этот подход, если вам нужно измерять внешние события с разрешением 4–10 мкс или выше на > Максимум 2 контакта за раз.

Они аналогичны внешним прерываниям, за исключением того, что вы должны управлять до 8 выводами в одной подпрограмме обслуживания прерываний (ISR), а не только 1 выводом на ISR, поэтому они не так хороши, как «внешние прерывания». Каждый цифровой вывод на Arduino Uno или Nano может сделать это. Я использую этот подход, например, при считывании многих сигналов ШИМ от приемника радиоуправления, но он требует некоторой сложности и кольцевого буфера. сделать это правильно, так как время в ISR должно быть сведено к минимуму, иначе вы получите тонны джиттера повсюду! Это означает, что вы просто берете метку времени в ISR, сохраняете ее в кольцевом буфере и выходите. ВЫ НИЧЕГО НЕ ДЕЛАЕТЕ! Без вычитания, без вычислений, без определения того, какой вывод сработал, ничего! Затем вы обрабатываете кольцевой буфер временных меток и состояний выводов в своем основном цикле, чтобы определить, какой вывод изменился, и выполняете математические операции, чтобы получить новое время. чтение на этом штифте. Я использовал это для передачи сигналов через гексакоптер, стреляющий боевыми роботами, который летал по ABC TV. Это сработало хорошо. Меня очень порадовало, что ISR выполняет свою работу.

Пример 5: использование захвата входных данных (только на контакте 8) для измерения внешнего события

Это "золотой" или "лучший" подход. Но вы получаете 1 контакт на Arduino Uno или Nano, который может это сделать. Используйте этот подход, когда вам нужно измерять внешние события с разрешением 62,5 наносекунды или выше, без джиттера. При таком подходе не будет НИКАКОЙ ЗАДЕРЖКИ ВРЕМЕННОЙ МЕТКИ ISR, что действительно здорово.

Захват ввода доступен только для 16-битных таймеров на 8-битных микроконтроллерах AVR, таких как ATmega328. Поскольку Uno или Nano имеют только 1 16-битный таймер, это означает, что они получают 1 один входной контакт захвата. Это контакт 8. Не тратьте этот контакт ни на что другое, если вам нужны идеальные измерения времени внешних событий с использованием захвата ввода. Захват входных данных является «идеальным»; способ измерения внешних событий, поскольку он сохраняет отсчет времени в аппаратном регистре в момент возникновения события, без взаимодействия с процессором через ISR, что, как мы знаем, может вызвать задержку и дрожание в 4+ мкс.

Сначала я сделал это на своем коммерческом продукте, который должен был считывать один вывод PWM приемника Radio Control. Я был рад видеть, что он работает правильно, так как у него нулевой джиттер. Я вернусь и добавлю демоверсию (только код, без упоминания продукта), если у меня будет шанс. Этот метод также идеально для считывания сигналов PPM (импульсно-позиционная модуляция), которые представляют собой просто набор мультиплексированных сигналов PWM радиоуправления.

,

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

Этого не должно быть. Можешь сделать скриншот? Недавно я обновил свои объявления до автоматических объявлений Google. Мне нужно настроить его и уменьшить их. Я позабочусь об этом, как только смогу, но я не знаю, какой адрес электронной почты или пожертвование может быть запрошено для продолжения., @Gabriel Staples

Кроме того, я никогда не слышал о блокировке всего вопроса по одной ссылке. Это кажется действительно странным., @Gabriel Staples

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


1

Ваш заголовок касается «установки таймера», но на самом деле ваш вопрос касается измерение длины импульса. Существуют две функции, предоставляемые Arduino IDE для этой цели, pulseIn() и pulseInLong():

  • pulseIn() основан на тщательно синхронизированной петле задержки. Это имеет разрешение порядка одной микросекунды, но не будет считаться время, затраченное на обслуживание запросов на прерывание. Лучше всего работает для очень короткие импульсы, синхронизированные с отключенными прерываниями.
  • pulseInLong() основан на micros(). Оно имеет разрешением 4 мкс и будет не работать должным образом, если прерывания выключено. Он лучше всего работает для более длинных импульсов, где он ограничен. разрешение и задержка прерывания допустимы.

Обе эти функции являются блокирующими: они полностью блокируют скетч во время выполнения измерения. Если вы не хотите, чтобы скетч чтобы не отвечать в течение этого времени, вы можете написать неблокирующий версия pulseInLong() с использованием машины с конечным числом состояний, например это:

// Измерение длины импульса неблокирующим способом.
// Возвращает 0, если во время вызова нет доступных измерений.
void get_pulse_length() {
    static enum {
        INITIAL_WAIT,    // ждем окончания первого (частичного) импульса
        BETWEEN_PULSES,  // ждем начала импульса
        WITHIN_PULSE     // ждем окончания импульса
    } pulse_state = INITIAL_WAIT;
    static uint32_t pulse_start;  // когда начался текущий импульс

    uint8_t pin_state = digitalRead(pulse_pin);
    uint32_t now = micros();
    switch (pulse_state) {
        case INITIAL_WAIT:
            if (pin_state == LOW)
                pulse_state = BETWEEN_PULSES;
            break;
        case BETWEEN_PULSES:
            if (pin_state == HIGH) {
                pulse_start = now;
                pulse_state = WITHIN_PULSE;
            }
            break;
        case WITHIN_PULSE:
            if (pin_state == LOW) {
                pulse_state = BETWEEN_PULSES;
                return now - pulse_start;
            }
            break;
    }
    return 0;
}

Обратите внимание, что при этом измеряются высокие импульсы. Вам придется поменять местами HIGH и LOW, если вы хотите измерять низкие импульсы. Вы бы использовали это так:

void loop() {
    uint32_t pulse_length = get_pulse_length();
    if (pulse_length) {
        // обрабатывать импульс
    }
}

Разрешение измерения – это время выполнения loop(), поэтому вы должны убедиться, что там ничего не блокирует, и особенно нет вызовы delay(). Если вам нужно лучшее разрешение от неблокирующего метод, вы можете использовать прерывания для запуска процесса измерения:

volatile uint32_t pulse_start, pulse_length;
volatile bool pulse_valid;

void on_rise() {
    pulse_start = micros();
    attachInterrupt(digitalPinToInterrupt(pin), on_fall, FALLING);
}

void on_fall() {
    pulse_length = micros() - pulse_start;
    pulse_valid = true;
    attachInterrupt(digitalPinToInterrupt(pin), on_rise, RISING);
}

uint32_t get_pulse_length()
{
    if (!pulse_valid) return 0;
    noInterrupts();
    uint32_t pulse_length_copy = pulse_length;
    pulse_valid = false;
    interrupts();
    return pulse_length_copy;
}

void setup() {
    attachInterrupt(digitalPinToInterrupt(pin), on_rise, RISING);
}

Это должно дать вам разрешение micros(), то есть 4 мкс, но иногда вы можете получить результаты, которые немного отличаются, если прерывания оказывается отключенным при переходе ввода. Если это неприемлемо, единственный другой вариант, который я вижу, это использовать аппаратный таймер с возможностью захвата ввода. надо смотреть даташит вашего микроконтроллера, чтобы увидеть, как он работает, и, возможно, выполнить поиск в Интернете для «захвата ввода Arduino».

,