Files
Sprinter-SDCC/libc/kbd/kbd_raw_poll.c
T
Александр Петров b56f2b4582 libc/kbd: kbd_raw_poll + замер потери нажатий при зажатом Shift (KBD-1)
Симптом: при удерживаемом Shift часть нажатий стрелок не отрабатывает
(~15 % по наблюдению пользователя), без Shift потерь нет.

Переведено в числа: нажимается Home — тоже расширенная клавиша (тот же
E0-префикс и тот же «fake shift»), но игрой игнорируется, поэтому рельеф
комнаты на результат не влияет.  Счётчики — брейкпоинты MAME с действием
{ b@ADDR = b@ADDR+1 ; g } на входе клавиатурной ветки трамплина, на чтении
порта 0x18 и на установке make-бита.

Что измерено (10 нажатий Shift+Home, дошло make):
  игра идёт, опрос ВКЛ   9/10     игра идёт, опрос ВЫКЛ  9/10
  игра ЗАМОРОЖЕНА (блитов нет вообще, длинных DI нет)  8/10

Обе исходные гипотезы отпали:
- длина наших DI-окон ни при чём (в замороженном кадре потерь больше);
- снятие di в accel-ядрах libbgi УРОНИЛО машину — режим «акселератор при
  EI» из docs/new/06-accel.md §6.6 в этой прошивке недоступен.

Байт теряется НИЖЕ нашего кода: на 49 прочитанных байт пришлось только 28
входов в клавиатурную ветку, то есть ~44 % импульсов запроса прерывания не
обслуживается и трёхбайтовый FIFO SIO переполняется.

Потолок приёма измерен отдельной программой tests/kbdpoll (ничего, кроме
kbd_raw_poll в цикле): 25 нажатий -> 25 make, 250 байт из 250, ноль потерь.
Значит опрос лечит полностью, вопрос только в плотности: нужно раз в
~0.5 мс, а шесть вызовов за 60-мс кадр давали раз в 10 мс.

Поэтому вызовы из roomtest.c УБРАНЫ (они стояли там, где прерывания и так
разрешены, и дублировали трамплин — 9/10 с ними и без).  Сама функция
kbd_raw_poll оставлена в libc: она корректна и нужна как основа плотного
опроса.  В заголовке и в libc-reference — честная оговорка, чтобы её не
ставили в игровой цикл «на всякий случай» без замера.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 15:33:16 +03:00

146 lines
6.9 KiB
C
Raw 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.
/*
* 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 <kbd_raw.h>
#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;
}