Программирование assembler 6502 nes/famicom/dendy векторы прерывания, процедуры и их вызов
Векторы прерывания в формате программирование на ассемблере 6502, можно представить как всем хорошо известный патерн event-observer в высокоуровневых языках программирования. Конечно же можно реализовать данный патерн и на ассемблере но я его привел для большего понимания работы вектора прерывания.
Если помните раннее я уже упоминал про память в 6 байт (3 слова, по 16 бит), которые выделены для секции VECTOR данный сегмент содержит всего 3 адреса процедур
.segment "VECTORS"
.addr nmi_isr, reset, irq_isr
Когда ассемблер соберет код в бинарный файл заместо меток будет установлен соответствующий адрес данных процедур, которые обработают определенное прерывания. Всего их 3 в Famicom
NMI - Not Masked Interrupt - событие срабатывающие при генерации каждого кадра то есть 60 раз в секунду. Тут будет отрабатывать код для отрисовки кадра игры.
RESET - Событие перезагрузки - тут мы будем инициализировать программу, исходя из названия данное прерывание срабатывает при перезагрузки/загрузки процессора
IRQ - Interrupt request - прерывание которое может быть запущенно по запросу какого либо оборудования
Я в своей игре пока не использую IRQ по этому процедура выглядит довольно просто
.proc irq_isr
RTI
.endproc
RTI - return from interrupt возврат в прерывание, это довольно важное замечание забегая вперед скажу что в остальных случаях объявление .proc будет выполнять инструкцию RTS - Return from subroutine что буквально вернуть в подпрограмму.
Мы плавно подошли к понятию .proc (procedure), сам по себе .proc определяет некую область видимость где могут быть использованы и определенны одноименные метки, но в контексте процедуры возможен только переход на метки данной процедуры. К примеру:
.proc foo
LDA var
CMP #$01
BEQ return
LDA var2
STA var3
return:
RTS
.endproc
.proc bar
LDA var
CMP #$02
BEQ return
LDA var2
STA var3
return:
RTS
.endproc
.proc main
JSR foo
JSR bar
.endproc
Но не только саброуты (процедуры) они еще позволяют структурировать наш код в более понятные секции как функции/процедуры/методы в других языках. И еще один огромный плюс, конструкции ветвления JMP, BEQ, BNE, BCC, BCS могут перейти только на интервал -128-+128 строк, то есть в пределах адресации лимитированные 1 байтом (максимальное число 256) а вот процедуры по факту не имеют такого ограничения.
Выше в подпрограмме я привел пример как вызвать процедуру, с помощью инструкции JSR - jump to SubRoutine с помощью нее мы переходим в подпрограмму, выполняем какие то действия и в окончание в подпрограмме которую мы вызвали происходит вызов инструкции RTS, которая возвращает нас обратно в процедуру которая до этого вызвала эту подпрограмму, и родительская подпрограмма продолжает выполняться дальше.
Ассемблер ca65 и многие другие ассемблеры позволяют выполнять инструкции .include "filename.asm" которая включает файлы в главный файл игры. Интересно что в каждом файле мы можем определить нужные нам секции, которые определены в ini файлы конфигурации линкера который определяет в какой диапазон памяти положить ту или иную секцию кода, но это уже разговор для следующих статей.
Полезные ссылки:
https://youtu.be/KGXwoqYJ9Dk - видео/аудио формат статьи
https://github.com/lnroma/newGameNes - репозиторий с кодом игры
https://habr.com/ru/post/715994/ - считывание контроллера
https://habr.com/ru/post/551488/ - вводная статья
https://habr.com/ru/post/715994/ - прокрутка фона
https://habr.com/ru/post/553848/ - создание графики nes/dendy