Saturday, May 2, 2026

ESC Protocol in rt-fc-offloader: How We Switch from DShot to ESC Boot

Why this matters

ESC flashing is timing-sensitive. In rt-fc-offloader, we switch motor IO in hardware so Linux scheduling jitter does not break bootloader entry.

DShot → ESC boot protocol switch sequence (exact timing)

  1. Steer pin mux to serial mode
    Write 0x40000400 ← 0x00000004 (serial mode, selected motor channel, force_low=0).
  2. Assert break (force line LOW)
    Write 0x40000400 ← 0x00000014 (force_low=1).
  3. Hold LOW for bootloader entry window
    Hold for ≥ 20 ms (project guidance uses time.sleep(0.020) minimum; many tools use longer for margin).
  4. Release line to idle HIGH
    Write 0x40000400 ← 0x00000004 (force_low=0). ESC bootloader is now listening.
  5. Run ESC serial traffic over FCSP
    Send/receive bytes on FCSP channel 0x05 (ESC_SERIAL).
  6. Restore flight mode
    Write 0x40000400 ← 0x00000001 (DShot mode).

Half-duplex behavior (handled in RTL)

The ESC line is one-wire half-duplex. wb_esc_uart transmits, then automatically returns to receive after the TX stop bit and guard period. During TX, RX is gated to avoid self-capture.

Host FCSP CH0x05 --> ESC UART TX --> Motor pin --> ESC bootloader
                                      ^         |
                                      |         v
Host FCSP CH0x05 <-- packetizer <-- ESC UART RX <- ESC response

Where this is documented in the repo

Short version: channel 0x01 controls the hardware switch; channel 0x05 carries ESC bytes; RTL handles half-duplex timing; then we switch back to DShot.

How We Did ESC Configuration: MSP Compatibility + FCSP Runtime Path

Why we built this

We wanted ESC configuration that is small and fast on Linux targets like Raspberry Pi Zero 2W, while still keeping compatibility with existing tooling.

So we kept MSP where it helps, but moved runtime transport and timing-sensitive flow into FCSP + FPGA offload.

How we did it

  1. Ported core logic to Python
    We separated protocol operations from UI and moved ESC transaction flow into reusable Python modules.
  2. Kept transport explicit
    FCSP CONTROL (0x01) handles mode/mux/select operations, and ESC_SERIAL (0x05) carries tunneled ESC serial bytes.
  3. Used hardware steering
    Select motor channel in hardware, optionally issue break/preamble for bootloader entry, tunnel request/response bytes, then restore normal DShot mode.
  4. Hardened reliability
    Improved serial init/flush/error paths, explicit timeouts, and cleaner recovery when an ESC does not respond.

MSP vs FCSP (and why both)

MSP is still useful for compatibility and ecosystem tooling.

FCSP is the runtime-first path for deterministic behavior in this architecture. Timing-sensitive work stays in RTL, not Linux scheduling.

What works now

  • ESC passthrough/tunneling over FCSP
  • Motor select + mode control
  • Bootloader entry sequence support
  • Read version/settings workflows
  • MSP compatibility flow + FCSP runtime flow

Repo references

Next post: reliability details and the exact tunnel state-machine sequence.

rt-fc-offloader Intro: Why, SPI, Serial, Switching, MSP Difference, and ESC-Config Write-Up

rt-fc-offloader Intro

Why rt-fc-offloader exists

We have INAV running on Linux on a Pi Zero 2W. The hard part is not running Linux flight-control software — the hard part is strict real-time I/O behavior.

This project exists to offload three timing-critical paths into deterministic FPGA logic:

  • NeoPixel (NeoPx) output timing
  • DShot generation timing
  • ESC-protocol tunneling for configuration and firmware updates

The goal is small and fast: keep Linux flexible for flight-control logic while hardware guarantees the exact timing-sensitive work.

Why we moved from MSP to FCSP

MSP is useful for ecosystem compatibility, but we ran into issues using it as the center of runtime transport for these offloaded timing paths.

So we created FCSP as the runtime protocol for this architecture because it gives:

  • explicit framing and integrity checks
  • channelized routing model
  • deterministic behavior under load/noise
  • a cleaner FPGA implementation path for parser/router/FIFO pipelines

Transport usage: SPI and Serial

SPI is the primary runtime transport for FCSP between Linux host and FPGA.

Serial is a secondary/alternate transport for bring-up, diagnostics, and validation workflows.

ESC-config tunneling (channel/port details)

ESC configurator traffic is tunneled through FCSP channels:

  • CONTROL channel (0x01) for register/mux control
  • ESC_SERIAL channel (0x05) for ESC protocol byte stream tunneling

Typical ESC-config sequence:

  1. configure mux control (select motor/mode)
  2. optional break pulse for bootloader entry
  3. exchange ESC bytes over FCSP ESC_SERIAL channel (0x05)
  4. restore DShot mode after completion

How FCSP is parsed and routed

  1. ingress receives framed bytes (SPI or Serial)
  2. parser detects sync/header/length and packet context
  3. CRC16-XMODEM gate validates integrity
  4. router dispatches by channel (CONTROL, ESC_SERIAL, TELEMETRY, etc.)
  5. response path tracks return interface and sends replies to the correct egress

How this is done in FPGA

The architecture is implemented as dedicated RTL blocks instead of software-timed bit banging:

  • ingress transport block (SPI/UART)
  • frame parser + CRC block
  • channel router/switch block
  • control bridge for register access
  • hardware I/O engines for DShot, NeoPixel, and ESC serial tunneling
  • TX arbiter + framer for deterministic response egress

In short: Linux decides what to do, FPGA guarantees when it happens.

More updates soon as DShot and MSP-facing integration work continues.

Companion ESC configurator

Current status: we now support both MSP and FCSP. MSP remains available for compatibility/tooling workflows, and FCSP remains the runtime-first transport for deterministic offload behavior.

Why AbstractX Exists: Streaming First, Bus Second

AbstractX exists to solve a practical embedded systems problem: how to move real-time stream data and control traffic through one coherent FPGA-to-Linux model without creating a giant, fragile stack.

The core idea

Basics

  • Use SPI/QSPI to make the FPGA look like a streaming device to the host, and to minimize data jitter compared with low-speed USB.
  • Still keep a bus interface like Wishbone so configuration stays simple.
  • Use that control path to configure registers, DMA behavior, timers, and related control-plane features.

AbstractX is a switched streaming architecture built around AXIS-style metadata. Streams are marked with attributes such as route and timestamp information, then switched through the fabric, with DMA-style streaming as the main data path and Wishbone used for simple control and configuration.

How TUN works with this

TUN is the Linux-side virtual network interface that receives and injects packet data for AbstractX.

TUN allows packeting and creates an easy interface to a stream of sensors instead of reading one at a time. If there are multiple sensors, even different sensor types, the readings can be synchronized and correlated to each other by the FPGA because the timestamp is carried in the AXIS attributes for coherent timing across channels.

  • On RX: streamed data from AbstractX (via AXIS and transport framing) is delivered into Linux through the TUN interface, so userspace can read it like normal network traffic.
  • On TX: userspace writes packets to TUN, and the bridge forwards them back through AbstractX toward the FPGA data path.
  • This keeps Linux integration simple while the FPGA side handles fast streaming, DMA movement, and deterministic timing behavior.

Streaming platform first, bus second

The most important principle is:

AbstractX is a streaming platform first, and a bus interface second.

  • Streaming is treated as the primary workload.
  • Control/register access is integrated, but it is not the center of gravity.
  • This keeps the architecture aligned with real-time data movement instead of register choreography.

Where Wishbone fits

Wishbone is used as the practical control-plane attachment inside the design for simple configuration, setup, and status, while the switching/streaming path remains the main data plane.

In other words: Wishbone is important, but it is there to support the stream architecture—not replace it.

Why this matters

  • Less architectural drift: one consistent model for growth instead of many one-off interfaces.
  • Better real-time behavior: data movement is designed as a first-class path.
  • Cleaner integration: control and stream traffic coexist without fighting each other.
  • Maintainability: the system stays understandable as complexity increases.

The purpose of AbstractX

AbstractX is around because embedded FPGA systems need a practical middle path: not toy-simple, not framework-heavy, but structured enough to scale from bring-up to serious streaming workloads.

That is the “why” in one sentence: make streaming-centric systems easier to build, reason about, and evolve.

Best of both worlds

AbstractX gives us the best of both worlds: high-speed DMA data movement with explicit TX/RX paths plus auto-triggering behavior.

We can also do auto playback so accel/gyro data streams back continuously via DMA without processor intervention, with timestamps included for timing correlation and analysis.

This means control and stream traffic can coexist while data capture and movement stay deterministic.

Architecture at a glance

Here is the current AbstractX block diagram:

+----------------------+    SPI    +---------------------------+    +---------------------------+
| Linux Host           | <-------> | SPI to/from AbstractX     | <->| AbstractX Switch Fabric   |
| Control + Apps       |           | Transport Translator      |    | route + switch + tags     |
+----------------------+           +---------------------------+    +-------------+-------------+
        ^                                                                         ^
        | IRQ / INT_REQ                                                           |
        +-------------------------------------------------------------------------+

                                                                              /   \
                                                                             v     v

   control path <--> +----------------------+                      +----------------------+ <--> stream path
                     | Wishbone Gateway     |                      | DMA / Stream         |
                     | Register Access      |                      | Endpoints            |
                     +----------+-----------+                      +----------+-----------+
                                |                                             |
                                v                                             v
                     +----------------------+                      +----------------------+
                     | Control Peripherals  |                      | Streaming Sources    |
                     | GPIO / status / ADC  |                      | gyro / accel / IO   |
                     +----------------------+                      +----------------------+

                                 metadata / AXIS tags: route + ts_in + ts_out

First proof of concept: accelerometer / gyroscope offload

The first proof of concept is an accelerometer/gyroscope pipeline focused on full data-path offload and microsecond-level timing behavior.

  • Set up the sensor via SPI commands.
  • Set up DMA auto-playback of X/Y/Z samples for gyro + accel channels.
  • Use an ISR trigger path to launch reads and forward results over AbstractX AXIS into a Linux TUN driver.
  • Keep the processor out of the hot path as much as possible for deterministic, low-latency timing.

Next proof of concept: timer-driven A/D streaming

The next step is an A/D path, likely timer-driven, that automatically samples and streams conversion results through the same AbstractX data path.

Do you have something that needs real-time streaming but carries high processor overhead? Let’s see if we can do it in AbstractX.

All of these core architecture details are documented in the AbstractX README.

AbstractX Update: Moving to QMTECH Zynq-7020 with Python TUN/DMA Bring-Up

I’ve been doing a direction update on AbstractX, and the current focus is now shifting toward a QMTECH Zynq-7020 platform for the next phase of bring-up and integration work.

The Gowin-based targets are still useful and working reference points, but for the next round of development I wanted a platform that makes it easier to combine Linux, DMA, FPGA logic, and userspace networking experiments in one place. The QMTECH Zynq-7020 board is a good fit for that.

Why Zynq-7020?

The main reason is practical integration.

AbstractX already has the pieces for transport, switching, and host interaction, but moving toward a Zynq platform opens up a much smoother path for:

  • Linux-based userspace tooling
  • DMA-backed transport experiments
  • TUN/TAP style networking interfaces
  • tighter software/hardware iteration
  • eventual real-time tuning work

This is not about replacing the smaller FPGA targets. It is about creating a stronger system integration platform.

Current software direction

The software path is intentionally staged:

  • Python first
  • Rust next
  • kernel later only if needed

Right now, Python is the fastest way to get the system moving. It’s easy to inspect, easy to modify, and ideal for validating transport behavior while the board, Linux image, DMA path, and framing details are still evolving.

To support that, I added a unified Python userspace TUN bridge that can target either SPI or DMA-style backends.

After the behavior is proven in Python, the plan is to move the long-running bridge into Rust userspace. That should give a cleaner and more durable implementation for sustained TUN + DMA operation without jumping too early into kernel complexity.

The kernel path is still on the table, but only if actual measurements show that userspace cannot meet latency or jitter requirements.

CRC policy update

One specific design choice I wanted to make explicit:

  • CRC stays enabled for SPI / serial-style transport
  • CRC is disabled by default for DMA
  • the CRC field is still retained in the frame format for compatibility

That split is intentional.

SPI is an external serialized link and benefits from link-level error detection. DMA, on the other hand, is an in-box trusted path, so doing per-frame CRC there usually adds overhead without much value during normal operation.

Buildroot first, bleeding edge later

For Linux bring-up, I’m keeping the plan simple:

  • start from a Buildroot baseline
  • get the board, FPGA flow, DMA path, and userspace bridge working
  • only then decide whether a newer kernel or more RT-focused stack is worth the churn

That avoids turning early bring-up into a moving-target problem.

JTAG / bring-up notes

For debug and hardware access, I also documented the Pico/XVC path using the xvc-pico project. That gives a low-cost way to expose JTAG through XVC and hook into Vivado Hardware Manager without needing more specialized hardware right away.

There was also an important USB reminder worth capturing for Zynq bring-up: some external ULPI PHY setups may need the usb-nop-xceiv device-tree path, plus reset GPIO wiring and the matching kernel config.

Repo updates

The repository now includes:

  • Python TUN/DMA bridge scaffolding
  • Rust TUN/DMA bridge scaffolding
  • updated QMTECH Zynq-7020 notes
  • clarified CRC policy in the direction docs
  • Buildroot output directory conventions
  • updated engineering log entries explaining the why behind these choices

Bottom line

The short version is this:

AbstractX is now being pushed toward a QMTECH Zynq-7020 + Linux + DMA + TUN workflow, with Python for bring-up and Rust for the next-stage hardened userspace bridge.

That should give me a better platform for validating the real shape of the system before deciding how much belongs in userspace, how much belongs in the kernel, and what the real-time tradeoffs actually look like.

More soon as the board bring-up progresses.

Sunday, June 1, 2025

Gowin EDA on Ubuntu 24.04.2 LTS

To run on Ubuntu open terminal

 cd into the gowin installed directory where bin lib directories are after the untar

 export LD_LIBRARY_PATH=$PWD/lib 

export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libfreetype.so 

export QT_XCB_GL_INTEGRATION=none

./gw_ide

Saturday, May 31, 2025

Using Gowin IDE on ubuntu 22.04

 Need to install QT

   sudo apt install -y qtcreator qtbase5-dev qt5-qmake cmake

Open terminal and use

cd <to where the gowin tools are>

there should be a bin and library dir then


export LD_LIBRARY_PATH=$PWD/lib

cd bin

./gw_ide