GBA ASM - День 10: Прерывания и таймеры

Дата публикации 18 июл 2006

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

Об этом дне

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

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

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

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

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

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

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

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

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

Код (Text):
  1.  
  2. ;;--- НАЧАЛО КОДА ---;;
  3. @include screen.h  ; нужно почти всегда
  4. @include interrupt.h ; для прерываний
  5. @include dma.h  ; макрос DrawMode3Pic использует DMA
  6.  
  7. b start
  8. @include pic.asm  ; подключаем картинку - не забудьте перепрыгнуть через неё.
  9. start
  10.  
  11. ; устанавливаем режим экрана и включаем режим бэкграунда 2
  12. ldr r1,=REG_DISPCNT
  13. ldr r2,=(MODE_3|BG2_ENABLE)
  14. str r2,[r1]      
  15.  
  16. Interrupts_Enable  ; мой макрос, который включает в (REG_IME)
  17.                    ; поддержку прерываний
  18.  
  19. ldr r1,=REG_IE
  20. ldr r2,=INT_VBLANK  ; эти три строки указывают, что мы будет обрабатывать
  21. str r2,[r1]         ; прерывание VBlank
  22.  
  23. addr r1,inthandler   ; а эти три загружают в REG_INTADDR адрес
  24. ldr r2,=REG_INTADDR  ; обработчика прерывания. ADDR - это особенность
  25. str r1,[r2]   ; ассемблера, помещающий адрес метки в регистр CPU,
  26. ; который в дальнейшем мы помещаем в регистр памяти REG_INTADDR
  27. ;;--- STOP COPYING ---;;

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

Код (Text):
  1.  
  2. ;;--- CODE CONTINUE ---;;
  3. ldr r1,=REG_DISPSTAT
  4. ldr r2,=STAT_VBLANK
  5. str r2,[r1]
  6.  
  7. infin
  8. b infin
  9. ;;--- СТОП КОПИРОВАНИЕ ---;;

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

Код (Text):
  1.  
  2. ;;--- КОД ПРОДОЛЖАЕТСЯ ---;;
  3. inthandler   ; метка начала обработчика
  4.  
  5. DrawMode3Pic picBitmap    ; передаём метку картинки макросу
  6. ; заметье, что он использует DMA, поэтому нам и понадобился dma.h
  7.  
  8. ldr r9,=INT_VBLANK      ; обратите внимание, что нам нужно передать регистр
  9.                         ; макросу ReturnFromHandler
  10. ReturnFromHandler r9   ; говорим GBA, что прерывание обрабатывается
  11. ; Также отметьте, что вам необходимо использовать хотя бы r4 для этого макроса,
  12. ; так как r0-r3 используются внутри него.
  13.  
  14. bx lr ; возвращение из прерывания, это ветвчение к адресу в r13, так что не
  15.       ; не обращайте на это большого внимания. Вероятно, имеет смысл
  16.       ; поместить эту строку в ReturnFromHandler
  17. ;;--- КОНЕЦ КОДА ---;;

Теперь, когда вы запустите программу отобразится картинка. Ничего особенного. Но когда мы изучим как использовать рабочую память, вы увидите, насколько важными могут быть прерывания и в особенности 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, но рекоменду пройтись со мной по его созданию :smile3:.

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

ldr r10,=TIME_FREQUENCY_256 EnableTM3 r10

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

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

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

Код (Text):
  1.  
  2. ;;--- НАЧАЛО КОДА ---;;
  3. @macro WaitSeconds3 Number, arglabel
  4.  
  5. ldr r2,=Number  ; Number будет заменён на переданное при вызове макроса число
  6. arglabel   ; вам нужно передать имя, которое будет использовано в качестве
  7.            ; метки для цикла
  8. ;;--- КОД ПРИОСТАНОВЛЕН. ПОЯСНЕНИЕ ---;;

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

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

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

Код (Text):
  1.  
  2. ;;--- CODE CONTINUE ---;;
  3. subs r2,r2,#1
  4. bne arglabel
  5.  
  6. @endm
  7. ;;--- CODE STOP ---;;

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

Код (Text):
  1.  
  2. ;;--- НАЧАЛО КОДА ---;;
  3. @include screen.h  ; нам это нужно
  4. @include dma.h   ; это нужно макросу, отрисовывающему картинку
  5. @include timer.h ; нужно для таймеров
  6.  
  7. b start
  8. @include pic.asm  ; подключаем данные с картинкой и перепрыгиваем через них
  9. start
  10.  
  11. ldr r1,=REG_DISPCNT         ; устанавливаем режим
  12. ldr r2,=(MODE_3|BG2_ENABLE)  ; экрана
  13. str r2,[r1]                  ; №3
  14.  
  15. ldr r10,=TIME_FREQUENCY_256
  16. EnableTM0 r10
  17.  
  18. WaitSeconds0 30, waitlabel1  ; ждём 30 секунд и используем waitlabel1 в  
  19.                              ; качестве метки внутри макроса
  20.  
  21. DrawMode3Pic meBitmap  ; отрисовываем картинку на экране
  22.  
  23. infin
  24. b infin  ; бесконечный цикл
  25. ;;--- КОНЕЦ КОДА ---;;

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

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

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


0 1.341
archive

archive
New Member

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