Digital Up & Down Conversion
Est. read time: 2 minutes | Last updated: December 17, 2024 by John Gentile
Contents
import numpy as np
import matplotlib.pyplot as plt
from rfproto import measurements, nco, plot, sig_gen
f = 440.5 # desired output frequency
n = 48000 # number of output points to compute
fs = 48000 # sampling frequency
N = 32 # phase accumulator length (num bits)
P = 9 # LUT table address length (total depth = 2^P)
M = 16 # quantized word length (num bits)
test_NCO = nco.Nco(N, M, P, fs)
y = np.zeros(n) + 1j*np.zeros(n)
test_NCO.SetOutputFreq(f)
for i in range (n):
# just take imag part (starts at 0) for this
y[i] = test_NCO.Step()
plot.spec_an(y, fs, "DDS Output Spectrum", scale_noise=True, norm=True)
plt.show()
input_sig = sig_gen.cmplx_dt_sinusoid(2**15, 10000, fs, n)
plot.spec_an(input_sig, fs, "Input Signal Spectrum", scale_noise=False, norm=True)
plt.show()
Complex NCO- which acts as discrete-time form of analog heterodyne system’s Local Oscillator (LO)- allows us to mix an input signal up or down in frequency, without worrying about images that would occur with a real-valued NCO (e.g. real valued NCO has frequencies at both ). The process can be seen as:
It can be seen that mixing adds frequencies, causing an associated shift upwards in total signal output frequency of .
mixed = np.zeros(n) + 1j*np.zeros(n)
for i in range(n):
# NOTE: conj(NCO output) moves mixed signal down, while input * y moves signal up
mixed[i] = input_sig[i] * y[i]
# NOTE: since both input signal and NCO are complex, there are no images created in mixing
plot.spec_an(mixed, fs, "Mixed Signal Spectrum", scale_noise=False, norm=True)
plt.show()
To shift the output frequency down, we can simply take the complex conjugate of the NCO output (e.g. ) to create a “negative” frequency, since:
This mixing subtracts frequencies, causing an associated shift downwards in total signal output frequency of .
mixed = np.zeros(n) + 1j*np.zeros(n)
for i in range(n):
mixed[i] = input_sig[i] * np.conj(y[i])
plot.spec_an(mixed, fs, "Mixed Signal Spectrum", scale_noise=False, norm=True)
plt.show()
Spectral Inversion
Based on the mixing process of a signal (both up and down conversion), there exists times where the spectral image is used that inverts the desired spectrum:
To compensate- or generally reverse the direction of rotation- we can simply swap I
and Q
signals (generally more computationally efficient to multiplying by -1
):
for i in range(len(mixed)):
mixed[i] = mixed[i].imag + 1j*mixed[i].real
plot.spec_an(mixed, fs, "Inverted Signal Spectrum", scale_noise=False, norm=True)
plt.show()
Simplification
#TODO:
when mixer equals or , can just use alternating +/-1
(for ) or +1,0,-1,0
(for ) very cheaply! Can also be used in lieu of fftshift()
type applications.
Transmit Simplification
Since Digital-to-Analog Converters (DACs) operate on real digital data (real input to real analog output)- except in direct conversion (zero IF) front ends- we only need the real output of a digital upconverter (DUC), either or . In this case, we can simplify the digital mixer (the complex multiplier used to combine the NCO output and transmit I/Q stream) to not have to compute the full complex product (requiring 4x multiplies), but rather just the term (2x multiplies) as:
References
- DUC/DDC Compiler - AMD/Xilinx
- Designing DDC systems using CIC and FIR Filters - Intel/Altera
- DUC/DDC MATLAB
- Designing Efficient Digital Up and Down Converters for Narrowband Systems - Xilinx
- What’s Up with Digital Downconverters Part I - ADI
- What’s Up with Digital Downconverters Part II - ADI
- Complex RF Mixers, Zero-IF Architecture, and Advanced Algorithms: The Black Magic in Next-Generation SDR Transceivers - ADI