Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FFT example #291

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
matrix:
os: [ubuntu-20.04, macos-11, windows-2019]
gcc: ['7-2017-q4', 'latest']
cmake: ['3.6.0', ''] # Empty string installs the latest CMake release
cmake: ['3.13.0', ''] # Empty string installs the latest CMake release
fail-fast: false
runs-on: ${{ matrix.os }}
name: ${{ matrix.os }}, gcc ${{ matrix.gcc }}, cmake ${{ matrix.cmake || 'latest'}}
Expand Down
10 changes: 8 additions & 2 deletions .github/workflows/size-diff.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,13 @@ jobs:
.replace('lancaster-university/codal-microbit-v2', '${GITHUB_REPOSITORY}') \
.replace('master', '${GITHUB_SHA}') \
.replace(',\n \"dev\": true', ''))
f = pathlib.Path('source/main.cpp')
f.write_text(f.read_text().replace('out_of_box_experience()', 'ble_test()'))
EOF
echo "coda.json after:"
cat codal.json
- name: Move fft example
run: |
mv libraries/codal-microbit-v2/samples/fft_example.cpp source/main.cpp
cat source/main.cpp
- name: Build using build.py
run: python build.py
- name: Save ELF file in a different directory
Expand Down Expand Up @@ -116,6 +118,10 @@ jobs:
run: |
cd libraries/codal-microbit-v2
git checkout ${GIT_BASE_SHA}
git restore .
- name: Build the default OOB for the parent/base commit comparison
shell: bash
run: git checkout source/main.cpp
- name: Build 'base' project using build.py
run: python build.py --clean

Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "CMSIS_5"]
path = CMSIS_5
url = https://github.com/ARM-software/CMSIS_5.git
1 change: 1 addition & 0 deletions CMSIS_5
Submodule CMSIS_5 added at 2e98b2
25 changes: 25 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,29 @@ RECURSIVE_FIND_FILE(LIB_ARCHIVE_FILES "${CMAKE_CURRENT_LIST_DIR}/lib" "*.a")

set(CMAKE_SYSTEM_PROCESSOR "armv7-m" PARENT_SCOPE)

set(ROOT "${CMAKE_CURRENT_LIST_DIR}/CMSIS_5")
list(APPEND INCLUDE_DIRS "${ROOT}/CMSIS/Core/Include/")


# Define the path to CMSIS-DSP (ROOT is defined on command line when using cmake)
set(DSP ${ROOT}/CMSIS/DSP)

include(${DSP}/Toolchain/GCC.cmake)

# Add DSP folder to module path
list(APPEND CMAKE_MODULE_PATH ${DSP})

###########
#
# CMSIS DSP
#

# add them
include_directories(${INCLUDE_DIRS})

# Load CMSIS-DSP definitions. Libraries will be built in bin_dsp
add_subdirectory(${DSP}/Source bin_dsp)

# create our target
add_library(codal-microbit-v2 ${SOURCE_FILES})

Expand All @@ -61,6 +81,11 @@ target_link_libraries(
codal-nrf52
codal-core
codal-microbit-nrf5sdk
CMSISDSPSupport
CMSISDSPTransform
CMSISDSPCommon
CMSISDSPComplexMath
CMSISDSPStatistics
${LIB_OBJECT_FILES}
${LIB_ARCHIVE_FILES}
)
Expand Down
60 changes: 60 additions & 0 deletions inc/MicroBitAudioProcessor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
The MIT License (MIT)

Copyright (c) 2020 Arm Limited.

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

#include "MicroBit.h"
#include "DataStream.h"
#define ARM_MATH_CM4
#include "arm_math.h"

#ifndef MICROBIT_AUDIO_PROCESSOR_H
#define MICROBIT_AUDIO_PROCESSOR_H

#define MIC_SAMPLE_RATE (11 * 1024)
#define AUDIO_SAMPLES_NUMBER 1024

class MicroBitAudioProcessor : public DataSink
{
DataSource &audiostream;
int zeroOffset; // unsigned value that is the best effort guess of the zero point of the data source
int divisor; // Used for LINEAR modes
arm_rfft_fast_instance_f32 fft_instance;
float *buf;
float *output;
float *mag;
uint16_t position;
bool recording;
float rec[AUDIO_SAMPLES_NUMBER * 2];
int lastFreq;

public:
MicroBitAudioProcessor(DataSource& source);
~MicroBitAudioProcessor();
virtual int pullRequest();
int getFrequency();
int setDivisor(int d);
void startRecording();
void stopRecording(MicroBit& uBit);
};

#endif
74 changes: 74 additions & 0 deletions samples/fft_example.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#include "MicroBit.h"
#include "CodalDmesg.h"
#include "MicroBitAudioProcessor.h"
#include "StreamNormalizer.h"

static NRF52ADCChannel *mic = NULL;
static StreamNormalizer *processor = NULL;
static MicroBitAudioProcessor *fft = NULL;

MicroBit uBit;

/**
* fft_test function - creates an example MicroBitAudioProcessor and then queries it for results.
* Currently configured to use 1024 samples with 8bit signed data.
*/
void fft_test() {
uBit.display.print("L");
/*
if (mic == NULL){
mic = uBit.adc.getChannel(uBit.io.microphone);
mic->setGain(7,0);
}

if (processor == NULL)
processor = new StreamNormalizer(mic->output, 1.0f, true, DATASTREAM_FORMAT_8BIT_SIGNED, 10);

if (fft == NULL)
fft = new MicroBitAudioProcessor(processor->output);

uBit.io.runmic.setDigitalValue(1);
uBit.io.runmic.setHighDrive(true);
*/

// Code above commented out was from the original example, which can
// probably be replaced with this single line, but we need to double check
// the configuration because the fft calculation results are twice the frequency
fft = new MicroBitAudioProcessor(*uBit.audio.splitter->createChannel());

//Start fft running
fft->startRecording();

while (1){
//TODO - de-noise : if last X samples are same - display ect.
//The output values depend on the input type (DATASTREAM_FORMAT_8BIT_SIGNED) and the size
//of the FFT - which is changed using the 'AUDIO_SAMPLES_NUMBER' in MicroBitAudioProcessor.h
//default is 1024
uBit.sleep(100);
int freq = fft->getFrequency();
DMESG("%s %d", "frequency: ", freq);
if(freq > 0)
uBit.display.print("?");
if(freq > 530)
uBit.display.print("C");
if(freq > 600)
uBit.display.print("D");
if(freq > 680)
uBit.display.print("E");
if(freq > 710)
uBit.display.print("F");
if(freq > 800)
uBit.display.print("G");
if(freq > 900)
uBit.display.print("A");
if(freq > 1010)
uBit.display.print("B");
if(freq > 1050)
uBit.display.print("?");
}
}

int main() {
uBit.init();
fft_test();
}
133 changes: 133 additions & 0 deletions source/MicroBitAudioProcessor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
The MIT License (MIT)

Copyright (c) 2020 Arm Limited.

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

#include "MicroBit.h"
#include "MicroBitAudioProcessor.h"

MicroBitAudioProcessor::MicroBitAudioProcessor(DataSource& source) : audiostream(source)
{
audiostream.connect(*this);
zeroOffset = 0;
divisor = 1;
arm_rfft_fast_init_f32(&fft_instance, AUDIO_SAMPLES_NUMBER);

/* Double Buffering: We allocate twice the number of samples*/
buf = (float *)malloc(sizeof(float) * AUDIO_SAMPLES_NUMBER * 2);
output = (float *)malloc(sizeof(float) * AUDIO_SAMPLES_NUMBER);
mag = (float *)malloc(sizeof(float) * AUDIO_SAMPLES_NUMBER / 2);

position = 0;
recording = false;

if (buf == NULL || output == NULL || mag == NULL) {
DMESG("DEVICE_NO_RESOURCES");
target_panic(DEVICE_OOM);
}
}

MicroBitAudioProcessor::~MicroBitAudioProcessor()
{
free(buf);
free(output);
free(mag);
}

int MicroBitAudioProcessor::pullRequest()
{
int result;

auto mic_samples = audiostream.pull();

if (!recording)
return DEVICE_OK;

//using 8 bits produces more accurate to input results (not 2x like using 16) but issue with
//F and G both producing 363hz -> investigate futher with crossing 8 bit + differnet sample numbers
//int8_t *data = (int8_t *) &mic_samples[0];
int16_t *data = (int16_t *) &mic_samples[0];
int samples = mic_samples.length() / 2;

for (int i=0; i < samples; i++)
{

result = (int) *data;

data++;
buf[position++] = (float)result;


if (!(position % AUDIO_SAMPLES_NUMBER))
{
float maxValue = 0;
uint32_t index = 0;

/* We have AUDIO_SAMPLES_NUMBER samples, we can run the FFT on them */
uint16_t offset = position <= AUDIO_SAMPLES_NUMBER ? 0 : AUDIO_SAMPLES_NUMBER;
if (offset != 0)
position = 0;

DMESG("Run FFT, %d", offset);
//auto a = system_timer_current_time();
arm_rfft_fast_f32(&fft_instance, buf + offset, output, 0);
arm_cmplx_mag_f32(output, mag, AUDIO_SAMPLES_NUMBER / 2);
arm_max_f32(mag + 1, AUDIO_SAMPLES_NUMBER / 2 - 1, &maxValue, &index);
//auto b = system_timer_current_time();

//DMESG("Before FFT: %d", (int)a);
//DMESG("After FFT: %d (%d)", (int)b, (int)(b - a));

lastFreq = ((uint32_t)MIC_SAMPLE_RATE / AUDIO_SAMPLES_NUMBER) * (index + 1);
DMESG("Freq: %d (max: %d.%d, Index: %d)",
lastFreq,
(int)maxValue,
((int)(maxValue * 100) % 100),
index);
}
}

return DEVICE_OK;
}

int MicroBitAudioProcessor::getFrequency(){
return lastFreq;
}

int MicroBitAudioProcessor::setDivisor(int d)
{
divisor = d;
return DEVICE_OK;
}


void MicroBitAudioProcessor::startRecording()
{
this->recording = true;
DMESG("START RECORDING");
}

void MicroBitAudioProcessor::stopRecording(MicroBit& uBit)
{
this->recording = false;
DMESG("STOP RECORDING");
}
4 changes: 2 additions & 2 deletions target-locked.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"USE_ACCEL_LSB": 0
},
"cpp_flags": "-std=c++11 -fwrapv -fno-rtti -fno-threadsafe-statics -fno-exceptions -fno-unwind-tables -Wl,--gc-sections -Wl,--sort-common -Wl,--sort-section=alignment -Wno-array-bounds",
"cpu_opts": "-mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=softfp",
"cpu_opts": "-mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard",
"definitions": "-DAPP_TIMER_V2 -DAPP_TIMER_V2_RTC1_ENABLED -DNRF_DFU_TRANSPORT_BLE=1 -DNRF52833_XXAA -DNRF52833 -DTARGET_MCU_NRF52833 -DNRF5 -DNRF52833 -D__CORTEX_M4 -DS113 -DTOOLCHAIN_GCC -D__START=target_start",
"device": "MICROBIT",
"generate_bin": true,
Expand All @@ -69,7 +69,7 @@
"url": "https://github.com/microbit-foundation/codal-microbit-nrf5sdk"
}
],
"linker_flags": "-Wl,--no-wchar-size-warning -Wl,--gc-sections -Wl,--wrap,atexit -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=softfp -Wl,--start-group -lstdc++ -lsupc++ -lm -lc -lgcc -lnosys -Wl,--end-group",
"linker_flags": "-Wl,--no-wchar-size-warning -Wl,--gc-sections -Wl,--wrap,atexit -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard -Wl,--start-group -lstdc++ -lsupc++ -lm -lc -lgcc -lnosys -Wl,--end-group",
"post_process": "",
"processor": "NRF52833",
"snapshot_version": "v0.2.50",
Expand Down
4 changes: 2 additions & 2 deletions target.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@
"cmake_definitions":{
"MBED_LEGACY_TOOLCHAIN":"GCC_ARM;"
},
"cpu_opts":"-mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=softfp",
"cpu_opts":"-mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard",
"asm_flags":"-fno-exceptions -fno-unwind-tables --specs=nosys.specs -mcpu=cortex-m4 -mthumb",
"c_flags":"-std=c99 --specs=nosys.specs -Warray-bounds",
"cpp_flags":"-std=c++11 -fwrapv -fno-rtti -fno-threadsafe-statics -fno-exceptions -fno-unwind-tables -Wl,--gc-sections -Wl,--sort-common -Wl,--sort-section=alignment -Wno-array-bounds",
"linker_flags":"-Wl,--no-wchar-size-warning -Wl,--gc-sections -Wl,--wrap,atexit -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=softfp -Wl,--start-group -lstdc++ -lsupc++ -lm -lc -lgcc -lnosys -Wl,--end-group",
"linker_flags":"-Wl,--no-wchar-size-warning -Wl,--gc-sections -Wl,--wrap,atexit -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard -Wl,--start-group -lstdc++ -lsupc++ -lm -lc -lgcc -lnosys -Wl,--end-group",
"libraries":[
{
"name":"codal-core",
Expand Down