Skip to content

shared/tinyusb: Add support for USB Network (NCM) interface. #16459

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
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

andrewleech
Copy link
Contributor

@andrewleech andrewleech commented Dec 20, 2024

Summary

This PR provides a network interface over the USB connection which is connected to the existing LWIP network stack.

It includes integration into only rp2 port so far via a new RPI_PICO/USB_NET variant build.

dmesg after plugging in pico with RPI_PICO/USB_NET variant build.
image

The regular usb serial port is still provided for mpremote. Use that to enable the network interface:
image

DHCP server is being provided by the pico over the usb network interface, so the PC can be provided an IP address automatically:
image

After than, sockets / service can be used as per any normal network connection:

image
image

Any port based on TinyUSB will also be able to use this with minimal effort, with caveats on needing LWIP integration as well and the board / chip will need to be large enough to support the ~20KB ram needs of LWIP.

Building

This can be enabled on a cmake base port like so:

CFLAGS="-DMICROPY_HW_NETWORK_USBNET=1" make -C ports/rp2 BOARD=RPI_PICO2_W

On a make based port:

make -j -C ports/mimxrt BOARD=SEEED_ARCH_MIX CFLAGS_EXTRA="-DMICROPY_HW_NETWORK_USBNET=1 -DLWIP_IPV6=1"

Testing

  • PICO (minimal testing so far)
  • STM32F765 running microdot ( with my stm32 tinyusb PR merged in ).
  • mimx/SEEED_ARCH_MIX ( minimal connectivity testing )

This has had minimal testing so far on the PICO as per shown above and more extensive tesing on a STM32F765 running microdot.

The mimx build above does not appear to be working, once flashed it appears as COM and NCM devices but both are locked up / not usable.

Trade-offs and Alternatives

As mentioned above LWIP has overheads to include in a build, so for smaller parts using USB with a custom protocol for communication between device and pc can be smaller. However providing a network connection over the USB can open up use of many different existing servers and services for communicating with a device.

TODO

Test is this coexists with dynamic usb devices, ensure it can be kept active when creating dynamic devices and ensure if doesn't crash / is handled cleanly if dynamic setup disables the builtins.


This work was funded by Planet Innovation.

Copy link

codecov bot commented Dec 20, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 98.57%. Comparing base (e57aa7e) to head (936a3cc).
Report is 1 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #16459   +/-   ##
=======================================
  Coverage   98.57%   98.57%           
=======================================
  Files         169      169           
  Lines       21968    21968           
=======================================
  Hits        21654    21654           
  Misses        314      314           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

github-actions bot commented Dec 20, 2024

Code size report:

   bare-arm:    +0 +0.000% 
minimal x86:    +0 +0.000% 
   unix x64:    +0 +0.000% standard
      stm32:    +0 +0.000% PYBV10
     mimxrt:  +192 +0.051% TEENSY40
        rp2:  -128 -0.014% RPI_PICO_W[incl +20(bss)]
       samd:  +216 +0.080% ADAFRUIT_ITSYBITSY_M4_EXPRESS[incl +4(bss)]
  qemu rv32:    +0 +0.000% VIRT_RV32

@Josverl
Copy link
Contributor

Josverl commented Dec 20, 2024

Interesting, is the assumption that the MCU is network capable, and will act as a bridge/router?

@andrewleech
Copy link
Contributor Author

With this feature any MCU with TinyUSB based USB connection becomes network capable.

I hadn't thought of routing or bridging to other interfaces, though that's probably possible.

My initial use case at Planet Innovation is for larger systems where we have a Linux or windows host PC with an embedded module running micropython attached. We wanted an option to use standard networking protocols to communicate between the application on the host computer and the device, however not every system warrants an MCU with an Ethernet connection, or the host computer Ethernet is used for external connection and we want the internal connection on a separate interface etc.

With this feature the connection between host and device is still point to point like any USB connection, but you can easily run multiple services like a http server, mqtt client, webrepl etc.

Currently I don't think you can monitor or query for the IP address/s handed out by the micropython dhcp server to active clients, I might want to add that if I'm correct so that any client library could easily find / talk to services running on the host.

@robert-hh
Copy link
Contributor

With this feature any MCU with TinyUSB based USB connection becomes network capable.

In that respect, who is server, who is client?

  • Does a MP board without network interface get network capable as a client and uses a service provided by the entity it connects to? In that case, how is the service provided? or
  • Is the MP board a server providing network services to client connected by USB? In that case the board must have already a network interface.

@andrewleech
Copy link
Contributor Author

who is server, who is client?

There's no hard and fast rule here, both can run servers and be clients.

This is just a network connection.

Both the micropython device and the PC have their own network stacks.

The USB network connection kind of acts the same as if the micropython board has an Ethernet port that is connected to a PC Ethernet port via a crossover cable. So both ends have their own IP address that can be manually or automatically assigned but there's no router or hub in between.

Currently the micropython end is hardcoded to 192.168.7.1 though I'd plan to make that configurable eventually.

I have enabled the built-in dhcp server on micropython on the interface (so mpy end is a server from that perspective) and the PC end seems to get provisioned as 192.168.7.16 by default, though this isn't necessarily guaranteed.

If the PC happens to be running a web server then a micropython requests library will be able to access it on that 192.168.7.16 address.

Conversely if you run microdot or similar on the micropython board the PC will be able to access it on 192.168.7.1

I'm planning on often having a Linux host running mosquito mqtt broker which both the micropython board and the host PC can both publish/subscribe to. I've also looked briefly at a pure python mqtt broker that I might try running on the device instead though!

My point is server/client relationship is generally on a per-service basis and can work both ways.

@andrewleech
Copy link
Contributor Author

This has been updated to make mdns work so the device can be pinged from host based on hostname, though it seems pretty slow to resolve initially on windows.

The ip addresses used by default are now in the link-local range which feels appropriate for this kind of isolated point-to-point link and should reduce the chance of ip collisions with other networks on the host.

Once the host is provided with an ip from the dhcp server in micropython, the host ip address is set as the gateway address on the micropython side of the interface which makes it easy for the micropython application to find/use the host ip address.

I've written up a quick demo of host to use mqtt between the device and host pc here, it works really well! https://github.com/andrewleech/mpy-usb-net-mqtt-demo/

@dpgeorge dpgeorge added the shared Relates to shared/ directory in source label Mar 27, 2025
@xplshn
Copy link

xplshn commented Apr 3, 2025

Could you please introduce an example that forwards WLAN to RNDIS using this? It'd be a welcome thing in the usb examples folder.


EDIT: I was unable to build the branch (for rp2 ofc), I get this error:

ERROR: Cannot plug gap greater than alignment - gap 253892, alignment 32

EDIT: SOLVED: I forgot to properly specify my board variant and build variant.


EDIT: Nope, I don't get it, I've been trying for a while an am unable to build it for the Raspberry Pi Pico 2 W.

I tried with:

]~/Documents/ItAll/micropython/ports/rp2@ cmake -DMICROPY_BOARD=RPI_PICO2_W -DMICROPY_USB_NET=true
]~/Documents/ItAll/micropython/ports/rp2@ make -j4 -l4 BOARD=RPI_PICO2_W MICROPY_PY_USBNET=true
# Then I moved the firmware.uf2 file to my Pi
# Then I connected to it via serial after a reboot, and imported `network`, but wasn't able to find the USBNET interface.
# The network_usbd_ncm.c was built tho, as it appears in the `make` output

In case anyone feels like helping out a newbie. Thx.
I hope this gets merged soon :)


@andrewleech
Copy link
Contributor Author

andrewleech commented Apr 4, 2025

On most ports, features are generally enabled like:

make -j BOARD=RPI_PICO2_W CFLAGS_EXTRA="-DMICROPY_HW_NETWORK_USBNET=1"

That plug gap alignment error is a strange one, I actually got it yesterday on a RPI build on an unrelated branch. I think it's related to the compilation of picotool more than the firmware issue, don't know where it came.

I haven't used this change on a board with wifi, though it probably should work fine.

Not sure how to proxy interfaces with lwip / micropython but I'm guessing there will probably be some standard patterns for this.

@xplshn
Copy link

xplshn commented Apr 4, 2025

On most ports, features are generally enabled like:

make -j BOARD=RPI_PICO2_W CFLAGS_EXTRA="-DMICROPY_HW_NETWORK_USBNET=1"

That plug gap alignment error is a strange one, I actually got it yesterday on a RPI build on an unrelated branch. I think it's related to the compilation of picotool more than the firmware issue, don't know where it came.

I haven't used this change on a board with wifi, though it probably should work fine.

Not sure how to proxy interfaces with lwip / micropython but I'm guessing there will probably be some standard patterns for this.

Thanks a lot, I'm new to Python and really stumbled upon this out of sheer luck!

EDIT: It seems to be built but not linked, none of the ./firmware.* files contain the string USBNET.

image

NOTE:
I did make clean, then cmake -DMICROPY_BOARD=RPI_PICO2_W -DMICROPY_HW_NETWORK_USBNET=1 and then finally make -j BOARD=RPI_PICO2_W CFLAGS_EXTRA="-DMICROPY_HW_NETWORK_USBNET=1"


I hope this makes it into master as a default built option

@andrewleech
Copy link
Contributor Author

Thanks for your testing information, I'll have to look into making it easier to include on-demand with the make command.

At the moment I'm not expecting it to be built in by default as it adds a reasonable amount of flash/ram use; I've got a build variant of the original Pico added as an example but that's not really helping with your board.

@xplshn
Copy link

xplshn commented Apr 7, 2025

SOLVED: I ported the commit to the RPI_PICO2_W, it works.

I'm not sure how one should forward an interface to another (but that's unrelated to this PR)

@andrewleech
Copy link
Contributor Author

andrewleech commented Apr 17, 2025

Great to hear @xplshn
I finally figured out how to pass defines on the command line during make, it's different to the other ports:

make -j BOARD=RPI_PICO2_W clean

CFLAGS="-DMICROPY_HW_NETWORK_USBNET=1" make  -j BOARD=RPI_PICO2_W

@xplshn
Copy link

xplshn commented May 17, 2025

@andrewleech do you think an example of RNDIS usage could be provided? I want to finish my wifi2usb.py program, I'd appreciate help, perhaps this could become the RNDIS usage example :)

import network
import rp2
import time
import sys
import machine
from machine import Pin
import select
import socket

pico_led = Pin("LED", Pin.OUT)

SCAN_TIMEOUT = 5000

class WiFi2USB:
    def __init__(self):
        self.usb_net = network.USB_NET()
        self.usb_net.active(True)

        self.wlan = network.WLAN(network.STA_IF)
        self.wlan.active(True)

        self.networks = []
        self.current_ssid = ""
        self.current_password = ""
        self.connected = False

    def scan_networks(self):
        """Scan for available WiFi networks"""
        pico_led.on()
        print("Scanning for networks...")
        self.networks = self.wlan.scan()
        pico_led.off()
        return self.networks

    def connect_wifi(self, ssid, password):
        """Connect to a WiFi network with given SSID and password"""
        pico_led.off()
        print(f"Connecting to {ssid}...")

        self.wlan.connect(ssid, password)

        start_time = time.ticks_ms()
        while not self.wlan.isconnected():
            # Check for cancel with BOOTSEL button
            if rp2.bootsel_button() == 1:
                print("\nConnection attempt cancelled")
                return False

            if time.ticks_diff(time.ticks_ms(), start_time) > SCAN_TIMEOUT:
                print("\nConnection timeout - please try again")
                return False

            pico_led.on()
            time.sleep(0.1)
            pico_led.off()
            time.sleep(0.1)

        ip = self.wlan.ifconfig()[0]
        print(f'Connected on {ip}')
        pico_led.on()
        self.connected = True
        self.current_ssid = ssid
        self.current_password = password
        return True

    def disconnect_wifi(self):
        """Disconnect from WiFi network"""
        if self.connected:
            self.wlan.disconnect()
            self.connected = False
            pico_led.off()
            print("Disconnected from WiFi")

    def get_network_info(self):
        """Return current network configuration"""
        if self.connected:
            return {
                'ssid': self.current_ssid,
                'ip': self.wlan.ifconfig()[0],
                'subnet': self.wlan.ifconfig()[1],
                'gateway': self.wlan.ifconfig()[2],
                'dns': self.wlan.ifconfig()[3],
                'status': 'Connected'
            }
        else:
            return {'status': 'Not connected'}

    def test_connectivity(self):
        """Test network connectivity"""
        if not self.connected:
            print("Not connected to any network")
            return False

        try:
            ip = self.wlan.ifconfig()[0]
            print(f"Connected with IP: {ip}")
            print("Gateway: " + self.wlan.ifconfig()[2])

            # TODO: ping a reliable site here
            print("Connectivity test passed")
            return True
        except:
            print("Connectivity test failed")
            return False

    def get_usb_status(self):
        """Return USB RNDIS status"""
        return {
            'active': self.usb_net.active(),
            'ifconfig': self.usb_net.ifconfig()
        }

    def forward_traffic(self):
        """Forward traffic between WiFi and USB interfaces"""
        try:
            wlan_sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP)
            usb_sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP)

            wlan_sock.bind((self.wlan.ifconfig()[0], 0))
            usb_sock.bind((self.usb_net.ifconfig()[0], 0))

            poller = select.poll()
            poller.register(wlan_sock, select.POLLIN)
            poller.register(usb_sock, select.POLLIN)

            print("Forwarding traffic between WiFi and USB interfaces...")

            while True:
                events = poller.poll(1000)
                for fd, event in events:
                    if fd == wlan_sock.fileno():
                        data, addr = wlan_sock.recvfrom(1500)
                        usb_sock.sendto(data, (self.usb_net.ifconfig()[0], 0))
                    elif fd == usb_sock.fileno():
                        data, addr = usb_sock.recvfrom(1500)
                        wlan_sock.sendto(data, (self.wlan.ifconfig()[0], 0))
        except OSError as e:
            print(f"Socket error: {e}")
        except Exception as e:
            print(f"Unexpected error: {e}")

class CLI:
    def __init__(self, wifi2usb):
        self.wifi2usb = wifi2usb
        self.current_menu = self.main_menu

    def start(self):
        """Start the CLI interface"""
        print("\n" + "=" * 50)
        print("WiFi2USB Dongle - CLI Interface")
        print("=" * 50)

        while True:
            try:
                self.current_menu()
            except KeyboardInterrupt:
                print("\nExiting CLI Interface")
                break

    def main_menu(self):
        """Display the main menu"""
        print("\nMAIN MENU:")
        print("1. Reconfigure network")
        print("2. See network config")
        print("3. Test network connectivity")
        print("4. Enter microPython REPL")
        print("0. Exit")

        choice = input("\nEnter your choice: ")

        if choice == "1":
            self.current_menu = self.network_selection_menu
        elif choice == "2":
            self.network_config_menu()
        elif choice == "3":
            self.connectivity_test_menu()
        elif choice == "4":
            self.enter_repl()
        elif choice == "0":
            print("Exiting...")
            sys.exit()
        else:
            print("Invalid choice, please try again")

    def network_selection_menu(self):
        """Display network selection menu"""
        print("\nScanning for networks...")
        networks = self.wifi2usb.scan_networks()

        print("\nAvailable Networks:")
        print("   Network Name                [#Channel] |Other info|")
        print("-" * 60)

        for i, network in enumerate(networks, 1):
            try:
                ssid = network[0].decode('utf-8')
                bssid = network[1]  # MAC address in bytes
                channel = network[2]
                rssi = network[3]
                authmode = network[4]

                # Format MAC address
                mac = ':'.join('{:02x}'.format(b) for b in bssid)

                # Safe access to security types with fallback
                security_types = ["Open", "WEP", "WPA-PSK", "WPA2-PSK", "WPA/WPA2-PSK", "WPA3"]
                if 0 <= authmode < len(security_types):
                    security = security_types[authmode]
                else:
                    security = f"Unknown({authmode})"

                print(f"{i}. {ssid:<25} [#{channel}]     |mac:{mac}, db:{rssi}, {security}|")
            except Exception as e:
                print(f"{i}. [Error reading network info: {e}]")

        print("-" * 60)
        print("0: go back to last menu")

        try:
            choice = input("\nSelect network: ")

            if choice == "0":
                self.current_menu = self.main_menu
                return

            idx = int(choice) - 1
            if 0 <= idx < len(networks):
                selected_ssid = networks[idx][0].decode('utf-8')
                password = input(f"Enter password for {selected_ssid}: ")

                if self.wifi2usb.connect_wifi(selected_ssid, password):
                    print(f"Successfully connected to {selected_ssid}")
                    self.wifi2usb.forward_traffic()
                else:
                    print(f"Failed to connect to {selected_ssid}")

                # Return to main menu after connection attempt
                self.current_menu = self.main_menu
            else:
                print("Invalid selection")
        except ValueError:
            print("Please enter a valid number")

    def network_config_menu(self):
        """Display current network configuration"""
        print("\nCurrent Network Configuration:")
        print("-" * 50)

        # WiFi status
        wifi_info = self.wifi2usb.get_network_info()
        print("WiFi Status:", wifi_info['status'])

        if wifi_info['status'] == 'Connected':
            print(f"SSID: {wifi_info['ssid']}")
            print(f"IP Address: {wifi_info['ip']}")
            print(f"Subnet Mask: {wifi_info['subnet']}")
            print(f"Gateway: {wifi_info['gateway']}")
            print(f"DNS Server: {wifi_info['dns']}")

        # USB RNDIS status
        usb_status = self.wifi2usb.get_usb_status()
        print("\nUSB Network Interface:")
        print(f"Active: {usb_status['active']}")
        print(f"IP Configuration: {usb_status['ifconfig']}")

        print("-" * 50)
        input("Press Enter to return to main menu...")

    def connectivity_test_menu(self):
        """Test network connectivity"""
        print("\nTesting Network Connectivity:")
        print("-" * 50)

        self.wifi2usb.test_connectivity()

        print("-" * 50)
        input("Press Enter to return to main menu...")

    def enter_repl(self):
        """Enter MicroPython REPL"""
        print("\nEntering MicroPython REPL...")
        print('Type "exit()" to return to CLI')
        print("-" * 50)

        # TODO: Replace this, it sucks
        while True:
            try:
                code = input(">>> ")
                if code.strip() == "exit()":
                    break
                try:
                    result = eval(code)
                    if result is not None:
                        print(result)
                except SyntaxError:
                    exec(code)
            except Exception as e:
                print(f"Error: {e}")

        print("-" * 50)
        print("Returned from REPL")

def main():
    wifi2usb = WiFi2USB()
    cli = CLI(wifi2usb)
    cli.start()

if __name__ == "__main__":
    main()

@andrewleech
Copy link
Contributor Author

@xplshn I really don't know anything about how packet forwarding is supposed to work. Does your application there seem to work at all?
For your repl bit it would probably be better to integrate aiorepl from micropython-lib

FWIW I've got an example usage of this published at https://github.com/andrewleech/mpy-usb-net-mqtt-demo/

ps. it's not RNDIS, that's the older standard protocol for usb network adapters. NCM is the newer one, it's a different protocol / driver behind the scenes. They should basically work the same as far as users are concerned though.

@xplshn
Copy link

xplshn commented May 17, 2025

@xplshn I really don't know anything about how packet forwarding is supposed to work. Does your application there seem to work at all?

My code works, it stablishes a WLAN connection, and an NCM connection (as you said), but packet forwarding isn't handled at all, because I do not know how, if someone can shed some light, I'd appreciate it

For your repl bit it would probably be better to integrate aiorepl from micropython-lib

FWIW I've got an example usage of this published at https://github.com/andrewleech/mpy-usb-net-mqtt-demo/

Thank you! didn't know about these two

@andrewleech
Copy link
Contributor Author

I've been working on this PR on stm32 in conjunction with stm32/usb: Add support for using TinyUSB stack. #15592 and it's looking quite stable now.

I found and fixed a data corruption issue (hathach/tinyusb#3131) and have improved the speed of the interface significantly in the process.

I'm also finding ipv6 to be highly beneficial for this use case as I don't need to worry about clashing ip addresses from multiple devices being attached to a PC. For windows testing I've got some powershell snippets that automatically find the correct ip address to communicate with for a given USB vid/pid</serial> which has helped. I'll clean them up and share them here too.

@andrewleech andrewleech force-pushed the usbd_net branch 3 times, most recently from 5baf248 to cf9e19f Compare May 23, 2025 01:07
@andrewleech andrewleech force-pushed the usbd_net branch 4 times, most recently from bb3d97a to e59d8f2 Compare May 30, 2025 05:23
pi-anl added 8 commits June 21, 2025 22:43
This sets the DHCP server's own IP address as the default DNS server
when no explicit DNS is configured, providing a more complete network
configuration for DHCP clients.

Signed-off-by: Andrew Leech <[email protected]>
Provides atoi() function implementation required by lwIP's netif_find()
function when using string-based network interface lookup.

Based on (MIT) implementation from https://github.com/littlekernel/lk

Signed-off-by: Andrew Leech <[email protected]>
Adds TinyUSB configuration and descriptor support for USB Network
Control Model (NCM) interface. This provides the low-level USB
infrastructure needed for USB networking.

Signed-off-by: Andrew Leech <[email protected]>
Implements a complete USB Network Control Model (NCM) driver that
provides ethernet-over-USB functionality integrated with lwIP stack.

Features:
- Link-local IP address generation from MAC using CRC32
- DHCP server integration with connect callbacks
- Full lwIP network interface with IPv6 support
- USB NCM protocol handling via TinyUSB
- Python network.USB_NET class interface

Signed-off-by: Andrew Leech <[email protected]>
Adds USB Network support to the RP2 port through a build variant
of the RPI_PICO board. The USB_NET variant enables lwIP networking
and USB Network Control Model (NCM) interface.

This allows the Raspberry Pi Pico to function as a USB network adapter
providing ethernet-over-USB connectivity to a host computer.

Signed-off-by: Andrew Leech <[email protected]>
Integrates USB Network Control Model (NCM) support into the STM32 port,
enabling ethernet-over-USB functionality. Includes lwIP configuration
updates and network interface registration.

Signed-off-by: Andrew Leech <[email protected]>
Enables USB Network Control Model (NCM) interface in the MIMXRT port
for ethernet-over-USB functionality.

Signed-off-by: Andrew Leech <[email protected]>
Consolidates TinyUSB source includes across multiple ports (alif, mimxrt,
renesas-ra, samd) to use a common approach for TinyUSB integration.

This cleanup ensures consistent TinyUSB usage patterns across ports
and simplifies maintenance.

Signed-off-by: Andrew Leech <[email protected]>
pi-anl and others added 3 commits June 24, 2025 23:35
This change allows USB class drivers (CDC, MSC, NCM) to be individually
enabled/disabled at runtime without requiring TinyUSB submodule changes.

Key features:
- Classes are always compiled in but only enabled drivers appear in USB
  descriptors
- When MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE disabled: all compiled
  classes auto-enabled (backward compatibility)
- When MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE enabled: only CDC enabled
  by default, others require explicit activation
- New Python API: usb.enable_cdc(), usb.enable_msc(), usb.enable_ncm()
- Dynamic descriptor generation based on enabled classes
- Proper cleanup of disabled drivers (e.g. NCM networking)

Usage:
  usb = machine.USBDevice()
  usb.active(False)
  usb.enable_msc(True)  # Enable mass storage
  usb.enable_ncm(True)  # Enable USB networking
  usb.active(True)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Signed-off-by: Andrew Leech <[email protected]>
Automatically enable MICROPY_HW_NETWORK_USBNET when MICROPY_PY_LWIP
is enabled, following the same pattern as WEBSOCKET and WEBREPL.

This makes USB networking available by default on boards that support
both USB device mode and LWIP networking, while still allowing boards
to explicitly disable it by setting MICROPY_HW_NETWORK_USBNET=0.

Affects: STM32, RP2, MIMXRT, and Renesas-RA ports.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Signed-off-by: Andrew Leech <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
shared Relates to shared/ directory in source
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants