mirror of
https://github.com/eried/portapack-mayhem.git
synced 2024-12-24 23:09:26 -05:00
Merge pull request #426 from heurist1/update_pocsag_decoder
Update pocsag decoder
This commit is contained in:
commit
0d1d703ba9
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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();
|
||||||
|
@ -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};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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_ { };
|
||||||
|
Loading…
Reference in New Issue
Block a user