From ef966e33067b3c2b1c48aa9fe631916330972d34 Mon Sep 17 00:00:00 2001 From: Rob Riggs Date: Fri, 7 Feb 2020 19:51:44 -0600 Subject: [PATCH] Basic 9600 baud FSK support. --- TNC/AFSKModulator.hpp | 56 +++++-- TNC/Afsk1200Demodulator.cpp | 93 ++++++++++++ TNC/Afsk1200Demodulator.hpp | 181 +++++++++++++++++++++++ TNC/AfskDemodulator.hpp | 3 +- TNC/AudioInput.cpp | 284 +++++++++-------------------------- TNC/AudioInput.hpp | 18 ++- TNC/AudioLevel.cpp | 4 +- TNC/AudioLevel.hpp | 2 +- TNC/Demodulator.cpp | 44 ++++++ TNC/Demodulator.hpp | 43 ++++++ TNC/DigitalPLL.hpp | 11 +- TNC/FilterCoefficients.cpp | 35 +++++ TNC/FilterCoefficients.hpp | 22 +-- TNC/FirFilter.hpp | 1 - TNC/Fsk9600Demodulator.cpp | 287 ++++++++++++++++++++++++++++++++++++ TNC/Fsk9600Demodulator.hpp | 107 ++++++++++++++ TNC/Fsk9600Modulator.cpp | 16 ++ TNC/Fsk9600Modulator.hpp | 216 +++++++++++++++++++++++++++ TNC/Goertzel.h | 150 ++++++++++--------- TNC/HDLCEncoder.hpp | 8 +- TNC/IOEventTask.cpp | 4 + TNC/KissHardware.cpp | 35 ++++- TNC/KissHardware.hpp | 5 +- TNC/Modulator.hpp | 93 ++++++++++++ TNC/ModulatorTask.cpp | 29 +++- TNC/ModulatorTask.hpp | 8 +- stlink-tnc3.cfg | 2 +- 27 files changed, 1404 insertions(+), 353 deletions(-) create mode 100644 TNC/Afsk1200Demodulator.cpp create mode 100644 TNC/Afsk1200Demodulator.hpp create mode 100644 TNC/Demodulator.cpp create mode 100644 TNC/Demodulator.hpp create mode 100644 TNC/FilterCoefficients.cpp create mode 100644 TNC/Fsk9600Demodulator.cpp create mode 100644 TNC/Fsk9600Demodulator.hpp create mode 100644 TNC/Fsk9600Modulator.cpp create mode 100644 TNC/Fsk9600Modulator.hpp create mode 100644 TNC/Modulator.hpp diff --git a/TNC/AFSKModulator.hpp b/TNC/AFSKModulator.hpp index 7f31e04..e95456f 100644 --- a/TNC/AFSKModulator.hpp +++ b/TNC/AFSKModulator.hpp @@ -1,13 +1,13 @@ // Copyright 2015-2019 Mobilinkd LLC // All rights reserved. -#ifndef MOBILINKD__TNC__AFSK_MODULATOR_HPP_ -#define MOBILINKD__TNC__AFSK_MODULATOR_HPP_ +#pragma once #include #include "PTT.hpp" #include "Log.h" +#include "Modulator.hpp" #include "stm32l4xx_hal.h" #include "cmsis_os.h" @@ -62,8 +62,8 @@ const int16_t sin_table[SIN_TABLE_LEN] = { }; -struct AFSKModulator { - +struct AFSKModulator : Modulator +{ static const size_t DAC_BUFFER_LEN = 44; static const size_t BIT_LEN = DAC_BUFFER_LEN / 2; static const size_t MARK_SKIP = 12; @@ -84,7 +84,24 @@ struct AFSKModulator { buffer_[i] = 2048; } - void set_volume(uint16_t v) + void init(const kiss::Hardware& hw) override + { + set_twist(hw.tx_twist); + + // Configure 80MHz clock for 26.4ksps. + htim7.Init.Period = 3029; + if (HAL_TIM_Base_Init(&htim7) != HAL_OK) + { + ERROR("htim7 init failed"); + CxxErrorHandler(); + } + } + + void deinit() override + { + } + + void set_gain(uint16_t v) override { v = std::max(256, v); v = std::min(4096, v); @@ -103,7 +120,8 @@ struct AFSKModulator { void set_twist(uint8_t twist) {twist_ = twist;} - void send(bool bit) { + void send(bool bit) override + { switch (running_) { case -1: fill_first(bit); @@ -122,8 +140,10 @@ struct AFSKModulator { } } - void fill(uint16_t* buffer, bool bit) { - for (size_t i = 0; i != BIT_LEN; i++) { + void fill(uint16_t* buffer, bool bit) + { + for (size_t i = 0; i != BIT_LEN; i++) + { int s = sin_table[pos_]; s -= 2048; s *= volume_; @@ -149,15 +169,18 @@ struct AFSKModulator { } } - void fill_first(bool bit) { + void fill_first(bool bit) override + { fill(buffer_, bit); } - void fill_last(bool bit) { + void fill_last(bool bit) override + { fill(buffer_ + BIT_LEN, bit); } - void empty() { + void empty() override + { switch (running_) { case 1: running_ = 0; @@ -174,7 +197,8 @@ struct AFSKModulator { } } - void abort() { + void abort() override + { running_ = -1; HAL_DAC_Stop_DMA(&hdac1, DAC_CHANNEL_1); HAL_TIM_Base_Stop(&htim7); @@ -184,9 +208,11 @@ struct AFSKModulator { // Drain the queue. while (osMessageGet(dacOutputQueueHandle_, 0).status == osEventMessage); } + + float bits_per_ms() const override + { + return 1.2f; + } }; }} // mobilinkd::tnc - - -#endif // MOBILINKD__TNC__AFSK_MODULATOR_HPP_ diff --git a/TNC/Afsk1200Demodulator.cpp b/TNC/Afsk1200Demodulator.cpp new file mode 100644 index 0000000..f9fe4d0 --- /dev/null +++ b/TNC/Afsk1200Demodulator.cpp @@ -0,0 +1,93 @@ +// Copyright 2020 Rob Riggs +// All rights reserved. + +#include "Afsk1200Demodulator.hpp" +#include "Goertzel.h" +#include "AudioInput.hpp" + +namespace mobilinkd { namespace tnc { + +afsk1200::emphasis_filter_type Afsk1200Demodulator::filter_1; +afsk1200::emphasis_filter_type Afsk1200Demodulator::filter_2; +afsk1200::emphasis_filter_type Afsk1200Demodulator::filter_3; + +afsk1200::Demodulator Afsk1200Demodulator::demod1(26400, Afsk1200Demodulator::filter_1); +afsk1200::Demodulator Afsk1200Demodulator::demod2(26400, Afsk1200Demodulator::filter_2); +afsk1200::Demodulator Afsk1200Demodulator::demod3(26400, Afsk1200Demodulator::filter_3); + +/* + * Return twist as a the difference in dB between mark and space. The + * expected values are about 0dB for discriminator output and about 5.5dB + * for de-emphasized audio. + */ +float Afsk1200Demodulator::readTwist() +{ + DEBUG("enter Afsk1200Demodulator::readTwist"); + constexpr uint32_t channel = AUDIO_IN; + + float g1200 = 0.0f; + float g2200 = 0.0f; + + GoertzelFilter gf1200(1200.0, 0); + GoertzelFilter gf2200(2200.0, 0); + + const uint32_t AVG_SAMPLES = 20; + + startADC(1817, ADC_BLOCK_SIZE); + + for (uint32_t i = 0; i != AVG_SAMPLES; ++i) + { + uint32_t count = 0; + while (count < ADC_BLOCK_SIZE) + { + osEvent evt = osMessageGet(adcInputQueueHandle, osWaitForever); + if (evt.status != osEventMessage) + continue; + + auto block = (audio::adc_pool_type::chunk_type*) evt.value.p; + uint16_t* data = (uint16_t*) block->buffer; + gf1200(data, ADC_BLOCK_SIZE); + gf2200(data, ADC_BLOCK_SIZE); + + audio::adcPool.deallocate(block); + + count += ADC_BLOCK_SIZE; + } + + g1200 += (gf1200 / count); + g2200 += (gf2200 / count); + + gf1200.reset(); + gf2200.reset(); + } + + IDemodulator::stopADC(); + + g1200 = 10.0f * log10f(g1200 / AVG_SAMPLES); + g2200 = 10.0f * log10f(g2200 / AVG_SAMPLES); + + auto result = g1200 - g2200; + + INFO("Twist = %d / 100 (%d - %d)", int(result * 100), int(g1200), + int(g2200)); + + DEBUG("exit readTwist"); + return result; +} + +const q15_t Afsk1200Demodulator::bpf_coeffs[FILTER_TAP_NUM] = { + 4, 0, -5, -10, -13, -12, -9, -4, -2, -4, -12, -26, + -41, -52, -51, -35, -3, 39, 83, 117, 131, 118, 83, 36, + -6, -32, -30, -3, 36, 67, 66, 19, -74, -199, -323, -408, + -421, -344, -187, 17, 218, 364, 417, 369, 247, 106, 14, 26, + 166, 407, 676, 865, 866, 605, 68, -675, -1484, -2171, -2547, -2471, +-1895, -882, 394, 1692, 2747, 3337, 3337, 2747, 1692, 394, -882, -1895, +-2471, -2547, -2171, -1484, -675, 68, 605, 866, 865, 676, 407, 166, + 26, 14, 106, 247, 369, 417, 364, 218, 17, -187, -344, -421, + -408, -323, -199, -74, 19, 66, 67, 36, -3, -30, -32, -6, + 36, 83, 118, 131, 117, 83, 39, -3, -35, -51, -52, -41, + -26, -12, -4, -2, -4, -9, -12, -13, -10, -5, 0, 4, +}; + +}} // mobilinkd::tnc + diff --git a/TNC/Afsk1200Demodulator.hpp b/TNC/Afsk1200Demodulator.hpp new file mode 100644 index 0000000..7377099 --- /dev/null +++ b/TNC/Afsk1200Demodulator.hpp @@ -0,0 +1,181 @@ +// Copyright 2020 Rob Riggs +// All rights reserved. + +#pragma once + +#include "Demodulator.hpp" +#include "AfskDemodulator.hpp" +#include "FirFilter.hpp" +#include "FilterCoefficients.hpp" +#include "KissHardware.hpp" +#include "HdlcFrame.hpp" + +namespace mobilinkd { namespace tnc { + +struct Afsk1200Demodulator : IDemodulator +{ + /* + * Generated with Scipy Filter, 152 coefficients, 1100-2300Hz bandpass, + * Hann window, starting and ending 0 value coefficients removed. + * + * np.array( + * firwin2(152, + * [ + * 0.0, + * 1000.0/(sample_rate/2), + * 1100.0/(sample_rate/2), + * 2350.0/(sample_rate/2), + * 2500.0/(sample_rate/2), + * 1.0 + * ], + * [0,0,1,1,0,0], + * antisymmetric = False, + * window='hann') * 32768, + * dtype=int)[10:-10] + */ + static constexpr size_t FILTER_TAP_NUM = 132; + static constexpr uint32_t ADC_BLOCK_SIZE = afsk1200::ADC_BUFFER_SIZE; + static constexpr uint32_t SAMPLE_RATE = 26400; + static_assert(audio::ADC_BUFFER_SIZE >= ADC_BLOCK_SIZE); + + using audio_filter_t = Q15FirFilter; + + static const q15_t bpf_coeffs[FILTER_TAP_NUM]; + + + static afsk1200::emphasis_filter_type filter_1; + static afsk1200::emphasis_filter_type filter_2; + static afsk1200::emphasis_filter_type filter_3; + + static afsk1200::Demodulator demod1; + static afsk1200::Demodulator demod2; + static afsk1200::Demodulator demod3; + + audio_filter_t demod_filter; + uint16_t last_fcs{0}; + uint32_t last_counter{0}; + uint32_t counter{0}; + bool locked_{false}; + + virtual ~Afsk1200Demodulator() {} + + void start() override + { + INFO("Setting 48MHz SysClock."); + SysClock48(); + + // rx_twist is 6dB for discriminator input and 0db for de-emphasized input. + auto twist = kiss::settings().rx_twist; + + filter_1.init(*filter::fir::AfskFilters[twist + 3]); + filter_2.init(*filter::fir::AfskFilters[twist + 6]); + filter_3.init(*filter::fir::AfskFilters[twist + 9]); + + last_fcs = 0; + last_counter = 0; + counter = 0; + + demod_filter.init(bpf_coeffs); + + hadc1.Init.OversamplingMode = ENABLE; + if (HAL_ADC_Init(&hadc1) != HAL_OK) + { + CxxErrorHandler(); + } + + ADC_ChannelConfTypeDef sConfig; + + sConfig.Channel = AUDIO_IN; + sConfig.Rank = ADC_REGULAR_RANK_1; + sConfig.SingleDiff = ADC_SINGLE_ENDED; + sConfig.SamplingTime = ADC_SAMPLETIME_12CYCLES_5; + sConfig.OffsetNumber = ADC_OFFSET_NONE; + sConfig.Offset = 0; + if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) + CxxErrorHandler(); + + startADC(1817, ADC_BLOCK_SIZE); + } + + void stop() override + { + stopADC(); + locked_ = false; + } + + hdlc::IoFrame* operator()(const q15_t* samples) override + { + hdlc::IoFrame* result = nullptr; + + q15_t* filtered = demod_filter(const_cast(samples)); + + ++counter; + +#if 1 + auto frame1 = demod1(filtered, ADC_BLOCK_SIZE); + if (frame1) + { + if (frame1->fcs() != last_fcs or counter > last_counter + 2) + { + last_fcs = frame1->fcs(); + last_counter = counter; + result = frame1; + } + else + { + hdlc::release (frame1); + } + } +#endif + +#if 1 + auto frame2 = demod2(filtered, ADC_BLOCK_SIZE); + if (frame2) + { + if (frame2->fcs() != last_fcs or counter > last_counter + 2) + { + last_fcs = frame2->fcs(); + last_counter = counter; + result = frame2; + } + else + { + hdlc::release(frame2); + } + } +#endif + +#if 1 + auto frame3 = demod3(filtered, ADC_BLOCK_SIZE); + if (frame3) + { + if (frame3->fcs() != last_fcs or counter > last_counter + 2) + { + last_fcs = frame3->fcs(); + last_counter = counter; + result = frame3; + } + else + { + hdlc::release(frame3); + } + } +#endif + locked_ = demod1.locked() or demod2.locked() or demod3.locked(); + return result; + } + + float readTwist() override; + + bool locked() const override + { + return locked_; + } + + size_t size() const override + { + return ADC_BLOCK_SIZE; + } +}; + +}} // mobilinkd::tnc diff --git a/TNC/AfskDemodulator.hpp b/TNC/AfskDemodulator.hpp index a3411c0..4769417 100644 --- a/TNC/AfskDemodulator.hpp +++ b/TNC/AfskDemodulator.hpp @@ -31,7 +31,8 @@ const q15_t lpf_coeffs[] = { 14, 18, 20, 19, 17, 14, 11, 8, 5, 3, 1, 0, }; -typedef FirFilter emphasis_filter_type; +static constexpr uint32_t ADC_BUFFER_SIZE = 88; +typedef FirFilter emphasis_filter_type; struct Demodulator { diff --git a/TNC/AudioInput.cpp b/TNC/AudioInput.cpp index 2bde7e5..2bbe632 100644 --- a/TNC/AudioInput.cpp +++ b/TNC/AudioInput.cpp @@ -2,15 +2,13 @@ // All rights reserved. #include "AudioInput.hpp" -#include "AfskDemodulator.hpp" +#include "Afsk1200Demodulator.hpp" +#include "Fsk9600Demodulator.hpp" #include "AudioLevel.hpp" #include "Log.h" #include "KissHardware.hpp" #include "GPIO.hpp" #include "HdlcFrame.hpp" -#include "memory.hpp" -#include "IirFilter.hpp" -#include "FilterCoefficients.hpp" #include "PortInterface.hpp" #include "Goertzel.h" #include "DCD.h" @@ -28,18 +26,13 @@ extern osMessageQId ioEventQueueHandle; extern "C" void SystemClock_Config(void); -// 1kB -typedef mobilinkd::tnc::memory::Pool< - 8, mobilinkd::tnc::audio::ADC_BUFFER_SIZE * 2> adc_pool_type; -adc_pool_type adcPool; - // DMA Conversion first half complete. extern "C" void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef*) { using namespace mobilinkd::tnc::audio; auto block = adcPool.allocate(); if (!block) return; - memmove(block->buffer, adc_buffer, ADC_BUFFER_SIZE * 2); + memmove(block->buffer, adc_buffer, dma_transfer_size); auto status = osMessagePut(adcInputQueueHandle, (uint32_t) block, 0); if (status != osOK) adcPool.deallocate(block); } @@ -50,7 +43,7 @@ extern "C" void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef*) { auto block = adcPool.allocate(); if (!block) return; - memmove(block->buffer, adc_buffer + DMA_TRANSFER_SIZE, ADC_BUFFER_SIZE * 2); + memmove(block->buffer, adc_buffer + half_buffer_size, dma_transfer_size); auto status = osMessagePut(adcInputQueueHandle, (uint32_t) block, 0); if (status != osOK) adcPool.deallocate(block); } @@ -132,96 +125,47 @@ extern "C" void startAudioInputTask(void const*) { namespace mobilinkd { namespace tnc { namespace audio { -/* - * Generated with Scipy Filter, 152 coefficients, 1100-2300Hz bandpass, - * Hann window, starting and ending 0 value coefficients removed. - * - * np.array( - * firwin2(152, - * [ - * 0.0, - * 1000.0/(sample_rate/2), - * 1100.0/(sample_rate/2), - * 2350.0/(sample_rate/2), - * 2500.0/(sample_rate/2), - * 1.0 - * ], - * [0,0,1,1,0,0], - * antisymmetric = False, - * window='hann') * 32768, - * dtype=int)[10:-10] - */ -constexpr size_t FILTER_TAP_NUM = 132; -const q15_t bpf_coeffs[] = { - 4, 0, -5, -10, -13, -12, -9, -4, -2, -4, -12, -26, - -41, -52, -51, -35, -3, 39, 83, 117, 131, 118, 83, 36, - -6, -32, -30, -3, 36, 67, 66, 19, -74, -199, -323, -408, - -421, -344, -187, 17, 218, 364, 417, 369, 247, 106, 14, 26, - 166, 407, 676, 865, 866, 605, 68, -675, -1484, -2171, -2547, -2471, --1895, -882, 394, 1692, 2747, 3337, 3337, 2747, 1692, 394, -882, -1895, --2471, -2547, -2171, -1484, -675, 68, 605, 866, 865, 676, 407, 166, - 26, 14, 106, 247, 369, 417, 364, 218, 17, -187, -344, -421, - -408, -323, -199, -74, 19, 66, 67, 36, -3, -30, -32, -6, - 36, 83, 118, 131, 117, 83, 39, -3, -35, -51, -52, -41, - -26, -12, -4, -2, -4, -9, -12, -13, -10, -5, 0, 4, -}; +uint32_t adc_buffer[ADC_BUFFER_SIZE]; // Two samples per element. +uint32_t adc_block_size = ADC_BUFFER_SIZE; // Based on demodulator. +uint32_t dma_transfer_size = adc_block_size * 2; // Transfer size in bytes. +uint32_t half_buffer_size = adc_block_size / 2; // Transfer size in words / 2. +adc_pool_type adcPool; -uint32_t adc_buffer[ADC_BUFFER_SIZE]; // Two samples per element. - -typedef Q15FirFilter audio_filter_type; - -audio_filter_type audio_filter; - -mobilinkd::tnc::afsk1200::emphasis_filter_type filter_1; -mobilinkd::tnc::afsk1200::emphasis_filter_type filter_2; -mobilinkd::tnc::afsk1200::emphasis_filter_type filter_3; - -mobilinkd::tnc::afsk1200::Demodulator& getDemod1(const TFirCoefficients<9>& f) __attribute__((noinline)); -mobilinkd::tnc::afsk1200::Demodulator& getDemod2(const TFirCoefficients<9>& f) __attribute__((noinline)); -mobilinkd::tnc::afsk1200::Demodulator& getDemod3(const TFirCoefficients<9>& f) __attribute__((noinline)); - -mobilinkd::tnc::afsk1200::Demodulator& getDemod1(const TFirCoefficients<9>& f) { - filter_1.init(f); - static mobilinkd::tnc::afsk1200::Demodulator instance(26400, filter_1); - return instance; -} - -mobilinkd::tnc::afsk1200::Demodulator& getDemod2(const TFirCoefficients<9>& f) { - filter_2.init(f); - static mobilinkd::tnc::afsk1200::Demodulator instance(26400, filter_2); - return instance; -} - -mobilinkd::tnc::afsk1200::Demodulator& getDemod3(const TFirCoefficients<9>& f) { - filter_3.init(f); - static mobilinkd::tnc::afsk1200::Demodulator instance(26400, filter_3); - return instance; +void set_adc_block_size(uint32_t block_size) +{ + adc_block_size = block_size; + dma_transfer_size = block_size * 2; + half_buffer_size = block_size / 2; } q15_t normalized[ADC_BUFFER_SIZE]; + +IDemodulator* getDemodulator() +{ + static Afsk1200Demodulator afsk1200; + static Fsk9600Demodulator fsk9600; + + switch (kiss::settings().modem_type) + { + case kiss::Hardware::ModemType::AFSK1200: + return &afsk1200; + case kiss::Hardware::ModemType::FSK9600: + return &fsk9600; + default: + ERROR("Invalid demodulator"); + CxxErrorHandler(); + } +} + void demodulatorTask() { DEBUG("enter demodulatorTask"); - audio_filter.init(bpf_coeffs); - - // rx_twist is 6dB for discriminator input and 0db for de-emphasized input. - auto twist = kiss::settings().rx_twist; - - mobilinkd::tnc::afsk1200::Demodulator& demod1 = getDemod1(*filter::fir::AfskFilters[twist + 3]); - mobilinkd::tnc::afsk1200::Demodulator& demod2 = getDemod2(*filter::fir::AfskFilters[twist + 6]); - mobilinkd::tnc::afsk1200::Demodulator& demod3 = getDemod3(*filter::fir::AfskFilters[twist + 9]); - - startADC(AUDIO_IN); - - mobilinkd::tnc::hdlc::IoFrame* frame = 0; - - uint16_t last_fcs = 0; - uint32_t last_counter = 0; - uint32_t counter = 0; - bool dcd_status{false}; + auto demodulator = getDemodulator(); + + demodulator->start(); while (true) { osEvent peek = osMessagePeek(audioInputQueueHandle, 0); @@ -232,80 +176,36 @@ void demodulatorTask() { continue; } - ++counter; + gpio::BAT_DIVIDER::off(); auto block = (adc_pool_type::chunk_type*) evt.value.p; auto samples = (int16_t*) block->buffer; - arm_offset_q15(samples, 0 - virtual_ground, normalized, ADC_BUFFER_SIZE); + arm_offset_q15(samples, 0 - virtual_ground, normalized, demodulator->size()); adcPool.deallocate(block); - q15_t* audio = audio_filter(normalized); -#if 1 - frame = demod1(audio, ADC_BUFFER_SIZE); - if (frame) { - if (frame->fcs() != last_fcs or counter > last_counter + 2) { - auto save_fcs = frame->fcs(); - if (osMessagePut(ioEventQueueHandle, (uint32_t) frame, 1) == osOK) { - last_fcs = save_fcs; - last_counter = counter; - } else { - hdlc::release(frame); - } - } - else { + auto frame = (*demodulator)(normalized); + if (frame) + { + if (osMessagePut(ioEventQueueHandle, (uint32_t) frame, 1) != osOK) + { hdlc::release(frame); } } -#endif -#if 1 - frame = demod2(audio, ADC_BUFFER_SIZE); - if (frame) { - if (frame->fcs() != last_fcs or counter > last_counter + 2) { - auto save_fcs = frame->fcs(); - if (osMessagePut(ioEventQueueHandle, (uint32_t) frame, 1) == osOK) { - last_fcs = save_fcs; - last_counter = counter; - } else { - hdlc::release(frame); - } - } - else { - hdlc::release(frame); - } - } -#endif - -#if 1 - frame = demod3(audio, ADC_BUFFER_SIZE); - if (frame) { - if (frame->fcs() != last_fcs or counter > last_counter + 2) { - auto save_fcs = frame->fcs(); - if (osMessagePut(ioEventQueueHandle, (uint32_t) frame, 1) == osOK) { - last_fcs = save_fcs; - last_counter = counter; - } else { - hdlc::release(frame); - } - } - else { - hdlc::release(frame); - } - } -#endif - bool new_dcd_status = demod1.locked() or demod2.locked() or demod3.locked(); - if (new_dcd_status xor dcd_status) { - dcd_status = new_dcd_status; + if (demodulator->locked() xor dcd_status) { + dcd_status = demodulator->locked(); if (dcd_status) { dcd_on(); } else { dcd_off(); } } + gpio::BAT_DIVIDER::on(); } - stopADC(); + demodulator->stop(); + dcd_off(); DEBUG("exit demodulatorTask"); } @@ -317,7 +217,9 @@ void streamLevels(uint32_t channel, uint8_t cmd) { uint8_t data[9]; INFO("streamLevels: start"); - startADC(channel); + + auto demodulator = getDemodulator(); + demodulator->start(); while (true) { osEvent peek = osMessagePeek(audioInputQueueHandle, 0); @@ -328,15 +230,15 @@ void streamLevels(uint32_t channel, uint8_t cmd) { uint16_t vmin = std::numeric_limits::max(); uint16_t vmax = std::numeric_limits::min(); - while (count < 2640) { + while (count < demodulator->size() * 30) { osEvent evt = osMessageGet(adcInputQueueHandle, osWaitForever); if (evt.status != osEventMessage) continue; - count += ADC_BUFFER_SIZE; + count += demodulator->size(); auto block = (adc_pool_type::chunk_type*) evt.value.p; auto start = (uint16_t*) block->buffer; - auto end = start + ADC_BUFFER_SIZE; + auto end = start + demodulator->size(); vmin = std::min(vmin, *std::min_element(start, end)); vmax = std::max(vmax, *std::max_element(start, end)); @@ -363,46 +265,51 @@ void streamLevels(uint32_t channel, uint8_t cmd) { ioport->write(data, 9, 6, 10); } - stopADC(); + demodulator->stop(); DEBUG("exit streamLevels"); } -levels_type readLevels(uint32_t channel, uint32_t samples) { +levels_type readLevels(uint32_t) +{ DEBUG("enter readLevels"); // Return Vpp, Vavg, Vmin, Vmax as four 16-bit values, right justified. - uint16_t count = 0; + uint32_t BLOCKS = 30; uint32_t accum = 0; + uint32_t iaccum = 0; uint16_t vmin = std::numeric_limits::max(); uint16_t vmax = std::numeric_limits::min(); INFO("readLevels: start"); - startADC(channel); - - while (count < samples) { + auto demodulator = getDemodulator(); + demodulator->start(); + for (uint32_t count = 0; count != BLOCKS; ++count) + { osEvent evt = osMessageGet(adcInputQueueHandle, osWaitForever); if (evt.status != osEventMessage) continue; auto block = (adc_pool_type::chunk_type*) evt.value.p; auto start = (uint16_t*) block->buffer; - auto end = start + ADC_BUFFER_SIZE; + auto end = start + demodulator->size(); vmin = std::min(vmin, *std::min_element(start, end)); vmax = std::max(vmax, *std::max_element(start, end)); accum = std::accumulate(start, end, accum); + iaccum += (accum / demodulator->size()); + adcPool.deallocate(block); - count += ADC_BUFFER_SIZE; + accum = 0; } - stopADC(); + demodulator->stop(); uint16_t pp = vmax - vmin; - uint16_t avg = accum / count; + uint16_t avg = iaccum / BLOCKS; DEBUG("exit readLevels"); return levels_type(pp, avg, vmin, vmax); @@ -421,56 +328,7 @@ constexpr uint32_t TWIST_SAMPLE_SIZE = 88; */ float readTwist() { - - DEBUG("enter readTwist"); - constexpr uint32_t channel = AUDIO_IN; - - float g1200 = 0.0f; - float g2200 = 0.0f; - - GoertzelFilter gf1200(1200.0, 0); - GoertzelFilter gf2200(2200.0, 0); - - const uint32_t AVG_SAMPLES = 20; - - startADC(channel); - - for (uint32_t i = 0; i != AVG_SAMPLES; ++i) - { - uint32_t count = 0; - while (count < TWIST_SAMPLE_SIZE) - { - osEvent evt = osMessageGet(adcInputQueueHandle, osWaitForever); - if (evt.status != osEventMessage) continue; - - auto block = (adc_pool_type::chunk_type*) evt.value.p; - uint16_t* data = (uint16_t*) block->buffer; - gf1200(data, ADC_BUFFER_SIZE); - gf2200(data, ADC_BUFFER_SIZE); - - adcPool.deallocate(block); - - count += ADC_BUFFER_SIZE; - } - - g1200 += (gf1200 / count); - g2200 += (gf2200 / count); - - gf1200.reset(); - gf2200.reset(); - } - - stopADC(); - - g1200 = 10.0f * log10f(g1200 / AVG_SAMPLES); - g2200 = 10.0f * log10f(g2200 / AVG_SAMPLES); - - auto result = g1200 - g2200; - - INFO("Twist = %d / 100 (%d - %d)", int(result*100), int(g1200), int(g2200)); - - DEBUG("exit readTwist"); - return result; + return getDemodulator()->readTwist(); } /* @@ -507,7 +365,7 @@ void pollInputTwist() const uint32_t AVG_SAMPLES = 100; - startADC(channel); + IDemodulator::startADC(3029, TWIST_SAMPLE_SIZE); for (uint32_t i = 0; i != AVG_SAMPLES; ++i) { @@ -534,7 +392,7 @@ void pollInputTwist() gf2200.reset(); } - stopADC(); + IDemodulator::stopADC(); DEBUG("pollInputTwist: MARK=%d, SPACE=%d (x100)", int(g1200 * 100.0 / AVG_SAMPLES), int(g2200 * 100.0 / AVG_SAMPLES)); diff --git a/TNC/AudioInput.hpp b/TNC/AudioInput.hpp index 7b186b3..5ede020 100644 --- a/TNC/AudioInput.hpp +++ b/TNC/AudioInput.hpp @@ -4,6 +4,8 @@ #ifndef MOBILINKD__TNC__AUDIO__INPUT_HPP_ #define MOBILINKD__TNC__AUDIO__INPUT_HPP_ +#include "memory.hpp" + #include "main.h" #include "stm32l4xx_hal.h" #include "cmsis_os.h" @@ -83,10 +85,19 @@ enum AdcState { STREAM_INSTANT_TWIST_LEVEL }; -const size_t ADC_BUFFER_SIZE = 88; -const size_t DMA_TRANSFER_SIZE = ADC_BUFFER_SIZE / 2; +const size_t ADC_BUFFER_SIZE = 384; extern uint32_t adc_buffer[]; // Two int16_t samples per element. +extern uint32_t adc_block_size; +extern uint32_t dma_transfer_size; +extern uint32_t half_buffer_size; +// 1kB +typedef memory::Pool<8, ADC_BUFFER_SIZE * 2> adc_pool_type; +extern adc_pool_type adcPool; + +void set_adc_block_size(uint32_t block_size); + +#if 0 inline void stopADC() { if (HAL_ADC_Stop_DMA(&hadc1) != HAL_OK) CxxErrorHandler(); @@ -118,10 +129,11 @@ inline void restartADC() { if (HAL_ADC_Start_DMA(&hadc1, adc_buffer, ADC_BUFFER_SIZE * 2) != HAL_OK) CxxErrorHandler(); } +#endif /// Vpp, Vavg, Vmin, Vmax typedef std::tuple levels_type; -levels_type readLevels(uint32_t channel, uint32_t samples = 2640); +levels_type readLevels(uint32_t channel); float readTwist(); void demodulatorTask(); diff --git a/TNC/AudioLevel.cpp b/TNC/AudioLevel.cpp index ea7f3f4..096fb25 100644 --- a/TNC/AudioLevel.cpp +++ b/TNC/AudioLevel.cpp @@ -13,6 +13,8 @@ #include "stm32l4xx_hal.h" #include +#include +#include #include #include @@ -199,7 +201,7 @@ void setAudioOutputLevel() } else { gpio::AUDIO_OUT_ATTEN::off(); } - getModulator().set_volume(r); + getModulator().set_gain(r); } }}} // mobilinkd::tnc::audio diff --git a/TNC/AudioLevel.hpp b/TNC/AudioLevel.hpp index 1b0aeaa..6f7533d 100644 --- a/TNC/AudioLevel.hpp +++ b/TNC/AudioLevel.hpp @@ -28,7 +28,7 @@ void setAudioInputLevels(); void setAudioOutputLevel(); extern bool streamInputDCOffset; -constexpr const uint16_t vref = 16383; // Must match ADC output (adjust when oversampling) +constexpr const uint16_t vref = 4095; // Must match ADC output (adjust when oversampling) extern uint16_t virtual_ground; extern float i_vgnd; diff --git a/TNC/Demodulator.cpp b/TNC/Demodulator.cpp new file mode 100644 index 0000000..7fffc5e --- /dev/null +++ b/TNC/Demodulator.cpp @@ -0,0 +1,44 @@ +// Copyright 2020 Rob Riggs +// All rights reserved. + +#include "Demodulator.hpp" + +namespace mobilinkd { namespace tnc { + +/** + * Start the ADC DMA transfer. The block size is equal to the number of + * 32-bit elements in the buffer. This is also equal to the number of + * 16-bit elements in each DMA "half complete" transfer. Each DMA transfer + * results in block_size * 2 bytes being transferred. + * + * We must bear in mind that the DMA buffer size is expressed in DWORDs, + * the DMA transfer size is expressed in WORDs, and the memory copy operation + * in BYTEs. + * + * @param period + * @param block_size + */ +void IDemodulator::startADC(uint32_t period, uint32_t block_size) +{ + audio::set_adc_block_size(block_size); + + htim6.Init.Period = period; + htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; + if (HAL_TIM_Base_Init(&htim6) != HAL_OK) + { + CxxErrorHandler(); + } + + if (HAL_TIM_Base_Start(&htim6) != HAL_OK) + { + CxxErrorHandler(); + } + + if (HAL_ADC_Start_DMA(&hadc1, audio::adc_buffer, + block_size * 2) != HAL_OK) + { + CxxErrorHandler(); + } +} + +}} // mobilinkd::tnc diff --git a/TNC/Demodulator.hpp b/TNC/Demodulator.hpp new file mode 100644 index 0000000..9eb2b3c --- /dev/null +++ b/TNC/Demodulator.hpp @@ -0,0 +1,43 @@ +// Copyright 2020 Rob Riggs +// All rights reserved. + +#pragma once + +#include "HdlcFrame.hpp" +#include "FirFilter.hpp" +#include "AudioInput.hpp" + +#include + +#include + +namespace mobilinkd { namespace tnc { + +constexpr size_t FILTER_TAP_NUM = 132; + +using demod_filter_t = std::function; +using demodulator_t = std::function; + +struct IDemodulator +{ + virtual void start() = 0; + virtual void stop() = 0; + virtual hdlc::IoFrame* operator()(const q15_t* samples) = 0; + virtual float readTwist() = 0; + + virtual bool locked() const = 0; + virtual size_t size() const = 0; + + virtual ~IDemodulator() {} + + static void startADC(uint32_t period, uint32_t block_size); + + static void stopADC() { + if (HAL_ADC_Stop_DMA(&hadc1) != HAL_OK) + CxxErrorHandler(); + if (HAL_TIM_Base_Stop(&htim6) != HAL_OK) + CxxErrorHandler(); + } +}; + +}} // mobilinkd::tnc diff --git a/TNC/DigitalPLL.hpp b/TNC/DigitalPLL.hpp index 802dd28..13a73a9 100644 --- a/TNC/DigitalPLL.hpp +++ b/TNC/DigitalPLL.hpp @@ -29,10 +29,10 @@ struct PLLResult { // scipy.signal: // b, a = bessel(4, [80.0/(1200/2)], 'lowpass') // -const std::array lock_b = { +constexpr std::array lock_b = { 1.077063e-03,4.308253e-03,6.462379e-03,4.308253e-03,1.077063e-03, }; -const std::array lock_a = { +constexpr std::array lock_a = { 1.000000e+00,-2.774567e+00,2.962960e+00,-1.437990e+00,2.668296e-01, }; @@ -41,9 +41,10 @@ const std::array lock_a = { // loop_coeffs = firwin(9, [64.0/(1200/2)], width = None, // pass_zero = True, scale = True, window='hann') // -const std::array loop_coeffs = { +constexpr std::array loop_coeffs = { // 0.08160962754214955, 0.25029850550446403, 0.3361837339067726, 0.2502985055044641, 0.08160962754214969 - 3.196252e-02,1.204223e-01,2.176819e-01,2.598666e-01,2.176819e-01,1.204223e-01,3.196252e-02 +// 3.196252e-02,1.204223e-01,2.176819e-01,2.598666e-01,2.176819e-01,1.204223e-01,3.196252e-02 + 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0 }; } // pll @@ -59,7 +60,7 @@ struct BaseDigitalPLL float_type sps_; ///< Samples per symbol float_type limit_; ///< Samples per symbol / 2 libafsk::BaseHysteresis lock_; - FirFilter<1, 7> loop_filter_{pll::loop_coeffs.begin()}; + FirFilter<1, pll::loop_coeffs.size()> loop_filter_{pll::loop_coeffs.begin()}; IirFilter<5> lock_filter_{pll::lock_b, pll::lock_a}; bool last_; diff --git a/TNC/FilterCoefficients.cpp b/TNC/FilterCoefficients.cpp new file mode 100644 index 0000000..d335794 --- /dev/null +++ b/TNC/FilterCoefficients.cpp @@ -0,0 +1,35 @@ +// Copyright 2020 Rob Riggs +// All rights reserved. + +#include "FilterCoefficients.hpp" + +namespace mobilinkd { namespace tnc { namespace filter { + +namespace fir { + +const TFirCoefficients<9>* AfskFilters[19] = { + &dB_6, + &dB_5, + &dB_4, + &dB_3, + &dB_2, + &dB_1, + &dB0, + &dB1, + &dB2, + &dB3, + &dB4, + &dB5, + &dB6, + &dB7, + &dB8, + &dB9, + &dB10, + &dB11, + &dB12 +}; + +} // fir + + +}}} // mobilinkd::tnc::filter diff --git a/TNC/FilterCoefficients.hpp b/TNC/FilterCoefficients.hpp index 5711dfc..f422526 100644 --- a/TNC/FilterCoefficients.hpp +++ b/TNC/FilterCoefficients.hpp @@ -297,27 +297,7 @@ const TFirCoefficients<9> dB_6 = { } }; -const TFirCoefficients<9>* AfskFilters[] = { - &dB_6, - &dB_5, - &dB_4, - &dB_3, - &dB_2, - &dB_1, - &dB0, - &dB1, - &dB2, - &dB3, - &dB4, - &dB5, - &dB6, - &dB7, - &dB8, - &dB9, - &dB10, - &dB11, - &dB12 -}; +extern const TFirCoefficients<9>* AfskFilters[19]; } // fir diff --git a/TNC/FirFilter.hpp b/TNC/FirFilter.hpp index b679ee7..5ec221d 100644 --- a/TNC/FirFilter.hpp +++ b/TNC/FirFilter.hpp @@ -96,7 +96,6 @@ template struct Q15FirFilter { const q15_t* filter_taps{nullptr}; q15_t filter_state[BLOCK_SIZE + FILTER_SIZE - 1]; - q15_t filter_input[BLOCK_SIZE]; q15_t filter_output[BLOCK_SIZE]; q15_t vgnd_{0}; q15_t i_vgnd_{0}; diff --git a/TNC/Fsk9600Demodulator.cpp b/TNC/Fsk9600Demodulator.cpp new file mode 100644 index 0000000..2c94cc6 --- /dev/null +++ b/TNC/Fsk9600Demodulator.cpp @@ -0,0 +1,287 @@ +// Copyright 2020 Rob Riggs +// All rights reserved. + +#include "Fsk9600Demodulator.hpp" +#include "Goertzel.h" +#include "AudioInput.hpp" + +namespace mobilinkd { namespace tnc { + +hdlc::IoFrame* Fsk9600Demodulator::operator()(const q15_t* samples) +{ + hdlc::IoFrame* result = nullptr; + + auto filtered = demod_filter(const_cast(samples)); + + for (size_t i = 0; i != ADC_BLOCK_SIZE; ++i) + { + auto sample = filtered[i]; + + bool bit = sample >= 0; + auto pll = pll_(bit); + + if (pll.sample) + { + locked_ = pll.locked; + + // We will only ever get one frame because there are + // not enough bits in a block for more than one. + if (result) { + auto tmp = hdlc_decoder_(nrzi_.decode(lfsr_(bit)), locked_); + if (tmp) hdlc::release(tmp); + } else { + result = hdlc_decoder_(nrzi_.decode(lfsr_(bit)), locked_); + } + } + } + return result; +} + +/* + * Return twist as a the difference in dB between mark and space. The + * expected values are about 0dB for discriminator output and about 5.5dB + * for de-emphasized audio. + */ +float Fsk9600Demodulator::readTwist() +{ + DEBUG("enter Fsk9600Demodulator::readTwist"); + constexpr uint32_t channel = AUDIO_IN; + + float g120 = 0.0f; + float g4800 = 0.0f; + + GoertzelFilter gf120(120.0, 0); + GoertzelFilter gf4800(4800.0, 0); + + const uint32_t AVG_SAMPLES = 160; + + startADC(416, ADC_BLOCK_SIZE); + + for (uint32_t i = 0; i != AVG_SAMPLES; ++i) + { + uint32_t count = 0; + while (count < ADC_BLOCK_SIZE) + { + osEvent evt = osMessageGet(adcInputQueueHandle, osWaitForever); + if (evt.status != osEventMessage) + continue; + + auto block = (audio::adc_pool_type::chunk_type*) evt.value.p; + uint16_t* data = (uint16_t*) block->buffer; + gf120(data, ADC_BLOCK_SIZE); + gf4800(data, ADC_BLOCK_SIZE); + + audio::adcPool.deallocate(block); + + count += ADC_BLOCK_SIZE; + } + + g120 += (gf120 / count); + g4800 += (gf4800 / count); + + gf120.reset(); + gf4800.reset(); + } + + IDemodulator::stopADC(); + + g120 = 10.0f * log10f(g120 / AVG_SAMPLES); + g4800 = 10.0f * log10f(g4800 / AVG_SAMPLES); + + auto result = g120 - g4800; + + INFO("9600 Twist = %d / 100 (%d - %d)", int(result * 100), int(g120 * 100), + int(g4800 * 100)); + + DEBUG("exit Fsk9600Demodulator::readTwist"); + return result; +} + + +const Fsk9600Demodulator::bpf_bank_type Fsk9600Demodulator::bpf_bank = {{ + // -3dB + {{ + 1, 0, 0, 0, 0, 0, -1, -1, -1, -1, 0, 0, + 0, 0, 1, 2, 2, 3, 2, 2, 0, 0, -2, -3, + -4, -4, -3, -1, 3, 9, 17, 27, 38, 48, 58, 65, + 68, 67, 60, 46, 25, -1, -32, -67, -102, -135, -160, -175, + -174, -153, -110, -41, 54, 178, 327, 500, 692, 897, 1109, 1318, + 1518, 1700, 1857, 1981, 2067, 2110, 2110, 2067, 1981, 1857, 1700, 1518, + 1318, 1109, 897, 692, 500, 327, 178, 54, -41, -110, -153, -174, + -175, -160, -135, -102, -67, -32, -1, 25, 46, 60, 67, 68, + 65, 58, 48, 38, 27, 17, 9, 3, -1, -3, -4, -4, + -3, -2, 0, 0, 2, 2, 3, 2, 2, 1, 0, 0, + 0, 0, -1, -1, -1, -1, 0, 0, 0, 0, 0, 1 + }}, + // -2dB + {{ + 1, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, + 0, 0, 1, 1, 2, 2, 1, 0, 0, -1, -3, -5, + -6, -6, -5, -1, 3, 10, 20, 31, 42, 54, 64, 72, + 75, 72, 63, 46, 21, -11, -49, -92, -135, -175, -208, -229, + -233, -216, -173, -102, 0, 131, 293, 481, 691, 915, 1147, 1378, + 1598, 1799, 1971, 2108, 2203, 2251, 2251, 2203, 2108, 1971, 1799, 1598, + 1378, 1147, 915, 691, 481, 293, 131, 0, -102, -173, -216, -233, + -229, -208, -175, -135, -92, -49, -11, 21, 46, 63, 72, 75, + 72, 64, 54, 42, 31, 20, 10, 3, -1, -5, -6, -6, + -5, -3, -1, 0, 0, 1, 2, 2, 1, 1, 0, 0, + -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 1 + }}, + // -1dB + {{ + 1, 0, 0, 0, 0, -1, -1, -2, -2, -2, -2, -1, + -1, 0, 0, 0, 1, 1, 0, 0, -1, -3, -5, -7, + -8, -8, -6, -2, 3, 11, 22, 34, 48, 60, 72, 80, + 82, 78, 66, 45, 15, -22, -68, -119, -171, -221, -262, -291, + -300, -286, -244, -170, -62, 79, 255, 460, 689, 936, 1191, 1445, + 1688, 1909, 2100, 2251, 2356, 2410, 2410, 2356, 2251, 2100, 1909, 1688, + 1445, 1191, 936, 689, 460, 255, 79, -62, -170, -244, -286, -300, + -291, -262, -221, -171, -119, -68, -22, 15, 45, 66, 78, 82, + 80, 72, 60, 48, 34, 22, 11, 3, -2, -6, -8, -8, + -7, -5, -3, -1, 0, 0, 1, 1, 0, 0, 0, -1, + -1, -2, -2, -2, -2, -1, -1, 0, 0, 0, 0, 1 + }}, + // 0dB + {{ + 1, 0, 0, 0, -1, -1, -2, -2, -3, -3, -3, -2, + -2, -1, 0, 0, 0, 0, 0, -1, -3, -5, -7, -9, + -10, -10, -7, -3, 3, 13, 25, 39, 53, 68, 80, 88, + 91, 85, 70, 45, 9, -35, -90, -150, -212, -272, -323, -359, + -376, -366, -324, -247, -132, 21, 212, 436, 688, 959, 1240, 1520, + 1789, 2034, 2244, 2412, 2528, 2587, 2587, 2528, 2412, 2244, 2034, 1789, + 1520, 1240, 959, 688, 436, 212, 21, -132, -247, -324, -366, -376, + -359, -323, -272, -212, -150, -90, -35, 9, 45, 70, 85, 91, + 88, 80, 68, 53, 39, 25, 13, 3, -3, -7, -10, -10, + -9, -7, -5, -3, -1, 0, 0, 0, 0, 0, -1, -2, + -2, -3, -3, -3, -2, -2, -1, -1, 0, 0, 0, 1 + }}, + // 1dB + {{ + 1, 1, 0, 0, -1, -2, -2, -3, -3, -4, -4, -3, + -3, -2, -1, -1, -1, -1, -1, -3, -5, -7, -9, -11, + -12, -12, -9, -4, 3, 14, 28, 43, 60, 76, 89, 98, + 100, 93, 75, 45, 3, -50, -114, -185, -258, -329, -390, -437, + -460, -454, -413, -333, -209, -43, 164, 409, 686, 984, 1295, 1604, + 1901, 2173, 2406, 2592, 2721, 2786, 2786, 2721, 2592, 2406, 2173, 1901, + 1604, 1295, 984, 686, 409, 164, -43, -209, -333, -413, -454, -460, + -437, -390, -329, -258, -185, -114, -50, 3, 45, 75, 93, 100, + 98, 89, 76, 60, 43, 28, 14, 3, -4, -9, -12, -12, + -11, -9, -7, -5, -3, -1, -1, -1, -1, -1, -2, -3, + -3, -4, -4, -3, -3, -2, -2, -1, 0, 0, 1, 1 + }}, + // 2dB + {{ + 1, 1, 0, 0, -1, -2, -3, -4, -4, -5, -5, -4, + -4, -3, -3, -2, -2, -2, -3, -5, -7, -9, -12, -14, + -15, -14, -11, -5, 3, 15, 31, 49, 67, 85, 100, 109, + 110, 101, 80, 45, -3, -66, -141, -223, -309, -393, -467, -523, + -555, -554, -513, -429, -297, -116, 110, 379, 684, 1013, 1356, 1699, + 2028, 2329, 2588, 2794, 2937, 3010, 3010, 2937, 2794, 2588, 2329, 2028, + 1699, 1356, 1013, 684, 379, 110, -116, -297, -429, -513, -554, -555, + -523, -467, -393, -309, -223, -141, -66, -3, 45, 80, 101, 110, + 109, 100, 85, 67, 49, 31, 15, 3, -5, -11, -14, -15, + -14, -12, -9, -7, -5, -3, -2, -2, -2, -3, -3, -4, + -4, -5, -5, -4, -4, -3, -2, -1, 0, 0, 1, 1 + }}, + // 3dB + {{ + 2, 1, 0, 0, -1, -3, -4, -4, -5, -6, -6, -5, + -5, -4, -4, -3, -3, -4, -5, -7, -9, -12, -15, -17, + -18, -17, -13, -6, 3, 17, 35, 55, 75, 95, 111, 121, + 122, 111, 86, 45, -12, -85, -171, -267, -367, -465, -552, -620, + -661, -665, -626, -537, -395, -199, 49, 346, 681, 1045, 1425, 1805, + 2170, 2504, 2792, 3021, 3179, 3261, 3261, 3179, 3021, 2792, 2504, 2170, + 1805, 1425, 1045, 681, 346, 49, -199, -395, -537, -626, -665, -661, + -620, -552, -465, -367, -267, -171, -85, -12, 45, 86, 111, 122, + 121, 111, 95, 75, 55, 35, 17, 3, -6, -13, -17, -18, + -17, -15, -12, -9, -7, -5, -4, -3, -3, -4, -4, -5, + -5, -6, -6, -5, -4, -4, -3, -1, 0, 0, 1, 2 + }}, + // 4dB + {{ + 2, 1, 0, -1, -2, -3, -4, -5, -6, -7, -7, -7, + -6, -6, -5, -5, -5, -6, -7, -9, -12, -15, -18, -20, + -21, -20, -16, -8, 3, 19, 39, 61, 85, 107, 125, 135, + 135, 122, 92, 44, -21, -105, -205, -316, -432, -546, -648, -729, + -780, -791, -752, -658, -505, -291, -18, 308, 679, 1082, 1502, 1924, + 2330, 2701, 3021, 3275, 3452, 3542, 3542, 3452, 3275, 3021, 2701, 2330, + 1924, 1502, 1082, 679, 308, -18, -291, -505, -658, -752, -791, -780, + -729, -648, -546, -432, -316, -205, -105, -21, 44, 92, 122, 135, + 135, 125, 107, 85, 61, 39, 19, 3, -8, -16, -20, -21, + -20, -18, -15, -12, -9, -7, -6, -5, -5, -5, -6, -6, + -7, -7, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2 + }}, + // 5dB + {{ + 2, 1, 0, -1, -2, -4, -5, -6, -7, -8, -8, -8, + -8, -8, -7, -7, -7, -8, -9, -11, -14, -18, -21, -24, + -25, -23, -19, -9, 3, 21, 44, 69, 95, 119, 139, 151, + 150, 134, 99, 44, -31, -128, -243, -371, -505, -636, -756, -852, + -914, -931, -894, -795, -629, -394, -94, 265, 676, 1122, 1589, 2058, + 2508, 2921, 3277, 3560, 3757, 3858, 3858, 3757, 3560, 3277, 2921, 2508, + 2058, 1589, 1122, 676, 265, -94, -394, -629, -795, -894, -931, -914, + -852, -756, -636, -505, -371, -243, -128, -31, 44, 99, 134, 150, + 151, 139, 119, 95, 69, 44, 21, 3, -9, -19, -23, -25, + -24, -21, -18, -14, -11, -9, -8, -7, -7, -7, -8, -8, + -8, -8, -8, -7, -6, -5, -4, -2, -1, 0, 1, 2 + }}, + // 6dB + {{ + 2, 1, 0, -1, -3, -4, -6, -7, -9, -10, -10, -10, + -10, -9, -9, -9, -9, -10, -12, -14, -18, -21, -25, -28, + -29, -27, -22, -11, 3, 24, 49, 77, 107, 134, 156, 168, + 167, 148, 107, 44, -43, -154, -286, -432, -586, -738, -876, -989, + -1064, -1089, -1053, -947, -767, -510, -180, 218, 672, 1168, 1687, 2208, + 2709, 3169, 3565, 3881, 4100, 4212, 4212, 4100, 3881, 3565, 3169, 2709, + 2208, 1687, 1168, 672, 218, -180, -510, -767, -947, -1053, -1089, -1064, + -989, -876, -738, -586, -432, -286, -154, -43, 44, 107, 148, 167, + 168, 156, 134, 107, 77, 49, 24, 3, -11, -22, -27, -29, + -28, -25, -21, -18, -14, -12, -10, -9, -9, -9, -9, -10, + -10, -10, -10, -9, -7, -6, -4, -3, -1, 0, 1, 2 + }}, + // 7dB + {{ + 3, 1, 0, -1, -3, -5, -7, -9, -10, -11, -12, -12, + -12, -11, -11, -11, -11, -12, -14, -17, -21, -26, -30, -33, + -34, -32, -25, -13, 3, 27, 55, 87, 120, 150, 174, 188, + 185, 163, 116, 43, -56, -183, -334, -501, -678, -852, -1012, -1143, + -1232, -1266, -1231, -1119, -923, -641, -276, 164, 668, 1219, 1796, 2376, + 2934, 3446, 3888, 4240, 4484, 4609, 4609, 4484, 4240, 3888, 3446, 2934, + 2376, 1796, 1219, 668, 164, -276, -641, -923, -1119, -1231, -1266, -1232, + -1143, -1012, -852, -678, -501, -334, -183, -56, 43, 116, 163, 185, + 188, 174, 150, 120, 87, 55, 27, 3, -13, -25, -32, -34, + -33, -30, -26, -21, -17, -14, -12, -11, -11, -11, -11, -12, + -12, -12, -11, -10, -9, -7, -5, -3, -1, 0, 1, 3 + }}, + // 8dB + {{ + 3, 1, 0, -2, -4, -6, -8, -10, -12, -13, -14, -14, + -14, -14, -13, -13, -14, -15, -17, -21, -25, -30, -35, -38, + -39, -36, -29, -16, 3, 30, 62, 97, 134, 168, 195, 209, + 206, 180, 127, 43, -71, -216, -388, -579, -780, -980, -1164, -1316, + -1421, -1464, -1431, -1311, -1097, -787, -383, 104, 664, 1277, 1919, 2565, + 3187, 3758, 4251, 4643, 4916, 5055, 5055, 4916, 4643, 4251, 3758, 3187, + 2565, 1919, 1277, 664, 104, -383, -787, -1097, -1311, -1431, -1464, -1421, + -1316, -1164, -980, -780, -579, -388, -216, -71, 43, 127, 180, 206, + 209, 195, 168, 134, 97, 62, 30, 3, -16, -29, -36, -39, + -38, -35, -30, -25, -21, -17, -15, -14, -13, -13, -14, -14, + -14, -14, -13, -12, -10, -8, -6, -4, -2, 0, 1, 3 + }}, + // 9dB + {{ + 3, 2, 0, -2, -4, -7, -9, -12, -14, -15, -16, -17, + -17, -16, -16, -16, -17, -18, -21, -25, -30, -35, -40, -44, + -45, -42, -33, -18, 3, 33, 69, 109, 151, 189, 218, 234, + 230, 199, 138, 42, -88, -253, -448, -666, -895, -1123, -1334, -1510, + -1633, -1687, -1656, -1527, -1293, -951, -504, 37, 659, 1341, 2056, 2777, + 3470, 4107, 4658, 5095, 5400, 5556, 5556, 5400, 5095, 4658, 4107, 3470, + 2777, 2056, 1341, 659, 37, -504, -951, -1293, -1527, -1656, -1687, -1633, + -1510, -1334, -1123, -895, -666, -448, -253, -88, 42, 138, 199, 230, + 234, 218, 189, 151, 109, 69, 33, 3, -18, -33, -42, -45, + -44, -40, -35, -30, -25, -21, -18, -17, -16, -16, -16, -17, + -17, -16, -15, -14, -12, -9, -7, -4, -2, 0, 2, 3 + }} +}}; + +}} // mobilinkd::tnc + diff --git a/TNC/Fsk9600Demodulator.hpp b/TNC/Fsk9600Demodulator.hpp new file mode 100644 index 0000000..c2b1dc7 --- /dev/null +++ b/TNC/Fsk9600Demodulator.hpp @@ -0,0 +1,107 @@ +// Copyright 2020 Rob Riggs +// All rights reserved. + +#pragma once + +#include "Demodulator.hpp" +#include "AudioLevel.hpp" +#include "AudioInput.hpp" +#include "DigitalPLL.hpp" +#include "NRZI.hpp" +#include "HdlcDecoder.hpp" +#include "KissHardware.hpp" + +namespace mobilinkd { namespace tnc { + +struct Descrambler +{ + uint32_t state{0}; + + bool operator()(bool bit) + { + bool result = (bit ^ (state >> 16) ^ (state >> 11)) & 1; + state = ((state << 1) | (bit & 1)) & 0x1FFFF; + return result; + } +}; + +struct Fsk9600Demodulator : IDemodulator +{ + static constexpr size_t FILTER_TAP_NUM = 132; + static constexpr uint32_t ADC_BLOCK_SIZE = 384; + static constexpr uint32_t SAMPLE_RATE = 192000; + static_assert(audio::ADC_BUFFER_SIZE >= ADC_BLOCK_SIZE); + + using bpf_coeffs_type = std::array; + using bpf_bank_type = std::array; + using audio_filter_t = Q15FirFilter; + + static const bpf_bank_type bpf_bank; + + audio_filter_t demod_filter; + BaseDigitalPLL pll_{192000,9600}; + bool locked_{false}; + Descrambler lfsr_; + libafsk::NRZI nrzi_; + hdlc::NewDecoder hdlc_decoder_; + + virtual ~Fsk9600Demodulator() {} + + void start() override + { + INFO("Setting 80MHz SysClock."); + SysClock80(); + + auto const& bpf_coeffs = bpf_bank[kiss::settings().rx_twist + 3]; + const q15_t* bpf = bpf_coeffs.data(); + demod_filter.init(bpf); + + hadc1.Init.OversamplingMode = DISABLE; + if (HAL_ADC_Init(&hadc1) != HAL_OK) + { + CxxErrorHandler(); + } +// +// if (HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED) != HAL_OK) +// { +// CxxErrorHandler(); +// } + + ADC_ChannelConfTypeDef sConfig; + + sConfig.Channel = AUDIO_IN; + sConfig.Rank = ADC_REGULAR_RANK_1; + sConfig.SingleDiff = ADC_SINGLE_ENDED; + sConfig.SamplingTime = ADC_SAMPLETIME_92CYCLES_5; + sConfig.OffsetNumber = ADC_OFFSET_NONE; + sConfig.Offset = 0; + if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) + CxxErrorHandler(); + + startADC(416, ADC_BLOCK_SIZE); + } + + void stop() override + { + stopADC(); +// INFO("Setting 4MHz SysClock."); +// SysClock48(); + locked_ = false; + } + + float readTwist() override; + + hdlc::IoFrame* operator()(const q15_t* samples) override; + + bool locked() const override + { + return locked_; + } + + size_t size() const override + { + return ADC_BLOCK_SIZE; + } +}; + +}} // mobilinkd::tnc diff --git a/TNC/Fsk9600Modulator.cpp b/TNC/Fsk9600Modulator.cpp new file mode 100644 index 0000000..c461db6 --- /dev/null +++ b/TNC/Fsk9600Modulator.cpp @@ -0,0 +1,16 @@ +// Copyright 2020 Rob Riggs +// All rights reserved. + +#include "Fsk9600Modulator.hpp" + +namespace mobilinkd { namespace tnc { + +#if 0 +const std::array Fsk9600Modulator::cos_table = { + 2047, 2020, 1937, 1801, 1616, 1387, 1120, 822, 502, 169, + -169, -502, -822, -1120, -1387, -1616, -1801, -1937, -2020, -2048 +}; + +#endif + +}} // mobilinkd::tnc diff --git a/TNC/Fsk9600Modulator.hpp b/TNC/Fsk9600Modulator.hpp new file mode 100644 index 0000000..f9e491a --- /dev/null +++ b/TNC/Fsk9600Modulator.hpp @@ -0,0 +1,216 @@ +// Copyright 2020 Rob Riggs +// All rights reserved. + +#pragma once + +#include "Modulator.hpp" + +#include +#include +#include + +namespace mobilinkd { namespace tnc { + +struct Scrambler +{ + uint32_t state{0}; + + bool operator()(bool bit) + { + bool result = (bit ^ (state >> 16) ^ (state >> 11)) & 1; + state = ((state << 1) | result) & 0x1FFFF; + return result; + } +}; + +struct Fsk9600Modulator : Modulator +{ + static constexpr int8_t DAC_BUFFER_LEN = 40; + static constexpr int8_t BIT_LEN = DAC_BUFFER_LEN / 2; + + static constexpr std::array cos_table{ + 2047, 2020, 1937, 1801, 1616, 1387, 1120, 822, 502, 169, + -169, -502, -822, -1120, -1387, -1616, -1801, -1937, -2020, -2048 + }; + + enum class Level { ZERO, HIGH, LOW }; + enum class State { STOPPED, STARTING, RUNNING, STOPPING }; + + osMessageQId dacOutputQueueHandle_{0}; + PTT* ptt_{nullptr}; + uint16_t volume_{4096}; + std::array buffer_; + Level level{Level::HIGH}; + State state{State::STOPPED}; + Scrambler lfsr; + + Fsk9600Modulator(osMessageQId queue, PTT* ptt) + : dacOutputQueueHandle_(queue), ptt_(ptt) + {} + + ~Fsk9600Modulator() override {} + + void init(const kiss::Hardware& hw) override + { + for (auto& x : buffer_) x = 2048; + + state = State::STOPPED; + level = Level::HIGH; + + // Configure 80MHz clock for 192ksps. + htim7.Init.Period = 416; + if (HAL_TIM_Base_Init(&htim7) != HAL_OK) + { + ERROR("htim7 init failed"); + CxxErrorHandler(); + } + } + + void deinit() override + { + state = State::STOPPED; + HAL_DAC_Stop_DMA(&hdac1, DAC_CHANNEL_1); + HAL_TIM_Base_Stop(&htim7); + ptt_->off(); + } + + void set_gain(uint16_t level) override + { + auto v = std::max(256, level); + v = std::min(4096, v); + volume_ = v; + } + + void set_ptt(PTT* ptt) override + { + if (state != State::STOPPED) + { + ERROR("PTT change while not stopped"); + CxxErrorHandler(); + } + ptt_ = ptt; + ptt_->off(); + } + + void send(bool bit) override + { + auto scrambled = lfsr(bit); + + switch (state) + { + case State::STOPPING: + case State::STOPPED: + fill_first(scrambled); + state = State::STARTING; + break; + case State::STARTING: + fill_last(scrambled); + state = State::RUNNING; + ptt_->on(); + HAL_TIM_Base_Start(&htim7); + HAL_DAC_Start_DMA( + &hdac1, DAC_CHANNEL_1, + reinterpret_cast(buffer_.data()), buffer_.size(), + DAC_ALIGN_12B_R); + break; + case State::RUNNING: + osMessagePut(dacOutputQueueHandle_, scrambled, osWaitForever); + break; + } + } + + // DAC DMA interrupt functions. + + void fill_first(bool bit) override + { + fill(buffer_.data(), bit); + } + + void fill_last(bool bit) override + { + fill(buffer_.data() + BIT_LEN, bit); + } + + void empty() override + { + switch (state) + { + case State::STARTING: + // fall-through + case State::RUNNING: + state = State::STOPPING; + break; + case State::STOPPING: + state = State::STOPPED; + HAL_DAC_Stop_DMA(&hdac1, DAC_CHANNEL_1); + HAL_TIM_Base_Stop(&htim7); + ptt_->off(); + break; + case State::STOPPED: + break; + } + state = State::STOPPING; + level = Level::HIGH; + } + + void abort() override + { + state = State::STOPPED; + HAL_DAC_Stop_DMA(&hdac1, DAC_CHANNEL_1); + HAL_TIM_Base_Stop(&htim7); + ptt_->off(); + + // Drain the queue. + while (osMessageGet(dacOutputQueueHandle_, 0).status == osEventMessage); + } + + float bits_per_ms() const override + { + return 9.6f; + } + +private: + + uint16_t adjust_level(int32_t sample) const + { + sample *= volume_; + sample >>= 12; + sample += 2048; + return sample; + } + + void fill(uint16_t* buffer, bool bit) + { + switch (level) + { + case Level::HIGH: + if (bit) + { + std::fill(buffer, buffer + BIT_LEN, adjust_level(2047)); + } + else + { + std::transform(cos_table.begin(), cos_table.end(), buffer, + [this](auto x){return adjust_level(x);}); + level = Level::LOW; + } + break; + case Level::LOW: + if (bit) + { + std::transform(cos_table.rbegin(), cos_table.rend(), buffer, + [this](auto x){return adjust_level(x);}); + level = Level::HIGH; + } + else + { + std::fill(buffer, buffer + BIT_LEN, adjust_level(-2048)); + } + break; + default: + CxxErrorHandler(); + } + } +}; + +}} // mobilinkd::tnc diff --git a/TNC/Goertzel.h b/TNC/Goertzel.h index 47e8b90..3ccefac 100644 --- a/TNC/Goertzel.h +++ b/TNC/Goertzel.h @@ -1,94 +1,102 @@ // Copyright 2017 Rob Riggs // All rights reserved. -#ifndef MOBILINKD__TNC__GOERTZEL_FILTER_HPP_ -#define MOBILINKD__TNC__GOERTZEL_FILTER_HPP_ +#pragma once #include "AudioLevel.hpp" #include #include -namespace mobilinkd { namespace tnc { +namespace mobilinkd { +namespace tnc { extern const float WINDOW[]; -template +template class GoertzelFilter { - float filterFreq_; - int bin_; - float coeff_; - float d1{0.0f}; - float d2{0.0f}; - uint32_t count{0}; - const float* window_; - + float filterFreq_; + int bin_; + float coeff_; + float d1 { 0.0f }; + float d2 { 0.0f }; + uint32_t count { 0 }; + const float* window_; + public: - GoertzelFilter(float filter_freq, const float* window = WINDOW) - : filterFreq_(filter_freq) - , bin_(0.5f + ((filter_freq * SAMPLES) / SAMPLE_RATE)) - , coeff_(2.0f * cos((2.0f * M_PI * bin_) / float(SAMPLES))) - , window_(window) - {} - - void operator()(float* samples, uint32_t n) { - - for (size_t i = 0; i != n; ++i) { - float w = window_ ? window_[count] : 1.0; - float y = w * samples[i] + coeff_ * d1 - d2; - d2 = d1; - d1 = y; - ++count; - } - } - - void operator()(uint16_t* samples, uint32_t n) { - - for (uint32_t i = 0; i != n; ++i) { - float w = window_ ? window_[count] : 1.0; - float sample = (float(samples[i]) - audio::virtual_ground) * audio::i_vgnd; - float y = w * sample + coeff_ * d1 - d2; - d2 = d1; - d1 = y; - ++count; + GoertzelFilter(float filter_freq, const float* window = WINDOW) + : filterFreq_(filter_freq), bin_( + 0.5f + ((filter_freq * SAMPLES) / SAMPLE_RATE)), coeff_( + 2.0f * cos((2.0f * M_PI * bin_) / float(SAMPLES))), window_(window) + { } - } - operator float() const { - return d2 * d2 + d1 * d1 - coeff_ * d1 * d2; - } + void operator()(float* samples, uint32_t n) + { - void reset() { - d1 = 0.0f; - d2 = 0.0f; - count = 0; - } + for (size_t i = 0; i != n; ++i) + { + float w = window_ ? window_[count] : 1.0; + float y = w * samples[i] + coeff_ * d1 - d2; + d2 = d1; + d1 = y; + ++count; + } + } + + void operator()(uint16_t* samples, uint32_t n) + { + + for (uint32_t i = 0; i != n; ++i) + { + float w = window_ ? window_[count] : 1.0; + float sample = (float(samples[i]) - audio::virtual_ground) + * audio::i_vgnd; + float y = w * sample + coeff_ * d1 - d2; + d2 = d1; + d1 = y; + ++count; + } + } + + operator float() const + { + return d2 * d2 + d1 * d1 - coeff_ * d1 * d2; + } + + void reset() + { + d1 = 0.0f; + d2 = 0.0f; + count = 0; + } }; #if 0 template class GoertzelFactory { - float window_[SAMPLES]; - static void make_window(float* window) - { - for (size_t i = 0; i != SAMPLES; ++i) { - window[i] = 0.54f - 0.46f * cos(2.0f * M_PI * (float(i) / float(SAMPLE_RATE))); + float window_[SAMPLES]; + static void make_window(float* window) + { + for (size_t i = 0; i != SAMPLES; ++i) + { + window[i] = 0.54f - 0.46f * cos(2.0f * M_PI * (float(i) / float(SAMPLE_RATE))); + } } - } public: - GoertzelFactory() - : window_() - { - make_window(window_); - } + GoertzelFactory() + : window_() + { + make_window(window_); + } - GoertzelFilter make(float freq) - { - return GoertzelFilter(freq, window_); - } + GoertzelFilter make(float freq) + { + return GoertzelFilter(freq, window_); + } }; #endif @@ -104,20 +112,22 @@ struct Goertzel float_type sin_; float_type cos_; float_type coeff_; - float_type q0{0.0}, q1{0.0}, q2{0.0}; + float_type q0 { 0.0 }, q1 { 0.0 }, q2 { 0.0 }; explicit Goertzel(float_type omega) - : sin_(sin(omega)), cos_(cos(omega)), coeff_(2.0 * cos_) - {} + : sin_(sin(omega)), cos_(cos(omega)), coeff_(2.0 * cos_) + { + } static type from_frequency(float_type frequency, float_type sample_rate) { return Goertzel(2.0 * PI * frequency / sample_rate); } - template + template complex_type operator()(const Container& data) { - for (auto& v : data) { + for (auto& v : data) + { q0 = coeff_ * q1 - q2 + v; q2 = q1; q1 = q0; @@ -134,6 +144,6 @@ struct Goertzel typedef Goertzel FloatGoertzel; -}} // mobilinkd::tnc +} +} // mobilinkd::tnc -#endif // MOBILINKD__TNC__GOERTZEL_FILTER_HPP_ diff --git a/TNC/HDLCEncoder.hpp b/TNC/HDLCEncoder.hpp index ea8ccf8..6cf190e 100644 --- a/TNC/HDLCEncoder.hpp +++ b/TNC/HDLCEncoder.hpp @@ -4,7 +4,7 @@ #ifndef INC_HDLCENCODER_HPP_ #define INC_HDLCENCODER_HPP_ -#include "AFSKModulator.hpp" +#include "Modulator.hpp" #include "HdlcFrame.hpp" #include "NRZI.hpp" #include "PTT.hpp" @@ -47,11 +47,11 @@ struct Encoder { NRZI nrzi_; uint16_t crc_; osMessageQId input_; - AFSKModulator* modulator_; + Modulator* modulator_; volatile bool running_; bool send_delay_; // Avoid sending the preamble for back-to-back frames. - Encoder(osMessageQId input, AFSKModulator* output) + Encoder(osMessageQId input, Modulator* output) : tx_delay_(kiss::settings().txdelay), tx_tail_(kiss::settings().txtail) , p_persist_(kiss::settings().ppersist), slot_time_(kiss::settings().slot) , duplex_(kiss::settings().duplex), state_(state_type::STATE_IDLE) @@ -192,7 +192,7 @@ struct Encoder { } void send_delay() { - const size_t tmp = (tx_delay_ * 3) / 2; + const size_t tmp = tx_delay_ * modulator_->bits_per_ms(); for (size_t i = 0; i != tmp; i++) { send_raw(IDLE); } diff --git a/TNC/IOEventTask.cpp b/TNC/IOEventTask.cpp index 148fafc..ad2792e 100644 --- a/TNC/IOEventTask.cpp +++ b/TNC/IOEventTask.cpp @@ -60,6 +60,7 @@ void startIOEventTask(void const*) hardware.init(); hardware.store(); } +// hardware.init(); osMutexRelease(hardwareInitMutexHandle); @@ -106,6 +107,7 @@ void startIOEventTask(void const*) DEBUG("USB connected -- negotiate"); HAL_GPIO_WritePin(BT_SLEEP_GPIO_Port, BT_SLEEP_Pin, GPIO_PIN_RESET); +// SysClock48(); osTimerStart(usbShutdownTimerHandle, 5000); } } @@ -151,6 +153,7 @@ void startIOEventTask(void const*) hpcd_USB_FS.Instance->BCDR = 0; HAL_PCD_MspDeInit(&hpcd_USB_FS); HAL_GPIO_WritePin(USB_CE_GPIO_Port, USB_CE_Pin, GPIO_PIN_SET); +// SysClock4(); if (ioport != getUsbPort()) { break; @@ -272,6 +275,7 @@ void startIOEventTask(void const*) break; case CMD_USB_CONNECTED: INFO("VBUS Detected"); +// SysClock48(); MX_USB_DEVICE_Init(); HAL_PCD_MspInit(&hpcd_USB_FS); hpcd_USB_FS.Instance->BCDR = 0; diff --git a/TNC/KissHardware.cpp b/TNC/KissHardware.cpp index 8297997..13e29e8 100644 --- a/TNC/KissHardware.cpp +++ b/TNC/KissHardware.cpp @@ -9,6 +9,7 @@ #include #include +#include #include extern I2C_HandleTypeDef hi2c1; @@ -26,9 +27,17 @@ int powerOffViaUSB(void) namespace mobilinkd { namespace tnc { namespace kiss { -const char FIRMWARE_VERSION[] = "1.1.6"; +const char FIRMWARE_VERSION[] = "2.0.0a1"; const char HARDWARE_VERSION[] = "Mobilinkd TNC3 2.1.1"; + +const std::array modem_type_lookup = { + "NOT SET", + "AFSK1200", + "AFSK300", + "FSK9600", +}; + Hardware& settings() { static Hardware instance __attribute__((section(".bss3"))); @@ -119,7 +128,7 @@ inline void reply(uint8_t cmd, const uint8_t* data, uint16_t len) { } inline void reply_ext(uint8_t ext, uint8_t cmd, const uint8_t* data, uint16_t len) { - auto buffer = (uint8_t*) malloc(len + 2); + auto buffer = static_cast(alloca(len + 2)); if (buffer == nullptr) return; buffer[0] = ext; buffer[1] = cmd; @@ -337,7 +346,7 @@ void Hardware::handle_request(hdlc::IoFrame* frame) { if (tx_twist < 0) tx_twist = 0; if (tx_twist > 100) tx_twist = 100; DEBUG("SET_OUTPUT_TWIST: %d", int(tx_twist)); - getModulator().set_twist(uint8_t(tx_twist)); + getModulator().init(*this); update_crc(); [[fallthrough]]; case hardware::GET_OUTPUT_TWIST: @@ -376,6 +385,26 @@ void Hardware::handle_request(hdlc::IoFrame* frame) { reply8(hardware::GET_DUPLEX, duplex); break; + case hardware::SET_MODEM_TYPE: + DEBUG("SET_MODEM_TYPE"); + if ((*it > 0) and (*it < 4)) + { + modem_type = *it; + DEBUG(modem_type_lookup[*it]); + update_crc(); + } + else + { + ERROR("Unknown modem type"); + } + osMessagePut(audioInputQueueHandle, audio::UPDATE_SETTINGS, + osWaitForever); + [[fallthrough]]; + case hardware::GET_MODEM_TYPE: + DEBUG("GET_MODEM_TYPE"); + reply8(hardware::GET_MODEM_TYPE, modem_type); + break; + case hardware::GET_FIRMWARE_VERSION: DEBUG("GET_FIRMWARE_VERSION"); reply(hardware::GET_FIRMWARE_VERSION, (uint8_t*) FIRMWARE_VERSION, diff --git a/TNC/KissHardware.hpp b/TNC/KissHardware.hpp index 527aff9..7881a15 100644 --- a/TNC/KissHardware.hpp +++ b/TNC/KissHardware.hpp @@ -94,6 +94,9 @@ constexpr const uint8_t GET_TIMESLOT = 35; constexpr const uint8_t GET_TXTAIL = 36; constexpr const uint8_t GET_DUPLEX = 37; +constexpr const uint8_t SET_MODEM_TYPE = 38; +constexpr const uint8_t GET_MODEM_TYPE = 39; + constexpr const uint8_t GET_FIRMWARE_VERSION = 40; constexpr const uint8_t GET_HARDWARE_VERSION = 41; constexpr const uint8_t SAVE_EEPROM_SETTINGS = 42; @@ -269,7 +272,7 @@ struct Hardware slot = 10; txtail = 1; duplex = 0; - modem_type = ModemType::AFSK1200; + modem_type = ModemType::FSK9600; output_gain = 63; input_gain = 0; // 0-4 on TNC3 tx_twist = 50; diff --git a/TNC/Modulator.hpp b/TNC/Modulator.hpp new file mode 100644 index 0000000..9d3982c --- /dev/null +++ b/TNC/Modulator.hpp @@ -0,0 +1,93 @@ +// Copyright 2020 Rob Riggs +// All rights reserved. + +#pragma once + +#include "PTT.hpp" +#include "KissHardware.hpp" + +#include "cmsis_os.h" + +#include + +extern osMessageQId hdlcOutputQueueHandle; +extern osMessageQId dacOutputQueueHandle; +extern TIM_HandleTypeDef htim7; +extern DAC_HandleTypeDef hdac1; + +namespace mobilinkd { namespace tnc { + +/** + * The modulator has three distinct interfaces. The configuration interface + * which is used to initialize the modulator, the bit sending interface used + * by the HDLC encoder to transmit the bits, and the modulation interface + * used by the DAC DMA engine to get the analog values output by the modem. + */ +struct Modulator +{ + virtual ~Modulator() {} + + /** + * Implement all functionality required to configure the hardware and + * the modulator. For example, configuring the timer used by the DAC + * should be done here. + */ + virtual void init(const kiss::Hardware& hw) = 0; + + /** + * Implement all functionality required to deactivate the hardware and + * the modulator. For example, disabling the timer used by the DAC + * should be done here. + */ + virtual void deinit() = 0; + + virtual void set_gain(uint16_t level) = 0; + + /** + * Set the PTT controller used by the modulator. It is only legal + * to call this when the modulator is stopped. + * + * @param ptt + */ + virtual void set_ptt(PTT* ptt) = 0; + + /** + * Send a single bit. + * + * @param bit + */ + virtual void send(bool bit) = 0; + + /// The next three functions are called by the DAC DMA interrupt handler. + + /** + * Fill the first half of the DAC DMA buffer. + * + * @warning This function is called in an interrupt context. + * + * @param bit + */ + virtual void fill_first(bool bit) = 0; + + /** + * Fill the second half of the DAC DMA buffer. + * + * @warning This function is called in an interrupt context. + * + * @param bit + */ + virtual void fill_last(bool bit) = 0; + + /** + * The DAC bit buffer is empty. There are no more bits to process. + * + * @warning This function is called in an interrupt context. + */ + virtual void empty() = 0; + + virtual void abort() = 0; + + virtual float bits_per_ms() const = 0; +}; + +}} // mobilinkd::tnc diff --git a/TNC/ModulatorTask.cpp b/TNC/ModulatorTask.cpp index 286a52f..73d7136 100644 --- a/TNC/ModulatorTask.cpp +++ b/TNC/ModulatorTask.cpp @@ -2,13 +2,15 @@ // All rights reserved. #include "ModulatorTask.hpp" +#include "Fsk9600Modulator.hpp" +#include "AFSKModulator.hpp" #include "KissHardware.hpp" #include "main.h" mobilinkd::tnc::SimplexPTT simplexPtt; mobilinkd::tnc::MultiplexPTT multiplexPtt; -mobilinkd::tnc::AFSKModulator* modulator; +mobilinkd::tnc::Modulator* modulator; mobilinkd::tnc::hdlc::Encoder* encoder; // DMA Conversion half complete. @@ -34,9 +36,22 @@ extern "C" void HAL_DAC_DMAUnderrunCallbackCh1(DAC_HandleTypeDef*) { modulator->abort(); } -mobilinkd::tnc::AFSKModulator& getModulator() { - static mobilinkd::tnc::AFSKModulator instance(dacOutputQueueHandle, &simplexPtt); - return instance; +mobilinkd::tnc::Modulator& getModulator() +{ + using namespace mobilinkd::tnc; + + static AFSKModulator afsk1200modulator(dacOutputQueueHandle, &simplexPtt); + static Fsk9600Modulator fsk9600modulator(dacOutputQueueHandle, &simplexPtt); + + switch (kiss::settings().modem_type) + { + case kiss::Hardware::ModemType::FSK9600: + return fsk9600modulator; + case kiss::Hardware::ModemType::AFSK1200: + return afsk1200modulator; + default: + _Error_Handler(__FILE__, __LINE__); + } } mobilinkd::tnc::hdlc::Encoder& getEncoder() { @@ -61,9 +76,9 @@ void updatePtt() using namespace mobilinkd::tnc::kiss; if (settings().options & KISS_OPTION_PTT_SIMPLEX) - modulator->set_ptt(&simplexPtt); + getModulator().set_ptt(&simplexPtt); else - modulator->set_ptt(&multiplexPtt); + getModulator().set_ptt(&multiplexPtt); } void startModulatorTask(void const*) { @@ -78,7 +93,7 @@ void startModulatorTask(void const*) { updatePtt(); - modulator->set_twist(settings().tx_twist); + getModulator().init(settings()); encoder->tx_delay(settings().txdelay); encoder->p_persist(settings().ppersist); diff --git a/TNC/ModulatorTask.hpp b/TNC/ModulatorTask.hpp index 9f9ccfe..b52c98c 100644 --- a/TNC/ModulatorTask.hpp +++ b/TNC/ModulatorTask.hpp @@ -6,7 +6,7 @@ #define MOBILINKD__MODULATOR_TASK_HPP_ #include "HDLCEncoder.hpp" -#include "AFSKModulator.hpp" +#include "Modulator.hpp" #include "PTT.hpp" #include "cmsis_os.h" @@ -17,11 +17,7 @@ extern "C" { extern mobilinkd::tnc::SimplexPTT simplexPtt; extern mobilinkd::tnc::MultiplexPTT multiplexPtt; -extern mobilinkd::tnc::AFSKModulator* modulator; -extern mobilinkd::tnc::hdlc::Encoder* encoder; - -mobilinkd::tnc::AFSKModulator& getModulator(); -mobilinkd::tnc::hdlc::Encoder& getEncoder(); +mobilinkd::tnc::Modulator& getModulator(); void startModulatorTask(void const * argument); diff --git a/stlink-tnc3.cfg b/stlink-tnc3.cfg index 2c95e4f..37aaca1 100644 --- a/stlink-tnc3.cfg +++ b/stlink-tnc3.cfg @@ -9,5 +9,5 @@ source [find target/stm32l4x.cfg] reset_config srst_only itm port 0 on -tpiu config internal swv uart off 48000000 +tpiu config internal swv uart off 80000000