From a48a5bd7c0f64931f9581e90c025efd13754757f Mon Sep 17 00:00:00 2001 From: HiFiPhile Date: Tue, 21 Nov 2023 14:39:32 +0100 Subject: [PATCH] Add an optional HID debug interface. --- .../device/uac2_speaker_fb/src/audio_debug.py | 62 +++++++++++++ .../device/uac2_speaker_fb/src/common_types.h | 52 +++++++++++ examples/device/uac2_speaker_fb/src/main.c | 86 +++++++++++++++++-- .../device/uac2_speaker_fb/src/tusb_config.h | 26 ++++-- .../uac2_speaker_fb/src/usb_descriptors.c | 50 ++++++++++- .../uac2_speaker_fb/src/usb_descriptors.h | 9 +- src/class/audio/audio_device.c | 1 - 7 files changed, 263 insertions(+), 23 deletions(-) create mode 100644 examples/device/uac2_speaker_fb/src/audio_debug.py create mode 100644 examples/device/uac2_speaker_fb/src/common_types.h diff --git a/examples/device/uac2_speaker_fb/src/audio_debug.py b/examples/device/uac2_speaker_fb/src/audio_debug.py new file mode 100644 index 0000000000..0a7f8d4835 --- /dev/null +++ b/examples/device/uac2_speaker_fb/src/audio_debug.py @@ -0,0 +1,62 @@ +# Install python3 HID package https://pypi.org/project/hid/ +import hid +from ctypes import * +import matplotlib.pyplot as plt +import matplotlib.animation as animation + +# 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): + + for i in range(100): + str_in = dev.read(64, 10) + if str_in: + 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) + + # Limit to 1000 items + fifo_avg = fifo_avg[-1000:] + fifo_cnt = fifo_cnt[-1000:] + + # 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=100) + plt.show() diff --git a/examples/device/uac2_speaker_fb/src/common_types.h b/examples/device/uac2_speaker_fb/src/common_types.h new file mode 100644 index 0000000000..174e266714 --- /dev/null +++ b/examples/device/uac2_speaker_fb/src/common_types.h @@ -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 diff --git a/examples/device/uac2_speaker_fb/src/main.c b/examples/device/uac2_speaker_fb/src/main.c index 1574dcc494..ca8a9209c9 100644 --- a/examples/device/uac2_speaker_fb/src/main.c +++ b/examples/device/uac2_speaker_fb/src/main.c @@ -29,6 +29,7 @@ #include "bsp/board_api.h" #include "tusb.h" #include "usb_descriptors.h" +#include "common_types.h" //--------------------------------------------------------------------+ // MACRO CONSTANT TYPEDEF PROTOTYPES @@ -88,6 +89,11 @@ 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; +#endif + /*------------- MAIN -------------*/ int main(void) { @@ -107,6 +113,9 @@ int main(void) tud_task(); // TinyUSB device task audio_task(); led_blinking_task(); +#if CFG_AUDIO_DEBUG + audio_debug_task(); +#endif } } @@ -141,6 +150,10 @@ 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) { @@ -283,10 +296,6 @@ static bool tud_audio_feature_unit_set_request(uint8_t rhport, audio_control_req } } -//--------------------------------------------------------------------+ -// Application Callback API Implementations -//--------------------------------------------------------------------+ - // 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) { @@ -342,6 +351,10 @@ bool tud_audio_set_itf_cb(uint8_t rhport, tusb_control_request_t const * p_reque if (ITF_NUM_AUDIO_STREAMING == itf && alt != 0) blink_interval_ms = BLINK_STREAMING; +#if CFG_AUDIO_DEBUG + current_alt_settings = alt; +#endif + return true; } @@ -353,14 +366,15 @@ void tud_audio_feedback_params_cb(uint8_t func_id, uint8_t alt_itf, audio_feedba feedback_param->method = AUDIO_FEEDBACK_METHOD_FIFO_COUNT; feedback_param->sample_freq = current_sample_rate; } + //--------------------------------------------------------------------+ // AUDIO Task //--------------------------------------------------------------------+ void audio_task(void) { - // Yet to be filled - e.g. write audio to I2S buffer. - // Here we simulate a I2S transmit callback every 1ms. + // 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 @@ -399,3 +413,63 @@ void led_blinking_task(void) 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; + + uint16_t fifo_count = tud_audio_available(); + static uint32_t fifo_count_avg; + + // Same averaging method used in UAC2 class + fifo_count_avg = (uint32_t)(((uint64_t)fifo_count_avg * 63 + ((uint32_t)fifo_count << 16)) >> 6); + + 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]; + } + + 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; +} + +#endif diff --git a/examples/device/uac2_speaker_fb/src/tusb_config.h b/examples/device/uac2_speaker_fb/src/tusb_config.h index b56d9181e9..56a39f981d 100644 --- a/examples/device/uac2_speaker_fb/src/tusb_config.h +++ b/examples/device/uac2_speaker_fb/src/tusb_config.h @@ -32,6 +32,7 @@ extern "C" { #endif #include "usb_descriptors.h" + //--------------------------------------------------------------------+ // Board Specific Configuration //--------------------------------------------------------------------+ @@ -59,6 +60,8 @@ extern "C" { #define CFG_TUSB_OS OPT_OS_NONE #endif +// It's recommanded 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 @@ -88,16 +91,29 @@ extern "C" { // 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_HID 0 #define CFG_TUD_MIDI 0 -#define CFG_TUD_AUDIO 1 #define CFG_TUD_VENDOR 0 //-------------------------------------------------------------------- @@ -126,13 +142,13 @@ extern "C" { #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 +#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 +#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 +#define CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ 64 #ifdef __cplusplus } diff --git a/examples/device/uac2_speaker_fb/src/usb_descriptors.c b/examples/device/uac2_speaker_fb/src/usb_descriptors.c index 5b9595f979..864b689ff0 100644 --- a/examples/device/uac2_speaker_fb/src/usb_descriptors.c +++ b/examples/device/uac2_speaker_fb/src/usb_descriptors.c @@ -26,6 +26,7 @@ #include "bsp/board_api.h" #include "tusb.h" #include "usb_descriptors.h" +#include "common_types.h" /* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. @@ -71,38 +72,76 @@ uint8_t const * tud_descriptor_device_cb(void) 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 //--------------------------------------------------------------------+ -#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_AUDIO_SPEAKER_STEREO_FB_DESC_LEN) +#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[] = @@ -111,7 +150,12 @@ uint8_t const desc_configuration[] = 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 - 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) + 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), + +#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 }; // Invoked when received GET CONFIGURATION DESCRIPTOR @@ -142,7 +186,7 @@ char const *string_desc_arr[] = "TinyUSB", // 1: Manufacturer "TinyUSB Speaker", // 2: Product NULL, // 3: Serials will use unique ID if possible - "UAC2", // 4: Audio Interface + "UAC2 Speaker", // 4: Audio Interface }; static uint16_t _desc_str[32 + 1]; diff --git a/examples/device/uac2_speaker_fb/src/usb_descriptors.h b/examples/device/uac2_speaker_fb/src/usb_descriptors.h index e45396f25e..1ae423cdd3 100644 --- a/examples/device/uac2_speaker_fb/src/usb_descriptors.h +++ b/examples/device/uac2_speaker_fb/src/usb_descriptors.h @@ -32,13 +32,6 @@ #define UAC2_ENTITY_FEATURE_UNIT 0x02 #define UAC2_ENTITY_OUTPUT_TERMINAL 0x03 -enum -{ - ITF_NUM_AUDIO_CONTROL = 0, - ITF_NUM_AUDIO_STREAMING, - ITF_NUM_TOTAL -}; - #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\ @@ -84,6 +77,6 @@ enum /* 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, /*_interval*/ 4)\ + TUD_AUDIO_DESC_STD_AS_ISO_FB_EP(/*_ep*/ _epfb, /*_interval*/ TUD_OPT_HIGH_SPEED ? 4 : 1)\ #endif diff --git a/src/class/audio/audio_device.c b/src/class/audio/audio_device.c index 3966378214..989387738a 100644 --- a/src/class/audio/audio_device.c +++ b/src/class/audio/audio_device.c @@ -2269,7 +2269,6 @@ static bool audiod_set_fb_params_freq(audiod_function_t* audio, uint32_t sample_ static void audiod_fb_fifo_count_update(audiod_function_t* audio, uint16_t lvl_new) { - tusb_speed_t speed = tud_speed_get(); /* 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);