Функции задержки avr-libc — самый простой способ подождать на AVR: ядро крутит цикл заданное число тактов. Но сколько тактов получится на самом деле, зависит от частоты F_CPU, от реализации (встроенная функция компилятора или старые циклы), от округления и от того, не превышен ли предел. Калькулятор повторяет расчёт из исходников util/delay.h и util/delay_basic.h.
Как считаются задержки
Если компилятор поддерживает встроенную функцию __builtin_avr_delay_cycles и включена оптимизация, _delay_ms(t) и _delay_us(t) переводят время в такты:
и компилятор вставляет цикл ровно на N тактов. По умолчанию такты округляются вверх, чтобы задержка была не короче заданной; макросы __DELAY_ROUND_DOWN__ и __DELAY_ROUND_CLOSEST__ меняют округление на вниз и к ближайшему. Наибольшее значение — 0xFFFFFF00 тактов, то есть 4294967,04 / f_МГц мс: около 71 минуты при 1 МГц.
Без встроенной функции — или если определён __DELAY_BACKWARD_COMPATIBLE__ — работает старая реализация на двух циклах из util/delay_basic.h:
| Функция | Цикл | Тактов на проход | Наибольшая задержка |
|---|---|---|---|
_delay_loop_1(n) | 8-битный счётчик, DEC и BRNE | 3 | 768 / f_МГц мкс (n = 256) |
_delay_loop_2(n) | 16-битный счётчик, SBIW и BRNE | 4 | 262,14 / f_МГц мс (n = 65 536) |
Старый _delay_ms делит время на проходы _delay_loop_2 с отбрасыванием дробной части, а если их больше 65 535, переходит на шаги по 0,1 мс — до 6,5535 с независимо от частоты. Старый _delay_us использует _delay_loop_1, для больших значений — _delay_loop_2, а дальше вызывает _delay_ms. Пересчёт идёт с отбрасыванием дробной части, поэтому задержка получается чуть короче заданной: 10 мкс при 16 МГц — это 53 прохода по 3 такта, 9,94 мкс.
Условия, без которых задержки врут
- Оптимизация включена, задержка — константа. avr-libc прямо требует обоих условий: иначе время пересчитывается в такты при выполнении, в числах с плавающей точкой, задержка становится намного длиннее и непредсказуемой, а к программе подключается библиотека плавающей точки.
- F_CPU совпадает с реальной частотой. Макрос задаётся до подключения
util/delay.hили ключом-D F_CPU=.... Для Arduino Uno — 16 МГц, для ATmega328P с заводскими фьюзами — 1 МГц (внутренний генератор 8 МГц с делителем на 8); частоту, которую задают фьюзы, показывает калькулятор фьюзов AVR. - Прерывания удлиняют задержку. Функции не запрещают прерывания, и время обработчиков прибавляется к циклу. Для точного отсчёта и для задержек, во время которых процессор должен делать что-то ещё, используйте таймер — расчёт в калькуляторе таймера.
Такты команд циклов — DEC и BRNE, SBIW и BRNE — описаны в статье о системе команд AVR.
Частые вопросы
Какая максимальная задержка у _delay_ms?
Если компилятор поддерживает __builtin_avr_delay_cycles (avr-gcc с оптимизацией), — 4294967,04 / f_МГц миллисекунд, около 71 минуты при 1 МГц и 4,5 минуты при 16 МГц; больше задержка обрезается. В старой реализации — 262,14 / f_МГц мс с полной точностью (16,4 мс при 16 МГц), а дальше до 6,5535 с с шагом 0,1 мс.
Почему _delay_ms работает медленнее, чем задано?
Три частые причины. Компиляция без оптимизации (-O0): avr-libc требует включённой оптимизации и константы на этапе компиляции, иначе задержка непредсказуемо растёт, а в программу подключается библиотека плавающей точки. Неверное значение F_CPU — например, 16 МГц в коде при работе от внутреннего генератора 8 МГц. И прерывания: функции не запрещают их, и время обработчиков прибавляется к задержке.
Можно ли передать в _delay_ms переменную?
Нельзя: задержка должна быть константой, известной при компиляции, иначе пересчёт в такты выполняется во время работы в числах с плавающей точкой — задержка выходит намного длиннее и раздувается код. Для переменной задержки вызывают в цикле _delay_ms(1) или используют таймер.
Источники
- avr-libc. util/delay.h и util/delay_basic.h (репозиторий avrdudes/avr-libc) — _delay_ms и _delay_us: __builtin_avr_delay_cycles с округлением вверх, __DELAY_ROUND_DOWN__ и __DELAY_ROUND_CLOSEST__, насыщение 0xFFFFFF00 тактов — 4294967,04 / f_МГц мс; старая реализация: 262,14 / f_МГц мс, затем шаг 0,1 мс до 6,5535 с, 768 / f_МГц мкс; _delay_loop_1 — 3 такта, _delay_loop_2 — 4 такта на проход (эталон тестов)
- avr-libc. Руководство пользователя: <util/delay.h> — Convenience functions for busy-wait delay loops — те же пределы; требование оптимизации и константной задержки; прерывания удлиняют задержку
- Microchip. AVR Instruction Set Manual (DS40002198C) — DEC — 1 такт, SBIW — 2, BRNE — 2 при переходе: 3 и 4 такта на проход циклов (второй источник тестов)
- Microchip. ATmega48A/PA/88A/PA/168A/PA/328/P Data Sheet (DS40002061B) — 9.2.1 — поставляется с внутренним генератором 8 МГц и запрограммированным CKDIV8: системная частота 1 МГц
Встроить калькулятор на свой сайт
Скопируйте код и вставьте его в HTML страницы, статьи или вики. Виджет бесплатный, без рекламы, счётчиков и cookie. Условие одно — ссылка на GAW под рамкой остаётся на месте. Подробнее о виджетах
<iframe src="https://gaw.ru/embed/avr-delay/" title="Задержки AVR: _delay_ms, _delay_us и циклы avr-libc" width="100%" height="640" style="border:1px solid #e3e5e9;border-radius:8px" loading="lazy" allow="clipboard-write"></iframe>
<p style="margin:6px 0 0;font-size:14px">Калькулятор: <a href="https://gaw.ru/tools/avr-delay/">Задержки AVR: _delay_ms, _delay_us и циклы avr-libc</a> — GAW.ru</p>Автоподстройка высоты рамки
Если сайт разрешает скрипты, добавьте этот код один раз в любом месте страницы: рамка будет менять высоту вместе с результатом расчёта.
<script>window.addEventListener("message",function(e){if(e.origin!=="https://gaw.ru"||!e.data||e.data.type!=="gaw:embed-height")return;var f=document.querySelectorAll('iframe[src^="https://gaw.ru/embed/"]');for(var i=0;i<f.length;i++)if(f[i].contentWindow===e.source)f[i].style.height=e.data.height+"px"})</script>Обновлено .
Нашли ошибку в расчёте?