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(psk, esn0):
# Generate random decimal symbols
s = np.random.randint(0, psk.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.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"{psk.order}-PSK constellation")
plt.tight_layout()
plt.show()
sps = 10
h_srrc = sdr.root_raised_cosine(0.1, 6, sps)
tx_mf = sdr.Interpolator(sps, h_srrc)
y = tx_mf(x)
plt.figure(figsize=(10, 5))
sdr.plot.time_domain(y[0:1000])
plt.show()
Constellations¶
BPSK¶
bpsk = sdr.PSK(2)
analyze_psk(bpsk, 6)
![../../_images/c1ce68a9fd6db55655be6a14273e979a978e417914d720ee377de9c9b53ef4ef.png](../../_images/c1ce68a9fd6db55655be6a14273e979a978e417914d720ee377de9c9b53ef4ef.png)
![../../_images/1632e6f6a6acbdcf25a2d5ffc2e3d2fae064e731c11803041c1050aae7f4503d.png](../../_images/1632e6f6a6acbdcf25a2d5ffc2e3d2fae064e731c11803041c1050aae7f4503d.png)
QPSK¶
qpsk = sdr.PSK(4, phase_offset=45)
analyze_psk(qpsk, 9)
![../../_images/1f71763be5d46f0e6aaecd2141d94f06e2fcdc6d752f2431ca38c95a169d4e9f.png](../../_images/1f71763be5d46f0e6aaecd2141d94f06e2fcdc6d752f2431ca38c95a169d4e9f.png)
![../../_images/44d0e523e0f008ee0c48fb2993b3ee41a15dccf40d6df85f6da827861f02ce2d.png](../../_images/44d0e523e0f008ee0c48fb2993b3ee41a15dccf40d6df85f6da827861f02ce2d.png)
8-PSK¶
psk8 = sdr.PSK(8)
analyze_psk(psk8, 12)
![../../_images/f788ac494eff7a7543e918c67483398dfd7d5047dd6a513cf394e3308311fdc5.png](../../_images/f788ac494eff7a7543e918c67483398dfd7d5047dd6a513cf394e3308311fdc5.png)
![../../_images/86b74d303713a7a7badbb566d47ba5e2253a73e919578cfe80de376b1c333f6b.png](../../_images/86b74d303713a7a7badbb566d47ba5e2253a73e919578cfe80de376b1c333f6b.png)
16-PSK¶
psk16 = sdr.PSK(16)
analyze_psk(psk16, 18)
![../../_images/d8533739f6a399d1afb6477387c39955cf13dcac9600a4cb584cb16d5cb91eac.png](../../_images/d8533739f6a399d1afb6477387c39955cf13dcac9600a4cb584cb16d5cb91eac.png)
![../../_images/ac5f64f2229ae1cad7f2e3e2abdf7f61c9d5cc0f66da73fc54201e9ed08262bc.png](../../_images/ac5f64f2229ae1cad7f2e3e2abdf7f61c9d5cc0f66da73fc54201e9ed08262bc.png)
Error rate curves¶
def error_rates(psk, ebn0):
esn0 = sdr.ebn0_to_esn0(ebn0, psk.bps)
snr = sdr.esn0_to_snr(esn0)
ber = sdr.ErrorRate()
ser = sdr.ErrorRate()
for i in range(snr.size):
s = np.random.randint(0, psk.order, int(1e6))
x = psk.modulate(s)
x_hat = sdr.awgn(x, snr[i])
s_hat = psk.demodulate(x_hat)
ber.add(ebn0[i], sdr.unpack(s, psk.bps), sdr.unpack(s_hat, psk.bps))
ser.add(esn0[i], s, s_hat)
return ber, ser
ebn0 = np.linspace(-2, 10, 20)
bpsk_ber, bpsk_ser = error_rates(bpsk, ebn0)
qpsk_ber, qpsk_ser = error_rates(qpsk, ebn0)
psk8_ber, psk8_ser = error_rates(psk8, ebn0)
psk16_ber, psk16_ser = error_rates(psk16, ebn0)
Bit error rate curves¶
plt.figure(figsize=(10, 5))
ebn0 = np.linspace(-2, 10, 200)
sdr.plot.ber(ebn0, bpsk.ber(ebn0), label="BPSK theoretical")
sdr.plot.ber(ebn0, qpsk.ber(ebn0), label="QPSK theoretical")
sdr.plot.ber(ebn0, psk8.ber(ebn0), label="8-PSK theoretical")
sdr.plot.ber(ebn0, psk16.ber(ebn0), label="16-PSK theoretical")
plt.gca().set_prop_cycle(None)
sdr.plot.ber(*bpsk_ber.error_rates(), linestyle="none", marker=".", label="BPSK simulated")
sdr.plot.ber(*qpsk_ber.error_rates(), linestyle="none", marker=".", label="QPSK simulated")
sdr.plot.ber(*psk8_ber.error_rates(), linestyle="none", marker=".", label="8-PSK simulated")
sdr.plot.ber(*psk16_ber.error_rates(), linestyle="none", marker=".", label="16-PSK simulated")
plt.ylim(1e-6, 1e0)
plt.title("Bit error rate curves for PSK modulation in AWGN")
plt.tight_layout()
plt.show()
![../../_images/71cc0bb4e7111d9bc13d8b5d45d44a7cd6d3ed7d89a9a8114afbf080b0a831fb.png](../../_images/71cc0bb4e7111d9bc13d8b5d45d44a7cd6d3ed7d89a9a8114afbf080b0a831fb.png)
Symbol error rate curves¶
plt.figure(figsize=(10, 5))
esn0 = np.linspace(-4, 16, 200)
sdr.plot.ser(esn0, bpsk.ser(esn0), label="BPSK theoretical")
sdr.plot.ser(esn0, qpsk.ser(esn0), label="QPSK theoretical")
sdr.plot.ser(esn0, psk8.ser(esn0), label="8-PSK theoretical")
sdr.plot.ser(esn0, psk16.ser(esn0), label="16-PSK theoretical")
plt.gca().set_prop_cycle(None)
sdr.plot.ser(*bpsk_ser.error_rates(), linestyle="none", marker=".", label="BPSK simulated")
sdr.plot.ser(*qpsk_ser.error_rates(), linestyle="none", marker=".", label="QPSK simulated")
sdr.plot.ser(*psk8_ser.error_rates(), linestyle="none", marker=".", label="8-PSK simulated")
sdr.plot.ser(*psk16_ser.error_rates(), linestyle="none", marker=".", label="16-PSK simulated")
plt.ylim(1e-6, 1e0)
plt.title("Symbol error rate curves for PSK modulation in AWGN")
plt.tight_layout()
plt.show()
![../../_images/3aefa82071764fc0b28c8d4206073a97f4077a04bb4a6137e11fc251e78c7501.png](../../_images/3aefa82071764fc0b28c8d4206073a97f4077a04bb4a6137e11fc251e78c7501.png)
Symbol mapping¶
The mapping of decimal symbols to complex symbols is important.
Ideally, adjacent symbol errors only result in 1 bit error.
This is generally accomplished using a Gray code (the default in :class:sdr.PSK
).
psk8_bin = sdr.PSK(8, symbol_labels="bin")
psk8_gray = sdr.PSK(8, symbol_labels="gray")
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
sdr.plot.symbol_map(psk8_bin.symbol_map, limits=(-2, 2))
plt.title(f"Symbol map for binary coded 8-PSK")
plt.subplot(1, 2, 2)
sdr.plot.symbol_map(psk8_gray.symbol_map, limits=(-2, 2))
plt.title(f"Symbol map for gray coded 8-PSK")
plt.tight_layout()
plt.show()
![../../_images/772721577c2900d4abbe3ca510758d8997ac16c059e0c178a41f661bc2a64968.png](../../_images/772721577c2900d4abbe3ca510758d8997ac16c059e0c178a41f661bc2a64968.png)
Since adjacent symbol errors can lead to multiple bit errors, binary-coded 8-PSK has worse bit error performance.
plt.figure(figsize=(10, 5))
ebn0 = np.linspace(-2, 10, 200)
sdr.plot.ber(ebn0, sdr.PSK(8, symbol_labels="bin").ber(ebn0), label="8-PSK w/ binary code")
sdr.plot.ber(ebn0, sdr.PSK(8, symbol_labels="gray").ber(ebn0), label="8-PSK w/ gray code")
plt.title("Bit error rate curves for 8-PSK modulation in AWGN")
plt.tight_layout()
plt.show()
![../../_images/ad609c142343c13cc06d28baf02d21688bb47faf8321a32a71f47d4fdd3f7123.png](../../_images/ad609c142343c13cc06d28baf02d21688bb47faf8321a32a71f47d4fdd3f7123.png)
Last update:
Aug 13, 2023