OSPI PHY Grapher UART

Introduction

This example demonstrates OSPI PHY tuning data collection and visualization via UART using XMODEM1k protocol.

The example:

  • Initializes OSPI PHY tuning system

  • Sends a ready signal to the host

  • Receives a command header from the host

  • Executes OSPI_phyTuneGrapher() to collect tuning data (81,920 bytes: 5 rdDelay × 128 txDLL × 128 rxDLL)

  • Transmits collected data via XMODEM1k protocol with CRC16-CCITT validation

  • Sends a status response indicating completion

The host-side Python scripts (receiver and plotter) are provided to:

  • Receive the XMODEM1k data stream

  • Validate CRC on each block

  • Save binary data to file

  • Visualize passing points in a scatter plot with different colors per rdDelay

Supported Combinations

Parameter

Value

CPU + OS

wkup-r5fss0-0 freertos

Toolchain

ti-arm-clang

Board

am62px-sk

Example folder

examples/drivers/ospi/ospi_phy_grapher_uart

Steps to Run the Example

Device Side

  • When using CCS projects to build, import the CCS project for the required combination and build it using the CCS project menu (see Using SDK with CCS Projects).

  • When using makefiles to build, note the required combination and build using make command (see Using SDK with Makefiles)

Attention

As the wake-up R5 is the device manager, it needs to be started by the SBL. It should be flashed and booted through SBL.

Refer Getting Started Flash for flashing the application.

Host Side (Linux/PC)

Prerequisites

pip install pyserial numpy matplotlib

Connection Setup

Two UART connections required (separate USB adapters):

  • CONFIG_UART0 → Debug console (optional monitoring)

  • CONFIG_UART1 → Binary data transfer (required)

Warning

DO NOT OPEN A TERMINAL ON CONFIG_UART1 while the receiver script is running. Opening a serial monitor on the binary data port will corrupt the XMODEM transfer and cause CRC errors. Use CONFIG_UART0 for debug monitoring only.

Run Receiver and Plotter

Navigate to tools directory and receive data from CONFIG_UART1 port (the binary data port):

cd tools/boot/grapher/
AM275X

These SoCs use MCU domain UARTs:

  • CONFIG_UART0: /dev/ttyACM0 (debug)

  • CONFIG_UART1: /dev/ttyUSB0 (binary) ← Use this for receiver

python3 ospi_phy_grapher_receiver.py /dev/ttyUSB0 -o data.bin
python3 ospi_phy_grapher_plotter.py data.bin
AM62DX

These SoCs use MCU domain UARTs:

  • CONFIG_UART0: /dev/ttyUSB2 (debug)

  • CONFIG_UART1: /dev/ttyUSB1 (binary) ← Use this for receiver

python3 ospi_phy_grapher_receiver.py /dev/ttyUSB1 -o data.bin
python3 ospi_phy_grapher_plotter.py data.bin
AM62X / AM62AX / AM62PX

These SoCs use mixed domain UARTs (MCU debug, Main data):

  • CONFIG_UART0: /dev/ttyUSB2 (debug on MCU domain)

  • CONFIG_UART1: /dev/ttyUSB0 (binary on Main domain) ← Use this for receiver

python3 ospi_phy_grapher_receiver.py /dev/ttyUSB0 -o data.bin
python3 ospi_phy_grapher_plotter.py data.bin

Example output:

[INFO] Connected to /dev/ttyUSB2 at 115200 baud
[INFO] Waiting for ready signal...
[SUCCESS] Received ready signal: ... READY_FOR_SWEEP
[INFO] Receiving XMODEM1k data (81920 bytes, 80 blocks)...
[INFO] Block 1: received 1024/81920 bytes (1.3%)
...
[INFO] Block 80: received 81920/81920 bytes (100.0%)
[SUCCESS] XMODEM1k reception complete: 81920 bytes in 80 blocks
[SUCCESS] Data saved to data.bin (81920 bytes)

Optional: Mark Tuning Points

To overlay old and new tuning algorithm results:

python3 ospi_phy_grapher_plotter.py data.bin 50 60 65 70
# Arguments: data_file old_tx old_rx new_tx new_rx

Automated Full Flow

Quick Start: Complete Workflow in One Command

Flash the device, receive PHY data, and visualize results using the following command chain (run from MCU+SDK root):

cd tools/boot && \
python3 uart_bootloader.py -p /dev/ttyUSB0 \
  --bootloader=sbl_prebuilt/am6x-sk/sbl_uart.release.hs_fs.tiimage \
  --file=../../examples/drivers/ospi/ospi_phy_grapher_uart/am62ax-sk/r5fss0-0_freertos/ti-arm-clang/ospi_phy_grapher_uart.release.appimage.hs_fs ; \
python3 grapher/ospi_phy_grapher_receiver.py /dev/ttyUSB0 -o plot.bin && \
python3 grapher/ospi_phy_grapher_plotter.py plot.bin

This workflow:

  1. Flashes device via uart_bootloader.py on CONFIG_UART0

  2. Receives PHY data via XMODEM1k

  3. Visualizes results in matplotlib scatter plot

Note: Adjust SoC-specific paths (am62ax-sk, r5fss0-0_freertos, ti-arm-clang) based on your device and toolchain.

Complete Workflow Commands by SoC

Use the appropriate command for your device (run from tools/boot directory):

AM275X

cd tools/boot
python3 uart_bootloader.py -p /dev/ttyACM0 --cfg=sbl_prebuilt/am275x-evm/sbl_uart_hs_fs.cfg; \
python3 grapher/ospi_phy_grapher_receiver.py /dev/ttyUSB2 -o am275x.bin; \
python3 grapher/ospi_phy_grapher_plotter.py am275x.bin

Note

Before running this command, update sbl_prebuilt/am275x-evm/sbl_uart_hs_fs.cfg to include the OSPI PHY Grapher application binary file path in the configuration.

AM62AX

cd tools/boot
python3 uart_bootloader.py -p /dev/ttyUSB0 \
  --bootloader=sbl_prebuilt/am62ax-sk/sbl_uart.release.hs_fs.tiimage \
  --file=../../examples/drivers/ospi/ospi_phy_grapher_uart/am62ax-sk/r5fss0-0_freertos/ti-arm-clang/ospi_phy_grapher_uart.release.appimage.hs_fs; \
python3 grapher/ospi_phy_grapher_receiver.py /dev/ttyUSB0 -o am62a.bin; \
python3 grapher/ospi_phy_grapher_plotter.py am62a.bin

AM62DX

cd tools/boot
python3 uart_bootloader.py -p /dev/ttyUSB1 \
  --bootloader=sbl_prebuilt/am62dx-evm/sbl_uart.release.hs_fs.tiimage \
  --file=../../examples/drivers/ospi/ospi_phy_grapher_uart/am62dx-evm/r5fss0-0_freertos/ti-arm-clang/ospi_phy_grapher_uart.release.appimage.hs_fs; \
python3 grapher/ospi_phy_grapher_receiver.py /dev/ttyUSB1 -o am62dx.bin; \
python3 grapher/ospi_phy_grapher_plotter.py am62dx.bin

AM62PX

cd tools/boot
python3 uart_bootloader.py -p /dev/ttyUSB0 \
  --bootloader=sbl_prebuilt/am62px-sk/sbl_uart.release.hs_fs.tiimage \
  --file=../../examples/drivers/ospi/ospi_phy_grapher_uart/am62px-sk/wkup-r5fss0-0_freertos/ti-arm-clang/ospi_phy_grapher_uart.release.appimage.hs_fs; \
python3 grapher/ospi_phy_grapher_receiver.py /dev/ttyUSB0 -o am62p_plot.bin; \
python3 grapher/ospi_phy_grapher_plotter.py am62p_plot.bin

AM62X

cd tools/boot
python3 uart_bootloader.py -p /dev/ttyUSB0 \
  --bootloader=sbl_prebuilt/am62x-sk/sbl_uart.release.hs_fs.tiimage \
  --file=../../examples/drivers/ospi/ospi_phy_grapher_uart/am62x-sk/r5fss0-0_freertos/ti-arm-clang/ospi_phy_grapher_uart.release.appimage.hs_fs; \
python3 grapher/ospi_phy_grapher_receiver.py /dev/ttyUSB0 -o am62x.bin; \
python3 grapher/ospi_phy_grapher_plotter.py am62x.bin

Application Design

System Architecture

┌────────────────────────────────────────────────────┐
│           HOST SIDE (Linux/PC)                     │
│                                                    │
│  ┌──────────────────────────────────────────────┐  │
│  │ Host Scripts                                 │  │
│  │ - ospi_phy_grapher_receiver.py               │  │
│  │ - ospi_phy_grapher_plotter.py                │  │
│  │ - grapher_full_flow.py                       │  │
│  └──────────────┬───────────────────────────────┘  │
│                 │                                  │
└─────────────────┼──────────────────────────────────┘
                  │ UART (115200 bps, dual port)
                  ├─ CONFIG_UART0: Debug logs
                  └─ CONFIG_UART1: Binary XMODEM data
                  │
┌─────────────────┼──────────────────────────────────┐
│  ┌──────────────▼───────────────────────────────┐  │
│  │ Device Application                           │  │
│  │                                              │  │
│  │ 1. Initialize OSPI + Dual UART               │  │
│  │ 2. Send "READY_FOR_SWEEP\r\n" signal         │  │
│  │ 3. Receive 32-byte command header            │  │
│  │ 4. Run OSPI_phyTuneGrapher() (~30 sec)       │  │
│  │    └─ Collect [5][128][128] tuning data      │  │
│  │ 5. Send 80 XMODEM1k blocks (81,920 bytes)    │  │
│  │ 6. Send 16-byte status response              │  │
│  │ 7. Idle/Loop                                 │  │
│  └──────────────────────────────────────────────┘  │
│                                                    │
│  ┌──────────────────────────────────────────────┐  │
│  │ OSPI PHY Tuning                              │  │
│  │ - 128 TX DLL values × 128 RX DLL values      │  │
│  │ - 5 read delay values                        │  │
│  │ - 1 byte per point (0=fail, >0=pass)         │  │
│  └──────────────────────────────────────────────┘  │
│                                                    │
│        DEVICE SIDE (MCU+SDK)                       │
└────────────────────────────────────────────────────┘

Data Flow Sequence

Host                                Device
 │                                     │
 │←─── "READY_FOR_SWEEP\r\n" ──────────┤
 │                                     │
 │─── Send command header (32B) ──────→│
 │    Magic: 0x47524150 ("PRAG")       │
 │    OpType: 0xFD                     │
 │    Size: 81920                      │
 │                                     │
 │                      ┌─── PHY Sweep (~30 sec) ───┐
 │                      │ OSPI_phyTuneGrapher()     │
 │                      └─────────────────────────┬─┘
 │                                                │
 │←─ XMODEM1k Block #1 ───────────────────────────┤
 │  ├─ STX (0x02)
 │  ├─ Block#1 + Complement
 │  ├─ 1024 bytes data
 │  └─ CRC16 (CCITT)
 │
 │ ─ ACK (0x06) ─────────────────────────────────→|
 │
 │  (repeat 80 blocks = 81,920 bytes)
 │
 │←─ XMODEM EOT (0x04) ───────────────────────────┤
 │
 │ ─ ACK (0x06) ─────────────────────────────────→|
 │
 │←─ Status response (16B) ───────────────────────┤
 │   Magic: 0x47524152 ("RARG")
 │   Status: 0x00000000 (success)
 │
 └─ Parse & Plot

Dual UART Architecture

CONFIG_UART0:

  • Purpose: Debug logging via DebugP

  • Content: Operational status and error messages

  • Impact: Can be monitored separately without affecting data transfer

CONFIG_UART1:

  • Purpose: Binary XMODEM1k data transfer

  • Content: Pure binary frames (no debug text)

  • Impact: Clean transfer without corruption

This separation ensures:

  • ✅ No debug text corrupts XMODEM binary frames

  • ✅ Better diagnostics (monitor both streams simultaneously)

  • ✅ Production-ready implementation

Data Format

Binary Output

File Structure (81,920 bytes total):

Byte Range         Data                        Description
──────────────────  ────────────────────────   ───────────────────
0 - 16,383          arrays[0][0..127][0..127]  rdDelay=0 (128×128)
16,384 - 32,767     arrays[1][0..127][0..127]  rdDelay=1 (128×128)
32,768 - 49,151     arrays[2][0..127][0..127]  rdDelay=2 (128×128)
49,152 - 65,535     arrays[3][0..127][0..127]  rdDelay=3 (128×128)
65,536 - 81,919     arrays[4][0..127][0..127]  rdDelay=4 (128×128)

Element Meaning:

  • 0: Test point failed (could not read)

  • >0: Test point passed (valid read data)

Array Organization: C-style row-major (txDLL varies fastest)

Visualization

Scatter Plot:

  • X-axis: TX DLL values (0-128)

  • Y-axis: RX DLL values (0-128)

  • Colors: One per rdDelay value

    • rdDelay=0: Green

    • rdDelay=1: Blue

    • rdDelay=2: Red

    • rdDelay=3: Orange

    • rdDelay=4: Purple

  • Points: Only passing points plotted

Protocol Specification

Phase 1: Device Ready Signal

Device sends (asynchronous, after boot):

ASCII: "READY_FOR_SWEEP\r\n"

Purpose: Indicate device is ready for PHY sweep

Phase 2: Host Sends Command

Host sends (32-byte binary header):

Byte Range  Size  Field              Description
──────────  ────  ─────              ───────────
0x00-0x03   4     Magic              0x47524150 (little-endian "PRAG")
0x04-0x07   4     Operation Type     0xFD (PHY tuning operation)
0x08-0x0B   4     Offset             Flash offset (unused, set to 0)
0x0C-0x0F   4     Size               Data size in bytes (81920)
0x10-0x1F   16    Reserved           All zeros

Device validates:

  • ✅ Magic number is 0x47524150

  • ✅ Operation type is 0xFD

  • ✅ Size is 81920

  • ❌ If any check fails, send error status and abort

Phase 3: Device Runs PHY Sweep

Device executes:

status = OSPI_phyTuneGrapher(ospiHandle, flashOffset, gOspiPhyGrapherData);

Data collected (81,920 bytes):

Array Layout:      [5 rdDelays][128 txDLL][128 rxDLL]
Total Elements:    5 × 128 × 128 = 81,920
Element Type:      uint8_t (1 byte each)
Byte Order:        C-style row-major (txDLL varies fastest)
Element Values:    0 = fail, >0 = pass

Phase 4: Device Sends Data via XMODEM1k

XMODEM1k Block Format:

Byte    Size  Field                Description
────    ────  ─────                ───────────
0       1     STX                  0x02 (XMODEM1k start)
1       1     Block Number         1-255 (wraps at 256)
2       1     Block Number Comp    ~BlockNumber
3-1026  1024  Data                 Raw 1024-byte payload
1027    1     CRC High Byte        (crc >> 8) & 0xFF
1028    1     CRC Low Byte         crc & 0xFF
────────────────────────────────────────────
Total:  1029  bytes per frame

CRC-16 CCITT Calculation:

  • Polynomial: 0x1021

  • Initial value: 0x0000

  • Precomputed lookup table (256 entries)

  • Critical: Mask intermediate result to prevent overflow:

    crc = ((crc << 8) ^ TABLE[(crc >> 8) ^ byte]) & 0xFFFF
    

Transmission Sequence:

Host                                Device
 │                                     │
 │                        Send STX+Block1
 │                        (1024 data bytes)
 │←─────────────────────────────────────┤
 │                        + CRC16
 │
 ├─ Validate CRC ───────────┐           │
 │ ├─ Pass: Send ACK ────→  │           │
 │ └─ Fail: Send NAK ────→  │ Retry     │
 │        (up to 20 times)  │           │
 │                        ◄─┼───────────┤
 │                        Send STX+Block2
 │
 │  (repeat 80 blocks = 81,920 bytes)
 │
 │                        ◄─────────────┤
 │                        Send EOT (0x04)
 │
 ├─ Send ACK (0x06) ───────────────────→|

Retry Strategy:

  • Device retries up to 20 times if ACK not received

  • Host retries up to 20 times if CRC fails

  • Timeout: 5 seconds per block (increased for USB-UART adapters)

Phase 5: Device Sends Status Response

Status Header (16 bytes):

Byte Range  Size  Field              Value
──────────  ────  ─────              ─────
0x00-0x03   4     Magic              0x47524152 (little-endian "RARG")
0x04-0x07   4     Status Code        0x00000000 (success) or error code
0x08-0x0F   8     Reserved           All zeros

Status Codes:

0x00000000  SUCCESS              PHY sweep completed, data sent
0x10000001  MAGIC_ERROR          Invalid magic in command header
0x20000001  OPTYPE_ERROR         Invalid operation type
0x30000001  PHY_SWEEP_ERROR      PHY tuning sweep failed
0x40000001  XMODEM_ERROR         XMODEM transmission failed

Timing Analysis

Phase

Duration

Notes

Device initialization

1 sec

OSPI, UART setup

PHY sweep

~30 sec

5 rdDelays × 128 txDLL × 128 rxDLL

XMODEM send

~7 sec

80 blocks @ 1024B each, 115200 baud

Status send

0.1 sec

16-byte response

Total

~38-40 sec

Per sweep

XMODEM Throughput:

  • 81,920 bytes total

  • 1,029 bytes per XMODEM frame (1024 data + 5 overhead)

  • ~80 frames + 1 EOT

  • At 115200 baud ≈ 11.5 KB/sec effective

  • Time: ~7.1 seconds

Implementation Details

Key Design Decisions

  1. XMODEM1k instead of XMODEM

    • 1024-byte blocks reduce protocol overhead

    • Fit within typical MCU buffer constraints

    • Proven protocol with wide tool support

  2. Dual UART Separation

    • CONFIG_UART0 for DebugP logging

    • CONFIG_UART1 for binary data transfer

    • Eliminates debug text corruption of XMODEM frames

  3. CRC16 CCITT Validation

    • Standard XMODEM CRC

    • Hardware accelerators available on many MCUs

    • Reliable error detection

  4. 32-byte Command Header

    • Matches bootloader Uniflash pattern

    • Consistent with existing infrastructure

    • Includes validation magic and operation type

  5. Transaction-based UART API

    • Single bulk transfer superior to byte-by-byte polling

    • More reliable over USB-UART adapters

    • Deterministic operation in polling mode

  6. Polling Mode (No Interrupts)

    • intrEnable = "DISABLE" in SysConfig

    • Matches bootloader pattern

    • Simplifies synchronization during transfer

Error Handling

Device Side

  • Validates command magic and operation type

  • Validates data size (must be 81920)

  • Returns error status if PHY sweep fails

  • Retries XMODEM blocks on CRC error (up to 20 times)

  • Sends NAK on invalid block, then resends

Host Side

  • Validates CRC16 on each XMODEM block

  • Retries up to 20 times per block

  • Detects timeout and reports to user

  • Validates status magic and code

  • Reports transfer errors with detailed diagnostics

Performance Optimizations

  • Precomputed CRC lookup table: Avoid runtime polynomial calculations

  • 1K blocks in XMODEM: Larger blocks reduce protocol overhead

  • Single UART transaction: Minimize context switching

  • Dual UART: Parallel debug logging without affecting data transfer

Dependencies

Device:

  • OSPI driver (already available)

  • UART driver (already available)

  • OSPI_phyTuneGrapher() function from ospi_phy.c

Host:

  • Python 3.6+

  • pyserial: pip install pyserial

  • numpy: pip install numpy

  • matplotlib: pip install matplotlib

See Also

OSPI

Sample Output

OSPI PHY Grapher UART Example
==============================
Debug output on UART0
Binary data transfer on UART1 (CONFIG_UART1)
==============================
Opened CONFIG_UART1 for binary data
[OSPI PHY Grapher UART] Starting...
[OSPI PHY Grapher UART] Sending ready signal
[OSPI PHY Grapher UART] Starting PHY tuning sweep...
[OSPI PHY Grapher UART] PHY sweep complete. Data collected: 81920 bytes
[OSPI PHY Grapher UART] Starting XMODEM1k transmission...
[OSPI PHY Grapher UART] XMODEM1k transmission complete
[OSPI PHY Grapher UART] Complete

Example test passed!

OSPI PHY Tuning - Passing Points by rdDelay

Troubleshooting

“Invalid STX: 0x5B”

Debug text corrupting XMODEM data. Ensure:

  • Correct firmware flashed (debug logs removed)

  • Using CONFIG_UART1 port for receiver (not CONFIG_UART0)

  • No other terminals connected to device

“Timeout waiting for ready signal”

Device not responding. Check:

  • Device is powered on

  • Correct firmware loaded

  • Device console shows logs on CONFIG_UART0

“CRC errors during transfer”

Usually recovers automatically. If persistent:

  • Try lower baud rate: -b 9600

  • Check USB cable quality

  • Move away from RF sources

Additional Resources

  • Host Scripts: examples/drivers/ospi/ospi_phy_grapher_uart/

    • ospi_phy_grapher_receiver.py - XMODEM1k receiver

    • ospi_phy_grapher_plotter.py - Data visualizer

    • ospi_phy_grapher_debug.py - Debug monitor