From b28283271b0b4923e276a9c22d86aa22f86a395f Mon Sep 17 00:00:00 2001 From: Kyle Reed <3761006+kallanreed@users.noreply.github.com> Date: Sat, 9 Sep 2023 08:49:22 -0700 Subject: [PATCH] =?UTF-8?q?POCSAG=20clock=20detection=20=F0=9F=90=8B=20imp?= =?UTF-8?q?rovement(=3F)=20=20(#1442)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WIP convergence * Tighter code, allow for sample nudges during clock discovery. --- firmware/baseband/proc_pocsag2.cpp | 100 +++++++++++++++-------------- firmware/baseband/proc_pocsag2.hpp | 22 ++++--- 2 files changed, 66 insertions(+), 56 deletions(-) diff --git a/firmware/baseband/proc_pocsag2.cpp b/firmware/baseband/proc_pocsag2.cpp index e9e0b3bb..15c15738 100644 --- a/firmware/baseband/proc_pocsag2.cpp +++ b/firmware/baseband/proc_pocsag2.cpp @@ -127,41 +127,26 @@ uint32_t BitQueue::data() const { void BitExtractor::extract_bits(const buffer_f32_t& audio) { // Assumes input has been normalized +/- 1.0f. + // Positive == 0, Negative == 1. for (size_t i = 0; i < audio.count; ++i) { auto sample = audio.p[i]; - samples_until_next_ -= 1; - if (!current_rate_) { - // Feed the known rate queues for clock detection. + if (current_rate_) { + if (current_rate_->handle_sample(sample)) { + auto value = (current_rate_->bits.data() & 1) == 1; + bits_.push(value); + } + } else { + // Feed sample to all known rates for clock detection. for (auto& rate : known_rates_) { - if (handle_sample(rate, sample) && - diff_bit_count(rate.bits.data(), clock_magic_number) <= 2) { - // Clock detected. - // NB: This block should only happen on the second sample of a pulse. - // samples_until_next_ to start sampling the *next* pulse. + if (rate.handle_sample(sample) && + diff_bit_count(rate.bits.data(), clock_magic_number) <= 3) { + // Clock detected, continue with this rate. + rate.is_stable = true; current_rate_ = &rate; - samples_until_next_ = rate.sample_interval; - ready_to_send_ = false; } } } - - // Have a clock rate and it's time to process the next sample. - if (current_rate_ && samples_until_next_ <= 0) { - // TODO: It seems like it would be possible to combine this - // code with handle_sample. Nearly the same work. - - // Only send on the second sample of a bit. - // Sampling twice helps mitigate noisy audio data. - if (ready_to_send_) { - auto value = (prev_sample_ + sample) / 2; - bits_.push(signbit(value)); // NB: negative == '1' - } - - ready_to_send_ = !ready_to_send_; - prev_sample_ = sample; - samples_until_next_ += current_rate_->sample_interval; - } } } @@ -177,39 +162,60 @@ void BitExtractor::configure(uint32_t sample_rate) { void BitExtractor::reset() { current_rate_ = nullptr; - samples_until_next_ = 0.0; - prev_sample_ = 0.0; - ready_to_send_ = false; - for (auto& rate : known_rates_) { - rate.samples_until_next = 0.0; - rate.last_sample = 0.0; - rate.bits.reset(); - } + for (auto& rate : known_rates_) + rate.reset(); } uint16_t BitExtractor::baud_rate() const { return current_rate_ ? current_rate_->baud_rate : 0; } -bool BitExtractor::handle_sample(RateInfo& rate, float sample) { - // TODO: Still getting some clock misses at the start of messages. - rate.samples_until_next -= 1; +bool BitExtractor::RateInfo::handle_sample(float sample) { + samples_until_next -= 1; - // Not time to process a sample yet. - if (rate.samples_until_next > 0) + // Time to process a sample? + if (samples_until_next > 0) return false; - // Sample signs are the same, both samples are in the same bit pulse. - auto has_new_bit = signbit(sample) == signbit(rate.last_sample); - if (has_new_bit) - rate.bits.push(signbit(sample)); // NB: negative == '1' + bool value = signbit(sample); // NB: negative == '1' + bool bit_pushed = false; + + switch (state) { + case State::WaitForSample: + // Just need to wait for the first sample of the bit. + state = State::ReadyToSend; + break; + + case State::ReadyToSend: + if (!is_stable && prev_value != value) { + // Still looking for the clock signal but found a transition. + // Nudge the next sample a bit to try avoiding pulse edges. + samples_until_next += (sample_interval / 8.0); + } else { + // Either the clock has been found or both samples were + // (probably) in the same pulse. Send the bit. + // TODO: Wider/more samples for noise reduction? + state = State::WaitForSample; + bit_pushed = true; + bits.push(value); + } + break; + } // How long until the next sample? - rate.samples_until_next += rate.sample_interval; - rate.last_sample = sample; + samples_until_next += sample_interval; + prev_value = value; - return has_new_bit; + return bit_pushed; +} + +void BitExtractor::RateInfo::reset() { + state = State::WaitForSample; + samples_until_next = 0.0; + prev_value = false; + is_stable = false; + bits.reset(); } /* CodewordExtractor *************************************/ diff --git a/firmware/baseband/proc_pocsag2.hpp b/firmware/baseband/proc_pocsag2.hpp index 247c7808..d7ca2118 100644 --- a/firmware/baseband/proc_pocsag2.hpp +++ b/firmware/baseband/proc_pocsag2.hpp @@ -91,17 +91,25 @@ class BitExtractor { static constexpr uint32_t clock_magic_number = 0xAAAAAAAA; struct RateInfo { + enum class State : uint8_t { + WaitForSample, + ReadyToSend + }; + const int16_t baud_rate = 0; float sample_interval = 0.0; + State state = State::WaitForSample; float samples_until_next = 0.0; - float last_sample = 0.0; + bool prev_value = false; + bool is_stable = false; BitQueue bits{}; - }; - /* Updates a rate info with the given sample. - * Returns true if the rate info has a new bit in its queue. */ - bool handle_sample(RateInfo& rate, float sample); + /* Updates a rate info with the given sample. + * Returns true if the rate info has a new bit in its queue. */ + bool handle_sample(float sample); + void reset(); + }; std::array known_rates_{ RateInfo{512}, @@ -112,10 +120,6 @@ class BitExtractor { uint32_t sample_rate_ = 0; RateInfo* current_rate_ = nullptr; - - float samples_until_next_ = 0.0; - float prev_sample_ = 0.0; - bool ready_to_send_ = false; }; /* Extracts codeword batches from the BitQueue. */