diff --git a/AMDemodulator.cpp b/AMDemodulator.cpp index 726faaaf..acd59481 100644 --- a/AMDemodulator.cpp +++ b/AMDemodulator.cpp @@ -3,6 +3,7 @@ #include #include "PeakLevelDetector.h" + static shared_ptr sp_amdemod; std::mutex amdemod_mutex; @@ -81,9 +82,9 @@ void AMDemodulator::operator()() const auto startTime = std::chrono::high_resolution_clock::now(); auto timeLastPrint = std::chrono::high_resolution_clock::now(); std::chrono::high_resolution_clock::time_point now, start1; - + AudioProcessor Agc; - + int ifilter {-1}, span, rcount {0}, dropped_frames {0}; SampleVector audiosamples, audioframes; unique_lock lock_am(amdemod_mutex); @@ -91,6 +92,10 @@ void AMDemodulator::operator()() long long pr_time{0}; int vsize, passes{0}; + pNoisesp = make_unique(m_pcmrate, tuple(0, 2500)); + //pLMS = make_unique(); switched off memory leak in library + pXanr = make_unique(); + Agc.prepareToPlay(audio_output->get_samplerate()); Agc.setThresholdDB(gagc.get_threshold()); Agc.setRatio(10); @@ -114,7 +119,7 @@ void AMDemodulator::operator()() set_filter(m_pcmrate, ifilter); } - IQSampleVector iqsamples = m_source_buffer->pull(); + iqsamples = m_source_buffer->pull(); if (iqsamples.empty()) { printf("No samples queued 2\n"); @@ -148,9 +153,25 @@ void AMDemodulator::operator()() { if ((audio_output->queued_samples() / 2) < 4096) { - SampleVector audio_stereo; + SampleVector audio_stereo, audio_noise; - mono_to_left_right(audioframes, audio_stereo); + switch (gbar.get_noise()) + { + case 1: + pXanr->Process(audioframes, audio_noise); + break; + case 2: + pNoisesp->Process(audioframes, audio_noise); + mono_to_left_right(audio_noise, audio_stereo); + break; + case 3: + pNoisesp->Process_Kim1_NR(audioframes, audio_noise); + mono_to_left_right(audio_noise, audio_stereo); + break; + default: + mono_to_left_right(audioframes, audio_stereo); + break; + } audio_output->write(audio_stereo); audioframes.clear(); } @@ -212,6 +233,7 @@ void AMDemodulator::process(const IQSampleVector& samples_in, SampleVector& audi calc_if_level(filter1); if (gsetup.get_cw()) pMDecoder->decode(filter1); + for (auto col : filter1) { float v; diff --git a/AMDemodulator.h b/AMDemodulator.h index b5619abb..9c84d43f 100644 --- a/AMDemodulator.h +++ b/AMDemodulator.h @@ -9,6 +9,8 @@ #include "AudioOutput.h" #include "Demodulator.h" #include "MorseDecoder.h" +#include "LMSNoisereducer.h" +#include "SpectralNoiseReduction.h" class AMDemodulator : public Demodulator { @@ -32,6 +34,9 @@ class AMDemodulator : public Demodulator Agc_class agc; int m_iagc = 0; unique_ptr pMDecoder; + unique_ptr pLMS; + unique_ptr pXanr; + unique_ptr pNoisesp; }; void select_filter(int ifilter); \ No newline at end of file diff --git a/AMModulator.cpp b/AMModulator.cpp index d50f3d3a..db31bd2e 100644 --- a/AMModulator.cpp +++ b/AMModulator.cpp @@ -153,7 +153,7 @@ void AMModulator::process(const IQSampleVector& samples_in, SampleVector& sample { complex f; ampmodem_modulate(modAM, col, &f); - //printf("%f;%f;%f \n", col, f.real(), f.imag()); + //printf("audio %f;I %f;Q %f \n", col, f.real(), f.imag()); buf_mod.push_back(f); } double if_rms = rms_level_approx(buf_mod); @@ -169,9 +169,10 @@ void AMModulator::process(const IQSampleVector& samples_in, SampleVector& sample complex f; int16_t i, q; - i = (int16_t)round(col.real() * 16384.0f); - q = (int16_t)round(col.imag() * 16384.0f); + i = (int16_t)round(col.real() * 16384.0f) *10; + q = (int16_t)round(col.imag() * 16384.0f) *10; IQSample16 s16 {i, q}; + //printf("i %d q %d\n",i, q); buf_out16.push_back(s16); } mix_up_fft(buf_filter, buf_mod); diff --git a/AudioInput.cpp b/AudioInput.cpp index eae2fb40..c57a2afb 100644 --- a/AudioInput.cpp +++ b/AudioInput.cpp @@ -144,7 +144,7 @@ bool AudioInput::open(unsigned int device) void AudioInput::set_volume(int vol) { // log volume - m_volume = exp(((double)vol * 6.908) / 100.0) /10.0; + m_volume = exp(((double)vol * 6.908) / 100.0) / 5.0; printf("mic vol %f\n", (float)m_volume); } @@ -188,13 +188,13 @@ double AudioInput::Nexttone() { double angle = (asteps*cw_keyer_sidetone_frequency)*TWOPIOVERSAMPLERATE; if (++asteps >= 48000) asteps = 0; - return sin(angle); + return sin(angle) / 100.0; } void AudioInput::ToneBuffer() { SampleVector buf; - printf("tone %d \n", tune_tone); + for (int i = 0; i < bufferFrames; i++) { Sample f; @@ -219,7 +219,7 @@ double AudioInput::NextTwotone() double angle = (asteps*cw_keyer_sidetone_frequency)*TWOPIOVERSAMPLERATE; double angle2 = (asteps*cw_keyer_sidetone_frequency2)*TWOPIOVERSAMPLERATE; if (++asteps >= 48000) asteps = 0; - return (sin(angle) + sin(angle)) /2.0; + return (sin(angle) + sin(angle)) /200.0; } int AudioInput::queued_samples() diff --git a/CMakeLists.txt b/CMakeLists.txt index a6be90bc..9c68c3ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(sdrberry) file(GLOB_RECURSE SOURCES ./lvgl/src/*.c) set(LIBRARIES_FROM_REFERENCES -lpthread -lbluetooth -lSoapySDR -latomic -ltinyb -lliquid -lasound -lfftw3 -Wl,-verbose) include_directories(lvgl ft8) -add_executable(sdrberry sdrberry.cpp FT8Demodulator.cpp FT8Demodulator.h HidDev.cpp HidDev.h PeakLevelDetector.cpp PeakLevelDetector.h ft8/fft.cpp ft8/fft.h ft8/ft8.cpp ft8/ft8.h ft8/libldpc.cpp ft8/unpack.cpp ft8/util.cpp ft8/osd.cpp ft8/arrays.h gui_ft8.cpp gui_ft8.h gui_speech.cpp gui_speech.h FMDemodulator.cpp Agc_class.cpp AMDemodulator.cpp AMModulator.cpp AudioInput.cpp AudioOutput.cpp BandFilter.cpp ble_interface.cpp Catinterface.cpp configfile.cpp configoption.cpp Demodulator.cpp Filter.cpp FmDecode.cpp FMDemodulator.cpp FMModulator.cpp FreeSansOblique24.c FreeSansOblique32.c FreeSansOblique42.c FT891_CAT.cpp gui_agc.cpp Gui_band.cpp gui_bar.cpp gui_setup.cpp gui_top_bar.cpp gui_tx.cpp gui_vfo.cpp Keyboard.cpp MidiControle.cpp Mouse.cpp PCF8574.cpp RtMidi.cpp SdrDevice.cpp sdrstream.cpp Settings.cpp vfo.cpp RtAudio.cpp Waterfall.cpp wiringPiI2C.c wiringSerial.c lv_drivers/display/fbdev.c lv_drivers/indev/evdev.c lv_drivers/indev/mouse.c Agc_class.h AMDemodulator.h AMModulator.h Audiodefs.h AudioInput.h AudioOutput.h BandFilter.h ble_interface.h Catinterface.h configfile.h configoption.h DataBuffer.h Demodulator.h Filter.h FmDecode.h FMDemodulator.h FMModulator.h FT891_CAT.h gattlib.h gui_agc.h Gui_band.h gui_bar.h gui_setup.h gui_top_bar.h gui_tx.h gui_vfo.h Keyboard.h liquid.h liquid.internal.h lv_conf.h lv_drv_conf.h MidiControle.h Mouse.h PCF8574.h RtAudio.h RtMidi.h sdrberry.h SdrDevice.h sdrstream.h sma.h Settings.h vfo.h Waterfall.h wiringPiI2C.h wiringSerial.h strlib.cpp strlib.h MorseDecoder.cpp MorseDecoder.h EchoAudio.cpp EchoAudio.h FreeSans42.c ${SOURCES} lv_drivers/indev/evdev.h) +add_executable(sdrberry sdrberry.cpp FT8Demodulator.cpp FT8Demodulator.h SpectralNoiseReduction.cpp SpectralNoiseReduction.h LMSNoisereducer.cpp LMSNoisereducer.h HidDev.cpp HidDev.h PeakLevelDetector.cpp PeakLevelDetector.h ft8/fft.cpp ft8/fft.h ft8/ft8.cpp ft8/ft8.h ft8/libldpc.cpp ft8/unpack.cpp ft8/util.cpp ft8/osd.cpp ft8/arrays.h gui_ft8.cpp gui_ft8.h gui_rx.h gui_rx.cpp gui_speech.cpp gui_speech.h FMDemodulator.cpp Agc_class.cpp AMDemodulator.cpp AMModulator.cpp AudioInput.cpp AudioOutput.cpp BandFilter.cpp ble_interface.cpp Catinterface.cpp configfile.cpp configoption.cpp Demodulator.cpp Filter.cpp FmDecode.cpp FMDemodulator.cpp FMModulator.cpp FreeSansOblique24.c FreeSansOblique32.c FreeSansOblique42.c FT891_CAT.cpp gui_agc.cpp Gui_band.cpp gui_bar.cpp gui_setup.cpp gui_top_bar.cpp gui_tx.cpp gui_vfo.cpp Keyboard.cpp MidiControle.cpp Mouse.cpp PCF8574.cpp RtMidi.cpp SdrDevice.cpp sdrstream.cpp Settings.cpp vfo.cpp RtAudio.cpp Waterfall.cpp wiringPiI2C.c wiringSerial.c lv_drivers/display/fbdev.c lv_drivers/indev/evdev.c lv_drivers/indev/mouse.c Agc_class.h AMDemodulator.h AMModulator.h Audiodefs.h AudioInput.h AudioOutput.h BandFilter.h ble_interface.h Catinterface.h configfile.h configoption.h DataBuffer.h Demodulator.h Filter.h FmDecode.h FMDemodulator.h FMModulator.h FT891_CAT.h gattlib.h gui_agc.h Gui_band.h gui_bar.h gui_setup.h gui_top_bar.h gui_tx.h gui_vfo.h Keyboard.h liquid.h liquid.internal.h lv_conf.h lv_drv_conf.h MidiControle.h Mouse.h PCF8574.h RtAudio.h RtMidi.h sdrberry.h SdrDevice.h sdrstream.h sma.h Settings.h vfo.h Waterfall.h wiringPiI2C.h wiringSerial.h strlib.cpp strlib.h MorseDecoder.cpp MorseDecoder.h EchoAudio.cpp EchoAudio.h FreeSans42.c ${SOURCES} lv_drivers/indev/evdev.h) target_compile_definitions(sdrberry PRIVATE __LINUX_ALSA__ LV_LVGL_H_INCLUDE_SIMPLE) target_compile_options(sdrberry PRIVATE) target_include_directories(sdrberry PRIVATE /usr/local/include/liquid) diff --git a/FmDecode.cpp b/FmDecode.cpp index 561be3d4..b70082cf 100644 --- a/FmDecode.cpp +++ b/FmDecode.cpp @@ -505,6 +505,7 @@ void* rx_fm_thread(void* fm_ptr) usleep(5000); continue; } + int nosamples = iqsamples.size(); pfm->adjust_gain(iqsamples, gbar.get_if()); buf_mix.clear(); for (auto& col : iqsamples) @@ -516,7 +517,7 @@ void* rx_fm_thread(void* fm_ptr) buf_mix.push_back(v); } - if (buf_mix.size() >= nfft_samples && fft_block == 5) + if (fft_block == 5) { fft_block = 0; Fft_calc.process_samples(buf_mix); @@ -528,7 +529,7 @@ void* rx_fm_thread(void* fm_ptr) // Measure audio level. samples_mean_rms(audiosamples, audio_mean, audio_rms); audio_level = 0.95 * audio_level + 0.05 * audio_rms; - + int noaudiosamples = audiosamples.size(); // Set nominal audio volume. audio_output->adjust_gain(audiosamples); for (auto& col : audiosamples) @@ -540,11 +541,12 @@ void* rx_fm_thread(void* fm_ptr) } } const auto now = std::chrono::high_resolution_clock::now(); - if (timeLastPrint + std::chrono::seconds(1) < now) + if (timeLastPrint + std::chrono::seconds(20) < now) { timeLastPrint = now; const auto timePassed = std::chrono::duration_cast(now - startTime); - printf("Queued Audio Samples %d underrun %d\n", audio_output->queued_samples() / 2, audio_output->get_underrun()); + printf("Buffer queue %d Radio samples %d Audio Samples %d Queued Audio Samples %d underrun %d\n", fm_demod->source_buffer->size(),nosamples, noaudiosamples, audio_output->queued_samples() / 2, audio_output->get_underrun()); + audio_output->clear_underrun(); } iqsamples.clear(); audiosamples.clear(); diff --git a/HidDev.cpp b/HidDev.cpp index bb0d3a00..ac99c393 100644 --- a/HidDev.cpp +++ b/HidDev.cpp @@ -205,6 +205,7 @@ void HidDev::step_vfo() if (bevent && in_event.type == EV_REL && in_event.code == 11) { + switch (in_event.value) { case -240: @@ -216,6 +217,9 @@ void HidDev::step_vfo() case -480: value = -3; break; + case -600: + value = -4; + break; case 240: value = 1; break; @@ -225,6 +229,9 @@ void HidDev::step_vfo() case 480: value = 3; break; + case 600: + value = 4; + break; } } @@ -250,6 +257,11 @@ void HidDev::step_vfo() vfo.step_vfo(step, false); last_time = now; } + if (timePassed.count() > 1 && value == 4) + { + vfo.step_vfo(1.5*step, false); + last_time = now; + } if (timePassed.count() > 20 && value == -1) { vfo.step_vfo(-1 * step, false); @@ -265,6 +277,11 @@ void HidDev::step_vfo() vfo.step_vfo(-1 * step, false); last_time = now; } + if (timePassed.count() > 1 && value == -4) + { + vfo.step_vfo(-1.5 * step, false); + last_time = now; + } return; } diff --git a/LMSNoisereducer.cpp b/LMSNoisereducer.cpp new file mode 100644 index 00000000..0bbea78e --- /dev/null +++ b/LMSNoisereducer.cpp @@ -0,0 +1,145 @@ +#include "LMSNoisereducer.h" + +LMSNoisereducer::LMSNoisereducer(){ + q = nullptr; + q = eqlms_cccf_create(NULL, p); + eqlms_cccf_set_bw(q, mu); + eqlms_cccf_print(q); + first = false; + for (int i; i < 200; i++) + { + samples_training.push_back(0); + } +} + +LMSNoisereducer::~LMSNoisereducer() +{ + eqlms_cccf_destroy(q); +} + +void LMSNoisereducer::Process(const IQSampleVector &samples_in, IQSampleVector &samples_out) +{ + unsigned int i = 0; + + /*if (samples_training.size() < samples_in.size()) + { + samples_training.clear(); + for (auto con : samples_in) + { + samples_training.push_back(con); + } + } +*/ + + + for (auto con : samples_in) + { + IQSample s; + // push input sample + eqlms_cccf_push(q, con); + samples_training.push_back(con); + // compute output sample + eqlms_cccf_execute(q, &s); + samples_out.push_back(s); + // update internal weights + s = samples_training.front(); + samples_training.pop_front(); + eqlms_cccf_step(q, con, s); + //eqlms_cccf_step(q, samples_training[i], s); + //eqlms_cccf_step_blind(q, s); + //samples_training[i++] = con; + } +} + +LMSNoiseReduction::LMSNoiseReduction(int LMS_nr_strength) +{ + + + //LMS_Norm_instance.numTaps = calc_taps; + //LMS_Norm_instance.pCoeffs = LMS_NormCoeff_f32; + //LMS_Norm_instance.pState = LMS_StateF32; + + // Calculate "mu" (convergence rate) from user "DSP Strength" setting. This needs to be significantly de-linearized to + // squeeze a wide range of adjustment (e.g. several magnitudes) into a fairly small numerical range. + mu_calc = LMS_nr_strength; // get user setting + + // New DSP NR "mu" calculation method as of 0.0.214 + mu_calc /= 2; // scale input value + mu_calc += 2; // offset zero value + mu_calc /= 10; // convert from "bels" to "deci-bels" + mu_calc = powf(10, mu_calc); // convert to ratio + mu_calc = 1 / mu_calc; // invert to fraction + //LMS_Norm_instance.mu = mu_calc; +} + +Xanr::Xanr() +{ +} + +Xanr::~Xanr() +{ + +} + +void Xanr::Process(const SampleVector &samples_in, SampleVector &samples_out) +{ + int idx; + float c0, c1; + float y, error, sigma, inv_sigp; + float nel, nev; + + for (int i = 0; i < samples_in.size(); i++) + { + ANR_d[ANR_in_idx] = samples_in[i]; + + y = 0; + sigma = 0; + + for (int j = 0; j < ANR_taps; j++) + { + idx = (ANR_in_idx + j + ANR_delay) & ANR_mask; + y += ANR_w[j] * ANR_d[idx]; + sigma += ANR_d[idx] * ANR_d[idx]; + } + inv_sigp = 1.0 / (sigma + 1e-10); + error = ANR_d[ANR_in_idx] - y; + + if (ANR_notch) + { + //complex q; + //q.real(samples_in[i].real()); + //q.imag(error); + samples_out.push_back(error); + } + else + { + //complex q; + //q.real(samples_in[i].real()); + //q.imag(y); + samples_out.push_back(y); + } + + if ((nel = error * (1.0 - ANR_two_mu * sigma * inv_sigp)) < 0.0) + nel = -nel; + if ((nev = ANR_d[ANR_in_idx] - (1.0 - ANR_two_mu * ANR_ngamma) * y - ANR_two_mu * error * sigma * inv_sigp) < 0.0) + nev = -nev; + if (nev < nel) + { + if ((ANR_lidx += ANR_lincr) > ANR_lidx_max) + ANR_lidx = ANR_lidx_max; + else if ((ANR_lidx -= ANR_ldecr) < ANR_lidx_min) + ANR_lidx = ANR_lidx_min; + } + ANR_ngamma = ANR_gamma * (ANR_lidx * ANR_lidx) * (ANR_lidx * ANR_lidx) * ANR_den_mult; + + c0 = 1.0 - ANR_two_mu * ANR_ngamma; + c1 = ANR_two_mu * error * inv_sigp; + + for (int j = 0; j < ANR_taps; j++) + { + idx = (ANR_in_idx + j + ANR_delay) & ANR_mask; + ANR_w[j] = c0 * ANR_w[j] + c1 * ANR_d[idx]; + } + ANR_in_idx = (ANR_in_idx + ANR_mask) & ANR_mask; + } +} \ No newline at end of file diff --git a/LMSNoisereducer.h b/LMSNoisereducer.h new file mode 100644 index 00000000..d7edf119 --- /dev/null +++ b/LMSNoisereducer.h @@ -0,0 +1,72 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "DataBuffer.h" +#include "Audiodefs.h" + + +class LMSNoisereducer +{ + public: + LMSNoisereducer(); + ~LMSNoisereducer(); + void Process(const IQSampleVector &samples_in, IQSampleVector &samples_out); + + private: + eqlms_cccf q; + unsigned int n = 32; // number of training symbols + unsigned int p = 10; // equalizer order + float mu = 0.500f; // LMS learning rate + bool first; + std::list> samples_training; +}; + +class LMSNoiseReduction +{ + public: + LMSNoiseReduction(int); + ~LMSNoiseReduction(); + void Process(const IQSampleVector &samples_in, IQSampleVector &samples_out); + + private: + uint16_t calc_taps = 96; + float mu_calc; +}; + +#define ANR_DLINE_SIZE 1024 + +class Xanr +{ + public: + Xanr(); + ~Xanr(); + void Process(const SampleVector &samples_in, SampleVector &samples_out); + + private: + float ANR_d[ANR_DLINE_SIZE]; + int ANR_buff_size = ANR_DLINE_SIZE/2; + int ANR_in_idx = 0; + int ANR_dline_size = ANR_DLINE_SIZE; + int ANR_mask = ANR_dline_size - 1; + int ANR_delay = 16; + int ANR_taps = 64; + uint8_t ANR_notch = 0; + + float ANR_den_mult = 6.25e-10; + float ANR_gamma = 0.1; + float ANR_lidx = 120.0; + float ANR_lidx_min = 120.0; + float ANR_lidx_max = 200.0; + float ANR_lincr = 1.0; + float ANR_ldecr = 3.0; + float ANR_ngamma = 0.001; + float ANR_two_mu = 0.0001; + float ANR_w[ANR_DLINE_SIZE]; +}; \ No newline at end of file diff --git a/README.md b/README.md index 45b0482d..1b9079f5 100644 --- a/README.md +++ b/README.md @@ -18,16 +18,22 @@ a compiled executable and soapyradioberry library is available in executable dir installation instruction in install_guide.txt install script install.sh installs all components based on a fresh raspberry pi SD card (OS should be updated and in CLI mode) +Noise reduction code is an port from DD4WH https://github.com/df8oe/UHSDR/wiki/Noise-reduction +adapted for raspberry pi + ToDo: - Support for Midi contoler - Setup screen for Network and Wifi - waterfall display +- Different noise reduction schemas like lms etc. +- Support for audio sdr using Tal Done: - Adalm Pluto - Rtlsdr support - SDRPlay support - Radioberry support +- Hackrf support - FM broadband (only receive) - FM Narrowband - SSB support @@ -43,6 +49,7 @@ Done: - Morse code decoder - Support for Contour Shuttle Express - Receive FT8 +- Noise reduction (spectral noise reduction) Installation of libraries is necessary: - Liquid DSP diff --git a/SdrDevice.h b/SdrDevice.h index edb64a92..0f2bf122 100644 --- a/SdrDevice.h +++ b/SdrDevice.h @@ -25,8 +25,10 @@ class SdrDeviceChannel std::string probeChannel(); SoapySDR::Range get_full_gain_range() {return full_gain_range;} bool get_agc() {return agc;} - -private: + int get_bandwith_count() { return bandwidth_range.size(); } + long get_bandwith(int no) { return bandwidth_range[no].minimum(); } + + private: SoapySDR::Device *soapyDevice {nullptr}; int dir {0}; @@ -69,7 +71,9 @@ class SdrDevice std::vector get_tx_sample_rates(int channel) {return tx_channels[channel]->get_sample_rates();} int get_txchannels() {return numTxChans;} int get_rxchannels() {return numRxChans;} - + int get_bandwith_count(int channel) { return rx_channels[channel]->get_bandwith_count(); } + long get_bandwith(int channel, int no) { return rx_channels[channel]->get_bandwith(no); } + void setFrequency(const int direction, const size_t channel, const double frequency) { if (direction == SOAPY_SDR_TX && numTxChans < 1) diff --git a/Settings.cpp b/Settings.cpp index d7b7a785..5b35a0d2 100644 --- a/Settings.cpp +++ b/Settings.cpp @@ -11,8 +11,12 @@ const cfg::File::ConfigMap defaultOptions = { {"CAT", {{"USB", cfg::makeOption("/dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0")}}}, {"samplerate", {{"radioberry", cfg::makeOption(384)}, {"plutosdr", cfg::makeOption(1000)}, {"rtlsdr", cfg::makeOption(1000)}, {"sdrplay", cfg::makeOption(1000)}}}, {"samplerate_tx", {{"radioberry", cfg::makeOption(384)}}}, - {"Radio", {{"gain", cfg::makeOption(0, 0, 100)}, {"volume", cfg::makeOption(50)}, {"drive", cfg::makeOption(89)}, {"micgain", cfg::makeOption(50)}, {"band", cfg::makeOption("ham")}, {"AGC", cfg::makeOption("off")}}}, + {"radioberry", {{"gain", cfg::makeOption(60)}, {"drive", cfg::makeOption(89)}, {"if-gain", cfg::makeOption(30)}, {"samplerate", cfg::makeOption("384")}, {"samplerate_tx", cfg::makeOption("48")}, {"AGC", cfg::makeOption("off")}}}, + {"sdrplay", {{"gain", cfg::makeOption(30)}, {"drive", cfg::makeOption(89)}, {"if-gain", cfg::makeOption(30)}, {"AGC", cfg::makeOption("off")}, {"samplerate", cfg::makeOption("1000")}}}, + {"rtlsdr", {{"gain", cfg::makeOption(40)}, {"drive", cfg::makeOption(89)}, {"if-gain", cfg::makeOption(60)}, {"samplerate", cfg::makeOption("1000")}}}, + {"hackrf", {{"gain", cfg::makeOption(30)}, {"drive", cfg::makeOption(89)}, {"if-gain", cfg::makeOption(3)}, {"samplerate", cfg::makeOption("2000")}}}, + {"plutosdr", {{"gain", cfg::makeOption(60)}, {"drive", cfg::makeOption(89)}, {"if-gain", cfg::makeOption(30)}, {"AGC", cfg::makeOption("off")}}}, {"VFO1", {{"freq", cfg::makeOption(3500000)}, {"Mode", cfg::makeOption("LSB")}}}, {"VFO2", {{"freq", cfg::makeOption(3500000)}, {"Mode", cfg::makeOption("LSB")}}}, {"Audio", {{"device", cfg::makeOption("default")}}}, @@ -443,6 +447,22 @@ int Settings::if_gain() return 0; } +int Settings::if_gain(string sdrdevice) +{ + auto option = config->getSection(sdrdevice); + auto s = option.find("if-gain"); + string st = s->second; + return atoi((const char *)st.c_str()); +} + +int Settings::gain(string sdrdevice) +{ + auto option = config->getSection(sdrdevice); + auto s = option.find("gain"); + string st = s->second; + return atoi((const char *)st.c_str()); +} + int Settings::gain() { if (radio.find("gain") != radio.end()) @@ -682,4 +702,64 @@ void Settings::save_span(int span) config->useSection("Radio"); auto &col = (*config)("span"); col = span; +} + +// New functions + +int Settings::get_int(string sdrdevice, string key) +{ + auto option = config->getSection(sdrdevice); + auto s = option.find(key); + string st = s->second; + return atoi((const char *)st.c_str()); +} + +void Settings::save_int(string section, string key, int value) +{ + config->useSection(section); + auto &col = (*config)(key); + col = value; +} + +string Settings::get_string(string sdrdevice, string key) +{ + string st; + auto option = config->getSection(sdrdevice); + auto s = option.find(key); + if (s != option.end()) + st = s->second; + return st; +} + +void Settings::save_string(string section, string key, string value) +{ + config->useSection(section); + auto &col = (*config)(key); + col = value; +} + +void Settings::get_array_long(std::string section, std::string key, vector &array) +{ + config->useSection(section); + for (auto &col : (*config)(key)) + { + if (col.toInt() > 0) + array.push_back(col.toInt()); + } +} + +void Settings::set_array_long(std::string section, std::string key, vector &array) +{ + int i = 0; + config->useSection(section); + auto &val = (*config)(key); + for (auto col : array) + { + if (val.size() <= i) + val.push(cfg::makeOption(col)); + else + val[i] = col; + i++; + } + write_settings(); } \ No newline at end of file diff --git a/Settings.h b/Settings.h index 36d76fff..a6fc254a 100644 --- a/Settings.h +++ b/Settings.h @@ -50,8 +50,15 @@ class Settings void save_rf(int rf); void save_vfo(int vfo, long freq); void save_span(int span); - - + int if_gain(string sdrdevice); + int gain(string sdrdevice); + int get_int(string sdrdevice, string key); + string get_string(string sdrdevice, string key); + void save_int(string section, string key, int value); + void save_string(string section, string key, string value); + void get_array_long(std::string section, std::string key, vector &array); + void set_array_long(std::string section, std::string key, vector &array); + vector meters; vector labels; vector f_low; diff --git a/SpectralNoiseReduction.cpp b/SpectralNoiseReduction.cpp new file mode 100644 index 00000000..eca49757 --- /dev/null +++ b/SpectralNoiseReduction.cpp @@ -0,0 +1,488 @@ +#include "SpectralNoiseReduction.h" + +/************************************************************************************ + Adapted from T41-ep AC8GY and WTEE + + Noise reduction with spectral subtraction rule + based on Romanin et al. 2009 & Schmitt et al. 2002 + and MATLAB voicebox + and Gerkmann & Hendriks 2002 + and Yao et al. 2016 + + STAND: UHSDR github 14.1.2018 +************************************************************************************/ + + +SpectralNoiseReduction::SpectralNoiseReduction(float pcmrate, tuple bw) +{ + SampleRate = pcmrate; + NR_first_time_2 = 1; + NR_init_counter = 0; + DF = 1; //pcmrate / 12000.0; // decimation factor (48000/12000) + bandwidth = bw; + SpectralNoiseReductionInit(); +} + + +void SpectralNoiseReduction::SpectralNoiseReductionInit() +{ + for (int bindx = 0; bindx < NR_FFT_L / 2; bindx++) + { + NR_last_sample_buffer_L[bindx] = 0.1; + NR_Hk_old[bindx] = 0.1; // old gain + NR_Nest[bindx][0] = 0.01; + NR_Nest[bindx][1] = 0.015; + NR_Gts[bindx][1] = 0.1; + //NR_M[bindx] = 500.0; + //NR_E[bindx][0] = 0.1; + NR_X[bindx][1] = 0.5; + NR_SNR_post[bindx] = 2.0; + NR_SNR_prio[bindx] = 1.0; + //NR_first_time = 2; + NR_long_tone_gain[bindx] = 1.0; + NR_last_iFFT_result[bindx] = 0; + } +} + +void SpectralNoiseReduction::Process(const SampleVector &samples_in, SampleVector &samples_out) +{ + + int VAD_low = 0; + int VAD_high = 127; + float lf_freq; // = (offset - width/2) / (12000 / NR_FFT_L); // bin BW is 46.9Hz [12000Hz / 256 bins] @96kHz + float uf_freq; //= (offset + width/2) / (12000 / NR_FFT_L); + + // Need a total of 512 samples + if (samples_in.size() != NR_FFT_L) + return; + + ax = expf(-tinc / tax); + ap = expf(-tinc / tap); + xih1 = powf(10, (float)asnr / 10.0); + + xih1r = 1.0 / (1.0 + xih1) - 1.0; + pfac = (1.0 / pspri - 1.0) * (1.0 + xih1); + float snr_prio_min = powf(10, -(float)20 / 20.0); + + const int16_t NR_width = 4; + const float power_threshold = 0.4; + float ph1y[NR_FFT_L / 2]; + + lf_freq = std::get<0>(bandwidth); + uf_freq = std::get<1>(bandwidth); + lf_freq /= ((SampleRate / DF) / NR_FFT_L); // bin BW is 46.9Hz [12000Hz / 256 bins] @96kHz + uf_freq /= ((SampleRate / DF) / NR_FFT_L); + + // INITIALIZATION ONCE 1 + if (NR_first_time_2 == 1) + { // TODO: properly initialize all the variables + for (int bindx = 0; bindx < NR_FFT_L / 2; bindx++) + { + NR_last_sample_buffer_L[bindx] = 0.0; + NR_G[bindx] = 1.0; + //xu[bindx] = 1.0; //has to be replaced by other variable + NR_Hk_old[bindx] = 1.0; // old gain or xu in development mode + NR_Nest[bindx][0] = 0.0; + NR_Nest[bindx][1] = 1.0; + pslp[bindx] = 0.5; + } + NR_first_time_2 = 2; // we need to do some more a bit later down + } + + for (int k = 0; k < 2; k++) + { + // NR_FFT_buffer is 512 floats big + // interleaved r, i, r, i . . . + // fill first half of FFT_buffer with last events audio samples + for (int i = 0; i < NR_FFT_L / 2; i++) + { + NR_FFT_buffer[i].real(NR_last_sample_buffer_L[i]); // real + NR_FFT_buffer[i].imag(0.0); // imaginary + } + // copy recent samples to last_sample_buffer for next time! + for (int i = 0; i < NR_FFT_L / 2; i++) + { + NR_last_sample_buffer_L[i] = samples_in[i + k * (NR_FFT_L / 2)]; + } + // now fill recent audio samples into second half of FFT_buffer + for (int i = 0; i < NR_FFT_L / 2; i++) + { + NR_FFT_buffer[NR_FFT_L/2 + i].real(samples_in[i + k * (NR_FFT_L / 2)]); // real + NR_FFT_buffer[NR_FFT_L/2 + i].imag(0.0); + } + + for (int idx = 0; idx < NR_FFT_L; idx++) + { // sqrt Hann window + float q = hann(idx , NR_FFT_L); + NR_FFT_buffer[idx] *= q; + } + + // execute fft 256 buckets + fftplan plan = fft_create_plan(NR_FFT_L, NR_FFT_buffer.data(), NR_FFT_buffer1.data(), LIQUID_FFT_FORWARD, 0); + fft_execute(plan); + fft_destroy_plan(plan); + + std::memcpy(NR_FFT_buffer.data(), NR_FFT_buffer1.data(), NR_FFT_buffer1.size()); + + for (int bindx = 0; bindx < NR_FFT_L / 2; bindx++) + { + // this is squared magnitude for the current frame + NR_X[bindx][0] = (NR_FFT_buffer[bindx].real() * NR_FFT_buffer[bindx].real() + NR_FFT_buffer[bindx].imag() * NR_FFT_buffer[bindx].imag()); + } + + if (NR_first_time_2 == 2) + { // TODO: properly initialize all the variables + for (int bindx = 0; bindx < NR_FFT_L / 2; bindx++) + { + NR_Nest[bindx][0] = NR_Nest[bindx][0] + 0.05 * NR_X[bindx][0]; // we do it 20 times to average over 20 frames for app. 100ms only on NR_on/bandswitch/modeswitch,... + xt[bindx] = psini * NR_Nest[bindx][0]; + } + NR_init_counter++; + if (NR_init_counter > 19) + { //average over 20 frames for app. 100ms + NR_init_counter = 0; + NR_first_time_2 = 3; // now we did all the necessary initialization to actually start the noise reduction + } + } + if (NR_first_time_2 == 3) + { + for (int bindx = 0; bindx < NR_FFT_L / 2; bindx++) + { // 1. Step of NR - calculate the SNR's + ph1y[bindx] = 1.0 / (1.0 + pfac * expf(xih1r * NR_X[bindx][0] / xt[bindx])); + pslp[bindx] = ap * pslp[bindx] + (1.0 - ap) * ph1y[bindx]; + + if (pslp[bindx] > psthr) + { + ph1y[bindx] = 1.0 - pnsaf; + } + else + { + ph1y[bindx] = fmin(ph1y[bindx], 1.0); + } + xtr = (1.0 - ph1y[bindx]) * NR_X[bindx][0] + ph1y[bindx] * xt[bindx]; + xt[bindx] = ax * xt[bindx] + (1.0 - ax) * xtr; + } + for (int bindx = 0; bindx < NR_FFT_L / 2; bindx++) + { // 1. Step of NR - calculate the SNR's + NR_SNR_post[bindx] = fmax(fmin(NR_X[bindx][0] / xt[bindx], 1000.0), snr_prio_min); // limited to +30 /-15 dB, might be still too much of reduction, let's try it? + NR_SNR_prio[bindx] = fmax(NR_alpha * NR_Hk_old[bindx] + (1.0 - NR_alpha) * fmax(NR_SNR_post[bindx] - 1.0, 0.0), 0.0); + } + + VAD_low = (int)lf_freq; + VAD_high = (int)uf_freq; + if (VAD_low == VAD_high) + { + VAD_high++; + } + if (VAD_low < 1) + { + VAD_low = 1; + } + else if (VAD_low > NR_FFT_L / 2 - 2) + { + VAD_low = NR_FFT_L / 2 - 2; + } + if (VAD_high < 1) + { + VAD_high = 1; + } + else if (VAD_high > NR_FFT_L / 2) + { + VAD_high = NR_FFT_L / 2; + } + + float v; + for (int bindx = VAD_low; bindx < VAD_high; bindx++) + { // maybe we should limit this to the signal containing bins (filtering!!) + { + v = NR_SNR_prio[bindx] * NR_SNR_post[bindx] / (1.0 + NR_SNR_prio[bindx]); + NR_G[bindx] = 1.0 / NR_SNR_post[bindx] * sqrtf((0.7212 * v + v * v)); + NR_Hk_old[bindx] = NR_SNR_post[bindx] * NR_G[bindx] * NR_G[bindx]; // + } + + // MUSICAL NOISE TREATMENT HERE, DL2FW + + // musical noise "artefact" reduction by dynamic averaging - depending on SNR ratio + pre_power = 0.0; + post_power = 0.0; + for (int bindx = VAD_low; bindx < VAD_high; bindx++) + { + pre_power += NR_X[bindx][0]; + post_power += NR_G[bindx] * NR_G[bindx] * NR_X[bindx][0]; + } + + power_ratio = post_power / pre_power; + if (power_ratio > power_threshold) + { + power_ratio = 1.0; + NN = 1; + } + else + { + NN = 1 + 2 * (int)(0.5 + NR_width * (1.0 - power_ratio / power_threshold)); + } + + for (int bindx = VAD_low + NN / 2; bindx < VAD_high - NN / 2; bindx++) + { + NR_Nest[bindx][0] = 0.0; + for (int m = bindx - NN / 2; m <= bindx + NN / 2; m++) + { + NR_Nest[bindx][0] += NR_G[m]; + } + NR_Nest[bindx][0] /= (float)NN; + } + + // and now the edges - only going NN steps forward and taking the average + // lower edge + for (int bindx = VAD_low; bindx < VAD_low + NN / 2; bindx++) + { + NR_Nest[bindx][0] = 0.0; + for (int m = bindx; m < (bindx + NN); m++) + { + NR_Nest[bindx][0] += NR_G[m]; + } + NR_Nest[bindx][0] /= (float)NN; + } + + // upper edge - only going NN steps backward and taking the average + for (int bindx = VAD_high - NN; bindx < VAD_high; bindx++) + { + NR_Nest[bindx][0] = 0.0; + for (int m = bindx; m > (bindx - NN); m--) + { + NR_Nest[bindx][0] += NR_G[m]; + } + NR_Nest[bindx][0] /= (float)NN; + } + + // end of edge treatment + + for (int bindx = VAD_low + NN / 2; bindx < VAD_high - NN / 2; bindx++) + { + NR_G[bindx] = NR_Nest[bindx][0]; + } + // end of musical noise reduction + } //end of "if ts.nr_first_time == 3" + +#if 1 + // FINAL SPECTRAL WEIGHTING: Multiply current FFT results with NR_FFT_buffer for 128 bins with the 128 bin-specific gain factors G + // for(int bindx = 0; bindx < NR_FFT_L / 2; bindx++) // try 128: + for (int bindx = 0; bindx < NR_FFT_L / 2; bindx++) + { // try 128: + NR_FFT_buffer[bindx].real(NR_FFT_buffer[bindx].real() * NR_G[bindx] * NR_long_tone_gain[bindx]); // real part + NR_FFT_buffer[bindx].imag(NR_FFT_buffer[bindx].imag() * NR_G[bindx] * NR_long_tone_gain[bindx]); // imag part + NR_FFT_buffer[NR_FFT_L - bindx -1].real(NR_FFT_buffer[NR_FFT_L - bindx -1].real() * NR_G[bindx] * NR_long_tone_gain[bindx]); // real part conjugate symmetric + NR_FFT_buffer[NR_FFT_L - bindx -1].imag(NR_FFT_buffer[NR_FFT_L - bindx -1].imag() * NR_G[bindx] * NR_long_tone_gain[bindx]); // imag part conjugate symmetric + } + +#endif + + fftplan plan = fft_create_plan(NR_FFT_L, NR_FFT_buffer.data(), NR_FFT_buffer1.data(), LIQUID_FFT_BACKWARD, 0); + fft_execute(plan); + fft_destroy_plan(plan); + + for (int idx = 0; idx < NR_FFT_L; idx++) + { // sqrt Hann window + float q = hann(idx, NR_FFT_L); + NR_FFT_buffer[idx] = NR_FFT_buffer1[idx] * q ; + } + + // do the overlap & add + for (int i = 0; i < NR_FFT_L / 2; i++) + { // take real part of first half of current iFFT result and add to 2nd half of last iFFT_result + samples_out.push_back(0.01 *(NR_FFT_buffer[i].real() + NR_last_iFFT_result[i])); + } + for (int i = 0; i < NR_FFT_L / 2; i++) + { + NR_last_iFFT_result[i] = NR_FFT_buffer[NR_FFT_L/2 + i].real(); + } + // end of "for" loop which repeats the FFT_iFFT_chain two times !!! + } + } +} + + +/***** + Purpose: Kim1_NR() + Parameter list: + void + Return value; + void +*****/ +void SpectralNoiseReduction::Process_Kim1_NR(const SampleVector &samples_in, SampleVector &samples_out) +{ + /********************************************************************************** + EXPERIMENTAL STATION FOR SPECTRAL NOISE REDUCTION + FFT - iFFT Convolution + + thanks a lot for your support, Michael DL2FW ! + **********************************************************************************/ + NR_Kim=1; + if (NR_Kim == 1) + { + //////////////////////////////////////////////////////////////////////////////////////////////////////// + // this is exactly the implementation by + // Kim & Ruwisch 2002 - 7th International Conference on Spoken Language Processing Denver, Colorado, USA + // with two exceptions: + // 1.) we use power instead of magnitude for X + // 2.) we need to clamp for negative gains . . . + //////////////////////////////////////////////////////////////////////////////////////////////////////// + + // perform a loop two times (each time process 128 new samples) + // FFT 256 points + // frame step 128 samples + // half-overlapped data buffers + + int VAD_low = 0; + int VAD_high = 127; + float lf_freq; // = (offset - width/2) / (12000 / NR_FFT_L); // bin BW is 46.9Hz [12000Hz / 256 bins] @96kHz + float uf_freq; + + lf_freq = std::get<0>(bandwidth); + uf_freq = std::get<1>(bandwidth); + lf_freq /= ((SampleRate / DF) / NR_FFT_L); // bin BW is 46.9Hz [12000Hz / 256 bins] @96kHz + uf_freq /= ((SampleRate / DF) / NR_FFT_L); + + VAD_low = (int)lf_freq; + VAD_high = (int)uf_freq; + if (VAD_low == VAD_high) { + VAD_high++; + } + if (VAD_low < 1) { + VAD_low = 1; + } else if (VAD_low > NR_FFT_L / 2 - 2) { + VAD_low = NR_FFT_L / 2 - 2; + } + if (VAD_high < 1) { + VAD_high = 1; + } else if (VAD_high > NR_FFT_L / 2) { + VAD_high = NR_FFT_L / 2; + } + + for (int k = 0; k < 2; k++) { + // NR_FFT_buffer is 512 floats big + // interleaved r, i, r, i . . . + // fill first half of FFT_buffer with last events audio samples + for (int i = 0; i < NR_FFT_L / 2; i++) { + NR_FFT_buffer[i].real( NR_last_sample_buffer_L[i]); // real + NR_FFT_buffer[i].imag(0.0); // imaginary + } + // copy recent samples to last_sample_buffer for next time! + for (int i = 0; i < NR_FFT_L / 2; i++) { + NR_last_sample_buffer_L[i] = samples_in[i + k * (NR_FFT_L / 2)]; + } + // now fill recent audio samples into second half of FFT_buffer + for (int i = 0; i < NR_FFT_L / 2; i++) { + NR_FFT_buffer[NR_FFT_L/2 + i].real(samples_in[i + k * (NR_FFT_L / 2)]); // real + NR_FFT_buffer[NR_FFT_L/2 + i].imag(0.0); + } + // perform windowing on 256 real samples in the NR_FFT_buffer + for (int idx = 0; idx < NR_FFT_L; idx++) { // Hann window + float q = hann(idx, NR_FFT_L); + NR_FFT_buffer[idx] *= q; + } + + fftplan plan = fft_create_plan(NR_FFT_L, NR_FFT_buffer.data(), NR_FFT_buffer1.data(), LIQUID_FFT_FORWARD, 0); + fft_execute(plan); + fft_destroy_plan(plan); + std::memcpy(NR_FFT_buffer.data(), NR_FFT_buffer1.data(), NR_FFT_buffer1.size()); + + for (int bindx = 0; bindx < NR_FFT_L / 2; bindx++) { // take first 128 bin values of the FFT result + // it seems that taking power works better than taking magnitude . . . !? + //NR_X[bindx][NR_X_pointer] = sqrtf(NR_FFT_buffer[bindx * 2] * NR_FFT_buffer[bindx * 2] + NR_FFT_buffer[bindx * 2 + 1] * NR_FFT_buffer[bindx * 2 + 1]); + NR_X[bindx][NR_X_pointer] = (NR_FFT_buffer[bindx].real() * NR_FFT_buffer[bindx].real() + NR_FFT_buffer[bindx].imag() * NR_FFT_buffer[bindx].imag()); + } + + for (int bindx = VAD_low; bindx < VAD_high; bindx++) { // take first 128 bin values of the FFT result + NR_sum = 0.0; + for (int j = 0; j < NR_L_frames; j++) + { // sum up the L_frames |X| + NR_sum = NR_sum + NR_X[bindx][j]; + } + // divide sum of L_frames |X| by L_frames to calculate the average and save in NR_E + NR_E[bindx][NR_E_pointer] = NR_sum / (float)NR_L_frames; + } + for (int bindx = VAD_low; bindx < VAD_high; bindx++) { // take first 128 bin values of the FFT result + // we have to reset the minimum value to the first E value every time we start with a bin + NR_M[bindx] = NR_E[bindx][0]; + // therefore we start with the second E value (index j == 1) + for (uint8_t j = 1; j < NR_N_frames; j++) { + if (NR_E[bindx][j] < NR_M[bindx]) { + NR_M[bindx] = NR_E[bindx][j]; + } + } + } + for (int bindx = VAD_low; bindx < VAD_high; bindx++) { // take first 128 bin values of the FFT result + NR_T = NR_X[bindx][NR_X_pointer] / NR_M[bindx]; // dies scheint mir besser zu funktionieren ! + if (NR_T > NR_PSI) { + NR_lambda[bindx] = NR_M[bindx]; + } else { + NR_lambda[bindx] = NR_E[bindx][NR_E_pointer]; + } + } + + for (int bindx = VAD_low; bindx < VAD_high; bindx++) { // take first 128 bin values of the FFT result + if (NR_use_X) { + NR_G[bindx] = 1.0 - (NR_lambda[bindx] * NR_KIM_K / NR_X[bindx][NR_X_pointer]); + if (NR_G[bindx] < 0.0) + NR_G[bindx] = 0.0; + } else { + NR_G[bindx] = 1.0 - (NR_lambda[bindx] * NR_KIM_K / NR_E[bindx][NR_E_pointer]); + if (NR_G[bindx] < 0.0) + NR_G[bindx] = 0.0; + } + + // time smoothing + NR_Gts[bindx][0] = NR_alpha * NR_Gts[bindx][1] + (NR_onemalpha) * NR_G[bindx]; + NR_Gts[bindx][1] = NR_Gts[bindx][0]; // copy for next FFT frame + } + + // NR_G is always positive, however often 0.0 + for (int bindx = 1; bindx < ((NR_FFT_L / 2) - 1); bindx++) {// take first 128 bin values of the FFT result + NR_G[bindx] = NR_beta * NR_Gts[bindx - 1][0] + NR_onemtwobeta * NR_Gts[bindx][0] + NR_beta * NR_Gts[bindx + 1][0]; + } + // take care of bin 0 and bin NR_FFT_L/2 - 1 + NR_G[0] = (NR_onemtwobeta + NR_beta) * NR_Gts[0][0] + NR_beta * NR_Gts[1][0]; + NR_G[(NR_FFT_L / 2) - 1] = NR_beta * NR_Gts[(NR_FFT_L / 2) - 2][0] + (NR_onemtwobeta + NR_beta) * NR_Gts[(NR_FFT_L / 2) - 1][0]; + for (int bindx = 0; bindx < NR_FFT_L / 2; bindx++) { // try 128: + NR_FFT_buffer[bindx].real(NR_FFT_buffer [bindx].real() * NR_G[bindx]); // real part + NR_FFT_buffer[bindx].imag(NR_FFT_buffer [bindx].imag() * NR_G[bindx]); // imag part + NR_FFT_buffer[NR_FFT_L - bindx - 1].real(NR_FFT_buffer[NR_FFT_L - bindx - 1].real() * NR_G[bindx]); // real part conjugate symmetric + NR_FFT_buffer[NR_FFT_L - bindx - 1].imag(NR_FFT_buffer[NR_FFT_L - bindx - 1].imag() * NR_G[bindx]); // imag part conjugate symmetric + } + NR_X_pointer = NR_X_pointer + 1; + if (NR_X_pointer >= NR_L_frames) { + NR_X_pointer = 0; + } + // 3b ++NR_E_pointer + NR_E_pointer = NR_E_pointer + 1; + if (NR_E_pointer >= NR_N_frames) { + NR_E_pointer = 0; + } + + plan = fft_create_plan(NR_FFT_L, NR_FFT_buffer.data(), NR_FFT_buffer1.data(), LIQUID_FFT_BACKWARD, 0); + fft_execute(plan); + fft_destroy_plan(plan); + +#if 1 + // perform windowing on 256 real samples in the NR_FFT_buffer + for (int idx = 0; idx < NR_FFT_L; idx++) + { // sqrt Hann window + float q = hann(idx, NR_FFT_L); + NR_FFT_buffer[idx] = NR_FFT_buffer1[idx] * q; + } +#else + std::memcpy(NR_FFT_buffer.data(), NR_FFT_buffer1.data(), NR_FFT_buffer1.size()); +#endif + + for (int i = 0; i < NR_FFT_L / 2; i++) { // take real part of first half of current iFFT result and add to 2nd half of last iFFT_result + samples_out.push_back(0.01 * (NR_FFT_buffer[i].real() + NR_last_iFFT_result[i])); + } + + for (int i = 0; i < NR_FFT_L / 2; i++) { + NR_last_iFFT_result[i] = NR_FFT_buffer[NR_FFT_L/2 + i].real(); + } + } + } // end of Kim et al. 2002 algorithm + +} diff --git a/SpectralNoiseReduction.h b/SpectralNoiseReduction.h new file mode 100644 index 00000000..b91e4e4a --- /dev/null +++ b/SpectralNoiseReduction.h @@ -0,0 +1,87 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include "DataBuffer.h" +#include "Audiodefs.h" + +#define NR_FFT_L 1024 //256 +#define PI 3.14159265358979323846 + +class SpectralNoiseReduction +{ + public: + SpectralNoiseReduction(float pcmrate, tuple bandwidth); + void Process(const SampleVector &samples_in, SampleVector &samples_out); + void Process_Kim1_NR(const SampleVector &samples_in, SampleVector &samples_out); + void SpectralNoiseReductionInit(); + + private: + float pslp[NR_FFT_L / 2]; + float xt[NR_FFT_L / 2]; + float xtr; + float pre_power; + float post_power; + float power_ratio; + int16_t NN; + + float ax; //=0.8; // ax=exp(-tinc/tax); % noise output smoothing factor + float ap; //=0.9; // ap=exp(-tinc/tap); % noise output smoothing factor + float xih1; // = 31.6; + + float xih1r ; + float pfac ; + uint8_t NR_init_counter; + int NR_first_time_2; + + float NR_alpha = 0.95; + + const float tinc = 0.00533333; // frame time 5.3333ms + const float tax = 0.0239; // noise output smoothing time constant = -tinc/ln(0.8) + const float tap = 0.05062; // speech prob smoothing time constant = -tinc/ln(0.9) tinc = frame time (5.33ms) + const float psthr = 0.99; // threshold for smoothed speech probability [0.99] + const float pnsaf = 0.01; // noise probability safety value [0.01] + const float asnr = 20; // active SNR in dB + const float psini = 0.5; // initial speech probability [0.5] + const float pspri = 0.5; // prior speech probability [0.5] + const uint8_t NR_L_frames = 3; + const uint8_t NR_N_frames = 15; + float NR_KIM_K = 1.0; + float NR_onemalpha = (1.0 - NR_alpha); + float NR_beta = 0.85; + float NR_onemtwobeta = (1.0 - (2.0 * NR_beta)); + float DF; + float SampleRate; + float NR_PSI = 3.0; + uint32_t NR_X_pointer = 0; + uint32_t NR_E_pointer = 0; + float NR_sum = 0; + float NR_T; + uint8_t NR_use_X = 0; + uint8_t NR_Kim; + tuple bandwidth; + + array NR_last_sample_buffer_R; + array NR_last_sample_buffer_L; + array NR_G; + array NR_Hk_old; + array NR_last_iFFT_result; + array NR_SNR_prio; + array NR_SNR_post; + array NR_long_tone_gain; + array NR_lambda; + array NR_M; + + array,NR_FFT_L / 2> NR_E; + array, NR_FFT_L / 2> NR_X; + array, NR_FFT_L / 2> NR_Nest; + array, NR_FFT_L / 2> NR_Gts; + + array, NR_FFT_L> NR_FFT_buffer; + array, NR_FFT_L> NR_FFT_buffer1; +}; \ No newline at end of file diff --git a/Waterfall.cpp b/Waterfall.cpp index 91802cec..642291a9 100644 --- a/Waterfall.cpp +++ b/Waterfall.cpp @@ -62,7 +62,7 @@ void Fft_calculator::process_samples(const IQSampleVector& input) m_input.insert(m_input.end(), input.begin(), input.end()); if (m_input.size() >= nfft) { - std::unique_lock lock(m_mutex); + std::unique_lock lock(m_mutex); // Apply hamming window for (int i = 0; i < nfft; i++) { @@ -192,12 +192,12 @@ void Waterfall::init(lv_obj_t* scr, lv_coord_t x, lv_coord_t y, lv_coord_t w, lv void Waterfall::set_pos(int32_t offset) { uint32_t pos; - + offset = offset - ((m_ifrate.load() / 2) * m_n.load()); float div = m_ifrate.load() / nfft_samples; pos = (uint32_t)round(offset / div); lv_chart_set_cursor_point(chart, m_cursor, NULL, pos); - //printf("offset %d pos: %d ifrate %f div %f\n", offset, pos, m_ifrate.load(), div); + //printf("offset %d pos: %d ifrate %f div %f\n",offset, pos, m_ifrate.load(), div); } void Waterfall::set_fft_if_rate(float ifrate, int n) diff --git a/ft8/constants.c b/ft8/constants.c new file mode 100644 index 00000000..330342a9 --- /dev/null +++ b/ft8/constants.c @@ -0,0 +1,392 @@ +#include "constants.h" + +// Costas sync tone pattern +const uint8_t kFT8_Costas_pattern[7] = { 3, 1, 4, 0, 6, 5, 2 }; +const uint8_t kFT4_Costas_pattern[4][4] = { + { 0, 1, 3, 2 }, + { 1, 0, 2, 3 }, + { 2, 3, 1, 0 }, + { 3, 2, 0, 1 } +}; + +// Gray code map (FTx bits -> channel symbols) +const uint8_t kFT8_Gray_map[8] = { 0, 1, 3, 2, 5, 6, 4, 7 }; +const uint8_t kFT4_Gray_map[4] = { 0, 1, 3, 2 }; + +const uint8_t kFT4_XOR_sequence[10] = { + 0x4Au, // 01001010 + 0x5Eu, // 01011110 + 0x89u, // 10001001 + 0xB4u, // 10110100 + 0xB0u, // 10110000 + 0x8Au, // 10001010 + 0x79u, // 01111001 + 0x55u, // 01010101 + 0xBEu, // 10111110 + 0x28u, // 00101 [000] +}; + +// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first) +const uint8_t kFTX_LDPC_generator[FTX_LDPC_M][FTX_LDPC_K_BYTES] = { + { 0x83, 0x29, 0xce, 0x11, 0xbf, 0x31, 0xea, 0xf5, 0x09, 0xf2, 0x7f, 0xc0 }, + { 0x76, 0x1c, 0x26, 0x4e, 0x25, 0xc2, 0x59, 0x33, 0x54, 0x93, 0x13, 0x20 }, + { 0xdc, 0x26, 0x59, 0x02, 0xfb, 0x27, 0x7c, 0x64, 0x10, 0xa1, 0xbd, 0xc0 }, + { 0x1b, 0x3f, 0x41, 0x78, 0x58, 0xcd, 0x2d, 0xd3, 0x3e, 0xc7, 0xf6, 0x20 }, + { 0x09, 0xfd, 0xa4, 0xfe, 0xe0, 0x41, 0x95, 0xfd, 0x03, 0x47, 0x83, 0xa0 }, + { 0x07, 0x7c, 0xcc, 0xc1, 0x1b, 0x88, 0x73, 0xed, 0x5c, 0x3d, 0x48, 0xa0 }, + { 0x29, 0xb6, 0x2a, 0xfe, 0x3c, 0xa0, 0x36, 0xf4, 0xfe, 0x1a, 0x9d, 0xa0 }, + { 0x60, 0x54, 0xfa, 0xf5, 0xf3, 0x5d, 0x96, 0xd3, 0xb0, 0xc8, 0xc3, 0xe0 }, + { 0xe2, 0x07, 0x98, 0xe4, 0x31, 0x0e, 0xed, 0x27, 0x88, 0x4a, 0xe9, 0x00 }, + { 0x77, 0x5c, 0x9c, 0x08, 0xe8, 0x0e, 0x26, 0xdd, 0xae, 0x56, 0x31, 0x80 }, + { 0xb0, 0xb8, 0x11, 0x02, 0x8c, 0x2b, 0xf9, 0x97, 0x21, 0x34, 0x87, 0xc0 }, + { 0x18, 0xa0, 0xc9, 0x23, 0x1f, 0xc6, 0x0a, 0xdf, 0x5c, 0x5e, 0xa3, 0x20 }, + { 0x76, 0x47, 0x1e, 0x83, 0x02, 0xa0, 0x72, 0x1e, 0x01, 0xb1, 0x2b, 0x80 }, + { 0xff, 0xbc, 0xcb, 0x80, 0xca, 0x83, 0x41, 0xfa, 0xfb, 0x47, 0xb2, 0xe0 }, + { 0x66, 0xa7, 0x2a, 0x15, 0x8f, 0x93, 0x25, 0xa2, 0xbf, 0x67, 0x17, 0x00 }, + { 0xc4, 0x24, 0x36, 0x89, 0xfe, 0x85, 0xb1, 0xc5, 0x13, 0x63, 0xa1, 0x80 }, + { 0x0d, 0xff, 0x73, 0x94, 0x14, 0xd1, 0xa1, 0xb3, 0x4b, 0x1c, 0x27, 0x00 }, + { 0x15, 0xb4, 0x88, 0x30, 0x63, 0x6c, 0x8b, 0x99, 0x89, 0x49, 0x72, 0xe0 }, + { 0x29, 0xa8, 0x9c, 0x0d, 0x3d, 0xe8, 0x1d, 0x66, 0x54, 0x89, 0xb0, 0xe0 }, + { 0x4f, 0x12, 0x6f, 0x37, 0xfa, 0x51, 0xcb, 0xe6, 0x1b, 0xd6, 0xb9, 0x40 }, + { 0x99, 0xc4, 0x72, 0x39, 0xd0, 0xd9, 0x7d, 0x3c, 0x84, 0xe0, 0x94, 0x00 }, + { 0x19, 0x19, 0xb7, 0x51, 0x19, 0x76, 0x56, 0x21, 0xbb, 0x4f, 0x1e, 0x80 }, + { 0x09, 0xdb, 0x12, 0xd7, 0x31, 0xfa, 0xee, 0x0b, 0x86, 0xdf, 0x6b, 0x80 }, + { 0x48, 0x8f, 0xc3, 0x3d, 0xf4, 0x3f, 0xbd, 0xee, 0xa4, 0xea, 0xfb, 0x40 }, + { 0x82, 0x74, 0x23, 0xee, 0x40, 0xb6, 0x75, 0xf7, 0x56, 0xeb, 0x5f, 0xe0 }, + { 0xab, 0xe1, 0x97, 0xc4, 0x84, 0xcb, 0x74, 0x75, 0x71, 0x44, 0xa9, 0xa0 }, + { 0x2b, 0x50, 0x0e, 0x4b, 0xc0, 0xec, 0x5a, 0x6d, 0x2b, 0xdb, 0xdd, 0x00 }, + { 0xc4, 0x74, 0xaa, 0x53, 0xd7, 0x02, 0x18, 0x76, 0x16, 0x69, 0x36, 0x00 }, + { 0x8e, 0xba, 0x1a, 0x13, 0xdb, 0x33, 0x90, 0xbd, 0x67, 0x18, 0xce, 0xc0 }, + { 0x75, 0x38, 0x44, 0x67, 0x3a, 0x27, 0x78, 0x2c, 0xc4, 0x20, 0x12, 0xe0 }, + { 0x06, 0xff, 0x83, 0xa1, 0x45, 0xc3, 0x70, 0x35, 0xa5, 0xc1, 0x26, 0x80 }, + { 0x3b, 0x37, 0x41, 0x78, 0x58, 0xcc, 0x2d, 0xd3, 0x3e, 0xc3, 0xf6, 0x20 }, + { 0x9a, 0x4a, 0x5a, 0x28, 0xee, 0x17, 0xca, 0x9c, 0x32, 0x48, 0x42, 0xc0 }, + { 0xbc, 0x29, 0xf4, 0x65, 0x30, 0x9c, 0x97, 0x7e, 0x89, 0x61, 0x0a, 0x40 }, + { 0x26, 0x63, 0xae, 0x6d, 0xdf, 0x8b, 0x5c, 0xe2, 0xbb, 0x29, 0x48, 0x80 }, + { 0x46, 0xf2, 0x31, 0xef, 0xe4, 0x57, 0x03, 0x4c, 0x18, 0x14, 0x41, 0x80 }, + { 0x3f, 0xb2, 0xce, 0x85, 0xab, 0xe9, 0xb0, 0xc7, 0x2e, 0x06, 0xfb, 0xe0 }, + { 0xde, 0x87, 0x48, 0x1f, 0x28, 0x2c, 0x15, 0x39, 0x71, 0xa0, 0xa2, 0xe0 }, + { 0xfc, 0xd7, 0xcc, 0xf2, 0x3c, 0x69, 0xfa, 0x99, 0xbb, 0xa1, 0x41, 0x20 }, + { 0xf0, 0x26, 0x14, 0x47, 0xe9, 0x49, 0x0c, 0xa8, 0xe4, 0x74, 0xce, 0xc0 }, + { 0x44, 0x10, 0x11, 0x58, 0x18, 0x19, 0x6f, 0x95, 0xcd, 0xd7, 0x01, 0x20 }, + { 0x08, 0x8f, 0xc3, 0x1d, 0xf4, 0xbf, 0xbd, 0xe2, 0xa4, 0xea, 0xfb, 0x40 }, + { 0xb8, 0xfe, 0xf1, 0xb6, 0x30, 0x77, 0x29, 0xfb, 0x0a, 0x07, 0x8c, 0x00 }, + { 0x5a, 0xfe, 0xa7, 0xac, 0xcc, 0xb7, 0x7b, 0xbc, 0x9d, 0x99, 0xa9, 0x00 }, + { 0x49, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xf6, 0x5e, 0xcd, 0xc9, 0x07, 0x60 }, + { 0x19, 0x44, 0xd0, 0x85, 0xbe, 0x4e, 0x7d, 0xa8, 0xd6, 0xcc, 0x7d, 0x00 }, + { 0x25, 0x1f, 0x62, 0xad, 0xc4, 0x03, 0x2f, 0x0e, 0xe7, 0x14, 0x00, 0x20 }, + { 0x56, 0x47, 0x1f, 0x87, 0x02, 0xa0, 0x72, 0x1e, 0x00, 0xb1, 0x2b, 0x80 }, + { 0x2b, 0x8e, 0x49, 0x23, 0xf2, 0xdd, 0x51, 0xe2, 0xd5, 0x37, 0xfa, 0x00 }, + { 0x6b, 0x55, 0x0a, 0x40, 0xa6, 0x6f, 0x47, 0x55, 0xde, 0x95, 0xc2, 0x60 }, + { 0xa1, 0x8a, 0xd2, 0x8d, 0x4e, 0x27, 0xfe, 0x92, 0xa4, 0xf6, 0xc8, 0x40 }, + { 0x10, 0xc2, 0xe5, 0x86, 0x38, 0x8c, 0xb8, 0x2a, 0x3d, 0x80, 0x75, 0x80 }, + { 0xef, 0x34, 0xa4, 0x18, 0x17, 0xee, 0x02, 0x13, 0x3d, 0xb2, 0xeb, 0x00 }, + { 0x7e, 0x9c, 0x0c, 0x54, 0x32, 0x5a, 0x9c, 0x15, 0x83, 0x6e, 0x00, 0x00 }, + { 0x36, 0x93, 0xe5, 0x72, 0xd1, 0xfd, 0xe4, 0xcd, 0xf0, 0x79, 0xe8, 0x60 }, + { 0xbf, 0xb2, 0xce, 0xc5, 0xab, 0xe1, 0xb0, 0xc7, 0x2e, 0x07, 0xfb, 0xe0 }, + { 0x7e, 0xe1, 0x82, 0x30, 0xc5, 0x83, 0xcc, 0xcc, 0x57, 0xd4, 0xb0, 0x80 }, + { 0xa0, 0x66, 0xcb, 0x2f, 0xed, 0xaf, 0xc9, 0xf5, 0x26, 0x64, 0x12, 0x60 }, + { 0xbb, 0x23, 0x72, 0x5a, 0xbc, 0x47, 0xcc, 0x5f, 0x4c, 0xc4, 0xcd, 0x20 }, + { 0xde, 0xd9, 0xdb, 0xa3, 0xbe, 0xe4, 0x0c, 0x59, 0xb5, 0x60, 0x9b, 0x40 }, + { 0xd9, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xe6, 0xde, 0xcd, 0xc9, 0x03, 0x60 }, + { 0x9a, 0xd4, 0x6a, 0xed, 0x5f, 0x70, 0x7f, 0x28, 0x0a, 0xb5, 0xfc, 0x40 }, + { 0xe5, 0x92, 0x1c, 0x77, 0x82, 0x25, 0x87, 0x31, 0x6d, 0x7d, 0x3c, 0x20 }, + { 0x4f, 0x14, 0xda, 0x82, 0x42, 0xa8, 0xb8, 0x6d, 0xca, 0x73, 0x35, 0x20 }, + { 0x8b, 0x8b, 0x50, 0x7a, 0xd4, 0x67, 0xd4, 0x44, 0x1d, 0xf7, 0x70, 0xe0 }, + { 0x22, 0x83, 0x1c, 0x9c, 0xf1, 0x16, 0x94, 0x67, 0xad, 0x04, 0xb6, 0x80 }, + { 0x21, 0x3b, 0x83, 0x8f, 0xe2, 0xae, 0x54, 0xc3, 0x8e, 0xe7, 0x18, 0x00 }, + { 0x5d, 0x92, 0x6b, 0x6d, 0xd7, 0x1f, 0x08, 0x51, 0x81, 0xa4, 0xe1, 0x20 }, + { 0x66, 0xab, 0x79, 0xd4, 0xb2, 0x9e, 0xe6, 0xe6, 0x95, 0x09, 0xe5, 0x60 }, + { 0x95, 0x81, 0x48, 0x68, 0x2d, 0x74, 0x8a, 0x38, 0xdd, 0x68, 0xba, 0xa0 }, + { 0xb8, 0xce, 0x02, 0x0c, 0xf0, 0x69, 0xc3, 0x2a, 0x72, 0x3a, 0xb1, 0x40 }, + { 0xf4, 0x33, 0x1d, 0x6d, 0x46, 0x16, 0x07, 0xe9, 0x57, 0x52, 0x74, 0x60 }, + { 0x6d, 0xa2, 0x3b, 0xa4, 0x24, 0xb9, 0x59, 0x61, 0x33, 0xcf, 0x9c, 0x80 }, + { 0xa6, 0x36, 0xbc, 0xbc, 0x7b, 0x30, 0xc5, 0xfb, 0xea, 0xe6, 0x7f, 0xe0 }, + { 0x5c, 0xb0, 0xd8, 0x6a, 0x07, 0xdf, 0x65, 0x4a, 0x90, 0x89, 0xa2, 0x00 }, + { 0xf1, 0x1f, 0x10, 0x68, 0x48, 0x78, 0x0f, 0xc9, 0xec, 0xdd, 0x80, 0xa0 }, + { 0x1f, 0xbb, 0x53, 0x64, 0xfb, 0x8d, 0x2c, 0x9d, 0x73, 0x0d, 0x5b, 0xa0 }, + { 0xfc, 0xb8, 0x6b, 0xc7, 0x0a, 0x50, 0xc9, 0xd0, 0x2a, 0x5d, 0x03, 0x40 }, + { 0xa5, 0x34, 0x43, 0x30, 0x29, 0xea, 0xc1, 0x5f, 0x32, 0x2e, 0x34, 0xc0 }, + { 0xc9, 0x89, 0xd9, 0xc7, 0xc3, 0xd3, 0xb8, 0xc5, 0x5d, 0x75, 0x13, 0x00 }, + { 0x7b, 0xb3, 0x8b, 0x2f, 0x01, 0x86, 0xd4, 0x66, 0x43, 0xae, 0x96, 0x20 }, + { 0x26, 0x44, 0xeb, 0xad, 0xeb, 0x44, 0xb9, 0x46, 0x7d, 0x1f, 0x42, 0xc0 }, + { 0x60, 0x8c, 0xc8, 0x57, 0x59, 0x4b, 0xfb, 0xb5, 0x5d, 0x69, 0x60, 0x00 } +}; + +// Each row describes one LDPC parity check. +// Each number is an index into the codeword (1-origin). +// The codeword bits mentioned in each row must XOR to zero. +const uint8_t kFTX_LDPC_Nm[FTX_LDPC_M][7] = { + { 4, 31, 59, 91, 92, 96, 153 }, + { 5, 32, 60, 93, 115, 146, 0 }, + { 6, 24, 61, 94, 122, 151, 0 }, + { 7, 33, 62, 95, 96, 143, 0 }, + { 8, 25, 63, 83, 93, 96, 148 }, + { 6, 32, 64, 97, 126, 138, 0 }, + { 5, 34, 65, 78, 98, 107, 154 }, + { 9, 35, 66, 99, 139, 146, 0 }, + { 10, 36, 67, 100, 107, 126, 0 }, + { 11, 37, 67, 87, 101, 139, 158 }, + { 12, 38, 68, 102, 105, 155, 0 }, + { 13, 39, 69, 103, 149, 162, 0 }, + { 8, 40, 70, 82, 104, 114, 145 }, + { 14, 41, 71, 88, 102, 123, 156 }, + { 15, 42, 59, 106, 123, 159, 0 }, + { 1, 33, 72, 106, 107, 157, 0 }, + { 16, 43, 73, 108, 141, 160, 0 }, + { 17, 37, 74, 81, 109, 131, 154 }, + { 11, 44, 75, 110, 121, 166, 0 }, + { 45, 55, 64, 111, 130, 161, 173 }, + { 8, 46, 71, 112, 119, 166, 0 }, + { 18, 36, 76, 89, 113, 114, 143 }, + { 19, 38, 77, 104, 116, 163, 0 }, + { 20, 47, 70, 92, 138, 165, 0 }, + { 2, 48, 74, 113, 128, 160, 0 }, + { 21, 45, 78, 83, 117, 121, 151 }, + { 22, 47, 58, 118, 127, 164, 0 }, + { 16, 39, 62, 112, 134, 158, 0 }, + { 23, 43, 79, 120, 131, 145, 0 }, + { 19, 35, 59, 73, 110, 125, 161 }, + { 20, 36, 63, 94, 136, 161, 0 }, + { 14, 31, 79, 98, 132, 164, 0 }, + { 3, 44, 80, 124, 127, 169, 0 }, + { 19, 46, 81, 117, 135, 167, 0 }, + { 7, 49, 58, 90, 100, 105, 168 }, + { 12, 50, 61, 118, 119, 144, 0 }, + { 13, 51, 64, 114, 118, 157, 0 }, + { 24, 52, 76, 129, 148, 149, 0 }, + { 25, 53, 69, 90, 101, 130, 156 }, + { 20, 46, 65, 80, 120, 140, 170 }, + { 21, 54, 77, 100, 140, 171, 0 }, + { 35, 82, 133, 142, 171, 174, 0 }, + { 14, 30, 83, 113, 125, 170, 0 }, + { 4, 29, 68, 120, 134, 173, 0 }, + { 1, 4, 52, 57, 86, 136, 152 }, + { 26, 51, 56, 91, 122, 137, 168 }, + { 52, 84, 110, 115, 145, 168, 0 }, + { 7, 50, 81, 99, 132, 173, 0 }, + { 23, 55, 67, 95, 172, 174, 0 }, + { 26, 41, 77, 109, 141, 148, 0 }, + { 2, 27, 41, 61, 62, 115, 133 }, + { 27, 40, 56, 124, 125, 126, 0 }, + { 18, 49, 55, 124, 141, 167, 0 }, + { 6, 33, 85, 108, 116, 156, 0 }, + { 28, 48, 70, 85, 105, 129, 158 }, + { 9, 54, 63, 131, 147, 155, 0 }, + { 22, 53, 68, 109, 121, 174, 0 }, + { 3, 13, 48, 78, 95, 123, 0 }, + { 31, 69, 133, 150, 155, 169, 0 }, + { 12, 43, 66, 89, 97, 135, 159 }, + { 5, 39, 75, 102, 136, 167, 0 }, + { 2, 54, 86, 101, 135, 164, 0 }, + { 15, 56, 87, 108, 119, 171, 0 }, + { 10, 44, 82, 91, 111, 144, 149 }, + { 23, 34, 71, 94, 127, 153, 0 }, + { 11, 49, 88, 92, 142, 157, 0 }, + { 29, 34, 87, 97, 147, 162, 0 }, + { 30, 50, 60, 86, 137, 142, 162 }, + { 10, 53, 66, 84, 112, 128, 165 }, + { 22, 57, 85, 93, 140, 159, 0 }, + { 28, 32, 72, 103, 132, 166, 0 }, + { 28, 29, 84, 88, 117, 143, 150 }, + { 1, 26, 45, 80, 128, 147, 0 }, + { 17, 27, 89, 103, 116, 153, 0 }, + { 51, 57, 98, 163, 165, 172, 0 }, + { 21, 37, 73, 138, 152, 169, 0 }, + { 16, 47, 76, 130, 137, 154, 0 }, + { 3, 24, 30, 72, 104, 139, 0 }, + { 9, 40, 90, 106, 134, 151, 0 }, + { 15, 58, 60, 74, 111, 150, 163 }, + { 18, 42, 79, 144, 146, 152, 0 }, + { 25, 38, 65, 99, 122, 160, 0 }, + { 17, 42, 75, 129, 170, 172, 0 } +}; + +// Each row corresponds to a codeword bit. +// The numbers indicate which three LDPC parity checks (rows in Nm) refer to the codeword bit. +// 1-origin. +const uint8_t kFTX_LDPC_Mn[FTX_LDPC_N][3] = { + { 16, 45, 73 }, + { 25, 51, 62 }, + { 33, 58, 78 }, + { 1, 44, 45 }, + { 2, 7, 61 }, + { 3, 6, 54 }, + { 4, 35, 48 }, + { 5, 13, 21 }, + { 8, 56, 79 }, + { 9, 64, 69 }, + { 10, 19, 66 }, + { 11, 36, 60 }, + { 12, 37, 58 }, + { 14, 32, 43 }, + { 15, 63, 80 }, + { 17, 28, 77 }, + { 18, 74, 83 }, + { 22, 53, 81 }, + { 23, 30, 34 }, + { 24, 31, 40 }, + { 26, 41, 76 }, + { 27, 57, 70 }, + { 29, 49, 65 }, + { 3, 38, 78 }, + { 5, 39, 82 }, + { 46, 50, 73 }, + { 51, 52, 74 }, + { 55, 71, 72 }, + { 44, 67, 72 }, + { 43, 68, 78 }, + { 1, 32, 59 }, + { 2, 6, 71 }, + { 4, 16, 54 }, + { 7, 65, 67 }, + { 8, 30, 42 }, + { 9, 22, 31 }, + { 10, 18, 76 }, + { 11, 23, 82 }, + { 12, 28, 61 }, + { 13, 52, 79 }, + { 14, 50, 51 }, + { 15, 81, 83 }, + { 17, 29, 60 }, + { 19, 33, 64 }, + { 20, 26, 73 }, + { 21, 34, 40 }, + { 24, 27, 77 }, + { 25, 55, 58 }, + { 35, 53, 66 }, + { 36, 48, 68 }, + { 37, 46, 75 }, + { 38, 45, 47 }, + { 39, 57, 69 }, + { 41, 56, 62 }, + { 20, 49, 53 }, + { 46, 52, 63 }, + { 45, 70, 75 }, + { 27, 35, 80 }, + { 1, 15, 30 }, + { 2, 68, 80 }, + { 3, 36, 51 }, + { 4, 28, 51 }, + { 5, 31, 56 }, + { 6, 20, 37 }, + { 7, 40, 82 }, + { 8, 60, 69 }, + { 9, 10, 49 }, + { 11, 44, 57 }, + { 12, 39, 59 }, + { 13, 24, 55 }, + { 14, 21, 65 }, + { 16, 71, 78 }, + { 17, 30, 76 }, + { 18, 25, 80 }, + { 19, 61, 83 }, + { 22, 38, 77 }, + { 23, 41, 50 }, + { 7, 26, 58 }, + { 29, 32, 81 }, + { 33, 40, 73 }, + { 18, 34, 48 }, + { 13, 42, 64 }, + { 5, 26, 43 }, + { 47, 69, 72 }, + { 54, 55, 70 }, + { 45, 62, 68 }, + { 10, 63, 67 }, + { 14, 66, 72 }, + { 22, 60, 74 }, + { 35, 39, 79 }, + { 1, 46, 64 }, + { 1, 24, 66 }, + { 2, 5, 70 }, + { 3, 31, 65 }, + { 4, 49, 58 }, + { 1, 4, 5 }, + { 6, 60, 67 }, + { 7, 32, 75 }, + { 8, 48, 82 }, + { 9, 35, 41 }, + { 10, 39, 62 }, + { 11, 14, 61 }, + { 12, 71, 74 }, + { 13, 23, 78 }, + { 11, 35, 55 }, + { 15, 16, 79 }, + { 7, 9, 16 }, + { 17, 54, 63 }, + { 18, 50, 57 }, + { 19, 30, 47 }, + { 20, 64, 80 }, + { 21, 28, 69 }, + { 22, 25, 43 }, + { 13, 22, 37 }, + { 2, 47, 51 }, + { 23, 54, 74 }, + { 26, 34, 72 }, + { 27, 36, 37 }, + { 21, 36, 63 }, + { 29, 40, 44 }, + { 19, 26, 57 }, + { 3, 46, 82 }, + { 14, 15, 58 }, + { 33, 52, 53 }, + { 30, 43, 52 }, + { 6, 9, 52 }, + { 27, 33, 65 }, + { 25, 69, 73 }, + { 38, 55, 83 }, + { 20, 39, 77 }, + { 18, 29, 56 }, + { 32, 48, 71 }, + { 42, 51, 59 }, + { 28, 44, 79 }, + { 34, 60, 62 }, + { 31, 45, 61 }, + { 46, 68, 77 }, + { 6, 24, 76 }, + { 8, 10, 78 }, + { 40, 41, 70 }, + { 17, 50, 53 }, + { 42, 66, 68 }, + { 4, 22, 72 }, + { 36, 64, 81 }, + { 13, 29, 47 }, + { 2, 8, 81 }, + { 56, 67, 73 }, + { 5, 38, 50 }, + { 12, 38, 64 }, + { 59, 72, 80 }, + { 3, 26, 79 }, + { 45, 76, 81 }, + { 1, 65, 74 }, + { 7, 18, 77 }, + { 11, 56, 59 }, + { 14, 39, 54 }, + { 16, 37, 66 }, + { 10, 28, 55 }, + { 15, 60, 70 }, + { 17, 25, 82 }, + { 20, 30, 31 }, + { 12, 67, 68 }, + { 23, 75, 80 }, + { 27, 32, 62 }, + { 24, 69, 75 }, + { 19, 21, 71 }, + { 34, 53, 61 }, + { 35, 46, 47 }, + { 33, 59, 76 }, + { 40, 43, 83 }, + { 41, 42, 63 }, + { 49, 75, 83 }, + { 20, 44, 48 }, + { 42, 49, 57 } +}; + +const uint8_t kFTX_LDPC_Num_rows[FTX_LDPC_M] = { + 7, 6, 6, 6, 7, 6, 7, 6, 6, 7, 6, 6, 7, 7, 6, 6, + 6, 7, 6, 7, 6, 7, 6, 6, 6, 7, 6, 6, 6, 7, 6, 6, + 6, 6, 7, 6, 6, 6, 7, 7, 6, 6, 6, 6, 7, 7, 6, 6, + 6, 6, 7, 6, 6, 6, 7, 6, 6, 6, 6, 7, 6, 6, 6, 7, + 6, 6, 6, 7, 7, 6, 6, 7, 6, 6, 6, 6, 6, 6, 6, 7, + 6, 6, 6 +}; \ No newline at end of file diff --git a/ft8/constants.h b/ft8/constants.h new file mode 100644 index 00000000..b7531a45 --- /dev/null +++ b/ft8/constants.h @@ -0,0 +1,81 @@ +#ifndef _INCLUDE_CONSTANTS_H_ +#define _INCLUDE_CONSTANTS_H_ + +#include + +typedef enum +{ + PROTO_FT4, + PROTO_FT8 +} ftx_protocol_t; + +#define FT8_SYMBOL_PERIOD (0.160f) ///< FT8 symbol duration, defines tone deviation in Hz and symbol rate +#define FT8_SLOT_TIME (15.0f) ///< FT8 slot period + +#define FT4_SYMBOL_PERIOD (0.048f) ///< FT4 symbol duration, defines tone deviation in Hz and symbol rate +#define FT4_SLOT_TIME (7.5f) ///< FT4 slot period + +// Define FT8 symbol counts +// FT8 message structure: +// S D1 S D2 S +// S - sync block (7 symbols of Costas pattern) +// D1 - first data block (29 symbols each encoding 3 bits) +#define FT8_ND (58) ///< Data symbols +#define FT8_NN (79) ///< Total channel symbols (FT8_NS + FT8_ND) +#define FT8_LENGTH_SYNC (7) ///< Length of each sync group +#define FT8_NUM_SYNC (3) ///< Number of sync groups +#define FT8_SYNC_OFFSET (36) ///< Offset between sync groups + +// Define FT4 symbol counts +// FT4 message structure: +// R Sa D1 Sb D2 Sc D3 Sd R +// R - ramping symbol (no payload information conveyed) +// Sx - one of four _different_ sync blocks (4 symbols of Costas pattern) +// Dy - data block (29 symbols each encoding 2 bits) +#define FT4_ND (87) ///< Data symbols +#define FT4_NR (2) ///< Ramp symbols (beginning + end) +#define FT4_NN (105) ///< Total channel symbols (FT4_NS + FT4_ND + FT4_NR) +#define FT4_LENGTH_SYNC (4) ///< Length of each sync group +#define FT4_NUM_SYNC (4) ///< Number of sync groups +#define FT4_SYNC_OFFSET (33) ///< Offset between sync groups + +// Define LDPC parameters +#define FTX_LDPC_N (174) ///< Number of bits in the encoded message (payload with LDPC checksum bits) +#define FTX_LDPC_K (91) ///< Number of payload bits (including CRC) +#define FTX_LDPC_M (83) ///< Number of LDPC checksum bits (FTX_LDPC_N - FTX_LDPC_K) +#define FTX_LDPC_N_BYTES ((FTX_LDPC_N + 7) / 8) ///< Number of whole bytes needed to store 174 bits (full message) +#define FTX_LDPC_K_BYTES ((FTX_LDPC_K + 7) / 8) ///< Number of whole bytes needed to store 91 bits (payload + CRC only) + +// Define CRC parameters +#define FT8_CRC_POLYNOMIAL ((uint16_t)0x2757u) ///< CRC-14 polynomial without the leading (MSB) 1 +#define FT8_CRC_WIDTH (14) + +/// Costas 7x7 tone pattern for synchronization +extern const uint8_t kFT8_Costas_pattern[7]; +extern const uint8_t kFT4_Costas_pattern[4][4]; + +/// Gray code map to encode 8 symbols (tones) +extern const uint8_t kFT8_Gray_map[8]; +extern const uint8_t kFT4_Gray_map[4]; + +extern const uint8_t kFT4_XOR_sequence[10]; + +/// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first) +extern const uint8_t kFTX_LDPC_generator[FTX_LDPC_M][FTX_LDPC_K_BYTES]; + +/// LDPC(174,91) parity check matrix, containing 83 rows, +/// each row describes one parity check, +/// each number is an index into the codeword (1-origin). +/// The codeword bits mentioned in each row must xor to zero. +/// From WSJT-X's ldpc_174_91_c_reordered_parity.f90. +extern const uint8_t kFTX_LDPC_Nm[FTX_LDPC_M][7]; + +/// Mn from WSJT-X's bpdecode174.f90. Each row corresponds to a codeword bit. +/// The numbers indicate which three parity checks (rows in Nm) refer to the codeword bit. +/// The numbers use 1 as the origin (first entry). +extern const uint8_t kFTX_LDPC_Mn[FTX_LDPC_N][3]; + +/// Number of rows (columns in C/C++) in the array Nm. +extern const uint8_t kFTX_LDPC_Num_rows[FTX_LDPC_M]; + +#endif // _INCLUDE_CONSTANTS_H_ diff --git a/ft8/crc.c b/ft8/crc.c new file mode 100644 index 00000000..72a4c896 --- /dev/null +++ b/ft8/crc.c @@ -0,0 +1,63 @@ +#include "crc.h" +#include "constants.h" + +#define TOPBIT (1u << (FT8_CRC_WIDTH - 1)) + +// Compute 14-bit CRC for a sequence of given number of bits +// Adapted from https://barrgroup.com/Embedded-Systems/How-To/CRC-Calculation-C-Code +// [IN] message - byte sequence (MSB first) +// [IN] num_bits - number of bits in the sequence +uint16_t ftx_compute_crc(const uint8_t message[], int num_bits) +{ + uint16_t remainder = 0; + int idx_byte = 0; + + // Perform modulo-2 division, a bit at a time. + for (int idx_bit = 0; idx_bit < num_bits; ++idx_bit) + { + if (idx_bit % 8 == 0) + { + // Bring the next byte into the remainder. + remainder ^= (message[idx_byte] << (FT8_CRC_WIDTH - 8)); + ++idx_byte; + } + + // Try to divide the current data bit. + if (remainder & TOPBIT) + { + remainder = (remainder << 1) ^ FT8_CRC_POLYNOMIAL; + } + else + { + remainder = (remainder << 1); + } + } + + return remainder & ((TOPBIT << 1) - 1u); +} + +uint16_t ftx_extract_crc(const uint8_t a91[]) +{ + uint16_t chksum = ((a91[9] & 0x07) << 11) | (a91[10] << 3) | (a91[11] >> 5); + return chksum; +} + +void ftx_add_crc(const uint8_t payload[], uint8_t a91[]) +{ + // Copy 77 bits of payload data + for (int i = 0; i < 10; i++) + a91[i] = payload[i]; + + // Clear 3 bits after the payload to make 82 bits + a91[9] &= 0xF8u; + a91[10] = 0; + + // Calculate CRC of 82 bits (77 + 5 zeros) + // 'The CRC is calculated on the source-encoded message, zero-extended from 77 to 82 bits' + uint16_t checksum = ftx_compute_crc(a91, 96 - 14); + + // Store the CRC at the end of 77 bit message + a91[9] |= (uint8_t)(checksum >> 11); + a91[10] = (uint8_t)(checksum >> 3); + a91[11] = (uint8_t)(checksum << 5); +} diff --git a/ft8/crc.h b/ft8/crc.h new file mode 100644 index 00000000..b510122e --- /dev/null +++ b/ft8/crc.h @@ -0,0 +1,22 @@ +#ifndef _INCLUDE_CRC_H_ +#define _INCLUDE_CRC_H_ + +#include +#include + +// Compute 14-bit CRC for a sequence of given number of bits using FT8/FT4 CRC polynomial +// [IN] message - byte sequence (MSB first) +// [IN] num_bits - number of bits in the sequence +uint16_t ftx_compute_crc(const uint8_t message[], int num_bits); + +/// Extract the FT8/FT4 CRC of a packed message (during decoding) +/// @param[in] a91 77 bits of payload data + CRC +/// @return Extracted CRC +uint16_t ftx_extract_crc(const uint8_t a91[]); + +/// Add FT8/FT4 CRC to a packed message (during encoding) +/// @param[in] payload 77 bits of payload data +/// @param[out] a91 91 bits of payload data + CRC +void ftx_add_crc(const uint8_t payload[], uint8_t a91[]); + +#endif // _INCLUDE_CRC_H_ \ No newline at end of file diff --git a/ft8/encode.c b/ft8/encode.c new file mode 100644 index 00000000..33c249cd --- /dev/null +++ b/ft8/encode.c @@ -0,0 +1,195 @@ +#include "encode.h" +#include "constants.h" +#include "crc.h" + +#include + +// Returns 1 if an odd number of bits are set in x, zero otherwise +static uint8_t parity8(uint8_t x) +{ + x ^= x >> 4; // a b c d ae bf cg dh + x ^= x >> 2; // a b ac bd cae dbf aecg bfdh + x ^= x >> 1; // a ab bac acbd bdcae caedbf aecgbfdh + return x % 2; // modulo 2 +} + +// Encode via LDPC a 91-bit message and return a 174-bit codeword. +// The generator matrix has dimensions (87,87). +// The code is a (174,91) regular LDPC code with column weight 3. +// Arguments: +// [IN] message - array of 91 bits stored as 12 bytes (MSB first) +// [OUT] codeword - array of 174 bits stored as 22 bytes (MSB first) +static void encode174(const uint8_t* message, uint8_t* codeword) +{ + // This implementation accesses the generator bits straight from the packed binary representation in kFTX_LDPC_generator + + // Fill the codeword with message and zeros, as we will only update binary ones later + for (int j = 0; j < FTX_LDPC_N_BYTES; ++j) + { + codeword[j] = (j < FTX_LDPC_K_BYTES) ? message[j] : 0; + } + + // Compute the byte index and bit mask for the first checksum bit + uint8_t col_mask = (0x80u >> (FTX_LDPC_K % 8u)); // bitmask of current byte + uint8_t col_idx = FTX_LDPC_K_BYTES - 1; // index into byte array + + // Compute the LDPC checksum bits and store them in codeword + for (int i = 0; i < FTX_LDPC_M; ++i) + { + // Fast implementation of bitwise multiplication and parity checking + // Normally nsum would contain the result of dot product between message and kFTX_LDPC_generator[i], + // but we only compute the sum modulo 2. + uint8_t nsum = 0; + for (int j = 0; j < FTX_LDPC_K_BYTES; ++j) + { + uint8_t bits = message[j] & kFTX_LDPC_generator[i][j]; // bitwise AND (bitwise multiplication) + nsum ^= parity8(bits); // bitwise XOR (addition modulo 2) + } + + // Set the current checksum bit in codeword if nsum is odd + if (nsum % 2) + { + codeword[col_idx] |= col_mask; + } + + // Update the byte index and bit mask for the next checksum bit + col_mask >>= 1; + if (col_mask == 0) + { + col_mask = 0x80u; + ++col_idx; + } + } +} + +void ft8_encode(const uint8_t* payload, uint8_t* tones) +{ + uint8_t a91[FTX_LDPC_K_BYTES]; // Store 77 bits of payload + 14 bits CRC + + // Compute and add CRC at the end of the message + // a91 contains 77 bits of payload + 14 bits of CRC + ftx_add_crc(payload, a91); + + uint8_t codeword[FTX_LDPC_N_BYTES]; + encode174(a91, codeword); + + // Message structure: S7 D29 S7 D29 S7 + // Total symbols: 79 (FT8_NN) + + uint8_t mask = 0x80u; // Mask to extract 1 bit from codeword + int i_byte = 0; // Index of the current byte of the codeword + for (int i_tone = 0; i_tone < FT8_NN; ++i_tone) + { + if ((i_tone >= 0) && (i_tone < 7)) + { + tones[i_tone] = kFT8_Costas_pattern[i_tone]; + } + else if ((i_tone >= 36) && (i_tone < 43)) + { + tones[i_tone] = kFT8_Costas_pattern[i_tone - 36]; + } + else if ((i_tone >= 72) && (i_tone < 79)) + { + tones[i_tone] = kFT8_Costas_pattern[i_tone - 72]; + } + else + { + // Extract 3 bits from codeword at i-th position + uint8_t bits3 = 0; + + if (codeword[i_byte] & mask) + bits3 |= 4; + if (0 == (mask >>= 1)) + { + mask = 0x80u; + i_byte++; + } + if (codeword[i_byte] & mask) + bits3 |= 2; + if (0 == (mask >>= 1)) + { + mask = 0x80u; + i_byte++; + } + if (codeword[i_byte] & mask) + bits3 |= 1; + if (0 == (mask >>= 1)) + { + mask = 0x80u; + i_byte++; + } + + tones[i_tone] = kFT8_Gray_map[bits3]; + } + } +} + +void ft4_encode(const uint8_t* payload, uint8_t* tones) +{ + uint8_t a91[FTX_LDPC_K_BYTES]; // Store 77 bits of payload + 14 bits CRC + uint8_t payload_xor[10]; // Encoded payload data + + // '[..] for FT4 only, in order to avoid transmitting a long string of zeros when sending CQ messages, + // the assembled 77-bit message is bitwise exclusive-OR’ed with [a] pseudorandom sequence before computing the CRC and FEC parity bits' + for (int i = 0; i < 10; ++i) + { + payload_xor[i] = payload[i] ^ kFT4_XOR_sequence[i]; + } + + // Compute and add CRC at the end of the message + // a91 contains 77 bits of payload + 14 bits of CRC + ftx_add_crc(payload_xor, a91); + + uint8_t codeword[FTX_LDPC_N_BYTES]; + encode174(a91, codeword); // 91 bits -> 174 bits + + // Message structure: R S4_1 D29 S4_2 D29 S4_3 D29 S4_4 R + // Total symbols: 105 (FT4_NN) + + uint8_t mask = 0x80u; // Mask to extract 1 bit from codeword + int i_byte = 0; // Index of the current byte of the codeword + for (int i_tone = 0; i_tone < FT4_NN; ++i_tone) + { + if ((i_tone == 0) || (i_tone == 104)) + { + tones[i_tone] = 0; // R (ramp) symbol + } + else if ((i_tone >= 1) && (i_tone < 5)) + { + tones[i_tone] = kFT4_Costas_pattern[0][i_tone - 1]; + } + else if ((i_tone >= 34) && (i_tone < 38)) + { + tones[i_tone] = kFT4_Costas_pattern[1][i_tone - 34]; + } + else if ((i_tone >= 67) && (i_tone < 71)) + { + tones[i_tone] = kFT4_Costas_pattern[2][i_tone - 67]; + } + else if ((i_tone >= 100) && (i_tone < 104)) + { + tones[i_tone] = kFT4_Costas_pattern[3][i_tone - 100]; + } + else + { + // Extract 2 bits from codeword at i-th position + uint8_t bits2 = 0; + + if (codeword[i_byte] & mask) + bits2 |= 2; + if (0 == (mask >>= 1)) + { + mask = 0x80u; + i_byte++; + } + if (codeword[i_byte] & mask) + bits2 |= 1; + if (0 == (mask >>= 1)) + { + mask = 0x80u; + i_byte++; + } + tones[i_tone] = kFT4_Gray_map[bits2]; + } + } +} diff --git a/ft8/encode.h b/ft8/encode.h new file mode 100644 index 00000000..d5e2759a --- /dev/null +++ b/ft8/encode.h @@ -0,0 +1,32 @@ +#ifndef _INCLUDE_ENCODE_H_ +#define _INCLUDE_ENCODE_H_ + +#include + +// typedef struct +// { +// uint8_t tones[FT8_NN]; +// // for waveform readout: +// int n_spsym; // Number of waveform samples per symbol +// float *pulse; // [3 * n_spsym] +// int idx_symbol; // Index of the current symbol +// float f0; // Base frequency, Hertz +// float signal_rate; // Waveform sample rate, Hertz +// } encoder_t; + +// void encoder_init(float signal_rate, float *pulse_buffer); +// void encoder_set_f0(float f0); +// void encoder_process(const message_t *message); // in: message +// void encoder_generate(float *block); // out: block of waveforms + +/// Generate FT8 tone sequence from payload data +/// @param[in] payload - 10 byte array consisting of 77 bit payload +/// @param[out] tones - array of FT8_NN (79) bytes to store the generated tones (encoded as 0..7) +void ft8_encode(const uint8_t* payload, uint8_t* tones); + +/// Generate FT4 tone sequence from payload data +/// @param[in] payload - 10 byte array consisting of 77 bit payload +/// @param[out] tones - array of FT4_NN (105) bytes to store the generated tones (encoded as 0..3) +void ft4_encode(const uint8_t* payload, uint8_t* tones); + +#endif // _INCLUDE_ENCODE_H_ diff --git a/ft8/pack.c b/ft8/pack.c new file mode 100644 index 00000000..c00a0d15 --- /dev/null +++ b/ft8/pack.c @@ -0,0 +1,366 @@ +#include "pack.h" +#include "text.h" + +#include +#include +#include +#include + +#define NTOKENS ((uint32_t)2063592L) +#define MAX22 ((uint32_t)4194304L) +#define MAXGRID4 ((uint16_t)32400) + +// TODO: This is wasteful, should figure out something more elegant +const char A0[] = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?"; +const char A1[] = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; +const char A2[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; +const char A3[] = "0123456789"; +const char A4[] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +// Pack a special token, a 22-bit hash code, or a valid base call +// into a 28-bit integer. +int32_t pack28(const char* callsign) +{ + // Check for special tokens first + if (starts_with(callsign, "DE ")) + return 0; + if (starts_with(callsign, "QRZ ")) + return 1; + if (starts_with(callsign, "CQ ")) + return 2; + + if (starts_with(callsign, "CQ_")) + { + int nnum = 0, nlet = 0; + + // TODO: + } + + // TODO: Check for <...> callsign + + char c6[6] = { ' ', ' ', ' ', ' ', ' ', ' ' }; + + int length = 0; // strlen(callsign); // We will need it later + while (callsign[length] != ' ' && callsign[length] != 0) + { + length++; + } + + // Copy callsign to 6 character buffer + if (starts_with(callsign, "3DA0") && length <= 7) + { + // Work-around for Swaziland prefix: 3DA0XYZ -> 3D0XYZ + memcpy(c6, "3D0", 3); + memcpy(c6 + 3, callsign + 4, length - 4); + } + else if (starts_with(callsign, "3X") && is_letter(callsign[2]) && length <= 7) + { + // Work-around for Guinea prefixes: 3XA0XYZ -> QA0XYZ + memcpy(c6, "Q", 1); + memcpy(c6 + 1, callsign + 2, length - 2); + } + else + { + if (is_digit(callsign[2]) && length <= 6) + { + // AB0XYZ + memcpy(c6, callsign, length); + } + else if (is_digit(callsign[1]) && length <= 5) + { + // A0XYZ -> " A0XYZ" + memcpy(c6 + 1, callsign, length); + } + } + + // Check for standard callsign + int i0, i1, i2, i3, i4, i5; + if ((i0 = char_index(A1, c6[0])) >= 0 && (i1 = char_index(A2, c6[1])) >= 0 && (i2 = char_index(A3, c6[2])) >= 0 && (i3 = char_index(A4, c6[3])) >= 0 && (i4 = char_index(A4, c6[4])) >= 0 && (i5 = char_index(A4, c6[5])) >= 0) + { + // This is a standard callsign + int32_t n28 = i0; + n28 = n28 * 36 + i1; + n28 = n28 * 10 + i2; + n28 = n28 * 27 + i3; + n28 = n28 * 27 + i4; + n28 = n28 * 27 + i5; + return NTOKENS + MAX22 + n28; + } + + //char text[13]; + //if (length > 13) return -1; + + // TODO: + // Treat this as a nonstandard callsign: compute its 22-bit hash + return -1; +} + +// Check if a string could be a valid standard callsign or a valid +// compound callsign. +// Return base call "bc" and a logical "cok" indicator. +bool chkcall(const char* call, char* bc) +{ + int length = strlen(call); // n1=len_trim(w) + if (length > 11) + return false; + if (0 != strchr(call, '.')) + return false; + if (0 != strchr(call, '+')) + return false; + if (0 != strchr(call, '-')) + return false; + if (0 != strchr(call, '?')) + return false; + if (length > 6 && 0 != strchr(call, '/')) + return false; + + // TODO: implement suffix parsing (or rework?) + + return true; +} + +uint16_t packgrid(const char* grid4) +{ + if (grid4 == 0) + { + // Two callsigns only, no report/grid + return MAXGRID4 + 1; + } + + // Take care of special cases + if (equals(grid4, "RRR")) + return MAXGRID4 + 2; + if (equals(grid4, "RR73")) + return MAXGRID4 + 3; + if (equals(grid4, "73")) + return MAXGRID4 + 4; + + // Check for standard 4 letter grid + if (in_range(grid4[0], 'A', 'R') && in_range(grid4[1], 'A', 'R') && is_digit(grid4[2]) && is_digit(grid4[3])) + { + uint16_t igrid4 = (grid4[0] - 'A'); + igrid4 = igrid4 * 18 + (grid4[1] - 'A'); + igrid4 = igrid4 * 10 + (grid4[2] - '0'); + igrid4 = igrid4 * 10 + (grid4[3] - '0'); + return igrid4; + } + + // Parse report: +dd / -dd / R+dd / R-dd + // TODO: check the range of dd + if (grid4[0] == 'R') + { + int dd = dd_to_int(grid4 + 1, 3); + uint16_t irpt = 35 + dd; + return (MAXGRID4 + irpt) | 0x8000; // ir = 1 + } + else + { + int dd = dd_to_int(grid4, 3); + uint16_t irpt = 35 + dd; + return (MAXGRID4 + irpt); // ir = 0 + } + + return MAXGRID4 + 1; +} + +// Pack Type 1 (Standard 77-bit message) and Type 2 (ditto, with a "/P" call) +int pack77_1(const char* msg, uint8_t* b77) +{ + // Locate the first delimiter + const char* s1 = strchr(msg, ' '); + if (s1 == 0) + return -1; + + const char* call1 = msg; // 1st call + const char* call2 = s1 + 1; // 2nd call + + int32_t n28a = pack28(call1); + int32_t n28b = pack28(call2); + + if (n28a < 0 || n28b < 0) + return -1; + + uint16_t igrid4; + + // Locate the second delimiter + const char* s2 = strchr(s1 + 1, ' '); + if (s2 != 0) + { + igrid4 = packgrid(s2 + 1); + } + else + { + // Two callsigns, no grid/report + igrid4 = packgrid(0); + } + + uint8_t i3 = 1; // No suffix or /R + + // TODO: check for suffixes + + // Shift in ipa and ipb bits into n28a and n28b + n28a <<= 1; // ipa = 0 + n28b <<= 1; // ipb = 0 + + // Pack into (28 + 1) + (28 + 1) + (1 + 15) + 3 bits + b77[0] = (n28a >> 21); + b77[1] = (n28a >> 13); + b77[2] = (n28a >> 5); + b77[3] = (uint8_t)(n28a << 3) | (uint8_t)(n28b >> 26); + b77[4] = (n28b >> 18); + b77[5] = (n28b >> 10); + b77[6] = (n28b >> 2); + b77[7] = (uint8_t)(n28b << 6) | (uint8_t)(igrid4 >> 10); + b77[8] = (igrid4 >> 2); + b77[9] = (uint8_t)(igrid4 << 6) | (uint8_t)(i3 << 3); + + return 0; +} + +void packtext77(const char* text, uint8_t* b77) +{ + int length = strlen(text); + + // Skip leading and trailing spaces + while (*text == ' ' && *text != 0) + { + ++text; + --length; + } + while (length > 0 && text[length - 1] == ' ') + { + --length; + } + + // Clear the first 72 bits representing a long number + for (int i = 0; i < 9; ++i) + { + b77[i] = 0; + } + + // Now express the text as base-42 number stored + // in the first 72 bits of b77 + for (int j = 0; j < 13; ++j) + { + // Multiply the long integer in b77 by 42 + uint16_t x = 0; + for (int i = 8; i >= 0; --i) + { + x += b77[i] * (uint16_t)42; + b77[i] = (x & 0xFF); + x >>= 8; + } + + // Get the index of the current char + if (j < length) + { + int q = char_index(A0, text[j]); + x = (q > 0) ? q : 0; + } + else + { + x = 0; + } + // Here we double each added number in order to have the result multiplied + // by two as well, so that it's a 71 bit number left-aligned in 72 bits (9 bytes) + x <<= 1; + + // Now add the number to our long number + for (int i = 8; i >= 0; --i) + { + if (x == 0) + break; + x += b77[i]; + b77[i] = (x & 0xFF); + x >>= 8; + } + } + + // Set n3=0 (bits 71..73) and i3=0 (bits 74..76) + b77[8] &= 0xFE; + b77[9] &= 0x00; +} + +int pack77(const char* msg, uint8_t* c77) +{ + // Check Type 1 (Standard 77-bit message) or Type 2, with optional "/P" + if (0 == pack77_1(msg, c77)) + { + return 0; + } + + // TODO: + // Check 0.5 (telemetry) + + // Check Type 4 (One nonstandard call and one hashed call) + + // Default to free text + // i3=0 n3=0 + packtext77(msg, c77); + return 0; +} + +#ifdef UNIT_TEST + +#include + +bool test1() +{ + const char* inputs[] = { + "", + " ", + "ABC", + "A9", + "L9A", + "L7BC", + "L0ABC", + "LL3JG", + "LL3AJG", + "CQ ", + 0 + }; + + for (int i = 0; inputs[i]; ++i) + { + int32_t result = ft8_v2::pack28(inputs[i]); + printf("pack28(\"%s\") = %d\n", inputs[i], result); + } + + return true; +} + +bool test2() +{ + const char* inputs[] = { + "CQ LL3JG", + "CQ LL3JG KO26", + "L0UAA LL3JG KO26", + "L0UAA LL3JG +02", + "L0UAA LL3JG RRR", + "L0UAA LL3JG 73", + 0 + }; + + for (int i = 0; inputs[i]; ++i) + { + uint8_t result[10]; + int rc = ft8_v2::pack77_1(inputs[i], result); + printf("pack77_1(\"%s\") = %d\t[", inputs[i], rc); + for (int j = 0; j < 10; ++j) + { + printf("%02x ", result[j]); + } + printf("]\n"); + } + + return true; +} + +int main() +{ + test1(); + test2(); + return 0; +} + +#endif \ No newline at end of file diff --git a/ft8/pack.h b/ft8/pack.h new file mode 100644 index 00000000..4c03c9ec --- /dev/null +++ b/ft8/pack.h @@ -0,0 +1,11 @@ +#ifndef _INCLUDE_PACK_H_ +#define _INCLUDE_PACK_H_ + +#include + +// Pack FT8 text message into 72 bits +// [IN] msg - FT8 message (e.g. "CQ TE5T KN01") +// [OUT] c77 - 10 byte array to store the 77 bit payload (MSB first) +int pack77(const char* msg, uint8_t* c77); + +#endif // _INCLUDE_PACK_H_ diff --git a/ft8/text.c b/ft8/text.c new file mode 100644 index 00000000..68393a91 --- /dev/null +++ b/ft8/text.c @@ -0,0 +1,253 @@ +#include "text.h" + +#include + +const char* trim_front(const char* str) +{ + // Skip leading whitespace + while (*str == ' ') + { + str++; + } + return str; +} + +void trim_back(char* str) +{ + // Skip trailing whitespace by replacing it with '\0' characters + int idx = strlen(str) - 1; + while (idx >= 0 && str[idx] == ' ') + { + str[idx--] = '\0'; + } +} + +// 1) trims a string from the back by changing whitespaces to '\0' +// 2) trims a string from the front by skipping whitespaces +char* trim(char* str) +{ + str = (char*)trim_front(str); + trim_back(str); + // return a pointer to the first non-whitespace character + return str; +} + +char to_upper(char c) +{ + return (c >= 'a' && c <= 'z') ? (c - 'a' + 'A') : c; +} + +bool is_digit(char c) +{ + return (c >= '0') && (c <= '9'); +} + +bool is_letter(char c) +{ + return ((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')); +} + +bool is_space(char c) +{ + return (c == ' '); +} + +bool in_range(char c, char min, char max) +{ + return (c >= min) && (c <= max); +} + +bool starts_with(const char* string, const char* prefix) +{ + return 0 == memcmp(string, prefix, strlen(prefix)); +} + +bool equals(const char* string1, const char* string2) +{ + return 0 == strcmp(string1, string2); +} + +int char_index(const char* string, char c) +{ + for (int i = 0; *string; ++i, ++string) + { + if (c == *string) + { + return i; + } + } + return -1; // Not found +} + +// Text message formatting: +// - replaces lowercase letters with uppercase +// - merges consecutive spaces into single space +void fmtmsg(char* msg_out, const char* msg_in) +{ + char c; + char last_out = 0; + while ((c = *msg_in)) + { + if (c != ' ' || last_out != ' ') + { + last_out = to_upper(c); + *msg_out = last_out; + ++msg_out; + } + ++msg_in; + } + *msg_out = 0; // Add zero termination +} + +// Parse a 2 digit integer from string +int dd_to_int(const char* str, int length) +{ + int result = 0; + bool negative; + int i; + if (str[0] == '-') + { + negative = true; + i = 1; // Consume the - sign + } + else + { + negative = false; + i = (str[0] == '+') ? 1 : 0; // Consume a + sign if found + } + + while (i < length) + { + if (str[i] == 0) + break; + if (!is_digit(str[i])) + break; + result *= 10; + result += (str[i] - '0'); + ++i; + } + + return negative ? -result : result; +} + +// Convert a 2 digit integer to string +void int_to_dd(char* str, int value, int width, bool full_sign) +{ + if (value < 0) + { + *str = '-'; + ++str; + value = -value; + } + else if (full_sign) + { + *str = '+'; + ++str; + } + + int divisor = 1; + for (int i = 0; i < width - 1; ++i) + { + divisor *= 10; + } + + while (divisor >= 1) + { + int digit = value / divisor; + + *str = '0' + digit; + ++str; + + value -= digit * divisor; + divisor /= 10; + } + *str = 0; // Add zero terminator +} + +// convert integer index to ASCII character according to one of 6 tables: +// table 0: " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?" +// table 1: " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" +// table 2: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" +// table 3: "0123456789" +// table 4: " ABCDEFGHIJKLMNOPQRSTUVWXYZ" +// table 5: " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ/" +char charn(int c, int table_idx) +{ + if (table_idx != 2 && table_idx != 3) + { + if (c == 0) + return ' '; + c -= 1; + } + if (table_idx != 4) + { + if (c < 10) + return '0' + c; + c -= 10; + } + if (table_idx != 3) + { + if (c < 26) + return 'A' + c; + c -= 26; + } + + if (table_idx == 0) + { + if (c < 5) + return "+-./?"[c]; + } + else if (table_idx == 5) + { + if (c == 0) + return '/'; + } + + return '_'; // unknown character, should never get here +} + +// Convert character to its index (charn in reverse) according to a table +int nchar(char c, int table_idx) +{ + int n = 0; + if (table_idx != 2 && table_idx != 3) + { + if (c == ' ') + return n + 0; + n += 1; + } + if (table_idx != 4) + { + if (c >= '0' && c <= '9') + return n + (c - '0'); + n += 10; + } + if (table_idx != 3) + { + if (c >= 'A' && c <= 'Z') + return n + (c - 'A'); + n += 26; + } + + if (table_idx == 0) + { + if (c == '+') + return n + 0; + if (c == '-') + return n + 1; + if (c == '.') + return n + 2; + if (c == '/') + return n + 3; + if (c == '?') + return n + 4; + } + else if (table_idx == 5) + { + if (c == '/') + return n + 0; + } + + // Character not found + return -1; +} diff --git a/ft8/text.h b/ft8/text.h new file mode 100644 index 00000000..aac99213 --- /dev/null +++ b/ft8/text.h @@ -0,0 +1,37 @@ +#ifndef _INCLUDE_TEXT_H_ +#define _INCLUDE_TEXT_H_ + +#include +#include + +// Utility functions for characters and strings + +const char* trim_front(const char* str); +void trim_back(char* str); +char* trim(char* str); + +char to_upper(char c); +bool is_digit(char c); +bool is_letter(char c); +bool is_space(char c); +bool in_range(char c, char min, char max); +bool starts_with(const char* string, const char* prefix); +bool equals(const char* string1, const char* string2); + +int char_index(const char* string, char c); + +// Text message formatting: +// - replaces lowercase letters with uppercase +// - merges consecutive spaces into single space +void fmtmsg(char* msg_out, const char* msg_in); + +// Parse a 2 digit integer from string +int dd_to_int(const char* str, int length); + +// Convert a 2 digit integer to string +void int_to_dd(char* str, int value, int width, bool full_sign); + +char charn(int c, int table_idx); +int nchar(char c, int table_idx); + +#endif // _INCLUDE_TEXT_H_ diff --git a/gui_bar.cpp b/gui_bar.cpp index 38f8d7d3..08539d61 100644 --- a/gui_bar.cpp +++ b/gui_bar.cpp @@ -130,7 +130,7 @@ static void bar_button_handler(lv_event_t * e) // Noise if (lv_obj_get_state(obj) & LV_STATE_CHECKED) { - select_mode(mode_echo, true); + } else { @@ -155,7 +155,8 @@ static void if_slider_event_cb(lv_event_t *e) { lv_obj_t *slider = lv_event_get_target(e); lv_label_set_text_fmt(gbar.get_if_slider_label(), "if %d db", lv_slider_get_value(slider)); - gbar.m_if = 10 * lv_slider_get_value(slider); + int sl = lv_slider_get_value(slider); + gbar.m_if = std::pow(10.0, (float)sl / 20.0); Settings_file.save_ifgain(lv_slider_get_value(slider)); } @@ -467,9 +468,15 @@ void gui_bar::init(lv_obj_t *o_parent, lv_group_t *button_group, int mode, lv_co { if (SdrDevices.SdrDevices.at(default_radio)->rx_channels.at(default_rx_channel)->get_agc()) { - bool bAgc = SdrDevices.SdrDevices.at(default_radio)->getGainMode(SOAPY_SDR_RX, default_rx_channel); - if (bAgc) - lv_obj_add_state(button[9], LV_STATE_CHECKED); + string sagc = Settings_file.get_string(default_radio, "AGC"); + if (sagc == "off") + SdrDevices.SdrDevices.at(default_radio)->setGainMode(SOAPY_SDR_RX, default_rx_channel, false); + else + { + bool bAgc = SdrDevices.SdrDevices.at(default_radio)->getGainMode(SOAPY_SDR_RX, default_rx_channel); + if (bAgc) + lv_obj_add_state(button[9], LV_STATE_CHECKED); + } } } catch (const std::exception& e) @@ -646,10 +653,10 @@ float gui_bar::get_if() void gui_bar::set_if(int rf) { + m_if = std::pow(10.0, (float)rf / 20.0); lv_slider_set_value(if_slider, rf, LV_ANIM_ON); lv_label_set_text_fmt(if_slider_label, "if %d db", rf); Settings_file.save_ifgain(rf); - m_if = std::pow(10.0,(float)rf / 20.0); } void gui_bar::get_filter_range(vector &filters) @@ -710,4 +717,11 @@ void gui_bar::get_gain_range(int &max_gain, int &min_gain) min_gain = 0; } return; +} + +int gui_bar::get_noise() +{ + if (lv_obj_get_state(button[10]) & LV_STATE_CHECKED) + return guirx.get_noise() + 1; + return 0; } \ No newline at end of file diff --git a/gui_bar.h b/gui_bar.h index e4233a98..1a9a59bd 100644 --- a/gui_bar.h +++ b/gui_bar.h @@ -44,6 +44,7 @@ class gui_bar void set_tx(bool tx); void get_gain_range(int &max_gain, int &min_gain); int get_rf_gain() {return lv_slider_get_value(gain_slider); } + int get_noise(); lv_obj_t *get_button_obj(int i) { diff --git a/gui_rx.cpp b/gui_rx.cpp new file mode 100644 index 00000000..4b9d94c7 --- /dev/null +++ b/gui_rx.cpp @@ -0,0 +1,181 @@ +#include "gui_rx.h" + +gui_rx guirx; + +static void rx_button_handler(lv_event_t *e) +{ + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t *obj = lv_event_get_target(e); + if (code == LV_EVENT_CLICKED) + { + for (auto con : guirx.get_buttons()) + { + if (con.first == obj) + { + if (con.second > 0L) + vfo.set_vfo(con.second, false); + } + else + { + lv_obj_clear_state(con.first, LV_STATE_CHECKED); + } + } + } + if (code == LV_EVENT_LONG_PRESSED) + { + for (auto con : guirx.get_buttons()) + { + if (con.first == obj) + { + vector array; + guirx.set_freq(con.first, vfo.get_frequency()); + guirx.get_buttons(array); + Settings_file.set_array_long("preselect", "buttons", array); + } + else + { + } + } + } + +} + +vector> gui_rx::get_buttons() +{ + return buttons; +} + +void gui_rx::get_buttons(vector &array) +{ + for (auto con : buttons) + { + array.push_back(con.second); + } +} + +void noise_handler(lv_event_t *e) +{ + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t *obj = lv_event_get_target(e); + if (code == LV_EVENT_VALUE_CHANGED) + { + int noise = guirx.get_noise(); + Settings_file.save_int("Radio", "noise", noise); + } +} + +void gui_rx::set_freq(lv_obj_t *obj, long long freq) +{ + for (auto &con : buttons) + { + if (con.first == obj) + con.second = freq; + } +} + +int gui_rx::get_noise() +{ + return lv_dropdown_get_selected(drp_noise); +} + +void gui_rx::init(lv_obj_t *o_tab, lv_coord_t w) +{ + + const lv_coord_t x_margin = 10; + const lv_coord_t y_margin = 5; + const int x_number_buttons = 5; + const int y_number_buttons = 4; + const lv_coord_t tab_margin = 20; + + int button_width_margin = ((w - tab_margin) / x_number_buttons); + int button_width = ((w - tab_margin) / x_number_buttons) - x_margin; + int button_height = 50; + int button_height_margin = button_height + y_margin; + int ibutton_x = 0, ibutton_y = 0; + + lv_style_init(&style_btn); + lv_style_set_radius(&style_btn, 10); + lv_style_set_bg_color(&style_btn, lv_color_make(0x60, 0x60, 0x60)); + lv_style_set_bg_grad_color(&style_btn, lv_color_make(0x00, 0x00, 0x00)); + lv_style_set_bg_grad_dir(&style_btn, LV_GRAD_DIR_VER); + lv_style_set_bg_opa(&style_btn, 255); + lv_style_set_border_color(&style_btn, lv_color_make(0x9b, 0x36, 0x36)); // lv_color_make(0x2e, 0x44, 0xb2) + lv_style_set_border_width(&style_btn, 2); + lv_style_set_border_opa(&style_btn, 255); + lv_style_set_outline_color(&style_btn, lv_color_black()); + lv_style_set_outline_opa(&style_btn, 255); + lv_obj_clear_flag(o_tab, LV_OBJ_FLAG_SCROLLABLE); + + m_button_group = lv_group_create(); + int ibuttons = number_of_buttons; + vector array_long; + Settings_file.get_array_long("preselect", "buttons", array_long); + for (int i = 0; i < ibuttons; i++) + { + char str[80]; + + if (i < array_long.size()) + buttons.push_back(std::make_pair(lv_btn_create(o_tab), (long long)array_long.at(i))); + else + buttons.push_back(std::make_pair(lv_btn_create(o_tab), (long long)0)); + lv_group_add_obj(m_button_group, buttons.back().first); + lv_obj_add_style(buttons.back().first, &style_btn, 0); + lv_obj_add_event_cb(buttons.back().first, rx_button_handler, (lv_event_code_t)LV_EVENT_ALL /*(LV_EVENT_CLICKED | LV_EVENT_LONG_PRESSED)*/, NULL); + lv_obj_align(buttons.back().first, LV_ALIGN_TOP_LEFT, ibutton_x * button_width_margin, ibutton_y * button_height_margin); + //lv_obj_add_flag(button[i], LV_OBJ_FLAG_CHECKABLE); + lv_obj_set_size(buttons.back().first, button_width, button_height); + + lv_obj_t *lv_label = lv_label_create(buttons.back().first); + switch (i) + { + case 0: + strcpy(str, "Pre 1"); + lv_obj_add_flag(buttons.back().first, LV_OBJ_FLAG_CHECKABLE); + break; + case 1: + strcpy(str, "Pre 2"); + lv_obj_add_flag(buttons.back().first, LV_OBJ_FLAG_CHECKABLE); + break; + case 2: + strcpy(str, "Pre 3"); + lv_obj_add_flag(buttons.back().first, LV_OBJ_FLAG_CHECKABLE); + break; + case 3: + strcpy(str, "Pre 4"); + lv_obj_add_flag(buttons.back().first, LV_OBJ_FLAG_CHECKABLE); + break; + case 4: + strcpy(str, "Pre 5"); + lv_obj_add_flag(buttons.back().first, LV_OBJ_FLAG_CHECKABLE); + break; + } + lv_label_set_text(lv_label, str); + lv_obj_center(lv_label); + + ibutton_x++; + if (ibutton_x >= x_number_buttons) + { + ibutton_x = 0; + ibutton_y++; + } + } + + lv_obj_t *noise_label = lv_label_create(o_tab); + lv_label_set_text(noise_label, "Noise suppression"); + lv_obj_align(noise_label, LV_ALIGN_TOP_LEFT, 0, y_margin + ibutton_y * button_height_margin); + + drp_noise = lv_dropdown_create(o_tab); + lv_obj_align(drp_noise, LV_ALIGN_TOP_LEFT, 0, y_margin + ibutton_y * 1.5 * button_height_margin); + lv_dropdown_clear_options(drp_noise); + lv_group_add_obj(m_button_group, drp_noise); + lv_dropdown_add_option(drp_noise, "Leaky LMS", LV_DROPDOWN_POS_LAST); + //lv_dropdown_add_option(drp_noise, "LMS", LV_DROPDOWN_POS_LAST); + lv_dropdown_add_option(drp_noise, "Spectral", LV_DROPDOWN_POS_LAST); + lv_dropdown_add_option(drp_noise, "Kim", LV_DROPDOWN_POS_LAST); + + int noise = Settings_file.get_int("Radio", "noise"); + lv_dropdown_set_selected(drp_noise, noise); + lv_obj_add_event_cb(drp_noise, noise_handler, (lv_event_code_t)LV_EVENT_VALUE_CHANGED, NULL); + + lv_group_add_obj(m_button_group, lv_tabview_get_tab_btns(tabview_mid)); +} diff --git a/gui_rx.h b/gui_rx.h new file mode 100644 index 00000000..c0c6ca9b --- /dev/null +++ b/gui_rx.h @@ -0,0 +1,26 @@ +#pragma once +#include "AudioInput.h" +#include "lvgl/lvgl.h" +#include "Settings.h" +#include "sdrberry.h" +#include "vfo.h" +#include "gui_vfo.h" + +class gui_rx +{ + public: + void init(lv_obj_t *o_tab, lv_coord_t w); + vector> get_buttons(); + void set_freq(lv_obj_t * obj, long long freq); + void get_buttons(vector &array); + int get_noise(); + + private: + lv_group_t *m_button_group{nullptr}; + lv_style_t style_btn; + vector> buttons; + const int number_of_buttons{5}; + lv_obj_t *drp_noise; +}; + +extern gui_rx guirx; \ No newline at end of file diff --git a/gui_setup.cpp b/gui_setup.cpp index 77745f73..7c0b74a9 100644 --- a/gui_setup.cpp +++ b/gui_setup.cpp @@ -23,10 +23,32 @@ static void receivers_button_handler(lv_event_t * e) } } +static void contour_slider_event_cb(lv_event_t *e) +{ + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t *obj = lv_event_get_target(e); + + int i = lv_slider_get_value(obj); + if (i > 0) + { + gsetup.set_contour_value(i); + } +} + +static void floor_slider_event_cb(lv_event_t *e) +{ + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t *obj = lv_event_get_target(e); + + int i = lv_slider_get_value(obj); + if (i > 0) + { + gsetup.set_floor_value(i); + } +} + static void span_slider_event_cb(lv_event_t * e) { - char buf[20]; - lv_event_code_t code = lv_event_get_code(e); lv_obj_t *obj = lv_event_get_target(e); @@ -68,8 +90,21 @@ static void samplerate_button_handler(lv_event_t * e) int rate = lv_dropdown_get_selected(obj); ifrate = gsetup.get_sample_rate(rate); gsetup.m_ifrate = ifrate; - vfo.vfo_re_init((long)ifrate, audio_output->get_samplerate()); - destroy_demodulators(); + + if (SdrDevices.SdrDevices[default_radio]->get_bandwith_count(0) > 0) + { + long bw = 0L; + + int sel = gsetup.get_bandwidth_sel(); + bw = SdrDevices.SdrDevices[default_radio]->get_bandwith(0, sel); + SdrDevices.SdrDevices[default_radio]->setBandwidth(SOAPY_SDR_RX, 0, bw); + vfo.vfo_re_init((long)ifrate, audio_output->get_samplerate(), bw); + printf("setBandwidth %ld \n", bw); + } + else + vfo.vfo_re_init((long)ifrate, audio_output->get_samplerate(), 0L); + + destroy_demodulators(true); try { SdrDevices.SdrDevices.at(default_radio)->setSampleRate(SOAPY_SDR_RX, default_rx_channel, ifrate); @@ -108,6 +143,23 @@ static void event_handler_morse(lv_event_t *e) } } +static void bandwidth_button_handler(lv_event_t *e) +{ + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t *obj = lv_event_get_target(e); + if (code == LV_EVENT_VALUE_CHANGED) + { + int sel = lv_dropdown_get_selected(obj); + if (sel <= SdrDevices.SdrDevices[default_radio]->get_bandwith_count(0)) + { + long bw = SdrDevices.SdrDevices[default_radio]->get_bandwith(0, sel); + SdrDevices.SdrDevices[default_radio]->setBandwidth(SOAPY_SDR_RX, 0, bw); + vfo.vfo_re_init((long)ifrate, audio_output->get_samplerate(), bw); + printf("setBandwidth %ld \n", bw); + } + } +} + void gui_setup::toggle_cw() { if (lv_obj_get_state(check_cw) & LV_STATE_CHECKED) @@ -191,6 +243,21 @@ void gui_setup::set_radio(std::string name) lv_dropdown_set_selected(d_receivers, i); } +void gui_setup::init_bandwidth() +{ + lv_dropdown_clear_options(d_bandwitdth); + for (int i = 0; i < SdrDevices.SdrDevices[default_radio]->get_bandwith_count(0); i++) + { + char buf[80]; + + long bw = SdrDevices.SdrDevices[default_radio]->get_bandwith(0, i); + sprintf(buf, "%ld Khz", bw / 1000); + lv_dropdown_add_option(d_bandwitdth, buf, LV_DROPDOWN_POS_LAST); + if (i == 0) + lv_dropdown_set_selected(d_bandwitdth, 0); + } +} + void gui_setup::init(lv_obj_t* o_tab, lv_coord_t w) { @@ -241,7 +308,7 @@ void gui_setup::init(lv_obj_t* o_tab, lv_coord_t w) d_audio = lv_dropdown_create(o_tab); lv_group_add_obj(m_button_group, d_audio); lv_obj_align(d_audio, LV_ALIGN_TOP_LEFT, 2*button_width_margin, y_margin + ibutton_y * button_height_margin); - lv_obj_set_width(d_audio, 2*button_width); + lv_obj_set_width(d_audio, 1.5 * button_width); // 2* lv_dropdown_clear_options(d_audio); lv_obj_add_event_cb(d_audio, audio_button_handler, LV_EVENT_VALUE_CHANGED, NULL); std::vector devices; @@ -258,29 +325,60 @@ void gui_setup::init(lv_obj_t* o_tab, lv_coord_t w) } } } - //int span_y = 15 + y_margin + button_height_margin; + + d_bandwitdth = lv_dropdown_create(o_tab); + lv_group_add_obj(m_button_group, d_bandwitdth); + lv_obj_align(d_bandwitdth, LV_ALIGN_TOP_LEFT, 3.5 * button_width_margin, y_margin + ibutton_y * button_height_margin); + lv_obj_set_width(d_bandwitdth, button_width); // 2* + lv_dropdown_clear_options(d_bandwitdth); + lv_obj_add_event_cb(d_bandwitdth, bandwidth_button_handler, LV_EVENT_VALUE_CHANGED, NULL); + + + check_cw = lv_checkbox_create(o_tab); + lv_group_add_obj(m_button_group, check_cw); + lv_checkbox_set_text(check_cw, "Morse Decoder"); + lv_obj_add_event_cb(check_cw, event_handler_morse, LV_EVENT_ALL, NULL); + lv_obj_align(check_cw, LV_ALIGN_TOP_LEFT, 4 * button_width_margin, y_margin + ibutton_y * button_height_margin); + ibutton_y++; int y_span = y_margin + ibutton_y * button_height_margin + button_height_margin /2; + int brightness_y = 15 + y_margin + 2 * button_height_margin; span_slider = lv_slider_create(o_tab); lv_group_add_obj(m_button_group, span_slider); lv_obj_set_width(span_slider, w / 2 - 50); //lv_obj_center(span_slider); - lv_obj_align(span_slider, LV_ALIGN_TOP_MID, 0, y_span); + //lv_obj_align(span_slider, LV_ALIGN_TOP_MID, 0, y_span); + lv_obj_align_to(span_slider, o_tab, LV_ALIGN_TOP_LEFT, 0, y_span); lv_obj_add_event_cb(span_slider, span_slider_event_cb, LV_EVENT_VALUE_CHANGED, NULL); span_slider_label = lv_label_create(o_tab); - lv_label_set_text(span_slider_label, "span"); - lv_obj_align_to(span_slider_label, span_slider, LV_ALIGN_OUT_TOP_MID, -30, -10); + lv_label_set_text(span_slider_label, "span 500 Khz"); + //lv_obj_align_to(span_slider_label, span_slider, LV_ALIGN_OUT_TOP_MID, -30, -10); + lv_obj_align_to(span_slider_label, span_slider, LV_ALIGN_OUT_TOP_MID, 0, -10); + string span = Settings_file.find_radio("span"); int i = atoi(span.c_str()); if (((i * 1000) > (ifrate / 2)) || i == 0) i = ifrate / 2000; set_span_range(ifrate/2); set_span_value(i * 1000); + + //lv_obj_t *contour_slider_label, *contour_slider; + //lv_obj_t *floor_slider_label, *floor_slider; + + contour_slider = lv_slider_create(o_tab); + lv_group_add_obj(m_button_group, contour_slider); + lv_obj_set_width(contour_slider, w / 2 - 50); + lv_obj_align_to(contour_slider, o_tab, LV_ALIGN_TOP_LEFT, w / 2, y_span); + lv_slider_set_range(contour_slider, 1, 10); - int brightness_y = 15 + y_margin + 2* button_height_margin; + lv_obj_add_event_cb(contour_slider, contour_slider_event_cb, LV_EVENT_VALUE_CHANGED, NULL); + contour_slider_label = lv_label_create(o_tab); + lv_label_set_text(contour_slider_label, "speed 1"); + lv_obj_align_to(contour_slider_label, contour_slider, LV_ALIGN_OUT_TOP_MID, 0, -10); + brightness_slider = lv_slider_create(o_tab); lv_group_add_obj(m_button_group, brightness_slider); lv_obj_set_width(brightness_slider, w / 2 - 50); @@ -293,12 +391,17 @@ void gui_setup::init(lv_obj_t* o_tab, lv_coord_t w) lv_label_set_text(brightness_slider_label, "brightness"); lv_obj_align_to(brightness_slider_label, brightness_slider, LV_ALIGN_OUT_TOP_MID, 0, -10); - check_cw = lv_checkbox_create(o_tab); - lv_group_add_obj(m_button_group, check_cw); - lv_checkbox_set_text(check_cw, "Morse Decoder"); - lv_obj_add_event_cb(check_cw, event_handler_morse, LV_EVENT_ALL, NULL); - lv_obj_align_to(check_cw, d_samplerate, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 10); - //lv_obj_add_state(check_cw, LV_STATE_CHECKED); + floor_slider = lv_slider_create(o_tab); + lv_group_add_obj(m_button_group, floor_slider); + lv_obj_set_width(floor_slider, w / 2 - 50); + lv_obj_align_to(floor_slider, span_slider, LV_ALIGN_OUT_BOTTOM_MID, w / 2, 40); + lv_slider_set_range(floor_slider, 1, 20); + + lv_obj_add_event_cb(floor_slider, floor_slider_event_cb, LV_EVENT_VALUE_CHANGED, NULL); + floor_slider_label = lv_label_create(o_tab); + lv_label_set_text(floor_slider_label, "Noise floor 1"); + lv_obj_align_to(floor_slider_label, floor_slider, LV_ALIGN_OUT_TOP_MID, 0, -10); + lv_group_add_obj(m_button_group, lv_tabview_get_tab_btns(tabview_mid)); } @@ -376,6 +479,27 @@ void gui_setup::set_brightness(int brightness) myfile << std::string(buf); } +void gui_setup::set_contour_value(int speed) +{ + char buf[20]; + + sprintf(buf, " speed %d ",speed); + lv_label_set_text(contour_slider_label, buf); +} + +int gui_setup::get_contour_value() +{ + return lv_slider_get_value(contour_slider); +} + +void gui_setup::set_floor_value(int speed) +{ + char buf[20]; + + sprintf(buf, " noise floor %d ", speed); + lv_label_set_text(floor_slider_label, buf); +} + int gui_setup::get_brightness() { int brightness; diff --git a/gui_setup.h b/gui_setup.h index 2ebbab89..4be270ca 100644 --- a/gui_setup.h +++ b/gui_setup.h @@ -31,7 +31,11 @@ class gui_setup void set_cw(bool bcw); void toggle_cw(); void set_group(); - + void set_contour_value(int speed); + void set_floor_value(int floor); + int get_contour_value(); + void init_bandwidth(); + int get_bandwidth_sel() { return lv_dropdown_get_selected(d_bandwitdth); } double m_ifrate; private: @@ -41,9 +45,11 @@ class gui_setup lv_obj_t* d_receivers; lv_obj_t* span_slider_label, *span_slider; lv_obj_t* brightness_slider_label, *brightness_slider; - lv_obj_t* d_audio, *check_cw; + lv_obj_t* d_audio, *check_cw, *d_bandwitdth; atomic m_span; lv_group_t *m_button_group{nullptr}; + lv_obj_t *contour_slider_label, *contour_slider; + lv_obj_t *floor_slider_label, *floor_slider; }; extern gui_setup gsetup; \ No newline at end of file diff --git a/install/README b/install/README deleted file mode 100644 index f848a7fc..00000000 --- a/install/README +++ /dev/null @@ -1,3804 +0,0 @@ -Introduction -============ - -This directory contains Device Tree overlays. Device Tree makes it possible -to support many hardware configurations with a single kernel and without the -need to explicitly load or blacklist kernel modules. Note that this isn't a -"pure" Device Tree configuration (c.f. MACH_BCM2835) - some on-board devices -are still configured by the board support code, but the intention is to -eventually reach that goal. - -On Raspberry Pi, Device Tree usage is controlled from /boot/config.txt. By -default, the Raspberry Pi kernel boots with device tree enabled. You can -completely disable DT usage (for now) by adding: - - device_tree= - -to your config.txt, which should cause your Pi to revert to the old way of -doing things after a reboot. - -In /boot you will find a .dtb for each base platform. This describes the -hardware that is part of the Raspberry Pi board. The loader (start.elf and its -siblings) selects the .dtb file appropriate for the platform by name, and reads -it into memory. At this point, all of the optional interfaces (i2c, i2s, spi) -are disabled, but they can be enabled using Device Tree parameters: - - dtparam=i2c=on,i2s=on,spi=on - -However, this shouldn't be necessary in many use cases because loading an -overlay that requires one of those interfaces will cause it to be enabled -automatically, and it is advisable to only enable interfaces if they are -needed. - -Configuring additional, optional hardware is done using Device Tree overlays -(see below). - -GPIO numbering uses the hardware pin numbering scheme (aka BCM scheme) and -not the physical pin numbers. - -raspi-config -============ - -The Advanced Options section of the raspi-config utility can enable and disable -Device Tree use, as well as toggling the I2C and SPI interfaces. Note that it -is possible to both enable an interface and blacklist the driver, if for some -reason you should want to defer the loading. - -Modules -======= - -As well as describing the hardware, Device Tree also gives enough information -to allow suitable driver modules to be located and loaded, with the corollary -that unneeded modules are not loaded. As a result it should be possible to -remove lines from /etc/modules, and /etc/modprobe.d/raspi-blacklist.conf can -have its contents deleted (or commented out). - -Using Overlays -============== - -Overlays are loaded using the "dtoverlay" config.txt setting. As an example, -consider I2C Real Time Clock drivers. In the pre-DT world these would be loaded -by writing a magic string comprising a device identifier and an I2C address to -a special file in /sys/class/i2c-adapter, having first loaded the driver for -the I2C interface and the RTC device - something like this: - - modprobe i2c-bcm2835 - modprobe rtc-ds1307 - echo ds1307 0x68 > /sys/class/i2c-adapter/i2c-1/new_device - -With DT enabled, this becomes a line in config.txt: - - dtoverlay=i2c-rtc,ds1307 - -This causes the file /boot/overlays/i2c-rtc.dtbo to be loaded and a "node" -describing the DS1307 I2C device to be added to the Device Tree for the Pi. By -default it usees address 0x68, but this can be modified with an additional DT -parameter: - - dtoverlay=i2c-rtc,ds1307,addr=0x68 - -Parameters usually have default values, although certain parameters are -mandatory. See the list of overlays below for a description of the parameters -and their defaults. - -Making new Overlays based on existing Overlays -============================================== - -Recent overlays have been designed in a more general way, so that they can be -adapted to hardware by changing their parameters. When you have additional -hardware with more than one device of a kind, you end up using the same overlay -multiple times with other parameters, e.g. - - # 2 CAN FD interfaces on spi but with different pins - dtoverlay=mcp251xfd,spi0-0,interrupt=25 - dtoverlay=mcp251xfd,spi0-1,interrupt=24 - - # a realtime clock on i2c - dtoverlay=i2c-rtc,pcf85063 - -While this approach does work, it requires knowledge about the hardware design. -It is more feasible to simplify things for the end user by providing a single -overlay as it is done the traditional way. - -A new overlay can be generated by using ovmerge utility. -https://github.com/raspberrypi/utils/blob/master/ovmerge/ovmerge - -To generate an overlay for the above configuration we pass the configuration -to ovmerge and add the -c flag. - - ovmerge -c mcp251xfd-overlay.dts,spi0-0,interrupt=25 \ - mcp251xfd-overlay.dts,spi0-1,interrupt=24 \ - i2c-rtc-overlay.dts,pcf85063 \ - >> merged-overlay.dts - -The -c option writes the command above as a comment into the overlay as -a marker that this overlay is generated and how it was generated. -After compiling the overlay it can be loaded in a single line. - - dtoverlay=merged - -It does the same as the original configuration but without parameters. - -The Overlay and Parameter Reference -=================================== - -N.B. When editing this file, please preserve the indentation levels to make it -simple to parse programmatically. NO HARD TABS. - - -Name: -Info: Configures the base Raspberry Pi hardware -Load: -Params: - ant1 Select antenna 1 (default). CM4 only. - - ant2 Select antenna 2. CM4 only. - - noant Disable both antennas. CM4 only. - - audio Set to "on" to enable the onboard ALSA audio - interface (default "off") - - axiperf Set to "on" to enable the AXI bus performance - monitors. - See /sys/kernel/debug/raspberrypi_axi_monitor - for the results. - - cam0_reg Enables CAM 0 regulator. CM1 & 3 only. - - cam0_reg_gpio Set GPIO for CAM 0 regulator. Default 30. - CM1 & 3 only. - - cam1_reg Enables CAM 1 regulator. CM1 & 3 only. - - cam1_reg_gpio Set GPIO for CAM 1 regulator. Default 2. - CM1 & 3 only. - - eee Enable Energy Efficient Ethernet support for - compatible devices (default "on"). See also - "tx_lpi_timer". Pi3B+ only. - - eth_downshift_after Set the number of auto-negotiation failures - after which the 1000Mbps modes are disabled. - Legal values are 2, 3, 4, 5 and 0, where - 0 means never downshift (default 2). Pi3B+ only. - - eth_led0 Set mode of LED0 - amber on Pi3B+ (default "1"), - green on Pi4 (default "0"). - The legal values are: - - Pi3B+ - - 0=link/activity 1=link1000/activity - 2=link100/activity 3=link10/activity - 4=link100/1000/activity 5=link10/1000/activity - 6=link10/100/activity 14=off 15=on - - Pi4 - - 0=Speed/Activity 1=Speed - 2=Flash activity 3=FDX - 4=Off 5=On - 6=Alt 7=Speed/Flash - 8=Link 9=Activity - - eth_led1 Set mode of LED1 - green on Pi3B+ (default "6"), - amber on Pi4 (default "8"). See eth_led0 for - legal values. - - eth_max_speed Set the maximum speed a link is allowed - to negotiate. Legal values are 10, 100 and - 1000 (default 1000). Pi3B+ only. - - i2c_arm Set to "on" to enable the ARM's i2c interface - (default "off") - - i2c_vc Set to "on" to enable the i2c interface - usually reserved for the VideoCore processor - (default "off") - - i2c An alias for i2c_arm - - i2c_arm_baudrate Set the baudrate of the ARM's i2c interface - (default "100000") - - i2c_vc_baudrate Set the baudrate of the VideoCore i2c interface - (default "100000") - - i2c_baudrate An alias for i2c_arm_baudrate - - i2s Set to "on" to enable the i2s interface - (default "off") - - krnbt Set to "on" to enable autoprobing of Bluetooth - driver without need of hciattach/btattach - (default "off") - - krnbt_baudrate Set the baudrate of the PL011 UART when used - with krnbt=on - - spi Set to "on" to enable the spi interfaces - (default "off") - - spi_dma4 Use to enable 40-bit DMA on spi interfaces - (the assigned value doesn't matter) - (2711 only) - - random Set to "on" to enable the hardware random - number generator (default "on") - - sd_overclock Clock (in MHz) to use when the MMC framework - requests 50MHz - - sd_poll_once Looks for a card once after booting. Useful - for network booting scenarios to avoid the - overhead of continuous polling. N.B. Using - this option restricts the system to using a - single card per boot (or none at all). - (default off) - - sd_force_pio Disable DMA support for SD driver (default off) - - sd_pio_limit Number of blocks above which to use DMA for - SD card (default 1) - - sd_debug Enable debug output from SD driver (default off) - - sdio_overclock Clock (in MHz) to use when the MMC framework - requests 50MHz for the SDIO/WLAN interface. - - tx_lpi_timer Set the delay in microseconds between going idle - and entering the low power state (default 600). - Requires EEE to be enabled - see "eee". - - uart0 Set to "off" to disable uart0 (default "on") - - uart1 Set to "on" or "off" to enable or disable uart1 - (default varies) - - watchdog Set to "on" to enable the hardware watchdog - (default "off") - - act_led_trigger Choose which activity the LED tracks. - Use "heartbeat" for a nice load indicator. - (default "mmc") - - act_led_activelow Set to "on" to invert the sense of the LED - (default "off") - N.B. For Pi 3B, 3B+, 3A+ and 4B, use the act-led - overlay. - - act_led_gpio Set which GPIO to use for the activity LED - (in case you want to connect it to an external - device) - (default "16" on a non-Plus board, "47" on a - Plus or Pi 2) - N.B. For Pi 3B, 3B+, 3A+ and 4B, use the act-led - overlay. - - pwr_led_trigger - pwr_led_activelow - pwr_led_gpio - As for act_led_*, but using the PWR LED. - Not available on Model A/B boards. - - N.B. It is recommended to only enable those interfaces that are needed. - Leaving all interfaces enabled can lead to unwanted behaviour (i2c_vc - interfering with Pi Camera, I2S and SPI hogging GPIO pins, etc.) - Note also that i2c, i2c_arm and i2c_vc are aliases for the physical - interfaces i2c0 and i2c1. Use of the numeric variants is still possible - but deprecated because the ARM/VC assignments differ between board - revisions. The same board-specific mapping applies to i2c_baudrate, - and the other i2c baudrate parameters. - - -Name: act-led -Info: Pi 3B, 3B+, 3A+ and 4B use a GPIO expander to drive the LEDs which can - only be accessed from the VPU. There is a special driver for this with a - separate DT node, which has the unfortunate consequence of breaking the - act_led_gpio and act_led_activelow dtparams. - This overlay changes the GPIO controller back to the standard one and - restores the dtparams. -Load: dtoverlay=act-led,= -Params: activelow Set to "on" to invert the sense of the LED - (default "off") - - gpio Set which GPIO to use for the activity LED - (in case you want to connect it to an external - device) - REQUIRED - - -Name: adafruit-st7735r -Info: Overlay for the SPI-connected Adafruit 1.8" 160x128 or 128x128 displays, - based on the ST7735R chip. - This overlay uses the newer DRM/KMS "Tiny" driver. -Load: dtoverlay=adafruit-st7735r,= -Params: 128x128 Select the 128x128 driver (default 160x128) - rotate Display rotation {0,90,180,270} (default 90) - speed SPI bus speed in Hz (default 4000000) - dc_pin GPIO pin for D/C (default 24) - reset_pin GPIO pin for RESET (default 25) - led_pin GPIO used to control backlight (default 18) - - -Name: adafruit18 -Info: Overlay for the SPI-connected Adafruit 1.8" display (based on the - ST7735R chip). It includes support for the "green tab" version. - This overlay uses the older fbtft driver. -Load: dtoverlay=adafruit18,= -Params: green Use the adafruit18_green variant. - rotate Display rotation {0,90,180,270} - speed SPI bus speed in Hz (default 4000000) - fps Display frame rate in Hz - bgr Enable BGR mode (default off) - debug Debug output level {0-7} - dc_pin GPIO pin for D/C (default 24) - reset_pin GPIO pin for RESET (default 25) - led_pin GPIO used to control backlight (default 18) - - -Name: adau1977-adc -Info: Overlay for activation of ADAU1977 ADC codec over I2C for control - and I2S for data. -Load: dtoverlay=adau1977-adc -Params: - - -Name: adau7002-simple -Info: Overlay for the activation of ADAU7002 stereo PDM to I2S converter. -Load: dtoverlay=adau7002-simple,= -Params: card-name Override the default, "adau7002", card name. - - -Name: ads1015 -Info: Overlay for activation of Texas Instruments ADS1015 ADC over I2C -Load: dtoverlay=ads1015,= -Params: addr I2C bus address of device. Set based on how the - addr pin is wired. (default=0x48 assumes addr - is pulled to GND) - cha_enable Enable virtual channel a. (default=true) - cha_cfg Set the configuration for virtual channel a. - (default=4 configures this channel for the - voltage at A0 with respect to GND) - cha_datarate Set the datarate (samples/sec) for this channel. - (default=4 sets 1600 sps) - cha_gain Set the gain of the Programmable Gain - Amplifier for this channel. (default=2 sets the - full scale of the channel to 2.048 Volts) - - Channel (ch) parameters can be set for each enabled channel. - A maximum of 4 channels can be enabled (letters a thru d). - For more information refer to the device datasheet at: - http://www.ti.com/lit/ds/symlink/ads1015.pdf - - -Name: ads1115 -Info: Texas Instruments ADS1115 ADC -Load: dtoverlay=ads1115,[=] -Params: addr I2C bus address of device. Set based on how the - addr pin is wired. (default=0x48 assumes addr - is pulled to GND) - cha_enable Enable virtual channel a. - cha_cfg Set the configuration for virtual channel a. - (default=4 configures this channel for the - voltage at A0 with respect to GND) - cha_datarate Set the datarate (samples/sec) for this channel. - (default=7 sets 860 sps) - cha_gain Set the gain of the Programmable Gain - Amplifier for this channel. (Default 1 sets the - full scale of the channel to 4.096 Volts) - - Channel parameters can be set for each enabled channel. - A maximum of 4 channels can be enabled (letters a thru d). - For more information refer to the device datasheet at: - http://www.ti.com/lit/ds/symlink/ads1115.pdf - - -Name: ads7846 -Info: ADS7846 Touch controller -Load: dtoverlay=ads7846,= -Params: cs SPI bus Chip Select (default 1) - speed SPI bus speed (default 2MHz, max 3.25MHz) - penirq GPIO used for PENIRQ. REQUIRED - penirq_pull Set GPIO pull (default 0=none, 2=pullup) - swapxy Swap x and y axis - xmin Minimum value on the X axis (default 0) - ymin Minimum value on the Y axis (default 0) - xmax Maximum value on the X axis (default 4095) - ymax Maximum value on the Y axis (default 4095) - pmin Minimum reported pressure value (default 0) - pmax Maximum reported pressure value (default 65535) - xohms Touchpanel sensitivity (X-plate resistance) - (default 400) - - penirq is required and usually xohms (60-100) has to be set as well. - Apart from that, pmax (255) and swapxy are also common. - The rest of the calibration can be done with xinput-calibrator. - See: github.com/notro/fbtft/wiki/FBTFT-on-Raspian - Device Tree binding document: - www.kernel.org/doc/Documentation/devicetree/bindings/input/ads7846.txt - - -Name: adv7282m -Info: Analog Devices ADV7282M analogue video to CSI2 bridge. - Uses Unicam1, which is the standard camera connector on most Pi - variants. -Load: dtoverlay=adv7282m,= -Params: addr Overrides the I2C address (default 0x21) - media-controller Configure use of Media Controller API for - configuring the sensor (default off) - - -Name: adv728x-m -Info: Analog Devices ADV728[0|1|2]-M analogue video to CSI2 bridges. - This is a wrapper for adv7282m, and defaults to ADV7282M. -Load: dtoverlay=adv728x-m,= -Params: addr Overrides the I2C address (default 0x21) - adv7280m Select ADV7280-M. - adv7281m Select ADV7281-M. - adv7281ma Select ADV7281-MA. - media-controller Configure use of Media Controller API for - configuring the sensor (default off) - - -Name: akkordion-iqdacplus -Info: Configures the Digital Dreamtime Akkordion Music Player (based on the - OEM IQAudIO DAC+ or DAC Zero module). -Load: dtoverlay=akkordion-iqdacplus,= -Params: 24db_digital_gain Allow gain to be applied via the PCM512x codec - Digital volume control. Enable with - dtoverlay=akkordion-iqdacplus,24db_digital_gain - (The default behaviour is that the Digital - volume control is limited to a maximum of - 0dB. ie. it can attenuate but not provide - gain. For most users, this will be desired - as it will prevent clipping. By appending - the 24db_digital_gain parameter, the Digital - volume control will allow up to 24dB of - gain. If this parameter is enabled, it is the - responsibility of the user to ensure that - the Digital volume control is set to a value - that does not result in clipping/distortion!) - - -Name: allo-boss-dac-pcm512x-audio -Info: Configures the Allo Boss DAC audio cards. -Load: dtoverlay=allo-boss-dac-pcm512x-audio, -Params: 24db_digital_gain Allow gain to be applied via the PCM512x codec - Digital volume control. Enable with - "dtoverlay=allo-boss-dac-pcm512x-audio, - 24db_digital_gain" - (The default behaviour is that the Digital - volume control is limited to a maximum of - 0dB. ie. it can attenuate but not provide - gain. For most users, this will be desired - as it will prevent clipping. By appending - the 24db_digital_gain parameter, the Digital - volume control will allow up to 24dB of - gain. If this parameter is enabled, it is the - responsibility of the user to ensure that - the Digital volume control is set to a value - that does not result in clipping/distortion!) - slave Force Boss DAC into slave mode, using Pi a - master for bit clock and frame clock. Enable - with "dtoverlay=allo-boss-dac-pcm512x-audio, - slave" - - -Name: allo-boss2-dac-audio -Info: Configures the Allo Boss2 DAC audio card -Load: dtoverlay=allo-boss2-dac-audio -Params: - - -Name: allo-digione -Info: Configures the Allo Digione audio card -Load: dtoverlay=allo-digione -Params: - - -Name: allo-katana-dac-audio -Info: Configures the Allo Katana DAC audio card -Load: dtoverlay=allo-katana-dac-audio -Params: - - -Name: allo-piano-dac-pcm512x-audio -Info: Configures the Allo Piano DAC (2.0/2.1) audio cards. - (NB. This initial support is for 2.0 channel audio ONLY! ie. stereo. - The subwoofer outputs on the Piano 2.1 are not currently supported!) -Load: dtoverlay=allo-piano-dac-pcm512x-audio, -Params: 24db_digital_gain Allow gain to be applied via the PCM512x codec - Digital volume control. - (The default behaviour is that the Digital - volume control is limited to a maximum of - 0dB. ie. it can attenuate but not provide - gain. For most users, this will be desired - as it will prevent clipping. By appending - the 24db_digital_gain parameter, the Digital - volume control will allow up to 24dB of - gain. If this parameter is enabled, it is the - responsibility of the user to ensure that - the Digital volume control is set to a value - that does not result in clipping/distortion!) - - -Name: allo-piano-dac-plus-pcm512x-audio -Info: Configures the Allo Piano DAC (2.1) audio cards. -Load: dtoverlay=allo-piano-dac-plus-pcm512x-audio, -Params: 24db_digital_gain Allow gain to be applied via the PCM512x codec - Digital volume control. - (The default behaviour is that the Digital - volume control is limited to a maximum of - 0dB. ie. it can attenuate but not provide - gain. For most users, this will be desired - as it will prevent clipping. By appending - the 24db_digital_gain parameter, the Digital - volume control will allow up to 24dB of - gain. If this parameter is enabled, it is the - responsibility of the user to ensure that - the Digital volume control is set to a value - that does not result in clipping/distortion!) - glb_mclk This option is only with Kali board. If enabled, - MCLK for Kali is used and PLL is disabled for - better voice quality. (default Off) - - -Name: anyspi -Info: Universal device tree overlay for SPI devices - - Just specify the SPI address and device name ("compatible" property). - This overlay lacks any device-specific parameter support! - - For devices on spi1 or spi2, the interfaces should be enabled - with one of the spi1-1/2/3cs and/or spi2-1/2/3cs overlays. - - Examples: - 1. SPI NOR flash on spi0.1, maximum SPI clock frequency 45MHz: - dtoverlay=anyspi:spi0-1,dev="jedec,spi-nor",speed=45000000 - 2. MCP3204 ADC on spi1.2, maximum SPI clock frequency 500kHz: - dtoverlay=anyspi:spi1-2,dev="microchip,mcp3204" -Load: dtoverlay=anyspi,= -Params: spi- Configure device at spi, cs - (boolean, required) - dev Set device name to search compatible module - (string, required) - speed Set SPI clock frequency in Hz - (integer, optional, default 500000) - - -Name: apds9960 -Info: Configures the AVAGO APDS9960 digital proximity, ambient light, RGB and - gesture sensor -Load: dtoverlay=apds9960,= -Params: gpiopin GPIO used for INT (default 4) - noints Disable the interrupt GPIO line. - - -Name: applepi-dac -Info: Configures the Orchard Audio ApplePi-DAC audio card -Load: dtoverlay=applepi-dac -Params: - - -Name: at86rf233 -Info: Configures the Atmel AT86RF233 802.15.4 low-power WPAN transceiver, - connected to spi0.0 -Load: dtoverlay=at86rf233,= -Params: interrupt GPIO used for INT (default 23) - reset GPIO used for Reset (default 24) - sleep GPIO used for Sleep (default 25) - speed SPI bus speed in Hz (default 3000000) - trim Fine tuning of the internal capacitance - arrays (0=+0pF, 15=+4.5pF, default 15) - - -Name: audioinjector-addons -Info: Configures the audioinjector.net audio add on soundcards -Load: dtoverlay=audioinjector-addons,= -Params: non-stop-clocks Keeps the clocks running even when the stream - is paused or stopped (default off) - - -Name: audioinjector-isolated-soundcard -Info: Configures the audioinjector.net isolated soundcard -Load: dtoverlay=audioinjector-isolated-soundcard -Params: - - -Name: audioinjector-ultra -Info: Configures the audioinjector.net ultra soundcard -Load: dtoverlay=audioinjector-ultra -Params: - - -Name: audioinjector-wm8731-audio -Info: Configures the audioinjector.net audio add on soundcard -Load: dtoverlay=audioinjector-wm8731-audio -Params: - - -Name: audiosense-pi -Info: Configures the audiosense-pi add on soundcard - For more information refer to - https://gitlab.com/kakar0t/audiosense-pi -Load: dtoverlay=audiosense-pi -Params: - - -Name: audremap -Info: Switches PWM sound output to GPIOs on the 40-pin header -Load: dtoverlay=audremap,= -Params: swap_lr Reverse the channel allocation, which will also - swap the audio jack outputs (default off) - enable_jack Don't switch off the audio jack output - (default off) - pins_12_13 Select GPIOs 12 & 13 (default) - pins_18_19 Select GPIOs 18 & 19 - - -Name: balena-fin -Info: Overlay that enables WLAN, Bluetooth and the GPIO expander on the - balenaFin carrier board for the Raspberry Pi Compute Module 3/3+ Lite. -Load: dtoverlay=balena-fin -Params: - - -Name: bmp085_i2c-sensor -Info: This overlay is now deprecated - see i2c-sensor -Load: - - -Name: cap1106 -Info: Enables the ability to use the cap1106 touch sensor as a keyboard -Load: dtoverlay=cap1106,= -Params: int_pin GPIO pin for interrupt signal (default 23) - - -Name: chipdip-dac -Info: Configures Chip Dip audio cards. -Load: dtoverlay=chipdip-dac -Params: - - -Name: cma -Info: Set custom CMA sizes, only use if you know what you are doing, might - clash with other overlays like vc4-fkms-v3d and vc4-kms-v3d. -Load: dtoverlay=cma,= -Params: cma-512 CMA is 512MB (needs 1GB) - cma-448 CMA is 448MB (needs 1GB) - cma-384 CMA is 384MB (needs 1GB) - cma-320 CMA is 320MB (needs 1GB) - cma-256 CMA is 256MB (needs 1GB) - cma-192 CMA is 192MB (needs 1GB) - cma-128 CMA is 128MB - cma-96 CMA is 96MB - cma-64 CMA is 64MB - cma-size CMA size in bytes, 4MB aligned - cma-default Use upstream's default value - - -Name: cutiepi-panel -Info: 8" TFT LCD display and touch panel used by cutiepi.io -Load: dtoverlay=cutiepi-panel -Params: - - -Name: dht11 -Info: Overlay for the DHT11/DHT21/DHT22 humidity/temperature sensors - Also sometimes found with the part number(s) AM230x. -Load: dtoverlay=dht11,= -Params: gpiopin GPIO connected to the sensor's DATA output. - (default 4) - - -Name: dionaudio-loco -Info: Configures the Dion Audio LOCO DAC-AMP -Load: dtoverlay=dionaudio-loco -Params: - - -Name: dionaudio-loco-v2 -Info: Configures the Dion Audio LOCO-V2 DAC-AMP -Load: dtoverlay=dionaudio-loco-v2,= -Params: 24db_digital_gain Allow gain to be applied via the PCM512x codec - Digital volume control. Enable with - "dtoverlay=hifiberry-dacplus,24db_digital_gain" - (The default behaviour is that the Digital - volume control is limited to a maximum of - 0dB. ie. it can attenuate but not provide - gain. For most users, this will be desired - as it will prevent clipping. By appending - the 24dB_digital_gain parameter, the Digital - volume control will allow up to 24dB of - gain. If this parameter is enabled, it is the - responsibility of the user to ensure that - the Digital volume control is set to a value - that does not result in clipping/distortion!) - - -Name: disable-bt -Info: Disable onboard Bluetooth on Pi 3B, 3B+, 3A+, 4B and Zero W, restoring - UART0/ttyAMA0 over GPIOs 14 & 15. - N.B. To disable the systemd service that initialises the modem so it - doesn't use the UART, use 'sudo systemctl disable hciuart'. -Load: dtoverlay=disable-bt -Params: - - -Name: disable-wifi -Info: Disable onboard WLAN on Pi 3B, 3B+, 3A+, 4B and Zero W. -Load: dtoverlay=disable-wifi -Params: - - -Name: dpi18 -Info: Overlay for a generic 18-bit DPI display - This uses GPIOs 0-21 (so no I2C, uart etc.), and activates the output - 2-3 seconds after the kernel has started. -Load: dtoverlay=dpi18 -Params: - - -Name: dpi18cpadhi -Info: Overlay for a generic 18-bit DPI display (in 'mode 6' connection scheme) - This uses GPIOs 0-9,12-17,20-25 (so no I2C, uart etc.), and activates - the output 3-3 seconds after the kernel has started. -Load: dtoverlay=dpi18cpadhi -Params: - - -Name: dpi24 -Info: Overlay for a generic 24-bit DPI display - This uses GPIOs 0-27 (so no I2C, uart etc.), and activates the output - 2-3 seconds after the kernel has started. -Load: dtoverlay=dpi24 -Params: - - -Name: draws -Info: Configures the NW Digital Radio DRAWS Hat - - The board includes an ADC to measure various board values and also - provides two analog user inputs on the expansion header. The ADC - can be configured for various sample rates and gain values to adjust - the input range. Tables describing the two parameters follow. - - ADC Gain Values: - 0 = +/- 6.144V - 1 = +/- 4.096V - 2 = +/- 2.048V - 3 = +/- 1.024V - 4 = +/- 0.512V - 5 = +/- 0.256V - 6 = +/- 0.256V - 7 = +/- 0.256V - - ADC Datarate Values: - 0 = 128sps - 1 = 250sps - 2 = 490sps - 3 = 920sps - 4 = 1600sps (default) - 5 = 2400sps - 6 = 3300sps - 7 = 3300sps -Load: dtoverlay=draws,= -Params: draws_adc_ch4_gain Sets the full scale resolution of the ADCs - input voltage sensor (default 1) - - draws_adc_ch4_datarate Sets the datarate of the ADCs input voltage - sensor - - draws_adc_ch5_gain Sets the full scale resolution of the ADCs - 5V rail voltage sensor (default 1) - - draws_adc_ch5_datarate Sets the datarate of the ADCs 4V rail voltage - sensor - - draws_adc_ch6_gain Sets the full scale resolution of the ADCs - AIN2 input (default 2) - - draws_adc_ch6_datarate Sets the datarate of the ADCs AIN2 input - - draws_adc_ch7_gain Sets the full scale resolution of the ADCs - AIN3 input (default 2) - - draws_adc_ch7_datarate Sets the datarate of the ADCs AIN3 input - - alsaname Name of the ALSA audio device (default "draws") - - -Name: dwc-otg -Info: Selects the dwc_otg USB controller driver which has fiq support. This - is the default on all except the Pi Zero which defaults to dwc2. -Load: dtoverlay=dwc-otg -Params: - - -Name: dwc2 -Info: Selects the dwc2 USB controller driver -Load: dtoverlay=dwc2,= -Params: dr_mode Dual role mode: "host", "peripheral" or "otg" - - g-rx-fifo-size Size of rx fifo size in gadget mode - - g-np-tx-fifo-size Size of non-periodic tx fifo size in gadget - mode - - -[ The ds1307-rtc overlay has been deleted. See i2c-rtc. ] - - -Name: edt-ft5406 -Info: Overlay for the EDT FT5406 touchscreen on the CSI/DSI I2C interface. - This works with the Raspberry Pi 7" touchscreen when not being polled - by the firmware. - You MUST use either "disable_touchscreen=1" or "ignore_lcd=1" in - config.txt to stop the firmware polling the touchscreen. -Load: dtoverlay=edt-ft5406,= -Params: sizex Touchscreen size x (default 800) - sizey Touchscreen size y (default 480) - invx Touchscreen inverted x axis - invy Touchscreen inverted y axis - swapxy Touchscreen swapped x y axis - - -Name: enc28j60 -Info: Overlay for the Microchip ENC28J60 Ethernet Controller on SPI0 -Load: dtoverlay=enc28j60,= -Params: int_pin GPIO used for INT (default 25) - - speed SPI bus speed (default 12000000) - - -Name: enc28j60-spi2 -Info: Overlay for the Microchip ENC28J60 Ethernet Controller on SPI2 -Load: dtoverlay=enc28j60-spi2,= -Params: int_pin GPIO used for INT (default 39) - - speed SPI bus speed (default 12000000) - - -Name: exc3000 -Info: Enables I2C connected EETI EXC3000 multiple touch controller using - GPIO 4 (pin 7 on GPIO header) for interrupt. -Load: dtoverlay=exc3000,= -Params: interrupt GPIO used for interrupt (default 4) - sizex Touchscreen size x (default 4096) - sizey Touchscreen size y (default 4096) - invx Touchscreen inverted x axis - invy Touchscreen inverted y axis - swapxy Touchscreen swapped x y axis - - -Name: fbtft -Info: Overlay for SPI-connected displays using the fbtft drivers. - - This overlay seeks to replace the functionality provided by fbtft_device - which is now gone from the kernel. - - Most displays from fbtft_device have been ported over. - Example: - dtoverlay=fbtft,spi0-0,rpi-display,reset_pin=23,dc_pin=24,led_pin=18,rotate=270 - - It is also possible to specify the controller (this will use the default - init sequence in the driver). - Example: - dtoverlay=fbtft,spi0-0,ili9341,bgr,reset_pin=23,dc_pin=24,led_pin=18,rotate=270 - - For devices on spi1 or spi2, the interfaces should be enabled - with one of the spi1-1/2/3cs and/or spi2-1/2/3cs overlays. - - The following features of fbtft_device have not been ported over: - - parallel bus is not supported - - the init property which overrides the controller initialization - sequence is not supported as a parameter due to memory limitations in - the bootloader responsible for applying the overlay. - - See https://github.com/notro/fbtft/wiki/FBTFT-RPI-overlays for how to - create an overlay. - -Load: dtoverlay=fbtft,= -Params: - spi- Configure device at spi, cs - (boolean, required) - speed SPI bus speed in Hz (default 32000000) - cpha Shifted clock phase (CPHA) mode - cpol Inverse clock polarity (CPOL) mode - - adafruit18 Adafruit 1.8 - adafruit22 Adafruit 2.2 (old) - adafruit22a Adafruit 2.2 - adafruit28 Adafruit 2.8 - adafruit13m Adafruit 1.3 OLED - admatec_c-berry28 C-Berry28 - dogs102 EA DOGS102 - er_tftm050_2 ER-TFTM070-2 - er_tftm070_5 ER-TFTM070-5 - ew24ha0 EW24HA0 - ew24ha0_9bit EW24HA0 in 9-bit mode - freetronicsoled128 Freetronics OLED128 - hy28a HY28A - hy28b HY28B - itdb28_spi ITDB02-2.8 with SPI interface circuit - mi0283qt-2 Watterott MI0283QT-2 - mi0283qt-9a Watterott MI0283QT-9A - nokia3310 Nokia 3310 - nokia3310a Nokia 3310a - nokia5110 Nokia 5110 - piscreen PiScreen - pitft Adafruit PiTFT 2.8 - pioled ILSoft OLED - rpi-display Watterott rpi-display - sainsmart18 Sainsmart 1.8 - sainsmart32_spi Sainsmart 3.2 with SPI interfce circuit - tinylcd35 TinyLCD 3.5 - tm022hdh26 Tianma TM022HDH26 - tontec35_9481 Tontect 3.5 with ILI9481 controller - tontec35_9486 Tontect 3.5 with ILI9486 controller - waveshare32b Waveshare 3.2 - waveshare22 Waveshare 2.2 - - bd663474 BD663474 display controller - hx8340bn HX8340BN display controller - hx8347d HX8347D display controller - hx8353d HX8353D display controller - hx8357d HX8357D display controller - ili9163 ILI9163 display controller - ili9320 ILI9320 display controller - ili9325 ILI9325 display controller - ili9340 ILI9340 display controller - ili9341 ILI9341 display controller - ili9481 ILI9481 display controller - ili9486 ILI9486 display controller - pcd8544 PCD8544 display controller - ra8875 RA8875 display controller - s6d02a1 S6D02A1 display controller - s6d1121 S6D1121 display controller - seps525 SEPS525 display controller - sh1106 SH1106 display controller - ssd1289 SSD1289 display controller - ssd1305 SSD1305 display controller - ssd1306 SSD1306 display controller - ssd1325 SSD1325 display controller - ssd1331 SSD1331 display controller - ssd1351 SSD1351 display controller - st7735r ST7735R display controller - st7789v ST7789V display controller - tls8204 TLS8204 display controller - uc1611 UC1611 display controller - uc1701 UC1701 display controller - upd161704 UPD161704 display controller - - width Display width in pixels - height Display height in pixels - regwidth Display controller register width (default is - driver specific) - buswidth Display bus interface width (default 8) - debug Debug output level {0-7} - rotate Display rotation {0, 90, 180, 270} (counter - clockwise). Not supported by all drivers. - bgr Enable BGR mode (default off). Use if Red and - Blue are swapped. Not supported by all drivers. - fps Frames per second (default 30). In effect this - states how long the driver will wait after video - memory has been changed until display update - transfer is started. - txbuflen Length of the FBTFT transmit buffer - (default 4096) - startbyte Sets the Start byte used by fb_ili9320, - fb_ili9325 and fb_hx8347d. Common value is 0x70. - gamma String representation of Gamma Curve(s). Driver - specific. Not supported by all drivers. - reset_pin GPIO pin for RESET - dc_pin GPIO pin for D/C - led_pin GPIO pin for LED backlight - - -Name: fe-pi-audio -Info: Configures the Fe-Pi Audio Sound Card -Load: dtoverlay=fe-pi-audio -Params: - - -Name: fsm-demo -Info: A demonstration of the gpio-fsm driver. The GPIOs are chosen to work - nicely with a "traffic-light" display of red, amber and green LEDs on - GPIOs 7, 8 and 25 respectively. -Load: dtoverlay=fsm-demo,= -Params: fsm_debug Enable debug logging (default off) - - -Name: ghost-amp -Info: An overlay for the Ghost amplifier. -Load: dtoverlay=ghost-amp,= -Params: fsm_debug Enable debug logging of the GPIO FSM (default - off) - - -Name: goodix -Info: Enables I2C connected Goodix gt9271 multiple touch controller using - GPIOs 4 and 17 (pins 7 and 11 on GPIO header) for interrupt and reset. -Load: dtoverlay=goodix,= -Params: interrupt GPIO used for interrupt (default 4) - reset GPIO used for reset (default 17) - - -Name: googlevoicehat-soundcard -Info: Configures the Google voiceHAT soundcard -Load: dtoverlay=googlevoicehat-soundcard -Params: - - -Name: gpio-fan -Info: Configure a GPIO pin to control a cooling fan. -Load: dtoverlay=gpio-fan,= -Params: gpiopin GPIO used to control the fan (default 12) - temp Temperature at which the fan switches on, in - millicelcius (default 55000) - - -Name: gpio-ir -Info: Use GPIO pin as rc-core style infrared receiver input. The rc-core- - based gpio_ir_recv driver maps received keys directly to a - /dev/input/event* device, all decoding is done by the kernel - LIRC is - not required! The key mapping and other decoding parameters can be - configured by "ir-keytable" tool. -Load: dtoverlay=gpio-ir,= -Params: gpio_pin Input pin number. Default is 18. - - gpio_pull Desired pull-up/down state (off, down, up) - Default is "up". - - invert "1" = invert the input (active-low signalling). - "0" = non-inverted input (active-high - signalling). Default is "1". - - rc-map-name Default rc keymap (can also be changed by - ir-keytable), defaults to "rc-rc6-mce" - - -Name: gpio-ir-tx -Info: Use GPIO pin as bit-banged infrared transmitter output. - This is an alternative to "pwm-ir-tx". gpio-ir-tx doesn't require - a PWM so it can be used together with onboard analog audio. -Load: dtoverlay=gpio-ir-tx,= -Params: gpio_pin Output GPIO (default 18) - - invert "1" = invert the output (make it active-low). - Default is "0" (active-high). - - -Name: gpio-key -Info: This is a generic overlay for activating GPIO keypresses using - the gpio-keys library and this dtoverlay. Multiple keys can be - set up using multiple calls to the overlay for configuring - additional buttons or joysticks. You can see available keycodes - at https://github.com/torvalds/linux/blob/v4.12/include/uapi/ - linux/input-event-codes.h#L64 -Load: dtoverlay=gpio-key,= -Params: gpio GPIO pin to trigger on (default 3) - active_low When this is 1 (active low), a falling - edge generates a key down event and a - rising edge generates a key up event. - When this is 0 (active high), this is - reversed. The default is 1 (active low) - gpio_pull Desired pull-up/down state (off, down, up) - Default is "up". Note that the default pin - (GPIO3) has an external pullup - label Set a label for the key - keycode Set the key code for the button - - - -Name: gpio-led -Info: This is a generic overlay for activating LEDs (or any other component) - by a GPIO pin. Multiple LEDs can be set up using multiple calls to the - overlay. While there are many existing methods to activate LEDs on the - RPi, this method offers some advantages: - 1) Does not require any userspace programs. - 2) LEDs can be connected to the kernel's led-trigger framework, - and drive the LED based on triggers such as cpu load, heartbeat, - kernel panic, key input, timers and others. - 3) LED can be tied to the input state of another GPIO pin. - 4) The LED is setup early during the kernel boot process (useful - for cpu/heartbeat/panic triggers). - - Typical electrical connection is: - RPI-GPIO.19 -> LED -> 300ohm resister -> RPI-GND - The GPIO pin number can be changed with the 'gpio=' parameter. - - To control an LED from userspace, write a 0 or 1 value: - echo 1 > /sys/class/leds/myled1/brightness - The 'myled1' name can be changed with the 'label=' parameter. - - To connect the LED to a kernel trigger from userspace: - echo cpu > /sys/class/leds/myled1/trigger - echo heartbeat > /sys/class/leds/myled1/trigger - echo none > /sys/class/leds/myled1/trigger - To connect the LED to GPIO.26 pin (physical pin 37): - echo gpio > /sys/class/leds/myled1/trigger - echo 26 > /sys/class/leds/myled1/gpio - Available triggers: - cat /sys/class/leds/myled1/trigger - - More information about the Linux kernel LED/Trigger system: - https://www.kernel.org/doc/Documentation/leds/leds-class.rst - https://www.kernel.org/doc/Documentation/leds/ledtrig-oneshot.rst -Load: dtoverlay=gpio-led,= -Params: gpio GPIO pin connected to the LED (default 19) - label The label for this LED. It will appear under - /sys/class/leds/