Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
spo_voprosy.docx
Скачиваний:
9
Добавлен:
20.03.2016
Размер:
465.35 Кб
Скачать

24.9 Vigr

Аналогичноvipw, командаvigrможет быть использована для ручного редактирования файла/etc/group, т.к. она сделает правильную блокировку файла. Только опытные администраторы должны использоватьvi илиvigr для управления группами.

Теория.

Рассказать об Гарвардской и Фон-Неймановской структуре ядра.

Гарвардская архитектура:

  1. Хранилище инструкций и хранилище данных представляют собой разные физические устройства.

  2. Канал инструкций и канал данных также физически разделены.

  3. Разделение шин доступа к FLASH памяти и SRAM памяти дает возможность иметь шины данных для памяти данных и памяти программ различной разрядности, а также использовать технологию конвейеризации. Конвейеризация заключается в том, что во время исполнения текущей команды программный код следующей уже выбирается из памяти и дешифруется

Фон-Неймановская архитектура

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

Основное преимущество архитектуры Фон-Неймана - упрощение устройства МПС (микропроцессорной системы), так как реализуется обращение только к одной общей памяти. Кроме того, использование единой области памяти позволяло оперативно перераспределять ресурсы между областями программ и данных, что существенно повышало гибкость МПС с точки зрения разработчика программного обеспечения. Размещение стека в общей памяти облегчало доступ к его содержимому. Неслучайно поэтому фон-неймановская архитектура стала основной архитектурой универсальных компьютеров, включая персональные компьютеры.

Устройства ядра МК AVR (нарисовать примерную схему и рассказать, за что отвечает каждый из блоков)

Ядро состоит, в первую очередь, из памяти программ (Flash Programm Memory), Арифметико-логического устройства (ALU), блока управления (на диаграмме не показан) и программного счетчика (Program Counter). Также есть тактовый генератор, задающий импульсы, относительно которых работают блоки микроконтроллера.

Вся математика и обработка делается посредством ALU. Это, своего рода, калькулятор. Он может складывать, вычитать, сравнивать, сдвигать разными способами, иногда делить и умножать. В качестве промежуточных операндов используются 32 ячейки — Оперативные регистры общего назначения РОН. Доступ к этим ячейкам самый быстрый, а число операций с их содержимым наиболее богатое. В ассемблере регистры эти называются просто R0,R1,R2 … R31. Причем делятся они на три группы:

Младшие R0..R15

Обычные регистры общего назначения, но какие-то ущербные. С ними не работают многие команды, например, такие как загрузка непосредственного числа. Т.е. нельзя, например, взять и присвоить регистру число. Зато можно скопировать число из любого другого регистра.

Старшие R16..R31

Полноценные регистры, работающие со всеми командами без исключения.

Индексные R26…R31

Шесть последних регистров из старшей группы особенные. В принципе, их можно использовать и как обычные регистры общего назначения. Но, кроме этого, они могут образовывать регистровые пары X(R26:R27), Y(R28,R29), Z(R30:R31), которые используются как указатели при работе с памятью.

ОЗУ Кроме 32 регистров в микроконтроллере есть оперативная память. Правда не везде — в младших семействах AVR Tiny12 и Tiny11 оперативной памяти нет, так что приходиться вертеться в 32 ячейках. Оперативная память это несколько сотен ячеек памяти. От 64 байт до 4 килобайт, в зависимости от модели. В этих ячейках могут храниться любые данные, а доступ к ним осуществляется через команды Load и Store. То есть нельзя взять, например, и прибавить к ячейке в памяти, скажем, единицу. Нам сначала сделать операцию Load из ОЗУ в РОН, потом в регистре прибавить нашу единицу и операцией Store сохранить ее обратно в память. Только так.

EEPROM Долговременная память. Память, которая не пропадает после выключения питания. Если Flash может содержать только код и константы, а писать в нее при выполнении ничего нельзя (Это память Read Only), то в ЕЕПРОМ можно сколько угодно писать и читать. Но в качестве оперативки ее особо не используешь. Дело в том, что цикл записи в EEPROM длится очень долго — миллисекунды. Чтение тоже не ахти какое быстрое. Да и число циклов перезаписи всего 100 000, что не очень много в масштабах работы оперативной памяти. ЕЕПРОМ используется для сохранения всяких настроек, предустановок, собранных данных и прочего барахла, что может потребоваться после включения питания и в основном на чтение. Эта память есть не во всех моделях AVR, но в подавляющем их большинстве.

Устройство SRAM(нарисовать схему и написать, за что отвечает каждый из блоков)

ОЗУ микроконтроллеров AVR состоит из набора 8-разрядных регистров, пронумерованных по порядку, и делится, условно, на несколько областей. Порядковый номер регистра в области SRAM есть не что иное, как его адрес.

Первые 32 адреса SRAM занимают регистры общего назначения, те самые, что входят в состав CPU и непосредственно связаны с АЛУ. Данные регистры являются рабочей областью. Сначала сюда заносятся различные данные (это могут быть данные из какого-либо периферийного устройства или какая-то константа). Затем посредством АЛУ выполняются требуемые операции. Результат операции может быть отправлен из регистра общего назначения в периферийное устройство. Здесь также могут быть выделены регистры под промежуточные значения и буферные регистры. Шесть последних регистров общего назначения образуют три регистровых пары — 16-разрядные регистры X, Y и Z.

Как видно из рисунка, 16-разрядный регистр Х образован парой регистров общего назначения R26-R27, причем его младшим байтом XL (от low) является регистр R26, ну а старшим XH — регистр R27. Аналогичные заключения справедливы и для регистров Y и Z. При использовании специальных команд данные регистры можно использовать в качестве указателей адресов, например, при обращении к ячейкам памяти SRAM. С помощью регистра Z удобно извлекать различные константы, хранящиеся в памяти программ.

(устройство SRAM)

Непосредственно за регистрами общего назначения располагается область 64 регистров ввода/вывода. В данной области памяти располагаются регистры, посредством которых обеспечивается доступ ко всем периферийным устройствам. С помощью этих регистров осуществляется управление, настройка и запуск необходимых модулей, а также получение информации об их состоянии. Таблица регистров ввода/вывода может быть получена из технической документации на конкретный микроконтроллер. Для старших моделей Mega со сложной структурой (например, ATmega128) 64-х регистров ввода/вывода может оказаться недостаточно, поэтому для этих целей выделяется еще 160 дополнительных регистров (в адресном пространстве от $0060 до $00FF).

Сразу за областью регистров ввода/вывода располагается непосредственно внутреннее ОЗУ данных микроконтроллера. Эта область памяти используется для временного хранения некоторых переменных и констант, использующихся во время работы. Следует уточнить, что регистры ввода/вывода не отнимают адресное пространство у области внутреннего ОЗУ. Адреса внутренней оперативной памяти располагаются сразу поле адресов регистров ввода/вывода. А вот объем подключаемого внешнего ОЗУ всегда ограничен адресом $FFFF, и если в конкретной модели используется большое число регистров ввода/вывода, то объем возможной подключаемой внешней памяти уменьшается. Конечный адрес области внутреннего ОЗУ зависит от конкретной модели микроконтроллера и обычно обозначается константой RAMEND для удобства при программировании. В конце оперативной памяти обычно располагают программный стек, при отсутствии аппаратного.

Возможность подключения внешней оперативной памяти имеется только у некоторых моделей (ATmega8515, ATmega162, ATmega128, ATmega2560 и др.). Внешняя подключаемая память может быть любой статической разновидностью (FLASH, RAM, EEPROM) с параллельным интерфейсом. При подключении внешнего ОЗУ необходимо учитывать, что максимальный адрес ячейки ОЗУ, которую может использовать микроконтроллер, составляет $FFFF.

Таким образом, ОЗУ микроконтроллеров AVR условно делится на несколько областей:

- регистры общего назначения;

- регистры ввода/вывода,

- внутреннее ОЗУ и внешнее ОЗУ.

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

Определение стека. Его применение. Принцип действия стека в AVR.

Стек представляет собой область памяти, которую ЦПУ использует для сохранения и восстановления адресов возврата из подпрограмм. Практически у всех микроконтроллеров AVR стек размещается в SRAM. Для адресации текущего элемента (вершины стека) используется указатель стека SP (Stack Pointer). Это однобайтовый РВВ SPL у моделей с объемом памяти данных до 256 б, или двухбайтовый SPH:SPL (SPH – старший байт, SPL – младший байт).

Когда микропроцессор встречает одну из инструкций вызовов rcall/call/ecall/icall/eicall, то адрес следующего за ними слова в памяти программ аппаратно копируется в стек. В момент выхода из подпрограммы по команде ret адрес возврата восстанавливается из стека в программный счетчик. В моделях с объемом памяти программ 128 и 256 слов для сохранения PC в стеке потребуется 3 байта, для всех остальных – 2 байта. При сохранении каждого байта содержимое SP уменьшается на единицу, а при восстановлении, соответственно увеличивается.

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

пример использования – обмен R16 R17:

push R16 ; в стек записывается значение из R16 («верхнее значение» стека = R16)

push R17 ; в стек записывается значение из R17 («верхнее значение» стека = R17)

pop R16 ; из стека значение записывается в R16 (теперь то, что было в R17 записано в R16)

pop R17 ; из стека значение записывается в R17 (теперь то, что было в R16 записано в R17)

При старте контроллера, обычно, первым делом инициализируют стек, записывая в SP адрес его дна, откуда он будет расти. Обычно это конец ОЗУ, а растет он к началу. Делается это таким вот образом, в самом начале программы:

LDI R16,Low(RAMEND)

OUT SPL, R16

LDI R16,High(RAMEND)

OUT SPH, R16

Определение прерываний. Виды прерываний.

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

Когда приходит прерывание то контроллер завершит текущую команду (машинную инструкцию!) сразу же кинется выполнять процедуру обработки прерывания, а как выполнит, то вернется к прерванной фоновой программе.

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

Процессор узнает, куда ему переходить по вектору прерываний. Она в самом начале памяти идет. Вектор это адрес перехода. У каждого аппаратного события имеющего прерывание есть свой вектор. Аппаратных событий у AVR тьма, поэтому таблица прерываний весьма толстая, десятки адресов. Когда происходит прерывание, то ядро контроллера запоминает текущий адрес в стеке и делает прыжок на адрес вектора соответствующего прерывания. И все. Дальше наша забота — в ячейку памяти на которую указывает вектор прерывания мы вписываем свою команду RJMP которая перенаправит процессор на реальный код обработчика.

Когда процессор уходит на обработку прерываний, то аппаратно происходит запрет всех остальных прерываний. Его, конечно, можно включить программно, установив флаг I в регистре SREG, и тогда у нас будут вложенные прерывания, но обращаться с этим следует ОЧЕНЬ осторожно. Так как множественные прерывания нагружают стек, и может произойти его переполнение, что даст полный сбой работы и начнется невесть что.

По выходу из обработчика, по команде RETI флаг I вернется в прежнее состояние. У многих сразу возникнет вопрос, а что будет если во время обработки одного прерывания придет другое? У него взведется флаг и как только мы завершим первый обработчик автоматом произойдет переход к отложенному прерыванию. Единственное, что мы можем потерять количество одинаковых прерываний. Т.е. если обрабатывается, например, INT0 и прерывания запрещены, а в это время придет три раза INT1, то на выходе INT1 обработается только один раз.

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

Кратко:

  • Прерывания это аппаратные события.

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

  • По дефолту они запрещены локально и глобально.

  • Вызов прерывания может быть когда угодно и где угодно, между любыми двумя командами. (Хотя хрена там, между CLI CLI (запрет прерываний) его не будет :) )

  • В обработчике прерывания надо учитывать тот факт, что данные нужно сохранять и восстанавливать при выходе.

  • Приоритет прерываний работает по принципу «кто первый встал тот и ходит в тапках». Остальные запрещены аппаратно, но можно разрешить программно уже в обработчике.

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

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

  • Поскольку прерывание ВНЕЗАПНОЕ и может быть где угодно, то обработчик прерывания должен выполняться МАКСИМАЛЬНО КОРОТКО И БЫСТРО. Зашел, отметился, вышел. Только так. Никаких задержек и длинных циклов.

Таймер. Применение таймера. Виды прерывания таймера.

В микроконтроллерах AVR может быть до 4х таймеров/счетчиков (ТС). Разрядность этих таймеров 8 или 16 бит (т.е. они могут считать до 28=256 или до 216=65536). Обычно их используют для точного формирования временных интервалов, подсчета импульсов на выводах микроконтроллера, формирования последовательности импульсов. Таймеры способны вырабатывать запросы на прерывания, при этом освобождая процессор от необходимости опроса состояния таймеров.

Что умеют таймеры

  • Тикать с разной скоростью, подсчитывая время

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

  • Тикать от внешнего кварца на 32768гц

  • Генерировать несколько видов ШИМ сигнала

  • Выдавать прерывания (по полудесятку разных событий) и устанавливать флаги

Разные таймеры имеют разную функциональность и разную разрядность

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

То есть еще до попадания в счетный регистр частота импульсов будет делиться. Делить можно на 8, 32, 64, 128, 256, 1024. Так что если повесишь на Т/С2 часовой кварц, да пропустишь через предделитель на 128, то таймер у тебя будет тикать со скоростью один тик в секунду.

Удобно! Также удобно юзать предделитель когда надо просто получить большой интервал, а единственный источник тиков это тактовый генератор процессора на 8Мгц, считать эти мегагерцы задолбаешься, а вот если пропустить через предделитель, на 1024 то все уже куда радужней.

Для сброса предделителей достаточно записать бит PSR10 в регистре SFIOR. Бит PSR10 будет сброшен автоматически на следующем такте.

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

Если в восьмиразрядный регистр надо положить число, то нет проблем OUT TCNT0,Rx. Для 2-байтного регистра так не получится. Таймер считает независимо от процессора, поэтому мы можем положить вначале один байт, он начнет считаться, потом второй, и начнется пересчет уже с учетом второго байта.

Решение: Запись в старший регистр (TCNTxH) ведется вначале в регистр TEMP. Этот регистр чисто служебный, и нам никак недоступен.

Что в итоге получается: Записываем старший байт в регистр TEMP (для нас это один хрен TCNTxH), а затем записываем младший байт. В этот момент в реальный TCNTxH заносится ранее записанное нами значение. То есть два байта, старший и младший, записываются одновременно! Менять порядок нельзя!

CLI ; Запрещаем прерывания, в обязательном порядке!

OUT TCNT1H,R16 ; Старший байт записался вначале в TEMP

OUT TCNT1L,R17 ; А теперь записалось и в старший и в младший!

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

«Зачем запрещать прерывания? Да чтобы после записи первого байта, программа случайно не умчалась не прерывание, а там кто-нибудь наш таймер не изнасиловал. Тогда в его регистрах будет не то, что мы послали тут (или в прерывании), а черти что» ©

Читается все также, только в обратном порядке. Сначала младший байт (при этом старший пихается в TEMP), потом старший. Это гарантирует то, что мы считаем именно тот байт который был на данный момент в счетном регистре, а не тот который у нас натикал пока мы выковыривали его побайтно из счетного регистра.

Главным регистром для таймера является TCCRx. Для Т/С0 и Т/С2 это TCCR0 и TCCR2 соответственно, а для Т/С1 это TCCR1B. Нас пока интересуют только первые три бита этого регистра: CSx2.. CSx0, вместо х подставляется номер таймера. Они отвечают за установку предделителя и источник тактового сигнала.

У разных таймеров немного по разному, поэтому опишу биты CS02..CS00 только для таймера 0

  • 000 — таймер остановлен

  • 001 — предделитель равен 1, то есть выключен. таймер считает тактовые импульсы

  • 010 — предделитель равен 8, тактовая частота делится на 8

  • 011 — предделитель равен 64, тактовая частота делится на 64

  • 100 — предделитель равен 256, тактовая частота делится на 256

  • 101 — предделитель равен 1024, тактовая частота делится на 1024

  • 110 — тактовые импульсы идут от ножки Т0 на переходе с 1 на 0

  • 111 — тактовые импульсы идут от ножки Т0 на переходе с 0 на 1

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

За прерывания от таймеров отвечают регистры TIMSК, TIFR. А у более крутых AVR, таких как ATMega128, есть еще ETIFR и ETIMSK — своего рода продолжение, так как таймеров там поболее будет.

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

На данный момент нас тут интересуют только прерывания по переполнению. За них отвечают биты

  • TOIE0 — разрешение на прерывание по переполнению таймера 0

  • TOIE1 — разрешение на прерывание по переполнению таймера 1

  • TOIE2 — разрешение на прерывание по переполнению таймера 2

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

Чтобы этого не произошло флаг можно сбросить вручную. Для этого в регистре TIFR в него нужно записать 1!

Определение ШИМ. Скважность ШИМ.

Широтно Импульсная Модуляция (в буржуйской нотации этот режим зовется PWM — Pulse Width Modulation) это способ задания аналогового сигнала цифровым методом, то есть из цифрового выхода, дающего только нули и единицы получить какие то плавно меняющиеся величины.

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

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

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

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

Если взять и гнать ШИМ сигнал не от нуля до максимума, а от минуса до плюса. Скажем от +12 до -12. А можно задавать переменный сигнал! Когда на входе ноль, то на выходе -12В, когда один, то +12В. Если скважность 50% то на выходе 0В. Если скважность менять по синусоидальному закону от максимума к минимуму, то получим переменное напряжение.

Скважность - отношение длительности периода к длительности импульса

Принцип генерации ШИМ с помощью таймеров. (Нарисовать графики)

У таймера есть особый регистр сравнения OCR**. Когда значение в счётном регистре таймера достигнает значения находящегося в регистре сравнения, то могут возникнуть следующие аппаратные события:

  • Прерывание по совпадению

  • Изменение состояния внешнего выхода сравнения OC**.

Предположим, что мы настроили наш ШИМ генератор так, чтобы когда значение в счетном регистре больше чем в регистре сравнения, то на выходе у нас 1, а когда меньше, то 0.

Что при этом произойдет? Таймер будет считать как ему и положено, от нуля до 256, с частотой которую мы настроим битами предделителя таймера. После переполнения сбрасывается в 0 и продолжает заново.

Самих режимов ШИМ существует несколько:

Fast PWM

В этом режиме счетчик считает от нуля до 255, после достижения переполнения сбрасывается в нуль и счет начинается снова. Когда значение в счетчике достигает значения регистра сравнения, то соответствующий ему вывод ОСхх сбрасываться в ноль. При обнулении счетчика этот вывод устанавливается в 1. И все!

Частота получившегося ШИМ сигнала определяется просто: Частота процессора 8Мгц, таймер тикает до 256 с тактовой частотой. Значит, один период ШИМ будет равен 8000 000/256 = 31250Гц. Вполне недурно. Быстрей не получится — это максимальная скорость на внутреннем 8Мгц тактовом генераторе. Но если переключить FUSE биты на внешний кварц то можно раскачать МК на 16Мгц.

Еще есть возможность повысить разрешение, сделав счет 8, 9, 10 разрядным (если разрядность таймера позволяет), но надо учитывать, что повышение разрядности, вместе с повышением дискретности выходного аналогового сигнала, резко снижает частоту ШИМ.

Phase Correct PWM

ШИМ с точной фазой. Работает похоже, но тут счетчик считает несколько по другому. Сначала от 0 до 255, потом от 255 до 0. Вывод OCxx при первом совпадении сбрасывается, при втором устанавливается.

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

Чтобы не было кривых импульсов, то в регистр сравнения любое значение попадает через буфферный регистр и заносится только тогда, когда значение в счетчике достигнет максимума. Т.е. к началу нового периода ШИМ импульса.

Clear Timer On Compare

Сброс при сравнении. Это уже скорей ЧИМ — частотно-импульсно моделированный сигнал. Тут работает несколько иначе, чем при других режимах. Тут счетный таймер тикает не от 0 до предела, а от 0 до регистра сравнения! А после чего сбрасывается.

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

Например, надо нам прерывание каждую миллисекунду. И чтобы вот точно. Как это реализовать проще? Через Режим СТС! Пусть у нас частота 8Мгц.

Прескалер (?!) будет равен 64, таким образом, частота тиков таймера составит 125000 Гц. А нам надо прерывание с частотой 1000Гц. Поэтому настраиваем прерывание по совпадению с числом 125.

Дотикал до 125 — дал прерывание, обнулился. Дотикал до 125 — дал прерывание, обнулился. И так бесконечно, пока не выключим.

Инициализация стека.

.CSEG

LDI R16,Low(RAMEND) ; запись нижнего регистра адреса последней ячейки RAM в R16

OUT SPL, R16 ; запись нижнего регистра RAMEND как нижний регистр конца стека

LDI R16,High(RAMEND) ; запись верхнего регистра адреса последней ячейки RAM в R16

OUT SPH, R16 ; запись верхнего регистра RAMEND как нижний регистр конца стека

Написать макрос

Запись числа в один из регистров ввода/вывода

.include "m8def.inc"

.macro OUTI // Начало макроса "Запись числа в регистр ввода-вывода"

PUSH R16 // Записываем исходное значение временного регистра R16 в стек

ldi R16, @1 // Записываем во временный регистр R16 число

.if @0 < 0x40 // Not memory mapped

OUT @0, R16 // Записываем значение временного регистра R16 в РВВ

.else // Memory mapped

STS @0,R16 // Записываем значение временного регистра R16 в РВВ

.endif

POP R16 // Записываем во временный регистр R16 исходное значение из стека

.endm // Конец макроса

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

.macro CHANGE ;начало макроса "Обмен содержимого 2-ух регистров"

PUSH @0 ;сохраняем исходное значение 1-ого регистра в стек

PUSH @1 ;сохраняем исходное значение 2-ого регистра в стек

POP @0 ;записываем в 1-ый регистр исходное значение 2-ого регистра из стека

POP @1 ;записываем во 2-ой регистр исходное значение 1-ого регистра из стека

.endm ;конец макроса

ИЛИ

.macro CHANGE // Начало макроса "Обмен содержимого 2-ух регистров"

PUSH @0 // Сохраняем исходное значение 1-ого регистра в стек

MOV @0, @1 // Записываем значение 2-ого регистра в 1-ый регистр

POP @1 // Записываем во 2-ой регистр исходное значение 1-ого регистра из стека

.endm // Конец макроса

Установка определённого бита в регистре ввода/вывода

.macro OUTB // Начало макроса "Запись определенного бита в регистр ввода-вывода"

.if @2>0 // Устанавливаем бит

.if @0 < 0x20 // Low I/O

SBI @0,@1 // Устанавливаем бит на нужную позицию

.else // High I/O

.if @0<0x40 // Not memory mapped

PUSH R17 // Записываем исходное значение временного регистра R17 в стек

IN R17,@0 // Записываем во временный регистр R17 значение РВВ

ORI R17,1<<@1 // Устанавливаем бит на нужную позицию во временном регистре R17

OUT @0,R17 // Записываем измененное значение временного регистра R17 в РВВ

POP R17 // Записываем во временный регистр R17 его исходное значение из стека

.else // Memory mapped

PUSH R17 // Записываем исходное значение временного регистра R17 в стек

LDS R17,@0 // Записываем во временный регистр R17 значение РВВ

ORI R17,1<<@1 // Устанавливаем бит на нужную позицию во временном регистре R17

STS @0,R17 // Записываем измененное значение временного регистра R17 в РВВ

POP R17 // Записываем во временный регистр R17 его исходное значение из стека

.endif

.endif

.else // Сбрасываем бит

.if @0 < 0x20 // Low I/O

CBI @0,@1 // Сбрасываем бит на нужной позиции

.else // High I/O

.if @0<0x40 // Not memory mapped

PUSH R17 // Записываем исходное значение временного регистра R17 в стек

IN R17,@0 // Записываем во временный регистр R17 значение РВВ

ANDI R17,~(1<<@1)// Сбрасываем бит на нужной позиции во временном регистре R17

OUT @0,R17 // Записываем измененное значение временного регистра R17 в РВВ

POP R17 // Записываем во временный регистр R17 его исходное значение из стека

.else // Memory mapped

PUSH R17 // Записываем исходное значение временного регистра R17 в стек

LDS R17,@0 // Записываем во временный регистр R17 значение РВВ

ANDI R17,~(1<<@1)// Сбрасываем бит на нужной позиции во временном регистре R17

STS @0,R17 // Записываем измененное значение временного регистра R17 в РВВ

POP R17 // Записываем во временный регистр R17 его исходное значение из стека

.endif

.endif

.endif

.endm // Конец макроса

Написать одну из следующих программ

Затухание светодиода

.include "m8def.inc"

.macro OUTI // Начало макроса "Запись числа в регистр ввода-вывода"

PUSH R16

ldi R16,@1

OUT @0,R16

POP R16

.endm // Конец макроса

.CSEG

JMP INT_VECTORS_SIZE // Переход к концу таблицы прерываний, чтоб не выполнить прерывание в начале программы

.ORG OVF2addr // Прерывание Timer/Counter2 Overflow

RJMP Overflow // Переход на метку Overflow, если случилось прерывание по переполнению

.ORG OC2addr // Прерывание Timer/Counter2 Compare Match

RJMP Compare // Переход на метку Compare, если случилось прерывание по совпадению

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

ldi R16,low(RAMEND) // Инициализация стека

OUT SPL,R16

ldi R16,high(RAMEND)

OUT SPH,R16

OUTI DDRC, (1 << PC0) // Конфигурирование ножки PC0 на выход

OUTI PORTC, (1 << PC0) // Подать на ножку лог.единицу

OUTI TIMSK,(1<<TOIE2),(1 << OCIE2)// Разрешение прерывания по переполнению и совпадению

OUTI TCCR2, (1<<CS21) // Предделитель на 8

OUTI OCR2, 255 // Устанавливаем значение 255 в регистр сравнения

sei

M1: NOP

RJMP M1 // Бесконечный цикл

Compare:

cli

PUSH R17 // Записываем в стек значение временного регистра R17

PUSH R18 // Записываем в стек значение временного регистра R18

IN R17,PINC // Считываем состояние ножки PC0

// Меняем состояние ножки PC0 на противоположное

ldi R18,(1 << PC0)

EOR R17,R18

OUT PORTC,R17

sei

POP R18 // Записываем во временный регистр R18 исходное значение из стека

POP R17 // Записываем во временный регистр R17 исходное значение из стека

RETI

Overflow:

cli

PUSH R17 // Записываем в стек значение временного регистра R17

PUSH R18 // Записываем в стек значение временного регистра R18

IN R17,PINC // Считываем состояние ножки PC0

// Меняем состояние ножки PC0 на противоположное

ldi R18,(1 << PC0)

EOR R17,R18

OUT PORTC,R17

IN R18, OCR2 // Считываем значение регистра сравнения

subi R18, 1 // R18 --

OUT OCR2, R18 // Заносим измененное значение в регистр сравнения

POP R18 // Записываем во временный регистр R18 исходное значение из стека

POP R17 // Записываем во временный регистр R17 исходное значение из стека

sei

RETI

Плавное повышение яркости светодиода

.include "m8def.inc"

.macro OUTI // Начало макроса "Запись числа в регистр ввода-вывода"

PUSH R16

ldi R16,@1

OUT @0,R16

POP R16

.endm // Конец макроса

.CSEG

JMP INT_VECTORS_SIZE // Переход к концу таблицы прерываний, чтоб не выполнить прерывание в начале программы

.ORG OVF2addr // Прерывание Timer/Counter2 Overflow

RJMP Overflow // Переход на метку Overflow, если случилось прерывание по переполнению

.ORG OC2addr // Прерывание Timer/Counter2 Compare Match

RJMP Compare // Переход на метку Compare, если случилось прерывание по совпадению

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

ldi R16,low(RAMEND) // Инициализация стека

OUT SPL,R16

ldi R16,high(RAMEND)

OUT SPH,R16

OUTI DDRC, (1 << PC0) // Конфигирурирование ножки PC0 на выход

OUTI PORTC, (1 << PC0) // Подать на ножку лог.единицу

OUTI TIMSK,(1<<TOIE2),(1 << OCIE2)// Разрешение прерывания по переполнению и совпадению

OUTI TCCR2, (1<<CS21) // Предделитель на 8

OUTI OCR2, 0 // Устанавливаем значение 0 в регистр сравнения

sei

M1: NOP

RJMP M1 // Бесконечный цикл

Compare:

cli

PUSH R17 // Записываем в стек значение временного регистра R17

PUSH R18 // Записываем в стек значение временного регистра R18

IN R17,PINC // Считываем состояение ножки PC0

// Меняем состояние ножки PC0 на противоположное

ldi R18, (1 << PC0)

EOR R17,R18

OUT PORTC,R17

POP R18 // Записываем во временный регистр R18 исходное значение из стека

POP R17 // Записываем во временный регистр R17 исходное значение из стека

sei

RETI

Overflow:

cli

PUSH R17 // Записываем в стек значение временного регистра R17

PUSH R18 // Записываем в стек значение временного регистра R18

IN R17,PINC // Считываем состоение ножки PC0

// Меняем состояние ножки PC0 на противоположное

ldi R18, (1 << PC0)

EOR R17,R18

OUT PORTC,R17

IN R18, OCR2 // Считываем значение регистра сравнения

subi R18, -1 // R18 ++

OUT OCR2, R18 //Заносим измененное значение в регистр сравнения

POP R18 // Записываем во временный регистр R18 исходное значение из стека

POP R17 // Записываем во временный регистр R17 исходное значение из стека

sei

RETI

Затухание светодиода, при достижении состояния, когда светодиод перестал гореть, плавно повышать его яркость до максимума.

.include "m8def.inc"

.macro OUTI // Начало макроса "Запись числа в регистр ввода-вывода"

PUSH R16

ldi R16,@1

OUT @0,R16

POP R16

.endm // Конец макроса

.CSEG

JMP INT_VECTORS_SIZE // Переход к концу таблицы прерываний, чтоб не выполнить прерывание в начале программы

.ORG OVF2addr // Прерывание Timer/Counter2 Overflow

RJMP Overflow // Переход на метку Overflow, если случилось прерывание по переполнению

.ORG OC2addr // Прерывание Timer/Counter2 Compare Match

RJMP Compare // Переход на метку Compare, если случилось прерывание по совпадению

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

ldi R16,low(RAMEND) // Инициализация стека

OUT SPL,R16

ldi R16,high(RAMEND)

OUT SPH,R16

OUTI DDRC, (1 << PC0) // Конфигурирование ножки PC0 на выход

OUTI PORTC, (1 << PC0) // Подать на ножку лог.единицу

OUTI TIMSK,(1<<TOIE2),(1 << OCIE2)// Разрешение прерывания по переполнению и совпадению

OUTI TCCR2, (1<<CS21) // Предделитель на 8

OUTI OCR2, 255 // Устанавливаем значение 255 в регистр сравнения

ldi R19, 0 // Используем как флаг

sei

M1: NOP

RJMP M1 // Бесконечный цикл

Compare:

cli

PUSH R17 // Записываем в стек значение временного регистра R17

PUSH R18 // Записываем в стек значение временного регистра R18

IN R17,PINC // Считываем состояние ножки PC0

// Меняем состояние ножки PC0 на противоположное

ldi R18, (1 << PC0)

EOR R17,R18

OUT PORTC,R17

POP R18 // Записываем во временный регистр R18 исходное значение из стека

POP R17 // Записываем во временный регистр R17 исходное значение из стека

sei

RETI

Overflow:

cli

PUSH R17 // Записываем в стек значение временного регистра R17

PUSH R18 // Записываем в стек значение временного регистра R18

IN R17,PINC // Считываем состояние ножки PC0

// Меняем состояние ножки PC0 на противоположное

ldi R18, (1 << PC0)

EOR R17,R18

OUT PORTC,R17

IN R18, OCR2 // Считываем значение регистра сравнения

ldi R21 , 0

CP R18, R21 // Сравниваем два регистра

brne n // Если регистры не равны, то переходим на n

ldi R19, 0

n: ldi R21,255

CP R18, R21 // Сравниваем два регистра

brne n1 // Если регистры не равны, то переходим на n1

ldi R19, 1

n1: ldi R21,0

CP R19, R21 // Сравниваем два регистра

brne n2 // Если регистры не равны, то переходим на n2

subi R18, -1 // R18 ++

n2: ldi R21, 1

CP R19, R21 // Сравниваем два регистра

brne n3 // Если регистры не равны, то переходим на n3

subi R18, 1 // R18 --

n3: OUT OCR2, R18 // Заносим измененное значение в регистр сравнения

POP R18 // Записываем во временный регистр R18 исходное значение из стека

POP R17 // Записываем во временный регистр R17 исходное значение из стека

sei

RETI

Сгенирировать ШИМ сигнал со скважностью 10%, 20%, ..., 90%

.include "m8def.inc"

.macro OUTI // Начало макроса "Запись числа в регистр ввода-вывода"

PUSH R16

ldi R16,@1

OUT @0,R16

POP R16

.endm // Конец макроса

.CSEG

JMP INT_VECTORS_SIZE // Переход к концу таблицы прерываний, чтоб не выполнить прерывание в начале программы

.ORG OVF2addr // Прерывание Timer/Counter2 Overflow

RJMP Overflow // Переход на метку Overflow, если случилось прерывание по переполнению

.ORG OC2addr // Прерывание Timer/Counter2 Compare Match

RJMP Compare // Переход на метку Compare, если случилось прерывание по совпадению

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

ldi R16,low(RAMEND) // Инициализация стека

OUT SPL,R16

ldi R16,high(RAMEND)

OUT SPH,R16

OUTI DDRC, (1 << PC0) // Конфигирурирование ножки PC0 на выход

OUTI PORTC, (1 << PC0) // Подать на ножку лог.единицу

OUTI TIMSK,(1<<TOIE2),(1 << OCIE2) // Разрешение прерывания по переполнению и совпадению

OUTI TCCR2, (1<<CS21) // Предделитель на 8

OUTI OCR2, 26 // Устанавливаем значение 26 в регистр сравнения (=10%)

sei

M1: NOP

RJMP M1 // Бесконечный цикл

Compare:

cli

PUSH R17 // Записываем в стек значение временного регистра R17

PUSH R18 // Записываем в стек значение временного регистра R18

IN R17,PINC // Считываем состояние ножки PC0

// Меняем состояние ножки PC0 на противоположное

ldi R18,(1 << PC0)

EOR R17,R18

OUT PORTC,R17

POP R18 // Записываем во временный регистр R18 исходное значение из стека

POP R17 // Записываем во временный регистр R17 исходное значение из стека

sei

RETI

Overflow:

cli

PUSH R17 // Записываем в стек значение временного регистра R17

PUSH R18 // Записываем в стек значение временного регистра R18

IN R17,PINC // Считываем состояние ножки PC0

// Меняем состояние ножки PC0 на противоположное

ldi R18,(1 << PC0)

EOR R17,R18

OUT PORTC,R17

POP R18 // Записываем во временный регистр R18 исходное значение из стека

POP R17 // Записываем во временный регистр R17 исходное значение из стека

sei

RETI

Мигание светодиодом

.include "m8def.inc"

.macro OUTI // Начало макроса "Запись числа в регистр ввода-вывода"

PUSH R16

ldi R16, @1

OUT @0, R16

POP R16

.endm // Конец макроса

.CSEG

JMP INT_VECTORS_SIZE // Переход к концу таблицы прерываний, чтоб не выполнить прерывание в начале программы

.ORG OVF0addr // Прерывание Timer/Counter0 Overflow

RJMP Overflow // Переход на метку Overflow, если случилось прерывание

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

ldi R16, low(RAMEND) // Инициализация стека

OUT SPL, R16

ldi R16, high(RAMEND)

OUT SPH, R16

OUTI DDRC, (1<<PC0) // Конфигирурирование ножки PC0 на выход

OUTI PORTC, (1<<PC0) // Подать на ножку лог.единицу

OUTI TIMSK, (1<<TOIE0) // Разрешить прерывание по переполнению таймера 0

OUTI TCCR0, (1<<CS02) | (1<<CS00)// Установка предделителя на 1024

sei

M1: NOP

RJMP M1 // Бесконечный цикл

Overflow:

PUSH R17 // Записываем в стек значение временного регистра R17

PUSH R18 // Записываем в стек значение временного регистра R18

IN R17, PINC // Считываем состояние ножки PC0

// Меняем состояние ножки PC0 на противоположное

ldi R18, (1 << PC0)

EOR R17, R18

OUT PORTC, R17

POP R18 // Записываем во временный регистр R18 исходное значение из стека

POP R17 // Записываем во временный регистр R17 исходное значение из стека

RETI

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]