merge in branch devel

master
Clyne 3 years ago
commit e164629b38
Signed by: clyne
GPG Key ID: 3267C8EBF3F9AFC7

11
.gitignore vendored

@ -1,10 +1,9 @@
**/.*
**/*.o
**/*.so
.*
*.o
*.so
*.exe
*.dll
perf*
build
eagle/*#*
gui/stmdspgui
doc/guide.odt
*.exe
*.dll

@ -8,7 +8,7 @@ TARGET_PLATFORM = L4
# Compiler options here.
ifeq ($(USE_OPT),)
USE_OPT = -O0 -ggdb -fomit-frame-pointer -falign-functions=16 --specs=nosys.specs
USE_OPT = -O0 -g3 -ggdb -fomit-frame-pointer -falign-functions=16 --specs=nosys.specs
endif
# C specific options here (added to USE_OPT).
@ -18,7 +18,7 @@ endif
# C++ specific options here (added to USE_OPT).
ifeq ($(USE_CPPOPT),)
USE_CPPOPT = -std=c++2a -fno-rtti
USE_CPPOPT = -std=c++2a -fno-rtti -fno-exceptions
endif
# Enable this if you want the linker to remove unused code and data.
@ -128,8 +128,8 @@ include $(CHIBIOS)/os/rt/rt.mk
include $(CHIBIOS)/os/common/ports/ARMCMx/compilers/GCC/mk/port_v7m.mk
# Auto-build files in ./source recursively.
#include $(CHIBIOS)/tools/mk/autobuild.mk
ALLCSRC += $(wildcard source/*.c)
ALLCPPSRC += $(wildcard source/*.cpp)
ALLCSRC += $(wildcard source/*.c) $(wildcard source/periph/*.c)
ALLCPPSRC += $(wildcard source/*.cpp) $(wildcard source/periph/*.cpp)
ALLASMSRC += $(wildcard source/*.s)
# Other files (optional).
#include $(CHIBIOS)/test/lib/test.mk
@ -158,7 +158,8 @@ ASMSRC = $(ALLASMSRC)
ASMXSRC = $(ALLXASMSRC)
# Inclusion directories.
INCDIR = $(CONFDIR) $(ALLINC) $(TESTINC)
INCDIR = $(CONFDIR) $(ALLINC) $(TESTINC) \
source source/periph
# Define C warning options here.
CWARN = -Wall -Wextra -Wundef -Wstrict-prototypes -pedantic

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

@ -262,5 +262,26 @@ bool mmc_lld_is_write_protected(MMCDriver *mmcp) {
* @note You can add your board-specific code here.
*/
void boardInit(void) {
// Enable the FPU (floating-point unit)
SCB->CPACR |= 0xF << 20;
// Setup the MPU (memory protection unit):
// Region 2: Data for algorithm thread
// Region 3: Code for algorithm thread
// Region 4: User algorithm code
mpuConfigureRegion(MPU_REGION_2,
0x20000000,
MPU_RASR_ATTR_AP_RW_RW | MPU_RASR_ATTR_NON_CACHEABLE |
MPU_RASR_SIZE_64K |
MPU_RASR_ENABLE);
mpuConfigureRegion(MPU_REGION_3,
0x0807F800,
MPU_RASR_ATTR_AP_RO_RO | MPU_RASR_ATTR_NON_CACHEABLE |
MPU_RASR_SIZE_2K |
MPU_RASR_ENABLE);
mpuConfigureRegion(MPU_REGION_4,
0x00000000,
MPU_RASR_ATTR_AP_RW_RW | MPU_RASR_ATTR_NON_CACHEABLE |
MPU_RASR_SIZE_64K |
MPU_RASR_ENABLE);
}

@ -277,5 +277,31 @@ bool mmc_lld_is_write_protected(MMCDriver *mmcp) {
* @note You can add your board-specific code here.
*/
void boardInit(void) {
palSetLineMode(LINE_LED_RED, PAL_MODE_OUTPUT_PUSHPULL);
palSetLineMode(LINE_LED_GREEN, PAL_MODE_OUTPUT_PUSHPULL);
palSetLineMode(LINE_LED_BLUE, PAL_MODE_OUTPUT_PUSHPULL);
palClearLine(LINE_LED_RED);
palClearLine(LINE_LED_GREEN);
palClearLine(LINE_LED_BLUE);
SCB->CPACR |= 0xF << 20; // Enable FPU
// Region 2: Data for algorithm thread and ADC/DAC buffers
// Region 3: Code for algorithm thread
// Region 4: User algorithm code
mpuConfigureRegion(MPU_REGION_2,
0x20008000,
MPU_RASR_ATTR_AP_RW_RW | MPU_RASR_ATTR_NON_CACHEABLE |
MPU_RASR_SIZE_128K |
MPU_RASR_ENABLE);
mpuConfigureRegion(MPU_REGION_3,
0x0807F800,
MPU_RASR_ATTR_AP_RO_RO | MPU_RASR_ATTR_NON_CACHEABLE |
MPU_RASR_SIZE_2K |
MPU_RASR_ENABLE);
mpuConfigureRegion(MPU_REGION_4,
0x10000000,
MPU_RASR_ATTR_AP_RW_RW | MPU_RASR_ATTR_NON_CACHEABLE |
MPU_RASR_SIZE_32K |
MPU_RASR_ENABLE);
}

@ -1502,4 +1502,8 @@ extern "C" {
#endif
#endif /* _FROM_ASM_ */
#define LINE_LED_RED PAL_LINE(GPIOC_BASE, 10U)
#define LINE_LED_GREEN PAL_LINE(GPIOC_BASE, 11U)
#define LINE_LED_BLUE PAL_LINE(GPIOC_BASE, 12U)
#endif /* BOARD_H */

@ -47,11 +47,11 @@
#define STM32_HSE_ENABLED FALSE
#define STM32_LSE_ENABLED FALSE
#define STM32_MSIPLL_ENABLED FALSE
#define STM32_MSIRANGE STM32_MSIRANGE_8M
#define STM32_MSIRANGE STM32_MSIRANGE_4M
#define STM32_MSISRANGE STM32_MSISRANGE_4M
#define STM32_SW STM32_SW_PLL
#define STM32_PLLSRC STM32_PLLSRC_MSI
#define STM32_PLLM_VALUE 2
#define STM32_PLLM_VALUE 1
#define STM32_PLLN_VALUE 72
#define STM32_PLLP_VALUE 7
#define STM32_PLLQ_VALUE 6

@ -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,12 +1,12 @@
/**
* @file elf_format.cpp
* @file elf.h
* @brief Defines ELF binary format info.
*
* Free to use, written by Clyne Sullivan.
*/
#ifndef STMDSP_ELF_FORMAT_HPP_
#define STMDSP_ELF_FORMAT_HPP_
#ifndef STMDSP_ELF_HPP
#define STMDSP_ELF_HPP
#include <cstdint>
@ -96,5 +96,5 @@ typedef struct {
Elf32_Word p_align;
} __attribute__((packed)) Elf32_Phdr;
#endif // STMDSP_ELF_FORMAT_HPP_
#endif // STMDSP_ELF_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_

@ -1,36 +1,58 @@
/**
* @file elf_load.cpp
* @file elfload.cpp
* @brief Loads ELF binary data into memory for execution.
*
* Copyright (C) 2020 Clyne Sullivan
* 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 "elf_load.hpp"
#include "elf_format.hpp"
#include "elfload.hpp"
#include "elf.h"
#include <algorithm>
#include <cstring>
__attribute__((section(".convdata")))
ELFManager::EntryFunc ELFManager::m_entry = nullptr;
std::array<unsigned char, MAX_ELF_FILE_SIZE> ELFManager::m_file_buffer = {};
static const unsigned char elf_header[] = { '\177', 'E', 'L', 'F' };
__attribute__((section(".convcode")))
ELFManager::EntryFunc ELFManager::loadedElf()
{
return m_entry;
}
unsigned char *ELFManager::fileBuffer()
{
return m_file_buffer.data();
}
void ELFManager::unload()
{
m_entry = nullptr;
}
template<typename T>
constexpr static auto ptr_from_offset(void *base, uint32_t offset)
{
return reinterpret_cast<T>(reinterpret_cast<uint8_t *>(base) + offset);
}
namespace ELF {
Entry load(void *elf_data)
bool ELFManager::loadFromInternalBuffer()
{
m_entry = nullptr;
auto elf_data = m_file_buffer.data();
// Check the ELF's header signature
auto ehdr = reinterpret_cast<Elf32_Ehdr *>(elf_data);
if (!std::equal(ehdr->e_ident, ehdr->e_ident + 4, elf_header))
return nullptr;
return false;
// Iterate through program header LOAD sections
bool loaded = false;
@ -54,8 +76,9 @@ Entry load(void *elf_data)
}
return loaded ? reinterpret_cast<Entry>(ehdr->e_entry) : nullptr;
}
if (loaded)
m_entry = reinterpret_cast<ELFManager::EntryFunc>(ehdr->e_entry);
} // namespace ELF
return loaded;
}

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

@ -1,6 +1,18 @@
#include <array>
/**
* @file error.hpp
* @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/>.
*/
#ifndef STMDSP_ERROR_HPP
#define STMDSP_ERROR_HPP
constexpr unsigned int MAX_ERROR_QUEUE_SIZE = 8;
#include <array>
enum class Error : char
{
@ -15,28 +27,20 @@ enum class Error : char
class ErrorManager
{
public:
void add(Error error) {
if (m_index < m_queue.size())
m_queue[m_index++] = error;
}
bool assert(bool condition, Error error) {
if (!condition)
add(error);
return condition;
}
constexpr static unsigned int MAX_ERROR_QUEUE_SIZE = 8;
bool hasError() {
return m_index > 0;
}
Error pop() {
return m_index == 0 ? Error::None : m_queue[--m_index];
}
public:
void add(Error error);
bool assert(bool condition, Error error);
bool hasError();
Error pop();
private:
std::array<Error, MAX_ERROR_QUEUE_SIZE> m_queue;
unsigned int m_index = 0;
};
extern ErrorManager EM;
#endif // STMDSP_ERROR_HPP

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

@ -2,7 +2,7 @@
* @file main.cpp
* @brief Program entry point.
*
* Copyright (C) 2020 Clyne Sullivan
* 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.
@ -12,122 +12,32 @@
#include "ch.h"
#include "hal.h"
static_assert(sizeof(adcsample_t) == sizeof(uint16_t));
static_assert(sizeof(dacsample_t) == sizeof(uint16_t));
#include "adc.hpp"
#include "cordic.hpp"
#include "dac.hpp"
#include "elf_load.hpp"
#include "error.hpp"
#include "samplebuffer.hpp"
#include "sclock.hpp"
#include "usbserial.hpp"
#include <array>
constexpr unsigned int MAX_ELF_FILE_SIZE = 16 * 1024;
enum class RunStatus : char
{
Idle = '1',
Running,
Recovering
};
static RunStatus run_status = RunStatus::Idle;
#define MSG_CONVFIRST (1)
#define MSG_CONVSECOND (2)
#define MSG_CONVFIRST_MEASURE (3)
#define MSG_CONVSECOND_MEASURE (4)
#define MSG_FOR_FIRST(m) (m & 1)
#define MSG_FOR_MEASURE(m) (m > 2)
static ErrorManager EM;
static msg_t conversionMBBuffer[2];
static MAILBOX_DECL(conversionMB, conversionMBBuffer, 2);
// Thread for LED status and wakeup hold
__attribute__((section(".stacks")))
static THD_WORKING_AREA(monitorThreadWA, 256);
static THD_FUNCTION(monitorThread, arg);
// Thread for managing the conversion task
__attribute__((section(".stacks")))
static THD_WORKING_AREA(conversionThreadMonitorWA, 1024);
static THD_FUNCTION(conversionThreadMonitor, arg);
static thread_t *conversionThreadHandle = nullptr;
// Thread for unprivileged algorithm execution
__attribute__((section(".stacks")))
static THD_WORKING_AREA(conversionThreadWA, 128); // All we do is enter unprivileged mode.
static THD_FUNCTION(conversionThread, arg);
constexpr unsigned int conversionThreadUPWASize =
#if defined(TARGET_PLATFORM_H7)
62 * 1024;
#else
15 * 1024;
#endif
__attribute__((section(".convdata")))
static THD_WORKING_AREA(conversionThreadUPWA, conversionThreadUPWASize);
__attribute__((section(".convdata")))
static thread_t *conversionThreadMonitorHandle = nullptr;
// Thread for USB monitoring
__attribute__((section(".stacks")))
static THD_WORKING_AREA(communicationThreadWA, 4096);
static THD_FUNCTION(communicationThread, arg);
#include "runstatus.hpp"
RunStatus run_status = RunStatus::Idle;
static time_measurement_t conversion_time_measurement;
#if defined(TARGET_PLATFORM_H7)
__attribute__((section(".convdata")))
static SampleBuffer samplesIn (reinterpret_cast<Sample *>(0x38000000)); // 16k
__attribute__((section(".convdata")))
static SampleBuffer samplesOut (reinterpret_cast<Sample *>(0x30004000)); // 16k
static SampleBuffer samplesSigGen (reinterpret_cast<Sample *>(0x30000000)); // 16k
#else
__attribute__((section(".convdata")))
static SampleBuffer samplesIn (reinterpret_cast<Sample *>(0x20008000)); // 16k
__attribute__((section(".convdata")))
static SampleBuffer samplesOut (reinterpret_cast<Sample *>(0x2000C000)); // 16k
static SampleBuffer samplesSigGen (reinterpret_cast<Sample *>(0x20010000)); // 16k
#endif
// Other variables
//
//static char userMessageBuffer[128];
//static unsigned char userMessageSize = 0;
static unsigned char elf_file_store[MAX_ELF_FILE_SIZE];
__attribute__((section(".convdata")))
static ELF::Entry elf_entry = nullptr;
__attribute__((section(".convcode")))
static void conversion_unprivileged_main();
static void mpu_setup();
static void abortAlgorithmFromISR();
static void signal_operate(adcsample_t *buffer, size_t count);
static void signal_operate_measure(adcsample_t *buffer, size_t count);
#if defined(TARGET_PLATFORM_L4)
constexpr auto LINE_LED_GREEN = PAL_LINE(GPIOC_BASE, 10U);
constexpr auto LINE_LED_YELLOW = PAL_LINE(GPIOC_BASE, 11U);
constexpr auto LINE_LED_RED = PAL_LINE(GPIOC_BASE, 12U);
#endif
#include "conversion.hpp"
#include "communication.hpp"
#include "monitor.hpp"
int main()
{
// Initialize the RTOS
// Initialize ChibiOS
halInit();
chSysInit();
SCB->CPACR |= 0xF << 20; // Enable FPU
mpu_setup();
#if defined(TARGET_PLATFORM_L4)
palSetLineMode(LINE_LED_GREEN, PAL_MODE_OUTPUT_PUSHPULL);
palSetLineMode(LINE_LED_YELLOW, PAL_MODE_OUTPUT_PUSHPULL);
palSetLineMode(LINE_LED_RED, PAL_MODE_OUTPUT_PUSHPULL);
#endif
// Init peripherials
ADC::begin();
DAC::begin();
SClock::begin();
@ -137,561 +47,12 @@ int main()
SClock::setRate(SClock::Rate::R32K);
ADC::setRate(SClock::Rate::R32K);
chTMObjectInit(&conversion_time_measurement);
chThdCreateStatic(
monitorThreadWA, sizeof(monitorThreadWA),
LOWPRIO,
monitorThread, nullptr);
conversionThreadMonitorHandle = chThdCreateStatic(
conversionThreadMonitorWA, sizeof(conversionThreadMonitorWA),
NORMALPRIO + 1,
conversionThreadMonitor, nullptr);
conversionThreadHandle = chThdCreateStatic(
conversionThreadWA, sizeof(conversionThreadWA),
HIGHPRIO,
conversionThread,
reinterpret_cast<void *>(reinterpret_cast<uint32_t>(conversionThreadUPWA) +
conversionThreadUPWASize));
chThdCreateStatic(
communicationThreadWA, sizeof(communicationThreadWA),
NORMALPRIO,
communicationThread, nullptr);
// Start our threads.
ConversionManager::begin();
CommunicationManager::begin();
Monitor::begin();
chThdExit(0);
return 0;
}
THD_FUNCTION(communicationThread, arg)
{
(void)arg;
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
switch (cmd[0]) {
// 'a' - Read contents of ADC buffer.
// 'A' - Write contents of ADC buffer.
// 'B' - Set ADC/DAC buffer size.
// 'd' - Read contents of DAC buffer.
// 'D' - Set siggen size and write to its buffer.
// 'E' - Load algorithm binary.
// 'e' - Unload algorithm.
// 'i' - Read "stmdsp" identifier string.
// 'I' - Read status information.
// 'M' - Begin conversion, measure algorithm execution time.
// 'm' - Read last algorithm execution time.
// 'R' - Begin conversion.
// 'r' - Read or write sample rate.
// 'S' - Stop conversion.
// 's' - Get latest block of conversion results.
// 't' - Get latest block of conversion input.
// 'W' - Start signal generator (siggen).
// 'w' - Stop siggen.
case 'a':
USBSerial::write(samplesIn.bytedata(), samplesIn.bytesize());
break;
case 'A':
USBSerial::read(samplesIn.bytedata(), samplesIn.bytesize());
break;
case 'B':
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)) {
samplesIn.setSize(count);
samplesOut.setSize(count);
}
}
break;
case 'd':
USBSerial::write(samplesOut.bytedata(), samplesOut.bytesize());
break;
case 'D':
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)) {
samplesSigGen.setSize(count);
USBSerial::read(samplesSigGen.bytedata(), samplesSigGen.bytesize());
}
}
break;
// 'E' - Reads in and loads the compiled conversion code binary from USB.
case 'E':
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 < sizeof(elf_file_store), Error::BadUserCodeSize)) {
USBSerial::read(elf_file_store, size);
elf_entry = ELF::load(elf_file_store);
EM.assert(elf_entry != nullptr, Error::BadUserCodeLoad);
}
}
break;
// 'e' - Unloads the currently loaded conversion code
case 'e':
elf_entry = nullptr;
break;
// 'i' - Sends an identifying string to confirm that this is the stmdsp device.
case 'i':
#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
break;
// 'I' - Sends the current run status.
case 'I':
{
unsigned char buf[2] = {
static_cast<unsigned char>(run_status),
static_cast<unsigned char>(EM.pop())
};
USBSerial::write(buf, sizeof(buf));
}
break;
// 'M' - Begins continuous sampling, but measures the execution time of the first
// sample processing. This duration can be later read through 'm'.
case 'M':
if (EM.assert(run_status == RunStatus::Idle, Error::NotIdle)) {
run_status = RunStatus::Running;
samplesOut.clear();
ADC::start(samplesIn.data(), samplesIn.size(), signal_operate_measure);
DAC::start(0, samplesOut.data(), samplesOut.size());
}
break;
// 'm' - Returns the last measured sample processing time, presumably in processor
// ticks.
case 'm':
USBSerial::write(reinterpret_cast<uint8_t *>(&conversion_time_measurement.last),
sizeof(rtcnt_t));
break;
// 'R' - Begin continuous sampling/conversion of the ADC. Samples will go through
// the conversion code, and will be sent out over the DAC.
case 'R':
if (EM.assert(run_status == RunStatus::Idle, Error::NotIdle)) {
run_status = RunStatus::Running;
samplesOut.clear();
ADC::start(samplesIn.data(), samplesIn.size(), signal_operate);
DAC::start(0, samplesOut.data(), samplesOut.size());
}
break;
case 'r':
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);
}
}
break;
// 'S' - Stops the continuous sampling/conversion.
case 'S':
if (run_status == RunStatus::Running) {
DAC::stop(0);
ADC::stop();
run_status = RunStatus::Idle;
}
break;
case 's':
if (auto samps = samplesOut.modified(); samps != nullptr) {
unsigned char buf[2] = {
static_cast<unsigned char>(samplesOut.size() / 2 & 0xFF),
static_cast<unsigned char>(((samplesOut.size() / 2) >> 8) & 0xFF)
};
USBSerial::write(buf, 2);
unsigned int total = samplesOut.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);
}
break;
case 't':
if (auto samps = samplesIn.modified(); samps != nullptr) {
unsigned char buf[2] = {
static_cast<unsigned char>(samplesIn.size() / 2 & 0xFF),
static_cast<unsigned char>(((samplesIn.size() / 2) >> 8) & 0xFF)
};
USBSerial::write(buf, 2);
unsigned int total = samplesIn.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);
}
break;
case 'W':
DAC::start(1, samplesSigGen.data(), samplesSigGen.size());
break;
case 'w':
DAC::stop(1);
break;
default:
break;
}
}
}
chThdSleepMicroseconds(100);
}
}
THD_FUNCTION(conversionThreadMonitor, arg)
{
(void)arg;
while (1) {
msg_t message;
if (chMBFetchTimeout(&conversionMB, &message, TIME_INFINITE) == MSG_OK)
chMsgSend(conversionThreadHandle, message);
}
}
THD_FUNCTION(conversionThread, stack)
{
elf_entry = nullptr;
port_unprivileged_jump(reinterpret_cast<uint32_t>(conversion_unprivileged_main),
reinterpret_cast<uint32_t>(stack));
}
THD_FUNCTION(monitorThread, arg)
{
(void)arg;
palSetLineMode(LINE_BUTTON, PAL_MODE_INPUT_PULLUP);
auto readButton = [] {
#if defined(TARGET_PLATFORM_L4)
return !palReadLine(LINE_BUTTON);
#else
return palReadLine(LINE_BUTTON);
#endif
};
palClearLine(LINE_LED_RED);
palClearLine(LINE_LED_YELLOW);
while (1) {
bool isidle = run_status == RunStatus::Idle;
auto led = isidle ? LINE_LED_GREEN : LINE_LED_YELLOW;
auto delay = isidle ? 500 : 250;
palSetLine(led);
chThdSleepMilliseconds(delay);
palClearLine(led);
chThdSleepMilliseconds(delay);
if (run_status == RunStatus::Idle && readButton()) {
palSetLine(LINE_LED_RED);
palSetLine(LINE_LED_YELLOW);
chSysLock();
while (readButton())
asm("nop");
while (!readButton())
asm("nop");
chSysUnlock();
palClearLine(LINE_LED_RED);
palClearLine(LINE_LED_YELLOW);
chThdSleepMilliseconds(500);
}
static bool erroron = false;
if (auto err = EM.hasError(); err ^ erroron) {
erroron = err;
if (err)
palSetLine(LINE_LED_RED);
else
palClearLine(LINE_LED_RED);
}
}
}
void conversion_unprivileged_main()
{
while (1) {
msg_t message;
asm("svc 0; mov %0, r0" : "=r" (message)); // sleep until next message
if (message != 0) {
auto samples = MSG_FOR_FIRST(message) ? samplesIn.data() : samplesIn.middata();
auto size = samplesIn.size() / 2;
if (elf_entry) {
if (!MSG_FOR_MEASURE(message)) {
// Remember the stack pointer in case the algorithm messes things up.
uint32_t sp;
asm("mov %0, sp" : "=r" (sp));
samples = elf_entry(samples, size);
asm("mov sp, %0" :: "r" (sp));
} else {
uint32_t sp;
asm("mov %0, sp; eor r0, r0; svc 2" : "=r" (sp)); // start measurement
samples = elf_entry(samples, size);
asm("mov r0, #1; svc 2; mov sp, %0" :: "r" (sp)); // stop measurement
}
}
if (samples != nullptr) {
if (MSG_FOR_FIRST(message))
samplesOut.modify(samples, size);
else
samplesOut.midmodify(samples, size);
}
}
}
}
void mpu_setup()
{
// Set up MPU for user algorithm
#if defined(TARGET_PLATFORM_H7)
// Region 2: Data for algorithm thread
// Region 3: Code for algorithm thread
// Region 4: User algorithm code
mpuConfigureRegion(MPU_REGION_2,
0x20000000,
MPU_RASR_ATTR_AP_RW_RW | MPU_RASR_ATTR_NON_CACHEABLE |
MPU_RASR_SIZE_64K |
MPU_RASR_ENABLE);
mpuConfigureRegion(MPU_REGION_3,
0x0807F800,
MPU_RASR_ATTR_AP_RO_RO | MPU_RASR_ATTR_NON_CACHEABLE |
MPU_RASR_SIZE_2K |
MPU_RASR_ENABLE);
mpuConfigureRegion(MPU_REGION_4,
0x00000000,
MPU_RASR_ATTR_AP_RW_RW | MPU_RASR_ATTR_NON_CACHEABLE |
MPU_RASR_SIZE_64K |
MPU_RASR_ENABLE);
#else
// Region 2: Data for algorithm thread and ADC/DAC buffers
// Region 3: Code for algorithm thread
// Region 4: User algorithm code
mpuConfigureRegion(MPU_REGION_2,
0x20008000,
MPU_RASR_ATTR_AP_RW_RW | MPU_RASR_ATTR_NON_CACHEABLE |
MPU_RASR_SIZE_128K|
MPU_RASR_ENABLE);
mpuConfigureRegion(MPU_REGION_3,
0x0807F800,
MPU_RASR_ATTR_AP_RO_RO | MPU_RASR_ATTR_NON_CACHEABLE |
MPU_RASR_SIZE_2K |
MPU_RASR_ENABLE);
mpuConfigureRegion(MPU_REGION_4,
0x10000000,
MPU_RASR_ATTR_AP_RW_RW | MPU_RASR_ATTR_NON_CACHEABLE |
MPU_RASR_SIZE_32K |
MPU_RASR_ENABLE);
#endif
}
void abortAlgorithmFromISR()
{
elf_entry = nullptr;
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));
if ((uint32_t)psp >= (uint32_t)conversionThreadUPWA &&
(uint32_t)psp <= (uint32_t)conversionThreadUPWA + conversionThreadUPWASize)
{
// 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.
uint32_t *newpsp = reinterpret_cast<uint32_t *>(
(char *)conversionThreadUPWA +
conversionThreadUPWASize - 8 * sizeof(uint32_t));
// Set the LR register to the thread's entry point.
newpsp[5] = reinterpret_cast<uint32_t>(conversion_unprivileged_main);
// 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 signal_operate(adcsample_t *buffer, size_t)
{
chSysLockFromISR();
if (chMBGetUsedCountI(&conversionMB) > 1) {
chMBResetI(&conversionMB);
chMBResumeX(&conversionMB);
chSysUnlockFromISR();
abortAlgorithmFromISR();
} else {
if (buffer == samplesIn.data()) {
samplesIn.setModified();
chMBPostI(&conversionMB, MSG_CONVFIRST);
} else {
samplesIn.setMidmodified();
chMBPostI(&conversionMB, MSG_CONVSECOND);
}
chSysUnlockFromISR();
}
}
void signal_operate_measure(adcsample_t *buffer, [[maybe_unused]] size_t count)
{
chSysLockFromISR();
if (buffer == samplesIn.data()) {
samplesIn.setModified();
chMBPostI(&conversionMB, MSG_CONVFIRST_MEASURE);
} else {
samplesIn.setMidmodified();
chMBPostI(&conversionMB, MSG_CONVSECOND_MEASURE);
}
chSysUnlockFromISR();
ADC::setOperation(signal_operate);
}
extern "C" {
__attribute__((naked))
void port_syscall(struct port_extctx *ctxp, uint32_t n)
{
switch (n) {
case 0:
{
chSysLock();
chMsgWaitS();
auto msg = chMsgGet(conversionThreadMonitorHandle);
chMsgReleaseS(conversionThreadMonitorHandle, MSG_OK);
chSysUnlock();
ctxp->r0 = msg;
}
break;
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;
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;
case 3:
ctxp->r0 = ADC::readAlt(0);
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.
abortAlgorithmFromISR();
// 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,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

@ -2,7 +2,7 @@
* @file adc.cpp
* @brief Manages signal reading through the ADC.
*
* Copyright (C) 2020 Clyne Sullivan
* 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.
@ -39,7 +39,7 @@ ADCConversionGroup ADC::m_group_config = {
.end_cb = ADC::conversionCallback,
.error_cb = nullptr,
.cfgr = ADC_CFGR_EXTEN_RISING | ADC_CFGR_EXTSEL_SRC(13), /* TIM6_TRGO */
.cfgr2 = ADC_CFGR2_ROVSE | ADC_CFGR2_OVSR_1 | ADC_CFGR2_OVSS_0, // Oversampling 2x
.cfgr2 = 0,//ADC_CFGR2_ROVSE | ADC_CFGR2_OVSR_1 | ADC_CFGR2_OVSS_0, // Oversampling 2x
#if defined(TARGET_PLATFORM_H7)
.ccr = 0,
.pcsel = 0,
@ -69,11 +69,11 @@ static void readAltCallback(ADCDriver *)
}
ADCConversionGroup ADC::m_group_config2 = {
.circular = false,
.num_channels = 1,
.num_channels = 2,
.end_cb = readAltCallback,
.error_cb = nullptr,
.cfgr = ADC_CFGR_EXTEN_RISING | ADC_CFGR_EXTSEL_SRC(13), /* TIM6_TRGO */
.cfgr2 = ADC_CFGR2_ROVSE | ADC_CFGR2_OVSR_1 | ADC_CFGR2_OVSS_0, // Oversampling 2x
.cfgr2 = 0,//ADC_CFGR2_ROVSE | ADC_CFGR2_OVSR_1 | ADC_CFGR2_OVSS_0, // Oversampling 2x
#if defined(TARGET_PLATFORM_H7)
.ccr = 0,
.pcsel = 0,
@ -88,10 +88,10 @@ ADCConversionGroup ADC::m_group_config2 = {
.awd3cr = 0,
#endif
.smpr = {
ADC_SMPR1_SMP_AN1(ADC_SMPR_SMP_12P5), 0
ADC_SMPR1_SMP_AN1(ADC_SMPR_SMP_2P5) | ADC_SMPR1_SMP_AN2(ADC_SMPR_SMP_2P5), 0
},
.sqr = {
ADC_SQR1_SQ1_N(ADC_CHANNEL_IN1),
ADC_SQR1_SQ1_N(ADC_CHANNEL_IN1) | ADC_SQR1_SQ2_N(ADC_CHANNEL_IN2),
0, 0, 0
},
};
@ -107,6 +107,7 @@ void ADC::begin()
#else
palSetPadMode(GPIOA, 0, PAL_MODE_INPUT_ANALOG); // Algorithm in
palSetPadMode(GPIOC, 0, PAL_MODE_INPUT_ANALOG); // Potentiometer 1
palSetPadMode(GPIOC, 1, PAL_MODE_INPUT_ANALOG); // Potentiometer 2
#endif
adcStart(m_driver, &m_config);
@ -135,15 +136,15 @@ void ADC::stop()
adcsample_t ADC::readAlt(unsigned int id)
{
if (id != 0)
if (id > 1)
return 0;
static adcsample_t result[32] = {};
static adcsample_t result[16] = {};
readAltDone = false;
adcStartConversion(m_driver2, &m_group_config2, result, 32);
while (!readAltDone)
;
adcStartConversion(m_driver2, &m_group_config2, result, 8);
while (!readAltDone);
//__WFI();
adcStopConversion(m_driver2);
return result[0];
return result[id];
}
void ADC::setRate(SClock::Rate rate)
@ -181,13 +182,29 @@ void ADC::setRate(SClock::Rate rate)
adcStart(m_driver, &m_config);
#elif defined(TARGET_PLATFORM_L4)
std::array<std::array<uint32_t, 3>, 6> m_rate_presets = {{
// PLLSAI2 sources MSI of 4MHz, divided by PLLM of /1 = 4MHz.
// 4MHz is then multiplied by PLLSAI2N (x8 to x86), with result
// between 64 and 344 MHz.
//
// SAI2N MUST BE AT LEAST 16 TO MAKE 64MHz MINIMUM.
//
// That is then divided by PLLSAI2R:
// R of 0 = /2; 1 = /4, 2 = /6, 3 = /8.
// PLLSAI2 then feeds into the ADC, which has a prescaler of /10.
// Finally, the ADC's SMP value produces the desired sample rate.
//
// 4MHz * N / R / 10 / SMP = sample rate.
//
// With oversampling, must create faster clock
// (x2 oversampling requires x2 sample rate clock).
//
// Rate PLLSAI2N R SMPR
{/* 8k */ 8, 1, ADC_SMPR_SMP_12P5},
{/* 16k */ 16, 1, ADC_SMPR_SMP_12P5},
{/* 20k */ 20, 1, ADC_SMPR_SMP_12P5},
{/* 32k */ 32, 1, ADC_SMPR_SMP_12P5},
{/* 48k */ 24, 0, ADC_SMPR_SMP_12P5},
{/* 96k */ 73, 1, ADC_SMPR_SMP_6P5} // Technically 96.05263kS/s
{/* 8k */ 16, 1, ADC_SMPR_SMP_12P5}, // R3=32k (min), R1=64k
{/* 16k */ 16, 0, ADC_SMPR_SMP_12P5},
{/* 20k */ 20, 0, ADC_SMPR_SMP_12P5},
{/* 32k */ 32, 0, ADC_SMPR_SMP_12P5},
{/* 48k */ 48, 0, ADC_SMPR_SMP_12P5},
{/* 96k */ 73, 0, ADC_SMPR_SMP_6P5} // Technically 96.05263kS/s
}};
auto& preset = m_rate_presets[static_cast<int>(rate)];
@ -204,9 +221,9 @@ void ADC::setRate(SClock::Rate rate)
m_group_config.smpr[0] = ADC_SMPR1_SMP_AN5(smpr);
// Set 2x oversampling
m_group_config.cfgr2 = ADC_CFGR2_ROVSE | ADC_CFGR2_OVSR_0 | ADC_CFGR2_OVSS_1;
m_group_config2.cfgr2 = ADC_CFGR2_ROVSE | ADC_CFGR2_OVSR_0 | ADC_CFGR2_OVSS_1;
// 8x oversample
m_group_config.cfgr2 = ADC_CFGR2_ROVSE | (2 << ADC_CFGR2_OVSR_Pos) | (3 << ADC_CFGR2_OVSS_Pos);
m_group_config2.cfgr2 = ADC_CFGR2_ROVSE | (2 << ADC_CFGR2_OVSR_Pos) | (3 << ADC_CFGR2_OVSS_Pos);
#endif
}

@ -2,7 +2,7 @@
* @file adc.hpp
* @brief Manages signal reading through the ADC.
*
* Copyright (C) 2020 Clyne Sullivan
* 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.

@ -2,7 +2,7 @@
* @file dac.cpp
* @brief Manages signal creation using the DAC.
*
* Copyright (C) 2020 Clyne Sullivan
* 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.
@ -17,14 +17,21 @@ DACDriver *DAC::m_driver[2] = {
};
const DACConfig DAC::m_config = {
.init = 0,
.init = 2048,
.datamode = DAC_DHRM_12BIT_RIGHT,
.cr = 0
};
static int dacIsDone = -1;
static void dacEndCallback(DACDriver *dacd)
{
if (dacd == &DACD2)
dacIsDone = dacIsBufferComplete(dacd) ? 1 : 0;
}
const DACConversionGroup DAC::m_group_config = {
.num_channels = 1,
.end_cb = nullptr,
.end_cb = dacEndCallback,
.error_cb = nullptr,
#if defined(TARGET_PLATFORM_H7)
.trigger = 5 // TIM6_TRGO
@ -45,11 +52,29 @@ void DAC::begin()
void DAC::start(int channel, dacsample_t *buffer, size_t count)
{
if (channel >= 0 && channel < 2) {
if (channel == 1)
dacIsDone = -1;
dacStartConversion(m_driver[channel], &m_group_config, buffer, count);
SClock::start();
}
}
int DAC::sigGenWantsMore()
{
if (dacIsDone != -1) {
int tmp = dacIsDone;
dacIsDone = -1;
return tmp;
} else {
return -1;
}
}
int DAC::isSigGenRunning()
{
return m_driver[1]->state == DAC_ACTIVE;
}
void DAC::stop(int channel)
{
if (channel >= 0 && channel < 2) {

@ -2,7 +2,7 @@
* @file dac.hpp
* @brief Manages signal creation using the DAC.
*
* Copyright (C) 2020 Clyne Sullivan
* 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.
@ -23,6 +23,9 @@ public:
static void start(int channel, dacsample_t *buffer, size_t count);
static void stop(int channel);
static int sigGenWantsMore();
static int isSigGenRunning();
private:
static DACDriver *m_driver[2];

@ -2,7 +2,7 @@
* @file usbserial.cpp
* @brief Wrapper for ChibiOS's SerialUSBDriver.
*
* Copyright (C) 2020 Clyne Sullivan
* 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.
@ -32,7 +32,7 @@ bool USBSerial::isActive()
{
if (auto config = m_driver->config; config != nullptr) {
if (auto usbp = config->usbp; usbp != nullptr)
return usbp->state == USB_ACTIVE;
return usbp->state == USB_ACTIVE && !ibqIsEmptyI(&m_driver->ibqueue);
}
return false;

@ -2,7 +2,7 @@
* @file usbserial.hpp
* @brief Wrapper for ChibiOS's SerialUSBDriver.
*
* Copyright (C) 2020 Clyne Sullivan
* 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.

@ -0,0 +1,11 @@
// Run status
//
enum class RunStatus : char
{
Idle = '1',
Running,
Recovering
};
extern RunStatus run_status;

@ -1,3 +1,14 @@
/**
* @file samplebuffer.cpp
* @brief Manages ADC/DAC buffer data.
*
* 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 "samplebuffer.hpp"
SampleBuffer::SampleBuffer(Sample *buffer) :
@ -9,16 +20,62 @@ void SampleBuffer::clear() {
__attribute__((section(".convcode")))
void SampleBuffer::modify(Sample *data, unsigned int srcsize) {
auto size = srcsize < m_size ? srcsize : m_size;
size = (size + 15) & (~15);
m_modified = m_buffer;
for (Sample *d = m_buffer, *s = data; s != data + size;)
*d++ = *s++;
const int *src = reinterpret_cast<const int *>(data);
const int * const srcend = src + (size / 2);
int *dst = reinterpret_cast<int *>(m_buffer);
do {
int a = src[0];
int b = src[1];
int c = src[2];
int d = src[3];
int e = src[4];
int f = src[5];
int g = src[6];
int h = src[7];
dst[0] = a;
dst[1] = b;
dst[2] = c;
dst[3] = d;
dst[4] = e;
dst[5] = f;
dst[6] = g;
dst[7] = h;
src += 8;
dst += 8;
} while (src < srcend);
}
__attribute__((section(".convcode")))
void SampleBuffer::midmodify(Sample *data, unsigned int srcsize) {
auto size = srcsize < m_size / 2 ? srcsize : m_size / 2;
size = (size + 15) & (~15);
m_modified = middata();
for (Sample *d = middata(), *s = data; s != data + size;)
*d++ = *s++;
const int *src = reinterpret_cast<const int *>(data);
const int * const srcend = src + (size / 2);
int *dst = reinterpret_cast<int *>(middata());
do {
int a = src[0];
int b = src[1];
int c = src[2];
int d = src[3];
int e = src[4];
int f = src[5];
int g = src[6];
int h = src[7];
dst[0] = a;
dst[1] = b;
dst[2] = c;
dst[3] = d;
dst[4] = e;
dst[5] = f;
dst[6] = g;
dst[7] = h;
src += 8;
dst += 8;
} while (src < srcend);
}
void SampleBuffer::setModified() {

@ -1,3 +1,14 @@
/**
* @file samplebuffer.hpp
* @brief Manages ADC/DAC buffer data.
*
* 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 SAMPLEBUFFER_HPP_
#define SAMPLEBUFFER_HPP_

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

@ -1,3 +1,14 @@
/**
* @file sclock.cpp
* @brief Manages sampling rate clock speeds.
*
* 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 "sclock.hpp"
GPTDriver *SClock::m_timer = &GPTD6;
@ -24,7 +35,12 @@ const std::array<unsigned int, 6> SClock::m_rate_divs = {{
/* 48k */ 100,
/* 96k */ 50
#else
4500, 2250, 1800, 1125, 750, 375
/* 8k */ 4500,
/* 16k */ 2250,
/* 20k */ 1800,
/* 32k */ 1125,
/* 48k */ 750,
/* 96k */ 375
#endif
}};

@ -1,3 +1,14 @@
/**
* @file sclock.hpp
* @brief Manages sampling rate clock speeds.
*
* 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 SCLOCK_HPP_
#define SCLOCK_HPP_

Loading…
Cancel
Save