Table of Contents
Introduction
Taking an equation in a book and implementing it in hardware or software is hard. In this blog post I’ll show you how to interpret the equation for a PSK and QAM modulator and translate it into Python code.
Be sure to check out PySDR for another perspective on modulation!
More blog posts on DSP:
Modulation
Single carrier modulators typically (but not always) involve the following operations:
- Generating a data symbol or baud
- Pulse shaping
- Upconverting to pass-band
The next section describes common types of modulation schemes, or constellations, used for creating data symbols.
BPSK, QPSK, PSK and QAM
Binary Phase Shift Keying (BPSK) is one of the most fundamental modulation schemes for wireless communication. BPSK is characterized by two symbols, +1 and -1.
Where BPSK has two symbols, Quadrature Phase Shift Keying (QPSK) has four symbols. The symbols for QPSK are typically represented as
(1)
Phase Shift Keying (PSK) is a modulation whose symbols are only along the unit circle. An example is 8-PSK, represented by
(2)
BPSK can be considered 2-PSK and QPSK can be considered 4-PSK.
Quadrature Amplitude Modulation (QAM) is typically represents square or near-square modulations. An example is 16-QAM, whose real symbols are
(3)
and whose imaginary symbols are
(4)
QPSK can be interpreted as 4-QAM.
The modulations of BPSK, QPSK, PSK and QAM are all tightly related and only differ by the number of constellation points and their location in the complex plane.
Pulse Shaping
A pulse shape is applied to data symbols in order to be good spectral neighbor, meaning the power in the sidelobes of a transmission needs to be minimized such that other transmitters can exist in the adjacent spectrum.
You may have noticed that FM radio stations are not directly next to one another on the dial, this is because guard bands are needed to minimize the adjacent channel interference.
An example of a pulse shape is the square-root raised cosine filter.
Upconversion
Upconversion, or frequency-mixing, is the process of translating a baseband signal up to a passband frequency. Upconversion is accomplished by multiplying the pulse shaped symbols by a complex exponential.
Textbook Equation
The following analysis uses QAM as the example signal. However, since BPSK, QPSK, PSK and QAM are all closely related the analysis will also apply to these modulations.
Proakis uses the following equation for QAM [proakis2001, p.174]:
(5)
Let’s break down (5):
- is the result of modulating symbol m at time t
- the real symbols are
- the imaginary symbols are
- the pulse shape is
- the upconversion is
- the real component is taken through RE{ }
The next section will simplify the equation so it is easier to understand and translate into software.
Simplifying the Textbook Equation
Where (5) performs the upconversion to real pass-band, we will use a simplified representation by staying at complex baseband. This is accomplished by removing the upconversion step through setting and removing the real operator RE{ },
(9)
(11)
Analyzing the Modulation Equation
Let’s define to make analysis of (12) easier to understand.
A simple (but impractical) pulse shape filter is the rectangular pulse shape, sometimes referred to as the boxcar window. The rectangular pulse shape uses all 1’s for N samples. This example rectangular pulse shape is chose to be of length . The pulse shaping filter can then be written as
(13)
Example samples of the modulated signal (12) can then be written as
(14)
BPSK uses symbols of +1 and -1 and therefore an example of (14) could be written as
(15)
PSK and QAM Modulator Python Code
The modulator code in Python has to generate the sequence (14). There are many ways to do this in Python, including some that are more efficient, but this an illustrative example.
Import NumPy:
import numpy as np
Create the QPSK symbol map:
QPSKMapper = (np.sqrt(2) / 2) * np.array([1+1j, 1–1j, –1+1j, –1–1j])
Randomly generate some QPSK symbols:
ns = 16
mapIndex = np.random.randint(0, len(QPSKMapper), ns)
QPSKSymbols = QPSKMapper[mapIndex]
Define the samples per symbol, which is also the pulse shaping filter length:
SPS = 4
Upsample the symbols:
QPSKSymbolsUpsampled = np.zeros(ns*SPS,dtype=complex)
QPSKSymbolsUpsampled[::SPS] = QPSKSymbols
Define the pulse shaping filter:
pulseShape = np.ones(SPS)
Apply the pulse shaping filter:
QPSKSignal = np.convolve(QPSKSymbolsUpsampled,pulseShape)
The signal is now QPSK modulated! Other modulations such as PSK and QAM can be used by changing the QPSKMapper variable to include the desired constellation points.
Conclusion
This blog post started with a continuous-time equation for QAM and then simplified it into a discrete-time complex baseband signal. The equation was analyzed and simplified, and then used to create Python code for modulating QPSK, PSK and QAM signals.
More blog posts on DSP:
2 Responses
Hi Matt.
I saw your excellent website. Congratulations! One question: what is your complete process of rendering the image from production to web display? I am asking this question because your png images look crisper than most other png images on the web (they fade in quality for high resolution display). Thanks.
Thank you!
I generate them using Matplotlib within Python and save them as .png files using the savefig() call. I upload the raw .png files which are then compressed automatically using the EWWW image optimizer plugin.
Hope this helps!