diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2203cb1d..e3f828dd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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'}} diff --git a/.github/workflows/size-diff.yml b/.github/workflows/size-diff.yml index 13efa379..66f0f8fc 100644 --- a/.github/workflows/size-diff.yml +++ b/.github/workflows/size-diff.yml @@ -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 @@ -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 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..b46f77c1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "CMSIS_5"] + path = CMSIS_5 + url = https://github.com/ARM-software/CMSIS_5.git diff --git a/CMSIS_5 b/CMSIS_5 new file mode 160000 index 00000000..2e98b247 --- /dev/null +++ b/CMSIS_5 @@ -0,0 +1 @@ +Subproject commit 2e98b24711e8d73d425484334f3d54c41168ffd9 diff --git a/CMakeLists.txt b/CMakeLists.txt index 709b2a05..80f3a581 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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}) @@ -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} ) diff --git a/inc/MicroBitAudioProcessor.h b/inc/MicroBitAudioProcessor.h new file mode 100644 index 00000000..074e13e4 --- /dev/null +++ b/inc/MicroBitAudioProcessor.h @@ -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 diff --git a/samples/fft_example.cpp b/samples/fft_example.cpp new file mode 100644 index 00000000..1dd742c7 --- /dev/null +++ b/samples/fft_example.cpp @@ -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(); +} diff --git a/source/MicroBitAudioProcessor.cpp b/source/MicroBitAudioProcessor.cpp new file mode 100644 index 00000000..f42acc1c --- /dev/null +++ b/source/MicroBitAudioProcessor.cpp @@ -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"); +} diff --git a/target-locked.json b/target-locked.json index 81c4585e..80c7f3ad 100644 --- a/target-locked.json +++ b/target-locked.json @@ -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, @@ -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", diff --git a/target.json b/target.json index 5161fe0f..fd1760db 100644 --- a/target.json +++ b/target.json @@ -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",