From 777919da49c2bd2041cca03b12f86bea7a50428b Mon Sep 17 00:00:00 2001 From: Rob Riggs Date: Sat, 22 Jun 2019 21:53:34 -0500 Subject: [PATCH] Massive update to get the NucleoTNC firmware up to date with the TNC3. Mostly improvements to the moduleator and demodulator sections. Fix EEPROM read/write. Update version number to 1.0.0. --- Inc/main.h | 12 +- Nucleo_L432KC_TNC.ioc | 69 +++--- Src/arm_fir_fast_q15.c | 345 ++++++++++++++++++++++++++++ Src/arm_fir_init_q15.c | 154 +++++++++++++ Src/arm_offset_q15.c | 136 +++++++++++ Src/arm_q15_to_float.c | 134 +++++++++++ Src/main.c | 46 +++- Src/stm32l4xx_hal_msp.c | 2 +- TNC/AFSKModulator.hpp | 2 +- TNC/AFSKTestTone.hpp | 4 +- TNC/AfskDemodulator.cpp | 35 +-- TNC/AfskDemodulator.hpp | 96 ++------ TNC/AudioInput.cpp | 366 +++++++---------------------- TNC/AudioInput.hpp | 23 +- TNC/AudioLevel.cpp | 74 +++--- TNC/DigitalPLL.cpp | 32 --- TNC/DigitalPLL.hpp | 64 ++++-- TNC/Filter.cpp | 460 ------------------------------------- TNC/Filter.h | 42 ---- TNC/Filter.hpp | 16 -- TNC/FilterCoefficients.hpp | 81 ------- TNC/FirFilter.hpp | 53 ++++- TNC/Goertzel.h | 1 + TNC/HDLCEncoder.hpp | 23 +- TNC/HdlcDecoder.cpp | 130 ++++++++++- TNC/HdlcDecoder.hpp | 52 ++++- TNC/HdlcFrame.cpp | 19 ++ TNC/HdlcFrame.hpp | 23 +- TNC/IOEventTask.cpp | 223 ++++++++++-------- TNC/IirFilter.hpp | 56 +---- TNC/KissHardware.cpp | 222 ++++++++++-------- TNC/KissHardware.hpp | 245 +++++++++++--------- 32 files changed, 1706 insertions(+), 1534 deletions(-) create mode 100644 Src/arm_fir_fast_q15.c create mode 100644 Src/arm_fir_init_q15.c create mode 100644 Src/arm_offset_q15.c create mode 100644 Src/arm_q15_to_float.c delete mode 100644 TNC/DigitalPLL.cpp delete mode 100644 TNC/Filter.cpp delete mode 100644 TNC/Filter.h delete mode 100644 TNC/Filter.hpp diff --git a/Inc/main.h b/Inc/main.h index b8c26e7..1932506 100644 --- a/Inc/main.h +++ b/Inc/main.h @@ -58,7 +58,6 @@ /* USER CODE END Includes */ /* Private define ------------------------------------------------------------*/ - #define VCP_TX_Pin GPIO_PIN_2 #define VCP_TX_GPIO_Port GPIOA #define AUDIO_IN_Pin GPIO_PIN_3 @@ -100,17 +99,26 @@ /* USER CODE BEGIN Private defines */ +#define EEPROM_ADDRESS 0xA0 +#define EEPROM_CAPACITY 4096 +#define EEPROM_PAGE_SIZE 32 +#define EEPROM_WRITE_TIME 5 + #define CMD_USER_BUTTON_DOWN 1 #define CMD_USER_BUTTON_UP 2 #define CMD_SET_PTT_SIMPLEX 3 #define CMD_SET_PTT_MULTIPLEX 4 +#define CxxErrorHandler() _Error_Handler(const_cast(__FILE__), __LINE__) + +extern char serial_number_64[17]; + /* USER CODE END Private defines */ #ifdef __cplusplus extern "C" { #endif -void _Error_Handler(char *, int); +void _Error_Handler(const char *, int); #define Error_Handler() _Error_Handler(__FILE__, __LINE__) #ifdef __cplusplus diff --git a/Nucleo_L432KC_TNC.ioc b/Nucleo_L432KC_TNC.ioc index 144d0d8..4af0f43 100644 --- a/Nucleo_L432KC_TNC.ioc +++ b/Nucleo_L432KC_TNC.ioc @@ -39,15 +39,15 @@ CRC.InputDataInversionMode=CRC_INPUTDATA_INVERSION_BYTE DAC1.DAC_Channel-DAC_OUT2=DAC_CHANNEL_2 DAC1.DAC_Trigger-DAC_OUT1=DAC_TRIGGER_T7_TRGO DAC1.IPParameters=DAC_Channel-DAC_OUT2,DAC_Trigger-DAC_OUT1 -Dma.ADC1.5.Direction=DMA_PERIPH_TO_MEMORY -Dma.ADC1.5.Instance=DMA1_Channel1 -Dma.ADC1.5.MemDataAlignment=DMA_MDATAALIGN_HALFWORD -Dma.ADC1.5.MemInc=DMA_MINC_ENABLE -Dma.ADC1.5.Mode=DMA_CIRCULAR -Dma.ADC1.5.PeriphDataAlignment=DMA_PDATAALIGN_HALFWORD -Dma.ADC1.5.PeriphInc=DMA_PINC_DISABLE -Dma.ADC1.5.Priority=DMA_PRIORITY_LOW -Dma.ADC1.5.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority +Dma.ADC1.3.Direction=DMA_PERIPH_TO_MEMORY +Dma.ADC1.3.Instance=DMA1_Channel1 +Dma.ADC1.3.MemDataAlignment=DMA_MDATAALIGN_HALFWORD +Dma.ADC1.3.MemInc=DMA_MINC_ENABLE +Dma.ADC1.3.Mode=DMA_CIRCULAR +Dma.ADC1.3.PeriphDataAlignment=DMA_PDATAALIGN_HALFWORD +Dma.ADC1.3.PeriphInc=DMA_PINC_DISABLE +Dma.ADC1.3.Priority=DMA_PRIORITY_LOW +Dma.ADC1.3.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority Dma.DAC_CH1.2.Direction=DMA_MEMORY_TO_PERIPH Dma.DAC_CH1.2.Instance=DMA2_Channel4 Dma.DAC_CH1.2.MemDataAlignment=DMA_MDATAALIGN_HALFWORD @@ -57,30 +57,30 @@ Dma.DAC_CH1.2.PeriphDataAlignment=DMA_PDATAALIGN_HALFWORD Dma.DAC_CH1.2.PeriphInc=DMA_PINC_DISABLE Dma.DAC_CH1.2.Priority=DMA_PRIORITY_LOW Dma.DAC_CH1.2.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority -Dma.I2C3_RX.3.Direction=DMA_PERIPH_TO_MEMORY -Dma.I2C3_RX.3.Instance=DMA1_Channel3 -Dma.I2C3_RX.3.MemDataAlignment=DMA_MDATAALIGN_BYTE -Dma.I2C3_RX.3.MemInc=DMA_MINC_ENABLE -Dma.I2C3_RX.3.Mode=DMA_NORMAL -Dma.I2C3_RX.3.PeriphDataAlignment=DMA_PDATAALIGN_BYTE -Dma.I2C3_RX.3.PeriphInc=DMA_PINC_DISABLE -Dma.I2C3_RX.3.Priority=DMA_PRIORITY_LOW -Dma.I2C3_RX.3.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority -Dma.I2C3_TX.4.Direction=DMA_MEMORY_TO_PERIPH -Dma.I2C3_TX.4.Instance=DMA1_Channel2 -Dma.I2C3_TX.4.MemDataAlignment=DMA_MDATAALIGN_BYTE -Dma.I2C3_TX.4.MemInc=DMA_MINC_ENABLE -Dma.I2C3_TX.4.Mode=DMA_NORMAL -Dma.I2C3_TX.4.PeriphDataAlignment=DMA_PDATAALIGN_BYTE -Dma.I2C3_TX.4.PeriphInc=DMA_PINC_DISABLE -Dma.I2C3_TX.4.Priority=DMA_PRIORITY_LOW -Dma.I2C3_TX.4.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority +Dma.I2C3_RX.4.Direction=DMA_PERIPH_TO_MEMORY +Dma.I2C3_RX.4.Instance=DMA1_Channel3 +Dma.I2C3_RX.4.MemDataAlignment=DMA_MDATAALIGN_BYTE +Dma.I2C3_RX.4.MemInc=DMA_MINC_ENABLE +Dma.I2C3_RX.4.Mode=DMA_NORMAL +Dma.I2C3_RX.4.PeriphDataAlignment=DMA_PDATAALIGN_BYTE +Dma.I2C3_RX.4.PeriphInc=DMA_PINC_DISABLE +Dma.I2C3_RX.4.Priority=DMA_PRIORITY_LOW +Dma.I2C3_RX.4.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority +Dma.I2C3_TX.5.Direction=DMA_MEMORY_TO_PERIPH +Dma.I2C3_TX.5.Instance=DMA1_Channel2 +Dma.I2C3_TX.5.MemDataAlignment=DMA_MDATAALIGN_BYTE +Dma.I2C3_TX.5.MemInc=DMA_MINC_ENABLE +Dma.I2C3_TX.5.Mode=DMA_NORMAL +Dma.I2C3_TX.5.PeriphDataAlignment=DMA_PDATAALIGN_BYTE +Dma.I2C3_TX.5.PeriphInc=DMA_PINC_DISABLE +Dma.I2C3_TX.5.Priority=DMA_PRIORITY_LOW +Dma.I2C3_TX.5.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority Dma.Request0=USART2_RX Dma.Request1=USART2_TX Dma.Request2=DAC_CH1 -Dma.Request3=I2C3_RX -Dma.Request4=I2C3_TX -Dma.Request5=ADC1 +Dma.Request3=ADC1 +Dma.Request4=I2C3_RX +Dma.Request5=I2C3_TX Dma.RequestsNb=6 Dma.USART2_RX.0.Direction=DMA_PERIPH_TO_MEMORY Dma.USART2_RX.0.Instance=DMA1_Channel6 @@ -112,9 +112,9 @@ FREERTOS.configTOTAL_HEAP_SIZE=4096 FREERTOS.configUSE_TICKLESS_IDLE=1 FREERTOS.configUSE_TIMERS=1 File.Version=6 -I2C3.I2C_Speed_Mode=I2C_Fast_Plus +I2C3.I2C_Speed_Mode=I2C_Fast I2C3.IPParameters=Timing,I2C_Speed_Mode -I2C3.Timing=0x20000209 +I2C3.Timing=0x2010091A KeepUserPlacement=true Mcu.Family=STM32L4 Mcu.IP0=ADC1 @@ -169,8 +169,8 @@ Mcu.PinsNb=31 Mcu.ThirdPartyNb=0 Mcu.UserConstants= Mcu.UserName=STM32L432KCUx -MxCube.Version=4.26.1 -MxDb.Version=DB.4.0.261 +MxCube.Version=4.27.0 +MxDb.Version=DB.4.0.270 NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:false\:false\:false NVIC.DMA1_Channel1_IRQn=true\:5\:0\:false\:false\:true\:true\:false NVIC.DMA1_Channel2_IRQn=true\:5\:0\:false\:false\:true\:true\:false @@ -320,6 +320,7 @@ ProjectManager.KeepUserCode=true ProjectManager.LastFirmware=true ProjectManager.LibraryCopy=1 ProjectManager.MainLocation=Src +ProjectManager.NoMain=false ProjectManager.PreviousToolchain=TrueSTUDIO ProjectManager.ProjectBuild=false ProjectManager.ProjectFileName=Nucleo_L432KC_TNC.ioc diff --git a/Src/arm_fir_fast_q15.c b/Src/arm_fir_fast_q15.c new file mode 100644 index 0000000..5e2ed77 --- /dev/null +++ b/Src/arm_fir_fast_q15.c @@ -0,0 +1,345 @@ +/* ---------------------------------------------------------------------- +* Copyright (C) 2010-2014 ARM Limited. All rights reserved. +* +* $Date: 19. March 2015 +* $Revision: V.1.4.5 +* +* Project: CMSIS DSP Library +* Title: arm_fir_fast_q15.c +* +* Description: Q15 Fast FIR filter processing function. +* +* Target Processor: Cortex-M4/Cortex-M3 +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* - Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* - Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided with the +* distribution. +* - Neither the name of ARM LIMITED nor the names of its contributors +* may be used to endorse or promote products derived from this +* software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +* -------------------------------------------------------------------- */ + +#include "arm_math.h" + +/** + * @ingroup groupFilters + */ + +/** + * @addtogroup FIR + * @{ + */ + +/** + * @param[in] *S points to an instance of the Q15 FIR filter structure. + * @param[in] *pSrc points to the block of input data. + * @param[out] *pDst points to the block of output data. + * @param[in] blockSize number of samples to process per call. + * @return none. + * + * Scaling and Overflow Behavior: + * \par + * This fast version uses a 32-bit accumulator with 2.30 format. + * The accumulator maintains full precision of the intermediate multiplication results but provides only a single guard bit. + * Thus, if the accumulator result overflows it wraps around and distorts the result. + * In order to avoid overflows completely the input signal must be scaled down by log2(numTaps) bits. + * The 2.30 accumulator is then truncated to 2.15 format and saturated to yield the 1.15 result. + * + * \par + * Refer to the function arm_fir_q15() for a slower implementation of this function which uses 64-bit accumulation to avoid wrap around distortion. Both the slow and the fast versions use the same instance structure. + * Use the function arm_fir_init_q15() to initialize the filter structure. + */ + +void arm_fir_fast_q15( + const arm_fir_instance_q15 * S, + q15_t * pSrc, + q15_t * pDst, + uint32_t blockSize) +{ + q15_t *pState = S->pState; /* State pointer */ + q15_t *pCoeffs = S->pCoeffs; /* Coefficient pointer */ + q15_t *pStateCurnt; /* Points to the current sample of the state */ + q31_t acc0, acc1, acc2, acc3; /* Accumulators */ + q15_t *pb; /* Temporary pointer for coefficient buffer */ + q15_t *px; /* Temporary q31 pointer for SIMD state buffer accesses */ + q31_t x0, x1, x2, c0; /* Temporary variables to hold SIMD state and coefficient values */ + uint32_t numTaps = S->numTaps; /* Number of taps in the filter */ + uint32_t tapCnt, blkCnt; /* Loop counters */ + + + /* S->pState points to state array which contains previous frame (numTaps - 1) samples */ + /* pStateCurnt points to the location where the new input data should be written */ + pStateCurnt = &(S->pState[(numTaps - 1u)]); + + /* Apply loop unrolling and compute 4 output values simultaneously. + * The variables acc0 ... acc3 hold output values that are being computed: + * + * acc0 = b[numTaps-1] * x[n-numTaps-1] + b[numTaps-2] * x[n-numTaps-2] + b[numTaps-3] * x[n-numTaps-3] +...+ b[0] * x[0] + * acc1 = b[numTaps-1] * x[n-numTaps] + b[numTaps-2] * x[n-numTaps-1] + b[numTaps-3] * x[n-numTaps-2] +...+ b[0] * x[1] + * acc2 = b[numTaps-1] * x[n-numTaps+1] + b[numTaps-2] * x[n-numTaps] + b[numTaps-3] * x[n-numTaps-1] +...+ b[0] * x[2] + * acc3 = b[numTaps-1] * x[n-numTaps+2] + b[numTaps-2] * x[n-numTaps+1] + b[numTaps-3] * x[n-numTaps] +...+ b[0] * x[3] + */ + + blkCnt = blockSize >> 2; + + /* First part of the processing with loop unrolling. Compute 4 outputs at a time. + ** a second loop below computes the remaining 1 to 3 samples. */ + while(blkCnt > 0u) + { + /* Copy four new input samples into the state buffer. + ** Use 32-bit SIMD to move the 16-bit data. Only requires two copies. */ + *pStateCurnt++ = *pSrc++; + *pStateCurnt++ = *pSrc++; + *pStateCurnt++ = *pSrc++; + *pStateCurnt++ = *pSrc++; + + + /* Set all accumulators to zero */ + acc0 = 0; + acc1 = 0; + acc2 = 0; + acc3 = 0; + + /* Typecast q15_t pointer to q31_t pointer for state reading in q31_t */ + px = pState; + + /* Typecast q15_t pointer to q31_t pointer for coefficient reading in q31_t */ + pb = pCoeffs; + + /* Read the first two samples from the state buffer: x[n-N], x[n-N-1] */ + x0 = *__SIMD32(px)++; + + /* Read the third and forth samples from the state buffer: x[n-N-2], x[n-N-3] */ + x2 = *__SIMD32(px)++; + + /* Loop over the number of taps. Unroll by a factor of 4. + ** Repeat until we've computed numTaps-(numTaps%4) coefficients. */ + tapCnt = numTaps >> 2; + + while(tapCnt > 0) + { + /* Read the first two coefficients using SIMD: b[N] and b[N-1] coefficients */ + c0 = *__SIMD32(pb)++; + + /* acc0 += b[N] * x[n-N] + b[N-1] * x[n-N-1] */ + acc0 = __SMLAD(x0, c0, acc0); + + /* acc2 += b[N] * x[n-N-2] + b[N-1] * x[n-N-3] */ + acc2 = __SMLAD(x2, c0, acc2); + + /* pack x[n-N-1] and x[n-N-2] */ +#ifndef ARM_MATH_BIG_ENDIAN + x1 = __PKHBT(x2, x0, 0); +#else + x1 = __PKHBT(x0, x2, 0); +#endif + + /* Read state x[n-N-4], x[n-N-5] */ + x0 = _SIMD32_OFFSET(px); + + /* acc1 += b[N] * x[n-N-1] + b[N-1] * x[n-N-2] */ + acc1 = __SMLADX(x1, c0, acc1); + + /* pack x[n-N-3] and x[n-N-4] */ +#ifndef ARM_MATH_BIG_ENDIAN + x1 = __PKHBT(x0, x2, 0); +#else + x1 = __PKHBT(x2, x0, 0); +#endif + + /* acc3 += b[N] * x[n-N-3] + b[N-1] * x[n-N-4] */ + acc3 = __SMLADX(x1, c0, acc3); + + /* Read coefficients b[N-2], b[N-3] */ + c0 = *__SIMD32(pb)++; + + /* acc0 += b[N-2] * x[n-N-2] + b[N-3] * x[n-N-3] */ + acc0 = __SMLAD(x2, c0, acc0); + + /* Read state x[n-N-6], x[n-N-7] with offset */ + x2 = _SIMD32_OFFSET(px + 2u); + + /* acc2 += b[N-2] * x[n-N-4] + b[N-3] * x[n-N-5] */ + acc2 = __SMLAD(x0, c0, acc2); + + /* acc1 += b[N-2] * x[n-N-3] + b[N-3] * x[n-N-4] */ + acc1 = __SMLADX(x1, c0, acc1); + + /* pack x[n-N-5] and x[n-N-6] */ +#ifndef ARM_MATH_BIG_ENDIAN + x1 = __PKHBT(x2, x0, 0); +#else + x1 = __PKHBT(x0, x2, 0); +#endif + + /* acc3 += b[N-2] * x[n-N-5] + b[N-3] * x[n-N-6] */ + acc3 = __SMLADX(x1, c0, acc3); + + /* Update state pointer for next state reading */ + px += 4u; + + /* Decrement tap count */ + tapCnt--; + + } + + /* If the filter length is not a multiple of 4, compute the remaining filter taps. + ** This is always be 2 taps since the filter length is even. */ + if((numTaps & 0x3u) != 0u) + { + + /* Read last two coefficients */ + c0 = *__SIMD32(pb)++; + + /* Perform the multiply-accumulates */ + acc0 = __SMLAD(x0, c0, acc0); + acc2 = __SMLAD(x2, c0, acc2); + + /* pack state variables */ +#ifndef ARM_MATH_BIG_ENDIAN + x1 = __PKHBT(x2, x0, 0); +#else + x1 = __PKHBT(x0, x2, 0); +#endif + + /* Read last state variables */ + x0 = *__SIMD32(px); + + /* Perform the multiply-accumulates */ + acc1 = __SMLADX(x1, c0, acc1); + + /* pack state variables */ +#ifndef ARM_MATH_BIG_ENDIAN + x1 = __PKHBT(x0, x2, 0); +#else + x1 = __PKHBT(x2, x0, 0); +#endif + + /* Perform the multiply-accumulates */ + acc3 = __SMLADX(x1, c0, acc3); + } + + /* The results in the 4 accumulators are in 2.30 format. Convert to 1.15 with saturation. + ** Then store the 4 outputs in the destination buffer. */ + +#ifndef ARM_MATH_BIG_ENDIAN + + *__SIMD32(pDst)++ = + __PKHBT(__SSAT((acc0 >> 15), 16), __SSAT((acc1 >> 15), 16), 16); + + *__SIMD32(pDst)++ = + __PKHBT(__SSAT((acc2 >> 15), 16), __SSAT((acc3 >> 15), 16), 16); + +#else + + *__SIMD32(pDst)++ = + __PKHBT(__SSAT((acc1 >> 15), 16), __SSAT((acc0 >> 15), 16), 16); + + *__SIMD32(pDst)++ = + __PKHBT(__SSAT((acc3 >> 15), 16), __SSAT((acc2 >> 15), 16), 16); + + +#endif /* #ifndef ARM_MATH_BIG_ENDIAN */ + + /* Advance the state pointer by 4 to process the next group of 4 samples */ + pState = pState + 4u; + + /* Decrement the loop counter */ + blkCnt--; + } + + /* If the blockSize is not a multiple of 4, compute any remaining output samples here. + ** No loop unrolling is used. */ + blkCnt = blockSize % 0x4u; + while(blkCnt > 0u) + { + /* Copy two samples into state buffer */ + *pStateCurnt++ = *pSrc++; + + /* Set the accumulator to zero */ + acc0 = 0; + + /* Use SIMD to hold states and coefficients */ + px = pState; + pb = pCoeffs; + + tapCnt = numTaps >> 1u; + + do + { + + acc0 += (q31_t) * px++ * *pb++; + acc0 += (q31_t) * px++ * *pb++; + + tapCnt--; + } + while(tapCnt > 0u); + + /* The result is in 2.30 format. Convert to 1.15 with saturation. + ** Then store the output in the destination buffer. */ + *pDst++ = (q15_t) (__SSAT((acc0 >> 15), 16)); + + /* Advance state pointer by 1 for the next sample */ + pState = pState + 1u; + + /* Decrement the loop counter */ + blkCnt--; + } + + /* Processing is complete. + ** Now copy the last numTaps - 1 samples to the satrt of the state buffer. + ** This prepares the state buffer for the next function call. */ + + /* Points to the start of the state buffer */ + pStateCurnt = S->pState; + + /* Calculation of count for copying integer writes */ + tapCnt = (numTaps - 1u) >> 2; + + while(tapCnt > 0u) + { + *pStateCurnt++ = *pState++; + *pStateCurnt++ = *pState++; + *pStateCurnt++ = *pState++; + *pStateCurnt++ = *pState++; + + tapCnt--; + + } + + /* Calculation of count for remaining q15_t data */ + tapCnt = (numTaps - 1u) % 0x4u; + + /* copy remaining data */ + while(tapCnt > 0u) + { + *pStateCurnt++ = *pState++; + + /* Decrement the loop counter */ + tapCnt--; + } + +} + +/** + * @} end of FIR group + */ diff --git a/Src/arm_fir_init_q15.c b/Src/arm_fir_init_q15.c new file mode 100644 index 0000000..d976d73 --- /dev/null +++ b/Src/arm_fir_init_q15.c @@ -0,0 +1,154 @@ +/* ---------------------------------------------------------------------- +* Copyright (C) 2010-2014 ARM Limited. All rights reserved. +* +* $Date: 19. March 2015 +* $Revision: V.1.4.5 +* +* Project: CMSIS DSP Library +* Title: arm_fir_init_q15.c +* +* Description: Q15 FIR filter initialization function. +* +* Target Processor: Cortex-M4/Cortex-M3/Cortex-M0 +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* - Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* - Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided with the +* distribution. +* - Neither the name of ARM LIMITED nor the names of its contributors +* may be used to endorse or promote products derived from this +* software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +* ------------------------------------------------------------------- */ + +#include "arm_math.h" + +/** + * @ingroup groupFilters + */ + +/** + * @addtogroup FIR + * @{ + */ + +/** + * @param[in,out] *S points to an instance of the Q15 FIR filter structure. + * @param[in] numTaps Number of filter coefficients in the filter. Must be even and greater than or equal to 4. + * @param[in] *pCoeffs points to the filter coefficients buffer. + * @param[in] *pState points to the state buffer. + * @param[in] blockSize is number of samples processed per call. + * @return The function returns ARM_MATH_SUCCESS if initialization is successful or ARM_MATH_ARGUMENT_ERROR if + * numTaps is not greater than or equal to 4 and even. + * + * Description: + * \par + * pCoeffs points to the array of filter coefficients stored in time reversed order: + *
    
+ *    {b[numTaps-1], b[numTaps-2], b[N-2], ..., b[1], b[0]}    
+ * 
+ * Note that numTaps must be even and greater than or equal to 4. + * To implement an odd length filter simply increase numTaps by 1 and set the last coefficient to zero. + * For example, to implement a filter with numTaps=3 and coefficients + *
    
+ *     {0.3, -0.8, 0.3}    
+ * 
+ * set numTaps=4 and use the coefficients: + *
    
+ *     {0.3, -0.8, 0.3, 0}.    
+ * 
+ * Similarly, to implement a two point filter + *
    
+ *     {0.3, -0.3}    
+ * 
+ * set numTaps=4 and use the coefficients: + *
    
+ *     {0.3, -0.3, 0, 0}.    
+ * 
+ * \par + * pState points to the array of state variables. + * pState is of length numTaps+blockSize, when running on Cortex-M4 and Cortex-M3 and is of length numTaps+blockSize-1, when running on Cortex-M0 where blockSize is the number of input samples processed by each call to arm_fir_q15(). + */ + +arm_status arm_fir_init_q15( + arm_fir_instance_q15 * S, + uint16_t numTaps, + q15_t * pCoeffs, + q15_t * pState, + uint32_t blockSize) +{ + arm_status status; + + +#ifndef ARM_MATH_CM0_FAMILY + + /* Run the below code for Cortex-M4 and Cortex-M3 */ + + /* The Number of filter coefficients in the filter must be even and at least 4 */ + if(numTaps & 0x1u) + { + status = ARM_MATH_ARGUMENT_ERROR; + } + else + { + /* Assign filter taps */ + S->numTaps = numTaps; + + /* Assign coefficient pointer */ + S->pCoeffs = pCoeffs; + + /* Clear the state buffer. The size is always (blockSize + numTaps ) */ + memset(pState, 0, (numTaps + (blockSize)) * sizeof(q15_t)); + + /* Assign state pointer */ + S->pState = pState; + + status = ARM_MATH_SUCCESS; + } + + return (status); + +#else + + /* Run the below code for Cortex-M0 */ + + /* Assign filter taps */ + S->numTaps = numTaps; + + /* Assign coefficient pointer */ + S->pCoeffs = pCoeffs; + + /* Clear the state buffer. The size is always (blockSize + numTaps - 1) */ + memset(pState, 0, (numTaps + (blockSize - 1u)) * sizeof(q15_t)); + + /* Assign state pointer */ + S->pState = pState; + + status = ARM_MATH_SUCCESS; + + return (status); + +#endif /* #ifndef ARM_MATH_CM0_FAMILY */ + +} + +/** + * @} end of FIR group + */ diff --git a/Src/arm_offset_q15.c b/Src/arm_offset_q15.c new file mode 100644 index 0000000..72ad128 --- /dev/null +++ b/Src/arm_offset_q15.c @@ -0,0 +1,136 @@ +/* ---------------------------------------------------------------------- +* Copyright (C) 2010-2014 ARM Limited. All rights reserved. +* +* $Date: 19. March 2015 +* $Revision: V.1.4.5 +* +* Project: CMSIS DSP Library +* Title: arm_offset_q15.c +* +* Description: Q15 vector offset. +* +* Target Processor: Cortex-M4/Cortex-M3/Cortex-M0 +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* - Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* - Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided with the +* distribution. +* - Neither the name of ARM LIMITED nor the names of its contributors +* may be used to endorse or promote products derived from this +* software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +* -------------------------------------------------------------------- */ + +#include "arm_math.h" + +/** + * @ingroup groupMath + */ + +/** + * @addtogroup offset + * @{ + */ + +/** + * @brief Adds a constant offset to a Q15 vector. + * @param[in] *pSrc points to the input vector + * @param[in] offset is the offset to be added + * @param[out] *pDst points to the output vector + * @param[in] blockSize number of samples in the vector + * @return none. + * + * Scaling and Overflow Behavior: + * \par + * The function uses saturating arithmetic. + * Results outside of the allowable Q15 range [0x8000 0x7FFF] are saturated. + */ + +void arm_offset_q15( + q15_t * pSrc, + q15_t offset, + q15_t * pDst, + uint32_t blockSize) +{ + uint32_t blkCnt; /* loop counter */ + +#ifndef ARM_MATH_CM0_FAMILY + +/* Run the below code for Cortex-M4 and Cortex-M3 */ + q31_t offset_packed; /* Offset packed to 32 bit */ + + + /*loop Unrolling */ + blkCnt = blockSize >> 2u; + + /* Offset is packed to 32 bit in order to use SIMD32 for addition */ + offset_packed = __PKHBT(offset, offset, 16); + + /* First part of the processing with loop unrolling. Compute 4 outputs at a time. + ** a second loop below computes the remaining 1 to 3 samples. */ + while(blkCnt > 0u) + { + /* C = A + offset */ + /* Add offset and then store the results in the destination buffer, 2 samples at a time. */ + *__SIMD32(pDst)++ = __QADD16(*__SIMD32(pSrc)++, offset_packed); + *__SIMD32(pDst)++ = __QADD16(*__SIMD32(pSrc)++, offset_packed); + + /* Decrement the loop counter */ + blkCnt--; + } + + /* If the blockSize is not a multiple of 4, compute any remaining output samples here. + ** No loop unrolling is used. */ + blkCnt = blockSize % 0x4u; + + while(blkCnt > 0u) + { + /* C = A + offset */ + /* Add offset and then store the results in the destination buffer. */ + *pDst++ = (q15_t) __QADD16(*pSrc++, offset); + + /* Decrement the loop counter */ + blkCnt--; + } + +#else + + /* Run the below code for Cortex-M0 */ + + /* Initialize blkCnt with number of samples */ + blkCnt = blockSize; + + while(blkCnt > 0u) + { + /* C = A + offset */ + /* Add offset and then store the results in the destination buffer. */ + *pDst++ = (q15_t) __SSAT(((q31_t) * pSrc++ + offset), 16); + + /* Decrement the loop counter */ + blkCnt--; + } + +#endif /* #ifndef ARM_MATH_CM0_FAMILY */ + +} + +/** + * @} end of offset group + */ diff --git a/Src/arm_q15_to_float.c b/Src/arm_q15_to_float.c new file mode 100644 index 0000000..2e9624a --- /dev/null +++ b/Src/arm_q15_to_float.c @@ -0,0 +1,134 @@ +/* ---------------------------------------------------------------------------- +* Copyright (C) 2010-2014 ARM Limited. All rights reserved. +* +* $Date: 19. March 2015 +* $Revision: V.1.4.5 +* +* Project: CMSIS DSP Library +* Title: arm_q15_to_float.c +* +* Description: Converts the elements of the Q15 vector to floating-point vector. +* +* Target Processor: Cortex-M4/Cortex-M3/Cortex-M0 +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* - Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* - Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided with the +* distribution. +* - Neither the name of ARM LIMITED nor the names of its contributors +* may be used to endorse or promote products derived from this +* software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +* ---------------------------------------------------------------------------- */ + +#include "arm_math.h" + +/** + * @ingroup groupSupport + */ + +/** + * @defgroup q15_to_x Convert 16-bit Integer value + */ + +/** + * @addtogroup q15_to_x + * @{ + */ + + + + +/** + * @brief Converts the elements of the Q15 vector to floating-point vector. + * @param[in] *pSrc points to the Q15 input vector + * @param[out] *pDst points to the floating-point output vector + * @param[in] blockSize length of the input vector + * @return none. + * + * \par Description: + * + * The equation used for the conversion process is: + * + *
    
+ * 	pDst[n] = (float32_t) pSrc[n] / 32768;   0 <= n < blockSize.    
+ * 
+ * + */ + + +void arm_q15_to_float( + q15_t * pSrc, + float32_t * pDst, + uint32_t blockSize) +{ + q15_t *pIn = pSrc; /* Src pointer */ + uint32_t blkCnt; /* loop counter */ + + +#ifndef ARM_MATH_CM0_FAMILY + + /* Run the below code for Cortex-M4 and Cortex-M3 */ + + /*loop Unrolling */ + blkCnt = blockSize >> 2u; + + /* First part of the processing with loop unrolling. Compute 4 outputs at a time. + ** a second loop below computes the remaining 1 to 3 samples. */ + while(blkCnt > 0u) + { + /* C = (float32_t) A / 32768 */ + /* convert from q15 to float and then store the results in the destination buffer */ + *pDst++ = ((float32_t) * pIn++ / 32768.0f); + *pDst++ = ((float32_t) * pIn++ / 32768.0f); + *pDst++ = ((float32_t) * pIn++ / 32768.0f); + *pDst++ = ((float32_t) * pIn++ / 32768.0f); + + /* Decrement the loop counter */ + blkCnt--; + } + + /* If the blockSize is not a multiple of 4, compute any remaining output samples here. + ** No loop unrolling is used. */ + blkCnt = blockSize % 0x4u; + +#else + + /* Run the below code for Cortex-M0 */ + + /* Loop over blockSize number of values */ + blkCnt = blockSize; + +#endif /* #ifndef ARM_MATH_CM0_FAMILY */ + + while(blkCnt > 0u) + { + /* C = (float32_t) A / 32768 */ + /* convert from q15 to float and then store the results in the destination buffer */ + *pDst++ = ((float32_t) * pIn++ / 32768.0f); + + /* Decrement the loop counter */ + blkCnt--; + } +} + +/** + * @} end of q15_to_x group + */ diff --git a/Src/main.c b/Src/main.c index bb7f187..22f7924 100644 --- a/Src/main.c +++ b/Src/main.c @@ -52,7 +52,7 @@ #include "cmsis_os.h" /* USER CODE BEGIN Includes */ - +#include /* USER CODE END Includes */ /* Private variables ---------------------------------------------------------*/ @@ -80,10 +80,10 @@ DMA_HandleTypeDef hdma_usart2_rx; DMA_HandleTypeDef hdma_usart2_tx; osThreadId defaultTaskHandle; -uint32_t defaultTaskBuffer[ 256 ]; +uint32_t defaultTaskBuffer[ 64 ]; osStaticThreadDef_t defaultTaskControlBlock; osThreadId ioEventTaskHandle; -uint32_t ioEventTaskBuffer[ 384 ]; +uint32_t ioEventTaskBuffer[ 576 ]; osStaticThreadDef_t ioEventTaskControlBlock; osThreadId audioInputTaskHandle; uint32_t audioInputTaskBuffer[ 512 ]; @@ -122,6 +122,8 @@ osTimerId beaconTimer4Handle; /* USER CODE BEGIN PV */ /* Private variables ---------------------------------------------------------*/ +char serial_number_64[17] = {0}; +char error_message[80] __attribute__((section(".bss3"))) = {0}; /* USER CODE END PV */ @@ -154,6 +156,31 @@ extern void onBeaconTimer4(void const * argument); /* USER CODE BEGIN 0 */ +/* + * Same algorithm as here: https://github.com/libopencm3/libopencm3/blob/master/lib/stm32/desig.c + */ + +void encode_serial_number() +{ + uint8_t *uid = (uint8_t *)UID_BASE; + + uint8_t serial[6]; + serial[0] = uid[11]; + serial[1] = uid[10] + uid[2]; + serial[2] = uid[9]; + serial[3] = uid[8] + uid[0]; + serial[4] = uid[7]; + serial[5] = uid[6]; + + snprintf( + serial_number_64, + sizeof(serial_number_64), + "%02X%02X%02X%02X%02X%02X", + serial[0], serial[1], serial[2], + serial[3], serial[4], serial[5] + ); +} + /* USER CODE END 0 */ /** @@ -196,7 +223,7 @@ int main(void) MX_USART2_UART_Init(); MX_I2C3_Init(); /* USER CODE BEGIN 2 */ - + encode_serial_number(); /* USER CODE END 2 */ /* USER CODE BEGIN RTOS_MUTEX */ @@ -230,11 +257,11 @@ int main(void) /* Create the thread(s) */ /* definition and creation of defaultTask */ - osThreadStaticDef(defaultTask, startDefaultTask, osPriorityIdle, 0, 256, defaultTaskBuffer, &defaultTaskControlBlock); + osThreadStaticDef(defaultTask, startDefaultTask, osPriorityIdle, 0, 64, defaultTaskBuffer, &defaultTaskControlBlock); defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL); /* definition and creation of ioEventTask */ - osThreadStaticDef(ioEventTask, startIOEventTask, osPriorityLow, 0, 384, ioEventTaskBuffer, &ioEventTaskControlBlock); + osThreadStaticDef(ioEventTask, startIOEventTask, osPriorityLow, 0, 576, ioEventTaskBuffer, &ioEventTaskControlBlock); ioEventTaskHandle = osThreadCreate(osThread(ioEventTask), NULL); /* definition and creation of audioInputTask */ @@ -439,7 +466,7 @@ static void MX_ADC1_Init(void) */ sConfig.Channel = ADC_CHANNEL_8; sConfig.Rank = ADC_REGULAR_RANK_1; - sConfig.SamplingTime = ADC_SAMPLETIME_6CYCLES_5; + sConfig.SamplingTime = ADC_SAMPLETIME_12CYCLES_5; sConfig.SingleDiff = ADC_SINGLE_ENDED; sConfig.OffsetNumber = ADC_OFFSET_NONE; sConfig.Offset = 0; @@ -453,7 +480,6 @@ static void MX_ADC1_Init(void) /* CRC init function */ static void MX_CRC_Init(void) { - hcrc.Instance = CRC; hcrc.Init.DefaultPolynomialUse = DEFAULT_POLYNOMIAL_DISABLE; hcrc.Init.DefaultInitValueUse = DEFAULT_INIT_VALUE_DISABLE; @@ -512,7 +538,7 @@ static void MX_I2C3_Init(void) { hi2c3.Instance = I2C3; - hi2c3.Init.Timing = 0x20000209; + hi2c3.Init.Timing = 0x2010091A; hi2c3.Init.OwnAddress1 = 0; hi2c3.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c3.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; @@ -864,7 +890,7 @@ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) * @param line: The line in file as a number. * @retval None */ -void _Error_Handler(char *file, int line) +void _Error_Handler(const char *file, int line) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ diff --git a/Src/stm32l4xx_hal_msp.c b/Src/stm32l4xx_hal_msp.c index 2fb6130..32a3955 100644 --- a/Src/stm32l4xx_hal_msp.c +++ b/Src/stm32l4xx_hal_msp.c @@ -61,7 +61,7 @@ extern DMA_HandleTypeDef hdma_usart2_rx; extern DMA_HandleTypeDef hdma_usart2_tx; -extern void _Error_Handler(char *, int); +extern void _Error_Handler(const char *, int); /* USER CODE BEGIN 0 */ /* USER CODE END 0 */ diff --git a/TNC/AFSKModulator.hpp b/TNC/AFSKModulator.hpp index e735d5b..7f31e04 100644 --- a/TNC/AFSKModulator.hpp +++ b/TNC/AFSKModulator.hpp @@ -1,4 +1,4 @@ -// Copyright 2015 Robert C. Riggs +// Copyright 2015-2019 Mobilinkd LLC // All rights reserved. #ifndef MOBILINKD__TNC__AFSK_MODULATOR_HPP_ diff --git a/TNC/AFSKTestTone.hpp b/TNC/AFSKTestTone.hpp index bdddb5e..f73f971 100644 --- a/TNC/AFSKTestTone.hpp +++ b/TNC/AFSKTestTone.hpp @@ -2,7 +2,7 @@ // All rights reserved. #ifndef MOBILINKD__TNC__AFSK_TEST_TONE_HPP_ -#define MOBILINKD__TNC__AFSKTESTTONE_HPP_ +#define MOBILINKD__TNC__AFSK_TEST_TONE_HPP_ #include "cmsis_os.h" @@ -29,4 +29,4 @@ struct AFSKTestTone }} // mobilinkd::tnc -#endif // MOBILINKD__TNC__AFSKTESTTONE_HPP_ +#endif // MOBILINKD__TNC__AFSK_TEST_TONE_HPP_ diff --git a/TNC/AfskDemodulator.cpp b/TNC/AfskDemodulator.cpp index a3d34d3..e520852 100644 --- a/TNC/AfskDemodulator.cpp +++ b/TNC/AfskDemodulator.cpp @@ -1,41 +1,42 @@ -// Copyright 2017 Rob Riggs +// Copyright 2017-2019 Rob Riggs // All rights reserved. #include "AfskDemodulator.hpp" namespace mobilinkd { namespace tnc { namespace afsk1200 { - -hdlc::IoFrame* Demodulator::operator()(float* samples, size_t len) +hdlc::IoFrame* Demodulator::operator()(q15_t* samples, size_t len) { - using namespace mobilinkd::tnc::gpio; - hdlc::IoFrame* result = 0; float* fa = audio_filter_(samples); - std::transform(fa, fa + audio::ADC_BUFFER_SIZE, buffer_a, agc_); - auto levels = input_comparator_(buffer_a); for (size_t i = 0; i != len; i++) { + buffer_[i] = int16_t(fa[i]); + } - bool delayed = delay_line_(levels[i]); - float_type x = float_type(levels[i] ^ delayed) - .5f; - float_type fc = correlator_filter_(x); - bool bit = output_comparator_(fc); + for (size_t i = 0; i != len; i++) { + bool level = (buffer_[i] >= 0); + bool delayed = delay_line_(level); + buffer_[i] = (int16_t(level ^ delayed) << 1) - 1; + } + + auto* fc = lpf_filter_.filter(buffer_); + + for (size_t i = 0; i != len; i++) { + bool bit = fc[i] >= 0; auto pll = pll_(bit); if (pll.sample) { - if (locked_ != pll.locked) { - locked_ = pll.locked; - } + locked_ = pll.locked; // We will only ever get one frame because there are // not enough bits in a block for more than one. if (result) { - auto tmp = hdlc_decoder_(nrzi_.decode(bit), pll.locked); - if (tmp) hdlc::ioFramePool().release(tmp); + auto tmp = hdlc_decoder_(nrzi_.decode(bit), true); + if (tmp) hdlc::release(tmp); } else { - result = hdlc_decoder_(nrzi_.decode(bit), pll.locked); + result = hdlc_decoder_(nrzi_.decode(bit), true); } } } diff --git a/TNC/AfskDemodulator.hpp b/TNC/AfskDemodulator.hpp index c1119da..a3411c0 100644 --- a/TNC/AfskDemodulator.hpp +++ b/TNC/AfskDemodulator.hpp @@ -1,91 +1,37 @@ -// Copyright 2015 Mobilinkd LLC +// Copyright 2015-2019 Mobilinkd LLC // All rights reserved. #ifndef MOBILINKD__AFSK_DEMODULATOR_HPP_ #define MOBILINKD__AFSK_DEMODULATOR_HPP_ #include -#include "AGC.hpp" #include "DelayLine.hpp" #include "AudioInput.hpp" #include "DigitalPLL.hpp" -// #include "Filter.h" #include "HdlcDecoder.hpp" #include "Hysteresis.hpp" #include "FirFilter.hpp" -#include "IirFilter.hpp" #include "NRZI.hpp" -#include "GPIO.hpp" #include #include namespace mobilinkd { namespace tnc { namespace afsk1200 { -#if 0 -const float b[] = { - 4.57519926037e-06, - 2.28759963018e-05, - 4.57519926037e-05, - 4.57519926037e-05, - 2.28759963018e-05, - 4.57519926037e-06, -}; +const size_t LPF_FILTER_LEN = 96; -const float a[] = { - 1.0, - -4.41489189545, - 7.82710410154, - -6.96306748269, - 3.10736843037, - -0.556366747391, -}; - -// Bessel 760Hz -const float b[] = { - 4.36034607e-06, - 2.18017304e-05, - 4.36034607e-05, - 4.36034607e-05, - 2.18017304e-05, - 4.36034607e-06, -}; - -const float a[] = { - 1.0, - -4.32673235, - 7.51393353, - -6.54579279, - 2.86009139, - -0.50136025, -}; -#endif - -// Bessel 1200Hz 7-pole low-pass -const float b[] = { - 6.10481382e-07, - 4.27336967e-06, - 1.28201090e-05, - 2.13668484e-05, - 2.13668484e-05, - 1.28201090e-05, - 4.27336967e-06, - 6.10481382e-07, -}; - -const float a[] = { - 1.0, - -5.56209875, - 13.34528507, - -17.89828744, - 14.48661262, - -7.07391246, - 1.92904679, - -0.22656769, +const q15_t lpf_coeffs[] = { + 0, 1, 3, 5, 8, 11, 14, 17, 19, 20, 18, 14, + 7, -2, -16, -33, -53, -76, -101, -126, -151, -174, -194, -208, + -215, -212, -199, -173, -133, -79, -10, 74, 173, 287, 413, 549, + 693, 842, 993, 1142, 1287, 1423, 1547, 1656, 1747, 1817, 1865, 1889, + 1889, 1865, 1817, 1747, 1656, 1547, 1423, 1287, 1142, 993, 842, 693, + 549, 413, 287, 173, 74, -10, -79, -133, -173, -199, -212, -215, + -208, -194, -174, -151, -126, -101, -76, -53, -33, -16, -2, 7, + 14, 18, 20, 19, 17, 14, 11, 8, 5, 3, 1, 0, }; typedef FirFilter emphasis_filter_type; -// typedef IirFilter emphasis_filter_type; struct Demodulator { @@ -94,36 +40,30 @@ struct Demodulator { typedef std::function filter_function_type; static const size_t SYMBOL_RATE = 1200; - static const size_t BUFFER_SIZE = 330; typedef BaseDigitalPLL DPLL; size_t sample_rate_; emphasis_filter_type& audio_filter_; - libafsk::BaseAGC agc_; - libafsk::BlockHysteresis input_comparator_; libafsk::FixedDelayLine<40> delay_line_; - IirFilter<8> correlator_filter_; - libafsk::BaseHysteresis output_comparator_; DPLL pll_; + Q15FirFilter lpf_filter_; libafsk::NRZI nrzi_; - hdlc::Decoder hdlc_decoder_; + hdlc::NewDecoder hdlc_decoder_; bool locked_; - float_type buffer_a[audio::ADC_BUFFER_SIZE]; + q15_t buffer_[audio::ADC_BUFFER_SIZE]; Demodulator(size_t sample_rate, emphasis_filter_type& c) : sample_rate_(sample_rate) , audio_filter_(c) - , agc_(.01, .001, 0.3, 1000.0) - , input_comparator_(-0.0005, 0.0005) , delay_line_(sample_rate, 0.000448) - , correlator_filter_(b, a) - , output_comparator_(-0.05, 0.05) , pll_(sample_rate, SYMBOL_RATE) , nrzi_(), hdlc_decoder_(false), locked_(false) - {} + { + lpf_filter_.init(lpf_coeffs); + } - hdlc::IoFrame* operator()(float* samples, size_t len); + hdlc::IoFrame* operator()(q15_t* samples, size_t len); bool locked() const {return locked_;} }; diff --git a/TNC/AudioInput.cpp b/TNC/AudioInput.cpp index dce4b7c..8adb1de 100644 --- a/TNC/AudioInput.cpp +++ b/TNC/AudioInput.cpp @@ -1,4 +1,4 @@ -// Copyright 2015 Rob Riggs +// Copyright 2018-2019 Rob Riggs // All rights reserved. #include "AudioInput.hpp" @@ -99,11 +99,11 @@ extern "C" void startAudioInputTask(void const*) { break; case STREAM_AVERAGE_TWIST_LEVEL: DEBUG("STREAM_AVERAGE_TWIST_LEVEL"); - streamAverageInputTwist(); + // streamAverageInputTwist(); break; case STREAM_INSTANT_TWIST_LEVEL: DEBUG("STREAM_INSTANT_TWIST_LEVEL"); - streamInstantInputTwist(); + // streamInstantInputTwist(); break; case AUTO_ADJUST_INPUT_LEVEL: DEBUG("AUTO_ADJUST_INPUT_LEVEL"); @@ -129,260 +129,42 @@ extern "C" void startAudioInputTask(void const*) { namespace mobilinkd { namespace tnc { namespace audio { /* - -FIR filter designed with -http://t-filter.appspot.com - -sampling frequency: 26400 Hz - -* 0 Hz - 800 Hz - gain = 0 - desired attenuation = -40 dB - actual attenuation = -41.713187739640446 dB - -* 1100 Hz - 2300 Hz - gain = 1 - desired ripple = 3 dB - actual ripple = 1.9403554103597218 dB - -* 2600 Hz - 13200 Hz - gain = 0 - desired attenuation = -40 dB - actual attenuation = -41.713187739640446 dB - -*/ - -#define FILTER_TAP_NUM 121 - -const float taps_0dB_121[] = { - 0.00404434702588704, - -0.0003678805989470367, - -0.0011037474176397869, - -0.0023718433790735397, - -0.004074774206090812, - -0.005873042355767296, - -0.007185024927682025, - -0.00733586918005499, - -0.005849936137673611, - -0.0026340821242355635, - 0.001829866887380395, - 0.006596559367932984, - 0.010513363703436297, - 0.012581963716759209, - 0.012278717176141912, - 0.009711068794837135, - 0.005595156551222992, - 0.0010151465722928203, - -0.0028911100291917724, - -0.00525632756952279, - -0.005713225280738633, - -0.004481173891886628, - -0.0022979447479362165, - -0.00020042687909196258, - 0.0007698191454281245, - -0.00010521900913954391, - -0.0029591195718788473, - -0.00722329412770922, - -0.01171525034180743, - -0.014940096229612041, - -0.015533747313789608, - -0.012735965353880947, - -0.006719733175529481, - 0.0013488096154956725, - 0.00958058819325866, - 0.015905894530456915, - 0.018729580500272548, - 0.01748781569748245, - 0.012897342590446394, - 0.006795493031300682, - 0.0015881037163025886, - -0.0005374299552534915, - 0.0016047331197704602, - 0.007673729209166328, - 0.015724242346878387, - 0.022631203832237833, - 0.024935722640487233, - 0.01989507252801227, - 0.00645078432931977, - -0.014172629267618218, - -0.038442876378037664, - -0.06113274585379875, - -0.07647113797360244, - -0.07957164384983365, - -0.06778356575105521, - -0.04159845430900078, - -0.0048373906972505945, - 0.03598583922416374, - 0.0730150987796154, - 0.09880455186134979, - 0.10804223448811107, - 0.09880455186134979, - 0.0730150987796154, - 0.03598583922416374, - -0.0048373906972505945, - -0.04159845430900078, - -0.06778356575105521, - -0.07957164384983365, - -0.07647113797360244, - -0.06113274585379875, - -0.038442876378037664, - -0.014172629267618218, - 0.006450784329319771, - 0.01989507252801227, - 0.024935722640487233, - 0.022631203832237833, - 0.015724242346878387, - 0.007673729209166332, - 0.0016047331197704602, - -0.0005374299552534915, - 0.0015881037163025886, - 0.0067954930313006805, - 0.012897342590446394, - 0.017487815697482458, - 0.01872958050027255, - 0.015905894530456915, - 0.00958058819325866, - 0.0013488096154956762, - -0.006719733175529481, - -0.012735965353880947, - -0.015533747313789608, - -0.014940096229612034, - -0.01171525034180743, - -0.00722329412770922, - -0.0029591195718788473, - -0.00010521900913954758, - 0.0007698191454281245, - -0.00020042687909196258, - -0.0022979447479362165, - -0.004481173891886626, - -0.005713225280738633, - -0.005256327569522788, - -0.0028911100291917724, - 0.0010151465722928203, - 0.005595156551222992, - 0.009711068794837135, - 0.012278717176141915, - 0.012581963716759209, - 0.010513363703436297, - 0.006596559367932984, - 0.001829866887380395, - -0.0026340821242355635, - -0.005849936137673611, - -0.0073358691800549875, - -0.007185024927682024, - -0.005873042355767296, - -0.004074774206090811, - -0.0023718433790735397, - -0.0011037474176397869, - -0.00036788059894704404, - 0.00404434702588704 -}; - -/* - -FIR filter designed with -http://t-filter.appspot.com - -sampling frequency: 26400 Hz - -* 0 Hz - 600 Hz - gain = 0 - desired attenuation = -40 dB - actual attenuation = -41.59537969202882 dB - -* 1100 Hz - 2300 Hz - gain = 1 - desired ripple = 3 dB - actual ripple = 1.9670775534013671 dB - -* 2800 Hz - 13200 Hz - gain = 0 - desired attenuation = -40 dB - actual attenuation = -41.59537969202882 dB - -*/ - -// #define FILTER_TAP_NUM 73 - -const float taps_0dB_73[] = { - 0.0010893641938196257, - 0.0029403198794405202, - -0.0037231874681753637, - -0.005078116094780293, - -0.008797286521082463, - -0.011317340935878852, - -0.012200463385017889, - -0.01036925371439487, - -0.005637326405238566, - 0.0014334055832832988, - 0.009462055516437227, - 0.016585173167785613, - 0.020968649539195763, - 0.021402512805125434, - 0.017768177789191805, - 0.011189277365350641, - 0.003796773667470304, - -0.0019035128640327481, - -0.003853114272608765, - -0.0012626334798333488, - 0.004945485136075468, - 0.012177421685799305, - 0.016832103112238102, - 0.01536679512873413, - 0.005573647945409955, - -0.012436210925634471, - -0.03581550676890827, - -0.05935854007614169, - -0.07666569528110105, - -0.08180873487685453, - -0.07107700533422828, - -0.0442469908102239, - -0.005044918510227134, - 0.03944927956419565, - 0.0803589200537307, - 0.10908069178917619, - 0.11940367705752576, - 0.1090806917891762, - 0.0803589200537307, - 0.03944927956419565, - -0.005044918510227135, - -0.0442469908102239, - -0.07107700533422828, - -0.08180873487685453, - -0.07666569528110105, - -0.05935854007614169, - -0.03581550676890827, - -0.012436210925634473, - 0.005573647945409955, - 0.015366795128734134, - 0.016832103112238102, - 0.012177421685799305, - 0.004945485136075468, - -0.0012626334798333488, - -0.003853114272608765, - -0.001903512864032745, - 0.003796773667470304, - 0.011189277365350641, - 0.017768177789191805, - 0.021402512805125434, - 0.020968649539195763, - 0.016585173167785607, - 0.009462055516437227, - 0.0014334055832832988, - -0.005637326405238568, - -0.01036925371439487, - -0.012200463385017889, - -0.011317340935878852, - -0.008797286521082463, - -0.005078116094780293, - -0.0037231874681753637, - 0.0029403198794405202, - 0.0010893641938196242 + * Generated with Scipy Filter, 152 coefficients, 1100-2300Hz bandpass, + * Hann window, starting and ending 0 value coefficients removed. + * + * np.array( + * firwin2(152, + * [ + * 0.0, + * 1000.0/(sample_rate/2), + * 1100.0/(sample_rate/2), + * 2350.0/(sample_rate/2), + * 2500.0/(sample_rate/2), + * 1.0 + * ], + * [0,0,1,1,0,0], + * antisymmetric = False, + * window='hann') * 32768, + * dtype=int)[10:-10] + */ +constexpr size_t FILTER_TAP_NUM = 132; +const q15_t bpf_coeffs[] = { + 4, 0, -5, -10, -13, -12, -9, -4, -2, -4, -12, -26, + -41, -52, -51, -35, -3, 39, 83, 117, 131, 118, 83, 36, + -6, -32, -30, -3, 36, 67, 66, 19, -74, -199, -323, -408, + -421, -344, -187, 17, 218, 364, 417, 369, 247, 106, 14, 26, + 166, 407, 676, 865, 866, 605, 68, -675, -1484, -2171, -2547, -2471, +-1895, -882, 394, 1692, 2747, 3337, 3337, 2747, 1692, 394, -882, -1895, +-2471, -2547, -2171, -1484, -675, 68, 605, 866, 865, 676, 407, 166, + 26, 14, 106, 247, 369, 417, 364, 218, 17, -187, -344, -421, + -408, -323, -199, -74, 19, 66, 67, 36, -3, -30, -32, -6, + 36, 83, 118, 131, 117, 83, 39, -3, -35, -51, -52, -41, + -26, -12, -4, -2, -4, -9, -12, -13, -10, -5, 0, 4, }; uint32_t adc_buffer[ADC_BUFFER_SIZE]; // Two samples per element. -typedef FirFilter audio_filter_type; +typedef Q15FirFilter audio_filter_type; audio_filter_type audio_filter; @@ -412,12 +194,13 @@ mobilinkd::tnc::afsk1200::Demodulator& getDemod3(const TFirCoefficients<9>& f) { return instance; } +q15_t normalized[ADC_BUFFER_SIZE]; void demodulatorTask() { DEBUG("enter demodulatorTask"); - audio_filter.init(taps_0dB_121); + audio_filter.init(bpf_coeffs); // rx_twist is 6dB for discriminator input and 0db for de-emphasized input. auto twist = kiss::settings().rx_twist; @@ -437,7 +220,6 @@ void demodulatorTask() { bool dcd_status{false}; while (true) { - mobilinkd::tnc::gpio::LD3::off(); osEvent peek = osMessagePeek(audioInputQueueHandle, 0); if (peek.status == osEventMessage) break; @@ -445,14 +227,15 @@ void demodulatorTask() { if (evt.status != osEventMessage) { continue; } + ++counter; - mobilinkd::tnc::gpio::LD3::on(); auto block = (adc_pool_type::chunk_type*) evt.value.p; auto samples = (int16_t*) block->buffer; - float* audio = audio_filter(samples); + arm_offset_q15(samples, 0 - virtual_ground, normalized, ADC_BUFFER_SIZE); adcPool.deallocate(block); + q15_t* audio = audio_filter(normalized); #if 1 frame = demod1(audio, ADC_BUFFER_SIZE); @@ -463,11 +246,11 @@ void demodulatorTask() { last_fcs = save_fcs; last_counter = counter; } else { - hdlc::ioFramePool().release(frame); + hdlc::release(frame); } } else { - hdlc::ioFramePool().release(frame); + hdlc::release(frame); } } #endif @@ -481,11 +264,11 @@ void demodulatorTask() { last_fcs = save_fcs; last_counter = counter; } else { - hdlc::ioFramePool().release(frame); + hdlc::release(frame); } } else { - hdlc::ioFramePool().release(frame); + hdlc::release(frame); } } #endif @@ -499,11 +282,11 @@ void demodulatorTask() { last_fcs = save_fcs; last_counter = counter; } else { - hdlc::ioFramePool().release(frame); + hdlc::release(frame); } } else { - hdlc::ioFramePool().release(frame); + hdlc::release(frame); } } #endif @@ -538,8 +321,8 @@ void streamLevels(uint32_t channel, uint8_t cmd) { uint16_t count = 0; uint32_t accum = 0; - uint16_t min = std::numeric_limits::max(); - uint16_t max = std::numeric_limits::min(); + uint16_t vmin = std::numeric_limits::max(); + uint16_t vmax = std::numeric_limits::min(); while (count < 2640) { osEvent evt = osMessageGet(adcInputQueueHandle, osWaitForever); @@ -548,30 +331,30 @@ void streamLevels(uint32_t channel, uint8_t cmd) { count += ADC_BUFFER_SIZE; auto block = (adc_pool_type::chunk_type*) evt.value.p; - uint16_t* start = (uint16_t*) block->buffer; - uint16_t* end = start + ADC_BUFFER_SIZE; + auto start = (uint16_t*) block->buffer; + auto end = start + ADC_BUFFER_SIZE; - min = std::min(min, *std::min_element(start, end)); - max = std::max(max, *std::max_element(start, end)); + vmin = std::min(vmin, *std::min_element(start, end)); + vmax = std::max(vmax, *std::max_element(start, end)); accum = std::accumulate(start, end, accum); adcPool.deallocate(block); } - uint16_t pp = (max - min) << 4; + uint16_t pp = (vmax - vmin) << 4; uint16_t avg = (accum / count) << 4; - min <<= 4; - max <<= 4; + vmin <<= 4; + vmax <<= 4; data[0] = cmd; data[1] = (pp >> 8) & 0xFF; // Vpp data[2] = (pp & 0xFF); data[3] = (avg >> 8) & 0xFF; // Vavg (DC level) data[4] = (avg & 0xFF); - data[5] = (min >> 8) & 0xFF; // Vmin - data[6] = (min & 0xFF); - data[7] = (max >> 8) & 0xFF; // Vmax - data[8] = (max & 0xFF); + data[5] = (vmin >> 8) & 0xFF; // Vmin + data[6] = (vmin & 0xFF); + data[7] = (vmax >> 8) & 0xFF; // Vmax + data[8] = (vmax & 0xFF); ioport->write(data, 9, 6, 10); } @@ -600,11 +383,9 @@ levels_type readLevels(uint32_t channel, uint32_t samples) { if (evt.status != osEventMessage) continue; auto block = (adc_pool_type::chunk_type*) evt.value.p; - auto start = reinterpret_cast(block->buffer); + auto start = (uint16_t*) block->buffer; auto end = start + ADC_BUFFER_SIZE; -// if (count == 0) for (auto it = start; it != end; ++it) DEBUG("%hu\n", *it); - vmin = std::min(vmin, *std::min_element(start, end)); vmax = std::max(vmax, *std::max_element(start, end)); accum = std::accumulate(start, end, accum); @@ -623,6 +404,7 @@ levels_type readLevels(uint32_t channel, uint32_t samples) { return levels_type(pp, avg, vmin, vmax); } + /** * This provides 100Hz resolution to the Goerztel filter. */ @@ -649,22 +431,22 @@ float readTwist() startADC(channel); - for (uint32_t i = 0; i != AVG_SAMPLES; ++i) { - + for (uint32_t i = 0; i != AVG_SAMPLES; ++i) + { uint32_t count = 0; - while (count < TWIST_SAMPLE_SIZE) { - + while (count < TWIST_SAMPLE_SIZE) + { osEvent evt = osMessageGet(adcInputQueueHandle, osWaitForever); if (evt.status != osEventMessage) continue; - count += ADC_BUFFER_SIZE; - - auto block = (adc_pool_type::chunk_type*) evt.value.v; + auto block = (adc_pool_type::chunk_type*) evt.value.p; uint16_t* data = (uint16_t*) block->buffer; gf1200(data, ADC_BUFFER_SIZE); gf2200(data, ADC_BUFFER_SIZE); adcPool.deallocate(block); + + count += ADC_BUFFER_SIZE; } g1200 += (gf1200 / count); @@ -733,7 +515,7 @@ void pollInputTwist() count += ADC_BUFFER_SIZE; - auto block = (adc_pool_type::chunk_type*) evt.value.v; + auto block = (adc_pool_type::chunk_type*) evt.value.p; uint16_t* data = (uint16_t*) block->buffer; gf1200(data, ADC_BUFFER_SIZE); gf2200(data, ADC_BUFFER_SIZE); @@ -768,6 +550,7 @@ void pollInputTwist() DEBUG("exit pollInputTwist"); } +#if 0 void streamAverageInputTwist() { DEBUG("enter streamAverageInputTwist"); @@ -820,7 +603,8 @@ void streamAverageInputTwist() g2700 += 10.0 * log10(gf2700); char* buffer = 0; - int len = asiprintf( + // @TODO: Make re-entrant printf work (or convert to fixed-point). + int len = asiprintf_r( &buffer, "_%f, %f, %f, %f, %f\r\n", g700 / acount, @@ -884,7 +668,8 @@ void streamInstantInputTwist() } char* buffer = 0; - int len = asiprintf( + // @TODO: Make re-entrant printf work (or convert to fixed-point). + int len = asiprintf_r( &buffer, "_%f, %f, %f, %f, %f\r\n", 10.0 * log10(gf700), @@ -909,6 +694,7 @@ void streamInstantInputTwist() stopADC(); DEBUG("exit streamInstantInputTwist"); } +#endif void streamAmplifiedInputLevels() { DEBUG("enter streamAmplifiedInputLevels"); @@ -942,6 +728,7 @@ void pollAmplifiedInputLevel() { DEBUG("exit pollAmplifiedInputLevel"); } +#if 0 void stop() { osDelay(100); #if 0 @@ -971,5 +758,6 @@ void stop() { DEBUG("Wake"); #endif } +#endif }}} // mobilinkd::tnc::audio diff --git a/TNC/AudioInput.hpp b/TNC/AudioInput.hpp index d901180..9e2d9cb 100644 --- a/TNC/AudioInput.hpp +++ b/TNC/AudioInput.hpp @@ -28,10 +28,10 @@ void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef*); * This is the long-running audio input task/thread. The ADC can be * connected to one of 2 inputs: * - * - AMPLIFIED_AUDIO_IN, ADC1_IN7 -- this is the input used by the demodulator. + * - AUDIO_IN, ADC1, CHANNEL_8 -- this is the input used by the demodulator. * - BATTERY_VOLTAGE -- this is 1/2 raw battery voltage (nominal 1.6-2.1V). * This input should not be enabled unless BAT_DIVIDER is enabled and - * pulled low. + * pulled low. *** NOT USED on NucleoTNC *** * * These inputs can be measured in a couple of different ways: * - Vavg -- for the average DC level of the signal @@ -61,7 +61,6 @@ void TNC_Error_Handler(int dev, int err); #ifdef __cplusplus } -#endif namespace mobilinkd { namespace tnc { namespace audio { @@ -90,9 +89,9 @@ extern uint32_t adc_buffer[]; // Two int16_t samples per element. inline void stopADC() { if (HAL_ADC_Stop_DMA(&hadc1) != HAL_OK) - Error_Handler(); + CxxErrorHandler(); if (HAL_TIM_Base_Stop(&htim6) != HAL_OK) - Error_Handler(); + CxxErrorHandler(); } inline void startADC(uint32_t channel) { @@ -101,23 +100,23 @@ inline void startADC(uint32_t channel) { sConfig.Channel = channel; sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SingleDiff = ADC_SINGLE_ENDED; - sConfig.SamplingTime = ADC_SAMPLETIME_24CYCLES_5; + sConfig.SamplingTime = ADC_SAMPLETIME_12CYCLES_5; sConfig.OffsetNumber = ADC_OFFSET_NONE; sConfig.Offset = 0; if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) - Error_Handler(); + CxxErrorHandler(); if (HAL_TIM_Base_Start(&htim6) != HAL_OK) - Error_Handler(); + CxxErrorHandler(); if (HAL_ADC_Start_DMA(&hadc1, adc_buffer, ADC_BUFFER_SIZE * 2) != HAL_OK) - Error_Handler(); + CxxErrorHandler(); } inline void restartADC() { if (HAL_TIM_Base_Start(&htim6) != HAL_OK) - Error_Handler(); + CxxErrorHandler(); if (HAL_ADC_Start_DMA(&hadc1, adc_buffer, ADC_BUFFER_SIZE * 2) != HAL_OK) - Error_Handler(); + CxxErrorHandler(); } /// Vpp, Vavg, Vmin, Vmax @@ -138,4 +137,6 @@ void streamInstantInputTwist(); }}} // mobilinkd::tnc::audio +#endif // __cplusplus + #endif // MOBILINKD__TNC__AUDIO__INPUT_HPP_ diff --git a/TNC/AudioLevel.cpp b/TNC/AudioLevel.cpp index bb0bcaf..7e52ea0 100644 --- a/TNC/AudioLevel.cpp +++ b/TNC/AudioLevel.cpp @@ -1,12 +1,13 @@ -// Copyright 2017 Rob Riggs +// Copyright 2018 Rob Riggs // All rights reserved. #include "AudioLevel.hpp" #include "AudioInput.hpp" -#include "ModulatorTask.hpp" #include "KissHardware.hpp" +#include "ModulatorTask.hpp" #include "GPIO.hpp" #include "Led.h" +#include "ModulatorTask.hpp" #include "main.h" #include "stm32l4xx_hal.h" @@ -23,27 +24,23 @@ extern DAC_HandleTypeDef hdac1; namespace mobilinkd { namespace tnc { namespace audio { -uint16_t virtual_ground; -float i_vgnd; - -void setAudioPins(void) { - GPIO_InitTypeDef GPIO_InitStruct; - - GPIO_InitStruct.Pin = AUDIO_IN_Pin; - GPIO_InitStruct.Mode = GPIO_MODE_ANALOG_ADC_CONTROL; - GPIO_InitStruct.Pull = GPIO_NOPULL; - HAL_GPIO_Init(AUDIO_IN_GPIO_Port, &GPIO_InitStruct); -} +uint16_t virtual_ground{0}; +float i_vgnd{0.0f}; void set_input_gain(int level) { uint32_t dc_offset{}; + // Stop and de-init the op amp before changing its state. if (HAL_OPAMP_Stop(&hopamp1) != HAL_OK) - Error_Handler(); + CxxErrorHandler(); if (HAL_OPAMP_DeInit(&hopamp1) != HAL_OK) - Error_Handler(); + CxxErrorHandler(); + level = std::max(0, level); + level = std::min(4, level); + + // Adjust configuration and, if PGA, gain. switch (level) { case 0: // 0dB hopamp1.Init.Mode = OPAMP_FOLLOWER_MODE; @@ -70,20 +67,18 @@ void set_input_gain(int level) dc_offset = 128; break; default: - Error_Handler(); + CxxErrorHandler(); } + // Adjust DC offset. It is different for each gain setting. if (HAL_DAC_SetValue(&hdac1, DAC_CHANNEL_2, DAC_ALIGN_12B_R, dc_offset) != HAL_OK) - Error_Handler(); + CxxErrorHandler(); + // Init and start the op amp after the change. if (HAL_OPAMP_Init(&hopamp1) != HAL_OK) - Error_Handler(); - + CxxErrorHandler(); if (HAL_OPAMP_Start(&hopamp1)!= HAL_OK) - Error_Handler(); - -// setAudioPins(); - osDelay(300); + CxxErrorHandler(); } int adjust_input_gain() __attribute__((noinline)); @@ -93,15 +88,17 @@ int adjust_input_gain() { INFO("\nAdjusting input gain...\n"); int gain{0}; + uint16_t vpp, vavg, vmin, vmax; set_input_gain(gain); - auto [vpp, vavg, vmin, vmax] = readLevels(AUDIO_IN); + osDelay(100); // Need time for DC offset to settle. + + std::tie(vpp, vavg, vmin, vmax) = readLevels(AUDIO_IN); INFO("\nVpp = %" PRIu16 ", Vavg = %" PRIu16 "\n", vpp, vavg); INFO("\nVmin = %" PRIu16 ", Vmax = %" PRIu16 ", setting = %d\n", vmin, vmax, gain); while (gain == 0 and (vmax == vref or vmin == 0)) { - gpio::LED_OTHER::toggle(); std::tie(vpp, vavg, vmin, vmax) = readLevels(AUDIO_IN); INFO("\nVpp = %" PRIu16 ", Vavg = %" PRIu16 "\n", vpp, vavg); @@ -117,6 +114,8 @@ int adjust_input_gain() { else gain = 0; set_input_gain(gain); + osDelay(100); // Need time for DC offset to settle. + std::tie(vpp, vavg, vmin, vmax) = readLevels(AUDIO_IN); INFO("\nVpp = %" PRIu16 ", Vavg = %" PRIu16 "\n", vpp, vavg); INFO("\nVmin = %" PRIu16 ", Vmax = %" PRIu16 ", setting = %d\n", vmin, vmax, gain); @@ -124,17 +123,12 @@ int adjust_input_gain() { virtual_ground = vavg; i_vgnd = 1.0 / virtual_ground; - gpio::LED_OTHER::on(); - return gain; } + void autoAudioInputLevel() { - - led_dcd_on(); - led_tx_on(); - INFO("autoInputLevel"); mobilinkd::tnc::kiss::settings().input_gain = adjust_input_gain(); @@ -144,25 +138,26 @@ void autoAudioInputLevel() else if (rx_twist > 9) rx_twist = 9; INFO("TWIST = %ddB", rx_twist); mobilinkd::tnc::kiss::settings().rx_twist = rx_twist; + mobilinkd::tnc::kiss::settings().announce_input_settings(); mobilinkd::tnc::kiss::settings().update_crc(); - mobilinkd::tnc::kiss::settings().store(); - - led_tx_off(); - led_dcd_off(); } /** - * Set the audio input levels from the values stored in EEPROM. + * Set the audio input levels from the values stored in EEPROM. Then + * analyze the input levels to set the VGND level. */ void setAudioInputLevels() { // setAudioPins(); INFO("Setting input gain: %d", kiss::settings().input_gain); set_input_gain(kiss::settings().input_gain); - auto [vpp, vavg, vmin, vmax] = readLevels(AUDIO_IN); + + uint16_t vpp, vavg, vmin, vmax; + + std::tie(vpp, vavg, vmin, vmax) = readLevels(AUDIO_IN); INFO("Vpp = %" PRIu16 ", Vavg = %" PRIu16, vpp, vavg); - INFO("Vmin = %" PRIu16 ", Vmax = %" PRIu16 ", setting = %d", vmin, vmax, 2); + INFO("Vmin = %" PRIu16 ", Vmax = %" PRIu16, vmin, vmax); virtual_ground = vavg; i_vgnd = 1.0 / virtual_ground; } @@ -194,7 +189,8 @@ std::tuple computeLogAudioLevel(int16_t level) */ void setAudioOutputLevel() { - auto [l, r] = computeLogAudioLevel(kiss::settings().output_gain); + uint16_t l,r; + std::tie(l, r) = computeLogAudioLevel(kiss::settings().output_gain); INFO("Setting output gain: %" PRIi16 " (log %" PRIi16 " + %" PRIi16 ")", kiss::settings().output_gain, l, r); diff --git a/TNC/DigitalPLL.cpp b/TNC/DigitalPLL.cpp deleted file mode 100644 index 861a473..0000000 --- a/TNC/DigitalPLL.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2017 Rob Riggs -// All rights reserved. - -#include "DigitalPLL.hpp" - -namespace mobilinkd { namespace tnc { - -namespace pll { - -// Loop low-pass filter taps (64Hz Bessel) -float loop_b[] = { - 0.144668495309, - 0.144668495309, -}; -float loop_a[] = { - 1.0, - -0.710663009381, -}; - -// Lock low-pass filter taps (40Hz Bessel) -float lock_b[] = { - 0.0951079834025, - 0.0951079834025, -}; -float lock_a[] = { - 1.0, - -0.809784033195, -}; - -} // pll - -}} // mobilinkd::tnc diff --git a/TNC/DigitalPLL.hpp b/TNC/DigitalPLL.hpp index 564e2c4..802dd28 100644 --- a/TNC/DigitalPLL.hpp +++ b/TNC/DigitalPLL.hpp @@ -3,24 +3,20 @@ #include "Hysteresis.hpp" #include "IirFilter.hpp" +#include "FirFilter.hpp" #include "arm_math.h" #include #include - +#include +#include #include namespace mobilinkd { namespace tnc { namespace pll { -// Loop low-pass filter taps (64Hz Bessel) -extern float loop_b[2]; -extern float loop_a[2]; -// Lock low-pass filter taps (40Hz Bessel) -extern float lock_b[2]; -extern float lock_a[2]; template struct PLLResult { @@ -29,13 +25,32 @@ struct PLLResult { bool locked; }; +// Lock low-pass filter taps (80Hz Bessel) +// scipy.signal: +// b, a = bessel(4, [80.0/(1200/2)], 'lowpass') +// +const std::array lock_b = { + 1.077063e-03,4.308253e-03,6.462379e-03,4.308253e-03,1.077063e-03, +}; +const std::array lock_a = { + 1.000000e+00,-2.774567e+00,2.962960e+00,-1.437990e+00,2.668296e-01, +}; + +// 64 Hz loop filter. +// scipy.signal: +// loop_coeffs = firwin(9, [64.0/(1200/2)], width = None, +// pass_zero = True, scale = True, window='hann') +// +const std::array loop_coeffs = { +// 0.08160962754214955, 0.25029850550446403, 0.3361837339067726, 0.2502985055044641, 0.08160962754214969 + 3.196252e-02,1.204223e-01,2.176819e-01,2.598666e-01,2.176819e-01,1.204223e-01,3.196252e-02 +}; + } // pll template -struct BaseDigitalPLL { - - static const size_t N = 16; - +struct BaseDigitalPLL +{ typedef T float_type; typedef pll::PLLResult result_type; @@ -44,9 +59,9 @@ struct BaseDigitalPLL { float_type sps_; ///< Samples per symbol float_type limit_; ///< Samples per symbol / 2 libafsk::BaseHysteresis lock_; - IirFilter<2> loop_filter_; - IirFilter<2> lock_filter_; - + FirFilter<1, 7> loop_filter_{pll::loop_coeffs.begin()}; + IirFilter<5> lock_filter_{pll::lock_b, pll::lock_a}; + bool last_; float_type count_; @@ -57,20 +72,17 @@ struct BaseDigitalPLL { BaseDigitalPLL(float_type sample_rate, float_type symbol_rate) : sample_rate_(sample_rate), symbol_rate_(symbol_rate) , sps_(sample_rate / symbol_rate) - , limit_(sps_ / 2.0f) - , lock_(sps_ * 0.025, sps_ * .15, 1, 0) - , loop_filter_(pll::loop_b, pll::loop_a) - , lock_filter_(pll::lock_b, pll::lock_a) + , limit_(sps_ / float_type(2.0)) + , lock_(sps_ * float_type(0.03), sps_ * float_type(0.15), 1, 0) , last_(false), count_(0), sample_(false) , jitter_(0.0), bits_(1) {} result_type operator()(bool input) { - sample_ = false; - if (input != last_ or bits_ > 127) { + if (input != last_ or bits_ > 16) { // Record transition. last_ = input; @@ -78,11 +90,15 @@ struct BaseDigitalPLL { count_ -= sps_; } - float_type offset = count_ / bits_; - float_type jitter = loop_filter_(offset); - jitter_ = lock_filter_(abs(offset)); + // Force lock off when no stimulus is present (squelch closed). + const float_type adjust = bits_ > 16 ? 5.0 : 0.0; - count_ -= jitter * sps_ * 0.023f; + const float_type offset = count_ / bits_; + const float_type jitter = loop_filter_(offset); + const float_type abs_offset = std::abs(offset) + adjust; + jitter_ = lock_filter_(abs_offset); + + count_ -= jitter / 2; bits_ = 1; } else { diff --git a/TNC/Filter.cpp b/TNC/Filter.cpp deleted file mode 100644 index 71d4c9a..0000000 --- a/TNC/Filter.cpp +++ /dev/null @@ -1,460 +0,0 @@ -#include "Filter.h" - -#include - -const float filter_taps[13][FILTER_TAP_NUM] = { - { - // 0dB Hamming (1693.3) - 0.0092140576627, - 0.00768806806252, - 0.00498340794238, - -0.00171692105003, - -0.0142537522489, - -0.0319314833665, - -0.0509364246735, - -0.0650918044189, - -0.0677825846114, - -0.0544018818, - -0.0244193735639, - 0.0177467007765, - 0.0634791554618, - 0.102191070678, - 0.124356477862, - 0.124356477862, - 0.102191070678, - 0.0634791554618, - 0.0177467007765, - -0.0244193735639, - -0.0544018818, - -0.0677825846114, - -0.0650918044189, - -0.0509364246735, - -0.0319314833665, - -0.0142537522489, - -0.00171692105003, - 0.00498340794238, - 0.00768806806252, - 0.0092140576627, - }, - { - // 1dB Hamming (1749.2) - 0.00984360063296, - 0.00907660156831, - 0.00733802154094, - 0.00165885599255, - -0.01027803496, - -0.028288428514, - -0.0487844743735, - -0.0653441331879, - -0.0706814843758, - -0.0593532384181, - -0.0301832127721, - 0.0125781304703, - 0.0599187881831, - 0.100456125433, - 0.123807723158, - 0.123807723158, - 0.100456125433, - 0.0599187881831, - 0.0125781304703, - -0.0301832127721, - -0.0593532384181, - -0.0706814843758, - -0.0653441331879, - -0.0487844743735, - -0.028288428514, - -0.01027803496, - 0.00165885599255, - 0.00733802154094, - 0.00907660156831, - 0.00984360063296, - }, - { - // 2dB Hamming (1805.6) - 0.0101071938454, - 0.0101751907488, - 0.00949571140771, - 0.00500536790767, - -0.00608985117047, - -0.0241824665512, - -0.0460028160886, - -0.0649487308381, - -0.0730641303456, - -0.0640057046058, - -0.0358585166096, - 0.00736846731409, - 0.0562886910258, - 0.098688560769, - 0.12326772148, - 0.12326772148, - 0.098688560769, - 0.0562886910258, - 0.00736846731409, - -0.0358585166096, - -0.0640057046058, - -0.0730641303456, - -0.0649487308381, - -0.0460028160886, - -0.0241824665512, - -0.00608985117047, - 0.00500536790767, - 0.00949571140771, - 0.0101751907488, - 0.0101071938454, - }, - { - // 3dB Hamming (1861.5) - 0.00999732596792, - 0.0109364331138, - 0.0113663405297, - 0.00819091080479, - -0.00184492788726, - -0.0197616766146, - -0.0426984518018, - -0.0639498932619, - -0.0749094986595, - -0.0682876192213, - -0.0413467222516, - 0.00222026614421, - 0.0526805390514, - 0.0969649735461, - 0.1228029701, - 0.1228029701, - 0.0969649735461, - 0.0526805390514, - 0.00222026614421, - -0.0413467222516, - -0.0682876192213, - -0.0749094986595, - -0.0639498932619, - -0.0426984518018, - -0.0197616766146, - -0.00184492788726, - 0.00819091080479, - 0.0113663405297, - 0.0109364331138, - 0.00999732596792, - }, - { - // 4dB Hamming (1917.0) - 0.00953170942079, - 0.0113523463692, - 0.012917947221, - 0.0111611200433, - 0.00238829862135, - -0.0150956296368, - -0.0389313364275, - -0.0623942365343, - -0.07625339099, - -0.0722276074899, - -0.0466690276902, - -0.00287524097904, - 0.0491038547992, - 0.095314261046, - 0.122454987694, - 0.122454987694, - 0.095314261046, - 0.0491038547992, - -0.00287524097904, - -0.0466690276902, - -0.0722276074899, - -0.07625339099, - -0.0623942365343, - -0.0389313364275, - -0.0150956296368, - 0.00238829862135, - 0.0111611200433, - 0.012917947221, - 0.0113523463692, - 0.00953170942079, - }, - { - // 5dB Hamming (1972.5) - 0.00872938144139, - 0.011420928511, - 0.0141310255503, - 0.0138829549523, - 0.00657217070647, - -0.0102167866884, - -0.0347251133165, - -0.0603020097307, - -0.0771226785494, - -0.0758651302103, - -0.0518731312861, - -0.00796039989017, - 0.0455363026357, - 0.0937399215189, - 0.122245216908, - 0.122245216908, - 0.0937399215189, - 0.0455363026357, - -0.00796039989017, - -0.0518731312861, - -0.0758651302103, - -0.0771226785494, - -0.0603020097307, - -0.0347251133165, - -0.0102167866884, - 0.00657217070647, - 0.0138829549523, - 0.0141310255503, - 0.011420928511, - 0.00872938144139, - }, - { - // 6dB Hamming (2027.0) - 0.00763984171566, - 0.0111488308293, - 0.0149655370284, - 0.0162631703876, - 0.0105693777806, - -0.00527673382432, - -0.0302054840484, - -0.0577401237325, - -0.077513860058, - -0.079139201575, - -0.0568701896276, - -0.0129510233888, - 0.0420349410452, - 0.0922665002, - 0.122176947883, - 0.122176947883, - 0.0922665002, - 0.0420349410452, - -0.0129510233888, - -0.0568701896276, - -0.079139201575, - -0.077513860058, - -0.0577401237325, - -0.0302054840484, - -0.00527673382432, - 0.0105693777806, - 0.0162631703876, - 0.0149655370284, - 0.0111488308293, - 0.00763984171566, - }, - { - // 7dB Cosine (1986.0) - 0.00460770532874, - 0.0162873765157, - 0.0251984580718, - 0.0248176615108, - 0.0117845748105, - -0.0125423969002, - -0.0421426113528, - -0.0680353327511, - -0.0811090126152, - -0.0751714219734, - -0.0492188545506, - -0.00815365116672, - 0.038335087686, - 0.0784276419333, - 0.101570441881, - 0.101570441881, - 0.0784276419333, - 0.038335087686, - -0.00815365116672, - -0.0492188545506, - -0.0751714219734, - -0.0811090126152, - -0.0680353327511, - -0.0421426113528, - -0.0125423969002, - 0.0117845748105, - 0.0248176615108, - 0.0251984580718, - 0.0162873765157, - 0.00460770532874, - }, - { - // 8dB Cosine (2021.0, 2021.1) - 0.00420597097315, - 0.0159601020805, - 0.0260190570929, - 0.0273151975444, - 0.0157146317097, - -0.00808663238039, - -0.0383509470879, - -0.0659043862717, - -0.0810670338405, - -0.0769492259504, - -0.0520051674945, - -0.0109566710433, - 0.0362780863637, - 0.0773640302855, - 0.101184010968, - 0.101184010968, - 0.0773640302855, - 0.0362780863637, - -0.0109566710433, - -0.0520051674945, - -0.0769492259504, - -0.0810670338405, - -0.0659043862717, - -0.0383509470879, - -0.00808663238039, - 0.0157146317097, - 0.0273151975444, - 0.0260190570929, - 0.0159601020805, - 0.00420597097315, - - }, - { - // 9dB Hamming (2186.5) - 0.00308518474955, - 0.00850291315071, - 0.0151934174423, - 0.0211164164746, - 0.0210280537958, - 0.00946135065651, - -0.0151061442669, - -0.0474011160773, - -0.0758516294354, - -0.0867915071262, - -0.0707058729109, - -0.0275514181864, - 0.0316531744803, - 0.0881474710462, - 0.122515729369, - 0.122515729369, - 0.0881474710462, - 0.0316531744803, - -0.0275514181864, - -0.0707058729109, - -0.0867915071262, - -0.0758516294354, - -0.0474011160773, - -0.0151061442669, - 0.00946135065651, - 0.0210280537958, - 0.0211164164746, - 0.0151934174423, - 0.00850291315071, - 0.00308518474955, - }, - { - // 10dB Cosine (2087.0, 2087.1) - 0.003297704105, - 0.0148243949961, - 0.0268172892254, - 0.0313287864329, - 0.0227440831014, - 0.00036329489673, - -0.0307896841323, - -0.061300901507, - -0.0804397189579, - -0.0799281940065, - -0.0570830747612, - -0.0161917885552, - 0.0324263666501, - 0.0754301963068, - 0.100574174183, - 0.100574174183, - 0.0754301963068, - 0.0324263666501, - -0.0161917885552, - -0.0570830747612, - -0.0799281940065, - -0.0804397189579, - -0.061300901507, - -0.0307896841323, - 0.00036329489673, - 0.0227440831014, - 0.0313287864329, - 0.0268172892254, - 0.0148243949961, - 0.003297704105, - }, - { - // 11dB Cosine (2119.0, 2119.1) - 0.00279757679371, - 0.0140443163212, - 0.0268514195956, - 0.0329271588621, - 0.0259396342176, - 0.00444730813715, - -0.0269594711541, - -0.0588146601004, - -0.0798925711826, - -0.0812085944654, - -0.0594715656311, - -0.01871136648, - 0.0305735278948, - 0.0745360339334, - 0.100349256713, - 0.100349256713, - 0.0745360339334, - 0.0305735278948, - -0.01871136648, - -0.0594715656311, - -0.0812085944654, - -0.0798925711826, - -0.0588146601004, - -0.0269594711541, - 0.00444730813715, - 0.0259396342176, - 0.0329271588621, - 0.0268514195956, - 0.0140443163212, - 0.00279757679371, - }, - { - // 12dB Cosine (2150,2151) - 0.0022752196356, - 0.0131417834537, - 0.0266616639804, - 0.0342658094422, - 0.028925742996, - 0.00843279814311, - -0.0231061648363, - -0.0562188716332, - -0.0792056020955, - -0.0823667306435, - -0.0617763153975, - -0.0211791883525, - 0.0287608072456, - 0.0736871278906, - 0.100177853829, - 0.100177853829, - 0.0736871278906, - 0.0287608072456, - -0.0211791883525, - -0.0617763153975, - -0.0823667306435, - -0.0792056020955, - -0.0562188716332, - -0.0231061648363, - 0.00843279814311, - 0.028925742996, - 0.0342658094422, - 0.0266616639804, - 0.0131417834537, - 0.0022752196356, - } -}; - -void Filter_init(Filter* f, const float* taps) { - for (size_t i = 0; i != FILTER_TAP_NUM; ++i) { - f->history[i] = 0; - f->taps[i] = taps[i]; // initialize ccmram. - } - f->last_index = 0; -} - -void Filter_put(Filter* f, float input) { - f->history[f->last_index++] = input; - if (f->last_index == FILTER_TAP_NUM) - f->last_index = 0; -} - -float Filter_get(Filter* f) { - float acc = 0; - int index = f->last_index, i; - for (i = 0; i < FILTER_TAP_NUM; ++i) { - index = index != 0 ? index - 1 : FILTER_TAP_NUM - 1; - acc += f->history[index] * f->taps[i]; - }; - return acc; -} diff --git a/TNC/Filter.h b/TNC/Filter.h deleted file mode 100644 index c84010e..0000000 --- a/TNC/Filter.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef FILTER_H_ -#define FILTER_H_ - -/* - -FIR filter designed with - http://t-filter.appspot.com - -sampling frequency: 20000 Hz - -* 0 Hz - 800 Hz - gain = 0 - desired attenuation = -40 dB - actual attenuation = -40.37597293671087 dB - -* 1100 Hz - 2300 Hz - gain = 1 - desired ripple = 2 dB - actual ripple = 1.474114871585135 dB - -* 2600 Hz - 10000 Hz - gain = 0 - desired attenuation = -40 dB - actual attenuation = -40.37597293671087 dB - -*/ - -#define FILTER_TAP_NUM 31 - -extern const float filter_taps[13][FILTER_TAP_NUM]; - -typedef struct { - float taps[FILTER_TAP_NUM]; - float history[FILTER_TAP_NUM]; - unsigned int last_index; -} Filter; - -void Filter_init(Filter* f, const float* taps); -void Filter_put(Filter* f, float input); -float Filter_get(Filter* f); - -#endif diff --git a/TNC/Filter.hpp b/TNC/Filter.hpp deleted file mode 100644 index 42bf054..0000000 --- a/TNC/Filter.hpp +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2015 Rob Riggs -// All rights reserved. - -#ifndef MOBILINKD__TNC__FILTER_H_ -#define MOBILINKD__TNC__FILTER_H_ - -namespace mobilinkd { namespace tnc { - -struct Filter { - virtual float operator()(float) = 0; - virtual ~Filter() {} -}; - -}} // mobilinkd::tnc - -#endif // MOBILINKD__TNC__FILTER_H_ diff --git a/TNC/FilterCoefficients.hpp b/TNC/FilterCoefficients.hpp index 8d16667..5711dfc 100644 --- a/TNC/FilterCoefficients.hpp +++ b/TNC/FilterCoefficients.hpp @@ -9,28 +9,6 @@ namespace mobilinkd { namespace tnc { namespace filter { -const TIirCoefficients<2> dB_3 = { - {0.148416305668, 0.148416305668}, - {1.0, -0.703167388663} -}; - -const TIirCoefficients<2> dB0 = {{1.0, 1.0}, {1.0, 1.0}}; - -const TIirCoefficients<2> dB3 = { - {0.821330488584, -0.821330488584}, - {1.0, -0.642660977168} -}; - -const TIirCoefficients<3> dB6 = { - {0.704863714497, -1.40972742899, 0.704863714497}, - {1.0, -1.3445352861, 0.474919571889} -}; - -const TIirCoefficients<4> dB9= { - {0.605034079053, -1.81510223716, 1.81510223716, -0.605034079053}, - {1.0, -2.04985913806, 1.44420376126, -0.346209733102} -}; - namespace fir { // 1200Hz = -12dB, 2200Hz = 0dB; 3653Hz cutoff, 4.93 gain; cosine. @@ -341,65 +319,6 @@ const TFirCoefficients<9>* AfskFilters[] = { &dB12 }; - -#if 0 - -const TFirCoefficients<9> dB_3 = { - { 0.0190268296698, - 0.0720836132753, - 0.131839958497, - 0.178778602372, - 0.196541992372, - 0.178778602372, - 0.131839958497, - 0.0720836132753, - 0.0190268296698, - }, -}; - -const TFirCoefficients<9> dB3 = { - { - -0.0137747043237, - -0.0496069821386, - -0.0880620689921, - -0.11751320055, - 0.869433181254, - -0.11751320055, - -0.0880620689921, - -0.0496069821386, - -0.0137747043237, - } -}; - -const TFirCoefficients<9> dB6 = { - { - -0.00808574330071, - -0.0502334418895, - -0.115447610972, - -0.175051834084, - 0.796496156597, - -0.175051834084, - -0.115447610972, - -0.0502334418895, - -0.00808574330071, - } -}; - -const TFirCoefficients<9> dB9= { - { -0.000653382869306, - -0.0386591907088, - -0.121362869, - -0.208082569133, - 0.750548984054, - -0.208082569133, - -0.121362869, - -0.0386591907088, - -0.000653382869306, - } -}; -#endif - - } // fir diff --git a/TNC/FirFilter.hpp b/TNC/FirFilter.hpp index f868503..b679ee7 100644 --- a/TNC/FirFilter.hpp +++ b/TNC/FirFilter.hpp @@ -1,10 +1,9 @@ -// Copyright 2015 Rob Riggs +// Copyright 2015-2019 Rob Riggs // All rights reserved. #ifndef MOBILINKD__TNC__FIR_FILTER_H_ #define MOBILINKD__TNC__FIR_FILTER_H_ -#include "Filter.hpp" #include #include "arm_math.h" @@ -74,7 +73,7 @@ struct FirFilter { float* operator()(int16_t* input) // __attribute__((section(".bss2"))) { for (size_t i = 0; i != BLOCK_SIZE; i++) { - filter_input[i] = (float(input[i]) - vgnd_) * audio::i_vgnd; + filter_input[i] = float(input[i]); } arm_fir_f32(&instance, filter_input, filter_output, BLOCK_SIZE); return filter_output; @@ -85,6 +84,54 @@ struct FirFilter { arm_fir_f32(&instance, input, filter_output, BLOCK_SIZE); return filter_output; } + + float operator()(float input) // __attribute__((section(".bss2"))) + { + arm_fir_f32(&instance, &input, filter_output, 1); + return *filter_output; + } +}; + +template +struct Q15FirFilter { + const q15_t* filter_taps{nullptr}; + q15_t filter_state[BLOCK_SIZE + FILTER_SIZE - 1]; + q15_t filter_input[BLOCK_SIZE]; + q15_t filter_output[BLOCK_SIZE]; + q15_t vgnd_{0}; + q15_t i_vgnd_{0}; + arm_fir_instance_q15 instance{}; + + Q15FirFilter() + {} + + Q15FirFilter(const q15_t* taps) + : filter_taps(taps) + { + init(taps); + } + + void init(const q15_t* taps) + { + vgnd_ = audio::virtual_ground; + filter_taps = taps; + arm_fir_init_q15(&instance, FILTER_SIZE, const_cast(filter_taps), // WTF ARM?!? + filter_state, BLOCK_SIZE); + } + + // ADC input + q15_t* operator()(q15_t* input) // __attribute__((section(".bss2"))) + { + arm_fir_fast_q15(&instance, input, filter_output, BLOCK_SIZE); + return filter_output; + } + + // LPF input + q15_t* filter(q15_t* input) // __attribute__((section(".bss2"))) + { + arm_fir_fast_q15(&instance, input, filter_output, BLOCK_SIZE); + return filter_output; + } }; }} // mobilinkd::tnc diff --git a/TNC/Goertzel.h b/TNC/Goertzel.h index 5e3b63f..981fc96 100644 --- a/TNC/Goertzel.h +++ b/TNC/Goertzel.h @@ -8,6 +8,7 @@ #include #include +#include namespace mobilinkd { namespace tnc { diff --git a/TNC/HDLCEncoder.hpp b/TNC/HDLCEncoder.hpp index 21f61f3..2481741 100644 --- a/TNC/HDLCEncoder.hpp +++ b/TNC/HDLCEncoder.hpp @@ -25,6 +25,7 @@ using namespace mobilinkd::libafsk; struct Encoder { + static const uint8_t IDLE = 0x00; static const uint8_t FLAG = 0x7E; enum class state_type { @@ -71,6 +72,7 @@ struct Encoder { // See if we have back-to-back frames. evt = osMessagePeek(input_, 0); if (evt.status != osEventMessage) { + send_raw(IDLE); send_delay_ = true; if (!duplex_) { osMessagePut(audioInputQueueHandle, audio::DEMODULATOR, @@ -117,6 +119,8 @@ struct Encoder { * For APRS digipeaters, the slot_time and p values should be 0 and 255, * respectively. This is equivalent to 1-persistent CSMA. * + * @pre The demodulator is running in order to detect the data carrier. + * * @note For this to work, the demodulator must be left running * while CSMA is taking place in order to do carrier detection. * @@ -125,8 +129,7 @@ struct Encoder { */ bool do_csma() { // Wait until we can transmit. If we cannot transmit for 10s - // drop the frame. Note that we cheat a bit by looking at the - // state of the DCD_LED to determine if the channel is clear. + // drop the frame. if (!led_dcd_status()) { // Channel is clear... send now. @@ -134,8 +137,8 @@ struct Encoder { } uint16_t counter = 0; - while (counter < 10000) { - osDelay(slot_time_); // We count on minimum delay = 1. + while (counter < 1000) { + osDelay(slot_time_ * 10); // We count on minimum delay = 1. counter += slot_time_; if (rng_() < p_persist_) { @@ -160,6 +163,10 @@ struct Encoder { * flag is not cleared. This will cause CSMA and TX delay to be * attempted on the next frame. * + * @pre either send_delay_ is false or the demodulator is running. We + * expect that send_delay_ is false only when we have back-to-back + * packets. + * * @param frame */ void process(IoFrame* frame) { @@ -168,7 +175,10 @@ struct Encoder { frame->add_fcs(); if (send_delay_) { - if (not do_csma()) return; + if (not do_csma()) { + release(frame); + return; + } if (!duplex_) { osMessagePut(audioInputQueueHandle, audio::IDLE, osWaitForever); } @@ -184,8 +194,9 @@ struct Encoder { void send_delay() { const size_t tmp = (tx_delay_ * 3) / 2; for (size_t i = 0; i != tmp; i++) { - send_raw(FLAG); + send_raw(IDLE); } + send_raw(FLAG); } void send_fcs(uint16_t fcs) { diff --git a/TNC/HdlcDecoder.cpp b/TNC/HdlcDecoder.cpp index dbbd251..789a11f 100644 --- a/TNC/HdlcDecoder.cpp +++ b/TNC/HdlcDecoder.cpp @@ -1,13 +1,14 @@ -// Copyright 2016 Rob Riggs +// Copyright 2016-2019 Rob Riggs // All rights reserved. #include "HdlcDecoder.hpp" #include "GPIO.hpp" +#include "Log.h" namespace mobilinkd { namespace tnc { namespace hdlc { Decoder::Decoder(bool pass_all) -: state_(SEARCH), ones_(0), buffer_(0), frame_(ioFramePool().acquire()) +: state_(SEARCH), ones_(0), buffer_(0), frame_(acquire()) , bits_(0), passall_(pass_all), ready_(false) {} @@ -17,16 +18,16 @@ IoFrame* Decoder::operator()(bool bit, bool pll) // It appears that the ioFramePool may not get initialized in the proper // order during some builds. - if (nullptr == frame_) frame_ = ioFramePool().acquire(); + if (nullptr == frame_) frame_ = acquire(); if (nullptr == frame_) return result; if (not pll) { - if ((state_ == FRAMING) and (frame_->size() > 17) and passall_) { + if ((state_ == FRAMING) and (frame_->size() >= 15) and passall_) { frame_->parse_fcs(); if (passall_ or frame_->ok()) { result = frame_; ready_ = false; - frame_ = ioFramePool().acquire(); + frame_ = acquire(); return result; } } @@ -37,7 +38,8 @@ IoFrame* Decoder::operator()(bool bit, bool pll) frame_->parse_fcs(); if (passall_ or frame_->ok()) { result = frame_; - frame_ = ioFramePool().acquire(); + frame_ = acquire(); + if (frame_) start_hunt(); return result; } frame_->clear(); @@ -47,7 +49,119 @@ IoFrame* Decoder::operator()(bool bit, bool pll) return result; } +NewDecoder::optional_result_type NewDecoder::operator()(bool input, bool pll_lock) +{ + optional_result_type result = nullptr; + + auto status = process(input, pll_lock); + if (status) + { +// INFO("Frame Status = %02x, size = %d, CRC = %04x", +// int(status), int(packet->size()), int(packet->crc())); + if (((status & STATUS_OK) || passall) && packet->size() > 10) + { + result = packet; + packet = nullptr; + } else { + packet->clear(); + } + } + + return result; +} + +uint8_t NewDecoder::process(bool input, bool pll_lock) +{ + uint8_t result_code = 0; + + while (packet == nullptr) { + packet = ioFramePool().acquire(); + if (!packet) osThreadYield(); + } + + if (pll_lock) { + if (ones == 5) { + if (input) { + // flag byte + flag = 1; + } else { + // bit stuffing... + flag = 0; + ones = 0; + return result_code; + } + } + + buffer >>= 1; + buffer |= (input * 128); + bits += 1; // Free-running until Sync byte. + if (input) { + ++ones; + } else { + ones = 0; + } + + if (flag) { + switch (buffer) { + case 0x7E: + if (packet->size()) { + packet->parse_fcs(); + result_code = packet->ok() ? STATUS_OK : STATUS_CRC_ERROR; + } + state = State::SYNC; + flag = 0; + bits = 0; + break; + case 0xFE: + if (packet->size()) { + result_code = STATUS_FRAME_ABORT; + } + state = State::IDLE; + flag = 0; + bits = 0; + break; + default: + /* pass */ + break; + } + return result_code; + } + + switch (state) { + + case State::IDLE: + break; + + case State::SYNC: + if (bits == 8) { // 8th bit. + // Start of frame data. + state = State::RECEIVE; + packet->push_back(buffer); + bits = 0; + } + break; + + case State::RECEIVE: + if (bits == 8) { // 8th bit. + packet->push_back(buffer); + bits = 0; + } + } + } else { + // PLL unlocked. + if (packet->size()) { + packet->parse_fcs(); + result_code = packet->ok() ? STATUS_OK | STATUS_NO_CARRIER : STATUS_NO_CARRIER; + } + if (state != State::IDLE) { + buffer = 0; + flag = 0; + bits = 0; + state = State::IDLE; + } + } + + return result_code; +} }}} // mobilinkd::tnc::hdlc - - diff --git a/TNC/HdlcDecoder.hpp b/TNC/HdlcDecoder.hpp index 20e8120..ce6e746 100644 --- a/TNC/HdlcDecoder.hpp +++ b/TNC/HdlcDecoder.hpp @@ -1,4 +1,4 @@ -// Copyright 2015 Mobilinkd LLC +// Copyright 2015-2019 Mobilinkd LLC // All rights reserved. #ifndef MOBILINKD___HDLC_DECODER_HPP_ @@ -222,7 +222,7 @@ struct Decoder } if (have_flag()) { - if (frame_->size() > 17) { + if (frame_->size() >= 15) { ready_ = true; } } @@ -237,7 +237,7 @@ struct Decoder // Framing error. Drop the frame. If there is a FLAG // in the buffer, go into HUNT otherwise SEARCH. - if (frame_->size() > 17) { + if (frame_->size() > 15) { ready_ = true; return; } @@ -283,7 +283,7 @@ struct Decoder #if 0 Decoder::Decoder(bool pass_all) -: state_(SEARCH), ones_(0), buffer_(0), frame_(ioFramePool().acquire()) +: state_(SEARCH), ones_(0), buffer_(0), frame_(acquire()) , bits_(0), passall_(pass_all), ready_(false) {} @@ -293,15 +293,15 @@ IoFrame* Decoder::operator()(bool bit, bool pll) // It appears that the ioFramePool may not get initialized in the proper // order during some builds. - if (nullptr == frame_) frame_ = ioFramePool().acquire(); + if (nullptr == frame_) frame_ = acquire(); if (not pll) { - if ((state_ == FRAMING) and (frame_->size() > 17) and passall_) { + if ((state_ == FRAMING) and (frame_->size() > 15) and passall_) { frame_->parse_fcs(); if (passall_ or frame_->ok()) { result = frame_; ready_ = false; - frame_ = ioFramePool().acquire(); + frame_ = acquire(); return result; } } @@ -312,7 +312,7 @@ IoFrame* Decoder::operator()(bool bit, bool pll) frame_->parse_fcs(); if (passall_ or frame_->ok()) { result = frame_; - frame_ = ioFramePool().acquire(); + frame_ = acquire(); return result; } frame_->clear(); @@ -323,6 +323,42 @@ IoFrame* Decoder::operator()(bool bit, bool pll) } #endif + +struct NewDecoder +{ + enum class State {IDLE, SYNC, RECEIVE}; + using frame_type = IoFrame; + using result_type = std::tuple; + using optional_result_type = frame_type*; + + static constexpr uint8_t STATUS_OK{0x01}; + static constexpr uint8_t STATUS_USER_CANCEL{0x02}; + static constexpr uint8_t STATUS_FRAME_ABORT{0x04}; + static constexpr uint8_t STATUS_FRAME_ERROR{0x08}; + static constexpr uint8_t STATUS_NO_CARRIER{0x10}; + static constexpr uint8_t STATUS_CRC_ERROR{0x20}; + + const uint16_t VALID_CRC = 0xf0b8; + + State state{State::IDLE}; + + uint8_t buffer{0}; + + uint8_t bits{0}; + uint8_t ones{0}; + bool flag{0}; + bool passall{false}; + + frame_type* packet{nullptr}; + + NewDecoder(bool pass_all=false) + : passall(pass_all) + {} + + optional_result_type operator()(bool input, bool pll_lock); + uint8_t process(bool input, bool pll_lock); +}; + }}} // mobilinkd::tnc::hdlc #endif // MOBILINKD___HDLC_DECODER_HPP_ diff --git a/TNC/HdlcFrame.cpp b/TNC/HdlcFrame.cpp index 6d411ac..a101a7c 100644 --- a/TNC/HdlcFrame.cpp +++ b/TNC/HdlcFrame.cpp @@ -2,6 +2,8 @@ // All rights reserved. #include "HdlcFrame.hpp" +#include "Log.h" +#include "cmsis_os.h" namespace mobilinkd { namespace tnc { namespace hdlc { @@ -17,4 +19,21 @@ void release(IoFrame* frame) ioFramePool().release(frame); } +IoFrame* acquire() +{ + auto result = ioFramePool().acquire(); + if (result == nullptr) CxxErrorHandler(); + return result; +} + + +IoFrame* acquire_wait() +{ + IoFrame* result = nullptr; + while ((result = ioFramePool().acquire()) == nullptr) { + osThreadYield(); + } + return result; +} + }}} // mobilinkd::tnc::hdlc diff --git a/TNC/HdlcFrame.hpp b/TNC/HdlcFrame.hpp index f1388f7..d67be06 100644 --- a/TNC/HdlcFrame.hpp +++ b/TNC/HdlcFrame.hpp @@ -1,4 +1,4 @@ -// Copyright 2015 Mobilinkd LLC +// Copyright 2015-2019 Mobilinkd LLC // All rights reserved. @@ -36,7 +36,7 @@ public: typedef typename data_type::iterator iterator; enum Type { - DATA, TX_DELAY, P_PERSIST, SLOT_TIME, TX_TAIL, DUPLEX, HARDWARE, + DATA = 0, TX_DELAY, P_PERSIST, SLOT_TIME, TX_TAIL, DUPLEX, HARDWARE, TEXT, LOG}; enum Source { @@ -72,7 +72,7 @@ private: checksum <<= 16; // Shift asm volatile("rbit %0, %0" : "+r" (checksum)); // Reverse uint16_t result = checksum & 0xFFFF; -// DEBUG("CRC = %hx", result); + DEBUG("CRC = %hx", result); return result; } #else @@ -95,7 +95,7 @@ public: crc_ = -1; fcs_ = -2; complete_ = false; - frame_type_ = 0; + frame_type_ = 0; // RF_DATA. } void assign(data_type& data) { @@ -133,13 +133,13 @@ public: fcs_ = (*it); ++it; fcs_ |= (*it) << 8; -// DEBUG("FCS = %hx", fcs_); + DEBUG("FCS = %hx", fcs_); crc_ = compute_crc(data_.begin()); complete_ = true; } }; -template +template class FramePool { public: @@ -147,7 +147,7 @@ public: typedef list> FrameList; private: - static const uint16_t FRAME_COUNT = 16; + static const uint16_t FRAME_COUNT = SIZE; frame_type frames_[FRAME_COUNT]; FrameList free_list_; @@ -170,10 +170,12 @@ public: free_list_.pop_front(); } taskEXIT_CRITICAL_FROM_ISR(x); + DEBUG("Acquired frame %p (size after = %d)", result, free_list_.size()); return result; } void release(frame_type* frame) { + DEBUG("Released frame %p (size before = %d)", frame, free_list_.size()); frame->clear(); auto x = taskENTER_CRITICAL_FROM_ISR(); free_list_.push_back(*frame); @@ -186,7 +188,7 @@ typedef buffer::Pool<48> FrameSegmentPool; // 12K buffer of frames; extern FrameSegmentPool frameSegmentPool; typedef Frame IoFrame; -typedef FramePool IoFramePool; +typedef FramePool IoFramePool; IoFramePool& ioFramePool(void); @@ -195,13 +197,16 @@ IoFramePool& ioFramePool(void); * that causes functions using ioFramePool().release(frame) to use an * extremely large amount of stack space (4-6K vs. 24 bytes). * - * This function merely acts as some sort of firewall to the stack usage. + * This function merely acts as a compiler firewall to the stack usage. * It's own stack usage is minimal even though it is making the same call. * * @param frame */ void release(IoFrame* frame); +IoFrame* acquire(void); +IoFrame* acquire_wait(void); + }}} // mobilinkd::tnc::hdlc #endif // MOBILINKD__HDLC_FRAME_HPP_ diff --git a/TNC/IOEventTask.cpp b/TNC/IOEventTask.cpp index 9618679..b235ccf 100644 --- a/TNC/IOEventTask.cpp +++ b/TNC/IOEventTask.cpp @@ -1,8 +1,8 @@ -// Copyright 2017 Rob Riggs +// Copyright 2017-2019 Rob Riggs // All rights reserved. -#include -#include +#include "AudioLevel.hpp" +#include "Log.h" #include "IOEventTask.h" #include "PortInterface.h" #include "PortInterface.hpp" @@ -20,116 +20,137 @@ extern osMessageQId hdlcOutputQueueHandle; -void startIOEventTask(void const*) +static PTT getPttStyle(const mobilinkd::tnc::kiss::Hardware& hardware) { - initSerial(); - openSerial(); - - mobilinkd::tnc::audio::init_log_volume(); - mobilinkd::tnc::print_startup_banner(); - - - auto& hardware = mobilinkd::tnc::kiss::settings(); - if (!hardware.crc_ok()) { - hardware.init(); - if (!hardware.load()) hardware.store(); - } - hardware.debug(); - strcpy((char*)hardware.mycall, "WX9O"); - hardware.update_crc(); - - mobilinkd::tnc::audio::setAudioOutputLevel(); - mobilinkd::tnc::audio::setAudioInputLevels(); - - osMessagePut(audioInputQueueHandle, - mobilinkd::tnc::audio::DEMODULATOR, osWaitForever); - - /* Infinite loop */ - for(;;) - { - osEvent evt = osMessageGet(ioEventQueueHandle, osWaitForever); - if (evt.status != osEventMessage) continue; - - uint32_t cmd = evt.value.v; - if (cmd < FLASH_BASE) // Assumes FLASH_BASE < SRAM_BASE. - { - switch (cmd) { - case CMD_USER_BUTTON_DOWN: - INFO("Button Down"); - osMessagePut(audioInputQueueHandle, - mobilinkd::tnc::audio::AUTO_ADJUST_INPUT_LEVEL, osWaitForever); - osMessagePut(audioInputQueueHandle, - mobilinkd::tnc::audio::DEMODULATOR, osWaitForever); - break; - case CMD_USER_BUTTON_UP: - DEBUG("Button Up"); - break; - case CMD_SET_PTT_SIMPLEX: - getModulator().set_ptt(&simplexPtt); - break; - case CMD_SET_PTT_MULTIPLEX: - getModulator().set_ptt(&multiplexPtt); - break; - default: - WARN("unknown command = %04x", static_cast(cmd)); - break; - } - continue; - } - - using mobilinkd::tnc::hdlc::IoFrame; - - auto frame = static_cast(evt.value.p); - - switch (frame->source()) { - case IoFrame::RF_DATA: -// DEBUG("RF frame"); - if (!mobilinkd::tnc::ioport->write(frame, 100)) { - ERROR("Timed out sending frame"); - mobilinkd::tnc::hdlc::release(frame); - } - break; - case IoFrame::SERIAL_DATA: -// DEBUG("Serial frame"); - if ((frame->type() & 0x0F) == IoFrame::DATA) { - if (osMessagePut(hdlcOutputQueueHandle, reinterpret_cast(frame), - osWaitForever) != osOK) { - ERROR("Failed to write frame to TX queue"); - mobilinkd::tnc::hdlc::release(frame); - } - } else { - mobilinkd::tnc::kiss::handle_frame(frame->type(), frame); - } - break; - case IoFrame::DIGI_DATA: -// DEBUG("Digi frame"); - if (osMessagePut(hdlcOutputQueueHandle, reinterpret_cast(frame), - osWaitForever) != osOK) { - mobilinkd::tnc::hdlc::release(frame); - } - break; - case IoFrame::FRAME_RETURN: - mobilinkd::tnc::hdlc::release(frame); - break; - } - } + return hardware.options & KISS_OPTION_PTT_SIMPLEX ? PTT::SIMPLEX : PTT::MULTIPLEX; } -namespace mobilinkd { namespace tnc { +void startIOEventTask(void const*) +{ + using namespace mobilinkd::tnc; + + initSerial(); + openSerial(); + + print_startup_banner(); + + auto& hardware = kiss::settings(); + if (!hardware.load()) + { + hardware.init(); + hardware.store(); + } + + hardware.debug(); + + audio::init_log_volume(); + audio::setAudioOutputLevel(); + audio::setAudioInputLevels(); + setPtt(getPttStyle(hardware)); + + osMessagePut(audioInputQueueHandle, mobilinkd::tnc::audio::DEMODULATOR, + osWaitForever); + + /* Infinite loop */ + for (;;) + { + osEvent evt = osMessageGet(ioEventQueueHandle, osWaitForever); + if (evt.status != osEventMessage) + continue; + + uint32_t cmd = evt.value.v; + if (cmd < FLASH_BASE) // Assumes FLASH_BASE < SRAM_BASE. + { + switch (cmd) { + case CMD_USER_BUTTON_DOWN: + INFO("Button Down"); + osMessagePut(audioInputQueueHandle, + mobilinkd::tnc::audio::AUTO_ADJUST_INPUT_LEVEL, + osWaitForever); + osMessagePut(audioInputQueueHandle, + mobilinkd::tnc::audio::DEMODULATOR, osWaitForever); + break; + case CMD_USER_BUTTON_UP: + DEBUG("Button Up"); + break; + case CMD_SET_PTT_SIMPLEX: + getModulator().set_ptt(&simplexPtt); + break; + case CMD_SET_PTT_MULTIPLEX: + getModulator().set_ptt(&multiplexPtt); + break; + default: + WARN("unknown command = %04x", static_cast(cmd)); + break; + } + continue; + } + + using hdlc::IoFrame; + + auto frame = static_cast(evt.value.p); + + switch (frame->source()) { + case IoFrame::RF_DATA: +// DEBUG("RF frame"); + if (!ioport->write(frame, 100)) + { + ERROR("Timed out sending frame"); + } + break; + case IoFrame::SERIAL_DATA: +// DEBUG("Serial frame"); + if ((frame->type() & 0x0F) == IoFrame::DATA) + { + if (osMessagePut(hdlcOutputQueueHandle, + reinterpret_cast(frame), + osWaitForever) != osOK) + { + ERROR("Failed to write frame to TX queue"); + hdlc::release(frame); + } + } + else + { + kiss::handle_frame(frame->type(), frame); + } + break; + case IoFrame::DIGI_DATA: +// DEBUG("Digi frame"); + if (osMessagePut(hdlcOutputQueueHandle, + reinterpret_cast(frame), + osWaitForever) != osOK) + { + hdlc::release(frame); + } + break; + case IoFrame::FRAME_RETURN: + hdlc::release(frame); + break; + } + } +} + +namespace mobilinkd { +namespace tnc { void print_startup_banner() { - uint32_t* uid = (uint32_t*)0x1FFF7590; // STM32L4xx (same for 476 and 432) +#ifdef KISS_LOGGING + uint32_t* uid = (uint32_t*) UID_BASE; // STM32L4xx (same for 476 and 432) - INFO("%s version %s", mobilinkd::tnc::kiss::HARDWARE_VERSION, mobilinkd::tnc::kiss::FIRMWARE_VERSION); + INFO("%s version %s", mobilinkd::tnc::kiss::HARDWARE_VERSION, + mobilinkd::tnc::kiss::FIRMWARE_VERSION); INFO("CPU core clock: %luHz", SystemCoreClock); - INFO(" Serial number: %08lX %08lX %08lX", uid[0], uid[1], uid[2]); + INFO(" Device UID: %08lX %08lX %08lX", uid[0], uid[1], uid[2]); - uint8_t* version_ptr = (uint8_t*)0x1FFF6FF2; + uint8_t* version_ptr = (uint8_t*) 0x1FFF6FF2; int version = *version_ptr; INFO("Bootloader version: 0x%02X", version); +#endif } -}} // mobilinkd::tnc +} +} // mobilinkd::tnc diff --git a/TNC/IirFilter.hpp b/TNC/IirFilter.hpp index 267ea20..34ea565 100644 --- a/TNC/IirFilter.hpp +++ b/TNC/IirFilter.hpp @@ -4,69 +4,31 @@ #ifndef MOBILINKD__TNC__IIR_FILTER_H_ #define MOBILINKD__TNC__IIR_FILTER_H_ +#include #include #include namespace mobilinkd { namespace tnc { -template -struct TIirCoefficients { - float b[N]; - float a[N]; -}; - -struct IirCoefficients { - size_t size; - const float* b; - const float* a; - - template - IirCoefficients(const TIirCoefficients& c) - : size(N), b(c.b), a(c.b) - {} -}; - template struct IirFilter { - typedef float float_type; - const float* numerator_; - const float* denominator_; - float history_[N]; + const std::array& numerator_; + const std::array& denominator_; + std::array history_; size_t size_; - IirFilter(const float* b, const float* a) + IirFilter(const std::array& b, + const std::array& a) : numerator_(b), denominator_(a) , history_(), size_(N) { - memset(history_, 0, N); - } - - IirFilter(const float* b, const float* a, size_t size) - : numerator_(b), denominator_(a) - , history_(), size_(size) - { - memset(history_, 0, N); - } - - template - IirFilter(TIirCoefficients c) - : numerator_(c.b), denominator_(c.a) - , history_(), size_(M) - { - memset(history_, 0, N); - } - - IirFilter(IirCoefficients c) - : numerator_(c.b), denominator_(c.a) - , history_(), size_(c.size) - { - memset(history_, 0, N); + history_.fill(0.0f); } ~IirFilter() {} - float_type operator()(float_type input) + float operator()(float input) { for(size_t i = size_ - 1; i != 0; i--) history_[i] = history_[i - 1]; @@ -77,7 +39,7 @@ struct IirFilter { history_[0] -= denominator_[i] * history_[i]; } - float_type result = 0; + float result = 0; for (size_t i = 0; i != size_; i++) { result += numerator_[i] * history_[i]; } diff --git a/TNC/KissHardware.cpp b/TNC/KissHardware.cpp index 5b66640..896a7ef 100644 --- a/TNC/KissHardware.cpp +++ b/TNC/KissHardware.cpp @@ -1,21 +1,21 @@ -// Copyright 2015 Rob Riggs +// Copyright 2018-2019 Rob Riggs // All rights reserved. #include "KissHardware.hpp" #include "PortInterface.hpp" #include "AudioInput.hpp" #include "AudioLevel.hpp" -#include "AFSKTestTone.hpp" #include "IOEventTask.h" #include #include +#include extern I2C_HandleTypeDef hi2c3; namespace mobilinkd { namespace tnc { namespace kiss { -const char FIRMWARE_VERSION[] = "0.8.4"; +const char FIRMWARE_VERSION[] = "1.0.0"; const char HARDWARE_VERSION[] = "Mobilinkd Nucleo32 Breadboard TNC"; Hardware& settings() @@ -45,9 +45,7 @@ void Hardware::set_duplex(uint8_t value) { update_crc(); } -#if 1 - - void reply8(uint8_t cmd, uint8_t result) { +void reply8(uint8_t cmd, uint8_t result) { uint8_t data[2] { cmd, result }; ioport->write(data, 2, 6, osWaitForever); } @@ -58,26 +56,21 @@ void reply16(uint8_t cmd, uint16_t result) { } inline void reply(uint8_t cmd, const uint8_t* data, uint16_t len) { - auto buffer = (uint8_t*) malloc(len + 1); - if (buffer == nullptr) return; + uint8_t* buffer = static_cast(alloca(len + 1)); buffer[0] = cmd; - for (uint16_t i = 0; i != len and data[i] != 0; i++) + for (uint16_t i = 0; i != len; i++) buffer[i + 1] = data[i]; ioport->write(buffer, len + 1, 6, osWaitForever); - free(buffer); } inline void reply_ext(uint8_t ext, uint8_t cmd, const uint8_t* data, uint16_t len) { - auto buffer = (uint8_t*) malloc(len + 2); - if (buffer == nullptr) return; + uint8_t* buffer = static_cast(alloca(len + 2)); buffer[0] = ext; buffer[1] = cmd; - for (uint16_t i = 0; i != len and data[i] != 0; i++) + for (uint16_t i = 0; i != len; i++) buffer[i + 2] = data[i]; ioport->write(buffer, len + 2, 6, osWaitForever); - free(buffer); } -#endif void Hardware::get_alias(uint8_t alias) { uint8_t result[14]; @@ -96,13 +89,21 @@ void Hardware::set_alias(const hdlc::IoFrame* frame) { UNUSED(frame); } -void Hardware::handle_request(hdlc::IoFrame* frame) { +void Hardware::announce_input_settings() +{ + reply16(hardware::GET_INPUT_GAIN, input_gain); + reply8(hardware::GET_INPUT_TWIST, rx_twist); +} - static AFSKTestTone testTone; +AFSKTestTone& getAFSKTestTone() { + static AFSKTestTone testTone; + return testTone; +} +void Hardware::handle_request(hdlc::IoFrame* frame) +{ auto it = frame->begin(); uint8_t command = *it++; - uint8_t v = *it; switch (command) { case hardware::SEND_MARK: @@ -113,31 +114,28 @@ void Hardware::handle_request(hdlc::IoFrame* frame) { case hardware::SET_OUTPUT_TWIST: break; default: - testTone.stop(); + getAFSKTestTone().stop(); } switch (command) { -#if 0 case hardware::SAVE: case hardware::SAVE_EEPROM_SETTINGS: - save(); - reply8(hardware::OK, hardware::SAVE_EEPROM_SETTINGS); - audio::adcState = audio::DEMODULATOR; + update_crc(); + store(); + reply8(hardware::SAVE_EEPROM_SETTINGS, hardware::OK); break; -#endif case hardware::POLL_INPUT_LEVEL: DEBUG("POLL_INPUT_VOLUME"); - reply8(hardware::POLL_INPUT_LEVEL, 0); osMessagePut(audioInputQueueHandle, audio::POLL_AMPLIFIED_INPUT_LEVEL, osWaitForever); osMessagePut(audioInputQueueHandle, audio::DEMODULATOR, osWaitForever); break; case hardware::STREAM_INPUT_LEVEL: - DEBUG("STREAM_INPUT_VOLUME"); - osMessagePut(audioInputQueueHandle, audio::STREAM_AMPLIFIED_INPUT_LEVEL, - osWaitForever); + DEBUG("STREAM_INPUT_VOLUME"); + osMessagePut(audioInputQueueHandle, audio::STREAM_AMPLIFIED_INPUT_LEVEL, + osWaitForever); break; case hardware::GET_BATTERY_LEVEL: DEBUG("GET_BATTERY_LEVEL"); @@ -150,23 +148,23 @@ void Hardware::handle_request(hdlc::IoFrame* frame) { DEBUG("SEND_MARK"); osMessagePut(audioInputQueueHandle, audio::IDLE, osWaitForever); - testTone.mark(); + getAFSKTestTone().mark(); break; case hardware::SEND_SPACE: DEBUG("SEND_SPACE"); osMessagePut(audioInputQueueHandle, audio::IDLE, osWaitForever); - testTone.space(); + getAFSKTestTone().space(); break; case hardware::SEND_BOTH: DEBUG("SEND_BOTH"); osMessagePut(audioInputQueueHandle, audio::IDLE, osWaitForever); - testTone.both(); + getAFSKTestTone().both(); break; case hardware::STOP_TX: DEBUG("STOP_TX"); - testTone.stop(); + getAFSKTestTone().stop(); osMessagePut(audioInputQueueHandle, audio::IDLE, osWaitForever); break; @@ -177,13 +175,16 @@ void Hardware::handle_request(hdlc::IoFrame* frame) { break; case hardware::SET_OUTPUT_GAIN: DEBUG("SET_OUTPUT_VOLUME"); - output_gain = v; + output_gain = *it << 8; + ++it; + output_gain += *it; + DEBUG("SET_OUTPUT_GAIN = %d", int(output_gain)); audio::setAudioOutputLevel(); update_crc(); [[fallthrough]]; case hardware::GET_OUTPUT_GAIN: DEBUG("GET_OUTPUT_VOLUME"); - reply8(hardware::GET_OUTPUT_GAIN, output_gain); + reply16(hardware::GET_OUTPUT_GAIN, output_gain); break; case hardware::STREAM_DCD_VALUE: @@ -210,6 +211,14 @@ void Hardware::handle_request(hdlc::IoFrame* frame) { osWaitForever); break; + case hardware::ADJUST_INPUT_LEVELS: + DEBUG("ADJUST_INPUT_LEVELS"); + osMessagePut(audioInputQueueHandle, audio::AUTO_ADJUST_INPUT_LEVEL, + osWaitForever); + osMessagePut(audioInputQueueHandle, audio::STREAM_AMPLIFIED_INPUT_LEVEL, + osWaitForever); + break; + case hardware::SET_VERBOSITY: DEBUG("SET_VERBOSITY"); log_level = *it ? Log::Level::debug : Log::Level::warn; @@ -220,23 +229,28 @@ void Hardware::handle_request(hdlc::IoFrame* frame) { DEBUG("GET_VERBOSITY"); reply8(hardware::GET_VERBOSITY, log_level == Log::Level::debug); break; -#if 0 - case hardware::SET_LOWPASS_FREQ: - lowpass_freq = (*it++ << 8); - lowpass_freq = *it; - // lowpass_freq = antiAliasFilter.setFilterFreq(lowpass_freq); - audio::adcState = audio::UPDATE_SETTINGS; - case hardware::GET_LOWPASS_FREQ: - reply16(hardware::GET_LOWPASS_FREQ, lowpass_freq); + case hardware::SET_INPUT_GAIN: + input_gain = *it << 8; + ++it; + input_gain += *it; + DEBUG("SET_INPUT_GAIN = %d", input_gain); + update_crc(); + osMessagePut(audioInputQueueHandle, audio::UPDATE_SETTINGS, + osWaitForever); + osMessagePut(audioInputQueueHandle, audio::STREAM_AMPLIFIED_INPUT_LEVEL, + osWaitForever); + [[fallthrough]]; + case hardware::GET_INPUT_GAIN: + DEBUG("GET_INPUT_GAIN"); + reply16(hardware::GET_INPUT_GAIN, input_gain); break; -#endif case hardware::SET_INPUT_TWIST: DEBUG("SET_INPUT_TWIST"); rx_twist = *it; update_crc(); osMessagePut(audioInputQueueHandle, audio::UPDATE_SETTINGS, osWaitForever); - osMessagePut(audioInputQueueHandle, audio::DEMODULATOR, + osMessagePut(audioInputQueueHandle, audio::STREAM_AMPLIFIED_INPUT_LEVEL, osWaitForever); [[fallthrough]]; case hardware::GET_INPUT_TWIST: @@ -295,14 +309,20 @@ void Hardware::handle_request(hdlc::IoFrame* frame) { sizeof(HARDWARE_VERSION) - 1); break; + case hardware::GET_SERIAL_NUMBER: + DEBUG("GET_SERIAL_NUMBER"); + reply(hardware::GET_SERIAL_NUMBER, (uint8_t*) serial_number_64, + sizeof(serial_number_64) - 1); + break; + case hardware::SET_PTT_CHANNEL: DEBUG("SET_PTT_CHANNEL"); - options &= ~KISS_OPTION_PTT_SIMPLEX; if (*it) { - osMessagePut(ioEventQueueHandle, CMD_SET_PTT_MULTIPLEX, osWaitForever); + options &= ~KISS_OPTION_PTT_SIMPLEX; + osMessagePut(ioEventQueueHandle, CMD_SET_PTT_MULTIPLEX, osWaitForever); } else { - options |= KISS_OPTION_PTT_SIMPLEX; - osMessagePut(ioEventQueueHandle, CMD_SET_PTT_SIMPLEX, osWaitForever); + options |= KISS_OPTION_PTT_SIMPLEX; + osMessagePut(ioEventQueueHandle, CMD_SET_PTT_SIMPLEX, osWaitForever); } update_crc(); break; @@ -344,11 +364,15 @@ void Hardware::handle_request(hdlc::IoFrame* frame) { case hardware::GET_CAPABILITIES: DEBUG("GET_CAPABILITIES"); - reply16(hardware::GET_CAPABILITIES, 0); + reply16(hardware::GET_CAPABILITIES, + hardware::CAP_EEPROM_SAVE| + hardware::CAP_ADJUST_INPUT| + hardware::CAP_DFU_FIRMWARE); break; case hardware::GET_ALL_VALUES: DEBUG("GET_ALL_VALUES"); + reply16(hardware::GET_API_VERSION, hardware::KISS_API_VERSION); osMessagePut(audioInputQueueHandle, audio::POLL_BATTERY_LEVEL, osWaitForever); osMessagePut(audioInputQueueHandle, audio::POLL_TWIST_LEVEL, @@ -359,12 +383,12 @@ void Hardware::handle_request(hdlc::IoFrame* frame) { sizeof(FIRMWARE_VERSION) - 1); reply(hardware::GET_HARDWARE_VERSION, (uint8_t*) HARDWARE_VERSION, sizeof(HARDWARE_VERSION) - 1); - reply8(hardware::GET_USB_POWER_OFF, options & KISS_OPTION_VIN_POWER_OFF ? 0 : 1); - reply8(hardware::GET_USB_POWER_ON, options & KISS_OPTION_VIN_POWER_ON ? 0 : 1); - reply8(hardware::GET_OUTPUT_GAIN, output_gain); + reply(hardware::GET_SERIAL_NUMBER, (uint8_t*) serial_number_64, + sizeof(serial_number_64) - 1); + reply16(hardware::GET_OUTPUT_GAIN, output_gain); reply8(hardware::GET_OUTPUT_TWIST, tx_twist); + reply16(hardware::GET_INPUT_GAIN, input_gain); reply8(hardware::GET_INPUT_TWIST, rx_twist); - reply8(hardware::GET_VERBOSITY, log_level == Log::Level::debug); reply8(hardware::GET_TXDELAY, txdelay); reply8(hardware::GET_PERSIST, ppersist); reply8(hardware::GET_TIMESLOT, slot); @@ -372,7 +396,14 @@ void Hardware::handle_request(hdlc::IoFrame* frame) { reply8(hardware::GET_DUPLEX, duplex); reply8(hardware::GET_PTT_CHANNEL, options & KISS_OPTION_PTT_SIMPLEX ? 0 : 1); - reply16(hardware::GET_CAPABILITIES, 0); + reply16(hardware::GET_CAPABILITIES, + hardware::CAP_EEPROM_SAVE| + hardware::CAP_ADJUST_INPUT| + hardware::CAP_DFU_FIRMWARE); + reply16(hardware::GET_MIN_INPUT_GAIN, 0); // Constants for this FW + reply16(hardware::GET_MAX_INPUT_GAIN, 4); // Constants for this FW + reply8(hardware::GET_MIN_INPUT_TWIST, -3); // Constants for this FW + reply8(hardware::GET_MAX_INPUT_TWIST, 9); // Constants for this FW break; case hardware::EXTENDED_CMD: @@ -411,21 +442,20 @@ void Hardware::handle_ext_request(hdlc::IoFrame* frame) { bool Hardware::load() { - INFO("Loading settings from EEPROM"); - auto tmp = std::make_unique(); + Hardware tmp; - if (!tmp) return false; + memset(&tmp, 0, sizeof(Hardware)); - memset(tmp.get(), 0, sizeof(Hardware)); + if (!I2C_Storage::load(tmp)) { + ERROR("EEPROM read failed"); + return false; + } - if (!I2C_Storage::load(*tmp)) return false; - - if (tmp->crc_ok()) + if (tmp.crc_ok()) { - memcpy(this, tmp.get(), sizeof(Hardware)); - + memcpy(this, &tmp, sizeof(Hardware)); return true; } ERROR("EEPROM CRC error"); @@ -436,79 +466,69 @@ bool Hardware::store() const { INFO("Saving settings to EEPROM"); - I2C_Storage::store(*this); + if (!I2C_Storage::store(*this)) { + ERROR("EEPROM write failed"); + return false; + } INFO("EEPROM saved checksum is: %04x (crc = %04x)", checksum, crc()); - return true; + return crc_ok(); } - bool I2C_Storage::load(void* ptr, size_t len) { - uint32_t timeout = 1000; - auto start = osKernelSysTick(); + if (HAL_I2C_Init(&hi2c3) != HAL_OK) CxxErrorHandler(); + + DEBUG("Attempting to read %d bytes from EEPROM...", len); + + uint32_t timeout = 1000; // systicks... milliseconds auto tmp = static_cast(ptr); - auto result = HAL_I2C_Mem_Read_DMA(&hi2c3, i2c_address, 0, I2C_MEMADD_SIZE_16BIT, tmp, len); - if (result != HAL_OK) Error_Handler(); + auto result = HAL_I2C_Mem_Read(&hi2c3, i2c_address, 0, + I2C_MEMADD_SIZE_16BIT, tmp, len, timeout); + if (result != HAL_OK) CxxErrorHandler(); - while (HAL_I2C_GetState(&hi2c3) != HAL_I2C_STATE_READY) - { - if (osKernelSysTick() > start + timeout) return false; - osThreadYield(); - } + if (HAL_I2C_DeInit(&hi2c3) != HAL_OK) CxxErrorHandler(); return true; } bool I2C_Storage::store(const void* ptr, size_t len) { + if (HAL_I2C_Init(&hi2c3) != HAL_OK) CxxErrorHandler(); + auto tmp = const_cast(static_cast(ptr)); - uint32_t timeout = 1000; - - auto start = osKernelSysTick(); - uint32_t index = 0; size_t remaining = len; while (remaining > page_size) { - auto result = HAL_I2C_Mem_Write_DMA(&hi2c3, i2c_address, index, I2C_MEMADD_SIZE_16BIT, tmp + index, page_size); - if (result == HAL_BUSY) - { - if (osKernelSysTick() > start + timeout) return false; - osThreadYield(); - continue; + auto result = HAL_I2C_Mem_Write(&hi2c3, i2c_address, index, I2C_MEMADD_SIZE_16BIT, tmp + index, page_size, 20); + if (result != HAL_OK) { + ERROR("EEPROM write block error = %lu.", hi2c3.ErrorCode); + if (HAL_I2C_DeInit(&hi2c3) != HAL_OK) CxxErrorHandler(); + return false; } - if (result != HAL_OK) Error_Handler(); osDelay(write_time); index += page_size; remaining -= page_size; - while (HAL_I2C_GetState(&hi2c3) != HAL_I2C_STATE_READY) - { - if (osKernelSysTick() > start + timeout) return false; - osThreadYield(); - } } while (remaining) { - auto result = HAL_I2C_Mem_Write_DMA(&hi2c3, i2c_address << 8, index, I2C_MEMADD_SIZE_16BIT, tmp + index, remaining); - if (result == HAL_BUSY) { - osThreadYield(); - continue; + auto result = HAL_I2C_Mem_Write(&hi2c3, i2c_address, index, I2C_MEMADD_SIZE_16BIT, tmp + index, remaining, 20); + if (result != HAL_OK) { + ERROR("EEPROM write remainder error = %lu.", hi2c3.ErrorCode); + if (HAL_I2C_DeInit(&hi2c3) != HAL_OK) CxxErrorHandler(); + return false; } - if (result != HAL_OK) Error_Handler(); osDelay(write_time); index += remaining; remaining = 0; - while (HAL_I2C_GetState(&hi2c3) != HAL_I2C_STATE_READY) - { - if (osKernelSysTick() > start + timeout) return false; - osThreadYield(); - } } + if (HAL_I2C_DeInit(&hi2c3) != HAL_OK) CxxErrorHandler(); + return true; } diff --git a/TNC/KissHardware.hpp b/TNC/KissHardware.hpp index e043711..130a564 100644 --- a/TNC/KissHardware.hpp +++ b/TNC/KissHardware.hpp @@ -1,11 +1,11 @@ -// Copyright 2015 Mobilinkd LLC +// Copyright 2015-2019 Mobilinkd LLC // All rights reserved. -#ifndef MOBILINKD__TNC__KISS_HARDWARE_HPP_ -#define MOBILINKD__TNC__KISS_HARDWARE_HPP_ +#pragma once -#include +#include "Log.h" #include "HdlcFrame.hpp" +#include "AFSKTestTone.hpp" #include #include @@ -18,93 +18,121 @@ namespace mobilinkd { namespace tnc { namespace kiss { extern const char FIRMWARE_VERSION[]; extern const char HARDWARE_VERSION[]; +AFSKTestTone& getAFSKTestTone(); + namespace hardware { -const uint16_t CAP_DCD = 0x0001; -const uint16_t CAP_SQUELCH = 0x0002; -const uint16_t CAP_INPUT_ATTEN = 0x0004; -const uint16_t CAP_FIRMWARE_VERSION = 0x0008; -const uint16_t CAP_BATTERY_LEVEL = 0x0010; -const uint16_t CAP_BT_CONN_TRACK = 0x0020; -const uint16_t CAP_BT_NAME_CHANGE = 0x0040; -const uint16_t CAP_BT_PIN_CHANGE = 0x0080; -const uint16_t CAP_VERBOSE_ERROR = 0x0100; -const uint16_t CAP_EEPROM_SAVE = 0x0200; -const uint16_t CAP_ADJUST_INPUT = 0x0400; +/** + * This indicates the API version of the device. API versions are used to + * indicate to the user that the config app may need to be upgraded because + * the device is using a newer configuration API. + * + * The minor version should be updated whenever the API is extended (new + * GET/SET or CAP types added. + * + * The major version should be updated whenever non-backwards compatible + * changes to the API are made. + */ +constexpr const uint16_t KISS_API_VERSION = 0x0200; -const uint8_t SAVE = 0; // Save settings to EEPROM. -const uint8_t SET_OUTPUT_GAIN = 1; -const uint8_t SET_INPUT_GAIN = 2; -const uint8_t SET_SQUELCH_LEVEL = 3; -const uint8_t POLL_INPUT_LEVEL = 4; -const uint8_t STREAM_INPUT_LEVEL = 5; -const uint8_t GET_BATTERY_LEVEL = 6; -const uint8_t SEND_MARK = 7; -const uint8_t SEND_SPACE = 8; -const uint8_t SEND_BOTH = 9; -const uint8_t STOP_TX = 10; -const uint8_t RESET = 11; -const uint8_t GET_OUTPUT_GAIN = 12; -const uint8_t GET_INPUT_ATTEN = 13; -const uint8_t GET_SQUELCH_LEVEL = 14; -const uint8_t STREAM_DCD_VALUE = 15; +constexpr const uint16_t CAP_DCD = 0x0100; +constexpr const uint16_t CAP_SQUELCH = 0x0200; +constexpr const uint16_t CAP_INPUT_ATTEN = 0x0400; +constexpr const uint16_t CAP_FIRMWARE_VERSION = 0x0800; +constexpr const uint16_t CAP_BATTERY_LEVEL = 0x1000; +constexpr const uint16_t CAP_BT_CONN_TRACK = 0x2000; +constexpr const uint16_t CAP_BT_NAME_CHANGE = 0x4000; +constexpr const uint16_t CAP_BT_PIN_CHANGE = 0x8000; +constexpr const uint16_t CAP_VERBOSE_ERROR = 0x0001; +constexpr const uint16_t CAP_EEPROM_SAVE = 0x0002; +constexpr const uint16_t CAP_ADJUST_INPUT = 0x0004; // Auto-adjust input levels. +constexpr const uint16_t CAP_DFU_FIRMWARE = 0x0008; // DFU firmware style. -const uint8_t SET_VERBOSITY = 16; -const uint8_t GET_VERBOSITY = 17; +constexpr const uint8_t SAVE = 0; // Save settings to EEPROM. +constexpr const uint8_t SET_OUTPUT_GAIN = 1; +constexpr const uint8_t SET_INPUT_GAIN = 2; +constexpr const uint8_t SET_SQUELCH_LEVEL = 3; // deprecated. +constexpr const uint8_t POLL_INPUT_LEVEL = 4; +constexpr const uint8_t STREAM_INPUT_LEVEL = 5; +constexpr const uint8_t GET_BATTERY_LEVEL = 6; +constexpr const uint8_t SEND_MARK = 7; +constexpr const uint8_t SEND_SPACE = 8; +constexpr const uint8_t SEND_BOTH = 9; +constexpr const uint8_t STOP_TX = 10; +constexpr const uint8_t RESET = 11; +constexpr const uint8_t GET_OUTPUT_GAIN = 12; +constexpr const uint8_t GET_INPUT_GAIN = 13; +constexpr const uint8_t GET_SQUELCH_LEVEL = 14; +constexpr const uint8_t STREAM_DCD_VALUE = 15; -const uint8_t SET_INPUT_OFFSET = 18; -const uint8_t GET_INPUT_OFFSET = 19; -const uint8_t SET_OUTPUT_OFFSET = 20; -const uint8_t GET_OUTPUT_OFFSET = 21; -const uint8_t SET_LOWPASS_FREQ = 22; -const uint8_t GET_LOWPASS_FREQ = 23; -const uint8_t SET_INPUT_TWIST = 24; -const uint8_t GET_INPUT_TWIST = 25; -const uint8_t SET_OUTPUT_TWIST = 26; -const uint8_t GET_OUTPUT_TWIST = 27; +constexpr const uint8_t SET_VERBOSITY = 16; +constexpr const uint8_t GET_VERBOSITY = 17; -const uint8_t STREAM_RAW_INPUT = 28; -const uint8_t STREAM_AMPLIFIED_INPUT = 29; -const uint8_t STREAM_FILTERED_INPUT = 30; -const uint8_t STREAM_OUTPUT = 31; +constexpr const uint8_t SET_INPUT_OFFSET = 18; +constexpr const uint8_t GET_INPUT_OFFSET = 19; +constexpr const uint8_t SET_OUTPUT_OFFSET = 20; +constexpr const uint8_t GET_OUTPUT_OFFSET = 21; +constexpr const uint8_t SET_LOWPASS_FREQ = 22; +constexpr const uint8_t GET_LOWPASS_FREQ = 23; +constexpr const uint8_t SET_INPUT_TWIST = 24; +constexpr const uint8_t GET_INPUT_TWIST = 25; +constexpr const uint8_t SET_OUTPUT_TWIST = 26; +constexpr const uint8_t GET_OUTPUT_TWIST = 27; -const uint8_t OK = 32; // Acknowledge SET commands. +constexpr const uint8_t STREAM_RAW_INPUT = 28; +constexpr const uint8_t STREAM_AMPLIFIED_INPUT = 29; +constexpr const uint8_t STREAM_FILTERED_INPUT = 30; +constexpr const uint8_t STREAM_OUTPUT = 31; -const uint8_t GET_TXDELAY = 33; -const uint8_t GET_PERSIST = 34; -const uint8_t GET_TIMESLOT = 35; -const uint8_t GET_TXTAIL = 36; -const uint8_t GET_DUPLEX = 37; +constexpr const uint8_t OK = 32; // Acknowledge SET commands. -const uint8_t GET_FIRMWARE_VERSION = 40; -const uint8_t GET_HARDWARE_VERSION = 41; -const uint8_t SAVE_EEPROM_SETTINGS = 42; -const uint8_t ADJUST_INPUT_LEVELS = 43; -const uint8_t POLL_INPUT_TWIST = 44; -const uint8_t STREAM_AVG_INPUT_TWIST = 45; -const uint8_t STREAM_INPUT_TWIST = 46; +constexpr const uint8_t GET_TXDELAY = 33; +constexpr const uint8_t GET_PERSIST = 34; +constexpr const uint8_t GET_TIMESLOT = 35; +constexpr const uint8_t GET_TXTAIL = 36; +constexpr const uint8_t GET_DUPLEX = 37; -const uint8_t SET_BLUETOOTH_NAME = 65; -const uint8_t GET_BLUETOOTH_NAME = 66; -const uint8_t SET_BLUETOOTH_PIN = 67; // Danger Will Robinson. -const uint8_t GET_BLUETOOTH_PIN = 68; -const uint8_t SET_BT_CONN_TRACK = 69; // Bluetooth connection tracking -const uint8_t GET_BT_CONN_TRACK = 70; // Bluetooth connection tracking -const uint8_t SET_BT_MAJOR_CLASS = 71; // Bluetooth Major Class -const uint8_t GET_BT_MAJOR_CLASS = 72; // Bluetooth Major Class +constexpr const uint8_t GET_FIRMWARE_VERSION = 40; +constexpr const uint8_t GET_HARDWARE_VERSION = 41; +constexpr const uint8_t SAVE_EEPROM_SETTINGS = 42; +constexpr const uint8_t ADJUST_INPUT_LEVELS = 43; // Auto-adjust levels. +constexpr const uint8_t POLL_INPUT_TWIST = 44; +constexpr const uint8_t STREAM_AVG_INPUT_TWIST = 45; +constexpr const uint8_t STREAM_INPUT_TWIST = 46; +constexpr const uint8_t GET_SERIAL_NUMBER = 47; +constexpr const uint8_t GET_MAC_ADDRESS = 48; +constexpr const uint8_t GET_DATETIME = 49; +constexpr const uint8_t SET_DATETIME = 50; +constexpr const uint8_t GET_ERROR_MSG = 51; -const uint8_t SET_USB_POWER_ON = 73; // Power on when USB power available -const uint8_t GET_USB_POWER_ON = 74; -const uint8_t SET_USB_POWER_OFF = 75; // Power off when USB power unavailable -const uint8_t GET_USB_POWER_OFF = 76; -const uint8_t SET_BT_POWER_OFF = 77; // Power off after n seconds w/o BT conn -const uint8_t GET_BT_POWER_OFF = 78; +constexpr const uint8_t SET_BLUETOOTH_NAME = 65; +constexpr const uint8_t GET_BLUETOOTH_NAME = 66; +constexpr const uint8_t SET_BLUETOOTH_PIN = 67; // Danger Will Robinson. +constexpr const uint8_t GET_BLUETOOTH_PIN = 68; +constexpr const uint8_t SET_BT_CONN_TRACK = 69; // Bluetooth connection tracking +constexpr const uint8_t GET_BT_CONN_TRACK = 70; // Bluetooth connection tracking +constexpr const uint8_t SET_BT_MAJOR_CLASS = 71; // Bluetooth Major Class +constexpr const uint8_t GET_BT_MAJOR_CLASS = 72; // Bluetooth Major Class -const uint8_t SET_PTT_CHANNEL = 79; // Which PTT line to use (currently 0 or 1, -const uint8_t GET_PTT_CHANNEL = 80; // multiplex or simplex) +constexpr const uint8_t SET_USB_POWER_ON = 73; // Power on when USB power available +constexpr const uint8_t GET_USB_POWER_ON = 74; +constexpr const uint8_t SET_USB_POWER_OFF = 75; // Power off when USB power unavailable +constexpr const uint8_t GET_USB_POWER_OFF = 76; +constexpr const uint8_t SET_BT_POWER_OFF = 77; // Power off after n seconds w/o BT conn +constexpr const uint8_t GET_BT_POWER_OFF = 78; -const uint8_t GET_CAPABILITIES = 126; ///< Send all capabilities. -const uint8_t GET_ALL_VALUES = 127; ///< Send all settings & versions. +constexpr const uint8_t SET_PTT_CHANNEL = 79; // Which PTT line to use (currently 0 or 1, +constexpr const uint8_t GET_PTT_CHANNEL = 80; // multiplex or simplex) + +constexpr const uint8_t GET_MIN_OUTPUT_TWIST = 119; ///< int8_t (may be negative). +constexpr const uint8_t GET_MAX_OUTPUT_TWIST = 120; ///< int8_t (may be negative). +constexpr const uint8_t GET_MIN_INPUT_TWIST = 121; ///< int8_t (may be negative). +constexpr const uint8_t GET_MAX_INPUT_TWIST = 122; ///< int8_t (may be negative). +constexpr const uint8_t GET_API_VERSION = 123; ///< uint16_t (major/minor) +constexpr const uint8_t GET_MIN_INPUT_GAIN = 124; ///< int8_t (may be negative/attenuated). +constexpr const uint8_t GET_MAX_INPUT_GAIN = 125; ///< int8_t (may be negative/attenuated). +constexpr const uint8_t GET_CAPABILITIES = 126; ///< Send all capabilities. +constexpr const uint8_t GET_ALL_VALUES = 127; ///< Send all settings & versions. /** * Extended commands are two+ bytes in length. They start at 80:00 @@ -114,20 +142,20 @@ const uint8_t GET_ALL_VALUES = 127; ///< Send all settings & versions. * If needed, the commands can be extended to 9 nibbles (D0 - DF), * 13 nibbles (E0-EF) and 17 nibbles (F0-FF). */ -const uint8_t EXTENDED_CMD = 128; +constexpr const uint8_t EXTENDED_CMD = 128; -const uint8_t EXT_OK = 0; -const uint8_t EXT_GET_MODEM_TYPE = 1; -const uint8_t EXT_SET_MODEM_TYPE = 2; -const uint8_t EXT_GET_MODEM_TYPES = 3; ///< Return a list of supported modem types +constexpr const uint8_t EXT_OK = 0; +constexpr const uint8_t EXT_GET_MODEM_TYPE = 1; +constexpr const uint8_t EXT_SET_MODEM_TYPE = 2; +constexpr const uint8_t EXT_GET_MODEM_TYPES = 3; ///< Return a list of supported modem types -const uint8_t EXT_GET_ALIASES = 8; ///< Number of aliases supported -const uint8_t EXT_GET_ALIAS = 9; ///< Alias number (uint8_t), 8 characters, 5 bytes (set, use, insert_id, preempt, hops) -const uint8_t EXT_SET_ALIAS = 10; ///< Alias number (uint8_t), 8 characters, 5 bytes (set, use, insert_id, preempt, hops) +constexpr const uint8_t EXT_GET_ALIASES = 8; ///< Number of aliases supported +constexpr const uint8_t EXT_GET_ALIAS = 9; ///< Alias number (uint8_t), 8 characters, 5 bytes (set, use, insert_id, preempt, hops) +constexpr const uint8_t EXT_SET_ALIAS = 10; ///< Alias number (uint8_t), 8 characters, 5 bytes (set, use, insert_id, preempt, hops) -const uint8_t EXT_GET_BEACONS = 12; ///< Number of beacons supported -const uint8_t EXT_GET_BEACON = 13; ///< Beacon number (uint8_t), uint16_t interval in seconds, 3 NUL terminated strings (callsign, path, text) -const uint8_t EXT_SET_BEACON = 14; ///< Beacon number (uint8_t), uint16_t interval in seconds, 3 NUL terminated strings (callsign, path, text) +constexpr const uint8_t EXT_GET_BEACONS = 12; ///< Number of beacons supported +constexpr const uint8_t EXT_GET_BEACON = 13; ///< Beacon number (uint8_t), uint16_t interval in seconds, 3 NUL terminated strings (callsign, path, text) +constexpr const uint8_t EXT_SET_BEACON = 14; ///< Beacon number (uint8_t), uint16_t interval in seconds, 3 NUL terminated strings (callsign, path, text) constexpr const uint8_t EXT_GET_MYCALL = 16; ///< MYCALL callsign = 8 characters. right padded with NUL. constexpr const uint8_t EXT_SET_MYCALL = 17; ///< MYCALL callsign = 8 characters. right padded with NUL. @@ -143,7 +171,7 @@ constexpr const uint8_t MODEM_TYPE_MFSK16 = 6; #define KISS_OPTION_CONN_TRACK 0x01 #define KISS_OPTION_VERBOSE 0x02 #define KISS_OPTION_VIN_POWER_ON 0x04 // Power on when plugged in to USB -#define KISS_OPTION_VIN_POWER_OFF 0x08 // Power off when plugged in to USB +#define KISS_OPTION_VIN_POWER_OFF 0x08 // Power off when unplugged from USB #define KISS_OPTION_PTT_SIMPLEX 0x10 // Simplex PTT (the default) const char TOCALL[] = "APML00"; // Update for every feature change. @@ -223,7 +251,11 @@ struct Hardware } bool crc_ok() const { - return crc() == checksum; + auto result = (crc() == checksum); + if (!result) { + WARN("CRC mismatch %04x != %04x", checksum, crc()); + } + return result; } /** @@ -233,14 +265,6 @@ struct Hardware */ void init() { - if (crc_ok()) { - DEBUG("CRC OK"); - return; - } - - DEBUG("CRC FAILED"); - DEBUG("checksum 0x%04x != CRC 0x%04x", checksum, crc()); - txdelay = 30; ppersist = 64; slot = 10; @@ -248,7 +272,7 @@ struct Hardware duplex = 0; modem_type = ModemType::AFSK1200; output_gain = 63; - input_gain = 0; + input_gain = 0; // 0-4 on TNC3 tx_twist = 50; rx_twist = 0; log_level = Log::Level::debug; @@ -256,7 +280,7 @@ struct Hardware options = KISS_OPTION_PTT_SIMPLEX; /// Callsign. Pad unused with NUL. - strcpy((char*)mycall, "MYCALL"); + strcpy((char*)mycall, "NOCALL"); dedupe_seconds = 30; memset(aliases, 0, sizeof(aliases)); @@ -265,12 +289,11 @@ struct Hardware updatePtt(); - debug(); - DEBUG("Settings initialized"); } void debug() { +#ifdef KISS_LOGGING DEBUG("Hardware Settings (size=%d):", sizeof(Hardware)); DEBUG("TX Delay: %d", (int)txdelay); DEBUG("P* Persistence: %d", (int)ppersist); @@ -304,13 +327,11 @@ struct Hardware DEBUG(" frequency (secs): %d", (int)b.seconds); } DEBUG("Checksum: %04xs", checksum); +#endif } -#if 1 bool load(); - bool store() const; -#endif void set_txdelay(uint8_t value); void set_ppersist(uint8_t value); @@ -325,16 +346,18 @@ struct Hardware void get_alias(uint8_t alias); void set_alias(const hdlc::IoFrame* frame); + void announce_input_settings(); + }; // 812 bytes extern Hardware& settings(); struct I2C_Storage { - constexpr static const uint16_t i2c_address{0xA0}; - constexpr static const uint16_t capacity{4096}; - constexpr static const uint16_t page_size{32}; - constexpr static const uint32_t write_time{5}; + constexpr static const uint16_t i2c_address{EEPROM_ADDRESS}; + constexpr static const uint16_t capacity{EEPROM_CAPACITY}; + constexpr static const uint16_t page_size{EEPROM_PAGE_SIZE}; + constexpr static const uint32_t write_time{EEPROM_WRITE_TIME}; static bool load(void* ptr, size_t len); @@ -356,5 +379,3 @@ void reply8(uint8_t cmd, uint8_t result) __attribute__((noinline)); void reply16(uint8_t cmd, uint16_t result) __attribute__((noinline)); }}} // mobilinkd::tnc::kiss - -#endif // INMOBILINKD__TNC__KISS_HARDWARE_HPP_C_KISSHARDWARE_HPP_