From 706f9146c7426089dc1863d4ea7d75f66c757da7 Mon Sep 17 00:00:00 2001 From: dattran-itrvn Date: Wed, 30 Aug 2023 08:53:51 +0700 Subject: [PATCH 1/8] update --- .vscode/c_cpp_properties.json | 17 + .vscode/settings.json | 8 + CMakeLists.txt | 10 +- .../hello_pdm_microphone/CMakeLists.txt | 16 + .../hello_pdm_microphone/main.c | 83 ++ src/i2s_microphone.c | 732 ++++++++++++++++++ src/include/pico/i2s_microphone.h | 102 +++ 7 files changed, 963 insertions(+), 5 deletions(-) create mode 100644 .vscode/c_cpp_properties.json create mode 100644 .vscode/settings.json create mode 100644 examples/hello_i2s_microphone/hello_pdm_microphone/CMakeLists.txt create mode 100644 examples/hello_i2s_microphone/hello_pdm_microphone/main.c create mode 100644 src/i2s_microphone.c create mode 100644 src/include/pico/i2s_microphone.h diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..8613f27 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,17 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [], + "compilerPath": "/usr/bin/gcc", + "cStandard": "c11", + "cppStandard": "gnu++14", + "intelliSenseMode": "linux-gcc-x64", + "configurationProvider": "ms-vscode.cmake-tools" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4a633fb --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "files.associations": { + "pio.h": "c", + "dreq.h": "c", + "pio_instructions.h": "c", + "type_traits": "cpp" + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 230b75d..5796e06 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ 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) +set(ENV{PICO_SDK_PATH} "/home/dattran/pico/pico-sdk") # pico_sdk_import.cmake is a single file copied from this SDK # note: this must happen before project() @@ -39,8 +39,8 @@ target_include_directories(pico_analog_microphone INTERFACE ${CMAKE_CURRENT_LIST_DIR}/src/include ) -target_link_libraries(pico_analog_microphone INTERFACE pico_stdlib hardware_adc hardware_dma) +target_link_libraries(pico_i2s_microphone INTERFACE pico_stdlib hardware_adc hardware_dma) -add_subdirectory("examples/hello_analog_microphone") -add_subdirectory("examples/hello_pdm_microphone") -add_subdirectory("examples/usb_microphone") +add_subdirectory("examples/hello_i2s_microphone") +# add_subdirectory("examples/hello_pdm_microphone") +# add_subdirectory("examples/usb_microphone") diff --git a/examples/hello_i2s_microphone/hello_pdm_microphone/CMakeLists.txt b/examples/hello_i2s_microphone/hello_pdm_microphone/CMakeLists.txt new file mode 100644 index 0000000..dbf3e08 --- /dev/null +++ b/examples/hello_i2s_microphone/hello_pdm_microphone/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.12) + +# rest of your project +add_executable(hello_i2s_microphone + main.c +) + +target_link_libraries(hello_i2s_microphone pico_i2s_microphone) + +# enable usb output, disable uart output +pico_enable_stdio_usb(hello_i2s_microphone 1) +pico_enable_stdio_uart(hello_i2s_microphone 0) + +# create map/bin/hex/uf2 file in addition to ELF. +pico_add_extra_outputs(hello_i2s_microphone) + diff --git a/examples/hello_i2s_microphone/hello_pdm_microphone/main.c b/examples/hello_i2s_microphone/hello_pdm_microphone/main.c new file mode 100644 index 0000000..2d42cc2 --- /dev/null +++ b/examples/hello_i2s_microphone/hello_pdm_microphone/main.c @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * This examples captures data from a PDM microphone using a sample + * rate of 8 kHz and prints the sample values over the USB serial + * connection. + */ + +#include +#include + +#include "pico/stdlib.h" +#include "pico/i2s_microphone.h" +#include "tusb.h" + + +#define SCK 2 +#define WS 3 // needs to be SCK +1 +#define SD 6 +#define BPS 16 +#define SAMPLE_RATE 16000 +#define FFT_SIZE 256 +#define SPECTRUM_SHIFT 4 +#define INPUT_BUFFER_SIZE ((FFT_SIZE / 2) * SPECTRUM_SHIFT) +#define INPUT_SHIFT 0 +#define BUTTON 15 +#define LEN_WAVE_HEADER 44 + +// variables +int16_t sample_buffer[SIZEOF_HALF_DMA_BUFFER_IN_BYTES]; +volatile int samples_read = 0; +machine_i2s_obj_t* i2s0; +typedef void (*I2SHandler) (machine_i2s_obj_t* i2s0); + +void on_i2s_mic_samples_ready(machine_i2s_obj_t* i2s0){ + samples_read = machine_i2s_stream_read(i2s0, (void*)&sample_buffer, INPUT_BUFFER_SIZE * 2); + if(samples_read != 0){ + printf("Data OK\r\n"); + } +} + +int main( void ) +{ + // initialize stdio and wait for USB CDC connect + stdio_init_all(); + while (!tud_cdc_connected()) { + tight_loop_contents(); + } + + printf("hello i2s microphone\n"); + + // initialize the PDM microphone + i2s0 = machine_i2s_make_new(0, SCK, WS, SD, RX, BPS, MONO, SIZEOF_DMA_BUFFER_IN_BYTES, SAMPLE_RATE); + if(i2s0 == NULL){ + printf("Failed to initialize MIC I2S!\n"); + while (1) { tight_loop_contents(); } + } + else{ + printf("| MIC I2S initialize OK |\n"); + } + + // set callback that is called when all the samples in the library + // internal sample buffer are ready for reading + i2s_microphone_set_samples_ready_handler(on_i2s_mic_samples_ready); + + while (1) { + // wait for new samples + while (samples_read == 0) { tight_loop_contents(); } + + // store and clear the samples read from the callback + int sample_count = samples_read; + samples_read = 0; + + // loop through any new collected samples + for (int i = 0; i < sample_count; i++) { + printf("%d\n", sample_buffer[i]); + } + } + + return 0; +} diff --git a/src/i2s_microphone.c b/src/i2s_microphone.c new file mode 100644 index 0000000..9603418 --- /dev/null +++ b/src/i2s_microphone.c @@ -0,0 +1,732 @@ +/* + machine_i2s.c - + I2S digital audio input C library for the Raspberry Pi Pico RP2040 + + Copyright (C) 2022 Sfera Labs S.r.l. - All rights reserved. + + For information, see: + http://www.sferalabs.cc/ + + This code is adapted from the I2S implementation of the RP2 MicroPython port + by Mike Teachman, available at: + https://github.com/micropython/micropython/blob/master/ports/rp2/machine_i2s.c + Retrieved on January 25 2022. +*/ + +/* Original header */ + +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Mike Teachman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include "i2s_microphone.h" +#include "hardware/pio.h" +#include "hardware/clocks.h" +#include "hardware/gpio.h" +#include "hardware/dma.h" +#include "hardware/irq.h" + +STATIC machine_i2s_obj_t* machine_i2s_obj[MAX_I2S_RP2] = {NULL, NULL}; + +// The frame map is used with the readinto() method to transform the audio sample data coming +// from DMA memory (32-bit stereo) to the format specified +// in the I2S constructor. e.g. 16-bit mono +STATIC const int8_t i2s_frame_map[NUM_I2S_USER_FORMATS][I2S_RX_FRAME_SIZE_IN_BYTES] = { + {-1, -1, 0, 1, -1, -1, -1, -1 }, // Mono, 16-bits + { 0, 1, 2, 3, -1, -1, -1, -1 }, // Mono, 32-bits + {-1, -1, 0, 1, -1, -1, 2, 3 }, // Stereo, 16-bits + { 0, 1, 2, 3, 4, 5, 6, 7 }, // Stereo, 32-bits +}; + +STATIC const PIO pio_instances[NUM_PIOS] = {pio0, pio1}; + +// PIO program for 16-bit write +// set(x, 14) .side(0b01) +// label('left_channel') +// out(pins, 1) .side(0b00) +// jmp(x_dec, "left_channel") .side(0b01) +// out(pins, 1) .side(0b10) +// set(x, 14) .side(0b11) +// label('right_channel') +// out(pins, 1) .side(0b10) +// jmp(x_dec, "right_channel") .side(0b11) +// out(pins, 1) .side(0b00) +STATIC const uint16_t pio_instructions_write_16[] = {59438, 24577, 2113, 28673, 63534, 28673, 6213, 24577}; +STATIC const pio_program_t pio_write_16 = { + pio_instructions_write_16, + sizeof(pio_instructions_write_16) / sizeof(uint16_t), + -1 +}; + +// PIO program for 32-bit write +// set(x, 30) .side(0b01) +// label('left_channel') +// out(pins, 1) .side(0b00) +// jmp(x_dec, "left_channel") .side(0b01) +// out(pins, 1) .side(0b10) +// set(x, 30) .side(0b11) +// label('right_channel') +// out(pins, 1) .side(0b10) +// jmp(x_dec, "right_channel") .side(0b11) +// out(pins, 1) .side(0b00) +STATIC const uint16_t pio_instructions_write_32[] = {59454, 24577, 2113, 28673, 63550, 28673, 6213, 24577}; +STATIC const pio_program_t pio_write_32 = { + pio_instructions_write_32, + sizeof(pio_instructions_write_32) / sizeof(uint16_t), + -1 +}; + +// PIO program for 32-bit read +// set(x, 30) .side(0b00) +// label('left_channel') +// in_(pins, 1) .side(0b01) +// jmp(x_dec, "left_channel") .side(0b00) +// in_(pins, 1) .side(0b11) +// set(x, 30) .side(0b10) +// label('right_channel') +// in_(pins, 1) .side(0b11) +// jmp(x_dec, "right_channel") .side(0b10) +// in_(pins, 1) .side(0b01) +STATIC const uint16_t pio_instructions_read_32[] = {57406, 18433, 65, 22529, 61502, 22529, 4165, 18433}; +STATIC const pio_program_t pio_read_32 = { + pio_instructions_read_32, + sizeof(pio_instructions_read_32) / sizeof(uint16_t), + -1 +}; + +STATIC uint8_t dma_get_bits(i2s_mode_t mode, int8_t bits); +STATIC void dma_irq0_handler(void); +STATIC void dma_irq1_handler(void); +STATIC void machine_i2s_deinit(machine_i2s_obj_t *self); + +// Ring Buffer +// Thread safe when used with these constraints: +// - Single Producer, Single Consumer +// - Sequential atomic operations +// One byte of capacity is used to detect buffer empty/full + +STATIC void ringbuf_init(ring_buf_t *rbuf, uint8_t *buffer, size_t size) { + rbuf->buffer = buffer; + rbuf->size = size; + rbuf->head = 0; + rbuf->tail = 0; +} + +STATIC bool ringbuf_push(ring_buf_t *rbuf, uint8_t data) { + size_t next_tail = (rbuf->tail + 1) % rbuf->size; + + if (next_tail != rbuf->head) { + rbuf->buffer[rbuf->tail] = data; + rbuf->tail = next_tail; + return true; + } + + // full + return false; +} + +STATIC bool ringbuf_pop(ring_buf_t *rbuf, uint8_t *data) { + stdio_flush(); + if (rbuf->head == rbuf->tail) { + // empty + return false; + } + + *data = rbuf->buffer[rbuf->head]; + rbuf->head = (rbuf->head + 1) % rbuf->size; + return true; +} + +STATIC bool ringbuf_is_empty(ring_buf_t *rbuf) { + return rbuf->head == rbuf->tail; +} + +STATIC bool ringbuf_is_full(ring_buf_t *rbuf) { + return ((rbuf->tail + 1) % rbuf->size) == rbuf->head; +} + +STATIC size_t ringbuf_available_data(ring_buf_t *rbuf) { + return (rbuf->tail - rbuf->head + rbuf->size) % rbuf->size; +} + +STATIC size_t ringbuf_available_space(ring_buf_t *rbuf) { + return rbuf->size - ringbuf_available_data(rbuf) - 1; +} + +STATIC int8_t get_frame_mapping_index(int8_t bits, format_t format) { + if (format == MONO) { + if (bits == 16) { + return 0; + } else { // 32 bits + return 1; + } + } else { // STEREO + if (bits == 16) { + return 2; + } else { // 32 bits + return 3; + } + } +} + +STATIC uint32_t fill_appbuf_from_ringbuf(machine_i2s_obj_t *self, mp_buffer_info_t *appbuf) { + + // copy audio samples from the ring buffer to the app buffer + // loop, copying samples until the app buffer is filled + // For uasyncio mode, the loop will make an early exit if the ring buffer becomes empty + // Example: + // a MicroPython I2S object is configured for 16-bit mono (2 bytes per audio sample). + // For every frame coming from the ring buffer (8 bytes), 2 bytes are "cherry picked" and + // copied to the supplied app buffer. + // Thus, for every 1 byte copied to the app buffer, 4 bytes are read from the ring buffer. + // If a 8kB app buffer is supplied, 32kB of audio samples is read from the ring buffer. + + uint32_t num_bytes_copied_to_appbuf = 0; + uint8_t *app_p = (uint8_t *)appbuf->buf; + uint8_t appbuf_sample_size_in_bytes = (self->bits == 16? 2 : 4) * (self->format == STEREO ? 2: 1); + uint32_t num_bytes_needed_from_ringbuf = appbuf->len * (I2S_RX_FRAME_SIZE_IN_BYTES / appbuf_sample_size_in_bytes); + uint8_t discard_byte; + while (num_bytes_needed_from_ringbuf) { + + uint8_t f_index = get_frame_mapping_index(self->bits, self->format); + + for (uint8_t i = 0; i < I2S_RX_FRAME_SIZE_IN_BYTES; i++) { + int8_t r_to_a_mapping = i2s_frame_map[f_index][i]; + if (r_to_a_mapping != -1) { + if (self->io_mode == BLOCKING) { + // poll the ringbuf until a sample becomes available, copy into appbuf using the mapping transform + while (ringbuf_pop(&self->ring_buffer, app_p + r_to_a_mapping) == false) { + ; + } + num_bytes_copied_to_appbuf++; + } else if (self->io_mode == UASYNCIO) { + if (ringbuf_pop(&self->ring_buffer, app_p + r_to_a_mapping) == false) { + // ring buffer is empty, exit + goto exit; + } else { + num_bytes_copied_to_appbuf++; + } + } else { + return 0; // should never get here (non-blocking mode does not use this function) + } + } else { // r_a_mapping == -1 + // discard unused byte from ring buffer + if (self->io_mode == BLOCKING) { + // poll the ringbuf until a sample becomes available + while (ringbuf_pop(&self->ring_buffer, &discard_byte) == false) { + ; + } + } else if (self->io_mode == UASYNCIO) { + if (ringbuf_pop(&self->ring_buffer, &discard_byte) == false) { + // ring buffer is empty, exit + goto exit; + } + } else { + return 0; // should never get here (non-blocking mode does not use this function) + } + } + num_bytes_needed_from_ringbuf--; + } + app_p += appbuf_sample_size_in_bytes; + } +exit: + return num_bytes_copied_to_appbuf; +} + +STATIC uint32_t copy_appbuf_to_ringbuf(machine_i2s_obj_t *self, mp_buffer_info_t *appbuf) { + + // copy audio samples from the app buffer to the ring buffer + // loop, reading samples until the app buffer is emptied + // for uasyncio mode, the loop will make an early exit if the ring buffer becomes full + + uint32_t a_index = 0; + + while (a_index < appbuf->len) { + if (self->io_mode == BLOCKING) { + // copy a byte to the ringbuf when space becomes available + while (ringbuf_push(&self->ring_buffer, ((uint8_t *)appbuf->buf)[a_index]) == false) { + ; + } + a_index++; + } else if (self->io_mode == UASYNCIO) { + if (ringbuf_push(&self->ring_buffer, ((uint8_t *)appbuf->buf)[a_index]) == false) { + // ring buffer is full, exit + break; + } else { + a_index++; + } + } else { + return 0; // should never get here (non-blocking mode does not use this function) + } + } + + return a_index; +} + +// function is used in IRQ context +STATIC void empty_dma(machine_i2s_obj_t *self, uint8_t *dma_buffer_p) { + // when space exists, copy samples into ring buffer + if (ringbuf_available_space(&self->ring_buffer) >= SIZEOF_HALF_DMA_BUFFER_IN_BYTES) { + for (uint32_t i = 0; i < SIZEOF_HALF_DMA_BUFFER_IN_BYTES; i++) { + ringbuf_push(&self->ring_buffer, dma_buffer_p[i]); + } + } +} + +// function is used in IRQ context +STATIC void feed_dma(machine_i2s_obj_t *self, uint8_t *dma_buffer_p) { + // when data exists, copy samples from ring buffer + if (ringbuf_available_data(&self->ring_buffer) >= SIZEOF_HALF_DMA_BUFFER_IN_BYTES) { + + // copy a block of samples from the ring buffer to the dma buffer. + // STM32 HAL API has a stereo I2S implementation, but not mono + // mono format is implemented by duplicating each sample into both L and R channels. + if ((self->format == MONO) && (self->bits == 16)) { + for (uint32_t i = 0; i < SIZEOF_HALF_DMA_BUFFER_IN_BYTES / 4; i++) { + for (uint8_t b = 0; b < sizeof(uint16_t); b++) { + ringbuf_pop(&self->ring_buffer, &dma_buffer_p[i * 4 + b]); + dma_buffer_p[i * 4 + b + 2] = dma_buffer_p[i * 4 + b]; // duplicated mono sample + } + } + } else if ((self->format == MONO) && (self->bits == 32)) { + for (uint32_t i = 0; i < SIZEOF_HALF_DMA_BUFFER_IN_BYTES / 8; i++) { + for (uint8_t b = 0; b < sizeof(uint32_t); b++) { + ringbuf_pop(&self->ring_buffer, &dma_buffer_p[i * 8 + b]); + dma_buffer_p[i * 8 + b + 4] = dma_buffer_p[i * 8 + b]; // duplicated mono sample + } + } + } else { // STEREO, both 16-bit and 32-bit + for (uint32_t i = 0; i < SIZEOF_HALF_DMA_BUFFER_IN_BYTES; i++) { + ringbuf_pop(&self->ring_buffer, &dma_buffer_p[i]); + } + } + } else { + // underflow. clear buffer to transmit "silence" on the I2S bus + memset(dma_buffer_p, 0, SIZEOF_HALF_DMA_BUFFER_IN_BYTES); + } +} + +STATIC void irq_configure(machine_i2s_obj_t *self) { + if (self->i2s_id == 0) { + irq_set_exclusive_handler(DMA_IRQ_0, dma_irq0_handler); + irq_set_enabled(DMA_IRQ_0, true); + } else { + irq_set_exclusive_handler(DMA_IRQ_1, dma_irq1_handler); + irq_set_enabled(DMA_IRQ_1, true); + } +} + +STATIC void irq_deinit(machine_i2s_obj_t *self) { + if (self->i2s_id == 0) { + irq_set_enabled(DMA_IRQ_0, false); + irq_remove_handler(DMA_IRQ_0, dma_irq0_handler); + } else { + irq_set_enabled(DMA_IRQ_1, false); + irq_remove_handler(DMA_IRQ_1, dma_irq1_handler); + } +} + +STATIC int pio_configure(machine_i2s_obj_t *self) { + if (self->mode == TX) { + if (self->bits == 16) { + self->pio_program = &pio_write_16; + } else { + self->pio_program = &pio_write_32; + } + } else { // RX + self->pio_program = &pio_read_32; + } + + // find a PIO with a free state machine and adequate program space + PIO candidate_pio; + bool is_free_sm; + bool can_add_program; + for (uint8_t p = 0; p < NUM_PIOS; p++) { + candidate_pio = pio_instances[p]; + is_free_sm = false; + can_add_program = false; + + for (uint8_t sm = 0; sm < NUM_PIO_STATE_MACHINES; sm++) { + if (!pio_sm_is_claimed(candidate_pio, sm)) { + is_free_sm = true; + break; + } + } + + if (pio_can_add_program(candidate_pio, self->pio_program)) { + can_add_program = true; + } + + if (is_free_sm && can_add_program) { + break; + } + } + + if (!is_free_sm) { + return -1; + } + + if (!can_add_program) { + return -2; + } + + self->pio = candidate_pio; + self->sm = pio_claim_unused_sm(self->pio, false); + self->prog_offset = pio_add_program(self->pio, self->pio_program); + pio_sm_init(self->pio, self->sm, self->prog_offset, NULL); + + pio_sm_config config = pio_get_default_sm_config(); + + float pio_freq = self->rate * + SAMPLES_PER_FRAME * + dma_get_bits(self->mode, self->bits) * + PIO_INSTRUCTIONS_PER_BIT; + float clkdiv = clock_get_hz(clk_sys) / pio_freq; + sm_config_set_clkdiv(&config, clkdiv); + + if (self->mode == TX) { + sm_config_set_out_pins(&config, self->sd, 1); + sm_config_set_out_shift(&config, false, true, dma_get_bits(self->mode, self->bits)); + sm_config_set_fifo_join(&config, PIO_FIFO_JOIN_TX); // double TX FIFO size + } else { // RX + sm_config_set_in_pins(&config, self->sd); + sm_config_set_in_shift(&config, false, true, dma_get_bits(self->mode, self->bits)); + sm_config_set_fifo_join(&config, PIO_FIFO_JOIN_RX); // double RX FIFO size + } + + sm_config_set_sideset(&config, 2, false, false); + sm_config_set_sideset_pins(&config, self->sck); + sm_config_set_wrap(&config, self->prog_offset, self->prog_offset + self->pio_program->length - 1); + pio_sm_set_config(self->pio, self->sm, &config); + + return 0; +} + +STATIC void pio_deinit(machine_i2s_obj_t *self) { + if (self->pio) { + pio_sm_set_enabled(self->pio, self->sm, false); + pio_sm_unclaim(self->pio, self->sm); + pio_remove_program(self->pio, self->pio_program, self->prog_offset); + } +} + +STATIC void gpio_init_i2s(PIO pio, uint8_t sm, mp_hal_pin_obj_t pin_num, uint8_t pin_val, gpio_dir_t pin_dir) { + uint32_t pinmask = 1 << pin_num; + pio_sm_set_pins_with_mask(pio, sm, pin_val << pin_num, pinmask); + pio_sm_set_pindirs_with_mask(pio, sm, pin_dir << pin_num, pinmask); + pio_gpio_init(pio, pin_num); +} + +STATIC void gpio_configure(machine_i2s_obj_t *self) { + gpio_init_i2s(self->pio, self->sm, self->sck, 0, GP_OUTPUT); + gpio_init_i2s(self->pio, self->sm, self->ws, 0, GP_OUTPUT); + if (self->mode == TX) { + gpio_init_i2s(self->pio, self->sm, self->sd, 0, GP_OUTPUT); + } else { // RX + gpio_init_i2s(self->pio, self->sm, self->sd, 0, GP_INPUT); + } +} + +STATIC uint8_t dma_get_bits(i2s_mode_t mode, int8_t bits) { + if (mode == TX) { + return bits; + } else { // RX + // always read 32 bit words for I2S e.g. I2S MEMS microphones + return 32; + } +} + +// determine which DMA channel is associated to this IRQ +STATIC uint dma_map_irq_to_channel(uint irq_index) { + for (uint ch = 0; ch < NUM_DMA_CHANNELS; ch++) { + if ((dma_irqn_get_channel_status(irq_index, ch))) { + return ch; + } + } + // This should never happen + return -1; +} + +// note: first DMA channel is mapped to the top half of buffer, second is mapped to the bottom half +STATIC uint8_t *dma_get_buffer(machine_i2s_obj_t *i2s_obj, uint channel) { + for (uint8_t ch = 0; ch < I2S_NUM_DMA_CHANNELS; ch++) { + if (i2s_obj->dma_channel[ch] == channel) { + return i2s_obj->dma_buffer + (SIZEOF_HALF_DMA_BUFFER_IN_BYTES * ch); + } + } + // This should never happen + return NULL; +} + +STATIC int dma_configure(machine_i2s_obj_t *self) { + uint8_t num_free_dma_channels = 0; + for (uint8_t ch = 0; ch < NUM_DMA_CHANNELS; ch++) { + if (!dma_channel_is_claimed(ch)) { + num_free_dma_channels++; + } + } + if (num_free_dma_channels < I2S_NUM_DMA_CHANNELS) { + return -1; + } + + for (uint8_t ch = 0; ch < I2S_NUM_DMA_CHANNELS; ch++) { + self->dma_channel[ch] = dma_claim_unused_channel(false); + } + + // The DMA channels are chained together. The first DMA channel is used to access + // the top half of the DMA buffer. The second DMA channel accesses the bottom half of the DMA buffer. + // With chaining, when one DMA channel has completed a data transfer, the other + // DMA channel automatically starts a new data transfer. + enum dma_channel_transfer_size dma_size = (dma_get_bits(self->mode, self->bits) == 16) ? DMA_SIZE_16 : DMA_SIZE_32; + for (uint8_t ch = 0; ch < I2S_NUM_DMA_CHANNELS; ch++) { + dma_channel_config dma_config = dma_channel_get_default_config(self->dma_channel[ch]); + channel_config_set_transfer_data_size(&dma_config, dma_size); + channel_config_set_chain_to(&dma_config, self->dma_channel[(ch + 1) % I2S_NUM_DMA_CHANNELS]); + + uint8_t *dma_buffer = self->dma_buffer + (SIZEOF_HALF_DMA_BUFFER_IN_BYTES * ch); + if (self->mode == TX) { + channel_config_set_dreq(&dma_config, pio_get_dreq(self->pio, self->sm, true)); + channel_config_set_read_increment(&dma_config, true); + channel_config_set_write_increment(&dma_config, false); + dma_channel_configure(self->dma_channel[ch], + &dma_config, + (void *)&self->pio->txf[self->sm], // dest = PIO TX FIFO + dma_buffer, // src = DMA buffer + SIZEOF_HALF_DMA_BUFFER_IN_BYTES / (dma_get_bits(self->mode, self->bits) / 8), + false); + } else { // RX + channel_config_set_dreq(&dma_config, pio_get_dreq(self->pio, self->sm, false)); + channel_config_set_read_increment(&dma_config, false); + channel_config_set_write_increment(&dma_config, true); + dma_channel_configure(self->dma_channel[ch], + &dma_config, + dma_buffer, // dest = DMA buffer + (void *)&self->pio->rxf[self->sm], // src = PIO RX FIFO + SIZEOF_HALF_DMA_BUFFER_IN_BYTES / (dma_get_bits(self->mode, self->bits) / 8), + true); + } + } + + for (uint8_t ch = 0; ch < I2S_NUM_DMA_CHANNELS; ch++) { + dma_irqn_acknowledge_channel(self->i2s_id, self->dma_channel[ch]); // clear pending. e.g. from SPI + dma_irqn_set_channel_enabled(self->i2s_id, self->dma_channel[ch], true); + } + + return 0; +} + +STATIC void dma_deinit(machine_i2s_obj_t *self) { + for (uint8_t ch = 0; ch < I2S_NUM_DMA_CHANNELS; ch++) { + int channel = self->dma_channel[ch]; + + // unchain the channel to prevent triggering a transfer in the chained-to channel + dma_channel_config dma_config = dma_get_channel_config(channel); + channel_config_set_chain_to(&dma_config, channel); + dma_channel_set_config(channel, &dma_config, false); + + dma_irqn_set_channel_enabled(self->i2s_id, channel, false); + dma_channel_abort(channel); // in case a transfer is in flight + dma_channel_unclaim(channel); + } +} + +STATIC void dma_irq_handler(uint8_t irq_index) { + int dma_channel = dma_map_irq_to_channel(irq_index); + if (dma_channel == -1) { + // This should never happen + return; + } + + machine_i2s_obj_t *self = machine_i2s_obj[irq_index]; + if (self == NULL) { + // This should never happen + return; + } + + uint8_t *dma_buffer = dma_get_buffer(self, dma_channel); + + if (dma_buffer == NULL) { + // This should never happen + return; + } + + if (self->mode == RX) { + empty_dma(self, dma_buffer); + dma_irqn_acknowledge_channel(irq_index, dma_channel); + dma_channel_set_write_addr(dma_channel, dma_buffer, false); + } + self->flagHandler = 1; + +} + +STATIC void dma_irq0_handler(void) { + dma_irq_handler(0); +} + +STATIC void dma_irq1_handler(void) { + dma_irq_handler(1); +} + +STATIC void i2s_microphone_set_samples_ready_handler(i2s_samples_ready_handler_t handler) { + machine_i2s_obj_t *self; + self->handlerEvent = handler; +} + +STATIC int machine_i2s_init_helper(machine_i2s_obj_t *self, + mp_hal_pin_obj_t sck, mp_hal_pin_obj_t ws, mp_hal_pin_obj_t sd, + i2s_mode_t i2s_mode, int8_t i2s_bits, format_t i2s_format, + int32_t ring_buffer_len, int32_t i2s_rate) { + // + // ---- Check validity of arguments ---- + // + + // does WS pin follow SCK pin? + // note: SCK and WS are implemented as PIO sideset pins. Sideset pins must be sequential. + if (ws != (sck + 1)) { + return -1; + } + + // is Mode valid? + if ((i2s_mode != RX) && + (i2s_mode != TX)) { + return -2; + } + + // is Bits valid? + if ((i2s_bits != 16) && + (i2s_bits != 32)) { + return -3; + } + + // is Format valid? + if ((i2s_format != MONO) && + (i2s_format != STEREO)) { + return -4; + } + + // is Rate valid? + // Not checked + + // is Ibuf valid? + if (ring_buffer_len > 0) { + self->ring_buffer_storage = m_new(uint8_t, ring_buffer_len); + ; + ringbuf_init(&self->ring_buffer, self->ring_buffer_storage, ring_buffer_len); + } else { + return -5; + } + + self->sck = sck; + self->ws = ws; + self->sd = sd; + self->mode = i2s_mode; + self->bits = i2s_bits; + self->format = i2s_format; + self->rate = i2s_rate; + self->ibuf = ring_buffer_len; + self->io_mode = BLOCKING; + + irq_configure(self); + int err = pio_configure(self); + if (err != 0) { + return err; + } + gpio_configure(self); + err = dma_configure(self); + if (err != 0) { + return err; + } + + pio_sm_set_enabled(self->pio, self->sm, true); + dma_channel_start(self->dma_channel[0]); + + return 0; +} + +STATIC machine_i2s_obj_t* machine_i2s_make_new(uint8_t i2s_id, + mp_hal_pin_obj_t sck, mp_hal_pin_obj_t ws, mp_hal_pin_obj_t sd, + i2s_mode_t i2s_mode, int8_t i2s_bits, format_t i2s_format, + int32_t ring_buffer_len, int32_t i2s_rate) { + if (i2s_id >= MAX_I2S_RP2) { + return NULL; + } + + machine_i2s_obj_t *self; + if (machine_i2s_obj[i2s_id] == NULL) { + self = m_new_obj(machine_i2s_obj_t); + machine_i2s_obj[i2s_id] = self; + self->i2s_id = i2s_id; + } else { + self = machine_i2s_obj[i2s_id]; + machine_i2s_deinit(self); + } + + if (machine_i2s_init_helper(self, sck, ws, sd, i2s_mode, i2s_bits, + i2s_format, ring_buffer_len, i2s_rate) != 0) { + return NULL; + } + return self; +} + + + +STATIC void machine_i2s_deinit(machine_i2s_obj_t *self) { + // use self->pio as in indication that I2S object has already been de-initialized + if (self->pio != NULL) { + pio_deinit(self); + dma_deinit(self); + irq_deinit(self); + free(self->ring_buffer_storage); + self->pio = NULL; // flag object as de-initialized + } +} + +STATIC int machine_i2s_stream_read(machine_i2s_obj_t *self, void *buf_in, size_t size) { + if (self->mode != RX) { + return -1; + } + + uint8_t appbuf_sample_size_in_bytes = (self->bits / 8) * (self->format == STEREO ? 2: 1); + if (size % appbuf_sample_size_in_bytes != 0) { + return -2; + } + + if (size == 0) { + return 0; + } + + mp_buffer_info_t appbuf; + appbuf.buf = (void *)buf_in; + appbuf.len = size; + uint32_t num_bytes_read = fill_appbuf_from_ringbuf(self, &appbuf); + + return num_bytes_read; +} \ No newline at end of file diff --git a/src/include/pico/i2s_microphone.h b/src/include/pico/i2s_microphone.h new file mode 100644 index 0000000..3ad355e --- /dev/null +++ b/src/include/pico/i2s_microphone.h @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +#ifndef _PICO_I2S_MICROPHONE_H_ +#define _PICO_I2S_MICROPHONE_H_ + +#include "hardware/pio.h" + +#define MAX_I2S_RP2 (2) + +// The DMA buffer size was empirically determined. It is a tradeoff between: +// 1. memory use (smaller buffer size desirable to reduce memory footprint) +// 2. interrupt frequency (larger buffer size desirable to reduce interrupt frequency) +#define SIZEOF_DMA_BUFFER_IN_BYTES (512 * 2) +#define SIZEOF_HALF_DMA_BUFFER_IN_BYTES (SIZEOF_DMA_BUFFER_IN_BYTES / 2) +#define I2S_NUM_DMA_CHANNELS (2) + +#define NUM_I2S_USER_FORMATS (4) +#define I2S_RX_FRAME_SIZE_IN_BYTES (8) + +#define SAMPLES_PER_FRAME (2) +#define PIO_INSTRUCTIONS_PER_BIT (2) + +#define STATIC static +#define mp_hal_pin_obj_t uint +#define m_new(type, num) ((type *)(malloc(sizeof(type) * (num)))) +#define m_new_obj(type) (m_new(type, 1)) + +typedef void (*i2s_samples_ready_handler_t)(void); + +typedef enum { + RX, + TX +} i2s_mode_t; + +typedef enum { + MONO, + STEREO +} format_t; + +typedef enum { + BLOCKING, + NON_BLOCKING, + UASYNCIO +} io_mode_t; + +typedef enum { + GP_INPUT = 0, + GP_OUTPUT = 1 +} gpio_dir_t; + +typedef struct _ring_buf_t { + uint8_t *buffer; + size_t head; + size_t tail; + size_t size; +} ring_buf_t; + + +// Buffer protocol +typedef struct _mp_buffer_info_t { + void *buf; // can be NULL if len == 0 + size_t len; // in bytes + int typecode; // as per binary.h +} mp_buffer_info_t; + +typedef struct _machine_i2s_obj_t { + uint8_t i2s_id; + mp_hal_pin_obj_t sck; + mp_hal_pin_obj_t ws; + mp_hal_pin_obj_t sd; + i2s_mode_t mode; + int8_t bits; + format_t format; + int32_t rate; + int32_t ibuf; + io_mode_t io_mode; + PIO pio; + uint8_t sm; + const pio_program_t *pio_program; + uint prog_offset; + int dma_channel[I2S_NUM_DMA_CHANNELS]; + uint8_t dma_buffer[SIZEOF_DMA_BUFFER_IN_BYTES]; + ring_buf_t ring_buffer; + uint8_t *ring_buffer_storage; + uint8_t flagHandler; +} machine_i2s_obj_t; + +typedef void (*i2s_samples_ready_handler_t)(void); + +STATIC machine_i2s_obj_t* machine_i2s_make_new(uint8_t i2s_id, mp_hal_pin_obj_t sck, mp_hal_pin_obj_t ws, mp_hal_pin_obj_t sd, i2s_mode_t i2s_mode, int8_t i2s_bits, format_t i2s_format, int32_t ring_buffer_len, int32_t i2s_rate); + +STATIC void machine_i2s_deinit(machine_i2s_obj_t *self); + +STATIC void i2s_microphone_set_samples_ready_handler(i2s_samples_ready_handler_t handler); + + +#endif From 0818161e51e8bb26b148d83875d05af3cbfa68c9 Mon Sep 17 00:00:00 2001 From: dattran-itrvn Date: Wed, 30 Aug 2023 09:32:24 +0700 Subject: [PATCH 2/8] update --- CMakeLists.txt | 19 +++++++++++++++++-- src/i2s_microphone.c | 5 ++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5796e06..8b08c31 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,21 @@ pico_generate_pio_header(pico_pdm_microphone ${CMAKE_CURRENT_LIST_DIR}/src/pdm_m target_link_libraries(pico_pdm_microphone INTERFACE pico_stdlib hardware_dma hardware_pio) +add_library(pico_i2s_microphone INTERFACE) + +target_sources(pico_i2s_microphone INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/src/i2s_microphone.c +) + +target_include_directories(pico_i2s_microphone INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/src/include +) + +# pico_generate_pio_header(pico_pdm_microphone ${CMAKE_CURRENT_LIST_DIR}/src/pdm_microphone.pio) + +target_link_libraries(pico_i2s_microphone INTERFACE pico_stdlib hardware_dma hardware_pio) + + add_library(pico_analog_microphone INTERFACE) target_sources(pico_analog_microphone INTERFACE @@ -41,6 +56,6 @@ target_include_directories(pico_analog_microphone INTERFACE target_link_libraries(pico_i2s_microphone INTERFACE pico_stdlib hardware_adc hardware_dma) +add_subdirectory("examples/hello_pdm_microphone") +add_subdirectory("examples/usb_microphone") add_subdirectory("examples/hello_i2s_microphone") -# add_subdirectory("examples/hello_pdm_microphone") -# add_subdirectory("examples/usb_microphone") diff --git a/src/i2s_microphone.c b/src/i2s_microphone.c index 9603418..51cfc67 100644 --- a/src/i2s_microphone.c +++ b/src/i2s_microphone.c @@ -582,9 +582,12 @@ STATIC void dma_irq_handler(uint8_t irq_index) { empty_dma(self, dma_buffer); dma_irqn_acknowledge_channel(irq_index, dma_channel); dma_channel_set_write_addr(dma_channel, dma_buffer, false); + if (self->handlerEvent) { + self->handlerEvent(); + } } self->flagHandler = 1; - + } STATIC void dma_irq0_handler(void) { From 00c2a1b68286864d146d53b13afec92304d295d4 Mon Sep 17 00:00:00 2001 From: ThanhBinh_ITRVN Date: Wed, 30 Aug 2023 11:09:34 +0700 Subject: [PATCH 3/8] Build Success --- .vscode/settings.json | 3 ++- CMakeLists.txt | 16 ++-------------- .../{hello_pdm_microphone => }/CMakeLists.txt | 0 .../{hello_pdm_microphone => }/main.c | 0 src/i2s_microphone.c | 10 +++++----- src/include/pico/i2s_microphone.h | 7 ++++--- 6 files changed, 13 insertions(+), 23 deletions(-) rename examples/hello_i2s_microphone/{hello_pdm_microphone => }/CMakeLists.txt (100%) rename examples/hello_i2s_microphone/{hello_pdm_microphone => }/main.c (100%) diff --git a/.vscode/settings.json b/.vscode/settings.json index 4a633fb..051dd68 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,7 @@ "pio.h": "c", "dreq.h": "c", "pio_instructions.h": "c", - "type_traits": "cpp" + "type_traits": "cpp", + "i2s_microphone.h": "c" } } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b08c31..0542ffa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.12) # initialize pico_sdk from GIT # (note this can come from environment, CMake cache etc) -set(ENV{PICO_SDK_PATH} "/home/dattran/pico/pico-sdk") +set(ENV{PICO_SDK_PATH} "/home/ttb/raspberry-pi-pico/pico/pico-sdk") # pico_sdk_import.cmake is a single file copied from this SDK # note: this must happen before project() @@ -14,47 +14,35 @@ project(pico_microphone) pico_sdk_init() add_library(pico_pdm_microphone INTERFACE) - target_sources(pico_pdm_microphone INTERFACE ${CMAKE_CURRENT_LIST_DIR}/src/pdm_microphone.c ${CMAKE_CURRENT_LIST_DIR}/src/OpenPDM2PCM/OpenPDMFilter.c ) - target_include_directories(pico_pdm_microphone INTERFACE ${CMAKE_CURRENT_LIST_DIR}/src/include ) - pico_generate_pio_header(pico_pdm_microphone ${CMAKE_CURRENT_LIST_DIR}/src/pdm_microphone.pio) - target_link_libraries(pico_pdm_microphone INTERFACE pico_stdlib hardware_dma hardware_pio) add_library(pico_i2s_microphone INTERFACE) - target_sources(pico_i2s_microphone INTERFACE ${CMAKE_CURRENT_LIST_DIR}/src/i2s_microphone.c ) - target_include_directories(pico_i2s_microphone INTERFACE ${CMAKE_CURRENT_LIST_DIR}/src/include ) - -# pico_generate_pio_header(pico_pdm_microphone ${CMAKE_CURRENT_LIST_DIR}/src/pdm_microphone.pio) - target_link_libraries(pico_i2s_microphone INTERFACE pico_stdlib hardware_dma hardware_pio) add_library(pico_analog_microphone INTERFACE) - target_sources(pico_analog_microphone INTERFACE ${CMAKE_CURRENT_LIST_DIR}/src/analog_microphone.c ) - target_include_directories(pico_analog_microphone INTERFACE ${CMAKE_CURRENT_LIST_DIR}/src/include ) - -target_link_libraries(pico_i2s_microphone INTERFACE pico_stdlib hardware_adc hardware_dma) +target_link_libraries(pico_analog_microphone INTERFACE pico_stdlib hardware_adc hardware_dma) add_subdirectory("examples/hello_pdm_microphone") add_subdirectory("examples/usb_microphone") diff --git a/examples/hello_i2s_microphone/hello_pdm_microphone/CMakeLists.txt b/examples/hello_i2s_microphone/CMakeLists.txt similarity index 100% rename from examples/hello_i2s_microphone/hello_pdm_microphone/CMakeLists.txt rename to examples/hello_i2s_microphone/CMakeLists.txt diff --git a/examples/hello_i2s_microphone/hello_pdm_microphone/main.c b/examples/hello_i2s_microphone/main.c similarity index 100% rename from examples/hello_i2s_microphone/hello_pdm_microphone/main.c rename to examples/hello_i2s_microphone/main.c diff --git a/src/i2s_microphone.c b/src/i2s_microphone.c index 51cfc67..012f757 100644 --- a/src/i2s_microphone.c +++ b/src/i2s_microphone.c @@ -47,12 +47,12 @@ #include #include -#include "i2s_microphone.h" #include "hardware/pio.h" #include "hardware/clocks.h" #include "hardware/gpio.h" #include "hardware/dma.h" #include "hardware/irq.h" +#include "pico/i2s_microphone.h" STATIC machine_i2s_obj_t* machine_i2s_obj[MAX_I2S_RP2] = {NULL, NULL}; @@ -598,12 +598,12 @@ STATIC void dma_irq1_handler(void) { dma_irq_handler(1); } -STATIC void i2s_microphone_set_samples_ready_handler(i2s_samples_ready_handler_t handler) { +void i2s_microphone_set_samples_ready_handler(i2s_samples_ready_handler_t handler) { machine_i2s_obj_t *self; self->handlerEvent = handler; } -STATIC int machine_i2s_init_helper(machine_i2s_obj_t *self, +int machine_i2s_init_helper(machine_i2s_obj_t *self, mp_hal_pin_obj_t sck, mp_hal_pin_obj_t ws, mp_hal_pin_obj_t sd, i2s_mode_t i2s_mode, int8_t i2s_bits, format_t i2s_format, int32_t ring_buffer_len, int32_t i2s_rate) { @@ -674,7 +674,7 @@ STATIC int machine_i2s_init_helper(machine_i2s_obj_t *self, return 0; } -STATIC machine_i2s_obj_t* machine_i2s_make_new(uint8_t i2s_id, +machine_i2s_obj_t* machine_i2s_make_new(uint8_t i2s_id, mp_hal_pin_obj_t sck, mp_hal_pin_obj_t ws, mp_hal_pin_obj_t sd, i2s_mode_t i2s_mode, int8_t i2s_bits, format_t i2s_format, int32_t ring_buffer_len, int32_t i2s_rate) { @@ -712,7 +712,7 @@ STATIC void machine_i2s_deinit(machine_i2s_obj_t *self) { } } -STATIC int machine_i2s_stream_read(machine_i2s_obj_t *self, void *buf_in, size_t size) { +int machine_i2s_stream_read(machine_i2s_obj_t *self, void *buf_in, size_t size) { if (self->mode != RX) { return -1; } diff --git a/src/include/pico/i2s_microphone.h b/src/include/pico/i2s_microphone.h index 3ad355e..d5bd569 100644 --- a/src/include/pico/i2s_microphone.h +++ b/src/include/pico/i2s_microphone.h @@ -88,15 +88,16 @@ typedef struct _machine_i2s_obj_t { ring_buf_t ring_buffer; uint8_t *ring_buffer_storage; uint8_t flagHandler; + i2s_samples_ready_handler_t handlerEvent; } machine_i2s_obj_t; typedef void (*i2s_samples_ready_handler_t)(void); -STATIC machine_i2s_obj_t* machine_i2s_make_new(uint8_t i2s_id, mp_hal_pin_obj_t sck, mp_hal_pin_obj_t ws, mp_hal_pin_obj_t sd, i2s_mode_t i2s_mode, int8_t i2s_bits, format_t i2s_format, int32_t ring_buffer_len, int32_t i2s_rate); +machine_i2s_obj_t* machine_i2s_make_new(uint8_t i2s_id, mp_hal_pin_obj_t sck, mp_hal_pin_obj_t ws, mp_hal_pin_obj_t sd, i2s_mode_t i2s_mode, int8_t i2s_bits, format_t i2s_format, int32_t ring_buffer_len, int32_t i2s_rate); -STATIC void machine_i2s_deinit(machine_i2s_obj_t *self); +// void machine_i2s_deinit(machine_i2s_obj_t *self); -STATIC void i2s_microphone_set_samples_ready_handler(i2s_samples_ready_handler_t handler); +void i2s_microphone_set_samples_ready_handler(i2s_samples_ready_handler_t handler); #endif From 8e4cb6e711b2724ceb83656e70e98269359bd105 Mon Sep 17 00:00:00 2001 From: dattran-itrvn Date: Wed, 30 Aug 2023 15:50:30 +0700 Subject: [PATCH 4/8] update - cmake --- .vscode/settings.json | 4 +++- CMakeLists.txt | 15 ++------------- .../{hello_pdm_microphone => }/CMakeLists.txt | 0 .../{hello_pdm_microphone => }/main.c | 6 +++--- src/i2s_microphone.c | 15 +++++++-------- src/include/pico/i2s_microphone.h | 14 ++++++-------- 6 files changed, 21 insertions(+), 33 deletions(-) rename examples/hello_i2s_microphone/{hello_pdm_microphone => }/CMakeLists.txt (100%) rename examples/hello_i2s_microphone/{hello_pdm_microphone => }/main.c (95%) diff --git a/.vscode/settings.json b/.vscode/settings.json index 4a633fb..5cc7b6c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,8 @@ "pio.h": "c", "dreq.h": "c", "pio_instructions.h": "c", - "type_traits": "cpp" + "type_traits": "cpp", + "i2s_microphone.h": "c", + "stdio.h": "c" } } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b08c31..2e504ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,48 +14,37 @@ project(pico_microphone) pico_sdk_init() add_library(pico_pdm_microphone INTERFACE) - target_sources(pico_pdm_microphone INTERFACE ${CMAKE_CURRENT_LIST_DIR}/src/pdm_microphone.c ${CMAKE_CURRENT_LIST_DIR}/src/OpenPDM2PCM/OpenPDMFilter.c ) - target_include_directories(pico_pdm_microphone INTERFACE ${CMAKE_CURRENT_LIST_DIR}/src/include ) - pico_generate_pio_header(pico_pdm_microphone ${CMAKE_CURRENT_LIST_DIR}/src/pdm_microphone.pio) - target_link_libraries(pico_pdm_microphone INTERFACE pico_stdlib hardware_dma hardware_pio) add_library(pico_i2s_microphone INTERFACE) - target_sources(pico_i2s_microphone INTERFACE ${CMAKE_CURRENT_LIST_DIR}/src/i2s_microphone.c ) - target_include_directories(pico_i2s_microphone INTERFACE ${CMAKE_CURRENT_LIST_DIR}/src/include ) - -# pico_generate_pio_header(pico_pdm_microphone ${CMAKE_CURRENT_LIST_DIR}/src/pdm_microphone.pio) - target_link_libraries(pico_i2s_microphone INTERFACE pico_stdlib hardware_dma hardware_pio) add_library(pico_analog_microphone INTERFACE) - target_sources(pico_analog_microphone INTERFACE ${CMAKE_CURRENT_LIST_DIR}/src/analog_microphone.c ) - target_include_directories(pico_analog_microphone INTERFACE ${CMAKE_CURRENT_LIST_DIR}/src/include ) - -target_link_libraries(pico_i2s_microphone INTERFACE pico_stdlib hardware_adc hardware_dma) +target_link_libraries(pico_analog_microphone INTERFACE pico_stdlib hardware_adc hardware_dma) add_subdirectory("examples/hello_pdm_microphone") add_subdirectory("examples/usb_microphone") add_subdirectory("examples/hello_i2s_microphone") +add_subdirectory("examples/hello_analog_microphone") \ No newline at end of file diff --git a/examples/hello_i2s_microphone/hello_pdm_microphone/CMakeLists.txt b/examples/hello_i2s_microphone/CMakeLists.txt similarity index 100% rename from examples/hello_i2s_microphone/hello_pdm_microphone/CMakeLists.txt rename to examples/hello_i2s_microphone/CMakeLists.txt diff --git a/examples/hello_i2s_microphone/hello_pdm_microphone/main.c b/examples/hello_i2s_microphone/main.c similarity index 95% rename from examples/hello_i2s_microphone/hello_pdm_microphone/main.c rename to examples/hello_i2s_microphone/main.c index 2d42cc2..85ee832 100644 --- a/examples/hello_i2s_microphone/hello_pdm_microphone/main.c +++ b/examples/hello_i2s_microphone/main.c @@ -19,7 +19,7 @@ #define SCK 2 #define WS 3 // needs to be SCK +1 #define SD 6 -#define BPS 16 +#define BPS 32 #define SAMPLE_RATE 16000 #define FFT_SIZE 256 #define SPECTRUM_SHIFT 4 @@ -29,13 +29,13 @@ #define LEN_WAVE_HEADER 44 // variables -int16_t sample_buffer[SIZEOF_HALF_DMA_BUFFER_IN_BYTES]; +int16_t sample_buffer[INPUT_BUFFER_SIZE / 2]; volatile int samples_read = 0; machine_i2s_obj_t* i2s0; typedef void (*I2SHandler) (machine_i2s_obj_t* i2s0); void on_i2s_mic_samples_ready(machine_i2s_obj_t* i2s0){ - samples_read = machine_i2s_stream_read(i2s0, (void*)&sample_buffer, INPUT_BUFFER_SIZE * 2); + samples_read = machine_i2s_stream_read(i2s0, (void*)&sample_buffer, INPUT_BUFFER_SIZE); if(samples_read != 0){ printf("Data OK\r\n"); } diff --git a/src/i2s_microphone.c b/src/i2s_microphone.c index 51cfc67..8cc08b2 100644 --- a/src/i2s_microphone.c +++ b/src/i2s_microphone.c @@ -46,13 +46,13 @@ #include #include #include - -#include "i2s_microphone.h" #include "hardware/pio.h" #include "hardware/clocks.h" #include "hardware/gpio.h" #include "hardware/dma.h" #include "hardware/irq.h" +#include "pico/i2s_microphone.h" + STATIC machine_i2s_obj_t* machine_i2s_obj[MAX_I2S_RP2] = {NULL, NULL}; @@ -125,7 +125,6 @@ STATIC const pio_program_t pio_read_32 = { STATIC uint8_t dma_get_bits(i2s_mode_t mode, int8_t bits); STATIC void dma_irq0_handler(void); STATIC void dma_irq1_handler(void); -STATIC void machine_i2s_deinit(machine_i2s_obj_t *self); // Ring Buffer // Thread safe when used with these constraints: @@ -598,12 +597,12 @@ STATIC void dma_irq1_handler(void) { dma_irq_handler(1); } -STATIC void i2s_microphone_set_samples_ready_handler(i2s_samples_ready_handler_t handler) { +void i2s_microphone_set_samples_ready_handler(i2s_samples_ready_handler_t handler) { machine_i2s_obj_t *self; self->handlerEvent = handler; } -STATIC int machine_i2s_init_helper(machine_i2s_obj_t *self, +int machine_i2s_init_helper(machine_i2s_obj_t *self, mp_hal_pin_obj_t sck, mp_hal_pin_obj_t ws, mp_hal_pin_obj_t sd, i2s_mode_t i2s_mode, int8_t i2s_bits, format_t i2s_format, int32_t ring_buffer_len, int32_t i2s_rate) { @@ -674,7 +673,7 @@ STATIC int machine_i2s_init_helper(machine_i2s_obj_t *self, return 0; } -STATIC machine_i2s_obj_t* machine_i2s_make_new(uint8_t i2s_id, +machine_i2s_obj_t* machine_i2s_make_new(uint8_t i2s_id, mp_hal_pin_obj_t sck, mp_hal_pin_obj_t ws, mp_hal_pin_obj_t sd, i2s_mode_t i2s_mode, int8_t i2s_bits, format_t i2s_format, int32_t ring_buffer_len, int32_t i2s_rate) { @@ -701,7 +700,7 @@ STATIC machine_i2s_obj_t* machine_i2s_make_new(uint8_t i2s_id, -STATIC void machine_i2s_deinit(machine_i2s_obj_t *self) { +void machine_i2s_deinit(machine_i2s_obj_t *self) { // use self->pio as in indication that I2S object has already been de-initialized if (self->pio != NULL) { pio_deinit(self); @@ -712,7 +711,7 @@ STATIC void machine_i2s_deinit(machine_i2s_obj_t *self) { } } -STATIC int machine_i2s_stream_read(machine_i2s_obj_t *self, void *buf_in, size_t size) { +int machine_i2s_stream_read(machine_i2s_obj_t *self, void *buf_in, size_t size) { if (self->mode != RX) { return -1; } diff --git a/src/include/pico/i2s_microphone.h b/src/include/pico/i2s_microphone.h index 3ad355e..6bc48ec 100644 --- a/src/include/pico/i2s_microphone.h +++ b/src/include/pico/i2s_microphone.h @@ -15,7 +15,7 @@ // The DMA buffer size was empirically determined. It is a tradeoff between: // 1. memory use (smaller buffer size desirable to reduce memory footprint) // 2. interrupt frequency (larger buffer size desirable to reduce interrupt frequency) -#define SIZEOF_DMA_BUFFER_IN_BYTES (512 * 2) +#define SIZEOF_DMA_BUFFER_IN_BYTES (256 * 2) #define SIZEOF_HALF_DMA_BUFFER_IN_BYTES (SIZEOF_DMA_BUFFER_IN_BYTES / 2) #define I2S_NUM_DMA_CHANNELS (2) @@ -88,15 +88,13 @@ typedef struct _machine_i2s_obj_t { ring_buf_t ring_buffer; uint8_t *ring_buffer_storage; uint8_t flagHandler; + i2s_samples_ready_handler_t handlerEvent; } machine_i2s_obj_t; -typedef void (*i2s_samples_ready_handler_t)(void); - -STATIC machine_i2s_obj_t* machine_i2s_make_new(uint8_t i2s_id, mp_hal_pin_obj_t sck, mp_hal_pin_obj_t ws, mp_hal_pin_obj_t sd, i2s_mode_t i2s_mode, int8_t i2s_bits, format_t i2s_format, int32_t ring_buffer_len, int32_t i2s_rate); - -STATIC void machine_i2s_deinit(machine_i2s_obj_t *self); - -STATIC void i2s_microphone_set_samples_ready_handler(i2s_samples_ready_handler_t handler); +machine_i2s_obj_t* machine_i2s_make_new(uint8_t i2s_id, mp_hal_pin_obj_t sck, mp_hal_pin_obj_t ws, mp_hal_pin_obj_t sd, i2s_mode_t i2s_mode, int8_t i2s_bits, format_t i2s_format, int32_t ring_buffer_len, int32_t i2s_rate); +int machine_i2s_stream_read(machine_i2s_obj_t *self, void *buf_in, size_t size); +void machine_i2s_deinit(machine_i2s_obj_t *self); +void i2s_microphone_set_samples_ready_handler(i2s_samples_ready_handler_t handler); #endif From 33e4722da5f93fa5691146268a26883be59aedde Mon Sep 17 00:00:00 2001 From: ThanhBinh_ITRVN Date: Thu, 31 Aug 2023 13:59:57 +0700 Subject: [PATCH 5/8] update mic audio buffer --- CMakeLists.txt | 2 + examples/hello_i2s_microphone/CMakeLists.txt | 9 +- examples/hello_i2s_microphone/main.c | 102 ++---- examples/hello_i2s_microphone/tusb_config.h | 111 ++++++ .../hello_i2s_microphone/usb_descriptors.c | 160 +++++++++ .../hello_i2s_microphone/usb_microphone.c | 338 ++++++++++++++++++ .../hello_i2s_microphone/usb_microphone.h | 28 ++ src/i2s_microphone.c | 26 +- src/include/pico/i2s_microphone.h | 19 +- 9 files changed, 700 insertions(+), 95 deletions(-) create mode 100644 examples/hello_i2s_microphone/tusb_config.h create mode 100644 examples/hello_i2s_microphone/usb_descriptors.c create mode 100644 examples/hello_i2s_microphone/usb_microphone.c create mode 100644 examples/hello_i2s_microphone/usb_microphone.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0542ffa..5770d43 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,7 @@ target_sources(pico_i2s_microphone INTERFACE ) target_include_directories(pico_i2s_microphone INTERFACE ${CMAKE_CURRENT_LIST_DIR}/src/include + examples/usb_microphone ) target_link_libraries(pico_i2s_microphone INTERFACE pico_stdlib hardware_dma hardware_pio) @@ -45,5 +46,6 @@ target_include_directories(pico_analog_microphone INTERFACE target_link_libraries(pico_analog_microphone INTERFACE pico_stdlib hardware_adc hardware_dma) add_subdirectory("examples/hello_pdm_microphone") +add_subdirectory("examples/hello_analog_microphone") add_subdirectory("examples/usb_microphone") add_subdirectory("examples/hello_i2s_microphone") diff --git a/examples/hello_i2s_microphone/CMakeLists.txt b/examples/hello_i2s_microphone/CMakeLists.txt index dbf3e08..8c6c476 100644 --- a/examples/hello_i2s_microphone/CMakeLists.txt +++ b/examples/hello_i2s_microphone/CMakeLists.txt @@ -3,14 +3,17 @@ cmake_minimum_required(VERSION 3.12) # rest of your project add_executable(hello_i2s_microphone main.c + usb_descriptors.c + usb_microphone.c ) -target_link_libraries(hello_i2s_microphone pico_i2s_microphone) +target_include_directories(hello_i2s_microphone PRIVATE ${CMAKE_CURRENT_LIST_DIR}) + +target_link_libraries(hello_i2s_microphone PRIVATE tinyusb_device tinyusb_board pico_i2s_microphone) # enable usb output, disable uart output pico_enable_stdio_usb(hello_i2s_microphone 1) -pico_enable_stdio_uart(hello_i2s_microphone 0) +# pico_enable_stdio_uart(hello_i2s_microphone 0) # create map/bin/hex/uf2 file in addition to ELF. pico_add_extra_outputs(hello_i2s_microphone) - diff --git a/examples/hello_i2s_microphone/main.c b/examples/hello_i2s_microphone/main.c index 2d42cc2..3096982 100644 --- a/examples/hello_i2s_microphone/main.c +++ b/examples/hello_i2s_microphone/main.c @@ -1,83 +1,57 @@ -/* - * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. - * - * SPDX-License-Identifier: Apache-2.0 - * - * This examples captures data from a PDM microphone using a sample - * rate of 8 kHz and prints the sample values over the USB serial - * connection. - */ - -#include -#include +#include "stdio.h" +#include "tusb.h" #include "pico/stdlib.h" #include "pico/i2s_microphone.h" -#include "tusb.h" - +#include "usb_microphone.h" #define SCK 2 #define WS 3 // needs to be SCK +1 #define SD 6 #define BPS 16 -#define SAMPLE_RATE 16000 -#define FFT_SIZE 256 -#define SPECTRUM_SHIFT 4 -#define INPUT_BUFFER_SIZE ((FFT_SIZE / 2) * SPECTRUM_SHIFT) -#define INPUT_SHIFT 0 +#define RATE 16000 #define BUTTON 15 -#define LEN_WAVE_HEADER 44 +#define SIZE_APP_BUFFER SIZEOF_DMA_BUFFER_IN_BYTES/8 -// variables -int16_t sample_buffer[SIZEOF_HALF_DMA_BUFFER_IN_BYTES]; -volatile int samples_read = 0; +uint32_t byteReads = 0; +int16_t sample_buffer[SIZE_APP_BUFFER]; machine_i2s_obj_t* i2s0; -typedef void (*I2SHandler) (machine_i2s_obj_t* i2s0); +uint8_t id_i2s = 0; -void on_i2s_mic_samples_ready(machine_i2s_obj_t* i2s0){ - samples_read = machine_i2s_stream_read(i2s0, (void*)&sample_buffer, INPUT_BUFFER_SIZE * 2); - if(samples_read != 0){ - printf("Data OK\r\n"); - } +//// Callback function I2S ///// +void on_i2s_samples_ready(){ + byteReads = machine_i2s_stream_read(i2s0, (void*)&sample_buffer, SIZE_APP_BUFFER); // app buffer len <= dma buffer len / 8 + printf("%d\r\n",byteReads); } -int main( void ) -{ - // initialize stdio and wait for USB CDC connect - stdio_init_all(); - while (!tud_cdc_connected()) { - tight_loop_contents(); - } +// Callback function USB /// +void on_usb_microphone_tx_ready(){ + usb_microphone_write(sample_buffer, sizeof(sample_buffer)); +} - printf("hello i2s microphone\n"); +void init(){ + stdio_init_all(); + gpio_init(BUTTON); + gpio_set_dir(BUTTON, GPIO_IN); + gpio_pull_up(BUTTON); +} - // initialize the PDM microphone - i2s0 = machine_i2s_make_new(0, SCK, WS, SD, RX, BPS, MONO, SIZEOF_DMA_BUFFER_IN_BYTES, SAMPLE_RATE); - if(i2s0 == NULL){ - printf("Failed to initialize MIC I2S!\n"); - while (1) { tight_loop_contents(); } +int main(){ + init(); + i2s0 = machine_i2s_make_new(id_i2s, SCK, WS, SD, + RX, BPS, MONO, + SIZEOF_DMA_BUFFER_IN_BYTES*2, // set ring buffer len >= dma buffer len + RATE); + + printf("I2S OK \n\n"); + i2s_microphone_set_samples_ready(on_i2s_samples_ready, id_i2s); + + usb_microphone_init(); + usb_microphone_set_tx_ready_handler(on_usb_microphone_tx_ready); + + while (1){ + usb_microphone_task(); } - else{ - printf("| MIC I2S initialize OK |\n"); - } - - // set callback that is called when all the samples in the library - // internal sample buffer are ready for reading - i2s_microphone_set_samples_ready_handler(on_i2s_mic_samples_ready); - while (1) { - // wait for new samples - while (samples_read == 0) { tight_loop_contents(); } - - // store and clear the samples read from the callback - int sample_count = samples_read; - samples_read = 0; - - // loop through any new collected samples - for (int i = 0; i < sample_count; i++) { - printf("%d\n", sample_buffer[i]); - } - } - return 0; -} +} \ No newline at end of file diff --git a/examples/hello_i2s_microphone/tusb_config.h b/examples/hello_i2s_microphone/tusb_config.h new file mode 100644 index 0000000..21f52d1 --- /dev/null +++ b/examples/hello_i2s_microphone/tusb_config.h @@ -0,0 +1,111 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef _TUSB_CONFIG_H_ +#define _TUSB_CONFIG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +// defined by compiler flags for flexibility +#ifndef CFG_TUSB_MCU +#error CFG_TUSB_MCU must be defined +#endif + +#if CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX +#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | OPT_MODE_HIGH_SPEED) +#else +#define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_NONE +#endif + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +// CFG_TUSB_DEBUG is defined by compiler in DEBUG build +// #define CFG_TUSB_DEBUG 0 + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +//------------- CLASS -------------// +#define CFG_TUD_CDC 0 +#define CFG_TUD_MSC 0 +#define CFG_TUD_HID 0 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_AUDIO 1 +#define CFG_TUD_VENDOR 0 + +//-------------------------------------------------------------------- +// AUDIO CLASS DRIVER CONFIGURATION +//-------------------------------------------------------------------- + +// Have a look into audio_device.h for all configurations + +#define CFG_TUD_AUDIO_FUNC_1_DESC_LEN TUD_AUDIO_MIC_ONE_CH_DESC_LEN +#define CFG_TUD_AUDIO_FUNC_1_N_AS_INT 1 // Number of Standard AS Interface Descriptors (4.9.1) defined per audio function - this is required to be able to remember the current alternate settings of these interfaces - We restrict us here to have a constant number for all audio functions (which means this has to be the maximum number of AS interfaces an audio function has and a second audio function with less AS interfaces just wastes a few bytes) +#define CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ 64 // Size of control request buffer + +#define CFG_TUD_AUDIO_ENABLE_EP_IN 1 +#define CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX 2 // Driver gets this info from the descriptors - we define it here to use it to setup the descriptors and to do calculations with it below +#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX 1 // Driver gets this info from the descriptors - we define it here to use it to setup the descriptors and to do calculations with it below - be aware: for different number of channels you need another descriptor! +#define CFG_TUD_AUDIO_EP_SZ_IN (16 + 1) * CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX * CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX // 16 Samples (16 kHz) x 2 Bytes/Sample x 1 Channel +#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX CFG_TUD_AUDIO_EP_SZ_IN // Maximum EP IN size for all AS alternate settings used +#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ CFG_TUD_AUDIO_EP_SZ_IN + +#ifdef __cplusplus +} +#endif + +#endif /* _TUSB_CONFIG_H_ */ diff --git a/examples/hello_i2s_microphone/usb_descriptors.c b/examples/hello_i2s_microphone/usb_descriptors.c new file mode 100644 index 0000000..a4e9dc8 --- /dev/null +++ b/examples/hello_i2s_microphone/usb_descriptors.c @@ -0,0 +1,160 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "tusb.h" + +/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. + * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. + * + * Auto ProductID layout's Bitmap: + * [MSB] AUDIO | MIDI | HID | MSC | CDC [LSB] + */ +#define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) ) +#define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ + _PID_MAP(MIDI, 3) | _PID_MAP(AUDIO, 4) | _PID_MAP(VENDOR, 5) ) + +//--------------------------------------------------------------------+ +// Device Descriptors +//--------------------------------------------------------------------+ +tusb_desc_device_t const desc_device = +{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + + // Use Interface Association Descriptor (IAD) for CDC + // As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1) + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = 0xCafe, + .idProduct = USB_PID, + .bcdDevice = 0x0100, + + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + + .bNumConfigurations = 0x01 +}; + +// Invoked when received GET DEVICE DESCRIPTOR +// Application return pointer to descriptor +uint8_t const * tud_descriptor_device_cb(void) +{ + return (uint8_t const *) &desc_device; +} + +//--------------------------------------------------------------------+ +// Configuration Descriptor +//--------------------------------------------------------------------+ +enum +{ + ITF_NUM_AUDIO_CONTROL = 0, + ITF_NUM_AUDIO_STREAMING, + ITF_NUM_TOTAL +}; + +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_AUDIO * TUD_AUDIO_MIC_ONE_CH_DESC_LEN) + +#if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX +// LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number +// 0 control, 1 In, 2 Bulk, 3 Iso, 4 In etc ... +#define EPNUM_AUDIO 0x03 +#else +#define EPNUM_AUDIO 0x01 +#endif + +uint8_t const desc_configuration[] = +{ + // Interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), + + // Interface number, string index, EP Out & EP In address, EP size + TUD_AUDIO_MIC_ONE_CH_DESCRIPTOR(/*_itfnum*/ ITF_NUM_AUDIO_CONTROL, /*_stridx*/ 0, /*_nBytesPerSample*/ CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX, /*_nBitsUsedPerSample*/ CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX*8, /*_epin*/ 0x80 | EPNUM_AUDIO, /*_epsize*/ CFG_TUD_AUDIO_EP_SZ_IN) +}; + +// Invoked when received GET CONFIGURATION DESCRIPTOR +// Application return pointer to descriptor +// Descriptor contents must exist long enough for transfer to complete +uint8_t const * tud_descriptor_configuration_cb(uint8_t index) +{ + (void) index; // for multiple configurations + return desc_configuration; +} + +//--------------------------------------------------------------------+ +// String Descriptors +//--------------------------------------------------------------------+ + +// array of pointer to string descriptors +char const* string_desc_arr [] = +{ + (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) + "PaniRCorp", // 1: Manufacturer + "MicNode", // 2: Product + "123456", // 3: Serials, should use chip ID + "UAC2", // 4: Audio Interface +}; + +static uint16_t _desc_str[32]; + +// Invoked when received GET STRING DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) +{ + (void) langid; + + uint8_t chr_count; + + if ( index == 0) + { + memcpy(&_desc_str[1], string_desc_arr[0], 2); + chr_count = 1; + }else + { + // Convert ASCII string into UTF-16 + + if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL; + + const char* str = string_desc_arr[index]; + + // Cap at max char + chr_count = strlen(str); + if ( chr_count > 31 ) chr_count = 31; + + for(uint8_t i=0; ibRequest == AUDIO_CS_REQ_CUR); + + // Page 91 in UAC2 specification + uint8_t channelNum = TU_U16_LOW(p_request->wValue); + uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue); + uint8_t ep = TU_U16_LOW(p_request->wIndex); + + (void) channelNum; (void) ctrlSel; (void) ep; + + return false; // Yet not implemented +} + +// Invoked when audio class specific set request received for an interface +bool tud_audio_set_req_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff) +{ + (void) rhport; + (void) pBuff; + + // We do not support any set range requests here, only current value requests + TU_VERIFY(p_request->bRequest == AUDIO_CS_REQ_CUR); + + // Page 91 in UAC2 specification + uint8_t channelNum = TU_U16_LOW(p_request->wValue); + uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue); + uint8_t itf = TU_U16_LOW(p_request->wIndex); + + (void) channelNum; (void) ctrlSel; (void) itf; + + return false; // Yet not implemented +} + +// Invoked when audio class specific set request received for an entity +bool tud_audio_set_req_entity_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff) +{ + (void) rhport; + + // Page 91 in UAC2 specification + uint8_t channelNum = TU_U16_LOW(p_request->wValue); + uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue); + uint8_t itf = TU_U16_LOW(p_request->wIndex); + uint8_t entityID = TU_U16_HIGH(p_request->wIndex); + + (void) itf; + + // We do not support any set range requests here, only current value requests + TU_VERIFY(p_request->bRequest == AUDIO_CS_REQ_CUR); + + // If request is for our feature unit + if ( entityID == 2 ) + { + switch ( ctrlSel ) + { + case AUDIO_FU_CTRL_MUTE: + // Request uses format layout 1 + TU_VERIFY(p_request->wLength == sizeof(audio_control_cur_1_t)); + + mute[channelNum] = ((audio_control_cur_1_t*) pBuff)->bCur; + + TU_LOG2(" Set Mute: %d of channel: %u\r\n", mute[channelNum], channelNum); + + return true; + + case AUDIO_FU_CTRL_VOLUME: + // Request uses format layout 2 + TU_VERIFY(p_request->wLength == sizeof(audio_control_cur_2_t)); + + volume[channelNum] = ((audio_control_cur_2_t*) pBuff)->bCur; + + TU_LOG2(" Set Volume: %d dB of channel: %u\r\n", volume[channelNum], channelNum); + + return true; + + // Unknown/Unsupported control + default: + TU_BREAKPOINT(); + return false; + } + } + return false; // Yet not implemented +} + +// Invoked when audio class specific get request received for an EP +bool tud_audio_get_req_ep_cb(uint8_t rhport, tusb_control_request_t const * p_request) +{ + (void) rhport; + + // Page 91 in UAC2 specification + uint8_t channelNum = TU_U16_LOW(p_request->wValue); + uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue); + uint8_t ep = TU_U16_LOW(p_request->wIndex); + + (void) channelNum; (void) ctrlSel; (void) ep; + + // return tud_control_xfer(rhport, p_request, &tmp, 1); + + return false; // Yet not implemented +} + +// Invoked when audio class specific get request received for an interface +bool tud_audio_get_req_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request) +{ + (void) rhport; + + // Page 91 in UAC2 specification + uint8_t channelNum = TU_U16_LOW(p_request->wValue); + uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue); + uint8_t itf = TU_U16_LOW(p_request->wIndex); + + (void) channelNum; (void) ctrlSel; (void) itf; + + return false; // Yet not implemented +} + +// Invoked when audio class specific get request received for an entity +bool tud_audio_get_req_entity_cb(uint8_t rhport, tusb_control_request_t const * p_request) +{ + (void) rhport; + + // Page 91 in UAC2 specification + uint8_t channelNum = TU_U16_LOW(p_request->wValue); + uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue); + // uint8_t itf = TU_U16_LOW(p_request->wIndex); // Since we have only one audio function implemented, we do not need the itf value + uint8_t entityID = TU_U16_HIGH(p_request->wIndex); + + // Input terminal (Microphone input) + if (entityID == 1) + { + switch (ctrlSel) + { + case AUDIO_TE_CTRL_CONNECTOR:; + // The terminal connector control only has a get request with only the CUR attribute. + + audio_desc_channel_cluster_t ret; + + // Those are dummy values for now + ret.bNrChannels = 1; + ret.bmChannelConfig = 0; + ret.iChannelNames = 0; + + TU_LOG2(" Get terminal connector\r\n"); + + return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, (void*)&ret, sizeof(ret)); + + // Unknown/Unsupported control selector + default: TU_BREAKPOINT(); return false; + } + } + + // Feature unit + if (entityID == 2) + { + switch (ctrlSel) + { + case AUDIO_FU_CTRL_MUTE: + // Audio control mute cur parameter block consists of only one byte - we thus can send it right away + // There does not exist a range parameter block for mute + TU_LOG2(" Get Mute of channel: %u\r\n", channelNum); + return tud_control_xfer(rhport, p_request, &mute[channelNum], 1); + + case AUDIO_FU_CTRL_VOLUME: + + switch (p_request->bRequest) + { + case AUDIO_CS_REQ_CUR: + TU_LOG2(" Get Volume of channel: %u\r\n", channelNum); + return tud_control_xfer(rhport, p_request, &volume[channelNum], sizeof(volume[channelNum])); + case AUDIO_CS_REQ_RANGE: + TU_LOG2(" Get Volume range of channel: %u\r\n", channelNum); + + // Copy values - only for testing - better is version below + audio_control_range_2_n_t(1) ret; + + ret.wNumSubRanges = 1; + ret.subrange[0].bMin = -90; // -90 dB + ret.subrange[0].bMax = 90; // +90 dB + ret.subrange[0].bRes = 1; // 1 dB steps + + return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, (void*)&ret, sizeof(ret)); + + // Unknown/Unsupported control + default: TU_BREAKPOINT(); return false; + } + + // Unknown/Unsupported control + default: TU_BREAKPOINT(); return false; + } + } + + // Clock Source unit + if (entityID == 4) + { + switch (ctrlSel) + { + case AUDIO_CS_CTRL_SAM_FREQ: + + // channelNum is always zero in this case + + switch (p_request->bRequest) + { + case AUDIO_CS_REQ_CUR: + TU_LOG2(" Get Sample Freq.\r\n"); + return tud_control_xfer(rhport, p_request, &sampFreq, sizeof(sampFreq)); + case AUDIO_CS_REQ_RANGE: + TU_LOG2(" Get Sample Freq. range\r\n"); + return tud_control_xfer(rhport, p_request, &sampleFreqRng, sizeof(sampleFreqRng)); + + // Unknown/Unsupported control + default: TU_BREAKPOINT(); return false; + } + + case AUDIO_CS_CTRL_CLK_VALID: + // Only cur attribute exists for this request + TU_LOG2(" Get Sample Freq. valid\r\n"); + return tud_control_xfer(rhport, p_request, &clkValid, sizeof(clkValid)); + + // Unknown/Unsupported control + default: TU_BREAKPOINT(); return false; + } + } + + TU_LOG2(" Unsupported entity: %d\r\n", entityID); + return false; // Yet not implemented +} + +bool tud_audio_tx_done_pre_load_cb(uint8_t rhport, uint8_t itf, uint8_t ep_in, uint8_t cur_alt_setting) +{ + (void) rhport; + (void) itf; + (void) ep_in; + (void) cur_alt_setting; + + if (usb_microphone_tx_ready_handler) + { + usb_microphone_tx_ready_handler(); + } + + return true; +} + +bool tud_audio_tx_done_post_load_cb(uint8_t rhport, uint16_t n_bytes_copied, uint8_t itf, uint8_t ep_in, uint8_t cur_alt_setting) +{ + (void) rhport; + (void) n_bytes_copied; + (void) itf; + (void) ep_in; + (void) cur_alt_setting; + + return true; +} + +bool tud_audio_set_itf_close_EP_cb(uint8_t rhport, tusb_control_request_t const * p_request) +{ + (void) rhport; + (void) p_request; + + return true; +} diff --git a/examples/hello_i2s_microphone/usb_microphone.h b/examples/hello_i2s_microphone/usb_microphone.h new file mode 100644 index 0000000..cf45b9c --- /dev/null +++ b/examples/hello_i2s_microphone/usb_microphone.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +#ifndef _USB_MICROPHONE_H_ +#define _USB_MICROPHONE_H_ + +#include "tusb.h" + +#ifndef SAMPLE_RATE +#define SAMPLE_RATE ((CFG_TUD_AUDIO_EP_SZ_IN / 2) - 1) * 1000 +#endif + +#ifndef SAMPLE_BUFFER_SIZE +#define SAMPLE_BUFFER_SIZE ((CFG_TUD_AUDIO_EP_SZ_IN/2) - 1) +#endif + +typedef void (*usb_microphone_tx_ready_handler_t)(void); + +void usb_microphone_init(); +void usb_microphone_set_tx_ready_handler(usb_microphone_tx_ready_handler_t handler); +void usb_microphone_task(); +uint16_t usb_microphone_write(const void * data, uint16_t len); + +#endif diff --git a/src/i2s_microphone.c b/src/i2s_microphone.c index 012f757..a06f562 100644 --- a/src/i2s_microphone.c +++ b/src/i2s_microphone.c @@ -1,5 +1,5 @@ /* - machine_i2s.c - + i2s_microphone.c - I2S digital audio input C library for the Raspberry Pi Pico RP2040 Copyright (C) 2022 Sfera Labs S.r.l. - All rights reserved. @@ -47,12 +47,12 @@ #include #include +#include "pico/i2s_microphone.h" #include "hardware/pio.h" #include "hardware/clocks.h" #include "hardware/gpio.h" #include "hardware/dma.h" #include "hardware/irq.h" -#include "pico/i2s_microphone.h" STATIC machine_i2s_obj_t* machine_i2s_obj[MAX_I2S_RP2] = {NULL, NULL}; @@ -558,6 +558,10 @@ STATIC void dma_deinit(machine_i2s_obj_t *self) { } } +void i2s_microphone_set_samples_ready(i2s_samples_ready_handler_t handler, uint8_t I2S_ID){ + machine_i2s_obj[I2S_ID]->samples_ready_handler = handler; +} + STATIC void dma_irq_handler(uint8_t irq_index) { int dma_channel = dma_map_irq_to_channel(irq_index); if (dma_channel == -1) { @@ -582,11 +586,11 @@ STATIC void dma_irq_handler(uint8_t irq_index) { empty_dma(self, dma_buffer); dma_irqn_acknowledge_channel(irq_index, dma_channel); dma_channel_set_write_addr(dma_channel, dma_buffer, false); - if (self->handlerEvent) { - self->handlerEvent(); - } } - self->flagHandler = 1; + + if (self->samples_ready_handler) { + self->samples_ready_handler(); + } } @@ -598,12 +602,7 @@ STATIC void dma_irq1_handler(void) { dma_irq_handler(1); } -void i2s_microphone_set_samples_ready_handler(i2s_samples_ready_handler_t handler) { - machine_i2s_obj_t *self; - self->handlerEvent = handler; -} - -int machine_i2s_init_helper(machine_i2s_obj_t *self, +STATIC int machine_i2s_init_helper(machine_i2s_obj_t *self, mp_hal_pin_obj_t sck, mp_hal_pin_obj_t ws, mp_hal_pin_obj_t sd, i2s_mode_t i2s_mode, int8_t i2s_bits, format_t i2s_format, int32_t ring_buffer_len, int32_t i2s_rate) { @@ -655,7 +654,7 @@ int machine_i2s_init_helper(machine_i2s_obj_t *self, self->format = i2s_format; self->rate = i2s_rate; self->ibuf = ring_buffer_len; - self->io_mode = BLOCKING; + self->io_mode = UASYNCIO; irq_configure(self); int err = pio_configure(self); @@ -700,7 +699,6 @@ machine_i2s_obj_t* machine_i2s_make_new(uint8_t i2s_id, } - STATIC void machine_i2s_deinit(machine_i2s_obj_t *self) { // use self->pio as in indication that I2S object has already been de-initialized if (self->pio != NULL) { diff --git a/src/include/pico/i2s_microphone.h b/src/include/pico/i2s_microphone.h index d5bd569..a8a247e 100644 --- a/src/include/pico/i2s_microphone.h +++ b/src/include/pico/i2s_microphone.h @@ -1,10 +1,3 @@ -/* - * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ - #ifndef _PICO_I2S_MICROPHONE_H_ #define _PICO_I2S_MICROPHONE_H_ @@ -15,7 +8,7 @@ // The DMA buffer size was empirically determined. It is a tradeoff between: // 1. memory use (smaller buffer size desirable to reduce memory footprint) // 2. interrupt frequency (larger buffer size desirable to reduce interrupt frequency) -#define SIZEOF_DMA_BUFFER_IN_BYTES (512 * 2) +#define SIZEOF_DMA_BUFFER_IN_BYTES (256) #define SIZEOF_HALF_DMA_BUFFER_IN_BYTES (SIZEOF_DMA_BUFFER_IN_BYTES / 2) #define I2S_NUM_DMA_CHANNELS (2) @@ -87,17 +80,15 @@ typedef struct _machine_i2s_obj_t { uint8_t dma_buffer[SIZEOF_DMA_BUFFER_IN_BYTES]; ring_buf_t ring_buffer; uint8_t *ring_buffer_storage; - uint8_t flagHandler; - i2s_samples_ready_handler_t handlerEvent; + i2s_samples_ready_handler_t samples_ready_handler; } machine_i2s_obj_t; -typedef void (*i2s_samples_ready_handler_t)(void); - machine_i2s_obj_t* machine_i2s_make_new(uint8_t i2s_id, mp_hal_pin_obj_t sck, mp_hal_pin_obj_t ws, mp_hal_pin_obj_t sd, i2s_mode_t i2s_mode, int8_t i2s_bits, format_t i2s_format, int32_t ring_buffer_len, int32_t i2s_rate); // void machine_i2s_deinit(machine_i2s_obj_t *self); -void i2s_microphone_set_samples_ready_handler(i2s_samples_ready_handler_t handler); +void i2s_microphone_set_samples_ready(i2s_samples_ready_handler_t handler, uint8_t I2S_ID); +int machine_i2s_stream_read(machine_i2s_obj_t *self, void *buf_in, size_t size); -#endif +#endif \ No newline at end of file From 4ea87a16576340945b9aca56c8d9d56b374f1125 Mon Sep 17 00:00:00 2001 From: ThanhBinh_ITRVN Date: Tue, 5 Sep 2023 13:51:35 +0700 Subject: [PATCH 6/8] update code record --- examples/hello_i2s_microphone/CMakeLists.txt | 4 +- examples/hello_i2s_microphone/Wav.c | 102 +++++++++++++++++++ examples/hello_i2s_microphone/Wav.h | 4 + examples/hello_i2s_microphone/main.c | 21 ++-- src/i2s_microphone.c | 7 +- src/include/pico/i2s_microphone.h | 8 +- 6 files changed, 129 insertions(+), 17 deletions(-) create mode 100644 examples/hello_i2s_microphone/Wav.c create mode 100644 examples/hello_i2s_microphone/Wav.h diff --git a/examples/hello_i2s_microphone/CMakeLists.txt b/examples/hello_i2s_microphone/CMakeLists.txt index 8c6c476..3d2c81f 100644 --- a/examples/hello_i2s_microphone/CMakeLists.txt +++ b/examples/hello_i2s_microphone/CMakeLists.txt @@ -12,8 +12,8 @@ target_include_directories(hello_i2s_microphone PRIVATE ${CMAKE_CURRENT_LIST_DIR target_link_libraries(hello_i2s_microphone PRIVATE tinyusb_device tinyusb_board pico_i2s_microphone) # enable usb output, disable uart output -pico_enable_stdio_usb(hello_i2s_microphone 1) -# pico_enable_stdio_uart(hello_i2s_microphone 0) +# pico_enable_stdio_usb(hello_i2s_microphone 1) +pico_enable_stdio_uart(hello_i2s_microphone 1) # create map/bin/hex/uf2 file in addition to ELF. pico_add_extra_outputs(hello_i2s_microphone) diff --git a/examples/hello_i2s_microphone/Wav.c b/examples/hello_i2s_microphone/Wav.c new file mode 100644 index 0000000..994b0a6 --- /dev/null +++ b/examples/hello_i2s_microphone/Wav.c @@ -0,0 +1,102 @@ +#include "Wav.h" +#include "stdint.h" + +// MONO 16bit +void CreateWavHeader(uint8_t header[], int waveDataSize){ + header[0] = 'R'; + header[1] = 'I'; + header[2] = 'F'; + header[3] = 'F'; + unsigned int fileSizeMinus8 = waveDataSize + 44 - 8; + header[4] = (uint8_t)(fileSizeMinus8 & 0xFF); + header[5] = (uint8_t)((fileSizeMinus8 >> 8) & 0xFF); + header[6] = (uint8_t)((fileSizeMinus8 >> 16) & 0xFF); + header[7] = (uint8_t)((fileSizeMinus8 >> 24) & 0xFF); + header[8] = 'W'; + header[9] = 'A'; + header[10] = 'V'; + header[11] = 'E'; + header[12] = 'f'; + header[13] = 'm'; + header[14] = 't'; + header[15] = ' '; + header[16] = 0x10; // linear PCM + header[17] = 0x00; + header[18] = 0x00; + header[19] = 0x00; + header[20] = 0x01; // linear PCM + header[21] = 0x00; + header[22] = 0x01; // monoral + header[23] = 0x00; + header[24] = 0x80; // sampling rate 16000 + header[25] = 0x3E; + header[26] = 0x00; + header[27] = 0x00; + header[28] = 0x00; // Byte/sec = 16000x2x1 = 32000 + header[29] = 0x7D; + header[30] = 0x00; + header[31] = 0x00; + header[32] = 0x02; // 16bit Mono + header[33] = 0x00; + header[34] = 0x10; // 16bit + header[35] = 0x00; + header[36] = 'd'; + header[37] = 'a'; + header[38] = 't'; + header[39] = 'a'; + header[40] = (uint8_t)(waveDataSize & 0xFF); + header[41] = (uint8_t)((waveDataSize >> 8) & 0xFF); + header[42] = (uint8_t)((waveDataSize >> 16) & 0xFF); + header[43] = (uint8_t)((waveDataSize >> 24) & 0xFF); +} + +/* STEREO 32bit*/ +/* + void CreateWavHeader(uint8_t header[], int waveDataSize){ + header[0] = 'R'; + header[1] = 'I'; + header[2] = 'F'; + header[3] = 'F'; + unsigned int fileSizeMinus8 = waveDataSize + 44 - 8; + header[4] = (uint8_t)(fileSizeMinus8 & 0xFF); + header[5] = (uint8_t)((fileSizeMinus8 >> 8) & 0xFF); + header[6] = (uint8_t)((fileSizeMinus8 >> 16) & 0xFF); + header[7] = (uint8_t)((fileSizeMinus8 >> 24) & 0xFF); + header[8] = 'W'; + header[9] = 'A'; + header[10] = 'V'; + header[11] = 'E'; + header[12] = 'f'; + header[13] = 'm'; + header[14] = 't'; + header[15] = ' '; + header[16] = 0x10; // linear PCM + header[17] = 0x00; + header[18] = 0x00; + header[19] = 0x00; + header[20] = 0x01; // linear PCM + header[21] = 0x00; + header[22] = 0x02; // STEREO + header[23] = 0x00; + header[24] = 0x80; // sampling rate 16000 + header[25] = 0x3E; + header[26] = 0x00; + header[27] = 0x00; + header[28] = 0x00; // Byte/sec = 16000x4x1 = 64000 + header[29] = 0xFA; + header[30] = 0x00; + header[31] = 0x00; + header[32] = 0x04; // 32bit Stereo + header[33] = 0x00; + header[34] = 0x20; // 32bit + header[35] = 0x00; + header[36] = 'd'; + header[37] = 'a'; + header[38] = 't'; + header[39] = 'a'; + header[40] = (uint8_t)(waveDataSize & 0xFF); + header[41] = (uint8_t)((waveDataSize >> 8) & 0xFF); + header[42] = (uint8_t)((waveDataSize >> 16) & 0xFF); + header[43] = (uint8_t)((waveDataSize >> 24) & 0xFF); +} +*/ \ No newline at end of file diff --git a/examples/hello_i2s_microphone/Wav.h b/examples/hello_i2s_microphone/Wav.h new file mode 100644 index 0000000..a6a5796 --- /dev/null +++ b/examples/hello_i2s_microphone/Wav.h @@ -0,0 +1,4 @@ +#include "stdio.h" + +// 16bit, monoral, 16000Hz, linear PCM +void CreateWavHeader(unsigned char header[], int waveDataSize); // size of header is 44 \ No newline at end of file diff --git a/examples/hello_i2s_microphone/main.c b/examples/hello_i2s_microphone/main.c index 3096982..83e27b7 100644 --- a/examples/hello_i2s_microphone/main.c +++ b/examples/hello_i2s_microphone/main.c @@ -1,5 +1,4 @@ #include "stdio.h" - #include "tusb.h" #include "pico/stdlib.h" #include "pico/i2s_microphone.h" @@ -11,20 +10,20 @@ #define BPS 16 #define RATE 16000 #define BUTTON 15 -#define SIZE_APP_BUFFER SIZEOF_DMA_BUFFER_IN_BYTES/8 +#define SIZE_APP_BUFFER (SIZEOF_DMA_BUFFER_IN_BYTES/8) +uint32_t countByte = 0; uint32_t byteReads = 0; -int16_t sample_buffer[SIZE_APP_BUFFER]; +int16_t sample_buffer[SIZE_APP_BUFFER/2]; machine_i2s_obj_t* i2s0; uint8_t id_i2s = 0; //// Callback function I2S ///// void on_i2s_samples_ready(){ byteReads = machine_i2s_stream_read(i2s0, (void*)&sample_buffer, SIZE_APP_BUFFER); // app buffer len <= dma buffer len / 8 - printf("%d\r\n",byteReads); } -// Callback function USB /// +// Callback function USB // void on_usb_microphone_tx_ready(){ usb_microphone_write(sample_buffer, sizeof(sample_buffer)); } @@ -38,12 +37,16 @@ void init(){ int main(){ init(); + printf("START RECORD\r\n"); + printf("\r\n"); + printf("----- START DATA -----\r\n"); + sleep_ms(500); + i2s0 = machine_i2s_make_new(id_i2s, SCK, WS, SD, RX, BPS, MONO, - SIZEOF_DMA_BUFFER_IN_BYTES*2, // set ring buffer len >= dma buffer len + SIZEOF_DMA_BUFFER_IN_BYTES, // set ring buffer len >= dma buffer len RATE); - - printf("I2S OK \n\n"); + i2s_microphone_set_samples_ready(on_i2s_samples_ready, id_i2s); usb_microphone_init(); @@ -52,6 +55,6 @@ int main(){ while (1){ usb_microphone_task(); } - + return 0; } \ No newline at end of file diff --git a/src/i2s_microphone.c b/src/i2s_microphone.c index a06f562..b350d2e 100644 --- a/src/i2s_microphone.c +++ b/src/i2s_microphone.c @@ -125,7 +125,6 @@ STATIC const pio_program_t pio_read_32 = { STATIC uint8_t dma_get_bits(i2s_mode_t mode, int8_t bits); STATIC void dma_irq0_handler(void); STATIC void dma_irq1_handler(void); -STATIC void machine_i2s_deinit(machine_i2s_obj_t *self); // Ring Buffer // Thread safe when used with these constraints: @@ -531,7 +530,7 @@ STATIC int dma_configure(machine_i2s_obj_t *self) { dma_buffer, // dest = DMA buffer (void *)&self->pio->rxf[self->sm], // src = PIO RX FIFO SIZEOF_HALF_DMA_BUFFER_IN_BYTES / (dma_get_bits(self->mode, self->bits) / 8), - true); + false); } } @@ -654,7 +653,7 @@ STATIC int machine_i2s_init_helper(machine_i2s_obj_t *self, self->format = i2s_format; self->rate = i2s_rate; self->ibuf = ring_buffer_len; - self->io_mode = UASYNCIO; + self->io_mode = BLOCKING; irq_configure(self); int err = pio_configure(self); @@ -699,7 +698,7 @@ machine_i2s_obj_t* machine_i2s_make_new(uint8_t i2s_id, } -STATIC void machine_i2s_deinit(machine_i2s_obj_t *self) { +void machine_i2s_deinit(machine_i2s_obj_t *self) { // use self->pio as in indication that I2S object has already been de-initialized if (self->pio != NULL) { pio_deinit(self); diff --git a/src/include/pico/i2s_microphone.h b/src/include/pico/i2s_microphone.h index a8a247e..374bf44 100644 --- a/src/include/pico/i2s_microphone.h +++ b/src/include/pico/i2s_microphone.h @@ -83,9 +83,13 @@ typedef struct _machine_i2s_obj_t { i2s_samples_ready_handler_t samples_ready_handler; } machine_i2s_obj_t; -machine_i2s_obj_t* machine_i2s_make_new(uint8_t i2s_id, mp_hal_pin_obj_t sck, mp_hal_pin_obj_t ws, mp_hal_pin_obj_t sd, i2s_mode_t i2s_mode, int8_t i2s_bits, format_t i2s_format, int32_t ring_buffer_len, int32_t i2s_rate); +machine_i2s_obj_t* machine_i2s_make_new(uint8_t i2s_id, mp_hal_pin_obj_t sck, + mp_hal_pin_obj_t ws, mp_hal_pin_obj_t sd, + i2s_mode_t i2s_mode, int8_t i2s_bits, + format_t i2s_format, int32_t ring_buffer_len, + int32_t i2s_rate); -// void machine_i2s_deinit(machine_i2s_obj_t *self); +void machine_i2s_deinit(machine_i2s_obj_t *self); void i2s_microphone_set_samples_ready(i2s_samples_ready_handler_t handler, uint8_t I2S_ID); From e25abea56579c8cb5a04dc8582765cb6a22d3715 Mon Sep 17 00:00:00 2001 From: ThanhBinh_ITRVN Date: Tue, 5 Sep 2023 13:52:41 +0700 Subject: [PATCH 7/8] update code record --- examples/hello_i2s_microphone/Wav.c | 102 ---------------------------- examples/hello_i2s_microphone/Wav.h | 4 -- 2 files changed, 106 deletions(-) delete mode 100644 examples/hello_i2s_microphone/Wav.c delete mode 100644 examples/hello_i2s_microphone/Wav.h diff --git a/examples/hello_i2s_microphone/Wav.c b/examples/hello_i2s_microphone/Wav.c deleted file mode 100644 index 994b0a6..0000000 --- a/examples/hello_i2s_microphone/Wav.c +++ /dev/null @@ -1,102 +0,0 @@ -#include "Wav.h" -#include "stdint.h" - -// MONO 16bit -void CreateWavHeader(uint8_t header[], int waveDataSize){ - header[0] = 'R'; - header[1] = 'I'; - header[2] = 'F'; - header[3] = 'F'; - unsigned int fileSizeMinus8 = waveDataSize + 44 - 8; - header[4] = (uint8_t)(fileSizeMinus8 & 0xFF); - header[5] = (uint8_t)((fileSizeMinus8 >> 8) & 0xFF); - header[6] = (uint8_t)((fileSizeMinus8 >> 16) & 0xFF); - header[7] = (uint8_t)((fileSizeMinus8 >> 24) & 0xFF); - header[8] = 'W'; - header[9] = 'A'; - header[10] = 'V'; - header[11] = 'E'; - header[12] = 'f'; - header[13] = 'm'; - header[14] = 't'; - header[15] = ' '; - header[16] = 0x10; // linear PCM - header[17] = 0x00; - header[18] = 0x00; - header[19] = 0x00; - header[20] = 0x01; // linear PCM - header[21] = 0x00; - header[22] = 0x01; // monoral - header[23] = 0x00; - header[24] = 0x80; // sampling rate 16000 - header[25] = 0x3E; - header[26] = 0x00; - header[27] = 0x00; - header[28] = 0x00; // Byte/sec = 16000x2x1 = 32000 - header[29] = 0x7D; - header[30] = 0x00; - header[31] = 0x00; - header[32] = 0x02; // 16bit Mono - header[33] = 0x00; - header[34] = 0x10; // 16bit - header[35] = 0x00; - header[36] = 'd'; - header[37] = 'a'; - header[38] = 't'; - header[39] = 'a'; - header[40] = (uint8_t)(waveDataSize & 0xFF); - header[41] = (uint8_t)((waveDataSize >> 8) & 0xFF); - header[42] = (uint8_t)((waveDataSize >> 16) & 0xFF); - header[43] = (uint8_t)((waveDataSize >> 24) & 0xFF); -} - -/* STEREO 32bit*/ -/* - void CreateWavHeader(uint8_t header[], int waveDataSize){ - header[0] = 'R'; - header[1] = 'I'; - header[2] = 'F'; - header[3] = 'F'; - unsigned int fileSizeMinus8 = waveDataSize + 44 - 8; - header[4] = (uint8_t)(fileSizeMinus8 & 0xFF); - header[5] = (uint8_t)((fileSizeMinus8 >> 8) & 0xFF); - header[6] = (uint8_t)((fileSizeMinus8 >> 16) & 0xFF); - header[7] = (uint8_t)((fileSizeMinus8 >> 24) & 0xFF); - header[8] = 'W'; - header[9] = 'A'; - header[10] = 'V'; - header[11] = 'E'; - header[12] = 'f'; - header[13] = 'm'; - header[14] = 't'; - header[15] = ' '; - header[16] = 0x10; // linear PCM - header[17] = 0x00; - header[18] = 0x00; - header[19] = 0x00; - header[20] = 0x01; // linear PCM - header[21] = 0x00; - header[22] = 0x02; // STEREO - header[23] = 0x00; - header[24] = 0x80; // sampling rate 16000 - header[25] = 0x3E; - header[26] = 0x00; - header[27] = 0x00; - header[28] = 0x00; // Byte/sec = 16000x4x1 = 64000 - header[29] = 0xFA; - header[30] = 0x00; - header[31] = 0x00; - header[32] = 0x04; // 32bit Stereo - header[33] = 0x00; - header[34] = 0x20; // 32bit - header[35] = 0x00; - header[36] = 'd'; - header[37] = 'a'; - header[38] = 't'; - header[39] = 'a'; - header[40] = (uint8_t)(waveDataSize & 0xFF); - header[41] = (uint8_t)((waveDataSize >> 8) & 0xFF); - header[42] = (uint8_t)((waveDataSize >> 16) & 0xFF); - header[43] = (uint8_t)((waveDataSize >> 24) & 0xFF); -} -*/ \ No newline at end of file diff --git a/examples/hello_i2s_microphone/Wav.h b/examples/hello_i2s_microphone/Wav.h deleted file mode 100644 index a6a5796..0000000 --- a/examples/hello_i2s_microphone/Wav.h +++ /dev/null @@ -1,4 +0,0 @@ -#include "stdio.h" - -// 16bit, monoral, 16000Hz, linear PCM -void CreateWavHeader(unsigned char header[], int waveDataSize); // size of header is 44 \ No newline at end of file From a3c47cd0bd1eb61df43136f6a18a943a2441ffbb Mon Sep 17 00:00:00 2001 From: ThanhBinh_ITRVN Date: Tue, 5 Sep 2023 14:39:11 +0700 Subject: [PATCH 8/8] update readme --- README.md | 104 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 66 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index f671b47..64eed21 100644 --- a/README.md +++ b/README.md @@ -2,65 +2,83 @@ Capture audio from a microphone on your [Raspberry Pi Pico](https://www.raspberrypi.org/products/raspberry-pi-pico/) or any [RP2040](https://www.raspberrypi.org/products/rp2040/) based board. 🎤 - ## Hardware - * RP2040 board - * [Raspberry Pi Pico](https://www.raspberrypi.org/products/raspberry-pi-pico/) - * Microphones - * Analog - * [Electret Microphone Amplifier - MAX9814 with Auto Gain Control](https://www.adafruit.com/product/1713) - * PDM - * [Adafruit PDM MEMS Microphone Breakout](https://www.adafruit.com/product/3492) +* RP2040 board + + * [Raspberry Pi Pico](https://www.raspberrypi.org/products/raspberry-pi-pico/) + +* Microphones + + * I2S + + * [INMP441 I2S Omnidirectional Microphone](https://hshop.vn/products/cam-bien-am-thanh-inmp441-i2s-omnidirectional-microphone) + + * Analog + + * [Electret Microphone Amplifier - MAX9814 with Auto Gain Control](https://www.adafruit.com/product/1713) + + * PDM + + * [Adafruit PDM MEMS Microphone Breakout](https://www.adafruit.com/product/3492) ### Default Pinout +#### I2S Microphone + +| Raspberry Pi Pico / RP2040 | Analog Microphone | +| -------------------------- | ----------------- | +| 3.3V | VCC | +| GND | GND | +| GPIO 2 | SCK | +| GPIO 3 | WS | +| GPIO 6 | SD | +| GND | L/R | + #### Analog Microphone | Raspberry Pi Pico / RP2040 | Analog Microphone | | -------------------------- | ----------------- | -| 3.3V | VCC | -| GND | GND | -| GPIO 26 | OUT | +| 3.3V | VCC | +| GND | GND | +| GPIO 26 | OUT | #### PDM Microphone | Raspberry Pi Pico / RP2040 | PDM Microphone | -| -------------------------- | ----------------- | -| 3.3V | VCC | -| GND | GND | -| GND | SEL | -| GPIO 2 | DAT | -| GPIO 3 | CLK | +| -------------------------- | -------------- | +| 3.3V | VCC | +| GND | GND | +| GND | SEL | +| GPIO 2 | DAT | +| GPIO 3 | CLK | GPIO pins are configurable in examples or API. ## Examples -See [examples](examples/) folder. +[Git Examples about microphone](https://github.com/dattran-itrvn/microphone-library-for-pico/tree/Binh_Dev) +#### Set up Pico SDK -## Cloning +[Set up the Pico C/C++ SDK](https://datasheets.raspberrypi.org/pico/getting-started-with-pico.pdf) -```sh -git clone https://github.com/ArmDeveloperEcosystem/microphone-library-for-pico.git -``` +1. Set `PICO_SDK_PATH` + + ```sh + export PICO_SDK_PATH=/path/to/pico-sdk + ``` -## Building +2. Create `build` dir, run `cmake` and `make`: + + ``` + mkdir build + cd build + cmake .. -DPICO_BOARD=pico + make + ``` -1. [Set up the Pico C/C++ SDK](https://datasheets.raspberrypi.org/pico/getting-started-with-pico.pdf) -2. Set `PICO_SDK_PATH` -```sh -export PICO_SDK_PATH=/path/to/pico-sdk -``` -3. Create `build` dir, run `cmake` and `make`: -``` -mkdir build -cd build -cmake .. -DPICO_BOARD=pico -make -``` -4. Copy example `.uf2` to Pico when in BOOT mode. +3. Copy example `.uf2` to Pico when in BOOT mode. ## License @@ -68,10 +86,20 @@ make ## Acknowledgements -This project was created on behalf of the [Arm Software Developers](https://developer.arm.com/) team, follow them on Twitter: [@ArmSoftwareDev](https://twitter.com/armsoftwaredev) and YouTube: [Arm Software Developers](https://www.youtube.com/channel/UCHUAckhCfRom2EHDGxwhfOg) for more resources! +##### - Mic Analog or Mic PDM The [OpenPDM2PCM](https://os.mbed.com/teams/ST/code/X_NUCLEO_CCA02M1//file/53f8b511f2a1/Middlewares/OpenPDM2PCM/) library is used to filter raw PDM data into PCM. The [TinyUSB](https://github.com/hathach/tinyusb) library is used in the `usb_microphone` example. --- -Disclaimer: This is not an official Arm product. +##### - Mic I2S + +Get data raw using I2S protocol and using algorithm fill buffer to decode audio. + +Buffer App = Buffer Ring / 4 = Buffer DMA / 8. + +To record data, using [TinyUSB]([GitHub - hathach/tinyusb: An open source cross-platform USB stack for embedded system](https://github.com/hathach/tinyusb)) library is used in the `usb_microphone` example. Buffer output <= 32 bytes <=> 256 bytes in DMA buffer + +Using [Audacity software]([Download | Audacity ®](https://www.audacityteam.org/download/) get audio output + +---