kopia lustrzana https://github.com/mobilinkd/tnc3-firmware
				
				
				
			
		
			
				
	
	
		
			380 wiersze
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
			
		
		
	
	
			380 wiersze
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
// Copyright 2020 Rob Riggs <rob@mobilinkd.com>
 | 
						|
// All rights reserved.
 | 
						|
 | 
						|
#pragma once
 | 
						|
 | 
						|
#include "AudioInput.hpp"
 | 
						|
#include "Modulator.hpp"
 | 
						|
#include "HdlcFrame.hpp"
 | 
						|
#include "M17.h"
 | 
						|
 | 
						|
#include <arm_math.h>
 | 
						|
 | 
						|
#include <array>
 | 
						|
#include <algorithm>
 | 
						|
#include <cstdint>
 | 
						|
 | 
						|
namespace mobilinkd { namespace tnc {
 | 
						|
 | 
						|
/**
 | 
						|
 * M17 modulator. Collects 16 symbols of data, upsamples by 10x using
 | 
						|
 * an interpolating FIR filter, which is sent out via the DAC using
 | 
						|
 * DMA.
 | 
						|
 */
 | 
						|
struct M17Modulator : Modulator
 | 
						|
{
 | 
						|
    // Six buffers per M17 frame, or 12 half-buffer interrupts.
 | 
						|
    static constexpr uint8_t UPSAMPLE = 10;
 | 
						|
    static constexpr uint32_t BLOCKSIZE = 4;
 | 
						|
    static constexpr uint32_t STATE_SIZE = (m17::FILTER_TAP_NUM_15 / UPSAMPLE) + BLOCKSIZE - 1;
 | 
						|
    static constexpr int16_t DAC_BUFFER_LEN = 80;               // 8 symbols, 16 bits, 2 bytes.
 | 
						|
    static constexpr int16_t TRANSFER_LEN = DAC_BUFFER_LEN / 2; // 4 symbols, 8 bits, 1 byte.
 | 
						|
    static constexpr uint16_t VREF = 4095;
 | 
						|
    enum class State { STOPPED, STARTING, RUNNING, STOPPING };
 | 
						|
 | 
						|
    arm_fir_interpolate_instance_f32 fir_interpolator;
 | 
						|
    std::array<float, STATE_SIZE> fir_state;
 | 
						|
    std::array<int16_t, DAC_BUFFER_LEN> buffer_;
 | 
						|
    std::array<float, 4> symbols;
 | 
						|
    osMessageQId dacOutputQueueHandle_{0};
 | 
						|
    PTT* ptt_{nullptr};
 | 
						|
    uint16_t volume_{4096};
 | 
						|
    volatile uint16_t delay_count = 0;      // TX Delay
 | 
						|
    volatile uint16_t stop_count = 0;       // Flush the RRC matched filter.
 | 
						|
    State state{State::STOPPED};
 | 
						|
    float tmp[TRANSFER_LEN];
 | 
						|
    bool send_tone = false;
 | 
						|
 | 
						|
    M17Modulator(osMessageQId queue, PTT* ptt)
 | 
						|
    : dacOutputQueueHandle_(queue), ptt_(ptt)
 | 
						|
    {
 | 
						|
        arm_fir_interpolate_init_f32(
 | 
						|
            &fir_interpolator, UPSAMPLE, m17::FILTER_TAP_NUM_15,
 | 
						|
            (float32_t*) m17::rrc_taps_f15.data(), fir_state.data(), BLOCKSIZE);
 | 
						|
    }
 | 
						|
 | 
						|
    ~M17Modulator() override {}
 | 
						|
 | 
						|
    void start_loopback() override
 | 
						|
    {
 | 
						|
    }
 | 
						|
 | 
						|
    void stop_loopback() override
 | 
						|
    {
 | 
						|
    }
 | 
						|
 | 
						|
    void loopback(const void* input) override
 | 
						|
    {
 | 
						|
    }
 | 
						|
 | 
						|
    void init(const kiss::Hardware& hw) override;
 | 
						|
 | 
						|
    void deinit() override
 | 
						|
    {
 | 
						|
        state = State::STOPPED;
 | 
						|
        HAL_DAC_Stop(&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(uint8_t bits) override
 | 
						|
    {
 | 
						|
        uint16_t txdelay = 0;
 | 
						|
 | 
						|
        switch (state)
 | 
						|
        {
 | 
						|
        case State::STOPPING:
 | 
						|
        case State::STOPPED:
 | 
						|
#if defined(KISS_LOGGING) && !defined(NUCLEOTNC)
 | 
						|
            HAL_RCCEx_DisableLSCO();
 | 
						|
#endif
 | 
						|
            delay_count = 0;
 | 
						|
            txdelay = kiss::settings().txdelay * 12 - 5;
 | 
						|
            fill_empty(buffer_.data());
 | 
						|
            fill_empty(buffer_.data() + TRANSFER_LEN);
 | 
						|
            state = State::STARTING;
 | 
						|
            [[fallthrough]];
 | 
						|
        case State::STARTING:
 | 
						|
            osMessagePut(audioInputQueueHandle, tnc::audio::IDLE,
 | 
						|
              osWaitForever);
 | 
						|
            start_conversion();
 | 
						|
            ptt_->on();
 | 
						|
            while (delay_count < txdelay) osThreadYield();
 | 
						|
            stop_count = 4; // 16 symbols to flush the RRC filter.
 | 
						|
            osMessagePut(dacOutputQueueHandle_, bits, osWaitForever);
 | 
						|
            state = State::RUNNING;
 | 
						|
            break;
 | 
						|
        case State::RUNNING:
 | 
						|
            osMessagePut(dacOutputQueueHandle_, bits, osWaitForever);
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    constexpr std::array<float, 48> make_1000hz_tone()
 | 
						|
    {
 | 
						|
        std::array<float, 48> result;
 | 
						|
        for (size_t i = 0; i != result.size(); ++i) {
 | 
						|
            result[i] = std::sin(M_PI * i * 2.0 / result.size()) * 3;
 | 
						|
        }
 | 
						|
        return result;
 | 
						|
    }
 | 
						|
 | 
						|
    void tone(uint16_t) override
 | 
						|
    {
 | 
						|
        send_tone = true;
 | 
						|
    }
 | 
						|
 | 
						|
    // DAC DMA interrupt functions.
 | 
						|
    [[gnu::noinline]]
 | 
						|
    void fill_first(uint8_t bits) override
 | 
						|
    {
 | 
						|
        fill(buffer_.data(), bits);
 | 
						|
    }
 | 
						|
 | 
						|
    [[gnu::noinline]]
 | 
						|
    void fill_last(uint8_t bits) override
 | 
						|
    {
 | 
						|
        fill(buffer_.data() + TRANSFER_LEN, bits);
 | 
						|
    }
 | 
						|
 | 
						|
    /*
 | 
						|
     * DAC queue is empty when STARTING.  It is filled with '0' symbols
 | 
						|
     * for TX delay duration (using delay_count).  It then transitions
 | 
						|
     * to the running state in send() after filling the DAC queue.
 | 
						|
     *
 | 
						|
     * When no more symbols are available, the DAC queue is empty in the
 | 
						|
     * running state.  The FIR filter is flushed of the remaining data
 | 
						|
     * using '0' symbols.
 | 
						|
     */
 | 
						|
    void empty_first() override
 | 
						|
    {
 | 
						|
        switch (state)
 | 
						|
        {
 | 
						|
        case State::STARTING:
 | 
						|
            fill_empty(buffer_.data());
 | 
						|
            delay_count += 1;
 | 
						|
            break;
 | 
						|
        case State::RUNNING:
 | 
						|
            fill_empty(buffer_.data());
 | 
						|
            state = State::STOPPING;
 | 
						|
            break;
 | 
						|
        case State::STOPPING:
 | 
						|
            fill_empty(buffer_.data());
 | 
						|
            if (--stop_count == 0) state = State::STOPPED;
 | 
						|
            break;
 | 
						|
        case State::STOPPED:
 | 
						|
            stop_conversion();
 | 
						|
            ptt_->off();
 | 
						|
#if defined(KISS_LOGGING) && !defined(NUCLEOTNC)
 | 
						|
                HAL_RCCEx_EnableLSCO(RCC_LSCOSOURCE_LSE);
 | 
						|
#endif
 | 
						|
            osMessagePut(audioInputQueueHandle, tnc::audio::DEMODULATOR,
 | 
						|
              osWaitForever);
 | 
						|
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /*
 | 
						|
     * DAC queue is empty when STARTING.  It is filled with '0' symbols
 | 
						|
     * for TX delay duration (using delay_count).  It then transitions
 | 
						|
     * to the running state in send() after filling the DAC queue.
 | 
						|
     *
 | 
						|
     * When no more symbols are available, the DAC queue is empty in the
 | 
						|
     * running state.  The FIR filter is flushed of the remaining data
 | 
						|
     * using '0' symbols.
 | 
						|
     */
 | 
						|
    void empty_last() override
 | 
						|
    {
 | 
						|
        switch (state)
 | 
						|
        {
 | 
						|
        case State::STARTING:
 | 
						|
            fill_empty(buffer_.data() + TRANSFER_LEN);
 | 
						|
            delay_count += 1;
 | 
						|
            break;
 | 
						|
        case State::RUNNING:
 | 
						|
            fill_empty(buffer_.data() + TRANSFER_LEN);
 | 
						|
            state = State::STOPPING;
 | 
						|
            break;
 | 
						|
        case State::STOPPING:
 | 
						|
            fill_empty(buffer_.data() + TRANSFER_LEN);
 | 
						|
            if (--stop_count == 0) state = State::STOPPED;
 | 
						|
            break;
 | 
						|
        case State::STOPPED:
 | 
						|
            stop_conversion();
 | 
						|
            ptt_->off();
 | 
						|
#if defined(KISS_LOGGING) && !defined(NUCLEOTNC)
 | 
						|
                HAL_RCCEx_EnableLSCO(RCC_LSCOSOURCE_LSE);
 | 
						|
#endif
 | 
						|
            osMessagePut(audioInputQueueHandle, tnc::audio::DEMODULATOR,
 | 
						|
              osWaitForever);
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    void abort() override
 | 
						|
    {
 | 
						|
        state = State::STOPPED;
 | 
						|
        send_tone = false;
 | 
						|
        stop_conversion();
 | 
						|
        ptt_->off();
 | 
						|
#if defined(KISS_LOGGING) && !defined(NUCLEOTNC)
 | 
						|
            HAL_RCCEx_EnableLSCO(RCC_LSCOSOURCE_LSE);
 | 
						|
#endif
 | 
						|
        // Drain the queue.
 | 
						|
        while (osMessageGet(dacOutputQueueHandle_, 0).status == osEventMessage);
 | 
						|
    }
 | 
						|
 | 
						|
    float bits_per_ms() const override
 | 
						|
    {
 | 
						|
        return 9.6f;
 | 
						|
    }
 | 
						|
 | 
						|
private:
 | 
						|
 | 
						|
    /**
 | 
						|
     * Configure the DAC for timer-based DMA conversion, start the timer,
 | 
						|
     * and start DMA to DAC.
 | 
						|
     */
 | 
						|
    void start_conversion()
 | 
						|
    {
 | 
						|
        DAC_ChannelConfTypeDef sConfig;
 | 
						|
 | 
						|
        sConfig.DAC_SampleAndHold = DAC_SAMPLEANDHOLD_DISABLE;
 | 
						|
        sConfig.DAC_Trigger = DAC_TRIGGER_T7_TRGO;
 | 
						|
        sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
 | 
						|
        sConfig.DAC_ConnectOnChipPeripheral = DAC_CHIPCONNECT_ENABLE;
 | 
						|
        sConfig.DAC_UserTrimming = DAC_TRIMMING_FACTORY;
 | 
						|
        if (HAL_DAC_ConfigChannel(&hdac1, &sConfig, DAC_CHANNEL_1) != HAL_OK)
 | 
						|
        {
 | 
						|
          CxxErrorHandler();
 | 
						|
        }
 | 
						|
 | 
						|
        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);
 | 
						|
    }
 | 
						|
 | 
						|
    uint16_t adjust_level(float sample) const
 | 
						|
    {
 | 
						|
        sample *= volume_;
 | 
						|
        sample /= 8;
 | 
						|
        sample += 2048;
 | 
						|
        if (sample > 4095) sample = 4095;
 | 
						|
        else if (sample < 0) sample = 0;
 | 
						|
        return sample;
 | 
						|
    }
 | 
						|
 | 
						|
    constexpr int8_t bits_to_symbol(uint8_t bits)
 | 
						|
    {
 | 
						|
        switch (bits)
 | 
						|
        {
 | 
						|
        case 0: return 1;
 | 
						|
        case 1: return 3;
 | 
						|
        case 2: return -1;
 | 
						|
        case 3: return -3;
 | 
						|
        }
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
 | 
						|
    constexpr int16_t symbol_skew(int32_t symbol)
 | 
						|
    {
 | 
						|
        const size_t shift = 8;
 | 
						|
        if (symbol == 1 || symbol == -1)
 | 
						|
        {
 | 
						|
            int32_t offset = ((symbol * (kiss::settings().tx_twist - 50)) << shift) / 50;
 | 
						|
            return (symbol << shift) - offset;
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            return symbol << shift;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    void fill_tone(int16_t* buffer)
 | 
						|
    {
 | 
						|
        static uint8_t pos = 0;
 | 
						|
        static const auto Hz1000 = make_1000hz_tone();
 | 
						|
 | 
						|
        int16_t polarity = kiss::settings().tx_rev_polarity() ? -1 : 1;
 | 
						|
 | 
						|
        for (size_t i = 0; i != TRANSFER_LEN; ++i) {
 | 
						|
            buffer[i] = adjust_level(Hz1000[pos++] * polarity);
 | 
						|
            if (pos == Hz1000.size()) pos = 0;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    [[gnu::noinline]]
 | 
						|
    void fill(int16_t* buffer, uint8_t bits)
 | 
						|
    {
 | 
						|
        if (send_tone)
 | 
						|
        {
 | 
						|
            fill_tone(buffer);
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        int16_t polarity = kiss::settings().tx_rev_polarity() ? -1 : 1;
 | 
						|
 | 
						|
        for (size_t i = 0; i != 4; ++i)
 | 
						|
        {
 | 
						|
            symbols[i] = bits_to_symbol(bits >> 6) * polarity;
 | 
						|
            bits <<= 2;
 | 
						|
        }
 | 
						|
 | 
						|
        arm_fir_interpolate_f32(
 | 
						|
            &fir_interpolator, symbols.data(), tmp, BLOCKSIZE);
 | 
						|
 | 
						|
        for (size_t i = 0; i != TRANSFER_LEN; ++i)
 | 
						|
        {
 | 
						|
            buffer[i] = adjust_level(tmp[i]);
 | 
						|
        }
 | 
						|
    }
 | 
						|
#if 0
 | 
						|
    [[gnu::noinline]]
 | 
						|
    void fill_empty(int16_t* buffer)
 | 
						|
    {
 | 
						|
        symbols.fill(0);
 | 
						|
 | 
						|
        arm_fir_interpolate_f32(
 | 
						|
            &fir_interpolator, symbols.data(), tmp, BLOCKSIZE);
 | 
						|
 | 
						|
        for (size_t i = 0; i != TRANSFER_LEN; ++i)
 | 
						|
        {
 | 
						|
            buffer[i] = adjust_level(tmp[i]);
 | 
						|
        }
 | 
						|
    }
 | 
						|
#endif
 | 
						|
 | 
						|
    [[gnu::noinline]]
 | 
						|
    void fill_empty(int16_t* buffer)
 | 
						|
    {
 | 
						|
        send_tone = false;
 | 
						|
        for (size_t i = 0; i != TRANSFER_LEN; ++i)
 | 
						|
        {
 | 
						|
            buffer[i] = 2048;
 | 
						|
        }
 | 
						|
    }
 | 
						|
};
 | 
						|
 | 
						|
}} // mobilinkd::tnc
 |