From e7e1bedcad5a3bcf266e6d393cdc925d698e682f Mon Sep 17 00:00:00 2001 From: Kyle Reed <3761006+kallanreed@users.noreply.github.com> Date: Sun, 27 Aug 2023 15:56:40 -0700 Subject: [PATCH] Support squelch in pocsag (#1415) * Support squelch in pocsag * Revert smooth threshold --- firmware/application/apps/pocsag_app.cpp | 8 ++++- firmware/application/apps/pocsag_app.hpp | 38 +++++++++++--------- firmware/baseband/proc_pocsag.cpp | 39 ++++++++++++++------ firmware/baseband/proc_pocsag.hpp | 4 +-- firmware/common/dsp_iir_config.hpp | 4 +-- firmware/common/pocsag.cpp | 46 +++++------------------- firmware/common/pocsag.hpp | 39 +++++++++++++++----- 7 files changed, 100 insertions(+), 78 deletions(-) diff --git a/firmware/application/apps/pocsag_app.cpp b/firmware/application/apps/pocsag_app.cpp index 231ffa72..9d862761 100644 --- a/firmware/application/apps/pocsag_app.cpp +++ b/firmware/application/apps/pocsag_app.cpp @@ -95,6 +95,7 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav) &field_lna, &field_vga, &field_frequency, + &field_squelch, &field_volume, &image_status, &text_packet_count, @@ -111,6 +112,11 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav) logger.append(LOG_ROOT_DIR "/POCSAG.TXT"); + field_squelch.set_value(receiver_model.squelch_level()); + field_squelch.on_change = [this](int32_t v) { + receiver_model.set_squelch_level(v); + }; + button_ignore_last.on_select = [this](Button&) { settings_.enable_ignore = true; settings_.address_to_ignore = last_address; @@ -122,8 +128,8 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav) }; refresh_ui(); - receiver_model.enable(); audio::output::start(); + receiver_model.enable(); baseband::set_pocsag(); } diff --git a/firmware/application/apps/pocsag_app.hpp b/firmware/application/apps/pocsag_app.hpp index 621d6177..a52681fd 100644 --- a/firmware/application/apps/pocsag_app.hpp +++ b/firmware/application/apps/pocsag_app.hpp @@ -136,10 +136,7 @@ class POCSAGAppView : public View { bool hide_addr_only() const { return settings_.hide_addr_only; }; NavigationView& nav_; - RxRadioState radio_state_{ - 12'500, // POCSAG is FSK +/- 4.5MHz, 12k5 is a good filter. - 3'072'000, // Match baseband_fs in proc_pocsag. - }; + RxRadioState radio_state_{}; // Settings POCSAGSettings settings_{}; @@ -161,24 +158,33 @@ class POCSAGAppView : public View { void on_stats(const POCSAGStatsMessage* stats); uint32_t last_address = 0xFFFFFFFF; - pocsag::POCSAGState pocsag_state{}; + pocsag::EccContainer ecc{}; + pocsag::POCSAGState pocsag_state{&ecc}; POCSAGLogger logger{}; uint16_t packet_count = 0; - RFAmpField field_rf_amp{ - {13 * 8, 0 * 16}}; - LNAGainField field_lna{ - {15 * 8, 0 * 16}}; - VGAGainField field_vga{ - {18 * 8, 0 * 16}}; - RSSI rssi{ - {21 * 8, 3, 6 * 8, 4}}; - Audio audio{ - {21 * 8, 8, 6 * 8, 4}}; - RxFrequencyField field_frequency{ {0 * 8, 0 * 8}, nav_}; + + RFAmpField field_rf_amp{ + {11 * 8, 0 * 16}}; + LNAGainField field_lna{ + {13 * 8, 0 * 16}}; + VGAGainField field_vga{ + {16 * 8, 0 * 16}}; + + RSSI rssi{ + {19 * 8 - 4, 3, 6 * 8, 4}}; + Audio audio{ + {19 * 8 - 4, 8, 6 * 8, 4}}; + + NumberField field_squelch{ + {25 * 8, 0 * 16}, + 2, + {0, 99}, + 1, + ' '}; AudioVolumeField field_volume{ {28 * 8, 0 * 16}}; diff --git a/firmware/baseband/proc_pocsag.cpp b/firmware/baseband/proc_pocsag.cpp index 39e8d12f..b80207e5 100644 --- a/firmware/baseband/proc_pocsag.cpp +++ b/firmware/baseband/proc_pocsag.cpp @@ -24,12 +24,13 @@ #include "proc_pocsag.hpp" +#include "dsp_iir_config.hpp" #include "event_m4.hpp" -#include -#include #include #include +#include +#include void POCSAGProcessor::execute(const buffer_c8_t& buffer) { // This is called at 1500Hz @@ -41,9 +42,13 @@ void POCSAGProcessor::execute(const buffer_c8_t& buffer) { const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer); const auto channel_out = channel_filter.execute(decim_1_out, dst_buffer); auto audio = demod.execute(channel_out, audio_buffer); - smooth.Process(audio.p, audio.count); // Smooth the data to make decoding more accurate + + // Output audio pre-smoothing so squelch actually works. + // NB: It's useful to output *after* when debugging the smoothing filter. audio_output.write(audio); + // Smooth the data to make decoding more accurate. + smooth.Process(audio.p, audio.count); processDemodulatedSamples(audio.p, 16); extractFrames(); } @@ -71,8 +76,23 @@ int POCSAGProcessor::OnDataFrame(int len, int baud) { } void POCSAGProcessor::on_message(const Message* const message) { - if (message->id == Message::ID::POCSAGConfigure) - configure(); + switch (message->id) { + case Message::ID::POCSAGConfigure: + configure(); + break; + + case Message::ID::NBFMConfigure: { + auto config = reinterpret_cast(message); + audio_output.configure( + audio_24k_hpf_300hz_config, + audio_8k_deemph_300_6_config, + config->squelch_level / 100.0); + break; + } + + default: + break; + } } void POCSAGProcessor::configure() { @@ -91,13 +111,10 @@ void POCSAGProcessor::configure() { decim_1.configure(taps_11k0_decim_1.taps, 131072); channel_filter.configure(taps_11k0_channel.taps, 2); demod.configure(demod_input_fs, 4'500); // FSK +/- 4k5Hz. - // Smoothing should be roughly sample rate over max baud - // 24k / 3.2k is 7.5 - smooth.SetSize(8); - // TODO: support squelch? - // audio_output.configure(message.audio_hpf_config, message.audio_deemph_config, (float)message.squelch_level / 100.0); - audio_output.configure(false); + // Smoothing should be roughly sample rate over max baud + // 24k / 3.2k = 7.5 + smooth.SetSize(8); // Set up the frame extraction, limits of baud setFrameExtractParams(demod_input_fs, 4000, 300, 32); diff --git a/firmware/baseband/proc_pocsag.hpp b/firmware/baseband/proc_pocsag.hpp index 9858cd27..57253f2d 100644 --- a/firmware/baseband/proc_pocsag.hpp +++ b/firmware/baseband/proc_pocsag.hpp @@ -106,10 +106,10 @@ class SmoothVals { // Use a rolling smoothed value while processing the buffer for (int buffPos = 0; buffPos < numVals; ++buffPos) { - m_pos = (m_pos + 1); // Increment the position in the stored values + m_pos++; if (m_pos >= m_size) { m_pos = 0; - } // loop if reached the end of the stored values + } m_sumVal -= (CalcType)m_lastVals[m_pos]; // Subtract the oldest value m_lastVals[m_pos] = valBuff[buffPos]; // Store the new value diff --git a/firmware/common/dsp_iir_config.hpp b/firmware/common/dsp_iir_config.hpp index 83666cce..177475df 100644 --- a/firmware/common/dsp_iir_config.hpp +++ b/firmware/common/dsp_iir_config.hpp @@ -38,9 +38,7 @@ constexpr iir_biquad_config_t audio_48k_hpf_300hz_config{ // scipy.signal.butter(2, 300 / 12000.0, 'highpass', analog=False) constexpr iir_biquad_config_t audio_24k_hpf_300hz_config{ {0.94597686f, -1.89195371f, 0.94597686f}, - {1.00000000f, -1.88903308f, 0.89487434f} - -}; + {1.00000000f, -1.88903308f, 0.89487434f}}; // scipy.signal.butter(2, 30 / 12000.0, 'highpass', analog=False) constexpr iir_biquad_config_t audio_24k_hpf_30hz_config{ diff --git a/firmware/common/pocsag.cpp b/firmware/common/pocsag.cpp index 2ec60bc3..415537d0 100644 --- a/firmware/common/pocsag.cpp +++ b/firmware/common/pocsag.cpp @@ -225,30 +225,14 @@ void pocsag_encode(const MessageType type, BCHCode& BCH_code, const uint32_t fun } while (char_idx < message_size); } -// ------------------------------------------------------------------------------- -// Get the number of bits that differ between the two values. -// ------------------------------------------------------------------------------- -inline uint8_t bitsDiff(unsigned long left, unsigned long right) { - unsigned long xord = left ^ right; - uint8_t count = 0; - - for (int i = 0; i < 32; i++) { - if ((xord & 0x01) == 1) ++count; - xord = xord >> 1; - } - - return (count); +// ---------------------------------------------------------------------------- +// EccContainer +// ---------------------------------------------------------------------------- +EccContainer::EccContainer() { + setup_ecc(); } -// ------------------------------------------------------------------------------- -// ------------------------------------------------------------------------------- -static uint32_t ecs[32]; /* error correction sequence */ -static uint32_t bch[1025]; -static int eccSetup = 0; - -// ------------------------------------------------------------------------------- -// ------------------------------------------------------------------------------- -void setupecc() { +void EccContainer::setup_ecc() { unsigned int srr = 0x3b4; unsigned int i, n, j, k; @@ -311,23 +295,12 @@ void setupecc() { } } -// ------------------------------------------------------------------------------- -// ------------------------------------------------------------------------------- -inline int errorCorrection(uint32_t& val) { - // Set up the tables the first time - if (eccSetup == 0) { - setupecc(); - eccSetup = 1; - } - +int EccContainer::error_correct(uint32_t& val) { int i, synd, errl, acc, pari, ecc, b1, b2; errl = 0; pari = 0; - /* run through error detection and correction routine */ - - // for (i=0; i<=20; i++) ecc = 0; for (i = 31; i >= 11; --i) { if (val & (1 << i)) { @@ -336,7 +309,6 @@ inline int errorCorrection(uint32_t& val) { } } - // for (i=21; i<=30; i++) acc = 0; for (i = 10; i >= 1; --i) { acc = acc << 1; @@ -390,8 +362,8 @@ bool pocsag_decode_batch(const POCSAGPacket& batch, POCSAGState& state) { // Error correct twice. First time to fix any errors it can, // second time to count number of errors that couldn't be fixed. - errorCorrection(codeword); - auto error_count = errorCorrection(codeword); + state.ecc->error_correct(codeword); + auto error_count = state.ecc->error_correct(codeword); switch (state.mode) { case STATE_CLEAR: diff --git a/firmware/common/pocsag.hpp b/firmware/common/pocsag.hpp index 0c0de759..583fd015 100644 --- a/firmware/common/pocsag.hpp +++ b/firmware/common/pocsag.hpp @@ -56,23 +56,46 @@ enum MessageType : uint32_t { ALPHANUMERIC }; +/* Holds error correction arrays. This allows the arrays to + * be freed when POCSAG is not running, saving ~4kb of RAM. */ +class EccContainer { + public: + EccContainer(); + + EccContainer(const EccContainer&) = delete; + EccContainer(EccContainer&&) = delete; + EccContainer& operator=(const EccContainer&) = delete; + EccContainer& operator=(EccContainer&&) = delete; + + int error_correct(uint32_t& val); + + private: + void setup_ecc(); + + /* error correction sequence */ + uint32_t ecs[32]; + uint32_t bch[1025]; +}; + struct POCSAGState { - uint8_t codeword_index; - uint32_t function; - uint32_t address; + EccContainer* ecc = nullptr; + uint8_t codeword_index = 0; + uint32_t function = 0; + uint32_t address = 0; Mode mode = STATE_CLEAR; OutputType out_type = EMPTY; - uint32_t ascii_data; - uint32_t ascii_idx; - uint32_t errors; - std::string output; + uint32_t ascii_data = 0; + uint32_t ascii_idx = 0; + uint32_t errors = 0; + std::string output{}; }; const pocsag::BitRate pocsag_bitrates[4] = { pocsag::BitRate::FSK512, pocsag::BitRate::FSK1200, pocsag::BitRate::FSK2400, - pocsag::BitRate::FSK3200}; + pocsag::BitRate::FSK3200, +}; std::string bitrate_str(BitRate bitrate); std::string flag_str(PacketFlag packetflag);