merge in branch devel
commit
e164629b38
@ -1,45 +0,0 @@
|
||||
The work in the Hack project is Copyright 2018 Source Foundry Authors and licensed under the MIT License
|
||||
|
||||
The work in the DejaVu project was committed to the public domain.
|
||||
|
||||
Bitstream Vera Sans Mono Copyright 2003 Bitstream Inc. and licensed under the Bitstream Vera License with Reserved Font Names "Bitstream" and "Vera"
|
||||
|
||||
### MIT License
|
||||
|
||||
Copyright (c) 2018 Source Foundry Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
### BITSTREAM VERA LICENSE
|
||||
|
||||
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces.
|
||||
|
||||
The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera".
|
||||
|
||||
This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names.
|
||||
|
||||
The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself.
|
||||
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org.
|
Binary file not shown.
@ -1,66 +0,0 @@
|
||||
UNAME := $(shell uname)
|
||||
|
||||
ifeq ($(UNAME), Linux)
|
||||
RM = rm -f
|
||||
else
|
||||
RM = del
|
||||
endif
|
||||
|
||||
CXX = g++-10
|
||||
CXXFLAGS = --std=c++20 -ggdb -O0 \
|
||||
-Wall -Wextra -pedantic \
|
||||
-Wno-deprecated-copy \
|
||||
-Iserial/include -IMETL/include -IMETL/dependencies/PEGTL/include
|
||||
|
||||
ifeq ($(UNAME), Linux)
|
||||
CXXFLAGS += $(shell wx-config --cxxflags)
|
||||
else
|
||||
CXXFLAGS += -IC:\wx\include -DSTMDSP_WIN32 -Wa,-mbig-obj
|
||||
endif
|
||||
|
||||
ifeq ($(UNAME), Linux)
|
||||
CXXFILES = serial/src/serial.cc \
|
||||
serial/src/impl/unix.cc \
|
||||
serial/src/impl/list_ports/list_ports_linux.cc \
|
||||
$(wildcard *.cpp)
|
||||
else
|
||||
CXXFILES = serial/src/serial.cc \
|
||||
serial/src/impl/win.cc \
|
||||
serial/src/impl/list_ports/list_ports_win.cc \
|
||||
$(wildcard *.cpp)
|
||||
endif
|
||||
OFILES = $(patsubst %.cc, %.o, $(patsubst %.cpp, %.o, $(CXXFILES)))
|
||||
|
||||
ifeq ($(UNAME), Linux)
|
||||
LIBS = $(shell wx-config --libs) -lwx_gtk3u_stc-3.1
|
||||
else
|
||||
LIBS = -lSetupAPI \
|
||||
-LC:\wx\lib\gcc810_x64_dll -lwxbase31u -lwxmsw31u_core -lwxmsw31u_stc
|
||||
endif
|
||||
|
||||
OUTELF = stmdspgui
|
||||
|
||||
ifeq ($(UNAME), Linux)
|
||||
CLEANFILES = $(OUTELF) $(OFILES)
|
||||
else
|
||||
CLEANFILES = $(subst /,\\,$(OUTELF)) $(subst /,\\,$(OFILES))
|
||||
endif
|
||||
|
||||
all: $(OUTELF)
|
||||
|
||||
$(OUTELF): $(OFILES)
|
||||
@echo " CXX " $(OUTELF)
|
||||
@$(CXX) $(CXXFLAGS) $(OFILES) $(LIBS) -o $(OUTELF)
|
||||
|
||||
.cc.o:
|
||||
@echo " CXX " $<
|
||||
@$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||
|
||||
.cpp.o:
|
||||
@echo " CXX " $<
|
||||
@$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||
|
||||
clean:
|
||||
@echo " CLEAN"
|
||||
@$(RM) $(CLEANFILES)
|
||||
|
@ -1,474 +0,0 @@
|
||||
// Digilent Waveforms
|
||||
// Custom signal:
|
||||
// 4096 samples, X from 0 to 4095
|
||||
// sin(PI/8000*X*770)+sin(PI/8000*X*1336)
|
||||
|
||||
#include <cstdint>
|
||||
using float32_t = float;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint16_t numTaps; /**< number of filter coefficients in the filter. */
|
||||
float32_t *pState; /**< points to the state variable array. The array is of length numTaps+blockSize-1. */
|
||||
float32_t *pCoeffs; /**< points to the coefficient array. The array is of length numTaps. */
|
||||
} arm_fir_instance_f32;
|
||||
|
||||
static void arm_fir_f32(const arm_fir_instance_f32 * S, float32_t * pSrc, float32_t * pDst, uint32_t blockSize);
|
||||
|
||||
adcsample_t *process_data(adcsample_t *samples, unsigned int size)
|
||||
{
|
||||
// 1. Define our array sizes (Be sure to set Run > Set buffer size... to below value!)
|
||||
constexpr unsigned int buffer_size = 500;
|
||||
constexpr unsigned int filter_size = 100;
|
||||
|
||||
// 2. Define our filter and the working arrays
|
||||
static float filter[filter_size] = {
|
||||
0.0004387887025183802,0.000832200098857514,0.0015307184662295217,0.0025082006379108486,0.0037734534845577567,0.005298545881839843,0.007011536790726042,0.008794237700486741,0.0104866537299476,0.011898670086639574,0.01282927474471195,0.013091387203266143,0.012539923791204348,0.011099382508792464,0.008786721087738127,0.005724922305741446,0.002144437816471804,-0.0016305355316754216,-0.005210998401135279,-0.00818522521443848,-0.0101701012867913,-0.010865871085906236,-0.01010594970823667,-0.007895785082463622,-0.00443176063435828,-0.00009684904191641966,0.004570418146137933,0.008932018058761999,0.012326603606274784,0.014157852725048914,0.013983789156387687,0.011595484863822138,0.007073052408314296,0.0008096544104778903,-0.006503129471685042,-0.013929880283520692,-0.020382390416343286,-0.024738365522063946,-0.02597487538349127,-0.02330063345756659,-0.016270714607748243,-0.004868040016583383,0.010460249064783475,0.028818398768187533,0.04893049916926163,0.06925561893929223,0.0881379889511425,0.10397551230005074,0.11538733811709222,0.12136089030896859,0.12136089030896859,0.11538733811709222,0.10397551230005074,0.0881379889511425,0.06925561893929223,0.04893049916926163,0.028818398768187533,0.010460249064783475,-0.004868040016583383,-0.016270714607748243,-0.02330063345756659,-0.02597487538349127,-0.024738365522063946,-0.020382390416343286,-0.013929880283520692,-0.006503129471685042,0.0008096544104778903,0.007073052408314296,0.011595484863822138,0.013983789156387687,0.014157852725048914,0.012326603606274784,0.008932018058761999,0.004570418146137933,-0.00009684904191641966,-0.00443176063435828,-0.007895785082463622,-0.01010594970823667,-0.010865871085906236,-0.0101701012867913,-0.00818522521443848,-0.005210998401135279,-0.0016305355316754216,0.002144437816471804,0.005724922305741446,0.008786721087738127,0.011099382508792464,0.012539923791204348,0.013091387203266143,0.01282927474471195,0.011898670086639574,0.0104866537299476,0.008794237700486741,0.007011536790726042,0.005298545881839843,0.0037734534845577567,0.0025082006379108486,0.0015307184662295217,0.000832200098857514,0.0004387887025183802
|
||||
};
|
||||
static float input[buffer_size];
|
||||
static float output[buffer_size];
|
||||
static float working[buffer_size + filter_size];
|
||||
|
||||
// 3. Scale 0-4095 interger sample values to +/- 1.0 floats
|
||||
for (unsigned int i = 0; i < size; i++)
|
||||
input[i] = (samples[i] - 2048) / 2048.f;
|
||||
|
||||
// 4. Compute the FIR
|
||||
arm_fir_instance_f32 fir { filter_size, working, filter };
|
||||
arm_fir_f32(&fir, input, output, size);
|
||||
|
||||
// 5. Convert float results back to 0-4095 range for output
|
||||
for (unsigned int i = 0; i < size; i++)
|
||||
samples[i] = output[i] * 2048.f + 2048;
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
// Below taken from the CMSIS DSP Library (find it on GitHub)
|
||||
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--;
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
Sample *process_data(Samples samples)
|
||||
{
|
||||
constexpr unsigned int size = samples.size();
|
||||
constexpr unsigned int D = 2000;
|
||||
|
||||
float alpha = readalt() / 4095.;
|
||||
|
||||
static Sample output[size];
|
||||
static Sample prev[D]; // prev[0] = output[0 - D]
|
||||
|
||||
// Do calculations with previous output
|
||||
for (unsigned int i = 0; i < D; i++)
|
||||
output[i] = samples[i] + alpha * (prev[i] - 2048);
|
||||
|
||||
// Do calculations with current samples
|
||||
for (unsigned int i = D; i < size; i++)
|
||||
output[i] = samples[i] + alpha * (output[i - D] - 2048);
|
||||
|
||||
// Save outputs for next computation
|
||||
for (unsigned int i = 0; i < D; i++)
|
||||
prev[i] = output[size - (D - i)];
|
||||
|
||||
return output;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,13 +0,0 @@
|
||||
/**
|
||||
* @file main.cpp
|
||||
* @brief Program entry point.
|
||||
*
|
||||
* Written by Clyne Sullivan.
|
||||
*/
|
||||
|
||||
#include "wxapp.hpp"
|
||||
|
||||
#include <wx/app.h>
|
||||
|
||||
wxIMPLEMENT_APP(MainApp);
|
||||
|
@ -1 +0,0 @@
|
||||
Subproject commit cbcca7c83745fedd75afb7a0a27ee5c4112435c2
|
@ -1,208 +0,0 @@
|
||||
/**
|
||||
* @file stmdsp.cpp
|
||||
* @brief Interface for communication with stmdsp device over serial.
|
||||
*
|
||||
* Copyright (C) 2021 Clyne Sullivan
|
||||
*
|
||||
* Distributed under the GNU GPL v3 or later. You should have received a copy of
|
||||
* the GNU General Public License along with this program.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "stmdsp.hpp"
|
||||
|
||||
#include <serial/serial.h>
|
||||
|
||||
namespace stmdsp
|
||||
{
|
||||
std::list<std::string>& scanner::scan()
|
||||
{
|
||||
auto devices = serial::list_ports();
|
||||
for (auto& device : devices) {
|
||||
if (device.hardware_id.find(STMDSP_USB_ID) != std::string::npos)
|
||||
m_available_devices.emplace_front(device.port);
|
||||
}
|
||||
|
||||
return m_available_devices;
|
||||
}
|
||||
|
||||
device::device(const std::string& file) :
|
||||
m_serial(file, 1000000/*230400*/, serial::Timeout::simpleTimeout(50))
|
||||
{
|
||||
if (m_serial.isOpen()) {
|
||||
m_serial.flush();
|
||||
m_serial.write("i");
|
||||
if (auto id = m_serial.read(7); id.starts_with("stmdsp")) {
|
||||
if (id.back() == 'h')
|
||||
m_platform = platform::H7;
|
||||
else if (id.back() == 'l')
|
||||
m_platform = platform::L4;
|
||||
else
|
||||
m_serial.close();
|
||||
} else {
|
||||
m_serial.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void device::continuous_set_buffer_size(unsigned int size) {
|
||||
if (connected()) {
|
||||
m_buffer_size = size;
|
||||
|
||||
uint8_t request[3] = {
|
||||
'B',
|
||||
static_cast<uint8_t>(size),
|
||||
static_cast<uint8_t>(size >> 8)
|
||||
};
|
||||
m_serial.write(request, 3);
|
||||
}
|
||||
}
|
||||
|
||||
void device::set_sample_rate(unsigned int id) {
|
||||
if (connected()) {
|
||||
uint8_t request[2] = {
|
||||
'r',
|
||||
static_cast<uint8_t>(id)
|
||||
};
|
||||
m_serial.write(request, 2);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int device::get_sample_rate() {
|
||||
unsigned char result = 0xFF;
|
||||
|
||||
if (connected()) {
|
||||
uint8_t request[2] = {
|
||||
'r', 0xFF
|
||||
};
|
||||
m_serial.write(request, 2);
|
||||
m_serial.read(&result, 1);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void device::continuous_start() {
|
||||
if (connected())
|
||||
m_serial.write("R");
|
||||
}
|
||||
|
||||
void device::continuous_start_measure() {
|
||||
if (connected())
|
||||
m_serial.write("M");
|
||||
}
|
||||
|
||||
uint32_t device::continuous_start_get_measurement() {
|
||||
uint32_t count = 0;
|
||||
if (connected()) {
|
||||
m_serial.write("m");
|
||||
m_serial.read(reinterpret_cast<uint8_t *>(&count), sizeof(uint32_t));
|
||||
}
|
||||
|
||||
return count / 2;
|
||||
}
|
||||
|
||||
std::vector<adcsample_t> device::continuous_read() {
|
||||
if (connected()) {
|
||||
m_serial.write("s");
|
||||
unsigned char sizebytes[2];
|
||||
m_serial.read(sizebytes, 2);
|
||||
unsigned int size = sizebytes[0] | (sizebytes[1] << 8);
|
||||
if (size > 0) {
|
||||
std::vector<adcsample_t> data (size);
|
||||
unsigned int total = size * sizeof(adcsample_t);
|
||||
unsigned int offset = 0;
|
||||
|
||||
while (total > 512) {
|
||||
m_serial.read(reinterpret_cast<uint8_t *>(&data[0]) + offset, 512);
|
||||
m_serial.write("n");
|
||||
offset += 512;
|
||||
total -= 512;
|
||||
}
|
||||
m_serial.read(reinterpret_cast<uint8_t *>(&data[0]) + offset, total);
|
||||
m_serial.write("n");
|
||||
return data;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<adcsample_t> device::continuous_read_input() {
|
||||
if (connected()) {
|
||||
m_serial.write("t");
|
||||
unsigned char sizebytes[2];
|
||||
m_serial.read(sizebytes, 2);
|
||||
unsigned int size = sizebytes[0] | (sizebytes[1] << 8);
|
||||
if (size > 0) {
|
||||
std::vector<adcsample_t> data (size);
|
||||
unsigned int total = size * sizeof(adcsample_t);
|
||||
unsigned int offset = 0;
|
||||
|
||||
while (total > 512) {
|
||||
m_serial.read(reinterpret_cast<uint8_t *>(&data[0]) + offset, 512);
|
||||
m_serial.write("n");
|
||||
offset += 512;
|
||||
total -= 512;
|
||||
}
|
||||
m_serial.read(reinterpret_cast<uint8_t *>(&data[0]) + offset, total);
|
||||
m_serial.write("n");
|
||||
return data;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void device::continuous_stop() {
|
||||
if (connected())
|
||||
m_serial.write("S");
|
||||
}
|
||||
|
||||
void device::siggen_upload(dacsample_t *buffer, unsigned int size) {
|
||||
if (connected()) {
|
||||
uint8_t request[3] = {
|
||||
'D',
|
||||
static_cast<uint8_t>(size),
|
||||
static_cast<uint8_t>(size >> 8)
|
||||
};
|
||||
m_serial.write(request, 3);
|
||||
|
||||
m_serial.write((uint8_t *)buffer, size * sizeof(dacsample_t));
|
||||
}
|
||||
}
|
||||
|
||||
void device::siggen_start() {
|
||||
if (connected()) {
|
||||
m_is_siggening = true;
|
||||
m_serial.write("W");
|
||||
}
|
||||
}
|
||||
|
||||
void device::siggen_stop() {
|
||||
if (connected()) {
|
||||
m_is_siggening = false;
|
||||
m_serial.write("w");
|
||||
}
|
||||
}
|
||||
|
||||
void device::upload_filter(unsigned char *buffer, size_t size) {
|
||||
if (connected()) {
|
||||
uint8_t request[3] = {
|
||||
'E',
|
||||
static_cast<uint8_t>(size),
|
||||
static_cast<uint8_t>(size >> 8)
|
||||
};
|
||||
m_serial.write(request, 3);
|
||||
|
||||
m_serial.write(buffer, size);
|
||||
}
|
||||
}
|
||||
|
||||
void device::unload_filter() {
|
||||
if (connected())
|
||||
m_serial.write("e");
|
||||
}
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
/**
|
||||
* @file stmdsp.hpp
|
||||
* @brief Interface for communication with stmdsp device over serial.
|
||||
*
|
||||
* Copyright (C) 2021 Clyne Sullivan
|
||||
*
|
||||
* Distributed under the GNU GPL v3 or later. You should have received a copy of
|
||||
* the GNU General Public License along with this program.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef STMDSP_HPP_
|
||||
#define STMDSP_HPP_
|
||||
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <serial/serial.h>
|
||||
#include <string>
|
||||
|
||||
namespace stmdsp
|
||||
{
|
||||
constexpr unsigned int SAMPLES_MAX = 4096;
|
||||
|
||||
class scanner
|
||||
{
|
||||
private:
|
||||
constexpr static const char *STMDSP_USB_ID =
|
||||
#ifndef STMDSP_WIN32
|
||||
"USB VID:PID=0483:5740";
|
||||
#else
|
||||
"USB\\VID_0483&PID_5740";
|
||||
#endif
|
||||
|
||||
public:
|
||||
std::list<std::string>& scan();
|
||||
auto& devices() {
|
||||
return m_available_devices;
|
||||
}
|
||||
|
||||
private:
|
||||
std::list<std::string> m_available_devices;
|
||||
};
|
||||
|
||||
using adcsample_t = uint16_t;
|
||||
using dacsample_t = uint16_t;
|
||||
|
||||
enum class platform {
|
||||
Unknown,
|
||||
H7,
|
||||
L4,
|
||||
G4
|
||||
};
|
||||
|
||||
class device
|
||||
{
|
||||
public:
|
||||
device(const std::string& file);
|
||||
|
||||
~device() {
|
||||
m_serial.close();
|
||||
}
|
||||
|
||||
bool connected() {
|
||||
return m_serial.isOpen();
|
||||
}
|
||||
|
||||
auto get_platform() const { return m_platform; }
|
||||
void continuous_set_buffer_size(unsigned int size);
|
||||
unsigned int get_buffer_size() const { return m_buffer_size; }
|
||||
void set_sample_rate(unsigned int id);
|
||||
unsigned int get_sample_rate();
|
||||
void continuous_start();
|
||||
void continuous_start_measure();
|
||||
uint32_t continuous_start_get_measurement();
|
||||
std::vector<adcsample_t> continuous_read();
|
||||
std::vector<adcsample_t> continuous_read_input();
|
||||
void continuous_stop();
|
||||
|
||||
void siggen_upload(dacsample_t *buffer, unsigned int size);
|
||||
void siggen_start();
|
||||
void siggen_stop();
|
||||
bool is_siggening() const { return m_is_siggening; }
|
||||
|
||||
// buffer is ELF binary
|
||||
void upload_filter(unsigned char *buffer, size_t size);
|
||||
void unload_filter();
|
||||
|
||||
private:
|
||||
serial::Serial m_serial;
|
||||
platform m_platform = platform::Unknown;
|
||||
unsigned int m_buffer_size = SAMPLES_MAX;
|
||||
bool m_is_siggening = false;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // STMDSP_HPP_
|
||||
|
@ -1,29 +0,0 @@
|
||||
/**
|
||||
* 1_convolve_simple.cpp
|
||||
* Written by Clyne Sullivan.
|
||||
*
|
||||
* Computes a convolution in the simplest way possible. While the code is brief, it lacks many
|
||||
* possible optimizations. The convolution's result will not fill the output buffer either, as the
|
||||
* transient response is not calculated.
|
||||
*/
|
||||
|
||||
adcsample_t *process_data(adcsample_t *samples, unsigned int size)
|
||||
{
|
||||
// Define our output buffer. SIZE is the largest size of the 'samples' buffer.
|
||||
static adcsample_t buffer[SIZE];
|
||||
|
||||
// Define our filter
|
||||
constexpr unsigned int filter_size = 3;
|
||||
float filter[filter_size] = {
|
||||
0.3333, 0.3333, 0.3333
|
||||
};
|
||||
|
||||
// Begin convolving:
|
||||
for (int n = 0; n < size - (filter_size - 1); n++) {
|
||||
buffer[n] = 0;
|
||||
for (int k = 0; k < filter_size; k++)
|
||||
buffer[n] += samples[n + k] * filter[k];
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
/**
|
||||
* 2_convolve_overlap_save.cpp
|
||||
* Written by Clyne Sullivan.
|
||||
*
|
||||
* This convolution examples takes an overlap-save approach, where samples from the previous run
|
||||
* are saved so that the overall operation is not interrupted (i.e. the observed output will
|
||||
* transition smoothly between processed "chunks").
|
||||
*
|
||||
* Note that there are still improvements that can be made to the code; for example, notice every
|
||||
* spot where an integer/float conversion is necessary. Operations like these may slow down the
|
||||
* computation.
|
||||
*/
|
||||
|
||||
adcsample_t *process_data(adcsample_t *samples, unsigned int size)
|
||||
{
|
||||
static adcsample_t buffer[SIZE];
|
||||
|
||||
constexpr unsigned int filter_size = 3;
|
||||
float filter[filter_size] = {
|
||||
0.3333, 0.3333, 0.3333
|
||||
};
|
||||
|
||||
// Keep a buffer of extra samples for overlap-save
|
||||
static adcsample_t prev[filter_size];
|
||||
|
||||
for (int n = 0; n < size; n++) {
|
||||
buffer[n] = 0;
|
||||
|
||||
for (int k = 0; k < filter_size; k++) {
|
||||
int i = n - (filter_size - 1) + k;
|
||||
|
||||
// If i is >= 0, access current sample buffer.
|
||||
// If i is < 0, provide the previous samples from the 'prev' buffer
|
||||
if (i >= 0)
|
||||
buffer[n] += samples[i] * filter[k];
|
||||
else
|
||||
buffer[n] += prev[filter_size - 1 + i] * filter[k];
|
||||
}
|
||||
}
|
||||
|
||||
// Save samples for the next convolution run
|
||||
for (int i = 0; i < filter_size; i++)
|
||||
prev[i] = samples[size - filter_size + i];
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
@ -1,47 +0,0 @@
|
||||
/**
|
||||
* 3_fir.cpp
|
||||
* Written by Clyne Sullivan.
|
||||
*
|
||||
* The below code was written for applying FIR filters. While this is still essentially an overlap-
|
||||
* save convolution, other optimizations have been made to allow for larger filters to be applied
|
||||
* within the available execution time. Samples are also normalized so that they center around zero.
|
||||
*/
|
||||
|
||||
adcsample_t *process_data(adcsample_t *samples, unsigned int size)
|
||||
{
|
||||
static adcsample_t buffer[SIZE];
|
||||
|
||||
// Define the filter:
|
||||
constexpr unsigned int filter_size = 3;
|
||||
static float filter[filter_size] = {
|
||||
// Put filter values here (note: precision will be truncated for 'float' size).
|
||||
0.3333, 0.3333, 0.3333
|
||||
};
|
||||
|
||||
// Do an overlap-save convolution
|
||||
static adcsample_t prev[filter_size];
|
||||
|
||||
for (int n = 0; n < size; n++) {
|
||||
// Using a float variable for accumulation allows for better code optimization
|
||||
float v = 0;
|
||||
|
||||
for (int k = 0; k < filter_size; k++) {
|
||||
int i = n - (filter_size - 1) + k;
|
||||
|
||||
auto s = i >= 0 ? samples[i] : prev[filter_size - 1 + i];
|
||||
// Sample values are 0 to 4095. Below, the original sample is normalized to a -1.0 to
|
||||
// 1.0 range for calculation.
|
||||
v += (s / 2048.f - 1) * filter[k];
|
||||
}
|
||||
|
||||
// Return value to sample range of 0-4095.
|
||||
buffer[n] = (v + 1) * 2048.f;
|
||||
}
|
||||
|
||||
// Save samples for next convolution
|
||||
for (int i = 0; i < filter_size; i++)
|
||||
prev[i] = samples[size - filter_size + i];
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
@ -1,478 +0,0 @@
|
||||
#include <cstdint>
|
||||
using float32_t = float;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint16_t numTaps; /**< number of filter coefficients in the filter. */
|
||||
float32_t *pState; /**< points to the state variable array. The array is of length numTaps+blockSize-1. */
|
||||
float32_t *pCoeffs; /**< points to the coefficient array. The array is of length numTaps. */
|
||||
} arm_fir_instance_f32;
|
||||
|
||||
static void arm_fir_f32(const arm_fir_instance_f32 * S, float32_t * pSrc, float32_t * pDst, uint32_t blockSize);
|
||||
|
||||
adcsample_t *process_data(adcsample_t *samples, unsigned int size)
|
||||
{
|
||||
// 1. Define our array sizes (Be sure to set Run > Set buffer size... to below value!)
|
||||
constexpr unsigned int buffer_size = 500;
|
||||
constexpr unsigned int filter_size = 100;
|
||||
|
||||
// 2. Define our filter and the working arrays
|
||||
static float filter[filter_size] = {
|
||||
.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,
|
||||
.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,
|
||||
.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,
|
||||
.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,
|
||||
.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,
|
||||
.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,
|
||||
.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,
|
||||
.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,
|
||||
.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,
|
||||
.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f,.01f
|
||||
};
|
||||
static float input[buffer_size];
|
||||
static float output[buffer_size];
|
||||
static float working[buffer_size + filter_size];
|
||||
|
||||
// 3. Scale 0-4095 interger sample values to +/- 1.0 floats
|
||||
for (unsigned int i = 0; i < size; i++)
|
||||
input[i] = (samples[i] - 2048) / 2048.f;
|
||||
|
||||
// 4. Compute the FIR
|
||||
arm_fir_instance_f32 fir { filter_size, working, filter };
|
||||
arm_fir_f32(&fir, input, output, size);
|
||||
|
||||
// 5. Convert float results back to 0-4095 range for output
|
||||
for (unsigned int i = 0; i < size; i++)
|
||||
samples[i] = output[i] * 2048.f + 2048;
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
// Below taken from the CMSIS DSP Library (find it on GitHub)
|
||||
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--;
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
/**
|
||||
* 5_fir_differentiator.cpp
|
||||
* Written by Clyne Sullivan.
|
||||
*
|
||||
* Does an FIR differentiation on the incoming signal, so that the output is representative of the
|
||||
* rate of change of the input.
|
||||
* A scaling factor is applied so that the output's form is more clearly visible.
|
||||
*/
|
||||
|
||||
adcsample_t *process_data(adcsample_t *samples, unsigned int size)
|
||||
{
|
||||
constexpr int scaling_factor = 4;
|
||||
static adcsample_t output[SIZE];
|
||||
static adcsample_t prev = 2048;
|
||||
|
||||
// Compute the first output value using the saved sample.
|
||||
output[0] = 2048 + ((samples[0] - prev) * scaling_factor);
|
||||
|
||||
for (unsigned int i = 1; i < size; i++) {
|
||||
// Take the rate of change and scale it.
|
||||
// 2048 is added as the output should be centered in the voltage range.
|
||||
output[i] = 2048 + ((samples[i] - samples[i - 1]) * scaling_factor);
|
||||
}
|
||||
|
||||
// Save the last sample for the next iteration.
|
||||
prev = samples[size - 1];
|
||||
|
||||
return output;
|
||||
}
|
||||
|
@ -1,13 +0,0 @@
|
||||
adcsample_t *process_data(adcsample_t *samples, unsigned int size)
|
||||
{
|
||||
constexpr float alpha = 0.7;
|
||||
|
||||
static adcsample_t prev = 2048;
|
||||
|
||||
samples[0] = (1 - alpha) * samples[0] + alpha * prev;
|
||||
for (unsigned int i = 1; i < size; i++)
|
||||
samples[i] = (1 - alpha) * samples[i] + alpha * samples[i - 1];
|
||||
prev = samples[size - 1];
|
||||
|
||||
return samples;
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
adcsample_t *process_data(adcsample_t *samples, unsigned int size)
|
||||
{
|
||||
constexpr float alpha = 0.75;
|
||||
constexpr unsigned int D = 100;
|
||||
|
||||
static adcsample_t output[SIZE];
|
||||
static adcsample_t prev[D]; // prev[0] = output[0 - D]
|
||||
|
||||
// Do calculations with previous output
|
||||
for (unsigned int i = 0; i < D; i++)
|
||||
output[i] = samples[i] + alpha * (prev[i] - 2048);
|
||||
|
||||
// Do calculations with current samples
|
||||
for (unsigned int i = D; i < size; i++)
|
||||
output[i] = samples[i] + alpha * (output[i - D] - 2048);
|
||||
|
||||
// Save outputs for next computation
|
||||
for (unsigned int i = 0; i < D; i++)
|
||||
prev[i] = output[size - (D - i)];
|
||||
|
||||
return output;
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
#ifndef WAV_HPP_
|
||||
#define WAV_HPP_
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
|
||||
namespace wav
|
||||
{
|
||||
struct header {
|
||||
char riff[4]; // "RIFF"
|
||||
uint32_t filesize; // Total file size minus eight bytes
|
||||
char wave[4]; // "WAVE"
|
||||
|
||||
bool valid() const {
|
||||
return strncmp(riff, "RIFF", 4) == 0 && filesize > 8 && strncmp(wave, "WAVE", 4) == 0;
|
||||
}
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct format {
|
||||
char fmt_[4]; // "fmt "
|
||||
uint32_t size;
|
||||
uint16_t type;
|
||||
uint16_t channelcount;
|
||||
uint32_t samplerate;
|
||||
uint32_t byterate;
|
||||
uint16_t unused;
|
||||
uint16_t bps;
|
||||
|
||||
bool valid() const {
|
||||
return strncmp(fmt_, "fmt ", 4) == 0;
|
||||
}
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct data {
|
||||
char data[4]; // "data"
|
||||
uint32_t size;
|
||||
|
||||
bool valid() const {
|
||||
return strncmp(data, "data", 4) == 0;
|
||||
}
|
||||
} __attribute__ ((packed));
|
||||
|
||||
class clip {
|
||||
public:
|
||||
clip(const char *path) {
|
||||
std::ifstream file (path);
|
||||
if (!file.good())
|
||||
return;
|
||||
{
|
||||
header h;
|
||||
file.read(reinterpret_cast<char *>(&h), sizeof(header));
|
||||
if (!h.valid())
|
||||
return;
|
||||
}
|
||||
{
|
||||
format f;
|
||||
file.read(reinterpret_cast<char *>(&f), sizeof(format));
|
||||
if (!f.valid() || f.type != 1) // ensure PCM
|
||||
return;
|
||||
}
|
||||
{
|
||||
wav::data d;
|
||||
file.read(reinterpret_cast<char *>(&d), sizeof(wav::data));
|
||||
if (!d.valid())
|
||||
return;
|
||||
m_data = new char[d.size + 4096 - (d.size % 4096)];
|
||||
m_size = d.size;
|
||||
file.read(m_data, d.size);
|
||||
}
|
||||
}
|
||||
|
||||
bool valid() const {
|
||||
return m_data != nullptr && m_size > 0;
|
||||
}
|
||||
auto data() const {
|
||||
return m_data;
|
||||
}
|
||||
auto next(unsigned int chunksize = 3000) {
|
||||
if (m_pos == m_size) {
|
||||
m_pos = 0;
|
||||
}
|
||||
auto ret = m_data + m_pos;
|
||||
m_pos = std::min(m_pos + chunksize, m_size);
|
||||
return ret;
|
||||
}
|
||||
private:
|
||||
char *m_data = nullptr;
|
||||
uint32_t m_size = 0;
|
||||
uint32_t m_pos = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // WAV_HPP_
|
||||
|
@ -1,37 +0,0 @@
|
||||
/**
|
||||
* @file wxapp.hpp
|
||||
* @brief Main application object for the stmdsp gui.
|
||||
*
|
||||
* Copyright (C) 2021 Clyne Sullivan
|
||||
*
|
||||
* Distributed under the GNU GPL v3 or later. You should have received a copy of
|
||||
* the GNU General Public License along with this program.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef WXAPP_HPP_
|
||||
#define WXAPP_HPP_
|
||||
|
||||
#include "wxmain.hpp"
|
||||
|
||||
#include <wx/app.h>
|
||||
#include <wx/font.h>
|
||||
|
||||
class MainApp : public wxApp
|
||||
{
|
||||
public:
|
||||
virtual bool OnInit() final {
|
||||
wxFont::AddPrivateFont("./Hack-Regular.ttf");
|
||||
|
||||
m_main_frame = new MainFrame;
|
||||
m_main_frame->Show(true);
|
||||
SetTopWindow(m_main_frame);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
MainFrame *m_main_frame = nullptr;
|
||||
};
|
||||
|
||||
#endif // WXAPP_HPP_
|
||||
|
@ -1,454 +0,0 @@
|
||||
/**
|
||||
* @file wxmain.cpp
|
||||
* @brief Main window definition.
|
||||
*
|
||||
* Copyright (C) 2021 Clyne Sullivan
|
||||
*
|
||||
* Distributed under the GNU GPL v3 or later. You should have received a copy of
|
||||
* the GNU General Public License along with this program.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "wxmain.hpp"
|
||||
|
||||
#include <wx/combobox.h>
|
||||
#include <wx/dcbuffer.h>
|
||||
#include <wx/dcclient.h>
|
||||
#include <wx/dir.h>
|
||||
#include <wx/filename.h>
|
||||
#include <wx/filedlg.h>
|
||||
#include <wx/menu.h>
|
||||
#include <wx/menuitem.h>
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/panel.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/splitter.h>
|
||||
#include <wx/statusbr.h>
|
||||
#include <wx/textdlg.h>
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#ifndef WIN32
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
#include "wxmain_devdata.h"
|
||||
|
||||
enum Id {
|
||||
MeasureTimer = 1,
|
||||
|
||||
MFileNew,
|
||||
MFileOpen,
|
||||
MFileOpenTemplate,
|
||||
MFileSave,
|
||||
MFileSaveAs,
|
||||
MFileQuit,
|
||||
MRunConnect,
|
||||
MRunStart,
|
||||
MRunMeasure,
|
||||
MRunDrawSamples,
|
||||
MRunLogResults,
|
||||
MRunUpload,
|
||||
MRunUnload,
|
||||
MRunEditBSize,
|
||||
MRunGenUpload,
|
||||
MRunGenStart,
|
||||
MCodeCompile,
|
||||
MCodeDisassemble,
|
||||
CompileOutput
|
||||
};
|
||||
|
||||
MainFrame::MainFrame() :
|
||||
wxFrame(nullptr, wxID_ANY, "stmdspgui", wxDefaultPosition, wxSize(640, 800))
|
||||
{
|
||||
// Main frame structure:
|
||||
// Begin with a main splitter for the code and terminal panes
|
||||
auto mainSplitter = new wxSplitterWindow(this, wxID_ANY);
|
||||
auto panelCode = new wxPanel(mainSplitter, wxID_ANY);
|
||||
auto panelOutput = new wxPanel(mainSplitter, wxID_ANY);
|
||||
// Additional panel for the toolbar
|
||||
auto panelToolbar = new wxPanel(panelCode, wxID_ANY);
|
||||
// Sizers for the controls
|
||||
auto sizerToolbar = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto sizerCode = new wxBoxSizer(wxVERTICAL);
|
||||
auto sizerOutput = new wxBoxSizer(wxVERTICAL);
|
||||
auto sizerMain = new wxBoxSizer(wxVERTICAL);
|
||||
// Menu objects
|
||||
auto menuFile = new wxMenu;
|
||||
auto menuRun = new wxMenu;
|
||||
auto menuCode = new wxMenu;
|
||||
|
||||
// Member initialization
|
||||
m_status_bar = new wxStatusBar(this);
|
||||
m_text_editor = new wxStyledTextCtrl(panelCode, wxID_ANY,
|
||||
wxDefaultPosition, wxSize(620, 440));
|
||||
m_compile_output = new wxTextCtrl(panelOutput, Id::CompileOutput,
|
||||
wxEmptyString,
|
||||
wxDefaultPosition, wxSize(620, 250),
|
||||
wxTE_READONLY | wxTE_MULTILINE | wxHSCROLL | wxTE_RICH2);
|
||||
m_measure_timer = new wxTimer(this, Id::MeasureTimer);
|
||||
m_menu_bar = new wxMenuBar;
|
||||
m_rate_select = new wxComboBox(panelToolbar, wxID_ANY,
|
||||
wxEmptyString,
|
||||
wxDefaultPosition, wxDefaultSize,
|
||||
srateValues.size(), srateValues.data(),
|
||||
wxCB_READONLY);
|
||||
#ifndef WIN32
|
||||
m_device_samples = reinterpret_cast<stmdsp::adcsample_t *>(::mmap(
|
||||
nullptr, stmdsp::SAMPLES_MAX * sizeof(stmdsp::adcsample_t),
|
||||
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0));
|
||||
m_device_samples_input = reinterpret_cast<stmdsp::adcsample_t *>(::mmap(
|
||||
nullptr, stmdsp::SAMPLES_MAX * sizeof(stmdsp::adcsample_t),
|
||||
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0));
|
||||
#else
|
||||
m_device_samples = new stmdsp::adcsample_t[stmdsp::SAMPLES_MAX];
|
||||
m_device_samples_input = new stmdsp::adcsample_t[stmdsp::SAMPLES_MAX];
|
||||
#endif
|
||||
|
||||
m_menu_bar->Append(menuFile, "&File");
|
||||
m_menu_bar->Append(menuRun, "&Run");
|
||||
m_menu_bar->Append(menuCode, "&Code");
|
||||
SetMenuBar(m_menu_bar);
|
||||
|
||||
// Toolbar initialization
|
||||
auto comp = new wxButton(panelToolbar, wxID_ANY, "Compile");
|
||||
sizerToolbar->Add(comp, 0, wxLEFT | wxTOP, 4);
|
||||
sizerToolbar->Add(m_rate_select, 0, wxLEFT | wxTOP, 4);
|
||||
panelToolbar->SetSizer(sizerToolbar);
|
||||
|
||||
// Code panel init.
|
||||
prepareEditor();
|
||||
sizerCode->Add(panelToolbar, 0, wxBOTTOM, 4);
|
||||
sizerCode->Add(m_text_editor, 1, wxEXPAND, 0);
|
||||
panelCode->SetSizer(sizerCode);
|
||||
|
||||
// Output panel init.
|
||||
m_compile_output->SetBackgroundColour(wxColour(0, 0, 0));
|
||||
m_compile_output->SetDefaultStyle(wxTextAttr(*wxWHITE, *wxBLACK, wxFont("Hack")));
|
||||
sizerOutput->Add(m_compile_output, 1, wxEXPAND | wxALL, 0);
|
||||
panelOutput->SetSizer(sizerOutput);
|
||||
|
||||
// Main splitter init.
|
||||
mainSplitter->SetSashGravity(0.5);
|
||||
mainSplitter->SetMinimumPaneSize(20);
|
||||
mainSplitter->SplitHorizontally(panelCode, panelOutput, 100);
|
||||
sizerMain->Add(mainSplitter, 1, wxEXPAND, 5);
|
||||
sizerMain->SetSizeHints(this);
|
||||
SetSizer(sizerMain);
|
||||
|
||||
m_status_bar->SetStatusText("Ready.");
|
||||
SetStatusBar(m_status_bar);
|
||||
|
||||
// Binds:
|
||||
|
||||
// General
|
||||
Bind(wxEVT_TIMER, &MainFrame::onMeasureTimer, this, Id::MeasureTimer);
|
||||
Bind(wxEVT_CLOSE_WINDOW, &MainFrame::onCloseEvent, this, wxID_ANY);
|
||||
m_compile_output->
|
||||
Bind(wxEVT_PAINT, &MainFrame::onPaint, this, Id::CompileOutput);
|
||||
|
||||
// Toolbar actions
|
||||
Bind(wxEVT_BUTTON, &MainFrame::onRunCompile, this, wxID_ANY, wxID_ANY, comp);
|
||||
Bind(wxEVT_COMBOBOX, &MainFrame::onToolbarSampleRate, this, wxID_ANY, wxID_ANY, m_rate_select);
|
||||
|
||||
// File menu actions
|
||||
Bind(wxEVT_MENU, &MainFrame::onFileNew, this, Id::MFileNew, wxID_ANY, menuFile->Append(MFileNew, "&New"));
|
||||
Bind(wxEVT_MENU, &MainFrame::onFileOpen, this, Id::MFileOpen, wxID_ANY, menuFile->Append(MFileOpen, "&Open"));
|
||||
menuFile->Append(MFileOpenTemplate, "Open &Template", loadTemplates());
|
||||
Bind(wxEVT_MENU, &MainFrame::onFileSave, this, Id::MFileSave, wxID_ANY, menuFile->Append(MFileSave, "&Save"));
|
||||
Bind(wxEVT_MENU, &MainFrame::onFileSaveAs, this, Id::MFileSaveAs, wxID_ANY, menuFile->Append(MFileSaveAs, "Save &As"));
|
||||
menuFile->AppendSeparator();
|
||||
Bind(wxEVT_MENU, &MainFrame::onFileQuit, this, Id::MFileQuit, wxID_ANY, menuFile->Append(MFileQuit, "&Quit"));
|
||||
|
||||
// Run menu actions
|
||||
Bind(wxEVT_MENU, &MainFrame::onRunConnect, this, Id::MRunConnect, wxID_ANY, menuRun->Append(MRunConnect, "&Connect"));
|
||||
menuRun->AppendSeparator();
|
||||
Bind(wxEVT_MENU, &MainFrame::onRunStart, this, Id::MRunStart, wxID_ANY, menuRun->Append(MRunStart, "&Start"));
|
||||
m_run_measure = menuRun->AppendCheckItem(MRunMeasure, "&Measure code time");
|
||||
m_run_draw_samples = menuRun->AppendCheckItem(MRunDrawSamples, "&Draw samples");
|
||||
Bind(wxEVT_MENU, &MainFrame::onRunLogResults, this, Id::MRunLogResults, wxID_ANY, menuRun->AppendCheckItem(MRunLogResults, "&Log results..."));
|
||||
menuRun->AppendSeparator();
|
||||
Bind(wxEVT_MENU, &MainFrame::onRunUpload, this, Id::MRunUpload, wxID_ANY, menuRun->Append(MRunUpload, "&Upload code"));
|
||||
Bind(wxEVT_MENU, &MainFrame::onRunUnload, this, Id::MRunUnload, wxID_ANY, menuRun->Append(MRunUnload, "U&nload code"));
|
||||
Bind(wxEVT_MENU, &MainFrame::onRunEditBSize, this, Id::MRunEditBSize, wxID_ANY, menuRun->Append(MRunEditBSize, "Set &buffer size..."));
|
||||
menuRun->AppendSeparator();
|
||||
Bind(wxEVT_MENU, &MainFrame::onRunGenUpload, this, Id::MRunGenUpload, wxID_ANY, menuRun->Append(MRunGenUpload, "&Load signal generator..."));
|
||||
Bind(wxEVT_MENU, &MainFrame::onRunGenStart, this, Id::MRunGenStart, wxID_ANY, menuRun->AppendCheckItem(MRunGenStart, "Start &generator"));
|
||||
|
||||
// Code menu actions
|
||||
Bind(wxEVT_MENU, &MainFrame::onRunCompile, this, Id::MCodeCompile, wxID_ANY, menuCode->Append(MCodeCompile, "&Compile code"));
|
||||
Bind(wxEVT_MENU, &MainFrame::onCodeDisassemble, this, Id::MCodeDisassemble, wxID_ANY, menuCode->Append(MCodeDisassemble, "Show &Disassembly"));
|
||||
|
||||
updateMenuOptions();
|
||||
}
|
||||
|
||||
// Closes the window
|
||||
// Needs to clean things up
|
||||
void MainFrame::onCloseEvent(wxCloseEvent& event)
|
||||
{
|
||||
SetMenuBar(nullptr);
|
||||
//delete m_menu_bar->Remove(2);
|
||||
//delete m_menu_bar->Remove(1);
|
||||
//delete m_menu_bar->Remove(0);
|
||||
//delete m_menu_bar;
|
||||
delete m_measure_timer;
|
||||
delete m_device;
|
||||
|
||||
Unbind(wxEVT_COMBOBOX, &MainFrame::onToolbarSampleRate, this, wxID_ANY, wxID_ANY);
|
||||
Unbind(wxEVT_BUTTON, &MainFrame::onRunCompile, this, wxID_ANY, wxID_ANY);
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
// Measure timer tick handler
|
||||
// Only called while connected and running.
|
||||
void MainFrame::onMeasureTimer(wxTimerEvent&)
|
||||
{
|
||||
if (m_conv_result_log || m_run_draw_samples->IsChecked()) {
|
||||
auto samples = m_device->continuous_read();
|
||||
if (samples.size() > 0) {
|
||||
std::copy(samples.cbegin(), samples.cend(), m_device_samples);
|
||||
|
||||
if (m_conv_result_log) {
|
||||
for (auto& s : samples) {
|
||||
auto str = std::to_string(s) + ',';
|
||||
m_conv_result_log->Write(str.c_str(), str.size());
|
||||
}
|
||||
m_conv_result_log->Write("\n", 1);
|
||||
}
|
||||
if (m_run_draw_samples->IsChecked()) {
|
||||
samples = m_device->continuous_read_input();
|
||||
std::copy(samples.cbegin(), samples.cend(), m_device_samples_input);
|
||||
m_compile_output->Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_wav_clip) {
|
||||
// Stream out next WAV chunk
|
||||
auto size = m_device->get_buffer_size();
|
||||
auto chunk = new stmdsp::adcsample_t[size];
|
||||
auto src = reinterpret_cast<uint16_t *>(m_wav_clip->next(size));
|
||||
for (unsigned int i = 0; i < size; i++)
|
||||
chunk[i] = ((uint32_t)*src++) / 16 + 2048;
|
||||
m_device->siggen_upload(chunk, size);
|
||||
delete[] chunk;
|
||||
}
|
||||
|
||||
if (m_run_measure->IsChecked()) {
|
||||
// Show execution time
|
||||
m_status_bar->SetStatusText(wxString::Format(wxT("Execution time: %u cycles"),
|
||||
m_device->continuous_start_get_measurement()));
|
||||
}
|
||||
}
|
||||
|
||||
void MainFrame::onPaint(wxPaintEvent&)
|
||||
{
|
||||
if (!m_is_running || !m_run_draw_samples->IsChecked())
|
||||
return;
|
||||
|
||||
const auto& dim = m_compile_output->GetSize();
|
||||
wxRect rect {
|
||||
0, 0, dim.GetWidth(), dim.GetHeight()
|
||||
};
|
||||
|
||||
wxBufferedPaintDC dc (m_compile_output);
|
||||
dc.SetBrush(*wxBLACK_BRUSH);
|
||||
dc.SetPen(*wxBLACK_PEN);
|
||||
dc.DrawRectangle(rect);
|
||||
auto stoy = [&](stmdsp::adcsample_t s) {
|
||||
return static_cast<float>(rect.GetHeight()) -
|
||||
(static_cast<float>(rect.GetHeight()) * s / 4095.f);
|
||||
};
|
||||
auto scount = m_device->get_buffer_size();
|
||||
float dx = static_cast<float>(rect.GetWidth()) / scount;
|
||||
float x = 0;
|
||||
float lasty = stoy(2048);
|
||||
dc.SetBrush(wxBrush(wxColour(0xFF, 0, 0, 0x80)));
|
||||
dc.SetPen(wxPen(wxColour(0xFF, 0, 0, 0x80)));
|
||||
for (decltype(scount) i = 0; i < scount; i++) {
|
||||
auto y = stoy(m_device_samples[i]);
|
||||
dc.DrawLine(x, lasty, x + dx, y);
|
||||
x += dx, lasty = y;
|
||||
}
|
||||
x = 0;
|
||||
lasty = stoy(2048);
|
||||
dc.SetBrush(wxBrush(wxColour(0, 0, 0xFF, 0x80)));
|
||||
dc.SetPen(wxPen(wxColour(0, 0, 0xFF, 0x80)));
|
||||
for (decltype(scount) i = 0; i < scount; i++) {
|
||||
auto y = stoy(m_device_samples_input[i]);
|
||||
dc.DrawLine(x, lasty, x + dx, y);
|
||||
x += dx, lasty = y;
|
||||
}
|
||||
}
|
||||
|
||||
void MainFrame::prepareEditor()
|
||||
{
|
||||
m_text_editor->SetLexer(wxSTC_LEX_CPP);
|
||||
m_text_editor->SetMarginWidth(0, 30);
|
||||
m_text_editor->SetMarginType(0, wxSTC_MARGIN_NUMBER);
|
||||
m_text_editor->StyleSetFaceName(wxSTC_STYLE_DEFAULT, "Hack");
|
||||
m_text_editor->StyleClearAll();
|
||||
m_text_editor->SetTabWidth(4);
|
||||
m_text_editor->StyleSetForeground(wxSTC_STYLE_LINENUMBER, wxColor(75, 75, 75));
|
||||
m_text_editor->StyleSetBackground(wxSTC_STYLE_LINENUMBER, wxColor(220, 220, 220));
|
||||
|
||||
m_text_editor->StyleSetForeground(wxSTC_C_STRING, wxColour(150,0,0));
|
||||
m_text_editor->StyleSetForeground(wxSTC_C_PREPROCESSOR, wxColour(165,105,0));
|
||||
m_text_editor->StyleSetForeground(wxSTC_C_IDENTIFIER, wxColour(40,0,60));
|
||||
m_text_editor->StyleSetForeground(wxSTC_C_NUMBER, wxColour(0,150,0));
|
||||
m_text_editor->StyleSetForeground(wxSTC_C_CHARACTER, wxColour(150,0,0));
|
||||
m_text_editor->StyleSetForeground(wxSTC_C_WORD, wxColour(0,0,150));
|
||||
m_text_editor->StyleSetForeground(wxSTC_C_WORD2, wxColour(0,150,0));
|
||||
m_text_editor->StyleSetForeground(wxSTC_C_COMMENT, wxColour(150,150,150));
|
||||
m_text_editor->StyleSetForeground(wxSTC_C_COMMENTLINE, wxColour(150,150,150));
|
||||
m_text_editor->StyleSetForeground(wxSTC_C_COMMENTDOC, wxColour(150,150,150));
|
||||
m_text_editor->StyleSetForeground(wxSTC_C_COMMENTDOCKEYWORD, wxColour(0,0,200));
|
||||
m_text_editor->StyleSetForeground(wxSTC_C_COMMENTDOCKEYWORDERROR, wxColour(0,0,200));
|
||||
m_text_editor->StyleSetBold(wxSTC_C_WORD, true);
|
||||
m_text_editor->StyleSetBold(wxSTC_C_WORD2, true);
|
||||
m_text_editor->StyleSetBold(wxSTC_C_COMMENTDOCKEYWORD, true);
|
||||
|
||||
// a sample list of keywords, I haven't included them all to keep it short...
|
||||
m_text_editor->SetKeyWords(0,
|
||||
wxT("return for while do break continue if else goto asm"));
|
||||
m_text_editor->SetKeyWords(1,
|
||||
wxT("void char short int long auto float double unsigned signed "
|
||||
"volatile static const constexpr constinit consteval "
|
||||
"virtual final noexcept public private protected"));
|
||||
wxCommandEvent dummy;
|
||||
onFileNew(dummy);
|
||||
}
|
||||
|
||||
wxString MainFrame::compileEditorCode()
|
||||
{
|
||||
stmdsp::platform platform;
|
||||
if (m_device != nullptr) {
|
||||
platform = m_device->get_platform();
|
||||
} else {
|
||||
m_status_bar->SetStatusText("Assuming L4 platform...");
|
||||
platform = stmdsp::platform::L4;
|
||||
}
|
||||
|
||||
if (m_temp_file_name.IsEmpty())
|
||||
m_temp_file_name = wxFileName::CreateTempFileName("stmdspgui");
|
||||
|
||||
wxFile file (m_temp_file_name, wxFile::write);
|
||||
wxString file_text (platform == stmdsp::platform::L4 ? file_header_l4
|
||||
: file_header_h7);
|
||||
file_text.Replace("$0", std::to_string(m_device ? m_device->get_buffer_size()
|
||||
: stmdsp::SAMPLES_MAX));
|
||||
file.Write(wxString(file_text) + m_text_editor->GetText());
|
||||
file.Close();
|
||||
|
||||
constexpr const char *script_ext =
|
||||
#ifndef STMDSP_WIN32
|
||||
".sh";
|
||||
#else
|
||||
".bat";
|
||||
#endif
|
||||
|
||||
wxFile makefile (m_temp_file_name + script_ext, wxFile::write);
|
||||
wxString make_text (platform == stmdsp::platform::L4 ? makefile_text_l4
|
||||
: makefile_text_h7);
|
||||
make_text.Replace("$0", m_temp_file_name);
|
||||
makefile.Write(make_text);
|
||||
makefile.Close();
|
||||
|
||||
wxString make_output = m_temp_file_name + script_ext + ".log";
|
||||
wxString make_command = m_temp_file_name + script_ext + " > " +
|
||||
make_output + " 2>&1";
|
||||
|
||||
#ifndef STMDSP_WIN32
|
||||
system(wxString("chmod +x ") + m_temp_file_name + script_ext);
|
||||
#endif
|
||||
int result = system(make_command.ToAscii());
|
||||
wxFile result_file (make_output);
|
||||
wxString result_text;
|
||||
result_file.ReadAll(&result_text);
|
||||
result_file.Close();
|
||||
m_compile_output->Clear();
|
||||
m_compile_output->WriteText(result_text);
|
||||
|
||||
wxRemoveFile(m_temp_file_name);
|
||||
wxRemoveFile(m_temp_file_name + script_ext);
|
||||
wxRemoveFile(make_output);
|
||||
|
||||
if (result == 0) {
|
||||
m_status_bar->SetStatusText("Compilation succeeded.");
|
||||
return m_temp_file_name + ".o";
|
||||
} else {
|
||||
m_status_bar->SetStatusText("Compilation failed.");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void MainFrame::onToolbarSampleRate(wxCommandEvent& ce)
|
||||
{
|
||||
auto combo = dynamic_cast<wxComboBox *>(ce.GetEventUserData());
|
||||
m_device->set_sample_rate(combo->GetCurrentSelection());
|
||||
m_status_bar->SetStatusText("Ready.");
|
||||
}
|
||||
|
||||
void MainFrame::onCodeDisassemble(wxCommandEvent&)
|
||||
{
|
||||
auto output = m_temp_file_name + ".asm.log";
|
||||
if (!m_temp_file_name.IsEmpty()) {
|
||||
wxString command = wxString("arm-none-eabi-objdump -d --no-show-raw-insn ") +
|
||||
m_temp_file_name + ".orig.o" // +
|
||||
" > " + output + " 2>&1";
|
||||
|
||||
if (system(command.ToAscii()) == 0) {
|
||||
wxFile result_file (output);
|
||||
wxString result_text;
|
||||
result_file.ReadAll(&result_text);
|
||||
result_file.Close();
|
||||
m_compile_output->Clear();
|
||||
m_compile_output->WriteText(result_text);
|
||||
wxRemoveFile(output);
|
||||
m_status_bar->SetStatusText(wxString::Format(wxT("Done. Line count: %u."),
|
||||
m_compile_output->GetNumberOfLines()));
|
||||
} else {
|
||||
m_status_bar->SetStatusText("Failed to load disassembly.");
|
||||
}
|
||||
} else {
|
||||
m_status_bar->SetStatusText("Need to compile code before analyzing.");
|
||||
}
|
||||
}
|
||||
|
||||
wxMenu *MainFrame::loadTemplates()
|
||||
{
|
||||
wxMenu *menu = new wxMenu;
|
||||
|
||||
wxArrayString files;
|
||||
if (wxDir::GetAllFiles(wxGetCwd() + "/templates", &files, "*.cpp", wxDIR_FILES) > 0) {
|
||||
files.Sort();
|
||||
int id = 1000;
|
||||
for (auto file : files) {
|
||||
Bind(wxEVT_MENU, &MainFrame::onFileOpenTemplate, this, id, wxID_ANY,
|
||||
menu->Append(id, file.AfterLast('/')));
|
||||
id++;
|
||||
}
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
void MainFrame::updateMenuOptions()
|
||||
{
|
||||
bool connected = m_device != nullptr;
|
||||
m_menu_bar->Enable(MRunStart, connected);
|
||||
m_menu_bar->Enable(MRunGenUpload, connected);
|
||||
m_menu_bar->Enable(MRunGenStart, connected);
|
||||
|
||||
bool nrunning = connected && !m_is_running;
|
||||
m_menu_bar->Enable(MRunUpload, nrunning);
|
||||
m_menu_bar->Enable(MRunUnload, nrunning);
|
||||
m_menu_bar->Enable(MRunEditBSize, nrunning);
|
||||
m_menu_bar->Enable(MRunMeasure, nrunning);
|
||||
m_menu_bar->Enable(MRunDrawSamples, nrunning);
|
||||
m_menu_bar->Enable(MRunLogResults, nrunning);
|
||||
m_rate_select->Enable(nrunning);
|
||||
}
|
||||
|
@ -1,107 +0,0 @@
|
||||
/**
|
||||
* @file wxmain.hpp
|
||||
* @brief Main window definition.
|
||||
*
|
||||
* Copyright (C) 2021 Clyne Sullivan
|
||||
*
|
||||
* Distributed under the GNU GPL v3 or later. You should have received a copy of
|
||||
* the GNU General Public License along with this program.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef WXMAIN_HPP_
|
||||
#define WXMAIN_HPP_
|
||||
|
||||
#include "stmdsp.hpp"
|
||||
#include "wav.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <future>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <wx/button.h>
|
||||
#include <wx/combobox.h>
|
||||
#include <wx/dcclient.h>
|
||||
#include <wx/filedlg.h>
|
||||
#include <wx/font.h>
|
||||
#include <wx/frame.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/stc/stc.h>
|
||||
#include <wx/timer.h>
|
||||
#include <wx/wfstream.h>
|
||||
|
||||
class MainFrame : public wxFrame
|
||||
{
|
||||
public:
|
||||
MainFrame();
|
||||
|
||||
void onCloseEvent(wxCloseEvent&);
|
||||
|
||||
void onFileNew(wxCommandEvent&);
|
||||
void onFileOpen(wxCommandEvent&);
|
||||
void onFileOpenTemplate(wxCommandEvent&);
|
||||
void onFileSave(wxCommandEvent&);
|
||||
void onFileSaveAs(wxCommandEvent&);
|
||||
void onFileQuit(wxCommandEvent&);
|
||||
|
||||
void onRunConnect(wxCommandEvent&);
|
||||
void onRunStart(wxCommandEvent&);
|
||||
void onRunLogResults(wxCommandEvent&);
|
||||
void onRunUpload(wxCommandEvent&);
|
||||
void onRunUnload(wxCommandEvent&);
|
||||
void onRunEditBSize(wxCommandEvent&);
|
||||
void onRunGenUpload(wxCommandEvent&);
|
||||
void onRunGenStart(wxCommandEvent&);
|
||||
|
||||
void onToolbarSampleRate(wxCommandEvent&);
|
||||
|
||||
void onRunCompile(wxCommandEvent&);
|
||||
void onCodeDisassemble(wxCommandEvent&);
|
||||
|
||||
void onPaint(wxPaintEvent&);
|
||||
void onMeasureTimer(wxTimerEvent&);
|
||||
|
||||
private:
|
||||
// Set to true if connected and running
|
||||
bool m_is_running = false;
|
||||
|
||||
wxComboBox *m_device_combo = nullptr;
|
||||
wxStyledTextCtrl *m_text_editor = nullptr;
|
||||
wxTextCtrl *m_compile_output = nullptr;
|
||||
wxControl *m_signal_area = nullptr;
|
||||
wxMenuItem *m_run_measure = nullptr;
|
||||
wxMenuItem *m_run_draw_samples = nullptr;
|
||||
wxTimer *m_measure_timer = nullptr;
|
||||
wxStatusBar *m_status_bar = nullptr;
|
||||
wxMenuBar *m_menu_bar = nullptr;
|
||||
wxComboBox *m_rate_select = nullptr;
|
||||
|
||||
// File handle for logging output samples
|
||||
// Not null when logging is enabled
|
||||
wxFileOutputStream *m_conv_result_log = nullptr;
|
||||
// File path of currently opened file
|
||||
// Empty if new file
|
||||
wxString m_open_file_path;
|
||||
// File path for temporary files (e.g. compiled ELF)
|
||||
// Set by compile action
|
||||
wxString m_temp_file_name;
|
||||
|
||||
// Device interface
|
||||
// Not null if connected
|
||||
stmdsp::device *m_device = nullptr;
|
||||
stmdsp::adcsample_t *m_device_samples = nullptr;
|
||||
stmdsp::adcsample_t *m_device_samples_input = nullptr;
|
||||
// WAV data for signal generator
|
||||
// Not null when a WAV is loaded
|
||||
wav::clip *m_wav_clip = nullptr;
|
||||
|
||||
bool tryDevice();
|
||||
void prepareEditor();
|
||||
wxString compileEditorCode();
|
||||
wxMenu *loadTemplates();
|
||||
// Updates control availabilities based on device connection
|
||||
void updateMenuOptions();
|
||||
};
|
||||
|
||||
#endif // WXMAIN_HPP_
|
||||
|
@ -1,187 +0,0 @@
|
||||
#include "wxmain_devdata.h"
|
||||
|
||||
const std::array<wxString, 6> srateValues {
|
||||
"8 kS/s",
|
||||
"16 kS/s",
|
||||
"20 kS/s",
|
||||
"32 kS/s",
|
||||
"48 kS/s",
|
||||
"96 kS/s"
|
||||
};
|
||||
const std::array<unsigned int, 6> srateNums {
|
||||
8000,
|
||||
16000,
|
||||
20000,
|
||||
32000,
|
||||
48000,
|
||||
96000
|
||||
};
|
||||
|
||||
#ifdef STMDSP_WIN32
|
||||
#define NEWLINE "\r\n"
|
||||
#define COPY "copy"
|
||||
#else
|
||||
#define NEWLINE "\n"
|
||||
#define COPY "cp"
|
||||
#endif
|
||||
|
||||
// $0 = temp file name
|
||||
const char *makefile_text_h7 =
|
||||
#ifdef STMDSP_WIN32
|
||||
"echo off" NEWLINE
|
||||
#endif
|
||||
"arm-none-eabi-g++ -x c++ -Os -std=c++20 -fno-exceptions -fno-rtti "
|
||||
"-mcpu=cortex-m7 -mthumb -mfloat-abi=hard -mfpu=fpv5-d16 -mtune=cortex-m7 "
|
||||
"-nostartfiles "
|
||||
"-Wl,-Ttext-segment=0x00000000 -Wl,-zmax-page-size=512 -Wl,-eprocess_data_entry "
|
||||
"$0 -o $0.o" NEWLINE
|
||||
COPY " $0.o $0.orig.o" NEWLINE
|
||||
"arm-none-eabi-strip -s -S --strip-unneeded $0.o" NEWLINE
|
||||
"arm-none-eabi-objcopy --remove-section .ARM.attributes "
|
||||
"--remove-section .comment "
|
||||
"--remove-section .noinit "
|
||||
"$0.o" NEWLINE
|
||||
"arm-none-eabi-size $0.o" NEWLINE;
|
||||
const char *makefile_text_l4 =
|
||||
#ifdef STMDSP_WIN32
|
||||
"echo off" NEWLINE
|
||||
#endif
|
||||
"arm-none-eabi-g++ -x c++ -Os -std=c++20 -fno-exceptions -fno-rtti "
|
||||
"-mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -mtune=cortex-m4 "
|
||||
"-nostartfiles "
|
||||
"-Wl,-Ttext-segment=0x10000000 -Wl,-zmax-page-size=512 -Wl,-eprocess_data_entry "
|
||||
"$0 -o $0.o" NEWLINE
|
||||
COPY " $0.o $0.orig.o" NEWLINE
|
||||
"arm-none-eabi-strip -s -S --strip-unneeded $0.o" NEWLINE
|
||||
"arm-none-eabi-objcopy --remove-section .ARM.attributes "
|
||||
"--remove-section .comment "
|
||||
"--remove-section .noinit "
|
||||
"$0.o" NEWLINE
|
||||
"arm-none-eabi-size $0.o" NEWLINE;
|
||||
|
||||
// $0 = buffer size
|
||||
const char *file_header_h7 = R"cpp(
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
|
||||
using Sample = uint16_t;
|
||||
using Samples = std::span<Sample, $0>;
|
||||
|
||||
Sample *process_data(Samples samples);
|
||||
extern "C" void process_data_entry()
|
||||
{
|
||||
Sample *samples;
|
||||
asm("mov %0, r0" : "=r" (samples));
|
||||
process_data(Samples(samples, $0));
|
||||
}
|
||||
|
||||
constexpr double PI = 3.14159265358979323846L;
|
||||
__attribute__((naked))
|
||||
auto sin(double x) {
|
||||
asm("vmov.f64 r1, r2, d0;"
|
||||
"eor r0, r0;"
|
||||
"svc 1;"
|
||||
"vmov.f64 d0, r1, r2;"
|
||||
"bx lr");
|
||||
return 0;
|
||||
}
|
||||
__attribute__((naked))
|
||||
auto cos(double x) {
|
||||
asm("vmov.f64 r1, r2, d0;"
|
||||
"mov r0, #1;"
|
||||
"svc 1;"
|
||||
"vmov.f64 d0, r1, r2;"
|
||||
"bx lr");
|
||||
return 0;
|
||||
}
|
||||
__attribute__((naked))
|
||||
auto tan(double x) {
|
||||
asm("vmov.f64 r1, r2, d0;"
|
||||
"mov r0, #2;"
|
||||
"svc 1;"
|
||||
"vmov.f64 d0, r1, r2;"
|
||||
"bx lr");
|
||||
return 0;
|
||||
}
|
||||
__attribute__((naked))
|
||||
auto sqrt(double x) {
|
||||
asm("vsqrt.f64 d0, d0; bx lr");
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto readalt() {
|
||||
Sample s;
|
||||
asm("svc 3; mov %0, r0" : "=&r"(s));
|
||||
return s;
|
||||
}
|
||||
|
||||
// End stmdspgui header code
|
||||
|
||||
)cpp";
|
||||
const char *file_header_l4 = R"cpp(
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
|
||||
using Sample = uint16_t;
|
||||
using Samples = std::span<Sample, $0>;
|
||||
|
||||
Sample *process_data(Samples samples);
|
||||
extern "C" void process_data_entry()
|
||||
{
|
||||
Sample *samples;
|
||||
asm("mov %0, r0" : "=r" (samples));
|
||||
process_data(Samples(samples, $0));
|
||||
}
|
||||
|
||||
constexpr float PI = 3.14159265358979L;
|
||||
__attribute__((naked))
|
||||
auto sin(float x) {
|
||||
asm("vmov.f32 r1, s0;"
|
||||
"eor r0, r0;"
|
||||
"svc 1;"
|
||||
"vmov.f32 s0, r1;"
|
||||
"bx lr");
|
||||
return 0;
|
||||
}
|
||||
__attribute__((naked))
|
||||
auto cos(float x) {
|
||||
asm("vmov.f32 r1, s0;"
|
||||
"mov r0, #1;"
|
||||
"svc 1;"
|
||||
"vmov.f32 s0, r1;"
|
||||
"bx lr");
|
||||
return 0;
|
||||
}
|
||||
__attribute__((naked))
|
||||
auto tan(float x) {
|
||||
asm("vmov.f32 r1, s0;"
|
||||
"mov r0, #2;"
|
||||
"svc 1;"
|
||||
"vmov.f32 s0, r1;"
|
||||
"bx lr");
|
||||
return 0;
|
||||
}
|
||||
__attribute__((naked))
|
||||
auto sqrt(float) {
|
||||
asm("vsqrt.f32 s0, s0; bx lr");
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto readalt() {
|
||||
Sample s;
|
||||
asm("push {r4-r6}; svc 3; mov %0, r0; pop {r4-r6}" : "=&r"(s));
|
||||
return s;
|
||||
}
|
||||
|
||||
// End stmdspgui header code
|
||||
|
||||
)cpp";
|
||||
|
||||
|
||||
const char *file_content =
|
||||
R"cpp(Sample *process_data(Samples samples)
|
||||
{
|
||||
return samples.data();
|
||||
}
|
||||
)cpp";
|
||||
|
@ -1,16 +0,0 @@
|
||||
#ifndef WXMAIN_DEVDATA_H_
|
||||
#define WXMAIN_DEVDATA_H_
|
||||
|
||||
#include <array>
|
||||
#include <wx/string.h>
|
||||
|
||||
extern const std::array<wxString, 6> srateValues;
|
||||
extern const std::array<unsigned int, 6> srateNums;
|
||||
extern const char *makefile_text_h7;
|
||||
extern const char *makefile_text_l4;
|
||||
extern const char *file_header_h7;
|
||||
extern const char *file_header_l4;
|
||||
extern const char *file_content;
|
||||
|
||||
#endif // WXMAIN_DEVDATA_H_
|
||||
|
@ -1,113 +0,0 @@
|
||||
#include "wxmain.hpp"
|
||||
#include "wxmain_devdata.h"
|
||||
|
||||
#include <wx/menu.h>
|
||||
|
||||
void MainFrame::onFileNew(wxCommandEvent&)
|
||||
{
|
||||
m_open_file_path = "";
|
||||
m_text_editor->SetText(file_content);
|
||||
m_text_editor->DiscardEdits();
|
||||
m_status_bar->SetStatusText("Ready.");
|
||||
}
|
||||
|
||||
void MainFrame::onFileOpen(wxCommandEvent&)
|
||||
{
|
||||
wxFileDialog openDialog(this, "Open filter file", "", "",
|
||||
"C++ source file (*.cpp)|*.cpp",
|
||||
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
|
||||
if (openDialog.ShowModal() != wxID_CANCEL) {
|
||||
if (wxFileInputStream file_stream (openDialog.GetPath()); file_stream.IsOk()) {
|
||||
auto size = file_stream.GetSize();
|
||||
auto buffer = new char[size + 1];
|
||||
buffer[size] = '\0';
|
||||
if (file_stream.ReadAll(buffer, size)) {
|
||||
m_open_file_path = openDialog.GetPath();
|
||||
m_text_editor->SetText(buffer);
|
||||
m_text_editor->DiscardEdits();
|
||||
m_compile_output->ChangeValue("");
|
||||
m_status_bar->SetStatusText("Ready.");
|
||||
} else {
|
||||
m_status_bar->SetStatusText("Failed to read file contents.");
|
||||
}
|
||||
delete[] buffer;
|
||||
} else {
|
||||
m_status_bar->SetStatusText("Failed to open file.");
|
||||
}
|
||||
} else {
|
||||
m_status_bar->SetStatusText("Ready.");
|
||||
}
|
||||
}
|
||||
|
||||
void MainFrame::onFileOpenTemplate(wxCommandEvent& event)
|
||||
{
|
||||
auto file_path = wxGetCwd() + "/templates/" + m_menu_bar->GetLabel(event.GetId());
|
||||
|
||||
if (wxFileInputStream file_stream (file_path); file_stream.IsOk()) {
|
||||
auto size = file_stream.GetSize();
|
||||
auto buffer = new char[size + 1];
|
||||
buffer[size] = '\0';
|
||||
if (file_stream.ReadAll(buffer, size)) {
|
||||
m_open_file_path = "";
|
||||
m_text_editor->SetText(buffer);
|
||||
//m_text_editor->DiscardEdits();
|
||||
m_status_bar->SetStatusText("Ready.");
|
||||
} else {
|
||||
m_status_bar->SetStatusText("Failed to read file contents.");
|
||||
}
|
||||
delete[] buffer;
|
||||
} else {
|
||||
m_status_bar->SetStatusText("Ready.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MainFrame::onFileSave(wxCommandEvent& ce)
|
||||
{
|
||||
if (m_text_editor->IsModified()) {
|
||||
if (m_open_file_path.IsEmpty()) {
|
||||
onFileSaveAs(ce);
|
||||
} else {
|
||||
if (wxFile file (m_open_file_path, wxFile::write); file.IsOpened()) {
|
||||
file.Write(m_text_editor->GetText());
|
||||
file.Close();
|
||||
m_text_editor->DiscardEdits();
|
||||
m_status_bar->SetStatusText("Saved.");
|
||||
} else {
|
||||
m_status_bar->SetStatusText("Save failed: couldn't open file.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m_status_bar->SetStatusText("No modifications to save.");
|
||||
}
|
||||
}
|
||||
|
||||
void MainFrame::onFileSaveAs(wxCommandEvent&)
|
||||
{
|
||||
if (m_text_editor->IsModified()) {
|
||||
wxFileDialog saveDialog(this, "Save filter file", "", "",
|
||||
"C++ source file (*.cpp)|*.cpp",
|
||||
wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
||||
|
||||
if (saveDialog.ShowModal() != wxID_CANCEL) {
|
||||
if (wxFile file (saveDialog.GetPath(), wxFile::write); file.IsOpened()) {
|
||||
file.Write(m_text_editor->GetText());
|
||||
file.Close();
|
||||
m_text_editor->DiscardEdits();
|
||||
m_open_file_path = saveDialog.GetPath();
|
||||
m_status_bar->SetStatusText("Saved.");
|
||||
} else {
|
||||
m_status_bar->SetStatusText("Save failed: couldn't open file.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m_status_bar->SetStatusText("No modifications to save.");
|
||||
}
|
||||
}
|
||||
|
||||
void MainFrame::onFileQuit(wxCommandEvent&)
|
||||
{
|
||||
Close(true);
|
||||
}
|
||||
|
@ -1,233 +0,0 @@
|
||||
#include "wxmain.hpp"
|
||||
#include "wxmain_devdata.h"
|
||||
#include "wxsiggen.hpp"
|
||||
|
||||
#include <wx/menuitem.h>
|
||||
#include <wx/textdlg.h>
|
||||
|
||||
void MainFrame::onRunConnect(wxCommandEvent& ce)
|
||||
{
|
||||
auto menuItem = dynamic_cast<wxMenuItem *>(ce.GetEventUserData());
|
||||
|
||||
if (!m_device) {
|
||||
stmdsp::scanner scanner;
|
||||
if (auto devices = scanner.scan(); devices.size() > 0) {
|
||||
m_device = new stmdsp::device(devices.front());
|
||||
if (m_device->connected()) {
|
||||
auto rate = m_device->get_sample_rate();
|
||||
m_rate_select->SetSelection(rate);
|
||||
|
||||
updateMenuOptions();
|
||||
menuItem->SetItemLabel("&Disconnect");
|
||||
m_status_bar->SetStatusText("Connected.");
|
||||
} else {
|
||||
delete m_device;
|
||||
m_device = nullptr;
|
||||
|
||||
menuItem->SetItemLabel("&Connect");
|
||||
m_status_bar->SetStatusText("Failed to connect.");
|
||||
}
|
||||
} else {
|
||||
m_status_bar->SetStatusText("No devices found.");
|
||||
}
|
||||
} else {
|
||||
delete m_device;
|
||||
m_device = nullptr;
|
||||
updateMenuOptions();
|
||||
menuItem->SetItemLabel("&Connect");
|
||||
m_status_bar->SetStatusText("Disconnected.");
|
||||
}
|
||||
}
|
||||
|
||||
void MainFrame::onRunStart(wxCommandEvent& ce)
|
||||
{
|
||||
auto menuItem = dynamic_cast<wxMenuItem *>(ce.GetEventUserData());
|
||||
|
||||
if (!m_is_running) {
|
||||
if (m_run_measure->IsChecked()) {
|
||||
m_device->continuous_start_measure();
|
||||
m_measure_timer->StartOnce(1000);
|
||||
} else {
|
||||
if (m_device->is_siggening() && m_wav_clip) {
|
||||
m_measure_timer->Start(m_device->get_buffer_size() * 500 /
|
||||
srateNums[m_rate_select->GetSelection()]);
|
||||
} else if (m_conv_result_log) {
|
||||
m_measure_timer->Start(15);
|
||||
} else if (m_run_draw_samples->IsChecked()) {
|
||||
m_measure_timer->Start(300);
|
||||
}
|
||||
|
||||
m_device->continuous_start();
|
||||
}
|
||||
|
||||
m_rate_select->Enable(false);
|
||||
menuItem->SetItemLabel("&Stop");
|
||||
m_status_bar->SetStatusText("Running.");
|
||||
m_is_running = true;
|
||||
} else {
|
||||
m_device->continuous_stop();
|
||||
m_measure_timer->Stop();
|
||||
|
||||
m_rate_select->Enable(true);
|
||||
menuItem->SetItemLabel("&Start");
|
||||
m_status_bar->SetStatusText("Ready.");
|
||||
m_is_running = false;
|
||||
|
||||
if (m_run_draw_samples->IsChecked())
|
||||
m_compile_output->Refresh();
|
||||
}
|
||||
|
||||
updateMenuOptions();
|
||||
}
|
||||
|
||||
void MainFrame::onRunLogResults(wxCommandEvent& ce)
|
||||
{
|
||||
auto menuItem = dynamic_cast<wxMenuItem *>(ce.GetEventUserData());
|
||||
if (menuItem->IsChecked()) {
|
||||
wxFileDialog dialog (this, "Choose log file", "", "", "*.csv",
|
||||
wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
||||
|
||||
if (dialog.ShowModal() != wxID_CANCEL) {
|
||||
if (m_conv_result_log) {
|
||||
m_conv_result_log->Close();
|
||||
delete m_conv_result_log;
|
||||
m_conv_result_log = nullptr;
|
||||
}
|
||||
|
||||
m_conv_result_log = new wxFileOutputStream(dialog.GetPath());
|
||||
}
|
||||
|
||||
m_status_bar->SetStatusText("Ready.");
|
||||
} else if (m_conv_result_log) {
|
||||
m_conv_result_log->Close();
|
||||
delete m_conv_result_log;
|
||||
m_conv_result_log = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void MainFrame::onRunEditBSize(wxCommandEvent&)
|
||||
{
|
||||
wxTextEntryDialog dialog (this, "Enter new buffer size (100-4096)", "Set Buffer Size");
|
||||
if (dialog.ShowModal() == wxID_OK) {
|
||||
if (wxString value = dialog.GetValue(); !value.IsEmpty()) {
|
||||
if (unsigned long n; value.ToULong(&n)) {
|
||||
if (n >= 100 && n <= stmdsp::SAMPLES_MAX) {
|
||||
if ((n & 1) == 1)
|
||||
++n;
|
||||
m_device->continuous_set_buffer_size(n);
|
||||
} else {
|
||||
m_status_bar->SetStatusText("Error: Invalid buffer size.");
|
||||
}
|
||||
} else {
|
||||
m_status_bar->SetStatusText("Error: Invalid buffer size.");
|
||||
}
|
||||
} else {
|
||||
m_status_bar->SetStatusText("Ready.");
|
||||
}
|
||||
} else {
|
||||
m_status_bar->SetStatusText("Ready.");
|
||||
}
|
||||
}
|
||||
|
||||
void MainFrame::onRunGenUpload(wxCommandEvent&)
|
||||
{
|
||||
if (SiggenDialog dialog (this); dialog.ShowModal() == wxID_OK) {
|
||||
auto result = dialog.Result();
|
||||
if (result.Find(".wav") != wxNOT_FOUND) {
|
||||
// Audio
|
||||
m_wav_clip = new wav::clip(result/*.Mid(1)*/);
|
||||
if (m_wav_clip->valid()) {
|
||||
m_status_bar->SetStatusText("Generator ready.");
|
||||
} else {
|
||||
delete m_wav_clip;
|
||||
m_wav_clip = nullptr;
|
||||
m_status_bar->SetStatusText("Error: Bad WAV file.");
|
||||
}
|
||||
} else if (result.find_first_not_of("0123456789 \t\r\n") == wxString::npos) {
|
||||
// List
|
||||
std::vector<stmdsp::dacsample_t> samples;
|
||||
while (!result.IsEmpty() && samples.size() <= stmdsp::SAMPLES_MAX * 2) {
|
||||
if (auto number_end = result.find_first_not_of("0123456789");
|
||||
number_end != wxString::npos && number_end > 0)
|
||||
{
|
||||
auto number = result.Left(number_end);
|
||||
if (unsigned long n; number.ToULong(&n))
|
||||
samples.push_back(n & 4095);
|
||||
|
||||
if (auto next = result.find_first_of("0123456789", number_end + 1);
|
||||
next != wxString::npos)
|
||||
{
|
||||
result = result.Mid(next);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (samples.size() <= stmdsp::SAMPLES_MAX * 2) {
|
||||
// DAC buffer must be of even size
|
||||
if ((samples.size() & 1) == 1)
|
||||
samples.push_back(0);
|
||||
m_device->siggen_upload(&samples[0], samples.size());
|
||||
m_status_bar->SetStatusText("Generator ready.");
|
||||
} else {
|
||||
m_status_bar->SetStatusText(wxString::Format("Error: Too many samples (max is %u).",
|
||||
stmdsp::SAMPLES_MAX * 2));
|
||||
}
|
||||
} else {
|
||||
extern std::vector<stmdsp::dacsample_t> siggen_formula_parse(const std::string&);
|
||||
auto samples = siggen_formula_parse(result.ToStdString());
|
||||
if (samples.size() > 0) {
|
||||
m_device->siggen_upload(&samples[0], samples.size());
|
||||
m_status_bar->SetStatusText("Generator ready.");
|
||||
} else {
|
||||
m_status_bar->SetStatusText("Error: Bad formula.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m_status_bar->SetStatusText("Ready.");
|
||||
}
|
||||
}
|
||||
|
||||
void MainFrame::onRunGenStart(wxCommandEvent& ce)
|
||||
{
|
||||
auto menuItem = dynamic_cast<wxMenuItem *>(ce.GetEventUserData());
|
||||
if (menuItem->IsChecked()) {
|
||||
m_device->siggen_start();
|
||||
menuItem->SetItemLabel("Stop &generator");
|
||||
m_status_bar->SetStatusText("Generator running.");
|
||||
} else {
|
||||
m_device->siggen_stop();
|
||||
menuItem->SetItemLabel("Start &generator");
|
||||
m_status_bar->SetStatusText("Ready.");
|
||||
}
|
||||
}
|
||||
|
||||
void MainFrame::onRunUpload(wxCommandEvent&)
|
||||
{
|
||||
if (auto file = compileEditorCode(); !file.IsEmpty()) {
|
||||
if (wxFileInputStream file_stream (file); file_stream.IsOk()) {
|
||||
auto size = file_stream.GetSize();
|
||||
auto buffer = new unsigned char[size];
|
||||
file_stream.ReadAll(buffer, size);
|
||||
m_device->upload_filter(buffer, size);
|
||||
m_status_bar->SetStatusText("Code uploaded.");
|
||||
} else {
|
||||
m_status_bar->SetStatusText("Couldn't load compiled code.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainFrame::onRunUnload(wxCommandEvent&)
|
||||
{
|
||||
m_device->unload_filter();
|
||||
m_status_bar->SetStatusText("Unloaded code.");
|
||||
}
|
||||
|
||||
void MainFrame::onRunCompile(wxCommandEvent&)
|
||||
{
|
||||
compileEditorCode();
|
||||
}
|
||||
|
@ -1,28 +0,0 @@
|
||||
#include "stmdsp.hpp"
|
||||
#include "exprtk.hpp"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
std::vector<stmdsp::dacsample_t> siggen_formula_parse(const std::string& formulaString)
|
||||
{
|
||||
double x = 0;
|
||||
|
||||
exprtk::symbol_table<double> symbol_table;
|
||||
symbol_table.add_variable("x", x);
|
||||
symbol_table.add_constants();
|
||||
|
||||
exprtk::expression<double> expression;
|
||||
expression.register_symbol_table(symbol_table);
|
||||
|
||||
exprtk::parser<double> parser;
|
||||
parser.compile(formulaString, expression);
|
||||
|
||||
std::vector<stmdsp::dacsample_t> samples;
|
||||
for (x = 0; samples.size() < stmdsp::SAMPLES_MAX; x += 1) {
|
||||
auto y = static_cast<stmdsp::dacsample_t>(expression.value());
|
||||
samples.push_back(y);
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
@ -1,92 +0,0 @@
|
||||
/**
|
||||
* @file wxsiggen.cpp
|
||||
* @brief Dialog prompt for providing signal generator input.
|
||||
*
|
||||
* Copyright (C) 2021 Clyne Sullivan
|
||||
*
|
||||
* Distributed under the GNU GPL v3 or later. You should have received a copy of
|
||||
* the GNU General Public License along with this program.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "wxsiggen.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <wx/filedlg.h>
|
||||
#include <wx/radiobox.h>
|
||||
|
||||
static const std::array<wxString, 3> Sources {{
|
||||
"List",
|
||||
"Formula",
|
||||
"WAV audio"
|
||||
}};
|
||||
static const std::array<wxString, 3> Instructions {{
|
||||
"Enter a list of numbers:",
|
||||
"Enter a formula. f(x) = ",
|
||||
wxEmptyString
|
||||
}};
|
||||
|
||||
SiggenDialog::SiggenDialog(wxWindow *parent) :
|
||||
wxDialog(parent, wxID_ANY, "stmdspgui signal generator", wxDefaultPosition, wxSize(300, 200))
|
||||
{
|
||||
m_instruction = new wxStaticText(this, wxID_ANY, wxEmptyString, wxPoint(10, 70));
|
||||
m_source_list = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxPoint(10, 100), wxSize(280, 30));
|
||||
m_source_math = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxPoint(10, 100), wxSize(280, 30));
|
||||
m_source_file = new wxButton(this, 42, "Choose file...", wxPoint(10, 75), wxSize(280, 50));
|
||||
|
||||
auto radio = new wxRadioBox(this, wxID_ANY, "Source", wxPoint(10, 10), wxSize(280, 50),
|
||||
Sources.size(), Sources.data());
|
||||
auto save = new wxButton(this, 43, "Save", wxPoint(200, 150));
|
||||
|
||||
m_instruction->SetLabel(Instructions[0]);
|
||||
m_source_math->Hide();
|
||||
m_source_file->Hide();
|
||||
|
||||
Bind(wxEVT_RADIOBOX, &SiggenDialog::onSourceChange, this, wxID_ANY, wxID_ANY, radio);
|
||||
Bind(wxEVT_BUTTON, &SiggenDialog::onSourceFile, this, 42, 42, m_source_file);
|
||||
Bind(wxEVT_BUTTON, &SiggenDialog::onSave, this, 43, 43, save);
|
||||
}
|
||||
|
||||
SiggenDialog::~SiggenDialog()
|
||||
{
|
||||
Unbind(wxEVT_BUTTON, &SiggenDialog::onSave, this, 43, 43);
|
||||
Unbind(wxEVT_BUTTON, &SiggenDialog::onSourceFile, this, 42, 42);
|
||||
Unbind(wxEVT_RADIOBOX, &SiggenDialog::onSourceChange, this, wxID_ANY, wxID_ANY);
|
||||
}
|
||||
|
||||
void SiggenDialog::onSourceFile(wxCommandEvent&)
|
||||
{
|
||||
wxFileDialog dialog (this, "Open audio file", "", "",
|
||||
"Audio file (*.wav)|*.wav",
|
||||
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (dialog.ShowModal() != wxID_CANCEL)
|
||||
m_result = dialog.GetPath();
|
||||
}
|
||||
|
||||
void SiggenDialog::onSave(wxCommandEvent&)
|
||||
{
|
||||
if (m_source_list->IsShown())
|
||||
m_result = m_source_list->GetValue();
|
||||
else if (m_source_math->IsShown())
|
||||
m_result = m_source_math->GetValue();
|
||||
|
||||
EndModal(!m_result.IsEmpty() ? wxID_OK : wxID_CANCEL);
|
||||
}
|
||||
|
||||
void SiggenDialog::onSourceChange(wxCommandEvent& ce)
|
||||
{
|
||||
auto radio = dynamic_cast<wxRadioBox*>(ce.GetEventObject());
|
||||
if (radio == nullptr)
|
||||
return;
|
||||
|
||||
m_result.Clear();
|
||||
if (unsigned int selection = static_cast<unsigned int>(radio->GetSelection());
|
||||
selection < Sources.size())
|
||||
{
|
||||
m_instruction->SetLabel(Instructions[selection]);
|
||||
m_source_list->Show(selection == 0);
|
||||
m_source_math->Show(selection == 1);
|
||||
m_source_file->Show(selection == 2);
|
||||
}
|
||||
}
|
||||
|
@ -1,42 +0,0 @@
|
||||
/**
|
||||
* @file wxsiggen.hpp
|
||||
* @brief Dialog prompt for providing signal generator input.
|
||||
*
|
||||
* Copyright (C) 2021 Clyne Sullivan
|
||||
*
|
||||
* Distributed under the GNU GPL v3 or later. You should have received a copy of
|
||||
* the GNU General Public License along with this program.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef WXSIGGEN_HPP_
|
||||
#define WXSIGGEN_HPP_
|
||||
|
||||
#include <wx/button.h>
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/textctrl.h>
|
||||
|
||||
class SiggenDialog : public wxDialog {
|
||||
public:
|
||||
SiggenDialog(wxWindow *parent);
|
||||
~SiggenDialog();
|
||||
|
||||
auto Result() const {
|
||||
return m_result;
|
||||
}
|
||||
|
||||
void onSourceChange(wxCommandEvent&);
|
||||
void onSourceFile(wxCommandEvent&);
|
||||
void onSave(wxCommandEvent&);
|
||||
|
||||
private:
|
||||
wxStaticText *m_instruction = nullptr;
|
||||
wxTextCtrl *m_source_list = nullptr;
|
||||
wxTextCtrl *m_source_math = nullptr;
|
||||
wxButton *m_source_file = nullptr;
|
||||
wxString m_result;
|
||||
};
|
||||
|
||||
#endif // WXSIGGEN_HPP_
|
||||
|
@ -1,3 +1,3 @@
|
||||
source [find interface/stlink.cfg]
|
||||
source [find target/stm32h7x.cfg]
|
||||
source [find target/stm32l4x.cfg]
|
||||
|
||||
|
@ -0,0 +1,295 @@
|
||||
#include "communication.hpp"
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
|
||||
#include "periph/adc.hpp"
|
||||
#include "periph/dac.hpp"
|
||||
#include "periph/usbserial.hpp"
|
||||
#include "elfload.hpp"
|
||||
#include "error.hpp"
|
||||
#include "conversion.hpp"
|
||||
#include "runstatus.hpp"
|
||||
#include "samples.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <tuple>
|
||||
|
||||
__attribute__((section(".stacks")))
|
||||
std::array<char, 4096> CommunicationManager::m_thread_stack = {};
|
||||
|
||||
void CommunicationManager::begin()
|
||||
{
|
||||
chThdCreateStatic(m_thread_stack.data(),
|
||||
m_thread_stack.size(),
|
||||
NORMALPRIO,
|
||||
threadComm,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
static void writeADCBuffer(unsigned char *);
|
||||
static void setBufferSize(unsigned char *);
|
||||
static void updateGenerator(unsigned char *);
|
||||
static void loadAlgorithm(unsigned char *);
|
||||
static void readStatus(unsigned char *);
|
||||
static void startConversionMeasure(unsigned char *);
|
||||
static void startConversion(unsigned char *);
|
||||
static void stopConversion(unsigned char *);
|
||||
static void startGenerator(unsigned char *);
|
||||
static void readADCBuffer(unsigned char *);
|
||||
static void readDACBuffer(unsigned char *);
|
||||
static void unloadAlgorithm(unsigned char *);
|
||||
static void readIdentifier(unsigned char *);
|
||||
static void readExecTime(unsigned char *);
|
||||
static void sampleRate(unsigned char *);
|
||||
static void readConversionResults(unsigned char *);
|
||||
static void readConversionInput(unsigned char *);
|
||||
static void readMessage(unsigned char *);
|
||||
static void stopGenerator(unsigned char *);
|
||||
|
||||
static const std::array<std::pair<char, void (*)(unsigned char *)>, 19> commandTable {{
|
||||
{'A', writeADCBuffer},
|
||||
{'B', setBufferSize},
|
||||
{'D', updateGenerator},
|
||||
{'E', loadAlgorithm},
|
||||
{'I', readStatus},
|
||||
{'M', startConversionMeasure},
|
||||
{'R', startConversion},
|
||||
{'S', stopConversion},
|
||||
{'W', startGenerator},
|
||||
{'a', readADCBuffer},
|
||||
{'d', readDACBuffer},
|
||||
{'e', unloadAlgorithm},
|
||||
{'i', readIdentifier},
|
||||
{'m', readExecTime},
|
||||
{'r', sampleRate},
|
||||
{'s', readConversionResults},
|
||||
{'t', readConversionInput},
|
||||
{'u', readMessage},
|
||||
{'w', stopGenerator}
|
||||
}};
|
||||
|
||||
void CommunicationManager::threadComm(void *)
|
||||
{
|
||||
while (1) {
|
||||
if (USBSerial::isActive()) {
|
||||
// Attempt to receive a command packet
|
||||
if (unsigned char cmd[3]; USBSerial::read(&cmd[0], 1) > 0) {
|
||||
// Packet received, first byte represents the desired command/action
|
||||
auto func = std::find_if(commandTable.cbegin(), commandTable.cend(),
|
||||
[&cmd](const auto& f) { return f.first == cmd[0]; });
|
||||
if (func != commandTable.cend())
|
||||
func->second(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
chThdSleepMicroseconds(100);
|
||||
}
|
||||
}
|
||||
|
||||
void writeADCBuffer(unsigned char *)
|
||||
{
|
||||
USBSerial::read(Samples::In.bytedata(), Samples::In.bytesize());
|
||||
}
|
||||
|
||||
void setBufferSize(unsigned char *cmd)
|
||||
{
|
||||
if (EM.assert(run_status == RunStatus::Idle, Error::NotIdle) &&
|
||||
EM.assert(USBSerial::read(&cmd[1], 2) == 2, Error::BadParamSize))
|
||||
{
|
||||
// count is multiplied by two since this command receives size of buffer
|
||||
// for each algorithm application.
|
||||
unsigned int count = (cmd[1] | (cmd[2] << 8)) * 2;
|
||||
if (EM.assert(count <= MAX_SAMPLE_BUFFER_SIZE, Error::BadParam)) {
|
||||
Samples::In.setSize(count);
|
||||
Samples::Out.setSize(count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateGenerator(unsigned char *cmd)
|
||||
{
|
||||
if (EM.assert(USBSerial::read(&cmd[1], 2) == 2, Error::BadParamSize)) {
|
||||
unsigned int count = cmd[1] | (cmd[2] << 8);
|
||||
if (EM.assert(count <= MAX_SAMPLE_BUFFER_SIZE, Error::BadParam)) {
|
||||
if (!DAC::isSigGenRunning()) {
|
||||
Samples::Generator.setSize(count);
|
||||
USBSerial::read(
|
||||
reinterpret_cast<uint8_t *>(Samples::Generator.data()),
|
||||
Samples::Generator.bytesize());
|
||||
} else {
|
||||
const int more = DAC::sigGenWantsMore();
|
||||
if (more == -1) {
|
||||
USBSerial::write(reinterpret_cast<const uint8_t *>("\0"), 1);
|
||||
} else {
|
||||
USBSerial::write(reinterpret_cast<const uint8_t *>("\1"), 1);
|
||||
|
||||
// Receive streamed samples in half-buffer chunks.
|
||||
USBSerial::read(reinterpret_cast<uint8_t *>(
|
||||
more == 0 ? Samples::Generator.data() : Samples::Generator.middata()),
|
||||
Samples::Generator.bytesize() / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadAlgorithm(unsigned char *cmd)
|
||||
{
|
||||
if (EM.assert(run_status == RunStatus::Idle, Error::NotIdle) &&
|
||||
EM.assert(USBSerial::read(&cmd[1], 2) == 2, Error::BadParamSize))
|
||||
{
|
||||
// Only load the binary if it can fit in the memory reserved for it.
|
||||
unsigned int size = cmd[1] | (cmd[2] << 8);
|
||||
if (EM.assert(size < MAX_ELF_FILE_SIZE, Error::BadUserCodeSize)) {
|
||||
USBSerial::read(ELFManager::fileBuffer(), size);
|
||||
auto success = ELFManager::loadFromInternalBuffer();
|
||||
EM.assert(success, Error::BadUserCodeLoad);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void readStatus(unsigned char *)
|
||||
{
|
||||
unsigned char buf[2] = {
|
||||
static_cast<unsigned char>(run_status),
|
||||
static_cast<unsigned char>(EM.pop())
|
||||
};
|
||||
|
||||
USBSerial::write(buf, sizeof(buf));
|
||||
}
|
||||
|
||||
void startConversionMeasure(unsigned char *)
|
||||
{
|
||||
if (EM.assert(run_status == RunStatus::Idle, Error::NotIdle)) {
|
||||
run_status = RunStatus::Running;
|
||||
ConversionManager::startMeasured();
|
||||
}
|
||||
}
|
||||
|
||||
void startConversion(unsigned char *)
|
||||
{
|
||||
if (EM.assert(run_status == RunStatus::Idle, Error::NotIdle)) {
|
||||
run_status = RunStatus::Running;
|
||||
ConversionManager::start();
|
||||
}
|
||||
}
|
||||
|
||||
void stopConversion(unsigned char *)
|
||||
{
|
||||
if (run_status == RunStatus::Running) {
|
||||
ConversionManager::stop();
|
||||
run_status = RunStatus::Idle;
|
||||
}
|
||||
}
|
||||
|
||||
void startGenerator(unsigned char *)
|
||||
{
|
||||
DAC::start(1, Samples::Generator.data(), Samples::Generator.size());
|
||||
}
|
||||
|
||||
void readADCBuffer(unsigned char *)
|
||||
{
|
||||
USBSerial::write(Samples::In.bytedata(), Samples::In.bytesize());
|
||||
}
|
||||
|
||||
void readDACBuffer(unsigned char *)
|
||||
{
|
||||
|
||||
USBSerial::write(Samples::Out.bytedata(), Samples::Out.bytesize());
|
||||
}
|
||||
|
||||
void unloadAlgorithm(unsigned char *)
|
||||
{
|
||||
ELFManager::unload();
|
||||
}
|
||||
|
||||
void readIdentifier(unsigned char *)
|
||||
{
|
||||
#if defined(TARGET_PLATFORM_H7)
|
||||
USBSerial::write(reinterpret_cast<const uint8_t *>("stmdsph"), 7);
|
||||
#else
|
||||
USBSerial::write(reinterpret_cast<const uint8_t *>("stmdspl"), 7);
|
||||
#endif
|
||||
}
|
||||
|
||||
void readExecTime(unsigned char *)
|
||||
{
|
||||
// Stores the measured execution time.
|
||||
extern time_measurement_t conversion_time_measurement;
|
||||
USBSerial::write(reinterpret_cast<uint8_t *>(&conversion_time_measurement.last),
|
||||
sizeof(rtcnt_t));
|
||||
}
|
||||
|
||||
void sampleRate(unsigned char *cmd)
|
||||
{
|
||||
if (EM.assert(USBSerial::read(&cmd[1], 1) == 1, Error::BadParamSize)) {
|
||||
if (cmd[1] == 0xFF) {
|
||||
unsigned char r = SClock::getRate();
|
||||
USBSerial::write(&r, 1);
|
||||
} else {
|
||||
auto r = static_cast<SClock::Rate>(cmd[1]);
|
||||
SClock::setRate(r);
|
||||
ADC::setRate(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void readConversionResults(unsigned char *)
|
||||
{
|
||||
if (auto samps = Samples::Out.modified(); samps != nullptr) {
|
||||
unsigned char buf[2] = {
|
||||
static_cast<unsigned char>(Samples::Out.size() / 2 & 0xFF),
|
||||
static_cast<unsigned char>(((Samples::Out.size() / 2) >> 8) & 0xFF)
|
||||
};
|
||||
USBSerial::write(buf, 2);
|
||||
unsigned int total = Samples::Out.bytesize() / 2;
|
||||
unsigned int offset = 0;
|
||||
unsigned char unused;
|
||||
while (total > 512) {
|
||||
USBSerial::write(reinterpret_cast<uint8_t *>(samps) + offset, 512);
|
||||
while (USBSerial::read(&unused, 1) == 0);
|
||||
offset += 512;
|
||||
total -= 512;
|
||||
}
|
||||
USBSerial::write(reinterpret_cast<uint8_t *>(samps) + offset, total);
|
||||
while (USBSerial::read(&unused, 1) == 0);
|
||||
} else {
|
||||
USBSerial::write(reinterpret_cast<const uint8_t *>("\0\0"), 2);
|
||||
}
|
||||
}
|
||||
|
||||
void readConversionInput(unsigned char *)
|
||||
{
|
||||
if (auto samps = Samples::In.modified(); samps != nullptr) {
|
||||
unsigned char buf[2] = {
|
||||
static_cast<unsigned char>(Samples::In.size() / 2 & 0xFF),
|
||||
static_cast<unsigned char>(((Samples::In.size() / 2) >> 8) & 0xFF)
|
||||
};
|
||||
USBSerial::write(buf, 2);
|
||||
unsigned int total = Samples::In.bytesize() / 2;
|
||||
unsigned int offset = 0;
|
||||
unsigned char unused;
|
||||
while (total > 512) {
|
||||
USBSerial::write(reinterpret_cast<uint8_t *>(samps) + offset, 512);
|
||||
while (USBSerial::read(&unused, 1) == 0);
|
||||
offset += 512;
|
||||
total -= 512;
|
||||
}
|
||||
USBSerial::write(reinterpret_cast<uint8_t *>(samps) + offset, total);
|
||||
while (USBSerial::read(&unused, 1) == 0);
|
||||
} else {
|
||||
USBSerial::write(reinterpret_cast<const uint8_t *>("\0\0"), 2);
|
||||
}
|
||||
}
|
||||
|
||||
void readMessage(unsigned char *)
|
||||
{
|
||||
//USBSerial::write(reinterpret_cast<uint8_t *>(userMessageBuffer), userMessageSize);
|
||||
}
|
||||
|
||||
void stopGenerator(unsigned char *)
|
||||
{
|
||||
DAC::stop(1);
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @file communication.hpp
|
||||
* @brief Manages communication with the host computer.
|
||||
*
|
||||
* Copyright (C) 2021 Clyne Sullivan
|
||||
*
|
||||
* Distributed under the GNU GPL v3 or later. You should have received a copy of
|
||||
* the GNU General Public License along with this program.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef STMDSP_COMMUNICATION_HPP
|
||||
#define STMDSP_COMMUNICATION_HPP
|
||||
|
||||
#include <array>
|
||||
|
||||
class CommunicationManager
|
||||
{
|
||||
public:
|
||||
static void begin();
|
||||
|
||||
private:
|
||||
static void threadComm(void *);
|
||||
|
||||
static std::array<char, 4096> m_thread_stack;
|
||||
};
|
||||
|
||||
#endif // STMDSP_COMMUNICATION_HPP
|
||||
|
@ -0,0 +1,218 @@
|
||||
/**
|
||||
* @file conversion.cpp
|
||||
* @brief Manages algorithm application (converts input samples to output).
|
||||
*
|
||||
* Copyright (C) 2021 Clyne Sullivan
|
||||
*
|
||||
* Distributed under the GNU GPL v3 or later. You should have received a copy of
|
||||
* the GNU General Public License along with this program.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "conversion.hpp"
|
||||
|
||||
#include "periph/adc.hpp"
|
||||
#include "periph/dac.hpp"
|
||||
#include "elfload.hpp"
|
||||
#include "error.hpp"
|
||||
#include "runstatus.hpp"
|
||||
#include "samples.hpp"
|
||||
|
||||
// MSG_* things below are macros rather than constexpr
|
||||
// to ensure inlining.
|
||||
|
||||
#define MSG_CONVFIRST (1)
|
||||
#define MSG_CONVSECOND (2)
|
||||
#define MSG_CONVFIRST_MEASURE (3)
|
||||
#define MSG_CONVSECOND_MEASURE (4)
|
||||
|
||||
#define MSG_FOR_FIRST(msg) (msg & 1)
|
||||
#define MSG_FOR_MEASURE(msg) (msg > 2)
|
||||
|
||||
__attribute__((section(".convdata")))
|
||||
thread_t *ConversionManager::m_thread_monitor = nullptr;
|
||||
thread_t *ConversionManager::m_thread_runner = nullptr;
|
||||
|
||||
__attribute__((section(".stacks")))
|
||||
std::array<char, 1024> ConversionManager::m_thread_monitor_stack = {};
|
||||
__attribute__((section(".stacks")))
|
||||
std::array<char, THD_WORKING_AREA_SIZE(128)> ConversionManager::m_thread_runner_entry_stack = {};
|
||||
__attribute__((section(".convdata")))
|
||||
std::array<char, CONVERSION_THREAD_STACK_SIZE> ConversionManager::m_thread_runner_stack = {};
|
||||
|
||||
std::array<msg_t, 2> ConversionManager::m_mailbox_buffer;
|
||||
mailbox_t ConversionManager::m_mailbox = _MAILBOX_DATA(m_mailbox, m_mailbox_buffer.data(), m_mailbox_buffer.size());
|
||||
|
||||
void ConversionManager::begin()
|
||||
{
|
||||
m_thread_monitor = chThdCreateStatic(m_thread_monitor_stack.data(),
|
||||
m_thread_monitor_stack.size(),
|
||||
NORMALPRIO + 1,
|
||||
threadMonitor,
|
||||
nullptr);
|
||||
auto runner_stack_end = &m_thread_runner_stack[CONVERSION_THREAD_STACK_SIZE];
|
||||
m_thread_runner = chThdCreateStatic(m_thread_runner_entry_stack.data(),
|
||||
m_thread_runner_entry_stack.size(),
|
||||
HIGHPRIO,
|
||||
threadRunnerEntry,
|
||||
runner_stack_end);
|
||||
}
|
||||
|
||||
void ConversionManager::start()
|
||||
{
|
||||
Samples::Out.clear();
|
||||
ADC::start(Samples::In.data(), Samples::In.size(), adcReadHandler);
|
||||
DAC::start(0, Samples::Out.data(), Samples::Out.size());
|
||||
}
|
||||
|
||||
void ConversionManager::startMeasured()
|
||||
{
|
||||
Samples::Out.clear();
|
||||
ADC::start(Samples::In.data(), Samples::In.size(), adcReadHandlerMeasure);
|
||||
DAC::start(0, Samples::Out.data(), Samples::Out.size());
|
||||
}
|
||||
|
||||
void ConversionManager::stop()
|
||||
{
|
||||
DAC::stop(0);
|
||||
ADC::stop();
|
||||
}
|
||||
|
||||
thread_t *ConversionManager::getMonitorHandle()
|
||||
{
|
||||
return m_thread_monitor;
|
||||
}
|
||||
|
||||
void ConversionManager::abort(bool fpu_stacked)
|
||||
{
|
||||
ELFManager::unload();
|
||||
EM.add(Error::ConversionAborted);
|
||||
//run_status = RunStatus::Recovering;
|
||||
|
||||
// Confirm that the exception return thread is the algorithm...
|
||||
uint32_t *psp;
|
||||
asm("mrs %0, psp" : "=r" (psp));
|
||||
|
||||
bool isRunnerStack =
|
||||
(uint32_t)psp >= reinterpret_cast<uint32_t>(m_thread_runner_stack.data()) &&
|
||||
(uint32_t)psp <= reinterpret_cast<uint32_t>(m_thread_runner_stack.data() +
|
||||
m_thread_runner_stack.size());
|
||||
|
||||
if (isRunnerStack)
|
||||
{
|
||||
// If it is, we can force the algorithm to exit by "resetting" its thread.
|
||||
// We do this by rebuilding the thread's stacked exception return.
|
||||
auto newpsp = reinterpret_cast<uint32_t *>(m_thread_runner_stack.data() +
|
||||
m_thread_runner_stack.size() -
|
||||
(fpu_stacked ? 26 : 8) * sizeof(uint32_t));
|
||||
// Set the LR register to the thread's entry point.
|
||||
newpsp[5] = reinterpret_cast<uint32_t>(threadRunner);
|
||||
// Overwrite the instruction we'll return to with "bx lr" (jump to address in LR).
|
||||
newpsp[6] = psp[6];
|
||||
*reinterpret_cast<uint16_t *>(newpsp[6]) = 0x4770; // "bx lr"
|
||||
// Keep PSR contents (bit set forces Thumb mode, just in case).
|
||||
newpsp[7] = psp[7] | (1 << 24);
|
||||
// Set the new stack pointer.
|
||||
asm("msr psp, %0" :: "r" (newpsp));
|
||||
}
|
||||
}
|
||||
|
||||
void ConversionManager::threadMonitor(void *)
|
||||
{
|
||||
while (1) {
|
||||
msg_t message;
|
||||
msg_t fetch = chMBFetchTimeout(&m_mailbox, &message, TIME_INFINITE);
|
||||
if (fetch == MSG_OK)
|
||||
chMsgSend(m_thread_runner, message);
|
||||
}
|
||||
}
|
||||
|
||||
void ConversionManager::threadRunnerEntry(void *stack)
|
||||
{
|
||||
ELFManager::unload();
|
||||
port_unprivileged_jump(reinterpret_cast<uint32_t>(threadRunner),
|
||||
reinterpret_cast<uint32_t>(stack));
|
||||
}
|
||||
|
||||
__attribute__((section(".convcode")))
|
||||
void ConversionManager::threadRunner(void *)
|
||||
{
|
||||
while (1) {
|
||||
// Sleep until we receive a mailbox message.
|
||||
msg_t message;
|
||||
asm("svc 0; mov %0, r0" : "=r" (message));
|
||||
|
||||
if (message != 0) {
|
||||
auto samples = MSG_FOR_FIRST(message) ? Samples::In.data()
|
||||
: Samples::In.middata();
|
||||
auto size = Samples::In.size() / 2;
|
||||
|
||||
auto entry = ELFManager::loadedElf();
|
||||
if (entry) {
|
||||
// Below, we remember the stack pointer just in case the
|
||||
// loaded algorithm messes things up.
|
||||
uint32_t sp;
|
||||
|
||||
if (!MSG_FOR_MEASURE(message)) {
|
||||
asm("mov %0, sp" : "=r" (sp));
|
||||
samples = entry(samples, size);
|
||||
asm("mov sp, %0" :: "r" (sp));
|
||||
} else {
|
||||
// Start execution timer:
|
||||
asm("mov %0, sp; eor r0, r0; svc 2" : "=r" (sp));
|
||||
samples = entry(samples, size);
|
||||
// Stop execution timer:
|
||||
asm("mov r0, #1; svc 2; mov sp, %0" :: "r" (sp));
|
||||
}
|
||||
}
|
||||
|
||||
// Update the sample out buffer with the transformed samples.
|
||||
if (samples != nullptr) {
|
||||
if (MSG_FOR_FIRST(message))
|
||||
Samples::Out.modify(samples, size);
|
||||
else
|
||||
Samples::Out.midmodify(samples, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConversionManager::adcReadHandler(adcsample_t *buffer, size_t)
|
||||
{
|
||||
chSysLockFromISR();
|
||||
|
||||
// If previous request hasn't been handled, then we're going too slow.
|
||||
// We'll need to abort.
|
||||
if (chMBGetUsedCountI(&m_mailbox) > 1) {
|
||||
chMBResetI(&m_mailbox);
|
||||
chMBResumeX(&m_mailbox);
|
||||
chSysUnlockFromISR();
|
||||
abort();
|
||||
} else {
|
||||
// Mark the modified samples as 'fresh' or ready for manipulation.
|
||||
if (buffer == Samples::In.data()) {
|
||||
Samples::In.setModified();
|
||||
chMBPostI(&m_mailbox, MSG_CONVFIRST);
|
||||
} else {
|
||||
Samples::In.setMidmodified();
|
||||
chMBPostI(&m_mailbox, MSG_CONVSECOND);
|
||||
}
|
||||
chSysUnlockFromISR();
|
||||
}
|
||||
}
|
||||
|
||||
void ConversionManager::adcReadHandlerMeasure(adcsample_t *buffer, size_t)
|
||||
{
|
||||
chSysLockFromISR();
|
||||
if (buffer == Samples::In.data()) {
|
||||
Samples::In.setModified();
|
||||
chMBPostI(&m_mailbox, MSG_CONVFIRST_MEASURE);
|
||||
} else {
|
||||
Samples::In.setMidmodified();
|
||||
chMBPostI(&m_mailbox, MSG_CONVSECOND_MEASURE);
|
||||
}
|
||||
chSysUnlockFromISR();
|
||||
|
||||
ADC::setOperation(adcReadHandler);
|
||||
}
|
||||
|
@ -0,0 +1,64 @@
|
||||
/**
|
||||
* @file conversion.hpp
|
||||
* @brief Manages algorithm application (converts input samples to output).
|
||||
*
|
||||
* Copyright (C) 2021 Clyne Sullivan
|
||||
*
|
||||
* Distributed under the GNU GPL v3 or later. You should have received a copy of
|
||||
* the GNU General Public License along with this program.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef STMDSP_CONVERSION_HPP
|
||||
#define STMDSP_CONVERSION_HPP
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
constexpr unsigned int CONVERSION_THREAD_STACK_SIZE =
|
||||
#if defined(TARGET_PLATFORM_H7)
|
||||
62 * 1024;
|
||||
#else
|
||||
15 * 1024;
|
||||
#endif
|
||||
|
||||
class ConversionManager
|
||||
{
|
||||
public:
|
||||
static void begin();
|
||||
|
||||
// Begins sample conversion.
|
||||
static void start();
|
||||
// Begins conversion with execution time measured.
|
||||
static void startMeasured();
|
||||
// Stops conversion.
|
||||
static void stop();
|
||||
|
||||
static thread_t *getMonitorHandle();
|
||||
|
||||
// Internal only: Aborts a running conversion.
|
||||
static void abort(bool fpu_stacked = true);
|
||||
|
||||
private:
|
||||
static void threadMonitor(void *);
|
||||
static void threadRunnerEntry(void *stack);
|
||||
|
||||
static void threadRunner(void *);
|
||||
static void adcReadHandler(adcsample_t *buffer, size_t);
|
||||
static void adcReadHandlerMeasure(adcsample_t *buffer, size_t);
|
||||
|
||||
static thread_t *m_thread_monitor;
|
||||
static thread_t *m_thread_runner;
|
||||
|
||||
static std::array<char, 1024> m_thread_monitor_stack;
|
||||
static std::array<char, THD_WORKING_AREA_SIZE(128)> m_thread_runner_entry_stack;
|
||||
static std::array<char, CONVERSION_THREAD_STACK_SIZE> m_thread_runner_stack;
|
||||
|
||||
static std::array<msg_t, 2> m_mailbox_buffer;
|
||||
static mailbox_t m_mailbox;
|
||||
};
|
||||
|
||||
#endif // STMDSP_CONVERSION_HPP
|
||||
|
@ -1,26 +0,0 @@
|
||||
/**
|
||||
* @file elf_load.hpp
|
||||
* @brief Loads ELF binary data into memory for execution.
|
||||
*
|
||||
* Copyright (C) 2020 Clyne Sullivan
|
||||
*
|
||||
* Distributed under the GNU GPL v3 or later. You should have received a copy of
|
||||
* the GNU General Public License along with this program.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ELF_LOAD_HPP_
|
||||
#define ELF_LOAD_HPP_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ELF
|
||||
{
|
||||
using Entry = uint16_t *(*)(uint16_t *, size_t);
|
||||
|
||||
Entry load(void *elf_data);
|
||||
}
|
||||
|
||||
#endif // ELF_LOAD_HPP_
|
||||
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @file elfload.hpp
|
||||
* @brief Loads ELF binary data into memory for execution.
|
||||
*
|
||||
* Copyright (C) 2021 Clyne Sullivan
|
||||
*
|
||||
* Distributed under the GNU GPL v3 or later. You should have received a copy of
|
||||
* the GNU General Public License along with this program.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ELF_LOAD_HPP_
|
||||
#define ELF_LOAD_HPP_
|
||||
|
||||
#include "samplebuffer.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
||||
constexpr unsigned int MAX_ELF_FILE_SIZE = 16 * 1024;
|
||||
|
||||
class ELFManager
|
||||
{
|
||||
public:
|
||||
using EntryFunc = Sample *(*)(Sample *, size_t);
|
||||
|
||||
static bool loadFromInternalBuffer();
|
||||
static EntryFunc loadedElf();
|
||||
static unsigned char *fileBuffer();
|
||||
static void unload();
|
||||
|
||||
private:
|
||||
static EntryFunc m_entry;
|
||||
|
||||
static std::array<unsigned char, MAX_ELF_FILE_SIZE> m_file_buffer;
|
||||
};
|
||||
|
||||
#endif // ELF_LOAD_HPP_
|
||||
|
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @file error.cpp
|
||||
* @brief Tracks and reports non-critical errors.
|
||||
*
|
||||
* Copyright (C) 2021 Clyne Sullivan
|
||||
*
|
||||
* Distributed under the GNU GPL v3 or later. You should have received a copy of
|
||||
* the GNU General Public License along with this program.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "error.hpp"
|
||||
|
||||
ErrorManager EM;
|
||||
|
||||
void ErrorManager::add(Error error)
|
||||
{
|
||||
if (m_index < m_queue.size())
|
||||
m_queue[m_index++] = error;
|
||||
}
|
||||
|
||||
bool ErrorManager::assert(bool condition, Error error)
|
||||
{
|
||||
if (!condition)
|
||||
add(error);
|
||||
return condition;
|
||||
}
|
||||
|
||||
bool ErrorManager::hasError()
|
||||
{
|
||||
return m_index > 0;
|
||||
}
|
||||
|
||||
Error ErrorManager::pop()
|
||||
{
|
||||
return m_index == 0 ? Error::None : m_queue[--m_index];
|
||||
}
|
||||
|
@ -0,0 +1,140 @@
|
||||
#include "handlers.hpp"
|
||||
|
||||
#include "adc.hpp"
|
||||
#include "conversion.hpp"
|
||||
#include "cordic.hpp"
|
||||
#include "runstatus.hpp"
|
||||
|
||||
extern "C" {
|
||||
|
||||
time_measurement_t conversion_time_measurement;
|
||||
|
||||
__attribute__((naked))
|
||||
void port_syscall(struct port_extctx *ctxp, uint32_t n)
|
||||
{
|
||||
switch (n) {
|
||||
|
||||
// Sleeps the current thread until a message is received.
|
||||
// Used the algorithm runner to wait for new data.
|
||||
case 0:
|
||||
{
|
||||
chSysLock();
|
||||
chMsgWaitS();
|
||||
auto monitor = ConversionManager::getMonitorHandle();
|
||||
auto msg = chMsgGet(monitor);
|
||||
chMsgReleaseS(monitor, MSG_OK);
|
||||
chSysUnlock();
|
||||
ctxp->r0 = msg;
|
||||
}
|
||||
break;
|
||||
|
||||
// Provides access to advanced math functions.
|
||||
// A service call like this is required for some hardware targets that
|
||||
// provide hardware-accelerated math computations (e.g. CORDIC).
|
||||
case 1:
|
||||
{
|
||||
using mathcall = void (*)();
|
||||
static mathcall funcs[3] = {
|
||||
reinterpret_cast<mathcall>(cordic::sin),
|
||||
reinterpret_cast<mathcall>(cordic::cos),
|
||||
reinterpret_cast<mathcall>(cordic::tan),
|
||||
};
|
||||
#if defined(PLATFORM_H7)
|
||||
asm("vmov.f64 d0, %0, %1" :: "r" (ctxp->r1), "r" (ctxp->r2));
|
||||
if (ctxp->r0 < 3) {
|
||||
funcs[ctxp->r0]();
|
||||
asm("vmov.f64 %0, %1, d0" : "=r" (ctxp->r1), "=r" (ctxp->r2));
|
||||
} else {
|
||||
asm("eor r0, r0; vmov.f64 d0, r0, r0");
|
||||
}
|
||||
#else
|
||||
asm("vmov.f32 s0, %0" :: "r" (ctxp->r1));
|
||||
if (ctxp->r0 < 3) {
|
||||
funcs[ctxp->r0]();
|
||||
asm("vmov.f32 %0, s0" : "=r" (ctxp->r1));
|
||||
} else {
|
||||
asm("eor r0, r0; vmov.f32 s0, r0");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
// Starts or stops precise cycle time measurement.
|
||||
// Used to measure algorithm execution time.
|
||||
case 2:
|
||||
if (ctxp->r0 == 0) {
|
||||
chTMStartMeasurementX(&conversion_time_measurement);
|
||||
} else {
|
||||
chTMStopMeasurementX(&conversion_time_measurement);
|
||||
// Subtract measurement overhead from the result.
|
||||
// Running an empty algorithm ("bx lr") takes 196 cycles as of 2/4/21.
|
||||
// Only measures algorithm code time (loading args/storing result takes 9 cycles).
|
||||
constexpr rtcnt_t measurement_overhead = 196 - 1;
|
||||
if (conversion_time_measurement.last > measurement_overhead)
|
||||
conversion_time_measurement.last -= measurement_overhead;
|
||||
}
|
||||
break;
|
||||
|
||||
// Reads one of the analog inputs made available for algorithm run-time input.
|
||||
case 3:
|
||||
ctxp->r0 = ADC::readAlt(ctxp->r0);
|
||||
break;
|
||||
|
||||
//case 4:
|
||||
// {
|
||||
// const char *str = reinterpret_cast<const char *>(ctxp->r0);
|
||||
// auto src = str;
|
||||
// auto dst = userMessageBuffer;
|
||||
// while (*src)
|
||||
// *dst++ = *src++;
|
||||
// *dst = '\0';
|
||||
// userMessageSize = src - str;
|
||||
// }
|
||||
// break;
|
||||
default:
|
||||
while (1);
|
||||
break;
|
||||
}
|
||||
|
||||
asm("svc 0");
|
||||
while (1);
|
||||
}
|
||||
|
||||
__attribute__((naked))
|
||||
void MemManage_Handler()
|
||||
{
|
||||
// 1. Get the stack pointer.
|
||||
uint32_t lr;
|
||||
asm("mov %0, lr" : "=r" (lr));
|
||||
|
||||
// 2. Recover from the fault.
|
||||
ConversionManager::abort((lr & (1 << 4)) ? false : true);
|
||||
|
||||
// 3. Return.
|
||||
asm("mov lr, %0; bx lr" :: "r" (lr));
|
||||
}
|
||||
|
||||
__attribute__((naked))
|
||||
void HardFault_Handler()
|
||||
{
|
||||
// Get the stack pointer.
|
||||
//uint32_t *stack;
|
||||
uint32_t lr;
|
||||
asm("mov %0, lr" : "=r" (lr));
|
||||
/*asm("\
|
||||
tst lr, #4; \
|
||||
ite eq; \
|
||||
mrseq %0, msp; \
|
||||
mrsne %0, psp; \
|
||||
mov %1, lr; \
|
||||
" : "=r" (stack), "=r" (lr));*/
|
||||
|
||||
// If coming from the algorithm, attempt to recover; otherwise, give up.
|
||||
if (run_status != RunStatus::Running && (lr & 4) != 0)
|
||||
MemManage_Handler();
|
||||
|
||||
while (1);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @file handlers.hpp
|
||||
* @brief Interrupt service routine handlers.
|
||||
*
|
||||
* Copyright (C) 2021 Clyne Sullivan
|
||||
*
|
||||
* Distributed under the GNU GPL v3 or later. You should have received a copy of
|
||||
* the GNU General Public License along with this program.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef STMDSP_HANDLERS_HPP
|
||||
#define STMDSP_HANDLERS_HPP
|
||||
|
||||
#include "ch.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((naked))
|
||||
void port_syscall(struct port_extctx *ctxp, uint32_t n);
|
||||
|
||||
__attribute__((naked))
|
||||
void MemManage_Handler();
|
||||
|
||||
__attribute__((naked))
|
||||
void HardFault_Handler();
|
||||
|
||||
}
|
||||
|
||||
#endif // STMDSP_HANDLERS_HPP
|
||||
|
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* @file monitor.cpp
|
||||
* @brief Manages the device monitoring thread (status LEDs, etc.).
|
||||
*
|
||||
* Copyright (C) 2021 Clyne Sullivan
|
||||
*
|
||||
* Distributed under the GNU GPL v3 or later. You should have received a copy of
|
||||
* the GNU General Public License along with this program.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "monitor.hpp"
|
||||
|
||||
#include "error.hpp"
|
||||
#include "runstatus.hpp"
|
||||
|
||||
#include "hal.h"
|
||||
|
||||
__attribute__((section(".stacks")))
|
||||
std::array<char, THD_WORKING_AREA_SIZE(256)> Monitor::m_thread_stack = {};
|
||||
|
||||
void Monitor::begin()
|
||||
{
|
||||
chThdCreateStatic(m_thread_stack.data(),
|
||||
m_thread_stack.size(),
|
||||
LOWPRIO,
|
||||
threadMonitor,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
void Monitor::threadMonitor(void *)
|
||||
{
|
||||
palSetLineMode(LINE_BUTTON, PAL_MODE_INPUT_PULLUP);
|
||||
auto readButton = [] {
|
||||
#ifdef TARGET_PLATFORM_L4
|
||||
return !palReadLine(LINE_BUTTON);
|
||||
#else
|
||||
return palReadLine(LINE_BUTTON);
|
||||
#endif
|
||||
};
|
||||
|
||||
palSetLine(LINE_LED_RED);
|
||||
palSetLine(LINE_LED_GREEN);
|
||||
palSetLine(LINE_LED_BLUE);
|
||||
|
||||
while (1) {
|
||||
bool isidle = run_status == RunStatus::Idle;
|
||||
auto led = isidle ? LINE_LED_GREEN : LINE_LED_BLUE;
|
||||
auto delay = isidle ? 500 : 250;
|
||||
|
||||
palToggleLine(led);
|
||||
chThdSleepMilliseconds(delay);
|
||||
palToggleLine(led);
|
||||
chThdSleepMilliseconds(delay);
|
||||
|
||||
if (isidle && readButton()) {
|
||||
palClearLine(LINE_LED_GREEN);
|
||||
palClearLine(LINE_LED_BLUE);
|
||||
chSysLock();
|
||||
while (readButton())
|
||||
asm("nop");
|
||||
while (!readButton())
|
||||
asm("nop");
|
||||
chSysUnlock();
|
||||
palSetLine(LINE_LED_GREEN);
|
||||
palSetLine(LINE_LED_BLUE);
|
||||
chThdSleepMilliseconds(500);
|
||||
}
|
||||
|
||||
static bool erroron = false;
|
||||
if (auto err = EM.hasError(); err ^ erroron) {
|
||||
erroron = err;
|
||||
if (err)
|
||||
palClearLine(LINE_LED_RED);
|
||||
else
|
||||
palSetLine(LINE_LED_RED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @file monitor.hpp
|
||||
* @brief Manages the device monitoring thread (status LEDs, etc.).
|
||||
*
|
||||
* Copyright (C) 2021 Clyne Sullivan
|
||||
*
|
||||
* Distributed under the GNU GPL v3 or later. You should have received a copy of
|
||||
* the GNU General Public License along with this program.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef STMDSP_MONITOR_HPP
|
||||
#define STMDSP_MONITOR_HPP
|
||||
|
||||
#include "ch.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
class Monitor
|
||||
{
|
||||
public:
|
||||
static void begin();
|
||||
|
||||
private:
|
||||
static void threadMonitor(void *);
|
||||
|
||||
static std::array<char, THD_WORKING_AREA_SIZE(256)> m_thread_stack;
|
||||
};
|
||||
|
||||
#endif // STMDSP_MONITOR_HPP
|
||||
|
@ -0,0 +1,11 @@
|
||||
// Run status
|
||||
//
|
||||
enum class RunStatus : char
|
||||
{
|
||||
Idle = '1',
|
||||
Running,
|
||||
Recovering
|
||||
};
|
||||
|
||||
extern RunStatus run_status;
|
||||
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @file samples.cpp
|
||||
* @brief Provides sample buffers for inputs and outputs.
|
||||
*
|
||||
* Copyright (C) 2021 Clyne Sullivan
|
||||
*
|
||||
* Distributed under the GNU GPL v3 or later. You should have received a copy of
|
||||
* the GNU General Public License along with this program.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "samples.hpp"
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
static_assert(sizeof(adcsample_t) == sizeof(uint16_t));
|
||||
static_assert(sizeof(dacsample_t) == sizeof(uint16_t));
|
||||
|
||||
#if defined(TARGET_PLATFORM_H7)
|
||||
__attribute__((section(".convdata")))
|
||||
SampleBuffer Samples::In (reinterpret_cast<Sample *>(0x38000000)); // 16k
|
||||
__attribute__((section(".convdata")))
|
||||
SampleBuffer Samples::Out (reinterpret_cast<Sample *>(0x30004000)); // 16k
|
||||
SampleBuffer Samples::SigGen (reinterpret_cast<Sample *>(0x30000000)); // 16k
|
||||
#else
|
||||
__attribute__((section(".convdata")))
|
||||
SampleBuffer Samples::In (reinterpret_cast<Sample *>(0x20008000)); // 16k
|
||||
__attribute__((section(".convdata")))
|
||||
SampleBuffer Samples::Out (reinterpret_cast<Sample *>(0x2000C000)); // 16k
|
||||
SampleBuffer Samples::Generator (reinterpret_cast<Sample *>(0x20010000)); // 16k
|
||||
#endif
|
||||
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @file samples.hpp
|
||||
* @brief Provides sample buffers for inputs and outputs.
|
||||
*
|
||||
* Copyright (C) 2021 Clyne Sullivan
|
||||
*
|
||||
* Distributed under the GNU GPL v3 or later. You should have received a copy of
|
||||
* the GNU General Public License along with this program.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef STMDSP_SAMPLES_HPP
|
||||
#define STMDSP_SAMPLES_HPP
|
||||
|
||||
#include "samplebuffer.hpp"
|
||||
|
||||
class Samples
|
||||
{
|
||||
public:
|
||||
static SampleBuffer In;
|
||||
static SampleBuffer Out;
|
||||
static SampleBuffer Generator;
|
||||
};
|
||||
|
||||
#endif // STMDSP_SAMPLES_HPP
|
||||
|
Loading…
Reference in New Issue