Таймеры. Организация временной задержки

При написании программ часто требуется реализовать определенную временную задержку, например чтобы помигать светодиодом. Паузу можно организовать программным путем или с помощью внутренних таймеров микроконтроллера. Программный способ заключается в многократном выполнении циклического кода, при этом время рассчитывается исходя из количества выполненных инструкций и продолжительности машинного цикла.

Рассмотрим пример организации программной паузы, код представлен ниже:

Перейдя в подпрограмму pause, сначала записываем число 100 в регистр Sec, который будет использоваться в качестве счетчика. Далее декрементируем (с условием) данный регистр, если содержимое регистра не равно нулю, выполняется переход на метку p1, где снова производится декремент. Этот цикл будет повторяться до тех пор, пока значение регистра Sec не станет равным нулю, после чего происходит выход из подпрограммы. В данном случае цикл, состоящий из 2-ух инструкций decfsz и goto повториться 100 раз, при этом время выполнения инструкции decfsz составит один машинный цикл (один такт), а инструкция goto выполняется за два такта. Получаем 3 такта по 100 раз, пусть продолжительность машинного цикла составляет 1 мкс (при этом частота тактового генератора должна быть равна 4 МГц), в итоге время выполнения подпрограммы (пауза) составит 300 мкс. Но это не совсем точное значение, еще надо учесть время записи числа в регистр (2 такта), время перехода (инструкция call – 2 такта) и выхода (инструкция return – 2 такта) из подпрограммы. Кроме этого инструкция decfsz при обнулении регистра выполняется за 2 такта, то есть следующая команда (goto p1) пропускается, вместо нее выполняется пустой цикл, называемый nop. Тем самым получим 300+2+2+2-1 = 305 мкс, это все мелочи по сравнению с задержкой, которая нам требуется, поэтому считаю, что нет смысла рассчитывать точное время. Достаточно умножить число из регистра Sec на 3 (ведь основной многократно выполняемый цикл длиться 3 такта), затем результат умножить на продолжительность машинного цикла, вот и все.

Если требуется большая пауза, можно использовать несколько регистров, при входе в подпрограмму pause1, записываем числа сразу в 3 регистра. После обнуления регистра Sec, декрементируем регистр Sec1, если он не равен нулю, снова записываем число в регистр Sec, и заново начинаем круговой цикл декремента Sec. То есть внутренний цикл (в 3 такта) декремента регистра Sec от 255 до 0, повториться еще 218 раз, пока не обнулится регистр Sec1, после чего декрементируем регистр Sec2. Таким образом, внутренняя конструкция декремента Sec и Sec1 повториться еще 6 раз. В итого время выполнения подпрограммы составит 3х255х218х6 = 1000620 мкс = 1,00062 сек, то есть внутренний цикл (3 такта по 255 раз) повториться 218 раз, а все это вместе повториться еще 6 раз. Опять же это не совсем точное время, если разбираться, запись числа в регистр Sec (2 такта) повториться 218х6 = 1308 раз, запись в регистр Sec1 повториться 6 раз, это еще плюс 2х1308+2х6 = 2628 мкс, но это не существенно по сравнению с 1 сек.

Программная пауза отнимает процессорное время, при уходе в подпрограмму паузы микроконтроллер больше ничем не может заниматься, кроме прерываний конечно. Если прерывания возникают строго регулярно, то стоит учесть что время выполнения подпрограммы паузы увеличиться.

Другой способ организации паузы это использование внутренних таймеров микроконтроллера. В этом случае процессорное время не растрачивается и может быть направлено на решение других задач, что является несомненным преимуществом перед программной паузой. PIC16 имеют 3 таймера: TMR0, TMR1, TMR2, которые также могут работать в режиме счетчика. Таймер TMR0 является 8-ми разрядным, с программируемым предделителем, настраивается в регистре OPTION_REG. Таймер TMR1 16-ти разрядный, также имеет программируемый предделитель, настраивается в регистре T1CON. Таймер TMR2 8-ми разрядный, кроме предделителя имеет программируемый выходной делитель, регистры настройки T2CON и PR2. Таймеры TMR0, TMR1 можно настроить на внешний тактовый сигнал с выводов T0CKI и T1CKI соответственно (режим счета), или использовать внутренний тактовый сигнал Fosc/4 (режим таймера), при этом приращение (икремент) значений таймеров выполняется за каждый машинный цикл. TMR2 работает только от внутреннего тактового сигнала.

Для организации паузы будем использовать прерывания по переполнению таймера. Частоту тактового генератора примем за 4 МГц, машинный цикл соответственно 1 мкс. Ниже представлен пример кода для организации паузы в 10 мс при помощи таймера TMR0:

В основной программе необходимо настроить таймер TMR0, для этого изменяем значения битов регистра OPTION_REG, записывая в него двоичное число 11010101 (в десятеричной форме 213), так как регистр расположен в 1-ом банке, перед записью переключаем банки. Записывая число в OPTION_REG,выбираем внутренний источник тактового сигнала для TMR0, включаем предделитель перед TMR0 (после сброса микроконтроллера при включении питания предделитель подключен к выходу сторожевого таймера WDT, а TMR0 тактируется сигналом Fosc/4), устанавливаем значение коэффициента предделителя равным 1:64. Таймер TMR0 нельзя включить или выключить отдельным битом в отличии от TMR1 и TMR2, поэтому при выборе внутреннего тактового сигнала таймер сразу начнет инкрементироваться. Надеюсь всем понятно, что без предделителя TMR0 переполнится за 256 машинных цикла (при условии, что начальное значение таймера равно 0), с коэффициентом 1:64 таймер переполнится за 16384 машинных цикла или 16384 мкс.

Далее записываем число 100 в регистр таймера TMR0, почему именно 100? А для того чтобы получить паузу примерно в 10 мс. Давайте посчитаем: при инкременте таймера от числа 100 до переполнения, необходимо 256-100 = 156 машинных цикла или 156 мкс, а учитывая предделитель получим 156х64 = 9984 мкс = 9,984 мс. После записи числа в таймер, сбрасываем флаг прерывания по переполнению TMR0, разрешаем прерывания по переполнению TMR0 и наконец, разрешаем глобальные прерывания. Приращение таймера начинается через 2 машинных цикла после записи нового значения, это без предделителя. С учетом предделителя при коэффициенте 1:64, приращение начнется через 64х2 = 128 машинных цикла или 128 мкс, думаю, это несущественно по сравнению с длительностью паузы в 10 мс, поэтому я обычно не заморачиваюсь точными расчетами.

По прошествии примерно 10 мс таймер переполнится, что вызовет прерывание и переход в подпрограмму обработки прерываний. Здесь после сохранения значений ключевых регистров (об этом я писал в статье про прерывания) выполняем требуемые операции (то для чего и нужна была пауза) или устанавливаем флаг в дополнительном регистре, который опрашиваем в основной программе. После чего запрещаем прерывания по переполнению TMR0, восстанавливаем значения ключевых регистров и выходим из подпрограммы обработки прерываний. Если снова потребуется пауза то в основной программе выполняем почти те же действия что и при первом запуске, записываем TMR0, сбрасываем флаг и разрешаем прерывания по переполнению.

Иногда требуется пауза, которую таймер не сможет обеспечить даже с максимальным коэффициентом предделителя, для TMR0 этот предел равен 256х256 = 65536 машинных цикла, или 65,536 мс (машинный цикл 1 мкс). В этом случае можно запускать таймер несколько раз. Нам потребуется дополнительный регистр var, который будет выступать в качестве счетчика запусков таймера. Код представлен ниже:

Перед запуском таймера записываем число 5 в регистр var, в подрограмме обработки прерываний первым делом декрементируем регистр var, если он не равен нулю, сбрасываем флаг прерывания, записываем число в таймер и выходим из подпрограммы обработки прерываний. Как только регистр var обнулится (после 5-го запуска таймера), выполняем требуемые операции, запрещаем прерывания и выходим из обработчика. В данном коде пауза составит примерно 150 мс, таймер отсчитывает (256-22)х128 = 29952 мкс, и запускается 5 раз 29952х5 = 149760 мкс = 149,760 мс. Варьируя коэффициентом предделителя, значением в регистре TMR0 и var, можно настроить временную задержку в широких пределах. Желательно настраивать таймер так, чтобы уменьшить количество прерываний, ведь желаемую паузу можно получить разными настройками, например, поставить малый коэффициент предделителя и большое число в var, тем самым увеличивая количество прерываний.

Для таймера TMR1, справедливы те же действия, здесь надо учесть, что таймер 16-ти разрядный, запись значения производится в 2 регистра TMR1H (старший) и TMR1L (младший), кроме этого таймер можно включать и выключать битом TMR1ON в регистре T1CON, что очень удобно. В подпрограмме обработки прерываний можно не запрещать прерывания по переполнению, а просто остановить таймер. Перед записью нового значения, таймер желательно останавливать. Ниже представлены коды для организации паузы в 30 мс, и 3 сек с помощью дополнительного регистра var:

В первом случае таймер отсчитывает 65536-35536 = 30000 мкс = 30 мс, коэффициент предделителя равен 1:1. Во втором, таймер отсчитывает (65536-3036)х8 = 500000 мкс = 500 мс = 0,5 сек, (коэффициент предделителя 1:8), таймер запускается 6 раз, итого пауза равна 0,5х6 = 3 сек.

У этой записи один комментарий

  1. Ну Вы и гоните. Вы вообще даташит читали? Предделитель в OPTION_REG сбрасывается при записи в сам таймер, следовательно, его надо настраивать после этого. Просветители, чёрт побери.

Имя (обязательно)Email (обязательно)Веб-сайт

Добавить комментарий