From 9ecd997cad526e5615ee90d26f28ff23d608eaef Mon Sep 17 00:00:00 2001 From: Rob Riggs Date: Sun, 29 Jul 2018 21:34:53 -0500 Subject: [PATCH] Add TNC code, openocd configs, etc. --- .gitignore | 2 + .project | 1 + Inc/main.h | 5 + Src/arm_fir_f32.c | 997 +++++++++++++++++++++++++++ Src/arm_fir_init_f32.c | 96 +++ Src/main.c | 4 + Src/stm32l4xx_it.c | 14 +- TNC/AFSKModulator.hpp | 181 +++++ TNC/AFSKTestTone.cpp | 101 +++ TNC/AFSKTestTone.hpp | 32 + TNC/AGC.hpp | 61 ++ TNC/AfskDemodulator.cpp | 45 ++ TNC/AfskDemodulator.hpp | 136 ++++ TNC/AudioInput.cpp | 962 ++++++++++++++++++++++++++ TNC/AudioInput.hpp | 139 ++++ TNC/AudioLevel.cpp | 196 ++++++ TNC/AudioLevel.hpp | 34 + TNC/DelayLine.hpp | 79 +++ TNC/Digipeater.cpp | 56 ++ TNC/Digipeater.h | 20 + TNC/Digipeater.hpp | 87 +++ TNC/DigitalPLL.cpp | 32 + TNC/DigitalPLL.hpp | 116 ++++ TNC/Filter.cpp | 460 +++++++++++++ TNC/Filter.h | 42 ++ TNC/Filter.hpp | 16 + TNC/FilterCoefficients.hpp | 408 +++++++++++ TNC/FirFilter.hpp | 92 +++ TNC/GPIO.hpp | 48 ++ TNC/Goertzel.cpp | 1336 ++++++++++++++++++++++++++++++++++++ TNC/Goertzel.h | 138 ++++ TNC/HDLCEncoder.hpp | 232 +++++++ TNC/HdlcDecoder.cpp | 53 ++ TNC/HdlcDecoder.hpp | 328 +++++++++ TNC/HdlcFrame.cpp | 20 + TNC/HdlcFrame.hpp | 207 ++++++ TNC/Hysteresis.hpp | 110 +++ TNC/IOEventTask.cpp | 132 ++++ TNC/IOEventTask.h | 32 + TNC/IirFilter.hpp | 93 +++ TNC/Kiss.cpp | 71 ++ TNC/Kiss.hpp | 259 +++++++ TNC/KissHardware.cpp | 516 ++++++++++++++ TNC/KissHardware.hpp | 355 ++++++++++ TNC/KissTask.cpp | 2 + TNC/Led.cpp | 85 +++ TNC/Led.h | 79 +++ TNC/Log.cpp | 68 ++ TNC/Log.h | 68 ++ TNC/ModulatorTask.cpp | 85 +++ TNC/ModulatorTask.hpp | 38 + TNC/NRZI.hpp | 32 + TNC/NullPort.cpp | 15 + TNC/NullPort.hpp | 57 ++ TNC/PTT.hpp | 44 ++ TNC/PortInterface.cpp | 41 ++ TNC/PortInterface.h | 34 + TNC/PortInterface.hpp | 41 ++ TNC/SegmentedBuffer.hpp | 144 ++++ TNC/SerialPort.cpp | 415 +++++++++++ TNC/SerialPort.h | 23 + TNC/SerialPort.hpp | 40 ++ TNC/memory.hpp | 74 ++ TNC/power.h | 43 ++ newlib/_exit.c | 48 ++ newlib/_sbrk.c | 65 ++ newlib/_syscalls.c | 1212 ++++++++++++++++++++++++++++++++ stlink-tnc5.cfg | 14 + stm32l4x.cfg | 111 +++ 69 files changed, 11220 insertions(+), 2 deletions(-) create mode 100644 Src/arm_fir_f32.c create mode 100644 Src/arm_fir_init_f32.c create mode 100644 TNC/AFSKModulator.hpp create mode 100644 TNC/AFSKTestTone.cpp create mode 100644 TNC/AFSKTestTone.hpp create mode 100644 TNC/AGC.hpp create mode 100644 TNC/AfskDemodulator.cpp create mode 100644 TNC/AfskDemodulator.hpp create mode 100644 TNC/AudioInput.cpp create mode 100644 TNC/AudioInput.hpp create mode 100644 TNC/AudioLevel.cpp create mode 100644 TNC/AudioLevel.hpp create mode 100644 TNC/DelayLine.hpp create mode 100644 TNC/Digipeater.cpp create mode 100644 TNC/Digipeater.h create mode 100644 TNC/Digipeater.hpp create mode 100644 TNC/DigitalPLL.cpp create mode 100644 TNC/DigitalPLL.hpp create mode 100644 TNC/Filter.cpp create mode 100644 TNC/Filter.h create mode 100644 TNC/Filter.hpp create mode 100644 TNC/FilterCoefficients.hpp create mode 100644 TNC/FirFilter.hpp create mode 100644 TNC/GPIO.hpp create mode 100644 TNC/Goertzel.cpp create mode 100644 TNC/Goertzel.h create mode 100644 TNC/HDLCEncoder.hpp create mode 100644 TNC/HdlcDecoder.cpp create mode 100644 TNC/HdlcDecoder.hpp create mode 100644 TNC/HdlcFrame.cpp create mode 100644 TNC/HdlcFrame.hpp create mode 100644 TNC/Hysteresis.hpp create mode 100644 TNC/IOEventTask.cpp create mode 100644 TNC/IOEventTask.h create mode 100644 TNC/IirFilter.hpp create mode 100644 TNC/Kiss.cpp create mode 100644 TNC/Kiss.hpp create mode 100644 TNC/KissHardware.cpp create mode 100644 TNC/KissHardware.hpp create mode 100644 TNC/KissTask.cpp create mode 100644 TNC/Led.cpp create mode 100644 TNC/Led.h create mode 100644 TNC/Log.cpp create mode 100644 TNC/Log.h create mode 100644 TNC/ModulatorTask.cpp create mode 100644 TNC/ModulatorTask.hpp create mode 100644 TNC/NRZI.hpp create mode 100644 TNC/NullPort.cpp create mode 100644 TNC/NullPort.hpp create mode 100644 TNC/PTT.hpp create mode 100644 TNC/PortInterface.cpp create mode 100644 TNC/PortInterface.h create mode 100644 TNC/PortInterface.hpp create mode 100644 TNC/SegmentedBuffer.hpp create mode 100644 TNC/SerialPort.cpp create mode 100644 TNC/SerialPort.h create mode 100644 TNC/SerialPort.hpp create mode 100644 TNC/memory.hpp create mode 100644 TNC/power.h create mode 100644 newlib/_exit.c create mode 100644 newlib/_sbrk.c create mode 100644 newlib/_syscalls.c create mode 100644 stlink-tnc5.cfg create mode 100644 stm32l4x.cfg diff --git a/.gitignore b/.gitignore index 3853b24..c9c8156 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,8 @@ *.exe *.out *.app +ARM_Debug/ +ARM_Release/ # STM32CubeMX garbage files *.launch diff --git a/.project b/.project index 9e8496f..b14df9b 100644 --- a/.project +++ b/.project @@ -68,5 +68,6 @@ org.eclipse.cdt.core.cnature org.eclipse.cdt.managedbuilder.core.managedBuildNature org.eclipse.cdt.managedbuilder.core.ScannerConfigNature + org.eclipse.cdt.core.ccnature diff --git a/Inc/main.h b/Inc/main.h index 19c0f2f..04f69cb 100644 --- a/Inc/main.h +++ b/Inc/main.h @@ -98,6 +98,11 @@ /* USER CODE BEGIN Private defines */ +#define CMD_USER_BUTTON_DOWN 1 +#define CMD_USER_BUTTON_UP 2 +#define CMD_SET_PTT_SIMPLEX 3 +#define CMD_SET_PTT_MULTIPLEX 4 + /* USER CODE END Private defines */ #ifdef __cplusplus diff --git a/Src/arm_fir_f32.c b/Src/arm_fir_f32.c new file mode 100644 index 0000000..3ecb7b5 --- /dev/null +++ b/Src/arm_fir_f32.c @@ -0,0 +1,997 @@ +/* ---------------------------------------------------------------------- +* 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_f32.c +* +* Description: Floating-point FIR filter processing 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 +*/ + +/** +* @defgroup FIR Finite Impulse Response (FIR) Filters +* +* This set of functions implements Finite Impulse Response (FIR) filters +* for Q7, Q15, Q31, and floating-point data types. Fast versions of Q15 and Q31 are also provided. +* The functions operate on blocks of input and output data and each call to the function processes +* blockSize samples through the filter. pSrc and +* pDst points to input and output arrays containing blockSize values. +* +* \par Algorithm: +* The FIR filter algorithm is based upon a sequence of multiply-accumulate (MAC) operations. +* Each filter coefficient b[n] is multiplied by a state variable which equals a previous input sample x[n]. +*
  
+*    y[n] = b[0] * x[n] + b[1] * x[n-1] + b[2] * x[n-2] + ...+ b[numTaps-1] * x[n-numTaps+1]  
+* 
+* \par +* \image html FIR.gif "Finite Impulse Response filter" +* \par +* pCoeffs points to a coefficient array of size numTaps. +* Coefficients are stored in time reversed order. +* \par +*
  
+*    {b[numTaps-1], b[numTaps-2], b[N-2], ..., b[1], b[0]}  
+* 
+* \par +* pState points to a state array of size numTaps + blockSize - 1. +* Samples in the state buffer are stored in the following order. +* \par +*
  
+*    {x[n-numTaps+1], x[n-numTaps], x[n-numTaps-1], x[n-numTaps-2]....x[0], x[1], ..., x[blockSize-1]}  
+* 
+* \par +* Note that the length of the state buffer exceeds the length of the coefficient array by blockSize-1. +* The increased state buffer length allows circular addressing, which is traditionally used in the FIR filters, +* to be avoided and yields a significant speed improvement. +* The state variables are updated after each block of data is processed; the coefficients are untouched. +* \par Instance Structure +* The coefficients and state variables for a filter are stored together in an instance data structure. +* A separate instance structure must be defined for each filter. +* Coefficient arrays may be shared among several instances while state variable arrays cannot be shared. +* There are separate instance structure declarations for each of the 4 supported data types. +* +* \par Initialization Functions +* There is also an associated initialization function for each data type. +* The initialization function performs the following operations: +* - Sets the values of the internal structure fields. +* - Zeros out the values in the state buffer. +* To do this manually without calling the init function, assign the follow subfields of the instance structure: +* numTaps, pCoeffs, pState. Also set all of the values in pState to zero. +* +* \par +* Use of the initialization function is optional. +* However, if the initialization function is used, then the instance structure cannot be placed into a const data section. +* To place an instance structure into a const data section, the instance structure must be manually initialized. +* Set the values in the state buffer to zeros before static initialization. +* The code below statically initializes each of the 4 different data type filter instance structures +*
  
+*arm_fir_instance_f32 S = {numTaps, pState, pCoeffs};  
+*arm_fir_instance_q31 S = {numTaps, pState, pCoeffs};  
+*arm_fir_instance_q15 S = {numTaps, pState, pCoeffs};  
+*arm_fir_instance_q7 S =  {numTaps, pState, pCoeffs};  
+* 
+* +* where numTaps is the number of filter coefficients in the filter; pState is the address of the state buffer; +* pCoeffs is the address of the coefficient buffer. +* +* \par Fixed-Point Behavior +* Care must be taken when using the fixed-point versions of the FIR filter functions. +* In particular, the overflow and saturation behavior of the accumulator used in each function must be considered. +* Refer to the function specific documentation below for usage guidelines. +*/ + +/** +* @addtogroup FIR +* @{ +*/ + +/** +* +* @param[in] *S points to an instance of the floating-point 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. +* +*/ + +#if defined(ARM_MATH_CM7) + +void arm_fir_f32( +const arm_fir_instance_f32 * S, +float32_t * pSrc, +float32_t * pDst, +uint32_t blockSize) +{ + float32_t *pState = S->pState; /* State pointer */ + float32_t *pCoeffs = S->pCoeffs; /* Coefficient pointer */ + float32_t *pStateCurnt; /* Points to the current sample of the state */ + float32_t *px, *pb; /* Temporary pointers for state and coefficient buffers */ + float32_t acc0, acc1, acc2, acc3, acc4, acc5, acc6, acc7; /* Accumulators */ + float32_t x0, x1, x2, x3, x4, x5, x6, x7, c0; /* Temporary variables to hold state and coefficient values */ + uint32_t numTaps = S->numTaps; /* Number of filter coefficients in the filter */ + uint32_t i, 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 8 output values simultaneously. + * The variables acc0 ... acc7 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 >> 3; + + /* First part of the processing with loop unrolling. Compute 8 outputs at a time. + ** a second loop below computes the remaining 1 to 7 samples. */ + while(blkCnt > 0u) + { + /* Copy four new input samples into the state buffer */ + *pStateCurnt++ = *pSrc++; + *pStateCurnt++ = *pSrc++; + *pStateCurnt++ = *pSrc++; + *pStateCurnt++ = *pSrc++; + + /* Set all accumulators to zero */ + acc0 = 0.0f; + acc1 = 0.0f; + acc2 = 0.0f; + acc3 = 0.0f; + acc4 = 0.0f; + acc5 = 0.0f; + acc6 = 0.0f; + acc7 = 0.0f; + + /* Initialize state pointer */ + px = pState; + + /* Initialize coeff pointer */ + pb = (pCoeffs); + + /* This is separated from the others to avoid + * a call to __aeabi_memmove which would be slower + */ + *pStateCurnt++ = *pSrc++; + *pStateCurnt++ = *pSrc++; + *pStateCurnt++ = *pSrc++; + *pStateCurnt++ = *pSrc++; + + /* Read the first seven samples from the state buffer: x[n-numTaps], x[n-numTaps-1], x[n-numTaps-2] */ + x0 = *px++; + x1 = *px++; + x2 = *px++; + x3 = *px++; + x4 = *px++; + x5 = *px++; + x6 = *px++; + + /* Loop unrolling. Process 8 taps at a time. */ + tapCnt = numTaps >> 3u; + + /* Loop over the number of taps. Unroll by a factor of 8. + ** Repeat until we've computed numTaps-8 coefficients. */ + while(tapCnt > 0u) + { + /* Read the b[numTaps-1] coefficient */ + c0 = *(pb++); + + /* Read x[n-numTaps-3] sample */ + x7 = *(px++); + + /* acc0 += b[numTaps-1] * x[n-numTaps] */ + acc0 += x0 * c0; + + /* acc1 += b[numTaps-1] * x[n-numTaps-1] */ + acc1 += x1 * c0; + + /* acc2 += b[numTaps-1] * x[n-numTaps-2] */ + acc2 += x2 * c0; + + /* acc3 += b[numTaps-1] * x[n-numTaps-3] */ + acc3 += x3 * c0; + + /* acc4 += b[numTaps-1] * x[n-numTaps-4] */ + acc4 += x4 * c0; + + /* acc1 += b[numTaps-1] * x[n-numTaps-5] */ + acc5 += x5 * c0; + + /* acc2 += b[numTaps-1] * x[n-numTaps-6] */ + acc6 += x6 * c0; + + /* acc3 += b[numTaps-1] * x[n-numTaps-7] */ + acc7 += x7 * c0; + + /* Read the b[numTaps-2] coefficient */ + c0 = *(pb++); + + /* Read x[n-numTaps-4] sample */ + x0 = *(px++); + + /* Perform the multiply-accumulate */ + acc0 += x1 * c0; + acc1 += x2 * c0; + acc2 += x3 * c0; + acc3 += x4 * c0; + acc4 += x5 * c0; + acc5 += x6 * c0; + acc6 += x7 * c0; + acc7 += x0 * c0; + + /* Read the b[numTaps-3] coefficient */ + c0 = *(pb++); + + /* Read x[n-numTaps-5] sample */ + x1 = *(px++); + + /* Perform the multiply-accumulates */ + acc0 += x2 * c0; + acc1 += x3 * c0; + acc2 += x4 * c0; + acc3 += x5 * c0; + acc4 += x6 * c0; + acc5 += x7 * c0; + acc6 += x0 * c0; + acc7 += x1 * c0; + + /* Read the b[numTaps-4] coefficient */ + c0 = *(pb++); + + /* Read x[n-numTaps-6] sample */ + x2 = *(px++); + + /* Perform the multiply-accumulates */ + acc0 += x3 * c0; + acc1 += x4 * c0; + acc2 += x5 * c0; + acc3 += x6 * c0; + acc4 += x7 * c0; + acc5 += x0 * c0; + acc6 += x1 * c0; + acc7 += x2 * c0; + + /* Read the b[numTaps-4] coefficient */ + c0 = *(pb++); + + /* Read x[n-numTaps-6] sample */ + x3 = *(px++); + /* Perform the multiply-accumulates */ + acc0 += x4 * c0; + acc1 += x5 * c0; + acc2 += x6 * c0; + acc3 += x7 * c0; + acc4 += x0 * c0; + acc5 += x1 * c0; + acc6 += x2 * c0; + acc7 += x3 * c0; + + /* Read the b[numTaps-4] coefficient */ + c0 = *(pb++); + + /* Read x[n-numTaps-6] sample */ + x4 = *(px++); + + /* Perform the multiply-accumulates */ + acc0 += x5 * c0; + acc1 += x6 * c0; + acc2 += x7 * c0; + acc3 += x0 * c0; + acc4 += x1 * c0; + acc5 += x2 * c0; + acc6 += x3 * c0; + acc7 += x4 * c0; + + /* Read the b[numTaps-4] coefficient */ + c0 = *(pb++); + + /* Read x[n-numTaps-6] sample */ + x5 = *(px++); + + /* Perform the multiply-accumulates */ + acc0 += x6 * c0; + acc1 += x7 * c0; + acc2 += x0 * c0; + acc3 += x1 * c0; + acc4 += x2 * c0; + acc5 += x3 * c0; + acc6 += x4 * c0; + acc7 += x5 * c0; + + /* Read the b[numTaps-4] coefficient */ + c0 = *(pb++); + + /* Read x[n-numTaps-6] sample */ + x6 = *(px++); + + /* Perform the multiply-accumulates */ + acc0 += x7 * c0; + acc1 += x0 * c0; + acc2 += x1 * c0; + acc3 += x2 * c0; + acc4 += x3 * c0; + acc5 += x4 * c0; + acc6 += x5 * c0; + acc7 += x6 * c0; + + tapCnt--; + } + + /* If the filter length is not a multiple of 8, compute the remaining filter taps */ + tapCnt = numTaps % 0x8u; + + while(tapCnt > 0u) + { + /* Read coefficients */ + c0 = *(pb++); + + /* Fetch 1 state variable */ + x7 = *(px++); + + /* Perform the multiply-accumulates */ + acc0 += x0 * c0; + acc1 += x1 * c0; + acc2 += x2 * c0; + acc3 += x3 * c0; + acc4 += x4 * c0; + acc5 += x5 * c0; + acc6 += x6 * c0; + acc7 += x7 * c0; + + /* Reuse the present sample states for next sample */ + x0 = x1; + x1 = x2; + x2 = x3; + x3 = x4; + x4 = x5; + x5 = x6; + x6 = x7; + + /* Decrement the loop counter */ + tapCnt--; + } + + /* Advance the state pointer by 8 to process the next group of 8 samples */ + pState = pState + 8; + + /* The results in the 8 accumulators, store in the destination buffer. */ + *pDst++ = acc0; + *pDst++ = acc1; + *pDst++ = acc2; + *pDst++ = acc3; + *pDst++ = acc4; + *pDst++ = acc5; + *pDst++ = acc6; + *pDst++ = acc7; + + blkCnt--; + } + + /* If the blockSize is not a multiple of 8, compute any remaining output samples here. + ** No loop unrolling is used. */ + blkCnt = blockSize % 0x8u; + + while(blkCnt > 0u) + { + /* Copy one sample at a time into state buffer */ + *pStateCurnt++ = *pSrc++; + + /* Set the accumulator to zero */ + acc0 = 0.0f; + + /* Initialize state pointer */ + px = pState; + + /* Initialize Coefficient pointer */ + pb = (pCoeffs); + + i = numTaps; + + /* Perform the multiply-accumulates */ + do + { + acc0 += *px++ * *pb++; + i--; + + } while(i > 0u); + + /* The result is store in the destination buffer. */ + *pDst++ = acc0; + + /* Advance state pointer by 1 for the next sample */ + pState = pState + 1; + + blkCnt--; + } + + /* Processing is complete. + ** Now copy the last numTaps - 1 samples to the start 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; + + tapCnt = (numTaps - 1u) >> 2u; + + /* copy data */ + while(tapCnt > 0u) + { + *pStateCurnt++ = *pState++; + *pStateCurnt++ = *pState++; + *pStateCurnt++ = *pState++; + *pStateCurnt++ = *pState++; + + /* Decrement the loop counter */ + tapCnt--; + } + + /* Calculate remaining number of copies */ + tapCnt = (numTaps - 1u) % 0x4u; + + /* Copy the remaining q31_t data */ + while(tapCnt > 0u) + { + *pStateCurnt++ = *pState++; + + /* Decrement the loop counter */ + tapCnt--; + } +} + +#elif defined(ARM_MATH_CM0_FAMILY) + +void arm_fir_f32( +const arm_fir_instance_f32 * S, +float32_t * pSrc, +float32_t * pDst, +uint32_t blockSize) +{ + float32_t *pState = S->pState; /* State pointer */ + float32_t *pCoeffs = S->pCoeffs; /* Coefficient pointer */ + float32_t *pStateCurnt; /* Points to the current sample of the state */ + float32_t *px, *pb; /* Temporary pointers for state and coefficient buffers */ + uint32_t numTaps = S->numTaps; /* Number of filter coefficients in the filter */ + uint32_t i, tapCnt, blkCnt; /* Loop counters */ + + /* Run the below code for Cortex-M0 */ + + float32_t acc; + + /* 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)]); + + /* Initialize blkCnt with blockSize */ + blkCnt = blockSize; + + while(blkCnt > 0u) + { + /* Copy one sample at a time into state buffer */ + *pStateCurnt++ = *pSrc++; + + /* Set the accumulator to zero */ + acc = 0.0f; + + /* Initialize state pointer */ + px = pState; + + /* Initialize Coefficient pointer */ + pb = pCoeffs; + + i = numTaps; + + /* Perform the multiply-accumulates */ + do + { + /* acc = 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] */ + acc += *px++ * *pb++; + i--; + + } while(i > 0u); + + /* The result is store in the destination buffer. */ + *pDst++ = acc; + + /* Advance state pointer by 1 for the next sample */ + pState = pState + 1; + + blkCnt--; + } + + /* Processing is complete. + ** Now copy the last numTaps - 1 samples to the starting 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; + + /* Copy numTaps number of values */ + tapCnt = numTaps - 1u; + + /* Copy data */ + while(tapCnt > 0u) + { + *pStateCurnt++ = *pState++; + + /* Decrement the loop counter */ + tapCnt--; + } + +} + +#else + +/* Run the below code for Cortex-M4 and Cortex-M3 */ + +void arm_fir_f32( +const arm_fir_instance_f32 * S, +float32_t * pSrc, +float32_t * pDst, +uint32_t blockSize) +{ + float32_t *pState = S->pState; /* State pointer */ + float32_t *pCoeffs = S->pCoeffs; /* Coefficient pointer */ + float32_t *pStateCurnt; /* Points to the current sample of the state */ + float32_t *px, *pb; /* Temporary pointers for state and coefficient buffers */ + float32_t acc0, acc1, acc2, acc3, acc4, acc5, acc6, acc7; /* Accumulators */ + float32_t x0, x1, x2, x3, x4, x5, x6, x7, c0; /* Temporary variables to hold state and coefficient values */ + uint32_t numTaps = S->numTaps; /* Number of filter coefficients in the filter */ + uint32_t i, tapCnt, blkCnt; /* Loop counters */ + float32_t p0,p1,p2,p3,p4,p5,p6,p7; /* Temporary product values */ + + /* 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 8 output values simultaneously. + * The variables acc0 ... acc7 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 >> 3; + + /* First part of the processing with loop unrolling. Compute 8 outputs at a time. + ** a second loop below computes the remaining 1 to 7 samples. */ + while(blkCnt > 0u) + { + /* Copy four new input samples into the state buffer */ + *pStateCurnt++ = *pSrc++; + *pStateCurnt++ = *pSrc++; + *pStateCurnt++ = *pSrc++; + *pStateCurnt++ = *pSrc++; + + /* Set all accumulators to zero */ + acc0 = 0.0f; + acc1 = 0.0f; + acc2 = 0.0f; + acc3 = 0.0f; + acc4 = 0.0f; + acc5 = 0.0f; + acc6 = 0.0f; + acc7 = 0.0f; + + /* Initialize state pointer */ + px = pState; + + /* Initialize coeff pointer */ + pb = (pCoeffs); + + /* This is separated from the others to avoid + * a call to __aeabi_memmove which would be slower + */ + *pStateCurnt++ = *pSrc++; + *pStateCurnt++ = *pSrc++; + *pStateCurnt++ = *pSrc++; + *pStateCurnt++ = *pSrc++; + + /* Read the first seven samples from the state buffer: x[n-numTaps], x[n-numTaps-1], x[n-numTaps-2] */ + x0 = *px++; + x1 = *px++; + x2 = *px++; + x3 = *px++; + x4 = *px++; + x5 = *px++; + x6 = *px++; + + /* Loop unrolling. Process 8 taps at a time. */ + tapCnt = numTaps >> 3u; + + /* Loop over the number of taps. Unroll by a factor of 8. + ** Repeat until we've computed numTaps-8 coefficients. */ + while(tapCnt > 0u) + { + /* Read the b[numTaps-1] coefficient */ + c0 = *(pb++); + + /* Read x[n-numTaps-3] sample */ + x7 = *(px++); + + /* acc0 += b[numTaps-1] * x[n-numTaps] */ + p0 = x0 * c0; + + /* acc1 += b[numTaps-1] * x[n-numTaps-1] */ + p1 = x1 * c0; + + /* acc2 += b[numTaps-1] * x[n-numTaps-2] */ + p2 = x2 * c0; + + /* acc3 += b[numTaps-1] * x[n-numTaps-3] */ + p3 = x3 * c0; + + /* acc4 += b[numTaps-1] * x[n-numTaps-4] */ + p4 = x4 * c0; + + /* acc1 += b[numTaps-1] * x[n-numTaps-5] */ + p5 = x5 * c0; + + /* acc2 += b[numTaps-1] * x[n-numTaps-6] */ + p6 = x6 * c0; + + /* acc3 += b[numTaps-1] * x[n-numTaps-7] */ + p7 = x7 * c0; + + /* Read the b[numTaps-2] coefficient */ + c0 = *(pb++); + + /* Read x[n-numTaps-4] sample */ + x0 = *(px++); + + acc0 += p0; + acc1 += p1; + acc2 += p2; + acc3 += p3; + acc4 += p4; + acc5 += p5; + acc6 += p6; + acc7 += p7; + + + /* Perform the multiply-accumulate */ + p0 = x1 * c0; + p1 = x2 * c0; + p2 = x3 * c0; + p3 = x4 * c0; + p4 = x5 * c0; + p5 = x6 * c0; + p6 = x7 * c0; + p7 = x0 * c0; + + /* Read the b[numTaps-3] coefficient */ + c0 = *(pb++); + + /* Read x[n-numTaps-5] sample */ + x1 = *(px++); + + acc0 += p0; + acc1 += p1; + acc2 += p2; + acc3 += p3; + acc4 += p4; + acc5 += p5; + acc6 += p6; + acc7 += p7; + + /* Perform the multiply-accumulates */ + p0 = x2 * c0; + p1 = x3 * c0; + p2 = x4 * c0; + p3 = x5 * c0; + p4 = x6 * c0; + p5 = x7 * c0; + p6 = x0 * c0; + p7 = x1 * c0; + + /* Read the b[numTaps-4] coefficient */ + c0 = *(pb++); + + /* Read x[n-numTaps-6] sample */ + x2 = *(px++); + + acc0 += p0; + acc1 += p1; + acc2 += p2; + acc3 += p3; + acc4 += p4; + acc5 += p5; + acc6 += p6; + acc7 += p7; + + /* Perform the multiply-accumulates */ + p0 = x3 * c0; + p1 = x4 * c0; + p2 = x5 * c0; + p3 = x6 * c0; + p4 = x7 * c0; + p5 = x0 * c0; + p6 = x1 * c0; + p7 = x2 * c0; + + /* Read the b[numTaps-4] coefficient */ + c0 = *(pb++); + + /* Read x[n-numTaps-6] sample */ + x3 = *(px++); + + acc0 += p0; + acc1 += p1; + acc2 += p2; + acc3 += p3; + acc4 += p4; + acc5 += p5; + acc6 += p6; + acc7 += p7; + + /* Perform the multiply-accumulates */ + p0 = x4 * c0; + p1 = x5 * c0; + p2 = x6 * c0; + p3 = x7 * c0; + p4 = x0 * c0; + p5 = x1 * c0; + p6 = x2 * c0; + p7 = x3 * c0; + + /* Read the b[numTaps-4] coefficient */ + c0 = *(pb++); + + /* Read x[n-numTaps-6] sample */ + x4 = *(px++); + + acc0 += p0; + acc1 += p1; + acc2 += p2; + acc3 += p3; + acc4 += p4; + acc5 += p5; + acc6 += p6; + acc7 += p7; + + /* Perform the multiply-accumulates */ + p0 = x5 * c0; + p1 = x6 * c0; + p2 = x7 * c0; + p3 = x0 * c0; + p4 = x1 * c0; + p5 = x2 * c0; + p6 = x3 * c0; + p7 = x4 * c0; + + /* Read the b[numTaps-4] coefficient */ + c0 = *(pb++); + + /* Read x[n-numTaps-6] sample */ + x5 = *(px++); + + acc0 += p0; + acc1 += p1; + acc2 += p2; + acc3 += p3; + acc4 += p4; + acc5 += p5; + acc6 += p6; + acc7 += p7; + + /* Perform the multiply-accumulates */ + p0 = x6 * c0; + p1 = x7 * c0; + p2 = x0 * c0; + p3 = x1 * c0; + p4 = x2 * c0; + p5 = x3 * c0; + p6 = x4 * c0; + p7 = x5 * c0; + + /* Read the b[numTaps-4] coefficient */ + c0 = *(pb++); + + /* Read x[n-numTaps-6] sample */ + x6 = *(px++); + + acc0 += p0; + acc1 += p1; + acc2 += p2; + acc3 += p3; + acc4 += p4; + acc5 += p5; + acc6 += p6; + acc7 += p7; + + /* Perform the multiply-accumulates */ + p0 = x7 * c0; + p1 = x0 * c0; + p2 = x1 * c0; + p3 = x2 * c0; + p4 = x3 * c0; + p5 = x4 * c0; + p6 = x5 * c0; + p7 = x6 * c0; + + tapCnt--; + + acc0 += p0; + acc1 += p1; + acc2 += p2; + acc3 += p3; + acc4 += p4; + acc5 += p5; + acc6 += p6; + acc7 += p7; + } + + /* If the filter length is not a multiple of 8, compute the remaining filter taps */ + tapCnt = numTaps % 0x8u; + + while(tapCnt > 0u) + { + /* Read coefficients */ + c0 = *(pb++); + + /* Fetch 1 state variable */ + x7 = *(px++); + + /* Perform the multiply-accumulates */ + p0 = x0 * c0; + p1 = x1 * c0; + p2 = x2 * c0; + p3 = x3 * c0; + p4 = x4 * c0; + p5 = x5 * c0; + p6 = x6 * c0; + p7 = x7 * c0; + + /* Reuse the present sample states for next sample */ + x0 = x1; + x1 = x2; + x2 = x3; + x3 = x4; + x4 = x5; + x5 = x6; + x6 = x7; + + acc0 += p0; + acc1 += p1; + acc2 += p2; + acc3 += p3; + acc4 += p4; + acc5 += p5; + acc6 += p6; + acc7 += p7; + + /* Decrement the loop counter */ + tapCnt--; + } + + /* Advance the state pointer by 8 to process the next group of 8 samples */ + pState = pState + 8; + + /* The results in the 8 accumulators, store in the destination buffer. */ + *pDst++ = acc0; + *pDst++ = acc1; + *pDst++ = acc2; + *pDst++ = acc3; + *pDst++ = acc4; + *pDst++ = acc5; + *pDst++ = acc6; + *pDst++ = acc7; + + blkCnt--; + } + + /* If the blockSize is not a multiple of 8, compute any remaining output samples here. + ** No loop unrolling is used. */ + blkCnt = blockSize % 0x8u; + + while(blkCnt > 0u) + { + /* Copy one sample at a time into state buffer */ + *pStateCurnt++ = *pSrc++; + + /* Set the accumulator to zero */ + acc0 = 0.0f; + + /* Initialize state pointer */ + px = pState; + + /* Initialize Coefficient pointer */ + pb = (pCoeffs); + + i = numTaps; + + /* Perform the multiply-accumulates */ + do + { + acc0 += *px++ * *pb++; + i--; + + } while(i > 0u); + + /* The result is store in the destination buffer. */ + *pDst++ = acc0; + + /* Advance state pointer by 1 for the next sample */ + pState = pState + 1; + + blkCnt--; + } + + /* Processing is complete. + ** Now copy the last numTaps - 1 samples to the start 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; + + tapCnt = (numTaps - 1u) >> 2u; + + /* copy data */ + while(tapCnt > 0u) + { + *pStateCurnt++ = *pState++; + *pStateCurnt++ = *pState++; + *pStateCurnt++ = *pState++; + *pStateCurnt++ = *pState++; + + /* Decrement the loop counter */ + tapCnt--; + } + + /* Calculate remaining number of copies */ + tapCnt = (numTaps - 1u) % 0x4u; + + /* Copy the remaining q31_t data */ + while(tapCnt > 0u) + { + *pStateCurnt++ = *pState++; + + /* Decrement the loop counter */ + tapCnt--; + } +} + +#endif + +/** +* @} end of FIR group +*/ diff --git a/Src/arm_fir_init_f32.c b/Src/arm_fir_init_f32.c new file mode 100644 index 0000000..92bdc9e --- /dev/null +++ b/Src/arm_fir_init_f32.c @@ -0,0 +1,96 @@ +/*----------------------------------------------------------------------------- +* 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_f32.c +* +* Description: Floating-point 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 + * @{ + */ + +/** + * @details + * + * @param[in,out] *S points to an instance of the floating-point FIR filter structure. + * @param[in] numTaps Number of filter coefficients in the filter. + * @param[in] *pCoeffs points to the filter coefficients buffer. + * @param[in] *pState points to the state buffer. + * @param[in] blockSize number of samples that are processed per call. + * @return none. + * + * 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]}    
+ * 
+ * \par + * pState points to the array of state variables. + * pState is of length numTaps+blockSize-1 samples, where blockSize is the number of input samples processed by each call to arm_fir_f32(). + */ + +void arm_fir_init_f32( + arm_fir_instance_f32 * S, + uint16_t numTaps, + float32_t * pCoeffs, + float32_t * pState, + uint32_t blockSize) +{ + /* Assign filter taps */ + S->numTaps = numTaps; + + /* Assign coefficient pointer */ + S->pCoeffs = pCoeffs; + + /* Clear state buffer and the size of state buffer is (blockSize + numTaps - 1) */ + memset(pState, 0, (numTaps + (blockSize - 1u)) * sizeof(float32_t)); + + /* Assign state pointer */ + S->pState = pState; + +} + +/** + * @} end of FIR group + */ diff --git a/Src/main.c b/Src/main.c index c0671e1..2c148e5 100644 --- a/Src/main.c +++ b/Src/main.c @@ -284,6 +284,9 @@ int main(void) /* USER CODE BEGIN RTOS_QUEUES */ /* add queues, ... */ + HAL_DAC_Start(&hdac1, DAC_CHANNEL_2); + HAL_DAC_SetValue(&hdac1, DAC_CHANNEL_2, DAC_ALIGN_12B_R, 1024); + HAL_OPAMP_Start(&hopamp1); /* USER CODE END RTOS_QUEUES */ @@ -816,6 +819,7 @@ void startDefaultTask(void const * argument) { /* USER CODE BEGIN 5 */ + /* Infinite loop */ for(;;) { diff --git a/Src/stm32l4xx_it.c b/Src/stm32l4xx_it.c index d93fb0e..ee0748f 100644 --- a/Src/stm32l4xx_it.c +++ b/Src/stm32l4xx_it.c @@ -37,6 +37,9 @@ #include "cmsis_os.h" /* USER CODE BEGIN 0 */ +#include "main.h" + +extern osMessageQId ioEventQueueHandle; /* USER CODE END 0 */ @@ -82,7 +85,10 @@ void SysTick_Handler(void) void EXTI1_IRQHandler(void) { /* USER CODE BEGIN EXTI1_IRQn 0 */ - + if (HAL_GPIO_ReadPin(BUTTON_AUDIO_IN_ADJUST_GPIO_Port, BUTTON_AUDIO_IN_ADJUST_Pin)) + osMessagePut(ioEventQueueHandle, CMD_USER_BUTTON_UP, 0); + else + osMessagePut(ioEventQueueHandle, CMD_USER_BUTTON_DOWN, 0); /* USER CODE END EXTI1_IRQn 0 */ HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_1); /* USER CODE BEGIN EXTI1_IRQn 1 */ @@ -180,7 +186,11 @@ void TIM2_IRQHandler(void) void USART2_IRQHandler(void) { /* USER CODE BEGIN USART2_IRQn 0 */ - + if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE)) { + idleInterruptCallback(&huart2); + __HAL_UART_CLEAR_FLAG(&huart2, UART_FLAG_IDLE); + return; + } /* USER CODE END USART2_IRQn 0 */ HAL_UART_IRQHandler(&huart2); /* USER CODE BEGIN USART2_IRQn 1 */ diff --git a/TNC/AFSKModulator.hpp b/TNC/AFSKModulator.hpp new file mode 100644 index 0000000..6d0821c --- /dev/null +++ b/TNC/AFSKModulator.hpp @@ -0,0 +1,181 @@ +// Copyright 2015 Robert C. Riggs +// All rights reserved. + +#ifndef MOBILINKD__TNC__AFSK_MODULATOR_HPP_ +#define MOBILINKD__TNC__AFSK_MODULATOR_HPP_ + +#include + +#include "PTT.hpp" +#include "Log.h" + +#include "stm32l4xx_hal.h" +#include "cmsis_os.h" +#include "main.h" + +#include + +extern osMessageQId hdlcOutputQueueHandle; +extern osMessageQId dacOutputQueueHandle; +extern TIM_HandleTypeDef htim7; +extern DAC_HandleTypeDef hdac1; + + +namespace mobilinkd { namespace tnc { + +const size_t SIN_TABLE_LEN = 264; + +const int16_t sin_table[SIN_TABLE_LEN] = { + 2048, 2096, 2145, 2194, 2242, 2291, 2339, 2387, + 2435, 2483, 2530, 2577, 2624, 2671, 2717, 2763, + 2808, 2853, 2898, 2942, 2985, 3029, 3071, 3113, + 3154, 3195, 3235, 3274, 3313, 3351, 3388, 3424, + 3460, 3495, 3529, 3562, 3595, 3626, 3657, 3686, + 3715, 3743, 3770, 3795, 3820, 3844, 3867, 3889, + 3910, 3929, 3948, 3965, 3982, 3997, 4012, 4025, + 4037, 4048, 4058, 4066, 4074, 4080, 4085, 4089, + 4092, 4094, 4095, 4094, 4092, 4089, 4085, 4080, + 4074, 4066, 4058, 4048, 4037, 4025, 4012, 3997, + 3982, 3965, 3948, 3929, 3910, 3889, 3867, 3844, + 3820, 3795, 3770, 3743, 3715, 3686, 3657, 3626, + 3595, 3562, 3529, 3495, 3460, 3424, 3388, 3351, + 3313, 3274, 3235, 3195, 3154, 3113, 3071, 3029, + 2985, 2942, 2898, 2853, 2808, 2763, 2717, 2671, + 2624, 2577, 2530, 2483, 2435, 2387, 2339, 2291, + 2242, 2194, 2145, 2096, 2048, 1999, 1950, 1901, + 1853, 1804, 1756, 1708, 1660, 1612, 1565, 1518, + 1471, 1424, 1378, 1332, 1287, 1242, 1197, 1153, + 1110, 1066, 1024, 982, 941, 900, 860, 821, + 782, 744, 707, 671, 635, 600, 566, 533, + 500, 469, 438, 409, 380, 352, 325, 300, + 275, 251, 228, 206, 185, 166, 147, 130, + 113, 98, 83, 70, 58, 47, 37, 29, + 21, 15, 10, 6, 3, 1, 1, 1, + 3, 6, 10, 15, 21, 29, 37, 47, + 58, 70, 83, 98, 113, 130, 147, 166, + 185, 206, 228, 251, 275, 300, 325, 352, + 380, 409, 438, 469, 500, 533, 566, 600, + 635, 671, 707, 744, 782, 821, 860, 900, + 941, 982, 1024, 1066, 1110, 1153, 1197, 1242, + 1287, 1332, 1378, 1424, 1471, 1518, 1565, 1612, + 1660, 1708, 1756, 1804, 1853, 1901, 1950, 1999, +}; + + +struct AFSKModulator { + + static const size_t DAC_BUFFER_LEN = 44; + static const size_t BIT_LEN = DAC_BUFFER_LEN / 2; + static const size_t MARK_SKIP = 12; + static const size_t SPACE_SKIP = 22; + + size_t pos_; + int running_; + osMessageQId dacOutputQueueHandle_; + PTT* ptt_; + uint8_t twist_; + uint16_t volume_{4096}; + uint16_t buffer_[DAC_BUFFER_LEN]; + + AFSKModulator(osMessageQId queue, PTT* ptt) + : pos_(0), running_(-1), dacOutputQueueHandle_(queue), ptt_(ptt) + , twist_(50), buffer_() + { + for (size_t i = 0; i != DAC_BUFFER_LEN; i++) + buffer_[i] = 2048; + } + + void set_volume(uint16_t v) + { + v = std::max(256, v); + v = std::min(4096, v); + volume_ = v; + } + + void set_ptt(PTT* ptt) { + if (ptt == ptt_) return; // No change. + auto old = ptt_; + ptt_ = ptt; + old->off(); + if (running_ == 1) { + ptt_->on(); + } + } + + void set_twist(uint8_t twist) {twist_ = twist;} + + void send(bool bit) { + if (running_ != 1) { + osMessagePut(dacOutputQueueHandle_, bit, osWaitForever); + running_ += 1; + if (running_ == 1) { + ptt_->on(); + HAL_TIM_Base_Start(&htim7); + HAL_DAC_Start_DMA(&hdac1, DAC_CHANNEL_1, (uint32_t*) buffer_, DAC_BUFFER_LEN, DAC_ALIGN_12B_R); + } + } + + osMessagePut(dacOutputQueueHandle_, bit, osWaitForever); + } + + void fill(uint16_t* buffer, bool bit) { + for (size_t i = 0; i != BIT_LEN; i++) { + int s = sin_table[pos_]; + s -= 2048; + s *= volume_; + s >>= 12; + s += 2048; + + if (bit) { + if (twist_ > 50) { + s = (((s - 2048) * (100 - twist_)) / 50) + 2048; + } + } else { + if (twist_ < 50) { + s = (((s - 2048) * twist_) / 50) + 2048; + } + } + if (s < 0 or s > 4095) { + DEBUG("DAC inversion (%d)", s); + } + *buffer = uint16_t(s); + ++buffer; + pos_ += (bit ? MARK_SKIP : SPACE_SKIP); + if (pos_ >= SIN_TABLE_LEN) pos_ -= SIN_TABLE_LEN; + } + } + + void fill_first(bool bit) { + fill(buffer_, bit); + } + + void fill_last(bool bit) { + fill(buffer_ + BIT_LEN, bit); + } + + void empty() { + if (running_ != -1) { + --running_; + if (running_ == -1) { + HAL_DAC_Stop_DMA(&hdac1, DAC_CHANNEL_1); + HAL_TIM_Base_Stop(&htim7); + ptt_->off(); + } + } + } + + void abort() { + running_ = -1; + HAL_DAC_Stop_DMA(&hdac1, DAC_CHANNEL_1); + HAL_TIM_Base_Stop(&htim7); + ptt_->off(); + // Drain the queue. + + while (osMessageGet(dacOutputQueueHandle_, 0).status == osEventMessage); + } +}; + +}} // mobilinkd::tnc + + +#endif // MOBILINKD__TNC__AFSK_MODULATOR_HPP_ diff --git a/TNC/AFSKTestTone.cpp b/TNC/AFSKTestTone.cpp new file mode 100644 index 0000000..16db756 --- /dev/null +++ b/TNC/AFSKTestTone.cpp @@ -0,0 +1,101 @@ +// Copyright 2016 Rob Riggs +// All rights reserved. + +#include "AFSKTestTone.hpp" +#include "ModulatorTask.hpp" + +void startAfskToneTask(void const* arg) +{ + using mobilinkd::tnc::AFSKTestTone; + + auto test = static_cast(arg); + + while (true) { + switch (test->state()) { + case AFSKTestTone::State::NONE: + osThreadYield(); + break; + case AFSKTestTone::State::MARK: + case AFSKTestTone::State::SPACE: + case AFSKTestTone::State::BOTH: + test->fill(); + break; + default: + break; + } + } +} + +uint32_t testToneTaskBuffer[ 256 ]; +osStaticThreadDef_t testToneTaskControlBlock; +static osThreadStaticDef(testToneTask, startAfskToneTask, osPriorityIdle, 0, + 256, testToneTaskBuffer, &testToneTaskControlBlock); + +namespace mobilinkd { namespace tnc { + +AFSKTestTone::AFSKTestTone() +{ + testToneTask_ = osThreadCreate(osThread(testToneTask), this); + osThreadSuspend(testToneTask_); +} + +void AFSKTestTone::transmit(State prev) +{ + if (prev == State::NONE) { + osThreadResume(testToneTask_); + } +} + +void AFSKTestTone::mark() +{ + auto prev = state_; + state_ = State::MARK; + transmit(prev); +} + +void AFSKTestTone::space() +{ + auto prev = state_; + state_ = State::SPACE; + transmit(prev); +} + +void AFSKTestTone::both() +{ + auto prev = state_; + state_ = State::BOTH; + transmit(prev); +} + +void AFSKTestTone::stop() +{ + if (state_ == State::NONE) return; + + state_ = State::NONE; + getModulator().abort(); + osThreadSuspend(testToneTask_); +} + +void AFSKTestTone::fill() const +{ + static State current = State::SPACE; + + switch (state_) { + case AFSKTestTone::State::NONE: + return; + case AFSKTestTone::State::MARK: + getModulator().send(true); + break; + case AFSKTestTone::State::SPACE: + getModulator().send(false); + break; + case AFSKTestTone::State::BOTH: + getModulator().send(current == State::SPACE); + current = (current == State::MARK ? State::SPACE : State::MARK); + break; + default: + break; + } +} + +}} // mobilinkd::tnc diff --git a/TNC/AFSKTestTone.hpp b/TNC/AFSKTestTone.hpp new file mode 100644 index 0000000..bdddb5e --- /dev/null +++ b/TNC/AFSKTestTone.hpp @@ -0,0 +1,32 @@ +// Copyright 2016 Rob Riggs +// All rights reserved. + +#ifndef MOBILINKD__TNC__AFSK_TEST_TONE_HPP_ +#define MOBILINKD__TNC__AFSKTESTTONE_HPP_ + +#include "cmsis_os.h" + +extern "C" void startAfskToneTask(void* arg); + +namespace mobilinkd { namespace tnc { + +struct AFSKTestTone +{ + enum class State {MARK, SPACE, BOTH, NONE}; + AFSKTestTone(); + void transmit(State prev); + void mark(); + void space(); + void both(); + void stop(); + void fill() const; + State state() const { return state_; } + + State state_{State::NONE}; + osThreadId testToneTask_{0}; +}; + +}} // mobilinkd::tnc + + +#endif // MOBILINKD__TNC__AFSKTESTTONE_HPP_ diff --git a/TNC/AGC.hpp b/TNC/AGC.hpp new file mode 100644 index 0000000..80d6697 --- /dev/null +++ b/TNC/AGC.hpp @@ -0,0 +1,61 @@ +#ifndef MOBILINKD__AGC_H_ +#define MOBILINKD__AGC_H_ + +#include + +namespace mobilinkd { namespace libafsk { + +template +struct BaseAGC { + + typedef T float_type; + float_type attack_; + float_type decay_; + float_type reference_; + float_type max_gain_; + bool has_max_gain_; + + float_type gain_; + + BaseAGC(float_type attack, float_type decay, float_type reference = 1.0) + : attack_(attack), decay_(decay), reference_(reference) + , max_gain_(0.0), has_max_gain_(false), gain_(1.0) + {} + + BaseAGC(float_type decay, float_type attack, float_type reference, float_type max_gain) + : attack_(attack), decay_(decay), reference_(reference) + , max_gain_(max_gain), has_max_gain_(true), gain_(1.0) + {} + + float_type operator()(float_type value) { + + float_type output = value * gain_; + float_type tmp = fabs(output) - reference_; + float_type rate = decay_; + + if (fabs(tmp) > gain_) { + rate = attack_; + } + + gain_ -= tmp * rate; + + if (gain_ < 0.0f) { + gain_ = .000001f; + } + + if (has_max_gain_ and (gain_ > max_gain_)) { + gain_ = max_gain_; + } + + return output; + } +}; + +typedef BaseAGC AGC; +typedef BaseAGC FastAGC; + +}} // mobilinkd::libafsk + +#endif // MOBILINKD__AGC_H_ + + diff --git a/TNC/AfskDemodulator.cpp b/TNC/AfskDemodulator.cpp new file mode 100644 index 0000000..a3d34d3 --- /dev/null +++ b/TNC/AfskDemodulator.cpp @@ -0,0 +1,45 @@ +// Copyright 2017 Rob Riggs +// All rights reserved. + +#include "AfskDemodulator.hpp" + +namespace mobilinkd { namespace tnc { namespace afsk1200 { + + +hdlc::IoFrame* Demodulator::operator()(float* 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++) { + + 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); + auto pll = pll_(bit); + + if (pll.sample) { + if (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); + } else { + result = hdlc_decoder_(nrzi_.decode(bit), pll.locked); + } + } + } + return result; +} + +}}} // mobilinkd::tnc::afsk1200 diff --git a/TNC/AfskDemodulator.hpp b/TNC/AfskDemodulator.hpp new file mode 100644 index 0000000..409beb4 --- /dev/null +++ b/TNC/AfskDemodulator.hpp @@ -0,0 +1,136 @@ +// Copyright 2015 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 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, +}; + +typedef FirFilter emphasis_filter_type; +// typedef IirFilter emphasis_filter_type; + +struct Demodulator { + + typedef float float_type; + + 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_; + libafsk::NRZI nrzi_; + hdlc::Decoder hdlc_decoder_; + bool locked_; + float_type buffer_a[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) + {} + + // hdlc::IoFrame* operator()(float* samples, size_t len) __attribute__((section(".bss2"))); + hdlc::IoFrame* operator()(float* samples, size_t len); + + bool locked() const {return locked_;} +}; + + +}}} // mobilinkd::tnc::afsk1200 + + +#endif // MOBILINKD__AFSK_DEMODULATOR_HPP_ diff --git a/TNC/AudioInput.cpp b/TNC/AudioInput.cpp new file mode 100644 index 0000000..056bd49 --- /dev/null +++ b/TNC/AudioInput.cpp @@ -0,0 +1,962 @@ +// Copyright 2015 Rob Riggs +// All rights reserved. + +#include "AudioInput.hpp" +#include "AfskDemodulator.hpp" +#include "AudioLevel.hpp" +#include "Log.h" +#include "KissHardware.hpp" +#include "GPIO.hpp" +#include "HdlcFrame.hpp" +#include "memory.hpp" +#include "IirFilter.hpp" +#include "FilterCoefficients.hpp" +#include "PortInterface.hpp" +#include "Goertzel.h" +#include "Led.h" + +#include "arm_math.h" +#include "stm32l4xx_hal.h" + +#include +#include +#include +#include +#include + +extern osMessageQId ioEventQueueHandle; + +extern "C" void SystemClock_Config(void); + +// 1kB +typedef mobilinkd::tnc::memory::Pool< + 8, mobilinkd::tnc::audio::ADC_BUFFER_SIZE * 2> adc_pool_type; +adc_pool_type adcPool; + +// DMA Conversion first half complete. +extern "C" void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef*) { + using namespace mobilinkd::tnc::audio; + + auto block = adcPool.allocate(); + if (!block) return; + memmove(block->buffer, adc_buffer, ADC_BUFFER_SIZE * 2); + auto status = osMessagePut(adcInputQueueHandle, (uint32_t) block, 0); + if (status != osOK) adcPool.deallocate(block); +} + +// DMA Conversion second half complete. +extern "C" void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef*) { + using namespace mobilinkd::tnc::audio; + + auto block = adcPool.allocate(); + if (!block) return; + memmove(block->buffer, adc_buffer + DMA_TRANSFER_SIZE, ADC_BUFFER_SIZE * 2); + auto status = osMessagePut(adcInputQueueHandle, (uint32_t) block, 0); + if (status != osOK) adcPool.deallocate(block); +} + +extern "C" void HAL_ADC_ErrorCallback(ADC_HandleTypeDef* /* hadc */) { + using namespace mobilinkd::tnc::audio; + + // __HAL_ADC_CLEAR_FLAG(hadc, (ADC_FLAG_EOC | ADC_FLAG_EOS | ADC_FLAG_OVR)); + // HAL_DMA_Start(hadc->DMA_Handle, (uint32_t)&hadc->Instance->DR, (uint32_t)adc_buffer, ADC_BUFFER_SIZE * 2); +} + +extern "C" void startAudioInputTask(void const*) { + + using namespace mobilinkd::tnc::audio; + DEBUG("startAudioInputTask"); + + adcPool.init(); + + uint8_t adcState = mobilinkd::tnc::audio::IDLE; + + while (true) { + osEvent event = osMessageGet(audioInputQueueHandle, osWaitForever); + if (event.status != osEventMessage) continue; + adcState = event.value.v; + + switch (adcState) { + case STOPPED: + DEBUG("STOPPED"); + // stop(); + break; + case DEMODULATOR: + DEBUG("DEMODULATOR"); + demodulatorTask(); + break; + case STREAM_AMPLIFIED_INPUT_LEVEL: + DEBUG("STREAM_AMPLIFIED_INPUT_LEVEL"); + streamAmplifiedInputLevels(); + break; + case POLL_AMPLIFIED_INPUT_LEVEL: + DEBUG("POLL_AMPLIFIED_INPUT_LEVEL"); + pollAmplifiedInputLevel(); + break; + case POLL_TWIST_LEVEL: + DEBUG("POLL_TWIST_LEVEL"); + pollInputTwist(); + break; + case STREAM_AVERAGE_TWIST_LEVEL: + DEBUG("STREAM_AVERAGE_TWIST_LEVEL"); + streamAverageInputTwist(); + break; + case STREAM_INSTANT_TWIST_LEVEL: + DEBUG("STREAM_INSTANT_TWIST_LEVEL"); + streamInstantInputTwist(); + break; + case AUTO_ADJUST_INPUT_LEVEL: + DEBUG("AUTO_ADJUST_INPUT_LEVEL"); + autoAudioInputLevel(); + break; + case CONFIGURE_INPUT_LEVELS: + DEBUG("CONFIGURE_INPUT_LEVELS"); + setAudioInputLevels(); + break; + case UPDATE_SETTINGS: + DEBUG("UPDATE_SETTINGS"); + setAudioInputLevels(); + break; + case IDLE: + DEBUG("IDLE"); + break; + default: + break; + } + } +} + +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 +}; + +uint32_t adc_buffer[ADC_BUFFER_SIZE]; // Two samples per element. + +typedef FirFilter audio_filter_type; + +audio_filter_type audio_filter; + +mobilinkd::tnc::afsk1200::emphasis_filter_type filter_1; +mobilinkd::tnc::afsk1200::emphasis_filter_type filter_2; +mobilinkd::tnc::afsk1200::emphasis_filter_type filter_3; + +mobilinkd::tnc::afsk1200::Demodulator& getDemod1(const TFirCoefficients<9>& f) __attribute__((noinline)); +mobilinkd::tnc::afsk1200::Demodulator& getDemod2(const TFirCoefficients<9>& f) __attribute__((noinline)); +mobilinkd::tnc::afsk1200::Demodulator& getDemod3(const TFirCoefficients<9>& f) __attribute__((noinline)); + +mobilinkd::tnc::afsk1200::Demodulator& getDemod1(const TFirCoefficients<9>& f) { + filter_1.init(f); + static mobilinkd::tnc::afsk1200::Demodulator instance(26400, filter_1); + return instance; +} + +mobilinkd::tnc::afsk1200::Demodulator& getDemod2(const TFirCoefficients<9>& f) { + filter_2.init(f); + static mobilinkd::tnc::afsk1200::Demodulator instance(26400, filter_2); + return instance; +} + +mobilinkd::tnc::afsk1200::Demodulator& getDemod3(const TFirCoefficients<9>& f) { + filter_3.init(f); + static mobilinkd::tnc::afsk1200::Demodulator instance(26400, filter_3); + return instance; +} + + +void demodulatorTask() { + + DEBUG("enter demodulatorTask"); + + audio_filter.init(taps_0dB_121); + + // rx_twist is 6dB for discriminator input and 0db for de-emphasized input. + auto twist = kiss::settings().rx_twist; + + mobilinkd::tnc::afsk1200::Demodulator& demod1 = getDemod1(*filter::fir::AfskFilters[twist + 3]); + mobilinkd::tnc::afsk1200::Demodulator& demod2 = getDemod2(*filter::fir::AfskFilters[twist + 6]); + mobilinkd::tnc::afsk1200::Demodulator& demod3 = getDemod3(*filter::fir::AfskFilters[twist + 9]); + + startADC(AUDIO_IN); + + mobilinkd::tnc::hdlc::IoFrame* frame = 0; + + uint16_t last_fcs = 0; + uint32_t last_counter = 0; + uint32_t counter = 0; + + bool dcd_status{false}; + + while (true) { + osEvent peek = osMessagePeek(audioInputQueueHandle, 0); + if (peek.status == osEventMessage) break; + + osEvent evt = osMessageGet(adcInputQueueHandle, osWaitForever); + if (evt.status != osEventMessage) { + continue; + } + ++counter; + + auto block = (adc_pool_type::chunk_type*) evt.value.p; + auto samples = (int16_t*) block->buffer; + + float* audio = audio_filter(samples); + adcPool.deallocate(block); + +#if 1 + frame = demod1(audio, ADC_BUFFER_SIZE); + if (frame) { + if (frame->fcs() != last_fcs or counter > last_counter + 2) { + auto save_fcs = frame->fcs(); + if (osMessagePut(ioEventQueueHandle, (uint32_t) frame, 1) == osOK) { + last_fcs = save_fcs; + last_counter = counter; + } else { + hdlc::ioFramePool().release(frame); + } + } + else { + hdlc::ioFramePool().release(frame); + } + } +#endif + +#if 1 + frame = demod2(audio, ADC_BUFFER_SIZE); + if (frame) { + if (frame->fcs() != last_fcs or counter > last_counter + 2) { + auto save_fcs = frame->fcs(); + if (osMessagePut(ioEventQueueHandle, (uint32_t) frame, 1) == osOK) { + last_fcs = save_fcs; + last_counter = counter; + } else { + hdlc::ioFramePool().release(frame); + } + } + else { + hdlc::ioFramePool().release(frame); + } + } +#endif + +#if 1 + frame = demod3(audio, ADC_BUFFER_SIZE); + if (frame) { + if (frame->fcs() != last_fcs or counter > last_counter + 2) { + auto save_fcs = frame->fcs(); + if (osMessagePut(ioEventQueueHandle, (uint32_t) frame, 1) == osOK) { + last_fcs = save_fcs; + last_counter = counter; + } else { + hdlc::ioFramePool().release(frame); + } + } + else { + hdlc::ioFramePool().release(frame); + } + } +#endif + bool new_dcd_status = demod1.locked() or demod2.locked() or demod3.locked(); + if (new_dcd_status xor dcd_status) { + dcd_status = new_dcd_status; + if (dcd_status) { + led_dcd_on(); + } else { + led_dcd_off(); + } + } + } + + stopADC(); + led_dcd_off(); + DEBUG("exit demodulatorTask"); +} + + +void streamLevels(uint32_t channel, uint8_t cmd) { + + // Stream out Vpp, Vavg, Vmin, Vmax as four 16-bit values, left justified. + + uint8_t data[9]; + INFO("streamLevels: start"); + startADC(channel); + + while (true) { + osEvent peek = osMessagePeek(audioInputQueueHandle, 0); + if (peek.status == osEventMessage) break; + + uint16_t count = 0; + uint32_t accum = 0; + uint16_t min = 4096; + uint16_t max = 0; + + while (count < 2640) { + osEvent evt = osMessageGet(adcInputQueueHandle, osWaitForever); + if (evt.status != osEventMessage) continue; + + count += ADC_BUFFER_SIZE; + + auto block = (adc_pool_type::chunk_type*) evt.value.p; + uint16_t* start = (uint16_t*) block->buffer; + uint16_t* end = (uint16_t*) block->buffer + ADC_BUFFER_SIZE; + + min = std::min(min, *std::min_element(start, end)); + max = std::max(max, *std::max_element(start, end)); + accum = std::accumulate(start, end, accum); + + adcPool.deallocate(block); + } + + uint16_t pp = (max - min) << 4; + uint16_t avg = (accum / count) << 4; + min <<= 4; + max <<= 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); + + ioport->write(data, 9, 6, 10); + } + + stopADC(); + DEBUG("exit streamLevels"); +} + +levels_type readLevels(uint32_t channel, uint32_t samples) { + + DEBUG("enter readLevels"); + + // Return Vpp, Vavg, Vmin, Vmax as four 16-bit values, right justified. + + uint16_t count = 0; + uint32_t accum = 0; + uint16_t vmin = 4096; + uint16_t vmax = 0; + + INFO("readLevels: start"); + startADC(channel); + + while (count < samples) { + + osEvent evt = osMessageGet(adcInputQueueHandle, osWaitForever); + if (evt.status != osEventMessage) continue; + + count += ADC_BUFFER_SIZE; + + auto block = (adc_pool_type::chunk_type*) evt.value.v; + uint16_t* start = (uint16_t*) block->buffer; + uint16_t* end = (uint16_t*) block->buffer + ADC_BUFFER_SIZE; + + 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); + } + + stopADC(); + + uint16_t pp = vmax - vmin; + uint16_t avg = accum / count; + DEBUG("exit readLevels"); + + return levels_type(pp, avg, vmin, vmax); +} + + +constexpr uint32_t TWIST_SAMPLE_SIZE = 264 * 5; + +/* + * Return twist as a the difference in dB between mark and space. The + * expected values are about 0dB for discriminator output and about 5.5dB + * for de-emphasized audio. + */ +float readTwist() +{ + + DEBUG("enter readTwist"); + constexpr uint32_t channel = AUDIO_IN; + + float g1200 = 0.0f; + float g2200 = 0.0f; + + GoertzelFilter gf1200(1200.0); + GoertzelFilter gf2200(2200.0); + + const uint32_t AVG_SAMPLES = 100; + + startADC(channel); + + for (uint32_t i = 0; i != AVG_SAMPLES; ++i) { + + uint32_t count = 0; + while (count < TWIST_SAMPLE_SIZE) { + + osEvent evt = osMessageGet(adcInputQueueHandle, osWaitForever); + if (evt.status != osEventMessage) continue; + + count += ADC_BUFFER_SIZE; + + auto block = (adc_pool_type::chunk_type*) evt.value.v; + uint16_t* data = (uint16_t*) block->buffer; + gf1200(data, ADC_BUFFER_SIZE); + gf2200(data, ADC_BUFFER_SIZE); + + adcPool.deallocate(block); + } + + g1200 += 10.0 * log10(gf1200); + g2200 += 10.0 * log10(gf2200); + + gf1200.reset(); + gf2200.reset(); + } + + stopADC(); + DEBUG("exit readTwist"); + + return (g1200 / AVG_SAMPLES) - (g2200 / AVG_SAMPLES); +} + +/* + * Get the input twist level as a pair of numbers -- the relative dB + * level of the Bell 202 mark and space tones. + * + * This is intended to measure noise levels on an empty channel. + * + * When de-emphasis is applied, the noise at 1200Hz will be about 5.5dB + * higher than at 2200Hz. When de-emphasis is not applied (discriminator + * output), the levels should be about the same. + * + * This is used to adjust the demodulator filters so that the proper + * input twist is applied to the signal. In general, properly modulated + * signals are expected to be pre-emphasized so that they are equal + * when de-emphasis is applied. + * + * If no de-emphasis is detected, the de-emphasis has to be applied in + * the demodulator. + * + * This takes about 5 seconds to complete as it averages 100 50ms samples + * to get a reasonable sampling of the noise. + */ +void pollInputTwist() +{ + DEBUG("enter pollInputTwist"); + constexpr uint32_t channel = AUDIO_IN; + + float g1200 = 0.0f; + float g2200 = 0.0f; + + GoertzelFilter gf1200(1200.0); + GoertzelFilter gf2200(2200.0); + + const uint32_t AVG_SAMPLES = 100; + + startADC(channel); + + for (uint32_t i = 0; i != AVG_SAMPLES; ++i) { + + uint32_t count = 0; + while (count < TWIST_SAMPLE_SIZE) { + + osEvent evt = osMessageGet(adcInputQueueHandle, osWaitForever); + if (evt.status != osEventMessage) continue; + + count += ADC_BUFFER_SIZE; + + auto block = (adc_pool_type::chunk_type*) evt.value.v; + uint16_t* data = (uint16_t*) block->buffer; + gf1200(data, ADC_BUFFER_SIZE); + gf2200(data, ADC_BUFFER_SIZE); + + adcPool.deallocate(block); + } + + g1200 += 10.0 * log10(gf1200); + g2200 += 10.0 * log10(gf2200); + + gf1200.reset(); + gf2200.reset(); + } + + stopADC(); + + DEBUG("pollInputTwist: MARK=%d, SPACE=%d (x100)", + int(g1200 * 100.0 / AVG_SAMPLES), int(g2200 * 100.0 / AVG_SAMPLES)); + + int16_t g1200i = int16_t(g1200 * 256 / AVG_SAMPLES); + int16_t g2200i = int16_t(g2200 * 256 / AVG_SAMPLES); + + uint8_t buffer[5]; + buffer[0] = kiss::hardware::POLL_INPUT_TWIST; + buffer[1] = (g1200i >> 8) & 0xFF; + buffer[2] = g1200i & 0xFF; + buffer[3] = (g2200i >> 8) & 0xFF; + buffer[4] = g2200i & 0xFF; + + ioport->write(buffer, 5, 6, 10); + + DEBUG("exit pollInputTwist"); +} + +void streamAverageInputTwist() +{ + DEBUG("enter streamAverageInputTwist"); + + constexpr uint32_t channel = AUDIO_IN; + + startADC(channel); + + uint32_t acount = 0; + float g700 = 0.0f; + float g1200 = 0.0f; + float g1700 = 0.0f; + float g2200 = 0.0f; + float g2700 = 0.0f; + + GoertzelFilter gf700(700.0); + GoertzelFilter gf1200(1200.0); + GoertzelFilter gf1700(1700.0); + GoertzelFilter gf2200(2200.0); + GoertzelFilter gf2700(2700.0); + + while (true) { + osEvent peek = osMessagePeek(audioInputQueueHandle, 0); + if (peek.status == osEventMessage) break; + + acount++; + uint32_t count = 0; + while (count < TWIST_SAMPLE_SIZE) { + + osEvent evt = osMessageGet(adcInputQueueHandle, osWaitForever); + if (evt.status != osEventMessage) continue; + + count += ADC_BUFFER_SIZE; + + auto block = (adc_pool_type::chunk_type*) evt.value.v; + uint16_t* data = (uint16_t*) block->buffer; + gf700(data, ADC_BUFFER_SIZE); + gf1200(data, ADC_BUFFER_SIZE); + gf1700(data, ADC_BUFFER_SIZE); + gf2200(data, ADC_BUFFER_SIZE); + gf2700(data, ADC_BUFFER_SIZE); + + adcPool.deallocate(block); + } + + g700 += 10.0 * log10(gf700); + g1200 += 10.0 * log10(gf1200); + g1700 += 10.0 * log10(gf1700); + g2200 += 10.0 * log10(gf2200); + g2700 += 10.0 * log10(gf2700); + + char* buffer = 0; + int len = asiprintf( + &buffer, + "_%f, %f, %f, %f, %f\r\n", + g700 / acount, + g1200 / acount, + g1700 / acount, + g2200 / acount, + g2700 / acount); + + if (len > 0) { + buffer[0] = kiss::hardware::POLL_INPUT_TWIST; + ioport->write((uint8_t*)buffer, len - 1, 6, 10); + free(buffer); + } + + gf700.reset(); + gf1200.reset(); + gf1700.reset(); + gf2200.reset(); + gf2700.reset(); + } + + stopADC(); + DEBUG("exit streamAverageInputTwist"); +} + +void streamInstantInputTwist() +{ + DEBUG("enter streamInstantInputTwist"); + + constexpr uint32_t channel = AUDIO_IN; + + startADC(channel); + + GoertzelFilter gf700(700.0); + GoertzelFilter gf1200(1200.0); + GoertzelFilter gf1700(1700.0); + GoertzelFilter gf2200(2200.0); + GoertzelFilter gf2700(2700.0); + + while (true) { + osEvent peek = osMessagePeek(audioInputQueueHandle, 0); + if (peek.status == osEventMessage) break; + + uint32_t count = 0; + while (count < TWIST_SAMPLE_SIZE) { + + osEvent evt = osMessageGet(adcInputQueueHandle, osWaitForever); + if (evt.status != osEventMessage) continue; + + count += ADC_BUFFER_SIZE; + + auto block = (adc_pool_type::chunk_type*) evt.value.v; + uint16_t* data = (uint16_t*) block->buffer; + gf700(data, ADC_BUFFER_SIZE); + gf1200(data, ADC_BUFFER_SIZE); + gf1700(data, ADC_BUFFER_SIZE); + gf2200(data, ADC_BUFFER_SIZE); + gf2700(data, ADC_BUFFER_SIZE); + + adcPool.deallocate(block); + } + + char* buffer = 0; + int len = asiprintf( + &buffer, + "_%f, %f, %f, %f, %f\r\n", + 10.0 * log10(gf700), + 10.0 * log10(gf1200), + 10.0 * log10(gf1700), + 10.0 * log10(gf2200), + 10.0 * log10(gf2700)); + + if (len > 0) { + buffer[0] = kiss::hardware::POLL_INPUT_TWIST; + ioport->write((uint8_t*)buffer, len - 1, 6, 10); + free(buffer); + } + + gf700.reset(); + gf1200.reset(); + gf1700.reset(); + gf2200.reset(); + gf2700.reset(); + } + + stopADC(); + DEBUG("exit streamInstantInputTwist"); +} + +void streamAmplifiedInputLevels() { + DEBUG("enter streamAmplifiedInputLevels"); + streamLevels(AUDIO_IN, kiss::hardware::POLL_INPUT_LEVEL); + DEBUG("exit streamAmplifiedInputLevels"); +} + +void pollAmplifiedInputLevel() { + DEBUG("enter pollAmplifiedInputLevel"); + + uint16_t Vpp, Vavg, Vmin, Vmax; + std::tie(Vpp, Vavg, Vmin, Vmax) = readLevels(AUDIO_IN); + + Vpp <<= 4; + Vavg <<= 4; + Vmin <<= 4; + Vmax <<= 4; + + uint8_t data[9]; + data[0] = kiss::hardware::POLL_INPUT_LEVEL; + data[1] = (Vpp >> 8) & 0xFF; // Vpp + data[2] = (Vpp & 0xFF); + data[3] = (Vavg >> 8) & 0xFF; // Vavg (DC level) + data[4] = (Vavg & 0xFF); + data[5] = (Vmin >> 8) & 0xFF; // Vmin + data[6] = (Vmin & 0xFF); + data[7] = (Vmax >> 8) & 0xFF; // Vmax + data[8] = (Vmax & 0xFF); + + ioport->write(data, 9, 6, 10); + DEBUG("exit pollAmplifiedInputLevel"); +} + +void stop() { + osDelay(100); +#if 0 + auto restore = SysTick->CTRL; + + kiss::settings().input_offset += 6; + setAudioInputLevels(); + kiss::settings().input_offset -= 6; + DEBUG("Stop"); + // __disable_irq(); + vTaskSuspendAll(); + SysTick->CTRL = 0; + HAL_COMP_Init(&hcomp1); + HAL_COMP_Start_IT(&hcomp1); + while (adcState == STOPPED) { + // PWR_MAINREGULATOR_ON / PWR_LOWPOWERREGULATOR_ON + HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); + } + SystemClock_Config(); + SysTick->CTRL = restore; + // __enable_irq(); + HAL_COMP_Stop_IT(&hcomp1); + HAL_COMP_DeInit(&hcomp1); + xTaskResumeAll(); + setAudioInputLevels(); + // adcState = DEMODULATOR; + DEBUG("Wake"); +#endif +} + +}}} // mobilinkd::tnc::audio diff --git a/TNC/AudioInput.hpp b/TNC/AudioInput.hpp new file mode 100644 index 0000000..de82567 --- /dev/null +++ b/TNC/AudioInput.hpp @@ -0,0 +1,139 @@ +// Copyright 2015 Rob Riggs +// All rights reserved. + +#ifndef MOBILINKD__TNC__AUDIO__INPUT_HPP_ +#define MOBILINKD__TNC__AUDIO__INPUT_HPP_ + +#include "main.h" +#include "stm32l4xx_hal.h" +#include "cmsis_os.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern osMessageQId hdlcInputQueueHandle; +extern osMessageQId audioInputQueueHandle; +extern osMessageQId adcInputQueueHandle; +extern TIM_HandleTypeDef htim6; +extern ADC_HandleTypeDef hadc1; + +void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef*); +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. + * - 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. + * + * These inputs can be measured in a couple of different ways: + * - Vavg -- for the average DC level of the signal + * - Vpp -- for the peak-to-peak level of the signal + * + * The outputs can be routed five ways: + * + * 1. KISS output via serial (data or hardware frames) + * 2. AX.25 output via serial (data frames) + * 3. Text output via serial (measurements) + * 4. To another process + * 5. /dev/null + * + * The usual case is that the AMPLIFIED_AUDIO_IN is routed to the + * DEMODULATOR and that output is sent to the serial port as KISS + * output. + * + * Each process monitors the adcState and loops while in that state + * that matches its process. Each combination of ADC source, + * measurement/demodulation and output will have a different adcState. + * + * @param argument is ignored. + */ +void startAudioInputTask(void const * argument); + +void TNC_Error_Handler(int dev, int err); + +#ifdef __cplusplus +} +#endif + +namespace mobilinkd { namespace tnc { namespace audio { + +const uint32_t SAMPLE_RATE = 26400; + +enum AdcState { + STOPPED, // STOP MODE, wait for comparator + DEMODULATOR, // Demod + STREAM_RAW_INPUT_LEVEL, // Unamplified input levels + STREAM_AMPLIFIED_INPUT_LEVEL, // Amplified input levels + POLL_AMPLIFIED_INPUT_LEVEL, // Amplified input levels + POLL_BATTERY_LEVEL, // Battery level + STREAM_OUTPUT_LEVEL, // Amplified & filtered output levels + AUTO_ADJUST_INPUT_LEVEL, // Automatically adjust input levels + CONFIGURE_INPUT_LEVELS, // Set configured input levels + UPDATE_SETTINGS, // Update the device settings + IDLE, // No DMA; sleep for 10ms + POLL_TWIST_LEVEL, + STREAM_AVERAGE_TWIST_LEVEL, + STREAM_INSTANT_TWIST_LEVEL +}; + +const size_t ADC_BUFFER_SIZE = 88; +const size_t DMA_TRANSFER_SIZE = ADC_BUFFER_SIZE / 2; +extern uint32_t adc_buffer[]; // Two int16_t samples per element. + +inline void stopADC() { + HAL_ADC_Stop_DMA(&hadc1); + HAL_TIM_Base_Stop(&htim6); +} + +inline void startADC(uint32_t channel) { + ADC_ChannelConfTypeDef sConfig; + + sConfig.Channel = channel; + sConfig.Rank = 1; + sConfig.SingleDiff = ADC_SINGLE_ENDED; + sConfig.SamplingTime = ADC_SAMPLETIME_24CYCLES_5; + sConfig.OffsetNumber = ADC_OFFSET_NONE; + sConfig.Offset = 0; + HAL_ADC_ConfigChannel(&hadc1, &sConfig); + + HAL_StatusTypeDef adcStatus = HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED); + if (adcStatus != HAL_OK) { + Error_Handler(); + } + + HAL_TIM_Base_Start(&htim6); + HAL_ADC_Start_DMA(&hadc1, adc_buffer, ADC_BUFFER_SIZE * 2); +} + +inline void restartADC() { + HAL_TIM_Base_Start(&htim6); + HAL_ADC_Start_DMA(&hadc1, adc_buffer, ADC_BUFFER_SIZE * 2); +} + +/// Vpp, Vavg, Vmin, Vmax +typedef std::tuple levels_type; +levels_type readLevels(uint32_t channel, uint32_t samples = 2640); +float readTwist(); + +void demodulatorTask(); +void streamRawInputLevels(); +void streamAmplifiedInputLevels(); +void pollAmplifiedInputLevel(); +void pollBatteryLevel(); +void streamOutputLevels(); +void stop(); +void pollInputTwist(); +void streamAverageInputTwist(); +void streamInstantInputTwist(); + +}}} // mobilinkd::tnc::audio + +#endif // MOBILINKD__TNC__AUDIO__INPUT_HPP_ diff --git a/TNC/AudioLevel.cpp b/TNC/AudioLevel.cpp new file mode 100644 index 0000000..edc77f7 --- /dev/null +++ b/TNC/AudioLevel.cpp @@ -0,0 +1,196 @@ +// Copyright 2017 Rob Riggs +// All rights reserved. + +#include "AudioLevel.hpp" +#include "AudioInput.hpp" +#include "ModulatorTask.hpp" +#include "KissHardware.hpp" +#include "GPIO.hpp" +#include "Led.h" + +#include "main.h" +#include "stm32l4xx_hal.h" + +#include +#include +#include + +#include +#include + +extern OPAMP_HandleTypeDef hopamp1; +extern DAC_HandleTypeDef hdac1; + +namespace mobilinkd { namespace tnc { namespace audio { + +uint16_t virtual_ground; + +void setAudioPins(void) { + GPIO_InitTypeDef GPIO_InitStruct; + + GPIO_InitStruct.Pin = GPIO_PIN_3; + GPIO_InitStruct.Mode = GPIO_MODE_ANALOG_ADC_CONTROL; + GPIO_InitStruct.Pull = GPIO_NOPULL; + HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); +} + +void set_input_gain(int level) +{ + uint32_t dc_offset{}; + + if (HAL_OPAMP_DeInit(&hopamp1) != HAL_OK) + { + Error_Handler(); + } + + + switch (level) { + case 0: // 0dB + hopamp1.Init.Mode = OPAMP_FOLLOWER_MODE; + dc_offset = 2048; + break; + case 1: // 6dB + hopamp1.Init.Mode = OPAMP_PGA_MODE; + hopamp1.Init.PgaGain = OPAMP_PGA_GAIN_2; + dc_offset = 1024; + break; + case 2: // 12dB + hopamp1.Init.Mode = OPAMP_PGA_MODE; + hopamp1.Init.PgaGain = OPAMP_PGA_GAIN_4; + dc_offset = 512; + break; + case 3: // 18dB + hopamp1.Init.Mode = OPAMP_PGA_MODE; + hopamp1.Init.PgaGain = OPAMP_PGA_GAIN_8; + dc_offset = 256; + break; + case 4: // 24dB + hopamp1.Init.Mode = OPAMP_PGA_MODE; + hopamp1.Init.PgaGain = OPAMP_PGA_GAIN_16; + dc_offset = 128; + break; + default: + Error_Handler(); + } + + HAL_DAC_SetValue(&hdac1, DAC_CHANNEL_2, DAC_ALIGN_12B_R, dc_offset); + + if (HAL_OPAMP_Init(&hopamp1) != HAL_OK) + { + Error_Handler(); + } + HAL_OPAMP_Start(&hopamp1); + osDelay(100); +} + +int adjust_input_gain() __attribute__((noinline)); + +int adjust_input_gain() { + + INFO("Adjusting input gain..."); + + int gain{0}; + + while (true) { + set_input_gain(gain); + + auto [vpp, vavg, vmin, vmax] = readLevels(AUDIO_IN); + + INFO("Vpp = %" PRIu16 ", Vavg = %" PRIu16, vpp, vavg); + INFO("Vmin = %" PRIu16 ", Vmax = %" PRIu16 ", setting = %d", vmin, vmax, gain); + + while (gain == 0 and (vmax > 4090 or vmin < 5)) { + + gpio::LED_OTHER::toggle(); + std::tie(vpp, vavg, vmin, vmax) = readLevels(AUDIO_IN); + + INFO("Vpp = %" PRIu16 ", Vavg = %" PRIu16 ", Vmin = %" PRIu16 + ", Vmax = %" PRIu16 ", setting = %d", vpp, vavg, vmin, vmax, gain); + + } + gpio::LED_OTHER::off(); + + virtual_ground = vavg; + + if (vpp > 2048) break; + if (gain == 4) break; + ++gain; + } + 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(); + + int rx_twist = readTwist() + 0.5f; + if (rx_twist < -3) rx_twist = -3; + 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().update_crc(); + + //mobilinkd::tnc::kiss::settings().store(); + + led_tx_off(); + led_dcd_off(); +} + +/** + * Set the audio input levels from the values stored in EEPROM. + */ +void setAudioInputLevels() +{ + // setAudioPins(); + INFO("Setting input gain: %d", kiss::settings().input_gain); + set_input_gain(kiss::settings().input_gain); +} + +std::array log_volume; + +void init_log_volume() +{ + int16_t level = 256; + float gain = 1.0f; + float factor = 1.02207f; + + for (auto& i : log_volume) { + i = int16_t(roundf(level * gain)); + gain *= factor; + } +} + +std::tuple computeLogAudioLevel(int16_t level) +{ + int16_t l = level & 0x80 ? 1 : 0; + int16_t r = log_volume[(level & 0x7F)]; + + return std::make_tuple(l, r); +} + +/** + * Set the audio output level from the values stored in EEPROM. + */ +void setAudioOutputLevel() +{ + auto [l, r] = computeLogAudioLevel(kiss::settings().output_gain); + + INFO("Setting output gain: %" PRIi16 " (log %" PRIi16 " + %" PRIi16 ")", kiss::settings().output_gain, l, r); + + if (l) { + gpio::AUDIO_OUT_ATTEN::on(); + } else { + gpio::AUDIO_OUT_ATTEN::off(); + } + getModulator().set_volume(r); +} + +}}} // mobilinkd::tnc::audio diff --git a/TNC/AudioLevel.hpp b/TNC/AudioLevel.hpp new file mode 100644 index 0000000..abedaef --- /dev/null +++ b/TNC/AudioLevel.hpp @@ -0,0 +1,34 @@ +// Copyright 2015 Rob Riggs +// All rights reserved. + +#ifndef MOBILINKD__TNC__AUDIO__AUDIO_LEVEL_H_ +#define MOBILINKD__TNC__AUDIO__AUDIO_LEVEL_H_ + +#include "arm_math.h" +#include "cmsis_os.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern osMessageQId audioInputQueueHandle; + +#ifdef __cplusplus +} +#endif + +#define AUDIO_IN ADC_CHANNEL_8 + +namespace mobilinkd { namespace tnc { namespace audio { + +void init_log_volume(); +void autoAudioInputLevel(); +void setAudioInputLevels(); +void setAudioOutputLevel(); + +extern bool streamInputDCOffset; +extern uint16_t virtual_ground; + +}}} // mobilinkd::tnc::audio + +#endif // MOBILINKD__TNC__AUDIO__AUDIO_LEVEL_H_ diff --git a/TNC/DelayLine.hpp b/TNC/DelayLine.hpp new file mode 100644 index 0000000..15856e4 --- /dev/null +++ b/TNC/DelayLine.hpp @@ -0,0 +1,79 @@ +#ifndef MOBILINKD_DELAY_LINE_H_ +#define MOBILINKD_DELAY_LINE_H_ + +#include +#include +#include +#include +#include + +namespace mobilinkd { namespace libafsk { + +struct DelayLine { + + size_t length_; + std::vector buffer_; + size_t pos_; + + DelayLine(double sample_rate, double delay) + : length_((delay / (1.0 / sample_rate)) + .5), buffer_(length_), pos_(0) + {} + + bool operator()(bool value) { + bool r = buffer_[pos_]; + buffer_[pos_++] = value; + if (pos_ == length_) pos_ = 0; + return r; + } +}; + + +template +struct FixedDelayLine { + + size_t length_; + char buffer_[N]; + size_t pos_; + + FixedDelayLine(double sample_rate, double delay) + : length_((delay / (1.0 / sample_rate)) + .5), buffer_(), pos_(0) + { + assert(length_ <= N); + memset(buffer_, 0, N); + } + + bool operator()(bool value) { + bool r = buffer_[pos_]; + buffer_[pos_++] = value; + if (pos_ == length_) pos_ = 0; + return r; + } +}; + +#if 0 +template +struct BlockDelayLine { + + uint8_t buffer_[(BLOCK_SIZE + DELAY_BITS + 7) / 8]; + size_t pos_; + + BlockDelayLine(double sample_rate, double delay) + : length_((delay / (1.0 / sample_rate)) + .5), buffer_(), pos_(0) + { + assert(length_ <= N); + memset(buffer_, 0, N); + } + + bool operator()(bool value) { + bool r = buffer_[pos_]; + buffer_[pos_++] = value; + if (pos_ == length_) pos_ = 0; + return r; + } +}; + +#endif +}} // mobilinkd::libafsk + +#endif // MOBILINKD_DELAY_LINE_H_ + diff --git a/TNC/Digipeater.cpp b/TNC/Digipeater.cpp new file mode 100644 index 0000000..0dd031e --- /dev/null +++ b/TNC/Digipeater.cpp @@ -0,0 +1,56 @@ +// Copyright 2017 Rob Riggs +// All rights reserved. + +#include "Digipeater.h" +#include "Digipeater.hpp" +#include "IOEventTask.h" + +void startDigipeaterTask(void* arg) +{ + using mobilinkd::tnc::Digipeater; + using mobilinkd::tnc::hdlc::IoFrame; + + auto digi = static_cast(arg); + for(;;) + { + osEvent evt = osMessageGet(digipeaterQueueHandle, osWaitForever); + if (evt.status != osEventMessage) continue; + + uint32_t cmd = evt.value.v; + if (cmd < FLASH_BASE) // Assumes FLASH_BASE < SRAM_BASE. + { + // this is a command, not a packet. + return; + } + + digi->clean_history(); + + auto frame = static_cast(evt.value.p); + + if (!digi->can_repeat(frame)) continue; + + auto digi_frame = digi->rewrite_frame(frame); + + } +} + +void onBeaconTimer1(void const *) +{ + +} +void onBeaconTimer2(void const *) +{ + +} +void onBeaconTimer3(void const *) +{ + +} +void onBeaconTimer4(void const *) +{ + +} + +namespace mobilinkd { namespace tnc { + +}} // mobilinkd::tnc diff --git a/TNC/Digipeater.h b/TNC/Digipeater.h new file mode 100644 index 0000000..473f61b --- /dev/null +++ b/TNC/Digipeater.h @@ -0,0 +1,20 @@ +// Copyright 2017 Rob Riggs +// All rights reserved. + +#ifndef MOBILINKD__TNC__DIGIPEATER_H_ +#define MOBILINKD__TNC__DIGIPEATER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +extern void onBeaconTimer1(void const * argument); +extern void onBeaconTimer2(void const * argument); +extern void onBeaconTimer3(void const * argument); +extern void onBeaconTimer4(void const * argument); + +#ifdef __cplusplus +} +#endif + +#endif /* MOBILINKD__TNC__DIGIPEATER_H_ */ diff --git a/TNC/Digipeater.hpp b/TNC/Digipeater.hpp new file mode 100644 index 0000000..b4df907 --- /dev/null +++ b/TNC/Digipeater.hpp @@ -0,0 +1,87 @@ +// Copyright 2017 Rob Riggs +// All rights reserved. + + +#ifndef MOBILINKD__TNC__DIGIPEATER_HPP_ +#define MOBILINKD__TNC__DIGIPEATER_HPP_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern osThreadId digipeaterTaskHandle; +extern osMessageQId digipeaterQueueHandle; + +void startDigipeaterTask(void* arg); +void beacon(void* arg); + +#ifdef __cplusplus +} // extern "C" + +#include "KissHardware.hpp" +#include "HdlcFrame.hpp" + +namespace mobilinkd { namespace tnc { + +/** + * We only process ALL, BEACON, CQ, QST, GPSxxx and APxxxx TOCALLs, + * and exact matches to our TOCALL and any of our aliases. + * + * The following constants are used: + * - kiss::NUMBER_OF_ALIASES + * - kiss::NUMBER_OF_BEACONS + * - kiss::BEACON_PATH_LEN + * - kiss::BEACON_TEXT_LEN + * - kiss::CALLSIGN_LEN + * - kiss::TOCALL + */ +struct Digipeater +{ + const kiss::Alias* aliases_; + const kiss::Beacon* beacons_; + + Digipeater(const kiss::Alias* aliases, const kiss::Beacon* beacons) + : aliases_(aliases), beacons_(beacons) + {} + + /** + * Scan the history table and remove outdated entries. + */ + void clean_history() + { + + } + + /** + * Can the frame be digipeated? + * + * - Does it match an active digi alias? + * - set = true + * - use = true + * - hops > 0 + * - alias name matches first frame via OR preempt = true and alias name + * matches any frame via. + * - Does it not exist in history? + * @param frame + * @return + */ + bool can_repeat(hdlc::IoFrame* frame) + { + return false; + } + + hdlc::IoFrame* rewrite_frame(hdlc::IoFrame* frame) + { + frame->source(hdlc::IoFrame::DIGI_DATA); + return frame; + } +}; + + +}} // mobilinkd::tnc + +#endif // __cplusplus + +#endif // MOBILINKD__TNC__DIGIPEATER_HPP_ diff --git a/TNC/DigitalPLL.cpp b/TNC/DigitalPLL.cpp new file mode 100644 index 0000000..861a473 --- /dev/null +++ b/TNC/DigitalPLL.cpp @@ -0,0 +1,32 @@ +// 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 new file mode 100644 index 0000000..564e2c4 --- /dev/null +++ b/TNC/DigitalPLL.hpp @@ -0,0 +1,116 @@ +#ifndef MOBILINKD__DIGITAL_PLL_H_ +#define MOBILINKD__DIGITAL_PLL_H_ + +#include "Hysteresis.hpp" +#include "IirFilter.hpp" + +#include "arm_math.h" + +#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 { + FloatType jitter; + bool sample; + bool locked; +}; + +} // pll + +template +struct BaseDigitalPLL { + + static const size_t N = 16; + + typedef T float_type; + typedef pll::PLLResult result_type; + + float_type sample_rate_; + float_type symbol_rate_; + float_type sps_; ///< Samples per symbol + float_type limit_; ///< Samples per symbol / 2 + libafsk::BaseHysteresis lock_; + IirFilter<2> loop_filter_; + IirFilter<2> lock_filter_; + + bool last_; + float_type count_; + + bool sample_; + float_type jitter_; + uint8_t bits_; + + 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) + , last_(false), count_(0), sample_(false) + , jitter_(0.0), bits_(1) + {} + + result_type operator()(bool input) + { + + sample_ = false; + + if (input != last_ or bits_ > 127) { + // Record transition. + last_ = input; + + if (count_ > limit_) { + count_ -= sps_; + } + + float_type offset = count_ / bits_; + float_type jitter = loop_filter_(offset); + jitter_ = lock_filter_(abs(offset)); + + count_ -= jitter * sps_ * 0.023f; + + bits_ = 1; + } else { + if (count_ > limit_) { + sample_ = true; + count_ -= sps_; + ++bits_; + } + } + + count_ += 1; + result_type result = {jitter_, sample_, locked()}; + return result; + } + + bool locked() { + return lock_(jitter_); + } + + bool sample() const { + return sample_; + } +}; + +typedef BaseDigitalPLL DigitalPLL; +typedef BaseDigitalPLL FastDigitalPLL; + +}} // mobilinkd::tnc + +#endif // MOBILINKD__DIGITAL_PLL_H_ + diff --git a/TNC/Filter.cpp b/TNC/Filter.cpp new file mode 100644 index 0000000..71d4c9a --- /dev/null +++ b/TNC/Filter.cpp @@ -0,0 +1,460 @@ +#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 new file mode 100644 index 0000000..c84010e --- /dev/null +++ b/TNC/Filter.h @@ -0,0 +1,42 @@ +#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 new file mode 100644 index 0000000..42bf054 --- /dev/null +++ b/TNC/Filter.hpp @@ -0,0 +1,16 @@ +// 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 new file mode 100644 index 0000000..8d16667 --- /dev/null +++ b/TNC/FilterCoefficients.hpp @@ -0,0 +1,408 @@ +// Copyright 2015 Rob Riggs +// All rights reserved. + +#ifndef MOBILINKD__TNC__FILTER_COEFFICIENTS_HPP_ +#define MOBILINKD__TNC__FILTER_COEFFICIENTS_HPP_ + +#include "IirFilter.hpp" +#include "FirFilter.hpp" + +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. +const TFirCoefficients<9> dB12 = { + { + 0.0223997567081, + -0.132588208904, + -0.590869965255, + -1.12325491747, + 3.55525416434, + -1.12325491747, + -0.590869965255, + -0.132588208904, + 0.0223997567081, + } +}; + +// 1200Hz = -11dB, 2200Hz = 0dB; 3537Hz cutoff, 4.51 gain; cosine. +const TFirCoefficients<9> dB11 = { + { + 0.0138943225784, + -0.137800909104, + -0.544488104185, + -1.00269495093, + 3.29019584315, + -1.00269495093, + -0.544488104185, + -0.137800909104, + 0.0138943225784, + } +}; + +// 1200Hz = -10dB, 2200Hz = 0dB; 3405Hz cutoff, 4.11 gain; cosine. +const TFirCoefficients<9> dB10 = { + { + 0.00564557245371, + -0.141643920093, + -0.498513944796, + -0.887264289827, + 3.03792032485, + -0.887264289827, + -0.498513944796, + -0.141643920093, + 0.00564557245371, + } +}; + +// 1200Hz = -9dB, 2200Hz = 0dB; 3252Hz cutoff, 3.7 gain; cosine. +const TFirCoefficients<9> dB9 = { + { + -0.00232554104135, + -0.142858725752, + -0.449053780255, + -0.770264863826, + 2.77651146344, + -0.770264863826, + -0.449053780255, + -0.142858725752, + -0.00232554104135, + } +}; + +// 1200Hz = -8dB, 2200Hz = 0dB; 3075Hz cutoff, 3.31 gain; cosine. +const TFirCoefficients<9> dB8 = { + { + -0.0096785005294, + -0.141786249744, + -0.399423790874, + -0.658608816643, + 2.52741445003, + -0.658608816643, + -0.399423790874, + -0.141786249744, + -0.0096785005294, + } +}; + +// 1200Hz = -7dB, 2200Hz = 0dB; 2874Hz cutoff, 2.949 gain; cosine. +const TFirCoefficients<9> dB7 = { + { + -0.0159546975608, + -0.137623905223, + -0.349491872081, + -0.553149017309, + 2.28934729422, + -0.553149017309, + -0.349491872081, + -0.137623905223, + -0.0159546975608, + } +}; + +// 1200Hz = -6dB, 2200Hz = 0dB; 2640Hz cutoff, 2.59 gain; cosine. +const TFirCoefficients<9> dB6 = { + { + -0.0209448226653, + -0.130107651829, + -0.299004731072, + -0.45336946386, + 2.0629448761, + -0.45336946386, + -0.299004731072, + -0.130107651829, + -0.0209448226653, + } +}; + +// 1200Hz = -5dB, 2200Hz = 0dB; 2372Hz cutoff, 2.26 gain; cosine. +const TFirCoefficients<9> dB5 = { + { + -0.0209448226653, + -0.130107651829, + -0.299004731072, + -0.45336946386, + 2.0629448761, + -0.45336946386, + -0.299004731072, + -0.130107651829, + -0.0209448226653, + } +}; + + +// 1200Hz = -4dB, 2200Hz = 0dB; 2064Hz cutoff, 1.96 gain; cosine. +const TFirCoefficients<9> dB4 = { + { + -0.0209448226653, + -0.130107651829, + -0.299004731072, + -0.45336946386, + 2.0629448761, + -0.45336946386, + -0.299004731072, + -0.130107651829, + -0.0209448226653, + } +}; + +// 1200Hz = -3dB, 2200Hz = 0dB; 1700Hz cutoff, 1.68 gain; cosine. +const TFirCoefficients<9> dB3 = { + { + -0.0231416146776, + -0.0833375337803, + -0.147937602401, + -0.197411259519, + 1.46066084756, + -0.197411259519, + -0.147937602401, + -0.0833375337803, + -0.0231416146776, + } +}; + + +// 1200Hz = -2dB, 2200Hz = 0dB; 1270Hz cutoff, 1.44 gain; cosine. +const TFirCoefficients<9> dB2 = { + { + -0.0185923370593, + -0.0601029235689, + -0.0996864670836, + -0.128090353439, + 1.30017105427, + -0.128090353439, + -0.0996864670836, + -0.0601029235689, + -0.0185923370593, + } +}; + +// 1200Hz = -1dB, 2200Hz = 0dB; 730Hz cutoff, 1.22 gain; cosine. +const TFirCoefficients<9> dB1 = { + { + -0.0107931468169, + -0.0322211933056, + -0.0506402474814, + -0.0630689498437, + 1.1522865023, + -0.0630689498437, + -0.0506402474814, + -0.0322211933056, + -0.0107931468169, + } +}; + +const TFirCoefficients<9> dB0 = { + { + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + } +}; + +// 1200Hz = 0dB, 2200Hz = -1dB; 4280Hz cutoff, 1.045 gain; cosine. +const TFirCoefficients<9> dB_1 = { + { + -0.0107931468169, + -0.0322211933056, + -0.0506402474814, + -0.0630689498437, + 1.1522865023, + -0.0630689498437, + -0.0506402474814, + -0.0322211933056, + -0.0107931468169, + } +}; + +// 1200Hz = 0dB, 2200Hz = -2dB; 3098Hz cutoff, 1.1 gain; cosine. +const TFirCoefficients<9> dB_2 = { + { + 0.00299520319909, + 0.0482175156295, + 0.137632853632, + 0.228067265055, + 0.266174324969, + 0.228067265055, + 0.137632853632, + 0.0482175156295, + 0.00299520319909, + } +}; + +// 1200Hz = 0dB, 2200Hz = -3dB; 1830Hz cutoff, 1.149 gain; cosine. +const TFirCoefficients<9> dB_3 = { + { + 0.0221215152936, + 0.0832006412609, + 0.151534395598, + 0.205025020719, + 0.225236854257, + 0.205025020719, + 0.151534395598, + 0.0832006412609, + 0.0221215152936, + } +}; + +// 1200Hz = 0dB, 2200Hz = -4dB; 2606Hz cutoff, 1.194 gain; boxcar. +const TFirCoefficients<9> dB_4 = { + { + 0.0498539382844, + 0.103801174967, + 0.153695746099, + 0.188874162863, + 0.201549955573, + 0.188874162863, + 0.153695746099, + 0.103801174967, + 0.0498539382844, + } +}; + +// 1200Hz = 0dB, 2200Hz = -5dB; 2174Hz cutoff, 1.237 gain; boxcar. +const TFirCoefficients<9> dB_5 = { + { + 0.0782137588209, + 0.118736939542, + 0.153156300897, + 0.176223458893, + 0.184339083696, + 0.176223458893, + 0.153156300897, + 0.118736939542, + 0.0782137588209, + } +}; + +// 1200Hz = 0dB, 2200Hz = -6dB; 1706Hz cutoff, 1.275 gain; boxcar. +const TFirCoefficients<9> dB_6 = { + { + 0.104477241089, + 0.130913242609, + 0.151854419973, + 0.165293215366, + 0.169923761926, + 0.165293215366, + 0.151854419973, + 0.130913242609, + 0.104477241089, + } +}; + +const TFirCoefficients<9>* AfskFilters[] = { + &dB_6, + &dB_5, + &dB_4, + &dB_3, + &dB_2, + &dB_1, + &dB0, + &dB1, + &dB2, + &dB3, + &dB4, + &dB5, + &dB6, + &dB7, + &dB8, + &dB9, + &dB10, + &dB11, + &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 + + +}}} // mobilinkd::tnc::filter + +#endif // MOBILINKD__TNC__FILTER_COEFFICIENTS_HPP_ diff --git a/TNC/FirFilter.hpp b/TNC/FirFilter.hpp new file mode 100644 index 0000000..95cf105 --- /dev/null +++ b/TNC/FirFilter.hpp @@ -0,0 +1,92 @@ +// Copyright 2015 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" + +#include +#include + +namespace mobilinkd { namespace tnc { + +template +struct TFirCoefficients { + float taps[N]; +}; + +struct FirCoefficients { + size_t size; + const float* taps; + + template + FirCoefficients(const TFirCoefficients& c) + : size(N), taps(c.taps) + {} +}; + +template +struct FirFilter { + const float* filter_taps; + float filter_state[BLOCK_SIZE + FILTER_SIZE - 1]; + float filter_input[BLOCK_SIZE]; + float filter_output[BLOCK_SIZE]; + float vgnd_; + size_t filter_size; + arm_fir_instance_f32 instance; + + FirFilter() + : filter_taps(0), filter_state(), filter_input(), filter_output() + , vgnd_(0.0f) + , filter_size(FILTER_SIZE), instance() + {} + + FirFilter(const float* taps, size_t len = FILTER_SIZE) + : filter_taps(taps), filter_state(), filter_input(), filter_output() + , filter_size(len), instance() + { + init(taps, len); + } + + void init(const float* taps, size_t len = FILTER_SIZE) + { + vgnd_ = float(audio::virtual_ground); + filter_size = len; + filter_taps = taps; + arm_fir_init_f32(&instance, filter_size, (float32_t*)filter_taps, + filter_state, BLOCK_SIZE); + } + + void init(const FirCoefficients& filt) + { + vgnd_ = float(audio::virtual_ground); + filter_size = filt.size; + filter_taps = filt.taps; + arm_fir_init_f32(&instance, filter_size, (float32_t*)filter_taps, + filter_state, BLOCK_SIZE); + } + + // ADC input + float* operator()(int16_t* input) // __attribute__((section(".bss2"))) + { + for (size_t i = 0; i != BLOCK_SIZE; i++) { + filter_input[i] = (float(input[i]) - vgnd_) * 0.00048828125f; + } + arm_fir_f32(&instance, filter_input, filter_output, BLOCK_SIZE); + return filter_output; + } + + float* operator()(float* input) // __attribute__((section(".bss2"))) + { + arm_fir_f32(&instance, input, filter_output, BLOCK_SIZE); + return filter_output; + } +}; + +}} // mobilinkd::tnc + +#endif // MOBILINKD__TNC__FIR_FILTER_H_ diff --git a/TNC/GPIO.hpp b/TNC/GPIO.hpp new file mode 100644 index 0000000..5dbd8eb --- /dev/null +++ b/TNC/GPIO.hpp @@ -0,0 +1,48 @@ +// Copyright 2017 Mobilinkd LLC +// All rights reserved. + +#ifndef MOBILINKD__TNC__GPIO_HPP_ +#define MOBILINKD__TNC__GPIO_HPP_ + +#include "main.h" +#include "stm32l4xx_hal.h" +#include + +namespace mobilinkd { namespace tnc { + +template +struct GPIO { + static void on() { + HAL_GPIO_WritePin(reinterpret_cast(BANK), PIN, GPIO_PIN_SET); + } + static void off() { + HAL_GPIO_WritePin(reinterpret_cast(BANK), PIN, GPIO_PIN_RESET); + } + static void toggle() { + HAL_GPIO_TogglePin(reinterpret_cast(BANK), PIN); + } + static bool get() { + return HAL_GPIO_ReadPin(reinterpret_cast(BANK), PIN); + } +#if 0 + static bool operator bool() { + return HAL_GPIO_ReadPin(BANK, PIN); + } + static bool operator=(bool value) { + return HAL_GPIO_WritePin(BANK, PIN, GPIO_PinState(value)); + } +#endif +}; + +namespace gpio { + +typedef GPIO<(uint32_t)GPIOA_BASE,PTT_S_Pin> PTT_SIMPLEX; +typedef GPIO<(uint32_t)GPIOA_BASE,PTT_M_Pin> PTT_MULTIPLEX; +typedef GPIO<(uint32_t)GPIOA_BASE,LED_RED_Pin> LED_TX; +typedef GPIO<(uint32_t)GPIOA_BASE,LED_GREEN_Pin> LED_DCD; +typedef GPIO<(uint32_t)GPIOA_BASE,LED_YELLOW_Pin> LED_OTHER; +typedef GPIO<(uint32_t)GPIOB_BASE,AUDIO_OUT_ATTEN_Pin> AUDIO_OUT_ATTEN; + +}}} // mobilinkd::tnc::gpio + +#endif // MOBILINKD__TNC__GPIO_HPP_ diff --git a/TNC/Goertzel.cpp b/TNC/Goertzel.cpp new file mode 100644 index 0000000..8ae6d7e --- /dev/null +++ b/TNC/Goertzel.cpp @@ -0,0 +1,1336 @@ +// Copyright 2017 Rob Riggs +// All rights reserved. + +#include "Goertzel.h" + +namespace mobilinkd { namespace tnc { + +#if 1 +const float WINDOW[] = { + 0.006878, + 0.00687800535762, + 0.00687802143048, + 0.00687804821858, + 0.00687808572193, + 0.00687813394055, + 0.00687819287442, + 0.00687826252358, + 0.00687834288804, + 0.00687843396781, + 0.00687853576292, + 0.00687864827338, + 0.00687877149922, + 0.00687890544047, + 0.00687905009716, + 0.00687920546932, + 0.00687937155698, + 0.00687954836018, + 0.00687973587896, + 0.00687993411336, + 0.00688014306342, + 0.0068803627292, + 0.00688059311073, + 0.00688083420806, + 0.00688108602126, + 0.00688134855038, + 0.00688162179547, + 0.00688190575659, + 0.00688220043381, + 0.00688250582719, + 0.0068828219368, + 0.0068831487627, + 0.00688348630498, + 0.0068838345637, + 0.00688419353893, + 0.00688456323077, + 0.00688494363928, + 0.00688533476456, + 0.00688573660669, + 0.00688614916575, + 0.00688657244184, + 0.00688700643505, + 0.00688745114548, + 0.00688790657321, + 0.00688837271837, + 0.00688884958103, + 0.00688933716132, + 0.00688983545934, + 0.00689034447519, + 0.00689086420899, + 0.00689139466085, + 0.00689193583089, + 0.00689248771923, + 0.00689305032598, + 0.00689362365128, + 0.00689420769525, + 0.00689480245801, + 0.00689540793969, + 0.00689602414044, + 0.00689665106038, + 0.00689728869965, + 0.0068979370584, + 0.00689859613676, + 0.00689926593487, + 0.0068999464529, + 0.00690063769098, + 0.00690133964926, + 0.00690205232791, + 0.00690277572707, + 0.00690350984691, + 0.00690425468758, + 0.00690501024926, + 0.0069057765321, + 0.00690655353627, + 0.00690734126195, + 0.00690813970931, + 0.00690894887852, + 0.00690976876976, + 0.0069105993832, + 0.00691144071905, + 0.00691229277746, + 0.00691315555864, + 0.00691402906278, + 0.00691491329006, + 0.00691580824067, + 0.00691671391482, + 0.00691763031271, + 0.00691855743453, + 0.00691949528048, + 0.00692044385078, + 0.00692140314564, + 0.00692237316525, + 0.00692335390984, + 0.00692434537961, + 0.0069253475748, + 0.00692636049561, + 0.00692738414227, + 0.006928418515, + 0.00692946361403, + 0.0069305194396, + 0.00693158599192, + 0.00693266327124, + 0.00693375127779, + 0.00693485001181, + 0.00693595947354, + 0.00693707966322, + 0.00693821058111, + 0.00693935222744, + 0.00694050460247, + 0.00694166770645, + 0.00694284153963, + 0.00694402610228, + 0.00694522139465, + 0.006946427417, + 0.00694764416961, + 0.00694887165273, + 0.00695010986663, + 0.00695135881159, + 0.00695261848788, + 0.00695388889578, + 0.00695517003556, + 0.0069564619075, + 0.0069577645119, + 0.00695907784903, + 0.00696040191918, + 0.00696173672264, + 0.00696308225971, + 0.00696443853067, + 0.00696580553584, + 0.0069671832755, + 0.00696857174996, + 0.00696997095952, + 0.00697138090449, + 0.00697280158518, + 0.00697423300189, + 0.00697567515496, + 0.00697712804468, + 0.00697859167137, + 0.00698006603537, + 0.00698155113699, + 0.00698304697655, + 0.00698455355439, + 0.00698607087083, + 0.00698759892621, + 0.00698913772086, + 0.00699068725512, + 0.00699224752933, + 0.00699381854382, + 0.00699540029895, + 0.00699699279507, + 0.00699859603251, + 0.00700021001163, + 0.00700183473278, + 0.00700347019633, + 0.00700511640262, + 0.00700677335202, + 0.0070084410449, + 0.00701011948161, + 0.00701180866252, + 0.00701350858801, + 0.00701521925844, + 0.00701694067419, + 0.00701867283565, + 0.00702041574318, + 0.00702216939716, + 0.00702393379799, + 0.00702570894605, + 0.00702749484173, + 0.00702929148541, + 0.0070310988775, + 0.00703291701839, + 0.00703474590847, + 0.00703658554814, + 0.00703843593781, + 0.00704029707789, + 0.00704216896878, + 0.00704405161089, + 0.00704594500463, + 0.00704784915041, + 0.00704976404867, + 0.0070516896998, + 0.00705362610424, + 0.0070555732624, + 0.00705753117472, + 0.00705949984162, + 0.00706147926353, + 0.00706346944089, + 0.00706547037414, + 0.0070674820637, + 0.00706950451002, + 0.00707153771354, + 0.0070735816747, + 0.00707563639396, + 0.00707770187177, + 0.00707977810857, + 0.00708186510481, + 0.00708396286097, + 0.00708607137748, + 0.00708819065483, + 0.00709032069346, + 0.00709246149384, + 0.00709461305645, + 0.00709677538175, + 0.00709894847022, + 0.00710113232233, + 0.00710332693855, + 0.00710553231938, + 0.00710774846528, + 0.00710997537675, + 0.00711221305427, + 0.00711446149834, + 0.00711672070943, + 0.00711899068805, + 0.0071212714347, + 0.00712356294986, + 0.00712586523405, + 0.00712817828776, + 0.0071305021115, + 0.00713283670578, + 0.00713518207111, + 0.007137538208, + 0.00713990511696, + 0.00714228279852, + 0.00714467125318, + 0.00714707048149, + 0.00714948048395, + 0.00715190126109, + 0.00715433281345, + 0.00715677514156, + 0.00715922824594, + 0.00716169212714, + 0.00716416678568, + 0.00716665222212, + 0.007169148437, + 0.00717165543085, + 0.00717417320424, + 0.0071767017577, + 0.00717924109179, + 0.00718179120707, + 0.00718435210408, + 0.0071869237834, + 0.00718950624558, + 0.00719209949118, + 0.00719470352078, + 0.00719731833493, + 0.00719994393421, + 0.0072025803192, + 0.00720522749047, + 0.00720788544859, + 0.00721055419416, + 0.00721323372774, + 0.00721592404992, + 0.0072186251613, + 0.00722133706245, + 0.00722405975398, + 0.00722679323647, + 0.00722953751053, + 0.00723229257674, + 0.00723505843572, + 0.00723783508807, + 0.00724062253438, + 0.00724342077527, + 0.00724622981136, + 0.00724904964324, + 0.00725188027154, + 0.00725472169688, + 0.00725757391987, + 0.00726043694113, + 0.0072633107613, + 0.00726619538099, + 0.00726909080084, + 0.00727199702147, + 0.00727491404352, + 0.00727784186763, + 0.00728078049443, + 0.00728372992457, + 0.00728669015868, + 0.00728966119742, + 0.00729264304143, + 0.00729563569136, + 0.00729863914785, + 0.00730165341158, + 0.00730467848319, + 0.00730771436334, + 0.00731076105269, + 0.00731381855191, + 0.00731688686166, + 0.00731996598262, + 0.00732305591544, + 0.00732615666082, + 0.00732926821941, + 0.0073323905919, + 0.00733552377897, + 0.0073386677813, + 0.00734182259957, + 0.00734498823448, + 0.0073481646867, + 0.00735135195694, + 0.00735455004589, + 0.00735775895424, + 0.00736097868268, + 0.00736420923193, + 0.00736745060269, + 0.00737070279565, + 0.00737396581154, + 0.00737723965105, + 0.0073805243149, + 0.00738381980381, + 0.00738712611849, + 0.00739044325966, + 0.00739377122804, + 0.00739711002436, + 0.00740045964934, + 0.00740382010372, + 0.00740719138821, + 0.00741057350357, + 0.00741396645051, + 0.00741737022979, + 0.00742078484213, + 0.00742421028828, + 0.00742764656899, + 0.00743109368501, + 0.00743455163707, + 0.00743802042595, + 0.00744150005238, + 0.00744499051712, + 0.00744849182094, + 0.00745200396459, + 0.00745552694884, + 0.00745906077446, + 0.0074626054422, + 0.00746616095284, + 0.00746972730716, + 0.00747330450593, + 0.00747689254992, + 0.00748049143992, + 0.0074841011767, + 0.00748772176106, + 0.00749135319377, + 0.00749499547563, + 0.00749864860742, + 0.00750231258994, + 0.00750598742399, + 0.00750967311036, + 0.00751336964985, + 0.00751707704327, + 0.00752079529142, + 0.0075245243951, + 0.00752826435514, + 0.00753201517233, + 0.00753577684749, + 0.00753954938144, + 0.007543332775, + 0.00754712702899, + 0.00755093214423, + 0.00755474812154, + 0.00755857496176, + 0.00756241266571, + 0.00756626123423, + 0.00757012066815, + 0.00757399096831, + 0.00757787213555, + 0.00758176417071, + 0.00758566707463, + 0.00758958084816, + 0.00759350549215, + 0.00759744100745, + 0.0076013873949, + 0.00760534465538, + 0.00760931278973, + 0.00761329179881, + 0.0076172816835, + 0.00762128244464, + 0.00762529408311, + 0.00762931659978, + 0.00763334999552, + 0.0076373942712, + 0.0076414494277, + 0.0076455154659, + 0.00764959238668, + 0.00765368019092, + 0.0076577788795, + 0.00766188845331, + 0.00766600891325, + 0.00767014026021, + 0.00767428249507, + 0.00767843561874, + 0.00768259963212, + 0.00768677453611, + 0.0076909603316, + 0.00769515701951, + 0.00769936460074, + 0.00770358307621, + 0.00770781244683, + 0.00771205271351, + 0.00771630387717, + 0.00772056593873, + 0.00772483889911, + 0.00772912275923, + 0.00773341752003, + 0.00773772318243, + 0.00774203974736, + 0.00774636721576, + 0.00775070558855, + 0.00775505486669, + 0.0077594150511, + 0.00776378614274, + 0.00776816814254, + 0.00777256105145, + 0.00777696487043, + 0.00778137960042, + 0.00778580524238, + 0.00779024179727, + 0.00779468926603, + 0.00779914764964, + 0.00780361694906, + 0.00780809716525, + 0.00781258829918, + 0.00781709035181, + 0.00782160332413, + 0.00782612721711, + 0.00783066203172, + 0.00783520776893, + 0.00783976442975, + 0.00784433201514, + 0.00784891052609, + 0.00785349996359, + 0.00785810032863, + 0.00786271162221, + 0.00786733384532, + 0.00787196699895, + 0.00787661108411, + 0.00788126610179, + 0.00788593205301, + 0.00789060893876, + 0.00789529676006, + 0.00789999551792, + 0.00790470521335, + 0.00790942584736, + 0.00791415742098, + 0.00791889993523, + 0.00792365339112, + 0.00792841778967, + 0.00793319313193, + 0.00793797941892, + 0.00794277665166, + 0.00794758483119, + 0.00795240395855, + 0.00795723403478, + 0.00796207506091, + 0.007966927038, + 0.00797178996707, + 0.00797666384919, + 0.0079815486854, + 0.00798644447675, + 0.0079913512243, + 0.00799626892911, + 0.00800119759222, + 0.00800613721471, + 0.00801108779763, + 0.00801604934206, + 0.00802102184906, + 0.00802600531969, + 0.00803099975504, + 0.00803600515617, + 0.00804102152416, + 0.0080460488601, + 0.00805108716506, + 0.00805613644013, + 0.00806119668639, + 0.00806626790493, + 0.00807135009684, + 0.00807644326321, + 0.00808154740515, + 0.00808666252374, + 0.00809178862008, + 0.00809692569528, + 0.00810207375045, + 0.00810723278668, + 0.00811240280508, + 0.00811758380677, + 0.00812277579286, + 0.00812797876447, + 0.00813319272271, + 0.0081384176687, + 0.00814365360356, + 0.00814890052842, + 0.00815415844441, + 0.00815942735265, + 0.00816470725428, + 0.00816999815042, + 0.00817530004221, + 0.0081806129308, + 0.00818593681732, + 0.00819127170291, + 0.00819661758873, + 0.0082019744759, + 0.00820734236559, + 0.00821272125895, + 0.00821811115712, + 0.00822351206127, + 0.00822892397256, + 0.00823434689214, + 0.00823978082117, + 0.00824522576082, + 0.00825068171227, + 0.00825614867667, + 0.0082616266552, + 0.00826711564903, + 0.00827261565934, + 0.00827812668731, + 0.00828364873412, + 0.00828918180095, + 0.00829472588898, + 0.00830028099941, + 0.00830584713342, + 0.00831142429221, + 0.00831701247697, + 0.00832261168889, + 0.00832822192917, + 0.00833384319902, + 0.00833947549964, + 0.00834511883223, + 0.008350773198, + 0.00835643859816, + 0.00836211503393, + 0.00836780250651, + 0.00837350101713, + 0.00837921056699, + 0.00838493115734, + 0.00839066278937, + 0.00839640546433, + 0.00840215918344, + 0.00840792394793, + 0.00841369975903, + 0.00841948661798, + 0.00842528452601, + 0.00843109348436, + 0.00843691349428, + 0.008442744557, + 0.00844858667377, + 0.00845443984585, + 0.00846030407447, + 0.0084661793609, + 0.00847206570638, + 0.00847796311218, + 0.00848387157955, + 0.00848979110975, + 0.00849572170406, + 0.00850166336372, + 0.00850761609002, + 0.00851357988422, + 0.00851955474759, + 0.00852554068142, + 0.00853153768697, + 0.00853754576552, + 0.00854356491836, + 0.00854959514677, + 0.00855563645203, + 0.00856168883544, + 0.00856775229828, + 0.00857382684185, + 0.00857991246744, + 0.00858600917635, + 0.00859211696987, + 0.00859823584931, + 0.00860436581598, + 0.00861050687117, + 0.0086166590162, + 0.00862282225238, + 0.00862899658102, + 0.00863518200343, + 0.00864137852093, + 0.00864758613484, + 0.00865380484648, + 0.00866003465717, + 0.00866627556825, + 0.00867252758104, + 0.00867879069686, + 0.00868506491706, + 0.00869135024296, + 0.00869764667591, + 0.00870395421724, + 0.00871027286829, + 0.00871660263041, + 0.00872294350495, + 0.00872929549324, + 0.00873565859665, + 0.00874203281652, + 0.00874841815421, + 0.00875481461108, + 0.00876122218848, + 0.00876764088777, + 0.00877407071033, + 0.00878051165751, + 0.00878696373068, + 0.00879342693122, + 0.0087999012605, + 0.00880638671988, + 0.00881288331075, + 0.00881939103449, + 0.00882590989247, + 0.00883243988609, + 0.00883898101672, + 0.00884553328576, + 0.00885209669459, + 0.00885867124461, + 0.0088652569372, + 0.00887185377378, + 0.00887846175573, + 0.00888508088446, + 0.00889171116137, + 0.00889835258787, + 0.00890500516536, + 0.00891166889526, + 0.00891834377897, + 0.00892502981791, + 0.0089317270135, + 0.00893843536715, + 0.00894515488029, + 0.00895188555434, + 0.00895862739073, + 0.00896538039087, + 0.00897214455621, + 0.00897891988817, + 0.00898570638819, + 0.00899250405771, + 0.00899931289815, + 0.00900613291097, + 0.00901296409761, + 0.0090198064595, + 0.00902665999811, + 0.00903352471487, + 0.00904040061125, + 0.00904728768868, + 0.00905418594864, + 0.00906109539257, + 0.00906801602194, + 0.00907494783821, + 0.00908189084285, + 0.00908884503732, + 0.00909581042309, + 0.00910278700163, + 0.00910977477442, + 0.00911677374293, + 0.00912378390864, + 0.00913080527304, + 0.00913783783759, + 0.00914488160379, + 0.00915193657313, + 0.00915900274708, + 0.00916608012715, + 0.00917316871483, + 0.00918026851161, + 0.00918737951899, + 0.00919450173846, + 0.00920163517154, + 0.00920877981973, + 0.00921593568452, + 0.00922310276744, + 0.00923028106999, + 0.00923747059368, + 0.00924467134003, + 0.00925188331055, + 0.00925910650677, + 0.00926634093021, + 0.00927358658239, + 0.00928084346483, + 0.00928811157907, + 0.00929539092664, + 0.00930268150906, + 0.00930998332787, + 0.00931729638461, + 0.00932462068082, + 0.00933195621804, + 0.00933930299781, + 0.00934666102167, + 0.00935403029118, + 0.00936141080789, + 0.00936880257334, + 0.0093762055891, + 0.00938361985671, + 0.00939104537774, + 0.00939848215374, + 0.00940593018628, + 0.00941338947693, + 0.00942086002725, + 0.0094283418388, + 0.00943583491318, + 0.00944333925193, + 0.00945085485665, + 0.00945838172891, + 0.00946591987028, + 0.00947346928236, + 0.00948102996673, + 0.00948860192496, + 0.00949618515866, + 0.00950377966942, + 0.00951138545881, + 0.00951900252845, + 0.00952663087993, + 0.00953427051484, + 0.0095419214348, + 0.0095495836414, + 0.00955725713624, + 0.00956494192095, + 0.00957263799712, + 0.00958034536637, + 0.00958806403032, + 0.00959579399058, + 0.00960353524878, + 0.00961128780652, + 0.00961905166544, + 0.00962682682716, + 0.00963461329331, + 0.00964241106551, + 0.00965022014541, + 0.00965804053463, + 0.00966587223481, + 0.00967371524759, + 0.0096815695746, + 0.0096894352175, + 0.00969731217793, + 0.00970520045752, + 0.00971310005794, + 0.00972101098083, + 0.00972893322785, + 0.00973686680065, + 0.00974481170089, + 0.00975276793023, + 0.00976073549033, + 0.00976871438286, + 0.00977670460948, + 0.00978470617186, + 0.00979271907166, + 0.00980074331057, + 0.00980877889026, + 0.0098168258124, + 0.00982488407868, + 0.00983295369076, + 0.00984103465035, + 0.00984912695912, + 0.00985723061876, + 0.00986534563096, + 0.00987347199742, + 0.00988160971981, + 0.00988975879986, + 0.00989791923924, + 0.00990609103966, + 0.00991427420282, + 0.00992246873044, + 0.0099306746242, + 0.00993889188583, + 0.00994712051704, + 0.00995536051953, + 0.00996361189502, + 0.00997187464524, + 0.00998014877189, + 0.0099884342767, + 0.0099967311614, + 0.0100050394277, + 0.0100133590774, + 0.0100216901121, + 0.0100300325336, + 0.0100383863436, + 0.010046751544, + 0.0100551281363, + 0.0100635161224, + 0.010071915504, + 0.0100803262828, + 0.0100887484606, + 0.0100971820391, + 0.0101056270202, + 0.0101140834054, + 0.0101225511967, + 0.0101310303957, + 0.0101395210042, + 0.010148023024, + 0.0101565364569, + 0.0101650613045, + 0.0101735975687, + 0.0101821452513, + 0.0101907043539, + 0.0101992748785, + 0.0102078568267, + 0.0102164502004, + 0.0102250550013, + 0.0102336712312, + 0.0102422988919, + 0.0102509379852, + 0.0102595885129, + 0.0102682504767, + 0.0102769238785, + 0.01028560872, + 0.0102943050031, + 0.0103030127295, + 0.010311731901, + 0.0103204625196, + 0.0103292045869, + 0.0103379581047, + 0.010346723075, + 0.0103554994994, + 0.0103642873798, + 0.0103730867181, + 0.010381897516, + 0.0103907197753, + 0.010399553498, + 0.0104083986857, + 0.0104172553404, + 0.0104261234639, + 0.0104350030579, + 0.0104438941244, + 0.0104527966652, + 0.010461710682, + 0.0104706361768, + 0.0104795731514, + 0.0104885216076, + 0.0104974815473, + 0.0105064529723, + 0.0105154358845, + 0.0105244302857, + 0.0105334361778, + 0.0105424535626, + 0.0105514824421, + 0.0105605228179, + 0.0105695746921, + 0.0105786380665, + 0.0105877129429, + 0.0105967993233, + 0.0106058972094, + 0.0106150066032, + 0.0106241275065, + 0.0106332599212, + 0.0106424038493, + 0.0106515592925, + 0.0106607262527, + 0.0106699047319, + 0.010679094732, + 0.0106882962547, + 0.0106975093021, + 0.010706733876, + 0.0107159699782, + 0.0107252176108, + 0.0107344767756, + 0.0107437474745, + 0.0107530297093, + 0.0107623234821, + 0.0107716287947, + 0.0107809456491, + 0.0107902740471, + 0.0107996139906, + 0.0108089654816, + 0.010818328522, + 0.0108277031138, + 0.0108370892587, + 0.0108464869588, + 0.010855896216, + 0.0108653170322, + 0.0108747494094, + 0.0108841933495, + 0.0108936488543, + 0.0109031159259, + 0.0109125945662, + 0.0109220847772, + 0.0109315865607, + 0.0109410999187, + 0.0109506248532, + 0.0109601613661, + 0.0109697094594, + 0.010979269135, + 0.0109888403949, + 0.0109984232411, + 0.0110080176754, + 0.0110176236999, + 0.0110272413165, + 0.0110368705272, + 0.0110465113339, + 0.0110561637387, + 0.0110658277435, + 0.0110755033502, + 0.0110851905609, + 0.0110948893776, + 0.0111045998021, + 0.0111143218365, + 0.0111240554828, + 0.011133800743, + 0.011143557619, + 0.0111533261128, + 0.0111631062265, + 0.011172897962, + 0.0111827013213, + 0.0111925163065, + 0.0112023429194, + 0.0112121811622, + 0.0112220310368, + 0.0112318925453, + 0.0112417656896, + 0.0112516504718, + 0.0112615468938, + 0.0112714549577, + 0.0112813746655, + 0.0112913060192, + 0.0113012490209, + 0.0113112036725, + 0.0113211699762, + 0.0113311479338, + 0.0113411375475, + 0.0113511388192, + 0.0113611517511, + 0.0113711763451, + 0.0113812126033, + 0.0113912605277, + 0.0114013201204, + 0.0114113913834, + 0.0114214743187, + 0.0114315689285, + 0.0114416752147, + 0.0114517931793, + 0.0114619228246, + 0.0114720641524, + 0.0114822171649, + 0.0114923818642, + 0.0115025582522, + 0.0115127463311, + 0.0115229461028, + 0.0115331575696, + 0.0115433807334, + 0.0115536155964, + 0.0115638621605, + 0.0115741204279, + 0.0115843904007, + 0.0115946720809, + 0.0116049654706, + 0.0116152705719, + 0.0116255873869, + 0.0116359159177, + 0.0116462561663, + 0.0116566081349, + 0.0116669718255, + 0.0116773472403, + 0.0116877343812, + 0.0116981332506, + 0.0117085438503, + 0.0117189661827, + 0.0117294002496, + 0.0117398460533, + 0.0117503035959, + 0.0117607728795, + 0.0117712539061, + 0.011781746678, + 0.0117922511972, + 0.0118027674658, + 0.011813295486, + 0.0118238352599, + 0.0118343867896, + 0.0118449500773, + 0.011855525125, + 0.0118661119349, + 0.0118767105092, + 0.0118873208499, + 0.0118979429593, + 0.0119085768394, + 0.0119192224924, + 0.0119298799204, + 0.0119405491256, + 0.0119512301102, + 0.0119619228762, + 0.0119726274258, + 0.0119833437613, + 0.0119940718847, + 0.0120048117981, + 0.0120155635039, + 0.012026327004, + 0.0120371023008, + 0.0120478893962, + 0.0120586882926, + 0.0120694989921, + 0.0120803214969, + 0.012091155809, + 0.0121020019308, + 0.0121128598643, + 0.0121237296118, + 0.0121346111755, + 0.0121455045575, + 0.01215640976, + 0.0121673267852, + 0.0121782556352, + 0.0121891963124, + 0.0122001488188, + 0.0122111131567, + 0.0122220893282, + 0.0122330773356, + 0.0122440771811, + 0.0122550888669, + 0.0122661123951, + 0.012277147768, + 0.0122881949877, + 0.0122992540566, + 0.0123103249768, + 0.0123214077505, + 0.0123325023799, + 0.0123436088673, + 0.0123547272149, + 0.0123658574249, + 0.0123769994995, + 0.012388153441, + 0.0123993192515, + 0.0124104969334, + 0.0124216864888, + 0.01243288792, + 0.0124441012292, + 0.0124553264186, + 0.0124665634906, + 0.0124778124472, + 0.0124890732909, + 0.0125003460238, + 0.0125116306481, + 0.0125229271662, + 0.0125342355802, + 0.0125455558925, + 0.0125568881053, + 0.0125682322208, + 0.0125795882413, + 0.012590956169, + 0.0126023360063, + 0.0126137277554, + 0.0126251314186, + 0.012636546998, + 0.0126479744961, + 0.012659413915, + 0.0126708652571, + 0.0126823285246, + 0.0126938037198, + 0.012705290845, + 0.0127167899025, + 0.0127283008945, + 0.0127398238233, + 0.0127513586913, + 0.0127629055007, + 0.0127744642538, + 0.0127860349529, + 0.0127976176003, + 0.0128092121983, + 0.0128208187492, + 0.0128324372553, + 0.012844067719, + 0.0128557101424, + 0.0128673645279, + 0.0128790308779, + 0.0128907091946, + 0.0129023994803, + 0.0129141017375, + 0.0129258159683, + 0.0129375421751, + 0.0129492803602, + 0.012961030526, + 0.0129727926748, + 0.0129845668089, + 0.0129963529306, + 0.0130081510423, + 0.0130199611462, + 0.0130317832448, + 0.0130436173404, + 0.0130554634352, + 0.0130673215318, + 0.0130791916323, + 0.0130910737391, + 0.0131029678546, + 0.0131148739811, + 0.013126792121, + 0.0131387222767, + 0.0131506644504, + 0.0131626186445, + 0.0131745848615, + 0.0131865631036, + 0.0131985533732, + 0.0132105556727, + 0.0132225700045, + 0.0132345963708, + 0.0132466347742, + 0.0132586852168, + 0.0132707477013, + 0.0132828222298, + 0.0132949088047, + 0.0133070074286, + 0.0133191181036, + 0.0133312408323, + 0.013343375617, + 0.0133555224601, + 0.0133676813639, + 0.0133798523309, + 0.0133920353635, + 0.013404230464, + 0.0134164376349, + 0.0134286568785, + 0.0134408881972, + 0.0134531315935, + 0.0134653870697, + 0.0134776546283, + 0.0134899342716, + 0.0135022260021, + 0.0135145298222, + 0.0135268457342, + 0.0135391737407, + 0.013551513844, + 0.0135638660465, + 0.0135762303506, + 0.0135886067589, + 0.0136009952736, + 0.0136133958973, + 0.0136258086323, + 0.0136382334811, + 0.0136506704462, + 0.0136631195298, + 0.0136755807346, + 0.0136880540629, + 0.0137005395171, + 0.0137130370997, + 0.0137255468132, + 0.0137380686599, + 0.0137506026424, + 0.0137631487631, + 0.0137757070244, + 0.0137882774287, + 0.0138008599786, + 0.0138134546765, + 0.0138260615249, + 0.0138386805261, + 0.0138513116827, + 0.0138639549971, + 0.0138766104718, + 0.0138892781093, + 0.013901957912, + 0.0139146498824, + 0.013927354023, + 0.0139400703362, + 0.0139527988246, + 0.0139655394905, + 0.0139782923365, + 0.0139910573651, + 0.0140038345787, + 0.0140166239798, + 0.0140294255709, + 0.0140422393546, + 0.0140550653332, + 0.0140679035093, + 0.0140807538854, + 0.014093616464, + 0.0141064912475, + 0.0141193782385, + 0.0141322774395, + 0.0141451888529, + 0.0141581124814, + 0.0141710483273, + 0.0141839963932, + 0.0141969566816, + 0.0142099291951, + 0.014222913936, + 0.0142359109071, + 0.0142489201107, + 0.0142619415493, + 0.0142749752256, + 0.014288021142, + 0.0143010793011, + 0.0143141497054, + 0.0143272323574, + 0.0143403272596, + 0.0143534344146, + 0.0143665538249, + 0.0143796854931, + 0.0143928294216, + 0.0144059856131, + 0.01441915407, + 0.014432334795, + 0.0144455277905, + 0.0144587330591, + 0.0144719506033, + 0.0144851804258, + 0.014498422529, + 0.0145116769155, + 0.0145249435879, + 0.0145382225487, + 0.0145515138005, + 0.0145648173459, + 0.0145781331873, + 0.0145914613275, + 0.0146048017688, + 0.014618154514, + 0.0146315195656, + 0.0146448969261, + 0.0146582865981, + 0.0146716885842, + 0.014685102887, + 0.0146985295091, + 0.014711968453, + 0.0147254197214, + 0.0147388833167, + 0.0147523592417, + 0.0147658474988, + 0.0147793480907, + 0.01479286102, + 0.0148063862893, + 0.0148199239011, + 0.014833473858, + 0.0148470361627, + 0.0148606108178, + 0.0148741978258, + 0.0148877971894, + 0.0149014089111, + 0.0149150329937, + 0.0149286694396, + 0.0149423182515, + 0.014955979432, + 0.0149696529838, + 0.0149833389094, + 0.0149970372115, + 0.0150107478926, + 0.0150244709555, + 0.0150382064027, + 0.0150519542368, + 0.0150657144606, + 0.0150794870765, + 0.0150932720873, + 0.0151070694956, + 0.015120879304, + 0.0151347015151, + 0.0151485361317, + 0.0151623831562, + 0.0151762425915, + 0.01519011444, + 0.0152039987046, + 0.0152178953877, + 0.0152318044921, + 0.0152457260204, + 0.0152596599752, + 0.0152736063593, + 0.0152875651752, + 0.0153015364257, + 0.0153155201133, + 0.0153295162409, + 0.0153435248109, + 0.0153575458261, + 0.0153715792891, + 0.0153856252027, + 0.0153996835694, + 0.015413754392, + 0.0154278376731, + 0.0154419334155, + 0.0154560416217, + 0.0154701622944, + 0.0154842954365, + 0.0154984410504, + 0.0155125991389, + 0.0155267697048, + 0.0155409527506, + 0.0155551482791, + 0.0155693562929, + 0.0155835767948, + 0.0155978097874, + 0.0156120552735, + 0.0156263132558, + 0.0156405837368, + 0.0156548667195, + 0.0156691622063, + 0.0156834702001, + 0.0156977907036, + 0.0157121237195, + 0.0157264692504, + 0.0157408272991, + 0.0157551978683, + 0.0157695809607, + 0.0157839765791, + 0.0157983847261, + 0.0158128054045, + 0.015827238617, + 0.0158416843663, + 0.0158561426551, + 0.0158706134863, + 0.0158850968624, + 0.0158995927862, + 0.0159141012606, + 0.0159286222881, + 0.0159431558716, + 0.0159577020137, + 0.0159722607172, + 0.0159868319849, + 0.0160014158195, + 0.0160160122238, + 0.0160306212004, + 0.0160452427522, + 0.0160598768818, + 0.0160745235921, + 0.0160891828858, + 0.0161038547656, + 0.0161185392343, + 0.0161332362947, + 0.0161479459495, + 0.0161626682015, + 0.0161774030534, + 0.016192150508, + 0.0162069105681, + 0.0162216832365, + 0.0162364685158, + 0.016251266409, + 0.0162660769187, + 0.0162809000477, + 0.0162957357989, + 0.0163105841749, + 0.0163254451786, + 0.0163403188128, + 0.0163552050802, + 0.0163701039836, + 0.0163850155258, + 0.0163999397096, + 0.0164148765379, + 0.0164298260132, + 0.0164447881386, + 0.0164597629167, + 0.0164747503504, + 0.0164897504425, + +}; + +#endif + +}} // mobilinkd::tnc + diff --git a/TNC/Goertzel.h b/TNC/Goertzel.h new file mode 100644 index 0000000..40dea7a --- /dev/null +++ b/TNC/Goertzel.h @@ -0,0 +1,138 @@ +// Copyright 2017 Rob Riggs +// All rights reserved. + +#ifndef MOBILINKD__TNC__GOERTZEL_FILTER_HPP_ +#define MOBILINKD__TNC__GOERTZEL_FILTER_HPP_ + +#include +#include + +namespace mobilinkd { namespace tnc { + +extern const float WINDOW[]; + +template +class GoertzelFilter +{ + float filterFreq_; + int bin_; + float coeff_; + float d1{0.0f}; + float d2{0.0f}; + uint32_t count{0}; + const float* window_; + +public: + GoertzelFilter(float filter_freq, const float* window = WINDOW) + : filterFreq_(filter_freq) + , bin_(0.5f + ((filter_freq * SAMPLES) / SAMPLE_RATE)) + , coeff_(2.0f * cos((2.0f * M_PI * bin_) / float(SAMPLES))) + , window_(window) + {} + + void operator()(float* samples, uint32_t n) { + + for (size_t i = 0; i != n; ++i) { + float w = window_ ? window_[count] : 1.0; + float y = w * samples[i] + coeff_ * d1 - d2; + d2 = d1; + d1 = y; + ++count; + } + } + + void operator()(uint16_t* samples, uint32_t n) { + + for (uint32_t i = 0; i != n; ++i) { + float w = window_ ? window_[count] : 1.0; + float sample = (float(samples[i]) - 2048.0f) / 2048.0f; + float y = w * sample + coeff_ * d1 - d2; + d2 = d1; + d1 = y; + ++count; + } + } + + operator float() const { + return d2 * d2 + d1 * d1 - coeff_ * d1 * d2; + } + + void reset() { + d1 = 0.0f; + d2 = 0.0f; + count = 0; + } +}; + +#if 0 +template +class GoertzelFactory +{ + float window_[SAMPLES]; + static void make_window(float* window) + { + for (size_t i = 0; i != SAMPLES; ++i) { + window[i] = 0.54f - 0.46f * cos(2.0f * M_PI * (float(i) / float(SAMPLE_RATE))); + } + } + +public: + + GoertzelFactory() + : window_() + { + make_window(window_); + } + + GoertzelFilter make(float freq) + { + return GoertzelFilter(freq, window_); + } +}; +#endif + +// Complex Goertzel +// Based on https://dsp.stackexchange.com/a/23946/36581 +template +struct Goertzel +{ + typedef Goertzel type; + typedef std::complex complex_type; + typedef Float float_type; + + float_type sin_; + float_type cos_; + float_type coeff_; + float_type q0{0.0}, q1{0.0}, q2{0.0}; + explicit Goertzel(float_type omega) + : sin_(sin(omega)), cos_(cos(omega)), coeff_(2.0 * cos_) + {} + + static type from_frequency(float_type frequency, float_type sample_rate) + { + return Goertzel(2.0 * PI * frequency / sample_rate); + } + + template + complex_type operator()(const Container& data) + { + for (auto& v : data) { + q0 = coeff_ * q1 - q2 + v; + q2 = q1; + q1 = q0; + } + + auto real = (q1 - q2 * cos_); + auto imag = (q2 * sin_); + + q0 = q1 = q2 = 0.0; + + return complex_type(real, imag); + } +}; + +typedef Goertzel FloatGoertzel; + +}} // mobilinkd::tnc + +#endif // MOBILINKD__TNC__GOERTZEL_FILTER_HPP_ diff --git a/TNC/HDLCEncoder.hpp b/TNC/HDLCEncoder.hpp new file mode 100644 index 0000000..21f61f3 --- /dev/null +++ b/TNC/HDLCEncoder.hpp @@ -0,0 +1,232 @@ +// Copyright 2015 Mobilinkd LLC +// All rights reserved. + +#ifndef INC_HDLCENCODER_HPP_ +#define INC_HDLCENCODER_HPP_ + +#include "AFSKModulator.hpp" +#include "HdlcFrame.hpp" +#include "NRZI.hpp" +#include "PTT.hpp" +#include "GPIO.hpp" +#include "KissHardware.hpp" +#include "AudioInput.hpp" +#include "Led.h" + +#include "main.h" + +#include + +#include + +namespace mobilinkd { namespace tnc { namespace hdlc { + +using namespace mobilinkd::libafsk; + +struct Encoder { + + static const uint8_t FLAG = 0x7E; + + enum class state_type { + STATE_IDLE, + STATE_HEAD, + STATE_FRAME, + STATE_CRC_LOW, + STATE_CRC_HIGH, + STATE_TAIL, + }; + + uint8_t tx_delay_; + uint8_t tx_tail_; + uint8_t p_persist_; + uint8_t slot_time_; + bool duplex_; + state_type state_; + int ones_; + NRZI nrzi_; + uint16_t crc_; + osMessageQId input_; + AFSKModulator* modulator_; + volatile bool running_; + bool send_delay_; // Avoid sending the preamble for back-to-back frames. + + Encoder(osMessageQId input, AFSKModulator* output) + : tx_delay_(kiss::settings().txdelay), tx_tail_(kiss::settings().txtail) + , p_persist_(kiss::settings().ppersist), slot_time_(kiss::settings().slot) + , duplex_(kiss::settings().duplex), state_(state_type::STATE_IDLE) + , ones_(0), nrzi_(), crc_() + , input_(input), modulator_(output) + , running_(false), send_delay_(true) + {} + + void run() { + running_ = true; + send_delay_ = true; + while (running_) { + state_ = state_type::STATE_IDLE; + osEvent evt = osMessageGet(input_, osWaitForever); + if (evt.status == osEventMessage) { + auto frame = (IoFrame*) evt.value.p; + process(frame); + // See if we have back-to-back frames. + evt = osMessagePeek(input_, 0); + if (evt.status != osEventMessage) { + send_delay_ = true; + if (!duplex_) { + osMessagePut(audioInputQueueHandle, audio::DEMODULATOR, + osWaitForever); + } + } + } + } + } + + int tx_delay() const { return tx_delay_; } + void tx_delay(int ms) { tx_delay_ = ms; } + + int tx_tail() const { return tx_tail_; } + void tx_tail(int ms) { tx_tail_ = ms; } + + int slot_time() const { return slot_time_; } + void slot_time(int value) { slot_time_ = value; } + + int p_persist() const { return p_persist_; } + void p_persist(int value) { p_persist_ = value; } + + state_type status() const {return state_; } + void stop() { running_ = false; } + + int rng_() const {return osKernelSysTick() & 0xFF;} + + /** + * Do the p*persistent CSMA handling. In order to prevent resource + * starvation, we drop any packets delayed by more than 5 seconds. + * + * 0. CSMA called. + * 1. If the channel is open + * 1a. Pick a random number between 0-255. + * 1b. If it less than or equal to p, transmit the packet. + * 2. Otherwise wait slot_time * 10 ms. + * 2.b Go to step 1. + * + * In general, a p*persistent CSMA protocol should be adaptive. The + * p value should be dynamically computed based on network load. In + * practice, this is rather difficult to do with APRS because there + * is no easy way to measure the collision rate. + * + * For APRS digipeaters, the slot_time and p values should be 0 and 255, + * respectively. This is equivalent to 1-persistent CSMA. + * + * @note For this to work, the demodulator must be left running + * while CSMA is taking place in order to do carrier detection. + * + * @return true if OK to send, otherwise CSMA has timed out and + * the packet should be dropped. + */ + 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. + + if (!led_dcd_status()) { + // Channel is clear... send now. + return true; + } + + uint16_t counter = 0; + while (counter < 10000) { + osDelay(slot_time_); // We count on minimum delay = 1. + counter += slot_time_; + + if (rng_() < p_persist_) { + if (!led_dcd_status()) { + // Channel is clear... send now. + return true; + } + } + } + return false; + } + + /** + * Send the frame. If send_delay_ is set, we are sending the first + * of potentially multiple frames. We must do two things in this case: + * wait for the channel to clear and send the TX delay preamble. + * Otherwise, we are sending a follow-on frame and can skip the + * CSMA and preamble. The channel is ours and the begin/end frame + * bytes are enough to delimit the frames. + * + * If CSMA fails (times out), the frame is dropped and the send_delay_ + * flag is not cleared. This will cause CSMA and TX delay to be + * attempted on the next frame. + * + * @param frame + */ + void process(IoFrame* frame) { + ones_ = 0; // Reset the ones count for each frame. + + frame->add_fcs(); + + if (send_delay_) { + if (not do_csma()) return; + if (!duplex_) { + osMessagePut(audioInputQueueHandle, audio::IDLE, osWaitForever); + } + send_delay(); + send_delay_ = false; + } + + for (auto c : *frame) send(c); + release(frame); + send_tail(); + } + + void send_delay() { + const size_t tmp = (tx_delay_ * 3) / 2; + for (size_t i = 0; i != tmp; i++) { + send_raw(FLAG); + } + } + + void send_fcs(uint16_t fcs) { + uint8_t low = fcs & 0xFF; + uint8_t high = (fcs >> 8) & 0xFF; + + send(low); + send(high); + } + + void send_tail() { + send_raw(FLAG); + } + + // No bit stuffing for PREAMBLE and TAIL + void send_raw(uint8_t byte) { + for (size_t i = 0; i != 8; i++) { + uint8_t bit = byte & 1; + modulator_->send(nrzi_.encode(bit)); + byte >>= 1; + } + } + + void send(uint8_t byte) { + for (size_t i = 0; i != 8; i++) { + uint8_t bit = byte & 1; + modulator_->send(nrzi_.encode(bit)); + if (bit) { + ++ones_; + if (ones_ == 5) { + modulator_->send(nrzi_.encode(0)); + ones_ = 0; + } + } else { + ones_ = 0; + } + byte >>= 1; + } + } +}; + +}}} // mobilinkd::tnc::hdlc + +#endif // INC_HDLCENCODER_HPP_ diff --git a/TNC/HdlcDecoder.cpp b/TNC/HdlcDecoder.cpp new file mode 100644 index 0000000..dbbd251 --- /dev/null +++ b/TNC/HdlcDecoder.cpp @@ -0,0 +1,53 @@ +// Copyright 2016 Rob Riggs +// All rights reserved. + +#include "HdlcDecoder.hpp" +#include "GPIO.hpp" + +namespace mobilinkd { namespace tnc { namespace hdlc { + +Decoder::Decoder(bool pass_all) +: state_(SEARCH), ones_(0), buffer_(0), frame_(ioFramePool().acquire()) +, bits_(0), passall_(pass_all), ready_(false) +{} + +IoFrame* Decoder::operator()(bool bit, bool pll) +{ + IoFrame* result = nullptr; + + // 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_) return result; + + if (not pll) { + if ((state_ == FRAMING) and (frame_->size() > 17) and passall_) { + frame_->parse_fcs(); + if (passall_ or frame_->ok()) { + result = frame_; + ready_ = false; + frame_ = ioFramePool().acquire(); + return result; + } + } + reset(); + } else { + if (process(bit)) { + ready_ = false; + frame_->parse_fcs(); + if (passall_ or frame_->ok()) { + result = frame_; + frame_ = ioFramePool().acquire(); + return result; + } + frame_->clear(); + } + } + + return result; +} + + +}}} // mobilinkd::tnc::hdlc + + diff --git a/TNC/HdlcDecoder.hpp b/TNC/HdlcDecoder.hpp new file mode 100644 index 0000000..20e8120 --- /dev/null +++ b/TNC/HdlcDecoder.hpp @@ -0,0 +1,328 @@ +// Copyright 2015 Mobilinkd LLC +// All rights reserved. + +#ifndef MOBILINKD___HDLC_DECODER_HPP_ +#define MOBILINKD___HDLC_DECODER_HPP_ + +#include "HdlcFrame.hpp" + +#include + +namespace mobilinkd { namespace tnc { namespace hdlc { + +struct Decoder +{ + static const uint16_t FLAG = 0x7E00; + static const uint16_t ABORT = 0x7F; + static const uint16_t IDLE = 0xFF; + + enum state {SEARCH, HUNT, FRAMING}; + + state state_; + int ones_; + uint16_t buffer_; + IoFrame* frame_; + int bits_; + bool passall_; + bool ready_; + + Decoder(bool pass_all = false); + + void reset() { + state_ = SEARCH; + ones_ = 0; + buffer_ = 0; + if (frame_) frame_->clear(); + ready_ = false; + } + + bool process(bool bit) + { + switch (state_) + { + case SEARCH: + search(bit); + break; + case HUNT: + hunt(bit); + break; + case FRAMING: + frame(bit); + break; + default: + reset(); + break; + } + + return ready_; + } + + /** + * Process a bit. If the bit results in a frame, a result set containing + * the frame, the FCS, and a flag indicating whether it is valid is + * returned. If HDLC was contructed with passall=false, the flag returned + * is always true as only valid frames are returned. + * + * When PLL is passed, when true it indicates that the bit should be + * processed, and when false indicates that any frame in progress should + * be dropped and the state reset to SEARCH. + */ + IoFrame* operator()(bool bit, bool pll); + + void add_bit(bool bit) + { + const uint16_t BIT = 0x8000; + + buffer_ >>= 1; + buffer_ |= (BIT * int(bit)); + bits_ += 1; + } + + char get_char() + { + char result = (buffer_ & 0xFF); + + return result; + } + + void consume_byte() + { + const uint16_t MASK = 0xFF00; + + buffer_ &= MASK; + bits_ -= 8; + } + + void consume_bit() + { + const uint16_t MASK = 0xFF00; + + uint16_t tmp = (buffer_ & 0x7F); + tmp <<= 1; + buffer_ &= MASK; + buffer_ |= tmp; + bits_ -= 1; + } + + void start_search() + { + state_ = SEARCH; + } + + bool have_flag() + { + const uint16_t MASK = 0xFF00; + + return (buffer_ & MASK) == FLAG; + } + + void start_hunt() + { + state_ = HUNT; + bits_ = 0; + buffer_ = 0; + } + + void search(bool bit) + { + add_bit(bit); + + if (have_flag()) + { + start_hunt(); + } + } + + bool have_frame_error() + { + switch (buffer_ & 0xFF00) + { + case 0xFF00: + case 0xFE00: + case 0xFC00: + case 0x7F00: + case 0x7E00: + case 0x3F00: + return true; + default: + return false; + } + } + + bool have_bogon() + { + const uint16_t MASK = 0xFF00; + + if (bits_ != 8) return false; + + const uint16_t test = (buffer_ & MASK); + + switch (test) + { + case 0xFF00: + case 0xFE00: + case 0x7F00: + return true; + default: + return false; + } + } + + void start_frame() + { + state_ = FRAMING; + frame_->clear(); + ones_ = 0; + buffer_ &= 0xFF00; + } + + void hunt(bool bit) + { + const uint16_t MASK = 0xFF00; + + add_bit(bit); + buffer_ &= MASK; + + if (bits_ != 8) return; + + if (have_flag()) { + start_hunt(); + return; + } + + if (have_bogon()) { + start_search(); + return; + } + + if (not have_frame_error()) { + start_frame(); + return; + } + + start_search(); + } + + void frame(bool bit) + { + add_bit(bit); + + if (ones_ < 5) { + if (buffer_ & 0x80) ones_ += 1; + else ones_ = 0; + + if (bits_ == 16) { + if (frame_->push_back(get_char())) { + consume_byte(); + } else { + // Allocation error. + frame_->clear(); + start_search(); + } + } + + if (have_flag()) { + if (frame_->size() > 17) { + ready_ = true; + } + } + } else { + // 5 ones in a row means the next one should be 0 and be skipped. + + if ((buffer_ & 0x80) == 0) { + ones_ = 0; + consume_bit(); + return; + } else { + + // Framing error. Drop the frame. If there is a FLAG + // in the buffer, go into HUNT otherwise SEARCH. + if (frame_->size() > 17) { + ready_ = true; + return; + } + + if ((buffer_ >> (16 - bits_) & 0xFF) == 0x7E) { + // Cannot call start_hunt() here because we need + // to preserve buffer state. + bits_ -= 8; + state_ = HUNT; + frame_->clear(); + } else { + start_search(); + } + } + } + } + + bool frame_end() + { + uint16_t tmp = (buffer_ >> (16 - bits_)); + return (tmp & 0xFF) == FLAG; + } + + bool frame_abort() + { + uint16_t tmp = (buffer_ >> (16 - bits_)); + return (tmp & 0x7FFF) == 0x7FFF; + } + + void abort_frame() + { + bits_ = 8; + buffer_ &= 0xFF00; + frame_->clear(); + } + + bool ready() const + { + return ready_; + } +}; + +#if 0 + +Decoder::Decoder(bool pass_all) +: state_(SEARCH), ones_(0), buffer_(0), frame_(ioFramePool().acquire()) +, bits_(0), passall_(pass_all), ready_(false) +{} + +IoFrame* Decoder::operator()(bool bit, bool pll) +{ + IoFrame* result = nullptr; + + // It appears that the ioFramePool may not get initialized in the proper + // order during some builds. + if (nullptr == frame_) frame_ = ioFramePool().acquire(); + + if (not pll) { + if ((state_ == FRAMING) and (frame_->size() > 17) and passall_) { + frame_->parse_fcs(); + if (passall_ or frame_->ok()) { + result = frame_; + ready_ = false; + frame_ = ioFramePool().acquire(); + return result; + } + } + reset(); + } else { + if (process(bit)) { + ready_ = false; + frame_->parse_fcs(); + if (passall_ or frame_->ok()) { + result = frame_; + frame_ = ioFramePool().acquire(); + return result; + } + frame_->clear(); + } + } + + return result; +} +#endif + +}}} // mobilinkd::tnc::hdlc + +#endif // MOBILINKD___HDLC_DECODER_HPP_ diff --git a/TNC/HdlcFrame.cpp b/TNC/HdlcFrame.cpp new file mode 100644 index 0000000..6d411ac --- /dev/null +++ b/TNC/HdlcFrame.cpp @@ -0,0 +1,20 @@ +// Copyright 2015 Mobilinkd LLC +// All rights reserved. + +#include "HdlcFrame.hpp" + +namespace mobilinkd { namespace tnc { namespace hdlc { + +FrameSegmentPool frameSegmentPool __attribute__((section(".bss2"))); + +IoFramePool& ioFramePool() { + static IoFramePool pool; + return pool; +} + +void release(IoFrame* frame) +{ + ioFramePool().release(frame); +} + +}}} // mobilinkd::tnc::hdlc diff --git a/TNC/HdlcFrame.hpp b/TNC/HdlcFrame.hpp new file mode 100644 index 0000000..5d91b77 --- /dev/null +++ b/TNC/HdlcFrame.hpp @@ -0,0 +1,207 @@ +// Copyright 2015 Mobilinkd LLC +// All rights reserved. + + +#ifndef MOBILINKD__HDLC_FRAME_HPP_ +#define MOBILINKD__HDLC_FRAME_HPP_ + +#ifndef EXCLUDE_CRC +#include "stm32l4xx_hal.h" +extern CRC_HandleTypeDef hcrc; +#endif + +#include "cmsis_os.h" + +#include +#include "SegmentedBuffer.hpp" + +#include + +#include +#include + +namespace mobilinkd { namespace tnc { namespace hdlc { + + +using boost::intrusive::list_base_hook; +using boost::intrusive::list; +using boost::intrusive::constant_time_size; + +template +class Frame : public list_base_hook<> +{ +public: + typedef POOL pool_type; + typedef buffer::SegmentedBuffer data_type; + typedef typename data_type::iterator iterator; + + enum Type { + DATA, TX_DELAY, P_PERSIST, SLOT_TIME, TX_TAIL, DUPLEX, HARDWARE, + TEXT, LOG}; + + enum Source { + RF_DATA = 0x00, SERIAL_DATA = 0x10, DIGI_DATA = 0x20, + FRAME_RETURN = 0xF0}; + +private: + data_type data_; + int crc_{-1}; + int fcs_{-2}; + bool complete_{false}; + uint8_t frame_type_{Type::DATA}; + +#ifndef EXCLUDE_CRC + uint16_t compute_crc(iterator first) { + + uint16_t bytes = data_.size(); + uint16_t block_size = std::min(bytes, POOL::chunk_type::size()); + + uint32_t checksum = HAL_CRC_Calculate(&hcrc, (uint32_t*) &(*first), block_size); + + bytes -= block_size; + std::advance(first, block_size); + + while (bytes) { + block_size = std::min(bytes, POOL::chunk_type::size()); + checksum = HAL_CRC_Accumulate(&hcrc, (uint32_t*) &(*first), block_size); + bytes -= block_size; + std::advance(first, block_size); + } + + checksum ^= 0xFFFF; // Compliment + checksum <<= 16; // Shift + asm volatile("rbit %0, %0" : "+r" (checksum)); // Reverse + uint16_t result = checksum & 0xFFFF; + DEBUG("CRC = %hx", result); + return result; + } +#else + uint16_t compute_crc(iterator first, iterator last) {return 0;} +#endif + +public: + Frame() + : list_base_hook<>(), data_(), crc_(-1), fcs_(-2), complete_(false) + {} + + uint8_t type() const {return frame_type_ & 0x0F;} + void type(uint8_t t) {frame_type_ |= ((frame_type_ & 0xF0) | t);} + + uint8_t source() const {return frame_type_ & 0xF0;} + void source(uint8_t s) {frame_type_ |= ((frame_type_ & 0x0F) | s);} + + void clear() { + data_.clear(); + crc_ = -1; + fcs_ = -2; + complete_ = false; + frame_type_ = 0; + } + + void assign(data_type& data) { + data_.splice(data_.end(), data); + } + + uint16_t size() const {return data_.size();} + + uint16_t crc() const {return crc_;} + uint16_t fcs() const {return fcs_;} + + bool complete() const {return complete_;} + + bool ok() const {return crc_ == 0x0f47; /*0xf0b8;*/} + + typename data_type::iterator begin() { return data_.begin(); } + typename data_type::iterator end() { return data_.end(); } + + bool push_back(uint8_t value) + { + return data_.push_back(value); + } + + void add_fcs() { // TX frames have the checksums added. + fcs_ = compute_crc(data_.begin()); + data_.push_back(uint8_t(fcs_ & 0xFF)); + data_.push_back(uint8_t((fcs_ >> 8) & 0xFF)); + crc_ = 0x0f47; + complete_ = true; + } + + void parse_fcs() { // RX frames have the checksums parsed. + auto it = data_.begin(); + std::advance(it, data_.size() - 2); + fcs_ = (*it); + ++it; + fcs_ |= (*it) << 8; + DEBUG("FCS = %hx", fcs_); + crc_ = compute_crc(data_.begin()); + complete_ = true; + } +}; + +template +class FramePool +{ +public: + typedef Frame frame_type; + typedef list> FrameList; + +private: + static const uint16_t FRAME_COUNT = 16; + frame_type frames_[FRAME_COUNT]; + FrameList free_list_; + +public: + FramePool() + : frames_(), free_list_() + { + for (auto& frame : frames_) { + free_list_.push_back(frame); + } + } + + uint16_t size() const {return free_list_.size();} + + frame_type* acquire() { + frame_type* result = nullptr; + auto x = taskENTER_CRITICAL_FROM_ISR(); + if (!free_list_.empty()) { + result = &free_list_.front(); + free_list_.pop_front(); + } + taskEXIT_CRITICAL_FROM_ISR(x); + return result; + } + + void release(frame_type* frame) { + frame->clear(); + auto x = taskENTER_CRITICAL_FROM_ISR(); + free_list_.push_back(*frame); + taskEXIT_CRITICAL_FROM_ISR(x); + } +}; + +typedef buffer::Pool<48> FrameSegmentPool; // 12K buffer of frames; + +extern FrameSegmentPool frameSegmentPool; + +typedef Frame IoFrame; +typedef FramePool IoFramePool; + +IoFramePool& ioFramePool(void); + +/** + * This is needed to work around a major defect in the GCC 5.2.0 compiler + * 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. + * It's own stack usage is minimal even though it is making the same call. + * + * @param frame + */ +void release(IoFrame* frame); + +}}} // mobilinkd::tnc::hdlc + +#endif // MOBILINKD__HDLC_FRAME_HPP_ diff --git a/TNC/Hysteresis.hpp b/TNC/Hysteresis.hpp new file mode 100644 index 0000000..b104327 --- /dev/null +++ b/TNC/Hysteresis.hpp @@ -0,0 +1,110 @@ +#ifndef MOBILINKD__HYSTERESIS_H_ +#define MOBILINKD__HYSTERESIS_H_ + +#include + +namespace mobilinkd { namespace libafsk { + +template +struct BaseHysteresis { + + typedef T float_type; + float_type min_; + float_type max_; + + int low_; + int high_; + + float_type last_; + + BaseHysteresis(float_type minimum, float_type maximum, int low = 0, int high = 1) + : min_(minimum), max_(maximum), low_(low), high_(high), last_(0.0) + {} + + int operator()(float_type value) { + if (value <= min_) { + last_ = low_; + } else if (value >= max_) { + last_ = high_; + } + + return last_; + } +}; + +typedef BaseHysteresis Hysteresis; +typedef BaseHysteresis FastHysteresis; + +template +struct BlockHysteresis { + + typedef T* result_type; + + float min_; + float max_; + + T low_; + T high_; + + T last_; + T buffer_[N]; + + BlockHysteresis(float minimum, float maximum, T low = 0, T high = 1) + : min_(minimum), max_(maximum), low_(low), high_(high), last_(low) + , buffer_() + {} + + result_type operator()(float* value) { + + for (size_t i = 0; i != N; ++i) { + if (value[i] <= min_) { + last_ = low_; + } else if (value[i] >= max_) { + last_ = high_; + } + buffer_[i] = last_; + } + + return buffer_; + } +}; +/* +template +struct BlockHysteresis { + + typedef uint8_t* result_type; + + float min_; + float max_; + + bool low_; + bool high_; + + bool last_; + uint8_t buffer_[(N + 7) / 8]; + + BlockHysteresis(float minimum, float maximum, bool low = false, bool high = true) + : min_(minimum), max_(maximum), low_(low), high_(high), last_(low) + , buffer_() + {} + + result_type operator()(float* value) { + + for (size_t i = 0; i != N; ++i) { + if (value[i] <= min_) { + last_ = low_; + } else if (value[i] >= max_) { + last_ = high_; + } + buffer_[i >> 3] = (last_ << (i & 7)); + } + + return buffer_; + } +}; +*/ +}} // mobilinkd::libafsk + +#endif // MOBILINKD__HYSTERESIS_H_ + + diff --git a/TNC/IOEventTask.cpp b/TNC/IOEventTask.cpp new file mode 100644 index 0000000..a8edde7 --- /dev/null +++ b/TNC/IOEventTask.cpp @@ -0,0 +1,132 @@ +// Copyright 2017 Rob Riggs +// All rights reserved. + +#include +#include +#include "IOEventTask.h" +#include "PortInterface.h" +#include "PortInterface.hpp" +#include "main.h" +#include "AudioInput.hpp" +#include "HdlcFrame.hpp" +#include "Kiss.hpp" +#include "KissHardware.hpp" +#include "ModulatorTask.hpp" +#include "SerialPort.h" +#include "Led.h" + +#include "stm32l4xx_hal.h" +#include "cmsis_os.h" + +extern osMessageQId hdlcOutputQueueHandle; + +void startIOEventTask(void const*) +{ + 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; + } + } +} + +namespace mobilinkd { namespace tnc { + +void print_startup_banner() +{ + uint32_t* uid = (uint32_t*)0x1FFF7590; // STM32L4xx (same for 476 and 432) + + 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]); + + uint8_t* version_ptr = (uint8_t*)0x1FFF6FF2; + + int version = *version_ptr; + + INFO("Bootloader version: 0x%02X", version); +} + +}} // mobilinkd::tnc diff --git a/TNC/IOEventTask.h b/TNC/IOEventTask.h new file mode 100644 index 0000000..ea89bd9 --- /dev/null +++ b/TNC/IOEventTask.h @@ -0,0 +1,32 @@ +// Copyright 2017 Rob Riggs +// All rights reserved. + +#ifndef MOBILINKD__TNC__IO_EVENT_TASK_H_ +#define MOBILINKD__TNC__IO_EVENT_TASK_H_ + +#include "cmsis_os.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void startIOEventTask(void const* argument); +void startCdcBlinker(void const* argument); + +#ifdef __cplusplus +} + +extern osMessageQId ioEventQueueHandle; + +namespace mobilinkd { namespace tnc { + +void print_startup_banner() __attribute__((noinline)); +void start_cdc_blink(); +void stop_cdc_blink(); + +}} // mobilinkd::tnc + +#endif + + +#endif /* MOBILINKD__TNC__IO_EVENT_TASK_H_ */ diff --git a/TNC/IirFilter.hpp b/TNC/IirFilter.hpp new file mode 100644 index 0000000..267ea20 --- /dev/null +++ b/TNC/IirFilter.hpp @@ -0,0 +1,93 @@ +// Copyright 2015 Rob Riggs +// All rights reserved. + +#ifndef MOBILINKD__TNC__IIR_FILTER_H_ +#define MOBILINKD__TNC__IIR_FILTER_H_ + +#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]; + size_t size_; + + IirFilter(const float* b, const float* 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); + } + + ~IirFilter() {} + + float_type operator()(float_type input) + { + + for(size_t i = size_ - 1; i != 0; i--) history_[i] = history_[i - 1]; + + history_[0] = input; + + for (size_t i = 1; i != size_; i++) { + history_[0] -= denominator_[i] * history_[i]; + } + + float_type result = 0; + for (size_t i = 0; i != size_; i++) { + result += numerator_[i] * history_[i]; + } + + return result; + } +}; + +}} // mobilinkd::tnc + +#endif // MOBILINKD__TNC__IIR_FILTER_H_ + + diff --git a/TNC/Kiss.cpp b/TNC/Kiss.cpp new file mode 100644 index 0000000..72c84cc --- /dev/null +++ b/TNC/Kiss.cpp @@ -0,0 +1,71 @@ +// Copyright 2016 Rob Riggs +// All rights reserved. + +#include +#include "Kiss.hpp" +#include "KissHardware.hpp" + +// extern osMessageQId hdlcOutputQueueHandle; + +namespace mobilinkd { namespace tnc { namespace kiss { + +void handle_frame(uint8_t frame_type, hdlc::IoFrame* frame) { + + if (frame->size() == 0) { + hdlc::release(frame); + return; + } + + uint8_t value = *(frame->begin()); + switch (frame_type) { + case kiss::FRAME_DATA: + DEBUG("FRAME_DATA"); + // osMessagePut(hdlcOutputQueueHandle, (uint32_t) frame, 0); + break; + case kiss::FRAME_TX_DELAY: + DEBUG("FRAME_TX_DELAY"); + kiss::settings().txdelay = value; + hdlc::release(frame); + break; + case kiss::FRAME_P_PERSIST: + DEBUG("FRAME_P_PERSIST"); + kiss::settings().ppersist = value; + hdlc::release(frame); + break; + case kiss::FRAME_SLOT_TIME: + DEBUG("FRAME_SLOT_TIME"); + kiss::settings().slot = value; + hdlc::release(frame); + break; + case kiss::FRAME_TX_TAIL: + DEBUG("FRAME_TX_TAIL"); + // Ignore. + hdlc::release(frame); + break; + case kiss::FRAME_DUPLEX: + DEBUG("FRAME_DUPLEX"); + kiss::settings().duplex = value; + hdlc::release(frame); + break; + case kiss::FRAME_HARDWARE: + DEBUG("FRAME_HARDWARE"); + kiss::settings().handle_request(frame); + hdlc::release(frame); + break; + case kiss::FRAME_LOG: + DEBUG("FRAME_LOG"); + hdlc::release(frame); + // IGNORE + break; + case kiss::FRAME_RETURN: + DEBUG("FRAME_RETURN"); + hdlc::release(frame); + // Leave KISS mode + break; + default: + DEBUG("Unknown KISS frame type"); + hdlc::release(frame); + } +} + +}}} // mobilinkd::tnc::kiss diff --git a/TNC/Kiss.hpp b/TNC/Kiss.hpp new file mode 100644 index 0000000..48f6afa --- /dev/null +++ b/TNC/Kiss.hpp @@ -0,0 +1,259 @@ +// Copyright 2015 Mobilinkd LLC +// All rights reserved. + +#ifndef MOBILINKD__KISS_HPP_ +#define MOBILINKD__KISS_HPP_ + +#include +#include +#include + +#include "HdlcFrame.hpp" + +namespace mobilinkd { namespace tnc { namespace kiss { + +const uint8_t FRAME_DATA = 0x00; +const uint8_t FRAME_TX_DELAY = 0x01; +const uint8_t FRAME_P_PERSIST = 0x02; +const uint8_t FRAME_SLOT_TIME = 0x03; +const uint8_t FRAME_TX_TAIL = 0x04; +const uint8_t FRAME_DUPLEX = 0x05; +const uint8_t FRAME_HARDWARE = 0x06; +const uint8_t FRAME_LOG = 0x07; +const uint8_t FRAME_RETURN = 0xFF; + +void handle_frame(uint8_t frame_type, hdlc::IoFrame* frame) __attribute__((optimize("-Os"))); +// void handle_frame(uint8_t frame_type, hdlc::IoFrame* frame); + +struct slip_encoder +{ + typedef std::forward_iterator_tag iterator_category; + typedef size_t difference_type; + typedef char value_type; + typedef char& reference; + typedef char* pointer; + + static const char FEND = 0xC0; + static const char FESC = 0xDB; + static const char TFEND = 0xDC; + static const char TFESC = 0xDD; + + const char* packet_; + size_t size_; + size_t pos_; + char current_; + bool shifting_; + + slip_encoder() + : packet_(0), size_(0), pos_(0), current_(0), shifting_(false) + { + set_current(); + } + + slip_encoder(const char* packet, size_t len) + : packet_(packet), size_(len), pos_(0), current_(0), shifting_(false) + { + set_current(); + } + + void set_current() { + if ((packet_[pos_] == FEND) or (packet_[pos_] == FESC)) { + current_ = FESC; + shifting_ = true; + } else { + current_ = packet_[pos_]; + } + } + + char operator*() const { + return current_; + } + + slip_encoder& operator++() { + + if (!size_) return *this; + + if (shifting_) { + shifting_ = false; + current_ = (packet_[pos_] == FEND ? TFEND : TFESC); + return *this; + } + pos_ += 1; + + if (pos_ != size_) { + set_current(); + } else { + packet_ = 0; + pos_ = 0; + size_ = 0; + } + + return *this; + } + + slip_encoder operator++(int) { + slip_encoder tmp(*this); + ++(*this); + return tmp; + } + + bool operator==(const slip_encoder& other) const { + return (packet_ == other.packet_) and + (pos_ == other.pos_) and (size_ == other.size_); + } + + bool operator!=(const slip_encoder& other) const { + return not ((*this) == other); + } +}; + +struct slip_encoder2 +{ + typedef std::forward_iterator_tag iterator_category; + typedef size_t difference_type; + typedef char value_type; + typedef char& reference; + typedef char* pointer; + + static const uint8_t FEND = 0xC0; + static const uint8_t FESC = 0xDB; + static const uint8_t TFEND = 0xDC; + static const uint8_t TFESC = 0xDD; + + hdlc::IoFrame::iterator iter_; + mutable char current_; + mutable bool shifting_; + + slip_encoder2() + : iter_(), current_(0), shifting_(false) + {} + + slip_encoder2(hdlc::IoFrame::iterator iter) + : iter_(iter), current_(0), shifting_(false) + {} + + slip_encoder2(const slip_encoder2& other) + : iter_(other.iter_), current_(other.current_), shifting_(other.shifting_) + {} + + void set_current() const { + uint8_t c = *iter_; + if ((c == FEND) or (c == FESC)) { + current_ = FESC; + shifting_ = true; + } else { + current_ = c; + } + } + + char operator*() const { + set_current(); + return current_; + } + + slip_encoder2& operator++() { + + if (shifting_) { + shifting_ = false; + current_ = (*iter_ == FEND ? TFEND : TFESC); + return *this; + } + ++iter_; + + return *this; + } + + slip_encoder2 operator++(int) { + slip_encoder2 tmp(*this); + ++(*this); + return tmp; + } + + bool operator==(const slip_encoder2& other) const { + return iter_ == other.iter_; + } + + bool operator!=(const slip_encoder2& other) const { + return iter_ != other.iter_; + } +}; + +struct slip_decoder +{ + typedef std::forward_iterator_tag iterator_category; + typedef size_t difference_type; + typedef char value_type; + typedef char& reference; + typedef char* pointer; + + static const char FEND = 0xC0; + static const char FESC = 0xDB; + static const char TFEND = 0xDC; + static const char TFESC = 0xDD; + + const char* packet_; + size_t size_; + size_t pos_; + char current_; + + slip_decoder() + : packet_(0), size_(0), pos_(0), current_(0) + { + set_current(); + } + + slip_decoder(const char* packet, size_t len) + : packet_(packet), size_(len), pos_(0), current_(0) + { + set_current(); + } + + void set_current() { + if (packet_[pos_] == FESC) { + pos_ += 1; + current_ = (packet_[pos_] == TFEND ? FEND : FESC); + } else { + current_ = packet_[pos_]; + } + } + + char operator*() const { + return current_; + } + + slip_decoder& operator++() { + + if (!size_) return *this; + + pos_ += 1; + + if (pos_ != size_) { + set_current(); + } else { + packet_ = 0; + pos_ = 0; + size_ = 0; + } + + return *this; + } + + slip_decoder operator++(int) { + slip_decoder tmp(*this); + ++(*this); + return tmp; + } + + bool operator==(const slip_decoder& other) const { + return (packet_ == other.packet_) and + (pos_ == other.pos_) and (size_ == other.size_); + } + + bool operator!=(const slip_decoder& other) const { + return not ((*this) == other); + } +}; + +}}} // mobilinkd::tnc::kiss + +#endif // MOBILINKD_KISS_HPP_ diff --git a/TNC/KissHardware.cpp b/TNC/KissHardware.cpp new file mode 100644 index 0000000..36f1692 --- /dev/null +++ b/TNC/KissHardware.cpp @@ -0,0 +1,516 @@ +// Copyright 2015 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 + +extern I2C_HandleTypeDef hi2c3; + +namespace mobilinkd { namespace tnc { namespace kiss { + +const char FIRMWARE_VERSION[] = "0.8.4"; +const char HARDWARE_VERSION[] = "Mobilinkd Nucleo32 Breadboard TNC"; + +Hardware& settings() +{ + static Hardware instance __attribute__((section(".bss3"))); + return instance; +} + +void Hardware::set_txdelay(uint8_t value) { + txdelay = value; + update_crc(); +} +void Hardware::set_ppersist(uint8_t value) { + ppersist = value; + update_crc(); +} +void Hardware::set_slottime(uint8_t value) { + slot = value; + update_crc(); +} +void Hardware::set_txtail(uint8_t value) { + txtail = value; + update_crc(); +} +void Hardware::set_duplex(uint8_t value) { + duplex = value; + update_crc(); +} + +#if 1 + + void reply8(uint8_t cmd, uint8_t result) { + uint8_t data[2] { cmd, result }; + ioport->write(data, 2, 6, osWaitForever); +} + +void reply16(uint8_t cmd, uint16_t result) { + uint8_t data[3] { cmd, uint8_t((result >> 8) & 0xFF), uint8_t(result & 0xFF) }; + ioport->write(data, 3, 6, osWaitForever); +} + +inline void reply(uint8_t cmd, const uint8_t* data, uint16_t len) { + auto buffer = (uint8_t*) malloc(len + 1); + if (buffer == nullptr) return; + buffer[0] = cmd; + for (uint16_t i = 0; i != len and data[i] != 0; 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; + buffer[0] = ext; + buffer[1] = cmd; + for (uint16_t i = 0; i != len and data[i] != 0; 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]; + if (alias >= NUMBER_OF_ALIASES or not aliases[alias].set) return; + result[0] = alias; + memcpy(result + 1, aliases[alias].call, CALLSIGN_LEN); + result[9] = aliases[alias].set; + result[10] = aliases[alias].use; + result[11] = aliases[alias].insert_id; + result[12] = aliases[alias].preempt; + result[13] = aliases[alias].hops; + reply_ext(hardware::EXTENDED_CMD, hardware::EXT_GET_ALIASES, result, 14); +} + +void Hardware::set_alias(const hdlc::IoFrame* frame) { + UNUSED(frame); +} + +void Hardware::handle_request(hdlc::IoFrame* frame) { + + static AFSKTestTone testTone; + + auto it = frame->begin(); + uint8_t command = *it++; + uint8_t v = *it; + + switch (command) { + case hardware::SEND_MARK: + case hardware::SEND_SPACE: + case hardware::SEND_BOTH: + case hardware::SET_OUTPUT_GAIN: + case hardware::SET_OUTPUT_OFFSET: + case hardware::SET_OUTPUT_TWIST: + break; + default: + testTone.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; + 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); + break; + case hardware::GET_BATTERY_LEVEL: + DEBUG("GET_BATTERY_LEVEL"); + osMessagePut(audioInputQueueHandle, audio::POLL_BATTERY_LEVEL, + osWaitForever); + osMessagePut(audioInputQueueHandle, audio::DEMODULATOR, + osWaitForever); + break; + case hardware::SEND_MARK: + DEBUG("SEND_MARK"); + osMessagePut(audioInputQueueHandle, audio::IDLE, + osWaitForever); + testTone.mark(); + break; + case hardware::SEND_SPACE: + DEBUG("SEND_SPACE"); + osMessagePut(audioInputQueueHandle, audio::IDLE, + osWaitForever); + testTone.space(); + break; + case hardware::SEND_BOTH: + DEBUG("SEND_BOTH"); + osMessagePut(audioInputQueueHandle, audio::IDLE, + osWaitForever); + testTone.both(); + break; + case hardware::STOP_TX: + DEBUG("STOP_TX"); + testTone.stop(); + osMessagePut(audioInputQueueHandle, audio::IDLE, + osWaitForever); + break; + case hardware::RESET: + DEBUG("RESET"); + osMessagePut(audioInputQueueHandle, audio::DEMODULATOR, + osWaitForever); + break; + case hardware::SET_OUTPUT_GAIN: + DEBUG("SET_OUTPUT_VOLUME"); + output_gain = v; + audio::setAudioOutputLevel(); + update_crc(); + [[fallthrough]]; + case hardware::GET_OUTPUT_GAIN: + DEBUG("GET_OUTPUT_VOLUME"); + reply8(hardware::GET_OUTPUT_GAIN, output_gain); + break; + + case hardware::STREAM_DCD_VALUE: + DEBUG("STREAM_DCD_VALUE"); + break; + + case hardware::POLL_INPUT_TWIST: + DEBUG("POLL_INPUT_TWIST"); + osMessagePut(audioInputQueueHandle, audio::POLL_TWIST_LEVEL, + osWaitForever); + osMessagePut(audioInputQueueHandle, audio::DEMODULATOR, + osWaitForever); + break; + + case hardware::STREAM_AVG_INPUT_TWIST: + DEBUG("STREAM_AVG_INPUT_TWIST"); + osMessagePut(audioInputQueueHandle, audio::STREAM_AVERAGE_TWIST_LEVEL, + osWaitForever); + break; + + case hardware::STREAM_INPUT_TWIST: + DEBUG("STREAM_INPUT_TWIST"); + osMessagePut(audioInputQueueHandle, audio::STREAM_INSTANT_TWIST_LEVEL, + osWaitForever); + break; + + case hardware::SET_VERBOSITY: + DEBUG("SET_VERBOSITY"); + log_level = *it ? Log::Level::debug : Log::Level::warn; + Log().setLevel(*it ? Log::Level::debug : Log::Level::warn); + update_crc(); + [[fallthrough]]; + case hardware::GET_VERBOSITY: + 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); + 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, + osWaitForever); + [[fallthrough]]; + case hardware::GET_INPUT_TWIST: + DEBUG("GET_INPUT_TWIST"); + reply8(hardware::GET_INPUT_TWIST, rx_twist); + break; + + case hardware::SET_OUTPUT_TWIST: + tx_twist = *it; + if (tx_twist < 0) tx_twist = 0; + if (tx_twist > 100) tx_twist = 100; + DEBUG("SET_OUTPUT_TWIST: %d", int(tx_twist)); + getModulator().set_twist(uint8_t(tx_twist)); + update_crc(); + [[fallthrough]]; + case hardware::GET_OUTPUT_TWIST: + DEBUG("GET_OUTPUT_TWIST"); + reply8(hardware::GET_OUTPUT_TWIST, tx_twist); + break; + + case hardware::STREAM_AMPLIFIED_INPUT: + DEBUG("STREAM_AMPLIFIED_INPUT"); + osMessagePut(audioInputQueueHandle, audio::STREAM_AMPLIFIED_INPUT_LEVEL, + osWaitForever); + break; + + case hardware::GET_TXDELAY: + DEBUG("GET_TXDELAY"); + reply8(hardware::GET_TXDELAY, txdelay); + break; + case hardware::GET_PERSIST: + DEBUG("GET_PERSIST"); + reply8(hardware::GET_PERSIST, ppersist); + break; + case hardware::GET_TIMESLOT: + DEBUG("GET_TIMESLOT"); + reply8(hardware::GET_TIMESLOT, slot); + break; + case hardware::GET_TXTAIL: + DEBUG("GET_TXTAIL"); + reply8(hardware::GET_TXTAIL, txtail); + break; + case hardware::GET_DUPLEX: + DEBUG("GET_DUPLEX"); + reply8(hardware::GET_DUPLEX, duplex); + break; + + case hardware::GET_FIRMWARE_VERSION: + DEBUG("GET_FIRMWARE_VERSION"); + reply(hardware::GET_FIRMWARE_VERSION, (uint8_t*) FIRMWARE_VERSION, + sizeof(FIRMWARE_VERSION) - 1); + break; + case hardware::GET_HARDWARE_VERSION: + DEBUG("GET_HARDWARE_VERSION"); + reply(hardware::GET_HARDWARE_VERSION, (uint8_t*) HARDWARE_VERSION, + sizeof(HARDWARE_VERSION) - 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); + } else { + options |= KISS_OPTION_PTT_SIMPLEX; + osMessagePut(ioEventQueueHandle, CMD_SET_PTT_SIMPLEX, osWaitForever); + } + update_crc(); + break; + case hardware::GET_PTT_CHANNEL: + DEBUG("GET_PTT_CHANNEL"); + reply8(hardware::GET_PTT_CHANNEL, + options & KISS_OPTION_PTT_SIMPLEX ? 0 : 1); + break; + + case hardware::SET_USB_POWER_OFF: + DEBUG("SET_USB_POWER_OFF"); + if (*it) { + options |= KISS_OPTION_VIN_POWER_OFF; + } else { + options &= ~KISS_OPTION_VIN_POWER_OFF; + } + update_crc(); + [[fallthrough]]; + case hardware::GET_USB_POWER_OFF: + DEBUG("GET_USB_POWER_OFF"); + reply8(hardware::GET_USB_POWER_OFF, + options & KISS_OPTION_VIN_POWER_OFF ? 1 : 0); + break; + + case hardware::SET_USB_POWER_ON: + DEBUG("SET_USB_POWER_ON"); + if (*it) { + options |= KISS_OPTION_VIN_POWER_ON; + } else { + options &= ~KISS_OPTION_VIN_POWER_ON; + } + update_crc(); + [[fallthrough]]; + case hardware::GET_USB_POWER_ON: + DEBUG("GET_USB_POWER_ON"); + reply8(hardware::GET_USB_POWER_ON, + options & KISS_OPTION_VIN_POWER_ON ? 1 : 0); + break; + + case hardware::GET_CAPABILITIES: + DEBUG("GET_CAPABILITIES"); + reply16(hardware::GET_CAPABILITIES, 0); + break; + + case hardware::GET_ALL_VALUES: + DEBUG("GET_ALL_VALUES"); + osMessagePut(audioInputQueueHandle, audio::POLL_BATTERY_LEVEL, + osWaitForever); + osMessagePut(audioInputQueueHandle, audio::POLL_TWIST_LEVEL, + osWaitForever); + osMessagePut(audioInputQueueHandle, audio::IDLE, + osWaitForever); + reply(hardware::GET_FIRMWARE_VERSION, (uint8_t*) FIRMWARE_VERSION, + 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); + reply8(hardware::GET_OUTPUT_TWIST, tx_twist); + 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); + reply8(hardware::GET_TXTAIL, txtail); + reply8(hardware::GET_DUPLEX, duplex); + reply8(hardware::GET_PTT_CHANNEL, + options & KISS_OPTION_PTT_SIMPLEX ? 0 : 1); + reply16(hardware::GET_CAPABILITIES, 0); + + break; + case hardware::EXTENDED_CMD: + handle_ext_request(frame); + break; + default: + ERROR("Unknown hardware request"); + } +} + +inline void ext_reply(uint8_t cmd, uint8_t result) { + uint8_t data[3] { hardware::EXTENDED_CMD, cmd, result }; + ioport->write(data, 3, 6); +} + +void Hardware::handle_ext_request(hdlc::IoFrame* frame) { + auto it = frame->begin(); + ++it; + uint8_t ext_command = *it++; + + switch (ext_command) { + case hardware::EXT_GET_MODEM_TYPE: + DEBUG("EXT_GET_MODEM_TYPE"); + ext_reply(hardware::EXT_GET_MODEM_TYPE, 1); + break; + case hardware::EXT_SET_MODEM_TYPE: + DEBUG("EXT_SET_MODEM_TYPE"); + ext_reply(hardware::EXT_OK, hardware::EXT_SET_MODEM_TYPE); + break; + case hardware::EXT_GET_MODEM_TYPES: + DEBUG("EXT_GET_MODEM_TYPES"); + ext_reply(hardware::EXT_GET_MODEM_TYPES, 1); + break; + } +} + +bool Hardware::load() +{ + + INFO("Loading settings from EEPROM"); + + auto tmp = std::make_unique(); + + if (!tmp) return false; + + memset(tmp.get(), 0, sizeof(Hardware)); + + if (!I2C_Storage::load(*tmp)) return false; + + if (tmp->crc_ok()) + { + memcpy(this, tmp.get(), sizeof(Hardware)); + + return true; + } + ERROR("EEPROM CRC error"); + return false; +} + +bool Hardware::store() const +{ + INFO("Saving settings to EEPROM"); + + I2C_Storage::store(*this); + + INFO("EEPROM saved checksum is: %04x (crc = %04x)", checksum, crc()); + + return true; +} + + +bool I2C_Storage::load(void* ptr, size_t len) +{ + uint32_t timeout = 1000; + auto start = osKernelSysTick(); + + 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(); + + while (HAL_I2C_GetState(&hi2c3) != HAL_I2C_STATE_READY) + { + if (osKernelSysTick() > start + timeout) return false; + osThreadYield(); + } + + return true; +} + +bool I2C_Storage::store(const void* ptr, size_t len) +{ + 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 << 8, index, I2C_MEMADD_SIZE_16BIT, tmp + index, page_size); + if (result == HAL_BUSY) + { + if (osKernelSysTick() > start + timeout) return false; + osThreadYield(); + continue; + } + 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; + } + 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(); + } + } + + return true; +} + +}}} // mobilinkd::tnc::kiss + diff --git a/TNC/KissHardware.hpp b/TNC/KissHardware.hpp new file mode 100644 index 0000000..53fb513 --- /dev/null +++ b/TNC/KissHardware.hpp @@ -0,0 +1,355 @@ +// Copyright 2015 Mobilinkd LLC +// All rights reserved. + +#ifndef MOBILINKD__TNC__KISS_HARDWARE_HPP_ +#define MOBILINKD__TNC__KISS_HARDWARE_HPP_ + +#include +#include "HdlcFrame.hpp" + +#include +#include +#include + +extern "C" void updatePtt(void); + +namespace mobilinkd { namespace tnc { namespace kiss { + +extern const char FIRMWARE_VERSION[]; +extern const char HARDWARE_VERSION[]; + +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; + +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; + +const uint8_t SET_VERBOSITY = 16; +const uint8_t GET_VERBOSITY = 17; + +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; + +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; + +const uint8_t OK = 32; // Acknowledge SET commands. + +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; + +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; + +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 + +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; + +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) + +const uint8_t GET_CAPABILITIES = 126; ///< Send all capabilities. +const uint8_t GET_ALL_VALUES = 127; ///< Send all settings & versions. + +/** + * Extended commands are two+ bytes in length. They start at 80:00 + * and go through BF:FF (14 significant bits), then proceed to C0:00:00 + * through CF:FF:FF (20 more significant bits). + * + * 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; + +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 + +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) + +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) + +const uint8_t MODEM_TYPE_1200 = 1; +const uint8_t MODEM_TYPE_300 = 2; +const uint8_t MODEM_TYPE_9600 = 3; +const uint8_t MODEM_TYPE_PSK31 = 4; + +// Boolean options. +#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_PTT_SIMPLEX 0x10 // Simplex PTT (the default) + +const char TOCALL[] = "APML50"; // Update for every feature change. + +} // hardware + +const size_t CALLSIGN_LEN = 8; + +struct Alias { + uint8_t call[CALLSIGN_LEN]; ///< Callsign. Pad unused with NUL. + bool set; ///< Alias is configured. + bool use; ///< Use this alias. + bool insert_id; ///< Tracing. + bool preempt; ///< Allow out of order pathing. + uint8_t hops; +}; // size = 10 + +const size_t BEACON_PATH_LEN = 30; +const size_t BEACON_TEXT_LEN = 128; + +struct Beacon { + uint8_t dest[CALLSIGN_LEN]; ///< callsign. Pad unused with NUL. + uint8_t path[BEACON_PATH_LEN + 1]; ///< NUL terminated string. + uint8_t text[BEACON_TEXT_LEN + 1]; ///< NUL terminated string. + uint16_t seconds; ///< Number of seconds between beacons. +}; // size = 170 + +const size_t NUMBER_OF_ALIASES = 8; // 80 bytes +const size_t NUMBER_OF_BEACONS = 4; // 680 bytes + +/** + * Values from the KISS settings (including hardware settings) which are + * stored in EEPROM. + */ +struct Hardware +{ + enum ModemType { + AFSK1200 = 1, + AFSK300, + FSK9600, + PSK31 + }; + + uint8_t txdelay; ///< How long in 10mS units to wait for TX to settle before starting data + uint8_t ppersist; ///< Likelihood of taking the channel when its not busy + uint8_t slot; ///< How long in 10mS units to wait between sampling the channel to see if free + uint8_t txtail; ///< How long in 10mS units to wait after the data before keying off the transmitter + uint8_t duplex; ///< Ignore current channel activity - just key up + uint8_t modem_type; ///< Modem type. + uint16_t output_gain; ///< output volume (0-256). + uint16_t input_gain; ///< input volume (0-256). + int8_t tx_twist; ///< 0 to 100 (50 = even). + int8_t rx_twist; ///< 0, 3, 6 dB + uint8_t log_level; ///< Log level (0 - 4 : debug - severe). + + uint16_t options; ///< boolean options + + /// Callsign. Pad unused with NUL. + uint8_t mycall[CALLSIGN_LEN]; + + uint8_t dedupe_seconds; ///< number of seconds to dedupe packets. + Alias aliases[NUMBER_OF_ALIASES]; ///< Digipeater aliases + Beacon beacons[NUMBER_OF_BEACONS]; ///< Beacons + uint16_t checksum; ///< Validity check of param data (CRC16) + + uint16_t crc() const { + + uint32_t crc = HAL_CRC_Calculate( + &hcrc, (uint32_t*) this, sizeof(Hardware) - 2); + + return crc & 0xFFFF; + } + + void update_crc() { + checksum = crc(); + INFO("EEPROM checksum = %04xs", checksum); + } + + bool crc_ok() const { + return crc() == checksum; + } + + /** + * Configure hardware settings. Load up the defaults. Call load() to + * load values from EEPROM and save() to store the settings in EEPROM. + * + */ + 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; + txtail = 1; + duplex = 0; + modem_type = ModemType::AFSK1200; + output_gain = 63; + input_gain = 0; + tx_twist = 50; + rx_twist = 0; + log_level = Log::Level::debug; + + options = KISS_OPTION_PTT_SIMPLEX; + + /// Callsign. Pad unused with NUL. + strcpy((char*)mycall, "MYCALL"); + + dedupe_seconds = 30; + memset(aliases, 0, sizeof(aliases)); + memset(beacons, 0, sizeof(beacons)); + update_crc(); + + updatePtt(); + + debug(); + + DEBUG("Settings initialized"); + } + + void debug() { + DEBUG("Hardware Settings (size=%d):", sizeof(Hardware)); + DEBUG("TX Delay: %d", (int)txdelay); + DEBUG("P* Persistence: %d", (int)ppersist); + DEBUG("Slot Time: %d", (int)slot); + DEBUG("TX Tail: %d", (int)txtail); + DEBUG("Duplex: %d", (int)duplex); + DEBUG("Modem Type: %d", (int)modem_type); + DEBUG("TX Gain: %d", (int)output_gain); + DEBUG("RX Gain: %d", (int)input_gain); + DEBUG("TX Twist: %d", (int)tx_twist); + DEBUG("RX Twist: %d", (int)rx_twist); + DEBUG("Log Level: %d", (int)log_level); + DEBUG("Options: %d", (int)options); + DEBUG("MYCALL: %s", (char*) mycall); + DEBUG("Dedupe time (secs): %d", (int)dedupe_seconds); + DEBUG("Aliases:"); + for (auto& a : aliases) { + if (!a.set) continue; + DEBUG(" call: %s", (char*)a.call); + DEBUG(" use: %d", (int)a.use); + DEBUG(" insert: %d", (int)a.insert_id); + DEBUG(" preempt: %d", (int)a.preempt); + DEBUG(" hops: %d", (int)a.hops); + } + DEBUG("Beacons:"); + for (auto& b : this->beacons) { + if (b.seconds == 0) continue; + DEBUG(" dest: %s", (char*)b.dest); + DEBUG(" path: %s", (char*)b.path); + DEBUG(" text: %s", (char*)b.text); + DEBUG(" frequency (secs): %d", (int)b.seconds); + } + DEBUG("Checksum: %04xs", checksum); + } + +#if 1 + bool load(); + + bool store() const; +#endif + + void set_txdelay(uint8_t value); + void set_ppersist(uint8_t value); + void set_slottime(uint8_t value); + void set_txtail(uint8_t value); + void set_duplex(uint8_t value); + + void handle_request(hdlc::IoFrame* frame) __attribute__((optimize("-O2"))); + void handle_ext_request(hdlc::IoFrame* frame); + + void get_aliases(); + void get_alias(uint8_t alias); + void set_alias(const hdlc::IoFrame* frame); + +}; // 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}; + + static bool load(void* ptr, size_t len); + + template + static bool load(T& t) { + return load(&t, sizeof(T)); + } + + static bool store(const void* ptr, size_t len); + + template + static bool store(const T& t) { + return store(&t, sizeof(T)); + } +}; + +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_ diff --git a/TNC/KissTask.cpp b/TNC/KissTask.cpp new file mode 100644 index 0000000..32bb5c8 --- /dev/null +++ b/TNC/KissTask.cpp @@ -0,0 +1,2 @@ +// Copyright 2015 Mobilinkd LLC +// All rights reserved. diff --git a/TNC/Led.cpp b/TNC/Led.cpp new file mode 100644 index 0000000..92f7ad2 --- /dev/null +++ b/TNC/Led.cpp @@ -0,0 +1,85 @@ +// Copyright 2017 Rob Riggs +// All rights reserved. + +#include "Led.h" +#include "GPIO.hpp" + +void led_tx_on() +{ + mobilinkd::tnc::getTxLed().on(); +} + +void led_tx_off() +{ + mobilinkd::tnc::getTxLed().off(); +} + +void led_tx_toggle() +{ + mobilinkd::tnc::gpio::LED_TX::toggle(); +} + +int led_tx_status() +{ + return mobilinkd::tnc::getTxLed().status(); +} + +void led_dcd_on() +{ + mobilinkd::tnc::getDcdLed().on(); +} + +void led_dcd_off() +{ + mobilinkd::tnc::getDcdLed().off(); +} + +int led_dcd_status() +{ + return mobilinkd::tnc::getDcdLed().status(); +} + +void led_other_on() +{ + mobilinkd::tnc::getOtherLed().on(); +} + +void led_other_off() +{ + mobilinkd::tnc::getOtherLed().off(); +} + +void led_other_toggle() +{ + mobilinkd::tnc::getOtherLed().toggle(); +} + +int led_other_status() +{ + return mobilinkd::tnc::getOtherLed().status(); +} + +namespace mobilinkd { namespace tnc { + +Led& getTxLed() +{ + static Led led(LED_RED_GPIO_Port, LED_RED_Pin); + return led; +} + +Led& getDcdLed() +{ + static Led led(LED_GREEN_GPIO_Port, LED_GREEN_Pin); + return led; +} + +Led& getOtherLed() +{ + static Led led(LED_YELLOW_GPIO_Port, LED_YELLOW_Pin); + return led; +} + +}} // mobilinkd::tnc + + + diff --git a/TNC/Led.h b/TNC/Led.h new file mode 100644 index 0000000..8117f0e --- /dev/null +++ b/TNC/Led.h @@ -0,0 +1,79 @@ +// Copyright 2017 Rob Riggs +// All rights reserved. + +#ifndef MOBILINKD__TNC__LED_H_ +#define MOBILINKD__TNC__LED_H_ + +#include "stm32l4xx_hal.h" +#include "cmsis_os.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void led_tx_on(void); +void led_tx_off(void); +void led_tx_toggle(void); +int led_tx_status(void); + +void led_dcd_on(void); +void led_dcd_off(void); +int led_dcd_status(void); + +void led_other_on(void); +void led_other_off(void); +void led_other_toggle(void); +int led_other_status(void); + +#ifdef __cplusplus +} + +namespace mobilinkd { namespace tnc { + +struct Led +{ + GPIO_TypeDef* GPIOx_; + uint16_t pin_; + uint32_t count_; + + Led(GPIO_TypeDef* GPIOx, uint16_t pin) + : GPIOx_(GPIOx), pin_(pin), count_(0) + {} + + void on() { + taskENTER_CRITICAL(); + ++count_; + if (count_ == 1) { + HAL_GPIO_WritePin(GPIOx_, pin_, GPIO_PIN_RESET); + } + taskEXIT_CRITICAL(); + } + + void off() { + taskENTER_CRITICAL(); + if (count_ > 0) --count_; + if (count_ == 0) { + HAL_GPIO_WritePin(GPIOx_, pin_, GPIO_PIN_SET); + } + taskEXIT_CRITICAL(); + } + + void toggle() { + taskENTER_CRITICAL(); + HAL_GPIO_TogglePin(GPIOx_, pin_); + count_ = 0; + taskEXIT_CRITICAL(); + } + + uint32_t status() const {return count_;} +}; + +Led& getTxLed(); +Led& getDcdLed(); +Led& getOtherLed(); + +}} // mobilinkd::tnc + +#endif //__cplusplus + +#endif /* MOBILINKD__TNC__LED_H_ */ diff --git a/TNC/Log.cpp b/TNC/Log.cpp new file mode 100644 index 0000000..52a17ba --- /dev/null +++ b/TNC/Log.cpp @@ -0,0 +1,68 @@ +// Copyright 2015 Rob RIggs +// All rights reserved. + +#include +#include "PortInterface.hpp" + +void log_(int level, const char* fmt, ...) +{ + if (level < mobilinkd::tnc::log().level_) return; + if (!mobilinkd::tnc::ioport) return; + + va_list args; + va_start(args, fmt); + char* buffer = 0; + int len = vasiprintf(&buffer, fmt, args); + va_end(args); + + if (len >= 0) { + mobilinkd::tnc::ioport->write((uint8_t*)buffer, len, 10); + free(buffer); + } else { + mobilinkd::tnc::ioport->write((uint8_t*) "Allocation Error\r\n", 18, 10); + } +} + +namespace mobilinkd { namespace tnc { + +#ifdef KISS_LOGGING + +Log& log(void) { + static Log log(Log::Level::debug); + return log; +} + +#endif + +#if 1 +void Log::log(Level level, const char* fmt, ...) { + + if (level < level_) return; + + va_list args; + va_start(args, fmt); + char* buffer = 0; + int len = vasiprintf(&buffer, fmt, args); + va_end(args); + + if (len >= 0) { + ioport->write((uint8_t*)buffer, len, 10); + free(buffer); + } else { + ioport->write((uint8_t*) "Allocation Error\r\n", 18, 10); + } +} +#else +void Log::log(Level level, const char* fmt, ...) { + + if (level < level_) return; + va_list args; + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + printf("\r\n"); +} +#endif + +}} // mobilinkd::tnc + diff --git a/TNC/Log.h b/TNC/Log.h new file mode 100644 index 0000000..f1153b7 --- /dev/null +++ b/TNC/Log.h @@ -0,0 +1,68 @@ +// Copyright 2015 Mobilinkd LLC +// All rights reserved. + +#ifndef MOBILINKD__TNC_LOG_HPP_ +#define MOBILINKD__TNC_LOG_HPP_ + +#ifdef __cplusplus +#include +#include +#include +#include + +extern "C" { +#else +#include +#include +#include +#include +#endif + +void log_(int level, const char *fmt, ...) __attribute__((format(printf, 2, 3))); + +#ifdef KISS_LOGGING +#define DEBUG(...) log_(0, __VA_ARGS__) +#define INFO(...) log_(1, __VA_ARGS__) +#define WARN(...) log_(2, __VA_ARGS__) +#define ERROR(...) log_(3, __VA_ARGS__) +#define SEVERE(...) log_(4, __VA_ARGS__) +#else +#define DEBUG(...) +#define INFO(...) +#define WARN(...) +#define ERROR(...) +#define SEVERE(...) +#endif + +#ifdef __cplusplus +} + +namespace mobilinkd { namespace tnc { + +#define APP_TX_DATA_SIZE 64 + +struct Log { + enum Level {debug = 0, info, warn, error, severe}; + + Level level_; + + Log() + : level_(Level::debug) + {} + + Log(Level level) + : level_(level) + {} + + void setLevel(Level level) {level_ = level;} + + void log(Level level, const char *fmt, ...); +}; + +Log& log(void); + +}} // mobilinkd::tnc + +#endif // __cplusplus + +#endif // MOBILINKD__TNC_LOG_HPP_ diff --git a/TNC/ModulatorTask.cpp b/TNC/ModulatorTask.cpp new file mode 100644 index 0000000..8dff187 --- /dev/null +++ b/TNC/ModulatorTask.cpp @@ -0,0 +1,85 @@ +// Copyright 2015 Mobilinkd LLC +// All rights reserved. + +#include "ModulatorTask.hpp" +#include "KissHardware.hpp" + +mobilinkd::tnc::SimplexPTT simplexPtt; +mobilinkd::tnc::MultiplexPTT multiplexPtt; + +mobilinkd::tnc::AFSKModulator* modulator; +mobilinkd::tnc::hdlc::Encoder* encoder; + +// DMA Conversion half complete. +extern "C" void HAL_DAC_ConvHalfCpltCallbackCh1(DAC_HandleTypeDef*) { + osEvent evt = osMessageGet(dacOutputQueueHandle, 0); + if (evt.status == osEventMessage) { + modulator->fill_first(evt.value.v); + } else { + modulator->empty(); + } +} + +extern "C" void HAL_DAC_ConvCpltCallbackCh1(DAC_HandleTypeDef*) { + osEvent evt = osMessageGet(dacOutputQueueHandle, 0); + if (evt.status == osEventMessage) { + modulator->fill_last(evt.value.v); + } else { + modulator->empty(); + } +} + +extern "C" void HAL_DAC_DMAUnderrunCallbackCh1(DAC_HandleTypeDef*) { + modulator->abort(); +} + +mobilinkd::tnc::AFSKModulator& getModulator() { + static mobilinkd::tnc::AFSKModulator instance(dacOutputQueueHandle, &simplexPtt); + return instance; +} + +mobilinkd::tnc::hdlc::Encoder& getEncoder() { + static mobilinkd::tnc::hdlc::Encoder instance(hdlcOutputQueueHandle, &getModulator()); + return instance; +} + +void setPtt(PTT ptt) +{ + switch (ptt) { + case PTT::SIMPLEX: + getModulator().set_ptt(&simplexPtt); + break; + case PTT::MULTIPLEX: + getModulator().set_ptt(&multiplexPtt); + break; + } +} + +void updatePtt() +{ + using namespace mobilinkd::tnc::kiss; + + if (settings().options & KISS_OPTION_PTT_SIMPLEX) + modulator->set_ptt(&simplexPtt); + else + modulator->set_ptt(&multiplexPtt); +} + +void startModulatorTask(void const*) { + + using namespace mobilinkd::tnc::kiss; + + modulator = &(getModulator()); + encoder = &(getEncoder()); + + updatePtt(); + + modulator->set_twist(settings().tx_twist); + + encoder->tx_delay(settings().txdelay); + encoder->p_persist(settings().ppersist); + encoder->slot_time(settings().slot); + encoder->tx_tail(settings().txtail); + + encoder->run(); +} diff --git a/TNC/ModulatorTask.hpp b/TNC/ModulatorTask.hpp new file mode 100644 index 0000000..9f9ccfe --- /dev/null +++ b/TNC/ModulatorTask.hpp @@ -0,0 +1,38 @@ +// Copyright 2015 Mobilinkd LLC +// All rights reserved. + + +#ifndef MOBILINKD__MODULATOR_TASK_HPP_ +#define MOBILINKD__MODULATOR_TASK_HPP_ + +#include "HDLCEncoder.hpp" +#include "AFSKModulator.hpp" +#include "PTT.hpp" +#include "cmsis_os.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern mobilinkd::tnc::SimplexPTT simplexPtt; +extern mobilinkd::tnc::MultiplexPTT multiplexPtt; + +extern mobilinkd::tnc::AFSKModulator* modulator; +extern mobilinkd::tnc::hdlc::Encoder* encoder; + +mobilinkd::tnc::AFSKModulator& getModulator(); +mobilinkd::tnc::hdlc::Encoder& getEncoder(); + +void startModulatorTask(void const * argument); + +enum class PTT {SIMPLEX, MULTIPLEX}; + +void setPtt(PTT ptt); + +void updatePtt(void); + +#ifdef __cplusplus +} +#endif + +#endif // MOBILINKD__MODULATOR_TASK_HPP_ diff --git a/TNC/NRZI.hpp b/TNC/NRZI.hpp new file mode 100644 index 0000000..64983e8 --- /dev/null +++ b/TNC/NRZI.hpp @@ -0,0 +1,32 @@ +#ifndef MOBILINKD__NRZI_H_ +#define MOBILINKD__NRZI_H_ + +namespace mobilinkd { namespace libafsk { + +struct NRZI { + + bool state_; + + NRZI() + : state_(false) + {} + + bool decode(bool x) { + bool result = (x == state_); + state_ = x; + return result; + } + + bool encode(bool x) { + if (x == 0) { + state_ ^= 1; // Flip the bit. + } + return state_; + } +}; + +}} // mobilinkd::libafsk + +#endif // MOBILINKD__NRZI_H_ + + diff --git a/TNC/NullPort.cpp b/TNC/NullPort.cpp new file mode 100644 index 0000000..3fd8b21 --- /dev/null +++ b/TNC/NullPort.cpp @@ -0,0 +1,15 @@ +// Copyright 2016 Rob Riggs +// All rights reserved. + +#include "NullPort.hpp" + +namespace mobilinkd { namespace tnc { + +NullPort* getNullPort() +{ + static NullPort instance; + return &instance; +} + +}} // mobilinkd::tnc + diff --git a/TNC/NullPort.hpp b/TNC/NullPort.hpp new file mode 100644 index 0000000..961f108 --- /dev/null +++ b/TNC/NullPort.hpp @@ -0,0 +1,57 @@ +// Copyright 2016 Rob Riggs +// All rights reserved. + +#ifndef MOBILINKD__TNC__NULL_PORT_HPP_ +#define MOBILINKD__TNC__NULL_PORT_HPP_ + +#include "PortInterface.hpp" + +#include + +namespace mobilinkd { namespace tnc { + +/** + * This interface defines the semi-asynchronous interface used for reading + * and writing + */ +struct NullPort : PortInterface +{ + virtual ~NullPort() {} + virtual bool open() + { + if (open_) return open_; + open_ = true; + return open_; + } + + virtual bool isOpen() const { return open_; } + + virtual void close() { + open_ = false; + } + virtual osMessageQId queue() const { return 0; } + virtual bool write(const uint8_t* data, uint32_t size, uint8_t type, + uint32_t timeout) + { + return true; + } + virtual bool write(const uint8_t* data, uint32_t size, uint32_t timeout) + { + return true; + } + virtual bool write(hdlc::IoFrame* frame, uint32_t = osWaitForever) + { + hdlc::ioFramePool().release(frame); + return true; + } + + void init() {} + + std::atomic open_{false}; +}; + +NullPort* getNullPort(); + +}} // mobilinkd::tnc + +#endif // MOBILINKD__TNC__NULL_PORT_HPP_ diff --git a/TNC/PTT.hpp b/TNC/PTT.hpp new file mode 100644 index 0000000..2bcfc01 --- /dev/null +++ b/TNC/PTT.hpp @@ -0,0 +1,44 @@ +// Copyright 2015 Robert C. Riggs +// All rights reserved. + +#ifndef INC_PTT_HPP_ +#define INC_PTT_HPP_ + +#include "GPIO.hpp" +#include "Led.h" + +namespace mobilinkd { namespace tnc { + +struct PTT { + virtual void on() = 0; + virtual void off() = 0; + virtual ~PTT() {} +}; + +struct SimplexPTT : PTT { + void on() { + led_tx_on(); // LED + gpio::PTT_SIMPLEX::on(); // PTT + } + void off() { + led_tx_off(); // LED + gpio::PTT_SIMPLEX::off(); // PTT + } +}; + + +struct MultiplexPTT : PTT { + void on() { + led_tx_on(); // LED + gpio::PTT_MULTIPLEX::on(); // PTT + } + void off() { + led_tx_off(); // LED + gpio::PTT_MULTIPLEX::off(); // PTT + } +}; + +}} // mobilinkd::tnc + + +#endif // INC_PTT_HPP_ diff --git a/TNC/PortInterface.cpp b/TNC/PortInterface.cpp new file mode 100644 index 0000000..021220a --- /dev/null +++ b/TNC/PortInterface.cpp @@ -0,0 +1,41 @@ +// Copyright 2016 Rob Riggs +// All rights reserved. + +#include "PortInterface.hpp" + +#include +#include + +namespace mobilinkd { namespace tnc { + +uint8_t TxBuffer[TX_BUFFER_SIZE]; +PortInterface* ioport{0}; + + +int write(hdlc::IoFrame* frame, uint32_t timeout) +{ + if (mobilinkd::tnc::ioport == 0) return -1; + + return mobilinkd::tnc::ioport->write(frame, timeout); +} + +}} // mobilinkd::tnc + +int writeLog(const uint8_t* data, uint32_t size, uint32_t timeout) +{ + if (mobilinkd::tnc::ioport == 0) return -1; + return mobilinkd::tnc::ioport->write(data, size, 7, timeout); +} + +int writeTNC(const uint8_t* data, uint32_t size, uint32_t timeout) +{ + if (mobilinkd::tnc::ioport == 0) return -1; + return mobilinkd::tnc::ioport->write(data, size, timeout); +} + +int printTNC(const char* zstring, uint32_t timeout) +{ + if (mobilinkd::tnc::ioport == 0) return -1; + return mobilinkd::tnc::ioport->write((uint8_t*) zstring, strlen(zstring), timeout); +} + diff --git a/TNC/PortInterface.h b/TNC/PortInterface.h new file mode 100644 index 0000000..785ba70 --- /dev/null +++ b/TNC/PortInterface.h @@ -0,0 +1,34 @@ +// Copyright 2016 Rob Riggs +// All rights reserved. + +#ifndef MOBILINKD_PORTINTERFACE_H_ +#define MOBILINKD_PORTINTERFACE_H_ + +#include "cmsis_os.h" + +#include "stdint.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void init_ioport(void); + +void initNull(void); + +int openNull(void); + +void closeCDC(void); + +int writeCDC(const uint8_t* data, uint32_t size, uint32_t timeout); + +int writeLog(const uint8_t* data, uint32_t size, uint32_t timeout); +int writeTNC(const uint8_t* data, uint32_t size, uint32_t timeout); +int printTNC(const char* zstring, uint32_t timeout); + + +#ifdef __cplusplus +} +#endif + +#endif /* MOBILINKD_PORTINTERFACE_H_ */ diff --git a/TNC/PortInterface.hpp b/TNC/PortInterface.hpp new file mode 100644 index 0000000..be8c356 --- /dev/null +++ b/TNC/PortInterface.hpp @@ -0,0 +1,41 @@ +// Copyright 2016 Rob Riggs +// All rights reserved. + +#ifndef MOBILINKD__TNC__PORT_INTERFACE_HPP_ +#define MOBILINKD__TNC__PORT_INTERFACE_HPP_ + +#include "cmsis_os.h" + +#include "HdlcFrame.hpp" +#include "PortInterface.h" + +namespace mobilinkd { namespace tnc { + +const uint32_t TX_BUFFER_SIZE = 64; +extern uint8_t TxBuffer[TX_BUFFER_SIZE]; + +/** + * This interface defines the semi-asynchronous interface used for reading + * and writing. The write interface is synchronous. The read interface + * is asynchronous. The call to open() starts a task that reads from port + * and puts that data on the read_queue, one byte at a time. + */ +struct PortInterface { + virtual ~PortInterface() {} + virtual bool open() = 0; + virtual bool isOpen() const = 0; + virtual void close() = 0; + virtual osMessageQId queue() const = 0; + virtual bool write(const uint8_t* data, uint32_t size, uint8_t type, + uint32_t timeout) = 0; + virtual bool write(const uint8_t* data, uint32_t size, uint32_t timeout) = 0; + virtual bool write(hdlc::IoFrame* frame, uint32_t timeout = osWaitForever) = 0; +}; + +extern PortInterface* ioport; + +int write(hdlc::IoFrame* frame, uint32_t timeout = osWaitForever); + +}} // mobilinkd::tnc + +#endif // MOBILINKD__TNC__PORT_INTERFACE_HPP_ diff --git a/TNC/SegmentedBuffer.hpp b/TNC/SegmentedBuffer.hpp new file mode 100644 index 0000000..b900873 --- /dev/null +++ b/TNC/SegmentedBuffer.hpp @@ -0,0 +1,144 @@ +// Copyright 2015 Mobilinkd LLC +// All rights reserved. + +#ifndef MOBILINKD__SEGMENTED_BUFFER_HPP_ +#define MOBILINKD__SEGMENTED_BUFFER_HPP_ + +#include "memory.hpp" + +#include + +#include + +namespace mobilinkd { namespace tnc { namespace buffer { + + +using boost::intrusive::list_base_hook; +using boost::intrusive::list; +using boost::intrusive::constant_time_size; + +template +struct Pool { + typedef memory::chunk chunk_type; + typedef list > chunk_list; + chunk_type segments[SIZE]; + chunk_list free_list; + + Pool() { + for(uint16_t i = 0; i != SIZE; ++i) { + free_list.push_back(segments[i]); + } + } + + bool allocate(chunk_list& list) { + bool result = false; + auto x = taskENTER_CRITICAL_FROM_ISR(); + if (!free_list.empty()) { + list.splice(list.end(), free_list, free_list.begin()); + result = true; + } + taskEXIT_CRITICAL_FROM_ISR(x); + return result; + } + + void deallocate(chunk_list& list) { + auto x = taskENTER_CRITICAL_FROM_ISR(); + free_list.splice(free_list.end(), list); + taskEXIT_CRITICAL_FROM_ISR(x); + } +}; + +template struct SegmentedBufferIterator; + +template +struct SegmentedBuffer { + typedef uint8_t value_type; + typedef value_type* pointer; + typedef value_type& reference; + + typedef SegmentedBufferIterator iterator; + typedef const SegmentedBufferIterator const_iterator; + + typename POOL::chunk_list segments_; + typename POOL::chunk_list::iterator current_; + uint16_t size_; + + SegmentedBuffer() + : segments_(), current_(segments_.end()), size_(0) + {} + + ~SegmentedBuffer() { + allocator->deallocate(segments_); + } + + void clear() { + if (size_) { + allocator->deallocate(segments_); + size_ = 0; + current_ = segments_.end(); + } + } + + uint16_t size() const {return size_;} + + bool push_back(value_type value) { + uint16_t offset = size_ & 0xFF; + if (offset == 0) { // Must allocate. + if (not allocator->allocate(segments_)) + return false; + current_ = segments_.end(); + --current_; + } + current_->buffer[offset] = value; + ++size_; + return true; + } + + iterator begin() __attribute__((noinline)) { + return iterator(segments_.begin(), 0); + } + iterator end() __attribute__((noinline)) { + return iterator(segments_.end(), size_); + } +}; + +template +struct SegmentedBufferIterator : public boost::iterator_facade< + SegmentedBufferIterator, uint8_t, boost::bidirectional_traversal_tag> +{ + typename POOL::chunk_list::iterator iter_; + uint16_t index_; + + SegmentedBufferIterator() + : iter_(), index_(0) + {} + + SegmentedBufferIterator(typename POOL::chunk_list::iterator it, uint16_t index) + : iter_(it), index_(index) + {} + + friend class boost::iterator_core_access; + + void increment() { + ++index_; + if ((index_ & 0xFF) == 0) ++iter_; + } + + void decrement() { + if ((index_ & 0xFF) == 0) --iter_; + --index_; + } + + bool equal(SegmentedBufferIterator const& other) const { + return (index_ == other.index_); + } + + uint8_t& dereference() const { + return iter_->buffer[index_ & 0xFF]; + } + +}; + +}}} // mobilinkd::tnc::buffer + +#endif // MOBILINKD__SEGMENTED_BUFFER_HPP_ diff --git a/TNC/SerialPort.cpp b/TNC/SerialPort.cpp new file mode 100644 index 0000000..967b217 --- /dev/null +++ b/TNC/SerialPort.cpp @@ -0,0 +1,415 @@ +// Copyright 2016 Rob Riggs +// All rights reserved. + +#include "SerialPort.hpp" +#include "SerialPort.h" +#include "HdlcFrame.hpp" +#include "Kiss.hpp" +#include "main.h" + +#include "stm32l4xx_hal.h" +#include "cmsis_os.h" + +#include +#include +#include +#include + +extern UART_HandleTypeDef huart2; +extern osMessageQId ioEventQueueHandle; + +extern "C" void startSerialTask(void const* arg); + +osThreadId serialTaskHandle; +uint32_t serialTaskBuffer[ 256 ]; +osStaticThreadDef_t serialTaskControlBlock; +osThreadStaticDef(serialTask, startSerialTask, osPriorityNormal, 0, 128, serialTaskBuffer, &serialTaskControlBlock); + +constexpr const int RX_BUFFER_SIZE = 127; +unsigned char rxBuffer[RX_BUFFER_SIZE * 2]; + +// 3 chunks of 128 bytes. The first byte in each chunk is the length. +typedef mobilinkd::tnc::memory::Pool< + 3, RX_BUFFER_SIZE + 1> serial_pool_type; +serial_pool_type serialPool; + +extern "C" void startSerialTask(void const* arg) +{ + using namespace mobilinkd::tnc; + + auto serialPort = static_cast(arg); + + const uint8_t FEND = 0xC0; + const uint8_t FESC = 0xDB; + const uint8_t TFEND = 0xDC; + const uint8_t TFESC = 0xDD; + + enum State {WAIT_FBEGIN, WAIT_FRAME_TYPE, WAIT_FEND, WAIT_ESCAPED}; + + State state = WAIT_FBEGIN; + + hdlc::IoFrame* frame = hdlc::ioFramePool().acquire(); + + HAL_UART_Receive_DMA(&huart2, rxBuffer, RX_BUFFER_SIZE * 2); + __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE); + + while (true) { + osEvent evt = osMessageGet(serialPort->queue(), osWaitForever); + + if (evt.status != osEventMessage) { + continue; + } + + auto block = (serial_pool_type::chunk_type*) evt.value.p; + auto data = static_cast(block->buffer); + + uint8_t len = data[0]; + for (uint8_t i = 0; i != len; ++i) { + uint8_t c = data[i+1]; + switch (state) { + case WAIT_FBEGIN: + if (c == FEND) state = WAIT_FRAME_TYPE; + break; + case WAIT_FRAME_TYPE: + if (c == FEND) break; // Still waiting for FRAME_TYPE. + frame->type(c); + state = WAIT_FEND; + break; + case WAIT_FEND: + switch (c) { + case FESC: + state = WAIT_ESCAPED; + break; + case FEND: + frame->source(hdlc::IoFrame::SERIAL_DATA); + osMessagePut(ioEventQueueHandle, reinterpret_cast(frame), osWaitForever); + frame = hdlc::ioFramePool().acquire(); + state = WAIT_FBEGIN; + break; + default: + if (not frame->push_back(c)) { + hdlc::ioFramePool().release(frame); + state = WAIT_FBEGIN; // Drop frame; + frame = hdlc::ioFramePool().acquire(); + } + } + break; + case WAIT_ESCAPED: + state = WAIT_FEND; + switch (c) { + case TFESC: + if (not frame->push_back(FESC)) { + hdlc::ioFramePool().release(frame); + state = WAIT_FBEGIN; // Drop frame; + frame = hdlc::ioFramePool().acquire(); + } + break; + case TFEND: + if (not frame->push_back(FEND)) { + hdlc::ioFramePool().release(frame); + state = WAIT_FBEGIN; // Drop frame; + frame = hdlc::ioFramePool().acquire(); + } + break; + default: + hdlc::ioFramePool().release(frame); + state = WAIT_FBEGIN; // Drop frame; + frame = hdlc::ioFramePool().acquire(); + } + break; + } + } + serialPool.deallocate(block); + } +} + +extern "C" void HAL_UART_TxCpltCallback(UART_HandleTypeDef*) +{ + osMutexRelease(mobilinkd::tnc::getSerialPort()->mutex_); +} + +extern "C" void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart) +{ + uint32_t len = (RX_BUFFER_SIZE * 2) - __HAL_DMA_GET_COUNTER(huart->hdmarx); + if (len == 0) return; + + auto block = serialPool.allocate(); + if (!block) return; + memmove(block->buffer + 1, rxBuffer, len); + auto status = osMessagePut(mobilinkd::tnc::getSerialPort()->queue(), (uint32_t) block, 0); + if (status != osOK) serialPool.deallocate(block); +} + +extern "C" void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) +{ + uint32_t len = RX_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(huart->hdmarx); + if (len == 0) return; + + auto block = serialPool.allocate(); + if (!block) return; + memmove(block->buffer + 1, rxBuffer + RX_BUFFER_SIZE, len); + auto status = osMessagePut(mobilinkd::tnc::getSerialPort()->queue(), (uint32_t) block, 0); + if (status != osOK) serialPool.deallocate(block); +} + +extern "C" void idleInterruptCallback(UART_HandleTypeDef* huart) +{ + uint32_t len = (RX_BUFFER_SIZE * 2) - __HAL_DMA_GET_COUNTER(huart->hdmarx); + + if (len == 0) return; + + auto block = serialPool.allocate(); + if (!block) return; + + HAL_UART_AbortReceive(huart); + + if (len > RX_BUFFER_SIZE) { + // Second half + len = len - RX_BUFFER_SIZE; + memmove(block->buffer + 1, rxBuffer + RX_BUFFER_SIZE, len); + } else { + // First half + memmove(block->buffer + 1, rxBuffer, len); + } + + block->buffer[0] = len; + + HAL_UART_Receive_DMA(huart, rxBuffer, RX_BUFFER_SIZE * 2); + + auto status = osMessagePut(mobilinkd::tnc::getSerialPort()->queue(), (uint32_t) block, 0); + if (status != osOK) serialPool.deallocate(block); + +} + +extern "C" void HAL_UART_ErrorCallback(UART_HandleTypeDef*) +{ + Error_Handler(); +} + +namespace mobilinkd { namespace tnc { + +void SerialPort::init() +{ + if (serialTaskHandle_) return; + + osMessageQDef(uartQueue, 32, void*); + queue_ = osMessageCreate(osMessageQ(uartQueue), 0); + + osMutexDef(uartMutex); + mutex_ = osMutexCreate(osMutex(uartMutex)); + + serialTaskHandle = osThreadCreate(osThread(serialTask), this); + + serialTaskHandle_ = serialTaskHandle; + // DEBUG("serialTaskHandle_ = %p", serialTaskHandle_); +} + +bool SerialPort::open() +{ + if (open_ or !serialTaskHandle_) return open_; + + open_ = true; + return open_; +} + +void SerialPort::close() +{ + open_ = false; +} + +bool SerialPort::write(const uint8_t* data, uint32_t size, uint8_t type, uint32_t timeout) +{ + if (!open_) return false; + + uint32_t start = osKernelSysTick(); + + if (osMutexWait(mutex_, timeout) != osOK) + return false; + + using ::mobilinkd::tnc::kiss::slip_encoder; + + auto slip_iter = slip_encoder((const char*)data, size); + auto slip_end = slip_encoder(); + + size_t pos = 0; + + TxBuffer[pos++] = 0xC0; // FEND + TxBuffer[pos++] = type; // KISS Data Frame + + while (slip_iter != slip_end) { + TxBuffer[pos++] = *slip_iter++; + if (pos == TX_BUFFER_SIZE) { + while (open_ and HAL_UART_Transmit(&huart2, TxBuffer, pos, timeout) == HAL_BUSY) + { + if (osKernelSysTick() > start + timeout) { + osMutexRelease(mutex_); + return false; + } + osThreadYield(); + } + pos = 0; + } + } + + // Buffer has room for at least one more byte. + TxBuffer[pos++] = 0xC0; + while (open_ and HAL_UART_Transmit(&huart2, TxBuffer, pos, timeout) == HAL_BUSY) + { + if (osKernelSysTick() > start + timeout) { + osMutexRelease(mutex_); + return false; + } + osThreadYield(); + } + + osMutexRelease(mutex_); + + return true; +} + +bool SerialPort::write(const uint8_t* data, uint32_t size, uint32_t timeout) +{ + if (!open_) return false; + + uint32_t start = osKernelSysTick(); + + if (osMutexWait(mutex_, timeout) != osOK) + return false; + + size_t pos = 0; + + auto first = data; + auto last = data + size; + + while (first != last) { + TxBuffer[pos++] = *first++; + if (pos == TX_BUFFER_SIZE) { + while (open_ and HAL_UART_Transmit(&huart2, TxBuffer, pos, timeout) == HAL_BUSY) + { + if (osKernelSysTick() > start + timeout) { + osMutexRelease(mutex_); + return false; + } + osThreadYield(); + } + pos = 0; + } + } + + while (open_ and HAL_UART_Transmit(&huart2, TxBuffer, pos, timeout) == HAL_BUSY) + { + if (osKernelSysTick() > start + timeout) { + osMutexRelease(mutex_); + return false; + } + osThreadYield(); + } + + while (open_ and HAL_UART_Transmit(&huart2, (uint8_t*)"\r\n", 2, timeout) == HAL_BUSY) + { + if (osKernelSysTick() > start + timeout) { + osMutexRelease(mutex_); + return false; + } + osThreadYield(); + } + + osMutexRelease(mutex_); + + return true; +} + +bool SerialPort::write(hdlc::IoFrame* frame, uint32_t timeout) +{ + if (!open_) { + hdlc::release(frame); + return false; + } + + uint32_t start = osKernelSysTick(); + + if (osMutexWait(mutex_, timeout) != osOK) { + hdlc::release(frame); + return false; + } + + using ::mobilinkd::tnc::kiss::slip_encoder2; + + hdlc::IoFrame::iterator begin = frame->begin(); + hdlc::IoFrame::iterator end = frame->begin(); + std::advance(end, frame->size() - 2); // Drop FCS + + auto slip_iter = slip_encoder2(begin); + auto slip_end = slip_encoder2(end); + + size_t pos = 0; + + TxBuffer[pos++] = 0xC0; // FEND + TxBuffer[pos++] = static_cast(frame->type()); // KISS Data Frame + + while (slip_iter != slip_end) { + TxBuffer[pos++] = *slip_iter++; + if (pos == TX_BUFFER_SIZE) { + while (open_ and HAL_UART_Transmit(&huart2, TxBuffer, pos, timeout) == HAL_BUSY) + { + if (osKernelSysTick() > start + timeout) { + osMutexRelease(mutex_); + hdlc::release(frame); + return false; + } + osThreadYield(); + } + pos = 0; + } + } + + // Buffer has room for at least one more byte. + TxBuffer[pos++] = 0xC0; + while (open_ and HAL_UART_Transmit(&huart2, TxBuffer, pos, timeout) == HAL_BUSY) { + if (osKernelSysTick() > start + timeout) { + osMutexRelease(mutex_); + hdlc::release(frame); + return false; + } + osThreadYield(); + } + + osMutexRelease(mutex_); + hdlc::release(frame); + + return true; +} + + +SerialPort* getSerialPort() +{ + static SerialPort instance; + return &instance; +} + + +}} // mobilinkd::tnc + +void initSerial() +{ + mobilinkd::tnc::getSerialPort()->init(); +} + +int openSerial() +{ + mobilinkd::tnc::PortInterface* tmp = mobilinkd::tnc::getSerialPort(); + tmp->open(); + if (mobilinkd::tnc::ioport != tmp and tmp->isOpen()) + { + std::swap(tmp, mobilinkd::tnc::ioport); + if (tmp) tmp->close(); + return true; + } + return mobilinkd::tnc::ioport == tmp; +} + +int writeSerial(const uint8_t* data, uint32_t size, uint32_t timeout) +{ + return mobilinkd::tnc::getSerialPort()->write(data, size, timeout); +} + diff --git a/TNC/SerialPort.h b/TNC/SerialPort.h new file mode 100644 index 0000000..b31e4a9 --- /dev/null +++ b/TNC/SerialPort.h @@ -0,0 +1,23 @@ +// Copyright 2018 Rob Riggs +// All rights reserved. + +#ifndef SERIALPORT_H_ +#define SERIALPORT_H_ + +#include "stm32l4xx_hal.h" +#include "stdint.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void initSerial(void); +int openSerial(void); +void closeSerial(void); +void idleInterruptCallback(UART_HandleTypeDef* huart); + +#ifdef __cplusplus +} +#endif + +#endif /* SERIALPORT_H_ */ diff --git a/TNC/SerialPort.hpp b/TNC/SerialPort.hpp new file mode 100644 index 0000000..df23e99 --- /dev/null +++ b/TNC/SerialPort.hpp @@ -0,0 +1,40 @@ +// Copyright 2016 Rob Riggs +// All rights reserved. + +#ifndef MOBILINKD__TNC__SERIAL_PORT_HPP_ +#define MOBILINKD__TNC__SERIAL_PORT_HPP_ + +#include "PortInterface.hpp" +#include "cmsis_os.h" + +namespace mobilinkd { namespace tnc { + +/** + * This interface defines the semi-asynchronous interface used for reading + * and writing + */ +struct SerialPort : PortInterface +{ + virtual ~SerialPort() {} + virtual bool open(); + virtual bool isOpen() const { return open_; } + virtual void close(); + virtual osMessageQId queue() const { return queue_; } + virtual bool write(const uint8_t* data, uint32_t size, uint8_t type, + uint32_t timeout); + virtual bool write(const uint8_t* data, uint32_t size, uint32_t timeout); + virtual bool write(hdlc::IoFrame* frame, uint32_t timeout = osWaitForever); + + void init(); + + bool open_{false}; // opened/closed + osMutexId mutex_{0}; // TX Mutex + osMessageQId queue_{0}; // ISR read queue + osThreadId serialTaskHandle_{0}; +}; + +SerialPort* getSerialPort(); + +}} // mobilinkd::tnc + +#endif // MOBILINKD__TNC__SERIAL_PORT_HPP_ diff --git a/TNC/memory.hpp b/TNC/memory.hpp new file mode 100644 index 0000000..e3ae05f --- /dev/null +++ b/TNC/memory.hpp @@ -0,0 +1,74 @@ +// Copyright 2015 Mobilinkd LLC +// All rights reserved. + +#ifndef MOBILINKD__MEMORY_HPP_ +#define MOBILINKD__MEMORY_HPP_ + +#include "cmsis_os.h" + +#include + +#include + +namespace mobilinkd { namespace tnc { namespace memory { + +using boost::intrusive::list_base_hook; +using boost::intrusive::list; +using boost::intrusive::constant_time_size; + +template +struct chunk : public list_base_hook<> +{ + uint8_t buffer[BLOCK_SIZE]; + + static uint16_t constexpr size() { return BLOCK_SIZE; } + + chunk() + : list_base_hook<>(), buffer() + {} +}; + + +template +struct Pool { + typedef chunk chunk_type; + typedef list > chunk_list; + + chunk_type segments[SIZE]; + chunk_list free_list; + + Pool() { + for(uint16_t i = 0; i != SIZE; ++i) { + free_list.push_back(segments[i]); + } + } + + void init() { + free_list.clear(); + for(uint16_t i = 0; i != SIZE; ++i) { + free_list.push_back(segments[i]); + } + } + + chunk_type* allocate() { + auto x = taskENTER_CRITICAL_FROM_ISR(); + chunk_type* result = 0; + if (not free_list.empty()) { + result = &free_list.front(); + free_list.pop_front(); + } + taskEXIT_CRITICAL_FROM_ISR(x); + return result; + } + + void deallocate(chunk_type* item) { + auto x = taskENTER_CRITICAL_FROM_ISR(); + free_list.push_back(*item); + taskEXIT_CRITICAL_FROM_ISR(x); + } +}; + + +}}} // mobilinkd::tnc::memory + +#endif // MOBILINKD__MEMORY_HPP_ diff --git a/TNC/power.h b/TNC/power.h new file mode 100644 index 0000000..eb4f466 --- /dev/null +++ b/TNC/power.h @@ -0,0 +1,43 @@ +// Copyright 2017 Rob Riggs +// All rights reserved. + +#ifndef MOBILINKD__TNC__POWER_H_ +#define MOBILINKD__TNC__POWER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +void shutdown_normal(void); +void shutdown_with_usb(void); +void shutdown_safe_mode(void); + +void wakeup(); + +#ifdef __cplusplus +} + +namespace mobilinkd { namespace tnc { + +/** + * The type of power on or off process to follow. + * + * - POWERON occurs when the RTC backup domain has been erased due to + * complete power loss. This should only happen if the battery is + * completely drained or removed. + * - NORMAL occurs when the TNC is powered off and the TNC is not on USB + * power. Note that this is the state when the TNC was powered off, + * not the state when it is powered on. + * - USB occurs when the TNC is powered off and the TNC is connected to + * USB power. + */ +enum PowerType {POWERON, NORMAL, USB, SAFE}; + +void shutdown(PowerType type); +void wakeup(PowerType type); + +}} // mobilinkd::tnc + +#endif //__cplusplus + +#endif // MOBILINKD__TNC__POWER_H_ diff --git a/newlib/_exit.c b/newlib/_exit.c new file mode 100644 index 0000000..30563e1 --- /dev/null +++ b/newlib/_exit.c @@ -0,0 +1,48 @@ +// +// This file is part of the µOS++ III distribution. +// Copyright (c) 2014 Liviu Ionescu. +// + +// ---------------------------------------------------------------------------- + +#include + +// ---------------------------------------------------------------------------- + +extern void +__attribute__((noreturn)) +Reset_Handler(void); + +// ---------------------------------------------------------------------------- + +// Forward declaration + +void +_exit(int code); + +// ---------------------------------------------------------------------------- + +// On Release, call the hardware reset procedure. +// On Debug we just enter an infinite loop, to be used as landmark when halting +// the debugger. +// +// It can be redefined in the application, if more functionality +// is required. + +void +__attribute__((weak)) +_exit(int code __attribute__((unused))) +{ + Reset_Handler(); +} + +// ---------------------------------------------------------------------------- + +void +__attribute__((weak,noreturn)) +abort(void) +{ + _exit(1); +} + +// ---------------------------------------------------------------------------- diff --git a/newlib/_sbrk.c b/newlib/_sbrk.c new file mode 100644 index 0000000..ccccbc1 --- /dev/null +++ b/newlib/_sbrk.c @@ -0,0 +1,65 @@ +// +// This file is part of the µOS++ III distribution. +// Copyright (c) 2014 Liviu Ionescu. +// + +// ---------------------------------------------------------------------------- + +#include +#include + +// ---------------------------------------------------------------------------- + +caddr_t +_sbrk(int incr); + +// ---------------------------------------------------------------------------- + +// The definitions used here should be kept in sync with the +// stack definitions in the linker script. + +caddr_t +_sbrk(int incr) +{ + extern char _Heap_Begin; // Defined by the linker. + extern char _Heap_Limit; // Defined by the linker. + + static char* current_heap_end = 0; + char* current_block_address; + + if (current_heap_end == 0) + { + current_heap_end = &_Heap_Begin; + } + + current_block_address = current_heap_end; + + // Need to align heap to word boundary, else will get + // hard faults on Cortex-M0. So we assume that heap starts on + // word boundary, hence make sure we always add a multiple of + // 4 to it. + incr = (incr + 3) & (~3); // align value to 4 + if (current_heap_end + incr > &_Heap_Limit) + { + // Some of the libstdc++-v3 tests rely upon detecting + // out of memory errors, so do not abort here. +#if 0 + extern void abort (void); + + _write (1, "_sbrk: Heap and stack collision\n", 32); + + abort (); +#else + // Heap has overflowed + errno = ENOMEM; + return (caddr_t) - 1; +#endif + } + + current_heap_end += incr; + + return (caddr_t) current_block_address; +} + +// ---------------------------------------------------------------------------- + diff --git a/newlib/_syscalls.c b/newlib/_syscalls.c new file mode 100644 index 0000000..e70a679 --- /dev/null +++ b/newlib/_syscalls.c @@ -0,0 +1,1212 @@ +// +// This file is part of the µOS++ III distribution. +// Parts of this file are from the newlib sources, issued under GPL. +// Copyright (c) 2014 Liviu Ionescu +// + +// ---------------------------------------------------------------------------- + +int errno; + +// ---------------------------------------------------------------------------- + +#if !defined(OS_USE_SEMIHOSTING) + +#include <_ansi.h> +#include <_syslist.h> +#include +//#include +#include +#include +#include +#include +#include + +void +__initialize_args(int* p_argc, char*** p_argv); + +// This is the standard default implementation for the routine to +// process args. It returns a single empty arg. +// For semihosting applications, this is redefined to get the real +// args from the debugger. You can also use it if you decide to keep +// some args in a non-volatile memory. + +void __attribute__((weak)) +__initialize_args(int* p_argc, char*** p_argv) +{ + // By the time we reach this, the data and bss should have been initialised. + + // The strings pointed to by the argv array shall be modifiable by the + // program, and retain their last-stored values between program startup + // and program termination. (static, no const) + static char name[] = ""; + + // The string pointed to by argv[0] represents the program name; + // argv[0][0] shall be the null character if the program name is not + // available from the host environment. argv[argc] shall be a null pointer. + // (static, no const) + static char* argv[2] = + { name, NULL }; + + *p_argc = 1; + *p_argv = &argv[0]; + return; +} + +// These functions are defined here to avoid linker errors in freestanding +// applications. They might be called in some error cases from library +// code. +// +// If you detect other functions to be needed, just let us know +// and we'll add them. + +int +raise(int sig __attribute__((unused))) +{ + errno = ENOSYS; + return -1; +} + +int +kill(pid_t pid, int sig); + +int +kill(pid_t pid __attribute__((unused)), int sig __attribute__((unused))) +{ + errno = ENOSYS; + return -1; +} + +#endif // !defined(OS_USE_SEMIHOSTING) + +// ---------------------------------------------------------------------------- + +// If you need the empty definitions, remove the -ffreestanding option. + +#if __STDC_HOSTED__ == 1 + +char* __env[1] = + { 0 }; +char** environ = __env; + +#if !defined(OS_USE_SEMIHOSTING) + +// Forward declarations + +int +_chown(const char* path, uid_t owner, gid_t group); + +int +_close(int fildes); + +int +_execve(char* name, char** argv, char** env); + +int +_fork(void); + +int +_fstat(int fildes, struct stat* st); + +int +_getpid(void); + +int +_gettimeofday(struct timeval* ptimeval, void* ptimezone); + +int +_isatty(int file); + +int +_kill(int pid, int sig); + +int +_link(char* existing, char* _new); + +int +_lseek(int file, int ptr, int dir); + +int +_open(char* file, int flags, int mode); + +int +_read(int file, char* ptr, int len); + +int +_readlink(const char* path, char* buf, size_t bufsize); + +int +_stat(const char* file, struct stat* st); + +int +_symlink(const char* path1, const char* path2); + +clock_t +_times(struct tms* buf); + +int +_unlink(char* name); + +int +_wait(int* status); + +int +_write(int file, char* ptr, int len); + +// Definitions + +int __attribute__((weak)) +_chown(const char* path __attribute__((unused)), + uid_t owner __attribute__((unused)), gid_t group __attribute__((unused))) +{ + errno = ENOSYS; + return -1; +} + +int __attribute__((weak)) +_close(int fildes __attribute__((unused))) +{ + errno = ENOSYS; + return -1; +} + +int __attribute__((weak)) +_execve(char* name __attribute__((unused)), char** argv __attribute__((unused)), + char** env __attribute__((unused))) +{ + errno = ENOSYS; + return -1; +} + +int __attribute__((weak)) +_fork(void) +{ + errno = ENOSYS; + return -1; +} + +int __attribute__((weak)) +_fstat(int fildes __attribute__((unused)), + struct stat* st __attribute__((unused))) +{ + errno = ENOSYS; + return -1; +} + +int __attribute__((weak)) +_getpid(void) +{ + errno = ENOSYS; + return -1; +} + +int __attribute__((weak)) +_gettimeofday(struct timeval* ptimeval __attribute__((unused)), + void* ptimezone __attribute__((unused))) +{ + errno = ENOSYS; + return -1; +} + +int __attribute__((weak)) +_isatty(int file __attribute__((unused))) +{ + errno = ENOSYS; + return 0; +} + +int __attribute__((weak)) +_kill(int pid __attribute__((unused)), int sig __attribute__((unused))) +{ + errno = ENOSYS; + return -1; +} + +int __attribute__((weak)) +_link(char* existing __attribute__((unused)), + char* _new __attribute__((unused))) +{ + errno = ENOSYS; + return -1; +} + +int __attribute__((weak)) +_lseek(int file __attribute__((unused)), int ptr __attribute__((unused)), + int dir __attribute__((unused))) +{ + errno = ENOSYS; + return -1; +} + +int __attribute__((weak)) +_open(char* file __attribute__((unused)), int flags __attribute__((unused)), + int mode __attribute__((unused))) +{ + errno = ENOSYS; + return -1; +} + +int __attribute__((weak)) +_read(int file __attribute__((unused)), char* ptr __attribute__((unused)), + int len __attribute__((unused))) +{ + errno = ENOSYS; + return -1; +} + +int __attribute__((weak)) +_readlink(const char* path __attribute__((unused)), + char* buf __attribute__((unused)), size_t bufsize __attribute__((unused))) +{ + errno = ENOSYS; + return -1; +} + +int __attribute__((weak)) +_stat(const char* file __attribute__((unused)), + struct stat* st __attribute__((unused))) +{ + errno = ENOSYS; + return -1; +} + +int __attribute__((weak)) +_symlink(const char* path1 __attribute__((unused)), + const char* path2 __attribute__((unused))) +{ + errno = ENOSYS; + return -1; +} + +clock_t __attribute__((weak)) +_times(struct tms* buf __attribute__((unused))) +{ + errno = ENOSYS; + return ((clock_t) -1); +} + +int __attribute__((weak)) +_unlink(char* name __attribute__((unused))) +{ + errno = ENOSYS; + return -1; +} + +int __attribute__((weak)) +_wait(int* status __attribute__((unused))) +{ + errno = ENOSYS; + return -1; +} + +int __attribute__((weak)) +_write(int file __attribute__((unused)), char* ptr __attribute__((unused)), + int len __attribute__((unused))) +{ + errno = ENOSYS; + return -1; +} + +// ---------------------------------------------------------------------------- + +#else // defined(OS_USE_SEMIHOSTING) + +// ---------------------------------------------------------------------------- + +/* Support files for GNU libc. Files in the system namespace go here. + Files in the C namespace (ie those that do not start with an + underscore) go in .c. */ + +#include <_ansi.h> +#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "arm/semihosting.h" + +int +_kill (int pid, int sig); + +void +__attribute__((noreturn)) +_exit (int status); + +// Forward declarations. +int +_system (const char*); +int +_rename (const char*, const char*); +int +_isatty (int); +clock_t +_times (struct tms*); +int +_gettimeofday (struct timeval *, void*); +int +_unlink (const char*); +int +_link (void); + +int +_stat (const char*, struct stat*); + +int +_fstat (int, struct stat*); +int +_swistat (int fd, struct stat* st); +int +_getpid (int); +int +_close (int); +clock_t +_clock (void); +int +_swiclose (int); +int +_open (const char*, int, ...); +int +_swiopen (const char*, int); +int +_write (int, char*, int); +int +_swiwrite (int, char*, int); +int +_lseek (int, int, int); +int +_swilseek (int, int, int); +int +_read (int, char*, int); +int +_swiread (int, char*, int); + +void +initialise_monitor_handles (void); + +void +__initialize_args (int* p_argc, char*** p_argv); + +static int +checkerror (int); +static int +error (int); +static int +get_errno (void); + +// ---------------------------------------------------------------------------- + +#define ARGS_BUF_ARRAY_SIZE 80 +#define ARGV_BUF_ARRAY_SIZE 10 + +typedef struct +{ + char* pCommandLine; + int size; +} CommandLineBlock; + +void +__initialize_args (int* p_argc, char*** p_argv) +{ + + // Array of chars to receive the command line from the host + static char args_buf[ARGS_BUF_ARRAY_SIZE]; + + // Array of pointers to store the final argv pointers (pointing + // in the above array). + static char* argv_buf[ARGV_BUF_ARRAY_SIZE]; + + int argc = 0; + int isInArgument = 0; + + CommandLineBlock cmdBlock; + cmdBlock.pCommandLine = args_buf; + cmdBlock.size = sizeof(args_buf) - 1; + + int ret = call_host (SEMIHOSTING_SYS_GET_CMDLINE, &cmdBlock); + if (ret == 0) + { + + // In case the host send more than we can chew, limit the + // string to our buffer. + args_buf[ARGS_BUF_ARRAY_SIZE - 1] = '\0'; + + // The command line is a null terminated string + char* p = cmdBlock.pCommandLine; + + int delim = '\0'; + int ch; + + while ((ch = *p) != '\0') + { + if (isInArgument == 0) + { + if (!isblank(ch)) + { + if (argc + >= (int) ((sizeof(argv_buf) / sizeof(argv_buf[0])) - 1)) + break; + + if (ch == '"' || ch == '\'') + { + // Remember the delimiter to search for the + // corresponding terminator + delim = ch; + ++p; // skip the delimiter + ch = *p; + } + // Remember the arg beginning address + argv_buf[argc++] = p; + isInArgument = 1; + } + } + else + { + if ((ch == delim) || isblank(ch)) + { + delim = '\0'; + *p = '\0'; + isInArgument = 0; + } + } + ++p; + } + } + + if (argc == 0) + { + // No args found in string, return a single empty name. + args_buf[0] = '\0'; + argv_buf[0] = &args_buf[0]; + ++argc; + } + + // Must end the array with a null pointer. + argv_buf[argc] = NULL; + + *p_argc = argc; + *p_argv = &argv_buf[0]; + + // temporary here + initialise_monitor_handles (); + + return; +} + +// ---------------------------------------------------------------------------- + +void +_exit (int status) +{ + /* There is only one SWI for both _exit and _kill. For _exit, call + the SWI with the second argument set to -1, an invalid value for + signum, so that the SWI handler can distinguish the two calls. + Note: The RDI implementation of _kill throws away both its + arguments. */ + report_exception ( + status == 0 ? ADP_Stopped_ApplicationExit : ADP_Stopped_RunTimeError); +} + +// ---------------------------------------------------------------------------- + +int __attribute__((weak)) +_kill (int pid __attribute__((unused)), int sig __attribute__((unused))) +{ + errno = ENOSYS; + return -1; +} + +// ---------------------------------------------------------------------------- + +/* Struct used to keep track of the file position, just so we + can implement fseek(fh,x,SEEK_CUR). */ +struct fdent +{ + int handle; + int pos; +}; + +#define MAX_OPEN_FILES 20 + +/* User file descriptors (fd) are integer indexes into + the openfiles[] array. Error checking is done by using + findslot(). + + This openfiles array is manipulated directly by only + these 5 functions: + + findslot() - Translate entry. + newslot() - Find empty entry. + initilise_monitor_handles() - Initialize entries. + _swiopen() - Initialize entry. + _close() - Handle stdout == stderr case. + + Every other function must use findslot(). */ + +static struct fdent openfiles[MAX_OPEN_FILES]; + +static struct fdent* +findslot (int); +static int +newslot (void); + +/* Register name faking - works in collusion with the linker. */ +register char* stack_ptr asm ("sp"); + +/* following is copied from libc/stdio/local.h to check std streams */ +extern void _EXFUN(__sinit,(struct _reent*)); +#define CHECK_INIT(ptr) \ + do \ + { \ + if ((ptr) && !(ptr)->__sdidinit) \ + __sinit (ptr); \ + } \ + while (0) + +static int monitor_stdin; +static int monitor_stdout; +static int monitor_stderr; + +/* Return a pointer to the structure associated with + the user file descriptor fd. */ +static struct fdent* +findslot (int fd) +{ + CHECK_INIT(_REENT); + + /* User file descriptor is out of range. */ + if ((unsigned int) fd >= MAX_OPEN_FILES) + { + return NULL; + } + + /* User file descriptor is open? */ + if (openfiles[fd].handle == -1) + { + return NULL; + } + + /* Valid. */ + return &openfiles[fd]; +} + +/* Return the next lowest numbered free file + structure, or -1 if we can't find one. */ +static int +newslot (void) +{ + int i; + + for (i = 0; i < MAX_OPEN_FILES; i++) + { + if (openfiles[i].handle == -1) + { + break; + } + } + + if (i == MAX_OPEN_FILES) + { + return -1; + } + + return i; +} + +void +initialise_monitor_handles (void) +{ + int i; + + /* Open the standard file descriptors by opening the special + * teletype device, ":tt", read-only to obtain a descriptor for + * standard input and write-only to obtain a descriptor for standard + * output. Finally, open ":tt" in append mode to obtain a descriptor + * for standard error. Since this is a write mode, most kernels will + * probably return the same value as for standard output, but the + * kernel can differentiate the two using the mode flag and return a + * different descriptor for standard error. + */ + + int volatile block[3]; + + block[0] = (int) ":tt"; + block[2] = 3; /* length of filename */ + block[1] = 0; /* mode "r" */ + monitor_stdin = call_host (SEMIHOSTING_SYS_OPEN, (void*) block); + + block[0] = (int) ":tt"; + block[2] = 3; /* length of filename */ + block[1] = 4; /* mode "w" */ + monitor_stdout = call_host (SEMIHOSTING_SYS_OPEN, (void*) block); + + block[0] = (int) ":tt"; + block[2] = 3; /* length of filename */ + block[1] = 8; /* mode "a" */ + monitor_stderr = call_host (SEMIHOSTING_SYS_OPEN, (void*) block); + + /* If we failed to open stderr, redirect to stdout. */ + if (monitor_stderr == -1) + { + monitor_stderr = monitor_stdout; + } + + for (i = 0; i < MAX_OPEN_FILES; i++) + { + openfiles[i].handle = -1; + } + + openfiles[0].handle = monitor_stdin; + openfiles[0].pos = 0; + openfiles[1].handle = monitor_stdout; + openfiles[1].pos = 0; + openfiles[2].handle = monitor_stderr; + openfiles[2].pos = 0; +} + +static int +get_errno (void) +{ + return call_host (SEMIHOSTING_SYS_ERRNO, NULL); +} + +/* Set errno and return result. */ +static int +error (int result) +{ + errno = get_errno (); + return result; +} + +/* Check the return and set errno appropriately. */ +static int +checkerror (int result) +{ + if (result == -1) + { + return error (-1); + } + + return result; +} + +/* fh, is a valid internal file handle. + ptr, is a null terminated string. + len, is the length in bytes to read. + Returns the number of bytes *not* written. */ +int +_swiread (int fh, char* ptr, int len) +{ + int block[3]; + + block[0] = fh; + block[1] = (int) ptr; + block[2] = len; + + return checkerror (call_host (SEMIHOSTING_SYS_READ, block)); +} + +/* fd, is a valid user file handle. + Translates the return of _swiread into + bytes read. */ +int +_read (int fd, char* ptr, int len) +{ + int res; + struct fdent *pfd; + + pfd = findslot (fd); + if (pfd == NULL) + { + errno = EBADF; + return -1; + } + + res = _swiread (pfd->handle, ptr, len); + + if (res == -1) + { + return res; + } + + pfd->pos += len - res; + + /* res == len is not an error, + at least if we want feof() to work. */ + return len - res; +} + +/* fd, is a user file descriptor. */ +int +_swilseek (int fd, int ptr, int dir) +{ + int res; + struct fdent *pfd; + + /* Valid file descriptor? */ + pfd = findslot (fd); + if (pfd == NULL) + { + errno = EBADF; + return -1; + } + + /* Valid whence? */ + if ((dir != SEEK_CUR) && (dir != SEEK_SET) && (dir != SEEK_END)) + { + errno = EINVAL; + return -1; + } + + /* Convert SEEK_CUR to SEEK_SET */ + if (dir == SEEK_CUR) + { + ptr = pfd->pos + ptr; + /* The resulting file offset would be negative. */ + if (ptr < 0) + { + errno = EINVAL; + if ((pfd->pos > 0) && (ptr > 0)) + { + errno = EOVERFLOW; + } + return -1; + } + dir = SEEK_SET; + } + + int block[2]; + if (dir == SEEK_END) + { + block[0] = pfd->handle; + res = checkerror (call_host (SEMIHOSTING_SYS_FLEN, block)); + if (res == -1) + { + return -1; + } + ptr += res; + } + + /* This code only does absolute seeks. */ + block[0] = pfd->handle; + block[1] = ptr; + res = checkerror (call_host (SEMIHOSTING_SYS_SEEK, block)); + + /* At this point ptr is the current file position. */ + if (res >= 0) + { + pfd->pos = ptr; + return ptr; + } + else + { + return -1; + } +} + +int +_lseek (int fd, int ptr, int dir) +{ + return _swilseek (fd, ptr, dir); +} + +/* fh, is a valid internal file handle. + Returns the number of bytes *not* written. */ +int +_swiwrite (int fh, char* ptr, int len) +{ + int block[3]; + + block[0] = fh; + block[1] = (int) ptr; + block[2] = len; + + return checkerror (call_host (SEMIHOSTING_SYS_WRITE, block)); +} + +/* fd, is a user file descriptor. */ +int +_write (int fd, char* ptr, int len) +{ + int res; + struct fdent *pfd; + + pfd = findslot (fd); + if (pfd == NULL) + { + errno = EBADF; + return -1; + } + + res = _swiwrite (pfd->handle, ptr, len); + + /* Clearly an error. */ + if (res < 0) + { + return -1; + } + + pfd->pos += len - res; + + /* We wrote 0 bytes? + Retrieve errno just in case. */ + if ((len - res) == 0) + { + return error (0); + } + + return (len - res); +} + +int +_swiopen (const char* path, int flags) +{ + int aflags = 0, fh; + uint32_t block[3]; + + int fd = newslot (); + + if (fd == -1) + { + errno = EMFILE; + return -1; + } + + /* It is an error to open a file that already exists. */ + if ((flags & O_CREAT) && (flags & O_EXCL)) + { + struct stat st; + int res; + res = _stat (path, &st); + if (res != -1) + { + errno = EEXIST; + return -1; + } + } + + /* The flags are Unix-style, so we need to convert them. */ +#ifdef O_BINARY + if (flags & O_BINARY) + { + aflags |= 1; + } +#endif + + /* In O_RDONLY we expect aflags == 0. */ + + if (flags & O_RDWR) + { + aflags |= 2; + } + + if ((flags & O_CREAT) || (flags & O_TRUNC) || (flags & O_WRONLY)) + { + aflags |= 4; + } + + if (flags & O_APPEND) + { + /* Can't ask for w AND a; means just 'a'. */ + aflags &= ~4; + aflags |= 8; + } + + block[0] = (uint32_t) path; + block[2] = strlen (path); + block[1] = (uint32_t) aflags; + + fh = call_host (SEMIHOSTING_SYS_OPEN, block); + + /* Return a user file descriptor or an error. */ + if (fh >= 0) + { + openfiles[fd].handle = fh; + openfiles[fd].pos = 0; + return fd; + } + else + { + return error (fh); + } +} + +int +_open (const char* path, int flags, ...) +{ + return _swiopen (path, flags); +} + +/* fh, is a valid internal file handle. */ +int +_swiclose (int fh) +{ + return checkerror (call_host (SEMIHOSTING_SYS_CLOSE, &fh)); +} + +/* fd, is a user file descriptor. */ +int +_close (int fd) +{ + int res; + struct fdent *pfd; + + pfd = findslot (fd); + if (pfd == NULL) + { + errno = EBADF; + return -1; + } + + /* Handle stderr == stdout. */ + if ((fd == 1 || fd == 2) && (openfiles[1].handle == openfiles[2].handle)) + { + pfd->handle = -1; + return 0; + } + + /* Attempt to close the handle. */ + res = _swiclose (pfd->handle); + + /* Reclaim handle? */ + if (res == 0) + { + pfd->handle = -1; + } + + return res; +} + +int __attribute__((weak)) +_getpid (int n __attribute__ ((unused))) +{ + return 1; +} + +int +_swistat (int fd, struct stat* st) +{ + struct fdent *pfd; + int res; + + pfd = findslot (fd); + if (pfd == NULL) + { + errno = EBADF; + return -1; + } + + /* Always assume a character device, + with 1024 byte blocks. */ + st->st_mode |= S_IFCHR; + st->st_blksize = 1024; + res = checkerror (call_host (SEMIHOSTING_SYS_FLEN, &pfd->handle)); + if (res == -1) + { + return -1; + } + + /* Return the file size. */ + st->st_size = res; + return 0; +} + +int __attribute__((weak)) +_fstat (int fd, struct stat* st) +{ + memset (st, 0, sizeof(*st)); + return _swistat (fd, st); +} + +int __attribute__((weak)) +_stat (const char*fname, struct stat *st) +{ + int fd, res; + memset (st, 0, sizeof(*st)); + /* The best we can do is try to open the file readonly. + If it exists, then we can guess a few things about it. */ + if ((fd = _open (fname, O_RDONLY)) == -1) + { + return -1; + } + st->st_mode |= S_IFREG | S_IREAD; + res = _swistat (fd, st); + /* Not interested in the error. */ + _close (fd); + return res; +} + +int __attribute__((weak)) +_link (void) +{ + errno = ENOSYS; + return -1; +} + +int +_unlink (const char* path) +{ + int res; + uint32_t block[2]; + block[0] = (uint32_t) path; + block[1] = strlen (path); + res = call_host (SEMIHOSTING_SYS_REMOVE, block); + + if (res == -1) + { + return error (res); + } + return 0; +} + +int +_gettimeofday (struct timeval* tp, void* tzvp) +{ + struct timezone* tzp = tzvp; + if (tp) + { + /* Ask the host for the seconds since the Unix epoch. */ + tp->tv_sec = call_host (SEMIHOSTING_SYS_TIME, NULL); + tp->tv_usec = 0; + } + + /* Return fixed data for the timezone. */ + if (tzp) + { + tzp->tz_minuteswest = 0; + tzp->tz_dsttime = 0; + } + + return 0; +} + +/* Return a clock that ticks at 100Hz. */ +clock_t +_clock (void) +{ + clock_t timeval; + + timeval = (clock_t) call_host (SEMIHOSTING_SYS_CLOCK, NULL); + return timeval; +} + +/* Return a clock that ticks at 100Hz. */ +clock_t +_times (struct tms* tp) +{ + clock_t timeval = _clock (); + + if (tp) + { + tp->tms_utime = timeval; /* user time */ + tp->tms_stime = 0; /* system time */ + tp->tms_cutime = 0; /* user time, children */ + tp->tms_cstime = 0; /* system time, children */ + } + + return timeval; +} + +int +_isatty (int fd) +{ + struct fdent *pfd; + int tty; + + pfd = findslot (fd); + if (pfd == NULL) + { + errno = EBADF; + return 0; + } + + tty = call_host (SEMIHOSTING_SYS_ISTTY, &pfd->handle); + + if (tty == 1) + { + return 1; + } + + errno = get_errno (); + return 0; +} + +int +_system (const char* s) +{ + uint32_t block[2]; + int e; + + /* Hmmm. The ARM debug interface specification doesn't say whether + SYS_SYSTEM does the right thing with a null argument, or assign any + meaning to its return value. Try to do something reasonable.... */ + if (!s) + { + return 1; /* maybe there is a shell available? we can hope. :-P */ + } + block[0] = (uint32_t) s; + block[1] = strlen (s); + e = checkerror (call_host (SEMIHOSTING_SYS_SYSTEM, block)); + if ((e >= 0) && (e < 256)) + { + /* We have to convert e, an exit status to the encoded status of + the command. To avoid hard coding the exit status, we simply + loop until we find the right position. */ + int exit_code; + + for (exit_code = e; e && WEXITSTATUS (e) != exit_code; e <<= 1) + { + continue; + } + } + return e; +} + +int +_rename (const char* oldpath, const char* newpath) +{ + uint32_t block[4]; + block[0] = (uint32_t) oldpath; + block[1] = strlen (oldpath); + block[2] = (uint32_t) newpath; + block[3] = strlen (newpath); + return checkerror (call_host (SEMIHOSTING_SYS_RENAME, block)) ? -1 : 0; +} + +// ---------------------------------------------------------------------------- +// Required by Google Tests + +int +mkdir (const char *path __attribute__((unused)), + mode_t mode __attribute__((unused))) +{ +#if 0 + // always return true + return 0; +#else + errno = ENOSYS; + return -1; +#endif +} + +char * +getcwd (char *buf, size_t size) +{ + // no cwd available via semihosting, so we use the temporary folder + strncpy (buf, "/tmp", size); + return buf; +} + +#endif // defined OS_USE_SEMIHOSTING + +#endif // __STDC_HOSTED__ == 1 diff --git a/stlink-tnc5.cfg b/stlink-tnc5.cfg new file mode 100644 index 0000000..8393a6e --- /dev/null +++ b/stlink-tnc5.cfg @@ -0,0 +1,14 @@ + # This is an STM32L discovery board with a single STM32L152RBT6 chip. +# http://www.st.com/internet/evalboard/product/250990.jsp + +source [find interface/stlink-v2-1.cfg] + +transport select hla_swd + +set WORKAREASIZE 0x4000 +source [find target/stm32l4x.cfg] + +reset_config srst_only +itm port 0 on +tpiu config internal swv uart off 48000000 + diff --git a/stm32l4x.cfg b/stm32l4x.cfg new file mode 100644 index 0000000..296507d --- /dev/null +++ b/stm32l4x.cfg @@ -0,0 +1,111 @@ +# script for stm32l4x family + +# +# stm32l4 devices support both JTAG and SWD transports. +# +source [find target/swj-dp.tcl] +source [find mem_helper.tcl] + +if { [info exists CHIPNAME] } { + set _CHIPNAME $CHIPNAME +} else { + set _CHIPNAME stm32l4x +} + +set _ENDIAN little + +# Work-area is a space in RAM used for flash programming +# Smallest current target has 64kB ram, use 32kB by default to avoid surprises +if { [info exists WORKAREASIZE] } { + set _WORKAREASIZE $WORKAREASIZE +} else { + set _WORKAREASIZE 0x8000 +} + +#jtag scan chain +if { [info exists CPUTAPID] } { + set _CPUTAPID $CPUTAPID +} else { + if { [using_jtag] } { + # See STM Document RM0351 + # Section 44.6.3 - corresponds to Cortex-M4 r0p1 + set _CPUTAPID 0x4ba00477 + } { + set _CPUTAPID 0x2ba01477 + } +} + +swj_newdap $_CHIPNAME cpu -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_CPUTAPID + +if {[using_jtag]} { + jtag newtap $_CHIPNAME bs -irlen 5 +} + +set _TARGETNAME $_CHIPNAME.cpu +target create $_TARGETNAME cortex_m -endian $_ENDIAN -chain-position $_TARGETNAME + +$_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size $_WORKAREASIZE -work-area-backup 0 + +set _FLASHNAME $_CHIPNAME.flash +flash bank $_FLASHNAME stm32l4x 0 0 0 0 $_TARGETNAME + +# Common knowledges tells JTAG speed should be <= F_CPU/6. +# F_CPU after reset is MSI 4MHz, so use F_JTAG = 500 kHz to stay on +# the safe side. +# +# Note that there is a pretty wide band where things are +# more or less stable, see http://openocd.zylin.com/#/c/3366/ +adapter_khz 500 + +adapter_nsrst_delay 100 +if {[using_jtag]} { + jtag_ntrst_delay 100 +} + +reset_config srst_nogate + +if {![using_hla]} { + # if srst is not fitted use SYSRESETREQ to + # perform a soft reset + cortex_m reset_config sysresetreq +} + +$_TARGETNAME configure -event reset-init { + # CPU comes out of reset with MSI_ON | MSI_RDY | MSI Range 6 (4 MHz). + # Use MSI 24 MHz clock, compliant even with VOS == 2. + # 3 WS compliant with VOS == 2 and 24 MHz. + mww 0x40022000 0x00000103 ;# FLASH_ACR = PRFTBE | 3(Latency) + mww 0x40021000 0x00000099 ;# RCC_CR = MSI_ON | MSIRGSEL| MSI Range 10 + # Boost JTAG frequency + adapter_khz 4000 +} + +$_TARGETNAME configure -event reset-start { + # Reset clock is MSI (4 MHz) + adapter_khz 500 +} + +$_TARGETNAME configure -event examine-end { + # DBGMCU_CR |= DBG_STANDBY | DBG_STOP | DBG_SLEEP + mmw 0xE0042004 0x00000007 0 + + # Stop watchdog counters during halt + # DBGMCU_APB1_FZ |= DBG_IWDG_STOP | DBG_WWDG_STOP + mmw 0xE0042008 0x00001800 0 +} + +$_TARGETNAME configure -event trace-config { + # Set TRACE_IOEN; TRACE_MODE is set to async; when using sync + # change this value accordingly to configure trace pins + # assignment + mmw 0xE0042004 0x00000020 0 +} + +$_TARGETNAME configure -event gdb-attach { + halt +} + +$_TARGETNAME configure -event gdb-attach { + reset init +} +