Engee documentation
Notebook

DSP basics: signal generation

Review basic techniques for generating periodic, pulsed and noise signals for testing digital signal processing (DSP) algorithms, modelling communication systems, radar and radio systems in general. The main topics of the example are:

  • periodic signal assignment
  • time grid of a discrete signal
  • generation of typical periodic signals (sine wave, meander, sawtooth)
  • generation of discrete noise
  • pulse sequences
  • frequency modulated signals

The libraries used in the example:

In [ ]:
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
In [ ]:
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. Visualise it in time with a step line:

In [ ]:
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 = "Простой периодический сигнал")
Out[0]:

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, we should use a time vector - a sequence of uniformly increasing time samples with a specified sampling period. Let's set the time vector for signals with a sampling rate of 1000 Hz:

In [ ]:
fs = 1000;              # частота дискретизации сигнала
dt = 1/fs;              # период дискретизации
stoptime = 0.5;         # время окончания сигнала
t = 0:dt:stoptime-dt    # вектор отсчётов времени
Out[0]:
0.0:0.001:0.499

Now let's create a periodic sinusoidal signal with a basic frequency of 10 Hz (the shape of the sinusoid will be repeated every 0.1 sec). The variable sine_wave is a vector of 500 samples. Visualise the signal with the function plot:

In [ ]:
sine_wave = sin.(2*pi*t*10);
plot(t, sine_wave, title = "Синусоида частотой 10 Гц")
Out[0]:

We can generate several sinusoids with different amplitudes, frequencies and initial phases with one line of code. To do this, let's set these parameters as vectors. The resulting variable three_sines will already be a 500x3 matrix. We can pass it to the function plot for simultaneous display of three graphs on the same axes:

In [ ]:
three_sines = [1.4 0.6 1] .* sin.(2*pi*t.*[10 15 30] .+ [pi/4 0 pi/6]);
plot(t, three_sines, title = "Три синусоиды 10 Гц, 15 Гц и 30 Гц")
Out[0]:

If we are interested in observing the shape of the sum of three sinusoids, we can add the columns of the matrix using the function sum:

In [ ]:
sum_sines = sum(three_sines, dims = 2);
plot(t, sum_sines, title = "Сумма трёх синусоид")
Out[0]:

Additional functions for signal generation

We often need rectangular, sawtooth and triangular periodic signals as well as random sequences for noise modelling.

The library Waveforms.jl contains functions for generating the above periodic signals, but a uniformly increasing sawtooth signal varying from 0 to 2pi is used as the "setting" signal that determines the fundamental frequency. Let's create it from a time vector and loop it by the operation of dividing mod by 2pi:

In [ ]:
base_signal = mod.(2*pi*t*10, 2*pi);
plot(t, base_signal, title = "Задающий сигнал для функций")
Out[0]:

A popular signal in radio engineering is the meander. It is a rectangular periodic signal with a frequency of 2 (or a fill factor of 50%). We can generate it with the function squarewave. This function can also accept an additional input argument - the fill factor in the range from 0 to 1. Let's generate the second rectangular signal with 20% fill factor:

In [ ]:
sqw = squarewave.(base_signal);
sqw02 = squarewave.(base_signal, 0.2);
plot(t, sqw)
plot!(t, sqw02, title = "Прямоугольный сигнал")
Out[0]:

A sawtooth signal varying from -1 to +1 can be obtained by the function sawtoothwave:

In [ ]:
st = sawtoothwave.(base_signal);
plot(t, st, title = "Пилообразный сигнал")
Out[0]:

Also consider the function trianglewave to generate a triangular signal:

In [ ]:
trw = trianglewave.(base_signal);
plot(t, trw, title = "Треугольный сигнал")
Out[0]:

Engee has functions available to generate random numbers with different distributions. Let's use the function rand to generate a noise signal with uniform distribution:

In [ ]:
noise = rand.(length(t));
noise = noise .- 0.5;
plot(t, noise, title = "Шум с равномерным распределением")
Out[0]:

Now let's try to combine noise and a sawtooth signal, but do it in such a way that the noise level increases with the signal level. Using a reference signal, let's generate a sawtooth signal that varies between 0 and +1. Then multiply the noise by the resulting sawtooth signal and sum the increasing sawtooth with the increasing noise:

In [ ]:
clean_sawtooth = mod.(t*10,1);                          # "чистый" пилообразный сигнал
amp_noise = noise .* clean_sawtooth;                    # нарастающий шумовой сигнал
noisy_sawtooth = clean_sawtooth .+ (0.25 .* amp_noise); # их сумма
plot(t, noisy_sawtooth, title = "Усиление сигнал и шума")
Out[0]:

Generation of non-periodic signals and packet pulses

As an example of a non-periodic signal, let us consider a common sinc-pulse. We obtain 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 steps pi/16).

In [ ]:
rads = -2*pi:pi/16:2*pi;
rads = rads[1:end-1];
one_sinc = sinc.(rads);
plot(rads, one_sinc, title = "sinc-импульс")
Out[0]:

Now let's create a pack of sinc-pulses following with a certain period. To do this, add some number of zeros to the vector of one pulse at the end, and multiply the result with the function repeat. We will also add damping to our pack of pulses - the function LinRange will help us with this:

In [ ]:
zero_padding = [one_sinc; zeros(2*length(one_sinc))];   # sinc-импульс с нулями
four_sinc = repeat(zero_padding, 4);                    # пачка импульсов без затухания
decay = LinRange(1, 0.4, length(four_sinc));            # сигнал затухания
pulse_train = four_sinc .* decay;                       # пачка импульсов с затуханием
plot(pulse_train, title = "Пачка sinc-импульсов с затуханием")
Out[0]:

Frequency modulated signals

Finally, let's look at typical frequency modulated signals, such as linear frequency modulated (LFM) signals and quadrature frequency modulated signals. To create such signals we will use the function chirp:

In [ ]:
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 = "Время, сек", 
        ylabel = "Частота, Гц",
        title = "Спектрограмма ЛЧМ-сигнала")
Out[0]:

And to display the signals we will call the function DSP.spectrogram, which visualises the changes of the signal spectrum (the dependence of its power on frequency) in time. We can observe linear frequency growth for the LFM signal and quadratic in the case of the option method = "quadratic":

In [ ]:
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 = "Время, сек", 
        ylabel = "Частота, Гц",
        title = "Спектрограмма КЧМ-сигнала")
Out[0]:

Conclusion

We have reviewed the basic techniques for generating periodic, aperiodic and noise signals to help us develop signal processing algorithms and model radio systems.