Подавление узкополосной помехи
Подавление узкополосной помехи в сигнале BPSK
В этом примере рассмотрим приём сигнала с двоичной фазовой манипуляцией BPSK при сильной узкополосной помехе. Модель Engee формирует биты, модулирует их и добавляет синусоидальную помеху. Сравним сигнал и долю ошибочных битов до и после режекторного фильтра.
При BPSK каждый бит передаётся одним из двух противоположных фазовых состояний. В выбранном отображении биту 0 соответствует положительная действительная часть, биту 1 — отрицательная. Поэтому решение можно получить по знаку отсчёта.
Сильная синусоидальная помеха меняет этот знак и вызывает ошибки. Режекторный фильтр ослабляет узкий участок спектра около частоты помехи. Однако вместе с помехой он может удалить часть полезного сигнала — это ограничение тоже нужно учитывать.
Долю ошибочных битов BER (bit error rate) рассчитаем как
Сравнение будем проводить по одной и той же последовательности битов. Первые 20 отсчётов исключим, чтобы начальное состояние фильтра не определяло результат. Решения здесь принимаются непосредственно по отсчётам; отдельная тактовая синхронизация и согласованный фильтр не моделируются.

Модель NotchJammer.engee содержит цепочку блоков Bits → BPSK → AddJammer → NotchFilter (Bernoulli Binary Generator, BPSK Modulator Baseband, Add, Notch-Peak Filter). Помеха имеет частоту 120 Гц. Используется режекторный выход фильтра.
В рабочую область записываются исходные биты bits, сигнал с помехой raw и отфильтрованный сигнал filtered.
Подключим библиотеки и зададим путь к модели относительно папки скрипта. Вспомогательная функция загрузит и откроет модель, применит параметры и закроет её после запуска. Данные To Workspace читаются из общей рабочей области Engee, поэтому программный запуск этого примера выполняем с isolate = false.
using Statistics, DataFrames, Plots
gr()
name = "NotchJammer"
path = joinpath(@__DIR__, name * ".engee")
@assert isfile(path) "Не найден файл модели: $path"
function load_and_run(path, name; settings=Pair{String,String}[])
try engee.close(name; force=true) catch end
model = engee.load(path; force=true)
engee.open(name)
for (block, keyvalue) in settings
engee.set_param!("$name/$block", keyvalue)
end
try
engee.run(model)
finally
engee.close(name; force=true)
end
end
wa(name) = Core.eval(Main, :(collect($(Symbol(name)))))
Запустим модель и прочитаем три записанных сигнала. Сравним решения по знаку с исходными битами и найдём BER на одинаковом участке записи.
load_and_run(path,name)
bit_df = wa("bits"); raw_df = wa("raw"); filt_df = wa("filtered")
n = min(size(bit_df,1), size(raw_df,1), size(filt_df,1))
bits = Float64.(bit_df.value[1:n]) .> 0.5
raw = real.(raw_df.value[1:n])
filtered = real.(filt_df.value[1:n])
valid = 21:n
ber_raw = mean((raw[valid] .< 0) .!= bits[valid])
ber_filtered = mean((filtered[valid] .< 0) .!= bits[valid])
t = Float64.(raw_df.time[1:n])
Построим фрагменты сигналов и сравним BER до и после фильтра. Это позволит одновременно увидеть действие фильтра и оценить качество приёма.
p1 = plot(t[1:100],raw[1:100],label="До фильтра",xlabel="Время, с",ylabel="Амплитуда",title="BPSK и узкополосная помеха")
plot!(p1,t[1:100],filtered[1:100],label="После фильтра")
p2 = bar(["До", "После"],[ber_raw,ber_filtered],ylabel="BER",title="Доля ошибочных битов",legend=false)
display(plot(p1,p2,layout=(2,1),size=(850,650)))
println("BER до=",ber_raw,"; после=",ber_filtered,"; проверено бит=",length(valid))
После фильтра синусоидальная составляющая должна уменьшиться. Столбцы BER показывают, насколько это помогло восстановить биты для выбранной частоты и амплитуды помехи.
Нулевое число ошибок относится только к исследованной конечной записи. Оно не означает нулевую вероятность ошибки при любых условиях. Если сдвинуть частоту помехи или изменить полосу фильтра, эффект нужно измерить заново.
Вывод
В данном примере мы показали, как в Engee исследовать подавление помехи и проверить его по битовым решениям. Режекторная фильтрация полезна, когда частота узкополосной помехи известна. При выборе полосы режекции важно сохранить полезную часть спектра.
