From 892b039ef3ef608e92283076c12541cb9ff90db5 Mon Sep 17 00:00:00 2001 From: Henrik Hose Date: Tue, 28 Jul 2026 15:28:21 +0200 Subject: [PATCH 1/3] [stm32] H7 ethernet lan8742 freertos support --- ext/aws/modm_lan8742a.cpp | 656 ++++++++++++++++++ ext/aws/module.lb | 23 + src/modm/driver/ethernet/lan8742a.hpp | 62 ++ src/modm/driver/ethernet/lan8742a.lb | 29 + .../eth/stm32/{eth.hpp => eth.hpp.in} | 347 ++++++++- .../stm32/{eth_impl.hpp => eth_impl.hpp.in} | 108 +++ src/modm/platform/eth/stm32/module.lb | 14 +- 7 files changed, 1220 insertions(+), 19 deletions(-) create mode 100644 ext/aws/modm_lan8742a.cpp create mode 100644 src/modm/driver/ethernet/lan8742a.hpp create mode 100644 src/modm/driver/ethernet/lan8742a.lb rename src/modm/platform/eth/stm32/{eth.hpp => eth.hpp.in} (65%) rename src/modm/platform/eth/stm32/{eth_impl.hpp => eth_impl.hpp.in} (55%) diff --git a/ext/aws/modm_lan8742a.cpp b/ext/aws/modm_lan8742a.cpp new file mode 100644 index 0000000000..080e3f5320 --- /dev/null +++ b/ext/aws/modm_lan8742a.cpp @@ -0,0 +1,656 @@ +/* + * Copyright (c) 2026, Henrik Hose + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include +#include +#include + +#include +#include +#include + +#include "FreeRTOS_ARP.h" +#include "FreeRTOS_DNS.h" +#include "FreeRTOS_IP.h" +#include "FreeRTOS_IP_Private.h" +#include "FreeRTOS_Sockets.h" +#include "NetworkBufferManagement.h" + +using EMAC = modm::platform::Eth; + +namespace modm +{ + +struct ethernet +{ + static constexpr BaseType_t MAX_PACKET_SIZE{1536}; + static constexpr BaseType_t RX_BUFFER_SIZE{1536}; + static constexpr BaseType_t TX_BUFFER_SIZE{1536}; + static constexpr BaseType_t RX_BUFFER_NUMBER{5}; + static constexpr BaseType_t TX_BUFFER_NUMBER{5}; + + static constexpr configSTACK_DEPTH_TYPE emacTaskStackDepth{configMINIMAL_STACK_SIZE * 2}; + static constexpr UBaseType_t emacTaskPriority{configMAX_PRIORITIES - 1}; + + enum class InitStatus : uint8_t + { + Init, + Pass, + Failed + }; + + enum class TxDescriptor2 : uint32_t + { + InterruptOnCompletion = modm::Bit31, + }; + MODM_FLAGS32(TxDescriptor2); + + enum class TxDescriptor3 : uint32_t + { + DmaOwned = modm::Bit31, + FirstSegment = modm::Bit29, + LastSegment = modm::Bit28, + ChecksumFull = modm::Bit17 | modm::Bit16, + }; + MODM_FLAGS32(TxDescriptor3); + + enum class RxDescriptorReady : uint32_t + { + DmaOwned = modm::Bit31, + InterruptOnCompletion = modm::Bit30, + Buffer1Valid = modm::Bit24, + }; + MODM_FLAGS32(RxDescriptorReady); + + enum class RxDescriptorStatus : uint32_t + { + DmaOwned = modm::Bit31, + FirstSegment = modm::Bit29, + LastSegment = modm::Bit28, + ErrorSummary = modm::Bit15, + }; + MODM_FLAGS32(RxDescriptorStatus); + + static constexpr uint32_t Buffer1LengthMask{0x00003fff}; + static constexpr uint32_t Buffer2LengthMask{0x3fff0000}; + static constexpr uint32_t FrameLengthMask{0x00007fff}; + + struct DmaDescriptor + { + __IO uint32_t DESC0; + __IO uint32_t DESC1; + __IO uint32_t DESC2; + __IO uint32_t DESC3; + uint32_t BackupAddr0; + uint32_t BackupAddr1; + }; + using DmaDescriptor_t = DmaDescriptor; + + static InitStatus initStatus; + static SemaphoreHandle_t txDescriptorSemaphore; + static TaskHandle_t emacTaskHandle; + + static modm::platform::eth::Event_t isrEvent; + + static TimeOut_t phyLinkStatusTimer; + static constexpr TickType_t PhyLinkStatusHighMs{pdMS_TO_TICKS(2'000)}; + static constexpr TickType_t PhyLinkStatusLowMs{pdMS_TO_TICKS(1'000)}; + static TickType_t phyLinkStatusRemaining; + static modm::platform::eth::LinkStatus lastPhyLinkStatus; + + modm_aligned(32) + modm_section(".bss_d2_sram1") static DmaDescriptor_t DmaRxDescriptorTable[RX_BUFFER_NUMBER]; + + modm_aligned(32) + modm_section(".bss_d2_sram1") static DmaDescriptor_t DmaTxDescriptorTable[TX_BUFFER_NUMBER]; + + modm_aligned(32) + modm_section(".bss_d2_sram1") static uint8_t RxBuffers[RX_BUFFER_NUMBER][RX_BUFFER_SIZE]; + + modm_aligned(32) + modm_section(".bss_d2_sram2") static uint8_t TxBuffers[TX_BUFFER_NUMBER][TX_BUFFER_SIZE]; + + static DmaDescriptor_t *RxDescriptor; + static DmaDescriptor_t *TxDescriptor; + static DmaDescriptor_t *DmaTxDescriptorToClear; + + static constexpr uintptr_t CacheLineSize{32}; + + static bool + isDCacheEnabled() + { +#if (__DCACHE_PRESENT == 1U) + return (SCB->CCR & SCB_CCR_DC_Msk) != 0; +#else + return false; +#endif + } + + static void + cleanDCache(const void *address, std::size_t size) + { +#if (__DCACHE_PRESENT == 1U) + if (not isDCacheEnabled() or address == nullptr or size == 0) return; + + const uintptr_t start = uintptr_t(address) & ~(CacheLineSize - 1); + const uintptr_t end = + (uintptr_t(address) + size + CacheLineSize - 1) & ~(CacheLineSize - 1); + SCB_CleanDCache_by_Addr(reinterpret_cast(start), end - start); +#else + (void)address; + (void)size; +#endif + } + + static void + invalidateDCache(const void *address, std::size_t size) + { +#if (__DCACHE_PRESENT == 1U) + if (not isDCacheEnabled() or address == nullptr or size == 0) return; + + const uintptr_t start = uintptr_t(address) & ~(CacheLineSize - 1); + const uintptr_t end = + (uintptr_t(address) + size + CacheLineSize - 1) & ~(CacheLineSize - 1); + SCB_InvalidateDCache_by_Addr(reinterpret_cast(start), end - start); +#else + (void)address; + (void)size; +#endif + } + + static void + DMATxDescListInit() + { + std::memset(DmaTxDescriptorTable, 0, sizeof(DmaTxDescriptorTable)); + cleanDCache(DmaTxDescriptorTable, sizeof(DmaTxDescriptorTable)); + EMAC::setDmaTxDescriptorTable(uint32_t(TxDescriptor), TX_BUFFER_NUMBER); + } + + static void + DMARxDescListInit() + { + ETH->DMACRCR = (ETH->DMACRCR & ~ETH_DMACRCR_RBSZ) | + ((uint32_t(RX_BUFFER_SIZE) << ETH_DMACRCR_RBSZ_Pos) & ETH_DMACRCR_RBSZ); + + for (BaseType_t index = 0; index < RX_BUFFER_NUMBER; ++index) + { + auto &descriptor = RxDescriptor[index]; + std::memset(&descriptor, 0, sizeof(descriptor)); + descriptor.BackupAddr0 = uint32_t(RxBuffers[index]); + descriptor.DESC0 = uint32_t(RxBuffers[index]); + descriptor.DESC3 = RxDescriptorReady_t(RxDescriptorReady::DmaOwned | + RxDescriptorReady::InterruptOnCompletion | + RxDescriptorReady::Buffer1Valid) + .value; + + cleanDCache(RxBuffers[index], sizeof(RxBuffers[index])); + cleanDCache(&descriptor, sizeof(descriptor)); + } + + EMAC::setDmaRxDescriptorTable(uint32_t(RxDescriptor), RX_BUFFER_NUMBER); + __DMB(); + ETH->DMACRDTPR = uint32_t(&RxDescriptor[RX_BUFFER_NUMBER - 1]); + } + + static void + clearTxBuffers() + { + const std::size_t count = TX_BUFFER_NUMBER - uxSemaphoreGetCount(txDescriptorSemaphore); + + for (std::size_t index = 0; index < count; ++index) + { + invalidateDCache(DmaTxDescriptorToClear, sizeof(*DmaTxDescriptorToClear)); + if ((DmaTxDescriptorToClear->DESC3 & uint32_t(TxDescriptor3::DmaOwned)) != 0) break; + + DmaTxDescriptorToClear->DESC0 = 0; + DmaTxDescriptorToClear->DESC1 = 0; + DmaTxDescriptorToClear->DESC2 = 0; + DmaTxDescriptorToClear->DESC3 = 0; + DmaTxDescriptorToClear->BackupAddr0 = 0; + DmaTxDescriptorToClear->BackupAddr1 = 0; + + if (++DmaTxDescriptorToClear == &DmaTxDescriptorTable[TX_BUFFER_NUMBER]) + DmaTxDescriptorToClear = DmaTxDescriptorTable; + + xSemaphoreGive(txDescriptorSemaphore); + } + } + + static bool + mayAcceptPacket(uint8_t *buffer) + { + const auto *protocolPacket = reinterpret_cast(buffer); + + switch (protocolPacket->xTCPPacket.xEthernetHeader.usFrameType) + { + case ipARP_FRAME_TYPE: + return true; + case ipIPv4_FRAME_TYPE: + break; + default: + return false; + } + +#if ipconfigETHERNET_DRIVER_FILTERS_PACKETS == 1 + static constexpr uint16_t ipFragmentOffsetBitMask{0x0fff}; + const auto *ipHeader = &(protocolPacket->xTCPPacket.xIPHeader); + const uint16_t fragmentOffset = + FreeRTOS_ntohs(ipHeader->usFragmentOffset) & ipFragmentOffsetBitMask; + if (fragmentOffset != 0) return false; + + if (ipHeader->ucVersionHeaderLength < 0x45 or ipHeader->ucVersionHeaderLength > 0x4f) + return false; + + const uint32_t destIpAddress = ipHeader->ulDestinationIPAddress; + if (destIpAddress != *ipLOCAL_IP_ADDRESS_POINTER and + (FreeRTOS_ntohl(destIpAddress) & 0xff) != 0xff and *ipLOCAL_IP_ADDRESS_POINTER) + { + return false; + } + + if (ipHeader->ucProtocol == ipPROTOCOL_UDP) + { + const uint16_t sourcePort = + FreeRTOS_ntohs(protocolPacket->xUDPPacket.xUDPHeader.usSourcePort); + const uint16_t destPort = + FreeRTOS_ntohs(protocolPacket->xUDPPacket.xUDPHeader.usDestinationPort); + + if (not xPortHasUDPSocket(destPort) and sourcePort != ipDNS_PORT) return false; + } +#endif + + return true; + } + + static void + passMessage(NetworkBufferDescriptor_t *descriptor) + { + IPStackEvent_t rxEvent{.eEventType = eNetworkRxEvent, + .pvData = reinterpret_cast(descriptor)}; + + if (xSendEventStructToIPTask(&rxEvent, TickType_t(1000)) != pdPASS) + { + do + { + NetworkBufferDescriptor_t *next = descriptor->pxNextBuffer; + vReleaseNetworkBufferAndDescriptor(descriptor); + descriptor = next; + } while (descriptor); + + iptraceETHERNET_RX_EVENT_LOST(); + } else + { + iptraceNETWORK_INTERFACE_RECEIVE(); + } + } + + static bool + emacInterfaceInput() + { + static constexpr TickType_t descriptorWaitTime{pdMS_TO_TICKS(250)}; + + NetworkBufferDescriptor_t *currentDescriptor{nullptr}; + NetworkBufferDescriptor_t *newDescriptor{nullptr}; + NetworkBufferDescriptor_t *firstDescriptor{nullptr}; + NetworkBufferDescriptor_t *lastDescriptor{nullptr}; + BaseType_t receivedLength{0}; + auto *dmaRxDescriptor = RxDescriptor; + + invalidateDCache(dmaRxDescriptor, sizeof(*dmaRxDescriptor)); + while ((dmaRxDescriptor->DESC3 & uint32_t(RxDescriptorStatus::DmaOwned)) == 0) + { + bool accepted = true; + newDescriptor = nullptr; + receivedLength = (dmaRxDescriptor->DESC3 & FrameLengthMask) - 4; + auto *buffer = reinterpret_cast(dmaRxDescriptor->BackupAddr0); + invalidateDCache(buffer, receivedLength); + + if ((dmaRxDescriptor->DESC3 & uint32_t(RxDescriptorStatus::ErrorSummary)) != 0) + { + accepted = false; + } else if ((dmaRxDescriptor->DESC3 & uint32_t(RxDescriptorStatus::LastSegment)) == 0) + { + accepted = false; + } else + { + accepted = mayAcceptPacket(buffer); + } + + if (accepted) + { + newDescriptor = + pxGetNetworkBufferWithDescriptor(receivedLength, descriptorWaitTime); + if (newDescriptor == nullptr) accepted = false; + } + + if (accepted) + { + currentDescriptor = newDescriptor; + std::memcpy(currentDescriptor->pucEthernetBuffer, buffer, receivedLength); + currentDescriptor->xDataLength = receivedLength; + currentDescriptor->pxNextBuffer = nullptr; + if (firstDescriptor == nullptr) + firstDescriptor = currentDescriptor; + else if (lastDescriptor) + lastDescriptor->pxNextBuffer = currentDescriptor; + lastDescriptor = currentDescriptor; + } + + dmaRxDescriptor->DESC0 = dmaRxDescriptor->BackupAddr0; + dmaRxDescriptor->DESC1 = 0; + dmaRxDescriptor->DESC2 = 0; + __DMB(); + dmaRxDescriptor->DESC3 = RxDescriptorReady_t(RxDescriptorReady::DmaOwned | + RxDescriptorReady::InterruptOnCompletion | + RxDescriptorReady::Buffer1Valid) + .value; + cleanDCache(dmaRxDescriptor, sizeof(*dmaRxDescriptor)); + + __DMB(); + ETH->DMACRDTPR = uint32_t(dmaRxDescriptor); + + if (++dmaRxDescriptor == &DmaRxDescriptorTable[RX_BUFFER_NUMBER]) + dmaRxDescriptor = DmaRxDescriptorTable; + RxDescriptor = dmaRxDescriptor; + invalidateDCache(dmaRxDescriptor, sizeof(*dmaRxDescriptor)); + } + + if (firstDescriptor) passMessage(firstDescriptor); + + return receivedLength > 0; + } + + static void + updateConfig(bool force) + { + using modm::platform::eth; + + if (force or lastPhyLinkStatus == eth::LinkStatus::Up) + { + const bool autoNegotiationFailed = not EMAC::phyStartAutoNegotiation(); + EMAC::configureMac(autoNegotiationFailed); + EMAC::start(); + } else + { + EMAC::stop(); + } + } + + static bool + phyCheckLinkStatus(bool hasReceived) + { + using modm::platform::eth; + + if (hasReceived) + { + vTaskSetTimeOutState(&phyLinkStatusTimer); + phyLinkStatusRemaining = pdMS_TO_TICKS(PhyLinkStatusHighMs); + return false; + } + + bool checkNeeded{false}; + if (xTaskCheckForTimeOut(&phyLinkStatusTimer, &phyLinkStatusRemaining)) + { + const eth::LinkStatus phyLinkStatus = EMAC::phyReadLinkStatus(); + if (lastPhyLinkStatus != phyLinkStatus) + { + lastPhyLinkStatus = phyLinkStatus; + if (phyLinkStatus == eth::LinkStatus::Down) + { + IPStackEvent_t rxEvent = {eNetworkDownEvent, nullptr}; + xSendEventStructToIPTask(&rxEvent, 0); + } + checkNeeded = true; + } + + vTaskSetTimeOutState(&phyLinkStatusTimer); + phyLinkStatusRemaining = pdMS_TO_TICKS( + phyLinkStatus == eth::LinkStatus::Up ? PhyLinkStatusHighMs : PhyLinkStatusLowMs); + } + + return checkNeeded; + } + + static void + emacHandlerTask(void *) + { + using modm::platform::eth; + + static constexpr TickType_t maxBlockTime{pdMS_TO_TICKS(100)}; + + bool result{false}; + + for (;;) + { + result = false; + + if (isrEvent == eth::Event(0)) + ulTaskNotifyTake(pdFALSE, maxBlockTime); + else + { + if ((isrEvent & eth::Event::Receive) == eth::Event::Receive) + { + isrEvent = isrEvent & ~eth::Event::Receive; + result = emacInterfaceInput(); + } + if ((isrEvent & eth::Event::Transmit) == eth::Event::Transmit) + { + isrEvent = isrEvent & ~eth::Event::Transmit; + clearTxBuffers(); + } + if ((isrEvent & eth::Event::Error) == eth::Event::Error) + { + isrEvent = isrEvent & ~eth::Event::Error; + } + } + + if (phyCheckLinkStatus(result)) updateConfig(false); + } + } +}; + +ethernet::InitStatus ethernet::initStatus = ethernet::InitStatus::Init; +SemaphoreHandle_t ethernet::txDescriptorSemaphore{nullptr}; +TaskHandle_t ethernet::emacTaskHandle{nullptr}; + +modm::platform::eth::Event_t ethernet::isrEvent{modm::platform::eth::Event::None}; + +TimeOut_t ethernet::phyLinkStatusTimer; +modm::platform::eth::LinkStatus ethernet::lastPhyLinkStatus{modm::platform::eth::LinkStatus::Down}; +TickType_t ethernet::phyLinkStatusRemaining{0}; + +modm_aligned(32) modm_section( + ".bss_d2_sram1") ethernet::DmaDescriptor_t ethernet::DmaRxDescriptorTable[RX_BUFFER_NUMBER]; +modm_aligned(32) modm_section( + ".bss_d2_sram1") ethernet::DmaDescriptor_t ethernet::DmaTxDescriptorTable[TX_BUFFER_NUMBER]; +modm_aligned(32) + modm_section(".bss_d2_sram1") uint8_t ethernet::RxBuffers[RX_BUFFER_NUMBER][RX_BUFFER_SIZE]; +modm_aligned(32) + modm_section(".bss_d2_sram2") uint8_t ethernet::TxBuffers[TX_BUFFER_NUMBER][TX_BUFFER_SIZE]; +ethernet::DmaDescriptor_t *ethernet::RxDescriptor{nullptr}; +ethernet::DmaDescriptor_t *ethernet::TxDescriptor{nullptr}; +ethernet::DmaDescriptor_t *ethernet::DmaTxDescriptorToClear{nullptr}; + +} // namespace modm + +extern "C" BaseType_t +xNetworkInterfaceInitialise() +{ + using modm::ethernet; + + if (ethernet::initStatus == ethernet::InitStatus::Init) + { + ethernet::txDescriptorSemaphore = xSemaphoreCreateCounting( + UBaseType_t(ethernet::TX_BUFFER_NUMBER), UBaseType_t(ethernet::TX_BUFFER_NUMBER)); + if (ethernet::txDescriptorSemaphore == nullptr) + { + ethernet::initStatus = ethernet::InitStatus::Failed; + return pdFAIL; + } + + EMAC::setMacAddress(EMAC::MacAddressIndex::Index0, FreeRTOS_GetMACAddress()); +#if (ipconfigUSE_LLMNR != 0) + EMAC::setMacAddress(EMAC::MacAddressIndex::Index1, + reinterpret_cast(xLLMNR_MACAddress)); +#endif + + (void)EMAC::initialize(); + + ethernet::TxDescriptor = ethernet::DmaTxDescriptorTable; + ethernet::RxDescriptor = ethernet::DmaRxDescriptorTable; + ethernet::DmaTxDescriptorToClear = ethernet::DmaTxDescriptorTable; + + ethernet::DMATxDescListInit(); + ethernet::DMARxDescListInit(); + ethernet::updateConfig(true); + + if (not xTaskCreate(ethernet::emacHandlerTask, "EMAC", ethernet::emacTaskStackDepth, + nullptr, ethernet::emacTaskPriority, ðernet::emacTaskHandle)) + { + ethernet::initStatus = ethernet::InitStatus::Failed; + return pdFAIL; + } + + ethernet::initStatus = ethernet::InitStatus::Pass; + } + + if (ethernet::initStatus != ethernet::InitStatus::Pass) return pdFAIL; + + if (EMAC::getLinkStatus() == modm::platform::eth::LinkStatus::Up) + { + EMAC::enableInterrupt( + EMAC::Interrupt_t(EMAC::Interrupt::NormalIrqSummary | + EMAC::Interrupt::AbnormalIrqSummary | EMAC::Interrupt::FatalBusError | + EMAC::Interrupt::ReceiveBufferUnavailable | EMAC::Interrupt::Receive | + EMAC::Interrupt::TransmitStopped | EMAC::Interrupt::Transmit)); + return pdPASS; + } + + return pdFAIL; +} + +extern "C" BaseType_t +xNetworkInterfaceOutput(NetworkBufferDescriptor_t *const descriptor, BaseType_t releaseAfterSend) +{ + using modm::ethernet; + + static constexpr TickType_t blockTimeTicks{pdMS_TO_TICKS(50)}; + + BaseType_t result{pdFAIL}; + + do + { + auto *packet = reinterpret_cast(descriptor->pucEthernetBuffer); +#if (ipconfigDRIVER_INCLUDED_TX_IP_CHECKSUM == 1) + if (packet->xICMPPacket.xIPHeader.ucProtocol == ipPROTOCOL_ICMP) + packet->xICMPPacket.xICMPHeader.usChecksum = 0; +#else + (void)packet; +#endif + + if (EMAC::getLinkStatus() == modm::platform::eth::LinkStatus::Down) break; + + ethernet::clearTxBuffers(); + if (xSemaphoreTake(ethernet::txDescriptorSemaphore, blockTimeTicks) != pdPASS) break; + + auto *dmaTxDescriptor = ethernet::TxDescriptor; + bool descriptorReady{false}; + TimeOut_t timeout; + TickType_t remainingTime{blockTimeTicks}; + vTaskSetTimeOutState(&timeout); + + while (not descriptorReady) + { + ethernet::invalidateDCache(dmaTxDescriptor, sizeof(*dmaTxDescriptor)); + descriptorReady = + (dmaTxDescriptor->DESC3 & uint32_t(ethernet::TxDescriptor3::DmaOwned)) == 0; + if (descriptorReady) break; + + // Keep the software ring bookkeeping in sync with DMA write-back before + // giving up on a descriptor under sustained transmit load. + ethernet::clearTxBuffers(); + if (xTaskCheckForTimeOut(&timeout, &remainingTime) == pdTRUE) + { + xSemaphoreGive(ethernet::txDescriptorSemaphore); + break; + } + + taskYIELD(); + } + if (not descriptorReady) break; + + uint32_t transmitSize = descriptor->xDataLength; + if (transmitSize > ethernet::TX_BUFFER_SIZE) transmitSize = ethernet::TX_BUFFER_SIZE; + + std::memcpy(ethernet::TxBuffers[dmaTxDescriptor - ethernet::DmaTxDescriptorTable], + descriptor->pucEthernetBuffer, transmitSize); + ethernet::cleanDCache(ethernet::TxBuffers[dmaTxDescriptor - ethernet::DmaTxDescriptorTable], + transmitSize); + + dmaTxDescriptor->DESC0 = + uint32_t(ethernet::TxBuffers[dmaTxDescriptor - ethernet::DmaTxDescriptorTable]); + dmaTxDescriptor->DESC1 = 0; + dmaTxDescriptor->DESC2 = + (transmitSize & ethernet::Buffer1LengthMask) | + ethernet::TxDescriptor2_t(ethernet::TxDescriptor2::InterruptOnCompletion).value; + dmaTxDescriptor->DESC3 = ethernet::TxDescriptor3_t(ethernet::TxDescriptor3::FirstSegment | + ethernet::TxDescriptor3::LastSegment) + .value | + (transmitSize & ethernet::FrameLengthMask); +#if (ipconfigDRIVER_INCLUDED_TX_IP_CHECKSUM == 1) + dmaTxDescriptor->DESC3 |= + ethernet::TxDescriptor3_t(ethernet::TxDescriptor3::ChecksumFull).value; +#endif + + __DMB(); + dmaTxDescriptor->DESC3 |= uint32_t(ethernet::TxDescriptor3::DmaOwned); + ethernet::cleanDCache(dmaTxDescriptor, sizeof(*dmaTxDescriptor)); + + if (++ethernet::TxDescriptor == ðernet::DmaTxDescriptorTable[ethernet::TX_BUFFER_NUMBER]) + ethernet::TxDescriptor = ethernet::DmaTxDescriptorTable; + + __DSB(); + ETH->DMACTDTPR = uint32_t(ethernet::TxDescriptor); + iptraceNETWORK_INTERFACE_TRANSMIT(); + result = pdPASS; + } while (0); + + if (releaseAfterSend) vReleaseNetworkBufferAndDescriptor(descriptor); + + return result; +} + +extern "C" BaseType_t +xGetPhyLinkStatus() +{ return EMAC::getLinkStatus() == modm::platform::eth::LinkStatus::Up ? pdTRUE : pdFALSE; } + +MODM_ISR(ETH) +{ + using modm::ethernet; + using modm::platform::eth; + + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + const EMAC::InterruptFlags_t irq = EMAC::getInterruptFlags(); + EMAC::acknowledgeInterrupt(irq); + + if (irq & (eth::InterruptFlags::Receive | eth::InterruptFlags::ReceiveBufferUnavailable)) + { + ethernet::isrEvent |= eth::Event::Receive; + } + if (irq & eth::InterruptFlags::Transmit) { ethernet::isrEvent |= eth::Event::Transmit; } + if (irq & (eth::InterruptFlags::AbnormalIrqSummary | eth::InterruptFlags::FatalBusError)) + ethernet::isrEvent |= eth::Event::Error; + + if (ethernet::emacTaskHandle) + { + vTaskNotifyGiveFromISR(ethernet::emacTaskHandle, &xHigherPriorityTaskWoken); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); + } +} diff --git a/ext/aws/module.lb b/ext/aws/module.lb index 5994d020ff..d7cb90d38f 100644 --- a/ext/aws/module.lb +++ b/ext/aws/module.lb @@ -33,6 +33,28 @@ This module implements TCP over Ethernet via the LAN8720A transceiver. env.copy("modm_lan8720a.cpp") +class FreeRTOS_TCP_LAN8742A(Module): + def init(self, module): + module.name = "lan8742a" + module.description = """ +# a:FreeRTOS-Plus-TCP Ethernet via LAN8742A + +This module implements TCP over Ethernet via the LAN8742A transceiver. +""" + def prepare(self, module, options): + device = options[":target"] + if not device.has_driver("eth:stm32*"): + return False + if device.identifier.family not in ["h7"]: + return False + module.depends(":platform:eth", ":driver:lan8742a") + return True + + def build(self, env): + env.outbasepath = "modm/ext/freertos_plus_tcp" + env.copy("modm_lan8742a.cpp") + + class FreeRTOS_TCP(Module): def init(self, module): module.name = "tcp" @@ -40,6 +62,7 @@ class FreeRTOS_TCP(Module): def prepare(self, module, options): module.add_submodule(FreeRTOS_TCP_LAN8720A()) + module.add_submodule(FreeRTOS_TCP_LAN8742A()) return True def build(self, env): diff --git a/src/modm/driver/ethernet/lan8742a.hpp b/src/modm/driver/ethernet/lan8742a.hpp new file mode 100644 index 0000000000..99af987e95 --- /dev/null +++ b/src/modm/driver/ethernet/lan8742a.hpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2020, Mike Wolfram + * Copyright (c) 2026, Henrik Hose + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef MODM_LAN8742A_HPP +#define MODM_LAN8742A_HPP + +namespace modm +{ + +/// @ingroup modm_driver_lan8742a +struct Lan8742a +{ + static constexpr uint32_t Address = 0x00; + + struct Register + { + static constexpr uint16_t BCR = 0x0000; + static constexpr uint16_t BSR = 0x0001; + static constexpr uint16_t AN = 0x0004; + static constexpr uint16_t SR = 0x001f; + static constexpr uint16_t ISFR = 0x001d; + }; + + static constexpr uint32_t ResetDelay = 0x000000FF; + static constexpr uint32_t ConfigDelay = 0x00000FFF; + static constexpr int ReadTimeout = 0xffff; + static constexpr int WriteTimeout = 0xffff; + + static constexpr uint16_t Reset = 0x8000; + static constexpr uint16_t LoopBack = 0x4000; + static constexpr uint16_t FullDuplex100M = 0x2100; + static constexpr uint16_t HalfDuplex100M = 0x2000; + static constexpr uint16_t FullDuplex10M = 0x0100; + static constexpr uint16_t HalfDuplex10M = 0x0000; + static constexpr uint16_t AutoNegotiation = 0x1000; + static constexpr uint16_t RestartAutoNegotiation = 0x0200; + + static constexpr uint16_t LinkedStatus = 0x0004; + static constexpr uint16_t DuplexStatus = 0x0010; + static constexpr uint16_t SpeedStatus = 0x0004; + static constexpr uint16_t AutoNegotiationComplete = 0x0020; + static constexpr uint16_t JabberDetection = 0x0002; + + static constexpr uint16_t StatusAutoNegotiationDone = 0x1000; + static constexpr uint16_t StatusSpeedMask = 0x001c; + static constexpr uint16_t Status10HalfDuplex = 0x0004; + static constexpr uint16_t Status10FullDuplex = 0x0014; + static constexpr uint16_t Status100HalfDuplex = 0x0008; + static constexpr uint16_t Status100FullDuplex = 0x0018; +}; + +} // namespace modm + +#endif // MODM_LAN8742A_HPP diff --git a/src/modm/driver/ethernet/lan8742a.lb b/src/modm/driver/ethernet/lan8742a.lb new file mode 100644 index 0000000000..1642bec682 --- /dev/null +++ b/src/modm/driver/ethernet/lan8742a.lb @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020, Mike Wolfram +# Copyright (c) 2026, Henrik Hose +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + + +def init(module): + module.name = ":driver:lan8742a" + module.description = """\ +# LAN8742A Ethernet Transceiver + +Microchip's LAN8742A is a low-power 10BASE-T/100BASE-TX transceiver +connected via an RMII interface. +""" + +def prepare(module, options): + return True + +def build(env): + env.outbasepath = "modm/src/modm/driver/ethernet" + env.copy("lan8742a.hpp") diff --git a/src/modm/platform/eth/stm32/eth.hpp b/src/modm/platform/eth/stm32/eth.hpp.in similarity index 65% rename from src/modm/platform/eth/stm32/eth.hpp rename to src/modm/platform/eth/stm32/eth.hpp.in index c27779af86..ac0250fc04 100644 --- a/src/modm/platform/eth/stm32/eth.hpp +++ b/src/modm/platform/eth/stm32/eth.hpp.in @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, Mike Wolfram + * Copyright (c) 2026, Henrik Hose * * This file is part of the modm project. * @@ -18,6 +19,7 @@ #include "../device.hpp" #include +#include #include namespace modm @@ -32,7 +34,11 @@ struct eth MediaInterface : uint32_t { MII = 0x00, +%% if family == "h7" + RMII = SYSCFG_PMCR_EPIS_SEL_2 +%% elif family in ["f4", "f7"] RMII = SYSCFG_PMC_MII_RMII_SEL +%% endif }; enum class @@ -68,6 +74,23 @@ struct eth enum class Interrupt : uint32_t { +%% if family == "h7" + NormalIrqSummary = ETH_DMACIER_NIE, + AbnormalIrqSummary = ETH_DMACIER_AIE, + EarlyReceive = ETH_DMACIER_ERIE, + FatalBusError = ETH_DMACIER_FBEE, + EarlyTransmit = ETH_DMACIER_ETIE, + ReceiveWatchdog = ETH_DMACIER_RWTE, + ReceiveStopped = ETH_DMACIER_RSE, + ReceiveBufferUnavailable = ETH_DMACIER_RBUE, + Receive = ETH_DMACIER_RIE, + TransmitUnderflow = 0, + ReceiveOverflow = 0, + TransmitJabberTimeout = 0, + TransmitBufferUnavailable = ETH_DMACIER_TBUE, + TransmitStopped = ETH_DMACIER_TXSE, + Transmit = ETH_DMACIER_TIE, +%% elif family in ["f4", "f7"] NormalIrqSummary = modm::Bit16, AbnormalIrqSummary = modm::Bit15, EarlyReceive = modm::Bit14, @@ -83,12 +106,42 @@ struct eth TransmitBufferUnavailable = modm::Bit2, TransmitStopped = modm::Bit1, Transmit = modm::Bit0, +%% endif }; MODM_FLAGS32(Interrupt); enum class InterruptFlags : uint32_t { +%% if family == "h7" + TimeStampTrigger = 0, + Pmt = 0, + Mmc = 0, + ErrorBitStatus2 = 0, + ErrorBitStatus1 = 0, + ErrorBitStatus0 = 0, + TransmitProcessState2 = 0, + TransmitProcessState1 = 0, + TransmitProcessState0 = 0, + ReceiveProcessState2 = 0, + ReceiveProcessState1 = 0, + ReceiveProcessState0 = 0, + NormalIrqSummary = ETH_DMACSR_NIS, + AbnormalIrqSummary = ETH_DMACSR_AIS, + EarlyReceive = ETH_DMACSR_ERI, + FatalBusError = ETH_DMACSR_FBE, + EarlyTransmit = ETH_DMACSR_ETI, + ReceiveWatchdog = ETH_DMACSR_RWT, + ReceiveStopped = ETH_DMACSR_RPS, + ReceiveBufferUnavailable = ETH_DMACSR_RBU, + Receive = ETH_DMACSR_RI, + TransmitUnderflow = 0, + ReceiveOverflow = 0, + TransmitJabberTimeout = 0, + TransmitBufferUnavailable = ETH_DMACSR_TBU, + TransmitStopped = ETH_DMACSR_TPS, + Transmit = ETH_DMACSR_TI, +%% elif family in ["f4", "f7"] TimeStampTrigger = modm::Bit29, Pmt = modm::Bit28, Mmc = modm::Bit27, @@ -116,6 +169,7 @@ struct eth TransmitBufferUnavailable = modm::Bit2, TransmitStopped = modm::Bit1, Transmit = modm::Bit0, +%% endif }; MODM_FLAGS32(InterruptFlags); @@ -140,6 +194,14 @@ class Eth : public eth enum class MacConfiguration : uint32_t { +%% if family == "h7" + EthernetSpeed = ETH_MACCR_FES, + DuplexMode = ETH_MACCR_DM, + Ipv4ChecksumOffLoad = ETH_MACCR_IPC, + RetryDisable = ETH_MACCR_DR, + TransmitterEnable = ETH_MACCR_TE, + ReceiveEnable = ETH_MACCR_RE, +%% elif family in ["f4", "f7"] WatchDogDisable = modm::Bit23, JabberDisable = modm::Bit22, InterframeGap2 = modm::Bit19, @@ -158,10 +220,15 @@ class Eth : public eth DeferalCheck = modm::Bit4, TransmitterEnable = modm::Bit3, ReceiveEnable = modm::Bit2, +%% endif }; MODM_FLAGS32(MacConfiguration); +%% if family == "h7" + static constexpr uint32_t MacCrClearMask { 0xFFFB7F7C }; +%% elif family in ["f4", "f7"] static constexpr uint32_t MacCrClearMask { 0xFF20810F }; +%% endif enum class Watchdog : uint32_t @@ -207,6 +274,20 @@ class Eth : public eth enum class MacFrameFilter : uint32_t { +%% if family == "h7" + ReceiveAll = ETH_MACPFR_RA, + HashOrPerfect = ETH_MACPFR_HPF, + SourceAddress = ETH_MACPFR_SAF, + SourceAddressInverse = ETH_MACPFR_SAIF, + PassControl1 = 0, + PassControl0 = 0, + BroadcastDisable = ETH_MACPFR_DBF, + PassAllMulticast = ETH_MACPFR_PM, + DestinationAddressInverse = ETH_MACPFR_DAIF, + HashMulticast = ETH_MACPFR_HMC, + HasUnicast = ETH_MACPFR_HUC, + PromiscuousMode = ETH_MACPFR_PR, +%% elif family in ["f4", "f7"] ReceiveAll = modm::Bit31, HashOrPerfect = modm::Bit10, SourceAddress = modm::Bit9, @@ -219,6 +300,7 @@ class Eth : public eth HashMulticast = modm::Bit2, HasUnicast = modm::Bit1, PromiscuousMode = modm::Bit0, +%% endif }; MODM_FLAGS32(MacFrameFilter); @@ -235,6 +317,15 @@ class Eth : public eth enum class MacFlowControl : uint32_t { +%% if family == "h7" + ZeroQuantaPauseDisable = ETH_MACTFCR_DZPQ, + PauseLowThreshold1 = 0, + PauseLowThreshold0 = 0, + UnicastPauseDetect = ETH_MACRFCR_UP, + ReceiveFlowControlEnable = ETH_MACRFCR_RFE, + TransmitFlowControlEnable = ETH_MACTFCR_TFE, + FlowControlBusy = 0, +%% elif family in ["f4", "f7"] PauseTime15 = modm::Bit31, PauseTime14 = modm::Bit30, PauseTime13 = modm::Bit29, @@ -258,10 +349,15 @@ class Eth : public eth ReceiveFlowControlEnable = modm::Bit2, TransmitFlowControlEnable = modm::Bit1, FlowControlBusy = modm::Bit0, +%% endif }; MODM_FLAGS32(MacFlowControl); +%% if family == "h7" + static constexpr uint32_t MacFcrClearMask { 0xFFFF00F2 }; +%% elif family in ["f4", "f7"] static constexpr uint32_t MacFcrClearMask { 0x0000FF41 }; +%% endif enum class PauseLowThreshold : uint32_t @@ -276,6 +372,9 @@ class Eth : public eth enum class DmaOperationMode : uint32_t { +%% if family == "h7" + None = 0, +%% elif family in ["f4", "f7"] DropCrcErrorFrameDisable = modm::Bit26, ReceiveStoreAndForward = modm::Bit25, DisableFlushReceivedFrames = modm::Bit24, @@ -291,10 +390,13 @@ class Eth : public eth ReceiveThreshold0 = modm::Bit3, OperateOnSecondFrame = modm::Bit2, StartReceive = modm::Bit1, +%% endif }; MODM_FLAGS32(DmaOperationMode); +%% if family in ["f4", "f7"] static constexpr uint32_t DmaOmrClearMask { 0xF8DE3F23 }; +%% endif enum class TransmitThreshold : uint32_t @@ -323,6 +425,9 @@ class Eth : public eth enum class DmaBusMode : uint32_t { +%% if family == "h7" + None = 0, +%% elif family in ["f4", "f7"] MixedBurst = modm::Bit26, AddressAlignedBeats = modm::Bit25, PblModeX4 = modm::Bit24, @@ -350,6 +455,7 @@ class Eth : public eth DescriptorSkipLength0 = modm::Bit2, DmaArbitration = modm::Bit1, SoftwareReset = modm::Bit0, +%% endif }; MODM_FLAGS32(DmaBusMode); @@ -381,12 +487,27 @@ class Eth : public eth { using namespace modm::literals; +%% if family == "h7" + RCC->AHB1ENR |= RCC_AHB1ENR_ETH1MACEN | RCC_AHB1ENR_ETH1TXEN | RCC_AHB1ENR_ETH1RXEN; + __DSB(); + RCC->AHB1RSTR |= RCC_AHB1RSTR_ETH1MACRST; + __DSB(); + RCC->AHB1RSTR &= ~RCC_AHB1RSTR_ETH1MACRST; +%% elif family in ["f4", "f7"] Rcc::enable(); +%% endif NVIC_SetPriority(ETH_IRQn, priority); NVIC_EnableIRQ(ETH_IRQn); /* Select MII or RMII Mode*/ +%% if family == "h7" + SYSCFG->PMCR &= ~SYSCFG_PMCR_EPIS_SEL; + SYSCFG->PMCR |= uint32_t(Interface); + (void) SYSCFG->PMCR; + + ETH->DMAMR |= ETH_DMAMR_SWR; +%% elif family in ["f4", "f7"] SYSCFG->PMC &= ~(SYSCFG_PMC_MII_RMII_SEL); SYSCFG->PMC |= uint32_t(Interface); @@ -394,12 +515,13 @@ class Eth : public eth /* Set the SWR bit: resets all MAC subsystem internal registers and logic */ /* After reset all the registers holds their respective reset values */ ETH->DMABMR |= DmaBusMode_t(DmaBusMode::SoftwareReset | DmaBusMode::EnhancedDescFormat).value; +%% endif /* Wait for software reset */ /* Note: The SWR is not performed if the ETH_RX_CLK or the ETH_TX_CLK are * not available, please check your external PHY or the IO configuration */ int timeout = 1'000; // max 1ms - while ((DmaBusMode(ETH->DMABMR) & DmaBusMode_t(DmaBusMode::SoftwareReset)) and (timeout-- > 0)) { + while ((isSoftwareResetActive()) and (timeout-- > 0)) { // Wait until the PHY has reset. modm::delay_us(1); @@ -410,19 +532,11 @@ class Eth : public eth return false; /* Configure SMI clock range */ - uint32_t csr_clock_divider = ETH->MACMIIAR & ETH_MACMIIAR_CR_Msk; - if (SystemCoreClock >= 20_MHz and SystemCoreClock < 35_MHz) - csr_clock_divider |= ETH_MACMIIAR_CR_Div16; - else if (SystemCoreClock >= 35_MHz and SystemCoreClock < 60_MHz) - csr_clock_divider |= ETH_MACMIIAR_CR_Div26; - else if (SystemCoreClock >= 60_MHz and SystemCoreClock < 100_MHz) - csr_clock_divider |= ETH_MACMIIAR_CR_Div42; - else if (SystemCoreClock >= 100_MHz and SystemCoreClock < 150_MHz) - csr_clock_divider |= ETH_MACMIIAR_CR_Div62; - else if (SystemCoreClock >= 150_MHz) - csr_clock_divider |= ETH_MACMIIAR_CR_Div102; + configureMdioClockRange(); - ETH->MACMIIAR = csr_clock_divider; +%% if family == "h7" + ETH->MAC1USTCR = (getHclkFrequency() / 1'000'000U) - 1U; +%% endif // Initialize PHY uint32_t phy_register { 0 }; @@ -470,6 +584,32 @@ class Eth : public eth static void configureMac(bool autoNegotiationFailed = false) { +%% if family == "h7" + if (autoNegotiationFailed) { + duplexMode = DuplexMode::Full; + speed = Speed::Speed100M; + } + + uint32_t tmp = ETH->MACCR & ~MacCrClearMask; + tmp |= MacConfiguration_t( + MacConfiguration::Ipv4ChecksumOffLoad | + MacConfiguration::RetryDisable).value; + tmp |= Speed_t(speed).value; + tmp |= DuplexMode_t(duplexMode).value; + writeMACCR(tmp); + + writeMACFFR(0); + + ETH->MACHT0R = 0x00000000; + ETH->MACHT1R = 0x00000000; + + tmp = ETH->MACTFCR & ~MacFcrClearMask; + tmp |= MacFlowControl_t(MacFlowControl::ZeroQuantaPauseDisable).value; + writeMACTFCR(tmp); + tmp = ETH->MACRFCR; + tmp &= ~(ETH_MACRFCR_RFE | ETH_MACRFCR_UP); + writeMACRFCR(tmp); +%% elif family in ["f4", "f7"] uint32_t tmp; if (autoNegotiationFailed) { @@ -506,11 +646,44 @@ class Eth : public eth // no VLAN support for now writeMACVLANTR(0x00000000); +%% endif } static void configureDma() { +%% if family == "h7" + ETH->MTLTQOMR = + (ETH->MTLTQOMR & ~0x00000072) | + ETH_MTLTQOMR_TSF; + ETH->MTLRQOMR = + (ETH->MTLRQOMR & ~0x0000007B) | + ETH_MTLRQOMR_RSF; + + ETH->DMAMR = + (ETH->DMAMR & ~0x00007802); + + ETH->DMASBMR = + (ETH->DMASBMR & ~0x0000D001) | + ETH_DMASBMR_AAL | + ETH_DMASBMR_FB; + + ETH->DMACCR = + (ETH->DMACCR & ~0x00013FFF) | + ETH_DMACCR_DSL_64BIT; + + ETH->DMACTCR = + (ETH->DMACTCR & ~0x003F1010) | + ETH_DMACTCR_TPBL_32PBL; + + ETH->DMACRCR = + (ETH->DMACRCR & ~0x803F0000) | + ETH_DMACRCR_RPBL_32PBL; + + enableInterrupt(Interrupt::NormalIrqSummary | Interrupt::Receive); + + configureMacAddresses(); +%% elif family in ["f4", "f7"] uint32_t tmp; DmaOperationMode_t dmaomr { @@ -535,6 +708,7 @@ class Eth : public eth enableInterrupt(Interrupt::NormalIrqSummary | Interrupt::Receive); configureMacAddresses(); +%% endif } static void @@ -553,26 +727,68 @@ class Eth : public eth static void setDmaTxDescriptorTable(uint32_t address) { +%% if family == "h7" + setDmaTxDescriptorTable(address, 1); +%% elif family in ["f4", "f7"] ETH->DMATDLAR = address; +%% endif } static void setDmaRxDescriptorTable(uint32_t address) { +%% if family == "h7" + setDmaRxDescriptorTable(address, 1); +%% elif family in ["f4", "f7"] + ETH->DMARDLAR = address; +%% endif + } + static void + setDmaTxDescriptorTable(uint32_t address, std::size_t count) { +%% if family == "h7" + ETH->DMACTDLAR = address; + ETH->DMACTDRLR = count ? uint32_t(count - 1) : 0; + ETH->DMACTDTPR = address; +%% elif family in ["f4", "f7"] + (void) count; + ETH->DMATDLAR = address; +%% endif + } + static void + setDmaRxDescriptorTable(uint32_t address, std::size_t count) { +%% if family == "h7" + ETH->DMACRDLAR = address; + ETH->DMACRDRLR = count ? uint32_t(count - 1) : 0; + ETH->DMACRDTPR = address; +%% elif family in ["f4", "f7"] + (void) count; ETH->DMARDLAR = address; +%% endif } static InterruptFlags getInterruptFlags() { +%% if family == "h7" + return InterruptFlags(ETH->DMACSR); +%% elif family in ["f4", "f7"] return InterruptFlags(ETH->DMASR); +%% endif } static void acknowledgeInterrupt(InterruptFlags_t irq) { // set only the bits you want to clear! // using an |= here would clear other fields as well +%% if family == "h7" + ETH->DMACSR = irq.value; +%% elif family in ["f4", "f7"] ETH->DMASR = irq.value; +%% endif } static void enableInterrupt(Interrupt_t irq) { +%% if family == "h7" + ETH->DMACIER |= irq.value; +%% elif family in ["f4", "f7"] ETH->DMAIER |= irq.value; +%% endif } // FIXME: Make this more generic by delegating specifics to the PHY @@ -634,6 +850,66 @@ class Eth : public eth } private: + static bool + isSoftwareResetActive() { +%% if family == "h7" + return (ETH->DMAMR & ETH_DMAMR_SWR) != 0; +%% elif family in ["f4", "f7"] + return (DmaBusMode(ETH->DMABMR) & + DmaBusMode_t(DmaBusMode::SoftwareReset)).value != 0; +%% endif + } + + static uint32_t + getHclkFrequency() { +%% if family == "h7" + static constexpr uint16_t hpreShift[] {1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 8, 16, 64, 128, 256, 512}; + const uint32_t prescaler = RCC->D1CFGR & RCC_D1CFGR_HPRE; + return SystemCoreClock / hpreShift[prescaler]; +%% elif family in ["f4", "f7"] + return SystemCoreClock; +%% endif + } + + static void + configureMdioClockRange() { + using namespace modm::literals; + +%% if family == "h7" + uint32_t csrClockDivider = ETH->MACMDIOAR & ~ETH_MACMDIOAR_CR; + const uint32_t hclk = getHclkFrequency(); + + if (hclk >= 20_MHz and hclk < 35_MHz) + csrClockDivider |= ETH_MACMDIOAR_CR_DIV16; + else if (hclk >= 35_MHz and hclk < 60_MHz) + csrClockDivider |= ETH_MACMDIOAR_CR_DIV26; + else if (hclk >= 60_MHz and hclk < 100_MHz) + csrClockDivider |= ETH_MACMDIOAR_CR_DIV42; + else if (hclk >= 100_MHz and hclk < 150_MHz) + csrClockDivider |= ETH_MACMDIOAR_CR_DIV62; + else if (hclk >= 150_MHz and hclk < 250_MHz) + csrClockDivider |= ETH_MACMDIOAR_CR_DIV102; + else + csrClockDivider |= ETH_MACMDIOAR_CR_DIV124; + + ETH->MACMDIOAR = csrClockDivider; +%% elif family in ["f4", "f7"] + uint32_t csrClockDivider = ETH->MACMIIAR & ETH_MACMIIAR_CR_Msk; + if (SystemCoreClock >= 20_MHz and SystemCoreClock < 35_MHz) + csrClockDivider |= ETH_MACMIIAR_CR_Div16; + else if (SystemCoreClock >= 35_MHz and SystemCoreClock < 60_MHz) + csrClockDivider |= ETH_MACMIIAR_CR_Div26; + else if (SystemCoreClock >= 60_MHz and SystemCoreClock < 100_MHz) + csrClockDivider |= ETH_MACMIIAR_CR_Div42; + else if (SystemCoreClock >= 100_MHz and SystemCoreClock < 150_MHz) + csrClockDivider |= ETH_MACMIIAR_CR_Div62; + else if (SystemCoreClock >= 150_MHz) + csrClockDivider |= ETH_MACMIIAR_CR_Div102; + + ETH->MACMIIAR = csrClockDivider; +%% endif + } + static void writeMACCR(uint32_t value) { ETH->MACCR = value; @@ -643,38 +919,83 @@ class Eth : public eth } static void writeMACFCR(uint32_t value) { +%% if family == "h7" + (void) value; +%% elif family in ["f4", "f7"] ETH->MACFCR = value; (void) ETH->MACFCR; modm::delay_ms(1); ETH->MACFCR = value; +%% endif } static void writeMACFFR(uint32_t value) { +%% if family == "h7" + ETH->MACPFR = value; + (void) ETH->MACPFR; + modm::delay_ms(1); + ETH->MACPFR = value; +%% elif family in ["f4", "f7"] ETH->MACFFR = value; (void) ETH->MACFFR; modm::delay_ms(1); ETH->MACFFR = value; +%% endif + } + static void + writeMACTFCR(uint32_t value) { +%% if family == "h7" + ETH->MACTFCR = value; + (void) ETH->MACTFCR; + modm::delay_ms(1); + ETH->MACTFCR = value; +%% elif family in ["f4", "f7"] + (void) value; +%% endif + } + static void + writeMACRFCR(uint32_t value) { +%% if family == "h7" + ETH->MACRFCR = value; + (void) ETH->MACRFCR; + modm::delay_ms(1); + ETH->MACRFCR = value; +%% elif family in ["f4", "f7"] + (void) value; +%% endif } static void writeMACVLANTR(uint32_t value) { +%% if family == "h7" + (void) value; +%% elif family in ["f4", "f7"] ETH->MACVLANTR = value; (void) ETH->MACVLANTR; modm::delay_ms(1); ETH->MACVLANTR = value; +%% endif } static void writeDMABMR(uint32_t value) { +%% if family == "h7" + (void) value; +%% elif family in ["f4", "f7"] ETH->DMABMR= value; (void) ETH->DMABMR; modm::delay_ms(1); ETH->DMABMR = value; +%% endif } static void writeDMAOMR(uint32_t value) { +%% if family == "h7" + (void) value; +%% elif family in ["f4", "f7"] ETH->DMAOMR= value; (void) ETH->DMAOMR; modm::delay_ms(1); ETH->DMAOMR = value; +%% endif } static bool diff --git a/src/modm/platform/eth/stm32/eth_impl.hpp b/src/modm/platform/eth/stm32/eth_impl.hpp.in similarity index 55% rename from src/modm/platform/eth/stm32/eth_impl.hpp rename to src/modm/platform/eth/stm32/eth_impl.hpp.in index 9ce0b060d8..8ac0d35633 100644 --- a/src/modm/platform/eth/stm32/eth_impl.hpp +++ b/src/modm/platform/eth/stm32/eth_impl.hpp.in @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, Mike Wolfram + * Copyright (c) 2026, Henrik Hose * * This file is part of the modm project. * @@ -20,6 +21,18 @@ template void Eth::start() { +%% if family == "h7" + ETH->DMACTCR |= ETH_DMACTCR_ST; + ETH->DMACRCR |= ETH_DMACRCR_SR; + ETH->DMACSR |= ETH_DMACSR_TPS | ETH_DMACSR_RPS; + ETH->MTLTQOMR |= ETH_MTLTQOMR_FTQ; + + uint32_t tmp = ETH->MACCR | ETH_MACCR_TE; + writeMACCR(tmp); + + tmp = ETH->MACCR | ETH_MACCR_RE; + writeMACCR(tmp); +%% elif family in ["f4", "f7"] // transmission enable uint32_t tmp = ETH->MACCR | ETH_MACCR_TE; writeMACCR(tmp); @@ -39,12 +52,25 @@ Eth::start() // DMA reception enable tmp = ETH->DMAOMR | ETH_DMAOMR_SR; writeDMAOMR(tmp); +%% endif } template void Eth::stop() { +%% if family == "h7" + ETH->DMACTCR &= ~ETH_DMACTCR_ST; + ETH->DMACRCR &= ~ETH_DMACRCR_SR; + + uint32_t tmp = ETH->MACCR & ~ETH_MACCR_RE; + writeMACCR(tmp); + + ETH->MTLTQOMR |= ETH_MTLTQOMR_FTQ; + + tmp = ETH->MACCR & ~ETH_MACCR_TE; + writeMACCR(tmp); +%% elif family in ["f4", "f7"] // DMA transmission disable uint32_t tmp = ETH->DMAOMR & ~ETH_DMAOMR_ST; writeDMAOMR(tmp); @@ -64,12 +90,48 @@ Eth::stop() // transmission disable tmp = ETH->MACCR & ~ETH_MACCR_TE; writeMACCR(tmp); +%% endif } template void Eth::configureMacAddresses() { +%% if family == "h7" + static constexpr MacAddress zeroMac { 0 }; + + for (std::size_t i = 0; i < macAddresses.size(); ++i) { + auto const &macAddress = macAddresses[i]; + if (std::memcmp(zeroMac.data(), macAddress.data(), macAddress.size()) == 0) + continue; + + const uint32_t high = ((uint32_t(macAddress[5]) << 8) | macAddress[4]) | + (i ? ETH_MACAHR_AE : 0); + const uint32_t low = (uint32_t(macAddress[3]) << 24) | + (uint32_t(macAddress[2]) << 16) | + (uint32_t(macAddress[1]) << 8) | + macAddress[0]; + + switch (i) { + case 0: + ETH->MACA0HR = high; + ETH->MACA0LR = low; + break; + case 1: + ETH->MACA1HR = high; + ETH->MACA1LR = low; + break; + case 2: + ETH->MACA2HR = high; + ETH->MACA2LR = low; + break; + case 3: + ETH->MACA3HR = high; + ETH->MACA3LR = low; + break; + } + } +%% elif family in ["f4", "f7"] static constexpr uint32_t ETH_MAC_ADDR_HBASE { ETH_MAC_BASE + 0x40 }; /* Ethernet MAC address high offset */ static constexpr uint32_t ETH_MAC_ADDR_LBASE { ETH_MAC_BASE + 0x44 }; /* Ethernet MAC address low offset */ @@ -85,12 +147,35 @@ Eth::configureMacAddresses() tmp_register = (macAddress[3] << 24) | (macAddress[2] << 16) | (macAddress[1] << 8) | macAddress[0]; *reinterpret_cast<__IO uint32_t *>(ETH_MAC_ADDR_LBASE + i * 0x08) = tmp_register; } +%% endif } template bool Eth::readPhyRegister(uint16_t reg, uint32_t &value) { +%% if family == "h7" + uint32_t tmp = ETH->MACMDIOAR & ~( + ETH_MACMDIOAR_PA | + ETH_MACMDIOAR_RDA | + ETH_MACMDIOAR_MOC | + ETH_MACMDIOAR_C45E | + ETH_MACMDIOAR_MB); + tmp |= ((PHY::Address << ETH_MACMDIOAR_PA_Pos) & ETH_MACMDIOAR_PA); + tmp |= ((reg << ETH_MACMDIOAR_RDA_Pos) & ETH_MACMDIOAR_RDA); + tmp |= ETH_MACMDIOAR_MOC_RD; + tmp |= ETH_MACMDIOAR_MB; + + ETH->MACMDIOAR = tmp; + + int timeout = PHY::ReadTimeout; + while (timeout-- > 0) { + if ((ETH->MACMDIOAR & ETH_MACMDIOAR_MB) == 0) { + value = ETH->MACMDIODR; + return true; + } + } +%% elif family in ["f4", "f7"] // get only CR bits from MACMIIAR uint32_t tmp = ETH->MACMIIAR & ETH_MACMIIAR_CR_Msk; tmp |= (PHY::Address << 11) & ETH_MACMIIAR_PA; @@ -108,6 +193,7 @@ Eth::readPhyRegister(uint16_t reg, uint32_t &value) return true; } } +%% endif return false; } @@ -116,6 +202,27 @@ template bool Eth::writePhyRegister(uint16_t reg, uint32_t value) { +%% if family == "h7" + uint32_t tmp = ETH->MACMDIOAR & ~( + ETH_MACMDIOAR_PA | + ETH_MACMDIOAR_RDA | + ETH_MACMDIOAR_MOC | + ETH_MACMDIOAR_C45E | + ETH_MACMDIOAR_MB); + tmp |= ((PHY::Address << ETH_MACMDIOAR_PA_Pos) & ETH_MACMDIOAR_PA); + tmp |= ((reg << ETH_MACMDIOAR_RDA_Pos) & ETH_MACMDIOAR_RDA); + tmp |= ETH_MACMDIOAR_MOC_WR; + tmp |= ETH_MACMDIOAR_MB; + + ETH->MACMDIODR = value; + ETH->MACMDIOAR = tmp; + + int timeout = PHY::WriteTimeout; + while (timeout-- > 0) { + if ((ETH->MACMDIOAR & ETH_MACMDIOAR_MB) == 0) + return true; + } +%% elif family in ["f4", "f7"] // get only CR bits from MACMIIAR uint32_t tmp = ETH->MACMIIAR & ETH_MACMIIAR_CR_Msk; tmp |= (PHY::Address << 11) & ETH_MACMIIAR_PA; @@ -131,6 +238,7 @@ Eth::writePhyRegister(uint16_t reg, uint32_t value) if ((ETH->MACMIIAR & ETH_MACMIIAR_MB) == 0) return true; } +%% endif return false; } diff --git a/src/modm/platform/eth/stm32/module.lb b/src/modm/platform/eth/stm32/module.lb index b74e261d2f..94bb74e8b6 100644 --- a/src/modm/platform/eth/stm32/module.lb +++ b/src/modm/platform/eth/stm32/module.lb @@ -20,8 +20,7 @@ def prepare(module, options): if not device.has_driver("eth:stm32*"): return False - # FIXME the driver is for F7 only right now - if device.identifier["family"] not in ["f7", "f4"]: + if device.identifier["family"] not in ["h7", "f7", "f4"]: return False module.depends(":architecture:delay", @@ -35,9 +34,12 @@ def prepare(module, options): return True def build(env): - env.substitutions = {"target": env[":target"].identifier} + target = env[":target"].identifier + env.substitutions = { + "target": target, + "family": target.family + } env.outbasepath = "modm/src/modm/platform/eth" - env.copy("eth.hpp") - env.copy("eth_impl.hpp") - + env.template("eth.hpp.in") + env.template("eth_impl.hpp.in") From 8da69f4b1cff79dfc10a4c751156ba7a1cfd3b2d Mon Sep 17 00:00:00 2001 From: Henrik Hose Date: Tue, 28 Jul 2026 15:29:32 +0200 Subject: [PATCH 2/3] [examples] Added h723 ethernet example --- .../ethernet/FreeRTOSIPConfigLocal.h | 104 ++++++++ examples/nucleo_h723zg/ethernet/main.cpp | 231 ++++++++++++++++++ examples/nucleo_h723zg/ethernet/project.xml | 11 + 3 files changed, 346 insertions(+) create mode 100644 examples/nucleo_h723zg/ethernet/FreeRTOSIPConfigLocal.h create mode 100644 examples/nucleo_h723zg/ethernet/main.cpp create mode 100644 examples/nucleo_h723zg/ethernet/project.xml diff --git a/examples/nucleo_h723zg/ethernet/FreeRTOSIPConfigLocal.h b/examples/nucleo_h723zg/ethernet/FreeRTOSIPConfigLocal.h new file mode 100644 index 0000000000..c27d5ee3bc --- /dev/null +++ b/examples/nucleo_h723zg/ethernet/FreeRTOSIPConfigLocal.h @@ -0,0 +1,104 @@ +/* + * FreeRTOS+TCP V2.2.1 + * Copyright (C) 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://www.FreeRTOS.org + * http://aws.amazon.com/freertos + * + * 1 tab == 4 spaces! + */ + +#ifndef FREERTOS_IP_CONFIG_H +#error "Don't include this file, use 'FreeRTOSIPConfig.h' instead!" +#endif + +#define ipconfigHAS_DEBUG_PRINTF 0 +#define ipconfigHAS_PRINTF 0 + +// Keep checksum handling in software for the first H7 ethernet backend. +#define ipconfigDRIVER_INCLUDED_RX_IP_CHECKSUM 0 +#define ipconfigDRIVER_INCLUDED_TX_IP_CHECKSUM 0 + +#define ipconfigSOCK_DEFAULT_RECEIVE_BLOCK_TIME (5000) +#define ipconfigSOCK_DEFAULT_SEND_BLOCK_TIME (5000) + +#define ipconfigUSE_LLMNR (0) +#define ipconfigUSE_NBNS (0) + +#define ipconfigUSE_DNS_CACHE (1) +#define ipconfigDNS_CACHE_NAME_LENGTH (16) +#define ipconfigDNS_CACHE_ENTRIES (4) +#define ipconfigDNS_REQUEST_ATTEMPTS (2) + +extern UBaseType_t +uxRand(void); +#define ipconfigRAND32() uxRand() + +#define ipconfigUSE_NETWORK_EVENT_HOOK 1 +#define ipconfigUDP_MAX_SEND_BLOCK_TIME_TICKS (5000 / portTICK_PERIOD_MS) + +#define ipconfigUSE_DHCP 0 +#define ipconfigMAXIMUM_DISCOVER_TX_PERIOD (120000 / portTICK_PERIOD_MS) + +#define ipconfigARP_CACHE_ENTRIES 6 +#define ipconfigMAX_ARP_RETRANSMISSIONS (5) +#define ipconfigMAX_ARP_AGE 150 + +#define ipconfigINCLUDE_FULL_INET_ADDR 1 + +#define ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS 60 +#define ipconfigEVENT_QUEUE_LENGTH (ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS + 5) + +#define ipconfigALLOW_SOCKET_SEND_WITHOUT_BIND 1 + +#define ipconfigUDP_TIME_TO_LIVE 128 +#define ipconfigTCP_TIME_TO_LIVE 128 + +#define ipconfigUSE_TCP (1) +#define ipconfigUSE_TCP_WIN (1) +#define ipconfigNETWORK_MTU 1500 +#define ipconfigUSE_DNS 0 + +#define ipconfigREPLY_TO_INCOMING_PINGS 1 +#define ipconfigSUPPORT_OUTGOING_PINGS 0 +#define ipconfigSUPPORT_SELECT_FUNCTION 1 +#define ipconfigFILTER_OUT_NON_ETHERNET_II_FRAMES 1 +#define ipconfigETHERNET_DRIVER_FILTERS_FRAME_TYPES 1 + +#define configWINDOWS_MAC_INTERRUPT_SIMULATOR_DELAY (20 / portTICK_PERIOD_MS) + +#define ipconfigPACKET_FILLER_SIZE 2 + +#define ipconfigTCP_WIN_SEG_COUNT 240 +#define ipconfigTCP_RX_BUFFER_LENGTH (2000) +#define ipconfigTCP_TX_BUFFER_LENGTH (2000) + +#define ipconfigIS_VALID_PROG_ADDRESS(x) ((x) != NULL) + +#define ipconfigTCP_HANG_PROTECTION (1) +#define ipconfigTCP_HANG_PROTECTION_TIME (30) + +#define ipconfigTCP_KEEP_ALIVE (1) +#define ipconfigTCP_KEEP_ALIVE_INTERVAL (20) + +#define ipconfigETHERNET_DRIVER_FILTERS_FRAME_TYPES 1 +#define ipconfigZERO_COPY_RX_DRIVER 0 +#define ipconfigZERO_COPY_TX_DRIVER 0 +#define ipconfigUSE_LINKED_RX_MESSAGES 1 diff --git a/examples/nucleo_h723zg/ethernet/main.cpp b/examples/nucleo_h723zg/ethernet/main.cpp new file mode 100644 index 0000000000..81516ba3b2 --- /dev/null +++ b/examples/nucleo_h723zg/ethernet/main.cpp @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2026, Henrik Hose + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include +#include + +#include +#include +#include + +using namespace Board; + +namespace Ethernet +{ +using RMII_Ref_Clk = GpioInputA1; +using RMII_Mdio = GpioA2; +using RMII_Crs_Dv = GpioInputA7; +using RMII_Tx_En = GpioOutputG11; +using RMII_Tx_D0 = GpioOutputG13; +using RMII_Tx_D1 = GpioOutputB13; +using RMII_Mdc = GpioOutputC1; +using RMII_Rx_D0 = GpioInputC4; +using RMII_Rx_D1 = GpioInputC5; +using Port = Eth; +} // namespace Ethernet + +UBaseType_t ulNextRand; + +void +vApplicationIPNetworkEventHook(eIPCallbackEvent_t eNetworkEvent); + +class NetworkInitTask : modm::rtos::Thread +{ +public: + NetworkInitTask() : Thread(configMAX_PRIORITIES - 1, 2048, "network_init") {} + + void + run() + { + uint8_t ipAddress[4]{192, 168, 10, 50}; + uint8_t netmask[4]{255, 255, 255, 0}; + uint8_t gatewayAddress[4]{0, 0, 0, 0}; + uint8_t dnsAddress[4]{0, 0, 0, 0}; + + uint8_t macAddress[]{0x02, 0x00, 0x00, 0x00, 0x00, 0x00}; + + time_t now; + time(&now); + ulNextRand = uint32_t(now); + + FreeRTOS_IPInit(ipAddress, netmask, gatewayAddress, dnsAddress, &macAddress[0]); + + vTaskDelete(0); + } +}; + +class HttpConnection +{ + static constexpr TickType_t shutdownTimeout{pdMS_TO_TICKS(5000)}; + static constexpr TickType_t receiveTimeout{pdMS_TO_TICKS(5000)}; + static constexpr TickType_t sendTimeout{pdMS_TO_TICKS(5000)}; + +public: + static constexpr char name[]{"HTTPConnection"}; + static constexpr uint8_t httpText[] = { + "HTTP/1.1 200 OK \r\n" + "Content-Type: text/html\r\n" + "Connection: keep-alive\r\n" + "\r\n" + "

Hello from your STM32H723!

"}; + + static void + run(void *parameter) + { + Socket_t connectedSocket = reinterpret_cast(parameter); + uint8_t *buffer = reinterpret_cast(pvPortMalloc(ipconfigTCP_MSS)); + + if (buffer) + { + FreeRTOS_setsockopt(connectedSocket, 0, FREERTOS_SO_RCVTIMEO, &receiveTimeout, + sizeof(receiveTimeout)); + FreeRTOS_setsockopt(connectedSocket, 0, FREERTOS_SO_SNDTIMEO, &sendTimeout, + sizeof(sendTimeout)); + + while (true) + { + std::memset(buffer, 0, ipconfigTCP_MSS); + const int32_t bytes = FreeRTOS_recv(connectedSocket, buffer, ipconfigTCP_MSS, 0); + if (bytes <= 0) break; + if (FreeRTOS_send(connectedSocket, httpText, sizeof(httpText) - 1, 0) < 0) break; + } + } + + FreeRTOS_shutdown(connectedSocket, FREERTOS_SHUT_RDWR); + const TickType_t shutdownTime{xTaskGetTickCount()}; + do + { + if (FreeRTOS_recv(connectedSocket, buffer, ipconfigTCP_MSS, 0) < 0) break; + } while ((xTaskGetTickCount() - shutdownTime) < shutdownTimeout); + + vPortFree(buffer); + FreeRTOS_closesocket(connectedSocket); + vTaskDelete(0); + } +}; + +class HttpServerListener +{ + static constexpr TickType_t receiveTimeout{portMAX_DELAY}; + static constexpr BaseType_t backlog{20}; + +public: + static constexpr char name[]{"HTTPListener"}; + + static void + run(void *) + { + Socket_t listeningSocket = + FreeRTOS_socket(FREERTOS_AF_INET, FREERTOS_SOCK_STREAM, FREERTOS_IPPROTO_TCP); + Socket_t connectedSocket; + + FreeRTOS_setsockopt(listeningSocket, 0, FREERTOS_SO_RCVTIMEO, &receiveTimeout, + sizeof(receiveTimeout)); + +#if ipconfigUSE_TCP_WIN == 1 + WinProperties_t winProps{ + .lTxBufSize = ipconfigTCP_TX_BUFFER_LENGTH, + .lTxWinSize = 2, + .lRxBufSize = ipconfigTCP_RX_BUFFER_LENGTH, + .lRxWinSize = 2, + }; + FreeRTOS_setsockopt(listeningSocket, 0, FREERTOS_SO_WIN_PROPERTIES, + reinterpret_cast(&winProps), sizeof(winProps)); +#endif + + struct freertos_sockaddr bindAddress{}; + bindAddress.sin_port = FreeRTOS_htons(80); + FreeRTOS_bind(listeningSocket, &bindAddress, sizeof(bindAddress)); + FreeRTOS_listen(listeningSocket, backlog); + + struct freertos_sockaddr clientAddress; + + while (true) + { + connectedSocket = FreeRTOS_accept(listeningSocket, &clientAddress, 0); + char buffer[16]; + FreeRTOS_inet_ntoa(clientAddress.sin_addr, buffer); + xTaskCreate(HttpConnection::run, HttpConnection::name, configMINIMAL_STACK_SIZE * 5, + reinterpret_cast(connectedSocket), configMAX_PRIORITIES - 3, 0); + } + } +}; + +NetworkInitTask networkInit; + +int +main() +{ + Board::initialize(); + Leds::setOutput(); + MODM_LOG_INFO << "\n\nReboot: Ethernet Example" << modm::endl; + + Ethernet::Port::connect< + Ethernet::RMII_Ref_Clk::Refclk, Ethernet::RMII_Mdc::Mdc, Ethernet::RMII_Mdio::Mdio, + Ethernet::RMII_Crs_Dv::Rcccrsdv, Ethernet::RMII_Tx_En::Txen, Ethernet::RMII_Tx_D0::Txd0, + Ethernet::RMII_Tx_D1::Txd1, Ethernet::RMII_Rx_D0::Rxd0, Ethernet::RMII_Rx_D1::Rxd1>(); + + modm::rtos::Scheduler::schedule(); + + return 0; +} + +void +vApplicationIPNetworkEventHook(eIPCallbackEvent_t eNetworkEvent) +{ + static bool taskCreated = false; + + if (eNetworkEvent != eNetworkUp) return; + + if (not taskCreated) + { + xTaskCreate(HttpServerListener::run, HttpServerListener::name, configMINIMAL_STACK_SIZE * 2, + 0, configMAX_PRIORITIES - 2, 0); + taskCreated = true; + } + + uint32_t ipAddress; + uint32_t netmask; + uint32_t gateway; + uint32_t dns; + char buffer[16]; + + FreeRTOS_GetAddressConfiguration(&ipAddress, &netmask, &gateway, &dns); + FreeRTOS_inet_ntoa(ipAddress, buffer); + MODM_LOG_DEBUG << "IP address: " << buffer << modm::endl; + FreeRTOS_inet_ntoa(netmask, buffer); + MODM_LOG_DEBUG << "Netmask : " << buffer << modm::endl; + FreeRTOS_inet_ntoa(gateway, buffer); + MODM_LOG_DEBUG << "Gateway : " << buffer << modm::endl; + FreeRTOS_inet_ntoa(dns, buffer); + MODM_LOG_DEBUG << "DNS : " << buffer << modm::endl; +} + +UBaseType_t +uxRand() +{ + static constexpr uint32_t ulMultiplier = 0x015a4e35UL; + static constexpr uint32_t ulIncrement = 1UL; + + ulNextRand = (ulMultiplier * ulNextRand) + ulIncrement; + return (int(ulNextRand >> 16UL) & 0x7fffUL); +} + +BaseType_t +xApplicationGetRandomNumber(uint32_t *pulNumber) +{ + *pulNumber = uxRand(); + return pdTRUE; +} + +uint32_t +ulApplicationGetNextSequenceNumber(uint32_t, uint16_t, uint32_t, uint16_t) +{ return uxRand(); } diff --git a/examples/nucleo_h723zg/ethernet/project.xml b/examples/nucleo_h723zg/ethernet/project.xml new file mode 100644 index 0000000000..fcfca94869 --- /dev/null +++ b/examples/nucleo_h723zg/ethernet/project.xml @@ -0,0 +1,11 @@ + + modm:nucleo-h723zg + + + + + modm:build:scons + modm:freertos:tcp:lan8742a + modm:processing:rtos + + From 102f550740a0cf381d0fdbcaa46c4f9bb8f3ae6d Mon Sep 17 00:00:00 2001 From: Henrik Hose Date: Tue, 28 Jul 2026 15:29:58 +0200 Subject: [PATCH 3/3] pleasing the CI --- README.md | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 4207036bf9..ba78f2c081 100644 --- a/README.md +++ b/README.md @@ -900,32 +900,34 @@ We have out-of-box support for many development boards including documentation. NUCLEO-L496ZG-P NUCLEO-L552ZE-Q +NUCLEO-N657X0-Q NUCLEO-U083RC -NUCLEO-U385RG-Q +NUCLEO-U385RG-Q NUCLEO-U575ZI-Q OLIMEXINO-STM32 Raspberry Pi Pico -SAMD21-MINI +SAMD21-MINI SAMD21-XPLAINED-PRO SAME54-XPLAINED-PRO SAME70-XPLAINED -SAMG55-XPLAINED-PRO +SAMG55-XPLAINED-PRO SAMV71-XPLAINED-ULTRA Smart Response XE STM32-F4VE -STM32F030-DEMO +STM32F030-DEMO THINGPLUS-RP2040 WEACT-C011F6 WEACT-G0B1CB -WEACT-H503CB +WEACT-H503CB WEACT-H523CE WEACT-H562RG WEACT-U585CI + @@ -999,67 +1001,69 @@ your specific needs. L3GD20 LAN8720A +LAN8742A LAWICEL LIS302DL LIS3DSH LIS3MDL LM75 -LP503x +LP503x LSM303A LSM6DS33 LSM6DSO LTC2984 MAX31855 -MAX31865 +MAX31865 MAX6966 MAX7219 MCP23x17 MCP2515 MCP3008 -MCP7941x +MCP7941x MCP990X MMC5603 MS5611 MS5837 NOKIA5110 -NRF24 +NRF24 TFT-DISPLAY PAT9125EL PCA8574 PCA9535 PCA9548A -PCA9685 +PCA9685 QMC5883L SH1106 SIEMENS-S65 SIEMENS-S75 SK6812 -SK9822 +SK9822 SSD1306 ST7586S ST7789 STTS22H STUSB4500 -SX1276 +SX1276 SX128X TCS3414 TCS3472 TLC594x TMP102 -TMP12x +TMP12x TMP175 TOUCH2046 VL53L0 VL6180 WS2812 +