esp32_wroom_rp/
lib.rs

1//! esp32-wroom-rp
2//!
3//! This crate is an Espressif ESP32-WROOM WiFi module communications driver for RP2040 series microcontroller implemented in Rust.
4//! It currently supports the [ESP32-WROOM-32E](https://www.espressif.com/sites/default/files/documentation/esp32-wroom-32e_esp32-wroom-32ue_datasheet_en.pdf)
5//! and [ESP32-WROOM-32UE](https://www.espressif.com/sites/default/files/documentation/esp32-wroom-32e_esp32-wroom-32ue_datasheet_en.pdf) modules.
6//! Future implementations intend to add support for the [ESP32-WROOM-DA](https://www.espressif.com/sites/default/files/documentation/esp32-wroom-da_datasheet_en.pdf) module.
7//!
8//! It's intended to communicate with recent versions of most [Arduino-derived WiFiNINA firmwares](https://www.arduino.cc/reference/en/libraries/wifinina/)
9//! that run on an ESP32-WROOM-XX WiFi module. For example, Adafruit makes such WiFi hardware, referred to as the [Airlift](https://www.adafruit.com/product/4201), and maintains its [firmware](https://github.com/adafruit/nina-fw).
10//!
11//! This driver is implemented on top of [embedded-hal](https://github.com/rust-embedded/embedded-hal/), which makes it platform-independent, but is currently only intended
12//! to be used with [rp2040-hal](https://github.com/rp-rs/rp-hal/tree/main/rp2040-hal) for your application.
13//!
14//! Please see the README.md for details on where to obtain and how to connect your RP2040-based device (e.g. Pico) to your ESP32-WROOM-XX WiFi board.
15//!
16//! Once connected, note that all communication with the WiFi board occurs via a SPI bus. As the example below (and all examples under the directory `cross/`)
17//! show, you first need to create an `embedded_hal::spi::Spi` instance. See the [rp2040-hal documentation](https://docs.rs/rp2040-hal/0.6.0/rp2040_hal/spi/index.html) along
18//! with the datasheet for your device on what specific SPI ports are available to you.
19//!
20//! You'll also need to reserve 4 important [GPIO pins](https://docs.rs/rp2040-hal/0.6.0/rp2040_hal/gpio/index.html) (3 output, 1 input) that are used to mediate communication between the two boards. The examples
21//! also demonstrate how to do this through instantiating an instance of `esp32_wroom_rp::gpio::EspControlPins`.
22//!
23//! **NOTE:** This crate is still under active development. This API will remain volatile until 1.0.0.
24//!
25//! ## Usage
26//!
27//! First add this to your Cargo.toml
28//!
29//! ```toml
30//! [dependencies]
31//! esp32_wroom_rp = 0.3.0
32//! ...
33//! ```
34//!
35//! Next:
36//!
37//! ```
38//! // The macro for our start-up function
39//! use cortex_m_rt::entry;
40//!
41//! // Needed for debug output symbols to be linked in binary image
42//! use defmt_rtt as _;
43//!
44//! use panic_probe as _;
45//!
46//! // Alias for our HAL crate
47//! use rp2040_hal as hal;
48//!
49//! use embedded_hal::spi::MODE_0;
50//! use fugit::RateExtU32;
51//! use hal::clocks::Clock;
52//! use hal::pac;
53//!
54//! use esp32_wroom_rp::gpio::EspControlPins;
55//! use esp32_wroom_rp::wifi::Wifi;
56//!
57//! // The linker will place this boot block at the start of our program image. We
58//! // need this to help the ROM bootloader get our code up and running.
59//! #[link_section = ".boot2"]
60//! #[used]
61//! pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080;
62//!
63//! // External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust
64//! // if your board has a different frequency
65//! const XTAL_FREQ_HZ: u32 = 12_000_000u32;
66//!
67//! // Entry point to our bare-metal application.
68//! //
69//! // The `#[entry]` macro ensures the Cortex-M start-up code calls this function
70//! // as soon as all global variables are initialized.
71//! #[entry]
72//! fn main() -> ! {
73//!     // Grab our singleton objects
74//!     let mut pac = pac::Peripherals::take().unwrap();
75//!     let core = pac::CorePeripherals::take().unwrap();
76//!
77//!     // Set up the watchdog driver - needed by the clock setup code
78//!     let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
79//!
80//!     // Configure the clocks
81//!     let clocks = hal::clocks::init_clocks_and_plls(
82//!         XTAL_FREQ_HZ,
83//!         pac.XOSC,
84//!         pac.CLOCKS,
85//!         pac.PLL_SYS,
86//!         pac.PLL_USB,
87//!         &mut pac.RESETS,
88//!         &mut watchdog,
89//!     )
90//!     .ok()
91//!     .unwrap();
92//!
93//!     let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz());
94//!
95//!     // The single-cycle I/O block controls our GPIO pins
96//!     let sio = hal::Sio::new(pac.SIO);
97//!
98//!     // Set the pins to their default state
99//!     let pins = hal::gpio::Pins::new(
100//!         pac.IO_BANK0,
101//!         pac.PADS_BANK0,
102//!         sio.gpio_bank0,
103//!         &mut pac.RESETS,
104//!     );
105//!
106//!     defmt::info!("ESP32-WROOM-RP get NINA firmware version example");
107//!
108//!     // These are implicitly used by the spi driver if they are in the correct mode
109//!     let _spi_miso = pins.gpio16.into_mode::<hal::gpio::FunctionSpi>();
110//!     let _spi_sclk = pins.gpio18.into_mode::<hal::gpio::FunctionSpi>();
111//!     let _spi_mosi = pins.gpio19.into_mode::<hal::gpio::FunctionSpi>();
112//!
113//!     let spi = hal::Spi::<_, _, 8>::new(pac.SPI0);
114//!
115//!     // Exchange the uninitialized SPI driver for an initialized one
116//!     let spi = spi.init(
117//!         &mut pac.RESETS,
118//!         clocks.peripheral_clock.freq(),
119//!         8.MHz(),
120//!         &MODE_0,
121//!     );
122//!
123//!     let esp_pins = EspControlPins {
124//!         // CS on pin x (GPIO7)
125//!         cs: pins.gpio7.into_mode::<hal::gpio::PushPullOutput>(),
126//!         // GPIO0 on pin x (GPIO2)
127//!         gpio0: pins.gpio2.into_mode::<hal::gpio::PushPullOutput>(),
128//!         // RESETn on pin x (GPIO11)
129//!         resetn: pins.gpio11.into_mode::<hal::gpio::PushPullOutput>(),
130//!         // ACK on pin x (GPIO10)
131//!         ack: pins.gpio10.into_mode::<hal::gpio::FloatingInput>(),
132//!     };
133//!     let mut wifi = Wifi::init(spi, esp_pins, &mut delay).unwrap();
134//!     let firmware_version = wifi.firmware_version();
135//!     defmt::info!("NINA firmware version: {:?}", firmware_version);
136//!
137//!     // Infinitely sit in a main loop
138//!     loop {}
139//! }
140//! ```
141//!
142//! ## More examples
143//!
144//! Please refer to the `cross/` directory in this crate's source for examples that demonstrate how to use every part of its public API.
145//!
146
147#![doc(html_root_url = "https://docs.rs/esp32-wroom-rp")]
148#![doc(issue_tracker_base_url = "https://github.com/Jim-Hodapp-Coaching/esp32-wroom-rp/issues")]
149#![warn(missing_docs)]
150#![cfg_attr(not(test), no_std)]
151
152pub mod gpio;
153pub mod network;
154pub mod protocol;
155pub mod tcp_client;
156pub mod wifi;
157
158mod spi;
159
160use defmt::{write, Format, Formatter};
161
162use network::NetworkError;
163
164use protocol::ProtocolError;
165
166const ARRAY_LENGTH_PLACEHOLDER: usize = 8;
167
168/// Highest level error types for this crate.
169#[derive(Debug, Eq, PartialEq)]
170pub enum Error {
171    /// SPI/I2C related communications error with the ESP32 WiFi target
172    Bus,
173    /// Protocol error in communicating with the ESP32 WiFi target
174    Protocol(ProtocolError),
175
176    /// Network related error
177    Network(NetworkError),
178}
179
180impl Format for Error {
181    fn format(&self, fmt: Formatter) {
182        match self {
183            Error::Bus => write!(fmt, "Bus error"),
184            Error::Protocol(e) => write!(
185                fmt,
186                "Communication protocol error with ESP32 WiFi target: {}",
187                e
188            ),
189            Error::Network(e) => write!(fmt, "Network error: {}", e),
190        }
191    }
192}
193
194impl From<protocol::ProtocolError> for Error {
195    fn from(err: protocol::ProtocolError) -> Self {
196        Error::Protocol(err)
197    }
198}
199
200impl From<network::NetworkError> for Error {
201    fn from(err: network::NetworkError) -> Self {
202        Error::Network(err)
203    }
204}
205
206/// A structured representation of a connected NINA firmware device's version number (e.g. 1.7.4).
207#[derive(Debug, Default, Eq, PartialEq)]
208pub struct FirmwareVersion {
209    major: u8,
210    minor: u8,
211    patch: u8,
212}
213
214impl FirmwareVersion {
215    fn new(version: [u8; ARRAY_LENGTH_PLACEHOLDER]) -> FirmwareVersion {
216        Self::parse(version)
217    }
218
219    // Takes in 8 bytes (e.g. 1.7.4) and returns a FirmwareVersion instance
220    fn parse(version: [u8; ARRAY_LENGTH_PLACEHOLDER]) -> FirmwareVersion {
221        let major_version: u8;
222        let minor_version: u8;
223        let patch_version: u8;
224
225        [major_version, _, minor_version, _, patch_version, _, _, _] = version;
226
227        FirmwareVersion {
228            major: major_version,
229            minor: minor_version,
230            patch: patch_version,
231        }
232    }
233}
234
235impl Format for FirmwareVersion {
236    fn format(&self, fmt: Formatter) {
237        write!(
238            fmt,
239            "Major: {:?}, Minor: {:?}, Patch: {:?}",
240            self.major as char, self.minor as char, self.patch as char
241        );
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    #[test]
250    fn firmware_new_returns_a_populated_firmware_struct() {
251        let firmware_version: FirmwareVersion =
252            FirmwareVersion::new([0x1, 0x2e, 0x7, 0x2e, 0x4, 0x0, 0x0, 0x0]);
253
254        assert_eq!(
255            firmware_version,
256            FirmwareVersion {
257                major: 1,
258                minor: 7,
259                patch: 4
260            }
261        )
262    }
263}