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>
This commit is contained in:
2026-07-29 18:26:31 +03:00
parent 1214785a56
commit 64ce6339eb
32 changed files with 23762 additions and 3 deletions
+595
View File
@@ -0,0 +1,595 @@
# 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`).
Простая реализация:
```asm
; 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-обработчик
```asm
; Флаги: 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:**
```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 по обеим осям.
```asm
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), инициализация обязательна:
```asm
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`:
```asm
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):**
```asm
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:
```
**Чтение байта с мыши (опрос, без прерываний):**
```asm
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` — правая.
**Декодирование:**
```asm
; 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`