GBA ASM - День 10: Прерывания и таймеры — Архив WASM.RU
Об этом дне
Сегодня мы должны изучить прерывания и таймеры. Я не уверен, что у прерываний есть иное полезное применение кроме процедуры HBlank или VBlank, поэтому именно это и будет продемнстрировано в коде. Таймеры, по крайней мере насколько могу судить я, имеют несколько применений, включая.. гм.. тайминг . Сходу я могу назвать одну вещь, которая требует применение DMA, таймеров и прерываний одновременно - это вывод звука (например, проигрывание wav-файла - мы сделаем это в другой раз).
О прерываниях
Прерывание - это точно то, что вы думаете (возможно ). Они прерывают работу маленького ARM7-процессора GBA и заставляют его начать выполнение другой части программы, чтобы затем вернуться обратно, как только всё, что нужно будет сделано. Вы можете решить, что это уничтожить значения, находящиеся в регистрах, но на самом деле этого не происходит, так как сначала управление переходит к BIOS'у, который сохраняет значения регистров, перед тем как передать управление обработчику прерывания. После того, как обработчик отработал, значения регистров восстанавливаются (обратите внимание, что под здесь под регистрами я подразумеваю регистры процессора - r0 и подобные).
Для указания GBA, что нам требуется обрабатывать прерывание, мы должны сделать несколько вещей.
Шаги для обработки прерывания:
- Включить прерывания в REG_IME. Я написал специальный макрос для этого, который находится в interrupt.h.
- В регистре REG_IE необходимо указать, какое прерывание мы хотим обрабатывать.
- В REG_INTADDR задаётся адрес обработчика прерывания.
- Включить прерывания снова с помощью регистра, который зависит от того, какое прерывание мы собираемся использовать.
- Вот и всё, теперь прерывание будет происходить, и GBA автоматически будет передавать туда управление!
Давайте напишем прерывание
Скачайте специальный заголовочный файл interrupt.h.
Почему бы вам не сконвертировать изображение в ассемблер, как мы делали это раньше. Не забудьте заменить "DCW" на "@DCW". Откройте его и убедитесь, что метка внутри - picBitmap.
Код (Text):
;;--- НАЧАЛО КОДА ---;; @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.
Код (Text):
;;--- CODE CONTINUE ---;; ldr r1,=REG_DISPSTAT ldr r2,=STAT_VBLANK str r2,[r1] infin b infin ;;--- СТОП КОПИРОВАНИЕ ---;;Это конец основной части программы, но мы ещё НЕ закончили. На необходимо написать обработчик прерывания. Когда вы посмотрите и потестируете сам код, вы поймёте больше, чем я могу объяснить так, поэтому продолжнаем:
Код (Text):
;;--- КОД ПРОДОЛЖАЕТСЯ ---;; 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 называется переполнением, и таймер можно заставить генерировать соответствующее прерывание. Мы не будем этого делать, но знайте, что это возможно.
Частоты таймеров:
- Системная частота - 15 оборотов.
- 64-х кратная системная частота
- 256-ти кратная системная частота (почти секунда, ох).
- 1024-х системая частота.
Полагаю, что 3-я наиболее подходит. Нам осталось написать макрос WaitSeconds, что не должно оказаться слишком сложным. Я уже написал его, и он находится в timer.h, но рекоменду пройтись со мной по его созданию .
Чтобы запустить таймер, вам потребуется включить его и загрузить в REG_TMxCNT частоту. Включение таймера происходит с помощью макроса EnableTMx (x=№ таймера):
ldr r10,=TIME_FREQUENCY_256 EnableTM3 r10
Обратите внимание, что мы не можем передать значение, определённое директивой define в силу ограничения Goldroad. Поэтому вам нужно загрузить его в регистр CPU, и передать уже его.
Теперь мы можем начать писать макрос WaitSeconds. Ок, это тот случай, когда нам нужен цикл и нельзя использовать DMA. Поехали:
ПОЖАЛУЙСТА, ПОМОГИТЕ! Я НЕ МОГУ ЗАСТАВИТЬ ЭТО РАБОТАТЬ! (опа - прим. пер.)
Код (Text):
;;--- НАЧАЛО КОДА ---;; @macro WaitSeconds3 Number, arglabel ldr r2,=Number ; Number будет заменён на переданное при вызове макроса число arglabel ; вам нужно передать имя, которое будет использовано в качестве ; метки для цикла ;;--- КОД ПРИОСТАНОВЛЕН. ПОЯСНЕНИЕ ---;;Здесь начинается макрос. Мы загружаем в r2 количество секунд и создаём метку для основного цикла.
Код (Text):
;;--- ПРОДОЛЖЕНИЕ КОДА ---;; ldr r1,=REG_TM3D ; сравниваем содержимое таймера с 0, ldrh r3,[r1] ; и если оно не равно нулю, то ldr r4,=0 ; то переходим обратно на arglabel cmp r3,r4 bne arglabel ;;--- КОД ПРИОСТАНОВЛЕН. ПОЯСНЕНИЕ ---;;Здесь мы проверяем, произошло ли переполение. Если нет, то переходим обратно к arglabel. Заметьте, что поскольку мы включили таймер на частоте в 256 раз большей системной, то нам нужно просто замерить столько переполнений, сколько секунд нам нужно подождать. И закончим.
Код (Text):
;;--- CODE CONTINUE ---;; subs r2,r2,#1 bne arglabel @endm ;;--- CODE STOP ---;;Это было весело. Я надеюсь, что у вас ещё осталась та картинка, которую вы сконвертировали для первой части этого Дня, потому что сейчас мы напишем маленькую программу, которая будет ждать 30 секунд перед выводом картинки. Давайте начнём:
Код (Text):
;;--- НАЧАЛО КОДА ---;; @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 ; бесконечный цикл ;;--- КОНЕЦ КОДА ---;;Я знаю, что здесь немного кода, но за ним стоит очень немало всего!
Обзор этого Дня
Я надеюсь, что вам понравился этот День. Он отнял у меня немало сил. Извините, что код таймера работает не очень хорошо :(. © Mike H, пер. Aquila
GBA ASM - День 10: Прерывания и таймеры
Дата публикации 18 июл 2006