Metadata-Version: 2.4
Name: pynq-oscilloscope
Version: 1.1.0
Summary: High-level Python package for a real-time 1 MSPS DMA Oscilloscope on PYNQ boards using Analog Discovery 3
Home-page: https://github.com/SiririComun/sw-pynq-oscilloscope
Author: Juan Pablo Sánchez (SiririComun)
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: POSIX :: Linux
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.19.0
Requires-Dist: plotly<6.0.0,>=5.10.0
Requires-Dist: ipywidgets>=8.0.0
Requires-Dist: pydwf>=1.1.0
Dynamic: author
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license-file
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# Real-Time 1 MSPS Hardware-Triggered PYNQ Oscilloscope

[![PyPI Version](https://img.shields.io/pypi/v/pynq-oscilloscope.svg)](https://pypi.org/project/pynq-oscilloscope/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Hardware Overlay](https://img.shields.io/badge/Hardware-hw--xadc--dma--overlays%20v1.1.0-orange.svg)](https://github.com/SiririComun/hw-xadc-dma-overlays)
[![Board Support](https://img.shields.io/badge/Board-PYNQ--Z2-green.svg)](https://tul.com.tw/ProductsPYNQ-Z2.html)

A high-performance, dark-mode real-time Oscilloscope software stack running natively on PYNQ Linux platforms. 

Features **sub-microsecond hardware-level edge triggering** (`axis_trigger_unit`), **1 MSPS XADC streaming via AXI DMA** direct to DDR memory, and non-blocking analog signal generation with the **Digilent Analog Discovery 3** via `pydwf`.

---

## 🏛 System Architecture

This repository adopts the **canonical PYNQ Custom Overlay pattern** (`OscilloscopeOverlay`). It automatically pulls its compiled hardware bitstream and metadata from GitHub Releases (or loads local custom `.bit` builds) and encapsulates the DMA receiver, AXI-Lite trigger registers, and wavegen into a unified Python object.

```
 [ Analog Discovery 3 (W1) ] ──(Analog Jumper Wire)──> [ PYNQ-Z2 Header (A0) ]
              │                                                     │
        (pydwf SDK)                                     (XADC 1 MSPS AXI-Stream)
              │                                                     │
              ▼                                                     ▼
 [ AD3SignalGenerator ]                              [ axis_trigger_unit IP ]
              │                                     (Edge, Threshold, Auto Timeout)
              │                                                     │
              │                                                     ▼
              │                                          [ AXI DMA S2MM Engine ]
              │                                                     │
              └───────────────────────┬─────────────────────────────┘
                                      │
                                      ▼
                           [ OscilloscopeOverlay ]
                   (Subclasses pynq.Overlay with sub-drivers)
                    ├── .trigger  (HardwareTrigger AXI-Lite)
                    ├── .xadc     (StreamingXADC DMA Driver)
                    ├── .wavegen  (AD3SignalGenerator)
                    └── .dashboard() (Interactive Plotly Canvas)
```

---

## 🖥 Interactive Dashboard UI Guide

![Real-Time 1 MSPS PYNQ Oscilloscope Dashboard](https://raw.githubusercontent.com/SiririComun/sw-pynq-oscilloscope/main/docs/images/dashboard_screenshot.png)

### 1. Control & Action Bar (Row 1)
* **`▶ Start`:** Initializes the background acquisition worker, turns on AD3 waveform generation, arms the FPGA trigger, and begins DMA streaming.
* **`■ Stop`:** Cleanly halts the acquisition loop, disarms the trigger, stops the AD3 wavegen, and releases device handles.
* **`⚡ Force / Arm`:** 
  * In **Single Mode**, re-arms the FPGA trigger unit to capture the next transient event.
  * In any mode, pulses bit 4 of `CONTROL_REG` (`0x00`) to force an immediate hardware frame capture.
* **`Auto-Range` (Toggle):** Dynamically scales the visible horizontal timebase (showing 5–10 signal periods) and adapts the vertical Y-axis limits ($1.65\,\text{V} \pm \text{Amplitude}$ with margin).
* **`Live Vpp`:** Real-time peak-to-peak voltage calculation updated live ($V_{pp} = V_{\max} - V_{\min}$).

### 2. Hardware Trigger Controls (Row 2)
* **`Trig Mode`:**
  * **`Auto`:** Continuous live stream. Locks onto trigger edges when present; if no edge occurs within 50 ms (e.g., disconnected input or threshold out of range), the hardware auto-timeout forces a frame capture so the display never freezes.
  * **`Normal`:** Strictly edge-triggered. The FPGA *only* captures and transfers data to DDR memory when a valid trigger event occurs.
  * **`Single`:** Captures **one single frame** on the first trigger event and freezes the display. Re-arm by clicking **`⚡ Force / Arm`**.
* **`Trig Edge` (`Rising` / `Falling`):** Configures whether the FPGA comparator triggers on the upward slope ($\nearrow$) or downward slope ($\searrow$).
* **`Trig Level` (Slider & Numeric Box):** Sets the FPGA voltage threshold register (`0x08`) between $0.0\,\text{V}$ and $3.3\,\text{V}$ with client-side zero-latency linking (`widgets.jslink`).

### 3. AD3 Signal Generator Controls (Rows 3 & 4)
* **`Waveform` (`Sine`, `Triangle`, `Square`):** Selects the DAC output waveform on AD3 Wavegen Channel 1 (W1).
* **`Amp Slider / Exact Amp`:** Adjusts the signal amplitude in Volts ($0.1\,\text{V}$ to $1.5\,\text{V}$).
* **`Freq Slider / Exact Freq`:** Sets the generation frequency in Hertz ($100\,\text{Hz}$ to $1\,\text{MHz}$).

### 4. Interactive Plotly Canvas
* **Cyan Trace (`A0 (Analog In)`):** 1 MSPS analog signal stream read directly from DDR memory. Sample $[0]$ ($t=0\,\mu\text{s}$) is hardware-aligned to the trigger edge.
* **Orange Dashed Trace (`Trigger Level`):** Live visual threshold line reflecting the FPGA trigger register level.

---

## 🔌 Hardware Setup & Wiring

1. **AD3 USB Connection:**
   * Plug the Analog Discovery 3 USB cable into the large rectangular **USB HOST** port on the PYNQ-Z2 board (adjacent to Ethernet).
2. **USB Cable Quality:**
   * Ensure you use a **Data + Power USB-C cable** (charging-only cables will not be detected by Linux).
3. **Power Supply:**
   * Power the AD3 with an external **5V auxiliary power supply** to prevent brownouts under load.
4. **Analog Signals:**
   * Connect a jumper wire from **Wavegen 1 (W1)** on the AD3 to **Analog Input A0** on the PYNQ-Z2 Arduino header.
   * Connect an AD3 **GND** pin to a PYNQ-Z2 **GND** pin.

---

## 🚀 Quick Start & Installation

### 1. Install Package from PyPI
Connect to your PYNQ board via SSH or Jupyter Terminal and run:

```bash
pip install --upgrade pynq-oscilloscope
```

### 2. Copy Example Notebooks to Jupyter Workspace
Copy this project's notebooks into `/home/xilinx/jupyter_notebooks/pynq_oscilloscope/`:

```bash
pynq-oscilloscope-get-notebooks
```

### 3. Install Digilent AD3 Drivers
Run the automated environment setup inside Python or a Jupyter cell:

```python
from pynq_oscilloscope import install_ad3_drivers

# Downloads Adept Runtime + WaveForms SDK and configures USB permissions
install_ad3_drivers()
```

---

## 💻 Python API Usage

### 1. Launch Interactive Dashboard in 2 Lines (Default Cloud Fetch)
```python
from pynq_oscilloscope import OscilloscopeOverlay

# Automatically identifies board (PYNQ-Z2), downloads v1.1.0 release, and loads FPGA
ol = OscilloscopeOverlay()

# Launch dark-mode interactive Plotly + IPywidgets dashboard
app = ol.dashboard()
```

### 2. Load Local Custom Bitstream (Offline / Development)
```python
from pynq_oscilloscope import OscilloscopeOverlay

# Load a local bitstream while preserving all driver hooks and UI tools
ol = OscilloscopeOverlay("./pynq_z2.bit")
app = ol.dashboard()
```

### 3. Programmatic Hardware Trigger & DMA Capture
```python
from pynq_oscilloscope import OscilloscopeOverlay

ol = OscilloscopeOverlay()

# Configure FPGA Trigger: Rising Edge @ 1.65V with 50 ms Auto-timeout
ol.trigger.configure(mode="Auto", edge="Rising", threshold_volts=1.65, timeout_ms=50.0)

# Capture 16,384 samples (Sample [0] is guaranteed hardware-aligned to trigger point!)
voltages = ol.capture()
print(f"Captured {len(voltages)} samples. Min: {voltages.min():.2f}V, Max: {voltages.max():.2f}V")

# Clean release of memory buffers
ol.close()
```

---

## 📓 Notebook Suite

| Notebook | Description | Key Modules Used |
| :--- | :--- | :--- |
| **`01_ad3_getting_started.ipynb`** | Verifies Digilent drivers and generates analog signals (Sine, Square, Triangle) in a non-blocking background worker. | `AD3SignalGenerator`, `check_usb_permissions` |
| **`02_xadc_getting_started.ipynb`** | Demonstrates `OscilloscopeOverlay`, hardware register trigger configuration (`ol.trigger`), and DMA capture. | `OscilloscopeOverlay`, `HardwareTrigger` |
| **`03_oscilloscope_dashboard.ipynb`** | **Main Application:** Deploys the complete interactive Plotly Oscilloscope with live trigger line, auto-ranging, and AD3 integration. | `OscilloscopeOverlay` |

---

## 📄 License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
