-
Notifications
You must be signed in to change notification settings - Fork 171
Add STM32H7 Ethernet support with lwIP #1373
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
WasabiFan
wants to merge
39
commits into
modm-io:develop
Choose a base branch
from
WasabiFan:h7-lwip-ethernet
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
39 commits
Select commit
Hold shift + click to select a range
f1e6ebb
[core] Add helper to check if in ISR context
WasabiFan c812ccc
[freertos] Rename Ethernet driver implementation
WasabiFan 68a07fe
[architecture] Add Ethernet interface types
WasabiFan 09631c1
[platform] Add unique MAC address helper from STM32 device ID
WasabiFan bea93a4
fixup! [platform] Add unique MAC address helper from STM32 device ID
WasabiFan 29c4b64
[driver] Add LAN87xx Ethernet PHY drivers
WasabiFan ce18a52
[eth] Fix STM32F4/F7 PHY software reset
WasabiFan 64fff06
[rcc] Add RX/TX clocks for STM32H7/H5 Ethernet
WasabiFan 39aa734
fixup! [rcc] Add RX/TX clocks for STM32H7/H5 Ethernet
salkinium 78430c6
[stm32] Enable SRAM3 clock at startup
WasabiFan ba5bfc8
[eth] Add STM32H7/H5 Ethernet MAC driver
WasabiFan 3660fd0
fixup! [eth] Add STM32H7/H5 Ethernet MAC driver
salkinium e3fac5c
fixup! [eth] Add STM32H7/H5 Ethernet MAC driver
WasabiFan 7f66cfc
fixup! [eth] Add STM32H7/H5 Ethernet MAC driver
WasabiFan dba3457
[ext] Add partial lwIP submodule
WasabiFan 51fb703
[lwip] Add lwIP module and configs for NO_SYS API
WasabiFan 2a0d579
fixup! [lwip] Add lwIP module and configs for NO_SYS API
WasabiFan 439f459
fixup! [lwip] Add lwIP module and configs for NO_SYS API
WasabiFan 4768ce5
[lwip] Add lwIP Ethernet adapter
WasabiFan 72f1b65
fixup! [lwip] Add lwIP Ethernet adapter
WasabiFan 48da939
[stdc] Support opting out of Newlib Nano
WasabiFan 847e200
fixup! [stdc] Support opting out of Newlib Nano
WasabiFan 03ab2d4
fixup! [stdc] Support opting out of Newlib Nano
WasabiFan bef7a37
[board] Add Ethernet clocks/pins to STM32H7/H5 boards
WasabiFan 0e8a11d
fixup! [board] Add Ethernet clocks/pins to STM32H7/H5 boards
salkinium 2345d85
[test] Add STM32H7 Ethernet and lwIP tests
WasabiFan f76faf7
fixup! [test] Add STM32H7 Ethernet and lwIP tests
WasabiFan f7158d6
fixup! [test] Add STM32H7 Ethernet and lwIP tests
WasabiFan 9b9c6b9
[ci] Add lwip tests to CI
WasabiFan a15d138
[examples] Add STM32H7/H5 Ethernet examples
WasabiFan 1d76ba6
fixup! [examples] Add STM32H7/H5 Ethernet examples
salkinium cb32847
fixup! [examples] Add STM32H7/H5 Ethernet examples
WasabiFan 3b07782
fixup! [examples] Add STM32H7/H5 Ethernet examples
WasabiFan b313c2c
fixup! [examples] Add STM32H7/H5 Ethernet examples
WasabiFan a4832c7
[examples] Add lwIP echo host test
WasabiFan 276fb5c
fixup! [examples] Add lwIP echo host test
WasabiFan 542af58
[docs] Update support matrix for STM32H7 Ethernet
WasabiFan e5fed9a
fixup! [docs] Update support matrix for STM32H7 Ethernet
WasabiFan b739338
[assert] Remove packing from assertion info structs
WasabiFan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| # STM32 Ethernet MAC | ||
|
|
||
| This example exercises the STM32H5/H7 Ethernet MAC and LAN8742A PHY directly, | ||
| without a network stack. It sends one broadcast Ethernet frame per second and | ||
| alternates between the copied transmit API and the acquire/fill/commit API. | ||
| The MAC configuration uses its default locally administered address derived | ||
| from the STM32 unique identifier. The lwIP example uses the same driver | ||
| helper. The frames use | ||
| `0x88b5`, which the | ||
| [IEEE Registration Authority EtherType registry](https://standards-oui.ieee.org/ethertype/eth.txt) | ||
| assigns as Local Experimental EtherType 1 for public prototype and | ||
| vendor-specific protocol development. They are not IP packets. | ||
| The typed MAC configuration selects `ChecksumMode::Software`, leaving hardware | ||
| checksum offload disabled because these frames contain no IP or transport | ||
| checksum. | ||
| Initialization succeeds with the cable unplugged; periodic link service reports | ||
| `down`, `negotiating`, and `up` transitions, and transmission begins only when | ||
| the negotiated link is up. | ||
|
|
||
| Applications that need IP networking should use the | ||
| [lwIP Ethernet example](../ethernet_lwip_raw) instead. | ||
|
|
||
| Build from this directory: | ||
|
|
||
| ```sh | ||
| lbuild build | ||
| scons -Q build=release | ||
| scons -Q build=release program | ||
| ``` | ||
|
|
||
| Connect the board Ethernet port to the capture interface and start the capture | ||
| before running the `program` command. Monitor the ST-LINK virtual COM port at | ||
| 115200 baud, 8 data bits, no parity, and 1 stop bit. The serial log reports | ||
| initialization, link transitions, the alternating transmit APIs used, and the | ||
| sequence number. | ||
|
|
||
| Use this Wireshark display filter: | ||
|
|
||
| ```text | ||
| eth.type == 0x88b5 | ||
| ``` | ||
|
|
||
| Or capture from a command prompt, replacing `<interface>` with the capture | ||
| interface reported by `tshark -D`: | ||
|
|
||
| ```text | ||
| tshark -i <interface> -f "ether proto 0x88b5" -V | ||
| ``` | ||
|
|
||
| Both transmit APIs construct the same frame layout. Each 60-byte frame contains | ||
| the ASCII marker `modm-stm32h7-eth` followed by a little-endian 32-bit sequence | ||
| number. The destination is broadcast so the frames remain visible when the | ||
| board and capture host are connected through a switch. | ||
|
|
||
| Validation is manual: confirm that capture lengths are 60 bytes, source MAC | ||
| addresses match the serial log, destination addresses are broadcast, EtherType | ||
| is `0x88b5`, markers are intact, and sequence numbers increase without gaps. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| /* | ||
| * Copyright (c) 2026, Kaelin Laundry | ||
| * | ||
| * 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/. | ||
| */ | ||
| // ---------------------------------------------------------------------------- | ||
|
|
||
| // Sends raw IEEE 802 local experimental frames without a network stack. | ||
|
|
||
| #include <modm/board.hpp> | ||
| #include <modm/driver/ethernet/lan8742a.hpp> | ||
|
|
||
| #include <algorithm> | ||
| #include <array> | ||
| #include <cstddef> | ||
| #include <cstdint> | ||
| #include <span> | ||
|
|
||
| using namespace Board; | ||
| using namespace std::chrono_literals; | ||
|
|
||
| namespace | ||
| { | ||
|
|
||
| using Mac = modm::platform::EthernetMac; | ||
| using Phy = modm::Lan8742a<Board::eth::PhyAddress>; | ||
|
|
||
| constexpr std::size_t FrameSize = 60; | ||
| constexpr uint16_t ExperimentalEtherType = 0x88b5; | ||
| constexpr std::array<uint8_t, 16> PayloadMarker{{ | ||
| 'm', 'o', 'd', 'm', '-', 's', 't', 'm', '3', '2', 'h', '7', '-', 'e', 't', 'h' | ||
| }}; | ||
|
|
||
| static void | ||
| logMacAddress(const modm::ethernet::MacAddress& address) | ||
| { | ||
| MODM_LOG_INFO << "MAC "; | ||
| for (std::size_t index = 0; index < address.size(); ++index) { | ||
| MODM_LOG_INFO << modm::hex << address[index] << modm::ascii; | ||
| if (index + 1 < address.size()) { | ||
| MODM_LOG_INFO << ':'; | ||
| } | ||
| } | ||
| MODM_LOG_INFO << modm::endl; | ||
| } | ||
|
|
||
| void | ||
| fillFrame(std::span<uint8_t> frame, const std::array<uint8_t, 6>& macAddress, | ||
| uint32_t sequence) | ||
| { | ||
| std::fill(frame.begin(), frame.end(), 0); | ||
| std::fill_n(frame.begin(), 6, uint8_t{0xff}); | ||
| std::copy(macAddress.begin(), macAddress.end(), frame.begin() + 6); | ||
| frame[12] = uint8_t(ExperimentalEtherType >> 8); | ||
| frame[13] = uint8_t(ExperimentalEtherType); | ||
| std::copy(PayloadMarker.begin(), PayloadMarker.end(), frame.begin() + 14); | ||
| frame[30] = uint8_t(sequence); | ||
| frame[31] = uint8_t(sequence >> 8); | ||
| frame[32] = uint8_t(sequence >> 16); | ||
| frame[33] = uint8_t(sequence >> 24); | ||
| } | ||
|
|
||
| const char* | ||
| linkStateName(modm::ethernet::LinkState state) | ||
| { | ||
| switch (state) | ||
| { | ||
| case modm::ethernet::LinkState::Down: return "down"; | ||
| case modm::ethernet::LinkState::Negotiating: return "negotiating"; | ||
| case modm::ethernet::LinkState::Up: return "up"; | ||
| } | ||
| return "unknown"; | ||
| } | ||
|
|
||
| void | ||
| sendCopiedFrame(const std::array<uint8_t, 6>& macAddress, uint32_t sequence) | ||
| { | ||
| std::array<uint8_t, FrameSize> frame{}; | ||
| fillFrame(frame, macAddress, sequence); | ||
| const auto result = Mac::transmit(frame); | ||
| modm_assert(result.error != Mac::TransmitError::Busy, "eth.tx.copy.busy", | ||
| "Copied Ethernet transmit ring is busy"); | ||
| modm_assert(result, "eth.tx.copy", "Copied Ethernet transmit failed", | ||
| static_cast<uintptr_t>(result.error)); | ||
| MODM_LOG_INFO << "TX copied sequence=" << sequence << modm::endl; | ||
| } | ||
|
|
||
| void | ||
| sendLeaseFrame(const std::array<uint8_t, 6>& macAddress, uint32_t sequence) | ||
| { | ||
| auto lease = Mac::acquireTransmitBuffer(FrameSize); | ||
| modm_assert(lease.error() != Mac::TransmitError::Busy, "eth.tx.acquire.busy", | ||
| "Ethernet transmit ring is busy"); | ||
| modm_assert(lease, "eth.tx.acquire", "Failed to acquire Ethernet transmit buffer", | ||
| static_cast<uintptr_t>(lease.error())); | ||
|
|
||
| fillFrame(lease.buffer(), macAddress, sequence); | ||
| const auto result = lease.commit(); | ||
| modm_assert(result, "eth.tx.commit", "Acquired Ethernet transmit commit failed", | ||
| static_cast<uintptr_t>(result.error)); | ||
| MODM_LOG_INFO << "TX acquired sequence=" << sequence << modm::endl; | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| int | ||
| main() | ||
| { | ||
| Board::initialize(); | ||
| MODM_LOG_INFO << "\n\nReboot: STM32H7 raw Ethernet MAC example" << modm::endl; | ||
|
|
||
| Mac::connect<Board::eth::RefClk::Refclk, | ||
| Board::eth::Mdc::Mdc, | ||
| Board::eth::Mdio::Mdio, | ||
| Board::eth::CrsDv::Rcccrsdv, | ||
| Board::eth::TxEn::Txen, | ||
| Board::eth::Txd0::Txd0, | ||
| Board::eth::Txd1::Txd1, | ||
| Board::eth::Rxd0::Rxd0, | ||
| Board::eth::Rxd1::Rxd1>(); | ||
|
|
||
| const Mac::Configuration configuration{ | ||
| .checksumMode = Mac::ChecksumMode::Software, | ||
| }; | ||
| const auto macAddress = configuration.macAddress; | ||
| const auto initialized = | ||
| Mac::initialize<Board::SystemClock, Mac::MediaInterface::RMII>(configuration); | ||
| modm_assert(initialized, "eth.initialize", "Ethernet MAC initialization failed", | ||
| static_cast<uintptr_t>(initialized.error)); | ||
| const auto phyInitialized = Phy::initialize<Mac>(); | ||
| modm_assert(phyInitialized, "eth.phy.initialize", "Ethernet PHY initialization failed", | ||
| static_cast<uintptr_t>(phyInitialized.error)); | ||
|
|
||
| logMacAddress(macAddress); | ||
| MODM_LOG_INFO << "EtherType 0x88b5" << modm::endl; | ||
|
|
||
| bool haveLinkState = false; | ||
| modm::ethernet::LinkState previousLinkState = modm::ethernet::LinkState::Down; | ||
| uint32_t sequence = 0; | ||
| uint8_t serviceTicks = 0; | ||
| while (true) | ||
| { | ||
| const auto observed = Phy::readLinkStatus<Mac>(); | ||
| modm_assert(observed, "eth.phy.link", "Ethernet PHY link read failed", | ||
| static_cast<uintptr_t>(observed.error)); | ||
| const auto link = Mac::notifyUpdatedLinkStatus(observed.status); | ||
| modm_assert(link, "eth.link", "Ethernet MAC link update failed", | ||
| static_cast<uintptr_t>(link.error)); | ||
| if (not haveLinkState or link.status.state != previousLinkState) { | ||
| previousLinkState = link.status.state; | ||
| haveLinkState = true; | ||
| MODM_LOG_INFO << "Link " << linkStateName(previousLinkState) << modm::endl; | ||
| } | ||
|
|
||
| if (++serviceTicks == 10) { | ||
| serviceTicks = 0; | ||
| if (previousLinkState == modm::ethernet::LinkState::Up) { | ||
| if ((sequence & 1u) == 0) { | ||
| sendCopiedFrame(macAddress, sequence); | ||
| } | ||
| else { | ||
| sendLeaseFrame(macAddress, sequence); | ||
| } | ||
| sequence++; | ||
| } | ||
| } | ||
|
|
||
| modm::delay(100ms); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| <library> | ||
| <extends>modm:nucleo-h753zi</extends> | ||
| <!-- <extends>modm:nucleo-h563zi</extends> --> | ||
| <!-- <extends>modm:nucleo-h743zi</extends> --> | ||
| <!-- <extends>modm:nucleo-h723zg</extends> --> | ||
| <options> | ||
| <option name="modm:build:build.path">../../../build/generic/ethernet</option> | ||
| </options> | ||
| <modules> | ||
| <module>modm:build:scons</module> | ||
| <module>modm:platform:eth</module> | ||
| <module>modm:driver:lan8742a</module> | ||
| </modules> | ||
| </library> |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't modm_assert a bit heavy here? How severe are these failures? Does it warrant abandoning executing entirely?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a real app I would recommend handling these more gracefully. These calls would fail if the link went down, for example, which is probably not a good reason to crash. I figured it was easier to understand the logic flow if the example used asserts. Is the convention for examples that it should implement recommended error handling patterns instead?