Сложение регистровой пары с регистром или константой

Иногда в памяти программ удобно хранить некий блок констант для последующего использования. Определяется такой блок директивой .db (для однобайтовых значений) и помечается меткой, предположим const_label.
Как пример – набор битовых масок цифр и символов для 7-сегментного индикатора.
Или последовательность задержек для формирования сложного сигнала.
В чем разница?
В первом случае из памяти программ извлекается единственное значение –  нужная в данный момент битовая маска.
Во втором происходит последовательное чтение всей цепочки до некоего определённого стоп-байта (как вариант – $FF).
Для задания адреса, по которому находится нужный блок и элемент в нём, используется регистровая пара Z (она же ZH:ZL, она же r31:r30), разрядностью в 16 бит.
В первом случае эта регистровая пара складывается либо с 8-битной константой,
либо с 8-битным же регистром.

Этот случай и рассмотрим.

Берём пример из (1) – стр. 135:

ldi ZH,High(const_label *2) ;загружаем адрес начала маски
ldi ZL,Low(const_label *2)
add ZL,5 ;адрес маски цифры 5
lpm ;маска окажется в регистре r0
 
Листинг №1

и зарекаемся его использовать “в чистом виде”, ибо в нём зарыты грабли. Даже не одни. Первые и самые важные – в системе команд AVR отсутствует сложение регистра
с константой, есть только вычитание.
Использование константы в данном случае тоже кажется сомнительным – зачем под каждую цифру лепить свой кусок кода, если проще занести смещение в регистр?

Переписываем:

.def temp = r16 ; в области определений
...
ldi temp, 5 ;смещение - адрес маски цифры 5
ldi ZH,High(const_label *2) ;загружаем адрес начала маски
ldi ZL,Low(const_label *2)
add ZL,temp ;
lpm ;маска окажется в регистре r0
 
Листинг №2

А вот и вторые грабли. Если начало блока придётся на адрес например $00fc, а мы попытаемся выдернуть битмаску для цифры 5, нас ждёт облом: в регистровой паре Z вместо $0101 окажется значение $0001, поскольку команда add понятия не имеет о переносе единицы в старшую половину Z.

Заметки на полях.
Если очень нужно всё же сложить регистровую пару с константой, используйте команду adiw, она умеет учитывать перенос.
Важно лишь помнить: диапазон значений константы для данной команды
составляет 0…63, а сами регистровые пары могут быть
только Z (r31:R30), Y (r29:r28), X (r27:r26) и r25:r24.


ldi ZH,High(const_label *2) ;загружаем адрес начала маски
ldi ZL,Low(const_label *2)
adiw ZL,5 ;адрес маски цифры 5
lpm ;маска окажется в регистре r0
 
Листинг №3

Как будем выкручиваться?

Например так:

.def temp = r16 ; в области определений
...
ldi temp, 5 ; смещение - адрес маски цифры 5
ldi ZH,High(const_label *2) ;загружаем адрес начала маски
ldi ZL,Low(const_label *2)
add ZL,temp
brcc add_8
inc ZH
add_8: lpm ;маска окажется в регистре r0
 
Листинг №4

Плюсы – не задействованы дополнительные регистры

Или так:

.def temp = r16 ; в области определений
...
ldi temp, 5 ; смещение - адрес маски цифры 5
ldi ZH,High(const_label *2) ;загружаем адрес начала маски
ldi ZL,Low(const_label *2)
add ZL,temp
clr temp
adc ZH,temp
lpm ;маска окажется в регистре r0
 
Листинг №5

Или так – если содержимое temp терять не хочется, и есть свободный регистр:

; в области определений
.def zero_REG = r1 ; можно использовать регистр из диапазона r1…r29, кроме r0 и r16
.def temp = r16 ; поскольку r0 занят под команду lpm, а r16 под рабочий регистр temp
...
clr zero_reg ; в области стартовой инициализации
...
ldi temp, 5 ; смещение - адрес маски цифры 5
ldi ZH,High(const_label *2) ;загружаем адрес начала маски
ldi ZL,Low(const_label *2)
add ZL,temp
adc ZH,zero_reg
lpm ;маска окажется в регистре r0
 
Листинг №6

Или даже так, но в этом случае в temp нельзя грузить значение “0”, только больше:

.def temp = r16 ; в области определений
...
ldi temp, 5 ; смещение - адрес маски цифры 5
neg temp ; превращаем в отрицательное число
ldi ZH,High(const_label *2) ;загружаем адрес начала маски
ldi ZL,Low(const_label *2)
sub ZL,temp ; вычитание отрицательного числа
sbci ZH, (-1) ; да с учетом переноса даёт сложение
lpm ;маска окажется в регистре r0
 
Листинг №7

Есть ещё один вариант: если длина блока не превышает 256 байт и позволяет размер программы, директивой .org разместить блок по адресу, к примеру $0700 (для ATTiny2313 это последние 256 байт памяти программ) , в этом случае перенос можно не учитывать, потому что он просто не возникнет. В этом случае код из листинга №2 можно использовать без изменений.

Разумеется, область применения арифметической операции сложения 16-ти и 8-и битных чисел довольно широка, и совершенно не ограничивается приведённым примером.

Второй случай, инкремент 16-битного числа, рассмотрим позже.

Литература:
(1) Юрий Ревич, “Практическое программирование микроконтроллеров Atmel AVR на языке ассемблера”, изд. “БХВ-Петербург”, 2008 г.