Started writing (copying...) AFSK RX

Encoders: removed bit duration setting (frame duration is more useful)
This commit is contained in:
furrtek 2017-08-29 09:42:04 +01:00
parent cd6a1a7f3f
commit 42439d1885
23 changed files with 453 additions and 423 deletions

View File

@ -163,8 +163,9 @@ set(CPPSRC
ui_about.cpp ui_about.cpp
ui_adsb_rx.cpp ui_adsb_rx.cpp
ui_adsb_tx.cpp ui_adsb_tx.cpp
ui_afsk_rx.cpp
ui_alphanum.cpp ui_alphanum.cpp
ui_aprstx.cpp ui_aprs_tx.cpp
ui_audio.cpp ui_audio.cpp
ui_baseband_stats_view.cpp ui_baseband_stats_view.cpp
ui_bht_tx.cpp ui_bht_tx.cpp

View File

@ -117,9 +117,16 @@ void set_sstv_data(const uint8_t vis_code, const uint32_t pixel_duration) {
send_message(&message); send_message(&message);
} }
void set_afsk(const uint32_t bitrate) {
const AFSKRxConfigureMessage message {
bitrate
};
send_message(&message);
}
void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space, void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space,
const uint8_t afsk_repeat, const uint32_t afsk_bw, const uint8_t symbol_count) { const uint8_t afsk_repeat, const uint32_t afsk_bw, const uint8_t symbol_count) {
const AFSKConfigureMessage message { const AFSKTxConfigureMessage message {
afsk_samples_per_bit, afsk_samples_per_bit,
afsk_phase_inc_mark, afsk_phase_inc_mark,
afsk_phase_inc_space, afsk_phase_inc_space,
@ -131,7 +138,7 @@ void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phas
} }
void kill_afsk() { void kill_afsk() {
const AFSKConfigureMessage message { const AFSKTxConfigureMessage message {
0, 0,
0, 0,
0, 0,

View File

@ -67,6 +67,7 @@ void set_pwmrssi(int32_t avg, bool enabled);
void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space, void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space,
const uint8_t afsk_repeat, const uint32_t afsk_bw, const uint8_t symbol_count); const uint8_t afsk_repeat, const uint32_t afsk_bw, const uint8_t symbol_count);
void kill_afsk(); void kill_afsk();
void set_afsk(const uint32_t bitrate);
void set_ook_data(const uint32_t stream_length, const uint32_t samples_per_bit, const uint8_t repeat, void set_ook_data(const uint32_t stream_length, const uint32_t samples_per_bit, const uint8_t repeat,
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,

View File

@ -26,10 +26,10 @@
* B(2,4) means: * B(2,4) means:
* Alphabet size = 2 (binary) * Alphabet size = 2 (binary)
* Code length = 4 * Code length = 4
* The length of the bitstream is (2 ^ 4) - 1 = 15 * The length of the bitstream is (2 ^ 4) + (4 - 1) = 19 bits
* The primitive polynomials come from the de_bruijn_polys[] array (one for each code length) * The primitive polynomials come from the de_bruijn_polys[] array (one for each code length)
* The shift register is init with 1 and shifted left each step * The shift register is init with 1 and shifted left each step
* The polynomial is kept on the right, and ANDed with the corresponding shift register bits * The polynomial is kept on the right, and used as a AND mask applied on the corresponding shift register bits
* The resulting bits are XORed together to produce the new bit pushed in the shift register * The resulting bits are XORed together to produce the new bit pushed in the shift register
* *
* 0001 (init) * 0001 (init)
@ -52,9 +52,28 @@
* AND 1001 * AND 1001
* 1000 XOR'd -> 1 * 1000 XOR'd -> 1
* ... * ...
* After 16 steps: (0+) 000111101011001000
* Each of the 16 possible values appears in the sequence:
* -0000-111101011001000 0
* 0-0001-11101011001000 1
* 0000111101011-0010-00 2
* 00-0011-1101011001000 3
* 00001111010110-0100-0 4
* 00001111-0101-1001000 5
* 0000111101-0110-01000 6
* 000-0111-101011001000 7
* 000011110101100-1000- 8
* 000011110101-1001-000 9
* 0000111-1010-11001000 10
* 000011110-1011-001000 11
* 00001111010-1100-1000 12
* 000011-1101-011001000 13
* 00001-1110-1011001000 14
* 0000-1111-01011001000 15
*/ */
void de_bruijn::init(const uint32_t n) { size_t de_bruijn::init(const uint32_t n) {
// Cap
if ((n < 3) || (n > 16)) if ((n < 3) || (n > 16))
length = 3; length = 3;
else else
@ -62,6 +81,8 @@ void de_bruijn::init(const uint32_t n) {
poly = de_bruijn_polys[length - 3]; poly = de_bruijn_polys[length - 3];
shift_register = 1; shift_register = 1;
return (1U << length) + (length - 1);
} }
uint32_t de_bruijn::compute(const uint32_t steps) { uint32_t de_bruijn::compute(const uint32_t steps) {

View File

@ -25,7 +25,7 @@
#ifndef __DE_BRUIJN_H__ #ifndef __DE_BRUIJN_H__
#define __DE_BRUIJN_H__ #define __DE_BRUIJN_H__
// Starts at n = 3 // n from 3 to 16
const uint32_t de_bruijn_polys[14] { const uint32_t de_bruijn_polys[14] {
0b0000000000000101, 0b0000000000000101,
0b0000000000001001, 0b0000000000001001,
@ -45,7 +45,7 @@ const uint32_t de_bruijn_polys[14] {
struct de_bruijn { struct de_bruijn {
public: public:
void init(const uint32_t n); size_t init(const uint32_t n);
uint32_t compute(const uint32_t steps); uint32_t compute(const uint32_t steps);
private: private:

View File

@ -64,16 +64,6 @@ private:
bool ignore { false }; bool ignore { false };
uint32_t last_address = 0xFFFFFFFF; uint32_t last_address = 0xFFFFFFFF;
pocsag::POCSAGState pocsag_state { }; pocsag::POCSAGState pocsag_state { };
MessageHandlerRegistration message_handler_packet {
Message::ID::POCSAGPacket,
[this](Message* const p) {
const auto message = static_cast<const POCSAGPacketMessage*>(p);
this->on_packet(message);
}
};
static constexpr ui::Dim header_height = 1 * 16;
RFAmpField field_rf_amp { RFAmpField field_rf_amp {
{ 13 * 8, 0 * 16 } { 13 * 8, 0 * 16 }
@ -138,6 +128,14 @@ private:
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);
MessageHandlerRegistration message_handler_packet {
Message::ID::POCSAGPacket,
[this](Message* const p) {
const auto message = static_cast<const POCSAGPacketMessage*>(p);
this->on_packet(message);
}
};
}; };
} /* namespace ui */ } /* namespace ui */

View File

@ -0,0 +1,92 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_afsk_rx.hpp"
#include "baseband_api.hpp"
//#include "string_format.hpp"
using namespace portapack;
namespace ui {
void AFSKRxView::focus() {
field_frequency.focus();
}
void AFSKRxView::update_freq(rf::Frequency f) {
receiver_model.set_tuning_frequency(f);
}
void AFSKRxView::on_bitrate_changed(const uint32_t new_bitrate) {
baseband::set_afsk(new_bitrate);
}
AFSKRxView::AFSKRxView(NavigationView& nav) {
baseband::run_image(portapack::spi_flash::image_tag_afsk_rx);
add_children({
&rssi,
&channel,
&field_rf_amp,
&field_lna,
&field_vga,
&field_frequency,
&options_bitrate,
&console
});
//receiver_model.set_sampling_rate(3072000);
//receiver_model.set_baseband_bandwidth(1750000);
//receiver_model.enable();
field_frequency.set_value(receiver_model.tuning_frequency());
field_frequency.set_step(receiver_model.frequency_step());
field_frequency.on_change = [this](rf::Frequency f) {
update_freq(f);
};
field_frequency.on_edit = [this, &nav]() {
// TODO: Provide separate modal method/scheme?
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
new_view->on_changed = [this](rf::Frequency f) {
update_freq(f);
field_frequency.set_value(f);
};
};
options_bitrate.on_change = [this](size_t, OptionsField::value_t v) {
on_bitrate_changed(v);
};
options_bitrate.set_selected_index(1); // 1200bps
}
void AFSKRxView::on_data(uint_fast8_t byte) {
std::string str_byte(1, byte);
console.write(str_byte);
}
AFSKRxView::~AFSKRxView() {
receiver_model.disable();
baseband::shutdown();
}
} /* namespace ui */

View File

@ -0,0 +1,96 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
*
* 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 __UI_AFSK_RX_H__
#define __UI_AFSK_RX_H__
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_widget.hpp"
#include "utility.hpp"
namespace ui {
class AFSKRxView : public View {
public:
AFSKRxView(NavigationView& nav);
~AFSKRxView();
void focus() override;
std::string title() const override { return "AFSK RX (beta)"; };
private:
void on_data(uint_fast8_t byte);
RFAmpField field_rf_amp {
{ 13 * 8, 0 * 16 }
};
LNAGainField field_lna {
{ 15 * 8, 0 * 16 }
};
VGAGainField field_vga {
{ 18 * 8, 0 * 16 }
};
RSSI rssi {
{ 21 * 8, 0, 6 * 8, 4 },
};
Channel channel {
{ 21 * 8, 5, 6 * 8, 4 },
};
FrequencyField field_frequency {
{ 0 * 8, 0 * 8 },
};
OptionsField options_bitrate {
{ 12 * 8, 21 },
7,
{
{ "600bps ", 600 },
{ "1200bps", 1200 },
{ "2400bps", 2400 }
}
};
Console console {
{ 0, 4 * 16, 240, 240 }
};
void update_freq(rf::Frequency f);
void on_bitrate_changed(const uint32_t new_bitrate);
void on_data_afsk(const AFSKDataMessage& message);
MessageHandlerRegistration message_handler_packet {
Message::ID::AFSKData,
[this](Message* const p) {
const auto message = static_cast<const AFSKDataMessage*>(p);
this->on_data(message->byte);
}
};
};
} /* namespace ui */
#endif/*__UI_AFSK_RX_H__*/

View File

@ -1,140 +0,0 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_afskrx.hpp"
#include "ui_spectrum.hpp"
#include "ui_console.hpp"
#include "event_m0.hpp"
#include "ff.h"
#include "portapack.hpp"
using namespace portapack;
namespace ui {
AFSKRXView::AFSKRXView(
NavigationView& nav
)
{
add_children({
&button_done,
&text_rx
});
button_done.on_select = [&nav](Button&){
nav.pop();
};
receiver_model.set_baseband_configuration({
.mode = 6,
.sampling_rate = 3072000,
.decimation_factor = 4,
});
receiver_model.set_baseband_bandwidth(1750000);
}
AFSKRXView::~AFSKRXView() {
receiver_model.disable();
}
void AFSKRXView::on_show() {
EventDispatcher::message_map().register_handler(Message::ID::AFSKData,
[this](Message* const p) {
const auto message = static_cast<const AFSKDataMessage*>(p);
this->on_data_afsk(*message);
}
);
receiver_model.enable();
}
void AFSKRXView::on_hide() {
EventDispatcher::message_map().unregister_handler(Message::ID::AFSKData);
}
void AFSKRXView::on_data_afsk(const AFSKDataMessage& message) {
Coord oy,ny;
//text_rx.set(to_string_dec_int(abs(message.data), 8, '0'));
portapack::display.fill_rectangle({0,160-64,240,128},{32,32,32});
oy = 160;
for (int c=0;c<128;c++) {
ny = 160-(message.data[c]>>10);
portapack::display.draw_line({static_cast<Coord>(c),oy},{static_cast<Coord>(c+1),ny},{255,127,0});
oy = ny;
}
/*auto payload = message.packet.payload;
auto payload_length = message.packet.bits_received;
std::string hex_data;
std::string hex_error;
uint8_t byte_data = 0;
uint8_t byte_error = 0;
for(size_t i=0; i<payload_length; i+=2) {
const auto bit_data = payload[i+1];
const auto bit_error = (payload[i+0] == payload[i+1]);
byte_data <<= 1;
byte_data |= bit_data ? 1 : 0;
byte_error <<= 1;
byte_error |= bit_error ? 1 : 0;
if( ((i >> 1) & 7) == 7 ) {
hex_data += to_string_hex(byte_data, 2);
hex_error += to_string_hex(byte_error, 2);
}
}
auto console = reinterpret_cast<Console*>(widget_content.get());
console->writeln(hex_data.substr(0, 240 / 8));
if( !f_error(&fil_tpms) ) {
rtc::RTC datetime;
rtcGetTime(&RTCD1, &datetime);
std::string timestamp =
to_string_dec_uint(datetime.year(), 4) +
to_string_dec_uint(datetime.month(), 2, '0') +
to_string_dec_uint(datetime.day(), 2, '0') +
to_string_dec_uint(datetime.hour(), 2, '0') +
to_string_dec_uint(datetime.minute(), 2, '0') +
to_string_dec_uint(datetime.second(), 2, '0');
const auto tuning_frequency = receiver_model.tuning_frequency();
// TODO: function doesn't take uint64_t, so when >= 1<<32, weirdness will ensue!
const auto tuning_frequency_str = to_string_dec_uint(tuning_frequency, 10);
std::string log = timestamp + " " + tuning_frequency_str + " FSK 38.4 19.2 " + hex_data + "/" + hex_error + "\r\n";
f_puts(log.c_str(), &fil_tpms);
f_sync(&fil_tpms);
}*/
}
void AFSKRXView::focus() {
button_done.focus();
}
} /* namespace ui */

View File

@ -1,74 +0,0 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
*
* 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 __UI_AFSKRX_H__
#define __UI_AFSKRX_H__
#include "ui.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_navigation.hpp"
#include "ui_painter.hpp"
#include "ui_widget.hpp"
#include "ui_console.hpp"
#include "utility.hpp"
#include "receiver_model.hpp"
#include <cstddef>
#include <cstdint>
#include <algorithm>
#include <functional>
namespace ui {
class AFSKRXView : public View {
public:
AFSKRXView(NavigationView& nav);
~AFSKRXView();
void focus() override;
void on_show() override;
void on_hide() override;
private:
std::unique_ptr<Widget> widget_content;
Button button_done {
{ 4 * 8, 0 * 16, 3 * 8, 16 },
" < "
};
Text text_rx {
{ 1 * 8, 2 * 16, 28 * 8, 16 },
"Ready..."
};
void on_tuning_frequency_changed(rf::Frequency f);
void on_edit_frequency();
void on_data_afsk(const AFSKDataMessage& message);
};
} /* namespace ui */
#endif/*__UI_RECEIVER_H__*/

View File

@ -20,7 +20,7 @@
* Boston, MA 02110-1301, USA. * Boston, MA 02110-1301, USA.
*/ */
#include "ui_aprstx.hpp" #include "ui_aprs_tx.hpp"
#include "ui_alphanum.hpp" #include "ui_alphanum.hpp"
#include "aprs.hpp" #include "aprs.hpp"

View File

@ -32,9 +32,7 @@ namespace ui {
EncodersConfigView::EncodersConfigView( EncodersConfigView::EncodersConfigView(
NavigationView& nav, Rect parent_rect NavigationView& nav, Rect parent_rect
) { ) {
using name_t = std::string; using option_t = std::pair<std::string, int32_t>;
using value_t = int32_t;
using option_t = std::pair<name_t, value_t>;
std::vector<option_t> enc_options; std::vector<option_t> enc_options;
size_t i; size_t i;
@ -47,9 +45,8 @@ EncodersConfigView::EncodersConfigView(
add_children({ add_children({
&labels, &labels,
&options_enctype, &options_enctype,
&numberfield_clk, &field_clk,
&numberfield_bitduration, &field_frameduration,
&numberfield_wordduration,
&symfield_word, &symfield_word,
&text_format, &text_format,
&waveform &waveform
@ -71,31 +68,19 @@ EncodersConfigView::EncodersConfigView(
}; };
// Selecting input clock changes symbol and word duration // Selecting input clock changes symbol and word duration
numberfield_clk.on_change = [this](int32_t value) { field_clk.on_change = [this](int32_t value) {
// value is in kHz, new_value is in us // value is in kHz, new_value is in us
int32_t new_value = 1000000 / ((value * 1000) / encoder_def->clk_per_symbol); int32_t new_value = (encoder_def->clk_per_symbol * 1000000) / (value * 1000);
if (new_value != numberfield_bitduration.value()) { if (new_value != field_frameduration.value())
numberfield_bitduration.set_value(new_value, false); field_frameduration.set_value(new_value * encoder_def->word_length, false);
numberfield_wordduration.set_value(new_value * encoder_def->word_length, false);
}
};
// Selecting symbol duration changes input clock and word duration
numberfield_bitduration.on_change = [this](int32_t value) {
int32_t new_value = 1000000 / (((float)value * 1000) / encoder_def->clk_per_symbol);
if (new_value != numberfield_clk.value()) {
numberfield_clk.set_value(new_value, false);
numberfield_wordduration.set_value(value * encoder_def->word_length, false);
}
}; };
// Selecting word duration changes input clock and symbol duration // Selecting word duration changes input clock and symbol duration
numberfield_wordduration.on_change = [this](int32_t value) { field_frameduration.on_change = [this](int32_t value) {
int32_t new_value = value / encoder_def->word_length; // value is in us, new_value is in kHz
if (new_value != numberfield_bitduration.value()) { int32_t new_value = (value * 1000) / (encoder_def->word_length * encoder_def->clk_per_symbol);
numberfield_bitduration.set_value(new_value, false); if (new_value != field_clk.value())
numberfield_clk.set_value(1000000 / (((float)new_value * 1000) / encoder_def->clk_per_symbol), false); field_clk.set_value(1000000 / new_value, false);
}
}; };
} }
@ -104,16 +89,13 @@ void EncodersConfigView::focus() {
} }
void EncodersConfigView::on_type_change(size_t index) { void EncodersConfigView::on_type_change(size_t index) {
std::string word_format, format_string = ""; std::string format_string = "";
size_t word_length; size_t word_length;
char symbol_type; char symbol_type;
//size_t address_length;
//enc_type = index;
encoder_def = &encoder_defs[index]; encoder_def = &encoder_defs[index];
numberfield_clk.set_value(encoder_def->default_speed / 1000); field_clk.set_value(encoder_def->default_speed / 1000);
// SymField setup // SymField setup
word_length = encoder_def->word_length; word_length = encoder_def->word_length;
@ -139,16 +121,14 @@ void EncodersConfigView::on_type_change(size_t index) {
} }
void EncodersConfigView::on_show() { void EncodersConfigView::on_show() {
// TODO: Remove ? options_enctype.set_selected_index(0);
//options_enctype.set_selected_index(enc_type); on_type_change(0);
//on_type_change(enc_type);
} }
void EncodersConfigView::draw_waveform() { void EncodersConfigView::draw_waveform() {
size_t length = frame_symbols.length(); size_t length = frame_symbols.length();
size_t n;
for (size_t n = 0; n < length; n++) {
for (n = 0; n < length; n++) {
if (frame_symbols[n] == '0') if (frame_symbols[n] == '0')
waveform_buffer[n] = 0; waveform_buffer[n] = 0;
else else
@ -179,7 +159,7 @@ uint8_t EncodersConfigView::repeat_min() {
} }
uint32_t EncodersConfigView::samples_per_bit() { uint32_t EncodersConfigView::samples_per_bit() {
return OOK_SAMPLERATE / ((numberfield_clk.value() * 1000) / encoder_def->clk_per_fragment); return OOK_SAMPLERATE / ((field_clk.value() * 1000) / encoder_def->clk_per_fragment);
} }
uint32_t EncodersConfigView::pause_symbols() { uint32_t EncodersConfigView::pause_symbols() {
@ -199,20 +179,26 @@ EncodersScanView::EncodersScanView(
add_children({ add_children({
&labels, &labels,
&field_debug, &field_debug,
&text_debug &text_debug,
&text_length
}); });
// DEBUG // DEBUG
field_debug.on_change = [this](int32_t value) { field_debug.on_change = [this](int32_t value) {
uint32_t l; uint32_t l;
size_t length;
de_bruijn debruijn_seq; de_bruijn debruijn_seq;
debruijn_seq.init(value); length = debruijn_seq.init(value);
l = 1; l = 1;
l <<= value; l <<= value;
l--; l--;
if (l > 25) if (l > 25)
l = 25; l = 25;
text_debug.set(to_string_bin(debruijn_seq.compute(l), 25)); text_debug.set(to_string_bin(debruijn_seq.compute(l), 25));
text_length.set(to_string_dec_uint(length));
}; };
} }
@ -256,7 +242,7 @@ void EncodersView::on_txdone(int n, const bool txdone) {
if (!txdone) { if (!txdone) {
// Repeating... // Repeating...
//repeat_index = n + 1; repeat_index = n + 1;
/*if (tx_mode == SCAN) { /*if (tx_mode == SCAN) {
scan_progress++; scan_progress++;
@ -299,8 +285,9 @@ void EncodersView::on_txdone(int n, const bool txdone) {
void EncodersView::start_tx(const bool scan) { void EncodersView::start_tx(const bool scan) {
(void)scan; (void)scan;
uint8_t* ook_bitstream = shared_memory.bb_data.data; uint8_t byte = 0;
uint32_t ook_bitstream_length; size_t bitstream_length =0;
uint8_t * bitstream = shared_memory.bb_data.data;
repeat_min = view_config.repeat_min(); repeat_min = view_config.repeat_min();
@ -324,17 +311,14 @@ void EncodersView::start_tx(const bool scan) {
view_config.generate_frame(); view_config.generate_frame();
// Clear bitstream
memset(ook_bitstream, 0, 256);
size_t n = 0;
for (auto c : view_config.frame_symbols) { for (auto c : view_config.frame_symbols) {
byte <<= 1;
if (c != '0') if (c != '0')
ook_bitstream[n >> 3] |= (1 << (7 - (n & 7))); byte |= 1;
n++; if ((bitstream_length & 7) == 7)
bitstream[bitstream_length >> 3] = byte;
bitstream_length++;
} }
ook_bitstream_length = n;
transmitter_model.set_sampling_rate(OOK_SAMPLERATE); transmitter_model.set_sampling_rate(OOK_SAMPLERATE);
transmitter_model.set_rf_amp(true); transmitter_model.set_rf_amp(true);
@ -342,10 +326,7 @@ void EncodersView::start_tx(const bool scan) {
transmitter_model.enable(); transmitter_model.enable();
baseband::set_ook_data( baseband::set_ook_data(
ook_bitstream_length, bitstream_length,
// 2280000/2 = 1140000Hz = 0,877192982us
// numberfield_clk.value() / encoder_def->clk_per_fragment
// 455000 / 12 = 37917Hz = 26,37339452us
view_config.samples_per_bit(), view_config.samples_per_bit(),
repeat_min, repeat_min,
view_config.pause_symbols() view_config.pause_symbols()

View File

@ -67,12 +67,10 @@ private:
{ { 1 * 8, 0 }, "Type:", Color::light_grey() }, { { 1 * 8, 0 }, "Type:", Color::light_grey() },
{ { 16 * 8, 0 }, "Clk:", Color::light_grey() }, { { 16 * 8, 0 }, "Clk:", Color::light_grey() },
{ { 24 * 8, 0 }, "kHz", Color::light_grey() }, { { 24 * 8, 0 }, "kHz", Color::light_grey() },
{ { 16 * 8, 2 * 8 }, "Bit:", Color::light_grey() }, { { 14 * 8, 2 * 8 }, "Frame:", Color::light_grey() },
{ { 25 * 8, 2 * 8 }, "us", Color::light_grey() }, { { 26 * 8, 2 * 8 }, "us", Color::light_grey() },
{ { 15 * 8, 4 * 8 }, "Word:", Color::light_grey() }, { { 2 * 8, 4 * 8 }, "Symbols:", Color::light_grey() },
{ { 26 * 8, 4 * 8 }, "us", Color::light_grey() }, { { 1 * 8, 11 * 8 }, "Waveform:", Color::light_grey() }
{ { 2 * 8, 6 * 8 }, "Word:", Color::light_grey() },
{ { 1 * 8, 13 * 8 }, "Waveform:", Color::light_grey() }
}; };
OptionsField options_enctype { // Options are loaded at runtime OptionsField options_enctype { // Options are loaded at runtime
@ -82,7 +80,7 @@ private:
} }
}; };
NumberField numberfield_clk { NumberField field_clk {
{ 21 * 8, 0 }, { 21 * 8, 0 },
3, 3,
{ 1, 500 }, { 1, 500 },
@ -90,16 +88,8 @@ private:
' ' ' '
}; };
NumberField numberfield_bitduration { NumberField field_frameduration {
{ 21 * 8, 2 * 8 }, { 21 * 8, 2 * 8 },
4,
{ 50, 9999 },
1,
' '
};
NumberField numberfield_wordduration {
{ 21 * 8, 4 * 8 },
5, 5,
{ 300, 99999 }, { 300, 99999 },
100, 100,
@ -107,18 +97,18 @@ private:
}; };
SymField symfield_word { SymField symfield_word {
{ 2 * 8, 8 * 8 }, { 2 * 8, 6 * 8 },
20, 20,
SymField::SYMFIELD_DEF SymField::SYMFIELD_DEF
}; };
Text text_format { Text text_format {
{ 2 * 8, 10 * 8, 24 * 8, 16 }, { 2 * 8, 8 * 8, 24 * 8, 16 },
"" ""
}; };
Waveform waveform { Waveform waveform {
{ 0, 16 * 8, 240, 32 }, { 0, 14 * 8, 240, 32 },
waveform_buffer, waveform_buffer,
0, 0,
0, 0,
@ -136,7 +126,7 @@ public:
private: private:
Labels labels { Labels labels {
{ { 1 * 8, 1 * 8 }, "Test", Color::light_grey() } { { 1 * 8, 1 * 8 }, "Coming soon...", Color::light_grey() }
}; };
// DEBUG // DEBUG
@ -153,6 +143,12 @@ private:
{ 1 * 8, 8 * 8, 24 * 8, 16 }, { 1 * 8, 8 * 8, 24 * 8, 16 },
"" ""
}; };
// DEBUG
Text text_length {
{ 1 * 8, 10 * 8, 24 * 8, 16 },
""
};
}; };
class EncodersView : public View { class EncodersView : public View {
@ -192,7 +188,7 @@ private:
.foreground = Color::blue(), .foreground = Color::blue(),
}; };
Rect view_rect = { 0, 5 * 8, 240, 168 }; Rect view_rect = { 0, 4 * 8, 240, 168 };
EncodersConfigView view_config { nav_, view_rect }; EncodersConfigView view_config { nav_, view_rect };
EncodersScanView view_scan { nav_, view_rect }; EncodersScanView view_scan { nav_, view_rect };

View File

@ -177,9 +177,9 @@ private:
}; };
MessageHandlerRegistration message_handler_audio_level { MessageHandlerRegistration message_handler_audio_level {
Message::ID::AudioLevel, Message::ID::AudioLevelReport,
[this](const Message* const p) { [this](const Message* const p) {
const auto message = static_cast<const AudioLevelMessage*>(p); const auto message = static_cast<const AudioLevelReportMessage*>(p);
this->audio_level = message->value; this->audio_level = message->value;
} }
}; };

View File

@ -33,7 +33,8 @@
#include "ui_about.hpp" #include "ui_about.hpp"
#include "ui_adsb_tx.hpp" #include "ui_adsb_tx.hpp"
#include "ui_adsb_rx.hpp" #include "ui_adsb_rx.hpp"
#include "ui_aprstx.hpp" #include "ui_aprs_tx.hpp"
#include "ui_afsk_rx.hpp"
#include "ui_bht_tx.hpp" #include "ui_bht_tx.hpp"
#include "ui_coasterp.hpp" #include "ui_coasterp.hpp"
#include "ui_siggen.hpp" #include "ui_siggen.hpp"
@ -282,9 +283,9 @@ void NavigationView::focus() {
ReceiversMenuView::ReceiversMenuView(NavigationView& nav) { ReceiversMenuView::ReceiversMenuView(NavigationView& nav) {
add_items({ add_items({
{ "ADS-B: Planes", ui::Color::green(),&bitmap_icon_adsb, [&nav](){ nav.push<ADSBRxView>(); }, }, { "ADS-B: Planes", ui::Color::green(), &bitmap_icon_adsb, [&nav](){ nav.push<ADSBRxView>(); }, },
{ "AIS: Boats", ui::Color::green(), &bitmap_icon_ais, [&nav](){ nav.push<AISAppView>(); } }, { "AIS: Boats", ui::Color::green(), &bitmap_icon_ais, [&nav](){ nav.push<AISAppView>(); } },
{ "APRS", ui::Color::grey(), &bitmap_icon_aprs, [&nav](){ nav.push<NotImplementedView>(); } }, { "APRS", ui::Color::orange(),&bitmap_icon_aprs, [&nav](){ nav.push<AFSKRxView>(); } },
{ "Audio", ui::Color::green(), &bitmap_icon_speaker, [&nav](){ nav.push<AnalogAudioView>(false); } }, { "Audio", ui::Color::green(), &bitmap_icon_speaker, [&nav](){ nav.push<AnalogAudioView>(false); } },
{ "ERT: Utility Meters", ui::Color::green(), &bitmap_icon_ert, [&nav](){ nav.push<ERTAppView>(); } }, { "ERT: Utility Meters", ui::Color::green(), &bitmap_icon_ert, [&nav](){ nav.push<ERTAppView>(); } },
{ "POCSAG", ui::Color::green(), &bitmap_icon_pocsag, [&nav](){ nav.push<POCSAGAppView>(); } }, { "POCSAG", ui::Color::green(), &bitmap_icon_pocsag, [&nav](){ nav.push<POCSAGAppView>(); } },

View File

@ -294,7 +294,14 @@ DeclareTargets(PADT adsbtx)
set(MODE_CPPSRC set(MODE_CPPSRC
proc_afsk.cpp proc_afsk.cpp
) )
DeclareTargets(PAFS afsk) DeclareTargets(PAFT afsktx)
### AFSK RX
set(MODE_CPPSRC
proc_afskrx.cpp
)
DeclareTargets(PAFR afskrx)
### AIS ### AIS

View File

@ -93,9 +93,9 @@ void AFSKProcessor::execute(const buffer_c8_t& buffer) {
} }
void AFSKProcessor::on_message(const Message* const msg) { void AFSKProcessor::on_message(const Message* const msg) {
const auto message = *reinterpret_cast<const AFSKConfigureMessage*>(msg); const auto message = *reinterpret_cast<const AFSKTxConfigureMessage*>(msg);
if (message.id == Message::ID::AFSKConfigure) { if (message.id == Message::ID::AFSKTxConfigure) {
if (message.samples_per_bit) { if (message.samples_per_bit) {
afsk_samples_per_bit = message.samples_per_bit; afsk_samples_per_bit = message.samples_per_bit;
afsk_phase_inc_mark = message.phase_inc_mark * AFSK_DELTA_COEF; afsk_phase_inc_mark = message.phase_inc_mark * AFSK_DELTA_COEF;

View File

@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* *
* This file is part of PortaPack. * This file is part of PortaPack.
* *
@ -20,90 +21,93 @@
*/ */
#include "proc_afskrx.hpp" #include "proc_afskrx.hpp"
#include "sine_table.hpp"
#include "portapack_shared_memory.hpp" #include "portapack_shared_memory.hpp"
using namespace lpc43xx; #include "event_m4.hpp"
void AFSKRXProcessor::execute(const buffer_c8_t& buffer) { #include <cstdint>
if( !configured ) { #include <cstddef>
return;
} void AFSKRxProcessor::execute(const buffer_c8_t& buffer) {
/* Called every 2048/3072000 second -- 1500Hz. */ // This is called at 1500Hz
if (!configured) return;
const auto decim_0_out = decim_0.execute(buffer, dst_buffer); const auto decim_0_out = decim_0.execute(buffer, dst_buffer);
const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer); const auto 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, work_audio_buffer); feed_channel_stats(channel_out);
auto audio = demod.execute(channel_out, audio_buffer);
/*static uint64_t audio_present_history = 0; for (size_t c = 0; c < audio.count; c++) {
const auto audio_present_now = squelch.execute(audio);
audio_present_history = (audio_present_history << 1) | (audio_present_now ? 1 : 0); const int32_t sample_int = audio.p[c] * 32768.0f;
const bool audio_present = (audio_present_history != 0); const int32_t audio_sample = __SSAT(sample_int, 16);
*/
//if( !audio_present ) { /*slicer_sr <<= 1;
// Zero audio buffer. slicer_sr |= (audio_sample < 0); // Do we need hysteresis ?
/*for(size_t i=0; i<audio.count; i++) {
if ((i % 3) > 1) // Detect transitions to adjust clock
audio.p[i] = 4096; if ((slicer_sr ^ (slicer_sr >> 1)) & 1) {
if (sphase < (0x8000u - sphase_delta_half))
sphase += sphase_delta_eighth;
else else
audio.p[i] = -4096; sphase -= sphase_delta_eighth;
}*/
//}
//audio_hpf.execute_in_place(audio);
for(size_t i=0; i<audio.count; i++) {
if (spur > 10) {
if (audio.p[i] > 2000)
sign = 1;
if (audio.p[i] < -2000)
sign = 0;
spur = 0;
} else {
spur++;
} }
if (sign != prev_sign) {
if (freq_timer < 15) // 48 sphase += sphase_delta;*/
bit = 0;
else // Symbol time elapsed
bit++; //if (sphase >= 0x10000u) {
freq_timer = 0; // sphase &= 0xFFFFu;
}
prev_sign = sign; rx_data <<= 1;
if (freq_timer < 1000) freq_timer++; // TODO: Limit in a more intelligent way rx_data |= 1;
bit_count++;
if (bit_count == 8) {
data_message.byte = rx_data;
shared_memory.application_queue.push(data_message);
bit_count = 0;
}
//}
} }
if (bit_timer >= 40) {
bit_timer = 0;
// Check bit state here !
} else {
bit_timer++;
}
if (sc >= 600) {
sc = 0;
//AFSKDataMessage message;
//memcpy(message.data,aud,128*2);
//shared_memory.application_queue.push(message);
audc = 0;
} else {
sc++;
}
if (audc < 4) {
memcpy(aud+(audc*32),audio.p,32*2);
audc++;
}
audio_output.write(audio);
} }
void AFSKRXProcessor::data_handler( void AFSKRxProcessor::on_message(const Message* const message) {
const double data if (message->id == Message::ID::AFSKRxConfigure)
) { configure(*reinterpret_cast<const AFSKRxConfigureMessage*>(message));
/*AFSKDataMessage message; }
message.data = 'T';
shared_memory.application_queue.push(message);*/ void AFSKRxProcessor::configure(const AFSKRxConfigureMessage& message) {
constexpr size_t decim_0_input_fs = baseband_fs;
constexpr size_t decim_0_output_fs = decim_0_input_fs / decim_0.decimation_factor;
constexpr size_t decim_1_input_fs = decim_0_output_fs;
constexpr size_t decim_1_output_fs = decim_1_input_fs / decim_1.decimation_factor;
constexpr size_t channel_filter_input_fs = decim_1_output_fs;
const size_t channel_filter_output_fs = channel_filter_input_fs / 2;
const size_t demod_input_fs = channel_filter_output_fs;
decim_0.configure(taps_16k0_decim_0.taps, 33554432);
decim_1.configure(taps_16k0_decim_1.taps, 131072);
channel_filter.configure(taps_16k0_channel.taps, 2);
demod.configure(demod_input_fs, 5000);
bitrate = message.bitrate;
sphase_delta = 0x10000u * bitrate / 24000;
sphase_delta_half = sphase_delta / 2; // Just for speed
sphase_delta_eighth = sphase_delta / 8;
configured = true;
}
int main() {
EventDispatcher event_dispatcher { std::make_unique<AFSKRxProcessor>() };
event_dispatcher.run();
return 0;
} }

View File

@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* *
* This file is part of PortaPack. * This file is part of PortaPack.
* *
@ -23,46 +24,59 @@
#define __PROC_AFSKRX_H__ #define __PROC_AFSKRX_H__
#include "baseband_processor.hpp" #include "baseband_processor.hpp"
#include "baseband_thread.hpp"
#include "rssi_thread.hpp"
#include "dsp_decimate.hpp" #include "dsp_decimate.hpp"
#include "dsp_demodulate.hpp" #include "dsp_demodulate.hpp"
#include "audio_output.hpp" #include "fifo.hpp"
#include "message.hpp" #include "message.hpp"
class AFSKRXProcessor : public BasebandProcessor { class AFSKRxProcessor : public BasebandProcessor {
public: 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;
private: private:
std::array<complex16_t, 512> dst; static constexpr size_t baseband_fs = 3072000;
BasebandThread baseband_thread { baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive };
RSSIThread rssi_thread { NORMALPRIO + 10 };
std::array<complex16_t, 512> dst { };
const buffer_c16_t dst_buffer { const buffer_c16_t dst_buffer {
dst.data(), dst.data(),
dst.size() dst.size()
}; };
const buffer_f32_t work_audio_buffer { std::array<float, 32> audio { };
(float*)dst.data(), const buffer_f32_t audio_buffer {
sizeof(dst) / sizeof(float) audio.data(),
audio.size()
}; };
dsp::decimate::FIRAndDecimateComplex channel_filter; // Can't use FIFO class here since it only allows power-of-two sizes
dsp::demodulate::FM demod; // 48000 5000 std::array<int32_t, 24000/1200> delay_line { 0 };
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0; dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0 { };
dsp::decimate::FIRC16xR16x32Decim8 decim_1; dsp::decimate::FIRC16xR16x32Decim8 decim_1 { };
dsp::decimate::FIRAndDecimateComplex channel_filter { };
dsp::demodulate::FM demod { };
AudioOutput audio_output; uint32_t bitrate { };
uint32_t sphase { 0 };
uint16_t bit_timer = 0, freq_timer = 0; uint32_t sphase_delta { 0 };
uint16_t sc; uint32_t sphase_delta_half { 0 };
uint8_t audc, spur, sign, prev_sign, bit = 0; uint32_t sphase_delta_eighth { 0 };
uint32_t rx_data { 0 };
int16_t aud[128]; uint32_t bit_count { 0 };
void data_handler(const double data);
bool configured { false }; bool configured { false };
void configure(const NBFMConfigureMessage& message); void configure(const AFSKRxConfigureMessage& message);
AFSKDataMessage data_message { 0 };
}; };
#endif/*__PROC_TPMS_H__*/ #endif/*__PROC_TPMS_H__*/

View File

@ -59,7 +59,7 @@ private:
int8_t re { 0 }, im { 0 }; int8_t re { 0 }, im { 0 };
AudioLevelMessage level_message { }; AudioLevelReportMessage level_message { };
TXDoneMessage txdone_message { }; TXDoneMessage txdone_message { };
}; };

View File

@ -71,12 +71,13 @@ public:
CaptureThreadDone = 18, CaptureThreadDone = 18,
ReplayConfig = 19, ReplayConfig = 19,
ReplayThreadDone = 20, ReplayThreadDone = 20,
AFSKRxConfigure = 21,
TXDone = 30, TXDone = 30,
Retune = 31, Retune = 31,
TonesConfigure = 32, TonesConfigure = 32,
AFSKConfigure = 33, AFSKTxConfigure = 33,
PWMRSSIConfigure = 34, PWMRSSIConfigure = 34,
OOKConfigure = 35, OOKConfigure = 35,
RDSConfigure = 36, RDSConfigure = 36,
@ -93,11 +94,12 @@ public:
POCSAGPacket = 50, POCSAGPacket = 50,
ADSBFrame = 51, ADSBFrame = 51,
AFSKData = 52,
RequestSignal = 52, RequestSignal = 60,
FIFOData = 53, FIFOData = 61,
AudioLevel = 54, AudioLevelReport = 70,
MAX MAX
}; };
@ -331,6 +333,18 @@ public:
adsb::ADSBFrame frame; adsb::ADSBFrame frame;
}; };
class AFSKDataMessage : public Message {
public:
constexpr AFSKDataMessage(
const uint_fast8_t byte
) : Message { ID::AFSKData },
byte { byte }
{
}
uint_fast8_t byte;
};
class ShutdownMessage : public Message { class ShutdownMessage : public Message {
public: public:
constexpr ShutdownMessage( constexpr ShutdownMessage(
@ -598,7 +612,17 @@ public:
bool done = false; bool done = false;
}; };
class AFSKRxConfigureMessage : public Message {
public:
constexpr AFSKRxConfigureMessage(
const uint32_t bitrate
) : Message { ID::AFSKRxConfigure },
bitrate(bitrate)
{
}
const uint32_t bitrate;
};
class PWMRSSIConfigureMessage : public Message { class PWMRSSIConfigureMessage : public Message {
public: public:
@ -665,10 +689,10 @@ public:
uint32_t range = 0; uint32_t range = 0;
}; };
class AudioLevelMessage : public Message { class AudioLevelReportMessage : public Message {
public: public:
constexpr AudioLevelMessage( constexpr AudioLevelReportMessage(
) : Message { ID::AudioLevel } ) : Message { ID::AudioLevelReport }
{ {
} }
@ -729,16 +753,16 @@ public:
const uint32_t tone_delta; const uint32_t tone_delta;
}; };
class AFSKConfigureMessage : public Message { class AFSKTxConfigureMessage : public Message {
public: public:
constexpr AFSKConfigureMessage( constexpr AFSKTxConfigureMessage(
const uint32_t samples_per_bit, const uint32_t samples_per_bit,
const uint32_t phase_inc_mark, const uint32_t phase_inc_mark,
const uint32_t phase_inc_space, const uint32_t phase_inc_space,
const uint8_t repeat, const uint8_t repeat,
const uint32_t fm_delta, const uint32_t fm_delta,
const uint8_t symbol_count const uint8_t symbol_count
) : Message { ID::AFSKConfigure }, ) : Message { ID::AFSKTxConfigure },
samples_per_bit(samples_per_bit), samples_per_bit(samples_per_bit),
phase_inc_mark(phase_inc_mark), phase_inc_mark(phase_inc_mark),
phase_inc_space(phase_inc_space), phase_inc_space(phase_inc_space),

View File

@ -64,6 +64,7 @@ private:
}; };
constexpr image_tag_t image_tag_adsb_rx { 'P', 'A', 'D', 'R' }; constexpr image_tag_t image_tag_adsb_rx { 'P', 'A', 'D', 'R' };
constexpr image_tag_t image_tag_afsk_rx { 'P', 'A', 'F', 'R' };
constexpr image_tag_t image_tag_ais { 'P', 'A', 'I', 'S' }; constexpr image_tag_t image_tag_ais { 'P', 'A', 'I', 'S' };
constexpr image_tag_t image_tag_am_audio { 'P', 'A', 'M', 'A' }; constexpr image_tag_t image_tag_am_audio { 'P', 'A', 'M', 'A' };
constexpr image_tag_t image_tag_capture { 'P', 'C', 'A', 'P' }; constexpr image_tag_t image_tag_capture { 'P', 'C', 'A', 'P' };
@ -76,7 +77,7 @@ constexpr image_tag_t image_tag_wideband_spectrum { 'P', 'S', 'P', 'E' };
constexpr image_tag_t image_tag_jammer { 'P', 'J', 'A', 'M' }; constexpr image_tag_t image_tag_jammer { 'P', 'J', 'A', 'M' };
constexpr image_tag_t image_tag_audio_tx { 'P', 'A', 'T', 'X' }; constexpr image_tag_t image_tag_audio_tx { 'P', 'A', 'T', 'X' };
constexpr image_tag_t image_tag_afsk { 'P', 'A', 'F', 'S' }; constexpr image_tag_t image_tag_afsk { 'P', 'A', 'F', 'T' };
constexpr image_tag_t image_tag_tones { 'P', 'T', 'O', 'N' }; constexpr image_tag_t image_tag_tones { 'P', 'T', 'O', 'N' };
constexpr image_tag_t image_tag_rds { 'P', 'R', 'D', 'S' }; constexpr image_tag_t image_tag_rds { 'P', 'R', 'D', 'S' };
constexpr image_tag_t image_tag_ook { 'P', 'O', 'O', 'K' }; constexpr image_tag_t image_tag_ook { 'P', 'O', 'O', 'K' };