Використання 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, а значить, нижче умова може не стати істинним !. якщо ((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().

Редагувати: Щодо "чому мікрос ()" починає поводитися нерівномірно "", як пояснено у веб-сторінці " експертиза функції 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 будуть пропущені. [Переривання, записані в коді С в системі Arduino, не є ретентивними (здатні правильно обробляти декілька виконуваних перекриттям виконання в одному і тому ж обробнику), але можна було б написати обробник мови збірки реєранта, який повторно використовує переривання до того, як розпочне трудомісткий процес.]

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

Що стосується "коротшого, ніж 500 мкс", як верхнього часового обмеження для обробки перерв, "щоб запобігти занадто довгому блокуванню переривання таймера", ви можете піднятись трохи менше 1024 мкс (наприклад, 1020 мкс) і millis()все одно буде працювати, більшість час. Однак я вважаю, що обробник перерв, який займає більше 5 мкс як млявий, більше 10 мкс - лінивий, понад 20 мкс - як слимаковий.


Чи можете ви, будь ласка, детальніше розповісти про "як все працює", зокрема, чому micros()"почніть поводитись хаотично"? А що ви маєте на увазі під "правильно написаним рутинним перериванням"? Я припускаю, що це означає "коротше, ніж 500us" (щоб запобігти занадто довгому блокуванню переривання таймера), "використовувати летючі змінні для зв'язку" та "не викликати код бібліотеки" якомога більше, чи є ще щось?
Петро Пудлак

@ PetrPudlák, див. Редагувати
Джеймс Уолдбі - jwpat7
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.