diff --git a/Cargo.lock b/Cargo.lock index 5d87484..a1825e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -373,6 +373,7 @@ dependencies = [ "cortex-m-rt", "cortex-m-semihosting 0.5.0", "fugit", + "ieee802_3_miim", "log", "miniconf", "nb 1.1.0", diff --git a/Cargo.toml b/Cargo.toml index 2ff4f8f..d60fb8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,8 @@ bare-metal = "1" nb = "1" cortex-m-log = { version = "0.7.0", features = ["log-integration", "semihosting"] } stm32f4xx-hal = { version = "0.14.0", features = ["rt", "stm32f407", "usb_fs"] } -stm32-eth = { version = "0.5.2", features = ["stm32f407"] } +stm32-eth = { version = "0.5.2", features = ["stm32f407", "smoltcp-phy", "smoltcp"] } +ieee802_3_miim = "0.8.0" smoltcp = { version = "0.10.0", default-features = false, features = ["proto-ipv4", "socket-tcp", "log", "medium-ethernet"] } uom = { version = "0.30", default-features = false, features = ["autoconvert", "si", "f64", "use_serde"] } num-traits = { version = "0.2.15", default-features = false, features = ["libm"] } diff --git a/echo_eth_test.py b/echo_eth_test.py new file mode 100644 index 0000000..26be4c2 --- /dev/null +++ b/echo_eth_test.py @@ -0,0 +1,14 @@ +# echo-client.py + +import socket + +#192, 168, 1, 132 +HOST = "192.168.1.132" # The server's hostname or IP address +PORT = 1337 # The port used by the server + +with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.connect((HOST, PORT)) + s.sendall(b"Hello, world") + data = s.recv(1024) + +print(f"Received {data!r}") diff --git a/src/device/boot.rs b/src/device/boot.rs index f9090c8..9823fbd 100644 --- a/src/device/boot.rs +++ b/src/device/boot.rs @@ -1,9 +1,12 @@ use super::{gpio, sys_timer, usb}; use crate::device::flash_store::{self, FlashStore}; +use crate::device::sys_timer::sleep; use crate::laser_diode::ld_ctrl::{*}; use crate::laser_diode::laser_diode::LdDrive; use crate::thermostat::max1968::MAX1968; use crate::thermostat::thermostat::Thermostat; +use crate::net::net::ServerHandle; +use stm32_eth; use fugit::ExtU32; use log::info; use stm32f4xx_hal::{ @@ -41,7 +44,7 @@ pub fn bootup( sys_timer::setup(core_perif.SYST, clocks); - let (_eth_pins, usb, current_source_phy, ad7172_phy, max1968_phy, pd_mon_phy) = gpio::setup( + let (eth_pins, eth_mgmt_pins, usb, current_source_phy, ad7172_phy, max1968_phy, pd_mon_phy) = gpio::setup( clocks, perif.TIM4, perif.GPIOA, @@ -59,8 +62,21 @@ pub fn bootup( usb::State::setup(usb); + let ethernet_parts_in = stm32_eth::PartsIn { + dma: perif.ETHERNET_DMA, + mac: perif.ETHERNET_MAC, + mmc: perif.ETHERNET_MMC, + ptp: perif.ETHERNET_PTP, + }; + + sleep(1000); + + info!("Setting up ETH"); + ServerHandle::new(eth_pins, eth_mgmt_pins, ethernet_parts_in, clocks); + info!("Setting finish setting up ETH"); + let current_source = LdCtrl::new(current_source_phy); - + info!("Setting up Laser"); let mut laser = LdDrive::new(current_source, perif.ADC2, pd_mon_phy); laser.setup(); laser.ld_open(); @@ -69,6 +85,7 @@ pub fn bootup( laser.set_pd_i_limit(ElectricCurrent::new::(2.5)); laser.power_up(); + info!("Setting up TEC"); let tec_driver = MAX1968::new(max1968_phy, perif.ADC1); let mut thermostat = Thermostat::new(tec_driver, ad7172_phy); diff --git a/src/device/gpio.rs b/src/device/gpio.rs index 4399540..ba3bb1c 100644 --- a/src/device/gpio.rs +++ b/src/device/gpio.rs @@ -4,9 +4,10 @@ use crate::laser_diode::ld_pwr_exc_protector::LdPwrExcProtectorPhy; use crate::thermostat::ad5680; use crate::thermostat::max1968::{self, MAX1968PinSet, MAX1968Phy, PWM_FREQ_KHZ}; use crate::thermostat::ad7172; +use crate::net::net::EthernetMgmtPins; use stm32_eth::EthPins; use stm32f4xx_hal::{ - gpio::{gpioa::*, gpiob::*, gpioc::*, GpioExt, Input}, + gpio::{gpioa::*, gpiob::*, gpioc::*, GpioExt, Input, Speed}, otg_fs::USB, pac::{ GPIOA, GPIOB, GPIOC, GPIOD, GPIOG, OTG_FS_DEVICE, OTG_FS_GLOBAL, OTG_FS_PWRCLK, SPI1, SPI2, SPI3, @@ -36,6 +37,7 @@ pub fn setup( otg_fs_pwrclk: OTG_FS_PWRCLK, ) -> ( EthernetPins, + EthernetMgmtPins, USB, LdCtrlPhy, ad7172::AdcPhy, @@ -66,6 +68,13 @@ pub fn setup( rx_d0: gpioc.pc4, rx_d1: gpioc.pc5, }; + + let mut eth_mgmt_pins = EthernetMgmtPins { + mdio: gpioa.pa2.into_alternate::<11>(), + mdc: gpioc.pc1.into_alternate::<11>(), + }; + eth_mgmt_pins.mdio.set_speed(Speed::VeryHigh); + eth_mgmt_pins.mdc.set_speed(Speed::VeryHigh); let current_source_phy = LdCtrlPhy { dac: max5719::Dac::new(Spi::new( @@ -137,5 +146,5 @@ pub fn setup( gpioa.pa15.into_push_pull_output(), ).unwrap(); - (eth_pins, usb, current_source_phy, ad7172_phy, max1968_phy, pd_mon_phy) + (eth_pins, eth_mgmt_pins, usb, current_source_phy, ad7172_phy, max1968_phy, pd_mon_phy) } diff --git a/src/main.rs b/src/main.rs index aec093a..4d8cd25 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ mod device; mod laser_diode; mod thermostat; mod pid; +mod net; use device::{boot::bootup, log_setup, sys_timer}; use uom::fmt::DisplayStyle::Abbreviation; diff --git a/src/net/mod.rs b/src/net/mod.rs new file mode 100644 index 0000000..36b1010 --- /dev/null +++ b/src/net/mod.rs @@ -0,0 +1 @@ +pub mod net; \ No newline at end of file diff --git a/src/net/net.rs b/src/net/net.rs new file mode 100644 index 0000000..4599cfc --- /dev/null +++ b/src/net/net.rs @@ -0,0 +1,289 @@ +use crate::device::sys_timer; +use log::{debug, info, warn}; +use smoltcp::{ + iface::{ + self, Interface, SocketHandle, SocketSet, SocketStorage + }, + socket::tcp::{State, SocketBuffer, Socket}, + wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address, Ipv4Cidr}, + time::Instant, +}; +use stm32_eth::{ + Parts, EthPins, PartsIn, + dma::{ + TxRingEntry, RxRingEntry, EthernetDMA + }}; +use stm32f4xx_hal::{ + gpio::{gpioa::*, gpiob::*, gpioc::*, Alternate, Input}, + rcc::Clocks, + interrupt, +}; + +const IPV4_ADDR: (u8, u8, u8, u8) = (192, 168, 1, 132); +const IP_INIT: IpCidr = IpCidr::Ipv4(Ipv4Cidr::new( + Ipv4Address::new(IPV4_ADDR.0, IPV4_ADDR.1, IPV4_ADDR.2, IPV4_ADDR.3), + 24, +)); +const ADDRESS: (IpAddress, u16) = ( + IpAddress::Ipv4(Ipv4Address::new( + IPV4_ADDR.0, + IPV4_ADDR.1, + IPV4_ADDR.2, + IPV4_ADDR.3, + )), + 1337, +); +const MAC: [u8; 6] = [0x02, 0x5f, 0x25, 0x37, 0x93, 0x0e]; + +pub struct ServerHandle { + socket_handle: SocketHandle, + socket_set: SocketSet<'static>, + iface: EthInterface, + dma: EthernetDMA<'static, 'static>, +} +pub type EthernetPins = + EthPins, PA7, PB11, PB12, PB13, PC4, PC5>; +pub struct EthernetMgmtPins { + pub mdio: PA2>, + pub mdc: PC1>, +} +pub type EthInterface = Interface; + +static mut RX_RING: Option<[RxRingEntry; 8]> = None; +static mut TX_RING: Option<[TxRingEntry; 2]> = None; +static mut SOCKET_STORAGE: Option<[SocketStorage<'static>; 1]> = None; + +static mut TCP_SOCKET_STORAGE : Option = None; + +#[derive(Copy, Clone)] +pub struct TcpSocketStorage { + rx_storage: [u8; 2048], + tx_storage: [u8; 2048], +} + +impl TcpSocketStorage { + const fn new() -> Self { + Self { + rx_storage: [0; 2048], + tx_storage: [0; 2048], + } + } +} + +fn now_fn() -> smoltcp::time::Instant { + Instant::from_millis(i64::from(sys_timer::now())) +} + +static mut SERVER_HANDLE : Option = None; + +impl ServerHandle { + pub fn new (eth_pins: EthernetPins, + eth_mgmt_pins: EthernetMgmtPins, + ethernet_parts_in: PartsIn, + clocks: Clocks, + ) { + let rx_ring = unsafe { RX_RING.get_or_insert(Default::default()) }; + let tx_ring = unsafe { TX_RING.get_or_insert(Default::default()) }; + let tcp_socket_storage = unsafe { TCP_SOCKET_STORAGE.get_or_insert(TcpSocketStorage::new()) }; + let socket_storage = unsafe { SOCKET_STORAGE.get_or_insert([SocketStorage::EMPTY; 1]) }; + + let Parts { + mut dma, + mac, + #[cfg(feature = "ptp")] + .. + } = stm32_eth::new_with_mii( + ethernet_parts_in, + &mut rx_ring[..], + &mut tx_ring[..], + clocks, + eth_pins, + eth_mgmt_pins.mdio, + eth_mgmt_pins.mdc + ).unwrap(); + + let mut routes = smoltcp::iface::Routes::new(); + routes + .add_default_ipv4_route(Ipv4Address::new(192, 168, 1, 1)) + .ok(); + dma.enable_interrupt(); + + let rx_buffer = SocketBuffer::new(&mut tcp_socket_storage.rx_storage[..]); + let tx_buffer = SocketBuffer::new(&mut tcp_socket_storage.tx_storage[..]); + let socket = Socket::new(rx_buffer, tx_buffer); + + let config = iface::Config::new(EthernetAddress::from_bytes(&MAC).into()); + let mut iface = Interface::new(config, &mut &mut dma, smoltcp::time::Instant::ZERO); + iface.set_hardware_addr(EthernetAddress(MAC).into()); + + iface.update_ip_addrs(|addr| { + addr.push(IP_INIT).unwrap(); + }); + + let mut sockets = SocketSet::new(&mut socket_storage[..]); + let tcp_handle = sockets.add(socket); + let socket = sockets.get_mut::(tcp_handle); + socket.listen(ADDRESS).ok(); + iface.poll(Instant::from_millis(i64::from(sys_timer::now())), &mut &mut dma, &mut sockets); + + let server = ServerHandle { + socket_handle: tcp_handle, + socket_set: sockets, + iface: iface, + dma: dma, + }; + + if let Ok(mut phy) = EthernetPhy::from_miim(mac, 0) { + info!( + "Resetting PHY as an extra step. Type: {}", + phy.ident_string() + ); + + phy.phy_init(); + } else { + info!("Not resetting unsupported PHY."); + } + + unsafe { + SERVER_HANDLE = Some(server); + } + } + + pub fn poll(&mut self, buffer: &mut [u8]) { + self.iface.poll(now_fn(), &mut &mut self.dma, &mut self.socket_set); + let socket = self.socket_set.get_mut::(self.socket_handle); + + if let Ok(recv_bytes) = socket.recv_slice(buffer) { + if recv_bytes > 0 { + socket.send_slice(&buffer[..recv_bytes]).ok(); + info!("Echoed {} bytes.", recv_bytes); + } + } + + if !socket.is_listening() && !socket.is_open() || socket.state() == State::CloseWait { + socket.abort(); + socket.listen(ADDRESS).ok(); + warn!("Disconnected... Reopening listening socket."); + } + + self.iface.poll(now_fn(), &mut &mut self.dma, &mut self.socket_set); + } +} + +use ieee802_3_miim::{ + phy::{ + lan87xxa::{LAN8720A, LAN8742A}, + BarePhy, KSZ8081R, + }, + Miim, Pause, Phy, +}; + +/// An ethernet PHY +pub enum EthernetPhy { + /// LAN8720A + LAN8720A(LAN8720A), + /// LAN8742A + LAN8742A(LAN8742A), + /// KSZ8081R + KSZ8081R(KSZ8081R), +} + +impl Phy for EthernetPhy { + fn best_supported_advertisement(&self) -> ieee802_3_miim::AutoNegotiationAdvertisement { + unimplemented!() + } + + fn get_miim(&mut self) -> &mut M { + match self { + EthernetPhy::LAN8720A(phy) => phy.get_miim(), + EthernetPhy::LAN8742A(phy) => phy.get_miim(), + EthernetPhy::KSZ8081R(phy) => phy.get_miim(), + } + } + + fn get_phy_addr(&self) -> u8 { + match self { + EthernetPhy::LAN8720A(phy) => phy.get_phy_addr(), + EthernetPhy::LAN8742A(phy) => phy.get_phy_addr(), + EthernetPhy::KSZ8081R(phy) => phy.get_phy_addr(), + } + } +} + +impl EthernetPhy { + /// Attempt to create one of the known PHYs from the given + /// MIIM. + /// + /// Returns an error if the PHY does not support the extended register + /// set, or if the PHY's identifier does not correspond to a known PHY. + pub fn from_miim(miim: M, phy_addr: u8) -> Result { + let mut bare = BarePhy::new(miim, phy_addr, Pause::NoPause); + let phy_ident = if let Some(id) = bare.phy_ident() { + id.raw_u32() + } else { + return Err(bare.release()); + }; + let miim = bare.release(); + match phy_ident & 0xFFFFFFF0 { + 0x0007C0F0 => Ok(Self::LAN8720A(LAN8720A::new(miim, phy_addr))), + 0x0007C130 => Ok(Self::LAN8742A(LAN8742A::new(miim, phy_addr))), + 0x00221560 => Ok(Self::KSZ8081R(KSZ8081R::new(miim, phy_addr))), + _ => Err(miim), + } + } + + /// Get a string describing the type of PHY + pub const fn ident_string(&self) -> &'static str { + match self { + EthernetPhy::LAN8720A(_) => "LAN8720A", + EthernetPhy::LAN8742A(_) => "LAN8742A", + EthernetPhy::KSZ8081R(_) => "KSZ8081R", + } + } + + /// Initialize the PHY + pub fn phy_init(&mut self) { + match self { + EthernetPhy::LAN8720A(phy) => phy.phy_init(), + EthernetPhy::LAN8742A(phy) => phy.phy_init(), + EthernetPhy::KSZ8081R(phy) => { + phy.set_autonegotiation_advertisement(phy.best_supported_advertisement()); + } + } + } + + #[allow(dead_code)] + pub fn speed(&mut self) -> Option { + match self { + EthernetPhy::LAN8720A(phy) => phy.link_speed(), + EthernetPhy::LAN8742A(phy) => phy.link_speed(), + EthernetPhy::KSZ8081R(phy) => phy.link_speed(), + } + } + + #[allow(dead_code)] + pub fn release(self) -> M { + match self { + EthernetPhy::LAN8720A(phy) => phy.release(), + EthernetPhy::LAN8742A(phy) => phy.release(), + EthernetPhy::KSZ8081R(phy) => phy.release(), + } + } +} + +/// Potentially wake up from `wfi()`, set the interrupt pending flag, +/// clear interrupt flags. +#[interrupt] +fn ETH() { + let interrupt_reason = stm32_eth::eth_interrupt_handler(); + debug!("Ethernet Interrupt{:?}", interrupt_reason); + unsafe{ + if let Some(ref mut server_handle ) = SERVER_HANDLE { + let mut data : [u8; 512] = [0u8; 512]; + server_handle.poll(&mut data); + } + else { + panic!("Interrupt is called before init"); + } + } +}