Документация Engee
Notebook

Основы ЦОС: генерация сигналов

Рассмотрим основные техники генерации периодических, импульсных и шумовых сигналов для тестирования алгоритмов цифровой обработки сигналов (ЦОС), моделирования систем связи, радаров и радиотехнических систем в целом. Основные темы примера:

  • задание периодического сигнала
  • временная сетка дискретного сигнала
  • генерация типичных периодических сигналов (синусоида, меандр, пила)
  • генерация дискретного шума
  • последовательности импульсов
  • частотно-модулированные сигналы

Используемые в примере библиотеки:

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

Генерация периодических сигналов

Периодический сигнал - это повторяющаяся последовательность. Создадим один период значений сигнала, а затем повторим его пять раз функцией repeat. Визуализируем во времени ступенчатой линией:

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]:

Теперь попробуем сгенерировать периодический сигнал при помощи математических функций. К примеру, функция sin высчитывает значение синуса для входного значения угла в радианах. Для того, чтобы сгенерировать синусоиду с требуемой частотой повторения, стоит воспользоваться вектором времени - последовательностью равномерно нарастающих отсчётов времени с указанным периодом дискретизации. Зададим вектор времени для сигналов с частотой дискретизации 1000 Гц:

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

Теперь создадим периодический синусоидальный сигнал с основной частотой 10 Гц (форма синусоиды будет повторяться каждые 0.1 сек). Переменная sine_wave - это вектор из 500 отсчётов. Визуализируем сигнал функцией plot:

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

Мы можем одной строчкой кода сгенерировать несколько синусоид с разными амплитудами, частотами и начальными фазами. Для этого зададим эти параметры векторами. Результирующая переменная three_sines уже будет матрицей размером 500х3. Мы можем передать её функции plot для одновременного отображения трёх графиков на одних осях:

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]:

Если нам интересно наблюдать форму суммы трёх синусоид, то мы можем сложить столбцы матрицы при помощи функции sum:

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

Дополнительные функции для генерации сигналов

Часто нам требуются прямоугольные, пилообразные и треугольные периодические сигналы, а также случайные последовательности для моделирования шума.

Библиотека Waveforms.jl содержит функции для генерации перечисленных периодических сигналов, но в качестве "задающего" сигнала, определяющего основную частоту, используется равномерно нарастающий пилообразный сигнал, изменяющийся в пределах от 0 до 2pi. Создадим его из вектора времени и зациклим операцией деления mod на 2pi:

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

Популярный сигнал в радиотехнике - меандр. Он же прямоугольный периодический сигнал со скважностью 2 (или коэффициентом заполнения в 50%). Мы можем сгенерировать его функцией squarewave. Также эта функция может принимать дополнительный входной аргумент - коэффициент заполнения в пределах от 0 до 1. Сгенерируем второй прямоугольный сигнал с 20% заполнения:

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

Пилообразный сигнал, изменяющийся в пределах от -1 до +1 можно получить функцией sawtoothwave:

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

Также рассмотрим функцию trianglewave для генерации треугольного сигнала:

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

В Engee доступны функции для генерации случайных чисел с различными распределениями. Воспользуемся функцией rand для создания шумового сигнала с равномерным распределением:

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

Теперь попробуем объединить шум и пилообразный сигнал, но сделаем это таким образом, чтобы уровень шума возрастал с уровнем сигнала. При помощи задающего сигнала сгенерируем пилообразный, изменяющийся в пределах от 0 до +1. Затем умножим шум на получившийся пилообразный сигнал и просуммируем нарастающую "пилу" с нарастающим шумом:

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]:

Генерация непериодических сигналов и пачки импульсов

В качестве примера непериодического сигнала рассмотрим распространённый sinc-импульс. Мы получаем его при помощи тригонометрической функции и входного вектора значений угла в радианах. В примере мы генерируем импульс в пределах от -2pi до +2pi (с шагом 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]:

Теперь создадим пачку sinc-импульсов, следующих с определённым периодом. Для этого добавим к вектору одного импульса некоторое количество нулей в конце, и размножим результат функцией repeat. Также добавим нашей пачке импульсов затухание - в этом нам поможет функция LinRange:

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]:

Частотно-модулированные сигналы

В заключении рассмотрим типичные частотно-модулированные сигналы, такие как сигналы с линейной частотной модуляцией (ЛЧМ) и сигналы с квадратурной частотной модуляцией. Для создания подобных сигналов мы воспользуемся функцией 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]:

А для отображения сигналов мы вызовем функцию DSP.spectrogram, которая визуализирует изменения спектра сигнала (зависимости его мощности от частоты) во времени. Мы можем наблюдать линейный рост частоты для ЛЧМ-сигнала и квадратичный в случае опции 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]:

Заключение

Мы рассмотрели основные техники генерации периодических, апериодических и шумовых сигналов, помогающих нам разрабатывать алгоритмы обработки сигналов и моделировать радиотехнические системы.