Skip to content

Comments

[gen3] USB Tethering#2898

Open
scott-brust wants to merge 15 commits intodevelopfrom
feature/sc-137645/usb-tethering
Open

[gen3] USB Tethering#2898
scott-brust wants to merge 15 commits intodevelopfrom
feature/sc-137645/usb-tethering

Conversation

@scott-brust
Copy link
Member

@scott-brust scott-brust commented Feb 19, 2026

USB CDC Tethering Implementation

Overview

Extends the existing USART-based PPP tethering feature to support USB CDC as an alternative serial transport. The host connects to the device's USB CDC serial port, goes through the same AT command handshake and PPP negotiation flow, and gets a tethered network connection over USB instead of a hardware UART.

Changes

New files:

  • hal/inc/usb_hal_private.h — Private C API exposing FreeRTOS event group functions for USB CDC (hal_usb_cdc_pvt_get_event_group_handle, hal_usb_cdc_pvt_wait_event)
  • hal/src/nRF52840/serial_usb_stream.h / .cpp — SerialUSBStream class, a stream adapter that wraps the USB CDC HAL (HAL_USB_USART_* functions) behind the same EventGroupBasedStream interface used by the existing SerialStream (USART). Implements read, write, peek, skip, flush, waitEvent, and availForRead/availForWrite.

Modified files:

  • hal/src/nRF52840/usb_hal_cdc.c — Added a FreeRTOS event group (ev_group) to the USB instance. READABLE is signaled from the RX_DONE ISR handler (via xEventGroupSetBitsFromISR) when data is successfully copied into the rx FIFO. WRITABLE is signaled from TX_DONE after schedule_tx frees FIFO space. An enableEvent function checks current FIFO state from thread context to handle the race where data is already available before waitEvent blocks.
  • hal/network/lwip/pppservernetif.cpp — PppServerNetif::start() now creates either a SerialStream or SerialUSBStream based on settings_.usbserial. AT command handler lambdas updated to pass this (instead of serial_.get()) to properly distinguish transport type for flow control (+IFC) and baud rate (+IPR) queries. The ATD connect handler similarly updated.
  • wiring/inc/spark_wiring_tether.h — Added TetherUSBConfig struct and TetherClass::bind(TetherUSBConfig) overload. Added TetherInterface enum and activeSerialInterface() accessor.
  • wiring/src/spark_wiring_tether.cpp — Implements the USB bind path: sets settings.serial = 0 and settings.usbserial to the USB serial instance, signaling PppServerNetif to use SerialUSBStream.

Steps to Test

  • Build + flash this branch + test app to B5SOM harware
  • Connect B5SOM to SBC like Tachyon or RPI5
  • Create + enable ppp connection over ttyACM0 (see host setup below)

Host Setup

Tachyon and RPI need udev rules update to tell modem manager to not block the ttyACM0 connection:

sudo tee /etc/udev/rules.d/99-particle-modem.rules << 'EOF'
SUBSYSTEM=="usb", ATTRS{idVendor}=="2b04", ATTRS{idProduct}=="c019", ENV{ID_MM_DEVICE_IGNORE}="0", ENV{ID_MM_DEVICE_PROCESS}="1"
SUBSYSTEM=="tty", ATTRS{idVendor}=="2b04", ATTRS{idProduct}=="c019", ENV{ID_MM_DEVICE_IGNORE}="0", ENV{ID_MM_CANDIDATE}="1", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
EOF

sudo udevadm control --reload-rules
sudo udevadm trigger
sudo systemctl restart ModemManager

The rest of the setup is the ame as with USART tethering, just make sure the serial endpoint is the USB one instead. for example on tachyon

nmcli connection add type gsm con-name "particle" gsm.apn "particle" connection.interface ttyACM0 ppp.crtscts true autoconnect yes

Scan + list for modems

sudo mmcli -S
mmcli -L 

Activate PPP connection, test interface

nmcli c up particle
ping -I ppp0 google.com

Download test

curl --interface ppp0 -sS -o /dev/null -w "%{speed_download}\n" http://technobly.com/100/phrack.29.phk | awk '{ printf "Download speed: %.2f KiB/s\n", $1/1024 }'

Upload test

curl --interface ppp0 -T /dev/urandom -o /dev/null --max-time 10 \
  -w "Speed: %{speed_upload} bytes/sec\n" \
  http://speedtest.tele2.net/upload.php

Example App

#include "application.h"

Serial1LogHandler logHandler(115200, LOG_LEVEL_ALL, 
{
    { "app", LOG_LEVEL_ALL }, 
    { "comm.protocol", LOG_LEVEL_TRACE },
    { "system", LOG_LEVEL_TRACE },
    { "network", LOG_LEVEL_TRACE },
    { "ncp.at", LOG_LEVEL_TRACE },
    { "system.nm", LOG_LEVEL_TRACE },
    { "system.cm", LOG_LEVEL_TRACE },
    { "system.nd", LOG_LEVEL_TRACE },
    { "ncp.client", LOG_LEVEL_TRACE },
    { "net.rltkncp", LOG_LEVEL_TRACE },
    { "net.ifapi", LOG_LEVEL_TRACE },
    { "net.ppp.client", LOG_LEVEL_TRACE },
    { "net.pppserver", LOG_LEVEL_TRACE },
    { "net.ppp.client", LOG_LEVEL_TRACE },
    { "net.en", LOG_LEVEL_TRACE },
    { "service.ntp", LOG_LEVEL_TRACE },
    { "net.en", LOG_LEVEL_TRACE },
    { "mux", LOG_LEVEL_ERROR },
    { "net.ppp.ipcp", LOG_LEVEL_ERROR },
    { "comm.cloud.posix", LOG_LEVEL_TRACE },
}
);
SYSTEM_MODE(AUTOMATIC);

/* executes once at startup */
void setup() {
#if HAL_PLATFORM_ENV
    // Clear all environment variables to restore the default settings
    System.clearEnv();
#endif

    // waitUntil(Serial.isConnected);
    // Enable Cellular
    Cellular.on();
    Cellular.connect();
    // Bind Tether interface to Serial with default settings (8n1 + RTS/CTS flow control)
    // Tether.bind(TetherSerialConfig().baudrate(921600).serial(Serial1));

    Tether.bind(TetherUSBConfig());
    Tether.on();
    Tether.connect();

    Particle.connect();
}

void loop() {
}

References

Test download speeds around 36 kb/s

particle@tachyon-1c5472eb:~$ curl --interface ppp0 -sS -o /dev/null -w "%{speed_download}\n" http://technobly.com/100/phrack.29.phk | awk '{ printf "Download speed: %.2f KiB/s\n", $1/1024 }'
Download speed: 36.87 KiB/s

Test upload speeds around 65 kb/s

particle@tachyon-1c5472eb:~$ curl --interface ppp0 -T /dev/urandom -o /dev/null --max-time 10 \
  -w "Speed: %{speed_upload} bytes/sec\n" \
  http://speedtest.tele2.net/upload.php
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  639k    0     0    0  639k      0  65526 --:--:--  0:00:10 --:--:-- 64468
curl: (28) Operation timed out after 10000 milliseconds with 0 bytes received
Speed: 65526 bytes/sec

Completeness

  • User is totes amazing for contributing!
  • Contributor has signed CLA (Info here)
  • Problem and Solution clearly stated
  • Run unit/integration/application tests on device
  • Added documentation
  • Added to CHANGELOG.md after merging (add links to docs and issues)

@scott-brust scott-brust marked this pull request as ready for review February 19, 2026 20:34
@scott-brust
Copy link
Member Author

One outstanding item is how the particle udev linux rules should be updated to allow modem manager to consider the B5SOM tty connection as a modem. Right now the particle cli disallows this by default, but when enabling USB tethering this needs to be disabled. IE this snippet needs to be created

sudo tee /etc/udev/rules.d/99-particle-modem.rules << 'EOF'
SUBSYSTEM=="usb", ATTRS{idVendor}=="2b04", ATTRS{idProduct}=="c019", ENV{ID_MM_DEVICE_IGNORE}="0", ENV{ID_MM_DEVICE_PROCESS}="1"
SUBSYSTEM=="tty", ATTRS{idVendor}=="2b04", ATTRS{idProduct}=="c019", ENV{ID_MM_DEVICE_IGNORE}="0", ENV{ID_MM_CANDIDATE}="1", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
EOF

Should we incorporate this into the 'canonical' tethering setup script?

return network_ready(*this, 0, NULL);
}

bool isOn(void) {
Copy link
Member

Choose a reason for hiding this comment

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

Weird, this should be working as-is. It's in NetworkClass

Copy link
Member Author

Choose a reason for hiding this comment

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

oh, yes you are right. I think basically all of these functions in TetherClass are redundant

@Kategrode Kategrode added this to the 6.4.0 milestone Feb 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants