Merge pull request #2328 from HiFiPhile/rx_fb

UAC2: Implement feedback by fifo counting.
This commit is contained in:
HiFiPhile 2024-08-05 17:07:47 +02:00 committed by GitHub
commit a7d1888328
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 1863 additions and 279 deletions

View File

@ -141,7 +141,7 @@ static bool tud_audio_feature_unit_get_request(uint8_t rhport, audio_control_req
TU_LOG1("Get channel %u mute %d\r\n", request->bChannelNumber, mute1.bCur);
return tud_audio_buffer_and_schedule_control_xfer(rhport, (tusb_control_request_t const *)request, &mute1, sizeof(mute1));
}
else if (UAC2_ENTITY_SPK_FEATURE_UNIT && request->bControlSelector == AUDIO_FU_CTRL_VOLUME)
else if (request->bControlSelector == AUDIO_FU_CTRL_VOLUME)
{
if (request->bRequest == AUDIO_CS_REQ_RANGE)
{

View File

@ -229,7 +229,7 @@ static bool tud_audio_feature_unit_get_request(uint8_t rhport, audio_control_req
TU_LOG1("Get channel %u mute %d\r\n", request->bChannelNumber, mute1.bCur);
return tud_audio_buffer_and_schedule_control_xfer(rhport, (tusb_control_request_t const *)request, &mute1, sizeof(mute1));
}
else if (UAC2_ENTITY_SPK_FEATURE_UNIT && request->bControlSelector == AUDIO_FU_CTRL_VOLUME)
else if (request->bControlSelector == AUDIO_FU_CTRL_VOLUME)
{
if (request->bRequest == AUDIO_CS_REQ_RANGE)
{

View File

@ -0,0 +1,33 @@
cmake_minimum_required(VERSION 3.17)
include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake)
# gets PROJECT name for the example (e.g. <BOARD>-<DIR_NAME>)
family_get_project_name(PROJECT ${CMAKE_CURRENT_LIST_DIR})
project(${PROJECT} C CXX ASM)
# Checks this example is valid for the family and initializes the project
family_initialize_project(${PROJECT} ${CMAKE_CURRENT_LIST_DIR})
# Espressif has its own cmake build system
if(FAMILY STREQUAL "espressif")
return()
endif()
add_executable(${PROJECT})
# 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
)
# Configure compilation flags and libraries for the example without RTOS.
# See the corresponding function in hw/bsp/FAMILY/family.cmake for details.
family_configure_device_example(${PROJECT} noos)

View File

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

View File

@ -0,0 +1,8 @@
mcu:LPC11UXX
mcu:LPC13XX
mcu:NUC121
mcu:SAMD11
mcu:SAME5X
mcu:SAMG
board:stm32l052dap52
family:broadcom_64bit

View File

@ -0,0 +1,69 @@
# Install python3 HID package https://pypi.org/project/hid/
# Install python3 matplotlib package https://pypi.org/project/matplotlib/
from ctypes import *
try:
import hid
import matplotlib.pyplot as plt
import matplotlib.animation as animation
except:
print("Missing import, please try 'pip install hid matplotlib' or consult your OS's python package manager.")
# Example must be compiled with CFG_AUDIO_DEBUG=1
VID = 0xcafe
PID = 0x4014
CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX = 2
class audio_debug_info_t (Structure):
_fields_ = [("sample_rate", c_uint32),
("alt_settings", c_uint8),
("mute", (CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1) * c_int8),
("volume", (CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1) * c_int16),
("fifo_size", c_uint16),
("fifo_count", c_uint16),
("fifo_count_avg", c_uint16)
]
dev = hid.Device(VID, PID)
if dev:
# Create figure for plotting
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
fifo_avg = []
fifo_cnt = []
# This function is called periodically from FuncAnimation
def animate(i):
info = None
for i in range(30):
try:
str_in = dev.read(64, 50)
info = audio_debug_info_t.from_buffer_copy(str_in)
global fifo_avg
global fifo_cnt
fifo_avg.append(info.fifo_count_avg)
fifo_cnt.append(info.fifo_count)
except:
exit(1)
# Limit to 1000 items
fifo_avg = fifo_avg[-1000:]
fifo_cnt = fifo_cnt[-1000:]
if info is not None:
# Draw x and y lists
ax.clear()
ax.plot(fifo_cnt, label='FIFO count')
ax.plot(fifo_avg, label='FIFO average')
ax.legend()
ax.set_ylim(bottom=0, top=info.fifo_size)
# Format plot
plt.title('FIFO information')
plt.grid()
print(f'Sample rate:{info.sample_rate} | Alt settings:{info.alt_settings} | Volume:{info.volume[:]}')
ani = animation.FuncAnimation(fig, animate, interval=10)
plt.show()

View File

@ -0,0 +1,52 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2023 HiFiPhile
*
* 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 _COMMON_TYPES_H_
#define _COMMON_TYPES_H_
enum
{
ITF_NUM_AUDIO_CONTROL = 0,
ITF_NUM_AUDIO_STREAMING,
#if CFG_AUDIO_DEBUG
ITF_NUM_DEBUG,
#endif
ITF_NUM_TOTAL
};
#if CFG_AUDIO_DEBUG
typedef struct
{
uint32_t sample_rate;
uint8_t alt_settings;
int8_t mute[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1];
int16_t volume[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1];
uint16_t fifo_size;
uint16_t fifo_count;
uint16_t fifo_count_avg;
} audio_debug_info_t;
#endif
#endif

View File

@ -0,0 +1,505 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2020 Jerzy Kasenberg
*
* 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 <stdio.h>
#include <string.h>
#include "bsp/board_api.h"
#include "tusb.h"
#include "usb_descriptors.h"
#include "common_types.h"
#ifdef CFG_QUIRK_OS_GUESSING
#include "quirk_os_guessing.h"
#endif
//--------------------------------------------------------------------+
// MACRO CONSTANT TYPEDEF PROTOTYPES
//--------------------------------------------------------------------+
// List of supported sample rates
#if defined(__RX__)
const uint32_t sample_rates[] = {44100, 48000};
#else
const uint32_t sample_rates[] = {44100, 48000, 88200, 96000};
#endif
uint32_t current_sample_rate = 44100;
#define N_SAMPLE_RATES TU_ARRAY_SIZE(sample_rates)
/* Blink pattern
* - 25 ms : streaming data
* - 250 ms : device not mounted
* - 1000 ms : device mounted
* - 2500 ms : device is suspended
*/
enum
{
BLINK_STREAMING = 25,
BLINK_NOT_MOUNTED = 250,
BLINK_MOUNTED = 1000,
BLINK_SUSPENDED = 2500,
};
enum
{
VOLUME_CTRL_0_DB = 0,
VOLUME_CTRL_10_DB = 2560,
VOLUME_CTRL_20_DB = 5120,
VOLUME_CTRL_30_DB = 7680,
VOLUME_CTRL_40_DB = 10240,
VOLUME_CTRL_50_DB = 12800,
VOLUME_CTRL_60_DB = 15360,
VOLUME_CTRL_70_DB = 17920,
VOLUME_CTRL_80_DB = 20480,
VOLUME_CTRL_90_DB = 23040,
VOLUME_CTRL_100_DB = 25600,
VOLUME_CTRL_SILENCE = 0x8000,
};
static uint32_t blink_interval_ms = BLINK_NOT_MOUNTED;
// Audio controls
// Current states
int8_t mute[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1]; // +1 for master channel 0
int16_t volume[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1]; // +1 for master channel 0
// Buffer for speaker data
uint16_t i2s_dummy_buffer[CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ/2];
void led_blinking_task(void);
void audio_task(void);
#if CFG_AUDIO_DEBUG
void audio_debug_task(void);
uint8_t current_alt_settings;
uint16_t fifo_count;
uint32_t fifo_count_avg;
#endif
/*------------- MAIN -------------*/
int main(void)
{
board_init();
// init device stack on configured roothub port
tud_init(BOARD_TUD_RHPORT);
if (board_init_after_tusb) {
board_init_after_tusb();
}
TU_LOG1("Speaker running\r\n");
while (1)
{
tud_task(); // TinyUSB device task
led_blinking_task();
#if CFG_AUDIO_DEBUG
audio_debug_task();
#endif
audio_task();
}
}
//--------------------------------------------------------------------+
// 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 = tud_mounted() ? BLINK_MOUNTED : BLINK_NOT_MOUNTED;
}
//--------------------------------------------------------------------+
// Application Callback API Implementations
//--------------------------------------------------------------------+
// Helper for clock get requests
static bool tud_audio_clock_get_request(uint8_t rhport, audio_control_request_t const *request)
{
TU_ASSERT(request->bEntityID == UAC2_ENTITY_CLOCK);
if (request->bControlSelector == AUDIO_CS_CTRL_SAM_FREQ)
{
if (request->bRequest == AUDIO_CS_REQ_CUR)
{
TU_LOG1("Clock get current freq %lu\r\n", current_sample_rate);
audio_control_cur_4_t curf = { (int32_t) tu_htole32(current_sample_rate) };
return tud_audio_buffer_and_schedule_control_xfer(rhport, (tusb_control_request_t const *)request, &curf, sizeof(curf));
}
else if (request->bRequest == AUDIO_CS_REQ_RANGE)
{
audio_control_range_4_n_t(N_SAMPLE_RATES) rangef =
{
.wNumSubRanges = tu_htole16(N_SAMPLE_RATES)
};
TU_LOG1("Clock get %d freq ranges\r\n", N_SAMPLE_RATES);
for(uint8_t i = 0; i < N_SAMPLE_RATES; i++)
{
rangef.subrange[i].bMin = (int32_t) sample_rates[i];
rangef.subrange[i].bMax = (int32_t) sample_rates[i];
rangef.subrange[i].bRes = 0;
TU_LOG1("Range %d (%d, %d, %d)\r\n", i, (int)rangef.subrange[i].bMin, (int)rangef.subrange[i].bMax, (int)rangef.subrange[i].bRes);
}
return tud_audio_buffer_and_schedule_control_xfer(rhport, (tusb_control_request_t const *)request, &rangef, sizeof(rangef));
}
}
else if (request->bControlSelector == AUDIO_CS_CTRL_CLK_VALID &&
request->bRequest == AUDIO_CS_REQ_CUR)
{
audio_control_cur_1_t cur_valid = { .bCur = 1 };
TU_LOG1("Clock get is valid %u\r\n", cur_valid.bCur);
return tud_audio_buffer_and_schedule_control_xfer(rhport, (tusb_control_request_t const *)request, &cur_valid, sizeof(cur_valid));
}
TU_LOG1("Clock get request not supported, entity = %u, selector = %u, request = %u\r\n",
request->bEntityID, request->bControlSelector, request->bRequest);
return false;
}
// Helper for clock set requests
static bool tud_audio_clock_set_request(uint8_t rhport, audio_control_request_t const *request, uint8_t const *buf)
{
(void)rhport;
TU_ASSERT(request->bEntityID == UAC2_ENTITY_CLOCK);
TU_VERIFY(request->bRequest == AUDIO_CS_REQ_CUR);
if (request->bControlSelector == AUDIO_CS_CTRL_SAM_FREQ)
{
TU_VERIFY(request->wLength == sizeof(audio_control_cur_4_t));
current_sample_rate = (uint32_t) ((audio_control_cur_4_t const *)buf)->bCur;
TU_LOG1("Clock set current freq: %ld\r\n", current_sample_rate);
return true;
}
else
{
TU_LOG1("Clock set request not supported, entity = %u, selector = %u, request = %u\r\n",
request->bEntityID, request->bControlSelector, request->bRequest);
return false;
}
}
// Helper for feature unit get requests
static bool tud_audio_feature_unit_get_request(uint8_t rhport, audio_control_request_t const *request)
{
TU_ASSERT(request->bEntityID == UAC2_ENTITY_FEATURE_UNIT);
if (request->bControlSelector == AUDIO_FU_CTRL_MUTE && request->bRequest == AUDIO_CS_REQ_CUR)
{
audio_control_cur_1_t mute1 = { .bCur = mute[request->bChannelNumber] };
TU_LOG1("Get channel %u mute %d\r\n", request->bChannelNumber, mute1.bCur);
return tud_audio_buffer_and_schedule_control_xfer(rhport, (tusb_control_request_t const *)request, &mute1, sizeof(mute1));
}
else if (request->bControlSelector == AUDIO_FU_CTRL_VOLUME)
{
if (request->bRequest == AUDIO_CS_REQ_RANGE)
{
audio_control_range_2_n_t(1) range_vol = {
.wNumSubRanges = tu_htole16(1),
.subrange[0] = { .bMin = tu_htole16(-VOLUME_CTRL_50_DB), tu_htole16(VOLUME_CTRL_0_DB), tu_htole16(256) }
};
TU_LOG1("Get channel %u volume range (%d, %d, %u) dB\r\n", request->bChannelNumber,
range_vol.subrange[0].bMin / 256, range_vol.subrange[0].bMax / 256, range_vol.subrange[0].bRes / 256);
return tud_audio_buffer_and_schedule_control_xfer(rhport, (tusb_control_request_t const *)request, &range_vol, sizeof(range_vol));
}
else if (request->bRequest == AUDIO_CS_REQ_CUR)
{
audio_control_cur_2_t cur_vol = { .bCur = tu_htole16(volume[request->bChannelNumber]) };
TU_LOG1("Get channel %u volume %d dB\r\n", request->bChannelNumber, cur_vol.bCur / 256);
return tud_audio_buffer_and_schedule_control_xfer(rhport, (tusb_control_request_t const *)request, &cur_vol, sizeof(cur_vol));
}
}
TU_LOG1("Feature unit get request not supported, entity = %u, selector = %u, request = %u\r\n",
request->bEntityID, request->bControlSelector, request->bRequest);
return false;
}
// Helper for feature unit set requests
static bool tud_audio_feature_unit_set_request(uint8_t rhport, audio_control_request_t const *request, uint8_t const *buf)
{
(void)rhport;
TU_ASSERT(request->bEntityID == UAC2_ENTITY_FEATURE_UNIT);
TU_VERIFY(request->bRequest == AUDIO_CS_REQ_CUR);
if (request->bControlSelector == AUDIO_FU_CTRL_MUTE)
{
TU_VERIFY(request->wLength == sizeof(audio_control_cur_1_t));
mute[request->bChannelNumber] = ((audio_control_cur_1_t const *)buf)->bCur;
TU_LOG1("Set channel %d Mute: %d\r\n", request->bChannelNumber, mute[request->bChannelNumber]);
return true;
}
else if (request->bControlSelector == AUDIO_FU_CTRL_VOLUME)
{
TU_VERIFY(request->wLength == sizeof(audio_control_cur_2_t));
volume[request->bChannelNumber] = ((audio_control_cur_2_t const *)buf)->bCur;
TU_LOG1("Set channel %d volume: %d dB\r\n", request->bChannelNumber, volume[request->bChannelNumber] / 256);
return true;
}
else
{
TU_LOG1("Feature unit set request not supported, entity = %u, selector = %u, request = %u\r\n",
request->bEntityID, request->bControlSelector, request->bRequest);
return false;
}
}
// 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)
{
audio_control_request_t const *request = (audio_control_request_t const *)p_request;
if (request->bEntityID == UAC2_ENTITY_CLOCK)
return tud_audio_clock_get_request(rhport, request);
if (request->bEntityID == UAC2_ENTITY_FEATURE_UNIT)
return tud_audio_feature_unit_get_request(rhport, request);
else
{
TU_LOG1("Get request not handled, entity = %d, selector = %d, request = %d\r\n",
request->bEntityID, request->bControlSelector, request->bRequest);
}
return false;
}
// 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 *buf)
{
audio_control_request_t const *request = (audio_control_request_t const *)p_request;
if (request->bEntityID == UAC2_ENTITY_FEATURE_UNIT)
return tud_audio_feature_unit_set_request(rhport, request, buf);
if (request->bEntityID == UAC2_ENTITY_CLOCK)
return tud_audio_clock_set_request(rhport, request, buf);
TU_LOG1("Set request not handled, entity = %d, selector = %d, request = %d\r\n",
request->bEntityID, request->bControlSelector, request->bRequest);
return false;
}
bool tud_audio_set_itf_close_EP_cb(uint8_t rhport, tusb_control_request_t const * p_request)
{
(void)rhport;
uint8_t const itf = tu_u16_low(tu_le16toh(p_request->wIndex));
uint8_t const alt = tu_u16_low(tu_le16toh(p_request->wValue));
if (ITF_NUM_AUDIO_STREAMING == itf && alt == 0)
blink_interval_ms = BLINK_MOUNTED;
return true;
}
bool tud_audio_set_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request)
{
(void)rhport;
uint8_t const itf = tu_u16_low(tu_le16toh(p_request->wIndex));
uint8_t const alt = tu_u16_low(tu_le16toh(p_request->wValue));
TU_LOG2("Set interface %d alt %d\r\n", itf, alt);
if (ITF_NUM_AUDIO_STREAMING == itf && alt != 0)
blink_interval_ms = BLINK_STREAMING;
#if CFG_AUDIO_DEBUG
current_alt_settings = alt;
#endif
return true;
}
void tud_audio_feedback_params_cb(uint8_t func_id, uint8_t alt_itf, audio_feedback_params_t* feedback_param)
{
(void)func_id;
(void)alt_itf;
// Set feedback method to fifo counting
feedback_param->method = AUDIO_FEEDBACK_METHOD_FIFO_COUNT;
feedback_param->sample_freq = current_sample_rate;
}
#if CFG_AUDIO_DEBUG
bool tud_audio_rx_done_post_read_cb(uint8_t rhport, uint16_t n_bytes_received, uint8_t func_id, uint8_t ep_out, uint8_t cur_alt_setting)
{
(void)rhport;
(void)n_bytes_received;
(void)func_id;
(void)ep_out;
(void)cur_alt_setting;
fifo_count = tud_audio_available();
// Same averaging method used in UAC2 class
fifo_count_avg = (uint32_t)(((uint64_t)fifo_count_avg * 63 + ((uint32_t)fifo_count << 16)) >> 6);
return true;
}
#endif
#if CFG_QUIRK_OS_GUESSING
bool tud_audio_feedback_format_correction_cb(uint8_t func_id)
{
(void)func_id;
if(tud_speed_get() == TUSB_SPEED_FULL && quirk_os_guessing_get() == QUIRK_OS_GUESSING_OSX) {
return true;
} else {
return false;
}
}
#endif
//--------------------------------------------------------------------+
// AUDIO Task
//--------------------------------------------------------------------+
void audio_task(void)
{
// Replace audio_task() with your I2S transmit callback.
// Here we simulate a callback called every 1ms.
static uint32_t start_ms = 0;
uint32_t curr_ms = board_millis();
if ( start_ms == curr_ms ) return; // not enough time
start_ms = curr_ms;
uint16_t length = current_sample_rate/1000 * CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_RX * CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX;
if (current_sample_rate == 44100 && (curr_ms % 10 == 0))
{
// Take one more sample every 10 cycles, to have a average reading speed of 44.1
// This correction is not needed in real world cases
length += CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_RX * CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX;
} else
if (current_sample_rate == 88200 && (curr_ms % 5 == 0))
{
// Take one more sample every 5 cycles, to have a average reading speed of 88.2
// This correction is not needed in real world cases
length += CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_RX * CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX;
}
tud_audio_read(i2s_dummy_buffer, length);
}
//--------------------------------------------------------------------+
// 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;
start_ms += blink_interval_ms;
board_led_write(led_state);
led_state = 1 - led_state;
}
#if CFG_AUDIO_DEBUG
//--------------------------------------------------------------------+
// HID interface for audio debug
//--------------------------------------------------------------------+
// Every 1ms, we will sent 1 debug information report
void audio_debug_task(void)
{
static uint32_t start_ms = 0;
uint32_t curr_ms = board_millis();
if ( start_ms == curr_ms ) return; // not enough time
start_ms = curr_ms;
audio_debug_info_t debug_info;
debug_info.sample_rate = current_sample_rate;
debug_info.alt_settings = current_alt_settings;
debug_info.fifo_size = CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ;
debug_info.fifo_count = fifo_count;
debug_info.fifo_count_avg = fifo_count_avg >> 16;
for (int i = 0; i < CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1; i++)
{
debug_info.mute[i] = mute[i];
debug_info.volume[i] = volume[i];
}
if(tud_hid_ready())
tud_hid_report(0, &debug_info, sizeof(debug_info));
}
// Invoked when received GET_REPORT control request
// Unused here
uint16_t tud_hid_get_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen)
{
// TODO not Implemented
(void) itf;
(void) report_id;
(void) report_type;
(void) buffer;
(void) reqlen;
return 0;
}
// Invoked when received SET_REPORT control request or
// Unused here
void tud_hid_set_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize)
{
// This example doesn't use multiple report and report ID
(void) itf;
(void) report_id;
(void) report_type;
(void) buffer;
(void) bufsize;
}
#endif

View File

@ -0,0 +1,90 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2024 HiFiPhile
*
* 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 "quirk_os_guessing.h"
static tusb_desc_type_t desc_req_buf[2];
static int desc_req_idx = 0;
// Place at the start of tud_descriptor_device_cb()
void quirk_os_guessing_desc_device_cb() {
desc_req_idx = 0;
}
// Place at the start of tud_descriptor_configuration_cb()
void quirk_os_guessing_desc_configuration_cb() {
// Skip redundant request
if (desc_req_idx == 0 || (desc_req_idx == 1 && desc_req_buf[0] != TUSB_DESC_CONFIGURATION)) {
desc_req_buf[desc_req_idx++] = TUSB_DESC_CONFIGURATION;
}
}
// Place at the start of tud_descriptor_bos_cb()
void quirk_os_guessing_desc_bos_cb() {
// Skip redundant request
if (desc_req_idx == 0 || (desc_req_idx == 1 && desc_req_buf[0] != TUSB_DESC_BOS)) {
desc_req_buf[desc_req_idx++] = TUSB_DESC_BOS;
}
}
// Place at the start of tud_descriptor_string_cb()
void quirk_os_guessing_desc_string_cb() {
// Skip redundant request
if (desc_req_idx == 0 || (desc_req_idx == 1 && desc_req_buf[0] != TUSB_DESC_STRING)) {
desc_req_buf[desc_req_idx++] = TUSB_DESC_STRING;
}
}
// Each OS request descriptors differently:
// Windows 10 - 11
// Device Desc
// Config Desc
// BOS Desc
// String Desc
// Linux 3.16 - 6.8
// Device Desc
// BOS Desc
// Config Desc
// String Desc
// OS X Ventura - Sonoma
// Device Desc
// String Desc
// Config Desc || BOS Desc
// BOS Desc || Config Desc
quirk_os_guessing_t quirk_os_guessing_get(void) {
if (desc_req_idx < 2) {
return QUIRK_OS_GUESSING_UNKNOWN;
}
if (desc_req_buf[0] == TUSB_DESC_BOS && desc_req_buf[1] == TUSB_DESC_CONFIGURATION) {
return QUIRK_OS_GUESSING_LINUX;
} else if (desc_req_buf[0] == TUSB_DESC_CONFIGURATION && desc_req_buf[1] == TUSB_DESC_BOS) {
return QUIRK_OS_GUESSING_WINDOWS;
} else if (desc_req_buf[0] == TUSB_DESC_STRING && (desc_req_buf[1] == TUSB_DESC_BOS || desc_req_buf[1] == TUSB_DESC_CONFIGURATION)) {
return QUIRK_OS_GUESSING_OSX;
}
return QUIRK_OS_GUESSING_UNKNOWN;
}

View File

@ -0,0 +1,75 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2024 HiFiPhile
*
* 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 _QUIRK_OS_GUESSING_H_
#define _QUIRK_OS_GUESSING_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "tusb.h"
//================================== !!! WARNING !!! ====================================
// This quirk operate out of USB specification in order to workaround specific issues.
// It may not work on your platform.
//=======================================================================================
//
// Prerequisites:
// - Set USB version to at least 2.01 in Device Descriptor
// - Has a valid BOS Descriptor, refer to webusb_serial example
//
// Attention:
// Windows detection result comes out after Configuration Descriptor request,
// meaning it will be too late to do descriptor adjustment. It's advised to make
// Windows as default configuration and adjust to other OS accordingly.
typedef enum {
QUIRK_OS_GUESSING_UNKNOWN,
QUIRK_OS_GUESSING_LINUX,
QUIRK_OS_GUESSING_OSX,
QUIRK_OS_GUESSING_WINDOWS,
} quirk_os_guessing_t;
// Get Host OS type
quirk_os_guessing_t quirk_os_guessing_get(void);
// Place at the start of tud_descriptor_device_cb()
void quirk_os_guessing_desc_device_cb(void);
// Place at the start of tud_descriptor_configuration_cb()
void quirk_os_guessing_desc_configuration_cb(void);
// Place at the start of tud_descriptor_bos_cb()
void quirk_os_guessing_desc_bos_cb(void);
// Place at the start of tud_descriptor_string_cb()
void quirk_os_guessing_desc_string_cb(void);
#ifdef __cplusplus
}
#endif
#endif /* _QUIRK_OS_GUESSING_H_ */

View File

@ -0,0 +1,168 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2020 Ha Thach (tinyusb.org)
* Copyright (c) 2020 Jerzy Kasenberg
*
* 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
#include "usb_descriptors.h"
//--------------------------------------------------------------------+
// Board Specific Configuration
//--------------------------------------------------------------------+
// RHPort number used for device can be defined by board.mk, default to port 0
#ifndef BOARD_TUD_RHPORT
#define BOARD_TUD_RHPORT 0
#endif
// RHPort max operational speed can defined by board.mk
#ifndef BOARD_TUD_MAX_SPEED
#define BOARD_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED
#endif
//--------------------------------------------------------------------
// Common Configuration
//--------------------------------------------------------------------
// defined by compiler flags for flexibility
#ifndef CFG_TUSB_MCU
#error CFG_TUSB_MCU must be defined
#endif
#ifndef CFG_TUSB_OS
#define CFG_TUSB_OS OPT_OS_NONE
#endif
// It's recommended to disable debug unless for control requests debugging,
// as the extra time needed will impact data stream !
#ifndef CFG_TUSB_DEBUG
#define CFG_TUSB_DEBUG 0
#endif
// Enable Device stack
#define CFG_TUD_ENABLED 1
// Default is max speed that hardware controller could support with on-chip PHY
#define CFG_TUD_MAX_SPEED BOARD_TUD_MAX_SPEED
/* 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
/* (Needed for Full-Speed only)
* Enable host OS guessing to workaround UAC2 compatibility issues between Windows and OS X
* The default configuration only support Windows and Linux, enable this option for OS X
* support. Otherwise if you don't need Windows support you can make OS X's configuration as
* default.
*/
#define CFG_QUIRK_OS_GUESSING 1
//--------------------------------------------------------------------
// DEVICE CONFIGURATION
//--------------------------------------------------------------------
// Expose audio class debug information via HID interface
#ifndef CFG_AUDIO_DEBUG
#define CFG_AUDIO_DEBUG 1
#endif
#ifndef CFG_TUD_ENDPOINT0_SIZE
#define CFG_TUD_ENDPOINT0_SIZE 64
#endif
#define CFG_TUD_HID_EP_BUFSIZE 64
//------------- CLASS -------------//
#define CFG_TUD_AUDIO 1
#if CFG_AUDIO_DEBUG
#define CFG_TUD_HID 1
#else
#define CFG_TUD_HID 0
#endif
#define CFG_TUD_CDC 0
#define CFG_TUD_MSC 0
#define CFG_TUD_MIDI 0
#define CFG_TUD_VENDOR 0
//--------------------------------------------------------------------
// AUDIO CLASS DRIVER CONFIGURATION
//--------------------------------------------------------------------
#define CFG_TUD_AUDIO_FUNC_1_DESC_LEN TUD_AUDIO_SPEAKER_STEREO_FB_DESC_LEN
// Enable if Full-Speed on OSX, also set feedback EP size to 3
#define CFG_TUD_AUDIO_ENABLE_FEEDBACK_FORMAT_CORRECTION 0
// Audio format type I specifications
#if defined(__RX__)
#define CFG_TUD_AUDIO_FUNC_1_MAX_SAMPLE_RATE 48000
#else
#define CFG_TUD_AUDIO_FUNC_1_MAX_SAMPLE_RATE 96000
#endif
#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX 2
// 16bit in 16bit slots
#define CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_RX 2
#define CFG_TUD_AUDIO_FUNC_1_RESOLUTION_RX 16
// EP and buffer size - for isochronous EP´s, the buffer and EP size are equal (different sizes would not make sense)
#define CFG_TUD_AUDIO_ENABLE_EP_OUT 1
#define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX TUD_AUDIO_EP_SIZE(CFG_TUD_AUDIO_FUNC_1_MAX_SAMPLE_RATE, CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_RX, CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX)
#define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ (TUD_OPT_HIGH_SPEED ? 32 : 4) * CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX // Example read FIFO every 1ms, so it should be 8 times larger for HS device
// Enable feedback EP
#define CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP 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_N_AS_INT 1
// Size of control request buffer
#define CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ 64
#ifdef __cplusplus
}
#endif
#endif /* _TUSB_CONFIG_H_ */

View File

@ -0,0 +1,295 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2023 HiFiPhile
*
* 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 "bsp/board_api.h"
#include "tusb.h"
#include "usb_descriptors.h"
#include "common_types.h"
#ifdef CFG_QUIRK_OS_GUESSING
#include "quirk_os_guessing.h"
#endif
/* 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 = 0x0201,
// Use Interface Association Descriptor (IAD) for Audio
// 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)
{
#if CFG_QUIRK_OS_GUESSING
quirk_os_guessing_desc_device_cb();
#endif
return (uint8_t const *)&desc_device;
}
#if CFG_AUDIO_DEBUG
//--------------------------------------------------------------------+
// HID Report Descriptor
//--------------------------------------------------------------------+
uint8_t const desc_hid_report[] =
{
HID_USAGE_PAGE_N ( HID_USAGE_PAGE_VENDOR, 2 ),\
HID_USAGE ( 0x01 ),\
HID_COLLECTION ( HID_COLLECTION_APPLICATION ),\
HID_USAGE ( 0x02 ),\
HID_LOGICAL_MIN ( 0x00 ),\
HID_LOGICAL_MAX_N ( 0xff, 2 ),\
HID_REPORT_SIZE ( 8 ),\
HID_REPORT_COUNT( sizeof(audio_debug_info_t) ),\
HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\
HID_COLLECTION_END
};
// Invoked when received GET HID REPORT DESCRIPTOR
// Application return pointer to descriptor
// Descriptor contents must exist long enough for transfer to complete
uint8_t const * tud_hid_descriptor_report_cb(uint8_t itf)
{
(void) itf;
return desc_hid_report;
}
#endif
//--------------------------------------------------------------------+
// Configuration Descriptor
//--------------------------------------------------------------------+
#if CFG_AUDIO_DEBUG
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_AUDIO_SPEAKER_STEREO_FB_DESC_LEN + TUD_HID_DESC_LEN)
#else
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_AUDIO_SPEAKER_STEREO_FB_DESC_LEN)
#endif
#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_FB 0x03
#define EPNUM_AUDIO_OUT 0x03
#define EPNUM_DEBUG 0x04
#elif CFG_TUSB_MCU == OPT_MCU_NRF5X
// ISO endpoints for NRF5x are fixed to 0x08 (0x88)
#define EPNUM_AUDIO_FB 0x08
#define EPNUM_AUDIO_OUT 0x08
#define EPNUM_DEBUG 0x01
#elif CFG_TUSB_MCU == OPT_MCU_SAMG || CFG_TUSB_MCU == OPT_MCU_SAMX7X
// SAMG & SAME70 don't support a same endpoint number with different direction IN and OUT
// e.g EP1 OUT & EP1 IN cannot exist together
#define EPNUM_AUDIO_FB 0x01
#define EPNUM_AUDIO_OUT 0x02
#define EPNUM_DEBUG 0x03
#elif CFG_TUSB_MCU == OPT_MCU_FT90X || CFG_TUSB_MCU == OPT_MCU_FT93X
// FT9XX doesn't support a same endpoint number with different direction IN and OUT
// e.g EP1 OUT & EP1 IN cannot exist together
#define EPNUM_AUDIO_FB 0x01
#define EPNUM_AUDIO_OUT 0x02
#define EPNUM_DEBUG 0x03
#else
#define EPNUM_AUDIO_FB 0x01
#define EPNUM_AUDIO_OUT 0x01
#define EPNUM_DEBUG 0x02
#endif
uint8_t const desc_configuration_default[] =
{
// Config number, 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, byte per sample, bit per sample, EP Out, EP size, EP feedback, feedback EP size,
TUD_AUDIO_SPEAKER_STEREO_FB_DESCRIPTOR(0, 4, CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_RX, CFG_TUD_AUDIO_FUNC_1_RESOLUTION_RX, EPNUM_AUDIO_OUT, CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX, EPNUM_AUDIO_FB | 0x80, 4),
#if CFG_AUDIO_DEBUG
// Interface number, string index, protocol, report descriptor len, EP In address, size & polling interval
TUD_HID_DESCRIPTOR(ITF_NUM_DEBUG, 0, HID_ITF_PROTOCOL_NONE, sizeof(desc_hid_report), EPNUM_DEBUG | 0x80, CFG_TUD_HID_EP_BUFSIZE, 7)
#endif
};
#if CFG_QUIRK_OS_GUESSING
// OS X needs 3 bytes feedback endpoint on FS
uint8_t const desc_configuration_osx_fs[] =
{
// Config number, 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, byte per sample, bit per sample, EP Out, EP size, EP feedback, feedback EP size,
TUD_AUDIO_SPEAKER_STEREO_FB_DESCRIPTOR(0, 4, CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_RX, CFG_TUD_AUDIO_FUNC_1_RESOLUTION_RX, EPNUM_AUDIO_OUT, CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX, EPNUM_AUDIO_FB | 0x80, 3),
#if CFG_AUDIO_DEBUG
// Interface number, string index, protocol, report descriptor len, EP In address, size & polling interval
TUD_HID_DESCRIPTOR(ITF_NUM_DEBUG, 0, HID_ITF_PROTOCOL_NONE, sizeof(desc_hid_report), EPNUM_DEBUG | 0x80, CFG_TUD_HID_EP_BUFSIZE, 7)
#endif
};
#endif
// 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
#if CFG_QUIRK_OS_GUESSING
quirk_os_guessing_desc_configuration_cb();
if(tud_speed_get() == TUSB_SPEED_FULL && quirk_os_guessing_get() == QUIRK_OS_GUESSING_OSX) {
return desc_configuration_osx_fs;
}
#endif
return desc_configuration_default;
}
//--------------------------------------------------------------------+
// BOS Descriptor, required for OS guessing quirk
//--------------------------------------------------------------------+
#define TUD_BOS_USB20_EXT_DESC_LEN 7
#define BOS_TOTAL_LEN (TUD_BOS_DESC_LEN + TUD_BOS_USB20_EXT_DESC_LEN)
// BOS Descriptor is required for webUSB
uint8_t const desc_bos[] =
{
// total length, number of device caps
TUD_BOS_DESCRIPTOR(BOS_TOTAL_LEN, 1),
// USB 2.0 Extension Descriptor
0x07, TUSB_DESC_DEVICE_CAPABILITY, DEVICE_CAPABILITY_USB20_EXTENSION, 0x00, 0x00, 0x00,0x00
};
uint8_t const * tud_descriptor_bos_cb(void)
{
#if CFG_QUIRK_OS_GUESSING
quirk_os_guessing_desc_bos_cb();
#endif
return desc_bos;
}
//--------------------------------------------------------------------+
// String Descriptors
//--------------------------------------------------------------------+
// String Descriptor Index
enum {
STRID_LANGID = 0,
STRID_MANUFACTURER,
STRID_PRODUCT,
STRID_SERIAL,
};
// array of pointer to string descriptors
char const *string_desc_arr[] =
{
(const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
"TinyUSB", // 1: Manufacturer
"TinyUSB Speaker", // 2: Product
NULL, // 3: Serials will use unique ID if possible
"UAC2 Speaker", // 4: Audio Interface
};
static uint16_t _desc_str[32 + 1];
// 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;
size_t chr_count;
#if CFG_QUIRK_OS_GUESSING
quirk_os_guessing_desc_string_cb();
#endif
switch ( index ) {
case STRID_LANGID:
memcpy(&_desc_str[1], string_desc_arr[0], 2);
chr_count = 1;
break;
case STRID_SERIAL:
chr_count = board_usb_get_serial(_desc_str + 1, 32);
break;
default:
// Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors.
// https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors
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);
size_t const max_count = sizeof(_desc_str) / sizeof(_desc_str[0]) - 1; // -1 for string type
if ( chr_count > max_count ) chr_count = max_count;
// Convert ASCII string into UTF-16
for ( size_t i = 0; i < chr_count; i++ ) {
_desc_str[1 + i] = str[i];
}
break;
}
// first byte is length (including header), second byte is string type
_desc_str[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (2 * chr_count + 2));
return _desc_str;
}

View File

@ -0,0 +1,82 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2023 HiFiPhile
*
* 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 _USB_DESCRIPTORS_H_
#define _USB_DESCRIPTORS_H_
// Defined in TUD_AUDIO_SPEAKER_STEREO_FB_DESCRIPTOR
#define UAC2_ENTITY_CLOCK 0x04
#define UAC2_ENTITY_INPUT_TERMINAL 0x01
#define UAC2_ENTITY_FEATURE_UNIT 0x02
#define UAC2_ENTITY_OUTPUT_TERMINAL 0x03
#define TUD_AUDIO_SPEAKER_STEREO_FB_DESC_LEN (TUD_AUDIO_DESC_IAD_LEN\
+ TUD_AUDIO_DESC_STD_AC_LEN\
+ TUD_AUDIO_DESC_CS_AC_LEN\
+ TUD_AUDIO_DESC_CLK_SRC_LEN\
+ TUD_AUDIO_DESC_INPUT_TERM_LEN\
+ TUD_AUDIO_DESC_OUTPUT_TERM_LEN\
+ TUD_AUDIO_DESC_FEATURE_UNIT_TWO_CHANNEL_LEN\
+ TUD_AUDIO_DESC_STD_AS_INT_LEN\
+ TUD_AUDIO_DESC_STD_AS_INT_LEN\
+ TUD_AUDIO_DESC_CS_AS_INT_LEN\
+ TUD_AUDIO_DESC_TYPE_I_FORMAT_LEN\
+ TUD_AUDIO_DESC_STD_AS_ISO_EP_LEN\
+ TUD_AUDIO_DESC_CS_AS_ISO_EP_LEN\
+ TUD_AUDIO_DESC_STD_AS_ISO_FB_EP_LEN)
#define TUD_AUDIO_SPEAKER_STEREO_FB_DESCRIPTOR(_itfnum, _stridx, _nBytesPerSample, _nBitsUsedPerSample, _epout, _epoutsize, _epfb, _epfbsize) \
/* Standard Interface Association Descriptor (IAD) */\
TUD_AUDIO_DESC_IAD(/*_firstitf*/ _itfnum, /*_nitfs*/ 0x02, /*_stridx*/ 0x00),\
/* Standard AC Interface Descriptor(4.7.1) */\
TUD_AUDIO_DESC_STD_AC(/*_itfnum*/ _itfnum, /*_nEPs*/ 0x00, /*_stridx*/ _stridx),\
/* Class-Specific AC Interface Header Descriptor(4.7.2) */\
TUD_AUDIO_DESC_CS_AC(/*_bcdADC*/ 0x0200, /*_category*/ AUDIO_FUNC_DESKTOP_SPEAKER, /*_totallen*/ TUD_AUDIO_DESC_CLK_SRC_LEN+TUD_AUDIO_DESC_INPUT_TERM_LEN+TUD_AUDIO_DESC_OUTPUT_TERM_LEN+TUD_AUDIO_DESC_FEATURE_UNIT_TWO_CHANNEL_LEN, /*_ctrl*/ AUDIO_CS_AS_INTERFACE_CTRL_LATENCY_POS),\
/* Clock Source Descriptor(4.7.2.1) */\
TUD_AUDIO_DESC_CLK_SRC(/*_clkid*/ 0x04, /*_attr*/ AUDIO_CLOCK_SOURCE_ATT_INT_PRO_CLK, /*_ctrl*/ (AUDIO_CTRL_RW << AUDIO_CLOCK_SOURCE_CTRL_CLK_FRQ_POS), /*_assocTerm*/ 0x01, /*_stridx*/ 0x00),\
/* Input Terminal Descriptor(4.7.2.4) */\
TUD_AUDIO_DESC_INPUT_TERM(/*_termid*/ 0x01, /*_termtype*/ AUDIO_TERM_TYPE_USB_STREAMING, /*_assocTerm*/ 0x00, /*_clkid*/ 0x04, /*_nchannelslogical*/ 0x02, /*_channelcfg*/ AUDIO_CHANNEL_CONFIG_NON_PREDEFINED, /*_idxchannelnames*/ 0x00, /*_ctrl*/ 0 * (AUDIO_CTRL_R << AUDIO_IN_TERM_CTRL_CONNECTOR_POS), /*_stridx*/ 0x00),\
/* Output Terminal Descriptor(4.7.2.5) */\
TUD_AUDIO_DESC_OUTPUT_TERM(/*_termid*/ 0x03, /*_termtype*/ AUDIO_TERM_TYPE_OUT_DESKTOP_SPEAKER, /*_assocTerm*/ 0x01, /*_srcid*/ 0x02, /*_clkid*/ 0x04, /*_ctrl*/ 0x0000, /*_stridx*/ 0x00),\
/* Feature Unit Descriptor(4.7.2.8) */\
TUD_AUDIO_DESC_FEATURE_UNIT_TWO_CHANNEL(/*_unitid*/ 0x02, /*_srcid*/ 0x01, /*_ctrlch0master*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch1*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch2*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS,/*_stridx*/ 0x00),\
/* Standard AS Interface Descriptor(4.9.1) */\
/* Interface 1, Alternate 0 - default alternate setting with 0 bandwidth */\
TUD_AUDIO_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum) + 1), /*_altset*/ 0x00, /*_nEPs*/ 0x00, /*_stridx*/ 0x00),\
/* Standard AS Interface Descriptor(4.9.1) */\
/* Interface 1, Alternate 1 - alternate interface for data streaming */\
TUD_AUDIO_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum) + 1), /*_altset*/ 0x01, /*_nEPs*/ 0x02, /*_stridx*/ 0x00),\
/* Class-Specific AS Interface Descriptor(4.9.2) */\
TUD_AUDIO_DESC_CS_AS_INT(/*_termid*/ 0x01, /*_ctrl*/ AUDIO_CTRL_NONE, /*_formattype*/ AUDIO_FORMAT_TYPE_I, /*_formats*/ AUDIO_DATA_FORMAT_TYPE_I_PCM, /*_nchannelsphysical*/ 0x02, /*_channelcfg*/ AUDIO_CHANNEL_CONFIG_NON_PREDEFINED, /*_stridx*/ 0x00),\
/* Type I Format Type Descriptor(2.3.1.6 - Audio Formats) */\
TUD_AUDIO_DESC_TYPE_I_FORMAT(_nBytesPerSample, _nBitsUsedPerSample),\
/* Standard AS Isochronous Audio Data Endpoint Descriptor(4.10.1.1) */\
TUD_AUDIO_DESC_STD_AS_ISO_EP(/*_ep*/ _epout, /*_attr*/ (uint8_t) ((uint8_t)TUSB_XFER_ISOCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_ASYNCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_DATA), /*_maxEPsize*/ _epoutsize, /*_interval*/ 0x01),\
/* Class-Specific AS Isochronous Audio Data Endpoint Descriptor(4.10.1.2) */\
TUD_AUDIO_DESC_CS_AS_ISO_EP(/*_attr*/ AUDIO_CS_AS_ISO_DATA_EP_ATT_NON_MAX_PACKETS_OK, /*_ctrl*/ AUDIO_CTRL_NONE, /*_lockdelayunit*/ AUDIO_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_MILLISEC, /*_lockdelay*/ 0x0001),\
/* Standard AS Isochronous Feedback Endpoint Descriptor(4.10.2.1) */\
TUD_AUDIO_DESC_STD_AS_ISO_FB_EP(/*_ep*/ _epfb, /*_epsize*/ _epfbsize, /*_interval*/ TUD_OPT_HIGH_SPEED ? 4 : 1)\
#endif

View File

@ -81,44 +81,73 @@ void USB1_IRQHandler(void) {
}
/****************************************************************
name: BOARD_BootClockFROHF96M
name: BOARD_BootClockPLL100M
outputs:
- {id: SYSTICK_clock.outFreq, value: 96 MHz}
- {id: System_clock.outFreq, value: 96 MHz}
- {id: System_clock.outFreq, value: 100 MHz}
settings:
- {id: SYSCON.MAINCLKSELA.sel, value: SYSCON.fro_hf}
- {id: PLL0_Mode, value: Normal}
- {id: ANALOG_CONTROL_FRO192M_CTRL_ENDI_FRO_96M_CFG, value: Enable}
- {id: ENABLE_CLKIN_ENA, value: Enabled}
- {id: ENABLE_SYSTEM_CLK_OUT, value: Enabled}
- {id: SYSCON.MAINCLKSELB.sel, value: SYSCON.PLL0_BYPASS}
- {id: SYSCON.PLL0CLKSEL.sel, value: SYSCON.CLK_IN_EN}
- {id: SYSCON.PLL0M_MULT.scale, value: '100', locked: true}
- {id: SYSCON.PLL0N_DIV.scale, value: '4', locked: true}
- {id: SYSCON.PLL0_PDEC.scale, value: '4', locked: true}
sources:
- {id: SYSCON.fro_hf.outFreq, value: 96 MHz}
- {id: ANACTRL.fro_hf.outFreq, value: 96 MHz}
- {id: SYSCON.XTAL32M.outFreq, value: 16 MHz, enabled: true}
******************************************************************/
void BootClockFROHF96M(void) {
/*!< Set up the clock sources */
/*!< Set up FRO */
POWER_DisablePD(kPDRUNCFG_PD_FRO192M); /*!< Ensure FRO is on */
CLOCK_SetupFROClocking(12000000U); /*!< Set up FRO to the 12 MHz, just for sure */
CLOCK_AttachClk(kFRO12M_to_MAIN_CLK); /*!< Switch to FRO 12MHz first to ensure we can change voltage without
accidentally being below the voltage for current speed */
void BOARD_BootClockPLL100M(void)
{
/*!< Set up the clock sources */
/*!< Configure FRO192M */
POWER_DisablePD(kPDRUNCFG_PD_FRO192M); /*!< Ensure FRO is on */
CLOCK_SetupFROClocking(12000000U); /*!< Set up FRO to the 12 MHz, just for sure */
CLOCK_AttachClk(kFRO12M_to_MAIN_CLK); /*!< Switch to FRO 12MHz first to ensure we can change the clock setting */
CLOCK_SetupFROClocking(96000000U); /*!< Set up high frequency FRO output to selected frequency */
CLOCK_SetupFROClocking(96000000U); /* Enable FRO HF(96MHz) output */
POWER_SetVoltageForFreq(96000000U); /*!< Set voltage for the one of the fastest clock outputs: System clock output */
CLOCK_SetFLASHAccessCyclesForFreq(96000000U); /*!< Set FLASH wait states for core */
/*!< Configure XTAL32M */
POWER_DisablePD(kPDRUNCFG_PD_XTAL32M); /* Ensure XTAL32M is powered */
POWER_DisablePD(kPDRUNCFG_PD_LDOXO32M); /* Ensure XTAL32M is powered */
CLOCK_SetupExtClocking(16000000U); /* Enable clk_in clock */
SYSCON->CLOCK_CTRL |= SYSCON_CLOCK_CTRL_CLKIN_ENA_MASK; /* Enable clk_in from XTAL32M clock */
ANACTRL->XO32M_CTRL |= ANACTRL_XO32M_CTRL_ENABLE_SYSTEM_CLK_OUT_MASK; /* Enable clk_in to system */
/*!< Set up dividers */
CLOCK_SetClkDiv(kCLOCK_DivAhbClk, 1U, false); /*!< Set AHBCLKDIV divider to value 1 */
POWER_SetVoltageForFreq(100000000U); /*!< Set voltage for the one of the fastest clock outputs: System clock output */
CLOCK_SetFLASHAccessCyclesForFreq(100000000U); /*!< Set FLASH wait states for core */
/*!< Set up clock selectors - Attach clocks to the peripheries */
CLOCK_AttachClk(kFRO_HF_to_MAIN_CLK); /*!< Switch MAIN_CLK to FRO_HF */
/*!< Set up PLL */
CLOCK_AttachClk(kEXT_CLK_to_PLL0); /*!< Switch PLL0CLKSEL to EXT_CLK */
POWER_DisablePD(kPDRUNCFG_PD_PLL0); /* Ensure PLL is on */
POWER_DisablePD(kPDRUNCFG_PD_PLL0_SSCG);
const pll_setup_t pll0Setup = {
.pllctrl = SYSCON_PLL0CTRL_CLKEN_MASK | SYSCON_PLL0CTRL_SELI(53U) | SYSCON_PLL0CTRL_SELP(26U),
.pllndec = SYSCON_PLL0NDEC_NDIV(4U),
.pllpdec = SYSCON_PLL0PDEC_PDIV(2U),
.pllsscg = {0x0U,(SYSCON_PLL0SSCG1_MDIV_EXT(100U) | SYSCON_PLL0SSCG1_SEL_EXT_MASK)},
.pllRate = 100000000U,
.flags = PLL_SETUPFLAG_WAITLOCK
};
CLOCK_SetPLL0Freq(&pll0Setup); /*!< Configure PLL0 to the desired values */
/*!< Set SystemCoreClock variable. */
SystemCoreClock = 96000000U;
/*!< Set up dividers */
CLOCK_SetClkDiv(kCLOCK_DivAhbClk, 1U, false); /*!< Set AHBCLKDIV divider to value 1 */
/*!< Set up clock selectors - Attach clocks to the peripheries */
CLOCK_AttachClk(kPLL0_to_MAIN_CLK); /*!< Switch MAIN_CLK to PLL0 */
/*< Set SystemCoreClock variable. */
SystemCoreClock = 100000000U;
}
void board_init(void) {
// Enable IOCON clock
CLOCK_EnableClock(kCLOCK_Iocon);
// Init 96 MHz clock
BootClockFROHF96M();
// Init 100 MHz clock
BOARD_BootClockPLL100M();
#if CFG_TUSB_OS == OPT_OS_NONE
// 1ms tick timer

View File

@ -2,6 +2,7 @@
* The MIT License (MIT)
*
* Copyright (c) 2020 Reinhard Panhuber, Jerzy Kasenberg
* Copyright (c) 2023 HiFiPhile
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -65,8 +66,10 @@
//--------------------------------------------------------------------+
// Use ring buffer if it's available, some MCUs need extra RAM requirements
// For DWC2 enable ring buffer will disable DMA (if available)
#ifndef TUD_AUDIO_PREFER_RING_BUFFER
#if CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT1XXX
#if CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT1XXX || \
defined(TUP_USBIP_DWC2)
#define TUD_AUDIO_PREFER_RING_BUFFER 0
#else
#define TUD_AUDIO_PREFER_RING_BUFFER 1
@ -118,23 +121,23 @@
// EP IN software buffers and mutexes
#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING
#if CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ > 0
IN_SW_BUF_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t audio_ep_in_sw_buf_1[CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ];
tu_static IN_SW_BUF_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t audio_ep_in_sw_buf_1[CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ];
#if CFG_FIFO_MUTEX
osal_mutex_def_t ep_in_ff_mutex_wr_1; // No need for read mutex as only USB driver reads from FIFO
tu_static osal_mutex_def_t ep_in_ff_mutex_wr_1; // No need for read mutex as only USB driver reads from FIFO
#endif
#endif // CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ > 0
#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ > 0
IN_SW_BUF_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t audio_ep_in_sw_buf_2[CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ];
tu_static IN_SW_BUF_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t audio_ep_in_sw_buf_2[CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ];
#if CFG_FIFO_MUTEX
osal_mutex_def_t ep_in_ff_mutex_wr_2; // No need for read mutex as only USB driver reads from FIFO
tu_static osal_mutex_def_t ep_in_ff_mutex_wr_2; // No need for read mutex as only USB driver reads from FIFO
#endif
#endif // CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ > 0
#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ > 0
IN_SW_BUF_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t audio_ep_in_sw_buf_3[CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ];
tu_static IN_SW_BUF_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t audio_ep_in_sw_buf_3[CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ];
#if CFG_FIFO_MUTEX
osal_mutex_def_t ep_in_ff_mutex_wr_3; // No need for read mutex as only USB driver reads from FIFO
tu_static osal_mutex_def_t ep_in_ff_mutex_wr_3; // No need for read mutex as only USB driver reads from FIFO
#endif
#endif // CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ > 0
#endif // CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING
@ -144,38 +147,38 @@
// - the software encoding is used - in this case the linear buffers serve as a target memory where logical channels are encoded into
#if CFG_TUD_AUDIO_ENABLE_EP_IN && (USE_LINEAR_BUFFER || CFG_TUD_AUDIO_ENABLE_ENCODING)
#if CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX > 0
CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t lin_buf_in_1[CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX];
tu_static CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t lin_buf_in_1[CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX];
#endif
#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_IN_SZ_MAX > 0
CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t lin_buf_in_2[CFG_TUD_AUDIO_FUNC_2_EP_IN_SZ_MAX];
tu_static CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t lin_buf_in_2[CFG_TUD_AUDIO_FUNC_2_EP_IN_SZ_MAX];
#endif
#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_IN_SZ_MAX > 0
CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t lin_buf_in_3[CFG_TUD_AUDIO_FUNC_3_EP_IN_SZ_MAX];
tu_static CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t lin_buf_in_3[CFG_TUD_AUDIO_FUNC_3_EP_IN_SZ_MAX];
#endif
#endif // CFG_TUD_AUDIO_ENABLE_EP_IN && (USE_LINEAR_BUFFER || CFG_TUD_AUDIO_ENABLE_DECODING)
// EP OUT software buffers and mutexes
#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING
#if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ > 0
OUT_SW_BUF_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t audio_ep_out_sw_buf_1[CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ];
tu_static OUT_SW_BUF_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t audio_ep_out_sw_buf_1[CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ];
#if CFG_FIFO_MUTEX
osal_mutex_def_t ep_out_ff_mutex_rd_1; // No need for write mutex as only USB driver writes into FIFO
tu_static osal_mutex_def_t ep_out_ff_mutex_rd_1; // No need for write mutex as only USB driver writes into FIFO
#endif
#endif // CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ > 0
#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ > 0
OUT_SW_BUF_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t audio_ep_out_sw_buf_2[CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ];
tu_static OUT_SW_BUF_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t audio_ep_out_sw_buf_2[CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ];
#if CFG_FIFO_MUTEX
osal_mutex_def_t ep_out_ff_mutex_rd_2; // No need for write mutex as only USB driver writes into FIFO
tu_static osal_mutex_def_t ep_out_ff_mutex_rd_2; // No need for write mutex as only USB driver writes into FIFO
#endif
#endif // CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ > 0
#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ > 0
OUT_SW_BUF_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t audio_ep_out_sw_buf_3[CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ];
tu_static OUT_SW_BUF_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t audio_ep_out_sw_buf_3[CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ];
#if CFG_FIFO_MUTEX
osal_mutex_def_t ep_out_ff_mutex_rd_3; // No need for write mutex as only USB driver writes into FIFO
tu_static osal_mutex_def_t ep_out_ff_mutex_rd_3; // No need for write mutex as only USB driver writes into FIFO
#endif
#endif // CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ > 0
#endif // CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING
@ -185,89 +188,89 @@
// - the software encoding is used - in this case the linear buffers serve as a target memory where logical channels are encoded into
#if CFG_TUD_AUDIO_ENABLE_EP_OUT && (USE_LINEAR_BUFFER || CFG_TUD_AUDIO_ENABLE_DECODING)
#if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX > 0
CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t lin_buf_out_1[CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX];
tu_static CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t lin_buf_out_1[CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX];
#endif
#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SZ_MAX > 0
CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t lin_buf_out_2[CFG_TUD_AUDIO_FUNC_2_EP_OUT_SZ_MAX];
tu_static CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t lin_buf_out_2[CFG_TUD_AUDIO_FUNC_2_EP_OUT_SZ_MAX];
#endif
#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX > 0
CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t lin_buf_out_3[CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX];
tu_static CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t lin_buf_out_3[CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX];
#endif
#endif // CFG_TUD_AUDIO_ENABLE_EP_OUT && (USE_LINEAR_BUFFER || CFG_TUD_AUDIO_ENABLE_DECODING)
// Control buffers
CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t ctrl_buf_1[CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ];
tu_static CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t ctrl_buf_1[CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ];
#if CFG_TUD_AUDIO > 1
CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t ctrl_buf_2[CFG_TUD_AUDIO_FUNC_2_CTRL_BUF_SZ];
tu_static CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t ctrl_buf_2[CFG_TUD_AUDIO_FUNC_2_CTRL_BUF_SZ];
#endif
#if CFG_TUD_AUDIO > 2
CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t ctrl_buf_3[CFG_TUD_AUDIO_FUNC_3_CTRL_BUF_SZ];
tu_static CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t ctrl_buf_3[CFG_TUD_AUDIO_FUNC_3_CTRL_BUF_SZ];
#endif
// Active alternate setting of interfaces
uint8_t alt_setting_1[CFG_TUD_AUDIO_FUNC_1_N_AS_INT];
tu_static uint8_t alt_setting_1[CFG_TUD_AUDIO_FUNC_1_N_AS_INT];
#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_N_AS_INT > 0
uint8_t alt_setting_2[CFG_TUD_AUDIO_FUNC_2_N_AS_INT];
tu_static uint8_t alt_setting_2[CFG_TUD_AUDIO_FUNC_2_N_AS_INT];
#endif
#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_N_AS_INT > 0
uint8_t alt_setting_3[CFG_TUD_AUDIO_FUNC_3_N_AS_INT];
tu_static uint8_t alt_setting_3[CFG_TUD_AUDIO_FUNC_3_N_AS_INT];
#endif
// Software encoding/decoding support FIFOs
#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING
#if CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ > 0
CFG_TUSB_MEM_ALIGN uint8_t tx_supp_ff_buf_1[CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ];
tu_fifo_t tx_supp_ff_1[CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO];
tu_static CFG_TUSB_MEM_ALIGN uint8_t tx_supp_ff_buf_1[CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ];
tu_static tu_fifo_t tx_supp_ff_1[CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO];
#if CFG_FIFO_MUTEX
osal_mutex_def_t tx_supp_ff_mutex_wr_1[CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO]; // No need for read mutex as only USB driver reads from FIFO
tu_static osal_mutex_def_t tx_supp_ff_mutex_wr_1[CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO]; // No need for read mutex as only USB driver reads from FIFO
#endif
#endif
#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ > 0
CFG_TUSB_MEM_ALIGN uint8_t tx_supp_ff_buf_2[CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ];
tu_fifo_t tx_supp_ff_2[CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO];
tu_static CFG_TUSB_MEM_ALIGN uint8_t tx_supp_ff_buf_2[CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ];
tu_static tu_fifo_t tx_supp_ff_2[CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO];
#if CFG_FIFO_MUTEX
osal_mutex_def_t tx_supp_ff_mutex_wr_2[CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO]; // No need for read mutex as only USB driver reads from FIFO
tu_static osal_mutex_def_t tx_supp_ff_mutex_wr_2[CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO]; // No need for read mutex as only USB driver reads from FIFO
#endif
#endif
#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ > 0
CFG_TUSB_MEM_ALIGN uint8_t tx_supp_ff_buf_3[CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ];
tu_fifo_t tx_supp_ff_3[CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO];
tu_static CFG_TUSB_MEM_ALIGN uint8_t tx_supp_ff_buf_3[CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ];
tu_static tu_fifo_t tx_supp_ff_3[CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO];
#if CFG_FIFO_MUTEX
osal_mutex_def_t tx_supp_ff_mutex_wr_3[CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO]; // No need for read mutex as only USB driver reads from FIFO
tu_static osal_mutex_def_t tx_supp_ff_mutex_wr_3[CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO]; // No need for read mutex as only USB driver reads from FIFO
#endif
#endif
#endif
#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING
#if CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ > 0
CFG_TUSB_MEM_ALIGN uint8_t rx_supp_ff_buf_1[CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ];
tu_fifo_t rx_supp_ff_1[CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO];
tu_static CFG_TUSB_MEM_ALIGN uint8_t rx_supp_ff_buf_1[CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ];
tu_static tu_fifo_t rx_supp_ff_1[CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO];
#if CFG_FIFO_MUTEX
osal_mutex_def_t rx_supp_ff_mutex_rd_1[CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO]; // No need for write mutex as only USB driver writes into FIFO
tu_static osal_mutex_def_t rx_supp_ff_mutex_rd_1[CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO]; // No need for write mutex as only USB driver writes into FIFO
#endif
#endif
#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ > 0
CFG_TUSB_MEM_ALIGN uint8_t rx_supp_ff_buf_2[CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ];
tu_fifo_t rx_supp_ff_2[CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO];
tu_static CFG_TUSB_MEM_ALIGN uint8_t rx_supp_ff_buf_2[CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ];
tu_static tu_fifo_t rx_supp_ff_2[CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO];
#if CFG_FIFO_MUTEX
osal_mutex_def_t rx_supp_ff_mutex_rd_2[CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO]; // No need for write mutex as only USB driver writes into FIFO
tu_static osal_mutex_def_t rx_supp_ff_mutex_rd_2[CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO]; // No need for write mutex as only USB driver writes into FIFO
#endif
#endif
#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ > 0
CFG_TUSB_MEM_ALIGN uint8_t rx_supp_ff_buf_3[CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ];
tu_fifo_t rx_supp_ff_3[CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO];
tu_static CFG_TUSB_MEM_ALIGN uint8_t rx_supp_ff_buf_3[CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ];
tu_static tu_fifo_t rx_supp_ff_3[CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO];
#if CFG_FIFO_MUTEX
osal_mutex_def_t rx_supp_ff_mutex_rd_3[CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO]; // No need for write mutex as only USB driver writes into FIFO
tu_static osal_mutex_def_t rx_supp_ff_mutex_rd_3[CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO]; // No need for write mutex as only USB driver writes into FIFO
#endif
#endif
#endif
@ -300,19 +303,18 @@ typedef struct
bool mounted; // Device opened
/*------------- From this point, data is not cleared by bus reset -------------*/
uint16_t desc_length; // Length of audio function descriptor
#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP
struct {
CFG_TUSB_MEM_ALIGN uint32_t value; // Feedback value for asynchronous mode (in 16.16 format).
CFG_TUSB_MEM_ALIGN uint32_t send_buf;
uint32_t value; // Feedback value for asynchronous mode (in 16.16 format).
uint32_t min_value; // min value according to UAC2 FMT-2.0 section 2.3.1.1.
uint32_t max_value; // max value according to UAC2 FMT-2.0 section 2.3.1.1.
uint8_t frame_shift; // bInterval-1 in unit of frame (FS), micro-frame (HS)
uint8_t compute_method;
bool format_correction;
union {
uint8_t power_of_2; // pre-computed power of 2 shift
float float_const; // pre-computed float constant
@ -322,12 +324,12 @@ typedef struct
uint32_t mclk_freq;
}fixed;
#if 0 // implement later
struct {
uint32_t nominal_value;
uint32_t threshold_bytes;
uint32_t nom_value; // In 16.16 format
uint32_t fifo_lvl_avg; // In 16.16 format
uint16_t fifo_lvl_thr; // fifo level threshold
uint16_t rate_const[2]; // pre-computed feedback/fifo_depth rate
}fifo_count;
#endif
}compute;
} feedback;
@ -365,6 +367,8 @@ typedef struct
#endif
#endif
/*------------- From this point, data is not cleared by bus reset -------------*/
// Buffer for control requests
uint8_t * ctrl_buf;
uint8_t ctrl_buf_sz;
@ -373,14 +377,10 @@ typedef struct
uint8_t * alt_setting; // We need to save the current alternate setting this way, because it is possible that there are AS interfaces which do not have an EP!
// EP Transfer buffers and FIFOs
#if CFG_TUD_AUDIO_ENABLE_EP_OUT
#if !CFG_TUD_AUDIO_ENABLE_DECODING
#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING
tu_fifo_t ep_out_ff;
#endif
#endif // CFG_TUD_AUDIO_ENABLE_EP_OUT
#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING
tu_fifo_t ep_in_ff;
#endif
@ -390,7 +390,6 @@ typedef struct
CFG_TUSB_MEM_ALIGN uint8_t ep_int_buf[6];
#endif
// Support FIFOs for software encoding and decoding
#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING
tu_fifo_t * rx_supp_ff;
@ -433,10 +432,147 @@ typedef struct
#define ITF_MEM_RESET_SIZE offsetof(audiod_function_t, ctrl_buf)
//--------------------------------------------------------------------+
// WEAK FUNCTION STUBS
//--------------------------------------------------------------------+
#if CFG_TUD_AUDIO_ENABLE_EP_IN
TU_ATTR_WEAK bool tud_audio_tx_done_pre_load_cb(uint8_t rhport, uint8_t func_id, uint8_t ep_in, uint8_t cur_alt_setting) {
(void) rhport;
(void) func_id;
(void) ep_in;
(void) cur_alt_setting;
return true;
}
TU_ATTR_WEAK bool tud_audio_tx_done_post_load_cb(uint8_t rhport, uint16_t n_bytes_copied, uint8_t func_id, uint8_t ep_in, uint8_t cur_alt_setting) {
(void) rhport;
(void) n_bytes_copied;
(void) func_id;
(void) ep_in;
(void) cur_alt_setting;
return true;
}
#endif
#if CFG_TUD_AUDIO_ENABLE_EP_OUT
TU_ATTR_WEAK bool tud_audio_rx_done_pre_read_cb(uint8_t rhport, uint16_t n_bytes_received, uint8_t func_id, uint8_t ep_out, uint8_t cur_alt_setting) {
(void) rhport;
(void) n_bytes_received;
(void) func_id;
(void) ep_out;
(void) cur_alt_setting;
return true;
}
TU_ATTR_WEAK bool tud_audio_rx_done_post_read_cb(uint8_t rhport, uint16_t n_bytes_received, uint8_t func_id, uint8_t ep_out, uint8_t cur_alt_setting) {
(void) rhport;
(void) n_bytes_received;
(void) func_id;
(void) ep_out;
(void) cur_alt_setting;
return true;
}
#endif
#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP
TU_ATTR_WEAK void tud_audio_fb_done_cb(uint8_t func_id) {
(void) func_id;
}
TU_ATTR_WEAK void tud_audio_feedback_params_cb(uint8_t func_id, uint8_t alt_itf, audio_feedback_params_t* feedback_param) {
(void) func_id;
(void) alt_itf;
feedback_param->method = AUDIO_FEEDBACK_METHOD_DISABLED;
}
TU_ATTR_WEAK bool tud_audio_feedback_format_correction_cb(uint8_t func_id) {
(void) func_id;
return CFG_TUD_AUDIO_ENABLE_FEEDBACK_FORMAT_CORRECTION;
}
#endif
TU_ATTR_WEAK TU_ATTR_FAST_FUNC void tud_audio_feedback_interval_isr(uint8_t func_id, uint32_t frame_number, uint8_t interval_shift) {
(void) func_id;
(void) frame_number;
(void) interval_shift;
}
#if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP
TU_ATTR_WEAK void tud_audio_int_done_cb(uint8_t rhport) {
(void) rhport;
}
#endif
// Invoked when audio set interface request received
TU_ATTR_WEAK bool tud_audio_set_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request) {
(void) rhport;
(void) p_request;
return true;
}
// Invoked when audio set interface request received which closes an EP
TU_ATTR_WEAK 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;
}
// Invoked when audio class specific set request received for an EP
TU_ATTR_WEAK bool tud_audio_set_req_ep_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff) {
(void) rhport;
(void) p_request;
(void) pBuff;
TU_LOG2(" No EP set request callback available!\r\n");
return false; // In case no callback function is present or request can not be conducted we stall it
}
// Invoked when audio class specific set request received for an interface
TU_ATTR_WEAK bool tud_audio_set_req_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff) {
(void) rhport;
(void) p_request;
(void) pBuff;
TU_LOG2(" No interface set request callback available!\r\n");
return false; // In case no callback function is present or request can not be conducted we stall it
}
// Invoked when audio class specific set request received for an entity
TU_ATTR_WEAK bool tud_audio_set_req_entity_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff) {
(void) rhport;
(void) p_request;
(void) pBuff;
TU_LOG2(" No entity set request callback available!\r\n");
return false; // In case no callback function is present or request can not be conducted we stall it
}
// Invoked when audio class specific get request received for an EP
TU_ATTR_WEAK bool tud_audio_get_req_ep_cb(uint8_t rhport, tusb_control_request_t const * p_request) {
(void) rhport;
(void) p_request;
TU_LOG2(" No EP get request callback available!\r\n");
return false; // Stall
}
// Invoked when audio class specific get request received for an interface
TU_ATTR_WEAK bool tud_audio_get_req_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request) {
(void) rhport;
(void) p_request;
TU_LOG2(" No interface get request callback available!\r\n");
return false; // Stall
}
// Invoked when audio class specific get request received for an entity
TU_ATTR_WEAK bool tud_audio_get_req_entity_cb(uint8_t rhport, tusb_control_request_t const * p_request) {
(void) rhport;
(void) p_request;
TU_LOG2(" No entity get request callback available!\r\n");
return false; // Stall
}
//--------------------------------------------------------------------+
// INTERNAL OBJECT & FUNCTION DECLARATION
//--------------------------------------------------------------------+
CFG_TUD_MEM_SECTION audiod_function_t _audiod_fct[CFG_TUD_AUDIO];
tu_static CFG_TUD_MEM_SECTION audiod_function_t _audiod_fct[CFG_TUD_AUDIO];
#if CFG_TUD_AUDIO_ENABLE_EP_OUT
static bool audiod_rx_done_cb(uint8_t rhport, audiod_function_t* audio, uint16_t n_bytes_received);
@ -479,7 +615,8 @@ static uint16_t audiod_tx_packet_size(const uint16_t* norminal_size, uint16_t da
#endif
#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP
static bool set_fb_params_freq(audiod_function_t* audio, uint32_t sample_freq, uint32_t mclk_freq);
static bool audiod_set_fb_params_freq(audiod_function_t* audio, uint32_t sample_freq, uint32_t mclk_freq);
static void audiod_fb_fifo_count_update(audiod_function_t* audio, uint16_t lvl_new);
#endif
bool tud_audio_n_mounted(uint8_t func_id)
@ -560,19 +697,13 @@ static bool audiod_rx_done_cb(uint8_t rhport, audiod_function_t* audio, uint16_t
uint8_t const *dummy2;
uint8_t idx_audio_fct = 0;
if (tud_audio_rx_done_pre_read_cb || tud_audio_rx_done_post_read_cb)
{
idx_audio_fct = audiod_get_audio_fct_idx(audio);
TU_VERIFY(audiod_get_AS_interface_index(audio->ep_out_as_intf_num, audio, &idxItf, &dummy2));
}
idx_audio_fct = audiod_get_audio_fct_idx(audio);
TU_VERIFY(audiod_get_AS_interface_index(audio->ep_out_as_intf_num, audio, &idxItf, &dummy2));
// Call a weak callback here - a possibility for user to get informed an audio packet was received and data gets now loaded into EP FIFO (or decoded into support RX software FIFO)
if (tud_audio_rx_done_pre_read_cb)
{
TU_VERIFY(tud_audio_rx_done_pre_read_cb(rhport, n_bytes_received, idx_audio_fct, audio->ep_out, audio->alt_setting[idxItf]));
}
TU_VERIFY(tud_audio_rx_done_pre_read_cb(rhport, n_bytes_received, idx_audio_fct, audio->ep_out, audio->alt_setting[idxItf]));
#if CFG_TUD_AUDIO_ENABLE_DECODING && CFG_TUD_AUDIO_ENABLE_EP_OUT
#if CFG_TUD_AUDIO_ENABLE_DECODING
switch (audio->format_type_rx)
{
@ -621,13 +752,17 @@ static bool audiod_rx_done_cb(uint8_t rhport, audiod_function_t* audio, uint16_t
TU_VERIFY(usbd_edpt_xfer_fifo(rhport, audio->ep_out, &audio->ep_out_ff, audio->ep_out_sz), false);
#endif
#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP
if(audio->feedback.compute_method == AUDIO_FEEDBACK_METHOD_FIFO_COUNT)
{
audiod_fb_fifo_count_update(audio, tu_fifo_count(&audio->ep_out_ff));
}
#endif
#endif
// Call a weak callback here - a possibility for user to get informed decoding was completed
if (tud_audio_rx_done_post_read_cb)
{
TU_VERIFY(tud_audio_rx_done_post_read_cb(rhport, n_bytes_received, idx_audio_fct, audio->ep_out, audio->alt_setting[idxItf]));
}
TU_VERIFY(tud_audio_rx_done_post_read_cb(rhport, n_bytes_received, idx_audio_fct, audio->ep_out, audio->alt_setting[idxItf]));
return true;
}
@ -731,6 +866,13 @@ static bool audiod_decode_type_I_pcm(uint8_t rhport, audiod_function_t* audio, u
// Number of bytes should be a multiple of CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX * CFG_TUD_AUDIO_N_CHANNELS_RX but checking makes no sense - no way to correct it
// TU_VERIFY(cnt != n_bytes);
#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP
if(audio->feedback.compute_method == AUDIO_FEEDBACK_METHOD_FIFO_COUNT)
{
audiod_fb_fifo_count_update(audio, tu_fifo_count(&audio->rx_supp_ff[0]));
}
#endif
return true;
}
#endif //CFG_TUD_AUDIO_ENABLE_DECODING
@ -854,7 +996,7 @@ static bool audiod_tx_done_cb(uint8_t rhport, audiod_function_t * audio)
// Call a weak callback here - a possibility for user to get informed former TX was completed and data gets now loaded into EP in buffer (in case FIFOs are used) or
// if no FIFOs are used the user may use this call back to load its data into the EP IN buffer by use of tud_audio_n_write_ep_in_buffer().
if (tud_audio_tx_done_pre_load_cb) TU_VERIFY(tud_audio_tx_done_pre_load_cb(rhport, idx_audio_fct, audio->ep_in, audio->alt_setting[idxItf]));
TU_VERIFY(tud_audio_tx_done_pre_load_cb(rhport, idx_audio_fct, audio->ep_in, audio->alt_setting[idxItf]));
// Send everything in ISO EP FIFO
uint16_t n_bytes_tx;
@ -917,7 +1059,7 @@ static bool audiod_tx_done_cb(uint8_t rhport, audiod_function_t * audio)
#endif
// Call a weak callback here - a possibility for user to get informed former TX was completed and how many bytes were loaded for the next frame
if (tud_audio_tx_done_post_load_cb) TU_VERIFY(tud_audio_tx_done_post_load_cb(rhport, n_bytes_tx, idx_audio_fct, audio->ep_in, audio->alt_setting[idxItf]));
TU_VERIFY(tud_audio_tx_done_post_load_cb(rhport, n_bytes_tx, idx_audio_fct, audio->ep_in, audio->alt_setting[idxItf]));
return true;
}
@ -1073,9 +1215,38 @@ static uint16_t audiod_encode_type_I_pcm(uint8_t rhport, audiod_function_t* audi
// This function is called once a transmit of a feedback packet was successfully completed. Here, we get the next feedback value to be sent
#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP
static inline bool audiod_fb_send(uint8_t rhport, audiod_function_t *audio)
static inline bool audiod_fb_send(audiod_function_t *audio)
{
return usbd_edpt_xfer(rhport, audio->ep_fb, (uint8_t *) &audio->feedback.value, 4);
bool apply_correction = (TUSB_SPEED_FULL == tud_speed_get()) && audio->feedback.format_correction;
// Format the feedback value
if (apply_correction)
{
uint8_t * fb = (uint8_t *) &audio->feedback.send_buf;
// For FS format is 10.14
*(fb++) = (audio->feedback.value >> 2) & 0xFF;
*(fb++) = (audio->feedback.value >> 10) & 0xFF;
*(fb++) = (audio->feedback.value >> 18) & 0xFF;
*fb = 0;
} else
{
audio->feedback.send_buf = audio->feedback.value;
}
// About feedback format on FS
//
// 3 variables: Format | packetSize | sendSize | Working OS:
// 16.16 4 4 Linux, Windows
// 16.16 4 3 Linux
// 16.16 3 4 Linux
// 16.16 3 3 Linux
// 10.14 4 4 Linux
// 10.14 4 3 Linux
// 10.14 3 4 Linux, OSX
// 10.14 3 3 Linux, OSX
//
// We send 3 bytes since sending packet larger than wMaxPacketSize is pretty ugly
return usbd_edpt_xfer(audio->rhport, audio->ep_fb, (uint8_t *) &audio->feedback.send_buf, apply_correction ? 3 : 4);
}
#endif
@ -1693,7 +1864,7 @@ static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const *
#endif
// Invoke callback - can be used to stop data sampling
if (tud_audio_set_itf_close_EP_cb) TU_VERIFY(tud_audio_set_itf_close_EP_cb(rhport, p_request));
TU_VERIFY(tud_audio_set_itf_close_EP_cb(rhport, p_request));
audio->ep_in = 0; // Necessary?
@ -1724,7 +1895,7 @@ static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const *
#endif
// Invoke callback - can be used to stop data sampling
if (tud_audio_set_itf_close_EP_cb) TU_VERIFY(tud_audio_set_itf_close_EP_cb(rhport, p_request));
TU_VERIFY(tud_audio_set_itf_close_EP_cb(rhport, p_request));
audio->ep_out = 0; // Necessary?
@ -1842,9 +2013,6 @@ static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const *
{
audio->ep_fb = ep_addr;
audio->feedback.frame_shift = desc_ep->bInterval -1;
// Enable SOF interrupt if callback is implemented
if (tud_audio_feedback_interval_isr) usbd_sof_enable(rhport, SOF_CONSUMER_AUDIO, true);
}
#endif
#endif // CFG_TUD_AUDIO_ENABLE_EP_OUT
@ -1857,20 +2025,23 @@ static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const *
TU_VERIFY(foundEPs == nEps);
// Invoke one callback for a final set interface
if (tud_audio_set_itf_cb) TU_VERIFY(tud_audio_set_itf_cb(rhport, p_request));
TU_VERIFY(tud_audio_set_itf_cb(rhport, p_request));
#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP
// Prepare feedback computation if callback is available
if (tud_audio_feedback_params_cb)
// Prepare feedback computation if endpoint is available
if(audio->ep_fb != 0)
{
audio_feedback_params_t fb_param;
tud_audio_feedback_params_cb(func_id, alt, &fb_param);
audio->feedback.compute_method = fb_param.method;
if(TUSB_SPEED_FULL == tud_speed_get())
audio->feedback.format_correction = tud_audio_feedback_format_correction_cb(func_id);
// Minimal/Maximum value in 16.16 format for full speed (1ms per frame) or high speed (125 us per frame)
uint32_t const frame_div = (TUSB_SPEED_FULL == tud_speed_get()) ? 1000 : 8000;
audio->feedback.min_value = (fb_param.sample_freq/frame_div - 1) << 16;
audio->feedback.min_value = ((fb_param.sample_freq - 1)/frame_div) << 16;
audio->feedback.max_value = (fb_param.sample_freq/frame_div + 1) << 16;
switch(fb_param.method)
@ -1878,20 +2049,32 @@ static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const *
case AUDIO_FEEDBACK_METHOD_FREQUENCY_FIXED:
case AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT:
case AUDIO_FEEDBACK_METHOD_FREQUENCY_POWER_OF_2:
set_fb_params_freq(audio, fb_param.sample_freq, fb_param.frequency.mclk_freq);
audiod_set_fb_params_freq(audio, fb_param.sample_freq, fb_param.frequency.mclk_freq);
break;
#if 0 // implement later
case AUDIO_FEEDBACK_METHOD_FIFO_COUNT:
{
uint64_t fb64 = ((uint64_t) fb_param.sample_freq) << 16;
audio->feedback.compute.fifo_count.nominal_value = (uint32_t) (fb64 / frame_div);
audio->feedback.compute.fifo_count.threshold_bytes = fb_param.fifo_count.threshold_bytes;
tud_audio_fb_set(audio->feedback.compute.fifo_count.nominal_value);
// Initialize the threshold level to half filled
uint16_t fifo_lvl_thr;
#if CFG_TUD_AUDIO_ENABLE_DECODING
fifo_lvl_thr = tu_fifo_depth(&audio->rx_supp_ff[0]) / 2;
#else
fifo_lvl_thr = tu_fifo_depth(&audio->ep_out_ff) / 2;
#endif
audio->feedback.compute.fifo_count.fifo_lvl_thr = fifo_lvl_thr;
audio->feedback.compute.fifo_count.fifo_lvl_avg = ((uint32_t)fifo_lvl_thr) << 16;
// Avoid 64bit division
uint32_t nominal = ((fb_param.sample_freq / 100) << 16) / (frame_div / 100);
audio->feedback.compute.fifo_count.nom_value = nominal;
audio->feedback.compute.fifo_count.rate_const[0] = (audio->feedback.max_value - nominal) / fifo_lvl_thr;
audio->feedback.compute.fifo_count.rate_const[1] = (nominal - audio->feedback.min_value) / fifo_lvl_thr;
// On HS feedback is more sensitive since packet size can vary every MSOF, could cause instability
if(tud_speed_get() == TUSB_SPEED_HIGH) {
audio->feedback.compute.fifo_count.rate_const[0] /= 8;
audio->feedback.compute.fifo_count.rate_const[1] /= 8;
}
}
break;
#endif
// nothing to do
default: break;
@ -1909,16 +2092,19 @@ static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const *
#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP
// Disable SOF interrupt if no driver has any enabled feedback EP
bool disable = true;
bool enable_sof = false;
for(uint8_t i=0; i < CFG_TUD_AUDIO; i++)
{
if (_audiod_fct[i].ep_fb != 0)
if (_audiod_fct[i].ep_fb != 0 &&
(_audiod_fct[i].feedback.compute_method == AUDIO_FEEDBACK_METHOD_FREQUENCY_FIXED ||
_audiod_fct[i].feedback.compute_method == AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT ||
_audiod_fct[i].feedback.compute_method == AUDIO_FEEDBACK_METHOD_FREQUENCY_POWER_OF_2 ))
{
disable = false;
enable_sof = true;
break;
}
}
if (disable) usbd_sof_enable(rhport, SOF_CONSUMER_AUDIO, false);
usbd_sof_enable(rhport, SOF_CONSUMER_AUDIO, enable_sof);
#endif
#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL
@ -1948,35 +2134,19 @@ static bool audiod_control_complete(uint8_t rhport, tusb_control_request_t const
if (entityID != 0)
{
if (tud_audio_set_req_entity_cb)
{
// Check if entity is present and get corresponding driver index
TU_VERIFY(audiod_verify_entity_exists(itf, entityID, &func_id));
// Check if entity is present and get corresponding driver index
TU_VERIFY(audiod_verify_entity_exists(itf, entityID, &func_id));
// Invoke callback
return tud_audio_set_req_entity_cb(rhport, p_request, _audiod_fct[func_id].ctrl_buf);
}
else
{
TU_LOG2(" No entity set request callback available!\r\n");
return false; // In case no callback function is present or request can not be conducted we stall it
}
// Invoke callback
return tud_audio_set_req_entity_cb(rhport, p_request, _audiod_fct[func_id].ctrl_buf);
}
else
{
if (tud_audio_set_req_itf_cb)
{
// Find index of audio driver structure and verify interface really exists
TU_VERIFY(audiod_verify_itf_exists(itf, &func_id));
// Find index of audio driver structure and verify interface really exists
TU_VERIFY(audiod_verify_itf_exists(itf, &func_id));
// Invoke callback
return tud_audio_set_req_itf_cb(rhport, p_request, _audiod_fct[func_id].ctrl_buf);
}
else
{
TU_LOG2(" No interface set request callback available!\r\n");
return false; // In case no callback function is present or request can not be conducted we stall it
}
// Invoke callback
return tud_audio_set_req_itf_cb(rhport, p_request, _audiod_fct[func_id].ctrl_buf);
}
}
break;
@ -1985,19 +2155,11 @@ static bool audiod_control_complete(uint8_t rhport, tusb_control_request_t const
{
uint8_t ep = TU_U16_LOW(p_request->wIndex);
if (tud_audio_set_req_ep_cb)
{
// Check if entity is present and get corresponding driver index
TU_VERIFY(audiod_verify_ep_exists(ep, &func_id));
// Check if entity is present and get corresponding driver index
TU_VERIFY(audiod_verify_ep_exists(ep, &func_id));
// Invoke callback
return tud_audio_set_req_ep_cb(rhport, p_request, _audiod_fct[func_id].ctrl_buf);
}
else
{
TU_LOG2(" No EP set request callback available!\r\n");
return false; // In case no callback function is present or request can not be conducted we stall it
}
// Invoke callback
return tud_audio_set_req_ep_cb(rhport, p_request, _audiod_fct[func_id].ctrl_buf);
}
break;
// Unknown/Unsupported recipient
@ -2054,15 +2216,7 @@ static bool audiod_control_request(uint8_t rhport, tusb_control_request_t const
// In case we got a get request invoke callback - callback needs to answer as defined in UAC2 specification page 89 - 5. Requests
if (p_request->bmRequestType_bit.direction == TUSB_DIR_IN)
{
if (tud_audio_get_req_entity_cb)
{
return tud_audio_get_req_entity_cb(rhport, p_request);
}
else
{
TU_LOG2(" No entity get request callback available!\r\n");
return false; // Stall
}
return tud_audio_get_req_entity_cb(rhport, p_request);
}
}
else
@ -2073,15 +2227,7 @@ static bool audiod_control_request(uint8_t rhport, tusb_control_request_t const
// In case we got a get request invoke callback - callback needs to answer as defined in UAC2 specification page 89 - 5. Requests
if (p_request->bmRequestType_bit.direction == TUSB_DIR_IN)
{
if (tud_audio_get_req_itf_cb)
{
return tud_audio_get_req_itf_cb(rhport, p_request);
}
else
{
TU_LOG2(" No interface get request callback available!\r\n");
return false; // Stall
}
return tud_audio_get_req_itf_cb(rhport, p_request);
}
}
}
@ -2097,15 +2243,7 @@ static bool audiod_control_request(uint8_t rhport, tusb_control_request_t const
// In case we got a get request invoke callback - callback needs to answer as defined in UAC2 specification page 89 - 5. Requests
if (p_request->bmRequestType_bit.direction == TUSB_DIR_IN)
{
if (tud_audio_get_req_ep_cb)
{
return tud_audio_get_req_ep_cb(rhport, p_request);
}
else
{
TU_LOG2(" No EP get request callback available!\r\n");
return false; // Stall
}
return tud_audio_get_req_ep_cb(rhport, p_request);
}
}
break;
@ -2160,7 +2298,7 @@ bool audiod_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint3
// I assume here, that things above are handled by PHY
// All transmission is done - what remains to do is to inform job was completed
if (tud_audio_int_done_cb) tud_audio_int_done_cb(rhport);
tud_audio_int_done_cb(rhport);
return true;
}
@ -2201,13 +2339,13 @@ bool audiod_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint3
// Transmission of feedback EP finished
if (audio->ep_fb == ep_addr)
{
if (tud_audio_fb_done_cb) tud_audio_fb_done_cb(func_id);
tud_audio_fb_done_cb(func_id);
// Schedule a transmit with the new value if EP is not busy
if (!usbd_edpt_busy(rhport, audio->ep_fb))
if (usbd_edpt_claim(rhport, audio->ep_fb))
{
// Schedule next transmission - value is changed bytud_audio_n_fb_set() in the meantime or the old value gets sent
return audiod_fb_send(rhport, audio);
return audiod_fb_send(audio);
}
}
#endif
@ -2219,7 +2357,7 @@ bool audiod_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint3
#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP
static bool set_fb_params_freq(audiod_function_t* audio, uint32_t sample_freq, uint32_t mclk_freq)
static bool audiod_set_fb_params_freq(audiod_function_t* audio, uint32_t sample_freq, uint32_t mclk_freq)
{
// Check if frame interval is within sane limits
// The interval value n_frames was taken from the descriptors within audiod_set_interface()
@ -2238,11 +2376,11 @@ static bool set_fb_params_freq(audiod_function_t* audio, uint32_t sample_freq, u
if ((mclk_freq % sample_freq) == 0 && tu_is_power_of_two(mclk_freq / sample_freq))
{
audio->feedback.compute_method = AUDIO_FEEDBACK_METHOD_FREQUENCY_POWER_OF_2;
audio->feedback.compute.power_of_2 = 16 - audio->feedback.frame_shift - tu_log2(mclk_freq / sample_freq);
audio->feedback.compute.power_of_2 = 16 - (audio->feedback.frame_shift - 1) - tu_log2(mclk_freq / sample_freq);
}
else if ( audio->feedback.compute_method == AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT)
{
audio->feedback.compute.float_const = (float)sample_freq / mclk_freq * (1UL << (16 - audio->feedback.frame_shift));
audio->feedback.compute.float_const = (float)sample_freq / mclk_freq * (1UL << (16 - (audio->feedback.frame_shift - 1)));
}
else
{
@ -2253,6 +2391,38 @@ static bool set_fb_params_freq(audiod_function_t* audio, uint32_t sample_freq, u
return true;
}
static void audiod_fb_fifo_count_update(audiod_function_t* audio, uint16_t lvl_new)
{
/* Low-pass (averaging) filter */
uint32_t lvl = audio->feedback.compute.fifo_count.fifo_lvl_avg;
lvl = (uint32_t)(((uint64_t)lvl * 63 + ((uint32_t)lvl_new << 16)) >> 6);
audio->feedback.compute.fifo_count.fifo_lvl_avg = lvl;
uint32_t const ff_lvl = lvl >> 16;
uint16_t const ff_thr = audio->feedback.compute.fifo_count.fifo_lvl_thr;
uint16_t const *rate = audio->feedback.compute.fifo_count.rate_const;
uint32_t feedback;
if(ff_lvl < ff_thr)
{
feedback = audio->feedback.compute.fifo_count.nom_value + (ff_thr - ff_lvl) * rate[0];
} else
{
feedback = audio->feedback.compute.fifo_count.nom_value - (ff_lvl - ff_thr) * rate[1];
}
if ( feedback > audio->feedback.max_value ) feedback = audio->feedback.max_value;
if ( feedback < audio->feedback.min_value ) feedback = audio->feedback.min_value;
audio->feedback.value = feedback;
// Schedule a transmit with the new value if EP is not busy - this triggers repetitive scheduling of the feedback value
if (usbd_edpt_claim(audio->rhport, audio->ep_fb))
{
audiod_fb_send(audio);
}
}
uint32_t tud_audio_feedback_update(uint8_t func_id, uint32_t cycles)
{
audiod_function_t* audio = &_audiod_fct[func_id];
@ -2270,7 +2440,7 @@ uint32_t tud_audio_feedback_update(uint8_t func_id, uint32_t cycles)
case AUDIO_FEEDBACK_METHOD_FREQUENCY_FIXED:
{
uint64_t fb64 = (((uint64_t) cycles) * audio->feedback.compute.fixed.sample_freq) << (16 - audio->feedback.frame_shift);
uint64_t fb64 = (((uint64_t) cycles) * audio->feedback.compute.fixed.sample_freq) << (16 - (audio->feedback.frame_shift - 1));
feedback = (uint32_t) (fb64 / audio->feedback.compute.fixed.mclk_freq);
}
break;
@ -2289,6 +2459,21 @@ uint32_t tud_audio_feedback_update(uint8_t func_id, uint32_t cycles)
return feedback;
}
bool tud_audio_n_fb_set(uint8_t func_id, uint32_t feedback)
{
TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL);
_audiod_fct[func_id].feedback.value = feedback;
// Schedule a transmit with the new value if EP is not busy - this triggers repetitive scheduling of the feedback value
if (usbd_edpt_claim(_audiod_fct[func_id].rhport, _audiod_fct[func_id].ep_fb))
{
return audiod_fb_send(&_audiod_fct[func_id]);
}
return true;
}
#endif
TU_ATTR_FAST_FUNC void audiod_sof_isr (uint8_t rhport, uint32_t frame_count)
@ -2315,7 +2500,7 @@ TU_ATTR_FAST_FUNC void audiod_sof_isr (uint8_t rhport, uint32_t frame_count)
uint32_t const interval = 1UL << (audio->feedback.frame_shift - hs_adjust);
if ( 0 == (frame_count & (interval-1)) )
{
if(tud_audio_feedback_interval_isr) tud_audio_feedback_interval_isr(i, frame_count, audio->feedback.frame_shift);
tud_audio_feedback_interval_isr(i, frame_count, audio->feedback.frame_shift);
}
}
}
@ -2703,44 +2888,8 @@ static uint16_t audiod_tx_packet_size(const uint16_t* norminal_size, uint16_t da
#endif
#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP
bool tud_audio_n_fb_set(uint8_t func_id, uint32_t feedback)
{
TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL);
// Format the feedback value
#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_FORMAT_CORRECTION
if ( TUSB_SPEED_FULL == tud_speed_get() )
{
uint8_t * fb = (uint8_t *) &_audiod_fct[func_id].feedback.value;
// For FS format is 10.14
*(fb++) = (feedback >> 2) & 0xFF;
*(fb++) = (feedback >> 10) & 0xFF;
*(fb++) = (feedback >> 18) & 0xFF;
// 4th byte is needed to work correctly with MS Windows
*fb = 0;
}else
#else
{
// Send value as-is, caller will choose the appropriate format
_audiod_fct[func_id].feedback.value = feedback;
}
#endif
// Schedule a transmit with the new value if EP is not busy - this triggers repetitive scheduling of the feedback value
if (!usbd_edpt_busy(_audiod_fct[func_id].rhport, _audiod_fct[func_id].ep_fb))
{
return audiod_fb_send(_audiod_fct[func_id].rhport, &_audiod_fct[func_id]);
}
return true;
}
#endif
// No security checks here - internal function only which should always succeed
uint8_t audiod_get_audio_fct_idx(audiod_function_t * audio)
static uint8_t audiod_get_audio_fct_idx(audiod_function_t * audio)
{
for (uint8_t cnt=0; cnt < CFG_TUD_AUDIO; cnt++)
{

View File

@ -3,6 +3,7 @@
*
* Copyright (c) 2020 Ha Thach (tinyusb.org)
* Copyright (c) 2020 Reinhard Panhuber
* Copyright (c) 2023 HiFiPhile
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -192,6 +193,7 @@
#endif
// Enable/disable conversion from 16.16 to 10.14 format on full-speed devices. See tud_audio_n_fb_set().
// Can be override by tud_audio_feedback_format_correction_cb()
#ifndef CFG_TUD_AUDIO_ENABLE_FEEDBACK_FORMAT_CORRECTION
#define CFG_TUD_AUDIO_ENABLE_FEEDBACK_FORMAT_CORRECTION 0 // 0 or 1
#endif
@ -242,7 +244,8 @@
// Enable encoding/decodings - for these to work, support FIFOs need to be setup in appropriate numbers and size
// The actual coding parameters of active AS alternate interface is parsed from the descriptors
// The item size of the FIFO is always fixed to one i.e. bytes! Furthermore, the actively used FIFO depth is reconfigured such that the depth is a multiple of the current sample size in order to avoid samples to get split up in case of a wrap in the FIFO ring buffer (depth = (max_depth / sampe_sz) * sampe_sz)!
// The item size of the FIFO is always fixed to one i.e. bytes! Furthermore, the actively used FIFO depth is reconfigured such that the depth is a multiple
// of the current sample size in order to avoid samples to get split up in case of a wrap in the FIFO ring buffer (depth = (max_depth / sample_sz) * sample_sz)!
// This is important to remind in case you use DMAs! If the sample sizes changes, the DMA MUST BE RECONFIGURED just like the FIFOs for a different depth!!!
// For PCM encoding/decoding
@ -446,63 +449,80 @@ static inline bool tud_audio_int_write (const audio_interru
bool tud_audio_buffer_and_schedule_control_xfer(uint8_t rhport, tusb_control_request_t const * p_request, void* data, uint16_t len);
//--------------------------------------------------------------------+
// Application Callback API (weak is optional)
// Application Callback API
//--------------------------------------------------------------------+
#if CFG_TUD_AUDIO_ENABLE_EP_IN
TU_ATTR_WEAK bool tud_audio_tx_done_pre_load_cb(uint8_t rhport, uint8_t func_id, uint8_t ep_in, uint8_t cur_alt_setting);
TU_ATTR_WEAK bool tud_audio_tx_done_post_load_cb(uint8_t rhport, uint16_t n_bytes_copied, uint8_t func_id, uint8_t ep_in, uint8_t cur_alt_setting);
bool tud_audio_tx_done_pre_load_cb(uint8_t rhport, uint8_t func_id, uint8_t ep_in, uint8_t cur_alt_setting);
bool tud_audio_tx_done_post_load_cb(uint8_t rhport, uint16_t n_bytes_copied, uint8_t func_id, uint8_t ep_in, uint8_t cur_alt_setting);
#endif
#if CFG_TUD_AUDIO_ENABLE_EP_OUT
TU_ATTR_WEAK bool tud_audio_rx_done_pre_read_cb(uint8_t rhport, uint16_t n_bytes_received, uint8_t func_id, uint8_t ep_out, uint8_t cur_alt_setting);
TU_ATTR_WEAK bool tud_audio_rx_done_post_read_cb(uint8_t rhport, uint16_t n_bytes_received, uint8_t func_id, uint8_t ep_out, uint8_t cur_alt_setting);
bool tud_audio_rx_done_pre_read_cb(uint8_t rhport, uint16_t n_bytes_received, uint8_t func_id, uint8_t ep_out, uint8_t cur_alt_setting);
bool tud_audio_rx_done_post_read_cb(uint8_t rhport, uint16_t n_bytes_received, uint8_t func_id, uint8_t ep_out, uint8_t cur_alt_setting);
#endif
#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP
TU_ATTR_WEAK void tud_audio_fb_done_cb(uint8_t func_id);
void tud_audio_fb_done_cb(uint8_t func_id);
// determined by the user itself and set by use of tud_audio_n_fb_set(). The feedback value may be determined e.g. from some fill status of some FIFO buffer. Advantage: No ISR interrupt is enabled, hence the CPU need not to handle an ISR every 1ms or 125us and thus less CPU load, disadvantage: typically a larger FIFO is needed to compensate for jitter (e.g. 8 frames), i.e. a larger delay is introduced.
// Note about feedback calculation
//
// Option 1 - AUDIO_FEEDBACK_METHOD_FIFO_COUNT
// Feedback value is calculated within the audio driver by regulating the FIFO level to half fill.
// Advantage: No ISR interrupt is enabled, hence the CPU need not to handle an ISR every 1ms or 125us and thus less CPU load, well tested
// (Windows, Linux, OSX) with a reliable result so far.
// Disadvantage: A FIFO of minimal 4 frames is needed to compensate for jitter, an average delay of 2 frames is introduced.
//
// Option 2 - AUDIO_FEEDBACK_METHOD_FREQUENCY_FIXED / AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT
// Feedback value is calculated within the audio driver by use of SOF interrupt. The driver needs information about the master clock f_m from
// which the audio sample frequency f_s is derived, f_s itself, and the cycle count of f_m at time of the SOF interrupt (e.g. by use of a hardware counter).
// See tud_audio_set_fb_params() and tud_audio_feedback_update()
// Advantage: Reduced jitter in the feedback value computation, hence, the receive FIFO can be smaller and thus a smaller delay is possible.
// Disadvantage: higher CPU load due to SOF ISR handling every frame i.e. 1ms or 125us. (The most critical point is the reading of the cycle counter value of f_m.
// It is read from within the SOF ISR - see: audiod_sof() -, hence, the ISR must has a high priority such that no software dependent "random" delay i.e. jitter is introduced).
// Long-term drift could occur since error is accumulated.
//
// Option 3 - manual
// Determined by the user itself and set by use of tud_audio_n_fb_set(). The feedback value may be determined e.g. from some fill status of some FIFO buffer.
// Advantage: No ISR interrupt is enabled, hence the CPU need not to handle an ISR every 1ms or 125us and thus less CPU load.
// Disadvantage: typically a larger FIFO is needed to compensate for jitter (e.g. 6 frames), i.e. a larger delay is introduced.
// Feedback value is calculated within the audio driver by use of SOF interrupt. The driver needs information about the master clock f_m from which the audio sample frequency f_s is derived, f_s itself, and the cycle count of f_m at time of the SOF interrupt (e.g. by use of a hardware counter) - see tud_audio_set_fb_params(). Advantage: Reduced jitter in the feedback value computation, hence, the receive FIFO can be smaller (e.g. 2 frames) and thus a smaller delay is possible, disadvantage: higher CPU load due to SOF ISR handling every frame i.e. 1ms or 125us. This option is a great starting point to try the SOF ISR option but depending on your hardware setup (performance of the CPU) it might not work. If so, figure out why and use the next option. (The most critical point is the reading of the cycle counter value of f_m. It is read from within the SOF ISR - see: audiod_sof() -, hence, the ISR must has a high priority such that no software dependent "random" delay i.e. jitter is introduced).
// Feedback value is determined by the user by use of SOF interrupt. The user may use tud_audio_sof_isr() which is called every SOF (of course only invoked when an alternate interface other than zero was set). The number of frames used to determine the feedback value for the currently active alternate setting can be get by tud_audio_get_fb_n_frames(). The feedback value must be set by use of tud_audio_n_fb_set().
// This function is used to provide data rate feedback from an asynchronous sink. Feedback value will be sent at FB endpoint interval till it's changed.
//
// The feedback format is specified to be 16.16 for HS and 10.14 for FS devices (see Universal Serial Bus Specification Revision 2.0 5.12.4.2). By default,
// the choice of format is left to the caller and feedback argument is sent as-is. If CFG_TUD_AUDIO_ENABLE_FEEDBACK_FORMAT_CORRECTION is set, then tinyusb
// expects 16.16 format and handles the conversion to 10.14 on FS.
// the choice of format is left to the caller and feedback argument is sent as-is. If CFG_TUD_AUDIO_ENABLE_FEEDBACK_FORMAT_CORRECTION is set or tud_audio_feedback_format_correction_cb()
// return true, then tinyusb expects 16.16 format and handles the conversion to 10.14 on FS.
//
// Note that due to a bug in its USB Audio 2.0 driver, Windows currently requires 16.16 format for _all_ USB 2.0 devices. On Linux and it seems the
// driver can work with either format.
//
// Note that due to a bug in its USB Audio 2.0 driver, Windows currently requires 16.16 format for _all_ USB 2.0 devices. On Linux and macOS it seems the
// driver can work with either format. So a good compromise is to keep format correction disabled and stick to 16.16 format.
// Feedback value can be determined from within the SOF ISR of the audio driver. This should reduce jitter. If the feature is used, the user can not set the feedback value.
//
// Determine feedback value - The feedback method is described in 5.12.4.2 of the USB 2.0 spec
// Boiled down, the feedback value Ff = n_samples / (micro)frame.
// Since an accuracy of less than 1 Sample / second is desired, at least n_frames = ceil(2^K * f_s / f_m) frames need to be measured, where K = 10 for full speed and K = 13 for high speed, f_s is the sampling frequency e.g. 48 kHz and f_m is the cpu clock frequency e.g. 100 MHz (or any other master clock whose clock count is available and locked to f_s)
// Since an accuracy of less than 1 Sample / second is desired, at least n_frames = ceil(2^K * f_s / f_m) frames need to be measured, where K = 10 for full speed and K = 13
// for high speed, f_s is the sampling frequency e.g. 48 kHz and f_m is the cpu clock frequency e.g. 100 MHz (or any other master clock whose clock count is available and locked to f_s)
// The update interval in the (4.10.2.1) Feedback Endpoint Descriptor must be less or equal to 2^(K - P), where P = min( ceil(log2(f_m / f_s)), K)
// feedback = n_cycles / n_frames * f_s / f_m in 16.16 format, where n_cycles are the number of main clock cycles within fb_n_frames
bool tud_audio_n_fb_set(uint8_t func_id, uint32_t feedback);
static inline bool tud_audio_fb_set(uint32_t feedback);
// Update feedback value with passed cycles since last time this update function is called.
// Update feedback value with passed MCLK cycles since last time this update function is called.
// Typically called within tud_audio_sof_isr(). Required tud_audio_feedback_params_cb() is implemented
// This function will also call tud_audio_feedback_set()
// return feedback value in 16.16 for reference (0 for error)
// Example :
// binterval=3 (4ms); FS = 48kHz; MCLK = 12.288MHz
// In 4 SOF MCLK counted 49152 cycles
uint32_t tud_audio_feedback_update(uint8_t func_id, uint32_t cycles);
enum {
AUDIO_FEEDBACK_METHOD_DISABLED,
AUDIO_FEEDBACK_METHOD_FREQUENCY_FIXED,
AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT,
AUDIO_FEEDBACK_METHOD_FREQUENCY_POWER_OF_2,
// impelemnt later
// AUDIO_FEEDBACK_METHOD_FIFO_COUNT
AUDIO_FEEDBACK_METHOD_FREQUENCY_POWER_OF_2, // For driver internal use only
AUDIO_FEEDBACK_METHOD_FIFO_COUNT
};
typedef struct {
@ -514,52 +534,50 @@ typedef struct {
uint32_t mclk_freq; // Main clock frequency in Hz i.e. master clock to which sample clock is based on
}frequency;
#if 0 // implement later
struct {
uint32_t threshold_bytes; // minimum number of bytes received to be considered as filled/ready
}fifo_count;
#endif
};
}audio_feedback_params_t;
// Invoked when needed to set feedback parameters
TU_ATTR_WEAK void tud_audio_feedback_params_cb(uint8_t func_id, uint8_t alt_itf, audio_feedback_params_t* feedback_param);
void tud_audio_feedback_params_cb(uint8_t func_id, uint8_t alt_itf, audio_feedback_params_t* feedback_param);
// Callback in ISR context, invoked periodically according to feedback endpoint bInterval.
// Could be used to compute and update feedback value, should be placed in RAM if possible
// frame_number : current SOF count
// interval_shift: number of bit shift i.e log2(interval) from Feedback endpoint descriptor
TU_ATTR_WEAK TU_ATTR_FAST_FUNC void tud_audio_feedback_interval_isr(uint8_t func_id, uint32_t frame_number, uint8_t interval_shift);
TU_ATTR_FAST_FUNC void tud_audio_feedback_interval_isr(uint8_t func_id, uint32_t frame_number, uint8_t interval_shift);
// (Full-Speed only) Callback to set feedback format correction is applied or not,
// default to CFG_TUD_AUDIO_ENABLE_FEEDBACK_FORMAT_CORRECTION if not implemented.
bool tud_audio_feedback_format_correction_cb(uint8_t func_id);
#endif // CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP
#if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP
TU_ATTR_WEAK void tud_audio_int_done_cb(uint8_t rhport);
void tud_audio_int_done_cb(uint8_t rhport);
#endif
// Invoked when audio set interface request received
TU_ATTR_WEAK bool tud_audio_set_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request);
bool tud_audio_set_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request);
// Invoked when audio set interface request received which closes an EP
TU_ATTR_WEAK bool tud_audio_set_itf_close_EP_cb(uint8_t rhport, tusb_control_request_t const * p_request);
bool tud_audio_set_itf_close_EP_cb(uint8_t rhport, tusb_control_request_t const * p_request);
// Invoked when audio class specific set request received for an EP
TU_ATTR_WEAK bool tud_audio_set_req_ep_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff);
bool tud_audio_set_req_ep_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff);
// Invoked when audio class specific set request received for an interface
TU_ATTR_WEAK bool tud_audio_set_req_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff);
bool tud_audio_set_req_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff);
// Invoked when audio class specific set request received for an entity
TU_ATTR_WEAK bool tud_audio_set_req_entity_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff);
bool tud_audio_set_req_entity_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff);
// Invoked when audio class specific get request received for an EP
TU_ATTR_WEAK bool tud_audio_get_req_ep_cb(uint8_t rhport, tusb_control_request_t const * p_request);
bool tud_audio_get_req_ep_cb(uint8_t rhport, tusb_control_request_t const * p_request);
// Invoked when audio class specific get request received for an interface
TU_ATTR_WEAK bool tud_audio_get_req_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request);
bool tud_audio_get_req_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request);
// Invoked when audio class specific get request received for an entity
TU_ATTR_WEAK bool tud_audio_get_req_entity_cb(uint8_t rhport, tusb_control_request_t const * p_request);
bool tud_audio_get_req_entity_cb(uint8_t rhport, tusb_control_request_t const * p_request);
//--------------------------------------------------------------------+
// Inline Functions

View File

@ -434,8 +434,8 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ
/* Standard AS Isochronous Feedback Endpoint Descriptor(4.10.2.1) */
#define TUD_AUDIO_DESC_STD_AS_ISO_FB_EP_LEN 7
#define TUD_AUDIO_DESC_STD_AS_ISO_FB_EP(_ep, _interval) \
TUD_AUDIO_DESC_STD_AS_ISO_FB_EP_LEN, TUSB_DESC_ENDPOINT, _ep, (uint8_t) ((uint8_t)TUSB_XFER_ISOCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_NO_SYNC | (uint8_t)TUSB_ISO_EP_ATT_EXPLICIT_FB), U16_TO_U8S_LE(4), _interval
#define TUD_AUDIO_DESC_STD_AS_ISO_FB_EP(_ep, _epsize, _interval) \
TUD_AUDIO_DESC_STD_AS_ISO_FB_EP_LEN, TUSB_DESC_ENDPOINT, _ep, (uint8_t) ((uint8_t)TUSB_XFER_ISOCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_NO_SYNC | (uint8_t)TUSB_ISO_EP_ATT_EXPLICIT_FB), U16_TO_U8S_LE(_epsize), _interval
// AUDIO simple descriptor (UAC2) for 1 microphone input
// - 1 Input Terminal, 1 Feature Unit (Mute and Volume Control), 1 Output Terminal, 1 Clock Source
@ -553,7 +553,7 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ
+ TUD_AUDIO_DESC_CS_AS_ISO_EP_LEN\
+ TUD_AUDIO_DESC_STD_AS_ISO_FB_EP_LEN)
#define TUD_AUDIO_SPEAKER_MONO_FB_DESCRIPTOR(_itfnum, _stridx, _nBytesPerSample, _nBitsUsedPerSample, _epout, _epsize, _epfb) \
#define TUD_AUDIO_SPEAKER_MONO_FB_DESCRIPTOR(_itfnum, _stridx, _nBytesPerSample, _nBitsUsedPerSample, _epout, _epoutsize, _epfb, _epfbsize) \
/* Standard Interface Association Descriptor (IAD) */\
TUD_AUDIO_DESC_IAD(/*_firstitf*/ _itfnum, /*_nitfs*/ 0x02, /*_stridx*/ 0x00),\
/* Standard AC Interface Descriptor(4.7.1) */\
@ -567,7 +567,7 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ
/* Output Terminal Descriptor(4.7.2.5) */\
TUD_AUDIO_DESC_OUTPUT_TERM(/*_termid*/ 0x03, /*_termtype*/ AUDIO_TERM_TYPE_OUT_DESKTOP_SPEAKER, /*_assocTerm*/ 0x01, /*_srcid*/ 0x02, /*_clkid*/ 0x04, /*_ctrl*/ 0x0000, /*_stridx*/ 0x00),\
/* Feature Unit Descriptor(4.7.2.8) */\
TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL(/*_unitid*/ 0x02, /*_srcid*/ 0x01, /*_ctrlch0master*/ 0 * (AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS), /*_ctrlch1*/ 0 * (AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS), /*_stridx*/ 0x00),\
TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL(/*_unitid*/ 0x02, /*_srcid*/ 0x01, /*_ctrlch0master*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch1*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, /*_stridx*/ 0x00),\
/* Standard AS Interface Descriptor(4.9.1) */\
/* Interface 1, Alternate 0 - default alternate setting with 0 bandwidth */\
TUD_AUDIO_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum) + 1), /*_altset*/ 0x00, /*_nEPs*/ 0x00, /*_stridx*/ 0x00),\
@ -579,11 +579,11 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ
/* Type I Format Type Descriptor(2.3.1.6 - Audio Formats) */\
TUD_AUDIO_DESC_TYPE_I_FORMAT(_nBytesPerSample, _nBitsUsedPerSample),\
/* Standard AS Isochronous Audio Data Endpoint Descriptor(4.10.1.1) */\
TUD_AUDIO_DESC_STD_AS_ISO_EP(/*_ep*/ _epout, /*_attr*/ (uint8_t) ((uint8_t)TUSB_XFER_ISOCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_ASYNCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_DATA), /*_maxEPsize*/ _epsize, /*_interval*/ 0x01),\
TUD_AUDIO_DESC_STD_AS_ISO_EP(/*_ep*/ _epout, /*_attr*/ (uint8_t) ((uint8_t)TUSB_XFER_ISOCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_ASYNCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_DATA), /*_maxEPsize*/ _epoutsize, /*_interval*/ 0x01),\
/* Class-Specific AS Isochronous Audio Data Endpoint Descriptor(4.10.1.2) */\
TUD_AUDIO_DESC_CS_AS_ISO_EP(/*_attr*/ AUDIO_CS_AS_ISO_DATA_EP_ATT_NON_MAX_PACKETS_OK, /*_ctrl*/ AUDIO_CTRL_NONE, /*_lockdelayunit*/ AUDIO_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_UNDEFINED, /*_lockdelay*/ 0x0000),\
/* Standard AS Isochronous Feedback Endpoint Descriptor(4.10.2.1) */\
TUD_AUDIO_DESC_STD_AS_ISO_FB_EP(/*_ep*/ _epfb, /*_interval*/ 1)\
TUD_AUDIO_DESC_STD_AS_ISO_FB_EP(/*_ep*/ _epfb, /*_epsize*/ _epfbsize, /*_interval*/ 1)
// Calculate wMaxPacketSize of Endpoints
#define TUD_AUDIO_EP_SIZE(_maxFrequency, _nBytesPerSample, _nChannels) \