Найти в Дзене
Будни инженера

В каких случаях нельзя использовать регистры R0…R15 и почему

Регистры общего назначения (РОН) в микроконтроллерах AVR делятся на две неравные группы: младшие (R0-R15) и старшие (R16-R31). Хотя документация часто называет их «общего назначения», на практике между ними существует важное функциональное неравенство, обусловленное архитектурой ядра и форматом команд. В этой статье мы подробно разберем, когда и почему необходимо использовать регистр R16 и его старших собратьев, а также каких возможностей лишены регистры R0-R15. Ответ кроется не в «волшебных» свойствах самого регистра R16, а в ограничениях системы команд (instruction set) AVR. Все дело в 16-битном формате командного слова процессора. Представьте себе команду, которая должна загрузить в регистр произвольное 8-битное число (константу). В 16-битном коде операции нужно уместить: Если выделить 8 бит под данные, то на код операции и адрес регистра остается еще 8 бит. Четыре бита из этих восьми отводятся под адрес регистра. Четырьмя битами можно закодировать адреса только для 16 регистров (2^
Оглавление

Регистры общего назначения (РОН) в микроконтроллерах AVR делятся на две неравные группы: младшие (R0-R15) и старшие (R16-R31). Хотя документация часто называет их «общего назначения», на практике между ними существует важное функциональное неравенство, обусловленное архитектурой ядра и форматом команд. В этой статье мы подробно разберем, когда и почему необходимо использовать регистр R16 и его старших собратьев, а также каких возможностей лишены регистры R0-R15.

Почему R16? Корень вопроса в системе команд

Ответ кроется не в «волшебных» свойствах самого регистра R16, а в ограничениях системы команд (instruction set) AVR. Все дело в 16-битном формате командного слова процессора.

Представьте себе команду, которая должна загрузить в регистр произвольное 8-битное число (константу). В 16-битном коде операции нужно уместить:

  1. Сам код операции (что делать — загрузить).
  2. Адрес регистра-приемника (куда загружать).
  3. Само 8-битное значение (что загружать).

Если выделить 8 бит под данные, то на код операции и адрес регистра остается еще 8 бит. Четыре бита из этих восьми отводятся под адрес регистра. Четырьмя битами можно закодировать адреса только для 16 регистров (2^4 = 16). Чтобы обратиться ко всем 32 регистрам, нужен был бы 5-битный адрес, что нарушило бы структуру команды. Инженеры Atmel (ныне Microchip) пошли на компромисс: «лишний» старший бит адреса для таких команд был жестко «зашит» в единицу. Это означает, что команды, работающие с непосредственными константами (числами), физически могут адресовать только вторую половину регистрового файла — с R16 по R31.

Возможности регистров R16–R31, недоступные для R0–R15

Регистры младшей группы (R0-R15) являются «ущербными» ровно в той степени, что они не могут быть операндами в командах, использующих непосредственные значения. Какие же конкретно команды недоступны при работе с R0-R15?

1. Загрузка константы (LDI)

Самая главная команда, ради которой чаще всего и выбирают R16. Команда LDI (LoaD Immediate) служит для записи конкретного числа в регистр.

; Правильно и красиво
LDI R16, 0xFF ; Загрузить число 255 в регистр R16
LDI R17, 0b00000001 ; Загрузить единицу в R17
; ОШИБКА! Так нельзя
; LDI R0, 0xFF ; Ассемблер выдаст ошибку

Если вам нужно загрузить константу в R0, придется делать это через «костыль» — сначала загрузить число в старший регистр, а затем скопировать его в младший командой MOV.

LDI R16, 0xFF ; Сначала в R16
MOV R0, R16 ; Теперь скопировали в R0

2. Логические операции с константой (ANDI, ORI)

Команды побитового умножения (ANDI — AND with Immediate) и побитового сложения (ORI — OR with Immediate) с непосредственным значением также работают исключительно с регистрами R16-R31.

ANDI R16, 0b00001111 ; Оставить только младшие 4 бита в R16
ORI R17, 0b00100000 ; Установить 5-й бит в единицу

3. Арифметика с константами (SUBI, SBCI)

Команды вычитания констант (SUBI — SUBtract Immediate) и вычитания с учетом флага переноса (SBCI — SuBtract with Carry Immediate) также предназначены только для старших регистров.

SUBI R16, 5 ; Вычесть 5 из R16

4. Сравнение с константой (CPI)

Команду сравнения регистра с числом (CPI — ComPare with Immediate), необходимой для организации ветвлений, постигла та же участь.

CPI R16, 13 ; Сравнить R16 с числом 13
BRLO Label ; Переход, если R16 < 13 (меньше)

Если бы мы попытались сравнить R0 с числом 13, нам потребовалось бы использовать дополнительный регистр-посредник.

Особый статус регистров R26–R31 (X, Y, Z)

Говоря о R16, нельзя не упомянуть, что последние шесть регистров старшей группы (R26–R31) имеют двойное назначение. Они могут использоваться как обычные регистры (например, R16), но могут также образовывать 16-битные индексные регистры-указатели:

  • X (R27:R26) — регистр-указатель X (младший байт в R26, старший в R27).
  • Y (R29:R28) — регистр-указатель Y.
  • Z (R31:R30) — регистр-указатель Z.

Использовать их как обычные регистры можно, но рискованно. Если вы заняли R30 и R31 под хранение переменных, то не сможете прочитать байт из памяти программ (Flash) командой LPM или выполнить косвенный переход IJMP, так как для этих операций требуется именно Z-регистр.

Пример кода: Подсчет единиц в байте

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

Вариант 1: Корректный (с использованием R16)

Вариант 2: Некорректный (если бы мы попытались использовать R0)

Представим, что мы захотели использовать для счетчика или переменной цикла R0. Мы бы столкнулись с проблемой:

-2

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

Заключение

Использование регистра R16 и других регистров из диапазона R16–R31 при программировании на ассемблере для микроконтроллеров AVR — это не просто дань традиции, а вынужденная мера, продиктованная архитектурой. Команды работы с непосредственными данными (константами) физически не могут адресовать младшие регистры R0–R15.

Если вы пишете на C, то компиляторы Си для AVR, такие как avr-gcc, автоматически распределяют регистры, учитывая эти ограничения. Для них нет «магии» R16 — компилятор сам знает, в какие регистры можно загружать константы, а в какие нет . Однако при оптимизации или написании вставок на ассемблере знание этого нюанса помогает писать более эффективный и чистый код.

На этом всё. Подписывайтесь на канал, чтобы ничего не пропустить…