PDM IN TO MEM OUT Example

Brief

This example show how to use pdm microphones on the audio addon board ir EVK. It aims to record one or multiple microphones and output data into individual files per microphone. Each file will be saved in the build folder and will be named like this: out_files_{mic}.wav where {mic} can be:

  • EVK only:
    • vm: Vesper microphone

  • EVK + AudioAddon:
    • al : A Left

    • ar : A Right

    • bl : B Left

    • br : B Right

Hardware configuration

EVK Only

To enable the vesper microphone in the EVK, you need to put these 2 jumpers:

  • J7

  • jumper on CN9 between pin 1-2

EVK + AudioAddon

  • 1 to 4 pdm microphone connected to the serial audio interface it can be either the ones on the board or other pdm microphone

  • GAP9Mod v1.0b / GAP9EVK v1.3 / Audio addon v2.1

Added to this, you need an FFC cable to connect the GAP9EVK (FFC1 connector) with the Audio addon (FFC1 connector) to provide serial audio interface to the audio addon’s DACs.

Audio addon’s jumpers needs to be configured as described below:

  1. P4 and P9 needs to be closed to output Left channel on the jack connector.

  2. P10 and P11 needs to be closed to output Right channel on the jack connector.

  3. P3 needs to be set on VROOT.

  4. P5 needs to be set on LDO.

  5. P6 needs to be set on LDO.

  6. Configure Audio Addon SAIs and PDM microphones in PDM mode:

    • W1: Opened

    • W4: Opened

    • W6: Opened

    • W10: Opened

    • W11: Opened

    • W14: Opened

    • W12: Closed - MicA Data wired to SAI2_SDO

    • W13: Closed - MicB Data wired to SAI1_SDO

    • W15: Closed - MicA Clock wired to SAI2_SCk (MicB Clock is naturally wired to SAI1_SCK)

    if you use embedded microphone from audio addon board: - M1: Closed - M2: Closed - M3: Closed - M4: Closed

SW/CMake configuration

To work properly, this example require the following options (already set in the sdk.config file in this directory):

Option name

Meaning

CONFIG_BOARD_GAP9MOD_V1_0_B

GAP9Mod board is used by this example.

CONFIG_BOARD_GAP9EVK_V1_3

GAP9EVK board is used by this example.

CONFIG_PLATFORM_BOARD

This example runs on board platform

CONFIG_DRIVER_TYPE_FLASH

This example requires a flash since it uses the SFU

CONFIG_DRIVER_MX25U51245G

This example use the MX25U51245G flash

CONFIG_DRIVER_I2S

This example require to configure the i2s

CONFIG_DRIVER_HOSTFS

This example require it to write in the pc memory

CONFIG_DRIVER_SFU

This examples uses the SFU

In case of AudioAddon (default setting), it also requires these:

Option name

Meaning

CONFIG_BOARD_AUDIO_ADDON_V2_1

Audio addon v2.1 board is used by this example.

CONFIG_BOARD_AUDIO_ADDON_V2_1_OPTION_SSM6515_PDM

Audio addon v2.1 board is configured in PDM mode

CONFIG_DRIVER_T3902

This example use the t3902 hardware in the devicetree

How to run

  1. Configure CMake

cmake -B build
  1. Select the wanted audio source (Vesper Mic if AudioAddon is not selected else Mic A left and/or A right and/or Mic B left and/or Mic B right) in the “Application” menu

cmake -- build build -t menuconfig
  1. Build and run the application

cmake --build build -t run

Code

/*
 * Copyright (C) 2023 GreenWaves Technologies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "pmsis.h"
#include "sfu_pmsis_runtime.h"
#include "Graph_L2_Descr.h"
#include "wav_out.h"
#include "bsp/bsp.h"

#define Q_SIG_IN 31
#define Q_SIG_OUT 18

#define DURATION_S    2
#define SAMPLING_RATE 48000
#define WORD_SIZE 32
#define WORD_BYTES WORD_SIZE/8


#define BUFF_LENGTH (DURATION_S*SAMPLING_RATE)
#define BUFF_SIZE (BUFF_LENGTH*WORD_BYTES)

#define SAI_ID               (48)
#define SAI_SCK(itf)         (48+(itf*4)+0)
#define SAI_WS(itf)          (48+(itf*4)+1)
#define SAI_SDI(itf)         (48+(itf*4)+2)
#define SAI_SDO(itf)         (48+(itf*4)+3)

#ifdef CONFIG_AUDIO_FROM_MIC_A_LEFT
    #define MICRO_PDM_CHANNEL_AL    DT_MICRO_AL_PDM_CHANNEL
    #define MICRO_PDM_SAI_AL        DT_MICRO_AL_SAI_ITF
    #define MIC_PDM_AL_DEVICE       PI_MIC_MICRO_AL_ID
#endif

#ifdef CONFIG_AUDIO_FROM_MIC_A_RIGHT
    #define MICRO_PDM_CHANNEL_AR    DT_MICRO_AR_PDM_CHANNEL
    #define MICRO_PDM_SAI_AR        DT_MICRO_AR_SAI_ITF
    #define MIC_PDM_AR_DEVICE       PI_MIC_MICRO_AR_ID
#endif

#ifdef CONFIG_AUDIO_FROM_MIC_B_LEFT
    #define MICRO_PDM_CHANNEL_BL    DT_MICRO_BL_PDM_CHANNEL
    #define MICRO_PDM_SAI_BL        DT_MICRO_BL_SAI_ITF
    #define MIC_PDM_BL_DEVICE       PI_MIC_MICRO_BL_ID
#endif

#ifdef CONFIG_AUDIO_FROM_MIC_B_RIGHT
    #define MICRO_PDM_CHANNEL_BR    DT_MICRO_BR_PDM_CHANNEL
    #define MICRO_PDM_SAI_BR        DT_MICRO_BR_SAI_ITF
    #define MIC_PDM_BR_DEVICE       PI_MIC_MICRO_BR_ID
#endif

#ifdef CONFIG_AUDIO_FROM_VESPER_MIC
    #define MICRO_PDM_CHANNEL_VM    2
    #define MICRO_PDM_SAI_VM        1
#endif

pi_device_t* mic_dev;

static int open_i2s_PDM(struct pi_device *i2s, u_int32_t SAIn, u_int32_t Frequency, u_int32_t Direction, u_int32_t Diff)
{
    struct pi_i2s_conf i2s_conf;
    pi_i2s_conf_init(&i2s_conf);

    // polarity: b0: SDI: slave/master, b1:SDO: slave/master	1:RX, 0:TX
    // i2s_conf.options = PI_I2S_OPT_REF_CLK_FAST;
    i2s_conf.frame_clk_freq = Frequency;       		    // In pdm mode, the frame_clk_freq = i2s_clk
    i2s_conf.itf = SAIn;                          	    // Which sai interface
    i2s_conf.mode = PI_I2S_MODE_PDM;                	// Choose PDM mode
    i2s_conf.pdm_direction = Direction;               	// 2b'11 slave on both SDI and SDO (SDO under test)
    i2s_conf.pdm_diff = Diff;                           // Set differential mode on pairs (TX only)

    //    i2s_conf.options |= PI_I2S_OPT_EXT_CLK;             // Put I2S CLK in input mode for safety

    pi_open_from_conf(i2s, &i2s_conf);

    if (pi_i2s_open(i2s))
    {
        printf("Failed to open the SAI\n");
        return -1;
    }

    pi_pad_function_set(SAI_SCK(SAIn),PI_PAD_FUNC0);
    pi_pad_function_set(SAI_SDI(SAIn),PI_PAD_FUNC0);
    pi_pad_function_set(SAI_SDO(SAIn),PI_PAD_FUNC0);
    pi_pad_function_set(SAI_WS(SAIn),PI_PAD_FUNC0);

    return 0;
}

static int32_t Mic_In_to_Mem_Out(struct pi_device i2s,int32_t sai_itf, int32_t sai_channel, char Mic_n[3])
{
    pi_sfu_buffer_t sfu_buffer;
    pi_sfu_conf_t conf = {0};
    pi_sfu_conf_init (&conf);
    if (pi_sfu_open(&conf)) return -1;

    void *buffer = pi_l2_malloc(BUFF_SIZE);
    if (buffer == NULL)
    {
        printf("Failed to alloc memory\n");
        return -1;
    }

    pi_sfu_buffer_init( &sfu_buffer, buffer, BUFF_LENGTH, 4);

    // Get in and out uDMA channels
    pi_sfu_graph_t *graph = pi_sfu_graph_open(&SFU_RTD(Graph));
    pi_sfu_mem_port_t *port = pi_sfu_mem_port_get(graph, SFU_Name(Graph, Out1));
    pi_sfu_enqueue(graph, port , &sfu_buffer);

    pi_sfu_pdm_itf_id_t pdm_itf_id = {
        sai_itf,            // Microphone SAI
        sai_channel,        // SFU Channel dedicated to input SAI right channel
        0                   // Input
    };
    // Connect Channels to SFU
    pi_sfu_graph_pdm_bind(graph, SFU_Name(Graph, In1), &pdm_itf_id);

    // Let the microphone start (20ms in datasheet) before capturing
    pi_i2s_ioctl(&i2s, PI_I2S_IOCTL_START, NULL);
    pi_time_wait_us(30000);

    // Load and start Graph
    printf("Starting Mic %s\n", Mic_n);
    pi_sfu_graph_load( graph );
    pi_time_wait_us(DURATION_S*1000*1000);
    pi_i2s_ioctl(&i2s, PI_I2S_IOCTL_STOP, NULL);
    printf("Stopped Mic %s\n", Mic_n);

    int *       buffer_org   = (int *)       buffer;
    short int * buffer_short = (short int *) buffer;
    for (int i=0; i<BUFF_LENGTH; i++) {
        buffer_short[i] = (short int) (buffer_org[i] >> (Q_SIG_IN - Q_SIG_OUT));
    }
    /* dumping */
    char path[30];
    sprintf(path, "out_file_%s.wav", Mic_n);
    printf("Writing buff of %d Bytes to file %s\n", BUFF_SIZE/2, path);
    dump_wav_open(path, 16, SAMPLING_RATE, 1, BUFF_SIZE/2);
    dump_wav_write(buffer_short, BUFF_SIZE/2);
    dump_wav_close();

    /* Stop the graph */
    pi_sfu_graph_unload( graph );
    pi_sfu_close();
    pi_l2_free(buffer, BUFF_SIZE);
    return 0;
}

int main(void)
{
    struct pi_device i2sa;
    struct pi_device i2sb;

    #ifdef CONFIG_AUDIO_FROM_MIC_A_LEFT
        char al[3] = {"al\0"};
        pi_open(MIC_PDM_AL_DEVICE, &mic_dev);
        if (open_i2s_PDM(&i2sa, MICRO_PDM_SAI_AL, 3072000, 3, 0)) return -1;
        Mic_In_to_Mem_Out(i2sa, MICRO_PDM_SAI_AL, MICRO_PDM_CHANNEL_AL, al);
        pi_close(MIC_PDM_AL_DEVICE);
    #endif    
    
    #ifdef CONFIG_AUDIO_FROM_MIC_A_RIGHT
        pi_open(MIC_PDM_AR_DEVICE, &mic_dev);
        char ar[3] = {"ar\0"};
        if (open_i2s_PDM(&i2sa, MICRO_PDM_SAI_AR, 3072000, 3, 0)) return -1;
        Mic_In_to_Mem_Out(i2sa, MICRO_PDM_SAI_AR, MICRO_PDM_CHANNEL_AR, ar);
        pi_close(MIC_PDM_AR_DEVICE);
    #endif

    #ifdef CONFIG_AUDIO_FROM_MIC_B_LEFT
        pi_open(MIC_PDM_BL_DEVICE, &mic_dev);
        char bl[3] = {"bl\0"};
        if (open_i2s_PDM(&i2sb, MICRO_PDM_SAI_BL, 3072000, 3, 0)) return -1;
        Mic_In_to_Mem_Out(i2sb, MICRO_PDM_SAI_BL, MICRO_PDM_CHANNEL_BL, bl);
        pi_close(MIC_PDM_BL_DEVICE);
    #endif

    #ifdef CONFIG_AUDIO_FROM_MIC_B_RIGHT
        pi_open(MIC_PDM_BR_DEVICE, &mic_dev);
        char br[3] = {"br\0"};
        if (open_i2s_PDM(&i2sb, MICRO_PDM_SAI_BR, 3072000, 3, 0)) return -1;
        Mic_In_to_Mem_Out(i2sb, MICRO_PDM_SAI_BR, MICRO_PDM_CHANNEL_BR, br);
        pi_close(MIC_PDM_BR_DEVICE);
    #endif

    #ifdef CONFIG_AUDIO_FROM_VESPER_MIC
        char vm[3] = {"vm\0"};
        if (open_i2s_PDM(&i2sb, MICRO_PDM_SAI_VM, 3072000, 3, 0)) return -1;
        Mic_In_to_Mem_Out(i2sb, MICRO_PDM_SAI_VM, MICRO_PDM_CHANNEL_VM, vm);
    #endif

    return 0;
}