Files

207 lines
12 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file isr.h
* @brief ISR (Interrupt Service Routine) для FRAME-прерывания TSConfig
* @version 2.0
*
* Зависимости: types.h.
* Реализация: asm/isr.s (чистый ASM, hot-path без C).
*
* ═══════════════════════════════════════════════════════════════════
* АРХИТЕКТУРА
* ═══════════════════════════════════════════════════════════════════
*
* Частота ISR задаётся вариантом сборки (ISR_FREQ в variant_cfg.h).
* N_ENTRIES прерываний × STEP T-states = 71680 T = 1 TV-кадр (замыкание).
*
* Позиции INT — предрассчитанная таблица POS_TABLE (N_ENTRIES x 5 байт).
* ISR читает 3 байта через OUTI и записывает в VSINTH/VSINTL/HSINT,
* затем переходит по next_ptr (замкнутый цикл).
*
* ═══════════════════════════════════════════════════════════════════
* ДВОЙНАЯ БУФЕРИЗАЦИЯ КОМАНД
* ═══════════════════════════════════════════════════════════════════
*
* CMD_BUF_A, CMD_BUF_B — по 512 байт (до 128 команд).
* ISR читает из активного буфера, main loop заполняет свободный.
* Переключение буфера: CMD_END_BUF в конце каждого заполненного.
*
* ═══════════════════════════════════════════════════════════════════
* ФОРМАТ КОМАНДЫ (4 байта, выровнено)
* ═══════════════════════════════════════════════════════════════════
*
* Код │ Байт 1 │ Байт 2 │ Байт 3 │ Действие
* ──────────────┼────────┼────────┼────────┼─────────────────────
* CMD_WRITE_AY │ reg │ val │ 0 │ OUT AY8910 chip 1
* CMD_INC_SEC │ 0 │ 0 │ 0 │ Инкр. isr_play_seconds
* CMD_WRITE_AY2 │ reg │ val │ 0 │ OUT AY8910 chip 2 (TS)
* CMD_FINALIZE │ 0 │ 0 │ 0 │ Конец VGM → shutdown в main
* CMD_WRITE_B0 │ reg │ val │ 0 │ OUT OPL3 Bank 0
* CMD_SKIP_TICKS│ N │ smsk_lo│ smsk_hi│ Пропустить N позиций pos_table
* CMD_WRITE_SAA │ reg │ val │ 0 │ OUT SAA1099 chip 0
* CMD_WRITE_B1 │ reg │ val │ 0 │ OUT OPL3 Bank 1
* CMD_WRITE_SA2 │ reg │ val │ 0 │ OUT SAA1099 chip 1
* CMD_WRITE_WAVE│ reg │ val │ 0 │ OUT OPL4 Wave-часть (#7E/#7F)
* CMD_WAIT │ lo │ hi │ 0 │ Ждать (hi<<8)|lo тиков
* CMD_END_BUF │ 0 │ 0 │ 0 │ Переключить буфер
*
* CMD_SKIP_TICKS carries 16-bit spectrum bitmask: bit N → bar N triggered
*
* ═══════════════════════════════════════════════════════════════════
* ПРИМЕР ИСПОЛЬЗОВАНИЯ
* ═══════════════════════════════════════════════════════════════════
*
* #include "isr.h"
*
* // 1. Заполнить оба буфера
* fill_buffer(cmd_buf_a);
* fill_buffer(cmd_buf_b);
*
* // 2. Настроить ISR
* isr_enabled = 0; // пока не запускать
* isr_active_buf = 0; // начать с буфера A
* isr_init(); // IM2 + таблица векторов
*
* // 3. Запустить
* isr_enabled = 1;
* __asm__("ei");
*
* // 4. В main loop:
* if (isr_active_buf != last_buf) {
* fill_buffer(isr_active_buf ? cmd_buf_a : cmd_buf_b);
* last_buf = isr_active_buf;
* }
*/
#ifndef ISR_H
#define ISR_H
#include "types.h"
/* ── Коды команд (8 типов, шаг 0x20 + ISR_DONE) ─────────────────── */
#define CMD_WRITE_AY 0x00 /* AY8910 chip 1: [reg, val, 0] */
#define CMD_INC_SEC 0x10 /* Инкрементировать счётчик секунд: [0,0,0] */
#define CMD_WRITE_AY2 0x20 /* AY8910 chip 2 (TurboSound): [reg,val,0]*/
#define CMD_FINALIZE 0x30 /* Конец VGM: остановить ISR и уведомить main */
#define CMD_WRITE_B0 0x40 /* OPL3 Bank 0 write: [reg, val, 0] */
#define CMD_SKIP_TICKS 0x50 /* Пропустить N pos_table: [N,sml,smh] */
#define CMD_WRITE_SAA 0x60 /* SAA1099 write: [reg, val, 0] bit7=chip */
#define CMD_WRITE_B1 0x80 /* OPL3 Bank 1 write: [reg, val, 0] */
#define CMD_WRITE_WAVE 0x70 /* OPL4 Wave-часть write (ZXM-MoonSound, порты #7E/#7F): [reg, val, 0] */
#define CMD_WAIT 0xC0 /* Ждать N ISR-тиков: [lo, hi, 0] */
#define CMD_END_BUF 0xE0 /* Переключить буфер: [0, 0, 0] */
#define CMD_ISR_DONE 0xF0 /* Заморозить ISR, выставить isr_done: [0,0,0] */
/* ── Размер командного буфера ────────────────────────────────────── */
#define CMD_BUF_SIZE 512 /* байт; 128 команд по 4 байта */
/* ── Параметры варианта (ISR_FREQ, VGM_SAMPLE_*, budget) ───── */
#include "variant_cfg.h"
/* ── Переменные ISR (volatile = разделяемые между ISR и main) ────── */
/** Управляющий байт MultiSound (пишется в #FFFD перед каждой записью AY).
* bit0: chip select (0=chip1, 1=chip2) — ISR управляет автоматически.
* bit1: 1=normal read (всегда 1).
* bit2: 0=FM ON (high-Z), 1=FM OFF (forced low).
* bit3: 0=SAA CLK ON, 1=SAA CLK OFF.
* bit[7:4]: 0xF (маркер MultiSound ctrl).
* Idle = 0xFE (FM OFF, SAA OFF). */
extern volatile uint8_t isr_ms_ctrl;
/** Индекс активного буфера: 0 = A, 1 = B.
* Записывается ISR при CMD_END_BUF, читается main loop. */
extern volatile uint8_t isr_active_buf;
/** Флаг включения: 0 = ISR пропускает выполнение команд
* (pos_table и tick_ctr продолжают работать).
* Записывается main loop, читается ISR.
* instant_abort() использует это для заморозки без доп. флагов. */
extern volatile uint8_t isr_enabled;
/** Счётчик тиков ISR. Инкрементируется каждый ISR-вызов (частота = ISR_FREQ),
* оборачивается при 256 (полный оборот ≈ 256/ISR_FREQ секунд).
* main loop читает для определения прошедшего времени. */
extern volatile uint8_t isr_tick_ctr;
/** Цвет бордюра, восстанавливаемый ISR при выходе.
* Записывается main loop (0=black, 2=red и т.д.), читается ISR. */
extern volatile uint8_t isr_border_color;
/** Счётчик секунд воспроизведения.
* Инкрементируется ISR по команде CMD_INC_SEC, читается main loop. */
extern volatile uint16_t isr_play_seconds;
/** Флаг завершения: 1 = ISR заморожен на CMD_ISR_DONE.
* Записывается ISR, читается main loop.
* Main loop очищает при старте воспроизведения. */
extern volatile uint8_t isr_done;
/** ISR дошёл до последнего 0x66; main должен запустить shutdown. */
extern volatile uint8_t isr_final_pending;
/** Командные буферы A и B. Заполняются main loop, читаются ISR. */
extern uint8_t cmd_buf_a[CMD_BUF_SIZE];
extern uint8_t cmd_buf_b[CMD_BUF_SIZE];
/* ── Spectrum analyzer (16 bars, 0-8 levels) ─────────────────────── */
#define SPECTRUM_BARS 16
#define SPECTRUM_MAX_LEVEL 8
/** уровни спектра для каждой из 16 полос, 0..SPECTRUM_MAX_LEVEL.
* Заполняются ISR (через CMD_SKIP_TICKS), читаются main loop для рендеринга.
* Каждый бит в CMD_SKIP_TICKS соответствует одной полосе, устанавливается при активации.
* Например, если бит 0x0001 установлен, то полоса 0 (самая левая) будет на максимальном уровне.
* Если бит 0x0002 установлен, то полоса 1 будет на максимальном уровне, и так далее.
*/
extern volatile uint8_t spectrum_levels[SPECTRUM_BARS];
/** Контрольный счётчик текущей задержки CMD_WAIT.
* ISR декрементирует при каждом тике, переходит к следующей команде после достижения 0.
* Не используется напрямую main loop — только ISR. */
extern volatile uint16_t isr_wait_ctr;
/** Указатель чтения ISR внутри активного буфера. */
extern volatile uint16_t isr_read_ptr;
/* ── Функции ─────────────────────────────────────────────────────── */
/**
* Перехват вектора IM2 (#5BFF) и настройка 2734 Hz.
*
* Действия:
* 1. Сохранить вектор WC (#5BFF) → isr_saved_vec
* 2. Записать адрес isr_handler в #5BFF
* 3. Настроить INTMASK = FRAME only, позиции INT
* 4. Инициализировать pos_ptr, wait_ctr, tick_ctr, read_ptr
* 5. IM 2 (EI делается вызывающим кодом)
*
* ВАЖНО: вызывать ПОСЛЕ заполнения обоих буферов!
* ВАЖНО: вызывать при DI (прерывания выключены).
*/
void isr_init(void);
/**
* Восстановить вектор WC и сбросить INT-позиции.
*
* Действия:
* 1. Восстановить оригинальный вектор из isr_saved_vec → #5BFF
* 2. Сбросить позицию INT на line=0 (один INT/кадр)
* 3. INTMASK = FRAME only (WC default)
* НЕ делает EI — вызывающий код решает сам.
*
* Безопасно вызывать без предшествующего isr_init (no-op).
*/
void isr_deinit(void);
/**
* Вызов оригинального WC ISR handler из main loop.
*
* WC ожидает периодический вызов своего ISR (обновление таймера,
* клавиатуры, часов). Вызывать ~50 Гц из main loop.
* Делает DI → JP в WC handler → WC делает EI+RET.
* Безопасно вызывать без предшествующего isr_init (no-op).
*/
void call_wc_handler(void);
#endif /* ISR_H */