diff --git a/firmware/application/apps/pocsag_app.cpp b/firmware/application/apps/pocsag_app.cpp index bf9d88d83..10c3eb201 100644 --- a/firmware/application/apps/pocsag_app.cpp +++ b/firmware/application/apps/pocsag_app.cpp @@ -30,6 +30,7 @@ using namespace pocsag; #include "string_format.hpp" #include "utility.hpp" +#include "audio.hpp" void POCSAGLogger::log_raw_data(const pocsag::POCSAGPacket& packet, const uint32_t frequency) { std::string entry = "Raw: F:" + to_string_dec_uint(frequency) + "Hz " + @@ -64,6 +65,7 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav) { add_children({ &rssi, &channel, + &audio, &field_rf_amp, &field_lna, &field_vga, @@ -71,6 +73,7 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav) { &options_bitrate, &options_phase, &check_log, + &field_volume, &check_ignore, &sym_ignore, &console @@ -107,6 +110,12 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav) { options_phase.on_change = [this](size_t, OptionsField::value_t v) { on_config_changed(options_bitrate.selected_index_value(),v); }; + + field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99); + field_volume.on_change = [this](int32_t v) { + this->on_headphone_volume_changed(v); + }; + check_ignore.set_value(ignore); check_ignore.on_select = [this](Checkbox&, bool v) { ignore = v; @@ -121,9 +130,15 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav) { logger = std::make_unique(); if (logger) logger->append("pocsag.txt"); + + audio::output::start(); + audio::output::unmute(); + } POCSAGAppView::~POCSAGAppView() { + audio::output::stop(); + // Save ignored address persistent_memory::set_pocsag_ignore_address(sym_ignore.value_dec_u32()); @@ -135,6 +150,12 @@ void POCSAGAppView::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 ? void POCSAGAppView::set_parent_rect(const Rect new_parent_rect) { View::set_parent_rect(new_parent_rect); @@ -154,6 +175,11 @@ void POCSAGAppView::on_packet(const POCSAGPacketMessage * message) { // " Ignored address " + to_string_dec_uint(pocsag_state.address)); return; } + // Too many errors for reliable decode + if ((ignore) && (pocsag_state.errors >= 3)) { + return; + } + std::string console_info; diff --git a/firmware/application/apps/pocsag_app.hpp b/firmware/application/apps/pocsag_app.hpp index faa47954d..e9e577ed7 100644 --- a/firmware/application/apps/pocsag_app.hpp +++ b/firmware/application/apps/pocsag_app.hpp @@ -61,7 +61,7 @@ private: static constexpr uint32_t initial_target_frequency = 466175000; bool logging { true }; - bool ignore { false }; + bool ignore { true }; uint32_t last_address = 0xFFFFFFFF; pocsag::POCSAGState pocsag_state { }; @@ -80,6 +80,9 @@ private: Channel channel { { 21 * 8, 5, 6 * 8, 4 }, }; + Audio audio{ + { 21 * 8, 10, 6 * 8, 4 }, + }; FrequencyField field_frequency { { 0 * 8, 0 * 8 }, @@ -90,8 +93,7 @@ private: { { "512bps ", 0 }, { "1200bps", 1 }, - { "2400bps", 2 }, - { "3200bps", 3 } + { "2400bps", 2 } } }; OptionsField options_phase { @@ -108,6 +110,13 @@ private: "LOG", true }; + NumberField field_volume{ + { 28 * 8, 0 * 16 }, + 2, + { 0, 99 }, + 1, + ' ', + }; Checkbox check_ignore { { 1 * 8, 40 }, @@ -134,6 +143,7 @@ private: 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; void set_target_frequency(const uint32_t new_value); diff --git a/firmware/baseband/CMakeLists.txt b/firmware/baseband/CMakeLists.txt index 7a3951234..ddec7af86 100644 --- a/firmware/baseband/CMakeLists.txt +++ b/firmware/baseband/CMakeLists.txt @@ -431,6 +431,7 @@ DeclareTargets(POOK ook) set(MODE_CPPSRC proc_pocsag.cpp + extract_frame_pager.cpp ) DeclareTargets(PPOC pocsag) diff --git a/firmware/baseband/extract_frame_pager.cpp b/firmware/baseband/extract_frame_pager.cpp new file mode 100644 index 000000000..a06b2474b --- /dev/null +++ b/firmware/baseband/extract_frame_pager.cpp @@ -0,0 +1,452 @@ +#include "extract_frame_pager.hpp" +#include // std::max +#include + +#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); + +} + + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// +extract_frame_pager::extract_frame_pager() +{ + init(); +} + +// ==================================================================== +// +// ==================================================================== +extract_frame_pager::~extract_frame_pager() +{ +} + +// ==================================================================== +// +// ==================================================================== +void extract_frame_pager::init() +{ + 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 extract_frame_pager::resetVals() +{ + // Set up the xtraction mode + m_detectionMode = DM_DYNAMIC; + + // 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 extract_frame_pager::setParams(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; + + init(); +} + +// ==================================================================== +// +// ==================================================================== +int extract_frame_pager::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 < (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; + } + } + // Not a good transition + // --------------------- + else + { + // m_goodTransitions = 0; + } + } + + // 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 + else + { + //TRACE("Len too small %f",len); + } + + // 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 + // ------------------------ + int 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 extract_frame_pager::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 extract_frame_pager::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; + //TRACE("SYNC %x %d\n", m_fifo.codeword, m_numCode); + } + else if (bitsDiff(m_fifo.codeword, M_NOTSYNC) <= 2) + { + m_inverted = true; + m_gotSync = true; + m_numCode = -1; + m_fifo.numBits = 0; + //TRACE("ISYNC %x %d\n", m_fifo.codeword, m_numCode); + } + 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); + //TRACE("WORD %x %d\n", m_fifo.codeword, 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 extract_frame_pager::getBit() +{ + if (m_bitsEnd != m_bitsStart) + { + if (++m_bitsEnd >= BIT_BUF_SIZE) + { + m_bitsEnd = 0; + } + return m_bits[m_bitsEnd]; + } + else + { + return -1; + } +} + +// ==================================================================== +// +// ==================================================================== +int extract_frame_pager::getNoOfBits() +{ + int bits = m_bitsEnd - m_bitsStart; + if (bits < 0) { bits += BIT_BUF_SIZE; } + return bits; +} + +// ==================================================================== +// +// ==================================================================== +uint32_t extract_frame_pager::getRate() +{ + return ((m_samplesPerSec<<10)+512) / m_lastStableSymbolLen_1024; +} diff --git a/firmware/baseband/extract_frame_pager.hpp b/firmware/baseband/extract_frame_pager.hpp new file mode 100644 index 000000000..2a7f17f94 --- /dev/null +++ b/firmware/baseband/extract_frame_pager.hpp @@ -0,0 +1,96 @@ +/* + * 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 __EXTRACT_FRAME_PAGER_H__ +#define __EXTRACT_FRAME_PAGER_H__ + +#include +using namespace std; + +#define MAX_CODEWORDS (16) +class extract_frame_pager +{ +public: + struct FIFOStruct { + unsigned long codeword; + int numBits; + }; + +#define BIT_BUF_SIZE (64) + enum BitsState { BS_SEARCHING, BS_SYNC, BS_LOCKED }; + enum DetectionMode { DM_STATIC, DM_DYNAMIC, DM_DIFFERENTIAL }; + + extract_frame_pager(); + virtual ~extract_frame_pager(); + + + void resetVals(); + void setParams(long a_samplesPerSec, long a_maxBaud = 8000, long a_minBaud = 200, long maxRunOfSameValue = 32); + + int processDemodulatedSamples(float * sampleBuff, int noOfSamples); + int extractFrames(); + virtual int OnDataFrame(int len, int baud) = 0; + virtual int OnDataWord(uint32_t word, int pos) = 0; + + void init(); + void storeBit(); + short getBit(); + + int getNoOfBits(); + uint32_t getRate(); + +protected: + uint32_t m_averageSymbolLen_1024; + uint32_t m_lastStableSymbolLen_1024; + + uint32_t m_samplesPerSec; + uint32_t m_goodTransitions; + uint32_t m_badTransitions; + +private: + uint32_t m_sampleNo; + float m_sample; + float m_valMid; + float m_lastSample; + + BitsState m_state; + uint32_t m_lastTransPos_1024; + uint32_t m_lastSingleBitPos_1024; + + uint32_t m_nextBitPosInt; // Integer rounded up version to save on ops + uint32_t m_nextBitPos_1024; + uint32_t m_lastBitPos_1024; + + uint32_t m_shortestGoodTrans_1024; + uint32_t m_minSymSamples_1024; + uint32_t m_maxSymSamples_1024; + uint32_t m_maxRunOfSameValue; + + bitset<(size_t)BIT_BUF_SIZE> m_bits; + long m_bitsStart; + long m_bitsEnd; + DetectionMode m_detectionMode; + + FIFOStruct m_fifo; + bool m_gotSync; + int m_numCode; + bool m_inverted; +}; + +#endif/*__EXTRACT_FRAME_PAGER_H__*/ diff --git a/firmware/baseband/proc_pocsag.cpp b/firmware/baseband/proc_pocsag.cpp index a1854286d..42667650f 100644 --- a/firmware/baseband/proc_pocsag.cpp +++ b/firmware/baseband/proc_pocsag.cpp @@ -39,98 +39,58 @@ void POCSAGProcessor::execute(const buffer_c8_t& buffer) { const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer); const auto channel_out = channel_filter.execute(decim_1_out, dst_buffer); auto audio = demod.execute(channel_out, audio_buffer); - //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++) { - - 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 ? + processDemodulatedSamples(audio.p, 16); + extractFrames(); + +} + +// ==================================================================== +// +// ==================================================================== +int POCSAGProcessor::OnDataWord(uint32_t word, int pos) +{ + packet.set(pos, word); + return 0; +} + +// ==================================================================== +// +// ==================================================================== +int POCSAGProcessor::OnDataFrame(int len, int baud) +{ + if (len > 0) + { + if (baud > 492 && baud < 542) + { + bitrate = pocsag::BitRate::FSK512; + } + else if (baud > 1000 && baud < 1400) + { + bitrate = pocsag::BitRate::FSK1200; + } + else if (baud > 2300 && baud < 2500) + { + bitrate = pocsag::BitRate::FSK2400; + } + else if (baud > 3100 && baud < 3300) + { + bitrate = pocsag::BitRate::FSK3200; + } 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; + { + bitrate = pocsag::BitRate::UNKNOWN; } - - 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; - } - } + packet.set_bitrate(bitrate); + 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::push_packet(pocsag::PacketFlag flag) { @@ -162,7 +122,8 @@ void POCSAGProcessor::configure(const POCSAGConfigureMessage& message) { decim_1.configure(taps_11k0_decim_1.taps, 131072); channel_filter.configure(taps_11k0_channel.taps, 2); demod.configure(demod_input_fs, 4500); - //audio_output.configure(false); + smooth.SetSize(9); + audio_output.configure(false); bitrate = message.bitrate; phase = message.phase; @@ -171,6 +132,9 @@ void POCSAGProcessor::configure(const POCSAGConfigureMessage& message) { sphase_delta_eighth = sphase_delta / 8; rx_state = WAITING; + + setParams(demod_input_fs, 6000, 300, 32); + configured = true; } diff --git a/firmware/baseband/proc_pocsag.hpp b/firmware/baseband/proc_pocsag.hpp index b437aac82..9d1e02318 100644 --- a/firmware/baseband/proc_pocsag.hpp +++ b/firmware/baseband/proc_pocsag.hpp @@ -33,6 +33,7 @@ #include "dsp_demodulate.hpp" #include "pocsag_packet.hpp" +#include "extract_frame_pager.hpp" #include "pocsag.hpp" #include "message.hpp" @@ -41,12 +42,91 @@ #include -class POCSAGProcessor : public BasebandProcessor { + +template +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; // + +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 POCSAGProcessor : public BasebandProcessor, extract_frame_pager { public: void execute(const buffer_c8_t& buffer) override; void on_message(const Message* const message) override; + virtual int OnDataFrame(int len, int baud); + virtual int OnDataWord(uint32_t word, int pos); + private: enum rx_states { WAITING = 0, @@ -79,8 +159,9 @@ private: dsp::decimate::FIRC16xR16x32Decim8 decim_1 { }; dsp::decimate::FIRAndDecimateComplex channel_filter { }; dsp::demodulate::FM demod { }; + SmoothVals smooth; - //AudioOutput audio_output { }; + AudioOutput audio_output { }; uint32_t sync_timeout { 0 }; uint32_t msg_timeout { 0 }; @@ -95,7 +176,7 @@ private: bool configured = false; rx_states rx_state { WAITING }; pocsag::BitRate bitrate { pocsag::BitRate::FSK1200 }; - bool phase = false ; + bool phase; uint32_t codeword_count { 0 }; pocsag::POCSAGPacket packet { }; diff --git a/firmware/common/pocsag.cpp b/firmware/common/pocsag.cpp index 2bddb4811..2fb87660c 100644 --- a/firmware/common/pocsag.cpp +++ b/firmware/common/pocsag.cpp @@ -36,6 +36,7 @@ std::string bitrate_str(BitRate bitrate) { case BitRate::FSK512: return "512bps "; case BitRate::FSK1200: return "1200bps"; case BitRate::FSK2400: return "2400bps"; + case BitRate::FSK3200: return "3200bps"; default: return "????"; } } @@ -219,7 +220,176 @@ void pocsag_encode(const MessageType type, BCHCode& BCH_code, const uint32_t fun } 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) { + int errors = 0; uint32_t codeword; char ascii_char; 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++) { codeword = batch[i]; + errorCorrection(&codeword); + errors = errorCorrection(&codeword); + if (!(codeword & 0x80000000U)) { // Address codeword 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->address = (codeword >> 10) & 0x1FFFF8U; // 18 MSBs are transmitted state->mode = STATE_HAVE_ADDRESS; state->out_type = ADDRESS; - + state->errors = errors; + state->ascii_idx = 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 } } else { + state->errors += errors; // Message codeword if (state->mode == STATE_HAVE_ADDRESS) { // First message codeword: complete address @@ -270,7 +447,10 @@ void pocsag_decode_batch(const POCSAGPacket& batch, POCSAGState * const state) { // Translate non-printable chars 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 output_text += ascii_char; } diff --git a/firmware/common/pocsag.hpp b/firmware/common/pocsag.hpp index 49346af43..6c86f8f3f 100644 --- a/firmware/common/pocsag.hpp +++ b/firmware/common/pocsag.hpp @@ -62,6 +62,7 @@ struct POCSAGState { OutputType out_type = EMPTY; uint32_t ascii_data; uint32_t ascii_idx; + uint32_t errors; std::string output; };