Прерывание по таймеру atmega 16. Учебный курс AVR

  • 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.

Таймеры счетчики микроконтроллеров AVR (часы реального времени). Урок AVR 7

Когда я еще начинал изучать микроконтроллеры, мне захотелось сделать , чтобы . Честно признаюсь, я хотел попробовать включать телевизор только с 7 до 8 часов, а все остальное время он должен был быть отключен. Устройство я сделал, но так его и не применил...

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

Таймеры общего назначения умеют:

  • Тактировать от внешнего часового кварца на 32768 герц
  • Считать разные временные интервалы
  • Считать внешние импульсы в режиме счетчика
  • Генерировать ШИМ-сигнал на определённых выводах МК
  • Генерировать прерывания по какому-то событию, например, при переполнению

Таймеры счетчики могут тактировать от внутреннего генератора тактовой частоты и от счетного входа. Давайте рассмотрим функционал таймера-счетчика 1 в микроконтроллере atmega8 . Запускаем CodeVision AVR, создаем новый проект и соглашаемся на предложение запустить Code WizardAVR

Давайте на примере timer2 реализуем часы реального времени с выводом на lcd дисплей, для этого выставляем таймер как показано на скриншоте

здесь выставляется внешний источник тактирования таймера, в качестве внешнего источника мы будем использовать часовой кварц на 32768 герц, далее установим предделитель на 128, то есть таймер будет работать на частоте 32768/128=256, а счетный регистр то в нас 8-битный (максимальное число 255), получается, что он будет переполнятся раз в секунду, далее мы выставляем галочку возле Overflow interrupt и кликаем на file->Generate, save and exit.

Code Wizard cгенерировал вот такой код

#include // Timer2 overflow interrupt service routine interrupt void timer2_ovf_isr(void) { } void main(void) { // Input/Output Ports initialization // Port B initialization PORTB=0x00; DDRB=0x00; // Port C initialization PORTC=0x00; DDRC=0x00; // Port D initialization PORTD=0x00; DDRD=0x00; // Timer/Counter 0 initialization // Clock source: System Clock // Clock value: Timer 0 Stopped TCCR0=0x00; TCNT0=0x00; // Timer/Counter 1 initialization // Clock source: System Clock // Clock value: 125,000 kHz // Mode: Fast PWM top=00FFh // OC1A output: Discon. // OC1B output: Discon. // Noise Canceler: Off // Input Capture on Falling Edge // Timer1 Overflow Interrupt: Off // Input Capture Interrupt: Off // Compare A Match Interrupt: Off // Compare B Match Interrupt: Off TCCR1A=0x01; TCCR1B=0x0A; TCNT1H=0x00; TCNT1L=0x00; ICR1H=0x00; ICR1L=0x00; OCR1AH=0x00; OCR1AL=0x00; OCR1BH=0x00; OCR1BL=0x00; // Timer/Counter 2 initialization // Clock source: TOSC1 pin // Clock value: PCK2/128 // Mode: Normal top=FFh // OC2 output: Disconnected ASSR=0x08; TCCR2=0x05; TCNT2=0x00; OCR2=0x00; // External Interrupt(s) initialization // INT0: Off // INT1: Off MCUCR=0x00; // Timer(s)/Counter(s) Interrupt(s) initialization TIMSK=0x40; // Analog Comparator initialization // Analog Comparator: Off // Analog Comparator Input Capture by Timer/Counter 1: Off ACSR=0x80; SFIOR=0x00; // Global enable interrupts #asm("sei") while (1) { }; }

#include #include // Alphanumeric LCD Module functions #asm .equ __lcd_port=0x12 ;PORTD #endasm #include unsigned char second=0; //переменная для хранения секунд unsigned char minute=0; //переменная для хранения минут unsigned char hour=0; //переменная для хранения часов char lcd_buffer; //переменная буфер для вывода на дисплей // Timer2 overflow interrupt service routine interrupt void timer2_ovf_isr(void) { if (++second==59) //увеличиваем количество секунд на 1 и проверяем равенство 59 {second = 0; if (++minute==59) {minute = 0; if (++hour==59) { hour = 0; } } } lcd_clear(); //чистим дисплей перед выводом lcd_gotoxy(0,0); // переводим курсор в точку x=0 y=0 sprintf(lcd_buffer,"%i:%i:%i",hour,minute,second); // формируем строку для вывода lcd_puts(lcd_buffer); // выводим строку на дисплей } void main(void) { // Timer/Counter 2 initialization // Clock source: TOSC1 pin // Clock value: PCK2/128 // Mode: Normal top=FFh // OC2 output: Disconnected ASSR=0x08; TCCR2=0x05; TCNT2=0x00; OCR2=0x00; // Timer(s)/Counter(s) Interrupt(s) initialization TIMSK=0x40; // LCD module initialization lcd_init(16); // Global enable interrupts #asm("sei") while (1) { }; }

Программа готова, теперь составим схему в Proteus

С счетчиком итераций главного цикла мы разобрались и выяснили, что для точных временных отсчетов он не годится совершенно — выдержка плавает, да и считать ее сложно. Что делать?

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

И такой счетчик есть, даже не один — это периферийные таймеры. В AVR их может быть несколько штук да еще с разной разрядностью. В ATmega16 три, в ATmega128 четыре. А в новых МК серии AVR может даже еще больше, не узнавал.

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

Что умееют таймеры

  • Тикать с разной скоростью, подсчитывая время
  • Считать входящие извне импульсы (режим счетчика)
  • Тикать от внешнего кварца на 32768гц
  • Генерировать несколько видов ШИМ сигнала
  • Выдавать прерывания (по полудесятку разных событий) и устанавливать флаги

Разные таймеры имеют разную функциональность и разную разрядность. Это подробней смотреть в даташите.

Источник тиков таймера
Таймер/Счетчик (далее буду звать его Т/С) считает либо тактовые импульсы от встроенного тактового генератора, либо со счетного входа.

Погляди внимательно на распиновку ног ATmega16, видишь там ножки T1 и T0?

Так вот это и есть счетные входы Timer 0 и Timer 1. При соответствующей настройке Т/С будет считать либо передний (перепад с 0-1), либо задний (перепад 1-0) фронт импульсов, пришедших на эти входы.

Главное, чтобы частота входящих импульсов не превышала тактовую частоту процессора, иначе он не успеет обработать импульсы.

Кроме того, Т/С2 способен работать в асинхронном режиме. То есть Т/С считает не тактовые импульсы процессора, не входящие импульсы на ножки, а импульсы своего собственного собственного генератора, работающего от отдельного кварца. Для этого у Т/С2 есть входы TOSC1 и TOSC2, на которые можно повесить кварцевый резонатор.

Зачем это вообще надо? Да хотя бы организовать часы реального времени. Повесил на них часовой кварц на 32768 Гц да считай время — за секунду произойдет 128 переполнений (т.к. Т/С2 восьми разрядный). Так что одно переполнение это 1/128 секунды. Причем на время обработки прерывания по переполнению таймер не останавливается, он также продолжает считать. Так что часы сделать плевое дело!

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

То есть еще до попадания в счетный регистр частота импульсов будет делиться. Делить можно на 8, 32, 64, 128, 256, 1024. Так что если повесишь на Т/С2 часовой кварц, да пропустишь через предделитель на 128, то таймер у тебя будет тикать со скоростью один тик в секунду.

Удобно! Также удобно юзать предделитель когда надо просто получить большой интервал, а единственный источник тиков это тактовый генератор процессора на 8Мгц, считать эти мегагерцы задолбаешься, а вот если пропустить через предделитель, на 1024 то все уже куда радужней.

Но тут есть одна особенность, дело в том, что если мы запустим Т/С с каким нибудь зверским предделителем, например на 1024, то первый тик на счетный регистр придет не обязательно через 1024 импульса.

Это зависит от того в каком состоянии находился предделитель, а вдруг он к моменту нашего включения уже досчитал почти до 1024? Значит тик будет сразу же. Предделитель работает все время, вне зависимости от того включен таймер или нет.

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

Например первый таймер работает на выводе 1:64, а второй на выводе 1:1024 предделителя. У второго почти дотикало в предделителе до 1024 и вот вот должен быть тик таймера, но тут ты взял и сбросил предделитель, чтобы запустить первый таймер точно с нуля. Что произойдет? Правильно, у второго делилка тут же скинется в 0 (предделитель то единый, регистр у него один) и второму таймеру придется ждать еще 1024 такта, чтобы получить таки вожделенный импульс!

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

Для сброса предделителей достаточно записать бит PSR10 в регистре SFIOR. Бит PSR10 будет сброшен автоматически на следующем такте.

Счетный регистр
Весь результат мучений, описанных выше, накапливается в счетном регистре TCNTх, где вместо х номер таймера. он может быть как восьмиразрядным, так и шестнадцати разрядным, в таком случае он состоит из двух регистров TCNTxH и TCNTxL — старший и младший байты соответственно.

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

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

Чувствуете лажу? Вот! Таймер точное устройство, поэтому грузить его счетные регистры надо одновременно! Но как? А инженеры из Atmel решили проблему просто:
Запись в старший регистр (TCNTxH) ведется вначале в регистр TEMP. Этот регистр чисто служебный, и нам никак недоступен.

Что в итоге получается: Записываем старший байт в регистр TEMP (для нас это один хрен TCNTxH), а затем записываем младший байт. В этот момент, в реальный TCNTxH, заносится ранее записанное нами значение. То есть два байта, старший и младший, записываются одновременно! Менять порядок нельзя! Только так

Выглядит это так:

CLI ; Запрещаем прерывания, в обязательном порядке! OUT TCNT1H,R16 ; Старшей байт записался вначале в TEMP OUT TCNT1L,R17 ; А теперь записалось и в старший и младший! SEI ; Разрешаем прерывания

Зачем запрещать прерывания? Да чтобы после записи первого байта, прога случайно не умчалась не прерывание, а там кто нибудь наш таймер не изнасиловал. Тогда в его регистрах будет не то что мы послали тут (или в прерывании), а черти что. Вот и попробуй потом такую багу отловить! А ведь она может вылезти в самый неподходящий момент, да хрен поймаешь, ведь прерывание это почти случайная величина. Так что такие моменты надо просекать сразу же.

Читается все также, только в обратном порядке. Сначала младший байт (при этом старший пихается в TEMP), потом старший. Это гарантирует то, что мы считаем именно тот байт который был на данный момент в счетном регистре, а не тот который у нас натикал пока мы выковыривали его побайтно из счетного регистра.

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

Итак, главным регистром является TCCRx
Для Т/С0 и Т/С2 это TCCR0 и TCCR2 соответственно, а для Т/С1 это TCCR1B

Нас пока интересуют только первые три бита этого регистра:
CSx2.. CSx0, вместо х подставляется номер таймера.
Они отвечают за установку предделителя и источник тактового сигнала.

У разных таймеров немного по разному, поэтому опишу биты CS02..CS00 только для таймера 0

  • 000 — таймер остановлен
  • 001 — предделитель равен 1, то есть выключен. таймер считает тактовые импульсы
  • 010 — предделитель равен 8, тактовая частота делится на 8
  • 011 — предделитель равен 64, тактовая частота делится на 64
  • 100 — предделитель равен 256, тактовая частота делится на 256
  • 101 — предделитель равен 1024, тактовая частота делится на 1024
  • 110 — тактовые импульсы идут от ножки Т0 на переходе с 1 на 0
  • 111 — тактовые импульсы идут от ножки Т0 на переходе с 0 на 1

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

За прерывания от таймеров отвечают регистры TIMSК, TIFR. А у более крутых AVR, таких как ATMega128, есть еще ETIFR и ETIMSK — своего рода продолжение, так как таймеров там поболее будет.

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

На данный момент нас тут интересуют только прерывания по переполнению. За них отвечают биты

  • TOIE0 — разрешение на прерывание по переполнению таймера 0
  • TOIE1 — разрешение на прерывание по переполнению таймера 1
  • TOIE2 — разрешение на прерывание по переполнению таймера 2

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

Регистр TIFR это непосредственно флаговый регистр. Когда какое то прерывание срабатывает, то выскакивает там флаг, что у нас есть прерывание. Этот флаг сбрасывается аппаратно когда программа уходит по вектору. Если прерывания запрещены, то флаг так и будет стоять до тех пор пока прерывания не разрешат и программа не уйдет на прерывание.

Чтобы этого не произошло флаг можно сбросить вручную. Для этого в регистре TIFR в него нужно записать 1!

А теперь похимичим
Ну перекроим программу на работу с таймером. Введем программный таймер. Шарманка так и останется, пускай тикает. А мы добавим вторую переменную, тоже на четыре байта:

ORG $010 RETI ; (TIMER1 OVF) Timer/Counter1 Overflow .ORG $012 RJMP Timer0_OV ; (TIMER0 OVF) Timer/Counter0 Overflow .ORG $014 RETI ; (SPI,STC) Serial Transfer Complete

Добавим обработчик прерывания по переполнению таймера 0, в секцию Interrupt. Так как наш тикающий макрос активно работает с регистрами и портит флаги, то надо это дело все сохранить в стеке сначала:

Кстати, давайте создадим еще один макрос, пихающий в стек флаговый регистр SREG и второй — достающий его оттуда.

1 2 3 4 5 6 7 8 9 10 11 12 .MACRO PUSHF PUSH R16 IN R16,SREG PUSH R16 .ENDM .MACRO POPF POP R16 OUT SREG,R16 POP R16 .ENDM

MACRO PUSHF PUSH R16 IN R16,SREG PUSH R16 .ENDM .MACRO POPF POP R16 OUT SREG,R16 POP R16 .ENDM

Как побочный эффект он еще сохраняет и R16, помним об этом:)

1 2 3 4 5 6 7 8 9 10 11 12 13 Timer0_OV: PUSHF PUSH R17 PUSH R18 PUSH R19 INCM TCNT POP R19 POP R18 POP R17 POPF RETI

Timer0_OV: PUSHF PUSH R17 PUSH R18 PUSH R19 INCM TCNT POP R19 POP R18 POP R17 POPF RETI

Теперь инициализация таймера. Добавь ее в секцию инита локальной периферии (Internal Hardware Init).

; Internal Hardware Init ====================================== SETB DDRD,4,R16 ; DDRD.4 = 1 SETB DDRD,5,R16 ; DDRD.5 = 1 SETB DDRD,7,R16 ; DDRD.7 = 1 SETB PORTD,6,R16 ; Вывод PD6 на вход с подтягом CLRB DDRD,6,R16 ; Чтобы считать кнопку SETB TIMSK,TOIE0,R16 ; Разрешаем прерывание таймера OUTI TCCR0,1<

Осталось переписать наш блок сравнения и пересчитать число. Теперь все просто, один тик один такт. Без всяких заморочек с разной длиной кода. Для одной секунды на 8Мгц должно быть сделано 8 миллионов тиков. В хексах это 7A 12 00 с учетом, что младший байт у нас TCNT0, то на наш счетчик остается 7А 12 ну и еще старшие два байта 00 00, их можно не проверять. Маскировать не нужно, таймер мы потом переустановим все равно.

Одна только проблема — младший байт, тот что в таймере. Он тикает каждый такт и проверить его на соответствие будет почти невозможно. Т.к. малейшее несовпадение и условие сравнение выпадет в NoMatch, а подгадать так, чтобы проверка его значения совпала именно с этим тактом… Проще иголку из стога сена вытащить с первой попытки наугад.

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

Тогда наша секунда обеспечивается с точностью 8000 000 плюс минус 256 тактов. Не велика погрешность, всего 0,003%.

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 42 43 44 45 46 47 48 49 ; Main ========================================================= Main: SBIS PIND,6 ; Если кнопка нажата - переход RJMP BT_Push SETB PORTD,5 ; Зажгем LED2 CLRB PORTD,4 ; Погасим LED1 Next: LDS R16,TCNT ; Грузим числа в регистры LDS R17,TCNT+1 CPI R16,0x12 ; Сравниванем побайтно. Первый байт BRCS NoMatch ; Если меньше -- значит не натикало. CPI R17,0x7A ; Второй байт BRCS NoMatch ; Если меньше -- значит не натикало. ; Если совпало то делаем экшн Match: INVB PORTD,7,R16,R17 ; Инвертировали LED3 ; Теперь надо обнулить счетчик, иначе за эту же итерацию главного цикла; мы сюда попадем еще не один раз -- таймер то не успеет натикать 255 значений, ; чтобы число в первых двух байтах счетчика изменилось и условие сработает. ; Конечно, можно обойти это доп флажком, но проще сбросить счетчик:) CLR R16 ; Нам нужен ноль CLI ; Доступ к многобайтной переменной; одновременно из прерывания и фона; нужен атомарный доступ. Запрет прерываний OUTU TCNT0,R16 ; Ноль в счетный регистр таймера STS TCNT,R16 ; Ноль в первый байт счетчика в RAM STS TCNT+1,R16 ; Ноль в второй байт счетчика в RAM STS TCNT+2,R16 ; Ноль в третий байт счетчика в RAM STS TCNT+3,R16 ; Ноль в первый байт счетчика в RAM SEI ; Разрешаем прерывания снова. ; Не совпало - не делаем:) NoMatch: NOP INCM CCNT ; Счетчик циклов по тикает; Пускай, хоть и не используется. JMP Main BT_Push: SETB PORTD,4 ; Зажгем LED1 CLRB PORTD,5 ; Погасим LED2 RJMP Next ; End Main =====================================================

; Main ========================================================= Main: SBIS PIND,6 ; Если кнопка нажата - переход RJMP BT_Push SETB PORTD,5 ; Зажгем LED2 CLRB PORTD,4 ; Погасим LED1 Next: LDS R16,TCNT ; Грузим числа в регистры LDS R17,TCNT+1 CPI R16,0x12 ; Сравниванем побайтно. Первый байт BRCS NoMatch ; Если меньше -- значит не натикало. CPI R17,0x7A ; Второй байт BRCS NoMatch ; Если меньше -- значит не натикало. ; Если совпало то делаем экшн Match: INVB PORTD,7,R16,R17 ; Инвертировали LED3 ; Теперь надо обнулить счетчик, иначе за эту же итерацию главного цикла; мы сюда попадем еще не один раз -- таймер то не успеет натикать 255 значений, ; чтобы число в первых двух байтах счетчика изменилось и условие сработает. ; Конечно, можно обойти это доп флажком, но проще сбросить счетчик:) CLR R16 ; Нам нужен ноль CLI ; Доступ к многобайтной переменной; одновременно из прерывания и фона; нужен атомарный доступ. Запрет прерываний OUTU TCNT0,R16 ; Ноль в счетный регистр таймера STS TCNT,R16 ; Ноль в первый байт счетчика в RAM STS TCNT+1,R16 ; Ноль в второй байт счетчика в RAM STS TCNT+2,R16 ; Ноль в третий байт счетчика в RAM STS TCNT+3,R16 ; Ноль в первый байт счетчика в RAM SEI ; Разрешаем прерывания снова. ; Не совпало - не делаем:) NoMatch: NOP INCM CCNT ; Счетчик циклов по тикает; Пускай, хоть и не используется. JMP Main BT_Push: SETB PORTD,4 ; Зажгем LED1 CLRB PORTD,5 ; Погасим LED2 RJMP Next ; End Main =====================================================

Вот как это выглядит в работе

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

Можно еще немного оптимизировать процесс проверки. Сделать его более быстрым.

Надо только сделать счет не на повышение, а на понижение. Т.е. загружаем в переменную число и начинаем его декрементировать в прерывании. И там же, в обработчике, проверяем его на ноль. Если ноль, то выставляем в памяти флажок. А наша фоновая программа этот флажок ловит и запускает экшн, попутно переустанавливая выдержку.

А что если надо точней? Ну тут вариант только один — заюзать обработку события прям в обработчике прерывания, а значение в TCNT:TCNT0 каждый раз подстраивать так, чтобы прерывание происходило точно в нужное время.


Описание работы таймера/счётчика 1.
Прерывания от TC1

Таймер/счётчик 1 (TC1) представляет из себя 16-битный модуль, содержащий 10 8-битных регистров. Эти регистры фактически являются набором из 5 16-битных регистров. Счёт происходит в регистрах TCNT1H (Timer counter 1 High byte) и TCNT1L (Low byte), вместе составляющих 16-битный регистр TCNT1. ВНИМАНИЕ! Если использовать прямое чтение 8-битных регистров TCNT1H и TCNT1L, то нельзя быть уверенным, что эти регистры прочитались одновременно. Может произойти следующая ситуация: Счётчик содержал значение $01FF, Вы считали TCNT1H (содержащий значение 01 в какую-то переменную). За это время произошёл счётный импульс, и содержимое TCNT1L стало равно $00, а в TCNT1H записалось значение $02. Теперь Вы читаете значение TCNT1L в другую переменную, получаете в этой переменной значение $00 (ведь таймер-счётчик уже произвёл счёт). 16-битное значение этих переменных получилось $0100, но на момент считывания старшего байта содержимое счётчика было $01FF, и младший байт у Вас должен был прочитаться как FF. Для предотвращения такой ситуации служит временный регистр, содержащийся в блоке таймера-счётчика. Этот регистр прозрачный, т.е. действует автоматически. При считывании значения регистра TCNT1L в переменную, содержимое TCNT1H попадает в этот регистр. Затем при чтении старшего байта в переменную, считывается значение временного регистра. Временный регистр абсолютно прозрачен для пользователя, но для его корректной работы необходимо соблюдать такую последовательность действий:
Для 16-битной операции записи, старший байт должен быть записан первым. Младший - вторым.
Для операции 16-битного чтения, младший байт должен быть прочитан первым, а содержимое старшего байта считывается вторым.
Регистр TCCR1A служит для задания режимов работы таймера/счётчика 1:

Биты COM1A1,COM1A0, COM1B1 и COM1B0 - контролируют поведение выводов OC1A и OC1B.
Биты FOC1A, FOC1B, WGM11 и WGM10 служат для задания работы ТС1 как широтно-импульсного модулятора.
Скорость счёта ТС1 можно установить в регистре TCCR1B :

Где биты ICNC1, ICES1, WGM13 и WGM12 также служат для PWM, а CS12, CS11 и CS10 настраивают скорость счёта следующим образом:


В случае, если в эти биты записаны значения 000, ТС0 остановлен. Если записано 001, то тактовая частота процессора подаётся через схему делителя без изменений, и на каждый такт процессора ТС1 увеличивает значение в регистре TCNT1. Соответственно, если в CSxx Записано 101, то увеличение значения в TCNT1 происходит на каждый 1024-ый такт процессора.

16-битные регистры OCR1A и OCR1B служат для задания значения, при достижении которого в режиме счёта, ТС1 генерирует соответствующие прерывания.

Обработка прерываний от TC1

ТС1 при переполнении значения TCNT1 посылает процессору сигнал Timer/Counter 1 Overflow. Также процессору посылается сигнал Timer/Counter 1 A или B Compare Match при совпадении значений в регистрах TCNT1 и OCR1A и OCR1B соответственно. Реакция процессора на эти сигналы (вызов соответствующих прерываний) зависит от значения регистров TIMSK и флага I в Status регистре процессора.
Для задания реакции на события TC1 в регистре TIMSK служат четыре бита:

Бит 2 - TOIE1 - Когда этот бит установлен в 1 и разрешены прерывания, процессор реагирует на сигнал переполнения ТС1 и вызывает прерывание по вектору $010 (OVF1addr).
Бит 3 - OCIE1B - Когда этот бит установлен в 1 и разрешены прерывания, процессор реагирует вызовом прерывания по вектору $00E (OC1Baddr) на событие совпадения счёта с константой в регистре OCR1B. Бит 4 - OCIE1A - Когда этот бит установлен в 1 и разрешены прерывания, процессор реагирует вызовом прерывания по вектору $00C (OC1Aaddr) на событие совпадения счёта с константой в регистре OCR1A. Бит 5 - TICIE1 - Если установлен этот бит и разрешены прерывания, разрешено прерывание захвата ТС1, расположенного по вектору $00A (ICP1addr).