kopia lustrzana https://github.com/mobilinkd/NucleoTNC
Add TNC code, openocd configs, etc.
rodzic
5d38c4a1ad
commit
9ecd997cad
|
@ -30,6 +30,8 @@
|
|||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
ARM_Debug/
|
||||
ARM_Release/
|
||||
|
||||
# STM32CubeMX garbage files
|
||||
*.launch
|
||||
|
|
1
.project
1
.project
|
@ -68,5 +68,6 @@
|
|||
<nature>org.eclipse.cdt.core.cnature</nature>
|
||||
<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
|
||||
<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
|
||||
<nature>org.eclipse.cdt.core.ccnature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
* <code>blockSize</code> samples through the filter. <code>pSrc</code> and
|
||||
* <code>pDst</code> points to input and output arrays containing <code>blockSize</code> values.
|
||||
*
|
||||
* \par Algorithm:
|
||||
* The FIR filter algorithm is based upon a sequence of multiply-accumulate (MAC) operations.
|
||||
* Each filter coefficient <code>b[n]</code> is multiplied by a state variable which equals a previous input sample <code>x[n]</code>.
|
||||
* <pre>
|
||||
* y[n] = b[0] * x[n] + b[1] * x[n-1] + b[2] * x[n-2] + ...+ b[numTaps-1] * x[n-numTaps+1]
|
||||
* </pre>
|
||||
* \par
|
||||
* \image html FIR.gif "Finite Impulse Response filter"
|
||||
* \par
|
||||
* <code>pCoeffs</code> points to a coefficient array of size <code>numTaps</code>.
|
||||
* Coefficients are stored in time reversed order.
|
||||
* \par
|
||||
* <pre>
|
||||
* {b[numTaps-1], b[numTaps-2], b[N-2], ..., b[1], b[0]}
|
||||
* </pre>
|
||||
* \par
|
||||
* <code>pState</code> points to a state array of size <code>numTaps + blockSize - 1</code>.
|
||||
* Samples in the state buffer are stored in the following order.
|
||||
* \par
|
||||
* <pre>
|
||||
* {x[n-numTaps+1], x[n-numTaps], x[n-numTaps-1], x[n-numTaps-2]....x[0], x[1], ..., x[blockSize-1]}
|
||||
* </pre>
|
||||
* \par
|
||||
* Note that the length of the state buffer exceeds the length of the coefficient array by <code>blockSize-1</code>.
|
||||
* 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
|
||||
* <pre>
|
||||
*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};
|
||||
* </pre>
|
||||
*
|
||||
* where <code>numTaps</code> is the number of filter coefficients in the filter; <code>pState</code> is the address of the state buffer;
|
||||
* <code>pCoeffs</code> 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
|
||||
*/
|
|
@ -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.
|
||||
*
|
||||
* <b>Description:</b>
|
||||
* \par
|
||||
* <code>pCoeffs</code> points to the array of filter coefficients stored in time reversed order:
|
||||
* <pre>
|
||||
* {b[numTaps-1], b[numTaps-2], b[N-2], ..., b[1], b[0]}
|
||||
* </pre>
|
||||
* \par
|
||||
* <code>pState</code> points to the array of state variables.
|
||||
* <code>pState</code> is of length <code>numTaps+blockSize-1</code> samples, where <code>blockSize</code> is the number of input samples processed by each call to <code>arm_fir_f32()</code>.
|
||||
*/
|
||||
|
||||
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
|
||||
*/
|
|
@ -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(;;)
|
||||
{
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
// Copyright 2015 Robert C. Riggs <rob@pangalactic.org>
|
||||
// All rights reserved.
|
||||
|
||||
#ifndef MOBILINKD__TNC__AFSK_MODULATOR_HPP_
|
||||
#define MOBILINKD__TNC__AFSK_MODULATOR_HPP_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "PTT.hpp"
|
||||
#include "Log.h"
|
||||
|
||||
#include "stm32l4xx_hal.h"
|
||||
#include "cmsis_os.h"
|
||||
#include "main.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
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<uint16_t>(256, v);
|
||||
v = std::min<uint16_t>(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_
|
|
@ -0,0 +1,101 @@
|
|||
// Copyright 2016 Rob Riggs <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#include "AFSKTestTone.hpp"
|
||||
#include "ModulatorTask.hpp"
|
||||
|
||||
void startAfskToneTask(void const* arg)
|
||||
{
|
||||
using mobilinkd::tnc::AFSKTestTone;
|
||||
|
||||
auto test = static_cast<const AFSKTestTone*>(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
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright 2016 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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_
|
|
@ -0,0 +1,61 @@
|
|||
#ifndef MOBILINKD__AGC_H_
|
||||
#define MOBILINKD__AGC_H_
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace mobilinkd { namespace libafsk {
|
||||
|
||||
template <typename T>
|
||||
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<double> AGC;
|
||||
typedef BaseAGC<float> FastAGC;
|
||||
|
||||
}} // mobilinkd::libafsk
|
||||
|
||||
#endif // MOBILINKD__AGC_H_
|
||||
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2017 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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
|
|
@ -0,0 +1,136 @@
|
|||
// Copyright 2015 Mobilinkd LLC <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#ifndef MOBILINKD__AFSK_DEMODULATOR_HPP_
|
||||
#define MOBILINKD__AFSK_DEMODULATOR_HPP_
|
||||
|
||||
#include <arm_math.h>
|
||||
#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 <algorithm>
|
||||
#include <functional>
|
||||
|
||||
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<audio::ADC_BUFFER_SIZE, 9> emphasis_filter_type;
|
||||
// typedef IirFilter<audio::ADC_BUFFER_SIZE, 2> emphasis_filter_type;
|
||||
|
||||
struct Demodulator {
|
||||
|
||||
typedef float float_type;
|
||||
|
||||
typedef std::function<float_type(float_type)> filter_function_type;
|
||||
|
||||
static const size_t SYMBOL_RATE = 1200;
|
||||
static const size_t BUFFER_SIZE = 330;
|
||||
|
||||
typedef BaseDigitalPLL<float_type> DPLL;
|
||||
|
||||
size_t sample_rate_;
|
||||
emphasis_filter_type& audio_filter_;
|
||||
libafsk::BaseAGC<float_type> agc_;
|
||||
libafsk::BlockHysteresis<bool, audio::ADC_BUFFER_SIZE> input_comparator_;
|
||||
libafsk::FixedDelayLine<40> delay_line_;
|
||||
IirFilter<8> correlator_filter_;
|
||||
libafsk::BaseHysteresis<float_type> 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_
|
|
@ -0,0 +1,962 @@
|
|||
// Copyright 2015 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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 <algorithm>
|
||||
#include <numeric>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <atomic>
|
||||
|
||||
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<ADC_BUFFER_SIZE, FILTER_TAP_NUM> 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<TWIST_SAMPLE_SIZE, SAMPLE_RATE> gf1200(1200.0);
|
||||
GoertzelFilter<TWIST_SAMPLE_SIZE, SAMPLE_RATE> 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<TWIST_SAMPLE_SIZE, SAMPLE_RATE> gf1200(1200.0);
|
||||
GoertzelFilter<TWIST_SAMPLE_SIZE, SAMPLE_RATE> 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<TWIST_SAMPLE_SIZE, 26400> gf700(700.0);
|
||||
GoertzelFilter<TWIST_SAMPLE_SIZE, 26400> gf1200(1200.0);
|
||||
GoertzelFilter<TWIST_SAMPLE_SIZE, 26400> gf1700(1700.0);
|
||||
GoertzelFilter<TWIST_SAMPLE_SIZE, 26400> gf2200(2200.0);
|
||||
GoertzelFilter<TWIST_SAMPLE_SIZE, 26400> 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<TWIST_SAMPLE_SIZE, 26400> gf700(700.0);
|
||||
GoertzelFilter<TWIST_SAMPLE_SIZE, 26400> gf1200(1200.0);
|
||||
GoertzelFilter<TWIST_SAMPLE_SIZE, 26400> gf1700(1700.0);
|
||||
GoertzelFilter<TWIST_SAMPLE_SIZE, 26400> gf2200(2200.0);
|
||||
GoertzelFilter<TWIST_SAMPLE_SIZE, 26400> 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
|
|
@ -0,0 +1,139 @@
|
|||
// Copyright 2015 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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 <tuple>
|
||||
#include <atomic>
|
||||
|
||||
#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<uint16_t, uint16_t, uint16_t, uint16_t> 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_
|
|
@ -0,0 +1,196 @@
|
|||
// Copyright 2017 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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 <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <cstdint>
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <Log.h>
|
||||
|
||||
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<int16_t, 128> 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<int16_t, int16_t> 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
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright 2015 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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_
|
|
@ -0,0 +1,79 @@
|
|||
#ifndef MOBILINKD_DELAY_LINE_H_
|
||||
#define MOBILINKD_DELAY_LINE_H_
|
||||
|
||||
#include <vector>
|
||||
#include <cstdlib>
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
namespace mobilinkd { namespace libafsk {
|
||||
|
||||
struct DelayLine {
|
||||
|
||||
size_t length_;
|
||||
std::vector<uint8_t> 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 <size_t N>
|
||||
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 <size_t BLOCK_SIZE, size_t DELAY_BITS>
|
||||
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_
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright 2017 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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<Digipeater*>(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<IoFrame*>(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
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright 2017 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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_ */
|
|
@ -0,0 +1,87 @@
|
|||
// Copyright 2017 Rob Riggs <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
|
||||
#ifndef MOBILINKD__TNC__DIGIPEATER_HPP_
|
||||
#define MOBILINKD__TNC__DIGIPEATER_HPP_
|
||||
|
||||
#include <cmsis_os.h>
|
||||
|
||||
#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_
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright 2017 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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
|
|
@ -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 <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
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 <typename FloatType>
|
||||
struct PLLResult {
|
||||
FloatType jitter;
|
||||
bool sample;
|
||||
bool locked;
|
||||
};
|
||||
|
||||
} // pll
|
||||
|
||||
template <typename T>
|
||||
struct BaseDigitalPLL {
|
||||
|
||||
static const size_t N = 16;
|
||||
|
||||
typedef T float_type;
|
||||
typedef pll::PLLResult<float_type> 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<float_type> 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<double> DigitalPLL;
|
||||
typedef BaseDigitalPLL<float> FastDigitalPLL;
|
||||
|
||||
}} // mobilinkd::tnc
|
||||
|
||||
#endif // MOBILINKD__DIGITAL_PLL_H_
|
||||
|
|
@ -0,0 +1,460 @@
|
|||
#include "Filter.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
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;
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2015 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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_
|
|
@ -0,0 +1,408 @@
|
|||
// Copyright 2015 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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_
|
|
@ -0,0 +1,92 @@
|
|||
// Copyright 2015 Rob Riggs <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#ifndef MOBILINKD__TNC__FIR_FILTER_H_
|
||||
#define MOBILINKD__TNC__FIR_FILTER_H_
|
||||
|
||||
#include "Filter.hpp"
|
||||
#include <AudioLevel.hpp>
|
||||
|
||||
#include "arm_math.h"
|
||||
|
||||
#include <vector>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace mobilinkd { namespace tnc {
|
||||
|
||||
template <size_t N>
|
||||
struct TFirCoefficients {
|
||||
float taps[N];
|
||||
};
|
||||
|
||||
struct FirCoefficients {
|
||||
size_t size;
|
||||
const float* taps;
|
||||
|
||||
template <size_t N>
|
||||
FirCoefficients(const TFirCoefficients<N>& c)
|
||||
: size(N), taps(c.taps)
|
||||
{}
|
||||
};
|
||||
|
||||
template <size_t BLOCK_SIZE, size_t FILTER_SIZE>
|
||||
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_
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright 2017 Mobilinkd LLC <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#ifndef MOBILINKD__TNC__GPIO_HPP_
|
||||
#define MOBILINKD__TNC__GPIO_HPP_
|
||||
|
||||
#include "main.h"
|
||||
#include "stm32l4xx_hal.h"
|
||||
#include <cstdint>
|
||||
|
||||
namespace mobilinkd { namespace tnc {
|
||||
|
||||
template <uint32_t BANK, uint16_t PIN>
|
||||
struct GPIO {
|
||||
static void on() {
|
||||
HAL_GPIO_WritePin(reinterpret_cast<GPIO_TypeDef*>(BANK), PIN, GPIO_PIN_SET);
|
||||
}
|
||||
static void off() {
|
||||
HAL_GPIO_WritePin(reinterpret_cast<GPIO_TypeDef*>(BANK), PIN, GPIO_PIN_RESET);
|
||||
}
|
||||
static void toggle() {
|
||||
HAL_GPIO_TogglePin(reinterpret_cast<GPIO_TypeDef*>(BANK), PIN);
|
||||
}
|
||||
static bool get() {
|
||||
return HAL_GPIO_ReadPin(reinterpret_cast<GPIO_TypeDef*>(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_
|
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,138 @@
|
|||
// Copyright 2017 Rob Riggs <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#ifndef MOBILINKD__TNC__GOERTZEL_FILTER_HPP_
|
||||
#define MOBILINKD__TNC__GOERTZEL_FILTER_HPP_
|
||||
|
||||
#include <arm_math.h>
|
||||
#include <complex>
|
||||
|
||||
namespace mobilinkd { namespace tnc {
|
||||
|
||||
extern const float WINDOW[];
|
||||
|
||||
template <uint32_t SAMPLES, uint32_t SAMPLE_RATE>
|
||||
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 <uint32_t SAMPLES, uint32_t SAMPLE_RATE>
|
||||
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<SAMPLES, SAMPLE_RATE> make(float freq)
|
||||
{
|
||||
return GoertzelFilter<SAMPLES, SAMPLE_RATE>(freq, window_);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
// Complex Goertzel
|
||||
// Based on https://dsp.stackexchange.com/a/23946/36581
|
||||
template<typename Float>
|
||||
struct Goertzel
|
||||
{
|
||||
typedef Goertzel<Float> type;
|
||||
typedef std::complex<Float> 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 <typename Container>
|
||||
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<float> FloatGoertzel;
|
||||
|
||||
}} // mobilinkd::tnc
|
||||
|
||||
#endif // MOBILINKD__TNC__GOERTZEL_FILTER_HPP_
|
|
@ -0,0 +1,232 @@
|
|||
// Copyright 2015 Mobilinkd LLC <rob@mobilinkd.com>
|
||||
// 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 <cmsis_os.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
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_
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2016 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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
|
||||
|
||||
|
|
@ -0,0 +1,328 @@
|
|||
// Copyright 2015 Mobilinkd LLC <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#ifndef MOBILINKD___HDLC_DECODER_HPP_
|
||||
#define MOBILINKD___HDLC_DECODER_HPP_
|
||||
|
||||
#include "HdlcFrame.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
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_
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright 2015 Mobilinkd LLC <rob@mobilinkd.com>
|
||||
// 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
|
|
@ -0,0 +1,207 @@
|
|||
// Copyright 2015 Mobilinkd LLC <rob@mobilinkd.com>
|
||||
// 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 <Log.h>
|
||||
#include "SegmentedBuffer.hpp"
|
||||
|
||||
#include <boost/intrusive/list.hpp>
|
||||
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
|
||||
namespace mobilinkd { namespace tnc { namespace hdlc {
|
||||
|
||||
|
||||
using boost::intrusive::list_base_hook;
|
||||
using boost::intrusive::list;
|
||||
using boost::intrusive::constant_time_size;
|
||||
|
||||
template <typename POOL, POOL* allocator>
|
||||
class Frame : public list_base_hook<>
|
||||
{
|
||||
public:
|
||||
typedef POOL pool_type;
|
||||
typedef buffer::SegmentedBuffer<POOL, allocator> 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 <typename Frame>
|
||||
class FramePool
|
||||
{
|
||||
public:
|
||||
typedef Frame frame_type;
|
||||
typedef list<frame_type, constant_time_size<false>> 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<FrameSegmentPool, &frameSegmentPool> IoFrame;
|
||||
typedef FramePool<IoFrame> 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_
|
|
@ -0,0 +1,110 @@
|
|||
#ifndef MOBILINKD__HYSTERESIS_H_
|
||||
#define MOBILINKD__HYSTERESIS_H_
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace mobilinkd { namespace libafsk {
|
||||
|
||||
template <typename T>
|
||||
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<double> Hysteresis;
|
||||
typedef BaseHysteresis<float> FastHysteresis;
|
||||
|
||||
template <typename T, size_t N>
|
||||
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 <bool, size_t N>
|
||||
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_
|
||||
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
// Copyright 2017 Rob Riggs <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#include <AudioLevel.hpp>
|
||||
#include <Log.h>
|
||||
#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<unsigned int>(cmd));
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
using mobilinkd::tnc::hdlc::IoFrame;
|
||||
|
||||
auto frame = static_cast<IoFrame*>(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<uint32_t>(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<uint32_t>(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
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright 2017 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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_ */
|
|
@ -0,0 +1,93 @@
|
|||
// Copyright 2015 Rob Riggs <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#ifndef MOBILINKD__TNC__IIR_FILTER_H_
|
||||
#define MOBILINKD__TNC__IIR_FILTER_H_
|
||||
|
||||
#include <cstring>
|
||||
#include <cstddef>
|
||||
|
||||
namespace mobilinkd { namespace tnc {
|
||||
|
||||
template <size_t N>
|
||||
struct TIirCoefficients {
|
||||
float b[N];
|
||||
float a[N];
|
||||
};
|
||||
|
||||
struct IirCoefficients {
|
||||
size_t size;
|
||||
const float* b;
|
||||
const float* a;
|
||||
|
||||
template <size_t N>
|
||||
IirCoefficients(const TIirCoefficients<N>& c)
|
||||
: size(N), b(c.b), a(c.b)
|
||||
{}
|
||||
};
|
||||
|
||||
template <size_t N>
|
||||
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 <size_t M>
|
||||
IirFilter(TIirCoefficients<M> 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_
|
||||
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright 2016 Rob Riggs <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#include <Log.h>
|
||||
#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
|
|
@ -0,0 +1,259 @@
|
|||
// Copyright 2015 Mobilinkd LLC <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#ifndef MOBILINKD__KISS_HPP_
|
||||
#define MOBILINKD__KISS_HPP_
|
||||
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
#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_
|
|
@ -0,0 +1,516 @@
|
|||
// Copyright 2015 Rob Riggs <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#include "KissHardware.hpp"
|
||||
#include "PortInterface.hpp"
|
||||
#include "AudioInput.hpp"
|
||||
#include "AudioLevel.hpp"
|
||||
#include "AFSKTestTone.hpp"
|
||||
#include "IOEventTask.h"
|
||||
#include <ModulatorTask.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
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<Hardware>();
|
||||
|
||||
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<uint8_t*>(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<uint8_t*>(static_cast<const uint8_t*>(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
|
||||
|
|
@ -0,0 +1,355 @@
|
|||
// Copyright 2015 Mobilinkd LLC <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#ifndef MOBILINKD__TNC__KISS_HARDWARE_HPP_
|
||||
#define MOBILINKD__TNC__KISS_HARDWARE_HPP_
|
||||
|
||||
#include <Log.h>
|
||||
#include "HdlcFrame.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
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 <typename T>
|
||||
static bool load(T& t) {
|
||||
return load(&t, sizeof(T));
|
||||
}
|
||||
|
||||
static bool store(const void* ptr, size_t len);
|
||||
|
||||
template <typename T>
|
||||
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_
|
|
@ -0,0 +1,2 @@
|
|||
// Copyright 2015 Mobilinkd LLC <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
|
@ -0,0 +1,85 @@
|
|||
// Copyright 2017 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
// Copyright 2017 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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_ */
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright 2015 Rob RIggs <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#include <Log.h>
|
||||
#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
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright 2015 Mobilinkd LLC <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#ifndef MOBILINKD__TNC_LOG_HPP_
|
||||
#define MOBILINKD__TNC_LOG_HPP_
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdarg>
|
||||
#include <cstdlib>
|
||||
|
||||
extern "C" {
|
||||
#else
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#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_
|
|
@ -0,0 +1,85 @@
|
|||
// Copyright 2015 Mobilinkd LLC <rob@mobilinkd.com>
|
||||
// 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();
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright 2015 Mobilinkd LLC <rob@mobilinkd.com>
|
||||
// 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_
|
|
@ -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_
|
||||
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright 2016 Rob Riggs <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#include "NullPort.hpp"
|
||||
|
||||
namespace mobilinkd { namespace tnc {
|
||||
|
||||
NullPort* getNullPort()
|
||||
{
|
||||
static NullPort instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
}} // mobilinkd::tnc
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright 2016 Rob Riggs <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#ifndef MOBILINKD__TNC__NULL_PORT_HPP_
|
||||
#define MOBILINKD__TNC__NULL_PORT_HPP_
|
||||
|
||||
#include "PortInterface.hpp"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
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<bool> open_{false};
|
||||
};
|
||||
|
||||
NullPort* getNullPort();
|
||||
|
||||
}} // mobilinkd::tnc
|
||||
|
||||
#endif // MOBILINKD__TNC__NULL_PORT_HPP_
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright 2015 Robert C. Riggs <rob@pangalactic.org>
|
||||
// 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_
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright 2016 Rob Riggs <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#include "PortInterface.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright 2016 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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_ */
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright 2016 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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_
|
|
@ -0,0 +1,144 @@
|
|||
// Copyright 2015 Mobilinkd LLC <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#ifndef MOBILINKD__SEGMENTED_BUFFER_HPP_
|
||||
#define MOBILINKD__SEGMENTED_BUFFER_HPP_
|
||||
|
||||
#include "memory.hpp"
|
||||
|
||||
#include <boost/iterator/iterator_facade.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace mobilinkd { namespace tnc { namespace buffer {
|
||||
|
||||
|
||||
using boost::intrusive::list_base_hook;
|
||||
using boost::intrusive::list;
|
||||
using boost::intrusive::constant_time_size;
|
||||
|
||||
template <uint16_t SIZE, uint16_t CHUNK_SIZE = 256>
|
||||
struct Pool {
|
||||
typedef memory::chunk<CHUNK_SIZE> chunk_type;
|
||||
typedef list<chunk_type, constant_time_size<false> > 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 <typename POOL, POOL* allocator> struct SegmentedBufferIterator;
|
||||
|
||||
template <typename POOL, POOL* allocator>
|
||||
struct SegmentedBuffer {
|
||||
typedef uint8_t value_type;
|
||||
typedef value_type* pointer;
|
||||
typedef value_type& reference;
|
||||
|
||||
typedef SegmentedBufferIterator<POOL, allocator> iterator;
|
||||
typedef const SegmentedBufferIterator<POOL, allocator> 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 <typename POOL, POOL* allocator>
|
||||
struct SegmentedBufferIterator : public boost::iterator_facade<
|
||||
SegmentedBufferIterator<POOL, allocator>, 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_
|
|
@ -0,0 +1,415 @@
|
|||
// Copyright 2016 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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 <cstring>
|
||||
#include <cstdlib>
|
||||
#include <cstdint>
|
||||
#include <atomic>
|
||||
|
||||
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<const SerialPort*>(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<unsigned char*>(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<uint32_t>(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<int>(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);
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright 2018 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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_ */
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright 2016 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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_
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright 2015 Mobilinkd LLC <rob@mobilinkd.com>
|
||||
// All rights reserved.
|
||||
|
||||
#ifndef MOBILINKD__MEMORY_HPP_
|
||||
#define MOBILINKD__MEMORY_HPP_
|
||||
|
||||
#include "cmsis_os.h"
|
||||
|
||||
#include <boost/intrusive/list.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace mobilinkd { namespace tnc { namespace memory {
|
||||
|
||||
using boost::intrusive::list_base_hook;
|
||||
using boost::intrusive::list;
|
||||
using boost::intrusive::constant_time_size;
|
||||
|
||||
template <uint16_t BLOCK_SIZE = 256>
|
||||
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 <uint16_t SIZE, uint16_t CHUNK_SIZE=256>
|
||||
struct Pool {
|
||||
typedef chunk<CHUNK_SIZE> chunk_type;
|
||||
typedef list<chunk_type, constant_time_size<false> > 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_
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright 2017 Rob Riggs <rob@mobilinkd.com>
|
||||
// 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_
|
|
@ -0,0 +1,48 @@
|
|||
//
|
||||
// This file is part of the µOS++ III distribution.
|
||||
// Copyright (c) 2014 Liviu Ionescu.
|
||||
//
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// This file is part of the µOS++ III distribution.
|
||||
// Copyright (c) 2014 Liviu Ionescu.
|
||||
//
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <errno.h>
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,14 @@
|
|||
# This is an STM32L discovery board with a single STM32L152RBT6 chip.
|
||||
# http://www.st.com/internet/evalboard/product/250990.jsp
|
||||
|
||||
source [find interface/stlink-v2-1.cfg]
|
||||
|
||||
transport select hla_swd
|
||||
|
||||
set WORKAREASIZE 0x4000
|
||||
source [find target/stm32l4x.cfg]
|
||||
|
||||
reset_config srst_only
|
||||
itm port 0 on
|
||||
tpiu config internal swv uart off 48000000
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
# script for stm32l4x family
|
||||
|
||||
#
|
||||
# stm32l4 devices support both JTAG and SWD transports.
|
||||
#
|
||||
source [find target/swj-dp.tcl]
|
||||
source [find mem_helper.tcl]
|
||||
|
||||
if { [info exists CHIPNAME] } {
|
||||
set _CHIPNAME $CHIPNAME
|
||||
} else {
|
||||
set _CHIPNAME stm32l4x
|
||||
}
|
||||
|
||||
set _ENDIAN little
|
||||
|
||||
# Work-area is a space in RAM used for flash programming
|
||||
# Smallest current target has 64kB ram, use 32kB by default to avoid surprises
|
||||
if { [info exists WORKAREASIZE] } {
|
||||
set _WORKAREASIZE $WORKAREASIZE
|
||||
} else {
|
||||
set _WORKAREASIZE 0x8000
|
||||
}
|
||||
|
||||
#jtag scan chain
|
||||
if { [info exists CPUTAPID] } {
|
||||
set _CPUTAPID $CPUTAPID
|
||||
} else {
|
||||
if { [using_jtag] } {
|
||||
# See STM Document RM0351
|
||||
# Section 44.6.3 - corresponds to Cortex-M4 r0p1
|
||||
set _CPUTAPID 0x4ba00477
|
||||
} {
|
||||
set _CPUTAPID 0x2ba01477
|
||||
}
|
||||
}
|
||||
|
||||
swj_newdap $_CHIPNAME cpu -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_CPUTAPID
|
||||
|
||||
if {[using_jtag]} {
|
||||
jtag newtap $_CHIPNAME bs -irlen 5
|
||||
}
|
||||
|
||||
set _TARGETNAME $_CHIPNAME.cpu
|
||||
target create $_TARGETNAME cortex_m -endian $_ENDIAN -chain-position $_TARGETNAME
|
||||
|
||||
$_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size $_WORKAREASIZE -work-area-backup 0
|
||||
|
||||
set _FLASHNAME $_CHIPNAME.flash
|
||||
flash bank $_FLASHNAME stm32l4x 0 0 0 0 $_TARGETNAME
|
||||
|
||||
# Common knowledges tells JTAG speed should be <= F_CPU/6.
|
||||
# F_CPU after reset is MSI 4MHz, so use F_JTAG = 500 kHz to stay on
|
||||
# the safe side.
|
||||
#
|
||||
# Note that there is a pretty wide band where things are
|
||||
# more or less stable, see http://openocd.zylin.com/#/c/3366/
|
||||
adapter_khz 500
|
||||
|
||||
adapter_nsrst_delay 100
|
||||
if {[using_jtag]} {
|
||||
jtag_ntrst_delay 100
|
||||
}
|
||||
|
||||
reset_config srst_nogate
|
||||
|
||||
if {![using_hla]} {
|
||||
# if srst is not fitted use SYSRESETREQ to
|
||||
# perform a soft reset
|
||||
cortex_m reset_config sysresetreq
|
||||
}
|
||||
|
||||
$_TARGETNAME configure -event reset-init {
|
||||
# CPU comes out of reset with MSI_ON | MSI_RDY | MSI Range 6 (4 MHz).
|
||||
# Use MSI 24 MHz clock, compliant even with VOS == 2.
|
||||
# 3 WS compliant with VOS == 2 and 24 MHz.
|
||||
mww 0x40022000 0x00000103 ;# FLASH_ACR = PRFTBE | 3(Latency)
|
||||
mww 0x40021000 0x00000099 ;# RCC_CR = MSI_ON | MSIRGSEL| MSI Range 10
|
||||
# Boost JTAG frequency
|
||||
adapter_khz 4000
|
||||
}
|
||||
|
||||
$_TARGETNAME configure -event reset-start {
|
||||
# Reset clock is MSI (4 MHz)
|
||||
adapter_khz 500
|
||||
}
|
||||
|
||||
$_TARGETNAME configure -event examine-end {
|
||||
# DBGMCU_CR |= DBG_STANDBY | DBG_STOP | DBG_SLEEP
|
||||
mmw 0xE0042004 0x00000007 0
|
||||
|
||||
# Stop watchdog counters during halt
|
||||
# DBGMCU_APB1_FZ |= DBG_IWDG_STOP | DBG_WWDG_STOP
|
||||
mmw 0xE0042008 0x00001800 0
|
||||
}
|
||||
|
||||
$_TARGETNAME configure -event trace-config {
|
||||
# Set TRACE_IOEN; TRACE_MODE is set to async; when using sync
|
||||
# change this value accordingly to configure trace pins
|
||||
# assignment
|
||||
mmw 0xE0042004 0x00000020 0
|
||||
}
|
||||
|
||||
$_TARGETNAME configure -event gdb-attach {
|
||||
halt
|
||||
}
|
||||
|
||||
$_TARGETNAME configure -event gdb-attach {
|
||||
reset init
|
||||
}
|
||||
|
Ładowanie…
Reference in New Issue