GBA ASM - День 7: Всё о макросах

Дата публикации 23 сен 2003

GBA ASM - День 7: Всё о макросах — Архив WASM.RU

Зачем использовать макросы?

Макросы - это алиасы для участков кода. Они делают вашу ассемблерную жизнь проще, так как вам не нужно запоминать куски кода (возможно, большие), которые вы часто употребляете. Чтобы вызвать макрос, напишите его имя, а через пробел параметры, разделённые запятыми. Когда ассемблер встречает макрос, он просто заменяет его вызов указанным в описании макроса кодом, подставляя, если это необходимо, параметры. Вот пример вызова макроса:

Код (Text):
  1.  
  2. LoadRegRGB r0, 255, 0 , 0

Вот и всё! Макросы великолепно подходят для снижения количества печатаемого кода и повышения его читаемости.

Простой макрос

Макрос определяются примерно следующим образом: (что-то вроде шаблона макроса)

Код (Text):
  1.  
  2. @macro macro_name arg1, arg2  ; макрос может иметь столько параметров,
  3.                               ; сколько вам нужно, даже ни одного!
  4.  
  5. ;;; здесь идёт код ;;;
  6. @endm      ; конец макроса

Если вы не поняли, что это значит, вот код бесполезного макроса, загружающего 5 в r0:

Код (Text):
  1.  
  2. @macro MakeR0Equal5
  3. ldr r0,=5
  4. @endm

Это макрос абсолютно бесполезен.

Теперь, я уверен, вы хотите узнать, как передавать аргументы (параметры) в макрос и использовать их в коде. Вот более полезная версия вышеприведённого макроса:

Код (Text):
  1.  
  2. @macro LoadReg Register, Num
  3. ldr Register,=Num
  4. @endm

Если вы решите использовать этот макрос в реальном коде (не делайте этого, так как он абсолютно бесполезен!), вам следует поместить его либо в заголовочном файле, либо в начале кода. Я покажу вам, как выглядит вызов макроса, а затем объясню, как ассемблер обрабатывает его. Я вызываю этот макрос, чтобы загрузить 52 в r4. Смотрите:

Код (Text):
  1.  
  2. LoadReg r4, 52

Вот как это выглядит в исходном коде. Когда ассемблер доходит до вызова этого макроса, он сначала определяет параметры, а затем производит замену:

Код (Text):
  1.  
  2. - Register заменяется на r4
  3. - Num заменяется на 52

Затем, после подстановки параметров, сам макрос заменяется на код в макросе. Поэтому после того, как вызов макроса обработан:

Код (Text):
  1.  
  2. LoadReg r4, 52

Ассемблер удаляет его, а вместо него мы получаем:

Код (Text):
  1.  
  2. ldr r4,=52

Обратите внимание, что параметры могут быть чем угодно, начиная от числа или регистра и кончая бессмысленным набором символов, которые могут привести к следующей ошибке:

Скажем, я попытался сделать следующее:

Код (Text):
  1.  
  2. LoadReg $(#(*@*#_@, 52

Это переводится в:

Код (Text):
  1.  
  2. ldr $(#(*@*#_@,=52

И я получаю несколько ошибок от Goldroad'а:

Код (Text):
  1.  
  2. error : line 0 : syntax error
  3. ldr $(
  4. error : line 0 : syntax error
  5. ldr $( (
  6. error : line 0 : in expression - missing right parenthesis
  7. ldr $( (*
  8. error : line 0 : load out of range (max 0xffc b)
  9. ldr $( (*
  10. error : line 0 : syntax error
  11. ldr $( (*
  12. assembled with 5 errors

Теперь мы сделаем несколько более полезных макросов.

Более полезные макросы

Мне кажется, вам сейчас интересно, как задавать RGB-значения (на GBA это, фактически, BGR). Мы сделаем макрос, загружающий такое число в регистр. Итак, сколько аргуметов нам нужно. Давайте предположим... Так каким будет ваш ответ? Ок, я не знаю, что придумали вы, но я остановился на 4-ёх. 1 - это регистр, 2 - количество красного, 3 - количество зелёного, 4-ый - количество синего. Для тех, кто не знает, скажу, что на GBA RGBзначения задаются следующим образом:

Код (Text):
  1.  
  2. F E D C  B A 9 8  7 6 5 4  3 2 1 0
  3. X B B B  B B G G  G G G R  R R R R
  4.  
  5. 0-4 (R) = Красный
  6. 5-9 (G) = Зелёный
  7. A-F (B) = Синий

Диаграмма выдрана из CowBite Spec

Обратите внимание, что каждое значение равно 5-ти битам, при том, что 16 бит неиспользовано. Поэтому на нужно сдвинуть биты заданных значений. Для этого я использовал содержимое типичного подобного макроса на C. Вот, что получилось:

Код (Text):
  1.  
  2. @macro LoadRegRGB Register, Red, Green, Blue
  3. ldr Register,=((Red)+(Green<<5)+(Blue<<10))
  4. @endm

Разве не круто? Чтобы поместить красную точку, надо вызвать его следующим образом:

Код (Text):
  1.  
  2. LoadRegRGB r3, 255, 0 , 0

И ассемблер воспримет это следующим образом:

Код (Text):
  1.  
  2. ldr r3,=((255)+(0<<5)+(0<<10))

Обратите внимание, что вы уже должны понимать, на что ассемблер заменяет макрос, поэтому я больше не буду писать подобные строки (я голоден, мои пальцы болят и я замёрз).

Теперь давайте сделаем макрос для перемещения спрайтов, который мы использовали в Дне 5!

Макрос, перемещающий спрайты

Ок, нам нужен номер спрайта (0-127), если помните. А если нет, то не страшно :smile3:. Нам также нужна новая позиция по x и y. Это в сумме составляет 3 аргумента.

Номер спрайта мы умножим на 8, чтобы добраться до начала элементов спрайтов в OAM. 8 - это длина (в байтах) элементов спрайтов (Аттрибут0 - Аттрубит3). Затем загрузим в регистр содержимое аттрибутов 0&1 (аттрибуты 16-ти, а регистры - 32-х битны, поэтому у нас нет выбора). Затем мы сдвинем содержммое регистра так, чтобы в нём был только аттрибут 0 (в младших 16 битах). Далее мы выполним операцию побитового AND над регистром, используя значение 0xFF00, чтобы очистить старую y-позицию, а затем сделаем побитовый OR над нашей новой y-позцией. Так как наши операции загрузки и сохранения 32-х битны, мы должны загрузить два регистра аттрибутами 0 и 1. Мы не можем сделать это за один раз, потому что когда мы сохраняем аттрибут 0, мы обнуляем аттрибут 1. Поэтому мы также делаем побитовый AND над регистром, в котором находится аттрибут 1 (после некоторого сдлвига)

Ух, ты! Какой здоровенный параграф получился. Я надеюсь, что вы поняли его смысл. Может быть вам нужно поглядеть на конкретный код? Прекрасно. Вот моя реализация:

Код (Text):
  1.  
  2. @macro MoveSprite Sprite, X, Y
  3. ldr r2,=(OAM+(Sprite*8))  ; получаем начало аттрибутов спрайта в OAM
  4. ldr r3,[r2]           ; загружаем в r3 аттрибуты 0 и 1
  5. mov r3,r3, lsr#16     ; делаем сдвиг, чтобы в r3 был только аттрибут 0
  6. ldr r4,[r2]           ; загружаем в r4 аттрибуты 0 и 1
  7. mov r4,r4, lsl#16     ; делаем сдвиг, чтобы в r4 был только аттрибут 1
  8. mov r4,r4, lsr#16     ; получаем значение r4 в младших битах
  9. and r3,r3,#0xFF00     ; очищаем старое значение y
  10. and r4,r4,#0xFE00    ; очищаем старое значение x
  11. orr r3,r3,#X       ; устанавливаем новое значение y
  12. orr r4,r4,#Y       ; устанавливаем новое значение x
  13. mov r3,r3, lsl#16  ; готовимся добавить их
  14. orr r4,r4,r3       ; добавляем их вместе, чтобы они оба были в r4
  15. str r4,[r2]        ; сохраняем в OAV, всё! :)
  16. @endm

LSR (Логический Сдвиг впРаво) и LSL (Логический Сдвиг вЛево) - это опции, а не инструкции. Это означает, что они должны употребляться только вместе с инструкциями. Они могут быть присоединены к концу инструкции MOV.

Вам понравился этот макрос?

Подведение итогов

Хотя иногда создание макроса может отнимать ощутимое количество времени б(создание макроса MoveSprite отняло у меня 4 часа), их использавание всегда оправдывает затраченные усилия, потому что как только вы создали макрос, вы можете использовать его так часто, как это вам нужно. Что удобнее, писать каждый раз 13 строчек кода, когда вам нужно переместить спрайт, или просто написать "MoveSprite 0, 35, 50"? © Mike H, пер. Aquila


0 923
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532