GBA ASM - День 8: Загрузка данных с помощью DMA — Архив WASM.RU

Все статьи

GBA ASM - День 8: Загрузка данных с помощью DMA — Архив WASM.RU

Зачем вообще использовать DMA?

DMA (Direct Memory Access) - это относительно быстрый способ загрузки данных в память. До сих пор мы использовали обычный цикл загрузки данных. Преимущества использования DMA по сравнение с этим подходом следующие:

  • DMA предположительно быстрее.
  • В силу каких-то причин я получаю значительно меньший размер бинарника, когда использую DMA.
  • Нам больше не нужно заморачиваться с циклом.
  • Использование DMA для загрузки данных проще, чем использование цикла.
  • Чтобы поместить необходимую информацию в три (3) DMA-регистра, потребуется использовать два из R0-R12 обычных регистров. 3 загрузки и сохрания займут 6 строк кода, что по размеру больше, чем загрузочный цикл, но это проще.
  • Возможно, есть что-то ещё, но мне ничего не приходит в голову.

Поэтому, почему бы не использовать DMA? Для начала давайте посмотрим, какие возможности оно даёт и как их можно использовать.

Об опциях DMA

DMA имеет 4 канала (0-3). 0 - особый, пока его не используйте. 1-3 доступны для использования. Каналы 1 и 2 также также можно использовать для работы со звуком. В общем-то, между 1, 2 и 3 каналом не слишком много разницы, за исключением того, что они имеют разные приоритеты.

Приоритеты необходимы, так как DMA останавливает работу процессора, пока не закончит выполнение своей текущей задачи.

Набор опций DMA достаточно велик:

  1. Загрузка в память 32-х битными или 16-ти битными блоками (16-ти битные по умолчанию).
  2. Когда начинать DMA-загрузку (также называют "трансфером").
    1. Прямо сейчас (DMA_TIMEING_IMMEDIATE)
    2. При следующем HBlank'е (DMA_TIMEING_HBLANK)
    3. При следующем VBlank'е (DMA_TIMEING_VBLANK)
    4. Используется только для 3 канала, есть опция, позволяющая потоковое видео (согласно ThePernProject.Com).
  3. Нужно ли увеличивать, понижать или держать фиксированным значение регистра адреса источника.
  4. Нужно ли увеличивать, понижать или держать фиксированным значение регистра адреса назначения.

Много всего! Но пока что нам нужно только загружать данные, как-то: спрайты, палитры и картинки в режиме 3 на экран. Поэтому мы только начнём трансфер и укажем DMA увеличивать значение обоих регистров источника и назначения. Мы будем использовать трансфер 32-х битными блоками для спрайтов и палитр, но для картинок в режиме 3 мы будем использовать 16-ти битные блоки.

Регистры DMA

Есть 3 DMA-регистра для каждого канала (на самом деле 4, но 2 являются 16-ти битными):

  • REG_DMAxSAD : Принимает адрес источника.
  • REG_DMAxDAD : Принимает адрес назначения.
  • REG_DMAxCNT : Принимает количество данных и опции, перечисленные в вышеприведённой секции.

Нам необходимо выполнить три шага, чтобы осуществить требуемый DMA-трансфер:

  • Поместить адрес источника в REG_DMAxSAD.
  • Поместить адрес назначения (то, где должны оказаться данные) REG_DMAxDAD.
  • Поместить количество данных, трансфер которых будет осуществляться, и опции трансфера в REG_DMAxCNT.

Обратите внимание, что 'x' в названии регистра нужно будет заменить на номер DMA-канала. Теперь вы знаете, что нужно сделать.

Загрузка картинки в режиме 3 с помощью DMA

Пожалуйста, скачайте dma.h.

Мы сейчас загрузим картинку в режиме 3 с помощью DMA. Так как мы используем DMA, нам не нужен будет цикл, а также потребуется всего лишь 2 регистра. Пожалуйста, сконвертируйте картинку под названием "pic.bmp", как это мы сделали в Дне 3, и назовите получившийся файл "pic.asm" (кавычки не нужны :) ). Теперь вместо того, чтобы менять старый исходник, мы просто сделаем новый. Вот его начало: (он включает всё, что нужно и устанавливает 3-ий режим экрана).

;;--- НАЧАЛО ИСХОДНОГО КОДА ---;;

@include screen.h   ; здесь всё, что нужно для экрана
@include dma.h      ; здесь всё, что нужно для DMA
b start             ; прыгаем через данные
@include pic.asm   
start

ldr r1,=REG_DISPCNT  ; r1 указывает на регистра управления экраном
ldr r0,=(MODE_3|BG2_ENABLE)  ; в r0 - необходимые значения для установки режима 3 и фона 2
str r0,[r1]          ; устанавливаем регистр контроля, сохраняя значения в памяти

;;--- КОНЕЦ КОПИРОВАНИЯ ---;;

Метка в pic.asm будет называться как имя .bmp-файла плюс "Bitmap", то есть, если вы переименовали bmp в "pic.bmp" перед конверсией, у вас получится pic.asm, внутри которого метка будет называться picBitmap, которая и будет являться адресом источки. VRAM или по-другому "экран" будет адресом назначения. Мы начнём трансфер немедленно и будет осуществлять его 16-ти битными блоками. Мы будем использовать 3-ий канал DMA, так как это официльно канал общего назначения.

;;--- КОД ПРОДОЛЖАЕТСЯ ---;;

ldr r1,=picBitmap     ; \
ldr r0,=REG_DMA3SAD   ; - устанавливаем адрес источника в регистре адреса 
                      ; источника 3-его канала
str r1,[r0]           ; /

ldr r1,=VRAM          ; \
ldr r0,=REG_DMA3DAD   ; - устанавливаем адрес назначения в регистре адреса 
                      ; назначения 3-его канала

str r1,[r0]           ; /

ldr r1,=(38400|DMA_16NOW)  ; \
ldr r0,=REG_DMA3CNT       ;  - указываем регистру контроля, сколько блоков копировать 
; на экране 240x160=38400 пикселей, мы перегоняем их 16-битными блоками, поэтому нам 
; не нужно уполовинивать это число
str r1,[r0]      ; сразу начинается DMA-трансфер, который перегоняет 38400 
                 ; 16-ти битных блоков данных, и программа не продолжит выполняться, 
				 ; пока эта операция не будет закончена. Но она довольно быстра.

infinite
b infinite

;;--- КОНЕЦ КОПИРОВАНИЯ ---;;
;;--- КОНЕЦ ИСХОДНОГО ФАЙЛА ---;;

Разве это не просто? Я всегда считал, что DMA - это что-то очень сложное, но оказалось, что это просто инициализация трёх регистров и всё! DMA - это круто. :)

Загрузка данных из Дня 5 с помощью DMA

Как вы помните, в Дне 5 у нас было 2 загрузочных цикла, которые были прекрасными кандидатами на замену DMA-трансфером. Один из них использовался для загрузки палитры, а другой - для загрузки спрайтов. В обоих случаях у нас есть всё, что нужно для DMA-трансфера: исходник (спрайт или палитра), назначение (память для символов или память для палитры) и сколько раз нужно обратиться к памяти. Для палитры это количество при трансфере 32-х битными блоками будет равным 128, так как в палитре 256 элементов. Для спрайта оно будет тем же самым, потому что.... ох, я не знаю! Мы делали это 128 раз в Дне 5, используя 32-х битные загрузку/сохранение, поэтому если мы будем придерживаться того же размера, то и обращаться к памяти придётся ровно столько же.

;;--- ЭТОТ КУСОК КОДА ИЗ ДНЯ 5 НАДО БУДЕТ ЗАМЕНИТЬ ---;;

ldr r1,=OBJPAL ; r1 указывает на палитку спрайта (0x500200) ldr r2,=128 ; 128 - сколько раз обращаемся к палитре ldr r3,=pallete ; палитра задана в sprit.asm (полученый конвертацией) palloop: ; Этот цикл - типичный пример загрузки данных с помощью цикла ldr r7,[r3]+4! ; 4 байта из палитры помещается в r. каждый раз не забываем ; увеличить указатель на 4 subs r2,r2,1 ; здесь количество обращений уменьшается на 1 bne palloop ; проверяем на равно ; если r2 = 0 (r2==0 для тех, кто привыки писать на C). Условие выполняется ; только, если значение не равно 0. ;;--- КОНЕЦ КУСКА КОДА ---;;

Заменить вышеуказанное на следующее:

;;--- НАЧАЛО КОДА ---;;

ldr r1,=pallete       ; \
ldr r2,=REG_DMA3SAD   ;  - адрес источника в DMA-регистре источника 
str r1,[r2]           ; /

ldr r1,=OBJPAL        ; \
ldr r2,=REG_DMA3DAD   ;  - адрес назначения в DMA-регистре назначения
str r1,[r2]           ; /

ldr r1,=(128|DMA_32NOW) ; \
ldr r2,=REG_DMA3CNT     ;  - количество данных для трансфера
str r1,[r2]             ; какого размера блоки используются (мы используем 32-х 
                        ; битные)

;;--- КОНЕЦ КОДА ---;;

Ок, теперь замените следующее:

;;--- ЭТО НАДО БУДЕТ ЗАМЕНИТЬ ---;;

ldr r1,=CHARMEM   ; смотрите описание ниже
ldr r2,=128
ldr r3,=obj0

sprloop:
ldr r7,[r3]+4!
str r7,[r1]+4!
subs r2,r2,1
bne sprloop

;;--- КОНЕЦ ---;;

Обратите внимание, что я оставил комментарии в коде из Дня 5 просто для того, чтобы вам было легче его найти.

Замените вышеприведённое на следующее:

;;--- НАЧАЛО КОДА ---;;

ldr r1,=obj0       ; \
ldr r2,=REG_DMA3SAD   ;  - адрес источника в DMA-регистре источника 
str r1,[r2]           ; /

ldr r1,=CHARMEM        ; \
ldr r2,=REG_DMA3DAD   ;  - адрес назначения в DMA-регистре назначения
str r1,[r2]           ; /

ldr r1,=(128|DMA_32NOW) ; \
ldr r2,=REG_DMA3CNT     ;  - количество данных для трансфера

str r1,[r2]             ; какого размера блоки используются (мы используем 32-х 
                        ; битные)

;;--- КОНЕЦ КОДА ---;;

Обратите внимание, что этот практически тот же самый код, не считая изменившихся адресов источника и назначения.

Если вы скомпилируете и запустите код из дня 5 с данными исправлениями, то он будет делать ТО ЖЕ САМОЕ, разве что загрузится быстрее. Вы можете заметиь, что полученный бинарник меньше размером, по сравнению с тем, где DMA не использовалось.

2002-2013 (c) wasm.ru