"Phase shift keying is the foundation of every modern digital communication system. Wi-Fi, GPS, cellular -- they all start with phase." — Software Defined Radio for Engineers, Travis Collins et al. (Analog Devices, 2019), Ch. 5
Lecture (50 min)
9.1 PSK from First Principles
Phase shift keying encodes bits by varying the phase of a carrier signal. The receiver measures phase at each symbol boundary and maps the phase back to a bit pattern.
BPSK (Binary PSK): 2 phase states (0°, 180°). One bit per symbol.
- Symbol 0: carrier phase = 0° (positive)
- Symbol 1: carrier phase = 180° (negative / inverted)
- Constellation: two clusters on the real axis at (-1, 0) and (+1, 0)
QPSK (Quadrature PSK): 4 phase states (45°, 135°, 225°, 315°). Two bits per symbol.
- Constellation: four clusters at equal spacing, each at unit distance from origin
- Gray coded: adjacent symbols differ by 1 bit (an error in one direction loses only 1 bit)
- Same bandwidth as BPSK, twice the throughput
8-PSK: 3 bits per symbol, 8 phase states at 45° spacing. Used in advanced APSK modes (DVB-S2) and some LTE channels.
Why not just use more phase states? As the number of states increases, the minimum phase difference between adjacent symbols decreases. A small amount of phase noise or timing error causes symbol errors. The practical ceiling depends on SNR. BPSK is the most noise-tolerant; 1024-QAM (used in Wi-Fi 6) requires very high SNR.
9.2 The BPSK Modulation Chain in GNU Radio
[Random Source] → [BPSK Mod] → [Interpolating FIR Filter] → [osmocom Sink]
Key parameters:
- Samples per symbol: how many IQ samples represent one BPSK symbol. At 1 Msps and 8 samples/symbol: 125,000 symbols/second.
- Excess bandwidth (alpha): controls the RRC (Root Raised Cosine) pulse shaping filter. Alpha=0.35 is standard. Pulse shaping prevents ISI (inter-symbol interference) at the cost of some bandwidth expansion.
- Center frequency: the RF carrier frequency for the transmission
The corresponding receive chain:
[osmocom Source] → [Low Pass Filter] → [Polyphase Clock Sync] → [BPSK Demod] → [Bit Recovery]
The Polyphase Clock Sync block recovers the symbol timing from the received signal. Without it, the demodulator samples at random points in the symbol interval, causing high bit error rates.
9.3 Designing the 3-Way Handshake Protocol
The capstone PSK 3-way handshake is a toy protocol over BPSK. The design requirements:
- Establish that both sides are present and can decode each other
- Use BPSK as the physical layer (no packet switching, no error correction)
- Fit in a GNU Radio flowgraph without custom blocks (use Python source/sink blocks)
Frame format (16 bytes):
[PREAMBLE: 4 bytes] [TYPE: 1 byte] [SEQ: 1 byte] [PAYLOAD: 8 bytes] [CRC16: 2 bytes]
- Preamble:
0xAA AA AA AA(alternating 1/0 for clock recovery) - Type: 0x01 = SYN, 0x02 = ACK, 0x03 = SYN-ACK
- SEQ: sequence counter (0-255)
- Payload: 8 bytes (arbitrary data or zero-padded)
- CRC16: CRC of TYPE + SEQ + PAYLOAD
Handshake sequence:
Station A Station B
|--- SYN (type=0x01) ------>|
|<-- SYN-ACK (type=0x03) ---|
|--- ACK (type=0x02) ------->|
|=== Channel Established ====|
Station A transmits first. Station B demodulates, detects SYN, constructs SYN-ACK. Station A demodulates SYN-ACK, sends ACK. Both sides log the exchange as complete.
9.4 Implementing in GNU Radio
BPSK TX flowgraph (Station A):
# Python Block: Handshake Frame Generator
# Emits the SYN frame bytes, then waits for trigger to emit ACK
import numpy as np
import struct, binascii
def make_frame(ftype, seq, payload=b'\x00'*8):
hdr = bytes([0xAA,0xAA,0xAA,0xAA, ftype, seq]) + payload
crc = binascii.crc_hqx(hdr[4:], 0xFFFF)
return hdr + struct.pack('>H', crc)
syn = make_frame(0x01, 0x00)
bits = np.unpackbits(np.frombuffer(syn, dtype=np.uint8))
bpsk_symbols = (2*bits - 1).astype(np.complex64)
bpsk_symbols.tofile("syn_frame.iq")
Use File Source in GNU Radio to feed these symbols through the BPSK Mod chain.
Virtual path: run both TX and RX chains as File Source → BPSK Mod → Channel Model (AWGN) → BPSK Demod → File Sink with bpsk_symbols as input. No hardware required.
9.5 Reading an IQ Capture: Decoding ASK from First Principles
For the sub-GHz ASK portion of Lab 9, the decode process:
import numpy as np
import matplotlib.pyplot as plt
samples = np.fromfile("capture.iq", dtype=np.complex64)
sample_rate = 250000 # confirm from capture metadata
# Step 1: amplitude envelope
amplitude = np.abs(samples)
# Step 2: low-pass filter to smooth
from scipy.signal import butter, filtfilt
b, a = butter(4, 10000 / (sample_rate / 2), btype='low')
smoothed = filtfilt(b, a, amplitude)
# Step 3: threshold
threshold = (smoothed.max() + smoothed.min()) / 2
bits = (smoothed > threshold).astype(int)
# Step 4: clock recovery (manual or scipy.signal.find_peaks)
# Find transitions; measure symbol duration; sample at mid-symbol
This manual decode is what URH (Universal Radio Hacker) automates with its signal analysis wizard.
Lab Preview
Lab 9 has three exercises (see Lab 9 file for full detail):
- Exercise A: Decode the provided ASK IQ capture in Python (all students)
- Exercise B: Build and run the BPSK 3-way handshake flowgraph in GNU Radio (in-lab or virtual path)
- Exercise C: Observe the rolling-code demo device and record the counter progression
Homework
Reading (45 min): PySDR (Lichtman) Chapter 5 (PSK and QAM). Work through the BPSK constellation simulation example.
Hands-on (60 min): Implement the QPSK mapper and demapper in Python (no GNU Radio):
import numpy as np
def qpsk_map(bits):
# Gray code: 00->45°, 01->135°, 11->225°, 10->315°
gray_map = {(0,0): 1+1j, (0,1): -1+1j, (1,1): -1-1j, (1,0): 1-1j}
pairs = bits.reshape(-1,2)
return np.array([gray_map[tuple(p)] for p in pairs], dtype=np.complex64) / np.sqrt(2)
bits = np.array([0,0,0,1,1,1,1,0])
symbols = qpsk_map(bits)
print(symbols)
Then add AWGN noise at SNR = 10 dB, 5 dB, and 0 dB. At what SNR does your hard-decision demapper start making errors?
Toolchain Diary Entry
First-introduce this week: URH (Universal Radio Hacker), GNU Radio BPSK Mod/Demod blocks
urh capture.iq: launches the URH GUI for signal analysis. Auto-detects modulation parameters (bit rate, modulation type). Provides protocol analysis + decoder plugin support.
GNU Radio blocks: BPSK Mod (gr.digital.psk.mod, modulation_order=2), BPSK Demod, Polyphase Clock Sync, CRC16 Async, Correlate Access Code -- Tag, Packet Decoder.
Key Terms
- BPSK: Binary Phase Shift Keying; 2 phase states; 1 bit/symbol; most noise-tolerant PSK variant
- QPSK: Quadrature PSK; 4 phase states; 2 bits/symbol; same bandwidth as BPSK, twice the throughput
- Gray coding: constellation labeling where adjacent symbols differ by exactly 1 bit; minimizes bit errors from adjacent-symbol confusion
- Excess bandwidth (alpha): RRC pulse shaping parameter; controls bandwidth vs. ISI trade-off; alpha=0 = minimum bandwidth but infinite time-domain extent; typical alpha=0.35
- ISI: Inter-Symbol Interference; adjacent symbols blur together in time domain; eliminated by matched filtering (RRC)
- Polyphase Clock Sync: GNU Radio block for symbol timing recovery; critical for correct BPSK/QPSK demodulation from a real receiver
- URH: Universal Radio Hacker; open-source tool for IQ signal analysis, protocol reverse engineering, and replay