DSP Basics: Signal generation
Let's consider the basic techniques for generating periodic, pulse, and noise signals for testing digital signal processing (DSP) algorithms, modeling communication systems, radars, and radio engineering systems in general. The main themes of the example:
- setting the periodic signal
- Discrete signal time grid
- generation of typical periodic signals (sinusoid, meander, saw)
- Discrete noise generation
- Pulse sequences
- frequency-modulated signals
Libraries used in the example:
let
intalled_packages = collect(x.name for (_, x) in Pkg.dependencies() if x.is_direct_dep)
list_packages = ["DSP","ChirpSignal","Waveforms"]
for pack in list_packages
pack in intalled_packages || Pkg.add(pack)
end
end
using DSP, ChirpSignal, Waveforms
Generation of periodic signals
A periodic signal is a repeating sequence. Let's create one period of signal values, and then repeat it five times with the function repeat. We visualize it in time using a stepped line:
one_period = [1; 4; 6; 8; 7; 5; 0; 0; 0; 0];
five_period = repeat(one_period, 5);
plot(five_period,
linewidth = 2,
marker = "circle",
markersize = 3,
linetype=:steppost,
title = "A simple periodic signal")
Now let's try to generate a periodic signal using mathematical functions. For example, the function sin calculates the sine value for the input angle value in radians. In order to generate a sinusoid with the required repetition rate, it is worth using a time vector - a sequence of evenly increasing time samples with a specified sampling period. Let's set the time vector for signals with a sampling frequency of 1000 Hz:
fs = 1000; # sampling rate of the signal
dt = 1/fs; # sampling period
stoptime = 0.5; # the end time of the signal
t = 0:dt:stoptime-dt # vector of time counts
Now we will create a periodic sinusoidal signal with a fundamental frequency of 10 Hz (the shape of the sinusoid will be repeated every 0.1 seconds). Variable sine_wave - this is a vector of 500 samples. Visualize the signal with the function plot:
sine_wave = sin.(2*pi*t*10);
plot(t, sine_wave, title = "A 10 Hz sine wave")
With one line of code, we can generate several sinusoids with different amplitudes, frequencies, and initial phases. To do this, set these parameters as vectors. The resulting variable three_sines it will already be a 500x3 matrix. We can pass it to functions plot for simultaneous display of three graphs on the same axes:
three_sines = [1.4 0.6 1] .* sin.(2*pi*t.*[10 15 30] .+ [pi/4 0 pi/6]);
plot(t, three_sines, title = "Three sine waves of 10 Hz, 15 Hz and 30 Hz")
If we are interested in observing the shape of the sum of three sinusoids, then we can add up the columns of the matrix using the function sum:
sum_sines = sum(three_sines, dims = 2);
plot(t, sum_sines, title = "The sum of three sinusoids")
Additional functions for signal generation
We often need rectangular, sawtooth, and triangular periodic signals, as well as random sequences to simulate noise.
Library Waveforms.jl It contains functions for generating the listed periodic signals, but as the "master" signal that determines the fundamental frequency, a uniformly increasing sawtooth signal is used, varying from 0 to 2pi. Let's create it from the time vector and loop it with the division operation. mod at 2pi:
base_signal = mod.(2*pi*t*10, 2*pi);
plot(t, base_signal, title = "The setting signal for the functions")
A popular signal in radio engineering is a meander. It is also a rectangular periodic signal with a borehole of 2 (or a fill factor of 50%). We can generate it with the function squarewave. This function can also take an additional input argument, a fill factor ranging from 0 to 1. Generate a second rectangular signal with 20% fill.:
sqw = squarewave.(base_signal);
sqw02 = squarewave.(base_signal, 0.2);
plot(t, sqw)
plot!(t, sqw02, title = "Rectangular signal")
A sawtooth signal varying from -1 to +1 can be obtained by the function sawtoothwave:
st = sawtoothwave.(base_signal);
plot(t, st, title = "Sawtooth signal")
Also consider the function trianglewave to generate a triangular signal:
trw = trianglewave.(base_signal);
plot(t, trw, title = "The triangular signal")
Functions for generating random numbers with different distributions are available in Engee. Let's use the function rand to create a uniform noise signal:
noise = rand.(length(t));
noise = noise .- 0.5;
plot(t, noise, title = "Noise with uniform distribution")
Now let's try to combine noise and a sawtooth signal, but we'll do it in such a way that the noise level increases with the signal level. Using the master signal, we will generate a sawtooth, varying from 0 to +1. Then we multiply the noise by the resulting sawtooth signal and sum the increasing "saw" with increasing noise.:
clean_sawtooth = mod.(t*10,1); # A "clean" sawtooth signal
amp_noise = noise .* clean_sawtooth; # increasing noise signal
noisy_sawtooth = clean_sawtooth .+ (0.25 .* amp_noise); # their sum
plot(t, noisy_sawtooth, title = "Signal and noise amplification")
Generation of non-periodic signals and pulse packets
As an example of a non-periodic signal, consider the common sinc- an impulse. We get it using a trigonometric function and an input vector of angle values in radians. In the example, we generate a pulse in the range from -2pi to +2pi (in increments of pi/16).
rads = -2*pi:pi/16:2*pi;
rads = rads[1:end-1];
one_sinc = sinc.(rads);
plot(rads, one_sinc, title = "sinc-pulse")
Now let's create a bundle sinc-pulses following a certain period. To do this, add a number of zeros to the vector of one pulse at the end, and multiply the result by the function repeat. We will also add attenuation to our burst of pulses. The function will help us with this. LinRange:
zero_padding = [one_sinc; zeros(2*length(one_sinc))]; # sinc-pulse with zeros
four_sinc = repeat(zero_padding, 4); # a burst of pulses without attenuation
decay = LinRange(1, 0.4, length(four_sinc)); # attenuation signal
pulse_train = four_sinc .* decay; # a packet of attenuated pulses
plot(pulse_train, title = "A packet of sinc pulses with attenuation")
Frequency-modulated signals
In conclusion, we will consider typical frequency-modulated signals, such as linear frequency modulation (LFM) and quadrature frequency modulation signals. To create such signals, we will use the function chirp:
LFM = chirp(10, fs, 0, 500; method = "linear");
sg1 = DSP.spectrogram(LFM, 511, 256; fs = fs);
heatmap(sg1.time, sg1.freq, pow2db.(sg1.power),
xlabel = "Time, seconds",
ylabel = "Frequency, Hz",
title = "LFM signal spectrogram")
And to display the signals, we will call the function DSP.spectrogram, which visualizes changes in the spectrum of a signal (depending on its power and frequency) over time. We can observe a linear frequency increase for the FM signal and a quadratic one in the case of the option method = "quadratic":
QFM = chirp(10, fs, 0, 500; method = "quadratic");
sg2 = DSP.spectrogram(QFM, 511, 256; fs = fs);
heatmap(sg2.time, sg2.freq, pow2db.(sg2.power),
xlabel = "Time, seconds",
ylabel = "Frequency, Hz",
title = "KCHM signal spectrogram")
Conclusion
We have reviewed the basic techniques for generating periodic, aperiodic, and noise signals that help us develop signal processing algorithms and simulate radio engineering systems.