Quellcode durchsuchen

Add 4 channel microphone audio example using software encoding

Reinhard Panhuber vor 4 Jahren
Ursprung
Commit
930eca0748

+ 0 - 0
examples/device/audio_4_channel_mic/.skip.MCU_SAMD11


+ 0 - 0
examples/device/audio_4_channel_mic/.skip.MCU_SAME5X


+ 0 - 0
examples/device/audio_4_channel_mic/.skip.MCU_SAMG


+ 39 - 0
examples/device/audio_4_channel_mic/CMakeLists.txt

@@ -0,0 +1,39 @@
+# use BOARD-Directory name for project id
+get_filename_component(PROJECT ${CMAKE_CURRENT_SOURCE_DIR} NAME)
+set(PROJECT ${BOARD}-${PROJECT})
+
+# TOP is absolute path to root directory of TinyUSB git repo
+set(TOP "../../..")
+get_filename_component(TOP "${TOP}" REALPATH)
+
+# Check for -DFAMILY=
+if(FAMILY STREQUAL "rp2040")
+  cmake_minimum_required(VERSION 3.12)
+  
+  set(PICO_SDK_PATH ${TOP}/hw/mcu/raspberrypi/pico-sdk)
+  include(${PICO_SDK_PATH}/pico_sdk_init.cmake)
+
+  project(${PROJECT})
+  add_executable(${PROJECT})
+
+  include(${TOP}/hw/bsp/${FAMILY}/family.cmake)
+
+  # Example source
+  target_sources(${PROJECT} PUBLIC
+    ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
+    ${CMAKE_CURRENT_SOURCE_DIR}/src/usb_descriptors.c
+  )
+
+  # Example include
+  target_include_directories(${PROJECT} PUBLIC
+    ${CMAKE_CURRENT_SOURCE_DIR}/src
+  )
+
+  # Example defines
+  target_compile_definitions(${PROJECT} PUBLIC
+    CFG_TUSB_OS=OPT_OS_PICO
+  )
+
+else()
+  message(FATAL_ERROR "Invalid FAMILY specified")
+endif()

+ 12 - 0
examples/device/audio_4_channel_mic/Makefile

@@ -0,0 +1,12 @@
+include ../../../tools/top.mk
+include ../../make.mk
+
+INC += \
+  src \
+  $(TOP)/hw \
+
+# Example source
+EXAMPLE_SOURCE += $(wildcard src/*.c)
+SRC_C += $(addprefix $(CURRENT_PATH)/, $(EXAMPLE_SOURCE))
+
+include ../../rules.mk

+ 451 - 0
examples/device/audio_4_channel_mic/src/main.c

@@ -0,0 +1,451 @@
+/* 
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2020 Reinhard Panhuber
+ *
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "bsp/board.h"
+#include "tusb.h"
+
+//--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF PROTYPES
+//--------------------------------------------------------------------+
+
+#ifndef AUDIO_SAMPLE_RATE
+#define AUDIO_SAMPLE_RATE   48000
+#endif
+
+/* Blink pattern
+ * - 250 ms  : device not mounted
+ * - 1000 ms : device mounted
+ * - 2500 ms : device is suspended
+ */
+enum  {
+  BLINK_NOT_MOUNTED = 250,
+  BLINK_MOUNTED = 1000,
+  BLINK_SUSPENDED = 2500,
+};
+
+static uint32_t blink_interval_ms = BLINK_NOT_MOUNTED;
+
+// Audio controls
+// Current states
+bool mute[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX + 1]; 				          // +1 for master channel 0
+uint16_t volume[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX + 1]; 					// +1 for master channel 0
+uint32_t sampFreq;
+uint8_t clkValid;
+
+// Range states
+audio_control_range_2_n_t(1) volumeRng[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX+1]; 			// Volume range state
+audio_control_range_4_n_t(1) sampleFreqRng; 						// Sample frequency range state
+
+// Audio test data
+uint8_t i2s_dummy_buffer[CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ];
+
+void led_blinking_task(void);
+void audio_task(void);
+
+/*------------- MAIN -------------*/
+int main(void)
+{
+  board_init();
+
+  tusb_init();
+
+  // Init values
+  sampFreq = AUDIO_SAMPLE_RATE;
+  clkValid = 1;
+
+  sampleFreqRng.wNumSubRanges = 1;
+  sampleFreqRng.subrange[0].bMin = AUDIO_SAMPLE_RATE;
+  sampleFreqRng.subrange[0].bMax = AUDIO_SAMPLE_RATE;
+  sampleFreqRng.subrange[0].bRes = 0;
+
+  while (1)
+  {
+    tud_task(); // tinyusb device task
+    led_blinking_task();
+    audio_task();
+  }
+
+
+  return 0;
+}
+
+//--------------------------------------------------------------------+
+// Device callbacks
+//--------------------------------------------------------------------+
+
+// Invoked when device is mounted
+void tud_mount_cb(void)
+{
+  blink_interval_ms = BLINK_MOUNTED;
+}
+
+// Invoked when device is unmounted
+void tud_umount_cb(void)
+{
+  blink_interval_ms = BLINK_NOT_MOUNTED;
+}
+
+// Invoked when usb bus is suspended
+// remote_wakeup_en : if host allow us  to perform remote wakeup
+// Within 7ms, device must draw an average of current less than 2.5 mA from bus
+void tud_suspend_cb(bool remote_wakeup_en)
+{
+  (void) remote_wakeup_en;
+  blink_interval_ms = BLINK_SUSPENDED;
+}
+
+// Invoked when usb bus is resumed
+void tud_resume_cb(void)
+{
+  blink_interval_ms = BLINK_MOUNTED;
+}
+
+//--------------------------------------------------------------------+
+// AUDIO Task
+//--------------------------------------------------------------------+
+
+void audio_task(void)
+{
+  // Yet to be filled - e.g. put meas data into TX FIFOs etc.
+  asm("nop");
+}
+
+//--------------------------------------------------------------------+
+// Application Callback API Implementations
+//--------------------------------------------------------------------+
+
+// Invoked when audio class specific set request received for an EP
+bool tud_audio_set_req_ep_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 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));
+      }
+      break;
+
+        // 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;
+        }
+      break;
+
+        // 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;
+        }
+      break;
+
+      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;
+
+  for (uint8_t cnt=0; cnt < CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO; cnt++)
+  {
+    tud_audio_write_support_ff(cnt, i2s_dummy_buffer[cnt], AUDIO_SAMPLE_RATE/1000 * CFG_TUD_AUDIO_FUNC_1_CHANNEL_PER_FIFO_TX);
+  }
+
+  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;
+
+  uint16_t dataVal;
+
+  // Generate dummy data
+  for (uint16_t cnt = 0; cnt < CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO; cnt++)
+  {
+    uint16_t * p_buff = (uint16_t *) i2s_dummy_buffer[cnt];           // 2 bytes per sample
+    dataVal = 1;
+    for (uint16_t cnt2 = 0; cnt2 < AUDIO_SAMPLE_RATE/1000; cnt2++)
+    {
+      for (uint8_t cnt3 = 0; cnt3 < CFG_TUD_AUDIO_FUNC_1_CHANNEL_PER_FIFO_TX; cnt++)
+      {
+        *p_buff++ = dataVal;
+      }
+      dataVal++;
+    }
+  }
+  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;
+  startVal = 0;
+
+  return true;
+}
+
+//--------------------------------------------------------------------+
+// BLINKING TASK
+//--------------------------------------------------------------------+
+void led_blinking_task(void)
+{
+  static uint32_t start_ms = 0;
+  static bool led_state = false;
+
+  // Blink every interval ms
+  if ( board_millis() - start_ms < blink_interval_ms) return; // not enough time
+  start_ms += blink_interval_ms;
+
+  board_led_write(led_state);
+  led_state = 1 - led_state; // toggle
+}

+ 26 - 0
examples/device/audio_4_channel_mic/src/plot_audio_samples.py

@@ -0,0 +1,26 @@
+import sounddevice as sd
+import matplotlib.pyplot as plt
+import numpy as np
+
+if __name__ == '__main__':
+
+  #  Currently tested for Windows - on Linux try to find the device or use default device if not clearly listed
+  #  devList = sd.query_devices()
+  #  print(devList)
+
+    fs = 48000  												# Sample rate
+    duration = 100e-3                     						# Duration of recording
+    device = 'Microphone (MicNode_4_Ch), Windows WDM-KS' 		# WDM-KS is needed since there are more than one MicNode device APIs (at least in Windows)
+
+    myrecording = sd.rec(int(duration * fs), samplerate=fs, channels=4, dtype='int16', device=device)
+    print('Waiting...')
+    sd.wait()  # Wait until recording is finished
+    print('Done!')
+
+    time = np.arange(0, duration, 1 / fs)  # time vector
+    plt.plot(time, myrecording)
+    plt.xlabel('Time [s]')
+    plt.ylabel('Amplitude')
+    plt.title('MicNode 4 Channel')
+    plt.show()
+    

+ 118 - 0
examples/device/audio_4_channel_mic/src/tusb_config.h

@@ -0,0 +1,118 @@
+/*
+ * 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_FOUR_CH_DESC_LEN
+
+#define CFG_TUD_AUDIO_FUNC_1_N_AS_INT                                 1
+#define CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ                              64
+
+#define CFG_TUD_AUDIO_ENABLE_EP_IN                                    1
+#define CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX                    2         // This value is not required by the driver, it parses this information from the descriptor once the alternate interface is set by the host - we use it for the setup
+#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX                            4         // This value is not required by the driver, it parses this information from the descriptor once the alternate interface is set by the host - we use it for the setup
+#define CFG_TUD_AUDIO_EP_SZ_IN                                        (48 + 1) * CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX * CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX      // 48 Samples (48 kHz) x 2 Bytes/Sample x CFG_TUD_AUDIO_N_CHANNELS_TX Channels - the Windows driver always needs an extra sample per channel of space more, otherwise it complains... found by trial and error
+#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX                             CFG_TUD_AUDIO_EP_SZ_IN
+#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ                          CFG_TUD_AUDIO_EP_SZ_IN
+
+#define CFG_TUD_AUDIO_ENABLE_ENCODING                                 1
+#define CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING                          1
+#define CFG_TUD_AUDIO_FUNC_1_CHANNEL_PER_FIFO_TX                      2         // One I2S stream contains two channels, each stream is saved within one support FIFO - this value is currently fixed, the driver does not support a changing value
+#define CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO                        (CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX / CFG_TUD_AUDIO_FUNC_1_CHANNEL_PER_FIFO_TX)
+#define CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ                       (CFG_TUD_AUDIO_EP_SZ_IN / CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _TUSB_CONFIG_H_ */

+ 160 - 0
examples/device/audio_4_channel_mic/src/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_FOUR_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, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
+
+    // Interface number, string index, EP Out & EP In address, EP size
+    TUD_AUDIO_MIC_FOUR_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_4_Ch",    		// 2: Product
+    "123458",                      	// 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; i<chr_count; i++)
+    {
+      _desc_str[1+i] = str[i];
+    }
+  }
+
+  // first byte is length (including header), second byte is string type
+  _desc_str[0] = (TUSB_DESC_STRING << 8 ) | (2*chr_count + 2);
+
+  return _desc_str;
+}