From 73e3d94ff1bac1351fdac83112941cf033f7aded Mon Sep 17 00:00:00 2001 From: Winonymous Date: Sat, 25 Mar 2023 23:30:19 +0100 Subject: [PATCH 1/3] First Commit --- CMakeLists.txt | 15 +++++++++++++++ src/include/pico/analog_microphone.h | 11 +++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 230b75d..1a3483d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,21 @@ target_include_directories(pico_analog_microphone INTERFACE target_link_libraries(pico_analog_microphone INTERFACE pico_stdlib hardware_adc hardware_dma) +add_library(pico_i2s_microphone INTERFACE) + +target_sources(pico_i2s_microphone INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/src/include/analog_microphone.c +) + +target_include_directories(pico_i2s_microphone INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/src/include +) + +target_link_libraries(pico_i2s_microphone INTERFACE pico_stdlib hardware_dma hardware_pio hardware_irq pico_multicore) + add_subdirectory("examples/hello_analog_microphone") add_subdirectory("examples/hello_pdm_microphone") add_subdirectory("examples/usb_microphone") +add_subdirectory("examples/analog_usb_microphone") +add_subdirectory("examples/usb_i2s_microphone") + diff --git a/src/include/pico/analog_microphone.h b/src/include/pico/analog_microphone.h index 31b9234..40ff88a 100644 --- a/src/include/pico/analog_microphone.h +++ b/src/include/pico/analog_microphone.h @@ -8,13 +8,16 @@ #ifndef _PICO_ANALOG_MICROPHONE_H_ #define _PICO_ANALOG_MICROPHONE_H_ +// #include "hardware/pio.h" +#include + typedef void (*analog_samples_ready_handler_t)(void); struct analog_microphone_config { - uint gpio; + unsigned int gpio; float bias_voltage; - uint sample_rate; - uint sample_buffer_size; + unsigned int sample_rate; + unsigned int sample_buffer_size; }; int analog_microphone_init(const struct analog_microphone_config* config); @@ -25,6 +28,6 @@ void analog_microphone_stop(); void analog_microphone_set_samples_ready_handler(analog_samples_ready_handler_t handler); -int analog_microphone_read(int16_t* buffer, size_t samples); +int analog_microphone_read(signed short* buffer, size_t samples); #endif From c6e249db61356f6653fc244deec3af0ea1c86dbd Mon Sep 17 00:00:00 2001 From: Winonymous Date: Sat, 25 Mar 2023 23:50:19 +0100 Subject: [PATCH 2/3] Actual First Commit --- examples/analog_usb_microphone/CMakeLists.txt | 15 + examples/analog_usb_microphone/main.c | 74 ++ examples/analog_usb_microphone/tusb_config.h | 111 +++ .../analog_usb_microphone/usb_descriptors.c | 160 ++++ .../analog_usb_microphone/usb_microphone.c | 338 ++++++++ .../analog_usb_microphone/usb_microphone.h | 28 + examples/usb_i2s_microphone/CMakeLists.txt | 15 + examples/usb_i2s_microphone/main.c | 59 ++ examples/usb_i2s_microphone/tusb_config.h | 111 +++ examples/usb_i2s_microphone/usb_descriptors.c | 160 ++++ examples/usb_i2s_microphone/usb_microphone.c | 338 ++++++++ examples/usb_i2s_microphone/usb_microphone.h | 28 + src/include/pico/machine_i2s.c | 798 ++++++++++++++++++ 13 files changed, 2235 insertions(+) create mode 100644 examples/analog_usb_microphone/CMakeLists.txt create mode 100644 examples/analog_usb_microphone/main.c create mode 100644 examples/analog_usb_microphone/tusb_config.h create mode 100644 examples/analog_usb_microphone/usb_descriptors.c create mode 100644 examples/analog_usb_microphone/usb_microphone.c create mode 100644 examples/analog_usb_microphone/usb_microphone.h create mode 100644 examples/usb_i2s_microphone/CMakeLists.txt create mode 100644 examples/usb_i2s_microphone/main.c create mode 100644 examples/usb_i2s_microphone/tusb_config.h create mode 100644 examples/usb_i2s_microphone/usb_descriptors.c create mode 100644 examples/usb_i2s_microphone/usb_microphone.c create mode 100644 examples/usb_i2s_microphone/usb_microphone.h create mode 100644 src/include/pico/machine_i2s.c diff --git a/examples/analog_usb_microphone/CMakeLists.txt b/examples/analog_usb_microphone/CMakeLists.txt new file mode 100644 index 0000000..7be43fa --- /dev/null +++ b/examples/analog_usb_microphone/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.12) + +# rest of your project +add_executable(usb_analog_microphone + main.c + usb_descriptors.c + usb_microphone.c +) + +target_include_directories(usb_analog_microphone PRIVATE ${CMAKE_CURRENT_LIST_DIR}) + +target_link_libraries(usb_analog_microphone PRIVATE tinyusb_device tinyusb_board pico_analog_microphone) + +# create map/bin/hex/uf2 file in addition to ELF. +pico_add_extra_outputs(usb_analog_microphone) diff --git a/examples/analog_usb_microphone/main.c b/examples/analog_usb_microphone/main.c new file mode 100644 index 0000000..5b2f7c2 --- /dev/null +++ b/examples/analog_usb_microphone/main.c @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * This examples creates a USB Microphone device using the TinyUSB + * library and captures data from a PDM microphone using a sample + * rate of 16 kHz, to be sent the to PC. + * + * The USB microphone code is based on the TinyUSB audio_test example. + * + * https://github.com/hathach/tinyusb/tree/master/examples/device/audio_test + */ + +#include "pico/analog_microphone.h" + +#include "usb_microphone.h" + +// configuration +const struct analog_microphone_config config = { + // GPIO to use for input, must be ADC compatible (GPIO 26 - 28) + .gpio = 26, + + // bias voltage of microphone in volts + .bias_voltage = 1.25, + + // sample rate in Hz + .sample_rate = 8000, + + // number of samples to buffer + .sample_buffer_size = SAMPLE_BUFFER_SIZE, +}; + +// variables +uint16_t sample_buffer[SAMPLE_BUFFER_SIZE]; + +// callback functions +void on_analog_samples_ready(); +void on_usb_microphone_tx_ready(); + +int main(void) +{ + // initialize and start the PDM microphone + analog_microphone_init(&config); + analog_microphone_set_samples_ready_handler(on_analog_samples_ready); + analog_microphone_start(); + + // initialize the USB microphone interface + usb_microphone_init(); + usb_microphone_set_tx_ready_handler(on_usb_microphone_tx_ready); + + while (1) { + // run the USB microphone task continuously + usb_microphone_task(); + } + + return 0; +} + +void on_analog_samples_ready() +{ + // callback from library when all the samples in the library + // internal sample buffer are ready for reading + analog_microphone_read(sample_buffer, 256); +} + +void on_usb_microphone_tx_ready() +{ + // Callback from TinyUSB library when all data is ready + // to be transmitted. + // + // Write local buffer to the USB microphone + usb_microphone_write(sample_buffer, sizeof(sample_buffer)); +} diff --git a/examples/analog_usb_microphone/tusb_config.h b/examples/analog_usb_microphone/tusb_config.h new file mode 100644 index 0000000..21f52d1 --- /dev/null +++ b/examples/analog_usb_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/analog_usb_microphone/usb_descriptors.c b/examples/analog_usb_microphone/usb_descriptors.c new file mode 100644 index 0000000..a4e9dc8 --- /dev/null +++ b/examples/analog_usb_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/analog_usb_microphone/usb_microphone.h b/examples/analog_usb_microphone/usb_microphone.h new file mode 100644 index 0000000..cf45b9c --- /dev/null +++ b/examples/analog_usb_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/examples/usb_i2s_microphone/CMakeLists.txt b/examples/usb_i2s_microphone/CMakeLists.txt new file mode 100644 index 0000000..709826f --- /dev/null +++ b/examples/usb_i2s_microphone/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.12) + +# rest of your project +add_executable(usb_i2s_microphone + main.c + usb_descriptors.c + usb_microphone.c +) + +target_include_directories(usb_i2s_microphone PRIVATE ${CMAKE_CURRENT_LIST_DIR}) + +target_link_libraries(usb_i2s_microphone PRIVATE tinyusb_device tinyusb_board pico_i2s_microphone) + +# create map/bin/hex/uf2 file in addition to ELF. +pico_add_extra_outputs(usb_i2s_microphone) diff --git a/examples/usb_i2s_microphone/main.c b/examples/usb_i2s_microphone/main.c new file mode 100644 index 0000000..33a39f2 --- /dev/null +++ b/examples/usb_i2s_microphone/main.c @@ -0,0 +1,59 @@ +/** + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "pico/stdlib.h" +#include "pico/machine_i2s.c" +#include "pico/multicore.h" + +#include "usb_microphone.h" + +#define SCK 3 +#define WS 4 // needs to be SCK +1 +#define SD 6 +#define BPS 32 // 24 is not valid in this implementation, but INMP441 outputs 24 bits samples +#define RATE 16000 + +// variables +uint16_t sample_buffer[SAMPLE_BUFFER_SIZE]; + +// callback functions +void on_usb_microphone_tx_ready(); + +void core1_entry() { + + machine_i2s_obj_t* i2s0 = machine_i2s_make_new(0, SCK, WS, SD, RX, BPS, MONO, /*ringbuf_len*/SIZEOF_DMA_BUFFER_IN_BYTES, RATE); + int32_t buffer[I2S_RX_FRAME_SIZE_IN_BYTES /4]; + + while (1){ + // // run the USB microphone task continuously + for(int i = 0; i < SAMPLE_BUFFER_SIZE; i++) { + machine_i2s_stream_read(i2s0, (void*)&buffer[0], I2S_RX_FRAME_SIZE_IN_BYTES); + sample_buffer[i] = buffer[0]/2000; // right channel is empty, play using $ cat /dev/ttyACM0 | xxd -r -p | aplay -r16000 -c1 -fS32_BE + } + } +} + +int main() { + stdio_init_all(); + + // initialize the USB microphone interface + usb_microphone_init(); + usb_microphone_set_tx_ready_handler(on_usb_microphone_tx_ready); + + multicore_launch_core1(core1_entry); + + while (1) { + + + usb_microphone_task(); + } +} + +void on_usb_microphone_tx_ready() +{ + // Callback from TinyUSB library when all data is ready + // to be transmitted. + // + // Write local buffer to the USB microphone + usb_microphone_write(sample_buffer, sizeof(sample_buffer)); +} diff --git a/examples/usb_i2s_microphone/tusb_config.h b/examples/usb_i2s_microphone/tusb_config.h new file mode 100644 index 0000000..21f52d1 --- /dev/null +++ b/examples/usb_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/usb_i2s_microphone/usb_descriptors.c b/examples/usb_i2s_microphone/usb_descriptors.c new file mode 100644 index 0000000..a4e9dc8 --- /dev/null +++ b/examples/usb_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/usb_i2s_microphone/usb_microphone.h b/examples/usb_i2s_microphone/usb_microphone.h new file mode 100644 index 0000000..cf45b9c --- /dev/null +++ b/examples/usb_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/include/pico/machine_i2s.c b/src/include/pico/machine_i2s.c new file mode 100644 index 0000000..f1c97fa --- /dev/null +++ b/src/include/pico/machine_i2s.c @@ -0,0 +1,798 @@ +/* + 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 "hardware/pio.h" +#include "hardware/clocks.h" +#include "hardware/gpio.h" +#include "hardware/dma.h" +#include "hardware/irq.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 (256) +#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 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; + +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; +} machine_i2s_obj_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; + +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), + false); + } + } + + 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); + } +} + +STATIC void dma_irq0_handler(void) { + dma_irq_handler(0); +} + +STATIC void dma_irq1_handler(void) { + dma_irq_handler(1); +} + + +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) { + printf("Here"); + 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; +} From da04c133d4508d209d6272fa26ac32253bb79c19 Mon Sep 17 00:00:00 2001 From: Winonymous Date: Sun, 26 Mar 2023 11:39:24 +0100 Subject: [PATCH 3/3] Everything is working now --- CMakeLists.txt | 4 +- examples/usb_i2s_microphone/main.c | 4 +- src/include/pico/machine_i2s.c | 798 ----------------------------- 3 files changed, 4 insertions(+), 802 deletions(-) delete mode 100644 src/include/pico/machine_i2s.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a3483d..693426e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,11 +44,11 @@ target_link_libraries(pico_analog_microphone INTERFACE pico_stdlib hardware_adc add_library(pico_i2s_microphone INTERFACE) target_sources(pico_i2s_microphone INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/src/include/analog_microphone.c + ${CMAKE_CURRENT_LIST_DIR}/src/i2s_microphone.c ) target_include_directories(pico_i2s_microphone INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/src/include + ${CMAKE_CURRENT_LIST_DIR}/src ) target_link_libraries(pico_i2s_microphone INTERFACE pico_stdlib hardware_dma hardware_pio hardware_irq pico_multicore) diff --git a/examples/usb_i2s_microphone/main.c b/examples/usb_i2s_microphone/main.c index 33a39f2..6c396d9 100644 --- a/examples/usb_i2s_microphone/main.c +++ b/examples/usb_i2s_microphone/main.c @@ -2,7 +2,7 @@ * SPDX-License-Identifier: BSD-3-Clause */ #include "pico/stdlib.h" -#include "pico/machine_i2s.c" +#include "i2s_microphone.c" #include "pico/multicore.h" #include "usb_microphone.h" @@ -28,7 +28,7 @@ void core1_entry() { // // run the USB microphone task continuously for(int i = 0; i < SAMPLE_BUFFER_SIZE; i++) { machine_i2s_stream_read(i2s0, (void*)&buffer[0], I2S_RX_FRAME_SIZE_IN_BYTES); - sample_buffer[i] = buffer[0]/2000; // right channel is empty, play using $ cat /dev/ttyACM0 | xxd -r -p | aplay -r16000 -c1 -fS32_BE + sample_buffer[i] = buffer[0]/5000; // right channel is empty, play using $ cat /dev/ttyACM0 | xxd -r -p | aplay -r16000 -c1 -fS32_BE } } } diff --git a/src/include/pico/machine_i2s.c b/src/include/pico/machine_i2s.c deleted file mode 100644 index f1c97fa..0000000 --- a/src/include/pico/machine_i2s.c +++ /dev/null @@ -1,798 +0,0 @@ -/* - 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 "hardware/pio.h" -#include "hardware/clocks.h" -#include "hardware/gpio.h" -#include "hardware/dma.h" -#include "hardware/irq.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 (256) -#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 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; - -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; -} machine_i2s_obj_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; - -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), - false); - } - } - - 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); - } -} - -STATIC void dma_irq0_handler(void) { - dma_irq_handler(0); -} - -STATIC void dma_irq1_handler(void) { - dma_irq_handler(1); -} - - -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) { - printf("Here"); - 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; -}