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>
This commit is contained in:
@@ -57,6 +57,23 @@ const uint8_t n_banks = 4;
|
|||||||
/* Режим читов (pop_cheat.h): на время разработки включаем в main. */
|
/* Режим читов (pop_cheat.h): на время разработки включаем в main. */
|
||||||
uint8_t pop_cheats;
|
uint8_t pop_cheats;
|
||||||
|
|
||||||
|
/* ПОТЕРЯ НАЖАТИЙ ПРИ ЗАЖАТОМ Shift — НЕ ЧИНИТСЯ ИЗ ЭТОГО ЦИКЛА.
|
||||||
|
*
|
||||||
|
* Здесь стояли шесть вызовов kbd_raw_poll() (вычерпать FIFO клавиатуры
|
||||||
|
* после каждой тяжёлой фазы кадра). Убраны: замерено, что они не меняют
|
||||||
|
* НИЧЕГО — 9 дошедших make-байт из 10 нажатий и с ними, и без них. Они
|
||||||
|
* стояли ровно там, где прерывания и так разрешены, то есть добавляли то,
|
||||||
|
* что трамплин делает сам.
|
||||||
|
*
|
||||||
|
* Настоящая причина лежит НИЖЕ нашего кода: при зажатом Shift PS/2 шлёт на
|
||||||
|
* расширенную клавишу вдвое больше байт («fake shift»), а импульс запроса
|
||||||
|
* прерывания пропускается примерно в 44 % случаев — байты копятся в
|
||||||
|
* трёхбайтовом FIFO SIO и один из полусотни теряется ДО чтения порта.
|
||||||
|
* Длина наших DI-окон ни при чём: в замороженном кадре, где блитов нет
|
||||||
|
* вообще, потерь БОЛЬШЕ, а не меньше.
|
||||||
|
*
|
||||||
|
* Полный протокол замеров и куда копать дальше — TASKS.md, KBD-1. */
|
||||||
|
|
||||||
/* seq_2_stand (types.h seqids) — Kid стоит, ждёт ввода. */
|
/* seq_2_stand (types.h seqids) — Kid стоит, ждёт ввода. */
|
||||||
#define SEQ_STAND 2
|
#define SEQ_STAND 2
|
||||||
#define ROOM_1 1
|
#define ROOM_1 1
|
||||||
|
|||||||
+26
-9
@@ -279,15 +279,32 @@ Quick wins:
|
|||||||
- [ ] **kbd_raw: частые Rx-overrun в MAME при тапах** (roomtest,
|
- [ ] **kbd_raw: частые Rx-overrun в MAME при тапах** (roomtest,
|
||||||
2026-07-22) — замеры watchpoint-счётчиками: при стабильном
|
2026-07-22) — замеры watchpoint-счётчиками: при стабильном
|
||||||
удержании клавиши overrun'ов ноль, но каждый быстрый тап (5 байт:
|
удержании клавиши overrun'ов ноль, но каждый быстрый тап (5 байт:
|
||||||
E0 74 + E0 F0 74 при FIFO 3) даёт overrun. Подозрение: dev-MAME
|
E0 74 + E0 F0 74 при FIFO 3) даёт overrun.
|
||||||
не эмулирует прерывание SIO на каждый принятый байт (байты
|
**Уточнение 2026-08-01 (по коду драйвера, прежняя гипотеза
|
||||||
вычерпываются только кадровым IRQ 50 Гц) и/или подаёт пачку без
|
НЕВЕРНА):** dev-MAME per-byte INT ДАЁТ —
|
||||||
реальных ~1 мс/байт — на железе per-byte INT должен делать
|
`mame/sources/MAME/src/mame/sinclair/sprinter.cpp`
|
||||||
overrun'ы редкостью. Проверить mame/sources/MAME/src/mame/
|
`on_kbd_data()` ставит `m_irqs->in_set<1>()` на каждый принятый
|
||||||
sinclair/sprinter.cpp (путь байта клавиатуры → INT) и при
|
байт. Но тут же заводит `m_irq_off_timer` на **32 такта CPU**, и
|
||||||
желании поправить dev-MAME. Recovery-политика уже терпима к
|
`irq_off()` снимает линию — то есть импульс, пришедшийся на наше
|
||||||
частым overrun'ам (селективный wipe, docs/kbd-games.md), но
|
DI-окно, теряется НАСОВСЕМ (байт остаётся в FIFO до следующего
|
||||||
аккорд «держу →, тапнул ↑» остаётся уязвим.
|
IRQ или кадрового 50 Гц). А DI-окна у нас длинные: ядра
|
||||||
|
акселератора держат `di` на весь блит (`libbgi/bgi256/
|
||||||
|
_bgi_blit_cols_raw.c`), это сотни микросекунд против 32 тактов.
|
||||||
|
**Замерено 2026-08-01 (PoP roomtest, счётчики в MAME): обе
|
||||||
|
«наши» гипотезы отпали.** (а) `kbd_raw_poll()` из главного цикла
|
||||||
|
не меняет ничего (9/10 с ним и без); (б) снятие `di` в accel-ядрах
|
||||||
|
УРОНИЛО машину — режим «акселератор при EI» тут недоступен; и сама
|
||||||
|
длина DI ни при чём (в замороженном кадре без блитов потерь
|
||||||
|
БОЛЬШЕ). Байт теряется ДО чтения порта: на 49 прочитанных байт
|
||||||
|
только 28 входов в клавиатурную ветку, т.е. ~44 % импульсов
|
||||||
|
запроса не обслужены и 3-байтовый FIFO переполняется. Главный
|
||||||
|
подозреваемый — `m_irq_off_timer` в sprinter.cpp: он ОДИН на два
|
||||||
|
источника (экран + клавиатура), `irq_off()` гасит обе линии, так
|
||||||
|
что кадровое прерывание способно обрезать клавиатурный импульс.
|
||||||
|
Полный протокол — applications/PoP/roomtest/TASKS.md, KBD-1.
|
||||||
|
Recovery-политика уже терпима к частым overrun'ам (селективный
|
||||||
|
wipe, docs/kbd-games.md), но аккорды «держу →, тапнул ↑» и
|
||||||
|
«держу Shift, тапаю ←» остаются уязвимы.
|
||||||
|
|
||||||
## Known quirks (зафиксированы, обходы в libc)
|
## Known quirks (зафиксированы, обходы в libc)
|
||||||
|
|
||||||
|
|||||||
@@ -313,6 +313,7 @@ Set 2: `0xF0` — префикс отпускания, `0xE0` — префикс
|
|||||||
| `void kbd_raw_close(void)` | выключить, вернуть клавиатуру DSS; идемпотентно; висит на atexit |
|
| `void kbd_raw_close(void)` | выключить, вернуть клавиатуру DSS; идемпотентно; висит на atexit |
|
||||||
| `uint8_t kbd_raw_down(uint16_t code)` | зажата ли code ПРЯМО СЕЙЧАС (0/1); code вне 0..511 — 0 |
|
| `uint8_t kbd_raw_down(uint16_t code)` | зажата ли code ПРЯМО СЕЙЧАС (0/1); code вне 0..511 — 0 |
|
||||||
| `void kbd_raw_sync(void)` | звать РАЗ В КАДР до опроса: recovery после Rx-overrun SIO — сбрасывает held-состояние всех клавиш КРОМЕ модификаторов (те не перечитываются typematic'ом; docs/kbd-games.md) |
|
| `void kbd_raw_sync(void)` | звать РАЗ В КАДР до опроса: recovery после Rx-overrun SIO — сбрасывает held-состояние всех клавиш КРОМЕ модификаторов (те не перечитываются typematic'ом; docs/kbd-games.md) |
|
||||||
|
| `uint8_t kbd_raw_poll(void)` | вычерпать FIFO ОПРОСОМ, не дожидаясь прерывания: 0 — было пусто (~40 тактов), 1 — что-то декодировано. Звать МЕЖДУ фазами кадра, после тяжёлых блитов — не раз в кадр (см. ниже, «почему одного прерывания мало») |
|
||||||
| `KBD_EXT` | ИЛИ-флаг кода: клавиша была расширенной (0xE0-префикс на проводе) |
|
| `KBD_EXT` | ИЛИ-флаг кода: клавиша была расширенной (0xE0-префикс на проводе) |
|
||||||
| `KBD_UP/DOWN/LEFT/RIGHT/SPACE/ENTER/ESC/LSHIFT/RSHIFT` | позиционные коды PS/2 Set 2 — LEFT/ESC/UP подтверждены (см. ниже); остальные — по стандарту, не перепроверены поштучно |
|
| `KBD_UP/DOWN/LEFT/RIGHT/SPACE/ENTER/ESC/LSHIFT/RSHIFT` | позиционные коды PS/2 Set 2 — LEFT/ESC/UP подтверждены (см. ниже); остальные — по стандарту, не перепроверены поштучно |
|
||||||
| `KBD_LCTRL/LALT/RCTRL/RALT` | коды модификаторов (R* — расширенные, с KBD_EXT); добавлены 2026-07-22 |
|
| `KBD_LCTRL/LALT/RCTRL/RALT` | коды модификаторов (R* — расширенные, с KBD_EXT); добавлены 2026-07-22 |
|
||||||
@@ -335,6 +336,32 @@ git как урок: при повторных «нет эффекта» на с
|
|||||||
брейкпоинтом/watchpoint'ом на конкретный адрес кода, не полагаться
|
брейкпоинтом/watchpoint'ом на конкретный адрес кода, не полагаться
|
||||||
только на визуальный снимок с произвольным таймингом.
|
только на визуальный снимок с произвольным таймингом.
|
||||||
|
|
||||||
|
**ПОЧЕМУ ОДНОГО ПРЕРЫВАНИЯ МАЛО (`kbd_raw_poll`, 2026-08-01).** Приёмный
|
||||||
|
FIFO SIO — 3 байта, и рассчитывать на «каждый байт разбудит нас» нельзя:
|
||||||
|
запрос прерывания клавиатуры держится единицы микросекунд (в dev-MAME —
|
||||||
|
32 такта CPU, `sprinter.cpp` `on_kbd_data` → `irq_off_timer`), а ядра
|
||||||
|
акселератора держат `DI` на весь блит — сотни микросекунд. Импульс,
|
||||||
|
попавший в такое окно, теряется насовсем; байт лежит в FIFO до следующего
|
||||||
|
прерывания (следующий байт либо кадровое, 50 Гц). Трафик же идёт пачками:
|
||||||
|
тап стрелки = 5 байт, а при УДЕРЖИВАЕМОМ Shift PS/2 обрамляет расширенный
|
||||||
|
код «фиктивным шифтом» (`E0 F0 12` … `E0 12`) — по 5 байт и на нажатие, и
|
||||||
|
на отпускание. Три ячейки такую пачку не держат: потерянный make = «нажатие
|
||||||
|
не сработало», потерянный break = залипание. Отсюда `kbd_raw_poll()` —
|
||||||
|
вычерпывание тем же декодером, но из главного цикла. Тело идёт под `DI`
|
||||||
|
(чтение порта 0x18 деструктивно, а read-modify-write карты гонится с
|
||||||
|
трамплином) и БЕЗУСЛОВНО делает `EI` на выходе — из ISR звать нельзя.
|
||||||
|
|
||||||
|
**Замер эффективности (2026-08-01, PoP roomtest — читать до применения!).**
|
||||||
|
Расстановка «несколько вызовов за кадр, после тяжёлых фаз» **не даёт
|
||||||
|
ничего**: 9 дошедших make-байт из 10 нажатий и с ней, и без неё. Причина —
|
||||||
|
такие вызовы попадают ровно в участки с разрешёнными прерываниями и лишь
|
||||||
|
дублируют трамплин. Там же измерено, что длина DI-окон графики на потери
|
||||||
|
НЕ влияет (в кадре вообще без блитов потерь больше), а теряется байт ДО
|
||||||
|
чтения порта: примерно 44 % импульсов запроса прерывания не обслуживаются,
|
||||||
|
и трёхбайтовый FIFO переполняется. Полный протокол и куда копать —
|
||||||
|
`applications/PoP/roomtest/TASKS.md`, задача KBD-1. Смысл может быть
|
||||||
|
только у ПЛОТНОГО опроса (в цикле ожидания кадра), это ещё не проверено.
|
||||||
|
|
||||||
**ГЛАВНОЕ СЛЕДСТВИЕ:** пока `kbd_raw_open()` активен, `kbhit/getch/getkey/
|
**ГЛАВНОЕ СЛЕДСТВИЕ:** пока `kbd_raw_open()` активен, `kbhit/getch/getkey/
|
||||||
kbd_mod_state` НЕ получают новых событий (в т.ч. CTRLKEY подряд отдаёт
|
kbd_mod_state` НЕ получают новых событий (в т.ч. CTRLKEY подряд отдаёт
|
||||||
то же самое, что было на момент открытия — резидентный обработчик DSS,
|
то же самое, что было на момент открытия — резидентный обработчик DSS,
|
||||||
|
|||||||
@@ -57,6 +57,30 @@ uint8_t kbd_raw_down(uint16_t code);
|
|||||||
* «отваливался» при каждом overrun (см. kbd_raw_sync.c). */
|
* «отваливался» при каждом overrun (см. kbd_raw_sync.c). */
|
||||||
void kbd_raw_sync(void);
|
void kbd_raw_sync(void);
|
||||||
|
|
||||||
|
/* Вычерпать приёмный FIFO ОПРОСОМ, не дожидаясь прерывания. Возвращает
|
||||||
|
* 0 (FIFO был пуст, ~40 тактов) или 1 (что-то вычерпано и декодировано в
|
||||||
|
* ту же карту, что ведёт трамплин).
|
||||||
|
*
|
||||||
|
* Зачем: на каждый принятый байт запрос прерывания живёт единицы
|
||||||
|
* микросекунд, и пропущенный импульс не «догоняется» — байт лежит в
|
||||||
|
* 3-байтовом FIFO до следующего прерывания. Пачки в 5 байт (тап стрелки;
|
||||||
|
* при зажатом Shift — ещё и «fake shift» вокруг расширенного кода) при
|
||||||
|
* этом переполняют FIFO: теряется make («нажатие не сработало») или break
|
||||||
|
* (залипание).
|
||||||
|
*
|
||||||
|
* ЧЕСТНАЯ ОГОВОРКА ПО ЭФФЕКТИВНОСТИ (замер 2026-08-01, PoP roomtest,
|
||||||
|
* см. applications/PoP/roomtest/TASKS.md, KBD-1): расстановка «несколько
|
||||||
|
* вызовов за кадр, после тяжёлых фаз» НЕ ДАЁТ НИЧЕГО — потери те же, что
|
||||||
|
* без неё. Такие вызовы попадают в участки, где прерывания и так
|
||||||
|
* разрешены, и лишь дублируют трамплин. Смысл появляется только у
|
||||||
|
* ПЛОТНОГО опроса (в цикле ожидания, десятки-сотни раз за кадр), и это
|
||||||
|
* ещё не подтверждено замером. Не ставить эту функцию в игровой цикл
|
||||||
|
* «на всякий случай» — сначала померить.
|
||||||
|
*
|
||||||
|
* Тело идёт под DI и БЕЗУСЛОВНО делает EI на выходе: рассчитано на вызов
|
||||||
|
* из главного цикла, из ISR звать нельзя. */
|
||||||
|
uint8_t kbd_raw_poll(void);
|
||||||
|
|
||||||
#define KBD_EXT 0x0100 /* база кода была расширенной (0xE0-префикс) */
|
#define KBD_EXT 0x0100 /* база кода была расширенной (0xE0-префикс) */
|
||||||
|
|
||||||
/* Позиционные коды PS/2 Set 2 — то, что реально нужно платформеру.
|
/* Позиционные коды PS/2 Set 2 — то, что реально нужно платформеру.
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Build kbdpoll.exe — потолок «плотного опроса» клавиатуры (KBD-1).
|
||||||
|
|
||||||
|
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||||
|
EXAMPLE := kbdpoll
|
||||||
|
include $(PROJ_ROOT)/app.mk
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* kbdpoll — измерение ПОТОЛКА «плотного опроса» клавиатуры.
|
||||||
|
*
|
||||||
|
* Вопрос, на который отвечает тест (applications/PoP/roomtest/TASKS.md,
|
||||||
|
* KBD-1): при зажатом Shift часть нажатий расширенных клавиш теряется —
|
||||||
|
* байт не доезжает до порта 0x18, потому что импульс запроса прерывания
|
||||||
|
* пропускается примерно в 44 % случаев и трёхбайтовый FIFO SIO
|
||||||
|
* переполняется. Редкий опрос (несколько раз за кадр) не помог вовсе.
|
||||||
|
* Здесь опрос идёт МАКСИМАЛЬНО плотно — вся программа только этим и
|
||||||
|
* занята, — то есть измеряется ЛУЧШЕЕ, чего опрос в принципе может
|
||||||
|
* достичь. Если и здесь байты теряются, приём не спасёт и в игре, и
|
||||||
|
* искать надо в эмуляции/железе, а не у нас.
|
||||||
|
*
|
||||||
|
* Как мерить (MAME, счётчики брейкпоинтами, машина не останавливается):
|
||||||
|
* 1. Найти в .map адреса `_kbd_raw_poll` и хвоста карты `__kbdraw_down`.
|
||||||
|
* 2. bpset <адрес чтения порта 0x18 внутри kbd_raw_poll>,1,{ b@СЧ1 = b@СЧ1+1 ; g }
|
||||||
|
* bpset <адрес установки make-бита>,E==0x6C && (b@_kbdraw_pending & 1)==0,
|
||||||
|
* { b@СЧ2 = b@СЧ2+1 ; g }
|
||||||
|
* 3. Держать Left Shift, нажать Home ровно N раз
|
||||||
|
* (`:kbd:ms_naturl:P1.7` 0x0002 — Shift, `:kbd:ms_naturl:P2.5` 0x0001 — Home).
|
||||||
|
* Home выбран потому, что он расширенный (тот же «fake shift», что у
|
||||||
|
* стрелок), но в этой программе ничего не делает.
|
||||||
|
* 4. Ожидание: байт ровно 10*N (нажатие E0 F0 12 + E0 6C, отпускание
|
||||||
|
* E0 F0 6C + E0 12), make'ов ровно N.
|
||||||
|
*
|
||||||
|
* Итог теста на экран не выводится СПЕЦИАЛЬНО: любой вывод — это работа,
|
||||||
|
* которая разрежает опрос и портит замер. Числа снимаются из памяти.
|
||||||
|
*
|
||||||
|
* Выход — ESC. Чтобы отличить «программа жива» от «повисла», крутится
|
||||||
|
* бордюр: цвет меняется раз в 4096 опросов (медленное мерцание = работаем).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <kbd_raw.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
__sfr __at (0xFE) io_border;
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
uint16_t spin = 0;
|
||||||
|
|
||||||
|
if (kbd_raw_open() != 0)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
/* Ничего, кроме опроса. Ни vsync, ни графики, ни ввода-вывода —
|
||||||
|
* иначе замеряли бы не потолок приёма, а свою же нагрузку. */
|
||||||
|
while (!kbd_raw_down(KBD_ESC)) {
|
||||||
|
kbd_raw_poll();
|
||||||
|
if (++spin == 0)
|
||||||
|
io_border ^= 4; /* признак жизни: бит 2 (зелёный) */
|
||||||
|
}
|
||||||
|
|
||||||
|
kbd_raw_close();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user