В уроке 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 шт
Смотреть ВИДЕОУРОК (нажмите на картинку)
А если нужно вести счет с частотой 1 раз в две секунды тогда как настроить предделитель и что занести в счетчик не снижая тактовою частоту или как то делать через циклы?
Только не через циклы. Завести глобальную переменную и инкрементировать её в обработчике прерывания от таймера, если не хватает периода на желаемый интервал. Ослеживать инкрементирование до определённого значения и, если достигли, то выполнять желаемую задачу и обнулять переменную. Мы везде и всегда так делаем.
Сорри за оффтоп. Немного из собственной практики, когда происходит конфликт двух прерываний: заводим отдельную переменную, которая взводится до 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 в нулевой бит. В остальном, думаю, понятно. Циклы лишние я опустил.
Поясните вот этот Момент, мы число разделили не равно арофметически 0x85EE
А просто по знакам позволили на два регистра
TMR1H=0x85;
TMR1L=0xEE;
Поделили
переносом ledprint в тело обработчика TMR0 автор ничего не добился.
правильное решение написал Валерий в комментариях.
еще тут просится sleep в тело.