diff --git a/firmware/application/apps/analog_audio_app.cpp b/firmware/application/apps/analog_audio_app.cpp index 85d7bd9d..381871c2 100644 --- a/firmware/application/apps/analog_audio_app.cpp +++ b/firmware/application/apps/analog_audio_app.cpp @@ -404,11 +404,7 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) { } void AnalogAudioView::handle_coded_squelch(uint32_t value) { - tone_index idx = tone_key_index_by_value(value); - if (idx >= 0) - text_ctcss.set("CTCSS " + tone_key_string(idx)); - else - text_ctcss.set("???"); + text_ctcss.set(tone_key_string_by_value(value, text_ctcss.parent_rect().width() / 8)); } } /* namespace ui */ diff --git a/firmware/application/apps/analog_audio_app.hpp b/firmware/application/apps/analog_audio_app.hpp index d95a9873..b3a75f2b 100644 --- a/firmware/application/apps/analog_audio_app.hpp +++ b/firmware/application/apps/analog_audio_app.hpp @@ -71,10 +71,10 @@ class NBFMOptionsView : public View { }}; Text text_squelch{ - {9 * 8, 0 * 16, 8 * 8, 1 * 16}, + {7 * 8, 0 * 16, 8 * 8, 1 * 16}, "SQ /99"}; NumberField field_squelch{ - {12 * 8, 0 * 16}, + {10 * 8, 0 * 16}, 2, {0, 99}, 1, @@ -200,7 +200,7 @@ class AnalogAudioView : public View { {28 * 8, 0 * 16}}; Text text_ctcss{ - {19 * 8, 1 * 16, 11 * 8, 1 * 16}, + {16 * 8, 1 * 16, 14 * 8, 1 * 16}, ""}; std::unique_ptr options_widget{}; diff --git a/firmware/application/apps/ui_level.cpp b/firmware/application/apps/ui_level.cpp index d0dd3c76..742063e0 100644 --- a/firmware/application/apps/ui_level.cpp +++ b/firmware/application/apps/ui_level.cpp @@ -216,23 +216,10 @@ size_t LevelView::change_mode(freqman_index_t new_mod) { } void LevelView::handle_coded_squelch(const uint32_t value) { - static tone_index last_squelch_index = -1; - - if (field_mode.selected_index() == NFM_MODULATION) { - tone_index idx = tone_key_index_by_value(value); - - if ((last_squelch_index < 0) || (last_squelch_index != idx)) { - last_squelch_index = idx; - if (idx >= 0) { - text_ctcss.set("T: " + tone_key_string(idx)); - return; - } - } else { - return; - } - } - - text_ctcss.set(" "); + if (field_mode.selected_index() == NFM_MODULATION) + text_ctcss.set(tone_key_string_by_value(value, text_ctcss.parent_rect().width() / 8)); + else + text_ctcss.set(" "); } } /* namespace ui */ diff --git a/firmware/application/apps/ui_level.hpp b/firmware/application/apps/ui_level.hpp index a849afd8..862e79d8 100644 --- a/firmware/application/apps/ui_level.hpp +++ b/firmware/application/apps/ui_level.hpp @@ -114,7 +114,7 @@ class LevelView : public View { }}; Text text_ctcss{ - {22 * 8, 3 * 16 + 4, 14 * 8, 1 * 8}, + {22 * 8, 3 * 16 + 4, 8 * 8, 1 * 8}, ""}; // RSSI: XX/XX/XXX,dt: XX diff --git a/firmware/application/apps/ui_mictx.hpp b/firmware/application/apps/ui_mictx.hpp index 0cbb6f28..4c7e724a 100644 --- a/firmware/application/apps/ui_mictx.hpp +++ b/firmware/application/apps/ui_mictx.hpp @@ -269,7 +269,7 @@ class MicTXView : public View { OptionsField options_tone_key{ {12 * 8, (13 * 8) - 5}, - 23, + 18, {}}; Checkbox check_rogerbeep{ diff --git a/firmware/application/apps/ui_recon.cpp b/firmware/application/apps/ui_recon.cpp index 4fce5246..88afc6e3 100644 --- a/firmware/application/apps/ui_recon.cpp +++ b/firmware/application/apps/ui_recon.cpp @@ -1426,20 +1426,10 @@ size_t ReconView::change_mode(freqman_index_t new_mod) { } void ReconView::handle_coded_squelch(const uint32_t value) { - if (field_mode.selected_index() == NFM_MODULATION) { - tone_index idx = tone_key_index_by_value(value); - - if ((last_squelch_index < 0) || (last_squelch_index != idx)) { - last_squelch_index = idx; - if (idx >= 0) { - text_ctcss.set("T: " + tone_key_string(idx)); - return; - } - } else { - return; - } - } - text_ctcss.set(" "); + if (field_mode.selected_index() == NFM_MODULATION) + text_ctcss.set(tone_key_string_by_value(value, text_ctcss.parent_rect().width() / 8)); + else + text_ctcss.set(" "); } } /* namespace ui */ diff --git a/firmware/application/apps/ui_recon.hpp b/firmware/application/apps/ui_recon.hpp index b1d25f3e..36f0e0b4 100644 --- a/firmware/application/apps/ui_recon.hpp +++ b/firmware/application/apps/ui_recon.hpp @@ -139,7 +139,6 @@ class ReconView : public View { int8_t last_rssi_med{-127}; int8_t last_rssi_max{-127}; int32_t last_index{-1}; - tone_index last_squelch_index{-1}; int64_t last_freq{0}; std::string freq_file_path{}; systime_t chrono_start{}; diff --git a/firmware/application/freqman.cpp b/firmware/application/freqman.cpp index 648e8803..ee6d455b 100644 --- a/firmware/application/freqman.cpp +++ b/firmware/application/freqman.cpp @@ -122,6 +122,8 @@ bool load_freqman_file(std::string& file_stem, freqman_db& db, bool load_freqs, freqman_index_t bandwidth = -1; freqman_index_t step = -1; freqman_index_t tone = -1; + uint32_t tone_freq; + char c; auto result = freqman_file.open("FREQMAN/" + file_stem + ".TXT"); if (result.is_valid()) @@ -210,12 +212,26 @@ bool load_freqman_file(std::string& file_stem, freqman_db& db, bool load_freqs, step = freqman_entry_get_step_from_str_short(pos); } // ctcss tone if any - /* disabled until better form - pos = strstr(line_start, "c="); - if (pos) { - pos += 2; - tone = tone_key_index_by_value( strtoll( pos , nullptr , 10 ) ); - } */ + pos = strstr(line_start, "c="); + if (pos) { + pos += 2; + // find decimal point and replace with 0 if there is one, for strtoll + length = strcspn(pos, ".,\x0A"); + if (pos + length <= line_end) { + c = *(pos + length); + *(pos + length) = 0; + // ASCII Hz to integer Hz x 100 + tone_freq = strtoll(pos, nullptr, 10) * 100; + // stuff saved character back into string in case it was not a decimal point + *(pos + length) = c; + // now get first digit after decimal point (10ths of Hz) + pos += length + 1; + if (c == '.' && *pos >= '0' && *pos <= '9') + tone_freq += (*pos - '0') * 10; + // convert tone_freq (100x the freq in Hz) to a tone_key index + tone = tone_key_index_by_value(tone_freq); + } + } // Read description until , or LF pos = strstr(line_start, "d="); if (pos) { @@ -284,7 +300,7 @@ bool get_freq_string(freqman_entry& entry, std::string& item_string) { item_string = "r=" + to_string_dec_uint(frequency_a / 1000) + to_string_dec_uint(frequency_a % 1000UL, 3, '0'); item_string += ",t=" + to_string_dec_uint(frequency_b / 1000) + to_string_dec_uint(frequency_b % 1000UL, 3, '0'); if (entry.tone >= 0) { - item_string += ",c=" + tone_key_string(entry.tone); + item_string += ",c=" + tone_key_value_string(entry.tone); } } if (entry.modulation >= 0 && (unsigned)entry.modulation < freqman_entry_modulations.size()) { diff --git a/firmware/application/tone_key.cpp b/firmware/application/tone_key.cpp index d9655938..d2402426 100644 --- a/firmware/application/tone_key.cpp +++ b/firmware/application/tone_key.cpp @@ -27,62 +27,66 @@ namespace tonekey { // Keep list in ascending order by tone frequency const tone_key_t tone_keys = { - {"None", 0.0}, - {"1 XZ", 67.000}, - {"39 WZ", 69.300}, - {"2 XA", 71.900}, - {"3 WA", 74.400}, - {"4 XB", 77.000}, - {"5 WB", 79.700}, - {"6 YZ", 82.500}, - {"7 YA", 85.400}, - {"8 YB", 88.500}, - {"9 ZZ", 91.500}, - {"10 ZA", 94.800}, - {"11 ZB", 97.400}, - {"12 1Z", 100.000}, - {"13 1A", 103.500}, - {"14 1B", 107.200}, - {"15 2Z", 110.900}, - {"16 2A", 114.800}, - {"17 2B", 118.800}, - {"18 3Z", 123.000}, - {"19 3A", 127.300}, - {"20 3B", 131.800}, - {"21 4Z", 136.500}, - {"22 4A", 141.300}, - {"23 4B", 146.200}, - {"24 5Z", 151.400}, - {"25 5A", 156.700}, - {"40 --", 159.800}, - {"26 5B", 162.200}, - {"41 --", 165.500}, - {"27 6Z", 167.900}, - {"42 --", 171.300}, - {"28 6A", 173.800}, - {"43 --", 177.300}, - {"29 6B", 179.900}, - {"44 --", 183.500}, - {"30 7Z", 186.200}, - {"45 --", 189.900}, - {"31 7A", 192.800}, - {"46 --", 196.600}, - {"47 --", 199.500}, - {"32 M1", 203.500}, - {"48 8Z", 206.500}, - {"33 M2", 210.700}, - {"34 M3", 218.100}, - {"35 M4", 225.700}, - {"49 9Z", 229.100}, - {"36 M5", 233.600}, - {"37 M6", 241.800}, - {"38 M7", 250.300}, - {"50 0Z", 254.100}, - {"Shure 19kHz", 19000.0}, - {"Axient 28kHz", 28000.0}, - {"Senn. 32.000k", 32000.0}, - {"Sony 32.382k", 32382.0}, - {"Senn. 32.768k", 32768.0}}; + {"None", F2Ix100(0.0)}, + {"1 XZ", F2Ix100(67.0)}, + {"39 WZ", F2Ix100(69.3)}, + {"2 XA", F2Ix100(71.9)}, + {"3 WA", F2Ix100(74.4)}, + {"4 XB", F2Ix100(77.0)}, + {"5 WB", F2Ix100(79.7)}, + {"6 YZ", F2Ix100(82.5)}, + {"7 YA", F2Ix100(85.4)}, + {"8 YB", F2Ix100(88.5)}, + {"9 ZZ", F2Ix100(91.5)}, + {"10 ZA", F2Ix100(94.8)}, + {"11 ZB", F2Ix100(97.4)}, + {"12 1Z", F2Ix100(100.0)}, + {"13 1A", F2Ix100(103.5)}, + {"14 1B", F2Ix100(107.2)}, + {"15 2Z", F2Ix100(110.9)}, + {"16 2A", F2Ix100(114.8)}, + {"17 2B", F2Ix100(118.8)}, + {"18 3Z", F2Ix100(123.0)}, + {"19 3A", F2Ix100(127.3)}, + {"20 3B", F2Ix100(131.8)}, + {"21 4Z", F2Ix100(136.5)}, + {"22 4A", F2Ix100(141.3)}, + {"23 4B", F2Ix100(146.2)}, + {"24 5Z", F2Ix100(151.4)}, + {"25 5A", F2Ix100(156.7)}, + {"40 --", F2Ix100(159.8)}, + {"26 5B", F2Ix100(162.2)}, + {"41 --", F2Ix100(165.5)}, + {"27 6Z", F2Ix100(167.9)}, + {"42 --", F2Ix100(171.3)}, + {"28 6A", F2Ix100(173.8)}, + {"43 --", F2Ix100(177.3)}, + {"29 6B", F2Ix100(179.9)}, + {"44 --", F2Ix100(183.5)}, + {"30 7Z", F2Ix100(186.2)}, + {"45 --", F2Ix100(189.9)}, + {"31 7A", F2Ix100(192.8)}, + {"46 --", F2Ix100(196.6)}, + {"47 --", F2Ix100(199.5)}, + {"32 M1", F2Ix100(203.5)}, + {"48 8Z", F2Ix100(206.5)}, + {"33 M2", F2Ix100(210.7)}, + {"34 M3", F2Ix100(218.1)}, + {"35 M4", F2Ix100(225.7)}, + {"49 9Z", F2Ix100(229.1)}, + {"36 M5", F2Ix100(233.6)}, + {"37 M6", F2Ix100(241.8)}, + {"38 M7", F2Ix100(250.3)}, + {"50 0Z", F2Ix100(254.1)}, + {"Shure 19kHz", F2Ix100(19000.0)}, + {"Axient 28kHz", F2Ix100(28000.0)}, + {"Senn. 32.000k", F2Ix100(32000.0)}, + {"Sony 32.382k", F2Ix100(32382.0)}, + {"Senn. 32.768k", F2Ix100(32768.0)}}; + +std::string fx100_string(uint32_t f) { + return to_string_dec_uint(f / 100) + "." + to_string_dec_uint((f / 10) % 10); +} void tone_keys_populate(OptionsField& field) { using option_t = std::pair; @@ -91,12 +95,11 @@ void tone_keys_populate(OptionsField& field) { std::string tone_name; for (size_t c = 0; c < tone_keys.size(); c++) { - if (c && c < 51) { - auto f = tone_keys[c].second; - tone_name = "CTCSS " + tone_keys[c].first + " " + to_string_dec_uint(f) + "." + to_string_dec_uint((uint32_t)(f * 10) % 10); - } else { + auto f = tone_keys[c].second; + if ((c != 0) && (f < 1000 * 100)) + tone_name = "CTCSS " + fx100_string(f) + " #" + tone_keys[c].first; + else tone_name = tone_keys[c].first; - } tone_key_options.emplace_back(tone_name, c); } @@ -104,8 +107,8 @@ void tone_keys_populate(OptionsField& field) { field.set_options(tone_key_options); } -float tone_key_frequency(const tone_index index) { - return tone_keys[index].second; +float tone_key_frequency(tone_index index) { + return float(tone_keys[index].second) / 100.0; } std::string tone_key_string(tone_index index) { @@ -114,20 +117,65 @@ std::string tone_key_string(tone_index index) { return tone_keys[index].first; } -std::string tone_key_string_by_value(uint32_t value) { - return tone_key_string(tone_key_index_by_value(value)); +// Return string showing frequency only from specific table index +std::string tone_key_value_string(tone_index index) { + if (index < 0 || (unsigned)index >= tone_keys.size()) + return std::string(""); + return fx100_string(tone_keys[index].second); } +// Return variable-length string showing CTCSS tone from tone frequency +// Value is in 0.01 Hz units +std::string tone_key_string_by_value(uint32_t value, size_t max_length) { + static uint8_t tone_display_toggle{0}; + static uint32_t last_value; + tone_index idx; + std::string freq_str; + + // If >10Hz difference between consecutive samples, it's probably noise, so ignore + if (abs(value - last_value) > 10 * 100) { + last_value = value; + tone_display_toggle = 0; + return " "; + } + last_value = value; + + // Only display 1/10 Hz accuracy if <1000 Hz; max 5 characters + if (value < 1000 * 100) + freq_str = "T:" + fx100_string(value); + else + freq_str = "T:" + to_string_dec_uint(value / 100); + + // Check field length is enough for character counts in the string below + if (max_length >= 7 + 2 + 5) { + idx = tone_key_index_by_value(value); + if (idx != -1) + return freq_str + " #" + tone_key_string(idx); + } else { + // Not enough space; toggle between display of tone received and tone code # + if (tone_display_toggle++ >= TONE_DISPLAY_TOGGLE_COUNTER) { + if (tone_display_toggle >= TONE_DISPLAY_TOGGLE_COUNTER * 2) tone_display_toggle = 0; + + // Look for a match in the table (otherwise just display frequency) + idx = tone_key_index_by_value(value); + if (idx != -1) + return "T:" + tone_key_string(idx); + } + } + return freq_str; +} + +// Search tone_key table for tone frequency value +// Value is in 0.01 Hz units tone_index tone_key_index_by_value(uint32_t value) { - float diff; - float min_diff{(float)value}; - float fvalue{(float)(min_diff / 100.0)}; + uint32_t diff; + uint32_t min_diff{value * 2}; tone_index min_idx{-1}; tone_index idx; // Find nearest match for (idx = 0; idx < (tone_index)tone_keys.size(); idx++) { - diff = abs(fvalue - tone_keys[idx].second); + diff = abs(value - tone_keys[idx].second); if (diff < min_diff) { min_idx = idx; min_diff = diff; @@ -138,7 +186,7 @@ tone_index tone_key_index_by_value(uint32_t value) { } // Arbitrary confidence threshold - if (min_diff < 40.0) + if (min_diff < TONE_FREQ_TOLERANCE_CENTIHZ) return min_idx; else return -1; diff --git a/firmware/application/tone_key.hpp b/firmware/application/tone_key.hpp index fa26046d..6fc24927 100644 --- a/firmware/application/tone_key.hpp +++ b/firmware/application/tone_key.hpp @@ -30,17 +30,22 @@ using namespace ui; namespace tonekey { +#define TONE_FREQ_TOLERANCE_CENTIHZ (4 * 100) +#define TONE_DISPLAY_TOGGLE_COUNTER 3 +#define F2Ix100(x) (int32_t)(x * 100.0) + typedef int32_t tone_index; -using tone_key_t = std::vector>; +using tone_key_t = std::vector>; extern const tone_key_t tone_keys; void tone_keys_populate(OptionsField& field); -float tone_key_frequency(const tone_index index); +float tone_key_frequency(tone_index index); -std::string tone_key_string(const tone_index index); -std::string tone_key_string_by_value(uint32_t value); +std::string tone_key_string(tone_index index); +std::string tone_key_value_string(tone_index index); +std::string tone_key_string_by_value(uint32_t value, size_t max_length); tone_index tone_key_index_by_value(uint32_t value); } // namespace tonekey diff --git a/firmware/baseband/proc_nfm_audio.cpp b/firmware/baseband/proc_nfm_audio.cpp index e3aef78a..e09db1c2 100644 --- a/firmware/baseband/proc_nfm_audio.cpp +++ b/firmware/baseband/proc_nfm_audio.cpp @@ -53,7 +53,9 @@ void NarrowbandFMAudio::execute(const buffer_c8_t& buffer) { if (ctcss_detect_enabled) { /* 24kHz int16_t[16] * -> FIR filter, <300Hz pass, >300Hz stop, gain of 1 - * -> 12kHz int16_t[8] */ + * -> 12kHz int16_t[8] + * + * Note we're only processing a small section of the wave each time this fn is called */ auto audio_ctcss = ctcss_filter.execute(audio, work_audio_buffer); // s16 to f32 for hpf @@ -79,9 +81,11 @@ void NarrowbandFMAudio::execute(const buffer_c8_t& buffer) { prev_sample = cur_sample; } - if (z_count >= 30) { + z_filter_count++; + if ((z_filter_count >= Z_MIN_FILTER_COUNT) && (z_count >= Z_MIN_ZERO_CROSSINGS)) { ctcss_message.value = (100 * 12000 / 2 * z_count) / z_acc; shared_memory.application_queue.push(ctcss_message); + z_filter_count = 0; z_count = 0; z_acc = 0; } diff --git a/firmware/baseband/proc_nfm_audio.hpp b/firmware/baseband/proc_nfm_audio.hpp index e93d7aaf..708dfa2b 100644 --- a/firmware/baseband/proc_nfm_audio.hpp +++ b/firmware/baseband/proc_nfm_audio.hpp @@ -36,6 +36,9 @@ #include +#define Z_MIN_FILTER_COUNT 224 +#define Z_MIN_ZERO_CROSSINGS 20 + class NarrowbandFMAudio : public BasebandProcessor { public: void execute(const buffer_c8_t& buffer) override; @@ -88,7 +91,7 @@ class NarrowbandFMAudio : public BasebandProcessor { bool pitch_rssi_enabled{false}; float cur_sample{}, prev_sample{}; - uint32_t z_acc{0}, z_timer{0}, z_count{0}; + uint32_t z_acc{0}, z_timer{0}, z_count{0}, z_filter_count{0}; bool ctcss_detect_enabled{true}; static constexpr float k = 32768.0f; static constexpr float ki = 1.0f / k;