m5stack_core/board/fire27.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! M5Stack Fire v2.7 (ESP32) board-level pin wiring.
3
4use esp_hal::{
5 Blocking,
6 dma::AnySpiDmaChannel,
7 gpio::AnyPin,
8 i2c::master::{BusTimeout, Config as I2cConfig, I2c},
9 interrupt::software::SoftwareInterruptControl,
10 peripherals::{BT, PCNT, Peripherals, RMT, UART0, WIFI},
11 spi::master::AnySpi,
12 time::Rate,
13 timer::{AnyTimer, timg::TimerGroup},
14};
15
16use crate::board::SystemResources;
17use crate::board::spi2::Spi2Resources;
18use crate::io::buttons::ButtonResources;
19
20/// External M5-Bus pins the BSP does not claim for on-board functions. What
21/// hangs off them (1-Wire sensors, RPM pickups, ...) is application wiring.
22/// Not exhaustive — extend as needed.
23pub struct M5Bus<'a> {
24 pub gpio5: AnyPin<'a>,
25 pub gpio26: AnyPin<'a>,
26}
27
28/// The Fire27's peripherals, split into board-fact groups. See
29/// [`Board::split`].
30pub struct Board {
31 /// Display + SD-card shared SPI2 bus (see [`super::spi2`]).
32 pub spi2: Spi2Resources<'static>,
33 /// Internal I2C bus (SDA=GPIO21, SCL=GPIO22) — IP5306 fuel gauge (0x75,
34 /// M5GO bottom) and any Grove/external I2C devices.
35 ///
36 /// Returned BLOCKING: defer `into_async()` to the core that runs the I2C
37 /// tasks — `into_async()` binds the I2C peripheral IRQ to the calling
38 /// core, and on a BLE app it must stay off the core running RWBLE's
39 /// level-1 IRQ (cumulative IRQ blocking otherwise desyncs the BLE
40 /// controller blob and wedges that core's executor).
41 pub i2c0: I2c<'static, Blocking>,
42 /// Front-panel buttons A/B/C (GPIO39/38/37, active-low). Use
43 /// [`ButtonResources::into_buttons`] (feature `buttons`).
44 pub buttons: ButtonResources<'static>,
45 pub bt: BT<'static>,
46 pub wifi: WIFI<'static>,
47 /// UART0 — console + serial-cmd
48 /// ([`crate::io::serial_cmd::SerialCmdResources`] +
49 /// [`crate::io::console`]).
50 pub uart0: UART0<'static>,
51 /// UART0 RX pin (GPIO3).
52 pub uart0_rx: AnyPin<'static>,
53 /// UART0 TX pin (GPIO1).
54 pub uart0_tx: AnyPin<'static>,
55 /// SK6812 LED bars data pin (M-Bus pin 23 → GPIO15), driven over RMT
56 /// ([`crate::driver::sk6812::Sk6812Driver`]).
57 pub sk6812: AnyPin<'static>,
58 /// RMT — SK6812 LED bars or the 1-Wire driver (one owner;
59 /// [`crate::io::ow_temp::OnewireResources`]).
60 pub rmt: RMT<'static>,
61 /// PCNT — e.g. pulse counting ([`crate::io::rpm::RpmResources`]).
62 pub pcnt: PCNT<'static>,
63 /// Spare SPI3 unit — the BSP does not drive it, listed for the app to
64 /// claim (same idea as [`Self::rmt`]/[`Self::pcnt`]). Wiring (which
65 /// [`Self::m5bus`] pin is SCK/MOSI/MISO/CS) and the interrupt priority /
66 /// core allocation are application knowledge. Attach the CS pin as the
67 /// peripheral's **hardware** chip-select (`with_cs`), not a
68 /// software-toggled [`esp_hal::gpio::Output`] — some designs depend on a
69 /// sub-microsecond CS gap that software toggling cannot hold. See #27.
70 pub spi3: AnySpi<'static>,
71 /// PDMA channel for [`Self::spi3`] — SPI3's own dedicated PDMA unit, not
72 /// shared with `spi2` (`DMA_SPI2`).
73 pub spi3_dma: AnySpiDmaChannel<'static>,
74 /// External SPI PSRAM (~4 MB) — feed to `mem::psram_map` / `mem::psram_split`
75 /// (the ESP32 cannot DMA from PSRAM). [feature `psram`]
76 pub psram: esp_hal::peripherals::PSRAM<'static>,
77 pub m5bus: M5Bus<'static>,
78 pub system: SystemResources<'static>,
79}
80
81impl Board {
82 /// Wire the Fire27 pin map. Call once with the output of
83 /// [`crate::board::init`] (or `esp_hal::init`); heap setup stays with the
84 /// app (sizing is application policy).
85 pub fn split(peripherals: Peripherals) -> Self {
86 let spi2 = Spi2Resources {
87 spi2: AnySpi::from(peripherals.SPI2),
88 spi2_dma: AnySpiDmaChannel::from(peripherals.DMA_SPI2),
89 sck: AnyPin::from(peripherals.GPIO18),
90 mosi: AnyPin::from(peripherals.GPIO23),
91 miso: AnyPin::from(peripherals.GPIO19),
92 display_cs: AnyPin::from(peripherals.GPIO14),
93 display_dc: AnyPin::from(peripherals.GPIO27),
94 display_rst: AnyPin::from(peripherals.GPIO33),
95 display_bl: AnyPin::from(peripherals.GPIO32),
96 card_cs: AnyPin::from(peripherals.GPIO4),
97 };
98
99 let i2c0 = I2c::new(
100 peripherals.I2C0,
101 I2cConfig::default()
102 .with_frequency(Rate::from_khz(400))
103 .with_timeout(BusTimeout::BusCycles(20)),
104 )
105 .expect("I2C0 init failed")
106 .with_sda(AnyPin::from(peripherals.GPIO21))
107 .with_scl(AnyPin::from(peripherals.GPIO22));
108
109 let buttons = ButtonResources {
110 left: AnyPin::from(peripherals.GPIO39),
111 center: AnyPin::from(peripherals.GPIO38),
112 right: AnyPin::from(peripherals.GPIO37),
113 };
114
115 let tg0 = TimerGroup::new(peripherals.TIMG0);
116 let tg1 = TimerGroup::new(peripherals.TIMG1);
117 let system = SystemResources {
118 sw_int: SoftwareInterruptControl::new(peripherals.SW_INTERRUPT),
119 timer0_0: AnyTimer::from(tg0.timer0),
120 timer0_1: AnyTimer::from(tg0.timer1),
121 timer1_0: AnyTimer::from(tg1.timer0),
122 timer1_1: AnyTimer::from(tg1.timer1),
123 cpu_ctrl: peripherals.CPU_CTRL,
124 lpwr: peripherals.LPWR,
125 };
126
127 Board {
128 spi2,
129 i2c0,
130 buttons,
131 bt: peripherals.BT,
132 wifi: peripherals.WIFI,
133 uart0: peripherals.UART0,
134 uart0_rx: AnyPin::from(peripherals.GPIO3),
135 uart0_tx: AnyPin::from(peripherals.GPIO1),
136 sk6812: AnyPin::from(peripherals.GPIO15),
137 rmt: peripherals.RMT,
138 pcnt: peripherals.PCNT,
139 spi3: AnySpi::from(peripherals.SPI3),
140 spi3_dma: AnySpiDmaChannel::from(peripherals.DMA_SPI3),
141 psram: peripherals.PSRAM,
142 m5bus: M5Bus {
143 gpio5: AnyPin::from(peripherals.GPIO5),
144 gpio26: AnyPin::from(peripherals.GPIO26),
145 },
146 system,
147 }
148 }
149}