Skip to content

RS232 bridge transmits at wrong baud on nRF52 when the bridge UART is opened earlier in boot #3011

Description

@varialflip

This took me a few days of tinkering to understand. Was trying to use RS232 between a Rak 1w board and a Heltec WSLv3 but it wasn't working.
Confirmed the WSL could hear and talk RS232 with a external USB-UART adapter but the Rak 3401 / 19007 wasn't listening or talking. Now everything seems to work.

The rest is entirely Claude generated.
I will leave a human to read / submit this as this is way over my head.

Fix: RS232 bridge transmits at wrong baud on nRF52 when the bridge UART is opened earlier in boot

Summary

On nRF52 boards, RS232Bridge::begin() calls Uart::begin(bridge_baud) to configure
the bridge serial port. If that same Uart object (e.g. Serial1) was already opened
earlier in the boot sequence at a different baud rate, the Adafruit nRF52 core silently
ignores the bridge's begin() call, and the port keeps transmitting at the earlier baud.

The result: the bridge appears to work (pins are configured, BRIDGE: TX logs fire,
bytes physically leave the TX pin), but the peripheral is clocking them out at the wrong
rate, so nothing on the other end can decode them.

Affected hardware

Any nRF52 variant where the bridge UART is brought up before RS232Bridge::begin() runs.

Confirmed on the RAK3401 (WisMesh 1W), whose variant.h aliases Serial1 to the GPS
UART:

#define PIN_GPS_RX PIN_SERIAL1_RX
#define PIN_GPS_TX PIN_SERIAL1_TX
#define GPS_BAUD_RATE 9600

Serial1 is opened at 9600 during board/GPS bring-up, so the bridge's later
begin(115200) is discarded. RAK4631 / Heltec T114 are unaffected because they do not
pre-open the bridge UART.

Root cause

Uart::begin() in the Adafruit nRF52 core guards against re-initialization:

void Uart::begin(unsigned long baudrate, uint16_t config) {
  if ( _begun ) return;   // <-- silently no-ops if already begun
  ...
  // BAUDRATE register is only written past this guard
}

Because setPins() writes the PSEL registers directly (outside the guard), the pins
end up correct while the BAUDRATE register retains its earlier value — which is exactly
why the failure is so hard to see: every register looks right except the baud.

Diagnosis (register-level evidence)

Reading the raw UARTE registers on a RAK3401 bridge build, before any fix:

[BEFORE setPins] UARTE0 ENABLE=ON  BAUD=0x00275000  (=9600)  TXD->P0.15 RXD->P0.16
[AFTER begin]    UARTE0 ENABLE=ON  BAUD=0x00275000  (=9600)  <-- begin(115200) had no effect

get bridge.baud correctly reported 115200 (the pref was right); the value simply never
reached the peripheral. After the fix:

[AFTER end()]    UARTE0 ENABLE=off  DISCONNECTED
[AFTER begin]    UARTE0 ENABLE=ON  BAUD=0x01D60000  (115200)  TXD->P0.16 RXD->P0.15

Bytes then decode correctly on the far end (verified end-to-end: adverts from a peer node
were received and parsed over the bridge).

Fix

Call end() before begin() in the nRF52 branch of RS232Bridge::begin() to clear the
_begun guard, then re-apply pins (since end() disconnects PSEL) and begin at the
bridge baud:

#elif defined(NRF52_PLATFORM)
  ((Uart *)_serial)->end();
  ((Uart *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX);
  ((Uart *)_serial)->begin(_prefs->bridge_baud);

end() on a not-yet-begun port is a harmless no-op, so this is safe for boards that
were already working (RAK4631, T114) — they simply end() an unopened port and proceed
as before. No behavior change on ESP32 / RP2040 / STM32 branches.

Testing

  • RAK3401 (WisMesh 1W) bridged to a Heltec Wireless Stick Lite V3 over RS232 (J10 UART1,
    pins P0.15/P0.16). Before: BRIDGE: TX logged but far end saw only garbled bytes.
    After: self-test text and real mesh packets decode cleanly at 115200.
  • Verified the UARTE BAUDRATE register now reflects the configured bridge baud.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions