MicroChip mulltiple SERCOM to TCPIP sockets

Summary

This postmortem analyzes a design failure encountered when attempting to bridge multiple SERCOM/UART interfaces on a Microchip PIC32CZ CA80 board to independent TCP/IP sockets. The issue stemmed from architectural assumptions about Harmony’s driver model, concurrency handling, and socket lifecycle management. The result was unstable UART‑to‑TCP bridging, dropped connections, and unpredictable data flow.

Root Cause

The failure originated from treating each UART↔TCP path as an isolated pipeline without accounting for how Harmony’s TCP/IP stack and SERCOM drivers actually behave under load.

Key root causes included:

  • Single-threaded execution model in Harmony tasks causing starvation between UART and TCP tasks
  • Blocking UART reads stalling TCP socket writes
  • Improper socket state handling (CONNECTING, CONNECTED, CLOSING)
  • Lack of ring buffers between UART and TCP layers
  • Assuming Microchip’s old UART‑TCP bridge examples still apply to the PIC32CZ architecture
  • No concurrency abstraction (e.g., RTOS tasks per SERCOM)

Why This Happens in Real Systems

Systems like this fail because embedded networking is not symmetric with UART communication.

Common systemic reasons:

  • TCP is bursty, UART is continuous
  • TCP backpressure can stall UART unless buffered
  • UART overruns occur when TCP cannot drain fast enough
  • Harmony’s TCP/IP stack requires periodic servicing, unlike UART interrupts
  • Developers underestimate the complexity of bridging two asynchronous transports

Real-World Impact

When deployed, these issues manifest as:

  • Dropped TCP connections under moderate UART throughput
  • Corrupted or interleaved UART data
  • SERCOM FIFO overruns
  • High CPU usage from busy‑polling sockets
  • Unrecoverable socket states requiring system reset
  • Unpredictable latency that breaks protocols riding over UART

Example or Code (if necessary and relevant)

Below is a minimal example of how senior engineers structure a non-blocking UART↔TCP bridge task using ring buffers and state machines.

void BridgeTask(BRIDGE_CTX *ctx)
{
    // Pump UART → TCP
    size_t uartBytes = UART_Read(ctx->uart, ctx->uartBuf, sizeof(ctx->uartBuf));
    if (uartBytes > 0 && TCPIP_TCP_IsConnected(ctx->sock)) {
        TCPIP_TCP_ArrayPut(ctx->sock, ctx->uartBuf, uartBytes);
    }

    // Pump TCP → UART
    int tcpBytes = TCPIP_TCP_GetIsReady(ctx->sock);
    if (tcpBytes > 0) {
        tcpBytes = TCPIP_TCP_ArrayGet(ctx->sock, ctx->tcpBuf, sizeof(ctx->tcpBuf));
        UART_Write(ctx->uart, ctx->tcpBuf, tcpBytes);
    }
}

How Senior Engineers Fix It

Experienced engineers solve this by architecting the system, not by patching symptoms.

They typically:

  • Use one RTOS task per SERCOM↔TCP bridge
  • Implement ring buffers on both UART and TCP sides
  • Use non-blocking drivers only
  • Add connection watchdogs to auto‑reconnect sockets
  • Ensure TCPIP_Task() is serviced frequently
  • Use interrupt-driven UART with DMA when possible
  • Add flow control (RTS/CTS) if the UART protocol supports it
  • Build a state machine for each socket (INIT → CONNECT → RUN → ERROR → RETRY)

Why Juniors Miss It

Less experienced engineers often overlook:

  • TCP is not a byte stream like UART
  • Harmony drivers are cooperative, not preemptive
  • Blocking calls silently break real-time behavior
  • UART and TCP run at different speeds and patterns
  • Examples from older PIC32 families do not apply
  • Bridging transports requires buffering and scheduling, not just forwarding bytes

They tend to write “straight-line” code that works in a lab but collapses under real traffic.

Leave a Comment