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
+
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
+
+ ../../../build/nucleo_h723zg/ethernet
+
+
+ modm:build:scons
+ modm:freertos:tcp:lan8742a
+ modm:processing:rtos
+
+
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")