Add second pocsag proc for experimenting (#1428)

This commit is contained in:
Kyle Reed 2023-08-30 23:05:49 -07:00 committed by GitHub
parent 900086c1c9
commit 5d602ece5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 820 additions and 30 deletions

View File

@ -54,7 +54,8 @@ POCSAGSettingsView::POCSAGSettingsView(
POCSAGSettings& settings) POCSAGSettings& settings)
: settings_{settings} { : settings_{settings} {
add_children( add_children(
{&check_log, {&check_beta,
&check_log,
&check_log_raw, &check_log_raw,
&check_small_font, &check_small_font,
&check_hide_bad, &check_hide_bad,
@ -63,6 +64,7 @@ POCSAGSettingsView::POCSAGSettingsView(
&field_ignore, &field_ignore,
&button_save}); &button_save});
check_beta.set_value(settings_.use_new_proc);
check_log.set_value(settings_.enable_logging); check_log.set_value(settings_.enable_logging);
check_log_raw.set_value(settings_.enable_raw_log); check_log_raw.set_value(settings_.enable_raw_log);
check_small_font.set_value(settings_.enable_small_font); check_small_font.set_value(settings_.enable_small_font);
@ -72,6 +74,7 @@ POCSAGSettingsView::POCSAGSettingsView(
field_ignore.set_value(settings_.address_to_ignore); field_ignore.set_value(settings_.address_to_ignore);
button_save.on_select = [this, &nav](Button&) { button_save.on_select = [this, &nav](Button&) {
settings_.use_new_proc = check_beta.value();
settings_.enable_logging = check_log.value(); settings_.enable_logging = check_log.value();
settings_.enable_raw_log = check_log_raw.value(); settings_.enable_raw_log = check_log_raw.value();
settings_.enable_small_font = check_small_font.value(); settings_.enable_small_font = check_small_font.value();
@ -86,7 +89,10 @@ POCSAGSettingsView::POCSAGSettingsView(
POCSAGAppView::POCSAGAppView(NavigationView& nav) POCSAGAppView::POCSAGAppView(NavigationView& nav)
: nav_{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( add_children(
{&rssi, {&rssi,

View File

@ -60,6 +60,7 @@ struct POCSAGSettings {
bool hide_bad_data = false; bool hide_bad_data = false;
bool hide_addr_only = false; bool hide_addr_only = false;
uint32_t address_to_ignore = 0; uint32_t address_to_ignore = 0;
bool use_new_proc = false;
}; };
class POCSAGSettingsView : public View { class POCSAGSettingsView : public View {
@ -67,45 +68,46 @@ class POCSAGSettingsView : public View {
POCSAGSettingsView(NavigationView& nav, POCSAGSettings& settings); POCSAGSettingsView(NavigationView& nav, POCSAGSettings& settings);
std::string title() const override { return "POCSAG Config"; }; std::string title() const override { return "POCSAG Config"; };
void focus() override { button_save.focus(); }
private: private:
POCSAGSettings& settings_; POCSAGSettings& settings_;
Checkbox check_beta{
{0 * 8 + 2, 18 * 16 - 4},
6,
"Beta",
true /*small*/};
Checkbox check_log{ Checkbox check_log{
{2 * 8, 2 * 16}, {2 * 8, 2 * 16},
10, 10,
"Enable Log", "Enable Log"};
false};
Checkbox check_log_raw{ Checkbox check_log_raw{
{2 * 8, 4 * 16}, {2 * 8, 4 * 16},
12, 12,
"Log Raw Data", "Log Raw Data"};
false};
Checkbox check_small_font{ Checkbox check_small_font{
{2 * 8, 6 * 16}, {2 * 8, 6 * 16},
4, 4,
"Use Small Font", "Use Small Font"};
false};
Checkbox check_hide_bad{ Checkbox check_hide_bad{
{2 * 8, 8 * 16}, {2 * 8, 8 * 16},
22, 22,
"Hide Bad Data", "Hide Bad Data"};
false};
Checkbox check_hide_addr_only{ Checkbox check_hide_addr_only{
{2 * 8, 10 * 16}, {2 * 8, 10 * 16},
22, 22,
"Hide Addr Only", "Hide Addr Only"};
false};
Checkbox check_ignore{ Checkbox check_ignore{
{2 * 8, 12 * 16}, {2 * 8, 12 * 16},
22, 22,
"Enable Ignored Address", "Enable Ignored Address"};
false};
NumberField field_ignore{ NumberField field_ignore{
{7 * 8, 13 * 16 + 8}, {7 * 8, 13 * 16 + 8},
@ -115,7 +117,7 @@ class POCSAGSettingsView : public View {
'0'}; '0'};
Button button_save{ Button button_save{
{12 * 8, 16 * 16, 10 * 8, 2 * 16}, {11 * 8, 16 * 16, 10 * 8, 2 * 16},
"Save"}; "Save"};
}; };
@ -150,6 +152,7 @@ class POCSAGAppView : public View {
{"address_to_ignore"sv, &settings_.address_to_ignore}, {"address_to_ignore"sv, &settings_.address_to_ignore},
{"hide_bad_data"sv, &settings_.hide_bad_data}, {"hide_bad_data"sv, &settings_.hide_bad_data},
{"hide_addr_only"sv, &settings_.hide_addr_only}, {"hide_addr_only"sv, &settings_.hide_addr_only},
{"use_new_proc"sv, &settings_.use_new_proc},
}}; }};
void refresh_ui(); void refresh_ui();

View File

@ -444,6 +444,13 @@ set(MODE_CPPSRC
) )
DeclareTargets(PPOC pocsag) DeclareTargets(PPOC pocsag)
### POCSAG2 RX
set(MODE_CPPSRC
proc_pocsag2.cpp
)
DeclareTargets(PPOC pocsag2)
### RDS ### RDS
set(MODE_CPPSRC set(MODE_CPPSRC

View File

@ -36,6 +36,7 @@ bool FMSquelch::execute(const buffer_f32_t& audio) {
squelch_energy_buffer.size()}; squelch_energy_buffer.size()};
non_audio_hpf.execute(audio, squelch_energy); non_audio_hpf.execute(audio, squelch_energy);
// "Non-audio" implies "noise" here. Find the loudest noise sample.
float non_audio_max_squared = 0; float non_audio_max_squared = 0;
for (const auto sample : squelch_energy_buffer) { for (const auto sample : squelch_energy_buffer) {
const float sample_squared = sample * sample; 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); return (non_audio_max_squared < threshold_squared);
} }
void FMSquelch::set_threshold(const float new_value) { void FMSquelch::set_threshold(const float new_value) {
threshold_squared = new_value * new_value; threshold_squared = new_value * new_value;
} }
bool FMSquelch::enabled() const {
return threshold_squared > 0.0;
}

View File

@ -31,9 +31,12 @@
class FMSquelch { class FMSquelch {
public: public:
/* Check if noise level is lower than threshold.
* Returns true if noise is below threshold. */
bool execute(const buffer_f32_t& audio); bool execute(const buffer_f32_t& audio);
void set_threshold(const float new_value); void set_threshold(const float new_value);
bool enabled() const;
private: private:
static constexpr size_t N = 32; static constexpr size_t N = 32;

View File

@ -3,6 +3,7 @@
* Copyright (C) 2012-2014 Elias Oenal (multimon-ng@eliasoenal.com) * Copyright (C) 2012-2014 Elias Oenal (multimon-ng@eliasoenal.com)
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek * Copyright (C) 2016 Furrtek
* Copyright (C) 2016 Kyle Reed
* *
* This file is part of PortaPack. * 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); const auto channel_out = channel_filter.execute(decim_1_out, dst_buffer);
auto audio = demod.execute(channel_out, audio_buffer); auto audio = demod.execute(channel_out, audio_buffer);
// When use_squelch_ is true, AudioOutput applies filters // If squelching, check for audio before smoothing because smoothing
// which should eliminate the need to do this smoothing. // causes the squelch noise detection to fail. Likely because squelch
if (use_squelch_ == false) // looks for HF noise and smoothing is basically a lowpass filter.
smooth.Process(audio.p, audio.count); // NB: Squelch in this processor is only for the the audio output.
// Squelching will likely drop data "noise" and break processing.
// NB: This applies audio filters in-place when use_squelch_ is true. if (squelch_.enabled()) {
audio_output.write(audio); 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); processDemodulatedSamples(audio.p, 16);
extractFrames(); 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: { case Message::ID::NBFMConfigure: {
auto config = reinterpret_cast<const NBFMConfigureMessage*>(message); auto config = reinterpret_cast<const NBFMConfigureMessage*>(message);
squelch_.set_threshold(config->squelch_level / 100.0);
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_);
break; break;
} }
@ -119,6 +127,9 @@ void POCSAGProcessor::configure() {
// 24k / 3.2k = 7.5 // 24k / 3.2k = 7.5
smooth.SetSize(8); smooth.SetSize(8);
// Don't have audio process the stream.
audio_output.configure(false);
// Set up the frame extraction, limits of baud // Set up the frame extraction, limits of baud
setFrameExtractParams(demod_input_fs, 4000, 300, 32); setFrameExtractParams(demod_input_fs, 4000, 300, 32);

View File

@ -3,6 +3,7 @@
* Copyright (C) 2012-2014 Elias Oenal (multimon-ng@eliasoenal.com) * Copyright (C) 2012-2014 Elias Oenal (multimon-ng@eliasoenal.com)
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek * Copyright (C) 2016 Furrtek
* Copyright (C) 2023 Kyle Reed
* *
* This file is part of PortaPack. * This file is part of PortaPack.
* *
@ -215,7 +216,8 @@ class POCSAGProcessor : public BasebandProcessor {
int m_numCode{0}; int m_numCode{0};
bool m_inverted{false}; 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. */ /* NB: Threads should be the last members in the class definition. */
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive}; BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};

View File

@ -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 <algorithm>
#include <cmath>
#include <cstdint>
#include <cstddef>
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<const NBFMConfigureMessage*>(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<POCSAGProcessor>()};
event_dispatcher.run();
return 0;
}

View File

@ -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 <cstdint>
#include <bitset>
using namespace std;
// Class used to smooth demodulated waveform prior to decoding
// -----------------------------------------------------------
template <class ValType, class CalcType>
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<float, float>&) {
}
SmoothVals& operator=(const SmoothVals<float, float>&) {
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<complex16_t, 512> dst{};
const buffer_c16_t dst_buffer{
dst.data(),
dst.size()};
std::array<float, 32> 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<float, float> 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__*/

View File

@ -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_ert{'P', 'E', 'R', 'T'};
constexpr image_tag_t image_tag_nfm_audio{'P', 'N', 'F', 'M'}; 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_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_sonde{'P', 'S', 'O', 'N'};
constexpr image_tag_t image_tag_tpms{'P', 'T', 'P', 'M'}; constexpr image_tag_t image_tag_tpms{'P', 'T', 'P', 'M'};
constexpr image_tag_t image_tag_wfm_audio{'P', 'W', 'F', 'M'}; constexpr image_tag_t image_tag_wfm_audio{'P', 'W', 'F', 'M'};