В этом уроке мы поговорим о таймерах.
Данная тема непосредственно связана с темой тактирования микроконтроллера. Поэтому рекомендую перед прочтением данного урока ознакомиться с предыдущим.
Итак, зачем нам таймер?
При построении проектов на микроконтроллерах очень часто возникает необходимость измерение точных временных промежутков. Например, желание мигать светодиодом с определенной частотой, или опрашивать состояние кнопки с необходимыми временными промежутками.
Решить поставленные задачи помогают именно таймеры. Но таймеры микроконтроллеров AVR не знают что такое секунда, минута, час. Однако они прекрасно знают, что такое такт! Работают они именно благодаря наличию тактирования контроллера. То есть, таймер считает количество тактов контроллера, отмеряя тем самым промежутки времени. Допустим, контроллер работает при тактовой частоте 8МГц, то есть когда таймер досчитает до 8 000 000, пройдет одна секунда, досчитав до 16 000 000, пройдет 2 секунды и так далее.
Однако, тут возникает первое препятствие. Регистры то у нас 8 битные, то есть досчитать мы можем максимум до 255, а взяв 16 битный таймер, мы, досчитаем максимум до 65535. То есть за одну секунду мы должны обнулить таймер огромное количество раз! Конечно, можно заняться этим, если больше заняться нечем. Но ведь просто измерять время, используя мощный микроконтроллер совсем не интересно, хочется сделать нечто большее. Тут нам на помощь приходит предделитель. В общем виде это промежуточное звено между таймером и тактовой частотой контроллера. Предделитель облегчает нашу задачу позволяя поделить тактовую частоту на определенное число, перед подачей её на таймер. То есть установив предделитель на 8, за 1 секунду наш таймер досчитает до 1 000 000, вместо 8 000 000 (Разумеется, при частоте тактирования контроллера 8МГц). Уже интереснее, не так ли? А поделить мы можем и не только на 8, но и на 64 и даже на 1024.
Теперь настало время собрать схему, настроить наш таймер, предделитель, и сделать уже хоть что-то полезное!
А делать мы сегодня будем “бегущие огни” из светодиодов. То есть поочередно будем зажигать 3 светодиода, с периодом 0.75 секунды (То есть время работы одного светодиода 0.25 секунды). Соберем следующую схему:
Номиналы резисторов R 1-R 3 рассчитайте самостоятельно.
Далее, рассмотрим регистры отвечающие за работу таймеров. Всего AtMega 8 имеет в своем составе 3 таймера.Два 8 битных(Timer 0,Timer 2) и один 16 битный(Timer 1).Рассматривать будем на примере 16 битного таймера 1.
Пара регистров 8 битных регистров TCNT 1H и TCNT 1L , вместе образуют 16 битный регистр TCNT 1. Данный регистр открыт как для записи, так и для чтения. При работе таймера 1, значение данного регистра при каждом счете изменяется на единицу. То есть в регистре TCNT 1 записано число тактов, которые сосчитал таймер. Так же мы можем записать сюда любое число в диапазоне от 0 до 2 в 16 степени. В таком случае отсчет тактов будет вестись не с 0, а с записанного нами числа.
Регистр TIMSK отвечает за прерывания, генерируемые при работе таймеров микроконтроллера. Прерывание – обработчик специального сигнала, поступающего при изменении чего либо . Любое прерывания микроконтроллера может быть разрешено или запрещено. При возникновении разрешенного прерывания, ход основной программы прерывается, и происходит обработка данного сигнала. При возникновении запрещенного прерывания, ход программы не прерывается, а прерывание игнорируется. За разрешение прерывания переполнения счетного регистра TCNT 1 таймера 1 отвечает бит TOIE 1(Timer 1 Overflow Interrupt Enable ).При записи 1 в данный бит прерывание разрешено, а при записи 0 – запрещено. Данное прерывание генерируется таймером 1 при достижении максимального значения регистра TCNT 1. Подробнее о прерываниях поговорим в следующем уроке.
Регистр TCCR 1B отвечает за конфигурацию таймера 1. В данном случае битами CS 10-CS 12 мы задаем значение предделителя согласно следующей таблицы.
Остальные биты пока нас не интересуют.
Так же существует регистр TCCR 1A , который позволяет настроить другие режимы работы таймера, например ШИМ, но о них в отдельной статье.
А теперь код на C :
#define F_CPU 16000000UL
#include #define F_CPU 16000000UL
#include #include uint8_t
num
=
;
ISR
(TIMER1_OVF_vect
)
PORTD
=
(1
<<
num
)
;
num
++
;
if
(num
>
2
)
num
=
;
TCNT1
=
61630
;
//Начальное значение таймера
int
main
(void
)
DDRD
|=
(1
<<
PD0
)
|
(1
<<
PD1
)
|
(1
<<
PD2
)
;
TCCR1B
|=
(1
<<
CS12
)
|
(1
<<
CS10
)
;
//Предделитель = 1024
TIMSK
|=
(1
<<
TOIE1
)
;
//Разрешить прерывание по переполнению таймера 1
TCNT1
=
61630
;
//Начальное значение таймера
sei
()
;
//Разрешить прерывания
while
(1
)
//Основной цикл программы, он пуст, так как вся работа в прерывании
Код на ASM
: Assembly (x86)
Include "m8def.inc"
rjmp start
.org OVF1addr
rjmp TIM1_OVF
start:
ldi R16,LOW(RamEnd)
out SPL,R16
ldi R16,HIGH(RamEnd)
out SPH,R16
ldi R16,1
ldi R17,0b00000111
out DDRD,R17
ldi R17,0b00000101
out TCCR1B,R17
ldi R17,0b11110000
out TCNT1H,R17
ldi R17,0b10111110
out TCNT1l,R17
ldi R17,0b00000100
out TIMSK,R17
sei
main_loop:
nop
rjmp main_loop
TIM1_OVF:
out PORTD,R16
lsl R16
cpi R16,8
brlo label_1
ldi R16,1
label_1:
ldi R17,0b10111110
out TCNT1L,R17
ldi R17,0b11110000
out TCNT1H,R17
reti Include
"m8def.inc"
Rjmp
start
Org
OVF
1addr
Rjmp
TIM
1_
OVF
start
:
Ldi
R
16,
LOW
(RamEnd
)
Out
SPL
,
R
16 Ldi
R
16,
HIGH
(RamEnd
)
Out
SPH
,
R
16 Ldi
R
16,
1
Ldi
R
17,
0b00000111
Out
DDRD
,
R
17 Ldi
R
17,
0b00000101
Out
TCCR
1B
,
R
17 Ldi
R
17,
0b11110000
Out
TCNT
1H
,
R
17 Ldi
R
17,
0b10111110
С счетчиком итераций главного цикла мы разобрались и выяснили, что для точных временных отсчетов он не годится совершенно — выдержка плавает, да и считать ее сложно. Что делать? Очевидно, что нужен какой то внешний счетчик, который тикал бы независимо от работы процессора, а процессор мог в любой момент посмотреть что в нем такое натикало. Либо чтобы счетчик выдавал события по переполнению или опустошению — флажок поднимал или прерывание генерил. А проц это прочухает и обработает. И такой счетчик есть, даже не один — это периферийные таймеры. В AVR их может быть несколько штук да еще с разной разрядностью. В ATmega16 три, в ATmega128 четыре. А в новых МК серии AVR может даже еще больше, не узнавал. Причем таймер может быть не просто тупым счетчиком, таймер является одним из самых навороченных (в плане альтернативных функций) периферийных девайсов. Что умееют таймеры
Разные таймеры имеют разную функциональность и разную разрядность. Это подробней смотреть в даташите. Погляди внимательно на распиновку ног 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 будет сброшен автоматически на следующем такте. Счетный регистр
Причем тут есть подвох, если в восьмиразрядный регистр надо положить число, то нет проблем OUT TCNT0,Rx и никаких гвоздей, то с двухбайтными придется поизвращаться. А дело все в чем - таймер считает независимо от процессора, поэтому мы можем положить вначале один байт, он начнет считаться, потом второй, и начнется пересчет уже с учетом второго байта. Чувствуете лажу? Вот! Таймер точное устройство, поэтому грузить его счетные регистры надо одновременно! Но как? А инженеры из Atmel решили проблему просто: Что в итоге получается: Записываем старший байт в регистр TEMP (для нас это один хрен TCNTxH), а затем записываем младший байт. В этот момент, в реальный TCNTxH, заносится ранее записанное нами значение. То есть два байта, старший и младший, записываются одновременно! Менять порядок нельзя! Только так Выглядит это так: CLI ; Запрещаем прерывания, в обязательном порядке!
OUT TCNT1H,R16 ; Старшей байт записался вначале в TEMP
OUT TCNT1L,R17 ; А теперь записалось и в старший и младший!
SEI ; Разрешаем прерывания Зачем запрещать прерывания? Да чтобы после записи первого байта, прога случайно не умчалась не прерывание, а там кто нибудь наш таймер не изнасиловал. Тогда в его регистрах будет не то что мы послали тут (или в прерывании), а черти что. Вот и попробуй потом такую багу отловить! А ведь она может вылезти в самый неподходящий момент, да хрен поймаешь, ведь прерывание это почти случайная величина. Так что такие моменты надо просекать сразу же. Читается все также, только в обратном порядке. Сначала младший байт (при этом старший пихается в TEMP), потом старший. Это гарантирует то, что мы считаем именно тот байт который был на данный момент в счетном регистре, а не тот который у нас натикал пока мы выковыривали его побайтно из счетного регистра. Контрольные регистры
Итак, главным регистром является TCCRx Нас пока интересуют только первые три бита этого регистра: У разных таймеров немного по разному, поэтому опишу биты CS02..CS00 только для таймера 0 Прерывания
За прерывания от таймеров отвечают регистры TIMSК, TIFR. А у более крутых AVR, таких как ATMega128, есть еще ETIFR и ETIMSK — своего рода продолжение, так как таймеров там поболее будет. TIMSK это регистр масок. То есть биты, находящиеся в нем, локально разрешают прерывания. Если бит установлен, значит конкретное прерывание разрешено. Если бит в нуле, значит данное прерывание накрывается тазиком. По дефолту все биты в нуле. На данный момент нас тут интересуют только прерывания по переполнению. За них отвечают биты О остальных фичах и прерываниях таймера мы поговорим попозжа, когда будем разбирать ШИМ. Регистр 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 и второй — достающий его оттуда. MACRO PUSHF
PUSH R16
IN R16,SREG
PUSH R16
.ENDM
.MACRO POPF
POP R16
OUT SREG,R16
POP R16
.ENDM Как побочный эффект он еще сохраняет и R16, помним об этом:) 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%. ; 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 каждый раз подстраивать так, чтобы прерывание происходило точно в нужное время. В ходе реализации проекта может потребоваться несколько прерываний, но если каждое из них будет иметь максимальный приоритет, то фактически его не будет ни у одной из функций. По этой же причине не рекомендуется использовать более десятка прерываний. Обработчики должны применяться только к тем процессам, которые имеют максимальную чувствительность ко временным интервалам. Не стоит забывать, что пока программа находится в обработчике прерывания – все другие прерывания отключены. Большое количество прерываний ведет к ухудшению их ответа. В момент, когда действует одно прерывание, а остальные отключаются, возникает два важных нюанса, которые должен учитывать схемотехник. Во-первых, время прерывание должно быть максимально коротким. Это позволит не пропустить все остальные запланированные прерывания. Во-вторых, при обработке прерывания программный код не должен требовать активности от других прерываний. Если этого не предотвратить, то программа просто зависнет. Не стоит использовать длительную обработку в loop()
, лучше разработать код для обработчика прерывания с установкой переменной volatile. Она подскажет программе, что дальнейшая обработка не нужна. Если вызов функции Update()
все же необходим, то предварительно необходимо будет проверить переменную состояния. Это позволит выяснить, необходима ли последующая обработка. Перед тем, как заняться конфигурацией таймера, следует произвести проверку кода. Таймеры Anduino стоит отнести к ограниченным ресурсам, ведь их всего три, а применяются они для выполнения самых разных функций. Если запутаться с использованием таймеров, то ряд операций может просто перестать работать. Какими функциями оперирует тот или иной таймер? Для микроконтроллера Arduino Uno у каждого из трех таймеров свои операции. Так Timer0
отвечает за ШИМ на пятом и шестом пине, функции millis()
, micros()
, delay()
. Другой таймер – Timer1,
используется с ШИМ на девятом и десятом пине, с библиотеками WaveHC и Servo.
Timer2
работает с ШИМ на 11 и 13 пинах, а также с Tone
. Схемотехник должен позаботиться о безопасном использовании обрабатываемых совместно данных. Ведь прерывание останавливает на миллисекунду все операции процессора, а обмен данных между loop()
и обработчиками прерываний должен быть постоянным. Может возникнуть ситуация, когда компилятор ради достижения своей максимальной производительности начнет оптимизацию кода. Результатом этого процесса будет сохранение в регистре копии основных переменных кода, что позволит обеспечить максимальную скорость доступа к ним. Недостатком этого процесса может стать подмена реальных значений сохраненными копиями, что может привести к потере функциональности. Чтобы этого не произошло нужно использовать переменную voltatile
,
которая поможет предотвратить ненужные оптимизации. При использовании больших массивов, которым требуются циклы для обновлений, нужно отключить прерывания на момент этих обновлений. В этом уроке мы поговорим о прерываниях. Как понятно из названия, прерывание это событие, которое приостанавливает выполнение текущих задач и передает управление обработчику прерывания. Обработчик прерывания — это функция. Например: если вы написали скетч по управлению мотором или просто плавно зажигаете и гасите светодиод в цикле, то нажатие на кнопку может не обрабатываться, так как Arduino в данный момент занята другой частью кода. Если же использовать прерывание, то такой проблемы не возникнет, так как прерывания имеют более высокий приоритет. В ардуино есть прерывания по таймеру и аппаратное прерывание. Далее я подробнее расскажу что это, как это использовать и зачем оно вам нужно. В Arduino имеется 4 вида аппаратных прерываний. Отличаются они сигналом на контакте прерывания. Если прерывание ожидает нажатия кнопки, то это может стать проблемой из-за дребезга контактов. В мы уже говорили о дребезге контактов. Тогда мы использовали функцию но в прерываниях данная функция не доступна. Поэтому нам придется подавить дребезг контактов немного усложнив схему подключения кнопки к пину прерывания. Для этого понадобится резистор на 10 КОм, конденсатор на 10 микрофарад, В Arduino Uno есть два пина, поддерживающие прерывания. Это цифровые пины 2 (int 0) и 3 (int 1). Один из них мы и будем использовать в нашей схеме. Предлагаю сделать устройство, которое будет поочередно изменять яркость светодиодов в зависимости от показаний инфракрасного датчика расстояния, а по нажатию на кнопку прерывания будем переходить от одного светодиода к другому. Наше устройство будет выглядеть примерно вот так: Схема кажется сложной и запутанной, но это не так. Мы подключаем кнопку прерывания к пину Arduino D2, используя аппаратное подавление дребезга контактов. К аналоговому пину A0 мы подключаем инфракрасный дальномер. И к пинам D9, D10 и D11 мы подключаем светодиоды через резисторы на 150 Ом. Мы выбрали именно эти контакты для светодиодов, потому что они могут выдавать ШИМ сигнал.Теперь рассмотрим скетч: // Назначение прерывания
int buttonInt = 0;
// Переменные с пинами светодиодов
int yellowLed = 11;
int redLed = 10;
int greenLed = 9;
int nullLed = 6;
volatile int selectedLed = greenLed;
// Инфракрасный дальномер
int distPin = 0;
void setup () {
// Устанавливаем режимы пинов
pinMode(redLed, OUTPUT);
pinMode(greenLed, OUTPUT);
pinMode(yellowLed, OUTPUT);
pinMode(nullLed, OUTPUT);
// Устанавливаем прерывание
attachInterrupt(buttonInt, swap, RISING);
}
// Обработчик прерывания
void swap() {
if(selectedLed == greenLed)
selectedLed = redLed;
else if(selectedLed == redLed)
selectedLed = yellowLed;
else if(selectedLed == yellowLed)
selectedLed = nullLed;
else
selectedLed = greenLed;
}
void loop () {
// Получаем данные с дальномера
int dist = analogRead(distPin);
int brightness = map(dist, 0, 1023, 0, 255);
// Управляем яркостью
analogWrite(selectedLed, brightness);
} // Назначение прерывания
int
buttonInt
=
0
;
// Переменные с пинами светодиодов
int
yellowLed
=
11
;
int
redLed
=
10
;
int
greenLed
=
9
;
int
nullLed
=
6
;
volatile
int
selectedLed
=
greenLed
;
// Инфракрасный дальномер
int
distPin
=
0
;
void
setup
()
{
// Устанавливаем режимы пинов
pinMode
(redLed
,
OUTPUT
)
;
pinMode
(greenLed
,
OUTPUT
)
;
pinMode
(yellowLed
,
OUTPUT
)
;
pinMode
(nullLed
,
OUTPUT
)
;
// Устанавливаем прерывание
attachInterrupt
(buttonInt
,
swap
,
RISING
)
;
// Обработчик прерывания
void
swap
()
{
if
(selectedLed
==
greenLed
)
selectedLed
=
redLed
;
else
if
(selectedLed
==
redLed
)
selectedLed
=
yellowLed
;
else
if
(selectedLed
==
yellowLed
)
С Таймером 1 и Таймером 2 связаны три
вектора прерывания: Прерывание переполнения таймера -
Timer Overflow Interrupt (INT00,2000H); Прерывание переполнения Таймера 2 -
Timer 2 Overflow Interrupt (INT12,2038H); Прерывание фиксатора Таймера 2 - Timer
2 Capture Interrupt (INT11,2036H). Регистр IOS1 содержит флажки, которые
указывают какое событие вызвало
прерывание. Обращение к битам регистра
IOS1 по командам JBC или JBS обнуляет биты
0-5. По этой причине, мы рекомендуем чтобы
Вы копировали содержимое регистра IOS1
в промежуточный регистр и затем выполняли
команды проверки разрядов типа JBC или
JBS на промежуточном регистре. И Таймер 1 и Таймер 2 могут вызвать
прерывание переполнения Таймера (INT00).
Установите INT_MASK.0 ,чтобы разрешить это
прерывание. Установите или IOC1.2 (Таймер
1) или IOC1.3 (Таймер 2) чтобы выбрать
источник прерывания. Когда происходит
переполнение, устанавливается флажок
состояния в регистре IOS1. Переполнение
Таймера 1 устанавливает IOS1.5, а переполнение
Таймера 2 устанавливает IOS1.4. HWindow 0 (Write), HWindow 15 (Read) Hwindow 0 (Write), HWindow 15 (Read) Таймер 2 может генерировать прерывание
переполнения Таймера 2 (INT12, адрес вектора
- 2038H) вместо стандартного прерывания
переполнения Таймера. Это прерывание
разрешается установкой INT_MASK1.4. Переполнение
Таймера 2 устанавливает IOS1.4. Таймер 2
может генерировать прерывание переполнения
Таймера 2 или на границе 7FFFH/8000H или на
границе 0FFFFH/0000H. Переполнение может
происходить в любом направлении. IOC2.5
выбирает границу переполнения. Когда
IOC2.5 установлен, Таймер 2 прерывается на
границе 7FFFH/8000H. Иначе, он прерывается
на границе 0FFFFH/0000H. Положительный переход на контакте
T2CAPTURE (P2.7) заставляет значение Таймера
2 загружаться в регистр T2CAPTURE
. Это
событие генерирует прерывание фиксатора
Таймера 2 (INT11), если установлен INT_MASK1.3 и
T2CAPTURE утверждается в течение более двух
времен состояний. Hwindow 15(Read /Write) При использовании таймеров как датчиков
времени для HSI или HSO, следующие руководящие
принципы помогут Вам избежать потенциальных
проблем. Будьте осторожны при записи в регистры
таймера TIMER1 и TIMER2:
Изменение значения TIMER1 после инициализизации
HSI модуля может разрушить относительные
ссылки между HSI событиями. Также,
изменение значения соответствующего
таймера (TIMER1 или TIMER2), после инициализизации
HSO модуля, может заставить HSO пропускать
запрограммированные события или
выполнять их в неправильном порядке. Конфигурируйте Таймер 2 для
функционирования в нормальном режиме
(не в быстром режиме приращения):
Так как для полного сканирования CAM,
HSO требует восьми времен состояния,
Таймер 2, когда он используется как
датчик времени для HSO, должен
функционировать в нормальном режиме
приращения (не в быстром режиме приращения) . Очистите бит FAST_T2_ENA(IOC2.0) для выбора
нормального режима работы таймера. Конфигурируйте Таймер 2 для счета
только в одном направлении.
Таймер 2 , когда он используется как
датчик времени для HSO, должен считать
только в одном направлении, поскольку,
если Таймер 2 колеблется вокруг отметки
времени выполнения команды, блокировка
входов может происходить несколько
раз. Очистите бит T2UD_ENA(IOC2.1), чтобы сконфигурировать
Таймер 2 как суммирующий счетчик. Используйте предостережение при
сбросе Таймера 2.
Не сбрасывайте Tаймер 2 до того, как
его значение достигнет самого наибольшего
времени, запрограммированного в CAM. CAM
задерживает ожидание события до
соответствующего времени. Если
запрограммированное значение Таймера
2 никогда не достигается, событие будет
оставаться отложенным, пока устройство
не сбросится или CAM не очистится. Когда Таймер 2 сконфигурирован для
сброса внешним контактом, события
программы должны происходить когда
Таймер 2 равен единице, а не нулю:
Когда Таймер 2 сконфигурирован, чтобы
сбрасываться внешним контактом сброса
(IOC0.3 =1), программные события не должны
происходить, когда Таймер 2 равен нулю.
Если HSI.0 или T2RST (P2.3) сбрасывают Таймер
2, событие может не произойти. Внешние
контакты сбрасывают Таймер 2 асинхронно,
и Таймер 2 может увеличиться до 1 до того,
как HSO может сравнить и распознать CAM
запись. Программируйте события так,
чтобы они происходили, когда Таймер 2
равен 1, это гарантирует,что HSO имеет
достаточное время, чтобы распознать
команду, записанную в CAM . ФРАГМЕНТ ПРОГРАММЫ ИЗУЧЕНИЯ ПРОГРАММИРОВАНИЯ
ТАЙМЕРОВ ;**Исследование пpoгpаммных задеpжек с
использованием Timer1* ldb wsr,#15 ; Пеpеключиться в HWindow 15 ld timer1,#0c000h ; Загpузить значение счетчика ldb wsr,#0 ; Пеpеключиться в HWindow 0 jbc ios1, 5, $ ; Ожидание пеpеполнения Timer ;***********************************************************
Источник тиков таймера
Таймер/Счетчик (далее буду звать его Т/С) считает либо тактовые импульсы от встроенного тактового генератора, либо со счетного входа.
Если таймер считает импульсы от тактового генератора, или от своего внутреннего, то их еще можно пропустить через предделитель.
Весь результат мучений, описанных выше, накапливается в счетном регистре TCNTх, где вместо х номер таймера. он может быть как восьмиразрядным, так и шестнадцати разрядным, в таком случае он состоит из двух регистров TCNTxH и TCNTxL — старший и младший байты соответственно.
Запись в старший регистр (TCNTxH) ведется вначале в регистр TEMP. Этот регистр чисто служебный, и нам никак недоступен.
Всех функций таймеров я расписывать не буду, а то получится неподьемный трактат, лучше расскажу о основной — счетной, а всякие ШИМ и прочие генераторы будут в другой статье. Так что наберитесь терпения, ну или грызите даташит, тоже полезно.
Для Т/С0 и Т/С2 это TCCR0 и TCCR2 соответственно, а для Т/С1 это TCCR1B
CSx2.. CSx0, вместо х подставляется номер таймера.
Они отвечают за установку предделителя и источник тактового сигнала.
У каждого аппаратного события есть прерывание, вот и таймер не исключение. Как только происходит переполнение или еще какое любопытное событие, так сразу же вылазит прерывание.
Ну перекроим программу на работу с таймером. Введем программный таймер. Шарманка так и останется, пускай тикает. А мы добавим вторую переменную, тоже на четыре байта: 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
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
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 =====================================================
В этом уроке используется:
Аппаратные прерывания
и инвертирующий триггер шмитта. Подключается все по следующей схеме:
1.3.1. Прерывание переполнения таймера
Input/Output Control Register 1
Input/Output Status Register 1
1.3.2. Прерывание переполнения Таймера 2
1.3.3. Прерывание фиксатора Таймера 2
Timer 2 Capture Register
1.4. Предосторожности при работе с Таймерами