Таймер на атмега8 своими руками

Добавил пользователь Владимир З.
Обновлено: 17.09.2024

Параметры таймера задаются регистром TCCR0 , точнее первыми 3-мя битам (Clock Select) этого регистра: CS00,CS01,CS02 – выбор тактового источника

CS02 CS01 CS00
0 0 0 нет источника, таймер остановлен
0 0 1 без предделителя, на вход таймера подается тактовая частота
0 1 0 F_CPU/8 (предделитель/prescaler)
0 1 1 F_CPU/64 (предделитель/prescaler)
1 0 0 F_CPU/256 (предделитель/prescaler)
1 0 1 F_CPU/1024 (предделитель/prescaler)
1 1 0 внешний источник, по фронту
1 1 1 внешний источник, по спаду

Для работы необходим разрешить соответствующее прерывание в регистре TIMSK установкой бита TOIE0

Сброс предделителя можно осуществить установив соответствующий бит (для ATMega8 это PSR10) регистра SFIOR.

В ATmega8 для timer0 и timer1 регистр предделителя общий, и надо внимательно относиться, т.к. возможны проблемы: если на вход таймер0 подается F_CPU, на таймер1 F_CPU/1024, а в обработчике TIMER0_OVF происходит установка PSR10, то TIMER1 работать перестанет, т.к. каждые 256 тактов счетчик предделителя будет обнуляться, и значения F_CPU/1024 ни разу не достигнет.

Мы уже познакомились с таймерами в целом в статье " Микроконтроллеры для начинающих. Часть 52. Таймеры " и с подробностями реализации таймеров в Microchp PIC в статье " Микроконтроллеры для начинающих. Часть 53. Таймеры PIC ". Теперь, как я и обещал, познакомимся с таймерами в Atmel AVR (ныне Microchip AVR). Напомню, что популярная Arduino это те самые микроконтроллеры AVR, так что статья может быть полезной и любителям Arduino.

Еще раз отмечу, что мы пока не изучали прерывания. Поэтому сегодня будут рассматриваться только возможности счета времени/импульсов таймерами. Не будет рассматриваться и использование таймеров для работы других модулей, например ШИМ. Это все темы последующих статей.

Несмотря на то, что AVR считается "более одинаковой" чем тот же PIC, это далеко не всегда так. И сегодня мы увидим это на примере таймеров. Они так же, как в PIC, прошли свой путь развития. Вот такой простейший базовый таймер можно встретить в ATmega8A

В отличии от Microchip, Atmel не показывает особых подробностей схемотехники, предпочитая ограничиваться регистрами и крупными основными блоками. Именно поэтому я и не назвал схему функциональной. Она скорее ближе к блок-схеме.

Здесь все основные компоненты нам уже знакомы. Собственно сам регистр-счетчик таймера - TCNTn, где n обозначает номер таймера. В данном случае это TCNT0. Счетчик может считать только в одну сторону, в сторону увеличения. Функциональный блок "Control Logic" скрывает в себе все подробности схемы управления. Но для нас это не страшно.

Начинка блока управляющей логики достаточно проста. Это мультиплексор выбора источника счетных импульсов и схема формирования сигнала TOV0, который соответствует флагу переполнения таймера. Сам флаг переполнения TOV0 располагается в регистре TFIR, который не относится к модулю таймера.

На иллюстрации показана схема определения момента переполнения, компаратор "=0xFF". Однако, в этом простейшем таймере компаратор на самом деле отсутствует. Сигналом переполнения является перенос из старшего разряда счетчика. Именно из него и формируется сигнал TOV0.

Источник счетных импульсов выбирается с помощью бит SC00-SC02 регистра TCCR0. Он показан на иллюстрации. Если все три бита равны 0, источник счетных импульсов отсутствует, что соответствует остановке таймера.

Основным внутренним источником счетных импульсов является системный тактовый генератор с предварительным делителем. Причем предделитель в AVR общий для таймеров 0 и 1, но для каждого таймера можно выбирать свой коэффициент деления независимо. Этот предварительный делитесь мы чуть позже рассмотрим подробнее.

Так же счетные импульсы могут поступать от внешнего источника, через вывод T0 (Tn). При этом работа счетчика возможна по нарастающему или по спадающему фронту.

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

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

Обратите внимание, адреса регистров я указал для ATmega8A. Все очень похоже на то, как мы делали такую задержку на базовом TIMER0 в PIC. Но теперь у нас нет возможности задать коэффициент деления 1:4, поэтому выбираем 1:8. Соответственно и начальное значение счетчика будет -125.

Так же, вопросы может вызвать и такая вот операция

if(TIFR.TOV0) TIFR.TOV0=1;

Зачем устанавливать бит, если он и так установлен? Но дело в том, что этот бит устанавливается аппаратно, при переполнении. А вот сбрасывается аппаратно только при выполнении обработчика прерывания, которого у нас нет. И я даже не показал регистр маскирования прерываний. Поэтому сбрасывать бит приходится вручную. При этом его сброс выполняется несколько "парадоксальным" образом - повторной установкой. Что мы и делаем.

Предварительный делитель. Один на двоих

В AVR один предварительный делитель на два таймера, 0 и 1. И выбор коэффициентов деления для таймеров довольно ограничен.

Собственно говоря, предварительный делитель это просто цепочка триггеров (счетчик) с выводами сигналов с отдельных триггеров. И коэффициенты деления могут быть лишь 1:1, 1:8, 1:64, 1:256 и 1:1024.

Биты CS просто управляют мультиплексорами, которые выбирают и источник сигнала, и фронт, и коэффициент деления.

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

Да, это все еще тот же самый таймер! Отличия, на первый взгляд, не так и велики. Источники счетных импульсов остались теми же самыми. И выбор осуществляется теми же самыми битами. Только теперь они находятся в регистре TCCR0B. Остались такими же блоки обработки внешнего сигнала и выбора источника сигнала.

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

Сразу скажу, что генерацию сигналов (блоки Waveform Generation) мы сегодня рассматривать не будем, как и различные режимы формирования ШИМ с помощью таймера. Я об этом предупреждал в самом начале статьи. Эти вопросы будут рассматриваться позднее, в отдельных статьях.

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

Режимами работы, в основном, управляет регистр TCCR0A. За исключением одного бита, который по неизвестной причине был выселен в TCCR0B, не смотря на то, что свободное место в TCCR0A оставалось.

Нас будут интересовать биты WGM02:WGM00. И пусть вас не смущает, что эта аббревиатура образовалась от Waveform Generation Mode, который мы сегодня не рассматриваем. Именно эти биты управляют работой таймера. А вот биты COM0A1:COM0A0 и COM0B1:COM0B0 управляют использованием выводов OC0A и OC0B генераторами сигналов.

Для нас интересны только два режима из 6 доступных. Режимы ШИМ использовать возможно, но не имеет особого смысла. Поэтому их рассмотрим в соответствующей статье.

Нормальный режим

Если WGM02:WGM00=000, то таймер работает в нормальном режиме, который практически полностью идентичен работе базового таймера. В этом режиме счетчик таймера считает только в сторону увеличения, а при его переполнении устанавливается флаг TOV0, правда в регистре TIFR0. А таймер продолжает счет с начала.

Именно этот факт обозначен на иллюстрации прямоугольником в надписью "Fixed TOP Value". Это фиксированное верхнее значение и равно 0xFF.

Однако, этот режим все таки отличается от работы базового таймера. Так как два регистра, OCR0A и OCR0B, хоть и не оказывают влияния на работу собственно таймер, но позволяют управлять двумя дополнительными флагами. И эти флаги могут вызывать прерывания.

Когда содержимое TCNT0 совпадет с содержимым регистра OCR0A, будет установлен бит OCRF0A в регистре TIFR0. Этот бит будет сброшен автоматически при обработке прерывания, или потребует ручного сброса, если прерывания не используются. Причем для его сброса, если он установлен, нужно "парадоксально" установить его еще раз (записать 1).

Точно так же ведет себя и регистр ORC0B, только установлен будет флаг OCRF0B.

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

Сброс при совпадении

Если WGM02:WGM00=010, то регистр OCR0A задает верхний предел для регистра счетчика. В этом режиме таймер работает аналогично ранее рассмотренному (в предыдущей статье) TIMER2 в микроконтроллерах PIC. Там же я приводил графическую иллюстрацию такого режима работы.

Счетчик таймера в этом режиме считает от 0 в сторону увеличения. Когда счетчик достигнет значения заданного регистром OCR0A, счетчик будет сброшен в 0. И будет установлен флаг OCRF0A.

Но вот флаг TOV0 при этом не будет установлен. Так как переполнения счетчика не произойдет. Флаг OCRF0B будет установлен только если содержимое регистра OCR0B меньше содержимого OCR0A.

Да, есть и такой. Например, в ATmega328. Он полностью идентичен описанному ранее, но для включения таймера (подачи на него питания) нужно сбросить бит PRTIM0 в регистре PRR.

Иных отличий нет.

Универсальный 16-битный таймер

А вот в ATtiny10 этот таймер называется Timer0, при этом базового таймера в этом микроконтроллере нет. Даже улучшенного. Что добавляет путаницы и вопросов у новичков.

Так же, обратите внимание, что в ATnega328 дополнительно потребуется сбросить бит PRTIM1 в регистре PRR, что бы включить питание таймера.

продаётся раскрученный сайт недорого обращаться в личку

Этот таймер предназначен для установки выдержек от 5 секунд до 100 минут. На его выходе имеется достаточно мощное электромагнитное реле, позволяющее коммутировать ток до З0А при напряжении 12V и ток до 10А при напряжении 220V. Благодаря применению электромагнитного реле таймер может управлять не только нагревательными или осветительными приборами, но и электронными приборами, критичными к форме питающего переменного напряжения. Трансформаторное питание, в сочетании с реле, обеспечивает полную гальваническую развязку электронной схемы таймера от сети.

Для общения таймера с оператором есть четырехразрядный светодиодный индикатор, в нем очень старые 7-сегментные матрицы АЛ304 в количестве четырех штук, соединены в матрицу путем соединения вместе одноименных сегментных выводов. Конечно можно использовать и более современные светодиодные индикаторы, и даже готовые матрицы по четыре разряда под динамическую индикацию.

Источник питания выполнен на маломощном трансформаторе. Поскольку вторичная обмотка трансформатора имеет отвод от середины (12-0-12), то выпрямитель сделан не по мостовой, а по двухполупериодной схеме на двух диодах VD2 и VD3. Если трансформатор будет с обмоткой 12V без отводом, то нужен выпрямительный мост. Реле питается непосредственно с выхода выпрямителя, а остальная схема через стабилизатор А1 напряжения 5V.

При прошивке нужно задать на работу с внутренним генератором 8 МГц.

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

ранзисторы КТ315 можно заменить на КТ3102 или любые аналоги. Транзистор КТ815 можно заменить на КТ817, КТ604. Диод КД521 - практически любой аналог. Диоды в выпрямителе КД209 - любые диоды выпрямительные на постоянный ток не ниже 150 мА. Интегральный стабилизатор 7805 можно заменить любым 5-вольтовым, например, КР142ЕН5А. Или сделать стабилизатор по параметрической схеме на двух транзисторах и стабилитроне на 5V. По поводу индикаторов сказано выше. Это могут быть любые семисегментные индикаторы с общим анодом(катодом).

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

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

И вот эти таймеры-счётчики постоянно считают, если мы их инициализируем.

Таймеров в МК Atmega8 три.

image00

Два из них — это восьмибитные таймеры, то есть такие, которые могут максимально досчитать только до 255. Данной величины нам будет маловато. Даже если мы применим максимальный делитель частоты, то мы не то что секунду не отсчитаем, мы даже полсекунды не сможем посчитать. А у нас задача именно такая, чтобы досчитывать до 1 секунды, чтобы управлять наращиванием счёта светодиодного индикатора. Можно конечно применить ещё наращивание переменной до определенной величины, но хотелось бы полностью аппаратного счёта.

Но есть ещё один таймер — это полноправный 16-битный таймер. Он не только 16-битный, но есть в нём ещё определённые прелести, которых нет у других таймеров. С данными опциями мы познакомимся позже.

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

Прерывания (Interrupts) — это такие механизмы, которые прерывают код в зависимости от определённых условий или определённой обстановки, которые будут диктовать некоторые устройства, модули и шины, находящиеся в микроконтроллере.

В нашем контроллере Atmega8 существует 19 видов прерываний. Вот они все находятся в таблице в технической документации на контроллер

image01

Какого типа могут быть условия? В нашем случае, например, досчитал таймер до определённой величины, либо например в какую-нибудь шину пришёл байт и другие условия.

На данный момент мы будем обрабатывать прерывание, которое находится в таблице, размещённой выше на 7 позиции — TIMER1 COMPA, вызываемое по адресу 0x006.

Теперь давайте рассмотрим наш 16-битный таймер или TIMER1.

Вот его структурная схема

image02

Мы видим там регистр TCNTn, в котором постоянно меняется число, то есть оно постоянно наращивается. Практически это и есть счётчик. То есть данный регистр и хранит число, до которого и досчитал таймер.

А в регистры OCRnA и OCRnB (буквы n — это номер таймера, в нашем случае будет 1) — это регистры, в которые мы заносим число, с которым будет сравниваться чило в регистре TCNTn.

Например, занесли мы какое-нибудь число в регистр OCRnA и как только данное число совпало со значением в регистре счёта, то возникнет прерывание и мы его сможем обработать. Таймеры с прерываниями очень похожи на обычную задержку в коде, только когда мы находимся в задержке, то мы в это время не можем выполнять никакой код (ну опять же образно "мы", на самом деле АЛУ). А когда считает таймер, то весь код нашей программы в это время спокойно выполняется. Так что мы выигрываем колоссально, не давая простаивать огромным ресурсам контроллера по секунде или даже по полсекунды. В это время мы можем обрабатывать нажатия кнопок, которые мы также можем обрабатывать в таймере и многое другое.

Есть также регистр TCCR. Данный регистр — это регистр управления. Там настраиваются определенные биты, отвечающие за конфигурацию таймера.

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

Даный регист TCCR отвечает за установку делителя, чтобы таймер не так быстро считал, также он отвечает (вернее его определённые биты) за установку определённого режима.

За установку режима отвечают биты WGM

image03

Мы видим здесь очень много разновидностей режимов.

Normal — это обычный режим, таймер считает до конца.

PWM — это ШИМ только разные разновидности, то есть таймер может играть роль широтно-импульсного модулятора. С данной технологией мы будем знакомиться в более поздних занятиях.

CTC — это сброс по совпадению, как раз то что нам будет нужно. Здесь то и сравнивются регистры TCNT и OCR. Таких режима два, нам нужен первый, второй работает с другим регистром.

Все разновидности режимов мы в данном занятии изучать не будем. Когда нам эти режимы потребуются, тогда и разберёмся.

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

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

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

Добавим ещё одну функцию, благо добавлять функции мы на прошлом занятии научились. Код функции разместим после функции segchar и до функции main. После из-за того, что мы будем внутри нашей новой функции вызывать функцию segchar.

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

Поэтому первую функцию мы назвовём timer_ini

void timer_ini ( void )

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

Данная функция, как мы видим не имеет ни каких аргументов — ни входных, не возвращаемых. Давайте сразу данную функцию вызовем в функции main()

unsigned char butcount=0, butstate=0;

timer_ini ();

Теперь мы данную функцию начнём потихонечку наполнять кодом.

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

void timer_ini ( void )

TCCR1B |= (1 WGM12 ); // устанавливаем режим СТС (сброс по совпадению)

Из комментария мы видим, что мы работает с битами режима, и установим мы из них только бит WGM12, остальные оставим нули. Исходя из этого мы сконфигурировали вот такой режим:

image04

Также у таймера существует ещё вот такой регистр — TIMSK. Данный регистр отвечает за маски прерываний — Interrupt Mask. Доступен данный регистр для всех таймеров, не только для первого, он общий. В данном регистре мы установим бит OCIE1A, который включит нужный нам тип прерывания TIMER1 COMPA

image05

TCCR1B |= (1 WGM12 ); // устанавливаем режим СТС (сброс по совпадению)

TIMSK |= (1 OCIE1A ); //устанавливаем бит разрешения прерывания 1ого счетчика по совпадению с OCR1A(H и L)

Теперь давайте поиграемся с самими регистрами сравнения OCR1A(H и L). Для этого придётся немного посчитать. Регистр OCR1AH хранит старшую часть числа для сравнения, а регистр OCR1AL — младшую.

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

TIMSK |= (1 OCIE1A ); //устанавливаем бит разрешения прерывания 1ого счетчика по совпадению с OCR1A(H и L)

OCR1AH = 0b10000000; //записываем в регистр число для сравнения

OCR1AL = 0b00000000;

TCCR1B |= ( ); //установим делитель.

Пока никакой делитель не устанавливаем, так как мы его ещё не посчитали. Давайте мы этим и займёмся.

Пока у нас в регистре OCR1A находится число 0b1000000000000000, что соответствует десятичному числу 32768.

Микроконтроллер у нас работает, как мы договорились, на частоте 8000000 Гц.

Разделим 8000000 на 32768, получим приблизительно 244,14. Вот с такой частотой в герцах и будет работать наш таймер, если мы не применим делитель. То есть цифры наши будут меняться 244 раза в секунду, поэтому мы их даже не увидим. Поэтому нужно будет применить делитель частоты таймера. Выберем делитель на 256. Он нам как раз подойдёт, а ровно до 1 Гц мы скорректируем затем числом сравнения.

Вот какие существуют делители для 1 таймера

image07

Я выделил в таблице требуемый нам делитель. Мы видим, что нам требуется установить только бит CS12.

Так как делитель частоты у нас 256, то на этот делитель мы поделим 8000000, получится 31250, вот такое вот мы и должны занести число в TCNT. До такого числа и будет считать наш таймер, чтобы досчитать до 1 секунды. Число 31250 — это в двоичном представлении 0b0111101000010010. Занесём данное число в регистровую пару, и также применим делитель

OCR1AH = 0b01111010; //записываем в регистр число для сравнения

OCR1AL = 0b00010010;

TCCR1B |= (1 CS12 ); //установим делитель.

С данной функцией всё.

Теперь следующая функция — обработчик прерывания от таймера по совпадению. Пишется она вот так

ISR ( TIMER1_COMPA_vect )

И тело этой функции будет выполняться само по факту наступления совпадения чисел.

Нам нужна будет переменная. Объявим её глобально, в начале файла

unsigned char i ;

Соответственно, из кода в функции main() мы такую же переменную уберём

unsigned char i ;

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

while (1)

// for(i=0;i

// while (butstate==0)

// if (!(PINB&0b00000001))

// if(butcount

// butcount++;

// else

// i=0;

// butstate=1;

// else

// if(butcount > 0)

// butcount—;

// else

// butstate=1;

// segchar(i);

// _delay_ms(500);

// butstate=0;

Теперь, собственно, тело функции-обработчика. Здесь мы будем вызывать функцию segchar. Затем будем наращивать на 1 переменную i. И чтобы она не ушла за пределы однозначного числа, будем её обнулять при данном условии

if ( i >9) i =0;

segchar ( i );

i ++;

Теперь немного исправим код вначале функции main(). Порт D, отвечающий за состояние сегментов, забьём единичками, чтобы при включении у нас не светился индикатор, так как он с общим анодом. Затем мы здесь занесём число 0 в глобавльную переменную i, просто для порядка. Вообще, как правило, при старте в неициализированных переменных и так всегда нули. Но мы всё же проинициализируем её. И, самое главное, чтобы прерывание от таймера работало, её недостаточно включить в инициализации таймера. Также вообще для работы всех прерываний необходимо разрешить глобальные прерывания. Для этого существует специальная функция sei() — Set Interrupt.

Теперь код будет вот таким

PORTD = 0b11111111;

i =0;

sei ();

Также ещё мы обязаны подключить файл библиотеки прерываний вначале файла

Также переменные для кнопки нам пока не потребуются, так как с кнопкой мы сегодня работать не будем. Закомментируем их

//unsigned char butcount=0, butstate=0;

Соберём наш код и проверим его работоспособность сначала в протеусе. Если всё нормально работает, то проверим также в живой схеме

Всё у нас работает. Отлично!

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

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

Купить программатор можно здесь (продавец надёжный) USBASP USBISP 2.0

Читайте также: