Merge pull request #426 from heurist1/update_pocsag_decoder

Update pocsag decoder
This commit is contained in:
Erwin Ried 2021-11-24 12:32:33 +01:00 committed by GitHub
commit 0d1d703ba9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 852 additions and 200 deletions

View File

@ -30,10 +30,11 @@ using namespace pocsag;
#include "string_format.hpp" #include "string_format.hpp"
#include "utility.hpp" #include "utility.hpp"
#include "audio.hpp"
void POCSAGLogger::log_raw_data(const pocsag::POCSAGPacket& packet, const uint32_t frequency) { void POCSAGLogger::log_raw_data(const pocsag::POCSAGPacket& packet, const uint32_t frequency) {
std::string entry = "Raw: F:" + to_string_dec_uint(frequency) + "Hz " + std::string entry = "Raw: F:" + to_string_dec_uint(frequency) + "Hz " +
pocsag::bitrate_str(packet.bitrate()) + " Codewords:"; to_string_dec_uint(packet.bitrate()) + " Codewords:";
// Raw hex dump of all the codewords // Raw hex dump of all the codewords
for (size_t c = 0; c < 16; c++) for (size_t c = 0; c < 16; c++)
@ -64,13 +65,13 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav) {
add_children({ add_children({
&rssi, &rssi,
&channel, &channel,
&audio,
&field_rf_amp, &field_rf_amp,
&field_lna, &field_lna,
&field_vga, &field_vga,
&field_frequency, &field_frequency,
&options_bitrate,
&options_phase,
&check_log, &check_log,
&field_volume,
&check_ignore, &check_ignore,
&sym_ignore, &sym_ignore,
&console &console
@ -99,14 +100,11 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav) {
logging = v; logging = v;
}; };
options_bitrate.on_change = [this](size_t, OptionsField::value_t v) { field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
on_config_changed(v, options_phase.selected_index_value()); field_volume.on_change = [this](int32_t v) {
}; this->on_headphone_volume_changed(v);
options_bitrate.set_selected_index(1); // 1200bps
options_phase.on_change = [this](size_t, OptionsField::value_t v) {
on_config_changed(options_bitrate.selected_index_value(),v);
}; };
check_ignore.set_value(ignore); check_ignore.set_value(ignore);
check_ignore.on_select = [this](Checkbox&, bool v) { check_ignore.on_select = [this](Checkbox&, bool v) {
ignore = v; ignore = v;
@ -121,9 +119,16 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav) {
logger = std::make_unique<POCSAGLogger>(); logger = std::make_unique<POCSAGLogger>();
if (logger) if (logger)
logger->append("pocsag.txt"); logger->append("pocsag.txt");
audio::output::start();
audio::output::unmute();
baseband::set_pocsag();
} }
POCSAGAppView::~POCSAGAppView() { POCSAGAppView::~POCSAGAppView() {
audio::output::stop();
// Save ignored address // Save ignored address
persistent_memory::set_pocsag_ignore_address(sym_ignore.value_dec_u32()); persistent_memory::set_pocsag_ignore_address(sym_ignore.value_dec_u32());
@ -135,6 +140,12 @@ void POCSAGAppView::focus() {
field_frequency.focus(); field_frequency.focus();
} }
void POCSAGAppView::on_headphone_volume_changed(int32_t v) {
const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
receiver_model.set_headphone_volume(new_volume);
}
// Useless ? // Useless ?
void POCSAGAppView::set_parent_rect(const Rect new_parent_rect) { void POCSAGAppView::set_parent_rect(const Rect new_parent_rect) {
View::set_parent_rect(new_parent_rect); View::set_parent_rect(new_parent_rect);
@ -154,11 +165,17 @@ void POCSAGAppView::on_packet(const POCSAGPacketMessage * message) {
// " Ignored address " + to_string_dec_uint(pocsag_state.address)); // " Ignored address " + to_string_dec_uint(pocsag_state.address));
return; return;
} }
// Too many errors for reliable decode
if ((ignore) && (pocsag_state.errors >= 3)) {
return;
}
std::string console_info; std::string console_info;
const uint32_t roundVal = 50;
const uint32_t bitrate = roundVal * ((message->packet.bitrate() + (roundVal/2))/roundVal);
console_info = "\n" + to_string_datetime(message->packet.timestamp(), HM); console_info = "\n" + to_string_datetime(message->packet.timestamp(), HM);
console_info += " " + pocsag::bitrate_str(message->packet.bitrate()); console_info += " " + to_string_dec_uint(bitrate);
console_info += " ADDR:" + to_string_dec_uint(pocsag_state.address); console_info += " ADDR:" + to_string_dec_uint(pocsag_state.address);
console_info += " F" + to_string_dec_uint(pocsag_state.function); console_info += " F" + to_string_dec_uint(pocsag_state.function);
@ -201,10 +218,6 @@ void POCSAGAppView::on_packet(const POCSAGPacketMessage * message) {
logger->log_raw_data(message->packet, target_frequency()); logger->log_raw_data(message->packet, target_frequency());
} }
void POCSAGAppView::on_config_changed(const uint32_t new_bitrate, bool new_phase) {
baseband::set_pocsag(pocsag_bitrates[new_bitrate], new_phase);
}
void POCSAGAppView::set_target_frequency(const uint32_t new_value) { void POCSAGAppView::set_target_frequency(const uint32_t new_value) {
target_frequency_ = new_value; target_frequency_ = new_value;
receiver_model.set_tuning_frequency(new_value); receiver_model.set_tuning_frequency(new_value);

View File

@ -61,7 +61,7 @@ private:
static constexpr uint32_t initial_target_frequency = 466175000; static constexpr uint32_t initial_target_frequency = 466175000;
bool logging { true }; bool logging { true };
bool ignore { false }; bool ignore { true };
uint32_t last_address = 0xFFFFFFFF; uint32_t last_address = 0xFFFFFFFF;
pocsag::POCSAGState pocsag_state { }; pocsag::POCSAGState pocsag_state { };
@ -80,49 +80,41 @@ private:
Channel channel { Channel channel {
{ 21 * 8, 5, 6 * 8, 4 }, { 21 * 8, 5, 6 * 8, 4 },
}; };
Audio audio{
{ 21 * 8, 10, 6 * 8, 4 },
};
FrequencyField field_frequency { FrequencyField field_frequency {
{ 0 * 8, 0 * 8 }, { 0 * 8, 0 * 8 },
}; };
OptionsField options_bitrate {
{ 12 * 8, 21 },
7,
{
{ "512bps ", 0 },
{ "1200bps", 1 },
{ "2400bps", 2 },
{ "3200bps", 3 }
}
};
OptionsField options_phase {
{ 6 * 8, 21 },
1,
{
{ "P", 0 },
{ "N", 1 },
}
};
Checkbox check_log { Checkbox check_log {
{ 22 * 8, 21 }, { 24 * 8, 21 },
3, 3,
"LOG", "LOG",
true true
}; };
NumberField field_volume{
{ 28 * 8, 0 * 16 },
2,
{ 0, 99 },
1,
' ',
};
Checkbox check_ignore { Checkbox check_ignore {
{ 1 * 8, 40 }, { 1 * 8, 21 },
15, 12,
"Ignore address:", "Ignore addr:",
true true
}; };
SymField sym_ignore { SymField sym_ignore {
{ 19 * 8, 40 }, { 16 * 8, 21 },
7, 7,
SymField::SYMFIELD_DEC SymField::SYMFIELD_DEC
}; };
Console console { Console console {
{ 0, 4 * 16, 240, 240 } { 0, 3 * 16, 240, 256 }
}; };
std::unique_ptr<POCSAGLogger> logger { }; std::unique_ptr<POCSAGLogger> logger { };
@ -133,7 +125,7 @@ private:
void on_packet(const POCSAGPacketMessage * message); void on_packet(const POCSAGPacketMessage * message);
void on_config_changed(const uint32_t new_bitrate, const bool phase); void on_headphone_volume_changed(int32_t v);
uint32_t target_frequency() const; uint32_t target_frequency() const;
void set_target_frequency(const uint32_t new_value); void set_target_frequency(const uint32_t new_value);

View File

@ -236,11 +236,8 @@ void set_fsk_data(const uint32_t stream_length, const uint32_t samples_per_bit,
send_message(&message); send_message(&message);
} }
void set_pocsag(const pocsag::BitRate bitrate, bool phase) { void set_pocsag() {
const POCSAGConfigureMessage message { const POCSAGConfigureMessage message {};
bitrate,
phase
};
send_message(&message); send_message(&message);
} }

View File

@ -79,7 +79,7 @@ void set_ook_data(const uint32_t stream_length, const uint32_t samples_per_bit,
const uint32_t pause_symbols); const uint32_t pause_symbols);
void set_fsk_data(const uint32_t stream_length, const uint32_t samples_per_bit, const uint32_t shift, void set_fsk_data(const uint32_t stream_length, const uint32_t samples_per_bit, const uint32_t shift,
const uint32_t progress_notice); const uint32_t progress_notice);
void set_pocsag(const pocsag::BitRate bitrate, bool phase); void set_pocsag();
void set_adsb(); void set_adsb();
void set_jammer(const bool run, const jammer::JammerType type, const uint32_t speed); void set_jammer(const bool run, const jammer::JammerType type, const uint32_t speed);
void set_rds_data(const uint16_t message_length); void set_rds_data(const uint16_t message_length);

View File

@ -28,6 +28,9 @@
#include <cstdint> #include <cstdint>
#include <cstddef> #include <cstddef>
#include <algorithm> // std::max
#include <cmath>
void POCSAGProcessor::execute(const buffer_c8_t& buffer) { void POCSAGProcessor::execute(const buffer_c8_t& buffer) {
// This is called at 1500Hz // This is called at 1500Hz
@ -39,114 +42,45 @@ void POCSAGProcessor::execute(const buffer_c8_t& buffer) {
const auto decim_1_out = decim_1.execute(decim_0_out, 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); 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);
//audio_output.write(audio); smooth.Process(audio.p, audio.count); // Smooth the data to make decoding more accurate
audio_output.write(audio);
for (uint32_t c = 0; c < 16; c++) { processDemodulatedSamples(audio.p, 16);
extractFrames();
const int32_t sample_int = audio.p[c] * 32768.0f;
const int32_t audio_sample = __SSAT(sample_int, 16);
slicer_sr <<= 1;
if (phase == 0)
slicer_sr |= (audio_sample < 0); // Do we need hysteresis ?
else
slicer_sr |= !(audio_sample < 0);
// Detect transitions to adjust clock
if ((slicer_sr ^ (slicer_sr >> 1)) & 1) {
if (sphase < (0x8000u - sphase_delta_half))
sphase += sphase_delta_eighth;
else
sphase -= sphase_delta_eighth;
}
sphase += sphase_delta;
// Symbol time elapsed
if (sphase >= 0x10000u) {
sphase &= 0xFFFFu;
rx_data <<= 1;
rx_data |= (slicer_sr & 1);
switch (rx_state) {
case WAITING:
if (rx_data == 0xAAAAAAAA) {
rx_state = PREAMBLE;
sync_timeout = 0;
}
break;
case PREAMBLE:
if (sync_timeout < POCSAG_TIMEOUT) {
sync_timeout++;
if (rx_data == POCSAG_SYNCWORD) {
packet.clear();
codeword_count = 0;
rx_bit = 0;
msg_timeout = 0;
rx_state = SYNC;
}
} else {
// Timeout here is normal (end of message)
rx_state = WAITING;
//push_packet(pocsag::PacketFlag::TIMED_OUT);
}
break;
case SYNC:
if (msg_timeout < POCSAG_BATCH_LENGTH) {
msg_timeout++;
rx_bit++;
if (rx_bit >= 32) {
rx_bit = 0;
// Got a complete codeword
//pocsag_brute_repair(&s->l2.pocsag, &rx_data);
packet.set(codeword_count, rx_data);
if (codeword_count < 15) {
codeword_count++;
} else {
push_packet(pocsag::PacketFlag::NORMAL);
rx_state = PREAMBLE;
sync_timeout = 0;
}
}
} else {
packet.set(0, codeword_count); // Replace first codeword with count, for debug
push_packet(pocsag::PacketFlag::TIMED_OUT);
rx_state = WAITING;
}
break;
default:
break;
}
}
}
} }
void POCSAGProcessor::push_packet(pocsag::PacketFlag flag) { // ====================================================================
packet.set_bitrate(bitrate); //
packet.set_flag(flag); // ====================================================================
packet.set_timestamp(Timestamp::now()); int POCSAGProcessor::OnDataWord(uint32_t word, int pos)
const POCSAGPacketMessage message(packet); {
shared_memory.application_queue.push(message); 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) { void POCSAGProcessor::on_message(const Message* const message) {
if (message->id == Message::ID::POCSAGConfigure) if (message->id == Message::ID::POCSAGConfigure)
configure(*reinterpret_cast<const POCSAGConfigureMessage*>(message)); configure();
} }
void POCSAGProcessor::configure(const POCSAGConfigureMessage& message) { void POCSAGProcessor::configure() {
constexpr size_t decim_0_input_fs = baseband_fs; 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_0_output_fs = decim_0_input_fs / decim_0.decimation_factor;
@ -162,18 +96,442 @@ void POCSAGProcessor::configure(const POCSAGConfigureMessage& message) {
decim_1.configure(taps_11k0_decim_1.taps, 131072); decim_1.configure(taps_11k0_decim_1.taps, 131072);
channel_filter.configure(taps_11k0_channel.taps, 2); channel_filter.configure(taps_11k0_channel.taps, 2);
demod.configure(demod_input_fs, 4500); demod.configure(demod_input_fs, 4500);
//audio_output.configure(false); // Smoothing should be roughly sample rate over max baud
// 24k / 3.2k is 7.5
smooth.SetSize(8);
audio_output.configure(false);
bitrate = message.bitrate; // Set up the frame extraction, limits of baud
phase = message.phase; setFrameExtractParams(demod_input_fs, 4000, 300, 32);
sphase_delta = 0x10000u * bitrate / POCSAG_AUDIO_RATE;
sphase_delta_half = sphase_delta / 2; // Just for speed // Mark the class as ready to accept data
sphase_delta_eighth = sphase_delta / 8;
rx_state = WAITING;
configured = true; 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() { int main() {
EventDispatcher event_dispatcher { std::make_unique<POCSAGProcessor>() }; EventDispatcher event_dispatcher { std::make_unique<POCSAGProcessor>() };
event_dispatcher.run(); event_dispatcher.run();

View File

@ -40,25 +40,100 @@
#include "portapack_shared_memory.hpp" #include "portapack_shared_memory.hpp"
#include <cstdint> #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; // Previoius 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; //
class POCSAGProcessor : public BasebandProcessor {
public: 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;
}
// --------------------------------------------------
// 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 = (m_pos + 1); // Increment the position in the stored values
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
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 execute(const buffer_c8_t& buffer) override;
void on_message(const Message* const message) override; void on_message(const Message* const message) override;
private: int OnDataFrame(int len, int baud);
enum rx_states { int OnDataWord(uint32_t word, int pos);
WAITING = 0,
PREAMBLE = 32,
SYNC = 64,
//LOSING_SYNC = 65,
//LOST_SYNC = 66,
//ADDRESS = 67,
//MESSAGE = 68,
//END_OF_MESSAGE = 69
};
private:
static constexpr size_t baseband_fs = 3072000; static constexpr size_t baseband_fs = 3072000;
BasebandThread baseband_thread { baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive }; BasebandThread baseband_thread { baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive };
@ -79,28 +154,71 @@ private:
dsp::decimate::FIRC16xR16x32Decim8 decim_1 { }; dsp::decimate::FIRC16xR16x32Decim8 decim_1 { };
dsp::decimate::FIRAndDecimateComplex channel_filter { }; dsp::decimate::FIRAndDecimateComplex channel_filter { };
dsp::demodulate::FM demod { }; dsp::demodulate::FM demod { };
SmoothVals<float, float> smooth;
//AudioOutput audio_output { }; AudioOutput audio_output { };
uint32_t sync_timeout { 0 };
uint32_t msg_timeout { 0 };
uint32_t slicer_sr { 0 };
uint32_t sphase { 0 };
uint32_t sphase_delta { 0 };
uint32_t sphase_delta_half { 0 };
uint32_t sphase_delta_eighth { 0 };
uint32_t rx_data { 0 };
uint32_t rx_bit { 0 };
bool configured = false; bool configured = false;
rx_states rx_state { WAITING };
pocsag::BitRate bitrate { pocsag::BitRate::FSK1200 };
bool phase = false ;
uint32_t codeword_count { 0 };
pocsag::POCSAGPacket packet { }; pocsag::POCSAGPacket packet { };
void push_packet(pocsag::PacketFlag flag); void configure();
void configure(const POCSAGConfigureMessage& message);
// ----------------------------------------
// Frame extractraction methods and members
// ----------------------------------------
private:
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};
}; };

View File

@ -1019,17 +1019,10 @@ public:
class POCSAGConfigureMessage : public Message { class POCSAGConfigureMessage : public Message {
public: public:
constexpr POCSAGConfigureMessage( constexpr POCSAGConfigureMessage()
const pocsag::BitRate bitrate, : Message { ID::POCSAGConfigure }
const bool phase
) : Message { ID::POCSAGConfigure },
bitrate(bitrate),
phase(phase)
{ {
} }
const pocsag::BitRate bitrate;
const bool phase;
}; };
class APRSPacketMessage : public Message { class APRSPacketMessage : public Message {

View File

@ -36,6 +36,7 @@ std::string bitrate_str(BitRate bitrate) {
case BitRate::FSK512: return "512bps "; case BitRate::FSK512: return "512bps ";
case BitRate::FSK1200: return "1200bps"; case BitRate::FSK1200: return "1200bps";
case BitRate::FSK2400: return "2400bps"; case BitRate::FSK2400: return "2400bps";
case BitRate::FSK3200: return "3200bps";
default: return "????"; default: return "????";
} }
} }
@ -219,7 +220,176 @@ void pocsag_encode(const MessageType type, BCHCode& BCH_code, const uint32_t fun
} while (char_idx < message_size); } while (char_idx < message_size);
} }
// -------------------------------------------------------------------------------
// -------------------------------------------------------------------------------
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);
}
// -------------------------------------------------------------------------------
// -------------------------------------------------------------------------------
static uint32_t ecs[32]; /* error correction sequence */
static uint32_t bch[1025];
static int eccSetup = 0;
// -------------------------------------------------------------------------------
// -------------------------------------------------------------------------------
void setupecc()
{
unsigned int srr = 0x3b4;
unsigned int i, n, j, k;
/* calculate all information needed to implement error correction */
// Note : this is only for 31,21 code used in pocsag & flex
// one should probably also make use of 32nd parity bit
for (i = 0; i <= 20; i++)
{
ecs[i] = srr;
if ((srr & 0x01) != 0) srr = (srr >> 1) ^ 0x3B4; else srr = srr >> 1;
}
/* bch holds a syndrome look-up table telling which bits to correct */
// first 5 bits hold location of first error; next 5 bits hold location
// of second error; bits 12 & 13 tell how many bits are bad
for (i = 0; i<1024; i++) bch[i] = 0;
/* two errors in data */
for (n = 0; n <= 20; n++)
{
for (i = 0; i <= 20; i++)
{
j = (i << 5) + n;
k = ecs[n] ^ ecs[i];
bch[k] = j + 0x2000;
}
}
/* one error in data */
for (n = 0; n <= 20; n++)
{
k = ecs[n];
j = n + (0x1f << 5);
bch[k] = j + 0x1000;
}
/* one error in data and one error in ecc portion */
for (n = 0; n <= 20; n++)
{
for (i = 0; i<10; i++) /* ecc screwed up bit */
{
k = ecs[n] ^ (1 << i);
j = n + (0x1f << 5);
bch[k] = j + 0x2000;
}
}
/* one error in ecc */
for (n = 0; n<10; n++)
{
k = 1 << n;
bch[k] = 0x3ff + 0x1000;
}
/* two errors in ecc */
for (n = 0; n<10; n++)
{
for (i = 0; i<10; i++)
{
if (i != n)
{
k = (1 << n) ^ (1 << i);
bch[k] = 0x3ff + 0x2000;
}
}
}
}
// -------------------------------------------------------------------------------
// -------------------------------------------------------------------------------
inline int errorCorrection(uint32_t * val)
{
// Set up the tables the first time
if (eccSetup == 0)
{
setupecc();
eccSetup = 1;
}
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))) { ecc = ecc ^ ecs[31 - i]; pari = pari ^ 0x01; }
}
// for (i=21; i<=30; i++)
acc = 0;
for (i = 10; i >= 1; --i)
{
acc = acc << 1;
if ((*val&(1 << i))) { acc = acc ^ 0x01; }
}
synd = ecc ^ acc;
errl = 0;
if (synd != 0) /* if nonzero syndrome we have error */
{
if (bch[synd] != 0) /* check for correctable error */
{
b1 = bch[synd] & 0x1f;
b2 = bch[synd] >> 5;
b2 = b2 & 0x1f;
if (b2 != 0x1f)
{
*val ^= 0x01 << (31 - b2);
ecc = ecc ^ ecs[b2];
}
if (b1 != 0x1f)
{
*val ^= 0x01 << (31 - b1);
ecc = ecc ^ ecs[b1];
}
errl = bch[synd] >> 12;
}
else
{
errl = 3;
}
if (errl == 1) pari = pari ^ 0x01;
}
if (errl == 4) errl = 3;
return errl;
}
void pocsag_decode_batch(const POCSAGPacket& batch, POCSAGState * const state) { void pocsag_decode_batch(const POCSAGPacket& batch, POCSAGState * const state) {
int errors = 0;
uint32_t codeword; uint32_t codeword;
char ascii_char; char ascii_char;
std::string output_text = ""; std::string output_text = "";
@ -230,15 +400,21 @@ void pocsag_decode_batch(const POCSAGPacket& batch, POCSAGState * const state) {
for (size_t i = 0; i < 16; i++) { for (size_t i = 0; i < 16; i++) {
codeword = batch[i]; codeword = batch[i];
errorCorrection(&codeword);
errors = errorCorrection(&codeword);
if (!(codeword & 0x80000000U)) { if (!(codeword & 0x80000000U)) {
// Address codeword // Address codeword
if (state->mode == STATE_CLEAR) { if (state->mode == STATE_CLEAR) {
if (codeword != POCSAG_IDLEWORD) { //if (codeword != POCSAG_IDLEWORD) {
if (! (bitsDiff(codeword, POCSAG_IDLEWORD) < 1)){
state->function = (codeword >> 11) & 3; state->function = (codeword >> 11) & 3;
state->address = (codeword >> 10) & 0x1FFFF8U; // 18 MSBs are transmitted state->address = (codeword >> 10) & 0x1FFFF8U; // 18 MSBs are transmitted
state->mode = STATE_HAVE_ADDRESS; state->mode = STATE_HAVE_ADDRESS;
state->out_type = ADDRESS; state->out_type = ADDRESS;
state->errors = errors;
state->ascii_idx = 0; state->ascii_idx = 0;
state->ascii_data = 0; state->ascii_data = 0;
} }
@ -246,6 +422,7 @@ void pocsag_decode_batch(const POCSAGPacket& batch, POCSAGState * const state) {
state->mode = STATE_CLEAR; // New address = new message state->mode = STATE_CLEAR; // New address = new message
} }
} else { } else {
state->errors += errors;
// Message codeword // Message codeword
if (state->mode == STATE_HAVE_ADDRESS) { if (state->mode == STATE_HAVE_ADDRESS) {
// First message codeword: complete address // First message codeword: complete address
@ -270,7 +447,10 @@ void pocsag_decode_batch(const POCSAGPacket& batch, POCSAGState * const state) {
// Translate non-printable chars // Translate non-printable chars
if ((ascii_char < 32) || (ascii_char > 126)) if ((ascii_char < 32) || (ascii_char > 126))
output_text += "[" + to_string_dec_uint(ascii_char) + "]"; {
//output_text += "[" + to_string_dec_uint(ascii_char) + "]";
output_text += ".";
}
else else
output_text += ascii_char; output_text += ascii_char;
} }

View File

@ -62,6 +62,7 @@ struct POCSAGState {
OutputType out_type = EMPTY; OutputType out_type = EMPTY;
uint32_t ascii_data; uint32_t ascii_data;
uint32_t ascii_idx; uint32_t ascii_idx;
uint32_t errors;
std::string output; std::string output;
}; };

View File

@ -63,11 +63,11 @@ public:
return (index < 16) ? codewords[index] : 0; return (index < 16) ? codewords[index] : 0;
} }
void set_bitrate(const BitRate bitrate) { void set_bitrate(const uint16_t bitrate) {
bitrate_ = bitrate; bitrate_ = bitrate;
} }
BitRate bitrate() const { uint16_t bitrate() const {
return bitrate_; return bitrate_;
} }
@ -81,12 +81,12 @@ public:
void clear() { void clear() {
codewords.fill(0); codewords.fill(0);
bitrate_ = UNKNOWN; bitrate_ = 0u;
flag_ = NORMAL; flag_ = NORMAL;
} }
private: private:
BitRate bitrate_ { UNKNOWN }; uint16_t bitrate_ { 0 };
PacketFlag flag_ { NORMAL }; PacketFlag flag_ { NORMAL };
std::array <uint32_t, 16> codewords { 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 }; std::array <uint32_t, 16> codewords { 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 };
Timestamp timestamp_ { }; Timestamp timestamp_ { };