diff options
Diffstat (limited to 'examples')
| -rw-r--r-- | examples/host/audio_host/CMakeLists.txt | 30 | ||||
| -rw-r--r-- | examples/host/audio_host/Makefile | 14 | ||||
| -rw-r--r-- | examples/host/audio_host/README.md | 98 | ||||
| -rw-r--r-- | examples/host/audio_host/src/app.h | 26 | ||||
| -rw-r--r-- | examples/host/audio_host/src/audio_app.c | 226 | ||||
| -rw-r--r-- | examples/host/audio_host/src/main.c | 73 | ||||
| -rw-r--r-- | examples/host/audio_host/src/tusb_config.h | 103 |
7 files changed, 570 insertions, 0 deletions
diff --git a/examples/host/audio_host/CMakeLists.txt b/examples/host/audio_host/CMakeLists.txt new file mode 100644 index 000000000..0891f5829 --- /dev/null +++ b/examples/host/audio_host/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.20) + +include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake) + +project(audio_host C CXX ASM) + +# Checks this example is valid for the family and initializes the project +family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}) + +# Espressif has its own cmake build system +if(FAMILY STREQUAL "espressif") + return() +endif() + +add_executable(${PROJECT_NAME}) + +# Example source +target_sources(${PROJECT_NAME} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src/audio_app.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c + ) + +# Example include +target_include_directories(${PROJECT_NAME} 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_host_example(${PROJECT_NAME} noos) diff --git a/examples/host/audio_host/Makefile b/examples/host/audio_host/Makefile new file mode 100644 index 000000000..5c2e23184 --- /dev/null +++ b/examples/host/audio_host/Makefile @@ -0,0 +1,14 @@ +include ../../../hw/bsp/family_support.mk + +INC += \ + src \ + + +# Example source +EXAMPLE_SOURCE += \ + src/audio_app.c \ + src/main.c + +SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE)) + +include ../../../hw/bsp/family_rules.mk diff --git a/examples/host/audio_host/README.md b/examples/host/audio_host/README.md new file mode 100644 index 000000000..4d8f66b71 --- /dev/null +++ b/examples/host/audio_host/README.md @@ -0,0 +1,98 @@ +# USB Audio Host Example + +This example demonstrates how to use TinyUSB's USB Audio Host driver (TUH_AUDIO) to communicate with a UAC 1.0 compatible USB Audio Device. + +## Features + +- Enumerates and mounts USB Audio Class 1.0 devices +- Receives audio data from IN endpoint (e.g., microphone) +- Sends audio data to OUT endpoint (e.g., speaker) +- Sets sampling frequency via control requests +- Demonstrates isochronous transfer handling + +## Supported Devices + +This example supports any UAC 1.0 compliant USB Audio device, such as: +- USB microphones +- USB speakers/headphones +- USB audio interfaces + +## Building + +### Using CMake (recommended) + +```bash +cd examples/host/audio_host +mkdir -p build && cd build +cmake -DBOARD=<your_board> -G Ninja .. +cmake --build . +``` + +Replace `<your_board>` with your target board name (e.g., `raspberry_pi_pico`, `stm32f407disco`, etc.) + +### Using Make + +```bash +cd examples/host/audio_host +make BOARD=<your_board> all +``` + +## Flashing + +```bash +# Using CMake +ninja flash + +# Using Make +make BOARD=<your_board> flash +``` + +## Usage + +1. Build and flash the example to your board +2. Connect a USB Audio device (UAC 1.0) to the USB host port +3. Open a serial terminal to view output +4. The example will: + - Print device information when mounted + - Set sampling frequency to 48kHz + - Receive audio samples from the device (IN endpoint) + - Send test sine wave audio to the device (OUT endpoint) + +## Serial Output Example + +``` +TinyUSB Host USB Audio Example +Connect a USB Audio Device (UAC 1.0) to test +Audio device mounted: idx=0, daddr=1 + --- Microphone --- + IN EP: 0x81 (max size: 192) + Input Terminal: ID=1, Type=0x0201, Channels=1 + Format Type: 1, Channels: 1, SubFrameSize: 2, BitResolution: 16 + Sampling Freq: Discrete, count=4 + Freq[0]: 44100 Hz + Freq[1]: 48000 Hz + Freq[2]: 96000 Hz + Freq[3]: 192000 Hz + --- Speaker --- + OUT EP: 0x02 (max size: 192) + Output Terminal: ID=2, Type=0x0301 + Format Type: 1, Channels: 2, SubFrameSize: 2, BitResolution: 16 + Sampling Freq: Continuous range 8000 Hz - 48000 Hz + Feature Unit: ID=3, SourceID=1 + Setting IN sampling frequency to 48000 Hz + Setting OUT sampling frequency to 48000 Hz + Sampling frequency set OK, ready for isochronous transfer +``` + +## Configuration + +Edit `src/tusb_config.h` to modify: +- `CFG_TUH_AUDIO_MAX`: Maximum number of audio devices supported +- `CFG_TUH_AUDIO_EPIN_BUFSIZE`: IN endpoint buffer size +- `CFG_TUH_AUDIO_EPOUT_BUFSIZE`: OUT endpoint buffer size + +## Notes + +- This example uses isochronous transfers which require precise timing +- For production applications, synchronize audio transfers with the device's audio clock +- The example sends a simple sine wave for testing; replace with actual audio data in real applications diff --git a/examples/host/audio_host/src/app.h b/examples/host/audio_host/src/app.h new file mode 100644 index 000000000..a807aeaa6 --- /dev/null +++ b/examples/host/audio_host/src/app.h @@ -0,0 +1,26 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2025 TinyUSB contributors + * + * 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. + */ + +#ifndef TUSB_TINYUSB_EXAMPLES_APP_H +#define TUSB_TINYUSB_EXAMPLES_APP_H + +#include <stdio.h> +#include <stdbool.h> +#include <stdint.h> + +void audio_app_task(void); + +#endif diff --git a/examples/host/audio_host/src/audio_app.c b/examples/host/audio_host/src/audio_app.c new file mode 100644 index 000000000..ac7596d91 --- /dev/null +++ b/examples/host/audio_host/src/audio_app.c @@ -0,0 +1,226 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2025 TinyUSB contributors + * + * 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. + */ + +#include <stdio.h> +#include "bsp/board_api.h" +#include "tusb.h" +#include "app.h" + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ + +static bool audio_mounted = false; +static uint8_t audio_dev_addr = 0; +static uint8_t audio_idx = 0; +static uint8_t audio_ep_in = 0; +static uint8_t audio_ep_out = 0; +static uint32_t sampling_freq = 48000; // Default sampling frequency (Hz) +static uint8_t audio_mic_channels = 1; +static volatile bool audio_rx_busy = false; // Track IN endpoint transfer state +static volatile bool audio_tx_busy = false; // Track OUT endpoint transfer state +static volatile bool audio_ready = false; // Wait for sampling freq set before starting isochronous transfer +static uint8_t audio_ac_itf = 0; // Audio Control interface number +static uint8_t audio_feature_unit_id = 0; // Feature Unit ID + +static uint8_t audio_rx_buffer[CFG_TUH_AUDIO_EPIN_BUFSIZE] __attribute__((aligned(4))); +static uint8_t audio_tx_buffer[CFG_TUH_AUDIO_EPOUT_BUFSIZE] __attribute__((aligned(4))); + +//--------------------------------------------------------------------+ +// Helper Functions +//--------------------------------------------------------------------+ + +// Mono (96 bytes, 48 samples) -> Stereo (192 bytes) +static void mono_to_stereo(const uint8_t *mono, uint8_t *stereo, uint16_t mono_samples) { + for (uint16_t i = 0; i < mono_samples; i++) { + // Copy 2 bytes (one int16 sample) to left channel + stereo[i * 4] = mono[i * 2]; + stereo[i * 4 + 1] = mono[i * 2 + 1]; + // Copy same 2 bytes to right channel + stereo[i * 4 + 2] = mono[i * 2]; + stereo[i * 4 + 3] = mono[i * 2 + 1]; + } +} + +// Print sampling frequency info for an AS interface +static void print_sampling_freq(const tuh_audio_as_info_t *as) { + if (as->sam_freq_type == 0) { + printf(" Sampling Freq: Continuous range %lu Hz - %lu Hz\r\n", (unsigned long)as->sam_freq_lower, + (unsigned long)as->sam_freq_upper); + } else { + printf(" Sampling Freq: Discrete, count=%u\r\n", as->sam_freq_type); + for (uint8_t j = 0; j < as->sam_freq_type && j < CFG_TUH_AUDIO_MAX_SAM_FREQ; j++) { + printf(" Freq[%u]: %lu Hz\r\n", j, (unsigned long)as->sam_freq[j]); + } + } +} + +// Print all AS interface info +static void print_as_interfaces(const tuh_audio_mount_cb_t *mount_cb_data) { + for (uint8_t i = 0; i < mount_cb_data->as_count; i++) { + const tuh_audio_as_info_t *as = &mount_cb_data->as_info[i]; + if (as->ep_dir == TUSB_DIR_IN) { + // Save microphone channel count for mono-to-stereo conversion + audio_mic_channels = as->num_channels; + printf(" --- Microphone (AS %u) ---\r\n", i); + printf(" IN EP: 0x%02x (max size: %u)\r\n", as->ep_addr, as->ep_size); + } else { + printf(" --- Speaker (AS %u) ---\r\n", i); + printf(" OUT EP: 0x%02x (max size: %u)\r\n", as->ep_addr, as->ep_size); + } + printf(" Interface: %u, Alt: %u\r\n", as->interface_num, as->alt_setting); + printf(" Format Type: %u, Channels: %u, SubFrameSize: %u, BitResolution: %u\r\n", as->format_type, + as->num_channels, as->sub_frame_size, as->bit_resolution); + print_sampling_freq(as); + } +} + +// Find IN and OUT endpoints from AS interfaces, return IN sampling freq +static uint32_t find_audio_endpoints(const tuh_audio_mount_cb_t *mount_cb_data) { + uint32_t in_sam_freq = 0; + audio_ep_in = 0; + audio_ep_out = 0; + + for (uint8_t i = 0; i < mount_cb_data->as_count; i++) { + const tuh_audio_as_info_t *as = &mount_cb_data->as_info[i]; + if (as->ep_dir == TUSB_DIR_IN) { + audio_ep_in = as->ep_addr; + if (as->sam_freq_type > 0) { + in_sam_freq = as->sam_freq[0]; + } + } else { + audio_ep_out = as->ep_addr; + } + } + return in_sam_freq; +} + +// Set Feature Unit volume to un-mute +static void set_feature_unit_volume(void) { + if (audio_feature_unit_id == 0) { + return; + } + printf(" Setting Feature Unit %u volume to 0x0600\r\n", audio_feature_unit_id); + tuh_audio_feature_unit_set(audio_dev_addr, audio_ac_itf, audio_feature_unit_id, AUDIO10_FU_CTRL_VOLUME, 0, 0x0600, + NULL, 0); +} + +//--------------------------------------------------------------------+ +// Application Task +//--------------------------------------------------------------------+ +void audio_app_task(void) { + if (!audio_mounted || !audio_ready) { + return; + } + + if (!audio_rx_busy) { + if (tuh_audio_receive(audio_dev_addr, audio_idx, audio_rx_buffer, CFG_TUH_AUDIO_EPIN_BUFSIZE)) { + audio_rx_busy = true; + } + } +} + +//--------------------------------------------------------------------+ +// TinyUSB Callbacks +//--------------------------------------------------------------------+ + + +// Callback after IN sampling frequency is set +static void in_sampling_freq_set_cb(tuh_xfer_t *xfer) { + if (xfer->result != XFER_RESULT_SUCCESS) { + printf(" Sampling frequency set FAILED: result=%u\r\n", xfer->result); + return; + } + printf(" Sampling frequency set OK, ready for isochronous transfer\r\n"); + // Set Feature Unit volume to un-mute + set_feature_unit_volume(); + // Set OUT sampling frequency then send empty packet to kick-start device + tuh_audio_set_sampling_freq(audio_dev_addr, audio_ep_out, sampling_freq, NULL, 0); + audio_ready = true; +} + +void tuh_audio_mount_cb(uint8_t idx, const tuh_audio_mount_cb_t *mount_cb_data) { + if (!mount_cb_data) { + return; + } + + printf("Audio device mounted: idx=%u, daddr=%u, AS count=%u\r\n", idx, mount_cb_data->daddr, mount_cb_data->as_count); + + print_as_interfaces(mount_cb_data); + + // Feature Unit + if (mount_cb_data->feature_unit_id != 0) { + printf(" Feature Unit: ID=%u, SourceID=%u\r\n", mount_cb_data->feature_unit_id, + mount_cb_data->feature_unit_source_id); + } + + // Save device info + audio_dev_addr = mount_cb_data->daddr; + audio_idx = idx; + audio_mounted = true; + audio_ac_itf = mount_cb_data->bInterfaceNumber; + audio_feature_unit_id = mount_cb_data->feature_unit_id; + + // Find endpoints and IN sampling frequency + uint32_t in_sam_freq = find_audio_endpoints(mount_cb_data); + + // Set IN sampling frequency before starting isochronous transfer + if (audio_ep_in != 0 && in_sam_freq != 0) { + sampling_freq = in_sam_freq; + printf(" Setting IN sampling frequency to %lu Hz\r\n", (unsigned long)sampling_freq); + tuh_audio_set_sampling_freq(mount_cb_data->daddr, audio_ep_in, sampling_freq, in_sampling_freq_set_cb, 0); + } +} + +// Invoked when device with Audio interface is un-mounted +void tuh_audio_umount_cb(uint8_t idx) { + printf("Audio device unmounted: idx=%u\r\n", idx); + if (audio_mounted && audio_idx == idx) { + audio_mounted = false; + audio_ready = false; + audio_rx_busy = false; + audio_tx_busy = false; + audio_dev_addr = 0; + audio_idx = 0; + } +} + +// Invoked when an isochronous IN transfer is complete +void tuh_audio_rx_cb(uint8_t idx, uint8_t ep_addr, uint16_t xferred_bytes) { + (void)idx; + (void)ep_addr; + audio_rx_busy = false; + + if (xferred_bytes > 0) { + if (audio_mic_channels == 1) { + // Mono microphone, convert to stereo and send to OUT endpoint + uint16_t samples = xferred_bytes / 2; + mono_to_stereo(audio_rx_buffer, audio_tx_buffer, samples); + tuh_audio_send(audio_dev_addr, audio_idx, audio_tx_buffer, xferred_bytes * 2); + } else { + // Stereo microphone, send directly to OUT endpoint + tuh_audio_send(audio_dev_addr, audio_idx, audio_rx_buffer, xferred_bytes); + } + } +} + +// Invoked when an isochronous OUT transfer is complete +void tuh_audio_tx_cb(uint8_t idx, uint8_t ep_addr, uint16_t xferred_bytes) { + (void)idx; + (void)ep_addr; + (void)xferred_bytes; + audio_tx_busy = false; +} diff --git a/examples/host/audio_host/src/main.c b/examples/host/audio_host/src/main.c new file mode 100644 index 000000000..b80cd2938 --- /dev/null +++ b/examples/host/audio_host/src/main.c @@ -0,0 +1,73 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2025 TinyUSB contributors + * + * 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. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "bsp/board_api.h" +#include "tusb.h" +#include "app.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF PROTYPES +//--------------------------------------------------------------------+ +void led_blinking_task(void); + +/*------------- MAIN -------------*/ +int main(void) { + board_init(); + + printf("TinyUSB Host USB Audio Example\r\n"); + printf("Connect a USB Audio Device (UAC 1.0) to test\r\n"); + + // init host stack on configured roothub port + tusb_rhport_init_t host_init = { + .role = TUSB_ROLE_HOST, + .speed = TUSB_SPEED_AUTO + }; + tusb_init(BOARD_TUH_RHPORT, &host_init); + + board_init_after_tusb(); + + while (1) { + // tinyusb host task + tuh_task(); + led_blinking_task(); + audio_app_task(); + } +} + +//--------------------------------------------------------------------+ +// TinyUSB Callbacks +//--------------------------------------------------------------------+ + +//--------------------------------------------------------------------+ +// Blinking Task +//--------------------------------------------------------------------+ +void led_blinking_task(void) { + const uint32_t interval_ms = 1000; + static uint32_t start_ms = 0; + + static bool led_state = false; + + // Blink every interval ms + if ( tusb_time_millis_api() - start_ms < interval_ms) return; // not enough time + start_ms += interval_ms; + + board_led_write(led_state); + led_state = 1 - led_state; // toggle +} diff --git a/examples/host/audio_host/src/tusb_config.h b/examples/host/audio_host/src/tusb_config.h new file mode 100644 index 000000000..a4fb17fa5 --- /dev/null +++ b/examples/host/audio_host/src/tusb_config.h @@ -0,0 +1,103 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2025 TinyUSB contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef TUSB_CONFIG_H_ +#define TUSB_CONFIG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +//-------------------------------------------------------------------- +// Common Configuration +//-------------------------------------------------------------------- + +#ifndef CFG_TUSB_MCU + #error CFG_TUSB_MCU must be defined +#endif + +#ifndef CFG_TUSB_OS + #define CFG_TUSB_OS OPT_OS_NONE +#endif + +#ifndef CFG_TUSB_DEBUG + #define CFG_TUSB_DEBUG 0 +#endif + +#ifndef CFG_TUH_MEM_SECTION + #define CFG_TUH_MEM_SECTION +#endif + +#ifndef CFG_TUH_MEM_ALIGN + #define CFG_TUH_MEM_ALIGN __attribute__((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// Host Configuration +//-------------------------------------------------------------------- + +#define CFG_TUH_ENABLED 1 + +#if CFG_TUSB_MCU == OPT_MCU_RP2040 + #if (defined(CFG_TUH_RPI_PIO_USB) && CFG_TUH_RPI_PIO_USB) || (defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421) + #define BOARD_TUH_RHPORT 1 + #endif +#endif + +#define CFG_TUH_MAX_SPEED BOARD_TUH_MAX_SPEED + +#ifndef BOARD_TUH_RHPORT + #define BOARD_TUH_RHPORT 1 +#endif + +#ifndef BOARD_TUH_MAX_SPEED + #define BOARD_TUH_MAX_SPEED OPT_MODE_DEFAULT_SPEED +#endif + +//-------------------------------------------------------------------- +// Driver Configuration +//-------------------------------------------------------------------- + +#define CFG_TUH_ENUMERATION_BUFSIZE 512 + +#define CFG_TUH_HUB 1 +#define CFG_TUH_CDC 0 +#define CFG_TUH_HID 1 +#define CFG_TUH_MSC 0 +#define CFG_TUH_VENDOR 0 +#define CFG_TUH_AUDIO 1 + +// max device support (excluding hub device): 1 hub typically has 4 ports +#define CFG_TUH_DEVICE_MAX (3 * CFG_TUH_HUB + 1) + +//------------- Audio Host Config -------------// +#define CFG_TUH_AUDIO_MAX 2 +#define CFG_TUH_AUDIO_EPIN_BUFSIZE 192 +#define CFG_TUH_AUDIO_EPOUT_BUFSIZE 192 + +#ifdef __cplusplus +} +#endif + +#endif /* TUSB_CONFIG_H_ */ |
