From 5d602ece5cb760bee5bdd6af44e5e38bb78c67a2 Mon Sep 17 00:00:00 2001 From: Kyle Reed <3761006+kallanreed@users.noreply.github.com> Date: Wed, 30 Aug 2023 23:05:49 -0700 Subject: [PATCH] Add second pocsag proc for experimenting (#1428) --- firmware/application/apps/pocsag_app.cpp | 10 +- firmware/application/apps/pocsag_app.hpp | 29 +- firmware/baseband/CMakeLists.txt | 7 + firmware/baseband/dsp_squelch.cpp | 6 + firmware/baseband/dsp_squelch.hpp | 3 + firmware/baseband/proc_pocsag.cpp | 39 +- firmware/baseband/proc_pocsag.hpp | 4 +- firmware/baseband/proc_pocsag2.cpp | 524 +++++++++++++++++++++++ firmware/baseband/proc_pocsag2.hpp | 227 ++++++++++ firmware/common/spi_image.hpp | 1 + 10 files changed, 820 insertions(+), 30 deletions(-) create mode 100644 firmware/baseband/proc_pocsag2.cpp create mode 100644 firmware/baseband/proc_pocsag2.hpp diff --git a/firmware/application/apps/pocsag_app.cpp b/firmware/application/apps/pocsag_app.cpp index 9d862761..efa82a65 100644 --- a/firmware/application/apps/pocsag_app.cpp +++ b/firmware/application/apps/pocsag_app.cpp @@ -54,7 +54,8 @@ POCSAGSettingsView::POCSAGSettingsView( POCSAGSettings& settings) : settings_{settings} { add_children( - {&check_log, + {&check_beta, + &check_log, &check_log_raw, &check_small_font, &check_hide_bad, @@ -63,6 +64,7 @@ POCSAGSettingsView::POCSAGSettingsView( &field_ignore, &button_save}); + check_beta.set_value(settings_.use_new_proc); check_log.set_value(settings_.enable_logging); check_log_raw.set_value(settings_.enable_raw_log); check_small_font.set_value(settings_.enable_small_font); @@ -72,6 +74,7 @@ POCSAGSettingsView::POCSAGSettingsView( field_ignore.set_value(settings_.address_to_ignore); button_save.on_select = [this, &nav](Button&) { + settings_.use_new_proc = check_beta.value(); settings_.enable_logging = check_log.value(); settings_.enable_raw_log = check_log_raw.value(); settings_.enable_small_font = check_small_font.value(); @@ -86,7 +89,10 @@ POCSAGSettingsView::POCSAGSettingsView( POCSAGAppView::POCSAGAppView(NavigationView& nav) : nav_{nav} { - baseband::run_image(portapack::spi_flash::image_tag_pocsag); + if (settings_.use_new_proc) + baseband::run_image(portapack::spi_flash::image_tag_pocsag2); + else + baseband::run_image(portapack::spi_flash::image_tag_pocsag); add_children( {&rssi, diff --git a/firmware/application/apps/pocsag_app.hpp b/firmware/application/apps/pocsag_app.hpp index a52681fd..9cb88d65 100644 --- a/firmware/application/apps/pocsag_app.hpp +++ b/firmware/application/apps/pocsag_app.hpp @@ -60,6 +60,7 @@ struct POCSAGSettings { bool hide_bad_data = false; bool hide_addr_only = false; uint32_t address_to_ignore = 0; + bool use_new_proc = false; }; class POCSAGSettingsView : public View { @@ -67,45 +68,46 @@ class POCSAGSettingsView : public View { POCSAGSettingsView(NavigationView& nav, POCSAGSettings& settings); std::string title() const override { return "POCSAG Config"; }; + void focus() override { button_save.focus(); } private: POCSAGSettings& settings_; + Checkbox check_beta{ + {0 * 8 + 2, 18 * 16 - 4}, + 6, + "Beta", + true /*small*/}; + Checkbox check_log{ {2 * 8, 2 * 16}, 10, - "Enable Log", - false}; + "Enable Log"}; Checkbox check_log_raw{ {2 * 8, 4 * 16}, 12, - "Log Raw Data", - false}; + "Log Raw Data"}; Checkbox check_small_font{ {2 * 8, 6 * 16}, 4, - "Use Small Font", - false}; + "Use Small Font"}; Checkbox check_hide_bad{ {2 * 8, 8 * 16}, 22, - "Hide Bad Data", - false}; + "Hide Bad Data"}; Checkbox check_hide_addr_only{ {2 * 8, 10 * 16}, 22, - "Hide Addr Only", - false}; + "Hide Addr Only"}; Checkbox check_ignore{ {2 * 8, 12 * 16}, 22, - "Enable Ignored Address", - false}; + "Enable Ignored Address"}; NumberField field_ignore{ {7 * 8, 13 * 16 + 8}, @@ -115,7 +117,7 @@ class POCSAGSettingsView : public View { '0'}; Button button_save{ - {12 * 8, 16 * 16, 10 * 8, 2 * 16}, + {11 * 8, 16 * 16, 10 * 8, 2 * 16}, "Save"}; }; @@ -150,6 +152,7 @@ class POCSAGAppView : public View { {"address_to_ignore"sv, &settings_.address_to_ignore}, {"hide_bad_data"sv, &settings_.hide_bad_data}, {"hide_addr_only"sv, &settings_.hide_addr_only}, + {"use_new_proc"sv, &settings_.use_new_proc}, }}; void refresh_ui(); diff --git a/firmware/baseband/CMakeLists.txt b/firmware/baseband/CMakeLists.txt index 1721bb9a..9dfcf086 100644 --- a/firmware/baseband/CMakeLists.txt +++ b/firmware/baseband/CMakeLists.txt @@ -444,6 +444,13 @@ set(MODE_CPPSRC ) DeclareTargets(PPOC pocsag) +### POCSAG2 RX + +set(MODE_CPPSRC + proc_pocsag2.cpp +) +DeclareTargets(PPOC pocsag2) + ### RDS set(MODE_CPPSRC diff --git a/firmware/baseband/dsp_squelch.cpp b/firmware/baseband/dsp_squelch.cpp index 7525e09e..cb87189c 100644 --- a/firmware/baseband/dsp_squelch.cpp +++ b/firmware/baseband/dsp_squelch.cpp @@ -36,6 +36,7 @@ bool FMSquelch::execute(const buffer_f32_t& audio) { squelch_energy_buffer.size()}; non_audio_hpf.execute(audio, squelch_energy); + // "Non-audio" implies "noise" here. Find the loudest noise sample. float non_audio_max_squared = 0; for (const auto sample : squelch_energy_buffer) { const float sample_squared = sample * sample; @@ -44,9 +45,14 @@ bool FMSquelch::execute(const buffer_f32_t& audio) { } } + // Is the noise less than the threshold? return (non_audio_max_squared < threshold_squared); } void FMSquelch::set_threshold(const float new_value) { threshold_squared = new_value * new_value; } + +bool FMSquelch::enabled() const { + return threshold_squared > 0.0; +} diff --git a/firmware/baseband/dsp_squelch.hpp b/firmware/baseband/dsp_squelch.hpp index 6b69a5bf..37a046a3 100644 --- a/firmware/baseband/dsp_squelch.hpp +++ b/firmware/baseband/dsp_squelch.hpp @@ -31,9 +31,12 @@ class FMSquelch { public: + /* Check if noise level is lower than threshold. + * Returns true if noise is below threshold. */ bool execute(const buffer_f32_t& audio); void set_threshold(const float new_value); + bool enabled() const; private: static constexpr size_t N = 32; diff --git a/firmware/baseband/proc_pocsag.cpp b/firmware/baseband/proc_pocsag.cpp index 48ec3959..24878789 100644 --- a/firmware/baseband/proc_pocsag.cpp +++ b/firmware/baseband/proc_pocsag.cpp @@ -3,6 +3,7 @@ * Copyright (C) 2012-2014 Elias Oenal (multimon-ng@eliasoenal.com) * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. * Copyright (C) 2016 Furrtek + * Copyright (C) 2016 Kyle Reed * * This file is part of PortaPack. * @@ -41,16 +42,29 @@ void POCSAGProcessor::execute(const buffer_c8_t& buffer) { const auto channel_out = channel_filter.execute(decim_1_out, dst_buffer); auto audio = demod.execute(channel_out, audio_buffer); - // When use_squelch_ is true, AudioOutput applies filters - // which should eliminate the need to do this smoothing. - if (use_squelch_ == false) - smooth.Process(audio.p, audio.count); - - // NB: This applies audio filters in-place when use_squelch_ is true. - audio_output.write(audio); + // If squelching, check for audio before smoothing because smoothing + // causes the squelch noise detection to fail. Likely because squelch + // looks for HF noise and smoothing is basically a lowpass filter. + // NB: Squelch in this processor is only for the the audio output. + // Squelching will likely drop data "noise" and break processing. + if (squelch_.enabled()) { + bool has_audio = squelch_.execute(audio); + squelch_history = (squelch_history << 1) | (has_audio ? 1 : 0); + } + smooth.Process(audio.p, audio.count); processDemodulatedSamples(audio.p, 16); extractFrames(); + + // Clear the output before sending to audio chip. + // Only clear the audio buffer when there hasn't been any audio for a while. + if (squelch_.enabled() && squelch_history == 0) { + for (size_t i = 0; i < audio.count; ++i) { + audio.p[i] = 0.0; + } + } + + audio_output.write(audio); } // ==================================================================== @@ -83,13 +97,7 @@ void POCSAGProcessor::on_message(const Message* const message) { case Message::ID::NBFMConfigure: { auto config = reinterpret_cast(message); - - use_squelch_ = config->squelch_level > 0; - audio_output.configure( - audio_24k_hpf_300hz_config, - audio_24k_lpf_2400hz_config, - config->squelch_level / 100.0); - audio_output.configure(use_squelch_); + squelch_.set_threshold(config->squelch_level / 100.0); break; } @@ -119,6 +127,9 @@ void POCSAGProcessor::configure() { // 24k / 3.2k = 7.5 smooth.SetSize(8); + // Don't have audio process the stream. + audio_output.configure(false); + // 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 947ec714..e520bea7 100644 --- a/firmware/baseband/proc_pocsag.hpp +++ b/firmware/baseband/proc_pocsag.hpp @@ -3,6 +3,7 @@ * Copyright (C) 2012-2014 Elias Oenal (multimon-ng@eliasoenal.com) * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. * Copyright (C) 2016 Furrtek + * Copyright (C) 2023 Kyle Reed * * This file is part of PortaPack. * @@ -215,7 +216,8 @@ class POCSAGProcessor : public BasebandProcessor { int m_numCode{0}; bool m_inverted{false}; - bool use_squelch_ = false; + FMSquelch squelch_{}; + uint64_t squelch_history = 0; /* NB: Threads should be the last members in the class definition. */ BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive}; diff --git a/firmware/baseband/proc_pocsag2.cpp b/firmware/baseband/proc_pocsag2.cpp new file mode 100644 index 00000000..9615fe78 --- /dev/null +++ b/firmware/baseband/proc_pocsag2.cpp @@ -0,0 +1,524 @@ +/* + * Copyright (C) 1996 Thomas Sailer (sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu) + * Copyright (C) 2012-2014 Elias Oenal (multimon-ng@eliasoenal.com) + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * Copyright (C) 2016 Kyle Reed + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "proc_pocsag2.hpp" + +#include "dsp_iir_config.hpp" +#include "event_m4.hpp" + +#include +#include +#include +#include + +void POCSAGProcessor::execute(const buffer_c8_t& buffer) { + if (!configured) return; + + // Get 24kHz audio + const auto decim_0_out = decim_0.execute(buffer, dst_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); + + // If squelching, check for audio before smoothing because smoothing + // causes the squelch noise detection to fail. Likely because squelch + // looks for HF noise and smoothing is basically a lowpass filter. + // NB: Squelch in this processor is only for the the audio output. + // Squelching will likely drop data "noise" and break processing. + if (squelch_.enabled()) { + bool has_audio = squelch_.execute(audio); + squelch_history = (squelch_history << 1) | (has_audio ? 1 : 0); + } + + smooth.Process(audio.p, audio.count); + processDemodulatedSamples(audio.p, 16); + extractFrames(); + + // Clear the output before sending to audio chip. + if (squelch_.enabled() && squelch_history == 0) { + for (size_t i = 0; i < audio.count; ++i) { + audio.p[i] = 0.0; + } + } + + audio_output.write(audio); +} + +// ==================================================================== +// +// ==================================================================== +int POCSAGProcessor::OnDataWord(uint32_t word, int pos) { + packet.set(pos, word); + return 0; +} + +// ==================================================================== +// +// ==================================================================== +int POCSAGProcessor::OnDataFrame(int len, int baud) { + if (len > 0) { + packet.set_bitrate(baud); + packet.set_flag(pocsag::PacketFlag::NORMAL); + packet.set_timestamp(Timestamp::now()); + const POCSAGPacketMessage message(packet); + shared_memory.application_queue.push(message); + } + return 0; +} + +void POCSAGProcessor::on_message(const Message* const message) { + switch (message->id) { + case Message::ID::POCSAGConfigure: + configure(); + break; + + case Message::ID::NBFMConfigure: { + auto config = reinterpret_cast(message); + squelch_.set_threshold(config->squelch_level / 100.0); + break; + } + + default: + break; + } +} + +void POCSAGProcessor::configure() { + constexpr size_t decim_0_input_fs = baseband_fs; + constexpr size_t decim_0_output_fs = decim_0_input_fs / decim_0.decimation_factor; + + constexpr size_t decim_1_input_fs = decim_0_output_fs; + constexpr size_t decim_1_output_fs = decim_1_input_fs / decim_1.decimation_factor; + + constexpr size_t channel_filter_input_fs = decim_1_output_fs; + const size_t channel_filter_output_fs = channel_filter_input_fs / 2; + + const size_t demod_input_fs = channel_filter_output_fs; + + decim_0.configure(taps_11k0_decim_0.taps); + decim_1.configure(taps_11k0_decim_1.taps); + 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 = 7.5 + smooth.SetSize(8); + + // Don't have audio process the stream. + audio_output.configure(false); + + // Set up the frame extraction, limits of baud + setFrameExtractParams(demod_input_fs, 4000, 300, 32); + + // Mark the class as ready to accept data + configured = true; +} + +// ----------------------------- +// Frame extractraction methods +// ----------------------------- +#define BAUD_STABLE (104) +#define MAX_CONSEC_SAME (32) +#define MAX_WITHOUT_SINGLE (64) +#define MAX_BAD_TRANS (10) + +#define M_SYNC (0x7cd215d8) +#define M_NOTSYNC (0x832dea27) + +#define M_IDLE (0x7a89c197) + +// ==================================================================== +// +// ==================================================================== +inline int bitsDiff(unsigned long left, unsigned long right) { + unsigned long xord = left ^ right; + int count = 0; + for (int i = 0; i < 32; i++) { + if ((xord & 0x01) != 0) ++count; + xord = xord >> 1; + } + return (count); +} + +// ==================================================================== +// +// ==================================================================== +void POCSAGProcessor::initFrameExtraction() { + m_averageSymbolLen_1024 = m_maxSymSamples_1024; + m_lastStableSymbolLen_1024 = m_minSymSamples_1024; + + m_badTransitions = 0; + m_bitsStart = 0; + m_bitsEnd = 0; + m_inverted = false; + + resetVals(); +} + +// ==================================================================== +// +// ==================================================================== +void POCSAGProcessor::resetVals() { + // Reset the parameters + // -------------------- + m_goodTransitions = 0; + m_badTransitions = 0; + m_averageSymbolLen_1024 = m_maxSymSamples_1024; + m_shortestGoodTrans_1024 = m_maxSymSamples_1024; + m_valMid = 0; + + // And reset the counts + // -------------------- + m_lastTransPos_1024 = 0; + m_lastBitPos_1024 = 0; + m_lastSample = 0; + m_sampleNo = 0; + m_nextBitPos_1024 = m_maxSymSamples_1024; + m_nextBitPosInt = (long)m_nextBitPos_1024; + + // Extraction + m_fifo.numBits = 0; + m_gotSync = false; + m_numCode = 0; +} + +// ==================================================================== +// +// ==================================================================== +void POCSAGProcessor::setFrameExtractParams(long a_samplesPerSec, long a_maxBaud, long a_minBaud, long maxRunOfSameValue) { + m_samplesPerSec = a_samplesPerSec; + m_minSymSamples_1024 = (uint32_t)(1024.0f * (float)a_samplesPerSec / (float)a_maxBaud); + m_maxSymSamples_1024 = (uint32_t)(1024.0f * (float)a_samplesPerSec / (float)a_minBaud); + m_maxRunOfSameValue = maxRunOfSameValue; + + m_shortestGoodTrans_1024 = m_maxSymSamples_1024; + m_averageSymbolLen_1024 = m_maxSymSamples_1024; + m_lastStableSymbolLen_1024 = m_minSymSamples_1024; + + m_nextBitPos_1024 = m_averageSymbolLen_1024 / 2; + m_nextBitPosInt = m_nextBitPos_1024 >> 10; + + initFrameExtraction(); +} + +// ==================================================================== +// +// ==================================================================== +int POCSAGProcessor::processDemodulatedSamples(float* sampleBuff, int noOfSamples) { + bool transition = false; + uint32_t samplePos_1024 = 0; + uint32_t len_1024 = 0; + + // Loop through the block of data + // ------------------------------ + for (int pos = 0; pos < noOfSamples; ++pos) { + m_sample = sampleBuff[pos]; + m_valMid += (m_sample - m_valMid) / 1024.0f; + + ++m_sampleNo; + + // Detect Transition + // ----------------- + transition = !((m_lastSample < m_valMid) ^ (m_sample >= m_valMid)); // use XOR for speed + + // If this is a transition + // ----------------------- + if (transition) { + // Calculate samples since last trans + // ---------------------------------- + int32_t fractional_1024 = (int32_t)(((m_sample - m_valMid) * 1024) / (m_sample - m_lastSample)); + if (fractional_1024 < 0) { + fractional_1024 = -fractional_1024; + } + + samplePos_1024 = (m_sampleNo << 10) - fractional_1024; + len_1024 = samplePos_1024 - m_lastTransPos_1024; + m_lastTransPos_1024 = samplePos_1024; + + // If symbol is large enough to be valid + // ------------------------------------- + if (len_1024 > m_minSymSamples_1024) { + // Check for shortest good transition + // ---------------------------------- + if ((len_1024 < m_shortestGoodTrans_1024) && + (m_goodTransitions < BAUD_STABLE)) // detect change of symbol size + { + int32_t fractionOfShortest_1024 = (len_1024 << 10) / m_shortestGoodTrans_1024; + + // If currently at half the baud rate + // ---------------------------------- + if ((fractionOfShortest_1024 > 410) && (fractionOfShortest_1024 < 614)) // 0.4 and 0.6 + { + m_averageSymbolLen_1024 /= 2; + m_shortestGoodTrans_1024 = len_1024; + } + // If currently at the wrong baud rate + // ----------------------------------- + else if (fractionOfShortest_1024 < 768) // 0.75 + { + m_averageSymbolLen_1024 = len_1024; + m_shortestGoodTrans_1024 = len_1024; + m_goodTransitions = 0; + m_lastSingleBitPos_1024 = samplePos_1024 - len_1024; + } + } + + // Calc the number of bits since events + // ------------------------------------ + int32_t halfSymbol_1024 = m_averageSymbolLen_1024 / 2; + int bitsSinceLastTrans = max((uint32_t)1, (len_1024 + halfSymbol_1024) / m_averageSymbolLen_1024); + int bitsSinceLastSingle = (((m_sampleNo << 10) - m_lastSingleBitPos_1024) + halfSymbol_1024) / m_averageSymbolLen_1024; + + // Check for single bit + // -------------------- + if (bitsSinceLastTrans == 1) { + m_lastSingleBitPos_1024 = samplePos_1024; + } + + // If too long since last transition + // --------------------------------- + if (bitsSinceLastTrans > MAX_CONSEC_SAME) { + resetVals(); + } + // If too long sice last single bit + // -------------------------------- + else if (bitsSinceLastSingle > MAX_WITHOUT_SINGLE) { + resetVals(); + } else { + // If this is a good transition + // ---------------------------- + int32_t offsetFromExtectedTransition_1024 = len_1024 - (bitsSinceLastTrans * m_averageSymbolLen_1024); + if (offsetFromExtectedTransition_1024 < 0) { + offsetFromExtectedTransition_1024 = -offsetFromExtectedTransition_1024; + } + if (offsetFromExtectedTransition_1024 < ((int32_t)m_averageSymbolLen_1024 / 4)) // Has to be within 1/4 of symbol to be good + { + ++m_goodTransitions; + uint32_t bitsCount = min((uint32_t)BAUD_STABLE, m_goodTransitions); + + uint32_t propFromPrevious = m_averageSymbolLen_1024 * bitsCount; + uint32_t propFromCurrent = (len_1024 / bitsSinceLastTrans); + m_averageSymbolLen_1024 = (propFromPrevious + propFromCurrent) / (bitsCount + 1); + m_badTransitions = 0; + // if ( len < m_shortestGoodTrans ){m_shortestGoodTrans = len;} + // Store the old symbol size + if (m_goodTransitions >= BAUD_STABLE) { + m_lastStableSymbolLen_1024 = m_averageSymbolLen_1024; + } + } + } + + // Set the point of the last bit if not yet stable + // ----------------------------------------------- + if ((m_goodTransitions < BAUD_STABLE) || (m_badTransitions > 0)) { + m_lastBitPos_1024 = samplePos_1024 - (m_averageSymbolLen_1024 / 2); + } + + // Calculate the exact positiom of the next bit + // -------------------------------------------- + int32_t thisPlusHalfsymbol_1024 = samplePos_1024 + (m_averageSymbolLen_1024 / 2); + int32_t lastPlusSymbol = m_lastBitPos_1024 + m_averageSymbolLen_1024; + m_nextBitPos_1024 = lastPlusSymbol + ((thisPlusHalfsymbol_1024 - lastPlusSymbol) / 16); + + // Check for bad pos error + // ----------------------- + if (m_nextBitPos_1024 < samplePos_1024) m_nextBitPos_1024 += m_averageSymbolLen_1024; + + // Calculate integer sample after next bit + // --------------------------------------- + m_nextBitPosInt = (m_nextBitPos_1024 >> 10) + 1; + + } // symbol is large enough to be valid + else { + // Bad transition, so reset the counts + // ----------------------------------- + ++m_badTransitions; + if (m_badTransitions > MAX_BAD_TRANS) { + resetVals(); + } + } + } // end of if transition + + // Reached the point of the next bit + // --------------------------------- + if (m_sampleNo >= m_nextBitPosInt) { + // Everything is good so extract a bit + // ----------------------------------- + if (m_goodTransitions > 20) { + // Store value at the center of bit + // -------------------------------- + storeBit(); + } + // Check for long 1 or zero + // ------------------------ + uint32_t bitsSinceLastTrans = ((m_sampleNo << 10) - m_lastTransPos_1024) / m_averageSymbolLen_1024; + if (bitsSinceLastTrans > m_maxRunOfSameValue) { + resetVals(); + } + + // Store the point of the last bit + // ------------------------------- + m_lastBitPos_1024 = m_nextBitPos_1024; + + // Calculate the exact point of the next bit + // ----------------------------------------- + m_nextBitPos_1024 += m_averageSymbolLen_1024; + + // Look for the bit after the next bit pos + // --------------------------------------- + m_nextBitPosInt = (m_nextBitPos_1024 >> 10) + 1; + + } // Reached the point of the next bit + + m_lastSample = m_sample; + + } // Loop through the block of data + + return getNoOfBits(); +} + +// ==================================================================== +// +// ==================================================================== +void POCSAGProcessor::storeBit() { + if (++m_bitsStart >= BIT_BUF_SIZE) { + m_bitsStart = 0; + } + + // Calculate the bit value + float sample = (m_sample + m_lastSample) / 2; + // int32_t sample_1024 = m_sample_1024; + bool bit = sample > m_valMid; + + // If buffer not full + if (m_bitsStart != m_bitsEnd) { + // Decide on output val + if (bit) { + m_bits[m_bitsStart] = 0; + } else { + m_bits[m_bitsStart] = 1; + } + } + // Throw away bits if the buffer is full + else { + if (--m_bitsStart <= -1) { + m_bitsStart = BIT_BUF_SIZE - 1; + } + } +} + +// ==================================================================== +// +// ==================================================================== +int POCSAGProcessor::extractFrames() { + int msgCnt = 0; + // While there is unread data in the bits buffer + //---------------------------------------------- + while (getNoOfBits() > 0) { + m_fifo.codeword = (m_fifo.codeword << 1) + getBit(); + m_fifo.numBits++; + + // If number of bits in fifo equals 32 + //------------------------------------ + if (m_fifo.numBits >= 32) { + // Not got sync + // ------------ + if (!m_gotSync) { + if (bitsDiff(m_fifo.codeword, M_SYNC) <= 2) { + m_inverted = false; + m_gotSync = true; + m_numCode = -1; + m_fifo.numBits = 0; + } else if (bitsDiff(m_fifo.codeword, M_NOTSYNC) <= 2) { + m_inverted = true; + m_gotSync = true; + m_numCode = -1; + m_fifo.numBits = 0; + } else { + // Cause it to load one more bit + m_fifo.numBits = 31; + } + } // Not got sync + else { + // Increment the word count + // ------------------------ + ++m_numCode; // It got set to -1 when a sync was found, now count the 16 words + uint32_t val = m_inverted ? ~m_fifo.codeword : m_fifo.codeword; + OnDataWord(val, m_numCode); + + // If at the end of a 16 word block + // -------------------------------- + if (m_numCode >= 15) { + msgCnt += OnDataFrame(m_numCode + 1, (m_samplesPerSec << 10) / m_lastStableSymbolLen_1024); + m_gotSync = false; + m_numCode = -1; + } + m_fifo.numBits = 0; + } + } // If number of bits in fifo equals 32 + } // While there is unread data in the bits buffer + return msgCnt; +} // extractFrames + +// ==================================================================== +// +// ==================================================================== +short POCSAGProcessor::getBit() { + if (m_bitsEnd != m_bitsStart) { + if (++m_bitsEnd >= BIT_BUF_SIZE) { + m_bitsEnd = 0; + } + return m_bits[m_bitsEnd]; + } else { + return -1; + } +} + +// ==================================================================== +// +// ==================================================================== +int POCSAGProcessor::getNoOfBits() { + int bits = m_bitsEnd - m_bitsStart; + if (bits < 0) { + bits += BIT_BUF_SIZE; + } + return bits; +} + +// ==================================================================== +// +// ==================================================================== +uint32_t POCSAGProcessor::getRate() { + return ((m_samplesPerSec << 10) + 512) / m_lastStableSymbolLen_1024; +} + +// ==================================================================== +// +// ==================================================================== +int main() { + EventDispatcher event_dispatcher{std::make_unique()}; + event_dispatcher.run(); + return 0; +} diff --git a/firmware/baseband/proc_pocsag2.hpp b/firmware/baseband/proc_pocsag2.hpp new file mode 100644 index 00000000..609d4541 --- /dev/null +++ b/firmware/baseband/proc_pocsag2.hpp @@ -0,0 +1,227 @@ +/* + * Copyright (C) 1996 Thomas Sailer (sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu) + * Copyright (C) 2012-2014 Elias Oenal (multimon-ng@eliasoenal.com) + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * Copyright (C) 2023 Kyle Reed + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PROC_POCSAG2_H__ +#define __PROC_POCSAG2_H__ + +#include "baseband_processor.hpp" +#include "baseband_thread.hpp" +#include "rssi_thread.hpp" + +#include "dsp_decimate.hpp" +#include "dsp_demodulate.hpp" + +#include "pocsag_packet.hpp" + +#include "pocsag.hpp" +#include "message.hpp" +#include "audio_output.hpp" +#include "portapack_shared_memory.hpp" + +#include +#include +using namespace std; + +// Class used to smooth demodulated waveform prior to decoding +// ----------------------------------------------------------- +template +class SmoothVals { + protected: + ValType* m_lastVals; // Previous N values + int m_size; // The size N + CalcType m_sumVal; // Running sum of lastVals + int m_pos; // Current position in last vals ring buffer + int m_count; // + + public: + SmoothVals() + : m_lastVals(NULL), m_size(1), m_sumVal(0), m_pos(0), m_count(0) { + m_lastVals = new ValType[m_size]; + } + + // -------------------------------------------------- + // -------------------------------------------------- + virtual ~SmoothVals() { + delete[] m_lastVals; + } + + SmoothVals(const SmoothVals&) { + } + + SmoothVals& operator=(const SmoothVals&) { + return *this; + } + + // -------------------------------------------------- + // Set size of smoothing + // -------------------------------------------------- + void SetSize(int size) { + m_size = std::max(size, 1); + m_pos = 0; + delete[] m_lastVals; + m_lastVals = new ValType[m_size]; + m_sumVal = 0; + } + + // -------------------------------------------------- + // Get size of smoothing + // -------------------------------------------------- + int Size() { return m_size; } + + // -------------------------------------------------- + // In place processing + // -------------------------------------------------- + void Process(ValType* valBuff, int numVals) { + ValType tmpVal; + + if (m_count > (1024 * 10)) { + // Recalculate the sum value occasionaly, stops accumulated errors when using float + m_count = 0; + m_sumVal = 0; + for (int i = 0; i < m_size; ++i) { + m_sumVal += (CalcType)m_lastVals[i]; + } + } + + // Use a rolling smoothed value while processing the buffer + for (int buffPos = 0; buffPos < numVals; ++buffPos) { + m_pos++; + if (m_pos >= m_size) { + m_pos = 0; + } + + m_sumVal -= (CalcType)m_lastVals[m_pos]; // Subtract the oldest value + m_lastVals[m_pos] = valBuff[buffPos]; // Store the new value + m_sumVal += (CalcType)m_lastVals[m_pos]; // Add on the new value + + tmpVal = (ValType)(m_sumVal / m_size); // Scale by number of values smoothed + valBuff[buffPos] = tmpVal; + } + + m_count += numVals; + } +}; + +// -------------------------------------------------- +// Class to process base band data to pocsag frames +// -------------------------------------------------- +class POCSAGProcessor : public BasebandProcessor { + public: + void execute(const buffer_c8_t& buffer) override; + void on_message(const Message* const message) override; + + int OnDataFrame(int len, int baud); + int OnDataWord(uint32_t word, int pos); + + private: + static constexpr size_t baseband_fs = 3072000; + + std::array dst{}; + const buffer_c16_t dst_buffer{ + dst.data(), + dst.size()}; + std::array audio{}; + const buffer_f32_t audio_buffer{ + audio.data(), + audio.size()}; + + dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0{}; + dsp::decimate::FIRC16xR16x32Decim8 decim_1{}; + dsp::decimate::FIRAndDecimateComplex channel_filter{}; + dsp::demodulate::FM demod{}; + SmoothVals smooth = {}; + + AudioOutput audio_output{}; + + bool configured = false; + pocsag::POCSAGPacket packet{}; + + void configure(); + + // ---------------------------------------- + // Frame extractraction methods and members + // ---------------------------------------- + void initFrameExtraction(); + struct FIFOStruct { + unsigned long codeword; + int numBits; + }; + +#define BIT_BUF_SIZE (64) + + void resetVals(); + void setFrameExtractParams(long a_samplesPerSec, long a_maxBaud = 8000, long a_minBaud = 200, long maxRunOfSameValue = 32); + + int processDemodulatedSamples(float* sampleBuff, int noOfSamples); + int extractFrames(); + + void storeBit(); + short getBit(); + + int getNoOfBits(); + uint32_t getRate(); + + uint32_t m_averageSymbolLen_1024{0}; + uint32_t m_lastStableSymbolLen_1024{0}; + + uint32_t m_samplesPerSec{0}; + uint32_t m_goodTransitions{0}; + uint32_t m_badTransitions{0}; + + uint32_t m_sampleNo{0}; + float m_sample{0}; + float m_valMid{0.0f}; + float m_lastSample{0.0f}; + + uint32_t m_lastTransPos_1024{0}; + uint32_t m_lastSingleBitPos_1024{0}; + + uint32_t m_nextBitPosInt{0}; // Integer rounded up version to save on ops + uint32_t m_nextBitPos_1024{0}; + uint32_t m_lastBitPos_1024{0}; + + uint32_t m_shortestGoodTrans_1024{0}; + uint32_t m_minSymSamples_1024{0}; + uint32_t m_maxSymSamples_1024{0}; + uint32_t m_maxRunOfSameValue{0}; + + bitset<(size_t)BIT_BUF_SIZE> m_bits{0}; + long m_bitsStart{0}; + long m_bitsEnd{0}; + + FIFOStruct m_fifo{0, 0}; + bool m_gotSync{false}; + int m_numCode{0}; + bool m_inverted{false}; + + FMSquelch squelch_{}; + uint64_t squelch_history = 0; + + /* NB: Threads should be the last members in the class definition. */ + BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive}; + RSSIThread rssi_thread{}; +}; + +#endif /*__PROC_POCSAG2_H__*/ diff --git a/firmware/common/spi_image.hpp b/firmware/common/spi_image.hpp index 61d5bb78..dafa970c 100644 --- a/firmware/common/spi_image.hpp +++ b/firmware/common/spi_image.hpp @@ -86,6 +86,7 @@ constexpr image_tag_t image_tag_capture{'P', 'C', 'A', 'P'}; constexpr image_tag_t image_tag_ert{'P', 'E', 'R', 'T'}; constexpr image_tag_t image_tag_nfm_audio{'P', 'N', 'F', 'M'}; constexpr image_tag_t image_tag_pocsag{'P', 'P', 'O', 'C'}; +constexpr image_tag_t image_tag_pocsag2{'P', 'P', 'O', '2'}; constexpr image_tag_t image_tag_sonde{'P', 'S', 'O', 'N'}; constexpr image_tag_t image_tag_tpms{'P', 'T', 'P', 'M'}; constexpr image_tag_t image_tag_wfm_audio{'P', 'W', 'F', 'M'};