diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index 6a53ee912..4ad2dca40 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -139,7 +139,6 @@ set(CPPSRC debounce.cpp touch.cpp touch_adc.cpp - protocols/adsb.cpp protocols/aprs.cpp protocols/ax25.cpp protocols/bht.cpp @@ -217,6 +216,8 @@ set(CPPSRC ${COMMON}/pocsag_packet.cpp ${COMMON}/pocsag.cpp ${COMMON}/morse.cpp + ${COMMON}/adsb_frame.cpp + ${COMMON}/adsb.cpp ais_app.cpp tpms_app.cpp pocsag_app.cpp diff --git a/firmware/application/main.cpp b/firmware/application/main.cpp index a0e611423..630aca26a 100755 --- a/firmware/application/main.cpp +++ b/firmware/application/main.cpp @@ -24,7 +24,6 @@ // Gimp image > indexed colors (16), then "xxd -i *.bmp" //TODO: De bruijn sequence scanner for encoders -//TODO: Waveform viewer //TEST: Mic tx //TEST: Menuview refresh, seems to blink a lot //TEST: Check AFSK transmit end, skips last bits ? diff --git a/firmware/application/protocols/adsb.cpp b/firmware/application/protocols/adsb.cpp deleted file mode 100644 index 5312e8118..000000000 --- a/firmware/application/protocols/adsb.cpp +++ /dev/null @@ -1,198 +0,0 @@ -/* - * 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. - */ - -#include "adsb.hpp" - -#include "portapack_persistent_memory.hpp" - -namespace adsb { - -uint8_t adsb_frame::get_DF() { - return (raw_data[0] >> 3); -} - -uint8_t adsb_frame::get_msg_type() { - return (raw_data[4] >> 3); -} - -uint32_t adsb_frame::get_ICAO_address() { - return (raw_data[1] << 16) + (raw_data[2] << 8) + raw_data[3]; -} - -void adsb_frame::clear() { - index = 0; - memset(raw_data, 0, 14); -} - -void adsb_frame::push_byte(uint8_t byte) { - if (index >= 14) - return; - - raw_data[index++] = byte; -} - -uint8_t adsb_frame::get_byte(uint8_t index) { - if (index >= 14) - return 0; - - return raw_data[index]; -} - -std::string adsb_frame::get_callsign() { - uint64_t callsign_coded = 0; - uint32_t c; - std::string callsign = ""; - - // Frame bytes to long - for (c = 5; c < 11; c++) { - callsign_coded <<= 8; - callsign_coded |= raw_data[c]; - } - - // Long to 6-bit characters - for (c = 0; c < 8; c++) { - callsign.append(1, icao_id_lut[(callsign_coded >> 42) & 0x3F]); - callsign_coded <<= 6; - } - - return callsign; -} - -void adsb_frame::make_CRC() { - uint8_t adsb_crc[14]; // Temp buffer - uint8_t b, c, s, bitn; - const uint32_t crc_poly = 0x1205FFF; - - // Clear CRC - raw_data[11] = 0x00; - raw_data[12] = 0x00; - raw_data[13] = 0x00; - - // Compute CRC - memcpy(adsb_crc, raw_data, 14); - for (c = 0; c < 11; c++) { - for (b = 0; b < 8; b++) { - if ((adsb_crc[c] << b) & 0x80) { - for (s = 0; s < 25; s++) { - bitn = (c * 8) + b + s; - if ((crc_poly >> s) & 1) adsb_crc[bitn >> 3] ^= (0x80 >> (bitn & 7)); - } - } - } - } - - // Insert CRC in frame - memcpy(&raw_data[11], &adsb_crc[11], 3); -} - -void make_frame_mode_s(adsb_frame& frame, const uint32_t ICAO_address) { - frame.clear(); - frame.push_byte((17 << 3) | 5); // DF17 and CA - frame.push_byte(ICAO_address >> 16); - frame.push_byte(ICAO_address >> 8); - frame.push_byte(ICAO_address & 0xFF); -} - -void generate_frame_id(adsb_frame& frame, const uint32_t ICAO_address, std::string & callsign) { - std::string callsign_formatted(8, '_'); - uint64_t callsign_coded = 0; - uint32_t c, s; - char ch; - - make_frame_mode_s(frame, ICAO_address); - - frame.push_byte(4 << 3); // TC = 4: Aircraft ID - - // Translate and encode callsign - for (c = 0; c < 8; c++) { - ch = callsign[c]; - - for (s = 0; s < 64; s++) - if (ch == icao_id_lut[s]) break; - - if (s >= 64) { - ch = ' '; // Invalid character - s = 32; - } - callsign_coded |= ((uint64_t)s << ((7 - c) * 6)); - callsign[c] = ch; - } - - // Insert callsign in frame - for (c = 0; c < 6; c++) - frame.push_byte((callsign_coded >> ((5 - c) * 8)) & 0xFF); - - frame.make_CRC(); -} - -void generate_frame_emergency(adsb_frame& frame, const uint32_t ICAO_address, const uint8_t code) { - make_frame_mode_s(frame, ICAO_address); - - frame.push_byte((28 << 3) + 1); // TC = 28 (Emergency), subtype = 1 (Emergency) - frame.push_byte(code << 5); - - frame.make_CRC(); -} - -/* -void generate_frame_pos(uint8_t * const adsb_frame, const uint32_t ICAO_address, const uint32_t altitude, - const float latitude, const float longitude) { - uint8_t c, time_parity; - uint32_t altitude_coded; - uint32_t LAT, LON; - float delta_lat, yz, rlat, delta_lon, xz; - - LAT = 0; - - make_frame_mode_s(adsb_frame, ICAO_address); - - adsb_frame[4] = 11 << 3; // TC = 11: Airborne position - - altitude_coded = (altitude + 1000) / 25; // Can be coded in 100ft steps also - - // LAT: - // index j = floor(59*latcprE-60*latcprO+0.50) - // latE = DlatE*(mod(j,60)+latcprE) - // latO = DlatO*(mod(j,59)+latcprO) - // if latE >= 270 -> latE -= 360 - // if latO >= 270 -> latO -= 360 - //time_parity = 0; // 0~1 - //delta_lat = 90.0 / (60.0 - (time_parity / 4.0)); - //yz = 524288.0 * (mod(lat, delta_lat) / delta_lat); // Round to int ! - //rlat = delta_lat * ((yz / 524288.0) + int(lat / delta_lat)); - //delta_lon = 360.0 / (NL(rlat) - time_parity); - //xz = 524288.0 * (mod(lon, delta_lon) / delta_lon); // Round to int ! - if (time_parity) { - A = sign(rlat0)[NL(rlat0) - NL(rlat1)]; - } - // int xz and yz, then: - // xz >>= 2; - // yz >>= 2; - // To get 17 bits - - // aaaaaaa Q bbbb - adsb_frame[5] = ((altitude_coded & 0x7F0) >> 3) | 1; - adsb_frame[6] = ((altitude_coded & 0x00F) << 4) | (LAT >> 15); // Then 0, even/odd, and the 2 LAT-CPR MSBs -} -*/ - -} /* namespace adsb */ diff --git a/firmware/application/protocols/adsb.hpp b/firmware/application/protocols/adsb.hpp deleted file mode 100644 index 6b047cd02..000000000 --- a/firmware/application/protocols/adsb.hpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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. - */ - -#include -#include - -#ifndef __ADSB_H__ -#define __ADSB_H__ - -#define ADSB_PREAMBLE_LENGTH 16 - -namespace adsb { - -struct adsb_frame { -public: - uint8_t get_DF(); - uint8_t get_msg_type(); - uint32_t get_ICAO_address(); - std::string get_callsign(); - - void push_byte(uint8_t byte); - uint8_t get_byte(uint8_t index); // DEBUG - void make_CRC(); - void clear(); - -private: - uint8_t index { 0 }; - uint8_t raw_data[14] { }; // 112 bits at most -}; - -const char icao_id_lut[65] = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ##### ###############0123456789######"; -const uint8_t adsb_preamble[16] = { 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0 }; - -void make_frame_mode_s(adsb_frame& frame, const uint32_t ICAO_address); -void generate_frame_id(adsb_frame&, const uint32_t ICAO_address, std::string& callsign); -//void generate_frame_pos(uint8_t * const adsb_frame, const uint32_t ICAO_address, const uint32_t altitude, -// const float latitude, const float longitude); -void generate_frame_emergency(adsb_frame&, const uint32_t ICAO_address, const uint8_t code); - -} /* namespace adsb */ - -#endif/*__ADSB_H__*/ diff --git a/firmware/application/ui_adsb_rx.cpp b/firmware/application/ui_adsb_rx.cpp index 1799bd55c..3df854587 100644 --- a/firmware/application/ui_adsb_rx.cpp +++ b/firmware/application/ui_adsb_rx.cpp @@ -26,7 +26,6 @@ #include "adsb.hpp" #include "string_format.hpp" -#include "sine_table_int8.hpp" #include "portapack.hpp" #include "baseband_api.hpp" #include "portapack_persistent_memory.hpp" @@ -39,25 +38,35 @@ using namespace portapack; namespace ui { +template<> +void RecentEntriesTable::draw( + const Entry& entry, + const Rect& target_rect, + Painter& painter, + const Style& style +) { + painter.draw_string(target_rect.location(), style, to_string_hex_array((uint8_t*)entry.raw_data, 10) + " " + entry.time); +} + void ADSBRxView::focus() { offset_field.focus(); offset_field.set_value(13179); } ADSBRxView::~ADSBRxView() { - //transmitter_model.disable(); - //baseband::shutdown(); + receiver_model.disable(); + baseband::shutdown(); } -bool ADSBRxView::analyze(uint64_t offset) { +/*bool ADSBRxView::analyze(uint64_t offset) { Coord lcd_x = 0, lcd_y = 0; + int8_t re, im; adsb_frame frame; int16_t file_data[128]; // 256 bytes / 2 IQ / 16 bits = 64 samples complex8_t iq_data[256]; // 256 samples uint64_t file_offset = 0; uint8_t data_put = 0, data_get = 0; int16_t f_re, f_im; - int8_t re, im; uint32_t c; uint8_t level, bit, byte; Color mark_color; @@ -224,42 +233,64 @@ bool ADSBRxView::analyze(uint64_t offset) { } return false; +}*/ + +void ADSBRxView::on_frame(const ADSBFrameMessage * message) { + rtc::RTC datetime; + std::string str_timestamp; + auto frame = message->frame; + auto& entry = ::on_packet(recent, frame.get_ICAO_address()); + + rtcGetTime(&RTCD1, &datetime); + str_timestamp = to_string_dec_uint(datetime.hour(), 2, '0') + ":" + + to_string_dec_uint(datetime.minute(), 2, '0') + ":" + + to_string_dec_uint(datetime.second(), 2, '0'); + entry.set_time(str_timestamp); + entry.set_raw(frame.get_raw_data()); + recent_entries_view.set_dirty(); } ADSBRxView::ADSBRxView(NavigationView& nav) { - - //baseband::run_image(portapack::spi_flash::image_tag_adsb_rx); + baseband::run_image(portapack::spi_flash::image_tag_adsb_rx); add_children({ - &labels, + //&labels, &offset_field, - &button_ffw, + //&button_ffw, &text_debug_a, &text_debug_b, &text_debug_c, - &text_debug_d, - &text_debug_e + &recent_entries_view }); - // File must be 16bit complex @ 2Msps ! + recent_entries_view.set_parent_rect({ 0, 64, 240, 224 }); - auto result = iq_file.open("ADSB.C16"); + // File must be 16bit complex @ 2Msps ! + /*auto result = iq_file.open("ADSB.C16"); if (result.is_valid()) { text_debug_a.set("Can't open file"); - } + }*/ - offset_field.on_change = [this, &nav](int32_t value) { + /*offset_field.on_change = [this, &nav](int32_t value) { // TODO - }; + };*/ - button_ffw.on_select = [this, &nav](Button&) { - auto new_view = nav.push(); - /*while (!analyze(f_offset)) { + /*button_ffw.on_select = [this, &nav](Button&) { + //nav.push(); + while (!analyze(f_offset)) { f_offset++; } offset_field.set_value(f_offset); - f_offset++;*/ - }; + f_offset++; + };*/ + + baseband::set_adsb(); + + receiver_model.set_tuning_frequency(1090000000); + receiver_model.set_modulation(ReceiverModel::Mode::SpectrumAnalysis); + receiver_model.set_sampling_rate(2000000); + receiver_model.set_baseband_bandwidth(2500000); + receiver_model.enable(); } } /* namespace ui */ diff --git a/firmware/application/ui_adsb_rx.hpp b/firmware/application/ui_adsb_rx.hpp index 4e6d0bc9d..5aabfb108 100644 --- a/firmware/application/ui_adsb_rx.hpp +++ b/firmware/application/ui_adsb_rx.hpp @@ -26,12 +26,48 @@ //#include "ui_widget.hpp" #include "ui_navigation.hpp" #include "ui_font_fixed_8x16.hpp" +#include "recent_entries.hpp" #include "message.hpp" #include "portapack.hpp" namespace ui { +struct ADSBRecentEntry { + using Key = uint32_t; + + static constexpr Key invalid_key = 0xffffffff; + + uint32_t ICAO_address; + uint8_t raw_data[14] { }; // 112 bits at most + std::string time { "" }; + + ADSBRecentEntry( + ) : ADSBRecentEntry { 0 } + { + } + + ADSBRecentEntry( + const uint32_t ICAO_address + ) : ICAO_address { ICAO_address } + { + } + + Key key() const { + return ICAO_address; + } + + void set_time(std::string& new_time) { + time = new_time; + } + + void set_raw(uint8_t * raw_ptr) { + memcpy(raw_data, raw_ptr, 14); + } +}; + +using ADSBRecentEntries = RecentEntries; + class ADSBRxView : public View { public: ADSBRxView(NavigationView& nav); @@ -42,16 +78,24 @@ public: std::string title() const override { return "ADS-B debug"; }; private: - static constexpr float k = 1.0f / 128.0f; + //static constexpr float k = 1.0f / 128.0f; - File iq_file { }; - size_t f_offset { 0 }; + //File iq_file { }; + //size_t f_offset { 0 }; - bool analyze(uint64_t offset); + //bool analyze(uint64_t offset); + void on_frame(const ADSBFrameMessage * message); - Labels labels { + const RecentEntriesColumns columns { { + { "Raw", 21 }, + { "Time", 8 }, + } }; + ADSBRecentEntries recent { }; + RecentEntriesView> recent_entries_view { columns, recent }; + + /*Labels labels { { { 0 * 8, 0 * 8 }, "Test", Color::light_grey() } - }; + };*/ NumberField offset_field { { 0, 0 }, @@ -60,10 +104,6 @@ private: 1, '0' }; - Text text_debug_e { - { 7 * 8, 0 * 16, 14 * 8, 16 }, - "-" - }; Text text_debug_a { { 0 * 8, 1 * 16, 30 * 8, 16 }, @@ -77,24 +117,19 @@ private: { 0 * 8, 3 * 16, 30 * 8, 16 }, "-" }; - Text text_debug_d { - { 0 * 8, 4 * 16, 30 * 8, 16 }, - "-" - }; - Button button_ffw { + /*Button button_ffw { { 184, 0 * 16, 56, 16 }, "FFW" - }; - - /* - MessageHandlerRegistration message_handler_tx_done { - Message::ID::TXDone, - [this](const Message* const p) { - const auto message = *reinterpret_cast(p); - this->on_txdone(message.done); - } };*/ + + MessageHandlerRegistration message_handler_frame { + Message::ID::ADSBFrame, + [this](Message* const p) { + const auto message = static_cast(p); + this->on_frame(message); + } + }; }; } /* namespace ui */ diff --git a/firmware/application/ui_adsb_tx.cpp b/firmware/application/ui_adsb_tx.cpp index aaef02f01..6445aa278 100644 --- a/firmware/application/ui_adsb_tx.cpp +++ b/firmware/application/ui_adsb_tx.cpp @@ -26,7 +26,6 @@ #include "string_format.hpp" #include "portapack.hpp" #include "baseband_api.hpp" -#include "portapack_persistent_memory.hpp" #include #include @@ -37,7 +36,6 @@ using namespace portapack; namespace ui { void ADSBTxView::focus() { - //sym_icao.focus(); tx_view.focus(); } @@ -46,28 +44,38 @@ ADSBTxView::~ADSBTxView() { baseband::shutdown(); } -void ADSBTxView::paint(Painter& painter) { - (void)painter; +void ADSBTxView::paint(Painter&) { button_callsign.set_text(callsign); } void ADSBTxView::generate_frame() { uint32_t c; - std::string str_debug; + uint8_t * bin_ptr = shared_memory.bb_data.data; - generate_frame_id(frame, sym_icao.value_hex_u64(), callsign); + generate_frame_id(frames[0], sym_icao.value_hex_u64(), callsign); + generate_frame_pos(frames[1], sym_icao.value_hex_u64(), 5000, field_lat_degrees.value(), field_lon_degrees.value(), 0); + generate_frame_pos(frames[2], sym_icao.value_hex_u64(), 5000, field_lat_degrees.value(), field_lon_degrees.value(), 1); - memset(adsb_bin, 0, 112); + memset(bin_ptr, 0, 240); + auto raw_ptr = frames[0].get_raw_data(); + + memcpy(bin_ptr, adsb_preamble, 16); + // Convert to binary (1 byte per bit, faster for baseband code) for (c = 0; c < 112; c++) { - if ((frame.get_byte(c >> 3) << (c & 7)) & 0x80) - adsb_bin[c] = 1; + if ((raw_ptr[c >> 3] << (c & 7)) & 0x80) { + bin_ptr[(c * 2) + 16] = 1; + bin_ptr[(c * 2) + 16 + 1] = 0; + } else { + bin_ptr[(c * 2) + 16] = 0; + bin_ptr[(c * 2) + 16 + 1] = 1; + } } // Display in hex for debug - //text_frame_a.set(to_string_hex_array(frame.get_byte(0), 7)); - //text_frame_b.set(to_string_hex_array(frame.get_byte(7), 7)); + text_frame_a.set(to_string_hex_array(frames[0].get_raw_data(), 7)); + text_frame_b.set(to_string_hex_array(frames[0].get_raw_data() + 7, 7)); button_callsign.set_text(callsign); } @@ -75,28 +83,78 @@ void ADSBTxView::generate_frame() { bool ADSBTxView::start_tx() { generate_frame(); - memcpy(shared_memory.bb_data.data, adsb_bin, 112); - baseband::set_adsb(); - - transmitter_model.set_tuning_frequency(434000000); // DEBUG + transmitter_model.set_tuning_frequency(434000000); transmitter_model.set_sampling_rate(4000000U); transmitter_model.set_rf_amp(true); transmitter_model.set_vga(40); - transmitter_model.set_baseband_bandwidth(2500000); + transmitter_model.set_baseband_bandwidth(10000000); transmitter_model.enable(); - return false; // DEBUG + baseband::set_adsb(); + + return true; } void ADSBTxView::on_txdone(const bool v) { - //size_t sr; - if (v) { transmitter_model.disable(); tx_view.set_transmitting(false); - //progress.set_value(0); - } else { - //progress.set_value(n); + } +} + +void ADSBTxView::rotate_frames() { + // DEBUG + uint8_t * bin_ptr = shared_memory.bb_data.data; + uint8_t * raw_ptr; + uint32_t frame_index = 0, plane_index = 0; + uint32_t c, regen = 0; + float offs = 0; + + for (;;) { + if (!regen) { + regen = 10; + + generate_frame_id(frames[0], plane_index, "DEMO" + to_string_dec_uint(plane_index)); + generate_frame_pos(frames[1], plane_index, 5000, plane_lats[plane_index]/8 + offs + 38.5, plane_lons[plane_index]/8 + 125.8, 0); + generate_frame_pos(frames[2], plane_index, 5000, plane_lats[plane_index]/8 + offs + 38.5, plane_lons[plane_index]/8 + 125.8, 1); + generate_frame_identity(frames[3], plane_index, 1337); + + if (plane_index == 11) + plane_index = 0; + else + plane_index++; + + offs += 0.001; + } + + memset(bin_ptr, 0, 240); + + raw_ptr = frames[frame_index].get_raw_data(); + + memcpy(bin_ptr, adsb_preamble, 16); + + // Convert to binary (1 byte per bit, faster for baseband code) + for (c = 0; c < 112; c++) { + if ((raw_ptr[c >> 3] << (c & 7)) & 0x80) { + bin_ptr[(c * 2) + 16] = 1; + bin_ptr[(c * 2) + 16 + 1] = 0; + } else { + bin_ptr[(c * 2) + 16] = 0; + bin_ptr[(c * 2) + 16 + 1] = 1; + } + } + + baseband::set_adsb(); + + chThdSleepMilliseconds(5); + + if (frame_index == 3) { + frame_index = 0; + if (regen) + regen--; + } else { + frame_index++; + } } } @@ -126,16 +184,14 @@ ADSBTxView::ADSBTxView(NavigationView& nav) { options_format.set_by_value(17); // Mode S - options_format.on_change = [this](size_t i, int32_t v) { - (void)i; - (void)v; + options_format.on_change = [this](size_t, int32_t) { generate_frame(); }; sym_icao.on_change = [this]() { generate_frame(); }; button_callsign.on_select = [this, &nav](Button&) { - text_prompt(nav, &callsign, 9); + text_prompt(nav, &callsign, 8); }; field_altitude.set_value(11000); @@ -146,19 +202,24 @@ ADSBTxView::ADSBTxView(NavigationView& nav) { field_lon_minutes.set_value(0); field_lon_seconds.set_value(0); + field_lat_degrees.on_change = [this](int32_t) { + generate_frame(); + }; + field_lon_degrees.on_change = [this](int32_t) { + generate_frame(); + }; + for (c = 0; c < 4; c++) field_squawk.set_sym(c, 0); generate_frame(); - tx_view.on_edit_frequency = [this, &nav]() { - // Shouldn't be able to edit frequency - return; - }; + receiver_model.set_tuning_frequency(434000000); // DEBUG tx_view.on_start = [this]() { if (start_tx()) tx_view.set_transmitting(true); + //rotate_frames(); }; tx_view.on_stop = [this]() { diff --git a/firmware/application/ui_adsb_tx.hpp b/firmware/application/ui_adsb_tx.hpp index b45938cf4..ee63f2a48 100644 --- a/firmware/application/ui_adsb_tx.hpp +++ b/firmware/application/ui_adsb_tx.hpp @@ -41,7 +41,7 @@ public: ADSBTxView(NavigationView& nav); ~ADSBTxView(); - void paint(Painter& painter) override; + void paint(Painter&) override; void focus() override; @@ -54,16 +54,44 @@ private: SEQUENCE }; + const float plane_lats[12] = { + 0, + -1, + -2, + -3, + -4, + -5, + -4.5, + -5, + -4, + -3, + -2, + -1 + }; + const float plane_lons[12] = { + 0, + 1, + 1, + 1, + 2, + 1, + 0, + -1, + -2, + -1, + -1, + -1 + }; + tx_modes tx_mode = IDLE; - std::string callsign = "KLM1023 "; + std::string callsign = "PORTAPAC"; - adsb_frame frame { }; - uint8_t adsb_bin[112]; // 112 bit data block + ADSBFrame frames[4] { }; bool start_tx(); void generate_frame(); - void generate_frame_pos(); + void rotate_frames(); void on_txdone(const bool v); const Style style_val { @@ -82,8 +110,8 @@ private: { { 2 * 8, 4 * 8 }, "ICAO24:", Color::light_grey() }, { { 2 * 8, 7 * 8 }, "ID:", Color::light_grey() }, { { 2 * 8, 10 * 8 }, "Altitude: feet", Color::light_grey() }, - { { 2 * 8, 12 * 8 }, "Latitude: * ' \"", Color::light_grey() }, // No ° symbol in 8x16 font - { { 2 * 8, 14 * 8 }, "Longitude: * ' \"", Color::light_grey() }, // No ° symbol in 8x16 font + { { 2 * 8, 12 * 8 }, "Latitude: * ' \"", Color::light_grey() }, // No ° symbol in 8x16 font + { { 2 * 8, 14 * 8 }, "Longitude: * ' \"", Color::light_grey() }, // No ° symbol in 8x16 font { { 15 * 8, 18 * 8 }, "Squawk", Color::light_grey() } }; @@ -117,23 +145,23 @@ private: }; NumberField field_lat_degrees { - { 12 * 8, 6 * 16 }, 3, { -90, 90 }, 1, ' ' + { 12 * 8, 6 * 16 }, 4, { -90, 90 }, 1, ' ' }; NumberField field_lat_minutes { - { 16 * 8, 6 * 16 }, 2, { 0, 59 }, 1, ' ' + { 17 * 8, 6 * 16 }, 2, { 0, 59 }, 1, ' ' }; NumberField field_lat_seconds { - { 19 * 8, 6 * 16 }, 2, { 0, 59 }, 1, ' ' + { 20 * 8, 6 * 16 }, 2, { 0, 59 }, 1, ' ' }; NumberField field_lon_degrees { - { 12 * 8, 7 * 16 }, 3, { -90, 90 }, 1, ' ' + { 12 * 8, 7 * 16 }, 4, { -180, 180 }, 1, ' ' }; NumberField field_lon_minutes { - { 16 * 8, 7 * 16 }, 2, { 0, 59 }, 1, ' ' + { 17 * 8, 7 * 16 }, 2, { 0, 59 }, 1, ' ' }; NumberField field_lon_seconds { - { 19 * 8, 7 * 16 }, 2, { 0, 59 }, 1, ' ' + { 20 * 8, 7 * 16 }, 2, { 0, 59 }, 1, ' ' }; Checkbox check_emergency { @@ -160,8 +188,8 @@ private: TransmitterView tx_view { 16 * 16, - 1090000000, - 2000000, + 0, + 0, true }; diff --git a/firmware/application/ui_transmitter.cpp b/firmware/application/ui_transmitter.cpp index 205ef19f0..a5cebcd46 100644 --- a/firmware/application/ui_transmitter.cpp +++ b/firmware/application/ui_transmitter.cpp @@ -76,7 +76,7 @@ void TransmitterView::on_tuning_frequency_changed(rf::Frequency f) { } void TransmitterView::on_bandwidth_changed(uint32_t bandwidth) { - transmitter_model.set_bandwidth(bandwidth); + //transmitter_model.set_bandwidth(bandwidth); } void TransmitterView::set_transmitting(const bool transmitting) { @@ -92,7 +92,7 @@ void TransmitterView::set_transmitting(const bool transmitting) { } void TransmitterView::on_show() { - field_frequency.set_value(receiver_model.tuning_frequency()); + //field_frequency.set_value(receiver_model.tuning_frequency()); } void TransmitterView::focus() { diff --git a/firmware/baseband/CMakeLists.txt b/firmware/baseband/CMakeLists.txt index 1bf1297d4..55fccf8af 100644 --- a/firmware/baseband/CMakeLists.txt +++ b/firmware/baseband/CMakeLists.txt @@ -275,12 +275,19 @@ macro(DeclareTargets chunk_tag name) set(BASEBAND_IMAGES ${BASEBAND_IMAGES} ${PROJECT_NAME}.img) endmacro() +### ADS-B RX + +set(MODE_CPPSRC + proc_adsbrx.cpp +) +DeclareTargets(PADR adsbrx) + ### ADS-B TX set(MODE_CPPSRC proc_adsbtx.cpp ) -DeclareTargets(PADS ads) +DeclareTargets(PADT adsbtx) ### AFSK diff --git a/firmware/baseband/channel_stats_collector.hpp b/firmware/baseband/channel_stats_collector.hpp index daab08220..ca7d0eedb 100644 --- a/firmware/baseband/channel_stats_collector.hpp +++ b/firmware/baseband/channel_stats_collector.hpp @@ -35,7 +35,14 @@ class ChannelStatsCollector { public: template void feed(const buffer_c16_t& src, Callback callback) { - max_squared = compute_max_squared(src, max_squared); + auto src_p = src.p; + while(src_p < &src.p[src.count]) { + const uint32_t sample = *__SIMD32(src_p)++; + const uint32_t mag_sq = __SMUAD(sample, sample); + if( mag_sq > max_squared ) { + max_squared = mag_sq; + } + } count += src.count; const size_t samples_per_update = src.sampling_rate * update_interval; @@ -54,22 +61,6 @@ private: static constexpr float update_interval { 0.1f }; uint32_t max_squared { 0 }; size_t count { 0 }; - - static uint32_t compute_max_squared( - const buffer_c16_t& src, - uint32_t max_squared - ) { - auto src_p = src.p; - while(src_p < &src.p[src.count]) { - const uint32_t sample = *__SIMD32(src_p)++; - const uint32_t mag_sq = __SMUAD(sample, sample); - if( mag_sq > max_squared ) { - max_squared = mag_sq; - } - } - - return max_squared; - } }; #endif/*__CHANNEL_STATS_COLLECTOR_H__*/ diff --git a/firmware/baseband/proc_adsbrx.cpp b/firmware/baseband/proc_adsbrx.cpp new file mode 100644 index 000000000..9eb2f210e --- /dev/null +++ b/firmware/baseband/proc_adsbrx.cpp @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2017 Furrtek + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "proc_adsbrx.hpp" +#include "portapack_shared_memory.hpp" +#include "sine_table_int8.hpp" +#include "event_m4.hpp" + +#include +#include + +using namespace adsb; + +void ADSBRXProcessor::execute(const buffer_c8_t& buffer) { + int8_t re, im; + float mag; + uint32_t c; + uint8_t level, bit, byte; + bool confidence, first_in_window, last_in_window; + + // This is called at 2M/2048 = 977Hz + // One pulse = 500ns = 2 samples + // One bit = 2 pulses = 1us = 4 samples + + if (!configured) return; + + for (size_t i = 0; i < buffer.count; i++) { + + // Compute sample's magnitude + re = buffer.p[i].real(); + im = buffer.p[i].imag(); + mag = __builtin_sqrtf((re * re) + (im * im)) * k; + + // Only used for preamble detection and visualisation + level = (mag < 0.3) ? 0 : // Blank weak signals + (mag > prev_mag) ? 1 : 0; + + if (decoding) { + // Decode + + // 1 bit lasts 2 samples + if (sample_count & 1) { + if ((prev_mag < threshold_low) && (mag < threshold_low)) { + // Both under window, silence. + if (null_count > 3) { + //text_debug_b.set("Bits:" + bits.substr(0, 25)); + //text_debug_c.set("Hex:" + hex_str.substr(0, 26)); + //text_debug_d.set("DF=" + to_string_dec_uint(frame.get_DF()) + " ICAO=" + to_string_hex(frame.get_ICAO_address(), 6)); + + const ADSBFrameMessage message(frame); + shared_memory.application_queue.push(message); + + decoding = false; + } else + null_count++; + + confidence = false; + if (prev_mag > mag) + bit = 1; + else + bit = 0; + + } else { + + null_count = 0; + + first_in_window = ((prev_mag >= threshold_low) && (prev_mag <= threshold_high)); + last_in_window = ((mag >= threshold_low) && (mag <= threshold_high)); + + if ((first_in_window && !last_in_window) || (!first_in_window && last_in_window)) { + confidence = true; + if (prev_mag > mag) + bit = 1; + else + bit = 0; + } else { + confidence = false; + if (prev_mag > mag) + bit = 1; + else + bit = 0; + } + } + + byte = bit | (byte << 1); + bit_count++; + if (!(bit_count & 7)) { + // Got one byte + frame.push_byte(byte); + } + } + sample_count++; + } else { + // Look for preamble + + // Shift + for (c = 0; c < (ADSB_PREAMBLE_LENGTH - 1); c++) + shifter[c] = shifter[c + 1]; + shifter[15] = std::make_pair(mag, level); + + // Compare + for (c = 0; c < ADSB_PREAMBLE_LENGTH; c++) { + if (shifter[c].second != adsb_preamble[c]) + break; + } + + if (c == ADSB_PREAMBLE_LENGTH) { + decoding = true; + sample_count = 0; + null_count = 0; + bit_count = 0; + frame.clear(); + + // Compute preamble pulses power to set thresholds + threshold = (shifter[0].first + shifter[2].first + shifter[7].first + shifter[9].first) / 4; + threshold_high = threshold * 1.414; // +3dB + threshold_low = threshold * 0.707; // -3dB + } + } + + prev_mag = mag; + } +} + +void ADSBRXProcessor::on_message(const Message* const message) { + if (message->id == Message::ID::ADSBConfigure) { + null_count = 0; + bit_count = 0; + sample_count = 0; + decoding = false; + configured = true; + } +} + +int main() { + EventDispatcher event_dispatcher { std::make_unique() }; + event_dispatcher.run(); + return 0; +} diff --git a/firmware/baseband/proc_adsbrx.hpp b/firmware/baseband/proc_adsbrx.hpp new file mode 100644 index 000000000..3ab8b89f5 --- /dev/null +++ b/firmware/baseband/proc_adsbrx.hpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2017 Furrtek + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PROC_ADSBRX_H__ +#define __PROC_ADSBRX_H__ + +#include "baseband_processor.hpp" +#include "baseband_thread.hpp" + +#include "adsb_frame.hpp" + +using namespace adsb; + +#define ADSB_PREAMBLE_LENGTH 16 + +class ADSBRXProcessor : public BasebandProcessor { +public: + void execute(const buffer_c8_t& buffer) override; + + void on_message(const Message* const message) override; + +private: + static constexpr float k = 1.0f / 128.0f; + + static constexpr size_t baseband_fs = 2000000; + + BasebandThread baseband_thread { baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive }; + + ADSBFrame frame { }; + bool configured { false }; + float prev_mag { 0 }; + float threshold, threshold_low, threshold_high; + size_t null_count, bit_count, sample_count; + std::pair shifter[ADSB_PREAMBLE_LENGTH]; + bool decoding { }; + bool preamble { }, active { }; + uint16_t bit_pos { 0 }; + uint8_t cur_bit { 0 }; + uint32_t sample { 0 }; + int8_t re { }, im { }; +}; + +#endif diff --git a/firmware/baseband/proc_adsbtx.cpp b/firmware/baseband/proc_adsbtx.cpp index a40385aec..27d2eb86d 100644 --- a/firmware/baseband/proc_adsbtx.cpp +++ b/firmware/baseband/proc_adsbtx.cpp @@ -32,26 +32,29 @@ void ADSBTXProcessor::execute(const buffer_c8_t& buffer) { // This is called at 4M/2048 = 1953Hz // One pulse = 500ns = 2 samples // One bit = 2 pulses = 1us = 4 samples + // Test this with ./dump1090 --freq 434000000 --gain 20 + // Or ./dump1090 --freq 434000000 --gain 20 --interactive --net --net-http-port 8080 --net-beast if (!configured) return; - for (size_t i = 0; i < buffer.count; i++) { - - if (active) { - if (!sample) { - sample = 3; - - if (preamble) { - if (bit_pos >= 16) { - preamble = false; - bit_pos = 0; - } else { - cur_bit = (preamble_parts << bit_pos) >> 15; - bit_pos++; - } - } - - if (!preamble) { + /*if (terminate) { + for (size_t i = 0; i < buffer.count; i++) { + buffer.p[i] = { 0, 0 }; + } + terminate--; + if (!terminate) { + message.done = true; + shared_memory.application_queue.push(message); + configured = false; + return; + } + } else {*/ + + for (size_t i = 0; i < buffer.count; i++) { + + /*if (active) { + if (!sample) { + sample = 300; if (bit_pos >= 112) { active = false; // Stop cur_bit = 0; @@ -59,38 +62,39 @@ void ADSBTXProcessor::execute(const buffer_c8_t& buffer) { cur_bit = shared_memory.bb_data.data[bit_pos]; bit_pos++; } + } else + sample--; + + if (!preamble) { + if (sample == 150) + cur_bit ^= 1; // Invert } - } else - sample--; + } else {*/ + /*cur_bit = 0; + if (bit_pos >= 16384) { + configured = false; + message.done = true; + shared_memory.application_queue.push(message); + }*/ + if (bit_pos >= (240 << 1)) { + configured = false; + terminate = 100; + cur_bit = 0; + } else { + cur_bit = shared_memory.bb_data.data[bit_pos >> 1]; + bit_pos++; + } + //} - if (sample == 1) - cur_bit ^= 1; // Invert - } else { - //cur_bit = 0; - if (bit_pos == 8192) { // ? - configured = false; - message.done = true; - shared_memory.application_queue.push(message); + if (cur_bit) { + // Crude AM + buffer.p[i] = am_lut[phase & 3]; + phase++; + } else { + buffer.p[i] = { 0, 0 }; } - bit_pos++; } - - delta = tone_sample * fm_delta; - tone_sample += 128; - - if (cur_bit) { - phase += delta; - sphase = phase + (64 << 24); - - re = (sine_table_i8[(sphase & 0xFF000000U) >> 24]); - im = (sine_table_i8[(phase & 0xFF000000U) >> 24]); - } else { - re = 0; - im = 0; - } - - buffer.p[i] = {re, im}; - } + //} } void ADSBTXProcessor::on_message(const Message* const p) { @@ -98,13 +102,10 @@ void ADSBTXProcessor::on_message(const Message* const p) { if (message.id == Message::ID::ADSBConfigure) { bit_pos = 0; - sample = 0; - cur_bit = 0; - preamble = true; + phase = 0; active = true; configured = true; - - fm_delta = 50000 * (0xFFFFFFULL / 4000000); // Fixed bw for now + terminate = 0; } } diff --git a/firmware/baseband/proc_adsbtx.hpp b/firmware/baseband/proc_adsbtx.hpp index 1b68afa29..2567dc696 100644 --- a/firmware/baseband/proc_adsbtx.hpp +++ b/firmware/baseband/proc_adsbtx.hpp @@ -26,8 +26,6 @@ #include "baseband_processor.hpp" #include "baseband_thread.hpp" -#define TEST_F2D(f) (uint32_t)((f) * ((1ULL << 32) / 4000000)) - class ADSBTXProcessor : public BasebandProcessor { public: void execute(const buffer_c8_t& buffer) override; @@ -39,16 +37,18 @@ private: BasebandThread baseband_thread { 4000000, this, NORMALPRIO + 20, baseband::Direction::Transmit }; - const uint16_t preamble_parts = 0b1010000101000000; - bool preamble { }, active { }; - uint16_t bit_pos { 0 }; - uint8_t cur_bit { 0 }; - uint32_t sample { 0 }; - uint32_t tone_phase { 0 }; - uint32_t fm_delta { 0 }; - uint32_t phase { 0 }, sphase { 0 }; - int32_t tone_sample { 0 }, delta { 0 }; - int8_t re { }, im { }; + const complex8_t am_lut[4] = { + { 127, 0 }, + { 0, 127 }, + { -127, 0 }, + { 0, -127 } + }; + + bool active { }; + uint32_t terminate { }; + uint32_t bit_pos { 0 }; + uint32_t cur_bit { 0 }; + uint32_t phase { 0 }; TXDoneMessage message { }; }; diff --git a/firmware/chibios-portapack/os/hal/platforms/LPC43xx_M4/lpc43xx_m4.h b/firmware/chibios-portapack/os/hal/platforms/LPC43xx_M4/lpc43xx_m4.h index d56457ae8..c17925a07 100644 --- a/firmware/chibios-portapack/os/hal/platforms/LPC43xx_M4/lpc43xx_m4.h +++ b/firmware/chibios-portapack/os/hal/platforms/LPC43xx_M4/lpc43xx_m4.h @@ -129,7 +129,7 @@ typedef enum IRQn { #define __SIMD32_TYPE int32_t #define __SIMD32(addr) (*(__SIMD32_TYPE **) & (addr)) -#define _SIMD32_OFFSET(addr) (*(__SIMD32_TYPE *) (addr)) +#define _SIMD32_OFFSET(addr) (*(__SIMD32_TYPE *) (addr)) /* Overload of __SXTB16() to add ROR argument, since using __ROR() as an * argument to the existing __SXTB16() doesn't produce optimum/sane code. diff --git a/firmware/common/adsb.cpp b/firmware/common/adsb.cpp new file mode 100644 index 000000000..eda163760 --- /dev/null +++ b/firmware/common/adsb.cpp @@ -0,0 +1,177 @@ +/* + * 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. + */ + +#include "adsb.hpp" + +#include + +namespace adsb { + +void make_frame_mode_s(ADSBFrame& frame, const uint32_t ICAO_address) { + frame.clear(); + frame.push_byte((17 << 3) | 5); // DF17 and CA + frame.push_byte(ICAO_address >> 16); + frame.push_byte(ICAO_address >> 8); + frame.push_byte(ICAO_address & 0xFF); +} + +void generate_frame_id(ADSBFrame& frame, const uint32_t ICAO_address, const std::string& callsign) { + std::string callsign_formatted(8, '_'); + uint64_t callsign_coded = 0; + uint32_t c, s; + char ch; + + make_frame_mode_s(frame, ICAO_address); + + frame.push_byte(4 << 3); // TC = 4: Aircraft ID + + // Translate and encode callsign + for (c = 0; c < 8; c++) { + ch = callsign[c]; + + for (s = 0; s < 64; s++) + if (ch == icao_id_lut[s]) break; + + if (s == 64) { + ch = ' '; // Invalid character + s = 32; + } + + callsign_coded <<= 6; + callsign_coded |= s; + + //callsign[c] = ch; + } + + // Insert callsign in frame + for (c = 0; c < 6; c++) + frame.push_byte((callsign_coded >> ((5 - c) * 8)) & 0xFF); + + frame.make_CRC(); +} + +/*void generate_frame_emergency(ADSBFrame& frame, const uint32_t ICAO_address, const uint8_t code) { + make_frame_mode_s(frame, ICAO_address); + + frame.push_byte((28 << 3) + 1); // TC = 28 (Emergency), subtype = 1 (Emergency) + frame.push_byte(code << 5); + + frame.make_CRC(); +}*/ + +void generate_frame_identity(ADSBFrame& frame, const uint32_t ICAO_address, const uint32_t code) { + frame.clear(); + + frame.push_byte(21 << 3); // DF = 21 + frame.push_byte(0); + + // 1337: + frame.push_byte(0b00011100); + frame.push_byte(0b00111101); + + frame.make_CRC(); +} + +float cpr_mod(float a, float b) { + return a - (b * floor(a / b)); +} + +int cpr_NL(float lat) { + if (lat < 0) + lat = -lat; // Symmetry + + for (size_t c = 0; c < 58; c++) { + if (lat < adsb_lat_lut[c]) + return 59 - c; + } + + return 1; +} + +int cpr_N(float lat, int is_odd) { + int nl = cpr_NL(lat) - is_odd; + + if (nl < 1) + nl = 1; + + return nl; +} + +void generate_frame_pos(ADSBFrame& frame, const uint32_t ICAO_address, const uint32_t altitude, + const float latitude, const float longitude, const uint32_t time_parity) { + uint32_t altitude_coded; + uint32_t lat, lon; + float delta_lat, yz, rlat, delta_lon, xz; + + make_frame_mode_s(frame, ICAO_address); + + frame.push_byte(11 << 3); // TC = 11 (Airborne position) + + altitude_coded = (altitude + 1000) / 25; // Can be coded in 100ft steps also + frame.push_byte(altitude_coded >> 4); // Top-most altitude bits + + // Decoding method (from dump1090): + // index int j = floor(((59 * latcprE - 60 * latcprO) / 131072) + 0.50) + // latE = DlatE * (cpr_mod(j, 60) + (latcprE / 131072)) + // latO = DlatO * (cpr_mod(j, 59) + (latcprO / 131072)) + // if latE >= 270 -> latE -= 360 + // if latO >= 270 -> latO -= 360 + // if (cpr_NL(latE) != cpr_NL(latO)) return; + + // int ni = cpr_N(latE ,0); + // int m = floor((((loncprE * (cpr_NL(latE) - 1)) - (loncprO * cpr_NL(latE))) / 131072) + 0.5) + // lon = cpr_Dlon(latE, 0) * (cpr_mod(m, ni) + loncprE / 131072); + // lat = latE; + // ... or ... + // int ni = cpr_N(latO ,0); + // int m = floor((((loncprE * (cpr_NL(latO) - 1)) - (loncprO * cpr_NL(latO))) / 131072) + 0.5) + // lon = cpr_Dlon(latO, 0) * (cpr_mod(m, ni) + loncprO / 131072); + // lat = latO; + // ... and ... + // if (lon > 180) lon -= 360; + + // CPR encoding + // Info: http://antena.fe.uni-lj.si/literatura/Razno/Avionika/modes/CPRencoding.pdf + + delta_lat = 360.0 / ((4.0 * 15.0) - time_parity); // NZ = 15 + yz = floor(131072.0 * (cpr_mod(latitude, delta_lat) / delta_lat) + 0.5); + rlat = delta_lat * ((yz / 131072.0) + floor(latitude / delta_lat)); + + if ((cpr_NL(rlat) - time_parity) > 0) + delta_lon = 360.0 / cpr_N(rlat, time_parity); + else + delta_lon = 360.0; + xz = floor(131072.0 * (cpr_mod(longitude, delta_lon) / delta_lon) + 0.5); + + lat = cpr_mod(yz, 131072.0); + lon = cpr_mod(xz, 131072.0); + + frame.push_byte((altitude_coded << 4) | ((uint32_t)time_parity << 2) | (lat >> 15)); + frame.push_byte(lat >> 7); + frame.push_byte((lat << 1) | (lon >> 16)); + frame.push_byte(lon >> 8); + frame.push_byte(lon); + + frame.make_CRC(); +} + +} /* namespace adsb */ diff --git a/firmware/common/adsb.hpp b/firmware/common/adsb.hpp new file mode 100644 index 000000000..7f31c42d4 --- /dev/null +++ b/firmware/common/adsb.hpp @@ -0,0 +1,60 @@ +/* + * 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 __ADSB_H__ +#define __ADSB_H__ + +#include "adsb_frame.hpp" + +#include +#include + +namespace adsb { + +const float adsb_lat_lut[58] = { + 10.47047130, 14.82817437, 18.18626357, 21.02939493, + 23.54504487, 25.82924707, 27.93898710, 29.91135686, + 31.77209708, 33.53993436, 35.22899598, 36.85025108, + 38.41241892, 39.92256684, 41.38651832, 42.80914012, + 44.19454951, 45.54626723, 46.86733252, 48.16039128, + 49.42776439, 50.67150166, 51.89342469, 53.09516153, + 54.27817472, 55.44378444, 56.59318756, 57.72747354, + 58.84763776, 59.95459277, 61.04917774, 62.13216659, + 63.20427479, 64.26616523, 65.31845310, 66.36171008, + 67.39646774, 68.42322022, 69.44242631, 70.45451075, + 71.45986473, 72.45884545, 73.45177442, 74.43893416, + 75.42056257, 76.39684391, 77.36789461, 78.33374083, + 79.29428225, 80.24923213, 81.19801349, 82.13956981, + 83.07199445, 83.99173563, 84.89166191, 85.75541621, + 86.53536998, 87.00000000 +}; + +void make_frame_mode_s(ADSBFrame& frame, const uint32_t ICAO_address); +void generate_frame_id(ADSBFrame& frame, const uint32_t ICAO_address, const std::string& callsign); +void generate_frame_pos(ADSBFrame& frame, const uint32_t ICAO_address, const uint32_t altitude, + const float latitude, const float longitude, const uint32_t time_parity); +void generate_frame_emergency(ADSBFrame& frame, const uint32_t ICAO_address, const uint8_t code); +void generate_frame_identity(ADSBFrame& frame, const uint32_t ICAO_address, const uint32_t code); + +} /* namespace adsb */ + +#endif/*__ADSB_H__*/ diff --git a/firmware/common/adsb_frame.cpp b/firmware/common/adsb_frame.cpp new file mode 100644 index 000000000..471d92d76 --- /dev/null +++ b/firmware/common/adsb_frame.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2017 Furrtek + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "adsb_frame.hpp" + +namespace adsb { + +} /* namespace adsb */ diff --git a/firmware/common/adsb_frame.hpp b/firmware/common/adsb_frame.hpp new file mode 100644 index 000000000..c15204a14 --- /dev/null +++ b/firmware/common/adsb_frame.hpp @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2017 Furrtek + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __ADSB_FRAME_H__ +#define __ADSB_FRAME_H__ + +#include +#include + +namespace adsb { + +alignas(4) const uint8_t adsb_preamble[16] = { 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0 }; +alignas(4) const char icao_id_lut[65] = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ##### ###############0123456789######"; + +class ADSBFrame { +public: + uint8_t get_DF() { + return (raw_data[0] >> 3); + } + + uint8_t get_msg_type() { + return (raw_data[4] >> 3); + } + + uint32_t get_ICAO_address() { + return (raw_data[1] << 16) + (raw_data[2] << 8) + raw_data[3]; + } + + void clear() { + index = 0; + memset(raw_data, 0, 14); + } + + void push_byte(uint8_t byte) { + if (index >= 14) + return; + + raw_data[index++] = byte; + } + + uint8_t * get_raw_data() { + return raw_data; + } + + std::string get_callsign() { + uint64_t callsign_coded = 0; + uint32_t c; + std::string callsign = ""; + + // Frame bytes to long + for (c = 5; c < 11; c++) { + callsign_coded <<= 8; + callsign_coded |= raw_data[c]; + } + + // Long to 6-bit characters + for (c = 0; c < 8; c++) { + callsign.append(1, icao_id_lut[(callsign_coded >> 42) & 0x3F]); + callsign_coded <<= 6; + } + + return callsign; + } + + void make_CRC() { + uint8_t adsb_crc[14]; // Temp buffer + uint8_t b, c, s, bitn; + const uint32_t crc_poly = 0x1205FFF; + + // Clear CRC + raw_data[11] = 0x00; + raw_data[12] = 0x00; + raw_data[13] = 0x00; + + // Compute CRC + memcpy(adsb_crc, raw_data, 14); + for (c = 0; c < 11; c++) { + for (b = 0; b < 8; b++) { + if ((adsb_crc[c] << b) & 0x80) { + for (s = 0; s < 25; s++) { + bitn = (c * 8) + b + s; + if ((crc_poly >> s) & 1) adsb_crc[bitn >> 3] ^= (0x80 >> (bitn & 7)); + } + } + } + } + + // Insert CRC in frame + memcpy(&raw_data[11], &adsb_crc[11], 3); + } + +private: + static const uint8_t adsb_preamble[16]; + static const char icao_id_lut[65]; + alignas(4) uint8_t index { 0 }; + alignas(4) uint8_t raw_data[14] { }; // 112 bits at most +}; + +} /* namespace adsb */ + +#endif/*__ADSB_FRAME_H__*/ diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index b6b974c48..07c9fafdb 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -35,6 +35,7 @@ #include "ert_packet.hpp" #include "tpms_packet.hpp" #include "pocsag_packet.hpp" +#include "adsb_frame.hpp" #include "jammer.hpp" #include "dsp_fir_taps.hpp" #include "dsp_iir.hpp" @@ -91,6 +92,7 @@ public: SigGenTone = 44, POCSAGPacket = 50, + ADSBFrame = 51, RequestSignal = 52, FIFOData = 53, @@ -317,6 +319,18 @@ public: pocsag::POCSAGPacket packet; }; +class ADSBFrameMessage : public Message { +public: + constexpr ADSBFrameMessage( + const adsb::ADSBFrame& frame + ) : Message { ID::ADSBFrame }, + frame { frame } + { + } + + adsb::ADSBFrame frame; +}; + class ShutdownMessage : public Message { public: constexpr ShutdownMessage( diff --git a/firmware/common/pocsag_packet.hpp b/firmware/common/pocsag_packet.hpp index d0f9b0b68..bb19e8b71 100644 --- a/firmware/common/pocsag_packet.hpp +++ b/firmware/common/pocsag_packet.hpp @@ -26,8 +26,6 @@ #include #include -#include "optional.hpp" - #include "baseband.hpp" namespace pocsag { diff --git a/firmware/common/spi_image.hpp b/firmware/common/spi_image.hpp index 64380a8b3..b955f1ee2 100644 --- a/firmware/common/spi_image.hpp +++ b/firmware/common/spi_image.hpp @@ -63,6 +63,7 @@ private: char c[4]; }; +constexpr image_tag_t image_tag_adsb_rx { 'P', 'A', 'D', 'R' }; constexpr image_tag_t image_tag_ais { 'P', 'A', 'I', 'S' }; constexpr image_tag_t image_tag_am_audio { 'P', 'A', 'M', 'A' }; constexpr image_tag_t image_tag_capture { 'P', 'C', 'A', 'P' }; @@ -79,7 +80,7 @@ constexpr image_tag_t image_tag_afsk { 'P', 'A', 'F', 'S' }; constexpr image_tag_t image_tag_tones { 'P', 'T', 'O', 'N' }; constexpr image_tag_t image_tag_rds { 'P', 'R', 'D', 'S' }; constexpr image_tag_t image_tag_ook { 'P', 'O', 'O', 'K' }; -constexpr image_tag_t image_tag_adsb_tx { 'P', 'A', 'D', 'S' }; +constexpr image_tag_t image_tag_adsb_tx { 'P', 'A', 'D', 'T' }; constexpr image_tag_t image_tag_replay { 'P', 'R', 'E', 'P' }; constexpr image_tag_t image_tag_fsktx { 'P', 'F', 'S', 'K' }; constexpr image_tag_t image_tag_mic_tx { 'P', 'M', 'T', 'X' };