diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index 014f9a86..ba5f7814 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -244,6 +244,7 @@ set(CPPSRC apps/ais_app.cpp apps/analog_audio_app.cpp apps/analog_tv_app.cpp + apps/ble_app.cpp apps/capture_app.cpp apps/ert_app.cpp apps/gps_sim_app.cpp @@ -268,6 +269,7 @@ set(CPPSRC apps/ui_fileman.cpp apps/ui_flash_utility.cpp apps/ui_freqman.cpp + apps/ui_fsk_rx.cpp apps/ui_iq_trim.cpp apps/ui_jammer.cpp # apps/ui_keyfob.cpp diff --git a/firmware/application/apps/ble_app.cpp b/firmware/application/apps/ble_app.cpp new file mode 100644 index 00000000..894b314a --- /dev/null +++ b/firmware/application/apps/ble_app.cpp @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2017 Furrtek + * Copyright (C) 2023 TJ Baginski + * + * 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 "ble_app.hpp" +#include "ui_modemsetup.hpp" + +#include "modems.hpp" +#include "audio.hpp" +#include "rtc_time.hpp" +#include "baseband_api.hpp" +#include "string_format.hpp" +#include "portapack_persistent_memory.hpp" + +using namespace portapack; +using namespace modems; + +void BLELogger::log_raw_data(const std::string& data) { + log_file.write_entry(data); +} + +std::string pad_string_with_spaces(int snakes) { + std::string paddedStr(snakes, ' '); + return paddedStr; +} + +namespace ui { +template <> +void RecentEntriesTable::draw( + const Entry& entry, + const Rect& target_rect, + Painter& painter, + const Style& style) { + std::string line = to_string_hex(entry.macAddress & 0xFF, 2); + + line += ":" + to_string_hex((entry.macAddress >> 8) & 0xFF, 2); + line += ":" + to_string_hex((entry.macAddress >> 16) & 0xFF, 2); + line += ":" + to_string_hex((entry.macAddress >> 24) & 0xFF, 2); + line += ":" + to_string_hex((entry.macAddress >> 32) & 0xFF, 2); + line += ":" + to_string_hex((entry.macAddress >> 40), 2); + + // Handle spacing for negative sign. + uint8_t db_spacing = entry.dbValue > 0 ? 7 : 6; + + // Pushing single digit values down right justified. + if (entry.dbValue > 9 || entry.dbValue < -9) { + db_spacing--; + } + + line += pad_string_with_spaces(db_spacing) + to_string_dec_int(entry.dbValue); + + line.resize(target_rect.width() / 8, ' '); + painter.draw_string(target_rect.location(), style, line); +} + +BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const BleRecentEntry& entry) + : nav_{nav}, + entry_{entry} { + add_children({&button_done, + &labels}); + + button_done.on_select = [this](const ui::Button&) { + nav_.pop(); + }; +} + +void BleRecentEntryDetailView::update_data() { +} + +void BleRecentEntryDetailView::focus() { + button_done.focus(); +} + +Rect BleRecentEntryDetailView::draw_field( + Painter& painter, + const Rect& draw_rect, + const Style& style, + const std::string& label, + const std::string& value) { + const int label_length_max = 4; + + painter.draw_string(Point{draw_rect.left(), draw_rect.top()}, style, label); + painter.draw_string(Point{draw_rect.left() + (label_length_max + 1) * 8, draw_rect.top()}, style, value); + + return {draw_rect.left(), draw_rect.top() + draw_rect.height(), draw_rect.width(), draw_rect.height()}; +} + +void BleRecentEntryDetailView::paint(Painter& painter) { + View::paint(painter); + + const auto s = style(); + const auto rect = screen_rect(); + + auto field_rect = Rect{rect.left(), rect.top() + 16, rect.width(), 16}; + + uint8_t type[total_data_lines]; + uint8_t length[total_data_lines]; + uint8_t data[total_data_lines][40]; + + int currentByte = 0; + int currentPacket = 0; + int i = 0; + int j = 0; + int k = 0; + + for (currentByte = 0; (currentByte < entry_.packetData.dataLen) && (currentPacket < total_data_lines);) { + length[currentPacket] = entry_.packetData.data[currentByte++]; + type[currentPacket] = entry_.packetData.data[currentByte++]; + + // This should never happen, but in here just in case. + // Break because we can't trust rest of data. + // if (length[currentPacket] > entry_.packetData.dataLen) + // { + // break; + // } + + // Subtract 1 because type is part of the length. + for (i = 0; i < length[currentPacket] - 1; i++) { + data[currentPacket][i] = entry_.packetData.data[currentByte++]; + } + + currentPacket++; + } + + for (i = 0; i < currentPacket; i++) { + uint8_t number_data_lines = ceil((float)(length[i] - 1) / 10.0); + uint8_t current_line = 0; + std::array data_strings{}; + + for (j = 0; (j < (number_data_lines * 10)) && (j < length[i] - 1); j++) { + if ((j / 10) != current_line) { + current_line++; + } + + data_strings[current_line] += to_string_hex(data[i][j], 2); + } + + // Read the type back to the total length. + field_rect = draw_field(painter, field_rect, s, to_string_hex(length[i]), to_string_hex(type[i]) + pad_string_with_spaces(3) + data_strings[0]); + + if (number_data_lines > 1) { + for (k = 1; k < number_data_lines; k++) { + if (data_strings[k].empty()) { + field_rect = draw_field(painter, field_rect, s, "", pad_string_with_spaces(5) + data_strings[k]); + } + } + } + } +} + +void BleRecentEntryDetailView::set_entry(const BleRecentEntry& entry) { + entry_ = entry; + set_dirty(); +} + +static std::uint64_t get_freq_by_channel_number(uint8_t channel_number) { + uint64_t freq_hz; + + switch (channel_number) { + case 37: + freq_hz = 2'402'000'000ull; + break; + case 38: + freq_hz = 2'426'000'000ull; + break; + case 39: + freq_hz = 2'480'000'000ull; + break; + case 0 ... 10: + freq_hz = 2'404'000'000ull + channel_number * 2'000'000ull; + break; + case 11 ... 36: + freq_hz = 2'428'000'000ull + (channel_number - 11) * 2'000'000ull; + break; + default: + freq_hz = UINT64_MAX; + } + + return freq_hz; +} + +void BLERxView::focus() { + field_frequency.focus(); +} + +BLERxView::BLERxView(NavigationView& nav) + : nav_{nav} { + baseband::run_image(portapack::spi_flash::image_tag_btle_rx); + + add_children({&rssi, + &channel, + &field_rf_amp, + &field_lna, + &field_vga, + &options_region, + &field_frequency, + &check_log, + &recent_entries_view, + &recent_entry_detail_view}); + + recent_entry_detail_view.hidden(true); + + recent_entries_view.on_select = [this](const BleRecentEntry& entry) { + nav_.push(entry); + }; + + // field_frequency.set_value(get_freq_by_channel_number(37)); + field_frequency.set_step(2000000); + + check_log.set_value(logging); + + check_log.on_select = [this](Checkbox&, bool v) { + str_log = ""; + logging = v; + }; + + options_region.on_change = [this](size_t, int32_t i) { + field_frequency.set_value(get_freq_by_channel_number(i)); + channel_number = i; + + baseband::set_btle(channel_number); + }; + + options_region.set_selected_index(0, true); + + logger = std::make_unique(); + + if (logger) + logger->append(LOG_ROOT_DIR "/BLELOG_" + to_string_timestamp(rtc_time::now()) + ".TXT"); + + // Auto-configure modem for LCR RX (will be removed later) + baseband::set_btle(channel_number); + + receiver_model.enable(); +} + +void BLERxView::on_data(BlePacketData* packet) { + std::string str_console = ""; + + if (!logging) { + str_log = ""; + } + + switch ((ADV_PDU_TYPE)packet->type) { + case ADV_IND: + str_console += "ADV_IND"; + break; + case ADV_DIRECT_IND: + str_console += "ADV_DIRECT_IND"; + break; + case ADV_NONCONN_IND: + str_console += "ADV_NONCONN_IND"; + break; + case SCAN_REQ: + str_console += "SCAN_REQ"; + break; + case SCAN_RSP: + str_console += "SCAN_RSP"; + break; + case CONNECT_REQ: + str_console += "CONNECT_REQ"; + break; + case ADV_SCAN_IND: + str_console += "ADV_SCAN_IND"; + break; + case RESERVED0: + case RESERVED1: + case RESERVED2: + case RESERVED3: + case RESERVED4: + case RESERVED5: + case RESERVED6: + case RESERVED7: + case RESERVED8: + str_console += "RESERVED"; + break; + default: + str_console += "UNKNOWN"; + break; + } + + // str_console += to_string_dec_uint(value); + + str_console += " Len: "; + str_console += to_string_dec_uint(packet->size); + + str_console += "\n"; + + str_console += "Mac"; + str_console += ":" + to_string_hex(packet->macAddress[0], 2); + str_console += ":" + to_string_hex(packet->macAddress[1], 2); + str_console += ":" + to_string_hex(packet->macAddress[2], 2); + str_console += ":" + to_string_hex(packet->macAddress[3], 2); + str_console += ":" + to_string_hex(packet->macAddress[4], 2); + str_console += ":" + to_string_hex(packet->macAddress[5], 2); + + str_console += "\n"; + str_console += "Data:"; + + int i; + + for (i = 0; i < packet->dataLen; i++) { + str_console += " " + to_string_hex(packet->data[i], 2); + } + + str_console += "\n"; + + // Start of Packet stuffing. + uint64_t macAddressEncoded = 0; + + memcpy(&macAddressEncoded, packet->macAddress, sizeof(uint64_t)); + + // Masking off the top 2 bytes to avoid invalid keys. + auto& entry = ::on_packet(recent, macAddressEncoded & 0xFFFFFFFFFFFF); + + entry.dbValue = packet->max_dB; + entry.packetData.type = packet->type; + entry.packetData.size = packet->size; + entry.packetData.dataLen = packet->dataLen; + + entry.packetData.macAddress[0] = packet->macAddress[0]; + entry.packetData.macAddress[1] = packet->macAddress[1]; + entry.packetData.macAddress[2] = packet->macAddress[2]; + entry.packetData.macAddress[3] = packet->macAddress[3]; + entry.packetData.macAddress[4] = packet->macAddress[4]; + entry.packetData.macAddress[5] = packet->macAddress[5]; + + for (int i = 0; i < packet->dataLen; i++) { + entry.packetData.data[i] = packet->data[i]; + } + + // entry.update(packet); + recent_entries_view.set_dirty(); + + // TODO: Crude hack, should be a more formal listener arrangement... + if (entry.key() == recent_entry_detail_view.entry().key()) { + recent_entry_detail_view.set_entry(entry); + } + + // Log at End of Packet. + if (logger && logging) { + logger->log_raw_data(str_console); + } +} + +void BLERxView::set_parent_rect(const Rect new_parent_rect) { + View::set_parent_rect(new_parent_rect); + const Rect content_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height}; + recent_entries_view.set_parent_rect(content_rect); + recent_entry_detail_view.set_parent_rect(content_rect); +} + +BLERxView::~BLERxView() { + receiver_model.disable(); + baseband::shutdown(); +} + +// BleRecentEntry +// void BleRecentEntry::update(const BlePacketData * packet) +// { + +// } + +} /* namespace ui */ diff --git a/firmware/application/apps/ble_app.hpp b/firmware/application/apps/ble_app.hpp new file mode 100644 index 00000000..6f3031b5 --- /dev/null +++ b/firmware/application/apps/ble_app.hpp @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2017 Furrtek + * Copyright (C) 2023 TJ Baginski + * + * 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 __BLE_APP_H__ +#define __BLE_APP_H__ + +#include "ui.hpp" +#include "ui_navigation.hpp" +#include "ui_receiver.hpp" +#include "ui_freq_field.hpp" +#include "ui_record_view.hpp" +#include "app_settings.hpp" +#include "radio_state.hpp" +#include "log_file.hpp" +#include "utility.hpp" + +#include "recent_entries.hpp" + +class BLELogger { + public: + Optional append(const std::string& filename) { + return log_file.append(filename); + } + + void log_raw_data(const std::string& data); + + private: + LogFile log_file{}; +}; + +namespace ui { +typedef enum { + ADV_IND = 0, + ADV_DIRECT_IND = 1, + ADV_NONCONN_IND = 2, + SCAN_REQ = 3, + SCAN_RSP = 4, + CONNECT_REQ = 5, + ADV_SCAN_IND = 6, + RESERVED0 = 7, + RESERVED1 = 8, + RESERVED2 = 9, + RESERVED3 = 10, + RESERVED4 = 11, + RESERVED5 = 12, + RESERVED6 = 13, + RESERVED7 = 14, + RESERVED8 = 15 +} ADV_PDU_TYPE; + +struct BleRecentEntry { + using Key = uint64_t; + + static constexpr Key invalid_key = 0xffffffff; + + uint64_t macAddress; + int dbValue; + BlePacketData packetData; + + BleRecentEntry() + : BleRecentEntry{0} { + } + + BleRecentEntry( + const uint64_t macAddress) + : macAddress{macAddress}, + dbValue{}, + packetData{} { + } + + Key key() const { + return macAddress; + } + + // void update(const BlePacketData * packet); +}; + +using BleRecentEntries = RecentEntries; +using BleRecentEntriesView = RecentEntriesView; + +class BleRecentEntryDetailView : public View { + public: + BleRecentEntryDetailView(NavigationView& nav, const BleRecentEntry& entry); + + void set_entry(const BleRecentEntry& new_entry); + const BleRecentEntry& entry() const { return entry_; }; + + void update_data(); + void focus() override; + void paint(Painter&) override; + + private: + NavigationView& nav_; + BleRecentEntry entry_{}; + + static constexpr uint8_t total_data_lines{5}; + + Labels labels{ + {{0 * 8, 0 * 16}, "Len", Color::light_grey()}, + {{5 * 8, 0 * 16}, "Type", Color::light_grey()}, + {{10 * 8, 0 * 16}, "Value", Color::light_grey()}, + }; + + Button button_done{ + {125, 224, 96, 24}, + "Done"}; + + bool send_updates{false}; + + Rect draw_field( + Painter& painter, + const Rect& draw_rect, + const Style& style, + const std::string& label, + const std::string& value); +}; + +class BLERxView : public View { + public: + BLERxView(NavigationView& nav); + ~BLERxView(); + + void set_parent_rect(const Rect new_parent_rect) override; + void paint(Painter&) override{}; + + void focus() override; + + std::string title() const override { return "BLE RX"; }; + + private: + void on_data(BlePacketData* packetData); + + NavigationView& nav_; + RxRadioState radio_state_{ + 2402000000 /* frequency */, + 4000000 /* bandwidth */, + 4000000 /* sampling rate */, + ReceiverModel::Mode::WidebandFMAudio}; + app_settings::SettingsManager settings_{ + "BLE Rx", app_settings::Mode::RX}; + + uint8_t console_color{0}; + uint32_t prev_value{0}; + uint8_t channel_number = 37; + + static constexpr auto header_height = 12 + 2 * 16; + + RFAmpField field_rf_amp{ + {16 * 8, 0 * 16}}; + + LNAGainField field_lna{ + {18 * 8, 0 * 16}}; + + VGAGainField field_vga{ + {21 * 8, 0 * 16}}; + + RSSI rssi{ + {24 * 8, 0, 6 * 8, 4}}; + + Channel channel{ + {24 * 8, 5, 6 * 8, 4}}; + + RxFrequencyField field_frequency{ + {6 * 8, 0 * 16}, + nav_}; + + OptionsField options_region{ + {0 * 8, 0 * 8}, + 5, + {{"Ch.37 ", 37}, + {"Ch.38", 38}, + {"Ch.39", 39}}}; + + Console console{ + {0, 4 * 16, 240, 240}}; + + Checkbox check_log{ + {0 * 8, 1 * 16}, + 3, + "LOG", + false}; + + std::string str_log{""}; + bool logging{false}; + + std::unique_ptr logger{}; + + // const RecentEntriesColumns columns{{{"Source", 9}, + // {"Loc", 6}, + // {"Hits", 4}, + // {"Time", 8}}}; + + BleRecentEntries recent{}; + + const RecentEntriesColumns columns{{ + {"Mac Address", 20}, + {"dB", 20}, + }}; + + BleRecentEntry entry_{}; + BleRecentEntriesView recent_entries_view{columns, recent}; + BleRecentEntryDetailView recent_entry_detail_view{nav_, entry_}; + + MessageHandlerRegistration message_handler_packet{ + Message::ID::BlePacket, + [this](Message* const p) { + const auto message = static_cast(p); + this->on_data(message->packet); + }}; +}; + +} /* namespace ui */ + +#endif /*__UI_AFSK_RX_H__*/ diff --git a/firmware/application/apps/ui_about_simple.cpp b/firmware/application/apps/ui_about_simple.cpp index 3be7187a..47026703 100644 --- a/firmware/application/apps/ui_about_simple.cpp +++ b/firmware/application/apps/ui_about_simple.cpp @@ -37,6 +37,7 @@ void AboutView::update() { break; case 2: console.writeln("NotherNgineer,zxkmm,u-foka"); + console.writeln("Netro"); console.writeln(""); break; diff --git a/firmware/application/apps/ui_btle_rx.cpp b/firmware/application/apps/ui_btle_rx.cpp index 1db2c225..a7272011 100644 --- a/firmware/application/apps/ui_btle_rx.cpp +++ b/firmware/application/apps/ui_btle_rx.cpp @@ -70,7 +70,7 @@ BTLERxView::BTLERxView(NavigationView& nav) }; // Auto-configure modem for LCR RX (will be removed later) - baseband::set_btle(persistent_memory::modem_baudrate(), 8, 0, false); + baseband::set_btle(channel_number); audio::set_rate(audio::Rate::Hz_24000); audio::output::start(); diff --git a/firmware/application/apps/ui_btle_rx.hpp b/firmware/application/apps/ui_btle_rx.hpp index f071779d..963bfa1b 100644 --- a/firmware/application/apps/ui_btle_rx.hpp +++ b/firmware/application/apps/ui_btle_rx.hpp @@ -60,6 +60,8 @@ class BTLERxView : public View { uint8_t console_color{0}; uint32_t prev_value{0}; + static constexpr uint8_t channel_number = 37; + RFAmpField field_rf_amp{ {13 * 8, 0 * 16}}; LNAGainField field_lna{ diff --git a/firmware/application/apps/ui_fsk_rx.cpp b/firmware/application/apps/ui_fsk_rx.cpp new file mode 100644 index 00000000..6e0c6259 --- /dev/null +++ b/firmware/application/apps/ui_fsk_rx.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 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_fsk_rx.hpp" + +#include "audio.hpp" +#include "baseband_api.hpp" +#include "portapack_persistent_memory.hpp" +#include "string_format.hpp" +#include "utility.hpp" + +#include "ui_freqman.hpp" + +using namespace portapack; +namespace pmem = portapack::persistent_memory; + +void FskRxLogger::log_raw_data(const std::string& data, const uint32_t frequency) { + std::string entry = "Raw: F:" + to_string_dec_uint(frequency) + "Hz"; + + // // Raw hex dump of all the codewords + // for (size_t c = 0; c < 16; c++) + // entry += to_string_hex(packet[c], 8) + " "; + + log_file.write_entry(data + entry); +} + +void FskRxLogger::log_decoded(Timestamp timestamp, const std::string& text) { + log_file.write_entry(timestamp, text); +} + +namespace ui { +//--------------------------------------------------------------------------------------------------------------- +// Console View +//--------------------------------------------------------------------------------------------------------------- +FskRxAppConsoleView::FskRxAppConsoleView(NavigationView& nav, Rect parent_rect) + : View(parent_rect), nav_{nav} { + add_child(&console); +}; + +void FskRxAppConsoleView::on_packet(uint32_t value, bool is_data) { + if (is_data) { + console.write(to_string_dec_uint(value) + " "); + } +} + +void FskRxAppConsoleView::on_show() { + hidden(false); +} + +void FskRxAppConsoleView::on_hide() { + hidden(true); +} + +FskRxAppConsoleView::~FskRxAppConsoleView() { +} + +//--------------------------------------------------------------------------------------------------------------- +// Spectrum View +//--------------------------------------------------------------------------------------------------------------- +FskRxAppView::FskRxAppView(NavigationView& nav, Rect parent_rect) + : View(parent_rect), nav_{nav} { + add_child(&waterfall); + hidden(true); +} + +FskRxAppView::~FskRxAppView() { +} + +void FskRxAppView::focus() { +} + +void FskRxAppView::on_show() { + hidden(false); + waterfall.start(); +} + +void FskRxAppView::on_hide() { + hidden(true); + waterfall.stop(); +} + +//--------------------------------------------------------------------------------------------------------------- +// Base View +//--------------------------------------------------------------------------------------------------------------- +FskxRxMainView::FskxRxMainView(NavigationView& nav) + : nav_{nav} { + add_children({&tab_view, + &view_data, + &view_stream, + &labels, + &rssi, + &channel, + &field_rf_amp, + &field_lna, + &field_vga, + &field_frequency, + &deviation_frequency, + &record_view}); + + baseband::run_image(portapack::spi_flash::image_tag_fskrx); + + // DEBUG + record_view.on_error = [&nav](std::string message) { + nav.display_modal("Error", message); + }; + + deviation_frequency.on_change = [this](rf::Frequency f) { + refresh_ui(f); + }; + + // Set initial sampling rate + /* Bandwidth of 2FSK is 2 * Deviation */ + record_view.set_sampling_rate(initial_deviation * 2); + + field_frequency.set_value(initial_target_frequency); + deviation_frequency.set_value(initial_deviation); + + logger.append(LOG_ROOT_DIR "/FSKRX.TXT"); + + baseband::set_fsk(initial_deviation); + + audio::output::start(); + receiver_model.enable(); +} + +void FskxRxMainView::handle_decoded(Timestamp timestamp, const std::string& prefix) { + if (logging()) { + logger.log_decoded(timestamp, prefix); + } +} + +void FskxRxMainView::refresh_ui(rf::Frequency deviationHz) { + /* Nyquist would imply a sample rate of 2x bandwidth, but because the ADC + * provides 2 values (I,Q), the sample_rate is equal to bandwidth here. */ + /* Bandwidth of 2FSK is 2 * Deviation */ + auto sample_rate = deviationHz * 2; + + /* base_rate (bandwidth) is used for FFT calculation and display LCD, and also in recording writing SD Card rate. */ + /* ex. sampling_rate values, 4Mhz, when recording 500 kHz (BW) and fs 8 Mhz, when selected 1 Mhz BW ... */ + /* ex. recording 500kHz BW to .C16 file, base_rate clock 500kHz x2(I,Q) x 2 bytes (int signed) =2MB/sec rate SD Card. */ + + if (!view_stream.hidden()) { + view_stream.waterfall.stop(); + } + + // record_view determines the correct oversampling to apply and returns the actual sample rate. + // NB: record_view is what actually updates proc_capture baseband settings. + auto actual_sample_rate = record_view.set_sampling_rate(sample_rate); + + // Update the radio model with the actual sampling rate. + receiver_model.set_sampling_rate(actual_sample_rate); + + // Get suitable anti-aliasing BPF bandwidth for MAX2837 given the actual sample rate. + auto anti_alias_filter_bandwidth = filter_bandwidth_for_sampling_rate(actual_sample_rate); + receiver_model.set_baseband_bandwidth(anti_alias_filter_bandwidth); + + if (!view_stream.hidden()) { + view_stream.waterfall.start(); + } +} + +void FskxRxMainView::focus() { + field_frequency.focus(); +} + +void FskxRxMainView::set_parent_rect(const Rect new_parent_rect) { + View::set_parent_rect(new_parent_rect); + + ui::Rect waterfall_rect{0, 0, new_parent_rect.width(), new_parent_rect.height() - header_height}; + view_stream.waterfall.set_parent_rect(waterfall_rect); +} + +FskxRxMainView::~FskxRxMainView() { + audio::output::stop(); + receiver_model.disable(); + baseband::shutdown(); +} +} /* namespace ui */ diff --git a/firmware/application/apps/ui_fsk_rx.hpp b/firmware/application/apps/ui_fsk_rx.hpp new file mode 100644 index 00000000..e9e2aca0 --- /dev/null +++ b/firmware/application/apps/ui_fsk_rx.hpp @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2018 Furrtek + * Copyright (C) 2023 gullradriel, Nilorea Studio 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_FSK_RX_H__ +#define __UI_FSK_RX_H__ + +#include "ui_widget.hpp" +#include "ui_freq_field.hpp" +#include "ui_receiver.hpp" +#include "ui_record_view.hpp" +#include "ui_rssi.hpp" +#include "ui_spectrum.hpp" +#include "ui_styles.hpp" +#include "ui_tabview.hpp" + +#include "app_settings.hpp" +#include "log_file.hpp" +#include "radio_state.hpp" +#include "pocsag_app.hpp" + +#include + +class FskRxLogger { + public: + Optional append(const std::string& filename) { + return log_file.append(filename); + } + + void log_raw_data(const std::string& data, const uint32_t frequency); + void log_decoded(Timestamp timestamp, const std::string& text); + + private: + LogFile log_file{}; +}; + +namespace ui { +class FskRxAppConsoleView : public View { + public: + FskRxAppConsoleView(NavigationView& nav, Rect parent_rec); + ~FskRxAppConsoleView(); + + std::string title() const override { return "FSK RX Data"; }; + + void on_packet(uint32_t value, bool is_data); + + void on_show() override; + void on_hide() override; + + private: + NavigationView& nav_; + + Console console{ + {0, 0, 240, 224}}; +}; + +class FskRxAppView : public View { + public: + FskRxAppView(NavigationView& nav, Rect parent_rect); + ~FskRxAppView(); + + void focus() override; + void on_show() override; + void on_hide() override; + + spectrum::WaterfallView waterfall{}; + + std::string title() const override { return "FSK RX Stream"; }; + + private: + NavigationView& nav_; + RxRadioState radio_state_{}; +}; + +class FskxRxMainView : public View { + public: + FskxRxMainView(NavigationView& nav); + ~FskxRxMainView(); + + void focus() override; + void set_parent_rect(const Rect new_parent_rect) override; + + std::string title() const override { return "FSK RX"; }; + + private: + static constexpr uint32_t initial_target_frequency = 902'075'000; + static constexpr ui::Dim header_height = (5 * 16); + + uint32_t initial_deviation{3750}; + + bool logging() const { return false; }; + bool logging_raw() const { return false; }; + + NavigationView& nav_; + Rect view_rect = {0, header_height, 240, 224}; + + FskRxAppView view_stream{nav_, view_rect}; + FskRxAppConsoleView view_data{nav_, view_rect}; + + TabView tab_view{ + {"Data", Color::yellow(), &view_data}, + {"Stream", Color::cyan(), &view_stream}}; + + void refresh_ui(rf::Frequency f); + void on_packet(uint32_t value, bool is_data); + void handle_decoded(Timestamp timestamp, const std::string& prefix); + + uint32_t last_address = 0; + FskRxLogger logger{}; + uint16_t packet_count = 0; + + RxFrequencyField field_frequency{ + {0 * 8, 4 * 8}, + nav_}; + + RFAmpField field_rf_amp{ + {11 * 8, 2 * 16}}; + + LNAGainField field_lna{ + {13 * 8, 2 * 16}}; + + VGAGainField field_vga{ + {16 * 8, 2 * 16}}; + + RSSI rssi{ + {19 * 8 - 4, 35, 6 * 8, 4}}; + + Channel channel{ + {19 * 8 - 4, 40, 6 * 8, 4}}; + + Labels labels{ + {{0 * 8, 3 * 16}, "Deviation:", Color::light_grey()}, + }; + + FrequencyField deviation_frequency{ + {10 * 8, 3 * 16}, + {3750, 500000}, + }; + + // DEBUG + RecordView record_view{ + {0 * 8, 4 * 16, 30 * 8, 1 * 16}, + u"FSKRX_????.C16", + u"FSKRX", + RecordView::FileType::RawS16, + 16384, + 3}; + + MessageHandlerRegistration message_handler_packet{ + Message::ID::AFSKData, + [this](Message* const p) { + const auto message = static_cast(p); + this->view_data.on_packet(message->value, message->is_data); + }}; +}; + +} /* namespace ui */ + +#endif /*__POCSAG_APP_H__*/ \ No newline at end of file diff --git a/firmware/application/baseband_api.cpp b/firmware/application/baseband_api.cpp index c4f76c42..dc934edb 100644 --- a/firmware/application/baseband_api.cpp +++ b/firmware/application/baseband_api.cpp @@ -149,12 +149,9 @@ void set_aprs(const uint32_t baudrate) { send_message(&message); } -void set_btle(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word) { +void set_btle(uint8_t channel_number) { const BTLERxConfigureMessage message{ - baudrate, - word_length, - trigger_value, - trigger_word}; + channel_number}; send_message(&message); } @@ -167,6 +164,17 @@ void set_nrf(const uint32_t baudrate, const uint32_t word_length, const uint32_t send_message(&message); } +void set_fsk(const size_t deviation) { + const FSKRxConfigureMessage message{ + taps_200k_decim_0, + taps_16k0_decim_1, + taps_11k0_channel, + 2, + deviation}; + + 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, const uint8_t afsk_repeat, const uint32_t afsk_bw, const uint8_t symbol_count) { const AFSKTxConfigureMessage message{ afsk_samples_per_bit, diff --git a/firmware/application/baseband_api.hpp b/firmware/application/baseband_api.hpp index b635aa15..fef615a0 100644 --- a/firmware/application/baseband_api.hpp +++ b/firmware/application/baseband_api.hpp @@ -69,9 +69,10 @@ void set_pitch_rssi(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, const uint8_t afsk_repeat, const uint32_t afsk_bw, const uint8_t symbol_count); 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_btle(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word); +void set_btle(uint8_t channel_number); void set_nrf(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word); diff --git a/firmware/application/ui/ui_receiver.cpp b/firmware/application/ui/ui_receiver.cpp index 5bfbcf1a..24756f82 100644 --- a/firmware/application/ui/ui_receiver.cpp +++ b/firmware/application/ui/ui_receiver.cpp @@ -42,6 +42,16 @@ FrequencyField::FrequencyField( set_focusable(true); } +FrequencyField::FrequencyField( + const Point parent_pos, + const rf::FrequencyRange range) + : Widget{{parent_pos, {8 * 10, 16}}}, + length_{11}, + range_{range} { + initial_switch_config_ = get_switches_long_press_config(); + set_focusable(true); +} + FrequencyField::~FrequencyField() { reset_switch_config(); } diff --git a/firmware/application/ui/ui_receiver.hpp b/firmware/application/ui/ui_receiver.hpp index d9be0a0b..b123700c 100644 --- a/firmware/application/ui/ui_receiver.hpp +++ b/firmware/application/ui/ui_receiver.hpp @@ -48,6 +48,7 @@ class FrequencyField : public Widget { using range_t = rf::FrequencyRange; FrequencyField(Point parent_pos); + FrequencyField(Point parent_pos, rf::FrequencyRange range); ~FrequencyField(); rf::Frequency value() const; @@ -66,8 +67,7 @@ class FrequencyField : public Widget { private: const size_t length_; - const range_t range_; - + range_t range_; rf::Frequency value_{0}; rf::Frequency step_{25000}; uint64_t last_ms_{0}; diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index 664e8f2b..daedb2bc 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -45,6 +45,7 @@ #include "ui_flash_utility.hpp" #include "ui_font_fixed_8x16.hpp" #include "ui_freqman.hpp" +#include "ui_fsk_rx.hpp" #include "ui_iq_trim.hpp" #include "ui_jammer.hpp" // #include "ui_keyfob.hpp" @@ -85,6 +86,7 @@ #include "ais_app.hpp" #include "analog_audio_app.hpp" #include "analog_tv_app.hpp" +#include "ble_app.hpp" #include "capture_app.hpp" #include "ert_app.hpp" #include "gps_sim_app.hpp" @@ -547,7 +549,8 @@ ReceiversMenuView::ReceiversMenuView(NavigationView& nav) { {"Analog TV", Color::yellow(), &bitmap_icon_sstv, [&nav]() { nav.push(); }}, {"APRS", Color::green(), &bitmap_icon_aprs, [&nav]() { nav.push(); }}, {"Audio", Color::green(), &bitmap_icon_speaker, [&nav]() { nav.push(); }}, - {"BTLE", Color::yellow(), &bitmap_icon_btle, [&nav]() { nav.push(); }}, + //{"BTLE", Color::yellow(), &bitmap_icon_btle, [&nav]() { nav.push(); }}, + {"BLE", Color::yellow(), &bitmap_icon_btle, [&nav]() { nav.push(); }}, {"ERT Meter", Color::green(), &bitmap_icon_ert, [&nav]() { nav.push(); }}, {"Level", Color::green(), &bitmap_icon_options_radio, [&nav]() { nav.push(); }}, {"NRF", Color::yellow(), &bitmap_icon_nrf, [&nav]() { nav.push(); }}, @@ -556,6 +559,7 @@ ReceiversMenuView::ReceiversMenuView(NavigationView& nav) { {"Recon", Color::green(), &bitmap_icon_scanner, [&nav]() { nav.push(); }}, {"Search", Color::yellow(), &bitmap_icon_search, [&nav]() { nav.push(); }}, {"TPMS Cars", Color::green(), &bitmap_icon_tpms, [&nav]() { nav.push(); }}, + // {"FSK RX", Color::yellow(), &bitmap_icon_remote, [&nav]() { nav.push(); }}, // {"DMR", Color::dark_grey(), &bitmap_icon_dmr, [&nav](){ nav.push(); }}, // {"SIGFOX", Color::dark_grey(), &bitmap_icon_fox, [&nav](){ nav.push(); }}, // {"LoRa", Color::dark_grey(), &bitmap_icon_lora, [&nav](){ nav.push(); }}, diff --git a/firmware/baseband/CMakeLists.txt b/firmware/baseband/CMakeLists.txt index 905d42c8..a97645f2 100644 --- a/firmware/baseband/CMakeLists.txt +++ b/firmware/baseband/CMakeLists.txt @@ -416,6 +416,13 @@ set(MODE_CPPSRC ) DeclareTargets(PFSK fsktx) +### FSK RX + +set(MODE_CPPSRC + proc_fsk_rx.cpp +) +DeclareTargets(PFSR fskrx) + ### Jammer set(MODE_CPPSRC diff --git a/firmware/baseband/proc_btlerx.cpp b/firmware/baseband/proc_btlerx.cpp index ed39115c..8c43881e 100644 --- a/firmware/baseband/proc_btlerx.cpp +++ b/firmware/baseband/proc_btlerx.cpp @@ -2,6 +2,7 @@ * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. * Copyright (C) 2016 Furrtek * Copyright (C) 2020 Shao + * Copyright (C) 2023 TJ Baginski * * This file is part of PortaPack. * @@ -26,245 +27,475 @@ #include "event_m4.hpp" +uint32_t BTLERxProcessor::crc_init_reorder(uint32_t crc_init) { + int i; + uint32_t crc_init_tmp, crc_init_input, crc_init_input_tmp; + + crc_init_input_tmp = crc_init; + crc_init_input = 0; + + crc_init_input = crc_init_input_tmp & 0xFF; + + crc_init_input_tmp = (crc_init_input_tmp >> 8); + crc_init_input = ((crc_init_input << 8) | (crc_init_input_tmp & 0xFF)); + + crc_init_input_tmp = (crc_init_input_tmp >> 8); + crc_init_input = ((crc_init_input << 8) | (crc_init_input_tmp & 0xFF)); + + crc_init_input = (crc_init_input << 1); + crc_init_tmp = 0; + + for (i = 0; i < 24; i++) { + crc_init_input = (crc_init_input >> 1); + crc_init_tmp = ((crc_init_tmp << 1) | (crc_init_input & 0x01)); + } + + return (crc_init_tmp); +} + +uint_fast32_t BTLERxProcessor::crc_update(uint_fast32_t crc, const void* data, size_t data_len) { + const unsigned char* d = (const unsigned char*)data; + unsigned int tbl_idx; + + while (data_len--) { + tbl_idx = (crc ^ *d) & 0xff; + crc = (crc_table[tbl_idx] ^ (crc >> 8)) & 0xffffff; + + d++; + } + + return crc & 0xffffff; +} + +uint_fast32_t BTLERxProcessor::crc24_byte(uint8_t* byte_in, int num_byte, uint32_t init_hex) { + uint_fast32_t crc = init_hex; + + crc = crc_update(crc, byte_in, num_byte); + + return (crc); +} + +bool BTLERxProcessor::crc_check(uint8_t* tmp_byte, int body_len, uint32_t crc_init) { + int crc24_checksum; + + crc24_checksum = crc24_byte(tmp_byte, body_len, crc_init); // 0x555555 --> 0xaaaaaa. maybe because byte order + checksumReceived = 0; + checksumReceived = ((checksumReceived << 8) | tmp_byte[body_len + 2]); + checksumReceived = ((checksumReceived << 8) | tmp_byte[body_len + 1]); + checksumReceived = ((checksumReceived << 8) | tmp_byte[body_len + 0]); + + return (crc24_checksum != checksumReceived); +} + +void BTLERxProcessor::scramble_byte(uint8_t* byte_in, int num_byte, const uint8_t* scramble_table_byte, uint8_t* byte_out) { + int i; + + for (i = 0; i < num_byte; i++) { + byte_out[i] = byte_in[i] ^ scramble_table_byte[i]; + } +} + +// void BTLERxProcessor::demod_byte(int num_byte, uint8_t *out_byte) +//{ +// int i, j; +// int I0, Q0, I1, Q1; +// uint8_t bit_decision; +// int sample_idx = 0; + +// for (i = 0; i < num_byte; i++) +// { +// out_byte[i] = 0; + +// for (j = 0; j < 8; j++) +// { +// I0 = dst_buffer.p[sample_idx].real(); +// Q0 = dst_buffer.p[sample_idx].imag(); +// I1 = dst_buffer.p[sample_idx + 1].real(); +// Q1 = dst_buffer.p[sample_idx + 1].imag(); + +// bit_decision = (I0 * Q1 - I1 * Q0) > 0 ? 1 : 0; + +// out_byte[i] = out_byte[i] | (bit_decision << j); + +// sample_idx += SAMPLE_PER_SYMBOL;;} +//} + +int BTLERxProcessor::parse_adv_pdu_payload_byte(uint8_t* payload_byte, int num_payload_byte, ADV_PDU_TYPE pdu_type, void* adv_pdu_payload) { + // Should at least have 6 bytes for the MAC Address. + // Also ensuring that there is at least 1 byte of data. + if (num_payload_byte <= 6) { + // printf("Error: Payload Too Short (only %d bytes)!\n", num_payload_byte); + return -1; + } + + if (pdu_type == ADV_IND || pdu_type == ADV_NONCONN_IND || pdu_type == SCAN_RSP || pdu_type == ADV_SCAN_IND) { + payload_type_0_2_4_6 = (ADV_PDU_PAYLOAD_TYPE_0_2_4_6*)adv_pdu_payload; + + macAddress[0] = payload_byte[5]; + macAddress[1] = payload_byte[4]; + macAddress[2] = payload_byte[3]; + macAddress[3] = payload_byte[2]; + macAddress[4] = payload_byte[1]; + macAddress[5] = payload_byte[0]; + + memcpy(payload_type_0_2_4_6->Data, payload_byte + 6, num_payload_byte - 6); + } + // Only processing advertisments for right now. + else { + return -1; + } + // else if (pdu_type == ADV_DIRECT_IND || pdu_type == SCAN_REQ) + // { + // if (num_payload_byte != 12) + // { + // //printf("Error: Payload length %d bytes. Need to be 12 for PDU Type %s!\n", num_payload_byte, ADV_PDU_TYPE_STR[pdu_type]); + // return(-1); + // } + + // payload_type_1_3 = (ADV_PDU_PAYLOAD_TYPE_1_3 *)adv_pdu_payload; + + // //AdvA = reorder_bytes_str( payload_bytes(1 : (2*6)) ); + // macAddress[0] = payload_byte[5]; + // macAddress[1] = payload_byte[4]; + // macAddress[2] = payload_byte[3]; + // macAddress[3] = payload_byte[2]; + // macAddress[4] = payload_byte[1]; + // macAddress[5] = payload_byte[0]; + + // //InitA = reorder_bytes_str( payload_bytes((2*6+1):end) ); + // payload_type_1_3->A1[0] = payload_byte[11]; + // payload_type_1_3->A1[1] = payload_byte[10]; + // payload_type_1_3->A1[2] = payload_byte[9]; + // payload_type_1_3->A1[3] = payload_byte[8]; + // payload_type_1_3->A1[4] = payload_byte[7]; + // payload_type_1_3->A1[5] = payload_byte[6]; + + // //payload_parse_result_str = ['AdvA:' AdvA ' InitA:' InitA]; + // } + // else if (pdu_type == CONNECT_REQ) + // { + // if (num_payload_byte != 34) + // { + // //printf("Error: Payload length %d bytes. Need to be 34 for PDU Type %s!\n", num_payload_byte, ADV_PDU_TYPE_STR[pdu_type]); + // return(-1); + // } + + // payload_type_5 = (ADV_PDU_PAYLOAD_TYPE_5 *)adv_pdu_payload; + + // //InitA = reorder_bytes_str( payload_bytes(1 : (2*6)) ); + // macAddress[0] = payload_byte[5]; + // macAddress[1] = payload_byte[4]; + // macAddress[2] = payload_byte[3]; + // macAddress[3] = payload_byte[2]; + // macAddress[4] = payload_byte[1]; + // macAddress[5] = payload_byte[0]; + + // //AdvA = reorder_bytes_str( payload_bytes((2*6+1):(2*6+2*6)) ); + // payload_type_5->AdvA[0] = payload_byte[11]; + // payload_type_5->AdvA[1] = payload_byte[10]; + // payload_type_5->AdvA[2] = payload_byte[9]; + // payload_type_5->AdvA[3] = payload_byte[8]; + // payload_type_5->AdvA[4] = payload_byte[7]; + // payload_type_5->AdvA[5] = payload_byte[6]; + + // //AA = reorder_bytes_str( payload_bytes((2*6+2*6+1):(2*6+2*6+2*4)) ); + // payload_type_5->AA[0] = payload_byte[15]; + // payload_type_5->AA[1] = payload_byte[14]; + // payload_type_5->AA[2] = payload_byte[13]; + // payload_type_5->AA[3] = payload_byte[12]; + + // //CRCInit = payload_bytes((2*6+2*6+2*4+1):(2*6+2*6+2*4+2*3)); + // payload_type_5->CRCInit = ( payload_byte[16] ); + // payload_type_5->CRCInit = ( (payload_type_5->CRCInit << 8) | payload_byte[17] ); + // payload_type_5->CRCInit = ( (payload_type_5->CRCInit << 8) | payload_byte[18] ); + + // //WinSize = payload_bytes((2*6+2*6+2*4+2*3+1):(2*6+2*6+2*4+2*3+2*1)); + // payload_type_5->WinSize = payload_byte[19]; + + // //WinOffset = reorder_bytes_str( payload_bytes((2*6+2*6+2*4+2*3+2*1+1):(2*6+2*6+2*4+2*3+2*1+2*2)) ); + // payload_type_5->WinOffset = ( payload_byte[21] ); + // payload_type_5->WinOffset = ( (payload_type_5->WinOffset << 8) | payload_byte[20] ); + + // //Interval = reorder_bytes_str( payload_bytes((2*6+2*6+2*4+2*3+2*1+2*2+1):(2*6+2*6+2*4+2*3+2*1+2*2+2*2)) ); + // payload_type_5->Interval = ( payload_byte[23] ); + // payload_type_5->Interval = ( (payload_type_5->Interval << 8) | payload_byte[22] ); + + // //Latency = reorder_bytes_str( payload_bytes((2*6+2*6+2*4+2*3+2*1+2*2+2*2+1):(2*6+2*6+2*4+2*3+2*1+2*2+2*2+2*2)) ); + // payload_type_5->Latency = ( payload_byte[25] ); + // payload_type_5->Latency = ( (payload_type_5->Latency << 8) | payload_byte[24] ); + + // //Timeout = reorder_bytes_str( payload_bytes((2*6+2*6+2*4+2*3+2*1+2*2+2*2+2*2+1):(2*6+2*6+2*4+2*3+2*1+2*2+2*2+2*2+2*2)) ); + // payload_type_5->Timeout = ( payload_byte[27] ); + // payload_type_5->Timeout = ( (payload_type_5->Timeout << 8) | payload_byte[26] ); + + // //ChM = reorder_bytes_str( payload_bytes((2*6+2*6+2*4+2*3+2*1+2*2+2*2+2*2+2*2+1):(2*6+2*6+2*4+2*3+2*1+2*2+2*2+2*2+2*2+2*5)) ); + // payload_type_5->ChM[0] = payload_byte[32]; + // payload_type_5->ChM[1] = payload_byte[31]; + // payload_type_5->ChM[2] = payload_byte[30]; + // payload_type_5->ChM[3] = payload_byte[29]; + // payload_type_5->ChM[4] = payload_byte[28]; + + // //tmp_bits = payload_bits((end-7) : end); + // //Hop = num2str( bi2de(tmp_bits(1:5), 'right-msb') ); + // //SCA = num2str( bi2de(tmp_bits(6:end), 'right-msb') ); + // payload_type_5->Hop = (payload_byte[33]&0x1F); + // payload_type_5->SCA = ((payload_byte[33]>>5)&0x07); + // } + // else + // { + // //TODO: Handle Unknown PDU. + // payload_type_R = (ADV_PDU_PAYLOAD_TYPE_R *)adv_pdu_payload; + // memcpy(payload_type_R->payload_byte, payload_byte, num_payload_byte); + // return(-1); + // } + + return 0; +} + void BTLERxProcessor::execute(const buffer_c8_t& buffer) { if (!configured) return; - // FM demodulation + // Pulled this implementation from channel_stats_collector.c to time slice a specific packet's dB. + uint32_t max_squared = 0; - /*const auto decim_0_out = decim_0.execute(buffer, dst_buffer); - const auto channel = decim_1.execute(decim_0_out, dst_buffer); + void* src_p = buffer.p; - feed_channel_stats(channel); - - auto audio_oversampled = demod.execute(channel, work_audio_buffer);*/ - - const auto decim_0_out = decim_0.execute(buffer, dst_buffer); - feed_channel_stats(decim_0_out); - - auto audio_oversampled = demod.execute(decim_0_out, work_audio_buffer); - - /*std::fill(spectrum.begin(), spectrum.end(), 0); - for(size_t i=0; i max_squared) { + max_squared = mag_sq; } - const buffer_c16_t buffer_c16 {spectrum.data(),spectrum.size(),buffer.sampling_rate}; - feed_channel_stats(buffer_c16); + } - auto audio_oversampled = demod.execute(buffer_c16, work_audio_buffer);*/ - // Audio signal processing - for (size_t c = 0; c < audio_oversampled.count; c++) { - /*const int32_t sample_int = audio_oversampled.p[c] * 32768.0f; - int32_t current_sample = __SSAT(sample_int, 16); - current_sample /= 128;*/ + const float max_squared_f = max_squared; + const int32_t max_dB = mag2_to_dbv_norm(max_squared_f * (1.0f / (32768.0f * 32768.0f))); - int32_t current_sample = audio_oversampled.p[c]; // if I directly use this, some results can pass crc but not correct. - rb_head++; - rb_head = (rb_head) % RB_SIZE; + decim_0.execute(buffer, dst_buffer); + feed_channel_stats(dst_buffer); - rb_buf[rb_head] = current_sample; + const buffer_c8_t iq_buffer{ + buffer.p, + buffer.count, + baseband_fs}; - skipSamples = skipSamples - 1; + // process++; - if (skipSamples < 1) { - int32_t threshold_tmp = 0; - for (int c = 0; c < 8; c++) { - threshold_tmp = threshold_tmp + (int32_t)rb_buf[(rb_head + c) % RB_SIZE]; - } - g_threshold = (int32_t)threshold_tmp / 8; + // if ((process % 50) != 0) return; - int transitions = 0; - if (rb_buf[(rb_head + 9) % RB_SIZE] > g_threshold) { - for (int c = 0; c < 8; c++) { - if (rb_buf[(rb_head + c) % RB_SIZE] > rb_buf[(rb_head + c + 1) % RB_SIZE]) - transitions = transitions + 1; - } - } else { - for (int c = 0; c < 8; c++) { - if (rb_buf[(rb_head + c) % RB_SIZE] < rb_buf[(rb_head + c + 1) % RB_SIZE]) - transitions = transitions + 1; + // 4Mhz 2048 samples + + //--------------Variable Defines---------------------------------// + + int i, sp, j = 0; + int I0, Q0, I1, Q1 = 0; + int k, p, phase_idx = 0; + int num_demod_byte = 0; + + bool unequal_flag; + + const int demod_buf_len = LEN_DEMOD_BUF_ACCESS; // For AA + int demod_buf_offset = 0; + int num_symbol_left = dst_buffer.count / SAMPLE_PER_SYMBOL; // One buffer sample consist of I and Q. + int symbols_eaten = 0; + int hit_idx = (-1); + + //--------------Start Parsing For Access Address---------------// + + static uint8_t demod_buf_access[SAMPLE_PER_SYMBOL][LEN_DEMOD_BUF_ACCESS]; + + uint32_t uint32_tmp = DEFAULT_ACCESS_ADDR; + uint8_t accessAddrBits[LEN_DEMOD_BUF_ACCESS]; + + uint32_t accesssAddress = 0; + + // Filling up addressBits with the access address we are looking to find. + for (i = 0; i < 32; i++) { + accessAddrBits[i] = 0x01 & uint32_tmp; + uint32_tmp = (uint32_tmp >> 1); + } + + memset(demod_buf_access, 0, SAMPLE_PER_SYMBOL * demod_buf_len); + + for (i = 0; i < num_symbol_left * SAMPLE_PER_SYMBOL; i += SAMPLE_PER_SYMBOL) { + sp = ((demod_buf_offset - demod_buf_len + 1) & (demod_buf_len - 1)); + + for (j = 0; j < SAMPLE_PER_SYMBOL; j++) { + // Sample and compare with the adjacent next sample. + I0 = dst_buffer.p[i + j].real(); + Q0 = dst_buffer.p[i + j].imag(); + I1 = dst_buffer.p[i + j + 1].real(); + Q1 = dst_buffer.p[i + j + 1].imag(); + + phase_idx = j; + + demod_buf_access[phase_idx][demod_buf_offset] = (I0 * Q1 - I1 * Q0) > 0 ? 1 : 0; + + k = sp; + unequal_flag = false; + + accesssAddress = 0; + + for (p = 0; p < demod_buf_len; p++) { + if (demod_buf_access[phase_idx][k] != accessAddrBits[p]) { + unequal_flag = true; + hit_idx = (-1); + break; } + + accesssAddress = (accesssAddress & (~(1 << p))) | (demod_buf_access[phase_idx][k] << p); + + k = ((k + 1) & (demod_buf_len - 1)); } - bool packet_detected = false; - // if ( transitions==4 && abs(g_threshold)<15500) - if (transitions == 4) { - uint8_t packet_data[500]; - int packet_length; - uint32_t packet_crc; - // uint32_t calced_crc; // NOTE: restore when CRC is passing - uint64_t packet_addr_l; - // uint32_t result; // NOTE: restore when CRC is passing - uint8_t crc[3]; - uint8_t packet_header_arr[2]; + if (unequal_flag == false) { + hit_idx = (i + j - (demod_buf_len - 1) * SAMPLE_PER_SYMBOL); + break; + } + } - packet_addr_l = 0; - for (int i = 0; i < 4; i++) { - bool current_bit; - uint8_t byte = 0; - for (int c = 0; c < 8; c++) { - if (rb_buf[(rb_head + (i + 1) * 8 + c) % RB_SIZE] > g_threshold) - current_bit = true; - else - current_bit = false; - byte |= current_bit << (7 - c); - } - uint8_t byte_temp = (uint8_t)(((byte * 0x0802LU & 0x22110LU) | (byte * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16); - packet_addr_l |= ((uint64_t)byte_temp) << (8 * i); - } + if (unequal_flag == false) { + break; + } - channel_number = 38; + demod_buf_offset = ((demod_buf_offset + 1) & (demod_buf_len - 1)); + } - for (int t = 0; t < 2; t++) { - bool current_bit; - uint8_t byte = 0; - for (int c = 0; c < 8; c++) { - if (rb_buf[(rb_head + 5 * 8 + t * 8 + c) % RB_SIZE] > g_threshold) - current_bit = true; - else - current_bit = false; - byte |= current_bit << (7 - c); - } + if (hit_idx == -1) { + // Process more samples. + return; + } - packet_header_arr[t] = byte; - } + symbols_eaten += hit_idx; - uint8_t byte_temp2 = (uint8_t)(((channel_number * 0x0802LU & 0x22110LU) | (channel_number * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16); - uint8_t lfsr_1 = byte_temp2 | 2; - int header_length = 2; - int header_counter = 0; - while (header_length--) { - for (uint8_t i = 0x80; i; i >>= 1) { - if (lfsr_1 & 0x80) { - lfsr_1 ^= 0x11; - (packet_header_arr[header_counter]) ^= i; - } - lfsr_1 <<= 1; - } - header_counter = header_counter + 1; - } + symbols_eaten += (8 * NUM_ACCESS_ADDR_BYTE * SAMPLE_PER_SYMBOL); // move to beginning of PDU header - if (packet_addr_l == 0x8E89BED6) { - uint8_t byte_temp3 = (uint8_t)(((packet_header_arr[1] * 0x0802LU & 0x22110LU) | (packet_header_arr[1] * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16); - packet_length = byte_temp3 & 0x3F; + num_symbol_left = num_symbol_left - symbols_eaten; - } else { - packet_length = 0; - } + //--------------Start PDU Header Parsing-----------------------// - for (int t = 0; t < packet_length + 2 + 3; t++) { - bool current_bit; - uint8_t byte = 0; - for (int c = 0; c < 8; c++) { - if (rb_buf[(rb_head + 5 * 8 + t * 8 + c) % RB_SIZE] > g_threshold) - current_bit = true; - else - current_bit = false; - byte |= current_bit << (7 - c); - } + num_demod_byte = 2; // PDU header has 2 octets - packet_data[t] = byte; - } + symbols_eaten += 8 * num_demod_byte * SAMPLE_PER_SYMBOL; - uint8_t byte_temp4 = (uint8_t)(((channel_number * 0x0802LU & 0x22110LU) | (channel_number * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16); - uint8_t lfsr_2 = byte_temp4 | 2; - int pdu_crc_length = packet_length + 2 + 3; - int pdu_crc_counter = 0; - while (pdu_crc_length--) { - for (uint8_t i = 0x80; i; i >>= 1) { - if (lfsr_2 & 0x80) { - lfsr_2 ^= 0x11; - (packet_data[pdu_crc_counter]) ^= i; - } - lfsr_2 <<= 1; - } - pdu_crc_counter = pdu_crc_counter + 1; - } + if (symbols_eaten > (int)dst_buffer.count) { + return; + } - if (packet_addr_l == 0x8E89BED6) { - crc[0] = crc[1] = crc[2] = 0x55; - } else { - crc[0] = crc[1] = crc[2] = 0; - } + // //Demod the PDU Header + uint8_t bit_decision; - uint8_t v, t, d, crc_length; - uint32_t crc_result = 0; - crc_length = packet_length + 2; - int counter = 0; - while (crc_length--) { - uint8_t byte_temp5 = (uint8_t)(((packet_data[counter] * 0x0802LU & 0x22110LU) | (packet_data[counter] * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16); - d = byte_temp5; - for (v = 0; v < 8; v++, d >>= 1) { - t = crc[0] >> 7; - crc[0] <<= 1; - if (crc[1] & 0x80) crc[0] |= 1; - crc[1] <<= 1; - if (crc[2] & 0x80) crc[1] |= 1; - crc[2] <<= 1; - if (t != (d & 1)) { - crc[2] ^= 0x5B; - crc[1] ^= 0x06; - } - } - counter = counter + 1; - } - for (v = 0; v < 3; v++) crc_result = (crc_result << 8) | crc[v]; - // calced_crc = crc_result; // NOTE: restore when CRC is passing + // Jump back down to beginning of PDU header. + int sample_idx = symbols_eaten - (8 * num_demod_byte * SAMPLE_PER_SYMBOL); - packet_crc = 0; - for (int c = 0; c < 3; c++) packet_crc = (packet_crc << 8) | packet_data[packet_length + 2 + c]; + uint16_t packet_index = 0; - if (packet_addr_l == 0x8E89BED6) - // if (packet_crc==calced_crc) // NOTE: restore when CRC is passing - { - uint8_t mac_data[6]; - int counter = 0; - for (int i = 7; i >= 2; i--) { - uint8_t byte_temp6 = (uint8_t)(((packet_data[i] * 0x0802LU & 0x22110LU) | (packet_data[i] * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16); - // result = byte_temp6; // NOTE: restore when CRC is passing - mac_data[counter] = byte_temp6; - counter = counter + 1; - } + for (i = 0; i < num_demod_byte; i++) { + rb_buf[packet_index] = 0; - data_message.is_data = false; - data_message.value = 'A'; - shared_memory.application_queue.push(data_message); + for (j = 0; j < 8; j++) { + I0 = dst_buffer.p[sample_idx].real(); + Q0 = dst_buffer.p[sample_idx].imag(); + I1 = dst_buffer.p[sample_idx + 1].real(); + Q1 = dst_buffer.p[sample_idx + 1].imag(); - data_message.is_data = true; - data_message.value = mac_data[0]; - shared_memory.application_queue.push(data_message); + bit_decision = (I0 * Q1 - I1 * Q0) > 0 ? 1 : 0; + rb_buf[packet_index] = rb_buf[packet_index] | (bit_decision << j); - data_message.is_data = true; - data_message.value = mac_data[1]; - shared_memory.application_queue.push(data_message); + sample_idx += SAMPLE_PER_SYMBOL; + } - data_message.is_data = true; - data_message.value = mac_data[2]; - shared_memory.application_queue.push(data_message); + packet_index++; + } - data_message.is_data = true; - data_message.value = mac_data[3]; - shared_memory.application_queue.push(data_message); + // demod_byte(num_demod_byte, rb_buf); - data_message.is_data = true; - data_message.value = mac_data[4]; - shared_memory.application_queue.push(data_message); + scramble_byte(rb_buf, num_demod_byte, scramble_table[channel_number], rb_buf); - data_message.is_data = true; - data_message.value = mac_data[5]; - shared_memory.application_queue.push(data_message); + uint8_t pdu_type = (ADV_PDU_TYPE)(rb_buf[0] & 0x0F); + // uint8_t tx_add = ((rb_buf[0] & 0x40) != 0); + // uint8_t rx_add = ((rb_buf[0] & 0x80) != 0); + uint8_t payload_len = (rb_buf[1] & 0x3F); - data_message.is_data = false; - data_message.value = 'B'; - shared_memory.application_queue.push(data_message); + // Not valid Advertise Payload. + if ((payload_len < 6) || (payload_len > 37)) { + return; + } - packet_detected = true; - } else - packet_detected = false; + //--------------Start Payload Parsing--------------------------// + + num_demod_byte = (payload_len + 3); + symbols_eaten += 8 * num_demod_byte * SAMPLE_PER_SYMBOL; + + if (symbols_eaten > (int)dst_buffer.count) { + return; + } + + // sample_idx = symbols_eaten - (8 * num_demod_byte * SAMPLE_PER_SYMBOL); + + for (i = 0; i < num_demod_byte; i++) { + rb_buf[packet_index] = 0; + + for (j = 0; j < 8; j++) { + I0 = dst_buffer.p[sample_idx].real(); + Q0 = dst_buffer.p[sample_idx].imag(); + I1 = dst_buffer.p[sample_idx + 1].real(); + Q1 = dst_buffer.p[sample_idx + 1].imag(); + + bit_decision = (I0 * Q1 - I1 * Q0) > 0 ? 1 : 0; + rb_buf[packet_index] = rb_buf[packet_index] | (bit_decision << j); + + sample_idx += SAMPLE_PER_SYMBOL; + } + + packet_index++; + } + + // demod_byte(num_demod_byte, rb_buf + 2); + + scramble_byte(rb_buf + 2, num_demod_byte, scramble_table[channel_number] + 2, rb_buf + 2); + + //--------------Start CRC Checking-----------------------------// + + // Check CRC + bool crc_flag = crc_check(rb_buf, payload_len + 2, crc_init_internal); + // pkt_count++; + + // This should be the flag that determines if the data should be sent to the application layer. + bool sendPacket = false; + + // Checking CRC and excluding Reserved PDU types. + if (pdu_type < RESERVED0 && !crc_flag) { + if (parse_adv_pdu_payload_byte(rb_buf + 2, payload_len, (ADV_PDU_TYPE)pdu_type, (void*)(&adv_pdu_payload)) == 0) { + sendPacket = true; + } + + // TODO: Make this a packet builder function? + if (sendPacket) { + blePacketData.max_dB = max_dB; + + blePacketData.type = pdu_type; + blePacketData.size = payload_len; + + blePacketData.macAddress[0] = macAddress[0]; + blePacketData.macAddress[1] = macAddress[1]; + blePacketData.macAddress[2] = macAddress[2]; + blePacketData.macAddress[3] = macAddress[3]; + blePacketData.macAddress[4] = macAddress[4]; + blePacketData.macAddress[5] = macAddress[5]; + + // Skip Header Byte and MAC Address + uint8_t startIndex = 8; + + for (i = 0; i < payload_len - 6; i++) { + blePacketData.data[i] = rb_buf[startIndex++]; } - if (packet_detected) { - skipSamples = 20; - } + blePacketData.dataLen = i; + + BLEPacketMessage data_message{&blePacketData}; + + shared_memory.application_queue.push(data_message); } } } @@ -275,12 +506,13 @@ void BTLERxProcessor::on_message(const Message* const message) { } void BTLERxProcessor::configure(const BTLERxConfigureMessage& message) { - (void)message; // avoid warning + channel_number = message.channel_number; decim_0.configure(taps_200k_wfm_decim_0.taps); - decim_1.configure(taps_200k_wfm_decim_1.taps); - demod.configure(audio_fs, 5000); + demod.configure(48000, 5000); configured = true; + + crc_init_internal = crc_init_reorder(crc_initalVale); } int main() { diff --git a/firmware/baseband/proc_btlerx.hpp b/firmware/baseband/proc_btlerx.hpp index e137912f..4ad4e34e 100644 --- a/firmware/baseband/proc_btlerx.hpp +++ b/firmware/baseband/proc_btlerx.hpp @@ -2,6 +2,7 @@ * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. * Copyright (C) 2016 Furrtek * Copyright (C) 2020 Shao + * Copyright (C) 2023 Netro * * This file is part of PortaPack. * @@ -42,48 +43,191 @@ class BTLERxProcessor : public BasebandProcessor { void on_message(const Message* const message) override; private: + static constexpr int SAMPLE_PER_SYMBOL{1}; + static constexpr int LEN_DEMOD_BUF_ACCESS{32}; + static constexpr uint32_t DEFAULT_ACCESS_ADDR{0x8E89BED6}; + static constexpr int NUM_ACCESS_ADDR_BYTE{4}; + + enum ADV_PDU_TYPE { + ADV_IND = 0, + ADV_DIRECT_IND = 1, + ADV_NONCONN_IND = 2, + SCAN_REQ = 3, + SCAN_RSP = 4, + CONNECT_REQ = 5, + ADV_SCAN_IND = 6, + RESERVED0 = 7, + RESERVED1 = 8, + RESERVED2 = 9, + RESERVED3 = 10, + RESERVED4 = 11, + RESERVED5 = 12, + RESERVED6 = 13, + RESERVED7 = 14, + RESERVED8 = 15 + }; + + uint8_t macAddress[6]; + int checksumReceived = 0; + + struct ADV_PDU_PAYLOAD_TYPE_0_2_4_6 { + uint8_t Data[31]; + }; + + struct ADV_PDU_PAYLOAD_TYPE_1_3 { + uint8_t A1[6]; + }; + + struct ADV_PDU_PAYLOAD_TYPE_5 { + uint8_t AdvA[6]; + uint8_t AA[4]; + uint32_t CRCInit; + uint8_t WinSize; + uint16_t WinOffset; + uint16_t Interval; + uint16_t Latency; + uint16_t Timeout; + uint8_t ChM[5]; + uint8_t Hop; + uint8_t SCA; + }; + + struct ADV_PDU_PAYLOAD_TYPE_R { + uint8_t payload_byte[40]; + }; + static constexpr size_t baseband_fs = 4000000; static constexpr size_t audio_fs = baseband_fs / 8 / 8 / 2; - std::array dst{}; + uint_fast32_t crc_update(uint_fast32_t crc, const void* data, size_t data_len); + uint_fast32_t crc24_byte(uint8_t* byte_in, int num_byte, uint32_t init_hex); + bool crc_check(uint8_t* tmp_byte, int body_len, uint32_t crc_init); + uint32_t crc_init_reorder(uint32_t crc_init); + + uint32_t crc_initalVale = 0x555555; + uint32_t crc_init_internal = 0x00; + + void scramble_byte(uint8_t* byte_in, int num_byte, const uint8_t* scramble_table_byte, uint8_t* byte_out); + // void demod_byte(int num_byte, uint8_t *out_byte); + int parse_adv_pdu_payload_byte(uint8_t* payload_byte, int num_payload_byte, ADV_PDU_TYPE pdu_type, void* adv_pdu_payload); + + std::array dst{}; const buffer_c16_t dst_buffer{ dst.data(), dst.size()}; - std::array spectrum{}; - const buffer_c16_t spectrum_buffer{ - spectrum.data(), - spectrum.size()}; + static constexpr int RB_SIZE = 2048; + uint8_t rb_buf[2048]; - const buffer_s16_t work_audio_buffer{ - (int16_t*)dst.data(), - sizeof(dst) / sizeof(int16_t)}; - - // Array size ok down to 375 bauds (24000 / 375) - std::array delay_line{0}; - std::array rb_buf{0}; - - /*dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0 { }; - dsp::decimate::FIRC16xR16x32Decim8 decim_1 { }; - dsp::decimate::FIRAndDecimateComplex channel_filter { };*/ dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0{}; - dsp::decimate::FIRC16xR16x16Decim2 decim_1{}; dsp::demodulate::FM demod{}; int rb_head{-1}; int32_t g_threshold{0}; - uint8_t channel_number{38}; - int skipSamples{1000}; - int RB_SIZE{1000}; + uint8_t channel_number{37}; + + uint16_t process = 0; bool configured{false}; - AFSKDataMessage data_message{false, 0}; + BlePacketData blePacketData{}; /* NB: Threads should be the last members in the class definition. */ BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive}; RSSIThread rssi_thread{}; void configure(const BTLERxConfigureMessage& message); + + ADV_PDU_PAYLOAD_TYPE_0_2_4_6* payload_type_0_2_4_6 = nullptr; + ADV_PDU_PAYLOAD_TYPE_1_3* payload_type_1_3 = nullptr; + ADV_PDU_PAYLOAD_TYPE_5* payload_type_5 = nullptr; + ADV_PDU_PAYLOAD_TYPE_R* payload_type_R = nullptr; + ADV_PDU_PAYLOAD_TYPE_R adv_pdu_payload = {0}; + + // clang-format off + // Scramble table definition + const uint8_t scramble_table[40][42] = + { + {64, 178, 188, 195, 31, 55, 74, 95, 133, 246, 156, 154, 193, 214, 197, 68, 32, 89, 222, 225, 143, 27, 165, 175, 66, 123, 78, 205, 96, 235, 98, 34, 144, 44, 239, 240, 199, 141, 210, 87, 161, 61, }, + {137, 64, 178, 188, 195, 31, 55, 74, 95, 133, 246, 156, 154, 193, 214, 197, 68, 32, 89, 222, 225, 143, 27, 165, 175, 66, 123, 78, 205, 96, 235, 98, 34, 144, 44, 239, 240, 199, 141, 210, 87, 161, }, + {210, 87, 161, 61, 167, 102, 176, 117, 49, 17, 72, 150, 119, 248, 227, 70, 233, 171, 208, 158, 83, 51, 216, 186, 152, 8, 36, 203, 59, 252, 113, 163, 244, 85, 104, 207, 169, 25, 108, 93, 76, 4, }, + {27, 165, 175, 66, 123, 78, 205, 96, 235, 98, 34, 144, 44, 239, 240, 199, 141, 210, 87, 161, 61, 167, 102, 176, 117, 49, 17, 72, 150, 119, 248, 227, 70, 233, 171, 208, 158, 83, 51, 216, 186, 152, }, + {100, 121, 135, 63, 110, 148, 190, 10, 237, 57, 53, 131, 173, 139, 137, 64, 178, 188, 195, 31, 55, 74, 95, 133, 246, 156, 154, 193, 214, 197, 68, 32, 89, 222, 225, 143, 27, 165, 175, 66, 123, 78, }, + {173, 139, 137, 64, 178, 188, 195, 31, 55, 74, 95, 133, 246, 156, 154, 193, 214, 197, 68, 32, 89, 222, 225, 143, 27, 165, 175, 66, 123, 78, 205, 96, 235, 98, 34, 144, 44, 239, 240, 199, 141, 210, }, + {246, 156, 154, 193, 214, 197, 68, 32, 89, 222, 225, 143, 27, 165, 175, 66, 123, 78, 205, 96, 235, 98, 34, 144, 44, 239, 240, 199, 141, 210, 87, 161, 61, 167, 102, 176, 117, 49, 17, 72, 150, 119, }, + {63, 110, 148, 190, 10, 237, 57, 53, 131, 173, 139, 137, 64, 178, 188, 195, 31, 55, 74, 95, 133, 246, 156, 154, 193, 214, 197, 68, 32, 89, 222, 225, 143, 27, 165, 175, 66, 123, 78, 205, 96, 235, }, + {8, 36, 203, 59, 252, 113, 163, 244, 85, 104, 207, 169, 25, 108, 93, 76, 4, 146, 229, 29, 254, 184, 81, 250, 42, 180, 231, 212, 12, 182, 46, 38, 2, 201, 242, 14, 127, 220, 40, 125, 21, 218, }, + {193, 214, 197, 68, 32, 89, 222, 225, 143, 27, 165, 175, 66, 123, 78, 205, 96, 235, 98, 34, 144, 44, 239, 240, 199, 141, 210, 87, 161, 61, 167, 102, 176, 117, 49, 17, 72, 150, 119, 248, 227, 70, }, + {154, 193, 214, 197, 68, 32, 89, 222, 225, 143, 27, 165, 175, 66, 123, 78, 205, 96, 235, 98, 34, 144, 44, 239, 240, 199, 141, 210, 87, 161, 61, 167, 102, 176, 117, 49, 17, 72, 150, 119, 248, 227, }, + {83, 51, 216, 186, 152, 8, 36, 203, 59, 252, 113, 163, 244, 85, 104, 207, 169, 25, 108, 93, 76, 4, 146, 229, 29, 254, 184, 81, 250, 42, 180, 231, 212, 12, 182, 46, 38, 2, 201, 242, 14, 127, }, + {44, 239, 240, 199, 141, 210, 87, 161, 61, 167, 102, 176, 117, 49, 17, 72, 150, 119, 248, 227, 70, 233, 171, 208, 158, 83, 51, 216, 186, 152, 8, 36, 203, 59, 252, 113, 163, 244, 85, 104, 207, 169, }, + {229, 29, 254, 184, 81, 250, 42, 180, 231, 212, 12, 182, 46, 38, 2, 201, 242, 14, 127, 220, 40, 125, 21, 218, 115, 106, 6, 91, 23, 19, 129, 100, 121, 135, 63, 110, 148, 190, 10, 237, 57, 53, }, + {190, 10, 237, 57, 53, 131, 173, 139, 137, 64, 178, 188, 195, 31, 55, 74, 95, 133, 246, 156, 154, 193, 214, 197, 68, 32, 89, 222, 225, 143, 27, 165, 175, 66, 123, 78, 205, 96, 235, 98, 34, 144, }, + {119, 248, 227, 70, 233, 171, 208, 158, 83, 51, 216, 186, 152, 8, 36, 203, 59, 252, 113, 163, 244, 85, 104, 207, 169, 25, 108, 93, 76, 4, 146, 229, 29, 254, 184, 81, 250, 42, 180, 231, 212, 12, }, + {208, 158, 83, 51, 216, 186, 152, 8, 36, 203, 59, 252, 113, 163, 244, 85, 104, 207, 169, 25, 108, 93, 76, 4, 146, 229, 29, 254, 184, 81, 250, 42, 180, 231, 212, 12, 182, 46, 38, 2, 201, 242, }, + {25, 108, 93, 76, 4, 146, 229, 29, 254, 184, 81, 250, 42, 180, 231, 212, 12, 182, 46, 38, 2, 201, 242, 14, 127, 220, 40, 125, 21, 218, 115, 106, 6, 91, 23, 19, 129, 100, 121, 135, 63, 110, }, + {66, 123, 78, 205, 96, 235, 98, 34, 144, 44, 239, 240, 199, 141, 210, 87, 161, 61, 167, 102, 176, 117, 49, 17, 72, 150, 119, 248, 227, 70, 233, 171, 208, 158, 83, 51, 216, 186, 152, 8, 36, 203, }, + {139, 137, 64, 178, 188, 195, 31, 55, 74, 95, 133, 246, 156, 154, 193, 214, 197, 68, 32, 89, 222, 225, 143, 27, 165, 175, 66, 123, 78, 205, 96, 235, 98, 34, 144, 44, 239, 240, 199, 141, 210, 87, }, + {244, 85, 104, 207, 169, 25, 108, 93, 76, 4, 146, 229, 29, 254, 184, 81, 250, 42, 180, 231, 212, 12, 182, 46, 38, 2, 201, 242, 14, 127, 220, 40, 125, 21, 218, 115, 106, 6, 91, 23, 19, 129, }, + {61, 167, 102, 176, 117, 49, 17, 72, 150, 119, 248, 227, 70, 233, 171, 208, 158, 83, 51, 216, 186, 152, 8, 36, 203, 59, 252, 113, 163, 244, 85, 104, 207, 169, 25, 108, 93, 76, 4, 146, 229, 29, }, + {102, 176, 117, 49, 17, 72, 150, 119, 248, 227, 70, 233, 171, 208, 158, 83, 51, 216, 186, 152, 8, 36, 203, 59, 252, 113, 163, 244, 85, 104, 207, 169, 25, 108, 93, 76, 4, 146, 229, 29, 254, 184, }, + {175, 66, 123, 78, 205, 96, 235, 98, 34, 144, 44, 239, 240, 199, 141, 210, 87, 161, 61, 167, 102, 176, 117, 49, 17, 72, 150, 119, 248, 227, 70, 233, 171, 208, 158, 83, 51, 216, 186, 152, 8, 36, }, + {152, 8, 36, 203, 59, 252, 113, 163, 244, 85, 104, 207, 169, 25, 108, 93, 76, 4, 146, 229, 29, 254, 184, 81, 250, 42, 180, 231, 212, 12, 182, 46, 38, 2, 201, 242, 14, 127, 220, 40, 125, 21, }, + {81, 250, 42, 180, 231, 212, 12, 182, 46, 38, 2, 201, 242, 14, 127, 220, 40, 125, 21, 218, 115, 106, 6, 91, 23, 19, 129, 100, 121, 135, 63, 110, 148, 190, 10, 237, 57, 53, 131, 173, 139, 137, }, + {10, 237, 57, 53, 131, 173, 139, 137, 64, 178, 188, 195, 31, 55, 74, 95, 133, 246, 156, 154, 193, 214, 197, 68, 32, 89, 222, 225, 143, 27, 165, 175, 66, 123, 78, 205, 96, 235, 98, 34, 144, 44, }, + {195, 31, 55, 74, 95, 133, 246, 156, 154, 193, 214, 197, 68, 32, 89, 222, 225, 143, 27, 165, 175, 66, 123, 78, 205, 96, 235, 98, 34, 144, 44, 239, 240, 199, 141, 210, 87, 161, 61, 167, 102, 176, }, + {188, 195, 31, 55, 74, 95, 133, 246, 156, 154, 193, 214, 197, 68, 32, 89, 222, 225, 143, 27, 165, 175, 66, 123, 78, 205, 96, 235, 98, 34, 144, 44, 239, 240, 199, 141, 210, 87, 161, 61, 167, 102, }, + {117, 49, 17, 72, 150, 119, 248, 227, 70, 233, 171, 208, 158, 83, 51, 216, 186, 152, 8, 36, 203, 59, 252, 113, 163, 244, 85, 104, 207, 169, 25, 108, 93, 76, 4, 146, 229, 29, 254, 184, 81, 250, }, + {46, 38, 2, 201, 242, 14, 127, 220, 40, 125, 21, 218, 115, 106, 6, 91, 23, 19, 129, 100, 121, 135, 63, 110, 148, 190, 10, 237, 57, 53, 131, 173, 139, 137, 64, 178, 188, 195, 31, 55, 74, 95, }, + {231, 212, 12, 182, 46, 38, 2, 201, 242, 14, 127, 220, 40, 125, 21, 218, 115, 106, 6, 91, 23, 19, 129, 100, 121, 135, 63, 110, 148, 190, 10, 237, 57, 53, 131, 173, 139, 137, 64, 178, 188, 195, }, + {96, 235, 98, 34, 144, 44, 239, 240, 199, 141, 210, 87, 161, 61, 167, 102, 176, 117, 49, 17, 72, 150, 119, 248, 227, 70, 233, 171, 208, 158, 83, 51, 216, 186, 152, 8, 36, 203, 59, 252, 113, 163, }, + {169, 25, 108, 93, 76, 4, 146, 229, 29, 254, 184, 81, 250, 42, 180, 231, 212, 12, 182, 46, 38, 2, 201, 242, 14, 127, 220, 40, 125, 21, 218, 115, 106, 6, 91, 23, 19, 129, 100, 121, 135, 63, }, + {242, 14, 127, 220, 40, 125, 21, 218, 115, 106, 6, 91, 23, 19, 129, 100, 121, 135, 63, 110, 148, 190, 10, 237, 57, 53, 131, 173, 139, 137, 64, 178, 188, 195, 31, 55, 74, 95, 133, 246, 156, 154, }, + {59, 252, 113, 163, 244, 85, 104, 207, 169, 25, 108, 93, 76, 4, 146, 229, 29, 254, 184, 81, 250, 42, 180, 231, 212, 12, 182, 46, 38, 2, 201, 242, 14, 127, 220, 40, 125, 21, 218, 115, 106, 6, }, + {68, 32, 89, 222, 225, 143, 27, 165, 175, 66, 123, 78, 205, 96, 235, 98, 34, 144, 44, 239, 240, 199, 141, 210, 87, 161, 61, 167, 102, 176, 117, 49, 17, 72, 150, 119, 248, 227, 70, 233, 171, 208, }, + {141, 210, 87, 161, 61, 167, 102, 176, 117, 49, 17, 72, 150, 119, 248, 227, 70, 233, 171, 208, 158, 83, 51, 216, 186, 152, 8, 36, 203, 59, 252, 113, 163, 244, 85, 104, 207, 169, 25, 108, 93, 76, }, + {214, 197, 68, 32, 89, 222, 225, 143, 27, 165, 175, 66, 123, 78, 205, 96, 235, 98, 34, 144, 44, 239, 240, 199, 141, 210, 87, 161, 61, 167, 102, 176, 117, 49, 17, 72, 150, 119, 248, 227, 70, 233, }, + {31, 55, 74, 95, 133, 246, 156, 154, 193, 214, 197, 68, 32, 89, 222, 225, 143, 27, 165, 175, 66, 123, 78, 205, 96, 235, 98, 34, 144, 44, 239, 240, 199, 141, 210, 87, 161, 61, 167, 102, 176, 117, }, + }; + + /** + * Static table used for the table_driven implementation. + *****************************************************************************/ + const uint_fast32_t crc_table[256] = + { + 0x000000, 0x01b4c0, 0x036980, 0x02dd40, 0x06d300, 0x0767c0, 0x05ba80, 0x040e40, + 0x0da600, 0x0c12c0, 0x0ecf80, 0x0f7b40, 0x0b7500, 0x0ac1c0, 0x081c80, 0x09a840, + 0x1b4c00, 0x1af8c0, 0x182580, 0x199140, 0x1d9f00, 0x1c2bc0, 0x1ef680, 0x1f4240, + 0x16ea00, 0x175ec0, 0x158380, 0x143740, 0x103900, 0x118dc0, 0x135080, 0x12e440, + 0x369800, 0x372cc0, 0x35f180, 0x344540, 0x304b00, 0x31ffc0, 0x332280, 0x329640, + 0x3b3e00, 0x3a8ac0, 0x385780, 0x39e340, 0x3ded00, 0x3c59c0, 0x3e8480, 0x3f3040, + 0x2dd400, 0x2c60c0, 0x2ebd80, 0x2f0940, 0x2b0700, 0x2ab3c0, 0x286e80, 0x29da40, + 0x207200, 0x21c6c0, 0x231b80, 0x22af40, 0x26a100, 0x2715c0, 0x25c880, 0x247c40, + 0x6d3000, 0x6c84c0, 0x6e5980, 0x6fed40, 0x6be300, 0x6a57c0, 0x688a80, 0x693e40, + 0x609600, 0x6122c0, 0x63ff80, 0x624b40, 0x664500, 0x67f1c0, 0x652c80, 0x649840, + 0x767c00, 0x77c8c0, 0x751580, 0x74a140, 0x70af00, 0x711bc0, 0x73c680, 0x727240, + 0x7bda00, 0x7a6ec0, 0x78b380, 0x790740, 0x7d0900, 0x7cbdc0, 0x7e6080, 0x7fd440, + 0x5ba800, 0x5a1cc0, 0x58c180, 0x597540, 0x5d7b00, 0x5ccfc0, 0x5e1280, 0x5fa640, + 0x560e00, 0x57bac0, 0x556780, 0x54d340, 0x50dd00, 0x5169c0, 0x53b480, 0x520040, + 0x40e400, 0x4150c0, 0x438d80, 0x423940, 0x463700, 0x4783c0, 0x455e80, 0x44ea40, + 0x4d4200, 0x4cf6c0, 0x4e2b80, 0x4f9f40, 0x4b9100, 0x4a25c0, 0x48f880, 0x494c40, + 0xda6000, 0xdbd4c0, 0xd90980, 0xd8bd40, 0xdcb300, 0xdd07c0, 0xdfda80, 0xde6e40, + 0xd7c600, 0xd672c0, 0xd4af80, 0xd51b40, 0xd11500, 0xd0a1c0, 0xd27c80, 0xd3c840, + 0xc12c00, 0xc098c0, 0xc24580, 0xc3f140, 0xc7ff00, 0xc64bc0, 0xc49680, 0xc52240, + 0xcc8a00, 0xcd3ec0, 0xcfe380, 0xce5740, 0xca5900, 0xcbedc0, 0xc93080, 0xc88440, + 0xecf800, 0xed4cc0, 0xef9180, 0xee2540, 0xea2b00, 0xeb9fc0, 0xe94280, 0xe8f640, + 0xe15e00, 0xe0eac0, 0xe23780, 0xe38340, 0xe78d00, 0xe639c0, 0xe4e480, 0xe55040, + 0xf7b400, 0xf600c0, 0xf4dd80, 0xf56940, 0xf16700, 0xf0d3c0, 0xf20e80, 0xf3ba40, + 0xfa1200, 0xfba6c0, 0xf97b80, 0xf8cf40, 0xfcc100, 0xfd75c0, 0xffa880, 0xfe1c40, + 0xb75000, 0xb6e4c0, 0xb43980, 0xb58d40, 0xb18300, 0xb037c0, 0xb2ea80, 0xb35e40, + 0xbaf600, 0xbb42c0, 0xb99f80, 0xb82b40, 0xbc2500, 0xbd91c0, 0xbf4c80, 0xbef840, + 0xac1c00, 0xada8c0, 0xaf7580, 0xaec140, 0xaacf00, 0xab7bc0, 0xa9a680, 0xa81240, + 0xa1ba00, 0xa00ec0, 0xa2d380, 0xa36740, 0xa76900, 0xa6ddc0, 0xa40080, 0xa5b440, + 0x81c800, 0x807cc0, 0x82a180, 0x831540, 0x871b00, 0x86afc0, 0x847280, 0x85c640, + 0x8c6e00, 0x8ddac0, 0x8f0780, 0x8eb340, 0x8abd00, 0x8b09c0, 0x89d480, 0x886040, + 0x9a8400, 0x9b30c0, 0x99ed80, 0x985940, 0x9c5700, 0x9de3c0, 0x9f3e80, 0x9e8a40, + 0x972200, 0x9696c0, 0x944b80, 0x95ff40, 0x91f100, 0x9045c0, 0x929880, 0x932c40 + }; + // clang-format on }; #endif /*__PROC_BTLERX_H__*/ diff --git a/firmware/baseband/proc_fsk_rx.cpp b/firmware/baseband/proc_fsk_rx.cpp new file mode 100644 index 00000000..0bd1b0b8 --- /dev/null +++ b/firmware/baseband/proc_fsk_rx.cpp @@ -0,0 +1,318 @@ +/* + * Copyright (C) 1996 Thomas Sailer (sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu) + * Copyright (C) 2012-2014 Elias Oenal (multimon-ng@eliasoenal.com) + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * Copyright (C) 2023 Kyle Reed + * + * 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 "proc_fsk_rx.hpp" + +#include "event_m4.hpp" + +#include +#include +#include +#include + +using namespace std; +using namespace dsp::decimate; + +namespace { +/* Count of bits that differ between the two values. */ +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 + +/* AudioNormalizer ***************************************/ + +void AudioNormalizer::execute_in_place(const buffer_f32_t& audio) { + // Decay min/max every second (@24kHz). + if (counter_ >= 24'000) { + // 90% decay factor seems to work well. + // This keeps large transients from wrecking the filter. + 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_hi_) + val = 1.0f; + else if (val <= t_lo_) + val = -1.0f; + else + val = 0.0; + } +} + +void AudioNormalizer::calculate_thresholds() { + auto center = (max_ + min_) / 2.0f; + auto range = (max_ - min_) / 2.0f; + + // 10% off center force either +/-1.0f. + // Higher == larger dead zone. + // Lower == more false positives. + auto threshold = range * 0.1; + t_hi_ = center + threshold; + t_lo_ = center - threshold; +} + +/* FSKRxProcessor ******************************************/ + +void FSKRxProcessor::clear_data_bits() { + data = 0; + bit_count = 0; +} + +void FSKRxProcessor::handle_sync(bool inverted) { + clear_data_bits(); + has_sync_ = true; + inverted = inverted; + word_count = 0; +} + +void FSKRxProcessor::process_bits(const buffer_c8_t& buffer) { + // Process all of the bits in the bits queue. + while (buffer.count > 0) { + // Wait until data_ is full. + if (bit_count < data_bit_count) + continue; + + // Wait for the sync frame. + if (!has_sync_) { + if (diff_bit_count(data, sync_codeword) <= 2) + handle_sync(/*inverted=*/false); + else if (diff_bit_count(data, ~sync_codeword) <= 2) + handle_sync(/*inverted=*/true); + continue; + } + } +} + +/* FSKRxProcessor ***************************************/ + +FSKRxProcessor::FSKRxProcessor() { +} + +void FSKRxProcessor::execute(const buffer_c8_t& buffer) { + if (!configured) { + return; + } + + // Decimate by current decim 0 and decim 1. + const auto decim_0_out = decim_0.execute(buffer, dst_buffer); + const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer); + + feed_channel_stats(decim_1_out); + + spectrum_samples += decim_1_out.count; + + if (spectrum_samples >= spectrum_interval_samples) { + spectrum_samples -= spectrum_interval_samples; + channel_spectrum.feed(decim_1_out, channel_filter_low_f, + channel_filter_high_f, channel_filter_transition); + } + + // process_bits(); + + // Update the status. + samples_processed += buffer.count; + + if (samples_processed >= stat_update_threshold) { + // send_packet(data); + samples_processed -= stat_update_threshold; + } +} + +void FSKRxProcessor::on_message(const Message* const message) { + switch (message->id) { + case Message::ID::FSKRxConfigure: + configure(*reinterpret_cast(message)); + break; + case Message::ID::UpdateSpectrum: + case Message::ID::SpectrumStreamingConfig: + channel_spectrum.on_message(message); + break; + + case Message::ID::SampleRateConfig: + sample_rate_config(*reinterpret_cast(message)); + break; + + case Message::ID::CaptureConfig: + capture_config(*reinterpret_cast(message)); + break; + + default: + break; + } +} + +void FSKRxProcessor::configure(const FSKRxConfigureMessage& message) { + // Extract message variables. + deviation = message.deviation; + channel_decimation = message.channel_decimation; + // channel_filter_taps = message.channel_filter; + + channel_spectrum.set_decimation_factor(1); +} + +void FSKRxProcessor::capture_config(const CaptureConfigMessage& message) { + if (message.config) { + audio_output.set_stream(std::make_unique(message.config)); + } else { + audio_output.set_stream(nullptr); + } +} + +void FSKRxProcessor::sample_rate_config(const SampleRateConfigMessage& message) { + const auto sample_rate = message.sample_rate; + + // The actual sample rate is the requested rate * the oversample rate. + // See oversample.hpp for more details on oversampling. + baseband_fs = sample_rate * toUType(message.oversample_rate); + baseband_thread.set_sampling_rate(baseband_fs); + + // TODO: Do we need to use the taps that the decimators get configured with? + channel_filter_low_f = taps_200k_decim_1.low_frequency_normalized * sample_rate; + channel_filter_high_f = taps_200k_decim_1.high_frequency_normalized * sample_rate; + channel_filter_transition = taps_200k_decim_1.transition_normalized * sample_rate; + + // Compute the scalar that corrects the oversample_rate to be x8 when computing + // the spectrum update interval. The original implementation only supported x8. + // TODO: Why is this needed here but not in proc_replay? There must be some other + // assumption about x8 oversampling in some component that makes this necessary. + const auto oversample_correction = toUType(message.oversample_rate) / 8.0; + + // The spectrum update interval controls how often the waterfall is fed new samples. + spectrum_interval_samples = sample_rate / (spectrum_rate_hz * oversample_correction); + spectrum_samples = 0; + + // For high sample rates, the M4 is busy collecting samples so the + // waterfall runs slower. Reduce the update interval so it runs faster. + // NB: Trade off: looks nicer, but more frequent updates == more CPU. + if (sample_rate >= 1'500'000) + spectrum_interval_samples /= (sample_rate / 750'000); + + switch (message.oversample_rate) { + case OversampleRate::x4: + // M4 can't handle 2 decimation passes for sample rates needing x4. + decim_0.set().configure(taps_200k_decim_0.taps); + decim_1.set(); + break; + + case OversampleRate::x8: + // M4 can't handle 2 decimation passes for sample rates <= 600k. + if (message.sample_rate < 600'000) { + decim_0.set().configure(taps_200k_decim_0.taps); + decim_1.set().configure(taps_200k_decim_1.taps); + } else { + // Using 180k taps to provide better filtering with a single pass. + decim_0.set().configure(taps_180k_wfm_decim_0.taps); + decim_1.set(); + } + break; + + case OversampleRate::x16: + decim_0.set().configure(taps_200k_decim_0.taps); + decim_1.set().configure(taps_200k_decim_1.taps); + break; + + case OversampleRate::x32: + decim_0.set().configure(taps_200k_decim_0.taps); + decim_1.set().configure(taps_16k0_decim_1.taps); + break; + + case OversampleRate::x64: + decim_0.set().configure(taps_200k_decim_0.taps); + decim_1.set().configure(taps_16k0_decim_1.taps); + break; + + default: + chDbgPanic("Unhandled OversampleRate"); + break; + } + + // Update demodulator based on new decimation. Todo: Confirm this works. + size_t decim_0_input_fs = baseband_fs; + size_t decim_0_output_fs = decim_0_input_fs / decim_0.decimation_factor(); + + size_t decim_1_input_fs = decim_0_output_fs; + size_t decim_1_output_fs = decim_1_input_fs / decim_1.decimation_factor(); + + // size_t channel_filter_input_fs = decim_1_output_fs; + // size_t channel_filter_output_fs = channel_filter_input_fs / channel_decimation; + + size_t demod_input_fs = decim_1_output_fs; + + send_packet((uint32_t)demod_input_fs); + + // Set ready to process data. + configured = true; +} + +void FSKRxProcessor::flush() { + // word_extractor.flush(); +} + +void FSKRxProcessor::reset() { + clear_data_bits(); + has_sync_ = false; + inverted = false; + word_count = 0; + + samples_processed = 0; +} + +void FSKRxProcessor::send_packet(uint32_t data) { + data_message.is_data = true; + data_message.value = data; + shared_memory.application_queue.push(data_message); +} + +/* main **************************************************/ + +int main() { + EventDispatcher event_dispatcher{std::make_unique()}; + event_dispatcher.run(); + return 0; +} diff --git a/firmware/baseband/proc_fsk_rx.hpp b/firmware/baseband/proc_fsk_rx.hpp new file mode 100644 index 00000000..25c790af --- /dev/null +++ b/firmware/baseband/proc_fsk_rx.hpp @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 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 __PROC_FSK_RX_H__ +#define __PROC_FSK_RX_H__ + +#include "audio_output.hpp" +#include "baseband_processor.hpp" +#include "baseband_thread.hpp" +#include "rssi_thread.hpp" + +#include "dsp_decimate.hpp" +#include "dsp_demodulate.hpp" +#include "dsp_iir_config.hpp" +#include "dsp_fir_taps.hpp" + +#include "spectrum_collector.hpp" +#include "stream_input.hpp" + +#include "message.hpp" +#include "portapack_shared_memory.hpp" + +#include +#include +#include +#include +#include +#include + +/* Normalizes audio stream to +/-1.0f */ +class AudioNormalizer { + 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_hi_ = 1.0; + float t_lo_ = 1.0; +}; + +/* A decimator that just returns the source buffer. */ +class NoopDecim { + public: + static constexpr int decimation_factor = 1; + + template + Buffer execute(const Buffer& src, const Buffer&) { + // TODO: should this copy to 'dst'? + return {src.p, src.count, src.sampling_rate}; + } +}; + +/* Decimator wrapper that can hold one of a set of decimators and dispatch at runtime. */ +template +class MultiDecimator { + public: + /* Dispatches to the underlying type's execute. */ + template + Destination execute( + const Source& src, + const Destination& dst) { + return std::visit( + [&src, &dst](auto&& arg) -> Destination { + return arg.execute(src, dst); + }, + decimator_); + } + + size_t decimation_factor() const { + return std::visit( + [](auto&& arg) -> size_t { + return arg.decimation_factor; + }, + decimator_); + } + + /* Sets this decimator to a new instance of the specified decimator type. + * NB: The instance is returned by-ref so 'configure' can easily be called. */ + template + Decimator& set() { + decimator_ = Decimator{}; + return std::get(decimator_); + } + + private: + std::variant decimator_{}; +}; + +class FSKRxProcessor : public BasebandProcessor { + public: + FSKRxProcessor(); + void execute(const buffer_c8_t& buffer) override; + void on_message(const Message* const message) override; + + private: + size_t baseband_fs = 1024000; // aka: sample_rate + uint8_t stat_update_interval = 10; + uint32_t stat_update_threshold = baseband_fs / stat_update_interval; + static constexpr auto spectrum_rate_hz = 50.0f; + + void configure(const FSKRxConfigureMessage& message); + void capture_config(const CaptureConfigMessage& message); + void sample_rate_config(const SampleRateConfigMessage& message); + void flush(); + void reset(); + void send_packet(uint32_t data); + void process_bits(const buffer_c8_t& buffer); + + void clear_data_bits(); + void handle_sync(bool inverted); + + /* Returns true if the batch has as sync frame. */ + bool has_sync() const { return has_sync_; } + + /* Set once app is ready to receive messages. */ + bool configured = false; + + /* Buffer for decimated IQ data. */ + std::array dst{}; + const buffer_c16_t dst_buffer{ + dst.data(), + dst.size()}; + + /* Buffer for demodulated audio. */ + std::array audio{}; + const buffer_f32_t audio_buffer{audio.data(), audio.size()}; + + /* The actual type will be configured depending on the sample rate. */ + MultiDecimator< + dsp::decimate::FIRC8xR16x24FS4Decim4, + dsp::decimate::FIRC8xR16x24FS4Decim8> + decim_0{}; + MultiDecimator< + dsp::decimate::FIRC16xR16x16Decim2, + dsp::decimate::FIRC16xR16x32Decim8, + NoopDecim> + decim_1{}; + + /* Filter to 24kHz and demodulate. */ + dsp::decimate::FIRAndDecimateComplex channel_filter{}; + size_t deviation = 3750; + // fir_taps_real<32> channel_filter_taps = 0; + size_t channel_decimation = 2; + int32_t channel_filter_low_f = 0; + int32_t channel_filter_high_f = 0; + int32_t channel_filter_transition = 0; + + /* Squelch to ignore noise. */ + FMSquelch squelch{}; + uint64_t squelch_history = 0; + + // /* LPF to reduce noise. POCSAG supports 2400 baud, but that falls + // * nicely into the transition band of this 1800Hz filter. + // * scipy.signal.butter(2, 1800, "lowpass", fs=24000, analog=False) */ + // IIRBiquadFilter lpf{{{0.04125354f, 0.082507070f, 0.04125354f}, + // {1.00000000f, -1.34896775f, 0.51398189f}}}; + + /* Attempts to de-noise and normalize signal. */ + AudioNormalizer normalizer{}; + + /* Handles writing audio stream to hardware. */ + AudioOutput audio_output{}; + + /* Holds the data sent to the app. */ + AFSKDataMessage data_message{false, 0}; + + /* Used to keep track of how many samples were processed + * between status update messages. */ + uint32_t samples_processed = 0; + + /* Number of bits in 'data_' member. */ + static constexpr uint8_t data_bit_count = sizeof(uint32_t) * 8; + + /* Sync frame codeword. */ + static constexpr uint32_t sync_codeword = 0x12345678; + + /* When true, sync frame has been received. */ + bool has_sync_ = false; + + /* When true, bit vales are flipped in the codewords. */ + bool inverted = false; + + uint32_t data = 0; + uint8_t bit_count = 0; + uint8_t word_count = 0; + + /* LPF to reduce noise. POCSAG supports 2400 baud, but that falls + * nicely into the transition band of this 1800Hz filter. + * scipy.signal.butter(2, 1800, "lowpass", fs=24000, analog=False) */ + IIRBiquadFilter lpf{{{0.04125354f, 0.082507070f, 0.04125354f}, + {1.00000000f, -1.34896775f, 0.51398189f}}}; + + SpectrumCollector channel_spectrum{}; + size_t spectrum_interval_samples = 0; + size_t spectrum_samples = 0; + + /* NB: Threads should be the last members in the class definition. */ + BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive}; + RSSIThread rssi_thread{}; +}; + +#endif diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index dac16176..73f1a052 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -112,6 +112,8 @@ class Message { SpectrumPainterBufferRequestConfigure = 55, SpectrumPainterBufferResponseConfigure = 56, POCSAGStats = 57, + FSKRxConfigure = 58, + BlePacket = 58, MAX }; @@ -400,6 +402,26 @@ class AFSKDataMessage : public Message { uint32_t value; }; +struct BlePacketData { + int max_dB; + uint8_t type; + uint8_t size; + uint8_t macAddress[6]; + uint8_t data[40]; + uint8_t dataLen; +}; + +class BLEPacketMessage : public Message { + public: + constexpr BLEPacketMessage( + BlePacketData* packet) + : Message{ID::BlePacket}, + packet{packet} { + } + + BlePacketData* packet{nullptr}; +}; + class CodedSquelchMessage : public Message { public: constexpr CodedSquelchMessage( @@ -726,20 +748,11 @@ class APRSRxConfigureMessage : public Message { class BTLERxConfigureMessage : public Message { public: constexpr BTLERxConfigureMessage( - const uint32_t baudrate, - const uint32_t word_length, - const uint32_t trigger_value, - const bool trigger_word) + const uint8_t channel_number) : Message{ID::BTLERxConfigure}, - baudrate(baudrate), - word_length(word_length), - trigger_value(trigger_value), - trigger_word(trigger_word) { + channel_number(channel_number) { } - const uint32_t baudrate; - const uint32_t word_length; - const uint32_t trigger_value; - const bool trigger_word; + const uint8_t channel_number; }; class NRFRxConfigureMessage : public Message { @@ -1013,6 +1026,29 @@ class FSKConfigureMessage : public Message { const uint32_t progress_notice; }; +class FSKRxConfigureMessage : public Message { + public: + constexpr FSKRxConfigureMessage( + const fir_taps_real<24> decim_0_filter, + const fir_taps_real<32> decim_1_filter, + const fir_taps_real<32> channel_filter, + const size_t channel_decimation, + const size_t deviation) + : Message{ID::FSKRxConfigure}, + decim_0_filter(decim_0_filter), + decim_1_filter(decim_1_filter), + channel_filter(channel_filter), + channel_decimation{channel_decimation}, + deviation{deviation} { + } + + const fir_taps_real<24> decim_0_filter; + const fir_taps_real<32> decim_1_filter; + const fir_taps_real<32> channel_filter; + const size_t channel_decimation; + const size_t deviation; +}; + class POCSAGConfigureMessage : public Message { public: constexpr POCSAGConfigureMessage() diff --git a/firmware/common/spi_image.hpp b/firmware/common/spi_image.hpp index dafa970c..c1aa14d6 100644 --- a/firmware/common/spi_image.hpp +++ b/firmware/common/spi_image.hpp @@ -97,6 +97,7 @@ constexpr image_tag_t image_tag_adsb_tx{'P', 'A', 'D', 'T'}; constexpr image_tag_t image_tag_afsk{'P', 'A', 'F', 'T'}; constexpr image_tag_t image_tag_audio_tx{'P', 'A', 'T', 'X'}; constexpr image_tag_t image_tag_fsktx{'P', 'F', 'S', 'K'}; +constexpr image_tag_t image_tag_fskrx{'P', 'F', 'S', 'R'}; constexpr image_tag_t image_tag_jammer{'P', 'J', 'A', 'M'}; constexpr image_tag_t image_tag_mic_tx{'P', 'M', 'T', 'X'}; constexpr image_tag_t image_tag_ook{'P', 'O', 'O', 'K'};