diff --git a/CMakeLists.txt b/CMakeLists.txt index 230b75d..693426e 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/i2s_microphone.c +) + +target_include_directories(pico_i2s_microphone INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/src +) + +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/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..6c396d9 --- /dev/null +++ b/examples/usb_i2s_microphone/main.c @@ -0,0 +1,59 @@ +/** + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "pico/stdlib.h" +#include "i2s_microphone.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]/5000; // 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/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