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