AVR. Учебный курс. Таймеры

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

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

И такой счетчик есть, даже не один — это периферийные таймеры. В 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, заносится ранее записанное нами значение. То есть два байта, старший и младший, записываются одновременно! Менять порядок нельзя! Только так

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

1
2
3
4
	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!

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

1
2
3
4
; RAM ========================================================
		.DSEG
CCNT:	.byte	4
TCNT:	.byte	4

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

Делаем RJMP на обработчик с вектора.

1
2
3
4
5
6
         .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

Как побочный эффект он еще сохраняет и 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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
; 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<<CS00		; Запускаем таймер. Предделитель=1
					; Т.е. тикаем с тактовой частотой.
 
	SEI				; Разрешаем глобальные прерывания
; End Internal Hardware Init ===================================

Осталось переписать наш блок сравнения и пересчитать число. Теперь все просто, один тик один такт. Без всяких заморочек с разной длиной кода. Для одной секунды на 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 =====================================================

Скачать проект с этим примером

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

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

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

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

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

250 thoughts on “AVR. Учебный курс. Таймеры”

  1. LDI R16,5 ; 0000 0101 предделитель на 1024
    OUT TCCR1B,R16 ; Запускаем таймер!

    почему нельзя OUT TCCR1B,5 ?
    заче лишняя операция? со времен з80 помню еше
    мой вариант — это реализация прямого доступа к памяти aka dma?

  2. А я так и не понял почему у меня МК не просыпается из power-save спящего режима, когда таймер работает асинхронно… Из idle отлично просыпается по прерыванию.

  3. Привет. Вопрос по таймерам. Микроконтроллер ATtiny25. По идее следующий код должен запускать счетчик (Timer/Counter1):

    ;разрешаем прерывание по переполнению
    ldi temp, 0b00000100
    out TIMSK, temp
    ;запускаем счетчик без делителя
    ldi temp, 0b00000001
    out TCCR1, temp

    Но ничего не происходит. Счетчик не считает, прерывание не происходит. Смотрю в симуляторе AVR Studio 4.
    Где моя ошибка? Спасибо.

  4. Посмотри описание битов CS02..CS00: В даташите написано:
    110 External clock source on T0 pin. Clock on falling edge.
    111 External clock source on T0 pin. Clock on rising edge.
    т.е. 110 — это спад, с 1 до 0, а 111 — это фронт, с 0 до 1
    А у тебя наоборот

  5. что-то я в даташите не нашёл каким образом нужно выставлять CSn2:0 что бы получить деление частоты на 64 в attiny2313. Там написано, что эти биты шевелить надо, а каким именно образом — не написано. :(

    1. Там табличка должна быть, смотри внимательней шит. Если мне не изменяет память, на делитель 64 значение 3 (11 т.е.) Для 1024 это 5.

      1. В даташите на 2313 таблички нет, но по даташиту на другой контроллер увидел, что 000 это остановка, 001 — систем клок, а потом все СУЩЕСТВУЮЩИЕ в контроллере предделители идут подряд. В общем, так всё оно и было.

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

    1. Можно можно! Присобачить схемку, которая раз в минуту будет дергать какой либо из INT =)

      или повесить на TOSC1 TOSC2 низкочастотный кварц, который будет дрыгаться со скоростью 17.0666666666666 Гц

  7. Насчёт первого комментария и OUT SFIOR,PSR10 — AVR Studio говорит «invalid register».
    Может быть, правильнее так:

    LDI r16,1<<PSR10
    OUT SFIOR,r16

    ?

  8. Сделал я свои часы (давно уже), но они на секунду в сутки спешат. Для того, что бы не спешили нужно раз в секнду останавливать их на 47 системных тактов, а т.к. у меня предделитель таймера на 64, то это получается меньше одного тика таймера.
    сначала хотел тключить таймер от предделителя, а потом понял, что отключённый предделитель будет продолжать тикать…
    Как мне это сделать красиво?

    PS Есть смутная идея — сразу после прерывания счётчик предделителя должен быть в нуле, поэтому можно отсчитать 47 тактов и сбросить предделитель… Но уж больно это на грязный хак похоже…

    1. А чего ты изобретаешь велосипед? Затактуй все от кварца на 32768Гц (часовой кварц) и будет тебе счастье. Это же часы, быстродействие тут не важно.

      1. Затактовано там всё как надо, а уход на секунду в сутки из-за погрешности самого кварца. Хочу программно подкорректировать.

  9. Юзаю дефайн для меги 8
    Вот никак не пойму, что за инструкция такая: ‘SETB PORTB,6’
    студия упорно твердит что не знает ее!
    Сделал так:
    PUSH R16
    LDI R16,1<<6
    OUT PINB,R16
    POP R16

    Вроде 6й пин выставляет, но не восстанавливает регистр R16 (че за лажа?)

    И есть странность, как только добавляю
    .ORG OVF1addr
    RJMP Timer1OV
    и сам обработчик, состоящий только из RETI происходит срыв стека, МК постоянно генерит прерывание таймера, причем при отладке — прогу ни разу не начинает выполнять, а сразу рвется запускать обработчик прерывания таймера, там RETI взводит флаг I и понеслось — лавина запусков этого обработчика — ну и естественно срыв крышы у стека

        1. Пофиксил. Раньше у меня все исходники были в ХТМЛ оформлены, и всякие скобочки кодами приходилось прописывать. Щас плагин прикрутил, чтобы копипастить можно, но старые записи приходится перделывать при обработке их новыми тэгами.

        2. Ну тогда еще маленькая опечатка. Во втором листинге написано
          SBI PORTB,6 ; Хотя речь идет о порте D. Впрочем, и так всем всё понятно…

    1. А ты стек инициализировал?

      Переход с нулевого адреса на тело программы (перепрыг через таблицу векторов) сделал?

      1. да, я об этом тоже подумал, глядя на дизассеблер в студии… теперь все ясно чего это он вдруг сразу в обработчик заперся, а я грешным делом с чего-то взял, что он сам пропускает таблицу векторов прерываний… Спасибо! Бум пробовать.

        P.S. А стек не инициализировал. Я только начинаю осваиваться, пока не знаю как — поищу сначала, может у тебя на сайте уже описано.

        1. Открой любой из моих примеров программ. (первую, вторую или третью) Там в самом начале, в исходниках.

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

            1. Это практически невозможный вариант. Когда размер прошивки такой, что не влазит в ПЗУ, то это наверняка ОЧЕНЬ навороченная программа и прерывания там юзаются все какие только можно.

              1. Похоже на то, хотя в x86 asm’e как только я не извращался… :-) Ну, не буду флудить — читаю дальше твои полезнючие статьи. Надеюсь смогу разобраться во всем и сделать свой задуманный мегапроект.

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

    Не — команда OUT только для РВВ с номерами от 0 до 31, а TCNT0 имеет номер 0x32

  11. А нельзя как-нибудь прочитать текущее значение предделителя? Т.е. задача примерно такая: AtMega8, типа частотомер, 16-битный таймер задает интервал, 8-битный — считает клоку. 1-го бита — на весь диапазон мало, вот если поставить предделитель 256 и по прерыванию получить все 16 бит(старший байт — в счетчике, младший — в предделителе).
    Другие варианты есть, но хочется как попроще.

  12. Подскажите, пожалуйста, почему светодиоды, подключенные к РВ0 и РВ2, не мигают. Мигает тока РВ1, он же ОС1 (почему он мигает догадался). МК работет без внешнего кварца (надеюсь на внутренний). Вот листинг для меги8л:

    .include «c:avrdefm8def.inc»

    .def Temp=R16
    .def Temp1=R17
    .def Temp2=R18
    .def Temp3=R19
    .def Temp4=R20
    .def Temp5=R26
    .def Temp6=R21
    .def Temp7=R29
    .def Tempo=R22
    .equ k_div=0

    .cseg
    .org 0

    jmp RESET ; Reset Handler
    jmp EXT_INT0 ; IRQ0 Handler
    jmp EXT_INT1 ; IRQ1 Handler
    jmp TIM2_COMP ; Timer2 Compare Handler
    jmp TIM2_OVF ; Timer2 Overflow Handler
    jmp TIM1_CAPT ; Timer1 Capture Handler
    jmp TIM1_COMPA ; Timer1 CompareA Handler
    jmp TIM1_COMPB ; Timer1 CompareB Handler
    jmp TIM1_OVF ; Timer1 Overflow Handler
    jmp TIM0_OVF ; Timer0 Overflow Handler
    jmp SPI_STC ; SPI Transfer Complete Handler
    jmp USART_RXC ; USART RX Complete Handler
    jmp USART_UDRE ; UDR Empty Handler
    jmp USART_TXC ; USART TX Complete Handler
    jmp ADC_ ; ADC Conversion Complete Handler
    jmp EE_RDY ; EEPROM Ready Handler
    jmp ANA_COMP ; Analog Comparator Handler
    jmp TWSI ; Two-wire Serial Interface Handler
    jmp SPM_RDY ; Store Program Memory Ready Handler

    RESET:
    ldi Temp,high(RAMEND) ; Main program start
    out SPH,Temp ; Set stack pointer to top of RAM
    ldi Temp,low(RAMEND)
    out SPL,Temp
    ldi Temp7,0b00000001
    ldi Temp,0b11111111
    out DDRB,Temp

    ldi Temp5,high(15625)
    out OCR1AH,temp5
    ldi Temp5,low(15625)
    out OCR1AL,Temp5
    ldi Temp5,(1<<COM1A0)
    out TCCR1A,Temp5
    ldi temp5,0b00001011
    out TCCR1B,temp5
    ldi Temp5,(1<<OCIE1A ) +(1<<TOIE1)
    out TIMSK,Temp5

    ldi Temp5,$00
    out TCNT1H,Temp5
    ldi Temp5,$00
    out TCNT1L,Temp5
    sei ; Enable interrupts

    Cycle:

    rjmp cycle;

    TIM1_OVF:
    TIM1_COMPA:
    cli
    ldi Temp5,$00
    out TCNT1H,Temp5
    ldi Temp5,$00
    out TCNT1L,Temp5
    sei

    sbrc Temp7,0
    rjmp Init

    ; lsl Temp7
    ldi Temp7,0b00000001
    rjmp Output

    Init:
    ldi Temp7,0b00000100

    Output:
    out PortB,Temp7

    reti

    ;RESET:
    EXT_INT0: ; IRQ0 Handler
    EXT_INT1: ; IRQ1 Handler
    TIM2_COMP: ; Timer2 Compare Handler
    TIM2_OVF: ;Timer2 Overflow Handler
    TIM1_CAPT: ; Timer1 Capture Handler
    ;TIM1_COMPA: ; Timer1 CompareA Handler
    TIM1_COMPB: ; Timer1 CompareB Handler
    ;TIM1_OVF: ; Timer1 Overflow Handler
    TIM0_OVF: ; Timer0 Overflow Handler
    SPI_STC: ; SPI Transfer Complete Handler
    USART_RXC: ; USART RX Complete Handler
    USART_UDRE: ; UDR Empty Handler
    USART_TXC: ; USART TX Complete Handler
    ADC_: ; ADC Conversion Complete Handler
    EE_RDY: ; EEPROM Ready Handler
    ANA_COMP: ; Analog Comparator Handler
    TWSI: ; Two-wire Serial Interface Handler
    SPM_RDY: ; Store Program Memory Ready Handler

    reti;

    1. В общем ответ сам нашёл. Вместо команд jmp МЕТКА нужно писать rjmp МЕТКА. Т.к. первая команда работает с абсолютным адресом, а вторая с относительным. Соответсвенно адреса перехода в первом случае 2 байта занимают, во втором 1 байт, что и привело к ошибке.

  13. В моей проге работают все три таймера
    вот инициализация:
    OUTI TCNT0,0
    OUTI TCCR0,4

    OUTI TIMSK,1<<TOIE0
    OUTI TCCR1A,(0<<COM1A1)|(1<<COM1B1)|(1<<WGM10)
    OUTI TCCR1B,(1<<WGM12)|(1<<CS10)

    OUTI TCNT2,189
    OUTI TCCR2,4
    OUTI TIMSK,1<<TOIE2

    почемуто не входит в прерывание от нулевого таймера, а от второго работает. В чем может быть причина? Причем пока был только нулевой таймер он работал, а как добавил второй то нулевой работать перестал =(
    Вектор прерывания стоит
    .ORG OVF0addr ; Timer/Counter0 Overflow
    RJMP TIM0OVF

    1. Сам разобрался =)
      В регистр TIMSK биты надо было записывать одновременно потомучто вторая запись стирала первую =)

  14. есть вопросик… если я выбераю внутренний такт частоту по таймеру стороживой собачки —
    это 128к = 131072 или = 128000?
    что меня ждет(чтото включится чтото выключится..) если я выберу пониженую тактовку от собачки?

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

      1. спасибо.. пониженая частота и нужна.. вычисления просты и важна энергосбережение

        а ниже никак не сделать.. ниже только 32к от кварца?

          1. прескалер CLK — делитель тактовой частоты? круто.. тротлинг организовать или режим сбережения энергии (как в ноутбуке) (автоматическому понижению частоты при переключении работы от сети-блокаПитания на батарейку)

            128 kHz Internal Oscillator
            Clock Prescale Register CLKPR:
            Bit 7 – CLKPCE: Clock Prescaler Change Enable
            Bits 3..0 – CLKPS3..0: Clock Prescaler Select Bits 3 — 0 — оно?

            1. Он самый. Там правда хитрая процедура записи. Надо вначале флажок поставить, а потом в течении четырех тактов записать, но в даташите все расписано.

  15. «А для того чтобы получить секунду нам надо тикнуть 3906 раз. Ок, значит в счетный регистр надо загрузить число 65535-3906»

    тут ошибка — надо отнимать от 65536 т.к. прерывание не при достижении 65535, а при переполнении..

  16. Ответте плиз на такой вопрос. Использую прерывание от таймера, которое вызывается каждую 0,1мсек (таймер в режиме СТС). А теперь вопрос. Что будет если мой обработчик прерывания будет занимать болше времени, чем 0,1мсек. Будет прервано выполнение ранее вызванного прерывания и оно же будет запцщено с начала, или как?

  17. Если имеется у кого-нибудь, хотелось бы посмотреть реализацию часов реального времени. Увидеть код.

  18. Господа, посмотрите прогу, реализующую часы. МК — ATiny2313. Всё собирается и даже вроде работает. Скажите есть ли какие-нибудь ошибки или подводные камни в данной реализации.

    .include «tn2313def.inc»

    .MACRO exit
    reti
    .ENDMACRO

    .CSEG
    .org 0
    start: rjmp main
    reti
    reti
    reti
    reti
    reti
    reti
    reti
    reti
    reti
    reti
    reti
    reti
    reti
    reti
    reti
    reti
    main:
    CLI
    LDI R16, High(65535-15535)
    OUT TCNT1H, R16
    LDI R16, Low(65535-15535)
    OUT TCNT1L, R16
    SEI
    ldi R16,128
    out TIMSK,R16
    LDI R16,2
    OUT TCCR1B,R16
    time:
    CPI R17,20
    BRNE tick
    CBR R17,0
    CPI R18,60
    BRNE secund
    CBR R18,0
    CPI R19,60
    BRNE minute
    CBR R19,0
    CPI R20,24
    BRNE hour
    CBR R20,0
    tick: inc R17
    exit
    secund: inc r18
    exit
    minute: inc r19
    exit
    hour: inc r20
    exit

  19. Здравствуйте!

    Подскажите пожалуйста начинающему «программисту».
    Возникла такая проблема:
    При работе программы — последовательно зажигается следующий светодиод, а предыдущий тухнет, задержка 0,5с. Светодиоды подключенны к порту D. При симуляции в AVR Studio 4 всё нормально, но при заргузке в микроконтроллер цикл останавливается на состоянии — все светодиоды потухли. Если использовать порт B — всё нормально, цикл повторяется.
    В чём причина и как можно её обойти, чтобы использовать порт D?

    ; «Бегущий огонёк»
    ; Для AVR: ATTINY2313-20PU
    ; Тактовая частота: 20 МГц

    .device ATtiny2313
    .include «tn2313def.inc»

    ;Переменные
    .def temp=r16 ;Рабочая переменная
    .def Delay1=r17
    .def Delay2=r18
    .def Delay3=r19

    ;Определение выходов
    ldi temp,0b1111111 ;Определяем выходы порта D
    out DDRD,temp
    ldi temp,0b0000001 ;Начальное состояние для выходов порта D
    out PortD,temp

    START:
    ;Задержка — 0,5с
    ldi Delay1,0x80 ;Присвоение значений переменным
    ldi Delay2,0x84
    ldi Delay3,0x1E
    Loop:
    subi Delay1,1 ;Вычитание 1 из Delay1
    sbci Delay2,0 ;Вычитание 1 из Delay2, если флаг C=1
    sbci Delay3,0 ;Вычитание 1 из Delay3, если флаг C=1
    brcc Loop ;Возвращение в начало, если флаг С сброшен

    ;Заданное время прошло, зажигаем следующий светодиод
    in temp,PortD ;Считывание текущего состояния индикатора
    lsl temp ;Сдвиг влево
    brcc PC+2 ;Если переноса не было пропускаем команду
    ldi temp,0b0000001 ;Сброс в начальное состояние
    out PortD,temp ; Вывод в порт D
    rjmp START

    1. Вроде ничего крамольного не вижу. Должно работать и неважно на каком порту.

      Все диоды погашены значит бит ушел в ПД7 которого нет… Так. А PD7 у нас нету, а что из него читается? По даташиту ты из него можешь ТОЛЬКО читать. Т.к. физически его нет. А начальное значение у него 0. Соответственно твоя единичка уходит в PD7 и там пропадает навсегда. И в С-Flag уже не попадет никогда. Вот твой цикл и клинит.

      1. Спасибо большое за подробный ответ.
        Всё исправил, на скорую руку(может конечно коряво), но работает.
        ;********************************************************
        ;Заданное время прошло, зажигаем следующий светодиод
        in temp,PortD ;Считывание текущего состояния индикатора
        lsl temp ;Сдвиг влево
        cpi temp,0x80 ;temp=128
        brne PC+2 ;Если нет, пропускаем команду
        ldi temp,0b0000001 ;Сброс в начальное состояние
        out PortD,temp ; Вывод в порт D
        rjmp START
        *********************************************************

  20. у Т/С2 есть входы TOSC1 и TOSC2, на которые можно повесить кварцевый резонатор.

    А подскажи пожалуста, оба эти входа настраиваются на ввод?

    1. Вроде бы ничего там не надо делать. ПРи асинхронном режиме как только он включается эти выводы сами настраиваются как надо. Но я не уверен, даташит надо покурить.

  21. LDI R16,1<<TOIE1
    OUT TIMSK, R16

    Господа, понятно, что эти команды должны сделать, но не понятно как именно это происходит. ПОдскажите, плз, это не ясно: 1<<TOIE1

    1. Это означает поставить 1 на место TOIE1
      1<<4 означает поставить 1 на 4е место в бите. Т.е. будет 00010000

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

  22. Доброго времени суток!
    Подскажите что у меня не так.
    Работаю в AVR Studio, использую ATtiny2313, работают оба таймера.
    Вот инициализация:

    outi OCR1AH,High(15280) ; загружаем старший байт в ТС1
    outi OCR1AL,Low(15280) ; загружаем младший байт в ТС1
    outi OCR0A,124 ; загружаем байт в ТС0

    ; сбрасываем таймер 1 при совпадении, делитель на 1024

    outi TCCR1B,(1<<WGM12)|(5<<CS10)

    outi TCCR0A,(2<<WGM00) ; сброс таймера 0 при совпадении
    outi TCCR0B,(5<<CS00) ; делитель — 1024

    Вот кусок файла макросов:

    ; ==============================================
    .MACRO seim ; разрешение прерывания от таймера 0

    clri TCNT0 ; сброс счетчика таймера 0
    clri TIFR ; сброс флагов прерывания от таймера 0
    ; при совпадении (канал А)
    outi TIMSK,1<<OCIE0A ; разрешение прерывания при совпадении
    ; счетчика 0 (канал А)
    sei ; разрешили прерывания глобально

    .ENDMACRO
    ; ==============================================
    .MACRO clim ; запрет прерывания от таймера 0

    outi TIMSK,0<<OCIE0A ; запрещение прерывания при совпадении
    ; счетчика 0 (канал А)
    .ENDMACRO
    ; ==============================================
    .MACRO seit ; разрешение прерывания от таймера 1

    clri TCNT1H ; сброс счетчика таймера 1
    clri TCNT1L ;
    clri TIFR ; сброс флага прерывания от таймера 1
    outi TIMSK,1<<OCIE1A ; разрешение прерывания при совпадении
    ; счетчика 1 (канал А)
    sei ; разрешили прерывания глобально

    .ENDMACRO
    ; ==============================================
    .MACRO clit ; запрещение прерывания от таймера 1

    outi TIMSK,0<<OCIE1A ; запрещение прерывания при совпадении
    ; счетчика 1 (канал А)

    .ENDMACRO
    ; ==============================================

    Естественно выше определены макросы для
    outi — вывод данных в порт;
    clri — очистка порта…

    Проблема следующая:
    ТС1 работает нормально, если разрешить прерывание — сначала обнуляет счетчик, сбрасывает TIFR, потом происходит прерывание, обрабатывается и т.д… При запрете прерываний OCIE1A падает в 0. Тут как бы проблем нет.
    ТС0 выделывается… Если разрешить прерывания, а флаг OCF0A установлен (прерывание произошло раннее), то счетчик сбрасывается, и программа сразу уходит на обработку прерываний от ТС0. Получается, что в макросе не работает строка

    clri TIFR ; сброс флагов прерывания от таймера 0
    ; при совпадении (канал А)
    Пробовал напрямую через

    clr temp
    out TIFR,temp

    Пробовал маску (чистим первые биты TIFR)

    andi temp,0b11111000
    out TIFR,temp

    Не идет, проблема остается…

  23. Вопрос снят. Извините за беспокойство, английский не знаю, с переводом туго…
    Правильно работает если в обоих таймерах писать

    seri TIFR

    то есть TIFR сбрасывается записью в него 1!!!

      1. получается что при проверке флага 1 — установлен, 0 — не установлен.
        а программно сбросить флаг можно если в него записать 1, тогда он установится в 0.
        я правильно понимаю?

  24. Созрел вопрос:
    Нарисовал программку мигания диода. Для проверки текущего статуса использовал CPI. Если в регистре 0 — включаем и пишем 1. Если же 1 — выключаем и пишем в регистр 0. Такое впечатление, что не работает ни на эмуляторе, ни в контроллере. А вот CPSE (если константу пихать в другой регистр и сравнивать с ним) заработало.
    Почему же не работало CPI? Заранее спасибо.

      1. Спасибо, прочитал «Ветвления» и понял. Я рассчитывал, что оно будет работать так же, как SBIC/SBIS. Т.е. пропускать следующую команду (у меня это был RJMP).
        В реальности же дальше должна идти именно команда условного перехода (для такой логики — BRNE).
        Черт, хочется всего и сразу, а без знания теории граблей много =)

        Вопрос по таймеру:
        Я его запустил, он у меня досчитал до переполнения, пришло прерывание. Все замечательно. Если же я хочу, чтобы у меня что-то происходило раз в секунду, мне в обработке прерывания каждый раз переинициализировать таймер? Или это можно как-то изящнее сделать?

        1. Во первых поставь делитель на таймер. Скажем в 1024 раза. В обработчике прерывания не надо каждый раз переинициализировать таймер, достатчно перезаписывать TCNT нужным значением и щелкать счетчиком.

          Есть вариант (не для всех таймеров) настроить на прерывание по сравнению. Т.е. таймер считает до OCR и дает прерывание, ну и сам сбрасывается. Тебе лишь остается выставить делитель таймера и OCR таким, чтобы интервал был удобным каким нибудь, скажем 100мс в этом случае в прерывании тебе надо только щелкать счетчиком и как нащелкает 10шт то выставлять флаг какой нибудь в памяти и это будет означать что наступила секунда

  25. DI HALT, подскажи плиз, как подключать осциллятор ко второму таймеру? Не могу въехать, у тебя написано, просто подключить к ногам TOSC1 и TOSC2, а в даташите про TOSC2 ни слова =(

    When AS2 is written to one, Timer/Counter2 is clocked from a Crystal Oscillator connected to the Timer Oscillator 1 (TOSC1) pin.

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

    1. Не там смотрел, вот что гласит глава клок сорцес:

      Timer/Counter For AVR microcontrollers with Timer/Counter Oscillator pins (TOSC1 and TOSC2), the crystal is connected directly between the pins.

      No external capacitors are needed. The Oscillator is optimized for use with a 32.768kHz watch crystal. Applying an external clock source to TOSC1 is not recommended.

      Note:The Timer/Counter Oscillator uses the same type of crystal oscillator as Low-Frequency Oscillator and the internal capacitors have the same nominal value of 36 pF.

  26. Здравствуйте!!!

    Есть 2 вопроса:

    1 Можно ли программировать микроконтроллер ATMEGA644-20PU в Uniprof если именно эту модель он не поддерживает? А если можно, то какую модель выбирать из списка вручную:
    1200, 2313, 2323, 2333, 2343, 4414, 4433, 8515, 8535, mega103, mega128, mega1280, mega1281, mega16, mega161, mega162, mega163, mega165, mega168, mega169, mega2560, mega2561, mega32, mega325, mega3250, mega48, mega603, mega64, mega640, mega645, mega6450, mega8, mega8515, mega8535, mega88, pwm23, tiny12, tiny15, tiny13, tiny2313, tiny24, tiny25, tiny26, tiny45, tiny44, tiny85, tiny84.

    2 Помогите пожалуйста найти ошибку в программе.
    Микроконтроллер ATtiny2313-20PU (стоит кварц на 20МГц) считает количество импульсов с датчика частоты вращения. При выводе значения на динамический светодиодный 7-ми сегментный 3х разрядный дисплей должна гореть точка целой части (на среднем индикаторе). Точка горит, но появляется бледный её дубликат на соседнем старшем индикаторе (разряд десятков). В схеме ошибок нет, так как когда не был очищен регистр разрядов индикатора, разряды при включении микроконтроллера имели случайное место на индикаторе и всё равно этот бледный дубликат точки присутствовал, всегда также на разряде десятков.
    Вопрос: откуда берётся этот бледный дубликат точки?
    ;********************************************************************************
    ;Вывод на динамический дисплей
    DISPLAY:

    ;Обновление дисплея при каждом N-ым вызове
    dec Counter ;Уменьшение счётчика на 1
    breq PC+2 ;Если не 0 пропускаем следующую команду
    reti ;Переход назад в цикл
    ldi Counter, 255 ; Установка счётчика в исходное состояние

    ;Сброс предыдущего значения разряда порта B
    ldi temp, 255 ;Устанавливаем все единицы
    out PortB, temp ;Выводим в порт B

    ;Включение соответствующего индикатора
    in temp, PinD ;Считываем текущее значение порта D
    lsl temp ;Указываем на следующий индикатор
    sbrc temp, 3 ;Если не вышли за пределы пропускаем следующую команду
    ldi temp, 0b00000001 ;Переход к первому разряду

    out PortD,temp ;Выводим разряд в порт D

    ;Вывод значения индикатора
    inc Discharging ;Выбираем следующий индикатор
    cpi Discharging, 3 ;Если разряд равен 3
    brne PC+2 ;Пропускаем следующую команду
    clr Discharging ;Выбираем первый индикатор

    ldi ZL, 23 ;Устанавливаем ZL на R23
    add ZL, Discharging ;Прибавляем номер индикатора
    ld temp, Z ;Копируем конвертируемое число в temp
    clr ZL ;Устанавливаем ZL на R0
    add ZL, temp ;Прибавляем temp к ZL
    ld temp, Z ;Считываем RX в temp

    cpi Discharging, 1 ;Если не средний индикатор
    brne PC+2 ;Пропускаем следующую команду
    cbr temp, 0b10000000 ;Сброс 7 bit десятков, отвечающего за точку целой части

    out PortB,temp ;Выводим значение разряда в порт В

    reti ;Переход назад в цикл
    ;********************************************************************************

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

    1. 1) А он ее даже не определил? Если не определил, то унипроф тут тебе уже не поможет :( Переходи на AVRDUDE или более часто обновляющиеся программаторы.

      2)А точно в схеме нет утечек? И уверен в правильности схемы? т.к. если у тебя динамическая индикация работает нормально, то и все остальные сегменты должны быть в норме. Ты же байт переключаешь целиком, включая точку.

      Попробуй это отладить в замедленном режиме. Сделай интервал между переключениями сегментов в 1-2секунды. Тупым могучим циклом и посмотри какие у тебя сигналы выводятся для правого и левого сегмента. Так и поймаешь багу.

      Вообще заведи себе макрос затык такого вида:

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

      и пихай его в сомнительные места.

      1. Спасибо за ответ.
        Будем искать, придётся с работы брать осциллограф.

        Подскажите какие программы, работают с программатором для Uniprof, переделывать не хочется?

        1. Да многие. AVRDUDE например. В дополнении к посту про программатор громова есть как настроить аврдуд на простейший программатор.

  27. Спасибо DI HALT, классные статьи, но у меня такой вопрос, с момента нажатия кнопки до включения таймера проходит 9 тактов контроллера, значит реальное время еще зависит и от этого, и при больших частотах надо кроме обнуления предделителя учитывать и это? А еще в обработчике прерываняи неизвестно сколько команд! Я правильно понимаю?

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

  28. Доброе утро!

    Подскажите пожалуйста, можно ли к ATtiny2313 подключить отдельную микросхему памяти примерно на 8кбайт(лучше больше) для сохранения данных в автономной установке, если да то посоветуйте какую и как считать с неё данные в компьютер, чтобы получить график?

  29. Приветствую! Что-то никак не могу пофиксить проблему с таймерами на Mega8.
    Штука следующая: Для движения робота пользую ШИМ от OC1B и выводы PB1, PB3 для выбора направления вращения. Также пользую UART для обмена информацией с компом. По UART приходят к МК команды на вкл/выкл двигателя и должны уходить события датчиков. Отдельно управление двигателем и опрос датчиков работают отлично, а вот вместе…
    В Proteus все живет, а в железе — не хочет.
    Подозреваю, что как-то вмешивается таймер Timer0, который я завожу для опроса датчиков.
    При этом когда приходит команда на двигатель, он слегка дергается и все.
    Пробовал ставить опрос датчиков в основной цикл — двигатель вообще ни на что не реагирует.
    Создается впечатление, что при наличии второго таймера, он сбивает сигнал на PB1 или PB3.
    Буду признателен за подсказку — где копать и, особенно, за пример кода, как же все-таки одновременно сделать:
    — и ШИМ на OC1B
    — и управление направлением вращения на PB и PB3
    — и опрос датчиков по таймеру
    Заранее спасибо!

    1. У тебя начинаются уже многозадачные процессы, когда тебе надо рулить сразу всем.

      Тут тебе проще всего будет поднять ядро диспетчера и погнать цепочками.

  30. написал свою первую пробную программу — часы
    только вот проблема, в основном ровно в 1 мин. и 47 сек. происходит сброс на 0:0
    сторожевой таймер не включал, стек вроде проследил до 40 секунд, держится на 1 уровне, все в цикле, должно быть нормально,
    в общем не могу понять что делать??
    студия пишет
    AVR Simulator: Invalid opcode 0x00c0 at address 0x00003f
    и таких предупреждений полно, с разными адресами

    1. одновременно за такт. Точнее на предделитель каждого из них поступает тактовй импульс от генератора, но тикают они все синхронно только с разной скоростью (в зависимости от настроек предделителя)

      1. Тогда получается, что можно организовать выполнение двух прерываний по таймерам одновременно? И если в них одновременно происходят взаимоисключающие операции, то произойдет конфликт прерываний (ёмть, сто раз слышал это выражение и только сейчас понял его смысл))? И что происходит с микроконтроллером тогда?
        Или они, даже если происходят одновременно, по очереди выполняются (типа сначала прерывание от таймера Т/С0, потом от Т/С1)?

  31. Привет DI
    У меня проблема со сторожевым таймером Mega16. Как сделать так, что бы он сбрасывал контроллер при зависании которое возникает вовремя выполнения программы обработки прерывания? Я пробовал после вызова программы обработки делать так:
    sei
    wdr
    ldi temp,0b00001000
    out WDTCR,temp
    r: rjmp r ; это типа зависание
    Но это не помогает(
    При зависании в других участках программы всё срабатывает отлично.

    1. Естественно
      r: rjmp r
      в реальной программе нет.
      Устройство реально собрано и работает но изредко зависает при выполнении одного и тогоже действия…

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

        1. Причина в сильных электромагнитных помехах. Зависание возникает очень редко, но это всёравно неприятно.
          Как я понимаю, собачий таймер бесполезен при зависаниях, если они возникают при обработке прерывания(, покрайней мере в меге16.

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

  32. Доброе время суток всем форумчанам. Пожалуйста помогите в решении такого вопроса: пишу программку для частотомера, в принципе все уже готово и работает, НО сама программа задумана таким образом что перед самим началом активации счетчика внешних импульсов и таймера (временных ворот) должен произойти сброс предделителей счетчиков Т/С0 и Т/С1. (Кристалл — )

  33. Доброе время суток всем форумчанам. Пожалуйста помогите в решении такого вопроса: пишу программку для частотомера, в принципе все уже готово и работает, НО сама программа задумана таким образом что перед самим началом активации счетчика внешних импульсов и таймера (временных ворот) должен произойти сброс предделителей счетчиков Т/С0 и Т/С1. (Кристалл — tiny 2313). Для этого в регистр GTCCR в перевый разряд PSR10 надо записать 1. Так вот сам вопрос — какой командой это можно сделать? Пробовал:
    ldi temp, 0x01;
    out GTCCR, temp;
    Такие строчки AVR Studio переваривает, но сами предделители НЕ сбрасывает. А на команду:
    out GTCCR, PSR10;
    вобще ругается.
    На этой страничке форума вычитал что можно задать:
    outi GTCCR, 1;
    но кристал tiny 2313 не поддерживает команду outi.

    Заранее благодарен всем за ответы на вопрос.

    1. Ну во первых outi это не команда а макрос.

      А во вторых запись
      out GTCCR, PSR10 не корректна. Нельзя напрямую записать в порт ИО что нибудь. Только через регистр.

      Первый случай

      1
      2
      
      ldi temp, 0×01
      out GTCCR, temp

      Корректен и должен работать. Откуда уверенность что прескалер не сбарсыватеся?

      1. Благадарю за ответ! Уверенность о несбрасывании прескалера исходит из следующего:
        1) в AVR Studio при прогоне программы через встроенный эмулятор при команде

        ldi temp, 0×01;
        out GTCCR, temp;

        визуально не отображается установка 1 в разряд PSR10 при выполнении строки out GTCCR, temp;, а при выпоснении следующей строчки эта единичка должна сбрасываться. Но это вполне возможено и простая недоработка разработчиков AVR Studio.
        2) предварительно установив таймер Т/С1 в режим счетчика с частотой работы
        Ф/64 (при установленной по умолчанию в AVR Studio частоте тактового генератора 4 МГц, период составит 16 мс). В программе строка пуска этого таймера стоит сразу же после строки сброса прскалеров. Тоесть первый тик таймера должен произойти через 16 мс после его пуска, а он происходи гараздо раньше, а вот второй тик и все последующие — через каждые 16 мс.
        3) при прогоне программы (частотомера) в реальной схеме визуально видно очень частое дергание двух младших разрядов числа. Опять же, при прогоне нескольких измерительных циклов на эмуляторе в AVR Studio хорошо видно что каждый раз подсчитанное число тиков отличается на несколько единиц (иногда превышает десяток).

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

        1. Отвечаю сам на поставленный вопрос — вся трабла началась со следующего:

          ldi rab, timezero;
          ………………..
          ldi time, 0x01;
          out GTCCR, rab;

          И после этого я думал что записал в регистр GTCCR содержимое РОН temp. А всего-то нужно было внимательно проверить.

          Прошу прощения за лишнюю тревогу по «глупому вопросу». Теперь сам с уверенностью могу сказать что команда

          ldi temp, 0x01;
          out GTCCR, temp;

          работает (счетчики сбрасываются в ноль).

  34. Всем привет.
    У меня тут возникла необходимость создания частотомера на ATmega88.
    Я прикинул и решил сделать это дела на счетчике считая количество тактов между 2-мя фронтами на каком-то пине (я использовал прерывание INT0), но когда стал переводить такты в Герцы, то возникла проблемы точности моего частотомера:
    — Если время между 2-мя фронтами равно 1 такту контроллера, то получается что частоты источника равна частоте контроллера.
    — Если … 2 такта контроллера, то — 1/2 частоты контроллера.
    И так далее, чем больше тактов, тем меньше диапазон и выше точность, но это никуда не годится, так как при таком раскладе нельзя замерить какую-то промежуточную частоту (поближе к частоте контроллера).
    Получается, если контроллер — 4Mhz, то 1 такт счетчика = 4MHz, а 2 такта уже 2MHz, таким макаром высчитать 3MHz нереально.
    Подскажите как реализовать частотомер, в принципе?
    Спасибо.

    1. Ну по моему это очевидно. Частототомер просто обязан работать на частоте намного больше чем измеряемая частота. Либо иметь делитель частоты на входе.

      AVR не имеет делителя частоты, поэтому частотомеры любят делать на PIC т.к. у них у многих МК есть аппаратный делитель входной частоты.

      Ну либо делать его на дискретке, например, на счетчике.

  35. Попытался сделать програмку, чтобы освоить таймеры. По прерыванию меняю уровень на PortA0. На PortD4 висит кнопка, по нажатию на которую должна повысится частота импульса. К PortA0 еще прицепил динамик через сопротивление, чтобы не только померять прибором, но и услышать. Вот и все. Работать это дело отказывается (на PortA0 постоянно низкий уровень). Может кто поможет. Код вдогонку:

    .INCLUDE «m16def.inc»

    .MACRO outi
    LDI R16,@1
    OUT @0,R16
    .ENDMACRO

    .CSEG
    .ORG 0x000
    rjmp RESET ; Reset Handler
    .ORG OVF1addr ; Timer/Counter1 Overflow
    rjmp T1_OVF

    .org 0x0030
    RESET: ldi r16,high(RAMEND)
    out SPH,r16
    ldi r16,low(RAMEND)
    out SPL,r16
    OUTI DDRA,0xFF
    OUTI DDRD,0x00
    OUTI PORTD,0xFF
    OUTI TIMSK, 1<<TOIE1
    cbi PORTA,0
    cli
    OUTI TCNT1H, High(65535-32)
    OUTI TCNT1L, Low(65535-32)
    sei
    OUTI TCCR1B, 5
    main: in R18, PIND
    sbrc R18, 4
    rjmp main
    rcall wait1
    OUTI TCCR1B, 0
    cli
    OUTI TCNT1H, High(65535-10)
    OUTI TCNT1L, Low(65535-10)
    sei
    OUTI TCCR1B, 5
    rjmp main

    wait1: push R16
    ldi R16,250
    wt: dec R16
    brne wt
    pop R16
    ret

    T1_OVF: in R17, PINA
    sbrc R17,0
    cbi PORTA,0
    sbrs R17,0
    sbi PORTA,0
    reti

      1. Я так понял, счетный регистр не изменяется. Но я же запускаю таймер OUTI TCCR1B, 5 и прерывания разрешены. В чем же косяк?..

        1. Ой-ой-ой. А он после переполнения сбрасывается на 0 оказывается. Хотя по сути почему меня это удивляет… Пошел думать дальше.

        2. Он изменяется. но предделитель стоит в 1024 так что в студии наблюдать это будет проблематично (надо будет сделать 1024 шага чтобы увидеть изменение). Сделай пока запуск как
          OUTI TCCR1B, 1 и прогони в студии до переполнения.

          1. Я просто думал, что после переполнения Т1 сбросится на записанное мной значение, а не на ноль). Если ждать весь пересчет Т1 да еще и с предделителем, получится слишком маленькая частота, и я ее не услышу. Решил теперь использовать Т0. Все переделал, изменил таблицу прерываний, коэффициент деления, добавил цикл ожидания отпускания кнопки. В студии вижу: таймер несколько раз переполняется — программа все равно не уходит в прерывание.

            .INCLUDE «m16def.inc»
            ;freq = 16.000MHz
            .MACRO outi
            LDI R20,@1
            OUT @0,R20
            .ENDMACRO

            .CSEG
            ;Start of Interrupts table
            .ORG 0x000
            rjmp RESET ; Reset Handler
            reti ; IRQ0 Handler
            reti ; IRQ1 Handler
            reti ; Timer2 Compare Handler
            reti ; Timer2 Overflow Handler
            reti ; Timer1 Capture Handler
            reti ; Timer1 CompareA Handler
            reti ; Timer1 CompareB Handler
            reti ; Timer1 Overflow Handler
            rjmp T0_OVF ; Timer0 Overflow Handler
            reti ; SPI Transfer Complete Handler
            reti ; USART RX Complete Handler
            reti ; UDR Empty Handler
            reti ; USART TX Complete Handler
            reti ; ADC Conversion Complete Handle
            reti ; EEPROM Ready Handler
            reti ; Analog Comparator Handler
            reti ; Two-wire Serial Interface Hand
            reti ; IRQ2 Handler
            reti ; Timer0 Compare Handler
            reti ; Store Program Memory Ready Han
            ;End of Interrupts table
            .org 0x0030
            RESET: OUTI SPH, high(RAMEND) ; Main program start
            OUTI SPL, low(RAMEND) ; Set stack pointer to top of RA
            OUTI DDRA, 0xFF
            OUTI DDRD, 0x00
            OUTI PORTD, 0xFF
            OUTI TIMSK, 1<<TOIE0
            cli
            OUTI TCNT0, 0x00
            sei
            OUTI TCCR0, 4
            cbi PORTA, 0
            main: in R18, PIND
            sbrc R18, 4
            rjmp main
            rcall wait1
            main1: in R18, PIND
            sbrs R18, 4
            rjmp main1
            rcall wait1
            cli
            OUTI TCCR1B, 3
            sei
            rjmp main

            wait1: push R16
            ldi R16,250
            wt: dec R16
            brne wt
            pop R16
            ret

            T0_OVF: in R17, PINA
            sbrc R17,0
            cbi PORTA,0
            sbrs R17,0
            sbi PORTA,0
            reti

              1. А. Я это исправил уже) Частота просто бы не менялась. (Как я понимаю) Но ситуацию это не меняет. Программа все равно не заходит в прерывание.

                  1. Все заработало, как я и задумал. Закометил таблицу прерываний, добавив:
                    .ORG OVF0addr
                    rjmp T0_OVF
                    А также переставил sei.
                    Так что где был косяк, я так и не понял. Sei, кстати, имеет ограниченную область действия?(Или я сейчас чушь сказал?)
                    Конечный вариант такой:
                    .INCLUDE «m16def.inc»
                    ;freq = 16.000MHz
                    .MACRO outi
                    LDI R20,@1
                    OUT @0,R20
                    .ENDMACRO

                    .CSEG
                    ;Start of Interrupts table
                    .ORG 0x000
                    rjmp RESET ; Reset Handler
                    .ORG OVF0addr
                    rjmp T0_OVF
                    /*reti ; IRQ0 Handler
                    reti ; IRQ1 Handler
                    reti ; Timer2 Compare Handler
                    reti ; Timer2 Overflow Handler
                    reti ; Timer1 Capture Handler
                    reti ; Timer1 CompareA Handler
                    reti ; Timer1 CompareB Handler
                    reti ; Timer1 Overflow Handler
                    rjmp T0_OVF ; Timer0 Overflow Handler
                    reti ; SPI Transfer Complete Handler
                    reti ; USART RX Complete Handler
                    reti ; UDR Empty Handler
                    reti ; USART TX Complete Handler
                    reti ; ADC Conversion Complete Handle
                    reti ; EEPROM Ready Handler
                    reti ; Analog Comparator Handler
                    reti ; Two-wire Serial Interface Hand
                    reti ; IRQ2 Handler
                    reti ; Timer0 Compare Handler
                    reti ; Store Program Memory Ready Han
                    */
                    ;End of Interrupts table
                    .org 0x0030
                    RESET: OUTI SPH, high(RAMEND) ; Main program start
                    OUTI SPL, low(RAMEND) ; Set stack pointer to top of RA
                    OUTI DDRA, 0xFF
                    OUTI DDRD, 0x00
                    OUTI PORTD, 0xFF
                    sei
                    OUTI TCNT0, 0
                    OUTI TCCR0, 4
                    OUTI TIMSK, 1<<TOIE0
                    cbi PORTA, 0

                    main: in R18, PIND
                    sbrc R18, 4
                    rjmp main
                    rcall wait1

                    main1: in R18, PIND
                    sbrs R18, 4
                    rjmp main1
                    rcall wait1
                    OUTI TCCR0, 3
                    rjmp main

                    ;Çàäåðæêà
                    wait1: push R16
                    ldi R16,250
                    wt: dec R16
                    brne wt
                    pop R16
                    ret

                    ; Îáðàáîò÷èê ïðåðûâàíèÿ
                    T0_OVF: in R17, PINA
                    sbrc R17,0 ; Åñëè íîëü, òî ïðîïóñêàåì óñòàíîâêó â íîëü
                    cbi PORTA,0 ; Óñòàíîâêà â íîëü
                    sbrs R17,0 ; Åñëè 1, òî ïðîïóñêàåì óñòàíîâêó â 1
                    sbi PORTA,0 ; Âûäàåì â PA «1»
                    reti ; Âîçâðàò èç ïðåðûâàíèÿ

                    1. Я понял в чем ошибка. В таблице векторов. Оказывается у разных МК она имеет разную плотность.

                      У тини2313 ее вектора имеют длину одну команду (т.к. памяти мало и не нужна дальнобойна разрядность), поэтому байда вида

                      RETI
                      RETI
                      RETI

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

                      RETI
                      NOP
                      RETI
                      NOP

                      Либо прописывать ORG для каждого вектора.

                    2. Вон оно что. Спасибо за пояснение. Я таблицу копипастил из даташита. Там адреса слева стояли, но я как всегда был невнимателен. А шаг там и правда двухбайтовый.

            1. В студии трассировал? Явно что нет. Я с первого же прогона увидел, что прерывание выполняется, но переход идет не на тот вектор. =)

  36. Дошло наконец-то: частоту с генератора можно поделить регистром CLKPR и это будет основная тактовая частота контроллера (про это кстати мало где упоминается), а для подсчета таймером эту частоту еще раз делим битами CSx2:CSx0. Как бы мне у себя в голове тактовую частоту поднять…

  37. Все комменты не читал — больно много. Есть замечание по поводу инвертирования состояния выходной ножки. Пока есть маааааленький опыт работы только с Тиней2313, так у неё для инвертирования состояния выходной ножки (или режима входной) надо просто выставить соответствующий бит в PINx. То есть:

    sbi DDRB, DDRB0 ;PORTB0 — выход
    sbi PORTB, PORTB0 ;поставили 1 в PORTB0
    nop ;чтобы в PINB0 1 записалась

    sbi PINB, PINB0 ;теперь в PORTB0 и PINB0 вылез 0
    sbi PINB, PINB0 ;теперь в PORTB0 опять 1
    nop ;и только теперь в PINB0 — 1

    sbi PINB, PINB0 ;теперь в PORTB0 и PINB0 вылез 0
    ;…

    Как прочитал в даташите — не поверил. Но симулятор AVRStudio развеял сомнения :)

  38. Дословно из даташита:

    Writing a logic one to PINxn toggles the value of PORTxn, independent on the value of
    DDRxn. Note that the SBI instruction can be used to toggle one single bit in a port.

    1. Да, в курсе. Это фича новых аврок. Тини2313 из их числа. А вот старые (мега8, мега16, мега32, мега128) так не умеют.

  39. Привет. Работаю с 90S2313. в ходе выполнения программы используются оба таймера поочереди — сначала 8-битный с предделителем 1024, а потом 16-битный с предделителем 64. вопрос такой: нужно ли сбрасывать предделитель перед использованием 16-битного таймера в моем случае и как это делается в s2313? чот в даташите не нашел про это

  40. Попробовал поиграть с регистром SFIOR в Atmega8L. Поставил на все таймеры предделитель 64 и такой код чтоб постоянно сбрасывать предделитель.
    Start:
    nop
    nop
    ldi r16,0b00000011
    nop
    out sfior,r16
    nop
    nop
    rjmp Start
    запустил прогу, все равно все счетчики тикают, Т0 и Т1 одинаково, Т2 как то с запозданием.
    Это что баг в AVR studio? Надеюсь в самом мк все пучком будет сбрасываться?

    1. Вполне может быть баг студии. У ней есть ряд косяков работы с таймерами. Зашей в железо и позырь осциллографом.

      1. Ладнек позырю. А интересно сами разработчики хоть знают об этих косяках? Или ждут себе пока пользователи сами не сообщат им? Недавно закачал sp2 на версию 4.18 с их сайта. Все равно видать не исправили еще. А еще только что на макетной плате тестировал кое что с atmega8 и чисто случайно перемычку воткнул не туда. Потом радостно включил питание и у меня 20 вольт очень радостно прямо на мк даванули. Ясно что мк просто щелкнул и дым повалил. Сейчас вот сижу и радуюсь. Повезло что в загашнике еще штук 8 валяется. Это мой первый мк который спалил за пол года работы с ними. Как я так лоханулс, непонятно. Вообщето я все очень аккуратно всегда делаю. Сто раз перепроверяю. Поэтому по жизни у меня с электричеством мало аварий было. Особо ничего не сжигал.

        1. А хз. может попробовать им багрепорт написать. У аврстудии с мегой8 и таймером другой тоже баг уже давно есть — в режиме фаст шим он считает как фаз коррект шим. Т.е. 0..1-255..254-0 а не 0-255-0. А в железе все ок

          1. Я написал бы, да в ангельском языке не соображаю. Можно попробовать конечно на русском. Но думаю если ты напишешь, то у тебя лучше получится. Кстати SFIOR в студии все таки нормально сбрасывает Т2, а вот Т0 и Т1 никак. А в железе все пучком работает. Тиристор открывается как миленький на Т1. Угол открытия не плавает.

  41. Подскажите пожалуйста, что не так:

    .include «m8def.inc»
    .DSEG
    .CSEG

    .ORG OVF0addr ;0x0009
    RJMP Timer_over
    .ORG INT_VECTORS_SIZE ;19

    Reset:
    LDI R16,1
    OUT TIMSK,R16
    Main:
    LDI R16,200
    Delay1:
    LDI R17,172
    OUT TCNT0, R17
    CLT
    LDI R17,5
    OUT TCCR0,R17
    Delay2:
    BRTC Delay2
    DEC R16
    BRNE Delay1
    RJMP Main

    Timer_over:
    SET
    LDI R17,0
    OUT TCCR0,R17
    RETI

    .ESEG

    Уже довольно долго бьюсь над тем чтобы при переполнении попадать на свою функцию, но никак не выходит >.<

    1. Как минимум две очень грубых ошибки. Если бы ты протрассировал свой код в отладчике, покомандно, то ты бы сразу увидел что у тебя неправильно работать и как оно себя ведет. Но ты видимо не догадался. Так сделай это! :)

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

        1. Ну во первых. Ладно. Рас ржамп ресет с нулевого адреса сделал, то минус одна серьезная ошибка.

          Второе — где инициализация стека? У тебя без нее не будет возврата из прерывания.

          Третье — а прерывания глобально кто разрешать будет?

          Четвертое, не ошибка но тоже важно. Чо бля за магические числа в иницйиализации таймера?

          1. Скопипастил инициализацию стека и разрешение прерываний. Все заработало. Прерывание не срабатывало из за того, что я его не разрешил :))
            Магические числа инициализации таймера из доков. было как то неуютно пользоваться библиотекой m8def.inc для таких целей. К тому же в начале изучения МК первых нормальных результатов я добился только из за этой документации ))
            Спасибо!

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

    1. В обычный режим. Записать в TCCRxx только делитель (биты CS), оставив все как есть (вроде бы по нулям по дефолту) Ну и в TIMSK разрешить прерывания.

      1. для мега8 уходит на прерывание по совпадению раньше. Подскажите пожалуйста, что не так:

        .ORG $003
        RJMP Metka1
        Metka:LDI R16,1 ; 0000 0001 предделитель на 1
        OUT TCCR2,R16 ; Запускаем таймер

        LDI R16,64
        OUT OCR2,R16; заношу в регистр сравнения число

        SEI ;разрешение прерывания

        ldi r16,0b10000000
        OUT TIMSK,R16; разрешение прерывания по совпадению

        1. Скорей всего у тебя 1. разрешен еще какой то вектор прерываний.
          2. косячная таблица векторов прерываний. Покажи инициализацию таймера (только без магических чисел) и таблицу векторов прерываний.

  43. DIHALT ты выше писал по тексту
    CLI ; Доступ к многобайтной переменной
    ; одновременно из прерывания и фона
    ; нужен атомарный доступ. Запрет прерываний

    OUTU TCNT0,R16 ; Ноль в счетный регистр таймера
    STS TCNT,R16 ; Ноль в первый байт счетчика в RAM
    STS TCNT+1,R16 ; Ноль в второй байт счетчика в RAM
    STS TCNT+2,R16 ; Ноль в третий байт счетчика в RAM
    STS TCNT+3,R16 ; Ноль в первый байт счетчика в RAM
    SEI ; Разрешаем прерывания снова.
    Если регистр R16 и так сохраняется в стеке во входе в прерывание, обязательно ли запрещать прерывания в данной ситуации? Выйдя из прерывания он снова станет 0 и дальше запишется это значение в нужные нам регистры.

    1. В принципе можно и не запрещать, но если при этом у нас в прерывании будет доступ к TCNT то будет работать некорректно. Ведь мы можем начать ТСНТ менять, а в прерывании считать или записать значение наполовину измененное.

      1. Да понял, рубишь в точку. А вот еще мысля, допустим я хочу на таймере T0 замутить 10 таких счетчиков. Регистр то счетный у Т0, всего 225. Пока он у себя в прерывании прощелкает все эти 10 счетчиков, то как раз натикает свои 255 тактов.И выйдя из прерывания опять нужно нырять обратно. На исполнение основного кода не останется лишних тактов. Как бы ты это решил? Т1 и Т2 уже заняты. Я бы наверное прескал поставил 8. Тогда прерывание возникало бы каждые 256×8=2048 тактов.Погрешность слегка увеличиться ну и ладно не проблема. Зато больше времени останется на исполнение основного кода. Кстати в последнем своем проекте я уже применил прием описанный в этой статье. Два таких программных таймера замутил на Т0. Сделал зарядное устройство на 10 ампер для гелевых батарей в поломоечную машину.

        А еще попутно, просто мне интересно.Ты все время в интернете сидишь? Как контролируешь появление новых вопросов? Сколько примерно в день ты получаешь вопросов на твоем сайте?

        1. Я бы сделал по другому. У меня был бы тик. Скажем 1мс (или еще минимальная достаточная дискретность для самого быстрого счетчика) и я бы в прерывании таймера этим тиком увеличивал 10 переменных. А главная программа сравнивала бы эти 10 переменных на совпадение (в каждом своем обработчике сравнивается своя переменная).

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

          Почти всегда, ага. Бывает отхожу — прихожу. У меня все комменты в админке в виде плоского списка. Там же я могу не отходя от кассы на них отвечать. В день отвечаю примерно на 20-50 вопросов.

          1. Так, так, хочу вникнуть в твои рассуждения. Не совсем понял. Я ж потом все это буду на практике применять. Значит есть один счетчик №1. Когда возникает прерывание по таймеру, проверяется что он натикал 1 мс. Если натикал, тогда 10 переменных увеличились на 1. И так каждую милисекунду все 10 пернеменных будут увеличены на 1. получается все эти 10 переменных имеют максимально достижимое время это 256 мс. Получается у меня будет 10 счетчиков и каждый может дотикать до 256 мс и потом снова с нуля. А если мне нужно скажем 1сек? Тогда счетчик №1 может иметь дискретность 4 мс. Тогда все 10 счетчиков смогут достичь 1 с. Так что ли?

            1. Нет. Прерывание этого таймера возникает не чаще чем раз в 1мс. В этот момент в его обработчике щелкают переменных-таймеров. У каждого ожидающего обработчика свой.

  44. Подскажите пожалуйста почему когда симулирую в протеусе частота не получаеться равной 16 МГц ?

    #include

    interrupt [20] void Tim (void)
    {
    PORTD.1=~PORTD.1; // меняю состояние
    }

    void main (void)
    {
    DDRD=0xFF;
    TCCR0=(1<<WGM01)|(0<<WGM00)|(0<<CS02)|(0<<CS01)|(1<<CS00); // сброс при совпад, тактора частота таймера 16 МГц
    TCNT0=0;
    /*
    Тактовая частота микроконтроллера 16 МГц
    Тактовый сигнал таймера равен 16000000 Гц/1 = 160000000 Гц.
    Время одного такта таймера 1/16000000 = 0.65*10^-8 с
    Время одного такта нужной нам частоты 1/16000000 = 0.65*10^-8 с
    Сколько тактов таймера укладывается в 0.65*10^-8 с ? 0.65*10^-8/0.65*10^-8 = 1 тактов
    1 в двоич. системе = 1
    */
    OCR0=0b1;

    #asm(«sei»)

    while (1)
    {
    TIMSK = (1<<OCIE0);
    }

    }

  45. Тактовая частота микроконтроллера 16 МГц, в микроконтроллере clock frequency и фьзы на 16 МГц поставил.

    1. И хочешь чтобы у тебя нога дергалась со скоростью 16мгц??? Не будет такого никогда!

      У тебя передерг ноги на прерывании. А теперь давай посчитаем:

      Переход к прерыванию. Если писать на асме — два такта минимум. Если на Си — не менее десятка (сохранение регистров)

      Передергивание вывода — четыре такта, минимум. На Си может и гораздо больше быть.

      Выход из прерывания — два такта в лучшем случае, и тот же десяток в худшем.

      Итого, на обработку передерга ножки уходит в среднем не меньше чем 25 тактов. И о каких 16мгц на ноге может идти речь? В лучшем случае 16/25мгц и то я сомневаюсь.

  46. Ди, приветствую! Необходимо твоё компетентное мнение. Есть задача: на ногу Mega168 циклически поступают 6 импульсов и два пропуска (11111100). Подскажи, пожалуйста, с какой стороны подойти к методу детектирования первого импульса в посылке? Частота импульсов внутри одной посылки постоянная, но может меняться от посылки к посылке. Задача сводится к отлавливанию двух пропусков, за которыми пойдёт первый импульс. Но как это сделать, пока ума не приложу. В среднем, частота импульсов в посылке от 0,6 до 5 кГц. Частота кварца МК — 20 МГц.

    Идея у меня пока, упрощённо, такая:
    1. Ловим на входе импульс (какой он по счёту, мы не знаем пока), запускаем таймер и ждём следующего импульса
    2. Получаем следующий и измеряем его период.
    3. Через измеренное время ждём импульс или его отсутствие.
    4. Если импульса не было, считаем, что это был первый пропуск, и ждём второй через измеренное время.
    5. Если снова не было импульса, отмеряем период и, получив импульс, считаем его первым…

    Но всё это будет жутко «плавать», как мне кажется. И не будет работать, если первым придёт 6-ой по счёту импульс. В общем, если есть идея, комрад, предложи. Заранее благодарен!

    1. Хм… А если мы поймаем между двумя пропусками? Не вариант! Т.к. у нас включение произвольное, то что нам мешает потупить какое то время, пожрать импульсов поболее, чтобы разобраться в ситуации лучше? Тогда можно будет точно сказать, что это у нас и где.

  47. Озадачен вот каким вопросом: в этой процедуре
    Timer0_OV: PUSHF
    PUSH R17
    PUSH R18
    PUSH R19

    INCM TCNT

    POP R19
    POP R18
    POP R17
    POPF

    RETI

    Я не понимаю что такое «INCM TCNT». Макрос мы такой не добавляли. Оператора у нас такого нет. Вообще до этого ни где INCM не упоминалось. Будьте добры, объясните откуда это и для чего.

  48. Доброго времени суток уважаемые форумчане!
    Помогите пожалуйста разобраться с таймером Tiny25.

    Выше в коментах уже встречал подобную проблему у devmind.livejournal.com, но человек пишет, что программа стала криво. У меня все хуже. Пробовал на трех разных машинах — результат один и тот-же…

    Итак суть проблемы.
    Использую Attiny25, пишу в AVR Studio (последняя версия с сервис паками)…
    ТС0 использую под протокол 1-Wire, с ним все в порядке, работает как нужно. А вот ТС1 не запускается. На зарубежных форумах вычитал рекомендации использовать AVR Simulator2. Попробовал, да, в нем таймер запускается, но возникают проблемы с прерываниями, INT0 работает не правильно (срабатывает по любому изменению на входе, а не по спаду, как нужно), биты в порте В как то не так становятся, в общем полный геморой. Кроме того, в самом счетчике ТС1 не очищается счетный регистр TCNT1, непонятки при попытке сбросить предделитель бит PSR1 в регистре GTCCR, и т.д.
    В железе пока не воплощал. Грешу на косяки AVR Studio. Может кто что еще подскажет полезного.
    Вот кусок кода.
    Всем зареннее спасибо.

    ; === Инициализация таймер 1 ==========================================================

    outi OCR1A,TI ; в канал А время импульса запуска симистора
    out OCR1B,calc_brigh ; в канал В время до включения симистора

    * * *

    ; === Запуск ТС1 ======================================================================

    sbrp TIMSK,(1<<OCIE1A) ; разрешаем прерывание по совпадению канал А
    sbrp TCCR1,(1<<CS12) ; запускаем ТС1 с предделителем 8
    VS_On ; включить оптопару

    * * *

    ; === Перезапуск ТС1 ==================================================================

    clri TCCR1 ; останавливаем ТС1
    clri TCNT1 ; сбрасываем ТС1

    cbrp TIMSK,(1<<OCIE1А) ; запрещаем прерывание по совпадению канал А
    sbrp TIFR,(1<<OCF1В) ; сбрасываем флаг совпадения канал В
    sbrp TIMSK,(1<<OCIE1В) ; разрешаем прерывание по совпадению канал В
    sbrp TCCR1,(1<<CS12) ; запускаем ТС1 с предделителем 8

    * * *

    ; === Остановка ТС1 ===================================================================

    clri TCCR1 ; останавливаем ТС1
    clri TCNT1 ; сбрасываем ТС1
    VS_Off ; выключаем оптопару

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

  49. DI привет, не могу понять, почему в студии не запускаются таймеры у Attiny25,45,85? Может сталкивался? Глюк студии чтоли? Пять часов убил а они не запускаются. Надо бы попробовать в железо прогу зашить и там проверить.

    1. Прескалеры выставил, а они не тикают? У этих тинек вроде бы PLL еще есть на таймеры. Там не все так просто. МОжет ты его забыл включить?

      1. Да прескалы выставил а они не тикают. У attiny25,45,85 у таймеров нет PLL регистров. Сдается мне что это глюк студии, наверное. Прикольно, как теперь программу отладить?

        1. ATtiny25 адреса 0х27(0х47) регистр PLLCSR… Все там есть, только по умолчанию PLL выключен. У меня Т/С0 работает нормально, а вот с Т/С1 глюки. И скорее глюки в программе AVRStudio… Все с кем общался рекомендуют пробовать воплощать проект в железе.
          Как вариант, попробуй симулировать работу таймеров в AVR Simulator2. Но учти, что при этом могут не корректно работать прерывания…
          ;-)

          1. Ну да точно есть. ШОК … не узрел. Просто этот PLL регистр в attiny26 прямо в таймере виден а в аттину 25 закопали его в другое место. Да надо внимательнее даташиты смотреть. Обычно это я делаю в последнюю очередь. А надобы наоборот сперва даташит потом все остальное. Просто английский не знаю поэтому косяк. Начать учить чтоли?

            1.А ты в английском шариш? Как переводиш? Какой то прогой?
            2.А вчем прикол между AVR Simulator2 и AVR Simulator?

          2. О! смотри че нарыл в коментах одной из статей:

            simulavr2 поддерживает больше функций и заточен под новые модели процов.
            Например обычный simulavr не поддерживает запуск и моделирование таймера1 в attiny25.

            Для тактового сигнала таймера1 примененен хитрый pll генератор на 64 мгц (PLL — означает генератор с фазовой автоподстройкой частоты). Через некоторое время после включения он синхронизируется с основным тактовым генератором процессора (чтобы их частоты отличались в целое число раз) и при этом выставляет в младший бит регистра pllcsr единичку.
            Только после появления этой единички можно настраивать коэффициент деления и выставлять задержку таймера 1.

            Ну попробуй допетри что поддерживает и что не поддерживает это AVR Simulator? ШОК!

            1. О! Полезная инфа. Я этого раньше нигде не читал. Попробую…

              С AVR Simulator2 есть свои глюки. Я нарывался на то, что не правильно срабатывают внешние прерывания. То есть настроил срабатывание INT0 по спаду импульса, а он срабатывает по любому изменению уровня (как PCI)…
              При этом в AVR Simulator прерывания отрабатываются правильно, а Т/С1 не запускается…
              __________
              В стране бардак, а вы хотите чтобы симуляторы нормально работали :-)

              1. Слушай Алекс, а ты каким программатором пользуешся? Случайно не аппаратным FT232R который у DIHALTA есть на сайте.

                1. Не… Использую USBasp.

                  Кстати, нашел у себя кусок переведенного даташита на таймер счетчик 1 ATtiny25… Перевод не полный и кривой, но для понимания достаточный… Давай почту, перешлю…
                  Да, еще, PLL используется если Т/С работает в асинхронном режиме (частота задается собственным кварцем или внешним генератором)… Если использовать генератор центрального процессора, в PLL, по идее лезть не нужно…

              2. Слушай Алекс, в общем фишка такая. В attiny 26 как равно и attiny 25_45_85 таймеры Т0 запускаются. А вот Т1 не запускаются как мы выше и говорили. НО! Оказываются запускаются все же и Т1. Просто студия не отображает что регистр TCNT1 тикает. А он все же тикает где то в глубинах студии. Так как прерывания по переполнению и по сравнению работают. А однажды при очередной эмуляции вдруг регистр TCNT1 у меня затикал циферки побежали, но это было только один раз. Думаю эта инфа и DI полезной будет. Но это я говорю о attiny 26, думаю что и в attiny 25_45_85 та же песня.
                А все таки земля вертится!

  50. «Поэтому предделители можно и нужно сбрасывать. Также надо учитывать и то, что предделитель един для всех счетчиков, поэтому сбрасывая его надо учитывать то, что у другого таймера собьется выдержка до следующего тика, причем может сбиться конкретно так.»
    А как выкручиваться — сохранять содержимое TCNTх другого таймера в стеке или где-нибудь в ОЗУ перед сбросом предделителя?

    1. ТСNT не сбивается, он у каждого свой. Не путай. Просто в ТСНТ другого таймера +1 придет позже если там юзается предделитель и его сбросили.

  51. Насторожила такая возможная ситуация:
    »
    А если ты будешь сбрасывать предделитель в цикле, во благо первого таймера (у которого предделитель, например, 1:64), чаще чем раз в 1024 такта, то второй таймер так никогда и не тикнет,»

    Как разруливать — правильным построением алгоритма? Или?

  52. МК ATTiny26. Почему в симуляторе не работает такой код?
    ldi r16, (1<<PLLE) ; Enable PLL
    out PLLCSR, r16

    waitPLL:
    in r16, PLLCSR ; Wait for PLL to lock (approx. 100ms)
    sbrs r16, PLOCK
    rjmp waitPLL

    in r16, PLLCSR ; Set PLL as PWM clock source
    ldi r17, (1<<PCKE)
    or r16, r17
    out PLLCSR, r16

    PLOCK на нуле все время. Ждал довольно долго. На железе не пробовал. Сколько примерно по времени занимает синхронизация? Может что-то еще может влияет на этот PLOCK?

  53. А подскажет кто-нибудь формулу для расчёта таймера 0 в ATMega8? Который 8 битный.
    Только не программы-калькуляторы, а формулу! Чтобы знать что надо записывать в таймер, чтобы получать переполнения каждые t мксек, при частоте f и предделителе n?
    Я бьюсь, бьюсь — но никак не догоню. Калькулятор-то мне посчитал всё красиво. Но я хочу нормальную прогу себе через define а не с магическими числами.

    1. Эмм… а чо там считать то?

      скорость тиков
      Ftim = Fcpu/Prescaler

      Длительность тика в секундах:
      Tick_leng = 1/Ftim

      На задержку Time надо
      N=Time/Tick тиков.

      Задержка это число до переполнения, т.е. в регистр вносим 255-N

      1. Угу, спасибо! У меня дело в расстановке скобок было, получается. Вышла такая хрень:
        n = (RTHiTimerTick * 0,000001) / (RTHiTimerPrescaler / RTCoreClock);
        Потому что по действиям на бумажке оно верно выходило, а вот в формулу собрать не хватало ума…

  54. Подскажите можно ли как-нибудь таймер сделать инвертированным? Имеется ввиду, что таймер тикает спадами в ноль на выходе T1.

    1. Таймерт тикает всегда вверх, направление счета тут задать нельзя.

      А что значит на выходе Т1? Нужна другая полярность выходных импульсов? Это можно сделать изменив биты COMxx

      1. В ночи видимо голова уже не соображала, и я не совсем правильно выразился =)
        Да, нужно именно изменить полярность выходных импульсов. У меня как бы было два варианта: дополнительно использовать внешний инвертор, либо инвертировать средствами МК. Сейчас попробую разобраться с битами COMxx. Спасибо.

      2. Ди, насколько я понял, этими битами задается состояние выхода OC1x, но не T1. Как получить на выходе T1 инвертированные пульсации по переполнению таймера я так и не воткнул (

          1. Ситуация простая: я по таймеру хочу запускать АЦП, но там вход запуска расчета инвертированный. То есть он запускается, когда на этом входе «0», в остальное время надо подавать «1».

          2. Таков сигнал на выходе T1 по умолчанию:

            _____|`|__________|`|__________|`|____

            А мне надо NOT(T1), т.е.:

            ««`|_|«««««|_|«««««|_|««`

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

            1. На каком, нахрен, ВЫХОДЕ Т1? Т1 это СЧЕТНЫЙ ВХОД. На нем не может быть никакого сигнала. Какой контроллер?

              1. Блин, я просто думал что когда таймер тикает он выдает на соответствующую лапу (T1 или T0) импульсы во время тика. Туплю серьезно. Спасибо за попытку вникнуть в весь этот бред =)

                1. Сделай проще — по прерыванию переполнения выдавай на любой нужной ноге любой нужный уровень. Думаю комбинации
                  0
                  NOP
                  NOP
                  NOP
                  1

                  Будет достаточно для того, чтобы АЦПшка запустилась

                  1. Да, я как раз об этом сейчас подумал. Просто изначально природу таймера как захотел, так и понял, а потом мучался =)

  55. Здраствуйте! Возник вопрос могут ли таймеры контроллеров tiny работать в асинхронном режиме?

  56. А когда там эти пины называются???? в 2313 чё то я не нашёл входов TOSC1 и TOSC2.
    И вот тут ппутный вопрос возник, в некоторых моделях МК(вроде в новых) в альтернативных функциях порта есть надпись PCINT8 например. Что это такое? вроде как некое прерывание но не могу понять как с ним работать.

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

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

      1. Ещё раз смотрел дадатшит на 2313, не могу найти ни слова про асинхронный режим работы его таймера, так же как и в др tiny. Вы можете сказать как в 2313 называется бит который надо выставлять чтобы включить таймер в асинхронный режим?

  57. Всем привет! Не могу побороть программу-частотомер, прошу помощи. Код перепроверил вдоль и поперёк, видимо, что-то упустил.

    Листинг по ссылке, описал максимально подробно, в коде всё с комментами.

    http://easyelectronics.ru/repository.php?act=view&id=73

    Спасибо всем, кто откликнется.

    1. Нашёл проблемную строку:

      LDI freq,TCNT0 //получаем значение счётного регистра

      Почему-то во «freq» всегда попадает значение «50». При этом пробовал другие регистры напрямую, без дефайна, результат такой же….
      Разве из TCNT0 нельзя получать значение при помощи LDI??? Щас буду пробовать LD….

      А значение константы «tic» всегда равно значению счётного регистра «TCNT0». Пробовал другое имя для константы — то же самое…..
      Бред какой-то…. Либо я чего-то не понимаю, либо студия выносит мне мозг…..

      1. Налицо полное непонимание языка ассемблер. Это тебе не Си.

        Команда LDI грузит ТОЛЬКО константу и только в старшие регистры (R16-R17). И ничего более. Для других действий нужны другие команды. см. даташит.

        Что же ты делаешь свой командой

        LDI freq,TCNT0

        ты во FREQ грузишь значение которому рана метка TCNT0 она равна 50. Т.к. регистр таймера TCNT лежит в 50й ячейке памяти. Это всего лишь переопределенная константа. Где то в inc файлах есть нечто вида .equ TCNT0 = 0x50 чтобы тебе не приходилось смотреть в даташит на адреса ячеек.

        Как же взять собственно значение из регистра перферии? Для этого есть команды IN и OUT ин берет ,аут кладет.

        Так что

        IN freq, TCNT0

        При этом freq должен быть регистром и только регистром от R0 до R16. Если же у тебя freq в памяти, как константа. То:

        IN R16,TCNT0
        STS freq, R16

      2. Внимательно смотри систему команд, она в даташите в разделе Instruction Set Summary там все команды есть, и для каждой укзано:

        С каким операндами она может работать, какие флаги она изменяет и изменяет ли, какое время в тактах нужно на команду.

    2. Перечитал вот эту статью:
      http://easyelectronics.ru/avr-uchebnyj- … h-rom.html

      Правильно было так:
      IN freq,TCNT0 //получаем значение счётного регистра

      Но всё равно не понятно, почему константа, объявленная через .equ всегда принимает значение счётного регистра «TCNT0»??? Или это всего-лишь ошибка трассировщика???

      1. Она не принимает значение счетного регистра. И это не ошибка. Просто компилятор подставляет вместо константы нужный адрес в команду и все. Чтобы тебе не держать ее в голове. Вот только компилятору то все равно что это за константа? Она может быть равна некому жестко прибитому физическому адресу (регистр периферии), а может быть назначена тобой от балды. Для него это некая абстрактная цифра которую он если может куда то занести, то занесет. Если не сможет (например команда не позволяет грузить константы или она вылазит за диапазон) то ругнется ошибкой.

  58. Здравствуйте!
    Первый раз использую прерывания, но пока без результатов.
    В симуляторе всё нормально работает, а на железе не хочет.
    С помощью таймера задаю задержку равную 1с, зажигаю дисплей (все сегменты) на 4х семисигментных индикаторах на сдвиговых регистрах, опять задержка 1с и вывод какого-то значения на дисплей.
    Правильно ли настроен таймер, и в какой последовательности это лучше сделать?
    Нужно ли его отключать в подпрограмме прерывания?
    ; Для AVR: ATmega8-16PU
    ; Тактовая частота: 16 МГц

    .device ATmega8 ;Марка контроллера
    .nolist ;Прекращение копирования считываемых данных в файл листинга
    .include «m8def.inc» ;Файл с настройками
    ;»\Atmel\AVR Tools\AvrAssembler2\Appnotes»
    .list ;Разрешение ассемблеру ввод данных в файл листинга

    ;Переменные
    .def temp=r16 ;Рабочая первая
    .def temp2=r17 ;Рабочая вторая
    .def Thousands=r18 ;Тысяч
    .def Hundreds=r19 ;Сотен
    .def Tens=r20 ;Десятков
    .def Ones=r21 ;Единиц

    ;Работа с памятью EEPROM
    .cseg ;Запись обычного кода
    .org 0 ;Адрес начала памяти EEPROM для программы

    ;Прерывания
    rjmp RESET
    reti ;rjmp INT0Int ;Внешний запрос на прерывание по входу INT0
    reti ;rjmp INT1Int ;Внешний запрос на прерывание по входу INT1
    reti ;rjmp OC2Int ;Прерывание по совпадению таймера/счётчика 2
    reti ;rjmp OVF2Int ;Прерывание по переполнению таймера/счётчика 2
    reti ;rjmp ICP1Int ;Прерывание по захвату таймера/счётчика 1
    rjmp OC1AInt ;Прерывание по совпадению таймера/счётчика 1, канал A
    reti ;rjmp OC1BInt ;Прерывание по совпадению таймера/счётчика 1, канал B
    reti ;rjmp OVF1Int ;Прерывание по переполнению таймера/счётчика 1
    reti ;rjmp OVF0Int ;Прерывание по переполнению таймера/счётчика 0
    reti ;rjmp SPIInt ;Передача по SPI завершена
    reti ;rjmp URXCInt ;USART, приём завершён
    reti ;rjmp UDREInt ;USART, регистр данных пуст
    reti ;rjmp UTXCInt ;USART, передача завершёна
    reti ;rjmp ADCCInt ;Преобразование АЦП завершено
    reti ;rjmp ERDYInt ;Готовность EEPROM
    reti ;rjmp ACIInt ;Прерывание от аналоговового компаратора
    reti ;rjmp TWIInt ;2-проводной последовательный интерфейс
    reti ;rjmp SPMRInt ;Готовность таблицы векторов прерываний

    .org 100 ;Адрес начала памяти ОЗУ для программы

    RESET: ;Инициализация
    ;Загрузка стека
    ldi temp, low(RAMEND) ;Указатель програмного стека
    out SPL, temp ;Для младшего разряда
    ldi temp,high(RAMEND) ;Указывает на последний адрес ОЗУ
    out SPH,temp ;Для старшего разряда

    ;Аналоговый компаратор
    ldi temp, 0b10000000 ;Выключение аналогового компаратора
    out ACSR, temp ;Настройка аналогового компаратора

    ;Определение входов и выходов (вход с подтяжкой-уменьшает потребление микроконтроллера)
    ldi temp,0b00000000 ;Определяем входы и выходы порта B
    out DDRB,temp
    ldi temp,0b00000000 ;Определяем входы и выходы порта C
    out DDRC,temp
    ldi temp,0b11100000 ;Определяем входы и выходы порта D
    out DDRD,temp

    ldi temp,0b00111111 ;Включение подтяжки для входов порта B
    out PortB,temp ;и устанавливаем их начальное состояние
    ldi temp,0b00111111 ;Включение подтяжки для входов порта C
    out PortC,temp ;и устанавливаем их начальное состояние
    ldi temp,0b11011111 ;Включение подтяжки для входов порта D
    out PortD,temp ;и устанавливаем их начальное состояние

    ;R0-R12 — Коды для семисегментного индикатора
    ldi temp, 0b00100001 ;(33) «0»
    mov r0, temp ;Сохранение значения в регистре
    ldi temp, 0b11101011 ;(235) «1»
    mov r1, temp ;Сохранение значения в регистре
    ldi temp, 0b00110010 ;(50) «2»
    mov r2, temp ;Сохранение значения в регистре
    ldi temp, 0b01100010 ;(98) «3»
    mov r3, temp ;Сохранение значения в регистре
    ldi temp, 0b11101000 ;(232) «4»
    mov r4, temp ;Сохранение значения в регистре
    ldi temp, 0b01100100 ;(100) «5»
    mov r5, temp ;Сохранение значения в регистре
    ldi temp, 0b00100100 ;(36) «6»
    mov r6, temp ;Сохранение значения в регистре
    ldi temp, 0b11100011 ;(227) «7»
    mov r7, temp ;Сохранение значения в регистре
    ldi temp, 0b00100000 ;(32) «8»
    mov r8, temp ;Сохранение значения в регистре
    ldi temp, 0b01100000 ;(96) «9»
    mov r9, temp ;Сохранение значения в регистре
    ldi temp, 0b10101000 ;(168) «H»
    mov r10, temp ;Сохранение значения в регистре
    ldi temp, 0b11111110 ;(254) «-»
    mov r11, temp ;Сохранение значения в регистре
    ldi temp, 0b11111111 ;(0) » »
    mov r12, temp ;Сохранение значения в регистре

    ;Задержка для запуска дисплея при включении
    ;Таймер T1 (16bit) — создание задержки 1с
    ;16МГц*1c/256=62500(11110100 00100100)
    ldi temp,0b00000100 ;Делитель равен 256
    out TCCR1B,temp ;Установка настроек таймера
    ldi temp,0b00010000 ;Разрешение прерываний по совпадению, канал A
    out TIMSK,temp ;Установка настроек таймера

    ldi temp,0b00000000 ;Запрет остальных прерываний
    out GIMSK,temp ;Установка значений настроек

    ldi temp,0b11110100 ;Запись значения старшего разряда для сравнения, канал A
    out OCR1AH,temp ;Установка настроек таймера
    ldi temp,0b00100100 ;Запись значения младшего разряда для сравнения, канал A
    out OCR1AL,temp ;Установка настроек таймера

    ldi temp,0b00000000 ;Очистка значений счётчика
    out TCNT1H,temp ;Сброс значения старшего разряда
    out TCNT1L,temp ;Сброс значения младшего разряда

    ldi temp,0b00010000 ;Сброс флага прерывания по совпадению, канал A
    out TIFR,temp ;Установка значения

    sei ;Глобальное разрешение прерываний

    ;Бесконечный цикл
    INFINITY:
    ;Проверка флага разрешения прерываний
    brbc 7,PC+2 ;Проверка бита (SREG) разрешения прерываний, если сброшен пропуск команды
    rjmp INFINITY ;Переход к началу цикла

    ;Присвоение значений переменным
    ldi Thousands, 8 ;Присвоение значения «8»
    ldi Hundreds, 8 ;Присвоение значения «8»
    ldi Tens, 8 ;Присвоение значения «8»
    ldi Ones, 8 ;Присвоение значения «8»

    ;Диагностика дисплея (зажигание всех сегментов)
    mov temp, Thousands ;Записываем в временную переменную значение тысяч
    rcall CODES ;Переход к выбору кода соответствующего значения для передачи на индикатор
    cbr temp, 0b00100000 ;Сброс 6bit, отвечающего за точку целой части
    rcall DISPLAY ;Переход к передаче значения на дисплей по SPI

    mov temp, Hundreds ;Записываем в временную переменную значение сотен
    rcall CODES ;Переход к выбору кода соответствующего значения для передачи на индикатор
    cbr temp, 0b00100000 ;Сброс 6bit, отвечающего за точку целой части
    rcall DISPLAY ;Переход к передаче значения на дисплей по SPI

    mov temp, Tens ;Записываем в временную переменную значение десятков
    rcall CODES ;Переход к выбору кода соответствующего значения для передачи на индикатор
    cbr temp, 0b00100000 ;Сброс 6bit, отвечающего за точку целой части
    rcall DISPLAY ;Переход к передаче значения на дисплей по SPI

    mov temp, Ones ;Записываем в временную переменную значение единиц
    rcall CODES ;Переход к выбору кода соответствующего значения для передачи на индикатор
    cbr temp, 0b00100000 ;Сброс 6bit, отвечающего за точку целой части
    rcall DISPLAY ;Переход к передаче значения на дисплей по SPI

    rjmp START ;Переход к началу основной программы

    ;Подпрограммы
    ;———————————————————————
    ;Передача значения на дисплей по SPI (цикл 8 тактов, 130мА)
    DISPLAY:
    ldi temp2, 8 ;Установка счётчика количества передаваемых символов в исходное состояние

    cbi PortD, 7 ;Установка SRCK в начальное состояние 0

    lsl temp ;Сдвиг temp влево (7bit копируется в флаг C, а 0bit сбрасывается в 0)
    brcc PC+3 ;Пропускаем 2 команды

    sbi PortD, 5 ;Установка SIN в 1
    rjmp PC+3 ;Пропускаем 2 команды

    cbi PortD, 5 ;Установка SIN в 0
    nop ;Выравнивание по времени

    sbi PortD, 7 ;Установка SRCK в 1 для считывания значения индикатором
    nop ;Выравнивание по времени
    nop ;Выравнивание по времени
    nop ;Выравнивание по времени

    ;Проверка на количество передаваемых символов
    subi temp2, 1 ;Уменьшение счётчика на 1
    brne DISPLAY+1 ;Если не 0 переход к началу передаче значения на дисплей по SPI

    ;Вывод значения на дисплей (включение регистра-защёлки)
    cbi PortD, 4 ;Сброс RCK в 0
    sbi PortD, 4 ;Установка RСK в 1 для появления значения на экране

    ret ;Переход назад в основную программу
    ;———————————————————————
    ;Выбор кода соответствующего значения для передачи на индикатор
    CODES:
    clr ZL ;Устанавливаем ZL на R0
    add ZL, temp ;Прибавляем соответствующее значение переменной
    ld temp, Z ;Копируем конвертируемое число в temp

    ret ;Возвращение назад в основную программу
    ;———————————————————————
    ;Прерывание по совпадению таймера/счётчика 1, канал A
    OC1AInt:
    ldi temp,0b00000000 ;Остановка таймера
    out TCCR1B,temp ;Установка настроек таймера

    ret ;Возвращение назад в основную программу
    ;———————————————————————
    ;********************************************************************
    START: ;Основная программа

    ;Таймер T1 (16bit) — создание задержки 1с
    ;16МГц*1c/256=62500(11110100 00100100)
    ldi temp,0b00000100 ;Делитель равен 256
    out TCCR1B,temp ;Установка настроек таймера
    ldi temp,0b00010000 ;Разрешение прерываний по совпадению, канал A
    out TIMSK,temp ;Установка настроек таймера

    ldi temp,0b00000000 ;Запрет остальных прерываний
    out GIMSK,temp ;Установка значений настроек

    ldi temp,0b11110100 ;Запись значения старшего разряда для сравнения, канал A
    out OCR1AH,temp ;Установка настроек таймера
    ldi temp,0b00100100 ;Запись значения младшего разряда для сравнения, канал A
    out OCR1AL,temp ;Установка настроек таймера

    ldi temp,0b00000000 ;Очистка значений счётчика
    out TCNT1H,temp ;Сброс значения старшего разряда
    out TCNT1L,temp ;Сброс значения младшего разряда

    ldi temp,0b00010000 ;Сброс флага прерывания по совпадению, канал A
    out TIFR,temp ;Установка значения

    sei ;Глобальное разрешение прерываний

    ;Бесконечный цикл
    INFINITY2:
    ;Проверка флага разрешения прерываний
    brbc 7,PC+2 ;Проверка бита (SREG) разрешения прерываний, если сброшен пропуск команды
    rjmp INFINITY2 ;Переход к началу цикла

    DIV:
    ;Присвоение значений переменным
    ldi Thousands, 5 ;Присвоение значения «5»
    ldi Hundreds, 5 ;Присвоение значения «5»
    ldi Tens, 5 ;Присвоение значения «5»
    ldi Ones, 5 ;Присвоение значения «5»

    ;Передача на дисплей
    mov temp, Thousands ;Записываем в временную переменную значение тысяч
    rcall CODES ;Переход к выбору кода соответствующего значения для передачи на индикатор
    rcall DISPLAY ;Переход к передаче значения на дисплей по SPI

    mov temp, Hundreds ;Записываем в временную переменную значение сотен
    rcall CODES ;Переход к выбору кода соответствующего значения для передачи на индикатор
    rcall DISPLAY ;Переход к передаче значения на дисплей по SPI

    mov temp, Tens ;Записываем в временную переменную значение десятков
    rcall CODES ;Переход к выбору кода соответствующего значения для передачи на индикатор
    rcall DISPLAY ;Переход к передаче значения на дисплей по SPI

    mov temp, Ones ;Записываем в временную переменную значение единиц
    rcall CODES ;Переход к выбору кода соответствующего значения для передачи на индикатор
    rcall DISPLAY ;Переход к передаче значения на дисплей по SPI

    rjmp START ;Переход к началу основной программы

    ;********************************************************************
    .exit ;Конец

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

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

    на примере attiny2313 и часового кварца. у него как раз 2 таймера 8 битный и 16ти. сам контроллер допустим тикает от своего внутреннего генератора на пониженной частоте 1МГц. (на ассамблере).

    1) как я понял, на первый таймер я вешаю часовой кварцс это 14 и 9 ноги контроллера. или можно повешать его на XTAL1 и XTAL и с них снимать значение?

    2) как сделать чтоб было прерывание по срабатыванию этого таймера 1 раз в секунду, а затем выполнялась какая-то команда/подпрограмма?

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

    4) если кого не затруднит, можно ли парочку простеньких примеров реализации
    а) как посчитать входящие извне импульсы (режим счетчика)
    б) «Выдавать прерывания (по полудесятку разных событий) и устанавливать флаги», я так понимаю по 1му таймеру можно не 1 прерывание делать?! тоесть можно сделать первое прерывание 1 раз в секунду, и второе например раз в 0,1 секунду?! если да то примерчик…

    Заранее Спасибо. ивините что столько много спросил… просто больше незнаю у кого спросить, а то «немного» запутался в таймерах..

  60. Доброго времени суток!
    Возникла такая проблема:
    Использую Attiny2313, задействованы оба таймера, ТС0 работает с шиной 1-Wire, TC1 — управляет двумя каналами симмисторного регулятора мощности.
    Нужно независимо регулировать мощность в каждом канале, для этого в регистры OCR1A и OCR1B заносим время задержки для каждого канала, запускаем ТС1. При совпадении вызывается прерывание в котором включаем выход МК. При возникновении внешнего прерывания INT0 (от детектора нуля) выключаем выход МК. Таким образом, при минимальной задержке канал включается сразу после перехода напряжения через ноль (максимальная мощность), при максимальной задержке канал включается в самом конце полупериода напряжения, или вообще не включается, так как ТС не успевает досчитать до совпадения данных в регистрах TCNT1 и OCR1A(B) (минимальная мощность).

    Вопрос 1:
    При сбросе флагов от ТС1 в регистре TIFR сбрасываются флаги и от ТС0! Как это обойти?
    То есть конструкция, типа
    in temp1,TIFR
    sbi temp1,(1<<OCF1A)
    out TIFR,temp1
    сбрасывает регистр флагов TIFR полностью.
    также регистр ведет себя при записи в него $00
    clr temp1
    out TIFR,temp1
    Адрес регистра TIFR — $38, поэтому команды CBI и SBI с ним не работают…

    Вопрос 2:
    Отказываемся от прерываний и работаем с выходами OC1A(B).
    Для этого, так же загружаем в регистры OCR1A и OCR1B время задержки для каждого канала, выставляем биты COM1A(B) так, чтобы при совпадении канала включался выход МК, здесь все ясно.
    А вот дальше то что?
    Нужно при возникновении внешнего прерывания INT0 выключить выход МК. Но, поскольку выходы OC1A(B) работают как альтернативная функция порта, то по идее управление регистром PortB не работает.
    Нашел datasheet описание флагов FOC1A(B) регистра TCCR1C, но как они работают не понял…

    А может кто-то подскажет вообще другой алгоритм управления яркостью. Например ШИМ, но при этом он должен синхронизироваться по INT0…

    Заранее благодарю за ответы и советы…

    1. Вопрос решился, для тех кто будет копать в этом направлении опишу как я его решал.

      1. Настраиваем переферию

      ; === Инициализация портов ввода-вывода

      outi DDRB, 0b00011000 ; B3 -> выход управления каналом 1 [выход 0]
      outi PortB,0b00000000 ; B4 -> выход управления каналом 2 [выход 0]

      ; === Инициализация таймера/счетчика 1
      ; Таймер используется для отсчета времени вкючения симмисторов
      ; Режим работы — Normal

      out16 OCR1A,TFull ; записываем начальные установки времени
      out16 OCR1B,TFull ; в оба канала ТС1
      /* При частоте кварца 8МГЦ, предделителе ТС1=8 и времени полупериода сетевого напряжения = 10мс
      Tfull=8000000/8/100+10=10010 ($271A), +10 нужно для того, чтобы таймер считал немного больше чем 10мс, тогда при мощности 0% на выходе МК не будет даже коротких управляющих импульсов.
      */
      sbrp TCCR1A,(3<<COM1A0) ; при совпадении включить каналы
      sbrp TCCR1A,(3<<COM1B0) ;
      /* Два чудесных флажка переключающие ногои МК на альтернативную функцию, теперь PB3 и PB4 выходы каналов А и В ТС1, причем если происходит совпадение данных в регистрах OCR1A(B) и TCNT1, то выходы включаются, OC1A(В)=1. То есть не нужно разрешать прерывания по совпадению, потом их обрабатывать устанавливая ножки в 0…
      */

      2. У нас есть замечательное внешнее прерывание INT1, на которое приходят сигналы от детектора нуля.

      ; === Инициализация внешних прерываний ================================================
      ; Прерывание INT0 используется для работы с сетью 1-Wire
      ; Прерывание INT1 синхронизирует работу регулятора мощности

      sbrp MCUCR,(1<<ISC01) ; прерывания INT0(1-Wire) по спаду
      sbrp MCUCR,(1<<ISC11) ; прерывания INT1(ZCD) по спаду

      3. При переходе синусойды напряжения через 0, вызывается прерывание INT1. Его обработчик должен синхронизировать ТС1 и однозначно выключить оба канала регулятора мощности OC1A(В)=0!

      ; ————————————————————————————-
      ; Прерывание от детектора нуля
      ; ————————————————————————————-

      Detect_zero: ;

      pushr ; сохранить регистры в стек
      push temp2 ;

      clr16 TCNT1 ; сбросить ТС1
      sbrp TCCR1B,(1<<CS11) ; перезапускаем ТС1 с предделителем 8

      /* со сбросом все понятно, а вот запуск\перезапуск производится имеено здесь для того, чтобы лампы каналов не мигали при первом включении напряжения питания
      */
      outi TCCR1A,$A0 ; при совпадении выключить каналы
      outi TCCR1C,$C0 ; эмулируем совпадение
      outi TCCR1A,$F0 ; при совпадении включить каналы

      /* А вот тут начинается самое интересное.
      Если нодходить классически (обработка прерываний по совпадению), то в этом месте нужно сначала сбросить флаги OCF1A(B) в регистре TIFR, а затем разрешить прерывания OCIE1A(B) в регистре TIMSK, с этим и была засада. Кроме того еще нужно сбросить выходы управления каналами (PB3 и PB4 = 0).
      Что же сделал я…
      В регистре TCCR1C есть два волшебных флага FOC1A и FOC1B, это флаги принудительного изменения состояния выходов OC1A и OC1B. При записи "1" в эти флаги выходы ТС1 переключаются в соответствии с установами битов COM1A(B) регистра TCCR1A. То есть для того чтобы однозначно выключить выходы каналов необходимо:
      — сконфигурировать биты COM1A(B) так, чтобы при возникновении совпадения выходы выключились
      outi TCCR1A,$A0 ; при совпадении выключить каналы
      — принудительно изменить состояние выходов выключив их, для этого записываем "1" в соответствующие биты OCF1A(B)
      outi TCCR1C,$C0 ; эмулируем совпадение
      — ну и не забываем вернуть настройки битов COM1A(B) назад, чтобы при совпадении каналов выходы управления включались (PB3 и PB4 = 1)
      outi TCCR1A,$F0 ; при совпадении включить каналы
      */
      … ну и дальше по тексту …

      !!!ВНИМАНИЕ!!!
      Этот кусок программы не работатет в эмуляторах AVRStudio и Proteus, в железе все прекрасно работает.

  61. Здраствуйте я изучаю pic контроллеры . Прочитал даташит как использовать таймер tmr0 в принципе все понятно. Только вопрос заключается в том для чего нужен предварительный делитель пресскайлер допустим мы задаем ему коэффициент, а он а таймер инкрементируется в зависимости от коэффициента в единицу времени т.е как бы делит тактовый сигнал от генератора ? т.е если коэфф 1к1 то инкремент будет проходит каждый маш.цикл я правильно понимаю ?

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

  62. Доброго времени суток уважаемый DI HALT и форумчане!
    Хочу обратиться к вам с вопросом:
    Есть плата на основе atmaga8, кварц 12МГц, пишу на Си в CodeVision. Плата должна генерировать дискретную развертку частоты от 20 Гц до 20 кГц и перед каждым кадром (их где-то 15 шт.) развертки выдавать строб синхронизации, чтобы система сбора дынных включалась на другом конце системы. Таблица кадров принимается с ПК, лежит массивом в еепром.
    Так вот, развертка работает, стробы есть, но не такие точные как хотелось бы. Их длительность 1 мс, а время между импульсом и началом кадра, как мне мой цифровой осцилл кажет, каждый раз разное — от 3 до 14 мс. Для нашей системы сбора данных плохо.
    Опыт программирования небольшой. Сделал что называется напрямую.
    Вот кусок кода.
    Убедительная просьба указать ошибку или, если есть, более изящное решение. Заранее спасибо.

    void Razvertka_Freq (short dlina)
    { /*Блок развертки частоты*/
    /***************************************************************************/
    TCCR1A=0x40; // включаем режим СТС
    TCCR1B=0x08; // и остановливаем счетчик

    cadrs = number_data_byte/4;
    lcd_clear(); //выведем значение на LCD
    lcd_gotoxy(0,0);
    lcd_puts(«Frame:»);
    itoa(cadrs, lcd);
    lcd_puts(lcd);
    delay_ms(1000);
    PORTB.0=1; //строб на начало кадра (!!!)
    delay_ms(1);
    PORTB.0=0;

    for (k=0; k<dlina; ) //выполняем пока каретка массива не дойдет до последнего элемента
    {
    ReceiveFreq = (mass[k] << 8); //объединяем в переменную первые два элемента с принятой частотой
    ReceiveFreq |= mass[++k];

    lcd_clear(); // отобразим текущее значение частоты
    lcd_puts("F:");
    sprintf(lcd, "%d", ReceiveFreq);
    // itoa(ReceiveFreq,lcd);
    lcd_puts(lcd);
    lcd_puts("Hz");

    PORTB.2=1; // выдаем строб на каждое значение частоты (!!!)
    delay_ms(1);
    PORTB.2=0;

    if (ReceiveFreq > 8; //записывам полученный значения в регистры сравнения
    OCR1AL = top & 0xff;

    time = (mass[++k] << 8) | mass[++k]; //берем следующие два байта, пишем значение времени в переменную(0хТТ)

    lcd_gotoxy(0,1); // текущее значение задержки на LCD
    lcd_puts("T:");
    itoa(time,lcd);
    lcd_puts(lcd);
    lcd_puts("ms");

    delay_ms(time);

    TCCR1B=0x08; //остановить счетчик
    k++; //чтоб нулевой индекс тоже брал
    }
    }

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

      А чтобы понять где лаг прогони в симуляторе AVR Studio (туда можно coff файл из кодвижна засунуть) и посмотришь где у тебя лишние такты набегают в разных стробах.

      1. В AVR моделировал уже, там вообще порядка 1 мс гладко. Прерывание только от USART, с компом связываюсь только в начале. Lcd_puts`ы идут до строба, значит не должны мешать. Получается пауза во время условия if записи в регистры таймера нового значения. А как на автомат на таймере сварганить? Знаю, что delay гадкая штука, но по другому не вижу. Второй что-ли таймер запустить и по нему синхронизироваться?

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

          Пример автомата:
          http://easyelectronics.ru/upravlenie-mnozhestvom-servomashinok.html

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

          1. Так, действительно, похоже предделитель капризничает. Спасибо за советы и статья кстати отличная. Продолжаю работать

  63. Вот разбираюсь потихоньку
    http://easyelectronics.ru/repository.php?act=view&id=106
    Возник вопрос — когда нажимаю кнопку, светодиод может быстро загореть и погаснуть, загореться, светить чуть тусклее, если нажал, но не погаснуть. Почему так? Есть подозрения, что обработка нажатия срабатывает несколько раз, но хотелось бы услышать Ваш вердикт.
    Заранее спасибо.

    1. Или подскажите, пожалуйста, как с помощью одной кнопки сделать переключение состояний светодиода. Я чего-то никак не соображу. :(

  64. протрасировал в студии весь код этого урока ,так и не понял для чего в обработчике прерывания надо прятать в стэк р17 р18и р19.. они же нигде не используются. в чем прикол?

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

  65. Доброго дня! Такая проблема. Мне нужно сделать задержку в 40 секунд( чтобы светодиод горел эти 40 секунд), с учетом того,что кварцевый генератор — 4МГц. Как это сделать?

  66. Жаль, нельзя удалить предыдущий вопрос. С ним разобрался))
    А вот как, при использовании ATtiny2313 инициализировать счетчик сравнения OCR1BH/L ?

  67. Здравствуйте. Может кто нибудь сказать, есть ли смысл в этих строках, идущих друг за другом в одной программе?
    Clr r16
    Out tccr1a,r16; для atmega8.
    Ведь там и так все 0?

    И настройка собаки:
    Ldi r16,(1<<wdce)|(1<<wde)
    Out wdtcr,r16
    Ldi r16,0b00001111
    Out wdtcr,r16
    Ведь после последней стр ldir16,0b00011000 анигилируется

    1. А кто сказал, что в R16 всегда ноль? Это только Си компилятор обычно (Но не всегда!) держит в R16 ноль для удобства. А так это обычный РОН и там может быть что угодно.
      Дело в том, что настройки собаки вещь критичная. Поэтому конфигурируется она хитрыми образом. Т.е. вначале надо поставить биты WDCE и WDE, а потом в течении 4 тактов успеть закинуть уже то, что нам нужно.

  68. Спасибо. Как раз эту строчку про собаку читаю в datasheete )
    Раза три перечитал)
    А на счет настройки таймера1, tccr1a, во всех битах, по умолчанию 0, или я не понимаю?

  69. Объясните пожалуйста, что именно делает вот этот макрос SETB
    Я задаю такие значения:
    var: .byte 1
    SETB var, 3, r16

    В результате в var ничего не меняется, т.е. третий байт не устанавливается.

    Макрос из вашего листинга:
    ;SET BIT with REG
    .MACRO SETB
    .if @0 < 0x20 ; Low IO
    SBI @0,@1
    .else
    .if @0<0x40 ; High IO
    IN @2,@0
    ORI @2,1<<@1
    OUT @0,@2
    .else ; Memory
    LDS @2,@0
    ORI @2,1<<@1
    STS @0,@2
    .endif
    .endif
    .ENDM

    1. Оно ставит биты. Там три варианта действий. Если адрес меньше 0х20 то это пространство ио и там делается подстановка

      SBI var,3 — работает только для портов и некоторых байтов конфигурации периферии. Не всех. Если адрес не больше 0x40, то ставит бит через команды IN OUT и битовые маски.

      Но со временем периферии в авр стало так много, что в новых контроллерах многие регистры IO стали размещены в памяти Memory mapped. И для обращения с ними нужны команды LD ST Групп. Это все регистры старше 0х40 адреса.

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

      LDS R16,var
      ORI R16,1<<3 STS var,R16 Только переменная var у тебя в .dseg расположена? Не забыл директиву разметки памяти?

    1. Тогда у тебя какая то беда с макросами. Там два компилятора аврассемблер 1 и авр ассемблер 2 какой то из них не умеет в макросы.

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

Ваш e-mail не будет опубликован.

Перед отправкой формы:
Human test by Not Captcha