Files
Sprinter-SDCC/docs/new/09-input.md
T
snark13 64ce6339eb docs: справочники по железу Sprinter + правка gfx_scroll_h
- docs/new/ — сводные справочники (архитектура, BIOS, DSS, память,
  графика, акселератор, IRQ, порты, ввод, звук, известные баги);
- docs/Original/ — первоисточники, из которых они собраны (BIOS, Estex
  DSS, мануалы, описание акселератора), + Форум.doc/.docx в reference;
- libbgi/common/gfx_scroll_h.c — обход бага скролла при ширине >256
  (правка автора: шаг банды 255 и продвижение указателей на cw; старый
  вариант с 256 оставлен закомментированным с TODO);
- удалён applications/PoP/roomtest/hang_variants.png — рабочая раскладка
  из разбора позы виса, в репозитории ей не место.

Большие архивы (docs/extra ~568 МБ, docs/sources с вложенными git-репо
~68 МБ) в коммит НЕ включены — см. обсуждение.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 18:26:31 +03:00

21 KiB
Raw Blame History

9. Клавиатура и мышь

9.1 Общие сведения

Клавиатура Sprinter — AT-совместимая (101 key, PS/2). В Sprinter-1 дополнительно присутствует ZX-клавиатурная матрица через порт #FE (скан-код через AT-контроллер преобразуется в ZX-матрицу). Sprinter-2+ использует AT-клавиатуру через внутренний SIO Z84C15 (канал A).

Мышь — Microsoft Serial Mouse (2 кнопки) через RS-232 на SIO-B (канал B Z84C15).

Порты SIO

Порт Назначение
#18 SIO-A data — клавиатура
#19 SIO-A control — статус/управление
#1A SIO-B data — мышь
#1B SIO-B control — мышь

Бит 0 порта #19 — признак прерывания от клавиатуры (используется в IM2 для различения источников с общим вектором #FF).

Прерывания

Клавиатура и мышь работают в IM2, общий вектор #FF (совместно с кадровым и CBL). Различение источника — по портам SIO и флаговым битам (07-irq.md §7.2).


9.2 Клавиатура: BIOS и ESTEX

FN_KBD_OUT (BIOS $EA)

Отправляет байт непосредственно на AT-клавиатуру [BIOS_v3.txt:0EAh].

Вход:  C = $EA
       A = байт команды/данных для AT-клавиатуры
Выход: нет

Используется для низкоуровневой настройки клавиатуры (LED-индикаторы, повтор, задержка).

ESTEX: функции клавиатуры (30h–37h)

Через RST 10h с C=30h37h [DiskSyscalls.txt:30h37h].

WAITKEY (30h): ждёт нажатия, возвращает код.

Вход:  C = 30h
Выход: A = код символа
       D = скан-код
       E = ASCII-код (0 для функциональных клавиш)

SCANKEY (31h): опрос без ожидания.

Вход:  C = 31h
Выход: A = код символа (0 если нажатий нет)
       D = скан-код
       E = ASCII-код

ECHOKEY (32h): ждёт + эхо-вывод.

Вход:  C = 32h
Выход: A = код символа
       D = скан-код
       E = ASCII-код

CTRLKEY (33h): состояние модификаторов и режимов. libc: kbd_mod_state().

Вход:   C = 33h
Выход:  C = mode (биты режимов — см. ниже)
        B = shift (биты зажатых модификаторов)
Бит C Режим
0 РУС/ЛАТ (0=ЛАТ, 1=РУС)
1 NumLock
2 ScrollLock
3 CapsLock
4 Insert
Бит B Модификатор
0 Left Shift
1 Right Shift
2 Ctrl (любой)
3 Alt (любой)
4 Left Ctrl
5 Left Alt
6 Right Ctrl
7 Right Alt

K_CLEAR (35h): сбросить буфер клавиатуры.

Вход: C = 35h

K_SETUP (36h): настройка раскладки. [DiskSyscalls.txt:36h]

Вход: C = 36h
       A = 0 — получить текущий номер раскладки
       A = 1 — установить раскладку по номеру из B
       A = 2 — получить имя текущей раскладки (адрес HL)
Выход: при A=0: A = номер раскладки
       при A=2: HL = адрес строки с именем
Номер Раскладка
0 Английская (US)
1 Русская
2 Английская (UK)
3 Украинская

Переключение раскладки по Ctrl+Space (если не переназначено).

TESTKEY (37h): тест на нажатие (без извлечения из буфера).

Вход:  C = 37h
Выход: A = код символа (0 если нет)
       D = скан-код
       E = ASCII-код

9.3 Клавиатура: необработанный (raw) доступ

Штатный DSS (WAITKEY/SCANKEY/CTRLKEY) даёт событийный интерфейс — нажатия, а не состояния клавиш. Для игр и real-time приложений требуется чтение зажатых клавиш в каждый момент. Это реализуется через перехват SIO-A напрямую в обработчике IM2, минуя DSS.

9.3.1 Принцип

  1. Установить свой обработчик IM2 (общий вектор #FF).
  2. В обработчике читать байты с SIO-A (порт #18), декодировать PS/2 Scan Code Set 2, обновлять битовую карту held-состояний.
  3. Основной код по кадровому прерыванию (или раз в кадр) читает карту опросом — без ожидания.
  4. DSS не получает байт с клавиатуры, пока обработчик их забирает. Для восстановления нормального ввода — временно отключить перехват.

9.3.2 SIO-A и прерывания

SIO-A (порт данных #18, управление #19) работает в IM2.

Инициализация SIO-A (выполняется BIOS при загрузке, повторно не требуется):

WR Значение Эффект
WR0 0x10 Сброс RR1 (ошибка)
WR1 0x07 Прерывания по Rx готовности
WR3 0xC1 Rx 8 бит, без CRC
WR4 0x44 ×1 клок, 1 стоп-бит, без паритета
WR5 0xEA Tx 8 бит, DTR+RTS=1

Различение в IM2: при входе в обработчик проверить бит 0 порта #19. Если 0 — прерывание не от SIO-A (кадровое, CBL — пропустить).

9.3.3 PS/2 Scan Code Set 2 — протокол

SIO-A передаёт скан-коды PS/2 Set 2. Каждый байт с порта #18 — один байт протокола. Размер сообщения:

Последовательность Значение
<code> Make — клавиша нажата (код < 0xF0)
0xF0 <code> Break — клавиша отпущена
0xE0 <code> Extended — расширенная клавиша (стрелки, RAlt, RCtrl)
0xE0 0xF0 <code> Extended break

Make-байт устанавливает бит клавиши в held-карте. Break-байт сбрасывает.

FSM декодирования (2 состояния):

PEND_MAKE:
  получить байт B
  if B == 0xF0 → переход PEND_BREAK
  if B == 0xE0 → переход PEND_EXT, Bзап = B
  иначе → make(B)

PEND_BREAK:
  получить байт B
  переход PEND_MAKE
  if (ext_flag) → break_ext(B); ext_flag = 0
  иначе → break(B)

PEND_EXT:
  получить байт B
  if B == 0xF0 → ext_flag = 1, переход PEND_BREAK_EXT
  иначе → make_ext(B); переход PEND_MAKE

PEND_BREAK_EXT:
  получить байт B
  ext_flag = 0
  break_ext(B)
  переход PEND_MAKE

9.3.4 Битовая карта held-состояний

Массив 128 байт (1024 бита для кодов 0..1023, расширенные — с битом 0x0100).

Простая реализация:

; HL = code (0..0x1FF)
; IX = base адреса карты
LD   A, H
AND  A, 0x07       ; номер бита 8..10
LD   D, A
LD   A, L
RRCA               ; HL/8 (3 сдвига)
RRCA
RRCA
AND  A, 0x1F
LD   L, A
LD   A, H
AND  A, 0x07
ADD  A, A          ; +8*...
ADD  A, A
ADD  A, A
ADD  A, L
LD   L, A
LD   H, 0x00
ADD  IX, HL        ; IX = &bitmap[code / 8]
LD   A, D
INC  A
LD   B, A          ; B = номер бита в байте + 1
XOR  A
SCF                ; carry=1 для make, 0 для break
RL   A
DJNZ $-2           ; A = 1 << (code & 7)
; make: OR (IX), A   ; break: CPL + AND (IX), A

9.3.5 Overrun recovery

SIO FIFO глубиной 3 байта. Если обработчик прерывания не успевает забрать байты (длинная DI-секция, акселератор), FIFO переполняется — RR1 bit5 = 1. С этого момента цепочка make+break нарушена, возможны залипшие клавиши.

Алгоритм восстановления:

  1. Обнаружить overrun (читать SIO RR1, бит 5).

  2. Сбросить held-карту всех клавиш, кроме:

    Код PS/2 Клавиша Почему
    0x12 Left Shift PS/2 автоповтор — только последняя
    0x59 Right Shift нажатая клавиша. Если сбросить
    0x14 Left Ctrl модификатор, он не восстановится до
    0x11 Left Alt физического отпускания. Игра «теряет»
    0xE014 Right Ctrl шифт/контрол/альт при каждом overrun.
    0xE011 Right Alt
  3. Взвести флаг «был overrun» — основной код может проигнорировать held-состояние на этот кадр.

9.3.6 Координация с DSS

Пока обработчик забирает байты с SIO-A, FIFO пуст — DSS не видит клавиатуру. Для вызова DSS-функций (консоль, диалоговые окна):

  1. Переключить флаг «raw active = 0» в IM2-обработчике.
  2. Обработчик начинает пропускать байты в DSS (JP 0038h или через штатный трамплин).
  3. После завершения ввода — переключить флаг обратно.

Внимание: если обработчик просто перестаёт читать SIO-A, но не возвращает управление DSS, порт SIO-A перестанет генерировать прерывания (FIFO полон, Rx готов = 0). Для корректной передачи управления нужно разрешить DSS-обработчику читать SIO-A.

9.3.7 Пример: минимальный raw-обработчик

; Флаги: raw_active = 1 — перехватывать, 0 — пропускать в DSS
; Карта: kbd_held — 128 байт, занулена при старте
; FSM:   kbd_state — 0=PEND_MAKE, 1=PEND_BREAK, 2=PEND_EXT, 3=PEND_BREAK_EXT
;        kbd_ext — флаг extended (0/1)

KBD_ISR:
  ; Проверить, что прерывание от SIO-A
  IN   A, (#19)     ; SIO-A RR0
  BIT  0, A         ; бит 0 = прерывание от SIO-A?
  RET  Z            ; нет — пропустить

  IN   A, (#18)     ; прочитать байт скан-кода
  BIT  raw_active   ; перехват активен?
  JP   Z, DSS_IRQ   ; нет — пусть DSS обработает

  ; FSM: текущее состояние в kbd_state
  LD   HL, kbd_state
  LD   A, (HL)
  AND  A, 3
  JP   Z, .pend_make
  DEC  A
  JP   Z, .pend_break
  DEC  A
  JP   Z, .pend_ext
  ; .pend_break_ext
  LD   (HL), 0      ; → PEND_MAKE
  LD   A, B         ; код клавиши
  CALL break_ext
  EI
  RETI

.pend_make:
  LD   A, B         ; байт с SIO-A (уже в B из IN)
  CP   0xF0
  JR   Z, .set_break
  CP   0xE0
  JR   Z, .set_ext
  CALL make          ; обычный make
  LD   (HL), 0       ; остаёмся PEND_MAKE
  EI
  RETI

.set_break:
  LD   (HL), 1       ; → PEND_BREAK
  EI
  RETI

.set_ext:
  LD   (HL), 2       ; → PEND_EXT
  EI
  RETI

.pend_break:
  LD   (HL), 0       ; → PEND_MAKE
  CALL break
  EI
  RETI

.pend_ext:
  LD   A, B
  CP   0xF0
  JR   Z, .set_ext_break
  CALL make_ext
  LD   (HL), 0
  EI
  RETI

.set_ext_break:
  LD   (HL), 3       ; → PEND_BREAK_EXT
  EI
  RETI

Размер кода — ~60 байт.


9.4 Мышь

Драйвер мыши установлен в системном shell Sprinter и доступен через SST-вызов RST 30h.

9.4.1 SST-функции мыши

Вызов: RST 30h с A = номер функции. Выход в зависимости от функции (см. таблицу).

Назначение Вход Выход
$00 Инициализация A=$00
$01 Показать курсор A=$01
$02 Спрятать курсор A=$02
$03 Читать состояние A=$03 DE=X, HL=Y, A=buttons (b0=left, b1=right)
$04 Переместить курсор A=$04, DE=X, HL=Y
$05 Установить границы X A=$05, DE=min, HL=max
$06 Установить границы Y A=$06, DE=min, HL=max
$07 Вид курсора (текст) A=$07, DE=sym_and:sym_xor, HL=attr_and:attr_xor
$09 Загрузить курсор (граф.) A=$09, IX=pointer
$0B Прочитать курсор A=$0B IX=pointer (копия)
$0C Чувствительность X A=$0C, D=value (1..255)
$0D Чувствительность Y A=$0D, D=value (1..255)
$0E Прочитать чувств. X A=$0E D=value
$0F Прочитать чувств. Y A=$0F D=value
$11 Уведомить о смене режима A=$11, D=режим (0=текст, 1=граф-256, 2=граф-16)

Пример: прочитать состояние мыши в asm:

  LD   A, $03        ; READ
  RST  30h
  ; DE = X, HL = Y, A = buttons
  LD   (mouse_x), DE
  LD   (mouse_y), HL
  AND  A, 3
  LD   (mouse_btn), A

9.4.2 Чувствительность

Значение — делитель: драйвер считает N raw-шагов мыши, прежде чем сдвинуть курсор на 1 пиксель. Меньше = быстрее (чувствительнее). Рекомендуемое начальное значение: 2 по обеим осям.

  LD   A, $0C        ; SET_SENS_H
  LD   D, 2
  RST  30h
  LD   A, $0D        ; SET_SENS_V
  LD   D, 2
  RST  30h

9.4.3 Порядок инициализации

После загрузки драйвер мыши уже инициализирован shell. Если программа начинает «с чистого листа» (без shell), инициализация обязательна:

  LD   A, $00        ; INIT — инициализировать драйвер
  RST  30h
  LD   A, $05        ; BOUNDS_X: границы экрана
  LD   DE, 0
  LD   HL, 319       ; для 320×256
  RST  30h
  LD   A, $06        ; BOUNDS_Y
  LD   DE, 0
  LD   HL, 255
  RST  30h
  LD   A, $01        ; SHOW — показать курсор
  RST  30h

9.4.4 Смена видеорежима

После переключения видеорежима (графический → текстовый и обратно) обязательно вызвать $11:

  LD   A, $11
  LD   D, 1          ; 0=текст, 1=граф-256, 2=граф-16
  RST  30h

Иначе драйвер применяет старые координаты, и курсор рисуется неправильно.

9.4.5 Прямой доступ к SIO-B (raw mouse)

Мышь подключена через SIO-B (порты #1A/#1B), Microsoft Serial Mouse, 1200 бод 7E1. Если программа хочет читать мышь напрямую, минуя SST-драйвер (например для нестандартного протокола или экономии на SST), SIO-B нужно инициализировать самостоятельно:

Инициализация SIO-B (1200 7E1):

  LD   HL, .sio_init
  LD   B, 6
  LD   C, #1B        ; SIO-B control
  OTIR
  JR   .done
.sio_init:
  DB   #10           ; WR0: сброс RR1
  DB   #04           ; WR1: прерывания выкл
  DB   #44           ; WR4: ×1 клок, 1 стоп, без паритета
  DB   #C1           ; WR3: Rx 8 бит
  DB   #EA           ; WR5: Tx 8 бит, DTR+RTS=1
  DB   #15           ; WR0: сброс ошибки + выбран RR1
.done:

Чтение байта с мыши (опрос, без прерываний):

  IN   A, (#1B)      ; SIO-B RR0
  BIT  0, A          ; бит 0 = Rx готов?
  JR   Z, .no_data
  IN   A, (#1A)      ; прочитать байт
  ; обработать ...

Формат пакета Microsoft Serial Mouse:

Длина: 3 байта.

Байт 7 6 5 4 3 2 1 0
0 1 0 0 0 L R Y7 Y6
1 0 X6 X5 X4 X3 X2 X1 X0
2 0 Y5 Y4 Y3 Y2 Y1 Y0 X7
  • Координаты X/Y — относительные (приращения с последнего пакета), знаковые.
  • X7 — старший бит X (знак), X6..X0 — младшие.
  • Y7..Y6 — старшие биты Y, Y5..Y0 — младшие.
  • L — левая кнопка (1 = нажата), R — правая.

Декодирование:

; B = байт 0, C = байт 1, D = байт 2
; X:
  LD   A, C          ; байт 1: X6..X0
  LD   E, A
  LD   A, D          ; байт 2: X7
  RLCA
  RLCA
  AND  A, 0x80
  OR   A, E          ; A = X (7 бит + знак)
  ; знаковое расширение в HL
  LD   L, A
  RLCA
  SBC  A, A
  LD   H, A          ; HL = X (знаковое 16-бит)

; Y:
  LD   A, B          ; байт 0: Y7..Y6
  RLCA
  RLCA               ; A[1:0] = Y7..Y6
  AND  A, 0xC0
  LD   E, A
  LD   A, D          ; байт 2: Y5..Y0
  RLCA
  RLCA               ; A[7:2] = Y5..Y0
  AND  A, 0xFC
  OR   A, E          ; A = Y (7 бит + знак)
  ; знаковое расширение в DE
  LD   E, A
  RLCA
  SBC  A, A
  LD   D, A          ; DE = Y (знаковое 16-бит)

; Кнопки:
  LD   A, B
  AND  A, 0x30       ; биты L (4) и R (5)
  RRCA               ; A = 0..3
  RRCA
  RRCA
  RRCA
  LD   (buttons), A

9.5 Особенности игровой клавиатуры

Документ: kbd-games.md. Ниже — ключевые паттерны для реализации.

  • Held-state vs edge-detect: DSS — событийный (сообщает только момент нажатия). Raw-канал (§9.3) — state-based (зажата/не зажата прямо сейчас). Играм нужен второй.
  • Sync once per frame: раз в кадр (по кадровому прерыванию) проверять held-карту. Между кадрами карта обновляется только в IM2-обработчике.
  • Edge-detect: для одноразовых действий (прыжок, выстрел) — запоминать предыдущее held-состояние и сравнивать:
    pressed = held & ~prev_held
    prev_held = held
    
  • Типоматик: игнорировать повторные make, пока не придёт break. Реализация: при make — установить бит только если он ещё не был установлен; при break — сбросить.
  • Автоотмена (reject): если за кадр нажато больше N клавиш (например 4) — игнорировать все (защита от баунса и мусора).
  • Recovery: при overrun — очистить held-карту всех клавиш, кроме модификаторов (LShift 0x12, RShift 0x59, LCtrl 0x14, LAlt 0x11, RCtrl 0xE014, RAlt 0xE011).

9.6 Кросс-ссылки

  • Прерывания (IM2, вектор #FF): 07-irq.md §7.2
  • DSS-функции клавиатуры: 03-dss.md §3.6
  • BIOS FN_KBD_OUT: 02-bios.md §2.7
  • Порт клавиатуры #FE: 11-ports.md §11.3
  • Игровая клавиатура: kbd-games.md
  • Баги: 12-bugs.md
  • SIO и порты прерываний: 11-ports.md §11.2