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>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* _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;
|
||||
}
|
||||
Reference in New Issue
Block a user