Небольшое исхищрение на тему экономии регистров МК.
Идея возникла в связи вот с какой задачей. Есть внешний цикл на 12 итераций (12 = 0x0C). Счётчик итераций умещается в пол-байта. И есть внутренний цикл на чуть более, чем 256 итераций (в моём случае 484 = 0x01E4). Счётчик итераций внутреннего цикла в байт ну никак не умещается, а второй регистр заводить для него жаба давит. Какой выход? Правильно! Уместить неуместившиеся разряды внутреннего счётчика в свободные разряды внешнего счётчика.
Сначала вариант "цивилизованного" кода, использующего все 3 регистра для счётчиков:
Код: Выделить всё
; {...}
; {делаем что-нибудь}
; {...}
ldi r20, 0x0C ; Инициализация внешнего счётчика
outer_loop:
; {...}
; {содержимое внешнего цикла}
; {...}
ldi r18, 0xE4 ; Инициализация внутреннего счётчика
ldi r19, 0x01 ; То, что не поместилось в байт
inner_loop:
; {...}
; {содержимое внутреннего цикла}
; {...}
dec r18 ; Уменьшаем внутренний счётчик
brne inner_loop
dec r19 ; Тоже внутренний счётчик
brne inner_loop
dec r20 ; Уменьшаем внешний счётчик
brne outer_loop
; {...}
; {делаем ещё что-нибудь}
; {...}
А теперь экономный вариант с использованием всего 2-х регистров:
Код: Выделить всё
; {...}
; {делаем что-нибудь}
; {...}
ldi r19, 0x0C ; Инициализация внешнего счётчика
outer_loop:
; {...}
; {содержимое внешнего цикла}
; {...}
ldi r18, 0xE4 ; Инициализация внутреннего счётчика
ori r19, 0xE0 ; То, что не поместилось в байт
; Величина, помещаемая в старший полубайт
; вычисляется по формуле y = 0xF - x
; В моём случае x=0x1, поэтому y=0xE
inner_loop:
; {...}
; {содержимое внутреннего цикла}
; {...}
dec r18 ; Уменьшаем внутренний счётчик
brne inner_loop
subi r19, 0xF0 ; Тоже внутренний счётчик
; Выполнение этой команды увеличивает
; старший полубайт регистра r16 на 0x1,
; при этом флаг переноса уставлен, кроме случая,
; когда старший полубайт оказывается равным 0x0
brcs inner_loop
dec r19 ; Уменьшаем внешний счётчик
brne outer_loop
; {...}
; {делаем ещё что-нибудь}
; {...}
Как можно заметить, количество инструкций не увеличилось, за то уменьшилось число используемых разрядов. Правда, достигнуто это ценой усложнения программы для понимания. Однако, есть и другая, эстетическая сторона: разнообразился состав используемых инструкций МК. То была связка
ldi /
dec /
brne, а теперь к ним добавились команды
ori /
subi /
brcs.
Это ухищрение обладает достаточно большой гибкостью. Если бы количество итераций внешнего цикла умещалось бы в 3 бита, то целых 13 бит можно было бы использовать для счётчика внутреннего цикла. С другой стороны, если когда для внутреннего цикла требуется 9 бит как в моём случае, то для счётчика внешнего цикла можно использовать целых 7 бит сдвоенного регистра. Модификация требует всего лишь правильно задать значения констант.
Более того, в случае 7/9 (как у меня) можно сэкономить ещё и на одной инструкции МК — на инициализации самого старшего бита внутреннего счётчика, так как его инвертированное значение на входе во внутренний цикл всегда равно нулю, что и требуется. Я же в своей программе этого делать не стал, так как у меня может возникнуть необходимость увеличить число итераций внутреннего цикла.
Вот ещё один, чуть более человечный вариант:
Спойлер
Код: Выделить всё
; {...}
; {делаем что-нибудь}
; {...}
ldi r19, 0x0C ; Инициализация внешнего счётчика
outer_loop:
; {...}
; {содержимое внешнего цикла}
; {...}
ldi r18, 0xE4 ; Инициализация внутреннего счётчика
ori r19, 0x10 ; То, что не поместилось в байт
inner_loop:
; {...}
; {содержимое внутреннего цикла}
; {...}
dec r18 ; Уменьшаем внутренний счётчик
brne inner_loop
subi r19, 0x10 ; Тоже внутренний счётчик
brcс inner_loop
subi r19, 0xF1 ; Уменьшаем внешний счётчик
brne outer_loop
; {...}
; {делаем ещё что-нибудь}
; {...}
Интересно, а компилятор Си умеет делать такую оптимизацию?