|
| 1 | +# Shrike-Lite ASK Modulator |
| 2 | + |
| 3 | +This project implements a mixed-signal Digital-to-Analog communication system using the Shrike-Lite board. It leverages the RP2040 as a Control Plane (Data/Protocol) and the ForgeFPGA as a Data Plane (Direct Digital Synthesis & PWM). |
| 4 | + |
| 5 | +The system generates a smooth Sine Wave using DDS (Direct Digital Synthesis), converts it to a digital bitstream using PWM, and modulates it using Amplitude Shift Keying (ASK) commands from the RP2040. |
| 6 | + |
| 7 | +To understand the working principle of DDS and PWM audio generation, refer to: |
| 8 | +[FPGA DDS Tutorial](https://www.fpga4fun.com/DDS.html). |
| 9 | + |
| 10 | +--- |
| 11 | + |
| 12 | +## Block Diagram |
| 13 | +```mermaid |
| 14 | + graph LR |
| 15 | + A[RP2040 MCU] -- 6-Bit Frequency Bus --> B[FPGA Input GPIO] |
| 16 | + A -- Data/Enable Signal --> B |
| 17 | + B -- Tuning Word --> C[DDS Logic] |
| 18 | + C -- Sine Value --> D[PWM Generator] |
| 19 | + D -- Digital Pulses --> E[FPGA Output Pin] |
| 20 | + E -- RC Filter --> F[Analog Scope/Audio] |
| 21 | +``` |
| 22 | +## Overview on FPGA Side |
| 23 | +The FPGA logic (`dds_ask_modulator`) acts as a high-speed synthesizer consisting of three main stages: |
| 24 | + |
| 25 | +1. **Phase Accumulator:** A 16-bit counter that increments by a specific "Tuning Word" value every clock cycle. The speed of the overflow determines the carrier frequency. |
| 26 | +2. **Sine Look-Up Table (LUT):** A 64-entry ROM that maps the phase value to a digital amplitude (0 to 63), creating a sine wave shape. |
| 27 | +3. **PWM Generator:** Converts the 6-bit amplitude into a 1-bit high-speed Pulse Width Modulated signal (approx 780 kHz switching rate) suitable for RC filtering. |
| 28 | + |
| 29 | +The modulation is performed logically at the output stage: |
| 30 | +- If `i_data` is High: The generated Sine Wave is passed to the output. |
| 31 | +- If `i_data` is Low: The output is forced to 0 (Silence). |
| 32 | + |
| 33 | +--- |
| 34 | + |
| 35 | +## Features |
| 36 | +- **Direct Digital Synthesis (DDS):** Generates mathematically precise sine waves without external DAC chips. |
| 37 | +- **Hybrid Architecture:** Offloads fast signal generation (50 MHz logic) to FPGA, keeping the MCU free for text processing and timing. |
| 38 | +- **Configurable Carrier:** A 6-bit parallel bus allows the RP2040 to tune the carrier frequency instantly. |
| 39 | +- **ASK (Amplitude Shift Keying):** Implements On-Off Keying (OOK) to transmit binary data bursts. |
| 40 | +- **Custom Encoding:** Implements a custom 6-bit character map for alphanumeric transmission over the ASK link. |
| 41 | + |
| 42 | +--- |
| 43 | + |
| 44 | +## Top Module Interface |
| 45 | + |
| 46 | +| Signal | Direction | Description | |
| 47 | +| :--- | :--- | :--- | |
| 48 | +| `i_clk` | In | System clock (50 MHz Internal Oscillator) | |
| 49 | +| `i_freq_word[5:0]` | In | 6-Bit Tuning Word (Controls Carrier Pitch) | |
| 50 | +| `i_data` | In | Modulation Signal (1=Carrier ON, 0=Silence) | |
| 51 | +| `o_pwm_out` | Out | PWM Digital Output (Requires Filter) | |
| 52 | +| `o_pwm_out_oe` | Out | Output Enable (Always 1) | |
| 53 | +| `o_clk_en` | Out | Oscillator Enable (Always 1) | |
| 54 | + |
| 55 | +--- |
| 56 | + |
| 57 | +## Parameters & Math |
| 58 | +#### `CLK_FREQ :` |
| 59 | +- System clock frequency. |
| 60 | + - `50 MHz` |
| 61 | +#### `PWM CARRIER :` |
| 62 | +- The PWM switching frequency is derived from the clock and the resolution ($2^6$). |
| 63 | + - `50 MHz / 64 steps ≈ 781.25 kHz` |
| 64 | +#### `OUTPUT FREQUENCY :` |
| 65 | +- The audible output frequency is determined by the Tuning Word ($TW$) sent by the RP2040. |
| 66 | + - $F_{out} = \frac{F_{clk} \times TW}{2^{16}}$ |
| 67 | + - With $TW=1$, Output $\approx 762 \text{ Hz}$ (Optimal for filtering). |
| 68 | + |
| 69 | +--- |
| 70 | + |
| 71 | +## Hardware Setup |
| 72 | +### 1. The RC Filter (Demodulator) |
| 73 | +The FPGA output is a digital square wave. To see the sine wave on an oscilloscope, an RC Low Pass Filter is required to reconstruct the analog signal. |
| 74 | + |
| 75 | +* **Resistor:** 1 kΩ |
| 76 | +* **Capacitor:** 10 nF (Code 103) - *Cutoff ~15.9 kHz* |
| 77 | +* **Connection:** Connect Resistor to FPGA Output. Connect Capacitor from Resistor leg to Ground. Probe the junction between R and C. |
| 78 | + |
| 79 | +### 2. Pin Interconnects (Jumper Wires) |
| 80 | +Due to pin availability on the specific package, a mixed-header wiring scheme is used. Ensure **Common Ground** between headers. |
| 81 | + |
| 82 | +| Signal | RP2040 Pin | FPGA Pin (Board Label) | Physical Pin (Bitstream) | |
| 83 | +| :--- | :--- | :--- | :--- | |
| 84 | +| **Freq Bit 0 (LSB)** | **GPIO 5** | **FPGA_IO1** | **PIN 14** | |
| 85 | +| **Freq Bit 1** | **GPIO 6** | **FPGA_IO2** | **PIN 15** | |
| 86 | +| **Freq Bit 2** | **GPIO 7** | **FPGA_IO17** | **PIN 8** | |
| 87 | +| **Freq Bit 3** | **GPIO 8** | **FPGA_IO18** | **PIN 9** | |
| 88 | +| **Freq Bit 4** | **GPIO 9** | **FPGA_IO8** | **PIN 23** | |
| 89 | +| **Freq Bit 5 (MSB)** | **GPIO 10**| **FPGA_IO9** | **PIN 24** | |
| 90 | +| **Data / Enable** | **GPIO 16**| **FPGA_IO0** | **PIN 13** | |
| 91 | +| **Power Control** | **GPIO 12/13** | *(Internal)* | *(Internal)* | |
| 92 | +| **PWM Output** | *(None)* | **FPGA_IO14** | **PIN 5** | |
| 93 | + |
| 94 | +> **Note:** FPGA Physical Pins refer to the IO Manager mapping in Go Configure. |
| 95 | +
|
| 96 | +--- |
| 97 | + |
| 98 | +## Firmware Overview |
| 99 | +The control logic is written in **MicroPython** running on the RP2040. |
| 100 | + |
| 101 | +1. **`flash.py`:** Uses the `shrike` library to write the Verilog bitstream (`.bin`) from the RP2040 filesystem into the FPGA configuration memory. |
| 102 | +2. **`main.py`:** |
| 103 | + * Initializes the 6-bit Parallel Bus to set the Carrier Frequency. |
| 104 | + * Powers up the FPGA Core (GPIO 12) and IOs (GPIO 13). |
| 105 | + * Implements a **Custom 6-Bit Look-Up Table** to map characters (A-Z, 0-9) to specific 6-bit integer codes. |
| 106 | + * Transmits the string `"HelloShrike123"` by toggling the `i_data` pin (ASK) using a Start-Bit/Stop-Bit protocol. |
| 107 | + |
| 108 | +### Exercise for the User |
| 109 | +The current implementation utilizes a custom, optimized 6-bit codebook to map alphanumeric characters to the available bandwidth. |
| 110 | +**A full standard ASCII (8-bit) implementation requires splitting characters into two 4-bit nibbles or serializing the data further. This implementation is left as an exercise for the reader.** |
| 111 | + |
| 112 | +--- |
| 113 | + |
| 114 | +## Quick Steps |
| 115 | +1. **Synthesize:** Generate the bitstream in Go Configure with the pin map above. |
| 116 | +2. **Upload:** Copy `FPGA_bitstream_MCU.bin`, `flash.py`, and `helloshrike.py` to the RP2040 via Thonny/VS Code. |
| 117 | +3. **Flash:** Run `flash.py` once to configure the FPGA. |
| 118 | +4. **Run:** Run `helloshrike.py` to start the transmission loop. |
| 119 | +5. **Observe:** Connect Oscilloscope to **FPGA_IO14**. Set Timebase to `50ms/div` and Trigger to `Normal`. |
| 120 | + |
| 121 | +## Expected Output |
| 122 | +When running `helloshrike.py`, the oscilloscope will show bursts of Sine Waves corresponding to the logic levels of the transmitted text: |
| 123 | + |
| 124 | +* **Logic 1:** 3V Sine Wave (Carrier ON) |
| 125 | +* **Logic 0:** 0V Flat Line (Carrier OFF) |
| 126 | + |
| 127 | +Output on the terminal should look like as shown below: |
| 128 | + |
| 129 | +``` |
| 130 | +--- Transmitting: HelloShrike123 --- |
| 131 | +Sending 'H' -> Code 01 -> 000001 |
| 132 | +Sending 'e' -> Code 02 -> 000010 |
| 133 | +Sending 'l' -> Code 03 -> 000011 |
| 134 | +Sending 'l' -> Code 03 -> 000011 |
| 135 | +Sending 'o' -> Code 04 -> 000100 |
| 136 | +Sending 'S' -> Code 05 -> 000101 |
| 137 | +Sending 'h' -> Code 06 -> 000110 |
| 138 | +Sending 'r' -> Code 07 -> 000111 |
| 139 | +Sending 'i' -> Code 08 -> 001000 |
| 140 | +Sending 'k' -> Code 09 -> 001001 |
| 141 | +Sending 'e' -> Code 02 -> 000010 |
| 142 | +Sending '1' -> Code 51 -> 110011 |
| 143 | +Sending '2' -> Code 52 -> 110100 |
| 144 | +Sending '3' -> Code 53 -> 110101 |
| 145 | +``` |
| 146 | + |
| 147 | +The signal will look like a structured sequence of bursts separated by silence, representing the binary data of the text string. |
0 commit comments