Initializing and polling the Ethernet peripheral

At this point, we know that the devkit is able to run our code, but it doesn't yet do anything network related, so let's change that.

First, we need to initialize the Ethernet peripheral driver from the HAL.

#![allow(unused)]
fn main() {
    let (_eth_dma, eth_mac) = ethernet::new(
        dp.ETHERNET_MAC,
        dp.ETHERNET_MTL,
        dp.ETHERNET_DMA,
        gpio.eth_pins,
        unsafe { liltcp::take_des_ring() },
        liltcp::MAC,
        ccdr.peripheral.ETH1MAC,
        &ccdr.clocks,
    );

    let mut lan8742a = ethernet::phy::LAN8742A::new(eth_mac.set_phy_addr(0));
    lan8742a.phy_reset();
    lan8742a.phy_init();
}

The initialization itself is pretty bare, the only remotely interesting part is the initialization of the PHY on the address 0.

The ethernet peripheral internally sets up DMA for receiving and transmitting data and lets the user know that something happened using an interrupt handler.

#![allow(unused)]
fn main() {
#[cortex_m_rt::interrupt]
fn ETH() {
    unsafe {
        ethernet::interrupt_handler();
    }
}
}

The interrupt must also be enabled in NVIC, which is done using the following function, called just before lilos spawns tasks.

#![allow(unused)]
fn main() {
pub unsafe fn enable_eth_interrupt(nvic: &mut pac::NVIC) {
    ethernet::enable_interrupt();
    nvic.set_priority(stm32h7xx_hal::stm32::Interrupt::ETH, NVIC_BASEPRI - 1);
    cortex_m::peripheral::NVIC::unmask(stm32h7xx_hal::stm32::Interrupt::ETH);
}
}

Once this is done, the peripheral is ready to send and receive data. That, however, is a topic for the next chapter. For now, we only want to check if the link is up. This is done by polling the PHY. Let's now add a new async task, which will periodically poll the PHY and print the link state on change. To also see the link state on the devkit, let's also turn the LED on, when the link is UP.

#![allow(unused)]
fn main() {
// Periodically poll if the link is up or down
async fn poll_link<MAC: StationManagement>(
    mut phy: LAN8742A<MAC>,
    mut link_led: ErasedPin<Output>,
) -> Infallible {
    let mut gate = PeriodicGate::from(Millis(1000));
    let mut eth_up = false;
    loop {
        gate.next_time().await;

        let eth_last = eth_up;
        eth_up = phy.poll_link();

        link_led.set_state(eth_up.into());

        if eth_up != eth_last {
            if eth_up {
                defmt::info!("UP");
            } else {
                defmt::info!("DOWN");
            }
        }
    }
}
}

The final thing left to do is to spawn the task and run the binary on our devkit.

#![allow(unused)]
fn main() {
    unsafe {
        liltcp::enable_eth_interrupt(&mut cp.NVIC);

        lilos::exec::run_tasks_with_preemption(
            &mut [
                core::pin::pin!(liltcp::led_task(gpio.led)),
                core::pin::pin!(poll_link(lan8742a, gpio.link_led)),
            ],
            lilos::exec::ALL_TASKS,
            Interrupts::Filtered(liltcp::NVIC_BASEPRI),
        );
    }
}

When you plug in an Ethernet cable, there should be a log visible in the terminal and an LED should light up.

We are now ready to move on to actually receiving and transmitting data via the Ethernet.