PIC Урок 8. TIMER1



В уроке 5 мы познакомились с одним из таймеров микроконтроллера PIC — TIMER0 и уже применяли его дважды. Последний раз мы его применяли для перебора разрядов четырёхразрядного светодиодного индикатора для обеспечения режима динамической индикации. Но так как мы со времён изучения таймера 0 перешли на более сложный и более функциональный контроллер PIC, то мы, читая на него техническую документацию не могли не заметить, что в нём уже таймер не один, а целых три. Причём другие таймеры более функциональны по отношению к таймеру 0. Поэтому давайте познакомимся со следующим таймером — таймером 1 или TIMER1.

Данный таймер также считает только вперёд, причём он самостоятельно (без помощи триггеров от других модулей) не сбрасывается при сравнении его счётчика с определённой величиной, а сбрасывается только после окончания счёта, но зато есть у него немало преимуществ. Основное, я считаю, его преимущество то, что он уже 16-битный, следовательно, считает он уже не до 255, а до 65535, что более удобно и позволяет без лишних плясок с переменными выжидать большие промежутки времени до следующего прерывания. Прерывание от таймера 1, так же, как и от таймера 0, существует только одно — по окончании счёта. Но при использовании модуля CCP, которого мы коснёмся в более поздних занятиях, можно достигать многих результатов, в том числе и использовать эту пару для получения аппаратного PWM (ШИМ) на определённых ножках контроллера. Теперь кратко о способностях и особенностях, а также о регистрах таймера 1.

Двухбайтовая регистровая пара TMR1H и TMR1L, состоящая из старшего и младшего байта, служит для хранения значения счётчиков. Она доступна как для чтения, так и для записи, хотя при определённых условиях и режимах записью в данные регистры нужно пользоваться с особой осторожностью во избежание непредсказуемых последствий.

Следующий регистр, который следовало бы рассмотреть, — это управляющий регистр таймера 1 — T1CON

 

 

Биты данного регистра имеют следующее назначение.

Биты T1CKPS1:T1CKPS0 (Timer1 Input Clock Prescale Select bits) — биты предделителя. Используются следующие их комбинации:

11 = 1:8 значение деления

10 = 1:4 значение деления

01 = 1:2 значение деления

00 = 1:1 значение деления

Бит T1OSCEN (Timer1 Oscillator Enable Control bit) — бит, включающий тактовый генератор.

1 — генератор включен

0 — генератор выключен (инвертор генератора отключен, чтобы исключить утечку энергии)

T1SYNC — (Timer1 External Clock Input Synchronization Control bit) — управление синхронизацией тактового сигнала

Если TMR1CS = 1:

1 — внешний тактовый сигнал не синхронизируется

0 — внешний тактовый сигнал синхронизируется

Если TMR1CS = 1, то значение игнорируется.

TMR1CS — (Timer1 Clock Source Select bit) — выбор источника тактового сигнала

1 — внешний источник с вывода RC0/T1OSO/T1CKI (активный передний фронт)

0 — тактирование от внутреннего генератора (FOSC/4)

TMR1ON — (Timer1 On bit) — бит включения таймера

1 — таймер включен

0 — таймер выключен

 

Теперь можно посмотреть блок-схему таймера 1 (нажмите на картинку для увеличения изображения)

 

 

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

Включение таймера осуществляется установкой бита TMR1ON в 1.

TMR1 также имеет внутренний вход сброса от модуля CCP.

Таймер 1 может работать в режиме собственно таймера, в режиме синхронного счётчика, а также в режиме асинхронного счётчика.

Так как мы сегодня работать будем с таймером в его основном режиме — режиме таймера, то об остальных режимах расскажу предельно кратко. Если они нам когда-то потребуются, то мы с ними тогда и познакомимся более подробно, а то до тех пор всё равно всё забудем.

В режиме синхронного счётчика тактирование счётчика таймера происходит от внешнего генератора, только такт синхронизируется ещё с передним фронтом внутреннего генератора микроконтроллера.

В режиме асинхронного счётчика происходит тактирование таймера также от внешнего генератора, но оно уже ни с чем не синхронизируется, а управляет счётчиком напрямую (с небольшой задержкой). Зато данный режим актуален в режиме SLEEP контроллера.

Ну а о работе в обычном режиме таймера, думаю, говорить нечего. Обычным образом счётчик считает тики с частотой внутреннего тактового генератора, делённой на 4, а также на величину предделителя.

Чтобы испытать наш таймер, я решил продолжить работу со схемой предыдущего урока по обеспечению режима динамической индикации, только таймер 0 у нас также будет перебирать уровень анодов для их переключения, а таймер 1 будет у нас считать (увеличивать цифру, отображаемую на индикаторе). Тем самым мы откажемся от кода в бесконечном цикле, а будем увеличивать цифру в обработчике прерывания. Это нам поможет не только узнать на практике работу таймера 1, но и значительно разгрузить контроллер, так как мы откажемся от задержек, что позволит контроллеру не висеть в момент ожидания изменения цифры, а заниматься какой-то полезной работой. Конечно, сразу предупреждаю, что использование сразу двух обработчиков прерываний вызовет определённые трудности, которых мы не боимся и обязательно преодолеем.

Так что вперёд!

Проект мы создадим из проекта предыдущего урока LED_DYN.X и назовём его TIMER1.X. Теперь мы вместе с файлом main.c копируем в папку нового проекта все остальные файлы с исходными кодами.

Откроем проект в MPLAB X, сделаем его главным, откроем файл main.c и уберём в функции main() весь код полностью из бесконечного цикла.

Также удалим объявление локальной переменной, так как она нам не потребуется

 

unsigned int i;

 

Затем мы исправим инициализацию различных регистров в функции main.

 

 

Для нулевого таймера поставим меньший делитель — 8. Так мерцания не видны не только на глаз, но и на камеру

 

OPTION_REG=0b00000010; //Prescaler 8

 

Инициализацию регистра INTCON удалим, теперь мы будем в нём включать каждый бит, используя макросы битов. Делать мы будем это несколько позже, а пока просто удалим инициализацию регистра

 

INTCON=0xA0;

 

Наш таймер будет считать так, что прерывания в конце счёта будут возникать с частотой 4 раза в секунду или 4 герца.

Как этого точно добиться?

Во-первых сначала пропишем делитель в определённые регистры

 

 

OPTION_REG=0b00000010; //Prescaler 8

T1CKPS0=1; //Prescaler 8 (1000000/31250/8 = 4 Hz)

T1CKPS1=1;

 

Делитель мы выставили 8. Если рассчитать всё и учесть, что тактовая частота контроллера 4 мегагерца, то делимое у нас будет 1 мегагерц, так как FOSC/4. И если мы поделим на предделитель, то есть на 8 и на необходимые нам 4 килогерца, то у нас получится 31250, что есть это цифра, обозначающая, сколько тиков должен считать таймер. Но резонный вопрос, как же этого добиться, если таймер у нас считает всегда до 65535? Ответ будет позже.

 

Обнулим бит TMR1CS, так как тактирование у нас будет происходить от внутреннего генератора

 

T1CKPS1=1;

TMR1CS=0; //Internal clock

 

А вот теперь разгадка секрета. Мы заранее занесём в счётчик такое число, чтобы таймеру до 65535 осталось считать ровно столько тиков, сколько нам требуется

 

TMR1CS=0; //Internal clock

TMR1L=0xEE; // 65536 - 31250 = 34286 = 0x85EE

TMR1H=0x85;

 

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

Включим необходимые биты в регистре , не забывая также и про таймер 0

 

TMR1H=0x85;

T0IE=1;

GIE=1;

PEIE=1;

 

Вот так мы и произвели инициализацию  регистра INTCON, только обращаясь к нему не по его имени, а по имени его битов. В этом нам помогли макросы библиотеки pic16f876a.h.

Включим прерывания от таймера 1 в регистре PIE

 

PEIE=1;

TMR1IE=1;

 

Включим таймер

 

TMR1IE=1;

TMR1ON=1;

 

Теперь обработчик прерывания.

Тот обработчик прерывания timer0(), который написан у нас, нам теперь не подходит. Обернём тело данного обработчика в общий обработчик прерываний с условием, что у нас установлены строго определённые флаги прерываний. Теперь будет так

 

void interrupt isr(void)

{

  if(TMR0IE&&TMR0IF)

  {

    TIM0_Callback();

    T0IF=0;

  }

}

 

 

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

 

  T0IF=0;

}

else if(TMR1IE&&TMR1IF)

{

}

 

Теперь начнём писать тело данного условия.

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

 

else if(TMR1IE&&TMR1IF)

{

  TMR1L=0xEE;

  TMR1H=0x85;

 

Затем сбросим флаг прерывания

 

TMR1H=0x85;

TMR1IF=0;

 

Добавим глобальную переменную для счёта значения, которое мы будем отображать на индикаторе

 

#include "main.h"

//--------------------------------------------------------------

unsigned int TIM1_Count=0;

//--------------------------------------------------------------

 

Вернёмся в обработчик прерывания, отобразим на дисплее и проинкрементируем данную переменную, а также ограничим её значение до 4 десятичных знаков

 

TMR1IF=0;

ledprint(TIM1_Count);

TIM1_Count++;

if(TIM1_Count>9999) TIM1_Count=0;

 

Казалось бы, на этом можно закончить. Но не тут то было.

Соберём код, прошьём контроллер и увидим, что у нас будут небольшие мигания разрядов в момент смены цифр в других разрядах. В протеусе этого мы не заметим.

С этим надо как-то вести борьбу.

Но чтобы вести борьбу с чем-то, надо знать причину возникновения этого чего-то.

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

 

ledprint(TIM1_Count);

 

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

 

unsigned char n_count=0, R1=0, R2=0, R3=0, R4=0;

extern unsigned int TIM1_Count;

static unsigned int LED_Count=0;

 

Затем в самом начале тела обработчика прерывания таймера 0 в этом же файле мы узнаем, не изменилось ли число, если изменилось, то мы его выведем на экран индикатора, а число сохраним в переменную

 

void TIM0_Callback(void)

{

  if(LED_Count!=TIM1_Count)

  {

    ledprint(TIM1_Count);

    LED_Count = TIM1_Count;

  }

  if(n_count==0)

 

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

Теперь соберём код, прошьём контроллер, и убедимся, что счётчик наш прекрасно работает, как и раньше

 

 

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

Всем спасибо за внимание!

 

 

Предыдущий урок Программирование МК PIC Следующий урок

 

Исходный код

 

 

Купить программатор (неоригинальный) можно здесь: PICKit3

Купить программатор (оригинальный) можно здесь: PICKit3 original

Отладочную плату PIC Open18F4520-16F877A можно приобрести здесь: PIC Open18F4520-16F877A

Семисегментный чертырехразрядный индикатор красный с общим анодом 10 шт

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

PIC TIMER1

7 комментариев на “PIC Урок 8. TIMER1
  1. san:

    А если нужно вести счет с частотой 1 раз в две секунды тогда как настроить предделитель и что занести в счетчик не снижая тактовою частоту или как то делать через циклы?

    • Только не через циклы. Завести глобальную переменную и инкрементировать её в обработчике прерывания от таймера, если не хватает периода на желаемый интервал. Ослеживать инкрементирование до определённого значения и, если достигли, то выполнять желаемую задачу и обнулять переменную. Мы везде и всегда так делаем.

  2. Валерий:

    Сорри за оффтоп. Немного из собственной практики, когда происходит конфликт двух прерываний: заводим отдельную переменную, которая взводится до 1 при срабатывании прерывания. В теле main ставим проверку значения этой переменной и там же ставим код, который должен отрабатываться по прерыванию с последующим обнулением переменной. Если используется несколько прерываний — то можно рулить отдельно битами в переменной, что бы место в памяти вхолостую не занимать.
    В этом случае, исполнение кода по таймеру 1 может быть прервано для исполнения прерывания по таймеру 0.

    • Валерий:

      Поясню примером из MicroC for PIC:
      char int_run;
      void interrupt()
      {
      if (int1)
      {int_run.b0=1; int1=0;}
      if (int2)
      {int_run.b1=1; int2=0;}
      }
      void main
      {
      if (int_run&0x01) {func1(); int_run.b0=0;}
      if (int_run&0x02) {func2(); int_run.b1=0;}
      }
      здесь int1 и int2 — два различных прерывания. Строчка int_run.b0=1 ставит 1 в нулевой бит. В остальном, думаю, понятно. Циклы лишние я опустил.

  3. Иван:

    Поясните вот этот Момент, мы число разделили не равно арофметически 0x85EE
    А просто по знакам позволили на два регистра

    TMR1H=0x85;

    TMR1L=0xEE;

  4. Иван:

    переносом ledprint в тело обработчика TMR0 автор ничего не добился.
    правильное решение написал Валерий в комментариях.
    еще тут просится sleep в тело.

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*