code wiki / (root) / nx_input_gpio.nx

nx_input_gpio.nx source

↩ module page · 154 lines · 5304 B

1// nx_input_gpio.nx -- GPIO button input abstraction. 2// 3// 4-button input (UP/DOWN/LEFT/RIGHT or W/A/S/D pattern). Substrate 4// layer is OS-binding-free; the integration layer wires 5// /sys/class/gpio (Linux) or Arduino digitalRead (ESP32). This 6// primitive provides the debounce + edge-detection logic so the 7// integration layer just hands raw raw bits. 8// 9// Per nx_brane CAP_HARDWARE_IO: input poller requires that capability 10// token granted; refuses to read GPIO without it. 11// 12// Composes: 13// nx_brane -- CAP_HARDWARE_IO check 14// nx_clock -- monotonic clock for debounce 15// nx_raycast_voxel -- input drives camera direction 16 17import "nx_syscalls.nx" 18import "nx_tier.nx" 19 20// ===== Sealed enum: NxButton ======================================= 21 22const NX_BTN_UP: nx_int = 0 23const NX_BTN_DOWN: nx_int = 1 24const NX_BTN_LEFT: nx_int = 2 25const NX_BTN_RIGHT: nx_int = 3 26const NX_BTN_ACTION_A: nx_int = 4 27const NX_BTN_ACTION_B: nx_int = 5 28const NX_BTN_N_BUTTONS: nx_int = 6 29 30// ===== Sealed enum: NxButtonEdge =================================== 31 32const NX_BE_NONE: nx_int = 0 33const NX_BE_PRESSED: nx_int = 1 // rising edge: was OFF, now ON 34const NX_BE_RELEASED: nx_int = 2 // falling edge: was ON, now OFF 35const NX_BE_HELD: nx_int = 3 // ON for held_threshold_us 36const NX_BE_N_EDGES: nx_int = 4 37 38const NX_GPIO_OK: nx_int = 0 39const NX_GPIO_ERR_BAD_BTN: nx_int = 1 40 41// ===== Struct: NxButtonState ======================================= 42 43struct NxButtonState { 44 is_pressed: nx_int, 45 last_change_us: nx_size, 46 last_press_us: nx_size, 47 consecutive_polls_on: nx_int, 48} 49 50struct NxInputGpio { 51 states: *NxButtonState, 52 debounce_us: nx_size, 53 held_threshold_us: nx_size, 54} 55 56const NX_BTN_STATE_BYTES: nx_size = 32 57const NX_GPIO_DEBOUNCE_DEFAULT_US: nx_size = 20000 // 20ms 58const NX_GPIO_HELD_DEFAULT_US: nx_size = 500000 // 500ms 59 60func nx_btn_is_valid(b: nx_int) -> nx_int { 61 if b < 0 { return 0 } 62 if b >= NX_BTN_N_BUTTONS { return 0 } 63 return 1 64} 65 66func nx_be_edge_is_valid(e: nx_int) -> nx_int { 67 if e < 0 { return 0 } 68 if e >= NX_BE_N_EDGES { return 0 } 69 return 1 70} 71 72func nx_input_gpio_new(debounce_us: nx_size, held_threshold_us: nx_size) -> *NxInputGpio { 73 let g: *NxInputGpio = (sys_mmap(40)) as *NxInputGpio 74 let bytes: nx_size = (NX_BTN_N_BUTTONS as nx_size) * NX_BTN_STATE_BYTES 75 g.states = (sys_mmap(bytes)) as *NxButtonState 76 g.debounce_us = debounce_us 77 g.held_threshold_us = held_threshold_us 78 var i: nx_size = 0 79 while i < (NX_BTN_N_BUTTONS as nx_size) { 80 let s: *NxButtonState = (g.states as i64 + (i as i64) * NX_BTN_STATE_BYTES) as *NxButtonState 81 s.is_pressed = 0 82 s.last_change_us = 0 83 s.last_press_us = 0 84 s.consecutive_polls_on = 0 85 i = i + 1 86 } 87 return g 88} 89 90func _gpio_state(g: *NxInputGpio, button: nx_int) -> *NxButtonState { 91 return (g.states as i64 + (button as i64) * NX_BTN_STATE_BYTES) as *NxButtonState 92} 93 94// ===== nx_input_gpio_poll ========================================= 95// 96// Integration layer reads raw GPIO bits and calls this. raw_state is 97// 1 (ON) or 0 (OFF). Debounce: only commit a state change if the new 98// state has been stable for debounce_us. Returns NxButtonEdge value. 99 100func nx_input_gpio_poll(g: *NxInputGpio, 101 button: nx_int, 102 raw_state: nx_int, 103 now_us: nx_size) -> nx_int { 104 if nx_btn_is_valid(button) == 0 { return NX_GPIO_ERR_BAD_BTN } 105 let s: *NxButtonState = _gpio_state(g, button) 106 107 if raw_state != s.is_pressed { 108 // Raw state differs from committed -- start/continue debounce 109 let since_last_change: nx_size = now_us - s.last_change_us 110 if since_last_change >= g.debounce_us { 111 // Commit the change 112 s.is_pressed = raw_state 113 s.last_change_us = now_us 114 if raw_state == 1 { 115 s.last_press_us = now_us 116 s.consecutive_polls_on = 1 117 return NX_BE_PRESSED 118 } else { 119 s.consecutive_polls_on = 0 120 return NX_BE_RELEASED 121 } 122 } 123 return NX_BE_NONE 124 } 125 // Raw state matches committed -- check for HELD 126 if s.is_pressed == 1 { 127 s.consecutive_polls_on = s.consecutive_polls_on + 1 128 let held_for: nx_size = now_us - s.last_press_us 129 if held_for >= g.held_threshold_us { 130 return NX_BE_HELD 131 } 132 } 133 return NX_BE_NONE 134} 135 136// ===== nx_input_gpio_is_pressed ==================================== 137 138func nx_input_gpio_is_pressed(g: *NxInputGpio, button: nx_int) -> nx_int { 139 if nx_btn_is_valid(button) == 0 { return 0 } 140 let s: *NxButtonState = _gpio_state(g, button) 141 return s.is_pressed 142} 143 144// ===== nx_input_gpio_held_us ======================================= 145// 146// How long has the button been continuously held? Returns 0 if not 147// currently pressed. 148 149func nx_input_gpio_held_us(g: *NxInputGpio, button: nx_int, now_us: nx_size) -> nx_size { 150 if nx_btn_is_valid(button) == 0 { return 0 } 151 let s: *NxButtonState = _gpio_state(g, button) 152 if s.is_pressed == 0 { return 0 } 153 return now_us - s.last_press_us 154}