diff --git a/firmware/application/baseband_api.cpp b/firmware/application/baseband_api.cpp index e989ea52e..0e340a009 100644 --- a/firmware/application/baseband_api.cpp +++ b/firmware/application/baseband_api.cpp @@ -280,6 +280,11 @@ void set_pocsag() { send_message(&message); } +void set_flex() { + const FlexConfigureMessage message{}; + send_message(&message); +} + void set_adsb() { const ADSBConfigureMessage message{}; send_message(&message); diff --git a/firmware/application/baseband_api.hpp b/firmware/application/baseband_api.hpp index 06598962b..3e7844aed 100644 --- a/firmware/application/baseband_api.hpp +++ b/firmware/application/baseband_api.hpp @@ -74,6 +74,7 @@ void kill_afsk(); void set_afsk(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word); void set_fsk(const size_t deviation); void set_aprs(const uint32_t baudrate); +void set_flex(); void set_btlerx(uint8_t channel_number); void set_btletx(uint8_t channel_number, char* macAddress, char* advertisementData, uint8_t pduType); diff --git a/firmware/application/external/external.cmake b/firmware/application/external/external.cmake index 66a9ceb3e..5d4c45bb1 100644 --- a/firmware/application/external/external.cmake +++ b/firmware/application/external/external.cmake @@ -207,6 +207,11 @@ set(EXTCPPSRC #gfxEQ external/gfxeq/main.cpp external/gfxeq/ui_gfxeq.cpp + + #flex_RX + external/flex/main.cpp + external/flex/ui_flex_rx.cpp + ) set(EXTAPPLIST @@ -260,4 +265,5 @@ set(EXTAPPLIST scanner level gfxeq + flex_rx ) diff --git a/firmware/application/external/external.ld b/firmware/application/external/external.ld index 4f03965f0..de6d15e50 100644 --- a/firmware/application/external/external.ld +++ b/firmware/application/external/external.ld @@ -73,6 +73,7 @@ MEMORY ram_external_app_scanner (rwx) : org = 0xADE00000, len = 32k ram_external_app_level (rwx) : org = 0xADE10000, len = 32k ram_external_app_gfxeq (rwx) : org = 0xADE20000, len = 32k + ram_external_app_flex_rx (rwx) : org = 0xADE30000, len = 32k } SECTIONS @@ -375,4 +376,10 @@ SECTIONS KEEP(*(.external_app.app_gfxeq.application_information)); *(*ui*external_app*gfxeq*); } > ram_external_app_gfxeq + + .external_app_flex_rx : ALIGN(4) SUBALIGN(4) + { + KEEP(*(.external_app.app_flex_rx.application_information)); + *(*ui*external_app*flex_rx*); + } > ram_external_app_flex_rx } diff --git a/firmware/application/external/flex/main.cpp b/firmware/application/external/flex/main.cpp new file mode 100644 index 000000000..9ab5be9a1 --- /dev/null +++ b/firmware/application/external/flex/main.cpp @@ -0,0 +1,70 @@ +/* + * ------------------------------------------------------------ + * | Made by RocketGod | + * | Find me at https://betaskynet.com | + * | Argh matey! | + * ------------------------------------------------------------ + */ + +#include "ui.hpp" +#include "ui_flex_rx.hpp" +#include "ui_navigation.hpp" +#include "external_app.hpp" + +namespace ui::external_app::flex_rx { +void initialize_app(ui::NavigationView& nav) { + nav.push(); +} +} // namespace ui::external_app::flex_rx + +extern "C" { + +__attribute__((section(".external_app.app_flex_rx.application_information"), used)) application_information_t _application_information_flex_rx = { + /*.memory_location = */ (uint8_t*)0x00000000, + /*.externalAppEntry = */ ui::external_app::flex_rx::initialize_app, + /*.header_version = */ CURRENT_HEADER_VERSION, + /*.app_version = */ VERSION_MD5, + + /*.app_name = */ "Flex RX", + /*.bitmap_data = */ { + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xF8, + 0x1F, + 0x04, + 0x20, + 0x02, + 0x40, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xAB, + 0xDF, + 0xAB, + 0xDF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + }, + /*.icon_color = */ ui::Color::green().v, + /*.menu_location = */ app_location_t::RX, + /*.desired_menu_position = */ -1, + + /*.m4_app_tag = */ {'P', 'F', 'L', 'X'}, + /*.m4_app_offset = */ 0x00000000, +}; +} \ No newline at end of file diff --git a/firmware/application/external/flex/ui_flex_rx.cpp b/firmware/application/external/flex/ui_flex_rx.cpp new file mode 100644 index 000000000..c2c82fa30 --- /dev/null +++ b/firmware/application/external/flex/ui_flex_rx.cpp @@ -0,0 +1,261 @@ +/* + * ------------------------------------------------------------ + * | Made by RocketGod | + * | Find me at https://betaskynet.com | + * | Argh matey! | + * ------------------------------------------------------------ + */ + +#include "ui_flex_rx.hpp" +#include "audio.hpp" +#include "baseband_api.hpp" +#include "string_format.hpp" +#include "file_path.hpp" +#include "portapack.hpp" +#include "portapack_shared_memory.hpp" + +namespace ui::external_app::flex_rx { + +void FlexLogger::log_decoded(const Timestamp& timestamp, const std::string& data) { + log_file.write_entry(timestamp, data); +} + +void FlexRxView::focus() { + field_frequency.focus(); +} + +FlexRxView::FlexRxView(NavigationView& nav) + : nav_{nav} { + baseband::run_prepared_image(portapack::memory::map::m4_code.base()); + + add_children({&rssi, &channel, &field_rf_amp, &field_lna, &field_vga, + &field_volume, &field_frequency, &check_log, &text_debug, + &console, &sync_status}); + + field_frequency.set_value(929614000); + field_frequency.set_step(100); + + check_log.set_value(logging); + check_log.on_select = [this](Checkbox&, bool v) { + logging = v; + }; + + logger = std::make_unique(); + if (logger) + logger->append(logs_dir / u"FLEX.TXT"); + + baseband::set_flex(); + + audio::set_rate(audio::Rate::Hz_24000); + audio::output::start(); + + portapack::receiver_model.enable(); +} + +FlexRxView::~FlexRxView() { + audio::output::stop(); + portapack::receiver_model.disable(); + baseband::shutdown(); +} + +uint8_t diff_bit_count(uint32_t left, uint32_t right) { + uint32_t diff = left ^ right; + uint8_t count = 0; + for (size_t i = 0; i < sizeof(diff) * 8; ++i) { + if (((diff >> i) & 0x1) == 1) + ++count; + } + return count; +} + +void FlexRxView::on_packet(const FlexPacketMessage& message) { + const auto& packet = message.packet; + const auto& batch = packet.batch(); + auto timestamp = to_string_datetime(packet.timestamp(), HM); + auto bitrate = packet.bitrate(); + std::string str_log = timestamp + " " + to_string_dec_uint(bitrate) + " "; + + if (batch.size() < 8) return; // Ensure full block + + uint32_t frame_info = batch[0]; + size_t vsa = (frame_info >> 10) & 0x3F; + size_t asa = ((frame_info >> 8) & 0x03) + 1; + + for (size_t j = asa; j < vsa && j < batch.size(); j++) { + uint32_t address_word = batch[j]; + uint32_t data = address_word >> 11; + uint32_t parity = address_word & 0x7FF; + + uint32_t syndrome = 0; + uint32_t temp = data; + for (int k = 0; k < 21; ++k) { + if (temp & 1) syndrome ^= (0x7FF >> k); + temp >>= 1; + } + syndrome ^= parity; + + if (syndrome != 0) { + uint32_t error_pos = 0; + bool correctable = false; + for (int k = 0; k < 10; ++k) { + if (syndrome == (static_cast(1) << k)) { + error_pos = k; + correctable = true; + break; + } + } + if (correctable && error_pos < 21) { + data ^= (1 << error_pos); + } else { + continue; + } + } + + bool long_address = (data & 0x7FFFF) < 0x008001 || ((data & 0x7FFFF) > 0x1E0000 && (data & 0x7FFFF) < 0x1F0001) || (data & 0x7FFFF) > 0x1F7FFE; + size_t vb = vsa + j - asa; + if (vb >= batch.size()) continue; + + uint32_t vector_word = batch[vb]; + uint32_t vector_data = vector_word >> 11; + uint32_t vector_parity = vector_word & 0x7FF; + + syndrome = 0; + temp = vector_data; + for (int k = 0; k < 21; ++k) { + if (temp & 1) syndrome ^= (0x7FF >> k); + temp >>= 1; + } + syndrome ^= vector_parity; + + if (syndrome != 0) { + uint32_t error_pos = 0; + bool correctable = false; + for (int k = 0; k < 10; ++k) { + if (syndrome == (static_cast(1) << k)) { + error_pos = k; + correctable = true; + break; + } + } + if (correctable && error_pos < 21) { + vector_data ^= (1 << error_pos); + } else { + continue; + } + } + + uint8_t vector_type = (vector_data >> 4) & 0x07; + if (long_address && j + 1 >= batch.size()) continue; + + std::string display_text; + uint32_t address; + if (!long_address) { + address = (data & 0x7FFFF) - 32768; + display_text = to_string_dec_uint(address); + } else { + uint32_t second_word = batch[j + 1]; + uint32_t second_data = second_word >> 11; + syndrome = 0; + temp = second_data; + for (int k = 0; k < 21; ++k) { + if (temp & 1) syndrome ^= (0x7FF >> k); + temp >>= 1; + } + syndrome ^= (second_word & 0x7FF); + if (syndrome == 0) { + address = ((second_data ^ 0x1FFFFF) << 15) + 2068480 + (data & 0x7FFFF); + display_text = to_string_dec_uint(address); + } else { + continue; + } + j++; + } + + console.writeln(display_text); + if (logger && logging) logger->log_decoded(packet.timestamp(), str_log + display_text); + + if (vector_type == 5) { // Alpha message + int w1 = (vector_data >> 7) & 0x7F; + int w2 = ((vector_data >> 14) & 0x7F) + w1 - 1; + if (w1 < 0 || w2 < 0 || static_cast(w1) >= batch.size() || static_cast(w2) >= batch.size()) continue; + + std::string message_text; + for (int k = w1; k <= w2; k++) { + uint32_t msg_word = batch[k]; + data = msg_word >> 11; + parity = msg_word & 0x7FF; + + syndrome = 0; + temp = data; + for (int m = 0; m < 21; ++m) { + if (temp & 1) syndrome ^= (0x7FF >> m); + temp >>= 1; + } + syndrome ^= parity; + + if (syndrome != 0) { + uint32_t error_pos = 0; + bool correctable = false; + for (int m = 0; m < 10; ++m) { + if (syndrome == (static_cast(1) << m)) { + error_pos = m; + correctable = true; + break; + } + } + if (correctable && error_pos < 21) { + data ^= (1 << error_pos); + } else { + continue; + } + } + + for (int m = 0; m < 20; m += 7) { + uint8_t byte = (data >> (13 - m)) & 0x7F; + if (byte >= 32 && byte < 127) message_text += (char)byte; + } + } + if (!message_text.empty()) { + console.writeln(message_text); + if (logger && logging) logger->log_decoded(packet.timestamp(), str_log + message_text); + } + } + } + console_color++; +} + +void FlexRxView::on_stats(const FlexStatsMessage& stats) { + if (stats.has_sync != last_has_sync) { + std::string str_console = "\x1B\x31"; + if (stats.has_sync) { + str_console += "SYNC ACQUIRED - Baud: " + to_string_dec_uint(stats.baud_rate); + } else if (last_has_sync) { + str_console += "SYNC LOST"; + } + console.writeln(str_console); + + sync_status.set_color(stats.has_sync ? Color::green() : Color::red()); + } + + if (stats.baud_rate != last_baud_rate || + stats.has_sync != last_has_sync || + stats.current_frames != last_frames || + stats.current_bits != last_bits) { + + std::string debug_text = "BR:" + to_string_dec_uint(stats.baud_rate) + + " Sync:" + (stats.has_sync ? "Y" : "N") + + " Fr:" + to_string_dec_uint(stats.current_frames); + text_debug.set(debug_text); + + last_baud_rate = stats.baud_rate; + last_has_sync = stats.has_sync; + last_frames = stats.current_frames; + last_bits = stats.current_bits; + } +} + +void FlexRxView::on_freqchg(int64_t freq) { + field_frequency.set_value(freq); +} + +} // namespace ui::external_app::flex_rx \ No newline at end of file diff --git a/firmware/application/external/flex/ui_flex_rx.hpp b/firmware/application/external/flex/ui_flex_rx.hpp new file mode 100644 index 000000000..ecee3c025 --- /dev/null +++ b/firmware/application/external/flex/ui_flex_rx.hpp @@ -0,0 +1,96 @@ +/* + * ------------------------------------------------------------ + * | Made by RocketGod | + * | Find me at https://betaskynet.com | + * | Argh matey! | + * ------------------------------------------------------------ + */ + +#ifndef __UI_FLEX_RX_H__ +#define __UI_FLEX_RX_H__ + +#include "ui.hpp" +#include "ui_language.hpp" +#include "ui_navigation.hpp" +#include "ui_receiver.hpp" +#include "ui_freq_field.hpp" +#include "app_settings.hpp" +#include "radio_state.hpp" +#include "log_file.hpp" +#include "utility.hpp" + +namespace ui::external_app::flex_rx { + +class FlexLogger { + public: + Optional append(const std::filesystem::path& filename) { + return log_file.append(filename); + } + void log_decoded(const Timestamp& timestamp, const std::string& data); + + private: + LogFile log_file{}; +}; + +class FlexRxView : public View { + public: + FlexRxView(NavigationView& nav); + ~FlexRxView(); + void focus() override; + std::string title() const override { return "Flex RX"; }; + + private: + void on_packet(const FlexPacketMessage& message); + void on_stats(const FlexStatsMessage& stats); + void on_freqchg(int64_t freq); + + NavigationView& nav_; + RxRadioState radio_state_{}; + app_settings::SettingsManager settings_{"rx_flex", app_settings::Mode::RX}; + uint8_t console_color{0}; + bool logging{false}; + std::string current_message{}; + uint32_t last_address{0}; + + uint16_t last_baud_rate{0}; + bool last_has_sync{false}; + uint8_t last_frames{0}; + uint32_t last_bits{0}; + + 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}}; + AudioVolumeField field_volume{{28 * 8, 0 * 16}}; + RxFrequencyField field_frequency{{0 * 8, 0 * 16}, nav_}; + Checkbox check_log{{0 * 8, 1 * 16}, 3, LanguageHelper::currentMessages[LANG_LOG], false}; + Text text_debug{{0 * 8, 12 + 2 * 16, screen_width, 16}, ""}; + Console console{{0, 4 * 16, 240, 176}}; + Rectangle sync_status{{214, 1 * 16, 12, 12}, Color::red()}; + + std::unique_ptr logger{}; + + MessageHandlerRegistration message_handler_packet{ + Message::ID::FlexPacket, + [this](Message* const p) { + const auto message = static_cast(p); + this->on_packet(*message); + }}; + MessageHandlerRegistration message_handler_stats{ + Message::ID::FlexStats, + [this](Message* const p) { + const auto message = static_cast(p); + this->on_stats(*message); + }}; + MessageHandlerRegistration message_handler_freqchg{ + Message::ID::FreqChangeCommand, + [this](Message* const p) { + const auto message = static_cast(p); + this->on_freqchg(message->freq); + }}; +}; + +} // namespace ui::external_app::flex_rx + +#endif /*__UI_FLEX_RX_H__*/ \ No newline at end of file diff --git a/firmware/baseband/CMakeLists.txt b/firmware/baseband/CMakeLists.txt index ace697266..f4832fe39 100644 --- a/firmware/baseband/CMakeLists.txt +++ b/firmware/baseband/CMakeLists.txt @@ -441,6 +441,13 @@ set(MODE_CPPSRC ) DeclareTargets(PPO2 pocsag2) +### FLEX RX + +set(MODE_CPPSRC + proc_flex.cpp +) +DeclareTargets(PFLX flex_rx) + ### RDS set(MODE_CPPSRC diff --git a/firmware/baseband/proc_flex.cpp b/firmware/baseband/proc_flex.cpp new file mode 100644 index 000000000..fed31c76e --- /dev/null +++ b/firmware/baseband/proc_flex.cpp @@ -0,0 +1,465 @@ +/* + * ------------------------------------------------------------ + * | Made by RocketGod | + * | Find me at https://betaskynet.com | + * | Argh matey! | + * ------------------------------------------------------------ + */ + +#include "proc_flex.hpp" +#include "event_m4.hpp" +#include "audio_dma.hpp" +#include "dsp_fir_taps.hpp" + +#include +#include +#include +#include +#include + +namespace { + +uint8_t diff_bit_count(uint32_t left, uint32_t right) { + uint32_t diff = left ^ right; + uint8_t count = 0; + for (size_t i = 0; i < sizeof(diff) * 8; ++i) { + if (((diff >> i) & 0x1) == 1) + ++count; + } + return count; +} + +} // namespace + +FlexProcessor::FlexProcessor() + : BasebandProcessor(), + state(State::Idle), + sync_buffer(0), + frame_buffer(), + current_word(0), + bit_count(0), + current_baud_rate(1600) { +} + +void FlexAudioNormalizer::execute_in_place(const buffer_f32_t& audio) { + if (counter_ >= 24'000) { + max_ *= 0.9f; + min_ *= 0.9f; + counter_ = 0; + calculate_thresholds(); + } + + counter_ += audio.count; + + for (size_t i = 0; i < audio.count; ++i) { + auto& val = audio.p[i]; + + if (val > max_) { + max_ = val; + calculate_thresholds(); + } + if (val < min_) { + min_ = val; + calculate_thresholds(); + } + + if (val >= t_high_) + val = 1.0f; + else if (val >= t_mid_high_) + val = 0.33f; + else if (val <= t_low_) + val = -1.0f; + else if (val <= t_mid_low_) + val = -0.33f; + else + val = 0.0f; + } +} + +void FlexAudioNormalizer::calculate_thresholds() { + auto center = (max_ + min_) / 2.0f; + auto range = (max_ - min_) / 2.0f; + auto step = range / 3.0f; + + t_high_ = center + step * 1.5f; + t_mid_high_ = center + step * 0.5f; + t_mid_low_ = center - step * 0.5f; + t_low_ = center - step * 1.5f; +} + +void FlexBitQueue::push(uint8_t symbol) { + data_ = (data_ << 2) | (symbol & 0x3); + if (count_ < max_size_) ++count_; +} + +bool FlexBitQueue::pop(uint8_t& symbol) { + if (count_ == 0) return false; + + --count_; + symbol = (data_ >> (count_ * 2)) & 0x3; + return true; +} + +void FlexBitQueue::reset() { + data_ = 0; + count_ = 0; +} + +uint8_t FlexBitQueue::size() const { + return count_; +} + +uint32_t FlexBitQueue::data() const { + return data_; +} + +void FlexBitExtractor::extract_bits(const buffer_f32_t& audio) { + for (size_t i = 0; i < audio.count; ++i) { + auto sample = audio.p[i]; + + if (current_rate_) { + if (current_rate_->handle_sample(sample)) { + uint8_t symbol; + if (sample >= 0.66f) + symbol = 0x0; + else if (sample >= 0.0f) + symbol = 0x1; + else if (sample >= -0.66f) + symbol = 0x2; + else + symbol = 0x3; + bits_.push(symbol); + } + } else { + for (auto& rate : known_rates_) { + if (rate.handle_sample(sample) && + diff_bit_count(rate.bits.data(), sync1_pattern) <= 4) { + rate.is_stable = true; + current_rate_ = &rate; + } + } + } + } +} + +void FlexBitExtractor::configure(uint32_t sample_rate) { + sample_rate_ = sample_rate; + for (auto& rate : known_rates_) { + rate.sample_interval = sample_rate / static_cast(rate.baud_rate); + } +} + +void FlexBitExtractor::reset() { + current_rate_ = nullptr; + for (auto& rate : known_rates_) + rate.reset(); +} + +uint16_t FlexBitExtractor::baud_rate() const { + return current_rate_ ? current_rate_->baud_rate : 0; +} + +bool FlexBitExtractor::RateInfo::handle_sample(float sample) { + samples_until_next -= 1; + if (samples_until_next > 0) return false; + + bool bit_pushed = false; + float delta = std::abs(sample - prev_value); + + switch (state) { + case State::WaitForSample: + state = State::ReadyToSend; + break; + + case State::ReadyToSend: + if (!is_stable && delta > 0.33f) { + samples_until_next += (sample_interval / 8.0f); + } else { + state = State::WaitForSample; + bit_pushed = true; + bits.push(sample >= 0.0f ? (sample >= 0.66f ? 0x0 : 0x1) : (sample >= -0.66f ? 0x2 : 0x3)); + } + break; + } + + samples_until_next += sample_interval; + prev_value = sample; + return bit_pushed; +} + +void FlexBitExtractor::RateInfo::reset() { + state = State::WaitForSample; + samples_until_next = 0.0f; + prev_value = 0.0f; + is_stable = false; + bits.reset(); +} + +void FlexCodewordExtractor::process_bits() { + uint8_t symbol; + while (bits_.pop(symbol)) { + data_ = (data_ << 2) | symbol; + bit_count_ += 2; + + if (bit_count_ >= data_bit_count) { + if (!has_sync_) { + if (diff_bit_count(data_, sync1_codeword) <= 4) { + handle_sync(); + } + } else { + save_current_codeword(); + } + } + } +} + +void FlexCodewordExtractor::flush() { + if (word_count_ == 0) return; + pad_idle(); + handle_batch_complete(); +} + +void FlexCodewordExtractor::reset() { + clear_data_bits(); + has_sync_ = false; + word_count_ = 0; +} + +void FlexCodewordExtractor::clear_data_bits() { + data_ = 0; + bit_count_ = 0; +} + +void FlexCodewordExtractor::take_one_symbol() { + uint8_t symbol; + if (bits_.pop(symbol)) { + data_ = (data_ << 2) | symbol; + if (bit_count_ < data_bit_count) + bit_count_ += 2; + } +} + +void FlexCodewordExtractor::handle_sync() { + clear_data_bits(); + has_sync_ = true; + word_count_ = 0; +} + +void FlexCodewordExtractor::save_current_codeword() { + batch_[word_count_++] = data_; + clear_data_bits(); + + if (word_count_ >= flex_batch_size) { + handle_batch_complete(); + } +} + +void FlexCodewordExtractor::handle_batch_complete() { + on_batch_(*this); + has_sync_ = false; + word_count_ = 0; +} + +void FlexCodewordExtractor::pad_idle() { + while (word_count_ < flex_batch_size) { + batch_[word_count_++] = 0xAAAAAAAA; + } +} + +void FlexProcessor::execute(const buffer_c8_t& buffer) { + if (!configured) return; + + 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 channel_out = channel_filter.execute(decim_1_out, dst_buffer); + auto audio = demod.execute(channel_out, audio_buffer); + + squelch.set_threshold(0.01); + squelch_history = (squelch_history << 1) | (squelch.execute(audio) ? 1 : 0); + + lpf.execute_in_place(audio); + normalizer.execute_in_place(audio); + audio_output.write(audio); + + bit_extractor.extract_bits(audio); + word_extractor.process_bits(); + + static uint64_t sync_buffer64 = 0; + static uint32_t block_buffer[8] = {0}; + static uint8_t word_count = 0; + static uint8_t bit_count = 0; + + for (size_t i = 0; i < audio.count; ++i) { + float sample = audio.p[i]; + + sync_buffer64 = (sync_buffer64 << 2); + if (sample >= 0.66f) + sync_buffer64 |= 0x0; + else if (sample >= 0.0f) + sync_buffer64 |= 0x1; + else if (sample >= -0.66f) + sync_buffer64 |= 0x2; + else + sync_buffer64 |= 0x3; + + switch (state) { + case State::Idle: + if (squelch.execute(audio)) { + uint32_t sync1 = (sync_buffer64 >> 32) & 0xFFFF; + uint32_t sync2 = sync_buffer64 & 0xFFFF; + if (diff_bit_count(sync1, 0xA6C6) <= 2 && diff_bit_count(sync2, 0xAAAA) <= 2) { + uint32_t sync_pre = (sync_buffer64 >> 48) & 0xFFFF; + uint32_t sync_post = (sync_buffer64 >> 16) & 0xFFFF; + if (diff_bit_count(sync_pre, 0x870C) <= 2 && diff_bit_count(sync_post, 0x78F3) <= 2) { + current_baud_rate = 1600; + state = State::FrameData; + word_count = 0; + bit_count = 0; + } else if (diff_bit_count(sync_pre, 0xB068) <= 2 && diff_bit_count(sync_post, 0x4F97) <= 2) { + current_baud_rate = 3200; + state = State::FrameData; + word_count = 0; + bit_count = 0; + } else if (diff_bit_count(sync_pre, 0xDEA0) <= 2 && diff_bit_count(sync_post, 0x215F) <= 2) { + current_baud_rate = 6400; + state = State::FrameData; + word_count = 0; + bit_count = 0; + } + } + } + break; + + case State::FrameData: + block_buffer[word_count] = (block_buffer[word_count] << 2); + if (sample >= 0.66f) + block_buffer[word_count] |= 0x0; + else if (sample >= 0.0f) + block_buffer[word_count] |= 0x1; + else if (sample >= -0.66f) + block_buffer[word_count] |= 0x2; + else + block_buffer[word_count] |= 0x3; + + bit_count += 2; + + if (bit_count >= 32) { + word_count++; + bit_count = 0; + if (word_count >= 8) { + packet.set_flag(FlexPacketFlag::FLEX_NORMAL); + packet.set_timestamp(Timestamp::now()); + packet.set_bitrate(current_baud_rate); + flex_batch_t batch{}; + for (size_t j = 0; j < 8; j++) { + batch[j] = block_buffer[j]; + } + packet.set(batch); + FlexPacketMessage message(packet); + shared_memory.application_queue.push(message); + word_count = 0; + state = State::Idle; // Reset to find next sync + } + } + break; + + default: + state = State::Idle; + break; + } + } + + samples_processed += buffer.count; + if (samples_processed >= stat_update_threshold) { + send_stats(); + samples_processed -= stat_update_threshold; + } +} + +void FlexProcessor::process_frame() { + // No longer used; packet sent directly in execute() +} + +void FlexProcessor::on_message(const Message* const message) { + switch (message->id) { + case Message::ID::FlexConfigure: + configure(); + break; + + case Message::ID::NBFMConfigure: + squelch.set_threshold(0.01); + break; + + case Message::ID::AudioBeep: + on_beep_message(*reinterpret_cast(message)); + break; + + default: + break; + } +} + +void FlexProcessor::configure() { + constexpr size_t decim_0_output_fs = baseband_fs / decim_0.decimation_factor; + constexpr size_t decim_1_output_fs = decim_0_output_fs / decim_1.decimation_factor; + constexpr size_t channel_filter_output_fs = decim_1_output_fs / 2; + constexpr size_t demod_input_fs = channel_filter_output_fs; + + decim_0.configure(taps_200k_decim_0.taps); + decim_1.configure(taps_16k0_decim_1.taps); + channel_filter.configure(taps_16k0_channel.taps, 2); + + demod.configure(demod_input_fs, 4800); + + audio_output.configure(false); + bit_extractor.configure(demod_input_fs); + + configured = true; +} + +void FlexProcessor::flush() { + word_extractor.flush(); +} + +void FlexProcessor::reset() { + state = State::Idle; + sync_buffer = 0; + frame_buffer.clear(); + current_word = 0; + bit_count = 0; + + bits.reset(); + bit_extractor.reset(); + word_extractor.reset(); + samples_processed = 0; +} + +void FlexProcessor::send_stats() const { + FlexStatsMessage message( + current_word, + frame_buffer.size(), + state != State::Idle, + current_baud_rate); + shared_memory.application_queue.push(message); +} + +void FlexProcessor::send_packet() { + packet.set_flag(FlexPacketFlag::FLEX_NORMAL); + packet.set_timestamp(Timestamp::now()); + packet.set_bitrate(bit_extractor.baud_rate()); + packet.set(word_extractor.batch()); + + FlexPacketMessage message(packet); + shared_memory.application_queue.push(message); +} + +void FlexProcessor::on_beep_message(const AudioBeepMessage& message) { + audio::dma::beep_start(message.freq, message.sample_rate, message.duration_ms); +} + +int main() { + audio::dma::init_audio_out(); + EventDispatcher event_dispatcher{std::make_unique()}; + event_dispatcher.run(); + return 0; +} \ No newline at end of file diff --git a/firmware/baseband/proc_flex.hpp b/firmware/baseband/proc_flex.hpp new file mode 100644 index 000000000..46af1e76b --- /dev/null +++ b/firmware/baseband/proc_flex.hpp @@ -0,0 +1,177 @@ +/* + * ------------------------------------------------------------ + * | Made by RocketGod | + * | Find me at https://betaskynet.com | + * | Argh matey! | + * ------------------------------------------------------------ + */ + +#ifndef __PROC_FLEX_H__ +#define __PROC_FLEX_H__ + +#include "audio_output.hpp" +#include "baseband_processor.hpp" +#include "baseband_thread.hpp" +#include "dsp_decimate.hpp" +#include "dsp_demodulate.hpp" +#include "dsp_iir_config.hpp" +#include "dsp_fir_taps.hpp" +#include "message.hpp" +#include "portapack_shared_memory.hpp" +#include "rssi_thread.hpp" + +#include +#include +#include +#include +#include + +using flex_batch_t = std::array; + +class FlexAudioNormalizer { + public: + void execute_in_place(const buffer_f32_t& audio); + + private: + void calculate_thresholds(); + uint32_t counter_ = 0; + float min_ = 99.0f; + float max_ = -99.0f; + float t_high_ = 1.0f; + float t_mid_high_ = 0.33f; + float t_mid_low_ = -0.33f; + float t_low_ = -1.0f; +}; + +class FlexBitQueue { + public: + void push(uint8_t symbol); + bool pop(uint8_t& symbol); + void reset(); + uint8_t size() const; + uint32_t data() const; + + private: + uint32_t data_ = 0; + uint8_t count_ = 0; + static constexpr uint8_t max_size_ = sizeof(data_) * 8 / 2; +}; + +class FlexBitExtractor { + public: + FlexBitExtractor(FlexBitQueue& bits) : bits_{bits} {} + void extract_bits(const buffer_f32_t& audio); + void configure(uint32_t sample_rate); + void reset(); + uint16_t baud_rate() const; + + private: + static constexpr uint32_t sync1_pattern = 0xA6C6AAAA; + + struct RateInfo { + enum class State : uint8_t { + WaitForSample, + ReadyToSend + }; + const uint16_t baud_rate = 0; + float sample_interval = 0.0; + State state = State::WaitForSample; + float samples_until_next = 0.0; + float prev_value = 0.0f; + bool is_stable = false; + FlexBitQueue bits{}; + bool handle_sample(float sample); + void reset(); + }; + std::array known_rates_{RateInfo{1600}, RateInfo{3200}, RateInfo{6400}}; + FlexBitQueue& bits_; + uint32_t sample_rate_ = 0; + RateInfo* current_rate_ = nullptr; +}; + +class FlexCodewordExtractor { + public: + using batch_t = flex_batch_t; + using batch_handler_t = std::function; + FlexCodewordExtractor(FlexBitQueue& bits, batch_handler_t on_batch) : bits_{bits}, on_batch_{on_batch} {} + void process_bits(); + void flush(); + void reset(); + const batch_t& batch() const { return batch_; } + uint32_t current() const { return data_; } + uint8_t count() const { return word_count_; } + bool has_sync() const { return has_sync_; } + + private: + static constexpr uint32_t sync1_codeword = 0xA6C6AAAA; + static constexpr uint8_t data_bit_count = sizeof(uint32_t) * 8; + void clear_data_bits(); + void take_one_symbol(); + void handle_sync(); + void save_current_codeword(); + void handle_batch_complete(); + void pad_idle(); + FlexBitQueue& bits_; + batch_handler_t on_batch_{}; + bool has_sync_ = false; + uint32_t data_ = 0; + uint8_t bit_count_ = 0; + uint8_t word_count_ = 0; + batch_t batch_{}; +}; + +class FlexProcessor : public BasebandProcessor { + public: + FlexProcessor(); + void execute(const buffer_c8_t& buffer) override; + void on_message(const Message* const message) override; + + private: + enum class State { + Idle, + FrameSync, + FrameData + }; + + static constexpr size_t baseband_fs = 3072000; + static constexpr uint8_t stat_update_interval = 10; + static constexpr uint32_t stat_update_threshold = baseband_fs / stat_update_interval; + void configure(); + void flush(); + void reset(); + void send_stats() const; + void send_packet(); + void process_frame(); + void on_beep_message(const AudioBeepMessage& message); + bool configured = false; + std::array dst{}; + const buffer_c16_t dst_buffer{dst.data(), dst.size()}; + std::array audio{}; + const buffer_f32_t audio_buffer{audio.data(), audio.size()}; + dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0{}; + dsp::decimate::FIRC16xR16x32Decim8 decim_1{}; + dsp::decimate::FIRAndDecimateComplex channel_filter{}; + dsp::demodulate::FM demod{}; + FMSquelch squelch{}; + uint64_t squelch_history = 0; + IIRBiquadFilter lpf{{{0.04125354f, 0.082507070f, 0.04125354f}, {1.00000000f, -1.34896775f, 0.51398189f}}}; + FlexAudioNormalizer normalizer{}; + AudioOutput audio_output{}; + FlexPacket packet{}; + uint32_t samples_processed = 0; + FlexBitQueue bits{}; + FlexBitExtractor bit_extractor{bits}; + FlexCodewordExtractor word_extractor{bits, [this](FlexCodewordExtractor&) { send_packet(); }}; + + State state = State::Idle; + uint32_t sync_buffer = 0; + std::vector frame_buffer; + uint32_t current_word = 0; + uint8_t bit_count = 0; + uint16_t current_baud_rate = 1600; + + BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive}; + RSSIThread rssi_thread{}; +}; + +#endif /*__PROC_FLEX_H__*/ \ No newline at end of file diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index 14e899f9a..d3a37df27 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -130,6 +130,9 @@ class Message { WeFaxRxConfigure = 73, WeFaxRxStatusData = 74, WeFaxRxImageData = 75, + FlexConfigure = 76, + FlexPacket = 77, + FlexStats = 78, MAX }; @@ -142,6 +145,60 @@ class Message { const ID id; }; +// FlexPacketFlag and FlexPacket definitions +enum FlexPacketFlag : uint8_t { + FLEX_NORMAL = 0, + CRC_ERROR = 1, + SYNC_ERROR = 2 +}; + +constexpr size_t flex_batch_size = 88; +using flex_batch_t = std::array; + +class FlexPacket { + public: + void set(const flex_batch_t& batch) { batch_ = batch; } + void set_flag(FlexPacketFlag flag) { flag_ = flag; } + void set_timestamp(Timestamp timestamp) { timestamp_ = timestamp; } + void set_bitrate(uint16_t bitrate) { bitrate_ = bitrate; } + + const flex_batch_t& batch() const { return batch_; } + FlexPacketFlag flag() const { return flag_; } + Timestamp timestamp() const { return timestamp_; } + uint16_t bitrate() const { return bitrate_; } + uint32_t operator[](size_t index) const { return batch_[index]; } + + private: + flex_batch_t batch_{}; + FlexPacketFlag flag_ = FLEX_NORMAL; + Timestamp timestamp_{}; + uint16_t bitrate_ = 0; +}; + +class FlexConfigureMessage : public Message { + public: + constexpr FlexConfigureMessage() + : Message{ID::FlexConfigure} {} + // Add configuration fields if needed in the future +}; + +class FlexPacketMessage : public Message { + public: + constexpr FlexPacketMessage(const FlexPacket& p) + : Message{ID::FlexPacket}, packet{p} {} + const FlexPacket& packet; +}; + +class FlexStatsMessage : public Message { + public: + constexpr FlexStatsMessage(uint32_t cb, uint8_t cf, bool hs, uint16_t br) + : Message{ID::FlexStats}, current_bits{cb}, current_frames{cf}, has_sync{hs}, baud_rate{br} {} + uint32_t current_bits; + uint8_t current_frames; + bool has_sync; + uint16_t baud_rate; +}; + struct RSSIStatistics { uint32_t accumulator{0}; uint8_t min{0};