My Beep-BRRR notes

Contents

New 2Apr2021. 26Jan2022. It concerns use of the round XMOS microphone array board, with code in xC. This note is in group Technology. Observe Standard disclaimer.

Also see the stream of consciousness style disclaimer – since I’m afraid the structure is the result of what is happening as I am working on this project, right now. Especially: some problem described early may have a solution further down, which I may not have referenced down or up. The Contents table may help, though, plus searching in the text. In other words, this blog note exposes my private development process, where there are no reviews or UML component diagrams, as such. No nice Trello boards for spec points with timelines. But I do use some alternatives. Should this blog note get too bloated (it already is bloated), I probably will sum up in a new note when I’m finished.

Background

Icon download: PDF

Beep-BRRR is supposed to listen to alarm sounds of any kind and then vibrate into a bed’s structure to wake a sleeping person. A sleep that might not, due to some degree of hearing loss, have been interrupted without this remedy.

Thanks, Kenny in Scotland, for suggesting it to be three R’s in the BRRR, so that the English reader sees that all those R’s are rolling. Or feels them in the body. .

Yes, I know that there exist a lot of boxes that would do this. May plan was to let go with one.

Fold handling with Collapse-O-Matic plugin

Expand All (for browser searching)
Collapse All

Typical fold

This text 123456789 will only be found with browser search when the fold is expanded. However, the search field on the top of this page is global for all notes, (expanding) all containing folds during the search. More precise: pages in that case will not have any collapsed state.

xCORE microphone array

Apart from the real intro (discovering the need) this started with me asking some xCORE Microphone Array board questions on the XCore Exchange Forum. I then bought two boards DEV KIT FOR MICROPHONE ARRAY of type XK-USB-MIC-UF216 from XMOS. Let’s just call it MIC_ARRAY. I have grouped some boards in 151:[xCORE microphone array]

Now I’ve made a box for one of them and mounted it on my desk. The other board will, if I succeed, end up at a bedside table, inside a wooden box. I have the box design also planned. So I’d better succeed!

For this application I don’t need the USB or Ethernet connectivity. What I’m basically left with then are these points:

Relevant contents

  • xCORE-200 (XUF216-512-TQ128) multicore microcontroller device
  • On version 2V0 of the board: seven INFINEON IM69D130 MEMS PDM (Pulse-Density Modulation) microphones [15]. On the older version(s?), the 10 dB more sensitive Akustica AKU441
  • An expansion header for with I2S and I2C and/or other connectivity and control solutions 􏰀
  • These I2S and I2C lines are also used with the Cirrus Logic CS43L21 DAC [13] for the a stereo 3.5 mm headphone jack output
  • Cirrus Logic CS2100-CP Fractional-N PLL [12]
  • Four general purpose push-button switches
  • 􏰀12 user-controlled red LEDs
  • 2MByte QSPI FLASH is internal (on chip)

Seven PDM MEMS microphones

MEMS microphone is an electronic chip where even the membrane that picks up the sound is made of silicon. On the MIC_ARRAY board six of these are placed around a radius of 45 mm at 60 degrees and one in the middle. In the XMOS application note AN00219_app_lores_DAS_fixed (more later) the file mic_array_das_beamformer_calcs.xls shows how the phase differences between the same signal coming in at 30° will differ by typically have a delay of 0.342 mm/µs → some 15 µs from one of the six microphones to the center. This may be used to hear where the source of the sound is. On that appnote I may set the delays and LEDs with the buttons such that, when I wear the headphones, I can hear myself loudest when the microphone array “points” to me. As the note says. “The beam is focused to a point of one meter away at an angle of thirty degrees from the plane of the microphone array in the direction indicated by the LEDs.”.

The microphones will decide on a new digital output at (like) 48 kHz rate. This is done with a delta-sigma modulator (see Wiki-refs) that delivers a 50% on/off at zero, fully on at max and fully off at min. This is PDM or pulse-density modulation.

To “decimate” the 8 pulse trains we need a decimator or low-pass filter for each. The analogue equivalent is an R-C, where the pulse train is presented to the R and the analogue value appears on the C. Then one would apply an A/D converter, but this functionality is built into the sw decimators. Each decimator handles four channels. They all output 16 or 32 bit samples, at some rate, like 16 kHz.

Aside. There are other ways to do this, like buying Adafruit PDM mic breakout boards [14] and some processing board containing, like a Microchip’s ARM® Cortex®-M0+ based flash microcontroller ATSAMD21, using some libraries there, too. But this would really not compare to the XMOS mic array board and xCORE architecture. But I generally like the Adafruit and MikroElektronika (MIKROE) boards. However, the latter don’t seem to have any PDM mic board. (As always: standard disclaimer).

Getting started log

  1. xTIMEcomposer 14.4.1 Makefile: TARGET = XUF212-512-TQ128-C20. No entry for the generic board type. Don’t follow this one. Go directly to point 3:
  2. Analysing debug_printf.c .././XUF212-512-TQ128-C20.xn:27 Warning: XN11206 Oscillator is not specified for USB node.
    • Update: Simon Gapp pointed put that adding
      <Node Id="1" InPackageId="1" Type="periph:XS1-SU" Reference="usb_tile" Oscillator="24MHz“> will work, see his full XUF216-256-TQ128-C20.xn at GitHub, here. This is part of his Master’s thesis [5]
  3. Instead of trying to find out what’s missing I’d rather see what XMOS has for me. I will now  try the AN00220 application note Microphone array phase-aligned capture example.  Download from https://www.xmos.ai/application-notes/ (more below). It depends on the microphone array library lib_mic_array, see 141:[XMOS libraries] or LIBRARIES
  4. Now ImportExisting project into workspace with Copy projects into workspace ticked, from the download location. I am offline from the xmos server
  5. The AN00220 uses
    TARGET = MIC-ARRAY-1V0 together with a file called MIC-ARRAY-1V0.xn.
    But then, there is a newer called MIC-ARRAY-1V3.xn that I found with the lib_mic_array_board_support v.2.2.0 according to its index.pdf  document XM009805, 2017. I also found it here.  It adds names for the expansion header J5. Plus the name “XS2 MC Audio2 for the AN002020 is now “Microphone Array Reference Hardware (XUF216)” for the lib_mic_array_board_support. Finally, I expanded it myself to TARGET = MIC-ARRAY-1V0-MODand MIC-ARRAY-1V3.xn to MIC-ARRAY-1V3-MOD.xn See point 9 (below)
  6. My question is why XMOS didn’t build this in as an option in xTIMEcomposer? Why do I have to download the AN00220 or lib_mic_array_board_support to discover this?

7. Code space left for me

The AN00220 compiled to the below HW usage. They say that “This demo application shows the minimum code to interface to the microphone array.” Fair enough, I shouldn’t be worried at all, I guess:

Creating app_phase_aligned_example.xe
Constraint check for tile[0]:
  Cores available:            8,   used:          4 .  OKAY
  Timers available:          10,   used:          4 .  OKAY
  Chanends available:        32,   used:          8 .  OKAY
  Memory available:       262144,   used:      20680 .  OKAY
    (Stack: 2252, Code: 7160, Data: 11268)
Constraints checks PASSED.
Constraint check for tile[1]:
  Cores available:            8,   used:          1 .  OKAY
  Timers available:          10,   used:          1 .  OKAY
  Chanends available:        32,   used:          0 .  OKAY
  Memory available:       262144,   used:       1232 .  OKAY
    (Stack: 348, Code: 624, Data: 260)
Constraints checks PASSED.

8. Adafruit 128×64 OLED display

Getting the Adafruit 128×64 OLED display up and running was not easy at all.

In my other boxes (aquarium controller, radio client  and bass/treble unit) I had used the smaller 128×32, product 931 (here) with zero problem getting it up and running for the first time. The flora of displays based on the SSD1306 chip from Univision Technology Inc was perhaps smaller then. I have used I2C address 0x3C for them all.

But the 128×64 hardware ref. v2.1, product 326 (here) was harder. Or maybe I didn’t pay enough attention to the right detail from the beginning. I could have stumbled upon the correct solution immediately, but Murphy’s law prohibited it. My road became maximum bumpy.

The boards from Univision UG-2832HSWEG02 for the 128×32 (here) and the 128×64 UG-2864HSWEG01 (here) say little about I2C address. The first says nothing, the second says that pin 15 D/C# “In I2C mode, this pin acts as SA0 for slave address selection.” My problem was that I went to the circuit diagram. Don’t do that! The page for 128×32 says right there that “This board/chip uses I2C 7-bit address 0x3C”. Correct. The page for the larger 128×64 says “This board/chip uses I2C 7-bit address between 0x3C-0x3D, selectable with jumpers”. Also 100% correct! But the diagram says 0x7A/0x78 (here). If you download the fresh code via the Arduino system the I2C addresses should be fine. But I ported the Adafruit code to XC some years ago, and have cut that update branch. .

It says “v2.1” and “5V ready” in the print on my 128×64 board. There is a long single page document for all of these display boards (here, updated 2012!) where I finally picked up the address: “128 x 64 size OLEDs (or changing the I2C address). If you are using a 128×64 display, the I2C address is probably different (0x3d), unless you’ve changed it by soldering some jumpers“. Had my attention span down the initial page for the 128×64 been longer, I’d saved a lot. Like this:

Then there is header pin 7 VIN (3V3 ok or 5V required?). My128x64 board as mentioned says “5V ready” in the print and it contains an AP2112K-3.3 regulator (here) according to another diagram. On my proper diagram it’s just drawn anonymously. Since my XMOS MIC_ARRAY board outputs 3V3 only and the AP211K-3.3 according to the data sheet must (?) have 4.3V to get the 3.3V out (even if the dropout is very low) I simply soldered it out and connected VIN and 3.3V internally. This was close to stupid, but was a shot in the dark since I hadn’t found the correct I2C address – the display was so dark. Because, when I got the I2C address correct I saw that the one board that I had not modified (I have two) and the one I did modify worked almost equally well – even if I think my removal got me the voltage drop more “3.3V” for the display, and I think it looked brighter. The AP2112K-3.3 takes 3.3V in quite well! I think this can be read from the single page document (above) as well, but there are so many ifs and buts there that it’s hard to get the cards shuffled correctly.

Adafruit has (as I do) written a lot of words, which is better than few words – provided they all point in the same direction or are able to point in a certain direction at all. I think that Adafruit would need to iterate and read again and then update non consistent information. Much doesn’t always mean correct.

By the way, I also had to add fresher pull-ups on the I2C SCL and SDA lines of 1k. The built-in 10k isn’t for much speed. I use 100 or 333 kbits/sec. Here is the connection diagram (drawn in iCircuit which has a global view of header pin numbering).

beep_brrr_cabling_adafruit_display_326_and_io_and_xmos_mic_array_oyvind_teig
Fig.1 – Cable connection diagram (30 kB) here)

I have noted in Tile[0] rules, what about tile[1]? that I might have a slight problem.

9. Target and .xn file for xflash

See my xCore Exchange community question (14Apr2021) at xCORE Microphone Array .xn file and xflash.

14Apr2021: my .xn file is here: MIC-ARRAY-1V3-MOD.xn. This goes with TARGET = MIC-ARRAY-1V3-MOD in Makefile.

Observe 141:[XFLASH from Terminal].

10. Serial number, processor and internal QSPI FLASH

See ref above. The 2MByte QSPI FLASH is internal (on chip) is integrated on this processor, opposite to the xCORE-200 Explorer board, which has an external 1MByte QSPI FLASH.

Serial number Processor type Internal (on chip) QSPI FLASH
1827-00193 Type: XUF216-512-TQ128
Printed: U11692C20 GT183302 PKKY15.00
(2018 week 33)
IS25LP016D (if newer than 2020.10.05,  [3])
IS25LQ016B (is older, manual)
1827-00254

11. Serial output to 3.5 mm jack

I probably hijacked this one on Tue Apr 27, 2021 9:12 pm: I2S Ports synchronized by same clock in two parallel tasks. Plus, see AN00219_app_lores_DAS_fixed (below).

My Beep-BRRR lab box

Fig.2 – My box with the XMOS microphones board and the Adafruit display

I bored holes for all microphones. The display is also seen. After having got AN00219 (below) up and running, I hear that I must add some audio damping material in the box. There is unnecessary echo in it.

Libraries

See 141:[XMOS libraries] and 141:[Importing (a source code) library when xTIMEcomposer is offline].

Some of the below have been mentioned above. I assume this is the  the most relevant list. I have experience with those in red:

  • APPLICATION NOTES – just to remind myself of those that may be of interest
    • AN01008 – Adding DSP to the USB Audio 2.0 L1 Reference Design
    • AN00103 – Enabling DSD256 in the USB Audio 2.0 Device Reference Design Software (DSD is Direct Stream Digital)
    • AN00209 – xCORE-200 DSP Elements Library
    • AN00218 – High Resolution Delay and Sum
    • AN00217 – High Resolution Delay Example
    • AN00219_app_lores_DAS_fixed – Low Resolution Delay and Sum (PDF, SW). Outputs to the 3.5 mm sound output jack.
      I compiled this with a newer versions of of lib_i2s (3.0.0 instead of 2.2.0) and lib_mic_array (3.0.1 instead of 2.1.0), both “greater major version” and then “there could be API incompatibilities”. At first it seemed to be ok, but then:
      * See lib_i2c and AN00219 (below) for the problems that appeared.
      * This appnote also fills in the three unused cores on tile[0] with
      par(int i=0;i<3;i++)while(1);
      Why? On the xCORE-200, if 1-5 cores used: 1/5 scheduled cycles each, and if 6-8 cores used then all cycles shared out. See 218:[The PDF]. In other words: according to [3] each logical core has guaranteed throughput of between 1/5 and 1/8 of tile MIPS. See xCore Exchange forum (below), 28Sep2021
      * AN00219_app_lores_DAS_fixed uses the mic array board’s DAC (1. here):
      1. On the mic array board the DAC chip is Cirrus Logic CS43L21 with headphone stereo outputs. It is connected to the xCORE-200 through an I2S interface and is configured using an I2C interface.
      2. On the xCORE-200 Multichannel Audio Platform the DAC is Cirrus Logic CS4384 with six single-ended outputs (Sep2021 “not recommended fro newer designs”). It is connected to the xCORE-200 via xSDOUT, and is configured using an I2C interface.
    • AN00220 – Microphone array phase-aligned capture example (above and below)
    • AN01009 – Optimizing USB Audio for stereo output, battery powered devices
    • AN01027 – Porting the XMOS USB 2.0 Audio Reference Software onto XU208 custom hardware
    • AN00162 – Using the |I2S| library
    • USB Audio Design Guide also covers the xCORE-200 Microphone Array Board. 110 pages! See [4]
  • Microphone array library lib_mic_array, code. Newer: code github, [7]
    • AN?? = separate application note not needed
    • AN00219 and AN00220 use it. Plus I base my version 0106 (below) on it
  • xCORE-200 DSP Library lib_dsp code
    • AN00209 describes it
  • S/PDIF library lib_spdif code
  • Sample Rate Conversion Library lib_src code
  • Microphone array board support library lib_mic_array_board_support code (latest PDF)
  • I2C Library, see lib_i2c and an00219 (below)
  • SPI Library lib_spi code
  • I2S/TDM Library lib_i2s, doc doc code. Newer: Github

Coding

Version 0106

Download 0106

These downloads may be considered intermediate. Starting with the app note AN002020 I made a new example_task, in AN00220_app_phase_aligned_example.xc. Read it as a .txt file here:

  1. ../AN00220_app_phase_aligned_example.xc.txt

I then added my own task called Handle_Beep_BRRRR_task in file _Beep_BRRR_01.xc Read it as a .txt file here:

  1. ../_Beep_BRRR_01.xc.txt

My code uses the XMOS source code libraries lib_i2c(4.0.0) lib_logging(2.1.0) lib_xassert(3.0.0) lib_mic_array(3.0.1) lib_dsp(3.1.0). From the version numbers I guess that they are rather ripe, even if anything that ends in 0.0 scares me somewhat. They are not included here:

  1. See Download code – (4.5 MB) also contains version history in .git

It compiles like this (plus xta static timing analysis outputs as “pass”):

Constraints tile[0]: C:8/8 T:10/8 C:32/21 M:48656 S:5192 C:28708 D:14756
Constraints tile[1]: C:8/1 T:10/1 C:32/00 M:1540  S:0348 C:0864  D:00328

As mentioned, I have noted in Tile[0] rules, what about tile[1]? that I might have a slight problem. It’s easy to see this (above). I use 8 cores on tile[0] and 1 only on tile[1]. Well, tile[1] is not used by me.

Chan or interface when critical timing

Here is the output of version 0104, which is about is the present code 0106 compiled with CONFIG_USE_INTERFACE:

Fig.3 – Disturbed by interface call with display write

The two configurations I have in this code is described rather coarsely in the below figure. There are two alternatives:

fig4_219_beep-brrr_msc

Fig.4.interface vs. channel during critical timing: Message Sequence Chart (MSC) (PDFJPG)

The code basically goes like this. The top code (CONFIG_USE_INTERFACE) in the select accepts the interface call from the example_task, and since Dislay_screen calls I2C serial communication to send a bitmap to the display we see that getting mic samples is halted. This would delay example_task too much. It needs to be back in an 8 kHz rate.

Observe that this is the way an interface call (RPC – Remote procedure call) is supposed to work in xC! This is not a design fault or a compiler error. It makes no difference in the semantics for those calls that do return a value and those that don’t.

I could have decorated with xta timing statements in this code, this would have been more accurate than discovering this by coincidence on the scope. In view of this, I find it rather strange that XMOS is parking xta and even interface calls, in the XTC Tools and lib_xcore.

// In Handle_Beep_BRRRR_task while(1) select case

#if (MY_CONFIG == CONFIG_USE_INTERFACE)

case if_mic_result.send_mic_data (const mic_result_t mic_result) : {
    buttons_context.mic_result = mic_result;

    // BREAKS TIMING, probably because interface returns at the end of the case, and this
    // needs to wait until Display_screen and by it writeToDisplay_i2c_all_buffer

#elif (MY_CONFIG == CONFIG_USE_CHAN)

case c_mic_result :> buttons_context.mic_result : {

// TIMING of example_task not disturbed because the channel input "returns" immediately

#endif
    display_context.state = is_on;
    display_context.screen_timeouts_since_last_button_countdown = NUM_TIMEOUTS_BEFORE_SCREEN_DARK;
    display_context.display_screen_name = SCREEN_MIC;
    Display_screen (display_context, buttons_context, if_i2c_internal_commands);
} break;

The lower config (CONFIG_USE_CHAN) shows the use of a channel instead. Everything is synchronous here, interface calls as well as chan comms. But with the semantics of a chan, only its “rendezvous” time is atomic. When the communication has passed the data across, the sender and the receiver are independent. Therefore this is the method I need to use to move data from the mic tasks to user tasks, so CONFIG_USE_INTERFACE will be removed.

This discussion follows (some moths later) at Decoupling ..task_a and ..task_b.

Min value from mic is constant

I have decided to count positive signed int values as positive dB as and negative values as negative dB and 0 as zero dB. I use 20 * log10 (value) as the basis. Meaning that 1000 or -1000 gives 60 dB and 10000 or -10000 gives 80 dB. Of course, this is not as clean math, see Decibel on Wikipedia. But my usage would look like having two digital VU meters.

20 * log10 (5174375) = 134 dB and -20 * log10 (2147351294) = -186 dB. In maths.xc I have this code:

int32_t int32_dB (const int32_t arg) {

    int32_t ret_dB;

    if (arg == 0) {
        ret_dB = 0; // (by math NaN or Inf)
    } else {
        float dB;
        if (arg > 0) {
            dB =    20 * (log10 ((float)   arg ));
        } else { // if (arg < 0) {
            dB = - (20 * (log10 ((float) -(arg) )));
        }
        ret_dB = (int32_t) dB;
    }

    return ret_dB;
}

I can see that if I make some sounds then the positive dB might become, like 161 dB. But the problem is that the negative value constantly shows the same value 2147351294 (0x7FFDFAFE)! I have not solved this. Update: with 4 mics it’s the positive max that is stuck! See Version 0109

I have set the S_DC_OFFSET_REMOVAL_ENABLED to 1. It runs a single pole high pass IIR filter: Y[n] = Y[n-1] * alpha + x[n] - x[n-1] which mutes DC. See [7] chapter 15. I don’t know if this is even relevant.

Tile[0] rules, what about tile[1]?

My startup code in main.c goes like this:

main.c config code (0106)
#define INCLUDES
#ifdef INCLUDES
    #include <xs1.h>
    #include <platform.h> // slice
    #include <timer.h>    // delay_milliseconds(200), XS1_TIMER_HZ etc
    #include <stdint.h>   // uint8_t
    #include <stdio.h>    // printf
    #include <string.h>   // memcpy
    #include <xccompat.h> // REFERENCE_PARAM(my_app_ports_t, my_app_ports) -> my_app_ports_t &my_app_ports
    #include <iso646.h>   // not etc.
    #include <i2c.h>

    #include "_version.h" // First this..
    #include "_globals.h" // ..then this

    #include "param.h"
    #include "i2c_client_task.h"
    #include "display_ssd1306.h"
    #include "core_graphics_adafruit_gfx.h"
    #include "_texts_and_constants.h"
    #include "button_press.h"
    #include "pwm_softblinker.h"
#endif

#include "mic_array.h"
#include "AN00220_app_phase_aligned_example.h"

#include "_Beep_BRRR_01.h"

#if (IS_MYTARGET == IS_MYTARGET_MIC_ARRAY)
    //                                                            MIC-ARRAY-1V3-MOD.xc
    on tile[0]: in port p_pdm_clk              = XS1_PORT_1E;  // PORT_PDM_CLK
    on tile[0]: in buffered port:32 p_pdm_mics = XS1_PORT_8B;  // The 8 bit wide port connected to the PDM microphones
                                                               // 7 used: MIC_0_DATA 8B0 to MIC_6_DATA 8B6 (8B7 not connected)
                                                               // Also: "Count of microphones(channels) must be set to a multiple of 4"
                                                               // PORT_PDM_DATA Inputs four chunks of 8 bits into a 32 bits value
    on tile[0]: in port p_mclk                 = XS1_PORT_1F;  // PORT_MCLK_TILE0
    on tile[0]: clock pdmclk                   = XS1_CLKBLK_1; // In "xs1b_user.h" system

    //                                                       MIC-ARRAY-1V3-MOD.xc
    on tile[1]:     port p_i2c             = XS1_PORT_4E; // PORT_I2C             SCL=BIT0, SDA=BIT1
    on tile[1]: out port p_shared_notReset = XS1_PORT_4F; // PORT_SHARED_RESET    BIT0 reset when low
    on tile[1]:     port p_i2s_bclk        = XS1_PORT_1M; // PORT_I2S_BCLK
    on tile[1]:     port p_i2s_lrclk       = XS1_PORT_1N; // PORT_I2S_LRCLK
    on tile[1]:     port p_i2s_dac_data    = XS1_PORT_1P; // PORT_I2S_DAC0

    on tile[0]: out port p_scope_gray       = XS1_PORT_1J; // Mic array expansion header J5, pin 10

    on tile[0]: in buffered port:4 inP_4_buttons = XS1_PORT_4A; // BUTTONS_NUM_CLIENTS
    //
    on tile[0]:     port p_display_scl      = XS1_PORT_1H; // Mic array expansion header J5, pin  3
    on tile[0]:     port p_display_sda      = XS1_PORT_1G; // Mic array expansion header J5, pin  1
    on tile[0]: out port p_display_notReset = XS1_PORT_1A; // Mic array expansion header J5, pin  5 Adafruit 326 v2.1 does not NOT have on-board reset logic
    on tile[0]: out port p_scope_orange     = XS1_PORT_1D; // Mic array expansion header J5, pin  7
    on tile[0]: out port p_scope_violet     = XS1_PORT_1I; // Mic array expansion header J5, pin  9


    on tile[0]: out port leds_00_07 = XS1_PORT_8C;  // BIT0 is LED_0

    on tile[0]: out buffered port:1 led_08 = XS1_PORT_1K;
    on tile[0]: out port led_09            = XS1_PORT_1L;
    on tile[0]: out port led_10            = XS1_PORT_1M;
    on tile[0]: out port led_11            = XS1_PORT_1N;
    on tile[0]: out port led_12            = XS1_PORT_1O;

    // If we need to collect them:
    //out port leds_08_12[NUM_LEDS_08_12] = {XS1_PORT_1K, XS1_PORT_1L, XS1_PORT_1M, XS1_PORT_1N, XS1_PORT_1O};

#endif

#define I2C_DISPLAY_MASTER_SPEED_KBPS  100 // 333 is same speed as used in the aquarium in i2c_client_task.xc,
                                           // i2c_internal_config.clockTicks 300 for older XMOS code struct r_i2c in i2c.h and module_i2c_master
#define I2C_INTERNAL_NUM_CLIENTS 1


#if ((MY_CONFIG == CONFIG_USE_INTERFACE) or (MY_CONFIG == CONFIG_USE_CHAN))
    #if (WARNINGS == 1)
        #warning MY_CONFIG == CONFIG_USE_INTERFACE
    #endif

    int data [NUM_DATA_X] [NUM_DATA_Y];

    int main() {

        interface pin_if_1       if_pin    [BUTTONS_NUM_CLIENTS];
        interface button_if_1    if_button [BUTTONS_NUM_CLIENTS];
        interface pwm_if         if_pwm;
        interface softblinker_if if_softblinker;
        i2c_general_commands_if  if_i2c_general_commands;
        i2c_internal_commands_if if_i2c_internal_commands;
        i2c_master_if            if_i2c[I2C_HARDWARE_NUM_BUSES][I2C_HARDWARE_NUM_CLIENTS];

        #if (MY_CONFIG == CONFIG_USE_INTERFACE)
            mic_result_if      if_mic_result;
            #define MIC_RESULT if_mic_result
            #if (WARNINGS == 1)
                #warning MY_CONFIG == CONFIG_USE_INTERFACE
            #endif
        #elif (MY_CONFIG == CONFIG_USE_CHAN)
            chan               c_mic_result;
            #define MIC_RESULT c_mic_result
            #if (WARNINGS == 1)
                #warning MY_CONFIG == CONFIG_USE_CHAN
            #endif
        #endif

        par {
            on tile[0]:{
                configure_clock_src_divide  (pdmclk, p_mclk, 4);
                configure_port_clock_output (p_pdm_clk, pdmclk);
                configure_in_port           (p_pdm_mics, pdmclk);
                start_clock                 (pdmclk);

                streaming chan c_pdm_to_dec[DECIMATOR_COUNT];
                streaming chan c_ds_ptr_output[DECIMATOR_COUNT]; // chan contains pointers to data and control information
                                                                 // Relies oo shared memory, so both's ends taks must on the same tile as this task
                // mic_array_pdm_rx()
                //     samples up to 8 microphones and filters the data to provide up to eight 384 kHz data streams, split in two streams of four channels.
                //     The gain is corrected so that a maximum signal on the PDM microphone corresponds to a maximum signal on the PCM signal.
                // PDM microphones typically have an initialization delay in the order of about 28ms. They also typically have a DC offset.
                // Both of these will be specified in the datasheet.

                par {
                    mic_array_pdm_rx              (p_pdm_mics, c_pdm_to_dec[0], c_pdm_to_dec[1]);                     // in pdm.xc, calls pdm_rx_asm in pdm_rx.xc which never returns
                    mic_array_decimate_to_pcm_4ch (c_pdm_to_dec[0], c_ds_ptr_output[0], MIC_ARRAY_NO_INTERNAL_CHANS); // asm in decimate_to_pcm_4ch.S
                    mic_array_decimate_to_pcm_4ch (c_pdm_to_dec[1], c_ds_ptr_output[1], MIC_ARRAY_NO_INTERNAL_CHANS); // asm in decimate_to_pcm_4ch.S
                    example_task                  (c_ds_ptr_output, data, p_scope_gray, MIC_RESULT);                  // chan contains ptr to shared data, must be on the same tile
                                                                                                                      // as mic_array_decimate_to_pcm_4ch
                }
            }

            par {
                on tile[0]: Handle_Beep_BRRRR_task (
                        if_button, leds_00_07, if_softblinker,
                        if_i2c_internal_commands,
                        if_i2c_general_commands,
                        p_display_notReset,
                        MIC_RESULT);

                // Having this here, and not in combined part below, avoids "line 2303" compiler crash:
                on tile[0].core[0]: I2C_Client_Task_simpler (if_i2c_internal_commands, if_i2c_general_commands, if_i2c);
            }

            on tile[0]: { // Having these share a core avoids "line 183" compiler crash and runs:
                [[combine]]
                par {
                    softblinker_task (if_pwm, if_softblinker);
                    pwm_for_LED_task (if_pwm, led_08);
                    i2c_master (if_i2c[I2C_HARDWARE_IOF_DISPLAY_AND_IO], I2C_HARDWARE_NUM_CLIENTS, p_display_scl, p_display_sda, I2C_DISPLAY_MASTER_SPEED_KBPS); // Synchronous==distributable
                }
            }

            on tile[0]: {
                [[combine]]
                par {
                    Buttons_demux_task (inP_4_buttons, if_pin);
                    Button_task        (IOF_BUTTON_LEFT,      if_pin[IOF_BUTTON_LEFT],      if_button[IOF_BUTTON_LEFT]);      // BUTTON_A
                    Button_task        (IOF_BUTTON_CENTER,    if_pin[IOF_BUTTON_CENTER],    if_button[IOF_BUTTON_CENTER]);    // BUTTON_B
                    Button_task        (IOF_BUTTON_RIGHT,     if_pin[IOF_BUTTON_RIGHT],     if_button[IOF_BUTTON_RIGHT]);     // BUTTON_C
                    Button_task        (IOF_BUTTON_FAR_RIGHT, if_pin[IOF_BUTTON_FAR_RIGHT], if_button[IOF_BUTTON_FAR_RIGHT]); // BUTTON_D
                }
            }
        }
        return 0;
    }
#else
    #error no config
#endif

Read less…

The mic array board only has one only expansion header, with all pins connected to tile[0]! Plus, the mics are connected to the same tile. So I have no code on tile[1] – which gives the system a major slant towards tile[0]. I guess I need to make some multplexer task to make it possible to have my data processing code code on tile[1]. Stay tuned. I will.

Version 0109

This version decimates 4 mics instead of 8. The very strange thing is that this time it’s the negative max value (= min value) that changes, while it’s the most positive value that is constant. But at a different value than the for the negative, which for 0106 was 2147351294 (0x7FFDFAFE). The stuck positive max this time is 670763580 (0x27FB0A3C). What is happening here?

Download 0109

This download may be considered intermediate.

  1. See Download code –  (500 kB) no .build or /bin, but .git

Log B

  1. I am trying to get acquainted with the code and documentation (and my requirements, looking at sound files and their FFT (Fast Fourier Transform) spectrum/spectra), and found this piece of code: 141:[Ordered select combined with default case]

lib_i2c and AN00219

Problems with I2C and DAC(?)

Download of the AN00219_app_lores_DAS_fixed code.

I had problems with getting the appnote AN00219_app_lores_DAS_fixed (above) to always run correctly. The input mics always worked (the center LED lit up), but the headset did not always get streamed DAC values to them. This is a perfect demo to learn about the workings of the board. But it’s not for newbies, with little comments, and it’s obvious that the programmer has not read his Code Complete (by Steve McConnell). (And.., but probably not relevant here (because I meant coding style, I have no idea how programmers fare inside XMOS): The Clean Coder: A Code of Conduct for Professional Programmers (by Robert Martin)). I wish XMOS would have taken their appnote programmers to a course, at least the ones that have coded all the appnotes and libraries I have had any close encounter with. The deep knowledge of the xCore architecture of the programmers always shine through, but also their Code unComplete. Plus, the appnotes could have been updated more..

This demo comes with the MIC-ARRAY-1V0.xn config file which at first seemed to be the only one that worked. However, it did have a problem with compiling the PORT_LED_OEN signal being init to null. On HW 2V0 of mic array board it is tied to ground, and there’s no port output to control it. So I removed it and modified in lib_mic_array_board_support.h in that library:

lib_mic_array_board_support.h mods
#if defined(PORT_LED_OEN)
    #define MIC_BOARD_SUPPORT_LED_PORTS {PORT_LED0_TO_7, PORT_LED8, PORT_LED9, PORT_LED10_TO_12, PORT_LED_OEN}
    /** Structure to describe the LED ports*/
    typedef struct {
        out port p_led0to7;     /**<LED 0 to 7.    P8C              */
        out port p_led8;        /**<LED 8.         P1K              */
        out port p_led9;        /**<LED 9.         P1L              */
        out port p_led10to12;   /**<LED 10 to 12.  P8D 0,1,2        */
        out port p_leds_oen;    /**<LED Output enable (active low). */
    } mabs_led_ports_t;
#else // Mic array board 2V0
    #define MIC_BOARD_SUPPORT_LED_PORTS {PORT_LED0_TO_7, PORT_LED8, PORT_LED9, PORT_LED10_TO_12}
    /** Structure to describe the LED ports*/
    typedef struct {
        out port p_led0to7;     /**<LED 0 to 7.   P8C       */
        out port p_led8;        /**<LED 8.        P1K       */
        out port p_led9;        /**<LED 9.        P1L       */
        out port p_led10to12;   /**<LED 10 to 12. P8D 0,1,2 */ 
    } mabs_led_ports_t;
#endif

I then saw that the lib_mic_array_board_support had an .xn config file by itself, which I removed. Since the compiler would not allow the same name of these config files, I did not like having two. I have no idea how the compiler (or mapper) might treat already defined values.

But I still experienced problems, much like those described in Microphone array AN00219 no sound (below). The lib_12c that was required was >=4.0.0. Observe that even if I have had no no-runs with the below fix, I am not certain that this is the fix. I measured the I2C traffic on the scope. It’s doing all I2C just after DAC_RST_N has gone high (it’s low by pull-down R71 and high impedance output after power up) – to initialise the PLL and the DAC at that phase. It worked, but then, on the “next” debug, it didn’t. But then, when I had all the tiny wires soldered onto I2C_SCL (TP18) and I2C_SDA (TP17) and on R71 and connected to my three inputs on the scope, I seemed to experience that it worked on every(?) debug session. I could hear myself typing away in the headphones every time. From experience then a yellow light lits. Timing problem? Observe that the mic input always worked, the center LED was blinking.

According to the I2C spec (Wiki-refs) chapter “Timing diagram” the SDA signal should never change while SCL is high. But what are the margins? The I2C speed is set to 100 kHz. On the mic array board the SDA and SCL lines are shared on the same port and this is solved in i2c_master_single_port. On all my other boxes (and this one, for that matter – for the display) I have used one bit ports for both SDA and SCL, which is “easier”. But some times they are scarce, so using two bits of a vacant 4 bit port will work on X2 processors.

Since loading with the scope probes seemed to change matters, I downloaded lib_i2c 5.0.0. By scoping the same lines,  the SCL low to SDA low went from 116 ns to 560 ns (see inset). I haven’t inspected the code to see whether this is even possible, the code may be the same. However, the code in i2c_master_single_port.xc differ so much that diff is just as confused as I am. There might be subtle differences that would account for the longer 116 vs. 560 ns timing. Having a short glimpse at the data sheet of the Cirrus Logic chips CS2100-CP PLL [12] and CS43L21 DAC [13] reveals some points that could point towards 116 ns being less repeatable than 560 ns, and may also describe why the scope probes seemed to help:

  • SDA Setup time to SCL Rising tsud is min = 250 ns
  • SDA Hold Time from SCL Falling (“Data must be held for sufficient time to bridge the transition time, tf, of SCL”) thdd = 0 µs

The first thing I had done when I suspected the I2C was to print out all the I2C returns i2c_regop_res_t at the init section of i2s_handler. They were all I2C_REGOP_SUCCESS, even with i2c_lib 4.0.0. But then, there is no parity or CRC checks here, and what would still be open would be single bit errors in the data, even if the start and stop conditions were successful.

I guess that the conclusion must be that if I had downloaded lib_i2c 5.0.0 initially then I would have saved some time! Anyhow, I did learn a thing or two..

Output a sine

I also added an option to output a sine, as suggested my mon2 in Microphone array AN00219 no sound. But it did not work when adding code in the i2s.send callback in the i2s_handler. You’d have to figure out some by yourself:

sine output code
#if (TEST_SINE_480HZ==1)
    #define SINE_TABLE_SIZE 100 // 48 kHz / 100 = 480 Hz
    const int32_t sine_table[SINE_TABLE_SIZE] = // Complete sine
    {
        0x0100da00,0x0200b000,0x02fe8100,0x03f94b00,0x04f01100,
        0x05e1da00,0x06cdb200,0x07b2aa00,0x088fdb00,0x09646600,
        0x0a2f7400,0x0af03700,0x0ba5ed00,0x0c4fde00,0x0ced5f00,
        0x0d7dd100,0x0e00a100,0x0e754b00,0x0edb5a00,0x0f326700,
        0x0f7a1800,0x0fb22700,0x0fda5b00,0x0ff28a00,0x0ffa9c00,
        0x0ff28a00,0x0fda5b00,0x0fb22700,0x0f7a1800,0x0f326700,
        0x0edb5a00,0x0e754b00,0x0e00a100,0x0d7dd100,0x0ced5f00,
        0x0c4fde00,0x0ba5ed00,0x0af03700,0x0a2f7400,0x09646600,
        0x088fdb00,0x07b2aa00,0x06cdb200,0x05e1da00,0x04f01100,
        0x03f94b00,0x02fe8100,0x0200b000,0x0100da00,0x00000000,
        0xfeff2600,0xfdff5000,0xfd017f00,0xfc06b500,0xfb0fef00,
        0xfa1e2600,0xf9324e00,0xf84d5600,0xf7702500,0xf69b9a00,
        0xf5d08c00,0xf50fc900,0xf45a1300,0xf3b02200,0xf312a100,
        0xf2822f00,0xf1ff5f00,0xf18ab500,0xf124a600,0xf0cd9900,
        0xf085e800,0xf04dd900,0xf025a500,0xf00d7600,0xf0056400,
        0xf00d7600,0xf025a500,0xf04dd900,0xf085e800,0xf0cd9900,
        0xf124a600,0xf18ab500,0xf1ff5f00,0xf2822f00,0xf312a100,
        0xf3b02200,0xf45a1300,0xf50fc900,0xf5d08c00,0xf69b9a00,
        0xf7702500,0xf84d5600,0xf9324e00,0xfa1e2600,0xfb0fef00,
        0xfc06b500,0xfd017f00,0xfdff5000,0xfeff2600,0x00000000,
    };
#endif

void lores_DAS_fixed (
    streaming chanend c_ds_output[DECIMATOR_COUNT], 
    client interface  mabs_led_button_if lb, 
    chanend           c_audio) {

    #if (TEST_SINE_480HZ==1)
      size_t iof_sine = 0;
    #endif
    unsafe {
        while(1) {
            select {}

            int output = 0;
            #if (TEST_SINE_480HZ==0)
                for (unsigned i=0;i<7;i++)
                    output += (delay_buffer[(delay_head - delay[i])%MAX_DELAY][i]>>3);
            #elif (TEST_SINE_480HZ==1)
                output = sine_table[iof_sine];
                iof_sine = (iof_sine + 1 ) % SINE_TABLE_SIZE;
            #else
                #error
            #endif
            output = ((int64_t)output * (int64_t)gain)>>16;

            // Update the center LED with a volume indicator
            unsigned value = output >> 20;
            unsigned magnitude = (value * value) >> 8;

            lb.set_led_brightness (12, magnitude); // The frequency is in there and
                                                   // not related to anything here!

            c_audio <: output;
            c_audio <: output;

            delay_head++;
            delay_head%=MAX_DELAY;
        }
    }
}
  • 480 Hz output → 2.083 ms per period → 20.83 µs for each of the 100 samples in the sine
  • DECIMATION_FACTOR 2 // Corresponds to a 48 kHz output sample rate → 20.83 µs
  • Or simply 48 kHz / 100 = 0.48 kHz = 480 Hz

I also saw that the max output was about 1.9V peak-peak when the gain value was 500953, but it did vary on gain going up or down, and it did have some hysteresis. When out of range the signal sounded terrible and looked terrible on the scope. I guess that’s what happens when audio samples wrap around into the DAC.

Version 0200

I have started to merge some of my edited to be more code complete‘ed from AN00219 into my own code. I am planning to do some mere merging so that I can output to the headset. I just assume that would be nice. The 180 Hz sine sine comes from the Online Tone Generator. This code uses these libraries, even if there is no i2s and i2c code for the DAC and PLL yet. I’ll port that from AN00219 for the next version. I’ll come back with analysing the timing here.

Using build modules: 
lib_i2c(5.0.0) 
lib_logging(2.1.0) 
lib_xassert(3.0.0) 
lib_mic_array(3.0.1) 
lib_i2s(2.2.0) 
lib_mic_array_board_support(2.2.0) l
ib_dsp(3.1.0)

With this code, the problem with the dB values seem ok. Both the negative values and the positive values would show the same dB, none is stuck to some value.

xSCOPE 0200

Fig.6 – Beep-BRRR version 2.0.0 with XScope and 180 Hz sine

USE_XSCOPE=1 from makefile is picked up in _globals.h and the function Handle_mic_data in AN00220_app_phase_aligned_example.xc does a:

mic_array_word_t sample = audio[iof_buffer].data[IOF_MIC_0][iof_frame];
XSCOPE_INT (VALUE, sample);

for every sample from mic_0. This is how xTIMEcomposer gets the values. It’s also possible to switch this on and off in the debug configuration with XScope modes Disabled, Off-line or Real-time mode. Anyhow, for my scheme to work it cannot be disabled.

More at xSCOPE (below).

Download 0200

This code is also very intermediate, but it’s a step forward.

See Download code –  (625 kB) no .build or /bin, but .git

Version 0218

In this I have changed the buttons to set the dB sound level of the headset. I have used quite some time to get this right, and understand that the format of gain was something I have called sfix16_t. See file maths_fix16.h. I decided not to use any of the types as defined in lib_dsp or import the advanced libfixmath. I guess this explains most of the pain now solved, with fixed point calculations: (update 13Jan2022: TODO the below cannot be 100% correct):

#define FIX16_UNITY_BITPOS 16                      // # sign bit if signed int
#define FIX16_UNITY        (1<<FIX16_UNITY_BITPOS) // 1.0000 (65565)
#define FIX16_NEG_UNITY    (-1)                    // 11111111 all 1 as 2's complement

typedef uint32_t ufix16_t;
typedef int32_t  sfix16_t;

My problem was to understand the scaling of the AN00219 app. When you see this you will of course understand that if gain is 65536 (1<<16) then the gain is 1.00 and if it’s 32768 (1<<15) the gain is 0.5 which is about the half or 6dB (decibel) down:

output = ((int64_t)output * (int64_t)gain)>>16;

Simple enough, but I did have to repeat all my experience from the years when I did do FFTs in assembler and MPP Pascal in the eighties. (Aside: For the Autronica GL-90 fluid level radar based instrument that we did install a lot of on tank ships (tankers, tankships)). Here is the code I ended up with in my code in file mics_in_headset_out.xc:

typedef ufix16_t headset_gain_t;

#define _HEADSET_GAIN_UNITY_BITPOS FIX16_UNITY_BITPOS
#define _HEADSET_GAIN_UNITY        (1 << _HEADSET_GAIN_UNITY_BITPOS)
#define HEADSET_GAIN_DEFAULT       _HEADSET_GAIN_UNITY

headset_gain_t headset_gain  = HEADSET_GAIN_DEFAULT; // change with buttons
int            output_to_dac = 0;

// ...

// Avoids divide/division
// Avoids dsp_math_divide from lib_dsp, since the xCORE-200 will not divide in one instruction
//
output_to_dac =
    (int) ((((int64_t) output_to_dac * (int64_t) headset_gain)) >> _HEADSET_GAIN_UNITY_BITPOS);

With that many layers of definitions, the gain is that I understand it again. And not going all the way from the top level ufix16_t to headset in one jump, I introduced the headset gain level, which is rather fair if you ask me. Plus, I do fancy explicit (value (or type)) conversions even if I see so many parentheses (plural of parenthesis) added.

ch_ab_bidir

This channel sends bidirectionally between ..task_a (as master) and ..task_b (as slave). This is possible (141:[Using a chanend in both directions]). I have chosen to set the channel contents up as struct with a union. In other words, the menu items like headset_gain in menu_result piggy backs on the (possibly) faster mic_result. This is because sending spontaneously in each direction is not possible without fast getting deadly embraced in a deadlock. (The interface pattern client/server with master/slave would cannot deadlock).

typedef struct {
    ab_src_e source;
    union {
        mic_result_t  mic_result;  // spontaneous message: source is task_a
        menu_result_t menu_result; // required response:   source is task_b
    } data;
} ch_ab_bidir_t;

while(1) in mics_in_headset_out_task_a
    mic_array_get_next_time_domain_frame // synchs on sample frequency
        // handle
        ch_headset_out <: ab_bidir_context
        // handle
        ch_ab_bidir :> ab_bidir_context;
        // handle

while(1) in Handle_Beep_BRRRR_task_b
    select
        // ....
        case ch_ab_bidir :> ab_bidir_context
            // handle
            ch_ab_bidir <: ab_bidir_context;
            // handle

How many samples out of decimation per time?

The value of MIC_ARRAY_MAX_FRAME_SIZE_LOG2 in mic_array_conf.h, used by lib_mic_array in decimate_to_pcm_4ch.S (asm), mic_array_frame.h and  pdm_rx.S (asm) tells how many samples per microphone that my mics_in_headset_out.xc is receiving per mic_array_get_next_time_domain_frame loop. Here are some examples (I have removed the C comment field, that makes the text more readable):

      # MIC_ARRAY_MAX_FRAME_SIZE_LOG2
2 exp(0)  =    1 sample  per microphone in audio.data in example (AN00219)
2 exp(1)  =    2 samples per microphone in audio.data in example
2 exp(2)  =    4 samples per microphone in audio.data in example
2 exp(3)  =    8 samples per microphone in audio.data in example (AN00220)
...
2 exp(13) = 8192 samples per microphone in audio.data in example

I can compile with code for either 0 or 3 for 1 or 8 samples with -DAPPLICATION_NOTE=219 or 220 in makefile, even if I for the headset only have tested with 219.

My problem is how to move on. I am going to do an FFT of some hundred ms of data. Should I collect the data by getting as many of them as possible from the decimators, and process some place in the mic_array_get_next_time_domain_frame loop, or should I send the data sample-by-sample to a background process that could run on another core to do this?

Of course, the sampling frequency is a factor here. I added this to mic_array_conf.h, which shows the range:

From the PDF doc XM010267
48 kHz, 24 kHz, 16 kHz, 12 kHz and 8 kHz output sample rate by default (3.072MHz PDM clock)
Configurable frame size from 1 sample to 8192 samples plus 50% overlapping frames option,
chapter 11 "Four Channel Decimator"

96 kHz divided by 2, 4, 6, 8 or 12. See "fir_coefs.h" and "fir_coefs.xc" in lib_mic_array
#if (DECIMATION_FACTOR == 2)
    #define COEF_ARRAYS           g_third_stage_div_2_fir  // [126]
    #define SAMPLING_FREQUENCY_HZ 48000                    // 48 kHz
    #define FIR_GAIN_COMPENSATION FIR_COMPENSATOR_DIV_2
#elif (DECIMATION_FACTOR == 4)
    #define COEF_ARRAYS           g_third_stage_div_4_fir  // [252]
    #define SAMPLING_FREQUENCY_HZ 24000                    // 24 kHz
    #define FIR_GAIN_COMPENSATION FIR_COMPENSATOR_DIV_4
#elif (DECIMATION_FACTOR == 6)
    #define COEF_ARRAYS           g_third_stage_div_6_fir  // [378]
    #define SAMPLING_FREQUENCY_HZ 16000                    // 16 kHz
    #define FIR_GAIN_COMPENSATION FIR_COMPENSATOR_DIV_6
#elif (DECIMATION_FACTOR == 8)
    #define COEF_ARRAYS           g_third_stage_div_8_fir  // [504]
    #define SAMPLING_FREQUENCY_HZ 12000                    // 12 kHz
    #define FIR_GAIN_COMPENSATION FIR_COMPENSATOR_DIV_8
#elif (DECIMATION_FACTOR == 12)
    #define COEF_ARRAYS           g_third_stage_div_12_fir // [756]
    #define SAMPLING_FREQUENCY_HZ 8000                     // 8 kHz
    #define FIR_GAIN_COMPENSATION FIR_COMPENSATOR_DIV_12
#else
    #error
#endif

I assume that I could accept 8 kHz. The Nyquist–Shannon sampling theorem then offers me a bandwdth up to 4 kHz.  This might be enough for the alarm signals I am going to pick up. For the next version of the .h file I have added this table:

Not used (yet), but nice to have:
#define FRAME_LENGTH_SEC_ ((1/SAMPLING_FREQUENCY_HZ) * MIC_ARRAY_NUM_SAMPLES)    // Time per sample * num samples
                                                                                 // Rewrite and multiply by 1000:
#define FRAME_LENGTH_MS   ((1000 * MIC_ARRAY_NUM_SAMPLES)/SAMPLING_FREQUENCY_HZ) // --"--

// NUM_SAMPLES SAMPLING_FREQ_HZ FRAME_LENGTH_MS NUM_FFT_FREQ_BINS HZ_PER_BIN
// 8192         8000            1024            4096              1.95
// 8192        12000            682.666..       4096              2.92
// 4096         8000            512             2048              3.91
// 4096        12000            341.333..       2048              5.86
// 2048         8000            256             1024              7.81
// 2048        12000            170.666..       1024             11.72

I assume that detecting an alarm would need at least 170 ms of the time sequence.

The alternative is to pick out sample-by-sample of some given sample time, put them in an array of “any” length, bit reverse the sequence indices and run the FFT, and then correlate it with some given frequency response(s) of the alarm(s) I’m going to detect. This would also save me memory, since I don’t have to waste it on the N-1 mics I’m not going to use. I have to set up minimum 4 mics because that’s how the decimator works, but with a 1 sample loop this instead only adds up to 4 samples which I collect only one of. With 8192 samples and 4 mics I need 32K words. I’d save at least 24K words.

To get all of this right, there’s a nice reference in “What is the relation between FFT length and frequency resolution?” [16]. (Update: I added the “BIN” columns above after I read this). When I need it for real I’ll have a second and some third looks. Plus, look in my textbook from the extra course I took in the eighties (or a newer one).

However, doing it sample-by-sample, I will miss out on the fact that the decimator can deliver any full sequence in an index bit-reversed manner. I would also miss out on the FIR filter (Finite Impulse response) that the decimators may do for me. But then.. If I am going to use the headset DAC output, I guess that alone will force me to do it sample-by-sample. I can’t really have a long delay, and I can’t have the samples bit reversed!

In the sample-by-sample case I would need to rely on lib_dsp to run dsp_adaptive_nlms (for the FIR) (if some windowing function of the signal in the frequency domain after the FFT won’t do the filtering good enough for me). The library lib_dsp can also be given time sequence samples directly using dsp_fft_bit_reverse. So sample-by-sample isn’t scary at all!

In other words, how often should I send from ..task_a to ..task_b and how much should I send each time? Plus, do I need an intermediate task to decouple the two tasks?

Download 0218

See Download code –  (1.3 MB) no .build but /bin and .git

Decoupling ..task_a and ..task_b

With less water having flown in this project, I did discuss this also at Chan or interface when critical timing. In other words, I earlier decided not to use interface, partly because it’s not supported in the XTC Tools and lib_xcore. After all, this is going to be new code that I may want to port in the future.

Observe this limitation, however: 141:[[[combine]] and [[combinable]]] where I have described that:

Observe that these are synonymous terms:
channels cannot be used between combined or distributed tasks == channels cannot be used between tasks on the same core, since combined or distributable tasks indeed may run on the same core.

Specification

  1. ..task_a (mics_in_headset_out_task_a which picks up microphone data at sampling rate speeds) cannot be blocked longer than the sampling period since I don’t think the sampling sw “likes” it (hard real-time requirements), plus I wouldn’t like that the unit should have any period where it would not listen for the Beep (alarms of different sorts)
  2. ..task_b (Handle_Beep_BRRRR_task_b) will some times be busy handling buttons (although, that should probably be ok) – but it shall also write to the display via I2C – quite time consuming. I2C is done in [[combined]] and [[distributed]] tasks. Plus, if I decide to pass over sample per sample, all the DSP handling (like FFT) will be done in that task as well, or at least administered by that task
  3. In other words, the display shall be able to be updated and not disturb the mic sampling. This is not like exactly in the Chan or interface when critical timing chapter, where the display basically was updated after a new mics data set

Implementation

Fig.7 – Decoupling of ..task_a and ..task_b – five alternatives – at least the 6th version (PDF)

For A-C I am not to using streaming chan simply because it won’t decouple by more than an extra sample. At least, this is not going to be my first try, even if it only implies adding streaming to the chan and chanend. It does not have any specific capacity – and streaming chan generally does not solve more than what they are designed for. That being said, implementation C became much simpler when I could remove task_z for a streaming chan, since task_z only purpose is to decouple – ending up in implementation E. The same for A ending up in F. In other words, the reasoning starting this paragraph I guess more reflects my experience with not having such a channel. In the CSP-based runtime systems we implemented at work I always just had the asynchronous channel as not containing any data, kind of like an interrupt or signal only (here). Looking it over, I guess my whole struggle with the A-E list suggests this.

Observe that there are streaming chan in my code already, taken from AN00219 and AN00220 (c_pdm_to_dec and chan_ds_ptr_output). See 141:[Synchronous, or asynchronous with streaming or buffered]

Observe that the “callback” scheme that my code (from AN00219 and AN00220) now uses I cannot use here, since what it does is to introduce synchronization on a not-polling basis. Search for i2c_callback_if and i2s_callback_if. Nice, but it’s this I don’t want here.

Implementation A is what I want to go away from, by spec.

Implementation B implies polling on ..task_a. This could be ok, though.

Implementation C is the more complex, but there is no polling. But maybe it needs too many channels and too many cores. I am not certain on how it would work as [[combinable]] tasks, if the comms between Buffer and Decoupler were done with interface calls (which I have decided not to do, but I still think that would be me first try). I’ll be back. This overflow buffer pattern comes from the occam world, where what to do when overflowing is also in full control of the programmer. This is the solution that perhaps is closest to the semantics of the classical bounded buffer (or bounded-buffer) (or even first in, first out FIFO buffer), without using semaphores (Wikipedia: Producer-consumer problem).

Implementation D. See below (here).

Implementation E. A simplification of implementation C with the introduction  of a streaming chan. I was so sure that I should go for it when I couldn’t fall asleep on the night of that decision  thinking why on the earth bother with the extra task_y? Why not just drop it and go for F?

Implementation F. A simplification of implementation A and E with the introduction  of a streaming chan. See even further below (here).

Implementation D (“knock-come”)

This is a pattern that I years ago called “knock-come“. I have, with Promela, formally verified it to be deadlock free, see 009:[The “knock-come” deadlock free pattern]. This solution is not possible without the non-blocking streaming chan which the slave uses to tell that it has some data it wants to get rid of, and then immediately go on with its next activity. It will do this only once per sequence, so it will never block on this channel (any buffered channel will also block when it’s full, this would of course have needed to be dealt with. If one have control of producer’s max filling rate and consumer’s minimum emptying rate and scale the channe’sl capacity accordingly, fine. And if there is a surprise then quite a lot of embedded systems (with less than unbounded buffer capacity) have traditionally just crashed and restarted. Don’t run that car or fly in that plane. This is why designing with synchronous channels as the basic tool is easier to get always right. Observe that on overflow it’s then also easier to have control of if, when and what data to discard.). The figure has two types of arrows, one no blocking / immediate handling and one blocking / waiting possible. Observe that this blocking is not pathologic, not any bad semantics, nothing wrong, this is the way CSP (Communicating Sequential Processes, see Wiki-refs) is meant to be from day one. In CSP a channel communication simply is a mutually shared state that tasks synchronize on. I have a full note about this at Not so blocking after all. For xC, from 141:[1] we read:

Channels provide the simplest method of communication between tasks; they allow synchronous passing of untyped data between tasks. Streaming channels allow asynchronous communication between tasks; they exploit any buffering in the communication fabric of the hardware to provide a simple short-length FIFO between tasks. The amount of buffering is hardware dependent but is typically one or two words of data.”

Observe that even if this pattern of course goes in any direction (left-right as master-slave or slave-master), in this case it’s only the shown roles that would work. It is ..task_b which has the potential to destroy for the time critical ..task_a, which then has to pay the price of doing the “knock“, wait for the “come” (and in the meantime may have to buffer any audio frames that might appear in the meantime) and then block-free send “data” over to the right. Since xC does not have occam’s counted array or variant protocols, ..task_a would need to send twice. First the size, then the data. In other words, there would be four comms between the slave and the master to get the data from slave to master. Master to slave requires only one comm. The good thing is that xCore and xC does all this with little overhead.

AN00219 and AN00220 have while (1) mic_array_get_next_time_domain_framein an endless loop. I need to be able to use a select with the channel from ..task_b as the other component.

The complexity of mic_array_get_next_time_domain_frame is such that wrapping it into a   select is perhaps meaningless. I could put the first channel input in a select (schkct: “Checks for a control token of a given value on a streaming channel end. If the next byte in the channel is a control token which matches the expected value then it is input and discarded, otherwise an exception is raised”):

for(unsigned i=0;i<decimator_count;i++)
    schkct(c_from_decimator[i], 8);

But calling f mic_array_get_next_time_domain_frame has timing requirements, and I don’t know it checking that control token can be done from a select.

Alternatively into a timerafter with zero delay. I did test this, and it seems to work.

I have queried about this at xCore Exchange forum (3).

Update 7Dec2021: I have now implemented the knock-come pattern. It seem like the atomic time interval spent in the slave, in a case of the select as mentioned above, with one return sending to the master and some calculations, seem to use 16-162 10 ns clock cycles = 160 ns to 1.6 µs. This is unbelievably fast. I cannot explain the range: [[TODO]]. I also must use [[ordered]] with this select, if not the come is never served. I cannot explain that either: [[TODO]].

Fig.9

Update 14Dec2021: With the 48 kHz output sample rate (T=20.83 µs) I see that if I do no DAC calculation and output to the headset, all the calculations and the complete knock-come takes 3.0 µs.

Press picture to see three scope screens. One is just after init with DAC on, the middle is standard with no DAC and the lower is standard with DAC again.

Observe the difference between the first and the last. This used to be much larger, the DAC outputs took much longer time before I by some stomach feeling added a single DAC output before the while loop. I observer that after a pause in the DAC, and then using it again, its time usage decreased. So I imaged this might have to do with timing. I tried adding delays after the init, but only the extra output helped.

Structure of ..task_a

The structure of ..task_a now goes like this. [[TODO]] I need to find out of this timing, and why a standard output takes two channel outputs. I have queried about this, see point 4 at xCore Exchange forum (below). I tested with one DAC output, and it’s noise only in the headset.

ch_headset_out <: FIX16_UNITY; // "half-write"

tmr :> time_when_samples_ready_ticks;

while (1) {
    [[ordered]]
    select {

        case ch_ab_bidir :> data_ch_ab_bidir : {
            // Handle come-data:
            ch_ab_bidir <: data_ch_ab_bidir;
        } break;

        case tmr when timerafter (timeout_ticks) :> void: {
            mic_array_get_next_time_domain_frame(..);
            // handle
            // Knock (if state allows):
            ch_ba_knock <: data_ch_ba_knock; 
            if (headset_gain_off) {
                // Do nothing here
            } else {
                // handle
                ch_headset_out <: output_to_dac; 
                ch_headset_out <: output_to_dac;
            }
            // handle timeout_ticks
        } break;
    }
}
Download 0249

See Download code –  (1.3 MB) no .build but /bin and .git

The reasons I dropped knock-come

Fig.10. Trying to send 256 samples across breaks timing

Update 22Dec2021: I have decided to drop knock-come and go for Overflow buffer. There are several reasons:

  1. Since ..task_b is going to do work by its own, not only write to the display over I2C, (I had also planned to do the DSP processing in it), when it will come back and pick up the data from ..task_b there will be “too many” samples that needed to be passed across. In Fig.10 I tested sending 256 samples across and the time critical 48 kHz sampling is broken. For 33 ms (333 kHz I2C writing to the display) not observant time in ..task_a I would need 264 samples : #define KC_SAMPLES_BUF_LEN ((SAMPLING_FREQUENCY_HZ * KC_MAX_WAIT_MS) / (1000 * USE_EVERY_N_SAMPLE)) and values 48000 * 33 / 1000 * 6 = 264. I could pass over in chunks of 128 which would work, but then:
  2. The problem is that even if I let the basic mic_array_get_next_time_domain_frame have its 48 kHz (since I haven’t succeeded with the DAC CS43L21 at 8 kHz) and use only 1/6 of those values, I need to “be there” at 20.83 µs (48 kHz) still. This is what is broken, as seen in Fig.10. The headset turns silent, even if only 1/6 of the timings are not met
  3. Even if I manage to run at 8 kHz for both the display and the DAC out, there would be buffering. I would need to handle some kind of FIFO buffer at both ends, in both tasks. The overflow buffer solution basically is a bounded buffer with detection of overflow and I would need to handle only a single FIFO. I have discussed the problems with that solution as well (above), but I have decided to give it a go.
  4. Plus, the extra select case in ..task_a and knock-come states, I can certainly do without. My next implementation will go back to where it started: one ..task_a output per sample only. Even if the complexity certainly is moved to the two overflow buffer tasks. A pipe or a bounded buffer aren’t just data!
  5. I can understand why the XMOS solution are channels with shared pointers. However, I am at appliaction level concurrency, sending pointers across is none of my business. Real channels are it. And, they can cross tiles as well!
Download 0255

This is the last version with knock-come. See Download code –  (520 KB) no .build and .git but  /bin .

Implementation F (“simplest possible”)

Fig.11 – Implementation F is “simplest possible”

It took me having to think through implementations A, B, C, D and E before I saw this implementation. I am hugely naïve on streaming chan, that’s for sure. But I seem to be learning, don’t I? I believe this will work for the following reasons:

  1. ..task_a will never block because it will ever send one mic data between each RDY. A streaming chan buffers one element (as far as I know), which is just what’s needed in this case. It would have 20.83 µs (48 kHz) to normally get rid of the data
  2. If a RDY has not arrived it would buffer in a FIFO buffer. No problem. This would be rather large, see theKC_SAMPLES_BUF_LEN formula (above). Remember ..task_a will be non-responsive when it writes to the display over I2C or does DSP processing
  3. Gain etc from the button pressing and menu system in ..task_b would be sent down to ..task_a on no invitation. It can afford to block since it would never deadlock with ..task_a since the other direction is a one-dataset-only per sending streaming chan
  4. How a full buffer should be sent “up” there would be two solutions I guess:
  5. ..Either one sample-by-sample, in which case, for the full speed 48 kHz it would have to catch up all samples before the next major unresponsiveness. I guess this is the critical part
  6. ..or send all that’s in the buffer when the RDY arrives. For this to happen I need to add length of a next message, so that message may include “all”. Provided streaming buffers any kind of data. But since C/xC does not allow counted arrays and variant protocols (I miss occam), I may still have to break this up, in like 100 and 100 samples. And then, C does not allow elements of arrays like [data FROM 200 FOR 100] (I miss occam 2) I’d have to do memcpy to get this done, one more than necessary, depending how the compiler behaves. I’ll stick to sample-by-sample as a first try. Update 29Dec2021: If ..task_a sends how many samples are left (like 190), then ..task_b may simply return a number saying how many samples it is waiting for next (like 200), to get both sides to agree on the next sending. The next sending would then be (like 200), where (like 193) are relevant (3 added since last, 7 empty samples). I had to do an extra round for this to work, since a streaming chan only is non-blocking up for max. 8 bytes

Fig.12 – Implementation F, sending of samples

Fig.12 shows how this version behaves when ..task_b is not listening on the channel for some time. I assume that the two scope screenshots speak for themselves.

Sending over the lost samples (in this version I just send some test values, which I even don’t analyse in ..task_b) is done like in point 6 above. To implement the simple protocol with counted array, which in occam is declared like this:

CHAN OF INT::[]INT packet:

proved to be more complex than I thought. If you are interested, instead of downloading the full code’s zip, here are the most important files (here):

  1. mic_array_conf_h.txt – This is needed for the lib_mic_array
  2. mics_in_headset_out_h.txt – Search fo the union of small to longer and longer packets: ch_ab_all_samples_t;
  3. mics_in_headset_out_xc.txt – Contains ..task_a =  mics_in_headset_out_task_a. Search for get_chan_mutual_packet_dim, send_variant_protocol_then_shift_down And see how was memcpy was to the alternatives) and receive_variant_protocol
  4. _Beep_BRRR_01_xc.txt – Contains ..task_b = Handle_Beep_BRRRR_task_b. Search for receive_variant_protocol

I said it was complex, but I finally got it alle spliced together. The scope pictures show it running.

But keep in mind that since I (now, at least), need to get samples at 48 kHz (20.83 µs), then sending over of the buffered data in the packets at 8 kHz rate, I still needed to get it done in much less that 20.83 µs. So there were a lot of scope pictures and versions before I found out how to do it: send max 128 samples at a time (good margin) and use memcpy to shift the buffer down. I did not use a ring buffer, since I think that would not have helped. (Update 13Jan2022: the memcpy usage is wrong in this 0414 version, since I have overlapping segments. It has been fixed in a newer version, coming soon.)

MCLK, DAC and PDM mics SW
fig13_219_xmos_xcore_microphone_array_hardware_manual_about_mclk

Fig.13 Overview of MCLK, DAC and PDM mics. Derived from XMOS AN00219, view PDF here.

This is meant as an overview for myself, to try to

  • Understand what the XMOS AN00219 and AN00220 did
  • What I have done with that code (basically names for numbers, or better names for names) plus comments
  • Maybe there’s a hidden clue here as to why I can sample the mics at 48 kHz down to 8 kHz, but the DACs for the headset do 48 kHz only. I have tried to discuss this a lot here, but I have also reached out, like at Stack Exchange, Electrical Engineering, point 1 (below)
Download 0414

This is the last version with knock-come. See Download code –  (520 KB) no .build and .git but  /bin . But some are also listed above.

Version 0437

Fig.14 Task diagram of 0437 (press for PDF)

Fig.14 shows the task diagram, almost like xTIMEcomposer generates it. The export from it is a bad bitmap jpg file, so I drew this in macOS Pages. I guess it speaks for itself. I added some detail, like the Constraint check from xmap. These show how many cores, timers, channels and memory that are used for each tile.

I have done quite a lot of unit testing (or rather task to task testing) on this version. Now sound samples arrive in a correct time series into dsp_task_y. 1024 samples are filled there in 128 ms, since the 48 kHz for the headset DAC output is divided by 6 and every 6th sample is sent away at an 8 kHz pace.

The communication as shown in fig.11 had a serious flaw. The sample that was used to tell “I have got more batch array data” of data_sch_phase1_t (after a button press at which it didn’t get rid of those samples) I had actually used and spliced them into the data set. Since there were older samples not sent away yet, newer samples were interleaved. One for each batch sent, and since I send 90 per batch that would be three wrong samples. To solve this I instead added those samples to the buffer of the sender’s array. Now in the “I have got more batch array data” the sample value there is treated with union:

typedef struct {
    union {
        mic_sample_t mic_sample; // iff chan_mutual_packet_dim == NUM_000
        int32_t      is_void;    // iff chan_mutual_packet_dim >= NUM_001 (MIC_SAMPLE_VOID)
    } valid;
} mic_sample_iff_t;

This caused the algorithm to send typically (for 264 samples collected during the 33 ms time while the receiver could not pick them up since it was busy writing to the display) 90 → 90 → 90 samples. But then, to get rid of the last picked up which also used an is_void sample for the last 90 , with this phase1-2-3 scheme this final value needed to be sent across. So now there are four of these sequences instead of three.

The data path of the samples now looks like this:

tile[0]: mic_array_pdm_rxmic_array_decimate_to_pcm_4ch → mics_in_headset_out_task_a → Handle_Beep_BRRRR_task_b which only kind of “smells” the data set but sends all of it over to tile[1]: → buffer_task_x → dsp_task_y (more cores vacant there!)

Now I’ll start with the real DSP stuff in dsp_task_y. I will hard code all data for the FFT etc, and then later add a chan up to the GUI (buttons and display) handling in Handle_Beep_BRRRR_task_b.

Download 0437

This is the first version with implementation F and it all working. No application specific DSP code yet. See Download code –  (540 KB) no .build and .git but  /bin.

DAC analog out alternatives

See fig.9 and the discussions there. Plus I have queried about this at xCore Exchange forum (3) and Stack Exchange, Electrical Engineering (1) plus not the least point (2).

I have not succeeded yet to run both the sampling and the DAC at 8 kHz. Alternatives might be:

  1. Go on like I do, with 48 kHz (for DAC) and using only 1/6 of the samples (for DSP)
  2. Use an external DAC, I have 5 not much used GPIO single bits ports left:
  3. NXP UDA1334ATS as used in Adafruit board 3678 (here). But it stops at 16 kHz I think. Plus, it has reached end of life, I think
  4. I think Microchip MCP4921/4922 would allow 8 kHz
  5. Cirrus logic WM8731 Codec with Headphone Driver certainly would take me down to 8 kHz. It sits in several board, like the MikroElektronika Audio Codec Board – PROTO here. Since this also has mic input, I could let the whole XMOS mic_array board go and use “any” XMOS board. But then, I would first look at the the board is the XMOS XK-VOICE-L71 here (containing an xcore.ai of type XU316-1024-QF60A-C24) – which also contains two MEMS mics
  6. Make a PWM output and a low pass filter and amp for the headphones is probably the easiest. Then I would have only myself to relate to. Well, almost. I probably would buy a MikroElektronika Headphone AMP click here which contains an LM4811 from Texas Instruments

xSCOPE

Update: I have struggled with xSCOPE, with real-time and 48 kHz values. But when I went over to 8 kHz, the curve seemed to show up (every time).

Observe that using xSCOPE (more at 143:[Analysis and xSCOPE]) probably requires that you do a debug print first. At least for me, today and yesterday.. For some reason a print has to happen after some time after startup. Maybe this has to do with my (in 2021) old Mac Mini (mid 2010)? I therefore added this delay:

#if (USE_XSCOPE == 1)
    // 2000, 1500 did not do the trick:
    delay_milliseconds (DELAY_FIRST_PRINT_MS); 
#endif
debug_print("%s\n", "Handle_Beep_BRRRR_task_b started");

I have shown more at xSCOPE 0200 (above).

I control printing with a

#define DO_DEBUG_PRINT 1 
#include "_print_macros_xc.h"

at the top of each .xc file. That system uses the lib_logging and debug_print.h and debug_conf.h which has a similar scheme. I don’t use it, I think that decision was based on the fact that I had more control of different printings in the same file with my scheme (even if I don’t use it at the moment).

Another matter. Also see 141:[Buffered asynchronous real-time printf] if printing should not block your cores. One blocks all!

Then, my config.xscope look like this:

<xSCOPEconfig ioMode="none" enabled="true">
    <!-- Remember #include <xscope.h> -->
    <Probe name="Value" type="CONTINUOUS" datatype="INT" units="Value" enabled="true"/>
    <!-- From the target code, call: xscope_int(VALUE, my_value);  -->
</xSCOPEconfig>

Also remember the |Debug Configurations|, the |xScope| tag, set to |Real-Time Mode|.

I also have a macro set defined such that I can turn xSCOPE on and off from Makefile. It goes like this (in _globals.h):

#ifndef USE_XSCOPE
    #error USE_XSCOPE not defined in makefile
#elif (USE_XSCOPE == 0)
    #define USE_XSCOPE(name,val)
#elif (USE_XSCOPE == 1)
    #if (DEBUG_PRINT_GLOBAL_APP == 0)
        #warning XSCOPE probably not working. Set to 1, plus some DO_DEBUG_PRINT must also be set
    #endif

    #define DO_XSCOPE_INT(name,val) xscope_int(name,val)

    // xScope scaling, if not top and bottom is not seen:
    #define XSCOPE_SCALE_MAX   1100
    #define XSCOPE_VALUE_TRUE  1000
    #define XSCOPE_VALUE_FALSE  100
    #define XSCOPE_SCALE_MIN      0
#endif

Of course, the .xn file also needs to contain the correct elements, but that’s another matter. This file is initially set by xTIMEcomposer, but I certainly have edited in mine. But not about any xscope.. matter.

Download code

When this project is finished the code will be downloadable from My xC code downloads page. However, on this page I have the following local intermediate downloads. So download any while it’s still here if you are curious. Observe that I change naming in the code so often that from one version to the next there may be quite some changes, and I like to think that they are improvements that increase my understanding of what I’m doing. If I above have shown code examples related to a particular version, that code is of course not updated. I would probably have jumped right to the newest:

  1. Download 0106 (9Sep2021)
  2. Download 0109 (12Sep2021)
  3. Download 0200 (20Oct2021)
  4. Download 0218 (11Nov2021)
  5. Download 0249 (14Dec2021)
  6. Download 0249 (21Dec2021) Last version with “knock-come”
  7. Download 0414 (5Jan2022) First version with “Implementation F”. See some of the files right now above
  8. Download 0437 (26Jan2022)

The choices

I guess there are hundreds of choices here, but if I limit myself to XMOS, this is where they are. Observe again the Standard disclaimer!

I must realise that XMOS say that they have four voice types of usages. I guess their menu shows it the best (as of 22Sep2021):

  • Voice interfaces – XVF3510 | XFV3500
    2-mic with Two Dev kit boards. See XMOS XVF3510 stereo voice processor (below)
  • Conference calling – XVF3000 | XFV3100
    4-mic. See XMOS XVF3000/3100 mono voice processor (below)
  • USB & multichannel audio
    Two boards for “DJ decks and mixers”. Not relevant here
  • Microphone aggregation
    8-mic board and one xCORE-200 that I can code myself, even in xC – or C and lib_xcore. This is is the solution I have chosen in my Beep-BRRR, since I am not after just getting the job done.This is a hobby that I also want to learn from and do coding. Also in this case! Even if it seems to dawn on me that using one of these boards and associated config tools might have taken me to the goal faster

XMOS XVF3510 stereo voice processor

See [9]. This seems to be a plug-and-play board that connects to a microphone board. They basically consist of a very advanced (my words) audio pipeline with configurable parameters – and internal firmware as supplied by XMOS. I think it contains the usual slices and cores of the xCORE architecture under the hood, but it’s not for me. It does have a Development kit. It is visible as a MIDI unit. It is also meaningful with Audacity.

See 098:[xCORE-VOICE] where I have quoted someone to say “XVF3510 is an XMOS turnkey firmware that runs on the xCORE-200.”

There are two processors, and two dev kits for them. Both of the dev kits contain two boards and a small 2-mic board: “one for USB plugin accessory implementations (with a USB Control Interface) and another for built-in voice implementations (with an I2C Control Interface).”

  • XVF3510-INT for voice interface integrated into the product
  • XVF3510-UA for USB plug-in voice accessory, and integrated products using USB

Aside: For some reason, these processors remind me of the INMOS IMS A100 cascadable signal processors from 1989. See www.transputer.net.

XMOS XVF3000/3100 mono voice processor

This is a processor with 4-mic mono inputs. No dev board. “The XVF3100 variant includes the Sensory TrulyHandsfreeTM wakeword.” [11]. It’s a day’s work to study the differences between these variants, which isn’t relevant here.

Tools

Sound

Sound Studio

I have used this tool since 2002, and I just love it. But I see that it has its limitations to the use I need here.

Audacity

See [10]. XMOS show in their XFV3510 Dev kit setup how Audacity is directly used with it. Audacity is “Free, open source, cross-platform audio software”.

Online Tone Generator

by Tomasz Szynalski. Hear it at https://www.szynalski.com/tone-generator/ (Hz: 180)

Math /DSP

baudline

by SigBlips DSP Engineering. From http://www.baudline.com/ I quote:

Baudline is a time-frequency browser designed for scientific visualization of the spectral domain. Signal analysis is performed by Fourier, correlation, and raster transforms that create colorful spectrograms with vibrant detail. Conduct test and measurement experiments with the built in function generator, or play back audio files with a multitude of effects and filters. The baudline signal analyzer combines fast digital signal processing, versatile high speed displays, and continuous capture tools for hunting down and studying elusive signal characteristics.

Also for MacOS. I found this in [16].

MATLAB

MATLAB (by MathWorks) isn’t too expensive used at home. It can be used with a plethora of toolboxes, see https://se.mathworks.com/store/link/products/standard/ML.

Scratchpad

LSTM

14Jan2022: Wiith the new xcore.ai, maybe using Long short-term memory (LSTM) to distinguish sound is a viable path (thanks, student!). This may be mentioned in the XMOS literature as well. Maybe I could even make a hand-coded algorithm based on this to treat the peaks of sequences of spectra (after FFTs) to detect any type of alarm? I could ask the user to have my unit “record” the alarm sound and then just fix some parameters to have it included as the set of sounds to relate to (or not relate to). I read in Wikipedia that “a recurrent neural network (RNN) is a class of artificial neural networks where connections between nodes form a directed or undirected graph along a temporal sequence” (Wikipedia RNN here). Maybe “temporal” is the opposite to “frequency domain” – or maybe temporal for my case would imply a sequence of some sort only? Then, about LSTM, from Wikipedia (here):

Long short-term memory (LSTM) is an artificial recurrent neural network (RNN) architecture[1] used in the field of deep learning. Unlike standard feedforward neural networks, LSTM has feedback connections. It can process not only single data points (such as images), but also entire sequences of data (such as speech or video). For example, LSTM is applicable to tasks such as unsegmented, connected handwriting recognition,[2] speech recognition[3][4] and anomaly detection in network traffic or IDSs (intrusion detection systems). (14Jan2022)

Also see Introduction to LSTM Units in RNN by Gaurav Singhal [17]. From the student report where I found all this I quote, for back-ref to myself: “The LSTM model has been implemented in 64-bit version of Python 3.9.8. Pandas and Numpy libraries have been used to treat and manipulate the data set, while Tensorflow, Keras and Scikit-learn have been used to make the model. The Matplotlib library has been used to graph, visualize and show data and results.”

Forums

xCore Exchange forum

Newest at the bottom:

  1. Microphone array AN00219 no sound – mspit, 22Oct2019
  2. Filling out unused cores with while(1) – me, 28sep2021
  3. mic_array_get_next_time_domain_frame in a select? – me, 19Nov2021. Relates to Implementation D
  4. mic_array board and adc out time usage – me, 14dec2021. Relates to the same as the point above. Observe error in title and most of the place: ADC should be DAC of course!

Stack Exchange

Electrical Engineering

  1. I have an XMOS 7-mic board using the CS43L21 for ADC output. I get it working at 48 kHz, but not at 8 kHz, following the example of XMOS AN00219 – me, 18Dec2021. Observe error in title and most of the place: ADC should be DAC of course!
  2. May I write to a DAC chip slower than data is “sampled” at? – me, 28dec2021

[[TODO]]

References

Wiki-refs: Communicating sequential processes (CSP)DecibelDelta-sigma modulation Fast Fourier Transform (FFT)FIR filter (Finite impulse response).  I2C. libfixmath. Long short-term memory (LSTM). Microelectromechanical systems (MEMS). Nyquist–Shannon sampling theoremPDM. Producer–consumer problem. Recurrent neural network (RNN). Q (number format) (also see [8])

  1. MICROPHONE AGGREGATION SOLUTIONS by XMOS, with ref to software tools, software libraries, application notes, hardware manual (below), design files and advisories & notices, see https://www.xmos.ai/microphone-aggregation/
  2. xCORE Microphone Array Hardware Manual (XMOS XM009730A 2018/6/14), download from [1] or https://www.xmos.ai/download/xCORE-Microphone-Array-hardware-manual(2V0).pdf –
    There is a small fault in it , with the errata here: On page 14 the pin X0D39 is called LED_OEN when it in fact goes to the expansion header J5 pin 10 (this is ok documented). The LEDs are always output enabled (OE) by all the hardware buffer chips 74LVC125A and 74LVC1G125 all have their OE control pins tied to GND, ie. always enabling all LEDs.
    Update: in lib_mic_array_board_support there is an #if defined PORT_LED_OEN which indicates that this output has at some stage been connected to the LED OE control pins
  3. XUF216-512-TQ128 Datasheet, 2020/10/05 XMOS Document Number: X007542, see https://www.xmos.ai/download/XUF216-512-TQ128-Datasheet 1.17.pdf. This document is not for my processors, but it’s only the QSPI flash that has changed. But it has a much nicer layout than the older documents, by which [1] on 15Apr2021 is referring to the older document 1.16. There is a “latest” url here: https://www.xmos.ai/file/xuf216-512-tq128-datasheet?version=latest
  4. USB Audio Design Guide by XMOS, 2016/5/12 (XM0088546.1). Also covers the xCORE-200 Microphone Array Board and not only USB. It even has a chapter named “The xCORE-200 Array Microphone Board“. 110 pages. See https://www.xmos.ai/download/sw_usb_audio:-sw_usb_audio-[design-guide](6.16.1alpha1).pdf. XMOS helped me find it, even if it didn’t show up on any search: “This design guide can be found on our website on usb multichannel audio page here https://www.xmos.ai/usb-multichannel-audio/. If you scroll down to the quick links section at the bottom of the page you will find the document linked under the ‘software design guide’ link.
  5. XMOS USB – MEMS microphone array audio interface by Simon Gapp (2021). Part of master thesis at TU Berlin, Department of Engineering Acoustics. See https://github.com/simongapp/xmos_usb_mems_interface
  6. USB Audio 2.0 Device Software – source code by XMOS. See https://www.xmos.ai/file/usbaudiodevice-software/ which downloads sw_usb_audio-[sw]_6 with directories lib_logging, sc_i2c, sc_usb_device, lib_mic_array, sc_spdif, sc_util, lib_xassert, sc_usb, sc_xud, sc_adat, sc_usb_audio, sw_usb_audio
  7. Microphone array library (3.0.1) by XMOS (2017). See https://www.xmos.ai/download/lib_mic_array-[userguide](3.0.1rc1).pdf (from https://www.xmos.ai/libraries/). Also download from https://www.xmos.ai/file/lib_mic_array-userguide/
  8. Fixed-Point Representation & Fractional Math, Revision 1.2 by Erick L. Oberstar (2007). See https://www.researchgate.net/publication/235791711_Fixed-Point_Representation_Fractional_Math_Revison_12?channel=doi&linkId=0912f5138a6546059b000000&showFulltext=true. Provides an overall understanding of the nature of Q[QI].[QF] format integer fixed-point numbers. Also see Wiki-refs (above)
  9. VocalFusion® XVF3510 Voice processor by XMOS, see https://www.xmos.ai/?s=xvf3510 and https://www.xmos.ai/download/XVF3510-User-Guide(4.2).pdf VocalFusion® XVF3510 VOICE PROCESSOR USER GUIDE V4.2 (2021) 81 pages
  10. Audacity, see https://www.audacityteam.org – “Free, open source, cross-platform audio software”
  11. Conference calling solutions by XMOS. See https://www.xmos.ai/vocalfusion-conference-calling/
  12. Fractional-N Clock Multiplier CS2100-CP by Cirrus Logic. The PLL chip. See https://statics.cirrus.com/pubs/proDatasheet/CS2100-CP_F3.pdf
  13. Low-Power, Stereo Digital-to-Analog Converter CS43L21 by Cirrus Logic. The DAC chip. See https://statics.cirrus.com/pubs/proDatasheet/CS43L21_F2.pdf
  14. Adafruit PDM Microphone Breakout, products 3492 and 4346, see https://learn.adafruit.com/adafruit-pdm-microphone-breakout. Based on the MEMS audio sensor omnidirectional digital microphone MP34DT01-M (2014 STMicroelectronics, 2021 obsoleted)
  15. INFINEON IM69D130 MEMS PDM microphone, see https://www.infineon.com/cms/en/product/sensor/mems-microphones/mems-microphones-for-consumer/im69d130/ (contrary to the Adafruit board in [14] (above), these are per 2021 not obsoleted)
  16. What is the relation between FFT length and frequency resolution? (2011) at StackExchange, ElectricalEngineering, see https://electronics.stackexchange.com/questions/12407/what-is-the-relation-between-fft-length-and-frequency-resolution? – thanks Simon Gapp, for the reference!
  17. Introduction to LSTM Units in RNN by Gaurav Singhal (2020), see https://www.pluralsight.com/guides/introduction-to-lstm-units-in-rnn (thanks, student, for this ref!)

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.