Skip to content

Latest commit

 

History

124 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

RAPID

ECE Capstone Design Project Award Winner, 2026

Software component of a capstone design project. Streams GDS2 lithography patterns to an FPGA over UART for motor-driven stage control.

Team: Jack Parrack, Laurel Leuwerke, Matt Bracker, Chance Besancenez


Overview

RAPID reads a GDS2 input file, converts the XY coordinates to polar form, and transmits each point as a framed binary packet over a serial COM port to a Zynq FPGA. The FPGA drives a stepper motor (radial axis) and laser to write the pattern on a spinning disc. The PC waits for each ACK before sending the next point (stop-and-wait flow control). A Python GUI provides real-time visualisation of the acknowledged points and full manual hardware control.

input.gds  ──►  inputParser  ──►  RAPID.exe  ──►  UART  ──►  systemControl
                (XY -> polar)    (frame + send)                  (recv + motor control)
                                      ▲                                  │
                                   gui.py  ◄──── ACK log ───────────────┘
                                (live XY scatter)

                                                       systemControl  ──►  AXI GPIO
                                                                      ──►  stepperDriver.vhd
                                                                      ──►  spindle.vhd
                                                                      ──►  LaserEn (pin P18)

Repository layout

RAPID/
├── src/
│   ├── main.c                # PC-side serial manager (compiled -> build/RAPID.exe)
│   ├── inputParser.c/h       # GDS2 parser: XY coordinates -> polar points
│   ├── serial.c/h            # Shared Win32 serial utilities
│   ├── packets.c/h           # PC-side packet constructors (send_polar_point, send_ctrl_packet, etc.)
│   ├── protocol.h            # Shared wire protocol constants
│   └── gui.py                # PySide6 real-time visualisation + manual control GUI
├── vitis_workspace/          # Xilinx Vitis workspace (FPGA software)
│   ├── platform/             # BSP platform project (generated from .xsa)
│   └── systemControl/        # Active FPGA app — UART receiver + motor/laser control
├── hardware/                 # Xilinx Vivado project files (FPGA hardware)
│   ├── RAPID.srcs/sources_1/new/
│   │   ├── stepperDriver.vhd # Stepper motor FSM (DRV8834)
│   │   ├── spindle.vhd       # BLDC 6-step commutation (DRV8323)
│   │   └── VoiceCoil.vhd     # Dual voice-coil PWM driver (~32 kHz)
│   └── RAPID.srcs/constrs_1/new/RAPID.xdc  # Pin constraints (Arty Z7-20)
├── PCB/              # PCB schematic/layout designed with Altium Designer
│   ├── Spindle       # BLDC spindle motor custom PCB files
│   └── Stepper       # Stepper motor custom PCB files
├── tests/                # Untested drivers / integration code
│   ├── mlx90393.c        # Bare-metal XIicPs driver for MLX90393 magnetometer (untested on hardware)
│   └── mlx90393.h        # Header: pin wiring, register definitions, calibration constants, API
├── old/                  # Archived / prototype code (not active)
│   ├── angleMeasure.ino  # Arduino sketch — MLX90393 magnetometer angle tracking (prototype, untested in system)
│   ├── BLDC.vhd          # Early abstract BLDC design
│   ├── OLD_pcCommunication.c   # Superseded PC communication code
│   └── OLD_systemControl.c     # Superseded FPGA application
├── docs/
│   ├── README.md             # This file
│   └── CLAUDE.md             # Full technical context for AI-assisted development
├── build/                    # Compiled output — generated by make, not committed
├── input.gds                 # Default GDS2 input file
├── Makefile                  # PC-side build
└── requirements.txt          # Python dependencies

Dependencies

PC (Windows)

Tool Purpose
MinGW-w64 / MSYS2 UCRT64 C compiler (gcc) and make
Python 3.11+ GUI
PySide6 6.x Qt GUI framework
pyqtgraph Real-time plotting

Install Python dependencies (preferably inside a venv):

python -m venv venv
venv\Scripts\activate
pip install -r requirements.txt

gcc and make are provided by MSYS2. If make is not on your PATH, add C:\msys64\usr\bin to your user environment variables.

FPGA

  • Xilinx Vivado 2025.1 (or compatible)
  • Xilinx Vitis 2025.1 (or compatible)
  • Target device: Arty Z7-20 (xc7z020clg400-1)
  • vitis_workspace/systemControl/main.c must be built inside a Vitis bare-metal project — it is not part of the PC Makefile.

Building & running

0 - Build the FPGA hardware & software (after cloning)

Hardware (Vivado)

  1. Open the Vivado project: File → Open Project → hardware/RAPID.xpr
  2. In the Sources panel, expand Design Sources and locate the block design top.bd
  3. Generate the HDL wrapper:
    • Right-click top (the .bd file) → Create HDL Wrapper…
    • Select "Let Vivado manage wrapper and auto-update" → OK
  4. Set the wrapper as the top module:
    • Right-click the newly created top_wrapper → Set as Top
  5. If IP are out of date: click Reports → Report IP Status → select all → Upgrade Selected
  6. Run Synthesis → Implementation → Generate Bitstream (Flow Navigator)
  7. Export the hardware definition:
    • File → Export → Export Hardware…
    • Check Include bitstream → save as top_wrapper.xsa

Software (Vitis)

The Vitis workspace is set up at vitis_workspace/ with two components:

  • platform — BSP platform project (built from the exported .xsa)
  • systemControl — active application: receives packets and drives motors/laser
  1. Launch Vitis and open the workspace: File → Open Workspace → vitis_workspace/
  2. Rebuild the platform if the .xsa changed: right-click platform → Build
  3. Build the application:
    • Right-click systemControl → Build Project
  4. Program the FPGA & run:
    • Connect the Arty Z7 via USB
    • Right-click systemControl → Run As → Launch on Hardware

Note: The XDC constraints file at hardware/RAPID.srcs/constrs_1/new/RAPID.xdc contains all pin and I/O standard assignments. If you change block design port names, update the XDC to match.

1 - Build RAPID.exe

make

Output: build/RAPID.exe

2 - Launch the GUI

make gui

make gui automatically uses the venv Python at venv/Scripts/python if the venv exists, and falls back to the system Python otherwise. To launch directly:

python src/gui.py

The GUI lets you:

  • Connect to the serial port and manage RAPID.exe as a subprocess
  • Select a GDS input file, preview its XY coordinates without hardware, and stream a pattern with live ACK plotting — the sled is automatically zeroed before each stream
  • Toggle spindle and laser, jog the stepper, send a zero request — all from the Manual Control tab
  • Set voice coil 1 duty cycle (0–100%) from the Manual Control tab
  • Stream a pattern multiple times (repeat count spinbox) without stopping the spindle between repetitions
  • Monitor spindle RPM on a live time-series plot in the RPM Monitor tab
  • Monitor ACK / CRC error counters and a scrolling output log

3 - Clean build artefacts

make clean

Packet wire format

Both main.c (PC) and systemControl/main.c (FPGA) use the same framing:

[ 0xAA | 0x55 | TYPE | LEN | PAYLOAD (LEN bytes) | CRC8 ]
Direction TYPE LEN Payload Purpose
PC → FPGA 0x01 0 (none) End of sequence — disables hardware, firmware stays running
PC → FPGA 0x10 8 r_um (int32 LE, µm) + theta_deg (float32 LE, degrees) Polar point
PC → FPGA 0x21 1 0x00/0x01 Spindle off/on
PC → FPGA 0x22 1 0x00/0x01 Stepper off/on
PC → FPGA 0x23 1 0x00/0x01 Laser off/on
PC → FPGA 0x24 1 0x00=inward, 0x01=outward Stepper direction
PC → FPGA 0x25 0 (none) Zero request
PC → FPGA 0x26 4 int32 LE step count Jog — move N steps in current direction
PC → FPGA 0x27 0 (none) RPM request — FPGA reads hall sensor period and responds
PC → FPGA 0x28 1 uint8 (0–100) Voice coil 1 duty cycle %
FPGA → PC 0x81 8 or 0 Echo of received payload ACK for all packet types
FPGA → PC 0x82 2 uint16 LE Computed spindle RPM (10000 / hall_period_ticks)
FPGA → PC 0xF0 N ASCII string Debug / status message

CRC8 is computed as XOR over [TYPE, LEN, PAYLOAD...].

Flow control: Stop-and-wait. Each packet waits up to 5 s for ACK.

TYPE_END: Disables laser, spindle, and stepper. The firmware ACKs and then continues the receive loop — it does not halt. Multiple pattern runs and manual commands work within one FPGA session.

Theta tracking: systemControl/main.c uses time-integration (theta_init() / wait_for_theta()) to stall each point until the disc reaches its target angle. theta_init() snapshots the hall-sensor RPM period and a Zynq global-timer timestamp; subsequent calls compute disc angle as (elapsed_us % rev_us) / rev_us × 360°. The laser is gated off between points when the remaining arc exceeds 50 ms to avoid unintended exposure during long cross-revolution waits.


GPIO control word (systemControl/main.c)

systemControl/main.c drives the motor hardware via a packed 27-bit value written to AXI GPIO Channel 1:

Bits Field Description
[0] spindle_en Spindle enable: 0=off, 1=on
[1] stepper_dir Stepper direction: 0=inward, 1=outward
[2] stepper_en Stepper enable: 0=off, 1=on
[3] zero_req Zero/home request: momentary high pulse
[24:4] num_step Number of steps to move (0–2,097,151)
[25] step_go Step go: momentary high pulse triggers move
[26] laser_en Laser enable: 0=off, 1=on

systemControl/main.c sequence

The firmware enters a packet receive loop immediately — no startup zeroing delay (the VHDL ZEROING state homes the sled at power-on). When streaming, RAPID.exe always zeros the sled before the first point is sent.

Packet Action
TYPE_POINT Compute target step = round(r_um / 30000 × 250), move stepper (dir set automatically), turn laser on after first move, ACK
TYPE_END Disable laser/spindle/stepper, reset state, ACK, continue loop
TYPE_SPINDLE/STEPPER/LASER/DIR Update corresponding GPIO bit, ACK
TYPE_ZERO Pulse zero_req 100 ms, reset current_step=0, ACK — ignored if stepper is disabled
TYPE_JOG Move N steps (int32 LE payload) in current direction, ACK — ignored if stepper is disabled
TYPE_RPM_REQ Read hall sensor period from axi_gpio_1, compute RPM = 10000 / ticks (6 pulses/rev, 1 kHz tick clock), respond with TYPE_RPM
TYPE_VC1_DC Write 0–100 duty cycle value to axi_gpio_1 ch2 → VoiceCoil.vhd VC1_DC port, ACK

250 steps = 30 mm (full disc range, inner to outer edge). The stepper step rate is 500 Hz (2 ms/step).

Spindle windup: At FPGA boot the firmware enables the spindle and waits 2 seconds for rotor alignment and speed. When streaming, RAPID.exe additionally waits 1 second after re-enabling the spindle before sending the first point. The laser turns on after the first point's stepper move completes.

RPM readout: spindle.vhd measures the period between consecutive hall sensor pulses using a 1 kHz tick counter and exposes the result via axi_gpio_1 ch1. The firmware computes RPM = 10000 / ticks (6 hall pulses per revolution) and logs it after every point during a stream. The PC can also request an on-demand reading by sending TYPE_RPM_REQ (0x27).

Voice coil: VoiceCoil.vhd generates a ~32 kHz PWM signal for VC1 (vertical / Z-axis). Duty cycle is software-controlled via axi_gpio_1 ch2 and the TYPE_VC1_DC packet. Firmware boots at 60% duty cycle. VC2 (horizontal) is present in hardware but not yet driven.


Future Work

Absolute spindle position via magnetometer (MLX90393)

The current theta tracking uses pure time-integration of RPM (accuracy ~1°/rev at 1% RPM error). A complete bare-metal Xilinx PS I2C driver for the MLX90393 3-axis magnetometer exists in tests/mlx90393.c / tests/mlx90393.h — untested on hardware. The algorithm reference is the Arduino sketch in old/angleMeasure.ino.

The driver communicates over PS I2C0, wired to the Arty Z7-20 Arduino-compatible header (SCL → A5 / P15, SDA → A4 / P16). It uses the same empirically derived calibration constants as the Arduino sketch and tracks accumulated angle via cross/dot-product of consecutive XY vectors (numerically stable through wrap-around).

To integrate into systemControl/main.c:

  1. Add tests/mlx90393.c and tests/mlx90393.h to the Vitis project.
  2. Call mlx_init(&mlx, XPAR_XIICPS_0_BASEADDR) at startup after the spindle windup.
  3. Replace or supplement wait_for_theta() with periodic mlx_read_angle() calls to get drift-free absolute angle.

The calibration constants are hardcoded and were derived empirically — a re-calibration step will be needed if the magnet is repositioned.

Voice coil VC2 — X direction (horizontal focus)

VoiceCoil.vhd already contains a fully wired PWM generator for VC2 (DC_cnt_2, clk_div_cnt2, PWM_2_sig) and the XDC pin assignments are defined (PWM2 → V6, PWM2r → U7). The channel is hardcoded to 0% duty cycle pending the following steps:

  1. Add VC2_DC : in STD_LOGIC_VECTOR(6 downto 0) to the VoiceCoil entity port list, remove DC_cnt_2 <= 0, and add DC_cnt_2 <= to_integer(unsigned(VC2_DC)) * 39.
  2. Connect VC2_DC to a new AXI GPIO output channel in the Vivado block design and re-export the hardware definition.
  3. Add TYPE_VC2_DC 0x29 to src/protocol.h and vitis_workspace/systemControl/protocol.h.
  4. Add a handler in vitis_workspace/systemControl/main.c (mirror the TYPE_VC1_DC handler).
  5. Add a VC2 <0-100> command in src/main.c and a matching GUI control in src/gui.py.

Hall-sector theta re-sync

The spindle already generates 6 hall pulses per revolution (known 60° sector boundaries). Rather than relying solely on time-integration, wait_for_theta() in systemControl/main.c could latch each hall rising edge (via a new interrupt or polled GPIO) and snap the theta reference to the nearest known sector angle, eliminating long-run drift without any additional sensors.

Closed-loop spindle speed control

Commutation is currently open-loop (fixed 10 Hz clock in spindle.vhd). The hall-sensor RPM measurement already available in the firmware could drive a simple proportional controller that adjusts count_max in the VHDL to maintain a target RPM — making theta tracking more accurate and the pattern write speed more consistent.

Stepper closed-loop position verification

stepperDriver.vhd exports step_total_out (21-bit absolute step counter from home), which is connected to axi_gpio_0 channel 2 (configured as input). The firmware currently does not read it. Reading step_total_out after each move and comparing it to current_step would detect missed steps and allow recovery homing.

About

Rapid Photonic Innovation Device | ECE Capstone Design Award Winner | Embedded System design in C & VHDL

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages