AVR. Учебный курс. Подпрограммы и прерывания

Распечатать

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

Вот, например, кусок кода, передающий в регистр UDR байты с некоторой выдержкой, выдержка делается за счет вращения бесконечного цикла:

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
	.CSEG
	LDI R16,Low(RAMEND)	; Инициализация стека
	OUT SPL,R16		; Обязательно!!!
 
	LDI R16,High(RAMEND)
	OUT SPH,R16
 
	.equ	Byte 	= 50
	.equ 	Delay 	= 20
 
	LDI	R16,Byte	; Загрузили значение
Start:	OUT	UDR,R16		; Выдали его в порт
 
	LDI	R17,Delay	; Загрузили длительность задержки
M1:	DEC	R17		; Уменьшили на 1
	NOP			; Пустая операция
	BRNE	M1		; Длительность не равна 0? Переход если не 0
 
	OUT	UDR,R16		; Выдали значение в порт
 
	LDI	R17,Delay	; Аналогично
M2:	DEC	R17
	NOP
	BRNE	M2
 
	OUT	UDR,R16
 
	LDI	R17,Delay
M3:	DEC	R17
	NOP
	BRNE	M3
 
	RJMP	Start		; Зациклим программу

Сразу напрашивается повторяющийся участок кода вынести за скобки.

1
2
3
4
	LDI	R17,Delay
M2:	DEC	R17
	NOP
	BRNE	M2

Для этих целей есть группа команд перехода к подпрограмме CALL (ICALL, RCALL, CALL)
И команда возврата из подпрограммы RET

В результате получается такой код:

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
	.CSEG
	LDI R16,Low(RAMEND)	; Инициализация стека
	OUT SPL,R16		; Обязательно!!!
 
	LDI R16,High(RAMEND)
	OUT SPH,R16
 
	.equ	Byte 	= 50
	.equ 	Delay 	= 20
 
	LDI	R16,Byte	; Загрузили значение
Start:	OUT	UDR,R16		; Выдали его в порт
 
	RCALL 	Wait
 
	OUT	UDR,R16
	RCALL 	Wait
	OUT	UDR,R16
	RCALL 	Wait	
	OUT	UDR,R16
	RCALL 	Wait
	RJMP 	Start		; Зациклим программу. 
 
 
Wait: 	LDI	R17,Delay
M1:	DEC	R17
	NOP
	BRNE	M1
	RET

Как видишь, программа резко сократилась в размерах. Теперь скопируй это в студию, скомпилируй и запусти на трассировку. Я хочу показать как работает команда RCALL и RET и при чем тут стек.

Вначале программа, как обычно, инициализирует стек. Потом загружает наши данные в регистры R16 и выдает первый байт в UDR… А потом по команде RCALL перейдет по адресу который мы присвоили нашей процедуре, поставив метку Wait в ее начале. Это понятно и логично, гораздо интересней то, что произойдет в этот момент со стеком.

До выполнения RCALL

Увеличить
Адрес команды RCALL в памяти, по данным PC = 0×000006, адрес следующей команды (OUT UDR,R16), очевидно, будет 0×000007. Указатель стека SP = 0x045F — конец памяти, где ему и положено быть в этот момент.

После RCALL

Увеличить
Смотри, в стек пихнулось число 0×000007, указатель сместился на два байта и стал 0x045D, а контроллер сделал прыжок на адрес Wait.

Наша процедура спокойно выполняется, как ей и положено, а по команде RET процессор достанет из стека наш заныченный адрес 0×000007 и прыгнет сразу же на команду OUT UDR,R16

Таким образом, где бы мы не вызвали нашу процедуру Wait — мы всегда вернемся к тому же месту откуда вызвали, точнее на шаг вперед. Так как при переходах в стеке сохраняется адрес возврата. А если испортить стек? Взять и засунуть туда еще что нибудь? Подправь процедуру Wait и добавь туда немного бреда, например, такого

1
2
3
4
5
6
7
8
Wait: 	LDI	R17,Delay
M1:	DEC	R17
	NOP
	BRNE	M1
 
	PUSH	R17		; Ой, я не специально!
 
	RET

Перекомпиль и посмотри что будет =) Заметь, компилятор тебе даже слова не скажет. Мол все путем, дерзай :)

До команды PUSH R17 в стеке будет адрес возврата 00 07, так как в регистре R17 ,в данный момент, ноль, и этот ноль попадет в стек, то там будет уже 00 00 07.

А потом идет команда RET… Она глупая, ей все равно! RET тупо возьмет два первых верхних байта из стека и запихает их в Programm Counter.

И куда мы перейдем? Правильно — по адресу 00 00, в самое начало проги, а не туда откуда мы ушли по RCALL. А будь в R17 не 00, а что нибудь другое и попади это что-то в стек, то мы бы перешли вообще черт знает куда с непредсказуемыми последствиями. Это и называется срыв стека.

Но это не значит, что в подпрограммах нельзя пользоваться стеком в своих грязных целях. Можно!!! Но делать это надо с умом. Класть туда данные и доставать их перед выходом. Следуя железному правилу «Сколько положил в стек — столько и достань!», чтобы на выходе из процедуры для команды RET лежал адрес возврата, а не черти что.

Мозговзрывной кодинг
Да, а еще тут возможны стековые извраты. Кто сказал, что мы должны вернуться именно туда откуда были вызываны? =))) А если условия изменились и по итогам вычислений в процедуре нам ВНЕЗАПНО туда стало не надо? Никто не запрещает тебе нужным образом подправить данные в стеке, а потом сделать RET и процессор, как миленький, забросит тебя туда куда надо. Легко!

Более того, я когда учился в универе и сдавал лабы по ассемблеру, то лихо взрывал мозги нашему преподу такими конструкциями (там, правда, был 8080, но разница не велика, привожу пример для AVR):

1
2
3
4
5
6
7
8
9
	LDI	R17,low(M1)
	PUSH	R17
	LDI	R17,High(M1)
	PUSH	R17
 
; потом дофига дофига другого кода... для отвлечения
; внимания, а затем, в нужном месте, ВНЕЗАПНО
 
	RET

И происходил переход на метку M1, своего рода извратский аналог RJMP M1. А точнее IJMP, только вместо Z пары мы используем данные адреса загруженные в стек из любого другого регистра, иногда пригождается. Но без особой нужды таким извратом заниматься не рекомендую — запутывает программу будь здоров.

Но побалуйся обязательно, чтобы во всей красе прочувствовать стековые переходы.

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

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

Подпрограммы vs Макросы
Но не стоит маникально все повторяющиеся участки заворачивать в подпрограммы. Дело в том, что переход и возврат добавляют две команды, а еще у нас идет прогрузка стека на 2 байта. Что тоже не есть гуд. И если заменяется три-четыре команды, то овчинка с CALL-RET не стоит выделки и лучше запихать все в макрос.

Прерывания

Это аппаратные события. Ведь у микроконтроллера кроме ядра есть еще дофига периферии. И она работает параллельно с контроллером. Пока контроллер занимается вычислением или гоняет байтики по памяти — АЦП может яростно оцифровывать входное напряжение, USART меланхолично передавать или принимайть байтик, а EEPROMка неспеша записывать в свои тормозные ячейки очередной байт.

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

Но некоторые события в принципе не могут ждать, например USART — вовремя не обработаешь входящий байт, считай провафлил передачу, т.к. передающий девайс пошлет второй, ему плевать успел ты его обработать или нет. Для таких срочных дел есть прерывания.

У AVR этих прерываний с полтора десятка наберется, на каждое переферийное устройство по прерыванию, а на некотрые и не по одному. Например, у USART их целых три — Байт пришел, Байт ушел, Передача завершена.

Как это работает

Когда случается прерывание, то процессор тут же завершает текущую команду, пихает следующий адрес в стек (точно также как и при CALL) и переходит… А куда, собственно, он переходит?

А переходит он на фиксированный вектор прерывания. За каждым аппаратным прерыванием закреплен свой именной адрес. Все вместе они образуют таблицу векторов прерывания. Расположена она в самом начале памяти программ. Для Атмега16, используемой в Pinboard таблица прерываний выглядит так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
RESET		0x0000	; Reset Vector
INT0addr	0x0002	; External Interrupt Request 0
INT1addr	0x0004	; External Interrupt Request 1
OC2addr		0x0006	; Timer/Counter2 Compare Match
OVF2addr	0x0008	; Timer/Counter2 Overflow
ICP1addr	0x000a	; Timer/Counter1 Capture Event
OC1Aaddr	0x000c	; Timer/Counter1 Compare Match A
OC1Baddr	0x000e	; Timer/Counter1 Compare Match B
OVF1addr	0x0010	; Timer/Counter1 Overflow
OVF0addr	0x0012	; Timer/Counter0 Overflow
SPIaddr		0x0014	; Serial Transfer Complete
URXCaddr	0x0016	; USART, Rx Complete
UDREaddr	0x0018	; USART Data Register Empty
UTXCaddr	0x001a	; USART, Tx Complete
ADCCaddr	0x001c	; ADC Conversion Complete
ERDYaddr	0x001e	; EEPROM Ready
ACIaddr		0x0020	; Analog Comparator
TWIaddr		0x0022	; 2-wire Serial Interface
INT2addr	0x0024	; External Interrupt Request 2
OC0addr		0x0026	; Timer/Counter0 Compare Match
SPMRaddr	0x0028	; Store Program Memory Ready

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

Запишем эту бодягу в цивильной форме, через ORG и добавим немного кода.

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
         .CSEG
         .ORG $000        ; (RESET) 
         RJMP   Reset
         .ORG $002
         RETI             ; (INT0) External Interrupt Request 0
         .ORG $004
         RETI             ; (INT1) External Interrupt Request 1
         .ORG $006
         RETI		    ; (TIMER2 COMP) Timer/Counter2 Compare Match
         .ORG $008
         RETI             ; (TIMER2 OVF) Timer/Counter2 Overflow
         .ORG $00A
         RETI		    ; (TIMER1 CAPT) Timer/Counter1 Capture Event
         .ORG $00C
         RETI             ; (TIMER1 COMPA) Timer/Counter1 Compare Match A
         .ORG $00E
         RETI             ; (TIMER1 COMPB) Timer/Counter1 Compare Match B
         .ORG $010
         RETI             ; (TIMER1 OVF) Timer/Counter1 Overflow
         .ORG $012
         RETI             ; (TIMER0 OVF) Timer/Counter0 Overflow
         .ORG $014
         RETI             ; (SPI,STC) Serial Transfer Complete
         .ORG $016
         RJMP   RX_OK     ; (USART,RXC) USART, Rx Complete
         .ORG $018
         RETI             ; (USART,UDRE) USART Data Register Empty
         .ORG $01A
         RETI             ; (USART,TXC) USART, Tx Complete
         .ORG $01C
         RETI		    ; (ADC) ADC Conversion Complete
         .ORG $01E
         RETI             ; (EE_RDY) EEPROM Ready
         .ORG $020
         RETI             ; (ANA_COMP) Analog Comparator
         .ORG $022
         RETI             ; (TWI) 2-wire Serial Interface
         .ORG $024
         RETI             ; (INT2) External Interrupt Request 2
         .ORG $026
         RETI             ; (TIMER0 COMP) Timer/Counter0 Compare Match
         .ORG $028
         RETI             ; (SPM_RDY) Store Program Memory Ready
 
	 .ORG   INT_VECTORS_SIZE      	; Конец таблицы прерываний
 
;----------------------------------------------------------------------
; Это обработчик прерывания. Тут, на просторе, можно наворотить сколько
; угодно кода. 
RX_OK:	 IN 	R16,UDR		; Тут мы делаем что то нужное и полезное
 
	 RETI			; Прерывание завершается командой RETI 
;----------------------------------------------------------------------
 
 
Reset:  LDI R16,Low(RAMEND)	; Инициализация стека
	 OUT SPL,R16		; Обязательно!!!
 
	 LDI R16,High(RAMEND)
	 OUT SPH,R16
 
	 SEI			; Разрешаем прерывания глобально
	 LDI   R17,(1<<RXCIE)	; Разрешаем прерывания по приему байта
	 OUT 	UCSRB,R17
 
M1:	 NOP			
	 NOP
	 NOP
	 NOP
	 RJMP M1

Теперь разберем эту портянку. Контроллер стартует с адреса 0000, это точка входа. Там мы сразу же делаем бросок на метку RESET. Если это не сделать, то контроллер пойдет выполнять команды из таблицы векторов, а они не для того там посажены. Да и не далеко он ускачет — без инициализации стека и наличия адреса возврата в нем первый же RETI вызовет коллапс. Ведь RETI работает почти также как и RET.

Поэтому сразу уносим оттуда ноги на RESET. Где первым делом инициализируем стек. А потом, командой SEI, разрешаем прерывания. И установкой бита в регистре периферии UCSRB включаем прерывание по приему байта.

Дальше зацикливаемся и ждем когда в приемный буффер USART извне свалится байт. Запускай это дело в эмуляцию и начинай трассировать по одной команде. Сначала, как я и говорил, проц прыгнет на метку Reset, потом выставит нужные значения и наглухо зациклится на

1
2
3
4
5
M1:	 NOP			
	 NOP
	 NOP
	 NOP
	 RJMP M1

До прихода байта. Но как же нам осуществить этот приход байта если весь наш эксперимент не более чем симуляция виртуального процессора в отладчике? А очень просто!

Вручную вписать этот байт в регистр и вручную же протыкать флаг, словно байт пришел. За прием байта отвечает флаг RXC в регистре периферии UCSRA, раздел USART. Найди там бит RXC и тыкни его, чтобы закрасился. Все, прерывание вроде как наступило.

Нажми F11, чтобы сделать еще один шаг по программе… Опа, стрелочка улетела в таблицу векторов, как раз на вектор

1
2
	.ORG $016
        RJMP   RX_OK     ; (USART,RXC) USART, Rx Complete

А оттуда уже прыжок сразу же на метку RX_OK, где мы забираем данные из регистра UDR в R17 и выходим из прерывания, по команде RETI.

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

Вот, как это было, если по коду:

Увеличить

Вот, вроде теперь вопросов по выполнению быть не должно.

Разрешение и запрещение прерываний
Прерываний много, но по умолчанию они все запрещены. Во-первых, глобально — есть в процессоре в регистре SREG (о нем чуть позже) флаг I (interrupt) когда он равен 0, то все прерывания запрещены вообще, глобально, все без исключения.

Когда он равен 1, то прерывания глобально разрешены, но могут быть запрещены локально.

Устанавливается и сбрасывается этот флаг командами

  • SEI — разрешить прерывания
  • CLI — запретить прерывания (Да, по поводу моего ника, DI это, кстати, то же самое что и CLI, но для процессора Z80 ;) )

Кроме того, у каждого прерывания есть еще свой собственный бит локального разрешения. В примере с UDR это бит RXCIE (Receive Complete Interrupt Enable) и расположены они в портах ввода вывода

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

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

Если все неиспользуемые прерывания заглушены командой RETI то ничего не пройзойдет — как пришел так и вернется обратно. Если же там ничего нет, то процессор будет выполнять эту пустоту пока не доберется до живого кода. Это может быть как переход на обработчик другого прерывания (ниже по таблице векторов) так и начало основного кода, тело обработчика прерывания, какая либо процедура, записанная до метки start. Да что угодно, что первой попадется.

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

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

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

О том, что прерывание произошло извещает флаг этого прерывания в регистре ввода вывода. Пока флаг поднят считается что прерывание не обработано и процессор будет пытаться по этому прерыванию перейти (если конечно прерывания разрешены и локально, и глобально).

Флаг этого события сбрасывается либо сам, при переходе к обработчику прерывания, либо при совершении какого-либо действия. Например, чтобы сбросить флаг события прерывания RxC надо считать байт из UDR. Протрассируй программу и сам увидишь, что сброс флага RxC происходит после выполнения команды

1
IN R16,UDR

Либо флаг скидывают вручную — записью в этот флаг единицы. Не нуля! Единицы! Подробней в даташите на конкретную периферию.

Очередность прерываний
Но что будет если произошло одно прерывание и процессор ушел на обработчик, а в этот момент произошло другое прерывание?

А ничего не будет, при уходе на прерывание происходит аппаратный глобальный запрет всех других прерываний— просто сбрасывается флаг I, а по команде RETI, при возврате, этот флаг устанавливается в 1. В этом, кстати, отличие RET от RETI

Но! Никто не запрещает нам внутри обработчика поставить SEI и разрешить прерывания. При этом мы получим вложенные прерывания. Можно, но это опасно. Черевато переполнением стека и прочими гадостями. Так что это надо делать с твердым осознанием последствий.

Что тогда? Прерывание которое пришло во время обработки первого потеряется?

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

А теперь такая ситуация — прерывания запрещены, неважно по какой причине, и тут приходит два прерывания. Поднимает каждая свой флажок и оба ждут глобального разрешения. SEI!!! Кто пойдет первым? А первым пойдет то прерывание, чей вектор меньше по адресу, ближе к началу памяти. За ним второй.

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

Бег по граблям
Прерывания штука мощная, но опасная. С их помощью плодятся такие глюки, по сравнению с которыми стековые срывы так — семечки.

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

Возникает плавающий глюк, появление которого зависит от того в каком именно месте кода произойдет вызов прерывания. Что поймать, сам понимаешь, почти невозможно.

Так что если у МК то понос, то золотуха, то программа петухом поет, а то молчит как рыба — знай, в 95% копать собаку надо в районе прерываний и их обработчиков.

Но если правильно написать прерывание, то багов оно не даст. Главное понимать в чем его опасность.

Грабли первые — спасай регистры!!!
Прерывание, когда оно разрешено, вызывается ВНЕЗАПНО, между двумя произвольными инструкциями кода. Поэтому очень важно, чтобы к моменту когда мы из прерывания вернемся все осталось как было.

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

Приведу пример: Вот есть у нас обработчик прерывания который сравнивает байт на входе в USART и если он равен 10, выдает обратно отклик ‘t’ (ten в смысле).

1
2
3
4
5
6
7
8
9
10
11
RX_OK:
 
	IN	R16,UDR
	CPI	R16,10
	BREQ	Ten
	RJMP	Exit
 
Ten:	LDI	R17,'t'
	OUT	UDR,R17
 
Exit:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
         . . . 	
         LPM    R18,Z
         CPI    R18,0
 
         BREQ   ExitStr
 
         SUBI   R16,65
         LSL    R16
 
         LDI    ZL,Low(Ltrs*2)
         LDI    ZH,High(Ltrs*2)
 
         ADD    ZL,R16
         ADC    ZH,R1
 
         . . .

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

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
         . . . 	
         LPM    R18,Z
         CPI    R18,0
 
         BREQ   ExitStr
 
         SUBI   R16,65
>>>>>>>>>>>Прерывание >>>>>>>>>>>
RX_OK:
 
	IN	R16,UDR
	CPI	R16,10
	BREQ	Ten
	RJMP	Exit
 
Ten:	LDI	R17,'t'
	OUT	UDR,R17
 
Exit:	RETI
<<<<<<<<<<< Возврат <<<<<<<<<<<<<
         LSL    R16
 
         LDI    ZL,Low(Ltrs*2)
         LDI    ZH,High(Ltrs*2)
 
         ADD    ZL,R16
         ADC    ZH,R1
         . . .

До входа в прерывание, после команды SUBI R16,65 в R16 было какое то число из которого вычли 65.
И дальше с ним должны были провернуть операцию логического сдвига LSL R16, но тут вклинился обработчик, где в R16 записалось значение из UDR.

И мы выпали из обработчика перед командой LSL R16, но в R16 уже мусор какой то. Совершенно не те данные, что мы планировали.

Естественно вся логика работы от такого издевательства порушилась и возник глюк. Но стоит прерыванию прийти чуть раньше, на одну микросекунду — как глюк исчезнет, т.к. SUBI R16,65 будет уже после прерывания и логика не будет порушена, но может возникнуть другой глюк.

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

Чтобы такого не было в обязательно порядке надо сохранять регистры и SREG на входе в прерывание и доставать на выходе.

У нас тут, в обработчике, используется R17 и R16 и SREG (в него помещается результат работы команды CPI). Вот их и сохраним.

Выглдеть это будет так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
RX_OK:	PUSH	R16		; Сохранили R16
	IN	R16,SREG	; Достали SREG в R16
	PUSH	R16		; Утопили его в стеке
	PUSH	R17		; Туда же утопили R17
 
; Теперь можно со спокойной совестью работу работать. 
 
	IN	R16,UDR
	CPI	R16,10
	BREQ	Ten
	RJMP	Exit
 
Ten:	LDI	R17,'t'
	OUT	UDR,R17
 
; А на выходе вернем все как было. 
; Достаем в обратном порядке
 
Exit:	POP	R17
	POP	R16
	OUT	SREG	R16
	POP	R16
	RETI			; Спокойно выходим. Регистры вернул как было.

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

Грабли вторые — не тормози!!!
Прерывания отвлекают процессор от основных дел, более того, они блокируют другие прерывания. Поэтому в прерывании главное все сделать максимально быстро и свалить. Никаких циклов задержки, никаких долгоиграющих процедур. Никаких ожиданий аппаратного события. СКОРОСТЬ! СКОРОСТЬ! СКОРОСТЬ! Вот что должно тобой руководить при написании обработчика.

Заскочил — сделал — выскочил!

Но такая красивая схема возможна далеко не всегда. Иногда бывает надо по прерыванию делать и медленные вещи. Например, прием байтов из интерфейса и запись их в какую нибудь медленную память, вроде EEPROM. Как тогда быть?

А тут делают проще. Цель прерывания — во что бы то ни стало среагировать на событие именно в тот момент, когда оно пришло, чтобы не прозевать. А вот обрабатывать его прямо сейчас обычно и не обязательно.
Заскочил в обработчик, схватил быстро тухнущие данные, перекинул их в буффер где им ничего не угрожает, поставил где-нибудь флажок отметку «мол там байт обработать надо» и вышел. Все! Остальное пусть сделает фоновая программа в порядке общей очереди.

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

Грабли третьи — атомарный доступ
Есть ряд операций которые должны выполняться неразрывно. Например, чтение 16ти разрядных регистров таймера.

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

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

Поэтому перед чтением делаем CLI, а после SEI.

Также нельзя разрешать прерывания если одна и та же многоходовая операция делается и в прерывании и в главном цикле.

Например, прерывание хватает байты из АЦП и пишет их в буффер (ОЗУ), образуя связные цепочки данных. Главный же цикл периодически из этого буффера данные читает. Так вот, на момент чтения буффера из памяти, надо запрещать прерывания, чтобы никто не посмел в этот буффер что-нибудь записать.
Иначе мы можем получить невалидные данные — половина байтов из старого замера, половина из нового.

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

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

Обязательно погоняй в отладчике код (да хотя бы кучу NOP) с разными прерываниями. Чтобы понять и прочувствовать как ведут себя прерывания.

Старая версия статьи. Чисто поржать :)

Запись опубликована в рубрике AVR. Учебный курс с метками , , . Добавьте в закладки постоянную ссылку.

211 комментариев: AVR. Учебный курс. Подпрограммы и прерывания

  1. axf говорит:

    А есть в данной вариации ассемблера аналог команд PUSHAD/POPAD, сохраняющих в стек и достающих из него значения всех регистров общего назначения?

  2. Xenomorph говорит:

    Начал изучать прерывания опять же на С. И вот какое дело, обработчик прерываний у меня срабатывает когда на линии PB5 меняется уровень, в обработчеке у меня простой код если PB5 = 0 и PB6= 0 то пишу ноль, исли PB6=1 пишу еденицу. Так вот за 100 ms
    у меня проходит 100 едениц и нулей. Суть в чем если я просто посылаю по UART, то принимаю все биты. Но стоит мне записать биты в массив, а потом перевести их в число, как на выходе я получаю какую то биллиберду. Я вот думаю успевает ли выполнится обработчик прерывания, до возникновения другого прерывания? Или может я что не так делаю???

  3. sVk говорит:

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

    В строке 3 проц уходит на обработку по метке loc_Control_VUART2UART_05 (при T=0). Там (строка 35)он
    Вопрос 1: обнуляет бит VUART_RECEIVE регистра Status_VUART. Я прав?
    Вопрос 2: что значит 1<<?
    после чего: выходит из loc_Control_VUART2UART_05 и
    Вопрос 3: продолжает «копать дальше» строку 4 и далее следующие строки?
    ИЛИ
    Вопрос 4: выходит из Control_VUART2UART в строке 1?
    Еще общий вопрос: если рассматривать call и brxx. Обе они переходят по метке. Call переходит по метке процедуры, сохраняя в стек адрес команды, к которой надо будет вернуться после выполнения процедуры. Branch(как и jmp) тоже переходит по метке, но в стек ничего не сейвит. Я прав? Если так, то на вопрос 4 получается ответ ДА.

    И еще вопрос-для закрепления знаний =):
    Впорос 5: Если я , например, по строке 22 перешел в лок_3 в строке 33, то далее проц «копает» 34-ю, 35-ю, 36-ю и далее по 37-й строке выходит либо из главной процедуры либо в строку 23(если branch сейвит в stack, надеюсь что он так не делает)))

    1 Control_VUART2UART:
    2 rcall ChkUART_RTS
    3 brtc loc_Control_VUART2UART_05
    4 sbrs Status_VUART,VUART_RECEIVE
    5 ret
    6 out UDR,RX_VUART
    7 #ifdef TestMode
    8 mov tstUART,RX_VUART
    9 sbr Status_VUART,1<<TST_UART_RX
    10 #endif
    11 tst NextBaudRate
    12 breq loc_Control_VUART2UART_05
    13 tst Count_ChkSumm
    14 brne loc_Control_VUART2UART_01
    15 mov ChkSumm,RX_VUART
    16 cpi RX_VUART,0×82
    17 brne loc_Control_VUART2UART_05
    18 rjmp loc_Control_VUART2UART_04
    19 loc_Control_VUART2UART_01:
    20 ldi temp0,0×05
    21 cp Count_ChkSumm,temp0
    22 brne loc_Control_VUART2UART_03
    23 cpi RX_VUART,0×54
    24 brne loc_Control_VUART2UART_02
    25 cp ChkSumm,RX_VUART
    26 brne loc_Control_VUART2UART_02
    27 mov CurrBaudRate,NextBaudRate
    28 loc_Control_VUART2UART_02:
    29 clr NextBaudRate
    30 rjmp loc_Control_VUART2UART_05
    31 loc_Control_VUART2UART_03:
    32 add ChkSumm,RX_VUART
    33 loc_Control_VUART2UART_04:
    34 inc Count_ChkSumm
    35 loc_Control_VUART2UART_05:
    36 cbr Status_VUART,1<<VUART_RECEIVE ;
    37 ret

    • DI HALT говорит:

      1<<? это макрооператор языка, не команда процессора — задвинуть 1 влево на число «?», то есть мы делаем число в котором все нули, но один бит с номером «?» установлен в 1. Ищи чему равно «?» (это должно быть макроопределение каке нибудь из серии .equ)

      В строке 36 происходит обнуление бита VUART_RECEIVE в регистре Status_VUART

      А в строке 37 происходит возврат к точке откуда это подпрограмму вызвали и это не строка 4, а где то явно выше ее. В строке 3 проц просто прыгает (это не CALL!!!) на метку. Выйдет по строке 37 вообще из процедуры «Control_VUART2UART:» выше, в вызвающую прогу. Не на строку 1, а куда то туда, где последний раз был «RCALL Control_VUART2UART»

      Разницу между CALL и JMP/BRanch ты понял правильно. Так и есть.

      Вопрос 5:
      После бранча проц продолжает копать оттуда куда его послали. Возврата нет, ибо бранч не сохраняет адреса возврата.

      • sVk говорит:

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

        • DI HALT говорит:

          А что это за хрень? Судя по меткам это гонит из виртуального уарта (VUART судя повсему это виртуальный) в реальный. Из Ethernet делаем UART? или чо?

          • sVk говорит:

            Вообще вся схема — это преобразователь интерфейса диагностического порта автомобиля k-line в интерфейс Bluetooth. Сам бы рад разобратся что там куда гонит. Вся прога вместе с назначениями типа .def .equ около 650 строк. Надо разобратся за 3 дня, и потом по возможности еще в протеусе смоделить(((. AVR я изучаю ровно неделю)))), контроллеры всякие — месяц))). А VUART это как я пока думаю — UART со стороны блютусника.

            • DI HALT говорит:

              650 строк эт немного :))))) В протеусе вряд ли смоделишь. Слишком кривая среда для моделинга сложных прог. Разве что МК будет точно такой же какие там есть. В протеусе хорошо куски программ отлаживать. Какие то узлы проверять.

              3 дня это сильно. Если чо стучись в аську. Оперативней будет. Номер в инфо есть.

              • sVk говорит:

                Оки, вообще говоря, моя задача сейчас — просто нарисовать некое подобие блок-схем логики работы устройства. Комменты есть к каждой строке, так что с этим, думаю справлюсь,понимания особого это не требует. А вот с протоколами k-line и BT, AT-командами придется разобраться))

  4. borsh говорит:

    Все прочитал, осознал и задался вопросом: Зачем указывать в программе на конец стека ? Неужели есть задачи, где стек сдвинут от конца SRAM, а данные лежат после стека? Почему бы производителю на зашивать в указатель стека конец SRAM ???

    • DI HALT говорит:

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

    • SergeyDon говорит:

      про стек:
      на своём втором компе Z80 (это проц) со стеком делал так, ставил его на конец видеопамяти в аккумулятор = 0 и циклом пробегал push A. очень быстро очищал экран, а затем стек на место и работаем дальше.

      тут правда ещё не придумал как использовать!

      есть вопрос мнемоники команд асма для АВР написанны везде и количество тактов есть, а машинный код можно гдето глянуть? чисто из любопытства посмотреть, дизасемблер ведь както работает?
      например команда jmp XX в памяти занимает два байта или три? чет совсем уже все забыл :(

  5. testicq говорит:

    Легкий косячек:
    «В случае ассемблерной процедуры, тебе бы пришлось вначале нужно было подсунуть гуталин вместо зубной пасты»
    лишнее «нужно было»

  6. ZorG говорит:

    2 DI HALT:
    Ну вот и я добрался до своего первого комментария, хоть читаю твои статьи уже не первый год в Хакере :) Чувак — ты реально крут, спасибо тебе за статьи и этот ресус :)

    Теперь по делу. Чего-то нигде не нашел упоминания про внешние прерывания. У меня вот имеется AT90S2313, судя по заголовочнику в AVR для него, имеют место быть два внешних прерывания.

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

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

  7. portvein говорит:

    Можно глупый вопрос? =) А что делать, если есть некоторый цифровой датчик, генерирующий продолжительные по времени импульсы, эти импульсы МК считывает через INT0 (причём INT0 настроен на Any change, т.к. нужно посчитать продолжительность импульса) и выводит на сегментный индикатор динамически, т.е. косяк в том, что при возникновении прерывания индикация виснет на том что успело вылезти и продолжается только после спада.

    • DI HALT говорит:

      а из прерывания выход делается когда?

      По хорошему надо так:

      Прерывание настроено на 01 пришло — мы в прерывании запустили таймер, перенастроили прерывание на 10, вышли из прерывания.

      Прерывание снова пришло (конец импульса). Мы в прерывании сняли показания таймера, перенастроили прерывание снова на 01 и вышли.

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

      • portvein говорит:

        ага, данке шон))) Что-то я стормозил и считал время прямо в обработчике прерывания))))

        ЗЫЖ А можно по подробнее про фичи с прерываниями по любой из восьми ног? Я так понял на ATTINY2313 такая штука есть? Только что-то не один симулятор 2313 не симулирует((((

  8. tranzistor говорит:

    Цитирую: «В AVR из программы до счетчика никак не добраться». Немного поправлю, если позволите. В принципе, можно написать типа rjmp PC+2. Это конечно, не непосредственное обращение к счетчику команд, но все-таки. Хотя я такой прием юзаю редко… И кстати, если писать JMP PC+2, то он не перескочит через команду! Нужно прибавлять 4!
    А теперь можно вопрос? В третьей части в файле, где определяются векторы прерываний, стоят команды rjmp. Я вот запутался, блин. Почитал Ревича мельком ту главу где про различия для этих команд (rjmp и jmp) и сделал для себя вывод:что, мол, если работаешь с Мегой16, пиши всегда jmp и call. Не ошибешься! Размер меня не интересует, проги маленькие, места всем хватит…
    Прав ли я? Или даже в мегах иногда НЕОБХОДИМО использовать rjmp и rcall?
    Спасибо

    • DI HALT говорит:

      Ну это ты добираешься не до счетчика, а всего лишь до текущего адреса.

      Счетчик, кстати, можно изменить или получить (это вызовет переход) извратскими методами.

      Например таким образом: Делаем RCALL, а возвращаемся через RJMP при этом у нас в стеке окажется PC и его можно POP нуть в регистры :)

      В процедуре сразе же после RCALL перехода можно сделать:
      POP R16 достать адрес возврата
      POP R17
      MOV R18,R16 Скопировать его
      MOV R19,R17 в безопасное место
      PUSH R17 положить его на место.
      PUSH R16

      А можно не только скопировать но и хитро изменить! Тогда по RET мы выйдем не туда откуда зашли, а туда куда хотим. Редкостный изврат, но порой так прикольно :) За такие крышесносные хохмы я и люблю ассемблер :)
      Этаким образом можно замутить адский конечный автомат… ууухххх… но мозг взорвет сразу :)

      Ну и тому подобные извраты

    • mrkoin говорит:

      Так как вопрос остался неотвеченным:

      Или даже в мегах иногда НЕОБХОДИМО использовать rjmp и rcall?

      ИМХО, если вектор прерывания позволяет (по размеру) использовать JMP/CALL (которые длиннее RJMP/RCALL), то можно и их использовать.

      Соответственно, в Меге8 (видимо — я в даташит еще не смотрел :-) ) вектор прерывания имеет места всего на 2 байта, поэтому там просто приходиться использовать короткие переходы.

  9. Jyraf говорит:

    А подскажите пожалуйста!
    Программу пишу в AVRStudio, эмуляция в Proteuse.
    Устройство на ATtiny2313, программа разбита на несколько файлов:
    - define.asm — определение переменных и имен регистров…
    - init.asm — очистка памяти, регистров конфигурация портов…
    - macro.asm — макросы…
    - vectors.asm — таблица векторов прерываний…
    и основной файл — ttt.asm…
    Программа начинает выполнятся с метки Init:, расположенной в файле init.asm, где сбрасывает в ноль все регистры, RAM, указатель стека и инициализирует всю периферию.
    В основном файле программы, при нажатии на кнопку (внешнее прерывание), программа входит в бесконечный цикл (режим СТОП) или должна начаться с начала (СТАРТ).
    Для перехода в начало программы (СТАРТ) использую
    rjmp Init
    Так вот, в AVRStudio при пошаговом прогоне все работает правильно, а вот в Proteuse, контроллер тупо вешается и не реагирует не на какие действия (в том числе сброс кнопкой Reset). После остановки и повторного запуска эмуляции контролер снова работает нормально.
    Не могу понять, это связано с глюками Proteusa, или это из за того, что метка Init: расположена не в главном файле программы.
    И еще один вопрос, можно ли прерывания и подпрограммы скинуть в отдельные файлы (естественно используя .include «prer.asm»)?

    • DI HALT говорит:

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

      Вынести в один файл можно, я так сделал — у меня есть отдельный vectors.asm где все вектора и переходы. Но там пришлось поиграть с ORG метками ,чтобы компилятор не ругался. В общем. не удалось мне ORG000 оставить до инклюда, и пришлось пихать ее внутрь.

  10. Jyraf говорит:

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

    Если никто не ответит — буду первый, кто попробует это сделать, потом отпишусь. :)

    Да, кстати, дайте рекомендации по составу программы в целом, что ставить в начало, что в середине, что в конце. Где расположить главный цикл программы. Я понимаю, что у каждого свои привычки, поэтому и прошу р е к о м е н д а ц и и.

    • DI HALT говорит:

      Ну обычно такая компоновка всегда

      СТарт.
      Инициализация
      главный цикл
      подпрограммы
      прерывания

      Прерывания дальше потому как до них проще дострелить из вектора каким нибудь JMP.

      Сама архитектура — либо диспетчер RTOS либо флаговый автомат. Я обычно на диспетчере делаю, удобней. В универах учат флаговым автоматам обычно.

  11. DIMA040891 говорит:

    В AVR Studio4 неполучается инициализировать стек
    .
    .
    .

    ldi Temp,RamEnd
    out SPL,Temp
    .
    .
    .
    Пишет что C:\NEW\A1\A1.asm(12): error: Operand(s) out of range in ‘ldi r16,0x45f’

    Пожалуйста объясните в чём ошибка???
    Контроллер ATmega8. На других МК тоже самое, только у ATiny2313 вроде как работает.

  12. sTARcRAB говорит:

    Вопрос по теме прерываний. Только не на асме, а на Си.
    1. Есть программа: «начало программы — основной цыкл программы». Программа крутится в основном цыкле WHILE (1). После обработки преривания мы идём в «начало программы». Можно ли организовать такое выполнение прерывания?
    2. Можно ли зделать так чтобы прерывание возникало при переходе 0-1 или 1-0, при этом 1 или 0 на ножке, по которой разрешено прерывание будет находится до следущего перепада 1 — 0 или 0 — 1 ?
    Спасибо.

    • DI HALT говорит:

      1) как то можно было сделать. С помощью описания обработчика. Есть там какие то хитрые модификаторы. А можно хакнуть — по окончании обработки вызвать какое нибудь неинициализированное прервыание программно.

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

  13. Maximka говорит:

    Таблица прерываний это адреса куда перейдет выполнение программы после наступления какого либо события. Эти адреса жестко «прописаны» в структуре МП, и при наступлении кокого либо из них процессор автоматически перейдет на вполнение в эту ячейку. А в этой ячейке команда безусловного перехода RJMP (JMP). Если нам это прерывание не нужно но наступить оно все таки может то мы пишем RETI. Тогда получается если нам нужно использовать только первое и последнее прерывание, то чтоб заполнить адреса в памяти нужно писать

    rjmp INT0 ; первое прерывание
    reti
    reti
    ….
    reti
    rjmp INTn ; последнее прерывание

    либо

    rjmp INT0 ; первое прерывание
    .org адрес
    rjmp INTn ; последнее прерывание
    а остальные запрещаем

    Я все правильно понял? ))

  14. Sher говорит:

    Подскажите пожалуйсто,в чем проблема. Использовал прерывания в Attiny2313 — программа работала как часики. Нужно было переписать прогу на ATmega16. Вот тут и начались фокусы: после того, как было вызвано прерывание, программа пререшла, как и положено на метку по вектору прерывания. Когда прерывание закончилось командой reti, курсор прыгнул не в то место, откуда было вызвано прерывание, а вообще не понятно куда, где-то в начало программы. Таблицу векторов прерывания, как и в Attiny2313 разместил в начале программы.

  15. strz говорит:

    DI_HALT,
    симулирую UDR прерывание. Значит програма зациклена, выставляю биты в UDR и ставлю флажок RXC, жму F11, все как положенно — программа идет по вектору, затем в обработчик, а вот там я ожидал что после команды
    IN R16, UDR
    в окне памяти по адрессу решистра R16 я увижу тот байт что я ставил в UDR, но там девственно стоят нули.

    • DI HALT говорит:

      Так и должно быть. Студия не симулирует передачу байтов из UDR. Байт надо подставлять напрямую в регистр куда ты делал IN

      • tyler.graip говорит:

        странно, как раз сейчас это проверял,
        получается после IN R16, UDR
        в R16 значение которое я выставил в UDR.
        использую avr simulator (1) и tiny2313

        • DI HALT говорит:

          Хм, может в разных версиях симулятора по разному? У меня на мега16 (не помню, врод бы симулятор 2) не передавалось значение из регистра UDR, приходилось натыкивать уже после.

          • Evg333 говорит:

            у меня тоже 00, UDR сбрасывается первым же нажатием F11. Хорошо в комментариях написано, а то уже хотел спрашивать )

            • Ana-bio-z говорит:

              Вот именно, что — сбрасывается при первом же нажатии F11. Поэтому пробивать биты в UDR нужно непосредственно перед выполнением команды:
              IN R16,UDR. Т.е. когда стрелочка в симуляторе стоит напротив нее. Пробиваем биты, нажимаем F11, значение UDR уходит в R16, а сам UDR обнуляется.

  16. Temp говорит:

    «Бег по граблям

    Вся засада багов из-за кривых прерываниЯХ в том…»
    Ошибочка в тексте. Простите за дотошность :))

  17. dima_m говорит:

    Выше в комментариях шла речь про внешнее прерывание int0. Как раз сейчас делаю детектор нуля через INT0, вопрос в том как правильно настроить ножку микроконтроллера.
    1.Как вход с подтяжкой или вход без подтяжки?
    Как понял если прерывание настраивать на низкий уровень, то с подтяжкой. А если по спадающему или нарастающему фронту сигнала, то как…? Без или с подтяжкой?

    • DI HALT говорит:

      Как настраивать совершенно не имеет значения для работы на прерывание. Зависит от схемотехники детектора. От подтяжки только зависит уровень на висящем в воздухе выводе.

  18. dima_m говорит:

    Допускается ли вызывать искусственно какое либо прерывание, допустим командой RCALL или обязательно оно должно вызваться только аппаратно самим мк?

  19. poljak181 говорит:

    DI HALT, зацени, в этой строчке очепятка закралась
    «Смотри, в стек пихнулось число 0×000007, указатель сместился на два байта и стал 0×035D, а контроллер сделал прыжок на адрес Wait.»
    Адрес должен быть 0×035D (как на скрине)

    З.Ы. Офигеннейший ресурс, учусь по нему) СПАСИБО!

  20. sam2sam говорит:

    Наверно опечатка, R17 заменить на R16
    «А оттуда уже прыжок сразу же на метку RX_OK, где мы забираем данные из регистра UDR в R17 и выходим из прерывания, по команде RETI.»
    В листинге программы команда (прерывание):
    RX_OK: IN R16,UDR

  21. smallghost говорит:

    Было

    45 .ORG INT_VECTORS_SIZE ; Конец таблицы прерываний

    Надо

    45 .ORG INT_VECTORS_SIZE * 2 ; Конец таблицы прерываний

    Т.к. константа содержит размер таблицы в словах, а не байтах.

    • DI HALT говорит:

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

      вот кусок для меги8

      .equ ERDYaddr = 0x000f ; EEPROM Ready
      .equ ACIaddr = 0×0010 ; Analog Comparator
      .equ TWIaddr = 0×0011 ; 2-wire Serial Interface
      .equ SPMRaddr = 0×0012 ; Store Program Memory Ready

      <— вот тут должна уже быть программа, т.е. по адресу 0х0013

      .equ INT_VECTORS_SIZE = 19 ; size in words

      что собственно и происходит 19 = 0х0013

      • smallghost говорит:

        Вижу тогда вот такую ошибку компилятора на первой строке кода:
        _PWM.asm(83): error: Overlap in .cseg: addr=0×14 conflicts with 0×14:0×15

        .ORG $024 ; (SPM_RDY) Store Program Memory Ready
        RETI

        .ORG INT_VECTORS_SIZE ; Конец таблицы прерываний
        ; Interrupts ==============================================

        ; = USART, Rx Complete ========
        RX_OK: PUSH R16 ;сохранили R16
        IN R16,SREG ;достали SREG
        PUSH R16 ;сохранили SREG

  22. smallghost говорит:

    8я мега
    таблица взята из твоего примера, убрал лишние прерывания которых в 8й нет на основании m8def.inc

    у меня получается последняя метка адреса .ORG $024 = 36 + 2 байта на переход = 38 (INT_VECTORS_SIZE * 2).

    у тебя для 16й меги последняя метка .ORG $028 = 40 + 2 байта на переход = 42 (INT_VECTORS_SIZE * 2).

    Или я что-то не понимаю

  23. smallghost говорит:

    8я мега
    таблица взята из твоего примера, убрал лишние прерывания которых в 8й нет на основании m8def.inc

    у меня получается последняя метка адреса .ORG $024 = 36 + 2 байта на переход = 38 (INT_VECTORS_SIZE для 8й * 2).

    у тебя для 16й меги последняя метка .ORG $028 = 40 + 2 байта на переход = 42 (INT_VECTORS_SIZE для 16й * 2).

    Или я что-то не понимаю

    • DI HALT говорит:

      Таблица векторов меги16

      ; ***** INTERRUPT VECTORS ************************************************
      .equ   INT0addr   = 0×0002   ; External Interrupt Request 0
      .equ   INT1addr   = 0×0004   ; External Interrupt Request 1
      .equ   OC2addr   = 0×0006   ; Timer/Counter2 Compare Match
      .equ   OVF2addr   = 0×0008   ; Timer/Counter2 Overflow
      .equ   ICP1addr   = 0x000a   ; Timer/Counter1 Capture Event
      .equ   OC1Aaddr   = 0x000c   ; Timer/Counter1 Compare Match A
      .equ   OC1Baddr   = 0x000e   ; Timer/Counter1 Compare Match B
      .equ   OVF1addr   = 0×0010   ; Timer/Counter1 Overflow
      .equ   OVF0addr   = 0×0012   ; Timer/Counter0 Overflow
      .equ   SPIaddr   = 0×0014   ; Serial Transfer Complete
      .equ   URXCaddr   = 0×0016   ; USART, Rx Complete
      .equ   UDREaddr   = 0×0018   ; USART Data Register Empty
      .equ   UTXCaddr   = 0x001a   ; USART, Tx Complete
      .equ   ADCCaddr   = 0x001c   ; ADC Conversion Complete
      .equ   ERDYaddr   = 0x001e   ; EEPROM Ready
      .equ   ACIaddr   = 0×0020   ; Analog Comparator
      .equ   TWIaddr   = 0×0022   ; 2-wire Serial Interface
      .equ   INT2addr   = 0×0024   ; External Interrupt Request 2
      .equ   OC0addr   = 0×0026   ; Timer/Counter0 Compare Match
      .equ   SPMRaddr   = 0×0028   ; Store Program Memory Ready
      

      И

      Таблица векторов меги8:

      ; ***** INTERRUPT VECTORS ************************************************
      .equ   INT0addr   = 0×0001   ; External Interrupt Request 0
      .equ   INT1addr   = 0×0002   ; External Interrupt Request 1
      .equ   OC2addr   = 0×0003   ; Timer/Counter2 Compare Match
      .equ   OVF2addr   = 0×0004   ; Timer/Counter2 Overflow
      .equ   ICP1addr   = 0×0005   ; Timer/Counter1 Capture Event
      .equ   OC1Aaddr   = 0×0006   ; Timer/Counter1 Compare Match A
      .equ   OC1Baddr   = 0×0007   ; Timer/Counter1 Compare Match B
      .equ   OVF1addr   = 0×0008   ; Timer/Counter1 Overflow
      .equ   OVF0addr   = 0×0009   ; Timer/Counter0 Overflow
      .equ   SPIaddr   = 0x000a   ; Serial Transfer Complete
      .equ   URXCaddr   = 0x000b   ; USART, Rx Complete
      .equ   UDREaddr   = 0x000c   ; USART Data Register Empty
      .equ   UTXCaddr   = 0x000d   ; USART, Tx Complete
      .equ   ADCCaddr   = 0x000e   ; ADC Conversion Complete
      .equ   ERDYaddr   = 0x000f   ; EEPROM Ready
      .equ   ACIaddr   = 0×0010   ; Analog Comparator
      .equ   TWIaddr   = 0×0011   ; 2-wire Serial Interface
      .equ   SPMRaddr   = 0×0012   ; Store Program Memory Ready
      

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

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

      .ORG INT0addr

      а не

      .ORG 0×0001 ; INT0 addr

      Тогда компилер все сделает сам. Я так не делаю только по тому, что мне лень переписывать с нуля все это :) — у меня под все контроллеры с которыми я работал есть уже готовая таблица прерываний с абсолютными адресами которую я копирую в нужный проект. Хотя это и не очень красиво.

      • DI HALT говорит:

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

      • fokuz говорит:

        Как видишь у меги16 таблица другая. Адреса зовутся также, но вот на каждый вектор у меги16 отводится ДВА байта
        А почему два байта, разве не 4?
        Ведь адресация в словах
        .ORG $002
        RETI ; (INT0) External Interrupt Request 0
        .ORG $004
        RETI ; (INT1) External Interrupt Request 1

        во флеше выглядит вот так:
        000002 18 95
        000003 FF FF
        000004 18 95
        000005 FF FF

  24. Любовь Назарова говорит:

    Спасибо огромное автору!

  25. miRasH говорит:

    Доступно и ясно. И как вам хватает нервов так всё детально описывать?)). Очень интересна,кстати,с точки зрения прерываний ATtiny28, к которой можно даже прилепить клавиатуру

  26. Sadness говорит:

    Помогите разобраться в коде:

    это кусок кода прерывания по переполнению таймера Т2 [ATmega8L]
    устройство — таймер который просто отсчитывает вперёд секунды минуты и часы
    при переходе с 23:59:59 на 24:00:00 должен происходить сброс переменных в ноли
    чтобы было 00:00:00 но этого не происходит, таймер показывает 24:00:00 и
    продолжает считать вперёд

    ;——Прерывание T2 [1с]——

    tim2: sei
    inc t_secl
    cpi t_secl, 10
    breq sh
    reti

    sh: ldi t_secl, 0
    inc t_sech
    cpi t_sech, 6
    breq ml
    reti

    ml: ldi t_secl, 0
    ldi t_sech, 0
    inc t_minl
    cpi t_minl, 10
    breq mh
    reti

    mh: ldi t_secl, 0
    ldi t_sech, 0
    ldi t_minl, 0
    inc t_minh
    cpi t_minh, 6
    breq hl
    reti

    hl: ldi t_secl, 0
    ldi t_sech, 0
    ldi t_minl, 0
    ldi t_minh, 0
    inc t_hrl
    cpi t_hrl, 10
    breq hh
    reti

    hh: ldi t_secl, 0
    ldi t_sech, 0
    ldi t_minl, 0
    ldi t_minh, 0
    ldi t_hrl, 0
    inc t_hrh
    cpi t_hrh, 2
    breq hh2
    reti

    hh2: cpi t_hrl, 4
    breq hh3
    reti

    hh3: ldi t_secl, 0
    ldi t_sech, 0
    ldi t_minl, 0
    ldi t_minh, 0
    ldi t_hrl, 0
    ldi t_hrh, 0
    reti

    • DI HALT говорит:

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

      • Sadness говорит:

        при трассировке с начала этой подпрограммы в sh: или ml: курсор прыгал на другую подпрограмму задержки которая к этой не относится (почему я так и не понял)

        • DI HALT говорит:

          А как ты туда попадал? Если так, то у тебя срыв стека где то.

          • Sadness говорит:

            хм… стек даже не использовал

            • DI HALT говорит:

              Т.е. даже не инициализировал? Замечательно. Значит твоя программа мертворожденная изначально. Т.е. тикать может она и тикает, но на большее будет неспособна. Т.к. по выходу из прерывания у ней сорвет стек, прога выполнить несуществующую инструкцию и перезагрузится. Можешь проверить. У тебя каждый выход из прерывания = перезагрузка. Разве что в регистрах значения не стираются как при аппаратном ресете. ПОтому то ты это и не заметил.

              Перечитай еще раз статью. Внимательно. И тогда узнаешь, что стек ты юзаешь, даже если и не знаешь об этом :)

    • DI HALT говорит:

      Хотя нет, налицо кривой выход из прерывания. Т.к. выходить тупо через RETI нельзя.

      Где сохранение SREG и всех использованых регистров? Где их возврат «Как было» перед выходом?

    • DI HALT говорит:

      И зачем разрешать прерывания при входе в обработчик? Есть что то более важное?

      • Sadness говорит:

        Всё исправил, всё тикает как должно, не глючит, спасибо =)

        • serg71277 говорит:

          У меня вопрос по прерываниях. Например: основная программа в один из моментов выполняет операцию, которая состоит из некоторого числа подпрограмм вложенных друг в друга. Можно ли в этот момент произвести внешнее прерывание(прервать выполняющуюся операцию)и по внешнему прерыванию выйти в совершенно другое место основной программы, не закончив выполнение прерванной операции.Так сказать грамотно сорвать стек?Если бы операция, которую прерывают была бы без подпрограмм, то проблем бы не было, а вот как быль в случае наличия подпрограмм?

          • DI HALT говорит:

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

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

          • DI HALT говорит:

            А блин, не правильно понял вопрос.

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

  27. serg71277 говорит:

    Большое спасибо. Если можно, помогите найти иной путь решения задачи.Попробую детально: основная программа в один из моментов должна одновременно выполнять две функции(1-я динамическое свечение светодиодов с частотой 40 Гц и скважностью 1:8, и 2-я звуковое сопровождение разными тонами 400 Гц, 800 Гц с паузами между тонами).Реализовать мне это удалось без особых проблем, но получилось множество вложенных друг в друга подпрограмм. Устройство имеет кнопку внешнего прерывания. Выше приведенные функции выполняют роль свето-звуковой индикации и во время их выполнения рука тянется прервать процесс, нажав на кнопку. А теперь вопрос: можно ли реализовать эту индикацию иным способом? То есть без подпрограмм или хотя бы использовав их малое количество.

  28. serg71277 говорит:

    Вопрос по поводу реализации звуков при помощи ШИМа.Я работаю ATtiny2313. Мне нужен не просто писк, а писк с регулировкой громкости. Я делаю это изменяя скважность. А в принципе требуется еще получить не просто писк, а какой то «живой» звук(все таки 21 век за окном). Кроме того писк режет ухо. Кроме того нужно динамически подсвечивать светодиоды во время звука. При этом не должно быть рывков в свечении. Подскажите, можно ли это сделать при при помощи ШИМа? Может у вас имеется какой то пример для наглядности.

  29. Poligrafych говорит:

    А там в начале (возле пива) строка 20 OUT UDR,R16
    21 RJMP Start

    переходим на 12 Start: OUT UDR,R16 (получается без задержки)

  30. sTARcRAB говорит:

    добрый вечер. ламерский вопрос про прерывания. скажем крутится в цикле код. в коде меняется переменная Х. тикает также таймер. прерывание по переполнению таймера. во время прерывания берем значение переменой и в зависимости от нее меняем ШИМ на какой либо ножке. так МК будет работать?

  31. GVS говорит:

    Сталкнулся с такой штукой, что при инициализации стека указанным способо, в случае возникновения прерывания по петеполнению T0(в моем случае), мы переходим в обработчик, а вот обратно возвращаемся совсем не туда, откуда ушли.. Погоняв в студии понял, что когда мы уходим на прерывание, адрес возврата пихается в стек в ячейку 0x45F(mega8, последняя ячейка) и равен 0! Если же проинициализировать стек так:
    http://easyelectronics.ru/repository.php?act=view&id=35
    то адрес возврата в стеке записывается правильно, занимая два последних байта.
    Ну а если до прерывания пробовать запихать в стек что-либо, то запись естественно начинается с адреса 0x45E..
    Вот пример кода:
    http://easyelectronics.ru/repository.php?act=view&id=36

    Почему так происходит?

    • DI HALT говорит:

      Хрень какая то. У тебя много PUSH\POP в коде. Нигде не напутал с ними ничего?

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

  32. Vladimir.F говорит:

    Здравствуйте. Подскажите пожалуйста, в чем может быть причина неправильного деления частоты нулевым таймером в Atmega16(вместо 40 Гц получается 39,5 Гц). Timer1 делит правильно. Других прерываний нет, только по переполнению. Пишу в AVR Studio на языке С.

  33. TU22 говорит:

    Пока контроллер занимается вычислением или гоняет байтики по памяти — АЦП может яростно оцифровывать входное напряжение, USART меланхолично передавать или принимайть байтик, а EEPROMка неспеша записывать в свои тормозные ячейки очередной байт.

    5 баллов!!! Жизненное описание! Молодец!

  34. tomba говорит:

    Так должно работать(см. ниже)? Здесь подпрограмма в подпрограмме. Непонятно. В симуляторе работает, а на кристалле(мега8) нет.
    И ещё вопрос о команде CALL. Согласно даташиту, таковой команды нет и компилер ругается, а есть только RCALL и ICALL. Может дело в вызовах?

    main:
    RCALL podproga


    podproga:


    RCALL pod_podproga


    RET

    pod_podproga



    RET

    • DI HALT говорит:

      Стек инициализировал?

      Если оно так как у тебя написано (без зацикливания) то работать не будет Суди сам:

      Сначала у тебя вызывается подпрога. Потом из нее выходит и дальше уже по очереди выполнения попадаем в тело подпроги оно выполняется еще раз, вызывается под_подпрога, а потом опа внезапно RET которого не ждали и стек срывает.

      А так, никаких проблем не вижу. Вызов вложенных прог может быть очень глубоким — пока стека хватит (у тини11/12 правда стек в три уровня всего)

  35. nes говорит:

    Вопросы по вот этому куску кода:

    RCALL Wait

    OUT UDR,R16
    RCALL Wait
    OUT UDR,R16
    RCALL Wait
    OUT UDR,R16
    RCALL Wait
    RJMP Start ; Зациклим программу.

    Wait: LDI R17,Delay
    M1: DEC R17
    NOP
    BRNE M1
    RET

    Итак, как компилятор понимает, что Wait это подпрограмма, а M1 всего лишь метка? Они объявляются одинаково ведь. Неужели только за счет того, что ранее мы сделали RCALL Wait? Получается, что если бы мы сделали RCALL M1, выполнялся бы вот этот кусок:
    M1: DEC R17
    NOP
    BRNE M1
    RET
    правильно?

    • nes говорит:

      а, ну сделал RCALL M1, получилось все, как я сказал. Тогда вы как опытный программист на асемблере поделитесь впечатлениями: не сильно ли путаются метки и подпрограммы в голове?

      • DI HALT говорит:

        C точки зрения компилятора нет понятия метки подпрограммы и метки перехода. Есть лишь одно понятие — Адрес. Ну и метка этого адреса. А уж ссылаешься на нее переходом или вызовом подпрограммы не имеет значения. Разница лишь в том, что подпрограмма пихает данные возврата в стек и потом должен быть RET который вернет данные обратно, иначе рано или поздно будет срыв стека.

    • DI HALT говорит:

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

  36. X-Ray говорит:

    Чтобы дальше мне не запутаться сразу вопрос.
    LDI R17,Delay ; Загрузили длительность задержки
    M1: DEC R17 ; Уменьшили на 1
    NOP ; Пустая операция
    BRNE M1 ; Длительность не равна 0? Переход если не 0

    Почему здесь BRNE работает? Прочитал описание команды в даташите. «if (Z=0)then PC<-PC+k+1″. Z подразумевает пару регистров R30 и R31? Тогда как программа понимает что нужно сравнить с 0-ём именно число из R17? Или я не правильно понимаю даташит?

  37. X-Ray говорит:

    Проблема номер два. Возможно связана с новой версией студии.
    При вызове подпрограммы с помощью RCALL по вашему примеру, в стеке почему то добавляется значение не 00 07, а 20 07. При этом программа работает правильно, после RET переходит именно туда куда и требовалось. А если испортить подпрограмму по вашему примеру и добавить в стек 0, при этом получится стек вида 00 20 07, программа также как и у Вас перейдет в начало. Если вставить в стек значение из R16, то стек будет выглядеть как 32 20 07, но при этом если переключить вид отображения байтов в студии с 1-Byte Integer на 2-Bite Integer, то этот стек примет вид 3200 0720. То есть здесь 07 выше 20. Что то не понятно. Как вариант можно конечно переставить на студию 4-ой версии, но хочется сразу в новом ПО разобраться.

    • DI HALT говорит:

      Видимо где то стоит смещение в виде ORG и реальный адрес подпрограммы не 00 07 как у меня, а 20 07.

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

      • X-Ray говорит:

        Спасибо за предыдущий ответ, хоть я уже сам понял что нужно было быть внимательнее))

        По поводу последнего.
        ORG в программе вообще не стоит. Поэтому так и не понял где копать. И появился еще один вопрос, может опять связанный с новой студией) Флаг RXC не ставится. При этом тот же RXCIE ставится щелчком и убирается. В UCSRA можно менять только два последних байта (U2X и MPCM). При этом по стандарту стоит флаг (убрать опять же нельзя) UDRE. В UCSRB можно менять все флаги кроме предпоследнего. Может что то ограничивает смену этих флагов?

    • ek50hey говорит:

      Поставил 6 версию (она теперь не AVR Studio а Atmel Studio называется), так там вообще бардак с этим делом. Ради эксперимента сделал около 150 строчек кода (NOPами заполнил) и попробовал добавить PUSH R16 (R16 = 01). При этом в стеке получилось 01 20 07 (вместо 01 00 07). После RETа перекинуло не на 01 00, а на 01 20. (при этом если стек не срывать то переходит на 00 07 вместо 20 07).

  38. BigBoy говорит:

    Вот кстати ошибка в тексте «Смотри, в стек пихнулось число 0×000007, указатель сместился на два байта и стал 0×035D, а контроллер сделал прыжок на адрес Wait.» Адрес указателя не 0×035D а 0×045D

  39. __bl__ говорит:

    Здравствуйте. Есть 2 вопроса по программированию. Контроллер МЕГА32А, Avrstudio 5.0.
    1. Выполняется ли обработка прерывания таймера (0, 1, 2) если долго выполняется прерывание INT (0, 1, 2)?
    2. Контроллер находится в пластиковой коробке на улице (постоянно). коробка не герметична. Сейчас температура упала до -2 град. Контроллер включается через 1 или 2 недели. После простоя не подаёт признаков жизни, на программатор не отзывается (программатор AVR Easy 3.2). Но прошивка заливается. После перезаливки прошивки работает нормально. При следующем запуске через неделю нужно опять заливать прошивку. Что происходит с контроллером? Контроллер служит тестовым образцом и прошивка заливалась в него раз 200.

    • DI HALT говорит:

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

      • __bl__ говорит:

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

        2. Уточните пожалуйста, что за «сброс».

        3. Не подскажите где оперативно на русском языке можно получить ответы на вопросы о контроллерах АТМЕЛ AVR серии?

        • DI HALT говорит:

          1. Значит глючно был написан :)
          2. просто reset системы. У вас на еепром не завязано ничего? а то у аврок почему то часто епром косячит.
          3. Нет таких мест, разве что форумы.

          • __bl__ говорит:

            В еепром ничего не записано. Программатором AVRISP 3.2 не удаётся считать прошивку, проверить фьюзы, даже Chip-ID не считывается. Но прошивка работает. После повторной прошивки контроллер восстанавливает свою работоспособность.
            Контроллер запаян на макетке AVR-P40-8535. Чуть позже впаяю новый.
            Как лучше впаять контроллер сразу в макетку или можно в цанговую панель?

  40. NPLM говорит:

    Здравствуйте, поставил AVRstudio 5 и обнаружил, что симулятор не разрешает сделать прерывания от переферии, тыкание по битам результатов не дает, при этом внешние прерывания выставляются легко. В четвертой студии все было нормально. Подскажите пожалуйста где я туплю…

    • __bl__ говорит:

      Добрый вечер! Если я правильно прочёл Atmelовские документы, то симулятор в AVRstudio 5 остаётся недописанным.

      • NPLM говорит:

        Спасибо, значит остаемя на четвертой…

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

        • DI HALT говорит:

          С интерфейсом там то как раз все замечательно, но интерфейс они взяли готовый от микрософта. А вот свои же шняжки убили на корню.

          • NPLM говорит:

            Ну не знаю, лично мне только внешний вид понравился, с семеркой больше сочетается. :)
            А вот работа с памятью стала неудобной, где разбиение на колонки? Я не нашел. Многие окна потеряли альтернативные варианты отображения. Вроде мелочи, но как-то напрягает с непривычки, может конечно я не туда смотрю и тыкаю. Есть конечно приятные фичи, но невозможность сделать прерывания убила…
            Вобщем-то 4-ая студия вполне себе устраивает, 5-ую поставил только эксперимента для.

    • DI HALT говорит:

      АВР Студия 5 недоделаное УГ.

  41. yuzd говорит:

    Приветствую!
    Подскажите по стековым переходам, есть у меня прерывание
    TIM0_COMPA:
    код
    код
    код
    reti
    при выходе из него я всегда хочу возвращается на M1, а не туда откуда пришел, я так понимаю нужно сначала забрать со стека предыдущий адрес перехода, потом положить свой на М0? Напишите плиз как это правильно делать.

    • DI HALT говорит:

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

      Но коль желаешь потрахаться…

      Раз у тебя состояние фоновой программы побоку, то сохранять регистры в стеке смысла нет никакого (им один хер капец). Потому достаточно просто pop-нуть старый адрес, куда угодно, в любой свободный регистр, он нам не нужен. И push’нуть туда новый адрес возврата. Правда надо посмотреть в каком порядке байты адреса пихаются в стек. Но тут тебе студия в помощь.

      • yuzd говорит:

        Думаю что вот так будет правильно?
        POP YL
        POP YH
        LDI YH,low(M0)
        PUSH YH
        LDI YL,High(M0)
        PUSH YL
        reti
        Попробовал, по карйней мере протеус не ругается на переполнение стека.
        Программка сама маленькая, и состояние основной программы после прерывания действительно побоку, т.к. при заходе в прерывание забирается результат счета с фоновой программы, и там уже по условию с ним работается, потом надо вернутся в начало (M0) и начать все считать заново, пока не придет прерывание и не заберет результат.

  42. der_poul говорит:

    Вот пытаюсь я проссимулировать сей процесс(приход данных по UART) Но никак не могу поставить бит RXC, не ставится он и все =( И в UDR ничего воткнуть не могу. AVR Studio 5.

  43. akocur говорит:

    у меня несколько вопросов.
    1. Что означает вот эта операция (1<<RXCIE)?
    2. Как узнать в какие регистры должен прийти байт, что бы возникло прерывание? Как в даташите их называют? Что в реальности подразумевается под тем что в регистр свалился байт? К какой то ножке микросхемы подали напряжение? В даташите не нашел вывод RXC.

    P.S. Может ранее об этом и спрашивали, но мне до сих пор не видны чьи либо комментарии.

  44. Veter0k говорит:

    Не могу понять почему не получается выставить ручками бит RXC? AVR Studio 5. Кто еще сталкивался с этой проблемой? какие методы ее решения?

  45. Alex0720 говорит:

    Доброго времени суток.
    При прогоне в AVR Studio 4 примера с прерываниями для Atmega16 , флаг RXC (который выставляем в рукопашную), сбрасывается самостоятельно сразу после команды RX_OK: IN R16,UDR
    Но при тех же действиях для Atmega128a, RXC (только там он обзывается RXC0) почему остается активным постоянно. Имена регистров в программе, понятное дело были адаптированы под Atmega128a и весь пример проходит одинаково с Atmega16.. Кроме этого бага…

  46. xbco говорит:

    Привет DI HALT спасибо за твои труды, которые пишешь. Огромнейший RESPECT! Читал книгу Д. Мортана по AVR несколько раз, но с твоими подробными статьями не сравниться. Очень понравилось как написана о организации архитектуры мк и памяти.
    У меня возник табл с вектором прерывания, ты писал: «Нажми F11, чтобы сделать еще один шаг по программе… Опа, стрелочка улетела в таблицу векторов, как раз на вектор
    1 .ORG $016
    2 RJMP RX_OK ; (USART,RXC) USART, Rx Complete
    » но у меня в авр студии v4.13 стрелка улетает на RETI между
    .ORG $00C
    —> RETI ; (TIMER1 COMPA) Timer/Counter1 Compare Match A
    .ORG $00E
    В чем может быть проблема?
    Ссылка на zip с проектом авр студии: http://narod.ru/disk/51455709001.15836ea55584f16b63ca3fcbe380176c/testing2.zip.html

    • DI HALT говорит:

      А тип МК Правильно выбран в симуляторе? Плюс что то ты намудрил с ORG. Вектора же жестко заданы на конкретные адреса, Потому поставь ORG000 и пропиши ВСЕ вектора вообще.

  47. limburan говорит:

    Приветствую! Я поставил себе Atmel Studio 6

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

    Но и это не все. Даже если его установить вручную, при установке флага RXC в регистре UCSRA он тут же сам снимается на следующий же шаг и зацикленный NOP продолжают выполняться дальше как ни в чем ни быловало, прерывания не происходит.

    Как так?

    • DI HALT говорит:

      Поздравляю, сноси эту недоделку нах :)

      Если SEI не ставится видимо косяк студии. Этот бит никто не может снять или поставить. Только ты сам.

      А вот RXC снимается сам автоматом, сразу же по входу в обработчик прерывания RXC

      • limburan говорит:

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

        Еще кстати на одном форуме мне написали вот что:
        «Я не знаю, что там с SEI, но что ты забыл сделать — включить приёмник битом RXEN в том же регистре UCSRB»

        Что скажешь? Это действительно обязательно делать?

      • limburan говорит:

        Снес 6ую студию, поставил 5.1, все заработало как надо.

  48. Neonorama говорит:

    День добрый. Да с 6-ой студией явные проблемы. Прерывания не включаются, установка флагов вручную не помогает. Буду ставить 4-ку, а то целый час мучился, пока комменты не прочитал :).

  49. nikom говорит:

    Привет, DI!
    Использую ATmega168. Таблица прерываний чуть больше, чем у ATmega16. Сначала сделал как у тебя, добавив дополнительные вектора. Получил Overlap in .cseg.
    Перечитал пост, скопировал метки. Тоже не помогло.
    Причём что интересно overlap только до адреса 0х28:
    Error 1 Overlap in .cseg: addr=0×0 conflicts with 0×0:0×28 C:\AVR_Studio_5\MyCandle\MyCandle\MyCandle.asm 11 0 MyCandle

    Error 20 Overlap in .cseg: addr=0×26 conflicts with 0×0:0×28 C:\AVR_Studio_5\MyCandle\MyCandle\MyCandle.asm 49 0 MyCandle

    Подскажи где я накосячил.

  50. DI HALT говорит:

    Где то у тебя пересекаются адресные пространства. Покажи этот кусок кода, где ты вектора определяешь.

    • nikom говорит:

      .include «m168def.inc»
      .include «macro.asm»
      .include «coreinit.asm»
      ; RAM ========================================================
      .DSEG
      SW_DOWN: .byte 1

      ; FLASH ======================================================
      .include «vectors.asm»
      ; Interrupts ==============================================
      ; End Interrupts ==========================================
      Reset: LDI R16,Low(RAMEND)
      OUT SPL,R16

      LDI R16,High(RAMEND)
      OUT SPH,R16

      В файле vectors.asm прописал твой код (до этого то же самое было только в моём исполнении).

  51. Moonshiner говорит:

    Добрый день.
    Помогите, пожалуйста, разбираюсь с курсом и в качестве МК взял ATmega1284P. Все дело запускал в 6-ой студии и багов замечено не было до момента симуляции прерывания от UART-а. При попытке выставить RXC0 он на следующем же шаге сбрасывается и не происходит перехода в обработчик. Код инициализации:
    Reset: LDI R16,low(RAMEND)
    OUT SPL,R16
    LDI R16,high(RAMEND)
    OUT SPH,R16

    SEI

    LDI R17,(1<<RXCIE0)
    STS UCSR0B,R17

    • DI HALT говорит:

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

      Правильно ставить бит надо через чтение-модификацию-запись. Т.е. читаешь UCSR0B дальше по OR накладываешь на него (1<

      • Moonshiner говорит:

        Она вообще странная. Изначально стек инициализирован, что бы ты ни написал и библиотеку МК не обязательно подключать. Поэтому, когда начал осваивать курс, не понимал, к чему эти строчки.
        Кстати, с ATmega16 только с установленным битом RXCIE работает, другие модели не проверял.

    • Demyan говорит:

      В 6-й версии Студии была аналогичная проблема. Поставил версию 4.19 (взятую на оф. сайте Атмела). Пока всё ровненько… По крайней мере нет баги с прерываниями от UART-а. Изучаю курс дальше. :):):)

  52. dantistus говорит:

    Я посмотрел по даташиту, RCALL выполняется 3 цикла, RET — 4 цикла. Я правильно понял, что вызов подпрограммы будет выполняться на 7 тактов дольше аналогичного куска кода без вызова подпрограммы?

  53. RokkerRulsan говорит:

    Добрый день! Вы не сказали, откуда мокроконтролер знает куда надо прыгать на конкретное прерывание. И почему таблица выглядит именно так? Почему каждое следующее прерывание смещено на 4 байта (2 слова)?

    • DI HALT говорит:

      Потому что так повелели боги!

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

  54. Valerii говорит:

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

  55. llpax говорит:

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

    Если включить RXEN, RXC обрабатывается дважды о_О
    Без RXEN флаг RXC сбрасывается сразу после перехода (или попытки перехода) даже без чтения из UDR

  56. RokkerRulsan говорит:

    Добрый день! Еще возник вопрос: во время вызова rcall, в памяти появляется не тот адрес, почему то старший байт равен 0×0207. Хотя вызываю с адреса 0×0007. AVR Studio 6. Возврат идет на правильное место. Но все равно как то неприятно. AVR Studio 4, такого поведения не замечено.
    http://yadi.sk/d/uyukfSdOCTFey

  57. RokkerRulsan говорит:

    Еще обнаружил проблему в AVR Studio 6. В ручную не устанавливаются биты в регистрах ввода вывода. Например, следуя примеру из статьи, нужно самостоятельно выставить бит rxc для возникновения прерывания. Нажимаю щелчком мыши, бит загорается, но на следующем шагу перехода на таблицу векторов прерываний не происходит, продолжает выполняться бесконечный цикл. В AVR Studio 4 все работает.

  58. Prizrak говорит:

    Уважаемый! То что Вы делаете — это просто супер! Статьи весьма интересны и познавательны. (Жаль что я это уже все знаю :-D ) Заметил я Ваших исходниках интересную особенность..
    Вы после любых арифметических команд добавляете операцию nop. Эта дань какой-то старой традиции или же это действительная необходимость о которой просто не все знают? Допустим, СИшный компилятор пустых операций не добавляет. Поясните, пожалуйста! )

  59. Fossa говорит:

    А вот такой интересный вопрос. Есть у меня самопальный частотомер на 16-ой меге. Крутится таймер 16-битный с полной тактовой частотой, когда переполняется — генерирует прерывание, по которому старшая часть апдейтится. Другое прерывание модулем захвата генерируется.

    Что произойдёт, если оба события строго одновременно наступят (в пределах одного такта). Какое из прерываний раньше сработает — неизвестно будет, да?

  60. Valina говорит:

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

    • DI HALT говорит:

      Есть бит разрешающий прерывания вообще — он в SREG. И есть биты разрешающие прерывания по каждому источнику. Они все в регистрах соответствующей периферии. Изучайте регистры конкретной периферии (например таймера) и там найдете искомое. Там обычно два бита. Бит разрешения прерывание ***IE и флаговый бит прерывания по которому можно понять, что прерывание случилось (если, например, оно было запрещено и не обработалось само). Ну и флаговые биты прерываний надо обрабатывать и сбрасывать, чтобы прерывание не сработало еще раз. Правда в большинстве случаев это происходит автоматически, при вызове обработчика, при чтении из регистра или еще как (в периферии описано), но не всегда.

  61. vlipt говорит:

    Микроконтроллер ATTiny 2313. Задача по прерыванию от кнопок, подключенных к PB1 и PB2 управлять светодиодом, подключенным к PB0. Часть кода

    .org 0 ;Задание нулевого адреса старта программы
    rjmp reset ;Безусловный переход к метке reset
    .org 0x000B ;Адрес прерывания по изменению состояния выводов
    rjmp pin_change ;Безусловный переход к метке pin_change

    reset: ;Начало раздела инициализации контроллера
    ldi r16,RAMEND ;Загрузка в регистр r16 адреса верхней границы ОЗУ
    out SPL, r16 ;Копирование значения из r16 в регистр указателя стека SPL
    sbi DDRB, 0 ;Установка 0-го бита в регистре DDRB в «1″ (РВ0 — выход)
    ldi r16,(3<<1) ;Загрузка в r16 двух "1", смещенных на 1 разряд влево
    out PORTB,r16 ;Включение подтягивающих резисторов на входах РВ1 и РВ2
    out PCMSK,r16 ;Разрешение прерываний по изм. сост. выводов для РВ1 и РВ2
    ldi r16,(1<<PCIE) ;Загрузка в регистр r16 единицы в разряд PCIE
    out GIMSK, r16 ;Разрешение прерывания по изменению состояния выводов

    Дальше пока не важно. По предпоследней команде в r16 загружается 0b00100000. А вот последняя команда out GIMSK, r16 в регистр GIMSK не записывает ничего. И в AVR Studio там одни нули и схема не работает. Почему не происходит запись в GIMSK?

  62. vlipt говорит:

    Это явление наблюдается только в студии 5 и 6 (на 4-й не проверял, нету её). В техподдержке Atmel ответили, что проблему знают и может быть, наверное, когда-нибудь её устранят.
    Эмпирическим путем удалось установить, что если в неиспользуемые разряды GIMSK записать единицы, то в значащие разряды будет записываться то, что нужно.

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