/* * kbd_raw_poll — вычерпать приёмный FIFO клавиатуры ОПРОСОМ, не дожидаясь * прерывания. Дополняет (не заменяет) декодер в трамплине: тот же разбор * PS/2 Set 2, та же битовая карта _kbdraw_down[], тот же FSM префиксов * _kbdraw_pending — просто вызванный из главного цикла приложения. * * ЗАЧЕМ (разбор 2026-08-01, applications/PoP/roomtest/TASKS.md, KBD-1). * Приёмный FIFO SIO — 3 байта, и рассчитывать на то, что каждый принятый * байт разбудит нас прерыванием, НЕЛЬЗЯ: * - импульс запроса прерывания клавиатуры живёт лишь 32 такта CPU * (dev-MAME: sprinter.cpp on_kbd_data → irq_off_timer), а ядра * акселератора держат DI на весь блит — сотни микросекунд. Импульс, * попавший в такое окно, теряется НАСОВСЕМ: байт остаётся в FIFO до * следующего прерывания (следующий байт либо кадровое, 50 Гц); * - трафик от клавиатуры пачками: тап стрелки — 5 байт, а при зажатом * Shift ещё и «fake shift» (E0 F0 12 / E0 12 вокруг расширенного кода) * — по 5 байт на нажатие И на отпускание. Три ячейки FIFO такую пачку * не держат: потерянный make = «нажатие не сработало», потерянный * break = залипшая клавиша (её потом лечит kbd_raw_sync, ценой сброса * всех немодификаторных клавиш). * Опрос из главного цикла снимает зависимость от пойманного импульса: чем * чаще позвали, тем меньше шанс, что в FIFO успеет накопиться четвёртый * байт. Звать между фазами кадра (после тяжёлых блитов), НЕ раз в кадр. * * ABI: __sdcccall(1), без аргументов, результат uint8 в A — 0 если FIFO * был пуст (быстрый выход, ~40 тактов), 1 если что-то вычерпано. IX не * трогаем. Тело идёт под DI: чтение порта 0x18 деструктивно, и гонка с * трамплином за read-modify-write ячейки _kbdraw_down[] стоила бы * потерянного бита. DI/EI — БЕЗУСЛОВНЫЕ (как IRQ_DISABLE/IRQ_ENABLE в * kbd_raw_open): функция рассчитана на вызов из главного цикла, где * прерывания разрешены; звать её из ISR нельзя. */ #include #include "_kbdraw.h" #include "../irq/_irq.h" uint8_t kbd_raw_poll(void) __naked { __asm ld a, (__kbdraw_active) or a, a jr Z, kp_off ; raw-канал закрыт — не наше дело ;; DI ДО пробы «есть ли байт»: иначе между пробой и DI успело бы ;; влезть прерывание, трамплин вычерпал бы FIFO, а мы всё равно ;; пошли бы читать порт 0x18 — и получили бы из пустого приёмника ;; мусорный «скан-код» (взведённый бит клавиши, которую не нажимали). di in a, (#_IRQ_SIO_A) ; SIO-A RR0 rrca ; бит 0 — байт принят? jr C, kp_work ei kp_off: xor a, a ret kp_work: push hl push de push bc kp_drain: in a, (#_IRQ_SIO_DATA) ; чтение деструктивно = дренаж байта cp #0xF0 jr Z, kp_setbrk cp #0xE0 jr Z, kp_setext ;; --- обычный байт кода: БИТ code&7 в _kbdraw_down[code>>3]. ;; Маска сдвигом, а не таблицей — ровно как в трамплине, чтобы ;; расхождения декодеров были невозможны by construction. --- ld e, a ; E = код and #7 ld b, a inc b ; B = (код&7)+1 для djnz ld a, #1 jr kp_mskdec kp_mskloop: add a, a kp_mskdec: djnz kp_mskloop ; A = 1 << (код&7) ld c, a ; C = маска бита ld a, e rrca rrca rrca and #0x1F ; A = код>>3 (код <= 255) ld hl, #__kbdraw_down ld d, #0 ld e, a add hl, de ld a, (__kbdraw_pending) bit 1, a ; _KBDRAW_PEND_EXT? jr Z, kp_noext ld de, #32 ; +256 кодов = +32 байта карты add hl, de kp_noext: ld a, (__kbdraw_pending) bit 0, a ; _KBDRAW_PEND_BREAK? jr Z, kp_mkdown ld a, c cpl ; break → сбросить бит and a, (hl) jr kp_wr kp_mkdown: ld a, c ; make → взвести бит or a, (hl) kp_wr: ld (hl), a xor a, a ld (__kbdraw_pending), a ; префиксы потреблены jr kp_next kp_setbrk: ld hl, #__kbdraw_pending set 0, (hl) jr kp_next kp_setext: ld hl, #__kbdraw_pending set 1, (hl) kp_next: in a, (#_IRQ_SIO_A) rrca ; ещё байт в FIFO? jr C, kp_drain ;; --- FIFO пуст: тот же разбор Rx-overrun, что и в трамплине. ;; RR1 бит5 = Rx Overrun; без Error Reset (WR0 = 0x30) бит залипнет ;; и следующий overrun будет неотличим. --- ld a, #1 out (#_IRQ_SIO_A), a ; указатель на RR1 in a, (#_IRQ_SIO_A) ; читаем RR1 (указатель авто-сброс в 0) bit 5, a jr Z, kp_noovr ld a, #0x30 out (#_IRQ_SIO_A), a ; WR0 Error Reset ld a, #1 ld (__kbdraw_overrun), a ; снимет kbd_raw_sync() kp_noovr: pop bc pop de pop hl ei ld a, #1 ret __endasm; }