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:
P4 and P9 needs to be closed to output Left channel on the jack connector.
P10 and P11 needs to be closed to output Right channel on the jack connector.
P3 needs to be set on VROOT.
P5 needs to be set on LDO.
P6 needs to be set on LDO.
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
Configure CMake
cmake -B build
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
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;
}
#
# 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.
#
cmake_minimum_required(VERSION 3.19)
set(TARGET_NAME "Pdm_in")
LIST(APPEND TARGET_SRCS Pdm_in.c wav_out.c)
include($ENV{GAP_SDK_HOME}/utils/cmake/setup.cmake)
project(${TARGET_NAME} C ASM)
add_executable(${TARGET_NAME} ${TARGET_SRCS})
if(CONFIG_USE_HP_FILTER)
set(GRAPH_FILE ${CMAKE_CURRENT_SOURCE_DIR}/GraphHpFilter.src)
else()
set(GRAPH_FILE ${CMAKE_CURRENT_SOURCE_DIR}/Graph.src)
endif()
sfu_add_graphs(${TARGET_NAME} SOURCE ${GRAPH_FILE})
if(CONFIG_AUDIO_FROM_MIC_A_LEFT)
message(STATUS "[${BoldCyan}APP${ColourReset}] Microphone A Left selected")
target_compile_options(${TARGET_NAME} PUBLIC "-DCONFIG_AUDIO_FROM_MIC_A_LEFT=1")
endif()
if(CONFIG_AUDIO_FROM_MIC_A_RIGHT)
message(STATUS "[${BoldCyan}APP${ColourReset}] Microphone A Right selected")
target_compile_options(${TARGET_NAME} PUBLIC "-DCONFIG_AUDIO_FROM_MIC_A_RIGHT=1")
endif()
if(CONFIG_AUDIO_FROM_MIC_B_LEFT)
message(STATUS "[${BoldCyan}APP${ColourReset}] Microphone B Left selected")
target_compile_options(${TARGET_NAME} PUBLIC "-DCONFIG_AUDIO_FROM_MIC_B_LEFT=1")
endif()
if(CONFIG_AUDIO_FROM_MIC_B_RIGHT)
message(STATUS "[${BoldCyan}APP${ColourReset}] Microphone B Right selected")
target_compile_options(${TARGET_NAME} PUBLIC "-DCONFIG_AUDIO_FROM_MIC_B_RIGHT=1")
endif()
if(CONFIG_AUDIO_FROM_VESPER_MIC)
message(STATUS "[${BoldCyan}APP${ColourReset}] Microphone VESPER selected")
target_compile_options(${TARGET_NAME} PUBLIC "-DCONFIG_AUDIO_FROM_VESPER_MIC=1")
endif()
setupos(${TARGET_NAME})
SFU_CreateGraph("Graph");
Defines:
CIC_N = 8; CIC_M = 2; CIC_R = 64; CIC_Shift = 25;
/* CIC Filter parameters for Input 3.072 MHz, Output: 48 KHz, DynOut = N*Log2(R*M) = 8*Log2(128) = 56 => Shift = 25 for Q31 */
Nodes:
In1 = Node(PDM_IN, CIC_N, CIC_M, CIC_R, CIC_Shift); // 3.072 MHZ Pdm In -> 48 KHz PCM out
Out1 = Node(MEM_OUT);
Configure:
In1.Frequency = 3072000;
Connects:
Connect(In1, Out1);
SFU_CloseGraph();
/*
* Copyright (C) 2018 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 <stdio.h>
#include <string.h>
#include <pmsis.h>
#include <bsp/fs.h>
#include "wav_out.h"
#include "bsp/fs/hostfs.h"
static PI_L2 uint8_t header_buffer[WAV_HEADER_SIZE];
static struct pi_device fs;
static void *file;
void dump_wav_open(char *filename, int width, int sampling_rate, int nb_channels, int size)
{
unsigned int idx = 0;
unsigned int sz = WAV_HEADER_SIZE + size;
// 4 bytes "RIFF"
header_buffer[idx++] = 'R';
header_buffer[idx++] = 'I';
header_buffer[idx++] = 'F';
header_buffer[idx++] = 'F';
// 4 bytes File size - 8bytes 32kS 0x10024 - 65408S 0x1ff24
//header_buffer[idx++] = 0x24;
//header_buffer[idx++] = 0xff;
//header_buffer[idx++] = 0x01;
//header_buffer[idx++] = 0x00;
header_buffer[idx++] = (unsigned char) (sz & 0x000000ff);
header_buffer[idx++] = (unsigned char)((sz & 0x0000ff00) >> 8);
header_buffer[idx++] = (unsigned char)((sz & 0x00ff0000) >> 16);
header_buffer[idx++] = (unsigned char)((sz & 0xff000000) >> 24);
// 4 bytes file type: "WAVE"
header_buffer[idx++] = 'W';
header_buffer[idx++] = 'A';
header_buffer[idx++] = 'V';
header_buffer[idx++] = 'E';
// 4 bytes format chunk: "fmt " last char is trailing NULL
header_buffer[idx++] = 'f';
header_buffer[idx++] = 'm';
header_buffer[idx++] = 't';
header_buffer[idx++] = ' ';
// 4 bytes length of format data below, until data part
header_buffer[idx++] = 0x10;
header_buffer[idx++] = 0x00;
header_buffer[idx++] = 0x00;
header_buffer[idx++] = 0x00;
// 2 bytes type of format: 1 (PCM)
header_buffer[idx++] = 0x01;
header_buffer[idx++] = 0x00;
// 2 bytes nb of channels: 1 or 2
//header_buffer[idx++] = 0x02;
//header_buffer[idx++] = 0x01;
header_buffer[idx++] = nb_channels;
header_buffer[idx++] = 0x00;
// 4 bytes sample rate in Hz:
header_buffer[idx++] = (sampling_rate >> 0) & 0xff;
header_buffer[idx++] = (sampling_rate >> 8) & 0xff;
header_buffer[idx++] = (sampling_rate >> 16) & 0xff;
header_buffer[idx++] = (sampling_rate >> 24) & 0xff;
// 4 bytes (Sample Rate * BitsPerSample * Channels) / 8:
// (8000*16*1)/8=0x3e80 * 2
// (16000*16*1)/8=32000 or 0x6F00
// (22050*16*1)/8=0xac44
// (22050*16*2)/8=0x15888
int rate = (sampling_rate * width * nb_channels) / 8;
header_buffer[idx++] = (rate >> 0) & 0xff;
header_buffer[idx++] = (rate >> 8) & 0xff;
header_buffer[idx++] = (rate >> 16) & 0xff;
header_buffer[idx++] = (rate >> 24) & 0xff;
// 2 bytes (BitsPerSample * Channels) / 8:
// 16*1/8=2 - 16b mono
// 16*2/8=4 - 16b stereo
rate = (width * nb_channels) / 8;
header_buffer[idx++] = (rate >> 0) & 0xff;
header_buffer[idx++] = (rate >> 8) & 0xff;
// 2 bytes bit per sample:
header_buffer[idx++] = width;
header_buffer[idx++] = 0x00;
// 4 bytes "data" chunk
header_buffer[idx++] = 'd';
header_buffer[idx++] = 'a';
header_buffer[idx++] = 't';
header_buffer[idx++] = 'a';
// 4 bytes size of data section in bytes:
header_buffer[idx++] = (unsigned char) (size & 0x000000ff);
header_buffer[idx++] = (unsigned char)((size & 0x0000ff00) >> 8);
header_buffer[idx++] = (unsigned char)((size & 0x00ff0000) >> 16);
header_buffer[idx++] = (unsigned char)((size & 0xff000000) >> 24);
struct pi_hostfs_conf conf;
pi_hostfs_conf_init(&conf);
pi_open_from_conf(&fs, &conf);
if (pi_fs_mount(&fs))
return;
file = pi_fs_open(&fs, filename, PI_FS_FLAGS_WRITE);
if (file == 0)
{
printf("Failed to open file, %s\n", filename);
return;
}
pi_fs_write(file, header_buffer, WAV_HEADER_SIZE);
}
void dump_wav_write(void *data, int size)
{
pi_fs_write(file, data, size);
}
void dump_wav_close()
{
pi_fs_close(file);
pi_fs_unmount(&fs);
}
/*
* Copyright (C) 2018 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.
*/
#ifndef __I2S_AUDIO_REC__
#define __I2S_AUDIO_REC_
#define WAV_HEADER_SIZE 44 //bytes
void dump_wav_open(char *filename, int width, int sampling_rate, int nb_channels, int size);
void dump_wav_write(void *data, int size);
void dump_wav_close();
#endif