Skip to content

wiring/usbserial: add multi-byte USBSerial write overload#2925

Open
TJett wants to merge 1 commit into
particle-iot:developfrom
TJett:usb-multi-byte-write
Open

wiring/usbserial: add multi-byte USBSerial write overload#2925
TJett wants to merge 1 commit into
particle-iot:developfrom
TJett:usb-multi-byte-write

Conversation

@TJett

@TJett TJett commented May 25, 2026

Copy link
Copy Markdown

Problem

Essential the same class of issue as 2924. Serial.write(buf, len) on USBSerial does not use a native buffer-write path. It falls back to Print::write(const uint8_t*, size_t), which iterates through write(uint8_t) one byte at a time.

On P2 / Photon 2 this causes buffered USB Serial writes, including writes performed through SerialLogHandler, to be fed into the HAL one byte at a time, which makes Serial.write(buf, len) take much longer than expected.

Solution

Add a native USBSerial::write(const uint8_t* buffer, size_t size) overload and route it to a public HAL_USB_USART_Send_Data_Buffer() HAL entrypoint. On rtl872x, that entrypoint is implemented as a thin wrapper around the existing HAL_USB_USART_Send_Data_Multiple() bulk transmit path.

Files changed:

Steps to Test

Application-level testing on P2 / Photon 2:

  • Build unmodified Device OS and run the example app below.
  • Measure the duration of Log.write(buf, len) using micros().
  • Repeat with this PR applied.

Expected results:

  • Before the fix:
    • Large USB log writes take significantly longer
  • After the fix:
    • the logging call returns much sooner because the data is queued through the bulk USB write path

When running the example code below, I got the following results:

Before, on 6.4.1:

  • Log.write avg=57267 us over 75 samples (230-byte packets)

After:

  • Log.write avg=545 us over 460 samples (230-byte packets)

Over a 100x speedup! From effectively 4 kB/s to 400 kB/s through the log handler

I also ran

  • wiring/usbserial
  • wiring/no_fixture/

Example App

#include "Particle.h"

SYSTEM_MODE(MANUAL);

SerialLogHandler logHandler(LOG_LEVEL_INFO);

constexpr size_t WRITE_SIZE = 230;

uint8_t tx[WRITE_SIZE];
system_tick_t sampleStart = 0;
uint32_t samples = 0;
uint64_t totalWriteUs = 0;

void setup() {
    for (size_t i = 0; i < WRITE_SIZE; ++i) {
        tx[i] = 'A' + (i % 26);
    }

    sampleStart = millis();
}

void loop() {
    system_tick_t startUs = micros();
    Log.write((const char*) tx, sizeof(tx));
    system_tick_t elapsedUs = micros() - startUs;

    totalWriteUs += elapsedUs;
    ++samples;

    if (millis() - sampleStart >= 5000) {
        if (samples > 0) {
            Log.info("Log.write avg=%lu us over %lu samples (%u-byte packets)",
                    (unsigned long)(totalWriteUs / samples),
                    (unsigned long)samples,
                    (unsigned)WRITE_SIZE);
        }
        delay(5000);
        totalWriteUs = 0;
        samples = 0;
        sampleStart = millis();
    }

    delay(10);
}

References

AI Disclosure

I used Codex/GPT-5.4 to assist me in making this PR.


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)

Add USBSerial::write(const uint8_t*, size_t) so Serial.write(buf, len)
does not fall back to Print::write() byte-by-byte.
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.

1 participant