Browse Source

Initial version

master
kaqu 8 months ago
commit
2386719fa4
16 changed files with 1166 additions and 0 deletions
  1. +6
    -0
      .gitignore
  2. +67
    -0
      .travis.yml
  3. +7
    -0
      .vscode/extensions.json
  4. +25
    -0
      MakeFirmware.sh
  5. BIN
      NodeSP2_Overview.jpg
  6. +35
    -0
      README.md
  7. +39
    -0
      include/README
  8. +46
    -0
      lib/README
  9. +28
    -0
      lib/sema/sema.cpp
  10. +59
    -0
      lib/sema/sema.h
  11. +18
    -0
      platformio.ini
  12. BIN
      res/NodeSP2.png
  13. +4
    -0
      res/tile.raw
  14. +763
    -0
      src/main.cpp
  15. +11
    -0
      test/README
  16. +58
    -0
      test/test_semas.cpp

+ 6
- 0
.gitignore View File

@ -0,0 +1,6 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
*.fw

+ 67
- 0
.travis.yml View File

@ -0,0 +1,67 @@
# Continuous Integration (CI) is the practice, in software
# engineering, of merging all developer working copies with a shared mainline
# several times a day < https://docs.platformio.org/page/ci/index.html >
#
# Documentation:
#
# * Travis CI Embedded Builds with PlatformIO
# < https://docs.travis-ci.com/user/integration/platformio/ >
#
# * PlatformIO integration with Travis CI
# < https://docs.platformio.org/page/ci/travis.html >
#
# * User Guide for `platformio ci` command
# < https://docs.platformio.org/page/userguide/cmd_ci.html >
#
#
# Please choose one of the following templates (proposed below) and uncomment
# it (remove "# " before each line) or use own configuration according to the
# Travis CI documentation (see above).
#
#
# Template #1: General project. Test it using existing `platformio.ini`.
#
# language: python
# python:
# - "2.7"
#
# sudo: false
# cache:
# directories:
# - "~/.platformio"
#
# install:
# - pip install -U platformio
# - platformio update
#
# script:
# - platformio run
#
# Template #2: The project is intended to be used as a library with examples.
#
# language: python
# python:
# - "2.7"
#
# sudo: false
# cache:
# directories:
# - "~/.platformio"
#
# env:
# - PLATFORMIO_CI_SRC=path/to/test/file.c
# - PLATFORMIO_CI_SRC=examples/file.ino
# - PLATFORMIO_CI_SRC=path/to/test/directory
#
# install:
# - pip install -U platformio
# - platformio update
#
# script:
# - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N

+ 7
- 0
.vscode/extensions.json View File

@ -0,0 +1,7 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
]
}

+ 25
- 0
MakeFirmware.sh View File

@ -0,0 +1,25 @@
#!/bin/sh
#
# MakeFirmware.sh
# Launch from VSC terminal within project directory:
# ./MakeFirmware.sh NodeSP2
#
# 04.03.20/KQ My little helper script ...
#
# TODO: Please adjust next line path to mkfw according to your local system!
PATH2MKFW="/mnt/a30054ad-3fe6-444a-8d93-16df937e448e/projects/ESP32/Odroid/odroid-go-firmware/tools/mkfw"
if [ "$1" = "" ]
then
echo "usage: echo y | ./MakeFirmware.sh <filebasename_no_extension>"
else
echo "Working within directory:" `pwd`
echo "Converting res/$1.png -> tile.raw ..."
ffmpeg -i ./res/$1.png -f rawvideo -pix_fmt rgb565 ./res/tile.raw
echo ""
echo "Converting firmware.bin to firmware.fw ..."
$PATH2MKFW/mkfw $1 ./res/tile.raw 0 16 1048576 app .pio/build/odroid_esp32/firmware.bin
echo "Renaming firmware.fw to $1.fw"
mv firmware.fw $1.fw
ls -l *.fw
echo "\n\n!!! Done, $1.fw created, copy to SD-Card (subdir: /odroid/firmware) !!!"
fi

BIN
NodeSP2_Overview.jpg View File

Before After
Width: 4160  |  Height: 3120  |  Size: 383 KiB

+ 35
- 0
README.md View File

@ -0,0 +1,35 @@
Title:__NodeSP2__
Date:__2020/08/02__
Author:__KQ__
Keywords: Odroid-Go,ESP32,FFT,Assembler,S32C1I,Semaphore
## NodeSP2 ##
![Showtime](NodeSP2_Overview.jpg)
The general project description can be found at
<https://www.hacknology.de/projekt/2020/nodesp2/>
## Project structure ##
This projekt actually consists of three independant
parts:
1. The I2S processing logic & the ESP32 FFT assembly
language integration (which, as explained in
https://www.hacknology.de/projekt/2020/nodesp/
is what this is all about ...).
Found in branch /src.
2. An inline assembly demonstration of how to use
the ESP32 atomic instruction for semaphores (w/o
framework, RTOS etc. overhead - very fast :).
Found in branch /lib/sema (a usage/test function can
be found under /test).
3. A shell script (MakeFirmware.sh) to patch the
Odroid-Starter picture.
Use it like: echo y | ./MakeFirmware.sh NodeSP2
from the project directory root (Standard w/ VSC).
(ignore the message flood).
Found in project root.

+ 39
- 0
include/README View File

@ -0,0 +1,39 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

+ 46
- 0
lib/README View File

@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

+ 28
- 0
lib/sema/sema.cpp View File

@ -0,0 +1,28 @@
//
// Sema.cpp
// ESP32 semaphores implementation
//
// History:
// --------
// 30.07.20/KQ VSC initial version
//
#include <Arduino.h>
#include "sema.h"
static SEMMUX sema[MAXSEMAS]; // Local semaphore data store
static int active_semas = 0; // Currently active semas
// Instantiate a new semaphore (a 'forward only' approach ;)
// As this is a SOC implementation, there is probably no need to delete it later ...
SEMMUX *create_sema()
{
if(active_semas >= MAXSEMAS)
return NULL; // Sorry, no bonus!
return(&sema[active_semas++]);
}
// Get an individual handle (as per task/process required)
uint32_t get_sema_handle(SEMMUX *sm)
{
return(++(sm->handle));
}

+ 59
- 0
lib/sema/sema.h View File

@ -0,0 +1,59 @@
//
// Sema.h
// ESP32 semaphores (re-)implementation
//
// History:
// --------
// 30.07.20/KQ VSC initial version
//
#include <Arduino.h>
typedef struct {
uint32_t sema;
uint32_t handle;
} SEMMUX;
#define MAXSEMAS 8
extern SEMMUX *create_sema();
extern uint32_t get_sema_handle(SEMMUX *sm);
void test_semaphores(); // May be removed ... (should have gone to 'test'!)
// Semaphores on the cheap (& proud of it!), completely w/o FreeRTOS
// Use same <target> but different (expected) <old_values> for different client tasks
inline bool S32C1I_wrapper(uint32_t *target, uint32_t old_value, uint32_t new_value)
{
/* Excerpt from Xtensa instruction set reference manual:
S32C1I <at>, <as>, 0..1020
S32C1I is a conditional store instruction intended for updating synchronization variables
in memory shared between multiple processors. It may also be used to atomically up-
date variables shared between different interrupt levels or other pairs of processes on a
single processor.
1. S32C1I attempts to store the contents of address register <at> to the
virtual address formed by adding the contents of address register <as> and an 8-bit zero-
extended constant value encoded in the instruction word shifted left by two.
2. If the old contents of memory at the physical address equals the contents of the SCOMPARE1 spe-
cial register, the new data is written
3. Otherwise the memory is left unchanged. In either case, the value read from the
location is written to address register <at>.
*/
__asm__ __volatile__("\
wsr %[v_old],scompare1 // Transfer (expected) old_value to SCOMPARE1 special register \n\
s32c1i %[v_new],%[v_target],0 // temp = *(<as>+(0<<2)) => temp = *target \n\
// if (*(<as>+(0<<2)) == SCOMPARE1) if (*target == SCOMPARE1) \n\
// *(<as>+(0<<2)) = <at> *target = new_value \n\
// <at> = temp new_value = temp \n\
"
: [v_new] "+r" (new_value)
: [v_old] "r" (old_value), [v_target] "r" (target)
);
return new_value == old_value; // a4(new_value!) == old_value ? true : false
}
#define sema_t(target,my_value) S32C1I_wrapper(target,0,my_value)
#define sema_g(target,my_value) S32C1I_wrapper(target,my_value,0)
#define sema_take(target,handle) sema_t(&(target->sema),handle)
#define sema_give(target,handle) sema_g(&(target->sema),handle)

+ 18
- 0
platformio.ini View File

@ -0,0 +1,18 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:odroid_esp32]
platform = espressif32
board = odroid_esp32
framework = arduino
lib_deps = ODROID-GO, WiFi
monitor_port = /dev/ttyUSB?
monitor_speed = 115200

BIN
res/NodeSP2.png View File

Before After
Width: 86  |  Height: 48  |  Size: 2.7 KiB

+ 4
- 0
res/tile.raw View File

@ -0,0 +1,4 @@
FS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'K鏗ィJ鏗餝FS'KFS'KFS'KFS'KFS'KFS'KS飩SKFS'KFS'KFS'KFS'KFS'KS鍮&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&SィJネR&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&SK&SFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'K'SKヌJ飩FS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'K&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&SS覊蹕S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&SゥJ雨雨雨雨雨雨雨�雨�雨覊KFS'K&S'K鏗ィJィJ雨ィJ雨ィJ雨ィJ雨ィJゥJィJゥJィJゥJィJゥJィJゥJィJゥJィJ雨ィJゥJィJゥJィJゥJィJゥJィJゥJィJゥJFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'K雨�iJiRiJ嘘iJhJiJ�雨�ァJS&K&SネJァJ雨ィR�ィRィJィRィJィRィJィRィJネRィJヌRネJヌRネJヌRネJヌRネJ躋ネJ躋鍮躋蹕躋蹕躋蹕SKSKS&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&SゥJ雨iJiJ雨iJ雨iJ雨雨雨雨ィJゥJ'KKィJゥJィJゥJィJゥJィJィJネJィJネJィJネJネJネJネJ蹕ネJ蹕ネJ蹕ネJ蹕ネJ蹕鍮K鍮K鍮KKKKKK'KKFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'K雨�iJiRiJ�iJ�雨�雨���雨ィRィJィRィJィRィJィRィJネRィJネRネJヌRネJヌRネJ躋ヌJヌRネJ躋蹕躋ヌJ躋蹕S蹕SKSKSK&SK&SK&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&SゥR雨雨iJ雨雨雨雨�雨ィJ雨ィJゥJィJゥJィJゥJィJゥJネJィJネJネJネJネJネJネJ蹕ネJ蹕ネJ蹕鍮蹕鍮K鍮K鍮KK'SK'KK'S'K&S'K&S'KFK'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KiJiRiJ�iJ�雨�雨�雨ィR�ィRィJィRィJィRィJィRィJネRィJヌRネJヌRネJ躋ネJヌRネJ躋蹕躋ヌJ躋蹕S蹕SKSKSK&SK&SK&SK&S&KFS&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&SiJjJ雨雨雨雨�雨ィJ雨ィJゥJィJゥJィJゥJネJゥJネJネJネJネJネJネJネJネJ鍮ネJ蹕ネJ蹕鍮蹕鍮K鍮KKKK'SK'SK&KK&K'K&K'KFS'KFK'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KiJiR雨�雨�雨��ィR�ィRィJィRィJィRィJネRィJヌRネJヌRネJヌRネJヌRヌJ躋ヌJ躋ヌJ躋蹕S蹕S蹕SKSK&SK&SK&SK&S&KFS&KFS&KFS&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&SiJjJィJ雨ィJ雨ィJ雨ィJゥJィJゥJィJゥJネJィJネJネJネJネJネJネJ蹕ネJ蹕ネJ蹕鍮蹕鍮蹕鍮K蹕KKSK'KK&KK&K'K&S'KFS'KFS'KFS&KFKFKFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KiJiR雨ィR�ィRィJィRィJィRィJィRィJネRィJネRネJヌRネJヌRネJ躋ヌJ躋鍮躋蹕躋蹕S蹕SKSKSK&SK&SK&S'K&S&KFS&KFS&KFS&KFSFKFS&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&SiJjJィJゥJィJゥJィJゥJィJィJネJィJネJィJネJネJネJネJ蹕ネJ躋)[ァ�
�S鍮K鍮K鍮KKKK'KK'SK&SK&S'KFS'KFK'KFS&KFKFKFSFKfKFKFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KiJiRィJィRィJィRィJィRィJィRィJネRィJヌRネJヌRネJ躋ヌJヌR躋hk、ハシSS蹕S蹕SKSK&SK&SK&SK&S&K&S&KFS&KFS&KFSFKFSFKESFKeS&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&SiJjJィJゥJィJゥJネJィJネJネJネJネJネJネJヌJネJ蹕ネJ蹕鍮[洩�+ヘH[(SKK'SK'SK'SK&SK&S'KFK'KFS'KFS'KFSFKFSFKfSFKfSFKfKfKFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KiJiRィJィRィJィRィJネRィJヌRネJヌRネJヌRネJ躋ネJ躋蹕躋Sィkァ琶ュGc�ノ{Fk'SSK&SK&SK&SK&S&KFS&KFS&KFS&KFSFKFSFKeSFKeSFKeS&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&SiJjJネJィJネJィJネJネJネJネJネJネJ蹕ネJ蹕ネJ蹕鍮蹕鍮FKゥ[&mノ}�*�+スヲ笈cS&KK&S'K&K'KFK'KFS'KFS&KFSFKfKFKfSFKfSFKfSfKfSfKFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KiJ嘘ィJネRィJヌRネJヌRネJヌRネJヌRネJ躋蹕躋ヌJ躋蹕S&Kネ[eネ}㌣I� ス・吃[SK&S'K&S&KFS&KFS&KFS&KFSFKFSFKeSFKeSFKeSfKeSfKeS&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&SiJjJネJネJネJネJヌJネJ蹕ネJ蹕ネJ蹕鍮蹕鍮蹕鍮K鍮FKィ[&mネ}ヲkJ|Kオニ{Ⅷ'K&S'K㌘g[g['SFS&KFKFKfSFKfKFKfSFKfSFKeSfK�fK�fKFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KiJ嘘ネJヌRネJヌRネJ躋ネJ躋鍮躋蹕躋蹕S蹕SKSFKネcFm�Tィlハ�\⑳EK&KFS�斡I憩kFKFSFKFSFKfSFKeSFKeSfKeSfKeSfK�eK�&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S雨雨鏗ネJ蹕ネJ蹕ネJ蹕鍮蹕鍮K鍮KKKKKKFKィS&mィu覺idゥ}eT⑳FKFS'Kノエh、IЁcFSFKfSFKfSFKfKfKfSfK�fK�fK�fK��FS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KiJ嘘ネJ躋ネJ躋ヌJ躋ヌJ躋蹕躋蹕SS'[(['[KSFKァ[eィ}ニShl厭d\Ⅷ�GSFSゥ、∪)|ecFKfSFS��e[FKeSfKeSeK�eK�eK���&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S雨雨蹕鍮蹕鍮蹕鍮蹕鍮K鍮KKヌ桔�
ス�'SKeK\・mI�\ノl)柴\伯ナゥ、檎}�ネlナKfSFKч�*スG怛SfK�fK�fK����・K�FS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KiJ嘘蹕躋蹕躋蹕躋蹕S蹕SKSァ{I矧、謫K&SFKヌ[&m閘覺�ゥ}Ыナ�*オi疲トuFmhdトSFKeSニ{H庫ャ&杷K�eK�eK�eK����・S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S雨雨蹕鍮蹕鍮K鍮KKKK'SKァs |i班{'S'KfKィS訶huニSH\i}ETナ{龕H�s'm軼Gd・KeKfKニs(|ィ�уKfK����・S�・S�・S�FS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KiJ嘘蹕S蹕S蹕SKSKSK&Sf\u�F}謫
ス*・|ヲmH�Tu)�\ト\)�u・d轗ヲuネl膣fKeSe\uネ�meK�eK����・S�・S�、S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S�雨K蹕K蹕KK'SK'SK'SKf\ネlh}m謫�*晄s�~%Tネd �Tト\驀mf\ヌufmネlナK�fKe\軼ァ}藹����・K�・S�・S・K・S・KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KiJゥRKSKSKSK&SK&SK&SニSGd㌫fdヲsi琶уcヲ\'uヲK'd)u$T%TuG\\軼ニd'\ナSfS�Tfd軼����・S�・S・K、S・K、S・KトS&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S�雨KKKK'KK'SK&K'K&S'Ke\鑞ァ}Gm・l怨怨E\・m(~E\鑞)�\臀 ~'mヲ\轗�ニ|閖愛�u輙ネ}]・S�・S�・S�・S・K、K・KトS・KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KiJゥRKSK&SK&SK&S'K&S&K&S�'uネ}�藹閻ノ}、\ナu㍽ET(ui�e臀I皐mナd~ニuニЗ癖シ霈G}}~Dm�・S�、S�、S・K、S・K、S、KトS&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S�ゥJ'SK'SK&K'K&K'K&SGSfSGS覺(\ァdg\TィdィlナKニd(mナS(\(u%L$T mgdT軼ヲ\&dglGЖ|fdF\輙eT・K�・S・KトS・KトS・KトS・KトSナKFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KiJゥRK&SK&S'K&S&K&S[)|(�県lヌtGu&m�h}Hud\fm�Tヌlノ}、\、\ネ�l�ァuemニd'u}}m虱㎡臈�、S・K、S・K、S、K、S、KトS、KトS&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S�ゥJ'S'K&S'K&S'KFS'Kc�)ヘノシ㊤G}G~ニmDe)�)�\%vネ磁\Hmノ縫]De且㎡藹g�v�ヌu~輹ヲu�H�eナK・KトK・KトS・KトS・KトSナK膃ナKFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KhJィRK&S&K&S&K&S&KFS%S(t(�ЖdFdァd�Tヌlィl膣ニ\GuナKGd(uDT%T(ugd%T輙ナdf\ヲdヲdナd��軼�・S藜ナS、S、KトS、KトSトKトSトKトS&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S�ゥJFS'KFK'KFS'KFK'KdSitヌ|㏄ァd㌧'mニde\m(u%L%eィuT㌧ィ}dTУiuヌdETgme・\軼ee訶ヲ\Gu訶虧g|gt、[トSナKトKトK膃トK膃トKFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KhJィR&KFS&KFS&KFSFKFSイI}~ニu㎡ヲu'~蛄EeH� �d%v邇・\guゥ擦eDeゥ紫ueg�%~�轗輹~ァuヲu'主�ャェユ況TトSトSトKトSトKトSトK膣&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S�ゥJFK&KFS'KFSFKfSFKКHdヲd��g\ヌd�E\ネdヌlL蘚GmTg\GueTdT(m�%T&m觸�ヲ\蘚ニ\・d�mニd|ィ己т[膣トK膃トK膣膃膣膃FS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'K�ィR&KFS&KFSFKFSFKESeK觴覺觴覺觴\\ナK&\\・S%Tfd・K觴gd膣薔fdT膣f\E\T%\%TE\%T$TE\D\トcFtFl」[トKトSトK膣トK經トK經&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S�ゥJFSFKfSFKfKFKfSFKfSFKfSfK�fK�fK�fK��・S�・S�・S�・K・K・S・KトS・KトS・KトS・KトSナK膣ナK膣ナK膃トK膣トK膣膃T膃膣トKFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'K�ネRFKFSFKfSFKeSFKeSFKeSfKeSeK�eK�eK������・S�、S�、S�、S・K、S、KトS、KトS、KトSトKトSトKトSトK膣トK經トK經膃經ナS」K&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&SィJノJFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFKFK・SeKFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'K鏗S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S'S'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'KFS'K鏗鏗&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&S&K&SハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRハZヒRヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZヒRェZ

+ 763
- 0
src/main.cpp View File

@ -0,0 +1,763 @@
/*
* NodeSP2.c
* Frequency analyzer w/ I2S mic. & DMA transfer on Odroid-Go (ESP32)
* FFT algo taken from: http://www.katjaas.nl/home/home.html
*
* 22.07.20/KQ 1st 'clean' version
* 28.07.20/KQ Publishable version
* 30.07.20/KQ Use (actually superfluous) 'fancy' semaphores (inlined, hence practically no overhead ...)
* 01.08.20/KQ Sample frequency variation tests, using > 2*15kHz (Shannon) now
*/
#include "odroid_go.h"
#include <driver/i2s.h>
#include <sema.h>
#define DEBUG 0 // More serial output
#define USE_SEMAS 1 // Use actual mutex access
// General Odroid-Go I/O
#define PIN_BLUE_LED 2 // Blue LED
#define SCREENWIDTH 320 // Screen width
#define SCREENHEIGHT 240 // Screen height
// ODROID-GO Header(P2) pin usage:
// 1: GND
// 2: [GPIO18, VSPICLK (shared w/ display!)]
// 3: GPIO12 (OUT only)
// 4: GPIO15 (ADC2 Ch#3, ADC2 not supported by lib?!)
// 5: GPIO4 (ADC2 Ch#0, ADC2 not supported by lib?!)
// 6: Vcc (3.3V)
// 7: [GPIO19 (VSPI MISO - shared w/ display!)]
// 8: [GPIO23 (VSPI MOSI - shared w/ display!)]
// 9: [- not used -]
// 10: [USB 5V - only if external USB connected!]
//
#define I2S_WS 15 // Data xchg pins for I2S
#define I2S_SD 4
#define I2S_SCK 12
#define I2S_PORT I2S_NUM_0 // 1st I2S port will be used
#define MIC_MINRANGE 60 // INMP441 (MEMS) microphone: 60Hz - 15kHz
#define MIC_MAXRANGE 15000
#define SAMPLE_FREQUENCY 30500 //48000 kHz permissible, acc. to INMP441 spec.
// General defs.
#define BLOCKSIZE 2048 // Block size is elements, not bytes! OK: 64 .. 2048
#define MAXBINS 10 // # of frequency bins (no of displayed bars)
int32_t i32_samples[BLOCKSIZE]; // Local pick-up buffer
static bool bReady1, bReady2; // Buffer ready indicators
static float *fArreal1, *fArimag1, *fArreal2, *fArimag2; // FFT buffers
static float *arrealtwiddle, *arimagtwiddle; // Prepared roots of unity data
static float *bins1, *bins2; // Analyzer bin buffers
static float fSlowMax[MAXBINS]; // Max. bin (delayed display)
/* Define 'normal' log sort of scale (but respect microphone range 60Hz..15kHz) ...
i16BinMax[0] = 90; // >60..90 Hz
i16BinMax[1] = 140; // >90..140 Hz
i16BinMax[2] = 210; // >140..210 Hz
i16BinMax[3] = 300; // >210..300 Hz
i16BinMax[4] = 600; // >300..600 Hz
i16BinMax[5] = 1200; // >600..1200 Hz
i16BinMax[6] = 2400; // >1200..2400 Hz
i16BinMax[7] = 5000; // >2400..5000 kHz
i16BinMax[8] = 10000; // >5..10 kHz
i16BinMax[9] = 15000; // >10..15 kHz */
static uint16_t i16BinMax[MAXBINS] = {90,140,210,300,600,1200,2400,5000,10000,MIC_MAXRANGE}; // Max. range in Hz per bin
static float fBinAutoLevelling[MAXBINS]; // Automatic noise reduction
#define AUTOLEVELDELAY 50 // Do NOT pick up keyboard accel. by button press action!
#define MAXAUTOLEVELROUNDS 150 // Max. rounds to use (yields: 150-50=100 cycles)
static int iAutoLevelCount = MAXAUTOLEVELROUNDS; // Auto level round #
static uint16_t iBinFreqCnt[MAXBINS]; // Frequencies per bin
// Display freq. range bars
#define BIN_XFRAMEBASE 19
#define BIN_XVALUEBASE 20
#define BIN_FRAMEXOFFSET 29
#define BIN_FRAMEWIDTH 18
#define BIN_VALUEWIDTH 16 // BIN_FRAMEWIDTH-2
#define BIN_FRAMEYOFFSET 80
#define BIN_VALUEYOFFSET 81
#define BIN_FRAMEHEIGHT 140
#define BIN_VALUEHEIGHT 138 // BIN_FRAMEHEIGHT-2
#if DEBUG==1
const char *szHzRange[MAXBINS] = {
" 60-90Hz",
" >90-140Hz",
" >140-210Hz",
" >210-300Hz",
" >300-600Hz",
" >600-1200Hz",
" >1200-2400Hz",
" >2400-5000Hz",
" >5-10kHz",
" >10-15kHz"
}; // Range in Hz per bin (for messages)
#endif
// 'Volume' control
#define DAMPINCREMENT 5
#define DAMPINGLOW 0
#define DAMPINGHIGH 500
#define DAMPINGDEFAULT 30
static int iDamping = DAMPINGDEFAULT; // Damping for log() bar scaling (500..20)
#define MAXMSGLEN 40 // Textual display output max. length
char szMsgBuf[MAXMSGLEN]; // & buffers
char szOldDamping[MAXMSGLEN];
#if USE_SEMAS==1
// Semaphores for 2 resources
static SEMMUX *sema1,*sema2; // Semaphores ...
static uint32_t sema1_h1,sema1_h2,sema2_h1,sema2_h2; // & handles
#endif
// Run -----------------------------------------------------------------------------------
// Indicate activity to user
void invert_BlueLED()
{
static int8_t bState = 0;
bState ^= 0x1;
digitalWrite(PIN_BLUE_LED, bState);
}
// THE actual thing ...
void fft_assembler(register unsigned int N, register float *real, register float *im,
register float *realtwiddle, register float *imtwiddle, register bool invert)
//---------------- a10,a11,a12,a13,a14,a15
//---------------- a2, a3, a4, a5, a6, a7
{
// C99 standard conformity ...
__asm__ __volatile__ ("\
//nop // Sentinel (disassemble only via breakpoint!) \n\
movi.n a9,1 // norm = 1 \n\
wfr f14,a9 // norm{f14} = 1.0 \n\
addi.n a9,a2,-1 // rootmask{a9} = N{a2} - 1 \n\
wfr f15,a9 // f15=a9 rootmask{f10} (free an address register!) \n\
movi.n a10,1 // phase{a10} = 1 \n\
srli a11,a2,1 // span{a11} = N{a2} >> 1 \n\
//span_cont: bltui a11,2,span_loop // span{a11} < 2 => exit \n\
span_cont: slli a10,a10,1 // phase{a10} <<=1 // update in every FFT stage \n\
sub a12,a2,a10 // rootindex{a12} = (N{a2}-phase{a10}) ^ invert{a7} \n\
xor a12,a12,a7 // -- initialize in every FFT stage \n\
sub a13,a2,a11 // for(even{a13}=N{a2}-span{a11}; even{a13};) \n\
//even_cont: beqz a13,even_loop // !even{a13} => exit \n\
even_cont: addi.n a13,a13,-1 // even{a13}-- \n\
or a14,a13,a11 // odd{a14} = even{a13} | span{a11} \n\
xor a13,a14,a11 // even{a13} = odd{a14} ^ span{a11} iterate over even blocks only \n\
// temp = real[even] + real[odd] \n\
slli a15,a13,2 // even(index*sizeof(float=4)) \n\
lsx f0,a3,a15 // f0 = *(a3 + a15){real[even]} \n\
slli a9,a14,2 // odd(index*sizeof(float=4)) \n\
lsx f1,a3,a9 // f1 = *(a3 + a9){real[odd]} \n\
add.s f2,f0,f1 // f2{temp} = f0{real[even]} + f1{real[odd]} \n\
// real[odd] = real[even] - real[odd]; \n\
sub.s f1,f0,f1 // f1{real[odd]} = f0{real[even]} - f1{real[odd]} \n\
ssx f1,a3,a9 // *(a3+a9){real[odd]} = f1{real[odd]} \n\
// real[even] = temp; \n\
ssx f2,a3,a15 // *(a3+15){real[even]} = f2{temp} \n\
// temp = im[even] + im[odd]; \n\
lsx f3,a4,a15 // f3 = *(a4 + a15){im[even]} \n\
lsx f4,a4,a9 // f4 = *(a4 + a9){im[odd]} \n\
add.s f5,f3,f4 // f5{temp} = f3{im[even]} + f4{im[odd]} \n\
// im[odd] = im[even] - im[odd]; \n\
sub.s f4,f3,f4 // f4{im[odd]} = f3{im[even]} - f4{im[odd]} \n\
ssx f4,a4,a9 // *(a4 + a9){im[odd]} = f4{im[odd]} \n\
// im[even] = temp; \n\
ssx f5,a4,a15 // *(a4 + a15){im[even]} = f5{temp} \n\
// if(rootindex) // rootindex[0] has an identity \n\
beqz a12,rootidx_if \n\
// temp=realtwiddle[rootindex]*real[odd]-imtwiddle[rootindex]*im[odd]; \n\
slli a15,a12,2 // rootindex(index*sizeof(float=4)) a15(rootindex/old even) \n\
lsx f0,a5,a15 // f0{realtwiddle[rootindex]} = *(a5 + a15){realtwiddle[rootindex]} \n\
mul.s f6,f0,f1 // f6{realtwiddle[rootindex]*real[odd]} = f0 * f1 \n\
lsx f7,a6,a15 // f7{imtwiddle[rootindex]} = *(a6 + a15){imtwiddle[rootindex]} \n\
mul.s f8,f7,f4 // f8{imtwiddle[rootindex]*im[odd]} = f7 * f4 \n\
sub.s f9,f6,f8 // f9{temp} = f6 - f8 \n\
// im[odd]=realtwiddle[rootindex]*im[odd]+imtwiddle[rootindex]*real[odd]; \n\
mul.s f10,f0,f4 // f10 = f0{realtwiddle[rootindex]} * f4{im[odd]} \n\
mul.s f11,f7,f1 // f11 = f7{imtwiddle[rootindex]} * f1{real[odd]} \n\
add.s f10,f10,f11 // f10 = f10 * f11 {im[odd]} \n\
ssx f10,a4,a9 // *(a4+a9){im[odd]} = f10 \n\
// real[odd] = temp; \n\
ssx f9,a3,a9 // *(a3+a9){real[odd]}= f9{temp} \n\
rootidx_if: sub a12,a12,a10 // rootindex{a12} -= phase{a10} \n\
rfr a15,f15 // Restore rootmask \n\
and a12,a12,a15 // rootindex &= rootmask; // decrement rootindex \n\
bnei a13,0,even_cont // even{a13} => loop \n\
//j even_cont // Start again \n\
even_loop: srli a11,a11,1 // span{a11} >>= 1 \n\
bgeui a11,2,span_cont // span{a11} >= 2 => loop \n\
//j span_cont // Start again \n\
// invert = !invert; // invert the boolean for faster check \n\
span_loop: neg a7,a7 // a7{invert} != a7{invert} \n\
// for(even=N; even; ) // last FFT stage including normalisation \n\
mov.n a13,a2 // a13{even} = a2{N} \n\
// even_cont2: beqz a13,even_loop2 // !a13{even} => exit loop \n\
even_cont2: addi a13,a13,-2 // even{a13} -= 2; // attention, decrement at unusual place \n\
movi.n a14,1 // odd{a14} = even{a13}|1 \n\
or a14,a13,a14 // \n\
// temp = real[even] + real[odd] \n\
slli a15,a13,2 // even(index*sizeof(float=4)) \n\
lsx f0,a3,a15 // f0 = *(a3 + a15){real[even]} \n\
slli a9,a14,2 // odd(index*sizeof(float=4)) \n\
lsx f1,a3,a9 // f1 = *(a3 + a9){real[odd]} \n\
add.s f2,f0,f1 // f2{temp} = f0{real[even]} + f1{real[odd]} \n\
// real[odd] = real[even] - real[odd] \n\
sub.s f1,f0,f1 // f1{real[odd]} = f0{real[even]} - f1{real[odd]} \n\
ssx f1,a3,a9 // *(a3+a9){real[odd]} = f1{real[odd]} \n\
// real[even] = temp; \n\
ssx f2,a3,a15 // *(a3+15){real[even]} = f2{temp} \n\
// temp = im[even] + im[odd] \n\
lsx f3,a4,a15 // f3 = *(a4 + a15){im[even]} \n\
lsx f4,a4,a9 // f4 = *(a4 + a9){im[odd]} \n\
add.s f5,f3,f4 // f5{temp} = f3{im[even]} + f4{im[odd]} \n\
// im[odd] = im[even] - im[odd] \n\
sub.s f4,f3,f4 // f4{im[odd]} = f3{im[even]} - f4{im[odd]} \n\
ssx f4,a4,a9 // *(a4 + a9){im[odd]} = f4{im[odd]} \n\
// im[even] = temp; \n\
ssx f5,a4,a15 // *(a4 + a15){im[even]} = f5{temp} \n\
// if(invert) // recall that invert = !invert \n\
// beqz a7,invert_if // !a7{invert} => omit if \n\
// real[even] *= norm{f14}; // 'normalisation' only in forward transform \n\
// im[even] *= norm; \n\
// real[odd] *= norm; \n\
// im[odd] *= norm; \n\
//invert_if:nop \n\
bnei a13,0,even_cont2 // a13{even} => cont loop \n\
// j even_cont2 // Start again \n\
even_loop2: srli a5,a2,1 // a5{halfn} = a2{N} >> 1 \n\
srli a6,a2,2 // a6{quartn} = a2{N} >> 2 \n\
addi.n a7,a2,-1 // a7{nmin1} = a2{N} - 1 \n\
mov.n a8,a5 // a8{forward} = a5{halfn} \n\
movi.n a9,1 // a9{rev} = 1; // frequently used 'constants' \n\
or a10,a6,a6 // a10{i}=a6{quartn} for(i=quartn; i; i--) // start of bitreversed perm.\n\
beqz a10,i_quit // !a10{i} => No loop! \n\
i_cont: neg a11,a10 // a11{nodd} = not a10{i} Gray code generator for even values \n\
xor a12,a6,a6 // a12{zeros} = 0 (for(zeros=0; nodd & 1; zeros++) nodd >>= 1; \n\
j zero_in // As bbci doesn't seem to work ? ... \n\
// _bbci a11,0,zero_quit // !(a11{nodd} & 1)? No loop! (doesn't seem to work?) \n\
zero_cnt: srli a11,a11,1 // _srli a11{nodd} >>= 1 \n\
addi.n a12,a12,1 // a12{zeros}++ \n\
zero_in: // bbsi a11,0,zero_cnt // a11{nodd} & 1 => loop \n\
movi.n a14,1 // a14{1} \n\
and a14,a11,a14 // a11{nodd} & a14{1} \n\
beqz a14,zero_cnt // a11{nodd} & a14{1} > 0 => loop (as bbsi doesn't seem to work) \n\
zero_quit: movi.n a13,2 // a13=2 \n\
ssl a12 // a12{zeros} Set shift left amount \n\
sll a13,a13 // a13 <<= a12{zeros} \n\
xor a8,a8,a13 // a8{forward} ^= a13{2 << zeros} \n\
ssr a12 // a12{zeros} Set shift right amount \n\
srl a13,a6 // a13 = a6{quartn} >> a12{zeros} \n\
xor a9,a9,a13 // a9{rev} ^= a13{quartn >> zeros} \n\
bge a8,a9,forw_if // if a8{forward} >= a9{rev} => omit if \n\
// ---------- swap(forward, rev, real, im); \n\
// temp = a3{real}[a8{forward}] \n\
slli a15,a8,2 // a15 = a8{forward}*sizeof(float=4)) \n\
lsx f2,a3,a15 // f2{temp} = *(a3{real} + a15{forward*4}){real[forward]} \n\
// real[forward] = real[rev]; \n\
slli a14,a9,2 // a14 = a8{rev}*sizeof(float=4)) \n\
lsx f3,a3,a14 // f3 = *(a3{real} + a14{rev*4}){real[rev]} \n\
ssx f3,a3,a15 // *(a3+a15){real[forward]} = f3{real[rev]} \n\
// real[rev] = temp; \n\
ssx f2,a3,a14 // *(a3+a14){real[rev]} = f2{temp} \n\
// temp = im[forward]; \n\
lsx f3,a4,a15 // f3{temp} = *(a4{im} + a15{forward*4}){im[forward]} \n\
// im[forward] = im[rev]; \n\
lsx f2,a4,a14 // f2 = *(a4{im} + a14{rev*4}){im[rev]} \n\
ssx f2,a4,a15 // *(a4+a15){im[forward]} = f2{im[rev]} \n\
// im[rev] = temp; \n\
ssx f3,a4,a14 // *(a4+a14){im[rev]} = f3{temp} \n\
// ---------- end swap \n\
xor a11,a7,a8 // a11{nodd} = a7{nmin1} ^ a8{forward} // compute the bitwise negations \n\
xor a14,a7,a9 // a14{noddrev} = a7{nmin1} ^ a9{rev} \n\
// ---------- swap(nodd, noddrev, real, im); // swap bitwise-negated pairs \n\
// temp = a3{real}[a11{nodd}] \n\
slli a15,a11,2 // a11{nodd}*sizeof(float=4)) \n\
lsx f2,a3,a15 // f2{temp} = *(a3{real} + a15{nodd*4}){real[nodd]} \n\
// real[nodd] = real[noddrev]; \n\
slli a14,a14,2 // a14{noddrev}*sizeof(float=4)) \n\
lsx f3,a3,a14 // f3 = *(a3{real} + a14{noddrev*4}){real[noddrev]} \n\
ssx f3,a3,a15 // *(a3+a15){real[nodd]} = f3{real[noddrev]} \n\
// real[noddrev] = temp; \n\
ssx f2,a3,a14 // *(a3+a14){real[noddrev]} = f2{temp} \n\
// temp = im[nodd]; \n\
lsx f3,a4,a15 // f3{temp} = *(a4{im} + a15{nodd*4}){im[nodd]} \n\
// im[nodd] = im[noddrev]; \n\
lsx f2,a4,a14 // f2 = *(a4{im} + a14{noddrev*4}){im[noddrev]} \n\
ssx f2,a4,a15 // *(a4+a15){im[nodd]} = f2{im[noddrev]} \n\
// im[noddrev] = temp; \n\
ssx f3,a4,a14 // *(a4+a14){im[noddrev]} = f3{temp} \n\
// ---------- end swap \n\
forw_if: movi.n a15,1 // \n\
xor a11,a8,a15 // a11{nodd} = a8{forward} ^ a15{1} \n\
xor a14,a9,a5 // a14{noddrev} = a9{rev} ^ a5{halfn} \n\
// ---------- swap(nodd, noddrev, real, im); // compute the odd values from the even \n\
// temp = a3{real}[a11{nodd}] \n\
slli a15,a11,2 // a11{nodd}*sizeof(float=4)) \n\
lsx f2,a3,a15 // f2{temp} = *(a3{real} + a15{nodd*4}){real[nodd]} \n\
// real[nodd] = real[noddrev]; \n\
slli a14,a14,2 // a14{noddrev}*sizeof(float=4)) \n\
lsx f3,a3,a14 // f3 = *(a3{real} + a14{noddrev*4}){real[noddrev]} \n\
ssx f3,a3,a15 // *(a3+a15){real[nodd]} = f3{real[noddrev]} \n\
// real[noddrev] = temp; \n\
ssx f2,a3,a14 // *(a3+a14){real[noddrev]} = f2{temp} \n\
// temp = im[nodd]; \n\
lsx f3,a4,a15 // f3{temp} = *(a4{im} + a15{nodd*4}){im[nodd]} \n\
// im[nodd] = im[noddrev]; \n\
lsx f2,a4,a14 // f2 = *(a4{im} + a14{noddrev*4}){im[noddrev]} \n\
ssx f2,a4,a15 // *(a4+a15){im[nodd]} = f2{im[noddrev]} \n\
// im[noddrev] = temp; \n\
ssx f3,a4,a14 // *(a4+a14){im[noddrev]} = f3{temp} \n\
// ---------- end swap \n\
addi.n a10,a10,-1 // a10{i}-- \n\
bnei a10,0,i_cont // a10{i} => loop \n\
i_quit: // Quit right away \n\
");
}
// Prepare & use noise reduction values
void auto_level_bins(float *bins)
{
if(iAutoLevelCount >= MAXAUTOLEVELROUNDS) { // Auto levelling ready?
for(int i=0;i<MAXBINS;i++) {
bins[i] -= fBinAutoLevelling[i]; // Subtract mean noise level ...
if(bins[i] < 0.0) // Corriger la fortune ...
bins[i] = 0.0;
}
}
else { // Still counting!
if(iAutoLevelCount > AUTOLEVELDELAY) { // De-bounce keyboard press ...
for(int i=0;i<MAXBINS;i++)
fBinAutoLevelling[i] += bins[i]; // Subtract noise reduction value
}
if(++iAutoLevelCount >= MAXAUTOLEVELROUNDS) { // Count elapsed?
Serial.println("Auto levelling elapsed."); // Yap!
for(int i=0;i<MAXBINS;i++) {
fBinAutoLevelling[i] /= (float)(MAXAUTOLEVELROUNDS-AUTOLEVELDELAY); // Only mean ...
fBinAutoLevelling[i] *= 1.1; // + 10%
#if DEBUG==1
Serial.printf("[%d] %7.3f\n",i,fBinAutoLevelling[i]);
#endif
}
}
}
}
// Fill analyzer bins
void binning(float fFreq, float *fBinRover, float *arreal, float *arimag)
{
int i,j;
float fFreqPerBin = fFreq/BLOCKSIZE; // Calc. frequency for each f-sample
int iBinsPerLogBin = 0; // Reset bin count
fBinRover[0] = 0.0; // Reset power sum (1st bin), bins being j-indexed (NOT i!) below ...
for(i=(int)ceil(MIC_MINRANGE/fFreqPerBin),j=0;(i<(BLOCKSIZE/2))&&(j<MAXBINS);i++) { // Walk buffer, but omit DC (0Hz) part and < fMicMin
iBinsPerLogBin++; // Advance count for this bin
fBinRover[j] += sqrt(arreal[i]*arreal[i]+arimag[i]*arimag[i]); // Add absolute value up front ...
if(((i+1) * fFreqPerBin) > i16BinMax[j]) { // If bin max. will be reached next, advance to next bin
fBinRover[j] /= (iBinsPerLogBin); // Normalize value (1/N) & move to next bin
iBinFreqCnt[j] = iBinsPerLogBin; // Store # of freqs.
j++; // Next bin
fBinRover[j] = 0.0; // Reset power sum for new bin ...
iBinsPerLogBin = 0; // Reset count for this bin
}
}
if(iBinsPerLogBin > 0) { // Do we have a remainder?
fBinRover[j] /= (iBinsPerLogBin); // Then normalize value (1/N) & move to next bin
iBinFreqCnt[j] = iBinsPerLogBin; // Store # of freqs.
}
}
#if DEBUG==1
// USB/serial debug output
void dump_bins(float *bins)
{
float fMax=0.0;
int iMaxIndex = 0;
for(int i=0;i<MAXBINS;i++) {
if(fMax < bins[i]) {
fMax = bins[i];
iMaxIndex = i;
}
Serial.print(bins[i]);
Serial.print("/");
Serial.print(iBinFreqCnt[i]);
Serial.print(" ");
}
Serial.print("Bin:");
Serial.print(iMaxIndex);
Serial.print(" Max:");
Serial.print(fMax);
Serial.print("\t\t");
Serial.println(szHzRange[iMaxIndex]);
}
#endif
// Odroid-Go: Display data
void draw_bins(float *bins)
{
// Draw vertical bars in lower half
for(int i=0;i<MAXBINS;i++) {
int32_t pct = (int32_t)bins[i]; // Get actual bin value
if(pct > 0) // Log()!
pct = floor(0.5+4*log(pct) * BIN_FRAMEHEIGHT/iDamping);
if(pct < 0) // Clip
pct = 0;
else { // if nec.
if(pct > BIN_VALUEHEIGHT)
pct = BIN_VALUEHEIGHT;
}
if(pct > (int32_t)fSlowMax[i]) fSlowMax[i] = (float)pct;
GO.lcd.fillRect(BIN_XVALUEBASE+i*BIN_FRAMEXOFFSET,BIN_VALUEYOFFSET,BIN_VALUEWIDTH,BIN_VALUEHEIGHT-pct,BLACK); // Top filler
GO.lcd.fillRect(BIN_XVALUEBASE+i*BIN_FRAMEXOFFSET,BIN_VALUEYOFFSET+BIN_VALUEHEIGHT-pct,BIN_VALUEWIDTH,pct,BLUE); //GREEN); // Bottom value
if(fSlowMax[i] > 3.0)
GO.lcd.fillRect(BIN_XVALUEBASE+i*BIN_FRAMEXOFFSET,BIN_VALUEYOFFSET+BIN_VALUEHEIGHT-fSlowMax[i],BIN_VALUEWIDTH,3,YELLOW); // Delayed max.
if(fSlowMax[i] > 0.0)
fSlowMax[i] -= 4.0;
}
}
// Async. task: Prepare transfer data for later drawing
void do_process()
{
float *fRoverReal,*fRoverImag, *fRoverBins;
bool *bReady;
#if USE_SEMAS==1
SEMMUX *sema;
uint32_t *handle;
#endif
if(!bReady1) { // Resource #1 available?
fRoverReal = fArreal1; // Yap!
fRoverImag = fArimag1;
fRoverBins = bins1;
bReady = &bReady1;
#if USE_SEMAS==1
sema = sema1;
handle = &sema1_h1;
#endif
}
else { // Resource #1 blocked, resource alternative (#2) avail.?
if(!bReady2) { // Yap!
fRoverReal = fArreal2;
fRoverImag = fArimag2;
fRoverBins = bins2;
bReady = &bReady2;
#if USE_SEMAS==1
sema = sema2;
handle = &sema2_h1;
#endif
}
else { // No resource available, indicate ...
Serial.println("*** do_process(): No ready buffers?");
return;
}
}
for(int i=0;i<BLOCKSIZE;i++) {
//if(i32_samples[i] != (i32_samples[i] & 0xffffff80)) // 1000 0000= 80h, 1100 0000=8+4=12=Ch-> c0h
fRoverReal[i] = ((float)(i32_samples[i]>>7))/100000.0; // 8000000 -> ~1.0
fRoverImag[i] = 0.0;
}
bool invert = false;
fft_assembler(BLOCKSIZE,fRoverReal,fRoverImag,arrealtwiddle,arimagtwiddle,invert); // TODO: invert!!!
binning(SAMPLE_FREQUENCY,fRoverBins,fRoverReal,fRoverImag); // 'Compress' results ...
#if USE_SEMAS==1
while(!sema_take(sema,*handle))
taskYIELD();
#endif
*bReady = true; // Indication: Resource prepared
#if USE_SEMAS==1
sema_give(sema,*handle);
#endif
}
// Async task
void read_i2s(void *pvParameters)
{
while(1) {
size_t num_bytes_read = 0; //i2s_read_bytes(I2S_NUM_0,
// (int32_t *)&i32_samples[0],
// BLOCKSIZE, // the doc says bytes, but its elements.
// portMAX_DELAY); // no timeout
// Yet, # returned is bytes(!)
if(i2s_read(I2S_NUM_0,(int32_t *)&i32_samples[0],BLOCKSIZE * sizeof(int32_t), &num_bytes_read, portMAX_DELAY) != ESP_OK)
Serial.println("***** i2s_read(): failed?!");
else { // Read good ...
if(num_bytes_read < (BLOCKSIZE<<2)) // but incomplete?!
Serial.printf("I2SREAD: %d < %d len data?!\n",num_bytes_read,BLOCKSIZE);
else // Ok, valid data available ...
do_process();
}
taskYIELD();
}
}
// Arduino 'task'
void loop()
{
static bool bUseKeyboard = true;
static int32_t iLoopCounter = 0;
static int32_t iNotReadyCounter = 0;
static int iLastDamping = 0;
uint8_t joystick_y;
// Keyboard & LED processing
if(++iLoopCounter >= 100) { // Slow down & de-bouncing
invert_BlueLED(); // Indicate 'operational'
iLoopCounter = 0;
if(bUseKeyboard) {
//GO.update(); // Read all button states/refresh (makes some chopper noise!) ...
GO.JOY_Y.readAxis(); // Pick up button w/o sound (i.e. skip update() !)
joystick_y = GO.JOY_Y.isAxisPressed(); // & read value (separately!)
if(joystick_y == 2) { // Up
if(iDamping > DAMPINCREMENT)
iDamping -= DAMPINCREMENT; // Reduce damping!
}
else {
if(joystick_y == 1) { // Down
if(iDamping < DAMPINGHIGH)
iDamping += DAMPINCREMENT; // Increase damping!
}
}
GO.BtnStart.read(); // Start button pressed?
if(GO.BtnStart.isPressed()) {
Serial.println("[Start] -> Auto levelling initiated ...");
iAutoLevelCount = 0; // Restart auto levelling
for(int i=0;i<MAXBINS;i++)
fBinAutoLevelling[i] = 0.0;
}
GO.BtnVolume.read(); // Volume button pressed?
if(GO.BtnVolume.isPressed())
bUseKeyboard = false; // Yap! Stop noise ...
}
}
// Wipe old content
GO.lcd.setTextSize(2);
GO.lcd.setTextColor(BLACK);
if(iDamping != iLastDamping) {
GO.lcd.setCursor(100,0);
GO.lcd.print(szOldDamping);
}
GO.lcd.setCursor(60,30);
GO.lcd.print(szMsgBuf);
// Update dynamic data only
GO.lcd.setTextColor(GREEN);
if(iDamping != iLastDamping) {
GO.lcd.setCursor(100,0);
snprintf(szOldDamping,MAXMSGLEN-1,"%3d",iDamping);
GO.lcd.print(szOldDamping);
iLastDamping = iDamping;
}
GO.lcd.setCursor(60,30);
// Data bars display
if(bReady1) { // Resoure #1 prepared?
snprintf(szMsgBuf,MAXMSGLEN-1,"(%6d)#1/A:%3d",iNotReadyCounter,iAutoLevelCount); // Yap!
GO.lcd.print(szMsgBuf);
#if DEBUG==1
dump_bins(bins1);
#endif
auto_level_bins(bins1); // Noise reduction
draw_bins(bins1); // Show FFT binning results
iNotReadyCounter = 0;
#if USE_SEMAS==1
while(!sema_take(sema1,sema1_h2))
taskYIELD();
#endif
bReady1 = false; // Clear resource #1
#if USE_SEMAS==1
sema_give(sema1,sema1_h2);
#endif
}
else {
if(bReady2) { // Resource #2 prepared?
snprintf(szMsgBuf,MAXMSGLEN-1,"(%6d)#2/A:%3d",iNotReadyCounter,iAutoLevelCount);
GO.lcd.print(szMsgBuf);
#if DEBUG==1
dump_bins(bins2);
#endif
auto_level_bins(bins2); // Noise reduction
draw_bins(bins2); // Show FFT binning results
iNotReadyCounter = 0;
#if USE_SEMAS==1
while(!sema_take(sema2,sema2_h2))
taskYIELD();
#endif
bReady2 = false; // Clear resource #2
#if USE_SEMAS==1
sema_give(sema2,sema2_h2);
#endif
}
else { // Nope, no resources prepared ...
if(iNotReadyCounter <= 200000)
iNotReadyCounter++;
else {
iNotReadyCounter = 0;
snprintf(szMsgBuf,MAXMSGLEN-1,">200000 (no data?)!");
Serial.println(szMsgBuf);
GO.lcd.print(szMsgBuf);
}
}
}
}
// Init. ---------------------------------------------------------------------------------
// Clear buffers
bool sampling_clear()
{
if(arimagtwiddle && arrealtwiddle && fArimag2 && fArreal2 && fArimag1 && fArreal1) {
memset(fArreal1,0,BLOCKSIZE*sizeof(float)); // Initialize memory ...
memset(fArimag1,0,BLOCKSIZE*sizeof(float));
memset(fArreal2,0,BLOCKSIZE*sizeof(float));
memset(fArimag2,0,BLOCKSIZE*sizeof(float));
memset(arrealtwiddle,0,BLOCKSIZE*sizeof(float));
memset(arimagtwiddle,0,BLOCKSIZE*sizeof(float));
memset(bins1,0,MAXBINS*sizeof(float));
memset(bins2,0,MAXBINS*sizeof(float));
}
return 0;
}
void free_buffers()
{
if(bins2) free(bins2);
if(bins1) free(bins1);
if(arimagtwiddle) free(arimagtwiddle);
if(arrealtwiddle) free(arrealtwiddle);
if(fArimag2) free(fArimag2);
if(fArreal2) free(fArreal2);
if(fArimag1) free(fArimag1);
if(fArreal1) free(fArreal1);
}
bool allocate_buffers()
{
fArreal1 = (float *)malloc(BLOCKSIZE*sizeof(float));
fArimag1 = (float *)malloc(BLOCKSIZE*sizeof(float));
fArreal2 = (float *)malloc(BLOCKSIZE*sizeof(float));
fArimag2 = (float *)malloc(BLOCKSIZE*sizeof(float));
arrealtwiddle = (float *)malloc(BLOCKSIZE*sizeof(float));
arimagtwiddle = (float *)malloc(BLOCKSIZE*sizeof(float));
bins1 = (float *)malloc(MAXBINS*sizeof(float));
bins2 = (float *)malloc(MAXBINS*sizeof(float));
if(bins1 && bins2 && arimagtwiddle && arrealtwiddle && fArimag2 && fArreal2 && fArimag1 && fArreal1) {
sampling_clear(); // Initialize right away ...
return true;
}
free_buffers();
Serial.println("*** allocate_buffers(): Memory allocation failed!");
return false;
}
void rootsofunity(unsigned int N, float *realtwiddle, float *imtwiddle)
{
float pi = 3.14159265358979;
unsigned int n;
//N = 1<<logN;
for(n=0; n<N; n+=2) {
realtwiddle[n] = cos(pi*n/N);
imtwiddle[n] = -sin(pi*n/N); // roots for n=even, forward transform
realtwiddle[n+1] = cos(pi*n/N); // roots for n=odd, inverse transform
imtwiddle[n+1] = sin(pi*n/N);
}
}
void i2s_install()
{
// Working
const i2s_config_t i2s_config = {
.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = SAMPLE_FREQUENCY,
.bits_per_sample = i2s_bits_per_sample_t(32), // 24 bit, 2's complement
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // L/R=0 (n.c.)
.communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
.intr_alloc_flags = 0, // default interrupt priority
.dma_buf_count = 8,
.dma_buf_len = 1024, // Max. bytes 1024 (min. 64)
.use_apll = true // <--------- CHECK!!!
};
esp_err_t err;
err = i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
if(err != ESP_OK) {
Serial.printf("***** I2S driver install failed: %d\n", err);
while (true);
}
const i2s_pin_config_t pin_config = {
.bck_io_num = I2S_SCK,
.ws_io_num = I2S_WS,
.data_out_num = -1,
.data_in_num = I2S_SD
};
Serial.printf("I2S pins: BCLK=GPIO12 LRCL/WS=GPIO15 DOUT=GPIO4 (ODROID-GO)\n");
i2s_set_pin(I2S_PORT, &pin_config);
}
void setup() {
GO.begin(); //Serial.begin(115200); included ...
GO.Speaker.mute(); // Turn off speaker noise
GO.Speaker.end();
GO.lcd.clearDisplay();
GO.lcd.setTextSize(3);
GO.lcd.setTextFont(1);
GO.lcd.setTextColor(GREEN);
GO.lcd.setCursor(0,90);
GO.lcd.println("NodeSP2 (I2S) ...");
GO.lcd.setCursor(0,120);
GO.lcd.setTextSize(2);
GO.lcd.printf("%d Hz sample rate",SAMPLE_FREQUENCY);
delay(3000);
// Draw static background
GO.lcd.clearDisplay();
for(int i=0;i<MAXBINS;i++) {
fSlowMax[i] = 0.0; // Reset max. storages
GO.lcd.drawRect(BIN_XFRAMEBASE+i*BIN_FRAMEXOFFSET,BIN_FRAMEYOFFSET,BIN_FRAMEWIDTH,BIN_FRAMEHEIGHT,DARKGREY);
}
GO.lcd.setTextSize(2);
GO.lcd.setCursor(0,0);
GO.lcd.print("Damping: ");
GO.lcd.setCursor(0,30);
GO.lcd.print("Task:");
snprintf(szMsgBuf,MAXMSGLEN," ");
GO.lcd.setTextSize(1);
GO.lcd.setTextColor(YELLOW);
GO.lcd.setCursor(0,230);
GO.lcd.print("[Hz] 90 140 210 300 600 1200 2400 5k 10k 15k");
pinMode(25, OUTPUT); // Speaker OFF!
digitalWrite(25, HIGH);
pinMode(PIN_BLUE_LED, OUTPUT); // Blue (frontal) LED
#if USE_SEMAS==1
// Create semaphores & handles
Serial.println("Setup semaphores ...");
sema1 = create_sema(); // Create semaphore for resource #1
sema1_h1 = get_sema_handle(sema1); // Accessor exec.path #1
sema1_h2 = get_sema_handle(sema1); // Accessor exec.path #2
sema2 = create_sema(); // Create semaphore for resource #2
sema2_h1 = get_sema_handle(sema2); // Accessor exec.path #1
sema2_h2 = get_sema_handle(sema2); // Accessor exec.path #2
#endif
// Initialize the I2S peripheral
i2s_install();
i2s_start(I2S_PORT);
delay(500);
if(allocate_buffers()) {
rootsofunity(BLOCKSIZE,arrealtwiddle,arimagtwiddle); // Only calculated once (ahead)
// Wifi/BT etc. (RF) stack run on core #0 by default
// Arduino loop code runs on Core #1
// As we're currently not using RF services, let's go for the first core(0)!
xTaskCreatePinnedToCore(read_i2s, "read_i2s", 2048, NULL, 1, NULL, 0); // Core #0/1
}
}

+ 11
- 0
test/README View File

@ -0,0 +1,11 @@
This directory is intended for PIO Unit Testing and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PIO Unit Testing:
- https://docs.platformio.org/page/plus/unit-testing.html

+ 58
- 0
test/test_semas.cpp View File

@ -0,0 +1,58 @@
#include <Arduino.h>
#include "sema.h"
// Test
void test_semaphores()
{
uint32_t sema1 = 0;
if(S32C1I_wrapper(&sema1,0,1)) // If the semaphore is not yet set (=0), set to 1
Serial.printf("1. Sema1=%d == 1? Success\n",sema1); // If semaphore is 1 now, it has been taken
if(!S32C1I_wrapper(&sema1,0,2)) // Try again, should fail! (Semaphore <>0, hence no setting to 2)
Serial.printf("2. Sema1=%d == 2? Fail\n",sema1); // If semaphore still 1 now, it has been occupied ...
if(S32C1I_wrapper(&sema1,1,0)) // (Re-)setting shall be permissible (expecting 1, reset to 0)
Serial.printf("3. Sema1=%d == 0? Success\n",sema1); // If semaphore is 0 now, it has been reset successfully!
if(sema_t(&sema1,1))
Serial.printf("4. sema_take(): Sema1=%d == 1? Success\n",sema1);
if(!sema_t(&sema1,2))
Serial.printf("5. sema_take(): Sema1=%d == 2? Fail\n",sema1);
if(sema_g(&sema1,1))
Serial.printf("6. sema_give(): Sema1=%d == 0? Success\n",sema1);
for(int s=0;s<10;s++) {
SEMMUX *sema2 = create_sema(); // Initialize new semaphore
if(sema2 != NULL) {
Serial.printf("Semaphore #%d successfully created:\n",s);
// Assume two tasks competing for access
uint32_t sema_h1 = get_sema_handle(sema2);
Serial.printf("First handle: %d\n",sema_h1);
uint32_t sema_h2 = get_sema_handle(sema2);
Serial.printf("2nd handle: %d\n",sema_h2);
// Now compete!
if(sema_take(sema2,sema_h1))
Serial.printf("7. sema_take(h1) ok\n");
if(!sema_take(sema2,sema_h2))
Serial.printf("8. sema_take(h2) failed (ok!)\n");
if(sema_give(sema2,sema_h1))
Serial.printf("9. sema_give(h1) ok\n");
if(sema_take(sema2,sema_h2))
Serial.printf("10. sema_take(h2) ok\n");
if(!sema_take(sema2,sema_h1))
Serial.printf("11. sema_take(h1) failed (ok!)\n");
if(sema_give(sema2,sema_h2))
Serial.printf("12. sema_give(h2) ok\n");
}
else
Serial.printf("*** Semaphore #%d creation failed.\n",s);
}
}
void setup()
{
Serial.begin(115200);
}
void loop()
{
test_semaphores();
}

Loading…
Cancel
Save