AVR. Учебный курс. Подпрограммы и прерывания
Автор DI HALT
Опубликовано 09 Авг 2008 (Править)
Рубрики: AVR. Учебный курс
Метки: Assembler, AVR, Программирование
Чтобы понятно было сразу и на века приведу отвлеченный пример.
Представь ты просыпаешься утром и идешь умываться и чистить зубы. Сам процесс умывания и чистки зубов отработан уже до автоматизма и ничем не отличается от вечерней процедуры. Да и если тебе захочется вдруг взять умыться среди дня или почистить зубы просто так, то все ты повторишь в точно такой же последовательности. Мелкие вариации, конечно не в счет. Совершенно очевидно, что в твоих мозгах зашиты две подпрограммы: “Чистка зубов” и “Умывание“.
Также и в коде, когда у тебя какая то последовательность действий часто повторяется, то ее рациональней вынести в подпрограмму. Они же процедуры, функциями называть их можно, но это не совсем корректно. В функцию можно передавать параметры, для передачи же параметров в подпрограмму в ассемблере механизмов нет, их приходится изобретать самостоятельно. Например я передаю через регистры, заранее загрузив туда нужные значения перед вызовом процедуры.
тюбик гуталина в руку (передаваемый параметр) и поперся к раковине. В случае ассемблерной процедуры, тебе бы пришлось вначале подсунуть гуталин вместо зубной пасты. А потом почистить зубы по обычному алгоритму “взяв тюбик возле раковины”. После зачистки зубов ты бы вернулся к тому месту откуда начал - если бы это было вечером, то ты бы лег спать, а если утром, то ушел на работу или учебу.
Прерывания
Они похожи на процедуры, но работают по несколько иному принципу и имеют принципиально иное назначение.
Опять условный пример.
Ты сидишь втыкаешь в монитор, читаешь эту статью, тут звонок в дверь — аппаратное прерывание от устройства дверь. Если ты не нычишься от военкомата или соседки которую ты вчера затопил, и у тебя разрешены прерывания от устройства “дверь” то ты оторвешь задницу от стула и пойдешь в прихожую — вектор прерывания, от аппаратного устройства дверь. Прийдя в прихожую, то есть на вектор прерывания, происходит переход к обработчику прерывания, то есть мы уже врубаем, какой либо заранее записанный алгоритм, определяющий что сделать: открыть дверь, обматерить тех кто за дверью, открыть и обматерить, вызвать милицию, вызвать дурку. Да что угодно.
Приоритеты прерываний.
Представь ты ломанулся открывать дверь, а тебе пришло второе аппаратное прерывание - захотелось крупно погадить. С одной стороны важно и то и другое. С другой стороны в туалет можно и потерпеть, а вот гость то может и свалить. Так что заранее выстраиваются приоритеты прерываний и вначале принимаем гостя, а потом идем гадить. С другой стороны можно принять гостя, а потом днище как сорвет и случится большой конфуз. Так что надо очень тщательно подходить к процессу выбора приоритетов.
Главное помнить то, что отложенное прерывание никуда не девается, и как только процессор освободится, то пойдет выполнять его. Но его можно отменить вручную, скинув флаговый бит отвечающий за вызов прерывания.
Прерывания можно запрещать как по одному, от каждого устройства, так и глобально все махом. К этому тоже следует относится осторожно, например во время секса вполне разумно игнорировать стуки по батарее, телефонные звонки и долбежку в дверь, а вот взрыв газа на кухне игнорировать уже не стоит, по понятным причинам.
Возврат.
После обработки прерывания, когда все гости выслушаны и разогнаны, сортир смыт и проветрен, взрыв газа локализован и пожар потушен, а стучащие по батарее соседи получили в дыню, осуществляется возврат к прерванной программе, в нашем примере ты бы вернулся к компу читать статью дальше, как ни в чем ни бывало.
Установка флагов.
Это относится не столько к функционированию ядра контроллера, сколько к методикам ассемблерного программирования.
Вот вернулся ты к своему компу, а у тебя по плану было написание поста в блог. Изначально ты хотел выдать порожняк, вроде “ааа скучно мне, дайте чо нибудь посмотреть, или расскажите анекдот”, но вот хрен там! У тебя же недалече как пять минут назад наплыв беженцев в дверь, стуки по батарее и взрыв газа на кухне, ты, как истинный блоггер, в тех же обработчиках прерываний написал себе “Ы! Об этом надо написать в блог!” и теперь, увидев эти пометки начал яростно строчить тексты, красочно описывая случившиеся события.
Для этих целей в AVR есть удобный флаг Т в регистре SREG. Он ни за что не отвечает и сделан для пользовательских целей, т.е. для свободного использования.
А теперь фактический материал.
Беллетристика закончилась, принцип понятен, расскажу как все это реализовано.
Программа записана в памяти и каждой команде соответствует свой адрес. А также в каждом без исключения процессоре, в том числе и в AVR, есть счетчик команд который содержит адрес следующей выполняемой команды. При выполнении каждой команды происходит увеличение счетчика на один или на два, в зависимости от того сколько байт занимала команда. Если в этот счетчик принудительно записать адрес какого либо операнда, то на следующем такте процессор перейдет выполнять код начиная с записанного адреса. В AVR из программы до счетчика никак не добраться, но это не важно, для работы с этим счетчиком есть другие средства - команды перехода и вызова.
Например команда JMP addres записывает свой операнд в программный счетчик, осуществляя переход на нужный адрес. RJMP offset это относительный переход, делает то же самое, что и JMP address, только она прибавляет к счетчику команд число, обеспечивая переход на столько то команд вперед или назад, относительно текущей. Это позволяет сократить размер команды с четырех у JMP addres до двух байт. Но максимальное смещение в команде RJMP может быть не больше чем 2048 команд. Так что если вдруг компилятор ругается, что не может сослаться, то меняйте RJMP на JMP. Есть еще команда IJMP которая работает еще хитрей - она запихивает в программный счетчик адрес, содержащийся в регистровой паре Z (R30:R31).
Стек
Также почти в любом процессоре есть особая область памяти - Стек. Размещается она, как правило, в ОЗУ в самом конце. Работает по принципу пачки тарелок. То есть какую тарелку положили последней, ту и взяли первой.
Самая первая ячейка памяти стековой памяти имеет адрес конца оперативки. Для меги 8 это будет адрес 045Fh (кстати, есть стандартное макроопределение RAMEND, указывающая конец памяти для каждого контроллера) с каждым новым значением положенным в стек адрес уменьшается на один. Таким образом, стек разрастается навстречу данным, лежащим в ОЗУ в начальных адресах памяти. Что будет когда стек вырастет до такой степени, что захавает данные оперативки? А капец будет! УПЯЧКА ПЫЩЬ ПЫЩЬ! Произойдет срыв стека, контроллер пойдет в разнос и начнет выполнять невесть что.
Также есть регистровая пара SP (Stack Pointer) — указатель стека, в котором указан адрес вершины стека. Когда в стек что либо кладется, то SP уменьшается, а когда достается, то увеличивается.
Для того, чтобы положить что либо в стек есть команда PUSH, чтобы достать - команда POP.
PUSH R14 ; Положить в стек R14
PUSH R16 ; Положить в стек R16
. . .
POP R16 ; Достать из стека R16
POP R14 ; Достать из стека R14
!!!ВНИМАНИЕ!!!
Доставать данные из стека нужно в обратном порядке!!! И сколько вы туда положили, столько же оттуда надо и достать! Зарубите себе это на носу, если ошибетесь то огребёте такие баги и глюки, что хрен найдете концы потом.
!!!ВНИМАНИЕ!!!
Вернемся к нашим баранам, т.е. к прерываниям и процедурам.
При вызове процедуры текущее значение счетчика команд, т.е. адрес того места в программе где мы находимся, пихается в стек. А в счетчик команд загружается адрес вызываемой процедуры и процессор улетает выполнять другой кусок кода.
В конце каждой процедуры должна стоять функция выхода из процедуры RET. Она возвращает все взад, а именно — достает из стека адрес первоначального положения в программе и пихает его в счетчик команд. После чего программа продолжает выполнять код как ни в чем ни бывало.
Процедуры вызываются командами CALL addres, RCALL offset , ICALL.
Разница между ними такая же как и с JMP‘ами. CALL - адресует все, но занимает 4 байта, RCALL может дотянуться только в пределах 2048 команд, зато короче, а ICALL вызывает процедуру адрес которой лежит в регистровой паре Z.
Возврат из процедуры осуществляется командой RET
Прерывание ведет себя похоже, но несколько иначе. При возникновении прерывания текущий адрес аппаратно суется в стек, а в программный счетчик запихивается адрес вектора прерываний. У каждого прерывания есть свой фиксированный адрес-вектор куда будет отправлен процессор при возникновении прерывания. Все эти вектора собраны в кучу в самом начале памяти программ и составляют таблицу векторов прерываний.
Вектора указывают на ячейки друг за другом. Так как у нас в одну ячейку можно сунуть только одну команду, а обработчик прерывания обычно занимает куда больше места, то выбора у нас особого нет - в эту ячейку мы пишем RJMP и упрыгиваем туда, где просторней и можно писать много, долго и счастливо. Если какое то прерывание нам не требуется, то от греха подальше лучше его заглушить командой RETI.
После обработки прерывания возврат к основной программе осуществляется командой RETI
Приоритеты прерываний в контроллере AVR.
Таблица приоритетов прерываний в контроллере AVR… отсутсвует!!! ТАДАМ! А как там быть? Куда бежать, открывать дверь или гадить в сортир? Поскольку тут прерывание имеет каждая шняжка, а шняжек этих в AVR столько, что всех пальцев на руках и ногах не хватит пересчитать, да еще в зависимости от того какой контроллер они все разные. В общем создатели мудро решили, что ну их нахрен эти приоритеты и при вызове какого либо прерывания оно тупо запрещает все остальные. Короче, кто раньше встал того и тапки. Тупо и однобоко? Вовсе нет! Если обработчик считает что он лох и есть обработчики и покруче его, то он разрешает прерывания внутри себя сам. Командой SEI (установить бит глобального разрешения прерываний) вначале обработчика. Вот так просто. Но что делать если пока один обработчик ходил в тапках, т.е. исполнялся, проснулась еще парочка прерываний? Кому отдать управление следующему? А тому у кого адрес в таблице векторов младше! И так по порядку.
Защита регистров
Не заметили западла в описании прерываний и процедур? А оно есть! Смотрите, у нас в стеке сохраняется ТОЛЬКО адрес, а ведь есть еще и SREG, хранящий условия, и куча регистров, которые тоже используются. И вот представь загрузил ты в R16 число и думаешь, ааа как я его сейчас проинкрементирую!!! А тут, обана! Прерывание! И там, в обработчике, этот несчастный регистр возможно отымеют по полной программе в хвост и в гриву, он начисто потеряет свое прежнее значение, а значит по выходу из прерывания нас будет ждать очередная Упячка. Как спастись? Да элементарно! При входе в прерывание все регистры которые используются в обработчике прерываний пихаются в стек, а перед выходом из прерываний достаются в обратном порядке. То же можно юзать и в процедурах, но уже не так фанатично, т.к. процедуры вызваются в конкретном месте и мы точно знаем какие регистры стоит спасать, а какие нет.
В коде выглядит это так:
; Допустим, у нас в обработчике используются регистры R16 и R17
Interrupt: PUSH R16 ; Сохраняем R16
IN R16,SREG ; Выковыриваем статусный регистр
PUSH R16 ; Сохраняем статусный регистр в стек
PUSH R17 ; Нычим регистр R17
; Все, критические данные сохранены. Можно писать обработчик
; _______________________________________________
; Вот тут располагается код обработчика прерываний
; _______________________________________________POP R17 ; Достаем из стека R17
POP R16 ; Достаем из стека SREG
OUT SREG,R16 ; Возвращем его на место
POP R16 ; Достаем R16RETI ; Выходим из прерывания
Продолжение следует…
Данная часть посвящена Лохову С.П. - нашему преподавателю ассемблера. У которого на лекциях от ржача валялись все, но лишь те кто до этого знал ассемблер понимал о чем он говорит вообще! К нему на пары ходили исключительно поржать.
Комментарии
40 комментариев на «AVR. Учебный курс. Подпрограммы и прерывания»
Оставьте свой отзыв



А есть в данной вариации ассемблера аналог команд PUSHAD/POPAD, сохраняющих в стек и достающих из него значения всех регистров общего назначения?
[Ответить]
Нету. Тут же 32 регистра. Никакой оперативки не напасешься пихать такую массу.
[Ответить]
Начал изучать прерывания опять же на С. И вот какое дело, обработчик прерываний у меня срабатывает когда на линии PB5 меняется уровень, в обработчеке у меня простой код если PB5 = 0 и PB6= 0 то пишу ноль, исли PB6=1 пишу еденицу. Так вот за 100 ms
у меня проходит 100 едениц и нулей. Суть в чем если я просто посылаю по UART, то принимаю все биты. Но стоит мне записать биты в массив, а потом перевести их в число, как на выходе я получаю какую то биллиберду. Я вот думаю успевает ли выполнится обработчик прерывания, до возникновения другого прерывания? Или может я что не так делаю???
[Ответить]
Помогите разобраться пэжл. Есть кусок программы, приведенный ниже.
В строке 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
[Ответить]
1<<? это макрооператор языка, не команда процессора - задвинуть 1 влево на число “?”, то есть мы делаем число в котором все нули, но один бит с номером “?” установлен в 1. Ищи чему равно “?” (это должно быть макроопределение каке нибудь из серии .equ)
В строке 36 происходит обнуление бита VUART_RECEIVE в регистре Status_VUART
А в строке 37 происходит возврат к точке откуда это подпрограмму вызвали и это не строка 4, а где то явно выше ее. В строке 3 проц просто прыгает (это не CALL!!!) на метку. Выйдет по строке 37 вообще из процедуры “Control_VUART2UART:” выше, в вызвающую прогу. Не на строку 1, а куда то туда, где последний раз был “RCALL Control_VUART2UART”
Разницу между CALL и JMP/BRanch ты понял правильно. Так и есть.
Вопрос 5:
После бранча проц продолжает копать оттуда куда его послали. Возврата нет, ибо бранч не сохраняет адреса возврата.
[Ответить]
Спасибо огромное. Ты резкий до ужаса)я бы только писал столько, за сколько ты и прочел и ответил))
[Ответить]
А что это за хрень? Судя по меткам это гонит из виртуального уарта (VUART судя повсему это виртуальный) в реальный. Из Ethernet делаем UART? или чо?
[Ответить]
Вообще вся схема - это преобразователь интерфейса диагностического порта автомобиля k-line в интерфейс Bluetooth. Сам бы рад разобратся что там куда гонит. Вся прога вместе с назначениями типа .def .equ около 650 строк. Надо разобратся за 3 дня, и потом по возможности еще в протеусе смоделить(((. AVR я изучаю ровно неделю)))), контроллеры всякие - месяц))). А VUART это как я пока думаю - UART со стороны блютусника.
[Ответить]
650 строк эт немного :))))) В протеусе вряд ли смоделишь. Слишком кривая среда для моделинга сложных прог. Разве что МК будет точно такой же какие там есть. В протеусе хорошо куски программ отлаживать. Какие то узлы проверять.
3 дня это сильно. Если чо стучись в аську. Оперативней будет. Номер в инфо есть.
[Ответить]
Оки, вообще говоря, моя задача сейчас - просто нарисовать некое подобие блок-схем логики работы устройства. Комменты есть к каждой строке, так что с этим, думаю справлюсь,понимания особого это не требует. А вот с протоколами k-line и BT, AT-командами придется разобраться))
[Ответить]
Все прочитал, осознал и задался вопросом: Зачем указывать в программе на конец стека ? Неужели есть задачи, где стек сдвинут от конца SRAM, а данные лежат после стека? Почему бы производителю на зашивать в указатель стека конец SRAM ???
[Ответить]
Да сколько угодно. Например задача в которой есть вероятность что стек зохавает важные данные, а корректность работы пофигу. Тогда делаем так, чтобы стек начинался после данных и рос до упора вперед. Дальше, если стек сорвет, процессор начнет гнать ахинею, но недолго - вачдог его ребутнет и на основании сохраненных данных он может снова восстановиться и продолжить работу.
[Ответить]
про стек:
на своём втором компе Z80 (это проц) со стеком делал так, ставил его на конец видеопамяти в аккумулятор = 0 и циклом пробегал push A. очень быстро очищал экран, а затем стек на место и работаем дальше.
тут правда ещё не придумал как использовать!
есть вопрос мнемоники команд асма для АВР написанны везде и количество тактов есть, а машинный код можно гдето глянуть? чисто из любопытства посмотреть, дизасемблер ведь както работает?
например команда jmp XX в памяти занимает два байта или три? чет совсем уже все забыл :(
[Ответить]
Легкий косячек:
“В случае ассемблерной процедуры, тебе бы пришлось вначале нужно было подсунуть гуталин вместо зубной пасты”
лишнее “нужно было”
[Ответить]
))
[Ответить]
2 DI HALT:
Ну вот и я добрался до своего первого комментария, хоть читаю твои статьи уже не первый год в Хакере :) Чувак - ты реально крут, спасибо тебе за статьи и этот ресус :)
Теперь по делу. Чего-то нигде не нашел упоминания про внешние прерывания. У меня вот имеется AT90S2313, судя по заголовочнику в AVR для него, имеют место быть два внешних прерывания.
Внимание вопрос: что должно произойти, чтобы эти прерывания случились?
И второй вопрос: есть ли возможность генерации прерываний по нажатию кнопок, иными словами при переходе уровней ног настроенных на вход в инверсное состояние?
[Ответить]
Для сработки INT0 или INT1 должен измениться логический уровень на входах INT0 или INT1 соответственно. Как изменится - зависит от настроек этого прерывания.
[Ответить]
Вот черт :) слона то я и не приметил :) RTFM как говорицца. А прерывания по нажатию кнопок, видимо, возникнет только на этих ногах?
[Ответить]
Да, но есть новые АВР, например Мега48..328 у которых есть такая милая фича как прерывание по изменению на одной из 8ми ног.
[Ответить]
Можно глупый вопрос? =) А что делать, если есть некоторый цифровой датчик, генерирующий продолжительные по времени импульсы, эти импульсы МК считывает через INT0 (причём INT0 настроен на Any change, т.к. нужно посчитать продолжительность импульса) и выводит на сегментный индикатор динамически, т.е. косяк в том, что при возникновении прерывания индикация виснет на том что успело вылезти и продолжается только после спада.
[Ответить]
а из прерывания выход делается когда?
По хорошему надо так:
Прерывание настроено на 01 пришло - мы в прерывании запустили таймер, перенастроили прерывание на 10, вышли из прерывания.
Прерывание снова пришло (конец импульса). Мы в прерывании сняли показания таймера, перенастроили прерывание снова на 01 и вышли.
В итоге, прерывание возникает два раза по началу концу импульса. а все время крутится фоновая прога которая и показвает нам индикацию и прочие плюшки. А сами прерывания длятся считанные десятки команд.
[Ответить]
ага, данке шон))) Что-то я стормозил и считал время прямо в обработчике прерывания))))
ЗЫЖ А можно по подробнее про фичи с прерываниями по любой из восьми ног? Я так понял на ATTINY2313 такая штука есть? Только что-то не один симулятор 2313 не симулирует((((
[Ответить]
Цитирую: “В AVR из программы до счетчика никак не добраться”. Немного поправлю, если позволите. В принципе, можно написать типа rjmp PC+2. Это конечно, не непосредственное обращение к счетчику команд, но все-таки. Хотя я такой прием юзаю редко… И кстати, если писать JMP PC+2, то он не перескочит через команду! Нужно прибавлять 4!
А теперь можно вопрос? В третьей части в файле, где определяются векторы прерываний, стоят команды rjmp. Я вот запутался, блин. Почитал Ревича мельком ту главу где про различия для этих команд (rjmp и jmp) и сделал для себя вывод:что, мол, если работаешь с Мегой16, пиши всегда jmp и call. Не ошибешься! Размер меня не интересует, проги маленькие, места всем хватит…
Прав ли я? Или даже в мегах иногда НЕОБХОДИМО использовать rjmp и rcall?
Спасибо
[Ответить]
Ну это ты добираешься не до счетчика, а всего лишь до текущего адреса.
Счетчик, кстати, можно изменить или получить (это вызовет переход) извратскими методами.
Например таким образом: Делаем RCALL, а возвращаемся через RJMP при этом у нас в стеке окажется PC и его можно POP нуть в регистры :)
В процедуре сразе же после RCALL перехода можно сделать:
POP R16 достать адрес возврата
POP R17
MOV R18,R16 Скопировать его
MOV R19,R17 в безопасное место
PUSH R17 положить его на место.
PUSH R16
А можно не только скопировать но и хитро изменить! Тогда по RET мы выйдем не туда откуда зашли, а туда куда хотим. Редкостный изврат, но порой так прикольно :) За такие крышесносные хохмы я и люблю ассемблер :)
Этаким образом можно замутить адский конечный автомат… ууухххх… но мозг взорвет сразу :)
Ну и тому подобные извраты
[Ответить]
Так как вопрос остался неотвеченным:
Или даже в мегах иногда НЕОБХОДИМО использовать rjmp и rcall?
ИМХО, если вектор прерывания позволяет (по размеру) использовать JMP/CALL (которые длиннее RJMP/RCALL), то можно и их использовать.
Соответственно, в Меге8 (видимо - я в даташит еще не смотрел :-) ) вектор прерывания имеет места всего на 2 байта, поэтому там просто приходиться использовать короткие переходы.
[Ответить]
А подскажите пожалуйста!
Программу пишу в 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”)?
[Ответить]
Протеус грузит в себя хекс и ему должно быть пофигу как там на файлы разбита прога. Возможно глюк протеуса.
Вынести в один файл можно, я так сделал - у меня есть отдельный vectors.asm где все вектора и переходы. Но там пришлось поиграть с ORG метками ,чтобы компилятор не ругался. В общем. не удалось мне ORG000 оставить до инклюда, и пришлось пихать ее внутрь.
[Ответить]
Понятно, спасибо.
А может кто-то сталкивался с такими глюками, часто встречаю коменты о том, что в Proteuse лажа, а в железе все нормально.
Потому как пока в эмуляции, можно и помучатся, главное чтобы в железе все работало… Очень не хочется потом тратить время на переделку программы.
Если никто не ответит - буду первый, кто попробует это сделать, потом отпишусь. :)
Да, кстати, дайте рекомендации по составу программы в целом, что ставить в начало, что в середине, что в конце. Где расположить главный цикл программы. Я понимаю, что у каждого свои привычки, поэтому и прошу р е к о м е н д а ц и и.
[Ответить]
Ну обычно такая компоновка всегда
СТарт.
Инициализация
главный цикл
подпрограммы
прерывания
Прерывания дальше потому как до них проще дострелить из вектора каким нибудь JMP.
Сама архитектура - либо диспетчер RTOS либо флаговый автомат. Я обычно на диспетчере делаю, удобней. В универах учат флаговым автоматам обычно.
[Ответить]
Спасибо…
[Ответить]
Скоро будет подробный разбор стандартных скелетов программ. Суперцикл, флаговый автомат, диспетчер, RTOS кооперативная, RTOS выстесняющая со всеми ее недостатками и достоинствами.
[Ответить]
В AVR Studio4 неполучается инициализировать стек
.
.
.
ldi Temp,RamEnd
out SPL,Temp
.
.
.
Пишет что C:\NEW\A1\A1.asm(12): error: Operand(s) out of range in ‘ldi r16,0×45f’
Пожалуйста объясните в чём ошибка???
Контроллер ATmega8. На других МК тоже самое, только у ATiny2313 вроде как работает.
[Ответить]
ldi Temp,low(RamEnd)
out SPL,Temp
ldi Temp,high(RamEnd)
out SPH,Temp
так надо. У тебя тут рамэнд двухбайтный получается, т.к. у меги 8 памяти больше чем у тини.
[Ответить]
Вопрос по теме прерываний. Только не на асме, а на Си.
1. Есть программа: “начало программы - основной цыкл программы”. Программа крутится в основном цыкле WHILE (1). После обработки преривания мы идём в “начало программы”. Можно ли организовать такое выполнение прерывания?
2. Можно ли зделать так чтобы прерывание возникало при переходе 0-1 или 1-0, при этом 1 или 0 на ножке, по которой разрешено прерывание будет находится до следущего перепада 1 - 0 или 0 - 1 ?
Спасибо.
[Ответить]
1) как то можно было сделать. С помощью описания обработчика. Есть там какие то хитрые модификаторы. А можно хакнуть - по окончании обработки вызвать какое нибудь неинициализированное прервыание программно.
2) Можно, но тебе придется в обработчике события перепрограммировать это прерывание на другой фронт. Только и всего.
[Ответить]
Таблица прерываний это адреса куда перейдет выполнение программы после наступления какого либо события. Эти адреса жестко “прописаны” в структуре МП, и при наступлении кокого либо из них процессор автоматически перейдет на вполнение в эту ячейку. А в этой ячейке команда безусловного перехода RJMP (JMP). Если нам это прерывание не нужно но наступить оно все таки может то мы пишем RETI. Тогда получается если нам нужно использовать только первое и последнее прерывание, то чтоб заполнить адреса в памяти нужно писать
rjmp INT0 ; первое прерывание
reti
reti
….
reti
rjmp INTn ; последнее прерывание
либо
rjmp INT0 ; первое прерывание
.org адрес
rjmp INTn ; последнее прерывание
а остальные запрещаем
Я все правильно понял? ))
[Ответить]
Можно сделать с орг, но лучше забить все неиспользованные прерывания командой RETI один фиг мы это место юзать не будем, но так надежней.
[Ответить]
Подскажите пожалуйсто,в чем проблема. Использовал прерывания в Attiny2313 - программа работала как часики. Нужно было переписать прогу на ATmega16. Вот тут и начались фокусы: после того, как было вызвано прерывание, программа пререшла, как и положено на метку по вектору прерывания. Когда прерывание закончилось командой reti, курсор прыгнул не в то место, откуда было вызвано прерывание, а вообще не понятно куда, где-то в начало программы. Таблицу векторов прерывания, как и в Attiny2313 разместил в начале программы.
[Ответить]
У тини2313 стековый указатель однобайтный, а у меги16 двухбайтный. Ты не забыл загрузить второй байт SP?
[Ответить]
Клево, заработало))! Спасибо, DI HALT!
[Ответить]