GBA ASM - День 10: Прерывания и таймеры — Архив WASM.RU

Все статьи

GBA ASM - День 10: Прерывания и таймеры — Архив WASM.RU

Об этом дне

Сегодня мы должны изучить прерывания и таймеры. Я не уверен, что у прерываний есть иное полезное применение кроме процедуры HBlank или VBlank, поэтому именно это и будет продемнстрировано в коде. Таймеры, по крайней мере насколько могу судить я, имеют несколько применений, включая.. гм.. тайминг :). Сходу я могу назвать одну вещь, которая требует применение DMA, таймеров и прерываний одновременно - это вывод звука (например, проигрывание wav-файла - мы сделаем это в другой раз).

О прерываниях

Прерывание - это точно то, что вы думаете (возможно :) ). Они прерывают работу маленького ARM7-процессора GBA и заставляют его начать выполнение другой части программы, чтобы затем вернуться обратно, как только всё, что нужно будет сделано. Вы можете решить, что это уничтожить значения, находящиеся в регистрах, но на самом деле этого не происходит, так как сначала управление переходит к BIOS'у, который сохраняет значения регистров, перед тем как передать управление обработчику прерывания. После того, как обработчик отработал, значения регистров восстанавливаются (обратите внимание, что под здесь под регистрами я подразумеваю регистры процессора - r0 и подобные).

Для указания GBA, что нам требуется обрабатывать прерывание, мы должны сделать несколько вещей.

Шаги для обработки прерывания:

  1. Включить прерывания в REG_IME. Я написал специальный макрос для этого, который находится в interrupt.h.
  2. В регистре REG_IE необходимо указать, какое прерывание мы хотим обрабатывать.
  3. В REG_INTADDR задаётся адрес обработчика прерывания.
  4. Включить прерывания снова с помощью регистра, который зависит от того, какое прерывание мы собираемся использовать.
  5. Вот и всё, теперь прерывание будет происходить, и GBA автоматически будет передавать туда управление!

Давайте напишем прерывание

Скачайте специальный заголовочный файл interrupt.h.

Почему бы вам не сконвертировать изображение в ассемблер, как мы делали это раньше. Не забудьте заменить "DCW" на "@DCW". Откройте его и убедитесь, что метка внутри - picBitmap.

;;--- НАЧАЛО КОДА ---;;
@include screen.h  ; нужно почти всегда
@include interrupt.h ; для прерываний
@include dma.h  ; макрос DrawMode3Pic использует DMA

b start
@include pic.asm  ; подключаем картинку - не забудьте перепрыгнуть через неё.
start

; устанавливаем режим экрана и включаем режим бэкграунда 2
ldr r1,=REG_DISPCNT
ldr r2,=(MODE_3|BG2_ENABLE)
str r2,[r1]       

Interrupts_Enable  ; мой макрос, который включает в (REG_IME)
                   ; поддержку прерываний

ldr r1,=REG_IE 
ldr r2,=INT_VBLANK  ; эти три строки указывают, что мы будет обрабатывать 
str r2,[r1]         ; прерывание VBlank

addr r1,inthandler   ; а эти три загружают в REG_INTADDR адрес
ldr r2,=REG_INTADDR  ; обработчика прерывания. ADDR - это особенность 
str r1,[r2]   ; ассемблера, помещающий адрес метки в регистр CPU,
; который в дальнейшем мы помещаем в регистр памяти REG_INTADDR
;;--- STOP COPYING ---;;

Следующий регистр памяти, который мы должны инициализировать зависит от того, какое прерывание мы хотим обрабатывать. Нам нужно прерывание VBlank, поэтому необходимо указать экрану его генерировать. Мы делаем это с помощью регистра REG_DISPSTAT.

;;--- CODE CONTINUE ---;;
ldr r1,=REG_DISPSTAT
ldr r2,=STAT_VBLANK
str r2,[r1]

infin
b infin
;;--- СТОП КОПИРОВАНИЕ ---;;

Это конец основной части программы, но мы ещё НЕ закончили. На необходимо написать обработчик прерывания. Когда вы посмотрите и потестируете сам код, вы поймёте больше, чем я могу объяснить так, поэтому продолжнаем:

;;--- КОД ПРОДОЛЖАЕТСЯ ---;;
inthandler   ; метка начала обработчика

DrawMode3Pic picBitmap    ; передаём метку картинки макросу
; заметье, что он использует DMA, поэтому нам и понадобился dma.h

ldr r9,=INT_VBLANK      ; обратите внимание, что нам нужно передать регистр
                        ; макросу ReturnFromHandler
ReturnFromHandler r9   ; говорим GBA, что прерывание обрабатывается
; Также отметьте, что вам необходимо использовать хотя бы r4 для этого макроса, 
; так как r0-r3 используются внутри него.

bx lr ; возвращение из прерывания, это ветвчение к адресу в r13, так что не
      ; не обращайте на это большого внимания. Вероятно, имеет смысл 
      ; поместить эту строку в ReturnFromHandler
;;--- КОНЕЦ КОДА ---;;

Теперь, когда вы запустите программу отобразится картинка. Ничего особенного. Но когда мы изучим как использовать рабочую память, вы увидите, насколько важными могут быть прерывания и в особенности VBlank. Есть несколько вещей, которые лучше делать во время VBlank, поэтому наличие соответствующего прерывания очень полезно. Обратите внимание, что прерывания могут создавать другие вещи, мы лишь прикоснулись к самой поверхности. Теперь переходит к таймерам!

О таймерах

Я думаю, что слово таймеры может ввести в небольшое заблужение, так как они скорее являются счётчиками, чем таймерами. Тем не менее, они дают возможность следить за верменим. Я не уверен, что следует конкретно сделать в приводимом мной коде, потому что единственное применение, которое пришло мне в голову - это вывод звука, а его мы будем изучать позже. Пожалуйста, помните, что даже хотя мы использовали Timer #3, всего их 4 (0-3). При необходимости замените 3 на требуемый номер в соответствующих макросах.

Теперь вам следует скачать timer.h.

Программирование таймеров

Прежде чем мы начнём, пара замечаний. Таймеры могут находится на 1 из 4 частот, что влияет на то, как быстро они считают. Таймеры считают от 0 до 655535. Переход от 65535 к 0 называется переполнением, и таймер можно заставить генерировать соответствующее прерывание. Мы не будем этого делать, но знайте, что это возможно.

Частоты таймеров:

  1. Системная частота - 15 оборотов.
  2. 64-х кратная системная частота
  3. 256-ти кратная системная частота (почти секунда, ох).
  4. 1024-х системая частота.

Полагаю, что 3-я наиболее подходит. Нам осталось написать макрос WaitSeconds, что не должно оказаться слишком сложным. Я уже написал его, и он находится в timer.h, но рекоменду пройтись со мной по его созданию :).

Чтобы запустить таймер, вам потребуется включить его и загрузить в REG_TMxCNT частоту. Включение таймера происходит с помощью макроса EnableTMx (x=№ таймера):

ldr r10,=TIME_FREQUENCY_256 EnableTM3 r10

Обратите внимание, что мы не можем передать значение, определённое директивой define в силу ограничения Goldroad. Поэтому вам нужно загрузить его в регистр CPU, и передать уже его.

Теперь мы можем начать писать макрос WaitSeconds. Ок, это тот случай, когда нам нужен цикл и нельзя использовать DMA. Поехали:

ПОЖАЛУЙСТА, ПОМОГИТЕ! Я НЕ МОГУ ЗАСТАВИТЬ ЭТО РАБОТАТЬ! (опа - прим. пер.)

;;--- НАЧАЛО КОДА ---;;
@macro WaitSeconds3 Number, arglabel

ldr r2,=Number  ; Number будет заменён на переданное при вызове макроса число
arglabel   ; вам нужно передать имя, которое будет использовано в качестве 
           ; метки для цикла
;;--- КОД ПРИОСТАНОВЛЕН. ПОЯСНЕНИЕ ---;;

Здесь начинается макрос. Мы загружаем в r2 количество секунд и создаём метку для основного цикла.

;;--- ПРОДОЛЖЕНИЕ КОДА ---;;
ldr r1,=REG_TM3D   ; сравниваем содержимое таймера с 0, 
ldrh r3,[r1]       ; и если оно не равно нулю, то
ldr r4,=0          ; то переходим обратно на arglabel
cmp r3,r4    
bne arglabel
;;--- КОД ПРИОСТАНОВЛЕН. ПОЯСНЕНИЕ ---;;

Здесь мы проверяем, произошло ли переполение. Если нет, то переходим обратно к arglabel. Заметьте, что поскольку мы включили таймер на частоте в 256 раз большей системной, то нам нужно просто замерить столько переполнений, сколько секунд нам нужно подождать. И закончим.

;;--- CODE CONTINUE ---;;
subs r2,r2,#1
bne arglabel

@endm
;;--- CODE STOP ---;;

Это было весело. Я надеюсь, что у вас ещё осталась та картинка, которую вы сконвертировали для первой части этого Дня, потому что сейчас мы напишем маленькую программу, которая будет ждать 30 секунд перед выводом картинки. Давайте начнём:

;;--- НАЧАЛО КОДА ---;;
@include screen.h  ; нам это нужно
@include dma.h   ; это нужно макросу, отрисовывающему картинку
@include timer.h ; нужно для таймеров

b start
@include pic.asm  ; подключаем данные с картинкой и перепрыгиваем через них
start

ldr r1,=REG_DISPCNT         ; устанавливаем режим
ldr r2,=(MODE_3|BG2_ENABLE)  ; экрана
str r2,[r1]                  ; №3

ldr r10,=TIME_FREQUENCY_256
EnableTM0 r10

WaitSeconds0 30, waitlabel1  ; ждём 30 секунд и используем waitlabel1 в  
                             ; качестве метки внутри макроса

DrawMode3Pic meBitmap  ; отрисовываем картинку на экране

infin
b infin  ; бесконечный цикл
;;--- КОНЕЦ КОДА ---;;

Я знаю, что здесь немного кода, но за ним стоит очень немало всего!

Обзор этого Дня

Я надеюсь, что вам понравился этот День. Он отнял у меня немало сил. Извините, что код таймера работает не очень хорошо :(.

2002-2013 (c) wasm.ru