Руководство по таймерам в микроконтроллерах PIC для начинающих


Ранее на нашем сайте мы рассмотрели основы работы с микроконтроллерами PIC с помощью MPLABX IDE, отладку программы мигания светодиодом для них в симуляторе и на "реальном" железе, а также мигание с их помощью последовательностью светодиодов. В этой же статье на основе предыдущего проекта мигания последовательностью светодиодов мы рассмотрим основы работы с таймерами в микроконтроллерах PIC. По сравнению с предыдущим проектом мигания последовательностью светодиодов в данном проекте мы добавим одну дополнительную кнопку.

Проект для объяснения работы таймеров в микроконтроллерах PIC

Ранее на нашем сайте мы достаточно подробно рассматривали работу с таймерами в платах Arduino.

Зачем нужны таймеры

Таймеры играют важную роль в большинстве проектов встраиваемой электроники. Они позволяют отмерять заданные интервалы времени. Но вы можете спросить зачем нам нужны таймеры если мы можем делать то же самое с помощью функции задержки (__delay_ms())?

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

Также у задержек, организуемых с помощью функции __delay_ms()), есть и другие недостатки:

  1. Значение задержки задается константой и его нельзя изменить во время исполнения программы.
  2. Значение задержки не такое точное как в случае использования для этой цели таймеров.
  3. Невозможно организовать очень длительные задержки, к примеру, полчаса и более. Максимальная величина задержки зависит от используемой частоты кварцевого генератора.

Таймеры в микроконтроллерах PIC

На физическом уровне таймер представляет собой регистр, значение в котором непрерывно увеличивается до 255, и затем счет в нем начинается сначала: 0, 1, 2, 3, 4...255....0, 1, 2, 3......и т. д.

Микроконтроллер PIC16F877A содержит три таймера, с именами Timer0, Timer1 и Timer2. Timer0 и Timer2 являются 8-битными, а Timer1 – 16-битным. В нашем проекте мы рассмотрим работу с Timer0. Если вы поймете принцип его работы, то по аналогии с ним вы сможете работать и с таймерами Timer1 и Timer2

Таймер/счетчик Timer0 имеет следующие особенности:

  • 8-битный;
  • можно записывать и считывать информацию;
  • 8-битный программируемый предварительный делитель частоты (предделитель, prescaler);
  • выбор внешней или внутренней частоты синхронизации;
  • прерывание при переполнении от FFh до 00h;
  • выбор спада или нарастания импульса для сигнала внешней синхронизации.

Для начала разберемся с рядом терминов, оказывающих влияние на функционирование таймера.

Предварительный делитель частоты (предделитель, prescaler) – это компонент микроконтроллера, который делит частоту задающего генератора перед тем как она достигает логического уровня, увеличивающего состояние таймера. Коэффициент деления предделителя для микроконтроллеров PIC лежит в диапазоне от 1 до 256, он устанавливается с помощью регистра OPTION (этот же регистр используется и для управления подтягивающими резисторами). К примеру, если значение (его коэффициент деления) предделителя равно 64, то с каждым 64-м импульсом частоты генератора значение таймера будет увеличиваться на 1.

Когда при инкрементировании таймера его значение достигает 255 (максимальное его значение) он инициализирует прерывание и сбрасывает свое значение снова до 0. Этот прерывание называется прерыванием от таймера. Оно информирует микроконтроллер что истек определенный промежуток времени.

Обозначим за Fosc частоту тактового генератора (Frequency of the Oscillator), то есть частоту используемого кристалла. Интервал времени, отсчитываемый таймером, зависит от значения предделителя (Prescaler) и значения частоты Fosc.

Объяснение программы для микроконтроллера PIC

Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты.

В нашем проекте мы будем использовать 2 кнопки и 8 светодиодов, которые подключены к 8 контактам микроконтроллера PIC. С помощью 1-й кнопки мы будем устанавливать временную задержку (500ms на каждое нажатие), а 2-й кнопкой будем запускать процесс мигания последовательности светодиодов. К примеру, если первую кнопку мы нажмем трижды (500*3 = 1500ms), то задержка составит 1,5 секунды и при нажатии второй кнопки светодиоды будут включаться и выключаться с этим значением задержки. Более подробно работу проекта вы можете посмотреть на видео, приведенном в конце статьи.

Тестирование работы проекта

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

Вначале посмотрим на структуру регистра OPTION в даташите нашего микроконтроллера PIC.

Структура регистра OPTION для нашего микроконтроллера PIC

Как мы уже рассмотрели в предыдущей статье, бит 7 используется для управления внутренними подтягивающими резисторами порта PORTB. Как видно из представленного рисунка, если бит 3 установить в 0, то предделитель будет устанавливаться для нашего таймера, а не для сторожевого таймера (WatchDogTimer, WDT). Режим работы таймера устанавливается при помощи установки бита 5 (T0CS) в 0 (OPTION_REG<5>).

Биты 2-0 используются для установки значения предделителя. Как видно из представленной таблицы, чтобы установить значение предделителя равным 64, в эти биты нужно записать 101.

Теперь посмотрим на регистры, ассоциированные с Timer0.

Регистры, ассоциированные с Timer0 в микроконтроллерах PIC

Таймер при своей установке начинает счет и переполняется при достижении значения 256, чтобы разрешить срабатывание прерывания от таймера в этой точке регистр TMR0IE необходимо установить в high. Поскольку Timer 0 по своей сути является периферийным устройством, мы также должны разрешить прерывания от периферийных устройств установив PEIE=1. И, наконец, мы должны установить глобальное разрешение прерываний для микроконтроллера, это делается при помощи установки GIE=1.

Задержку, отсчитываемую таймером, можно определить по формуле:

Delay = ((256-REG_val)*(Prescal*4))/Fosc

Если
REG_val = 100,
Prescal = 64,
Fosc = 20000000,

То по данной формуле получим значение задержки равное Delay = 0.0019968s

Далее в программе сконфигурируем работу портов ввода/вывода.

Конфигурация работы портов такая же, как и в нашей предыдущей статье поскольку мы используем одно и то же "железо", за исключением того, что мы добавили в схему проекта еще одну дополнительную кнопку – она конфигурируется с помощью строки TRISB1=1.

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

Переменная get_scnds инкрементируется с каждым нажатием первой кнопки. Переменная flag (определяемая программно) используется для удержания процесса инкрементирования пока пользователь не удалит свой палец с кнопки.

Следующий блок программы начинает работать при нажатии второй кнопки. Значение заданной пользователем задержки в результате работы первого блока сохранилось у нас в переменной get_scnds. В этом же блоке программы мы будем использовать переменную hscnd, которая будет управлять процедурой обработки прерывания (Interrupt service routine, ISR).

Данная процедура будет вызываться каждый раз при переполнении Timer0. Как мы уже указывали ранее, значение задержки должно увеличиваться на полсекунды при каждом нажатии первой кнопки, поэтому мы должны инкрементировать переменную hscnd для каждой полсекунды. Поскольку ранее мы запрограммировали задержку для нашего таймера равную 0.0019968s (~ 2ms), то чтобы отсчитать полсекунды значение переменной должно быть равно 250 поскольку 250*2ms = 0.5 секунды. Поэтому когда значение переменной count достигает 250 (250*2ms = 0.5 секунды) это будет означать что прошло полсекунды, поэтому увеличиваем значение переменной hscnd на 1 и сбрасываем значение переменной count в 0.

В дальнейшем мы переключаем состояние светодиодов, основываясь на заданной задержке.

Схема проекта

Схема проекта для демонстрации использования таймеров в микроконтроллерах PIC представлена на следующем рисунке.

Схема проекта для демонстрации использования таймеров в микроконтроллерах PICВнешний вид собранной конструкции проекта показан на следующих рисунках.

Внешний вид платы с микроконтроллером PIC Внешний вид платы с кнопками и светодиодами

Внешний вид всей конструкции проекта в сборе

Исходный код программы

Видео, демонстрирующее работу проекта

(1 голосов, оценка: 5,00 из 5)
Загрузка...
2 887 просмотров

Комментарии

Руководство по таймерам в микроконтроллерах PIC для начинающих — 10 комментариев

  1. int Crank=0;

    void main()
    {
    OPTION_REG=0b00000111;
    INTCON=0b11100000;
    TRISD=0x00;
    PORTD=0x00;
    TMR0=0x00;

    while(TMR0IF==0) // Флаг переполнения таймераб если флаг Труе, выполняется цикл, если флаг фолс, цикл не выполняется.

    if (Crank==50) // Если переменная кранк Фолс, следущая команда пропускается, если труе следущая команда выполняется.
    {
    RD1=1; // Включить выход порта D
    Crank=0; // Обнулить переменну кранк
    break; // Выйти из цикла
    }
    else
    {
    Crank++; // Инкрементировать переменной кранк +1
    }

    TMR0IF=0; // Обнулить флаг таймера
    GIE=1; // Включить глобальные прерывания

    while(TMR0IF==0) // Флаг переполнения таймераб если флаг Труе, выполняется цикл, если флаг фолс, цикл не выполняется.

    if (Crank==50) // Если переменная кранк Фолс, следущая команда пропускается, если труе следущая команда выполняется.
    {
    RD1=0; // Вылючить выход порта D
    Crank=0; // Обнулить перемнную кранк
    break; // Выйти из цикла
    }
    else
    {
    Crank++; // Инкрементировать переменной кранк +1
    }

    TMR0IF=0; // Обнулить флаг таймера
    GIE=1; // Включить глобальные прерывания
    return;
    }

    Можно сказать моя первая программа на Си)

    Было не плохо если подскажите как сделать что бы каждые 24 импульса генерировался еще 1 импульс в этом же цикле.

    • Завести еще одну переменную в этом цикле, которая будет инкрементироваться только тогда, когда (Crank==24) или (Crank==48)

  2. Я разобрался как в Си указывать конкретный бит, просто по названию бита. Я только учусь и начинал изучение с ассемблера, а сейчас в си кажется как то сложнее, так как в ассемблере ты конкретно указываешь что нужно сделать, а тут какие то команды, не совсем с понятной логикой работы. Хоть в ассемблере и больше кода писать, но там как то более понятно что конкретно происходит в памяти. А тут тот же цикл вайл, хрен поймешь когда и как какое условие он выполняет. Если взять ассемблер, там указал, что проверить вот это там то там, если так то перейти туда все просто. В си чет я так и не додумался как мне вызвать например функцию. Например функция ИМЯ, выполняется код, я хочу что бы программа перешла на выполнение этого кода, и вернулась обратно. Так и не додумался как это реализовать в си. На ассемблере если не ошибаюсь это команда GOTO, и указываешь адрес куда перейти. Выполнилось, вернулось обратно, выполняет дальше инструкции. Короче Си какой то мутный)

    И я разобрался с генерацией импульсов. 3 дня голову ломал как это реализовать...

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

    • Ну так сделайте этот цикл внутри бесконечного цикла while, и тогда после выполнения ваш цикл будет выполняться снова и снова

  3. Не компилируется файл, пишет ошибку...

    из за вот это строчки void interrupt timer_isr()

    слово interrupt убираешь, комплируется, в протеусе не работает, пишет оверфлоу интеррупт.
    всю голову сломал, так и не понял в чем проблема...

    2 errors generated.
    (908) exit status = 1
    nbproject/Makefile-default.mk:107: recipe for target 'build/default/production/main.p1' failed
    make[2]: Leaving directory 'C:/Users/Dream/MPLABXProjects/Timer Crank.X'
    nbproject/Makefile-default.mk:91: recipe for target '.build-conf' failed
    make[1]: Leaving directory 'C:/Users/Dream/MPLABXProjects/Timer Crank.X'
    nbproject/Makefile-impl.mk:39: recipe for target '.build-impl' failed

    BUILD FAILED (exit value 2, total time: 205ms)

    • К сожалению, с протеусом уже давно не работал, не могу подсказать здесь что то конкретное. А оверфлоу это означает переполнение в общем случае. У вас другие проекты (без использования прерываний) нормально компилируются?

      • Я не пробовал. Я только учусь.

        Может подскажете как решить такую задучу.

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

        Надо зайдествовать 2 порта. На одном генерируется 24 импульса, на другом 1. И так до бесконечности.

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

        • Вам в этом случае проще использовать обычную функцию задержки. Делаете цикл от 0 до 23 (он должен стоять внутри бесконечного цикла while в основной функции main). В нем с нужной вам задержкой (зависит от того какая длительность импульса вам нужна) переключаете состояние контакта, на котором вам нужно формировать 24 импульса. А внутри этого цикла ставите условие (например, когда переменная цикла равна 0) чтобы состояние второго контакта, на котором нужно формировать 1 импульс, переключалось всего один раз за время этого цикла

          • Я так пробовал. Тогда импульсы кривые и разные. Мне нужно что бы четко выходило 24 импульса одной длины. Тоесть например счетчик досчитал до 255, это равно длине времени 50мс, порт включился на 50мс и через 50мс выключился. Когда на одном порту прошло 23 импульса, то на 24 импульс включился второй порт на такой же промежуток времени.

            Тоесть с порта D7 выходят 24 имульса 50мс. 50мс включено, 50мс выключено. (Время 50мс не обязательно), длина не особо важна, главное что бы эти импульсы были одинаковые. И на 23 импульс порта D7 выходит импульс 50мс с порта D6.

            Контролллер PIC16F877A.

            И еще вопрос немогу сообразить, Как проверить отдельный бит регистра специального назначения,

            Например регистр INTCON bit2.

            Например я хочу в цикле проверять регистр INTCON bit2 На флаг переполения таймера. Когда флаг поднялся INTCON BIT2 (1), Мне нужно записать 1 в другой регистр.

            например я пишу цикл while (INTCON) А как мне тут проверить имено второй бит?

            Или я что то не так понимаю.

            Спасибо)

            • Его можно проверить сделав операцию логического И с маской, в которой все биты, кроме второго, равны 0

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

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