diff --git a/firmware/application/main.cpp b/firmware/application/main.cpp index 4f23d1899..3ef5d8ed1 100755 --- a/firmware/application/main.cpp +++ b/firmware/application/main.cpp @@ -23,6 +23,7 @@ // Color bitmaps generated with: // Gimp image > indexed colors (16), then "xxd -i *.bmp" +//TODO: Waveform widget as FFT view in scanner //BUG: Replay freezes when SD card not present //BUG: RDS doesn't stop baseband when stopping tx ? //BUG: Check AFSK transmit end, skips last bits ? diff --git a/firmware/application/protocols/adsb.cpp b/firmware/application/protocols/adsb.cpp index 1b7012686..bd233c5d8 100644 --- a/firmware/application/protocols/adsb.cpp +++ b/firmware/application/protocols/adsb.cpp @@ -76,6 +76,7 @@ void generate_frame_emergency(uint8_t * const adsb_frame, const uint32_t ICAO_ad ADSB_generate_CRC(adsb_frame); } +/* 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; @@ -103,9 +104,9 @@ void generate_frame_pos(uint8_t * const adsb_frame, const uint32_t ICAO_address, //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) { + if (time_parity) { A = sign(rlat0)[NL(rlat0) - NL(rlat1)]; - }*/ + } // int xz and yz, then: // xz >>= 2; // yz >>= 2; @@ -115,13 +116,15 @@ void generate_frame_pos(uint8_t * const adsb_frame, const uint32_t ICAO_address, 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 } +*/ void ADSB_generate_CRC(uint8_t * const in_frame) { uint8_t adsb_crc[14]; // Temp buffer uint8_t b, c, s, bitn; const uint32_t crc_poly = 0x1205FFF; - in_frame[11] = 0x00; // Clear CRC + // Clear CRC + in_frame[11] = 0x00; in_frame[12] = 0x00; in_frame[13] = 0x00; @@ -140,7 +143,6 @@ void ADSB_generate_CRC(uint8_t * const in_frame) { // Insert CRC in frame memcpy(&in_frame[11], &adsb_crc[11], 3); - } } /* namespace adsb */ diff --git a/firmware/application/protocols/adsb.hpp b/firmware/application/protocols/adsb.hpp index 41c7121c2..bcd989548 100644 --- a/firmware/application/protocols/adsb.hpp +++ b/firmware/application/protocols/adsb.hpp @@ -33,8 +33,8 @@ namespace adsb { void make_frame_mode_s(uint8_t * const adsb_frame, const uint32_t ICAO_address); void generate_frame_id(uint8_t * const 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_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(uint8_t * const adsb_frame, const uint32_t ICAO_address, const uint8_t code); void ADSB_generate_CRC(uint8_t * const in_message); diff --git a/firmware/application/string_format.cpp b/firmware/application/string_format.cpp index 4dd6af51f..86716331c 100644 --- a/firmware/application/string_format.cpp +++ b/firmware/application/string_format.cpp @@ -127,13 +127,25 @@ static void to_string_hex_internal(char* p, const uint64_t n, const int32_t l) { } } -std::string to_string_hex(const uint64_t n, const int32_t l) { +std::string to_string_hex(const uint64_t n, int32_t l) { char p[32]; + + l = std::min(l, 31L); to_string_hex_internal(p, n, l - 1); p[l] = 0; return p; } +std::string to_string_hex_array(uint8_t * const array, const int32_t l) { + std::string str_return = ""; + uint8_t bytes; + + for (bytes = 0; bytes < l; bytes++) + str_return += to_string_hex(array[bytes], 2); + + return str_return; +} + std::string to_string_datetime(const rtc::RTC& value) { return to_string_dec_uint(value.year(), 4, '0') + "/" + to_string_dec_uint(value.month(), 2, '0') + "/" + diff --git a/firmware/application/string_format.hpp b/firmware/application/string_format.hpp index 82db3f421..206d1ad6d 100644 --- a/firmware/application/string_format.hpp +++ b/firmware/application/string_format.hpp @@ -34,6 +34,7 @@ std::string to_string_bin(const uint32_t n, const uint8_t l = 0); std::string to_string_dec_uint(const uint32_t n, const int32_t l = 0, const char fill = 0); std::string to_string_dec_int(const int32_t n, const int32_t l = 0, const char fill = 0); std::string to_string_hex(const uint64_t n, const int32_t l = 0); +std::string to_string_hex_array(uint8_t * const array, const int32_t l = 0); std::string to_string_short_freq(const uint64_t f, const int32_t l = 4); diff --git a/firmware/application/ui_adsbtx.cpp b/firmware/application/ui_adsbtx.cpp index 54ce3092f..047ca25e7 100644 --- a/firmware/application/ui_adsbtx.cpp +++ b/firmware/application/ui_adsbtx.cpp @@ -38,7 +38,8 @@ using namespace portapack; namespace ui { void ADSBTxView::focus() { - sym_icao.focus(); + //sym_icao.focus(); + tx_view.focus(); } ADSBTxView::~ADSBTxView() { @@ -59,21 +60,15 @@ void ADSBTxView::generate_frame() { memset(adsb_bin, 0, 112); - // Convert to binary (1 bit per byte, faster for baseband code) + // Convert to binary (1 byte per bit, faster for baseband code) for (c = 0; c < 112; c++) { if ((adsb_frame[c >> 3] << (c & 7)) & 0x80) - adsb_bin[c] = 0xFF; + adsb_bin[c] = 1; } - // Display for debug - str_debug = ""; - for (c = 0; c < 7; c++) - str_debug += to_string_hex(adsb_frame[c], 2); - text_frame_a.set(str_debug); - str_debug = ""; - for (c = 0; c < 7; c++) - str_debug += to_string_hex(adsb_frame[c + 7], 2); - text_frame_b.set(str_debug); + // Display in hex for debug + text_frame_a.set(to_string_hex_array(&adsb_frame[0], 7)); + text_frame_b.set(to_string_hex_array(&adsb_frame[7], 7)); button_callsign.set_text(callsign); } @@ -81,25 +76,25 @@ void ADSBTxView::generate_frame() { bool ADSBTxView::start_tx() { generate_frame(); - transmitter_model.set_tuning_frequency(434000000); // FOR TESTING - DEBUG - transmitter_model.set_sampling_rate(2000000U); - transmitter_model.set_rf_amp(true); - transmitter_model.set_lna(40); - transmitter_model.set_vga(40); - transmitter_model.set_baseband_bandwidth(1750000); - transmitter_model.enable(); - memcpy(shared_memory.bb_data.data, adsb_bin, 112); baseband::set_adsb(); + transmitter_model.set_tuning_frequency(434000000); // FOR TESTING - DEBUG + 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.enable(); + return false; // DEBUG } -void ADSBTxView::on_txdone(const int n) { +void ADSBTxView::on_txdone(const bool v) { //size_t sr; - if (n == 200) { + if (v) { transmitter_model.disable(); + tx_view.set_transmitting(false); //progress.set_value(0); } else { //progress.set_value(n); diff --git a/firmware/application/ui_adsbtx.hpp b/firmware/application/ui_adsbtx.hpp index 71d1ec0b3..2dc03d605 100644 --- a/firmware/application/ui_adsbtx.hpp +++ b/firmware/application/ui_adsbtx.hpp @@ -61,7 +61,7 @@ private: bool start_tx(); void generate_frame(); void generate_frame_pos(); - void on_txdone(const int n); + void on_txdone(const bool v); const Style style_val { .font = font::fixed_8x16, @@ -166,7 +166,7 @@ private: Message::ID::TXDone, [this](const Message* const p) { const auto message = *reinterpret_cast(p); - this->on_txdone(message.progress); + this->on_txdone(message.done); } }; }; diff --git a/firmware/application/ui_numbers.cpp b/firmware/application/ui_numbers.cpp index 54fdd15ca..616f9907f 100644 --- a/firmware/application/ui_numbers.cpp +++ b/firmware/application/ui_numbers.cpp @@ -34,6 +34,8 @@ using namespace portapack; namespace ui { +// TODO: This app takes way too much space, find a way to shrink/simplify or make it an SD card module (loadable) + void NumbersStationView::focus() { if (file_error) nav_.display_modal("No voices", "No valid voices found in\nthe /numbers directory.", ABORT, nullptr); @@ -46,29 +48,34 @@ NumbersStationView::~NumbersStationView() { baseband::shutdown(); } -void NumbersStationView::on_tuning_frequency_changed(rf::Frequency f) { - transmitter_model.set_tuning_frequency(f); +NumbersStationView::wav_file_t * NumbersStationView::get_wav(uint32_t index) { + return ¤t_voice->available_wavs[index]; } void NumbersStationView::prepare_audio() { uint8_t code; + wav_file_t * wav_file; - /*if (sample_counter >= sample_duration) { + if (sample_counter >= sample_length) { if (segment == ANNOUNCE) { if (!announce_loop) { + code_index = 0; segment = MESSAGE; } else { - reader->open("/numbers/announce.wav"); - sample_duration = sound_sizes[10]; + wav_file = get_wav(11); + reader->open(current_voice->dir + file_names[wav_file->index].name + ".wav"); + sample_length = wav_file->length; announce_loop--; } } if (segment == MESSAGE) { - code = symfield_code.get_sym(code_index); - - if (code_index == 25) + if (code_index == 25) { transmitter_model.disable(); + return; + } + + code = symfield_code.get_sym(code_index); if (code >= 10) { memset(audio_buffer, 0, 1024); @@ -78,10 +85,12 @@ void NumbersStationView::prepare_audio() { pause = 33075; // P: 0.75s @ 44100Hz } else if (code == 12) { transmitter_model.disable(); + return; } } else { - reader->open("/numbers/" + file_names[code] + ".wav"); - sample_duration = sound_sizes[code]; + wav_file = get_wav(code); + reader->open(current_voice->dir + file_names[code].name + ".wav"); + sample_length = wav_file->length; } code_index++; } @@ -102,17 +111,17 @@ void NumbersStationView::prepare_audio() { if (pause >= 1024) { pause -= 1024; } else { - sample_counter = sample_duration; + sample_counter = sample_length; pause = 0; } } - baseband::set_fifo_data(audio_buffer);*/ + baseband::set_fifo_data(audio_buffer); } void NumbersStationView::start_tx() { - //sample_duration = sound_sizes[10]; // Announce - sample_counter = sample_duration; + //sample_length = sound_sizes[10]; // Announce + sample_counter = sample_length; code_index = 0; announce_loop = 2; @@ -126,7 +135,7 @@ void NumbersStationView::start_tx() { transmitter_model.enable(); baseband::set_audiotx_data( - (1536000 / 44100) - 1, + (1536000 / 44100) - 1, // TODO: Read wav file's samplerate 12000, 1, false, @@ -135,28 +144,37 @@ void NumbersStationView::start_tx() { } void NumbersStationView::on_tick_second() { - if (check_armed.value()) { - armed_blink = not armed_blink; - - if (armed_blink) - check_armed.set_style(&style_red); - else - check_armed.set_style(&style()); - - check_armed.set_dirty(); - } + armed_blink = not armed_blink; + + if (armed_blink) + check_armed.set_style(&style_red); + else + check_armed.set_style(&style()); + + check_armed.set_dirty(); } void NumbersStationView::on_voice_changed(size_t index) { - std::string flags_string = ""; + std::string code_list = ""; - if (voices[index].accent) { - flags_string = "^"; - } - if (voices[index].announce) { - flags_string += "A"; - } - text_voice_flags.set(flags_string); + for (const auto& wavs : voices[index].available_wavs) + code_list += wavs.code; + + for (uint32_t c = 0; c < 25; c++) + symfield_code.set_symbol_list(c, code_list); + + current_voice = &voices[index]; +} + +bool NumbersStationView::check_wav_validity(const std::string dir, const std::string file) { + if (reader->open("/numbers/" + dir + "/" + file)) { + // Check format (mono, 8 bits) + if ((reader->channels() == 1) && (reader->bits_per_sample() == 8)) + return true; + else + return false; + } else + return false; } NumbersStationView::NumbersStationView( @@ -167,8 +185,9 @@ NumbersStationView::NumbersStationView( using option_t = std::pair; using options_t = std::vector; options_t voice_options; - uint32_t c, i, ia; + voice_t temp_voice { }; bool valid; + uint32_t c; //uint8_t y, m, d, dayofweek; reader = std::make_unique(); @@ -179,42 +198,39 @@ NumbersStationView::NumbersStationView( file_error = true; return; } - // This is awfully repetitive + for (const auto& dir : directory_list) { - i = 0; + c = 0; for (const auto& file_name : file_names) { - valid = false; - if (reader->open("/numbers/" + dir.string() + "/" + file_name + ".wav")) { - // Check format (mono, 8 bits) - if ((reader->channels() == 1) && (reader->bits_per_sample() == 8)) - valid = true; + valid = check_wav_validity(dir.string(), file_name.name + ".wav"); + if ((!valid) && (file_name.required)) { + temp_voice.available_wavs.clear(); + break; // Invalid required file means invalid voice + } else if (valid) { + temp_voice.available_wavs.push_back({ file_name.code, c++, 0, 0 }); // TODO: Needs length and samplerate } - if (!valid) { - if (i < 10) - i = 0; // Missingno, invalid voice - break; - } - i++; } - if (i) { - // Voice ok, are there accent files ? - ia = 0; + if (!temp_voice.available_wavs.empty()) { + // Voice can be used, are there accent files ? + c = 0; for (const auto& file_name : file_names) { - valid = false; - if (reader->open("/numbers/" + dir.string() + "/" + file_name + "a.wav")) { - // Check format (mono, 8 bits) - if ((reader->channels() == 1) && (reader->bits_per_sample() == 8)) - valid = true; + valid = check_wav_validity(dir.string(), file_name.name + "a.wav"); + if ((!valid) && (file_name.required)) { + c = 0; + break; // Invalid required file means accents can't be used + } else if (valid) { + c++; } - if (!valid) - break; - ia++; } - voices.push_back({ dir.string(), (ia >= 10), (i == 11) }); + temp_voice.accent = c ? true : false; + temp_voice.dir = dir.string(); + + voices.push_back(temp_voice); } } - if (!voices.size()) { + + if (voices.empty()) { file_error = true; return; } @@ -254,9 +270,6 @@ NumbersStationView::NumbersStationView( } }; - for (c = 0; c < 25; c++) - symfield_code.set_symbol_list(c, "0123456789pPE"); - // DEBUG symfield_code.set_sym(0, 10); symfield_code.set_sym(1, 3); diff --git a/firmware/application/ui_numbers.hpp b/firmware/application/ui_numbers.hpp index 306e2fc8d..de9e04c28 100644 --- a/firmware/application/ui_numbers.hpp +++ b/firmware/application/ui_numbers.hpp @@ -42,6 +42,11 @@ class NumbersStationView : public View { public: NumbersStationView(NavigationView& nav); ~NumbersStationView(); + + NumbersStationView(const NumbersStationView&) = delete; + NumbersStationView(NumbersStationView&&) = delete; + NumbersStationView& operator=(const NumbersStationView&) = delete; + NumbersStationView& operator=(NumbersStationView&&) = delete; void focus() override; @@ -64,60 +69,66 @@ private: .foreground = Color::red() }; + typedef struct { + char code; + uint32_t index; + uint32_t length; + uint32_t samplerate; + } wav_file_t; + struct voice_t { std::string dir; + std::vector available_wavs; bool accent; - bool announce; + }; + + std::vector voices { }; + voice_t * current_voice { }; + + struct wav_file_list_t { + std::string name; + bool required; + char code; + }; + + const std::vector file_names = { + { "0", true, '0' }, + { "1", true, '1' }, + { "2", true, '2' }, + { "3", true, '3' }, + { "4", true, '4' }, + { "5", true, '5' }, + { "6", true, '6' }, + { "7", true, '7' }, + { "8", true, '8' }, + { "9", true, '9' }, + { "announce", false, 'A' } }; segments segment { IDLE }; bool armed { false }; bool file_error { false }; - std::vector voices; - - const std::vector file_names = { - { "0" }, - { "1" }, - { "2" }, - { "3" }, - { "4" }, - { "5" }, - { "6" }, - { "7" }, - { "8" }, - { "9" }, - { "announce" } - }; // const uint8_t month_table[12] = { 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 }; // const char * day_of_week[7] = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }; std::unique_ptr reader { }; - uint8_t code_index, announce_loop; - uint32_t sample_counter; - uint32_t sample_duration; - int8_t audio_buffer[1024]; - uint32_t pause = 0; + uint8_t code_index { 0 }, announce_loop { 0 }; + uint32_t sample_counter { 0 }; + uint32_t sample_length { 0 }; + int8_t audio_buffer[1024] { }; + uint32_t pause { 0 }; bool armed_blink { false }; SignalToken signal_token_tick_second { }; + wav_file_t * get_wav(uint32_t index); + bool check_wav_validity(const std::string dir, const std::string file); void on_voice_changed(size_t index); - void on_tick_second(); - void on_tuning_frequency_changed(rf::Frequency f); + void on_tick_second(); void prepare_audio(); void start_tx(); - // Schedule: save on sd card - // For each day of the week, max 8 messages ? - // For each message: Normal, accent. Can chose accent on first or last digit - // Prelude with number of repeats - // Message 1 with number of repeats - // Interlude ? - // Message 2 with number of repeats - // End - // Frequency list and sequence - Labels labels { { { 2 * 8, 5 * 8 }, "Voice: Flags:", Color::light_grey() }, { { 1 * 8, 8 * 8 }, "Code:", Color::light_grey() } diff --git a/firmware/baseband/proc_adsbtx.cpp b/firmware/baseband/proc_adsbtx.cpp index f43afcd1e..aca34c568 100644 --- a/firmware/baseband/proc_adsbtx.cpp +++ b/firmware/baseband/proc_adsbtx.cpp @@ -29,57 +29,67 @@ void ADSBTXProcessor::execute(const buffer_c8_t& buffer) { - // This is called at 2M/2048 = 977Hz + // This is called at 4M/2048 = 1953Hz + // One pulse = 500ns = 2 samples + // One bit = 2 pulses = 1us = 4 samples if (!configured) return; for (size_t i = 0; i < buffer.count; i++) { - // Synthesis at 2M - /*if (preamble == true) { - if (bit_pos >= 16) { - bit_pos = 0; - preamble = false; - bit_part = 0; - } else { - cur_bit = (preamble_parts << bit_pos) >> 15; - bit_pos++; - } - }*/ - //if (preamble == false) { - if (!bit_part) { - if (bit_pos >= 112) { - // Stop - message.progress = 200; - shared_memory.application_queue.push(message); - configured = false; - cur_bit = 0; - } else { - cur_bit = 0; //shared_memory.tx_data[bit_pos]; - bit_pos++; - bit_part = 1; + if (!sample) { + sample = 3; + + if (active) { + if (preamble) { + if (bit_pos >= 16) { + preamble = false; + bit_pos = 0; + } else { + cur_bit = (preamble_parts << bit_pos) >> 15; + bit_pos++; + } + } + + if (!preamble) { + if (bit_pos >= 112) { + active = false; // Stop + cur_bit = 0; + } else { + cur_bit = shared_memory.bb_data.data[bit_pos]; + bit_pos++; + } } } else { - bit_part = 0; - cur_bit ^= 1; + //cur_bit = 0; + if (bit_pos == 8192) { // ? + configured = false; + message.done = true; + shared_memory.application_queue.push(message); + } + bit_pos++; } - //} + } else + sample--; - // 8D48: 10001101 01001000 - // 1001010110100110 0110010110010101 + if (sample == 1) + cur_bit ^= 1; // Invert + + delta = tone_sample * fm_delta; + tone_sample += 128; if (cur_bit) { - phase = (phase + 0x1FE00); // What ? - sphase = phase + (64 << 18); + phase += delta; + sphase = phase + (64 << 24); - re = (sine_table_i8[(sphase & 0x03FC0000) >> 18]); - im = (sine_table_i8[(phase & 0x03FC0000) >> 18]); + re = (sine_table_i8[(sphase & 0xFF000000U) >> 24]); + im = (sine_table_i8[(phase & 0xFF000000U) >> 24]); } else { re = 0; im = 0; } - - buffer.p[i] = {(int8_t)re, (int8_t)im}; + + buffer.p[i] = {re, im}; } } @@ -87,11 +97,14 @@ void ADSBTXProcessor::on_message(const Message* const p) { const auto message = *reinterpret_cast(p); if (message.id == Message::ID::ADSBConfigure) { - bit_part = 0; bit_pos = 0; + sample = 0; cur_bit = 0; preamble = true; + active = true; configured = true; + + fm_delta = 50000 * (0xFFFFFFULL / 4000000); // Fixed bw for now } } diff --git a/firmware/baseband/proc_adsbtx.hpp b/firmware/baseband/proc_adsbtx.hpp index eb0777ff3..1b68afa29 100644 --- a/firmware/baseband/proc_adsbtx.hpp +++ b/firmware/baseband/proc_adsbtx.hpp @@ -26,6 +26,8 @@ #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; @@ -35,18 +37,20 @@ public: private: bool configured = false; - BasebandThread baseband_thread { 2000000, this, NORMALPRIO + 20, baseband::Direction::Transmit }; // 2280000 + BasebandThread baseband_thread { 4000000, this, NORMALPRIO + 20, baseband::Direction::Transmit }; const uint16_t preamble_parts = 0b1010000101000000; - uint8_t bit_part; - bool preamble; - int8_t re, im; - uint16_t bit_pos = 0; - uint8_t cur_bit = 0; - uint32_t phase, sphase; - int32_t sig, frq; + 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 { }; - TXDoneMessage message; + TXDoneMessage message { }; }; #endif diff --git a/firmware/portapack-h1-havoc.bin b/firmware/portapack-h1-havoc.bin index e934cc183..62261212c 100644 Binary files a/firmware/portapack-h1-havoc.bin and b/firmware/portapack-h1-havoc.bin differ