kopia lustrzana https://github.com/mobilinkd/tnc3-firmware
Basic 9600 baud FSK support.
rodzic
cd2b1f2dde
commit
ef966e3306
|
@ -1,13 +1,13 @@
|
|||
// Copyright 2015-2019 Mobilinkd LLC <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#ifndef MOBILINKD__TNC__AFSK_MODULATOR_HPP_
|
||||
#define MOBILINKD__TNC__AFSK_MODULATOR_HPP_
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#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<uint16_t>(256, v);
|
||||
v = std::min<uint16_t>(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_
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
// Copyright 2020 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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<ADC_BLOCK_SIZE, SAMPLE_RATE> gf1200(1200.0, 0);
|
||||
GoertzelFilter<ADC_BLOCK_SIZE, SAMPLE_RATE> 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
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
// Copyright 2020 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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<ADC_BLOCK_SIZE, FILTER_TAP_NUM>;
|
||||
|
||||
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<q15_t* >(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
|
|
@ -31,7 +31,8 @@ const q15_t lpf_coeffs[] = {
|
|||
14, 18, 20, 19, 17, 14, 11, 8, 5, 3, 1, 0,
|
||||
};
|
||||
|
||||
typedef FirFilter<audio::ADC_BUFFER_SIZE, 9> emphasis_filter_type;
|
||||
static constexpr uint32_t ADC_BUFFER_SIZE = 88;
|
||||
typedef FirFilter<ADC_BUFFER_SIZE, 9> emphasis_filter_type;
|
||||
|
||||
struct Demodulator {
|
||||
|
||||
|
|
|
@ -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<ADC_BUFFER_SIZE, FILTER_TAP_NUM> 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<uint16_t>::max();
|
||||
uint16_t vmax = std::numeric_limits<uint16_t>::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<uint16_t>::max();
|
||||
uint16_t vmax = std::numeric_limits<uint16_t>::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<TWIST_SAMPLE_SIZE, SAMPLE_RATE> gf1200(1200.0, 0);
|
||||
GoertzelFilter<TWIST_SAMPLE_SIZE, SAMPLE_RATE> 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));
|
||||
|
|
|
@ -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<uint16_t, uint16_t, uint16_t, uint16_t> levels_type;
|
||||
levels_type readLevels(uint32_t channel, uint32_t samples = 2640);
|
||||
levels_type readLevels(uint32_t channel);
|
||||
float readTwist();
|
||||
|
||||
void demodulatorTask();
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
#include "stm32l4xx_hal.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <tuple>
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <cstdint>
|
||||
|
||||
|
@ -199,7 +201,7 @@ void setAudioOutputLevel()
|
|||
} else {
|
||||
gpio::AUDIO_OUT_ATTEN::off();
|
||||
}
|
||||
getModulator().set_volume(r);
|
||||
getModulator().set_gain(r);
|
||||
}
|
||||
|
||||
}}} // mobilinkd::tnc::audio
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright 2020 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright 2020 Rob Riggs <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "HdlcFrame.hpp"
|
||||
#include "FirFilter.hpp"
|
||||
#include "AudioInput.hpp"
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <arm_math.h>
|
||||
|
||||
namespace mobilinkd { namespace tnc {
|
||||
|
||||
constexpr size_t FILTER_TAP_NUM = 132;
|
||||
|
||||
using demod_filter_t = std::function<q15_t*(q15_t*, size_t)>;
|
||||
using demodulator_t = std::function<hdlc::IoFrame*(q15_t*)>;
|
||||
|
||||
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
|
|
@ -29,10 +29,10 @@ struct PLLResult {
|
|||
// scipy.signal:
|
||||
// b, a = bessel(4, [80.0/(1200/2)], 'lowpass')
|
||||
//
|
||||
const std::array<float, 5> lock_b = {
|
||||
constexpr std::array<float, 5> lock_b = {
|
||||
1.077063e-03,4.308253e-03,6.462379e-03,4.308253e-03,1.077063e-03,
|
||||
};
|
||||
const std::array<float, 5> lock_a = {
|
||||
constexpr std::array<float, 5> lock_a = {
|
||||
1.000000e+00,-2.774567e+00,2.962960e+00,-1.437990e+00,2.668296e-01,
|
||||
};
|
||||
|
||||
|
@ -41,9 +41,10 @@ const std::array<float, 5> lock_a = {
|
|||
// loop_coeffs = firwin(9, [64.0/(1200/2)], width = None,
|
||||
// pass_zero = True, scale = True, window='hann')
|
||||
//
|
||||
const std::array<float, 7> loop_coeffs = {
|
||||
constexpr std::array<float, 7> 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<float_type> 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_;
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright 2020 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -96,7 +96,6 @@ template <size_t BLOCK_SIZE, size_t FILTER_SIZE>
|
|||
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};
|
||||
|
|
|
@ -0,0 +1,287 @@
|
|||
// Copyright 2020 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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<q15_t* >(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<ADC_BLOCK_SIZE, SAMPLE_RATE> gf120(120.0, 0);
|
||||
GoertzelFilter<ADC_BLOCK_SIZE, SAMPLE_RATE> 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
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
// Copyright 2020 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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<int16_t, FILTER_TAP_NUM>;
|
||||
using bpf_bank_type = std::array<bpf_coeffs_type, 13>;
|
||||
using audio_filter_t = Q15FirFilter<ADC_BLOCK_SIZE, FILTER_TAP_NUM>;
|
||||
|
||||
static const bpf_bank_type bpf_bank;
|
||||
|
||||
audio_filter_t demod_filter;
|
||||
BaseDigitalPLL<float> 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
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2020 Rob Riggs <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#include "Fsk9600Modulator.hpp"
|
||||
|
||||
namespace mobilinkd { namespace tnc {
|
||||
|
||||
#if 0
|
||||
const std::array<int16_t, BIT_LEN> 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
|
|
@ -0,0 +1,216 @@
|
|||
// Copyright 2020 Rob Riggs <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Modulator.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
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<int16_t, BIT_LEN> 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<uint16_t, DAC_BUFFER_LEN> 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<uint16_t>(256, level);
|
||||
v = std::min<uint16_t>(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<uint32_t*>(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
|
150
TNC/Goertzel.h
150
TNC/Goertzel.h
|
@ -1,94 +1,102 @@
|
|||
// Copyright 2017 Rob Riggs <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#ifndef MOBILINKD__TNC__GOERTZEL_FILTER_HPP_
|
||||
#define MOBILINKD__TNC__GOERTZEL_FILTER_HPP_
|
||||
#pragma once
|
||||
|
||||
#include "AudioLevel.hpp"
|
||||
#include <arm_math.h>
|
||||
#include <complex>
|
||||
|
||||
namespace mobilinkd { namespace tnc {
|
||||
namespace mobilinkd {
|
||||
namespace tnc {
|
||||
|
||||
extern const float WINDOW[];
|
||||
|
||||
template <uint32_t SAMPLES, uint32_t SAMPLE_RATE>
|
||||
template<uint32_t SAMPLES, uint32_t SAMPLE_RATE>
|
||||
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 <uint32_t SAMPLES, uint32_t SAMPLE_RATE>
|
||||
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<SAMPLES, SAMPLE_RATE> make(float freq)
|
||||
{
|
||||
return GoertzelFilter<SAMPLES, SAMPLE_RATE>(freq, window_);
|
||||
}
|
||||
GoertzelFilter<SAMPLES, SAMPLE_RATE> make(float freq)
|
||||
{
|
||||
return GoertzelFilter<SAMPLES, SAMPLE_RATE>(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 <typename Container>
|
||||
template<typename Container>
|
||||
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<float> FloatGoertzel;
|
||||
|
||||
}} // mobilinkd::tnc
|
||||
}
|
||||
} // mobilinkd::tnc
|
||||
|
||||
#endif // MOBILINKD__TNC__GOERTZEL_FILTER_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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <ModulatorTask.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
|
||||
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<const char*, 4> 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<uint8_t*>(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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
// Copyright 2020 Rob Riggs <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "PTT.hpp"
|
||||
#include "KissHardware.hpp"
|
||||
|
||||
#include "cmsis_os.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
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
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue