├── 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 | ![](https://komarev.com/ghpvc/?username=saschakirchCommunicationModulation&color=yellow) since 16.04.2022 33 | 34 | ## Star History 35 | 36 | 37 | 38 | 39 | 40 | Star History Chart 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 --------------------------------------------------------------------------------