Skip to content

cpu/lpc1768: add ethernet driver#22305

Merged
basilfx merged 7 commits into
RIOT-OS:masterfrom
basilfx:feature/lpc1768_eth
Jun 4, 2026
Merged

cpu/lpc1768: add ethernet driver#22305
basilfx merged 7 commits into
RIOT-OS:masterfrom
basilfx:feature/lpc1768_eth

Conversation

@basilfx

@basilfx basilfx commented May 19, 2026

Copy link
Copy Markdown
Member

Contribution description

Note

Development of this PR happens in parallel to the EFM32 ethernet driver in #22306. Any feedback on this one is likely applicable to the other one as well. I will try to keep both PRs in sync where it matters.

This PR adds an ethernet driver for the LPC1768. Although this CPU is ancient and not the best-supported one, it is still an active one. It is also very well supported by other projects and I have a board (Seeeduino Arch Pro) that has ethernet support.

I have no former experience with ethernet drivers, so that's why I chose this one. Together with the help of LLMs, I figured this would be an OK challenge, which is a small side-step to get Modbus TCP support testable.

I took the ethernet driver of the SAM0 and the STM32 as a basis, and modelled it closely to that one.

What is provided:

  • Low-level driver with netdev driver on top of it.
  • Separate modules (lpc1768_eth_link_up, lpc1768_eth_auto) like the STM32.
  • lwIP/GNRC auto-initialization.
  • Kconfig tunables for buffers/timeouts/etc.

Testing procedure

The only LPC1768 board with ethernet in RIOT's code base, is the Seeeduino Arch Pro. I don't think many people will own this board, so it is noted that actual testing will a be a bit harder.

At this point, I would rather receive feedback on the general structure and adoption. I will then do my best to collect as much evidence as possible.

Testing has been performed as follows:

  • Compile tests (mixes options and toolchains, see below)
  • Benchmark UDP
  • Driver test in tests/drivers/lpc1768_eth
  • Ping test (including fragmentation and large frames, >12 hours)
  • lwIP iPerf (depends on examples/networking/misc/lwiperf: add application #22286).
  • IPv4/IPv6.
  • Monkey testing (link up/down etc).

With lpc1768_eth_auto enabled, the link speed is ~15-20 MBit/sec (measured using lwIP iPerf). Without, it is ~75-100 KBit/sec (classical issue due to link negotation issues).

Compile test
#!/bin/sh

set -e

BOARD=seeeduino_arch-pro make -C tests/drivers/lpc1768_eth -j 14
BOARD=seeeduino_arch-pro USEMODULE=lpc1768_eth_link_up make -C tests/drivers/lpc1768_eth -j 14
BOARD=seeeduino_arch-pro USEMODULE=lpc1768_eth_auto make -C tests/drivers/lpc1768_eth -j 14

BOARD=seeeduino_arch-pro make -C examples/networking/misc/benchmark_udp -j 14
BOARD=seeeduino_arch-pro USEMODULE=lpc1768_eth_link_up make -C examples/networking/misc/benchmark_udp -j 14
BOARD=seeeduino_arch-pro USEMODULE=lpc1768_eth_auto make -C examples/networking/misc/benchmark_udp -j 14

BOARD=seeeduino_arch-pro LWIP=1 make -C examples/networking/misc/benchmark_udp -j 14
BOARD=seeeduino_arch-pro USEMODULE=lpc1768_eth_link_up LWIP=1 make -C examples/networking/misc/benchmark_udp -j 14
BOARD=seeeduino_arch-pro USEMODULE=lpc1768_eth_auto LWIP=1 make -C examples/networking/misc/benchmark_udp -j 14

# lwIP IPerf application is still a PR
# BOARD=seeeduino_arch-pro LWIP_IPV4=1 LWIP_IPV6=1 make -C examples/networking/misc/lwiperf -j 14
# BOARD=seeeduino_arch-pro USEMODULE=lpc1768_eth_link_up LWIP_IPV4=1 LWIP_IPV6=1 make -C examples/networking/misc/lwiperf -j 14
# BOARD=seeeduino_arch-pro USEMODULE=lpc1768_eth_auto LWIP_IPV4=1 LWIP_IPV6=1 make -C examples/networking/misc/lwiperf -j 14

BOARD=seeeduino_arch-pro LWIP_IPV4=1 LWIP_IPV6=1 make -C examples/basic/default -j 14
BOARD=seeeduino_arch-pro USEMODULE=lpc1768_eth_link_up LWIP_IPV4=1 LWIP_IPV6=1 make -C examples/basic/default -j 14
BOARD=seeeduino_arch-pro USEMODULE=lpc1768_eth_auto LWIP_IPV4=1 LWIP_IPV6=1 make -C examples/basic/default -j 14

TOOLCHAIN=llvm BOARD=seeeduino_arch-pro make -C tests/drivers/lpc1768_eth -j 14
TOOLCHAIN=llvm BOARD=seeeduino_arch-pro USEMODULE=lpc1768_eth_link_up make -C tests/drivers/lpc1768_eth -j 14
TOOLCHAIN=llvm BOARD=seeeduino_arch-pro USEMODULE=lpc1768_eth_auto make -C tests/drivers/lpc1768_eth -j 14

TOOLCHAIN=llvm BOARD=seeeduino_arch-pro make -C examples/networking/misc/benchmark_udp -j 14
TOOLCHAIN=llvm BOARD=seeeduino_arch-pro USEMODULE=lpc1768_eth_link_up make -C examples/networking/misc/benchmark_udp -j 14
TOOLCHAIN=llvm BOARD=seeeduino_arch-pro USEMODULE=lpc1768_eth_auto make -C examples/networking/misc/benchmark_udp -j 14

TOOLCHAIN=llvm BOARD=seeeduino_arch-pro LWIP=1 make -C examples/networking/misc/benchmark_udp -j 14
TOOLCHAIN=llvm BOARD=seeeduino_arch-pro USEMODULE=lpc1768_eth_link_up LWIP=1 make -C examples/networking/misc/benchmark_udp -j 14
TOOLCHAIN=llvm BOARD=seeeduino_arch-pro USEMODULE=lpc1768_eth_auto LWIP=1 make -C examples/networking/misc/benchmark_udp -j 14

# lwIP IPerf application is still a PR
# TOOLCHAIN=llvm BOARD=seeeduino_arch-pro LWIP_IPV4=1 LWIP_IPV6=1 make -C examples/networking/misc/lwiperf -j 14
# TOOLCHAIN=llvm BOARD=seeeduino_arch-pro USEMODULE=lpc1768_eth_link_up LWIP_IPV4=1 LWIP_IPV6=1 make -C examples/networking/misc/lwiperf -j 14
# TOOLCHAIN=llvm BOARD=seeeduino_arch-pro USEMODULE=lpc1768_eth_auto LWIP_IPV4=1 LWIP_IPV6=1 make -C examples/networking/misc/lwiperf -j 14

TOOLCHAIN=llvm BOARD=seeeduino_arch-pro LWIP_IPV4=1 LWIP_IPV6=1 make -C examples/basic/default -j 14
TOOLCHAIN=llvm BOARD=seeeduino_arch-pro USEMODULE=lpc1768_eth_link_up LWIP_IPV4=1 LWIP_IPV6=1 make -C examples/basic/default -j 14
TOOLCHAIN=llvm BOARD=seeeduino_arch-pro USEMODULE=lpc1768_eth_auto LWIP_IPV4=1 LWIP_IPV6=1 make -C examples/basic/default -j 14

Issues/PRs references

None

Declaration of AI-Tools / LLMs usage:

AI-Tools / LLMs that were used are:

  • Claude Code generated the first (non-working) version of the driver in cpu/lpc1768. I then refined, reworked and tested it myself. I did ask Claude Code to analyze bugs I encountered during testing. The final PR review was also done with Claude Code, but changes were applied manually. The test application, auto-init adoption and several other parts are simply copy-paste of existing drivers.

@github-actions github-actions Bot added Platform: ARM Platform: This PR/issue effects ARM-based platforms Area: network Area: Networking Area: doc Area: Documentation Area: tests Area: tests and testing framework Area: build system Area: Build system Area: pkg Area: External package ports Area: drivers Area: Device drivers Area: boards Area: Board ports Area: cpu Area: CPU/MCU ports Area: sys Area: System Area: Kconfig Area: Kconfig integration labels May 19, 2026
@basilfx basilfx added the Type: new feature The issue requests / The PR implemements a new feature for RIOT label May 19, 2026
@crasbe crasbe added the CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR label May 19, 2026
@basilfx basilfx force-pushed the feature/lpc1768_eth branch from 1393b11 to 6a742ef Compare May 19, 2026 10:03
@riot-ci

riot-ci commented May 19, 2026

Copy link
Copy Markdown

Murdock results

✔️ PASSED

d7ccbdc features: add lpc1768_eth to features

Success Failures Total Runtime
11124 0 11124 16m:17s

Artifacts

@basilfx basilfx force-pushed the feature/lpc1768_eth branch from e7c2cc8 to 257d4a6 Compare May 19, 2026 21:08
Comment thread cpu/lpc1768/include/drivers/lpc1768_eth_netdev.h
Comment thread cpu/lpc1768/periph/eth.c Outdated
Comment thread cpu/lpc1768/periph/eth.c Outdated
Comment on lines +212 to +213
while (LPC_EMAC->MIND & MIND_BUSY) {
if (ztimer_now(ZTIMER_MSEC) >= deadline) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are spinning for 100ms, better add a ztimer_sleep(ZTIMER_MSEC, 1) in here

@basilfx basilfx May 31, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added (also in the auto-negotiate function).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@benpicco after testing, I noticed that this won't work. The _mii_wait_idle method can be invoked from the link timer, so doing a sleep in an ISR context, which results in hard fault.

The 100ms is really a safe-guard. The wait should be in the µs range rather than ms range. Having a fail-safe is nice to have though.

Moving the link timer into netdev's ISR is another option I considered, but I don't see a benefit.

I'll add a comment to explain instead.

(this problem is also applicable to the EFM32 ethernet driver.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving the link timer into netdev's ISR is another option I considered, but I don't see a benefit.

The benefit would be not blocking interrupts for up to 100ms

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Let me try to see how this works out.

I'll add this as an additional fixup to the intermediate squash (not yet pushed).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have moved this to the event handler and it works.

The 1 ms sleep with a max of 100 ms is something I have to reconsider. Typical wait times are way-way less, so if the first wait fails, it will now wait for orders of magnitude longer for the second try. This will block other events to be processed. I haven't measured how often this happens, but given that the link timer runs every 1000 ms, I can imagine this to happen.

I could go back to a simply (bounded) busy-wait without ztimer, or reduce the sleep size to µs range instead.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ultimately, I changed it to a µs-bounded loop. The time-out is not 1000 µs, which should (at 2.5 MHz PHY interface connection) be about 20 times the worst case scenario.

I did not add a sleep: other drivers do a unbounded busy-wait, so I see this as an improvement already. The sooner it completes in the event handler, the faster it can continue receiving.

Comment thread cpu/lpc1768/periph/eth.c Outdated
Comment thread cpu/lpc1768/periph/eth.c
Comment thread cpu/lpc1768/periph/eth.c Outdated
Comment thread cpu/lpc1768/periph/eth.c Outdated
@basilfx

basilfx commented May 29, 2026

Copy link
Copy Markdown
Member Author

Thank you @benpicco for the review. I made a few quick adjustments already, but will leave the remaining/bigger ones for coming week, as I don't have access to hardware right now :-)

@basilfx

basilfx commented May 29, 2026

Copy link
Copy Markdown
Member Author

Update on the preliminary test results: a ping test with 16-byte increases up to 2048 bytes has been running non-stop for the past two-ish weeks. Apart from a few switch reboots (UniFi stuff), the MCU did not crash/reboot.

@basilfx basilfx force-pushed the feature/lpc1768_eth branch from 27b0ee4 to 333d248 Compare June 1, 2026 21:14
@basilfx

basilfx commented Jun 1, 2026

Copy link
Copy Markdown
Member Author

@benpicco Same as with the EFM32, OK if I do an intermediate squash?

@benpicco

benpicco commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Sure, got ahead and squash

@basilfx

basilfx commented Jun 2, 2026

Copy link
Copy Markdown
Member Author

Update on the testing (using 333d248), also including the EFM32, because I do them simultaneously, and my screenshots include both results.

The tests I did:

  • Ping test (16 byte increments) alternated with iPerf test: stable (13 hours)
    • Script is written in such a way that exit code 1 would abort the test.
  • Flood ping test: stable-ish (2 hours), 0.0031% drop for LPC1768, negligible for EFM32
Screenshots of the tests Scherm­afbeelding 2026-06-02 om 12 46 43 Scherm­afbeelding 2026-06-02 om 12 50 00 Scherm­afbeelding 2026-06-02 om 15 28 42

The LPC1768 driver has troubles with flood pings, even though the other tests were stable. I did notice that the throughput for the LPC1768 was fluctuating, while the EFM32 was almost exactly the same for each run.

The flood ping test confirms this: the driver cannot keep up. I found that interesting, given that the LPC1768 is higher clocked (100 MHz vs 72 MHz) and both drivers share a very similar structure. After a bit of help from Claude, I measured the 'RX Finished' interrupt, which indicate that the producer and consumer ring buffer indexes are equal. This is not an issue immediately, but that means that if the code services the interrupts too slow, the next frame will be dropped.

I have tried several fixes, such as moving the link timer to the event callback (so it does not block ethernet interrupts), adding/removing the ztimer_sleep instead of busy-looping for MII operations and dropping error frames earlier in receive path. None of that had an impact, or at least it was hard to measure. The thing that has an impact, was reconfiguring the ring buffers. The LPC1768 has peripheral RAM of 32 KiB. Half of it is reserved for USB. Reconfiguring from 4 RX and 4 TX buffers to 8 RX and 2 TX buffers solved the issue. The 4 RX and 4 TX buffer configuration seems to be the use in other projects too, so I consider this to be a sane default. The user can always reconfigure this.

This makes me believe that the flood test is really pushing the limits. I don't know if the observed loss is something to worry about. I don't have other boards to test with. I will do some more testing and see if I can improve, but maybe it is good enough (for now) already.

Edit: here is another run of last night:

LPC1768:

--- 10.0.0.170 ping statistics ---
101585084 packets transmitted, 101582557 received, 0.00248757% packet loss, time 30939638ms
rtt min/avg/max/mdev = 0.167/0.291/2.203/0.039 ms, ipg/ewma 0.304/0.251 ms
( sudo ping -f 10.0.0.170; )  411.01s user 1806.58s system 7% cpu 8:35:40.33 total

EFM32:

--- 10.0.0.185 ping statistics ---
96921762 packets transmitted, 96921761 received, 1.03176e-06% packet loss, time 31040439ms
rtt min/avg/max/mdev = 0.238/0.306/2.288/0.035 ms, ipg/ewma 0.320/0.352 ms
( sudo ping -f 10.0.0.185; )  390.45s user 1770.77s system 6% cpu 8:37:21.14 total

@basilfx basilfx force-pushed the feature/lpc1768_eth branch 3 times, most recently from d4a35a8 to 3dd395b Compare June 3, 2026 21:39
@basilfx

basilfx commented Jun 3, 2026

Copy link
Copy Markdown
Member Author

@benpicco the last three fixup commits are the latest changes since the intermediate squash.

I'm sorry that it is a bit bigger than expected, but looking into the timing/interrupt 'issue' made me find a few other improvements. To summarize:

  • Link timer now serviced in the event handler.
  • MII idle check still busy-waiting, but with 1000 us bound instead of 1000 ms. Adding a sleep would increase the chance of waiting too long, blocking the interrupt handler unnecessary. Other drivers only do unbounded busy-wait.
  • confirm_send expanded
  • Enable more interrupts to start receiving (based on this mbed driver).
  • Revisit documentation and modules.

Same applies to EFM32.

These changes are currently being tested. I did some more research on acceptable flood ping drop rate, comparing it to other (cable) connected appliances in my home, and I guess that less than 0.1% is still acceptable for an embedded device. I can probably not improve this any further, and leave it up to the end-user to maybe increase the number of RX buffers if really necessary.

Unless there are some blocking findings, I am not planning to overhaul this once more ;-)

@basilfx

basilfx commented Jun 4, 2026

Copy link
Copy Markdown
Member Author

Ping + iPerf tests results are in. No packet loss during the pings. Asked Claude to draw it nicely. You can see the sawtooth pattern, which is due to the increasing packet size on every run.

LPC1768:

lpc log

EFM32:

efm32 log

In all honesty, there are no differences. But it all still works. The flood ping tests are now running.

@basilfx

basilfx commented Jun 4, 2026

Copy link
Copy Markdown
Member Author

And the flood ping tests results (3 hours):

LPC1768 (883 packets lost, but less than 0.1%):

--- 10.0.0.170 ping statistics ---
37156965 packets transmitted, 37156082 received, 0.00237641% packet loss, time 11133888ms
rtt min/avg/max/mdev = 0.168/0.285/2.278/0.032 ms, ipg/ewma 0.299/0.253 ms
( sudo ping -f 10.0.0.170; )  168.00s user 745.83s system 8% cpu 3:05:36.44 total

EFM32 (1 packet lost):

--- 10.0.0.185 ping statistics ---
34529920 packets transmitted, 34529919 received, 2.89604e-06% packet loss, time 11140018ms
rtt min/avg/max/mdev = 0.244/0.306/1.607/0.040 ms, ipg/ewma 0.322/0.310 ms
( sudo ping -f 10.0.0.185; )  161.45s user 722.39s system 7% cpu 3:05:43.18 total

The results are still very similar to the previous run.

@benpicco

benpicco commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Go ahead and squash then

basilfx added 7 commits June 4, 2026 16:31
Although the ethernet peripheral isn't a common peripheral, it is best
placed under the peripheral configuration in the Kconfig menu.
This commit adds a driver for the ethernet peripheral. It is split
into a low-level peripheral driver, and a higher-level netdev driver.

Two pseudo-modules are introduced for link state monitoring and auto
negotiation.

Kconfig support has been added too.
@basilfx basilfx force-pushed the feature/lpc1768_eth branch from 3dd395b to d7ccbdc Compare June 4, 2026 14:33
@basilfx basilfx enabled auto-merge June 4, 2026 16:58
@basilfx basilfx added this pull request to the merge queue Jun 4, 2026
Merged via the queue into RIOT-OS:master with commit 8a14818 Jun 4, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: boards Area: Board ports Area: build system Area: Build system Area: cpu Area: CPU/MCU ports Area: doc Area: Documentation Area: drivers Area: Device drivers Area: Kconfig Area: Kconfig integration Area: network Area: Networking Area: pkg Area: External package ports Area: sys Area: System Area: tests Area: tests and testing framework CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Platform: ARM Platform: This PR/issue effects ARM-based platforms Type: new feature The issue requests / The PR implemements a new feature for RIOT

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants