diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b5c04a68..d229fad3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,6 +50,7 @@ jobs: toolchain: - stable - beta + variant: [phy_w5500, phy_enc424j600] steps: - uses: actions/checkout@v2 @@ -77,18 +78,19 @@ jobs: uses: actions-rs/cargo@v1 with: command: build + args: --no-default-features --features "${{ matrix.variant }}" - name: Build [Release] uses: actions-rs/cargo@v1 with: command: build - args: --release + args: --release --no-default-features --features "${{ matrix.variant }}" - name: Generate Release uses: actions-rs/cargo@v1 with: command: objcopy - args: --release --verbose -- -O binary booster-release.bin + args: --release --no-default-features --features "${{ matrix.variant }}" --verbose -- -O binary booster-release.bin - name: Upload Release uses: actions/upload-artifact@v2 @@ -96,13 +98,16 @@ jobs: (${{ github.ref == 'refs/heads/master' }} || ${{ github.ref == 'refs/heads/develop' }}) with: - name: Firmware Images + name: Firmware Images (variant=${{ matrix.variant }}) path: | target/*/release/booster booster-release.bin compile-unstable: runs-on: ubuntu-latest + strategy: + matrix: + variant: [phy_w5500, phy_enc424j600] steps: - uses: actions/checkout@v2 - name: Install Rust Nightly @@ -115,9 +120,9 @@ jobs: uses: actions-rs/cargo@v1 with: command: build - args: --features unstable + args: --no-default-features --features "unstable ${{ matrix.variant }}" - name: cargo build+release+unstable uses: actions-rs/cargo@v1 with: command: build - args: --release --features unstable + args: --release --no-default-features --features "unstable ${{ matrix.variant }}" diff --git a/Cargo.lock b/Cargo.lock index c9132664..32f228e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,6 +15,12 @@ dependencies = [ "embedded-hal", ] +[[package]] +name = "aligned" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d39da9b88ae1a81c03c9c082b8db83f1d0e93914126041962af61034ab44c4a5" + [[package]] name = "aligned" version = "0.3.4" @@ -26,12 +32,12 @@ dependencies = [ [[package]] name = "as-slice" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb4d1c23475b74e3672afa8c2be22040b8b7783ad9b461021144ed10a46bb0e6" +checksum = "45403b49e3954a4b8428a0ac21a4b7afadccf92bfd96273f1a58cd4812496ae0" dependencies = [ - "generic-array 0.12.3", - "generic-array 0.13.2", + "generic-array 0.12.4", + "generic-array 0.13.3", "generic-array 0.14.4", "stable_deref_trait", ] @@ -57,7 +63,7 @@ version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e6ffd9a6d04298eb55d5ac6d0fde48c2f991468b5ba8aa263fe2d1ea7288c0a" dependencies = [ - "generic-array 0.13.2", + "generic-array 0.13.3", ] [[package]] @@ -78,6 +84,12 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + [[package]] name = "booster" version = "0.2.0" @@ -86,7 +98,7 @@ dependencies = [ "ads7924", "bbqueue", "bit_field", - "cortex-m", + "cortex-m 0.6.3", "cortex-m-log", "cortex-m-rt", "cortex-m-rtic", @@ -94,8 +106,10 @@ dependencies = [ "dac7571", "debounced-pin", "embedded-hal", + "embedded-time", + "enc424j600", "enum-iterator", - "heapless", + "heapless 0.5.6", "log", "logos", "max6639", @@ -108,6 +122,7 @@ dependencies = [ "serde", "serde-json-core", "shared-bus", + "smoltcp-nal", "stm32f4xx-hal", "tca9548", "usb-device", @@ -146,13 +161,25 @@ dependencies = [ "num-traits", ] +[[package]] +name = "cortex-m" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59971a5cf4dacacaf738dd9d8660875118fce790f800f661893eb20894c1d622" +dependencies = [ + "aligned 0.2.0", + "bare-metal", + "cortex-m 0.6.3", + "volatile-register", +] + [[package]] name = "cortex-m" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2be99930c99669a74d986f7fd2162085498b322e6daae8ef63a97cc9ac1dc73c" dependencies = [ - "aligned", + "aligned 0.3.4", "bare-metal", "bitfield", "volatile-register", @@ -164,7 +191,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d63959cb1e003dd97233fee6762351540253237eadf06fcdcb98cbfa3f9be4a" dependencies = [ - "cortex-m", + "cortex-m 0.6.3", "log", ] @@ -195,10 +222,10 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b30efcb6b7920d9016182c485687f0012487032a14c415d2fce6e9862ef8260e" dependencies = [ - "cortex-m", + "cortex-m 0.6.3", "cortex-m-rt", "cortex-m-rtic-macros", - "heapless", + "heapless 0.5.6", "rtic-core", "version_check", ] @@ -273,11 +300,32 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae46eb1b02de5a76d9d0ea21d657ff5b0ad2cc47f3a7723608227b1dd1b3eb18" dependencies = [ - "heapless", + "heapless 0.5.6", "nb 1.0.0", "no-std-net", ] +[[package]] +name = "embedded-time" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86fbafcea0dea120d8ed8af67ddbf82afc031f1d3b064920645db8d061781e2c" +dependencies = [ + "num", +] + +[[package]] +name = "enc424j600" +version = "0.2.0" +source = "git+https://git.m-labs.hk/M-Labs/ENC424J600.git?rev=fbcc3778d27cfbeec7a1395c9b13a00c8a26af9a#fbcc3778d27cfbeec7a1395c9b13a00c8a26af9a" +dependencies = [ + "aligned 0.3.4", + "cortex-m 0.5.11", + "embedded-hal", + "smoltcp", + "volatile-register", +] + [[package]] name = "enum-iterator" version = "0.6.0" @@ -306,18 +354,18 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "generic-array" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" dependencies = [ "typenum", ] [[package]] name = "generic-array" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed1e761351b56f54eb9dcd0cfaca9fd0daecf93918e1cfc01c8a3d26ee7adcd" +checksum = "f797e67af32588215eaaab8327027ee8e71b9dd0b2b26996aedf20c030fce309" dependencies = [ "typenum", ] @@ -354,12 +402,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74911a68a1658cfcfb61bc0ccfbd536e3b6e906f8c2f7883ee50157e3e2184f1" dependencies = [ "as-slice", - "generic-array 0.13.2", + "generic-array 0.13.3", "hash32", "serde", "stable_deref_trait", ] +[[package]] +name = "heapless" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634bd4d29cbf24424d0a4bfcbf80c6960129dc24424752a7d1d1390607023422" +dependencies = [ + "as-slice", + "generic-array 0.14.4", + "hash32", + "stable_deref_trait", +] + [[package]] name = "indexmap" version = "1.6.0" @@ -403,6 +463,12 @@ dependencies = [ "utf8-ranges", ] +[[package]] +name = "managed" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75de51135344a4f8ed3cfe2720dc27736f7711989703a0b43aadf3753c55577" + [[package]] name = "max6639" version = "0.1.0" @@ -435,14 +501,14 @@ dependencies = [ [[package]] name = "minimq" -version = "0.1.0" -source = "git+https://github.com/quartiq/minimq#83e946544cebd09c9dd07ff1271be639138ec1ce" +version = "0.2.0" +source = "git+https://github.com/quartiq/minimq?rev=d2ec3e8351fa403ea96defd98c0b4410cbaa18a4#d2ec3e8351fa403ea96defd98c0b4410cbaa18a4" dependencies = [ "bit_field", "embedded-nal", "enum-iterator", "generic-array 0.14.4", - "heapless", + "heapless 0.6.1", ] [[package]] @@ -466,6 +532,28 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2178127478ae4ee9be7180bc9c3bffb6354dd7238400db567102f98c413a9f35" +[[package]] +name = "num" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f" +dependencies = [ + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5" +dependencies = [ + "num-traits", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -476,6 +564,28 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.14" @@ -511,7 +621,7 @@ name = "panic-persist" version = "0.2.1" source = "git+https://github.com/jamesmunns/panic-persist?branch=master#3f892297e97adc6a415145193000e3749b1959df" dependencies = [ - "cortex-m", + "cortex-m 0.6.3", ] [[package]] @@ -520,7 +630,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3e3f5c2e9a91383c6594ec68aa2dfdfe19a3c86f34b088ba7203f2483d2682f" dependencies = [ - "heapless", + "heapless 0.5.6", "postcard-cobs", "serde", ] @@ -632,7 +742,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89fd6016a00149b485f66da701f76d909210d319040c97b6eff300f6e2ba2153" dependencies = [ - "heapless", + "heapless 0.5.6", "serde", ] @@ -653,10 +763,32 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f42e140835229dea9c0b2498d2c88afe52ed2bd42a7cc6068c95adb5134b541" dependencies = [ - "cortex-m", + "cortex-m 0.6.3", "embedded-hal", ] +[[package]] +name = "smoltcp" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab527c390c7e107f687bd92a886a083fde61b8cdc700b37f3d7e4346ffd8fae1" +dependencies = [ + "bitflags", + "byteorder", + "managed", +] + +[[package]] +name = "smoltcp-nal" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e5aeb4818706fd74c35917692008d29a5314483c8180300a582253718ce57a" +dependencies = [ + "embedded-nal", + "heapless 0.5.6", + "smoltcp", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -670,7 +802,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11460b4de3a84f072e2cf6e76306c64d27f405a0e83bace0a726f555ddf4bf33" dependencies = [ "bare-metal", - "cortex-m", + "cortex-m 0.6.3", "cortex-m-rt", "vcell", ] @@ -682,7 +814,7 @@ source = "git+https://github.com/stm32-rs/stm32f4xx-hal#c8b11b200ac3c5f9be802f8f dependencies = [ "bare-metal", "cast", - "cortex-m", + "cortex-m 0.6.3", "cortex-m-rt", "embedded-dma", "embedded-hal", @@ -711,7 +843,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "461676dcf123675b3d3b02e2390e6a690cd186aacf2f439af7673c79e2561d53" dependencies = [ - "cortex-m", + "cortex-m 0.6.3", "usb-device", "vcell", ] diff --git a/Cargo.toml b/Cargo.toml index 38901296..3b3b9852 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,9 +26,12 @@ usb-device = "0.2.6" postcard = "0.5.1" crc-any = { version = "2.3.5", default-features = false } panic-persist = { git = "https://github.com/jamesmunns/panic-persist", branch = "master", features = ["custom-panic-handler"] } +smoltcp-nal = { version = "0.1.0", optional = true } +embedded-time = { version = "0.12.0", optional = true } [dependencies.minimq] git = "https://github.com/quartiq/minimq" +rev = "d2ec3e8351fa403ea96defd98c0b4410cbaa18a4" [dependencies.stm32f4xx-hal] git = "https://github.com/stm32-rs/stm32f4xx-hal" @@ -37,6 +40,13 @@ features = ["stm32f407", "rt", "usb_fs"] [dependencies.w5500] git = "https://github.com/quartiq/w5500" branch = "feature/tcp-nal" +optional = true + +[dependencies.enc424j600] +git = "https://git.m-labs.hk/M-Labs/ENC424J600.git" +rev = "fbcc3778d27cfbeec7a1395c9b13a00c8a26af9a" +features = [ "smoltcp-phy", "cortex-m-cpu" ] +optional = true [dependencies.ad5627] path = "ad5627" @@ -64,6 +74,9 @@ path = "tca9548" [features] unstable = [] +phy_enc424j600 = [ "enc424j600", "smoltcp-nal", "embedded-time" ] +phy_w5500 = [ "w5500" ] +default = [ "phy_w5500" ] [profile.release] codegen-units = 1 # better optimizations diff --git a/src/enc424j600_api.rs b/src/enc424j600_api.rs new file mode 100644 index 00000000..0fa965a9 --- /dev/null +++ b/src/enc424j600_api.rs @@ -0,0 +1,151 @@ +use super::{Enc424j600, Ethernet}; +use crate::BoosterSettings; +use cortex_m::peripheral::DWT; +use embedded_hal::blocking::delay::DelayUs; +use embedded_time::{clock, duration::*, Instant}; +use smoltcp_nal::smoltcp; + +/// Containers for smoltcp-related network configurations +pub struct NetStorage { + pub socket_storage: [Option>; 1], + pub ip_addrs: [smoltcp::wire::IpCidr; 1], + pub neighbor_cache: [Option<(smoltcp::wire::IpAddress, smoltcp::iface::Neighbor)>; 8], + pub routes_cache: [Option<(smoltcp::wire::IpCidr, smoltcp::iface::Route)>; 8], + pub tx_storage: [u8; 4096], + pub rx_storage: [u8; 1024], +} + +// Mutable reference to a singleton instance of NetStorage +fn net_store_as_mut() -> &'static mut NetStorage { + cortex_m::singleton!(: NetStorage = NetStorage { + // Placeholder for the real IP address, which is initialized at runtime. + ip_addrs: [smoltcp::wire::IpCidr::Ipv6( + smoltcp::wire::Ipv6Cidr::SOLICITED_NODE_PREFIX, + )], + neighbor_cache: [None; 8], + routes_cache: [None; 8], + socket_storage: [None; 1], + tx_storage: [0; 4096], + rx_storage: [0; 1024], + }) + .unwrap() +} + +pub fn setup( + mut enc424j600: Enc424j600, + settings: &BoosterSettings, + delay: &mut impl DelayUs, +) -> Ethernet { + use smoltcp as net; + + enc424j600.init(delay).expect("PHY initialization failed"); + // Overriding the MAC address stored on ENC424J600 is currently for consistency; + // the value stored on the chip is currently unused for transmitting packets. + enc424j600 + .write_mac_addr(settings.mac().as_bytes()) + .unwrap(); + + let net_store = net_store_as_mut(); + + let eth_iface = { + let device = enc424j600::smoltcp_phy::SmoltcpDevice::new(enc424j600); + + net_store.ip_addrs[0] = { + let ip = settings.ip().octets(); + let subnet = settings.subnet().octets(); + net::wire::IpCidr::new( + net::wire::IpAddress::from(net::wire::Ipv4Address::from_bytes(&ip)), + net::wire::IpAddress::from(net::wire::Ipv4Address::from_bytes(&subnet)) + .to_prefix_len() + .unwrap(), + ) + }; + + let routes = { + let gateway = net::wire::Ipv4Address::from_bytes(&settings.gateway().octets()); + let mut routes = net::iface::Routes::new(&mut net_store.routes_cache[..]); + routes.add_default_ipv4_route(gateway).unwrap(); + routes + }; + + let neighbor_cache = net::iface::NeighborCache::new(&mut net_store.neighbor_cache[..]); + + net::iface::EthernetInterfaceBuilder::new(device) + .ethernet_addr(net::wire::EthernetAddress::from_bytes( + settings.mac().as_bytes(), + )) + .neighbor_cache(neighbor_cache) + .ip_addrs(&mut net_store.ip_addrs[..]) + .routes(routes) + .finalize() + }; + + let sockets = { + let mut sockets = net::socket::SocketSet::new(&mut net_store.socket_storage[..]); + + let tcp_socket = { + let tx_buffer = net::socket::TcpSocketBuffer::new(&mut net_store.tx_storage[..]); + let rx_buffer = net::socket::TcpSocketBuffer::new(&mut net_store.rx_storage[..]); + + net::socket::TcpSocket::new(rx_buffer, tx_buffer) + }; + + sockets.add(tcp_socket); + sockets + }; + + Ethernet::new(eth_iface, sockets) +} + +/// Simple struct for implementing embedded_time::Clock +pub struct EpochClock { + /// Epoch time in milliseconds to store a greater value + epoch_time_ms: Milliseconds, + /// Epoch time in ticks to store a temporary value + epoch_time_ticks: Instant, +} + +impl EpochClock { + pub fn new() -> Self { + assert!(CPUFREQ == crate::CPU_FREQ); + + Self { + epoch_time_ms: Milliseconds::::new(0), + epoch_time_ticks: Instant::::new(0), + } + } + + /// Update the valid epoch time in milliseconds, and returns the value. + /// + /// Safe to call after RTIC #[init]. Returns Err() if DWT CYCCNT returns a + /// smaller value than the last recorded time. + pub fn now(&mut self) -> Result { + use clock::Clock; + use core::convert::TryInto; + + let now = self.try_now().unwrap(); + let elapsed: Milliseconds = now + .checked_duration_since(&self.epoch_time_ticks) + .unwrap() + .try_into() + .unwrap(); + self.epoch_time_ticks = now; + self.epoch_time_ms = self.epoch_time_ms + elapsed; + Ok(self.epoch_time_ms.integer()) + } +} + +/// Implement a simple embedded_time::clock::Clock at the given CPU clock frequency +/// based on DWT CYCCNT. +/// +/// This leverages "const generics" introduced in Rust 1.51. +impl clock::Clock for EpochClock { + type T = u32; + + const SCALING_FACTOR: Fraction = Fraction::new(1, CPUFREQ); + + /// Returns directly from DWT CYCCNT. Not guaranteed to be valid. + fn try_now(&self) -> Result, clock::Error> { + Ok(Instant::new(DWT::get_cycle_count())) + } +} diff --git a/src/main.rs b/src/main.rs index 612cb160..2c89ad88 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,13 @@ #![no_std] #![no_main] #![cfg_attr(feature = "unstable", feature(llvm_asm))] +#[cfg(not(any(feature = "phy_enc424j600", feature = "phy_w5500")))] +compile_error!( + "A least one PHY device must be enabled. Use a feature gate to + enable." +); +#[cfg(all(feature = "phy_enc424j600", feature = "phy_w5500"))] +compile_error!("Cannot enable multiple ethernet PHY devices."); #[macro_use] extern crate log; @@ -26,6 +33,8 @@ use usb_device::prelude::*; mod booster_channels; mod chassis_fans; mod delay; +#[cfg(feature = "phy_enc424j600")] +mod enc424j600_api; mod error; mod linear_transformation; mod logger; @@ -45,11 +54,15 @@ use logger::BufferedLog; use rf_channel::{AdcPin, AnalogPins as AdcPins, ChannelPins as RfChannelPins, ChannelState}; use serial_terminal::SerialTerminal; use settings::BoosterSettings; +#[cfg(feature = "phy_enc424j600")] +use smoltcp_nal::NetworkStack; use user_interface::{ButtonEvent, Color, UserButtons, UserLeds}; use watchdog::{WatchdogClient, WatchdogManager}; use rtic::cyccnt::Duration; +const CPU_FREQ: u32 = 168_000_000; + // Convenience type definition for the I2C bus used for booster RF channels. type I2C = hal::i2c::I2c< hal::stm32::I2C1, @@ -76,8 +89,16 @@ type SPI = hal::spi::Spi< ), >; +#[cfg(feature = "phy_w5500")] type Ethernet = w5500::Interface>, SPI>; +#[cfg(feature = "phy_enc424j600")] +type Enc424j600 = + enc424j600::Enc424j600>>; +#[cfg(feature = "phy_enc424j600")] +type Ethernet = NetworkStack<'static, 'static, enc424j600::smoltcp_phy::SmoltcpDevice>; +#[cfg(feature = "phy_enc424j600")] +type NalClock = enc424j600_api::EpochClock; type MqttClient = minimq::MqttClient; type I2cBusManager = mutex::AtomicCheckManager; @@ -151,6 +172,13 @@ pub struct MainBus { pub fans: ChassisFans, } +/// Container method for all Ethernet-related structs. +pub struct EthernetManager { + pub mqtt_client: MqttClient, + #[cfg(feature = "phy_enc424j600")] + pub nal_clock: NalClock, +} + #[rtic::app(device = stm32f4xx_hal::stm32, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)] const APP: () = { struct Resources { @@ -158,7 +186,7 @@ const APP: () = { buttons: UserButtons, leds: UserLeds, usb_terminal: SerialTerminal, - mqtt_client: MqttClient, + eth_mgr: EthernetManager, watchdog: WatchdogManager, identifier: String, delay: AsmDelay, @@ -185,7 +213,7 @@ const APP: () = { .cfgr .use_hse(8.mhz()) .sysclk(168.mhz()) - .hclk(168.mhz()) + .hclk(CPU_FREQ.hz()) .pclk1(42.mhz()) .require_pll48clk() .freeze(); @@ -355,59 +383,81 @@ const APP: () = { let identifier: String = String::from(settings.id()); - let mqtt_client = { - let interface = { - let spi = { - let sck = gpioa.pa5.into_alternate_af5(); - let miso = gpioa.pa6.into_alternate_af5(); - let mosi = gpioa.pa7.into_alternate_af5(); + let eth_mgr = { + let mqtt_client = { + let interface = { + let spi = { + let sck = gpioa.pa5.into_alternate_af5(); + let miso = gpioa.pa6.into_alternate_af5(); + let mosi = gpioa.pa7.into_alternate_af5(); + + let mode = hal::spi::Mode { + polarity: hal::spi::Polarity::IdleLow, + phase: hal::spi::Phase::CaptureOnFirstTransition, + }; + + hal::spi::Spi::spi1( + c.device.SPI1, + (sck, miso, mosi), + mode, + #[cfg(feature = "phy_w5500")] + 1.mhz().into(), + #[cfg(feature = "phy_enc424j600")] + 14.mhz().into(), + clocks, + ) + }; - let mode = hal::spi::Mode { - polarity: hal::spi::Polarity::IdleLow, - phase: hal::spi::Phase::CaptureOnFirstTransition, + let cs = { + let mut pin = gpioa.pa4.into_push_pull_output(); + pin.set_high().unwrap(); + pin }; - hal::spi::Spi::spi1( - c.device.SPI1, - (sck, miso, mosi), - mode, - 1.mhz().into(), - clocks, - ) - }; + #[cfg(feature = "phy_w5500")] + { + let mut w5500 = w5500::W5500::new( + spi, + cs, + w5500::OnWakeOnLan::Ignore, + w5500::OnPingRequest::Respond, + w5500::ConnectionType::Ethernet, + w5500::ArpResponses::Cache, + ) + .unwrap(); + + w5500.set_mac(settings.mac()).unwrap(); + + // Set default netmask and gateway. + w5500.set_gateway(settings.gateway()).unwrap(); + w5500.set_subnet(settings.subnet()).unwrap(); + w5500.set_ip(settings.ip()).unwrap(); + + w5500::Interface::new(w5500) + } - let cs = { - let mut pin = gpioa.pa4.into_push_pull_output(); - pin.set_high().unwrap(); - pin + #[cfg(feature = "phy_enc424j600")] + { + let enc424j600 = + Enc424j600::new(spi, cs).cpu_freq_mhz(CPU_FREQ / 1_000_000); + let interface = enc424j600_api::setup(enc424j600, &settings, &mut delay); + interface + } }; - let mut w5500 = w5500::W5500::new( - spi, - cs, - w5500::OnWakeOnLan::Ignore, - w5500::OnPingRequest::Respond, - w5500::ConnectionType::Ethernet, - w5500::ArpResponses::Cache, + minimq::MqttClient::::new( + minimq::embedded_nal::IpAddr::V4(settings.broker()), + settings.id(), + interface, ) - .unwrap(); - - w5500.set_mac(settings.mac()).unwrap(); - - // Set default netmask and gateway. - w5500.set_gateway(settings.gateway()).unwrap(); - w5500.set_subnet(settings.subnet()).unwrap(); - w5500.set_ip(settings.ip()).unwrap(); - - w5500::Interface::new(w5500) + .unwrap() }; - minimq::MqttClient::::new( - minimq::embedded_nal::IpAddr::V4(settings.broker()), - settings.id(), - interface, - ) - .unwrap() + EthernetManager { + mqtt_client, + #[cfg(feature = "phy_enc424j600")] + nal_clock: NalClock::new(), + } }; let mut fans = { @@ -444,7 +494,16 @@ const APP: () = { // Generate a device serial number from the MAC address. *USB_SERIAL = { let mut serial_string: String = String::new(); + + #[cfg(feature = "phy_w5500")] let octets = settings.mac().octets; + #[cfg(feature = "phy_enc424j600")] + let octets: [u8; 6] = { + let mut array = [0; 6]; + array.copy_from_slice(settings.mac().as_bytes()); + array + }; + write!( &mut serial_string, "{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}", @@ -484,7 +543,7 @@ const APP: () = { main_bus: MainBus { fans, channels }, buttons: buttons, leds: leds, - mqtt_client, + eth_mgr, usb_terminal, watchdog: watchdog_manager, identifier, @@ -550,7 +609,7 @@ const APP: () = { // TODO: Replace hard-coded CPU cycles here. // Schedule to run this task periodically at 10Hz. c.schedule - .channel_monitor(c.scheduled + Duration::from_cycles(168_000_000 / 10)) + .channel_monitor(c.scheduled + Duration::from_cycles(CPU_FREQ / 10)) .unwrap(); } @@ -584,11 +643,11 @@ const APP: () = { // TODO: Replace hard-coded CPU cycles here. // Schedule to run this task periodically at 1Hz. c.schedule - .fans(c.scheduled + Duration::from_cycles(168_000_000)) + .fans(c.scheduled + Duration::from_cycles(CPU_FREQ)) .unwrap(); } - #[task(priority = 1, schedule = [telemetry], resources=[main_bus, mqtt_client, watchdog, identifier])] + #[task(priority = 1, schedule = [telemetry], resources=[main_bus, eth_mgr, watchdog, identifier])] fn telemetry(mut c: telemetry::Context) { let id = &c.resources.identifier; @@ -613,7 +672,7 @@ const APP: () = { let message: String = serde_json_core::to_string(&measurements).unwrap(); - match c.resources.mqtt_client.publish( + match c.resources.eth_mgr.mqtt_client.publish( topic.as_str(), &message.into_bytes(), QoS::AtMostOnce, @@ -628,7 +687,7 @@ const APP: () = { // TODO: Replace hard-coded CPU cycles here. // Schedule to run this task periodically at 2Hz. c.schedule - .telemetry(c.scheduled + Duration::from_cycles(168_000_000 / 2)) + .telemetry(c.scheduled + Duration::from_cycles(CPU_FREQ / 2)) .unwrap(); } @@ -674,7 +733,7 @@ const APP: () = { // TODO: Replace hard-coded CPU cycles here. // Schedule to run this task every 3ms. c.schedule - .button(c.scheduled + Duration::from_cycles(3 * (168_000_000 / 1000))) + .button(c.scheduled + Duration::from_cycles(3 * (CPU_FREQ / 1000))) .unwrap(); } @@ -694,11 +753,11 @@ const APP: () = { // TODO: Replace hard-coded CPU cycles here. // Schedule to run this task every 10ms. c.schedule - .usb(c.scheduled + Duration::from_cycles(10 * (168_000_000 / 1_000))) + .usb(c.scheduled + Duration::from_cycles(10 * (CPU_FREQ / 1_000))) .unwrap(); } - #[idle(resources=[main_bus, mqtt_client, watchdog, identifier, delay])] + #[idle(resources=[main_bus, eth_mgr, watchdog, identifier, delay])] fn idle(mut c: idle::Context) -> ! { let mut manager = c .resources diff --git a/src/mqtt_control.rs b/src/mqtt_control.rs index dfe96f76..f974ce18 100644 --- a/src/mqtt_control.rs +++ b/src/mqtt_control.rs @@ -241,10 +241,20 @@ impl ControlState { /// * `resources` - The `idle` resources containing the client and RF channels. pub fn update(&mut self, resources: &mut Resources) { use rtic::Mutex as _; - // Subscribe to any control topics necessary. - if !self.subscribed { - resources.mqtt_client.lock(|client| { - if client.is_connected().unwrap() { + resources.eth_mgr.lock(|eth_mgr| { + // Update the NAL stack + #[cfg(feature = "phy_enc424j600")] + { + let now = eth_mgr.nal_clock.now().unwrap(); + // Note: smoltcp-nal 0.1.0 ONLY returns boolean, and does NOT + // raise errors from smoltcp. + // TODO: Bump smoltcp-nal + eth_mgr.mqtt_client.network_stack.poll(now); + } + + // Subscribe to any control topics necessary. + if !self.subscribed { + if eth_mgr.mqtt_client.is_connected().unwrap() { for topic in [ "channel/state", "channel/bias", @@ -253,67 +263,77 @@ impl ControlState { ] .iter() { - client + eth_mgr + .mqtt_client .subscribe(&self.generate_topic_string(topic), &[]) .unwrap(); } self.subscribed = true; } - }); - } + } + }); let main_bus = &mut resources.main_bus; let delay = &mut resources.delay; - resources.mqtt_client.lock(|client| { - match client.poll(|client, topic, message, properties| { - let (id, route) = topic.split_at(topic.find('/').unwrap()); - let route = &route[1..]; - - if id != self.id { - warn!("Ignoring topic for identifier: {}", id); - return; - } + resources.eth_mgr.lock(|eth_mgr| { + match eth_mgr + .mqtt_client + .poll(|client, topic, message, properties| { + let (id, route) = topic.split_at(topic.find('/').unwrap()); + let route = &route[1..]; - let response = main_bus.lock(|main_bus| match route { - "channel/state" => handle_channel_update(message, &mut main_bus.channels), - "channel/bias" => handle_channel_bias(message, &mut main_bus.channels, *delay), - "channel/read" => handle_channel_property_read(message, &mut main_bus.channels), - "channel/write" => { - handle_channel_property_write(message, &mut main_bus.channels) + if id != self.id { + warn!("Ignoring topic for identifier: {}", id); + return; } - _ => Response::error_msg("Unexpected topic"), - }); - if let Property::ResponseTopic(topic) = properties - .iter() - .find(|&prop| { - if let Property::ResponseTopic(_) = *prop { - true - } else { - false + let response = main_bus.lock(|main_bus| match route { + "channel/state" => handle_channel_update(message, &mut main_bus.channels), + "channel/bias" => { + handle_channel_bias(message, &mut main_bus.channels, *delay) } - }) - .or(Some(&Property::ResponseTopic( - &self.generate_topic_string("log"), - ))) - .unwrap() - { - client - .publish(topic, &response.into_bytes(), QoS::AtMostOnce, &[]) - .unwrap(); - } - }) { + "channel/read" => { + handle_channel_property_read(message, &mut main_bus.channels) + } + "channel/write" => { + handle_channel_property_write(message, &mut main_bus.channels) + } + _ => Response::error_msg("Unexpected topic"), + }); + + if let Property::ResponseTopic(topic) = properties + .iter() + .find(|&prop| { + if let Property::ResponseTopic(_) = *prop { + true + } else { + false + } + }) + .or(Some(&Property::ResponseTopic( + &self.generate_topic_string("log"), + ))) + .unwrap() + { + client + .publish(topic, &response.into_bytes(), QoS::AtMostOnce, &[]) + .unwrap(); + } + }) { Ok(_) => {} - // Whenever MQTT disconnects, we will lose our pending subscriptions. We will need - // to re-establish them once we reconnect. - Err(minimq::Error::Disconnected) => self.subscribed = false, + // Whenever the MQTT broker stops maintaining the session, + // this MQTT client will reset the session, + // and we will lose our pending subscriptions. + // We will need to re-establish them once we reconnect. + Err(minimq::Error::SessionReset) => self.subscribed = false, // Note: There's a race condition where the W5500 may disconnect the socket // immediately before Minimq tries to use it. In these cases, a NotReady error is // returned to indicate the socket is no longer connected. On the next processing // cycle of Minimq, the device should detect and handle the broker disconnection. + #[cfg(feature = "phy_w5500")] Err(minimq::Error::Network(w5500::Error::NotReady)) => {} Err(e) => error!("Unexpected error: {:?}", e), diff --git a/src/serial_terminal.rs b/src/serial_terminal.rs index 0b5aefc8..ce413097 100644 --- a/src/serial_terminal.rs +++ b/src/serial_terminal.rs @@ -11,7 +11,7 @@ use logos::Logos; use usbd_serial::UsbError; use core::fmt::Write; -use w5500::Ipv4Addr; +use minimq::embedded_nal::Ipv4Addr; #[derive(Logos)] enum Token { diff --git a/src/settings/global_settings.rs b/src/settings/global_settings.rs index 73b59b49..d01ab54d 100644 --- a/src/settings/global_settings.rs +++ b/src/settings/global_settings.rs @@ -6,8 +6,14 @@ //! Proprietary and confidential. use crate::{Eeprom, Error}; use heapless::{consts, String}; +use minimq::embedded_nal::Ipv4Addr; use serde::{Deserialize, Serialize}; -use w5500::{Ipv4Addr, MacAddress}; + +#[cfg(feature = "phy_w5500")] +use w5500::MacAddress; + +#[cfg(feature = "phy_enc424j600")] +use smoltcp_nal::smoltcp::wire::EthernetAddress as MacAddress; use super::{SemVersion, SinaraBoardId, SinaraConfiguration}; @@ -255,7 +261,13 @@ impl BoosterSettings { /// Get the Booster MAC address. pub fn mac(&self) -> MacAddress { - MacAddress::from_bytes(self.eui48) + #[cfg(feature = "phy_w5500")] + { + MacAddress::from_bytes(self.eui48) + } + + #[cfg(feature = "phy_enc424j600")] + MacAddress::from_bytes(&self.eui48) } /// Get the Booster IP address.