USB HID 键盘只在状态变化时上报;a3c1d22 为避免 Enter 洪流加入整份报告去重后, 按住不放不再产生任何字符。 - 记录当前按住的非 CapsLock 键与下一次补发时刻 - 同一份报告重复到达(按键仍按住)时,按 500ms 首次延迟、33ms 间隔 重新走 _decode_press,注入与按下沿相同的 ASCII/ANSI 字节 - 松开或换键时更新/清除重复目标;CapsLock 本身不重复
241 lines
9.9 KiB
Plaintext
241 lines
9.9 KiB
Plaintext
import drivers.usb.usb as usb
|
||
import drivers.usb.hid.hid as hid
|
||
import drivers.usb.uhci as uhci
|
||
import drivers.input.keyboard.keyboard as keyboard
|
||
import drivers.serial.uart.serial as serial
|
||
import platform.pch.timer as timer
|
||
|
||
define KBD_BUF_SIZE = 1024
|
||
define KBD_EVT_PRESS = 0
|
||
define KBD_EVT_RELEASE = 1
|
||
# 按住自动重复:首次延迟 500ms,之后每 33ms 一次(约 30 字符/秒)。
|
||
# USB 键盘只在状态变化时上报,同一份"按键仍按住"报告重复到达时由 IRQ 回调补发。
|
||
define KBD_REPEAT_DELAY_MS = 500
|
||
define KBD_REPEAT_INTERVAL_MS = 33
|
||
|
||
struct usb_kbd_event:
|
||
modifier: uint8
|
||
keycode: uint8
|
||
event_type: uint8
|
||
|
||
kbd_buf: [KBD_BUF_SIZE]usb_kbd_event
|
||
kbd_buf_head: int = 0
|
||
kbd_buf_tail: int = 0
|
||
kbd_buf_count: int = 0
|
||
kbd_dev: usb.usb_device* = None
|
||
kbd_report: hid.hid_kbd_report
|
||
kbd_prev_keycodes: [6]uint8
|
||
# 上一份报告的逻辑状态签名(modifier + 6 个 keycode,各 8 bit):设备对每次中断 IN
|
||
# 都会回同一份"当前按键"报告,同一份报告重复到达绝不能当成新的按下沿。
|
||
kbd_prev_sig: uint64 = 0
|
||
kbd_have_sig: int = 0
|
||
# 自动重复状态:当前按住的可重复键 + 下一次补发时刻(timer ticks,1ms/拍)
|
||
kbd_rep_code: uint8 = 0
|
||
kbd_rep_mod: uint8 = 0
|
||
kbd_rep_valid: int = 0
|
||
kbd_rep_next: uint64 = 0
|
||
kbd_initialized: int = 0
|
||
kbd_slot_id: int = -1
|
||
kbd_caps: int = 0
|
||
kbd_first_key: int = 0
|
||
|
||
# HID boot 报告 usage ID (0x04..0x38) -> ASCII;CapsLock / 方向键等在 _decode_press 单独处理。
|
||
hid_ascii: [64]char = [
|
||
'\0', '\0', '\0', '\0',
|
||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
||
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
|
||
'\n', '\x1b', '\b', '\t', ' ',
|
||
'-', '=', '[', ']', '\\', '\0', ';', '\'', '`', ',', '.', '/',
|
||
'\0', '\0', '\0', '\0', '\0', '\0', '\0'
|
||
]
|
||
|
||
hid_ascii_shift: [64]char = [
|
||
'\0', '\0', '\0', '\0',
|
||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
||
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||
'!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
|
||
'\n', '\x1b', '\b', '\t', ' ',
|
||
'_', '+', '{', '}', '|', '\0', ':', '"', '~', '<', '>', '?',
|
||
'\0', '\0', '\0', '\0', '\0', '\0', '\0'
|
||
]
|
||
|
||
# 按下沿调用一次:boot 报告 keycode -> ASCII / ANSI,直接进 PS/2 同一输出队列。
|
||
def _decode_press(code: uint8, modifier: uint8):
|
||
global kbd_caps
|
||
if code == uint8(0x39):
|
||
kbd_caps = kbd_caps ^ 1
|
||
return
|
||
if code == uint8(0x52): keyboard.inject_csi(uint8(0x41)) # Up
|
||
elif code == uint8(0x51): keyboard.inject_csi(uint8(0x42)) # Down
|
||
elif code == uint8(0x4F): keyboard.inject_csi(uint8(0x43)) # Right
|
||
elif code == uint8(0x50): keyboard.inject_csi(uint8(0x44)) # Left
|
||
elif code == uint8(0x4A): keyboard.inject_csi(uint8(0x48)) # Home
|
||
elif code == uint8(0x4D): keyboard.inject_csi(uint8(0x46)) # End
|
||
elif code == uint8(0x49): keyboard.inject_csi_tilde(uint8(0x32)) # Insert
|
||
elif code == uint8(0x4C): keyboard.inject_csi_tilde(uint8(0x33)) # Delete
|
||
elif code == uint8(0x4B): keyboard.inject_csi_tilde(uint8(0x35)) # PageUp
|
||
elif code == uint8(0x4E): keyboard.inject_csi_tilde(uint8(0x36)) # PageDown
|
||
# usage ID 最大 0xE7;ASCII 表只有 64 项,越界前必须先拒绝(表是只读的,但读越界同样非法)。
|
||
if int(code) >= 64: return
|
||
shifted: bool = (modifier & uint8(0x22)) != 0
|
||
if kbd_caps != 0 and code >= uint8(0x04) and code <= uint8(0x1D):
|
||
shifted = not shifted
|
||
ch: char = hid_ascii[int(code)]
|
||
if shifted:
|
||
ch = hid_ascii_shift[int(code)]
|
||
if ch != '\0':
|
||
keyboard.inject_byte(uint8(ch))
|
||
|
||
def init() -> int:
|
||
global kbd_dev, kbd_initialized, kbd_slot_id
|
||
dev: usb.usb_device* = usb.find_hid_device(0x01)
|
||
if dev is None:
|
||
serial.puts("[usb_kbd] no USB keyboard found\n")
|
||
return -1
|
||
kbd_dev = dev
|
||
if hid.init_keyboard(dev) != 0:
|
||
serial.puts("[usb_kbd] init failed\n")
|
||
return -2
|
||
kbd_initialized = 1
|
||
serial.puts("[usb_kbd] initialized\n")
|
||
kbd_slot_id = uhci.register_int_callback(_on_int_complete)
|
||
if kbd_slot_id < 0:
|
||
serial.puts("[usb_kbd] register callback failed\n")
|
||
kbd_initialized = 0
|
||
return -3
|
||
# 中断传输长度不得超过 kbd_report(8 字节),否则 DMA 越界写全局
|
||
kbd_pkt_len: uint8 = 8
|
||
if dev.ep_in_max < 8: kbd_pkt_len = uint8(dev.ep_in_max)
|
||
uhci.schedule_int_transfer(kbd_slot_id, dev.addr, dev.ep_in, dev.ls, kbd_pkt_len, dev.ep_in_toggle, &kbd_report)
|
||
return 0
|
||
|
||
# 只取语义字段(modifier + 6 个 keycode);reserved 变化不代表按键状态变化。
|
||
def _report_sig() -> uint64:
|
||
sig: uint64 = uint64(kbd_report.modifier)
|
||
mul: uint64 = uint64(256)
|
||
for i in range(6):
|
||
sig = sig | (uint64(kbd_report.keycode[i]) * mul)
|
||
mul = mul * uint64(256)
|
||
return sig
|
||
|
||
# 重排下一次中断 IN。**必须**在此之前完成对 kbd_report 的读取:重排后控制器随时会覆盖它。
|
||
def _rearm():
|
||
dev: usb.usb_device* = kbd_dev
|
||
if dev is None or kbd_slot_id < 0: return
|
||
kbd_pkt_len2: uint8 = 8
|
||
if dev.ep_in_max < 8: kbd_pkt_len2 = uint8(dev.ep_in_max)
|
||
# 推进 DATA0/DATA1 翻转位:本回调表示上一次中断 IN 事务已结束(IRQ 中 TD 不再
|
||
# ACTIVE),翻转后本次重排使用另一 PID。全仓仅此处推进 ep_in_toggle。
|
||
dev.ep_in_toggle = uint8(dev.ep_in_toggle ^ 1)
|
||
uhci.schedule_int_transfer(kbd_slot_id, dev.addr, dev.ep_in, dev.ls, kbd_pkt_len2, dev.ep_in_toggle, &kbd_report)
|
||
|
||
def _on_int_complete() -> int:
|
||
global kbd_buf_head, kbd_buf_tail, kbd_buf_count, kbd_prev_keycodes, kbd_prev_sig, kbd_have_sig, kbd_first_key
|
||
global kbd_rep_code, kbd_rep_mod, kbd_rep_valid, kbd_rep_next
|
||
if kbd_initialized == 0: return 0
|
||
sig: uint64 = _report_sig()
|
||
# 同一份报告再次到达(按键仍按住时设备每帧回同一份 / TD 重完成):没有新沿,
|
||
# 但正是自动重复的触发点 —— 按 delay/interval 补发当前按住键的字节。
|
||
if kbd_have_sig != 0 and sig == kbd_prev_sig:
|
||
if kbd_rep_valid != 0:
|
||
now: uint64 = timer.timer_get_ticks()
|
||
if now >= kbd_rep_next:
|
||
kbd_rep_next = now + uint64(KBD_REPEAT_INTERVAL_MS)
|
||
_decode_press(kbd_rep_code, kbd_rep_mod)
|
||
_rearm()
|
||
return 0
|
||
# 先把本份报告提交为"上一次状态",再做判定:无论后面发生什么,下一帧都不会重复按下沿。
|
||
old: [6]uint8
|
||
for i in range(6):
|
||
old[i] = kbd_prev_keycodes[i]
|
||
kbd_prev_keycodes[i] = kbd_report.keycode[i]
|
||
kbd_prev_sig = sig
|
||
kbd_have_sig = 1
|
||
# 全 0 报告(无按键)只更新状态,绝不能注入任何字节。
|
||
if sig == 0:
|
||
kbd_rep_valid = 0
|
||
_rearm()
|
||
return 0
|
||
j: int
|
||
new_press: uint8 = uint8(0)
|
||
for i in range(6):
|
||
code: uint8 = kbd_report.keycode[i]
|
||
if code == 0: continue
|
||
found: int = 0
|
||
for j in range(6):
|
||
if code == old[j]:
|
||
found = 1
|
||
break
|
||
if found == 0:
|
||
if kbd_first_key == 0:
|
||
kbd_first_key = 1
|
||
serial.puts("[usb_kbd] first key\n")
|
||
# 只在按下沿解码,避免按住时重复入队。
|
||
_decode_press(code, kbd_report.modifier)
|
||
if new_press == 0 and code != uint8(0x39):
|
||
new_press = code
|
||
if kbd_buf_count < KBD_BUF_SIZE:
|
||
kbd_buf[kbd_buf_tail].modifier = kbd_report.modifier
|
||
kbd_buf[kbd_buf_tail].keycode = code
|
||
kbd_buf[kbd_buf_tail].event_type = KBD_EVT_PRESS
|
||
kbd_buf_tail = kbd_buf_tail + 1
|
||
if kbd_buf_tail >= KBD_BUF_SIZE:
|
||
kbd_buf_tail = 0
|
||
kbd_buf_count += 1
|
||
for i in range(6):
|
||
prev: uint8 = old[i]
|
||
if prev == 0: continue
|
||
found2: int = 0
|
||
for j in range(6):
|
||
if prev == kbd_report.keycode[j]:
|
||
found2 = 1
|
||
break
|
||
if found2 == 0:
|
||
if kbd_buf_count < KBD_BUF_SIZE:
|
||
kbd_buf[kbd_buf_tail].modifier = kbd_report.modifier
|
||
kbd_buf[kbd_buf_tail].keycode = prev
|
||
kbd_buf[kbd_buf_tail].event_type = KBD_EVT_RELEASE
|
||
kbd_buf_tail = kbd_buf_tail + 1
|
||
if kbd_buf_tail >= KBD_BUF_SIZE:
|
||
kbd_buf_tail = 0
|
||
kbd_buf_count += 1
|
||
# 更新自动重复目标:优先刚按下的键;否则保持任一仍按住的键(CapsLock 不重复)。
|
||
rep: uint8 = new_press
|
||
if rep == 0:
|
||
for i in range(6):
|
||
c: uint8 = kbd_report.keycode[i]
|
||
if c != 0 and c != uint8(0x39):
|
||
rep = c
|
||
break
|
||
if rep != 0:
|
||
kbd_rep_code = rep
|
||
kbd_rep_mod = kbd_report.modifier
|
||
kbd_rep_valid = 1
|
||
kbd_rep_next = timer.timer_get_ticks() + uint64(KBD_REPEAT_DELAY_MS)
|
||
else:
|
||
kbd_rep_valid = 0
|
||
_rearm()
|
||
return 0
|
||
|
||
def has_data() -> int:
|
||
if kbd_buf_count > 0: return 1
|
||
return 0
|
||
|
||
def poll() -> int:
|
||
if kbd_initialized == 0: return 0
|
||
if kbd_buf_count > 0: return 1
|
||
return 0
|
||
|
||
def read_event(out: usb_kbd_event*) -> int:
|
||
global kbd_buf_head, kbd_buf_count
|
||
if kbd_buf_count == 0: return -1
|
||
out.modifier = kbd_buf[kbd_buf_head].modifier
|
||
out.keycode = kbd_buf[kbd_buf_head].keycode
|
||
out.event_type = kbd_buf[kbd_buf_head].event_type
|
||
kbd_buf_head = kbd_buf_head + 1
|
||
if kbd_buf_head >= KBD_BUF_SIZE:
|
||
kbd_buf_head = 0
|
||
kbd_buf_count -= 1
|
||
return 0
|