diff --git a/firmware/application/receiver_model.cpp b/firmware/application/receiver_model.cpp index cf8607b83..e6195dbaf 100644 --- a/firmware/application/receiver_model.cpp +++ b/firmware/application/receiver_model.cpp @@ -162,7 +162,7 @@ void ReceiverModel::disable() { } int32_t ReceiverModel::tuning_offset() { - if( (baseband_configuration.mode == 4) ) { + if( (baseband_configuration.mode == 4) || (baseband_configuration.mode == 10) ) { return 0; } else { return -(sampling_rate() / 4); diff --git a/firmware/application/ui_afsksetup.hpp b/firmware/application/ui_afsksetup.hpp index 6b4d7e717..ed8a3ab5d 100644 --- a/firmware/application/ui_afsksetup.hpp +++ b/firmware/application/ui_afsksetup.hpp @@ -64,7 +64,7 @@ private: NumberField field_mark { { 64, 104 }, 5, - { 100, 32000 }, + { 100, 15000 }, 25, ' ' }; @@ -76,7 +76,7 @@ private: NumberField field_space { { 64, 120 }, 5, - { 100, 32000 }, + { 100, 15000 }, 25, ' ' }; diff --git a/firmware/application/ui_closecall.cpp b/firmware/application/ui_closecall.cpp index 8343e541a..93ab314a1 100644 --- a/firmware/application/ui_closecall.cpp +++ b/firmware/application/ui_closecall.cpp @@ -56,48 +56,65 @@ void CloseCallView::do_detection() { uint8_t xmax = 0; uint16_t imax = 0; uint8_t threshold; + rf::Frequency resolved_frequency; size_t i; - portapack::display.fill_rectangle({last_pos, 60, 1, 9}, Color::black()); + portapack::display.fill_rectangle({last_pos, 92, 1, 9}, Color::black()); - mean /= (236 * (slices_max + 1)); + mean /= (CC_BIN_NB * (slices_max + 1)); for (i = 0; i < (slices_max + 1); i++) { threshold = slicemax_db[i]; - if (threshold >= 40) { - if (((uint8_t)(threshold - 40) > mean) && (threshold > xmax)) { + if (threshold >= min_threshold) { + if (((uint8_t)(threshold - min_threshold) > mean) && (threshold > xmax)) { xmax = threshold; - imax = slicemax_idx[i] + (i * 236); + imax = slicemax_idx[i] + (i * CC_BIN_NB); } } } + // e-Message POCSAG FR: + // 466.025 MHz - 466.05 MHz - 466.075 MHz - 466.175 MHz - 466.20625 MHz - 466.23125 MHz + // 25 25 100 31 25 + + // Catches: + // 466.000 + // 466.021 21 + // 466.042 21 + // 466.148 106 + // 466.169 21 + // 466.190 21 + // Lock / release - if ((imax >= last_channel - 2) && (imax <= last_channel + 2)) { + if ((imax >= last_channel - 2) && (imax <= last_channel + 2) && imax) { if (detect_counter == 8) { - if (imax != last_channel) { - char finalstr[24] = {0}; + if (imax != locked_frequency) { + //char finalstr[24] = {0}; - // 236 steps = 10MHz - // Resolution = 42.4kHz - auto fstr = to_string_dec_int((slice_start + (42373 * (imax - 118))) / 1000); + // 236 steps = 5MHz + // Resolution = 21.2kHz + resolved_frequency = slice_start + (CC_BIN_WIDTH * (imax - 118)); + //auto fstr = to_string_dec_int(resolved_frequency); - strcat(finalstr, "Locked: "); - strcat(finalstr, fstr.c_str()); - text_infos.set(finalstr); + //strcat(finalstr, "Locked: "); + //strcat(finalstr, fstr.c_str()); + text_infos.set("Locked ! "); + big_display.set(resolved_frequency); + + locked = true; + locked_frequency = imax; } - - locked = true; release_counter = 0; } else { detect_counter++; } } else { + detect_counter = 0; if (locked == true) { if (release_counter == 8) { - detect_counter = 0; locked = false; - text_infos.set("Lost "); + text_infos.set("Lost "); + //big_display.set(0); } else { release_counter++; } @@ -105,16 +122,17 @@ void CloseCallView::do_detection() { } last_channel = imax; + scan_counter++; last_pos = (ui::Coord)(imax + 2); - portapack::display.fill_rectangle({last_pos, 60, 1, 9}, Color::red()); + portapack::display.fill_rectangle({last_pos, 92, 1, 9}, Color::red()); } void CloseCallView::on_channel_spectrum(const ChannelSpectrum& spectrum) { uint8_t xmax = 0; uint16_t imax = 0; uint8_t threshold; - size_t i; + size_t i, m; if (!wait) { // Spectrum line (for debug) @@ -130,19 +148,29 @@ void CloseCallView::on_channel_spectrum(const ChannelSpectrum& spectrum) { } display.draw_pixels( - { { 0, 64 + slices_counter * 4 }, { pixel_row.size(), 1 } }, + { { 0, 96 + slices_counter * 4 }, { pixel_row.size(), 1 } }, pixel_row ); // Find max for this slice - for (i = 0; i < 118; i++) { + // Check if left of slice needs to be trimmed (masked) + if (slices_counter == 0) + i = slice_trim; + else + i = 0; + for ( ; i < 118; i++) { threshold = spectrum.db[256 - 120 + i]; if (threshold > xmax) { xmax = threshold; imax = i; } } - for (i = 122; i < 240; i++) { + // Check if right of slice needs to be trimmed (masked) + if (slices_counter == slices_max) + m = 240 - slice_trim; + else + m = 240; + for (i = 122 ; i < m; i++) { threshold = spectrum.db[i - 120]; if (threshold > xmax) { xmax = threshold; @@ -209,6 +237,7 @@ void CloseCallView::on_hide() { void CloseCallView::on_range_changed() { rf::Frequency f_min, f_max; rf::Frequency slices_span; + rf::Frequency resolved_frequency; int64_t offset; f_max = field_frequency_max.value(); @@ -217,43 +246,90 @@ void CloseCallView::on_range_changed() { if (scan_span > CC_SLICE_WIDTH) { // ex: 100~115 (15): 102.5(97.5~107.5) -> 112.5(107.5~117.5) = 2.5 lost left and right slices_max = (scan_span + CC_SLICE_WIDTH - 1) / CC_SLICE_WIDTH; - // = 2 slices_span = slices_max * CC_SLICE_WIDTH; - // = 20000000 offset = (slices_span - scan_span) / 2; - // = (20000000 - 15000000) / 2 = 5000000 / 2 = 2500000 slice_start = std::min(f_min, f_max) + offset; - slices_max--; + slices_max--; // For slices_counter slicing = true; + + // Todo: trims } else { slice_frequency = (f_max + f_min) / 2; - //offset = (CC_SLICE_WIDTH - scan_span) / 2; - //slice_start = std::min(f_min, f_max) - offset; slice_start = slice_frequency; receiver_model.set_tuning_frequency(slice_frequency); + + // DEBUG + resolved_frequency = (CC_SLICE_WIDTH - scan_span) / 2; // Trim frequency span (for both sides) + resolved_frequency /= CC_BIN_WIDTH; // Convert to bin span + slice_trim = resolved_frequency; + + portapack::display.fill_rectangle({0, 97, slice_trim, 4}, Color::orange()); + portapack::display.fill_rectangle({240 - slice_trim, 97, slice_trim, 4}, Color::orange()); + slices_max = 0; slices_counter = 0; slicing = false; } - text_slice.set(to_string_dec_int(slices_max + 1)); + text_debug.set(to_string_dec_uint(slice_start)); + + text_slices.set(to_string_dec_int(slices_max + 1)); slices_counter = 0; } +void CloseCallView::on_lna_changed(int32_t v_db) { + receiver_model.set_lna(v_db); +} + +void CloseCallView::on_vga_changed(int32_t v_db) { + receiver_model.set_vga(v_db); +} + +void CloseCallView::update_rate() { + text_rate.set(to_string_dec_uint(scan_counter, 3)); + scan_counter = 0; +} + CloseCallView::CloseCallView( NavigationView& nav ) { add_children({ { + &text_labels_a, + &text_labels_b, + &text_labels_c, &field_frequency_min, &field_frequency_max, - &text_slice, + &field_lna, + &field_vga, + &field_threshold, + &text_slices, + &text_rate, &text_infos, + &text_debug, + &big_display, &button_exit } }); + + static constexpr Style style_grey { + .font = font::fixed_8x16, + .background = Color::black(), + .foreground = Color::grey(), + }; - field_frequency_min.set_value(receiver_model.tuning_frequency() - 2000000); + text_labels_a.set_style(&style_grey); + text_labels_b.set_style(&style_grey); + text_labels_c.set_style(&style_grey); + text_slices.set_style(&style_grey); + text_rate.set_style(&style_grey); + + field_threshold.set_value(80); + field_threshold.on_change = [this](int32_t v) { + min_threshold = v; + }; + + field_frequency_min.set_value(receiver_model.tuning_frequency()); field_frequency_min.set_step(200000); field_frequency_min.on_change = [this](rf::Frequency f) { (void) f; @@ -267,7 +343,7 @@ CloseCallView::CloseCallView( }; }; - field_frequency_max.set_value(receiver_model.tuning_frequency() + 2000000); + field_frequency_max.set_value(receiver_model.tuning_frequency() + 5000000); field_frequency_max.set_step(200000); field_frequency_max.on_change = [this](rf::Frequency f) { (void) f; @@ -281,32 +357,21 @@ CloseCallView::CloseCallView( }; }; - on_range_changed(); - - button_exit.on_select = [&nav](Button&){ - nav.pop(); - }; - - /*field_lna.set_value(receiver_model.lna()); + field_lna.set_value(receiver_model.lna()); field_lna.on_change = [this](int32_t v) { this->on_lna_changed(v); }; - field_lna.on_show_options = [this]() { - this->on_show_options_rf_gain(); - }; - field_vga.set_value(receiver_model.vga()); field_vga.on_change = [this](int32_t v_db) { this->on_vga_changed(v_db); }; - field_vga.on_show_options = [this]() { - this->on_show_options_rf_gain(); - };*/ - //receiver_model.set_lna(0); - //receiver_model.set_vga(0); + on_range_changed(); + button_exit.on_select = [&nav](Button&){ + nav.pop(); + }; //audio::output::mute(); receiver_model.set_baseband_configuration({ diff --git a/firmware/application/ui_closecall.hpp b/firmware/application/ui_closecall.hpp index ad90f578f..42e2ef4a8 100644 --- a/firmware/application/ui_closecall.hpp +++ b/firmware/application/ui_closecall.hpp @@ -43,7 +43,9 @@ namespace ui { -#define CC_SLICE_WIDTH 10000000 +#define CC_SLICE_WIDTH 5000000 //10000000 +#define CC_BIN_NB 236 +#define CC_BIN_WIDTH CC_SLICE_WIDTH/CC_BIN_NB class CloseCallView : public View { public: @@ -53,43 +55,99 @@ public: void on_show() override; void on_hide() override; void focus() override; - std::string title() const override { return "Close call"; }; + std::string title() const override { return "Close Call"; }; private: Coord last_pos = 0; ChannelSpectrumFIFO* fifo { nullptr }; uint8_t detect_counter = 0, release_counter = 0; uint8_t wait = 2; + uint8_t slice_trim; uint32_t mean = 0; + uint32_t min_threshold = 80; // Todo: Put this in persistent / settings rf::Frequency slice_start; rf::Frequency slice_frequency; uint8_t slices_max; uint8_t slices_counter; uint16_t last_channel; - rf::Frequency scan_span; - uint8_t slicemax_db[32]; // Todo: Cap max slices ! + rf::Frequency scan_span, locked_frequency; + uint8_t slicemax_db[32]; // Todo: Cap max slices ! uint8_t slicemax_idx[32]; + uint8_t scan_counter; bool ignore = true; bool slicing; bool locked = false; void on_channel_spectrum(const ChannelSpectrum& spectrum); void on_range_changed(); void do_detection(); + void on_lna_changed(int32_t v_db); + void on_vga_changed(int32_t v_db); + void update_rate(); + /* |012345678901234567890123456789| + * | Min: Max: LNA VGA | + * | 0000.0000 0000.0000 00 00 | + * | Threshold: 000 | + * | Slices: 00 Rate: 000Hz | + * | + * */ + + Text text_labels_a { + { 1 * 8, 0 * 16, 28 * 8, 16 }, + "Min: Max: LNA VGA" + }; + Text text_labels_b { + { 1 * 8, 2 * 16, 10 * 8, 16 }, + "Threshold:" + }; + Text text_labels_c { + { 1 * 8, 3 * 16, 28 * 8, 16 }, + "Slices: Rate: Hz" + }; + + NumberField field_threshold { + { 12 * 8, 2 * 16 }, + 3, + { 5, 250 }, + 5, + ' ' + }; + FrequencyField field_frequency_min { - { 1 * 8, 0 * 16 }, + { 1 * 8, 1 * 16 }, }; FrequencyField field_frequency_max { - { 12 * 8, 0 * 16 }, + { 11 * 8, 1 * 16 }, }; - Text text_slice { - { 23 * 8, 0 * 16, 2 * 8, 16 }, + LNAGainField field_lna { + { 22 * 8, 1 * 16 } + }; + VGAGainField field_vga { + { 26 * 8, 1 * 16 } + }; + + Text text_slices { + { 9 * 8, 3 * 16, 2 * 8, 16 }, "--" }; + Text text_rate { + { 24 * 8, 3 * 16, 3 * 8, 16 }, + "---" + }; Text text_infos { - { 1 * 8, 1 * 16, 18 * 8, 16 }, + { 1 * 8, 6 * 16, 28 * 8, 16 }, + "..." + }; + + BigFrequency big_display { + { 4, 8 * 16, 28 * 8, 32 }, + 0 + }; + + Text text_debug { + { 1 * 8, 13 * 16, 28 * 8, 16 }, "..." }; diff --git a/firmware/common/ui_widget.cpp b/firmware/common/ui_widget.cpp index f9426141d..77e368875 100644 --- a/firmware/common/ui_widget.cpp +++ b/firmware/common/ui_widget.cpp @@ -292,6 +292,91 @@ void Text::paint(Painter& painter) { ); } +/* BigFrequency **********************************************************/ + +const uint8_t big_segment_font[11] = { + 0b00111111, // 0: ABCDEF + 0b00000110, // 1: AB + 0b01011011, // 2: ABDEG + 0b01001111, // 3: ABCDG + 0b01100110, // 4: BCFG + 0b01101101, // 5: ACDFG + 0b01111101, // 6: ACDEFG + 0b00000111, // 7: ABC + 0b01111111, // 8: ABCDEFG + 0b01101111, // 9: ABCDFG + 0b01000000 // -: G +}; + +BigFrequency::BigFrequency( + Rect parent_rect, + rf::Frequency frequency +) : Widget { parent_rect }, + _frequency { frequency } +{ +} + +void BigFrequency::set(const rf::Frequency frequency) { + _frequency = frequency; + set_dirty(); +} + +void BigFrequency::paint(Painter& painter) { + uint8_t i, digit_def; + char digits[7]; + char digit; + Coord digit_x, digit_y; + + const auto rect = screen_rect(); + + // Erase + painter.fill_rectangle({{0, rect.pos.y}, {240, 52}}, ui::Color::black()); + + if (!_frequency) { + for (i = 0; i < 7; i++) + digits[i] = 10; + } else { + _frequency /= 1000; // GMMM.KKKuuu + + for (i = 0; i < 7; i++) { + digits[6 - i] = _frequency % 10; + _frequency /= 10; + } + + // Remove leading zeros + for (i = 0; i < 7; i++) { + if (!digits[i]) + digits[i] = 16; // "Don't draw" code + else + break; + } + } + + // Draw + digit_x = rect.pos.x; // 7 * 32 + 8 = 232 (4 px margins) + for (i = 0; i < 7; i++) { + digit = digits[i]; + digit_y = rect.pos.y; + if (digit < 16) { + digit_def = big_segment_font[digit]; + if (digit_def & 0x01) painter.fill_rectangle({{digit_x + 4, digit_y}, {20, 4}}, ui::Color::white()); + if (digit_def & 0x02) painter.fill_rectangle({{digit_x + 24, digit_y + 4}, {4, 20}}, ui::Color::white()); + if (digit_def & 0x04) painter.fill_rectangle({{digit_x + 24, digit_y + 28}, {4, 20}}, ui::Color::white()); + if (digit_def & 0x08) painter.fill_rectangle({{digit_x + 4, digit_y + 48}, {20, 4}}, ui::Color::white()); + if (digit_def & 0x10) painter.fill_rectangle({{digit_x, digit_y + 28}, {4, 20}}, ui::Color::white()); + if (digit_def & 0x20) painter.fill_rectangle({{digit_x, digit_y + 4}, {4, 20}}, ui::Color::white()); + if (digit_def & 0x40) painter.fill_rectangle({{digit_x + 4, digit_y + 24}, {20, 4}}, ui::Color::white()); + } + if (i == 3) { + // Dot + painter.fill_rectangle({{digit_x + 34, digit_y + 48}, {4, 4}}, ui::Color::white()); + digit_x += 40; + } else { + digit_x += 32; + } + } +} + /* ProgressBar ***********************************************************/ ProgressBar::ProgressBar( diff --git a/firmware/common/ui_widget.hpp b/firmware/common/ui_widget.hpp index 1f4323383..42306d989 100644 --- a/firmware/common/ui_widget.hpp +++ b/firmware/common/ui_widget.hpp @@ -26,6 +26,7 @@ #include "ui_text.hpp" #include "ui_painter.hpp" #include "ui_focus.hpp" +#include "radio.hpp" #include "utility.hpp" @@ -197,6 +198,18 @@ private: std::string text; }; +class BigFrequency : public Widget { +public: + BigFrequency(Rect parent_rect, rf::Frequency frequency); + + void set(const rf::Frequency frequency); + + void paint(Painter& painter) override; + +private: + rf::Frequency _frequency; +}; + class ProgressBar : public Widget { public: ProgressBar(Rect parent_rect); diff --git a/firmware/portapack-h1-firmware.bin b/firmware/portapack-h1-firmware.bin index 71447fd78..a6059fd98 100644 Binary files a/firmware/portapack-h1-firmware.bin and b/firmware/portapack-h1-firmware.bin differ