diff --git a/firmware/application/Makefile b/firmware/application/Makefile index 553af84581..d4192e14bf 100755 --- a/firmware/application/Makefile +++ b/firmware/application/Makefile @@ -184,6 +184,7 @@ CPPSRC = main.cpp \ ui_soundboard.cpp \ ui_spectrum.cpp \ ui_text.cpp \ + ui_textentry.cpp \ ui_widget.cpp \ ui_xylos.cpp \ recent_entries.cpp \ diff --git a/firmware/application/bitmap.hpp b/firmware/application/bitmap.hpp index 79906dfbd1..78f0fa9f84 100644 --- a/firmware/application/bitmap.hpp +++ b/firmware/application/bitmap.hpp @@ -26,6 +26,52 @@ namespace ui { +static constexpr uint8_t bitmap_keyboard_data[] = { + 0x00, 0x00, + 0x00, 0x00, + 0x00, 0x00, + 0x00, 0x00, + 0xF8, 0x0F, + 0x88, 0x08, + 0x88, 0x08, + 0x88, 0x08, + 0xFE, 0x3F, + 0x22, 0x22, + 0x22, 0x22, + 0x22, 0x22, + 0xFE, 0x3F, + 0x00, 0x00, + 0x00, 0x00, + 0x00, 0x00, +}; + +static constexpr Bitmap bitmap_keyboard { + { 16, 16 }, bitmap_keyboard_data +}; + +static constexpr uint8_t bitmap_unistroke_data[] = { + 0x00, 0x00, + 0x00, 0x00, + 0x22, 0x00, + 0x22, 0x00, + 0x22, 0x00, + 0x22, 0x00, + 0x22, 0x00, + 0x22, 0x00, + 0xA2, 0x73, + 0xA2, 0x24, + 0xA2, 0x24, + 0xA2, 0x24, + 0xA2, 0x24, + 0x9C, 0x74, + 0x00, 0x00, + 0x00, 0x00, +}; + +static constexpr Bitmap bitmap_unistroke { + { 16, 16 }, bitmap_unistroke_data +}; + static constexpr uint8_t bitmap_record_data[] = { 0x00, 0x00, 0x00, 0x00, diff --git a/firmware/application/main.cpp b/firmware/application/main.cpp index e0568b3a90..1d71aa8f5e 100755 --- a/firmware/application/main.cpp +++ b/firmware/application/main.cpp @@ -20,7 +20,7 @@ */ // Bitmaps generated with: -// Gimp :(, then "xxd -i *.bmp" +// Gimp :( > indexed colors, then "xxd -i *.bmp" //BUG: No audio in about when shown second time //BUG: Description doesn't show up first time going to system>module info (UI drawn on top) @@ -35,18 +35,14 @@ //TODO: Morse coder //TODO: Playdead amnesia and login //TODO: Touch screen calibration -//TODO: Display module info (name, desc) somewhere //TODO: Show MD5 mismatches for modules not found, etc... -//TODO: More gfx, cute icons :) //TODO: Check jammer bandwidths //TODO: GSM channel detector //TODO: AFSK receiver //TODO: SIGFOX RX/TX -//TODO: Reset baseband if module not found (instead of lockup in RAM loop) //TODO: Module name/filename in modules.hpp to indicate requirement in case it's not found ui_loadmodule //BUG: Crash after TX stop (unregister message !) //TODO: Check bw setting in LCR TX -//TODO: BUG: Crash after PSN entry in RDS TX //TODO: Bodet :) //TODO: Whistler //TODO: Setup: Play dead by default ? Enable/disable ? @@ -54,7 +50,6 @@ //TODO: Persistent playdead ! //TODO: LCR EC=A,J,N //TODO: LCR full message former (see norm) -//TODO: LCR address scan //TODO: AFSK NRZI //TODO: TX power diff --git a/firmware/application/ui_alphanum.cpp b/firmware/application/ui_alphanum.cpp index eb38732c45..eaf8f1ba61 100644 --- a/firmware/application/ui_alphanum.cpp +++ b/firmware/application/ui_alphanum.cpp @@ -37,6 +37,7 @@ using namespace hackrf::one; namespace ui { void AlphanumView::paint(Painter& painter) { + (void)painter; move_cursor(); } @@ -51,13 +52,13 @@ AlphanumView::AlphanumView( static constexpr Style style_alpha { .font = font::fixed_8x16, .background = Color::red(), - .foreground = Color::black(), + .foreground = Color::black() }; static constexpr Style style_num { .font = font::fixed_8x16, .background = Color::yellow(), - .foreground = Color::black(), + .foreground = Color::black() }; txtidx = strlen(txt); diff --git a/firmware/application/ui_alphanum.hpp b/firmware/application/ui_alphanum.hpp index 9f1c8fd37d..150303f1e3 100644 --- a/firmware/application/ui_alphanum.hpp +++ b/firmware/application/ui_alphanum.hpp @@ -19,6 +19,9 @@ * Boston, MA 02110-1301, USA. */ +#ifndef __ALPHANUM_H__ +#define __ALPHANUM_H__ + #include "ui.hpp" #include "ui_widget.hpp" #include "ui_painter.hpp" @@ -81,3 +84,5 @@ class AlphanumView : public View { }; } /* namespace ui */ + +#endif/*__ALPHANUM_H__*/ diff --git a/firmware/application/ui_handwrite.cpp b/firmware/application/ui_handwrite.cpp index a6276091d3..021988ad13 100644 --- a/firmware/application/ui_handwrite.cpp +++ b/firmware/application/ui_handwrite.cpp @@ -51,7 +51,8 @@ HandWriteView::HandWriteView( // Handwriting alphabet definition here handwriting = &handwriting_unistroke; - //memcpy(txtinput, txt, max_len+1); + txtidx = strlen(txt); + memcpy(txtinput, txt, max_len + 1); add_children({ { &text_input, @@ -108,12 +109,12 @@ HandWriteView::HandWriteView( }; button_ok.on_select = [this, &nav, txt, max_len](Button&) { - //memcpy(txt, txtinput, max_len+1); - //on_changed(this->value()); + memcpy(txt, txtinput, max_len + 1); + on_changed(this->value()); nav.pop(); }; - //update_text(); + update_text(); } bool HandWriteView::on_touch(const TouchEvent event) { @@ -385,6 +386,10 @@ void HandWriteView::on_show() { clear_zone(Color::black(), false); } +void HandWriteView::on_hide() { + EventDispatcher::message_map().unregister_handler(Message::ID::DisplayFrameSync); +} + char * HandWriteView::value() { return txtinput; } diff --git a/firmware/application/ui_handwrite.hpp b/firmware/application/ui_handwrite.hpp index 29add26c37..4a86a80569 100644 --- a/firmware/application/ui_handwrite.hpp +++ b/firmware/application/ui_handwrite.hpp @@ -20,6 +20,9 @@ * Boston, MA 02110-1301, USA. */ +#ifndef __UNISTROKE_H__ +#define __UNISTROKE_H__ + #include "ui.hpp" #include "ui_widget.hpp" #include "ui_painter.hpp" @@ -38,6 +41,7 @@ class HandWriteView : public View { void paint(Painter& painter) override; void on_show() override; + void on_hide() override; bool on_touch(const TouchEvent event) override; char * value(); @@ -88,3 +92,5 @@ class HandWriteView : public View { }; } /* namespace ui */ + +#endif/*__UNISTROKE_H__*/ diff --git a/firmware/application/ui_lcr.cpp b/firmware/application/ui_lcr.cpp index ceb4dd04c4..d259b17247 100644 --- a/firmware/application/ui_lcr.cpp +++ b/firmware/application/ui_lcr.cpp @@ -20,8 +20,6 @@ * Boston, MA 02110-1301, USA. */ -#include "ui_alphanum.hpp" -#include "ui_rds.hpp" #include "ui_lcr.hpp" #include "ui_receiver.hpp" #include "ui_afsksetup.hpp" @@ -180,6 +178,8 @@ void LCRView::paint(Painter& painter) { ); offset.y += 32; } + + button_setrgsb.set_text(rgsb); } void LCRView::start_tx() { @@ -200,8 +200,8 @@ void LCRView::start_tx() { shared_memory.afsk_fmmod = portapack::persistent_memory::afsk_bw() * 8; - memset(shared_memory.lcrdata, 0, 256); - memcpy(shared_memory.lcrdata, lcrframe_f, 256); + memset(shared_memory.radio_data, 0, 256); + memcpy(shared_memory.radio_data, lcrframe_f, 256); shared_memory.afsk_transmit_done = false; shared_memory.afsk_repeat = 5; //(portapack::persistent_memory::afsk_config() >> 8) & 0xFF; @@ -249,8 +249,8 @@ void LCRView::start_tx() { strcpy(rgsb, RGSB_list[scan_index]); make_frame(); - memset(shared_memory.lcrdata, 0, 256); - memcpy(shared_memory.lcrdata, lcrframe_f, 256); + memset(shared_memory.radio_data, 0, 256); + memcpy(shared_memory.radio_data, lcrframe_f, 256); shared_memory.afsk_transmit_done = false; shared_memory.afsk_repeat = 5; @@ -352,31 +352,23 @@ LCRView::LCRView( button_scan.set_style(&style_val); button_setrgsb.on_select = [this,&nav](Button&) { - auto an_view = nav.push(rgsb, 4); - an_view->on_changed = [this](char *rgsb) { - button_setrgsb.set_text(rgsb); - }; + textentry(nav, rgsb, 4); }; button_setam_a.on_select = [this,&nav](Button&) { - auto an_view = nav.push(litteral[0], 7); - an_view->on_changed = [this](char *) {}; + textentry(nav, litteral[0], 7); }; button_setam_b.on_select = [this,&nav](Button&) { - auto an_view = nav.push(litteral[1], 7); - an_view->on_changed = [this](char *) {}; + textentry(nav, litteral[1], 7); }; button_setam_c.on_select = [this,&nav](Button&) { - auto an_view = nav.push(litteral[2], 7); - an_view->on_changed = [this](char *) {}; + textentry(nav, litteral[2], 7); }; button_setam_d.on_select = [this,&nav](Button&) { - auto an_view = nav.push(litteral[3], 7); - an_view->on_changed = [this](char *) {}; + textentry(nav, litteral[3], 7); }; button_setam_e.on_select = [this,&nav](Button&) { - auto an_view = nav.push(litteral[4], 7); - an_view->on_changed = [this](char *) {}; + textentry(nav, litteral[4], 7); }; button_txsetup.on_select = [&nav](Button&) { diff --git a/firmware/application/ui_lcr.hpp b/firmware/application/ui_lcr.hpp index ed19d11ec1..c042ba20dd 100644 --- a/firmware/application/ui_lcr.hpp +++ b/firmware/application/ui_lcr.hpp @@ -25,6 +25,7 @@ #include "ui_painter.hpp" #include "ui_menu.hpp" #include "ui_navigation.hpp" +#include "ui_textentry.hpp" #include "ui_font_fixed_8x16.hpp" #include "clock_manager.hpp" #include "message.hpp" diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index 7d0e82e2de..fde5a1350d 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -53,19 +53,33 @@ namespace ui { /* SystemStatusView ******************************************************/ SystemStatusView::SystemStatusView() { + uint8_t cfg; + add_children({ { &button_back, &title, + &button_textentry, &button_camera, &button_sleep, &sd_card_status_view, } }); + + cfg = portapack::persistent_memory::ui_config_textentry(); + + if (!cfg) + button_textentry.set_bitmap(&bitmap_keyboard); + else + button_textentry.set_bitmap(&bitmap_unistroke); button_back.on_select = [this](Button&){ if( this->on_back ) { this->on_back(); } }; + + button_textentry.on_select = [this](ImageButton&) { + this->on_textentry(); + }; button_camera.on_select = [this](ImageButton&) { this->on_camera(); @@ -90,6 +104,18 @@ void SystemStatusView::set_title(const std::string new_value) { } } +void SystemStatusView::on_textentry() { + uint8_t cfg; + + cfg = portapack::persistent_memory::ui_config_textentry(); + portapack::persistent_memory::set_config_textentry(cfg ^ 1); + + if (!cfg) + button_textentry.set_bitmap(&bitmap_unistroke); + else + button_textentry.set_bitmap(&bitmap_keyboard); +} + void SystemStatusView::on_camera() { const auto filename_stem = next_filename_stem_matching_pattern("SCR_????"); if( filename_stem.empty() ) { @@ -219,7 +245,7 @@ SystemMenuView::SystemMenuView(NavigationView& nav) { static constexpr ui::Style style_default { .font = ui::font::fixed_8x16, .background = ui::Color::black(), - .foreground = ui::Color::white(), + .foreground = ui::Color::white() }; SystemView::SystemView( diff --git a/firmware/application/ui_navigation.hpp b/firmware/application/ui_navigation.hpp index 6bc532f801..3947dc38b7 100644 --- a/firmware/application/ui_navigation.hpp +++ b/firmware/application/ui_navigation.hpp @@ -62,16 +62,23 @@ class SystemStatusView : public View { { 3 * 8, 0, 16 * 8, 1 * 16 }, default_title, }; + + ImageButton button_textentry { + { 164, 0, 2 * 8, 1 * 16 }, + &bitmap_unistroke, + Color::white(), + Color::black() + }; ImageButton button_camera { - { 22 * 8, 0, 2 * 8, 1 * 16 }, + { 184, 0, 2 * 8, 1 * 16 }, &bitmap_camera, Color::white(), Color::black() }; ImageButton button_sleep { - { 25 * 8, 0, 2 * 8, 1 * 16 }, + { 204, 0, 2 * 8, 1 * 16 }, &bitmap_sleep, Color::white(), Color::black() @@ -82,6 +89,7 @@ class SystemStatusView : public View { }; void on_camera(); + void on_textentry(); }; class NavigationView : public View { diff --git a/firmware/application/ui_rds.cpp b/firmware/application/ui_rds.cpp index 16b0d4809d..18a7a1ad00 100644 --- a/firmware/application/ui_rds.cpp +++ b/firmware/application/ui_rds.cpp @@ -23,8 +23,6 @@ #include "ui_rds.hpp" #include "ch.h" - -#include "ui_alphanum.hpp" #include "ff.h" #include "hackrf_gpio.hpp" #include "portapack.hpp" @@ -40,7 +38,7 @@ using namespace portapack; namespace ui { void RDSView::focus() { - button_setpsn.focus(); + button_editpsn.focus(); } RDSView::~RDSView() { @@ -86,7 +84,7 @@ uint8_t RDSView::b2b(const bool in) { } void RDSView::make_0B_group(uint32_t group[], const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool TA, - const bool MS, const bool DI, const uint8_t C, char * chars) { + const bool MS, const bool DI, const uint8_t C, const char * chars) { group[0] = PI_code; group[1] = (0x0 << 12) | (1 << 11) | (b2b(TP) << 10) | (PTY << 5) | (b2b(TA) << 4) | (b2b(MS) << 3) | (b2b(DI) << 2) | (C & 3); @@ -94,14 +92,23 @@ void RDSView::make_0B_group(uint32_t group[], const uint16_t PI_code, const bool group[3] = (chars[0] << 8) | chars[1]; } -void RDSView::paint(Painter& painter) { +void RDSView::make_2A_group(uint32_t group[], const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool AB, + const bool segment, const char * chars) { + + group[0] = PI_code; + group[1] = (0x0 << 12) | (1 << 11) | (b2b(TP) << 10) | (PTY << 5) | (b2b(AB) << 4) | (segment & 15); + group[2] = (chars[0] << 8) | chars[1]; + group[3] = (chars[2] << 8) | chars[3]; +} + +void RDSView::gen_PSN(const char * psname) { uint8_t c; uint32_t group[4][4] = { 0 }; - make_0B_group(&group[0][0], 0xF849, true, 7, false, true, false, 0, &psname[0]); - make_0B_group(&group[1][0], 0xF849, true, 7, false, true, false, 1, &psname[2]); - make_0B_group(&group[2][0], 0xF849, true, 7, false, true, false, 2, &psname[4]); - make_0B_group(&group[3][0], 0xF849, true, 7, false, true, false, 3, &psname[6]); + make_0B_group(&group[0][0], 0xF849, true, options_pty.selected_index(), false, true, false, 0, &psname[0]); + make_0B_group(&group[1][0], 0xF849, true, options_pty.selected_index(), false, true, false, 1, &psname[2]); + make_0B_group(&group[2][0], 0xF849, true, options_pty.selected_index(), false, true, false, 2, &psname[4]); + make_0B_group(&group[3][0], 0xF849, true, options_pty.selected_index(), false, true, false, 3, &psname[6]); /*uint32_t group[4][4] = { { @@ -140,42 +147,98 @@ void RDSView::paint(Painter& painter) { group[3][3] = (psname[6] << 8) | psname[7]; */ - //Generate checkbits + // Generate checkbits for (c = 0; c < 4; c++) { group[c][0] = makeblock(group[c][0], RDS_OFFSET_A); group[c][1] = makeblock(group[c][1], RDS_OFFSET_B); - group[c][2] = makeblock(group[c][2], RDS_OFFSET_Cp); //C' + group[c][2] = makeblock(group[c][2], RDS_OFFSET_Cp); // C' ! group[c][3] = makeblock(group[c][3], RDS_OFFSET_D); } + + for (c = 0; c < 16; c++) + shared_memory.radio_data[c] = group[c >> 2][c & 3]; + + shared_memory.bit_length = 4 * 4 * 26; +} - const Point offset = { - static_cast(64), - static_cast(32) - }; - - const auto text = psname; - painter.draw_string( - screen_pos() + offset, - style(), - text - ); +void RDSView::gen_RadioText(const char * radiotext) { + size_t c, i; + uint32_t * group; + char radiotext_buffer[65] = { 0 }; + uint8_t rtlen, groups; + + strcpy(radiotext_buffer, radiotext); - for (c = 0; c < 16; c++) { - shared_memory.rdsdata[c] = group[c >> 2][c & 3]; + rtlen = strlen(radiotext_buffer); + + radiotext_buffer[rtlen] = 0x0D; + + // Pad to multiple of 4 + while(rtlen & 3) { + radiotext_buffer[rtlen] = ' '; + rtlen++; + } + + groups = rtlen >> 2; // 4 characters per group + + group = (uint32_t*)chHeapAlloc(0x0, 4 * groups * sizeof(uint32_t)); + + for (c = 0; c < groups; c++) + make_2A_group(&group[c << 2], 0xF849, true, options_pty.selected_index(), false, c, &radiotext_buffer[c << 2]); + + // Generate checkbits + for (c = 0; c < groups; c++) { + i = c * 4; + group[i + 0] = makeblock(group[i + 0], RDS_OFFSET_A); + group[i + 1] = makeblock(group[i + 1], RDS_OFFSET_B); + group[i + 2] = makeblock(group[i + 2], RDS_OFFSET_C); + group[i + 3] = makeblock(group[i + 3], RDS_OFFSET_D); } + + for (c = 0; c < (groups * 4); c++) + shared_memory.radio_data[c] = group[c]; + + shared_memory.bit_length = groups * 4 * 26; +} + +void RDSView::paint(Painter& painter) { + char RadioTextA[17]; + + (void)painter; + + text_psn.set(PSN); + memcpy(RadioTextA, RadioText, 16); + RadioTextA[16] = 0; + text_radiotexta.set(RadioTextA); + text_radiotextb.set(&RadioText[16]); } RDSView::RDSView( NavigationView& nav ) { - strcpy(psname, "TEST1234"); + transmitter_model.set_baseband_configuration({ + .mode = 5, + .sampling_rate = 2280000, + .decimation_factor = 1, + }); + + strcpy(PSN, "TEST1234"); + strcpy(RadioText, "Radiotext test !"); add_children({ { &field_frequency, &options_pty, - &button_setpsn, - &button_transmit, + &options_countrycode, + &options_coverage, + &text_tx, + &button_editpsn, + &text_psn, + &button_txpsn, + &button_editradiotext, + &text_radiotexta, + &text_radiotextb, + &button_txradiotext, &button_exit } }); @@ -188,17 +251,44 @@ RDSView::RDSView( }; }; - options_pty.set_selected_index(0); + options_pty.set_selected_index(0); // None + options_countrycode.set_selected_index(18); // France + options_coverage.set_selected_index(0); // Local - button_setpsn.on_select = [this,&nav](Button&){ - auto an_view = nav.push(psname, 8); - an_view->on_changed = [this](char *value) { - memcpy(psname, value, 9); - }; + button_editpsn.on_select = [this,&nav](Button&){ + textentry(nav, PSN, 8); + }; + button_txpsn.on_select = [this](Button&){ + if (txing) { + transmitter_model.disable(); + button_txpsn.set_text("PSN"); + button_txradiotext.set_text("Radiotext"); + txing = false; + } else { + gen_PSN(PSN); + transmitter_model.set_tuning_frequency(field_frequency.value()); + button_txpsn.set_text("STOP"); + txing = true; + transmitter_model.enable(); + } }; - button_transmit.on_select = [this](Button&){ - transmitter_model.enable(); + button_editradiotext.on_select = [this,&nav](Button&){ + textentry(nav, RadioText, 24); + }; + button_txradiotext.on_select = [this](Button&){ + if (txing) { + transmitter_model.disable(); + button_txpsn.set_text("PSN"); + button_txradiotext.set_text("Radiotext"); + txing = false; + } else { + gen_RadioText(RadioText); + transmitter_model.set_tuning_frequency(field_frequency.value()); + button_txradiotext.set_text("STOP"); + txing = true; + transmitter_model.enable(); + } }; button_exit.on_select = [&nav](Button&){ diff --git a/firmware/application/ui_rds.hpp b/firmware/application/ui_rds.hpp index 2577106396..07686e8291 100644 --- a/firmware/application/ui_rds.hpp +++ b/firmware/application/ui_rds.hpp @@ -25,6 +25,7 @@ #include "ui_navigation.hpp" #include "ui_font_fixed_8x16.hpp" #include "ui_receiver.hpp" +#include "ui_textentry.hpp" #include "clock_manager.hpp" #include "message.hpp" #include "rf_path.hpp" @@ -50,67 +51,192 @@ class RDSView : public View { void paint(Painter& painter) override; private: - char psname[9]; + char PSN[9]; + char RadioText[25]; + bool txing = false; uint8_t b2b(const bool in); + + void gen_PSN(const char * psname); + void gen_RadioText(const char * radiotext); + void make_0B_group(uint32_t group[], const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool TA, - const bool MS, const bool DI, const uint8_t C, char * TWOCHARs); + const bool MS, const bool DI, const uint8_t C, const char * chars); + void make_2A_group(uint32_t group[], const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool AB, + const bool segment, const char * chars); FrequencyField field_frequency { { 1 * 8, 1 * 16 }, }; OptionsField options_pty { - { 1 * 8, 3 * 16 }, - 32, + { 1 * 8, 2 * 16 }, + 8, { - { "None ", 0 }, - { "News ", 1 }, - { "Affairs ", 2 }, - { "Info ", 3 }, - { "Sport ", 4 }, - { "Educate ", 5 }, - { "Drama ", 6 }, - { "Culture ", 7 }, - { "Science ", 8 }, - { "Varied ", 9 }, - { "Pop ", 10 }, - { "Rock ", 11 }, - { "Easy ", 12 }, - { "Light ", 13 }, + { "None", 0 }, + { "News", 1 }, + { "Affairs", 2 }, + { "Info", 3 }, + { "Sport", 4 }, + { "Educate", 5 }, + { "Drama", 6 }, + { "Culture", 7 }, + { "Science", 8 }, + { "Varied", 9 }, + { "Pop", 10 }, + { "Rock", 11 }, + { "Easy", 12 }, + { "Light", 13 }, { "Classics", 14 }, - { "Other ", 15 }, - { "Weather ", 16 }, - { "Finance ", 17 }, + { "Other", 15 }, + { "Weather", 16 }, + { "Finance", 17 }, { "Children", 18 }, - { "Social ", 19 }, + { "Social", 19 }, { "Religion", 20 }, - { "PhoneIn ", 21 }, - { "Travel ", 22 }, - { "Leisure ", 23 }, - { "Jazz ", 24 }, - { "Country ", 25 }, + { "PhoneIn", 21 }, + { "Travel", 22 }, + { "Leisure", 23 }, + { "Jazz", 24 }, + { "Country", 25 }, { "National", 26 }, - { "Oldies ", 27 }, - { "Folk ", 28 }, - { "Docs ", 29 }, + { "Oldies", 27 }, + { "Folk", 28 }, + { "Docs", 29 }, { "AlarmTst", 30 }, - { "Alarm ", 31 } + { "Alarm", 31 } } }; - - Button button_setpsn { - { 72, 92, 96, 32 }, - "Set PSN" + + OptionsField options_countrycode { + { 1 * 8, 3 * 16 }, + 11, + { + { "Albania", 9 }, + { "Algeria", 2 }, + { "Andorra", 3 }, + { "Austria", 10 }, + { "Azores", 8 }, + { "Belgium", 6 }, + { "Belarus", 15 }, + { "Bosnia", 15 }, + { "Bulgaria", 8 }, + { "Canaries", 14 }, + { "Croatia", 12 }, + { "Cyprus", 2 }, + { "Czech-Rep", 2 }, + { "Denmark", 9 }, + { "Egypt", 15 }, + { "Estonia", 2 }, + { "Faroe", 9 }, + { "Finland", 6 }, + { "France", 15 }, + { "Germany 1", 13 }, + { "Germany 2", 1 }, + { "Gibraltar", 10 }, + { "Greece", 1 }, + { "Hungary", 11 }, + { "Iceland", 10 }, + { "Iraq", 11 }, + { "Ireland", 2 }, + { "Israel", 4 }, + { "Italy", 5 }, + { "Jordan", 5 }, + { "Latvia", 9 }, + { "Lebanon", 10 }, + { "Libya", 13 }, + { "Liechtenst.", 9 }, + { "Lithuania", 12 }, + { "Luxembourg", 7 }, + { "Macedonia", 4 }, + { "Madeira", 8 }, + { "Malta", 12 }, + { "Moldova", 1 }, + { "Monaco", 11 }, + { "Morocco", 1 }, + { "Netherlands", 8 }, + { "Norway", 15 }, + { "Palestine", 8 }, + { "Poland", 3 }, + { "Portugal", 8 }, + { "Romania", 14 }, + { "Russia", 7 }, + { "San Marino", 3 }, + { "Slovakia", 5 }, + { "Slovenia", 9 }, + { "Spain", 14 }, + { "Sweden", 14 }, + { "Switzerland", 4 }, + { "Syria", 6 }, + { "Tunisia", 7 }, + { "Turkey", 3 }, + { "Ukraine", 6 }, + { "U.K.", 12 }, + { "Vatican", 4 }, + { "Yugoslavia", 13 } + } }; - Button button_transmit { - { 72, 130, 96, 32 }, - "Transmit" + OptionsField options_coverage { + { 1 * 8, 4 * 16 }, + 8, + { + { "Local", 0 }, + { "International", 1 }, + { "National", 2 }, + { "Supra-regional", 3 }, + { "R11", 4 }, + { "R12", 5 }, + { "R13", 6 }, + { "R14", 7 }, + { "R15", 8 }, + { "R16", 9 }, + { "R17", 10 }, + { "R18", 11 }, + { "R19", 12 }, + { "R110", 13 }, + { "R111", 14 }, + { "R112", 15 } + } + }; + + Text text_tx { + { 19 * 8, 4 * 16, 9 * 8, 16 }, + "Transmit:" + }; + + Button button_editpsn { + { 2 * 8, 6 * 16, 64, 24 }, + "Set" + }; + Text text_psn { + { 2 * 8, 15 * 8, 8 * 8, 16 }, + "TEST1234" + }; + Button button_txpsn { + { 19 * 8, 6 * 16, 10 * 8, 32 }, + "PSN" + }; + + Button button_editradiotext { + { 2 * 8, 9 * 16, 64, 24 }, + "Set" + }; + Text text_radiotexta { + { 2 * 8, 21 * 8, 16 * 8, 16 }, + "Radiotext test !" + }; + Text text_radiotextb { + { 2 * 8, 23 * 8, 16 * 8, 16 }, + "--------" + }; + Button button_txradiotext { + { 19 * 8, 9 * 16, 10 * 8, 32 }, + "Radiotext" }; Button button_exit { - { 72, 270, 96, 32 }, + { 72, 260, 96, 32 }, "Exit" }; }; diff --git a/firmware/application/ui_setup.cpp b/firmware/application/ui_setup.cpp index 4bd2ae3b6b..57b1c3a784 100644 --- a/firmware/application/ui_setup.cpp +++ b/firmware/application/ui_setup.cpp @@ -257,7 +257,7 @@ SetUIView::SetUIView(NavigationView& nav) { uint32_t ui_config = 0; if (checkbox_showsplash.value() == true) ui_config |= 1; if (checkbox_bloff.value() == true) ui_config |= 2; - + ui_config |= (portapack::persistent_memory::ui_config_textentry() << 2); ui_config |= (options_bloff.selected_index() << 5); portapack::persistent_memory::set_ui_config(ui_config); @@ -270,7 +270,7 @@ void SetUIView::focus() { } void ModInfoView::on_show() { - update_infos(0); + if (modules_nb) update_infos(0); } void ModInfoView::update_infos(uint8_t modn) { @@ -331,10 +331,10 @@ ModInfoView::ModInfoView(NavigationView& nav) { FIL modfile; DIR rootdir; FRESULT res; + uint8_t c; using option_t = std::pair; using options_t = std::vector; - uint8_t c; option_t opt; options_t opts; @@ -414,11 +414,13 @@ ModInfoView::ModInfoView(NavigationView& nav) { } f_closedir(&rootdir); - memcpy(info_str, "Found ", 7); - strcat(info_str, to_string_dec_uint(c, 1).c_str()); - strcat(info_str, " module(s)"); + modules_nb = c; - if (c) { + if (modules_nb) { + strcpy(info_str, "Found "); + strcat(info_str, to_string_dec_uint(modules_nb, 1).c_str()); + strcat(info_str, " module(s)"); + text_modcount.set(info_str); option_modules.set_options(opts); @@ -428,6 +430,9 @@ ModInfoView::ModInfoView(NavigationView& nav) { (void)n; update_infos(v); }; + } else { + strcpy(info_str, "No modules found"); + text_modcount.set(info_str); } button_ok.on_select = [&nav,this](Button&){ @@ -436,18 +441,21 @@ ModInfoView::ModInfoView(NavigationView& nav) { } void ModInfoView::focus() { - option_modules.focus(); + if (modules_nb) + option_modules.focus(); + else + button_ok.focus(); } SetupMenuView::SetupMenuView(NavigationView& nav) { add_items<7>({ { + { "UI", ui::Color::white(), [&nav](){ nav.push(); } }, { "SD card modules", ui::Color::white(), [&nav](){ nav.push(); } }, { "Date/Time", ui::Color::white(), [&nav](){ nav.push(); } }, { "Frequency correction", ui::Color::white(), [&nav](){ nav.push(); } }, { "Antenna Bias Voltage", ui::Color::white(), [&nav](){ nav.push(); } }, { "Touch screen", ui::Color::white(), [&nav](){ nav.push(); } }, - { "Play dead", ui::Color::red(), [&nav](){ nav.push(); } }, - { "UI", ui::Color::white(), [&nav](){ nav.push(); } }, + { "Play dead", ui::Color::red(), [&nav](){ nav.push(); } } } }); on_left = [&nav](){ nav.pop(); }; } diff --git a/firmware/application/ui_setup.hpp b/firmware/application/ui_setup.hpp index a11f60e893..ba116e1b2e 100644 --- a/firmware/application/ui_setup.hpp +++ b/firmware/application/ui_setup.hpp @@ -269,17 +269,17 @@ class SetUIView : public View { { 10 * 8, 5 * 16 + 4 }, 10, { - { "5 seconds ", 0 }, + { "5 seconds", 0 }, { "15 seconds", 1 }, - { "1 minute ", 2 }, - { "5 minutes ", 3 }, + { "1 minute", 2 }, + { "5 minutes", 3 }, { "10 minutes", 4 } } }; Button button_ok { - { 4 * 8, 272, 64, 24 }, - "Ok" + { 72, 260, 96, 32 }, + "OK" }; }; @@ -328,6 +328,8 @@ class ModInfoView : public View { moduleinfo_t module_list[8]; // 8 max for now + uint8_t modules_nb; + Text text_modcount { { 2 * 8, 1 * 16, 18 * 8, 16 }, "Searching..." @@ -336,7 +338,8 @@ class ModInfoView : public View { OptionsField option_modules { { 2 * 8, 2 * 16 }, 24, - { { "-", 0 } + { + { "-", 0 } } }; diff --git a/firmware/application/ui_textentry.cpp b/firmware/application/ui_textentry.cpp new file mode 100644 index 0000000000..83048e97ca --- /dev/null +++ b/firmware/application/ui_textentry.cpp @@ -0,0 +1,43 @@ +/* + * 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_textentry.hpp" + +namespace ui { + +bool textentry(NavigationView& nav, char * str, uint16_t max_length) { + if (portapack::persistent_memory::ui_config_textentry() == 0) { + auto an_view = nav.push(str, max_length); + an_view->on_changed = [str, max_length](char * value) { + memcpy(str, value, max_length + 1); + }; + } else { + auto an_view = nav.push(str, max_length); + an_view->on_changed = [str, max_length](char * value) { + memcpy(str, value, max_length + 1); + }; + } + + return true; +} + +} /* namespace ui */ diff --git a/firmware/application/ui_textentry.hpp b/firmware/application/ui_textentry.hpp new file mode 100644 index 0000000000..98b86cd35f --- /dev/null +++ b/firmware/application/ui_textentry.hpp @@ -0,0 +1,33 @@ +/* + * 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.hpp" +#include "ui_navigation.hpp" +#include "ui_handwrite.hpp" +#include "ui_alphanum.hpp" +#include "portapack_persistent_memory.hpp" + +namespace ui { + +bool textentry(NavigationView& nav, char * str, uint16_t max_length); + +} /* namespace ui */ diff --git a/firmware/application/ui_xylos.cpp b/firmware/application/ui_xylos.cpp index 54e1500193..1ef6ac3252 100644 --- a/firmware/application/ui_xylos.cpp +++ b/firmware/application/ui_xylos.cpp @@ -435,7 +435,6 @@ XylosView::XylosView( transmitter_model.enable(); } }; - } } /* namespace ui */ diff --git a/firmware/application/ui_xylos.hpp b/firmware/application/ui_xylos.hpp index 5601d0164b..132b204b2c 100644 --- a/firmware/application/ui_xylos.hpp +++ b/firmware/application/ui_xylos.hpp @@ -23,21 +23,20 @@ #include "ui.hpp" #include "ui_widget.hpp" #include "ui_painter.hpp" -#include "ui_menu.hpp" #include "ui_navigation.hpp" +#include "ui_menu.hpp" #include "ui_font_fixed_8x16.hpp" #include "bulb_on_bmp.hpp" #include "bulb_off_bmp.hpp" #include "bulb_ignore_bmp.hpp" -#include "clock_manager.hpp" #include "message.hpp" -#include "rf_path.hpp" #include "max2837.hpp" #include "volume.hpp" #include "transmitter_model.hpp" -#include "receiver_model.hpp" +//#include "receiver_model.hpp" +#include "portapack.hpp" namespace ui { diff --git a/firmware/baseband-tx.bin b/firmware/baseband-tx.bin index 0bbd08fbfa..f37fb9eeb8 100644 Binary files a/firmware/baseband-tx.bin and b/firmware/baseband-tx.bin differ diff --git a/firmware/baseband-tx/Makefile b/firmware/baseband-tx/Makefile index db9f9a07de..2079d1b677 100755 --- a/firmware/baseband-tx/Makefile +++ b/firmware/baseband-tx/Makefile @@ -143,6 +143,7 @@ CPPSRC = main.cpp \ proc_epar.cpp \ proc_playaudio.cpp \ proc_xylos.cpp \ + proc_rds.cpp \ dsp_squelch.cpp \ clock_recovery.cpp \ packet_builder.cpp \ diff --git a/firmware/baseband-tx/baseband_thread.cpp b/firmware/baseband-tx/baseband_thread.cpp index eeb1fdea86..83118ff60f 100644 --- a/firmware/baseband-tx/baseband_thread.cpp +++ b/firmware/baseband-tx/baseband_thread.cpp @@ -33,6 +33,7 @@ #include "proc_xylos.hpp" #include "proc_epar.hpp" #include "proc_fsk_lcr.hpp" +#include "proc_rds.hpp" #include "rssi.hpp" #include "i2s.hpp" @@ -126,6 +127,7 @@ BasebandProcessor* BasebandThread::create_processor(const int32_t mode) { case 2: return new XylosProcessor(); case 3: return new LCRFSKProcessor(); case 4: return new EPARProcessor(); + case 5: return new RDSProcessor(); default: return nullptr; } } diff --git a/firmware/baseband-tx/proc_fsk_lcr.cpp b/firmware/baseband-tx/proc_fsk_lcr.cpp index a7de6a0941..a76b002cf3 100644 --- a/firmware/baseband-tx/proc_fsk_lcr.cpp +++ b/firmware/baseband-tx/proc_fsk_lcr.cpp @@ -35,13 +35,13 @@ void LCRFSKProcessor::execute(const buffer_c8_t& buffer) { if (sample_count >= shared_memory.afsk_samples_per_bit) { if (shared_memory.afsk_transmit_done == false) - cur_byte = shared_memory.lcrdata[byte_pos]; + cur_byte = shared_memory.radio_data[byte_pos]; if (!cur_byte) { if (shared_memory.afsk_repeat) { shared_memory.afsk_repeat--; bit_pos = 0; byte_pos = 0; - cur_byte = shared_memory.lcrdata[0]; + cur_byte = shared_memory.radio_data[0]; message.n = shared_memory.afsk_repeat; shared_memory.application_queue.push(message); } else { diff --git a/firmware/baseband-tx/proc_rds.cpp b/firmware/baseband-tx/proc_rds.cpp index d6fb366f08..cc05fe3084 100644 --- a/firmware/baseband-tx/proc_rds.cpp +++ b/firmware/baseband-tx/proc_rds.cpp @@ -27,21 +27,24 @@ #include void RDSProcessor::execute(const buffer_c8_t& buffer) { + uint32_t * rdsdata; - for (size_t i = 0; i= 9) { s = 0; if(sample_count >= SAMPLES_PER_BIT) { - cur_bit = (shared_memory.rdsdata[(bit_pos / 26) & 15]>>(25-(bit_pos % 26))) & 1; + cur_bit = (rdsdata[(bit_pos / 26) & 15] >> (25 - (bit_pos % 26))) & 1; prev_output = cur_output; cur_output = prev_output ^ cur_bit; - int32_t *src = waveform_biphase; + const int32_t *src = waveform_biphase; // const ok ? int idx = in_sample_index; - for(int j=0; j= SAMPLE_BUFFER_SIZE) in_sample_index -= SAMPLE_BUFFER_SIZE; - bit_pos++; + if (bit_pos < shared_memory.bit_length) + bit_pos++; + else + bit_pos = 0; sample_count = 0; } @@ -60,7 +66,7 @@ void RDSProcessor::execute(const buffer_c8_t& buffer) { out_sample_index++; if (out_sample_index >= SAMPLE_BUFFER_SIZE) out_sample_index = 0; - //AM @ 228k/4=57kHz + //AM @ 228k/4 = 57kHz switch (mphase) { case 0: case 2: sample = 0; break; @@ -80,11 +86,10 @@ void RDSProcessor::execute(const buffer_c8_t& buffer) { phase = (phase + frq); sphase = phase + (256<<16); - - //re = sintab[(sphase & 0x03FF0000)>>16]; - //im = sintab[(phase & 0x03FF0000)>>16]; + + re = (sine_table_f32[(sphase & 0x03FF0000)>>18]*127); + im = (sine_table_f32[(phase & 0x03FF0000)>>18]*127); buffer.p[i] = {(int8_t)re,(int8_t)im}; } } - diff --git a/firmware/baseband-tx/proc_rds.hpp b/firmware/baseband-tx/proc_rds.hpp index 17faadaaea..adbecadd8b 100644 --- a/firmware/baseband-tx/proc_rds.hpp +++ b/firmware/baseband-tx/proc_rds.hpp @@ -45,12 +45,12 @@ class RDSProcessor : public BasebandProcessor { int sample_count = SAMPLES_PER_BIT; int in_sample_index = 0; int32_t sample; - int out_sample_index = SAMPLE_BUFFER_SIZE-1; + int out_sample_index = SAMPLE_BUFFER_SIZE - 1; uint32_t phase, sphase; int32_t sig, frq, frq_im, rdsc; int32_t k; - int32_t waveform_biphase[576] = { + const int32_t waveform_biphase[576] = { 165,167,168,168,167,166,163,160, 157,152,147,141,134,126,118,109, 99,88,77,66,53,41,27,14, diff --git a/firmware/common/modules.h b/firmware/common/modules.h index aefa79ff09..dfaf5445cd 100644 --- a/firmware/common/modules.h +++ b/firmware/common/modules.h @@ -1,2 +1,2 @@ const char md5_baseband[16] = {0xb8,0x9e,0x9b,0x08,0x44,0x34,0x04,0x20,0x0b,0xbc,0x60,0x7e,0x67,0x88,0x53,0xf7,}; -const char md5_baseband_tx[16] = {0xf2,0x82,0xb2,0x1e,0xc7,0xaa,0x15,0x73,0x02,0x74,0xbf,0x51,0xbe,0x6c,0xca,0xd0,}; +const char md5_baseband_tx[16] = {0x76,0x21,0x56,0xb0,0x12,0x45,0xdd,0x66,0x87,0xee,0x8a,0x97,0xda,0xee,0xc5,0xac,}; diff --git a/firmware/common/portapack_persistent_memory.cpp b/firmware/common/portapack_persistent_memory.cpp index 88f55dea99..4354ea6963 100644 --- a/firmware/common/portapack_persistent_memory.cpp +++ b/firmware/common/portapack_persistent_memory.cpp @@ -72,7 +72,7 @@ struct data_t { uint32_t playing_dead; uint32_t playdead_sequence; - int32_t ui_config; + uint32_t ui_config; }; static_assert(sizeof(data_t) <= backup_ram.size(), "Persistent memory structure too large for VBAT-maintained region"); @@ -164,7 +164,7 @@ uint32_t ui_config() { // Cap value bloff_value = (data->ui_config >> 5) & 7; - if (bloff_value > 4) bloff_value = 1; + if (bloff_value > 4) bloff_value = 1; // 15s default data->ui_config = (data->ui_config & 0x1F) | (bloff_value << 5); @@ -184,6 +184,14 @@ uint16_t ui_config_bloff() { return bloff_seconds[bloff_value]; } +void set_config_textentry(uint8_t new_value) { + data->ui_config = (data->ui_config & ~0b1100) | ((new_value & 1) << 2); +} + +uint8_t ui_config_textentry() { + return ((data->ui_config >> 2) & 1); +} + void set_ui_config(const uint32_t new_value) { data->ui_config = new_value; } diff --git a/firmware/common/portapack_persistent_memory.hpp b/firmware/common/portapack_persistent_memory.hpp index 294d9ebe4f..7646ec808a 100644 --- a/firmware/common/portapack_persistent_memory.hpp +++ b/firmware/common/portapack_persistent_memory.hpp @@ -60,8 +60,12 @@ void set_playdead_sequence(const uint32_t new_value); uint32_t ui_config(); void set_ui_config(const uint32_t new_value); + uint16_t ui_config_bloff(); +uint8_t ui_config_textentry(); +void set_config_textentry(uint8_t new_value); + } /* namespace persistent_memory */ } /* namespace portapack */ diff --git a/firmware/common/portapack_shared_memory.hpp b/firmware/common/portapack_shared_memory.hpp index acc2881ab7..1995aadd09 100644 --- a/firmware/common/portapack_shared_memory.hpp +++ b/firmware/common/portapack_shared_memory.hpp @@ -53,10 +53,10 @@ struct SharedMemory { TouchADCFrame touch_adc_frame; int test; + + uint8_t radio_data[256]; + size_t bit_length; - uint32_t rdsdata[16]; - - uint8_t lcrdata[256]; uint32_t afsk_samples_per_bit; uint32_t afsk_phase_inc_mark; uint32_t afsk_phase_inc_space; @@ -69,6 +69,7 @@ struct SharedMemory { char xylosdata[21]; char epardata[13]; int32_t excursion; + bool transmit_done; }; diff --git a/firmware/common/ui_painter.cpp b/firmware/common/ui_painter.cpp index 2b54e19484..b97caf3c3e 100644 --- a/firmware/common/ui_painter.cpp +++ b/firmware/common/ui_painter.cpp @@ -32,7 +32,7 @@ Style Style::invert() const { return { .font = font, .background = foreground, - .foreground = background, + .foreground = background }; } diff --git a/firmware/common/ui_painter.hpp b/firmware/common/ui_painter.hpp index 98d6d36e7e..dd39388436 100644 --- a/firmware/common/ui_painter.hpp +++ b/firmware/common/ui_painter.hpp @@ -60,7 +60,6 @@ class Painter { private: void draw_hline(Point p, int width, const Color c); void draw_vline(Point p, int height, const Color c); - void paint_widget(Widget* const w); }; diff --git a/firmware/common/ui_widget.cpp b/firmware/common/ui_widget.cpp index b6f8afbe78..2484b3d7d5 100644 --- a/firmware/common/ui_widget.cpp +++ b/firmware/common/ui_widget.cpp @@ -538,23 +538,19 @@ void Button::paint(Painter& painter) { const auto paint_style = (has_focus() || highlighted()) ? style().invert() : style(); - painter.draw_rectangle(r, style().foreground); + painter.draw_rectangle(r, paint_style.foreground); painter.fill_rectangle( { r.pos.x + 1, r.pos.y + 1, r.size.w - 2, r.size.h - 2 }, paint_style.background ); - //char *token = strtok(text_.c_str(), "\n"); - //while(token) { - const auto label_r = paint_style.font.size_of(text_); - painter.draw_string( - { r.pos.x + (r.size.w - label_r.w) / 2, r.pos.y + (r.size.h - label_r.h) / 2 }, - paint_style, - text_ - ); - // token = strtok(NULL, " "); - //} + const auto label_r = paint_style.font.size_of(text_); + painter.draw_string( + { r.pos.x + (r.size.w - label_r.w) / 2, r.pos.y + (r.size.h - label_r.h) / 2 }, + paint_style, + text_ + ); } bool Button::on_key(const KeyEvent key) { @@ -735,10 +731,10 @@ size_t ImageOptionsField::selected_index_value() const { } void ImageOptionsField::set_selected_index(const size_t new_index) { - if( new_index < options.size() ) { - if( new_index != selected_index() ) { + if ( new_index < options.size() ) { + if ( new_index != selected_index() ) { selected_index_ = new_index; - if( on_change ) { + if ( on_change ) { on_change(selected_index(), options[selected_index()].second); } set_dirty(); @@ -768,7 +764,7 @@ void ImageOptionsField::paint(Painter& painter) { if( selected_index() < options.size() ) { const auto bmp_ptr = options[selected_index()].first; - portapack::display.fill_rectangle({screen_rect().pos, {screen_rect().size.w + 4, screen_rect().size.h + 4}}, ui::Color::black()); + painter.fill_rectangle({screen_rect().pos, {screen_rect().size.w + 4, screen_rect().size.h + 4}}, ui::Color::black()); painter.draw_rectangle({screen_rect().pos, {screen_rect().size.w + 4, screen_rect().size.h + 4}}, paint_style.background); portapack::display.drawBMP({screen_pos().x + 2, screen_pos().y + 1}, bmp_ptr, true); } @@ -844,7 +840,9 @@ void OptionsField::set_options(options_t new_options) { void OptionsField::paint(Painter& painter) { const auto paint_style = has_focus() ? style().invert() : style(); - + + painter.fill_rectangle({screen_rect().pos, {length_ * 8, 16}}, ui::Color::black()); + if( selected_index() < options.size() ) { const auto text = options[selected_index()].first; painter.draw_string( diff --git a/firmware/portapack-h1-firmware.bin b/firmware/portapack-h1-firmware.bin index 93c8b6b72e..74ab82e917 100644 Binary files a/firmware/portapack-h1-firmware.bin and b/firmware/portapack-h1-firmware.bin differ diff --git a/sdcard/baseband-tx.bin b/sdcard/baseband-tx.bin index 0bbd08fbfa..f37fb9eeb8 100644 Binary files a/sdcard/baseband-tx.bin and b/sdcard/baseband-tx.bin differ