├── BPSK.py
├── LICENSE
├── QAM.py
├── QPSK.py
├── README.md
└── imgs
├── bpsk_constulation.PNG
├── bpsk_fft.PNG
├── bpsk_model.PNG
├── bpsk_timedomain.PNG
├── psk_constulation.PNG
├── psk_fft.PNG
├── psk_model.PNG
├── psk_timedomain.PNG
├── qam_constulation.PNG
├── qam_fft.PNG
├── qam_model.PNG
└── qam_timedomain.PNG
/BPSK.py:
--------------------------------------------------------------------------------
1 | # Import functions and libraries
2 | import numpy as np
3 | import matplotlib.pyplot as plt
4 | import matplotlib.gridspec as gridspec
5 | import scipy as scy
6 | import threading,time
7 | import multiprocessing
8 | import sys
9 | import bitarray
10 | import cmath
11 |
12 | from scipy.fftpack import fft
13 | from numpy import pi
14 | from numpy import sqrt
15 | from numpy import sin
16 | from numpy import cos
17 | from numpy import zeros
18 | from numpy import r_
19 | from scipy.io.wavfile import read as wavread
20 |
21 | # Used for symbol creation. Returns a decimal number from a 1 bit input
22 | def GetBpskSymbol(bit1:bool):
23 | if(~bit1):
24 | return 0
25 | elif(bit1):
26 | return 1
27 | else:
28 | return -1
29 |
30 | # Maps a given symbol to a complex signal. Optionally, noise and phase offset can be added.
31 | def BpskSymbolMapper(symbols:int,amplitude,noise1=0, noise2=0, phaseOffset = 0):
32 | if(symbols == 0):
33 | return amplitude*(cos(np.deg2rad(0) + phaseOffset)) + (noise1 + 1j*noise2)
34 | elif(symbols == 1):
35 | return amplitude*(cos(np.deg2rad(180) + phaseOffset)) + (noise1 + 1j*noise2)
36 | else:
37 | return complex(0)
38 |
39 | #-------------------------------------#
40 | #---------- Configuration ------------#
41 | #-------------------------------------#
42 | fs = 44100 # sampling rate
43 | baud = 900 # symbol rate
44 | Nbits = 4000 # number of bits
45 | f0 = 1800 # carrier Frequency
46 | Ns = int(fs/baud) # number of Samples per Symbol
47 | N = Nbits * Ns # Total Number of Samples
48 | t = r_[0.0:N]/fs # time points
49 | f = r_[0:N/2.0]/N*fs # Frequency Points
50 |
51 | # Limit for representation of time domain signals for better visibility.
52 | symbolsToShow = 20
53 | timeDomainVisibleLimit = np.minimum(Nbits/baud,symbolsToShow/baud)
54 |
55 | # Limit for representation of frequency domain signals for better visibility.
56 | sideLobesToShow = 9
57 | sideLobeWidthSpectrum = baud
58 | lowerLimit = np.maximum(0,f0-sideLobeWidthSpectrum*(1 + sideLobesToShow))
59 | upperLimit = f0 + sideLobeWidthSpectrum*(1 + sideLobesToShow)
60 |
61 | carrier1 = cos(2*pi*f0*t)
62 |
63 | #----------------------------#
64 | #---------- BPSK ------------#
65 | #----------------------------#
66 |
67 | # Modulator Input
68 | inputBits = np.random.randn(Nbits,1) > 0
69 |
70 | #Digital-to-Analog Conversion
71 | inputSignal = (np.tile(inputBits*2-1,(1,Ns))).ravel()
72 | dataSymbols = np.array([[GetBpskSymbol(inputBits[x])] for x in range(0,inputBits.size)])
73 |
74 | #Multiplicator / mixxer
75 | BPSK_signal = inputSignal*( carrier1)# + intermodulation1+ intermodulation2)
76 |
77 | #---------- Preperation BPSK Constellation Diagram ------------#
78 |
79 | amplitude = 1
80 |
81 | #Generate noise. Two sources for uncorelated noise.
82 | noiseStandardDeviation = 0.12
83 | noise1 = np.random.normal(0,noiseStandardDeviation,dataSymbols.size)
84 | noise2 = np.random.normal(0,noiseStandardDeviation,dataSymbols.size)
85 |
86 | #Transmitted and received symbols. Rx symbols are generated under the presence of noise
87 | Tx_symbols = np.array([[BpskSymbolMapper(dataSymbols[x],
88 | amplitude,
89 | phaseOffset = np.deg2rad(0)
90 | )] for x in range(0,dataSymbols.size)])
91 | Rx_symbols = np.array([[BpskSymbolMapper(dataSymbols[x],
92 | amplitude,
93 | noise1 = noise1[x],
94 | noise2 = noise2[x],
95 | phaseOffset = np.deg2rad(0)
96 | )] for x in range(0,dataSymbols.size)])
97 |
98 | #---------- Plot of BPSK Constellation Diagram ------------#
99 | fig, axis= plt.subplots(2,2,sharey='row')
100 | fig.suptitle('Constellation Diagram BPSK', fontsize=12)
101 |
102 | axis[0,0].plot(Tx_symbols.real, Tx_symbols.imag,'.', color='C1')
103 | axis[0,0].set_title('Tx (Source Code/ Block Diagram: "Tx_symbols")')
104 | axis[0,0].set_xlabel('Inphase [V]')
105 | axis[0,0].set_ylabel('Quadrature [V]')
106 | axis[0,0].set_xlim(-2,2)
107 | axis[0,0].set_xticks([-1.5,0,1.5])
108 | axis[0,0].set_yticks([-1.5,1.5])
109 | axis[0,0].set_ylim(-2,2)
110 | axis[0,0].grid(True)
111 |
112 | axis[0,1].plot(Tx_symbols.real, Tx_symbols.imag,'-',Tx_symbols.real, Tx_symbols.imag,'.')
113 | axis[0,1].set_title('Tx with Trajectory (Source Code/ Block Diagram: "Tx_symbols")')
114 | axis[0,1].set_xlabel('Inphase [V]')
115 | axis[0,1].set_ylabel('Quadrature [V]')
116 | axis[0,1].set_xlim(-2,2)
117 | axis[0,1].set_xticks([-1.5,0,1.5])
118 | axis[0,1].set_yticks([-1.5,1.5])
119 | axis[0,1].set_ylim(-2,2)
120 | axis[0,1].grid(True)
121 |
122 | axis[1,0].plot(Rx_symbols.real, Rx_symbols.imag,'.', color='C1')
123 | axis[1,0].set_title('Rx (Source Code/ Block Diagram: "Rx_symbols")')
124 | axis[1,0].set_xlabel('Inphase [V]')
125 | axis[1,0].set_ylabel('Quadrature [V]')
126 | axis[1,0].set_xlim(-2,2)
127 | axis[1,0].set_xticks([-1.5,0,1.5])
128 | axis[1,0].set_yticks([-1.5,1.5])
129 | axis[1,0].set_ylim(-2,2)
130 | axis[1,0].grid(True)
131 |
132 | axis[1,1].plot(Rx_symbols.real, Rx_symbols.imag,'-',Rx_symbols.real, Rx_symbols.imag,'.')
133 | axis[1,1].set_title('Rx with Trajectory (Source Code/ Block Diagram: "Rx_symbols")')
134 | axis[1,1].set_xlabel('Inphase [V]')
135 | axis[1,1].set_ylabel('Quadrature [V]')
136 | axis[1,1].set_xlim(-2,2)
137 | axis[1,1].set_xticks([-1.5,0,1.5])
138 | axis[1,1].set_yticks([-1.5,1.5])
139 | axis[1,1].set_ylim(-2,2)
140 | axis[1,1].grid(True)
141 |
142 | #---------- Plot of BPSK ------------#
143 | fig, axis = plt.subplots(3, 1)
144 | fig.suptitle('BPSK Modulation', fontsize=12)
145 |
146 | axis[0].plot(t, inputSignal, color='C1')
147 | axis[0].set_title('Input Signal (Source Code/ Block Diagram: "inputSignal")')
148 | axis[0].set_xlabel('Time [s]')
149 | axis[0].set_xlim(0,timeDomainVisibleLimit)
150 | axis[0].set_ylabel('Amplitude [V]')
151 | axis[0].grid(linestyle='dotted')
152 |
153 | axis[1].plot(t, carrier1, color='C2')
154 | axis[1].set_title('Carrier Signal (Source Code/ Block Diagram: "carrier1")')
155 | axis[1].set_xlabel('Time [s]')
156 | axis[1].set_xlim(0,timeDomainVisibleLimit)
157 | axis[1].set_ylabel('Amplitude [V]')
158 | axis[1].grid(linestyle='dotted')
159 |
160 | axis[2].plot(t,BPSK_signal, color='C3')
161 | axis[2].set_title('BPSK Modulated Signal (Source Code/ Block Diagram: "BPSK_signal")')
162 | axis[2].set_xlabel('Time [s]')
163 | axis[2].set_xlim(0,timeDomainVisibleLimit)
164 | axis[2].set_ylabel('Amplitude [V]')
165 | axis[2].grid(linestyle='dotted')
166 |
167 | plt.subplots_adjust(hspace=0.5)
168 |
169 | #---------- Plot of Modulated Signal and Spectrum ------------#
170 | fig = plt.figure(constrained_layout=True)
171 | gs = gridspec.GridSpec(4, 1, figure=fig)
172 | fig.suptitle('BPSK Modulation', fontsize=12)
173 | ax = fig.add_subplot(gs[0, :])
174 |
175 | ax1 = fig.add_subplot(gs[0])
176 | ax1.set_title('Magnitude Spectrum (Source Code/ Block Diagram: "BPSK_signal")');
177 | ax1.magnitude_spectrum(BPSK_signal, Fs=fs, color='C1')
178 | ax1.set_xlim(lowerLimit,upperLimit)
179 | ax1.grid(linestyle='dotted')
180 |
181 | ax2 = fig.add_subplot(gs[1])
182 | ax2.set_title('Log. Magnitude Spectrum (Source Code/ Block Diagram: "BPSK_signal")')
183 | ax2.magnitude_spectrum(BPSK_signal, Fs=fs, scale='dB', color='C1')
184 | ax2.set_xlim(lowerLimit,upperLimit)
185 | ax2.grid(linestyle='dotted')
186 |
187 | ax3 = fig.add_subplot(gs[2])
188 | ax3.set_title('Power Spectrum Density (PSD) (Source Code/ Block Diagram: "BPSK_signal")')
189 | ax3.psd(BPSK_signal,NFFT=len(t),Fs=fs)
190 | ax3.set_xlim(lowerLimit,upperLimit)
191 | ax3.grid(linestyle='dotted')
192 |
193 | ax4 = fig.add_subplot(gs[3])
194 | ax4.set_title('Carrier")');
195 | ax4.magnitude_spectrum(carrier1, Fs=fs, color='C1')
196 | ax4.set_xlim(lowerLimit,upperLimit)
197 | ax4.grid(linestyle='dotted')
198 |
199 | plt.subplots_adjust(hspace=0.5)
200 | plt.show()
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 SaKi1309
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/QAM.py:
--------------------------------------------------------------------------------
1 | # Import functions and libraries
2 | import numpy as np
3 | import matplotlib.pyplot as plt
4 | import matplotlib.gridspec as gridspec
5 | import scipy as scy
6 | import threading,time
7 | import multiprocessing
8 | import sys
9 | import bitarray
10 | import cmath
11 |
12 | from scipy.fftpack import fft
13 | from numpy import pi
14 | from numpy import sqrt
15 | from numpy import sin
16 | from numpy import cos
17 | from numpy import exp
18 | from numpy import zeros
19 | from numpy import r_
20 | from scipy.io.wavfile import read as wavread
21 |
22 | # DAC - Maps an two bit input to a certain amplitude provided
23 | def TwoBitToAmplitudeMapper(bit1:bool, bit2:bool, amplitude1, amplitude2):
24 | if(~bit1 & ~bit2):
25 | return np.array(-amplitude1)
26 | elif(~bit1 & bit2):
27 | return np.array(-amplitude2)
28 | elif(bit1 & ~bit2):
29 | return np.array(amplitude1)
30 | elif(bit1 & bit2):
31 | return np.array(amplitude2)
32 | else:
33 | return 0
34 |
35 | # Used for symbol creation. Returns a decimal number from a 4 bit input
36 | def GetQam16Symbol(bit1:bool, bit2:bool, bit3:bool, bit4:bool):
37 | if(~bit1 & ~bit2 & ~bit3 & ~bit4):
38 | return 0
39 | elif(~bit1 & ~bit2 & ~bit3 & bit4):
40 | return 1
41 | elif(~bit1 & ~bit2 & bit3 & ~bit4):
42 | return 2
43 | elif(~bit1 & ~bit2 & bit3 & bit4):
44 | return 3
45 | elif(~bit1 & bit2 & ~bit3 & ~bit4):
46 | return 4
47 | elif(~bit1 & bit2 & ~bit3 & bit4):
48 | return 5
49 | elif(~bit1 & bit2 & bit3 & ~bit4):
50 | return 6
51 | elif(~bit1 & bit2 & bit3 & bit4):
52 | return 7
53 | elif(bit1 & ~bit2 & ~bit3 & ~bit4):
54 | return 8
55 | elif(bit1 & ~bit2 & ~bit3 & bit4):
56 | return 9
57 | elif(bit1 & ~bit2 & bit3 & ~bit4):
58 | return 10
59 | elif(bit1 & ~bit2 & bit3 & bit4):
60 | return 11
61 | elif(bit1 & bit2 & ~bit3 & ~bit4):
62 | return 12
63 | elif(bit1 & bit2 & ~bit3 & bit4):
64 | return 13
65 | elif(bit1 & bit2 & bit3 & ~bit4):
66 | return 14
67 | elif(bit1 & bit2 & bit3 & bit4):
68 | return 15
69 | else:
70 | return -1
71 |
72 | # Maps a given symbol to a complex signal. Optionally, noise and phase offset can be added.
73 | def Qam16SymbolMapper(symbols:int, amplitude_I1, amplitude_Q1, amplitude_I2, amplitude_Q2, noise1=0, noise2=0, noise3=0, noise4=0, phaseOffset1 = 0, phaseOffset2 = 0):
74 | if(symbols == 0):#0000
75 | return sqrt(amplitude_I1**2 + amplitude_Q1**2)*(cos(np.deg2rad(225) + phaseOffset1)+ 1j *sin(np.deg2rad(225) + phaseOffset2)) + (noise1 + 1j*noise2)
76 | elif(symbols == 1):#0001
77 | return sqrt(amplitude_I2**2 + amplitude_Q1**2 )*(cos(np.deg2rad(198) + phaseOffset1)+ 1j *sin(np.deg2rad(198) + phaseOffset2)) + (noise3 + 1j*noise2)
78 | elif(symbols == 2):#0010
79 | return sqrt(amplitude_I1**2 + amplitude_Q2**2)*(cos(np.deg2rad(251) + phaseOffset1)+ 1j *sin(np.deg2rad(251) + phaseOffset2)) + (noise1 + 1j*noise4)
80 | elif(symbols == 3):#0011
81 | return sqrt(amplitude_I2**2 + amplitude_Q2**2)*(cos(np.deg2rad(225) + phaseOffset1)+ 1j *sin(np.deg2rad(225) + phaseOffset2)) + (noise3 + 1j*noise4)
82 | elif(symbols == 4):#0100
83 | return sqrt(amplitude_I1**2 + amplitude_Q1**2)*(cos(np.deg2rad(315) + phaseOffset1)+ 1j *sin(np.deg2rad(315) + phaseOffset2)) + (noise1 + 1j*noise2)
84 | elif(symbols == 5):#0101
85 | return sqrt(amplitude_I1**2 + amplitude_Q2**2)*(cos(np.deg2rad(288) + phaseOffset1)+ 1j *sin(np.deg2rad(288) + phaseOffset2)) + (noise1 + 1j*noise4)
86 | elif(symbols == 6):#0110
87 | return sqrt(amplitude_I2**2 + amplitude_Q1**2)*(cos(np.deg2rad(342) + phaseOffset1)+ 1j *sin(np.deg2rad(342) + phaseOffset2)) + (noise2 + 1j*noise3)
88 | elif(symbols == 7):#0111
89 | return sqrt(amplitude_I2**2 + amplitude_Q2**2)*(cos(np.deg2rad(315) + phaseOffset1)+ 1j *sin(np.deg2rad(315) + phaseOffset2)) + (noise3 + 1j*noise4)
90 | elif(symbols == 8):#1000
91 | return sqrt(amplitude_I1**2 + amplitude_Q1**2)*(cos(np.deg2rad(135) + phaseOffset1)+ 1j *sin(np.deg2rad(135) + phaseOffset2)) + (noise1 + 1j*noise2)
92 | elif(symbols == 9):#1001
93 | return sqrt(amplitude_I1**2 + amplitude_Q2**2)*(cos(np.deg2rad(108) + phaseOffset1)+ 1j *sin(np.deg2rad(108) + phaseOffset2)) + (noise1 + 1j*noise4)
94 | elif(symbols == 10):#1010
95 | return sqrt(amplitude_I2**2 + amplitude_Q1**2)*(cos(np.deg2rad(162) + phaseOffset1)+ 1j *sin(np.deg2rad(162) + phaseOffset2)) + (noise3 + 1j*noise2)
96 | elif(symbols == 11):#1011
97 | return sqrt(amplitude_I2**2 + amplitude_Q2**2)*(cos(np.deg2rad(135) + phaseOffset1)+ 1j *sin(np.deg2rad(135) + phaseOffset2)) + (noise3 + 1j*noise4)
98 | elif(symbols == 12):#1100
99 | return sqrt(amplitude_I1**2 + amplitude_Q1**2)*(cos(np.deg2rad(45) + phaseOffset1)+ 1j *sin(np.deg2rad(45) + phaseOffset2)) + (noise1 + 1j*noise2)
100 | elif(symbols == 13):#1101
101 | return sqrt(amplitude_I2**2 + amplitude_Q1**2)*(cos(np.deg2rad(18) + phaseOffset1)+ 1j *sin(np.deg2rad(15) + phaseOffset2)) + (noise2 + 1j*noise3)
102 | elif(symbols == 14):#1110
103 | return sqrt(amplitude_I1**2 + amplitude_Q2**2)*(cos(np.deg2rad(71) + phaseOffset1)+ 1j *sin(np.deg2rad(75) + phaseOffset2)) + (noise1 + 1j*noise4)
104 | elif(symbols == 15):#1111
105 | return sqrt(amplitude_I2**2 + amplitude_Q2**2)*(cos(np.deg2rad(45) + phaseOffset1)+ 1j *sin(np.deg2rad(45) + phaseOffset2)) + (noise3 + 1j*noise4)
106 | else:
107 | return complex(0)
108 |
109 | #-------------------------------------#
110 | #---------- Configuration ------------#
111 | #-------------------------------------#
112 | fs = 44100 # sampling rate
113 | baud = 900 # symbol rate
114 | Nbits = 4000 # number of bits
115 | f0 = 1800 # carrier Frequency
116 | Ns = int(fs/baud) # number of Samples per Symbol
117 | N = Nbits * Ns # Total Number of Samples
118 | t = r_[0.0:N]/fs # time points
119 | f = r_[0:N/2.0]/N*fs # Frequency Points
120 |
121 | # Limit for representation of time domain signals for better visibility.
122 | symbolsToShow = 20
123 | timeDomainVisibleLimit = np.minimum(Nbits/baud,symbolsToShow/baud)
124 |
125 | #----------------------------------------#
126 | #---------- QAM16 Modulation ------------#
127 | #----------------------------------------#
128 |
129 | #Input of the modulator
130 | inputBits = np.random.randn(Nbits,1) > 0
131 | inputSignal = (np.tile(inputBits,(1,Ns))).ravel()
132 |
133 | #Only calculate when dividable by 4
134 | if(inputBits.size%4 == 0):
135 |
136 | # amplitudes of I_signal and Q_signal (absolut values)
137 | amplitude1_I_signal = 0.25
138 | amplitude2_I_signal = 0.75
139 | amplitude1_Q_signal = 0.25
140 | amplitude2_Q_signal = 0.75
141 |
142 | #carrier signals used for modulation. carrier2 has 90° phaseshift compared to carrier1 -> IQ modulation
143 | carrier1 = cos(2*pi*f0*t)
144 | carrier2 = cos(2*pi*f0*t+pi/2)
145 |
146 | #Serial-to-Parallel Converter (1/4 of data rate)
147 | I1_bits = inputBits[::4]
148 | I2_bits = inputBits[1::4]
149 | Q1_bits = inputBits[2::4]
150 | Q2_bits = inputBits[3::4]
151 |
152 | #Digital-to-Analog Conversion
153 | I_symbols = np.array([[TwoBitToAmplitudeMapper(I1_bits[x], I2_bits[x], amplitude1_I_signal, amplitude2_I_signal)] for x in range(0,I1_bits.size)])
154 | Q_symbols = np.array([[TwoBitToAmplitudeMapper(Q1_bits[x], Q2_bits[x], amplitude1_Q_signal, amplitude2_Q_signal)] for x in range(0,Q1_bits.size)])
155 | I_signal = (np.tile(I_symbols,(1,4*Ns))).ravel()
156 | Q_signal = (np.tile(Q_symbols,(1,4*Ns))).ravel()
157 |
158 | #Multiplicator / mixxer
159 | I_signal_modulated = I_signal * carrier1
160 | Q_signal_modulated = Q_signal * carrier2
161 |
162 | #Summation befor transmission
163 | QAM16_signal = I_signal_modulated + Q_signal_modulated
164 |
165 | #---------- Preperation Constellation Diagram ------------#
166 | dataSymbols = np.array([[GetQam16Symbol(I1_bits[x], I2_bits[x], Q1_bits[x], Q2_bits[x])] for x in range(0,I1_bits.size)])
167 |
168 | #Generate noise. Four sources for uncorelated noise for each amplitude.
169 | noiseStandardDeviation = 0.055
170 | noise1 = np.random.normal(0,noiseStandardDeviation,dataSymbols.size)
171 | noise2 = np.random.normal(0,noiseStandardDeviation,dataSymbols.size)
172 | noise3 = np.random.normal(0,noiseStandardDeviation,dataSymbols.size)
173 | noise4 = np.random.normal(0,noiseStandardDeviation,dataSymbols.size)
174 |
175 | #Transmitted and received symbols. Rx symbols are generated under the presence of noise
176 | Tx_symbols = np.array([[Qam16SymbolMapper(dataSymbols[x],
177 | amplitude1_I_signal,
178 | amplitude1_Q_signal,
179 | amplitude2_I_signal,
180 | amplitude2_Q_signal,
181 | phaseOffset1 = np.deg2rad(0),
182 | phaseOffset2 = np.deg2rad(0)
183 | )]for x in range(0,dataSymbols.size)])
184 | Rx_symbols = np.array([[Qam16SymbolMapper(dataSymbols[x],
185 | amplitude1_I_signal,
186 | amplitude1_Q_signal,
187 | amplitude2_I_signal,
188 | amplitude2_Q_signal,
189 | noise1 = noise1[x],
190 | noise2 = noise2[x],
191 | noise3 = noise3[x],
192 | noise4 = noise4[x]
193 | )] for x in range(0,dataSymbols.size)])
194 |
195 | #-------------------------------------------#
196 | #---------- Data Representation ------------#
197 | #-------------------------------------------#
198 |
199 | #---------- Plot of QAM 16 Constellation Diagram ------------#
200 | fig, axis = plt.subplots(2,2,sharey='row')
201 | fig.suptitle('Constellation Diagram QAM16', fontsize=12)
202 |
203 | axis[0,0].plot(Tx_symbols.real, Tx_symbols.imag,'.', color='C1')
204 | axis[0,0].set_title('Tx (Source Code/ Block Diagram: "Tx_symbols")')
205 | axis[0,0].set_xlabel('Inphase [V]')
206 | axis[0,0].set_ylabel('Quadrature [V]')
207 | axis[0,0].set_xlim(-1.5,1.5)
208 | axis[0,0].set_ylim(-1.5,1.5)
209 | axis[0,0].set_xticks([-1,-0.5,0,0.5,1])
210 | axis[0,0].set_yticks([-1,-0.5,0,0.5,1])
211 | axis[0,0].grid(True)
212 |
213 | axis[0,1].plot(Tx_symbols.real, Tx_symbols.imag,'-',Tx_symbols.real, Tx_symbols.imag,'.')
214 | axis[0,1].set_title('Tx with Trajectory (Source Code/ Block Diagram: "Tx_symbols")')
215 | axis[0,1].set_xlabel('Inphase [V]')
216 | axis[0,1].set_ylabel('Quadrature [V]')
217 | axis[0,1].set_xlim(-1.5,1.5)
218 | axis[0,1].set_ylim(-1.5,1.5)
219 | axis[0,1].set_xticks([-1,-0.5,0,0.5,1])
220 | axis[0,1].set_yticks([-1,-0.5,0,0.5,1])
221 | axis[0,1].grid(True)
222 |
223 | axis[1,0].plot(Rx_symbols.real, Rx_symbols.imag,'.', color='C1')
224 | axis[1,0].set_title('Rx (Source Code/ Block Diagram: "Rx_symbols")')
225 | axis[1,0].set_xlabel('Inphase [V]')
226 | axis[1,0].set_ylabel('Quadrature [V]')
227 | axis[1,0].set_xticks([-1,-0.5,0,0.5,1])
228 | axis[1,0].set_yticks([-1,-0.5,0,0.5,1])
229 | axis[1,0].set_xlim(-1.5,1.5)
230 | axis[1,0].set_ylim(-1.5,1.5)
231 | axis[1,0].grid(True)
232 |
233 | axis[1,1].plot(Rx_symbols.real, Rx_symbols.imag,'-',Rx_symbols.real, Rx_symbols.imag,'.')
234 | axis[1,1].set_title('Rx with Trajectory (Source Code/ Block Diagram: "Rx_symbols")')
235 | axis[1,1].set_xlabel('Inphase [V]')
236 | axis[1,1].set_ylabel('Quadrature [V]')
237 | axis[1,1].set_xlim(-1.5,1.5)
238 | axis[1,1].set_ylim(-1.5,1.5)
239 | axis[1,1].set_xticks([-1,-0.5,0,0.5,1])
240 | axis[1,1].set_yticks([-1,-0.5,0,0.5,1])
241 | axis[1,1].grid(True)
242 |
243 | #---------- Plot of Modulating Signals ------------#
244 | fig, axis = plt.subplots(6,1,sharex='col')
245 | fig.suptitle('QAM16 Modulation', fontsize=12)
246 |
247 | axis[0].plot(t, inputSignal, color='C1')
248 | axis[0].set_title('Digital Data Signal (Source Code/ Block Diagram: "inputBits")')
249 | axis[0].set_xlabel('Time [s]')
250 | axis[0].set_ylabel('Amplitude [V]')
251 | axis[0].set_xlim(0,timeDomainVisibleLimit)
252 | axis[0].grid(linestyle='dotted')
253 |
254 | axis[1].plot(t, I_signal, color='C2')
255 | axis[1].set_title('Digital I-Signal (Source Code/ Block Diagram: "I_signal")')
256 | axis[1].set_xlabel('Time [s]')
257 | axis[1].set_xlim(0,timeDomainVisibleLimit)
258 | axis[1].set_ylabel('Amplitude [V]')
259 | axis[1].grid(linestyle='dotted')
260 |
261 | axis[2].plot(t, I_signal_modulated, color='C2')
262 | axis[2].set_title('Modulated I-Signal (Source Code/ Block Diagram: "I_signal_modulated")')
263 | axis[2].set_xlabel('Time [s]')
264 | axis[2].set_xlim(0,timeDomainVisibleLimit)
265 | axis[2].set_ylabel('Amplitude [V]')
266 | axis[2].grid(linestyle='dotted')
267 |
268 | axis[3].plot(t, Q_signal, color='C3')
269 | axis[3].set_title('Digital Q-Signal (Source Code/ Block Diagram: "Q_signal")')
270 | axis[3].set_xlabel('Time [s]')
271 | axis[3].set_xlim(0,timeDomainVisibleLimit)
272 | axis[3].set_ylabel('Amplitude [V]')
273 | axis[3].grid(linestyle='dotted')
274 |
275 | axis[4].plot(t, Q_signal_modulated, color='C3')
276 | axis[4].set_title('Modulated Q-Signal (Source Code/ Block Diagram: "Q_signal_modulated")')
277 | axis[4].set_xlabel('Time [s]')
278 | axis[4].set_xlim(0,timeDomainVisibleLimit)
279 | axis[4].set_ylabel('Amplitude [V]')
280 | axis[4].grid(linestyle='dotted')
281 |
282 | axis[5].plot(t,QAM16_signal, color='C4')
283 | axis[5].set_title('QAM16 Signal Modulated (Source Code/ Block Diagram: "QAM16_signal")')
284 | axis[5].set_xlabel('Time [s]')
285 | axis[5].set_xlim(0,timeDomainVisibleLimit)
286 | axis[5].set_ylabel('Amplitude [V]')
287 | axis[5].grid(linestyle='dotted')
288 |
289 | plt.subplots_adjust(hspace=0.5)
290 |
291 | #---------- Plot of Modulated Signal and Spectrum ------------#
292 | fig = plt.figure(constrained_layout=True)
293 | gs = gridspec.GridSpec(3, 1, figure=fig)
294 | fig.suptitle('QAM16 Modulation', fontsize=12)
295 |
296 | ax1 = fig.add_subplot(gs[0])
297 | ax1.set_title('Magnitude Spectrum (Source Code/ Block Diagram: "QAM16_signal")');
298 | ax1.magnitude_spectrum(QAM16_signal, Fs=fs, color='C1')
299 | ax1.set_xlim(0,6000)
300 | ax1.grid(linestyle='dotted')
301 |
302 | ax2 = fig.add_subplot(gs[1])
303 | ax2.set_title('Log. Magnitude Spectrum (Source Code/ Block Diagram: "QAM16_signal")')
304 | ax2.magnitude_spectrum(QAM16_signal, Fs=fs, scale='dB', color='C1')
305 | ax2.set_xlim(0,6000)
306 | ax2.grid(linestyle='dotted')
307 |
308 | ax3 = fig.add_subplot(gs[2])
309 | ax3.set_title('Power Spectrum Density (PSD) (Source Code/ Block Diagram: "QAM16_signal")')
310 | ax3.psd(QAM16_signal,NFFT=len(t),Fs=fs)
311 | ax3.set_xlim(0,6000)
312 | ax3.grid(linestyle='dotted')
313 |
314 | plt.subplots_adjust(hspace=0.5)
315 | plt.show()
316 | else:
317 | print("Error! Number of bits has to be a multiple of 4. Number of Bits entered: "+ str(Nbits)+".")
318 |
--------------------------------------------------------------------------------
/QPSK.py:
--------------------------------------------------------------------------------
1 | # Import functions and libraries
2 | import numpy as np
3 | import matplotlib.pyplot as plt
4 | import matplotlib.gridspec as gridspec
5 | import scipy as scy
6 | import threading,time
7 | import multiprocessing
8 | import sys
9 | import bitarray
10 | import cmath
11 |
12 | from scipy.fftpack import fft
13 | from numpy import pi
14 | from numpy import sqrt
15 | from numpy import sin
16 | from numpy import cos
17 | from numpy import zeros
18 | from numpy import r_
19 | from scipy.io.wavfile import read as wavread
20 |
21 | # Used for symbol creation. Returns a decimal number from a 2 bit input
22 | def GetQpskSymbol(bit1:bool, bit2:bool):
23 | if(~bit1 & ~bit2):
24 | return 0
25 | elif(~bit1 & bit2):
26 | return 1
27 | elif(bit1 & ~bit2):
28 | return 2
29 | elif(bit1 & bit2):
30 | return 3
31 | else:
32 | return -1
33 |
34 | # Maps a given symbol to a complex signal. Optionally, noise and phase offset can be added.
35 | def QpskSymbolMapper(symbols:int,amplitude_I, amplitude_Q,noise1=0, noise2=0, phaseOffset1 = 0, phaseOffset2 = 0):
36 | if(symbols == 0):
37 | return sqrt(amplitude_I**2 + amplitude_Q**2)*(cos(np.deg2rad(45) + phaseOffset1)+ 1j *sin(np.deg2rad(45) + phaseOffset2)) + (noise1 + 1j*noise2)
38 | elif(symbols == 1):
39 | return sqrt(amplitude_I**2 + amplitude_Q**2)*(cos(np.deg2rad(135) + phaseOffset1) + 1j * sin(np.deg2rad(135) + phaseOffset2)) + (noise1 + 1j*noise2)
40 | elif(symbols == 2):
41 | return sqrt(amplitude_I**2 + amplitude_Q**2)*(cos(np.deg2rad(225) + phaseOffset1) + 1j * sin(np.deg2rad(225) + phaseOffset2)) + (noise1 + 1j*noise2)
42 | elif(symbols == 3):
43 | return sqrt(amplitude_I**2 + amplitude_Q**2)*(cos(np.deg2rad(315) + phaseOffset1) + 1j *sin(np.deg2rad(315) + phaseOffset2)) + (noise1 + 1j*noise2)
44 | else:
45 | return complex(0)
46 |
47 | #-------------------------------------#
48 | #---------- Configuration ------------#
49 | #-------------------------------------#
50 | fs = 44100 # sampling rate
51 | baud = 900 # symbol rate
52 | Nbits = 4000 # number of bits
53 | f0 = 1800 # carrier Frequency
54 | Ns = int(fs/baud) # number of Samples per Symbol
55 | N = Nbits * Ns # Total Number of Samples
56 | t = r_[0.0:N]/fs # time points
57 | f = r_[0:N/2.0]/N*fs # Frequency Points
58 |
59 | # Limit for representation of time domain signals for better visibility.
60 | symbolsToShow = 20
61 | timeDomainVisibleLimit = np.minimum(Nbits/baud,symbolsToShow/baud)
62 |
63 | #----------------------------#
64 | #---------- QPSK ------------#
65 | #----------------------------#
66 | #Input of the modulator
67 | inputBits = np.random.randn(Nbits,1) > 0
68 | inputSignal = (np.tile(inputBits*2-1,(1,Ns))).ravel()
69 |
70 | #Only calculate when dividable by 2
71 | if(inputBits.size%2 == 0):
72 |
73 | #carrier signals used for modulation. carrier2 has 90° phaseshift compared to carrier1 -> IQ modulation
74 | carrier1 = cos(2*pi*f0*t)
75 | carrier2 = cos(2*pi*f0*t+pi/2)
76 |
77 | #Serial-to-Parallel Converter (1/2 of data rate)
78 | I_bits = inputBits[::2]
79 | Q_bits = inputBits[1::2]
80 |
81 | #Digital-to-Analog Conversion
82 | I_signal = (np.tile(I_bits*2-1,(1,2*Ns))).ravel()
83 | Q_signal = (np.tile(Q_bits*2-1,(1,2*Ns)) ).ravel()
84 |
85 | #Multiplicator / mixxer
86 | I_signal_modulated = I_signal * carrier1
87 | Q_signal_modulated = Q_signal * carrier2
88 |
89 | #Summation befor transmission
90 | QPSK_signal = I_signal_modulated + Q_signal_modulated
91 |
92 | #---------- Preperation QPSK Constellation Diagram ------------#
93 | dataSymbols = np.array([[GetQpskSymbol(I_bits[x],Q_bits[x])] for x in range(0,I_bits.size)])
94 |
95 | # amplitudes of I_signal and Q_signal (absolut values)
96 | amplitude_I_signal = 1
97 | amplitude_Q_signal = 1
98 |
99 | #Generate noise. Two sources for uncorelated noise for each amplitude.
100 | noiseStandardDeviation = 0.065
101 | noise1 = np.random.normal(0,noiseStandardDeviation,dataSymbols.size)
102 | noise2 = np.random.normal(0,noiseStandardDeviation,dataSymbols.size)
103 |
104 | #Transmitted and received symbols. Rx symbols are generated under the presence of noise
105 | Tx_symbols = np.array([[QpskSymbolMapper(dataSymbols[x],
106 | amplitude_I_signal,
107 | amplitude_Q_signal,
108 | phaseOffset1 = np.deg2rad(0),
109 | phaseOffset2 = np.deg2rad(0)
110 | )] for x in range(0,dataSymbols.size)])
111 | Rx_symbols = np.array([[QpskSymbolMapper(dataSymbols[x],
112 | amplitude_I_signal ,
113 | amplitude_Q_signal,
114 | noise1=noise1[x],
115 | noise2=noise2[x],
116 | )] for x in range(0,dataSymbols.size)])
117 |
118 | #---------- Plot of QPSK Constellation Diagram ------------#
119 | fig, axis = plt.subplots(2,2,sharey='row')
120 | fig.suptitle('Constellation Diagram QPSK', fontsize=12)
121 |
122 | axis[0,0].plot(Tx_symbols.real, Tx_symbols.imag,'.', color='C1')
123 | axis[0,0].set_title('Tx (Source Code/ Block Diagram: "Tx_symbols")')
124 | axis[0,0].set_xlabel('Inphase [V]')
125 | axis[0,0].set_ylabel('Quadrature [V]')
126 | axis[0,0].set_xlim(-2,2)
127 | axis[0,0].set_ylim(-2,2)
128 |
129 | axis[0,1].plot(Tx_symbols.real, Tx_symbols.imag,'-',Tx_symbols.real, Tx_symbols.imag,'.')
130 | axis[0,1].set_title('Tx with Trajectory (Source Code/ Block Diagram: "Tx_symbols")')
131 | axis[0,1].set_xlabel('Inphase [V]')
132 | axis[0,1].set_ylabel('Quadrature [V]')
133 | axis[0,1].set_xlim(-2,2)
134 | axis[0,1].set_ylim(-2,2)
135 |
136 | axis[1,0].plot(Rx_symbols.real, Rx_symbols.imag,'.', color='C1')
137 | axis[1,0].set_title('Rx (Source Code/ Block Diagram: "Rx_symbols")')
138 | axis[1,0].set_xlabel('Inphase [V]')
139 | axis[1,0].set_ylabel('Quadrature [V]')
140 | axis[1,0].set_xlim(-2,2)
141 | axis[1,0].set_ylim(-2,2)
142 |
143 | axis[1,1].plot(Rx_symbols.real, Rx_symbols.imag,'-',Rx_symbols.real, Rx_symbols.imag,'.')
144 | axis[1,1].set_title('Rx with Trajectory (Source Code/ Block Diagram: "Rx_symbols")')
145 | axis[1,1].set_xlabel('Inphase [V]')
146 | axis[1,1].set_ylabel('Quadrature [V]')
147 | axis[1,1].set_xlim(-2,2)
148 | axis[1,1].set_ylim(-2,2)
149 |
150 | #---------- Plot of QPSK Modulating Signals ------------#
151 | fig, axis = plt.subplots(6,1,sharex='col')
152 | fig.suptitle('QPSK Modulation', fontsize=12)
153 |
154 | axis[0].plot(t, inputSignal, color='C1')
155 | axis[0].set_title('Digital Data Signal (Source Code/ Block Diagram: "inputBits")')
156 | axis[0].set_xlabel('Time [s]')
157 | axis[0].set_ylabel('Amplitude [V]')
158 | axis[0].set_xlim(0,timeDomainVisibleLimit)
159 | axis[0].grid(linestyle='dotted')
160 |
161 | axis[1].plot(t, I_signal, color='C2')
162 | axis[1].set_title('Digital I-Signal (Source Code/ Block Diagram: "I_signal")')
163 | axis[1].set_xlabel('Time [s]')
164 | axis[1].set_xlim(0,timeDomainVisibleLimit)
165 | axis[1].set_ylabel('Amplitude [V]')
166 | axis[1].grid(linestyle='dotted')
167 |
168 | axis[2].plot(t, I_signal_modulated, color='C2')
169 | axis[2].set_title('Modulated I-Signal (Source Code/ Block Diagram: "I_signal_modulated")')
170 | axis[2].set_xlabel('Time [s]')
171 | axis[2].set_xlim(0,timeDomainVisibleLimit)
172 | axis[2].set_ylabel('Amplitude [V]')
173 | axis[2].grid(linestyle='dotted')
174 |
175 | axis[3].plot(t, Q_signal, color='C3')
176 | axis[3].set_title('Digital Q-Signal (Source Code/ Block Diagram: "Q_signal")')
177 | axis[3].set_xlabel('Time [s]')
178 | axis[3].set_xlim(0,timeDomainVisibleLimit)
179 | axis[3].set_ylabel('Amplitude [V]')
180 | axis[3].grid(linestyle='dotted')
181 |
182 | axis[4].plot(t, Q_signal_modulated, color='C3')
183 | axis[4].set_title('Modulated Q-Signal (Source Code/ Block Diagram: "Q_signal_modulated")')
184 | axis[4].set_xlabel('Time [s]')
185 | axis[4].set_xlim(0,timeDomainVisibleLimit)
186 | axis[4].set_ylabel('Amplitude [V]')
187 | axis[4].grid(linestyle='dotted')
188 |
189 | axis[5].plot(t,QPSK_signal, color='C4')
190 | axis[5].set_title('QPSK Signal Modulated (Source Code/ Block Diagram: "QPSK_signal")')
191 | axis[5].set_xlabel('Time [s]')
192 | axis[5].set_xlim(0,timeDomainVisibleLimit)
193 | axis[5].set_ylabel('Amplitude [V]')
194 | axis[5].grid(linestyle='dotted')
195 |
196 | plt.subplots_adjust(hspace=0.5)
197 |
198 | #---------- Plot of Modulated Signal and Spectrum ------------#
199 | fig = plt.figure(constrained_layout=True)
200 | gs = gridspec.GridSpec(3, 1, figure=fig)
201 | fig.suptitle('QPSK Modulation', fontsize=12)
202 | ax = fig.add_subplot(gs[0, :])
203 |
204 | ax1 = fig.add_subplot(gs[0])
205 | ax1.set_title('Magnitude Spectrum (Source Code/ Block Diagram: "QPSK_signal")');
206 | ax1.magnitude_spectrum(QPSK_signal, Fs=fs, color='C1')
207 | ax1.set_xlim(0,6000)
208 | ax1.grid(linestyle='dotted')
209 |
210 | ax2 = fig.add_subplot(gs[1])
211 | ax2.set_title('Log. Magnitude Spectrum (Source Code/ Block Diagram: "QPSK_signal")')
212 | ax2.magnitude_spectrum(QPSK_signal, Fs=fs, scale='dB', color='C1')
213 | ax2.set_xlim(0,6000)
214 | ax2.grid(linestyle='dotted')
215 |
216 | ax3 = fig.add_subplot(gs[2])
217 | ax3.set_title('Power Spectrum Density (PSD) (Source Code/ Block Diagram: "QPSK_signal")')
218 | ax3.psd(QPSK_signal,NFFT=len(t),Fs=fs)
219 | ax3.set_xlim(0,6000)
220 | ax3.grid(linestyle='dotted')
221 |
222 | plt.subplots_adjust(hspace=0.5)
223 | plt.show()
224 | else:
225 | print("Error! Number of bits has to be a multiple of 2. Number of Bits entered: "+ str(Nbits)+".")
226 |
227 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Communication_Modulation
2 | This repository provides a simple python script for getting experience with common modulation techniques i.e. QAM, QPSK, and BPSK
3 |
4 | ## QPSK - Quadrature Phase Shift Keying
5 |
6 | Image Description | Image
7 | ------------ | -------------
8 | Simulation Model |
9 | Time Domain Signals |
10 | Frequency Domain Signals |
11 | Constelation Diagram |
12 |
13 | ## QAM - Quadrature Amplitude Modulation
14 |
15 | Image Description | Image
16 | ------------ | -------------
17 | Simulation Model |
18 | Time Domain Signals |
19 | Frequency Domain Signals |
20 | Constelation Diagram |
21 |
22 | ## BPSK - Binary Phase Shift Keying
23 |
24 | Image Description | Image
25 | ------------ | -------------
26 | Simulation Model |
27 | Time Domain Signals |
28 | Frequency Domain Signals |
29 | Constelation Diagram |
30 |
31 | # Repo Stats
32 |  since 16.04.2022
33 |
34 | ## Star History
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/imgs/bpsk_constulation.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sascha-kirch/Communication_Modulation/2c5e5cada0eddda4c46c681ddf21bd4bae61673b/imgs/bpsk_constulation.PNG
--------------------------------------------------------------------------------
/imgs/bpsk_fft.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sascha-kirch/Communication_Modulation/2c5e5cada0eddda4c46c681ddf21bd4bae61673b/imgs/bpsk_fft.PNG
--------------------------------------------------------------------------------
/imgs/bpsk_model.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sascha-kirch/Communication_Modulation/2c5e5cada0eddda4c46c681ddf21bd4bae61673b/imgs/bpsk_model.PNG
--------------------------------------------------------------------------------
/imgs/bpsk_timedomain.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sascha-kirch/Communication_Modulation/2c5e5cada0eddda4c46c681ddf21bd4bae61673b/imgs/bpsk_timedomain.PNG
--------------------------------------------------------------------------------
/imgs/psk_constulation.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sascha-kirch/Communication_Modulation/2c5e5cada0eddda4c46c681ddf21bd4bae61673b/imgs/psk_constulation.PNG
--------------------------------------------------------------------------------
/imgs/psk_fft.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sascha-kirch/Communication_Modulation/2c5e5cada0eddda4c46c681ddf21bd4bae61673b/imgs/psk_fft.PNG
--------------------------------------------------------------------------------
/imgs/psk_model.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sascha-kirch/Communication_Modulation/2c5e5cada0eddda4c46c681ddf21bd4bae61673b/imgs/psk_model.PNG
--------------------------------------------------------------------------------
/imgs/psk_timedomain.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sascha-kirch/Communication_Modulation/2c5e5cada0eddda4c46c681ddf21bd4bae61673b/imgs/psk_timedomain.PNG
--------------------------------------------------------------------------------
/imgs/qam_constulation.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sascha-kirch/Communication_Modulation/2c5e5cada0eddda4c46c681ddf21bd4bae61673b/imgs/qam_constulation.PNG
--------------------------------------------------------------------------------
/imgs/qam_fft.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sascha-kirch/Communication_Modulation/2c5e5cada0eddda4c46c681ddf21bd4bae61673b/imgs/qam_fft.PNG
--------------------------------------------------------------------------------
/imgs/qam_model.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sascha-kirch/Communication_Modulation/2c5e5cada0eddda4c46c681ddf21bd4bae61673b/imgs/qam_model.PNG
--------------------------------------------------------------------------------
/imgs/qam_timedomain.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sascha-kirch/Communication_Modulation/2c5e5cada0eddda4c46c681ddf21bd4bae61673b/imgs/qam_timedomain.PNG
--------------------------------------------------------------------------------