summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorcopilot-swe-agent[bot] <[email protected]>2026-05-25 19:15:45 +0000
committerGitHub <[email protected]>2026-05-25 19:15:45 +0000
commit072c02e3684e886a93681ecb37f830be5b8c6b53 (patch)
tree0b7cd9e7d287fa563726cfb0cc1f70e418ce499a /examples
parente8baf2e0102660d51eb515d1b0651d8298178dc3 (diff)
parent7b9f6eabd8623c2bbe188c8d53072b16a087cdda (diff)
chore: resolve .gitignore merge conflict with master
Co-authored-by: HiFiPhile <[email protected]>
Diffstat (limited to 'examples')
-rw-r--r--examples/device/CMakeLists.txt2
-rwxr-xr-xexamples/device/audio_4_channel_mic/src/plot_audio_samples.py49
-rw-r--r--examples/device/audio_4_channel_mic/src/usb_descriptors.c8
-rwxr-xr-xexamples/device/audio_4_channel_mic_freertos/src/plot_audio_samples.py50
-rw-r--r--examples/device/audio_4_channel_mic_freertos/src/usb_descriptors.c4
-rwxr-xr-xexamples/device/audio_test/src/plot_audio_samples.py50
-rw-r--r--examples/device/audio_test/src/usb_descriptors.c8
-rwxr-xr-xexamples/device/audio_test_freertos/src/plot_audio_samples.py50
-rw-r--r--examples/device/audio_test_freertos/src/usb_descriptors.c4
-rwxr-xr-xexamples/device/audio_test_multi_rate/src/plot_audio_samples.py50
-rw-r--r--examples/device/audio_test_multi_rate/src/usb_descriptors.c8
-rw-r--r--examples/device/cdc_dual_ports/src/usb_descriptors.c25
-rw-r--r--examples/device/cdc_msc/src/usb_descriptors.c22
-rw-r--r--examples/device/cdc_msc_freertos/src/main.c4
-rw-r--r--examples/device/cdc_msc_freertos/src/usb_descriptors.c22
-rw-r--r--examples/device/cdc_msc_throughput/CMakeLists.txt35
-rw-r--r--examples/device/cdc_msc_throughput/CMakePresets.json6
-rw-r--r--examples/device/cdc_msc_throughput/Makefile11
-rw-r--r--examples/device/cdc_msc_throughput/src/main.c151
-rw-r--r--examples/device/cdc_msc_throughput/src/tusb_config.h103
-rw-r--r--examples/device/cdc_msc_throughput/src/usb_descriptors.c186
-rw-r--r--examples/device/cdc_uac2/src/usb_descriptors.c22
-rw-r--r--examples/device/dfu/skip.txt1
-rw-r--r--examples/device/dynamic_configuration/src/usb_descriptors.c2
-rw-r--r--examples/device/hid_generic_inout/src/usb_descriptors.c2
-rw-r--r--examples/device/midi2_device/CMakeLists.txt33
-rw-r--r--examples/device/midi2_device/CMakePresets.json6
-rw-r--r--examples/device/midi2_device/Makefile16
-rw-r--r--examples/device/midi2_device/README.md59
-rw-r--r--examples/device/midi2_device/skip.txt1
-rw-r--r--examples/device/midi2_device/src/main.c720
-rw-r--r--examples/device/midi2_device/src/tusb_config.h88
-rw-r--r--examples/device/midi2_device/src/usb_descriptors.c142
-rw-r--r--examples/device/midi_test/src/usb_descriptors.c2
-rw-r--r--examples/device/midi_test_freertos/src/usb_descriptors.c2
-rw-r--r--examples/device/msc_dual_lun/src/usb_descriptors.c12
-rw-r--r--examples/device/mtp/src/mtp_fs_example.c44
-rw-r--r--examples/device/mtp/src/tusb_config.h1
-rw-r--r--examples/device/mtp/src/usb_descriptors.c2
-rw-r--r--examples/device/net_lwip_webserver/skip.txt2
-rw-r--r--examples/device/net_lwip_webserver/src/lwipopts.h20
-rw-r--r--examples/device/net_lwip_webserver/src/tusb_config.h40
-rw-r--r--examples/device/net_lwip_webserver/src/usb_descriptors.c181
-rw-r--r--examples/device/printer_to_cdc/src/usb_descriptors.c21
-rw-r--r--examples/device/uac2_headset/src/usb_descriptors.c15
-rw-r--r--examples/device/uac2_speaker_fb/src/usb_descriptors.c15
-rw-r--r--examples/device/webusb_serial/src/usb_descriptors.c22
-rw-r--r--examples/dual/dynamic_switch/src/usb_descriptors.c2
-rw-r--r--examples/host/CMakeLists.txt2
-rw-r--r--examples/host/midi2_host/CMakeLists.txt29
-rw-r--r--examples/host/midi2_host/CMakePresets.json6
-rw-r--r--examples/host/midi2_host/Makefile13
-rw-r--r--examples/host/midi2_host/only.txt34
-rw-r--r--examples/host/midi2_host/skip.txt1
-rw-r--r--examples/host/midi2_host/src/main.c165
-rw-r--r--examples/host/midi2_host/src/tusb_config.h106
-rw-r--r--examples/host/msc_file_explorer_freertos/CMakeLists.txt42
-rw-r--r--examples/host/msc_file_explorer_freertos/CMakePresets.json6
-rw-r--r--examples/host/msc_file_explorer_freertos/Makefile27
-rw-r--r--examples/host/msc_file_explorer_freertos/README.md105
-rw-r--r--examples/host/msc_file_explorer_freertos/only.txt28
-rw-r--r--examples/host/msc_file_explorer_freertos/skip.txt3
-rw-r--r--examples/host/msc_file_explorer_freertos/src/CMakeLists.txt13
-rw-r--r--examples/host/msc_file_explorer_freertos/src/ffconf.h313
-rw-r--r--examples/host/msc_file_explorer_freertos/src/main.c158
-rw-r--r--examples/host/msc_file_explorer_freertos/src/msc_app.c709
-rw-r--r--examples/host/msc_file_explorer_freertos/src/msc_app.h34
-rw-r--r--examples/host/msc_file_explorer_freertos/src/tusb_config.h126
68 files changed, 4136 insertions, 105 deletions
diff --git a/examples/device/CMakeLists.txt b/examples/device/CMakeLists.txt
index 7173f455e..1432b36bb 100644
--- a/examples/device/CMakeLists.txt
+++ b/examples/device/CMakeLists.txt
@@ -16,6 +16,7 @@ set(EXAMPLE_LIST
cdc_dual_ports
cdc_msc
cdc_msc_freertos
+ cdc_msc_throughput
cdc_uac2
dfu
dfu_runtime
@@ -27,6 +28,7 @@ set(EXAMPLE_LIST
hid_multiple_interface
midi_test
midi_test_freertos
+ midi2_device
msc_dual_lun
mtp
net_lwip_webserver
diff --git a/examples/device/audio_4_channel_mic/src/plot_audio_samples.py b/examples/device/audio_4_channel_mic/src/plot_audio_samples.py
index 4d61e7f5e..618745934 100755
--- a/examples/device/audio_4_channel_mic/src/plot_audio_samples.py
+++ b/examples/device/audio_4_channel_mic/src/plot_audio_samples.py
@@ -4,6 +4,51 @@ import matplotlib.pyplot as plt
import numpy as np
import platform
+
+def find_windows_input_device(name_hint, channels, preferred_apis=None):
+ """Pick a Windows input device index from query_devices() output."""
+ preferred_apis = preferred_apis or []
+ name_hint = name_hint.lower()
+ candidates = []
+
+ for index in range(len(sd.query_devices())):
+ device_info = sd.query_devices(index)
+ max_input_channels = int(device_info.get('max_input_channels', 0))
+
+ if max_input_channels < channels:
+ continue
+
+ device_name = str(device_info.get('name', '')).lower()
+ if name_hint not in device_name:
+ continue
+
+ score = 0
+
+ # Prefer exact channel matches to avoid selecting a different stream layout.
+ if max_input_channels == channels:
+ score += 100
+ else:
+ score += 10
+
+ hostapi_index = int(device_info.get('hostapi', -1))
+ api_name = ''
+ if hostapi_index >= 0:
+ api_name = str(sd.query_hostapis(hostapi_index)).lower()
+
+ for priority, api_hint in enumerate(preferred_apis):
+ if api_hint in api_name:
+ score += 50 - priority
+ break
+
+ candidates.append((score, index))
+
+ if not candidates:
+ raise ValueError(
+ 'No input device matching hint="{}" with at least {} channels'.format(name_hint, channels)
+ )
+
+ return max(candidates, key=lambda item: item[0])[1]
+
if __name__ == '__main__':
# If you got "ValueError: No input device matching", that is because your PC name example device
@@ -14,8 +59,8 @@ if __name__ == '__main__':
duration = 1 # Duration of recording
if platform.system() == 'Windows':
- # WDM-KS is needed since there are more than one MicNode device APIs (at least in Windows)
- device = 'Microphone (MicNode_4_Ch), Windows WASAPI'
+ # Match by substring to support names like "Microphone (2- MicNode_4_Ch)".
+ device = find_windows_input_device('micnode_4_ch', channels=4, preferred_apis=['wasapi', 'wdm-ks', 'mme'])
elif platform.system() == 'Darwin':
device = 'MicNode_4_Ch'
else:
diff --git a/examples/device/audio_4_channel_mic/src/usb_descriptors.c b/examples/device/audio_4_channel_mic/src/usb_descriptors.c
index 00337eee7..6b9a9bbae 100644
--- a/examples/device/audio_4_channel_mic/src/usb_descriptors.c
+++ b/examples/device/audio_4_channel_mic/src/usb_descriptors.c
@@ -91,6 +91,14 @@ enum
// nRF5x ISO can only be endpoint 8
#define EPNUM_AUDIO 0x08
+#elif TU_CHECK_MCU(OPT_MCU_MAX32650, OPT_MCU_MAX32666, OPT_MCU_MAX32690, OPT_MCU_MAX78002)
+ // Put audio iso on EP>=8 so the 2048/4096-byte FIFOs can back double packet buffering
+ #define EPNUM_AUDIO 0x0A
+
+#elif TU_CHECK_MCU(OPT_MCU_CH32V20X, OPT_MCU_CH32V307)
+ // Only EP3 is available for ISO
+ #define EPNUM_AUDIO 0x03
+
#else
#define EPNUM_AUDIO 0x01
#endif
diff --git a/examples/device/audio_4_channel_mic_freertos/src/plot_audio_samples.py b/examples/device/audio_4_channel_mic_freertos/src/plot_audio_samples.py
index 4d5ca28d6..3b3cd0d83 100755
--- a/examples/device/audio_4_channel_mic_freertos/src/plot_audio_samples.py
+++ b/examples/device/audio_4_channel_mic_freertos/src/plot_audio_samples.py
@@ -4,6 +4,52 @@ import matplotlib.pyplot as plt
import numpy as np
import platform
+
+def find_windows_input_device(name_hint, channels, preferred_apis=None):
+ """Pick a Windows input device index from query_devices() output."""
+ preferred_apis = preferred_apis or []
+ name_hint = name_hint.lower()
+ candidates = []
+
+ for index in range(len(sd.query_devices())):
+ device_info = sd.query_devices(index)
+ max_input_channels = int(device_info.get('max_input_channels', 0))
+
+ if max_input_channels < channels:
+ continue
+
+ device_name = str(device_info.get('name', '')).lower()
+ if name_hint not in device_name:
+ continue
+
+ score = 0
+
+ # Prefer exact channel matches to avoid selecting a different stream layout.
+ if max_input_channels == channels:
+ score += 100
+ else:
+ score += 10
+
+ hostapi_index = int(device_info.get('hostapi', -1))
+ api_name = ''
+ if hostapi_index >= 0:
+ hostapi_info = sd.query_hostapis(hostapi_index)
+ api_name = str(hostapi_info.get('name', '')).lower()
+
+ for priority, api_hint in enumerate(preferred_apis):
+ if api_hint in api_name:
+ score += 50 - priority
+ break
+
+ candidates.append((score, index))
+
+ if not candidates:
+ raise ValueError(
+ 'No input device matching hint="{}" with at least {} channels'.format(name_hint, channels)
+ )
+
+ return max(candidates, key=lambda item: item[0])[1]
+
if __name__ == '__main__':
# If you got "ValueError: No input device matching", that is because your PC name example device
@@ -14,8 +60,8 @@ if __name__ == '__main__':
duration = 100e-3 # Duration of recording
if platform.system() == 'Windows':
- # WDM-KS is needed since there are more than one MicNode device APIs (at least in Windows)
- device = 'Microphone (MicNode_4_Ch), Windows WDM-KS'
+ # Match by substring to support names like "Microphone (2- MicNode_4_Ch)".
+ device = find_windows_input_device('micnode_4_ch', channels=4, preferred_apis=['wdm-ks', 'wasapi', 'mme'])
elif platform.system() == 'Darwin':
device = 'MicNode_4_Ch'
else:
diff --git a/examples/device/audio_4_channel_mic_freertos/src/usb_descriptors.c b/examples/device/audio_4_channel_mic_freertos/src/usb_descriptors.c
index 3bb93f67d..216cd062a 100644
--- a/examples/device/audio_4_channel_mic_freertos/src/usb_descriptors.c
+++ b/examples/device/audio_4_channel_mic_freertos/src/usb_descriptors.c
@@ -91,6 +91,10 @@ enum
// nRF5x ISO can only be endpoint 8
#define EPNUM_AUDIO 0x08
+#elif TU_CHECK_MCU(OPT_MCU_MAX32650, OPT_MCU_MAX32666, OPT_MCU_MAX32690, OPT_MCU_MAX78002)
+ // Put audio iso on EP>=8 so the 2048/4096-byte FIFOs can back double packet buffering
+ #define EPNUM_AUDIO 0x0A
+
#else
#define EPNUM_AUDIO 0x01
#endif
diff --git a/examples/device/audio_test/src/plot_audio_samples.py b/examples/device/audio_test/src/plot_audio_samples.py
index 2be8948ea..af01b7b3e 100755
--- a/examples/device/audio_test/src/plot_audio_samples.py
+++ b/examples/device/audio_test/src/plot_audio_samples.py
@@ -5,6 +5,52 @@ import numpy as np
import platform
import csv
+
+def find_windows_input_device(name_hint, channels, preferred_apis=None):
+ """Pick a Windows input device index from query_devices() output."""
+ preferred_apis = preferred_apis or []
+ name_hint = name_hint.lower()
+ candidates = []
+
+ for index in range(len(sd.query_devices())):
+ device_info = sd.query_devices(index)
+ max_input_channels = int(device_info.get('max_input_channels', 0))
+
+ if max_input_channels < channels:
+ continue
+
+ device_name = str(device_info.get('name', '')).lower()
+ if name_hint not in device_name:
+ continue
+
+ score = 0
+
+ # Prefer exact channel matches (for example 1ch source over a 4ch source).
+ if max_input_channels == channels:
+ score += 100
+ else:
+ score += 10
+
+ hostapi_index = int(device_info.get('hostapi', -1))
+ api_name = ''
+ if hostapi_index >= 0:
+ hostapi_info = sd.query_hostapis(hostapi_index)
+ api_name = str(hostapi_info.get('name', '')).lower()
+
+ for priority, api_hint in enumerate(preferred_apis):
+ if api_hint in api_name:
+ score += 50 - priority
+ break
+
+ candidates.append((score, index))
+
+ if not candidates:
+ raise ValueError(
+ 'No input device matching hint="{}" with at least {} channels'.format(name_hint, channels)
+ )
+
+ return max(candidates, key=lambda item: item[0])[1]
+
if __name__ == '__main__':
# If you got "ValueError: No input device matching", that is because your PC name example device
@@ -15,8 +61,8 @@ if __name__ == '__main__':
duration = 3 # Duration of recording
if platform.system() == 'Windows':
- # MME is needed since there are more than one MicNode device APIs (at least in Windows)
- device = 'Microphone (MicNode), Windows WASAPI'
+ # Match by substring to support names like "Microphone (2- MicNode)".
+ device = find_windows_input_device('micnode', channels=1, preferred_apis=['wasapi', 'mme', 'wdm-ks'])
elif platform.system() == 'Darwin':
device = 'MicNode'
else:
diff --git a/examples/device/audio_test/src/usb_descriptors.c b/examples/device/audio_test/src/usb_descriptors.c
index ad161939e..cea4eb8d1 100644
--- a/examples/device/audio_test/src/usb_descriptors.c
+++ b/examples/device/audio_test/src/usb_descriptors.c
@@ -91,6 +91,14 @@ enum
// nRF5x ISO can only be endpoint 8
#define EPNUM_AUDIO 0x08
+#elif TU_CHECK_MCU(OPT_MCU_MAX32650, OPT_MCU_MAX32666, OPT_MCU_MAX32690, OPT_MCU_MAX78002)
+ // Put audio iso on EP>=8 so the 2048/4096-byte FIFOs can back double packet buffering
+ #define EPNUM_AUDIO 0x0A
+
+#elif TU_CHECK_MCU(OPT_MCU_CH32V20X, OPT_MCU_CH32V307)
+ // Only EP3 is available for ISO
+ #define EPNUM_AUDIO 0x03
+
#else
#define EPNUM_AUDIO 0x01
#endif
diff --git a/examples/device/audio_test_freertos/src/plot_audio_samples.py b/examples/device/audio_test_freertos/src/plot_audio_samples.py
index b6d8e824b..b6a916be4 100755
--- a/examples/device/audio_test_freertos/src/plot_audio_samples.py
+++ b/examples/device/audio_test_freertos/src/plot_audio_samples.py
@@ -4,6 +4,52 @@ import matplotlib.pyplot as plt
import numpy as np
import platform
+
+def find_windows_input_device(name_hint, channels, preferred_apis=None):
+ """Pick a Windows input device index from query_devices() output."""
+ preferred_apis = preferred_apis or []
+ name_hint = name_hint.lower()
+ candidates = []
+
+ for index in range(len(sd.query_devices())):
+ device_info = sd.query_devices(index)
+ max_input_channels = int(device_info.get('max_input_channels', 0))
+
+ if max_input_channels < channels:
+ continue
+
+ device_name = str(device_info.get('name', '')).lower()
+ if name_hint not in device_name:
+ continue
+
+ score = 0
+
+ # Prefer exact channel matches (for example 1ch source over a 4ch source).
+ if max_input_channels == channels:
+ score += 100
+ else:
+ score += 10
+
+ hostapi_index = int(device_info.get('hostapi', -1))
+ api_name = ''
+ if hostapi_index >= 0:
+ hostapi_info = sd.query_hostapis(hostapi_index)
+ api_name = str(hostapi_info.get('name', '')).lower()
+
+ for priority, api_hint in enumerate(preferred_apis):
+ if api_hint in api_name:
+ score += 50 - priority
+ break
+
+ candidates.append((score, index))
+
+ if not candidates:
+ raise ValueError(
+ 'No input device matching hint="{}" with at least {} channels'.format(name_hint, channels)
+ )
+
+ return max(candidates, key=lambda item: item[0])[1]
+
if __name__ == '__main__':
# If you got "ValueError: No input device matching", that is because your PC name example device
@@ -14,8 +60,8 @@ if __name__ == '__main__':
duration = 3 # Duration of recording
if platform.system() == 'Windows':
- # MME is needed since there are more than one MicNode device APIs (at least in Windows)
- device = 'Microphone (MicNode), Windows WASAPI'
+ # Match by substring to support names like "Microphone (2- MicNode)".
+ device = find_windows_input_device('micnode', channels=1, preferred_apis=['wasapi', 'mme', 'wdm-ks'])
elif platform.system() == 'Darwin':
device = 'MicNode'
else:
diff --git a/examples/device/audio_test_freertos/src/usb_descriptors.c b/examples/device/audio_test_freertos/src/usb_descriptors.c
index ad161939e..37ebf84d3 100644
--- a/examples/device/audio_test_freertos/src/usb_descriptors.c
+++ b/examples/device/audio_test_freertos/src/usb_descriptors.c
@@ -91,6 +91,10 @@ enum
// nRF5x ISO can only be endpoint 8
#define EPNUM_AUDIO 0x08
+#elif TU_CHECK_MCU(OPT_MCU_MAX32650, OPT_MCU_MAX32666, OPT_MCU_MAX32690, OPT_MCU_MAX78002)
+ // Put audio iso on EP>=8 so the 2048/4096-byte FIFOs can back double packet buffering
+ #define EPNUM_AUDIO 0x0A
+
#else
#define EPNUM_AUDIO 0x01
#endif
diff --git a/examples/device/audio_test_multi_rate/src/plot_audio_samples.py b/examples/device/audio_test_multi_rate/src/plot_audio_samples.py
index 1f33a003e..f35cfa311 100755
--- a/examples/device/audio_test_multi_rate/src/plot_audio_samples.py
+++ b/examples/device/audio_test_multi_rate/src/plot_audio_samples.py
@@ -5,6 +5,52 @@ import numpy as np
import platform
import csv
+
+def find_windows_input_device(name_hint, channels, preferred_apis=None):
+ """Pick a Windows input device index from query_devices() output."""
+ preferred_apis = preferred_apis or []
+ name_hint = name_hint.lower()
+ candidates = []
+
+ for index in range(len(sd.query_devices())):
+ device_info = sd.query_devices(index)
+ max_input_channels = int(device_info.get('max_input_channels', 0))
+
+ if max_input_channels < channels:
+ continue
+
+ device_name = str(device_info.get('name', '')).lower()
+ if name_hint not in device_name:
+ continue
+
+ score = 0
+
+ # Prefer exact channel matches (for example 1ch source over a 4ch source).
+ if max_input_channels == channels:
+ score += 100
+ else:
+ score += 10
+
+ hostapi_index = int(device_info.get('hostapi', -1))
+ api_name = ''
+ if hostapi_index >= 0:
+ hostapi_info = sd.query_hostapis(hostapi_index)
+ api_name = str(hostapi_info.get('name', '')).lower()
+
+ for priority, api_hint in enumerate(preferred_apis):
+ if api_hint in api_name:
+ score += 50 - priority
+ break
+
+ candidates.append((score, index))
+
+ if not candidates:
+ raise ValueError(
+ 'No input device matching hint="{}" with at least {} channels'.format(name_hint, channels)
+ )
+
+ return max(candidates, key=lambda item: item[0])[1]
+
if __name__ == '__main__':
# If you got "ValueError: No input device matching", that is because your PC name example device
@@ -15,8 +61,8 @@ if __name__ == '__main__':
duration = 100e-3 # Duration of recording
if platform.system() == 'Windows':
- # MME is needed since there are more than one MicNode device APIs (at least in Windows)
- device = 'Microphone (MicNode) MME'
+ # Match by substring to support names like "Microphone (2- MicNode)".
+ device = find_windows_input_device('micnode', channels=1, preferred_apis=['mme', 'wasapi', 'wdm-ks'])
elif platform.system() == 'Darwin':
device = 'MicNode'
else:
diff --git a/examples/device/audio_test_multi_rate/src/usb_descriptors.c b/examples/device/audio_test_multi_rate/src/usb_descriptors.c
index 505936fdb..b1f60dd10 100644
--- a/examples/device/audio_test_multi_rate/src/usb_descriptors.c
+++ b/examples/device/audio_test_multi_rate/src/usb_descriptors.c
@@ -88,6 +88,14 @@ enum {
// nRF5x ISO can only be endpoint 8
#define EPNUM_AUDIO 0x08
+#elif TU_CHECK_MCU(OPT_MCU_MAX32650, OPT_MCU_MAX32666, OPT_MCU_MAX32690, OPT_MCU_MAX78002)
+ // Put audio iso on EP>=8 so the 2048/4096-byte FIFOs can back double packet buffering
+ #define EPNUM_AUDIO 0x0A
+
+#elif TU_CHECK_MCU(OPT_MCU_CH32V20X, OPT_MCU_CH32V307)
+ // Only EP3 is available for ISO
+ #define EPNUM_AUDIO 0x03
+
#else
#define EPNUM_AUDIO 0x01
#endif
diff --git a/examples/device/cdc_dual_ports/src/usb_descriptors.c b/examples/device/cdc_dual_ports/src/usb_descriptors.c
index e6011c35a..adfd8cf9d 100644
--- a/examples/device/cdc_dual_ports/src/usb_descriptors.c
+++ b/examples/device/cdc_dual_ports/src/usb_descriptors.c
@@ -106,16 +106,27 @@ enum {
#define EPNUM_CDC_1_OUT 0x05
#define EPNUM_CDC_1_IN 0x84
-#elif defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY)
+#elif CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
// MCUs that don't support a same endpoint number with different direction IN and OUT defined in tusb_mcu.h
// e.g EP1 OUT & EP1 IN cannot exist together
- #define EPNUM_CDC_0_NOTIF 0x81
- #define EPNUM_CDC_0_OUT 0x02
- #define EPNUM_CDC_0_IN 0x83
+ #if TU_CHECK_MCU(OPT_MCU_MAX32650, OPT_MCU_MAX32666, OPT_MCU_MAX32690, OPT_MCU_MAX78002)
+ // Put bulk on EP>=8 so the 2048/4096-byte FIFOs can back double packet buffering
+ #define EPNUM_CDC_0_NOTIF 0x81
+ #define EPNUM_CDC_0_OUT 0x08
+ #define EPNUM_CDC_0_IN 0x89
- #define EPNUM_CDC_1_NOTIF 0x84
- #define EPNUM_CDC_1_OUT 0x05
- #define EPNUM_CDC_1_IN 0x86
+ #define EPNUM_CDC_1_NOTIF 0x82
+ #define EPNUM_CDC_1_OUT 0x0A
+ #define EPNUM_CDC_1_IN 0x8B
+ #else
+ #define EPNUM_CDC_0_NOTIF 0x81
+ #define EPNUM_CDC_0_OUT 0x02
+ #define EPNUM_CDC_0_IN 0x83
+
+ #define EPNUM_CDC_1_NOTIF 0x84
+ #define EPNUM_CDC_1_OUT 0x05
+ #define EPNUM_CDC_1_IN 0x86
+ #endif
#else
#define EPNUM_CDC_0_NOTIF 0x81
diff --git a/examples/device/cdc_msc/src/usb_descriptors.c b/examples/device/cdc_msc/src/usb_descriptors.c
index c668ea3a7..5dc80dee3 100644
--- a/examples/device/cdc_msc/src/usb_descriptors.c
+++ b/examples/device/cdc_msc/src/usb_descriptors.c
@@ -102,15 +102,25 @@ enum {
#define EPNUM_MSC_OUT 0x05
#define EPNUM_MSC_IN 0x84
-#elif defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY)
+#elif CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
// MCUs that don't support a same endpoint number with different direction IN and OUT defined in tusb_mcu.h
// e.g EP1 OUT & EP1 IN cannot exist together
- #define EPNUM_CDC_NOTIF 0x81
- #define EPNUM_CDC_OUT 0x02
- #define EPNUM_CDC_IN 0x83
+ #if TU_CHECK_MCU(OPT_MCU_MAX32650, OPT_MCU_MAX32666, OPT_MCU_MAX32690, OPT_MCU_MAX78002)
+ // Put bulk on EP>=8 so the 2048/4096-byte FIFOs can back double packet buffering
+ #define EPNUM_CDC_NOTIF 0x81
+ #define EPNUM_CDC_OUT 0x08
+ #define EPNUM_CDC_IN 0x89
- #define EPNUM_MSC_OUT 0x04
- #define EPNUM_MSC_IN 0x85
+ #define EPNUM_MSC_OUT 0x0A
+ #define EPNUM_MSC_IN 0x8B
+ #else
+ #define EPNUM_CDC_NOTIF 0x81
+ #define EPNUM_CDC_OUT 0x02
+ #define EPNUM_CDC_IN 0x83
+
+ #define EPNUM_MSC_OUT 0x04
+ #define EPNUM_MSC_IN 0x85
+ #endif
#else
#define EPNUM_CDC_NOTIF 0x81
diff --git a/examples/device/cdc_msc_freertos/src/main.c b/examples/device/cdc_msc_freertos/src/main.c
index 4fb209fd0..f2f71d089 100644
--- a/examples/device/cdc_msc_freertos/src/main.c
+++ b/examples/device/cdc_msc_freertos/src/main.c
@@ -34,10 +34,10 @@
#define USBD_STACK_SIZE 4096
#else
// Increase stack size when debug log is enabled
- #define USBD_STACK_SIZE (3*configMINIMAL_STACK_SIZE/2) * (CFG_TUSB_DEBUG ? 2 : 1)
+ #define USBD_STACK_SIZE (configMINIMAL_STACK_SIZE * (CFG_TUSB_DEBUG ? 4 : 2))
#endif
-#define CDC_STACK_SIZE (configMINIMAL_STACK_SIZE * (CFG_TUSB_DEBUG ? 2 : 1))
+#define CDC_STACK_SIZE (configMINIMAL_STACK_SIZE * (CFG_TUSB_DEBUG ? 3 : 2))
#define BLINKY_STACK_SIZE configMINIMAL_STACK_SIZE
//--------------------------------------------------------------------+
diff --git a/examples/device/cdc_msc_freertos/src/usb_descriptors.c b/examples/device/cdc_msc_freertos/src/usb_descriptors.c
index 4950f02e0..f5b015051 100644
--- a/examples/device/cdc_msc_freertos/src/usb_descriptors.c
+++ b/examples/device/cdc_msc_freertos/src/usb_descriptors.c
@@ -102,15 +102,25 @@ enum {
#define EPNUM_MSC_OUT 0x05
#define EPNUM_MSC_IN 0x84
-#elif defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY)
+#elif CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
// MCUs that don't support a same endpoint number with different direction IN and OUT defined in tusb_mcu.h
// e.g EP1 OUT & EP1 IN cannot exist together
- #define EPNUM_CDC_NOTIF 0x81
- #define EPNUM_CDC_OUT 0x02
- #define EPNUM_CDC_IN 0x83
+ #if TU_CHECK_MCU(OPT_MCU_MAX32650, OPT_MCU_MAX32666, OPT_MCU_MAX32690, OPT_MCU_MAX78002)
+ // Put bulk on EP>=8 so the 2048/4096-byte FIFOs can back double packet buffering
+ #define EPNUM_CDC_NOTIF 0x81
+ #define EPNUM_CDC_OUT 0x08
+ #define EPNUM_CDC_IN 0x89
- #define EPNUM_MSC_OUT 0x04
- #define EPNUM_MSC_IN 0x85
+ #define EPNUM_MSC_OUT 0x0A
+ #define EPNUM_MSC_IN 0x8B
+ #else
+ #define EPNUM_CDC_NOTIF 0x81
+ #define EPNUM_CDC_OUT 0x02
+ #define EPNUM_CDC_IN 0x83
+
+ #define EPNUM_MSC_OUT 0x04
+ #define EPNUM_MSC_IN 0x85
+ #endif
#else
#define EPNUM_CDC_NOTIF 0x81
diff --git a/examples/device/cdc_msc_throughput/CMakeLists.txt b/examples/device/cdc_msc_throughput/CMakeLists.txt
new file mode 100644
index 000000000..69c1caa6a
--- /dev/null
+++ b/examples/device/cdc_msc_throughput/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.20)
+
+include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake)
+
+project(cdc_msc_throughput 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()
+
+if (RTOS STREQUAL zephyr)
+ set(EXE_NAME app)
+else()
+ set(EXE_NAME ${PROJECT_NAME})
+ add_executable(${EXE_NAME})
+endif()
+
+# Example source
+target_sources(${EXE_NAME} PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/usb_descriptors.c
+ )
+
+# Example include
+target_include_directories(${EXE_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_device_example(${EXE_NAME} ${RTOS})
diff --git a/examples/device/cdc_msc_throughput/CMakePresets.json b/examples/device/cdc_msc_throughput/CMakePresets.json
new file mode 100644
index 000000000..5cd8971e9
--- /dev/null
+++ b/examples/device/cdc_msc_throughput/CMakePresets.json
@@ -0,0 +1,6 @@
+{
+ "version": 6,
+ "include": [
+ "../../../hw/bsp/BoardPresets.json"
+ ]
+}
diff --git a/examples/device/cdc_msc_throughput/Makefile b/examples/device/cdc_msc_throughput/Makefile
new file mode 100644
index 000000000..035e90308
--- /dev/null
+++ b/examples/device/cdc_msc_throughput/Makefile
@@ -0,0 +1,11 @@
+include ../../../hw/bsp/family_support.mk
+
+INC += \
+ src \
+
+
+# Example source
+EXAMPLE_SOURCE += $(wildcard src/*.c)
+SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE))
+
+include ../../../hw/bsp/family_rules.mk
diff --git a/examples/device/cdc_msc_throughput/src/main.c b/examples/device/cdc_msc_throughput/src/main.c
new file mode 100644
index 000000000..116cbe13f
--- /dev/null
+++ b/examples/device/cdc_msc_throughput/src/main.c
@@ -0,0 +1,151 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+
+#include "bsp/board_api.h"
+#include "tusb.h"
+
+// cdc_msc_throughput: minimal CDC+MSC device aimed at measuring pure USB bulk throughput.
+// MSC read/write callbacks don't touch any backing storage - write discards the
+// data and read only zero-fills the low LBAs the host scans during enumeration
+// (partition table, GPT header). Higher LBAs return whatever is already in the
+// transfer buffer, so `dd` numbers reflect the USB/driver ceiling, not any
+// simulated storage or per-byte memset cost.
+// CDC path drains RX in tud_cdc_rx_cb and sources TX from a static filler in the
+// main loop so `dd` can target /dev/ttyACMx in either direction.
+
+static void cdc_throughput_task(void);
+
+//--------------------------------------------------------------------+
+// Main
+//--------------------------------------------------------------------+
+int main(void) {
+ board_init();
+
+ tusb_rhport_init_t dev_init = {.role = TUSB_ROLE_DEVICE, .speed = TUSB_SPEED_AUTO};
+ tusb_init(BOARD_TUD_RHPORT, &dev_init);
+
+ board_init_after_tusb();
+
+ while (1) {
+ tud_task();
+ cdc_throughput_task();
+ }
+}
+
+//--------------------------------------------------------------------+
+// CDC callbacks + tasks
+//--------------------------------------------------------------------+
+void tud_cdc_rx_cb(uint8_t itf) {
+ (void) itf;
+ tud_cdc_read_flush(); // Drain RX
+}
+
+static void cdc_throughput_task(void) {
+ if (!tud_cdc_connected()) return;
+
+ // Source TX: fill whatever write room is free.
+ static uint8_t const filler[CFG_TUD_CDC_TX_EPSIZE] = {0};
+ uint32_t room = tud_cdc_write_available();
+ while (room > 0) {
+ uint32_t n = tud_cdc_write(filler, tu_min32(room, sizeof(filler)));
+ if (n == 0) {
+ break;
+ }
+ room -= n;
+ }
+ tud_cdc_write_flush();
+}
+
+//--------------------------------------------------------------------+
+// MSC callbacks
+//--------------------------------------------------------------------+
+
+// 1 GiB logical capacity so `dd` can run long enough for stable numbers.
+// No real backing store - block content is synthesised on read, discarded on write.
+enum {
+ DISK_BLOCK_SIZE = 512,
+ DISK_BLOCK_COUNT = 0x00200000u, // 2 Mi blocks = 1 GiB
+ // Kernel probes partition-table / filesystem-superblock locations near the
+ // start of the disk during enumeration. Zero-fill only this head range so the
+ // block layer sees "no partition, no filesystem" and leaves us alone; higher
+ // LBAs skip the memset so `dd` measures pure USB/driver throughput.
+ DISK_ZEROFILL_LBA = 64, // 32 KiB
+};
+
+void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) {
+ (void) lun;
+ const char vid[] = "TinyUSB";
+ const char pid[] = "Mass Storage";
+ const char rev[] = "1.0";
+ (void) strncpy((char*) vendor_id, vid, 8);
+ (void) strncpy((char*) product_id, pid, 16);
+ (void) strncpy((char*) product_rev, rev, 4);
+}
+
+bool tud_msc_test_unit_ready_cb(uint8_t lun) {
+ (void) lun;
+ return true;
+}
+
+void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size) {
+ (void) lun;
+ *block_count = DISK_BLOCK_COUNT;
+ *block_size = DISK_BLOCK_SIZE;
+}
+
+bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) {
+ (void) lun; (void) power_condition; (void) start; (void) load_eject;
+ return true;
+}
+
+bool tud_msc_is_writable_cb(uint8_t lun) {
+ (void) lun;
+ return true;
+}
+
+// READ10: zero-fill only the head range the kernel inspects, skip memset everywhere
+// else so we measure the USB / driver path rather than memset cost.
+int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void *buffer, uint32_t bufsize) {
+ (void) lun; (void) offset;
+ if (lba < DISK_ZEROFILL_LBA) {
+ memset(buffer, 0, bufsize);
+ } else {
+ (void) buffer;
+ }
+ return (int32_t) bufsize;
+}
+
+// WRITE10: discard the received data entirely - this is the pure USB-speed test.
+int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize) {
+ (void) lun; (void) lba; (void) offset; (void) buffer;
+ return (int32_t) bufsize;
+}
+
+// Unknown SCSI commands: stall with Invalid Command sense.
+int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void *buffer, uint16_t bufsize) {
+ (void) scsi_cmd; (void) buffer; (void) bufsize;
+ tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00);
+ return -1;
+}
diff --git a/examples/device/cdc_msc_throughput/src/tusb_config.h b/examples/device/cdc_msc_throughput/src/tusb_config.h
new file mode 100644
index 000000000..6c8655719
--- /dev/null
+++ b/examples/device/cdc_msc_throughput/src/tusb_config.h
@@ -0,0 +1,103 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef TUSB_CONFIG_H_
+#define TUSB_CONFIG_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//--------------------------------------------------------------------
+// Board Specific Configuration
+//--------------------------------------------------------------------
+
+#ifndef BOARD_TUD_RHPORT
+ #define BOARD_TUD_RHPORT 0
+#endif
+
+#ifndef BOARD_TUD_MAX_SPEED
+ #define BOARD_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED
+#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
+
+// Enable Device stack
+#define CFG_TUD_ENABLED 1
+#define CFG_TUD_MAX_SPEED BOARD_TUD_MAX_SPEED
+
+#ifndef CFG_TUSB_MEM_SECTION
+ #define CFG_TUSB_MEM_SECTION
+#endif
+
+#ifndef CFG_TUSB_MEM_ALIGN
+ #define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4)))
+#endif
+
+//--------------------------------------------------------------------
+// DEVICE CONFIGURATION
+//--------------------------------------------------------------------
+
+#ifndef CFG_TUD_ENDPOINT0_SIZE
+ #define CFG_TUD_ENDPOINT0_SIZE 64
+#endif
+
+//------------- CLASS -------------//
+#define CFG_TUD_CDC 1
+#define CFG_TUD_MSC 1
+
+// Large MSC bulk buffer: host transfers big CBW payloads (e.g. dd bs=1M does 64KiB
+// chunks). A 4K per-bulk-IO buffer lets the class driver amortise the per-CBW
+// overhead across many USB packets, approximating the maximum USB bulk throughput.
+#define CFG_TUD_MSC_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 4096 : 1024)
+
+// #define CFG_TUD_CDC_TX_PERSISTENT 1
+
+// CDC throughput: size for HS; tinyusb will auto-scale for FS via TUD_OPT_HIGH_SPEED.
+#define CFG_TUD_CDC_RX_EPSIZE (TUD_OPT_HIGH_SPEED ? 2*512 : 2*64)
+#define CFG_TUD_CDC_TX_EPSIZE CFG_TUD_CDC_RX_EPSIZE
+
+#define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 2*512 : 2*64)
+#define CFG_TUD_CDC_TX_BUFSIZE CFG_TUD_CDC_RX_BUFSIZE
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TUSB_CONFIG_H_ */
diff --git a/examples/device/cdc_msc_throughput/src/usb_descriptors.c b/examples/device/cdc_msc_throughput/src/usb_descriptors.c
new file mode 100644
index 000000000..3b0ff6e17
--- /dev/null
+++ b/examples/device/cdc_msc_throughput/src/usb_descriptors.c
@@ -0,0 +1,186 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+
+#include "bsp/board_api.h"
+#include "tusb.h"
+
+#define USB_PID (0x4000 | ((CFG_TUD_CDC) ? (1 << 0) : 0) | ((CFG_TUD_MSC) ? (1 << 1) : 0))
+#define USB_VID 0xCafe
+#define USB_BCD 0x0200
+
+static tusb_desc_device_t const desc_device = {
+ .bLength = sizeof(tusb_desc_device_t),
+ .bDescriptorType = TUSB_DESC_DEVICE,
+ .bcdUSB = USB_BCD,
+
+ // IAD required for composite CDC + MSC
+ .bDeviceClass = TUSB_CLASS_MISC,
+ .bDeviceSubClass = MISC_SUBCLASS_COMMON,
+ .bDeviceProtocol = MISC_PROTOCOL_IAD,
+ .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
+
+ .idVendor = USB_VID,
+ .idProduct = USB_PID,
+ .bcdDevice = 0x0100,
+
+ .iManufacturer = 0x01,
+ .iProduct = 0x02,
+ .iSerialNumber = 0x03,
+
+ .bNumConfigurations = 0x01,
+};
+
+uint8_t const *tud_descriptor_device_cb(void) {
+ return (uint8_t const *) &desc_device;
+}
+
+enum {
+ ITF_NUM_CDC = 0,
+ ITF_NUM_CDC_DATA,
+ ITF_NUM_MSC,
+ ITF_NUM_TOTAL,
+};
+
+// Place bulk endpoints on EP>=8 for MAX32690 class parts (bigger FIFO, DPB-capable).
+#if CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
+ #if TU_CHECK_MCU(OPT_MCU_MAX32650, OPT_MCU_MAX32666, OPT_MCU_MAX32690, OPT_MCU_MAX78002)
+ // Put bulk on EP>=8 so the 2048/4096-byte FIFOs can back double packet buffering
+ #define EPNUM_CDC_NOTIF 0x81
+ #define EPNUM_CDC_OUT 0x08
+ #define EPNUM_CDC_IN 0x89
+ #define EPNUM_MSC_OUT 0x0A
+ #define EPNUM_MSC_IN 0x8B
+ #else
+ #define EPNUM_CDC_NOTIF 0x81
+ #define EPNUM_CDC_OUT 0x02
+ #define EPNUM_CDC_IN 0x83
+ #define EPNUM_MSC_OUT 0x04
+ #define EPNUM_MSC_IN 0x85
+ #endif
+#else
+ #define EPNUM_CDC_NOTIF 0x81
+ #define EPNUM_CDC_OUT 0x02
+ #define EPNUM_CDC_IN 0x82
+ #define EPNUM_MSC_OUT 0x03
+ #define EPNUM_MSC_IN 0x83
+#endif
+
+#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_MSC_DESC_LEN)
+
+static uint8_t const desc_fs_configuration[] = {
+ TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100),
+ TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 16, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64),
+ TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 5, EPNUM_MSC_OUT, EPNUM_MSC_IN, 64),
+};
+
+#if TUD_OPT_HIGH_SPEED
+static uint8_t const desc_hs_configuration[] = {
+ TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100),
+ TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 16, EPNUM_CDC_OUT, EPNUM_CDC_IN, 512),
+ TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 5, EPNUM_MSC_OUT, EPNUM_MSC_IN, 512),
+};
+
+static uint8_t desc_other_speed_config[CONFIG_TOTAL_LEN];
+
+static tusb_desc_device_qualifier_t const desc_device_qualifier = {
+ .bLength = sizeof(tusb_desc_device_qualifier_t),
+ .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
+ .bcdUSB = USB_BCD,
+ .bDeviceClass = 0x00,
+ .bDeviceSubClass = 0x00,
+ .bDeviceProtocol = 0x00,
+ .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
+ .bNumConfigurations = 0x01,
+ .bReserved = 0x00,
+};
+
+uint8_t const *tud_descriptor_device_qualifier_cb(void) {
+ return (uint8_t const *) &desc_device_qualifier;
+}
+
+uint8_t const *tud_descriptor_other_speed_configuration_cb(uint8_t index) {
+ (void) index;
+ memcpy(desc_other_speed_config,
+ (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_fs_configuration : desc_hs_configuration,
+ CONFIG_TOTAL_LEN);
+ desc_other_speed_config[1] = TUSB_DESC_OTHER_SPEED_CONFIG;
+ return desc_other_speed_config;
+}
+#endif
+
+uint8_t const *tud_descriptor_configuration_cb(uint8_t index) {
+ (void) index;
+#if TUD_OPT_HIGH_SPEED
+ return (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_hs_configuration : desc_fs_configuration;
+#else
+ return desc_fs_configuration;
+#endif
+}
+
+enum {
+ STRID_LANGID = 0,
+ STRID_MANUFACTURER,
+ STRID_PRODUCT,
+ STRID_SERIAL,
+};
+
+static char const *string_desc_arr[] = {
+ (const char[]) { 0x09, 0x04 },
+ "TinyUSB",
+ "Throughput",
+ NULL,
+ "TinyUSB CDC",
+ "TinyUSB MSC",
+};
+
+static uint16_t _desc_str[32 + 1];
+
+uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
+ (void) langid;
+ size_t chr_count;
+
+ 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:
+ if (!(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0]))) return NULL;
+ const char *str = string_desc_arr[index];
+ chr_count = strlen(str);
+ size_t const max_count = sizeof(_desc_str) / sizeof(_desc_str[0]) - 1;
+ if (chr_count > max_count) chr_count = max_count;
+ for (size_t i = 0; i < chr_count; i++) _desc_str[1 + i] = str[i];
+ break;
+ }
+
+ _desc_str[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (2 * chr_count + 2));
+ return _desc_str;
+}
diff --git a/examples/device/cdc_uac2/src/usb_descriptors.c b/examples/device/cdc_uac2/src/usb_descriptors.c
index e6caaa971..fdffc761e 100644
--- a/examples/device/cdc_uac2/src/usb_descriptors.c
+++ b/examples/device/cdc_uac2/src/usb_descriptors.c
@@ -97,15 +97,25 @@ uint8_t const * tud_descriptor_device_cb(void)
#define EPNUM_CDC_OUT 0x02
#define EPNUM_CDC_IN 0x82
-#elif defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY)
+#elif CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
// MCUs that don't support a same endpoint number with different direction IN and OUT defined in tusb_mcu.h
// e.g EP1 OUT & EP1 IN cannot exist together
- #define EPNUM_AUDIO_IN 0x01
- #define EPNUM_AUDIO_OUT 0x02
+ #if TU_CHECK_MCU(OPT_MCU_MAX32650, OPT_MCU_MAX32666, OPT_MCU_MAX32690, OPT_MCU_MAX78002)
+ // Put CDC bulk on EP>=8 and audio iso on EP10/11 so the 2048/4096-byte FIFOs can back double packet buffering
+ #define EPNUM_AUDIO_OUT 0x0A
+ #define EPNUM_AUDIO_IN 0x0B
- #define EPNUM_CDC_NOTIF 0x83
- #define EPNUM_CDC_OUT 0x04
- #define EPNUM_CDC_IN 0x85
+ #define EPNUM_CDC_NOTIF 0x83
+ #define EPNUM_CDC_OUT 0x08
+ #define EPNUM_CDC_IN 0x89
+ #else
+ #define EPNUM_AUDIO_IN 0x01
+ #define EPNUM_AUDIO_OUT 0x02
+
+ #define EPNUM_CDC_NOTIF 0x83
+ #define EPNUM_CDC_OUT 0x04
+ #define EPNUM_CDC_IN 0x85
+ #endif
#else
#define EPNUM_AUDIO_IN 0x01
diff --git a/examples/device/dfu/skip.txt b/examples/device/dfu/skip.txt
index 79d3da9d2..ccff857ac 100644
--- a/examples/device/dfu/skip.txt
+++ b/examples/device/dfu/skip.txt
@@ -1,3 +1,2 @@
-mcu:TM4C
mcu:BCM2835
family:espressif
diff --git a/examples/device/dynamic_configuration/src/usb_descriptors.c b/examples/device/dynamic_configuration/src/usb_descriptors.c
index 458b7c2a5..c4049414f 100644
--- a/examples/device/dynamic_configuration/src/usb_descriptors.c
+++ b/examples/device/dynamic_configuration/src/usb_descriptors.c
@@ -132,7 +132,7 @@ enum
#define EPNUM_1_MSC_OUT 0x02
#define EPNUM_1_MSC_IN 0x82
-#elif defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY)
+#elif CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
// MCUs that don't support a same endpoint number with different direction IN and OUT defined in tusb_mcu.h
// e.g EP1 OUT & EP1 IN cannot exist together
#define EPNUM_0_CDC_NOTIF 0x81
diff --git a/examples/device/hid_generic_inout/src/usb_descriptors.c b/examples/device/hid_generic_inout/src/usb_descriptors.c
index 929b2fd3a..93e718461 100644
--- a/examples/device/hid_generic_inout/src/usb_descriptors.c
+++ b/examples/device/hid_generic_inout/src/usb_descriptors.c
@@ -97,7 +97,7 @@ enum
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_INOUT_DESC_LEN)
-#if defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY)
+#if CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
// MCUs that don't support a same endpoint number with different direction IN and OUT defined in tusb_mcu.h
// e.g EP1 OUT & EP1 IN cannot exist together
#define EPNUM_HID_OUT 0x01
diff --git a/examples/device/midi2_device/CMakeLists.txt b/examples/device/midi2_device/CMakeLists.txt
new file mode 100644
index 000000000..295af6550
--- /dev/null
+++ b/examples/device/midi2_device/CMakeLists.txt
@@ -0,0 +1,33 @@
+cmake_minimum_required(VERSION 3.20)
+
+include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake)
+
+project(midi2_device 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/main.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/usb_descriptors.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_device_example(${PROJECT_NAME} noos)
+
+# Suppress pre-existing warning in usbd.c (uint8_t comparison always true/false)
+target_compile_options(${PROJECT_NAME} PRIVATE -Wno-type-limits)
diff --git a/examples/device/midi2_device/CMakePresets.json b/examples/device/midi2_device/CMakePresets.json
new file mode 100644
index 000000000..5cd8971e9
--- /dev/null
+++ b/examples/device/midi2_device/CMakePresets.json
@@ -0,0 +1,6 @@
+{
+ "version": 6,
+ "include": [
+ "../../../hw/bsp/BoardPresets.json"
+ ]
+}
diff --git a/examples/device/midi2_device/Makefile b/examples/device/midi2_device/Makefile
new file mode 100644
index 000000000..829d9da59
--- /dev/null
+++ b/examples/device/midi2_device/Makefile
@@ -0,0 +1,16 @@
+include ../../../hw/bsp/family_support.mk
+
+INC += \
+ src \
+
+# Example source
+EXAMPLE_SOURCE += \
+ src/main.c \
+ src/usb_descriptors.c \
+
+SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE))
+
+# Suppress pre-existing warning in usbd.c
+CFLAGS_GCC += -Wno-type-limits
+
+include ../../../hw/bsp/family_rules.mk
diff --git a/examples/device/midi2_device/README.md b/examples/device/midi2_device/README.md
new file mode 100644
index 000000000..1731dba57
--- /dev/null
+++ b/examples/device/midi2_device/README.md
@@ -0,0 +1,59 @@
+# MIDI 2.0 Song Sender
+
+USB MIDI 2.0 Device example that plays "Twinkle Twinkle Little Star" using
+native UMP (Universal MIDI Packet) format with full MIDI 2.0 expression.
+
+## MIDI 2.0 Features Demonstrated
+
+- 16-bit Velocity (vs 7-bit MIDI 1.0)
+- 32-bit Control Change values
+- 32-bit Pitch Bend (vs 14-bit MIDI 1.0)
+- 32-bit Channel Pressure (Aftertouch)
+- 32-bit Poly Pressure (Per-Note Aftertouch)
+- Per-Note Management (MIDI 2.0 exclusive)
+- Program Change with Bank Select
+- JR Timestamps
+
+## USB Descriptor
+
+The device exposes both USB-MIDI 1.0 (Alt Setting 0) and USB-MIDI 2.0 (Alt Setting 1)
+as required by the USB-MIDI 2.0 specification. A MIDI 2.0 capable host (e.g. Windows
+MIDI Services) will select Alt Setting 1 for native UMP transport. Legacy hosts use
+Alt Setting 0 with automatic MIDI 1.0 fallback.
+
+## Hardware
+
+- Any RP2040 board with USB (e.g. Raspberry Pi Pico)
+- LED on GPIO 25: steady = playing, slow blink = waiting for host
+
+## Building
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico -DPICO_SDK_FETCH_FROM_GIT=on -G Ninja ..
+cmake --build .
+```
+
+## Flashing
+
+Hold BOOTSEL, connect USB, drag `midi2_device.uf2` to the RPI-RP2 drive.
+
+## Testing
+
+**Linux:**
+```bash
+aseqdump -p "MIDI 2.0 Device"
+```
+
+**Windows (MIDI 2.0 native):**
+```powershell
+midi endpoint list
+midi endpoint monitor
+```
+
+## Song Data
+
+Twinkle Twinkle Little Star in C major, 120 BPM. Six phrases with dynamic
+shaping (pp to ff crescendo and back), pitch bend vibrato on sustained notes,
+and channel/poly pressure for expression. All values use genuine MIDI 2.0
+resolution with no 7-bit equivalent.
diff --git a/examples/device/midi2_device/skip.txt b/examples/device/midi2_device/skip.txt
new file mode 100644
index 000000000..eadb6e74a
--- /dev/null
+++ b/examples/device/midi2_device/skip.txt
@@ -0,0 +1 @@
+mcu:SAMD11
diff --git a/examples/device/midi2_device/src/main.c b/examples/device/midi2_device/src/main.c
new file mode 100644
index 000000000..62741ac41
--- /dev/null
+++ b/examples/device/midi2_device/src/main.c
@@ -0,0 +1,720 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 Saulo Verissimo
+ *
+ * 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 "class/midi/midi2_device.h"
+
+//--------------------------------------------------------------------+
+// MIDI 2.0 UMP Message Type Constants (M2-104-UM, Section 4)
+//--------------------------------------------------------------------+
+// Message Type (MT) occupies bits 31-28 of Word 0
+#define UMP_MT_UTILITY 0x00000000 // 32-bit: Utility (NOOP, JR Clock, JR Timestamp)
+#define UMP_MT_SYSTEM 0x10000000 // 32-bit: System Common / Real Time
+#define UMP_MT_MIDI1_CV 0x20000000 // 32-bit: MIDI 1.0 Channel Voice
+#define UMP_MT_DATA64 0x30000000 // 64-bit: Data (SysEx 7-bit)
+#define UMP_MT_MIDI2_CV 0x40000000 // 64-bit: MIDI 2.0 Channel Voice
+#define UMP_MT_DATA128 0x50000000 // 128-bit: Data (SysEx 8-bit)
+
+// MIDI 2.0 Channel Voice status (bits 23-20 of Word 0)
+#define UMP_STATUS_NOTE_OFF 0x00800000
+#define UMP_STATUS_NOTE_ON 0x00900000
+#define UMP_STATUS_POLY_PRESSURE 0x00A00000
+#define UMP_STATUS_CC 0x00B00000
+#define UMP_STATUS_PROGRAM 0x00C00000
+#define UMP_STATUS_CHAN_PRESSURE 0x00D00000
+#define UMP_STATUS_PITCH_BEND 0x00E00000
+#define UMP_STATUS_PN_MGMT 0x00F00000 // Per-Note Management
+
+// Note Attribute Types (MIDI 2.0 spec, Section 4.2.6)
+#define UMP_ATTR_NONE 0x00
+#define UMP_ATTR_MANUFACTURER 0x01
+#define UMP_ATTR_PROFILE 0x02
+#define UMP_ATTR_PITCH_7_9 0x03 // Pitch 7.9 format
+
+//--------------------------------------------------------------------+
+// MIDI 2.0 UMP Builders - Full Spec Coverage
+//--------------------------------------------------------------------+
+
+// Helper: send a 64-bit UMP (2 words)
+static inline void ump_send_64(uint32_t w0, uint32_t w1) {
+ uint32_t words[2] = { w0, w1 };
+ tud_midi2_ump_write(words, 2);
+}
+
+// Helper: send a 32-bit UMP (1 word)
+static inline void ump_send_32(uint32_t w0) {
+ tud_midi2_ump_write(&w0, 1);
+}
+
+// -- Utility Messages (MT=0x0, 32-bit) --
+
+static inline void ump_jr_timestamp(uint16_t timestamp) {
+ // Word 0: [MT(0x0) | Group(0) | Status(0x0020) | Timestamp(16-bit)]
+ ump_send_32(UMP_MT_UTILITY | 0x00200000 | (uint32_t)timestamp);
+}
+
+// -- MIDI 2.0 Channel Voice: Note On (MT=0x4, 64-bit) --
+// Word 0: [MT(4):Group(4):Status(4):Channel(4):NoteNumber(8):AttrType(8)]
+// Word 1: [Velocity(16):Attribute(16)]
+static inline void ump_note_on(uint8_t group, uint8_t channel,
+ uint8_t pitch, uint16_t velocity,
+ uint8_t attr_type, uint16_t attr_val) {
+ uint32_t w0 = UMP_MT_MIDI2_CV | ((uint32_t)(group & 0x0F) << 24)
+ | UMP_STATUS_NOTE_ON | ((uint32_t)(channel & 0x0F) << 16)
+ | ((uint32_t)(pitch & 0x7F) << 8)
+ | (uint32_t)(attr_type & 0xFF);
+ uint32_t w1 = ((uint32_t)(velocity & 0xFFFF) << 16)
+ | (uint32_t)(attr_val & 0xFFFF);
+ ump_send_64(w0, w1);
+}
+
+// -- MIDI 2.0 Channel Voice: Note Off (MT=0x4, 64-bit) --
+static inline void ump_note_off(uint8_t group, uint8_t channel,
+ uint8_t pitch, uint16_t velocity,
+ uint8_t attr_type, uint16_t attr_val) {
+ uint32_t w0 = UMP_MT_MIDI2_CV | ((uint32_t)(group & 0x0F) << 24)
+ | UMP_STATUS_NOTE_OFF | ((uint32_t)(channel & 0x0F) << 16)
+ | ((uint32_t)(pitch & 0x7F) << 8)
+ | (uint32_t)(attr_type & 0xFF);
+ uint32_t w1 = ((uint32_t)(velocity & 0xFFFF) << 16)
+ | (uint32_t)(attr_val & 0xFFFF);
+ ump_send_64(w0, w1);
+}
+
+// -- MIDI 2.0 Channel Voice: Control Change (MT=0x4, 64-bit) --
+// Word 0: [MT(4):Group(4):Status(0xB):Channel(4):Index(8):Reserved(8)]
+// Word 1: [Data(32)] -- full 32-bit CC resolution (vs 7-bit MIDI 1.0)
+static inline void ump_cc(uint8_t group, uint8_t channel,
+ uint8_t index, uint32_t value) {
+ uint32_t w0 = UMP_MT_MIDI2_CV | ((uint32_t)(group & 0x0F) << 24)
+ | UMP_STATUS_CC | ((uint32_t)(channel & 0x0F) << 16)
+ | ((uint32_t)(index & 0x7F) << 8);
+ ump_send_64(w0, value);
+}
+
+// -- MIDI 2.0 Channel Voice: Program Change (MT=0x4, 64-bit) --
+// Word 0: [MT(4):Group(4):Status(0xC):Channel(4):Reserved(8):OptionFlags(8)]
+// Word 1: [Program(8):Reserved(8):BankMSB(8):BankLSB(8)]
+// OptionFlags bit 0 = Bank Valid
+static inline void ump_program_change(uint8_t group, uint8_t channel,
+ uint8_t program,
+ bool bank_valid, uint8_t bank_msb,
+ uint8_t bank_lsb) {
+ uint8_t flags = bank_valid ? 0x01 : 0x00;
+ uint32_t w0 = UMP_MT_MIDI2_CV | ((uint32_t)(group & 0x0F) << 24)
+ | UMP_STATUS_PROGRAM | ((uint32_t)(channel & 0x0F) << 16)
+ | (uint32_t)flags;
+ uint32_t w1 = ((uint32_t)program << 24)
+ | ((uint32_t)bank_msb << 8)
+ | (uint32_t)bank_lsb;
+ ump_send_64(w0, w1);
+}
+
+// -- MIDI 2.0 Channel Voice: Pitch Bend (MT=0x4, 64-bit) --
+// Word 0: [MT(4):Group(4):Status(0xE):Channel(4):Reserved(16)]
+// Word 1: [PitchBend(32)] -- full 32-bit (vs 14-bit MIDI 1.0!)
+// 0x80000000 = center, 0x00000000 = min, 0xFFFFFFFF = max
+static inline void ump_pitch_bend(uint8_t group, uint8_t channel,
+ uint32_t value) {
+ uint32_t w0 = UMP_MT_MIDI2_CV | ((uint32_t)(group & 0x0F) << 24)
+ | UMP_STATUS_PITCH_BEND | ((uint32_t)(channel & 0x0F) << 16);
+ ump_send_64(w0, value);
+}
+
+// -- MIDI 2.0 Channel Voice: Channel Pressure / Aftertouch (MT=0x4, 64-bit) --
+// Word 0: [MT(4):Group(4):Status(0xD):Channel(4):Reserved(16)]
+// Word 1: [Pressure(32)] -- full 32-bit (vs 7-bit MIDI 1.0)
+static inline void ump_channel_pressure(uint8_t group, uint8_t channel,
+ uint32_t pressure) {
+ uint32_t w0 = UMP_MT_MIDI2_CV | ((uint32_t)(group & 0x0F) << 24)
+ | UMP_STATUS_CHAN_PRESSURE | ((uint32_t)(channel & 0x0F) << 16);
+ ump_send_64(w0, pressure);
+}
+
+// -- MIDI 2.0 Channel Voice: Poly Pressure / Per-Note Aftertouch --
+// Word 0: [MT(4):Group(4):Status(0xA):Channel(4):NoteNumber(8):Reserved(8)]
+// Word 1: [Pressure(32)]
+static inline void ump_poly_pressure(uint8_t group, uint8_t channel,
+ uint8_t pitch, uint32_t pressure) {
+ uint32_t w0 = UMP_MT_MIDI2_CV | ((uint32_t)(group & 0x0F) << 24)
+ | UMP_STATUS_POLY_PRESSURE | ((uint32_t)(channel & 0x0F) << 16)
+ | ((uint32_t)(pitch & 0x7F) << 8);
+ ump_send_64(w0, pressure);
+}
+
+// -- MIDI 2.0 Channel Voice: Per-Note Management (MT=0x4, 64-bit) --
+// Exclusive to MIDI 2.0: controls per-note behavior
+// Word 0: [MT(4):Group(4):Status(0xF):Channel(4):NoteNumber(8):Flags(8)]
+// Word 1: Reserved
+// Flags bit 1 = Reset (S), bit 0 = Detach (D)
+static inline void ump_per_note_mgmt(uint8_t group, uint8_t channel,
+ uint8_t pitch, bool detach,
+ bool reset) {
+ uint8_t flags = (reset ? 0x02 : 0x00) | (detach ? 0x01 : 0x00);
+ uint32_t w0 = UMP_MT_MIDI2_CV | ((uint32_t)(group & 0x0F) << 24)
+ | UMP_STATUS_PN_MGMT | ((uint32_t)(channel & 0x0F) << 16)
+ | ((uint32_t)(pitch & 0x7F) << 8)
+ | (uint32_t)flags;
+ ump_send_64(w0, 0x00000000);
+}
+
+//--------------------------------------------------------------------+
+// MIDI 1.0 Channel Voice Builders (UMP MT 0x2, 32-bit)
+//--------------------------------------------------------------------+
+// Used on Alt 1 when negotiated protocol is MIDI 1.0.
+// Word layout: [MT(0x2) | Group(4b) | Status(8b) | Data1(8b) | Data2(8b)]
+// Status nibbles: 0x8=NoteOff, 0x9=NoteOn, 0xA=PolyPress, 0xB=CC,
+// 0xC=ProgChg, 0xD=ChanPress, 0xE=PitchBend.
+
+static inline void ump_midi1_send(uint8_t group, uint8_t status,
+ uint8_t data1, uint8_t data2) {
+ uint32_t w = UMP_MT_MIDI1_CV
+ | ((uint32_t)(group & 0x0F) << 24)
+ | ((uint32_t)status << 16)
+ | ((uint32_t)data1 << 8)
+ | (uint32_t)data2;
+ ump_send_32(w);
+}
+
+static inline void ump_midi1_note_on(uint8_t group, uint8_t channel,
+ uint8_t note, uint8_t vel7) {
+ ump_midi1_send(group, 0x90 | (channel & 0x0F), note & 0x7F, vel7 & 0x7F);
+}
+
+static inline void ump_midi1_note_off(uint8_t group, uint8_t channel,
+ uint8_t note, uint8_t vel7) {
+ ump_midi1_send(group, 0x80 | (channel & 0x0F), note & 0x7F, vel7 & 0x7F);
+}
+
+static inline void ump_midi1_cc(uint8_t group, uint8_t channel,
+ uint8_t cc, uint8_t val7) {
+ ump_midi1_send(group, 0xB0 | (channel & 0x0F), cc & 0x7F, val7 & 0x7F);
+}
+
+static inline void ump_midi1_program(uint8_t group, uint8_t channel,
+ uint8_t program) {
+ ump_midi1_send(group, 0xC0 | (channel & 0x0F), program & 0x7F, 0);
+}
+
+static inline void ump_midi1_pitch_bend(uint8_t group, uint8_t channel,
+ uint16_t value14) {
+ // 14-bit pitch bend: LSB first, then MSB. Center = 0x2000.
+ ump_midi1_send(group, 0xE0 | (channel & 0x0F),
+ (uint8_t)(value14 & 0x7F),
+ (uint8_t)((value14 >> 7) & 0x7F));
+}
+
+static inline void ump_midi1_channel_pressure(uint8_t group, uint8_t channel,
+ uint8_t val7) {
+ ump_midi1_send(group, 0xD0 | (channel & 0x0F), val7 & 0x7F, 0);
+}
+
+static inline void ump_midi1_poly_pressure(uint8_t group, uint8_t channel,
+ uint8_t note, uint8_t val7) {
+ ump_midi1_send(group, 0xA0 | (channel & 0x0F), note & 0x7F, val7 & 0x7F);
+}
+
+//--------------------------------------------------------------------+
+// USB-MIDI 1.0 32-bit Event Packet Builders (Alt 0 transport)
+//--------------------------------------------------------------------+
+// Used on Alt 0 (USB-MIDI 1.0). Each packet is 4 raw bytes:
+// [(Cable << 4) | CIN] [Status] [Data1] [Data2]
+// CIN = Code Index Number. See USB-MIDI 1.0 spec Section 4.
+
+static inline void midi1_pkt_send(uint8_t cable, uint8_t cin,
+ uint8_t status, uint8_t data1,
+ uint8_t data2) {
+ uint8_t packet[4] = {
+ (uint8_t)(((cable & 0x0F) << 4) | (cin & 0x0F)),
+ status, data1, data2
+ };
+ (void) tud_midi2_packet_write(packet, 1);
+}
+
+static inline void midi1_pkt_note_on(uint8_t cable, uint8_t channel,
+ uint8_t note, uint8_t vel7) {
+ midi1_pkt_send(cable, 0x9, 0x90 | (channel & 0x0F),
+ note & 0x7F, vel7 & 0x7F);
+}
+
+static inline void midi1_pkt_note_off(uint8_t cable, uint8_t channel,
+ uint8_t note, uint8_t vel7) {
+ midi1_pkt_send(cable, 0x8, 0x80 | (channel & 0x0F),
+ note & 0x7F, vel7 & 0x7F);
+}
+
+static inline void midi1_pkt_cc(uint8_t cable, uint8_t channel,
+ uint8_t cc, uint8_t val7) {
+ midi1_pkt_send(cable, 0xB, 0xB0 | (channel & 0x0F),
+ cc & 0x7F, val7 & 0x7F);
+}
+
+static inline void midi1_pkt_program(uint8_t cable, uint8_t channel,
+ uint8_t program) {
+ midi1_pkt_send(cable, 0xC, 0xC0 | (channel & 0x0F),
+ program & 0x7F, 0);
+}
+
+static inline void midi1_pkt_pitch_bend(uint8_t cable, uint8_t channel,
+ uint16_t value14) {
+ midi1_pkt_send(cable, 0xE, 0xE0 | (channel & 0x0F),
+ (uint8_t)(value14 & 0x7F),
+ (uint8_t)((value14 >> 7) & 0x7F));
+}
+
+static inline void midi1_pkt_channel_pressure(uint8_t cable, uint8_t channel,
+ uint8_t val7) {
+ midi1_pkt_send(cable, 0xD, 0xD0 | (channel & 0x0F), val7 & 0x7F, 0);
+}
+
+static inline void midi1_pkt_poly_pressure(uint8_t cable, uint8_t channel,
+ uint8_t note, uint8_t val7) {
+ midi1_pkt_send(cable, 0xA, 0xA0 | (channel & 0x0F),
+ note & 0x7F, val7 & 0x7F);
+}
+
+//--------------------------------------------------------------------+
+// Scaling Helpers (MIDI 2.0 ↔ MIDI 1.0)
+//--------------------------------------------------------------------+
+
+static inline uint8_t scale_vel16_to_vel7(uint16_t v16) { return (uint8_t)(v16 >> 9); }
+static inline uint8_t scale_val32_to_val7(uint32_t v32) { return (uint8_t)(v32 >> 25); }
+static inline uint16_t scale_pb32_to_pb14(uint32_t pb32) { return (uint16_t)(pb32 >> 18); }
+
+//--------------------------------------------------------------------+
+// Dispatch Layer - Transport + Protocol Fallback
+//--------------------------------------------------------------------+
+// Follows the same idea as the UAC examples (`tud_descriptor_configuration_cb`
+// returning UAC1 or UAC2 based on bus speed): pick the path that matches the
+// state the host put us in. Here the decision is made per message because
+// MIDI 2.0 advertises both alts in a single config descriptor; the host
+// selects via SetInterface and UMP Stream protocol negotiation.
+
+static void send_note_on(uint8_t grp, uint8_t ch, uint8_t note, uint16_t vel16) {
+ uint8_t alt = tud_midi2_alt_setting();
+ if (alt == 0) {
+ midi1_pkt_note_on(grp, ch, note, scale_vel16_to_vel7(vel16));
+ } else if (tud_midi2_protocol() == MIDI_PROTOCOL_MIDI1) {
+ ump_midi1_note_on(grp, ch, note, scale_vel16_to_vel7(vel16));
+ } else {
+ ump_note_on(grp, ch, note, vel16, UMP_ATTR_NONE, 0);
+ }
+}
+
+static void send_note_off(uint8_t grp, uint8_t ch, uint8_t note, uint16_t vel16) {
+ uint8_t alt = tud_midi2_alt_setting();
+ if (alt == 0) {
+ midi1_pkt_note_off(grp, ch, note, scale_vel16_to_vel7(vel16));
+ } else if (tud_midi2_protocol() == MIDI_PROTOCOL_MIDI1) {
+ ump_midi1_note_off(grp, ch, note, scale_vel16_to_vel7(vel16));
+ } else {
+ ump_note_off(grp, ch, note, vel16, UMP_ATTR_NONE, 0);
+ }
+}
+
+static void send_cc(uint8_t grp, uint8_t ch, uint8_t cc, uint32_t val32) {
+ uint8_t alt = tud_midi2_alt_setting();
+ if (alt == 0) {
+ midi1_pkt_cc(grp, ch, cc, scale_val32_to_val7(val32));
+ } else if (tud_midi2_protocol() == MIDI_PROTOCOL_MIDI1) {
+ ump_midi1_cc(grp, ch, cc, scale_val32_to_val7(val32));
+ } else {
+ ump_cc(grp, ch, cc, val32);
+ }
+}
+
+static void send_program_change(uint8_t grp, uint8_t ch, uint8_t program,
+ bool with_bank, uint8_t bank_msb,
+ uint8_t bank_lsb) {
+ uint8_t alt = tud_midi2_alt_setting();
+ if (alt == 0) {
+ if (with_bank) {
+ midi1_pkt_cc(grp, ch, 0x00, bank_msb);
+ midi1_pkt_cc(grp, ch, 0x20, bank_lsb);
+ }
+ midi1_pkt_program(grp, ch, program);
+ } else if (tud_midi2_protocol() == MIDI_PROTOCOL_MIDI1) {
+ if (with_bank) {
+ ump_midi1_cc(grp, ch, 0x00, bank_msb);
+ ump_midi1_cc(grp, ch, 0x20, bank_lsb);
+ }
+ ump_midi1_program(grp, ch, program);
+ } else {
+ ump_program_change(grp, ch, program, with_bank, bank_msb, bank_lsb);
+ }
+}
+
+static void send_pitch_bend(uint8_t grp, uint8_t ch, uint32_t pb32) {
+ uint8_t alt = tud_midi2_alt_setting();
+ if (alt == 0) {
+ midi1_pkt_pitch_bend(grp, ch, scale_pb32_to_pb14(pb32));
+ } else if (tud_midi2_protocol() == MIDI_PROTOCOL_MIDI1) {
+ ump_midi1_pitch_bend(grp, ch, scale_pb32_to_pb14(pb32));
+ } else {
+ ump_pitch_bend(grp, ch, pb32);
+ }
+}
+
+static void send_channel_pressure(uint8_t grp, uint8_t ch, uint32_t val32) {
+ uint8_t alt = tud_midi2_alt_setting();
+ if (alt == 0) {
+ midi1_pkt_channel_pressure(grp, ch, scale_val32_to_val7(val32));
+ } else if (tud_midi2_protocol() == MIDI_PROTOCOL_MIDI1) {
+ ump_midi1_channel_pressure(grp, ch, scale_val32_to_val7(val32));
+ } else {
+ ump_channel_pressure(grp, ch, val32);
+ }
+}
+
+static void send_poly_pressure(uint8_t grp, uint8_t ch, uint8_t note,
+ uint32_t val32) {
+ uint8_t alt = tud_midi2_alt_setting();
+ if (alt == 0) {
+ midi1_pkt_poly_pressure(grp, ch, note, scale_val32_to_val7(val32));
+ } else if (tud_midi2_protocol() == MIDI_PROTOCOL_MIDI1) {
+ ump_midi1_poly_pressure(grp, ch, note, scale_val32_to_val7(val32));
+ } else {
+ ump_poly_pressure(grp, ch, note, val32);
+ }
+}
+
+//--------------------------------------------------------------------+
+// Song Data
+//--------------------------------------------------------------------+
+
+// Extended note event with MIDI 2.0 expression data
+typedef struct {
+ uint8_t pitch; // MIDI pitch (0-127, 0=rest)
+ uint16_t duration_ms; // Duration in ms
+ uint16_t velocity; // 16-bit velocity (MIDI 2.0)
+ uint32_t pressure; // 32-bit aftertouch (0 = none)
+ int16_t bend_cents; // Pitch bend in cents (0 = none, for vibrato/ornaments)
+} midi2_note_t;
+
+// 16-bit velocity (MIDI 2.0): values that have NO 7-bit equivalent.
+// MIDI 1.0 can only express 128 levels (0x0000, 0x0200, 0x0400 ... 0xFE00).
+// These use the full 16-bit range to prove genuine MIDI 2.0 resolution.
+#define V_PPP 0x0A3D // 2621 - between MIDI1 vel 5 and 6
+#define V_PP 0x1C71 // 7281 - between MIDI1 vel 14 and 15
+#define V_P 0x3219 // 12825 - between MIDI1 vel 24 and 25
+#define V_MP 0x4F5C // 20316 - between MIDI1 vel 39 and 40
+#define V_MF 0x6E93 // 28307 - between MIDI1 vel 55 and 56
+#define V_F 0x8DA5 // 36261 - between MIDI1 vel 70 and 71
+#define V_FF 0xAC37 // 44087 - between MIDI1 vel 85 and 86
+#define V_FFF 0xDEB8 // 57016 - between MIDI1 vel 111 and 112
+
+// Twinkle Twinkle Little Star - Traditional
+// Tempo: 120 BPM (500ms per quarter note)
+// Key: C major, 4/4
+// Demonstrates all MIDI 2.0 Channel Voice features:
+// 16-bit velocity, 32-bit CC, 32-bit pitch bend,
+// 32-bit channel pressure, per-note poly pressure,
+// per-note management, program change with bank select,
+// JR timestamps
+static const midi2_note_t song_data[] = {
+ // Phrase 1: "Twin-kle twin-kle lit-tle star" (C C G G A A G-)
+ // Crescendo pp -> mp, gentle entry
+ { .pitch = 60, .duration_ms = 500, .velocity = V_PP, .pressure = 0, .bend_cents = 0 }, // C4
+ { .pitch = 60, .duration_ms = 500, .velocity = V_P, .pressure = 0, .bend_cents = 0 }, // C4
+ { .pitch = 67, .duration_ms = 500, .velocity = V_MP, .pressure = 0, .bend_cents = 0 }, // G4
+ { .pitch = 67, .duration_ms = 500, .velocity = V_MP, .pressure = 0, .bend_cents = 0 }, // G4
+ { .pitch = 69, .duration_ms = 500, .velocity = V_MF, .pressure = 0x1A3D7E5F, .bend_cents = 0 }, // A4 (32-bit pressure)
+ { .pitch = 69, .duration_ms = 500, .velocity = V_MF, .pressure = 0x2B851EB9, .bend_cents = 0 }, // A4 (pressure swell)
+ { .pitch = 67, .duration_ms = 1000,.velocity = V_MF, .pressure = 0x3C6EF373, .bend_cents = 7 }, // G4 (half, bend 7 cents)
+
+ // Phrase 2: "How I won-der what you are" (F F E E D D C-)
+ // mf, sustained
+ { .pitch = 65, .duration_ms = 500, .velocity = V_MF, .pressure = 0, .bend_cents = 0 }, // F4
+ { .pitch = 65, .duration_ms = 500, .velocity = V_MF, .pressure = 0, .bend_cents = 0 }, // F4
+ { .pitch = 64, .duration_ms = 500, .velocity = V_MF, .pressure = 0x1E4C2B7A, .bend_cents = 0 }, // E4
+ { .pitch = 64, .duration_ms = 500, .velocity = V_MF, .pressure = 0x2D5A8FC1, .bend_cents = 0 }, // E4 (aftertouch swell)
+ { .pitch = 62, .duration_ms = 500, .velocity = V_MP, .pressure = 0, .bend_cents = 0 }, // D4
+ { .pitch = 62, .duration_ms = 500, .velocity = V_MP, .pressure = 0, .bend_cents = 0 }, // D4
+ { .pitch = 60, .duration_ms = 1000,.velocity = V_MP, .pressure = 0, .bend_cents = 0 }, // C4 (half, resolve)
+
+ // Phrase 3: "Up a-bove the world so high" (G G F F E E D-)
+ // f, building intensity
+ { .pitch = 67, .duration_ms = 500, .velocity = V_F, .pressure = 0, .bend_cents = 0 }, // G4
+ { .pitch = 67, .duration_ms = 500, .velocity = V_F, .pressure = 0, .bend_cents = 0 }, // G4
+ { .pitch = 65, .duration_ms = 500, .velocity = V_F, .pressure = 0x2F8A4E13, .bend_cents = 0 }, // F4
+ { .pitch = 65, .duration_ms = 500, .velocity = V_MF, .pressure = 0x41B2C9D7, .bend_cents = 0 }, // F4 (triggers poly pressure)
+ { .pitch = 64, .duration_ms = 500, .velocity = V_MF, .pressure = 0, .bend_cents = 0 }, // E4
+ { .pitch = 64, .duration_ms = 500, .velocity = V_MF, .pressure = 0, .bend_cents = 0 }, // E4
+ { .pitch = 62, .duration_ms = 1000,.velocity = V_MF, .pressure = 0x537DC2A6, .bend_cents = 13 }, // D4 (half, vibrato 13 cents)
+
+ // Phrase 4: "Like a dia-mond in the sky" (G G F F E E D-)
+ // ff, expressive peak
+ { .pitch = 67, .duration_ms = 500, .velocity = V_FF, .pressure = 0, .bend_cents = 0 }, // G4
+ { .pitch = 67, .duration_ms = 500, .velocity = V_FF, .pressure = 0, .bend_cents = 0 }, // G4
+ { .pitch = 65, .duration_ms = 500, .velocity = V_F, .pressure = 0x44E7B8D2, .bend_cents = 0 }, // F4 (triggers poly pressure)
+ { .pitch = 65, .duration_ms = 500, .velocity = V_F, .pressure = 0x56A3F14B, .bend_cents = 0 }, // F4 (triggers poly pressure)
+ { .pitch = 64, .duration_ms = 500, .velocity = V_MF, .pressure = 0x2C8E1F5A, .bend_cents = 0 }, // E4
+ { .pitch = 64, .duration_ms = 500, .velocity = V_MF, .pressure = 0x1D73A4E8, .bend_cents = 0 }, // E4
+ { .pitch = 62, .duration_ms = 1000,.velocity = V_MF, .pressure = 0x63F5B17D, .bend_cents = 19 }, // D4 (half, vibrato 19 cents)
+
+ // Phrase 5: "Twin-kle twin-kle lit-tle star" (C C G G A A G-)
+ // Diminuendo mf -> mp
+ { .pitch = 60, .duration_ms = 500, .velocity = V_MF, .pressure = 0, .bend_cents = 0 }, // C4
+ { .pitch = 60, .duration_ms = 500, .velocity = V_MF, .pressure = 0, .bend_cents = 0 }, // C4
+ { .pitch = 67, .duration_ms = 500, .velocity = V_MP, .pressure = 0, .bend_cents = 0 }, // G4
+ { .pitch = 67, .duration_ms = 500, .velocity = V_MP, .pressure = 0, .bend_cents = 0 }, // G4
+ { .pitch = 69, .duration_ms = 500, .velocity = V_MP, .pressure = 0x1B4F6D83, .bend_cents = 0 }, // A4
+ { .pitch = 69, .duration_ms = 500, .velocity = V_P, .pressure = 0x0E29C5A1, .bend_cents = 0 }, // A4
+ { .pitch = 67, .duration_ms = 1000,.velocity = V_P, .pressure = 0x2A6D3B9E, .bend_cents = 5 }, // G4 (half, gentle bend 5 cents)
+
+ // Phrase 6: "How I won-der what you are" (F F E E D D C-)
+ // Dying away mp -> ppp
+ { .pitch = 65, .duration_ms = 500, .velocity = V_MP, .pressure = 0, .bend_cents = 0 }, // F4
+ { .pitch = 65, .duration_ms = 500, .velocity = V_P, .pressure = 0, .bend_cents = 0 }, // F4
+ { .pitch = 64, .duration_ms = 500, .velocity = V_P, .pressure = 0, .bend_cents = 0 }, // E4
+ { .pitch = 64, .duration_ms = 500, .velocity = V_PP, .pressure = 0, .bend_cents = 0 }, // E4
+ { .pitch = 62, .duration_ms = 500, .velocity = V_PP, .pressure = 0, .bend_cents = 0 }, // D4
+ { .pitch = 62, .duration_ms = 500, .velocity = V_PPP, .pressure = 0, .bend_cents = 0 }, // D4
+ { .pitch = 60, .duration_ms = 2000,.velocity = V_PPP, .pressure = 0x07A1E3C9, .bend_cents = 0 }, // C4 (fermata)
+
+ // Silence before loop
+ { .pitch = 0, .duration_ms = 1000,.velocity = 0, .pressure = 0, .bend_cents = 0 },
+
+ // End marker
+ { .pitch = 0, .duration_ms = 0, .velocity = 0, .pressure = 0, .bend_cents = 0 },
+};
+
+#define SONG_LENGTH (sizeof(song_data) / sizeof(midi2_note_t))
+
+//--------------------------------------------------------------------+
+// Song Playback State Machine
+//--------------------------------------------------------------------+
+
+typedef struct {
+ uint32_t current_note_idx;
+ uint32_t note_start_ms;
+ uint8_t active_pitch;
+ bool note_is_active;
+ bool setup_sent; // Initial setup (Program Change, CC) sent?
+ uint32_t loop_count;
+} song_state_t;
+
+static song_state_t song = { 0 };
+
+// Forward declarations
+void update_song_playback(uint32_t now_ms);
+void send_initial_setup(void);
+
+//--------------------------------------------------------------------+
+// MIDI 2.0 Device Callbacks (override weak stubs from middleware)
+//--------------------------------------------------------------------+
+
+void tud_midi2_rx_cb(uint8_t itf) {
+ // Drain the RX FIFO in a loop until empty. Leaving words in the FIFO
+ // across callbacks can prevent subsequent bulk OUT transfers from landing.
+ uint32_t words[8];
+ uint32_t n;
+ while ((n = tud_midi2_n_ump_read(itf, words, TU_ARRAY_SIZE(words))) > 0) {
+ (void) n;
+ }
+}
+
+// Reset playback state and re-send setup when host switches alt setting or
+// renegotiates the UMP Stream protocol.
+void tud_midi2_set_itf_cb(uint8_t itf, uint8_t alt) {
+ (void)itf;
+ song.setup_sent = false;
+ printf("[ALT] Host selected alt=%u\r\n", (unsigned)alt);
+}
+
+//--------------------------------------------------------------------+
+// Initial Setup - Program Change, CC, Per-Note Management
+//--------------------------------------------------------------------+
+
+void send_initial_setup(void) {
+ // JR Timestamp is UMP-only (Utility MT 0x0); skipped on Alt 0 transport.
+ if (tud_midi2_alt_setting() == 1) ump_jr_timestamp(0x0001);
+
+ send_program_change(0, 0, 0, true, 0, 0);
+ send_cc(0, 0, 7, 0xCCCCCCCC); // Volume 80%
+ send_cc(0, 0, 11, 0xFFFFFFFF); // Expression 100%
+ send_cc(0, 0, 64, 0x00000000); // Sustain off
+ send_cc(0, 0, 1, 0x20000000); // Modulation
+ send_cc(0, 0, 10, 0x80000000); // Pan center
+
+ // Per-Note Management is MIDI 2.0 exclusive (MT 0x4 status 0xF).
+ // Skipped when the active path falls back to MIDI 1.0 in any form.
+ if (tud_midi2_alt_setting() == 1 &&
+ tud_midi2_protocol() == MIDI_PROTOCOL_MIDI2) {
+ ump_per_note_mgmt(0, 0, 0, false, true);
+ }
+
+ send_pitch_bend(0, 0, 0x80000000); // Center
+ send_channel_pressure(0, 0, 0x00000000);
+
+ printf("[SETUP] Piano | Vol 80%% | alt=%u proto=%u\r\n",
+ (unsigned)tud_midi2_alt_setting(),
+ (unsigned)tud_midi2_protocol());
+}
+
+//--------------------------------------------------------------------+
+// Pitch Bend Conversion: cents to 32-bit value
+//--------------------------------------------------------------------+
+
+// Convert pitch bend in cents (-200 to +200) to 32-bit UMP value
+// Center = 0x80000000, range = +/- 2 semitones (200 cents)
+static inline uint32_t cents_to_pitch_bend(int16_t cents) {
+ if (cents == 0) return 0x80000000;
+ // Scale: 200 cents = full range (0x7FFFFFFF deviation from center)
+ int32_t offset = (int32_t)(((int64_t)cents * 0x7FFFFFFF) / 200);
+ return (uint32_t)((int32_t)0x80000000 + offset);
+}
+
+//--------------------------------------------------------------------+
+// Song Playback Logic - Full MIDI 2.0 Expression
+//--------------------------------------------------------------------+
+
+void update_song_playback(uint32_t now_ms) {
+ const midi2_note_t *current = &song_data[song.current_note_idx];
+
+ if (!song.setup_sent) {
+ send_initial_setup();
+ song.setup_sent = true;
+ song.note_start_ms = now_ms;
+ }
+
+ // Note duration elapsed: send Note Off, advance
+ if (song.note_is_active && (now_ms - song.note_start_ms) >= current->duration_ms) {
+ if (song.active_pitch > 0) {
+ if (current->bend_cents != 0) send_pitch_bend(0, 0, 0x80000000);
+ if (current->pressure > 0) send_channel_pressure(0, 0, 0x00000000);
+ send_note_off(0, 0, song.active_pitch, V_P);
+ }
+
+ song.note_is_active = false;
+ song.current_note_idx++;
+
+ if (song.current_note_idx >= SONG_LENGTH) {
+ song.current_note_idx = 0;
+ song.setup_sent = false;
+ song.loop_count++;
+ printf("\r\n=== Loop %lu ===\r\n", (unsigned long)song.loop_count);
+ }
+
+ song.note_start_ms = now_ms;
+ }
+
+ // Start next note
+ if (!song.note_is_active && song.current_note_idx < SONG_LENGTH) {
+ const midi2_note_t *next = &song_data[song.current_note_idx];
+
+ if (next->duration_ms == 0) {
+ song.current_note_idx = 0;
+ song.setup_sent = false;
+ song.loop_count++;
+ printf("\r\n=== Loop %lu ===\r\n", (unsigned long)song.loop_count);
+ return;
+ }
+
+ if (next->pitch > 0) {
+ // JR Timestamp is UMP-only; skip on Alt 0 transport.
+ if (tud_midi2_alt_setting() == 1) {
+ ump_jr_timestamp((uint16_t)(now_ms & 0xFFFF));
+ }
+ if (next->bend_cents != 0) {
+ send_pitch_bend(0, 0, cents_to_pitch_bend(next->bend_cents));
+ }
+ send_note_on(0, 0, next->pitch, next->velocity);
+ if (next->pressure > 0) {
+ send_channel_pressure(0, 0, next->pressure);
+ }
+ if (next->pressure > 0x40000000 && next->duration_ms > 500) {
+ send_poly_pressure(0, 0, next->pitch, next->pressure);
+ }
+
+ song.active_pitch = next->pitch;
+ song.note_is_active = true;
+ }
+
+ // Rest: honor duration
+ if (!song.note_is_active) {
+ song.active_pitch = 0;
+ song.note_is_active = true;
+ song.note_start_ms = now_ms;
+ }
+ }
+}
+
+//--------------------------------------------------------------------+
+// Main
+//--------------------------------------------------------------------+
+
+int main(void) {
+ board_init();
+ printf("\r\n");
+ printf("===========================================\r\n");
+ printf(" TinyUSB MIDI 2.0 Device\r\n");
+ printf("===========================================\r\n");
+ printf("Tempo: 120 BPM | Song: %u notes\r\n", (unsigned)SONG_LENGTH);
+ printf("Transport + protocol fallback:\r\n");
+ printf(" Alt 0 -> USB-MIDI 1.0 32-bit Event Packets (packet_write)\r\n");
+ printf(" Alt 1 + MIDI1 -> UMP MT 0x2 MIDI 1.0 Channel Voice (ump_write)\r\n");
+ printf(" Alt 1 + MIDI2 -> UMP MT 0x4 MIDI 2.0 Channel Voice (ump_write)\r\n");
+ printf("Status: Initializing...\r\n");
+
+ tusb_rhport_init_t dev_init = {.role = TUSB_ROLE_DEVICE, .speed = TUSB_SPEED_AUTO};
+ tusb_init(BOARD_TUD_RHPORT, &dev_init);
+
+ board_init_after_tusb();
+ board_led_write(true);
+
+ uint32_t last_report_ms = 0;
+
+ while (1) {
+ tud_task();
+
+ uint32_t now_ms = tusb_time_millis_api();
+
+ if (tud_midi2_mounted()) {
+ update_song_playback(now_ms);
+ board_led_write(song.active_pitch > 0);
+ } else {
+ board_led_write((now_ms / 500) & 1);
+ }
+
+ // Status report every 10 seconds
+ if (now_ms - last_report_ms > 10000) {
+ last_report_ms = now_ms;
+ if (tud_midi2_mounted()) {
+ printf("[%lums] Playing idx %lu/%u loop %lu\r\n",
+ (unsigned long)now_ms,
+ (unsigned long)song.current_note_idx,
+ (unsigned)SONG_LENGTH,
+ (unsigned long)song.loop_count);
+ } else {
+ printf("[%lums] Waiting for host...\r\n", (unsigned long)now_ms);
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/examples/device/midi2_device/src/tusb_config.h b/examples/device/midi2_device/src/tusb_config.h
new file mode 100644
index 000000000..1ada0015f
--- /dev/null
+++ b/examples/device/midi2_device/src/tusb_config.h
@@ -0,0 +1,88 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 Saulo Verissimo
+ *
+ * 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
+
+//--------------------------------------------------------------------+
+// Board Specific Configuration
+//--------------------------------------------------------------------+
+
+#ifndef BOARD_TUD_RHPORT
+#define BOARD_TUD_RHPORT 0
+#endif
+
+#ifndef BOARD_TUD_MAX_SPEED
+#define BOARD_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED
+#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
+
+// Enable Device stack
+#define CFG_TUD_ENABLED 1
+
+#define CFG_TUD_MAX_SPEED BOARD_TUD_MAX_SPEED
+
+#ifndef CFG_TUSB_MEM_SECTION
+#define CFG_TUSB_MEM_SECTION
+#endif
+
+#ifndef CFG_TUSB_MEM_ALIGN
+#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4)))
+#endif
+
+//--------------------------------------------------------------------
+// DEVICE CONFIGURATION
+//--------------------------------------------------------------------
+
+#ifndef CFG_TUD_ENDPOINT0_SIZE
+#define CFG_TUD_ENDPOINT0_SIZE 64
+#endif
+
+//------------- CLASS -------------//
+#define CFG_TUD_MIDI2 1
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TUSB_CONFIG_H_ */
diff --git a/examples/device/midi2_device/src/usb_descriptors.c b/examples/device/midi2_device/src/usb_descriptors.c
new file mode 100644
index 000000000..19a43f2d8
--- /dev/null
+++ b/examples/device/midi2_device/src/usb_descriptors.c
@@ -0,0 +1,142 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 Saulo Verissimo
+ *
+ * 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 <string.h>
+#include "bsp/board_api.h"
+#include "tusb.h"
+#include "class/audio/audio.h"
+#include "class/midi/midi.h"
+
+//--------------------------------------------------------------------+
+// Device Descriptors
+//--------------------------------------------------------------------+
+
+static tusb_desc_device_t const desc_device = {
+ .bLength = sizeof(tusb_desc_device_t),
+ .bDescriptorType = TUSB_DESC_DEVICE,
+ .bcdUSB = 0x0200,
+ .bDeviceClass = 0x00,
+ .bDeviceSubClass = 0x00,
+ .bDeviceProtocol = 0x00,
+ .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
+
+ .idVendor = 0xcafe,
+ .idProduct = 0x4062, // MIDI 2.0 Device
+ .bcdDevice = 0x0100,
+
+ .iManufacturer = 0x01,
+ .iProduct = 0x02,
+ .iSerialNumber = 0x03,
+
+ .bNumConfigurations = 0x01
+};
+
+uint8_t const * tud_descriptor_device_cb(void) {
+ return (uint8_t const *) &desc_device;
+}
+
+//--------------------------------------------------------------------+
+// Configuration Descriptor - MIDI 2.0
+//--------------------------------------------------------------------+
+
+enum {
+ ITF_NUM_MIDI2 = 0, // Audio Control interface
+ ITF_NUM_MIDI2_STREAMING, // MIDI Streaming interface (auto-created by TUD_MIDI2_DESCRIPTOR)
+ ITF_NUM_TOTAL
+};
+
+#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MIDI2_DESC_LEN)
+
+// Endpoint addresses
+#define EPNUM_MIDI2_OUT 0x01
+#define EPNUM_MIDI2_IN 0x81
+
+static uint8_t const desc_fs_configuration[] = {
+ // 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),
+
+ // MIDI 2.0 Interface
+ TUD_MIDI2_DESCRIPTOR(ITF_NUM_MIDI2, 0, EPNUM_MIDI2_OUT, EPNUM_MIDI2_IN, 64)
+};
+
+uint8_t const * tud_descriptor_configuration_cb(uint8_t index) {
+ (void) index;
+ return desc_fs_configuration;
+}
+
+//--------------------------------------------------------------------+
+// String Descriptors
+//--------------------------------------------------------------------+
+
+enum {
+ STRID_LANGID = 0,
+ STRID_MANUFACTURER = 1,
+ STRID_PRODUCT = 2,
+ STRID_SERIAL = 3,
+};
+
+static char const *string_desc_arr[] = {
+ (const char[]) { 0x09, 0x04 }, // 0: Language
+ "TinyUSB", // 1: Manufacturer
+ "TinyUSB MIDI 2.0", // 2: Product
+ NULL, // 3: Serial
+};
+
+static uint16_t _desc_str[32 + 1];
+
+uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
+ (void) langid;
+ size_t chr_count;
+
+ 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:
+ if (!(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0]))) {
+ return NULL;
+ }
+
+ const char *str = string_desc_arr[index];
+ chr_count = strlen(str);
+ const size_t max_count = sizeof(_desc_str) / sizeof(_desc_str[0]) - 1;
+ if ( chr_count > max_count ) {
+ chr_count = max_count;
+ }
+
+ for ( size_t i = 0; i < chr_count; i++ ) {
+ _desc_str[1 + i] = str[i];
+ }
+ break;
+ }
+
+ _desc_str[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (2 * chr_count + 2));
+ return _desc_str;
+}
diff --git a/examples/device/midi_test/src/usb_descriptors.c b/examples/device/midi_test/src/usb_descriptors.c
index e969f33a3..99c798ce1 100644
--- a/examples/device/midi_test/src/usb_descriptors.c
+++ b/examples/device/midi_test/src/usb_descriptors.c
@@ -87,7 +87,7 @@ enum {
#define EPNUM_MIDI_OUT 0x02
#define EPNUM_MIDI_IN 0x81
-#elif defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY)
+#elif CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
// MCUs that don't support a same endpoint number with different direction IN and OUT defined in tusb_mcu.h
// e.g EP1 OUT & EP1 IN cannot exist together
#define EPNUM_MIDI_OUT 0x01
diff --git a/examples/device/midi_test_freertos/src/usb_descriptors.c b/examples/device/midi_test_freertos/src/usb_descriptors.c
index e969f33a3..99c798ce1 100644
--- a/examples/device/midi_test_freertos/src/usb_descriptors.c
+++ b/examples/device/midi_test_freertos/src/usb_descriptors.c
@@ -87,7 +87,7 @@ enum {
#define EPNUM_MIDI_OUT 0x02
#define EPNUM_MIDI_IN 0x81
-#elif defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY)
+#elif CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
// MCUs that don't support a same endpoint number with different direction IN and OUT defined in tusb_mcu.h
// e.g EP1 OUT & EP1 IN cannot exist together
#define EPNUM_MIDI_OUT 0x01
diff --git a/examples/device/msc_dual_lun/src/usb_descriptors.c b/examples/device/msc_dual_lun/src/usb_descriptors.c
index f73935ee0..b328cf17f 100644
--- a/examples/device/msc_dual_lun/src/usb_descriptors.c
+++ b/examples/device/msc_dual_lun/src/usb_descriptors.c
@@ -91,11 +91,17 @@ enum
#define EPNUM_MSC_OUT 0x02
#define EPNUM_MSC_IN 0x81
-#elif defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY)
+#elif CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
// MCUs that don't support a same endpoint number with different direction IN and OUT defined in tusb_mcu.h
// e.g EP1 OUT & EP1 IN cannot exist together
- #define EPNUM_MSC_OUT 0x01
- #define EPNUM_MSC_IN 0x82
+ #if TU_CHECK_MCU(OPT_MCU_MAX32650, OPT_MCU_MAX32666, OPT_MCU_MAX32690, OPT_MCU_MAX78002)
+ // Put bulk on EP>=8 so the 2048/4096-byte FIFOs can back double packet buffering
+ #define EPNUM_MSC_OUT 0x08
+ #define EPNUM_MSC_IN 0x89
+ #else
+ #define EPNUM_MSC_OUT 0x01
+ #define EPNUM_MSC_IN 0x82
+ #endif
#else
#define EPNUM_MSC_OUT 0x01
diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c
index 09697693e..b7d062c64 100644
--- a/examples/device/mtp/src/mtp_fs_example.c
+++ b/examples/device/mtp/src/mtp_fs_example.c
@@ -143,6 +143,7 @@ static int32_t fs_get_device_properties(tud_mtp_cb_data_t* cb_data);
static int32_t fs_get_object_handles(tud_mtp_cb_data_t* cb_data);
static int32_t fs_get_object_info(tud_mtp_cb_data_t* cb_data);
static int32_t fs_get_object(tud_mtp_cb_data_t* cb_data);
+static int32_t fs_get_partial_object(tud_mtp_cb_data_t* cb_data);
static int32_t fs_delete_object(tud_mtp_cb_data_t* cb_data);
static int32_t fs_send_object_info(tud_mtp_cb_data_t* cb_data);
static int32_t fs_send_object(tud_mtp_cb_data_t* cb_data);
@@ -164,6 +165,7 @@ fs_op_handler_dict_t fs_op_handler_dict[] = {
{ MTP_OP_GET_OBJECT_HANDLES, fs_get_object_handles },
{ MTP_OP_GET_OBJECT_INFO, fs_get_object_info },
{ MTP_OP_GET_OBJECT, fs_get_object },
+ { MTP_OP_GET_PARTIAL_OBJECT, fs_get_partial_object },
{ MTP_OP_DELETE_OBJECT, fs_delete_object },
{ MTP_OP_SEND_OBJECT_INFO, fs_send_object_info },
{ MTP_OP_SEND_OBJECT, fs_send_object },
@@ -330,6 +332,14 @@ int32_t tud_mtp_data_complete_cb(tud_mtp_cb_data_t* cb_data) {
break;
}
+ case MTP_OP_GET_PARTIAL_OBJECT: {
+ // response parameter: actual length of data sent excluding container header
+ const uint32_t len = cb_data->total_xferred_bytes - sizeof(mtp_container_header_t);
+ (void) mtp_container_add_uint32(resp, len);
+ resp->header->code = MTP_RESP_OK;
+ break;
+ }
+
default:
resp->header->code = (cb_data->xfer_result == XFER_RESULT_SUCCESS) ? MTP_RESP_OK : MTP_RESP_GENERAL_ERROR;
break;
@@ -535,6 +545,40 @@ static int32_t fs_get_object(tud_mtp_cb_data_t* cb_data) {
return 0;
}
+static int32_t fs_get_partial_object(tud_mtp_cb_data_t* cb_data) {
+ const mtp_container_command_t* command = cb_data->command_container;
+ mtp_container_info_t* io_container = &cb_data->io_container;
+ const uint32_t obj_handle = command->params[0];
+ const uint32_t req_offset = command->params[1];
+ const uint32_t req_max = command->params[2];
+ const fs_file_t* f = fs_get_file(obj_handle);
+ if (f == NULL) {
+ return MTP_RESP_INVALID_OBJECT_HANDLE;
+ }
+
+ const uint32_t avail = (req_offset >= f->size) ? 0u : (f->size - req_offset);
+ const uint32_t to_send = tu_min32(avail, req_max);
+
+ if (cb_data->phase == MTP_PHASE_COMMAND) {
+ // If file contents is larger than CFG_TUD_MTP_EP_BUFSIZE, data may only partially be added here
+ // the rest will be sent in tud_mtp_data_more_cb
+ (void) mtp_container_add_raw(io_container, f->data + req_offset, to_send);
+ tud_mtp_data_send(io_container);
+ } else if (cb_data->phase == MTP_PHASE_DATA) {
+ // continue sending remaining data: file contents offset is xferred byte minus header size
+ const uint32_t offset = cb_data->total_xferred_bytes - sizeof(mtp_container_header_t);
+ const uint32_t xact_len = tu_min32(to_send - offset, io_container->payload_bytes);
+ if (xact_len > 0) {
+ memcpy(io_container->payload, f->data + offset + req_offset, xact_len);
+ tud_mtp_data_send(io_container);
+ }
+ } else {
+ // nothing to do
+ }
+
+ return 0;
+}
+
static int32_t fs_send_object_info(tud_mtp_cb_data_t* cb_data) {
const mtp_container_command_t* command = cb_data->command_container;
mtp_container_info_t* io_container = &cb_data->io_container;
diff --git a/examples/device/mtp/src/tusb_config.h b/examples/device/mtp/src/tusb_config.h
index 95cc048ee..5224cd79b 100644
--- a/examples/device/mtp/src/tusb_config.h
+++ b/examples/device/mtp/src/tusb_config.h
@@ -106,6 +106,7 @@
MTP_OP_GET_OBJECT_HANDLES, \
MTP_OP_GET_OBJECT_INFO, \
MTP_OP_GET_OBJECT, \
+ MTP_OP_GET_PARTIAL_OBJECT, \
MTP_OP_DELETE_OBJECT, \
MTP_OP_SEND_OBJECT_INFO, \
MTP_OP_SEND_OBJECT, \
diff --git a/examples/device/mtp/src/usb_descriptors.c b/examples/device/mtp/src/usb_descriptors.c
index f0aa3de6b..4c840560e 100644
--- a/examples/device/mtp/src/usb_descriptors.c
+++ b/examples/device/mtp/src/usb_descriptors.c
@@ -94,7 +94,7 @@ enum
#define EPNUM_MTP_OUT 0x02
#define EPNUM_MTP_IN 0x81
-#elif defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY)
+#elif CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
// MCUs that don't support a same endpoint number with different direction IN and OUT defined in tusb_mcu.h
// e.g EP1 OUT & EP1 IN cannot exist together
#define EPNUM_MTP_EVT 0x81
diff --git a/examples/device/net_lwip_webserver/skip.txt b/examples/device/net_lwip_webserver/skip.txt
index ecb9eb7ec..5e5562087 100644
--- a/examples/device/net_lwip_webserver/skip.txt
+++ b/examples/device/net_lwip_webserver/skip.txt
@@ -19,6 +19,6 @@ board:at_start_f425
board:curiosity_nano
board:frdm_kl25z
# lpc55 has weird error 'ncm_interface' causes a section type conflict with 'ntb_parameters'
-family:lpc55
+#family:lpc55
family:nuc126
family:nuc100_120
diff --git a/examples/device/net_lwip_webserver/src/lwipopts.h b/examples/device/net_lwip_webserver/src/lwipopts.h
index 11686ce2a..350120423 100644
--- a/examples/device/net_lwip_webserver/src/lwipopts.h
+++ b/examples/device/net_lwip_webserver/src/lwipopts.h
@@ -32,6 +32,14 @@
#ifndef LWIPOPTS_H__
#define LWIPOPTS_H__
+// Pulls in tusb_option.h → tusb_config.h, which defines LWIP_HIGH_THROUGHPUT
+// based on the target MCU's SRAM tier.
+#include "tusb_option.h"
+
+#ifndef LWIP_HIGH_THROUGHPUT
+ #define LWIP_HIGH_THROUGHPUT 0
+#endif
+
/* Prevent having to link sys_arch.c (we don't test the API layers in unit tests) */
#define NO_SYS 1
#define MEM_ALIGNMENT 4
@@ -49,7 +57,15 @@
#define TCP_MSS (1500 /*mtu*/ - 20 /*iphdr*/ - 20 /*tcphhr*/)
#define TCP_SND_BUF (4 * TCP_MSS)
-#define TCP_WND (4 * TCP_MSS)
+#if LWIP_HIGH_THROUGHPUT
+ #define TCP_WND (8 * TCP_MSS)
+ #define PBUF_POOL_SIZE 8
+ // Must grow in step with TCP_SND_BUF (default MEMP_NUM_TCP_SEG=16 caps TCP_SND_BUF at 4*MSS).
+ #define MEMP_NUM_TCP_SEG 16
+#else
+ #define TCP_WND (4 * TCP_MSS)
+ #define PBUF_POOL_SIZE 4
+#endif
#define ETHARP_SUPPORT_STATIC_ENTRIES 1
@@ -60,8 +76,6 @@
#define LWIP_SINGLE_NETIF 1
#define LWIP_NETIF_LINK_CALLBACK 1
-#define PBUF_POOL_SIZE 4
-
#define HTTPD_USE_CUSTOM_FSDATA 0
#define LWIP_MULTICAST_PING 1
diff --git a/examples/device/net_lwip_webserver/src/tusb_config.h b/examples/device/net_lwip_webserver/src/tusb_config.h
index 3285ea52c..c594d1ebd 100644
--- a/examples/device/net_lwip_webserver/src/tusb_config.h
+++ b/examples/device/net_lwip_webserver/src/tusb_config.h
@@ -30,8 +30,6 @@
extern "C" {
#endif
-#include "lwipopts.h"
-
//--------------------------------------------------------------------+
// Board Specific Configuration
//--------------------------------------------------------------------+
@@ -92,32 +90,50 @@ extern "C" {
#define USE_ECM 1
#elif TU_CHECK_MCU(OPT_MCU_STM32F0, OPT_MCU_STM32F1)
#define USE_ECM 1
-#elif TU_CHECK_MCU(OPT_MCU_MAX32690, OPT_MCU_MAX32650, OPT_MCU_MAX32666, OPT_MCU_MAX78002)
- #define USE_ECM 1
#else
#define USE_ECM 0
- #define INCLUDE_IPERF
#endif
#endif
+// MCU SRAM tier — drives the bigger lwIP buffers in lwipopts.h, the larger
+// NCM OUT NTB size below, and whether iperf is built. Small-RAM MCUs
+// (stm32c0/f1/wb, lpc11/13, samd11) keep modest defaults to fit.
+#ifndef LWIP_HIGH_THROUGHPUT
+ #if TU_CHECK_MCU(OPT_MCU_MAX32650, OPT_MCU_MAX32666, OPT_MCU_MAX32690, OPT_MCU_MAX78002) || \
+ TU_CHECK_MCU(OPT_MCU_STM32F2, OPT_MCU_STM32F4, OPT_MCU_STM32F7) || \
+ TU_CHECK_MCU(OPT_MCU_STM32H5, OPT_MCU_STM32H7, OPT_MCU_STM32H7RS) || \
+ TU_CHECK_MCU(OPT_MCU_STM32U5, OPT_MCU_STM32N6) || \
+ TU_CHECK_MCU(OPT_MCU_RP2040, OPT_MCU_CH32V307) || \
+ TU_CHECK_MCU(OPT_MCU_MIMXRT1XXX) || \
+ TU_CHECK_MCU(OPT_MCU_NRF5X)
+ #define LWIP_HIGH_THROUGHPUT 1
+ #else
+ #define LWIP_HIGH_THROUGHPUT 0
+ #endif
+#endif
+
+#if LWIP_HIGH_THROUGHPUT && !defined(INCLUDE_IPERF)
+ #define INCLUDE_IPERF
+#endif
+
//--------------------------------------------------------------------
// NCM CLASS CONFIGURATION, SEE "ncm.h" FOR PERFORMANCE TUNING
//--------------------------------------------------------------------
-// Must be >> MTU
-// Can be set to 2048 without impact
-#define CFG_TUD_NCM_IN_NTB_MAX_SIZE (2 * TCP_MSS + 100)
+// CDC-NCM 1.0 Table 6-4 defines 2048 as the minimum required NTB size
+#define CFG_TUD_NCM_IN_NTB_MAX_SIZE 2048
-// Must be >> MTU
-// Can be set to smaller values if wNtbOutMaxDatagrams==1
-#define CFG_TUD_NCM_OUT_NTB_MAX_SIZE (2 * TCP_MSS + 100)
+#if LWIP_HIGH_THROUGHPUT
+ #define CFG_TUD_NCM_OUT_NTB_MAX_SIZE 4096
+#else
+ #define CFG_TUD_NCM_OUT_NTB_MAX_SIZE 2048
+#endif
// Number of NCM transfer blocks for reception side
#ifndef CFG_TUD_NCM_OUT_NTB_N
#define CFG_TUD_NCM_OUT_NTB_N 1
#endif
-// Number of NCM transfer blocks for transmission side
#ifndef CFG_TUD_NCM_IN_NTB_N
#define CFG_TUD_NCM_IN_NTB_N 1
#endif
diff --git a/examples/device/net_lwip_webserver/src/usb_descriptors.c b/examples/device/net_lwip_webserver/src/usb_descriptors.c
index c976cb62b..09090bb92 100644
--- a/examples/device/net_lwip_webserver/src/usb_descriptors.c
+++ b/examples/device/net_lwip_webserver/src/usb_descriptors.c
@@ -65,17 +65,19 @@ enum {
CONFIG_ID_COUNT
};
+#if CFG_TUD_NCM
+#define USB_BCD 0x0201
+#else
+#define USB_BCD 0x0200
+#endif
+
//--------------------------------------------------------------------+
// Device Descriptors
//--------------------------------------------------------------------+
static const tusb_desc_device_t desc_device = {
.bLength = sizeof(tusb_desc_device_t),
.bDescriptorType = TUSB_DESC_DEVICE,
-#if CFG_TUD_NCM
- .bcdUSB = 0x0201,
-#else
- .bcdUSB = 0x0200,
-#endif
+ .bcdUSB = USB_BCD,
// Use Interface Association Descriptor (IAD) device class
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
@@ -121,12 +123,20 @@ const uint8_t *tud_descriptor_device_cb(void) {
#define EPNUM_NET_OUT 0x02
#define EPNUM_NET_IN 0x81
-#elif defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY)
-// MCUs that don't support a same endpoint number with different direction IN and OUT defined in tusb_mcu.h
+#elif CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
+// MCUs that don't support the same endpoint number with different direction IN and OUT defined in tusb_mcu.h
// e.g EP1 OUT & EP1 IN cannot exist together
+
+#if TU_CHECK_MCU(OPT_MCU_MAX32650, OPT_MCU_MAX32666, OPT_MCU_MAX32690, OPT_MCU_MAX78002)
+// endpoint 8,9 has FIFO of 2048 bytes
+#define EPNUM_NET_NOTIF 0x81
+#define EPNUM_NET_OUT 0x08
+#define EPNUM_NET_IN 0x89
+#else
#define EPNUM_NET_NOTIF 0x81
#define EPNUM_NET_OUT 0x02
#define EPNUM_NET_IN 0x83
+#endif
#else
#define EPNUM_NET_NOTIF 0x81
@@ -136,57 +146,184 @@ const uint8_t *tud_descriptor_device_cb(void) {
#if CFG_TUD_ECM_RNDIS
-static uint8_t const rndis_configuration[] = {
+// full speed configuration
+static uint8_t const rndis_fs_configuration[] = {
// Config number (index+1), interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(CONFIG_ID_RNDIS + 1, ITF_NUM_TOTAL, 0, MAIN_CONFIG_TOTAL_LEN, 0, 100),
// Interface number, string index, EP notification address and size, EP data address (out, in) and size.
TUD_RNDIS_DESCRIPTOR(
- ITF_NUM_CDC, STRID_INTERFACE, EPNUM_NET_NOTIF, 8, EPNUM_NET_OUT, EPNUM_NET_IN, CFG_TUD_NET_ENDPOINT_SIZE),
+ ITF_NUM_CDC, STRID_INTERFACE, EPNUM_NET_NOTIF, 8, EPNUM_NET_OUT, EPNUM_NET_IN, 64),
};
-static const uint8_t ecm_configuration[] = {
+static const uint8_t ecm_fs_configuration[] = {
// Config number (index+1), interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(CONFIG_ID_ECM + 1, ITF_NUM_TOTAL, 0, ALT_CONFIG_TOTAL_LEN, 0, 100),
// Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size.
TUD_CDC_ECM_DESCRIPTOR(
ITF_NUM_CDC, STRID_INTERFACE, STRID_MAC, EPNUM_NET_NOTIF, 64, EPNUM_NET_OUT, EPNUM_NET_IN,
- CFG_TUD_NET_ENDPOINT_SIZE, CFG_TUD_NET_MTU),
+ 64, CFG_TUD_NET_MTU),
};
-#else
+#if TUD_OPT_HIGH_SPEED
+// Per USB specs: high speed capable device must report device_qualifier and other_speed_configuration
-static uint8_t const ncm_configuration[] = {
+// high speed configuration
+static uint8_t const rndis_hs_configuration[] = {
// Config number (index+1), interface count, string index, total length, attribute, power in mA
- TUD_CONFIG_DESCRIPTOR(CONFIG_ID_NCM + 1, ITF_NUM_TOTAL, 0, NCM_CONFIG_TOTAL_LEN, 0, 100),
+ TUD_CONFIG_DESCRIPTOR(CONFIG_ID_RNDIS + 1, ITF_NUM_TOTAL, 0, MAIN_CONFIG_TOTAL_LEN, 0, 100),
+
+ // Interface number, string index, EP notification address and size, EP data address (out, in) and size.
+ TUD_RNDIS_DESCRIPTOR(
+ ITF_NUM_CDC, STRID_INTERFACE, EPNUM_NET_NOTIF, 8, EPNUM_NET_OUT, EPNUM_NET_IN, 512),
+};
+
+static const uint8_t ecm_hs_configuration[] = {
+ // Config number (index+1), interface count, string index, total length, attribute, power in mA
+ TUD_CONFIG_DESCRIPTOR(CONFIG_ID_ECM + 1, ITF_NUM_TOTAL, 0, ALT_CONFIG_TOTAL_LEN, 0, 100),
// Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size.
- TUD_CDC_NCM_DESCRIPTOR(
+ TUD_CDC_ECM_DESCRIPTOR(
ITF_NUM_CDC, STRID_INTERFACE, STRID_MAC, EPNUM_NET_NOTIF, 64, EPNUM_NET_OUT, EPNUM_NET_IN,
- CFG_TUD_NET_ENDPOINT_SIZE, CFG_TUD_NET_MTU),
+ 512, CFG_TUD_NET_MTU),
+};
+#endif // highspeed
+
+#else
+
+// full speed configuration
+static uint8_t const ncm_fs_configuration[] = {
+ // Config number (index+1), interface count, string index, total length, attribute, power in mA
+ TUD_CONFIG_DESCRIPTOR(CONFIG_ID_NCM + 1, ITF_NUM_TOTAL, 0, NCM_CONFIG_TOTAL_LEN, 0, 100),
+
+ // Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size, EP notification bInterval, NCM capabilities.
+ TUD_CDC_NCM_DESCRIPTOR(ITF_NUM_CDC, STRID_INTERFACE, STRID_MAC, EPNUM_NET_NOTIF, 64, EPNUM_NET_OUT, EPNUM_NET_IN,
+ 64, CFG_TUD_NET_MTU, 50, (uint8_t)((uint8_t)NCM_NETWORK_CAPS_ETH_FILTER | (uint8_t)NCM_NETWORK_CAPS_NTB_INPUT_SIZE)),
+};
+
+#if TUD_OPT_HIGH_SPEED
+// Per USB specs: high speed capable device must report device_qualifier and other_speed_configuration
+
+// high speed configuration
+// bInterval: FS=50 means 50ms; HS encodes as 2^(n-1) * 125us, so 9 = 2^8 * 125us = 32ms
+static uint8_t const ncm_hs_configuration[] = {
+ // Config number (index+1), interface count, string index, total length, attribute, power in mA
+ TUD_CONFIG_DESCRIPTOR(CONFIG_ID_NCM + 1, ITF_NUM_TOTAL, 0, NCM_CONFIG_TOTAL_LEN, 0, 100),
+
+ // Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size, EP notification bInterval, NCM capabilities.
+ TUD_CDC_NCM_DESCRIPTOR(ITF_NUM_CDC, STRID_INTERFACE, STRID_MAC, EPNUM_NET_NOTIF, 64, EPNUM_NET_OUT, EPNUM_NET_IN,
+ 512, CFG_TUD_NET_MTU, 9, (uint8_t)((uint8_t)NCM_NETWORK_CAPS_ETH_FILTER | (uint8_t)NCM_NETWORK_CAPS_NTB_INPUT_SIZE)),
};
+#endif // highspeed
#endif
-// Configuration array: RNDIS and CDC-ECM
+// NCM work with all latest OS i.e macos 10.10+, windows 10+, and Linux.
+// For older system Configuration array of RNDIS and CDC-ECM may be needed for better compatibility.
// - Windows only works with RNDIS
// - MacOS only works with CDC-ECM
// - Linux will work on both
-static const uint8_t *const configuration_arr[CONFIG_ID_COUNT] = {
#if CFG_TUD_ECM_RNDIS
- [CONFIG_ID_RNDIS] = rndis_configuration,
- [CONFIG_ID_ECM] = ecm_configuration
+
+static const uint8_t *const configuration_fs_arr[CONFIG_ID_COUNT] = {
+ [CONFIG_ID_RNDIS] = rndis_fs_configuration,
+ [CONFIG_ID_ECM] = ecm_fs_configuration
+};
+
+#if TUD_OPT_HIGH_SPEED
+static const uint8_t *const configuration_hs_arr[CONFIG_ID_COUNT] = {
+ [CONFIG_ID_RNDIS] = rndis_hs_configuration,
+ [CONFIG_ID_ECM] = ecm_hs_configuration
+};
+
+// Size array for each configuration
+static const uint16_t configuration_sz_arr[CONFIG_ID_COUNT] = {
+ [CONFIG_ID_RNDIS] = MAIN_CONFIG_TOTAL_LEN,
+ [CONFIG_ID_ECM] = ALT_CONFIG_TOTAL_LEN
+};
+
+// Scratch buffer for other speed configuration (sized to hold the largest config)
+#define MAX_CONFIG_TOTAL_LEN TU_MAX(MAIN_CONFIG_TOTAL_LEN, ALT_CONFIG_TOTAL_LEN)
+#endif
+
#else
- [CONFIG_ID_NCM] = ncm_configuration
+
+static const uint8_t *const configuration_fs_arr[CONFIG_ID_COUNT] = {
+ [CONFIG_ID_NCM] = ncm_fs_configuration
+};
+
+#if TUD_OPT_HIGH_SPEED
+static const uint8_t *const configuration_hs_arr[CONFIG_ID_COUNT] = {
+ [CONFIG_ID_NCM] = ncm_hs_configuration
+};
+
+// Size array for each configuration
+static const uint16_t configuration_sz_arr[CONFIG_ID_COUNT] = {
+ [CONFIG_ID_NCM] = NCM_CONFIG_TOTAL_LEN
+};
+
+// Scratch buffer for other speed configuration (sized to hold the largest config)
+#define MAX_CONFIG_TOTAL_LEN NCM_CONFIG_TOTAL_LEN
#endif
+
+#endif
+
+#if TUD_OPT_HIGH_SPEED
+static uint8_t desc_other_speed_config[MAX_CONFIG_TOTAL_LEN];
+
+// device qualifier: device descriptor fields that differ at other speed
+static tusb_desc_device_qualifier_t const desc_device_qualifier = {
+ .bLength = sizeof(tusb_desc_device_qualifier_t),
+ .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
+ .bcdUSB = USB_BCD,
+
+ .bDeviceClass = TUSB_CLASS_MISC,
+ .bDeviceSubClass = MISC_SUBCLASS_COMMON,
+ .bDeviceProtocol = MISC_PROTOCOL_IAD,
+
+ .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
+ .bNumConfigurations = CONFIG_ID_COUNT,
+ .bReserved = 0x00
};
+// Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request
+// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete.
+// device_qualifier descriptor describes information about a high-speed capable device that would
+// change if the device were operating at the other speed. If not highspeed capable stall this request.
+uint8_t const *tud_descriptor_device_qualifier_cb(void) {
+ return (uint8_t const *) &desc_device_qualifier;
+}
+
+// Invoked when received GET OTHER SPEED CONFIGURATION DESCRIPTOR request
+// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
+// Configuration descriptor in the other speed e.g if high speed then this is for full speed and vice versa
+uint8_t const *tud_descriptor_other_speed_configuration_cb(uint8_t index) {
+ if (index >= CONFIG_ID_COUNT) return NULL;
+
+ // if link speed is high return fullspeed config, and vice versa
+ const uint8_t *const *arr = (tud_speed_get() == TUSB_SPEED_HIGH) ? configuration_fs_arr : configuration_hs_arr;
+
+ // Note: the descriptor type is OTHER_SPEED_CONFIG instead of CONFIG
+ memcpy(desc_other_speed_config, arr[index], configuration_sz_arr[index]);
+ desc_other_speed_config[1] = TUSB_DESC_OTHER_SPEED_CONFIG;
+
+ return desc_other_speed_config;
+}
+
+#endif // highspeed
+
// Invoked when received GET CONFIGURATION DESCRIPTOR
// Application return pointer to descriptor
// Descriptor contents must exist long enough for transfer to complete
const uint8_t *tud_descriptor_configuration_cb(uint8_t index) {
- return (index < CONFIG_ID_COUNT) ? configuration_arr[index] : NULL;
+ if (index >= CONFIG_ID_COUNT) return NULL;
+#if TUD_OPT_HIGH_SPEED
+ // Although we are highspeed, host may be fullspeed.
+ return (tud_speed_get() == TUSB_SPEED_HIGH) ? configuration_hs_arr[index] : configuration_fs_arr[index];
+#else
+ return configuration_fs_arr[index];
+#endif
}
#if CFG_TUD_NCM
diff --git a/examples/device/printer_to_cdc/src/usb_descriptors.c b/examples/device/printer_to_cdc/src/usb_descriptors.c
index 30d309ed4..db7bfe97a 100644
--- a/examples/device/printer_to_cdc/src/usb_descriptors.c
+++ b/examples/device/printer_to_cdc/src/usb_descriptors.c
@@ -67,12 +67,21 @@ uint8_t const *tud_descriptor_device_cb(void) {
//--------------------------------------------------------------------+
// Endpoint numbers
-#if defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY)
- #define EPNUM_CDC_NOTIF 0x81
- #define EPNUM_CDC_OUT 0x02
- #define EPNUM_CDC_IN 0x83
- #define EPNUM_PRINTER_OUT 0x04
- #define EPNUM_PRINTER_IN 0x85
+#if CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
+ #if TU_CHECK_MCU(OPT_MCU_MAX32650, OPT_MCU_MAX32666, OPT_MCU_MAX32690, OPT_MCU_MAX78002)
+ // Put bulk on EP>=8 so the 2048/4096-byte FIFOs can back double packet buffering
+ #define EPNUM_CDC_NOTIF 0x81
+ #define EPNUM_CDC_OUT 0x08
+ #define EPNUM_CDC_IN 0x89
+ #define EPNUM_PRINTER_OUT 0x0A
+ #define EPNUM_PRINTER_IN 0x8B
+ #else
+ #define EPNUM_CDC_NOTIF 0x81
+ #define EPNUM_CDC_OUT 0x02
+ #define EPNUM_CDC_IN 0x83
+ #define EPNUM_PRINTER_OUT 0x04
+ #define EPNUM_PRINTER_IN 0x85
+ #endif
#else
#define EPNUM_CDC_NOTIF 0x81
#define EPNUM_CDC_OUT 0x02
diff --git a/examples/device/uac2_headset/src/usb_descriptors.c b/examples/device/uac2_headset/src/usb_descriptors.c
index e4fbbf8a5..b554e7195 100644
--- a/examples/device/uac2_headset/src/usb_descriptors.c
+++ b/examples/device/uac2_headset/src/usb_descriptors.c
@@ -97,12 +97,19 @@ uint8_t const * tud_descriptor_device_cb(void)
#define EPNUM_AUDIO_OUT 0x08
#define EPNUM_AUDIO_INT 0x01
-#elif defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY)
+#elif CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
// MCUs that don't support a same endpoint number with different direction IN and OUT defined in tusb_mcu.h
// e.g EP1 OUT & EP1 IN cannot exist together
- #define EPNUM_AUDIO_IN 0x01
- #define EPNUM_AUDIO_OUT 0x02
- #define EPNUM_AUDIO_INT 0x03
+ #if TU_CHECK_MCU(OPT_MCU_MAX32650, OPT_MCU_MAX32666, OPT_MCU_MAX32690, OPT_MCU_MAX78002)
+ // Put audio iso on EP10/11 so the 4096-byte FIFOs can back double packet buffering
+ #define EPNUM_AUDIO_OUT 0x0A
+ #define EPNUM_AUDIO_IN 0x0B
+ #define EPNUM_AUDIO_INT 0x01
+ #else
+ #define EPNUM_AUDIO_IN 0x01
+ #define EPNUM_AUDIO_OUT 0x02
+ #define EPNUM_AUDIO_INT 0x03
+ #endif
#else
#define EPNUM_AUDIO_IN 0x01
diff --git a/examples/device/uac2_speaker_fb/src/usb_descriptors.c b/examples/device/uac2_speaker_fb/src/usb_descriptors.c
index c5a161a1e..f0c780e38 100644
--- a/examples/device/uac2_speaker_fb/src/usb_descriptors.c
+++ b/examples/device/uac2_speaker_fb/src/usb_descriptors.c
@@ -115,12 +115,19 @@ uint8_t const * tud_hid_descriptor_report_cb(uint8_t itf) {
#define EPNUM_AUDIO_FB 0x08
#define EPNUM_DEBUG 0x01
-#elif defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY)
+#elif CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
// MCUs that don't support a same endpoint number with different direction IN and OUT defined in tusb_mcu.h
// e.g EP1 OUT & EP1 IN cannot exist together
- #define EPNUM_AUDIO 0x02
- #define EPNUM_AUDIO_FB 0x01
- #define EPNUM_DEBUG 0x03
+ #if TU_CHECK_MCU(OPT_MCU_MAX32650, OPT_MCU_MAX32666, OPT_MCU_MAX32690, OPT_MCU_MAX78002)
+ // Put audio iso on EP10/11 so the 4096-byte FIFOs can back double packet buffering
+ #define EPNUM_AUDIO 0x0A
+ #define EPNUM_AUDIO_FB 0x0B
+ #define EPNUM_DEBUG 0x01
+ #else
+ #define EPNUM_AUDIO 0x02
+ #define EPNUM_AUDIO_FB 0x01
+ #define EPNUM_DEBUG 0x03
+ #endif
#else
#define EPNUM_AUDIO 0x01
diff --git a/examples/device/webusb_serial/src/usb_descriptors.c b/examples/device/webusb_serial/src/usb_descriptors.c
index 0ef41a68e..527837161 100644
--- a/examples/device/webusb_serial/src/usb_descriptors.c
+++ b/examples/device/webusb_serial/src/usb_descriptors.c
@@ -104,15 +104,25 @@ enum
#define EPNUM_VENDOR_OUT 0x05
#define EPNUM_VENDOR_IN 0x84
-#elif defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY)
+#elif CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
// MCUs that don't support a same endpoint number with different direction IN and OUT defined in tusb_mcu.h
// e.g EP1 OUT & EP1 IN cannot exist together
- #define EPNUM_CDC_NOTIF 0x81
- #define EPNUM_CDC_OUT 0x02
- #define EPNUM_CDC_IN 0x83
+ #if TU_CHECK_MCU(OPT_MCU_MAX32650, OPT_MCU_MAX32666, OPT_MCU_MAX32690, OPT_MCU_MAX78002)
+ // Put bulk on EP>=8 so the 2048/4096-byte FIFOs can back double packet buffering
+ #define EPNUM_CDC_NOTIF 0x81
+ #define EPNUM_CDC_OUT 0x08
+ #define EPNUM_CDC_IN 0x89
+
+ #define EPNUM_VENDOR_OUT 0x0A
+ #define EPNUM_VENDOR_IN 0x8B
+ #else
+ #define EPNUM_CDC_NOTIF 0x81
+ #define EPNUM_CDC_OUT 0x02
+ #define EPNUM_CDC_IN 0x83
- #define EPNUM_VENDOR_OUT 0x04
- #define EPNUM_VENDOR_IN 0x85
+ #define EPNUM_VENDOR_OUT 0x04
+ #define EPNUM_VENDOR_IN 0x85
+ #endif
#else
#define EPNUM_CDC_NOTIF 0x81
diff --git a/examples/dual/dynamic_switch/src/usb_descriptors.c b/examples/dual/dynamic_switch/src/usb_descriptors.c
index 54ffc2c18..ef6d795b7 100644
--- a/examples/dual/dynamic_switch/src/usb_descriptors.c
+++ b/examples/dual/dynamic_switch/src/usb_descriptors.c
@@ -86,7 +86,7 @@ enum {
#define EPNUM_CDC_OUT 0x02
#define EPNUM_CDC_IN 0x82
-#elif defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY)
+#elif CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
// MCUs that don't support a same endpoint number with different direction IN and OUT defined in tusb_mcu.h
// e.g EP1 OUT & EP1 IN cannot exist together
#define EPNUM_CDC_NOTIF 0x81
diff --git a/examples/host/CMakeLists.txt b/examples/host/CMakeLists.txt
index f8e0ce692..7c74e3c73 100644
--- a/examples/host/CMakeLists.txt
+++ b/examples/host/CMakeLists.txt
@@ -13,7 +13,9 @@ set(EXAMPLE_LIST
device_info
hid_controller
midi_rx
+ midi2_host
msc_file_explorer
+ msc_file_explorer_freertos
)
foreach (example ${EXAMPLE_LIST})
diff --git a/examples/host/midi2_host/CMakeLists.txt b/examples/host/midi2_host/CMakeLists.txt
new file mode 100644
index 000000000..0ec03bf5f
--- /dev/null
+++ b/examples/host/midi2_host/CMakeLists.txt
@@ -0,0 +1,29 @@
+cmake_minimum_required(VERSION 3.20)
+
+include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake)
+
+project(midi2_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/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/midi2_host/CMakePresets.json b/examples/host/midi2_host/CMakePresets.json
new file mode 100644
index 000000000..5cd8971e9
--- /dev/null
+++ b/examples/host/midi2_host/CMakePresets.json
@@ -0,0 +1,6 @@
+{
+ "version": 6,
+ "include": [
+ "../../../hw/bsp/BoardPresets.json"
+ ]
+}
diff --git a/examples/host/midi2_host/Makefile b/examples/host/midi2_host/Makefile
new file mode 100644
index 000000000..f8292385e
--- /dev/null
+++ b/examples/host/midi2_host/Makefile
@@ -0,0 +1,13 @@
+include ../../../hw/bsp/family_support.mk
+
+INC += \
+ src \
+
+
+# Example source
+EXAMPLE_SOURCE += \
+ src/main.c
+
+SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE))
+
+include ../../../hw/bsp/family_rules.mk
diff --git a/examples/host/midi2_host/only.txt b/examples/host/midi2_host/only.txt
new file mode 100644
index 000000000..c71aacd87
--- /dev/null
+++ b/examples/host/midi2_host/only.txt
@@ -0,0 +1,34 @@
+family:hpmicro
+family:samd21
+family:samd5x_e5x
+mcu:CH32V20X
+mcu:ESP32P4
+mcu:ESP32S2
+mcu:ESP32S3
+mcu:KINETIS_KL
+mcu:LPC175X_6X
+mcu:LPC177X_8X
+mcu:LPC18XX
+mcu:LPC40XX
+mcu:LPC43XX
+mcu:LPC54
+mcu:LPC55
+mcu:MAX3421
+mcu:MIMXRT10XX
+mcu:MIMXRT11XX
+mcu:MIMXRT1XXX
+mcu:MSP432E4
+mcu:RAXXX
+mcu:RP2040
+mcu:RW61X
+mcu:RX65X
+mcu:STM32C0
+mcu:STM32F4
+mcu:STM32F7
+mcu:STM32G0
+mcu:STM32H5
+mcu:STM32H7
+mcu:STM32H7RS
+mcu:STM32N6
+mcu:STM32U3
+mcu:STM32U5
diff --git a/examples/host/midi2_host/skip.txt b/examples/host/midi2_host/skip.txt
new file mode 100644
index 000000000..308796869
--- /dev/null
+++ b/examples/host/midi2_host/skip.txt
@@ -0,0 +1 @@
+board:lpcxpresso54114
diff --git a/examples/host/midi2_host/src/main.c b/examples/host/midi2_host/src/main.c
new file mode 100644
index 000000000..63b08318c
--- /dev/null
+++ b/examples/host/midi2_host/src/main.c
@@ -0,0 +1,165 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 Saulo Verissimo
+ *
+ * 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.
+ */
+
+// Minimal USB-MIDI 2.0 host example.
+// Receives UMP from any MIDI 2.0 device, prints each packet to stdout.
+
+#include <stdio.h>
+#include <string.h>
+#include "bsp/board_api.h"
+#include "tusb.h"
+#include "class/midi/midi2_host.h"
+
+//--------------------------------------------------------------------+
+// State
+//--------------------------------------------------------------------+
+
+static uint8_t midi2_idx = 0xFF;
+
+//--------------------------------------------------------------------+
+// UMP printer - shows MT and word(s) in hex; decodes Channel Voice
+//--------------------------------------------------------------------+
+
+static void print_ump(const uint32_t* words, uint8_t wc) {
+ uint8_t mt = (uint8_t)((words[0] >> 28) & 0x0F);
+ uint8_t group = (uint8_t)((words[0] >> 24) & 0x0F);
+
+ if (mt == 0x4 && wc >= 2) {
+ // MIDI 2.0 Channel Voice
+ uint8_t status = (uint8_t)((words[0] >> 20) & 0x0F);
+ uint8_t channel = (uint8_t)((words[0] >> 16) & 0x0F);
+ uint8_t data1 = (uint8_t)((words[0] >> 8) & 0x7F);
+ switch (status) {
+ case 0x9:
+ printf("[g%u ch%u] M2 NoteOn n=%u vel=%04X attr=%04X\r\n",
+ group, channel, data1,
+ (unsigned)((words[1] >> 16) & 0xFFFF),
+ (unsigned)(words[1] & 0xFFFF));
+ break;
+ case 0x8:
+ printf("[g%u ch%u] M2 NoteOff n=%u vel=%04X\r\n",
+ group, channel, data1,
+ (unsigned)((words[1] >> 16) & 0xFFFF));
+ break;
+ case 0xB:
+ printf("[g%u ch%u] M2 CC#%u = %08lX\r\n",
+ group, channel, data1, (unsigned long)words[1]);
+ break;
+ case 0xC:
+ printf("[g%u ch%u] M2 ProgChg %u\r\n",
+ group, channel, (unsigned)((words[1] >> 24) & 0x7F));
+ break;
+ case 0xD:
+ printf("[g%u ch%u] M2 ChanPress %08lX\r\n",
+ group, channel, (unsigned long)words[1]);
+ break;
+ case 0xE:
+ printf("[g%u ch%u] M2 PitchBend %08lX\r\n",
+ group, channel, (unsigned long)words[1]);
+ break;
+ default:
+ printf("[g%u ch%u] M2 status=0x%X w0=%08lX w1=%08lX\r\n",
+ group, channel, status,
+ (unsigned long)words[0], (unsigned long)words[1]);
+ break;
+ }
+ } else if (mt == 0x2 && wc == 1) {
+ // MIDI 1.0 Channel Voice
+ uint8_t status = (uint8_t)((words[0] >> 16) & 0xFF);
+ uint8_t data1 = (uint8_t)((words[0] >> 8) & 0x7F);
+ uint8_t data2 = (uint8_t)(words[0] & 0x7F);
+ printf("[g%u] M1 %02X %02X %02X\r\n", group, status, data1, data2);
+ } else {
+ printf("UMP MT=0x%X wc=%u w0=%08lX\r\n",
+ mt, wc, (unsigned long)words[0]);
+ }
+}
+
+//--------------------------------------------------------------------+
+// MIDI 2.0 Host Callbacks
+//--------------------------------------------------------------------+
+
+void tuh_midi2_descriptor_cb(uint8_t idx, const tuh_midi2_descriptor_cb_t* d) {
+ (void)idx;
+ printf("MIDI2 descriptor: %s, RX cables=%u TX cables=%u\r\n",
+ d->protocol_version ? "MIDI 2.0" : "MIDI 1.0",
+ d->rx_cable_count, d->tx_cable_count);
+}
+
+void tuh_midi2_mount_cb(uint8_t idx, const tuh_midi2_mount_cb_t* m) {
+ midi2_idx = idx;
+ printf("MIDI2 mounted: idx=%u protocol=%s\r\n",
+ idx, m->protocol_version ? "MIDI 2.0" : "MIDI 1.0");
+}
+
+void tuh_midi2_rx_cb(uint8_t idx, uint32_t xferred_bytes) {
+ (void)xferred_bytes;
+
+ uint32_t words[16];
+ while (1) {
+ uint32_t n = tuh_midi2_ump_read(idx, words, 16);
+ if (n == 0) break;
+
+ uint32_t i = 0;
+ while (i < n) {
+ uint8_t mt = (uint8_t)((words[i] >> 28) & 0x0F);
+ uint8_t wc = midi2_ump_word_count(mt);
+ if (i + wc > n) break;
+ print_ump(&words[i], wc);
+ i += wc;
+ }
+ }
+}
+
+void tuh_midi2_tx_cb(uint8_t idx, uint32_t xferred_bytes) {
+ (void)idx; (void)xferred_bytes;
+}
+
+void tuh_midi2_umount_cb(uint8_t idx) {
+ (void)idx;
+ midi2_idx = 0xFF;
+ printf("MIDI2 unmounted\r\n");
+}
+
+//--------------------------------------------------------------------+
+// Main
+//--------------------------------------------------------------------+
+
+int main(void) {
+ board_init();
+
+ printf("\r\nTinyUSB Host MIDI 2.0 Example\r\n");
+
+ tusb_rhport_init_t host_init = {
+ .role = TUSB_ROLE_HOST,
+ .speed = TUSB_SPEED_AUTO,
+ };
+ tusb_init(BOARD_TUH_RHPORT, &host_init);
+
+ while (1) {
+ tuh_task();
+ }
+
+ return 0;
+}
diff --git a/examples/host/midi2_host/src/tusb_config.h b/examples/host/midi2_host/src/tusb_config.h
new file mode 100644
index 000000000..4bd6ef2ea
--- /dev/null
+++ b/examples/host/midi2_host/src/tusb_config.h
@@ -0,0 +1,106 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 Saulo Verissimo
+ *
+ * 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
+
+#ifdef ESP_PLATFORM
+#define CFG_TUSB_OS_INC_PATH freertos/
+#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
+ // #define CFG_TUH_RPI_PIO_USB 1 // use pio-usb as host controller
+ // #define CFG_TUH_MAX3421 1 // use max3421 as host controller
+
+ // host roothub port is 1 if using either pio-usb or max3421
+ #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
+
+//------------------------- Board Specific --------------------------+
+
+#ifndef BOARD_TUH_RHPORT
+#define BOARD_TUH_RHPORT 0
+#endif
+
+#ifndef BOARD_TUH_MAX_SPEED
+#define BOARD_TUH_MAX_SPEED OPT_MODE_DEFAULT_SPEED
+#endif
+
+//--------------------------------------------------------------------+
+// Driver Configuration
+//--------------------------------------------------------------------+
+
+#define CFG_TUH_ENUMERATION_BUFSIZE 256
+
+#define CFG_TUH_HUB 1
+#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
+
+// USB-MIDI 2.0 Host
+#define CFG_TUH_MIDI2 CFG_TUH_DEVICE_MAX
+#define CFG_TUH_MIDI2_RX_BUFSIZE 512
+#define CFG_TUH_MIDI2_TX_BUFSIZE 512
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/examples/host/msc_file_explorer_freertos/CMakeLists.txt b/examples/host/msc_file_explorer_freertos/CMakeLists.txt
new file mode 100644
index 000000000..4893dd1fb
--- /dev/null
+++ b/examples/host/msc_file_explorer_freertos/CMakeLists.txt
@@ -0,0 +1,42 @@
+cmake_minimum_required(VERSION 3.20)
+
+include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake)
+
+project(msc_file_explorer_freertos 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/main.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/msc_app.c
+ ${TOP}/lib/fatfs/source/ff.c
+ ${TOP}/lib/fatfs/source/ffsystem.c
+ ${TOP}/lib/fatfs/source/ffunicode.c
+ )
+
+# Example include
+target_include_directories(${PROJECT_NAME} PUBLIC
+ ${CMAKE_CURRENT_SOURCE_DIR}/src
+ ${TOP}/lib/fatfs/source
+ ${TOP}/lib/embedded-cli
+ )
+
+# Configure compilation flags and libraries for the example with FreeRTOS.
+# See the corresponding function in hw/bsp/FAMILY/family.cmake for details.
+family_configure_host_example(${PROJECT_NAME} freertos)
+
+# Suppress warnings on fatfs
+if (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang")
+ set_source_files_properties(${TOP}/lib/fatfs/source/ff.c PROPERTIES
+ COMPILE_OPTIONS "-Wno-conversion;-Wno-cast-qual"
+ )
+endif ()
diff --git a/examples/host/msc_file_explorer_freertos/CMakePresets.json b/examples/host/msc_file_explorer_freertos/CMakePresets.json
new file mode 100644
index 000000000..5cd8971e9
--- /dev/null
+++ b/examples/host/msc_file_explorer_freertos/CMakePresets.json
@@ -0,0 +1,6 @@
+{
+ "version": 6,
+ "include": [
+ "../../../hw/bsp/BoardPresets.json"
+ ]
+}
diff --git a/examples/host/msc_file_explorer_freertos/Makefile b/examples/host/msc_file_explorer_freertos/Makefile
new file mode 100644
index 000000000..15c7420d4
--- /dev/null
+++ b/examples/host/msc_file_explorer_freertos/Makefile
@@ -0,0 +1,27 @@
+RTOS = freertos
+include ../../../hw/bsp/family_support.mk
+
+FATFS_PATH = lib/fatfs/source
+
+INC += \
+ src \
+ $(TOP)/$(FATFS_PATH) \
+ $(TOP)/lib/embedded-cli \
+
+# Example source
+EXAMPLE_SOURCE = \
+ src/main.c \
+ src/msc_app.c \
+
+SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE))
+
+# FatFS source
+SRC_C += \
+ $(FATFS_PATH)/ff.c \
+ $(FATFS_PATH)/ffsystem.c \
+ $(FATFS_PATH)/ffunicode.c \
+
+# suppress warning caused by fatfs
+CFLAGS += -Wno-error=cast-qual
+
+include ../../../hw/bsp/family_rules.mk
diff --git a/examples/host/msc_file_explorer_freertos/README.md b/examples/host/msc_file_explorer_freertos/README.md
new file mode 100644
index 000000000..4ee6b96fa
--- /dev/null
+++ b/examples/host/msc_file_explorer_freertos/README.md
@@ -0,0 +1,105 @@
+# MSC File Explorer (FreeRTOS)
+
+This host example implements an interactive command-line file browser for USB Mass Storage devices.
+When a USB flash drive is connected, the device is automatically mounted using FatFS and a shell-like
+CLI is presented over the board's serial console.
+
+## Features
+
+- Automatic mount/unmount of USB storage devices
+- FAT12/16/32 filesystem support via FatFS
+- Interactive CLI with command history
+- Read speed benchmarking with `dd`
+- Support for up to 4 simultaneous USB storage devices (via hub)
+
+## Supported Commands
+
+| Command | Usage | Description |
+|---------|--------------------|------------------------------------------------------|
+| help | `help` | Print list of available commands |
+| cat | `cat <file>` | Print file contents to the console |
+| cd | `cd <dir>` | Change current working directory |
+| cp | `cp <src> <dest>` | Copy a file |
+| dd | `dd [count]` | Read sectors and report speed (default 1024 sectors) |
+| ls | `ls [dir]` | List directory contents |
+| pwd | `pwd` | Print current working directory |
+| mkdir | `mkdir <dir>` | Create a directory |
+| mv | `mv <src> <dest>` | Rename/move a file or directory |
+| rm | `rm <file>` | Remove a file |
+
+## Build
+
+Build for a specific board using CMake (see [Getting Started](https://docs.tinyusb.org/en/latest/getting_started.html)):
+
+```bash
+# Example: build for STM32F407 Discovery board
+cmake -B build -DBOARD=stm32f407disco -GNinja examples/host/msc_file_explorer_freertos
+cmake --build build
+```
+
+## Usage
+
+1. Flash the firmware to your board.
+2. Open a serial terminal (e.g. `minicom`, `screen`, `PuTTY`) at 115200 baud.
+3. Plug a USB flash drive into the board's USB host port.
+4. The device is auto-mounted and the prompt appears:
+
+```
+TinyUSB MSC File Explorer Example
+
+Device connected
+ Vendor : Kingston
+ Product : DataTraveler 2.0
+ Rev : 1.0
+ Capacity: 1.9 GB
+
+0:/> _
+```
+
+### Browsing Files
+
+```
+0:/> ls
+----a 1234 readme.txt
+d---- 0 photos
+d---- 0 docs
+
+0:/> cd photos
+0:/photos> ls
+----a 520432 vacation.jpg
+----a 312088 family.png
+
+0:/> cat readme.txt
+Hello from USB drive!
+```
+
+### Copying and Moving Files
+
+```
+0:/> cp readme.txt backup.txt
+0:/> mv backup.txt docs/backup.txt
+```
+
+### Measuring Read Speed
+
+```
+0:/> dd
+Reading 1024 sectors...
+ Data speed: 823 KB/s
+```
+
+### Multiple Devices
+
+When using a USB hub, multiple drives are mounted as `0:`, `1:`, etc. Use the drive prefix to
+navigate between them:
+
+```
+0:/> cd 1:
+1:/> ls
+```
+
+## Testing
+
+Build-time validation follows the standard TinyUSB host example flow. Runtime behavior should be
+verified on hardware by attaching an MSC device and exercising CLI commands such as `ls`, `pwd`,
+and `dd`.
diff --git a/examples/host/msc_file_explorer_freertos/only.txt b/examples/host/msc_file_explorer_freertos/only.txt
new file mode 100644
index 000000000..519ac2ebd
--- /dev/null
+++ b/examples/host/msc_file_explorer_freertos/only.txt
@@ -0,0 +1,28 @@
+family:espressif
+family:samd21
+family:samd5x_e5x
+mcu:LPC175X_6X
+mcu:LPC177X_8X
+mcu:LPC18XX
+mcu:LPC40XX
+mcu:LPC43XX
+mcu:LPC54
+mcu:LPC55
+mcu:MAX3421
+mcu:MIMXRT10XX
+mcu:MIMXRT11XX
+mcu:MIMXRT1XXX
+mcu:MSP432E4
+mcu:RP2040
+mcu:RW61X
+mcu:RX65X
+mcu:STM32C0
+mcu:STM32F4
+mcu:STM32F7
+mcu:STM32G0
+mcu:STM32H5
+mcu:STM32H7
+mcu:STM32H7RS
+mcu:STM32N6
+mcu:STM32U3
+mcu:STM32U5
diff --git a/examples/host/msc_file_explorer_freertos/skip.txt b/examples/host/msc_file_explorer_freertos/skip.txt
new file mode 100644
index 000000000..f0be07d25
--- /dev/null
+++ b/examples/host/msc_file_explorer_freertos/skip.txt
@@ -0,0 +1,3 @@
+mcu:CH32F20X
+board:lpcxpresso54114
+mcu:FT90X
diff --git a/examples/host/msc_file_explorer_freertos/src/CMakeLists.txt b/examples/host/msc_file_explorer_freertos/src/CMakeLists.txt
new file mode 100644
index 000000000..c3fb35607
--- /dev/null
+++ b/examples/host/msc_file_explorer_freertos/src/CMakeLists.txt
@@ -0,0 +1,13 @@
+# This file is for ESP-IDF only
+set(FATFS_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../../lib/fatfs/source)
+set(EMBEDDED_CLI_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../../lib/embedded-cli)
+
+idf_component_register(
+ SRCS "main.c" "msc_app.c"
+ ${FATFS_DIR}/ff.c
+ ${FATFS_DIR}/ffsystem.c
+ ${FATFS_DIR}/ffunicode.c
+ INCLUDE_DIRS "." ${FATFS_DIR} ${EMBEDDED_CLI_DIR}
+ REQUIRES boards tinyusb_src)
+
+target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-error=format)
diff --git a/examples/host/msc_file_explorer_freertos/src/ffconf.h b/examples/host/msc_file_explorer_freertos/src/ffconf.h
new file mode 100644
index 000000000..5c89136fe
--- /dev/null
+++ b/examples/host/msc_file_explorer_freertos/src/ffconf.h
@@ -0,0 +1,313 @@
+/*---------------------------------------------------------------------------/
+/ Configurations of FatFs Module
+/---------------------------------------------------------------------------*/
+
+#define FFCONF_DEF 80386 /* Revision ID */
+
+/*---------------------------------------------------------------------------/
+/ Function Configurations
+/---------------------------------------------------------------------------*/
+
+#define FF_FS_READONLY 0
+/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
+/ Read-only configuration removes writing API functions, f_write(), f_sync(),
+/ f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
+/ and optional writing functions as well. */
+
+
+#define FF_FS_MINIMIZE 0
+/* This option defines minimization level to remove some basic API functions.
+/
+/ 0: Basic functions are fully enabled.
+/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename()
+/ are removed.
+/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
+/ 3: f_lseek() function is removed in addition to 2. */
+
+
+#define FF_USE_FIND 0
+/* This option switches filtered directory read functions, f_findfirst() and
+/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
+
+
+#define FF_USE_MKFS 0
+/* This option switches f_mkfs(). (0:Disable or 1:Enable) */
+
+
+#define FF_USE_FASTSEEK 0
+/* This option switches fast seek feature. (0:Disable or 1:Enable) */
+
+
+#define FF_USE_EXPAND 0
+/* This option switches f_expand(). (0:Disable or 1:Enable) */
+
+
+#define FF_USE_CHMOD 0
+/* This option switches attribute control API functions, f_chmod() and f_utime().
+/ (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */
+
+
+#define FF_USE_LABEL 0
+/* This option switches volume label API functions, f_getlabel() and f_setlabel().
+/ (0:Disable or 1:Enable) */
+
+
+#define FF_USE_FORWARD 0
+/* This option switches f_forward(). (0:Disable or 1:Enable) */
+
+
+#define FF_USE_STRFUNC 0
+#define FF_PRINT_LLI 0
+#define FF_PRINT_FLOAT 0
+#define FF_STRF_ENCODE 0
+/* FF_USE_STRFUNC switches string API functions, f_gets(), f_putc(), f_puts() and
+/ f_printf().
+/
+/ 0: Disable. FF_PRINT_LLI, FF_PRINT_FLOAT and FF_STRF_ENCODE have no effect.
+/ 1: Enable without LF-CRLF conversion.
+/ 2: Enable with LF-CRLF conversion.
+/
+/ FF_PRINT_LLI = 1 makes f_printf() support long long argument and FF_PRINT_FLOAT = 1/2
+/ makes f_printf() support floating point argument. These features want C99 or later.
+/ When FF_LFN_UNICODE >= 1 with LFN enabled, string API functions convert the character
+/ encoding in it. FF_STRF_ENCODE selects assumption of character encoding ON THE FILE
+/ to be read/written via those functions.
+/
+/ 0: ANSI/OEM in current CP
+/ 1: Unicode in UTF-16LE
+/ 2: Unicode in UTF-16BE
+/ 3: Unicode in UTF-8
+*/
+
+
+/*---------------------------------------------------------------------------/
+/ Locale and Namespace Configurations
+/---------------------------------------------------------------------------*/
+
+#define FF_CODE_PAGE 437
+/* This option specifies the OEM code page to be used on the target system.
+/ Incorrect code page setting can cause a file open failure.
+/
+/ 437 - U.S.
+/ 720 - Arabic
+/ 737 - Greek
+/ 771 - KBL
+/ 775 - Baltic
+/ 850 - Latin 1
+/ 852 - Latin 2
+/ 855 - Cyrillic
+/ 857 - Turkish
+/ 860 - Portuguese
+/ 861 - Icelandic
+/ 862 - Hebrew
+/ 863 - Canadian French
+/ 864 - Arabic
+/ 865 - Nordic
+/ 866 - Russian
+/ 869 - Greek 2
+/ 932 - Japanese (DBCS)
+/ 936 - Simplified Chinese (DBCS)
+/ 949 - Korean (DBCS)
+/ 950 - Traditional Chinese (DBCS)
+/ 0 - Include all code pages above and configured by f_setcp()
+*/
+
+
+#define FF_USE_LFN 1
+#define FF_MAX_LFN 255
+/* The FF_USE_LFN switches the support for LFN (long file name).
+/
+/ 0: Disable LFN. FF_MAX_LFN has no effect.
+/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
+/ 2: Enable LFN with dynamic working buffer on the STACK.
+/ 3: Enable LFN with dynamic working buffer on the HEAP.
+/
+/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN feature
+/ requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and
+/ additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled.
+/ The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can
+/ be in range of 12 to 255. It is recommended to be set 255 to fully support the LFN
+/ specification.
+/ When use stack for the working buffer, take care on stack overflow. When use heap
+/ memory for the working buffer, memory management functions, ff_memalloc() and
+/ ff_memfree() exemplified in ffsystem.c, need to be added to the project. */
+
+
+#define FF_LFN_UNICODE 0
+/* This option switches the character encoding on the API when LFN is enabled.
+/
+/ 0: ANSI/OEM in current CP (TCHAR = char)
+/ 1: Unicode in UTF-16 (TCHAR = WCHAR)
+/ 2: Unicode in UTF-8 (TCHAR = char)
+/ 3: Unicode in UTF-32 (TCHAR = DWORD)
+/
+/ Also behavior of string I/O functions will be affected by this option.
+/ When LFN is not enabled, this option has no effect. */
+
+
+#define FF_LFN_BUF 255
+#define FF_SFN_BUF 12
+/* This set of options defines size of file name members in the FILINFO structure
+/ which is used to read out directory items. These values should be sufficient for
+/ the file names to read. The maximum possible length of the read file name depends
+/ on character encoding. When LFN is not enabled, these options have no effect. */
+
+
+#define FF_FS_RPATH 2
+/* This option configures support for relative path feature.
+/
+/ 0: Disable relative path and remove related API functions.
+/ 1: Enable relative path and dot names. f_chdir() and f_chdrive() are available.
+/ 2: f_getcwd() is available in addition to 1.
+*/
+
+
+#define FF_PATH_DEPTH 10
+/* This option defines maximum depth of directory in the exFAT volume. It is NOT
+/ relevant to FAT/FAT32 volume.
+/ For example, FF_PATH_DEPTH = 3 will able to follow a path "/dir1/dir2/dir3/file"
+/ but a sub-directory in the dir3 will not able to be followed and set current
+/ directory.
+/ The size of filesystem object (FATFS) increases FF_PATH_DEPTH * 24 bytes.
+/ When FF_FS_EXFAT == 0 or FF_FS_RPATH == 0, this option has no effect.
+*/
+
+
+
+/*---------------------------------------------------------------------------/
+/ Drive/Volume Configurations
+/---------------------------------------------------------------------------*/
+
+#define FF_VOLUMES 4
+/* Number of volumes (logical drives) to be used. (1-10) */
+
+
+#define FF_STR_VOLUME_ID 0
+#define FF_VOLUME_STRS "RAM","NAND","CF","SD","SD2","USB","USB2","USB3"
+/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings.
+/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive
+/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each
+/ logical drive. Number of items must not be less than FF_VOLUMES. Valid
+/ characters for the volume ID strings are A-Z, a-z and 0-9, however, they are
+/ compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is
+/ not defined, a user defined volume string table is needed as:
+/
+/ const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",...
+*/
+
+
+#define FF_MULTI_PARTITION 0
+/* This option switches support for multiple volumes on the physical drive.
+/ By default (0), each logical drive number is bound to the same physical drive
+/ number and only an FAT volume found on the physical drive will be mounted.
+/ When this feature is enabled (1), each logical drive number can be bound to
+/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
+/ will be available. */
+
+
+#define FF_MIN_SS 512
+#define FF_MAX_SS 512
+/* This set of options configures the range of sector size to be supported. (512,
+/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and
+/ harddisk, but a larger value may be required for on-board flash memory and some
+/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is
+/ configured for variable sector size mode and disk_ioctl() needs to implement
+/ GET_SECTOR_SIZE command. */
+
+
+#define FF_LBA64 0
+/* This option switches support for 64-bit LBA. (0:Disable or 1:Enable)
+/ To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */
+
+
+#define FF_MIN_GPT 0x10000000
+/* Minimum number of sectors to switch GPT as partitioning format in f_mkfs() and
+/ f_fdisk(). 2^32 sectors maximum. This option has no effect when FF_LBA64 == 0. */
+
+
+#define FF_USE_TRIM 0
+/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable)
+/ To enable this feature, also CTRL_TRIM command should be implemented to
+/ the disk_ioctl(). */
+
+
+
+/*---------------------------------------------------------------------------/
+/ System Configurations
+/---------------------------------------------------------------------------*/
+
+#define FF_FS_TINY 0
+/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
+/ At the tiny configuration, size of file object (FIL) is reduced FF_MAX_SS bytes.
+/ Instead of private sector buffer eliminated from the file object, common sector
+/ buffer in the filesystem object (FATFS) is used for the file data transfer. */
+
+
+#define FF_FS_EXFAT 0
+/* This option switches support for exFAT filesystem. (0:Disable or 1:Enable)
+/ To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1)
+/ Note that enabling exFAT discards ANSI C (C89) compatibility. */
+
+
+#define FF_FS_NORTC 1
+#define FF_NORTC_MON 1
+#define FF_NORTC_MDAY 1
+#define FF_NORTC_YEAR 2025
+/* The option FF_FS_NORTC switches timestamp feature. If the system does not have
+/ an RTC or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable the
+/ timestamp feature. Every object modified by FatFs will have a fixed timestamp
+/ defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time.
+/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() need to be added
+/ to the project to read current time form real-time clock. FF_NORTC_MON,
+/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
+/ These options have no effect in read-only configuration (FF_FS_READONLY = 1). */
+
+
+#define FF_FS_CRTIME 0
+/* This option enables(1)/disables(0) the timestamp of the file created. When
+/ set 1, the file created time is available in FILINFO structure. */
+
+
+#define FF_FS_NOFSINFO 0
+/* If you need to know the correct free space on the FAT32 volume, set bit 0 of
+/ this option, and f_getfree() on the first time after volume mount will force
+/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
+/
+/ bit0=0: Use free cluster count in the FSINFO if available.
+/ bit0=1: Do not trust free cluster count in the FSINFO.
+/ bit1=0: Use last allocated cluster number in the FSINFO if available.
+/ bit1=1: Do not trust last allocated cluster number in the FSINFO.
+*/
+
+
+#define FF_FS_LOCK 0
+/* The option FF_FS_LOCK switches file lock function to control duplicated file open
+/ and illegal operation to open objects. This option must be 0 when FF_FS_READONLY
+/ is 1.
+/
+/ 0: Disable file lock function. To avoid volume corruption, application program
+/ should avoid illegal open, remove and rename to the open objects.
+/ >0: Enable file lock function. The value defines how many files/sub-directories
+/ can be opened simultaneously under file lock control. Note that the file
+/ lock control is independent of re-entrancy. */
+
+
+#define FF_FS_REENTRANT 0
+#define FF_FS_TIMEOUT 1000
+/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
+/ module itself. Note that regardless of this option, file access to different
+/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
+/ and f_fdisk(), are always not re-entrant. Only file/directory access to
+/ the same volume is under control of this featuer.
+/
+/ 0: Disable re-entrancy. FF_FS_TIMEOUT have no effect.
+/ 1: Enable re-entrancy. Also user provided synchronization handlers,
+/ ff_mutex_create(), ff_mutex_delete(), ff_mutex_take() and ff_mutex_give(),
+/ must be added to the project. Samples are available in ffsystem.c.
+/
+/ The FF_FS_TIMEOUT defines timeout period in unit of O/S time tick.
+*/
+
+
+
+/*--- End of configuration options ---*/
diff --git a/examples/host/msc_file_explorer_freertos/src/main.c b/examples/host/msc_file_explorer_freertos/src/main.c
new file mode 100644
index 000000000..d1e627f4f
--- /dev/null
+++ b/examples/host/msc_file_explorer_freertos/src/main.c
@@ -0,0 +1,158 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include <string.h>
+
+#include "bsp/board_api.h"
+#include "tusb.h"
+#ifdef ESP_PLATFORM
+ // ESP-IDF need "freertos/" prefix in include path.
+ // CFG_TUSB_OS_INC_PATH should be defined accordingly.
+ #include "freertos/FreeRTOS.h"
+ #include "freertos/task.h"
+ #include "freertos/timers.h"
+#else
+ #include "FreeRTOS.h"
+ #include "task.h"
+ #include "timers.h"
+#endif
+
+#include "msc_app.h"
+
+//--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF PROTYPES
+//--------------------------------------------------------------------+
+#ifdef ESP_PLATFORM
+ #define USBH_STACK_SIZE 4096
+#else
+ // Increase stack size when debug log is enabled.
+ #define USBH_STACK_SIZE (configMINIMAL_STACK_SIZE * (CFG_TUSB_DEBUG ? 4 : 3))
+#endif
+
+enum {
+ BLINK_MOUNTED = 1000,
+};
+
+#if configSUPPORT_STATIC_ALLOCATION
+StaticTimer_t blinky_tmdef;
+
+StackType_t usb_host_stack[USBH_STACK_SIZE];
+StaticTask_t usb_host_taskdef;
+#endif
+
+TimerHandle_t blinky_tm;
+
+static void led_blinky_cb(TimerHandle_t xTimer);
+static void usb_host_task(void* param);
+
+/*------------- MAIN -------------*/
+int main(void) {
+ board_init();
+
+ printf("TinyUSB Host MassStorage Explorer FreeRTOS Example\r\n");
+
+ // Create soft timer for blinky and task for TinyUSB host stack.
+#if configSUPPORT_STATIC_ALLOCATION
+ blinky_tm = xTimerCreateStatic(NULL, pdMS_TO_TICKS(BLINK_MOUNTED), true, NULL, led_blinky_cb, &blinky_tmdef);
+ xTaskCreateStatic(usb_host_task, "usbh", USBH_STACK_SIZE, NULL, configMAX_PRIORITIES - 1, usb_host_stack,
+ &usb_host_taskdef);
+#else
+ blinky_tm = xTimerCreate(NULL, pdMS_TO_TICKS(BLINK_MOUNTED), true, NULL, led_blinky_cb);
+ xTaskCreate(usb_host_task, "usbh", USBH_STACK_SIZE, NULL, configMAX_PRIORITIES - 1, NULL);
+#endif
+
+ xTimerStart(blinky_tm, 0);
+
+ // only start scheduler for non-espressif mcu
+#ifndef ESP_PLATFORM
+ vTaskStartScheduler();
+#endif
+
+ return 0;
+}
+
+#ifdef ESP_PLATFORM
+void app_main(void) {
+ main();
+}
+#endif
+
+// USB Host task
+// This top-level thread processes all USB events and invokes callbacks.
+static void usb_host_task(void* param) {
+ (void) param;
+
+ // init host stack on configured roothub port
+ tusb_rhport_init_t host_init = {
+ .role = TUSB_ROLE_HOST,
+ .speed = TUSB_SPEED_AUTO
+ };
+
+ if (!tusb_init(BOARD_TUH_RHPORT, &host_init)) {
+ printf("Failed to init USB Host Stack\r\n");
+ vTaskSuspend(NULL);
+ }
+
+ board_init_after_tusb();
+
+#if CFG_TUH_ENABLED && CFG_TUH_MAX3421
+ // FeatherWing MAX3421E uses MAX3421E GPIO0 for VBUS enable.
+ enum { IOPINS1_ADDR = 20u << 3 };
+ tuh_max3421_reg_write(BOARD_TUH_RHPORT, IOPINS1_ADDR, 0x01, false);
+#endif
+
+ if (!msc_app_init()) {
+ printf("Failed to init MSC app\r\n");
+ vTaskSuspend(NULL);
+ }
+
+ while (1) {
+ // TinyUSB host task.
+ tuh_task();
+ }
+}
+
+//--------------------------------------------------------------------+
+// TinyUSB Callbacks
+//--------------------------------------------------------------------+
+
+void tuh_mount_cb(uint8_t dev_addr) {
+ (void) dev_addr;
+}
+
+void tuh_umount_cb(uint8_t dev_addr) {
+ (void) dev_addr;
+}
+
+//--------------------------------------------------------------------+
+// Blinking Task
+//--------------------------------------------------------------------+
+static void led_blinky_cb(TimerHandle_t xTimer) {
+ (void) xTimer;
+ static bool led_state = false;
+
+ board_led_write(led_state);
+ led_state = 1 - led_state; // toggle
+}
diff --git a/examples/host/msc_file_explorer_freertos/src/msc_app.c b/examples/host/msc_file_explorer_freertos/src/msc_app.c
new file mode 100644
index 000000000..c7e00e52a
--- /dev/null
+++ b/examples/host/msc_file_explorer_freertos/src/msc_app.c
@@ -0,0 +1,709 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include <ctype.h>
+#include "tusb.h"
+#include "bsp/board_api.h"
+#ifdef ESP_PLATFORM
+ // ESP-IDF need "freertos/" prefix in include path.
+ // CFG_TUSB_OS_INC_PATH should be defined accordingly.
+ #include "freertos/FreeRTOS.h"
+ #include "freertos/task.h"
+ #include "freertos/timers.h"
+#else
+ #include "FreeRTOS.h"
+ #include "task.h"
+ #include "timers.h"
+#endif
+
+#include "ff.h"
+#include "diskio.h"
+
+// lib/embedded-cli
+#define EMBEDDED_CLI_IMPL
+#include "embedded_cli.h"
+
+#include "msc_app.h"
+
+
+//--------------------------------------------------------------------+
+// MACRO TYPEDEF CONSTANT ENUM DECLARATION
+//--------------------------------------------------------------------+
+
+//------------- embedded-cli -------------//
+#define CLI_BUFFER_SIZE 512
+#define CLI_RX_BUFFER_SIZE 16
+#define CLI_CMD_BUFFER_SIZE 64
+#define CLI_HISTORY_SIZE 32
+#define CLI_BINDING_COUNT 9
+
+#ifdef ESP_PLATFORM
+ #define MSC_APP_STACK_SIZE 4096
+#else
+ #define MSC_APP_STACK_SIZE (configMINIMAL_STACK_SIZE * (CFG_TUSB_DEBUG ? 3 : 2))
+#endif
+
+static EmbeddedCli *_cli;
+static CLI_UINT cli_buffer[BYTES_TO_CLI_UINTS(CLI_BUFFER_SIZE)];
+
+#if configSUPPORT_STATIC_ALLOCATION
+StackType_t msc_app_stack[MSC_APP_STACK_SIZE];
+StaticTask_t msc_app_taskdef;
+#endif
+
+//------------- Elm Chan FatFS -------------//
+static CFG_TUH_MEM_SECTION FATFS fatfs[CFG_TUH_DEVICE_MAX]; // for simplicity only support 1 LUN per device
+static volatile bool _disk_busy[CFG_TUH_DEVICE_MAX];
+static volatile bool _mount_pending[CFG_TUH_DEVICE_MAX];
+
+static CFG_TUH_MEM_SECTION FIL file1, file2;
+
+#ifndef CFG_EXAMPLE_MSC_FILE_EXPLORER_RW_BUFSIZE
+#define CFG_EXAMPLE_MSC_FILE_EXPLORER_RW_BUFSIZE 4096
+#endif
+static CFG_TUH_MEM_SECTION uint8_t rw_buf[CFG_EXAMPLE_MSC_FILE_EXPLORER_RW_BUFSIZE];
+
+// define the buffer to be place in USB/DMA memory with correct alignment/cache line size
+CFG_TUH_MEM_SECTION static struct {
+ TUH_EPBUF_TYPE_DEF(scsi_inquiry_resp_t, inquiry);
+} scsi_resp;
+
+
+//--------------------------------------------------------------------+
+//
+//--------------------------------------------------------------------+
+
+static bool cli_init(void);
+static void msc_app_task(void* param);
+static void process_pending_mount(void);
+
+bool msc_app_init(void) {
+ for (size_t i = 0; i < CFG_TUH_DEVICE_MAX; i++) {
+ _disk_busy[i] = false;
+ _mount_pending[i] = false;
+ }
+
+// disable stdout buffered for echoing typing command
+#ifndef __ICCARM__ // TODO IAR doesn't support stream control ?
+ setbuf(stdout, NULL);
+#endif
+
+ cli_init();
+
+#if configSUPPORT_STATIC_ALLOCATION
+ TaskHandle_t task_hdl = xTaskCreateStatic(msc_app_task, "msc", MSC_APP_STACK_SIZE, NULL,
+ configMAX_PRIORITIES - 2, msc_app_stack, &msc_app_taskdef);
+ TU_ASSERT(task_hdl != NULL);
+#else
+ TU_ASSERT(xTaskCreate(msc_app_task, "msc", MSC_APP_STACK_SIZE, NULL, configMAX_PRIORITIES - 2, NULL) == pdPASS);
+#endif
+
+ return true;
+}
+
+static void msc_app_task(void* param) {
+ (void) param;
+
+ while (1) {
+ process_pending_mount();
+
+ if (!_cli) {
+ vTaskDelay(1);
+ continue;
+ }
+
+ int ch = board_getchar();
+ if (ch > 0) {
+ while (ch > 0) {
+ embeddedCliReceiveChar(_cli, (char) ch);
+ ch = board_getchar();
+ }
+ embeddedCliProcess(_cli);
+ }
+
+ vTaskDelay(1);
+ }
+}
+
+static void process_pending_mount(void) {
+ for (uint8_t drive_num = 0; drive_num < CFG_TUH_DEVICE_MAX; drive_num++) {
+ if (!_mount_pending[drive_num]) {
+ continue;
+ }
+
+ _mount_pending[drive_num] = false;
+
+ const uint8_t dev_addr = drive_num + 1;
+ if (!tuh_msc_mounted(dev_addr)) {
+ continue;
+ }
+
+ char drive_path[3] = "0:";
+ drive_path[0] += drive_num;
+
+ if (f_mount(&fatfs[drive_num], drive_path, 1) != FR_OK) {
+ printf("mount failed\r\n");
+ continue;
+ }
+
+ f_chdrive(drive_path);
+ FRESULT rc = f_chdir("/");
+ if (rc != FR_OK) {
+ printf("chdir failed: %d\r\n", rc);
+ }
+ }
+}
+
+//--------------------------------------------------------------------+
+//
+//--------------------------------------------------------------------+
+
+static bool inquiry_complete_cb(uint8_t dev_addr, const tuh_msc_complete_data_t *cb_data) {
+ const msc_cbw_t *cbw = cb_data->cbw;
+ const msc_csw_t *csw = cb_data->csw;
+
+ if (csw->status != 0) {
+ printf("Inquiry failed\r\n");
+ return false;
+ }
+
+ // Print out Vendor ID, Product ID and Rev
+ printf("%.8s %.16s %.4s\r\n", scsi_resp.inquiry.vendor_id, scsi_resp.inquiry.product_id,
+ scsi_resp.inquiry.product_rev);
+
+ // Get capacity of device
+ const uint32_t block_count = tuh_msc_get_block_count(dev_addr, cbw->lun);
+ const uint32_t block_size = tuh_msc_get_block_size(dev_addr, cbw->lun);
+
+ printf("Disk Size: %" PRIu32 " %" PRIu32 "-byte blocks: %" PRIu32 " MB\r\n",
+ block_count, block_size, block_count / ((1024 * 1024) / block_size));
+
+ // For simplicity: we only mount 1 LUN per device
+ const uint8_t drive_num = dev_addr - 1;
+ _mount_pending[drive_num] = true;
+
+ // print the drive label
+ // char label[34];
+ // if ( FR_OK == f_getlabel(drive_path, label, NULL) )
+ // {
+ // puts(label);
+ // }
+
+ return true;
+}
+
+//------------- IMPLEMENTATION -------------//
+void tuh_msc_mount_cb(uint8_t dev_addr) {
+ printf("A MassStorage device (addr = %u) is mounted\r\n", dev_addr);
+
+ const uint8_t lun = 0;
+ tuh_msc_inquiry(dev_addr, lun, &scsi_resp.inquiry, inquiry_complete_cb, 0);
+}
+
+void tuh_msc_umount_cb(uint8_t dev_addr) {
+ printf("A MassStorage device is unmounted\r\n");
+
+ const uint8_t drive_num = dev_addr - 1;
+ char drive_path[3] = "0:";
+ drive_path[0] += drive_num;
+
+ _mount_pending[drive_num] = false;
+
+ f_unmount(drive_path);
+
+ // if ( phy_disk == f_get_current_drive() )
+ // { // active drive is unplugged --> change to other drive
+ // for(uint8_t i=0; i<CFG_TUH_DEVICE_MAX; i++)
+ // {
+ // if ( disk_is_ready(i) )
+ // {
+ // f_chdrive(i);
+ // cli_init(); // refractor, rename
+ // }
+ // }
+ // }
+}
+
+//--------------------------------------------------------------------+
+// DiskIO
+//--------------------------------------------------------------------+
+
+static void wait_for_disk_io(BYTE pdrv) {
+ while (_disk_busy[pdrv]) {
+ vTaskDelay(1);
+ }
+}
+
+static bool disk_io_complete(uint8_t dev_addr, const tuh_msc_complete_data_t *cb_data) {
+ (void)dev_addr;
+ (void)cb_data;
+ _disk_busy[dev_addr - 1] = false;
+ return true;
+}
+
+DSTATUS disk_status(BYTE pdrv /* Physical drive nmuber to identify the drive */
+) {
+ uint8_t dev_addr = pdrv + 1;
+ return tuh_msc_mounted(dev_addr) ? 0 : STA_NODISK;
+}
+
+DSTATUS disk_initialize(BYTE pdrv /* Physical drive nmuber to identify the drive */
+) {
+ (void)pdrv;
+ return 0; // nothing to do
+}
+
+DRESULT disk_read(BYTE pdrv, /* Physical drive nmuber to identify the drive */
+ BYTE *buff, /* Data buffer to store read data */
+ LBA_t sector, /* Start sector in LBA */
+ UINT count /* Number of sectors to read */
+) {
+ const uint8_t dev_addr = pdrv + 1;
+ const uint8_t lun = 0;
+
+ _disk_busy[pdrv] = true;
+ tuh_msc_read10(dev_addr, lun, buff, sector, (uint16_t)count, disk_io_complete, 0);
+ wait_for_disk_io(pdrv);
+
+ return RES_OK;
+}
+
+#if FF_FS_READONLY == 0
+
+DRESULT disk_write(BYTE pdrv, /* Physical drive nmuber to identify the drive */
+ const BYTE *buff, /* Data to be written */
+ LBA_t sector, /* Start sector in LBA */
+ UINT count /* Number of sectors to write */
+) {
+ const uint8_t dev_addr = pdrv + 1;
+ const uint8_t lun = 0;
+
+ _disk_busy[pdrv] = true;
+ tuh_msc_write10(dev_addr, lun, buff, sector, (uint16_t)count, disk_io_complete, 0);
+ wait_for_disk_io(pdrv);
+
+ return RES_OK;
+}
+
+#endif
+
+DRESULT disk_ioctl(BYTE pdrv, /* Physical drive nmuber (0..) */
+ BYTE cmd, /* Control code */
+ void *buff /* Buffer to send/receive control data */
+) {
+ const uint8_t dev_addr = pdrv + 1;
+ const uint8_t lun = 0;
+ switch (cmd) {
+ case CTRL_SYNC:
+ // nothing to do since we do blocking
+ return RES_OK;
+
+ case GET_SECTOR_COUNT:
+ *((DWORD *)buff) = (DWORD)tuh_msc_get_block_count(dev_addr, lun);
+ return RES_OK;
+
+ case GET_SECTOR_SIZE:
+ *((WORD *)buff) = (WORD)tuh_msc_get_block_size(dev_addr, lun);
+ return RES_OK;
+
+ case GET_BLOCK_SIZE:
+ *((DWORD *)buff) = 1; // erase block size in units of sector size
+ return RES_OK;
+
+ default:
+ return RES_PARERR;
+ }
+}
+
+//--------------------------------------------------------------------+
+// CLI Commands
+//--------------------------------------------------------------------+
+
+void cli_cmd_cat(EmbeddedCli *cli, char *args, void *context);
+void cli_cmd_cd(EmbeddedCli *cli, char *args, void *context);
+void cli_cmd_cp(EmbeddedCli *cli, char *args, void *context);
+void cli_cmd_dd(EmbeddedCli *cli, char *args, void *context);
+void cli_cmd_ls(EmbeddedCli *cli, char *args, void *context);
+void cli_cmd_pwd(EmbeddedCli *cli, char *args, void *context);
+void cli_cmd_mkdir(EmbeddedCli *cli, char *args, void *context);
+void cli_cmd_mv(EmbeddedCli *cli, char *args, void *context);
+void cli_cmd_rm(EmbeddedCli *cli, char *args, void *context);
+
+static void cli_write_char(EmbeddedCli *cli, char c) {
+ (void)cli;
+ putchar((int)c);
+}
+
+bool cli_init(void) {
+ EmbeddedCliConfig *config = embeddedCliDefaultConfig();
+ config->cliBuffer = cli_buffer;
+ config->cliBufferSize = CLI_BUFFER_SIZE;
+ config->rxBufferSize = CLI_RX_BUFFER_SIZE;
+ config->cmdBufferSize = CLI_CMD_BUFFER_SIZE;
+ config->historyBufferSize = CLI_HISTORY_SIZE;
+ config->maxBindingCount = CLI_BINDING_COUNT;
+
+ TU_ASSERT(embeddedCliRequiredSize(config) <= CLI_BUFFER_SIZE);
+
+ _cli = embeddedCliNew(config);
+ TU_ASSERT(_cli != NULL);
+
+ _cli->writeChar = cli_write_char;
+
+ embeddedCliAddBinding(_cli,
+ (CliCommandBinding){"cat", "Usage: cat [FILE]...\r\n\tConcatenate FILE(s) to standard output..",
+ true, NULL, cli_cmd_cat});
+
+ embeddedCliAddBinding(_cli, (CliCommandBinding){"cd", "Usage: cd [DIR]...\r\n\tChange the current directory to DIR.",
+ true, NULL, cli_cmd_cd});
+
+ embeddedCliAddBinding(_cli, (CliCommandBinding){"cp", "Usage: cp SOURCE DEST\r\n\tCopy SOURCE to DEST.", true, NULL,
+ cli_cmd_cp});
+
+ embeddedCliAddBinding(_cli, (CliCommandBinding){"dd", "Usage: dd [COUNT]\r\n\t" "Read COUNT sectors (default 1024) and report speed.", true, NULL,
+ cli_cmd_dd});
+
+ embeddedCliAddBinding(_cli, (CliCommandBinding){"ls",
+ "Usage: ls [DIR]...\r\n\tList information about the FILEs (the "
+ "current directory by default).",
+ true, NULL, cli_cmd_ls});
+
+ embeddedCliAddBinding(_cli,
+ (CliCommandBinding){"pwd", "Usage: pwd\r\n\tPrint the name of the current working directory.",
+ true, NULL, cli_cmd_pwd});
+
+ embeddedCliAddBinding(_cli, (CliCommandBinding){"mkdir",
+ "Usage: mkdir DIR...\r\n\tCreate the DIRECTORY(ies), if they do not "
+ "already exist..",
+ true, NULL, cli_cmd_mkdir});
+
+ embeddedCliAddBinding(_cli, (CliCommandBinding){"mv", "Usage: mv SOURCE DEST...\r\n\tRename SOURCE to DEST.", true,
+ NULL, cli_cmd_mv});
+
+ embeddedCliAddBinding(_cli, (CliCommandBinding){"rm", "Usage: rm [FILE]...\r\n\tRemove (unlink) the FILE(s).", true,
+ NULL, cli_cmd_rm});
+
+ return true;
+}
+
+void cli_cmd_dd(EmbeddedCli *cli, char *args, void *context) {
+ (void)cli;
+ (void)context;
+
+ uint32_t count = 1024; // default sectors to read
+ if (embeddedCliGetTokenCount(args) >= 1) {
+ count = (uint32_t)atoi(embeddedCliGetToken(args, 1));
+ if (count == 0) {
+ count = 1024;
+ }
+ }
+
+ // find first mounted MSC device
+ uint8_t dev_addr = 0;
+ for (uint8_t i = 1; i <= CFG_TUH_DEVICE_MAX; i++) {
+ if (tuh_msc_mounted(i)) {
+ dev_addr = i;
+ break;
+ }
+ }
+ if (dev_addr == 0) {
+ printf("no MSC device mounted\r\n");
+ return;
+ }
+
+ const uint8_t lun = 0;
+ const uint32_t block_size = tuh_msc_get_block_size(dev_addr, lun);
+ const uint32_t block_count = tuh_msc_get_block_count(dev_addr, lun);
+ if (count > block_count) {
+ count = block_count;
+ }
+
+ const uint16_t sectors_per_xfer = (uint16_t)(sizeof(rw_buf) / block_size);
+ const uint32_t xfer_count = (count + sectors_per_xfer - 1) / sectors_per_xfer;
+
+ printf("dd: reading %" PRIu32 " sectors (%" PRIu32 " bytes), %u sectors/xfer ...\r\n",
+ count, count * block_size, sectors_per_xfer);
+
+ const uint32_t start_ms = tusb_time_millis_api();
+ const uint8_t pdrv = dev_addr - 1;
+ bool submit_failed = false;
+
+ for (uint32_t i = 0; i < count; i += sectors_per_xfer) {
+ const uint16_t n = (uint16_t)((count - i < sectors_per_xfer) ? (count - i) : sectors_per_xfer);
+ _disk_busy[pdrv] = true;
+
+ if (!tuh_msc_read10(dev_addr, lun, rw_buf, i, n, disk_io_complete, 0)) {
+ _disk_busy[pdrv] = false;
+ printf("dd: failed to submit read at sector %" PRIu32 " (%u sectors)\r\n", i, n);
+ submit_failed = true;
+ break;
+ }
+
+ wait_for_disk_io(pdrv);
+ }
+
+ if (submit_failed) {
+ return;
+ }
+
+ const uint32_t elapsed_ms = tusb_time_millis_api() - start_ms;
+ const uint32_t total_data = count * block_size;
+ // each SCSI transaction has 31-byte CBW + data + 13-byte CSW
+ const uint32_t total_bus = total_data + xfer_count * (31 + 13);
+
+ if (elapsed_ms > 0) {
+ const uint32_t data_kbs = total_data / elapsed_ms; // KB/s (bytes/ms = KB/s)
+ const uint32_t bus_kbs = total_bus / elapsed_ms;
+ printf("dd: %" PRIu32 " bytes in %" PRIu32 " ms = %" PRIu32 " KB/s (bus %" PRIu32 " KB/s)\r\n",
+ total_data, elapsed_ms, data_kbs, bus_kbs);
+ } else {
+ printf("dd: %" PRIu32 " bytes in <1 ms\r\n", total_data);
+ }
+}
+
+void cli_cmd_cat(EmbeddedCli *cli, char *args, void *context) {
+ (void)cli;
+ (void)context;
+
+ uint16_t argc = embeddedCliGetTokenCount(args);
+
+ // need at least 1 argument
+ if (argc == 0) {
+ printf("invalid arguments\r\n");
+ return;
+ }
+
+ for (uint16_t i = 0; i < argc; i++) {
+ FIL *fi = &file1;
+ const char *fpath = embeddedCliGetToken(args, i + 1); // token count from 1
+
+ if (FR_OK != f_open(fi, fpath, FA_READ)) {
+ printf("%s: No such file or directory\r\n", fpath);
+ } else {
+ UINT count = 0;
+ while ((FR_OK == f_read(fi, rw_buf, sizeof(rw_buf), &count)) && (count > 0)) {
+ for (UINT c = 0; c < count; c++) {
+ const uint8_t ch = rw_buf[c];
+ if (isprint(ch) || iscntrl(ch)) {
+ putchar(ch);
+ } else {
+ putchar('.');
+ }
+ }
+ }
+ }
+
+ f_close(fi);
+ }
+}
+
+void cli_cmd_cd(EmbeddedCli *cli, char *args, void *context) {
+ (void)cli;
+ (void)context;
+
+ uint16_t argc = embeddedCliGetTokenCount(args);
+
+ // only support 1 argument
+ if (argc != 1) {
+ printf("invalid arguments\r\n");
+ return;
+ }
+
+ // default is current directory
+ const char *dpath = args;
+
+ if (FR_OK != f_chdir(dpath)) {
+ printf("%s: No such file or directory\r\n", dpath);
+ return;
+ }
+}
+
+void cli_cmd_cp(EmbeddedCli *cli, char *args, void *context) {
+ (void)cli;
+ (void)context;
+
+ uint16_t argc = embeddedCliGetTokenCount(args);
+ if (argc != 2) {
+ printf("invalid arguments\r\n");
+ return;
+ }
+
+ // default is current directory
+ const char *src = embeddedCliGetToken(args, 1);
+ const char *dst = embeddedCliGetToken(args, 2);
+
+ FIL *f_src = &file1;
+ FIL *f_dst = &file2;
+
+ if (FR_OK != f_open(f_src, src, FA_READ)) {
+ printf("cannot stat '%s': No such file or directory\r\n", src);
+ return;
+ }
+
+ if (FR_OK != f_open(f_dst, dst, FA_WRITE | FA_CREATE_ALWAYS)) {
+ printf("cannot create '%s'\r\n", dst);
+ f_close(f_src);
+ return;
+ } else {
+ UINT rd_count = 0;
+ while ((FR_OK == f_read(f_src, rw_buf, sizeof(rw_buf), &rd_count)) && (rd_count > 0)) {
+ UINT wr_count = 0;
+
+ if (FR_OK != f_write(f_dst, rw_buf, rd_count, &wr_count)) {
+ printf("cannot write to '%s'\r\n", dst);
+ break;
+ }
+ }
+ }
+
+ f_close(f_src);
+ f_close(f_dst);
+}
+
+void cli_cmd_ls(EmbeddedCli *cli, char *args, void *context) {
+ (void)cli;
+ (void)context;
+
+ uint16_t argc = embeddedCliGetTokenCount(args);
+
+ // only support 1 argument
+ if (argc > 1) {
+ printf("invalid arguments\r\n");
+ return;
+ }
+
+ // default is current directory
+ const char *dpath = ".";
+ if (argc) {
+ dpath = args;
+ }
+
+ DIR dir;
+ if (FR_OK != f_opendir(&dir, dpath)) {
+ printf("cannot access '%s': No such file or directory\r\n", dpath);
+ return;
+ }
+
+ FILINFO fno;
+ while ((f_readdir(&dir, &fno) == FR_OK) && (fno.fname[0] != 0)) {
+ if (fno.fname[0] != '.') // ignore . and .. entry
+ {
+ if (fno.fattrib & AM_DIR) {
+ // directory
+ printf("/%s\r\n", fno.fname);
+ } else {
+ printf("%-40s", fno.fname);
+ if (fno.fsize < 1024) {
+ printf("%" PRIu32 " B\r\n", fno.fsize);
+ } else {
+ printf("%" PRIu32 " KB\r\n", fno.fsize / 1024);
+ }
+ }
+ }
+ }
+
+ f_closedir(&dir);
+}
+
+void cli_cmd_pwd(EmbeddedCli *cli, char *args, void *context) {
+ (void)cli;
+ (void)context;
+ uint16_t argc = embeddedCliGetTokenCount(args);
+
+ if (argc != 0) {
+ printf("invalid arguments\r\n");
+ return;
+ }
+
+ char path[256];
+ if (FR_OK != f_getcwd(path, sizeof(path))) {
+ printf("cannot get current working directory\r\n");
+ return;
+ }
+
+ puts(path);
+}
+
+void cli_cmd_mkdir(EmbeddedCli *cli, char *args, void *context) {
+ (void)cli;
+ (void)context;
+
+ uint16_t argc = embeddedCliGetTokenCount(args);
+
+ // only support 1 argument
+ if (argc != 1) {
+ printf("invalid arguments\r\n");
+ return;
+ }
+
+ // default is current directory
+ const char *dpath = args;
+
+ if (FR_OK != f_mkdir(dpath)) {
+ printf("%s: cannot create this directory\r\n", dpath);
+ return;
+ }
+}
+
+void cli_cmd_mv(EmbeddedCli *cli, char *args, void *context) {
+ (void)cli;
+ (void)context;
+
+ uint16_t argc = embeddedCliGetTokenCount(args);
+ if (argc != 2) {
+ printf("invalid arguments\r\n");
+ return;
+ }
+
+ // default is current directory
+ const char *src = embeddedCliGetToken(args, 1);
+ const char *dst = embeddedCliGetToken(args, 2);
+
+ if (FR_OK != f_rename(src, dst)) {
+ printf("cannot mv %s to %s\r\n", src, dst);
+ return;
+ }
+}
+
+void cli_cmd_rm(EmbeddedCli *cli, char *args, void *context) {
+ (void)cli;
+ (void)context;
+
+ uint16_t argc = embeddedCliGetTokenCount(args);
+
+ // need at least 1 argument
+ if (argc == 0) {
+ printf("invalid arguments\r\n");
+ return;
+ }
+
+ for (uint16_t i = 0; i < argc; i++) {
+ const char *fpath = embeddedCliGetToken(args, i + 1); // token count from 1
+
+ if (FR_OK != f_unlink(fpath)) {
+ printf("cannot remove '%s': No such file or directory\r\n", fpath);
+ }
+ }
+}
diff --git a/examples/host/msc_file_explorer_freertos/src/msc_app.h b/examples/host/msc_file_explorer_freertos/src/msc_app.h
new file mode 100644
index 000000000..eff195b1a
--- /dev/null
+++ b/examples/host/msc_file_explorer_freertos/src/msc_app.h
@@ -0,0 +1,34 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2025 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+#ifndef MSC_APP_H
+#define MSC_APP_H
+
+#include <stdbool.h>
+#include <stdio.h>
+
+bool msc_app_init(void);
+
+#endif
diff --git a/examples/host/msc_file_explorer_freertos/src/tusb_config.h b/examples/host/msc_file_explorer_freertos/src/tusb_config.h
new file mode 100644
index 000000000..c3fc4624f
--- /dev/null
+++ b/examples/host/msc_file_explorer_freertos/src/tusb_config.h
@@ -0,0 +1,126 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef TUSB_CONFIG_H_
+#define TUSB_CONFIG_H_
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+//--------------------------------------------------------------------
+// Common Configuration
+//--------------------------------------------------------------------
+
+// defined by compiler flags for flexibility
+#ifndef CFG_TUSB_MCU
+#error CFG_TUSB_MCU must be defined
+#endif
+
+#ifndef CFG_TUSB_OS
+#define CFG_TUSB_OS OPT_OS_FREERTOS
+#endif
+
+// Espressif IDF requires "freertos/" prefix in include path
+#ifdef ESP_PLATFORM
+#define CFG_TUSB_OS_INC_PATH freertos/
+#endif
+
+#ifndef CFG_TUSB_DEBUG
+#define CFG_TUSB_DEBUG 0
+#endif
+
+/* 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_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
+//--------------------------------------------------------------------
+
+// Enable Host stack
+#define CFG_TUH_ENABLED 1
+
+// #define CFG_TUH_MAX3421 1 // use max3421 as host controller
+
+#if CFG_TUSB_MCU == OPT_MCU_RP2040
+ // #define CFG_TUH_RPI_PIO_USB 1 // use pio-usb as host controller
+
+ // host roothub port is 1 if using either pio-usb or max3421
+ #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
+
+// Default is max speed that hardware controller could support with on-chip PHY
+#define CFG_TUH_MAX_SPEED BOARD_TUH_MAX_SPEED
+
+//------------------------- Board Specific --------------------------
+
+// RHPort number used for host can be defined by board.mk, default to port 0
+#ifndef BOARD_TUH_RHPORT
+#define BOARD_TUH_RHPORT 0
+#endif
+
+// RHPort max operational speed can defined by board.mk
+#ifndef BOARD_TUH_MAX_SPEED
+#define BOARD_TUH_MAX_SPEED OPT_MODE_DEFAULT_SPEED
+#endif
+
+//--------------------------------------------------------------------
+// Driver Configuration
+//--------------------------------------------------------------------
+
+// Size of buffer to hold descriptors and other data used for enumeration
+#define CFG_TUH_ENUMERATION_BUFSIZE 256
+
+#define CFG_TUH_HUB 1 // number of supported hubs
+#define CFG_TUH_MSC 1
+#define CFG_TUH_CDC 0
+#define CFG_TUH_HID 0 // typical keyboard + mouse device can have 3-4 HID interfaces
+#define CFG_TUH_VENDOR 0
+
+// max device support (excluding hub device): 1 hub typically has 4 ports
+#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
+
+//------------- MSC -------------//
+#define CFG_TUH_MSC_MAXLUN 4 // typical for most card reader
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif /* TUSB_CONFIG_H_ */