Войти
Немного о компьютере
  • Как прошить htc desire x, и где скачать прошивку?
  • Создание виртуального образа диска в домашних условиях
  • Обзор Sharp AQUOS S2 – безрамочный, необычный, классный Новый sharp
  • Как разблокировать телефон, если забыл пароль?
  • Инфокоммуникационные технологии и системы связи
  • IPhone не синхронизируется с iTunes: почему возникает такая проблема и как ее исправить?
  • Avr создания таймера atmega8 пример. Программные таймеры на ассемблере. Что умееют таймеры

    Avr создания таймера atmega8 пример. Программные таймеры на ассемблере. Что умееют таймеры

    Таймеры — еще один классический модуль, присутствующий практически во всех МК. Он позволяет решать множество задач, самая распространная из которых — задание стабильных временных интервалов. Второе по популярности применение — генерация ШИМ (о нем далее) на выводе МК. Несмотря на то, что, как уже было сказано, применение таймеров отнюдь не ограничивается этими задачами, здесь будут рассмотрены только эти две как наиболее распространенные.

    Сам по себе таймер представляет собой двоичный счетчик, подключенный к системе тактирования микроконтроллера через дополнительный делитель. К нему, в свою очередь, подключены блоки сравнения (их может быть много), которые способны выполнять разные полезные функции и генерировать прерывания — в зависимости от настроек. Упрощенно устройство таймера можно представить следующим образом:

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

    Генерация временных интервалов с помощью таймера.

    Прерывания.

    Из названия явствует, что главным назначением блоков сравнения является постоянное сравнение текущего значения таймера со значением, заданным в регистре OCRnX. Уже упоминалось, что имена регистров часто несут в себе глубокий сакральный смысл — и регистры сравнения не являются исключением. Так, n обозначает номер таймера, X — букву (тоже способ нумерации, блоков сравнения может быть много) регистра сравнения. Таким образом, OCR1A можно понять как O utput C ompare R egister of 1 st timer, unit A . К слову, искушенному эмбеддеру это даcт возможность предположить, что, возможно, существует таймер 0 и регистр сравнения B…

    Итак, блоки сравнения могут генерировать прерывания при каждом совпадении значения таймера (к слову, оно находится в регистре TCNTn T imer/C ouNT er #n ) с заданым числом. Читателю уже должно быть знакомо понятие прерывания, однако на всякий случай освежим его в памяти, а заодно и поговорим о том, как его описать на С. Так вот, вышесказанное значит, что, как только случится описанное событие, процессор сохранит номер текущей команды в стеке и перейдет к выполению специально определенного кода, а после вернется обратно. Все происходит почти так же, как и при вызове обычной функции, только вызывается она на аппаратном уровне. Объявляются такие функции с помощью макроса, объявленного в avr/interrupt.h (ISR — «I nterrupt S ervice R outine», «обработчик прерывания»):

    ISR (< имя вектора прерывания> ) { /*код обработчка прерывания*/ }

    Каждому прерыванию (естесственно, их много) соответствует т.н. вектор прерывания — константа, также объявленная в avr/interrupt. Например, обработчик прерывания по совпадению значения таймера со значением регистра OCR1A будет иметь следующий вид:

    ISR (TIMER1_COMPA_vect) { /*код обработчика*/ }

    Несомненно, проницательный читатель уже догадался, каким образом формируются имена векторов. Тем не менее, полный список этих констант можно посмотреть в документации на avr-libc (библиотека стандартных функций для AVR-GCC).

    Даташит (от англ. datasheet) — файл технической документации, описание конкретного прибора (микросхемы, транзистора и т.д.). Содержит всю информацию о характеристиках и применении компонента. Почти всегда имеет формат PDF. Обычно гуглится как «<название компонента> pdf».

    Последние три бита управляют предделителем, упомянутым в самом начале (остальные же нас пока не интересуют):

    Сконфигурируем таймер так, чтобы прерывания происходили два раза в секунду. Выберем предделитель 64; для этого установим биты CS11 и CS10:

    TCCR1B= (1 < < CS11) | (1 < < CS10) ;

    Тогда частота счета составит 8МГц/64=125КГц, т.е. каждые 8мкС к значению TCNT1 будет прибавляться единица. Мы хотим, чтобы прерывания происходили с периодом 500мС. Очевидно, что за это время таймер досчитает до значения 500мС/8мкС=62500, или 0xF424. Таймер 1 — шестнадцатибитный, так что все в порядке.

    OCR1A= 0xF424 ;

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

    Осталось только разрешить прерывание по совпадению — за него отвечает бит в регистре TIMSK1:

    Про него написано следующее:

    Итак, устанавливаем нужное значение:

    TIMSK1= (1 < < OCIE1A) ;

    Кроме того, следует помнить, что перед использованием прерываний необходимо их глобально разрешить вызовом функции sei() . Для глобального запрета прерываний служит функция cli() . Эти функции устанавливают/очищают бит I в регистре SREG , управляя самой возможностью использования такого механизма, как прерывания. Регистры же вроде TIMSKn — не более чем локальные настройки конкретного модуля.

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

    Итак, программу, мигающую светодиодом, с использованием прерываний можно переписать следующим образом:

    # include < avr/io.h > # include < avr/interrupt.h > ISR (TIMER1_COMPA_vect) { TCNT1= 0 ; if (PORTB & (1 < < PB0) ) PORTB& = ~ (1 < < PB0) ; else PORTB| = (1 < < PB0) ; } void main (void ) { DDRB= 0xFF ; PORTB= 0 ; OCR1A= 0xF424 ; TIMSK1= (1 < < OCIE1A) ; TCCR1B= (1 < < CS11) | (1 < < CS10) ; sei() ; while (1 ) ; }

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

    Генерация ШИМ с помощью таймера.

    При определеных настройках блоки сравнения позволяют организовать аппаратную генерацию ШИМ-сигнала на ножках МК, обозначенных как OСnX:

    ШИМ (PWM) — Ш иротно-И мпульсная М одуляция (P ulse W idth M odulation). ШИМ-сигнал представляет собой последовательность прямоугольных импульсов с изменяющейся длительностью:

    Для ШИМ вводятся две родственные характеристики — коэффициент заполнения (duty cycle, D) и скважность — величина, обратная коэффицинту заполнения. Коэффициент заполнения представляет собой отношение времени импульса к длительности периода:

    Коэффициент заполнения часто выражается в процентах, но так же распространена запись в десятичных дробях.

    Значение ШИМ для народного хозяйства заключается в том, что действующее значение напряжения такого сигнала прямо пропоционально коэффициенту заполнения:

    — с этим интегралом пособие смотрится солиднее; зависимость же выражается следующей формулой:

    U avg — среднее значение напряжения (тут — оно же действующее);
    D — коэффициент заполнения;
    U p-p — амплитуда импульса.

    Таким образом, ШИМ является простым способом получить аналоговый сигнал с помощью микроконтроллера — для этого такую последовательность импульсов надо подать на фильтр низких частот (который, кстати, и является физическим воплощением интеграла, записанного выше).

    Наиболее употребительным режимом ШИМ является т.н. Fast PWM (об остальных режимах можно прочесть непосредственно в документации), поэтому рассмотрим его. В этом случае блоки сравнения работают следующим образом: с обнулением таймера на выход OCnX подается высокий уровень; как только таймер досчитает до числа, записанного в OCRnX, OCnX переводится в состояние низкого уровня. Все это повторяется с периодом переполнения счетчика. Получается, что ширина выходного импульса зависит от значения OCRnX, а выходная частота равна тактовой частоте таймера, поделенной на его максимальное значение. Рисунок из даташита поясняет сказанное:

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

    Настройка блока сравнения для генерации ШИМ.

    Здесь нам опять поможет документация. Итак, сначала надо перевести блок сравнения в режим генерации ШИМ и выбрать интересующий выход из доступных. Эти настройки доступны в регистре TCCR0A:

    Нас интересуют биты WGMxx и COMnXn. Про них сказано следующее:

    Т.е., нас интересуют биты WGM00 и WGM01 — Fast PWM mode,

    а также COM0A1 — non-inverting PWM на выводе OC0A. Настраиваем:

    TCCR0A= (1 < < COM0A1) | (1 < < WGM01) | (1 < < WGM00) ;

    Естесственно, кроме этого выбранная ножка должна быть настроена на выход с помощью регистра DDR соответствующего порта.

    OCR0A= 128 ;

    И, наконец, включить таймер, выбрав делитель. Тут все так же:


    Обычно для ШИМ выбирается максимально возможная частота (для того, чтобы получить максимальное качество выходного сигнала). Т.е., целесообразно установить минимальное значение делителя:

    TCCR0B= (1 < < CS00) ;

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

    Как упомянуто выше, ШИМ — простой способ получения аналогового сигнала с помощью МК. Например, можно организовать плавное мигание светодиода (в этом случае роль интегратора-ФНЧ выполняет глаз наблюдателя, так что светодиод можно подключить к ножке МК через обычный резистор).

    Некоторые моменты в предлагаемом примере требуют пояснения.

    В списке включаемых файлов присутствует загадочный stdint.h — в этом файле объявлены типы с явно указанной разрядностью, например

    uint8_t u nsigned 8 -bit int eger t ype
    uint16_t u nsigned 16 -bit int eger t ype
    uint32_t u nsigned 32 -bit int eger t ype
    int8_t — signed 8 -bit int eger t ype

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

    Модификатор volatile означает, что компилятору запрещается оптимизировать данную переменную. Например, если скомпилировать следующий пример:

    void main (void ) { unsigned char i= 0 ; while (1 ) { i+ + ; } }

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

    #include #include #include volatile uint8_t pwm_value= 0 , dn_count= 0 ; ISR (TIMER1_COMPA_vect) { TCNT1= 0 ; if (dn_count) //плавно меняем яркость диода, по шагу за раз pwm_value--; else pwm_value++; if (pwm_value== 0 ) //проверка границ, переключение разгорание/затухание dn_count= 0 ; if (pwm_value== 0xFF ) dn_count= 1 ; OCR0A= pwm_value; //устанавливаем новый коэфф. заполнения } void main(void ) { DDRD= 0xFF ; //настройка порта на выход PORTD= 0 ; OCR1A= 0xF424 ; //константа, определяющая частоту прерываний TIMSK1= (1 << OCIE1A) ; //разрешаем прерывание по совпадению канала А TCCR1B= (1 << CS11) | (1 << CS10) ; //запускаем таймер 1 TCCR0A= (1 << COM0A1) | (1 << WGM01) | (1 << WGM00) ; //таймер 0 будет генерировать ШИМ OCR0A= 128 ; //начальное значение ШИМ TCCR0B= (1 << CS00) ; //запускаем таймер 0 sei() ; //разрешаем прерывания while (1 ) ; //все, дальше процесс идет на прерываниях и аппаратном ШИМе } $37 ($57)
    OCIE2 TOIE2 TICIE1 OCIE1A OCIE1B TOIE1 OCIE0 TOIE0
    TIMSK Чтение/Запись
    • Bit 7 - OCIE2: Timer/Counter2 Output Compare Interrupt Enable - Разрешение прерывания по совпадению таймера/счетчика2
      При установленном бите OCIE2 и установленном бите I регистра статуса разрешается прерывание по совпадению содержимого регистра сравнения и состояния таймера/ счетчика2. Соответствующее прерывание (с вектором $0012) выполняется если произойдет совпадение при сравнении содержимого регистра сравнения и состояния таймера/счетчика2. В регистре флагов прерывания TIFR (Timer/Counter Interrupt Flag Register) устанавливается флаг совпадения таймера/счетчика2.
    • Bit 6 - TOIE2: Timer/Counter2 Overflow Interrupt Enable - Разрешение прерывания по переполнению таймера/счетчика2
      При установленном бите TOIE2 и установленном бите I регистра статуса разрешается прерывание по переполнению таймера/счетчика2. Соответствующее прерывание (с вектором $0014) выполняется если произойдет переполнение таймера/счетчика2. В регистре флагов TIFR (Timer/Counter Interrupt Flag Register) устанавливается флаг переполнения таймера/счетчика2.
    • Bit 5 - TICIE1: Timer/Counter1 Input Capture Interrupt Enable - Разрешение прерывания по захвату таймера/счетчика1
      При установленном бите TICIE1 и установленном бите I регистра статуса разрешается прерывание по захвату таймера/счетчика1. Соответствующее прерывание (с вектором $0016) выполняется если произойдет запуск захвата по выводу 29, PD4(IC1). В регистре флагов TIFR (Timer/Counter Interrupt Flag Register) устанавливается флаг захвата таймера/счетчика1.
    • Bit 4 - OCE1A: Timer/Counter1 Output CompareA Match Interrupt Enable - Разрешение прерывания по совпадению регистра A с таймером/счетчиком1
      При установленном бите OCIE1A и установленном бите I регистра статуса разрешается прерывание по совпадению регистра A с состоянием таймера/счетчика1. Соответствующее прерывание (с вектором $0018) выполняется если произойдет совпадение содержимого регистра A сравнения выхода с состоянием таймера/ счетчика1. В регистре флагов TIFR (Timer/Counter Interrupt Flag Register) устанавливается флаг совпадения регистра A с таймером/счетчиком1.
    • Bit 3 - OCIE1B: Timer/Counter1 Output CompareB Match Interrupt Enable - Разрешение прерывания по совпадению регистра B с таймером/счетчиком1
      При установленном бите OCIE1B и установленном бите I регистра статуса разрешается прерывание по совпадению регистра B с состоянием таймера/счетчика1. Соответствующее прерывание (с вектором $001A) выполняется если произойдет совпадение содержимого регистра B сравнения выхода с состоянием таймера/счетчика1. В регистре флагов TIFR (Timer/Counter Interrupt Flag Register) устанавливается флаг совпадения регистра B с таймером/счетчиком1.
    • Bit 2 - TOIE1: Timer/Counter1 Overflow Interrupt Enable - Разрешение прерывания по переполнению таймера/счетчика1
      При установленном бите OCIE1B и установленном бите I регистра статуса разрешается прерывание по переполнению таймера/счетчика1. Соответствующее прерывание (с вектором $001C) выполняется если произойдет переполнение таймера/счетчика1. В регистре флагов TIFR (Timer/Counter Interrupt Flag Register) устанавливается флаг переполнения таймера/счетчика1.При нахождении таймера/счетчика1 в PWM режиме флаг переполнения счетчика устанавливается когда счетчик изменит направление счета при $0000.
    • Bit 1 - OCIE0: Timer/Counter0 Output Compare Interrupt Enable - Разрешение прерывания по совпадению таймера/счетчика0
      При установленном бите OCIE0 и установленном бите I регистра статуса разрешается прерывание по совпадению содержимого регистра сравнения и состояния таймера/ счетчика0. Соответствующее прерывание (с вектором $001E) выполняется если произойдет совпадение при сравнении содержимого регистра сравнения и состояния таймера/счетчика0. В регистре флагов прерывания TIFR (Timer/Counter Interrupt Flag Register) устанавливается флаг совпадения таймера/счетчика0.
    • Bit 0 - TOIE0: Timer/Counter0 Overflow Interrupt Enable - Разрешение прерывания по переполнению таймера/счетчика0
      При установленном бите TOIE0 и установленном бите I регистра статуса разрешается прерывание по переполнению таймера/счетчика0. Соответствующее прерывание (с вектором $0020) выполняется если произойдет переполнение таймера/счетчика0. В регистре флагов TIFR (Timer/Counter Interrupt Flag Register) устанавливается флаг переполнения таймера/счетчика0.

    Прежде чем приступить к изучению таймера определимся с базовым понятием «частота». Простым языком, это количество повторений, в секунду. Это значит, что если вы за секунду хлопнете в ладошки 2 раза, то частота хлопков будет равна 2Гц. Если за 3 раза, значит 3Гц.

    Каждый микроконтроллер работает на определенной частоте. Большинство инструкций выполняется за один такт, поэтому чем выше частота, тем быстрее работает микроконтроллер. Если нет источника тактирования, соответственно ничего работать не будет. На случай отсутствия внешнего источника тактирования, в большинстве микроконтроллеров имеется свой внутренний генератор. Обычно на него «с завода» настроены.

    Частота внутреннего источника может изменяться («плавать») из за температуры и т.п., поэтому считается непригодным для серьезных проектов, а у нас ведь именно такие 🙂 Поэтому применяется стабильный источник внешней частоты — кварцевый резонатор (кварц). Один из вариантов исполнения кварцевого резонатора:

    Теперь, кое что о таймере. Таймер работает на той же частоте, что и микроконтроллер. Иногда это может быть слишком быстро, поэтому используют предделитель который уменьшает количество тиков в 8/64/256/1024… раз. Включается это все программно.

    Допустим, мы выбрали предделитель 1024, частота микроконтроллера 8 МГц, значит после предделителя частота таймера станет:
    8 000 000 / 1024 = 7813 Гц — это частота, на которой работает наш таймер. По простому говоря, за одну секунду таймер тикнет 7813 раз.

    К количеству тиков можно привязать выполнение кода. Эта фича есть не для всех таймеров, читайте документацию на свой камень. Допустим, нам нужно, чтобы раз в 0,5 секунды выполнялся наш код. За одну секунду 7813 тиков, за пол секунды в 2 раза меньше — 3906. Это значение вносится в регистр сравнения, и с каждым тиком проверяется достаточно ли оттикало или нет, как в будильнике, только очень быстро.

    Но вот у нас совпали эти 2 значения и что дальше? Для этого существует такая полезная штука как прерывание по совпадению. Это значит, что при совпадении таймера и регистра сравнения, ваша текущая программа остановится. После этого выполнится участок кода, который абсолютно не связан с основной программой. Внутри этого участка вы можете писать что угодно и не беспокоиться о том, что он как то повлияет на программу, выполнится он только когда значение таймера совпадет с регистром сравнения.

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

    Вот теперь мы готовы написать нашу программу. Поэтому создаем проект с помощью мастера проектов. Сразу прицепим LCD, мы же уже это умеем).

    Переходим на вкладку Timers и тут остановимся поподробнее:

    Выбираем частоту 7813 и устанавливаем галочку напротив пункта Interrupt on: Compare A Match. Таким образом мы указали, что при совпадении значения выполнять прерывание (то о чем было написано выше). Прерывание будем выполнять 1 раз в секунду, т.е. нам нужно тикнуть 7813 раз, поэтому переводим число 7813 в шестнадцатеричную систему и получим 1e85. Именно его и записываем в регистр сравнения Comp A. Регистр сравнения Comp A 16 битный, поэтому число больше 2^16=65536 мы записать не можем.

    Генерим, сохраняем, вычищаем наш код. Появится новый непонятный кусок кода

    // Timer 1 output compare A interrupt service routine
    interrupt void timer1_compa_isr(void)
    {

    Это то самое прерывание. Именно внутри этих скобок мы можем писать тот код, который мы хотели бы выполнять через определенные промежутки времени. У нас это одна секунда. Итак логично создать переменную, которую мы будем увеличивать 1 раз в секунду, т.е. 1 раз за прерывание. Поэтому проинициализируем переменную int s =0; а в прерывании будем ее увеличивать от 0 до 59. Значение переменной выведем на жк дисплей. Никаких хитростей, все очень просто.
    Получившийся код.

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include #asm .equ __lcd_port= 0x18 ; PORTB #endasm #include int s = 0 ; // переменная для хранения секунд // Обработка прерывания по совпадению interrupt [ TIM1_COMPA] void timer1_compa_isr(void ) { s++; // увеличиваем переменную каждую секунду if (s> 59 ) // обнуляем секунды после 59 { s= 0 ; } TCNT1= 0 ; //обнуляем таймер } void main(void ) { TCCR1A= 0x00 ; //настройка таймера TCCR1B= 0x05 ; TCNT1= 0x00 ; //здесь увеличиваются тики OCR1A= 0x1E85 ; //записываем число в регистр сравнения TIMSK= 0x10 ; //запускаем таймер lcd_init(8 ) ; #asm("sei") while (1 ) { lcd_gotoxy(0 , 0 ) ; //вывод в 0 координате X и Y lcd_putchar(s/ 10 + 0x30 ) ; //вывод десятков секунд lcd_putchar(s% 10 + 0x30 ) ; //вывод секунд } ; }

    #include #asm .equ __lcd_port=0x18 ;PORTB #endasm #include int s = 0; // переменная для хранения секунд // Обработка прерывания по совпадению interrupt void timer1_compa_isr(void) { s++; // увеличиваем переменную каждую секунду if(s>59) // обнуляем секунды после 59 { s=0; } TCNT1=0; //обнуляем таймер } void main(void) { TCCR1A=0x00; //настройка таймера TCCR1B=0x05; TCNT1=0x00; //здесь увеличиваются тики OCR1A=0x1E85; //записываем число в регистр сравнения TIMSK=0x10; //запускаем таймер lcd_init(8); #asm("sei") while (1) { lcd_gotoxy(0,0); //вывод в 0 координате X и Y lcd_putchar(s/10+0x30); //вывод десятков секунд lcd_putchar(s%10+0x30); //вывод секунд }; }

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

    Для чего это надо?

    Отсчитывать время программно, в теле основной программы - не самый лучший способ. Чтобы отсчитать секунду, программа только и будет делать, что считать эту самую секунду.

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

    Каждый раз, отсчитывая хотя бы миллисекунду, программа как бы виснет на это время.А если надо отсчитать месяц? – устройство зависнет на месяц? Что же делать?

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

    Создаём проект в MPLAB IDE с таймером TMR2

    Выберем контроллер 16F628A. Скачаем на него «даташит» (Data Sheet). В нём имеется таймер с компаратором TMR2. Создадим в MPLAB-е проект. Шаблон найдёте у себя на системном диске, например:

    C:\Program Files\Microchip\MPASM Suite\Template\Code\16F628ATEMP.ASM

    Лишние комментарии в шаблоне можно убрать и добавить свои.

    Для создания проекта лучше воспользоваться Мастером проектов в меню MPLAB / Project / Project Wizard…

    Попробуем скомпилировать то, что получилось.

    Так, MPLAB ругается на свой же шаблон!!!??? Проверим в файле P16F628A.INC как должно быть.

    Найти его можно там же, в каталоге Microchip:

    C:\Program Files\Microchip\MPASM Suite\ P16F628A.INC

    Можно скопировать этот файл, на всякий случай, в свой проект, пригодится.

    Посмотрим как там записано и поправим в шаблоне:

    CONFIG _CP_OFF & _DATA_CP_OFF

    На

    CONFIG _CP_OFF & DATA_CP_OFF

    Разница небольшая, но существенная. Теперь всё нормально, компилируется.

    В программировании мелочей не бывает. Так что не верьте всему, что пишут, проверяйте:-)

    Включимвменюсимулятор Debugger / Select Tool / MPLAB SIM

    В Debugger / Settings… выберемчастотукварца 4 MHz

    Там в же в Debugger берём секундомер Stopwatch

    В начале программы main настроим контроллер.

    Перед таймером установим предделитель:4 и число.249 в регистр предустановки.

    Теперь у нас: 4 Mhz / 4 = 1 машинный цикл = 1 микросекунда.

    Умножаем на предделитель 4 и на предустановленное число 250 (от 0 до 249), получаем = 1 миллисекунда.

    В начале подпрограммы прерывания, начинающегося с ORG 0x004 , добавим проверку на прерывание от TMR2, чтобы не путать с другими прерываниями. У нас пока других прерываний нет, но, возможно, потом появятся. Так что лучше сделать сразу:

    Bcf PIR1,TMR2IF ; Сброс прерывания по таймеру TMR2.

    ; И сразу ставим метку, двойным щелчком на строке со сбросом прерывания от TMR2:

    Компилируем программу и запускаем симулятор. Программа остановилась на нашей метке.

    Сбрасываем секундомер и снова запускаем симулятор.

    На этот раз на секундомере показывает 1000 МЦ (машинных циклов), а ниже 1 миллисекунда.

    То что нам надо. Прерывания происходят точно через равные промежутки времени в 1 миллисекунду.

    Что мы имеем.

    Произошло событие, контроллер отсчитал 1 мсек. Так и озаглавим эти строчки:

    Event_Time_1ms

    Btfss PIR1,TMR2IF ; Проверка прерывания от TMR2.

    Goto other_interrupts ; иначе переходим на проверку других прерываний.

    Bcf PIR1,TMR2IF ; Сброс прерывания по таймеру.

    Всё что ниже, будет происходить с периодичностью в 1 мсек.

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

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

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

    каждые 17 миллисекунд.

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

    времени. Например: 10 мсек, 0,1 сек, 1 сек, 1 мин, 1 час и т.д.

    Впрочем, при желании счётчик на те же 17 миллисекунд можно добавить тут же, в прерывании.

    Добавляем счётчики таймеров

    Для каждого интервала времени потребуется регистр для счёта:

    Reg_Time_10ms ; Регистры счётчиков времени.

    Reg_Time_01sec

    Reg_Time_1sec

    Reg_Time_1min

    Reg_Time_1hour

    Reg_Time_1day

    В эти регистры будем загружать константы. Назовём их соответствующим образом.

    #Define TIME_10ms .10 ; Константы для регистров счётчиков времени.

    #Define TIME_01sec .10 ;

    #Define TIME_1sec .10 ;

    #Define TIME_1min .60 ;

    #Define TIME_1hour .60 ;

    #Define TIME_1day .24 ;

    Все эти константы необходимо загрузить в соответствующие регистры в начале программы.

    Ограничимся днями, т.к. они все одинаковые, по 24 часа. Недели тоже одинаковые, но неделями редко считают,

    разве что беременность:-).

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

    проще использовать микросхемы реального времени типа PCF8583 и т.п.

    Счетчики могут быть с другими интервалами, не 10 ms, а 100 ms например. Это как вам удобно.

    Смотрите прилагаемый файл Time_intervals1.asm

    Счётчики запишем в таком виде:

    Event_Time_10ms

    Decfsz reg_Time_10ms,F ; Вычитаем 1, если не ноль, пропускаем следующую инструкцию.

    Goto other_interrupts ; иначе переходим на проверку других прерываний.

    Movlw TIME_10ms ; Константу загружаем

    Movwf reg_Time_10ms ; обратно в регистр.

    Все остальные счётчики - такие же.

    Теперь, если выставить точку останова в конце любого счётчика (ставьте точки останова только по одной),

    программа будет останавливаться там

    с соответствующей периодичностью.

    Как использовать программные счётчики?

    Создадим в Proteus-е модель со светодиодами и помигаем ими. Смотрите файл Time_intervals1.DSN

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

    Выделим ещё один регистр для индикатора и назовём его indicator.

    Индикатор будет переключаться только по необходимости.

    Пишем подпрограмму LED_Indicator.

    В начале подпрограммы проверяется флаг события EV_indicator и подпрограмма продолжит выполнение,

    только если это событие было, и флаг был поднят.

    Организуем переключение светодиода один раз в секунду. Для этого выставим флаг LED_1sec после

    прерывания в 1 сек.

    Это событие надо обработать. Напишем ещё одну программу Switch_ LED_1sec

    Как и предыдущая подпрограмма, она будет проверять флаг своего события EV_ LED_1sec.

    Переключим светодиод на индикаторе маской LED_1sec.

    Movlw LED_1sec ; Переключим светодиод LED_1sec

    Xorwf indicator,F ; на индикаторе.

    В конце подпрограммы поднимем флаг события для индикатора EV_indicator.

    Что если светодиод надо переключать не каждую секунду, а с частотой в 1 сек, т.е. переключать каждые 0,5 сек? Выделяем регистр и делаем врезку для отдельного счётчика. Тактовую частоту можно выбрать 0,1 сек, умножив на 5, или 0,01 сек, умножив на 50, её и возьмём. Константа у нас получается 50. Счётчик располагаем в месте поднятия флага 10 мсек. В конце счётчика как всегда поднимаем флаг. Да, и не забудьте про предустановку в начале программы.

    Event_Time_05sec

    Decfsz reg_Time_05sec,F ; Вычитаем 1, если не ноль, пропускаем следующую инструкцию

    Goto Event_Time_01sec ; иначе переходим к следующему счётчику.

    Movlw TIME_05sec

    Movwf reg_Time_05sec

    Bsf event,EV_LED_05sec ; Поднимаем этот флаг события раз в 0,5 сек.

    Ну и подключим ещё один светодиод и добавим подпрограмму переключающую его.

    Почему для каждого светодиода отдельная подпрограмма? Это для примера. События у нас разные, а к этому выводу может быть подключен не светодиод, а насос или вентилятор, сигнализация или нагреватель. И всё будет в программе подключаться отдельно, и работать независимо, не мешая друг другу. Ничто вам не мешает подключить блок светодиодов и выделить для этого блока только одну подпрограмму. Блок этот может содержать сотни светодиодов, а управляться будет по 2-3 проводам одной подпрограммой, которая будет вызываться всё тем же одним флагом. Я ещё не рассказал, как сделать задержки? Точно так же. Можно выделить один или несколько регистров, в зависимости от того, какие интервалы времени вам надо отсчитывать, и с какой точностью. Если на входе счётчика такты в 1 мсек, то и точность будет соответственная. Если точность нужна больше, то считайте машинные циклы. Как делать врезки, мы уже знаем. А запускается счётчик элементарно. Загружаете константы в счётчик и сбрасываете флаг. В конце счёта флаг подымется.

    Подведём итог Кажется, что получилось слишком много кода. На самом деле это не так. Некоторые строки написаны про запас, для отсчёта долей секунд, для минут, часов и дней. Пока они не используются, и вы можете их удалить, или использовать в своей программе. То же, что используется, выполняется точно в определённое время и очень быстро. Например, переключатель светодиода срабатывает только раз в секунду. Подпрограмма индикатора тоже срабатывает по мере необходимости. Для примера я сделал ещё одну программку (см. в папке Flasher101). Там 8 таймеров переключают 8 светодиодов. Первый светодиод мигает раз в секунду, а каждый следующий - на 1% дольше. То есть через 1% х 100 включений, они опять мигают вместе. Получаются интересные визуальные эффекты. И ещё один таймер выключает всю эту мигалку через 5 минут. Получилось просто, точно и эффективно.

    С обычными задержками программы это было бы сложно сделать, запутаешься. А добавить в такую программу что то ещё, вообще было бы невозможно, не меняя предыдущий алгоритм. Кроме того, подпрограмму индикатора можно вызывать не из других подпрограмм, а по времени. Например, каждые 10 мсек или чаще. Это бывает необходимо при динамической индикации. А можно наоборот, вызывать раз в секунду, чтобы показания не мельтешили, и их было удобно считывать. Это бывает необходимо при быстро меняющихся параметрах выводимых на индикацию. Так же можно объединить динамическую индикацию и смену текущих показаний раз в секунду. То есть - имеем временные промежутки в любых сочетаниях. И не только для индикации, а и для сканирования клавиатуры, энкодера, работы с датчиками и т.п. И при всём при этом, у контроллера останется куча машинного времени на другие нужды и вычисления. А подпрограммы, тщательно отлаженные под работу с флагами, будут легко переноситься в другие проекты.

    Всем удачи! Исходные файлы, проекты в Протеусе и другие материалы к данной статье можно взять

    Урок 10

    Таймеры-счетчики. Прерывания

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Он состоит из двух половинок, так как у нас конотроллер 8-битный и в нем не может быть 16-битных регистров. Поэтому в одной половинке регистра (а физически в одном регистре) хранится старшая часть регистра, а в другом — младшая. Можно также назвать это регистровой парой, состоящей из двух отдельных регистров TCCR1A и TCCR1B. Цифра 1 означает то, что регистр принадлежит именно таймеру 1.

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

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

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

    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, остальные оставим нули. Исходя из этого мы сконфигурировали вот такой режим:

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

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

    TIMSK |= (1<< OCIE1A );

    Теперь давайте поиграемся с самими регистрами сравнения 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 таймера

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

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

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

    OCR1AL = 0b00010010 ;

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

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

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

    ISR ( TIMER1_COMPA_vect )

    {

    }

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

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

    #include

    //———————————————

    unsigned char i ;

    //———————————————

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

    int main ( void )

    unsigned char i ;

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

    while (1)

    {

    // for(i=0;i<10;i++)

    // {

    // while (butstate==0)

    // {

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

    // {

    // if(butcount < 5)

    // {

    // butcount++;

    // }

    // else

    // {

    // i=0;

    // butstate=1;

    // }

    // }

    // else

    // {

    // if(butcount > 0)

    // {

    // butcount—;

    // }

    // else

    // {

    // butstate=1;

    // }

    // }

    // }

    // segchar(i);

    // _delay_ms(500);

    // butstate=0;

    // }

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

    ISR ( TIMER1_COMPA_vect )

    if ( i >9) i =0;

    segchar ( i );

    i ++;

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

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

    DDRB = 0x00;

    PORTD = 0b11111111 ;

    PORTB = 0b00000001;

    i =0;

    sei ();

    while (1)

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

    #include

    #include

    #include

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

    int main ( void )

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

    timer_ini ();

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

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

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

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

    Смотреть ВИДЕОУРОК

    Post Views: 17 413