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}