- class sdr.MSK(sdr.OQPSK)
Implements minimum-shift keying (MSK) modulation and demodulation.
Notes¶
MSK is a linear phase modulation scheme similar to OQPSK. One key distinction is that the pulse shape is a half sine wave. This results in a constant envelope signal, which results in a lower peak-to-average power ratio (PAPR).
MSK can also be consider as continuous-phase frequency-shift keying (CPFSK) with the frequency separation equaling half the bit period.
Note
The nomenclature for variable names in linear modulators is as follows: \(s[k]\) are decimal symbols, \(\hat{s}[k]\) are decimal symbol decisions, \(a[k]\) are complex symbols, \(\tilde{a}[k]\) are received complex symbols, \(\hat{a}[k]\) are complex symbol decisions, \(x[n]\) are pulse-shaped complex samples, and \(\tilde{x}[n]\) are received pulse-shaped complex samples. \(k\) indicates a symbol index and \(n\) indicates a sample index.
Examples¶
Create a MSK modem.
In [1]: msk = sdr.MSK(); msk Out[1]: sdr.MSK(phase_offset=45, symbol_labels='gray') In [2]: plt.figure(); \ ...: sdr.plot.symbol_map(msk); ...:
Generate a random bit stream, convert to 2-bit symbols, and map to complex symbols.
In [3]: bits = np.random.randint(0, 2, 1000); bits[0:8] Out[3]: array([0, 1, 1, 0, 1, 1, 1, 1]) In [4]: symbols = sdr.pack(bits, msk.bps); symbols[0:4] Out[4]: array([1, 2, 3, 3], dtype=uint8) In [5]: complex_symbols = msk.map_symbols(symbols); complex_symbols[0:4] Out[5]: array([-0.70710678+0.j , -0.70710678+0.70710678j, 0.70710678+0.70710678j, 0.70710678-0.70710678j]) In [6]: plt.figure(); \ ...: sdr.plot.constellation(complex_symbols, linestyle="-"); ...:
Modulate and pulse shape the symbols to a complex baseband signal.
In [7]: tx_samples = msk.modulate(symbols) In [8]: plt.figure(); \ ...: sdr.plot.time_domain(tx_samples[0:50*msk.sps]); ...:
MSK, like OQPSK, has I and Q channels that are offset by half a symbol period.
In [9]: plt.figure(figsize=(8, 6)); \ ...: sdr.plot.eye(tx_samples[5*msk.sps : -5*msk.sps], msk.sps); \ ...: plt.suptitle("Noiseless transmitted signal"); ...:
The phase trajectory of MSK is linear and continuous. Although, it should be noted that the phase is not differentiable at the symbol boundaries. This leads to lower spectral efficiency than, for instance, GMSK.
In [10]: plt.figure(); \ ....: sdr.plot.phase_tree(tx_samples[msk.sps:], msk.sps); ....:
Add AWGN noise such that \(E_b/N_0 = 30\) dB.
In [11]: ebn0 = 30; \ ....: snr = sdr.ebn0_to_snr(ebn0, bps=msk.bps, sps=msk.sps); \ ....: rx_samples = sdr.awgn(tx_samples, snr=snr) ....: In [12]: plt.figure(); \ ....: sdr.plot.time_domain(rx_samples[0:50*msk.sps]); ....:
Manually apply a matched filter. Examine the eye diagram of the matched filtered received signal.
In [13]: mf = sdr.FIR(msk.pulse_shape); \ ....: mf_samples = mf(rx_samples) ....: In [14]: plt.figure(figsize=(8, 6)); \ ....: sdr.plot.eye(mf_samples[10*msk.sps : -10*msk.sps], msk.sps); \ ....: plt.suptitle("Noisy received and matched filtered signal"); ....:
Matched filter and demodulate. Note, the first symbol has \(Q = 0\) and the last symbol has \(I = 0\).
In [15]: rx_symbols, rx_complex_symbols, _ = msk.demodulate(rx_samples) # The symbol decisions are error-free In [16]: np.array_equal(symbols, rx_symbols) Out[16]: True In [17]: plt.figure(); \ ....: sdr.plot.constellation(rx_complex_symbols); ....:
See the Phase-shift keying example.
Constructors¶
-
MSK(phase_offset: float =
45
, ...) Creates a new MSK object.
Methods¶
-
ber(ebn0: ArrayLike, diff_encoded: bool =
False
) NDArray[float64] Computes the bit error rate (BER) at the provided \(E_b/N_0\) values.
-
ser(esn0: ArrayLike, diff_encoded: bool =
False
) NDArray[float64] Computes the symbol error rate (SER) at the provided \(E_s/N_0\) values.
- map_symbols(s: ArrayLike) NDArray[complex128]
Converts the decimal symbols into complex symbols.
- decide_symbols(a_tilde) tuple[NDArray[int_], NDArray[complex128]]
Converts the received complex symbols into MLE symbol decisions.
- modulate(s: ArrayLike) NDArray[complex128]
Modulates the decimal symbols into pulse-shaped complex samples.
- demodulate(...) tuple[NDArray[int_], NDArray[complex128], NDArray[complex128]]
Demodulates the pulse-shaped complex samples.
Properties¶
- property phase_offset : float
The phase offset \(\phi\) in degrees.
- property symbol_map : NDArray[np.complex128]
The symbol map \(\{0, \dots, M-1\} \mapsto \mathbb{C}\).
- property pulse_shape : NDArray[np.float64]
The pulse shape \(h[n]\) of the modulated signal.
- property tx_filter : Interpolator
The transmit interpolating pulse shaping filter.
-
MSK(phase_offset: float =