Basic 9600 baud FSK support.

kf7r_9600_experimental
Rob Riggs 2020-02-07 19:51:44 -06:00
rodzic cd2b1f2dde
commit ef966e3306
27 zmienionych plików z 1404 dodań i 353 usunięć

Wyświetl plik

@ -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_

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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 {

Wyświetl plik

@ -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));

Wyświetl plik

@ -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();

Wyświetl plik

@ -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

Wyświetl plik

@ -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;

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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_;

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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};

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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_

Wyświetl plik

@ -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);
}

Wyświetl plik

@ -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;

Wyświetl plik

@ -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,

Wyświetl plik

@ -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;

93
TNC/Modulator.hpp 100644
Wyświetl plik

@ -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

Wyświetl plik

@ -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);

Wyświetl plik

@ -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);

Wyświetl plik

@ -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