У нас остались не рассмотренными команды пересылки данных в микроконтроллерах Atmel AVR. В данном случае можно говорить о наиболее близком к классическому набору команд пересылки. При этом, не смотря на все отличия от PIC или STM8, можно заметить, что сама суть во всех семействах очень близка. И это важный для нас момент.
Самой большой особенностью AVR является набор из 32 регистров вместо одного аккумулятора, о чем я уже говорил в статье "Микроконтроллеры для начинающих. Часть 12. Память и процессор в AVR". Причем они немного не равноправны. И это я буду указывать.
Еще одной особенностью AVR является то, что команды пересылки данных не изменяют флаги результата.
Важно отметить, что ваш микроконтроллер не обязательно будет поддерживать все перечисленные здесь (и в других статьях цикла) команды. Подробности необходимо смотреть в документации на используемый микроконтроллер.
Однако, прежде, чем перейти к рассмотрению команд, нам надо познакомиться с регистрами расширения адреса памяти.
RAMPD, RAMPX, RAMPY, RAMPZ
Эти регистры есть далеко не в каждом микроконтроллере AVR, а только в тех, где объем памяти программ или данных превышает 64 кБ. То есть, когда для формирования полного адреса ячейки памяти недостаточно 16-бит.
По сути, эти регистры задают адрес (номер) страницы памяти. Но если вам не нравится деление памяти на страницы, можете рассматривать их как расширение соответствующих индексных регистров. Суть от этого не меняется.
При доступе к памяти за пределами 64 кБ индексные регистры хранят младшие 16 бит полного адреса, а соответствующий регистр RAMPx старшие разряды. Теоретически, это дает возможность формирования 24-битного адреса.
Важно отметить, что при использовании косвенной адресации с автоинкрементом и автодекрементом содержимое регистров RAMPx автоматически не изменяется.
Общие команды пересылки
LDI reg, imm_data
Загрузка в регистр reg непосредственных данных imm_data. Другими словами, загрузка константы. Причем допустимы только регистры R16-R31.
LDS reg, addr
Загрузка в регистр reg данных из ячейки памяти с адресом addr. Другими словами, это загрузка с использованием прямой адресации. Регистр может быть любым, а вот адрес ячейки должен быть 16 разрядным. То есть, доступны только 64 кБ памяти данных. Для доступа за пределы 64 кБ (если такой объем действительно есть) дополнительно используется регистр RAMPD.
Нужно отметить, что данная команда существует и в другом варианте, например, в микроконтроллерах ATtiny10. При этом команда занимает не 4 байта, а только 2 и имеет дополнительные ограничения. Так допустимо указывать только регистры R16-R31, а адрес памяти ограничен диапазоном 0x40-0xBF. Скорее всего вам никогда не придется столкнуться с этим вариантом команды.
Важно отметить, что в микроконтроллере может быть только какой то один из вариантов, но не оба сразу. Это указывается в документации.
LD reg, {X | Y | Z}
LD reg, {X+ | Y+ | Z+}
LD reg, {-X | -Y | -Z}
Загрузка в регистр reg данных из ячейки памяти адрес которой находится в индексном регистре X, Y или Z. То есть, это загрузка с использованием косвенной адресации. В качестве регистра reg может быть указан любой регистр, теоретически. Не стоит забывать, что индексные регистры образованы парами R26:R27, R28:R29 и R30:R31.
Поэтому не стоит использовать в качестве reg любой из пары регистров образующей тот же самый индексный регистр, который используется для задания адреса ячейки памяти, в случае использования автоинкремента или автодекремента. Другими словами, команды подобные
LD r26, X+
LD r29, -Y
LD r30, -Z
приводят к неопределенному результату. О чем прямо говорится в документации. Но команды вроде
LD r28, -X
LD r26, Z+
вполне корректны.
Поскольку индексные регистры 16-битные, выход за пределы 64 кБ (если такой объем памяти доступен) осуществляется с привлечением регистров RAMPX, RAMPY или RAMPZ.
Так же нужно отметить, что в микроконтроллерах с памятью данных менее 256 байт для адресации используется только младшая половина индексного регистра. Причем это касается и адресации с автоинкрементом и автодекрементом. Старшие половины регистров, в таких микроконтроллерах, могут использоваться для хранения любых данных, что не оказывает никакого влияния на адресацию.
LDD reg, {Y+ofs | Z+ofs}
В целом, эта команда аналогична соответствующей команде LD, но адрес ячейки памяти формируется как сумма содержимого индексного регистра и смещения ofs. Причем смещение должно находиться в диапазоне 0-63.
Для выхода за границы 64 кБ используются регистры RAMPY и RAMPZ.
STS addr, reg
Сохранение данных из регистра reg в ячейке памяти с адресом addr. Эта команда отличается от LDS только направлением передачи данных. Все, что было ранее сказано про команду LDS, будет верным и для команды STS.
И точно так же, эта команда существует и во втором варианте, например, в ATtiny10. Но оба варианта одновременно существовать не могут.
ST {X | Y | Z}, reg
ST {X+ | Y+ | Z+}, reg
ST {-X | -Y | -Z}, reg
Сохранение содержимого регистра reg в ячейке памяти с адресом хранящимся в соответствующем индексном регистре. Эта команда отличается от LD только направление передачи данных. Все, что ранее было сказано про команду LD, будет верным и для команды ST.
STD {Y+ofs | Z+ofs}, reg
В целом, аналогична команде ST, но адрес ячейки памяти формируется как сумма содержимого индексного регистра и смещения ofs. Причем смещение должно находиться в диапазоне 0-63.
Эта команда отличается от LDD только направлением передачи данных.
MOV Rd, Rs
Наличие 32 регистров общего назначения приводит к необходимости переноса данных не только между регистром и ячейкой памяти, но и между двумя регистрами. В AVR для этого существует отдельная команда MOV, которая копирует содержимое регистра Rs в регистр Rd.
И в качестве регистра-источника, и в качестве регистра-приемника может быть указан любой из 32 регистров.
Команды пересылки 16-битных данных
MOVW Rd, Rs
Аналогична команде MOV, но копирует сразу 16 бит между регистровыми парами Rd+1:Rd <- Rs+1:Rs. Важно отметить, что в коде команды задается только номер младшего регистра каждой пары, причем младший бит номера не хранится. Другими словами, номер младшего регистра пары должен быть чётным (младший бит номера, который не хранится, равен 0).
Существует два возможных формата задания регистровой пары в ассемблерном коде. Первый указан в документации на AVR, например,
MOVW R1:R0, R7:R6
При этом регистровые пары указываются в явном виде, что несколько излишне, но позволяет повысить наглядность. Однако, не все ассемблеры понимают такой формат записи. Поэтому, возможно, вам придется использовать сокращенную запись
MOVW R0, R6
Какой именно формат записи допустим нужно смотреть в документации, но не на микроконтроллер, а на ваш ассемблер.
Команды работы со стеком
Здесь все просто. Однако, в отличии от STM8, возможна работа только с регистрами, но не с ячейками памяти.
PUSH reg
Помещает содержимое регистра reg в стек.
POP reg
Извлекает байт данных из стека и помещает в регистр reg.
Команды ввода-вывода
Как я уже писал в статье "Микроконтроллеры для начинающих. Часть 12. Память и процессор в AVR" адресное пространство ввода-вывода в AVR выглядит нелогично и является, по сути, рудиментом предназначенным для совместимости со старыми моделями микроконтроллеров. Однако, команды IN и OUT все таки существуют. Более того, их используют в документации и официальных примерах.
IN reg, io
Чтение из адресного пространства ввода-вывода ячейки с адресом io и сохранение в регистре reg. Регистр может быть указан любым. Адрес в пространстве ввода-вывода может лежать в диапазоне 0-63.
OUT io, reg
Копирует (выводит) содержимое регистра reg в заданную ячейку в пространстве ввода-вывода с адресом io.
Команды передачи данных с областью памяти программ
Область памяти программ может быть доступна или через специальные команды, или в виде отображения на область памяти данных (в некоторых микроконтроллерах).
Работа с памятью программ выходит за рамки тем для начинающих, однако, это полезно для хранения констант (например, текстовых строк). Поэтому я опишу кратко чтение памяти программ, а вот про запись только упомяну. Возможно, тему записи я рассмотрю отдельно, в статьях посвященных работе загрузчика (bootloader).
LPM
LPM reg, {Z | Z+}
Чтение байта, адрес которого задается индексным регистром Z, из памяти программ и загрузка его в регистр reg. Если операнды команды не указаны, то подразумевается LPM R0,Z.
Не смотря на то, что память команд организована в виде слов (по 2 байта), регистр Z задает не адрес слова, а адрес байта. Это позволяет получить доступ к каждому отдельному байту хранящемуся в памяти программ.
ELPM
ELPM reg, {Z | Z+}
Аналогичная команде LPM, но адрес задается RAMPZ:Z. Причем в данной команде, при использовании автоинкремента, он распространяется и на регистр RAMPZ.
SPM
Сохранение в памяти программ. Как я уже говорил, в данной статье я не буду рассматривать вопросы записи памяти программ. Поэтому ограничусь лишь упоминанием этой команды.
"Атомарные" команды пересылки данных
В основном, эти команды предназначены для обеспечения атомарности доступа к данным, например, к различным статусам и семафорам.
Все эти операции можно реализовать с помощью обычных команд, которые были рассмотрены ранее. Однако, при этом цикл доступа "чтение-модификация-запись" окажется прерываемым, что может привести к коллизиям если с ячейкой памяти одновременно работает и основная программа и программа обработки прерываний.
Специализированные команды позволяют сделать доступ чтение-модификация-запись неразрывным, атомарным, так как выполнение команды не может быть прервано.
XCH Z,reg
Обмен содержимого регистра reg и ячейки памяти адрес которой находится в индексном регистре Z. Для доступа к памяти за пределами 64 кБ дополнительно используется регистр RAMPZ.
LAC Z, reg
Загрузка со сбросом заданных бит. Содержимое регистра reg сохраняется во временном регистре, затем в регистр reg копируется содержимое ячейки памяти, адрес которой содержится в индексном регистре Z, наконец в ячейке памяти сбрасываются биты, которые установлены в регистре reg. Это выполняется операцией И с инверсным содержимым регистра reg.
LAS Z, reg
Загрузка с установкой заданных бит. Аналогична команде LAC, но в ячейке памяти выполняется установка бит, которые были установлены в регистре reg (операцией ИЛИ).
LAT Z, reg
Загрузка с переключением заданных бит. Аналогична командам LAC и LAS. Но содержимое ячейки памяти изменяется с помощью операции ИСКЛЮЧАЮЩЕЕ ИЛИ между ее содержимым и содержимым регистра reg.
Заключение
Мы закончили знакомиться с командами пересылки данных. Это достаточно простые команды, даже не смотря на наличие некоторых, довольно специфических, случаев в различных микроконтроллерах.
Несмотря на все различие между микроконтроллерами нельзя не заметить, что общего между ними довольно много.
До новых встреч!