// Copyright 2020 Rob Riggs // All rights reserved. #pragma once #include "Modulator.hpp" #include "stm32l4xx_hal.h" #include #include #include extern IWDG_HandleTypeDef hiwdg; 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 uint16_t VREF = 4095; using cos_table_type = std::array; static const cos_table_type cos_table; enum class Level { ZERO, HIGH, LOW }; enum class State { STOPPED, STARTING, RUNNING, STOPPING }; osMessageQId dacOutputQueueHandle_{0}; PTT* ptt_{nullptr}; uint16_t volume_{4096}; std::array buffer_; Level level{Level::HIGH}; State state{State::STOPPED}; Scrambler lfsr; Fsk9600Modulator(osMessageQId queue, PTT* ptt) : dacOutputQueueHandle_(queue), ptt_(ptt) {} ~Fsk9600Modulator() override {} void init(const kiss::Hardware& hw) override; 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(256, level); v = std::min(4096, v); volume_ = v; } void set_ptt(PTT* ptt) override { if (state != State::STOPPED) { ERROR("PTT change while not stopped"); CxxErrorHandler(); } ptt_ = ptt; ptt_->off(); } void send(uint8_t bit) override { auto scrambled = lfsr(bit); switch (state) { case State::STOPPING: case State::STOPPED: ptt_->on(); #if defined(KISS_LOGGING) && defined(HAVE_LSCO) HAL_RCCEx_DisableLSCO(); #endif fill_first(scrambled); state = State::STARTING; break; case State::STARTING: fill_last(scrambled); state = State::RUNNING; start_conversion(); break; case State::RUNNING: osMessagePut(dacOutputQueueHandle_, scrambled, osWaitForever); break; } } void tone(uint16_t freq) override {} // DAC DMA interrupt functions. void fill_first(uint8_t bit) override { fill(buffer_.data(), bit); } void fill_last(uint8_t bit) override { fill(buffer_.data() + BIT_LEN, bit); } void empty_first() override { empty(); } void empty_last() override { empty(); } void empty() { HAL_IWDG_Refresh(&hiwdg); switch (state) { case State::STARTING: // fall-through case State::RUNNING: state = State::STOPPING; break; case State::STOPPING: state = State::STOPPED; stop_conversion(); ptt_->off(); level = Level::HIGH; #if defined(KISS_LOGGING) && defined(HAVE_LSCO) HAL_RCCEx_EnableLSCO(RCC_LSCOSOURCE_LSE); #endif break; case State::STOPPED: break; } } void abort() override { state = State::STOPPED; stop_conversion(); ptt_->off(); level = Level::HIGH; #if defined(KISS_LOGGING) && defined(HAVE_LSCO) 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(buffer_.data()), buffer_.size(), DAC_ALIGN_12B_R); } uint16_t adjust_level(int32_t sample) const { sample *= volume_; sample >>= 12; sample += 2048; return sample; } void fill(uint16_t* buffer, bool bit) { HAL_IWDG_Refresh(&hiwdg); 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.begin(), cos_table.end(), buffer, [this](auto x){return adjust_level(-1 - x);}); level = Level::HIGH; } else { std::fill(buffer, buffer + BIT_LEN, adjust_level(-2048)); } break; default: CxxErrorHandler(); } } }; }} // mobilinkd::tnc