Add TNC code, openocd configs, etc.

m17
Rob Riggs 2018-07-29 21:34:53 -05:00
rodzic 5d38c4a1ad
commit 9ecd997cad
69 zmienionych plików z 11220 dodań i 2 usunięć

2
.gitignore vendored
Wyświetl plik

@ -30,6 +30,8 @@
*.exe
*.out
*.app
ARM_Debug/
ARM_Release/
# STM32CubeMX garbage files
*.launch

Wyświetl plik

@ -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>

Wyświetl plik

@ -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

997
Src/arm_fir_f32.c 100644
Wyświetl plik

@ -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
*/

Wyświetl plik

@ -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
*/

Wyświetl plik

@ -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(;;)
{

Wyświetl plik

@ -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 */

Wyświetl plik

@ -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_

Wyświetl plik

@ -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

Wyświetl plik

@ -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_

61
TNC/AGC.hpp 100644
Wyświetl plik

@ -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_

Wyświetl plik

@ -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

Wyświetl plik

@ -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_

962
TNC/AudioInput.cpp 100644
Wyświetl plik

@ -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

139
TNC/AudioInput.hpp 100644
Wyświetl plik

@ -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_

196
TNC/AudioLevel.cpp 100644
Wyświetl plik

@ -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

34
TNC/AudioLevel.hpp 100644
Wyświetl plik

@ -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_

79
TNC/DelayLine.hpp 100644
Wyświetl plik

@ -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_

56
TNC/Digipeater.cpp 100644
Wyświetl plik

@ -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

20
TNC/Digipeater.h 100644
Wyświetl plik

@ -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_ */

87
TNC/Digipeater.hpp 100644
Wyświetl plik

@ -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_

32
TNC/DigitalPLL.cpp 100644
Wyświetl plik

@ -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

116
TNC/DigitalPLL.hpp 100644
Wyświetl plik

@ -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_

460
TNC/Filter.cpp 100644
Wyświetl plik

@ -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;
}

42
TNC/Filter.h 100644
Wyświetl plik

@ -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

16
TNC/Filter.hpp 100644
Wyświetl plik

@ -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_

Wyświetl plik

@ -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_

92
TNC/FirFilter.hpp 100644
Wyświetl plik

@ -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_

48
TNC/GPIO.hpp 100644
Wyświetl plik

@ -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_

1336
TNC/Goertzel.cpp 100644

Plik diff jest za duży Load Diff

138
TNC/Goertzel.h 100644
Wyświetl plik

@ -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_

232
TNC/HDLCEncoder.hpp 100644
Wyświetl plik

@ -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_

Wyświetl plik

@ -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

328
TNC/HdlcDecoder.hpp 100644
Wyświetl plik

@ -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_

20
TNC/HdlcFrame.cpp 100644
Wyświetl plik

@ -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

207
TNC/HdlcFrame.hpp 100644
Wyświetl plik

@ -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_

110
TNC/Hysteresis.hpp 100644
Wyświetl plik

@ -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_

132
TNC/IOEventTask.cpp 100644
Wyświetl plik

@ -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

32
TNC/IOEventTask.h 100644
Wyświetl plik

@ -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_ */

93
TNC/IirFilter.hpp 100644
Wyświetl plik

@ -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_

71
TNC/Kiss.cpp 100644
Wyświetl plik

@ -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

259
TNC/Kiss.hpp 100644
Wyświetl plik

@ -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_

Wyświetl plik

@ -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

Wyświetl plik

@ -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_

2
TNC/KissTask.cpp 100644
Wyświetl plik

@ -0,0 +1,2 @@
// Copyright 2015 Mobilinkd LLC <rob@mobilinkd.com>
// All rights reserved.

85
TNC/Led.cpp 100644
Wyświetl plik

@ -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

79
TNC/Led.h 100644
Wyświetl plik

@ -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_ */

68
TNC/Log.cpp 100644
Wyświetl plik

@ -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

68
TNC/Log.h 100644
Wyświetl plik

@ -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_

Wyświetl plik

@ -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();
}

Wyświetl plik

@ -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_

32
TNC/NRZI.hpp 100644
Wyświetl plik

@ -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_

15
TNC/NullPort.cpp 100644
Wyświetl plik

@ -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

57
TNC/NullPort.hpp 100644
Wyświetl plik

@ -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_

44
TNC/PTT.hpp 100644
Wyświetl plik

@ -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_

Wyświetl plik

@ -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);
}

Wyświetl plik

@ -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_ */

Wyświetl plik

@ -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_

Wyświetl plik

@ -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_

415
TNC/SerialPort.cpp 100644
Wyświetl plik

@ -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);
}

23
TNC/SerialPort.h 100644
Wyświetl plik

@ -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_ */

40
TNC/SerialPort.hpp 100644
Wyświetl plik

@ -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_

74
TNC/memory.hpp 100644
Wyświetl plik

@ -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_

43
TNC/power.h 100644
Wyświetl plik

@ -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_

48
newlib/_exit.c 100644
Wyświetl plik

@ -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);
}
// ----------------------------------------------------------------------------

65
newlib/_sbrk.c 100644
Wyświetl plik

@ -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;
}
// ----------------------------------------------------------------------------

1212
newlib/_syscalls.c 100644

Plik diff jest za duży Load Diff

14
stlink-tnc5.cfg 100644
Wyświetl plik

@ -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

111
stm32l4x.cfg 100644
Wyświetl plik

@ -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
}