Files
snark13 46bd9ad0f1 gfx/time: vsync через polling бита кадра, sleep/delayms без halt-подсчёта
gfx_wait_vsync() и sleep() раньше предполагали, что КАЖДОЕ прерывание
на векторе 0xFF — кадровый тик; с CBL/клавиатурой на том же векторе
это уже не так.

gfx_wait_vsync(): вместо halt — polling бита 5 порта 0xFE (реальная
позиция луча, см. MAME kbd_fe_r), доступного пока включён CBL bit7
порта 0x004E. Разделяемое владение портом с CBL через
_cbl_port_ref/unref (тот же ref-counting паттерн, что у IM2-таблицы) —
cbl_close() возвращает "немой" режим вместо полного выключения, если
gfx ещё держит ссылку. Fallback на halt при таймауте.

sleep()/delayms(): калиброванный busy-wait по духу delayms.asm вместо
подсчёта halt-пробуждений. Калибровка одна на кадр (не на секунду —
не переполняет uint16_t и не требует умножения/32-бит арифметики),
общий движок libc/time/_sleep_calib.c для обеих функций. Fallback на
старое поведение при EBUSY (фрейм-хук занят другим irq_install()).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-08 10:47:57 +03:00

97 lines
4.5 KiB
C
Raw Permalink 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.
/*
* _sleep_calib — калиброванный busy-wait, общий движок для sleep() и
* delayms(). Один раз, лениво при первом вызове ЛЮБОЙ из них, ставим
* временный irq_install()-хендлер и считаем, сколько итераций тесного
* asm-цикла умещается между двумя кадровыми тиками (трамплин зовёт
* user-handler ТОЛЬКО на настоящих кадровых прерываниях — клавиатура/
* CBL уходят в свои ветки раньше, см. docs/im2_isr_design.md). Дальше
* — чистый busy-wait (_sleep_spin) БЕЗ единого обращения к прерываниям.
*
* Единица калибровки — "итераций НА ОДИН КАДР" (не на секунду и не
* заранее домноженная) — по духу docs/samples/delayms.asm (там тоже
* калибруют на 1 мс, а не на 1 с): масштабирование делает вызывающий
* код через вложенный цикл повторных вызовов _sleep_spin(), а не через
* предварительное умножение константы. _sleep_units_per_ms — простое
* деление на константу 20 (один раз при калибровке), НЕ умножение и НЕ
* 32-битная арифметика.
*
* Точность: калибровочный (_sleep_count_frame) и рабочий (_sleep_spin)
* циклы не тактово-идентичны — ожидаемая систематическая погрешность
* в пределах нескольких процентов, того же порядка, что и у
* delayms.asm. Достаточно для семантики "примерно N".
*
* Квирк: калибровка сама стоит ~20-40 мс (синхронизация на границу
* кадра + сам замер до следующего тика) — ПЕРВЫЙ вызов sleep()/
* delayms() в программе превысит запрошенное время на эту величину;
* все последующие вызовы точны (калибровка кэшируется). Для delayms()
* с маленьким ms это заметно (напр. delayms(5) на первом вызове может
* растянуться на ~25-45 мс) — если это критично, program может явно
* "прогреть" калибровку заранее (delayms(1) в начале main(), подальше
* от time-критичного кода) — отдельного calibrate()-примитива нет,
* решили не усложнять API.
*/
#include "_sleep_calib.h"
#include <irq.h>
uint16_t _sleep_units_per_frame;
uint16_t _sleep_units_per_ms;
static volatile uint8_t _sleep_tick_flag;
static void _sleep_tick_handler(void)
{
_sleep_tick_flag = 1;
}
/* Считать итерации тесного цикла, пока *flag (HL — arg1) не станет
* ненулевым; вернуть счётчик (DE — возврат uint16_t по ABI). */
static uint16_t _sleep_count_frame(volatile uint8_t *flag) __naked
{
(void)flag;
__asm
ld de, #0
_scf_loop:
inc de
ld a, (hl)
or a, a
jr z, _scf_loop
ret
__endasm;
}
/* Прогнать units (HL — arg1) итераций тесного цикла — чистый busy-wait,
* ни одного обращения к чему-либо, связанному с прерываниями. */
void _sleep_spin(uint16_t units) __naked
{
(void)units;
__asm
_ssp_loop:
ld a, h
or l
ret Z
dec hl
jr _ssp_loop
__endasm;
}
void _sleep_calibrate(void)
{
if (irq_install(_sleep_tick_handler) != 0) {
_sleep_units_per_frame = 0; /* останется 0 — вызывающий возьмёт fallback */
_sleep_units_per_ms = 0;
return;
}
_sleep_tick_flag = 0;
while (!_sleep_tick_flag) ; /* синхронизация на границу кадра */
_sleep_tick_flag = 0;
uint16_t count = _sleep_count_frame(&_sleep_tick_flag);
irq_remove();
_sleep_units_per_frame = count ? count : 1;
_sleep_units_per_ms = (uint16_t)(_sleep_units_per_frame / 20u);
if (!_sleep_units_per_ms) _sleep_units_per_ms = 1;
}