Использование millis () и micros () внутри подпрограммы прерывания

13

Документация для attachInterrupt()говорит:

... millis()полагается на количество прерываний, поэтому оно никогда не будет увеличиваться внутри ISR. Поскольку delay()для работы требуются прерывания, он не будет работать, если вызывается внутри ISR. micros()Первоначально работает, но через 1-2 мс начнет работать беспорядочно. ...

Чем они micros()отличаются millis()(кроме, конечно, их точности)? Означает ли приведенное выше предупреждение, что использование micros()внутри подпрограммы прерывания всегда плохая идея?

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

Петр Пудлак
источник

Ответы:

16

Другие ответы очень хороши, но я хочу уточнить, как это micros()работает. Он всегда читает текущий аппаратный таймер (возможно TCNT0), который постоянно обновляется аппаратными средствами (фактически каждые 4 мкс из-за прескалера 64). Затем он добавляет счетчик переполнения Таймера 0, который обновляется прерыванием переполнения таймера (умноженным на 256).

Таким образом, даже внутри ISR вы можете рассчитывать на micros()обновление. Однако, если вы будете ждать слишком долго, вы пропустите обновление переполнения, и тогда возвращаемый результат будет уменьшен (то есть вы получите 253, 254, 255, 0, 1, 2, 3 и т. Д.)

Это micros()немного упрощено для удаления определений для других процессоров:

unsigned long micros() {
    unsigned long m;
    uint8_t oldSREG = SREG, t;
    cli();
    m = timer0_overflow_count;
    t = TCNT0;
    if ((TIFR0 & _BV(TOV0)) && (t < 255))
        m++;
    SREG = oldSREG;
    return ((m << 8) + t) * (64 / clockCyclesPerMicrosecond());
}

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


TLDR;

  • Не делайте задержек внутри ISR
  • Если вы должны сделать их, вы можете время с, micros()но нет millis(). Также delayMicroseconds()есть возможность.
  • Не откладывайте более 500 мкс или около того, иначе вы пропустите переполнение таймера.
  • Даже короткие задержки могут привести к тому, что вы пропустите входящие последовательные данные (при 115200 бод вы будете получать нового персонажа каждые 87 мкс).
Ник Гаммон
источник
Не в состоянии понять приведенное ниже утверждение, которое используется в micros (). Не могли бы вы уточнить? Поскольку ISR записано, флаг TOV0 будет сброшен, как только будет введен ISR, и, следовательно, приведенное ниже условие может не стать истинным !. if ((TIFR0 & _BV (TOV0)) && (t <255)) m ++;
Раджеш
micros()не ISR. Это нормальная функция. Флаг TOV0 позволяет вам проверить оборудование, чтобы увидеть, произошло ли переполнение таймера (но еще не было обработано).
Ник Гэммон
Поскольку тест выполняется с отключенными прерываниями, вы знаете, что флаг не изменится во время теста.
Ник Гэммон
Таким образом, вы хотите сказать, что после cli () внутри функции micros () происходит ли переполнение, что нужно проверить? Это имеет смысл. В противном случае, хотя micros не является функцией, TIMER0_OVF_vect ISR очистит TOV0 в TIFR0 - это то, чем я сомневался.
Раджеш
Кроме того, почему TCNT0 сравнивается с 255, чтобы увидеть, если он меньше, чем 255? После cli (), если TCNT0 достигнет 255, что произойдет?
Раджеш
8

Это не неправильно использовать millis()или в micros()рамках программы прерывания.

Это является неправильным использовать их неправильно.

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

Таким образом, вы можете позвонить millis()или micros()узнать текущее время в вашем ISR, но не ожидайте, что это время изменится.

Это отсутствие изменений во времени, о которых предупреждают в приведенной вами цитате. delay()полагается на millis()изменение, чтобы знать, сколько времени прошло. Так как это не изменится, delay()никогда не закончится.

Так что, по существу , millis()и micros()сообщит вам время , когда ваш ISR не был вызван независимо от того , когда в вашем ISR вы используете их.

Маженко
источник
3
Нет, micros()обновления. Он всегда читает регистр аппаратного таймера.
Ник Гэммон
4

Цитируемая фраза - это не предупреждение, а просто заявление о том, как все работает.

Нет ничего плохого в использовании millis()или micros()в правильно написанной программе обработки прерываний.

С другой стороны, делать что-либо вообще в неправильно написанной подпрограмме прерывания по определению неправильно.

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

Вкратце: правильно написанная процедура прерывания не вызовет и не столкнется с проблемами millis()или micros().

Редактировать: Что касается того, «почему micros ()« начинает вести себя беспорядочно »», как объясняется на веб-странице « Проверка функции Arduino micros », micros()код на обычном Uno функционально эквивалентен

unsigned long micros() {
  return((timer0_overflow_count << 8) + TCNT0)*(64/16);
}

Это возвращает четырехбайтовую длинную без знака, состоящую из трех младших байтов из timer0_overflow_countи одного байта из регистра отсчета таймера-0.

Значение параметра timer0_overflow_countувеличивается примерно раз в миллисекунду TIMER0_OVF_vectобработчиком прерывания, как объяснено при рассмотрении веб-страницы функции миллидюйма arduino .

Перед началом обработчика прерываний аппаратное обеспечение AVR отключает прерывания. Если (например) обработчик прерываний будет работать в течение пяти миллисекунд с прерываниями, которые все еще отключены, то по крайней мере четыре переполнения таймера 0 будут пропущены. [Прерывания, написанные в коде C в системе Arduino, не являются реентерабельными (способными правильно обрабатывать несколько перекрывающихся выполнений в одном и том же обработчике), но можно написать обработчик реентерабельного языка ассемблера, который повторно активирует прерывания до того, как он начнет трудоемкий процесс.]

Другими словами, переполнения таймера не «складываются»; всякий раз, когда переполнение происходит до обработки прерывания от предыдущего переполнения, millis()счетчик теряет миллисекунду, а расхождение, timer0_overflow_countв свою очередь, также дает micros()ошибку на миллисекунду.

Что касается «короче 500 мкс» в качестве верхнего предела времени для обработки прерываний, «чтобы предотвратить слишком длительное блокирование прерывания таймера», вы можете подняться чуть менее 1024 мкс (например, 1020 мкс) и по- millis()прежнему работать, большая часть время. Тем не менее, я рассматриваю обработчик прерываний, который занимает более 5 мкс как медлительный, более 10 мкс как ленивый, более 20 мкс как улитка.

Джеймс Уолдби - jwpat7
источник
Не могли бы вы рассказать о том, «как все работает», в частности, почему micros()«начать вести себя странно»? А что вы подразумеваете под "правильно написанной программой прерываний"? Я предполагаю, что это означает «короче, чем 500 мкс» (чтобы предотвратить слишком длительное блокирование прерывания таймера), «используя переменные переменные для связи» и «не вызывая библиотечный код», насколько это возможно, есть что-нибудь еще?
Петр Пудлак
@ PetrPudlák, см. Редактирование
Джеймс Уолдби - jwpat7