При разработке программ для микроконтроллеров довольно часто требуется делать задержки в программе, например, для ожидания какого-то события. Обычно для этого используются таймеры. Однако бывают случаи, когда таймер использовать почему-то не хочется (или нецелесообразно). Ну или может у микроконтроллера вообще нет таймера (хотя у современных, если я правильно понимаю, они есть у всех).
В этом случае можно сделать задержку достаточно простым способом, например, вызывая в цикле команду NOP, которая ничего не делает.
О том, как узнать время выполнения одной команды, я рассказывал здесь.
К примеру, мы выяснили, что команда NOP при тактовой частоте 8 МГц выполняется за 0,125 микросекунд. Тогда, чтобы получить задержку в программе 125 микросекунд, нам надо вызвать эту команду 1000 раз.
Конечно, записывать в исходных кода 1000 раз команду NOP - это не лучшее решение. Поэтому напрашивается решение - вызвать эту команду в цикле 1000 раз. Однако, для организации цикла потребуются другие команды, и время их выполнения также надо учитывать.
Сразу перейдём к примеру:
Это пример для микроконтроллера AVR, а конкретно для модели ATtiny13A. Хотя комментарии имеются, я всё-таки сделаю некоторые пояснения, поскольку новичкам может что-то оказаться непонятным. Про зажигание светодиода говорить не буду. Если нужны разъяснения, то их можно найти здесь. Расскажу только о нашей теме - о задержке.
Итак, сначала мы зажигаем светодиод, а перед тем, как его погасить, делаем задержку. То есть наша задача - зажечь светодиод и погасить его через какое-то время. Задержка начинается с команды
LDI R16, 5
Которая записывает в регистр R16 число 5 - мы будем выполнять цикл пять раз. Время выполнения этой команды надо учитывать при вычислении общего времени задержки.
Затем следует метка с именем Loop, которая нужна нам для организации цикла. В теле цикла мы вызываем один раз команду NOP, которая выполняется за один такт, а затем отнимаем единицу от содержимого регистра R16 командой SUBI, которая также занимает один такт.
Если после этого в регистре R16 будет 0, то флаг нуля Z будет установлен. Иначе этот флаг будет сброшен. Состояние этого флага мы проверяем командой BRNE. Эта команда выполняется за один такт, если флаг нуля сброшен, и за два такта, если флаг нуля установлен (а он будет установлен, если после выполнения команды SUBI в регистре R16 окажется ноль, то есть когда будет выполнено 5 итераций цикла). Таким образом в теле цикла у нас будет три команды, каждая из которых выполняется за 1 такт. Следовательно, за пять итераций мы получаем время выполнения цикла 3*5=15 тактов. К этому мы добавляем ещё один такт, который заняла у нас команда LDI, и ещё один такт от команды BRNE (поскольку последний раз эта команды выполняется за два такта, а не за один).
В итоге получаем, что наша задержка отнимет у процессора 17 тактов. При частоте 8 МГц это будет 17*0,125=2,125 микросекунд. Конечно, это очень маленькая задержка, которую человеческий глаз просто не заметит. Но я лишь показал способ. Для реальных устройств увеличить задержку можно:
- Увеличив количество команд NOP в теле цикла
- Уменьшив тактовую частоту
- Увеличив количество итераций цикла
Чаще всего, конечно, выбирают увеличение количества команд NOP и/или увеличение количества итераций, поскольку уменьшать тактовую частоту только из-за задержки - это в большинстве случаев неоправданно.
Однако увеличивать количество команд NOP до бесконечности нельзя (устанем писать или копировать, да и при большом количестве команд можно сбиться со счёта, и тогда задержка окажется не такой, как мы думали). Поэтому придётся таки увеличивать количество итераций цикла.
Однако дешёвые микроконтроллеры, как правило, 8-разрядные. То есть в одном регистре можно хранить только число не более 255. Поэтому придётся использовать два регистра (или более), что усложнит программу. Но это уже другая история…
Скажу только, что, например, чтобы сделать задержку хотя бы в 0,25 секунды (250000 микросекунд), чтобы хоть как-то было заметно мигание светодиода, потребуется
250000 / 0,125 = 2000000 тактов
Для такого числа даже двух 8-разрядных регистров не хватит. И чтобы ограничиться хотя бы двумя регистрами при частоте 8 МГц, надо чтобы одна итерация цикла занимала более 30 тактов:
2000000 / 65535 = 30,52
то есть в тело цикла вам надо будет поместить 30 команд, которые выполняются за 1 такт. Либо использовать вложенные циклы.
Но тут надо сказать, что большие задержки таким образом организуются редко. Такой способ обычно применяется, когда надо выполнить задержку в несколько микросекунд или миллисекунд, например, для защиты от дребезга контактов.
Ну и ещё одно замечание. Если уж делать таким образом мигание светодиодом, то после гашения светодиода потребуется ещё одна такая же задержка (чего нет в примере, чтобы не загружать его лишним кодом). Иначе светодиод будет погашен и тут же снова включен, так что его гашение глаз не увидит.
И да, если я где-то ошибся в расчётах - просьба сильно не ругать ))) Я хотел показать лишь принцип, а детали уже зависят от конкретных задач, и в примере не так важны.
На этом всё. Подписывайтесь на канал, чтобы ничего не пропустить…