portapack-mayhem/firmware/application/apps/ui_mictx.cpp
2022-10-16 19:01:05 +02:00

634 lines
21 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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_mictx.hpp"
#include "baseband_api.hpp"
#include "audio.hpp"
#include "wm8731.hpp"
using wolfson::wm8731::WM8731;
#include "tonesets.hpp"
#include "portapack_hal.hpp"
#include "cpld_update.hpp"
#include "string_format.hpp"
#include "irq_controls.hpp"
#include <cstring>
using namespace tonekey;
using namespace portapack;
WM8731 audio_codec_wm8731 { i2c0, 0x1a };
namespace ui {
void MicTXView::focus() {
switch(focused_ui) {
case 0:
field_frequency.focus();
break;
case 1:
field_rxfrequency.focus();
break;
default:
field_va.focus();
break;
}
}
void MicTXView::update_vumeter() {
vumeter.set_value(audio_level);
}
void MicTXView::on_tx_progress(const bool done) {
// Roger beep played, stop transmitting
if (done)
set_tx(false);
}
void MicTXView::configure_baseband() {
baseband::set_audiotx_config(
sampling_rate / 20, // Update vu-meter at 20Hz
transmitting ? transmitter_model.channel_bandwidth() : 0,
mic_gain,
shift_bits_s16, // to be used in dsp_modulate
TONES_F2D(tone_key_frequency(tone_key_index), sampling_rate),
enable_am,
enable_dsb,
enable_usb,
enable_lsb
);
}
void MicTXView::set_tx(bool enable) {
if (enable) {
if (rx_enabled) //If audio RX is enabled
rxaudio(false); //Then turn off audio RX
transmitting = true;
configure_baseband();
transmitter_model.set_tuning_frequency(tx_frequency);
transmitter_model.set_tx_gain(tx_gain);
transmitter_model.set_rf_amp(rf_amp);
transmitter_model.enable();
portapack::pin_i2s0_rx_sda.mode(3); // This is already done in audio::init but gets changed by the CPLD overlay reprogramming
} else {
if (transmitting && rogerbeep_enabled) {
baseband::request_beep(); //Transmit the roger beep
transmitting = false; //And flag the end of the transmission so ...
} else { // (if roger beep was enabled, this will be executed after the beep ends transmitting.
transmitting = false;
configure_baseband();
transmitter_model.disable();
if (rx_enabled) //If audio RX is enabled and we've been transmitting
rxaudio(true); //Turn back on audio RX
}
}
}
void MicTXView::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 += lcd_frame_duration;
}
} 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 += lcd_frame_duration;
}
} else {
decay_timer = 0;
}
}
} else {
// Check for PTT release
const auto switches_state = get_switches_state();
if (!switches_state[4] && transmitting && !button_touch) // Select button
set_tx(false);
}
}
/* Hmmmm. Maybe useless now.
void MicTXView::on_tuning_frequency_changed(rf::Frequency f) {
transmitter_model.set_tuning_frequency(f);
//if ( rx_enabled )
receiver_model.set_tuning_frequency(f); //Update freq also for RX
}
*/
void MicTXView::rxaudio(bool is_on) {
if (is_on) {
audio::input::stop();
baseband::shutdown();
if (enable_am || enable_usb || enable_lsb || enable_dsb) {
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
receiver_model.set_modulation(ReceiverModel::Mode::AMAudio);
if (options_mode.selected_index() < 5) // We will called here, 2,3,4,5 , and we are excluding DSB case (5) , "NFM/FM",0 ," WFM ",1 , " AM ",2, " USB ", 3, " LSB ",4, " DSB ", 5
receiver_model.set_am_configuration(options_mode.selected_index() - 2); // selecting proper filter. 2-2=0=>6k(0) , 3-2=1=>usb(1), 4-2=2lsb(2),
else
receiver_model.set_am_configuration(0); // DSB case (5) , same BW as AM config(5)
}
else { // We are in NFM/FM or WFM (NFM BW:8k5 or 11k / FM BW 16k / WFM BW:200k)
if (enable_wfm) { // WFM , BW 200Khz aprox ,
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
receiver_model.set_wfm_configuration(0); // there are only 1 x config filters 200k WFM . (not like 8k5/11k/16k)
} else { // NFM BW:8k5 or 11k / FM BW 16k
baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio); //
// receiver_model.set_nbfm_configuration(n); is called above , depending 8k5, 11k, 16k
}
}
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
// receiver_model.set_tuning_frequency(field_frequency.value()); //probably this too can be commented out.
receiver_model.set_tuning_frequency(rx_frequency); // Now with seperate controls!
receiver_model.set_lna(rx_lna);
receiver_model.set_vga(rx_vga);
receiver_model.set_rf_amp(rx_amp);
receiver_model.enable();
hackrf::cpld::load_sram_no_verify(); // to have a good RX without any ghost inside Mic App
audio::output::start();
} else { //These incredibly convoluted steps are required for the vumeter to reappear when stopping RX.
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio); //This fixes something with AM RX...
receiver_model.disable();
baseband::shutdown();
baseband::run_image(portapack::spi_flash::image_tag_mic_tx);
audio::output::stop();
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // When detected AK4951 => set up ALC mode; when detected WM8731 => set up mic_boost ON/OFF.
portapack::pin_i2s0_rx_sda.mode(3);
configure_baseband();
}
}
void MicTXView::on_headphone_volume_changed(int32_t v) {
//if (rx_enabled) {
const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
receiver_model.set_headphone_volume(new_volume);
//}
}
void MicTXView::set_ptt_visibility(bool v) {
tx_button.hidden(!v);
}
MicTXView::MicTXView(
NavigationView& nav
)
{
portapack::pin_i2s0_rx_sda.mode(3); // This is already done in audio::init but gets changed by the CPLD overlay reprogramming
baseband::run_image(portapack::spi_flash::image_tag_mic_tx);
if (audio::debug::codec_name() =="WM8731" ) {
add_children({
&labels_WM8731, // we have audio codec WM8731, same MIC menu as original.
&vumeter,
&options_gain, // MIC GAIN float factor on the GUI.
&options_wm8731_boost_mode,
// &check_va,
&field_va,
&field_va_level,
&field_va_attack,
&field_va_decay,
&field_bw,
&field_rfgain,
&field_rfamp,
&options_mode,
&field_frequency,
&options_tone_key,
&check_rogerbeep,
&check_rxactive,
&field_volume,
&field_rxbw,
&field_squelch,
&field_rxfrequency,
&field_rxlna,
&field_rxvga,
&field_rxamp,
&tx_button
});
} else {
add_children({
&labels_AK4951, // we have audio codec AK4951, enable Automatic Level Control
&vumeter,
&options_gain,
&options_ak4951_alc_mode,
// &check_va,
&field_va,
&field_va_level,
&field_va_attack,
&field_va_decay,
&field_bw,
&field_rfgain,
&field_rfamp,
&options_mode,
&field_frequency,
&options_tone_key,
&check_rogerbeep,
&check_rxactive,
&field_volume,
&field_rxbw,
&field_squelch,
&field_rxfrequency,
&field_rxlna,
&field_rxvga,
&field_rxamp,
&tx_button
});
}
tone_keys_populate(options_tone_key);
options_tone_key.on_change = [this](size_t i, int32_t) {
tone_key_index = i;
};
options_tone_key.set_selected_index(0);
options_gain.on_change = [this](size_t, int32_t v) {
mic_gain = v / 10.0;
configure_baseband();
};
options_gain.set_selected_index(1); // x1.0 preselected default.
if (audio::debug::codec_name() =="WM8731") {
options_wm8731_boost_mode.on_change = [this](size_t, int8_t v) {
switch(v) {
case 0: // +12 dBs respect reference level orig fw 1.5.x fw FM : when +20dB's boost ON) and shift bits (>>8),
shift_bits_s16 = 6; // now mic-boost on (+20dBs) and shift bits (>>6), +20+12=32 dBs (orig fw +20 dBs+ 0dBs)=> +12dB's respect ref.
break;
case 1: // +06 dBs reference level , (when +20dB's boost ON)
shift_bits_s16 = 7; // now mic-boost on (+20dBs) and shift bits (>>7), +20+06=26 dBs (orig fw +20 dBs+ 0dBs) => +06dB's respect ref.
break;
case 2:
shift_bits_s16 = 4; // +04 dBs respect ref level , (when +20dB's boost OFF)
break; // now mic-boost off (+00dBs) shift bits (4) (+0+24dB's)=24 dBs => +04dB's respect ref.
case 3:
shift_bits_s16 = 5; // -02 dBs respect ref level , (when +20dB's boost OFF)
break; // now mic-boost off (+00dBs) shift bits (5) (+0+18dB's)=18 dBs => -02dB's respect ref.
case 4:
shift_bits_s16 = 6; // -08 dBs respect ref level , (when +20dB's boost OFF)
break; // now mic-boost off (+00dBs) shift bits (6) (+0+12dB's)=12 dBs => -08dB's respect ref.
}
ak4951_alc_and_wm8731_boost_GUI = v; // 0,..4 WM8731_boost dB's options, (combination boost on/off , and effective gain in captured data >>x)
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // Detected (WM8731) , set up the proper wm_boost on/off , 0..4 (0,1) boost_on , (2,3,4) boost_0ff
configure_baseband(); // to update in real timme,sending msg , var-parameters >>shift_bits FM msg ,to audio_tx from M0 to M4 Proc -
};
options_wm8731_boost_mode.set_selected_index(3); // preset GUI index 3 as default WM -> -02 dB's .
} else {
shift_bits_s16 = 8; // Initialized default fixed >>8_FM for FM tx mod , shift audio data for AK4951 ,using top 8 bits s16 data (>>8)
options_ak4951_alc_mode.on_change = [this](size_t, int8_t v) {
ak4951_alc_and_wm8731_boost_GUI = v; // 0,..11, AK4951 Mic -Automatic volume Level Control options,
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // Detected (AK4951) ==> Set up proper ALC mode from 0..11 options
configure_baseband(); // sending fixed >>8_FM , var-parameters msg , to audiotx from this M0 to M4 process.
};
}
// options_ak4951_alc_mode.set_selected_index(0);
tx_frequency = transmitter_model.tuning_frequency();
field_frequency.set_value(transmitter_model.tuning_frequency());
field_frequency.set_step(receiver_model.frequency_step());
field_frequency.on_change = [this](rf::Frequency f) {
tx_frequency = f;
if(!rx_enabled)
transmitter_model.set_tuning_frequency(f);
};
field_frequency.on_edit = [this, &nav]() {
focused_ui = 0;
// TODO: Provide separate modal method/scheme?
auto new_view = nav.push<FrequencyKeypadView>(tx_frequency);
new_view->on_changed = [this](rf::Frequency f) {
tx_frequency = f;
if(!rx_enabled)
transmitter_model.set_tuning_frequency(f);
this->field_frequency.set_value(f);
set_dirty();
};
};
field_bw.on_change = [this](uint32_t v) {
transmitter_model.set_channel_bandwidth(v * 1000);
};
field_bw.set_value(10); // pre-default first time, TX deviation FM for NFM / FM
tx_gain = transmitter_model.tx_gain();
field_rfgain.on_change = [this](int32_t v) {
tx_gain = v;
};
field_rfgain.set_value(tx_gain);
rf_amp = transmitter_model.rf_amp();
field_rfamp.on_change = [this](int32_t v) {
rf_amp = (bool)v;
};
field_rfamp.set_value(rf_amp ? 14 : 0);
options_mode.on_change = [this](size_t, int32_t v) { //{ "FM", 0 },{ "AM", 1 },{ "USB", 2 },{ "LSB", 3 },{ "DSB", 4 }
enable_am = false;
enable_usb = false;
enable_lsb = false;
enable_dsb = false;
enable_wfm = false;
using option_t = std::pair<std::string, int32_t>;
using options_t = std::vector<option_t>;
options_t rxbw; // Aux structure to change dynamically field_rxbw contents,
switch(v) {
case 0: //{ "FM", 0 }
enable_am = false;
enable_usb = false;
enable_lsb = false;
enable_dsb = false;
field_bw.set_value(10); // pre-default deviation FM for WFM
// field_bw.set_value(transmitter_model.channel_bandwidth() / 1000);
//if (rx_enabled)
rxaudio(rx_enabled); //Update now if we have RX audio on
options_tone_key.hidden(0); // we are in FM mode , we should have active the Key-tones & CTCSS option.
rxbw.emplace_back("8k5-NFM ", 0); // restore the original dynamic field_rxbw value.
rxbw.emplace_back("11k-NFM ", 1);
rxbw.emplace_back("16k-FM ", 2);
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
field_rxbw.hidden(0); // we are in FM mode, we need to allow the user set up of the RX NFM BW selection (8K5, 11K, 16K)
field_bw.hidden(0); // we are in FM mode, we need to allow FM deviation parameter , in non FM mode.
break;
case 1: //{ "WFM", 1 }
enable_am = false;
enable_usb = false;
enable_lsb = false;
enable_dsb = false;
enable_wfm = true;
field_bw.set_value(75); // pre-default deviation FM for WFM
// field_bw.set_value(transmitter_model.channel_bandwidth() / 1000);
//if (rx_enabled)
rxaudio(rx_enabled); //Update now if we have RX audio on
options_tone_key.hidden(0); // we are in WFM mode , we should have active the Key-tones & CTCSS option.
rxbw.emplace_back("200K-WFM", 0); // locked a fixed option , to display it .
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
field_rxbw.hidden(0); // we are in WFM mode, we need to show to the user the selected BW WFM filer .
field_bw.hidden(0); // we are in WFM mode, we need to allow WFM deviation parameter , in non FM mode.
break;
case 2: //{ "AM", 2 }
enable_am = true;
rxaudio(rx_enabled); //Update now if we have RX audio on
options_tone_key.set_selected_index(0); // we are NOT in FM mode , we reset the possible previous key-tones &CTCSS selection.
set_dirty(); // Refresh display
options_tone_key.hidden(1); // we hide that Key-tones & CTCSS input selecction, (no meaning in AM/DSB/SSB).
field_rxbw.hidden(1); // we hide the NFM BW selection in other modes non_FM (no meaning in AM/DSB/SSB)
field_bw.hidden(1); // we hide the FM deviation parameter , in non FM mode.
check_rogerbeep.hidden(0); // make visible again the "rogerbeep" selection.
break;
case 3: //{ "USB", 3 }
enable_usb = true;
rxaudio(rx_enabled); //Update now if we have RX audio on
check_rogerbeep.set_value(false); // reset the possible activation of roger beep, because it is not compatible with SSB , by now.
check_rogerbeep.hidden(1); // hide that roger beep selection.
set_dirty(); // Refresh display
break;
case 4: //{ "LSB", 4 }
enable_lsb = true;
rxaudio(rx_enabled); //Update now if we have RX audio on
check_rogerbeep.set_value(false); // reset the possible activation of roger beep, because it is not compatible with SSB , by now.
check_rogerbeep.hidden(1); // hide that roger beep selection.
set_dirty(); // Refresh display
break;
case 5: //{ "DSB", 5 }
enable_dsb = true;
rxaudio(rx_enabled); //Update now if we have RX audio on
check_rogerbeep.hidden(0); // make visible again the "rogerbeep" selection.
break;
}
//configure_baseband();
};
/*
check_va.on_select = [this](Checkbox&, bool v) {
va_enabled = v;
text_ptt.hidden(v); //hide / show PTT text
check_rxactive.hidden(v); //hide / show the RX AUDIO
set_dirty(); //Refresh display
};
*/
field_va.set_selected_index(1);
field_va.on_change = [this](size_t, int32_t v) {
switch(v) {
case 0:
va_enabled = 0;
this->set_ptt_visibility(0);
check_rxactive.hidden(0);
ptt_enabled = 0;
break;
case 1:
va_enabled = 0;
this->set_ptt_visibility(1);
check_rxactive.hidden(0);
ptt_enabled = 1;
break;
case 2:
if (!rx_enabled) {
va_enabled = 1;
this->set_ptt_visibility(0);
check_rxactive.hidden(1);
ptt_enabled = 0;
} else {
field_va.set_selected_index(1);
}
break;
}
set_dirty();
};
check_rogerbeep.on_select = [this](Checkbox&, bool v) {
rogerbeep_enabled = v;
};
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(1000);
check_rxactive.on_select = [this](Checkbox&, bool v) {
// vumeter.set_value(0); //Start with a clean vumeter
rx_enabled = v;
// check_va.hidden(v); //Hide or show voice activation
rxaudio(v); //Activate-Deactivate audio rx accordingly
set_dirty(); //Refresh interface
};
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
field_volume.on_change = [this](int32_t v) { this->on_headphone_volume_changed(v); };
// In Previous fw versions, that nbfm_configuration(n) was done in any mode (FM/AM/SSB/DSB)...strictly speaking only need it in NFM-FM .
if (!(enable_am || enable_usb || enable_lsb || enable_dsb || enable_wfm )) {
//we are in NFM/FM case, here it has meaning to set the selected NFM/FM mode.
field_rxbw.on_change = [this](size_t, int32_t v) {
switch(v) {
case 0:
receiver_model.set_nbfm_configuration(0); // NFM BW 8K5
break;
case 1:
receiver_model.set_nbfm_configuration(1); // NFM BW 11K
break;
case 2:
receiver_model.set_nbfm_configuration(2); // FM BW 16K
break;
}
};
field_rxbw.set_selected_index(2); // preselected FM BW 16K
}
field_squelch.on_change = [this](int32_t v) {
receiver_model.set_squelch_level(100 - v);
};
field_squelch.set_value(0);
receiver_model.set_squelch_level(0);
rx_frequency = receiver_model.tuning_frequency();
field_rxfrequency.set_value(rx_frequency);
field_rxfrequency.set_step(receiver_model.frequency_step());
field_rxfrequency.on_change = [this](rf::Frequency f) {
rx_frequency = f;
if(rx_enabled)
receiver_model.set_tuning_frequency(f);
};
field_rxfrequency.on_edit = [this, &nav]() {
focused_ui = 1;
// TODO: Provide separate modal method/scheme?
auto new_view = nav.push<FrequencyKeypadView>(rx_frequency);
new_view->on_changed = [this](rf::Frequency f) {
rx_frequency = f;
if(rx_enabled)
receiver_model.set_tuning_frequency(f);
this->field_rxfrequency.set_value(f);
set_dirty();
};
};
rx_lna = receiver_model.lna();
field_rxlna.on_change = [this](int32_t v) {
rx_lna = v;
if(rx_enabled)
receiver_model.set_lna(v);
};
field_rxlna.set_value(rx_lna);
rx_vga = receiver_model.vga();
field_rxvga.on_change = [this](int32_t v) {
rx_vga = v;
if(rx_enabled)
receiver_model.set_vga(v);
};
field_rxvga.set_value(rx_vga);
rx_amp = receiver_model.rf_amp();
field_rxamp.on_change = [this](int32_t v) {
rx_amp = v;
if(rx_enabled)
receiver_model.set_rf_amp(rx_amp);
};
field_rxamp.set_value(rx_amp);
tx_button.on_select = [this](Button&) {
if(ptt_enabled && !transmitting) {
set_tx(true);
}
};
tx_button.on_touch_release = [this](Button&) {
if(button_touch) {
button_touch = false;
set_tx(false);
}
};
tx_button.on_touch_press = [this](Button&) {
if(!transmitting) {
button_touch = true;
}
};
transmitter_model.set_sampling_rate(sampling_rate);
transmitter_model.set_baseband_bandwidth(1750000);
set_tx(false);
audio::set_rate(audio::Rate::Hz_24000);
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // When detected AK4951 => set up ALC mode; when detected WM8731 => set up mic_boost ON/OFF.
}
MicTXView::~MicTXView() {
audio::input::stop();
transmitter_model.set_tuning_frequency(tx_frequency); // Save Tx frequency instead of Rx. Or maybe we need some "System Wide" changes to seperate Tx and Rx frequency.
transmitter_model.disable();
if (rx_enabled) //Also turn off audio rx if enabled
rxaudio(false);
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit .
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
}
}