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}