Формирование программных задержек. Раздел: Начинающим.

Eugene's MCU

Пятница, 04.10.2013, 13:35

Главная | | Мой профиль | Выход | RSS
Меню сайта
Категории каталога
Рекламный блок





Рекомендовать этот сайт:

Главная » Статьи » » Начинающим


Формирование программных задержек
       В статье по адресу http://eugenemcu.ru/index/0-11 мы познакомились с общими принципами построения программы для микроконтроллера. Теперь, мы рассмотрим некоторые способы временнОй организации программы, то есть позаботимся о том, чтобы некие программные события происходили в нужный момент и продолжались в течение нужного интервала времени.
       Обратимся к простейшему и самому любимому для всех начинающих практическому примеру. Итак, необходимо чтобы:

       • светодиод зажёгся;
       • далее светился 0,5 секунды;
       • затем вдруг потух;
       • и сидел себе потухшим ещё 0,5 секунды;
       • далее всё заново по кругу.

       Очевидно, что в данном примере необходимо реализовать две программные задержки с длительностями в 0,5 секунды. Самое простейшее решение – после засвечивания светодиода заставить микроконтроллер толочь воду в ступе в течение 0,5 секунды. И в самом деле – время выполнения конкретной команды микроконтроллером постоянно и зависит от того, с какой частотой мы его тактируем, искомый временной интервал известен, остаётся подобрать задачу, на которую контроллер потратил бы N-ое количество команд и пол секунды времени. Например, отнимать от огромного числа единицу пока оно не станет равно нулю. Занятие не очень полезное, однако, и сама поставленная задача остроумием не блещет.

       На языке Си это может выглядеть примерно так:

       PORTB|=(1<<LED);           // Зажечь светодиод;
       long Chislo = 100000;        // Задать начальное значение числа.
       while (Chislo>0)                // Пока число больше нуля…
       {
               Chislo--;                   // Отнимать от него единицу.
       }
       …                                      // а сюда программа выберется, когда число станет равным нулю.
       PORTB&=~(1<<LED);        // Потушить светодиод;
       Chislo = 100000;               // И снова задержка...
       while (Chislo<0)
       {
              Chislo--;
       }

       Надо отметить, что на самом деле отнятие единицы с последующей проверкой на равенство нулю занимает далеко не одну команду микроконтроллера, кроме того, многие компиляторы, желая оптимизировать этот код могут вообще выбросить из него бездумное отнимание единицы от числа. Поэтому конкретное начальное значение переменной "Chislo” для заданной тактовой частоты проще подобрать опытным путём.
       Казалось бы, чего же боле? Задержка реализована.
       Однако на практике задачи решаемые программой выглядят немного сложнее. И заставлять процессор 99.99% времени тратить на декремент (вычитание единицы) переменной, в то время как он мог бы совершенно бесплатно заниматься другим полезным делом, мягко говоря, нерационально. Хотя достаточно часто подобный способ задержки имеет право на жизнь, однако ещё чаще принято формировать интервалы при помощи аппаратных таймеров. 

       Таймер – это 8-и или 16-и разрядный регистр способный прибавлять (или отнимать) в каждом такте единицу к своему текущему значению. Таймеры могут подключаются к тактовой частоте мк через «предделители». Тогда частота изменения их значений будет снижена в 2, 4 и т. д. раза, в зависимости от значения записанного в предварительный делитель. Способность таймеров вызывать прерывания по переполнению значения (т.е. останавливать обычный ход нашей программы и отправлять её на выполнение определенного куска кода при переходе значения таймера из максимально возможного в нулевое) позволяет нам выполнять определённые действия через определённые интервалы времени.
       Рассмотрим решение на примере микроконтроллера Atmega16 с тактовой частотой 2 МГц с использованием 16-и разрядного таймера Т1:

       1. Используя автоматический расчёт числа тактов таймера на странице "Инициализация таймеров...", определяем количество тактов, укладывающихся интервале времени 500 мс при частоте генератора 2 МГц и коэффициенте предварительного делителя равном 64 :
       N=15625;

       2. Вычисляем начальное значение содержимого таймера, которое через N тактов приведёт к его переполнению:
       M= 2^16 – 15625 = 65536 – 15625 = 49911 = 0xC2F7;

       3. Тогда инициализационный код таймера Т1:
 
       TCCR1B |=(1<<CS10)|(1<<CS11); // Предделитель для T1 - 64. (частота таймера 2МГц/64=31250 Гц)
       TIMSK |=(1<<TOIE1); // Разрешить прерывание по переполнению T1.
       SREG |= (1<<7); // Разрешить все прерывания.
       TCNT1H=0xC2;
TCNT1L=0xF7; // Инициализация счётчика.
 
       4. После инициализации приведённым кодом через каждые 500мс микроконтроллер будет обращаться к обработчику прерывания, где необходимо снова инициализировать таймер на заданный интервал времени и реализовать желаемое событие, в нашем случае смену состояния светодиода:
 
       #pragma vector=TIMER1_OVF_vect
       __interrupt void T1_OVER_INT() // Отработчик прерывания по переполнению Т1.
       {
              TCNT1H=0xC2; TCNT1L=0xF7; // Инициализация счётчика.
              PORTB ^=(1<<LED); // Изменить состояние светодиода.
       }
 
       Теперь искомые интервалы формируются аппаратно без существенного вмешательства программы, которая может заниматься решением более важных задач.
       Частым источником мелких неточностей при инициализации таймеров становиться нежелание учитывать небольшое значение, которое успевает насчитать таймер с момента его переполнения до очередной записи в него рассчитанного значения. В этом случае младший байт инициализирующего значения необходимо не просто записывать, а суммировать с "натикавшим" значением счётчика. Однако при достаточно больших значениях предделителя за счёт команд записи таймер наверняка нараститься не успеет. В любом случае не будет лишним проверить в симуляторе состояние счётчика в момент инициализации.
 
       Несколько проще в расчётах использование регистра сравнения OCR. При этом расчитанное число тактов N записывается в указаный регистр и прерывание формируется при совпадении этого значения с текущим значением таймера.
   
       TCCR1B |= (1<<CS10)|(1<<CS11); // Предделитель 64.
       OCR1AH=0x3D; OCR1AL=0x09; // Число N=15625=0x3D09.
       TIMSK |=(1<<OCIE1A); // Разрешение прерывания по совпадению с регистром сравнения OCR1A.
       SREG  |=(1<<7); // Разрешить все прерывания.
 
       Далее в отработчике необходимо сбрасывать таймер:
 
       #pragma vector=TIMER1_COMPA_vect
       __interrupt void T1_COMPARE()
       {
              TCNT1H=0; TCNT1L=0; // Сброс таймера.
              PORTB ^=(1<<LED); // Изменить состояние светодиода.
       }
 
         Примечания:
       - не следует забывать, что 16-и разрядные регистры в AVR записывются в строгой последовательности - сначала старший, а затем младший байт.
       - имена регистров, их битов и объявлений обработчиков прерываний приведены под компилятор IAR;
   
         Смотрите также материалы:
       - справочные данные по 8 -разрядным таймерам T0/T2 AVR.
       - справочные данные по 16-разрядным таймерам T1/T3 AVR.


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

Категория: Начинающим | Добавил: eugenemcu (02.04.2009) | Автор:

Просмотров: 11312 | Комментарии: 1 | Рейтинг: 4.4/12 |
Всего комментариев: 1
0
* 1 (05.12.2009 23:10) [Материал]
Спасибо огромное за эту статью! И вообще за материалы по Atmega.
Написано хорошо и понятно)))
Вариант задержки с помощью декремента улыбнул Неплохое введение)

Статистика

Наш магазин




Какую среду разработки Вы используете?

[ Результаты · Архив опросов ]
Всего ответов: 1328



Им нужна Ваша помощь: