diff --git a/firmware/application/baseband_api.cpp b/firmware/application/baseband_api.cpp index fae60570..27a7e8df 100644 --- a/firmware/application/baseband_api.cpp +++ b/firmware/application/baseband_api.cpp @@ -104,10 +104,12 @@ void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phas send_message(&message); } -void set_audiotx_data(const uint32_t divider, const uint32_t bw, const bool ctcss_enabled, const uint32_t ctcss_phase_inc) { +void set_audiotx_data(const uint32_t divider, const uint32_t bw, const uint32_t gain_x10, + const bool ctcss_enabled, const uint32_t ctcss_phase_inc) { const AudioTXConfigMessage message { divider, bw, + gain_x10, ctcss_phase_inc, ctcss_enabled }; diff --git a/firmware/application/baseband_api.hpp b/firmware/application/baseband_api.hpp index c0548d0c..303c1318 100644 --- a/firmware/application/baseband_api.hpp +++ b/firmware/application/baseband_api.hpp @@ -57,7 +57,8 @@ struct WFMConfig { void set_tones_data(const uint32_t bw, const uint32_t pre_silence, const uint16_t tone_count, const bool dual_tone, const bool audio_out); -void set_audiotx_data(const uint32_t divider, const uint32_t bw, const bool ctcss_enabled, const uint32_t ctcss_phase_inc); +void set_audiotx_data(const uint32_t divider, const uint32_t bw, const uint32_t gain_x10, + const bool ctcss_enabled, const uint32_t ctcss_phase_inc); void set_fifo_data(const int8_t * data); void set_pwmrssi(int32_t avg, bool enabled); void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space, diff --git a/firmware/application/bitmap.hpp b/firmware/application/bitmap.hpp index 7219c7e9..9aacbc46 100644 --- a/firmware/application/bitmap.hpp +++ b/firmware/application/bitmap.hpp @@ -126,7 +126,7 @@ static constexpr uint8_t bitmap_stripes_data[] = { 0x1F, 0x00, 0xFE, 0x0F, 0x00, 0xFF, 0x07, 0x80, 0xFF, -}; + }; static constexpr Bitmap bitmap_stripes { { 24, 8 }, bitmap_stripes_data }; @@ -499,20 +499,20 @@ static constexpr Bitmap bitmap_sd_card_ok { static constexpr uint8_t bitmap_icon_microphone_data[] = { 0xC0, 0x03, - 0x60, 0x05, - 0xB0, 0x0A, - 0x50, 0x0D, - 0xB0, 0x0E, + 0xA0, 0x06, 0x60, 0x05, 0xE0, 0x07, - 0x20, 0x04, - 0x20, 0x04, - 0xE0, 0x04, - 0xE0, 0x04, - 0xC0, 0x02, - 0x40, 0x02, - 0x40, 0x02, - 0x40, 0x02, + 0xEC, 0x37, + 0xEC, 0x37, + 0xE8, 0x17, + 0xE8, 0x17, + 0xE8, 0x17, + 0xC8, 0x13, + 0x18, 0x18, + 0xF0, 0x0F, + 0xC0, 0x03, + 0x80, 0x01, + 0x80, 0x01, 0xC0, 0x03, }; static constexpr Bitmap bitmap_icon_microphone { @@ -541,6 +541,28 @@ static constexpr Bitmap bitmap_icon_numbers { { 16, 16 }, bitmap_icon_numbers_data }; +static constexpr uint8_t bitmap_icon_setup_data[] = { + 0x00, 0x00, + 0x18, 0x18, + 0x18, 0x7E, + 0x18, 0x7E, + 0x18, 0x7E, + 0x18, 0x42, + 0x18, 0x42, + 0x18, 0x42, + 0x18, 0x18, + 0x7E, 0x18, + 0x7E, 0x18, + 0x7E, 0x18, + 0x42, 0x18, + 0x42, 0x18, + 0x42, 0x18, + 0x18, 0x18, +}; +static constexpr Bitmap bitmap_icon_setup { + { 16, 16 }, bitmap_icon_setup_data +}; + static constexpr uint8_t bitmap_icon_rds_data[] = { 0x00, 0x00, 0x00, 0x00, @@ -791,8 +813,8 @@ static constexpr uint8_t bitmap_icon_remote_data[] = { 0xD0, 0x08, 0x10, 0x08, 0x10, 0x08, - 0x10, 0x08, - 0x10, 0x08, + 0x30, 0x0C, + 0xF0, 0x0F, 0xE0, 0x07, }; static constexpr Bitmap bitmap_icon_remote { @@ -865,6 +887,28 @@ static constexpr Bitmap bitmap_icon_keyboard { { 16, 16 }, bitmap_icon_keyboard_data }; +static constexpr uint8_t bitmap_icon_utilities_data[] = { + 0xC0, 0x03, + 0x80, 0x0F, + 0x00, 0x1F, + 0x08, 0x1E, + 0x18, 0x3E, + 0x3C, 0x3E, + 0x3C, 0x39, + 0x98, 0x3A, + 0x48, 0x1D, + 0xA0, 0x1E, + 0x70, 0x0F, + 0xF8, 0x07, + 0xE0, 0x07, + 0xC0, 0x03, + 0xC0, 0x03, + 0xC0, 0x03, +}; +static constexpr Bitmap bitmap_icon_utilities { + { 16, 16 }, bitmap_icon_utilities_data +}; + static constexpr uint8_t bitmap_icon_capture_data[] = { 0xEF, 0x29, 0xEF, 0x69, @@ -933,19 +977,19 @@ static constexpr Bitmap bitmap_icon_replay { static constexpr uint8_t bitmap_icon_soundboard_data[] = { 0x00, 0x00, - 0xDE, 0x7B, + 0xFF, 0xFF, 0x63, 0x8C, 0x21, 0x84, 0x21, 0x84, - 0xDE, 0x7B, + 0xDF, 0xFB, 0x63, 0x8C, 0x21, 0x84, 0x21, 0x84, - 0xDE, 0x7B, + 0xDF, 0xFB, 0x63, 0x8C, 0x21, 0x84, 0x21, 0x84, - 0xDE, 0x7B, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, }; diff --git a/firmware/application/ctcss.cpp b/firmware/application/ctcss.cpp index e44a254c..58e8dd4a 100644 --- a/firmware/application/ctcss.cpp +++ b/firmware/application/ctcss.cpp @@ -21,7 +21,10 @@ */ #include "ctcss.hpp" +#include "string_format.hpp" +namespace ctcss { + const ctcss_tone ctcss_tones[CTCSS_TONES_NB] = { { "XZ", 0, 67.000 }, { "WZ", 1, 69.400 }, @@ -74,3 +77,23 @@ const ctcss_tone ctcss_tones[CTCSS_TONES_NB] = { { "--", 38, 250.300 }, { "0Z", 50, 254.100 } }; + +void ctcss_populate(OptionsField& field) { + using option_t = std::pair; + using options_t = std::vector; + options_t ctcss_options; + std::string f_string; + uint32_t c, f; + + ctcss_options.emplace_back(std::make_pair("None", 0)); + for (c = 0; c < CTCSS_TONES_NB; c++) { + f = (uint32_t)(ctcss_tones[c].frequency * 10); + f_string = ctcss_tones[c].PL_code; + f_string += " " + to_string_dec_uint(f / 10) + "." + to_string_dec_uint(f % 10); + ctcss_options.emplace_back(f_string, c); + } + + field.set_options(ctcss_options); +} + +} diff --git a/firmware/application/ctcss.hpp b/firmware/application/ctcss.hpp index 584e69b5..b5a47df9 100644 --- a/firmware/application/ctcss.hpp +++ b/firmware/application/ctcss.hpp @@ -24,9 +24,14 @@ #define __CTCSS_H_ #include "ui.hpp" +#include "ui_widget.hpp" + +using namespace ui; #define CTCSS_TONES_NB 50 +namespace ctcss { + struct ctcss_tone { char PL_code[3]; uint16_t num_code; @@ -35,4 +40,8 @@ struct ctcss_tone { extern const ctcss_tone ctcss_tones[CTCSS_TONES_NB]; +void ctcss_populate(OptionsField& field); + +} + #endif/*__CTCSS_H_*/ diff --git a/firmware/application/main.cpp b/firmware/application/main.cpp index 47140092..d68b25d3 100755 --- a/firmware/application/main.cpp +++ b/firmware/application/main.cpp @@ -24,11 +24,12 @@ // Gimp image > indexed colors (16), then "xxd -i *.bmp" //BUG: RDS Radiotext is not recognized in Redsea (and car radio) -//BUG: Check AFSK transmit end, skips last bits ? //BUG: RDS doesn't stop baseband when stopping tx ? +//BUG: Check AFSK transmit end, skips last bits ? //TEST: Imperial in whipcalc +//TODO: Roger beep in mic tx //TODO: Morse use prosigns //TODO: Morse live keying mode ? /* diff --git a/firmware/application/radio.cpp b/firmware/application/radio.cpp index b15de1db..2b55b3c5 100644 --- a/firmware/application/radio.cpp +++ b/firmware/application/radio.cpp @@ -141,6 +141,13 @@ bool set_tuning_frequency(const rf::Frequency frequency) { void set_rf_amp(const bool rf_amp) { rf_path.set_rf_amp(rf_amp); + + if (direction == rf::Direction::Transmit) { + if (rf_amp) + led_tx.on(); + else + led_tx.off(); + } } void set_lna_gain(const int_fast8_t db) { diff --git a/firmware/application/ui_adsbtx.cpp b/firmware/application/ui_adsbtx.cpp index b4381d2c..880c0916 100644 --- a/firmware/application/ui_adsbtx.cpp +++ b/firmware/application/ui_adsbtx.cpp @@ -153,7 +153,7 @@ ADSBTxView::ADSBTxView(NavigationView& nav) { field_lon_seconds.set_value(0); for (c = 0; c < 4; c++) - field_squawk.set_value(c, 0); + field_squawk.set_sym(c, 0); generate_frame(); diff --git a/firmware/application/ui_audiotx.cpp b/firmware/application/ui_audiotx.cpp index b4df39bd..8bbaeaef 100644 --- a/firmware/application/ui_audiotx.cpp +++ b/firmware/application/ui_audiotx.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek * * This file is part of PortaPack. * @@ -22,53 +23,102 @@ #include "ui_audiotx.hpp" #include "baseband_api.hpp" -#include "ui_alphanum.hpp" +#include "hackrf_gpio.hpp" #include "audio.hpp" #include "portapack.hpp" #include "pins.hpp" #include "string_format.hpp" +#include "irq_controls.hpp" #include "portapack_shared_memory.hpp" #include +using namespace ctcss; using namespace portapack; +using namespace hackrf::one; namespace ui { void AudioTXView::focus() { - button_transmit.focus(); + field_frequency.focus(); } -void AudioTXView::paint(Painter& painter) { - _painter = &painter; +void AudioTXView::update_vumeter() { + vumeter.set_value(audio_level); } -void AudioTXView::draw_vumeter() { - uint32_t bar; - Color color; - bool lit = true; - uint32_t bar_level = audio_level / 15; +void AudioTXView::set_tx(bool enable) { + uint32_t ctcss_index; + bool ctcss_enabled; - if (bar_level > 16) bar_level = 16; - - for (bar = 0; bar < 16; bar++) { - if (bar >= bar_level) - lit = false; + if (enable) { + ctcss_index = options_ctcss.selected_index(); - if (bar < 11) - color = lit ? Color::green() : Color::dark_green(); - else if ((bar >= 11) && (bar < 13)) - color = lit ? Color::yellow() : Color::dark_yellow(); - else if ((bar >= 13) && (bar < 15)) - color = lit ? Color::orange() : Color::dark_orange(); - else - color = lit ? Color::red() : Color::dark_red(); + if (ctcss_index) { + ctcss_enabled = true; + ctcss_index--; + } else + ctcss_enabled = false; - _painter->fill_rectangle({ 100, (Coord)(210 - (bar * 12)), 40, 10 }, color); + baseband::set_audiotx_data( + 1536000U / 20, // 20Hz level update + transmitter_model.bandwidth(), + mic_gain_x10, + ctcss_enabled, + (uint32_t)((ctcss_tones[ctcss_index].frequency / 1536000.0) * 0xFFFFFFFFULL) + ); + gpio_tx.write(1); + led_tx.on(); + transmitting = true; + } else { + baseband::set_audiotx_data( + 1536000U / 20, // 20Hz level update + 0, // BW 0 = TX off + mic_gain_x10, + false, // Ignore CTCSS + 0 + ); + gpio_tx.write(0); + led_tx.off(); + transmitting = false; + } +} + +void AudioTXView::do_timing() { + if (va_enabled) { + if (!transmitting) { + // Attack + if (audio_level >= va_level) { + if ((attack_timer >> 8) >= attack_ms) { + decay_timer = 0; + attack_timer = 0; + set_tx(true); + } else { + attack_timer += ((256 * 1000) / 60); // 1 frame @ 60fps in ms .8 fixed point + } + } else { + attack_timer = 0; + } + } else { + // Decay + if (audio_level < va_level) { + if ((decay_timer >> 8) >= decay_ms) { + decay_timer = 0; + attack_timer = 0; + set_tx(false); + } else { + decay_timer += ((256 * 1000) / 60); // 1 frame @ 60fps in ms .8 fixed point + } + } else { + decay_timer = 0; + } + } + } else { + // PTT disable :( + const auto switches_state = get_switches_state(); + if (!switches_state[1]) // Left button + set_tx(false); } - - //text_power.set(to_string_hex(LPC_I2S0->STATE, 8) + " " + to_string_dec_uint(audio_level) + " "); - text_power.set(to_string_dec_uint(audio_level) + " "); } void AudioTXView::on_tuning_frequency_changed(rf::Frequency f) { @@ -82,16 +132,31 @@ AudioTXView::AudioTXView( pins[P6_2].mode(3); // I2S0_RX_SDA ! baseband::run_image(portapack::spi_flash::image_tag_mic_tx); - - transmitter_model.set_tuning_frequency(92200000); add_children({ - &text_power, + &labels, + &vumeter, + &options_gain, + &check_va, + &field_va_level, + &field_va_attack, + &field_va_decay, + &field_bw, &field_frequency, - &button_transmit, + &options_ctcss, + //&check_rogerbeep, + &text_ptt, &button_exit }); + ctcss_populate(options_ctcss); + options_ctcss.set_selected_index(0); + + options_gain.on_change = [this](size_t, int32_t v) { + mic_gain_x10 = v; + }; + options_gain.set_selected_index(1); // x1.0 + field_frequency.set_value(transmitter_model.tuning_frequency()); field_frequency.set_step(receiver_model.frequency_step()); field_frequency.on_change = [this](rf::Frequency f) { @@ -106,26 +171,46 @@ AudioTXView::AudioTXView( }; }; - button_transmit.on_select = [](Button&){ - transmitter_model.set_sampling_rate(1536000U); - 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(); - - baseband::set_audiotx_data( - 76800, // 20Hz level update - 10000, // 10kHz bw - false, - 0 - ); + field_bw.on_change = [this](uint32_t v) { + transmitter_model.set_bandwidth(v * 1000); }; + field_bw.set_value(10); + + check_va.on_select = [this](Checkbox&, bool v) { + va_enabled = v; + text_ptt.hidden(v); + set_dirty(); + }; + check_va.set_value(false); + + field_va_level.on_change = [this](int32_t v) { + va_level = v; + vumeter.set_mark(v); + }; + field_va_level.set_value(40); + + field_va_attack.on_change = [this](int32_t v) { + attack_ms = v; + }; + field_va_attack.set_value(500); + + field_va_decay.on_change = [this](int32_t v) { + decay_ms = v; + }; + field_va_decay.set_value(2000); button_exit.on_select = [&nav](Button&){ nav.pop(); }; + // Run baseband as soon as the app starts to get audio levels without transmitting (rf amp off) + transmitter_model.set_sampling_rate(1536000U); + transmitter_model.set_rf_amp(true); + transmitter_model.set_baseband_bandwidth(1750000); + transmitter_model.enable(); + + set_tx(false); + audio::set_rate(audio::Rate::Hz_24000); audio::input::start(); } diff --git a/firmware/application/ui_audiotx.hpp b/firmware/application/ui_audiotx.hpp index 607481d6..119b99b3 100644 --- a/firmware/application/ui_audiotx.hpp +++ b/firmware/application/ui_audiotx.hpp @@ -29,9 +29,9 @@ #include "ui_navigation.hpp" #include "ui_font_fixed_8x16.hpp" #include "message.hpp" -#include "volume.hpp" #include "ui_receiver.hpp" #include "transmitter_model.hpp" +#include "ctcss.hpp" namespace ui { @@ -46,40 +46,130 @@ public: AudioTXView& operator=(AudioTXView&&) = delete; void focus() override; - void paint(Painter& painter) override; + + // PTT: Enable through KeyEvent (only works with presses), disable by polling :( + bool on_key(const KeyEvent key) { + if ((key == KeyEvent::Left) && (!va_enabled)) { + set_tx(true); + return true; + } else + return false; + }; std::string title() const override { return "Microphone TX"; }; private: - void draw_vumeter(); + void update_vumeter(); + void do_timing(); + void set_tx(bool enable); void on_tuning_frequency_changed(rf::Frequency f); + void on_ctcss_changed(uint32_t v); + bool transmitting { false }; + bool va_enabled { }; + uint32_t mic_gain_x10 { }; uint32_t audio_level { 0 }; - Painter * _painter { }; + uint32_t va_level { }; + uint32_t attack_ms { }; + uint32_t decay_ms { }; + uint32_t attack_timer { 0 }; + uint32_t decay_timer { 0 }; - Text text_power { - { 6 * 8, 28 * 8, 16 * 8, 16 }, - "-" + Labels labels { + { { 7 * 8, 1 * 8 }, "Mic. gain:", Color::light_grey() }, + { { 7 * 8, 4 * 8 }, "Voice activation:", Color::light_grey() }, + { { 8 * 8, 9 * 8 }, "Level: /255", Color::light_grey() }, + { { 8 * 8, 11 * 8 }, "Attack: ms", Color::light_grey() }, + { { 8 * 8, 13 * 8 }, "Decay: ms", Color::light_grey() }, + { { 7 * 8, 17 * 8 }, "Bandwidth: kHz", Color::light_grey() }, + { { 7 * 8, 19 * 8 }, "Frequency:", Color::light_grey() }, + { { 11 * 8, 21 * 8 }, "CTCSS:", Color::light_grey() } }; + VuMeter vumeter { + { 1 * 8, 2 * 8, 5 * 8, 26 * 8 }, + 16 + }; + + OptionsField options_gain { + { 17 * 8, 1 * 8 }, + 4, + { + { "x0.5", 5 }, + { "x1.0", 10 }, + { "x1.5", 15 }, + { "x2.0", 20 } + } + }; + + Checkbox check_va { + { 8 * 8, 6 * 8 }, + 7, + "Enabled", + false + }; + + NumberField field_va_level { + { 14 * 8, 9 * 8 }, + 3, + { 0, 255 }, + 2, + ' ' + }; + NumberField field_va_attack { + { 15 * 8, 11 * 8 }, + 3, + { 0, 999 }, + 20, + ' ' + }; + NumberField field_va_decay { + { 14 * 8, 13 * 8 }, + 4, + { 0, 9999 }, + 100, + ' ' + }; + + NumberField field_bw { + { 17 * 8, 17 * 8 }, + 3, + { 0, 150 }, + 1, + ' ' + }; FrequencyField field_frequency { - { 6 * 8, 30 * 8 }, + { 17 * 8, 19 * 8 }, }; - Button button_transmit { - { 1 * 8, 33 * 8, 12 * 8, 32 }, - "Transmit" + OptionsField options_ctcss { + { 17 * 8, 21 * 8 }, + 8, + { } + }; + + Checkbox check_rogerbeep { + { 8 * 8, 23 * 8 }, + 10, + "Roger beep", + false + }; + + Text text_ptt { + { 7 * 8, 28 * 8, 16 * 8, 16 }, + "PTT: LEFT BUTTON" }; Button button_exit { - { 16 * 8, 33 * 8, 12 * 8, 32 }, + { 18 * 8, 32 * 8, 10 * 8, 40 }, "Exit" }; MessageHandlerRegistration message_handler_lcd_sync { Message::ID::DisplayFrameSync, [this](const Message* const) { - this->draw_vumeter(); + this->update_vumeter(); + this->do_timing(); } }; diff --git a/firmware/application/ui_encoders.cpp b/firmware/application/ui_encoders.cpp index 650bab10..aa66a960 100644 --- a/firmware/application/ui_encoders.cpp +++ b/firmware/application/ui_encoders.cpp @@ -54,7 +54,7 @@ void EncodersView::generate_frame() { if (c == 'S') debug_text += encoder_def->sync; else - debug_text += encoder_def->bit_format[symfield_word.value(i++)]; + debug_text += encoder_def->bit_format[symfield_word.get_sym(i++)]; } draw_waveform(); diff --git a/firmware/application/ui_menu.cpp b/firmware/application/ui_menu.cpp index 5697763a..4168ad50 100644 --- a/firmware/application/ui_menu.cpp +++ b/firmware/application/ui_menu.cpp @@ -189,11 +189,11 @@ bool MenuView::set_highlighted(int32_t new_value) { if (new_value >= item_count) new_value = item_count - 1; - if ((new_value > offset_) && ((new_value - offset_) >= displayed_max_)) { + if (((uint32_t)new_value > offset_) && ((new_value - offset_) >= displayed_max_)) { // Shift MenuView up offset_ = new_value - displayed_max_ + 1; update_items(); - } else if (new_value < offset_) { + } else if ((uint32_t)new_value < offset_) { // Shift MenuView down offset_ = new_value; update_items(); diff --git a/firmware/application/ui_morse.cpp b/firmware/application/ui_morse.cpp index d3538ea6..bd1f8a35 100644 --- a/firmware/application/ui_morse.cpp +++ b/firmware/application/ui_morse.cpp @@ -102,8 +102,6 @@ bool MorseView::start_tx() { transmitter_model.set_sampling_rate(1536000U); 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(); diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index 43e94503..83b6b3b1 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -323,7 +323,7 @@ TransmitterAudioMenuView::TransmitterAudioMenuView(NavigationView& nav) { add_items<4>({ { { "Soundboard", ui::Color::green(), &bitmap_icon_soundboard, [&nav](){ nav.push(); } }, { "Numbers station", ui::Color::orange(),&bitmap_icon_numbers, [&nav](){ nav.push(); } }, - { "Microphone", ui::Color::orange(),&bitmap_icon_microphone, [&nav](){ nav.push(); } }, + { "Microphone", ui::Color::green(), &bitmap_icon_microphone, [&nav](){ nav.push(); } }, { "Whistle", ui::Color::yellow(),&bitmap_icon_whistle, [&nav](){ nav.push(); } }, } }); on_left = [&nav](){ nav.pop(); }; @@ -357,14 +357,14 @@ SystemMenuView::SystemMenuView(NavigationView& nav) { add_items<12>({ { { "Play dead", ui::Color::red(), &bitmap_icon_playdead, [&nav](){ nav.push(); } }, { "Receivers", ui::Color::cyan(), &bitmap_icon_receivers, [&nav](){ nav.push(); } }, - { "Capture", ui::Color::cyan(), &bitmap_icon_capture, [&nav](){ nav.push(); } }, //CaptureAppView + { "Capture", ui::Color::blue(), &bitmap_icon_capture, [&nav](){ nav.push(); } }, { "Replay", ui::Color::grey(), &bitmap_icon_replay, [&nav](){ nav.push(); } }, { "Code transmitters", ui::Color::green(), &bitmap_icon_codetx, [&nav](){ nav.push(); } }, { "Audio transmitters", ui::Color::green(), &bitmap_icon_audiotx, [&nav](){ nav.push(); } }, { "Close Call", ui::Color::orange(),&bitmap_icon_closecall, [&nav](){ nav.push(); } }, { "Jammer", ui::Color::orange(),&bitmap_icon_jammer, [&nav](){ nav.push(); } }, - { "Utilities", ui::Color::purple(),nullptr, [&nav](){ nav.push(); } }, - { "Setup", ui::Color::white(), nullptr, [&nav](){ nav.push(); } }, + { "Utilities", ui::Color::purple(),&bitmap_icon_utilities, [&nav](){ nav.push(); } }, + { "Setup", ui::Color::white(), &bitmap_icon_setup, [&nav](){ nav.push(); } }, //{ "Debug", ui::Color::white(), nullptr, [&nav](){ nav.push(); } }, { "HackRF mode", ui::Color::white(), &bitmap_icon_hackrf, [this, &nav](){ hackrf_mode(nav); } }, { "About", ui::Color::white(), nullptr, [&nav](){ nav.push(); } } diff --git a/firmware/application/ui_numbers.cpp b/firmware/application/ui_numbers.cpp index c16bd6d3..e6c9db77 100644 --- a/firmware/application/ui_numbers.cpp +++ b/firmware/application/ui_numbers.cpp @@ -66,7 +66,7 @@ void NumbersStationView::prepare_audio() { } if (segment == MESSAGE) { - code = symfield_code.value(code_index); + code = symfield_code.get_sym(code_index); if (code_index == 25) transmitter_model.disable(); @@ -131,6 +131,7 @@ void NumbersStationView::start_tx() { baseband::set_audiotx_data( (1536000 / 44100) - 1, number_bw.value(), + 1, false, 0 ); @@ -213,17 +214,17 @@ NumbersStationView::NumbersStationView( }; // DEBUG - symfield_code.set_value(0, 10); - symfield_code.set_value(1, 3); - symfield_code.set_value(2, 4); - symfield_code.set_value(3, 11); - symfield_code.set_value(4, 6); - symfield_code.set_value(5, 1); - symfield_code.set_value(6, 9); - symfield_code.set_value(7, 7); - symfield_code.set_value(8, 8); - symfield_code.set_value(9, 0); - symfield_code.set_value(10, 12); // End + symfield_code.set_sym(0, 10); + symfield_code.set_sym(1, 3); + symfield_code.set_sym(2, 4); + symfield_code.set_sym(3, 11); + symfield_code.set_sym(4, 6); + symfield_code.set_sym(5, 1); + symfield_code.set_sym(6, 9); + symfield_code.set_sym(7, 7); + symfield_code.set_sym(8, 8); + symfield_code.set_sym(9, 0); + symfield_code.set_sym(10, 12); // End for (c = 0; c < 25; c++) symfield_code.set_symbol_list(c, "0123456789pPE"); diff --git a/firmware/application/ui_pocsag_tx.cpp b/firmware/application/ui_pocsag_tx.cpp index 25c450bc..a13ea8a2 100644 --- a/firmware/application/ui_pocsag_tx.cpp +++ b/firmware/application/ui_pocsag_tx.cpp @@ -153,7 +153,7 @@ POCSAGTXView::POCSAGTXView( // TODO: set_value for whole symfield reload_address = portapack::persistent_memory::pocsag_address(); for (c = 0; c < 7; c++) { - field_address.set_value(6 - c, reload_address % 10); + field_address.set_sym(6 - c, reload_address % 10); reload_address /= 10; } diff --git a/firmware/application/ui_rds.cpp b/firmware/application/ui_rds.cpp index 4f646e8f..bf2cff84 100644 --- a/firmware/application/ui_rds.cpp +++ b/firmware/application/ui_rds.cpp @@ -132,10 +132,10 @@ RDSView::RDSView(NavigationView& nav) { check_TA.set_value(true); check_TP.set_value(true); - sym_pi_code.set_value(0, 0xF); - sym_pi_code.set_value(1, 0x3); - sym_pi_code.set_value(2, 0xE); - sym_pi_code.set_value(3, 0x0); + sym_pi_code.set_sym(0, 0xF); + sym_pi_code.set_sym(1, 0x3); + sym_pi_code.set_sym(2, 0xE); + sym_pi_code.set_sym(3, 0x0); sym_pi_code.on_change = [this]() { rds_flags.PI_code = sym_pi_code.value_hex_u64(); }; diff --git a/firmware/application/ui_soundboard.cpp b/firmware/application/ui_soundboard.cpp index adabeb8d..1729b161 100644 --- a/firmware/application/ui_soundboard.cpp +++ b/firmware/application/ui_soundboard.cpp @@ -33,6 +33,7 @@ #include #include +using namespace ctcss; using namespace portapack; namespace ui { @@ -137,6 +138,7 @@ void SoundBoardView::play_sound(uint16_t id) { baseband::set_audiotx_data( divider, number_bw.value() * 1000, + 1, ctcss_enabled, (uint32_t)((ctcss_tones[ctcss_index].frequency / 1536000.0) * 0xFFFFFFFFULL) ); @@ -193,13 +195,9 @@ SoundBoardView::SoundBoardView( NavigationView& nav ) : nav_ (nav) { - using option_t = std::pair; - using options_t = std::vector; - options_t ctcss_options; std::vector file_list; - std::string title, f_string; + std::string title; uint8_t c; - uint32_t f; reader = std::make_unique(); @@ -248,17 +246,7 @@ SoundBoardView::SoundBoardView( &button_exit }); - // Populate CTCSS list - ctcss_options.emplace_back(std::make_pair("None", 0)); - for (c = 0; c < CTCSS_TONES_NB; c++) { - f = (uint32_t)(ctcss_tones[c].frequency * 10); - f_string = ctcss_tones[c].PL_code; - f_string += " " + to_string_dec_uint(f / 10) + "." + to_string_dec_uint(f % 10); - ctcss_options.emplace_back(f_string, c); - } - - options_ctcss.set_options(ctcss_options); - + ctcss_populate(options_ctcss); options_ctcss.set_selected_index(0); const auto button_fn = [this](Button& button) { diff --git a/firmware/application/ui_transmitter.cpp b/firmware/application/ui_transmitter.cpp index f89b8004..d50d45dd 100644 --- a/firmware/application/ui_transmitter.cpp +++ b/firmware/application/ui_transmitter.cpp @@ -122,7 +122,7 @@ TransmitterView::TransmitterView( }); field_bw.on_change = [this](int32_t bandwidth) { - on_bandwidth_changed(bandwidth); + on_bandwidth_changed(bandwidth * 1000); }; field_bw.set_value(bandwidth); } diff --git a/firmware/baseband/audio_input.cpp b/firmware/baseband/audio_input.cpp index 4ebfc507..98032850 100644 --- a/firmware/baseband/audio_input.cpp +++ b/firmware/baseband/audio_input.cpp @@ -32,28 +32,9 @@ #include #include -void AudioInput::configure( - const iir_biquad_config_t& hpf_config, - const float squelch_threshold -) { - //hpf.configure(hpf_config); - //squelch.set_threshold(squelch_threshold); -} - void AudioInput::read_audio_buffer(buffer_s16_t& audio) { - //std::array audio_int; - auto audio_buffer = audio::dma::rx_empty_buffer(); - for(size_t i=0; iwrite(audio_int.data(), audio_buffer.count * sizeof(audio_int[0])); - }*/ - - //feed_audio_stats(audio); } diff --git a/firmware/baseband/audio_input.hpp b/firmware/baseband/audio_input.hpp index 963e09c3..4f3a4f11 100644 --- a/firmware/baseband/audio_input.hpp +++ b/firmware/baseband/audio_input.hpp @@ -35,27 +35,13 @@ class AudioInput { public: - void configure( - const iir_biquad_config_t& hpf_config, - const float squelch_threshold = 0.0f - ); - void read_audio_buffer(buffer_s16_t& audio); - /*void set_stream(std::unique_ptr new_stream) { - stream = std::move(new_stream); - }*/ - private: - static constexpr float k = 32768.0f; + /*static constexpr float k = 32768.0f; static constexpr float ki = 1.0f / k; - IIRBiquadFilter hpf { }; - //FMSquelch squelch { }; - - //std::unique_ptr stream { }; - - //AudioStatsCollector audio_stats { }; + IIRBiquadFilter hpf { };*/ }; #endif/*__AUDIO_INPUT_H__*/ diff --git a/firmware/baseband/proc_mictx.cpp b/firmware/baseband/proc_mictx.cpp index 1345a062..6aabfedd 100644 --- a/firmware/baseband/proc_mictx.cpp +++ b/firmware/baseband/proc_mictx.cpp @@ -37,13 +37,14 @@ void MicTXProcessor::execute(const buffer_c8_t& buffer){ for (size_t i = 0; i> 6] >> 8; + sample = audio_buffer.p[i >> 6] >> 8; // 1536000 / 64 = 24000 + sample = (sample * (int32_t)gain_x10) / 10; - power += (sample < 0) ? -sample : sample; + power += (sample < 0) ? -sample : sample; // Power mean for UI vu-meter if (!as) { as = divider; - level_message.value = power / (divider / 4); + level_message.value = power / (divider / 4); // Why ? shared_memory.application_queue.push(level_message); power = 0; } else { @@ -59,13 +60,18 @@ void MicTXProcessor::execute(const buffer_c8_t& buffer){ } // FM - delta = sample_mixed * fm_delta; - - phase += delta; - sphase = phase + (64 << 24); + if (fm_delta) { + delta = sample_mixed * fm_delta; + + phase += delta; + sphase = phase + (64 << 24); - re = (sine_table_i8[(sphase & 0xFF000000U) >> 24]); - im = (sine_table_i8[(phase & 0xFF000000U) >> 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}; } @@ -77,6 +83,7 @@ void MicTXProcessor::on_message(const Message* const msg) { switch(msg->id) { case Message::ID::AudioTXConfig: fm_delta = message.fm_delta * (0xFFFFFFULL / 1536000); + gain_x10 = message.gain_x10; divider = message.divider; ctcss_enabled = message.ctcss_enabled; ctcss_phase_inc = message.ctcss_phase_inc; diff --git a/firmware/baseband/proc_mictx.hpp b/firmware/baseband/proc_mictx.hpp index 99f59b35..c3a1a85c 100644 --- a/firmware/baseband/proc_mictx.hpp +++ b/firmware/baseband/proc_mictx.hpp @@ -46,7 +46,7 @@ private: AudioInput audio_input { }; - uint32_t divider { }; + uint32_t divider { }, gain_x10 { }; uint32_t as { 0 }; uint32_t fm_delta { 0 }; bool ctcss_enabled { false }; diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index 30b9c26a..c834cc01 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -645,11 +645,13 @@ public: constexpr AudioTXConfigMessage( const uint32_t divider, const uint32_t fm_delta, + const uint32_t gain_x10, const uint32_t ctcss_phase_inc, const bool ctcss_enabled ) : Message { ID::AudioTXConfig }, divider(divider), fm_delta(fm_delta), + gain_x10(gain_x10), ctcss_phase_inc(ctcss_phase_inc), ctcss_enabled(ctcss_enabled) { @@ -657,6 +659,7 @@ public: const uint32_t divider; const uint32_t fm_delta; + const uint32_t gain_x10; const uint32_t ctcss_phase_inc; const bool ctcss_enabled; }; diff --git a/firmware/common/ui_widget.cpp b/firmware/common/ui_widget.cpp index af2f87f7..7af99a44 100644 --- a/firmware/common/ui_widget.cpp +++ b/firmware/common/ui_widget.cpp @@ -376,20 +376,6 @@ void Labels::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 @@ -404,7 +390,7 @@ void BigFrequency::set(const rf::Frequency frequency) { } void BigFrequency::paint(Painter& painter) { - uint8_t i, digit_def; + uint32_t i, digit_def; char digits[7]; char digit; Coord digit_x, digit_y; @@ -443,7 +429,7 @@ void BigFrequency::paint(Painter& painter) { digit = digits[i]; digit_y = rect.location().y(); if (digit < 16) { - digit_def = big_segment_font[(uint8_t)digit]; + digit_def = segment_font[(uint8_t)digit]; if (digit_def & 0x01) painter.fill_rectangle({{digit_x + 4, digit_y}, {20, 4}}, segment_color); if (digit_def & 0x02) painter.fill_rectangle({{digit_x + 24, digit_y + 4}, {4, 20}}, segment_color); if (digit_def & 0x04) painter.fill_rectangle({{digit_x + 24, digit_y + 28}, {4, 20}}, segment_color); @@ -1236,13 +1222,13 @@ uint64_t SymField::value_hex_u64() { return 0; } -uint32_t SymField::value(const uint32_t index) { +uint32_t SymField::get_sym(const uint32_t index) { if (index >= length_) return 0; return values_[index]; } -void SymField::set_value(const uint32_t index, const uint32_t new_value) { +void SymField::set_sym(const uint32_t index, const uint32_t new_value) { if (index >= length_) return; uint32_t clipped_value = clip_value(index, new_value); @@ -1263,7 +1249,7 @@ void SymField::set_length(const uint32_t new_length) { // Clip eventual garbage from previous shorter word for (size_t n = 0; n < length_; n++) - set_value(n, values_[n]); + set_sym(n, values_[n]); erase_prev_ = true; set_dirty(); @@ -1276,7 +1262,7 @@ void SymField::set_symbol_list(const uint32_t index, const std::string symbol_li symbol_list_[index] = symbol_list; // Re-clip symbol's value - set_value(index, values_[index]); + set_sym(index, values_[index]); } void SymField::paint(Painter& painter) { @@ -1338,7 +1324,7 @@ bool SymField::on_encoder(const EncoderEvent delta) { int32_t new_value = (int)values_[selected_] + delta; if (new_value >= 0) - set_value(selected_, values_[selected_] + delta); + set_sym(selected_, values_[selected_] + delta); return true; } @@ -1419,10 +1405,10 @@ void Waveform::paint(Painter& painter) { if (n) { if (y != prev_y) - painter.draw_vline( {x, y_offset}, h, color_); + painter.draw_vline( {(Coord)x, y_offset}, h, color_); } - painter.draw_hline( {x, y_offset + y}, ceil(x_inc), color_); + painter.draw_hline( {(Coord)x, y_offset + y}, ceil(x_inc), color_); prev_y = y; x += x_inc; @@ -1443,4 +1429,89 @@ void Waveform::paint(Painter& painter) { } } + +/* VuMeter **************************************************************/ + +VuMeter::VuMeter( + Rect parent_rect, + uint32_t LEDs +) : Widget { parent_rect }, + LEDs_ { LEDs }, + height { parent_rect.size().height() } +{ + //set_focusable(false); + LED_height = height / LEDs; + split = 256 / LEDs; +} + +void VuMeter::set_value(const uint8_t new_value) { + value_ = new_value; + set_dirty(); +} + +void VuMeter::set_mark(const uint8_t new_mark) { + if (new_mark != mark) { + mark = new_mark; + set_dirty(); + } +} + +void VuMeter::paint(Painter& painter) { + Point pos = screen_rect().location(); + Dim width = screen_rect().size().width() - 4; + + if (value_ != prev_value) { + uint32_t bar; + Color color; + bool lit = false; + uint32_t bar_level = LEDs_ - ((value_ + 1) / split); + // Draw LEDs + for (bar = 0; bar < LEDs_; bar++) { + if (bar >= bar_level) + lit = true; + + if (bar == 0) + color = lit ? Color::red() : Color::dark_red(); + else if (bar == 1) + color = lit ? Color::orange() : Color::dark_orange(); + else if ((bar == 2) || (bar == 3)) + color = lit ? Color::yellow() : Color::dark_yellow(); + else + color = lit ? Color::green() : Color::dark_green(); + + painter.fill_rectangle({ pos.x(), pos.y() + (Coord)(bar * LED_height), width, (Coord)LED_height - 2 }, color); + } + prev_value = value_; + } + + // Update max level + if (value_ > max) { + max = value_; + hold_timer = 30; // 0.5s @ 60Hz + } else { + if (hold_timer) { + hold_timer--; + } else { + if (max) max--; // Let it drop + } + } + + // Draw max level + if (max != prev_max) { + painter.draw_hline({ pos.x() + width, pos.y() + height - (height * prev_max) / 256 }, 8, Color::black()); + painter.draw_hline({ pos.x() + width, pos.y() + height - (height * max) / 256 }, 8, Color::white()); + if (prev_max == mark) + prev_mark = 0; // Force mark refresh + } + + // Draw mark + if ((mark != prev_mark) && (mark)) { + painter.draw_hline({ pos.x() + width, pos.y() + height - (height * prev_mark) / 256 }, 8, Color::black()); + painter.draw_hline({ pos.x() + width, pos.y() + height - (height * mark) / 256 }, 8, Color::grey()); + } + + prev_max = max; + prev_mark = mark; +} + } /* namespace ui */ diff --git a/firmware/common/ui_widget.hpp b/firmware/common/ui_widget.hpp index 74653972..fffc525e 100644 --- a/firmware/common/ui_widget.hpp +++ b/firmware/common/ui_widget.hpp @@ -245,6 +245,20 @@ public: private: rf::Frequency _frequency; + + const uint8_t 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 + }; }; class ProgressBar : public Widget { @@ -511,8 +525,8 @@ public: SymField(const SymField&) = delete; SymField(SymField&&) = delete; - uint32_t value(const uint32_t index); - void set_value(const uint32_t index, const uint32_t new_value); + uint32_t get_sym(const uint32_t index); + void set_sym(const uint32_t index, const uint32_t new_value); void set_length(const uint32_t new_length); void set_symbol_list(const uint32_t index, const std::string symbol_list); uint32_t value_dec_u32(); @@ -558,6 +572,24 @@ private: Color color_; }; +class VuMeter : public Widget { +public: + + VuMeter(Rect parent_rect, uint32_t LEDs); + + void set_value(const uint8_t new_value); + void set_mark(const uint8_t new_mark); + + void paint(Painter& painter) override; + +private: + uint32_t LEDs_, LED_height { 0 }; + uint32_t value_ { 0 }, prev_value { 0 }; + uint32_t split { 0 }; + uint16_t max { 0 }, prev_max { 0 }, hold_timer { 0 }, mark { 0 }, prev_mark { 0 }; + int height; +}; + } /* namespace ui */ #endif/*__UI_WIDGET_H__*/ diff --git a/firmware/graphics/icon_microphone.png b/firmware/graphics/icon_microphone.png index c3b2fd0a..9ec6e0f4 100644 Binary files a/firmware/graphics/icon_microphone.png and b/firmware/graphics/icon_microphone.png differ diff --git a/firmware/graphics/icon_remote.png b/firmware/graphics/icon_remote.png index aeb8ddf5..f838f174 100644 Binary files a/firmware/graphics/icon_remote.png and b/firmware/graphics/icon_remote.png differ diff --git a/firmware/graphics/icon_setup.png b/firmware/graphics/icon_setup.png new file mode 100644 index 00000000..5c4a388e Binary files /dev/null and b/firmware/graphics/icon_setup.png differ diff --git a/firmware/graphics/icon_soundboard.png b/firmware/graphics/icon_soundboard.png index c732b776..b304ee74 100644 Binary files a/firmware/graphics/icon_soundboard.png and b/firmware/graphics/icon_soundboard.png differ diff --git a/firmware/graphics/icon_utilities.png b/firmware/graphics/icon_utilities.png new file mode 100644 index 00000000..46423f6b Binary files /dev/null and b/firmware/graphics/icon_utilities.png differ diff --git a/firmware/portapack-h1-havoc.bin b/firmware/portapack-h1-havoc.bin index c6ee165a..3fc5051e 100644 Binary files a/firmware/portapack-h1-havoc.bin and b/firmware/portapack-h1-havoc.bin differ