NucleoTNC/TNC/AudioInput.cpp

522 wiersze
14 KiB
C++
Czysty Zwykły widok Historia

// Copyright 2018-2021 Rob Riggs <rob@mobilinkd.com>
2018-07-30 02:34:53 +00:00
// All rights reserved.
#include "AudioInput.hpp"
#include "Afsk1200Demodulator.hpp"
#include "Fsk9600Demodulator.hpp"
#include "M17Demodulator.h"
2018-07-30 02:34:53 +00:00
#include "AudioLevel.hpp"
#include "Log.h"
#include "KissHardware.hpp"
#include "GPIO.hpp"
#include "HdlcFrame.hpp"
#include "PortInterface.hpp"
#include "Goertzel.h"
#include "DCD.h"
#include "ModulatorTask.hpp"
2018-07-30 02:34:53 +00:00
#include "arm_math.h"
#include "stm32l4xx_hal.h"
#include <algorithm>
#include <atomic>
2021-06-26 18:20:36 +00:00
#include <cstdint>
#include <cstring>
#include <numeric>
#include <type_traits>
2018-07-30 02:34:53 +00:00
extern osMessageQId ioEventQueueHandle;
extern IWDG_HandleTypeDef hiwdg;
2018-07-30 02:34:53 +00:00
extern "C" void SystemClock_Config(void);
// 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, dma_transfer_size);
2018-07-30 02:34:53 +00:00
auto status = osMessagePut(adcInputQueueHandle, (uint32_t) block, 0);
if (status != osOK) adcPool.deallocate(block);
}
// DMA Conversion second half complete.
extern "C" void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef*) {
using namespace mobilinkd::tnc::audio;
auto block = adcPool.allocate();
if (!block) return;
memmove(block->buffer, adc_buffer + half_buffer_size, dma_transfer_size);
2018-07-30 02:34:53 +00:00
auto status = osMessagePut(adcInputQueueHandle, (uint32_t) block, 0);
if (status != osOK) adcPool.deallocate(block);
}
extern "C" void HAL_ADC_ErrorCallback(ADC_HandleTypeDef* /* hadc */) {
using namespace mobilinkd::tnc::audio;
// __HAL_ADC_CLEAR_FLAG(hadc, (ADC_FLAG_EOC | ADC_FLAG_EOS | ADC_FLAG_OVR));
// HAL_DMA_Start(hadc->DMA_Handle, (uint32_t)&hadc->Instance->DR, (uint32_t)adc_buffer, ADC_BUFFER_SIZE * 2);
}
extern "C" void startAudioInputTask(void const*) {
using namespace mobilinkd::tnc::audio;
TNC_DEBUG("startAudioInputTask");
2018-07-30 02:34:53 +00:00
adcPool.init();
uint8_t adcState = mobilinkd::tnc::audio::IDLE;
while (true) {
osEvent event = osMessageGet(audioInputQueueHandle, osWaitForever);
if (event.status != osEventMessage) continue;
adcState = event.value.v;
switch (adcState) {
case STOPPED:
TNC_DEBUG("STOPPED");
2018-07-30 02:34:53 +00:00
// stop();
break;
case DEMODULATOR:
INFO("DEMODULATOR");
2018-07-30 02:34:53 +00:00
demodulatorTask();
break;
case STREAM_AMPLIFIED_INPUT_LEVEL:
TNC_DEBUG("STREAM_AMPLIFIED_INPUT_LEVEL");
2018-07-30 02:34:53 +00:00
streamAmplifiedInputLevels();
break;
case POLL_AMPLIFIED_INPUT_LEVEL:
TNC_DEBUG("POLL_AMPLIFIED_INPUT_LEVEL");
2018-07-30 02:34:53 +00:00
pollAmplifiedInputLevel();
break;
#ifndef NUCLEOTNC
case POLL_BATTERY_LEVEL:
TNC_DEBUG("POLL_BATTERY_LEVEL");
pollBatteryLevel();
break;
#endif
2018-07-30 02:34:53 +00:00
case POLL_TWIST_LEVEL:
TNC_DEBUG("POLL_TWIST_LEVEL");
2018-07-30 02:34:53 +00:00
pollInputTwist();
break;
case STREAM_AVERAGE_TWIST_LEVEL:
TNC_DEBUG("STREAM_AVERAGE_TWIST_LEVEL");
// streamAverageInputTwist();
2018-07-30 02:34:53 +00:00
break;
case STREAM_INSTANT_TWIST_LEVEL:
TNC_DEBUG("STREAM_INSTANT_TWIST_LEVEL");
// streamInstantInputTwist();
2018-07-30 02:34:53 +00:00
break;
case AUTO_ADJUST_INPUT_LEVEL:
TNC_DEBUG("AUTO_ADJUST_INPUT_LEVEL");
2018-07-30 02:34:53 +00:00
autoAudioInputLevel();
break;
case CONFIGURE_INPUT_LEVELS:
TNC_DEBUG("CONFIGURE_INPUT_LEVELS");
2018-07-30 02:34:53 +00:00
setAudioInputLevels();
break;
case UPDATE_SETTINGS:
TNC_DEBUG("UPDATE_SETTINGS");
2018-07-30 02:34:53 +00:00
setAudioInputLevels();
updateModulator();
2018-07-30 02:34:53 +00:00
break;
case IDLE:
INFO("IDLE");
2018-07-30 02:34:53 +00:00
break;
default:
break;
}
}
}
namespace mobilinkd { namespace tnc { namespace audio {
uint32_t adc_buffer[ADC_BUFFER_SIZE]; // Two samples per element.
volatile uint32_t adc_block_size = ADC_BUFFER_SIZE; // Based on demodulator.
volatile uint32_t dma_transfer_size = adc_block_size * 2; // Transfer size in bytes.
volatile uint32_t half_buffer_size = adc_block_size / 2; // Transfer size in words / 2.
adc_pool_type adcPool;
2018-07-30 02:34:53 +00:00
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;
2018-07-30 02:34:53 +00:00
}
q15_t normalized[ADC_BUFFER_SIZE];
2018-07-30 02:34:53 +00:00
IDemodulator* getDemodulator()
{
2021-06-26 18:13:53 +00:00
constexpr auto mem_size = std::max({
sizeof(Afsk1200Demodulator),
sizeof(Fsk9600Demodulator),
sizeof(M17Demodulator),
});
2018-07-30 02:34:53 +00:00
2021-06-26 18:13:53 +00:00
using storage_t = std::aligned_storage<mem_size, 4>::type;
static storage_t mem;
static IDemodulator* demod = nullptr;
static uint8_t modem_type = 0;
if (modem_type != kiss::settings().modem_type)
{
2021-06-26 18:13:53 +00:00
if (demod)
{
demod->~IDemodulator();
}
switch (kiss::settings().modem_type)
{
case kiss::Hardware::ModemType::AFSK1200:
demod = new (&mem) Afsk1200Demodulator();
break;
case kiss::Hardware::ModemType::FSK9600:
demod = new (&mem) Fsk9600Demodulator();
break;
case kiss::Hardware::ModemType::M17:
demod = new (&mem) M17Demodulator();
break;
default:
ERROR("Invalid demodulator");
CxxErrorHandler();
}
modem_type = kiss::settings().modem_type;
}
2021-06-26 18:13:53 +00:00
return demod;
}
2018-07-30 02:34:53 +00:00
void demodulatorTask() {
2018-07-30 02:34:53 +00:00
TNC_DEBUG("enter demodulatorTask");
2018-07-30 02:34:53 +00:00
bool dcd_status{false};
auto demodulator = getDemodulator();
demodulator->start();
2018-07-30 02:34:53 +00:00
while (true) {
osEvent peek = osMessagePeek(audioInputQueueHandle, 0);
if (peek.status == osEventMessage) break;
osEvent evt = osMessageGet(adcInputQueueHandle, osWaitForever);
if (evt.status != osEventMessage) {
continue;
}
HAL_IWDG_Refresh(&hiwdg);
2018-07-30 02:34:53 +00:00
auto block = (adc_pool_type::chunk_type*) evt.value.p;
auto samples = (int16_t*) block->buffer;
arm_offset_q15(samples, 0 - virtual_ground, normalized, demodulator->size());
2018-07-30 02:34:53 +00:00
adcPool.deallocate(block);
auto frame = (*demodulator)(normalized);
if (frame)
{
frame->source(frame->source() | hdlc::IoFrame::RF_DATA);
if (osMessagePut(ioEventQueueHandle, (uint32_t) frame, 1) != osOK)
{
hdlc::release(frame);
}
2018-07-30 02:34:53 +00:00
}
if (demodulator->locked() xor dcd_status) {
dcd_status = demodulator->locked();
2018-07-30 02:34:53 +00:00
if (dcd_status) {
dcd_on();
2018-07-30 02:34:53 +00:00
} else {
dcd_off();
2018-07-30 02:34:53 +00:00
}
}
}
demodulator->stop();
dcd_off();
TNC_DEBUG("exit demodulatorTask");
2018-07-30 02:34:53 +00:00
}
void streamLevels(uint8_t cmd) {
2018-07-30 02:34:53 +00:00
// Stream out Vpp, Vavg, Vmin, Vmax as four 16-bit values, left justified.
uint8_t data[9];
INFO("streamLevels: start");
auto demodulator = getDemodulator();
demodulator->start();
2018-07-30 02:34:53 +00:00
while (true) {
osEvent peek = osMessagePeek(audioInputQueueHandle, 0);
if (peek.status == osEventMessage) break;
uint16_t count = 0;
uint32_t accum = 0;
uint16_t vmin = std::numeric_limits<uint16_t>::max();
uint16_t vmax = std::numeric_limits<uint16_t>::min();
2018-07-30 02:34:53 +00:00
while (count < demodulator->size() * 30) {
2018-07-30 02:34:53 +00:00
osEvent evt = osMessageGet(adcInputQueueHandle, osWaitForever);
if (evt.status != osEventMessage) continue;
count += demodulator->size();
2018-07-30 02:34:53 +00:00
auto block = (adc_pool_type::chunk_type*) evt.value.p;
auto start = (uint16_t*) block->buffer;
auto end = start + demodulator->size();
2018-07-30 02:34:53 +00:00
vmin = std::min(vmin, *std::min_element(start, end));
vmax = std::max(vmax, *std::max_element(start, end));
2018-07-30 02:34:53 +00:00
accum = std::accumulate(start, end, accum);
adcPool.deallocate(block);
}
uint16_t pp = (vmax - vmin) << 4;
2018-07-30 02:34:53 +00:00
uint16_t avg = (accum / count) << 4;
vmin <<= 4;
vmax <<= 4;
2018-07-30 02:34:53 +00:00
data[0] = cmd;
data[1] = (pp >> 8) & 0xFF; // Vpp
data[2] = (pp & 0xFF);
data[3] = (avg >> 8) & 0xFF; // Vavg (DC level)
data[4] = (avg & 0xFF);
data[5] = (vmin >> 8) & 0xFF; // Vmin
data[6] = (vmin & 0xFF);
data[7] = (vmax >> 8) & 0xFF; // Vmax
data[8] = (vmax & 0xFF);
2018-07-30 02:34:53 +00:00
ioport->write(data, 9, 6, 10);
}
demodulator->stop();
TNC_DEBUG("exit streamLevels");
2018-07-30 02:34:53 +00:00
}
levels_type readLevels(uint32_t)
{
2018-07-30 02:34:53 +00:00
TNC_DEBUG("enter readLevels");
2018-07-30 02:34:53 +00:00
// Return Vpp, Vavg, Vmin, Vmax as four 16-bit values, right justified.
uint32_t BLOCKS = 30;
2018-07-30 02:34:53 +00:00
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();
2018-07-30 02:34:53 +00:00
INFO("readLevels: start");
auto demodulator = getDemodulator();
demodulator->start();
2018-07-30 02:34:53 +00:00
for (uint32_t count = 0; count != BLOCKS; ++count)
{
2018-07-30 02:34:53 +00:00
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 + demodulator->size();
2018-07-30 02:34:53 +00:00
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());
2018-07-30 02:34:53 +00:00
adcPool.deallocate(block);
accum = 0;
2018-07-30 02:34:53 +00:00
}
demodulator->stop();
2018-07-30 02:34:53 +00:00
uint16_t pp = vmax - vmin;
uint16_t avg = iaccum / BLOCKS;
INFO("exit readLevels");
2018-07-30 02:34:53 +00:00
return levels_type(pp, avg, vmin, vmax);
}
/**
* This provides 100Hz resolution to the Goerztel filter.
*/
constexpr uint32_t TWIST_SAMPLE_SIZE = 88;
2018-07-30 02:34:53 +00:00
/*
* 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 readTwist()
{
return getDemodulator()->readTwist();
2018-07-30 02:34:53 +00:00
}
/*
* Get the input twist level as a pair of numbers -- the relative dB
* level of the Bell 202 mark and space tones.
*
* This is intended to measure noise levels on an empty channel.
*
* When de-emphasis is applied, the noise at 1200Hz will be about 5.5dB
* higher than at 2200Hz. When de-emphasis is not applied (discriminator
* output), the levels should be about the same.
*
* This is used to adjust the demodulator filters so that the proper
* input twist is applied to the signal. In general, properly modulated
* signals are expected to be pre-emphasized so that they are equal
* when de-emphasis is applied.
*
* If no de-emphasis is detected, the de-emphasis has to be applied in
* the demodulator.
*
* This takes about 5 seconds to complete as it averages 100 50ms samples
* to get a reasonable sampling of the noise.
*/
void pollInputTwist()
{
TNC_DEBUG("enter pollInputTwist");
2018-07-30 02:34:53 +00:00
float g1200 = 0.0f;
float g2200 = 0.0f;
GoertzelFilter<TWIST_SAMPLE_SIZE, SAMPLE_RATE> gf1200(1200.0);
GoertzelFilter<TWIST_SAMPLE_SIZE, SAMPLE_RATE> gf2200(2200.0);
const uint32_t AVG_SAMPLES = 100;
IDemodulator::startADC(3029, TWIST_SAMPLE_SIZE);
2018-07-30 02:34:53 +00:00
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;
count += ADC_BUFFER_SIZE;
auto block = (adc_pool_type::chunk_type*) evt.value.p;
2018-07-30 02:34:53 +00:00
uint16_t* data = (uint16_t*) block->buffer;
gf1200(data, ADC_BUFFER_SIZE);
gf2200(data, ADC_BUFFER_SIZE);
adcPool.deallocate(block);
}
g1200 += 10.0 * log10(gf1200);
g2200 += 10.0 * log10(gf2200);
gf1200.reset();
gf2200.reset();
}
IDemodulator::stopADC();
2018-07-30 02:34:53 +00:00
TNC_DEBUG("pollInputTwist: MARK=%d, SPACE=%d (x100)",
2018-07-30 02:34:53 +00:00
int(g1200 * 100.0 / AVG_SAMPLES), int(g2200 * 100.0 / AVG_SAMPLES));
int16_t g1200i = int16_t(g1200 * 256 / AVG_SAMPLES);
int16_t g2200i = int16_t(g2200 * 256 / AVG_SAMPLES);
uint8_t buffer[5];
buffer[0] = kiss::hardware::POLL_INPUT_TWIST;
buffer[1] = (g1200i >> 8) & 0xFF;
buffer[2] = g1200i & 0xFF;
buffer[3] = (g2200i >> 8) & 0xFF;
buffer[4] = g2200i & 0xFF;
ioport->write(buffer, 5, 6, 10);
TNC_DEBUG("exit pollInputTwist");
2018-07-30 02:34:53 +00:00
}
void streamAmplifiedInputLevels() {
TNC_DEBUG("enter streamAmplifiedInputLevels");
streamLevels(kiss::hardware::POLL_INPUT_LEVEL);
TNC_DEBUG("exit streamAmplifiedInputLevels");
2018-07-30 02:34:53 +00:00
}
void pollAmplifiedInputLevel() {
TNC_DEBUG("enter pollAmplifiedInputLevel");
2018-07-30 02:34:53 +00:00
uint16_t Vpp, Vavg, Vmin, Vmax;
std::tie(Vpp, Vavg, Vmin, Vmax) = readLevels(AUDIO_IN);
Vpp <<= 4;
Vavg <<= 4;
Vmin <<= 4;
Vmax <<= 4;
uint8_t data[9];
data[0] = kiss::hardware::POLL_INPUT_LEVEL;
data[1] = (Vpp >> 8) & 0xFF; // Vpp
data[2] = (Vpp & 0xFF);
data[3] = (Vavg >> 8) & 0xFF; // Vavg (DC level)
data[4] = (Vavg & 0xFF);
data[5] = (Vmin >> 8) & 0xFF; // Vmin
data[6] = (Vmin & 0xFF);
data[7] = (Vmax >> 8) & 0xFF; // Vmax
data[8] = (Vmax & 0xFF);
ioport->write(data, 9, 6, 10);
TNC_DEBUG("exit pollAmplifiedInputLevel");
2018-07-30 02:34:53 +00:00
}
#ifndef NUCLEOTNC
void pollBatteryLevel()
{
auto vbat = getDemodulator()->readBatteryLevel();
uint8_t data[3];
data[0] = kiss::hardware::GET_BATTERY_LEVEL;
data[1] = (vbat >> 8) & 0xFF;
data[2] = (vbat & 0xFF);
ioport->write(data, 3, 6, 10);
}
#endif
#if 0
2018-07-30 02:34:53 +00:00
void stop() {
osDelay(100);
auto restore = SysTick->CTRL;
kiss::settings().input_offset += 6;
setAudioInputLevels();
kiss::settings().input_offset -= 6;
TNC_DEBUG("Stop");
2018-07-30 02:34:53 +00:00
// __disable_irq();
vTaskSuspendAll();
SysTick->CTRL = 0;
HAL_COMP_Init(&hcomp1);
HAL_COMP_Start_IT(&hcomp1);
while (adcState == STOPPED) {
// PWR_MAINREGULATOR_ON / PWR_LOWPOWERREGULATOR_ON
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
}
SystemClock_Config();
SysTick->CTRL = restore;
// __enable_irq();
HAL_COMP_Stop_IT(&hcomp1);
HAL_COMP_DeInit(&hcomp1);
xTaskResumeAll();
setAudioInputLevels();
// adcState = DEMODULATOR;
TNC_DEBUG("Wake");
2018-07-30 02:34:53 +00:00
}
#endif
2018-07-30 02:34:53 +00:00
}}} // mobilinkd::tnc::audio