Phase-shift keying

import matplotlib.pyplot as plt
import numpy as np

import sdr

%config InlineBackend.print_figure_kwargs = {"facecolor" : "w"}
# %matplotlib widget

In the sdr library, phase-shift keying modulation is available in the sdr.PSK class.

def analyze_psk(order, esn0, offset=0):
    # Create a PSK modulation object
    psk = sdr.PSK(order, phase_offset=offset)

    # Generate random decimal symbols
    s = np.random.randint(0, order, 100_000)

    # Modulate decimal symbols to complex symbols
    x = psk.modulate(s)

    # Add AWGN to complex symbols to achieve desired Es/N0
    snr = sdr.esn0_to_snr(esn0, sps=1)
    x_hat = sdr.awgn(x, snr)

    plt.figure(figsize=(10, 5))
    plt.subplot(1, 2, 1)
    # sdr.plot.constellation(x_hat, linestyle="none")
    sdr.plot.symbol_map(psk.symbol_map, limits=(-2, 2))
    plt.subplot(1, 2, 2)
    sdr.plot.constellation(x_hat, bins=75, heatmap=True, limits=(-2, 2))
    plt.title(f"Constellation at {esn0} dB $E_s/N_0$")
    plt.suptitle(f"{order}-PSK constellation")
    plt.tight_layout()
    plt.show()

    h_srrc = sdr.root_raised_cosine(0.1, 6, 10)
    tx_mf = sdr.FIRInterpolator(h_srrc, 10)
    y = tx_mf.filter(x)

    plt.figure(figsize=(10, 5))
    sdr.plot.time_domain(y[0:1000])
    plt.show()

Constellations

BPSK

analyze_psk(2, 6)
../../_images/f4a00e4cc6533c2c5b267827fa482ac1f9f733839aa2b9b160105b047e43a590.png ../../_images/8638491b97aede8d583a944f6a47378a66f0e3ba07da72ec2c40eaf846d110b7.png

QPSK

analyze_psk(4, 9, offset=45)
../../_images/ab8aaca8cbd8608385ec7ef2f99647a5ed070c5998e81e99fd13b41fa845b0f6.png ../../_images/306f54d4aed4dd139861d77bbb9b9297b9dab4aee431710d1721bea98f19e651.png

8-PSK

analyze_psk(8, 12)
../../_images/37cc834fc32b28b22f9e6884e51d2bcff10e94182321563c027aa98dacb6cc49.png ../../_images/af84c8f35157ba435fb5ccfc002a6380c4f43e8c8c6b083dda4eabe60feb44d4.png

16-PSK

analyze_psk(16, 18)
../../_images/25f723773d9ab6809479361ee0aae8ed3d23b4da3065b8848ba508860c945644.png ../../_images/59e8f6a149078e33d99f9554a1e9acaf0d3f827c9030e095e26826277c26a407.png

Error rate curves

def error_rates(order):
    psk = sdr.PSK(order)
    k = int(np.log2(order))  # Bits per symbol

    ebn0 = np.linspace(0, 10, 20)
    esn0 = ebn0 + 10 * np.log10(k)

    ber = sdr.ErrorRate()
    ser = sdr.ErrorRate()

    for i in range(esn0.size):
        N_symbols = int(1e6)
        s = np.random.randint(0, psk.order, N_symbols)
        x = psk.modulate(s)
        x_hat = sdr.awgn(x, snr=esn0[i])
        s_hat = psk.demodulate(x_hat)

        ber.add(ebn0[i], sdr.unpack(s, k), sdr.unpack(s_hat, k))
        ser.add(esn0[i], s, s_hat)

    return ber, ser
bpsk_ber, bpsk_ser = error_rates(2)
qpsk_ber, qpsk_ser = error_rates(4)
psk8_ber, psk8_ser = error_rates(8)
psk16_ber, psk16_ser = error_rates(16)
plt.figure(figsize=(10, 5))
sdr.plot.ber(*bpsk_ber.error_rates(), label="BPSK")
sdr.plot.ber(*qpsk_ber.error_rates(), label="QPSK")
sdr.plot.ber(*psk8_ber.error_rates(), label="8-PSK")
sdr.plot.ber(*psk16_ber.error_rates(), label="16-PSK")
plt.ylim(1e-5, 1e0)
plt.title("Bit error rate curves for PSK modulation in AWGN")
plt.tight_layout()
plt.show()
../../_images/94b5e565e61d70934f4de7fe99dbe32349fd2ff261cd3420e956ed218b415086.png
plt.figure(figsize=(10, 5))
sdr.plot.ser(*bpsk_ser.error_rates(), label="BPSK")
sdr.plot.ser(*qpsk_ser.error_rates(), label="QPSK")
sdr.plot.ser(*psk8_ser.error_rates(), label="8-PSK")
sdr.plot.ser(*psk16_ser.error_rates(), label="16-PSK")
plt.ylim(1e-5, 1e0)
plt.title("Symbol error rate curves for PSK modulation in AWGN")
plt.tight_layout()
plt.show()
../../_images/3c88a760a180b692ccaf20617ca338acafbc9a0f76a776e9ed210a07107c79e9.png

Last update: Jul 29, 2023