Скачать 146.24 Kb.
|
Вступление ко второй части В первой части нами были рассмотрены те самые примитивные основы программирования на ассемблере для AVR, без которых вообще никак нельзя обойтись. Теперь мы перейдем к изучению некоторых возможностей и особенностей, без которых, в принципе, обойтись можно, но на практике никто не пытается так "обходиться". Это как с забиванием гвоздей – в принципе гвоздь можно забить без молотка – камнем или подсвечником – однако к таким мерам следует прибегать лишь в самых крайних случаях. 8. Стек Перед тем, как обсуждать подпрограммы и прерывания нам нужно разобраться со стеком. Стек – специальное вместилище для данных, довольно популярное в архитектуре микропроцессоров. Поясняют его чаще всего на "бородатом" примере винтовочного магазина или стопки тарелок. Мы обращаться к этим примерам не будем, уж слишком они затерты. Вкратце речь идет о следующем: программист при необходимости может командой PUSH "затолкнуть" значение из какого-либо регистра в стек (сохранить на стеке) – при этом все, что было раньше в стеке, не исчезает, а как бы опускается "глубже". В дальнейшем можно будет извлечь сохраненное значение командой POP ("вытолкнуть"). Работу стека продемонстрируем грубой аналогией при помощи следующей программы на Паскале.
Описанные в этой программе процедуры Push и Pop в процессорах всегда реализованы аппаратно, так что единственное о чем программисту надо позаботиться – это инициализация стека в начале программы. Как видно, стек состоит собственно из "тела" стека – некоторой области памяти, куда записываются сохраняемые на стеке значения – и указателя стека – специального регистра, хранящего текущий адрес "вершины" стека и показывающий, куда будет записано следующее число. В нашем примере, как видим, стек по мере заполнения "растет" вниз, от старших адресов к младшим (хотя казалось бы логичнее сделать наоборот). Такая организация стека соответствует аппаратной его реализации во многих процессорах, в частности в тех контроллерах ATMEL, в которых есть оперативная память. В этом случае тело стека размещается в оперативной памяти, а указатель стека с самого начала устанавливают на самый старший адрес этой памяти. Другие же данные (например, переменные, массивы чисел, таблицы данных) в оперативной памяти размещают обычно начиная с младших адресов. Поэтому стек размещается в неиспользуемой части памяти и потихоньку начинает спускаться к используемой. Если программист будет невнимателен, стек может легко затереть данные (или наоборот), что приведет к совершенно непонятным ошибкам во время выполнения. В архитектурах где память данных и память кода совмещены стек может также "столкнуться" с исполнимыми кодами программы. Последствия бывают совершенно мрачными. У некоторых маленьких процессоров (ATtiny12, ATtiny15) оперативной памяти нет и стек сделан в виде отдельного устройства с собственной памятью на 6 байт. Инициализировать такой "встроенный" стек в начале программы не нужно, описанных выше проблем там не бывает, однако и функциональность стека довольно ограничена. В частности, инструкции PUSH и POP в этих процессорах отсутствуют. Стек был очень важен в процессорах семейства x86, потому что они имели мало общих регистров (всего 8 байт или 4 двухбайтовых слова), а запись данных в стек происходила намного быстрее, чем запись их в оперативную память (хотя стек тоже размещался в памяти). В процессорах ATMEL регистров довольно много, а время выполнения почти всех инструкций составляет всего 1 такт. В связи с этим стек играет значительно меньшую роль для временного хранения данных. Он используется здесь (как и почти везде) большей частью для обслуживания механизма обращения к подпрограммам. 9. Подпрограммы Рассмотрим один не очень хороший пример программы, мигающей светодиодом.
Обратим внимание на то, что задержка с помощью цикла использована дважды. Каждый раз под этот фрагмент занимает по 3 слова в памяти программ. Хуже всего, что если мы захотим что-либо исправить в задержке, мы должны будем исправить оба фрагмента (один поправить, скопировать в другой, изменить названия меток). Хорошая идея – разместить этот фрагмент кода отдельно и обращаться к нему из разных мест программы. Конечно в этом случае нам очень хотелось бы иметь способ возвращаться по окончании задержки именно в ту точку программы, откуда мы эту задержку "вызвали". Для осуществления этой задачи существует пара инструкций RCALL и RET – они собственно и позволяют организовывать процедуры. RCALL (Relative Call – относительный вызов подпрограммы) принимает в качестве аргумента адрес ячейки в памяти программ и осуществляет переход к этому адресу, точно так же, как это делает инструкция RJMP, однако перед этим она помещает текущее значение счетчика инструкций в стек. А счетчик инструкций (Program Counter) в это время указывает на адрес ячейки памяти программ, в котором расположена следующая за RCALL инструкция. Таким образом можно сказать, что RCALL делает то же самое, что пара инструкций PUSH PC и RJMP. Только вот "инструкцию" PUSH PC не стоит пытаться писать в программе – как минимум вы получите сообщение о неправильном типе операнда. Кроме того PC это двухбайтовый регистр, поэтому на самом деле в стек помещается не один байт а целых два. RET (Return – возврат) – попросту выталкивает адрес с верхушки стека в счетчик команд (Program Counter), благодаря чему происходит переход на адрес, который следовал после предыдущего обращения к инструкции CALL. Перепишем нашу программу, выделив фрагмент формирования задержки в отдельную процедуру и запустим ее в симуляторе, чтобы посмотреть, как это работает.
Программа была размером в 13 слов и осталась размером в 13 слов. Однако если бы мы переместили строку "out PORTB,r16" стоящую перед обоими вызовами процедуры внутрь этой процедуры, программа стала бы размером всего в 12 инструкций и мы бы уже сэкономили 1 слово. Кроме того, как уже сказано выше, эта программа неправильна и настоящий цикл задержки пишется несколько сложнее и занимает больше инструкций, так что разница будет больше. Выполним эту программу в симуляторе, и посмотрим, как осуществляются переходы по инструкциям RCALL и RET. К сожалению посмотреть содержимое встроенного стека контроллера ATtiny15 в симуляторе AVR Studio нельзя, впрочем, все видно и без него. Важно помнить, что стек не безграничен. У ATtiny15, как уже было сказано, он вообще встроенный и имеет размер 6 байт. Сохранить на нем, поэтому, можно лишь 3 адреса – а значит глубина вложенности процедур не может быть больше трех. Например, следующая программа не будет выполняться правильно (на ATtiny15).
Симулируйте эту программу, трассируйте ее и вы увидите, как она "застревает" при опустошении стека и очередная команда RET возвращает ее на свой же собственный адрес. Неприятная особенность инструкций CALL и RET заключается в том, что время их выполнения составляет 3 и 4 такта соответственно, поэтому слишком часто выполняемые фрагменты оформлять в виде процедур не следует, лучше напротив их несколько раз скопировать, пожертвовав памятью программ, но сэкономив время. В нашем примере два вызова и возврата добавили 14 тактов на каждый период переключения светодиода. Поскольку период переключения на самом деле должен измеряться десятыми долями секунды, эти 14 микросекунд большой роли для нас, в данном случае, не играют. 10. Прерывания Познакомимся со специальным видом подпрограмм – он довольно важен и, хотя крайне полезен, объяснения по нему немного запутаны и скучны. Тем не менее их нужно понять. Рассмотрим программу, включающую и выключающую светодиод (припаянный к PB0) каждый раз, когда пользователь нажимает кнопку, припаянную между PB2 и общим проводом (GND).
Здесь для разнообразия мы используем специальные инструкции включающие и выключающие отдельные биты в служебных регистрах (SBI и CBI), а также инструкцию SBIC, проверяющую состояние одного из таких битов и пропускающего следующую команду, если этот бит равен нулю. Операндами этих инструкций является номер служебного регистра (в оригинале они называются IO-registers, отсюда и буква I в названиях инструкций) и номер бита (младший бит имеет 0-й номер, старший 7-й). К сожалению эти инструкции работают только с 32 младшими служебными регистрами. Работать со служебными регистрами начиная с номера $20 и заканчивая номером $3F можно только целиком загружая и выгружая их. Когда какая-нибудь ножка сконфигурирована на ввод, запись единицы в соответствующий бит служебного регистра PORTB подключает внутренний резистор между этой ножкой и линией питания (VCC, +5В) – благодаря этому ножка оказывается как бы "притянутой" к уровню логической "1". При этом ножку можно будет безопасно замыкать на землю (GND) чтобы создать на ней логический "0", поскольку резистор этот довольно большой. Если такая "притяжка" не сделана, то когда ножку отключают от общего провода (GND) она остается как бы подвешенной и на ней получается неопределенное состояние – тогда к логической "1" ее придется притягивать искусственно, либо же использовать кнопку с тремя контактами, которая отключая ножку от общего провода будет сразу подключать ее к плюсу питания. Несмотря на все эти новшества, сама по себе программа плохая. Дело в том, что она должна переключать светодиод, когда кнопка нажата – и она так и будет переключать его все время, пока эта кнопка нажата. Даже если пользователь нажмет кнопку всего на 100мс, за это время программа попытается переключить светодиод несколько десятков тысяч раз. Какое из двух состояний окажется на ножке PB0 в момент размыкания контактов кнопки – это вопрос чистой случайности. Так что эта программа скорее "имитатор игры в чет/нечет". Чтобы переключение происходило только раз, необходимо улучшить программу, чтобы она осуществляла переключение, например, только если в текущем цикле на PB2 уже "0" а в предыдущем была еще "1" – таким образом мы будем выделять "отрицательные фронты" логического сигнала, то есть перепады с 1 в 0. Впрочем, эту же программу можно написать и с использованием внешнего прерывания, называемого "INT0" (это не прерывание номер ноль!), которое может происходить при смене логического уровня на PB2. Прерывание – особый вид подпрограммы, которая вызывается процессором не по желанию пользователя, а при наступлении каких-либо специфических обстоятельств, например при изменении логического уровня напряжения на одной из ножек, при срабатывании внутреннего таймера, при завершении длительной операции записи из перепрограммируемого ПЗУ данных. Мы до сих пор не встречались с прерываниями потому, что изначально, при запуске микроконтроллера, все прерывания запрещены. Если же программист, устанавливая соответствующие биты в некоторых служебных регистрах, разрешает прерывания, то ему следует ожидать, что в любой момент выполнения его программы (точнее по окончании выполнения любой инструкции) может случиться прерывание. В этом случае процессор сохраняет на стеке адрес инструкции, которую он должен начать выполнять и переходит к определенной программистом подпрограмме обработки данного прерывания. Если программист все сделал правильно, то после выполнения такой подпрограммы микроконтроллер должен "навести порядок", после чего вытолкнуть из стека адрес возврата в счетчик инструкций и, таким образом, вернуться к нормальному исполнению программы. Прежде чем рассмотреть пример, остается выяснить, как микроконтроллеру задается адрес подпрограммы, которая должна обрабатывать какое-то конкретное прерывание. Во многих системах для этого используется таблица векторов прерываний. Вектором прерывания называется указатель, в той или иной форме поясняющий, куда процессор должен обратиться в случае возникновения прерывания. Такие указатели собираются в массив, размер которого равен максимальному количеству прерываний в системе и записывается в условленное место памяти. У ATMEL память программ и данных разделена и в связи с этим оказалось удобным записывать таблицу векторов прерываний в самом начале памяти программ. Вектора записываются в виде инструкций переходов на метку, поставленную программистом в начале соответствующей подпрограммы обработки прерывания. Таким образом, например, при возникновении запроса на прерывание № 5, если это прерывание разрешено, процессор внутри себя формирует инструкцию RCALL $005 и выполняет ее. При этом он перейдет на инструкцию с номером 5 (счет, помним, начинается с нуля) и обнаружит там переход на нужную метку. Отдельно надо сказать о векторе прерывания № 0. Этому вектору не соответствует никакое прерывание, однако выполнение программы начинается именно с 0-го адреса – поэтому обычно по этому адресу записывается инструкция перехода к началу программы. Рассмотрим, все сказанное на примере.
Сразу обратим внимание: программа стала длиннее из-за необходимости записать вектора прерываний и произвести действия по настройке нужного прерывания. Однако она стала эффективнее, поскольку главный цикл, в который уходит программа в ожидании нажатии кнопки теперь состоит только из одной инструкции, а не из двух. Это значит, что программа сможет реагировать на кнопку, в принципе, на каждом такте. Кроме того, мы теперь можем спокойно заниматься своими делами (например, играть песенку про "чижика-пыжика" на наушнике, припаянном к другой ножке контроллера) – когда пользователь нажмет кнопку, обработка этого события случится сама собой! Однако, запишем некоторые пояснения текста программы. Внимательно посмотрев мы можем заметить, что в целом добавились три операции: - запись "1" в шестой бит служебного регистра GIMSK (номер этого регистра $3B, поэтому мы не можем работать с его битами по-отдельности) – этот регистр называется General Interrupt Mask – общая маска прерываний содержит биты, разрешающие отдельные прерывания по некоторым событиям – в частности бит, разрешающий прерывание от ножки PB2 (INT0) является шестым; - запись "0b10" в два младших разряда регистра MCUCR (MicroContorollerUnit Control Register – регистр управления микроконтроллером) – определяет при каком именно переключении напряжения на ножке PB2 будет происходить прерывание – здесь существует четыре разных варианта, из которых мы выбираем обратный (или отрицательный) фронт – переключение из "1" в "0"; полную таблицу можно найти в руководстве по микроконтроллеру; - установка флага прерываний (в слове состояния процессора SREG) командой SEI необходима потому, что когда этот флаг сброшен (равен нулю) прерывания происходить не будут даже если они разрешены соответствующими битами в нужных служебных регистрах. Таким образом видим, что для разрешения прерывания нужно установить в "1" целых два бита – собственно бит, допускающий выполнение этого прерывания – и кроме того глобальный флаг прерываний. Похожим образом, например, комната для хранения оружия в казарме закрывается двумя замками – один ключ получает начальник подразделения, а второй – дежурный – поэтому открыть оружейную они могут только вдвоем. Кроме того обратим внимание, что подпрограмма обработки прерывания завершается командой RETI (Return from Interrupt – возврат из прерывания) вместо обычного RET. Разница заключается собственно в том, что команда RETI устанавливает обратно в "1" флаг прерываний, который был сброшен заботливым процессором при вызове подпрограммы обработки прерывания. Это делается для того, чтобы процессор не прерывался новым прерыванием, пока обрабатывает старое. Необходимо симулировать выполнение этой программы, чтобы посмотреть, как она работает. Здесь нам следует познакомиться с некоторыми дополнительными возможностями AVR Studio а кроме того попробовать сделать это в другом симуляторе VMLAB. Этому мы и посвятим следующий раздел. |
Организации из Великобритании и Норвегии. В 1983 году сеть перешла на стек протоколов tcp/IP Стек протоколов tcp/ip был создан группой разработчиков во главе с сэром Винтоном Грей Серфом в 1972 году. В 1976 году впервые была... |
Задание для I варианта: найти ответы на все нечетные вопросы ответы: Задание для II Какого типа будет результат при вычислении квадратного корня из какого-либо числа? |
||
Используя материал данного курса в своей работе, обучающиеся узнают... Ответ yes, of course/ sure, столь привычный для россиян, не годится «на все случаи жизни» для выражения согласия, т к он воспринимается... |
Все дело в доме. Когда все было в Самом Начале, он уселся в очень... Разве что как раньше: привести рыл пятнадцать и патроны не жалеть; да только такими толпами больше никто не собирается, невыгодно.... |
||
Как то раз, я решил что большой и громоздкий ноутбук это совсем не... Понятное дело, что решением данного вопроса, окажется покупка нетбука, субноутбука или неттопа. Да, но какого? Рынок предлагает нам... |
Наименование сортоучастка Значение сорта, как важного фактора повышения величины и качества урожая сельскохозяйственных культур, велико и общеизвестно. При... |
||
Руководство по эксплуатации электронной панели Чтобы ее изменить, используйте кнопки "больше" и "меньше". Затем новое значение можно запомнить либо нажатием кнопки "set" (прибор... |
Руководство по эксплуатации гидравлический пресс дистрибьютор в россии компания «инжтехсервис» Если оператор не полностью понимает язык руководства, то эти инструкции должны быть прочитаны и объяснены ему на его родном языке... |
||
Барометрическое нивелирование При надлежащей постановке работ с помощью барометрического нивелирования можно получить также и абсолютные высоты пунктов наблюдения,... |
Инструкция Общие понятия об Adobe Flash Player Чтобы всем всё было... Чтобы всем всё было понятно, начну с того, что существует три вида таких Флеш-плееров, при этом для каждого браузера – свой персональный,... |
||
Как расшифровывается sql? Как выбрать все записи из таблицы "Persons", где значение поля "FirstName" равно "Peter"? |
Работа с электронной почтой Ндфл, произвольных файлов. Работа с отправленными отчетами: что делать, если получен протокол проверки с ошибками; как сохранить... |
||
2 Решение задачи, удаляющей в файле текст после точки В этом стеке различают несколько уровней, и протоколы высокого уровня всегда базируются на протоколах более низких уровней. В самом... |
Курс лекций по дисциплине «Безопасность жизнедеятельности» введение Также как и в случае с физиологическими потребностями, здесь можно сказать, что все будет инструментом обеспечения безопасности:... |
||
Предмет и задачи патопсихологии Патопсихология является отраслью психологической науки. Ее данные имеют теоретическое и практическое значение для психологии и психиатрии.... |
Инструкция по подключению фр с фн к офд подключение через rndis Для того, чтобы фр работал как rndis в таблице 14 "Сетевые интерфейсы" поле 9, "rndis" должно иметь значение Значение поля по умолчанию:... |
Поиск |