Skip to content

Commit d2d4d3f

Browse files
authored
example: add example demonstrating an ASK modulator (#28)
1 parent 72b5c5d commit d2d4d3f

File tree

5 files changed

+1358
-0
lines changed

5 files changed

+1358
-0
lines changed

examples/ask_modulator/README.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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

Comments
 (0)