lilkaoxide/controller.rs
1use esp_hal::gpio::{Input, InputConfig, Pull};
2use esp_hal::peripherals::{
3 GPIO0, GPIO4, GPIO5, GPIO6, GPIO9, GPIO10, GPIO38, GPIO39, GPIO40, GPIO41,
4};
5
6/// State of all physical input buttons on the Lilka game console.
7///
8/// Each field is an `esp-hal` [`Input`] pin configured with an internal pull-up
9/// resistor. The logic is **active-low**:
10/// * `is_low()` → button is **pressed**.
11/// * `is_high()` → button is **released**.
12///
13/// # Layout
14///
15/// ```text
16/// [UP GPIO38]
17/// [LEFT GPIO39] [DOWN GPIO41] [RIGHT GPIO40]
18///
19/// [A GPIO5] [B GPIO6] [C GPIO10] [D GPIO9]
20///
21/// [SELECT GPIO0] [START GPIO4]
22/// ```
23///
24/// # Example — D-pad and buttons in a game loop
25///
26/// ```rust,no_run
27/// use lilkaoxide::prelude::*;
28///
29/// # fn game_loop(mut lilka: Lilka) {
30/// loop {
31/// if lilka.controller.up.is_low() {
32/// // move player up
33/// }
34/// if lilka.controller.a.is_low() {
35/// // action A
36/// }
37/// if lilka.controller.start.is_low() {
38/// // pause menu
39/// }
40/// }
41/// # }
42/// ```
43pub struct ControllerState {
44 /// D-pad up — GPIO38, active-low.
45 pub up: Input<'static>,
46 /// D-pad down — GPIO41, active-low.
47 pub down: Input<'static>,
48 /// D-pad left — GPIO39, active-low.
49 pub left: Input<'static>,
50 /// D-pad right — GPIO40, active-low.
51 pub right: Input<'static>,
52 /// Action button A — GPIO5, active-low.
53 pub a: Input<'static>,
54 /// Action button B — GPIO6, active-low.
55 pub b: Input<'static>,
56 /// Action button C — GPIO10, active-low.
57 pub c: Input<'static>,
58 /// Action button D — GPIO9, active-low.
59 pub d: Input<'static>,
60 /// Select button — GPIO0, active-low.
61 pub select: Input<'static>,
62 /// Start button — GPIO4, active-low.
63 pub start: Input<'static>,
64}
65
66impl ControllerState {
67 /// Initialises all controller GPIO pins as pull-up inputs.
68 ///
69 /// This function is called internally by [`crate::Lilka::init`] — you should
70 /// not need to call it directly. It moves the concrete GPIO peripheral tokens
71 /// into [`Input`] handles with `Pull::Up` so that pressing a button drives the
72 /// pin to ground (active-low).
73 ///
74 /// # Pin assignment
75 ///
76 /// | Button | GPIO | Field |
77 /// |---|---|---|
78 /// | A | 5 | [`ControllerState::a`] |
79 /// | B | 6 | [`ControllerState::b`] |
80 /// | C | 10 | [`ControllerState::c`] |
81 /// | D | 9 | [`ControllerState::d`] |
82 /// | Up | 38 | [`ControllerState::up`] |
83 /// | Down | 41 | [`ControllerState::down`] |
84 /// | Left | 39 | [`ControllerState::left`] |
85 /// | Right | 40 | [`ControllerState::right`] |
86 /// | Select | 0 | [`ControllerState::select`] |
87 /// | Start | 4 | [`ControllerState::start`] |
88 pub fn init_controller(
89 gpio5: GPIO5<'static>,
90 gpio6: GPIO6<'static>,
91 gpio10: GPIO10<'static>,
92 gpio9: GPIO9<'static>,
93 gpio38: GPIO38<'static>,
94 gpio41: GPIO41<'static>,
95 gpio39: GPIO39<'static>,
96 gpio40: GPIO40<'static>,
97 gpio0: GPIO0<'static>,
98 gpio4: GPIO4<'static>,
99 ) -> Self {
100 let controller = ControllerState {
101 a: Input::new(gpio5, InputConfig::default().with_pull(Pull::Up)),
102 b: Input::new(gpio6, InputConfig::default().with_pull(Pull::Up)),
103 c: Input::new(gpio10, InputConfig::default().with_pull(Pull::Up)),
104 d: Input::new(gpio9, InputConfig::default().with_pull(Pull::Up)),
105 up: Input::new(gpio38, InputConfig::default().with_pull(Pull::Up)),
106 down: Input::new(gpio41, InputConfig::default().with_pull(Pull::Up)),
107 left: Input::new(gpio39, InputConfig::default().with_pull(Pull::Up)),
108 right: Input::new(gpio40, InputConfig::default().with_pull(Pull::Up)),
109 select: Input::new(gpio0, InputConfig::default().with_pull(Pull::Up)),
110 start: Input::new(gpio4, InputConfig::default().with_pull(Pull::Up)),
111 };
112 controller
113 }
114}