From 735e2af3d49fd01d83d2cb1dcb6fba4a79face6a Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 2 Dec 2021 20:49:26 +0000 Subject: [PATCH] initial commit of new codebase; --- ADF7021.cpp | 1197 ++++++++++++++++++++++++++++++ ADF7021.h | 347 +++++++++ BUILD.md | 739 +++++++++++++++++++ BitBuffer.cpp | 145 ++++ BitBuffer.h | 83 +++ CWIdTX.cpp | 202 ++++++ CWIdTX.h | 64 ++ CalRSSI.cpp | 89 +++ CalRSSI.h | 56 ++ Defines.h | 148 ++++ FirmwareMain.cpp | 150 ++++ Globals.h | 132 ++++ IO.cpp | 444 ++++++++++++ IO.h | 244 +++++++ IOSTM.cpp | 856 ++++++++++++++++++++++ LICENSE.md | 264 +++++++ README.md | 27 + STM_UART.cpp | 134 ++++ STM_UART.h | 137 ++++ SerialBuffer.cpp | 142 ++++ SerialBuffer.h | 89 +++ SerialPort.cpp | 1201 +++++++++++++++++++++++++++++++ SerialPort.h | 218 ++++++ SerialSTM.cpp | 316 ++++++++ Utils.cpp | 86 +++ Utils.h | 53 ++ bootloader.ld | 26 + dmr/CalDMR.cpp | 286 ++++++++ dmr/CalDMR.h | 90 +++ dmr/DMRDMORX.cpp | 354 +++++++++ dmr/DMRDMORX.h | 102 +++ dmr/DMRDMOTX.cpp | 228 ++++++ dmr/DMRDMOTX.h | 95 +++ dmr/DMRDefines.h | 135 ++++ dmr/DMRIdleRX.cpp | 186 +++++ dmr/DMRIdleRX.h | 82 +++ dmr/DMRRX.cpp | 108 +++ dmr/DMRRX.h | 71 ++ dmr/DMRSlotRX.cpp | 391 ++++++++++ dmr/DMRSlotRX.h | 115 +++ dmr/DMRSlotType.cpp | 334 +++++++++ dmr/DMRSlotType.h | 60 ++ dmr/DMRTX.cpp | 466 ++++++++++++ dmr/DMRTX.h | 135 ++++ dvm-firmware-hs.vcxproj | 197 +++++ dvm-firmware-hs.vcxproj.filters | 174 +++++ normal.ld | 26 + p25/CalP25.cpp | 144 ++++ p25/CalP25.h | 70 ++ p25/P25Defines.h | 135 ++++ p25/P25RX.cpp | 572 +++++++++++++++ p25/P25RX.h | 114 +++ p25/P25TX.cpp | 279 +++++++ p25/P25TX.h | 106 +++ stm32f10x_link.ld | 130 ++++ stm32f4xx_link.ld | 138 ++++ stm32f7xx_link.ld | 137 ++++ 57 files changed, 13049 insertions(+) create mode 100644 ADF7021.cpp create mode 100644 ADF7021.h create mode 100644 BUILD.md create mode 100644 BitBuffer.cpp create mode 100644 BitBuffer.h create mode 100644 CWIdTX.cpp create mode 100644 CWIdTX.h create mode 100644 CalRSSI.cpp create mode 100644 CalRSSI.h create mode 100644 Defines.h create mode 100644 FirmwareMain.cpp create mode 100644 Globals.h create mode 100644 IO.cpp create mode 100644 IO.h create mode 100644 IOSTM.cpp create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 STM_UART.cpp create mode 100644 STM_UART.h create mode 100644 SerialBuffer.cpp create mode 100644 SerialBuffer.h create mode 100644 SerialPort.cpp create mode 100644 SerialPort.h create mode 100644 SerialSTM.cpp create mode 100644 Utils.cpp create mode 100644 Utils.h create mode 100644 bootloader.ld create mode 100644 dmr/CalDMR.cpp create mode 100644 dmr/CalDMR.h create mode 100644 dmr/DMRDMORX.cpp create mode 100644 dmr/DMRDMORX.h create mode 100644 dmr/DMRDMOTX.cpp create mode 100644 dmr/DMRDMOTX.h create mode 100644 dmr/DMRDefines.h create mode 100644 dmr/DMRIdleRX.cpp create mode 100644 dmr/DMRIdleRX.h create mode 100644 dmr/DMRRX.cpp create mode 100644 dmr/DMRRX.h create mode 100644 dmr/DMRSlotRX.cpp create mode 100644 dmr/DMRSlotRX.h create mode 100644 dmr/DMRSlotType.cpp create mode 100644 dmr/DMRSlotType.h create mode 100644 dmr/DMRTX.cpp create mode 100644 dmr/DMRTX.h create mode 100644 dvm-firmware-hs.vcxproj create mode 100644 dvm-firmware-hs.vcxproj.filters create mode 100644 normal.ld create mode 100644 p25/CalP25.cpp create mode 100644 p25/CalP25.h create mode 100644 p25/P25Defines.h create mode 100644 p25/P25RX.cpp create mode 100644 p25/P25RX.h create mode 100644 p25/P25TX.cpp create mode 100644 p25/P25TX.h create mode 100644 stm32f10x_link.ld create mode 100644 stm32f4xx_link.ld create mode 100644 stm32f7xx_link.ld diff --git a/ADF7021.cpp b/ADF7021.cpp new file mode 100644 index 0000000..823908e --- /dev/null +++ b/ADF7021.cpp @@ -0,0 +1,1197 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* + * Copyright (C) 2020,2021 by Jonathan Naylor G4KLX + * Copyright (C) 2016 by Jim McLaughlin KI6ZUM + * Copyright (C) 2016,2017,2018,2019,2020 by Andy Uribe CA6JAU + * Copyright (C) 2017 by Danilo DB4PLE + * Copyright (C) 2021 Bryan Biedenkapp N2PLL + * + * Some of the code is based on work of Guus Van Dooren PE1PLM: + * https://github.com/ki6zum/gmsk-dstar/blob/master/firmware/dvmega/dvmega.ino + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include +#include "ADF7021.h" + +#if defined(ENABLE_ADF7021) + +// --------------------------------------------------------------------------- +// Globals +// --------------------------------------------------------------------------- + +volatile bool toTxRequest = false; +volatile bool toRxRequest = false; +volatile bool even = true; +static uint32_t lastClk = 2U; + +volatile uint32_t AD7021_CONTROL; +uint32_t ADF7021_RX_REG0; +uint32_t ADF7021_TX_REG0; +uint32_t ADF7021_REG1; + +uint32_t div2; +uint32_t f_div; + +uint8_t RX_N_Divider; // Rx - 8-bit Integer_N +uint16_t RX_F_Divider; // Rx - 15-bit Frational_N +uint8_t TX_N_Divider; // Tx - 8-bit Integer_N +uint16_t TX_F_Divider; // Tx - 15-bit Frational_N + +uint16_t m_dmrDev; +uint16_t m_p25Dev; + +// --------------------------------------------------------------------------- +// Global Functions +// --------------------------------------------------------------------------- + +/// +/// +/// +static void AD7021_IOCTL_Shift() +{ + for (int i = 31; i >= 0; i--) { + if (ADF_BIT_READ(AD7021_CONTROL, i) == HIGH) + io.SDATA(HIGH); + else + io.SDATA(LOW); + + io.delayBit(); + io.SCLK(HIGH); + io.delayBit(); + io.SCLK(LOW); + } + + // to keep SDATA signal at defined level when idle (not required) + io.SDATA(LOW); +} + +/// +/// +/// +static void AD7021_IOCTL_SLEPulse() +{ + io.SLE(HIGH); + io.delayBit(); + io.SLE(LOW); +} + +/// +/// +/// +/// +static void AD7021_1_IOCTL(bool doSle = true) +{ + AD7021_IOCTL_Shift(); + + if (doSle) + AD7021_IOCTL_SLEPulse(); +} + +#if defined(DUPLEX) +/// +/// +/// +static void AD7021_2_IOCTL_SLEPulse() +{ + io.SLE2(HIGH); + io.delayBit(); + io.SLE2(LOW); +} + +/// +/// +/// +/// +static void AD7021_2_IOCTL(bool doSle = true) +{ + AD7021_IOCTL_Shift(); + + if (doSle) + AD7021_2_IOCTL_SLEPulse(); +} +#endif + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Hardware interrupt handler. +/// +void IO::interrupt1() +{ + uint8_t bit = 0U; + + if (!m_started) + return; + + uint8_t clk = CLK(); + + // this is to prevent activation by spurious interrupts + // which seem to happen if you send out an control word + // needs investigation + // this workaround will fail if only rising or falling edge + // is used to trigger the interrupt !!!! + // TODO: figure out why sending the control word seems to issue interrupts + // possibly this is a design problem of the RF7021 board or too long wires + // on the breadboard build + // but normally this will not hurt too much + if (clk == lastClk) + return; + else + lastClk = clk; + + // we set the TX bit at TXD low, sampling of ADF7021 happens at rising clock + if (m_tx && clk == 0U) { + m_txBuffer.get(bit, m_control); + even = !even; + +#if defined(BIDIR_DATA_PIN) + if (bit) + setRXDInt(HIGH); + else + setRXDInt(LOW); +#else + if (bit) + setTXDInt(HIGH); + else + setTXDInt(LOW); +#endif + + // wait a brief period before raising SLE + if (toTxRequest == true) { + asm volatile( + "nop \n\t" + "nop \n\t" + "nop \n\t" + ); + + // SLE Pulse, should be moved out of here into class method + // according to datasheet in 4FSK we have to deliver this before 1/4 tbit == 26uS + SLE1(HIGH); + asm volatile( + "nop \n\t" + "nop \n\t" + "nop \n\t" + ); + + SLE1(LOW); + SDATA(LOW); + + // now do housekeeping + toTxRequest = false; + + // first tranmittted bit is always the odd bit + even = ADF7021_EVEN_BIT; + } + } + + // we sample the RX bit at rising TXD clock edge, so TXD must be 1 and we are not in tx mode + if (!m_tx && clk == 1U && !m_duplex) { + if (RXD1()) + bit = 1U; + else + bit = 0U; + + m_rxBuffer.put(bit, m_control); + } + + if (toRxRequest && even == ADF7021_EVEN_BIT && m_tx && clk == 0U) { + // that is absolutely crucial in 4FSK, see datasheet: + // enable sle after 1/4 tBit == 26uS when sending MSB (even == false) and clock is low + delayUS(26U); + + // SLE Pulse, should be moved out of here into class method + SLE1(HIGH); + asm volatile( + "nop \n\t" + "nop \n\t" + "nop \n\t" + ); + + SLE1(LOW); + SDATA(LOW); + + // now do housekeeping + m_tx = false; + toRxRequest = false; + + // last tranmittted bit is always the even bit + // since the current bit is a transitional "don't care" bit, never transmitted + even = !ADF7021_EVEN_BIT; + } + + m_watchdog++; + m_modeTimerCnt++; + m_int1Counter++; + + if (m_scanPauseCnt >= SCAN_PAUSE) + m_scanPauseCnt = 0U; + + if (m_scanPauseCnt != 0U) + m_scanPauseCnt++; +} + +#if defined(DUPLEX) +/// +/// Hardware interrupt handler. +/// +void IO::interrupt2() +{ + uint8_t bit = 0U; + + if (m_duplex) { + if (RXD2()) + bit = 1U; + else + bit = 0U; + + m_rxBuffer.put(bit, m_control); + } + + m_int2Counter++; +} +#endif + +/// +/// Sets the ADF7021 RF configuration. +/// +/// +/// +void IO::rf1Conf(DVM_STATE modemState, bool reset) +{ + uint32_t ADF7021_REG2 = 0U; + uint32_t ADF7021_REG3 = 0U; + uint32_t ADF7021_REG4 = 0U; + uint32_t ADF7021_REG10 = 0U; + uint32_t ADF7021_REG13 = 0U; + int32_t AFC_OFFSET = 0; + + uint32_t txFrequencyTmp, rxFrequencyTmp; + + if (modemState != STATE_CW) + m_modemStatePrev = modemState; + +#if defined (ZUMSPOT_ADF7021) || defined(SKYBRIDGE_HS) + io.checkBand(m_rxFrequency, m_txFrequency); +#endif + + // Toggle CE pin for ADF7021 reset + if (reset) { + CE(LOW); + delayReset(); + CE(HIGH); + delayReset(); + } + + switch (modemState) { + case STATE_DMR: + case STATE_CW: + AFC_OFFSET = AFC_OFFSET_DMR; + break; + case STATE_P25: + AFC_OFFSET = AFC_OFFSET_P25; + break; + default: + break; + } + + /* + ** VCO/Oscillator (Register 1) + */ + configureBand(); + + /* + ** Fractional-N Synthesizer (Register 0) + */ + float divider = 0.0f; + if (div2 == 1U) + divider = (m_rxFrequency - 100000 + AFC_OFFSET) / (ADF7021_PFD / 2U); + else + divider = (m_rxFrequency - 100000 + (2 * AFC_OFFSET)) / ADF7021_PFD; + + // calculate Integer_N and Fractional_N divider values for Rx + RX_N_Divider = floor(divider); + divider = (divider - RX_N_Divider) * 32768; + RX_F_Divider = floor(divider + 0.5); + + // setup rx register 0 + ADF7021_RX_REG0 = (uint32_t)0b0000; // register 0 +#if defined(BIDIR_DATA_PIN) + ADF7021_RX_REG0 |= (uint32_t)0b01001 << 27; // mux regulator/receive +#else + ADF7021_RX_REG0 |= (uint32_t)0b01011 << 27; // mux regulator/uart-spi enabled/receive +#endif + ADF7021_RX_REG0 |= (uint32_t)RX_N_Divider << 19; // frequency - 15-bit Frac_N + ADF7021_RX_REG0 |= (uint32_t)RX_F_Divider << 4; // frequency - 8-bit Int_N + + if (div2 == 1U) + divider = m_txFrequency / (ADF7021_PFD / 2U); + else + divider = m_txFrequency / ADF7021_PFD; + + // calculate Integer_N and Fractional_N divider values for Tx + TX_N_Divider = floor(divider); + divider = (divider - TX_N_Divider) * 32768; + TX_F_Divider = floor(divider + 0.5); + + // setup tx register 0 + ADF7021_TX_REG0 = (uint32_t)0b0000; // register 0 +#if defined(BIDIR_DATA_PIN) + ADF7021_TX_REG0 |= (uint32_t)0b01000 << 27; // mux regulator/transmit +#else + ADF7021_TX_REG0 |= (uint32_t)0b01010 << 27; // mux regulator/uart-spi enabled/transmit +#endif + ADF7021_TX_REG0 |= (uint32_t)TX_N_Divider << 19; // frequency - 15-bit Frac_N + ADF7021_TX_REG0 |= (uint32_t)TX_F_Divider << 4; // frequency - 8-bit Int_N + + /* + ** Configure the remaining registers based on modem state. + */ + + switch (modemState) { + case STATE_CW: // 4FSK + // Dev: +1 symb (variable), symb rate = 4800 + + /* + ** Tx/Rx Clock (Register 3) & AFC (Register 10) + */ + ADF7021_REG3 = ADF7021_REG3_DMR; + ADF7021_REG10 = ADF7021_REG10_DMR; + + /* + ** Demodulator Setup (Register 4) + */ + // K=32 + ADF7021_REG4 = (uint32_t)0b0100 << 0; // register 4 + ADF7021_REG4 |= (uint32_t)0b011 << 4; // mode, 4FSK + ADF7021_REG4 |= (uint32_t)0b0 << 7; // cross product + ADF7021_REG4 |= (uint32_t)0b11 << 8; // invert clk/data + ADF7021_REG4 |= (uint32_t)ADF7021_DISC_BW_DMR << 10; // discriminator BW + ADF7021_REG4 |= (uint32_t)ADF7021_POST_BW_DMR << 20; // post demod BW + ADF7021_REG4 |= (uint32_t)0b10 << 30; // IF filter (25 kHz) + + /* + ** 3FSK/4FSK Demod (Register 13) + */ + ADF7021_REG13 = (uint32_t)0b1101 << 0; // register 13 + ADF7021_REG13 |= (uint32_t)ADF7021_SLICER_TH_DMR << 4; // slicer threshold + + /* + ** Transmit Modulation (Register 2) + */ + ADF7021_REG2 = (uint32_t)0b0010; // register 2 + ADF7021_REG2 |= (uint32_t)m_rfPower << 13; // power level + ADF7021_REG2 |= (uint32_t)0b110001 << 7; // PA + ADF7021_REG2 |= (uint32_t)0b10 << 28; // invert data (and RC alpha = 0.5) + ADF7021_REG2 |= (uint32_t)(m_cwIdTXLevel / div2) << 19; // deviation + ADF7021_REG2 |= (uint32_t)0b111 << 4; // modulation (RC 4FSK) + break; + + case STATE_DMR: // 4FSK + // Dev: +1 symb 648 Hz, symb rate = 4800 + + /* + ** Tx/Rx Clock (Register 3) & AFC (Register 10) + */ + ADF7021_REG3 = ADF7021_REG3_DMR; + ADF7021_REG10 = ADF7021_REG10_DMR; + + /* + ** Demodulator Setup (Register 4) + */ + // K=32 + ADF7021_REG4 = (uint32_t)0b0100 << 0; // register 4 + ADF7021_REG4 |= (uint32_t)0b011 << 4; // mode, 4FSK + ADF7021_REG4 |= (uint32_t)0b0 << 7; // cross product + ADF7021_REG4 |= (uint32_t)0b11 << 8; // invert clk/data + ADF7021_REG4 |= (uint32_t)ADF7021_DISC_BW_DMR << 10; // discriminator BW + ADF7021_REG4 |= (uint32_t)ADF7021_POST_BW_DMR << 20; // post demod BW + ADF7021_REG4 |= (uint32_t)0b10 << 30; // IF filter (25 kHz) + + /* + ** 3FSK/4FSK Demod (Register 13) + */ + ADF7021_REG13 = (uint32_t)0b1101 << 0; // register 13 + ADF7021_REG13 |= (uint32_t)ADF7021_SLICER_TH_DMR << 4; // slicer threshold + + /* + ** Transmit Modulation (Register 2) + */ + ADF7021_REG2 = (uint32_t)0b0010; // register 2 + ADF7021_REG2 |= (uint32_t)m_rfPower << 13; // power level + ADF7021_REG2 |= (uint32_t)0b110001 << 7; // PA + ADF7021_REG2 |= (uint32_t)0b10 << 28; // invert data (and RC alpha = 0.5) + ADF7021_REG2 |= (uint32_t)(m_dmrDev / div2) << 19; // deviation +#if defined(ADF7021_DISABLE_RC_4FSK) + ADF7021_REG2 |= (uint32_t)0b011 << 4; // modulation (4FSK) +#else + ADF7021_REG2 |= (uint32_t)0b111 << 4; // modulation (RC 4FSK) +#endif + break; + + case STATE_P25: // 4FSK + // Dev: +1 symb 600 Hz, symb rate = 4800 + + /* + ** Tx/Rx Clock (Register 3) & AFC (Register 10) + */ + ADF7021_REG3 = ADF7021_REG3_P25; + ADF7021_REG10 = ADF7021_REG10_P25; + + /* + ** Demodulator Setup (Register 4) + */ + // K=32 + ADF7021_REG4 = (uint32_t)0b0100 << 0; // register 4 + ADF7021_REG4 |= (uint32_t)0b011 << 4; // mode, 4FSK + ADF7021_REG4 |= (uint32_t)0b0 << 7; // cross product + ADF7021_REG4 |= (uint32_t)0b11 << 8; // invert clk/data + ADF7021_REG4 |= (uint32_t)ADF7021_DISC_BW_P25 << 10; // discriminator BW + ADF7021_REG4 |= (uint32_t)ADF7021_POST_BW_P25 << 20; // post demod BW + ADF7021_REG4 |= (uint32_t)0b00 << 30; // IF filter (12.5 kHz) + + /* + ** 3FSK/4FSK Demod (Register 13) + */ + ADF7021_REG13 = (uint32_t)0b1101 << 0; // register 13 + ADF7021_REG13 |= (uint32_t)ADF7021_SLICER_TH_P25 << 4; // slicer threshold + + /* + ** Transmit Modulation (Register 2) + */ + ADF7021_REG2 = (uint32_t)0b0010; // register 2 + ADF7021_REG2 |= (uint32_t)m_rfPower << 13; // power level + ADF7021_REG2 |= (uint32_t)0b110001 << 7; // PA + ADF7021_REG2 |= (uint32_t)0b10 << 28; // invert data (and RC alpha = 0.5) + ADF7021_REG2 |= (uint32_t)(m_p25Dev / div2) << 19; // deviation +#if defined(ENABLE_P25_WIDE) || defined(ADF7021_DISABLE_RC_4FSK) + ADF7021_REG2 |= (uint32_t)0b011 << 4; // modulation (4FSK) +#else + ADF7021_REG2 |= (uint32_t)0b111 << 4; // modulation (RC 4FSK) +#endif + break; + default: // GMSK + // Dev: 1200 Hz, symb rate = 4800 + + /* + ** Tx/Rx Clock (Register 3) & AFC (Register 10) + */ + ADF7021_REG3 = ADF7021_REG3_DEFAULT; + ADF7021_REG10 = ADF7021_REG10_DEFAULT; + + /* + ** Demodulator Setup (Register 4) + */ + // K=32 + ADF7021_REG4 = (uint32_t)0b0100 << 0; // register 4 + ADF7021_REG4 |= (uint32_t)0b001 << 4; // mode, GMSK + ADF7021_REG4 |= (uint32_t)0b1 << 7; // dot product + ADF7021_REG4 |= (uint32_t)0b10 << 8; // invert data + ADF7021_REG4 |= (uint32_t)ADF7021_DISC_BW_DEFAULT << 10; // discriminator BW + ADF7021_REG4 |= (uint32_t)ADF7021_POST_BW_DEFAULT << 20; // post demod BW + ADF7021_REG4 |= (uint32_t)0b00 << 30; // IF filter (12.5 kHz) + + /* + ** 3FSK/4FSK Demod (Register 13) + */ + ADF7021_REG13 = (uint32_t)0b1101 << 0; // register 13 + ADF7021_REG13 |= (uint32_t)ADF7021_SLICER_TH_DEFAULT << 4; // slicer threshold + + /* + ** Transmit Modulation (Register 2) + */ + ADF7021_REG2 = (uint32_t)0b0010; // register 2 + ADF7021_REG2 |= (uint32_t)m_rfPower << 13; // power level + ADF7021_REG2 |= (uint32_t)0b110001 << 7; // PA + ADF7021_REG2 |= (uint32_t)0b00 << 28; // normal + ADF7021_REG2 |= (uint32_t)(ADF7021_DEV_DEFAULT / div2) << 19; // deviation + ADF7021_REG2 |= (uint32_t)0b001 << 4; // modulation (GMSK) + break; + } + + // write registers + /* + ** VCO/Oscillator (Register 1) + */ + AD7021_CONTROL = ADF7021_REG1; + AD7021_1_IOCTL(); + + /* + ** Tx/Rx Clock (Register 3) + */ + AD7021_CONTROL = ADF7021_REG3; + AD7021_1_IOCTL(); + + /* + ** Demodulator Setup (Register 4) + */ + AD7021_CONTROL = ADF7021_REG4; + AD7021_1_IOCTL(); + + /* + ** IF Fine Cal Setup (Register 6) + */ + AD7021_CONTROL = ADF7021_REG6; + AD7021_1_IOCTL(); + + /* + ** IF Coarse Cal Setup (Register 5) + */ + AD7021_CONTROL = ADF7021_REG5; + AD7021_1_IOCTL(); + + // delay for filter calibration + delayIfCal(); + + /* + ** N Register (Frequency) (Register 0) + */ + setRX(); + + /* + ** Transmit Modulation (Register 2) + */ + AD7021_CONTROL = ADF7021_REG2; + AD7021_1_IOCTL(); + + /* + ** Test DAC (Register 14) + */ +#if defined(TEST_DAC) + AD7021_CONTROL = 0x0000001E; +#else + AD7021_CONTROL = 0x0000000E; +#endif + AD7021_1_IOCTL(); + + /* + ** AGC (Register 9) + */ +#if defined(AD7021_GAIN_AUTO) + AD7021_CONTROL = 0x000231E9; // AGC ON, normal operation +#elif defined(AD7021_GAIN_AUTO_LIN) + AD7021_CONTROL = 0x100231E9; // AGC ON, LNA high linearity +#elif defined(AD7021_GAIN_LOW) + AD7021_CONTROL = 0x120631E9; // AGC OFF, low gain, LNA high linearity +#elif defined(AD7021_GAIN_HIGH) + AD7021_CONTROL = 0x00A631E9; // AGC OFF, high gain +#endif + AD7021_1_IOCTL(); + + /* + ** AFC (Register 10) + */ + AD7021_CONTROL = ADF7021_REG10; + AD7021_1_IOCTL(); + + /* + ** Sync Word Detect (Register 11) + */ + AD7021_CONTROL = 0x0000003B; + AD7021_1_IOCTL(); + + /* + ** SWD/Threshold Setup (Register 12) + */ + AD7021_CONTROL = 0x0000010C; + AD7021_1_IOCTL(); + + /* + ** 3FSK/4FSK Demod (Register 13) + */ + AD7021_CONTROL = ADF7021_REG13; + AD7021_1_IOCTL(); + + /* + ** Test Mode (Register 15) + */ +#if defined(TEST_TX) + PTT(HIGH); + AD7021_CONTROL = ADF7021_TX_REG0; + AD7021_1_IOCTL(); + + AD7021_CONTROL = 0x000E010F; +#else + AD7021_CONTROL = 0x000E000F; +#endif + AD7021_1_IOCTL(); + +#if defined(DUPLEX) + // if duplex -- auto setup the second ADF7021 + if (m_duplex && (modemState != STATE_CW)) + rf2Conf(modemState); +#endif +} + +#if defined(DUPLEX) +/// +/// Sets the ADF7021 RF configuration. +/// +/// +/// +void IO::rf2Conf(DVM_STATE modemState) +{ + uint32_t ADF7021_REG2 = 0U; + uint32_t ADF7021_REG3 = 0U; + uint32_t ADF7021_REG4 = 0U; + uint32_t ADF7021_REG10 = 0U; + uint32_t ADF7021_REG13 = 0U; + + switch (modemState) { + case STATE_DMR: // 4FSK + // Dev: +1 symb 648 Hz, symb rate = 4800 + + /* + ** Tx/Rx Clock (Register 3) & AFC (Register 10) + */ + ADF7021_REG3 = ADF7021_REG3_DMR; + ADF7021_REG10 = ADF7021_REG10_DMR; + + /* + ** Demodulator Setup (Register 4) + */ + // K=32 + ADF7021_REG4 = (uint32_t)0b0100 << 0; // register 4 + ADF7021_REG4 |= (uint32_t)0b011 << 4; // mode, 4FSK + ADF7021_REG4 |= (uint32_t)0b0 << 7; // cross product + ADF7021_REG4 |= (uint32_t)0b11 << 8; // invert clk/data + ADF7021_REG4 |= (uint32_t)ADF7021_DISC_BW_DMR << 10; // discriminator BW + ADF7021_REG4 |= (uint32_t)ADF7021_POST_BW_DMR << 20; // post demod BW + ADF7021_REG4 |= (uint32_t)0b10 << 30; // IF filter (25 kHz) + + /* + ** 3FSK/4FSK Demod (Register 13) + */ + ADF7021_REG13 = (uint32_t)0b1101 << 0; // register 13 + ADF7021_REG13 |= (uint32_t)ADF7021_SLICER_TH_DMR << 4; // slicer threshold + + /* + ** Transmit Modulation (Register 2) + */ + ADF7021_REG2 = (uint32_t)0b0010; // register 2 + ADF7021_REG2 |= (uint32_t)m_rfPower << 13; // power level + ADF7021_REG2 |= (uint32_t)0b110001 << 7; // PA + ADF7021_REG2 |= (uint32_t)0b10 << 28; // invert data (and RC alpha = 0.5) + ADF7021_REG2 |= (uint32_t)(m_dmrDev / div2) << 19; // deviation +#if defined(ADF7021_DISABLE_RC_4FSK) + ADF7021_REG2 |= (uint32_t)0b011 << 4; // modulation (4FSK) +#else + ADF7021_REG2 |= (uint32_t)0b111 << 4; // modulation (RC 4FSK) +#endif + break; + + case STATE_P25: // 4FSK + // Dev: +1 symb 600 Hz, symb rate = 4800 + + /* + ** Tx/Rx Clock (Register 3) & AFC (Register 10) + */ + ADF7021_REG3 = ADF7021_REG3_P25; + ADF7021_REG10 = ADF7021_REG10_P25; + + /* + ** Demodulator Setup (Register 4) + */ + // K=32 + ADF7021_REG4 = (uint32_t)0b0100 << 0; // register 4 + ADF7021_REG4 |= (uint32_t)0b011 << 4; // mode, 4FSK + ADF7021_REG4 |= (uint32_t)0b0 << 7; // cross product + ADF7021_REG4 |= (uint32_t)0b11 << 8; // invert clk/data + ADF7021_REG4 |= (uint32_t)ADF7021_DISC_BW_P25 << 10; // discriminator BW + ADF7021_REG4 |= (uint32_t)ADF7021_POST_BW_P25 << 20; // post demod BW + ADF7021_REG4 |= (uint32_t)0b00 << 30; // IF filter (12.5 kHz) + + /* + ** 3FSK/4FSK Demod (Register 13) + */ + ADF7021_REG13 = (uint32_t)0b1101 << 0; // register 13 + ADF7021_REG13 |= (uint32_t)ADF7021_SLICER_TH_P25 << 4; // slicer threshold + + /* + ** Transmit Modulation (Register 2) + */ + ADF7021_REG2 = (uint32_t)0b0010; // register 2 + ADF7021_REG2 |= (uint32_t)m_rfPower << 13; // power level + ADF7021_REG2 |= (uint32_t)0b110001 << 7; // PA + ADF7021_REG2 |= (uint32_t)0b10 << 28; // invert data (and RC alpha = 0.5) + ADF7021_REG2 |= (uint32_t)(m_p25Dev / div2) << 19; // deviation +#if defined(ENABLE_P25_WIDE) || defined(ADF7021_DISABLE_RC_4FSK) + ADF7021_REG2 |= (uint32_t)0b011 << 4; // modulation (4FSK) +#else + ADF7021_REG2 |= (uint32_t)0b111 << 4; // modulation (RC 4FSK) +#endif + break; + default: // GMSK + // Dev: 1200 Hz, symb rate = 4800 + + /* + ** Tx/Rx Clock (Register 3) & AFC (Register 10) + */ + ADF7021_REG3 = ADF7021_REG3_DEFAULT; + ADF7021_REG10 = ADF7021_REG10_DEFAULT; + + /* + ** Demodulator Setup (Register 4) + */ + // K=32 + ADF7021_REG4 = (uint32_t)0b0100 << 0; // register 4 + ADF7021_REG4 |= (uint32_t)0b001 << 4; // mode, GMSK + ADF7021_REG4 |= (uint32_t)0b1 << 7; // dot product + ADF7021_REG4 |= (uint32_t)0b10 << 8; // invert data + ADF7021_REG4 |= (uint32_t)ADF7021_DISC_BW_DEFAULT << 10; // discriminator BW + ADF7021_REG4 |= (uint32_t)ADF7021_POST_BW_DEFAULT << 20; // post demod BW + ADF7021_REG4 |= (uint32_t)0b00 << 30; // IF filter (12.5 kHz) + + /* + ** 3FSK/4FSK Demod (Register 13) + */ + ADF7021_REG13 = (uint32_t)0b1101 << 0; // register 13 + ADF7021_REG13 |= (uint32_t)ADF7021_SLICER_TH_DEFAULT << 4; // slicer threshold + + /* + ** Transmit Modulation (Register 2) + */ + ADF7021_REG2 = (uint32_t)0b0010; // register 2 + ADF7021_REG2 |= (uint32_t)m_rfPower << 13; // power level + ADF7021_REG2 |= (uint32_t)0b110001 << 7; // PA + ADF7021_REG2 |= (uint32_t)0b00 << 28; // normal + ADF7021_REG2 |= (uint32_t)(ADF7021_DEV_DEFAULT / div2) << 19; // deviation + ADF7021_REG2 |= (uint32_t)0b001 << 4; // modulation (GMSK) + break; + } + + // write registers + /* + ** VCO/Oscillator (Register 1) + */ + AD7021_CONTROL = ADF7021_REG1; + AD7021_2_IOCTL(); + + /* + ** Tx/Rx Clock (Register 3) + */ + AD7021_CONTROL = ADF7021_REG3; + AD7021_2_IOCTL(); + + /* + ** Demodulator Setup (Register 4) + */ + AD7021_CONTROL = ADF7021_REG4; + AD7021_2_IOCTL(); + + /* + ** IF Fine Cal Setup (Register 6) + */ + AD7021_CONTROL = ADF7021_REG6; + AD7021_2_IOCTL(); + + /* + ** IF Coarse Cal Setup (Register 5) + */ + AD7021_CONTROL = ADF7021_REG5; + AD7021_2_IOCTL(); + + // delay for filter calibration + delayIfCal(); + + /* + ** N Register (Frequency) (Register 0) + */ + // set to RX only + AD7021_CONTROL = ADF7021_RX_REG0; + AD7021_2_IOCTL(); + + /* + ** Transmit Modulation (Register 2) + */ + AD7021_CONTROL = ADF7021_REG2; + AD7021_2_IOCTL(); + + /* + ** Test DAC (Register 14) + */ + AD7021_CONTROL = 0x0000000E; + AD7021_2_IOCTL(); + + /* + ** AGC (Register 9) + */ +#if defined(AD7021_GAIN_AUTO) + AD7021_CONTROL = 0x000231E9; // AGC ON, normal operation +#elif defined(AD7021_GAIN_AUTO_LIN) + AD7021_CONTROL = 0x100231E9; // AGC ON, LNA high linearity +#elif defined(AD7021_GAIN_LOW) + AD7021_CONTROL = 0x120631E9; // AGC OFF, low gain, LNA high linearity +#elif defined(AD7021_GAIN_HIGH) + AD7021_CONTROL = 0x00A631E9; // AGC OFF, high gain +#endif + AD7021_2_IOCTL(); + + /* + ** AFC (Register 10) + */ + AD7021_CONTROL = ADF7021_REG10; + AD7021_2_IOCTL(); + + /* + ** Sync Word Detect (Register 11) + */ + AD7021_CONTROL = 0x0000003B; + AD7021_2_IOCTL(); + + /* + ** SWD/Threshold Setup (Register 12) + */ + AD7021_CONTROL = 0x0000010C; + AD7021_2_IOCTL(); + + /* + ** 3FSK/4FSK Demod (Register 13) + */ + AD7021_CONTROL = ADF7021_REG13; + AD7021_2_IOCTL(); + + /* + ** Test Mode (Register 15) + */ + AD7021_CONTROL = 0x000E000F; + AD7021_2_IOCTL(); +} +#endif // DUPLEX + +/// +/// +/// +/// +/// +void IO::setDeviations(uint8_t dmrTXLevel, uint8_t p25TXLevel) +{ + m_dmrDev = uint16_t((ADF7021_DEV_DMR * uint16_t(dmrTXLevel)) / 128U); + m_p25Dev = uint16_t((ADF7021_DEV_P25 * uint16_t(p25TXLevel)) / 128U); +} + +/// +/// +/// +void IO::updateCal() +{ + uint32_t ADF7021_REG2; + float divider; + + /* + ** VCO/Oscillator (Register 1) + */ + configureBand(); + + AD7021_CONTROL = ADF7021_REG1; + AD7021_1_IOCTL(); + + /* + ** Transmit Modulation (Register 2) + */ + ADF7021_REG2 = (uint32_t)0b0010; // register 2 + ADF7021_REG2 |= (uint32_t)m_rfPower << 13; // power level + ADF7021_REG2 |= (uint32_t)0b110001 << 7; // PA + ADF7021_REG2 |= (uint32_t)0b10 << 28; // invert data (and RC alpha = 0.5) + if (m_modemState == STATE_DMR) { + ADF7021_REG2 |= (uint32_t)(m_dmrDev / div2) << 19; // DMR deviation + ADF7021_REG2 |= (uint32_t)0b111 << 4; // modulation (RC 4FSK) + } + + AD7021_CONTROL = ADF7021_REG2; + AD7021_1_IOCTL(); + + /* + ** Fractional-N Synthesizer (Register 0) + */ + + if (div2 == 1U) + divider = m_txFrequency / (ADF7021_PFD / 2U); + else + divider = m_txFrequency / ADF7021_PFD; + + // calculate Integer_N and Fractional_N divider values for Rx + TX_N_Divider = floor(divider); + divider = (divider - TX_N_Divider) * 32768; + TX_F_Divider = floor(divider + 0.5); + + // setup tx register 0 + ADF7021_TX_REG0 = (uint32_t)0b0000; // register 0 +#if defined(BIDIR_DATA_PIN) + ADF7021_TX_REG0 |= (uint32_t)0b01000 << 27; // mux regulator/transmit +#else + ADF7021_TX_REG0 |= (uint32_t)0b01010 << 27; // mux regulator/uart-spi enabled/transmit +#endif + ADF7021_TX_REG0 |= (uint32_t)TX_N_Divider << 19; // frequency - 15-bit Frac_N + ADF7021_TX_REG0 |= (uint32_t)TX_F_Divider << 4; // frequency - 8-bit Int_N + + if (m_tx) + setTX(); + else + setRX(); +} + +/// +/// +/// +/// +uint16_t IO::readRSSI() +{ + uint32_t AD7021_RB; + uint16_t RB_word = 0U; + uint8_t RB_code, gainCode, gainCorr; + + // Register 7, readback enable, ADC RSSI mode + AD7021_RB = 0x0147; + + // Send control register + for (int i = 8; i >= 0; i--) { + if (ADF_BIT_READ(AD7021_RB, i) == HIGH) + SDATA(HIGH); + else + SDATA(LOW); + + delayBit(); + SCLK(HIGH); + delayBit(); + SCLK(LOW); + } + + SDATA(LOW); + +#if defined(DUPLEX) + if (m_duplex || m_calState == STATE_RSSI_CAL) + SLE2(HIGH); + else + SLE1(HIGH); +#else + SLE1(HIGH); +#endif + + delayBit(); + + // Read SREAD pin + for (int i = 17; i >= 0; i--) { + SCLK(HIGH); + delayBit(); + + if ((i != 17) && (i != 0)) + RB_word |= ((SREAD() & 0x01) << (i - 1)); + + SCLK(LOW); + delayBit(); + } + +#if defined(DUPLEX) + if (m_duplex || m_calState == STATE_RSSI_CAL) + SLE2(LOW); + else + SLE1(LOW); +#else + SLE1(LOW); +#endif + + // Process RSSI code + RB_code = RB_word & 0x7f; + gainCode = (RB_word >> 7) & 0x0f; + + switch (gainCode) { + case 0b1010: + gainCorr = 0U; + break; + case 0b0110: + gainCorr = 24U; + break; + case 0b0101: + gainCorr = 38U; + break; + case 0b0100: + gainCorr = 58U; + break; + case 0b0000: + gainCorr = 86U; + break; + default: + gainCorr = 0U; + break; + } + + return (130 - (RB_code + gainCorr) / 2); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// +/// +void IO::configureBand() +{ + /* + ** VCO/Oscillator (Register 1) + */ + /** 136 - 174mhz */ + if ((m_txFrequency >= VHF_MIN) && (m_txFrequency < VHF_MAX)) { + ADF7021_REG1 = ADF7021_REG1_VHF; // VHF (80 - 200), external VCO + div2 = 1U; + } + + /** 216 - 225mhz */ + if ((m_txFrequency >= VHF_220_MIN) && (m_txFrequency < VHF_220_MAX)) { + ADF7021_REG1 = ADF7021_REG1_VHF_220; // VHF (200 - 450), external VCO + div2 = 1U; // should be 2U? + } + + /** 380 - 431mhz */ + if ((m_txFrequency >= UHF_380_MIN) && (m_txFrequency < UHF_380_MAX)) { + // NOTE: I've included support for this band, but, this could be problematic due to + // the external VCO control on most (if not all) hotspots + ADF7021_REG1 = ADF7021_REG1_UHF_380; // UHF (200 - 450), external VCO + div2 = 1U; + } + + /** 431 - 450mhz */ + if ((m_txFrequency >= UHF_1_MIN) && (m_txFrequency < UHF_1_MAX)) { + ADF7021_REG1 = ADF7021_REG1_UHF_1; // UHF (431 - 450), internal VCO + div2 = 1U; + } + + /** 450 - 470mhz */ + if ((m_txFrequency >= UHF_2_MIN) && (m_txFrequency < UHF_2_MAX)) { + ADF7021_REG1 = ADF7021_REG1_UHF_2; // UHF (450 - 470), internal VCO + div2 = 1U; + } + + /** 470 - 520mhz */ + if ((m_txFrequency >= UHF_T_MIN) && (m_txFrequency < UHF_T_MAX)) { + // NOTE: I've included support for this band, but, this could be problematic due to + // the external VCO control on most (if not all) hotspots + ADF7021_REG1 = ADF7021_REG1_UHF_T; // UHF (470 - 520), external VCO + div2 = 1U; + } + + /** 842 - 900mhz */ + if ((m_txFrequency >= UHF_800_MIN) && (m_txFrequency < UHF_800_MAX)) { + ADF7021_REG1 = ADF7021_REG1_800; // UHF (862 - 900), internal VCO + div2 = 2U; + } + + /** 900 - 950mhz */ + if ((m_txFrequency >= UHF_900_MIN) && (m_txFrequency < UHF_900_MAX)) { + ADF7021_REG1 = ADF7021_REG1_900; // UHF (900 - 950), internal VCO + div2 = 2U; + } + + if (div2 == 1U) + f_div = 2U; + else + f_div = 1U; +} + +/// +/// +/// +void IO::setTX() +{ + // PTT pin on (doing it earlier helps to measure timing impact) + setPTTInt(HIGH); + + // Send register 0 for TX operation, but do not activate yet. + // This is done in the interrupt at the correct time + AD7021_CONTROL = ADF7021_TX_REG0; + AD7021_1_IOCTL(false); + +#if defined(BIDIR_DATA_PIN) + setDataDirOut(true); // data pin output mode +#endif + + toTxRequest = true; + while(CLK()); +} + +/// +/// +/// +/// +void IO::setRX(bool doSle) +{ + // PTT pin off (doing it earlier helps to measure timing impact) + setPTTInt(LOW); + + // Send register 0 for RX operation, but do not activate yet. + // This is done in the interrupt at the correct time + AD7021_CONTROL = ADF7021_RX_REG0; + AD7021_1_IOCTL(doSle); + +#if defined(BIDIR_DATA_PIN) + setDataDirOut(false); // data pin output mode +#endif + + if (!doSle) { + toRxRequest = true; + while(toRxRequest) { + asm volatile ("nop"); + } + } +} + + +#if defined(ENABLE_DEBUG) + +uint32_t CIO::RXfreq() +{ + return (uint32_t)((float)(ADF7021_PFD / f_div) * ((float)((32768 * m_RX_N_divider) + m_RX_F_divider) / 32768.0)) + 100000; +} + +uint32_t CIO::TXfreq() +{ + return (uint32_t)((float)(ADF7021_PFD / f_div) * ((float)((32768 * m_TX_N_divider) + m_TX_F_divider) / 32768.0)); +} + +uint16_t CIO::devDMR() +{ + return (uint16_t)((ADF7021_PFD * m_dmrDev) / (f_div * 65536)); +} + +uint16_t CIO::devP25() +{ + return (uint16_t)((ADF7021_PFD * m_p25Dev) / (f_div * 65536)); +} + +void CIO::printConf() +{ + DEBUG1("MMDVM_HS FW configuration:"); + DEBUG2I("TX freq (Hz):", TXfreq()); + DEBUG2I("RX freq (Hz):", RXfreq()); + DEBUG2("Power set:", m_power); + DEBUG2("DMR +1 sym dev (Hz):", devDMR()); + DEBUG2("P25 +1 sym dev (Hz):", devP25()); +} + +#endif + +#endif // ENABLE_ADF7021 diff --git a/ADF7021.h b/ADF7021.h new file mode 100644 index 0000000..ad59047 --- /dev/null +++ b/ADF7021.h @@ -0,0 +1,347 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2020 by Jonathan Naylor G4KLX +* Copyright (C) 2016 by Jim McLaughlin KI6ZUM +* Copyright (C) 2016,2017,2018 by Andy Uribe CA6JAU +* Copyright (C) 2017 by Danilo DB4PLE +* Copyright (C) 2021 Bryan Biedenkapp N2PLL +* +* Some of the code is based on work of Guus Van Dooren PE1PLM: +* https://github.com/ki6zum/gmsk-dstar/blob/master/firmware/dvmega/dvmega.ino +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__ADF7021_H__) +#define __ADF7021_H__ + +#include "Defines.h" +#include "IO.h" + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define LOW 0 +#define HIGH 1 + +/** Band Tables */ +/** 136 - 174 mhz */ +#define VHF_MIN 136000000 +#define VHF_MAX 174000000 + +/** 216 - 225 mhz */ +#define VHF_220_MIN 216000000 +#define VHF_220_MAX 225000000 + +/** 380 - 431mhz */ +#define UHF_380_MIN 380000000 +#define UHF_380_MAX 431000000 + +/** 431 - 450mhz */ +#define UHF_1_MIN 431000000 +#define UHF_1_MAX 470000000 + +/** 450 - 470mhz */ +#define UHF_2_MIN 450000000 +#define UHF_2_MAX 470000000 + +/** 470 - 520mhz (T-band) */ +#define UHF_T_MIN 470000000 +#define UHF_T_MAX 520000000 + +/** 842 - 900mhz */ +#define UHF_800_MIN 842000000 +#define UHF_800_MAX 900000000 + +/** 900 - 950mhz */ +#define UHF_900_MIN 900000000 +#define UHF_900_MAX 950000000 + +#if defined(ENABLE_ADF7021) + +#define ADF_BIT_READ(value, bit) (((value) >> (bit)) & 0x01) + +#if defined(ADF7021_DISABLE_RC_4FSK) +#define ADF7021_EVEN_BIT true +#else +#define ADF7021_EVEN_BIT false +#endif // ADF7021_DISABLE_RC_4FSK + +/* + - Most of the registers values are obteined from ADI eval software: + http://www.analog.com/en/products/rf-microwave/integrated-transceivers-transmitters-receivers/low-power-rf-transceivers/adf7021.html + - or ADF7021 datasheet formulas: + www.analog.com/media/en/technical-documentation/data-sheets/ADF7021.pdf +*/ + +/** Test modes */ + +// Enable SWD pin to access the demodulator output signal: +// (See application note AN-852 and ADF7021 datasheet, page 60) +// #define TEST_DAC + +// Transmit the carrier frequency: +// #define TEST_TX + +/*********************/ + +// Disable TX Raised Cosine filter for 4FSK modulation in ADF7021: +// #define ADF7021_DISABLE_RC_4FSK + +// Support for ADF7021-N version: +// #define ADF7021_N_VER + +// Enable AFC support for DMR, YSF, P25, and M17 (experimental): +// (AFC is already enabled by default in D-Star) +// #define ADF7021_ENABLE_4FSK_AFC + +// Configure AFC with positive initial frequency offset: +// #define ADF7021_AFC_POS + +/** Support for 14.7456 MHz TCXO (modified RF7021SE boards) */ +#if defined(ADF7021_14_7456) + +// R = 4 +// DEMOD_CLK = 2.4576 MHz (DEFAULT +// DEMOD_CLK = 4.9152 MHz (DMR, P25) +#define ADF7021_PFD 3686400.0 + +/* +** VCO/Oscillator (Register 1) +*/ +#define ADF7021_REG1_VHF 0x02175041 /** 136 - 174mhz */ +#define ADF7021_REG1_VHF_220 0x021B5041 /** 219 - 225mhz */ +#define ADF7021_REG1_UHF_380 0x021B5041 /** 380 - 431mhz */ // this could be problematic due to + // the external VCO control +#define ADF7021_REG1_UHF_1 0x00575041 /** 431 - 450mhz */ +#define ADF7021_REG1_UHF_2 0x01D75041 /** 450 - 470mhz */ +#define ADF7021_REG1_UHF_T 0x02235041 /** 470 - 520mhz */ // this could be problematic due to + // the external VCO control +#define ADF7021_REG1_800 0x00535041 /** 842 - 900mhz */ +#define ADF7021_REG1_900 0x01D35041 /** 900 - 950mhz */ + +/* +** Transmit Modulation (Register 2) +*/ +#define ADF7021_DEV_DEFAULT 43U +#define ADF7021_DEV_DMR 23U + +#if defined(ENABLE_P25_WIDE) +#define ADF7021_DEV_P25 32U +#else +#define ADF7021_DEV_P25 22U +#endif // ENABLE_P25_WIDE + +/* +** Tx/Rx Clock (Register 3) +*/ +#define ADF7021_REG3_DEFAULT 0x2A4C4193 +#if defined(TEST_DAC) +#define ADF7021_REG3_DMR 0x2A4C04D3 +#define ADF7021_REG3_P25 0x2A4C04D3 +#else +#define ADF7021_REG3_DMR 0x2A4C80D3 +#define ADF7021_REG3_P25 0x2A4C80D3 +#endif // TEST_DAC + +/* +** Demodulator Setup (Register 4) +*/ +// Discriminator bandwith, demodulator +// Bug in ADI evaluation software, use datasheet formula (4FSK) +#define ADF7021_DISC_BW_DEFAULT 522U // K=85 +#define ADF7021_DISC_BW_DMR 393U // K=32 +#define ADF7021_DISC_BW_P25 394U // K=32 + +// Post demodulator bandwith +#define ADF7021_POST_BW_DEFAULT 10U +#define ADF7021_POST_BW_DMR 80U +#define ADF7021_POST_BW_P25 6U + +/* +** IF Coarse Cal Setup (Register 5) +*/ +#define ADF7021_REG5 0x000024F5 + +/* +** IF Fine Cal Setup (Register 6) +*/ +#define ADF7021_REG6 0x05070E16 + +/* +** AFC (Register 10) +*/ +#define ADF7021_REG10_DEFAULT 0x0C96473A + +#if defined(ADF7021_ENABLE_4FSK_AFC) + +#define ADF7021_REG10_DMR 0x01FE473A +#define ADF7021_REG10_P25 0x01FE473A + +#if defined(ADF7021_AFC_POS) + +#define AFC_OFFSET_DMR -250 +#define AFC_OFFSET_P25 -250 + +#else + +#define AFC_OFFSET_DMR 250 +#define AFC_OFFSET_P25 250 + +#endif // ADF7021_AFC_POS + +#else + +#define ADF7021_REG10_DMR 0x049E472A +#define ADF7021_REG10_P25 0x049E472A +#define AFC_OFFSET_DMR 0 +#define AFC_OFFSET_P25 0 + +#endif // ADF7021_ENABLE_4FSK_AFC + +/** Support for 12.2880 MHz TCXO */ +#elif defined(ADF7021_12_2880) + +// R = 2 +// DEMOD_CLK = 2.4576 MHz (DEFAULT) +// DEMOD_CLK = 6.1440 MHz (DMR, P25) +#define ADF7021_PFD 6144000.0 + +/* +** VCO/Oscillator (Register 1) +*/ +#define ADF7021_REG1_VHF 0x02175021 /** 136 - 174mhz */ +#define ADF7021_REG1_VHF_220 0x021B5021 /** 219 - 225mhz */ +#define ADF7021_REG1_UHF_380 0x021B5021 /** 380 - 431mhz */ // this could be problematic due to +// the external VCO control +#define ADF7021_REG1_UHF_1 0x00575021 /** 431 - 450mhz */ +#define ADF7021_REG1_UHF_2 0x01D75021 /** 450 - 470mhz */ +#define ADF7021_REG1_UHF_T 0x02235021 /** 470 - 520mhz */ // this could be problematic due to + // the external VCO control +#define ADF7021_REG1_800 0x00535021 /** 842 - 900mhz */ +#define ADF7021_REG1_900 0x01D35021 /** 900 - 950mhz */ + +/* +** Transmit Modulation (Register 2) +*/ +#define ADF7021_DEV_DEFAULT 26U +#define ADF7021_DEV_DMR 14U + +#if defined(ENABLE_P25_WIDE) +#define ADF7021_DEV_P25 19U +#else +#define ADF7021_DEV_P25 13U +#endif // ENABLE_P25_WIDE + +/* +** Tx/Rx Clock (Register 3) +*/ +#define ADF7021_REG3_DEFAULT 0x29EC4153 +#if defined(TEST_DAC) +#define ADF7021_REG3_DMR 0x29EC0493 +#define ADF7021_REG3_P25 0x29EC0493 +#else +#define ADF7021_REG3_DMR 0x29ECA093 +#define ADF7021_REG3_P25 0x29ECA093 +#endif // TEST_DAC + +/* +** Demodulator Setup (Register 4) +*/ +// Discriminator bandwith, demodulator +// Bug in ADI evaluation software, use datasheet formula (4FSK) +#define ADF7021_DISC_BW_DEFAULT 522U // K=85 +#define ADF7021_DISC_BW_DMR 491U // K=32 +#define ADF7021_DISC_BW_P25 493U // K=32 + +// Post demodulator bandwith +#define ADF7021_POST_BW_DEFAULT 10U +#define ADF7021_POST_BW_DMR 80U +#define ADF7021_POST_BW_P25 6U + +/* +** IF Coarse Cal Setup (Register 5) +*/ +#define ADF7021_REG5 0x00001ED5 + +/* +** IF Fine Cal Setup (Register 6) +*/ +#define ADF7021_REG6 0x0505EBB6 + +/* +** AFC (Register 10) +*/ +#define ADF7021_REG10_DEFAULT 0x0C96557A + +#if defined(ADF7021_ENABLE_4FSK_AFC) + +#define ADF7021_REG10_DMR 0x01FE557A +#define ADF7021_REG10_P25 0x01FE557A + +#if defined(ADF7021_AFC_POS) + +#define AFC_OFFSET_DMR -250 +#define AFC_OFFSET_P25 -250 + +#else + +#define AFC_OFFSET_DMR 250 +#define AFC_OFFSET_P25 250 + +#endif // ADF7021_AFC_POS + +#else + +#define ADF7021_REG10_DMR 0x049E556A +#define ADF7021_REG10_P25 0x049E556A +#define AFC_OFFSET_DMR 0 +#define AFC_OFFSET_P25 0 + +#endif // ADF7021_ENABLE_4FSK_AFC + +#endif // ADF7021_12_2880 + +/* +** 3FSK/4FSK Demod (Register 13) +*/ +// Slicer threshold for 4FSK demodulator +#define ADF7021_SLICER_TH_DEFAULT 0U + +#if defined(ADF7021_N_VER) + +#define ADF7021_SLICER_TH_DMR 51U +#define ADF7021_SLICER_TH_P25 43U + +#else + +#define ADF7021_SLICER_TH_DMR 57U +#define ADF7021_SLICER_TH_P25 47U + +#endif // ADF7021_N_VER + +#endif // ENABLE_ADF7021 +#endif // __ADF7021_H__ diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 0000000..8a6b265 --- /dev/null +++ b/BUILD.md @@ -0,0 +1,739 @@ +# Building instructions + +This is a detailed guide for building the firmware of MMDVM_HS from the source code. For quick instructions, please see [README.md](README.md). This software runs on STM32F103 microcontroller. Also, Arduino with 3.3 V I/O (Arduino Due and Zero) and Teensy (3.1, 3.2, 3.5 or 3.6) are supported. You can build this code using Arduino IDE with Roger Clark's [STM32duino](https://github.com/rogerclarkmelbourne/Arduino_STM32/tree/ZUMspot) package, or using command line tools with ARM GCC tools. The preferred method under Windows is using STM32duino, and under Linux or macOS (command line) is using [STM32F10X_Lib](https://github.com/juribeparada/STM32F10X_Lib). + +# Index + +- ZUMspot RPi +- ZUMspot Libre Kit +- ZUMspot USB +- MMDVM_HS_Hat +- Makefile options +- Config.h options +- Pinout definitions +- Hidden options + +# ZUMspot RPi + +Download latest Raspbian image and install to a micro SD + +* See: https://www.raspberrypi.org/documentation/installation/installing-images/ +* Configure your SD before booting. Useful article: https://styxit.com/2017/03/14/headless-raspberry-setup.html + +Boot your Raspberry Pi. Run raspi-config and configure according to your preferences: + + sudo raspi-config + +Select at least: + +* Expand filesystem +* Change default password +* Enable "Wait for Network at Boot" +* Disable Desktop GUI if you don't plan to use it + +### Enable serial port in Raspberry Pi 3 or Pi Zero W + +Edit /boot/cmdline.txt: + + sudo nano /boot/cmdline.txt + (remove the text: console=serial0,115200) + +Disable services: + + sudo systemctl disable serial-getty@ttyAMA0.service + sudo systemctl disable bluetooth.service + +Edit /boot/config.txt + + sudo nano /boot/config.txt + +and add the following lines at the end of /boot/config.txt: + + enable_uart=1 + dtoverlay=pi3-disable-bt + +Reboot your RPi: + + sudo reboot + +### Enable serial port in Raspberry Pi 2 + +Edit /boot/cmdline.txt: + + sudo nano /boot/cmdline.txt + (remove the text: console=serial0,115200) + +Disable the service: + + sudo systemctl disable serial-getty@ttyAMA0.service + +Reboot your RPi: + + sudo reboot + +### Build the firmware and upload to ZUMspot RPi + +If you are using Pi-Star, expand filesystem (if you haven't done before): + + sudo pistar-expand + sudo reboot + +Enable RW filesystem if you are using Pi-Star: + + rpi-rw + +Install the necessary software tools: + + sudo apt-get update + sudo apt-get install gcc-arm-none-eabi gdb-arm-none-eabi libstdc++-arm-none-eabi-newlib libnewlib-arm-none-eabi + + cd ~ + git clone https://git.code.sf.net/p/stm32flash/code stm32flash + cd stm32flash + make + sudo make install + +Download the firmware sources: + + cd ~ + git clone https://github.com/juribeparada/MMDVM_HS + cd MMDVM_HS/ + git submodule init + git submodule update + +(Please do not download any different code inside MMDVM_HS folder) + +Edit Config.h + + nano Config.h + +and enable: + + #define ZUMSPOT_ADF7021 + #define ENABLE_ADF7021 + #define ADF7021_14_7456 + #define STM32_USART1_HOST + #define ENABLE_SCAN_MODE + +Build the firmware: + + make + +If you are using Pi-Star, stop services: + + sudo pistar-watchdog.service stop + sudo systemctl stop mmdvmhost.timer + sudo systemctl stop mmdvmhost.service + +Upload the firmware to ZUMspot RPi board: + + sudo make zumspot-pi + +Install MMDVMHost: + + cd ~ + git clone https://github.com/g4klx/MMDVMHost/ + cd MMDVMHost/ + make + +Edit MMDVM.ini according your preferences + + nano MMDVM.ini + (use Port=/dev/ttyAMA0 in [Modem]) + +Execute MMDVMHost: + + ./MMDVMHost MMDVM.ini + +# ZUMspot Libre Kit + +## Windows with Arduino IDE + +Download and install the Arduino IDE: + + https://www.arduino.cc/en/Main/Software + +Run Arduino IDE. On the Tools menu, select the Boards manager, and install the "Arduino SAM" from the list of available boards. + +Download STM32duino (Arduino for STM32) from this URL: + + https://github.com/rogerclarkmelbourne/Arduino_STM32/tree/ZUMspot + +Unzip and change the extracted folder name "Arduino_STM32-ZUMspot" to "Arduino_STM32" + +Copy Arduino_STM32 folder in: + + My Documents/Arduino/hardware + +Install the USB bootloader to STM32F103. Follow the instructions: + + https://github.com/rogerclarkmelbourne/Arduino_STM32/wiki/stm32duino-bootloader + +Connect the ZUMspot Libre Kit to your PC. Install the USB Mapple driver using the bat file (you may also check http://wiki.stm32duino.com/index.php?title=Windows_driver_installation): + + My Documents/Arduino/hardware/Arduino_STM32/drivers/win/install_drivers.bat + +You have to be sure that Windows detect your ZUMspot as an USB serial device COMx (please see Windows Device Manager). + +Download the source (zip file) of MMDVM_HS from: + + https://github.com/juribeparada/MMDVM_HS + +Do not download or install the STM32F103 library (STM32F10X_Lib) this is not necessary under STM32duino. + +Unzip MMDVM_HS-master.zip and change the folder name to "MMDVM_HS". The path name to this folder can't have spaces. + +Start the Arduino IDE. Open the MMDVM_HS.ino file in the MMDVM_HS folder. + +Under the menu "Tools" select "Board" and then select: + + Board: Generic STM32F103C Series + Variant: STM32F103C8 (20k RAM, 64k Flash) + CPU Speed: 72 MHz (Normal) + Upload method: STM32duino bootloader (you have transfered the USB bootloader before) + Serial port: COMx (Maple Mini) + +Edit Config.h: + + #define LIBRE_KIT_ADF7021 + #define ENABLE_ADF7021 + #define ADF7021_14_7456 + #define STM32_USB_HOST + #define ENABLE_SCAN_MODE + +Click the Upload button in the IDE and wait for the transfer. + +Once the transfer is completed, press the RESET button of the board or disconnect and connect the USB cable. You will see the LED (PC13) of the blue pill blinking. Once you connect with MMDVMHost, the LED will blink fast. + +For further help with STM32duino and STM32F103 blue pill boards, please see the STM32duino [forum](http://www.stm32duino.com). + +## Windows with command line + +Download the source code (zip file) of MMDVM_HS from: + + https://github.com/juribeparada/MMDVM_HS + +Unzip MMDVM_HS-master.zip and change the folder name to "MMDVM_HS". The path name to this folder can't have spaces. + +Download the ST libraries STM32F10X_Lib-master.zip from: + + https://github.com/juribeparada/STM32F10X_Lib/ + +Extract the STM32F10X_Lib-master folder into the same folder as the MMDVM_HS. Change the folder name to "STM32F10X_Lib". + +Download the GNU make utility: + + http://gnuwin32.sourceforge.net/packages/make.htm + +Download the binaries zip file and extract make.exe and put it in the same directory MMDVM_HS. Download the dependencies zip file and extract libintl3.dll and libiconv2.dll and put them in the same directory MMDVM_HS. + +Download the GNU ARM embedded toolchain from here: + + https://launchpad.net/gcc-arm-embedded/+download + +Currently the direct link to the installer is here: + https://launchpad.net/gcc-arm-embedded/5.0/5-2016-q3-update/+download/gcc-arm-none-eabi-5_4-2016q3-20160926-win32.exe + +Download STM32duino (Arduino for STM32) from this URL (only for USB drivers): + + https://github.com/rogerclarkmelbourne/Arduino_STM32/tree/ZUMspot + +Unzip and copy Arduino_STM32-ZUMspot folder in (for example): + + C:\Arduino_STM32-ZUMspot + +Connect the ZUMspot Libre Kit to your PC. Install the USB Mapple driver using the bat file (you may also check http://wiki.stm32duino.com/index.php?title=Windows_driver_installation): + + C:\Arduino_STM32-ZUMspot\drivers\win\install_drivers.bat + +Once the USB driver is installed, you may delete "C:\Arduino_STM32-ZUMspot" folder. + +Launch the "GCC Command Prompt" from "GNU Tools for ARM Embedded Processors" (Start Menu) and +cd to the folder where you put the MMDVM_HS folder. + +Edit Config.h according your preferences. The default Config.h is OK for ZUMSpot Libre Kit. + +Build the firmware: + + make clean + make bl + +Press the reset button of ZUMspot and upload the firmware: + + make dfu + +## Linux Raspbian + +If you are using Pi-Star, expand filesystem (if you haven't done before): + + sudo pistar-expand + sudo reboot + +Enable RW filesystem if you are using Pi-Star: + + rpi-rw + +Install the necessary software tools: + + sudo apt-get update + sudo apt-get install gcc-arm-none-eabi gdb-arm-none-eabi libstdc++-arm-none-eabi-newlib libnewlib-arm-none-eabi + +Download the sources: + + cd ~ + git clone https://github.com/juribeparada/MMDVM_HS + cd MMDVM_HS/ + git submodule init + git submodule update + +(Please do not download any different code inside MMDVM_HS folder) + +Edit Config.h: + + nano Config.h + +and enable: + + #define LIBRE_KIT_ADF7021 + #define ENABLE_ADF7021 + #define ADF7021_14_7456 + #define STM32_USB_HOST + #define ENABLE_SCAN_MODE + +Build the firmware with bootloader support: + + make bl + +If you are using Pi-Star, stop services: + + sudo pistar-watchdog.service stop + sudo systemctl stop mmdvmhost.timer + sudo systemctl stop mmdvmhost.service + +Upload bootloader and firmware to ZUMspot Libre Kit, using serial port first (you are using an USB-serial converter with device name /dev/ttyUSB0). Move BOOT0 jumper to 1, next press and release RESET and execute: + + sudo make serial-bl devser=/dev/ttyUSB0 + +Move BOOT0 jumper to 0. + +For following firmware updates, you could use the USB port directly: + + sudo make dfu devser=/dev/ttyACM0 + +Install MMDVMHost: + + cd ~ + git clone https://github.com/g4klx/MMDVMHost/ + cd MMDVMHost/ + make + +Edit MMDVM.ini according your preferences + + nano MMDVM.ini + (use Port=/dev/ttyACM0 in [Modem]) + +Execute MMDVMHost: + + ./MMDVMHost MMDVM.ini + +# ZUMspot USB + +## Windows + +Download and install the Arduino IDE: + + https://www.arduino.cc/en/Main/Software + +Run Arduino IDE. On the Tools menu, select the Boards manager, and install the "Arduino SAM" from the list of available boards. + +Download STM32duino (Arduino for STM32) from this URL: + + https://github.com/rogerclarkmelbourne/Arduino_STM32/tree/ZUMspot + +Unzip and change the extracted folder name "Arduino_STM32-ZUMspot" to "Arduino_STM32" + +Copy Arduino_STM32 folder in: + + My Documents/Arduino/hardware + +Connect the ZUMspot USB to your PC. Install the USB Mapple driver using the bat file: + + My Documents/Arduino/hardware/Arduino_STM32/drivers/win/install_drivers.bat + (you may also check: http://wiki.stm32duino.com/index.php?title=Windows_driver_installation) + +You have to be sure that Windows detect your ZUMspot as an USB serial device COMx (please +see Windows Device Manager) + +Download the source (zip file) of MMDVM_HS from: + + https://github.com/juribeparada/MMDVM_HS + +Do not download or install the STM32F103 library, STM32F10X_Lib, this is not necessary +under STM32duino. + +Unzip MMDVM_HS-master.zip and change the folder name to "MMDVM_HS". The path name to this +folder can't have spaces. + +Start the Arduino IDE. Open the MMDVM_HS.ino file in the MMDVM_HS folder. + +Under the menu "Tools" select "Board" and then select: + + Board: Generic STM32F103C Series + Variant: STM32F103C8 (20k RAM, 64k Flash) + CPU Speed: 72 MHz (Normal) + Upload method: STM32duino bootloader (you have transfered the USB bootloader before) + Serial port: COMx (Maple Mini) + +Edit Config.h: + + #define ZUMSPOT_ADF7021 + #define ENABLE_ADF7021 + #define ADF7021_14_7456 + #define STM32_USB_HOST + #define ENABLE_SCAN_MODE + +Click the Upload button in the IDE and wait for the transfer. + +Once the transfer is completed, press the RESET button of the board or disconnect and +connect the USB cable. + +## Linux Raspbian + +If you are using Pi-Star, expand filesystem (if you haven't done before): + + sudo pistar-expand + sudo reboot + +Enable RW filesystem if you are using Pi-Star: + + rpi-rw + +Install the necessary software tools: + + sudo apt-get update + sudo apt-get install gcc-arm-none-eabi gdb-arm-none-eabi libstdc++-arm-none-eabi-newlib libnewlib-arm-none-eabi + +Download the sources: + + cd ~ + git clone https://github.com/juribeparada/MMDVM_HS + cd MMDVM_HS/ + git submodule init + git submodule update + +(Please do not download any different code inside MMDVM_HS folder) + +Edit Config.h + + nano Config.h + +and enable: + + #define ZUMSPOT_ADF7021 + #define ENABLE_ADF7021 + #define ADF7021_14_7456 + #define STM32_USB_HOST + #define ENABLE_SCAN_MODE + +Build the firmware with bootloader support: + + make bl + +If you are using Pi-Star, stop services: + + sudo pistar-watchdog.service stop + sudo systemctl stop mmdvmhost.timer + sudo systemctl stop mmdvmhost.service + +Upload the firmware to ZUMspot USB: + + sudo make dfu devser=/dev/ttyACM0 + +Install MMDVMHost: + + cd ~ + git clone https://github.com/g4klx/MMDVMHost/ + cd MMDVMHost/ + make + +Edit MMDVM.ini according your preferences + + nano MMDVM.ini + (use Port=/dev/ttyACM0 in [Modem]) + +Execute MMDVMHost: + + ./MMDVMHost MMDVM.ini + +# MMDVM_HS_Hat + +Please check here for detailed instructions: + + https://github.com/mathisschmieder/MMDVM_HS_Hat/blob/master/README.md + +# Makefile options + +- make clean: delete all objects files *.o, for starting a new firmware building. + +- make: it builds a standard firmware (without USB bootloader support). + +- make bl: it builds a firmware with USB bootloader support. + +- make zumspot-pi: upload the firmware to a ZUMspot RPi version (using internal RPi serial port) + +- make mmdvm_hs_hat: upload the firmware to MMDVM_HS_Hat board (using internal RPi serial port) + +- make nano-hotspot: upload the firmware to Nano hotSPOT board (using internal serial port) + +- make nano-dv: upload the firmware to NanoDV board (using internal serial port) + +- make d2rg_mmdvm_hs: upload the firmware to D2RG MMDVM_HS board (using internal serial port) + +- make skybridge: upload the firmware to SkyBridge HotSpot board (using internal serial port) + +- make dfu [devser=/dev/ttyXXX]: upload firmware using USB bootloader. "devser" is optional, and it corresponds to the USB serial port device name. This option permits to perform a reset to enter to booloader mode (DFU). If you don't use "devser", you have to press the reset button of the ZUMspot just before using this command. + +- make serial devser=/dev/ttyXXX: upload standard firmware using serial port bootloader method. + +- make serial-bl devser=/dev/ttyXXX: upload firmware with USB bootloader support using serial port bootloader method. + +- make serial-nobl devser=/dev/ttyXXX: upload firmware with USB support using serial port bootloader method, but without USB bootloader installation. + +- make serial-bl-old devser=/dev/ttyXXX: same as "make serial-bl" but using bootloader with short reset pulse. + +- make stlink: upload standard firmware using ST-Link interface. + +- make stlink-bl: upload firmware with USB bootloader support using ST-Link interface. + +- make stlink-nobl: upload firmware with USB support using ST-Link interface, but without USB bootloader. + +- make stlink-bl-old: same as "make stlink-bl" but using bootloader with short reset pulse. + +- make ocd: upload standard firmware using ST-Link interface. This method uses a local openocd installation. + +- make ocd-nobl: upload firmware with USB support using ST-Link interface, but without USB bootloader. This method uses a local openocd installation. + +- make ocd-bl: upload firmware with USB bootloader support using ST-Link interface. This method uses a local openocd installation. + +- make ocd-bl-old: same as "make ocd-bl" but using bootloader with short reset pulse. + +## Common Makefile commands + +Serial programming (first programming, transfer the USB bootloader): + + make clean + make bl + sudo make serial-bl devser=/dev/ttyUSB0 + +USB programming (you have already transfered the USB bootloader): + + make clean + make bl + sudo make dfu (reset ZUMspot before) or + sudo make dfu devser=/dev/ttyACM0 (/dev/ttyACM0 is the device name of ZUMspot USB under Raspbian) + +ZUMspot RPi (no USB support needed): + + make clean + make + sudo make zumspot-pi + +# Config.h options + +- #define ZUMSPOT_ADF7021: enable pinouts support for ZUMspot RPi or ZUMspot USB. You have to enable this option if you have one of these products. + +- #define LIBRE_KIT_ADF7021: enable this option if you have a ZUMspot Libre Kit (Board with modified RF7021SE and Blue Pill STM32F103). + +- #define MMDVM_HS_HAT_REV12: enable this option if you have a MMDVM_HS_Hat board for RPi. + +- #define MMDVM_HS_DUAL_HAT_REV10: enable this option if you have a MMDVM_HS_Dual_Hat board for RPi/USB. + +- #define NANO_HOTSPOT: enable this option if you have a Nano hotSPOT (BI7JTA). + +- #define NANO_DV_REV10: enable this option if you have a Nano DV (BG4TGO & BG5HHP). + +- #define ENABLE_ADF7021: add support for ADF7021 (all boards, enabled by default). + +- #define DUPLEX: enable duplex mode with dual ADF7021. It is still under development. + +- #define ADF7021_14_7456: select this option if your board uses a 14.7456 MHz (enabled by default). + +- #define ADF7021_12_2880: select this option if your board uses a 12.2880 MHz. + +- #define STM32_USART1_HOST: enable direct serial host communication with ZUMspot (using USART1 PA9 and PA10 pins). Disable STM32_USB_HOST if you enable this option. Enable this if you have a ZUMspot RPi. You don't need to enable this option if you will transfer the bootloader. + +- #define STM32_USB_HOST: enable USB host communication with ZUMspot (using STM32F103 USB interface). Disable STM32_USART1_HOST if you enable this option. Enable this if you have a ZUMspot USB or ZUMspot Libre Kit. + +- #define ENABLE_SCAN_MODE: enable automatic mode detection in ZUMspot. This is based on scanning over all enabled modes, and you could have some detection delay. Enabled by default. + +- #define SEND_RSSI_DATA: enable RSSI reports to MMDVMHost. It is already converted to dBm. + +- #define SERIAL_REPEATER: enable a second serial port (USART2, pins PA2 and PA3) for Nextion LCD display. + +- #define SERIAL_REPEATER_USART1: enable USART1 (pins PA9 and PA10) for Nextion LCD display. Do not use with STM32_USART1_HOST enabled, only with USB host communication. + +- #define ENABLE_P25_WIDE: enable support for Motorola Wide P25 mod/demod in XTS3000 radios. Using this mode improves RX BER. You need to enable this mode in your radio for each conventional personalities. + +- #define QUIET_MODE_LEDS: disable mode LEDs blink during scan mode. + +# Pinout definitions + +## Pinout definitions for ZUMspot Libre Kit + +This is the carrier board or any board with RF7021SE + STM32F103. + +Main RF7021SE board: + + CE PC14 + SLE PB8 + SREAD PB7 + SDATA PB6 + SCLK PB5 + DATA PB4 (TxRxData)* + DCLK PB3 (TxRxCLK)* + CLKOUT PA15 (jumper wire in RF7021SE, not needed with BIDIR_DATA_PIN enabled) + PAC PB14 (PTT LED) + VCC 3.3 V + GND Ground + +Second RF7021SE board (duplex mode, experimental): + + SLE PA6 + DATA PA4 (TxRxData)* + DCLK PA5 (TxRxCLK)* + PAC NC + CLKOUT NC + VCC 3.3 V + GND Ground + + SDATA, SREAD, SCLK and CE are shared with the main ADF7021. + +Serial ports: + + TXD PA9 (serial port host communication) + RXD PA10 (serial port host communication) + DISP_TXD PA2 (Nextion LCD serial repeater) + DISP_RXD PA3 (Nextion LCD serial repeater) + +Status LEDs: + + COS_LED PB15 + PTT_LED PB14 + NXDN_LED PA8 + P25_LED PB0 + YSF_LED PB1 + DMR_LED PB13 + DSTAR_LED PB12 + +Misc pins: + + PIN_LED PC13 (status led) + PIN_DEB PB9 (debugging pin) + +You could install a serie resistor (10 - 100 ohms) in each TxRxData and TxRxCLK lines, for reducing EMI. + +## Pinout definitions for Arduino Due/Zero + RF7021SE + +Use Arduino IDE with SAM support for building the code. + +Main RF7021SE board: + + CE 12 + SLE 6 + SREAD 5 + SDATA 4 // 2 in Arduino Zero Pro + SCLK 3 + DATA 7 (TxRxData)* + DCLK 8 (TxRxCLK)* + CLKOUT 2 // 4 in Arduino Zero Pro (jumper wire in RF7021SE, not needed with BIDIR_DATA_PIN enabled) + PAC 9 (PTT LED) + VCC 3.3 V + GND Ground + +Serial ports: + + USB Arduino Programming Port (host communication) + +Status LEDs: + + COS_LED 10 + PTT_LED 9 + NXDN_LED 18 + P25_LED 17 + YSF_LED 16 + DMR_LED 15 + DSTAR_LED 14 + +Misc pins: + + PIN_LED 13 + PIN_DEB 11 + +You could install a serie resistor (10 - 100 ohms) in each TxRxData and TxRxCLK lines, for reducing EMI. + +## Pinout definitions for Teensy (3.1, 3.2, 3.5 or 3.6) + RF7021SE: + +Use Teensyduino + Arduino IDE for building the code. + +Main RF7021SE board: + + CE 6 + SLE 5 + SREAD 4 + SDATA 3 + SCLK 2 + DATA 7 (TxRxData)* + DCLK 8 (TxRxCLK)* + CLKOUT 22 (jumper wire in RF7021SE, not needed with BIDIR_DATA_PIN enabled) + PAC 14 (PTT LED) + VCC 3.3 V + GND Ground + +Serial ports: + + Teensy USB Port (host communication) + DISP_TXD 1 (Nextion LCD serial repeater) + DISP_RXD 0 (Nextion LCD serial repeater) + +Status LEDs: + + COS_LED 15 + PTT_LED 14 + NXDN_LED 20 + P25_LED 19 + YSF_LED 18 + DMR_LED 17 + DSTAR_LED 16 + +Misc pins: + + PIN_LED 13 + PIN_DEB 23 + +You could install a serie resistor (10 - 100 ohms) in each TxRxData and TxRxCLK lines, for reducing EMI. + +# Hidden functions + +You could enable two test modes, if you edit ADF7021.h file before compilation. Please always comment these two #defines for normal operation. + +- #define TEST_DAC: Enable SWD pin to access the demodulator output signal. See application note AN-852 and ADF7021 datasheet, page 60. + +- #define TEST_TX: Transmit the carrier frequency. This works only with D-Star mode enabled in MMDVM.ini. This test mode will transmit the carrier frequency defined with TXFrequency in MMDVM.ini. This could be useful to determine the frequency offset of your ZUMspot (with test equipment). + +Also in ADF7021.h: + +- #define ADF7021_N_VER: enable support for narrow band version of ADF7021 (ADF7021N). Disabled by default, in general all boards will have just ADF7021. + +- #define ADF7021_ENABLE_4FSK_AFC: enable AFC support for DMR, YSF and P25. This is experimental, depending on your frequency offset this option will improve or not your BER reception. + +- #define ADF7021_AFC_POS: enable this option if you can not receive any signal after enable the ADF7021_ENABLE_4FSK_AFC option. + +- #define ADF7021_DISABLE_RC_4FSK: disable TX Raised Cosine filter for 4FSK modulation in ADF7021. Default TX pulse shaping filter for 4FSK is not optimum for DMR, YSF and P25. Activating this option might improve audio in 4FSK digital modes. + +In Globals.h: + +- #define BIDIR_DATA_PIN: enable Standard TX/RX Data Interface of ADF7021 (enabled by default, needed for scanning mode detection feature). + +In IOSTM.cpp: + +- #define PI_HAT_7021_REV_02: enable pinouts for first revision of ZUMspot RPi. In general is not used. diff --git a/BitBuffer.cpp b/BitBuffer.cpp new file mode 100644 index 0000000..19fa814 --- /dev/null +++ b/BitBuffer.cpp @@ -0,0 +1,145 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* Serial FIFO Control Copyright (C) 2015 by James McLaughlin KI6ZUM +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Library General Public +* License as published by the Free Software Foundation; either +* version 2 of the License, or (at your option) any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Library General Public License for more details. +* +* You should have received a copy of the GNU Library General Public +* License along with this library; if not, write to the +* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +* Boston, MA 02110-1301, USA. +* +*/ +#include "BitBuffer.h" + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the BitBuffer class. +/// +BitBuffer::BitBuffer(uint16_t length) : + m_length(length), + m_bits(NULL), + m_control(NULL), + m_head(0U), + m_tail(0U), + m_full(false), + m_overflow(false) +{ + m_bits = new uint8_t[length / 8U]; + m_control = new uint8_t[length / 8U]; +} + +/// +/// Helper to get how much space the ring buffer has for samples. +/// +/// +uint16_t BitBuffer::getSpace() const +{ + uint16_t n = 0U; + + if (m_tail == m_head) + n = m_full ? 0U : m_length; + else if (m_tail < m_head) + n = m_length - m_head + m_tail; + else + n = m_tail - m_head; + + if (n > m_length) + n = 0U; + + return n; +} + +/// +/// +/// +/// +uint16_t BitBuffer::getData() const +{ + if (m_tail == m_head) + return m_full ? m_length : 0U; + else if (m_tail < m_head) + return m_head - m_tail; + else + return m_length - m_tail + m_head; +} + +/// +/// +/// +/// +/// +bool BitBuffer::put(uint8_t bit, uint8_t control) +{ + if (m_full) { + m_overflow = true; + return false; + } + + WRITE_BIT(m_bits, m_head, bit); + WRITE_BIT(m_control, m_head, control); + + m_head++; + if (m_head >= m_length) + m_head = 0U; + + if (m_head == m_tail) + m_full = true; + + return true; +} + +/// +/// +/// +/// +bool BitBuffer::get(uint8_t& bit, uint8_t& control) +{ + if (m_head == m_tail && !m_full) + return false; + + bit = READ_BIT(m_bits, m_tail); + control = READ_BIT(m_control, m_tail); + + m_full = false; + + m_tail++; + if (m_tail >= m_length) + m_tail = 0U; + + return true; +} + +/// +/// +/// +/// +bool BitBuffer::hasOverflowed() +{ + bool overflow = m_overflow; + m_overflow = false; + + return overflow; +} diff --git a/BitBuffer.h b/BitBuffer.h new file mode 100644 index 0000000..d7e621d --- /dev/null +++ b/BitBuffer.h @@ -0,0 +1,83 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* Serial FIFO Control Copyright (C) 2015 by James McLaughlin KI6ZUM +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Library General Public +* License as published by the Free Software Foundation; either +* version 2 of the License, or (at your option) any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Library General Public License for more details. +* +* You should have received a copy of the GNU Library General Public +* License along with this library; if not, write to the +* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +* Boston, MA 02110-1301, USA. +* +*/ +#if !defined(__BIT_RB_H__) +#define __BIT_RB_H__ + +#if defined(STM32F10X_MD) +#include "stm32f10x.h" +#elif defined(STM32F4XX) +#include "stm32f4xx.h" +#include +#endif + +#include "Defines.h" + +// --------------------------------------------------------------------------- +// Class Declaration +// Implements a circular buffer for bit data. +// --------------------------------------------------------------------------- + +class DSP_FW_API BitBuffer { +public: + /// Initializes a new instance of the BitBuffer class. + BitBuffer(uint16_t length); + + /// Helper to get how much space the ring buffer has for samples. + uint16_t getSpace() const; + + /// + uint16_t getData() const; + + /// + bool put(uint8_t bit, uint8_t control); + + /// + bool get(uint8_t& bit, uint8_t& control); + + /// + bool hasOverflowed(); + +private: + uint16_t m_length; + volatile uint8_t* m_bits; + volatile uint8_t* m_control; + + volatile uint16_t m_head; + volatile uint16_t m_tail; + + volatile bool m_full; + + bool m_overflow; +}; + +#endif // __BIT_RB_H__ diff --git a/CWIdTX.cpp b/CWIdTX.cpp new file mode 100644 index 0000000..eb29755 --- /dev/null +++ b/CWIdTX.cpp @@ -0,0 +1,202 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2009-2017 by Jonathan Naylor G4KLX +* Copyright (C) 2016 by Colin Durbridge G4EML +* Copyright (C) 2017 by Andy Uribe CA6JAU +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Globals.h" +#include "CWIdTX.h" + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +// 4FSK symbol sequence (800 Hz "tone" at 4800 baud): +1 +3 +1 -1 -3 -1 +// Bit sequence: 00 01 00 10 11 10 +uint8_t TONE[] = { 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0 }; + +uint8_t SILENCE[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + +const uint8_t CYCLE_LENGTH = 12U; + +const uint8_t DOT_LENGTH = 40U; + +const struct { + uint8_t c; + uint32_t pattern; + uint8_t length; +} SYMBOL_LIST[] = { + { 'A', 0xB8000000U, 8U }, + { 'B', 0xEA800000U, 12U }, + { 'C', 0xEBA00000U, 14U }, + { 'D', 0xEA000000U, 10U }, + { 'E', 0x80000000U, 4U }, + { 'F', 0xAE800000U, 12U }, + { 'G', 0xEE800000U, 12U }, + { 'H', 0xAA000000U, 10U }, + { 'I', 0xA0000000U, 6U }, + { 'J', 0xBBB80000U, 16U }, + { 'K', 0xEB800000U, 12U }, + { 'L', 0xBA800000U, 12U }, + { 'M', 0xEE000000U, 10U }, + { 'N', 0xE8000000U, 8U }, + { 'O', 0xEEE00000U, 14U }, + { 'P', 0xBBA00000U, 14U }, + { 'Q', 0xEEB80000U, 16U }, + { 'R', 0xBA000000U, 10U }, + { 'S', 0xA8000000U, 8U }, + { 'T', 0xE0000000U, 6U }, + { 'U', 0xAE000000U, 10U }, + { 'V', 0xAB800000U, 12U }, + { 'W', 0xBB800000U, 12U }, + { 'X', 0xEAE00000U, 14U }, + { 'Y', 0xEBB80000U, 16U }, + { 'Z', 0xEEA00000U, 14U }, + { '1', 0xBBBB8000U, 20U }, + { '2', 0xAEEE0000U, 18U }, + { '3', 0xABB80000U, 16U }, + { '4', 0xAAE00000U, 14U }, + { '5', 0xAA800000U, 12U }, + { '6', 0xEAA00000U, 14U }, + { '7', 0xEEA80000U, 16U }, + { '8', 0xEEEA0000U, 18U }, + { '9', 0xEEEE8000U, 20U }, + { '0', 0xEEEEE000U, 22U }, + { '/', 0xEAE80000U, 16U }, + { '?', 0xAEEA0000U, 18U }, + { ',', 0xEEAEE000U, 22U }, + { '-', 0xEAAE0000U, 18U }, + { '=', 0xEAB80000U, 16U }, + { '.', 0xBAEB8000U, 20U }, + { ' ', 0x00000000U, 4U }, + { 0U, 0x00000000U, 0U } +}; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the CWIdTX class. +/// +CWIdTX::CWIdTX() : + m_poBuffer(), + m_poLen(0U), + m_poPtr(0U), + m_n(0U) +{ + /* stub */ +} + +/// +/// Process local buffer and transmit on the air interface. +/// +void CWIdTX::process() +{ + if (m_poLen == 0U) + return; + + uint16_t space = io.getSpace(); + + while (space > CYCLE_LENGTH) { + bool b = _READ_BIT(m_poBuffer, m_poPtr); + if (b) + io.write(TONE, CYCLE_LENGTH); + else + io.write(SILENCE, CYCLE_LENGTH); + + space -= CYCLE_LENGTH; + + m_n++; + if (m_n >= DOT_LENGTH) { + m_poPtr++; + m_n = 0U; + } + + if (m_poPtr >= m_poLen) { + m_poPtr = 0U; + m_poLen = 0U; + return; + } + } +} + +/// +/// Write CW ID data to the local buffer. +/// +/// +/// +/// +uint8_t CWIdTX::write(const uint8_t* data, uint8_t length) +{ + ::memset(m_poBuffer, 0x00U, 300U * sizeof(uint8_t)); + + m_poLen = 8U; + m_poPtr = 0U; + m_n = 0U; + + for (uint8_t i = 0U; i < length; i++) { + for (uint8_t j = 0U; SYMBOL_LIST[j].c != 0U; j++) { + if (SYMBOL_LIST[j].c == data[i]) { + uint32_t MASK = 0x80000000U; + for (uint8_t k = 0U; k < SYMBOL_LIST[j].length; k++, m_poLen++, MASK >>= 1) { + bool b = (SYMBOL_LIST[j].pattern & MASK) == MASK; + _WRITE_BIT(m_poBuffer, m_poLen, b); + + if (m_poLen >= 295U) { + m_poLen = 0U; + return 4U; + } + } + + break; + } + } + } + + // An empty message + if (m_poLen == 8U) { + m_poLen = 0U; + return RSN_ILLEGAL_LENGTH; + } + + m_poLen += 5U; + + DEBUG2("CWIdTx: write(): message created with length", m_poLen); + + return RSN_OK; +} + +/// +/// Helper to reset data values to defaults. +/// +void CWIdTX::reset() +{ + m_poLen = 0U; + m_poPtr = 0U; + m_n = 0U; +} diff --git a/CWIdTX.h b/CWIdTX.h new file mode 100644 index 0000000..1a3f77a --- /dev/null +++ b/CWIdTX.h @@ -0,0 +1,64 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2009-2015 by Jonathan Naylor G4KLX +* Copyright (C) 2016 by Colin Durbridge G4EML +* Copyright (C) 2017 by Andy Uribe CA6JAU +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__CWID_TX_H__) +#define __CWID_TX_H__ + +#include "Defines.h" + +// --------------------------------------------------------------------------- +// Class Declaration +// Implements logic to transmit a CW ID. +// --------------------------------------------------------------------------- + +class DSP_FW_API CWIdTX { +public: + /// Initializes a new instance of the CWIdTX class. + CWIdTX(); + + /// Process local buffer and transmit on the air interface. + void process(); + + /// Write CW ID data to the local buffer. + uint8_t write(const uint8_t* data, uint8_t length); + + /// Helper to reset data values to defaults. + void reset(); + +private: + uint8_t m_poBuffer[300U]; + uint16_t m_poLen; + uint16_t m_poPtr; + + uint8_t m_n; +}; + +#endif // __CWID_TX_H__ diff --git a/CalRSSI.cpp b/CalRSSI.cpp new file mode 100644 index 0000000..66fa83b --- /dev/null +++ b/CalRSSI.cpp @@ -0,0 +1,89 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* Copyright (C) 2018 by Andy Uribe CA6JAU +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Globals.h" +#include "CalRSSI.h" +#include "Utils.h" + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the CalRSSI class. +/// +CalRSSI::CalRSSI() : + m_count(0U), + m_navg(0U), + m_accum(0U), + m_min(0xFFFFU), + m_max(0x0000U) +{ + /* stub */ +} + +/// +/// Sample RSSI values from the air interface. +/// +void CalRSSI::process() +{ + m_count++; + + if (m_count >= 32000U) { + uint16_t rssi = io.readRSSI(); + m_count = 0U; + m_navg++; + + m_accum += rssi; + + if (rssi > m_max) + m_max = rssi; + if (rssi < m_min) + m_min = rssi; + + if (m_navg >= 6U) { + uint16_t ave = m_accum / 6U; + + uint8_t buffer[6U]; + buffer[0U] = (m_max >> 8) & 0xFFU; + buffer[1U] = (m_max >> 0) & 0xFFU; + buffer[2U] = (m_min >> 8) & 0xFFU; + buffer[3U] = (m_min >> 0) & 0xFFU; + buffer[4U] = (ave >> 8) & 0xFFU; + buffer[5U] = (ave >> 0) & 0xFFU; + + serial.writeRSSIData(buffer, 6U); + + m_navg = 0U; + m_accum = 0U; + m_min = 0xFFFFU; + m_max = 0x0000U; + } + } +} diff --git a/CalRSSI.h b/CalRSSI.h new file mode 100644 index 0000000..f068464 --- /dev/null +++ b/CalRSSI.h @@ -0,0 +1,56 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* Copyright (C) 2018 by Andy Uribe CA6JAU +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__CAL_RSSI_H__) +#define __CAL_RSSI_H__ + +#include "Defines.h" + +// --------------------------------------------------------------------------- +// Class Declaration +// Implements logic for RSSI calibration mode. +// --------------------------------------------------------------------------- + +class DSP_FW_API CalRSSI { + /// Initializes a new instance of the CalRSSI class. + CalRSSI(); + + /// Sample RSSI values from the air interface. + void process(); + +private: + uint32_t m_count; + uint8_t m_navg; + uint32_t m_accum; + uint16_t m_min; + uint16_t m_max; +}; + +#endif // __CAL_RSSI_H__ diff --git a/Defines.h b/Defines.h new file mode 100644 index 0000000..3e55f53 --- /dev/null +++ b/Defines.h @@ -0,0 +1,148 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017,2018,2019,2020 by Andy Uribe CA6JAU +* Copyright (C) 2021 Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DEFINES_H__) +#define __DEFINES_H__ + +#include +#include + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +#ifndef _INT8_T_DECLARED +#ifndef __INT8_TYPE__ +typedef signed char int8_t; +#endif // __INT8_TYPE__ +#endif // _INT8_T_DECLARED +#ifndef _INT16_T_DECLARED +#ifndef __INT16_TYPE__ +typedef short int16_t; +#endif // __INT16_TYPE__ +#endif // _INT16_T_DECLARED +#ifndef _INT32_T_DECLARED +#ifndef __INT32_TYPE__ +typedef int int32_t; +#endif // __INT32_TYPE__ +#endif // _INT32_T_DECLARED +#ifndef _INT64_T_DECLARED +#ifndef __INT64_TYPE__ +typedef long long int64_t; +#endif // __INT64_TYPE__ +#endif // _INT64_T_DECLARED +#ifndef _UINT8_T_DECLARED +#ifndef __UINT8_TYPE__ +typedef unsigned char uint8_t; +#endif // __UINT8_TYPE__ +#endif // _UINT8_T_DECLARED +#ifndef _UINT16_T_DECLARED +#ifndef __UINT16_TYPE__ +typedef unsigned short uint16_t; +#endif // __UINT16_TYPE__ +#endif // _UINT16_T_DECLARED +#ifndef _UINT32_T_DECLARED +#ifndef __UINT32_TYPE__ +typedef unsigned int uint32_t; +#endif // __UINT32_TYPE__ +#endif // _UINT32_T_DECLARED +#ifndef _UINT64_T_DECLARED +#ifndef __UINT64_TYPE__ +typedef unsigned long long uint64_t; +#endif // __UINT64_TYPE__ +#endif // _UINT64_T_DECLARED + +#ifndef __LONG64_TYPE__ +typedef long long long64_t; +#endif // __LONG64_TYPE__ +#ifndef __ULONG64_TYPE__ +typedef unsigned long long ulong64_t; +#endif // __ULONG64_TYPE__ + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define DSP_FW_API + +// Allow the DMR protocol +#define ENABLE_DMR + +// Allow the P25 protocol +#define ENABLE_P25 + +// Enable P25 Wide modulation +// #define ENABLE_P25_WIDE + +// Enable ADF7021 support +#define ENABLE_ADF7021 + +// Bidirectional Data pin (Enable Standard TX/RX Data Interface of ADF7021) +#define BIDIR_DATA_PIN + +// Enable full duplex support with dual ADF7021 (valid for homebrew hotspots only) +#define DUPLEX + +// TCXO of the ADF7021 +// For 14.7456 MHz: +#define ADF7021_14_7456 +// For 12.2880 MHz: +// #define ADF7021_12_2880 + +// Configure receiver gain for ADF7021 +// AGC automatic, default settings +#define AD7021_GAIN_AUTO +// AGC automatic with high LNA linearity +// #define AD7021_GAIN_AUTO_LIN +// AGC OFF, lowest gain +// #define AD7021_GAIN_LOW +// AGC OFF, highest gain +// #define AD7021_GAIN_HIGH + +// Enable mode detection +#define ENABLE_SCAN_MODE + +// Pass RSSI information to the host +// #define SEND_RSSI_DATA + +// Enable for RPi 3B+, USB mode +// #define LONG_USB_RESET + +const uint8_t BIT_MASK_TABLE[] = { 0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U }; + +// --------------------------------------------------------------------------- +// Macros +// --------------------------------------------------------------------------- + +#define _WRITE_BIT(p, i, b) p[(i) >> 3] = (b) ? (p[(i) >> 3] | BIT_MASK_TABLE[(i) & 7]) : (p[(i)>>3] & ~BIT_MASK_TABLE[(i) & 7]) +#define _READ_BIT(p, i) (p[(i) >> 3] & BIT_MASK_TABLE[(i) & 7]) + +#endif // __DEFINES_H__ diff --git a/FirmwareMain.cpp b/FirmwareMain.cpp new file mode 100644 index 0000000..09054c7 --- /dev/null +++ b/FirmwareMain.cpp @@ -0,0 +1,150 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2020 by Jonathan Naylor G4KLX +* Copyright (C) 2016 by Mathis Schmieder DB9MAT +* Copyright (C) 2016 by Colin Durbridge G4EML +* Copyright (C) 2016,2017,2018,2019 by Andy Uribe CA6JAU +* Copyright (C) 2019 by Florian Wolters DF2ET +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Globals.h" + +// --------------------------------------------------------------------------- +// Globals +// --------------------------------------------------------------------------- + +DVM_STATE m_modemState = STATE_IDLE; +DVM_STATE m_calState = STATE_IDLE; +DVM_STATE m_modemStatePrev = STATE_IDLE; + +bool m_cwIdState = false; +uint8_t m_cwIdTXLevel = 30; + +uint32_t m_modeTimerCnt; + +#ifdef ENABLE_DMR +bool m_dmrEnable = true; +#else +bool m_dmrEnable = false; +#endif +#ifdef ENABLE_P25 +bool m_p25Enable = true; +#else +bool m_p25Enable = false; +#endif + +bool m_duplex = false; + +bool m_tx = false; +bool m_dcd = false; + +uint8_t m_control; + +#if defined(DUPLEX) +/** DMR BS */ +dmr::DMRIdleRX dmrIdleRX; +dmr::DMRRX dmrRX; +dmr::DMRTX dmrTX; +#endif + +/** DMR MS-DMO */ +dmr::DMRDMORX dmrDMORX; +dmr::DMRDMOTX dmrDMOTX; + +/** P25 */ +p25::P25RX p25RX; +p25::P25TX p25TX; + +/** Calibration */ +dmr::CalDMR calDMR; +p25::CalP25 calP25; +CalRSSI calRSSI; + +/** CW */ +CWIdTX cwIdTX; + +/** RS232 and Air Interface I/O */ +SerialPort serial; +IO io; + +// --------------------------------------------------------------------------- +// Global Functions +// --------------------------------------------------------------------------- + +void setup() +{ + serial.start(); +} + +void loop() +{ + io.process(); + + serial.process(); + + // The following is for transmitting + if (m_dmrEnable && m_modemState == STATE_DMR && m_calState == STATE_IDLE) { +#if defined(DUPLEX) + if (m_duplex) + dmrTX.process(); + else + dmrDMOTX.process(); +#else + dmrDMOTX.process(); +#endif + } + + if (m_p25Enable && m_modemState == STATE_P25) + p25TX.process(); + + if (m_modemState == STATE_DMR_DMO_CAL_1K || m_modemState == STATE_DMR_CAL_1K || + m_modemState == STATE_DMR_LF_CAL || m_modemState == STATE_DMR_CAL) + calDMR.process(); + + if (m_modemState == STATE_P25_CAL_1K || m_modemState == STATE_P25_LF_CAL || m_modemState == STATE_P25_CAL) + calP25.process(); + +#if defined(SEND_RSSI_DATA) + if (m_calState == STATE_RSSI_CAL) + calRSSI.process(); +#endif + + if (m_modemState == STATE_CW || m_modemState == STATE_IDLE) + cwIdTX.process(); +} + +// --------------------------------------------------------------------------- +// Firmware Entry Point +// --------------------------------------------------------------------------- + +int main() +{ + setup(); + + for (;;) + loop(); +} diff --git a/Globals.h b/Globals.h new file mode 100644 index 0000000..c40c796 --- /dev/null +++ b/Globals.h @@ -0,0 +1,132 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2020 by Jonathan Naylor G4KLX +* Copyright (C) 2016,2017,2018,2019 by Andy Uribe CA6JAU +* Copyright (C) 2019 by Florian Wolters DF2ET +* Copyright (C) 2021 Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__GLOBALS_H__) +#define __GLOBALS_H__ + +#if defined(STM32F10X_MD) +#include +#include "string.h" +#elif defined(STM32F4XX) +#include "stm32f4xx.h" +#include "string.h" +#elif defined(STM32F7XX) +#include "stm32f7xx.h" +#include "string.h" +#endif + +#include "Defines.h" +#include "SerialPort.h" +#if defined(DUPLEX) +#include "dmr/DMRIdleRX.h" +#include "dmr/DMRRX.h" +#include "dmr/DMRTX.h" +#endif +#include "dmr/DMRDMORX.h" +#include "dmr/DMRDMOTX.h" +#include "dmr/CalDMR.h" +#include "p25/P25RX.h" +#include "p25/P25TX.h" +#include "p25/CalP25.h" +#include "CalRSSI.h" +#include "CWIdTX.h" +#include "IO.h" + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint8_t MARK_SLOT1 = 0x08U; +const uint8_t MARK_SLOT2 = 0x04U; +const uint8_t MARK_NONE = 0x00U; + +// --------------------------------------------------------------------------- +// Macros +// --------------------------------------------------------------------------- + +#define DEBUG1(a) serial.writeDebug((a)) +#define DEBUG2(a,b) serial.writeDebug((a),(b)) +#define DEBUG3(a,b,c) serial.writeDebug((a),(b),(c)) +#define DEBUG4(a,b,c,d) serial.writeDebug((a),(b),(c),(d)) +#define DEBUG5(a,b,c,d,e) serial.writeDebug((a),(b),(c),(d),(e)) +#define DEBUG_DUMP(a,b) serial.writeDump((a),(b)) + +// --------------------------------------------------------------------------- +// Global Externs +// --------------------------------------------------------------------------- + +extern DVM_STATE m_modemState; +extern DVM_STATE m_calState; +extern DVM_STATE m_modemStatePrev; + +extern bool m_cwIdState; +extern uint8_t m_cwIdTXLevel; + +extern uint32_t m_modeTimerCnt; + +extern bool m_dmrEnable; +extern bool m_p25Enable; + +extern bool m_duplex; + +extern bool m_tx; +extern bool m_dcd; + +extern uint8_t m_control; + +extern SerialPort serial; +extern IO io; + +#if defined(DUPLEX) +/** DMR BS */ +extern dmr::DMRIdleRX dmrIdleRX; +extern dmr::DMRRX dmrRX; +extern dmr::DMRTX dmrTX; +#endif + +/** DMR MS-DMO */ +extern dmr::DMRDMORX dmrDMORX; +extern dmr::DMRDMOTX dmrDMOTX; + +/** P25 BS */ +extern p25::P25RX p25RX; +extern p25::P25TX p25TX; + +/** Calibration */ +extern dmr::CalDMR calDMR; +extern p25::CalP25 calP25; +extern CalRSSI calRSSI; + +/** CW */ +extern CWIdTX cwIdTX; + +#endif // __GLOBALS_H__ diff --git a/IO.cpp b/IO.cpp new file mode 100644 index 0000000..b6962f9 --- /dev/null +++ b/IO.cpp @@ -0,0 +1,444 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* + * Copyright (C) 2015,2016,2020 by Jonathan Naylor G4KLX + * Copyright (C) 2016,2017,2018,2019,2020 by Andy Uribe CA6JAU + * Copyright (C) 2017 by Danilo DB4PLE + * Copyright (C) 2021 Bryan Biedenkapp N2PLL + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "Config.h" +#include "Globals.h" +#include "IO.h" + + // --------------------------------------------------------------------------- + // Globals + // --------------------------------------------------------------------------- + +uint32_t m_rxFrequency; +uint32_t m_txFrequency; +uint8_t m_rfPower; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the IO class. +/// +IO::IO(): + m_started(false), + m_rxBuffer(1024U), + m_txBuffer(1024U), + m_ledCount(0U), + m_scanEnable(false), + m_scanPauseCnt(0U), + m_scanPos(0U), + m_ledValue(true), + m_watchdog(0U), + m_int1Counter(0U), + m_int2Counter(0U) +{ + initInt(); + + CE(HIGH); + + setLEDInt(HIGH); + setPTTInt(LOW); + setDMRInt(LOW); + setP25Int(LOW); + setCOSInt(LOW); + +#if !defined(BIDIR_DATA_PIN) + setTXDInt(LOW); +#endif + + SCLK(LOW); + SDATA(LOW); + SLE1(LOW); +#if defined(DUPLEX) + SLE2(LOW); +#endif + + selfTest(); + m_modeTimerCnt = 0U; +} + +/// +/// Starts air interface sampler. +/// +void IO::start() +{ + m_totalModes = 0U; + + if (m_dmrEnable) { + m_modes[m_totalModes] = STATE_DMR; + m_totalModes++; + } + if (m_p25Enable) { + m_modes[m_totalModes] = STATE_P25; + m_totalModes++; + } + +#if defined(ENABLE_SCAN_MODE) + if (m_totalModes > 1U) { + m_scanEnable = true; + } + else { + m_scanEnable = false; + setMode(m_modemState); + } +#else + m_scanEnable = false; + setMode(m_modemState); +#endif + + if (m_started) + return; + + startInt(); + + m_started = true; +} + +/// +/// Process samples from air interface. +/// +void IO::process() +{ + uint8_t bit; + uint32_t scantime; + uint8_t control; + + m_ledCount++; + if (m_started) { + // Two seconds timeout + if (m_watchdog >= 19200U) { + if (m_modemState == STATE_DMR || m_modemState == STATE_P25) { + m_modemState = STATE_IDLE; + setMode(m_modemState); + } + + m_watchdog = 0U; + } + + if (m_ledCount >= 48000U) { + m_ledCount = 0U; + m_ledValue = !m_ledValue; + setLEDInt(m_ledValue); + } + } + else { + if (m_ledCount >= 480000U) { + m_ledCount = 0U; + m_ledValue = !m_ledValue; + setLEDInt(m_ledValue); + } + return; + } + + // Switch off the transmitter if needed + if (m_txBuffer.getData() == 0U && m_tx) { + if (m_cwIdState) { + // check for CW ID end of transmission + m_cwIdState = false; + + // restoring previous mode + if (m_totalModes) { + io.rf1Conf(m_modemStatePrev, true); + } + } + + setRX(false); + } + + if (m_modemStatePrev == STATE_DMR) + scantime = SCAN_TIME * 2U; + else if (m_modemStatePrev == STATE_P25) + scantime = SCAN_TIME; + else + scantime = SCAN_TIME; + + if (m_modeTimerCnt >= scantime) { + m_modeTimerCnt = 0U; + if ((m_modemState == STATE_IDLE) && (m_scanPauseCnt == 0U) && m_scanEnable && !m_cwIdState) { + m_scanPos = (m_scanPos + 1U) % m_totalModes; + + setMode(m_modes[m_scanPos]); + io.rf1Conf(m_modes[m_scanPos], true); + } + } + + if (m_rxBuffer.getData() >= 1U) { + m_rxBuffer.get(bit, control); + + if (m_modemStatePrev == STATE_DMR) { +#if defined(DUPLEX) + if (m_duplex) { + if (m_tx) + dmrRX.databit(bit, control); + else + dmrIdleRX.databit(bit); + } + else + dmrDMORX.databit(bit); +#else + dmrDMORX.databit(bit); +#endif + } + else if (m_modemStatePrev == STATE_P25) { + p25RX.databit(bit); + } + } +} + +/// +/// Write bits to air interface. +/// +/// +/// +/// +/// +void IO::write(uint8_t* data, uint16_t length, const uint8_t* control) +{ + if (!m_started) + return; + + for (uint16_t i = 0U; i < length; i++) { + if (control == NULL) + m_txBuffer.put(data[i], MARK_NONE); + else + m_txBuffer.put(data[i], control[i]); + } + + // switch the transmitter on if needed + if (!m_tx) { + setTX(); + m_tx = true; + } +} + +/// +/// Helper to get how much space the transmit ring buffer has for samples. +/// +/// +uint16_t IO::getSpace() const +{ + return m_txBuffer.getSpace(); +} + +/// +/// +/// +/// +void IO::setDecode(bool dcd) +{ + if (dcd != m_dcd) { + m_scanPauseCnt = 1U; + setCOSInt(dcd ? true : false); + } + + m_dcd = dcd; +} + +/// +/// Helper to set the modem air interface state. +/// +void IO::setMode(DVM_STATE modemState) +{ + setDMRInt(modemState == STATE_DMR); + setP25Int(modemState == STATE_P25); +} + +/// +/// Sets the RF parameters. +/// +/// +/// +/// +uint8_t IO::setRFParams(uint32_t rxFreq, uint32_t txFreq, uint8_t rfPower) +{ + m_rfPower = rfPower >> 2; + + // check frequency ranges + if (!( + /** 136 - 174 mhz */ + ((rxFreq >= VHF_MIN) && (rxFreq < VHF_MAX)) || ((txFreq >= VHF_MIN) && (txFreq < VHF_MAX)) || + /** 216 - 225 mhz */ + ((rxFreq >= VHF_220_MIN) && (rxFreq < VHF_220_MAX)) || ((txFreq >= VHF_220_MIN) && (txFreq < VHF_220_MAX)) || + /** 380 - 431 mhz */ + ((rxFreq >= UHF_380_MIN) && (rxFreq < UHF_380_MAX)) || ((txFreq >= UHF_380_MIN) && (txFreq < UHF_380_MAX)) || + /** 431 - 450 mhz */ + ((rxFreq >= UHF_1_MIN) && (rxFreq < UHF_1_MAX)) || ((txFreq >= UHF_1_MIN) && (txFreq < UHF_1_MAX)) || + /** 450 - 470 mhz */ + ((rxFreq >= UHF_2_MIN) && (rxFreq < UHF_2_MAX)) || ((txFreq >= UHF_2_MIN) && (txFreq < UHF_2_MAX)) || + /** 470 - 520 mhz */ + ((rxFreq >= UHF_T_MIN) && (rxFreq < UHF_T_MAX)) || ((txFreq >= UHF_T_MIN) && (txFreq < UHF_T_MAX)) || + /** 842 - 900 mhz */ + ((rxFreq >= UHF_800_MIN) && (rxFreq < UHF_800_MAX)) || ((txFreq >= UHF_800_MIN) && (txFreq < UHF_800_MAX)) || + /** 900 - 950 mhz */ + ((rxFreq >= UHF_900_MIN) && (rxFreq < UHF_900_MAX)) || ((txFreq >= UHF_900_MIN) && (txFreq < UHF_900_MAX)) + )) + return RSN_INVALID_REQUEST; + +#if defined(ZUMSPOT_ADF7021) || defined(LONESTAR_USB) || defined(SKYBRIDGE_HS) + // check hotspot configuration for single or dual ADF7021 + if (!(io.hasSingleADF7021())) { + // there are two ADF7021s on the board -- are they dual-band? + if (io.isDualBand()) { + // dual band + if ((txFreq <= VHF_220_MAX) && (rxFreq <= VHF_220_MAX)) { + // turn on VHF side + io.setBandVHF(true); + } + else if ((txFreq >= UHF_1_MIN) && (rxFreq >= UHF_1_MIN)) { + // turn on UHF side + io.setBandVHF(false); + } + } + else if (!io.isDualBand()) { + // duplex board + if ((txFreq < UHF_1_MIN) || (rxFreq < UHF_1_MIN)) { + // Reject VHF frequencies + return RSN_INVALID_REQUEST; + } + } + } +#endif + + m_rxFrequency = rxFreq; + m_txFrequency = txFreq; + + return RSN_OK; +} + +/// +/// Flag indicating the TX ring buffer has overflowed. +/// +/// +bool IO::hasTXOverflow() +{ + return m_txBuffer.hasOverflowed(); +} + +/// +/// Flag indicating the RX ring buffer has overflowed. +/// +/// +bool IO::hasRXOverflow() +{ + return m_rxBuffer.hasOverflowed(); +} + +/// +/// +/// +void IO::resetWatchdog() +{ + m_watchdog = 0U; +} + +/// +/// +/// +/// +uint32_t IO::getWatchdog() +{ + return m_watchdog; +} + +/// +/// +/// +void IO::selfTest() +{ + bool ledValue = false; + uint32_t ledCount = 0U; + uint32_t blinks = 0U; + + while (true) { + ledCount++; + delayUS(1000U); + + if (ledCount >= 125U) { + ledCount = 0U; + ledValue = !ledValue; + + setLEDInt(!ledValue); + setPTTInt(ledValue); + setDMRInt(ledValue); + setP25Int(ledValue); + setCOSInt(ledValue); + + blinks++; + + if (blinks > 5U) + break; + } + } +} + +/// +/// +/// +/// +/// +void IO::getIntCounter(uint16_t& int1, uint16_t& int2) +{ + int1 = m_int1Counter; + int2 = m_int2Counter; + m_int1Counter = 0U; + m_int2Counter = 0U; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +#if defined(ZUMSPOT_ADF7021) || defined(LONESTAR_USB) || defined(SKYBRIDGE_HS) +/// +/// +/// +/// +/// +void IO::checkBand(uint32_t rxFreq, uint32_t txFreq) +{ + // check hotspot configuration for single or dual ADF7021 + if (!(io.hasSingleADF7021())) { + // there are two ADF7021s on the board -- are they dual-band? + if (io.isDualBand()) { + // dual band + if ((txFreq <= VHF_220_MAX) && (rxFreq <= VHF_220_MAX)) { + // turn on VHF side + io.setBandVHF(true); + } + else if ((txFreq >= UHF_1_MIN) && (rxFreq >= UHF_1_MIN)) { + // turn on UHF side + io.setBandVHF(false); + } + } + } +} +#endif diff --git a/IO.h b/IO.h new file mode 100644 index 0000000..55fcdd9 --- /dev/null +++ b/IO.h @@ -0,0 +1,244 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2020 by Jonathan Naylor G4KLX +* Copyright (C) 2016,2017,2018,2019,2020 by Andy Uribe CA6JAU +* Copyright (C) 2017 by Danilo DB4PLE +* Copyright (C) 2017-2021 Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__IO_H__) +#define __IO_H__ + +#include "Defines.h" +#include "Globals.h" +#include "BitBuffer.h" +#include "ADF7021.h" + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define SCAN_TIME 1920 +#define SCAN_PAUSE 20000 + +#if defined(DUPLEX) +#if defined(STM32_USB_HOST) +#define CAL_DLY_LOOP 98950U +#else +#define CAL_DLY_LOOP 96100U +#endif +#else +#if defined(STM32_USB_HOST) +#define CAL_DLY_LOOP 110850U +#else +#define CAL_DLY_LOOP 104600U +#endif +#endif + +// --------------------------------------------------------------------------- +// Global Externs +// --------------------------------------------------------------------------- + +extern uint32_t m_rxFrequency; +extern uint32_t m_txFrequency; +extern uint8_t m_rfPower; + +// --------------------------------------------------------------------------- +// Class Declaration +// Implements the input/output data path with the radio air interface. +// --------------------------------------------------------------------------- + +class DSP_FW_API IO { +public: + /// Initializes a new instance of the IO class. + IO(); + + /// Starts air interface sampler. + void start(); + + /// Process bits from air interface. + void process(); + + /// Write bits to air interface. + void write(uint8_t* data, uint16_t length, const uint8_t* control = NULL); + + /// Helper to get how much space the transmit ring buffer has for samples. + uint16_t getSpace(void) const; + + /// + void setDecode(bool dcd); + + /// Set modem mode. + void setMode(DVM_STATE modemState); + + /// Hardware interrupt handler. + void interrupt1(); +#if defined(DUPLEX) + /// Hardware interrupt handler. + void interrupt2(); +#endif + + /// Sets the ADF7021 RF configuration. + void rf1Conf(DVM_STATE modemState, bool reset); +#if defined(DUPLEX) + /// Sets the ADF7021 RF configuration. + void rf2Conf(DVM_STATE modemState); +#endif + + /// + void setDeviations(uint8_t dmrTXLevel, uint8_t p25TXLevel); + /// Sets the RF parameters. + uint8_t setRFParams(uint32_t rxFreq, uint32_t txFreq, uint8_t rfPower); + + /// Flag indicating the TX ring buffer has overflowed. + bool hasTXOverflow(void); + /// Flag indicating the RX ring buffer has overflowed. + bool hasRXOverflow(void); + + /// + void resetWatchdog(void); + /// + uint32_t getWatchdog(void); + + /// Gets the CPU type the firmware is running on. + uint8_t getCPU() const; + + /// Gets the unique identifier for the air interface. + void getUDID(uint8_t* buffer); + + /// + void updateCal(); + + /// + void delayBit(void); + + /// + uint16_t readRSSI(void); + + /// + void selfTest(); + + /// + void getIntCounter(uint16_t& int1, uint16_t& int2); + +private: + bool m_started; + + BitBuffer m_rxBuffer; + BitBuffer m_txBuffer; + + uint32_t m_ledCount; + + bool m_scanEnable; + uint32_t m_scanPauseCnt; + uint8_t m_scanPos; + + uint8_t m_totalModes; + DVM_STATE m_modes[6]; + bool m_ledValue; + + volatile uint32_t m_watchdog; + + volatile uint16_t m_int1Counter; + volatile uint16_t m_int2Counter; + + /// Helper to check the frequencies are within band ranges of the ADF7021. + void checkBand(uint32_t rxFreq, uint32_t txFreq); + + /// + void setBandVHF(bool enable); + /// + bool hasSingleADF7021(); + /// + bool isDualBand(); + + /// + void configureBand(); + + /// + void setTX(); + /// + void setRX(bool doSle = true); + + /// + void delayIfCal(); + /// + void delayReset(); + /// + void delayUS(uint32_t us); + + // Hardware specific routines + /// Initializes hardware interrupts. + void initInt(); + /// Starts hardware interrupts. + void startInt(); + + /// + void SCLK(bool on); + /// + void SDATA(bool on); + /// + bool SREAD(); + /// + void SLE1(bool on); + +#if defined(DUPLEX) + /// + void SLE2(bool on); + /// + bool RXD2(); +#endif + /// + void CE(bool on); + /// + bool RXD1(); + /// + bool CLK(); + + /// + void setTXDInt(bool on); +#if defined(BIDIR_DATA_PIN) + /// + void setDataDirOut(bool dir); + /// + void setRXDInt(bool on); +#endif + + /// + void setLEDInt(bool on); + /// + void setPTTInt(bool on); + /// + void setCOSInt(bool on); + + /// + void setDMRInt(bool on); + /// + void setP25Int(bool on); +}; + +#endif diff --git a/IOSTM.cpp b/IOSTM.cpp new file mode 100644 index 0000000..c984ff2 --- /dev/null +++ b/IOSTM.cpp @@ -0,0 +1,856 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2020 by Jonathan Naylor G4KLX +* Copyright (C) 2016 by Jim McLaughlin KI6ZUM +* Copyright (C) 2016,2017,2018,2019,2020 by Andy Uribe CA6JAU +* Copyright (C) 2017 by Danilo DB4PLE +* Copyright (C) 2021 Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Globals.h" +#include "IO.h" + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#if defined(STM32F10X_MD) +/** + * The STM32 factory-programmed UUID memory. + * Three values of 32 bits each starting at this address + * Use like this: STM32_UUID[0], STM32_UUID[1], STM32_UUID[2] + */ +#define STM32_UUID ((uint32_t *)0x1FFFF7E8) + +#if defined(PI_HAT_7021_REV_02) + +#define PIN_SCLK GPIO_Pin_4 +#define PORT_SCLK GPIOB + +#define PIN_SREAD GPIO_Pin_5 +#define PORT_SREAD GPIOB + +#define PIN_SDATA GPIO_Pin_6 +#define PORT_SDATA GPIOB + +#define PIN_SLE GPIO_Pin_7 +#define PORT_SLE GPIOB + +#define PIN_CE GPIO_Pin_14 +#define PORT_CE GPIOC + +#define PIN_RXD GPIO_Pin_3 +#define PORT_RXD GPIOB + +// TXD used in SPI Data mode of ADF7021 +// TXD is TxRxCLK of ADF7021, standard TX/RX data interface +#define PIN_TXD GPIO_Pin_15 +#define PORT_TXD GPIOA +#define PIN_TXD_INT GPIO_PinSource15 +#define PORT_TXD_INT GPIO_PortSourceGPIOA + +// CLKOUT used in SPI Data mode of ADF7021 +#define PIN_CLKOUT GPIO_Pin_14 +#define PORT_CLKOUT GPIOA +#define PIN_CLKOUT_INT GPIO_PinSource14 +#define PORT_CLKOUT_INT GPIO_PortSourceGPIOA + +#define PIN_LED GPIO_Pin_13 +#define PORT_LED GPIOC + +#define PIN_DEB GPIO_Pin_11 +#define PORT_DEB GPIOA + +#define PIN_DMR_LED GPIO_Pin_15 +#define PORT_DMR_LED GPIOB + +#define PIN_P25_LED GPIO_Pin_12 +#define PORT_P25_LED GPIOA + +#define PIN_PTT_LED GPIO_Pin_12 +#define PORT_PTT_LED GPIOB + +#define PIN_COS_LED GPIO_Pin_13 +#define PORT_COS_LED GPIOB + +#elif defined(ZUMSPOT_ADF7021) || defined(SKYBRIDGE_HS) || defined(LONESTAR_USB) + +#define PIN_SCLK GPIO_Pin_5 +#define PORT_SCLK GPIOB + +#define PIN_SREAD GPIO_Pin_6 +#define PORT_SREAD GPIOB + +#define PIN_SDATA GPIO_Pin_7 +#define PORT_SDATA GPIOB + +#define PIN_SLE GPIO_Pin_8 +#define PORT_SLE GPIOB + +#define PIN_SLE2 GPIO_Pin_6 +#define PORT_SLE2 GPIOA + +#define PIN_CE GPIO_Pin_14 +#define PORT_CE GPIOC + +#define PIN_RXD GPIO_Pin_4 +#define PORT_RXD GPIOB + +#define PIN_SGL_DBL GPIO_Pin_12 +#define PORT_SGL_DBL GPIOA + +#define PIN_DL_DPX GPIO_Pin_15 +#define PORT_DL_DPX GPIOC + +#define PIN_SET_BAND GPIO_Pin_15 +#define PORT_SET_BAND GPIOA + +// TXD used in SPI Data mode of ADF7021 +// TXD is TxRxCLK of ADF7021, standard TX/RX data interface +#define PIN_TXD GPIO_Pin_3 +#define PORT_TXD GPIOB +#define PIN_TXD_INT GPIO_PinSource3 +#define PORT_TXD_INT GPIO_PortSourceGPIOB + +#if defined(DUPLEX) +#define PIN_RXD2 GPIO_Pin_11 +#define PORT_RXD2 GPIOA + +// TXD2 is TxRxCLK of the second ADF7021, standard TX/RX data interface +#define PIN_TXD2 GPIO_Pin_8 +#define PORT_TXD2 GPIOA +#define PIN_TXD2_INT GPIO_PinSource8 +#define PORT_TXD2_INT GPIO_PortSourceGPIOA +#endif + +// CLKOUT used in SPI Data mode of ADF7021 +#define PIN_CLKOUT GPIO_Pin_15 +#define PORT_CLKOUT GPIOA +#define PIN_CLKOUT_INT GPIO_PinSource15 +#define PORT_CLKOUT_INT GPIO_PortSourceGPIOA + +#define PIN_LED GPIO_Pin_13 +#define PORT_LED GPIOC + +#define PIN_DEB GPIO_Pin_9 +#define PORT_DEB GPIOB + +#define PIN_DMR_LED GPIO_Pin_13 +#define PORT_DMR_LED GPIOB + +#define PIN_P25_LED GPIO_Pin_0 +#define PORT_P25_LED GPIOB + +#define PIN_PTT_LED GPIO_Pin_14 +#define PORT_PTT_LED GPIOB + +#define PIN_COS_LED GPIO_Pin_15 +#define PORT_COS_LED GPIOB + +#elif defined(MMDVM_HS_HAT_REV12) || defined(MMDVM_HS_DUAL_HAT_REV10) || defined(NANO_HOTSPOT) || defined(NANO_DV_REV11) + +#define PIN_SCLK GPIO_Pin_5 +#define PORT_SCLK GPIOB + +#define PIN_SREAD GPIO_Pin_7 +#define PORT_SREAD GPIOB + +#define PIN_SDATA GPIO_Pin_6 +#define PORT_SDATA GPIOB + +#define PIN_SLE GPIO_Pin_8 +#define PORT_SLE GPIOB + +#define PIN_SLE2 GPIO_Pin_6 +#define PORT_SLE2 GPIOA + +#define PIN_CE GPIO_Pin_14 +#define PORT_CE GPIOC + +#define PIN_RXD GPIO_Pin_4 +#define PORT_RXD GPIOB + +#define PIN_RXD2 GPIO_Pin_4 +#define PORT_RXD2 GPIOA + +// TXD used in SPI Data mode of ADF7021 +// TXD is TxRxCLK of ADF7021, standard TX/RX data interface +#define PIN_TXD GPIO_Pin_3 +#define PORT_TXD GPIOB +#define PIN_TXD_INT GPIO_PinSource3 +#define PORT_TXD_INT GPIO_PortSourceGPIOB + +// TXD2 is TxRxCLK of the second ADF7021, standard TX/RX data interface +#define PIN_TXD2 GPIO_Pin_5 +#define PORT_TXD2 GPIOA +#define PIN_TXD2_INT GPIO_PinSource5 +#define PORT_TXD2_INT GPIO_PortSourceGPIOA + +// CLKOUT used in SPI Data mode of ADF7021 +#define PIN_CLKOUT GPIO_Pin_15 +#define PORT_CLKOUT GPIOA +#define PIN_CLKOUT_INT GPIO_PinSource15 +#define PORT_CLKOUT_INT GPIO_PortSourceGPIOA + +#define PIN_LED GPIO_Pin_13 +#define PORT_LED GPIOC + +#define PIN_DEB GPIO_Pin_9 +#define PORT_DEB GPIOB + +#define PIN_DMR_LED GPIO_Pin_13 +#define PORT_DMR_LED GPIOB + +#define PIN_P25_LED GPIO_Pin_0 +#define PORT_P25_LED GPIOB + +#define PIN_PTT_LED GPIO_Pin_14 +#define PORT_PTT_LED GPIOB + +#define PIN_COS_LED GPIO_Pin_15 +#define PORT_COS_LED GPIOB + +#else +#error "Either PI_HAT_7021_REV_02, ZUMSPOT_ADF7021, LONESTAR_USB, MMDVM_HS_HAT_REV12, MMDVM_HS_DUAL_HAT_REV10, NANO_HOTSPOT, NANO_DV_REV11, or SKYBRIDGE_HS need to be defined" +#endif + +// --------------------------------------------------------------------------- +// Global Functions +// --------------------------------------------------------------------------- + +extern "C" { +#if defined(PI_HAT_7021_REV_02) + +#if defined(BIDIR_DATA_PIN) + void EXTI15_10_IRQHandler(void) + { + if (EXTI_GetITStatus(EXTI_Line15) != RESET) { + io.interrupt1(); + EXTI_ClearITPendingBit(EXTI_Line15); + } + } +#else + void EXTI15_10_IRQHandler(void) + { + if (EXTI_GetITStatus(EXTI_Line14) != RESET) { + io.interrupt1(); + EXTI_ClearITPendingBit(EXTI_Line14); + } + } +#endif // BIDIR_DATA_PIN + +#elif defined(ZUMSPOT_ADF7021) || defined(LONESTAR_USB) || defined(LIBRE_KIT_ADF7021) || defined(MMDVM_HS_HAT_REV12) || defined(MMDVM_HS_DUAL_HAT_REV10) || defined(NANO_HOTSPOT) || defined(NANO_DV_REV11) || defined(D2RG_MMDVM_HS) || defined(SKYBRIDGE_HS) + +#if defined(BIDIR_DATA_PIN) + void EXTI3_IRQHandler(void) { + if (EXTI_GetITStatus(EXTI_Line3) != RESET) { + io.interrupt1(); + EXTI_ClearITPendingBit(EXTI_Line3); + } + } +#else + void EXTI15_10_IRQHandler(void) { + if (EXTI_GetITStatus(EXTI_Line15) != RESET) { + io.interrupt1(); + EXTI_ClearITPendingBit(EXTI_Line15); + } + } +#endif // BIDIR_DATA_PIN + +#if defined(DUPLEX) + void EXTI9_5_IRQHandler(void) { +#if defined(ZUMSPOT_ADF7021) || defined(SKYBRIDGE_HS) + if (EXTI_GetITStatus(EXTI_Line8) != RESET) { + io.interrupt2(); + EXTI_ClearITPendingBit(EXTI_Line8); + } +#else + if (EXTI_GetITStatus(EXTI_Line5) != RESET) { + io.interrupt2(); + EXTI_ClearITPendingBit(EXTI_Line5); + } +#endif // ZUMSPOT_ADF7021 || SKYBRIDGE_HS + } +#endif + +#endif +} + +/// +/// Function delay_us() from stm32duino project +/// +/// Number of microseconds to delay. +static inline void delay_us(uint32_t us) +{ + us *= 12; + + /* fudge for function call overhead */ + us--; + asm volatile( + " mov r0, %[us] \n\t" + "1: subs r0, #1 \n\t" + " bhi 1b \n\t" + : + : [us] "r" (us) + : "r0"); +} + +/// +/// +/// +static inline void delay_ns() +{ + + asm volatile( + "nop \n\t" + "nop \n\t" + "nop \n\t" + ); +} + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// +/// +void IO::delayBit() +{ + delay_ns(); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +#if defined(ZUMSPOT_ADF7021) || defined(LONESTAR_USB) || defined(SKYBRIDGE_HS) +/// +/// +/// +/// +void IO::setBandVHF(bool enable) +{ + GPIO_WriteBit(PORT_SET_BAND, PIN_SET_BAND, vhf_on ? Bit_SET : Bit_RESET); +} + +/// +/// +/// +/// +bool IO::hasSingleADF7021() +{ + return GPIO_ReadInputDataBit(PORT_SGL_DBL, PIN_SGL_DBL) == Bit_SET; +} + +/// +/// +/// +/// +bool IO::isDualBand() +{ + return GPIO_ReadInputDataBit(PORT_DL_DPX, PIN_DL_DPX) == Bit_SET; +} +#endif + +/// +/// +/// +void IO::delayIfCal() +{ + delayUS(10000); +} + +/// +/// +/// +void IO::delayReset() +{ + delayUS(300); +} + +/// +/// +/// +/// +void IO::delayUS(uint32_t us) +{ + ::delay_us(us); +} + +/// +/// Initializes hardware interrupts. +/// +void IO::initInt() +{ + GPIO_InitTypeDef GPIO_InitStruct; + GPIO_StructInit(&GPIO_InitStruct); + + EXTI_InitTypeDef EXTI_InitStructure; +#if defined(DUPLEX) + EXTI_InitTypeDef EXTI_InitStructure2; +#endif + + RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO, ENABLE); + +#if defined(PI_HAT_7021_REV_02) + GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE); +#elif defined(ZUMSPOT_ADF7021) || defined(LONESTAR_USB) || defined(LIBRE_KIT_ADF7021) || defined(MMDVM_HS_HAT_REV12) || defined(MMDVM_HS_DUAL_HAT_REV10) || defined(NANO_HOTSPOT) || defined(NANO_DV_REV11) || defined(D2RG_MMDVM_HS) || defined(SKYBRIDGE_HS) + GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); +#endif + +#if defined(ZUMSPOT_ADF7021) || defined(LONESTAR_USB) || defined(SKYBRIDGE_HS) + // Pin defines if the board has a single ADF7021 or double + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_Pin = PIN_SGL_DBL; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; + GPIO_Init(PORT_SGL_DBL, &GPIO_InitStruct); + + // Pin defines if the board is dual band or duplex + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_Pin = PIN_DL_DPX; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; + GPIO_Init(PORT_DL_DPX, &GPIO_InitStruct); + + // Pin will set UHF or VHF + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_Pin = PIN_SET_BAND; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; + GPIO_Init(PORT_SET_BAND, &GPIO_InitStruct); + // TODO: Remove this line + // GPIO_WriteBit(PORT_SET_BAND, PIN_SET_BAND, Bit_RESET); + +#endif + +#if defined(STM32_USB_HOST) + // Pin PA11,PA12 = LOW, USB Reset + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; + GPIO_Init(GPIOA, &GPIO_InitStruct); + GPIO_WriteBit(GPIOA, GPIO_Pin_11, Bit_RESET); + GPIO_WriteBit(GPIOA, GPIO_Pin_12, Bit_RESET); + +#endif + +#if defined(LONG_USB_RESET) + // 10 ms delay + delayUS(10000U); +#else + volatile unsigned int delay; + for (delay = 0; delay < 512; delay++); +#endif + + GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; + GPIO_Init(GPIOA, &GPIO_InitStruct); + + RCC_USBCLKConfig(RCC_USBCLKSource_PLLCLK_1Div5); + RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB, ENABLE); + NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); + + // Pin SCLK + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_Pin = PIN_SCLK; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; + GPIO_Init(PORT_SCLK, &GPIO_InitStruct); + + // Pin SDATA + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_Pin = PIN_SDATA; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; + GPIO_Init(PORT_SDATA, &GPIO_InitStruct); + + // Pin SREAD + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_Pin = PIN_SREAD; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; + GPIO_Init(PORT_SREAD, &GPIO_InitStruct); + + // Pin SLE + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_Pin = PIN_SLE; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; + GPIO_Init(PORT_SLE, &GPIO_InitStruct); + +#if defined(DUPLEX) + // Pin SLE2 + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_Pin = PIN_SLE2; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; + GPIO_Init(PORT_SLE2, &GPIO_InitStruct); + + // Pin RXD2 + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_Pin = PIN_RXD2; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; + GPIO_Init(PORT_RXD2, &GPIO_InitStruct); +#endif + + // Pin CE + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_Pin = PIN_CE; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; + GPIO_Init(PORT_CE, &GPIO_InitStruct); + + // Pin RXD + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_Pin = PIN_RXD; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; + GPIO_Init(PORT_RXD, &GPIO_InitStruct); + + // Pin TXD + // TXD is TxRxCLK of ADF7021, standard TX/RX data interface + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_Pin = PIN_TXD; +#if defined(BIDIR_DATA_PIN) + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; +#else + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; +#endif + GPIO_Init(PORT_TXD, &GPIO_InitStruct); +#if defined(DUPLEX) + GPIO_InitStruct.GPIO_Pin = PIN_TXD2; + GPIO_Init(PORT_TXD2, &GPIO_InitStruct); +#endif + + // Pin TXRX_CLK +#if !defined(BIDIR_DATA_PIN) + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_Pin = PIN_CLKOUT; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; + GPIO_Init(PORT_CLKOUT, &GPIO_InitStruct); +#endif + + // Pin LED + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_Pin = PIN_LED; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; + GPIO_Init(PORT_LED, &GPIO_InitStruct); + + // Pin Debug + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_Pin = PIN_DEB; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; + GPIO_Init(PORT_DEB, &GPIO_InitStruct); + + // DMR LED + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_Pin = PIN_DMR_LED; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; + GPIO_Init(PORT_DMR_LED, &GPIO_InitStruct); + + // P25 LED + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_Pin = PIN_P25_LED; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; + GPIO_Init(PORT_P25_LED, &GPIO_InitStruct); + + // PTT LED + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_Pin = PIN_PTT_LED; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; + GPIO_Init(PORT_PTT_LED, &GPIO_InitStruct); + + // COS LED + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_Pin = PIN_COS_LED; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; + GPIO_Init(PORT_COS_LED, &GPIO_InitStruct); + +#if defined(PI_HAT_7021_REV_02) + +#if defined(BIDIR_DATA_PIN) + // Connect EXTI15 Line + GPIO_EXTILineConfig(PORT_TXD_INT, PIN_TXD_INT); + // Configure EXTI15 line + EXTI_InitStructure.EXTI_Line = EXTI_Line15; +#else + // Connect EXTI14 Line + GPIO_EXTILineConfig(PORT_CLKOUT_INT, PIN_CLKOUT_INT); + // Configure EXTI14 line + EXTI_InitStructure.EXTI_Line = EXTI_Line14; +#endif + +#elif defined(ZUMSPOT_ADF7021) || defined(LONESTAR_USB) || defined(LIBRE_KIT_ADF7021) || defined(MMDVM_HS_HAT_REV12) || defined(MMDVM_HS_DUAL_HAT_REV10) || defined(NANO_HOTSPOT) || defined(NANO_DV_REV11) || defined(D2RG_MMDVM_HS) || defined(SKYBRIDGE_HS) + +#if defined(BIDIR_DATA_PIN) + // Connect EXTI3 Line + GPIO_EXTILineConfig(PORT_TXD_INT, PIN_TXD_INT); + // Configure EXTI3 line + EXTI_InitStructure.EXTI_Line = EXTI_Line3; +#else + // Connect EXTI15 Line + GPIO_EXTILineConfig(PORT_CLKOUT_INT, PIN_CLKOUT_INT); + // Configure EXTI15 line + EXTI_InitStructure.EXTI_Line = EXTI_Line15; +#endif + +#if defined(DUPLEX) + // Connect EXTI5 Line + GPIO_EXTILineConfig(PORT_TXD2_INT, PIN_TXD2_INT); + // Configure EXT5 line +#if defined(ZUMSPOT_ADF7021) || defined(LONESTAR_USB) || defined(SKYBRIDGE_HS) + EXTI_InitStructure2.EXTI_Line = EXTI_Line8; +#else + EXTI_InitStructure2.EXTI_Line = EXTI_Line5; +#endif +#endif + +#endif + + EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; + EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; + EXTI_InitStructure.EXTI_LineCmd = ENABLE; + EXTI_Init(&EXTI_InitStructure); + +#if defined(DUPLEX) + EXTI_InitStructure2.EXTI_Mode = EXTI_Mode_Interrupt; + EXTI_InitStructure2.EXTI_Trigger = EXTI_Trigger_Rising; + EXTI_InitStructure2.EXTI_LineCmd = ENABLE; + EXTI_Init(&EXTI_InitStructure2); +#endif +} + +/// +/// Starts hardware interrupts. +/// +void IO::startInt() +{ + NVIC_InitTypeDef NVIC_InitStructure; + +#if defined(DUPLEX) + NVIC_InitTypeDef NVIC_InitStructure2; +#endif + +#if defined(PI_HAT_7021_REV_02) + + NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; + +#elif defined(ZUMSPOT_ADF7021) || defined(LONESTAR_USB) || defined(LIBRE_KIT_ADF7021) || defined(MMDVM_HS_HAT_REV12) || defined(MMDVM_HS_DUAL_HAT_REV10) || defined(NANO_HOTSPOT) || defined(NANO_DV_REV11) || defined(D2RG_MMDVM_HS) || defined(SKYBRIDGE_HS) + +#if defined(BIDIR_DATA_PIN) + // Enable and set EXTI3 Interrupt + NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn; +#else + // Enable and set EXTI15 Interrupt + NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; +#endif + +#if defined(DUPLEX) + NVIC_InitStructure2.NVIC_IRQChannel = EXTI9_5_IRQn; +#endif + +#endif + + NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; + NVIC_InitStructure.NVIC_IRQChannelSubPriority = 15; + NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; + NVIC_Init(&NVIC_InitStructure); + +#if defined(DUPLEX) + NVIC_InitStructure2.NVIC_IRQChannelPreemptionPriority = 1; + NVIC_InitStructure2.NVIC_IRQChannelSubPriority = 15; + NVIC_InitStructure2.NVIC_IRQChannelCmd = ENABLE; + NVIC_Init(&NVIC_InitStructure2); +#endif +} + +#if defined(BIDIR_DATA_PIN) + +/// +/// +/// +/// RXD pin is bidirectional in standard interfaces +/// +void IO::setDataDirOut(bool dir) +{ + GPIO_InitTypeDef GPIO_InitStruct; + + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_Pin = PIN_RXD; + + if (dir) + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; + else + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; + + GPIO_Init(PORT_RXD, &GPIO_InitStruct); +} +#endif + +/// +/// +/// +/// +void IO::SCLK(bool on) +{ + GPIO_WriteBit(PORT_SCLK, PIN_SCLK, on ? Bit_SET : Bit_RESET); +} + +/// +/// +/// +/// +void IO::SDATA(bool on) +{ + GPIO_WriteBit(PORT_SDATA, PIN_SDATA, on ? Bit_SET : Bit_RESET); +} + +/// +/// +/// +/// +bool IO::SREAD() +{ + return GPIO_ReadInputDataBit(PORT_SREAD, PIN_SREAD) == Bit_SET; +} + +/// +/// +/// +/// +void IO::SLE1(bool on) +{ + GPIO_WriteBit(PORT_SLE, PIN_SLE, on ? Bit_SET : Bit_RESET); +} + +#if defined(DUPLEX) +/// +/// +/// +/// +void IO::SLE2(bool on) +{ + GPIO_WriteBit(PORT_SLE2, PIN_SLE2, on ? Bit_SET : Bit_RESET); +} + +/// +/// +/// +/// +bool IO::RXD2() +{ + return GPIO_ReadInputDataBit(PORT_RXD2, PIN_RXD2) == Bit_SET; +} +#endif + +/// +/// +/// +/// +void IO::CE(bool on) +{ + GPIO_WriteBit(PORT_CE, PIN_CE, on ? Bit_SET : Bit_RESET); +} + +/// +/// +/// +/// +bool IO::RXD1() +{ + return GPIO_ReadInputDataBit(PORT_RXD, PIN_RXD) == Bit_SET; +} + +/// +/// +/// +/// +bool IO::CLK() +{ +#if defined(BIDIR_DATA_PIN) + return GPIO_ReadInputDataBit(PORT_TXD, PIN_TXD) == Bit_SET; +#else + return GPIO_ReadInputDataBit(PORT_CLKOUT, PIN_CLKOUT) == Bit_SET; +#endif +} + +#if defined(BIDIR_DATA_PIN) +/// +/// +/// +/// +void IO::setRXDInt(bool on) +{ + GPIO_WriteBit(PORT_RXD, PIN_RXD, on ? Bit_SET : Bit_RESET); +} +#endif + +/// +/// +/// +/// +void IO::setTXDInt(bool on) +{ + GPIO_WriteBit(PORT_TXD, PIN_TXD, on ? Bit_SET : Bit_RESET); +} + +/// +/// +/// +/// +void IO::setLEDInt(bool on) +{ + GPIO_WriteBit(PORT_LED, PIN_LED, on ? Bit_SET : Bit_RESET); +} + +/// +/// +/// +/// +void IO::setPTTInt(bool on) +{ + GPIO_WriteBit(PORT_PTT_LED, PIN_PTT_LED, on ? Bit_SET : Bit_RESET); +} + +/// +/// +/// +/// +void IO::setCOSInt(bool on) +{ + GPIO_WriteBit(PORT_COS_LED, PIN_COS_LED, on ? Bit_SET : Bit_RESET); +} + +/// +/// +/// +/// +void IO::setDMRInt(bool on) +{ + GPIO_WriteBit(PORT_DMR_LED, PIN_DMR_LED, on ? Bit_SET : Bit_RESET); +} + +/// +/// +/// +/// +void IO::setP25Int(bool on) +{ + GPIO_WriteBit(PORT_P25_LED, PIN_P25_LED, on ? Bit_SET : Bit_RESET); +} + +#endif // STM32F10X_MD diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..b017086 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,264 @@ +The GNU General Public License, Version 2, June 1991 (GPLv2) +============================================================ + +> Copyright (C) 1989, 1991 Free Software Foundation, Inc. +> 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + + +Preamble +-------- + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public License is intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. This General Public License applies to most +of the Free Software Foundation's software and to any other program whose +authors commit to using it. (Some other Free Software Foundation software is +covered by the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom to +distribute copies of free software (and charge for this service if you wish), +that you receive source code or can get it if you want it, that you can change +the software or use pieces of it in new free programs; and that you know you can +do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny +you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of the +software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for a +fee, you must give the recipients all the rights that you have. You must make +sure that they, too, receive or can get the source code. And you must show them +these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) offer +you this license which gives you legal permission to copy, distribute and/or +modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If the +software is modified by someone else and passed on, we want its recipients to +know that what they have is not the original, so that any problems introduced by +others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We wish +to avoid the danger that redistributors of a free program will individually +obtain patent licenses, in effect making the program proprietary. To prevent +this, we have made it clear that any patent must be licensed for everyone's free +use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + + +Terms And Conditions For Copying, Distribution And Modification +--------------------------------------------------------------- + +**0.** This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms of +this General Public License. The "Program", below, refers to any such program or +work, and a "work based on the Program" means either the Program or any +derivative work under copyright law: that is to say, a work containing the +Program or a portion of it, either verbatim or with modifications and/or +translated into another language. (Hereinafter, translation is included without +limitation in the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered by +this License; they are outside its scope. The act of running the Program is not +restricted, and the output from the Program is covered only if its contents +constitute a work based on the Program (independent of having been made by +running the Program). Whether that is true depends on what the Program does. + +**1.** You may copy and distribute verbatim copies of the Program's source code +as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this License +and to the absence of any warranty; and give any other recipients of the Program +a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may at +your option offer warranty protection in exchange for a fee. + +**2.** You may modify your copy or copies of the Program or any portion of it, +thus forming a work based on the Program, and copy and distribute such +modifications or work under the terms of Section 1 above, provided that you also +meet all of these conditions: + +* **a)** You must cause the modified files to carry prominent notices stating + that you changed the files and the date of any change. + +* **b)** You must cause any work that you distribute or publish, that in whole + or in part contains or is derived from the Program or any part thereof, to + be licensed as a whole at no charge to all third parties under the terms of + this License. + +* **c)** If the modified program normally reads commands interactively when + run, you must cause it, when started running for such interactive use in the + most ordinary way, to print or display an announcement including an + appropriate copyright notice and a notice that there is no warranty (or + else, saying that you provide a warranty) and that users may redistribute + the program under these conditions, and telling the user how to view a copy + of this License. (Exception: if the Program itself is interactive but does + not normally print such an announcement, your work based on the Program is + not required to print an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be reasonably +considered independent and separate works in themselves, then this License, and +its terms, do not apply to those sections when you distribute them as separate +works. But when you distribute the same sections as part of a whole which is a +work based on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the entire whole, +and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise the +right to control the distribution of derivative or collective works based on the +Program. + +In addition, mere aggregation of another work not based on the Program with the +Program (or with a work based on the Program) on a volume of a storage or +distribution medium does not bring the other work under the scope of this +License. + +**3.** You may copy and distribute the Program (or a work based on it, under +Section 2) in object code or executable form under the terms of Sections 1 and 2 +above provided that you also do one of the following: + +* **a)** Accompany it with the complete corresponding machine-readable source + code, which must be distributed under the terms of Sections 1 and 2 above on + a medium customarily used for software interchange; or, + +* **b)** Accompany it with a written offer, valid for at least three years, to + give any third party, for a charge no more than your cost of physically + performing source distribution, a complete machine-readable copy of the + corresponding source code, to be distributed under the terms of Sections 1 + and 2 above on a medium customarily used for software interchange; or, + +* **c)** Accompany it with the information you received as to the offer to + distribute corresponding source code. (This alternative is allowed only for + noncommercial distribution and only if you received the program in object + code or executable form with such an offer, in accord with Subsection b + above.) + +The source code for a work means the preferred form of the work for making +modifications to it. For an executable work, complete source code means all the +source code for all modules it contains, plus any associated interface +definition files, plus the scripts used to control compilation and installation +of the executable. However, as a special exception, the source code distributed +need not include anything that is normally distributed (in either source or +binary form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component itself +accompanies the executable. + +If distribution of executable or object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the source code +from the same place counts as distribution of the source code, even though third +parties are not compelled to copy the source along with the object code. + +**4.** You may not copy, modify, sublicense, or distribute the Program except as +expressly provided under this License. Any attempt otherwise to copy, modify, +sublicense or distribute the Program is void, and will automatically terminate +your rights under this License. However, parties who have received copies, or +rights, from you under this License will not have their licenses terminated so +long as such parties remain in full compliance. + +**5.** You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Program or its derivative works. These actions are prohibited by law if you do +not accept this License. Therefore, by modifying or distributing the Program (or +any work based on the Program), you indicate your acceptance of this License to +do so, and all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +**6.** Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the original +licensor to copy, distribute or modify the Program subject to these terms and +conditions. You may not impose any further restrictions on the recipients' +exercise of the rights granted herein. You are not responsible for enforcing +compliance by third parties to this License. + +**7.** If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), conditions +are imposed on you (whether by court order, agreement or otherwise) that +contradict the conditions of this License, they do not excuse you from the +conditions of this License. If you cannot distribute so as to satisfy +simultaneously your obligations under this License and any other pertinent +obligations, then as a consequence you may not distribute the Program at all. +For example, if a patent license would not permit royalty-free redistribution of +the Program by all those who receive copies directly or indirectly through you, +then the only way you could satisfy both it and this License would be to refrain +entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply and the +section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or +other property right claims or to contest validity of any such claims; this +section has the sole purpose of protecting the integrity of the free software +distribution system, which is implemented by public license practices. Many +people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose that +choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +**8.** If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Program under this License may add an explicit +geographical distribution limitation excluding those countries, so that +distribution is permitted only in or among countries not thus excluded. In such +case, this License incorporates the limitation as if written in the body of this +License. + +**9.** The Free Software Foundation may publish revised and/or new versions of +the General Public License from time to time. Such new versions will be similar +in spirit to the present version, but may differ in detail to address new +problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Program does not specify a version number of this License, you may choose any +version ever published by the Free Software Foundation. + +**10.** If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software Foundation, +write to the Free Software Foundation; we sometimes make exceptions for this. +Our decision will be guided by the two goals of preserving the free status of +all derivatives of our free software and of promoting the sharing and reuse of +software generally. + + +No Warranty +----------- + +**11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM +"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +**12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR +INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA +BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER +OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. diff --git a/README.md b/README.md new file mode 100644 index 0000000..199ab12 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# Digital Voice Modem Firmware (Hotspot) + +The DVM firmware provides the embedded microcontroller implementation of a mixed-mode DMR/P25 or dedicated-mode DMR or P25 repeater system. The firmware; is the portion of a complete Over-The-Air modem implementation that uses an ADF7021 to provide a raw RF interface. + +This project is a direct fork of the MMDVM (https://github.com/jelimoore/MMDVM_HS) project. + +## Building + +Please see the various Makefile's included in the project for more information. This project includes a few Makefiles to target different hardware. (All following information assumes familiarity with the standard Linux make system.) + +* Makefile - This makefile is used for targeting a generic STM32F103/ADF7021 device. + +* For STM32F103 using Ubuntu OS install the standard ARM embedded toolchain (typically arm-gcc-none-eabi). + 1. Create a directory under "/opt" called "tools" and change to the directory: + ``` + mkdir -p /opt/tools + cd /opt/tools + ``` + 2. Checkout ```https://github.com/juribeparada/STM32F10X_Lib``` to /opt/tools: + ```git clone https://github.com/juribeparada/STM32F10X_Lib``` + +Use the ```make``` command to build the firmware, choosing the appropriate makefile with the -F switch. + +## License + +This project is licensed under the GPLv2 License - see the [LICENSE.md](LICENSE.md) file for details. Use of this project is intended, strictly for amateur and educational use ONLY. Any other use is at the risk of user and all commercial purposes are strictly forbidden. + diff --git a/STM_UART.cpp b/STM_UART.cpp new file mode 100644 index 0000000..bf8b3f5 --- /dev/null +++ b/STM_UART.cpp @@ -0,0 +1,134 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (c) 2020 by Jonathan Naylor G4KLX +* Copyright (c) 2020 by Geoffrey Merck F4FXL - KC3FRA +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#if defined(STM32F10X_MD) || defined(STM32F4XX) + +#include "STM_UART.h" + + // --------------------------------------------------------------------------- + // Public Class Members + // --------------------------------------------------------------------------- + /// + /// Initializes a new instance of the STM_UART class. + /// +STM_UART::STM_UART() : + m_usart(NULL) +{ + /* stub */ +} + +/// +/// +void STM_UART::init(USART_TypeDef* usart) +{ + m_usart = usart; +} + +/// +/// +/// +void STM_UART::write(const uint8_t* data, uint16_t length) +{ + if (length == 0U || m_usart == NULL) + return; + + + m_txFifo.put(data[0]); + USART_ITConfig(m_usart, USART_IT_TXE, ENABLE);//switch TX IRQ is on + + for (uint16_t i = 1U; i < length; i++) { + m_txFifo.put(data[i]); + } + + USART_ITConfig(m_usart, USART_IT_TXE, ENABLE);//make sure TX IRQ is on +} + +/// +/// +uint8_t STM_UART::read() +{ + return m_rxFifo.get(); +} + +/// +void STM_UART::handleIRQ() +{ + if (m_usart == NULL) + return; + + if (USART_GetITStatus(m_usart, USART_IT_RXNE)) { + if (!m_rxFifo.isFull()) + m_rxFifo.put((uint8_t)USART_ReceiveData(m_usart)); + USART_ClearITPendingBit(USART1, USART_IT_RXNE); + } + + if (USART_GetITStatus(m_usart, USART_IT_TXE)) { + if (!m_txFifo.isEmpty()) + USART_SendData(m_usart, m_txFifo.get()); + + USART_ClearITPendingBit(m_usart, USART_IT_TXE); + + if (m_txFifo.isEmpty()) // if there's no more data to transmit then turn off TX interrupts + USART_ITConfig(m_usart, USART_IT_TXE, DISABLE); + } +} + +/// +/// Flushes the transmit shift register. +/// +/// +/// This call is blocking! +/// +void STM_UART::flush() +{ + if (m_usart == NULL) + return; + + // wait until the TXE shows the shift register is empty + while (USART_GetITStatus(m_usart, USART_FLAG_TXE)) + ; +} + +/// +/// +uint16_t STM_UART::available() +{ + return m_rxFifo.isEmpty() ? 0U : 1U; +} + +/// +/// +uint16_t STM_UART::availableForWrite() +{ + return m_txFifo.isFull() ? 0U : 1U; +} + +#endif diff --git a/STM_UART.h b/STM_UART.h new file mode 100644 index 0000000..17dc214 --- /dev/null +++ b/STM_UART.h @@ -0,0 +1,137 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (c) 2020 by Jonathan Naylor G4KLX +* Copyright (c) 2020 by Geoffrey Merck F4FXL - KC3FRA +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#if defined(STM32F10X_MD) || defined(STM32F4XX) +#if !defined(__STM_UART_H__) +#define __STM_UART_H__ + +#include "Defines.h" + +#if defined(STM32F10X_MD) +#include +#elif defined(STM32F4XX) +#include "stm32f4xx.h" +#endif + +const uint16_t BUFFER_SIZE = 2048U; //needs to be a power of 2 ! +const uint16_t BUFFER_MASK = BUFFER_SIZE - 1; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class represents a FIFO buffer on a STM32 UART. +// --------------------------------------------------------------------------- + +class DSP_FW_API STM_UARTFIFO { +public: + /// Initializes a new instance of the STM_UARTFIFO class. + STM_UARTFIFO() : + m_head(0U), + m_tail(0U) + { + /* stub */ + } + + /// + uint8_t get() + { + return m_buffer[BUFFER_MASK & (m_tail++)]; + } + + /// + void put(uint8_t data) + { + m_buffer[BUFFER_MASK & (m_head++)] = data; + } + + /// Helper to reset data values to defaults. + void reset() + { + m_tail = 0U; + m_head = 0U; + } + + /// + bool isEmpty() + { + return m_tail == m_head; + } + + /// + bool isFull() + { + return ((m_head + 1U) & BUFFER_MASK) == (m_tail & BUFFER_MASK); + } + +private: + volatile uint8_t m_buffer[BUFFER_SIZE]; + volatile uint16_t m_head; + volatile uint16_t m_tail; +}; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class represents an STM32 UART. +// --------------------------------------------------------------------------- + +class STM_UART { +public: + /// Initializes a new instance of the STM_UART class. + STM_UART(); + + /// + void init(USART_TypeDef* usart); + + /// + uint8_t read(); + /// + void write(const uint8_t* data, uint16_t length); + + /// + void handleIRQ(); + + /// Flushes the transmit shift register. + void flush(); + + /// + uint16_t available(); + + /// + uint16_t availableForWrite(); + +private: + USART_TypeDef* m_usart; + + STM_UARTFIFO m_rxFifo; + STM_UARTFIFO m_txFifo; +}; + +#endif // __SERIAL_PORT_H__ +#endif diff --git a/SerialBuffer.cpp b/SerialBuffer.cpp new file mode 100644 index 0000000..0053ae5 --- /dev/null +++ b/SerialBuffer.cpp @@ -0,0 +1,142 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* Serial FIFO Control Copyright (C) 2015 by James McLaughlin KI6ZUM +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Library General Public +* License as published by the Free Software Foundation; either +* version 2 of the License, or (at your option) any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Library General Public License for more details. +* +* You should have received a copy of the GNU Library General Public +* License along with this library; if not, write to the +* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +* Boston, MA 02110-1301, USA. +* +*/ +#include "SerialBuffer.h" + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the SerialBuffer class. +/// +SerialBuffer::SerialBuffer(uint16_t length) : + m_length(length), + m_buffer(NULL), + m_head(0U), + m_tail(0U), + m_full(false) +{ + m_buffer = new uint8_t[length]; +} + +/// +/// Helper to get how much space the ring buffer has for samples. +/// +/// +uint16_t SerialBuffer::getSpace() const +{ + uint16_t n = 0U; + + if (m_tail == m_head) + n = m_full ? 0U : m_length; + else if (m_tail < m_head) + n = m_length - m_head + m_tail; + else + n = m_tail - m_head; + + if (n > m_length) + n = 0U; + + return n; +} + +/// +/// +/// +/// +uint16_t SerialBuffer::getData() const +{ + if (m_tail == m_head) + return m_full ? m_length : 0U; + else if (m_tail < m_head) + return m_head - m_tail; + else + return m_length - m_tail + m_head; +} + +/// +/// Helper to reset data values to defaults. +/// +void SerialBuffer::reset() +{ + m_head = 0U; + m_tail = 0U; + m_full = false; +} + +/// +/// +/// +/// +/// +bool SerialBuffer::put(uint8_t c) +{ + if (m_full) + return false; + + m_buffer[m_head] = c; + + m_head++; + if (m_head >= m_length) + m_head = 0U; + + if (m_head == m_tail) + m_full = true; + + return true; +} + +/// +/// +/// +/// +uint8_t SerialBuffer::peek() const +{ + return m_buffer[m_tail]; +} + +/// +/// +/// +/// +uint8_t SerialBuffer::get() +{ + uint8_t value = m_buffer[m_tail]; + + m_full = false; + + m_tail++; + if (m_tail >= m_length) + m_tail = 0U; + + return value; +} diff --git a/SerialBuffer.h b/SerialBuffer.h new file mode 100644 index 0000000..5ce3b66 --- /dev/null +++ b/SerialBuffer.h @@ -0,0 +1,89 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* Serial FIFO Control Copyright (C) 2015 by James McLaughlin KI6ZUM +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Library General Public +* License as published by the Free Software Foundation; either +* version 2 of the License, or (at your option) any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Library General Public License for more details. +* +* You should have received a copy of the GNU Library General Public +* License along with this library; if not, write to the +* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +* Boston, MA 02110-1301, USA. +* +*/ +#if !defined(__SERIAL_RB_H__) +#define __SERIAL_RB_H__ + +#if defined(STM32F10X_MD) +#include "stm32f10x.h" +#elif defined(STM32F4XX) +#include "stm32f4xx.h" +#include +#endif + +#include "Defines.h" + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint16_t SERIAL_RINGBUFFER_SIZE = 1000U; + +// --------------------------------------------------------------------------- +// Class Declaration +// Implements a circular buffer for serial data. +// --------------------------------------------------------------------------- + +class DSP_FW_API SerialBuffer { +public: + /// Initializes a new instance of the SerialBuffer class. + SerialBuffer(uint16_t length = SERIAL_RINGBUFFER_SIZE); + + /// Helper to get how much space the ring buffer has for samples. + uint16_t getSpace() const; + + /// + uint16_t getData() const; + + /// Helper to reset data values to defaults. + void reset(); + + /// + bool put(uint8_t c); + + /// + uint8_t peek() const; + + /// + uint8_t get(); + +private: + uint16_t m_length; + volatile uint8_t* m_buffer; + + volatile uint16_t m_head; + volatile uint16_t m_tail; + + volatile bool m_full; +}; + +#endif // __SERIAL_RB_H__ diff --git a/SerialPort.cpp b/SerialPort.cpp new file mode 100644 index 0000000..1f6b3a9 --- /dev/null +++ b/SerialPort.cpp @@ -0,0 +1,1201 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2013,2015,2016,2018,2020,2021 by Jonathan Naylor G4KLX +* Copyright (C) 2016 by Colin Durbridge G4EML +* Copyright (C) 2016,2017,2018,2019 by Andy Uribe CA6JAU +* Copyright (C) 2019 by Florian Wolters DF2ET +* Copyright (C) 2017-2021 Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Globals.h" +#include "SerialPort.h" + + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + +#if defined(ENABLE_DMR) +#define DESCR_DMR "DMR, " +#else +#define DESCR_DMR "" +#endif +#if defined(ENABLE_P25) +#define DESCR_P25 "P25, " +#else +#define DESCR_P25 "" +#endif + +#if defined(SEND_RSSI_DATA) +#define DESCR_RSSI "RSSI, " +#else +#define DESCR_RSSI "" +#endif + +#if defined(ZUMSPOT_ADF7021) +#define BOARD_INFO "ZUMspot" +#elif defined(MMDVM_HS_HAT_REV12) +#define BOARD_INFO "MMDVM_HS_Hat" +#elif defined(MMDVM_HS_DUAL_HAT_REV10) +#define BOARD_INFO "MMDVM_HS_Dual_Hat" +#elif defined(NANO_HOTSPOT) +#define BOARD_INFO "Nano_hotSPOT" +#elif defined(NANO_DV_REV11) +#define BOARD_INFO "Nano_DV" +#elif defined(SKYBRIDGE_HS) +#define BOARD_INFO "SkyBridge" +#elif defined(LONESTAR_USB) +#define BOARD_INFO "LS_USB_STICK" +#else +#define BOARD_INFO "MMDVM_HS" +#endif + +#if defined(ADF7021_14_7456) +#define DESCR_OSC "TCXO 14.7456" +#endif +#if defined(ADF7021_12_2880) +#define DESCR_OSC "TCXO 12.2880" +#endif + +#if defined(ENABLE_ADF7021) && defined(ADF7021_N_VER) +#define RF_CHIP "ADF7021N" +#elif defined(ENABLE_ADF7021) +#define RF_CHIP "ADF7021" +#endif + +#define DESCRIPTION "Digital Voice Modem DSP Hotspot [" BOARD_INFO "] (" RF_CHIP DESCR_DMR DESCR_P25 DESCR_OSC DESCR_RSSI "CW Id)" + +#define concat(a, b, c) a " (build " b " " c ")" +const char HARDWARE[] = concat(DESCRIPTION, __TIME__, __DATE__); + +const uint8_t PROTOCOL_VERSION = 2U; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the SerialPort class. +/// +SerialPort::SerialPort() : + m_buffer(), + m_ptr(0U), + m_len(0U), + m_debug(false), + m_firstCal(false) +{ + // stub +} + +/// +/// Starts serial port communications. +/// +void SerialPort::start() +{ + beginInt(1U, SERIAL_SPEED); +} + +/// +/// Process data from serial port. +/// +void SerialPort::process() +{ + while (availableInt(1U)) { + uint8_t c = readInt(1U); + + if (m_ptr == 0U) { + if (c == DVM_FRAME_START) { + // Handle the frame start correctly + m_buffer[0U] = c; + m_ptr = 1U; + m_len = 0U; + } + else { + m_ptr = 0U; + m_len = 0U; + } + } + else if (m_ptr == 1U) { + // Handle the frame length + m_len = m_buffer[m_ptr] = c; + m_ptr = 2U; + } + else { + // Any other bytes are added to the buffer + m_buffer[m_ptr] = c; + m_ptr++; + + // The full packet has been received, process it + if (m_ptr == m_len) { + uint8_t err = 2U; + + switch (m_buffer[2U]) { + case CMD_GET_STATUS: + getStatus(); + break; + + case CMD_GET_VERSION: + getVersion(); + break; + + case CMD_SET_CONFIG: + err = setConfig(m_buffer + 3U, m_len - 3U); + if (err == RSN_OK) + sendACK(); + else + sendNAK(err); + break; + + case CMD_SET_MODE: + err = setMode(m_buffer + 3U, m_len - 3U); + if (err == RSN_OK) + sendACK(); + else + sendNAK(err); + break; + + case CMD_SET_SYMLVLADJ: + err = setSymbolLvlAdj(m_buffer + 3U, m_len - 3U); + if (err == RSN_OK) + sendACK(); + else + sendNAK(err); + break; + + case CMD_SET_RXLEVEL: + sendNAK(RSN_INVALID_REQUEST); // CMD_SET_RXLEVEL not supported by HS + break; + + case CMD_SET_RFPARAMS: + err = setRFParams(m_buffer + 3U, m_len - 3U); + if (err == RSN_OK) + sendACK(); + else + sendNAK(err); + break; + + case CMD_CAL_DATA: + if (m_modemState == STATE_DMR_DMO_CAL_1K || m_modemState == STATE_DMR_CAL_1K || + m_modemState == STATE_DMR_LF_CAL || m_modemState == STATE_DMR_CAL) + err = calDMR.write(m_buffer + 3U, m_len - 3U); +/* + if (m_modemState == STATE_P25_CAL_1K || m_modemState == STATE_P25_LF_CAL || m_modemState == STATE_P25_CAL) + err = calP25.write(m_buffer + 3U, m_len - 3U); +*/ + if (err == RSN_OK) { + sendACK(); + } + else { + DEBUG2("SerialPort: process(): received invalid calibration data", err); + sendNAK(err); + } + break; + + /** CW */ + case CMD_SEND_CWID: + err = RSN_RINGBUFF_FULL; + if (m_modemState == STATE_IDLE) { + m_cwIdState = true; + + io.rf1Conf(STATE_CW, true); + + err = cwIdTX.write(m_buffer + 3U, m_len - 3U); + } + if (err != RSN_OK) { + DEBUG2("SerialPort: process(): invalid CW Id data", err); + sendNAK(err); + } + break; + + /** Digital Mobile Radio */ + case CMD_DMR_DATA1: +#if defined(DUPLEX) + if (m_dmrEnable) { + if (m_modemState == STATE_IDLE || m_modemState == STATE_DMR) { + if (m_duplex) + err = dmrTX.writeData1(m_buffer + 3U, m_len - 3U); + } + } + if (err == RSN_OK) { + if (m_modemState == STATE_IDLE) + setMode(STATE_DMR); + } + else { + DEBUG2("SerialPort: process() received invalid DMR data", err); + sendNAK(err); + } +#else + sendNAK(RSN_INVALID_REQUEST); +#endif + break; + + case CMD_DMR_DATA2: + if (m_dmrEnable) { + if (m_modemState == STATE_IDLE || m_modemState == STATE_DMR) { +#if defined(DUPLEX) + if (m_duplex) + err = dmrTX.writeData2(m_buffer + 3U, m_len - 3U); + else + err = dmrDMOTX.writeData(m_buffer + 3U, m_len - 3U); +#else + err = dmrDMOTX.writeData(m_buffer + 3U, m_len - 3U); +#endif + } + } + if (err == RSN_OK) { + if (m_modemState == STATE_IDLE) + setMode(STATE_DMR); + } + else { + DEBUG2("SerialPort: process(): received invalid DMR data", err); + sendNAK(err); + } + break; + + case CMD_DMR_START: +#if defined(DUPLEX) + if (m_dmrEnable) { + err = RSN_INVALID_DMR_START; + if (m_len == 4U) { + if (m_buffer[3U] == 0x01U && m_modemState == STATE_DMR) { + if (!m_tx) + dmrTX.setStart(true); + err = RSN_OK; + } + else if (m_buffer[3U] == 0x00U && m_modemState == STATE_DMR) { + if (m_tx) + dmrTX.setStart(false); + err = RSN_OK; + } + } + } + if (err != RSN_OK) { + DEBUG3("SerialPort: process(): received invalid DMR start", err, m_len); + sendNAK(err); + } +#else + sendNAK(RSN_INVALID_REQUEST); +#endif + break; + + case CMD_DMR_SHORTLC: +#if defined(DUPLEX) + if (m_dmrEnable) + err = dmrTX.writeShortLC(m_buffer + 3U, m_len - 3U); + if (err != RSN_OK) { + DEBUG2("SerialPort: process(): received invalid DMR Short LC", err); + sendNAK(err); + } +#else + sendNAK(RSN_INVALID_REQUEST); +#endif + break; + + case CMD_DMR_ABORT: +#if defined(DUPLEX) + if (m_dmrEnable) + err = dmrTX.writeAbort(m_buffer + 3U, m_len - 3U); + if (err != RSN_OK) { + DEBUG2("SerialPort: process(): received invalid DMR Abort", err); + sendNAK(err); + } +#else + sendNAK(RSN_INVALID_REQUEST); +#endif + break; + + /** Project 25 */ + case CMD_P25_DATA: + if (m_p25Enable) { + if (m_modemState == STATE_IDLE || m_modemState == STATE_P25) + err = p25TX.writeData(m_buffer + 3U, m_len - 3U); + } + if (err == RSN_OK) { + if (m_modemState == STATE_IDLE) + setMode(STATE_P25); + } + else { + DEBUG2("SerialPort: process(): Received invalid P25 data", err); + sendNAK(err); + } + break; + + case CMD_P25_CLEAR: + if (m_p25Enable) { + if (m_modemState == STATE_IDLE || m_modemState == STATE_P25) + p25TX.clear(); + } + break; + + default: + // Handle this, send a NAK back + sendNAK(RSN_NAK); + break; + } + + m_ptr = 0U; + m_len = 0U; + } + } + } + + if (io.getWatchdog() >= 48000U) { + m_ptr = 0U; + m_len = 0U; + } +} + +/// +/// Write DMR frame data to serial port. +/// +/// +/// +/// +void SerialPort::writeDMRData(bool slot, const uint8_t* data, uint8_t length) +{ + if (m_modemState != STATE_DMR && m_modemState != STATE_IDLE) + return; + + if (!m_dmrEnable) + return; + + uint8_t reply[40U]; + + reply[0U] = DVM_FRAME_START; + reply[1U] = 0U; + reply[2U] = slot ? CMD_DMR_DATA2 : CMD_DMR_DATA1; + + uint8_t count = 3U; + for (uint8_t i = 0U; i < length; i++, count++) + reply[count] = data[i]; + + reply[1U] = count; + + writeInt(1U, reply, count); +} + +/// +/// Write lost DMR frame data to serial port. +/// +/// +void SerialPort::writeDMRLost(bool slot) +{ + if (m_modemState != STATE_DMR && m_modemState != STATE_IDLE) + return; + + if (!m_dmrEnable) + return; + + uint8_t reply[3U]; + + reply[0U] = DVM_FRAME_START; + reply[1U] = 3U; + reply[2U] = slot ? CMD_DMR_LOST2 : CMD_DMR_LOST1; + + writeInt(1U, reply, 3); +} + +/// +/// Write P25 frame data to serial port. +/// +/// +/// +void SerialPort::writeP25Data(const uint8_t* data, uint8_t length) +{ + if (m_modemState != STATE_P25 && m_modemState != STATE_IDLE) + return; + + if (!m_p25Enable) + return; + + uint8_t reply[250U]; + + reply[0U] = DVM_FRAME_START; + reply[1U] = 0U; + reply[2U] = CMD_P25_DATA; + + uint8_t count = 3U; + for (uint8_t i = 0U; i < length; i++, count++) + reply[count] = data[i]; + + reply[1U] = count; + + writeInt(1U, reply, count); +} + +/// +/// Write lost P25 frame data to serial port. +/// +void SerialPort::writeP25Lost() +{ + if (m_modemState != STATE_P25 && m_modemState != STATE_IDLE) + return; + + if (!m_p25Enable) + return; + + uint8_t reply[3U]; + + reply[0U] = DVM_FRAME_START; + reply[1U] = 3U; + reply[2U] = CMD_P25_LOST; + + writeInt(1U, reply, 3); +} + +/// +/// Write calibration frame data to serial port. +/// +/// +/// +void SerialPort::writeCalData(const uint8_t* data, uint8_t length) +{ + uint8_t reply[130U]; + + reply[0U] = DVM_FRAME_START; + reply[1U] = 0U; + reply[2U] = CMD_CAL_DATA; + + uint8_t count = 3U; + for (uint8_t i = 0U; i < length; i++, count++) + reply[count] = data[i]; + + reply[1U] = count; + + writeInt(1U, reply, count); +} + +/// +/// Write RSSI frame data to serial port. +/// +/// +/// +void SerialPort::writeRSSIData(const uint8_t* data, uint8_t length) +{ + if (m_modemState != STATE_RSSI_CAL) + return; + + uint8_t reply[30U]; + + reply[0U] = DVM_FRAME_START; + reply[1U] = 0U; + reply[2U] = CMD_RSSI_DATA; + + uint8_t count = 3U; + for (uint8_t i = 0U; i < length; i++, count++) + reply[count] = data[i]; + + reply[1U] = count; + + writeInt(1U, reply, count); +} + +/// +/// +/// +/// +void SerialPort::writeDebug(const char* text) +{ + if (!m_debug) + return; + + uint8_t reply[130U]; + + reply[0U] = DVM_FRAME_START; + reply[1U] = 0U; + reply[2U] = CMD_DEBUG1; + + uint8_t count = 3U; + for (uint8_t i = 0U; text[i] != '\0'; i++, count++) + reply[count] = text[i]; + + reply[1U] = count; + + writeInt(1U, reply, count, true); +} + +/// +/// +/// +/// +/// +void SerialPort::writeDebug(const char* text, int16_t n1) +{ + if (!m_debug) + return; + + uint8_t reply[130U]; + + reply[0U] = DVM_FRAME_START; + reply[1U] = 0U; + reply[2U] = CMD_DEBUG2; + + uint8_t count = 3U; + for (uint8_t i = 0U; text[i] != '\0'; i++, count++) + reply[count] = text[i]; + + reply[count++] = (n1 >> 8) & 0xFF; + reply[count++] = (n1 >> 0) & 0xFF; + + reply[1U] = count; + + writeInt(1U, reply, count, true); +} + +/// +/// +/// +/// +/// +/// +void SerialPort::writeDebug(const char* text, int16_t n1, int16_t n2) +{ + if (!m_debug) + return; + + uint8_t reply[130U]; + + reply[0U] = DVM_FRAME_START; + reply[1U] = 0U; + reply[2U] = CMD_DEBUG3; + + uint8_t count = 3U; + for (uint8_t i = 0U; text[i] != '\0'; i++, count++) + reply[count] = text[i]; + + reply[count++] = (n1 >> 8) & 0xFF; + reply[count++] = (n1 >> 0) & 0xFF; + + reply[count++] = (n2 >> 8) & 0xFF; + reply[count++] = (n2 >> 0) & 0xFF; + + reply[1U] = count; + + writeInt(1U, reply, count, true); +} + +/// +/// +/// +/// +/// +/// +/// +void SerialPort::writeDebug(const char* text, int16_t n1, int16_t n2, int16_t n3) +{ + if (!m_debug) + return; + + uint8_t reply[130U]; + + reply[0U] = DVM_FRAME_START; + reply[1U] = 0U; + reply[2U] = CMD_DEBUG4; + + uint8_t count = 3U; + for (uint8_t i = 0U; text[i] != '\0'; i++, count++) + reply[count] = text[i]; + + reply[count++] = (n1 >> 8) & 0xFF; + reply[count++] = (n1 >> 0) & 0xFF; + + reply[count++] = (n2 >> 8) & 0xFF; + reply[count++] = (n2 >> 0) & 0xFF; + + reply[count++] = (n3 >> 8) & 0xFF; + reply[count++] = (n3 >> 0) & 0xFF; + + reply[1U] = count; + + writeInt(1U, reply, count, true); +} + +/// +/// +/// +/// +/// +/// +/// +/// +void SerialPort::writeDebug(const char* text, int16_t n1, int16_t n2, int16_t n3, int16_t n4) +{ + if (!m_debug) + return; + + uint8_t reply[130U]; + + reply[0U] = DVM_FRAME_START; + reply[1U] = 0U; + reply[2U] = CMD_DEBUG5; + + uint8_t count = 3U; + for (uint8_t i = 0U; text[i] != '\0'; i++, count++) + reply[count] = text[i]; + + reply[count++] = (n1 >> 8) & 0xFF; + reply[count++] = (n1 >> 0) & 0xFF; + + reply[count++] = (n2 >> 8) & 0xFF; + reply[count++] = (n2 >> 0) & 0xFF; + + reply[count++] = (n3 >> 8) & 0xFF; + reply[count++] = (n3 >> 0) & 0xFF; + + reply[count++] = (n4 >> 8) & 0xFF; + reply[count++] = (n4 >> 0) & 0xFF; + + reply[1U] = count; + + writeInt(1U, reply, count, true); +} + +/// +/// +/// +/// +/// +void SerialPort::writeDump(const uint8_t* data, uint16_t length) +{ + if (!m_debug) + return; + + uint8_t reply[512U]; + + reply[0U] = DVM_FRAME_START; + + if (length > 252U) { + reply[1U] = 0U; + reply[2U] = (length + 4U) - 255U; + reply[3U] = CMD_DEBUG_DUMP; + + for (uint8_t i = 0U; i < length; i++) + reply[i + 4U] = data[i]; + + writeInt(1U, reply, length + 4U); + } + else { + reply[1U] = length + 3U; + reply[2U] = CMD_DEBUG_DUMP; + + for (uint8_t i = 0U; i < length; i++) + reply[i + 3U] = data[i]; + + writeInt(1U, reply, length + 3U); + } +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// Write acknowlegement. +/// +void SerialPort::sendACK() +{ + uint8_t reply[4U]; + + reply[0U] = DVM_FRAME_START; + reply[1U] = 4U; + reply[2U] = CMD_ACK; + reply[3U] = m_buffer[2U]; + + writeInt(1U, reply, 4); +} + +/// +/// Write negative acknowlegement. +/// +/// +void SerialPort::sendNAK(uint8_t err) +{ + uint8_t reply[5U]; + + reply[0U] = DVM_FRAME_START; + reply[1U] = 5U; + reply[2U] = CMD_NAK; + reply[3U] = m_buffer[2U]; + reply[4U] = err; + + writeInt(1U, reply, 5); +} + +/// +/// Write modem DSP status. +/// +void SerialPort::getStatus() +{ + io.resetWatchdog(); + + uint8_t reply[15U]; + + // send all sorts of interesting internal values + reply[0U] = DVM_FRAME_START; + reply[1U] = 11U; + reply[2U] = CMD_GET_STATUS; + + reply[3U] = 0x00U; + if (m_dmrEnable) + reply[3U] |= 0x02U; + if (m_p25Enable) + reply[3U] |= 0x08U; + + reply[4U] = uint8_t(m_modemState); + + reply[5U] = m_tx ? 0x01U : 0x00U; + + if (io.hasRXOverflow()) + reply[5U] |= 0x04U; + + if (io.hasTXOverflow()) + reply[5U] |= 0x08U; + + reply[5U] |= m_dcd ? 0x40U : 0x00U; + + reply[6U] = 0U; + + if (m_dmrEnable) { +#if defined(DUPLEX) + if (m_duplex) { + reply[7U] = dmrTX.getSpace1(); + reply[8U] = dmrTX.getSpace2(); + } else { + reply[7U] = 10U; + reply[8U] = dmrDMOTX.getSpace(); + } +#else + reply[7U] = 10U; + reply[8U] = dmrDMOTX.getSpace(); +#endif + } else { + reply[7U] = 0U; + reply[8U] = 0U; + } + + reply[9U] = 0U; + + if (m_p25Enable) + reply[10U] = p25TX.getSpace(); + else + reply[10U] = 0U; + + writeInt(1U, reply, 11); +} + +/// +/// Write modem DSP version. +/// +void SerialPort::getVersion() +{ + uint8_t reply[200U]; + + reply[0U] = DVM_FRAME_START; + reply[1U] = 0U; + reply[2U] = CMD_GET_VERSION; + + reply[3U] = PROTOCOL_VERSION; + + reply[4U] = io.getCPU(); + + // Reserve 16 bytes for the UDID + ::memcpy(reply + 5U, 0x00U, 16U); + io.getUDID(reply + 5U); + + uint8_t count = 4U; + for (uint8_t i = 0U; HARDWARE[i] != 0x00U; i++, count++) + reply[count] = HARDWARE[i]; + + reply[1U] = count; + + writeInt(1U, reply, count); +} + +/// +/// Helper to validate the passed modem state is valid. +/// +/// +/// +uint8_t SerialPort::modemStateCheck(DVM_STATE state) +{ + // invalid mode check + if (state != STATE_IDLE && state != STATE_DMR && state != STATE_P25 && + state != STATE_P25_CAL_1K && + state != STATE_DMR_DMO_CAL_1K && state != STATE_DMR_CAL_1K && + state != STATE_DMR_LF_CAL && state != STATE_P25_LF_CAL && + state != STATE_RSSI_CAL && + state != STATE_P25_CAL && state != STATE_DMR_CAL && + state != STATE_INT_CAL) + return RSN_INVALID_MODE; +/* + // DMR without DMR being enabled + if (state == STATE_DMR && !m_dmrEnable) + return RSN_DMR_DISABLED; + // P25 without P25 being enabled + if (state == STATE_P25 && !m_p25Enable) + return RSN_P25_DISABLED; +*/ + return RSN_OK; +} + +/// +/// Set modem DSP configuration from serial port data. +/// +/// +/// +/// +uint8_t SerialPort::setConfig(const uint8_t* data, uint8_t length) +{ + if (length < 14U) + return RSN_ILLEGAL_LENGTH; + + bool simplex = (data[0U] & 0x80U) == 0x80U; + + m_debug = (data[0U] & 0x10U) == 0x10U; + + bool dmrEnable = (data[1U] & 0x02U) == 0x02U; + bool p25Enable = (data[1U] & 0x08U) == 0x08U; + + uint8_t fdmaPreamble = data[2U]; + if (fdmaPreamble > 255U) + return RSN_INVALID_FDMA_PREAMBLE; + + DVM_STATE modemState = DVM_STATE(data[3U]); + + uint8_t ret = modemStateCheck(modemState); + if (ret != RSN_OK) + return ret; + + uint8_t colorCode = data[6U]; + if (colorCode > 15U) + return RSN_INVALID_DMR_CC; + +#if defined(DUPLEX) + uint8_t dmrRxDelay = data[7U]; + if (dmrRxDelay > 255U) + return RSN_INVALID_DMR_RX_DELAY; +#endif + + uint16_t nac = (data[8U] << 4) + (data[9U] >> 4); + + m_cwIdTXLevel = data[5U] >> 2; // ?? + + uint8_t dmrTXLevel = data[10U]; + uint8_t p25TXLevel = data[12U]; + + io.setDeviations(dmrTXLevel, p25TXLevel); + + uint8_t p25CorrCount = data[11U]; + if (p25CorrCount > 255U) + return RSN_INVALID_P25_CORR_COUNT; + + if (modemState == STATE_DMR_CAL || modemState == STATE_DMR_DMO_CAL_1K || modemState == STATE_RSSI_CAL || + modemState == STATE_INT_CAL) { + m_dmrEnable = true; + m_modemState = STATE_DMR; + m_calState = modemState; + if (m_firstCal) + io.updateCal(); + if (modemState == STATE_RSSI_CAL) + io.rf1Conf(STATE_DMR, true); + } else { + m_modemState = modemState; + m_calState = STATE_IDLE; + } + + m_dmrEnable = dmrEnable; + m_p25Enable = p25Enable; + m_duplex = !simplex; + +#if !defined(DUPLEX) + if (m_duplex && m_calState == STATE_IDLE) { + DEBUG1("Full duplex not supported with this firmware"); + return RSN_INVALID_STATE; + } +#elif defined(DUPLEX) && (defined(ZUMSPOT_ADF7021) || defined(LONESTAR_USB) || defined(SKYBRIDGE_HS)) + if (io.isDualBand() && m_duplex && m_calState == STATE_IDLE) { + DEBUG1("Full duplex is not supported on this board"); + return RSN_INVALID_STATE; + } +#endif + + p25TX.setPreambleCount(fdmaPreamble); + dmrDMOTX.setPreambleCount(fdmaPreamble); + + p25RX.setNAC(nac); + p25RX.setCorrCount(p25CorrCount); + +#if defined(DUPLEX) + dmrTX.setColorCode(colorCode); + dmrRX.setColorCode(colorCode); + dmrRX.setRxDelay(dmrRxDelay); + dmrIdleRX.setColorCode(colorCode); +#endif + + dmrDMORX.setColorCode(colorCode); + + if (!m_firstCal || (modemState != STATE_DMR_CAL && modemState != STATE_DMR_DMO_CAL_1K && + modemState != STATE_RSSI_CAL && modemState != STATE_INT_CAL)) { + if(m_dmrEnable) + io.rf1Conf(STATE_DMR, true); + else if(m_p25Enable) + io.rf1Conf(STATE_P25, true); + } + + io.start(); + + if (modemState == STATE_DMR_CAL || modemState == STATE_DMR_DMO_CAL_1K || + modemState == STATE_RSSI_CAL || modemState == STATE_INT_CAL) + m_firstCal = true; + + return RSN_OK; +} + +/// +/// Set modem DSP mode from serial port data. +/// +/// +/// +/// +uint8_t SerialPort::setMode(const uint8_t* data, uint8_t length) +{ + if (length < 1U) + return RSN_ILLEGAL_LENGTH; + + DVM_STATE modemState = DVM_STATE(data[0U]); + DVM_STATE tmpState; + + if (modemState == m_modemState) + return RSN_OK; + + uint8_t ret = modemStateCheck(modemState); + if (ret != RSN_OK) + return ret; + + if (modemState == STATE_DMR_CAL || modemState == STATE_DMR_DMO_CAL_1K || + modemState == STATE_RSSI_CAL || modemState == STATE_INT_CAL) { + m_dmrEnable = true; + tmpState = STATE_DMR; + m_calState = modemState; + if (m_firstCal) + io.updateCal(); + } else { + tmpState = modemState; + m_calState = STATE_IDLE; + } + + setMode(tmpState); + + return RSN_OK; +} + +/// +/// Sets the modem state. +/// +/// +void SerialPort::setMode(DVM_STATE modemState) +{ + switch (modemState) { + case STATE_DMR: + DEBUG1("SerialPort: setMode(): mode set to DMR"); + p25RX.reset(); + cwIdTX.reset(); + break; + case STATE_P25: + DEBUG1("SerialPort: setMode(): mode set to P25"); +#if defined(DUPLEX) + dmrIdleRX.reset(); + dmrRX.reset(); +#endif + dmrDMORX.reset(); + cwIdTX.reset(); + break; + case STATE_DMR_CAL: + DEBUG1("SerialPort: setMode(): mode set to DMR Calibrate"); +#if defined(DUPLEX) + dmrIdleRX.reset(); + dmrRX.reset(); +#endif + dmrDMORX.reset(); + p25RX.reset(); + cwIdTX.reset(); + break; + case STATE_P25_CAL: + DEBUG1("SerialPort: setMode(): mode set to P25 Calibrate"); +#if defined(DUPLEX) + dmrIdleRX.reset(); + dmrRX.reset(); +#endif + dmrDMORX.reset(); + p25RX.reset(); + cwIdTX.reset(); + break; + case STATE_P25_LF_CAL: + DEBUG1("SerialPort: setMode(): mode set to P25 80Hz Calibrate"); +#if defined(DUPLEX) + dmrIdleRX.reset(); + dmrRX.reset(); +#endif + dmrDMORX.reset(); + p25RX.reset(); + cwIdTX.reset(); + break; + case STATE_RSSI_CAL: + DEBUG1("SerialPort: setMode(): mode set to RSSI Calibrate"); +#if defined(DUPLEX) + dmrIdleRX.reset(); + dmrRX.reset(); +#endif + dmrDMORX.reset(); + p25RX.reset(); + cwIdTX.reset(); + break; + case STATE_DMR_LF_CAL: + DEBUG1("SerialPort: setMode(): mode set to DMR 80Hz Calibrate"); +#if defined(DUPLEX) + dmrIdleRX.reset(); + dmrRX.reset(); +#endif + dmrDMORX.reset(); + dmrRX.reset(); + p25RX.reset(); + cwIdTX.reset(); + break; + case STATE_DMR_CAL_1K: + DEBUG1("SerialPort: setMode(): mode set to DMR BS 1031Hz Calibrate"); +#if defined(DUPLEX) + dmrIdleRX.reset(); + dmrRX.reset(); +#endif + dmrDMORX.reset(); + p25RX.reset(); + cwIdTX.reset(); + break; + case STATE_DMR_DMO_CAL_1K: + DEBUG1("SerialPort: setMode(): mode set to DMR MS 1031Hz Calibrate"); +#if defined(DUPLEX) + dmrIdleRX.reset(); + dmrRX.reset(); +#endif + dmrDMORX.reset(); + p25RX.reset(); + cwIdTX.reset(); + break; + case STATE_P25_CAL_1K: + DEBUG1("SerialPort: setMode(): mode set to P25 1011Hz Calibrate"); +#if defined(DUPLEX) + dmrIdleRX.reset(); + dmrRX.reset(); +#endif + dmrDMORX.reset(); + p25RX.reset(); + cwIdTX.reset(); + break; + default: + DEBUG1("SerialPort: setMode(): mode set to Idle"); + // STATE_IDLE + break; + } + + m_modemState = modemState; + + if ((modemState != STATE_IDLE) && (m_modemStatePrev != modemState)) { + DEBUG1("SerialPort: setMode(): setting RF hardware"); + io.rf1Conf(modemState, true); + } + + io.setMode(m_modemState); +} + +/// +/// Sets the fine-tune symbol levels. +/// +/// +/// +/// +uint8_t SerialPort::setSymbolLvlAdj(const uint8_t* data, uint8_t length) +{ + if (length < 4U) + return RSN_ILLEGAL_LENGTH; + + int8_t dmrSymLvl3Adj = int8_t(data[0U]) - 128; + if (dmrSymLvl3Adj > 128) + return RSN_INVALID_REQUEST; + if (dmrSymLvl3Adj < -128) + return RSN_INVALID_REQUEST; + + int8_t dmrSymLvl1Adj = int8_t(data[1U]) - 128; + if (dmrSymLvl1Adj > 128) + return RSN_INVALID_REQUEST; + if (dmrSymLvl1Adj < -128) + return RSN_INVALID_REQUEST; + + int8_t p25SymLvl3Adj = int8_t(data[2U]) - 128; + if (p25SymLvl3Adj > 128) + return RSN_INVALID_REQUEST; + if (p25SymLvl3Adj < -128) + return RSN_INVALID_REQUEST; + + int8_t p25SymLvl1Adj = int8_t(data[3U]) - 128; + if (p25SymLvl1Adj > 128) + return RSN_INVALID_REQUEST; + if (p25SymLvl1Adj < -128) + return RSN_INVALID_REQUEST; + + p25TX.setSymbolLvlAdj(p25SymLvl3Adj, p25SymLvl1Adj); + + dmrDMOTX.setSymbolLvlAdj(dmrSymLvl3Adj, dmrSymLvl1Adj); + dmrTX.setSymbolLvlAdj(dmrSymLvl3Adj, dmrSymLvl1Adj); + + return RSN_OK; +} + +/// +/// Sets the RF parameters. +/// +/// +/// +/// +uint8_t SerialPort::setRFParams(const uint8_t* data, uint8_t length) +{ + if (length < 10U) + return RSN_ILLEGAL_LENGTH; + + uint32_t rxFreq, txFreq; + uint8_t rfPower; + + rxFreq = data[1U] << 0; + rxFreq |= data[2U] << 8; + rxFreq |= data[3U] << 16; + rxFreq |= data[4U] << 24; + + txFreq = data[5U] << 0; + txFreq |= data[6U] << 8; + txFreq |= data[7U] << 16; + txFreq |= data[8U] << 24; + + rfPower = data[9U]; + + return io.setRFParams(rxFreq, txFreq, rfPower); +} diff --git a/SerialPort.h b/SerialPort.h new file mode 100644 index 0000000..8e9e15e --- /dev/null +++ b/SerialPort.h @@ -0,0 +1,218 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2018,2020,2021 by Jonathan Naylor G4KLX +* Copyright (C) 2018 by Andy Uribe CA6JAU +* Copyright (C) 2018,2021 Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__SERIAL_PORT_H__) +#define __SERIAL_PORT_H__ + +#include "Defines.h" +#include "Globals.h" +#include "SerialBuffer.h" + + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + +enum DVM_STATE { + STATE_IDLE = 0U, + // DMR + STATE_DMR = 1U, + // Project 25 + STATE_P25 = 2U, + + // CW + STATE_CW = 10U, + + // Calibration States + STATE_P25_LF_CAL = 91U, + STATE_P25_CAL_1K = 92U, + + STATE_DMR_DMO_CAL_1K = 93U, + STATE_DMR_CAL_1K = 94U, + STATE_DMR_LF_CAL = 95U, + + STATE_RSSI_CAL = 96U, + + STATE_P25_CAL = 97U, + STATE_DMR_CAL = 98U, + STATE_INT_CAL = 99U +}; + +enum DVM_COMMANDS { + CMD_GET_VERSION = 0x00U, + CMD_GET_STATUS = 0x01U, + CMD_SET_CONFIG = 0x02U, + CMD_SET_MODE = 0x03U, + + CMD_SET_SYMLVLADJ = 0x04U, + CMD_SET_RXLEVEL = 0x05U, + CMD_SET_RFPARAMS = 0x06U, + + CMD_CAL_DATA = 0x08U, + CMD_RSSI_DATA = 0x09U, + + CMD_SEND_CWID = 0x0AU, + + CMD_DMR_DATA1 = 0x18U, + CMD_DMR_LOST1 = 0x19U, + CMD_DMR_DATA2 = 0x1AU, + CMD_DMR_LOST2 = 0x1BU, + CMD_DMR_SHORTLC = 0x1CU, + CMD_DMR_START = 0x1DU, + CMD_DMR_ABORT = 0x1EU, + + CMD_P25_DATA = 0x31U, + CMD_P25_LOST = 0x32U, + CMD_P25_CLEAR = 0x33U, + + CMD_ACK = 0x70U, + CMD_NAK = 0x7FU, + + CMD_DEBUG1 = 0xF1U, + CMD_DEBUG2 = 0xF2U, + CMD_DEBUG3 = 0xF3U, + CMD_DEBUG4 = 0xF4U, + CMD_DEBUG5 = 0xF5U, + CMD_DEBUG_DUMP = 0xFAU, +}; + +enum CMD_REASON_CODE { + RSN_OK = 0U, + RSN_NAK = 1U, + + RSN_ILLEGAL_LENGTH = 2U, + RSN_INVALID_REQUEST = 4U, + RSN_RINGBUFF_FULL = 8U, + + RSN_INVALID_FDMA_PREAMBLE = 10U, + RSN_INVALID_MODE = 11U, + + RSN_INVALID_DMR_CC = 12U, + RSN_INVALID_DMR_SLOT = 13U, + RSN_INVALID_DMR_START = 14U, + RSN_INVALID_DMR_RX_DELAY = 15U, + + RSN_INVALID_P25_CORR_COUNT = 16U, + + RSN_DMR_DISABLED = 63U, + RSN_P25_DISABLED = 64U, +}; + +const uint8_t DVM_FRAME_START = 0xFEU; + +#define SERIAL_SPEED 115200 + +// --------------------------------------------------------------------------- +// Class Declaration +// Implements the RS232 serial bus to communicate with the HOST S/W. +// --------------------------------------------------------------------------- + +class DSP_FW_API SerialPort { +public: + /// Initializes a new instance of the SerialPort class. + SerialPort(); + + /// Starts serial port communications. + void start(); + + /// Process data from serial port. + void process(); + + /// Write DMR frame data to serial port. + void writeDMRData(bool slot, const uint8_t* data, uint8_t length); + /// Write lost DMR frame data to serial port. + void writeDMRLost(bool slot); + + /// Write P25 frame data to serial port. + void writeP25Data(const uint8_t* data, uint8_t length); + /// Write lost P25 frame data to serial port. + void writeP25Lost(); + + /// Write calibration frame data to serial port. + void writeCalData(const uint8_t* data, uint8_t length); + /// Write RSSI frame data to serial port. + void writeRSSIData(const uint8_t* data, uint8_t length); + + /// + void writeDebug(const char* text); + /// + void writeDebug(const char* text, int16_t n1); + /// + void writeDebug(const char* text, int16_t n1, int16_t n2); + /// + void writeDebug(const char* text, int16_t n1, int16_t n2, int16_t n3); + /// + void writeDebug(const char* text, int16_t n1, int16_t n2, int16_t n3, int16_t n4); + /// + void writeDump(const uint8_t* data, uint16_t length); + +private: + uint8_t m_buffer[256U]; + uint8_t m_ptr; + uint8_t m_len; + + bool m_debug; + bool m_firstCal; + + /// Write acknowlegement. + void sendACK(); + /// Write negative acknowlegement. + void sendNAK(uint8_t err); + /// Write modem DSP status. + void getStatus(); + /// Write modem DSP version. + void getVersion(); + /// Helper to validate the passed modem state is valid. + uint8_t modemStateCheck(DVM_STATE state); + /// Set modem DSP configuration from serial port data. + uint8_t setConfig(const uint8_t* data, uint8_t length); + /// Set modem DSP mode from serial port data. + uint8_t setMode(const uint8_t* data, uint8_t length); + /// Sets the modem state. + void setMode(DVM_STATE modemState); + /// Sets the fine-tune symbol levels. + uint8_t setSymbolLvlAdj(const uint8_t* data, uint8_t length); + /// Sets the RF parameters. + uint8_t setRFParams(const uint8_t* data, uint8_t length); + + // Hardware specific routines + /// + void beginInt(uint8_t n, int speed); + /// + int availableInt(uint8_t n); + /// + int availableForWriteInt(uint8_t n); + /// + uint8_t readInt(uint8_t n); + /// + void writeInt(uint8_t n, const uint8_t* data, uint16_t length, bool flush = false); +}; + +#endif // __SERIAL_PORT_H__ diff --git a/SerialSTM.cpp b/SerialSTM.cpp new file mode 100644 index 0000000..50ffe01 --- /dev/null +++ b/SerialSTM.cpp @@ -0,0 +1,316 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jim McLaughlin KI6ZUM +* Copyright (C) 2016,2017,2018,2019 by Andy Uribe CA6JAU +* Copyright (C) 2021 Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Globals.h" +#include "SerialPort.h" +#include "STM_UART.h" + +/* + Pin definitions: + + - Host communication: + 1) USART1 - TXD PA9 - RXD PA10 + 2) USB VCOM + + - Serial repeater + USART2 - TXD PA2 - RXD PA3 +*/ + +#if defined(STM32F10X_MD) + +#if defined(STM32_USB_HOST) +#include +#endif + +#if defined(STM32_USART1_HOST) && defined(STM32_USB_HOST) +#error "You have to select STM32_USART1_HOST or STM32_USB_HOST, but not both" +#endif + +#if defined(STM32_USART1_HOST) || defined(SERIAL_REPEATER_USART1) +// --------------------------------------------------------------------------- +// Global Functions and Variables +// --------------------------------------------------------------------------- + +extern "C" { + void USART1_IRQHandler(); + void USART2_IRQHandler(); +} + +// --------------------------------------------------------------------------- +// UART1 +// --------------------------------------------------------------------------- + +static STM_UART m_USART1; + +/// +/// +/// +void USART1_IRQHandler() +{ + m_USART1.handleIRQ(); +} + +/// +/// +/// +/// +void InitUSART1(int speed) +{ + // USART1 - TXD PA9 - RXD PA10 + GPIO_InitTypeDef GPIO_InitStructure; + USART_InitTypeDef USART_InitStructure; + NVIC_InitTypeDef NVIC_InitStructure; + + RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); + RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); + + // USART IRQ init + NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; + NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; + NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 15; + NVIC_InitStructure.NVIC_IRQChannelSubPriority = 15; + NVIC_Init(&NVIC_InitStructure); + + // Configure USART as alternate function + GPIO_StructInit(&GPIO_InitStructure); + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // Tx + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_Init(GPIOA, &GPIO_InitStructure); + + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // Rx + GPIO_Init(GPIOA, &GPIO_InitStructure); + + // Configure USART baud rate + USART_StructInit(&USART_InitStructure); + USART_InitStructure.USART_BaudRate = speed; + USART_InitStructure.USART_WordLength = USART_WordLength_8b; + USART_InitStructure.USART_StopBits = USART_StopBits_1; + USART_InitStructure.USART_Parity = USART_Parity_No; + USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; + USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; + USART_Init(USART1, &USART_InitStructure); + + USART_Cmd(USART1, ENABLE); + + USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); + + m_USART1.init(USART1); +} + +#endif + +// --------------------------------------------------------------------------- +// UART2 +// --------------------------------------------------------------------------- + +static STM_UART m_USART2; + +/// +/// +/// +void USART2_IRQHandler() +{ + m_USART2.handleIRQ(); +} + +/// +/// +/// +/// +void InitUSART2(int speed) +{ + // USART2 - TXD PA2 - RXD PA3 + GPIO_InitTypeDef GPIO_InitStructure; + USART_InitTypeDef USART_InitStructure; + NVIC_InitTypeDef NVIC_InitStructure; + + RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); + RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); + + // USART IRQ init + NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; + NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; + NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 15; + NVIC_InitStructure.NVIC_IRQChannelSubPriority = 15; + NVIC_Init(&NVIC_InitStructure); + + // Configure USART as alternate function + GPIO_StructInit(&GPIO_InitStructure); + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; // Tx + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_Init(GPIOA, &GPIO_InitStructure); + + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; // Rx + GPIO_Init(GPIOA, &GPIO_InitStructure); + + // Configure USART baud rate + USART_StructInit(&USART_InitStructure); + USART_InitStructure.USART_BaudRate = speed; + USART_InitStructure.USART_WordLength = USART_WordLength_8b; + USART_InitStructure.USART_StopBits = USART_StopBits_1; + USART_InitStructure.USART_Parity = USART_Parity_No; + USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; + USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; + USART_Init(USART2, &USART_InitStructure); + + USART_Cmd(USART2, ENABLE); + + USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); + + m_USART2.init(USART2); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// +/// +/// +/// +void SerialPort::beginInt(uint8_t n, int speed) +{ + switch (n) { + case 1U: +#if defined(STM32_USART1_HOST) + InitUSART1(speed); +#elif defined(STM32_USB_HOST) + usbserial.begin(); +#endif + break; + case 3U: + InitUSART2(speed); + break; + default: + break; + } +} + +/// +/// +/// +/// +/// +int SerialPort::availableInt(uint8_t n) +{ + switch (n) { + case 1U: +#if defined(STM32_USART1_HOST) + return m_USART1.available(); +#elif defined(STM32_USB_HOST) + return usbserial.available(); +#endif + case 3U: + m_USART2.available(); + default: + return 0; + } +} + +/// +/// +/// +/// +/// +int SerialPort::availableForWriteInt(uint8_t n) +{ + switch (n) { + case 1U: +#if defined(STM32_USART1_HOST) + return m_USART1.availableForWrite(); +#elif defined(STM32_USB_HOST) + return usbserial.availableForWrite(); +#endif + case 3U: + return m_USART2.availableForWrite(); + default: + return 0; + } +} + +/// +/// +/// +/// +/// +uint8_t SerialPort::readInt(uint8_t n) +{ + switch (n) { + case 1U: +#if defined(STM32_USART1_HOST) + return m_USART1.read(); +#elif defined(STM32_USB_HOST) + return usbserial.read(); +#endif + case 3U: + return m_USART2.read(); + default: + return 0U; + } +} + + +/// +/// +/// +/// +/// +/// +/// +void SerialPort::writeInt(uint8_t n, const uint8_t* data, uint16_t length, bool flush) +{ + switch (n) { + case 1U: +#if defined(STM32_USART1_HOST) + m_USART1.write(data, length); + if (flush) + m_USART1.flush(); +#elif defined(STM32_USB_HOST) + usbserial.write(data, length); + if (flush) + usbserial.flush(); +#endif + break; + case 3U: + m_USART2.write(data, length); + if (flush) + m_USART2.flush(); + break; + default: + break; + } +} + +#endif diff --git a/Utils.cpp b/Utils.cpp new file mode 100644 index 0000000..bb2e0de --- /dev/null +++ b/Utils.cpp @@ -0,0 +1,86 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2020 by Jonathan Naylor G4KLX +* Copyright (C) 2017 by Andy Uribe CA6JAU +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Utils.h" + +// --------------------------------------------------------------------------- +// Constants/Macros +// --------------------------------------------------------------------------- + +const uint8_t BITS_TABLE[] = { +# define B2(n) n, n+1, n+1, n+2 +# define B4(n) B2(n), B2(n+1), B2(n+1), B2(n+2) +# define B6(n) B4(n), B4(n+1), B4(n+1), B4(n+2) + B6(0), B6(1), B6(1), B6(2) +}; + +// --------------------------------------------------------------------------- +// Global Functions +// --------------------------------------------------------------------------- + +uint8_t countBits8(uint8_t bits) +{ + return BITS_TABLE[bits]; +} + +uint8_t countBits16(uint16_t bits) +{ + uint8_t* p = (uint8_t*)&bits; + uint8_t n = 0U; + n += BITS_TABLE[p[0U]]; + n += BITS_TABLE[p[1U]]; + return n; +} + +uint8_t countBits32(uint32_t bits) +{ + uint8_t* p = (uint8_t*)&bits; + uint8_t n = 0U; + n += BITS_TABLE[p[0U]]; + n += BITS_TABLE[p[1U]]; + n += BITS_TABLE[p[2U]]; + n += BITS_TABLE[p[3U]]; + return n; +} + +uint8_t countBits64(uint64_t bits) +{ + uint8_t* p = (uint8_t*)&bits; + uint8_t n = 0U; + n += BITS_TABLE[p[0U]]; + n += BITS_TABLE[p[1U]]; + n += BITS_TABLE[p[2U]]; + n += BITS_TABLE[p[3U]]; + n += BITS_TABLE[p[4U]]; + n += BITS_TABLE[p[5U]]; + n += BITS_TABLE[p[6U]]; + n += BITS_TABLE[p[7U]]; + return n; +} diff --git a/Utils.h b/Utils.h new file mode 100644 index 0000000..054daec --- /dev/null +++ b/Utils.h @@ -0,0 +1,53 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2020 by Jonathan Naylor G4KLX +* Copyright (C) 2017 by Andy Uribe CA6JAU +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__UTILS_H__) +#define __UTILS_H__ + +#if defined(STM32F10X_MD) +#include "stm32f10x.h" +#elif defined(STM32F4XX) +#include "stm32f4xx.h" +#elif defined(STM32F7XX) +#include "stm32f7xx.h" +#endif + +#include "Defines.h" + +// --------------------------------------------------------------------------- +// Global Functions +// --------------------------------------------------------------------------- + +DSP_FW_API uint8_t countBits8(uint8_t bits); +DSP_FW_API uint8_t countBits16(uint16_t bits); +DSP_FW_API uint8_t countBits32(uint32_t bits); +DSP_FW_API uint8_t countBits64(ulong64_t bits); + +#endif // __UTILS_H__ diff --git a/bootloader.ld b/bootloader.ld new file mode 100644 index 0000000..4738f25 --- /dev/null +++ b/bootloader.ld @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2016-2018 by Andy Uribe CA6JAU + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* Memory areas */ +MEMORY +{ + ROM (rx) : ORIGIN = 0x08002000, LENGTH = 120K /* FLASH */ + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K /* Main RAM */ +} + +INCLUDE stm32f10x_link.ld diff --git a/dmr/CalDMR.cpp b/dmr/CalDMR.cpp new file mode 100644 index 0000000..af76239 --- /dev/null +++ b/dmr/CalDMR.cpp @@ -0,0 +1,286 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2009-2015 by Jonathan Naylor G4KLX +* Copyright (C) 2016 by Colin Durbridge G4EML +* Copyright (C) 2018,2019 by Andy Uribe CA6JAU +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Globals.h" +#include "dmr/CalDMR.h" + +using namespace dmr; + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +// Voice LC Header, CC: 1, srcID: 1, dstID: TG9 +const uint8_t VH_1K[] = { 0x00U, + 0x00U, 0x20U, 0x08U, 0x08U, 0x02U, 0x38U, 0x15U, 0x00U, 0x2CU, 0xA0U, 0x14U, + 0x60U, 0x84U, 0x6DU, 0xFFU, 0x57U, 0xD7U, 0x5DU, 0xF5U, 0xDEU, 0x30U, 0x30U, + 0x01U, 0x10U, 0x01U, 0x40U, 0x03U, 0xC0U, 0x13U, 0xC1U, 0x1EU, 0x80U, 0x6FU }; + +// Voice Term with LC, CC: 1, srcID: 1, dstID: TG9 +const uint8_t VT_1K[] = { 0x00U, + 0x00U, 0x4FU, 0x08U, 0xDCU, 0x02U, 0x88U, 0x15U, 0x78U, 0x2CU, 0xD0U, 0x14U, + 0xC0U, 0x84U, 0xADU, 0xFFU, 0x57U, 0xD7U, 0x5DU, 0xF5U, 0xD9U, 0x65U, 0x24U, + 0x02U, 0x28U, 0x06U, 0x20U, 0x0FU, 0x80U, 0x1BU, 0xC1U, 0x07U, 0x80U, 0x5CU }; + +// Voice LC MS Header, CC: 1, srcID: 1, dstID: TG9 +const uint8_t VH_DMO1K[] = { 0x00U, + 0x00U, 0x20U, 0x08U, 0x08U, 0x02U, 0x38U, 0x15U, 0x00U, 0x2CU, 0xA0U, 0x14U, + 0x60U, 0x84U, 0x6DU, 0x5DU, 0x7FU, 0x77U, 0xFDU, 0x75U, 0x7EU, 0x30U, 0x30U, + 0x01U, 0x10U, 0x01U, 0x40U, 0x03U, 0xC0U, 0x13U, 0xC1U, 0x1EU, 0x80U, 0x6FU }; + +// Voice Term MS with LC, CC: 1, srcID: 1, dstID: TG9 +const uint8_t VT_DMO1K[] = { 0x00U, + 0x00U, 0x4FU, 0x08U, 0xDCU, 0x02U, 0x88U, 0x15U, 0x78U, 0x2CU, 0xD0U, 0x14U, + 0xC0U, 0x84U, 0xADU, 0x5DU, 0x7FU, 0x77U, 0xFDU, 0x75U, 0x79U, 0x65U, 0x24U, + 0x02U, 0x28U, 0x06U, 0x20U, 0x0FU, 0x80U, 0x1BU, 0xC1U, 0x07U, 0x80U, 0x5CU }; + +// Voice coding data + FEC, 1031 Hz Test Pattern +const uint8_t VOICE_1K[] = { 0x00U, + 0xCEU, 0xA8U, 0xFEU, 0x83U, 0xACU, 0xC4U, 0x58U, 0x20U, 0x0AU, 0xCEU, 0xA8U, + 0xFEU, 0x83U, 0xA0U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x0CU, 0xC4U, 0x58U, + 0x20U, 0x0AU, 0xCEU, 0xA8U, 0xFEU, 0x83U, 0xACU, 0xC4U, 0x58U, 0x20U, 0x0AU }; + +// Embedded LC: CC: 1, srcID: 1, dstID: TG9 +const uint8_t SYNCEMB_1K[6][7] = { + { 0x07U, 0x55U, 0xFDU, 0x7DU, 0xF7U, 0x5FU, 0x70U }, // BS VOICE SYNC (audio seq 0) + { 0x01U, 0x30U, 0x00U, 0x00U, 0x90U, 0x09U, 0x10U }, // EMB + Embedded LC1 (audio seq 1) + { 0x01U, 0x70U, 0x00U, 0x90U, 0x00U, 0x07U, 0x40U }, // EMB + Embedded LC2 (audio seq 2) + { 0x01U, 0x70U, 0x00U, 0x31U, 0x40U, 0x07U, 0x40U }, // EMB + Embedded LC3 (audio seq 3) + { 0x01U, 0x50U, 0xA1U, 0x71U, 0xD1U, 0x70U, 0x70U }, // EMB + Embedded LC4 (audio seq 4) + { 0x01U, 0x10U, 0x00U, 0x00U, 0x00U, 0x0EU, 0x20U } }; // EMB (audio seq 5) + +// Embedded LC MS: CC: 1, srcID: 1, dstID: TG9 +const uint8_t SYNCEMB_DMO1K[6][7] = { + { 0x07U, 0xF7U, 0xD5U, 0xDDU, 0x57U, 0xDFU, 0xD0U }, // MS VOICE SYNC (audio seq 0) + { 0x01U, 0x30U, 0x00U, 0x00U, 0x90U, 0x09U, 0x10U }, // EMB + Embedded LC1 (audio seq 1) + { 0x01U, 0x70U, 0x00U, 0x90U, 0x00U, 0x07U, 0x40U }, // EMB + Embedded LC2 (audio seq 2) + { 0x01U, 0x70U, 0x00U, 0x31U, 0x40U, 0x07U, 0x40U }, // EMB + Embedded LC3 (audio seq 3) + { 0x01U, 0x50U, 0xA1U, 0x71U, 0xD1U, 0x70U, 0x70U }, // EMB + Embedded LC4 (audio seq 4) + { 0x01U, 0x10U, 0x00U, 0x00U, 0x00U, 0x0EU, 0x20U } }; // EMB (audio seq 5) + +// Short LC: +// TS1: dstID: 0, ACTIVITY_NONE +// TS2: dstID: TG9, ACTIVITY_VOICE +const uint8_t SHORTLC_1K[] = { 0x33U, 0x3AU, 0xA0U, 0x30U, 0x00U, 0x55U, 0xA6U, 0x5FU, 0x50U }; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the CalDMR class. +/// +CalDMR::CalDMR() : + m_transmit(false), + m_state(DMRCAL1K_IDLE), + m_frameStart(0U), + m_dmr1k(), + m_audioSeq(0), + m_count(0) +{ + ::memcpy(m_dmr1k, VOICE_1K, DMR_FRAME_LENGTH_BYTES + 1U); +} + +/// +/// Process local state and transmit on the air interface. +/// +void CalDMR::process() +{ + switch (m_calState) { + case STATE_DMR_CAL: + case STATE_DMR_LF_CAL: + if (m_transmit) { + dmrDMOTX.setCal(true); + dmrDMOTX.process(); + } + else { + dmrDMOTX.setCal(false); + } + break; + case STATE_DMR_CAL_1K: + dmr1kcal(); + break; + case STATE_DMR_DMO_CAL_1K: + dmrDMO1kcal(); + break; + case STATE_INT_CAL: + // Simple interrupt counter for board diagnostics (TCXO, connections, etc) + // Not intended for precise interrupt frequency measurements + m_count++; + if (m_count >= CAL_DLY_LOOP) { + m_count = 0U; + uint16_t int1, int2; + io.getIntCounter(int1, int2); + DEBUG3("Counter INT1/INT2:", int1 >> 1U, int2); + } + break; + default: + break; + } +} + +/// +/// +/// +/// +void CalDMR::createData1k(uint8_t n) +{ + for (uint8_t i = 0; i < 5U; i++) + m_dmr1k[i + 15U] = SYNCEMB_1K[n][i + 1U]; + + m_dmr1k[14U] &= 0xF0U; + m_dmr1k[20U] &= 0x0FU; + m_dmr1k[14U] |= SYNCEMB_1K[n][0] & 0x0FU; + m_dmr1k[20U] |= SYNCEMB_1K[n][6] & 0xF0U; +} + +/// +/// +/// +/// +void CalDMR::createDataDMO1k(uint8_t n) +{ + for (uint8_t i = 0; i < 5U; i++) + m_dmr1k[i + 15U] = SYNCEMB_DMO1K[n][i + 1U]; + + m_dmr1k[14U] &= 0xF0U; + m_dmr1k[20U] &= 0x0FU; + m_dmr1k[14U] |= SYNCEMB_DMO1K[n][0] & 0x0FU; + m_dmr1k[20U] |= SYNCEMB_DMO1K[n][6] & 0xF0U; +} + +/// +/// +/// +void CalDMR::dmr1kcal() +{ + dmrTX.process(); + + uint16_t space = dmrTX.getSpace2(); + if (space < 1U) + return; + + switch (m_state) { + case DMRCAL1K_VH: + dmrTX.setColorCode(1U); + dmrTX.writeShortLC(SHORTLC_1K, 9U); + dmrTX.writeData2(VH_1K, DMR_FRAME_LENGTH_BYTES + 1U); + dmrTX.setStart(true); + m_state = DMRCAL1K_VOICE; + break; + case DMRCAL1K_VOICE: + createData1k(m_audioSeq); + dmrTX.writeData2(m_dmr1k, DMR_FRAME_LENGTH_BYTES + 1U); + if (m_audioSeq == 5U) { + m_audioSeq = 0U; + if (!m_transmit) + m_state = DMRCAL1K_VT; + } + else + m_audioSeq++; + break; + case DMRCAL1K_VT: + dmrTX.writeData2(VT_1K, DMR_FRAME_LENGTH_BYTES + 1U); + m_frameStart = dmrTX.getFrameCount(); + m_state = DMRCAL1K_WAIT; + break; + case DMRCAL1K_WAIT: + if (dmrTX.getFrameCount() > (m_frameStart + 30U)) { + dmrTX.setStart(false); + dmrTX.resetFifo2(); + m_audioSeq = 0U; + m_state = DMRCAL1K_IDLE; + } + break; + default: + m_state = DMRCAL1K_IDLE; + break; + } +} + +/// +/// +/// +void CalDMR::dmrDMO1kcal() +{ + dmrDMOTX.process(); + + uint16_t space = dmrDMOTX.getSpace(); + if (space < 1U) + return; + + switch (m_state) { + case DMRCAL1K_VH: + dmrDMOTX.writeData(VH_DMO1K, DMR_FRAME_LENGTH_BYTES + 1U); + m_state = DMRCAL1K_VOICE; + break; + case DMRCAL1K_VOICE: + createDataDMO1k(m_audioSeq); + dmrDMOTX.writeData(m_dmr1k, DMR_FRAME_LENGTH_BYTES + 1U); + if (m_audioSeq == 5U) { + m_audioSeq = 0U; + if (!m_transmit) + m_state = DMRCAL1K_VT; + } + else + m_audioSeq++; + break; + case DMRCAL1K_VT: + dmrDMOTX.writeData(VT_DMO1K, DMR_FRAME_LENGTH_BYTES + 1U); + m_state = DMRCAL1K_IDLE; + break; + default: + m_state = DMRCAL1K_IDLE; + m_audioSeq = 0U; + break; + } +} + +/// +/// Write DMR calibration state. +/// +/// +/// +/// +uint8_t CalDMR::write(const uint8_t* data, uint8_t length) +{ + if (length != 1U) + return RSN_ILLEGAL_LENGTH; + + m_transmit = data[0U] == 1U; + + if (m_transmit && m_state == DMRCAL1K_IDLE && (m_modemState == STATE_DMR_CAL_1K || m_modemState == STATE_DMR_DMO_CAL_1K)) + m_state = DMRCAL1K_VH; + + if (m_transmit) + io.rf1Conf(STATE_DMR, true); + + return RSN_OK; +} diff --git a/dmr/CalDMR.h b/dmr/CalDMR.h new file mode 100644 index 0000000..5d0b097 --- /dev/null +++ b/dmr/CalDMR.h @@ -0,0 +1,90 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2009-2015 by Jonathan Naylor G4KLX +* Copyright (C) 2016 by Colin Durbridge G4EML +* Copyright (C) 2018 by Andy Uribe CA6JAU +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__CAL_DMR_H__) +#define __CAL_DMR_H__ + +#include "Defines.h" +#include "dmr/DMRDefines.h" + +namespace dmr +{ + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + enum DMRCAL1K { + DMRCAL1K_IDLE, + DMRCAL1K_VH, + DMRCAL1K_VOICE, + DMRCAL1K_VT, + DMRCAL1K_WAIT + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Implements logic for DMR calibration mode. + // --------------------------------------------------------------------------- + + class DSP_FW_API CalDMR { + public: + /// Initializes a new instance of the CalDMR class. + CalDMR(); + + /// Process local state and transmit on the air interface. + void process(); + + /// + void createData1k(uint8_t n); + /// + void createDataDMO1k(uint8_t n); + + /// + void dmr1kcal(); + /// + void dmrDMO1kcal(); + + /// Write DMR calibration state. + uint8_t write(const uint8_t* data, uint8_t length); + + private: + bool m_transmit; + DMRCAL1K m_state; + uint32_t m_frameStart; + + uint8_t m_dmr1k[DMR_FRAME_LENGTH_BYTES + 1U]; + + uint8_t m_audioSeq; + uint32_t m_count; + }; +} // namespace dmr + +#endif // __CAL_DMR_H__ diff --git a/dmr/DMRDMORX.cpp b/dmr/DMRDMORX.cpp new file mode 100644 index 0000000..622eabd --- /dev/null +++ b/dmr/DMRDMORX.cpp @@ -0,0 +1,354 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2009-2016 by Jonathan Naylor G4KLX +* Copyright (C) 2016,2017,2018 by Andy Uribe CA6JAU +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Globals.h" +#include "dmr/DMRDMORX.h" +#include "dmr/DMRSlotType.h" +#include "Utils.h" + +using namespace dmr; + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint8_t MAX_SYNC_BYTES_ERRS = 3U; + +const uint8_t MAX_SYNC_LOST_FRAMES = 13U; + +const uint16_t NOENDPTR = 9999U; + +const uint8_t CONTROL_NONE = 0x00U; +const uint8_t CONTROL_VOICE = 0x20U; +const uint8_t CONTROL_DATA = 0x40U; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the DMRDMORX class. +/// +DMRDMORX::DMRDMORX() : + m_bitBuffer(0x00U), + m_buffer(), + m_dataPtr(0U), + m_syncPtr(0U), + m_startPtr(0U), + m_endPtr(NOENDPTR), + m_control(CONTROL_NONE), + m_syncCount(0U), + m_colorCode(0U), + m_state(DMORXS_NONE), + m_n(0U), + m_type(0U) +{ + /* stub */ +} + +/// +/// Helper to reset data values to defaults. +/// +void DMRDMORX::reset() +{ + m_syncPtr = 0U; + m_control = CONTROL_NONE; + m_syncCount = 0U; + m_state = DMORXS_NONE; + m_startPtr = 0U; + m_endPtr = NOENDPTR; +} + +/// +/// Sample DMR bits from the air interface. +/// +/// +void DMRDMORX::databit(bool bit) +{ + _WRITE_BIT(m_buffer, m_dataPtr, bit); + + m_bitBuffer <<= 1; + if (bit) + m_bitBuffer |= 0x01U; + + if (m_state == DMORXS_NONE) { + correlateSync(); + } + else { + uint16_t min = m_syncPtr + DMO_BUFFER_LENGTH_BITS - 2; + uint16_t max = m_syncPtr + 2; + + if (min >= DMO_BUFFER_LENGTH_BITS) + min -= DMO_BUFFER_LENGTH_BITS; + if (max >= DMO_BUFFER_LENGTH_BITS) + max -= DMO_BUFFER_LENGTH_BITS; + + if (min < max) { + if (m_dataPtr >= min && m_dataPtr <= max) + correlateSync(); + } + else { + if (m_dataPtr >= min || m_dataPtr <= max) + correlateSync(); + } + } + + if (m_dataPtr == m_endPtr) { + frame[0U] = m_control; + + bitsToBytes(m_startPtr, DMR_FRAME_LENGTH_BYTES, frame + 1U); + + if (m_control == CONTROL_DATA) { + // Data sync + uint8_t colorCode; + uint8_t dataType; + CDMRSlotType slotType; + slotType.decode(frame + 1U, colorCode, dataType); + + if (colorCode == m_colorCode) { + m_syncCount = 0U; + m_n = 0U; + + frame[0U] |= dataType; + + switch (dataType) { + case DT_DATA_HEADER: + DEBUG2("DMRDMORX: databit(): data header found pos", m_syncPtr); + writeRSSIData(frame); + m_state = DMORXS_DATA; + m_type = 0x00U; + break; + case DT_RATE_12_DATA: + case DT_RATE_34_DATA: + case DT_RATE_1_DATA: + if (m_state == DMORXS_DATA) { + DEBUG2("DMRDMORX: databit(): data payload found pos", m_syncPtr); + writeRSSIData(frame); + m_type = dataType; + } + break; + case DT_VOICE_LC_HEADER: + DEBUG2("DMRDMORX: databit(): voice header found pos", m_syncPtr); + writeRSSIData(frame); + m_state = DMORXS_VOICE; + break; + case DT_VOICE_PI_HEADER: + if (m_state == DMORXS_VOICE) { + DEBUG2("DMRDMORX: databit(): voice pi header found pos", m_syncPtr); + writeRSSIData(frame); + } + m_state = DMORXS_VOICE; + break; + case DT_TERMINATOR_WITH_LC: + if (m_state == DMORXS_VOICE) { + DEBUG2("DMRDMORX: databit(): voice terminator found pos", m_syncPtr); + writeRSSIData(frame); + reset(); + } + break; + default: // DT_CSBK + DEBUG2("DMRDMORX: databit(): csbk found pos", m_syncPtr); + writeRSSIData(frame); + reset(); + break; + } + } + } + else if (m_control == CONTROL_VOICE) { + // Voice sync + DEBUG2("DMRDMORX: databit(): voice sync found pos", m_syncPtr); + writeRSSIData(frame); + + m_state = DMORXS_VOICE; + m_syncCount = 0U; + m_n = 0U; + } + else { + if (m_state != DMORXS_NONE) { + m_syncCount++; + if (m_syncCount >= MAX_SYNC_LOST_FRAMES) { + DEBUG1("DMRDMORX: databit(): sync timeout, lost lock"); + serial.writeDMRLost(true); + reset(); + } + } + + if (m_state == DMORXS_VOICE) { + if (m_n >= 5U) { + frame[0U] = CONTROL_VOICE; + m_n = 0U; + } + else { + frame[0U] = ++m_n; + } + serial.writeDMRData(true, frame, DMR_FRAME_LENGTH_BYTES + 1U); + } + else if (m_state == DMORXS_DATA) { + if (m_type != 0x00U) { + frame[0U] = CONTROL_DATA | m_type; + writeRSSIData(frame); + } + } + } + + // End of this slot, reset some items for the next slot. + m_control = CONTROL_NONE; + } + + m_dataPtr++; + + if (m_dataPtr >= DMO_BUFFER_LENGTH_BITS) + m_dataPtr = 0U; + + io.setDecode(m_state != DMORXS_NONE); +} + +/// +/// Sets the DMR color code. +/// +/// Color code. +void DMRDMORX::setColorCode(uint8_t colorCode) +{ + m_colorCode = colorCode; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// Frame synchronization correlator. +/// +void DMRDMORX::correlateSync() +{ + if ((countBits64((m_bitBuffer & DMR_SYNC_BITS_MASK) ^ DMR_MS_DATA_SYNC_BITS) <= MAX_SYNC_BYTES_ERRS) || \ + (countBits64((m_bitBuffer & DMR_SYNC_BITS_MASK) ^ DMR_S2_DATA_SYNC_BITS) <= MAX_SYNC_BYTES_ERRS)) { + m_control = CONTROL_DATA; + m_syncPtr = m_dataPtr; + + m_startPtr = m_dataPtr + DMO_BUFFER_LENGTH_BITS - DMR_SLOT_TYPE_LENGTH_BITS / 2U - DMR_INFO_LENGTH_BITS / 2U - DMR_SYNC_LENGTH_BITS + 1; + if (m_startPtr >= DMO_BUFFER_LENGTH_BITS) + m_startPtr -= DMO_BUFFER_LENGTH_BITS; + + m_endPtr = m_dataPtr + DMR_SLOT_TYPE_LENGTH_BITS / 2U + DMR_INFO_LENGTH_BITS / 2U; + if (m_endPtr >= DMO_BUFFER_LENGTH_BITS) + m_endPtr -= DMO_BUFFER_LENGTH_BITS; + + m_modeTimerCnt = 0; + + DEBUG4("DMRDMORX: correlateSync(): dataPtr/startPtr/endPtr", m_dataPtr, m_startPtr, m_endPtr); + } + else if ((countBits64((m_bitBuffer & DMR_SYNC_BITS_MASK) ^ DMR_MS_VOICE_SYNC_BITS) <= MAX_SYNC_BYTES_ERRS) || \ + (countBits64((m_bitBuffer & DMR_SYNC_BITS_MASK) ^ DMR_S2_VOICE_SYNC_BITS) <= MAX_SYNC_BYTES_ERRS)) { + m_control = CONTROL_VOICE; + m_syncPtr = m_dataPtr; + + m_startPtr = m_dataPtr + DMO_BUFFER_LENGTH_BITS - DMR_SLOT_TYPE_LENGTH_BITS / 2U - DMR_INFO_LENGTH_BITS / 2U - DMR_SYNC_LENGTH_BITS + 1; + if (m_startPtr >= DMO_BUFFER_LENGTH_BITS) + m_startPtr -= DMO_BUFFER_LENGTH_BITS; + + m_endPtr = m_dataPtr + DMR_SLOT_TYPE_LENGTH_BITS / 2U + DMR_INFO_LENGTH_BITS / 2U; + if (m_endPtr >= DMO_BUFFER_LENGTH_BITS) + m_endPtr -= DMO_BUFFER_LENGTH_BITS; + + m_modeTimerCnt = 0; + + DEBUG4("DMRDMORX: correlateSync(): dataPtr/startPtr/endPtr", m_dataPtr, m_startPtr, m_endPtr); + } +} + +/// +/// +/// +/// +/// +/// +void DMRDMORX::bitsToBytes(uint16_t start, uint8_t count, uint8_t* buffer) +{ + for (uint8_t i = 0U; i < count; i++) { + buffer[i] = 0U; + buffer[i] |= _READ_BIT(m_buffer, start) << 7; + start++; + if (start >= DMO_BUFFER_LENGTH_BITS) + start -= DMO_BUFFER_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 6; + start++; + if (start >= DMO_BUFFER_LENGTH_BITS) + start -= DMO_BUFFER_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 5; + start++; + if (start >= DMO_BUFFER_LENGTH_BITS) + start -= DMO_BUFFER_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 4; + start++; + if (start >= DMO_BUFFER_LENGTH_BITS) + start -= DMO_BUFFER_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 3; + start++; + if (start >= DMO_BUFFER_LENGTH_BITS) + start -= DMO_BUFFER_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 2; + start++; + if (start >= DMO_BUFFER_LENGTH_BITS) + start -= DMO_BUFFER_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 1; + start++; + if (start >= DMO_BUFFER_LENGTH_BITS) + start -= DMO_BUFFER_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 0; + start++; + if (start >= DMO_BUFFER_LENGTH_BITS) + start -= DMO_BUFFER_LENGTH_BITS; + } +} + +/// +/// +/// +/// +void DMRDMORX::writeRSSIData(uint8_t* frame) +{ +#if defined(SEND_RSSI_DATA) + uint16_t rssi = io.readRSSI(); + + frame[34U] = (rssi >> 8) & 0xFFU; + frame[35U] = (rssi >> 0) & 0xFFU; + + serial.writeDMRData(true, frame, DMR_FRAME_LENGTH_BYTES + 3U); +#else + serial.writeDMRData(true, frame, DMR_FRAME_LENGTH_BYTES + 1U); +#endif +} + diff --git a/dmr/DMRDMORX.h b/dmr/DMRDMORX.h new file mode 100644 index 0000000..c4d15cf --- /dev/null +++ b/dmr/DMRDMORX.h @@ -0,0 +1,102 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* Copyright (C) 2016,2017,2018 by Andy Uribe CA6JAU +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DMR_DMO_RX_H__) +#define __DMR_DMO_RX_H__ + +#include "Defines.h" +#include "dmr/DMRDefines.h" + +namespace dmr +{ + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + const uint16_t DMO_BUFFER_LENGTH_BITS = 576U; + + enum DMORX_STATE { + DMORXS_NONE, + DMORXS_VOICE, + DMORXS_DATA + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Implements receiver logic for DMR DMO mode operation. + // --------------------------------------------------------------------------- + + class DSP_FW_API DMRDMORX { + public: + /// Initializes a new instance of the DMRDMORX class. + DMRDMORX(); + + /// Helper to reset data values to defaults. + void reset(); + + /// Sample DMR bits from the air interface. + void databit(bool bit); + + /// Sets the DMR color code. + void setColorCode(uint8_t colorCode); + + private: + uint64_t m_bitBuffer; + uint8_t m_buffer[DMO_BUFFER_LENGTH_BITS / 8U]; // 72 bytes + + uint8_t frame[DMR_FRAME_LENGTH_BYTES + 3U]; + + uint16_t m_dataPtr; + uint16_t m_syncPtr; + uint16_t m_startPtr; + uint16_t m_endPtr; + + uint8_t m_control; + uint8_t m_syncCount; + + uint8_t m_colorCode; + + DMORX_STATE m_state; + + uint8_t m_n; + + uint8_t m_type; + + /// Frame synchronization correlator. + void correlateSync(); + + /// + void bitsToBytes(uint16_t start, uint8_t count, uint8_t* buffer); + /// + void writeRSSIData(uint8_t* frame); + }; +} // namespace dmr + +#endif // __DMR_DMO_RX_H__ diff --git a/dmr/DMRDMOTX.cpp b/dmr/DMRDMOTX.cpp new file mode 100644 index 0000000..6e01f00 --- /dev/null +++ b/dmr/DMRDMOTX.cpp @@ -0,0 +1,228 @@ +/** +* Digital Voice Modem - DSP Firmware +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware +* +*/ +// +// Based on code from the MMDVM project. (https://github.com/g4klx/MMDVM) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2009-2016 by Jonathan Naylor G4KLX +* Copyright (C) 2016 by Colin Durbridge G4EML +* Copyright (C) 2016,2017,2018 by Andy Uribe CA6JAU +* Copyright (C) 2021 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Globals.h" +#include "dmr/DMRSlotType.h" + +using namespace dmr; + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +// PR FILL pattern +const uint8_t PR_FILL[] = + { 0x63U, 0xEAU, 0x00U, 0x76U, 0x6CU, 0x76U, 0xC4U, 0x52U, 0xC8U, 0x78U, + 0x09U, 0x2DU, 0xB8U, 0x79U, 0x27U, 0x57U, 0x9BU, 0x31U, 0xBCU, 0x3EU, + 0xEAU, 0x45U, 0xC3U, 0x30U, 0x49U, 0x17U, 0x93U, 0xAEU, 0x8BU, 0x6DU, + 0xA4U, 0xA5U, 0xADU, 0xA2U, 0xF1U, 0x35U, 0xB5U, 0x3CU, 0x1EU }; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the DMRDMOTX class. +/// +DMRDMOTX::DMRDMOTX() : + m_fifo(875U), + m_poBuffer(), + m_poLen(0U), + m_poPtr(0U), + m_preambleCnt(DMRDMO_FIXED_DELAY), + m_cal(false) +{ + /* stub */ +} + +/// +/// Process local buffer and transmit on the air interface. +/// +void DMRDMOTX::process() +{ + if (m_poLen == 0U && m_fifo.getData() > 0U) { + if (!m_tx) { + for (uint16_t i = 0U; i < m_preambleCnt; i++) + m_poBuffer[i] = DMR_START_SYNC; + + m_poLen = m_preambleCnt; + } + else { + for (unsigned int i = 0U; i < 72U; i++) + m_poBuffer[i] = DMR_START_SYNC; + + for (unsigned int i = 0U; i < DMR_FRAME_LENGTH_BYTES; i++) + m_poBuffer[i + 39U] = m_fifo.get(); + + m_poLen = 72U; + } + + DEBUG2("DMRDMOTX: process(): poLen", m_poLen); + m_poPtr = 0U; + } + + if (m_poLen > 0U) { + uint16_t space = io.getSpace(); + + while (space > 8U) { + uint8_t c = m_poBuffer[m_poPtr++]; + + writeByte(c); + + space -= 8U; + + if (m_poPtr >= m_poLen) { + m_poPtr = 0U; + m_poLen = 0U; + return; + } + } + } +} + +/// +/// Write data to the local buffer. +/// +/// +/// +/// +uint8_t DMRDMOTX::writeData(const uint8_t* data, uint8_t length) +{ + if (length != (DMR_FRAME_LENGTH_BYTES + 1U)) + return RSN_ILLEGAL_LENGTH; + + uint16_t space = m_fifo.getSpace(); + DEBUG3("DMRDMOTX: writeData(): dataLength/fifoLength", length, space); + if (space < DMR_FRAME_LENGTH_BYTES) + return RSN_RINGBUFF_FULL; + + for (uint8_t i = 0U; i < DMR_FRAME_LENGTH_BYTES; i++) + m_fifo.put(data[i + 1U]); + + return RSN_OK; +} + +/// +/// +/// +/// +void DMRDMOTX::setCal(bool start) +{ + m_cal = start ? true : false; +} + +/// +/// Sets the FDMA preamble count. +/// +/// Count of preambles. +void DMRDMOTX::setPreambleCount(uint8_t preambleCnt) +{ + uint32_t preambles = (uint32_t)((float)preambleCnt / 0.2083F); + m_preambleCnt = DMRDMO_FIXED_DELAY + preambles; + + // clamp preamble count to 250ms maximum + if (m_preambleCnt > 1200U) + m_preambleCnt = 1200U; +} + +/// +/// Sets the fine adjust 4FSK symbol levels. +/// +/// +3/-3 symbol adjust. +/// +1/-1 symbol adjust. +void DMRDMOTX::setSymbolLvlAdj(int8_t level3Adj, int8_t level1Adj) +{ + /* ignored ADF7021 doesn't allow direct symbol level adjustments */ +} + +/// +/// Helper to get how much space the ring buffer has for samples. +/// +/// +uint16_t DMRDMOTX::getSpace() const +{ + return m_fifo.getSpace() / (DMR_FRAME_LENGTH_BYTES + 2U); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// +/// +void DMRDMOTX::createCal() +{ + // 1.2 kHz sine wave generation + if (m_calState == STATE_DMR_CAL) { + for (unsigned int i = 0U; i < DMR_FRAME_LENGTH_BYTES; i++) { + m_poBuffer[i] = 0x5FU; // +3, +3, -3, -3 pattern for deviation cal. + } + + m_poLen = DMR_FRAME_LENGTH_BYTES; + } + + // 80 Hz square wave generation + if (m_modemState == STATE_DMR_LF_CAL) { + for (unsigned int i = 0U; i < 7U; i++) { + m_poBuffer[i] = 0x55U; // +3, +3, ... pattern + } + + m_poBuffer[7U] = 0x5FU; // +3, +3, -3, -3 pattern + + for (unsigned int i = 8U; i < 15U; i++) { + m_poBuffer[i] = 0xFFU; // -3, -3, ... pattern + } + + m_poLen = 15U; + } + + m_poLen = DMR_FRAME_LENGTH_BYTES; + m_poPtr = 0U; +} + +/// +/// +/// +/// +void DMRDMOTX::writeByte(uint8_t c) +{ + uint8_t bit; + uint8_t mask = 0x80U; + + for (uint8_t i = 0U; i < 8U; i++, c <<= 1) { + if ((c & mask) == mask) + bit = 1U; + else + bit = 0U; + + io.write(&bit, 1); + } +} diff --git a/dmr/DMRDMOTX.h b/dmr/DMRDMOTX.h new file mode 100644 index 0000000..86c13bf --- /dev/null +++ b/dmr/DMRDMOTX.h @@ -0,0 +1,95 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* Copyright (C) 2016 by Colin Durbridge G4EML +* Copyright (C) 2016,2017,2018 by Andy Uribe CA6JAU +* Copyright (C) 2021 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DMR_DMO_TX_H__) +#define __DMR_DMO_TX_H__ + +#include "Defines.h" +#include "dmr/DMRDefines.h" +#include "SerialBuffer.h" + +namespace dmr +{ + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + +#define DMRDMO_FIXED_DELAY 300 // 300 = 62.49ms + // Delay Value * 0.2083 = Preamble Length (ms) + +// --------------------------------------------------------------------------- +// Class Declaration +// Implements transmitter logic for DMR DMO mode operation. +// --------------------------------------------------------------------------- + + class DSP_FW_API DMRDMOTX { + public: + /// Initializes a new instance of the DMRDMOTX class. + DMRDMOTX(); + + /// Process local buffer and transmit on the air interface. + void process(); + + /// Write data to the local buffer. + uint8_t writeData(const uint8_t* data, uint8_t length); + + /// Helper to set the calibration state for Tx. + void setCal(bool start); + + /// Sets the FDMA preamble count. + void setPreambleCount(uint8_t preambleCnt); + /// Sets the fine adjust 4FSK symbol levels. + void setSymbolLvlAdj(int8_t level3Adj, int8_t level1Adj); + + /// Helper to get how much space the ring buffer has for samples. + uint16_t getSpace() const; + + private: + SerialBuffer m_fifo; + + uint8_t m_poBuffer[80U]; + uint16_t m_poLen; + uint16_t m_poPtr; + + uint32_t m_preambleCnt; + + bool m_cal; + + /// + void createCal(); + + /// + void writeByte(uint8_t c); + }; +} // namespace dmr + +#endif // __DMR_DMO_TX_H__ diff --git a/dmr/DMRDefines.h b/dmr/DMRDefines.h new file mode 100644 index 0000000..16a4f2a --- /dev/null +++ b/dmr/DMRDefines.h @@ -0,0 +1,135 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2009-2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DMR_DEFINES_H__) +#define __DMR_DEFINES_H__ + +#include "Defines.h" + +namespace dmr +{ + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + const uint32_t DMR_RADIO_SYMBOL_LENGTH = 5U; // At 24 kHz sample rate + + const uint32_t DMR_FRAME_LENGTH_BYTES = 33U; + const uint32_t DMR_FRAME_LENGTH_BITS = DMR_FRAME_LENGTH_BYTES * 8U; + const uint32_t DMR_FRAME_LENGTH_SYMBOLS = DMR_FRAME_LENGTH_BYTES * 4U; + const uint32_t DMR_FRAME_LENGTH_SAMPLES = DMR_FRAME_LENGTH_SYMBOLS * DMR_RADIO_SYMBOL_LENGTH; + + const uint32_t DMR_SYNC_LENGTH_BYTES = 6U; + const uint32_t DMR_SYNC_LENGTH_BITS = DMR_SYNC_LENGTH_BYTES * 8U; + const uint32_t DMR_SYNC_LENGTH_SYMBOLS = DMR_SYNC_LENGTH_BYTES * 4U; + const uint32_t DMR_SYNC_LENGTH_SAMPLES = DMR_SYNC_LENGTH_SYMBOLS * DMR_RADIO_SYMBOL_LENGTH; + + const uint32_t DMR_EMB_LENGTH_BITS = 16U; + const uint32_t DMR_EMB_LENGTH_SYMBOLS = 8U; + const uint32_t DMR_EMB_LENGTH_SAMPLES = DMR_EMB_LENGTH_SYMBOLS * DMR_RADIO_SYMBOL_LENGTH; + + const uint32_t DMR_EMBSIG_LENGTH_BITS = 32U; + const uint32_t DMR_EMBSIG_LENGTH_SYMBOLS = 16U; + const uint32_t DMR_EMBSIG_LENGTH_SAMPLES = DMR_EMBSIG_LENGTH_SYMBOLS * DMR_RADIO_SYMBOL_LENGTH; + + const uint32_t DMR_SLOT_TYPE_LENGTH_BITS = 20U; + const uint32_t DMR_SLOT_TYPE_LENGTH_SYMBOLS = 10U; + const uint32_t DMR_SLOT_TYPE_LENGTH_SAMPLES = DMR_SLOT_TYPE_LENGTH_SYMBOLS * DMR_RADIO_SYMBOL_LENGTH; + + const uint32_t DMR_INFO_LENGTH_BITS = 196U; + const uint32_t DMR_INFO_LENGTH_SYMBOLS = 98U; + const uint32_t DMR_INFO_LENGTH_SAMPLES = DMR_INFO_LENGTH_SYMBOLS * DMR_RADIO_SYMBOL_LENGTH; + + const uint32_t DMR_AUDIO_LENGTH_BITS = 216U; + const uint32_t DMR_AUDIO_LENGTH_SYMBOLS = 108U; + const uint32_t DMR_AUDIO_LENGTH_SAMPLES = DMR_AUDIO_LENGTH_SYMBOLS * DMR_RADIO_SYMBOL_LENGTH; + + const uint32_t DMR_CACH_LENGTH_BYTES = 3U; + const uint32_t DMR_CACH_LENGTH_BITS = DMR_CACH_LENGTH_BYTES * 8U; + const uint32_t DMR_CACH_LENGTH_SYMBOLS = DMR_CACH_LENGTH_BYTES * 4U; + const uint32_t DMR_CACH_LENGTH_SAMPLES = DMR_CACH_LENGTH_SYMBOLS * DMR_RADIO_SYMBOL_LENGTH; + + const uint8_t DMR_SYNC_BYTES_LENGTH = 7U; + const uint8_t DMR_MS_DATA_SYNC_BYTES[] = { 0x0DU, 0x5DU, 0x7FU, 0x77U, 0xFDU, 0x75U, 0x70U }; + const uint8_t DMR_MS_VOICE_SYNC_BYTES[] = { 0x07U, 0xF7U, 0xD5U, 0xDDU, 0x57U, 0xDFU, 0xD0U }; + const uint8_t DMR_BS_DATA_SYNC_BYTES[] = { 0x0DU, 0xFFU, 0x57U, 0xD7U, 0x5DU, 0xF5U, 0xD0U }; + const uint8_t DMR_BS_VOICE_SYNC_BYTES[] = { 0x07U, 0x55U, 0xFDU, 0x7DU, 0xF7U, 0x5FU, 0x70U }; + const uint8_t DMR_S1_DATA_SYNC_BYTES[] = { 0x0FU, 0x7FU, 0xDDU, 0x5DU, 0xDFU, 0xD5U, 0x50U }; + const uint8_t DMR_S1_VOICE_SYNC_BYTES[] = { 0x05U, 0xD5U, 0x77U, 0xF7U, 0x75U, 0x7FU, 0xF0U }; + const uint8_t DMR_S2_DATA_SYNC_BYTES[] = { 0x0DU, 0x75U, 0x57U, 0xF5U, 0xFFU, 0x7FU, 0x50U }; + const uint8_t DMR_S2_VOICE_SYNC_BYTES[] = { 0x07U, 0xDFU, 0xFDU, 0x5FU, 0x55U, 0xD5U, 0xF0U }; + const uint8_t DMR_SYNC_BYTES_MASK[] = { 0x0FU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xF0U }; + + const uint8_t DMR_START_SYNC = 0x5FU; + + const uint64_t DMR_MS_DATA_SYNC_BITS = 0x0000D5D7F77FD757U; + const uint64_t DMR_MS_VOICE_SYNC_BITS = 0x00007F7D5DD57DFDU; + const uint64_t DMR_BS_DATA_SYNC_BITS = 0x0000DFF57D75DF5DU; + const uint64_t DMR_BS_VOICE_SYNC_BITS = 0x0000755FD7DF75F7U; + const uint64_t DMR_S1_DATA_SYNC_BITS = 0x0000F7FDD5DDFD55U; + const uint64_t DMR_S1_VOICE_SYNC_BITS = 0x00005D577F7757FFU; + const uint64_t DMR_S2_DATA_SYNC_BITS = 0x0000D7557F5FF7F5U; + const uint64_t DMR_S2_VOICE_SYNC_BITS = 0x00007DFFD5F55D5FU; + const uint64_t DMR_SYNC_BITS_MASK = 0x0000FFFFFFFFFFFFU; + + const uint32_t DMR_MS_DATA_SYNC_SYMBOLS = 0x0076286EU; + const uint32_t DMR_MS_VOICE_SYNC_SYMBOLS = 0x0089D791U; + const uint32_t DMR_BS_DATA_SYNC_SYMBOLS = 0x00439B4DU; + const uint32_t DMR_BS_VOICE_SYNC_SYMBOLS = 0x00BC64B2U; + const uint32_t DMR_S1_DATA_SYNC_SYMBOLS = 0x0021751FU; + const uint32_t DMR_S1_VOICE_SYNC_SYMBOLS = 0x00DE8AE0U; + const uint32_t DMR_S2_DATA_SYNC_SYMBOLS = 0x006F8C23U; + const uint32_t DMR_S2_VOICE_SYNC_SYMBOLS = 0x009073DCU; + const uint32_t DMR_SYNC_SYMBOLS_MASK = 0x00FFFFFFU; + + // D 5 D 7 F 7 7 F D 7 5 7 + // 11 01 01 01 11 01 01 11 11 11 01 11 01 11 11 11 11 01 01 11 01 01 01 11 + // -3 +3 +3 +3 -3 +3 +3 -3 -3 -3 +3 -3 +3 -3 -3 -3 -3 +3 +3 -3 +3 +3 +3 -3 + + const int8_t DMR_MS_DATA_SYNC_SYMBOLS_VALUES[] = { -3, +3, +3, +3, -3, +3, +3, -3, -3, -3, +3, -3, +3, -3, -3, -3, -3, +3, +3, -3, +3, +3, +3, -3 }; + + // 7 F 7 D 5 D D 5 7 D F D + // 01 11 11 11 01 11 11 01 01 01 11 01 11 01 01 01 01 11 11 01 11 11 11 01 + // +3 -3 -3 -3 +3 -3 -3 +3 +3 +3 -3 +3 -3 +3 +3 +3 +3 -3 -3 +3 -3 -3 -3 +3 + + const int8_t DMR_MS_VOICE_SYNC_SYMBOLS_VALUES[] = { +3, -3, -3, -3, +3, -3, -3, +3, +3, +3, -3, +3, -3, +3, +3, +3, +3, -3, -3, +3, -3, -3, -3, +3 }; + + // Data Type(s) + const uint8_t DT_VOICE_PI_HEADER = 0U; + const uint8_t DT_VOICE_LC_HEADER = 1U; + const uint8_t DT_TERMINATOR_WITH_LC = 2U; + const uint8_t DT_CSBK = 3U; + const uint8_t DT_DATA_HEADER = 6U; + const uint8_t DT_RATE_12_DATA = 7U; + const uint8_t DT_RATE_34_DATA = 8U; + const uint8_t DT_IDLE = 9U; + const uint8_t DT_RATE_1_DATA = 10U; +} // namespace dmr + +#endif // __DMR_DEFINES_H__ diff --git a/dmr/DMRIdleRX.cpp b/dmr/DMRIdleRX.cpp new file mode 100644 index 0000000..41e371e --- /dev/null +++ b/dmr/DMRIdleRX.cpp @@ -0,0 +1,186 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2009-2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017,2018 by Andy Uribe CA6JAU +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Globals.h" +#include "dmr/DMRIdleRX.h" +#include "dmr/DMRSlotType.h" +#include "Utils.h" + +using namespace dmr; + +#if defined(DUPLEX) + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint8_t MAX_SYNC_BYTES_ERRS = 2U; + +const uint16_t NOENDPTR = 9999U; + +const uint8_t CONTROL_IDLE = 0x80U; +const uint8_t CONTROL_DATA = 0x40U; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the DMRIdleRX class. +/// +DMRIdleRX::DMRIdleRX() : + m_bitBuffer(0U), + m_buffer(), + m_dataPtr(0U), + m_endPtr(NOENDPTR), + m_colorCode(0U) +{ + /* stub */ +} + +/// +/// Helper to reset data values to defaults. +/// +void DMRIdleRX::reset() +{ + m_dataPtr = 0U; + m_endPtr = NOENDPTR; +} + +/// +/// Sample DMR bits from the air interface. +/// +/// +void DMRIdleRX::databit(bool bit) +{ + _WRITE_BIT(m_buffer, m_dataPtr, bit); + + m_bitBuffer <<= 1; + if (bit) + m_bitBuffer |= 0x01U; + + if (countBits64((m_bitBuffer & DMR_SYNC_BITS_MASK) ^ DMR_MS_DATA_SYNC_BITS) <= MAX_SYNC_BYTES_ERRS) { + m_endPtr = m_dataPtr + DMR_SLOT_TYPE_LENGTH_BITS / 2U + DMR_INFO_LENGTH_BITS / 2U; + if (m_endPtr >= DMR_IDLE_LENGTH_BITS) + m_endPtr -= DMR_IDLE_LENGTH_BITS; + + DEBUG3("DMRIdleRx: databit(): dataPtr/endPtr", m_dataPtr, m_endPtr); + } + + if (m_dataPtr == m_endPtr) { + uint16_t ptr = m_endPtr + DMR_IDLE_LENGTH_BITS - DMR_FRAME_LENGTH_BITS + 1; + if (ptr >= DMR_IDLE_LENGTH_BITS) + ptr -= DMR_IDLE_LENGTH_BITS; + + uint8_t frame[DMR_FRAME_LENGTH_BYTES + 1U]; + bitsToBytes(ptr, DMR_FRAME_LENGTH_BYTES, frame + 1U); + + uint8_t colorCode; + uint8_t dataType; + DMRSlotType slotType; + slotType.decode(frame + 1U, colorCode, dataType); + + if (colorCode == m_colorCode && dataType == DT_CSBK) { + frame[0U] = CONTROL_IDLE | CONTROL_DATA | DT_CSBK; + serial.writeDMRData(false, frame, DMR_FRAME_LENGTH_BYTES + 1U); + } + + m_endPtr = NOENDPTR; + } + + m_dataPtr++; + if (m_dataPtr >= DMR_IDLE_LENGTH_BITS) + m_dataPtr = 0U; +} + +/// +/// Sets the DMR color code. +/// +/// Color code. +void DMRIdleRX::setColorCode(uint8_t colorCode) +{ + m_colorCode = colorCode; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// +/// +/// +/// +/// +void DMRIdleRX::bitsToBytes(uint16_t start, uint8_t count, uint8_t* buffer) +{ + for (uint8_t i = 0U; i < count; i++) { + buffer[i] = 0U; + buffer[i] |= _READ_BIT(m_buffer, start) << 7; + start++; + if (start >= DMR_IDLE_LENGTH_BITS) + start -= DMR_IDLE_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 6; + start++; + if (start >= DMR_IDLE_LENGTH_BITS) + start -= DMR_IDLE_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 5; + start++; + if (start >= DMR_IDLE_LENGTH_BITS) + start -= DMR_IDLE_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 4; + start++; + if (start >= DMR_IDLE_LENGTH_BITS) + start -= DMR_IDLE_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 3; + start++; + if (start >= DMR_IDLE_LENGTH_BITS) + start -= DMR_IDLE_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 2; + start++; + if (start >= DMR_IDLE_LENGTH_BITS) + start -= DMR_IDLE_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 1; + start++; + if (start >= DMR_IDLE_LENGTH_BITS) + start -= DMR_IDLE_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 0; + start++; + if (start >= DMR_IDLE_LENGTH_BITS) + start -= DMR_IDLE_LENGTH_BITS; + } +} + +#endif // DUPLEX diff --git a/dmr/DMRIdleRX.h b/dmr/DMRIdleRX.h new file mode 100644 index 0000000..3031bb6 --- /dev/null +++ b/dmr/DMRIdleRX.h @@ -0,0 +1,82 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015 by Jonathan Naylor G4KLX +* Copyright (C) 2017,2018 by Andy Uribe CA6JAU +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DMR_IDLE_RX_H__) +#define __DMR_IDLE_RX_H__ + +#include "Defines.h" +#include "dmr/DMRDefines.h" + +#if defined(DUPLEX) + +namespace dmr +{ + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + const uint16_t DMR_IDLE_LENGTH_BITS = 320U; + + // --------------------------------------------------------------------------- + // Class Declaration + // Implements receiver logic for idle DMR mode operation. + // --------------------------------------------------------------------------- + + class DSP_FW_API DMRIdleRX { + public: + /// Initializes a new instance of the DMRIdleRX class. + DMRIdleRX(); + + /// Helper to reset data values to defaults. + void reset(); + + /// Sample DMR bits from the air interface. + void databit(bool bit); + + /// Sets the DMR color code. + void setColorCode(uint8_t colorCode); + + private: + uint64_t m_bitBuffer; + uint8_t m_buffer[DMR_IDLE_LENGTH_BITS / 8U]; + + uint16_t m_dataPtr; + uint16_t m_endPtr; + + uint8_t m_colorCode; + + /// + void bitsToBytes(uint16_t start, uint8_t count, uint8_t* buffer); + }; +} // namespace dmr + +#endif // DUPLEX + +#endif // __DMR_IDLE_RX_H__ diff --git a/dmr/DMRRX.cpp b/dmr/DMRRX.cpp new file mode 100644 index 0000000..9d453db --- /dev/null +++ b/dmr/DMRRX.cpp @@ -0,0 +1,108 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* Copyright (C) 2017 by Andy Uribe CA6JAU +* Copyright (C) 2021 Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Globals.h" +#include "dmr/DMRRX.h" + +using namespace dmr; + +#if defined(DUPLEX) + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the DMRRX class. +/// +DMRRX::DMRRX() : + m_slot1RX(false), + m_slot2RX(true) +{ + /* stub */ +} + + +/// +/// Helper to reset data values to defaults. +/// +void DMRRX::reset() +{ + m_slot1RX.reset(); + m_slot2RX.reset(); +} + +/// +/// Sample DMR bits from the air interface. +/// +/// +void DMRRX::databit(bool bit, const uint8_t control) +{ + bool dcd1 = false; + bool dcd2 = false; + + switch (control) { + case MARK_SLOT1: + m_slot1RX.start(); + break; + case MARK_SLOT2: + m_slot2RX.start(); + break; + default: + break; + } + + dcd1 = m_slot1RX.databit(bit); + dcd2 = m_slot2RX.databit(bit); + + io.setDecode(dcd1 || dcd2); +} + +/// +/// Sets the DMR color code. +/// +/// Color code. +void DMRRX::setColorCode(uint8_t colorCode) +{ + m_slot1RX.setColorCode(colorCode); + m_slot2RX.setColorCode(colorCode); +} + +/// +/// Sets the number of samples to delay before processing. +/// +/// Number of samples to delay. +void DMRRX::setRxDelay(uint8_t delay) +{ + m_slot1RX.setRxDelay(delay); + m_slot2RX.setRxDelay(delay); +} + +#endif // DUPLEX diff --git a/dmr/DMRRX.h b/dmr/DMRRX.h new file mode 100644 index 0000000..326a7fc --- /dev/null +++ b/dmr/DMRRX.h @@ -0,0 +1,71 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* Copyright (C) 2017 by Andy Uribe CA6JAU +* Copyright (C) 2021 Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DMR_RX_H__) +#define __DMR_RX_H__ + +#include "Defines.h" +#include "dmr/DMRSlotRX.h" + +#if defined(DUPLEX) + +namespace dmr +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Implements receiver logic for duplex DMR mode operation. + // --------------------------------------------------------------------------- + + class DSP_FW_API DMRRX { + public: + /// Initializes a new instance of the DMRRX class. + DMRRX(); + + /// Helper to reset data values to defaults. + void reset(); + + /// Sample DMR bits from the air interface. + void databit(bool bit, const uint8_t control); + + /// Sets the DMR color code. + void setColorCode(uint8_t colorCode); + /// Sets the number of samples to delay before processing. + void setRxDelay(uint8_t delay); + + private: + DMRSlotRX m_slot1RX; + DMRSlotRX m_slot2RX; + }; +} // namespace dmr + +#endif // DUPLEX + +#endif // __DMR_RX_H__ diff --git a/dmr/DMRSlotRX.cpp b/dmr/DMRSlotRX.cpp new file mode 100644 index 0000000..0a28179 --- /dev/null +++ b/dmr/DMRSlotRX.cpp @@ -0,0 +1,391 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2009-2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017,2018 by Andy Uribe CA6JAU +* Copyright (C) 2021 Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Globals.h" +#include "dmr/DMRSlotRX.h" +#include "dmr/DMRSlotType.h" +#include "Utils.h" + +using namespace dmr; + +#if defined(DUPLEX) + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint16_t SCAN_START = 390U; +const uint16_t SCAN_END = 500U; + +const uint8_t MAX_SYNC_BYTES_ERRS = 3U; + +const uint8_t MAX_SYNC_LOST_FRAMES = 13U; + +const uint16_t NOENDPTR = 9999U; + +const uint8_t CONTROL_NONE = 0x00U; +const uint8_t CONTROL_VOICE = 0x20U; +const uint8_t CONTROL_DATA = 0x40U; + +const uint8_t BIT_MASK_TABLE[] = {0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U}; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the DMRSlotRX class. +/// +DMRSlotRX::DMRSlotRX(bool slot) : + m_slot(false), + m_bitBuffer(0x00U), + m_buffer(), + m_dataPtr(0U), + m_syncPtr(0U), + m_startPtr(0U), + m_endPtr(NOENDPTR), + m_delayPtr(0U), + m_control(CONTROL_NONE), + m_syncCount(0U), + m_colorCode(0U), + m_delay(0U), + m_state(DMRRXS_NONE), + m_n(0U), + m_type(0U) +{ + /* stub */ +} + +/// +/// Helper to set data values for start of Rx. +/// +void DMRSlotRX::start() +{ + m_dataPtr = 0U; + m_delayPtr = 0U; + m_control = CONTROL_NONE; +} + +/// +/// Helper to reset data values to defaults. +/// +void DMRSlotRX::reset() +{ + m_syncPtr = 0U; + m_dataPtr = 0U; + m_delayPtr = 0U; + + m_bitBuffer = 0U; + + m_control = CONTROL_NONE; + m_syncCount = 0U; + m_state = DMRRXS_NONE; + m_startPtr = 0U; + m_endPtr = NOENDPTR; +} + +/// +/// Sample DMR bits from the air interface. +/// +/// +bool DMRSlotRX::databit(bool bit) +{ + uint16_t min, max; + + m_delayPtr++; + if (m_delayPtr < m_delay) + return m_state != DMRRXS_NONE; + + _WRITE_BIT(m_buffer, m_dataPtr, bit); + + m_bitBuffer <<= 1; + if (bit) + m_bitBuffer |= 0x01U; + + // Ensure that the buffer doesn't overflow + if (m_dataPtr > m_endPtr || m_dataPtr >= 576U) + return m_state != DMRRXS_NONE; + + if (m_state == DMRRXS_NONE) { + if (m_dataPtr >= SCAN_START && m_dataPtr <= SCAN_END) + correlateSync(); + } + else { + + uint16_t min = m_syncPtr - 1U; + uint16_t max = m_syncPtr + 1U; + if (m_dataPtr >= min && m_dataPtr <= max) + correlateSync(); + } + + if (m_dataPtr == m_endPtr) { + uint8_t frame[DMR_FRAME_LENGTH_BYTES + 3U]; + frame[0U] = m_control; + + bitsToBytes(m_startPtr, DMR_FRAME_LENGTH_BYTES, frame + 1U); + + if (m_control == CONTROL_DATA) { + // Data sync + uint8_t colorCode; + uint8_t dataType; + DMRSlotType slotType; + slotType.decode(frame + 1U, colorCode, dataType); + + if (colorCode == m_colorCode) { + m_syncCount = 0U; + m_n = 0U; + + frame[0U] |= dataType; + + switch (dataType) { + case DT_DATA_HEADER: + DEBUG3("DMRSlotRX: databit(): data header found slot/pos", m_slot ? 2U : 1U, m_syncPtr); + writeRSSIData(frame); + m_state = DMRRXS_DATA; + m_type = 0x00U; + break; + case DT_RATE_12_DATA: + case DT_RATE_34_DATA: + case DT_RATE_1_DATA: + if (m_state == DMRRXS_DATA) { + DEBUG3("DMRSlotRX: databit(): data payload found slot/pos", m_slot ? 2U : 1U, m_syncPtr); + writeRSSIData(frame); + m_type = dataType; + } + break; + case DT_VOICE_LC_HEADER: + DEBUG3("DMRSlotRX: databit(): voice header found slot/pos", m_slot ? 2U : 1U, m_syncPtr); + writeRSSIData(frame); + m_state = DMRRXS_VOICE; + break; + case DT_VOICE_PI_HEADER: + if (m_state == DMRRXS_VOICE) { + DEBUG3("DMRSlotRX: databit(): voice pi header found slot/pos", m_slot ? 2U : 1U, m_syncPtr); + writeRSSIData(frame); + } + m_state = DMRRXS_VOICE; + break; + case DT_TERMINATOR_WITH_LC: + if (m_state == DMRRXS_VOICE) { + DEBUG3("DMRSlotRX: databit(): voice terminator found slot/pos", m_slot ? 2U : 1U, m_syncPtr); + writeRSSIData(frame); + m_state = DMRRXS_NONE; + m_endPtr = NOENDPTR; + } + break; + default: // DT_CSBK + DEBUG3("DMRSlotRX: databit(): csbk found slot/pos", m_slot ? 2U : 1U, m_syncPtr); + writeRSSIData(frame); + m_state = DMRRXS_NONE; + m_endPtr = NOENDPTR; + break; + } + } + } + else if (m_control == CONTROL_VOICE) { + // Voice sync + DEBUG3("DMRSlotRX: databit(): voice sync found slot/pos", m_slot ? 2U : 1U, m_syncPtr); + writeRSSIData(frame); + m_state = DMRRXS_VOICE; + m_syncCount = 0U; + m_n = 0U; + } + else { + if (m_state != DMRRXS_NONE) { + m_syncCount++; + if (m_syncCount >= MAX_SYNC_LOST_FRAMES) { + DEBUG1("DMRSlotRX: databit(): sync timeout, lost lock"); + serial.writeDMRLost(m_slot); + m_state = DMRRXS_NONE; + m_endPtr = NOENDPTR; + } + } + + if (m_state == DMRRXS_VOICE) { + if (m_n >= 5U) { + frame[0U] = CONTROL_VOICE; + m_n = 0U; + } + else { + frame[0U] = ++m_n; + } + + serial.writeDMRData(m_slot, frame, DMR_FRAME_LENGTH_BYTES + 1U); + } + else if (m_state == DMRRXS_DATA) { + if (m_type != 0x00U) { + frame[0U] = CONTROL_DATA | m_type; + writeRSSIData(frame); + } + } + } + } + + m_dataPtr++; + if (m_dataPtr >= DMR_BUFFER_LENGTH_BITS) + m_dataPtr = 0U; + + return m_state != DMRRXS_NONE; +} + +/// +/// Sets the DMR color code. +/// +/// Color code. +void DMRSlotRX::setColorCode(uint8_t colorCode) +{ + m_colorCode = colorCode; +} + +/// +/// Sets the number of samples to delay before processing. +/// +/// Number of samples to delay. +void DMRSlotRX::setRxDelay(uint8_t delay) +{ + m_delay = delay; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// Frame synchronization correlator. +/// +/// +void DMRSlotRX::correlateSync() +{ + if (countBits64((m_bitBuffer & DMR_SYNC_BITS_MASK) ^ DMR_MS_DATA_SYNC_BITS) <= MAX_SYNC_BYTES_ERRS) { + m_control = CONTROL_DATA; + m_syncPtr = m_dataPtr; + + m_startPtr = m_dataPtr + DMR_BUFFER_LENGTH_BITS - DMR_SLOT_TYPE_LENGTH_BITS / 2U - DMR_INFO_LENGTH_BITS / 2U - DMR_SYNC_LENGTH_BITS + 1; + if (m_startPtr >= DMR_BUFFER_LENGTH_BITS) + m_startPtr -= DMR_BUFFER_LENGTH_BITS; + + m_endPtr = m_dataPtr + DMR_SLOT_TYPE_LENGTH_BITS / 2U + DMR_INFO_LENGTH_BITS / 2U; + if (m_endPtr >= DMR_BUFFER_LENGTH_BITS) + m_endPtr -= DMR_BUFFER_LENGTH_BITS; + + DEBUG4("DMRSlotRX: correlateSync(): dataPtr/startPtr/endPtr", m_dataPtr, m_startPtr, m_endPtr); + } + else if (countBits64((m_bitBuffer & DMR_SYNC_BITS_MASK) ^ DMR_MS_VOICE_SYNC_BITS) <= MAX_SYNC_BYTES_ERRS) { + m_control = CONTROL_VOICE; + m_syncPtr = m_dataPtr; + + m_startPtr = m_dataPtr + DMR_BUFFER_LENGTH_BITS - DMR_SLOT_TYPE_LENGTH_BITS / 2U - DMR_INFO_LENGTH_BITS / 2U - DMR_SYNC_LENGTH_BITS + 1; + if (m_startPtr >= DMR_BUFFER_LENGTH_BITS) + m_startPtr -= DMR_BUFFER_LENGTH_BITS; + + m_endPtr = m_dataPtr + DMR_SLOT_TYPE_LENGTH_BITS / 2U + DMR_INFO_LENGTH_BITS / 2U; + if (m_endPtr >= DMR_BUFFER_LENGTH_BITS) + m_endPtr -= DMR_BUFFER_LENGTH_BITS; + + DEBUG4("DMRSlotRX: correlateSync(): dataPtr/startPtr/endPtr", m_dataPtr, m_startPtr, m_endPtr); + } +} + +/// +/// +/// +/// +/// +/// +void DMRSlotRX::bitsToBytes(uint16_t start, uint8_t count, uint8_t* buffer) +{ + for (uint8_t i = 0U; i < count; i++) { + buffer[i] = 0U; + buffer[i] |= _READ_BIT(m_buffer, start) << 7; + start++; + if (start >= DMR_BUFFER_LENGTH_BITS) + start -= DMR_BUFFER_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 6; + start++; + if (start >= DMR_BUFFER_LENGTH_BITS) + start -= DMR_BUFFER_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 5; + start++; + if (start >= DMR_BUFFER_LENGTH_BITS) + start -= DMR_BUFFER_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 4; + start++; + if (start >= DMR_BUFFER_LENGTH_BITS) + start -= DMR_BUFFER_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 3; + start++; + if (start >= DMR_BUFFER_LENGTH_BITS) + start -= DMR_BUFFER_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 2; + start++; + if (start >= DMR_BUFFER_LENGTH_BITS) + start -= DMR_BUFFER_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 1; + start++; + if (start >= DMR_BUFFER_LENGTH_BITS) + start -= DMR_BUFFER_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 0; + start++; + if (start >= DMR_BUFFER_LENGTH_BITS) + start -= DMR_BUFFER_LENGTH_BITS; + } +} + +/// +/// +/// +/// +void DMRSlotRX::writeRSSIData(uint8_t* frame) +{ +#if defined(SEND_RSSI_DATA) + // Calculate RSSI average over a burst period. We don't take into account 2.5 ms at the beginning and 2.5 ms at the end + uint16_t start = m_startPtr + DMR_SYNC_LENGTH_SAMPLES / 2U; + + uint32_t accum = 0U; + for (uint16_t i = 0U; i < (DMR_FRAME_LENGTH_SAMPLES - DMR_SYNC_LENGTH_SAMPLES); i++) + accum += m_rssi[start++]; + + uint16_t avg = accum / (DMR_FRAME_LENGTH_SAMPLES - DMR_SYNC_LENGTH_SAMPLES); + frame[34U] = (avg >> 8) & 0xFFU; + frame[35U] = (avg >> 0) & 0xFFU; + + serial.writeDMRData(m_slot, frame, DMR_FRAME_LENGTH_BYTES + 3U); +#else + serial.writeDMRData(m_slot, frame, DMR_FRAME_LENGTH_BYTES + 1U); +#endif +} + +#endif // DUPLEX diff --git a/dmr/DMRSlotRX.h b/dmr/DMRSlotRX.h new file mode 100644 index 0000000..6d7fa0d --- /dev/null +++ b/dmr/DMRSlotRX.h @@ -0,0 +1,115 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017,2018 by Andy Uribe CA6JAU +* Copyright (C) 2021 Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DMR_SLOT_RX_H__) +#define __DMR_SLOT_RX_H__ + +#include "Defines.h" +#include "dmr/DMRDefines.h" + +#if defined(DUPLEX) + +namespace dmr +{ + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + const uint16_t DMR_BUFFER_LENGTH_BITS = 576U; + + enum DMRRX_STATE { + DMRRXS_NONE, + DMRRXS_VOICE, + DMRRXS_DATA + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Implements receiver logic for DMR slots. + // --------------------------------------------------------------------------- + + class DSP_FW_API DMRSlotRX { + public: + /// Initializes a new instance of the DMRSlotRX class. + DMRSlotRX(bool slot); + + /// Helper to set data values for start of Rx. + void start(); + /// Helper to reset data values to defaults. + void reset(); + + /// Sample DMR bits from the air interface. + bool databit(bool bit); + + /// Sets the DMR color code. + void setColorCode(uint8_t colorCode); + /// Sets the number of samples to delay before processing. + void setRxDelay(uint8_t delay); + + + private: + bool m_slot; + + uint64_t m_bitBuffer; + uint8_t m_buffer[DMR_BUFFER_LENGTH_BITS / 8U]; // 72 bytes + + uint16_t m_dataPtr; + uint16_t m_syncPtr; + uint16_t m_startPtr; + uint16_t m_endPtr; + uint16_t m_delayPtr; + + uint8_t m_control; + uint8_t m_syncCount; + + uint8_t m_colorCode; + + uint16_t m_delay; + + DMRRX_STATE m_state; + + uint8_t m_n; + + uint8_t m_type; + + /// Frame synchronization correlator. + void correlateSync(); + + /// + void bitsToBytes(uint16_t start, uint8_t count, uint8_t* buffer); + /// + void writeRSSIData(uint8_t* frame); + }; +} // namespace dmr + +#endif // DUPLEX + +#endif // __DMR_SLOT_RX_H__ diff --git a/dmr/DMRSlotType.cpp b/dmr/DMRSlotType.cpp new file mode 100644 index 0000000..6b7e186 --- /dev/null +++ b/dmr/DMRSlotType.cpp @@ -0,0 +1,334 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Globals.h" +#include "dmr/DMRSlotType.h" + +using namespace dmr; + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint16_t ENCODING_TABLE_2087[] = { + 0x0000U, 0xB08EU, 0xE093U, 0x501DU, 0x70A9U, 0xC027U, 0x903AU, 0x20B4U, 0x60DCU, 0xD052U, 0x804FU, 0x30C1U, + 0x1075U, 0xA0FBU, 0xF0E6U, 0x4068U, 0x7036U, 0xC0B8U, 0x90A5U, 0x202BU, 0x009FU, 0xB011U, 0xE00CU, 0x5082U, + 0x10EAU, 0xA064U, 0xF079U, 0x40F7U, 0x6043U, 0xD0CDU, 0x80D0U, 0x305EU, 0xD06CU, 0x60E2U, 0x30FFU, 0x8071U, + 0xA0C5U, 0x104BU, 0x4056U, 0xF0D8U, 0xB0B0U, 0x003EU, 0x5023U, 0xE0ADU, 0xC019U, 0x7097U, 0x208AU, 0x9004U, + 0xA05AU, 0x10D4U, 0x40C9U, 0xF047U, 0xD0F3U, 0x607DU, 0x3060U, 0x80EEU, 0xC086U, 0x7008U, 0x2015U, 0x909BU, + 0xB02FU, 0x00A1U, 0x50BCU, 0xE032U, 0x90D9U, 0x2057U, 0x704AU, 0xC0C4U, 0xE070U, 0x50FEU, 0x00E3U, 0xB06DU, + 0xF005U, 0x408BU, 0x1096U, 0xA018U, 0x80ACU, 0x3022U, 0x603FU, 0xD0B1U, 0xE0EFU, 0x5061U, 0x007CU, 0xB0F2U, + 0x9046U, 0x20C8U, 0x70D5U, 0xC05BU, 0x8033U, 0x30BDU, 0x60A0U, 0xD02EU, 0xF09AU, 0x4014U, 0x1009U, 0xA087U, + 0x40B5U, 0xF03BU, 0xA026U, 0x10A8U, 0x301CU, 0x8092U, 0xD08FU, 0x6001U, 0x2069U, 0x90E7U, 0xC0FAU, 0x7074U, + 0x50C0U, 0xE04EU, 0xB053U, 0x00DDU, 0x3083U, 0x800DU, 0xD010U, 0x609EU, 0x402AU, 0xF0A4U, 0xA0B9U, 0x1037U, + 0x505FU, 0xE0D1U, 0xB0CCU, 0x0042U, 0x20F6U, 0x9078U, 0xC065U, 0x70EBU, 0xA03DU, 0x10B3U, 0x40AEU, 0xF020U, + 0xD094U, 0x601AU, 0x3007U, 0x8089U, 0xC0E1U, 0x706FU, 0x2072U, 0x90FCU, 0xB048U, 0x00C6U, 0x50DBU, 0xE055U, + 0xD00BU, 0x6085U, 0x3098U, 0x8016U, 0xA0A2U, 0x102CU, 0x4031U, 0xF0BFU, 0xB0D7U, 0x0059U, 0x5044U, 0xE0CAU, + 0xC07EU, 0x70F0U, 0x20EDU, 0x9063U, 0x7051U, 0xC0DFU, 0x90C2U, 0x204CU, 0x00F8U, 0xB076U, 0xE06BU, 0x50E5U, + 0x108DU, 0xA003U, 0xF01EU, 0x4090U, 0x6024U, 0xD0AAU, 0x80B7U, 0x3039U, 0x0067U, 0xB0E9U, 0xE0F4U, 0x507AU, + 0x70CEU, 0xC040U, 0x905DU, 0x20D3U, 0x60BBU, 0xD035U, 0x8028U, 0x30A6U, 0x1012U, 0xA09CU, 0xF081U, 0x400FU, + 0x30E4U, 0x806AU, 0xD077U, 0x60F9U, 0x404DU, 0xF0C3U, 0xA0DEU, 0x1050U, 0x5038U, 0xE0B6U, 0xB0ABU, 0x0025U, + 0x2091U, 0x901FU, 0xC002U, 0x708CU, 0x40D2U, 0xF05CU, 0xA041U, 0x10CFU, 0x307BU, 0x80F5U, 0xD0E8U, 0x6066U, + 0x200EU, 0x9080U, 0xC09DU, 0x7013U, 0x50A7U, 0xE029U, 0xB034U, 0x00BAU, 0xE088U, 0x5006U, 0x001BU, 0xB095U, + 0x9021U, 0x20AFU, 0x70B2U, 0xC03CU, 0x8054U, 0x30DAU, 0x60C7U, 0xD049U, 0xF0FDU, 0x4073U, 0x106EU, 0xA0E0U, + 0x90BEU, 0x2030U, 0x702DU, 0xC0A3U, 0xE017U, 0x5099U, 0x0084U, 0xB00AU, 0xF062U, 0x40ECU, 0x10F1U, 0xA07FU, + 0x80CBU, 0x3045U, 0x6058U, 0xD0D6U }; + +const uint32_t DECODING_TABLE_1987[] = { + 0x00000U, 0x00001U, 0x00002U, 0x00003U, 0x00004U, 0x00005U, 0x00006U, 0x00007U, 0x00008U, 0x00009U, 0x0000AU, 0x0000BU, 0x0000CU, + 0x0000DU, 0x0000EU, 0x24020U, 0x00010U, 0x00011U, 0x00012U, 0x00013U, 0x00014U, 0x00015U, 0x00016U, 0x00017U, 0x00018U, 0x00019U, + 0x0001AU, 0x0001BU, 0x0001CU, 0x0001DU, 0x48040U, 0x01480U, 0x00020U, 0x00021U, 0x00022U, 0x00023U, 0x00024U, 0x00025U, 0x00026U, + 0x24008U, 0x00028U, 0x00029U, 0x0002AU, 0x24004U, 0x0002CU, 0x24002U, 0x24001U, 0x24000U, 0x00030U, 0x00031U, 0x00032U, 0x08180U, + 0x00034U, 0x00C40U, 0x00036U, 0x00C42U, 0x00038U, 0x43000U, 0x0003AU, 0x43002U, 0x02902U, 0x24012U, 0x02900U, 0x24010U, 0x00040U, + 0x00041U, 0x00042U, 0x00043U, 0x00044U, 0x00045U, 0x00046U, 0x00047U, 0x00048U, 0x00049U, 0x0004AU, 0x02500U, 0x0004CU, 0x0004DU, + 0x48010U, 0x48011U, 0x00050U, 0x00051U, 0x00052U, 0x21200U, 0x00054U, 0x00C20U, 0x48008U, 0x48009U, 0x00058U, 0x00059U, 0x48004U, + 0x48005U, 0x48002U, 0x48003U, 0x48000U, 0x48001U, 0x00060U, 0x00061U, 0x00062U, 0x00063U, 0x00064U, 0x00C10U, 0x10300U, 0x0B000U, + 0x00068U, 0x00069U, 0x01880U, 0x01881U, 0x40181U, 0x40180U, 0x24041U, 0x24040U, 0x00070U, 0x00C04U, 0x00072U, 0x00C06U, 0x00C01U, + 0x00C00U, 0x00C03U, 0x00C02U, 0x05204U, 0x00C0CU, 0x48024U, 0x48025U, 0x05200U, 0x00C08U, 0x48020U, 0x48021U, 0x00080U, 0x00081U, + 0x00082U, 0x00083U, 0x00084U, 0x00085U, 0x00086U, 0x00087U, 0x00088U, 0x00089U, 0x0008AU, 0x50200U, 0x0008CU, 0x0A800U, 0x01411U, + 0x01410U, 0x00090U, 0x00091U, 0x00092U, 0x08120U, 0x00094U, 0x00095U, 0x04A00U, 0x01408U, 0x00098U, 0x00099U, 0x01405U, 0x01404U, + 0x01403U, 0x01402U, 0x01401U, 0x01400U, 0x000A0U, 0x000A1U, 0x000A2U, 0x08110U, 0x000A4U, 0x000A5U, 0x42400U, 0x42401U, 0x000A8U, + 0x000A9U, 0x01840U, 0x01841U, 0x40141U, 0x40140U, 0x24081U, 0x24080U, 0x000B0U, 0x08102U, 0x08101U, 0x08100U, 0x000B4U, 0x08106U, + 0x08105U, 0x08104U, 0x20A01U, 0x20A00U, 0x08109U, 0x08108U, 0x01423U, 0x01422U, 0x01421U, 0x01420U, 0x000C0U, 0x000C1U, 0x000C2U, + 0x000C3U, 0x000C4U, 0x000C5U, 0x000C6U, 0x000C7U, 0x000C8U, 0x000C9U, 0x01820U, 0x01821U, 0x20600U, 0x40120U, 0x16000U, 0x16001U, + 0x000D0U, 0x000D1U, 0x42801U, 0x42800U, 0x03100U, 0x18200U, 0x03102U, 0x18202U, 0x000D8U, 0x000D9U, 0x48084U, 0x01444U, 0x48082U, + 0x01442U, 0x48080U, 0x01440U, 0x000E0U, 0x32000U, 0x01808U, 0x04600U, 0x40109U, 0x40108U, 0x0180CU, 0x4010AU, 0x01802U, 0x40104U, + 0x01800U, 0x01801U, 0x40101U, 0x40100U, 0x01804U, 0x40102U, 0x0A408U, 0x08142U, 0x08141U, 0x08140U, 0x00C81U, 0x00C80U, 0x00C83U, + 0x00C82U, 0x0A400U, 0x0A401U, 0x01810U, 0x01811U, 0x40111U, 0x40110U, 0x01814U, 0x40112U, 0x00100U, 0x00101U, 0x00102U, 0x00103U, + 0x00104U, 0x00105U, 0x00106U, 0x41800U, 0x00108U, 0x00109U, 0x0010AU, 0x02440U, 0x0010CU, 0x0010DU, 0x0010EU, 0x02444U, 0x00110U, + 0x00111U, 0x00112U, 0x080A0U, 0x00114U, 0x00115U, 0x00116U, 0x080A4U, 0x00118U, 0x00119U, 0x15000U, 0x15001U, 0x02822U, 0x02823U, + 0x02820U, 0x02821U, 0x00120U, 0x00121U, 0x00122U, 0x08090U, 0x00124U, 0x00125U, 0x10240U, 0x10241U, 0x00128U, 0x00129U, 0x0012AU, + 0x24104U, 0x09400U, 0x400C0U, 0x02810U, 0x24100U, 0x00130U, 0x08082U, 0x08081U, 0x08080U, 0x31001U, 0x31000U, 0x02808U, 0x08084U, + 0x02806U, 0x0808AU, 0x02804U, 0x08088U, 0x02802U, 0x02803U, 0x02800U, 0x02801U, 0x00140U, 0x00141U, 0x00142U, 0x02408U, 0x00144U, + 0x00145U, 0x10220U, 0x10221U, 0x00148U, 0x02402U, 0x02401U, 0x02400U, 0x400A1U, 0x400A0U, 0x02405U, 0x02404U, 0x00150U, 0x00151U, + 0x00152U, 0x02418U, 0x03080U, 0x03081U, 0x03082U, 0x03083U, 0x09801U, 0x09800U, 0x02411U, 0x02410U, 0x48102U, 0x09804U, 0x48100U, + 0x48101U, 0x00160U, 0x00161U, 0x10204U, 0x10205U, 0x10202U, 0x40088U, 0x10200U, 0x10201U, 0x40085U, 0x40084U, 0x02421U, 0x02420U, + 0x40081U, 0x40080U, 0x10208U, 0x40082U, 0x41402U, 0x080C2U, 0x41400U, 0x080C0U, 0x00D01U, 0x00D00U, 0x10210U, 0x10211U, 0x40095U, + 0x40094U, 0x02844U, 0x080C8U, 0x40091U, 0x40090U, 0x02840U, 0x02841U, 0x00180U, 0x00181U, 0x00182U, 0x08030U, 0x00184U, 0x14400U, + 0x22201U, 0x22200U, 0x00188U, 0x00189U, 0x0018AU, 0x08038U, 0x40061U, 0x40060U, 0x40063U, 0x40062U, 0x00190U, 0x08022U, 0x08021U, + 0x08020U, 0x03040U, 0x03041U, 0x08025U, 0x08024U, 0x40C00U, 0x40C01U, 0x08029U, 0x08028U, 0x2C000U, 0x2C001U, 0x01501U, 0x01500U, + 0x001A0U, 0x08012U, 0x08011U, 0x08010U, 0x40049U, 0x40048U, 0x08015U, 0x08014U, 0x06200U, 0x40044U, 0x30400U, 0x08018U, 0x40041U, + 0x40040U, 0x40043U, 0x40042U, 0x08003U, 0x08002U, 0x08001U, 0x08000U, 0x08007U, 0x08006U, 0x08005U, 0x08004U, 0x0800BU, 0x0800AU, + 0x08009U, 0x08008U, 0x40051U, 0x40050U, 0x02880U, 0x0800CU, 0x001C0U, 0x001C1U, 0x64000U, 0x64001U, 0x03010U, 0x40028U, 0x08C00U, + 0x08C01U, 0x40025U, 0x40024U, 0x02481U, 0x02480U, 0x40021U, 0x40020U, 0x40023U, 0x40022U, 0x03004U, 0x03005U, 0x08061U, 0x08060U, + 0x03000U, 0x03001U, 0x03002U, 0x03003U, 0x0300CU, 0x40034U, 0x30805U, 0x30804U, 0x03008U, 0x40030U, 0x30801U, 0x30800U, 0x4000DU, + 0x4000CU, 0x08051U, 0x08050U, 0x40009U, 0x40008U, 0x10280U, 0x4000AU, 0x40005U, 0x40004U, 0x01900U, 0x40006U, 0x40001U, 0x40000U, + 0x40003U, 0x40002U, 0x14800U, 0x08042U, 0x08041U, 0x08040U, 0x03020U, 0x40018U, 0x08045U, 0x08044U, 0x40015U, 0x40014U, 0x08049U, + 0x08048U, 0x40011U, 0x40010U, 0x40013U, 0x40012U, 0x00200U, 0x00201U, 0x00202U, 0x00203U, 0x00204U, 0x00205U, 0x00206U, 0x00207U, + 0x00208U, 0x00209U, 0x0020AU, 0x50080U, 0x0020CU, 0x0020DU, 0x0020EU, 0x50084U, 0x00210U, 0x00211U, 0x00212U, 0x21040U, 0x00214U, + 0x00215U, 0x04880U, 0x04881U, 0x00218U, 0x00219U, 0x0E001U, 0x0E000U, 0x0021CU, 0x0021DU, 0x04888U, 0x0E004U, 0x00220U, 0x00221U, + 0x00222U, 0x00223U, 0x00224U, 0x00225U, 0x10140U, 0x10141U, 0x00228U, 0x00229U, 0x0022AU, 0x24204U, 0x12401U, 0x12400U, 0x24201U, + 0x24200U, 0x00230U, 0x00231U, 0x00232U, 0x21060U, 0x2A000U, 0x2A001U, 0x2A002U, 0x2A003U, 0x20881U, 0x20880U, 0x20883U, 0x20882U, + 0x05040U, 0x05041U, 0x05042U, 0x24210U, 0x00240U, 0x00241U, 0x00242U, 0x21010U, 0x00244U, 0x46000U, 0x10120U, 0x10121U, 0x00248U, + 0x00249U, 0x0024AU, 0x21018U, 0x20480U, 0x20481U, 0x20482U, 0x20483U, 0x00250U, 0x21002U, 0x21001U, 0x21000U, 0x18081U, 0x18080U, + 0x21005U, 0x21004U, 0x12800U, 0x12801U, 0x21009U, 0x21008U, 0x05020U, 0x05021U, 0x48200U, 0x48201U, 0x00260U, 0x00261U, 0x10104U, + 0x04480U, 0x10102U, 0x10103U, 0x10100U, 0x10101U, 0x62002U, 0x62003U, 0x62000U, 0x62001U, 0x05010U, 0x05011U, 0x10108U, 0x10109U, + 0x0500CU, 0x21022U, 0x21021U, 0x21020U, 0x05008U, 0x00E00U, 0x10110U, 0x10111U, 0x05004U, 0x05005U, 0x05006U, 0x21028U, 0x05000U, + 0x05001U, 0x05002U, 0x05003U, 0x00280U, 0x00281U, 0x00282U, 0x50008U, 0x00284U, 0x00285U, 0x04810U, 0x22100U, 0x00288U, 0x50002U, + 0x50001U, 0x50000U, 0x20440U, 0x20441U, 0x50005U, 0x50004U, 0x00290U, 0x00291U, 0x04804U, 0x04805U, 0x04802U, 0x18040U, 0x04800U, + 0x04801U, 0x20821U, 0x20820U, 0x50011U, 0x50010U, 0x0480AU, 0x01602U, 0x04808U, 0x01600U, 0x002A0U, 0x002A1U, 0x04441U, 0x04440U, + 0x002A4U, 0x002A5U, 0x04830U, 0x04444U, 0x06100U, 0x20810U, 0x50021U, 0x50020U, 0x06104U, 0x20814U, 0x50025U, 0x50024U, 0x20809U, + 0x20808U, 0x13000U, 0x08300U, 0x04822U, 0x2080CU, 0x04820U, 0x04821U, 0x20801U, 0x20800U, 0x20803U, 0x20802U, 0x20805U, 0x20804U, + 0x04828U, 0x20806U, 0x002C0U, 0x002C1U, 0x04421U, 0x04420U, 0x20408U, 0x18010U, 0x2040AU, 0x18012U, 0x20404U, 0x20405U, 0x50041U, + 0x50040U, 0x20400U, 0x20401U, 0x20402U, 0x20403U, 0x18005U, 0x18004U, 0x21081U, 0x21080U, 0x18001U, 0x18000U, 0x04840U, 0x18002U, + 0x20414U, 0x1800CU, 0x21089U, 0x21088U, 0x20410U, 0x18008U, 0x20412U, 0x1800AU, 0x04403U, 0x04402U, 0x04401U, 0x04400U, 0x10182U, + 0x04406U, 0x10180U, 0x04404U, 0x01A02U, 0x0440AU, 0x01A00U, 0x04408U, 0x20420U, 0x40300U, 0x20422U, 0x40302U, 0x04413U, 0x04412U, + 0x04411U, 0x04410U, 0x18021U, 0x18020U, 0x10190U, 0x18022U, 0x20841U, 0x20840U, 0x01A10U, 0x20842U, 0x05080U, 0x05081U, 0x05082U, + 0x05083U, 0x00300U, 0x00301U, 0x00302U, 0x00303U, 0x00304U, 0x00305U, 0x10060U, 0x22080U, 0x00308U, 0x00309U, 0x28800U, 0x28801U, + 0x44402U, 0x44403U, 0x44400U, 0x44401U, 0x00310U, 0x00311U, 0x10C01U, 0x10C00U, 0x00314U, 0x00315U, 0x10070U, 0x10C04U, 0x00318U, + 0x00319U, 0x28810U, 0x10C08U, 0x44412U, 0x00000U, 0x44410U, 0x44411U, 0x00320U, 0x60400U, 0x10044U, 0x10045U, 0x10042U, 0x0C800U, + 0x10040U, 0x10041U, 0x06080U, 0x06081U, 0x06082U, 0x06083U, 0x1004AU, 0x0C808U, 0x10048U, 0x10049U, 0x58008U, 0x08282U, 0x08281U, + 0x08280U, 0x10052U, 0x0C810U, 0x10050U, 0x10051U, 0x58000U, 0x58001U, 0x58002U, 0x08288U, 0x02A02U, 0x02A03U, 0x02A00U, 0x02A01U, + 0x00340U, 0x00341U, 0x10024U, 0x10025U, 0x10022U, 0x10023U, 0x10020U, 0x10021U, 0x34001U, 0x34000U, 0x02601U, 0x02600U, 0x1002AU, + 0x34004U, 0x10028U, 0x10029U, 0x0C400U, 0x0C401U, 0x21101U, 0x21100U, 0x60800U, 0x60801U, 0x10030U, 0x10031U, 0x0C408U, 0x34010U, + 0x21109U, 0x21108U, 0x60808U, 0x60809U, 0x10038U, 0x28420U, 0x10006U, 0x10007U, 0x10004U, 0x10005U, 0x10002U, 0x10003U, 0x10000U, + 0x10001U, 0x1000EU, 0x40284U, 0x1000CU, 0x1000DU, 0x1000AU, 0x40280U, 0x10008U, 0x10009U, 0x10016U, 0x10017U, 0x10014U, 0x10015U, + 0x10012U, 0x10013U, 0x10010U, 0x10011U, 0x05104U, 0x44802U, 0x44801U, 0x44800U, 0x05100U, 0x05101U, 0x10018U, 0x28400U, 0x00380U, + 0x00381U, 0x22005U, 0x22004U, 0x22003U, 0x22002U, 0x22001U, 0x22000U, 0x06020U, 0x06021U, 0x50101U, 0x50100U, 0x11800U, 0x11801U, + 0x22009U, 0x22008U, 0x45001U, 0x45000U, 0x08221U, 0x08220U, 0x04902U, 0x22012U, 0x04900U, 0x22010U, 0x06030U, 0x45008U, 0x08229U, + 0x08228U, 0x11810U, 0x11811U, 0x04908U, 0x22018U, 0x06008U, 0x06009U, 0x08211U, 0x08210U, 0x100C2U, 0x22022U, 0x100C0U, 0x22020U, + 0x06000U, 0x06001U, 0x06002U, 0x06003U, 0x06004U, 0x40240U, 0x06006U, 0x40242U, 0x08203U, 0x08202U, 0x08201U, 0x08200U, 0x08207U, + 0x08206U, 0x08205U, 0x08204U, 0x06010U, 0x20900U, 0x08209U, 0x08208U, 0x61002U, 0x20904U, 0x61000U, 0x61001U, 0x29020U, 0x29021U, + 0x100A4U, 0x22044U, 0x100A2U, 0x22042U, 0x100A0U, 0x22040U, 0x20504U, 0x40224U, 0x0D005U, 0x0D004U, 0x20500U, 0x40220U, 0x0D001U, + 0x0D000U, 0x03204U, 0x18104U, 0x08261U, 0x08260U, 0x03200U, 0x18100U, 0x03202U, 0x18102U, 0x11421U, 0x11420U, 0x00000U, 0x11422U, + 0x03208U, 0x18108U, 0x0D011U, 0x0D010U, 0x29000U, 0x29001U, 0x10084U, 0x04500U, 0x10082U, 0x40208U, 0x10080U, 0x10081U, 0x06040U, + 0x40204U, 0x06042U, 0x40206U, 0x40201U, 0x40200U, 0x10088U, 0x40202U, 0x29010U, 0x08242U, 0x08241U, 0x08240U, 0x10092U, 0x40218U, + 0x10090U, 0x10091U, 0x11401U, 0x11400U, 0x11403U, 0x11402U, 0x40211U, 0x40210U, 0x10098U, 0x40212U, 0x00400U, 0x00401U, 0x00402U, + 0x00403U, 0x00404U, 0x00405U, 0x00406U, 0x00407U, 0x00408U, 0x00409U, 0x0040AU, 0x02140U, 0x0040CU, 0x0040DU, 0x01091U, 0x01090U, + 0x00410U, 0x00411U, 0x00412U, 0x00413U, 0x00414U, 0x00860U, 0x01089U, 0x01088U, 0x00418U, 0x38000U, 0x01085U, 0x01084U, 0x01083U, + 0x01082U, 0x01081U, 0x01080U, 0x00420U, 0x00421U, 0x00422U, 0x00423U, 0x00424U, 0x00850U, 0x42080U, 0x42081U, 0x00428U, 0x00429U, + 0x48801U, 0x48800U, 0x09100U, 0x12200U, 0x24401U, 0x24400U, 0x00430U, 0x00844U, 0x00432U, 0x00846U, 0x00841U, 0x00840U, 0x1C000U, + 0x00842U, 0x00438U, 0x0084CU, 0x010A5U, 0x010A4U, 0x00849U, 0x00848U, 0x010A1U, 0x010A0U, 0x00440U, 0x00441U, 0x00442U, 0x02108U, + 0x00444U, 0x00830U, 0x70001U, 0x70000U, 0x00448U, 0x02102U, 0x02101U, 0x02100U, 0x20280U, 0x20281U, 0x02105U, 0x02104U, 0x00450U, + 0x00824U, 0x00452U, 0x00826U, 0x00821U, 0x00820U, 0x00823U, 0x00822U, 0x24802U, 0x02112U, 0x24800U, 0x02110U, 0x00829U, 0x00828U, + 0x48400U, 0x010C0U, 0x00460U, 0x00814U, 0x04281U, 0x04280U, 0x00811U, 0x00810U, 0x00813U, 0x00812U, 0x54000U, 0x54001U, 0x02121U, + 0x02120U, 0x00819U, 0x00818U, 0x0081BU, 0x0081AU, 0x00805U, 0x00804U, 0x41100U, 0x00806U, 0x00801U, 0x00800U, 0x00803U, 0x00802U, + 0x0A080U, 0x0080CU, 0x0A082U, 0x0080EU, 0x00809U, 0x00808U, 0x0080BU, 0x0080AU, 0x00480U, 0x00481U, 0x00482U, 0x00483U, 0x00484U, + 0x14100U, 0x42020U, 0x01018U, 0x00488U, 0x00489U, 0x01015U, 0x01014U, 0x20240U, 0x01012U, 0x01011U, 0x01010U, 0x00490U, 0x00491U, + 0x0100DU, 0x0100CU, 0x0100BU, 0x0100AU, 0x01009U, 0x01008U, 0x40900U, 0x01006U, 0x01005U, 0x01004U, 0x01003U, 0x01002U, 0x01001U, + 0x01000U, 0x004A0U, 0x004A1U, 0x42004U, 0x04240U, 0x42002U, 0x42003U, 0x42000U, 0x42001U, 0x30102U, 0x30103U, 0x30100U, 0x30101U, + 0x4200AU, 0x01032U, 0x42008U, 0x01030U, 0x25000U, 0x25001U, 0x08501U, 0x08500U, 0x008C1U, 0x008C0U, 0x42010U, 0x01028U, 0x0A040U, + 0x0A041U, 0x01025U, 0x01024U, 0x01023U, 0x01022U, 0x01021U, 0x01020U, 0x004C0U, 0x49000U, 0x04221U, 0x04220U, 0x20208U, 0x20209U, + 0x08900U, 0x08901U, 0x20204U, 0x20205U, 0x02181U, 0x02180U, 0x20200U, 0x20201U, 0x20202U, 0x01050U, 0x0A028U, 0x008A4U, 0x0104DU, + 0x0104CU, 0x008A1U, 0x008A0U, 0x01049U, 0x01048U, 0x0A020U, 0x0A021U, 0x01045U, 0x01044U, 0x20210U, 0x01042U, 0x01041U, 0x01040U, + 0x04203U, 0x04202U, 0x04201U, 0x04200U, 0x00891U, 0x00890U, 0x42040U, 0x04204U, 0x0A010U, 0x0A011U, 0x01C00U, 0x04208U, 0x20220U, + 0x40500U, 0x20222U, 0x40502U, 0x0A008U, 0x00884U, 0x04211U, 0x04210U, 0x00881U, 0x00880U, 0x00883U, 0x00882U, 0x0A000U, 0x0A001U, + 0x0A002U, 0x0A003U, 0x0A004U, 0x00888U, 0x01061U, 0x01060U, 0x00500U, 0x00501U, 0x00502U, 0x02048U, 0x00504U, 0x14080U, 0x00506U, + 0x14082U, 0x00508U, 0x02042U, 0x02041U, 0x02040U, 0x09020U, 0x09021U, 0x44200U, 0x02044U, 0x00510U, 0x00511U, 0x10A01U, 0x10A00U, + 0x4A001U, 0x4A000U, 0x4A003U, 0x4A002U, 0x40880U, 0x40881U, 0x02051U, 0x02050U, 0x40884U, 0x01182U, 0x01181U, 0x01180U, 0x00520U, + 0x60200U, 0x00522U, 0x60202U, 0x09008U, 0x09009U, 0x0900AU, 0x0900BU, 0x09004U, 0x09005U, 0x30080U, 0x02060U, 0x09000U, 0x09001U, + 0x09002U, 0x09003U, 0x41042U, 0x08482U, 0x41040U, 0x08480U, 0x00941U, 0x00940U, 0x41044U, 0x00942U, 0x09014U, 0x09015U, 0x02C04U, + 0x08488U, 0x09010U, 0x09011U, 0x02C00U, 0x02C01U, 0x00540U, 0x0200AU, 0x02009U, 0x02008U, 0x08882U, 0x0200EU, 0x08880U, 0x0200CU, + 0x02003U, 0x02002U, 0x02001U, 0x02000U, 0x02007U, 0x02006U, 0x02005U, 0x02004U, 0x0C200U, 0x0C201U, 0x41020U, 0x02018U, 0x00921U, + 0x00920U, 0x41024U, 0x00922U, 0x02013U, 0x02012U, 0x02011U, 0x02010U, 0x02017U, 0x02016U, 0x02015U, 0x02014U, 0x41012U, 0x0202AU, + 0x41010U, 0x02028U, 0x26000U, 0x00910U, 0x10600U, 0x10601U, 0x02023U, 0x02022U, 0x02021U, 0x02020U, 0x09040U, 0x40480U, 0x02025U, + 0x02024U, 0x41002U, 0x00904U, 0x41000U, 0x41001U, 0x00901U, 0x00900U, 0x41004U, 0x00902U, 0x4100AU, 0x02032U, 0x41008U, 0x02030U, + 0x00909U, 0x00908U, 0x28201U, 0x28200U, 0x00580U, 0x14004U, 0x00582U, 0x14006U, 0x14001U, 0x14000U, 0x08840U, 0x14002U, 0x40810U, + 0x40811U, 0x30020U, 0x020C0U, 0x14009U, 0x14008U, 0x01111U, 0x01110U, 0x40808U, 0x40809U, 0x08421U, 0x08420U, 0x14011U, 0x14010U, + 0x01109U, 0x01108U, 0x40800U, 0x40801U, 0x40802U, 0x01104U, 0x40804U, 0x01102U, 0x01101U, 0x01100U, 0x03801U, 0x03800U, 0x30008U, + 0x08410U, 0x14021U, 0x14020U, 0x42100U, 0x42101U, 0x30002U, 0x30003U, 0x30000U, 0x30001U, 0x09080U, 0x40440U, 0x30004U, 0x30005U, + 0x08403U, 0x08402U, 0x08401U, 0x08400U, 0x08407U, 0x08406U, 0x08405U, 0x08404U, 0x40820U, 0x40821U, 0x30010U, 0x08408U, 0x40824U, + 0x01122U, 0x01121U, 0x01120U, 0x08806U, 0x0208AU, 0x08804U, 0x02088U, 0x08802U, 0x14040U, 0x08800U, 0x08801U, 0x02083U, 0x02082U, + 0x02081U, 0x02080U, 0x20300U, 0x40420U, 0x08808U, 0x02084U, 0x03404U, 0x03405U, 0x08814U, 0x02098U, 0x03400U, 0x03401U, 0x08810U, + 0x08811U, 0x40840U, 0x40841U, 0x02091U, 0x02090U, 0x40844U, 0x01142U, 0x01141U, 0x01140U, 0x04303U, 0x04302U, 0x04301U, 0x04300U, + 0x40409U, 0x40408U, 0x08820U, 0x08821U, 0x40405U, 0x40404U, 0x30040U, 0x020A0U, 0x40401U, 0x40400U, 0x40403U, 0x40402U, 0x41082U, + 0x08442U, 0x41080U, 0x08440U, 0x00981U, 0x00980U, 0x41084U, 0x00982U, 0x0A100U, 0x11200U, 0x0A102U, 0x11202U, 0x40411U, 0x40410U, + 0x40413U, 0x40412U, 0x00600U, 0x00601U, 0x00602U, 0x00603U, 0x00604U, 0x00605U, 0x00606U, 0x00607U, 0x00608U, 0x05800U, 0x0060AU, + 0x05802U, 0x200C0U, 0x12020U, 0x44100U, 0x44101U, 0x00610U, 0x00611U, 0x10901U, 0x10900U, 0x51000U, 0x51001U, 0x51002U, 0x10904U, + 0x00618U, 0x05810U, 0x01285U, 0x01284U, 0x51008U, 0x01282U, 0x01281U, 0x01280U, 0x00620U, 0x60100U, 0x040C1U, 0x040C0U, 0x12009U, + 0x12008U, 0x21800U, 0x21801U, 0x12005U, 0x12004U, 0x12007U, 0x12006U, 0x12001U, 0x12000U, 0x12003U, 0x12002U, 0x00630U, 0x00A44U, + 0x040D1U, 0x040D0U, 0x00A41U, 0x00A40U, 0x21810U, 0x00A42U, 0x12015U, 0x12014U, 0x00000U, 0x12016U, 0x12011U, 0x12010U, 0x12013U, + 0x12012U, 0x00640U, 0x00641U, 0x040A1U, 0x040A0U, 0x20088U, 0x20089U, 0x2008AU, 0x040A4U, 0x20084U, 0x20085U, 0x19000U, 0x02300U, + 0x20080U, 0x20081U, 0x20082U, 0x20083U, 0x0C100U, 0x0C101U, 0x21401U, 0x21400U, 0x00A21U, 0x00A20U, 0x00A23U, 0x00A22U, 0x20094U, + 0x20095U, 0x19010U, 0x21408U, 0x20090U, 0x20091U, 0x20092U, 0x28120U, 0x04083U, 0x04082U, 0x04081U, 0x04080U, 0x00A11U, 0x00A10U, + 0x10500U, 0x04084U, 0x200A4U, 0x0408AU, 0x04089U, 0x04088U, 0x200A0U, 0x12040U, 0x200A2U, 0x12042U, 0x00A05U, 0x00A04U, 0x04091U, + 0x04090U, 0x00A01U, 0x00A00U, 0x00A03U, 0x00A02U, 0x05404U, 0x00A0CU, 0x28105U, 0x28104U, 0x05400U, 0x00A08U, 0x28101U, 0x28100U, + 0x00680U, 0x00681U, 0x04061U, 0x04060U, 0x20048U, 0x20049U, 0x2004AU, 0x04064U, 0x20044U, 0x20045U, 0x50401U, 0x50400U, 0x20040U, + 0x20041U, 0x20042U, 0x01210U, 0x68002U, 0x68003U, 0x68000U, 0x68001U, 0x04C02U, 0x0120AU, 0x04C00U, 0x01208U, 0x20054U, 0x01206U, + 0x01205U, 0x01204U, 0x20050U, 0x01202U, 0x01201U, 0x01200U, 0x18800U, 0x04042U, 0x04041U, 0x04040U, 0x42202U, 0x04046U, 0x42200U, + 0x04044U, 0x20064U, 0x0404AU, 0x04049U, 0x04048U, 0x20060U, 0x12080U, 0x20062U, 0x12082U, 0x18810U, 0x04052U, 0x04051U, 0x04050U, + 0x4C009U, 0x4C008U, 0x42210U, 0x04054U, 0x20C01U, 0x20C00U, 0x20C03U, 0x20C02U, 0x4C001U, 0x4C000U, 0x01221U, 0x01220U, 0x2000CU, + 0x04022U, 0x04021U, 0x04020U, 0x20008U, 0x20009U, 0x2000AU, 0x04024U, 0x20004U, 0x20005U, 0x20006U, 0x04028U, 0x20000U, 0x20001U, + 0x20002U, 0x20003U, 0x2001CU, 0x04032U, 0x04031U, 0x04030U, 0x20018U, 0x18400U, 0x2001AU, 0x18402U, 0x20014U, 0x20015U, 0x20016U, + 0x01244U, 0x20010U, 0x20011U, 0x20012U, 0x01240U, 0x04003U, 0x04002U, 0x04001U, 0x04000U, 0x20028U, 0x04006U, 0x04005U, 0x04004U, + 0x20024U, 0x0400AU, 0x04009U, 0x04008U, 0x20020U, 0x20021U, 0x20022U, 0x0400CU, 0x04013U, 0x04012U, 0x04011U, 0x04010U, 0x00A81U, + 0x00A80U, 0x04015U, 0x04014U, 0x0A200U, 0x11100U, 0x04019U, 0x04018U, 0x20030U, 0x20031U, 0x50800U, 0x50801U, 0x00700U, 0x60020U, + 0x10811U, 0x10810U, 0x4400AU, 0x60024U, 0x44008U, 0x44009U, 0x44006U, 0x02242U, 0x44004U, 0x02240U, 0x44002U, 0x44003U, 0x44000U, + 0x44001U, 0x0C040U, 0x10802U, 0x10801U, 0x10800U, 0x0C044U, 0x10806U, 0x10805U, 0x10804U, 0x23000U, 0x23001U, 0x10809U, 0x10808U, + 0x44012U, 0x44013U, 0x44010U, 0x44011U, 0x60001U, 0x60000U, 0x60003U, 0x60002U, 0x60005U, 0x60004U, 0x10440U, 0x10441U, 0x60009U, + 0x60008U, 0x44024U, 0x6000AU, 0x09200U, 0x12100U, 0x44020U, 0x44021U, 0x60011U, 0x60010U, 0x10821U, 0x10820U, 0x07003U, 0x07002U, + 0x07001U, 0x07000U, 0x23020U, 0x60018U, 0x28045U, 0x28044U, 0x09210U, 0x28042U, 0x28041U, 0x28040U, 0x0C010U, 0x0C011U, 0x02209U, + 0x02208U, 0x10422U, 0x10423U, 0x10420U, 0x10421U, 0x02203U, 0x02202U, 0x02201U, 0x02200U, 0x20180U, 0x20181U, 0x44040U, 0x02204U, + 0x0C000U, 0x0C001U, 0x0C002U, 0x10840U, 0x0C004U, 0x0C005U, 0x0C006U, 0x10844U, 0x0C008U, 0x0C009U, 0x02211U, 0x02210U, 0x0C00CU, + 0x28022U, 0x28021U, 0x28020U, 0x60041U, 0x60040U, 0x10404U, 0x04180U, 0x10402U, 0x10403U, 0x10400U, 0x10401U, 0x02223U, 0x02222U, + 0x02221U, 0x02220U, 0x1040AU, 0x28012U, 0x10408U, 0x28010U, 0x0C020U, 0x0C021U, 0x41200U, 0x41201U, 0x00B01U, 0x00B00U, 0x10410U, + 0x28008U, 0x11081U, 0x11080U, 0x28005U, 0x28004U, 0x28003U, 0x28002U, 0x28001U, 0x28000U, 0x52040U, 0x14204U, 0x22405U, 0x22404U, + 0x14201U, 0x14200U, 0x22401U, 0x22400U, 0x20144U, 0x20145U, 0x44084U, 0x022C0U, 0x20140U, 0x20141U, 0x44080U, 0x44081U, 0x40A08U, + 0x10882U, 0x10881U, 0x10880U, 0x14211U, 0x14210U, 0x1A008U, 0x10884U, 0x40A00U, 0x40A01U, 0x40A02U, 0x01304U, 0x1A002U, 0x01302U, + 0x1A000U, 0x01300U, 0x60081U, 0x60080U, 0x04141U, 0x04140U, 0x60085U, 0x60084U, 0x104C0U, 0x04144U, 0x06400U, 0x06401U, 0x30200U, + 0x30201U, 0x06404U, 0x40640U, 0x30204U, 0x30205U, 0x08603U, 0x08602U, 0x08601U, 0x08600U, 0x00000U, 0x08606U, 0x08605U, 0x08604U, + 0x11041U, 0x11040U, 0x30210U, 0x11042U, 0x11045U, 0x11044U, 0x1A020U, 0x01320U, 0x52000U, 0x52001U, 0x04121U, 0x04120U, 0x20108U, + 0x20109U, 0x08A00U, 0x08A01U, 0x20104U, 0x20105U, 0x02281U, 0x02280U, 0x20100U, 0x20101U, 0x20102U, 0x20103U, 0x0C080U, 0x0C081U, + 0x0C082U, 0x04130U, 0x0C084U, 0x06808U, 0x08A10U, 0x08A11U, 0x11021U, 0x11020U, 0x11023U, 0x11022U, 0x20110U, 0x06800U, 0x20112U, + 0x06802U, 0x04103U, 0x04102U, 0x04101U, 0x04100U, 0x10482U, 0x04106U, 0x10480U, 0x04104U, 0x11011U, 0x11010U, 0x04109U, 0x04108U, + 0x20120U, 0x40600U, 0x20122U, 0x40602U, 0x11009U, 0x11008U, 0x22800U, 0x04110U, 0x1100DU, 0x1100CU, 0x22804U, 0x04114U, 0x11001U, + 0x11000U, 0x11003U, 0x11002U, 0x11005U, 0x11004U, 0x28081U, 0x28080U }; + +#define X18 0x00040000 /* vector representation of X^{18} */ +#define X11 0x00000800 /* vector representation of X^{11} */ +#define MASK8 0xfffff800 /* auxiliary vector for testing */ +#define GENPOL 0x00000c75 /* generator polinomial, g(x) */ + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the DMRSlotType class. +/// +DMRSlotType::DMRSlotType() +{ + /* stub */ +} + +/// +/// Decodes DMR slot type. +/// +/// +/// +/// +void DMRSlotType::decode(const uint8_t* frame, uint8_t& colorCode, uint8_t& dataType) const +{ + uint8_t slotType[3U]; + slotType[0U] = (frame[12U] << 2) & 0xFCU; + slotType[0U] |= (frame[13U] >> 6) & 0x03U; + + slotType[1U] = (frame[13U] << 2) & 0xC0U; + slotType[1U] |= (frame[19U] << 2) & 0x3CU; + slotType[1U] |= (frame[20U] >> 6) & 0x03U; + + slotType[2U] = (frame[20U] << 2) & 0xF0U; + + uint8_t code = decode2087(slotType); + + colorCode = (code >> 4) & 0x0FU; + dataType = (code >> 0) & 0x0FU; +} + +/// +/// Encodes DMR slot type. +/// +/// +/// +/// +void DMRSlotType::encode(uint8_t colorCode, uint8_t dataType, uint8_t* frame) const +{ + uint8_t slotType[3U]; + slotType[0U] = (colorCode << 4) & 0xF0U; + slotType[0U] |= (dataType << 0) & 0x0FU; + + uint16_t cksum = ENCODING_TABLE_2087[slotType[0U]]; + + slotType[1U] = (cksum >> 0) & 0xFFU; + slotType[2U] = (cksum >> 8) & 0xFFU; + + frame[12U] = (frame[12U] & 0xC0U) | ((slotType[0U] >> 2) & 0x3FU); + frame[13U] = (frame[13U] & 0x0FU) | ((slotType[0U] << 6) & 0xC0U) | ((slotType[1U] >> 2) & 0x30U); + frame[19U] = (frame[19U] & 0xF0U) | ((slotType[1U] >> 2) & 0x0FU); + frame[20U] = (frame[20U] & 0x03U) | ((slotType[1U] << 6) & 0xC0U) | ((slotType[2U] >> 2) & 0x3CU); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// +/// +/// +/// +uint8_t DMRSlotType::decode2087(const uint8_t* data) const +{ + uint32_t code = (data[0U] << 11) + (data[1U] << 3) + (data[2U] >> 5); + uint32_t syndrome = getSyndrome1987(code); + uint32_t error_pattern = DECODING_TABLE_1987[syndrome]; + + if (error_pattern != 0x00U) + code ^= error_pattern; + + return code >> 11; +} + +/// +/// +/// +/// +/// Compute the syndrome corresponding to the given pattern, i.e., the +/// remainder after dividing the pattern (when considering it as the vector +/// representation of a polynomial) by the generator polynomial, GENPOL. +/// In the program this pattern has several meanings: (1) pattern = infomation +/// bits, when constructing the encoding table; (2) pattern = error pattern, +/// when constructing the decoding table; and (3) pattern = received vector, to +/// obtain its syndrome in decoding. +/// +/// +/// +uint32_t DMRSlotType::getSyndrome1987(uint32_t pattern) const +{ + unsigned int aux = X18; + + if (pattern >= X11) { + while (pattern & MASK8) { + while (!(aux & pattern)) + aux = aux >> 1; + + pattern ^= (aux / X11) * GENPOL; + } + } + + return pattern; +} diff --git a/dmr/DMRSlotType.h b/dmr/DMRSlotType.h new file mode 100644 index 0000000..f802980 --- /dev/null +++ b/dmr/DMRSlotType.h @@ -0,0 +1,60 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DMR_SLOT_TYPE_H__) +#define __DMR_SLOT_TYPE_H__ + +#include "Defines.h" + +namespace dmr +{ + // --------------------------------------------------------------------------- + // Class Declaration + // + // --------------------------------------------------------------------------- + + class DSP_FW_API DMRSlotType { + public: + /// Initializes a new instance of the DMRSlotType class. + DMRSlotType(); + + /// Decodes DMR slot type. + void decode(const uint8_t* frame, uint8_t& colorCode, uint8_t& dataType) const; + /// Encodes DMR slot type. + void encode(uint8_t colorCode, uint8_t dataType, uint8_t* frame) const; + + private: + /// + uint8_t decode2087(const uint8_t* data) const; + /// + uint32_t getSyndrome1987(uint32_t pattern) const; + }; +} // namespace dmr + +#endif // __DMR_SLOT_TYPE_H__ diff --git a/dmr/DMRTX.cpp b/dmr/DMRTX.cpp new file mode 100644 index 0000000..deb63fd --- /dev/null +++ b/dmr/DMRTX.cpp @@ -0,0 +1,466 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2009-2017 by Jonathan Naylor G4KLX +* Copyright (C) 2016 by Colin Durbridge G4EML +* Copyright (C) 2017 by Andy Uribe CA6JAU +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Globals.h" +#include "dmr/DMRSlotType.h" + +using namespace dmr; + +#if defined(DUPLEX) + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +// The PR FILL and BS Data Sync pattern. +const uint8_t IDLE_DATA[] = { + 0x53U, 0xC2U, 0x5EU, 0xABU, 0xA8U, 0x67U, 0x1DU, 0xC7U, 0x38U, 0x3BU, 0xD9U, + 0x36U, 0x00U, 0x0DU, 0xFFU, 0x57U, 0xD7U, 0x5DU, 0xF5U, 0xD0U, 0x03U, 0xF6U, + 0xE4U, 0x65U, 0x17U, 0x1BU, 0x48U, 0xCAU, 0x6DU, 0x4FU, 0xC6U, 0x10U, 0xB4U +}; + +const uint8_t CACH_INTERLEAVE[] = { + 1U, 2U, 3U, 5U, 6U, 7U, 9U, 10U, 11U, 13U, 15U, 16U, 17U, 19U, 20U, 21U, 23U, + 25U, 26U, 27U, 29U, 30U, 31U, 33U, 34U, 35U, 37U, 39U, 40U, 41U, 43U, 44U, 45U, 47U, + 49U, 50U, 51U, 53U, 54U, 55U, 57U, 58U, 59U, 61U, 63U, 64U, 65U, 67U, 68U, 69U, 71U, + 73U, 74U, 75U, 77U, 78U, 79U, 81U, 82U, 83U, 85U, 87U, 88U, 89U, 91U, 92U, 93U, 95U +}; + +const uint8_t EMPTY_SHORT_LC[] = { + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U +}; + +const uint32_t STARTUP_COUNT = 20U; +const uint32_t ABORT_COUNT = 6U; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the DMRTX class. +/// +DMRTX::DMRTX() : + m_fifo(), + m_state(DMRTXSTATE_IDLE), + m_idle(), + m_cachPtr(0U), + m_shortLC(), + m_newShortLC(), + m_markBuffer(), + m_poBuffer(), + m_poLen(0U), + m_poPtr(0U), + m_frameCount(0U), + m_abortCount(), + m_abort(), + m_control_old(0U) +{ + ::memcpy(m_newShortLC, EMPTY_SHORT_LC, 12U); + ::memcpy(m_shortLC, EMPTY_SHORT_LC, 12U); + + m_abort[0U] = false; + m_abort[1U] = false; + + m_abortCount[0U] = 0U; + m_abortCount[1U] = 0U; +} + +/// +/// Process local buffer and transmit on the air interface. +/// +void DMRTX::process() +{ + if (m_state == DMRTXSTATE_IDLE) + return; + + if (m_poLen == 0U) { + switch (m_state) { + case DMRTXSTATE_SLOT1: + createData(0U); + m_state = DMRTXSTATE_CACH2; + break; + + case DMRTXSTATE_CACH2: + createCACH(1U, 0U); + m_state = DMRTXSTATE_SLOT2; + break; + + case DMRTXSTATE_SLOT2: + createData(1U); + m_state = DMRTXSTATE_CACH1; + break; + + case DMRTXSTATE_CAL: + break; + + default: + createCACH(0U, 1U); + m_state = DMRTXSTATE_SLOT1; + break; + } + + DEBUG2("DMRTX: process(): poLen", m_poLen); + } + + if (m_poLen > 0U) { + uint16_t space = io.getSpace(); + + while (space > 8U) { + uint8_t c = m_poBuffer[m_poPtr]; + uint8_t m = m_markBuffer[m_poPtr]; + m_poPtr++; + + writeByte(c, m); + + space -= 8U; + + if (m_poPtr >= m_poLen) { + m_poPtr = 0U; + m_poLen = 0U; + return; + } + } + } +} + +/// +/// Write slot 1 data to the local buffer. +/// +/// +/// +/// +uint8_t DMRTX::writeData1(const uint8_t* data, uint8_t length) +{ + if (length != (DMR_FRAME_LENGTH_BYTES + 1U)) + return RSN_ILLEGAL_LENGTH; + + uint16_t space = m_fifo[0U].getSpace(); + DEBUG3("DMRTX: writeData1(): dataLength/fifoLength", length, space); + if (space < DMR_FRAME_LENGTH_BYTES) + return RSN_RINGBUFF_FULL; + + if (m_abort[0U]) { + m_fifo[0U].reset(); + m_abort[0U] = false; + } + + for (uint8_t i = 0U; i < DMR_FRAME_LENGTH_BYTES; i++) + m_fifo[0U].put(data[i + 1U]); + + // Start the TX if it isn't already on + if (!m_tx) + m_state = DMRTXSTATE_SLOT1; + + return RSN_OK; +} + +/// +/// Write slot 2 data to the local buffer. +/// +/// +/// +/// +uint8_t DMRTX::writeData2(const uint8_t* data, uint8_t length) +{ + if (length != (DMR_FRAME_LENGTH_BYTES + 1U)) + return RSN_ILLEGAL_LENGTH; + + uint16_t space = m_fifo[1U].getSpace(); + DEBUG3("DMRTX: writeData2(): dataLength/fifoLength", length, space); + if (space < DMR_FRAME_LENGTH_BYTES) + return RSN_RINGBUFF_FULL; + + if (m_abort[1U]) { + m_fifo[1U].reset(); + m_abort[1U] = false; + } + + for (uint8_t i = 0U; i < DMR_FRAME_LENGTH_BYTES; i++) + m_fifo[1U].put(data[i + 1U]); + + // Start the TX if it isn't already on + if (!m_tx) + m_state = DMRTXSTATE_SLOT1; + + return RSN_OK; +} + +/// +/// Write short LC data to the local buffer. +/// +/// +/// +/// +uint8_t DMRTX::writeShortLC(const uint8_t* data, uint8_t length) +{ + if (length != 9U) + return RSN_ILLEGAL_LENGTH; + + ::memset(m_newShortLC, 0x00U, 12U); + + for (uint8_t i = 0U; i < 68U; i++) { + bool b = _READ_BIT(data, i); + uint8_t n = CACH_INTERLEAVE[i]; + _WRITE_BIT(m_newShortLC, n, b); + } + + return RSN_OK; +} + +/// +/// Write abort data to the local buffer. +/// +/// +/// +/// +uint8_t DMRTX::writeAbort(const uint8_t* data, uint8_t length) +{ + if (length != 1U) + return RSN_ILLEGAL_LENGTH; + + switch (data[0U]) { + case 1U: + m_abort[0U] = true; + m_abortCount[0U] = 0U; + return RSN_OK; + + case 2U: + m_abort[1U] = true; + m_abortCount[1U] = 0U; + return RSN_OK; + + default: + return RSN_INVALID_DMR_SLOT; + } +} + +/// +/// Helper to set the start state for Tx. +/// +/// +void DMRTX::setStart(bool start) +{ + m_state = start ? DMRTXSTATE_SLOT1 : DMRTXSTATE_IDLE; + + m_frameCount = 0U; + m_abortCount[0U] = 0U; + m_abortCount[1U] = 1U; + + m_abort[0U] = false; + m_abort[1U] = false; +} + +/// +/// Helper to get how much space the slot 1 ring buffer has for samples. +/// +/// +uint8_t DMRTX::getSpace1() const +{ + return m_fifo[0U].getSpace() / (DMR_FRAME_LENGTH_BYTES + 2U); +} + +/// +/// Helper to get how much space the slot 2 ring buffer has for samples. +/// +/// +uint8_t DMRTX::getSpace2() const +{ + return m_fifo[1U].getSpace() / (DMR_FRAME_LENGTH_BYTES + 2U); +} + +/// +/// Sets the DMR color code. +/// +/// Color code. +void DMRTX::setColorCode(uint8_t colorCode) +{ + ::memcpy(m_idle, IDLE_DATA, DMR_FRAME_LENGTH_BYTES); + + DMRSlotType slotType; + slotType.encode(colorCode, DT_IDLE, m_idle); +} + +/// +/// Sets the fine adjust 4FSK symbol levels. +/// +/// +3/-3 symbol adjust. +/// +1/-1 symbol adjust. +void DMRTX::setSymbolLvlAdj(int8_t level3Adj, int8_t level1Adj) +{ + /* ignored ADF7021 doesn't allow direct symbol level adjustments */ +} + +/// +/// Helper to reset data values to defaults for slot 1 FIFO. +/// +void DMRTX::resetFifo1() +{ + m_fifo[0U].reset(); +} + +/// +/// Helper to reset data values to defaults for slot 2 FIFO. +/// +void DMRTX::resetFifo2() +{ + m_fifo[1U].reset(); +} + +/// +/// +/// +/// +uint32_t DMRTX::getFrameCount() +{ + return m_frameCount; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// +/// +/// +void DMRTX::createData(uint8_t slotIndex) +{ + if (m_fifo[slotIndex].getData() > 0U && m_frameCount >= STARTUP_COUNT) { + for (unsigned int i = 0U; i < DMR_FRAME_LENGTH_BYTES; i++) { + m_poBuffer[i] = m_fifo[slotIndex].get(); + if (i == 8U) + m_markBuffer[i] = slotIndex == 0U ? MARK_SLOT1 : MARK_SLOT2; + else + m_markBuffer[i] = MARK_NONE; + } + } + else { + m_abort[slotIndex] = false; + // Transmit an idle message + for (unsigned int i = 0U; i < DMR_FRAME_LENGTH_BYTES; i++) { + m_poBuffer[i] = m_idle[i]; + if (i == 8U) + m_markBuffer[i] = slotIndex == 0U ? MARK_SLOT1 : MARK_SLOT2; + else + m_markBuffer[i] = MARK_NONE; + } + } + + m_poLen = DMR_FRAME_LENGTH_BYTES; + m_poPtr = 0U; +} + +/// +/// +/// +/// +/// +void DMRTX::createCACH(uint8_t txSlotIndex, uint8_t rxSlotIndex) +{ + m_frameCount++; + m_abortCount[0U]++; + m_abortCount[1U]++; + + if (m_cachPtr >= 12U) + m_cachPtr = 0U; + + if (m_cachPtr == 0U) { + if (m_fifo[0U].getData() == 0U && m_fifo[1U].getData() == 0U) + ::memcpy(m_shortLC, EMPTY_SHORT_LC, 12U); + else + ::memcpy(m_shortLC, m_newShortLC, 12U); + } + + ::memcpy(m_poBuffer, m_shortLC + m_cachPtr, 3U); + m_markBuffer[0U] = MARK_NONE; + m_markBuffer[1U] = MARK_NONE; + m_markBuffer[2U] = rxSlotIndex == 1U ? MARK_SLOT1 : MARK_SLOT2; + + bool at = false; + if (m_frameCount >= STARTUP_COUNT) + at = m_fifo[rxSlotIndex].getData() > 0U; + bool tc = txSlotIndex == 1U; + bool ls0 = true; // For 1 and 2 + bool ls1 = true; + + if (m_cachPtr == 0U) // For 0 + ls1 = false; + else if (m_cachPtr == 9U) // For 3 + ls0 = false; + + bool h0 = at ^ tc ^ ls1; + bool h1 = tc ^ ls1 ^ ls0; + bool h2 = at ^ tc ^ ls0; + + m_poBuffer[0U] |= at ? 0x80U : 0x00U; + m_poBuffer[0U] |= tc ? 0x08U : 0x00U; + m_poBuffer[1U] |= ls1 ? 0x80U : 0x00U; + m_poBuffer[1U] |= ls0 ? 0x08U : 0x00U; + m_poBuffer[1U] |= h0 ? 0x02U : 0x00U; + m_poBuffer[2U] |= h1 ? 0x20U : 0x00U; + m_poBuffer[2U] |= h2 ? 0x02U : 0x00U; + + m_poLen = DMR_CACH_LENGTH_BYTES; + m_poPtr = 0U; + + m_cachPtr += 3U; +} + +/// +/// +/// +/// +/// +void DMRTX::writeByte(uint8_t c, uint8_t control) +{ + uint8_t bit; + uint8_t mask = 0x80U; + uint8_t control_tmp = m_control_old; + + for (uint8_t i = 0U; i < 8U; i++, c <<= 1) { + if ((c & mask) == mask) + bit = 1U; + else + bit = 0U; + + if (i == 7U) { + if (control == MARK_SLOT2) + control_tmp = true; + else if (control == MARK_SLOT1) + control_tmp = false; + + m_control_old = control_tmp; + } + + io.write(&bit, 1, &control_tmp); + } +} + +#endif // DUPLEX diff --git a/dmr/DMRTX.h b/dmr/DMRTX.h new file mode 100644 index 0000000..0f52d1a --- /dev/null +++ b/dmr/DMRTX.h @@ -0,0 +1,135 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2016 by Colin Durbridge G4EML +* Copyright (C) 2017 by Andy Uribe CA6JAU +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DMR_TX_H__) +#define __DMR_TX_H__ + +#include "Defines.h" +#include "dmr/DMRDefines.h" +#include "SerialBuffer.h" + +#if defined(DUPLEX) + +namespace dmr +{ + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + enum DMRTXSTATE { + DMRTXSTATE_IDLE, + DMRTXSTATE_SLOT1, + DMRTXSTATE_CACH1, + DMRTXSTATE_SLOT2, + DMRTXSTATE_CACH2, + DMRTXSTATE_CAL + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Implements receiver logic for duplex DMR mode operation. + // --------------------------------------------------------------------------- + + class DSP_FW_API DMRTX { + public: + /// Initializes a new instance of the DMRTX class. + DMRTX(); + + /// Process local buffer and transmit on the air interface. + void process(); + + /// Write slot 1 data to the local buffer. + uint8_t writeData1(const uint8_t* data, uint8_t length); + /// Write slot 2 data to the local buffer. + uint8_t writeData2(const uint8_t* data, uint8_t length); + + /// Write short LC data to the local buffer. + uint8_t writeShortLC(const uint8_t* data, uint8_t length); + /// Write abort data to the local buffer. + uint8_t writeAbort(const uint8_t* data, uint8_t length); + + /// Helper to set the start state for Tx. + void setStart(bool start); + + /// Helper to get how much space the slot 1 ring buffer has for samples. + uint8_t getSpace1() const; + /// Helper to get how much space the slot 2 ring buffer has for samples. + uint8_t getSpace2() const; + + /// Sets the DMR color code. + void setColorCode(uint8_t colorCode); + /// Sets the fine adjust 4FSK symbol levels. + void setSymbolLvlAdj(int8_t level3Adj, int8_t level1Adj); + + /// Helper to reset data values to defaults for slot 1 FIFO. + void resetFifo1(); + /// Helper to reset data values to defaults for slot 2 FIFO. + void resetFifo2(); + /// + uint32_t getFrameCount(); + + private: + SerialBuffer m_fifo[2U]; + + DMRTXSTATE m_state; + + uint8_t m_idle[DMR_FRAME_LENGTH_BYTES]; + uint8_t m_cachPtr; + + uint8_t m_shortLC[12U]; + uint8_t m_newShortLC[12U]; + + uint8_t m_markBuffer[40U]; + + uint8_t m_poBuffer[40U]; + uint16_t m_poLen; + uint16_t m_poPtr; + + uint32_t m_frameCount; + + uint32_t m_abortCount[2U]; + bool m_abort[2U]; + + uint8_t m_control_old; + + /// + void createData(uint8_t slotIndex); + /// + void createCACH(uint8_t txSlotIndex, uint8_t rxSlotIndex); + + /// + void writeByte(uint8_t c, uint8_t control); + }; +} // namespace dmr + +#endif // DUPLEX + +#endif // __DMR_TX_H__ diff --git a/dvm-firmware-hs.vcxproj b/dvm-firmware-hs.vcxproj new file mode 100644 index 0000000..943da1d --- /dev/null +++ b/dvm-firmware-hs.vcxproj @@ -0,0 +1,197 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {3733f055-083f-4bf4-9233-25d471de58d9} + dvmfirmwarehs + 10.0 + firmware-hs + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(ProjectDir);%(AdditionalIncludeDirectories) + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dvm-firmware-hs.vcxproj.filters b/dvm-firmware-hs.vcxproj.filters new file mode 100644 index 0000000..71a1146 --- /dev/null +++ b/dvm-firmware-hs.vcxproj.filters @@ -0,0 +1,174 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {03e2c302-d472-4e33-8ab9-53296423a641} + + + {19296f3e-bc16-4bc8-9faf-158ab22b8dc0} + + + {493de3cc-83da-49f4-ab01-a199ac989d23} + + + {ce2a5db1-59c6-4fdc-8774-f1d85651cd34} + + + + + Header Files\dmr + + + Header Files\dmr + + + Header Files\dmr + + + Header Files\dmr + + + Header Files\dmr + + + Header Files\dmr + + + Header Files\dmr + + + Header Files\dmr + + + Header Files\dmr + + + Header Files\p25 + + + Header Files\p25 + + + Header Files\p25 + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files\p25 + + + + + Source Files\dmr + + + Source Files\dmr + + + Source Files\dmr + + + Source Files\dmr + + + Source Files\dmr + + + Source Files\dmr + + + Source Files\dmr + + + Source Files\dmr + + + Source Files\p25 + + + Source Files\p25 + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files\p25 + + + \ No newline at end of file diff --git a/normal.ld b/normal.ld new file mode 100644 index 0000000..290f7ab --- /dev/null +++ b/normal.ld @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2016-2018 by Andy Uribe CA6JAU + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* Memory areas */ +MEMORY +{ + ROM (rx) : ORIGIN = 0x08000000, LENGTH = 128K /* FLASH */ + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K /* Main RAM */ +} + +INCLUDE stm32f10x_link.ld \ No newline at end of file diff --git a/p25/CalP25.cpp b/p25/CalP25.cpp new file mode 100644 index 0000000..3448938 --- /dev/null +++ b/p25/CalP25.cpp @@ -0,0 +1,144 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2018 by Andy Uribe CA6JAU +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Globals.h" +#include "p25/CalP25.h" + +using namespace p25; + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +// Recommended 1011 Hz test pattern for P25 Phase 1 (ANSI/TIA-102.CAAA) +// NAC: 0x293, srcID: 1, dstID: TG1 +unsigned char LDU1_1K[] = { 0x00, + 0x55, 0x75, 0xF5, 0xFF, 0x77, 0xFF, 0x29, 0x35, 0x54, 0x7B, 0xCB, 0x19, 0x4D, 0x0D, 0xCE, 0x24, 0xA1, 0x24, + 0x0D, 0x43, 0x3C, 0x0B, 0xE1, 0xB9, 0x18, 0x44, 0xFC, 0xC1, 0x62, 0x96, 0x27, 0x60, 0xE4, 0xE2, 0x4A, 0x10, + 0x90, 0xD4, 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4C, 0xFC, 0x16, 0x29, 0x62, 0x76, 0x0E, 0xC0, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x02, 0xF8, 0x6E, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x94, + 0x89, 0xD8, 0x39, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x38, 0x24, 0xA1, 0x24, 0x35, 0x0C, 0xF0, 0x2F, 0x86, 0xE4, + 0x18, 0x44, 0xFF, 0x05, 0x8A, 0x58, 0x9D, 0x83, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x70, 0xE2, 0x4A, 0x12, 0x40, + 0xD4, 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4F, 0xF0, 0x16, 0x29, 0x62, 0x76, 0x0E, 0x6D, 0xE5, 0xD5, 0x48, + 0xAD, 0xE3, 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x08, 0xF8, 0x6E, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x96, 0x24, + 0xD8, 0x3B, 0xA1, 0x41, 0xC2, 0xD2, 0xBA, 0x38, 0x90, 0xA1, 0x24, 0x35, 0x0C, 0xF0, 0x2F, 0x86, 0xE4, 0x60, + 0x44, 0xFF, 0x05, 0x8A, 0x58, 0x9D, 0x83, 0x94, 0xC8, 0xFB, 0x02, 0x35, 0xA4, 0xE2, 0x4A, 0x12, 0x43, 0x50, + 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4F, 0xF0, 0x58, 0x29, 0x62, 0x76, 0x0E, 0xC0, 0x00, 0x00, 0x00, 0x0C, + 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x0B, 0xE1, 0xB8, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x96, 0x27, 0x60, 0xE4 }; + +unsigned char LDU2_1K[] = { 0x00, + 0x55, 0x75, 0xF5, 0xFF, 0x77, 0xFF, 0x29, 0x3A, 0xB8, 0xA4, 0xEF, 0xB0, 0x9A, 0x8A, 0xCE, 0x24, 0xA1, 0x24, + 0x0D, 0x43, 0x3C, 0x0B, 0xE1, 0xB9, 0x18, 0x44, 0xFC, 0xC1, 0x62, 0x96, 0x27, 0x60, 0xEC, 0xE2, 0x4A, 0x10, + 0x90, 0xD4, 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4C, 0xFC, 0x16, 0x29, 0x62, 0x76, 0x0E, 0x40, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x02, 0xF8, 0x6E, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x94, + 0x89, 0xD8, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x24, 0xA1, 0x24, 0x35, 0x0C, 0xF0, 0x2F, 0x86, 0xE4, + 0x18, 0x44, 0xFF, 0x05, 0x8A, 0x58, 0x9D, 0x83, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE2, 0x4A, 0x12, 0x40, + 0xD4, 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4F, 0xF0, 0x16, 0x29, 0x62, 0x76, 0x0E, 0xE0, 0xE0, 0x00, 0x00, + 0x00, 0x03, 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x08, 0xF8, 0x6E, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x96, 0x24, + 0xD8, 0x39, 0xAE, 0x8B, 0x48, 0xB6, 0x49, 0x38, 0x90, 0xA1, 0x24, 0x35, 0x0C, 0xF0, 0x2F, 0x86, 0xE4, 0x60, + 0x44, 0xFF, 0x05, 0x8A, 0x58, 0x9D, 0x83, 0xB9, 0xA8, 0xF4, 0xF1, 0xFD, 0x60, 0xE2, 0x4A, 0x12, 0x43, 0x50, + 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4F, 0xF0, 0x58, 0x29, 0x62, 0x76, 0x0E, 0x40, 0x00, 0x00, 0x00, 0x0C, + 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x0B, 0xE1, 0xB8, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x96, 0x27, 0x60, 0xEC }; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the CalP25 class. +/// +CalP25::CalP25() : + m_transmit(false), + m_state(P25CAL1K_IDLE) +{ + /* stub */ +} + +/// +/// Process local state and transmit on the air interface. +/// +void CalP25::process() +{ + if (m_modemState == STATE_P25_CAL || m_modemState == STATE_P25_LF_CAL) { + m_state = P25CAL1K_IDLE; + + if (m_transmit) { + p25TX.setCal(true); + p25TX.process(); + } + else { + p25TX.setCal(false); + } + } + else { + p25TX.process(); + + uint16_t space = p25TX.getSpace(); + if (space < 1U) + return; + + switch (m_state) { + case P25CAL1K_LDU1: + p25TX.writeData(LDU1_1K, P25_LDU_FRAME_LENGTH_BYTES + 1U); + m_state = P25CAL1K_LDU2; + break; + case P25CAL1K_LDU2: + p25TX.writeData(LDU2_1K, P25_LDU_FRAME_LENGTH_BYTES + 1U); + if (!m_transmit) + m_state = P25CAL1K_IDLE; + else + m_state = P25CAL1K_LDU1; + break; + default: + m_state = P25CAL1K_IDLE; + break; + } + } +} + +/// +/// Write P25 calibration data to the local buffer. +/// +/// +/// +/// +uint8_t CalP25::write(const uint8_t* data, uint8_t length) +{ + if (length != 1U) + return RSN_ILLEGAL_LENGTH; + + m_transmit = data[0U] == 1U; + + if (m_transmit && m_state == P25CAL1K_IDLE) + m_state = P25CAL1K_LDU1; + + if (m_transmit) + io.rf1Conf(STATE_P25, true); + + return RSN_OK; +} diff --git a/p25/CalP25.h b/p25/CalP25.h new file mode 100644 index 0000000..f8113e6 --- /dev/null +++ b/p25/CalP25.h @@ -0,0 +1,70 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2018 by Andy Uribe CA6JAU +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__CAL_P25_H__) +#define __CAL_P25_H__ + +#include "Defines.h" +#include "p25/P25Defines.h" + +namespace p25 +{ + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + enum P25CAL1K { + P25CAL1K_IDLE, + P25CAL1K_LDU1, + P25CAL1K_LDU2 + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Implements logic for P25 calibration mode. + // --------------------------------------------------------------------------- + + class DSP_FW_API CalP25 { + public: + /// Initializes a new instance of the CalP25 class. + CalP25(); + + /// Process local state and transmit on the air interface. + void process(); + + /// Write P25 calibration state. + uint8_t write(const uint8_t* data, uint8_t length); + + private: + bool m_transmit; + P25CAL1K m_state; + }; +} // namespace p25 + +#endif // __CAL_P25_H__ diff --git a/p25/P25Defines.h b/p25/P25Defines.h new file mode 100644 index 0000000..0aa319f --- /dev/null +++ b/p25/P25Defines.h @@ -0,0 +1,135 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2009-2016 by Jonathan Naylor G4KLX +* Copyright (C) 2018 by Andy Uribe CA6JAU +* Copyright (C) 2017-2018 Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +/* + * Copyright (C) 2016 by Jonathan Naylor G4KLX + * Copyright (C) 2018 by Andy Uribe CA6JAU + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#if !defined(__P25_DEFINES_H__) +#define __P25_DEFINES_H__ + +#include "Defines.h" + +namespace p25 +{ + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + const uint32_t P25_RADIO_SYMBOL_LENGTH = 5U; // At 24 kHz sample rate + + const uint32_t P25_HDU_FRAME_LENGTH_BYTES = 99U; + const uint32_t P25_HDU_FRAME_LENGTH_BITS = P25_HDU_FRAME_LENGTH_BYTES * 8U; + const uint32_t P25_HDU_FRAME_LENGTH_SYMBOLS = P25_HDU_FRAME_LENGTH_BYTES * 4U; + const uint32_t P25_HDU_FRAME_LENGTH_SAMPLES = P25_HDU_FRAME_LENGTH_SYMBOLS * P25_RADIO_SYMBOL_LENGTH; + + const uint32_t P25_TDU_FRAME_LENGTH_BYTES = 18U; + const uint32_t P25_TDU_FRAME_LENGTH_BITS = P25_TDU_FRAME_LENGTH_BYTES * 8U; + const uint32_t P25_TDU_FRAME_LENGTH_SYMBOLS = P25_TDU_FRAME_LENGTH_BYTES * 4U; + const uint32_t P25_TDU_FRAME_LENGTH_SAMPLES = P25_TDU_FRAME_LENGTH_SYMBOLS * P25_RADIO_SYMBOL_LENGTH; + + const uint32_t P25_LDU_FRAME_LENGTH_BYTES = 216U; + const uint32_t P25_LDU_FRAME_LENGTH_BITS = P25_LDU_FRAME_LENGTH_BYTES * 8U; + const uint32_t P25_LDU_FRAME_LENGTH_SYMBOLS = P25_LDU_FRAME_LENGTH_BYTES * 4U; + const uint32_t P25_LDU_FRAME_LENGTH_SAMPLES = P25_LDU_FRAME_LENGTH_SYMBOLS * P25_RADIO_SYMBOL_LENGTH; + + const uint32_t P25_TSDU_FRAME_LENGTH_BYTES = 45U; + const uint32_t P25_TSDU_FRAME_LENGTH_BITS = P25_TSDU_FRAME_LENGTH_BYTES * 8U; + const uint32_t P25_TSDU_FRAME_LENGTH_SYMBOLS = P25_TSDU_FRAME_LENGTH_BYTES * 4U; + const uint32_t P25_TSDU_FRAME_LENGTH_SAMPLES = P25_TSDU_FRAME_LENGTH_SYMBOLS * P25_RADIO_SYMBOL_LENGTH; + + const uint32_t P25_TDULC_FRAME_LENGTH_BYTES = 54U; + const uint32_t P25_TDULC_FRAME_LENGTH_BITS = P25_TDULC_FRAME_LENGTH_BYTES * 8U; + const uint32_t P25_TDULC_FRAME_LENGTH_SYMBOLS = P25_TDULC_FRAME_LENGTH_BYTES * 4U; + const uint32_t P25_TDULC_FRAME_LENGTH_SAMPLES = P25_TDULC_FRAME_LENGTH_SYMBOLS * P25_RADIO_SYMBOL_LENGTH; + + const uint32_t P25_SYNC_LENGTH_BYTES = 6U; + const uint32_t P25_SYNC_LENGTH_BITS = P25_SYNC_LENGTH_BYTES * 8U; + const uint32_t P25_SYNC_LENGTH_SYMBOLS = P25_SYNC_LENGTH_BYTES * 4U; + const uint32_t P25_SYNC_LENGTH_SAMPLES = P25_SYNC_LENGTH_SYMBOLS * P25_RADIO_SYMBOL_LENGTH; + + const uint32_t P25_NID_LENGTH_BYTES = 2U; + const uint32_t P25_NID_LENGTH_BITS = P25_NID_LENGTH_BYTES * 8U; + const uint32_t P25_NID_LENGTH_SYMBOLS = P25_NID_LENGTH_BYTES * 4U; + const uint32_t P25_NID_LENGTH_SAMPLES = P25_NID_LENGTH_SYMBOLS * P25_RADIO_SYMBOL_LENGTH; + + const uint32_t P25_PDU_HDU_FRAME_LENGTH_BYTES = 39U; + const uint32_t P25_PDU_HDU_FRAME_LENGTH_BITS = P25_PDU_HDU_FRAME_LENGTH_BYTES * 8U; + const uint32_t P25_PDU_HDU_FRAME_LENGTH_SYMBOLS = P25_PDU_HDU_FRAME_LENGTH_BYTES * 4U; + const uint32_t P25_PDU_HDU_FRAME_LENGTH_SAMPLES = P25_PDU_HDU_FRAME_LENGTH_SYMBOLS * P25_RADIO_SYMBOL_LENGTH; + + const uint8_t P25_SYNC_BYTES[] = { 0x55U, 0x75U, 0xF5U, 0xFFU, 0x77U, 0xFFU }; + const uint8_t P25_SYNC_BYTES_LENGTH = 6U; + const uint8_t P25_START_SYNC = 0x5FU; + const uint8_t P25_NULL = 0x00U; + + const uint8_t P25_TEST_PATTERN[] = { 0x55, 0xFF }; + const uint8_t P25_TEST_PATTERN_LENGTH = 2U; + + const ulong64_t P25_SYNC_BITS = 0x00005575F5FF77FFU; + const ulong64_t P25_SYNC_BITS_MASK = 0x0000FFFFFFFFFFFFU; + + // 5 5 7 5 F 5 F F 7 7 F F + // 01 01 01 01 01 11 01 01 11 11 01 01 11 11 11 11 01 11 01 11 11 11 11 11 + // +3 +3 +3 +3 +3 -3 +3 +3 -3 -3 +3 +3 -3 -3 -3 -3 +3 -3 +3 -3 -3 -3 -3 -3 + + const int8_t P25_SYNC_SYMBOLS_VALUES[] = { +3, +3, +3, +3, +3, -3, +3, +3, -3, -3, +3, +3, -3, -3, -3, -3, +3, -3, +3, -3, -3, -3, -3, -3 }; + + const uint32_t P25_SYNC_SYMBOLS = 0x00FB30A0U; + const uint32_t P25_SYNC_SYMBOLS_MASK = 0x00FFFFFFU; + + const uint32_t P25_TX_BUFFER_LEN = 2000U; + + // Data Unit ID(s) + const uint8_t P25_DUID_HDU = 0x00U; // Header Data Unit + const uint8_t P25_DUID_TDU = 0x03U; // Simple Terminator Data Unit + const uint8_t P25_DUID_LDU1 = 0x05U; // Logical Link Data Unit 1 + const uint8_t P25_DUID_TSDU = 0x07U; // Trunking System Data Unit + const uint8_t P25_DUID_LDU2 = 0x0AU; // Logical Link Data Unit 2 + const uint8_t P25_DUID_PDU = 0x0CU; // Packet Data Unit + const uint8_t P25_DUID_TDULC = 0x0FU; // Terminator Data Unit with Link Control +} // namespace p25 + +#endif // __P25_DEFINES_H__ diff --git a/p25/P25RX.cpp b/p25/P25RX.cpp new file mode 100644 index 0000000..15d97b1 --- /dev/null +++ b/p25/P25RX.cpp @@ -0,0 +1,572 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2016,2017,2018 by Andy Uribe CA6JAU +* Copyright (C) 2021 Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Globals.h" +#include "p25/P25RX.h" +#include "Utils.h" + +using namespace p25; + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint8_t MAX_SYNC_BYTES_START_ERRS = 2U; +const uint8_t MAX_SYNC_BYTES_ERRS = 4U; + +const uint16_t MAX_SYNC_FRAMES = 7U; + +const uint8_t CORRELATION_COUNTDOWN = 6U; + +const uint16_t NOENDPTR = 9999U; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the P25RX class. +/// +P25RX::P25RX() : + m_bitBuffer(0x00U), + m_buffer(), + m_dataPtr(0U), + m_minSyncPtr(0U), + m_maxSyncPtr(NOENDPTR), + m_startPtr(0U), + m_endPtr(NOENDPTR), + m_syncPtr(0U), + m_lostCount(0U), + m_countdown(0U), + m_nac(0xF7EU), + m_corrCountdown(CORRELATION_COUNTDOWN), + m_state(P25RXS_NONE), + m_duid(0xFFU) +{ + /* stub */ +} + +/// +/// Helper to reset data values to defaults. +/// +void P25RX::reset() +{ + m_bitBuffer = 0x00U; + + m_dataPtr = 0U; + + m_minSyncPtr = 0U; + m_maxSyncPtr = NOENDPTR; + + m_startPtr = 0U; + m_endPtr = NOENDPTR; + m_syncPtr = 0U; + + m_lostCount = 0U; + m_countdown = 0U; + + m_state = P25RXS_NONE; + + m_duid = 0xFFU; +} + +/// +/// Sample P25 bits from the air interface. +/// +/// +void P25RX::databit(bool bit) +{ + _WRITE_BIT(m_buffer, m_dataPtr, bit); + + m_bitBuffer <<= 1; + if (bit) + m_bitBuffer |= 0x01U; + + if (m_state == P25RXS_SYNC) { + processBit(bit); + } + else if (m_state == P25RXS_VOICE) { + processVoice(bit); + } + else if (m_state == P25RXS_DATA) { + processData(bit); + } + else { + bool ret = correlateSync(); + if (ret) { + // on the first sync, start the countdown to the state change + if (m_countdown == 0U) { + io.setDecode(true); + + m_countdown = m_corrCountdown; + DEBUG2("P25RX: databit(): correlation countdown", m_countdown); + } + } + + if (m_countdown > 0U) + m_countdown--; + + if (m_countdown == 1U) { + m_minSyncPtr = m_syncPtr + P25_HDU_FRAME_LENGTH_BITS - 1U; + if (m_minSyncPtr >= P25_LDU_FRAME_LENGTH_BITS) + m_minSyncPtr -= P25_LDU_FRAME_LENGTH_BITS; + + m_maxSyncPtr = m_syncPtr + P25_HDU_FRAME_LENGTH_BITS + 1U; + if (m_maxSyncPtr >= P25_LDU_FRAME_LENGTH_BITS) + m_maxSyncPtr -= P25_LDU_FRAME_LENGTH_BITS; + + m_state = P25RXS_SYNC; + m_countdown = 0U; + } + } + + m_dataPtr++; + if (m_dataPtr >= P25_LDU_FRAME_LENGTH_BITS) { + m_duid = 0xFFU; + m_dataPtr = 0U; + } +} + +/// +/// Sets the P25 NAC. +/// +/// NAC. +void P25RX::setNAC(uint16_t nac) +{ + m_nac = nac; +} + +/// +/// Sets the P25 sync correlation countdown. +/// +/// Correlation Countdown Count. +void P25RX::setCorrCount(uint8_t count) +{ + m_corrCountdown = count; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// Helper to process P25 samples. +/// +/// +void P25RX::processBit(bool bit) +{ + if (m_minSyncPtr < m_maxSyncPtr) { + if (m_dataPtr >= m_minSyncPtr && m_dataPtr <= m_maxSyncPtr) + correlateSync(); + } + else { + if (m_dataPtr >= m_minSyncPtr || m_dataPtr <= m_maxSyncPtr) + correlateSync(); + } + + // initial sample processing does not have an end pointer -- we simply wait till we've read + // the bits up to the maximum sync pointer + if (m_dataPtr == m_maxSyncPtr) { + DEBUG4("P25RX: processBit(): dataPtr/startPtr/endPtr", m_dataPtr, m_startPtr, m_maxSyncPtr); + DEBUG4("P25RX: processBit(): lostCount/maxSyncPtr/minSyncPtr", m_lostCount, m_maxSyncPtr, m_minSyncPtr); + + if (!decodeNid(m_startPtr)) { + io.setDecode(false); + + serial.writeP25Lost(); + reset(); + } + else { + switch (m_duid) { + case P25_DUID_HDU: + { + DEBUG1("P25RX: processBit(): sync found in HDU pos", m_syncPtr); + + uint8_t frame[P25_HDU_FRAME_LENGTH_BYTES + 1U]; + bitsToBytes(m_startPtr + P25_NID_LENGTH_BITS, P25_HDU_FRAME_LENGTH_BITS, frame); + + frame[0U] = 0x01U; + serial.writeP25Data(frame, P25_HDU_FRAME_LENGTH_BYTES + 1U); + reset(); + } + break; + case P25_DUID_TDU: + { + DEBUG1("P25RX: processBit(): sync found in TDU pos", m_syncPtr); + + uint8_t frame[P25_TDU_FRAME_LENGTH_BYTES + 1U]; + bitsToBytes(m_startPtr + P25_NID_LENGTH_BITS, P25_TDU_FRAME_LENGTH_BITS, frame); + + frame[0U] = 0x01U; + serial.writeP25Data(frame, P25_TDU_FRAME_LENGTH_BYTES + 1U); + reset(); + } + break; + case P25_DUID_LDU1: + m_state = P25RXS_VOICE; + return; + case P25_DUID_TSDU: + { + DEBUG1("P25RX: processBit(): sync found in TSDU pos", m_syncPtr); + + uint8_t frame[P25_TSDU_FRAME_LENGTH_BYTES + 1U]; + bitsToBytes(m_startPtr + P25_NID_LENGTH_BITS, P25_TSDU_FRAME_LENGTH_BITS, frame); + + frame[0U] = 0x01U; + serial.writeP25Data(frame, P25_TSDU_FRAME_LENGTH_BYTES + 1U); + reset(); + } + break; + case P25_DUID_LDU2: + m_state = P25RXS_VOICE; + return; + case P25_DUID_PDU: + m_state = P25RXS_DATA; + return; + case P25_DUID_TDULC: + { + DEBUG1("P25RX: processBit(): sync found in TDULC pos", m_syncPtr); + + uint8_t frame[P25_TDULC_FRAME_LENGTH_BYTES + 1U]; + bitsToBytes(m_startPtr + P25_NID_LENGTH_BITS, P25_TDULC_FRAME_LENGTH_BITS, frame); + + frame[0U] = 0x01U; + serial.writeP25Data(frame, P25_TDULC_FRAME_LENGTH_BYTES + 1U); + reset(); + } + break; + default: + { + DEBUG3("P25RX: processBit(): illegal DUID in NID", m_nac, m_duid); + reset(); + } + return; + } + } + } + + m_minSyncPtr = m_syncPtr + P25_LDU_FRAME_LENGTH_BITS - 1U; + if (m_minSyncPtr >= P25_LDU_FRAME_LENGTH_BITS) + m_minSyncPtr -= P25_LDU_FRAME_LENGTH_BITS; + + m_maxSyncPtr = m_syncPtr + 1U; + if (m_maxSyncPtr >= P25_LDU_FRAME_LENGTH_BITS) + m_maxSyncPtr -= P25_LDU_FRAME_LENGTH_BITS; + + m_lostCount = MAX_SYNC_FRAMES; + + if (m_state == P25RXS_VOICE) { + processVoice(bit); + } + + if (m_state == P25RXS_DATA) { + processData(bit); + } +} + +/// +/// Helper to process LDU P25 bits. +/// +/// +void P25RX::processVoice(bool bit) +{ + if (m_minSyncPtr < m_maxSyncPtr) { + if (m_dataPtr >= m_minSyncPtr && m_dataPtr <= m_maxSyncPtr) + correlateSync(); + } + else { + if (m_dataPtr >= m_minSyncPtr || m_dataPtr <= m_maxSyncPtr) + correlateSync(); + } + + if (m_dataPtr == m_endPtr) { + if (m_lostCount == MAX_SYNC_FRAMES) { + m_minSyncPtr = m_syncPtr + P25_LDU_FRAME_LENGTH_BITS - 1U; + if (m_minSyncPtr >= P25_LDU_FRAME_LENGTH_BITS) + m_minSyncPtr -= P25_LDU_FRAME_LENGTH_BITS; + + m_maxSyncPtr = m_syncPtr + 1U; + if (m_maxSyncPtr >= P25_LDU_FRAME_LENGTH_BITS) + m_maxSyncPtr -= P25_LDU_FRAME_LENGTH_BITS; + } + + m_lostCount--; + + DEBUG4("P25RX: processVoice(): dataPtr/startPtr/endPtr", m_dataPtr, m_startPtr, m_endPtr); + DEBUG4("P25RX: processVoice(): lostCount/maxSyncPtr/minSyncPtr", m_lostCount, m_maxSyncPtr, m_minSyncPtr); + + // we've not seen a data sync for too long, signal sync lost and change to P25RXS_NONE + if (m_lostCount == 0U) { + DEBUG1("P25RX: processVoice(): sync timeout in LDU, lost lock"); + + io.setDecode(false); + + serial.writeP25Lost(); + reset(); + } + else { + if (!decodeNid(m_startPtr)) { + io.setDecode(false); + + serial.writeP25Lost(); + reset(); + } + else { + if (m_duid == P25_DUID_TDU) { + DEBUG1("P25RX: processBit(): sync found in TDU pos", m_syncPtr); + + uint8_t frame[P25_TDU_FRAME_LENGTH_BYTES + 1U]; + bitsToBytes(m_startPtr + P25_NID_LENGTH_BITS, P25_TDU_FRAME_LENGTH_BITS, frame); + + frame[0U] = 0x01U; + serial.writeP25Data(frame, P25_TDU_FRAME_LENGTH_BYTES + 1U); + + io.setDecode(false); + + reset(); + return; + } + + DEBUG1("P25RX: processVoice(): sync found in LDU pos", m_syncPtr); + + uint8_t frame[P25_LDU_FRAME_LENGTH_BYTES + 3U]; + bitsToBytes(m_startPtr + P25_NID_LENGTH_BITS, P25_LDU_FRAME_LENGTH_BITS, frame); + + frame[0U] = m_lostCount == (MAX_SYNC_FRAMES - 1U) ? 0x01U : 0x00U; +#if defined(SEND_RSSI_DATA) + if (m_rssiCount > 0U) { + uint16_t rssi = m_rssiAccum / m_rssiCount; + frame[217U] = (rssi >> 8) & 0xFFU; + frame[218U] = (rssi >> 0) & 0xFFU; + + serial.writeP25Data(false, frame, P25_LDU_FRAME_LENGTH_BYTES + 3U); + } + else { + serial.writeP25Data(false, frame, P25_LDU_FRAME_LENGTH_BYTES + 1U); + } +#else + serial.writeP25Data(frame, P25_LDU_FRAME_LENGTH_BYTES + 1U); +#endif + } + } + } +} + +/// +/// Helper to process PDU P25 bits. +/// +/// +void P25RX::processData(bool bit) +{ + if (m_minSyncPtr < m_maxSyncPtr) { + if (m_dataPtr >= m_minSyncPtr && m_dataPtr <= m_maxSyncPtr) + correlateSync(); + } + else { + if (m_dataPtr >= m_minSyncPtr || m_dataPtr <= m_maxSyncPtr) + correlateSync(); + } + + if (m_dataPtr == m_endPtr) { + if (m_lostCount == MAX_SYNC_FRAMES) { + m_minSyncPtr = m_syncPtr + P25_LDU_FRAME_LENGTH_BITS - 1U; + if (m_minSyncPtr >= P25_LDU_FRAME_LENGTH_BITS) + m_minSyncPtr -= P25_LDU_FRAME_LENGTH_BITS; + + m_maxSyncPtr = m_syncPtr + 1U; + if (m_maxSyncPtr >= P25_LDU_FRAME_LENGTH_BITS) + m_maxSyncPtr -= P25_LDU_FRAME_LENGTH_BITS; + } + + m_lostCount--; + + DEBUG4("P25RX: processData(): dataPtr/startPtr/endPtr", m_dataPtr, m_startPtr, m_endPtr); + DEBUG4("P25RX: processData(): lostCount/maxSyncPtr/minSyncPtr", m_lostCount, m_maxSyncPtr, m_minSyncPtr); + + // we've not seen a data sync for too long, signal sync lost and change to P25RXS_NONE + if (m_lostCount == 0U) { + DEBUG1("P25RX: processData(): sync timeout in PDU, lost lock"); + + io.setDecode(false); + + serial.writeP25Lost(); + reset(); + } + else { + if (!decodeNid(m_startPtr)) { + io.setDecode(false); + + serial.writeP25Lost(); + reset(); + } + else { + DEBUG1("P25RX: processVoice(): sync found in PDU pos", m_syncPtr); + + uint8_t frame[P25_LDU_FRAME_LENGTH_BYTES + 3U]; + bitsToBytes(m_startPtr + P25_NID_LENGTH_BITS, P25_LDU_FRAME_LENGTH_BITS, frame); + + frame[0U] = m_lostCount == (MAX_SYNC_FRAMES - 1U) ? 0x01U : 0x00U; +#if defined(SEND_RSSI_DATA) + if (m_rssiCount > 0U) { + uint16_t rssi = m_rssiAccum / m_rssiCount; + frame[217U] = (rssi >> 8) & 0xFFU; + frame[218U] = (rssi >> 0) & 0xFFU; + + serial.writeP25Data(false, frame, P25_LDU_FRAME_LENGTH_BYTES + 3U); + } + else { + serial.writeP25Data(false, frame, P25_LDU_FRAME_LENGTH_BYTES + 1U); + } +#else + serial.writeP25Data(frame, P25_LDU_FRAME_LENGTH_BYTES + 1U); +#endif + } + } + } +} + +/// +/// Frame synchronization correlator. +/// +/// +bool P25RX::correlateSync() +{ + uint8_t maxErrs; + if (m_state == P25RXS_NONE) + maxErrs = MAX_SYNC_BYTES_START_ERRS; + else + maxErrs = MAX_SYNC_BYTES_ERRS; + + uint8_t errs = countBits64((m_bitBuffer & P25_SYNC_BITS_MASK) ^ P25_SYNC_BITS); + if (errs <= maxErrs) { + DEBUG2("P25RX: correlateSync(): correlateSync errs", errs); + + m_syncPtr = m_dataPtr; + + m_startPtr = m_dataPtr + P25_LDU_FRAME_LENGTH_BITS - P25_SYNC_LENGTH_BITS + 1; + if (m_startPtr >= P25_LDU_FRAME_LENGTH_BITS) + m_startPtr -= P25_LDU_FRAME_LENGTH_BITS; + + m_endPtr = m_dataPtr + P25_LDU_FRAME_LENGTH_BITS - P25_SYNC_LENGTH_BITS - 1U; + if (m_endPtr >= P25_LDU_FRAME_LENGTH_BITS) + m_endPtr -= P25_LDU_FRAME_LENGTH_BITS; + + m_lostCount = MAX_SYNC_FRAMES; + + DEBUG4("P25RX: correlateSync(): dataPtr/startPtr/endPtr", m_dataPtr, m_startPtr, m_endPtr); + + return true; + } + + return false; +} + +/// +/// Helper to decode the P25 NID. +/// +/// +bool P25RX::decodeNid(uint16_t start) +{ + uint16_t nidStartPtr = start + P25_SYNC_LENGTH_BITS; + if (nidStartPtr >= P25_LDU_FRAME_LENGTH_BITS) + nidStartPtr -= P25_LDU_FRAME_LENGTH_BITS; + + uint8_t nid[P25_NID_LENGTH_BYTES]; + bitsToBytes(m_startPtr, P25_NID_LENGTH_BITS, nid); + + if (m_nac == 0xF7EU) { + m_duid = nid[1U] & 0x0FU; + DEBUG2("P25RX: decodeNid(): DUID for xDU", m_duid); + return true; + } + + uint16_t nac = (nid[0U] << 4) | ((nid[1U] & 0xF0U) >> 4); + if (nac == m_nac) { + m_duid = nid[1U] & 0x0FU; + DEBUG2("P25RX: decodeNid(): DUID for xDU", m_duid); + return true; + } + else { + DEBUG3("P25RX: decodeNid(): invalid NAC found; nac != m_nac", nac, m_nac); + } + + return false; +} + +/// +/// +/// +/// +/// +/// +void P25RX::bitsToBytes(uint16_t start, uint8_t count, uint8_t* buffer) +{ + for (uint8_t i = 0U; i < count; i++) { + buffer[i] = 0U; + buffer[i] |= _READ_BIT(m_buffer, start) << 7; + start++; + if (start >= P25_LDU_FRAME_LENGTH_BITS) + start -= P25_LDU_FRAME_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 6; + start++; + if (start >= P25_LDU_FRAME_LENGTH_BITS) + start -= P25_LDU_FRAME_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 5; + start++; + if (start >= P25_LDU_FRAME_LENGTH_BITS) + start -= P25_LDU_FRAME_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 4; + start++; + if (start >= P25_LDU_FRAME_LENGTH_BITS) + start -= P25_LDU_FRAME_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 3; + start++; + if (start >= P25_LDU_FRAME_LENGTH_BITS) + start -= P25_LDU_FRAME_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 2; + start++; + if (start >= P25_LDU_FRAME_LENGTH_BITS) + start -= P25_LDU_FRAME_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 1; + start++; + if (start >= P25_LDU_FRAME_LENGTH_BITS) + start -= P25_LDU_FRAME_LENGTH_BITS; + + buffer[i] |= _READ_BIT(m_buffer, start) << 0; + start++; + if (start >= P25_LDU_FRAME_LENGTH_BITS) + start -= P25_LDU_FRAME_LENGTH_BITS; + } +} diff --git a/p25/P25RX.h b/p25/P25RX.h new file mode 100644 index 0000000..3552d15 --- /dev/null +++ b/p25/P25RX.h @@ -0,0 +1,114 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2016,2017,2018 by Andy Uribe CA6JAU +* Copyright (C) 2021 Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__P25_RX_H__) +#define __P25_RX_H__ + +#include "Defines.h" +#include "p25/P25Defines.h" + +namespace p25 +{ + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + enum P25RX_STATE { + P25RXS_NONE, + P25RXS_SYNC, + P25RXS_VOICE, + P25RXS_DATA + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Implements receiver logic for P25 mode operation. + // --------------------------------------------------------------------------- + + class DSP_FW_API P25RX { + public: + /// Initializes a new instance of the P25RX class. + P25RX(); + + /// Helper to reset data values to defaults. + void reset(); + + /// Sample P25 bits from the air interface. + void databit(bool bit); + + /// Sets the P25 NAC. + void setNAC(uint16_t nac); + /// Sets the P25 sync correlation countdown. + void setCorrCount(uint8_t count); + + private: + uint64_t m_bitBuffer; + uint8_t m_buffer[P25_LDU_FRAME_LENGTH_BITS / 8U + 3U]; + + uint16_t m_dataPtr; + + uint16_t m_minSyncPtr; + uint16_t m_maxSyncPtr; + + uint16_t m_startPtr; + uint16_t m_endPtr; + uint16_t m_syncPtr; + + uint16_t m_lostCount; + uint8_t m_countdown; + + uint16_t m_nac; + + uint8_t m_corrCountdown; + + P25RX_STATE m_state; + + uint8_t m_duid; + + /// Helper to process P25 bits. + void processBit(bool bit); + /// Helper to process LDU P25 bits. + void processVoice(bool bit); + /// Helper to process PDU P25 bits. + void processData(bool bit); + + /// Frame synchronization correlator. + bool correlateSync(); + + /// Helper to decode the P25 NID. + bool decodeNid(uint16_t start); + + /// + void bitsToBytes(uint16_t start, uint8_t count, uint8_t* buffer); + }; +} // namespace p25 + +#endif // __P25_RX_H__ diff --git a/p25/P25TX.cpp b/p25/P25TX.cpp new file mode 100644 index 0000000..f0eca9a --- /dev/null +++ b/p25/P25TX.cpp @@ -0,0 +1,279 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* Copyright (C) 2016,2017 by Andy Uribe CA6JAU +* Copyright (C) 2021 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Globals.h" +#include "p25/P25TX.h" +#include "p25/P25Defines.h" + +using namespace p25; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the P25TX class. +/// +P25TX::P25TX() : + m_fifo(P25_TX_BUFFER_LEN), + m_state(P25TXSTATE_NORMAL), + m_poBuffer(), + m_poLen(0U), + m_poPtr(0U), + m_preambleCnt(P25_FIXED_DELAY), + m_tailCnt(0U) +{ + /* stub */ +} + +/// +/// Process local buffer and transmit on the air interface. +/// +void P25TX::process() +{ + if (m_fifo.getData() == 0U && m_poLen == 0U && m_tailCnt > 0U && + m_state != P25TXSTATE_CAL) { + // transmit silence until the hang timer has expired + uint16_t space = io.getSpace(); + + while (space > 8U) { + writeByte(P25_START_SYNC); + + space -= 8U; + m_tailCnt--; + + if (m_tailCnt == 0U) + return; + if (m_fifo.getData() > 0U) { + m_tailCnt = 0U; + return; + } + } + + if (m_fifo.getData() == 0U && m_poLen == 0U) + return; + } + + if (m_poLen == 0U) { + if (m_state == P25TXSTATE_CAL) { + m_tailCnt = 0U; + createCal(); + } + else { + if (m_fifo.getData() == 0U) + return; + + createData(); + } + + DEBUG2("P25TX: process(): poLen", m_poLen); + } + + if (m_poLen > 0U) { + uint16_t space = io.getSpace(); + + while (space > 8U) { + uint8_t c = m_poBuffer[m_poPtr++]; + + writeByte(c); + + space -= 8U; + m_tailCnt = P25_FIXED_TAIL; + + if (m_poPtr >= m_poLen) { + m_poPtr = 0U; + m_poLen = 0U; + return; + } + } + } +} + +/// +/// Write data to the local buffer. +/// +/// +/// +/// +uint8_t P25TX::writeData(const uint8_t* data, uint8_t length) +{ + if (length < (P25_TDU_FRAME_LENGTH_BYTES + 1U)) + return RSN_ILLEGAL_LENGTH; + + uint16_t space = m_fifo.getSpace(); + DEBUG3("P25TX: writeData(): dataLength/fifoLength", length, space); + if (space < length) { + m_fifo.reset(); + return RSN_RINGBUFF_FULL; + } + + m_fifo.put(length - 1U); + for (uint8_t i = 0U; i < (length - 1U); i++) + m_fifo.put(data[i + 1U]); + + return RSN_OK; +} + +/// +/// Clears the local buffer. +/// +void P25TX::clear() +{ + m_fifo.reset(); +} + +/// +/// Sets the FDMA preamble count. +/// +/// Count of preambles. +void P25TX::setPreambleCount(uint8_t preambleCnt) +{ + uint32_t preambles = (uint32_t)((float)preambleCnt / 0.2083F); + m_preambleCnt = P25_FIXED_DELAY + preambles; + + // clamp preamble count to 250ms maximum + if (m_preambleCnt > 1200U) + m_preambleCnt = 1200U; +} + +/// +/// Sets the fine adjust 4FSK symbol levels. +/// +/// +3/-3 symbol adjust. +/// +1/-1 symbol adjust. +void P25TX::setSymbolLvlAdj(int8_t level3Adj, int8_t level1Adj) +{ + /* ignored ADF7021 doesn't allow direct symbol level adjustments */ +} + +/// +/// Helper to set the calibration state for Tx. +/// +/// +void P25TX::setCal(bool start) +{ + m_state = start ? P25TXSTATE_CAL : P25TXSTATE_NORMAL; +} + +/// +/// Helper to get how much space the ring buffer has for samples. +/// +/// +uint8_t P25TX::getSpace() const +{ + return m_fifo.getSpace() / P25_LDU_FRAME_LENGTH_BYTES; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// +/// +void P25TX::createData() +{ + if (!m_tx) { + for (uint16_t i = 0U; i < m_preambleCnt; i++) + m_poBuffer[m_poLen++] = P25_START_SYNC; + } + else { + uint8_t length = m_fifo.get(); + DEBUG3("P25TX: createData(): dataLength/fifoSpace", length, m_fifo.getSpace()); + for (uint8_t i = 0U; i < length; i++) { + m_poBuffer[m_poLen++] = m_fifo.get(); + } + } + + m_poPtr = 0U; +} + +/// +/// +/// +void P25TX::createCal() +{ + // 1.2 kHz sine wave generation + if (m_modemState == STATE_P25_CAL) { + for (unsigned int i = 0U; i < P25_LDU_FRAME_LENGTH_BYTES; i++) { + m_poBuffer[i] = P25_START_SYNC; + } + + m_poLen = P25_LDU_FRAME_LENGTH_BYTES; + } + + // 80 Hz square wave generation + if (m_modemState == STATE_P25_LF_CAL) { + for (unsigned int i = 0U; i < 108U; i++) { + m_poBuffer[i] = 0x55U; // +3, +3, ... pattern + } + + m_poBuffer[109U] = 0x5FU; // +3, +3, -3, -3 pattern + + for (unsigned int i = 110U; i < 216U; i++) { + m_poBuffer[i] = 0xFFU; // -3, -3, ... pattern + } + + m_poLen = P25_LDU_FRAME_LENGTH_BYTES; + } + + m_poLen = P25_LDU_FRAME_LENGTH_BYTES; + m_poPtr = 0U; +} + +/// +/// +/// +/// +void P25TX::writeByte(uint8_t c) +{ + uint8_t bit; + uint8_t mask = 0x80U; + + for (uint8_t i = 0U; i < 8U; i++, c <<= 1) { + if ((c & mask) == mask) + bit = 1U; + else + bit = 0U; + + io.write(&bit, 1); + } +} + +/// +/// +/// +void P25TX::writeSilence() +{ + uint8_t bit; + for (uint8_t i = 0U; i < 4U; i++) { + bit = 0U; + io.write(&bit, 1); + } +} diff --git a/p25/P25TX.h b/p25/P25TX.h new file mode 100644 index 0000000..f1e0899 --- /dev/null +++ b/p25/P25TX.h @@ -0,0 +1,106 @@ +/** +* Digital Voice Modem - DSP Firmware (Hotspot) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware (Hotspot) +* +*/ +// +// Based on code from the MMDVM_HS project. (https://github.com/juribeparada/MMDVM_HS) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2016,2017 by Andy Uribe CA6JAU +* Copyright (C) 2021 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__P25_TX_H__) +#define __P25_TX_H__ + +#include "Defines.h" +#include "SerialBuffer.h" + +namespace p25 +{ + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + #define P25_FIXED_DELAY 300 // 300 = 62.49ms + // Delay Value * 0.2083 = Preamble Length (ms) + #define P25_FIXED_TAIL 600 // 600 = 500ms + + enum P25TXSTATE { + P25TXSTATE_NORMAL, + P25TXSTATE_CAL + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Implements transmitter logic for P25 mode operation. + // --------------------------------------------------------------------------- + + class DSP_FW_API P25TX { + public: + /// Initializes a new instance of the P25TX class. + P25TX(); + + /// Process local buffer and transmit on the air interface. + void process(); + + /// Write data to the local buffer. + uint8_t writeData(const uint8_t* data, uint8_t length); + + /// Clears the local buffer. + void clear(); + + /// Sets the FDMA preamble count. + void setPreambleCount(uint8_t preambleCnt); + /// Sets the fine adjust 4FSK symbol levels. + void setSymbolLvlAdj(int8_t level3Adj, int8_t level1Adj); + /// Helper to set the calibration state for Tx. + void setCal(bool start); + + /// Helper to get how much space the ring buffer has for samples. + uint8_t getSpace() const; + + private: + SerialBuffer m_fifo; + + P25TXSTATE m_state; + + uint8_t m_poBuffer[1200U]; + uint16_t m_poLen; + uint16_t m_poPtr; + + uint16_t m_preambleCnt; + uint16_t m_tailCnt; + + /// + void createData(); + /// + void createCal(); + + /// + void writeByte(uint8_t c); + /// + void writeSilence(); + }; +} // namespace p25 + +#endif // __P25_TX_H__ diff --git a/stm32f10x_link.ld b/stm32f10x_link.ld new file mode 100644 index 0000000..28047f0 --- /dev/null +++ b/stm32f10x_link.ld @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2016 by Andy Uribe CA6JAU + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* Required amount of heap and stack */ +_min_heap_size = 0x1000; +_min_stack_size = 0x0800; + +/* The entry point in the interrupt vector table */ +ENTRY(Reset_Handler) + +/* Stack start address (end of 20K RAM) */ +_estack = ORIGIN(RAM) + LENGTH(RAM); + +SECTIONS +{ + .text : + { + /* The interrupt vector table */ + . = ALIGN(4); + KEEP(*(.isr_vector .isr_vector.*)) + + /* The program code */ + . = ALIGN(4); + *(.text .text*) + *(.rodata .rodata*) + + /* ARM-Thumb code */ + *(.glue_7) *(.glue_7t) + + . = ALIGN(4); + KEEP(*(.init)) + KEEP(*(.fini)) + + /* EABI C++ global constructors support */ + . = ALIGN(4); + __preinit_array_start = .; + KEEP (*(.preinit_array)) + __preinit_array_end = .; + + /* EABI C++ global constructors support */ + . = ALIGN(4); + __init_array_start = .; + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array)) + __init_array_end = .; + + /* EABI C++ global constructors support */ + . = ALIGN(4); + __fini_array_start = .; + KEEP (*(.fini_array)) + KEEP (*(SORT(.fini_array.*))) + __fini_array_end = .; + + } > ROM + + /* ARM sections containing exception unwinding information */ + .ARM.extab : { + __extab_start = .; + *(.ARM.extab* .gnu.linkonce.armextab.*) + __extab_end = .; + } > ROM + + /* ARM index entries for section unwinding */ + .ARM.exidx : { + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; + } > ROM + + /* Start address for the initialization values of the .data section */ + _sidata = .; + + /* The .data section (initialized data) */ + .data : AT ( _sidata ) + { + . = ALIGN(4); + _sdata = . ; /* Start address for the .data section */ + *(.data .data*) + + . = ALIGN(4); + _edata = . ; /* End address for the .data section */ + } > RAM + + /* The .bss section (uninitialized data) */ + .bss : + { + . = ALIGN(4); + _sbss = .; /* Start address for the .bss section */ + __bss_start__ = _sbss; + *(.bss) + *(.bss*) + *(COMMON) + + . = ALIGN(4); + _ebss = . ; /* End address for the .bss section */ + __bss_end__ = _ebss; + } > RAM + + /* Space for heap and stack */ + .heap_stack : + { + end = . ; /* 'end' symbol defines heap location */ + _end = end ; + . = . + _min_heap_size; /* Additional space for heap and stack */ + . = . + _min_stack_size; + } > RAM + + /* Remove information from the standard libraries */ + /DISCARD/ : + { + libc.a ( * ) + libm.a ( * ) + libgcc.a ( * ) + } +} diff --git a/stm32f4xx_link.ld b/stm32f4xx_link.ld new file mode 100644 index 0000000..24ee380 --- /dev/null +++ b/stm32f4xx_link.ld @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2016 by Andy Uribe CA6JAU + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* Required amount of heap and stack */ +_min_heap_size = 0x1000; +_min_stack_size = 0x0800; + +/* The entry point in the interrupt vector table */ +ENTRY(Reset_Handler) + +/* Memory areas */ +MEMORY +{ + ROM (rx) : ORIGIN = 0x08000000, LENGTH = 1024K /* FLASH */ + CCMRAM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K /* Core Coupled Memory (CPU only access) */ + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K /* Main RAM (bus matrix)*/ +} + +/* Stack start address (end of 128K RAM) */ +_estack = ORIGIN(RAM) + LENGTH(RAM); + +SECTIONS +{ + .text : + { + /* The interrupt vector table */ + . = ALIGN(4); + KEEP(*(.isr_vector .isr_vector.*)) + + /* The program code */ + . = ALIGN(4); + *(.text .text*) + *(.rodata .rodata*) + + /* ARM-Thumb code */ + *(.glue_7) *(.glue_7t) + + . = ALIGN(4); + KEEP(*(.init)) + KEEP(*(.fini)) + + /* EABI C++ global constructors support */ + . = ALIGN(4); + __preinit_array_start = .; + KEEP (*(.preinit_array)) + __preinit_array_end = .; + + /* EABI C++ global constructors support */ + . = ALIGN(4); + __init_array_start = .; + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array)) + __init_array_end = .; + + /* EABI C++ global constructors support */ + . = ALIGN(4); + __fini_array_start = .; + KEEP (*(.fini_array)) + KEEP (*(SORT(.fini_array.*))) + __fini_array_end = .; + + } > ROM + + /* ARM sections containing exception unwinding information */ + .ARM.extab : { + __extab_start = .; + *(.ARM.extab* .gnu.linkonce.armextab.*) + __extab_end = .; + } > ROM + + /* ARM index entries for section unwinding */ + .ARM.exidx : { + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; + } > ROM + + /* Start address for the initialization values of the .data section */ + _sidata = .; + + /* The .data section (initialized data) */ + .data : AT ( _sidata ) + { + . = ALIGN(4); + _sdata = . ; /* Start address for the .data section */ + *(.data .data*) + + . = ALIGN(4); + _edata = . ; /* End address for the .data section */ + } > RAM + + /* The .bss section (uninitialized data) */ + .bss : + { + . = ALIGN(4); + _sbss = .; /* Start address for the .bss section */ + __bss_start__ = _sbss; + *(.bss) + *(.bss*) + *(COMMON) + + . = ALIGN(4); + _ebss = . ; /* End address for the .bss section */ + __bss_end__ = _ebss; + } > RAM + + /* Space for heap and stack */ + .heap_stack : + { + end = . ; /* 'end' symbol defines heap location */ + _end = end ; + . = . + _min_heap_size; /* Additional space for heap and stack */ + . = . + _min_stack_size; + } > RAM + + /* Remove information from the standard libraries */ + /DISCARD/ : + { + libc.a ( * ) + libm.a ( * ) + libgcc.a ( * ) + } +} diff --git a/stm32f7xx_link.ld b/stm32f7xx_link.ld new file mode 100644 index 0000000..3f82067 --- /dev/null +++ b/stm32f7xx_link.ld @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2017 by Andy Uribe CA6JAU + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* Required amount of heap and stack */ +_min_heap_size = 0x1000; +_min_stack_size = 0x0800; + +/* The entry point in the interrupt vector table */ +ENTRY(Reset_Handler) + +/* Memory areas */ +MEMORY +{ + ROM (rx) : ORIGIN = 0x08000000, LENGTH = 2048K /* FLASH */ + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 512K /* Main RAM */ +} + +/* Stack start address (end of 512K RAM) */ +_estack = ORIGIN(RAM) + LENGTH(RAM); + +SECTIONS +{ + .text : + { + /* The interrupt vector table */ + . = ALIGN(4); + KEEP(*(.isr_vector .isr_vector.*)) + + /* The program code */ + . = ALIGN(4); + *(.text .text*) + *(.rodata .rodata*) + + /* ARM-Thumb code */ + *(.glue_7) *(.glue_7t) + + . = ALIGN(4); + KEEP(*(.init)) + KEEP(*(.fini)) + + /* EABI C++ global constructors support */ + . = ALIGN(4); + __preinit_array_start = .; + KEEP (*(.preinit_array)) + __preinit_array_end = .; + + /* EABI C++ global constructors support */ + . = ALIGN(4); + __init_array_start = .; + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array)) + __init_array_end = .; + + /* EABI C++ global constructors support */ + . = ALIGN(4); + __fini_array_start = .; + KEEP (*(.fini_array)) + KEEP (*(SORT(.fini_array.*))) + __fini_array_end = .; + + } > ROM + + /* ARM sections containing exception unwinding information */ + .ARM.extab : { + __extab_start = .; + *(.ARM.extab* .gnu.linkonce.armextab.*) + __extab_end = .; + } > ROM + + /* ARM index entries for section unwinding */ + .ARM.exidx : { + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; + } > ROM + + /* Start address for the initialization values of the .data section */ + _sidata = .; + + /* The .data section (initialized data) */ + .data : AT ( _sidata ) + { + . = ALIGN(4); + _sdata = . ; /* Start address for the .data section */ + *(.data .data*) + + . = ALIGN(4); + _edata = . ; /* End address for the .data section */ + } > RAM + + /* The .bss section (uninitialized data) */ + .bss : + { + . = ALIGN(4); + _sbss = .; /* Start address for the .bss section */ + __bss_start__ = _sbss; + *(.bss) + *(.bss*) + *(COMMON) + + . = ALIGN(4); + _ebss = . ; /* End address for the .bss section */ + __bss_end__ = _ebss; + } > RAM + + /* Space for heap and stack */ + .heap_stack : + { + end = . ; /* 'end' symbol defines heap location */ + _end = end ; + . = . + _min_heap_size; /* Additional space for heap and stack */ + . = . + _min_stack_size; + } > RAM + + /* Remove information from the standard libraries */ + /DISCARD/ : + { + libc.a ( * ) + libm.a ( * ) + libgcc.a ( * ) + } +}