Замена стандартной функции _delay_ms()

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

Итак, один единственный минус - нужно пожертвовать одним с таймеров, я в своих проектах очень редко использую Таймер 0, вот на нем и будем делать задержки. Алгоритм такой: вычисляем, сколько тиков таймер сделает за одну милисекунду (назовем tics), далее обнуляем счетный регистр таймера (TCNT0) и ждем, пока значение в этом регистре превысит установленное значение tics. Вычислить tics очень просто: F_CPU/делитель/1000. F_CPU - тактовая частота микроконтроллера, делитель - это пределитель таймера, узнать который можно из даташита. Вот с ДШ на Atmega8 (у большинства Atmega настройки такие же):Предположим, что тактовая частота нашего микроконтроллера 10Мгц, выберем пределитель на 64 и в инициализации периферии (там, где настройки портов, таймеров и т.д.) пишем:
TCCR0 = (1<<CS01) | (1<<CS00); // prescaler = 64

Теперь, вот так можно сделать задержку в 1мс:
#define TIKS_1MS (F_CPU/64/1000)
TCNT0 = 0;
while(TCNT0 < TIKS_1MS);

Тоесть, обнуляем таймер и ждем пока его счетный регистр превысит расчетное количество тиков за 1мс.

Чтобы было удобно пользоваться такими задержками, напишем отдельную функцию:
void timerDelayMs(unsigned long int ms)
{
#define TIKS_1MS (F_CPU/64/1000)
while(ms--){
TCNT0 = 0;
while(TCNT0 < TIKS_1MS);
}
}

// Вызывать вот так:
timerDelayMs(300); // задержка на 300 мс

Как видно, мы просто выполняем задержку в 1мс указанное количество раз.

Для большей точности задержки, надо выбирать как можно меньший переделитель. Но не стоит забывать об максимальном значении счетного регистра. У нас Тамер 0 - восьмибитный, и максимально значение его счетного регистра - 255, при переполнении он обнуляется. Поэтому, делитель надо подобрать так, чтобы F_CPU/64/1000 было не более 254. У нас 10000000/64/1000 = 156,25. Был бы пределитель 32, то 10000000/32/1000 = 312,5.

Итак, теперь задержка может использоваться при отключенной оптимизации и ей не страшны прерывания. Но, она так же как и _delay_ms() тупит в цикле и ничего не делает. Добавим немного кода:
void RunTasks(void)
{
wdt_reset();
}

void timerDelayMs(unsigned long int ms)
{
#define TIKS_1MS (F_CPU/64/1000)
while(ms--){
TCNT0 = 0;
while(TCNT0 < TIKS_1MS) RunTasks();
}
}

Теперь, во время задержки, будет выполняться функция RunTasks(). Конечно, эта функция должна выполняться не более 900 МИКРОсекунд, но для сброса сторожевого таймера или проверки каких то регистров, этого вполне достаточно.

Чтобы постоянно не приходилось рассчитывать делитель и чтобы функция не торчала в главном файле, я вынес ее в отдельную библиотеку.

Скачать delay_lib (1 Kb).

Комментарии

  1. я с _delay_us(); подобный трюк проделать можно?

    ОтветитьУдалить
  2. да, только при больших частотах, так как даже при 16 мгц одна микросекунда - это 16 тиков.

    ОтветитьУдалить

Отправить комментарий

Популярные сообщения из этого блога

Прием команд с пульта дистанционного управления

STM32F4. Обновление прошивки с карты памяти (Bootloader SD)

Работа с шиной 1-wire. Подключение термодатчика DS18B20 к AVR