diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..378bc21 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.12) + +# initialize pico_sdk from GIT +# (note this can come from environment, CMake cache etc) +# set(PICO_SDK_FETCH_FROM_GIT on) + +# pico_sdk_import.cmake is a single file copied from this SDK +# note: this must happen before project() +include(pico_sdk_import.cmake) + +project(pico_microphone) + +# initialize the Pico SDK +pico_sdk_init() + +add_library(pico_microphone INTERFACE) + +target_sources(pico_microphone INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/src/pdm-microphone.c + ${CMAKE_CURRENT_LIST_DIR}/src/OpenPDM2PCM/OpenPDMFilter.c +) + +target_include_directories(pico_microphone INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/src/include +) + +pico_generate_pio_header(pico_microphone ${CMAKE_CURRENT_LIST_DIR}/src/pdm-microphone.pio) + +target_link_libraries(pico_microphone INTERFACE pico_stdlib hardware_dma hardware_pio) + +add_subdirectory("examples/hello_pdm") diff --git a/examples/hello_pdm/CMakeLists.txt b/examples/hello_pdm/CMakeLists.txt new file mode 100644 index 0000000..5306aea --- /dev/null +++ b/examples/hello_pdm/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.12) + +# rest of your project +add_executable(pico_microphone_hello_pdm + main.c +) + +target_link_libraries(pico_microphone_hello_pdm pico_microphone) + +# enable usb output, disable uart output +pico_enable_stdio_usb(pico_microphone_hello_pdm 1) +pico_enable_stdio_uart(pico_microphone_hello_pdm 0) + +# create map/bin/hex/uf2 file in addition to ELF. +pico_add_extra_outputs(pico_microphone_hello_pdm) diff --git a/examples/hello_pdm/main.c b/examples/hello_pdm/main.c new file mode 100644 index 0000000..b701de2 --- /dev/null +++ b/examples/hello_pdm/main.c @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +#include +#include + +#include "pico/stdlib.h" +#include "pico/microphone/pdm.h" +#include "tusb.h" + +struct pdm_microphone_config config = { + .pio = pio0, + .pio_sm = 0, + .sample_rate = 8000, + .gpio_clk = 2, + .gpio_data = 3, +}; + +int16_t sample_buffer[256]; +volatile int samples_read = 0; + +void on_pdm_data(int16_t* samples, uint num_samples) +{ + memcpy(sample_buffer, samples, num_samples * sizeof(samples[0])); + + samples_read = num_samples; +} + +int main( void ) +{ + // initialize stdio and wait for USB CDC connect + stdio_init_all(); + while (!tud_cdc_connected()) { + tight_loop_contents(); + } + + // printf("hello PDM microphone\n"); + + pdm_microphone_init(&config); + pdm_microphone_start(on_pdm_data); + + while (1) { + while (samples_read == 0) { + tight_loop_contents(); + } + + for (int i = 0; i < samples_read; i++) { + printf("%d\n", sample_buffer[i]); + } + + samples_read = 0; + } + + return 0; +} diff --git a/src/include/pico/microphone/pdm.h b/src/include/pico/microphone/pdm.h new file mode 100644 index 0000000..575b350 --- /dev/null +++ b/src/include/pico/microphone/pdm.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +#ifndef _PICO_MICROPHONE_PDM_H_ +#define _PICO_MICROPHONE_PDM_H_ + +#include "hardware/pio.h" + +typedef void (*pdm_data_handler_t)(int16_t*, uint); + +struct pdm_microphone_config { + PIO pio; + uint pio_sm; + uint sample_rate; + uint gpio_clk; + uint gpio_data; + // TODO: pass in buffer??? +}; + +int pdm_microphone_init(const struct pdm_microphone_config* config); +int pdm_microphone_start(pdm_data_handler_t handler); + +#endif diff --git a/src/pdm-microphone.c b/src/pdm-microphone.c new file mode 100644 index 0000000..bf3be75 --- /dev/null +++ b/src/pdm-microphone.c @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +#include "hardware/clocks.h" +#include "hardware/dma.h" +#include "hardware/irq.h" + +#include "OpenPDM2PCM/OpenPDMFilter.h" + +#include "pdm-microphone.pio.h" + +#include "pico/microphone/pdm.h" + +#define PDM_DECIMATION 64 +#define PCM_BUFFER_SIZE 256 + +static int dma_channel = -1; +static uint8_t pdm_buffer[2][PCM_BUFFER_SIZE * (PDM_DECIMATION / 8)]; +static volatile int pdm_buffer_index = 0; +static int16_t pcm_buffer[PCM_BUFFER_SIZE]; + +static TPDMFilter_InitStruct pdm_filter; +static pdm_data_handler_t pdm_data_handler = NULL; + +static void dma_handler() +{ + // clear IRQ + dma_hw->ints0 = 1u << dma_channel; + + // get the current buffer index + int read_index = pdm_buffer_index; + + // get the next capture index to send the dma to start + pdm_buffer_index = (pdm_buffer_index + 1) % 2; + + // give the channel a new buffer to write to and re-trigger it + dma_channel_transfer_to_buffer_now(dma_channel, pdm_buffer[pdm_buffer_index], sizeof(pdm_buffer[0])); + + uint8_t* in = pdm_buffer[read_index]; + int16_t* out = pcm_buffer; + + int filter_stride = (pdm_filter.Fs / 1000); + + for (int i = 0; i < (PCM_BUFFER_SIZE / filter_stride); i++) { +#if PDM_DECIMATION == 64 + Open_PDM_Filter_64(in, out, 1, &pdm_filter); +#elif PDM_DECIMATION == 128 + Open_PDM_Filter_128(in, out, 1, &pdm_filter); +#else + #error "Unsupported PDM_DECIMATION value!" +#endif + + in += filter_stride * (PDM_DECIMATION / 8); + out += filter_stride; + } + + pdm_data_handler(pcm_buffer, PCM_BUFFER_SIZE); +} + +int pdm_microphone_init(const struct pdm_microphone_config* config) +{ + uint pio_sm_offset = pio_add_program(config->pio, &pdm_microphone_data_program); + + float clk_div = clock_get_hz(clk_sys) / (config->sample_rate * PDM_DECIMATION * 2); + + pdm_microphone_data_init( + config->pio, + config->pio_sm, + pio_sm_offset, + clk_div, + config->gpio_clk, + config->gpio_data + ); + + dma_channel = dma_claim_unused_channel(true); + + // TODO: handle claim failure + + dma_channel_config dma_channel_cfg = dma_channel_get_default_config(dma_channel); + + channel_config_set_transfer_data_size(&dma_channel_cfg, DMA_SIZE_8); + channel_config_set_read_increment(&dma_channel_cfg, false); + channel_config_set_write_increment(&dma_channel_cfg, true); + channel_config_set_dreq(&dma_channel_cfg, pio_get_dreq(config->pio, config->pio_sm, false)); + + irq_set_enabled(DMA_IRQ_0, true); + irq_set_exclusive_handler(DMA_IRQ_0, dma_handler); + + dma_channel_set_irq0_enabled(dma_channel, true); + + dma_channel_configure(dma_channel, &dma_channel_cfg, NULL, &config->pio->rxf[config->pio_sm], sizeof(pdm_buffer[pdm_buffer_index]), false); + + pdm_filter.Fs = config->sample_rate; + pdm_filter.LP_HZ = config->sample_rate / 2; + pdm_filter.HP_HZ = 10; + pdm_filter.In_MicChannels = 1; + pdm_filter.Out_MicChannels = 1; + pdm_filter.Decimation = PDM_DECIMATION; + pdm_filter.MaxVolume = 0; + + Open_PDM_Filter_Init(&pdm_filter); + + return -1; +} + +int pdm_microphone_start(pdm_data_handler_t handler) +{ + pdm_data_handler = handler; + + // give the channel a new buffer to write to and re-trigger it + dma_channel_transfer_to_buffer_now(dma_channel, pdm_buffer[pdm_buffer_index], sizeof(pdm_buffer[0])); + + return -1; +} diff --git a/src/pdm-microphone.pio b/src/pdm-microphone.pio new file mode 100644 index 0000000..79a14fd --- /dev/null +++ b/src/pdm-microphone.pio @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +.program pdm_microphone_data +.side_set 1 +.wrap_target + nop side 1 + in pins, 1 side 0 +.wrap + +% c-sdk { + +static inline void pdm_microphone_data_init(PIO pio, uint sm, uint offset, float clk_div, uint clk_pin, uint data_pin) { + pio_sm_set_consecutive_pindirs(pio, sm, data_pin, 1, false); + pio_sm_set_consecutive_pindirs(pio, sm, clk_pin, 1, true); + + pio_sm_config c = pdm_microphone_data_program_get_default_config(offset); + + sm_config_set_sideset_pins(&c, clk_pin); + sm_config_set_in_pins(&c, data_pin); + + pio_gpio_init(pio, clk_pin); + pio_gpio_init(pio, data_pin); + + sm_config_set_in_shift(&c, false, true, 8); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); + + sm_config_set_clkdiv(&c, clk_div); + + pio_sm_init(pio, sm, offset, &c); + pio_sm_set_enabled(pio, sm, true); +} +%}