From c152725ca87940733519a77112a885f2f16a4c1b Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Thu, 30 Jul 2026 01:25:45 -0700 Subject: [PATCH 01/11] port majority of USB audio upgrades from teensy-4-usbAudio --- OpenAudio_ArduinoLibrary.h | 3 +- USB_Audio_F32.h | 145 +- docs/MULTICHANNEL_USB_AUDIO.md | 133 + .../USBAudioMultiChannel_F32.ino | 30 + input_usb_f32.cpp | 127 + input_usb_f32.h | 45 + keywords.txt | 31 +- library.properties | 10 + output_usb_f32.cpp | 139 + output_usb_f32.h | 46 + patched_teensy_core/config/BM-platform.txt | 137 + patched_teensy_core/config/TD-platform.txt | 137 + patched_teensy_core/config/boards.local.txt | 54 + patched_teensy_core/usb.c | 1206 +++++++ patched_teensy_core/usb_audio.cpp | 246 ++ patched_teensy_core/usb_audio.h | 112 + patched_teensy_core/usb_audio_interface.cpp | 1081 ++++++ patched_teensy_core/usb_audio_interface.h | 202 ++ patched_teensy_core/usb_desc.c | 2989 +++++++++++++++++ patched_teensy_core/usb_desc.h | 1114 ++++++ patched_teensy_core/util/LastCall.h | 339 ++ scripts/backup_teensy_audio.ps1 | 49 + scripts/restore_teensy_audio.ps1 | 38 + scripts/setup.ps1 | 127 + scripts/update_teensy_audio.ps1 | 95 + utility/LastCall.h | 339 ++ utility/usb_audio_defines.h | 76 + 27 files changed, 8895 insertions(+), 155 deletions(-) create mode 100644 docs/MULTICHANNEL_USB_AUDIO.md create mode 100644 examples/USBAudioMultiChannel_F32/USBAudioMultiChannel_F32.ino create mode 100644 input_usb_f32.cpp create mode 100644 input_usb_f32.h create mode 100644 library.properties create mode 100644 output_usb_f32.cpp create mode 100644 output_usb_f32.h create mode 100644 patched_teensy_core/config/BM-platform.txt create mode 100644 patched_teensy_core/config/TD-platform.txt create mode 100644 patched_teensy_core/config/boards.local.txt create mode 100644 patched_teensy_core/usb.c create mode 100644 patched_teensy_core/usb_audio.cpp create mode 100644 patched_teensy_core/usb_audio.h create mode 100644 patched_teensy_core/usb_audio_interface.cpp create mode 100644 patched_teensy_core/usb_audio_interface.h create mode 100644 patched_teensy_core/usb_desc.c create mode 100644 patched_teensy_core/usb_desc.h create mode 100644 patched_teensy_core/util/LastCall.h create mode 100644 scripts/backup_teensy_audio.ps1 create mode 100644 scripts/restore_teensy_audio.ps1 create mode 100644 scripts/setup.ps1 create mode 100644 scripts/update_teensy_audio.ps1 create mode 100644 utility/LastCall.h create mode 100644 utility/usb_audio_defines.h diff --git a/OpenAudio_ArduinoLibrary.h b/OpenAudio_ArduinoLibrary.h index 6809e81..46f27be 100644 --- a/OpenAudio_ArduinoLibrary.h +++ b/OpenAudio_ArduinoLibrary.h @@ -76,4 +76,5 @@ #include "radioNoiseBlanker_F32.h" #include "synth_sin_cos_f32.h" #include "UART_F32.h" -// #include "USB_Audio_F32.h" Include this separately if needed. Then in IDE Tools>USB Type>Audio +// #include "USB_Audio_F32.h" // Uncomment for USB Audio (Tools > USB Type > Audio or MIDI+Audio+Serial). +// // Requires patched Teensy core. Run scripts/setup.ps1 first. diff --git a/USB_Audio_F32.h b/USB_Audio_F32.h index c734155..bcd175f 100644 --- a/USB_Audio_F32.h +++ b/USB_Audio_F32.h @@ -1,146 +1,7 @@ -/* -* USB_Audio_F32 -* -* Created: Chip Audette (OpenAudio), Mar 2017 -* Float32 wrapper for the Audio USB classes from the Teensy Audio Library -* -* License: MIT License. Use at your own risk. -*/ - #ifndef usb_audio_f32_h_ #define usb_audio_f32_h_ -//#include "Arduino.h" -#include -#include - -class AudioInputUSB_F32 : public AudioStream_F32 -{ -//GUI: inputs:0, outputs:2 //this line used for automatic generation of GUI node -//GUI: shortName:usbAudioIn //this line used for automatic generation of GUI node -public: - AudioInputUSB_F32() : AudioStream_F32(0, NULL) { - //i16_to_f32.disconnectFromUpdateAll(); //requires modification to AudioStream.h - //output_queue.disconnectFromUpdateAll(); //requires modification to AudioStream.h - - makeConnections(); - } - AudioInputUSB_F32(const AudioSettings_F32 &settings) : AudioStream_F32(0, NULL) { - //i16_to_f32.disconnectFromUpdateAll(); //requires modification to AudioStream.h - //output_queue.disconnectFromUpdateAll(); //requires modification to AudioStream.h - - makeConnections(); - } - - void makeConnections(void) { - //make the audio connections - patchCord100_L = new AudioConnection(usb_in, 0, i16_to_f32_L, 0); //usb_in is an Int16 audio object. So, convert it! - patchCord100_R = new AudioConnection(usb_in, 1, i16_to_f32_R, 0); //usb_in is an Int16 audio object. So, convert it! - patchCord101_L = new AudioConnection_F32(i16_to_f32_L, 0, output_queue_L, 0); - patchCord101_R = new AudioConnection_F32(i16_to_f32_R, 0, output_queue_R, 0); - } - - //define audio processing blocks. - AudioInputUSB usb_in; //from the original Teensy Audio Library, expects Int16 audio data - AudioConvert_I16toF32 i16_to_f32_L, i16_to_f32_R; - AudioRecordQueue_F32 output_queue_L,output_queue_R; - - //define the audio connections - AudioConnection *patchCord100_L, *patchCord100_R; - AudioConnection_F32 *patchCord101_L, *patchCord101_R; - - void update(void) { - //Serial.println("AudioSynthNoiseWhite_F32: update()."); - output_queue_L.begin(); - output_queue_R.begin(); - - //manually update audio blocks in the desired order - usb_in.update(); //the output should be routed directly via the AudioConnection - i16_to_f32_L.update(); // output is routed via the AudioConnection - i16_to_f32_R.update(); // output is routed via the AudioConnection - output_queue_L.update(); - output_queue_R.update(); - - //handle the output for the left channel - audio_block_f32_t *block; - block = output_queue_L.getAudioBlock(); - if (block == NULL) return; - AudioStream_F32::transmit(block,0); - output_queue_L.freeAudioBlock(); - output_queue_L.end(); - - //handle the output for the left channel - block = output_queue_R.getAudioBlock(); - if (block == NULL) return; - AudioStream_F32::transmit(block,1); - output_queue_R.freeAudioBlock(); - output_queue_R.end(); - } -private: - -}; -class AudioOutputUSB_F32 : public AudioStream_F32 -{ -//GUI: inputs:2, outputs:0 //this line used for automatic generation of GUI node -//GUI: shortName:usbAudioOut //this line used for automatic generation of GUI node -public: - AudioOutputUSB_F32() : AudioStream_F32(2, inputQueueArray_f32) { - makeConnections(); - } - - AudioOutputUSB_F32(const AudioSettings_F32 &settings) : AudioStream_F32(2, inputQueueArray_f32) { - makeConnections(); - } - - void makeConnections(void) { - //make the audio connections - patchCord100_L = new AudioConnection_F32(queue_L, 0, f32_to_i16_L, 0); //noise is an Int16 audio object. So, convert it! - patchCord100_R = new AudioConnection_F32(queue_R, 0, f32_to_i16_R, 0); //noise is an Int16 audio object. So, convert it! - patchCord101_L = new AudioConnection(f32_to_i16_L, 0, usb_out, 0); //Int16 audio connection - patchCord101_R = new AudioConnection(f32_to_i16_R, 0, usb_out, 1); //Int16 audio connection - } - - //define audio processing blocks. - AudioPlayQueue_F32 queue_L,queue_R; - AudioConvert_F32toI16 f32_to_i16_L, f32_to_i16_R; - AudioOutputUSB usb_out; //from the original Teensy Audio Library, expects Int16 audio data - - //define the audio connections - AudioConnection_F32 *patchCord100_L, *patchCord100_R; - AudioConnection *patchCord101_L, *patchCord101_R; - - void update(void) { - //Serial.println("AudioSynthNoiseWhite_F32: update()."); - //queue_L.begin(); - //queue_R.begin(); - - //is there audio waiting for us for the left channel? - audio_block_f32_t *block; - block = receiveReadOnly_f32(0); - if (!block) return; //if no audio, return now. - - //there is some audio, so execute the processing chain for the left channel - queue_L.playAudioBlock(block); - AudioStream_F32::release(block); - queue_L.update(); - f32_to_i16_L.update(); - - //see if there is a right channel - block = receiveReadOnly_f32(1); - if (block) { - //there is a right channel. process it now - queue_R.playAudioBlock(block); - AudioStream_F32::release(block); - queue_R.update(); - f32_to_i16_R.update(); - } - - //whether or not there was right-channel audio, update the usb_out - usb_out.update(); - return; - } -private: - audio_block_f32_t *inputQueueArray_f32[2]; -}; +#include "input_usb_f32.h" +#include "output_usb_f32.h" -#endif \ No newline at end of file +#endif diff --git a/docs/MULTICHANNEL_USB_AUDIO.md b/docs/MULTICHANNEL_USB_AUDIO.md new file mode 100644 index 0000000..fccc4fc --- /dev/null +++ b/docs/MULTICHANNEL_USB_AUDIO.md @@ -0,0 +1,133 @@ +# Multi-Channel USB Audio for Teensy 4.x + +This extension adds multi-channel USB Audio Class 2.0 support with asynchronous feedback to OpenAudio_ArduinoLibrary, enabling full-speed (12 Mbit) and high-speed (480 Mbit) multi-channel audio streaming between a Teensy 4.x and a host computer. + +## Features + +- **Multi-channel**: Up to 8 channels, configurable via `USB_AUDIO_CHANNELS` in Boards menu +- **Multiple sample rates**: 44.1, 48, or 96kHz operation +- **Multiple bit depths**: 16, 24, or 32 bit integer or 32 bit float formats +- **Asynchronous feedback**: USB audio output (Teensy → Host) uses an isochronous feedback endpoint so the host adapts its sample rate to the Teensy's actual clock, preventing buffer under/overruns. +- **PI controller**: A proportional-integral controller drives the feedback value, using a ring-buffer-based fill-rate estimator (`LastCall`) for stable rate tracking. +- **AudioClass 2.0**: Complies with the USB Audio 2.0 specification for wide host compatibility. +- **Float32 native**: `audio_block_f32_t` input and output from USB I/O objects, for direct compatibility with OpenAudio library. + +## Compatibility + +| OS | USB Audio 2.0 Support | +|---|---| +| Windows | Native for Windows 10 and later versions (UAC2 built-in) | +| macOS | Native (class-compliant) | +| Linux | Native (UAC2 driver) | +| ChromeOS | Native | + +## Installation + +### Prerequisites + +1. Teensyduino 1.62.0 installed via Arduino Boards Manager or the Teensyduino installer +2. OpenAudio_ArduinoLibrary in your Arduino `libraries/` folder + +### Quick Setup + +From the OpenAudio library root: + +```powershell +.\scripts\setup.ps1 +``` + +The script handles everything: + +1. **`library.properties`** — Creates it if missing (required by Arduino IDE 2.x) +2. **Detect Teensyduino** — Finds your install under `%LOCALAPPDATA%\Arduino15\` +3. **Backup** — Saves originals to `backups//` +4. **Patch core** — Installs multi-channel `usb_desc.h`, `usb_desc.c`, `usb.c`, `usb_audio.*`, `usb_audio_interface.*` +5. **Config** — Adds `boards.local.txt` (for USB Audio Channels menu) and updates `platform.txt` +6. **Arduino IDE library** — Copies the library into `Documents\Arduino\libraries\` +7. **Cache** — Clears Arduino IDE 2.x cache + +After running, **restart Arduino IDE**. Examples appear under **File → Examples → OpenAudio_ArduinoLibrary**. + +### Manual Steps (if the script doesn't work) + +1. **Copy patched core files** from `patched_teensy_core/` to your Teensy core directory: + ``` + %LOCALAPPDATA%\Arduino15\packages\teensy\hardware\avr\\cores\teensy4\ + ``` +2. **Copy config files** from `patched_teensy_core/config/`: + - `boards.local.txt` → `%LOCALAPPDATA%\Arduino15\packages\teensy\hardware\avr\\` + - `BM-platform.txt` or `TD-platform.txt` → rename to `platform.txt` in same directory +3. **Link library** into Arduino's sketchbook libraries folder: + ``` + mkdir "%USERPROFILE%\Documents\Arduino\libraries" -Force + New-Item -ItemType Junction -Path "%USERPROFILE%\Documents\Arduino\libraries\OpenAudio_ArduinoLibrary" -Target "C:\path\to\OpenAudio_ArduinoLibrary" + ``` +4. **Restart Arduino IDE** + +> **Note:** The library ships with `library.properties`, but the setup script will create it as a fallback. + +## Usage + +### Selecting USB Type + +In Arduino IDE: **Tools → USB Type → "Audio"** or **"MIDI + Audio + Serial"** + +If you installed `boards.local.txt`, under the **Tools** menu you will also see settings for **Audio block size, Audio sample rate, and USB Audio Channels**. + +### Basic Sketch + +```cpp +#include +#include +#include "USB_Audio_F32.h" + +// Use channel-count-specific classes matching Tools > USB Channels +AudioInputUSBOct_F32 usb_in; +AudioOutputUSBOct_F32 usb_out; + +// One connection per channel +AudioConnection_F32 patch0(usb_in, 0, usb_out, 0); +AudioConnection_F32 patch1(usb_in, 1, usb_out, 1); +// ... add more for channels 2-7 as needed + +void setup() { + AudioMemory(100); + AudioMemory_F32(100); +} + +void loop() { + // Audio processing runs in the background + delay(1); +} +``` + +### Asynchronous vs Adaptive + +By default, the output endpoint (Teensy → Host) runs in asynchronous mode. The Teensy sends a feedback value to the host, which adjusts its transmission rate accordingly. This eliminates the need for sample-rate conversion on the host side. + +To switch to adaptive mode (simpler but may cause occasional glitches), comment out the `ASYNC_TX_ENDPOINT` define in `usb_desc.h`. + + +## Troubleshooting + +### Compilation errors about `USB_AUDIO_CHANNELS` +- The `boards.local.txt` may not be installed; the default falls back to 8 channels in `usb_desc.h` +- Check `usb_desc.h` for `USB_AUDIO_NO_CHANNELS_480` define + +### Buffer underruns / glitches +- Increase `AudioMemory()` in your sketch +- Try adaptive mode (comment out `ASYNC_TX_ENDPOINT`) + +### Restoring Original Files +```powershell +.\scripts\restore_teensy_audio.ps1 +``` + +### Library examples not appearing in Arduino IDE + +Arduino IDE looks for libraries under **File → Preferences → Sketchbook location** → `libraries/`. On Windows, OneDrive sometimes redirects `%USERPROFILE%\Documents` to a OneDrive path, causing the IDE to scan `C:\Users\\OneDrive\Documents\Arduino\libraries\` instead of `C:\Users\\Documents\Arduino\libraries\`. + +**Fix:** In Arduino IDE, go to **File → Preferences** and check the **Sketchbook location** field. If it points to a OneDrive path, change it to `C:\Users\\Documents\Arduino`. + +Alternatively, copy or move `OpenAudio_ArduinoLibrary` into the OneDrive `libraries/` folder that the IDE is already scanning. + diff --git a/examples/USBAudioMultiChannel_F32/USBAudioMultiChannel_F32.ino b/examples/USBAudioMultiChannel_F32/USBAudioMultiChannel_F32.ino new file mode 100644 index 0000000..f587416 --- /dev/null +++ b/examples/USBAudioMultiChannel_F32/USBAudioMultiChannel_F32.ino @@ -0,0 +1,30 @@ +#include +#include +#include "USB_Audio_F32.h" + +AudioInputUSBOct_F32 usb_in; +AudioOutputUSBOct_F32 usb_out; + +AudioConnection_F32 patch0(usb_in, 0, usb_out, 0); +AudioConnection_F32 patch1(usb_in, 1, usb_out, 1); +AudioConnection_F32 patch2(usb_in, 2, usb_out, 2); +AudioConnection_F32 patch3(usb_in, 3, usb_out, 3); +AudioConnection_F32 patch4(usb_in, 4, usb_out, 4); +AudioConnection_F32 patch5(usb_in, 5, usb_out, 5); +AudioConnection_F32 patch6(usb_in, 6, usb_out, 6); +AudioConnection_F32 patch7(usb_in, 7, usb_out, 7); + +// USB->USB requires an audio output ISR to drive the scheduler. +#include "output_i2s_f32.h" +AudioOutputI2S_F32 i2s_out; +AudioConnection_F32 patch8(usb_in, 0, i2s_out, 0); +AudioConnection_F32 patch9(usb_in, 1, i2s_out, 1); + +void setup() { + AudioMemory(100); + AudioMemory_F32(100); +} + +void loop() { + +} diff --git a/input_usb_f32.cpp b/input_usb_f32.cpp new file mode 100644 index 0000000..223c431 --- /dev/null +++ b/input_usb_f32.cpp @@ -0,0 +1,127 @@ +#include "input_usb_f32.h" + +#ifdef AUDIO_INTERFACE + +// debug counters +volatile uint32_t usb_input_update_count = 0; +volatile uint32_t usb_input_tx_count = 0; + +audio_block_f32_t *AudioInputUSB_F32::rxBuffer_f32[USBAudioInInterface::ringRxBufferSize][USB_AUDIO_MAX_NO_CHANNELS]; + +AudioInputUSB_F32::AudioInputUSB_F32(float kp, float ki) + : AudioStream_F32(0, NULL), _usbInterface(setBlockQuite, releaseBlock, allocateBlock, areBlocksReady, copy_to_buffers, kp, ki) +{ + for (uint16_t i = 0; i < USBAudioInInterface::ringRxBufferSize; i++) { + for (uint16_t j = 0; j < USB_AUDIO_MAX_NO_CHANNELS; j++) { + rxBuffer_f32[i][j] = NULL; + } + } + _usbInterface.begin(); +} + +AudioInputUSB_F32::AudioInputUSB_F32(const AudioSettings_F32 &settings, float kp, float ki) + : AudioStream_F32(0, NULL), _usbInterface(setBlockQuite, releaseBlock, allocateBlock, areBlocksReady, copy_to_buffers, kp, ki) +{ + for (uint16_t i = 0; i < USBAudioInInterface::ringRxBufferSize; i++) { + for (uint16_t j = 0; j < USB_AUDIO_MAX_NO_CHANNELS; j++) { + rxBuffer_f32[i][j] = NULL; + } + } + _usbInterface.begin(); +} + +void AudioInputUSB_F32::begin(void) +{ +} + +void AudioInputUSB_F32::update(void) +{ + int16_t bIdx = -1; + uint16_t noChannels; + usb_input_update_count++; + _usbInterface.update(bIdx, noChannels); + if (bIdx != -1) { + usb_input_tx_count++; + for (uint16_t i = 0; i < noChannels; i++) { + transmit(rxBuffer_f32[bIdx][i], i); + AudioStream_F32::release(rxBuffer_f32[bIdx][i]); + rxBuffer_f32[bIdx][i] = NULL; + } + _usbInterface.incrementBufferIndex(); + } +} + +float AudioInputUSB_F32::getBufferedSamples() const +{ + return _usbInterface.getBufferedSamples(); +} + +float AudioInputUSB_F32::getBufferedSamplesSmooth() const +{ + return _usbInterface.getBufferedSamplesSmooth(); +} + +float AudioInputUSB_F32::getRequestedSamplingFrequ() const +{ + return _usbInterface.getRequestedSamplingFrequ(); +} + +float AudioInputUSB_F32::getActualBIntervalUs() const +{ + return _usbInterface.getActualBIntervalUs(); +} + +USBAudioInInterface::Status AudioInputUSB_F32::getStatus() const +{ + return _usbInterface.getStatus(); +} + +void AudioInputUSB_F32::copy_to_buffers(const uint8_t *src, uint16_t bIdx, uint16_t noChannels, unsigned int count, unsigned int len) +{ + const int16_t *src16 = (const int16_t *)src; + for (uint32_t i = 0; i < len; i++) { + for (uint16_t j = 0; j < noChannels; j++) { + rxBuffer_f32[bIdx][j]->data[count + i] = *src16++ / 32768.0f; + } + } +} + +bool AudioInputUSB_F32::setBlockQuite(uint16_t bIdx, uint16_t channel) +{ + if (!rxBuffer_f32[bIdx][channel]) { + rxBuffer_f32[bIdx][channel] = AudioStream_F32::allocate_f32(); + } + if (rxBuffer_f32[bIdx][channel]) { + memset(rxBuffer_f32[bIdx][channel]->data, 0, AUDIO_BLOCK_SAMPLES * sizeof(float)); + return true; + } + return false; +} + +void AudioInputUSB_F32::releaseBlock(uint16_t bIdx, uint16_t channel) +{ + if (rxBuffer_f32[bIdx][channel]) { + AudioStream_F32::release(rxBuffer_f32[bIdx][channel]); + rxBuffer_f32[bIdx][channel] = NULL; + } +} + +bool AudioInputUSB_F32::allocateBlock(uint16_t bIdx, uint16_t channel) +{ + if (!rxBuffer_f32[bIdx][channel]) { + rxBuffer_f32[bIdx][channel] = AudioStream_F32::allocate_f32(); + } + return rxBuffer_f32[bIdx][channel] != NULL; +} + +bool AudioInputUSB_F32::areBlocksReady(uint16_t bIdx, uint16_t noChannels) +{ + for (uint16_t i = 0; i < noChannels; i++) { + if (!rxBuffer_f32[bIdx][i]) { + return false; + } + } + return true; +} + +#endif diff --git a/input_usb_f32.h b/input_usb_f32.h new file mode 100644 index 0000000..dc50228 --- /dev/null +++ b/input_usb_f32.h @@ -0,0 +1,45 @@ +#ifndef input_usb_f32_h_ +#define input_usb_f32_h_ + +#include +#include "AudioStream_F32.h" +#include + +#ifdef AUDIO_INTERFACE + +class AudioInputUSB_F32 : public AudioStream_F32 +{ +public: + AudioInputUSB_F32(float kp = 400.f, float ki = 0.2f); + AudioInputUSB_F32(const AudioSettings_F32 &settings, float kp = 400.f, float ki = 0.2f); + virtual void update(void); + void begin(void); + float getBufferedSamples() const; + float getBufferedSamplesSmooth() const; + float getRequestedSamplingFrequ() const; + float getActualBIntervalUs() const; + USBAudioInInterface::Status getStatus() const; + +private: + static void copy_to_buffers(const uint8_t *src, uint16_t bIdx, uint16_t noChannels, unsigned int count, unsigned int len); + static bool setBlockQuite(uint16_t bIdx, uint16_t channel); + static void releaseBlock(uint16_t bIdx, uint16_t channel); + static bool allocateBlock(uint16_t bIdx, uint16_t channel); + static bool areBlocksReady(uint16_t bIdx, uint16_t noChannels); + + static audio_block_f32_t *rxBuffer_f32[USBAudioInInterface::ringRxBufferSize][USB_AUDIO_MAX_NO_CHANNELS]; + USBAudioInInterface _usbInterface; +}; + +#if USB_AUDIO_NO_CHANNELS_480 >= 4 +class AudioInputUSBQuad_F32 : public AudioInputUSB_F32 { public: AudioInputUSBQuad_F32(float kp =400.f,float ki =.2f) : AudioInputUSB_F32(kp, ki) {} }; +#if USB_AUDIO_NO_CHANNELS_480 >= 6 +class AudioInputUSBHex_F32 : public AudioInputUSB_F32 { public: AudioInputUSBHex_F32(float kp =400.f,float ki =.2f) : AudioInputUSB_F32(kp, ki) {} }; +#if USB_AUDIO_NO_CHANNELS_480 >= 8 +class AudioInputUSBOct_F32 : public AudioInputUSB_F32 { public: AudioInputUSBOct_F32(float kp =400.f,float ki =.2f) : AudioInputUSB_F32(kp, ki) {} }; +#endif +#endif +#endif + +#endif // AUDIO_INTERFACE +#endif // input_usb_f32_h_ diff --git a/keywords.txt b/keywords.txt index df5278c..01aaf94 100644 --- a/keywords.txt +++ b/keywords.txt @@ -382,18 +382,6 @@ setUART KEYWORD2 getNDataBuffer KEYWORD2 setInputOffset KEYWORD2 -AudioInputUSB_F32 KEYWORD1 -makeConnections KEYWORD2 - -AudioOutputUSB_F32 KEYWORD1 - -analyze_CTCSS_F32 KEYWORD1 -initCTCSS KEYWORD2 -readTonePower KEYWORD2 -readRefPower KEYWORD2 -readTonePresent KEYWORD2 -thresholds KEYWORD2 - AudioAnalyzeFFT256_IQ_F32 KEYWORD1 AudioAnalyzeFFT1024_F32 KEYWORD1 AudioAnalyzeFFT1024_IQ_F32 KEYWORD1 @@ -521,3 +509,22 @@ onNoteOff KEYWORD2 AudioSynthNoiseWhite_F32 KEYWORD2 setDefaultValues KEYWORD2 + +AudioInputUSB_F32 KEYWORD1 +begin KEYWORD2 +volume KEYWORD2 +getBufferedSamples KEYWORD2 +getBufferedSamplesSmooth KEYWORD2 +getRequestedSamplingFrequ KEYWORD2 +getActualBIntervalUs KEYWORD2 +getStatus KEYWORD2 + +AudioOutputUSB_F32 KEYWORD1 +getStatus KEYWORD2 + +AudioInputUSBQuad_F32 KEYWORD1 +AudioInputUSBHex_F32 KEYWORD1 +AudioInputUSBOct_F32 KEYWORD1 +AudioOutputUSBQuad_F32 KEYWORD1 +AudioOutputUSBHex_F32 KEYWORD1 +AudioOutputUSBOct_F32 KEYWORD1 diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..4e1e687 --- /dev/null +++ b/library.properties @@ -0,0 +1,10 @@ +name=OpenAudio_ArduinoLibrary +version=1.0.0 +author=Chip Audette, Bob Larkin, and contributors +maintainer=Bob Larkin +sentence=OpenAudio F32 Arduino Library - Floating-point audio processing for Teensy. +paragraph=Extends the Teensy Audio Library with float32 audio blocks, including multi-channel USB Audio Class 2.0 with asynchronous feedback. +category=Signal Input/Output +url=https://github.com/chipaudette/OpenAudio_ArduinoLibrary +architectures=teensy +includes=OpenAudio_ArduinoLibrary.h diff --git a/output_usb_f32.cpp b/output_usb_f32.cpp new file mode 100644 index 0000000..199ac17 --- /dev/null +++ b/output_usb_f32.cpp @@ -0,0 +1,139 @@ +#include "output_usb_f32.h" + +#ifdef AUDIO_INTERFACE + +// debug counters +volatile uint32_t usb_output_update_count = 0; +volatile uint32_t usb_output_fill_count = 0; + +audio_block_f32_t *AudioOutputUSB_F32::txBuffer_f32[USBAudioOutInterface::ringTxBufferSize][USB_AUDIO_MAX_NO_CHANNELS]; + +AudioOutputUSB_F32::AudioOutputUSB_F32(void) + : AudioStream_F32(USB_AUDIO_MAX_NO_CHANNELS, inputQueueArray_f32), + _usbInterface(releaseBlocks, isBlockReady, copy_from_buffers), + numChannels(USB_AUDIO_MAX_NO_CHANNELS) +{ + begin(); + _usbInterface.begin(); +} + +AudioOutputUSB_F32::AudioOutputUSB_F32(const AudioSettings_F32 &settings) + : AudioStream_F32(USB_AUDIO_MAX_NO_CHANNELS, inputQueueArray_f32), + _usbInterface(releaseBlocks, isBlockReady, copy_from_buffers), + numChannels(USB_AUDIO_MAX_NO_CHANNELS) +{ + begin(); + _usbInterface.begin(); +} + +AudioOutputUSB_F32::AudioOutputUSB_F32(int nch) + : AudioStream_F32(nch, inputQueueArray_f32), + _usbInterface(releaseBlocks, isBlockReady, copy_from_buffers), + numChannels(nch) +{ + begin(); + _usbInterface.begin(); +} + +AudioOutputUSB_F32::AudioOutputUSB_F32(const AudioSettings_F32 &settings, int nch) + : AudioStream_F32(nch, inputQueueArray_f32), + _usbInterface(releaseBlocks, isBlockReady, copy_from_buffers), + numChannels(nch) +{ + begin(); + _usbInterface.begin(); +} + +void AudioOutputUSB_F32::begin(void) +{ + for (uint16_t i = 0; i < USBAudioOutInterface::ringTxBufferSize; i++) { + for (uint16_t j = 0; j < USB_AUDIO_MAX_NO_CHANNELS; j++) { + txBuffer_f32[i][j] = NULL; + } + } +} + +void AudioOutputUSB_F32::update(void) +{ + int16_t bIdx = -1; + uint16_t noChannels; + usb_output_update_count++; + _usbInterface.update(bIdx, noChannels); + if (bIdx < 0) { + for (uint16_t i = 0; i < USB_AUDIO_MAX_NO_CHANNELS; i++) { + audio_block_f32_t *b = receiveReadOnly_f32(i); + if (b) { + AudioStream_F32::release(b); + } + } + } + usb_output_fill_count++; + for (uint16_t i = 0; i < noChannels; i++) { + if (txBuffer_f32[bIdx][i]) { + AudioStream_F32::release(txBuffer_f32[bIdx][i]); + } + txBuffer_f32[bIdx][i] = receiveReadOnly_f32(i); + if (!txBuffer_f32[bIdx][i]) { + if (!txBuffer_f32[bIdx][i]) { + txBuffer_f32[bIdx][i] = AudioStream_F32::allocate_f32(); + } + if (txBuffer_f32[bIdx][i]) { + memset(txBuffer_f32[bIdx][i]->data, 0, AUDIO_BLOCK_SAMPLES * sizeof(float)); + } else { + releaseBlocks(bIdx, noChannels); + break; + } + } + } + _usbInterface.incrementBufferIndex(); +} + +float AudioOutputUSB_F32::getBufferedSamples() const +{ + return _usbInterface.getBufferedSamples(); +} + +float AudioOutputUSB_F32::getBufferedSamplesSmooth() const +{ + return _usbInterface.getBufferedSamplesSmooth(); +} + +float AudioOutputUSB_F32::getActualBIntervalUs() const +{ + return _usbInterface.getActualBIntervalUs(); +} + +USBAudioOutInterface::Status AudioOutputUSB_F32::getStatus() const +{ + return _usbInterface.getStatus(); +} + +void AudioOutputUSB_F32::copy_from_buffers(uint8_t *dst, uint16_t bIdx, uint16_t noChannels, unsigned int count, unsigned int len) +{ + int16_t *dst16 = (int16_t *)dst; + for (uint32_t i = 0; i < len; i++) { + for (uint16_t j = 0; j < noChannels; j++) { + float sample = txBuffer_f32[bIdx][j]->data[count + i]; + if (sample > 1.0f) sample = 1.0f; + else if (sample < -1.0f) sample = -1.0f; + *dst16++ = (int16_t)(sample * 32767.0f); + } + } +} + +void AudioOutputUSB_F32::releaseBlocks(uint16_t bIdx, uint16_t noChannels) +{ + for (uint16_t i = 0; i < noChannels; i++) { + if (txBuffer_f32[bIdx][i]) { + AudioStream_F32::release(txBuffer_f32[bIdx][i]); + txBuffer_f32[bIdx][i] = NULL; + } + } +} + +bool AudioOutputUSB_F32::isBlockReady(uint16_t bIdx, uint16_t channel) +{ + return txBuffer_f32[bIdx][channel] != NULL; +} + +#endif diff --git a/output_usb_f32.h b/output_usb_f32.h new file mode 100644 index 0000000..8c797a9 --- /dev/null +++ b/output_usb_f32.h @@ -0,0 +1,46 @@ +#ifndef output_usb_f32_h_ +#define output_usb_f32_h_ + +#include +#include "AudioStream_F32.h" +#include + +#ifdef AUDIO_INTERFACE + +class AudioOutputUSB_F32 : public AudioStream_F32 +{ +public: + AudioOutputUSB_F32(void); + AudioOutputUSB_F32(const AudioSettings_F32 &settings); + AudioOutputUSB_F32(int nch); + AudioOutputUSB_F32(const AudioSettings_F32 &settings, int nch); + virtual void update(void); + void begin(void); + float getBufferedSamples() const; + float getBufferedSamplesSmooth() const; + float getActualBIntervalUs() const; + USBAudioOutInterface::Status getStatus() const; + +private: + static void copy_from_buffers(uint8_t *dst, uint16_t bIdx, uint16_t noChannels, unsigned int count, unsigned int len); + static void releaseBlocks(uint16_t bIdx, uint16_t noChannels); + static bool isBlockReady(uint16_t bIdx, uint16_t channel); + + static audio_block_f32_t *txBuffer_f32[USBAudioOutInterface::ringTxBufferSize][USB_AUDIO_MAX_NO_CHANNELS]; + audio_block_f32_t *inputQueueArray_f32[USB_AUDIO_MAX_NO_CHANNELS]; + USBAudioOutInterface _usbInterface; + int numChannels; +}; + +#if USB_AUDIO_NO_CHANNELS_480 >= 4 +class AudioOutputUSBQuad_F32 : public AudioOutputUSB_F32 { public: AudioOutputUSBQuad_F32(void) : AudioOutputUSB_F32(4) {} }; +#if USB_AUDIO_NO_CHANNELS_480 >= 6 +class AudioOutputUSBHex_F32 : public AudioOutputUSB_F32 { public: AudioOutputUSBHex_F32(void) : AudioOutputUSB_F32(6) {} }; +#if USB_AUDIO_NO_CHANNELS_480 >= 8 +class AudioOutputUSBOct_F32 : public AudioOutputUSB_F32 { public: AudioOutputUSBOct_F32(void) : AudioOutputUSB_F32(8) {} }; +#endif +#endif +#endif + +#endif // AUDIO_INTERFACE +#endif // output_usb_f32_h_ diff --git a/patched_teensy_core/config/BM-platform.txt b/patched_teensy_core/config/BM-platform.txt new file mode 100644 index 0000000..2ec4f34 --- /dev/null +++ b/patched_teensy_core/config/BM-platform.txt @@ -0,0 +1,137 @@ +# https://www.pjrc.com/teensy/td_download.html +# https://arduino.github.io/arduino-cli/latest/platform-specification/ +# https://arduino.github.io/arduino-cli/0.30/platform-specification/ + +name=Teensyduino +version=1.8.5 +rewriting=disabled + +# Teensyduino Installer +#compiler.path={runtime.hardware.path}/../tools/ +#teensytools.path={runtime.hardware.path}/../tools/ + +# Arduino Boards Manager +compiler.path={runtime.tools.teensy-compile.path}/ +teensytools.path={runtime.tools.teensy-tools.path}/ + +build.flags.audioblocksize=128 +build.flags.audiorate=44100.0f +build.flags.audiorateI=44100 +build.flags.USBchannelcount=2 + +build.flags.audio=-DAUDIO_BLOCK_SAMPLES={build.flags.audioblocksize} -DAUDIO_SAMPLE_RATE_EXACT={build.flags.audiorate} -DAUDIO_SAMPLE_RATE_I={build.flags.audiorateI} -DAUDIO_USB_CHANNEL_COUNT={build.flags.USBchannelcount} + +## Debugger - Experimental +#debug.executable={build.path}/{build.project_name}.elf +#debug.toolchain=gcc +#debug.toolchain.path={compiler.path}{build.toolchain} +#debug.toolchain.prefix=arm-none-eabi- +#debug.server=openocd +#debug.server.openocd.path={teensytools.path}tdebug +#debug.server.openocd.scripts_dir=unused +#debug.server.openocd.script=toolspath:{teensytools.path}:buildpath:{build.path}:name:{build.project_name}:board:{build.board} + +## EEPROM Data +compiler.objcopy.eep.flags=-O ihex -j .eeprom --set-section-flags=.eeprom=alloc,load --no-change-warnings --change-section-lma .eeprom=0 +compiler.elf2hex.flags=-O ihex -R .eeprom + +## Preprocessor Includes +recipe.preproc.includes="{compiler.path}{build.toolchain}{build.command.g++}" -M -MG -MP -x c++ -w {build.flags.cpp} {build.flags.cpu} {build.flags.defs} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DF_CPU={build.fcpu} -D{build.usbtype} -DLAYOUT_{build.keylayout} {includes} "{source_file}" + +## Preprocessor Macros +recipe.preproc.macros="{compiler.path}{build.toolchain}{build.command.g++}" -E -CC -x c++ -w {compiler.cpp.flags} {build.flags.common} {build.flags.cpp} {build.flags.cpu} {build.flags.defs} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DF_CPU={build.fcpu} -D{build.usbtype} -DLAYOUT_{build.keylayout} {includes} "{source_file}" -o "{preprocessed_file_path}" + +## New Preprocessor for Arduino 1.9 +tools.arduino-preprocessor.path={runtime.tools.arduino-preprocessor.path} +tools.arduino-preprocessor.cmd.path={path}/arduino-preprocessor +tools.arduino-preprocessor.pattern="{cmd.path}" "{source_file}" "{codecomplete}" -- -std=gnu++14 + +## Precompile Arduino.h header +recipe.hooks.sketch.prebuild.1.pattern="{teensytools.path}precompile_helper" "{runtime.platform.path}/cores/{build.core}" "{build.path}" "{compiler.path}{build.toolchain}{build.command.g++}" -x c++-header {build.flags.optimize} {build.flags.common} {build.flags.dep} {build.flags.cpp} {build.flags.cpu} {build.flags.defs} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DF_CPU={build.fcpu} -D{build.usbtype} -DLAYOUT_{build.keylayout} "-I{runtime.platform.path}/cores/{build.core}" "{build.path}/pch/Arduino.h" -o "{build.path}/pch/Arduino.h.gch" + +## Compile c++ files +recipe.cpp.o.pattern="{compiler.path}{build.toolchain}{build.command.g++}" -c {build.flags.audio} {build.flags.optimize} {build.flags.common} {build.flags.dep} {build.flags.cpp} {build.flags.cpu} {build.flags.defs} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DF_CPU={build.fcpu} -D{build.usbtype} -DLAYOUT_{build.keylayout} "-I{build.path}/pch" {includes} "{source_file}" -o "{object_file}" + +## Compile c files +recipe.c.o.pattern="{compiler.path}{build.toolchain}{build.command.gcc}" -c {build.flags.audio} {build.flags.optimize} {build.flags.common} {build.flags.dep} {build.flags.c} {build.flags.cpu} {build.flags.defs} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DF_CPU={build.fcpu} -D{build.usbtype} -DLAYOUT_{build.keylayout} {includes} "{source_file}" -o "{object_file}" + +## Compile S files +recipe.S.o.pattern="{compiler.path}{build.toolchain}{build.command.gcc}" -c {build.flags.audio} {build.flags.optimize} {build.flags.common} {build.flags.dep} {build.flags.S} {build.flags.cpu} {build.flags.defs} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DF_CPU={build.fcpu} -D{build.usbtype} -DLAYOUT_{build.keylayout} {includes} "{source_file}" -o "{object_file}" + +## Create archives +recipe.ar.pattern="{compiler.path}{build.toolchain}{build.command.ar}" rcs "{archive_file_path}" "{object_file}" + +## Link +recipe.c.combine.pattern="{compiler.path}{build.toolchain}{build.command.linker}" {build.flags.optimize} {build.flags.ld} {build.flags.ldspecs} {build.flags.cpu} -o "{build.path}/{build.project_name}.elf" {object_files} "{build.path}/{archive_file}" "-L{build.path}" {build.flags.libs} + +## Patch ELF - TODO: not supported by modern Arduino... :( +recipe.elfpatch.pattern="{teensytools.path}/{build.elfpatch}" -mmcu={build.mcu} "{build.path}/{build.project_name}.elf" "{sketch_path}/disk" + +## Create eeprom +recipe.objcopy.eep.pattern="{compiler.path}{build.toolchain}{build.command.objcopy}" {compiler.objcopy.eep.flags} "{build.path}/{build.project_name}.elf" "{build.path}/{build.project_name}.eep" + +## Create hex +recipe.objcopy.hex.pattern="{compiler.path}{build.toolchain}{build.command.objcopy}" {compiler.elf2hex.flags} "{build.path}/{build.project_name}.elf" "{build.path}/{build.project_name}.hex" + +## EHEX file - for Teensy 4.x secure mode +recipe.hooks.objcopy.postobjcopy.1.pattern="{teensytools.path}teensy_secure" encrypthex {build.board} "{build.path}/{build.project_name}.hex" + + +## Post Build - inform Teensy Loader of new file +recipe.hooks.postbuild.1.pattern="{teensytools.path}teensy_post_compile" "-file={build.project_name}" "-path={build.path}" "-tools={teensytools.path}" "-board={build.board}" +recipe.hooks.postbuild.2.pattern="{teensytools.path}stdout_redirect" "{build.path}/{build.project_name}.sym" "{compiler.path}{build.toolchain}{build.command.objdump}" -t -C "{build.path}/{build.project_name}.elf" +recipe.hooks.postbuild.3.pattern="{teensytools.path}teensy_size" "{build.path}/{build.project_name}.elf" +# +# objdump to create .lst file is VERY SLOW for huge files +# https://forum.pjrc.com/threads/68121?p=288306&viewfull=1#post288306 +# +recipe.hooks.postbuild.4.pattern="{teensytools.path}stdout_redirect" "{build.path}/{build.project_name}.lst" "{compiler.path}{build.toolchain}{build.command.objdump}" -d -S -C "{build.path}/{build.project_name}.elf" + + +## Compute size +recipe.size.pattern="{compiler.path}{build.toolchain}{build.command.size}" -A "{build.path}/{build.project_name}.elf" +recipe.size.regex=^(?:\.text|\.text\.progmem|\.text\.itcm|\.data|\.text\.csf)\s+([0-9]+).* +recipe.size.regex.data=^(?:\.usbdescriptortable|\.dmabuffers|\.usbbuffers|\.data|\.bss|\.noinit|\.text\.itcm|\.text\.itcm\.padding)\s+([0-9]+).* +recipe.size.regex.eeprom=^(?:\.eeprom)\s+([0-9]+).* + +## Teensy Ports Discovery +## Arduino 1.8.9 requires pathPrefs patch +## discovery patters have only limited support for substitution macros, +## so we can not use {teensytools.path} or {compiler.path} here + +# Teensyduino Installer +#discovery.teensy.pattern="{runtime.hardware.path}/../tools/teensy_ports" -J2 + +# Arduino Boards Manager +discovery.teensy.pattern="{runtime.tools.teensy-tools.path}/teensy_ports" -J2 +pluggable_discovery.required=teensy:teensy-discovery +pluggable_monitor.required.teensy=teensy:teensy-monitor + + +## Teensy Loader + +# Teensyduino Installer +#tools.teensyloader.cmd.path={runtime.hardware.path}/../tools + +# Arduino Boards Manager +tools.teensyloader.cmd.path={runtime.tools.teensy-tools.path} + +tools.teensyloader.upload.params.quiet= +tools.teensyloader.upload.params.verbose=-verbose +tools.teensyloader.upload.pattern="{cmd.path}/teensy_post_compile" "-file={build.project_name}" "-path={build.path}" "-tools={cmd.path}" "-board={build.board}" -reboot "-port={serial.port}" "-portlabel={serial.port.label}" "-portprotocol={serial.port.protocol}" + + + + +## Export hex +recipe.output.tmp_file={build.project_name}.hex +recipe.output.save_file={build.project_name}.{build.board}.hex +recipe.hooks.savehex.postsavehex.1.pattern="{teensytools.path}teensy_secure" encrypthex {build.board} "{sketch_path}/{build.project_name}.{build.board}.hex" + +# TODO: missing patch in 1.6.6... +recipe.output.tmp_file2={build.project_name}.elf +recipe.output.save_file2={build.project_name}.elf + + +# documentation on this file's format +# https://arduino.github.io/arduino-cli/latest/platform-specification/ diff --git a/patched_teensy_core/config/TD-platform.txt b/patched_teensy_core/config/TD-platform.txt new file mode 100644 index 0000000..593b7a8 --- /dev/null +++ b/patched_teensy_core/config/TD-platform.txt @@ -0,0 +1,137 @@ +# https://www.pjrc.com/teensy/td_download.html +# https://arduino.github.io/arduino-cli/latest/platform-specification/ +# https://arduino.github.io/arduino-cli/0.30/platform-specification/ + +name=Teensyduino +version=1.8.5 +rewriting=disabled + +# Teensyduino Installer +compiler.path={runtime.hardware.path}/../tools/ +teensytools.path={runtime.hardware.path}/../tools/ + +# Arduino Boards Manager +#compiler.path={runtime.tools.teensy-compile.path}/ +#teensytools.path={runtime.tools.teensy-tools.path}/ + +build.flags.audioblocksize=128 +build.flags.audiorate=44100.0f +build.flags.audiorateI=44100 +build.flags.USBchannelcount=2 + +build.flags.audio=-DAUDIO_BLOCK_SAMPLES={build.flags.audioblocksize} -DAUDIO_SAMPLE_RATE_EXACT={build.flags.audiorate} -DAUDIO_SAMPLE_RATE_I={build.flags.audiorateI} -DAUDIO_USB_CHANNEL_COUNT={build.flags.USBchannelcount} + +## Debugger - Experimental +#debug.executable={build.path}/{build.project_name}.elf +#debug.toolchain=gcc +#debug.toolchain.path={compiler.path}{build.toolchain} +#debug.toolchain.prefix=arm-none-eabi- +#debug.server=openocd +#debug.server.openocd.path={teensytools.path}tdebug +#debug.server.openocd.scripts_dir=unused +#debug.server.openocd.script=toolspath:{teensytools.path}:buildpath:{build.path}:name:{build.project_name}:board:{build.board} + +## EEPROM Data +compiler.objcopy.eep.flags=-O ihex -j .eeprom --set-section-flags=.eeprom=alloc,load --no-change-warnings --change-section-lma .eeprom=0 +compiler.elf2hex.flags=-O ihex -R .eeprom + +## Preprocessor Includes +recipe.preproc.includes="{compiler.path}{build.toolchain}{build.command.g++}" -M -MG -MP -x c++ -w {build.flags.cpp} {build.flags.cpu} {build.flags.defs} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DF_CPU={build.fcpu} -D{build.usbtype} -DLAYOUT_{build.keylayout} {includes} "{source_file}" + +## Preprocessor Macros +recipe.preproc.macros="{compiler.path}{build.toolchain}{build.command.g++}" -E -CC -x c++ -w {compiler.cpp.flags} {build.flags.common} {build.flags.cpp} {build.flags.cpu} {build.flags.defs} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DF_CPU={build.fcpu} -D{build.usbtype} -DLAYOUT_{build.keylayout} {includes} "{source_file}" -o "{preprocessed_file_path}" + +## New Preprocessor for Arduino 1.9 +tools.arduino-preprocessor.path={runtime.tools.arduino-preprocessor.path} +tools.arduino-preprocessor.cmd.path={path}/arduino-preprocessor +tools.arduino-preprocessor.pattern="{cmd.path}" "{source_file}" "{codecomplete}" -- -std=gnu++14 + +## Precompile Arduino.h header +recipe.hooks.sketch.prebuild.1.pattern="{teensytools.path}precompile_helper" "{runtime.platform.path}/cores/{build.core}" "{build.path}" "{compiler.path}{build.toolchain}{build.command.g++}" -x c++-header {build.flags.optimize} {build.flags.common} {build.flags.dep} {build.flags.cpp} {build.flags.cpu} {build.flags.defs} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DF_CPU={build.fcpu} -D{build.usbtype} -DLAYOUT_{build.keylayout} "-I{runtime.platform.path}/cores/{build.core}" "{build.path}/pch/Arduino.h" -o "{build.path}/pch/Arduino.h.gch" + +## Compile c++ files +recipe.cpp.o.pattern="{compiler.path}{build.toolchain}{build.command.g++}" -c {build.flags.audio} {build.flags.optimize} {build.flags.common} {build.flags.dep} {build.flags.cpp} {build.flags.cpu} {build.flags.defs} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DF_CPU={build.fcpu} -D{build.usbtype} -DLAYOUT_{build.keylayout} "-I{build.path}/pch" {includes} "{source_file}" -o "{object_file}" + +## Compile c files +recipe.c.o.pattern="{compiler.path}{build.toolchain}{build.command.gcc}" -c {build.flags.audio} {build.flags.optimize} {build.flags.common} {build.flags.dep} {build.flags.c} {build.flags.cpu} {build.flags.defs} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DF_CPU={build.fcpu} -D{build.usbtype} -DLAYOUT_{build.keylayout} {includes} "{source_file}" -o "{object_file}" + +## Compile S files +recipe.S.o.pattern="{compiler.path}{build.toolchain}{build.command.gcc}" -c {build.flags.audio} {build.flags.optimize} {build.flags.common} {build.flags.dep} {build.flags.S} {build.flags.cpu} {build.flags.defs} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DF_CPU={build.fcpu} -D{build.usbtype} -DLAYOUT_{build.keylayout} {includes} "{source_file}" -o "{object_file}" + +## Create archives +recipe.ar.pattern="{compiler.path}{build.toolchain}{build.command.ar}" rcs "{archive_file_path}" "{object_file}" + +## Link +recipe.c.combine.pattern="{compiler.path}{build.toolchain}{build.command.linker}" {build.flags.optimize} {build.flags.ld} {build.flags.ldspecs} {build.flags.cpu} -o "{build.path}/{build.project_name}.elf" {object_files} "{build.path}/{archive_file}" "-L{build.path}" {build.flags.libs} + +## Patch ELF - TODO: not supported by modern Arduino... :( +recipe.elfpatch.pattern="{teensytools.path}/{build.elfpatch}" -mmcu={build.mcu} "{build.path}/{build.project_name}.elf" "{sketch_path}/disk" + +## Create eeprom +recipe.objcopy.eep.pattern="{compiler.path}{build.toolchain}{build.command.objcopy}" {compiler.objcopy.eep.flags} "{build.path}/{build.project_name}.elf" "{build.path}/{build.project_name}.eep" + +## Create hex +recipe.objcopy.hex.pattern="{compiler.path}{build.toolchain}{build.command.objcopy}" {compiler.elf2hex.flags} "{build.path}/{build.project_name}.elf" "{build.path}/{build.project_name}.hex" + +## EHEX file - for Teensy 4.x secure mode +recipe.hooks.objcopy.postobjcopy.1.pattern="{teensytools.path}teensy_secure" encrypthex {build.board} "{build.path}/{build.project_name}.hex" + + +## Post Build - inform Teensy Loader of new file +recipe.hooks.postbuild.1.pattern="{teensytools.path}teensy_post_compile" "-file={build.project_name}" "-path={build.path}" "-tools={teensytools.path}" "-board={build.board}" +recipe.hooks.postbuild.2.pattern="{teensytools.path}stdout_redirect" "{build.path}/{build.project_name}.sym" "{compiler.path}{build.toolchain}{build.command.objdump}" -t -C "{build.path}/{build.project_name}.elf" +recipe.hooks.postbuild.3.pattern="{teensytools.path}teensy_size" "{build.path}/{build.project_name}.elf" +# +# objdump to create .lst file is VERY SLOW for huge files +# https://forum.pjrc.com/threads/68121?p=288306&viewfull=1#post288306 +# +recipe.hooks.postbuild.4.pattern="{teensytools.path}stdout_redirect" "{build.path}/{build.project_name}.lst" "{compiler.path}{build.toolchain}{build.command.objdump}" -d -S -C "{build.path}/{build.project_name}.elf" + + +## Compute size +recipe.size.pattern="{compiler.path}{build.toolchain}{build.command.size}" -A "{build.path}/{build.project_name}.elf" +recipe.size.regex=^(?:\.text|\.text\.progmem|\.text\.itcm|\.data|\.text\.csf)\s+([0-9]+).* +recipe.size.regex.data=^(?:\.usbdescriptortable|\.dmabuffers|\.usbbuffers|\.data|\.bss|\.noinit|\.text\.itcm|\.text\.itcm\.padding)\s+([0-9]+).* +recipe.size.regex.eeprom=^(?:\.eeprom)\s+([0-9]+).* + +## Teensy Ports Discovery +## Arduino 1.8.9 requires pathPrefs patch +## discovery patters have only limited support for substitution macros, +## so we can not use {teensytools.path} or {compiler.path} here + +# Teensyduino Installer +discovery.teensy.pattern="{runtime.hardware.path}/../tools/teensy_ports" -J2 + +# Arduino Boards Manager +#discovery.teensy.pattern="{runtime.tools.teensy-tools.path}/teensy_ports" -J2 +#pluggable_discovery.required=teensy:teensy-discovery +#pluggable_monitor.required.teensy=teensy:teensy-monitor + + +## Teensy Loader + +# Teensyduino Installer +tools.teensyloader.cmd.path={runtime.hardware.path}/../tools + +# Arduino Boards Manager +#tools.teensyloader.cmd.path={runtime.tools.teensy-tools.path} + +tools.teensyloader.upload.params.quiet= +tools.teensyloader.upload.params.verbose=-verbose +tools.teensyloader.upload.pattern="{cmd.path}/teensy_post_compile" "-file={build.project_name}" "-path={build.path}" "-tools={cmd.path}" "-board={build.board}" -reboot "-port={serial.port}" "-portlabel={serial.port.label}" "-portprotocol={serial.port.protocol}" + + + + +## Export hex +recipe.output.tmp_file={build.project_name}.hex +recipe.output.save_file={build.project_name}.{build.board}.hex +recipe.hooks.savehex.postsavehex.1.pattern="{teensytools.path}teensy_secure" encrypthex {build.board} "{sketch_path}/{build.project_name}.{build.board}.hex" + +# TODO: missing patch in 1.6.6... +recipe.output.tmp_file2={build.project_name}.elf +recipe.output.save_file2={build.project_name}.elf + + +# documentation on this file's format +# https://arduino.github.io/arduino-cli/latest/platform-specification/ diff --git a/patched_teensy_core/config/boards.local.txt b/patched_teensy_core/config/boards.local.txt new file mode 100644 index 0000000..a61212a --- /dev/null +++ b/patched_teensy_core/config/boards.local.txt @@ -0,0 +1,54 @@ +## Audio +# Extra menu entries to tune the behaviour of the Audio library + +menu.audiorate=Audio sample rate +teensy41.menu.audiorate.44=44.1kHz +teensy41.menu.audiorate.44.build.flags.audiorate=44100.0f +teensy41.menu.audiorate.44.build.flags.audiorateI=44100 +teensy41.menu.audiorate.48=48kHz +teensy41.menu.audiorate.48.build.flags.audiorate=48000.0f +teensy41.menu.audiorate.48.build.flags.audiorateI=48000 +teensy41.menu.audiorate.96=96kHz +teensy41.menu.audiorate.96.build.flags.audiorate=96000.0f +teensy41.menu.audiorate.96.build.flags.audiorateI=96000 +teensy40.menu.audiorate.44=44.1kHz +teensy40.menu.audiorate.44.build.flags.audiorate=44100.0f +teensy40.menu.audiorate.44.build.flags.audiorateI=44100 +teensy40.menu.audiorate.48=48kHz +teensy40.menu.audiorate.48.build.flags.audiorate=48000.0f +teensy40.menu.audiorate.48.build.flags.audiorateI=48000 +teensy40.menu.audiorate.96=96kHz +teensy40.menu.audiorate.96.build.flags.audiorate=96000.0f +teensy40.menu.audiorate.96.build.flags.audiorateI=96000 + +menu.audioblocksize=Audio block size +teensy41.menu.audioblocksize.normal=128 samples (normal) +teensy41.menu.audioblocksize.normal.build.flags.audioblocksize=128 +teensy41.menu.audioblocksize.16=16 samples +teensy41.menu.audioblocksize.16.build.flags.audioblocksize=16 +teensy41.menu.audioblocksize.256=256 samples +teensy41.menu.audioblocksize.256.build.flags.audioblocksize=256 +teensy40.menu.audioblocksize.normal=128 samples (normal) +teensy40.menu.audioblocksize.normal.build.flags.audioblocksize=128 +teensy40.menu.audioblocksize.16=16 samples +teensy40.menu.audioblocksize.16.build.flags.audioblocksize=16 +teensy40.menu.audioblocksize.256=256 samples +teensy40.menu.audioblocksize.256.build.flags.audioblocksize=256 + +menu.USBchannelcount=USB channels +teensy41.menu.USBchannelcount.2=2 +teensy41.menu.USBchannelcount.2.build.flags.USBchannelcount=2 +teensy41.menu.USBchannelcount.4=4 +teensy41.menu.USBchannelcount.4.build.flags.USBchannelcount=4 +teensy41.menu.USBchannelcount.6=6 +teensy41.menu.USBchannelcount.6.build.flags.USBchannelcount=6 +teensy41.menu.USBchannelcount.8=8 +teensy41.menu.USBchannelcount.8.build.flags.USBchannelcount=8 +teensy40.menu.USBchannelcount.2=2 +teensy40.menu.USBchannelcount.2.build.flags.USBchannelcount=2 +teensy40.menu.USBchannelcount.4=4 +teensy40.menu.USBchannelcount.4.build.flags.USBchannelcount=4 +teensy40.menu.USBchannelcount.6=6 +teensy40.menu.USBchannelcount.6.build.flags.USBchannelcount=6 +teensy40.menu.USBchannelcount.8=8 +teensy40.menu.USBchannelcount.8.build.flags.USBchannelcount=8 diff --git a/patched_teensy_core/usb.c b/patched_teensy_core/usb.c new file mode 100644 index 0000000..f3988a1 --- /dev/null +++ b/patched_teensy_core/usb.c @@ -0,0 +1,1206 @@ +/* Teensyduino Core Library + * http://www.pjrc.com/teensy/ + * Copyright (c) 2019 PJRC.COM, LLC. + * + * 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: + * + * 1. The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * 2. If the Software is incorporated into a build system that allows + * selection among a list of target devices, then similar target + * devices manufactured by PJRC.COM must be included in the list of + * target devices and selectable in the same manner. + * + * 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 "usb_dev.h" +#define USB_DESC_LIST_DEFINE +#include "usb_desc.h" +#include "usb_serial.h" +#include "usb_seremu.h" +#include "usb_rawhid.h" +#include "usb_keyboard.h" +#include "usb_mouse.h" +#include "usb_joystick.h" +#include "usb_flightsim.h" +#include "usb_touch.h" +#include "usb_midi.h" +#include "usb_audio.h" +#include "usb_mtp.h" +#include "core_pins.h" // for delay() +#include "avr/pgmspace.h" +#include +#include "debug/printf.h" + +#define LSB(n) ((n) & 255) +#define MSB(n) (((n) >> 8) & 255) + +//#define LOG_SIZE 20 +//uint32_t transfer_log_head=0; +//uint32_t transfer_log_count=0; +//uint32_t transfer_log[LOG_SIZE]; + +// device mode, page 3155 + +#if defined(NUM_ENDPOINTS) + +typedef struct endpoint_struct endpoint_t; + +struct endpoint_struct { + uint32_t config; + uint32_t current; + uint32_t next; + uint32_t status; + uint32_t pointer0; + uint32_t pointer1; + uint32_t pointer2; + uint32_t pointer3; + uint32_t pointer4; + uint32_t reserved; + uint32_t setup0; + uint32_t setup1; + transfer_t *first_transfer; + transfer_t *last_transfer; + void (*callback_function)(transfer_t *completed_transfer); + uint32_t unused1; +}; + +/*struct transfer_struct { + uint32_t next; + uint32_t status; + uint32_t pointer0; + uint32_t pointer1; + uint32_t pointer2; + uint32_t pointer3; + uint32_t pointer4; + uint32_t callback_param; +};*/ + +endpoint_t endpoint_queue_head[(NUM_ENDPOINTS+1)*2] __attribute__ ((used, aligned(4096), section(".endpoint_queue") )); + +transfer_t endpoint0_transfer_data __attribute__ ((used, aligned(32))); +transfer_t endpoint0_transfer_ack __attribute__ ((used, aligned(32))); + + +typedef union { + struct { + union { + struct { + uint8_t bmRequestType; + uint8_t bRequest; + }; + uint16_t wRequestAndType; + }; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; + }; + struct { + uint32_t word1; + uint32_t word2; + }; + uint64_t bothwords; +} setup_t; + +static setup_t endpoint0_setupdata; +static uint32_t endpoint0_notify_mask=0; +static uint32_t endpointN_notify_mask=0; +//static int reset_count=0; +volatile uint8_t usb_configuration = 0; // non-zero when USB host as configured device +volatile uint8_t usb_high_speed = 0; // non-zero if running at 480 Mbit/sec speed +static uint8_t endpoint0_buffer[14]; +static uint8_t sof_usage = 0; +static uint8_t usb_reboot_timer = 0; + +extern uint8_t usb_descriptor_buffer[]; // defined in usb_desc.c +extern const uint8_t usb_config_descriptor_480[]; +extern const uint8_t usb_config_descriptor_12[]; + +void (*usb_timer0_callback)(void) = NULL; +void (*usb_timer1_callback)(void) = NULL; + +void usb_isr(void); +static void endpoint0_setup(uint64_t setupdata); +static void endpoint0_transmit(const void *data, uint32_t len, int notify); +static void endpoint0_receive(void *data, uint32_t len, int notify); +static void endpoint0_complete(void); + + +static void run_callbacks(endpoint_t *ep); + + +FLASHMEM void usb_init(void) +{ + // TODO: only enable when VBUS detected + // TODO: return to low power mode when VBUS removed + // TODO: protect PMU access with MPU + PMU_REG_3P0 = PMU_REG_3P0_OUTPUT_TRG(0x0F) | PMU_REG_3P0_BO_OFFSET(6) + | PMU_REG_3P0_ENABLE_LINREG; + + usb_init_serialnumber(); + + // assume PLL3 is already running - already done by usb_pll_start() in main.c + + CCM_CCGR6 |= CCM_CCGR6_USBOH3(CCM_CCGR_ON); // turn on clocks to USB peripheral + + printf("BURSTSIZE=%08lX\n", USB1_BURSTSIZE); + //USB1_BURSTSIZE = USB_BURSTSIZE_TXPBURST(4) | USB_BURSTSIZE_RXPBURST(4); + USB1_BURSTSIZE = 0x0404; + printf("BURSTSIZE=%08lX\n", USB1_BURSTSIZE); + printf("USB1_TXFILLTUNING=%08lX\n", USB1_TXFILLTUNING); + + // Before programming this register, the PHY clocks must be enabled in registers + // USBPHYx_CTRLn and CCM_ANALOG_USBPHYx_PLL_480_CTRLn. + + //printf("USBPHY1_PWD=%08lX\n", USBPHY1_PWD); + //printf("USBPHY1_TX=%08lX\n", USBPHY1_TX); + //printf("USBPHY1_RX=%08lX\n", USBPHY1_RX); + //printf("USBPHY1_CTRL=%08lX\n", USBPHY1_CTRL); + //printf("USB1_USBMODE=%08lX\n", USB1_USBMODE); + + // turn on PLL3, wait for 480 MHz lock? + // turn on CCM clock gates? CCGR6[CG0] +#if 1 + if ((USBPHY1_PWD & (USBPHY_PWD_RXPWDRX | USBPHY_PWD_RXPWDDIFF | USBPHY_PWD_RXPWD1PT1 + | USBPHY_PWD_RXPWDENV | USBPHY_PWD_TXPWDV2I | USBPHY_PWD_TXPWDIBIAS + | USBPHY_PWD_TXPWDFS)) || (USB1_USBMODE & USB_USBMODE_CM_MASK)) { + // USB controller is turned on from previous use + // reset needed to turn it off & start from clean slate + USBPHY1_CTRL_SET = USBPHY_CTRL_SFTRST; // USBPHY1_CTRL page 3292 + USB1_USBCMD |= USB_USBCMD_RST; // reset controller + int count=0; + while (USB1_USBCMD & USB_USBCMD_RST) count++; + NVIC_CLEAR_PENDING(IRQ_USB1); + USBPHY1_CTRL_CLR = USBPHY_CTRL_SFTRST; // reset PHY + //USB1_USBSTS = USB1_USBSTS; // TODO: is this needed? + printf("USB reset took %d loops\n", count); + //delay(10); + //printf("\n"); + //printf("USBPHY1_PWD=%08lX\n", USBPHY1_PWD); + //printf("USBPHY1_TX=%08lX\n", USBPHY1_TX); + //printf("USBPHY1_RX=%08lX\n", USBPHY1_RX); + //printf("USBPHY1_CTRL=%08lX\n", USBPHY1_CTRL); + //printf("USB1_USBMODE=%08lX\n", USB1_USBMODE); + delay(25); + } +#endif + // Device Controller Initialization, page 2351 (Rev 2, 12/2019) + // USBCMD pg 3216 + // USBSTS pg 3220 + // USBINTR pg 3224 + // DEVICEADDR pg 3227 + // ENDPTLISTADDR 3229 + // USBMODE pg 3244 + // ENDPTSETUPSTAT 3245 + // ENDPTPRIME pg 3246 + // ENDPTFLUSH pg 3247 + // ENDPTSTAT pg 3247 + // ENDPTCOMPLETE 3248 + // ENDPTCTRL0 pg 3249 + + USBPHY1_CTRL_CLR = USBPHY_CTRL_CLKGATE; + USBPHY1_PWD = 0; + //printf("USBPHY1_PWD=%08lX\n", USBPHY1_PWD); + //printf("USBPHY1_CTRL=%08lX\n", USBPHY1_CTRL); + + USB1_USBMODE = USB_USBMODE_CM(2) | USB_USBMODE_SLOM; + memset(endpoint_queue_head, 0, sizeof(endpoint_queue_head)); + endpoint_queue_head[0].config = (64 << 16) | (1 << 15); + endpoint_queue_head[1].config = (64 << 16); + USB1_ENDPOINTLISTADDR = (uint32_t)&endpoint_queue_head; + // Recommended: enable all device interrupts including: USBINT, USBERRINT, + // Port Change Detect, USB Reset Received, DCSuspend. + USB1_USBINTR = USB_USBINTR_UE | USB_USBINTR_UEE | /* USB_USBINTR_PCE | */ + USB_USBINTR_URE | USB_USBINTR_SLE; + //_VectorsRam[IRQ_USB1+16] = &usb_isr; + attachInterruptVector(IRQ_USB1, &usb_isr); + NVIC_ENABLE_IRQ(IRQ_USB1); + //printf("USB1_ENDPTCTRL0=%08lX\n", USB1_ENDPTCTRL0); + //printf("USB1_ENDPTCTRL1=%08lX\n", USB1_ENDPTCTRL1); + //printf("USB1_ENDPTCTRL2=%08lX\n", USB1_ENDPTCTRL2); + //printf("USB1_ENDPTCTRL3=%08lX\n", USB1_ENDPTCTRL3); + USB1_USBCMD = USB_USBCMD_RS; + //transfer_log_head = 0; + //transfer_log_count = 0; + //USB1_PORTSC1 |= USB_PORTSC1_PFSC; // force 12 Mbit/sec +} + + +FLASHMEM __attribute__((noinline)) void _reboot_Teensyduino_(void) +{ + if (!(HW_OCOTP_CFG5 & 0x02)) { + asm("bkpt #251"); // run bootloader + } else { + __disable_irq(); // secure mode NXP ROM reboot + USB1_USBCMD = 0; + IOMUXC_GPR_GPR16 = 0x00200003; + // TODO: wipe all RAM for security + __asm__ volatile("mov sp, %0" : : "r" (0x20201000) : ); + __asm__ volatile("dsb":::"memory"); + volatile uint32_t * const p = (uint32_t *)0x20208000; + *p = 0xEB120000; + ((void (*)(volatile void *))(*(uint32_t *)(*(uint32_t *)0x0020001C + 8)))(p); + } + __builtin_unreachable(); +} + + +void usb_isr(void) +{ + //printf("*"); + + // Port control in device mode is only used for + // status port reset, suspend, and current connect status. + uint32_t status = USB1_USBSTS; + USB1_USBSTS = status; + + // USB_USBSTS_SLI - set to 1 when enters a suspend state from an active state + // USB_USBSTS_SRI - set at start of frame + // USB_USBSTS_SRI - set when USB reset detected + + if (status & USB_USBSTS_UI) { + //printf("data\n"); + uint32_t setupstatus = USB1_ENDPTSETUPSTAT; + //printf("USB1_ENDPTSETUPSTAT=%X\n", setupstatus); + while (setupstatus) { + USB1_ENDPTSETUPSTAT = setupstatus; + setup_t s; + do { + USB1_USBCMD |= USB_USBCMD_SUTW; + s.word1 = endpoint_queue_head[0].setup0; + s.word2 = endpoint_queue_head[0].setup1; + } while (!(USB1_USBCMD & USB_USBCMD_SUTW)); + USB1_USBCMD &= ~USB_USBCMD_SUTW; + //printf("setup %08lX %08lX\n", s.word1, s.word2); + USB1_ENDPTFLUSH = (1<<16) | (1<<0); // page 3174 + while (USB1_ENDPTFLUSH & ((1<<16) | (1<<0))) ; + endpoint0_notify_mask = 0; + endpoint0_setup(s.bothwords); + setupstatus = USB1_ENDPTSETUPSTAT; // page 3175 + } + uint32_t completestatus = USB1_ENDPTCOMPLETE; + if (completestatus) { + USB1_ENDPTCOMPLETE = completestatus; + //printf("USB1_ENDPTCOMPLETE=%lX\n", completestatus); + if (completestatus & endpoint0_notify_mask) { + endpoint0_notify_mask = 0; + endpoint0_complete(); + } + completestatus &= endpointN_notify_mask; +#if 1 + if (completestatus) { + + // transmit: + uint32_t tx = completestatus >> 16; + while (tx) { + int p=__builtin_ctz(tx); + run_callbacks(endpoint_queue_head + p * 2 + 1); + tx &= ~(1<= 3) { + // shut off USB - easier to see results in protocol analyzer + //USB1_USBCMD &= ~USB_USBCMD_RS; + //printf("shut off USB\n"); + //} + } + if (status & USB_USBSTS_TI0) { + if (usb_timer0_callback != NULL) usb_timer0_callback(); + } + if (status & USB_USBSTS_TI1) { + if (usb_timer1_callback != NULL) usb_timer1_callback(); + } + if (status & USB_USBSTS_PCI) { + if (USB1_PORTSC1 & USB_PORTSC1_HSP) { + //printf("port at 480 Mbit\n"); + usb_high_speed = 1; + } else { + //printf("port at 12 Mbit\n"); + usb_high_speed = 0; + } + } + if (status & USB_USBSTS_SLI) { // page 3165 + //printf("suspend\n"); + } + if (status & USB_USBSTS_UEI) { + //printf("error\n"); + } + if ((USB1_USBINTR & USB_USBINTR_SRE) && (status & USB_USBSTS_SRI)) { + //printf("sof %d\n", usb_reboot_timer); + if (usb_reboot_timer) { + if (--usb_reboot_timer == 0) { + usb_stop_sof_interrupts(NUM_INTERFACE); + _reboot_Teensyduino_(); + } + } + #ifdef MIDI_INTERFACE + usb_midi_flush_output(); + #endif + #ifdef MULTITOUCH_INTERFACE + usb_touchscreen_update_callback(); + #endif + #ifdef FLIGHTSIM_INTERFACE + usb_flightsim_flush_output(); + #endif + } +} + + +void usb_start_sof_interrupts(int interface) +{ + __disable_irq(); + sof_usage |= (1 << interface); + uint32_t intr = USB1_USBINTR; + if (!(intr & USB_USBINTR_SRE)) { + USB1_USBSTS = USB_USBSTS_SRI; // clear prior SOF before SOF IRQ enable + USB1_USBINTR = intr | USB_USBINTR_SRE; + } + __enable_irq(); +} + +void usb_stop_sof_interrupts(int interface) +{ + sof_usage &= ~(1 << interface); + if (sof_usage == 0) { + USB1_USBINTR &= ~USB_USBINTR_SRE; + } +} + + + + +/* +struct transfer_struct { // table 55-60, pg 3159 + uint32_t next; + uint32_t status; + uint32_t pointer0; + uint32_t pointer1; + uint32_t pointer2; + uint32_t pointer3; + uint32_t pointer4; + uint32_t unused1; +}; +transfer_t endpoint0_transfer_data __attribute__ ((aligned(32)));; +transfer_t endpoint0_transfer_ack __attribute__ ((aligned(32)));; +*/ + +static uint8_t reply_buffer[8]; + +static void endpoint0_setup(uint64_t setupdata) +{ + setup_t setup; + uint32_t endpoint, dir, ctrl; + const usb_descriptor_list_t *list; + //printf("wRequestAndType= %x, word1=%x, wIndex=%d\n", setup.wRequestAndType, setup.word1, setup.wIndex); + setup.bothwords = setupdata; + switch (setup.wRequestAndType) { + case 0x0500: // SET_ADDRESS + endpoint0_receive(NULL, 0, 0); + USB1_DEVICEADDR = USB_DEVICEADDR_USBADR(setup.wValue) | USB_DEVICEADDR_USBADRA; + return; + case 0x0900: // SET_CONFIGURATION + usb_configuration = setup.wValue; + // configure all other endpoints + #if defined(ENDPOINT2_CONFIG) + USB1_ENDPTCTRL2 = ENDPOINT2_CONFIG; + #endif + #if defined(ENDPOINT3_CONFIG) + USB1_ENDPTCTRL3 = ENDPOINT3_CONFIG; + #endif + #if defined(ENDPOINT4_CONFIG) + USB1_ENDPTCTRL4 = ENDPOINT4_CONFIG; + #endif + #if defined(ENDPOINT5_CONFIG) + USB1_ENDPTCTRL5 = ENDPOINT5_CONFIG; + #endif + #if defined(ENDPOINT6_CONFIG) + USB1_ENDPTCTRL6 = ENDPOINT6_CONFIG; + #endif + #if defined(ENDPOINT7_CONFIG) + USB1_ENDPTCTRL7 = ENDPOINT7_CONFIG; + #endif + #if defined(CDC_STATUS_INTERFACE) && defined(CDC_DATA_INTERFACE) + usb_serial_configure(); + #elif defined(SEREMU_INTERFACE) + usb_seremu_configure(); + #endif + #if defined(CDC2_STATUS_INTERFACE) && defined(CDC2_DATA_INTERFACE) + usb_serial2_configure(); + #endif + #if defined(CDC3_STATUS_INTERFACE) && defined(CDC3_DATA_INTERFACE) + usb_serial3_configure(); + #endif + #if defined(RAWHID_INTERFACE) + usb_rawhid_configure(); + #endif + #if defined(KEYBOARD_INTERFACE) + usb_keyboard_configure(); + #endif + #if defined(MOUSE_INTERFACE) + usb_mouse_configure(); + #endif + #if defined(FLIGHTSIM_INTERFACE) + usb_flightsim_configure(); + #endif + #if defined(JOYSTICK_INTERFACE) + usb_joystick_configure(); + #endif + #if defined(MULTITOUCH_INTERFACE) + usb_touchscreen_configure(); + #endif + #if defined(MIDI_INTERFACE) + usb_midi_configure(); + #endif + #if defined(AUDIO_INTERFACE) + usb_audio_configure(); + #endif + #if defined(MTP_INTERFACE) + usb_mtp_configure(); + #endif + #if defined(EXPERIMENTAL_INTERFACE) + memset(endpoint_queue_head + 2, 0, sizeof(endpoint_t) * 2); + endpoint_queue_head[2].pointer4 = 0xB8C6CF5D; + endpoint_queue_head[3].pointer4 = 0x74D59319; + #endif + endpoint0_receive(NULL, 0, 0); + return; + case 0x0880: // GET_CONFIGURATION + reply_buffer[0] = usb_configuration; + endpoint0_transmit(reply_buffer, 1, 0); + return; + case 0x0080: // GET_STATUS (device) + reply_buffer[0] = 0; + reply_buffer[1] = 0; + endpoint0_transmit(reply_buffer, 2, 0); + return; + case 0x0082: // GET_STATUS (endpoint) + endpoint = setup.wIndex & 0x7F; + if (endpoint > 7) break; + dir = setup.wIndex & 0x80; + ctrl = *((uint32_t *)&USB1_ENDPTCTRL0 + endpoint); + reply_buffer[0] = 0; + reply_buffer[1] = 0; + if ((dir && (ctrl & USB_ENDPTCTRL_TXS)) || (!dir && (ctrl & USB_ENDPTCTRL_RXS))) { + reply_buffer[0] = 1; + } + endpoint0_transmit(reply_buffer, 2, 0); + return; + case 0x0302: // SET_FEATURE (endpoint) + endpoint = setup.wIndex & 0x7F; + if (endpoint > 7) break; + dir = setup.wIndex & 0x80; + if (dir) { + *((volatile uint32_t *)&USB1_ENDPTCTRL0 + endpoint) |= USB_ENDPTCTRL_TXS; + } else { + *((volatile uint32_t *)&USB1_ENDPTCTRL0 + endpoint) |= USB_ENDPTCTRL_RXS; + } + endpoint0_receive(NULL, 0, 0); + return; + case 0x0102: // CLEAR_FEATURE (endpoint) + endpoint = setup.wIndex & 0x7F; + if (endpoint > 7) break; + dir = setup.wIndex & 0x80; + if (dir) { + *((volatile uint32_t *)&USB1_ENDPTCTRL0 + endpoint) &= ~USB_ENDPTCTRL_TXS; + } else { + *((volatile uint32_t *)&USB1_ENDPTCTRL0 + endpoint) &= ~USB_ENDPTCTRL_RXS; + } + endpoint0_receive(NULL, 0, 0); + return; +#ifdef EXPERIMENTAL_INTERFACE + case 0xF8C0: // GET_MS_DESCRIPTOR (bRequest=0xF8 because microsoft_os_string_desc) + if ((setup.wIndex & 0xFF00) != 0) break; // 1=Genre, 4=Compat ID, 5=Properties + setup.wIndex |= 0xEE00; // alter wIndex and treat as normal USB descriptor + __attribute__((fallthrough)); +#endif + case 0x0680: // GET_DESCRIPTOR + case 0x0681: + for (list = usb_descriptor_list; list->addr != NULL; list++) { + if (setup.wValue == list->wValue && setup.wIndex == list->wIndex) { + uint32_t datalen; + if ((setup.wValue >> 8) == 3) { + // for string descriptors, use the descriptor's + // length field, allowing runtime configured length. + datalen = *(list->addr); + } else { + datalen = list->length; + } + if (datalen > setup.wLength) datalen = setup.wLength; + printf("datalength: %x", datalen); + // copy the descriptor, from PROGMEM to DMAMEM + if (setup.wValue == 0x200) { + // config descriptor needs to adapt to speed + const uint8_t *src = usb_config_descriptor_12; + if (usb_high_speed) src = usb_config_descriptor_480; + memcpy(usb_descriptor_buffer, src, datalen); + } else if (setup.wValue == 0x700) { + // other speed config also needs to adapt + const uint8_t *src = usb_config_descriptor_480; + if (usb_high_speed) src = usb_config_descriptor_12; + memcpy(usb_descriptor_buffer, src, datalen); + usb_descriptor_buffer[1] = 7; + } else { + memcpy(usb_descriptor_buffer, list->addr, datalen); + } + // prep transmit + arm_dcache_flush_delete(usb_descriptor_buffer, datalen); + endpoint0_transmit(usb_descriptor_buffer, datalen, 0); + return; + } + } + break; +#if defined(CDC_STATUS_INTERFACE) + case 0x2221: // CDC_SET_CONTROL_LINE_STATE + #ifdef CDC_STATUS_INTERFACE + if (setup.wIndex == CDC_STATUS_INTERFACE) { + usb_cdc_line_rtsdtr_millis = systick_millis_count; + usb_cdc_line_rtsdtr = setup.wValue; + } + #endif + #ifdef CDC2_STATUS_INTERFACE + if (setup.wIndex == CDC2_STATUS_INTERFACE) { + usb_cdc2_line_rtsdtr_millis = systick_millis_count; + usb_cdc2_line_rtsdtr = setup.wValue; + } + #endif + #ifdef CDC3_STATUS_INTERFACE + if (setup.wIndex == CDC3_STATUS_INTERFACE) { + usb_cdc3_line_rtsdtr_millis = systick_millis_count; + usb_cdc3_line_rtsdtr = setup.wValue; + } + #endif + __attribute__((fallthrough)); + // fall through to next case, to always send ZLP ACK + case 0x2321: // CDC_SEND_BREAK + endpoint0_receive(NULL, 0, 0); + return; + case 0x2021: // CDC_SET_LINE_CODING + if (setup.wLength != 7) break; + endpoint0_setupdata.bothwords = setupdata; + endpoint0_receive(endpoint0_buffer, 7, 1); + return; +#endif +#if defined(SEREMU_INTERFACE) || defined(KEYBOARD_INTERFACE) + case 0x0921: // HID SET_REPORT + if (setup.wLength <= sizeof(endpoint0_buffer)) { + //printf("hid set report %x %x\n", setup.word1, setup.word2); + endpoint0_setupdata.bothwords = setup.bothwords; + endpoint0_buffer[0] = 0xE9; + endpoint0_receive(endpoint0_buffer, setup.wLength, 1); + return; + } + break; +#endif +#if defined(AUDIO_INTERFACE) + case 0x0B01: // SET_INTERFACE (alternate setting) + if (setup.wIndex == AUDIO_INTERFACE+1) { + printf("case 0x0B01, word1=%x, wIndex=%d\n", setup.word1, setup.wIndex); + usb_audio_transmit_setting = setup.wValue; + if (usb_audio_transmit_setting > 0) { + // TODO: set up AUDIO_TX_ENDPOINT to transmit + } + endpoint0_receive(NULL, 0, 0); + return; + } else if (setup.wIndex == AUDIO_INTERFACE+2) { + usb_audio_receive_setting = setup.wValue; + endpoint0_receive(NULL, 0, 0); + return; + } + break; + case 0x0A81: // GET_INTERFACE (alternate setting) + if (setup.wIndex == AUDIO_INTERFACE+1) { + endpoint0_buffer[0] = usb_audio_transmit_setting; + endpoint0_transmit(endpoint0_buffer, 1, 0); + return; + } else if (setup.wIndex == AUDIO_INTERFACE+2) { + endpoint0_buffer[0] = usb_audio_receive_setting; + endpoint0_transmit(endpoint0_buffer, 1, 0); + return; + } + break; + + //Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section A.14, Table A-14 page 134-135: + //The only four audio class specific request are: 0x00 (UNDEFINED), 0x01 (CUR), 0x02 (RANGE), 0x03 (MEM) + // Therefore the audio specific cases are: + // 0x0121, 0x0221, 0x0321, + // 0x0122, 0x0222, 0x0322, + // 0x01A1, 0x02A1, 0x03A1, + // 0x01A2, 0x02A2, 0x03A2, + //all cases that end on '21': SET FEATURE directed to an interface (AudioControl or AudioStreaming)(Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 5.2.2, Table 5-1 page 90-91) + //all cases that end on '22': SET FEATURE directed to isochronous endpoint of an AudioStreaming interface (Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 5.2.2, Table 5-1 page 90-91) + case 0x0121: //-> set CUR request + case 0x0221: //-> set RANGE request + case 0x0321: //-> set MEM request + + if (setup.wLength <= sizeof(endpoint0_buffer)) { + endpoint0_setupdata.bothwords = setupdata; + endpoint0_receive(endpoint0_buffer, setup.wLength, 1); + return; // handle these after ACK + } + break; + //all cases that end on 'A1': GET FEATURE directed to an interface (AudioControl or AudioStreaming)(Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 5.2.2, Table 5-1 page 90-91) + //all cases that end on 'A2': GET FEATURE directed to isochronous endpoint of an AudioStreaming interface (Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 5.2.2, Table 5-1 page 90-91) + + case 0x01A1: //-> get CUR request + if( LSB(setup.wIndex) == AUDIO_INTERFACE && + MSB(setup.wIndex) == 0x3a //0x3a is the bClockID + ){ + if(setup.wValue == 0x0100){ + //0x01...CS_SAM_FREQ_CONTROL 0x00... channel must be set to 0 + endpoint0_buffer[0] = ((uint32_t)AUDIO_SAMPLE_RATE) & 255; + endpoint0_buffer[1] = (((uint32_t)AUDIO_SAMPLE_RATE) >> 8) & 255; + endpoint0_buffer[2] = (((uint32_t)AUDIO_SAMPLE_RATE) >> 16) & 255; + endpoint0_buffer[3] = 0; + uint32_t l = setup.wLength < 4 ? setup.wLength : 4; + endpoint0_transmit(endpoint0_buffer, l, 0); + return; + } + if(setup.wValue == 0x0200){ + //0x02...CS_CLOCK_VALID_CONTROL 0x00... channel must be set to 0 + endpoint0_buffer[0] = 1; //always valid + uint32_t l = setup.wLength < 1 ? setup.wLength : 1; + endpoint0_transmit(endpoint0_buffer, l, 0); + return; + } + } + case 0x02A1: //-> get RANGE request + if( LSB(setup.wIndex) == AUDIO_INTERFACE && + MSB(setup.wIndex) == 0x3a && //0x3a is the bClockID + setup.wValue == 0x0100 //range of clock + ){ + + endpoint0_buffer[0] = 1; //only one sub-range (LSB of 2 bytes) + endpoint0_buffer[1] = 0; //only one sub-range (MSB of 2 bytes) + //min value of range + endpoint0_buffer[2] = ((uint32_t)AUDIO_SAMPLE_RATE) & 255; + endpoint0_buffer[3] = (((uint32_t)AUDIO_SAMPLE_RATE) >> 8) & 255; + endpoint0_buffer[4] = (((uint32_t)AUDIO_SAMPLE_RATE) >> 16) & 255; + endpoint0_buffer[5] = 0; + //max value of range (=min value) + endpoint0_buffer[6]= endpoint0_buffer[2]; + endpoint0_buffer[7]= endpoint0_buffer[3]; + endpoint0_buffer[8]= endpoint0_buffer[4]; + endpoint0_buffer[9]= endpoint0_buffer[5]; + //resolution + endpoint0_buffer[10]= 0; + endpoint0_buffer[11]= 0; + endpoint0_buffer[12]= 0; + endpoint0_buffer[13]= 0; + uint32_t l = setup.wLength < 14 ? setup.wLength : 14; + endpoint0_transmit(endpoint0_buffer, l, 0); + return; + + } + case 0x03A1: //-> get MEM request + if( LSB(setup.wIndex) == AUDIO_INTERFACE && + MSB(setup.wIndex) == 0x31 //0x31 is the bUnitID of the volume control + ){ + uint32_t len; + if (usb_audio_get_feature(&setup, endpoint0_buffer, &len)) { + //printf("GET feature, len=%d\n", len); + uint32_t l = setup.wLength < len ? setup.wLength : len; + endpoint0_transmit(endpoint0_buffer, l, 0); + return; + } + break; + } + +#endif +#if defined(MULTITOUCH_INTERFACE) + case 0x01A1: + if (setup.wValue == 0x0300 && setup.wIndex == MULTITOUCH_INTERFACE) { + endpoint0_buffer[0] = MULTITOUCH_FINGERS; + endpoint0_transmit(endpoint0_buffer, 1, 0); + return; + } else if (setup.wValue == 0x0100 && setup.wIndex == MULTITOUCH_INTERFACE) { + memset(endpoint0_buffer, 0, 8); + endpoint0_transmit(endpoint0_buffer, 8, 0); + return; + } + break; +#endif +#if defined(MTP_INTERFACE) + case 0x6421: // Cancel Request, Still Image Class 1.0, 5.2.1, page 8 + if (setup.wLength == 6) { + endpoint0_setupdata.bothwords = setupdata; + endpoint0_receive(endpoint0_buffer, setup.wLength, 1); + return; + } + break; + case 0x65A1: // Get Extended Event Data, Still Image Class 1.0, 5.2.2, page 9 + break; + case 0x6621: // Device Reset, Still Image Class 1.0, 5.2.3 page 10 + break; + case 0x67A1: // Get Device Status, Still Image Class 1.0, 5.2.4, page 10 + if (setup.wLength >= 4) { + endpoint0_buffer[0] = 4; + endpoint0_buffer[1] = 0; + endpoint0_buffer[2] = usb_mtp_status; + endpoint0_buffer[3] = 0x20; + endpoint0_transmit(endpoint0_buffer, 4, 0); + //if (usb_mtp_status == 0x19) usb_mtp_status = 0x01; // testing only + return; + } + break; +#endif + } + //printf("endpoint 0 stall\n"); + + USB1_ENDPTCTRL0 = 0x000010001; // stall +} + +static void endpoint0_transmit(const void *data, uint32_t len, int notify) +{ + //printf("tx %lu\n", len); + if (len > 0) { + // Executing A Transfer Descriptor, page 3182 + endpoint0_transfer_data.next = 1; + endpoint0_transfer_data.status = (len << 16) | (1<<7); + uint32_t addr = (uint32_t)data; + endpoint0_transfer_data.pointer0 = addr; // format: table 55-60, pg 3159 + endpoint0_transfer_data.pointer1 = addr + 4096; + endpoint0_transfer_data.pointer2 = addr + 8192; + endpoint0_transfer_data.pointer3 = addr + 12288; + endpoint0_transfer_data.pointer4 = addr + 16384; + // Case 1: Link list is empty, page 3182 + endpoint_queue_head[1].next = (uint32_t)&endpoint0_transfer_data; + endpoint_queue_head[1].status = 0; + USB1_ENDPTPRIME |= (1<<16); + while (USB1_ENDPTPRIME) ; + } + endpoint0_transfer_ack.next = 1; + endpoint0_transfer_ack.status = (1<<7) | (notify ? (1 << 15) : 0); + endpoint0_transfer_ack.pointer0 = 0; + endpoint_queue_head[0].next = (uint32_t)&endpoint0_transfer_ack; + endpoint_queue_head[0].status = 0; + USB1_ENDPTCOMPLETE = (1<<0) | (1<<16); + USB1_ENDPTPRIME |= (1<<0); + endpoint0_notify_mask = (notify ? (1 << 0) : 0); + while (USB1_ENDPTPRIME) ; +} + +static void endpoint0_receive(void *data, uint32_t len, int notify) +{ + //printf("rx %lu\n", len); + if (len > 0) { + // Executing A Transfer Descriptor, page 3182 + endpoint0_transfer_data.next = 1; + endpoint0_transfer_data.status = (len << 16) | (1<<7); + uint32_t addr = (uint32_t)data; + endpoint0_transfer_data.pointer0 = addr; // format: table 55-60, pg 3159 + endpoint0_transfer_data.pointer1 = addr + 4096; + endpoint0_transfer_data.pointer2 = addr + 8192; + endpoint0_transfer_data.pointer3 = addr + 12288; + endpoint0_transfer_data.pointer4 = addr + 16384; + // Case 1: Link list is empty, page 3182 + endpoint_queue_head[0].next = (uint32_t)&endpoint0_transfer_data; + endpoint_queue_head[0].status = 0; + USB1_ENDPTPRIME |= (1<<0); + while (USB1_ENDPTPRIME) ; + } + endpoint0_transfer_ack.next = 1; + endpoint0_transfer_ack.status = (1<<7) | (notify ? (1 << 15) : 0); + endpoint0_transfer_ack.pointer0 = 0; + endpoint_queue_head[1].next = (uint32_t)&endpoint0_transfer_ack; + endpoint_queue_head[1].status = 0; + USB1_ENDPTCOMPLETE = (1<<0) | (1<<16); + USB1_ENDPTPRIME |= (1<<16); + endpoint0_notify_mask = (notify ? (1 << 16) : 0); + while (USB1_ENDPTPRIME) ; +} + +/*typedef union { + struct { + union { + struct { + uint8_t bmRequestType; + uint8_t bRequest; + }; + uint16_t wRequestAndType; + }; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; + }; + struct { + uint32_t word1; + uint32_t word2; + }; + uint64_t bothwords; +} setup_t; */ + + +static void endpoint0_complete(void) +{ + setup_t setup; + + setup.bothwords = endpoint0_setupdata.bothwords; + //printf("complete %x %x %x\n", setup.word1, setup.word2, endpoint0_buffer[0]); +#ifdef CDC_STATUS_INTERFACE + // 0x2021 is CDC_SET_LINE_CODING + if (setup.wRequestAndType == 0x2021 && setup.wIndex == CDC_STATUS_INTERFACE) { + memcpy(usb_cdc_line_coding, endpoint0_buffer, 7); + printf("usb_cdc_line_coding, baud=%u\n", usb_cdc_line_coding[0]); + if (usb_cdc_line_coding[0] == 134) { + usb_start_sof_interrupts(NUM_INTERFACE); + usb_reboot_timer = 80; // TODO: 10 if only 12 Mbit/sec + } + } +#endif +#ifdef CDC2_STATUS_INTERFACE + if (setup.wRequestAndType == 0x2021 && setup.wIndex == CDC2_STATUS_INTERFACE) { + memcpy(usb_cdc2_line_coding, endpoint0_buffer, 7); + printf("usb_cdc2_line_coding, baud=%u\n", usb_cdc2_line_coding[0]); + if (usb_cdc2_line_coding[0] == 134) { + usb_start_sof_interrupts(NUM_INTERFACE); + usb_reboot_timer = 80; // TODO: 10 if only 12 Mbit/sec + } + } +#endif +#ifdef CDC3_STATUS_INTERFACE + if (setup.wRequestAndType == 0x2021 && setup.wIndex == CDC3_STATUS_INTERFACE) { + memcpy(usb_cdc3_line_coding, endpoint0_buffer, 7); + printf("usb_cdc3_line_coding, baud=%u\n", usb_cdc3_line_coding[0]); + if (usb_cdc3_line_coding[0] == 134) { + usb_start_sof_interrupts(NUM_INTERFACE); + usb_reboot_timer = 80; // TODO: 10 if only 12 Mbit/sec + } + } +#endif +#ifdef KEYBOARD_INTERFACE + if (setup.word1 == 0x02000921 && setup.word2 == ((1 << 16) | KEYBOARD_INTERFACE)) { + keyboard_leds = endpoint0_buffer[0]; + endpoint0_transmit(NULL, 0, 0); + } +#endif +#ifdef SEREMU_INTERFACE + if (setup.word1 == 0x03000921 && setup.word2 == ((4<<16)|SEREMU_INTERFACE)) { + if (endpoint0_buffer[0] == 0xA9 && endpoint0_buffer[1] == 0x45 + && endpoint0_buffer[2] == 0xC2 && endpoint0_buffer[3] == 0x6B) { + printf("seremu reboot request\n"); + usb_start_sof_interrupts(NUM_INTERFACE); + usb_reboot_timer = 80; // TODO: 10 if only 12 Mbit/sec + } else { + // any other feature report means Arduino Serial Monitor is open + usb_seremu_online = 1; + } + } +#endif +#ifdef AUDIO_INTERFACE + if( setup.wRequestAndType == 0x0121 && // set CUR + LSB(setup.wIndex) == AUDIO_INTERFACE && + MSB(setup.wIndex) == 0x31 //0x31 is the bUnitID of the volume control + ){ + usb_audio_set_feature(&endpoint0_setupdata, endpoint0_buffer); + } +#endif +#ifdef MTP_INTERFACE + if (setup.wRequestAndType == 0x6421) { + if (endpoint0_buffer[0] == 0x01 && endpoint0_buffer[1] == 0x40) { + printf("MTP cancel, transaction ID=%08X\n", + endpoint0_buffer[2] | (endpoint0_buffer[3] << 8) | + (endpoint0_buffer[4] << 16) | (endpoint0_buffer[5] << 24)); + usb_mtp_status = 0x19; // 0x19 = host initiated cancel + } + } +#endif +} + +static void usb_endpoint_config(endpoint_t *qh, uint32_t config, void (*callback)(transfer_t *)) +{ + memset(qh, 0, sizeof(endpoint_t)); + qh->config = config; + qh->next = 1; // Terminate bit = 1 + qh->callback_function = callback; +} + +void usb_config_rx(uint32_t ep, uint32_t packet_size, int do_zlp, void (*cb)(transfer_t *)) +{ + uint32_t config = (packet_size << 16) | (do_zlp ? 0 : (1 << 29)); + if (ep < 2 || ep > NUM_ENDPOINTS) return; + usb_endpoint_config(endpoint_queue_head + ep * 2, config, cb); + if (cb) endpointN_notify_mask |= (1 << ep); +} + +void usb_config_tx(uint32_t ep, uint32_t packet_size, int do_zlp, void (*cb)(transfer_t *)) +{ + uint32_t config = (packet_size << 16) | (do_zlp ? 0 : (1 << 29)); + if (ep < 2 || ep > NUM_ENDPOINTS) return; + usb_endpoint_config(endpoint_queue_head + ep * 2 + 1, config, cb); + if (cb) endpointN_notify_mask |= (1 << (ep + 16)); +} + +void usb_config_rx_iso(uint32_t ep, uint32_t packet_size, int mult, void (*cb)(transfer_t *)) +{ + if (mult < 1 || mult > 3) return; + uint32_t config = (packet_size << 16) | (mult << 30); + if (ep < 2 || ep > NUM_ENDPOINTS) return; + usb_endpoint_config(endpoint_queue_head + ep * 2, config, cb); + if (cb) endpointN_notify_mask |= (1 << ep); +} + +void usb_config_tx_iso(uint32_t ep, uint32_t packet_size, int mult, void (*cb)(transfer_t *)) +{ + if (mult < 1 || mult > 3) return; + uint32_t config = (packet_size << 16) | (mult << 30); + if (ep < 2 || ep > NUM_ENDPOINTS) return; + usb_endpoint_config(endpoint_queue_head + ep * 2 + 1, config, cb); + if (cb) endpointN_notify_mask |= (1 << (ep + 16)); +} + + + +void usb_prepare_transfer(transfer_t *transfer, const void *data, uint32_t len, uint32_t param) +{ + transfer->next = 1; + transfer->status = (len << 16) | (1<<7); + uint32_t addr = (uint32_t)data; + transfer->pointer0 = addr; + transfer->pointer1 = addr + 4096; + transfer->pointer2 = addr + 8192; + transfer->pointer3 = addr + 12288; + transfer->pointer4 = addr + 16384; + transfer->callback_param = param; +} + +#if 0 +void usb_print_transfer_log(void) +{ + uint32_t i, count; + printf("log %d transfers\n", transfer_log_count); + count = transfer_log_count; + if (count > LOG_SIZE) count = LOG_SIZE; + + for (i=0; i < count; i++) { + if (transfer_log_head == 0) transfer_log_head = LOG_SIZE; + transfer_log_head--; + uint32_t log = transfer_log[transfer_log_head]; + printf(" %c %X\n", log >> 8, (int)(log & 255)); + } +} +#endif + +static void schedule_transfer(endpoint_t *endpoint, uint32_t epmask, transfer_t *transfer) +{ + // when we stop at 6, why is the last transfer missing from the USB output? + //if (transfer_log_count >= 6) return; + + //uint32_t ret = (*(const uint8_t *)transfer->pointer0) << 8; + if (endpoint->callback_function) { + transfer->status |= (1<<15); + } + __disable_irq(); + //digitalWriteFast(1, HIGH); + // Executing A Transfer Descriptor, page 2468 (RT1060 manual, Rev 1, 12/2018) + transfer_t *last = endpoint->last_transfer; + if (last) { + last->next = (uint32_t)transfer; + if (USB1_ENDPTPRIME & epmask) goto end; + //digitalWriteFast(2, HIGH); + //ret |= 0x01; + uint32_t status, cyccnt=ARM_DWT_CYCCNT; + do { + USB1_USBCMD |= USB_USBCMD_ATDTW; + status = USB1_ENDPTSTATUS; + } while (!(USB1_USBCMD & USB_USBCMD_ATDTW) && (ARM_DWT_CYCCNT - cyccnt < 2400)); + //USB1_USBCMD &= ~USB_USBCMD_ATDTW; + if (status & epmask) goto end; + //ret |= 0x02; + endpoint->next = (uint32_t)transfer; + endpoint->status = 0; + USB1_ENDPTPRIME |= epmask; + goto end; + } + //digitalWriteFast(4, HIGH); + endpoint->next = (uint32_t)transfer; + endpoint->status = 0; + USB1_ENDPTPRIME |= epmask; + endpoint->first_transfer = transfer; +end: + endpoint->last_transfer = transfer; + __enable_irq(); + //digitalWriteFast(4, LOW); + //digitalWriteFast(3, LOW); + //digitalWriteFast(2, LOW); + //digitalWriteFast(1, LOW); + //if (transfer_log_head > LOG_SIZE) transfer_log_head = 0; + //transfer_log[transfer_log_head++] = ret; + //transfer_log_count++; +} + // ENDPTPRIME - Software should write a one to the corresponding bit when + // posting a new transfer descriptor to an endpoint queue head. + // Hardware automatically uses this bit to begin parsing for a + // new transfer descriptor from the queue head and prepare a + // transmit buffer. Hardware clears this bit when the associated + // endpoint(s) is (are) successfully primed. + // Momentarily set by hardware during hardware re-priming + // operations when a dTD is retired, and the dQH is updated. + + // ENDPTSTATUS - Transmit Buffer Ready - set to one by the hardware as a + // response to receiving a command from a corresponding bit + // in the ENDPTPRIME register. . Buffer ready is cleared by + // USB reset, by the USB DMA system, or through the ENDPTFLUSH + // register. (so 0=buffer ready, 1=buffer primed for transmit) + + // USBCMD.ATDTW - This bit is used as a semaphore to ensure proper addition + // of a new dTD to an active (primed) endpoint's linked list. + // This bit is set and cleared by software. + // This bit would also be cleared by hardware when state machine + // is hazard region for which adding a dTD to a primed endpoint + // may go unrecognized. + +/*struct endpoint_struct { + uint32_t config; + uint32_t current; + uint32_t next; + uint32_t status; + uint32_t pointer0; + uint32_t pointer1; + uint32_t pointer2; + uint32_t pointer3; + uint32_t pointer4; + uint32_t reserved; + uint32_t setup0; + uint32_t setup1; + transfer_t *first_transfer; + transfer_t *last_transfer; + void (*callback_function)(transfer_t *completed_transfer); + uint32_t unused1; +};*/ + +static void run_callbacks(endpoint_t *ep) +{ + //printf("run_callbacks\n"); + transfer_t *first = ep->first_transfer; + if (first == NULL) return; + + // count how many transfers are completed, then remove them from the endpoint's list + uint32_t count = 0; + transfer_t *t = first; + while (1) { + if (t->status & (1<<7)) { + // found a still-active transfer, new list begins here + //printf(" still active\n"); + ep->first_transfer = t; + break; + } + count++; + t = (transfer_t *)t->next; + if ((uint32_t)t == 1) { + // reached end of list, all need callbacks, new list is empty + //printf(" end of list\n"); + ep->first_transfer = NULL; + ep->last_transfer = NULL; + break; + } + } + // do all the callbacks + while (count) { + transfer_t *next = (transfer_t *)first->next; + ep->callback_function(first); + first = next; + count--; + } +} + +void usb_transmit(int endpoint_number, transfer_t *transfer) +{ + if (endpoint_number < 2 || endpoint_number > NUM_ENDPOINTS) return; + endpoint_t *endpoint = endpoint_queue_head + endpoint_number * 2 + 1; + uint32_t mask = 1 << (endpoint_number + 16); + schedule_transfer(endpoint, mask, transfer); +} + +void usb_receive(int endpoint_number, transfer_t *transfer) +{ + if (endpoint_number < 2 || endpoint_number > NUM_ENDPOINTS) return; + endpoint_t *endpoint = endpoint_queue_head + endpoint_number * 2; + uint32_t mask = 1 << endpoint_number; + schedule_transfer(endpoint, mask, transfer); +} + +uint32_t usb_transfer_status(const transfer_t *transfer) +{ +#if defined(MTP_INTERFACE) + uint32_t status, cmd; + //int count=0; + cmd = USB1_USBCMD; + while (1) { + __disable_irq(); + USB1_USBCMD = cmd | USB_USBCMD_ATDTW; + status = transfer->status; + cmd = USB1_USBCMD; + __enable_irq(); + if (cmd & USB_USBCMD_ATDTW) return status; + //if (!(cmd & USB_USBCMD_ATDTW)) continue; + //if (status & 0x80) break; // for still active, only 1 reading needed + //if (++count > 1) break; // for completed, check 10 times + } +#else + return transfer->status; +#endif +} + +#else // defined(NUM_ENDPOINTS) + +void usb_init(void) +{ +} + +#endif // defined(NUM_ENDPOINTS) diff --git a/patched_teensy_core/usb_audio.cpp b/patched_teensy_core/usb_audio.cpp new file mode 100644 index 0000000..8e07f69 --- /dev/null +++ b/patched_teensy_core/usb_audio.cpp @@ -0,0 +1,246 @@ +/* Teensyduino Core Library + * http://www.pjrc.com/teensy/ + * Copyright (c) 2017 PJRC.COM, LLC. + * + * 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: + * + * 1. The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * 2. If the Software is incorporated into a build system that allows + * selection among a list of target devices, then similar target + * devices manufactured by PJRC.COM must be included in the list of + * target devices and selectable in the same manner. + * + * 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 +#include "usb_audio.h" + +#ifdef AUDIO_INTERFACE + + +audio_block_t* AudioInputUSB::rxBuffer[USBAudioInInterface::ringRxBufferSize][USB_AUDIO_MAX_NO_CHANNELS]; +audio_block_t* AudioOutputUSB::txBuffer[USBAudioOutInterface::ringTxBufferSize][USB_AUDIO_MAX_NO_CHANNELS]; + +void AudioInputUSB::update(void){ + int16_t bIdx = -1; + uint16_t noChannels; + _usbInterface.update(bIdx, noChannels); + if(bIdx != -1){ + for (uint16_t i =0; idata[count +i]=*src16Bit++; + } + } +} +#endif + +#if AUDIO_SUBSLOT_SIZE==3 +void AudioInputUSB::copy_to_buffers(const uint8_t *src, uint16_t bIdx, uint16_t noChannels, unsigned int count, unsigned int len) { + for (uint32_t i =0; i< len; i++){ + for (uint16_t j =0; j< noChannels; j++){ + ++src; + rxBuffer[bIdx][j]->data[count +i]=(*src++); + rxBuffer[bIdx][j]->data[count +i] |=(*src++)<<8; + } + } +} +#endif +bool AudioInputUSB::setBlockQuite(uint16_t bIdx, uint16_t channel){ + if(!rxBuffer[bIdx][channel]){ + rxBuffer[bIdx][channel] = AudioStream::allocate(); + } + if(rxBuffer[bIdx][channel]){ + memset(rxBuffer[bIdx][channel]->data, 0, AUDIO_BLOCK_SAMPLES*sizeof(rxBuffer[bIdx][channel]->data[0])); + return true; + } + return false; +} +void AudioInputUSB::releaseBlock(uint16_t bIdx, uint16_t channel){ + if(rxBuffer[bIdx][channel]){ + AudioStream::release(rxBuffer[bIdx][channel]); + rxBuffer[bIdx][channel]=NULL; + } +} +bool AudioInputUSB::allocateBlock(uint16_t bIdx, uint16_t channel){ + if(!rxBuffer[bIdx][channel]){ + rxBuffer[bIdx][channel] = AudioStream::allocate(); + } + return rxBuffer[bIdx][channel] != NULL; +} +bool AudioInputUSB::areBlocksReady(uint16_t bIdx, uint16_t noChannels){ + for (uint16_t i =0; idata, 0, AUDIO_BLOCK_SAMPLES*sizeof(txBuffer[bIdx][i]->data[0])); + } + else { + //we ran out of audio memory + releaseBlocks(bIdx, noChannels); + break; + } + } + } + _usbInterface.incrementBufferIndex(); +} +void AudioOutputUSB::begin(void){ + for (uint16_t i =0; i< USBAudioOutInterface::ringTxBufferSize; i++){ + for (uint16_t j =0; j< USB_AUDIO_MAX_NO_CHANNELS; j++){ + txBuffer[i][j]=NULL; + } + } +} +float AudioOutputUSB::getBufferedSamples() const{ + return _usbInterface.getBufferedSamples(); +} +float AudioOutputUSB::getBufferedSamplesSmooth() const{ + return _usbInterface.getBufferedSamplesSmooth(); +} +float AudioOutputUSB::getActualBIntervalUs() const{ + return _usbInterface.getActualBIntervalUs(); +} +USBAudioOutInterface::Status AudioOutputUSB::getStatus() const{ + return _usbInterface.getStatus(); +} +#if AUDIO_SUBSLOT_SIZE==2 +void AudioOutputUSB::copy_from_buffers(uint8_t *dst, uint16_t bIdx, uint16_t noChannels, unsigned int count, unsigned int len) { + uint16_t* dst16Bit = (uint16_t*)dst; + for (uint32_t i =0; i< len; i++){ + for (uint16_t j =0; j< noChannels; j++){ + *dst16Bit++ =txBuffer[bIdx][j]->data[count +i]; + } + } +} +#endif + +#if AUDIO_SUBSLOT_SIZE==3 +void AudioOutputUSB::copy_from_buffers(uint8_t *dst, uint16_t bIdx, uint16_t noChannels, unsigned int count, unsigned int len) { + for (uint32_t i =0; i< len; i++){ + for (uint16_t j =0; j< noChannels; j++){ + *dst++ =0; + *dst++ =((txBuffer[bIdx][j]->data[count +i])) & 255; + *dst++ =((txBuffer[bIdx][j]->data[count +i]) >> 8) & 255; + } + } +} +#endif + +void AudioOutputUSB::releaseBlocks(uint16_t bIdx, uint16_t noChannels){ + for (uint16_t i =0; i< noChannels; i++){ + if(txBuffer[bIdx][i]){ + AudioStream::release(txBuffer[bIdx][i]); + txBuffer[bIdx][i]=NULL; + } + } +} + +bool AudioOutputUSB::isBlockReady(uint16_t bIdx, uint16_t channel){ + return txBuffer[bIdx][channel]!=NULL; +} + +#endif //AUDIO_INTERFACE \ No newline at end of file diff --git a/patched_teensy_core/usb_audio.h b/patched_teensy_core/usb_audio.h new file mode 100644 index 0000000..60f4102 --- /dev/null +++ b/patched_teensy_core/usb_audio.h @@ -0,0 +1,112 @@ +/* Teensyduino Core Library + * http://www.pjrc.com/teensy/ + * Copyright (c) 2017 PJRC.COM, LLC. + * + * 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: + * + * 1. The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * 2. If the Software is incorporated into a build system that allows + * selection among a list of target devices, then similar target + * devices manufactured by PJRC.COM must be included in the list of + * target devices and selectable in the same manner. + * + * 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. + */ + +#pragma once + +#include "usb_audio_interface.h" + +#ifdef AUDIO_INTERFACE + +#if defined(__cplusplus) +#include "AudioStream.h" + +class AudioInputUSB : public AudioStream +{ +public: + + AudioInputUSB(float kp =400.f,float ki =.2f ); + virtual void update(void); + void begin(void); + float getBufferedSamples() const; + float getBufferedSamplesSmooth() const; + float getRequestedSamplingFrequ() const; + float getActualBIntervalUs() const; + USBAudioInInterface::Status getStatus() const; + float volume(void); + +private: + static void copy_to_buffers(const uint8_t *src, uint16_t bIdx, uint16_t noChannels, unsigned int count, unsigned int len); + static bool setBlockQuite(uint16_t bIdx, uint16_t channel); + static void releaseBlock(uint16_t bIdx, uint16_t channel); + static bool allocateBlock(uint16_t bIdx, uint16_t channel); + static bool areBlocksReady(uint16_t bIdx, uint16_t noChannels); + + static audio_block_t* rxBuffer[USBAudioInInterface::ringRxBufferSize][USB_AUDIO_MAX_NO_CHANNELS]; + USBAudioInInterface _usbInterface; +}; + + +#if USB_AUDIO_NO_CHANNELS_480 >= 4 +class AudioInputUSBQuad : public AudioInputUSB { public: AudioInputUSBQuad(float kp =400.f,float ki =.2f) : AudioInputUSB(kp, ki) {} }; +#if USB_AUDIO_NO_CHANNELS_480 >= 6 +class AudioInputUSBHex : public AudioInputUSB { public: AudioInputUSBHex(float kp =400.f,float ki =.2f) : AudioInputUSB(kp, ki) {} }; +#if USB_AUDIO_NO_CHANNELS_480 >= 8 +class AudioInputUSBOct : public AudioInputUSB { public: AudioInputUSBOct(float kp =400.f,float ki =.2f) : AudioInputUSB(kp, ki) {} }; +#endif // USB_AUDIO_NO_CHANNELS_480 >= 8 +#endif // USB_AUDIO_NO_CHANNELS_480 >= 6 +#endif // USB_AUDIO_NO_CHANNELS_480 >= 4 + + +class AudioOutputUSB : public AudioStream +{ +public: + AudioOutputUSB(void); + AudioOutputUSB(int nch); + virtual void update(void); + void begin(void); + + float getBufferedSamples() const; + float getBufferedSamplesSmooth() const; + float getActualBIntervalUs() const; + USBAudioOutInterface::Status getStatus() const; + +private: + + static void copy_from_buffers(uint8_t *dst, uint16_t bIdx, uint16_t noChannels, unsigned int count, unsigned int len); + static void releaseBlocks(uint16_t bIdx, uint16_t noChannels); + static bool isBlockReady(uint16_t bIdx, uint16_t channel); + + static audio_block_t* txBuffer[USBAudioOutInterface::ringTxBufferSize][USB_AUDIO_MAX_NO_CHANNELS]; + audio_block_t *inputQueueArray[USB_AUDIO_MAX_NO_CHANNELS]; + USBAudioOutInterface _usbInterface; +}; + +#if USB_AUDIO_NO_CHANNELS_480 >= 4 +class AudioOutputUSBQuad : public AudioOutputUSB { public: AudioOutputUSBQuad(void) : AudioOutputUSB(4) {} }; +#if USB_AUDIO_NO_CHANNELS_480 >= 6 +class AudioOutputUSBHex : public AudioOutputUSB { public: AudioOutputUSBHex(void) : AudioOutputUSB(6) {} }; +#if USB_AUDIO_NO_CHANNELS_480 >= 8 +class AudioOutputUSBOct : public AudioOutputUSB { public: AudioOutputUSBOct(void) : AudioOutputUSB(8) {} }; +#endif // USB_AUDIO_NO_CHANNELS_480 >= 8 +#endif // USB_AUDIO_NO_CHANNELS_480 >= 6 +#endif // USB_AUDIO_NO_CHANNELS_480 >= 4 + +#endif // defined(__cplusplus) +#endif // AUDIO_INTERFACE diff --git a/patched_teensy_core/usb_audio_interface.cpp b/patched_teensy_core/usb_audio_interface.cpp new file mode 100644 index 0000000..53b8ce6 --- /dev/null +++ b/patched_teensy_core/usb_audio_interface.cpp @@ -0,0 +1,1081 @@ +/* Teensyduino Core Library + * http://www.pjrc.com/teensy/ + * Copyright (c) 2017 PJRC.COM, LLC. + * + * 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: + * + * 1. The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * 2. If the Software is incorporated into a build system that allows + * selection among a list of target devices, then similar target + * devices manufactured by PJRC.COM must be included in the list of + * target devices and selectable in the same manner. + * + * 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 +#include "usb_dev.h" +#include "usb_audio_interface.h" +#include "debug/printf.h" +#include + +#ifdef AUDIO_INTERFACE + +// debug counters (volatile extern for sketch access) +volatile uint32_t tx_callback_count = 0; +volatile uint32_t tx_underrun_count = 0; + +namespace { + + //variables used by USBAudioInInterface and AudioOutputUSB ================== + #if (USB_AUDIO_NO_CHANNELS_12*AUDIO_NUM_SUBFRAMES_PER_POLLING_12) < (USB_AUDIO_NO_CHANNELS_480*AUDIO_NUM_SUBFRAMES_PER_POLLING_480) + #define AUDIO_TX_SIZE AUDIO_TX_SIZE_480 + #define AUDIO_RX_SIZE AUDIO_RX_SIZE_480 + #else + #define AUDIO_TX_SIZE AUDIO_TX_SIZE_12 + #define AUDIO_RX_SIZE AUDIO_RX_SIZE_12 + #endif + + uint16_t noTransmittedChannels=0; //depending if usb_high_speed is true this is either USB_AUDIO_NO_CHANNELS_480 or USB_AUDIO_NO_CHANNELS_12 + uint32_t maxRxTxSamples = 0; + float audioPollingIntervalSec=0; + uint16_t audioPollingIntervaluS =0; + uint32_t noSamplesPerPollingInterval=0; + constexpr uint32_t samplingRate=uint32_t(AUDIO_SAMPLE_RATE); + float expectedIsrIntervalCycles; + + //====================================================================== + + //variables used by USBAudioInInterface ================================ + LastCall<7> lastCallReceiveIsr; + float sumDiff=0.; + float lastDiff=0.; + + constexpr float targetNumRxBufferedSamples = TARGET_RX_BUFFER_TIME_S*AUDIO_SAMPLE_RATE; + bool rxBufferReady= false; //used to indicate if the reset after e.g. a buffer overrun is completed + volatile bool rxBufferOverrun = false; //changed in usb_audio_receive_callback + volatile uint32_t rxMemoryUnderrunCounter=0; //changed in usb_audio_receive_callback + volatile uint16_t incoming_rx_bIdx=0; //changed in usb_audio_receive_callback + volatile uint16_t rxIncoming_count=0; //changed in usb_audio_receive_callback + volatile uint8_t receive_flag; //changed in usb_audio_receive_callback + + uint16_t transmit_rx_bIdx=0; + uint32_t feedback_accumulator; + uint32_t feedback_accumulator_default; + + volatile uint32_t rxUsb_audio_underrun_count; //changed in update + volatile uint32_t rxUsb_audio_overrun_count; //changed in update + + float getNumBufferedRxSamples(uint16_t incomingIdx, uint16_t transmitIdx, uint16_t incomingCount){ + float bufferedSamples= incomingCount; + if(incomingIdx > transmitIdx+1 ){ + bufferedSamples+=(incomingIdx-(transmitIdx+1))*AUDIO_BLOCK_SAMPLES; + } + else if(incomingIdx < transmitIdx){ + bufferedSamples += incomingIdx*AUDIO_BLOCK_SAMPLES; + if(transmitIdx +1 < USBAudioInInterface::ringRxBufferSize){ + bufferedSamples += (USBAudioInInterface::ringRxBufferSize-(transmitIdx +1))*AUDIO_BLOCK_SAMPLES; + } + } + return bufferedSamples; + } + //======================================================================== +} + +struct usb_audio_features_struct USBAudioInInterface::features = {0,0,FEATURE_MAX_VOLUME/2}; +USBAudioInInterface::SetBlockQuite USBAudioInInterface::setBlockQuite; +USBAudioInInterface::ReleaseBlock USBAudioInInterface::releaseBlock; +USBAudioInInterface::AllocateBlock USBAudioInInterface::allocateBlock; +USBAudioInInterface::AreBlocksReady USBAudioInInterface::areBlocksReady; +USBAudioInInterface::Copy_to_buffers USBAudioInInterface::copy_to_buffers; + +extern volatile uint8_t usb_high_speed; +bool USBAudioInInterface::running=false; +static void rx_event(transfer_t *t); +static void tx_event(transfer_t *t); + +/*static*/ transfer_t rx_transfer __attribute__ ((used, aligned(32))); +/*static*/ transfer_t sync_transfer __attribute__ ((used, aligned(32))); +/*static*/ transfer_t tx_transfer __attribute__ ((used, aligned(32))); +DMAMEM static uint8_t rx_buffer[AUDIO_RX_SIZE] __attribute__ ((aligned(32))); +DMAMEM uint32_t usb_audio_sync_feedback __attribute__ ((aligned(32))); + +uint8_t usb_audio_receive_setting=0; //set in usb.c +uint8_t usb_audio_transmit_setting=0; //set in usb.c +uint8_t usb_audio_sync_nbytes; +uint8_t usb_audio_sync_rshift; + + +static void rx_event(transfer_t *t) +{ + if (t) { + int len = AUDIO_RX_SIZE - ((rx_transfer.status >> 16) & 0x7FFF); + //printf("rx %u\n", len); + usb_audio_receive_callback(len); + } + usb_prepare_transfer(&rx_transfer, rx_buffer, AUDIO_RX_SIZE, 0); + arm_dcache_delete(&rx_buffer, AUDIO_RX_SIZE); + usb_receive(AUDIO_RX_ENDPOINT, &rx_transfer); +} + +static void sync_event(transfer_t *t) +{ + if(feedback_accumulator > maxRxTxSamples*0x1000000){ + // Serial.print("usb_audio_interface: sync_event: Exceeded maximum number of requeseted Samples. Requested: "); + // Serial.print(((double)feedback_accumulator)/0x1000000,8); + // Serial.print(", buffer size: "); + // Serial.print(maxRxTxSamples); + //maximum amount + feedback_accumulator =maxRxTxSamples * 0x1000000; + } + // USB 2.0 Specification, 5.12.4.2 Feedback, pages 73-75 + //printf("sync %x\n", sync_transfer.status); // too slow, can't print this much + usb_audio_sync_feedback = feedback_accumulator >> usb_audio_sync_rshift; + usb_prepare_transfer(&sync_transfer, &usb_audio_sync_feedback, usb_audio_sync_nbytes, 0); + arm_dcache_flush(&usb_audio_sync_feedback, usb_audio_sync_nbytes); + usb_transmit(AUDIO_SYNC_ENDPOINT, &sync_transfer); +} + +USBAudioInInterface::Status USBAudioInInterface::getStatus() const{ + USBAudioInInterface::Status status; + NVIC_DISABLE_IRQ(IRQ_SOFTWARE); + status.usb_audio_underrun_count = rxUsb_audio_underrun_count; + status.usb_audio_overrun_count = rxUsb_audio_overrun_count; + status.audio_memory_underrun_count = rxMemoryUnderrunCounter; + status.target_num_buffered_samples = targetNumRxBufferedSamples; + status.num_transmitted_channels = noTransmittedChannels; + status.ring_buffer_size = ringRxBufferSize; + status.usb_rx_tx_buffer_size = AUDIO_RX_SIZE; + status.receivingData=_streaming; + status.bInterval_uS = audioPollingIntervaluS; + status.usb_high_speed = usb_high_speed; + NVIC_ENABLE_IRQ(IRQ_SOFTWARE); + return status; +} + +void USBAudioInInterface::releaseBlocks(uint16_t bufferIdx){ + for (uint16_t j =0; j< noTransmittedChannels; j++){ + USBAudioInInterface::releaseBlock(bufferIdx, j); + } +} + +bool USBAudioInInterface::setBlocksQuite(uint32_t noBlocks){ + bool allocationSuccessful=true; + for (uint16_t i =0; i< USBAudioInInterface::ringRxBufferSize; i++){ + for (uint16_t j =0; j< noTransmittedChannels; j++){ + if(i < noBlocks){ + if (!USBAudioInInterface::setBlockQuite(i, j)){ + //allocation failed + allocationSuccessful=false; + break; + } + } + else { + USBAudioInInterface::releaseBlock(i,j); + } + } + } + if(!allocationSuccessful){ + for (uint16_t i =0; i< USBAudioInInterface::ringRxBufferSize; i++){ + releaseBlocks(i); + } + } + return allocationSuccessful; +} + +bool USBAudioInInterface::allocateChannels(uint16_t idx){ + for (uint16_t i =0; i< noTransmittedChannels; i++){ + if(!USBAudioInInterface::allocateBlock(idx,i)){ + //allocation failed -> release all blocks that were allocated + releaseBlocks(idx); + return false; + } + } + return true; +} + +USBAudioInInterface::USBAudioInInterface( + SetBlockQuite sbq, + ReleaseBlock rb, + AllocateBlock ab, + AreBlocksReady abr, + Copy_to_buffers c_t_b, + float kp, + float ki) + : _kp(kp), _ki(ki) +{ + USBAudioInInterface::setBlockQuite =sbq; + USBAudioInInterface::releaseBlock = rb; + USBAudioInInterface::allocateBlock = ab; + USBAudioInInterface::areBlocksReady = abr; + USBAudioInInterface::copy_to_buffers = c_t_b; +} + + +void USBAudioInInterface::begin(){ + __disable_irq(); + if(USBAudioInInterface::running){ + __enable_irq(); + return; + } + USBAudioInInterface::running=true; + _bufferedSamplesSmooth=0; + _bufferedSamples=0; + rxIncoming_count = 0; + receive_flag = 0; + _streaming=false; + _lastCallUpdate.reset(blockDuration*F_CPU_ACTUAL); + __enable_irq(); + +} + + +float USBAudioInInterface::getActualBIntervalUs() const { + float toUS =1000000.f/F_CPU_ACTUAL; +__disable_irq(); + float bInterval= (float)lastCallReceiveIsr.getLastDuration()*toUS; +__enable_irq(); + return bInterval; +} + + +void USBAudioInInterface::stop(){ + __disable_irq(); + if(!USBAudioInInterface::running){ + __enable_irq(); + return; + } + USBAudioInInterface::running=false; + _bufferedSamplesSmooth=0; + _bufferedSamples=0; + __enable_irq(); +} + + +void USBAudioInInterface::tryIncreaseIdxIncoming(uint16_t& count){ + uint16_t idx = (incoming_rx_bIdx+1)%ringRxBufferSize; + if(idx == transmit_rx_bIdx){ + return; + } + incoming_rx_bIdx=idx; + if(!USBAudioInInterface::allocateChannels(incoming_rx_bIdx)){ + rxMemoryUnderrunCounter++; + return; + } + count =0; +} + +// Called from the USB interrupt when an isochronous packet arrives +// we must completely remove it from the receive buffer before returning +// +#if 1 +void usb_audio_receive_callback(unsigned int len) +{ + if(!USBAudioInInterface::running){ + return; + } + uint32_t t = ARM_DWT_CYCCNT; + lastCallReceiveIsr.addCall(t); + + // Over-estimate of audio update() calls before the callback fires again: + receive_flag = (audioPollingIntervaluS * samplingRate / AUDIO_BLOCK_SAMPLES / 1'000'000) + 1; + + if(!rxBufferReady){ + return; + } + + len /= (AUDIO_SUBSLOT_SIZE* noTransmittedChannels); // 1 sample = AUDIO_SUBSLOT_SIZE bytes times noTransmittedChannels channels + uint16_t count = rxIncoming_count; + const uint8_t *data = rx_buffer; + while (len > 0){ + if(count == AUDIO_BLOCK_SAMPLES){ + //we filled the blocks -> try to increase the ring buffer index and allocate new blocks + USBAudioInInterface::tryIncreaseIdxIncoming(count); + } + if(count == AUDIO_BLOCK_SAMPLES){ + //we were not able to increase the buffer index in the ring index + rxBufferOverrun=true; + rxIncoming_count=count; + return; + } + uint32_t avail = AUDIO_BLOCK_SAMPLES - count; + uint32_t numToCopy = min(avail, len); + USBAudioInInterface::copy_to_buffers(data, incoming_rx_bIdx, noTransmittedChannels, count, numToCopy); + data+=noTransmittedChannels*numToCopy *AUDIO_SUBSLOT_SIZE; + count+=numToCopy; + len-=numToCopy; + } + if(count == AUDIO_BLOCK_SAMPLES){ + //maybe we can already provide the current incoming_rx_bIdx block and prevent a buffer underflow + //if tryIncreaseIdxIncoming fails now, there is still the chance that it will succeed at the next ISR call + USBAudioInInterface::tryIncreaseIdxIncoming(count); + } + rxIncoming_count=count; +} +#endif + +float USBAudioInInterface::getBufferedSamples() const{ + NVIC_DISABLE_IRQ(IRQ_SOFTWARE); + float b = _bufferedSamples; + NVIC_ENABLE_IRQ(IRQ_SOFTWARE); + return b; +} +float USBAudioInInterface::getBufferedSamplesSmooth() const{ + NVIC_DISABLE_IRQ(IRQ_SOFTWARE); + float b = _bufferedSamplesSmooth; + NVIC_ENABLE_IRQ(IRQ_SOFTWARE); + return b; +} +float USBAudioInInterface::getRequestedSamplingFrequ() const{ + NVIC_DISABLE_IRQ(IRQ_SOFTWARE); + float fs = feedback_accumulator/(audioPollingIntervalSec*0x1000000); + NVIC_ENABLE_IRQ(IRQ_SOFTWARE); + return fs; +} +bool USBAudioInInterface::isBufferReady() { + if(transmit_rx_bIdx == incoming_rx_bIdx){ + return false; + } + return USBAudioInInterface::areBlocksReady(transmit_rx_bIdx, noTransmittedChannels); +} +bool USBAudioInInterface::resetBuffer(double updateCurrentSmooth){ + //resetBuffer should be called from the update function + //Here we prepare the buffer for the transmission after a buffer under- or overflow or at the beginning of a stream + + //first we estimate when the last USB samples arrived + double timeSinceLastUSBPaket=0.; + if(lastCallReceiveIsr.isHistoryValid()){ + double lastIsrSmooth = lastCallReceiveIsr.getLastCall<2>(); + timeSinceLastUSBPaket = toInt32Range(updateCurrentSmooth - lastIsrSmooth); + timeSinceLastUSBPaket /= F_CPU_ACTUAL; //to seconds + if(timeSinceLastUSBPaket > 1.5f*audioPollingIntervalSec || timeSinceLastUSBPaket < -0.5f){ + //normally this should not happen + timeSinceLastUSBPaket = 0.5f*blockDuration; + } + } + else { + timeSinceLastUSBPaket = expectedIsrIntervalCycles*0.5/F_CPU_ACTUAL; + } + //===================================================== + + //now we set incoming_rx_bIdx, transmit_rx_bIdx and incoming_count + float resetTimeSec = TARGET_RX_BUFFER_TIME_S+blockDuration-timeSinceLastUSBPaket; //+blockDuration because we want to transmit one block after this function + uint32_t resetSamples = uint32_t(resetTimeSec*AUDIO_SAMPLE_RATE); + uint32_t noBufferedBlocks = resetSamples/AUDIO_BLOCK_SAMPLES; + uint32_t count = resetSamples - noBufferedBlocks *AUDIO_BLOCK_SAMPLES; + uint32_t bufferIdx = noBufferedBlocks; + + //We need to set incoming_count to back to the target value. + //Otherwise we will nearly have an overrun at the next update call. + //This would make it very difficult for the controller to work + rxIncoming_count = count; + incoming_rx_bIdx = uint16_t(bufferIdx); + transmit_rx_bIdx =0; + return USBAudioInInterface::allocateChannels(incoming_rx_bIdx); +} + +void USBAudioInInterface::incrementBufferIndex(){ + __disable_irq(); + transmit_rx_bIdx = (transmit_rx_bIdx+1)%ringRxBufferSize; //it is ok if transmit_rx_bIdx==incoming_rx_bIdx here + __enable_irq(); +} +void USBAudioInInterface::update(int16_t& bIdx, uint16_t& noChannels) +{ + bIdx = -1; // -1 means that there is nothing to transmit + noChannels =noTransmittedChannels; + + if(!USBAudioInInterface::running){ + return; + } + //update time measurement of update calls + uint32_t clockCount = ARM_DWT_CYCCNT; + _lastCallUpdate.addCall(clockCount); + double updateCurrentSmooth= _lastCallUpdate.getLastCall<20>(); + //======================================= + + //get all information related to the USB receive ISR + __disable_irq(); + History<7> historyIsr = lastCallReceiveIsr.getHistory(); + bool bufferUnderflow = !isBufferReady(); + rxBufferReady = !bufferUnderflow && !rxBufferOverrun; + if(rxBufferOverrun){ + rxUsb_audio_overrun_count++; + } + uint16_t ic = rxIncoming_count; + uint16_t iIdx = incoming_rx_bIdx; + // Check if USB receive callback is firing correctly. + // Should never reach 0 if all is well. + uint8_t f = receive_flag; + if (receive_flag) receive_flag--; + __enable_irq(); + //======================================= + if(_streaming && !f){ + //the stream just stopped -> reset + _streaming=false; + __disable_irq(); + lastCallReceiveIsr.resetHistory(); + __enable_irq(); + sumDiff = 0.; + feedback_accumulator = feedback_accumulator_default; + } + + if ((_streaming && !rxBufferReady) || //we are already streaming but encounter a buffer over- or underrun + (!_streaming && f)) //we just start streaming -> we reset the buffer (fill it with the targeted number of samples) + { + _streaming = true; //this is maybe the start of a stream + if(bufferUnderflow && f){ + //we count under-runs only if we currently receive audio data from the host (f!=0) + rxUsb_audio_underrun_count++; + } + float resetTimeSec = TARGET_RX_BUFFER_TIME_S+blockDuration; //+blockDuration because we will transmit one block after this function and want TARGET_RX_BUFFER_TIME_S after this transmission + uint32_t resetSamples = uint32_t(resetTimeSec*AUDIO_SAMPLE_RATE); + uint32_t noBufferedBlocks = resetSamples/AUDIO_BLOCK_SAMPLES +1; + if(setBlocksQuite(noBufferedBlocks)){ + __disable_irq(); + //this must all happen in one un-interrupted block + rxBufferReady=resetBuffer(updateCurrentSmooth); + rxBufferOverrun=false; + bufferUnderflow=false; + ic = rxIncoming_count; + iIdx = incoming_rx_bIdx; + __enable_irq(); + } + else { + //can only happen if we run out of audio blocks/memory + _streaming=false; + rxMemoryUnderrunCounter++; + } + } + //======================================= + + // If there was a receive event, we update the feedback for the USB host + // Important: first compute the buffered samples before the block transmission and update of transmit_rx_bIdx below!! + if (_streaming) { + //we compute the mismatch of the the targeted number of buffered samples and the actual buffered samples + float lastIsrSmooth = (float)lastCallReceiveIsr.getLastCall<2>(historyIsr); + float timeSinceLastIsr = (float)toInt32Range(updateCurrentSmooth - lastIsrSmooth); + timeSinceLastIsr /= F_CPU_ACTUAL; //to seconds + + _bufferedSamples= getNumBufferedRxSamples(iIdx, transmit_rx_bIdx, ic); + _bufferedSamplesSmooth=_bufferedSamples + timeSinceLastIsr * AUDIO_SAMPLE_RATE; + + float diff= targetNumRxBufferedSamples -_bufferedSamplesSmooth; + if (abs(lastDiff) <= abs(diff)){ + //we only add the current diff to the sum, if we the difference is not already decreasing + sumDiff +=diff; + } + lastDiff = diff; + feedback_accumulator = uint32_t(feedback_accumulator_default + double(_kp*diff) + double(_ki*sumDiff) +0.5); + //======================================================================================================== + + //if buffer is ready, we transmit all channels and increase the transmit index in the ring buffer (indicated by bIdx!=-1) + //then transmit_rx_bIdx needs to be increased (incrementBufferIndex gets called from outside after the blocks at ring buffer index transmit_rx_bIdx were transmitted and released.) + bIdx = (int16_t)transmit_rx_bIdx; + //========================================= + } + +} + + + + + + + + + + + + + + + + + +#if 1 + +namespace { + + + uint16_t incoming_tx_bIdx=0; + volatile uint16_t transmit_tx_bIdx=0; //changed in usb_audio_transmit_callback + volatile USBAudioOutInterface::BufferState txBufferState = USBAudioOutInterface::ready; //0: buffer can be used, 1: buffer is full, 2: buffer overrun changed in usb_audio_transmit_callback + volatile float bufferedTxSamples=0.f; //changed in usb_audio_transmit_callback + volatile float bufferedTxSamplesSmooth=0.f; //changed in usb_audio_transmit_callback + volatile uint8_t transmit_flag; //changed in usb_audio_transmit_callback + volatile bool streamStart=true; //changed in usb_audio_transmit_callback + + volatile uint32_t txUsb_audio_underrun_count =0; + volatile uint32_t txUsb_audio_overrun_count =0; + + volatile uint32_t num_skipped_Samples=0; + volatile uint32_t num_padded_Samples=0; + volatile uint32_t num_send_one_less=0; + volatile uint32_t num_send_one_more=0; + + + LastCall<7> lastCallTransmitIsr; + constexpr float targetNumTxBufferedSamples = TARGET_TX_BUFFER_TIME_S*AUDIO_SAMPLE_RATE; + + float getNumBufferedTxSamples(USBAudioOutInterface::BufferState txBufferState, uint32_t target, uint16_t incomingIdx, uint16_t transmitIdx, uint16_t outgoingCount){ + float bufferedSamples= AUDIO_BLOCK_SAMPLES-outgoingCount; + if(txBufferState > USBAudioOutInterface::ready){ + //buffer full or overrund + bufferedSamples +=(AUDIO_BLOCK_SAMPLES *(USBAudioOutInterface::ringTxBufferSize-1)); + return bufferedSamples -target; //-target because it's assumed that target number of samples will be transmitted after this function call + } + if(incomingIdx > transmitIdx+1 ){ + bufferedSamples+=(incomingIdx-(transmitIdx+1))*AUDIO_BLOCK_SAMPLES; + } + else if(incomingIdx < transmitIdx){ + bufferedSamples += incomingIdx*AUDIO_BLOCK_SAMPLES; + if(transmitIdx +1 < USBAudioOutInterface::ringTxBufferSize){ + bufferedSamples += (USBAudioOutInterface::ringTxBufferSize-(transmitIdx +1))*AUDIO_BLOCK_SAMPLES; + } + } + return bufferedSamples-target; //-target because it's assumed that target number of samples will be transmitted after this function call + } + + void updateDevCounter(float bufferDiff, uint32_t& devCounter, int8_t& sign){ + const float thrs =2.f; //acts as hysteresis, small deviations of targeted number of samples in the buffer are ignored + if (bufferDiff > thrs){ + if(sign != 1){ + devCounter=1; + sign=1; + } + else { + devCounter++; + } + } + else if (bufferDiff < -thrs){ + if(sign != -1){ + devCounter=1; + sign =-1; + } + else { + devCounter++; + } + } + } + + void updateTarget(int8_t sign, uint32_t& devCounter, uint32_t& target){ + if(sign == -1){ + devCounter=0; + num_send_one_less++; + //we run out of samples -> slow transmission down + target--; + } + else if(sign ==1 && target < maxRxTxSamples){ + devCounter=0; + num_send_one_more++; + //we run out of buffer space -> speed transmission down + target++; + } + } +#ifndef ASYNC_TX_ENDPOINT + void updateBufferOffset(int8_t sign, uint32_t& devCounter, uint16_t& offset){ + if(sign == -1 && offset > 0){ + devCounter=0; + num_padded_Samples++; + //one sample is transmitted twice + offset--; + } + else if(sign ==1 && offset < AUDIO_BLOCK_SAMPLES){ + devCounter=0; + num_skipped_Samples++; + //we skip one sample in the buffer + offset++; + } + } +#endif + uint32_t getTransmissionTarget(){ + // Depending on the sampling frequency and the bInterval, we compute the number of samples that need to be transmitted. + // + // At 44.1 kHz and 1ms bInterval this function returns 9 times 44 samples and then 45 samples; + // at e.g. 188.4 kHz and 1ms bInterval the output is more 'complex': + // three times 188, once 189, two times 188, once 189,... cycle starts again + // + // This function does not take into account the current number of buffered samples, i.e. it does not change the target + // number of samples in order to prevent buffer over- and under-runs. + // It's just a version of the Bresenham-Algorithm + + const uint32_t N = samplingRate * audioPollingIntervaluS; + const uint32_t denominator = 1'000'000; + + //target is first set to the default number of samples we send per frame. + //e.g. if samplingRate=44100 and audioPollingIntervaluS=1000 then base = 44 + uint32_t target = N / denominator; + + //rem is the reminder of the devision 'N / denominator'. It represents the fractional samples (but multiplied by denominator). + uint32_t rem = N % denominator; + + //we accumulate the 'fractional' samples by means of the errorAccumulator. 0 <=errorAccumulator < denominator + static uint32_t errorAccumulator = 0; + errorAccumulator+=rem; + + if (errorAccumulator >= denominator && target < maxRxTxSamples) + { + errorAccumulator -= denominator; + target += 1; + } + + return target; + } + + void resetTransmissionIndex(float virtualSamples, uint16_t incomingIdx, uint16_t& idx, uint16_t& count, uint16_t numSentSamples){ + uint16_t targetNoSamples =uint16_t(targetNumTxBufferedSamples+numSentSamples-virtualSamples + 0.f); //+numSentSamples because we will immediatelly transmit 'numSentSamples' samples + uint16_t targetNumTxBufferedBlocks = uint16_t(targetNoSamples/AUDIO_BLOCK_SAMPLES); + count = AUDIO_BLOCK_SAMPLES-(targetNoSamples-targetNumTxBufferedBlocks*AUDIO_BLOCK_SAMPLES); + idx = (incomingIdx -(targetNumTxBufferedBlocks+1)+USBAudioOutInterface::ringTxBufferSize)%USBAudioOutInterface::ringTxBufferSize; + } + + void resetStatusCounter(){ + num_skipped_Samples=0; + num_padded_Samples=0; + num_send_one_less=0; + num_send_one_more=0; + txUsb_audio_underrun_count=0; + txUsb_audio_overrun_count=0; + } +} +double USBAudioOutInterface::updateCurrentSmooth=-1.; +uint16_t USBAudioOutInterface::outgoing_count; +bool USBAudioOutInterface::running=false; + +USBAudioOutInterface::ReleaseBlocks USBAudioOutInterface::releaseBlocks; +USBAudioOutInterface::IsBlockReady USBAudioOutInterface::isBlockReady; +USBAudioOutInterface::Copy_from_buffers USBAudioOutInterface::copy_from_buffer; + +/*DMAMEM*/ uint8_t usb_audio_transmit_buffer[AUDIO_TX_SIZE] __attribute__ ((used, aligned(32))); + +static void tx_event(transfer_t *t) +{ + int len = usb_audio_transmit_callback(); + usb_audio_sync_feedback = feedback_accumulator >> usb_audio_sync_rshift; + usb_prepare_transfer(&tx_transfer, usb_audio_transmit_buffer, len, 0); + arm_dcache_flush_delete(usb_audio_transmit_buffer, len); + usb_transmit(AUDIO_TX_ENDPOINT, &tx_transfer); +} +USBAudioOutInterface::USBAudioOutInterface(ReleaseBlocks rbs, IsBlockReady ibr, Copy_from_buffers c_f_b) { + USBAudioOutInterface::releaseBlocks=rbs; + USBAudioOutInterface::isBlockReady=ibr; + USBAudioOutInterface::copy_from_buffer = c_f_b; +} +float USBAudioOutInterface::getActualBIntervalUs() const { + float toUS =1000000.f/F_CPU_ACTUAL; + NVIC_DISABLE_IRQ(IRQ_SOFTWARE); + float bInterval= (float)lastCallTransmitIsr.getLastDuration()*toUS; + NVIC_ENABLE_IRQ(IRQ_SOFTWARE); + return bInterval; +} +void USBAudioOutInterface::begin(){ + __disable_irq(); + if(USBAudioOutInterface::running){ + __enable_irq(); + return; + } + outgoing_count = 0; + _lastCallUpdate.reset(blockDuration*F_CPU_ACTUAL); + USBAudioOutInterface::running=true; + bufferedTxSamplesSmooth=0; + bufferedTxSamples=0; + __enable_irq(); + +} +void USBAudioOutInterface::stop(){ + __disable_irq(); + if(!USBAudioOutInterface::running){ + __enable_irq(); + return; + } + USBAudioOutInterface::running=false; + bufferedTxSamplesSmooth=0; + bufferedTxSamples=0; + __enable_irq(); +} + +USBAudioOutInterface::Status USBAudioOutInterface::getStatus() const{ + USBAudioOutInterface::Status status; + NVIC_DISABLE_IRQ(IRQ_SOFTWARE); + status.usb_audio_underrun_count = txUsb_audio_underrun_count; + status.usb_audio_overrun_count = txUsb_audio_overrun_count; + status.target_num_buffered_samples = targetNumTxBufferedSamples; + status.num_transmitted_channels = noTransmittedChannels; + status.ring_buffer_size = ringTxBufferSize; + status.usb_rx_tx_buffer_size = AUDIO_TX_SIZE; + status.transmittingData=_streaming; + status.bInterval_uS = audioPollingIntervaluS; + status.num_skipped_Samples = num_skipped_Samples; + status.num_padded_Samples = num_padded_Samples; + status.num_send_one_less = num_send_one_less; + status.num_send_one_more = num_send_one_more; + status.usb_high_speed = usb_high_speed; + NVIC_ENABLE_IRQ(IRQ_SOFTWARE); + return status; +} + +void USBAudioOutInterface::update(int16_t& bIdx, uint16_t& noChannels) +{ + noChannels=noTransmittedChannels; + if(!running){ + bIdx = -1; + return; + } + //update time measurement of update calls + uint32_t t = ARM_DWT_CYCCNT; + _lastCallUpdate.addCall(t); + _updateCurrentSmoothPending= _lastCallUpdate.getLastCall<20>(); + //======================================= + + __disable_irq(); + streamStart=transmit_flag && !_streaming; + _streaming=transmit_flag != 0; + if (transmit_flag) transmit_flag--; + if(txBufferState < overrun && incoming_tx_bIdx == transmit_tx_bIdx){ + txBufferState=overrun; + } + if(txBufferState == overrun){ + //only set in case of buffer overrun. Normally updateCurrentSmooth should only be set in combination with the incoming_tx_bIdx incrementation + updateCurrentSmooth= _updateCurrentSmoothPending; + } + BufferState s = txBufferState; + __enable_irq(); + if(!_streaming){ + resetStatusCounter(); + bufferedTxSamplesSmooth=0.f; + bufferedTxSamples=0.f; + } + if(s == overrun){ + txUsb_audio_overrun_count++; + } + bIdx=incoming_tx_bIdx; +} + +void USBAudioOutInterface::incrementBufferIndex(){ + __disable_irq(); + updateCurrentSmooth= _updateCurrentSmoothPending; + incoming_tx_bIdx=(incoming_tx_bIdx+1)%ringTxBufferSize; + if(txBufferState < full && incoming_tx_bIdx == transmit_tx_bIdx){ + txBufferState = full; + } + __enable_irq(); +} +void USBAudioOutInterface::tryIncreaseIdxTransmission(uint16_t& tBIdx, uint16_t& offset){ + while (offset >= AUDIO_BLOCK_SAMPLES){ + USBAudioOutInterface::releaseBlocks(tBIdx, noTransmittedChannels); + uint16_t idxCandidate =(tBIdx+1)%ringTxBufferSize; + if(idxCandidate != incoming_tx_bIdx){ + tBIdx = idxCandidate; + offset -=AUDIO_BLOCK_SAMPLES; + } + else { + offset = AUDIO_BLOCK_SAMPLES; //we make sure that offset is never larger than AUDIO_BLOCK_SAMPLES! + break; + } + } +} +float USBAudioOutInterface::getBufferedSamples() const{ + NVIC_DISABLE_IRQ(IRQ_SOFTWARE); + float b = bufferedTxSamples; + NVIC_ENABLE_IRQ(IRQ_SOFTWARE); + return b; +} +float USBAudioOutInterface::getBufferedSamplesSmooth() const{ + NVIC_DISABLE_IRQ(IRQ_SOFTWARE); + float b = bufferedTxSamplesSmooth; + NVIC_ENABLE_IRQ(IRQ_SOFTWARE); + return b; +} + +// Called from the USB interrupt when ready to transmit another +// isochronous packet. +// the return is the number of bytes to transmit +unsigned int usb_audio_transmit_callback(void) +{ + tx_callback_count++; + //compute the number of samples we want to transmit (at 44.1kHz and a bInterval of 1ms that is either 44 or 45 samples) + uint32_t target = getTransmissionTarget(); + if(!USBAudioOutInterface::running){ + //Some hosts (Linux, Windows) would tolerate zero length data packages. + //However, sometimes there are troubles with MacOs. + //So we better always send the expected number of samples. + const uint32_t numBytes =target*noTransmittedChannels*AUDIO_SUBSLOT_SIZE; + uint8_t *data = usb_audio_transmit_buffer; + memset(data, 0, numBytes); + return target * noTransmittedChannels*AUDIO_SUBSLOT_SIZE; + } + + // data transmitted: may get multiple blocks before it happens again + transmit_flag = (audioPollingIntervaluS * samplingRate / AUDIO_BLOCK_SAMPLES / 1'000'000) + 1; + + //time measurement (needed for the computation of virtual samples) + uint32_t current =ARM_DWT_CYCCNT; + lastCallTransmitIsr.addCall(current); + //================================================================ + + + const uint16_t iBIdx = incoming_tx_bIdx; //we are not allowed to change incoming_tx_bIdx + uint16_t tBIdx = transmit_tx_bIdx; + uint16_t offset = USBAudioOutInterface::outgoing_count; + + //============================================================ + const uint32_t devCounterThrs =10; + static uint32_t devCounter=0; //how often in a row there were too many or too few samples, if counter reaches 'devCounterThrs', we take some action (hysteresis) + static int8_t sign =0; //-1... too few samples in the buffer, 0... state not known, 1... too many samples in the buffer + float virtualSamples =0.f; + if(USBAudioOutInterface::updateCurrentSmooth !=-1.){ + + float lastIsrSmooth = (float)lastCallTransmitIsr.getLastCall<2>(); + float timeSinceLastUpdate = (float)toInt32Range(lastIsrSmooth - USBAudioOutInterface::updateCurrentSmooth); + timeSinceLastUpdate /= F_CPU_ACTUAL; //to seconds + if (timeSinceLastUpdate > 1.5f*USBAudioOutInterface::blockDuration || timeSinceLastUpdate < -0.5f*USBAudioOutInterface::blockDuration){ + //something really went wrong since update is normally called eveey blockDuration seconds + //-> we use the average value to prevent serious problems + timeSinceLastUpdate =0.5f*USBAudioOutInterface::blockDuration; + } + virtualSamples =timeSinceLastUpdate * AUDIO_SAMPLE_RATE; + + bufferedTxSamples= getNumBufferedTxSamples(txBufferState, target, iBIdx, tBIdx, offset); + bufferedTxSamplesSmooth=bufferedTxSamples + virtualSamples; + updateDevCounter(bufferedTxSamplesSmooth -targetNumTxBufferedSamples, devCounter, sign); + } + + if(txBufferState == USBAudioOutInterface::overrun || streamStart){ + streamStart=false; + devCounter=0; + resetTransmissionIndex(virtualSamples, iBIdx, tBIdx, offset, noSamplesPerPollingInterval); + USBAudioOutInterface::releaseBlocks(tBIdx, noTransmittedChannels); + txBufferState=USBAudioOutInterface::ready; + } +#ifdef ASYNC_TX_ENDPOINT + if(devCounter == devCounterThrs){ + updateTarget(sign, devCounter, target); + } +#endif + uint32_t len=0; + uint8_t *data = usb_audio_transmit_buffer; + + while (len < target) { + uint32_t num = target - len; + uint32_t avail = AUDIO_BLOCK_SAMPLES - offset; + if( avail==0 || !USBAudioOutInterface::isBlockReady(tBIdx,0)){ + //Something went wrong. We either did not receive a block, or a buffer underrun occured. + //We will reset the buffer indices and offsets and transmit zeros. + if( avail==0){ + tx_underrun_count++; + devCounter=0; //only reset in case of an underrun and not if USBAudioOutInterface did not receive data in 'update' + txUsb_audio_underrun_count++; + resetTransmissionIndex(virtualSamples, iBIdx, tBIdx, offset, num); + } + const uint32_t numBytes = num*noTransmittedChannels*AUDIO_SUBSLOT_SIZE; + memset(data, 0, numBytes); + } + else { + if (num > avail){ + num = avail; + } + USBAudioOutInterface::copy_from_buffer(data, tBIdx, noTransmittedChannels, offset, num); + } + data += num*noTransmittedChannels*AUDIO_SUBSLOT_SIZE; + len+=num; + offset+=num; //num can be larger than 'AUDIO_BLOCK_SAMPLES' in case of 'avail==0 || !USBAudioOutInterface::isBlockReady(tBIdx,0)' +#ifndef ASYNC_TX_ENDPOINT + if(devCounter == devCounterThrs){ + updateBufferOffset(sign, devCounter, offset); + } +#endif + if (offset >= AUDIO_BLOCK_SAMPLES) { + USBAudioOutInterface::tryIncreaseIdxTransmission(tBIdx,offset); + } + } + transmit_tx_bIdx=tBIdx; + USBAudioOutInterface::outgoing_count = offset; + + return target * noTransmittedChannels*AUDIO_SUBSLOT_SIZE; +} +#endif + + + + + +void usb_audio_configure(void) +{ + rxUsb_audio_underrun_count = 0; + rxUsb_audio_overrun_count = 0; + rxMemoryUnderrunCounter=0; + sumDiff=0.; + lastDiff=0.; + rxBufferReady= false; + incoming_rx_bIdx=0; + transmit_rx_bIdx=0; + if (usb_high_speed) { + maxRxTxSamples = AUDIO_RX_TX_SIZE_SAMPLES_480; + noTransmittedChannels = USB_AUDIO_NO_CHANNELS_480; + audioPollingIntervalSec = AUDIO_POLLING_INTERVAL_480_SEC; + audioPollingIntervaluS = AUDIO_NUM_SUBFRAMES_PER_POLLING_480 * MICROFRAME_US; + usb_audio_sync_nbytes = 4; + usb_audio_sync_rshift = 8; + } else { + maxRxTxSamples = AUDIO_RX_TX_SIZE_SAMPLES_12; + noTransmittedChannels = USB_AUDIO_NO_CHANNELS_12; + audioPollingIntervalSec = AUDIO_POLLING_INTERVAL_12_SEC; + audioPollingIntervaluS = AUDIO_NUM_SUBFRAMES_PER_POLLING_12 * MICROFRAME_US; + usb_audio_sync_nbytes = 3; + usb_audio_sync_rshift = 10; + } + + noSamplesPerPollingInterval = (samplingRate*audioPollingIntervaluS)/1'000'000; + + feedback_accumulator_default = uint32_t((samplingRate *audioPollingIntervalSec) * 0x1000000 +0.5f); + feedback_accumulator = feedback_accumulator_default; + + memset(&rx_transfer, 0, sizeof(rx_transfer)); + usb_config_rx_iso(AUDIO_RX_ENDPOINT, AUDIO_RX_SIZE, 1, rx_event); + rx_event(NULL); + memset(&sync_transfer, 0, sizeof(sync_transfer)); + usb_config_tx_iso(AUDIO_SYNC_ENDPOINT, usb_audio_sync_nbytes, 1, sync_event); + sync_event(NULL); + memset(&tx_transfer, 0, sizeof(tx_transfer)); + usb_config_tx_iso(AUDIO_TX_ENDPOINT, AUDIO_TX_SIZE, 1, tx_event); + tx_event(NULL); + expectedIsrIntervalCycles = audioPollingIntervalSec *F_CPU_ACTUAL; + lastCallReceiveIsr.reset(expectedIsrIntervalCycles); + lastCallTransmitIsr.reset(expectedIsrIntervalCycles); + + //AudioOutputUSB ============================== + resetStatusCounter(); + //================================================= +} + + + + + + +struct setup_struct { + //union { + struct { + uint8_t bmRequestType; + uint8_t bRequest; + union { + struct { + uint8_t bChannel; // 0=main, 1=left, 2=right + uint8_t bCS; // Control Selector + }; + uint16_t wValue; + }; + union { + struct { + uint8_t bIfEp; // type of entity + uint8_t bEntityId; // UnitID, TerminalID, etc. + }; + uint16_t wIndex; + }; + uint16_t wLength; + }; + //}; +}; + +int usb_audio_get_feature(void *stp, uint8_t *data, uint32_t *datalen) +{ + + struct setup_struct setup = *((struct setup_struct *)stp); + if(setup.bmRequestType != 0xA1){ + //was not a get feature request directed to the feature unit + return 0; + } + + if (setup.bRequest==0x02) { // -> request = RANGE + if (setup.bCS==0x01) { // mute + data[0] =1; //only one sub-range (LSB of 2 bytes) + data[1] =0; //only one sub-range (MSB of 2 bytes) + data[2] =0; //unmute + data[3] = 1; //mute + data[4] = 1; //resolution + *datalen = 5; + return 1; + } + else if (setup.bCS==0x02) { // volume + //Have a look at the UAC2 specification page 102, section 5.2.5.7.2 Volume Control + const int16_t maxVol = FEATURE_MAX_VOLUME; + data[0] = 1; //only one sub-range (LSB of 2 bytes) + data[1] = 0; //only one sub-range (MSB of 2 bytes) + data[2] = 0; // min level is 0 (LSB) + data[3] = 0; // min level is 0 (MSB) + data[4] = maxVol & 0xFF; // max level, for range of 0 to MAX (LSB) + data[5] = (maxVol>>8) & 0xFF; // max level, for range of 0 to MAX (MSB) + data[6] = 1; // increment vol by by 1 (LSB) + data[7] = 0; // increment vol by by 1 (MSB) + *datalen = 8; + return 1; + } + else { // pass over SET_MEM, etc. + return 0; + } + } + if (setup.bRequest==0x01) { // -> request = CUR + if (setup.bCS==0x01) { // mute + data[0] = USBAudioInInterface::features.mute; // 1=mute, 0=unmute + *datalen = 1; + return 1; + } + else if (setup.bCS==0x02) { // volume + const int16_t vol = (int16_t)USBAudioInInterface::features.volume; + data[0] = vol & 0xFF; //(LSB) + data[1] = (vol>>8) & 0xFF; //(MSB) + *datalen = 2; + return 1; + } + else { // pass over SET_MEM, etc. + return 0; + } + } + return 0; +} + +int usb_audio_set_feature(void *stp, uint8_t *buf) +{ + struct setup_struct setup = *((struct setup_struct *)stp); + if (setup.bmRequestType==0x21) { // SET FEATURE should check bRequest, bChannel and UnitID + if (setup.bCS==0x01) { // mute + if (setup.bRequest==0x01) { // CUR + // if(Serial){ + // Serial.println("set mute"); + // } + USBAudioInInterface::features.mute = buf[0]; // 1=mute,0=unmute + USBAudioInInterface::features.change = 1; + return 1; + } + } + else if (setup.bCS==0x02) { // volume + if (setup.bRequest==0x01) { // CUR + //Have a look at the UAC2 specification page 102, section 5.2.5.7.2 Volume Control + //volume uses two bytes + const int16_t *volPtr =(const int16_t *)buf; + USBAudioInInterface::features.volume = *volPtr; + USBAudioInInterface::features.change = 1; + return 1; + } + } + } + return 0; +} + + +#endif // AUDIO_INTERFACE diff --git a/patched_teensy_core/usb_audio_interface.h b/patched_teensy_core/usb_audio_interface.h new file mode 100644 index 0000000..0ae4bf8 --- /dev/null +++ b/patched_teensy_core/usb_audio_interface.h @@ -0,0 +1,202 @@ +/* Teensyduino Core Library + * http://www.pjrc.com/teensy/ + * Copyright (c) 2017 PJRC.COM, LLC. + * + * 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: + * + * 1. The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * 2. If the Software is incorporated into a build system that allows + * selection among a list of target devices, then similar target + * devices manufactured by PJRC.COM must be included in the list of + * target devices and selectable in the same manner. + * + * 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. + */ + +#pragma once + + +#include "usb_desc.h" +#ifdef AUDIO_INTERFACE + +#ifndef AUDIO_SAMPLE_RATE_EXACT +#define AUDIO_SAMPLE_RATE_EXACT 44100.0f +#endif +#define AUDIO_SAMPLE_RATE AUDIO_SAMPLE_RATE_EXACT + +#include "util/LastCall.h" + +#define FEATURE_MAX_VOLUME 0xFF // volume accepted from 0 to 0xFF +#define TARGET_RX_BUFFER_TIME_S 0.0018f //targeted buffered time (latency) in seconds +#define MICROFRAME_US 125 // defined by the USB standard + +#ifdef __cplusplus +extern "C" { +#endif +extern void usb_audio_configure(); +extern uint8_t usb_audio_receive_setting; +extern uint8_t usb_audio_transmit_setting; +extern void usb_audio_receive_callback(unsigned int len); +extern unsigned int usb_audio_transmit_callback(void); +extern int usb_audio_set_feature(void *stp, uint8_t *buf); +extern int usb_audio_get_feature(void *stp, uint8_t *data, uint32_t *datalen); +#ifdef __cplusplus +} +#endif + +// audio features supported +struct usb_audio_features_struct { + int change; // set to 1 when any value is changed + int mute; // 1=mute, 0=unmute + int volume; // volume from 0 to FEATURE_MAX_VOLUME, maybe should be float from 0.0 to 1.0 +}; + +#ifdef __cplusplus + +class USBAudioInInterface +{ +public: + struct Status { + uint32_t usb_audio_underrun_count; //How often did we run out of audio samples. (usb host sends audio data too slowly) + uint32_t usb_audio_overrun_count; //How often did we run out of buffer space. (usb host sends audio data too fast) + uint32_t audio_memory_underrun_count; //How often did we run out audio blocks. (Increase N in AudioMemory(N) if that happens) + float target_num_buffered_samples; //How many samples do want to be in the buffer directly after an 'update' call (the more samples, the more stable. Should at least be: (bInterval_uS*e-6) * AUDIO_SAMPLE_RATE) + uint16_t num_transmitted_channels; //How many audio channels do we receive (might be smaller than expected in case the 12Mbit/s bandwidth limits the number of channels) + uint16_t ring_buffer_size; //Number of audio blocks per channel in the ring buffer (received audio data from the host is stored in this buffer) + uint16_t usb_rx_tx_buffer_size; //=AUDIO_RX_SIZE_480 or AUDIO_RX_SIZE_12 (bytes, must be larger than AUDIO_SAMPLE_RATE * bInterval_uS*1e-6 * USB_AUDIO_NO_CHANNELS_480 * AUDIO_SUBSLOT_SIZE + uint16_t bInterval_uS; //polling interval as requested by the Teensy (125, 250, 500 or 1000) + bool receivingData; //Teensy is currently receiving data (There was at least one 'usb_audio_receive_callback' since the last 'update' call) + uint8_t usb_high_speed; // 1 for high speed, 0 otherwise + }; + typedef bool (*SetBlockQuite) (uint16_t bIdx, uint16_t channel); + typedef void (*ReleaseBlock)(uint16_t bIdx, uint16_t channel); + typedef bool (*AllocateBlock)(uint16_t bIdx, uint16_t channel); + typedef bool (*AreBlocksReady)(uint16_t bIdx, uint16_t noChannels); + typedef void (*Copy_to_buffers)(const uint8_t *src, uint16_t bIdx, uint16_t noChannels, unsigned int count, unsigned int len); + constexpr static float blockDuration = AUDIO_BLOCK_SAMPLES/float(AUDIO_SAMPLE_RATE); + //no buffered blocks computation + // + // +3: + // +1 we round the number of needed blocks up + // +1 because we store the block that is ready for transmission in the buffer + // +1 just to have one block in reserve (prevents buffer overflows and comes at more or less no additional cost (at normal operation no additional latency + no additional memory needed)) + // *2: because to be symmetrically protected against buffer overflow (a high TARGET_RX_BUFFER_TIME_S only prevents buffer underflows) + constexpr static uint16_t ringRxBufferSize = uint16_t(TARGET_RX_BUFFER_TIME_S / USBAudioInInterface::blockDuration) *2 +3; + + USBAudioInInterface( + SetBlockQuite sbq, + ReleaseBlock rb, + AllocateBlock ab, + AreBlocksReady abr, + Copy_to_buffers c_t_b, + float kp =400.f, + float ki =.2f ); + void begin(); + void stop(); + void update(int16_t& bIdx, uint16_t& noChannels); + void incrementBufferIndex(); + float getBufferedSamples() const; + float getBufferedSamplesSmooth() const; + float getRequestedSamplingFrequ() const; + float getActualBIntervalUs() const; + Status getStatus() const; + friend void usb_audio_receive_callback(unsigned int len); + friend int usb_audio_set_feature(void *stp, uint8_t *buf); + friend int usb_audio_get_feature(void *stp, uint8_t *data, uint32_t *datalen); + static struct usb_audio_features_struct features; + float volume(void) { + if (features.mute) return 0.0; + return (float)(features.volume) * (1.0 / (float)FEATURE_MAX_VOLUME); + } + +private: + uint32_t _bufferedSamples=0; + float _kp =400.f; + float _ki =.2f; + float _bufferedSamplesSmooth=0; + bool _streaming= false; + LastCall<50> _lastCallUpdate; + static bool setBlocksQuite(uint32_t noBlocks); + static bool allocateChannels(uint16_t idx); + static bool resetBuffer(double updateCurrentSmooth); + static bool isBufferReady(); + static void tryIncreaseIdxIncoming(uint16_t& count); + static void releaseBlocks(uint16_t bufferIdx); + static bool running; + + static SetBlockQuite setBlockQuite; + static ReleaseBlock releaseBlock; + static AllocateBlock allocateBlock; + static AreBlocksReady areBlocksReady; + static Copy_to_buffers copy_to_buffers; +}; + +#define TARGET_TX_BUFFER_TIME_S 0.0035f //targeted buffered time (latency) in seconds +class USBAudioOutInterface { +public: + enum BufferState{ready, full, overrun}; + struct Status { + uint32_t usb_audio_underrun_count; //How often did we run out of audio samples. (usb host requests audio data too fast) + uint32_t usb_audio_overrun_count; //How often did we run out of buffer space. (usb host requests audio data too slowly) + float target_num_buffered_samples; //How many samples do want to be in the buffer directly after an 'usb_audio_transmit_callback' call (the more samples, the more stable. Should at least be: AUDIO_BLOCK_SAMPLES) + uint16_t num_transmitted_channels; //How many audio channels do we transmit (might be smaller than expected in case the 12Mbit/s bandwidth limits the number of channels) + uint16_t ring_buffer_size; //Number of audio blocks per channel in the ring buffer (audio blocks from the Teensy are stored in this buffer, before the data is sent to the host.) + uint16_t usb_rx_tx_buffer_size; //=AUDIO_TX_SIZE_480 or AUDIO_TX_SIZE_12 (bytes, must be larger than AUDIO_SAMPLE_RATE * bInterval_uS*1e-6 * USB_AUDIO_NO_CHANNELS_480 * AUDIO_SUBSLOT_SIZE + uint16_t bInterval_uS; //polling interval as requested by the Teensy (125, 250, 500 or 1000) + uint32_t num_skipped_Samples; //only used if not ASYNC_TX_ENDPOINT (->adaptive endpoint) In usb_audio_transmit_callback: how often was a sample skipped to prevent a buffer overrun. + uint32_t num_padded_Samples; //only used if not ASYNC_TX_ENDPOINT (->sdaptive endpoint) In usb_audio_transmit_callback: how often was a sample padded to prevent a buffer underrun. + uint32_t num_send_one_less; //only used if ASYNC_TX_ENDPOINT (-> asynchronous endpoint) In usb_audio_transmit_callback: how often was a sample less than initially planned sent to prevent a buffer underrun. + uint32_t num_send_one_more; //only used if ASYNC_TX_ENDPOINT (-> asynchronous endpoint) In usb_audio_transmit_callback: how often was a sample more than initially planned sent to prevent a buffer overrun. + bool transmittingData; //Teensy is currently sending data to the host (There was at least one 'usb_audio_transmit_callback' since the last 'update' call) + uint8_t usb_high_speed; // 1 for high speed, 0 otherwise + }; + + typedef void (*ReleaseBlocks)(uint16_t bIdx, uint16_t noChannels); + typedef bool (*IsBlockReady)(uint16_t bIdx, uint16_t channel); + typedef void (*Copy_from_buffers)(uint8_t *dst, uint16_t bIdx, uint16_t noChannels, unsigned int count, unsigned int len); + + constexpr static float blockDuration = AUDIO_BLOCK_SAMPLES/float(AUDIO_SAMPLE_RATE); + constexpr static uint16_t ringTxBufferSize = uint16_t(TARGET_TX_BUFFER_TIME_S / USBAudioInInterface::blockDuration) *2 +3; + + USBAudioOutInterface(ReleaseBlocks rbs, IsBlockReady ibr, Copy_from_buffers c_f_b); + void begin(void); + void stop(void); + void update(int16_t& bIdx, uint16_t& noChannels); + void incrementBufferIndex(); + friend unsigned int usb_audio_transmit_callback(void); + float getBufferedSamples() const; + float getBufferedSamplesSmooth() const; + float getActualBIntervalUs() const; + Status getStatus() const; +private: + static void tryIncreaseIdxTransmission(uint16_t& tBIdx, uint16_t& offset); + bool _streaming= false; + double _updateCurrentSmoothPending; + LastCall<50> _lastCallUpdate; + static double updateCurrentSmooth; + static uint16_t outgoing_count; + static bool running; + + static ReleaseBlocks releaseBlocks; + static IsBlockReady isBlockReady; + static Copy_from_buffers copy_from_buffer; +}; + +#endif // __cplusplus + +#endif // AUDIO_INTERFACE diff --git a/patched_teensy_core/usb_desc.c b/patched_teensy_core/usb_desc.c new file mode 100644 index 0000000..66f0c6c --- /dev/null +++ b/patched_teensy_core/usb_desc.c @@ -0,0 +1,2989 @@ +/* Teensyduino Core Library + * http://www.pjrc.com/teensy/ + * Copyright (c) 2019 PJRC.COM, LLC. + * + * 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: + * + * 1. The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * 2. If the Software is incorporated into a build system that allows + * selection among a list of target devices, then similar target + * devices manufactured by PJRC.COM must be included in the list of + * target devices and selectable in the same manner. + * + * 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. + */ + +//#if F_CPU >= 20000000 + +#define USB_DESC_LIST_DEFINE +#include "usb_desc.h" +#ifdef NUM_ENDPOINTS +#include "usb_names.h" +#include "imxrt.h" +#include "avr_functions.h" +#include "avr/pgmspace.h" + +// At very slow CPU speeds, the OCRAM just isn't fast enough for +// USB to work reliably. But the precious/limited DTCM is. So +// as an ugly workaround, undefine DMAMEM so all buffers which +// would normally be allocated in OCRAM are placed in DTCM. +#if defined(F_CPU) && F_CPU < 30000000 +#undef DMAMEM +#endif + +// USB Descriptors are binary data which the USB host reads to +// automatically detect a USB device's capabilities. The format +// and meaning of every field is documented in numerous USB +// standards. When working with USB descriptors, despite the +// complexity of the standards and poor writing quality in many +// of those documents, remember descriptors are nothing more +// than constant binary data that tells the USB host what the +// device can do. Computers will load drivers based on this data. +// Those drivers then communicate on the endpoints specified by +// the descriptors. + +// To configure a new combination of interfaces or make minor +// changes to existing configuration (eg, change the name or ID +// numbers), usually you would edit "usb_desc.h". This file +// is meant to be configured by the header, so generally it is +// only edited to add completely new USB interfaces or features. + + + +// ************************************************************** +// USB Device +// ************************************************************** + +#define LSB(n) ((n) & 255) +#define MSB(n) (((n) >> 8) & 255) + +#ifdef CDC_IAD_DESCRIPTOR +#ifndef DEVICE_CLASS +#define DEVICE_CLASS 0xEF +#endif +#ifndef DEVICE_SUBCLASS +#define DEVICE_SUBCLASS 0x02 +#endif +#ifndef DEVICE_PROTOCOL +#define DEVICE_PROTOCOL 0x01 +#endif +#endif + + +// USB Device Descriptor. The USB host reads this first, to learn +// what type of device is connected. +static uint8_t device_descriptor[] = { + 18, // bLength + 1, // bDescriptorType + 0x00, 0x02, // bcdUSB +#ifdef DEVICE_CLASS + DEVICE_CLASS, // bDeviceClass +#else + 0, +#endif +#ifdef DEVICE_SUBCLASS + DEVICE_SUBCLASS, // bDeviceSubClass +#else + 0, +#endif +#ifdef DEVICE_PROTOCOL + DEVICE_PROTOCOL, // bDeviceProtocol +#else + 0, +#endif + EP0_SIZE, // bMaxPacketSize0 + LSB(VENDOR_ID), MSB(VENDOR_ID), // idVendor + LSB(PRODUCT_ID), MSB(PRODUCT_ID), // idProduct +#ifdef BCD_DEVICE + LSB(BCD_DEVICE), MSB(BCD_DEVICE), // bcdDevice +#else + // For USB types that don't explicitly define BCD_DEVICE, + // use the minor version number to help teensy_ports + // identify which Teensy model is used. + #if defined(__IMXRT1062__) && defined(ARDUINO_TEENSY40) + 0x79, 0x02, // Teensy 4.0 + #elif defined(__IMXRT1062__) && defined(ARDUINO_TEENSY41) + 0x80, 0x02, // Teensy 4.1 + #elif defined(__IMXRT1062__) && defined(ARDUINO_TEENSY_MICROMOD) + 0x81, 0x02, // Teensy MicroMod + #else + 0x00, 0x02, + #endif +#endif + 1, // iManufacturer + 2, // iProduct + 3, // iSerialNumber + 1 // bNumConfigurations +}; + +PROGMEM static const uint8_t qualifier_descriptor[] = { // 9.6.2 Device_Qualifier, page 264 + 10, // bLength + 6, // bDescriptorType + 0x00, 0x02, // bcdUSB +#ifdef DEVICE_CLASS + DEVICE_CLASS, // bDeviceClass +#else + 0, +#endif +#ifdef DEVICE_SUBCLASS + DEVICE_SUBCLASS, // bDeviceSubClass +#else + 0, +#endif +#ifdef DEVICE_PROTOCOL + DEVICE_PROTOCOL, // bDeviceProtocol +#else + 0, +#endif + EP0_SIZE, // bMaxPacketSize0 + 1, // bNumConfigurations + 0 // bReserved +}; + +// These descriptors must NOT be "const", because the USB DMA +// has trouble accessing flash memory with enough bandwidth +// while the processor is executing from flash. + + + +// ************************************************************** +// HID Report Descriptors +// ************************************************************** + +// Each HID interface needs a special report descriptor that tells +// the meaning and format of the data. + +#ifdef KEYBOARD_INTERFACE +// Keyboard Protocol 1, HID 1.11 spec, Appendix B, page 59-60 +static uint8_t keyboard_report_desc[] = { + 0x05, 0x01, // Usage Page (Generic Desktop), + 0x09, 0x06, // Usage (Keyboard), + 0xA1, 0x01, // Collection (Application), + 0x75, 0x01, // Report Size (1), + 0x95, 0x08, // Report Count (8), + 0x05, 0x07, // Usage Page (Key Codes), + 0x19, 0xE0, // Usage Minimum (224), + 0x29, 0xE7, // Usage Maximum (231), + 0x15, 0x00, // Logical Minimum (0), + 0x25, 0x01, // Logical Maximum (1), + 0x81, 0x02, // Input (Data, Variable, Absolute), ;Modifier keys +#if KEYBOARD_SIZE == 8 + 0x95, 0x01, // Report Count (1), + 0x75, 0x08, // Report Size (8), + 0x81, 0x03, // Input (Constant), ;Reserved byte +#endif + 0x95, 0x05, // Report Count (5), + 0x75, 0x01, // Report Size (1), + 0x05, 0x08, // Usage Page (LEDs), + 0x19, 0x01, // Usage Minimum (1), + 0x29, 0x05, // Usage Maximum (5), + 0x91, 0x02, // Output (Data, Variable, Absolute), ;LED report + 0x95, 0x01, // Report Count (1), + 0x75, 0x03, // Report Size (3), + 0x91, 0x03, // Output (Constant), ;LED report padding +#if KEYBOARD_SIZE == 8 + 0x95, 0x06, // Report Count (6), + 0x75, 0x08, // Report Size (8), + 0x15, 0x00, // Logical Minimum (0), + 0x25, 0x7F, // Logical Maximum(104), + 0x05, 0x07, // Usage Page (Key Codes), + 0x19, 0x00, // Usage Minimum (0), + 0x29, 0x7F, // Usage Maximum (104), + 0x81, 0x00, // Input (Data, Array), ;Normal keys +#elif KEYBOARD_SIZE == 16 + 0x95, 0x78, // Report Count (120), + 0x75, 0x01, // Report Size (1), + 0x19, 0x00, // Usage Minimum (0), + 0x29, 0xE7, // Usage Maximum (119), + 0x05, 0x07, // Usage Page (Key Codes), + 0x15, 0x00, // Logical Minimum (0), + 0x25, 0x01, // Logical Maximum (1), + 0x81, 0x02, // Input (Data, Variable, Absolute), ;Normal keys NKRO +#endif + 0xC0 // End Collection +}; +#endif + +#ifdef KEYMEDIA_INTERFACE +static uint8_t keymedia_report_desc[] = { + 0x05, 0x0C, // Usage Page (Consumer) + 0x09, 0x01, // Usage (Consumer Controls) + 0xA1, 0x01, // Collection (Application) + 0x75, 0x0A, // Report Size (10) + 0x95, 0x04, // Report Count (4) + 0x19, 0x00, // Usage Minimum (0) + 0x2A, 0x9C, 0x02, // Usage Maximum (0x29C) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0x9C, 0x02, // Logical Maximum (0x29C) + 0x81, 0x00, // Input (Data, Array) + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x75, 0x08, // Report Size (8) + 0x95, 0x03, // Report Count (3) + 0x19, 0x00, // Usage Minimum (0) + 0x29, 0xB7, // Usage Maximum (0xB7) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xB7, 0x00, // Logical Maximum (0xB7) + 0x81, 0x00, // Input (Data, Array) + 0xC0 // End Collection +}; +#endif + +#ifdef MOUSE_INTERFACE +// Mouse Protocol 1, HID 1.11 spec, Appendix B, page 59-60, with wheel extension +static uint8_t mouse_report_desc[] = { + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x02, // Usage (Mouse) + 0xA1, 0x01, // Collection (Application) + 0x85, 0x01, // REPORT_ID (1) + 0x05, 0x09, // Usage Page (Button) + 0x19, 0x01, // Usage Minimum (Button #1) + 0x29, 0x08, // Usage Maximum (Button #8) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x95, 0x08, // Report Count (8) + 0x75, 0x01, // Report Size (1) + 0x81, 0x02, // Input (Data, Variable, Absolute) + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x09, 0x38, // Usage (Wheel) + 0x15, 0x81, // Logical Minimum (-127) + 0x25, 0x7F, // Logical Maximum (127) + 0x75, 0x08, // Report Size (8), + 0x95, 0x03, // Report Count (3), + 0x81, 0x06, // Input (Data, Variable, Relative) + 0x05, 0x0C, // Usage Page (Consumer) + 0x0A, 0x38, 0x02, // Usage (AC Pan) + 0x15, 0x81, // Logical Minimum (-127) + 0x25, 0x7F, // Logical Maximum (127) + 0x75, 0x08, // Report Size (8), + 0x95, 0x01, // Report Count (1), + 0x81, 0x06, // Input (Data, Variable, Relative) + 0xC0, // End Collection + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x02, // Usage (Mouse) + 0xA1, 0x01, // Collection (Application) + 0x85, 0x02, // REPORT_ID (2) + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x7F, // Logical Maximum (32767) + 0x75, 0x10, // Report Size (16), + 0x95, 0x02, // Report Count (2), + 0x81, 0x02, // Input (Data, Variable, Absolute) + 0xC0 // End Collection +}; +#endif + +#ifdef JOYSTICK_INTERFACE +#if JOYSTICK_SIZE == 12 +static uint8_t joystick_report_desc[] = { + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x04, // Usage (Joystick) + 0xA1, 0x01, // Collection (Application) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x20, // Report Count (32) + 0x05, 0x09, // Usage Page (Button) + 0x19, 0x01, // Usage Minimum (Button #1) + 0x29, 0x20, // Usage Maximum (Button #32) + 0x81, 0x02, // Input (variable,absolute) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x07, // Logical Maximum (7) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0x3B, 0x01, // Physical Maximum (315) + 0x75, 0x04, // Report Size (4) + 0x95, 0x01, // Report Count (1) + 0x65, 0x14, // Unit (20) + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x39, // Usage (Hat switch) + 0x81, 0x42, // Input (variable,absolute,null_state) + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x01, // Usage (Pointer) + 0xA1, 0x00, // Collection () + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x03, // Logical Maximum (1023) + 0x75, 0x0A, // Report Size (10) + 0x95, 0x04, // Report Count (4) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x09, 0x32, // Usage (Z) + 0x09, 0x35, // Usage (Rz) + 0x81, 0x02, // Input (variable,absolute) + 0xC0, // End Collection + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x03, // Logical Maximum (1023) + 0x75, 0x0A, // Report Size (10) + 0x95, 0x02, // Report Count (2) + 0x09, 0x36, // Usage (Slider) + 0x09, 0x36, // Usage (Slider) + 0x81, 0x02, // Input (variable,absolute) + 0xC0 // End Collection +}; +#elif JOYSTICK_SIZE == 64 +// extreme joystick (to use this, edit JOYSTICK_SIZE to 64 in usb_desc.h) +// 128 buttons 16 +// 6 axes 12 +// 17 sliders 34 +// 4 pov 2 +static uint8_t joystick_report_desc[] = { + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x04, // Usage (Joystick) + 0xA1, 0x01, // Collection (Application) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x80, // Report Count (128) + 0x05, 0x09, // Usage Page (Button) + 0x19, 0x01, // Usage Minimum (Button #1) + 0x29, 0x80, // Usage Maximum (Button #128) + 0x81, 0x02, // Input (variable,absolute) + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x01, // Usage (Pointer) + 0xA1, 0x00, // Collection () + 0x15, 0x00, // Logical Minimum (0) + 0x27, 0xFF, 0xFF, 0, 0, // Logical Maximum (65535) + 0x75, 0x10, // Report Size (16) + 0x95, 23, // Report Count (23) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x09, 0x32, // Usage (Z) + 0x09, 0x33, // Usage (Rx) + 0x09, 0x34, // Usage (Ry) + 0x09, 0x35, // Usage (Rz) + 0x09, 0x36, // Usage (Slider) + 0x09, 0x36, // Usage (Slider) + 0x09, 0x36, // Usage (Slider) + 0x09, 0x36, // Usage (Slider) + 0x09, 0x36, // Usage (Slider) + 0x09, 0x36, // Usage (Slider) + 0x09, 0x36, // Usage (Slider) + 0x09, 0x36, // Usage (Slider) + 0x09, 0x36, // Usage (Slider) + 0x09, 0x36, // Usage (Slider) + 0x09, 0x36, // Usage (Slider) + 0x09, 0x36, // Usage (Slider) + 0x09, 0x36, // Usage (Slider) + 0x09, 0x36, // Usage (Slider) + 0x09, 0x36, // Usage (Slider) + 0x09, 0x36, // Usage (Slider) + 0x09, 0x36, // Usage (Slider) + 0x81, 0x02, // Input (variable,absolute) + 0xC0, // End Collection + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x07, // Logical Maximum (7) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0x3B, 0x01, // Physical Maximum (315) + 0x75, 0x04, // Report Size (4) + 0x95, 0x04, // Report Count (4) + 0x65, 0x14, // Unit (20) + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x39, // Usage (Hat switch) + 0x09, 0x39, // Usage (Hat switch) + 0x09, 0x39, // Usage (Hat switch) + 0x09, 0x39, // Usage (Hat switch) + 0x81, 0x42, // Input (variable,absolute,null_state) + 0xC0 // End Collection +}; +#endif // JOYSTICK_SIZE +#endif // JOYSTICK_INTERFACE + +#ifdef MULTITOUCH_INTERFACE +// https://forum.pjrc.com/threads/32331-USB-HID-Touchscreen-support-needed +// https://msdn.microsoft.com/en-us/library/windows/hardware/jj151563%28v=vs.85%29.aspx +// https://msdn.microsoft.com/en-us/library/windows/hardware/jj151565%28v=vs.85%29.aspx +// https://msdn.microsoft.com/en-us/library/windows/hardware/ff553734%28v=vs.85%29.aspx +// https://msdn.microsoft.com/en-us/library/windows/hardware/jj151564%28v=vs.85%29.aspx +// download.microsoft.com/download/a/d/f/adf1347d-08dc-41a4-9084-623b1194d4b2/digitizerdrvs_touch.docx +static uint8_t multitouch_report_desc[] = { + 0x05, 0x0D, // Usage Page (Digitizer) + 0x09, 0x04, // Usage (Touch Screen) + 0xa1, 0x01, // Collection (Application) + 0x09, 0x22, // Usage (Finger) + 0xA1, 0x02, // Collection (Logical) + 0x09, 0x42, // Usage (Tip Switch) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x01, // Report Count (1) + 0x81, 0x02, // Input (variable,absolute) + 0x09, 0x51, // Usage (Contact Identifier) + 0x25, 0x7F, // Logical Maximum (127) + 0x75, 0x07, // Report Size (7) + 0x95, 0x01, // Report Count (1) + 0x81, 0x02, // Input (variable,absolute) + 0x09, 0x30, // Usage (Pressure) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8) + 0x95, 0x01, // Report Count (1) + 0x81, 0x02, // Input (variable,absolute) + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x26, 0xFF, 0x7F, // Logical Maximum (32767) + 0x65, 0x00, // Unit (None) <-- probably needs real units? + 0x75, 0x10, // Report Size (16) + 0x95, 0x02, // Report Count (2) + 0x81, 0x02, // Input (variable,absolute) + 0xC0, // End Collection + 0x05, 0x0D, // Usage Page (Digitizer) + 0x27, 0xFF, 0xFF, 0, 0, // Logical Maximum (65535) + 0x75, 0x10, // Report Size (16) + 0x95, 0x01, // Report Count (1) + 0x09, 0x56, // Usage (Scan Time) + 0x81, 0x02, // Input (variable,absolute) + 0x09, 0x54, // USAGE (Contact count) + 0x25, 0x7f, // LOGICAL_MAXIMUM (127) + 0x95, 0x01, // REPORT_COUNT (1) + 0x75, 0x08, // REPORT_SIZE (8) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0x05, 0x0D, // Usage Page (Digitizers) + 0x09, 0x55, // Usage (Contact Count Maximum) + 0x25, MULTITOUCH_FINGERS, // Logical Maximum (10) + 0x75, 0x08, // Report Size (8) + 0x95, 0x01, // Report Count (1) + 0xB1, 0x02, // Feature (variable,absolute) + 0xC0 // End Collection +}; +#endif + +#ifdef SEREMU_INTERFACE +static uint8_t seremu_report_desc[] = { + 0x06, 0xC9, 0xFF, // Usage Page 0xFFC9 (vendor defined) + 0x09, 0x04, // Usage 0x04 + 0xA1, 0x5C, // Collection 0x5C + 0x75, 0x08, // report size = 8 bits (global) + 0x15, 0x00, // logical minimum = 0 (global) + 0x26, 0xFF, 0x00, // logical maximum = 255 (global) + 0x95, SEREMU_TX_SIZE, // report count (global) + 0x09, 0x75, // usage (local) + 0x81, 0x02, // Input + 0x95, SEREMU_RX_SIZE, // report count (global) + 0x09, 0x76, // usage (local) + 0x91, 0x02, // Output + 0x95, 0x04, // report count (global) + 0x09, 0x76, // usage (local) + 0xB1, 0x02, // Feature + 0xC0 // end collection +}; +#endif + +#ifdef RAWHID_INTERFACE +static uint8_t rawhid_report_desc[] = { + 0x06, LSB(RAWHID_USAGE_PAGE), MSB(RAWHID_USAGE_PAGE), + 0x0A, LSB(RAWHID_USAGE), MSB(RAWHID_USAGE), + 0xA1, 0x01, // Collection 0x01 + 0x75, 0x08, // report size = 8 bits + 0x15, 0x00, // logical minimum = 0 + 0x26, 0xFF, 0x00, // logical maximum = 255 + 0x95, RAWHID_TX_SIZE, // report count + 0x09, 0x01, // usage + 0x81, 0x02, // Input (array) + 0x95, RAWHID_RX_SIZE, // report count + 0x09, 0x02, // usage + 0x91, 0x02, // Output (array) + 0xC0 // end collection +}; +#endif + +#ifdef FLIGHTSIM_INTERFACE +static uint8_t flightsim_report_desc[] = { + 0x06, 0x1C, 0xFF, // Usage page = 0xFF1C + 0x0A, 0x39, 0xA7, // Usage = 0xA739 + 0xA1, 0x01, // Collection 0x01 + 0x75, 0x08, // report size = 8 bits + 0x15, 0x00, // logical minimum = 0 + 0x26, 0xFF, 0x00, // logical maximum = 255 + 0x95, FLIGHTSIM_TX_SIZE, // report count + 0x09, 0x01, // usage + 0x81, 0x02, // Input (array) + 0x95, FLIGHTSIM_RX_SIZE, // report count + 0x09, 0x02, // usage + 0x91, 0x02, // Output (array) + 0xC0 // end collection +}; +#endif + +#ifdef EXPERIMENTAL_INTERFACE +static uint8_t microsoft_os_string_desc[] = { + 18, 3, + 'M', 0, 'S', 0, 'F', 0, 'T', 0, '1', 0, '0', 0, '0', 0, + 0xF8, 0 // GET_MS_DESCRIPTOR will use bRequest=0xF8 +}; +static uint8_t microsoft_os_compatible_id_desc[] = { + 40, 0, 0, 0, // total length, 16 header + 24 function * 1 + 0, 1, 4, 0, // version 1.00, wIndex=4 (Compat ID) + 1, 0, 0, 0, 0, 0, 0, 0, // 1 function + EXPERIMENTAL_INTERFACE, 1, + 'W','I','N','U','S','B',0,0, // compatibleID + 0,0,0,0,0,0,0,0, // subCompatibleID + 0,0,0,0,0,0 +}; +#endif + +// ************************************************************** +// USB Descriptor Sizes +// ************************************************************** + +// pre-compute the size and position of everything in the config descriptor +// +#define CONFIG_HEADER_DESCRIPTOR_SIZE 9 + +#define CDC_IAD_DESCRIPTOR_POS CONFIG_HEADER_DESCRIPTOR_SIZE +#ifdef CDC_IAD_DESCRIPTOR +#define CDC_IAD_DESCRIPTOR_SIZE 8 +#else +#define CDC_IAD_DESCRIPTOR_SIZE 0 +#endif + +#define CDC_DATA_INTERFACE_DESC_POS CDC_IAD_DESCRIPTOR_POS+CDC_IAD_DESCRIPTOR_SIZE +#ifdef CDC_DATA_INTERFACE +#define CDC_DATA_INTERFACE_DESC_SIZE 9+5+5+4+5+7+9+7+7 +#else +#define CDC_DATA_INTERFACE_DESC_SIZE 0 +#endif + +#define CDC2_DATA_INTERFACE_DESC_POS CDC_DATA_INTERFACE_DESC_POS+CDC_DATA_INTERFACE_DESC_SIZE +#ifdef CDC2_DATA_INTERFACE +#define CDC2_DATA_INTERFACE_DESC_SIZE 8 + 9+5+5+4+5+7+9+7+7 +#else +#define CDC2_DATA_INTERFACE_DESC_SIZE 0 +#endif + +#define CDC3_DATA_INTERFACE_DESC_POS CDC2_DATA_INTERFACE_DESC_POS+CDC2_DATA_INTERFACE_DESC_SIZE +#ifdef CDC3_DATA_INTERFACE +#define CDC3_DATA_INTERFACE_DESC_SIZE 8 + 9+5+5+4+5+7+9+7+7 +#else +#define CDC3_DATA_INTERFACE_DESC_SIZE 0 +#endif + +#define MIDI_INTERFACE_DESC_POS CDC3_DATA_INTERFACE_DESC_POS+CDC3_DATA_INTERFACE_DESC_SIZE +#ifdef MIDI_INTERFACE + #if !defined(MIDI_NUM_CABLES) || MIDI_NUM_CABLES < 1 || MIDI_NUM_CABLES > 16 + #error "MIDI_NUM_CABLES must be defined between 1 to 16" + #endif +#define MIDI_INTERFACE_DESC_SIZE 9+7+((6+6+9+9)*MIDI_NUM_CABLES)+(9+4+MIDI_NUM_CABLES)*2 +#else +#define MIDI_INTERFACE_DESC_SIZE 0 +#endif + +#define KEYBOARD_INTERFACE_DESC_POS MIDI_INTERFACE_DESC_POS+MIDI_INTERFACE_DESC_SIZE +#ifdef KEYBOARD_INTERFACE +#define KEYBOARD_INTERFACE_DESC_SIZE 9+9+7 +#define KEYBOARD_HID_DESC_OFFSET KEYBOARD_INTERFACE_DESC_POS+9 +#else +#define KEYBOARD_INTERFACE_DESC_SIZE 0 +#endif + +#define MOUSE_INTERFACE_DESC_POS KEYBOARD_INTERFACE_DESC_POS+KEYBOARD_INTERFACE_DESC_SIZE +#ifdef MOUSE_INTERFACE +#define MOUSE_INTERFACE_DESC_SIZE 9+9+7 +#define MOUSE_HID_DESC_OFFSET MOUSE_INTERFACE_DESC_POS+9 +#else +#define MOUSE_INTERFACE_DESC_SIZE 0 +#endif + +#define RAWHID_INTERFACE_DESC_POS MOUSE_INTERFACE_DESC_POS+MOUSE_INTERFACE_DESC_SIZE +#ifdef RAWHID_INTERFACE +#define RAWHID_INTERFACE_DESC_SIZE 9+9+7+7 +#define RAWHID_HID_DESC_OFFSET RAWHID_INTERFACE_DESC_POS+9 +#else +#define RAWHID_INTERFACE_DESC_SIZE 0 +#endif + +#define FLIGHTSIM_INTERFACE_DESC_POS RAWHID_INTERFACE_DESC_POS+RAWHID_INTERFACE_DESC_SIZE +#ifdef FLIGHTSIM_INTERFACE +#define FLIGHTSIM_INTERFACE_DESC_SIZE 9+9+7+7 +#define FLIGHTSIM_HID_DESC_OFFSET FLIGHTSIM_INTERFACE_DESC_POS+9 +#else +#define FLIGHTSIM_INTERFACE_DESC_SIZE 0 +#endif + +#define SEREMU_INTERFACE_DESC_POS FLIGHTSIM_INTERFACE_DESC_POS+FLIGHTSIM_INTERFACE_DESC_SIZE +#ifdef SEREMU_INTERFACE +#define SEREMU_INTERFACE_DESC_SIZE 9+9+7+7 +#define SEREMU_HID_DESC_OFFSET SEREMU_INTERFACE_DESC_POS+9 +#else +#define SEREMU_INTERFACE_DESC_SIZE 0 +#endif + +#define JOYSTICK_INTERFACE_DESC_POS SEREMU_INTERFACE_DESC_POS+SEREMU_INTERFACE_DESC_SIZE +#ifdef JOYSTICK_INTERFACE +#define JOYSTICK_INTERFACE_DESC_SIZE 9+9+7 +#define JOYSTICK_HID_DESC_OFFSET JOYSTICK_INTERFACE_DESC_POS+9 +#else +#define JOYSTICK_INTERFACE_DESC_SIZE 0 +#endif + +#define MTP_INTERFACE_DESC_POS JOYSTICK_INTERFACE_DESC_POS+JOYSTICK_INTERFACE_DESC_SIZE +#ifdef MTP_INTERFACE +#define MTP_INTERFACE_DESC_SIZE 9+7+7+7 +#else +#define MTP_INTERFACE_DESC_SIZE 0 +#endif + +#define KEYMEDIA_INTERFACE_DESC_POS MTP_INTERFACE_DESC_POS+MTP_INTERFACE_DESC_SIZE +#ifdef KEYMEDIA_INTERFACE +#define KEYMEDIA_INTERFACE_DESC_SIZE 9+9+7 +#define KEYMEDIA_HID_DESC_OFFSET KEYMEDIA_INTERFACE_DESC_POS+9 +#else +#define KEYMEDIA_INTERFACE_DESC_SIZE 0 +#endif + +#define AUDIO_INTERFACE_DESC_POS KEYMEDIA_INTERFACE_DESC_POS+KEYMEDIA_INTERFACE_DESC_SIZE +#ifdef AUDIO_INTERFACE + +#if USB_AUDIO_NO_CHANNELS_480 > 8 + //we restrict the volume controlled number of channels to 8 (there is still the master volume for all channels) + #define FEATURE_UNIT_DESC_SIZE_480 (6 + (1+8)*4) +#else + #define FEATURE_UNIT_DESC_SIZE_480 (6 + (1+USB_AUDIO_NO_CHANNELS_480)*4) +#endif + +#if USB_AUDIO_NO_CHANNELS_12 > 8 + //we restrict the volume controlled number of channels to 8 (there is still the master volume for all channels) + #define FEATURE_UNIT_DESC_SIZE_12 (6 + (1+8)*4) +#else + #define FEATURE_UNIT_DESC_SIZE_12 (6 + (1+USB_AUDIO_NO_CHANNELS_12)*4) +#endif + +#define AUDIO_INTERFACE_DESC_SIZE_480 8 + 9+9+8+17+12+17+FEATURE_UNIT_DESC_SIZE_480+12 + 9+9+16+6+7+8 + 9+9+16+6+7+8+7 +#define AUDIO_INTERFACE_DESC_SIZE_12 8 + 9+9+8+17+12+17+FEATURE_UNIT_DESC_SIZE_12+12 + 9+9+16+6+7+8 + 9+9+16+6+7+8+7 +#else +#define AUDIO_INTERFACE_DESC_SIZE_12 0 +#define AUDIO_INTERFACE_DESC_SIZE_480 0 +#endif + +#define MULTITOUCH_INTERFACE_DESC_POS_12 AUDIO_INTERFACE_DESC_POS+AUDIO_INTERFACE_DESC_SIZE_12 +#define MULTITOUCH_INTERFACE_DESC_POS_480 AUDIO_INTERFACE_DESC_POS+AUDIO_INTERFACE_DESC_SIZE_480 +#ifdef MULTITOUCH_INTERFACE +#define MULTITOUCH_INTERFACE_DESC_SIZE 9+9+7 +#define MULTITOUCH_HID_DESC_OFFSET MULTITOUCH_INTERFACE_DESC_POS+9 +#else +#define MULTITOUCH_INTERFACE_DESC_SIZE 0 +#endif + +#define EXPERIMENTAL_INTERFACE_DESC_POS_12 MULTITOUCH_INTERFACE_DESC_POS_12+MULTITOUCH_INTERFACE_DESC_SIZE +#define EXPERIMENTAL_INTERFACE_DESC_POS_480 MULTITOUCH_INTERFACE_DESC_POS_480+MULTITOUCH_INTERFACE_DESC_SIZE +#ifdef EXPERIMENTAL_INTERFACE +#define EXPERIMENTAL_INTERFACE_DESC_SIZE 9+7+7 +#define EXPERIMENTAL_HID_DESC_OFFSET MULTITOUCH_INTERFACE_DESC_POS+9 +#else +#define EXPERIMENTAL_INTERFACE_DESC_SIZE 0 +#endif + +#define CONFIG_DESC_SIZE_12 EXPERIMENTAL_INTERFACE_DESC_POS_12+EXPERIMENTAL_INTERFACE_DESC_SIZE +#define CONFIG_DESC_SIZE_480 EXPERIMENTAL_INTERFACE_DESC_POS_480+EXPERIMENTAL_INTERFACE_DESC_SIZE + + + +// ************************************************************** +// USB Configuration +// ************************************************************** + +// USB Configuration Descriptor. This huge descriptor tells all +// of the devices capabilities. + +PROGMEM const uint8_t usb_config_descriptor_480[CONFIG_DESC_SIZE_480] = { + // configuration descriptor, USB spec 9.6.3, page 264-266, Table 9-10 + 9, // bLength; + 2, // bDescriptorType; + LSB(CONFIG_DESC_SIZE_480), // wTotalLength + MSB(CONFIG_DESC_SIZE_480), + NUM_INTERFACE, // bNumInterfaces + 1, // bConfigurationValue + 0, // iConfiguration + 0xC0, // bmAttributes + 50, // bMaxPower + +#ifdef CDC_IAD_DESCRIPTOR + // interface association descriptor, USB ECN, Table 9-Z + 8, // bLength + 11, // bDescriptorType + CDC_STATUS_INTERFACE, // bFirstInterface + 2, // bInterfaceCount + 0x02, // bFunctionClass + 0x02, // bFunctionSubClass + 0x01, // bFunctionProtocol + 0, // iFunction +#endif + +#ifdef CDC_DATA_INTERFACE + // configuration for 480 Mbit/sec speed + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + CDC_STATUS_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 1, // bNumEndpoints + 0x02, // bInterfaceClass + 0x02, // bInterfaceSubClass + 0x01, // bInterfaceProtocol + 0, // iInterface + // CDC Header Functional Descriptor, CDC Spec 5.2.3.1, Table 26 + 5, // bFunctionLength + 0x24, // bDescriptorType + 0x00, // bDescriptorSubtype + 0x10, 0x01, // bcdCDC + // Call Management Functional Descriptor, CDC Spec 5.2.3.2, Table 27 + 5, // bFunctionLength + 0x24, // bDescriptorType + 0x01, // bDescriptorSubtype + 0x01, // bmCapabilities + 1, // bDataInterface + // Abstract Control Management Functional Descriptor, CDC Spec 5.2.3.3, Table 28 + 4, // bFunctionLength + 0x24, // bDescriptorType + 0x02, // bDescriptorSubtype + 0x06, // bmCapabilities + // Union Functional Descriptor, CDC Spec 5.2.3.8, Table 33 + 5, // bFunctionLength + 0x24, // bDescriptorType + 0x06, // bDescriptorSubtype + CDC_STATUS_INTERFACE, // bMasterInterface + CDC_DATA_INTERFACE, // bSlaveInterface0 + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + CDC_ACM_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + LSB(CDC_ACM_SIZE),MSB(CDC_ACM_SIZE), // wMaxPacketSize + 5, // bInterval + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + CDC_DATA_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 2, // bNumEndpoints + 0x0A, // bInterfaceClass + 0x00, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0, // iInterface + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + CDC_RX_ENDPOINT, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + LSB(CDC_RX_SIZE_480),MSB(CDC_RX_SIZE_480),// wMaxPacketSize + 0, // bInterval + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + CDC_TX_ENDPOINT | 0x80, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + LSB(CDC_TX_SIZE_480),MSB(CDC_TX_SIZE_480),// wMaxPacketSize + 0, // bInterval +#endif // CDC_DATA_INTERFACE + +#ifdef CDC2_DATA_INTERFACE + // configuration for 480 Mbit/sec speed + // interface association descriptor, USB ECN, Table 9-Z + 8, // bLength + 11, // bDescriptorType + CDC2_STATUS_INTERFACE, // bFirstInterface + 2, // bInterfaceCount + 0x02, // bFunctionClass + 0x02, // bFunctionSubClass + 0x01, // bFunctionProtocol + 0, // iFunction + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + CDC2_STATUS_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 1, // bNumEndpoints + 0x02, // bInterfaceClass + 0x02, // bInterfaceSubClass + 0x01, // bInterfaceProtocol + 0, // iInterface + // CDC Header Functional Descriptor, CDC Spec 5.2.3.1, Table 26 + 5, // bFunctionLength + 0x24, // bDescriptorType + 0x00, // bDescriptorSubtype + 0x10, 0x01, // bcdCDC + // Call Management Functional Descriptor, CDC Spec 5.2.3.2, Table 27 + 5, // bFunctionLength + 0x24, // bDescriptorType + 0x01, // bDescriptorSubtype + 0x01, // bmCapabilities + 1, // bDataInterface + // Abstract Control Management Functional Descriptor, CDC Spec 5.2.3.3, Table 28 + 4, // bFunctionLength + 0x24, // bDescriptorType + 0x02, // bDescriptorSubtype + 0x06, // bmCapabilities + // Union Functional Descriptor, CDC Spec 5.2.3.8, Table 33 + 5, // bFunctionLength + 0x24, // bDescriptorType + 0x06, // bDescriptorSubtype + CDC2_STATUS_INTERFACE, // bMasterInterface + CDC2_DATA_INTERFACE, // bSlaveInterface0 + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + CDC2_ACM_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + CDC_ACM_SIZE, 0, // wMaxPacketSize + 5, // bInterval + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + CDC2_DATA_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 2, // bNumEndpoints + 0x0A, // bInterfaceClass + 0x00, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0, // iInterface + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + CDC2_RX_ENDPOINT, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + LSB(CDC_RX_SIZE_480),MSB(CDC_RX_SIZE_480),// wMaxPacketSize + 0, // bInterval + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + CDC2_TX_ENDPOINT | 0x80, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + LSB(CDC_TX_SIZE_480),MSB(CDC_TX_SIZE_480),// wMaxPacketSize + 0, // bInterval +#endif // CDC2_DATA_INTERFACE + +#ifdef CDC3_DATA_INTERFACE + // configuration for 480 Mbit/sec speed + // interface association descriptor, USB ECN, Table 9-Z + 8, // bLength + 11, // bDescriptorType + CDC3_STATUS_INTERFACE, // bFirstInterface + 2, // bInterfaceCount + 0x02, // bFunctionClass + 0x02, // bFunctionSubClass + 0x01, // bFunctionProtocol + 0, // iFunction + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + CDC3_STATUS_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 1, // bNumEndpoints + 0x02, // bInterfaceClass + 0x02, // bInterfaceSubClass + 0x01, // bInterfaceProtocol + 0, // iInterface + // CDC Header Functional Descriptor, CDC Spec 5.2.3.1, Table 26 + 5, // bFunctionLength + 0x24, // bDescriptorType + 0x00, // bDescriptorSubtype + 0x10, 0x01, // bcdCDC + // Call Management Functional Descriptor, CDC Spec 5.2.3.2, Table 27 + 5, // bFunctionLength + 0x24, // bDescriptorType + 0x01, // bDescriptorSubtype + 0x01, // bmCapabilities + 1, // bDataInterface + // Abstract Control Management Functional Descriptor, CDC Spec 5.2.3.3, Table 28 + 4, // bFunctionLength + 0x24, // bDescriptorType + 0x02, // bDescriptorSubtype + 0x06, // bmCapabilities + // Union Functional Descriptor, CDC Spec 5.2.3.8, Table 33 + 5, // bFunctionLength + 0x24, // bDescriptorType + 0x06, // bDescriptorSubtype + CDC3_STATUS_INTERFACE, // bMasterInterface + CDC3_DATA_INTERFACE, // bSlaveInterface0 + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + CDC3_ACM_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + CDC_ACM_SIZE, 0, // wMaxPacketSize + 5, // bInterval + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + CDC3_DATA_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 2, // bNumEndpoints + 0x0A, // bInterfaceClass + 0x00, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0, // iInterface + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + CDC3_RX_ENDPOINT, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + LSB(CDC_RX_SIZE_480),MSB(CDC_RX_SIZE_480),// wMaxPacketSize + 0, // bInterval + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + CDC3_TX_ENDPOINT | 0x80, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + LSB(CDC_TX_SIZE_480),MSB(CDC_TX_SIZE_480),// wMaxPacketSize + 0, // bInterval +#endif // CDC3_DATA_INTERFACE + +#ifdef KEYBOARD_INTERFACE + // configuration for 480 Mbit/sec speed + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + KEYBOARD_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 1, // bNumEndpoints + 0x03, // bInterfaceClass (0x03 = HID) + 0x01, // bInterfaceSubClass (0x01 = Boot) + 0x01, // bInterfaceProtocol (0x01 = Keyboard) + 0, // iInterface + // HID interface descriptor, HID 1.11 spec, section 6.2.1 + 9, // bLength + 0x21, // bDescriptorType + 0x11, 0x01, // bcdHID + 0, // bCountryCode + 1, // bNumDescriptors + 0x22, // bDescriptorType + LSB(sizeof(keyboard_report_desc)), // wDescriptorLength + MSB(sizeof(keyboard_report_desc)), + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + KEYBOARD_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + KEYBOARD_SIZE, 0, // wMaxPacketSize + KEYBOARD_INTERVAL, // bInterval +#endif // KEYBOARD_INTERFACE + +#ifdef MOUSE_INTERFACE + // configuration for 480 Mbit/sec speed + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + MOUSE_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 1, // bNumEndpoints + 0x03, // bInterfaceClass (0x03 = HID) + 0x00, // bInterfaceSubClass (0x01 = Boot) + 0x00, // bInterfaceProtocol (0x02 = Mouse) + 0, // iInterface + // HID interface descriptor, HID 1.11 spec, section 6.2.1 + 9, // bLength + 0x21, // bDescriptorType + 0x11, 0x01, // bcdHID + 0, // bCountryCode + 1, // bNumDescriptors + 0x22, // bDescriptorType + LSB(sizeof(mouse_report_desc)), // wDescriptorLength + MSB(sizeof(mouse_report_desc)), + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + MOUSE_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + MOUSE_SIZE, 0, // wMaxPacketSize + MOUSE_INTERVAL, // bInterval +#endif // MOUSE_INTERFACE + +#ifdef RAWHID_INTERFACE + // configuration for 480 Mbit/sec speed + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + RAWHID_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 2, // bNumEndpoints + 0x03, // bInterfaceClass (0x03 = HID) + 0x00, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0, // iInterface + // HID interface descriptor, HID 1.11 spec, section 6.2.1 + 9, // bLength + 0x21, // bDescriptorType + 0x11, 0x01, // bcdHID + 0, // bCountryCode + 1, // bNumDescriptors + 0x22, // bDescriptorType + LSB(sizeof(rawhid_report_desc)), // wDescriptorLength + MSB(sizeof(rawhid_report_desc)), + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + RAWHID_TX_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + RAWHID_TX_SIZE, 0, // wMaxPacketSize + RAWHID_TX_INTERVAL, // bInterval + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + RAWHID_RX_ENDPOINT, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + RAWHID_RX_SIZE, 0, // wMaxPacketSize + RAWHID_RX_INTERVAL, // bInterval +#endif // RAWHID_INTERFACE + +#ifdef FLIGHTSIM_INTERFACE + // configuration for 480 Mbit/sec speed + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + FLIGHTSIM_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 2, // bNumEndpoints + 0x03, // bInterfaceClass (0x03 = HID) + 0x00, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0, // iInterface + // HID interface descriptor, HID 1.11 spec, section 6.2.1 + 9, // bLength + 0x21, // bDescriptorType + 0x11, 0x01, // bcdHID + 0, // bCountryCode + 1, // bNumDescriptors + 0x22, // bDescriptorType + LSB(sizeof(flightsim_report_desc)), // wDescriptorLength + MSB(sizeof(flightsim_report_desc)), + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + FLIGHTSIM_TX_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + FLIGHTSIM_TX_SIZE, 0, // wMaxPacketSize + FLIGHTSIM_TX_INTERVAL, // bInterval + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + FLIGHTSIM_RX_ENDPOINT, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + FLIGHTSIM_RX_SIZE, 0, // wMaxPacketSize + FLIGHTSIM_RX_INTERVAL, // bInterval +#endif // FLIGHTSIM_INTERFACE + +#ifdef SEREMU_INTERFACE + // configuration for 480 Mbit/sec speed + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + SEREMU_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 2, // bNumEndpoints + 0x03, // bInterfaceClass (0x03 = HID) + 0x00, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0, // iInterface + // HID interface descriptor, HID 1.11 spec, section 6.2.1 + 9, // bLength + 0x21, // bDescriptorType + 0x11, 0x01, // bcdHID + 0, // bCountryCode + 1, // bNumDescriptors + 0x22, // bDescriptorType + LSB(sizeof(seremu_report_desc)), // wDescriptorLength + MSB(sizeof(seremu_report_desc)), + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + SEREMU_TX_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + SEREMU_TX_SIZE, 0, // wMaxPacketSize + SEREMU_TX_INTERVAL, // bInterval + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + SEREMU_RX_ENDPOINT, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + SEREMU_RX_SIZE, 0, // wMaxPacketSize + SEREMU_RX_INTERVAL, // bInterval +#endif // SEREMU_INTERFACE + +#ifdef JOYSTICK_INTERFACE + // configuration for 480 Mbit/sec speed + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + JOYSTICK_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 1, // bNumEndpoints + 0x03, // bInterfaceClass (0x03 = HID) + 0x00, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0, // iInterface + // HID interface descriptor, HID 1.11 spec, section 6.2.1 + 9, // bLength + 0x21, // bDescriptorType + 0x11, 0x01, // bcdHID + 0, // bCountryCode + 1, // bNumDescriptors + 0x22, // bDescriptorType + LSB(sizeof(joystick_report_desc)), // wDescriptorLength + MSB(sizeof(joystick_report_desc)), + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + JOYSTICK_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + JOYSTICK_SIZE, 0, // wMaxPacketSize + JOYSTICK_INTERVAL, // bInterval +#endif // JOYSTICK_INTERFACE + +#ifdef MTP_INTERFACE + // configuration for 480 Mbit/sec speed + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + MTP_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 3, // bNumEndpoints + 0x06, // bInterfaceClass (0x06 = still image) + 0x01, // bInterfaceSubClass + 0x01, // bInterfaceProtocol + 4, // iInterface + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + MTP_TX_ENDPOINT | 0x80, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + LSB(MTP_TX_SIZE_480),MSB(MTP_TX_SIZE_480), // wMaxPacketSize + 0, // bInterval + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + MTP_RX_ENDPOINT, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + LSB(MTP_RX_SIZE_480),MSB(MTP_RX_SIZE_480), // wMaxPacketSize + 0, // bInterval + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + MTP_EVENT_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + MTP_EVENT_SIZE, 0, // wMaxPacketSize + MTP_EVENT_INTERVAL_480, // bInterval +#endif // MTP_INTERFACE + +#ifdef KEYMEDIA_INTERFACE + // configuration for 480 Mbit/sec speed + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + KEYMEDIA_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 1, // bNumEndpoints + 0x03, // bInterfaceClass (0x03 = HID) + 0x00, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0, // iInterface + // HID interface descriptor, HID 1.11 spec, section 6.2.1 + 9, // bLength + 0x21, // bDescriptorType + 0x11, 0x01, // bcdHID + 0, // bCountryCode + 1, // bNumDescriptors + 0x22, // bDescriptorType + LSB(sizeof(keymedia_report_desc)), // wDescriptorLength + MSB(sizeof(keymedia_report_desc)), + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + KEYMEDIA_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + KEYMEDIA_SIZE, 0, // wMaxPacketSize + KEYMEDIA_INTERVAL, // bInterval +#endif // KEYMEDIA_INTERFACE + +#ifdef AUDIO_INTERFACE + // UAC2: + // Standard Interface Association Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.6, Table 4-3 page 46 + 8, // bLength + 11, // bDescriptorType + AUDIO_INTERFACE, // bFirstInterface + 3, // bInterfaceCount + 0x01, // bFunctionClass + 0x00, // bFunctionSubClass, 0x00 = FUNCTION_SUBCLASS_UNDEFINED + 0x20, // bFunctionProtocol 0x20 = IP_VERSION_02_00 = AF_VERSION_02_00 + 0, // iFunction + // UAC2:: + // Standard AudioControl (AC) Interface Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.7.1, Table 4-4 page 47 + 9, // bLength + 4, // bDescriptorType, 4 = INTERFACE + AUDIO_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 0, // bNumEndpoints + 1, // bInterfaceClass, 1 = AUDIO + 1, // bInterfaceSubclass, 1 = AUDIO_CONTROL + 0x20, // bInterfaceProtocol, IP_VERSION_02_00==0x20 + 0, // iInterface + // UAC2: + // Input Terminal Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.7.2, Table 4-5 page 48-49 + 9, // bLength + 0x24, // bDescriptorType, 0x24 = CS_INTERFACE + 0x01, // bDescriptorSubtype, 1 = HEADER + 0x00, 0x02, // bcdADC (version 2.0) + 0xff, // bCategory 0xff = other + LSB(75+FEATURE_UNIT_DESC_SIZE_480), MSB(75+FEATURE_UNIT_DESC_SIZE_480),// wTotalLength: size of this descriptor + all input and output and feature unit descriptors + clock sources + 0, // bmControls + // UAC2: + // Clock Source Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.7.2.1, Table 4-9 page 49-50 + 8, // bLength + 0x24, // bDescriptorType, 0x24 = CS_INTERFACE + 0x0a, // bDescriptorSubType, 2 = CLOCK_SOURCE + 0x3a, // bClockID + 0x01, // Internal fixed clock, not synchronized to SOF + 1 | 4, // bmControls: 1: clock frequency control is read only, 4 clock validity control is read only + 0, // bAssocTerminal + 0, // iClockSource + // UAC2: + // Input Terminal Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.7.2.4, Table 4-9 page 52-53 + 17, // bLength + 0x24, // bDescriptorType, 0x24 = CS_INTERFACE + 2, // bDescriptorSubType, 2 = INPUT_TERMINAL + 1, // bTerminalID + 0x02, 0x06, // wTerminalType, 0x0602 = Digital Audio + 0, // bAssocTerminal, 0 = unidirectional + 0x3a, // bCSourceID + USB_AUDIO_NO_CHANNELS_480, // bNrChannels + LSB(CHANNEL_CONFIG_480), // bmChannelConfig + MSB(CHANNEL_CONFIG_480), // bmChannelConfig + (((CHANNEL_CONFIG_480) >> 16) & 255),// bmChannelConfig + (((CHANNEL_CONFIG_480) >> 24) & 255),// bmChannelConfig + 0x00, // iChannelNames + 0x00, 0x00, // bmControls e.g. copy protect readable and writeable + 0, // iTerminal + // UAC2: + // Output Terminal Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.7.2.5, Table 4-10 page 53 + 12, // bLength + 0x24, // bDescriptorType, 0x24 = CS_INTERFACE + 3, // bDescriptorSubtype, 3 = OUTPUT_TERMINAL + 2, // bTerminalID + 0x01, 0x01, // wTerminalType, 0x0101 = USB_STREAMING + 0, // bAssocTerminal, 0 = unidirectional + 1, // bSourceID, connected to feature, ID=1 + 0x3a, // bCSourceID + 0,0, // bmControls + 0, // iTerminal + // UAC2: + // Input Terminal Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.7.2.4, Table 4-9 page 52-53 + 17, // bLength + 0x24, // bDescriptorType, 0x24 = CS_INTERFACE + 2, // bDescriptorSubType, 2 = INPUT_TERMINAL + 3, // bTerminalID + 0x01, 0x01, // wTerminalType, 0x0101 = USB_STREAMING + 0, // bAssocTerminal, 0 = unidirectional + 0x3a, // bCSourceID + USB_AUDIO_NO_CHANNELS_480, // bNrChannels + LSB(CHANNEL_CONFIG_480), // bmChannelConfig + MSB(CHANNEL_CONFIG_480), // bmChannelConfig + (((CHANNEL_CONFIG_480) >> 16) & 255),// bmChannelConfig + (((CHANNEL_CONFIG_480) >> 24) & 255),// bmChannelConfig + 0x00, // iChannelNames + 0x00, 0x00, // bmControls e.g.: copy protect readable and writeable + 0, // iTerminal + // UAC2: + // Feature Unit Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.7.2.8, Table 4-13 page 58-59 + FEATURE_UNIT_DESC_SIZE_480, // bLength + 0x24, // bDescriptorType = CS_INTERFACE + 0x06, // bDescriptorSubType = FEATURE_UNIT + 0x31, // bUnitID + 0x03, // bSourceID (Input Terminal) + 15,0x00,0x00,0x00, // bmaControls(0) Master: Mute bit 0 and 1 + bit 2 + 3 (reading and writing master volume) + 12,0x00,0x00,0x00, // bmaControls(1) Left: Volume bit 2 and 3 + 12,0x00,0x00,0x00, // bmaControls(2) Right: Volume bit 2 and 3 +#if USB_AUDIO_NO_CHANNELS_480 > 2 + 12,0x00,0x00,0x00, // bmaControls(1) channel 3: Volume bit 2 and 3 + 12,0x00,0x00,0x00, // bmaControls(2) channel 4: Volume bit 2 and 3 +#endif +#if USB_AUDIO_NO_CHANNELS_480 > 4 + 12,0x00,0x00,0x00, // bmaControls(1) channel 5: Volume bit 2 and 3 + 12,0x00,0x00,0x00, // bmaControls(2) channel 6: Volume bit 2 and 3 +#endif +#if USB_AUDIO_NO_CHANNELS_480 > 6 + 12,0x00,0x00,0x00, // bmaControls(1) channel 7: Volume bit 2 and 3 + 12,0x00,0x00,0x00, // bmaControls(2) channel 8: Volume bit 2 and 3 +#endif + 0x00, // iFeature + // UAC2: + // Output Terminal Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.7.2.5, Table 4-10 page 53 + 12, // bLength + 0x24, // bDescriptorType, 0x24 = CS_INTERFACE + 3, // bDescriptorSubtype, 3 = OUTPUT_TERMINAL + 4, // bTerminalID + 0x02, 0x06, // wTerminalType, 0x0602 = Digital Audio + 0, // bAssocTerminal, 0 = unidirectional + 0x31, // bSourceID, connected to feature, ID=31 + 0x3a, // bCSourceID + 0,0, // bmControls + 0, // iTerminal + // UAC2: + // Standard AS Interface Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.9.1, Table 4-26 page 75 + // Alternate 0: default setting, disabled zero bandwidth + 9, // bLenght + 4, // bDescriptorType = INTERFACE + AUDIO_INTERFACE+1, // bInterfaceNumber + 0, // bAlternateSetting + 0, // bNumEndpoints + 1, // bInterfaceClass, 1 = AUDIO + 2, // bInterfaceSubclass, 2 = AUDIO_STREAMING + 0x20, // bInterfaceProtocol IP_VERSION_02_00==0x20 + 0, // iInterface + // Alternate 1: streaming data + 9, // bLenght + 4, // bDescriptorType = INTERFACE + AUDIO_INTERFACE+1, // bInterfaceNumber + 1, // bAlternateSetting + 1, // bNumEndpoints + 1, // bInterfaceClass, 1 = AUDIO + 2, // bInterfaceSubclass, 2 = AUDIO_STREAMING + 0x20, // bInterfaceProtocol IP_VERSION_02_00==0x20 + 0, // iInterface + // UAC2: + // Class-Specific AS Interface Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.9.2, Table 4-27 page 75-77 + 16, // bLength + 0x24, // bDescriptorType = CS_INTERFACE + 1, // bDescriptorSubtype, 1 = AS_GENERAL + 2, // bTerminalLink: Terminal ID = 2 + 0, // bmControls + 1, // bFormatType 1=FORMAT_TYPE_I + 0x01, 0x00, 0x00, 0x00, // bmFormats first bit: PCM + USB_AUDIO_NO_CHANNELS_480, // bNrChannels + LSB(CHANNEL_CONFIG_480), // bmChannelConfig + MSB(CHANNEL_CONFIG_480), // bmChannelConfig + (((CHANNEL_CONFIG_480) >> 16) & 255),// bmChannelConfig + (((CHANNEL_CONFIG_480) >> 24) & 255),// bmChannelConfig + 0, //iChannelNames index of string descriptor at the bottom of the file + // UAC2: + // Univeral Serial Bus Device Class Definition for Audio Data Formats 2.0, Section 2.3.1.6, Table 2-2, page 15-16 + 6, // bLength + 0x24, // bDescriptorType = CS_INTERFACE + 2, // bDescriptorSubtype = FORMAT_TYPE + 1, // bFormatType = FORMAT_TYPE_I + AUDIO_SUBSLOT_SIZE, // bSubSlotSize = size of a single sample in bytes (e.g. 2 bytes for 16bit audio) + AUDIO_BITRESOLUTION, // bBitResolution = 16 bits + // UAC2: + // Standard AS Isochronous Audio Data Endpoint Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.10.1.1, Table 4-33 page 85-86 + 7, // bLength + 5, // bDescriptorType, 5 = ENDPOINT_DESCRIPTOR + AUDIO_TX_ENDPOINT | 0x80, // bEndpointAddress (0x80 = 10000000 -> audio source) +#ifdef ASYNC_TX_ENDPOINT + 0x05, // bmAttributes, 0x09 = isochronous, adaptive, 0x05 = isochronous, asynchronous +#elif + 0x09, // bmAttributes, 0x09 = isochronous, adaptive, 0x05 = isochronous, asynchronous +#endif + LSB(AUDIO_TX_SIZE_480), MSB(AUDIO_TX_SIZE_480), // wMaxPacketSize + AUDIO_POLLING_INTERVAL_480, // bInterval, 4 -> 2^(4-1)=8 -> every 8 micro-frames + // UAC2: + // Class-Specific AS Isochronous Audio Data Endpoint Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.10.1.2, Table 4-34 page 86-87 + 8, // bLength + 0x25, // bDescriptorType, 0x25 = CS_ENDPOINT + 1, // bDescriptorSubtype, 1 = EP_GENERAL + 0x00, // bmAttributes + 0x00, // bmControls + 0, // bLockDelayUnits, 1 = ms + 0x00, 0x00, // wLockDelay + // UAC2: + // Standard AS Interface Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.9.1, Table 4-26 page 75 + // Alternate 0: default setting, disabled zero bandwidth + 9, // bLenght + 4, // bDescriptorType = INTERFACE + AUDIO_INTERFACE+2, // bInterfaceNumber + 0, // bAlternateSetting + 0, // bNumEndpoints + 1, // bInterfaceClass, 1 = AUDIO + 2, // bInterfaceSubclass, 2 = AUDIO_STREAMING + 0x20, // bInterfaceProtocol IP_VERSION_02_00==0x20 + 0, // iInterface + // Alternate 1: streaming data + 9, // bLenght + 4, // bDescriptorType = INTERFACE + AUDIO_INTERFACE+2, // bInterfaceNumber + 1, // bAlternateSetting + 2, // bNumEndpoints + 1, // bInterfaceClass, 1 = AUDIO + 2, // bInterfaceSubclass, 2 = AUDIO_STREAMING + 0x20, // bInterfaceProtocol IP_VERSION_02_00==0x20 + 0, // iInterface + // UAC2: + // Class-Specific AS Interface Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.9.2, Table 4-27 page 75-77 + 16, // bLength + 0x24, // bDescriptorType = CS_INTERFACE + 1, // bDescriptorSubtype, 1 = AS_GENERAL + 3, // bTerminalLink: Terminal ID = 3 + 0, //bmControls + 1, //bFormatType 1=FORMAT_TYPE_I + 0x01, 0x00, 0x00, 0x00, //bmFormats first bit: PCM + USB_AUDIO_NO_CHANNELS_480, // bNrChannels + LSB(CHANNEL_CONFIG_480), // bmChannelConfig + MSB(CHANNEL_CONFIG_480), // bmChannelConfig + (((CHANNEL_CONFIG_480) >> 16) & 255),// bmChannelConfig + (((CHANNEL_CONFIG_480) >> 24) & 255),// bmChannelConfig + 0, //iChannelNames index of string descriptor at the bottom of the file + // UAC2: + // Univeral Serial Bus Device Class Definition for Audio Data Formats 2.0, Section 2.3.1.6, Table 2-2, page 15-16 + 6, // bLength + 0x24, // bDescriptorType = CS_INTERFACE + 2, // bDescriptorSubtype = FORMAT_TYPE + 1, // bFormatType = FORMAT_TYPE_I + AUDIO_SUBSLOT_SIZE, // bSubSlotSize = size of a single sample in bytes (e.g. 2 bytes for 16bit audio) + AUDIO_BITRESOLUTION, // bBitResolution = 16 bits + // UAC2: + // Standard AS Isochronous Audio Data Endpoint Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.10.1.1, Table 4-33 page 85-86 + 7, // bLength + 5, // bDescriptorType, 5 = ENDPOINT_DESCRIPTOR + AUDIO_RX_ENDPOINT | 0x00, // bEndpointAddress (0x00 = 00000000 -> audio sink) + 0x05, // bmAttributes = isochronous, asynchronous + LSB(AUDIO_RX_SIZE_480), MSB(AUDIO_RX_SIZE_480), // wMaxPacketSize + AUDIO_POLLING_INTERVAL_480, // bInterval, 4 -> 2^(4-1)=8 -> every 8 micro-frames + // UAC2: + // Class-Specific AS Isochronous Audio Data Endpoint Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.10.1.2, Table 4-34 page 86-87 + 8, // bLength + 0x25, // bDescriptorType, 0x25 = CS_ENDPOINT + 1, // bDescriptorSubtype, 1 = EP_GENERAL + 0x00, // bmAttributes + 0x00, // bmControls + 0, // bLockDelayUnits, 1 = ms + 0x00, 0x00, // wLockDelay + // UAC2: + // Standard AS Isochronous Feedback Endpoint Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.10.2.1, Table 4-35 page 87-88 + 7, // bLength + 5, // bDescriptorType, 5 = ENDPOINT_DESCRIPTOR + AUDIO_SYNC_ENDPOINT | 0x80, // bEndpointAddress, 0x80=10000000 -> IN endpoint + 0x11, // bmAttributes = isochronous, feedback + 4, 0, // wMaxPacketSize, 4 bytes + AUDIO_POLLING_INTERVAL_480, // bInterval, 4 -> 2^(4-1) = 8 -> every 8 micro-frames +#endif + +#ifdef MIDI_INTERFACE + // configuration for 480 Mbit/sec speed + // Standard MS Interface Descriptor, + 9, // bLength + 4, // bDescriptorType + MIDI_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 2, // bNumEndpoints + 0x01, // bInterfaceClass (0x01 = Audio) + 0x03, // bInterfaceSubClass (0x03 = MIDI) + 0x00, // bInterfaceProtocol (unused for MIDI) + 0, // iInterface + // MIDI MS Interface Header, USB MIDI 6.1.2.1, page 21, Table 6-2 + 7, // bLength + 0x24, // bDescriptorType = CS_INTERFACE + 0x01, // bDescriptorSubtype = MS_HEADER + 0x00, 0x01, // bcdMSC = revision 01.00 + LSB(7+(6+6+9+9)*MIDI_NUM_CABLES), // wTotalLength + MSB(7+(6+6+9+9)*MIDI_NUM_CABLES), + // MIDI IN Jack Descriptor, B.4.3, Table B-7 (embedded), page 40 + 6, // bLength + 0x24, // bDescriptorType = CS_INTERFACE + 0x02, // bDescriptorSubtype = MIDI_IN_JACK + 0x01, // bJackType = EMBEDDED + 1, // bJackID, ID = 1 + 0, // iJack + // MIDI IN Jack Descriptor, B.4.3, Table B-8 (external), page 40 + 6, // bLength + 0x24, // bDescriptorType = CS_INTERFACE + 0x02, // bDescriptorSubtype = MIDI_IN_JACK + 0x02, // bJackType = EXTERNAL + 2, // bJackID, ID = 2 + 0, // iJack + // MIDI OUT Jack Descriptor, B.4.4, Table B-9, page 41 + 9, + 0x24, // bDescriptorType = CS_INTERFACE + 0x03, // bDescriptorSubtype = MIDI_OUT_JACK + 0x01, // bJackType = EMBEDDED + 3, // bJackID, ID = 3 + 1, // bNrInputPins = 1 pin + 2, // BaSourceID(1) = 2 + 1, // BaSourcePin(1) = first pin + 0, // iJack + // MIDI OUT Jack Descriptor, B.4.4, Table B-10, page 41 + 9, + 0x24, // bDescriptorType = CS_INTERFACE + 0x03, // bDescriptorSubtype = MIDI_OUT_JACK + 0x02, // bJackType = EXTERNAL + 4, // bJackID, ID = 4 + 1, // bNrInputPins = 1 pin + 1, // BaSourceID(1) = 1 + 1, // BaSourcePin(1) = first pin + 0, // iJack + #if MIDI_NUM_CABLES >= 2 + #define MIDI_INTERFACE_JACK_PAIR(a, b, c, d) \ + 6, 0x24, 0x02, 0x01, (a), 0, \ + 6, 0x24, 0x02, 0x02, (b), 0, \ + 9, 0x24, 0x03, 0x01, (c), 1, (b), 1, 0, \ + 9, 0x24, 0x03, 0x02, (d), 1, (a), 1, 0, + MIDI_INTERFACE_JACK_PAIR(5, 6, 7, 8) + #endif + #if MIDI_NUM_CABLES >= 3 + MIDI_INTERFACE_JACK_PAIR(9, 10, 11, 12) + #endif + #if MIDI_NUM_CABLES >= 4 + MIDI_INTERFACE_JACK_PAIR(13, 14, 15, 16) + #endif + #if MIDI_NUM_CABLES >= 5 + MIDI_INTERFACE_JACK_PAIR(17, 18, 19, 20) + #endif + #if MIDI_NUM_CABLES >= 6 + MIDI_INTERFACE_JACK_PAIR(21, 22, 23, 24) + #endif + #if MIDI_NUM_CABLES >= 7 + MIDI_INTERFACE_JACK_PAIR(25, 26, 27, 28) + #endif + #if MIDI_NUM_CABLES >= 8 + MIDI_INTERFACE_JACK_PAIR(29, 30, 31, 32) + #endif + #if MIDI_NUM_CABLES >= 9 + MIDI_INTERFACE_JACK_PAIR(33, 34, 35, 36) + #endif + #if MIDI_NUM_CABLES >= 10 + MIDI_INTERFACE_JACK_PAIR(37, 38, 39, 40) + #endif + #if MIDI_NUM_CABLES >= 11 + MIDI_INTERFACE_JACK_PAIR(41, 42, 43, 44) + #endif + #if MIDI_NUM_CABLES >= 12 + MIDI_INTERFACE_JACK_PAIR(45, 46, 47, 48) + #endif + #if MIDI_NUM_CABLES >= 13 + MIDI_INTERFACE_JACK_PAIR(49, 50, 51, 52) + #endif + #if MIDI_NUM_CABLES >= 14 + MIDI_INTERFACE_JACK_PAIR(53, 54, 55, 56) + #endif + #if MIDI_NUM_CABLES >= 15 + MIDI_INTERFACE_JACK_PAIR(57, 58, 59, 60) + #endif + #if MIDI_NUM_CABLES >= 16 + MIDI_INTERFACE_JACK_PAIR(61, 62, 63, 64) + #endif + // Standard Bulk OUT Endpoint Descriptor, B.5.1, Table B-11, pae 42 + 9, // bLength + 5, // bDescriptorType = ENDPOINT + MIDI_RX_ENDPOINT, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + LSB(MIDI_RX_SIZE_480),MSB(MIDI_RX_SIZE_480),// wMaxPacketSize + 0, // bInterval + 0, // bRefresh + 0, // bSynchAddress + // Class-specific MS Bulk OUT Endpoint Descriptor, B.5.2, Table B-12, page 42 + 4+MIDI_NUM_CABLES, // bLength + 0x25, // bDescriptorSubtype = CS_ENDPOINT + 0x01, // bJackType = MS_GENERAL + MIDI_NUM_CABLES, // bNumEmbMIDIJack = number of jacks + 1, // BaAssocJackID(1) = jack ID #1 + #if MIDI_NUM_CABLES >= 2 + 5, + #endif + #if MIDI_NUM_CABLES >= 3 + 9, + #endif + #if MIDI_NUM_CABLES >= 4 + 13, + #endif + #if MIDI_NUM_CABLES >= 5 + 17, + #endif + #if MIDI_NUM_CABLES >= 6 + 21, + #endif + #if MIDI_NUM_CABLES >= 7 + 25, + #endif + #if MIDI_NUM_CABLES >= 8 + 29, + #endif + #if MIDI_NUM_CABLES >= 9 + 33, + #endif + #if MIDI_NUM_CABLES >= 10 + 37, + #endif + #if MIDI_NUM_CABLES >= 11 + 41, + #endif + #if MIDI_NUM_CABLES >= 12 + 45, + #endif + #if MIDI_NUM_CABLES >= 13 + 49, + #endif + #if MIDI_NUM_CABLES >= 14 + 53, + #endif + #if MIDI_NUM_CABLES >= 15 + 57, + #endif + #if MIDI_NUM_CABLES >= 16 + 61, + #endif + // Standard Bulk IN Endpoint Descriptor, B.5.1, Table B-11, pae 42 + 9, // bLength + 5, // bDescriptorType = ENDPOINT + MIDI_TX_ENDPOINT | 0x80, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + LSB(MIDI_TX_SIZE_480),MSB(MIDI_TX_SIZE_480),// wMaxPacketSize + 0, // bInterval + 0, // bRefresh + 0, // bSynchAddress + // Class-specific MS Bulk IN Endpoint Descriptor, B.5.2, Table B-12, page 42 + 4+MIDI_NUM_CABLES, // bLength + 0x25, // bDescriptorSubtype = CS_ENDPOINT + 0x01, // bJackType = MS_GENERAL + MIDI_NUM_CABLES, // bNumEmbMIDIJack = number of jacks + 3, // BaAssocJackID(1) = jack ID #3 + #if MIDI_NUM_CABLES >= 2 + 7, + #endif + #if MIDI_NUM_CABLES >= 3 + 11, + #endif + #if MIDI_NUM_CABLES >= 4 + 15, + #endif + #if MIDI_NUM_CABLES >= 5 + 19, + #endif + #if MIDI_NUM_CABLES >= 6 + 23, + #endif + #if MIDI_NUM_CABLES >= 7 + 27, + #endif + #if MIDI_NUM_CABLES >= 8 + 31, + #endif + #if MIDI_NUM_CABLES >= 9 + 35, + #endif + #if MIDI_NUM_CABLES >= 10 + 39, + #endif + #if MIDI_NUM_CABLES >= 11 + 43, + #endif + #if MIDI_NUM_CABLES >= 12 + 47, + #endif + #if MIDI_NUM_CABLES >= 13 + 51, + #endif + #if MIDI_NUM_CABLES >= 14 + 55, + #endif + #if MIDI_NUM_CABLES >= 15 + 59, + #endif + #if MIDI_NUM_CABLES >= 16 + 63, + #endif +#endif // MIDI_INTERFACE + +#ifdef MULTITOUCH_INTERFACE + // configuration for 480 Mbit/sec speed + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + MULTITOUCH_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 1, // bNumEndpoints + 0x03, // bInterfaceClass (0x03 = HID) + 0x00, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0, // iInterface + // HID interface descriptor, HID 1.11 spec, section 6.2.1 + 9, // bLength + 0x21, // bDescriptorType + 0x11, 0x01, // bcdHID + 0, // bCountryCode + 1, // bNumDescriptors + 0x22, // bDescriptorType + LSB(sizeof(multitouch_report_desc)), // wDescriptorLength + MSB(sizeof(multitouch_report_desc)), + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + MULTITOUCH_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + MULTITOUCH_SIZE, 0, // wMaxPacketSize + 4, // bInterval, 4 = 1ms +#endif // MULTITOUCH_INTERFACE + +#ifdef EXPERIMENTAL_INTERFACE + // configuration for 480 Mbit/sec speed + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + EXPERIMENTAL_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 2, // bNumEndpoints + 0xFF, // bInterfaceClass (0xFF = Vendor) + 0x6A, // bInterfaceSubClass + 0xC7, // bInterfaceProtocol + 0, // iInterface + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + 1 | 0x80, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + LSB(512), MSB(512), // wMaxPacketSize + 1, // bInterval + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + 1, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + LSB(512), MSB(512), // wMaxPacketSize + 1, // bInterval +#endif // EXPERIMENTAL_INTERFACE +}; + + +PROGMEM const uint8_t usb_config_descriptor_12[CONFIG_DESC_SIZE_12] = { + // configuration descriptor, USB spec 9.6.3, page 264-266, Table 9-10 + 9, // bLength; + 2, // bDescriptorType; + LSB(CONFIG_DESC_SIZE_12), // wTotalLength + MSB(CONFIG_DESC_SIZE_12), + NUM_INTERFACE, // bNumInterfaces + 1, // bConfigurationValue + 0, // iConfiguration + 0xC0, // bmAttributes + 50, // bMaxPower + +#ifdef CDC_IAD_DESCRIPTOR + // interface association descriptor, USB ECN, Table 9-Z + 8, // bLength + 11, // bDescriptorType + CDC_STATUS_INTERFACE, // bFirstInterface + 2, // bInterfaceCount + 0x02, // bFunctionClass + 0x02, // bFunctionSubClass + 0x01, // bFunctionProtocol + 0, // iFunction +#endif + +#ifdef CDC_DATA_INTERFACE + // configuration for 12 Mbit/sec speed + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + CDC_STATUS_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 1, // bNumEndpoints + 0x02, // bInterfaceClass + 0x02, // bInterfaceSubClass + 0x01, // bInterfaceProtocol + 0, // iInterface + // CDC Header Functional Descriptor, CDC Spec 5.2.3.1, Table 26 + 5, // bFunctionLength + 0x24, // bDescriptorType + 0x00, // bDescriptorSubtype + 0x10, 0x01, // bcdCDC + // Call Management Functional Descriptor, CDC Spec 5.2.3.2, Table 27 + 5, // bFunctionLength + 0x24, // bDescriptorType + 0x01, // bDescriptorSubtype + 0x01, // bmCapabilities + 1, // bDataInterface + // Abstract Control Management Functional Descriptor, CDC Spec 5.2.3.3, Table 28 + 4, // bFunctionLength + 0x24, // bDescriptorType + 0x02, // bDescriptorSubtype + 0x06, // bmCapabilities + // Union Functional Descriptor, CDC Spec 5.2.3.8, Table 33 + 5, // bFunctionLength + 0x24, // bDescriptorType + 0x06, // bDescriptorSubtype + CDC_STATUS_INTERFACE, // bMasterInterface + CDC_DATA_INTERFACE, // bSlaveInterface0 + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + CDC_ACM_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + CDC_ACM_SIZE, 0, // wMaxPacketSize + 16, // bInterval + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + CDC_DATA_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 2, // bNumEndpoints + 0x0A, // bInterfaceClass + 0x00, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0, // iInterface + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + CDC_RX_ENDPOINT, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + LSB(CDC_RX_SIZE_12),MSB(CDC_RX_SIZE_12),// wMaxPacketSize + 0, // bInterval + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + CDC_TX_ENDPOINT | 0x80, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + LSB(CDC_TX_SIZE_12),MSB(CDC_TX_SIZE_12),// wMaxPacketSize + 0, // bInterval +#endif // CDC_DATA_INTERFACE + +#ifdef CDC2_DATA_INTERFACE + // configuration for 12 Mbit/sec speed + // interface association descriptor, USB ECN, Table 9-Z + 8, // bLength + 11, // bDescriptorType + CDC2_STATUS_INTERFACE, // bFirstInterface + 2, // bInterfaceCount + 0x02, // bFunctionClass + 0x02, // bFunctionSubClass + 0x01, // bFunctionProtocol + 0, // iFunction + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + CDC2_STATUS_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 1, // bNumEndpoints + 0x02, // bInterfaceClass + 0x02, // bInterfaceSubClass + 0x01, // bInterfaceProtocol + 0, // iInterface + // CDC Header Functional Descriptor, CDC Spec 5.2.3.1, Table 26 + 5, // bFunctionLength + 0x24, // bDescriptorType + 0x00, // bDescriptorSubtype + 0x10, 0x01, // bcdCDC + // Call Management Functional Descriptor, CDC Spec 5.2.3.2, Table 27 + 5, // bFunctionLength + 0x24, // bDescriptorType + 0x01, // bDescriptorSubtype + 0x01, // bmCapabilities + 1, // bDataInterface + // Abstract Control Management Functional Descriptor, CDC Spec 5.2.3.3, Table 28 + 4, // bFunctionLength + 0x24, // bDescriptorType + 0x02, // bDescriptorSubtype + 0x06, // bmCapabilities + // Union Functional Descriptor, CDC Spec 5.2.3.8, Table 33 + 5, // bFunctionLength + 0x24, // bDescriptorType + 0x06, // bDescriptorSubtype + CDC2_STATUS_INTERFACE, // bMasterInterface + CDC2_DATA_INTERFACE, // bSlaveInterface0 + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + CDC2_ACM_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + CDC_ACM_SIZE, 0, // wMaxPacketSize + 64, // bInterval + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + CDC2_DATA_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 2, // bNumEndpoints + 0x0A, // bInterfaceClass + 0x00, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0, // iInterface + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + CDC2_RX_ENDPOINT, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + CDC_RX_SIZE_12, 0, // wMaxPacketSize + 0, // bInterval + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + CDC2_TX_ENDPOINT | 0x80, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + CDC_TX_SIZE_12, 0, // wMaxPacketSize + 0, // bInterval +#endif // CDC2_DATA_INTERFACE + +#ifdef CDC3_DATA_INTERFACE + // configuration for 12 Mbit/sec speed + // interface association descriptor, USB ECN, Table 9-Z + 8, // bLength + 11, // bDescriptorType + CDC3_STATUS_INTERFACE, // bFirstInterface + 2, // bInterfaceCount + 0x02, // bFunctionClass + 0x02, // bFunctionSubClass + 0x01, // bFunctionProtocol + 0, // iFunction + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + CDC3_STATUS_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 1, // bNumEndpoints + 0x02, // bInterfaceClass + 0x02, // bInterfaceSubClass + 0x01, // bInterfaceProtocol + 0, // iInterface + // CDC Header Functional Descriptor, CDC Spec 5.2.3.1, Table 26 + 5, // bFunctionLength + 0x24, // bDescriptorType + 0x00, // bDescriptorSubtype + 0x10, 0x01, // bcdCDC + // Call Management Functional Descriptor, CDC Spec 5.2.3.2, Table 27 + 5, // bFunctionLength + 0x24, // bDescriptorType + 0x01, // bDescriptorSubtype + 0x01, // bmCapabilities + 1, // bDataInterface + // Abstract Control Management Functional Descriptor, CDC Spec 5.2.3.3, Table 28 + 4, // bFunctionLength + 0x24, // bDescriptorType + 0x02, // bDescriptorSubtype + 0x06, // bmCapabilities + // Union Functional Descriptor, CDC Spec 5.2.3.8, Table 33 + 5, // bFunctionLength + 0x24, // bDescriptorType + 0x06, // bDescriptorSubtype + CDC3_STATUS_INTERFACE, // bMasterInterface + CDC3_DATA_INTERFACE, // bSlaveInterface0 + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + CDC3_ACM_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + CDC_ACM_SIZE, 0, // wMaxPacketSize + 64, // bInterval + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + CDC3_DATA_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 2, // bNumEndpoints + 0x0A, // bInterfaceClass + 0x00, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0, // iInterface + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + CDC3_RX_ENDPOINT, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + CDC_RX_SIZE_12, 0, // wMaxPacketSize + 0, // bInterval + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + CDC3_TX_ENDPOINT | 0x80, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + CDC_TX_SIZE_12, 0, // wMaxPacketSize + 0, // bInterval +#endif // CDC3_DATA_INTERFACE + +#ifdef KEYBOARD_INTERFACE + // configuration for 12 Mbit/sec speed + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + KEYBOARD_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 1, // bNumEndpoints + 0x03, // bInterfaceClass (0x03 = HID) + 0x01, // bInterfaceSubClass (0x01 = Boot) + 0x01, // bInterfaceProtocol (0x01 = Keyboard) + 0, // iInterface + // HID interface descriptor, HID 1.11 spec, section 6.2.1 + 9, // bLength + 0x21, // bDescriptorType + 0x11, 0x01, // bcdHID + 0, // bCountryCode + 1, // bNumDescriptors + 0x22, // bDescriptorType + LSB(sizeof(keyboard_report_desc)), // wDescriptorLength + MSB(sizeof(keyboard_report_desc)), + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + KEYBOARD_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + KEYBOARD_SIZE, 0, // wMaxPacketSize + KEYBOARD_INTERVAL, // bInterval +#endif // KEYBOARD_INTERFACE + +#ifdef MOUSE_INTERFACE + // configuration for 12 Mbit/sec speed + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + MOUSE_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 1, // bNumEndpoints + 0x03, // bInterfaceClass (0x03 = HID) + 0x00, // bInterfaceSubClass (0x01 = Boot) + 0x00, // bInterfaceProtocol (0x02 = Mouse) + 0, // iInterface + // HID interface descriptor, HID 1.11 spec, section 6.2.1 + 9, // bLength + 0x21, // bDescriptorType + 0x11, 0x01, // bcdHID + 0, // bCountryCode + 1, // bNumDescriptors + 0x22, // bDescriptorType + LSB(sizeof(mouse_report_desc)), // wDescriptorLength + MSB(sizeof(mouse_report_desc)), + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + MOUSE_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + MOUSE_SIZE, 0, // wMaxPacketSize + MOUSE_INTERVAL, // bInterval +#endif // MOUSE_INTERFACE + +#ifdef RAWHID_INTERFACE + // configuration for 12 Mbit/sec speed + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + RAWHID_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 2, // bNumEndpoints + 0x03, // bInterfaceClass (0x03 = HID) + 0x00, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0, // iInterface + // HID interface descriptor, HID 1.11 spec, section 6.2.1 + 9, // bLength + 0x21, // bDescriptorType + 0x11, 0x01, // bcdHID + 0, // bCountryCode + 1, // bNumDescriptors + 0x22, // bDescriptorType + LSB(sizeof(rawhid_report_desc)), // wDescriptorLength + MSB(sizeof(rawhid_report_desc)), + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + RAWHID_TX_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + RAWHID_TX_SIZE, 0, // wMaxPacketSize + RAWHID_TX_INTERVAL, // bInterval + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + RAWHID_RX_ENDPOINT, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + RAWHID_RX_SIZE, 0, // wMaxPacketSize + RAWHID_RX_INTERVAL, // bInterval +#endif // RAWHID_INTERFACE + +#ifdef FLIGHTSIM_INTERFACE + // configuration for 12 Mbit/sec speed + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + FLIGHTSIM_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 2, // bNumEndpoints + 0x03, // bInterfaceClass (0x03 = HID) + 0x00, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0, // iInterface + // HID interface descriptor, HID 1.11 spec, section 6.2.1 + 9, // bLength + 0x21, // bDescriptorType + 0x11, 0x01, // bcdHID + 0, // bCountryCode + 1, // bNumDescriptors + 0x22, // bDescriptorType + LSB(sizeof(flightsim_report_desc)), // wDescriptorLength + MSB(sizeof(flightsim_report_desc)), + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + FLIGHTSIM_TX_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + FLIGHTSIM_TX_SIZE, 0, // wMaxPacketSize + FLIGHTSIM_TX_INTERVAL, // bInterval + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + FLIGHTSIM_RX_ENDPOINT, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + FLIGHTSIM_RX_SIZE, 0, // wMaxPacketSize + FLIGHTSIM_RX_INTERVAL, // bInterval +#endif // FLIGHTSIM_INTERFACE + +#ifdef SEREMU_INTERFACE + // configuration for 12 Mbit/sec speed + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + SEREMU_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 2, // bNumEndpoints + 0x03, // bInterfaceClass (0x03 = HID) + 0x00, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0, // iInterface + // HID interface descriptor, HID 1.11 spec, section 6.2.1 + 9, // bLength + 0x21, // bDescriptorType + 0x11, 0x01, // bcdHID + 0, // bCountryCode + 1, // bNumDescriptors + 0x22, // bDescriptorType + LSB(sizeof(seremu_report_desc)), // wDescriptorLength + MSB(sizeof(seremu_report_desc)), + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + SEREMU_TX_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + SEREMU_TX_SIZE, 0, // wMaxPacketSize + SEREMU_TX_INTERVAL, // bInterval + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + SEREMU_RX_ENDPOINT, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + SEREMU_RX_SIZE, 0, // wMaxPacketSize + SEREMU_RX_INTERVAL, // bInterval +#endif // SEREMU_INTERFACE + +#ifdef JOYSTICK_INTERFACE + // configuration for 12 Mbit/sec speed + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + JOYSTICK_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 1, // bNumEndpoints + 0x03, // bInterfaceClass (0x03 = HID) + 0x00, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0, // iInterface + // HID interface descriptor, HID 1.11 spec, section 6.2.1 + 9, // bLength + 0x21, // bDescriptorType + 0x11, 0x01, // bcdHID + 0, // bCountryCode + 1, // bNumDescriptors + 0x22, // bDescriptorType + LSB(sizeof(joystick_report_desc)), // wDescriptorLength + MSB(sizeof(joystick_report_desc)), + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + JOYSTICK_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + JOYSTICK_SIZE, 0, // wMaxPacketSize + JOYSTICK_INTERVAL, // bInterval +#endif // JOYSTICK_INTERFACE + +#ifdef MTP_INTERFACE + // configuration for 12 Mbit/sec speed + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + MTP_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 3, // bNumEndpoints + 0x06, // bInterfaceClass (0x06 = still image) + 0x01, // bInterfaceSubClass + 0x01, // bInterfaceProtocol + 0, // iInterface + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + MTP_TX_ENDPOINT | 0x80, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + LSB(MTP_TX_SIZE_12),MSB(MTP_TX_SIZE_12),// wMaxPacketSize + 0, // bInterval + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + MTP_RX_ENDPOINT, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + LSB(MTP_RX_SIZE_12),MSB(MTP_RX_SIZE_12),// wMaxPacketSize + 0, // bInterval + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + MTP_EVENT_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + MTP_EVENT_SIZE, 0, // wMaxPacketSize + MTP_EVENT_INTERVAL_12, // bInterval +#endif // MTP_INTERFACE + +#ifdef KEYMEDIA_INTERFACE + // configuration for 12 Mbit/sec speed + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + KEYMEDIA_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 1, // bNumEndpoints + 0x03, // bInterfaceClass (0x03 = HID) + 0x00, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0, // iInterface + // HID interface descriptor, HID 1.11 spec, section 6.2.1 + 9, // bLength + 0x21, // bDescriptorType + 0x11, 0x01, // bcdHID + 0, // bCountryCode + 1, // bNumDescriptors + 0x22, // bDescriptorType + LSB(sizeof(keymedia_report_desc)), // wDescriptorLength + MSB(sizeof(keymedia_report_desc)), + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + KEYMEDIA_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + KEYMEDIA_SIZE, 0, // wMaxPacketSize + KEYMEDIA_INTERVAL, // bInterval +#endif // KEYMEDIA_INTERFACE + +#ifdef AUDIO_INTERFACE + // UAC2: + // Standard Interface Association Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.6, Table 4-3 page 46 + 8, // bLength + 11, // bDescriptorType + AUDIO_INTERFACE, // bFirstInterface + 3, // bInterfaceCount + 0x01, // bFunctionClass + 0x00, // bFunctionSubClass, 0x00 = FUNCTION_SUBCLASS_UNDEFINED + 0x20, // bFunctionProtocol 0x20 = IP_VERSION_02_00 = AF_VERSION_02_00 + 0, // iFunction + // UAC2: + // Standard AudioControl (AC) Interface Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.7.1, Table 4-4 page 47 + 9, // bLength + 4, // bDescriptorType, 4 = INTERFACE + AUDIO_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 0, // bNumEndpoints + 1, // bInterfaceClass, 1 = AUDIO + 1, // bInterfaceSubclass, 1 = AUDIO_CONTROL + 0x20, // bInterfaceProtocol, IP_VERSION_02_00==0x20 + 0, // iInterface + // UAC2: + // Input Terminal Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.7.2, Table 4-5 page 48-49 + 9, // bLength + 0x24, // bDescriptorType, 0x24 = CS_INTERFACE + 0x01, // bDescriptorSubtype, 1 = HEADER + 0x00, 0x02, // bcdADC (version 2.0) + 0xff, // bCategory 0xff = other + LSB(75+FEATURE_UNIT_DESC_SIZE_12), MSB(75+FEATURE_UNIT_DESC_SIZE_12),// wTotalLength: size of this descriptor + all input and output and feature unit descriptors + clock sources + 0, // bmControls + // UAC2: + // Clock Source Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.7.2.1, Table 4-9 page 49-50 + 8, // bLength + 0x24, // bDescriptorType, 0x24 = CS_INTERFACE + 0x0a, // bDescriptorSubType, 2 = CLOCK_SOURCE + 0x3a, // bClockID + 0x01, // Internal fixed clock, not synchronized to SOF + 1 | 4, // bmControls: 1: clock frequency control is read only, 4 clock validity control is read only + 0, // bAssocTerminal + 0, // iClockSource + // UAC2: + // Input Terminal Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.7.2.4, Table 4-9 page 52-53 + 17, // bLength + 0x24, // bDescriptorType, 0x24 = CS_INTERFACE + 2, // bDescriptorSubType, 2 = INPUT_TERMINAL + 1, // bTerminalID + 0x02, 0x06, // wTerminalType, 0x0602 = Digital Audio + 0, // bAssocTerminal, 0 = unidirectional + 0x3a, // bCSourceID + USB_AUDIO_NO_CHANNELS_12, // bNrChannels + LSB(CHANNEL_CONFIG_12), // bmChannelConfig + MSB(CHANNEL_CONFIG_12), // bmChannelConfig + (((CHANNEL_CONFIG_12) >> 16) & 255),// bmChannelConfig + (((CHANNEL_CONFIG_12) >> 24) & 255),// bmChannelConfig + 0x00, // iChannelNames + 0x00, 0x00, // bmControls //Todo: do we need to set copy protect + 0, // iTerminal + // UAC2: + // Output Terminal Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.7.2.5, Table 4-10 page 53 + 12, // bLength + 0x24, // bDescriptorType, 0x24 = CS_INTERFACE + 3, // bDescriptorSubtype, 3 = OUTPUT_TERMINAL + 2, // bTerminalID + 0x01, 0x01, // wTerminalType, 0x0101 = USB_STREAMING + 0, // bAssocTerminal, 0 = unidirectional + 1, // bSourceID, connected to feature, ID=31 + 0x3a, // bCSourceID + 0,0, // bmControls + 0, // iTerminal + // UAC2: + // Input Terminal Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.7.2.4, Table 4-9 page 52-53 + 17, // bLength + 0x24, // bDescriptorType, 0x24 = CS_INTERFACE + 2, // bDescriptorSubType, 2 = INPUT_TERMINAL + 3, // bTerminalID + 0x01, 0x01, // wTerminalType, 0x0101 = USB_STREAMING + 0, // bAssocTerminal, 0 = unidirectional + 0x3a, // bCSourceID + USB_AUDIO_NO_CHANNELS_12, // bNrChannels + LSB(CHANNEL_CONFIG_12), // bmChannelConfig + MSB(CHANNEL_CONFIG_12), // bmChannelConfig + (((CHANNEL_CONFIG_12) >> 16) & 255),// bmChannelConfig + (((CHANNEL_CONFIG_12) >> 24) & 255),// bmChannelConfig + 0x00, // iChannelNames + 0x00, 0x00, // bmControls + 0, // iTerminal + // UAC2: + // Feature Unit Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.7.2.8, Table 4-13 page 58-59 + FEATURE_UNIT_DESC_SIZE_12, // bLength + 0x24, // bDescriptorType = CS_INTERFACE + 0x06, // bDescriptorSubType = FEATURE_UNIT + 0x31, // bUnitID + 0x03, // bSourceID (Input Terminal) + 15,0x00,0x00,0x00, // bmaControls(0) Master: Mute bit 0 and 1 + bit 2 + 3 (reading and writing master volume) + 12,0x00,0x00,0x00, // bmaControls(1) Left: Volume bit 2 and 3 + 12,0x00,0x00,0x00, // bmaControls(2) Right: Volume bit 2 and 3 + #if USB_AUDIO_NO_CHANNELS_12 > 2 + 12,0x00,0x00,0x00, // bmaControls(1) channel 3: Volume bit 2 and 3 + 12,0x00,0x00,0x00, // bmaControls(2) channel 4: Volume bit 2 and 3 + #endif + #if USB_AUDIO_NO_CHANNELS_12 > 4 + 12,0x00,0x00,0x00, // bmaControls(1) channel 5: Volume bit 2 and 3 + 12,0x00,0x00,0x00, // bmaControls(2) channel 6: Volume bit 2 and 3 + #endif + #if USB_AUDIO_NO_CHANNELS_12 > 6 + 12,0x00,0x00,0x00, // bmaControls(1) channel 7: Volume bit 2 and 3 + 12,0x00,0x00,0x00, // bmaControls(2) channel 8: Volume bit 2 and 3 + #endif + 0x00, // iFeature + // UAC2: + // Output Terminal Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.7.2.5, Table 4-10 page 53 + 12, // bLength + 0x24, // bDescriptorType, 0x24 = CS_INTERFACE + 3, // bDescriptorSubtype, 3 = OUTPUT_TERMINAL + 4, // bTerminalID + 0x02, 0x06, // wTerminalType, 0x0602 = Digital Audio + 0, // bAssocTerminal, 0 = unidirectional + 0x31, // bSourceID, connected to feature, ID=31 + 0x3a, // bCSourceID + 0,0, // bmControls + 0, // iTerminal + // UAC2: + // Standard AS Interface Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.9.1, Table 4-26 page 75 + // Alternate 0: default setting, disabled zero bandwidth + 9, // bLenght + 4, // bDescriptorType = INTERFACE + AUDIO_INTERFACE+1, // bInterfaceNumber + 0, // bAlternateSetting + 0, // bNumEndpoints + 1, // bInterfaceClass, 1 = AUDIO + 2, // bInterfaceSubclass, 2 = AUDIO_STREAMING + 0x20, // bInterfaceProtocol IP_VERSION_02_00==0x20 + 0, // iInterface + // Alternate 1: streaming data + 9, // bLenght + 4, // bDescriptorType = INTERFACE + AUDIO_INTERFACE+1, // bInterfaceNumber + 1, // bAlternateSetting + 1, // bNumEndpoints + 1, // bInterfaceClass, 1 = AUDIO + 2, // bInterfaceSubclass, 2 = AUDIO_STREAMING + 0x20, // bInterfaceProtocol IP_VERSION_02_00==0x20 + 0, // iInterface + // UAC2: + // Class-Specific AS Interface Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.9.2, Table 4-27 page 75-77 + 16, // bLength + 0x24, // bDescriptorType = CS_INTERFACE + 1, // bDescriptorSubtype, 1 = AS_GENERAL + 2, // bTerminalLink: Terminal ID = 2 + 0, //bmControls + 1, //bFormatType 1=FORMAT_TYPE_I + 0x01, 0x00, 0x00, 0x00, //bmFormats first bit: PCM + USB_AUDIO_NO_CHANNELS_12, // bNrChannels + LSB(CHANNEL_CONFIG_12), // bmChannelConfig + MSB(CHANNEL_CONFIG_12), // bmChannelConfig + (((CHANNEL_CONFIG_12) >> 16) & 255),// bmChannelConfig + (((CHANNEL_CONFIG_12) >> 24) & 255),// bmChannelConfig + 0, // iChannelNames index of string descriptor at the bottom of the file + // UAC2: + // Univeral Serial Bus Device Class Definition for Audio Data Formats 2.0, Section 2.3.1.6, Table 2-2, page 15-16 + 6, // bLength + 0x24, // bDescriptorType = CS_INTERFACE + 2, // bDescriptorSubtype = FORMAT_TYPE + 1, // bFormatType = FORMAT_TYPE_I + AUDIO_SUBSLOT_SIZE, // bSubSlotSize = size of a single sample in bytes (e.g. 2 bytes for 16bit audio) + AUDIO_BITRESOLUTION, // bBitResolution = 16 bits + // UAC2: + // Standard AS Isochronous Audio Data Endpoint Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.10.1.1, Table 4-33 page 85-86 + 7, // bLength + 5, // bDescriptorType, 5 = ENDPOINT_DESCRIPTOR + AUDIO_TX_ENDPOINT | 0x80, // bEndpointAddress (0x80 = 10000000 -> audio source) +#ifdef ASYNC_TX_ENDPOINT + 0x05, // bmAttributes, 0x09 = isochronous, adaptive, 0x05 = isochronous, asynchronous +#elif + 0x09, // bmAttributes, 0x09 = isochronous, adaptive, 0x05 = isochronous, asynchronous +#endif + LSB(AUDIO_TX_SIZE_12), MSB(AUDIO_TX_SIZE_12), // wMaxPacketSize + AUDIO_POLLING_INTERVAL_12, // bInterval 1...255 for full and low speed endpoints + // UAC2: + // Class-Specific AS Isochronous Audio Data Endpoint Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.10.1.2, Table 4-34 page 86-87 + 8, // bLength + 0x25, // bDescriptorType, 0x25 = CS_ENDPOINT + 1, // bDescriptorSubtype, 1 = EP_GENERAL + 0x00, // bmAttributes + 0x00, // bmControls + 0, // bLockDelayUnits, 1 = ms + 0x00, 0x00, // wLockDelay + // UAC2: + // Standard AS Interface Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.9.1, Table 4-26 page 75 + // Alternate 0: default setting, disabled zero bandwidth + 9, // bLenght + 4, // bDescriptorType = INTERFACE + AUDIO_INTERFACE+2, // bInterfaceNumber + 0, // bAlternateSetting + 0, // bNumEndpoints + 1, // bInterfaceClass, 1 = AUDIO + 2, // bInterfaceSubclass, 2 = AUDIO_STREAMING + 0x20, // bInterfaceProtocol IP_VERSION_02_00==0x20 + 0, // iInterface + // Alternate 1: streaming data + 9, // bLenght + 4, // bDescriptorType = INTERFACE + AUDIO_INTERFACE+2, // bInterfaceNumber + 1, // bAlternateSetting + 2, // bNumEndpoints + 1, // bInterfaceClass, 1 = AUDIO + 2, // bInterfaceSubclass, 2 = AUDIO_STREAMING + 0x20, // bInterfaceProtocol IP_VERSION_02_00==0x20 + 0, // iInterface + // UAC2: + // Class-Specific AS Interface Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.9.2, Table 4-27 page 75-77 + 16, // bLength + 0x24, // bDescriptorType = CS_INTERFACE + 1, // bDescriptorSubtype, 1 = AS_GENERAL + 3, // bTerminalLink: Terminal ID = 3 + 0, //bmControls + 1, //bFormatType 1=FORMAT_TYPE_I + 0x01, 0x00, 0x00, 0x00, //bmFormats first bit: PCM + USB_AUDIO_NO_CHANNELS_12, // bNrChannels + LSB(CHANNEL_CONFIG_12), // bmChannelConfig + MSB(CHANNEL_CONFIG_12), // bmChannelConfig + (((CHANNEL_CONFIG_12) >> 16) & 255),// bmChannelConfig + (((CHANNEL_CONFIG_12) >> 24) & 255),// bmChannelConfig + 0, //iChannelNames index of string descriptor at the bottom of the file + // UAC2: + // Univeral Serial Bus Device Class Definition for Audio Data Formats 2.0, Section 2.3.1.6, Table 2-2, page 15-16 + 6, // bLength + 0x24, // bDescriptorType = CS_INTERFACE + 2, // bDescriptorSubtype = FORMAT_TYPE + 1, // bFormatType = FORMAT_TYPE_I + AUDIO_SUBSLOT_SIZE, // bSubSlotSize = size of a single sample in bytes (e.g. 2 bytes for 16bit audio) + AUDIO_BITRESOLUTION, // bBitResolution = 16 bits + // UAC2: + // Standard AS Isochronous Audio Data Endpoint Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.10.1.1, Table 4-33 page 85-86 + 7, // bLength + 5, // bDescriptorType, 5 = ENDPOINT_DESCRIPTOR + AUDIO_RX_ENDPOINT | 0x00, // bEndpointAddress (0x00 = 00000000 -> audio sink) + 0x05, // bmAttributes = isochronous, asynchronous + LSB(AUDIO_RX_SIZE_12), MSB(AUDIO_RX_SIZE_12), // wMaxPacketSize + AUDIO_POLLING_INTERVAL_12, // bInterval 1...255 for full and low speed endpoints + // UAC2: + // Class-Specific AS Isochronous Audio Data Endpoint Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.10.1.2, Table 4-34 page 86-87 + 8, // bLength + 0x25, // bDescriptorType, 0x25 = CS_ENDPOINT + 1, // bDescriptorSubtype, 1 = EP_GENERAL + 0x00, // bmAttributes + 0x00, // bmControls + 0, // bLockDelayUnits, 1 = ms + 0x00, 0x00, // wLockDelay + // UAC2: + // Standard AS Isochronous Feedback Endpoint Descriptor + // Universal Serial Bus Device Class Definition for Audio Devices 2.0, Section 4.10.2.1, Table 4-35 page 87-88 + 7, // bLength + 5, // bDescriptorType, 5 = ENDPOINT_DESCRIPTOR + AUDIO_SYNC_ENDPOINT | 0x80, // bEndpointAddress, 0x80=10000000 -> IN endpoint + 0x11, // bmAttributes = isochronous, feedback + 4, 0, // wMaxPacketSize, 4 bytes + AUDIO_POLLING_INTERVAL_12, // bInterval 1...255 for full and low speed endpoints +#endif + +#ifdef MIDI_INTERFACE + // configuration for 12 Mbit/sec speed + // Standard MS Interface Descriptor, + 9, // bLength + 4, // bDescriptorType + MIDI_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 2, // bNumEndpoints + 0x01, // bInterfaceClass (0x01 = Audio) + 0x03, // bInterfaceSubClass (0x03 = MIDI) + 0x00, // bInterfaceProtocol (unused for MIDI) + 0, // iInterface + // MIDI MS Interface Header, USB MIDI 6.1.2.1, page 21, Table 6-2 + 7, // bLength + 0x24, // bDescriptorType = CS_INTERFACE + 0x01, // bDescriptorSubtype = MS_HEADER + 0x00, 0x01, // bcdMSC = revision 01.00 + LSB(7+(6+6+9+9)*MIDI_NUM_CABLES), // wTotalLength + MSB(7+(6+6+9+9)*MIDI_NUM_CABLES), + // MIDI IN Jack Descriptor, B.4.3, Table B-7 (embedded), page 40 + 6, // bLength + 0x24, // bDescriptorType = CS_INTERFACE + 0x02, // bDescriptorSubtype = MIDI_IN_JACK + 0x01, // bJackType = EMBEDDED + 1, // bJackID, ID = 1 + 0, // iJack + // MIDI IN Jack Descriptor, B.4.3, Table B-8 (external), page 40 + 6, // bLength + 0x24, // bDescriptorType = CS_INTERFACE + 0x02, // bDescriptorSubtype = MIDI_IN_JACK + 0x02, // bJackType = EXTERNAL + 2, // bJackID, ID = 2 + 0, // iJack + // MIDI OUT Jack Descriptor, B.4.4, Table B-9, page 41 + 9, + 0x24, // bDescriptorType = CS_INTERFACE + 0x03, // bDescriptorSubtype = MIDI_OUT_JACK + 0x01, // bJackType = EMBEDDED + 3, // bJackID, ID = 3 + 1, // bNrInputPins = 1 pin + 2, // BaSourceID(1) = 2 + 1, // BaSourcePin(1) = first pin + 0, // iJack + // MIDI OUT Jack Descriptor, B.4.4, Table B-10, page 41 + 9, + 0x24, // bDescriptorType = CS_INTERFACE + 0x03, // bDescriptorSubtype = MIDI_OUT_JACK + 0x02, // bJackType = EXTERNAL + 4, // bJackID, ID = 4 + 1, // bNrInputPins = 1 pin + 1, // BaSourceID(1) = 1 + 1, // BaSourcePin(1) = first pin + 0, // iJack + #if MIDI_NUM_CABLES >= 2 + #define MIDI_INTERFACE_JACK_PAIR(a, b, c, d) \ + 6, 0x24, 0x02, 0x01, (a), 0, \ + 6, 0x24, 0x02, 0x02, (b), 0, \ + 9, 0x24, 0x03, 0x01, (c), 1, (b), 1, 0, \ + 9, 0x24, 0x03, 0x02, (d), 1, (a), 1, 0, + MIDI_INTERFACE_JACK_PAIR(5, 6, 7, 8) + #endif + #if MIDI_NUM_CABLES >= 3 + MIDI_INTERFACE_JACK_PAIR(9, 10, 11, 12) + #endif + #if MIDI_NUM_CABLES >= 4 + MIDI_INTERFACE_JACK_PAIR(13, 14, 15, 16) + #endif + #if MIDI_NUM_CABLES >= 5 + MIDI_INTERFACE_JACK_PAIR(17, 18, 19, 20) + #endif + #if MIDI_NUM_CABLES >= 6 + MIDI_INTERFACE_JACK_PAIR(21, 22, 23, 24) + #endif + #if MIDI_NUM_CABLES >= 7 + MIDI_INTERFACE_JACK_PAIR(25, 26, 27, 28) + #endif + #if MIDI_NUM_CABLES >= 8 + MIDI_INTERFACE_JACK_PAIR(29, 30, 31, 32) + #endif + #if MIDI_NUM_CABLES >= 9 + MIDI_INTERFACE_JACK_PAIR(33, 34, 35, 36) + #endif + #if MIDI_NUM_CABLES >= 10 + MIDI_INTERFACE_JACK_PAIR(37, 38, 39, 40) + #endif + #if MIDI_NUM_CABLES >= 11 + MIDI_INTERFACE_JACK_PAIR(41, 42, 43, 44) + #endif + #if MIDI_NUM_CABLES >= 12 + MIDI_INTERFACE_JACK_PAIR(45, 46, 47, 48) + #endif + #if MIDI_NUM_CABLES >= 13 + MIDI_INTERFACE_JACK_PAIR(49, 50, 51, 52) + #endif + #if MIDI_NUM_CABLES >= 14 + MIDI_INTERFACE_JACK_PAIR(53, 54, 55, 56) + #endif + #if MIDI_NUM_CABLES >= 15 + MIDI_INTERFACE_JACK_PAIR(57, 58, 59, 60) + #endif + #if MIDI_NUM_CABLES >= 16 + MIDI_INTERFACE_JACK_PAIR(61, 62, 63, 64) + #endif + // Standard Bulk OUT Endpoint Descriptor, B.5.1, Table B-11, pae 42 + 9, // bLength + 5, // bDescriptorType = ENDPOINT + MIDI_RX_ENDPOINT, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + LSB(MIDI_RX_SIZE_12),MSB(MIDI_RX_SIZE_12),// wMaxPacketSize + 0, // bInterval + 0, // bRefresh + 0, // bSynchAddress + // Class-specific MS Bulk OUT Endpoint Descriptor, B.5.2, Table B-12, page 42 + 4+MIDI_NUM_CABLES, // bLength + 0x25, // bDescriptorSubtype = CS_ENDPOINT + 0x01, // bJackType = MS_GENERAL + MIDI_NUM_CABLES, // bNumEmbMIDIJack = number of jacks + 1, // BaAssocJackID(1) = jack ID #1 + #if MIDI_NUM_CABLES >= 2 + 5, + #endif + #if MIDI_NUM_CABLES >= 3 + 9, + #endif + #if MIDI_NUM_CABLES >= 4 + 13, + #endif + #if MIDI_NUM_CABLES >= 5 + 17, + #endif + #if MIDI_NUM_CABLES >= 6 + 21, + #endif + #if MIDI_NUM_CABLES >= 7 + 25, + #endif + #if MIDI_NUM_CABLES >= 8 + 29, + #endif + #if MIDI_NUM_CABLES >= 9 + 33, + #endif + #if MIDI_NUM_CABLES >= 10 + 37, + #endif + #if MIDI_NUM_CABLES >= 11 + 41, + #endif + #if MIDI_NUM_CABLES >= 12 + 45, + #endif + #if MIDI_NUM_CABLES >= 13 + 49, + #endif + #if MIDI_NUM_CABLES >= 14 + 53, + #endif + #if MIDI_NUM_CABLES >= 15 + 57, + #endif + #if MIDI_NUM_CABLES >= 16 + 61, + #endif + // Standard Bulk IN Endpoint Descriptor, B.5.1, Table B-11, pae 42 + 9, // bLength + 5, // bDescriptorType = ENDPOINT + MIDI_TX_ENDPOINT | 0x80, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + LSB(MIDI_TX_SIZE_12),MSB(MIDI_TX_SIZE_12),// wMaxPacketSize + 0, // bInterval + 0, // bRefresh + 0, // bSynchAddress + // Class-specific MS Bulk IN Endpoint Descriptor, B.5.2, Table B-12, page 42 + 4+MIDI_NUM_CABLES, // bLength + 0x25, // bDescriptorSubtype = CS_ENDPOINT + 0x01, // bJackType = MS_GENERAL + MIDI_NUM_CABLES, // bNumEmbMIDIJack = number of jacks + 3, // BaAssocJackID(1) = jack ID #3 + #if MIDI_NUM_CABLES >= 2 + 7, + #endif + #if MIDI_NUM_CABLES >= 3 + 11, + #endif + #if MIDI_NUM_CABLES >= 4 + 15, + #endif + #if MIDI_NUM_CABLES >= 5 + 19, + #endif + #if MIDI_NUM_CABLES >= 6 + 23, + #endif + #if MIDI_NUM_CABLES >= 7 + 27, + #endif + #if MIDI_NUM_CABLES >= 8 + 31, + #endif + #if MIDI_NUM_CABLES >= 9 + 35, + #endif + #if MIDI_NUM_CABLES >= 10 + 39, + #endif + #if MIDI_NUM_CABLES >= 11 + 43, + #endif + #if MIDI_NUM_CABLES >= 12 + 47, + #endif + #if MIDI_NUM_CABLES >= 13 + 51, + #endif + #if MIDI_NUM_CABLES >= 14 + 55, + #endif + #if MIDI_NUM_CABLES >= 15 + 59, + #endif + #if MIDI_NUM_CABLES >= 16 + 63, + #endif +#endif // MIDI_INTERFACE +#ifdef MULTITOUCH_INTERFACE + // configuration for 12 Mbit/sec speed + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + MULTITOUCH_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 1, // bNumEndpoints + 0x03, // bInterfaceClass (0x03 = HID) + 0x00, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0, // iInterface + // HID interface descriptor, HID 1.11 spec, section 6.2.1 + 9, // bLength + 0x21, // bDescriptorType + 0x11, 0x01, // bcdHID + 0, // bCountryCode + 1, // bNumDescriptors + 0x22, // bDescriptorType + LSB(sizeof(multitouch_report_desc)), // wDescriptorLength + MSB(sizeof(multitouch_report_desc)), + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + MULTITOUCH_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + MULTITOUCH_SIZE, 0, // wMaxPacketSize + 1, // bInterval +#endif // MULTITOUCH_INTERFACE + +#ifdef EXPERIMENTAL_INTERFACE + // configuration for 12 Mbit/sec speed + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + EXPERIMENTAL_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 2, // bNumEndpoints + 0xFF, // bInterfaceClass (0xFF = Vendor) + 0x6A, // bInterfaceSubClass + 0xFF, // bInterfaceProtocol + 0, // iInterface + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + 1 | 0x80, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + LSB(64), MSB(64), // wMaxPacketSize + 1, // bInterval + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + 1, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + LSB(64), MSB(64), // wMaxPacketSize + 1, // bInterval +#endif // EXPERIMENTAL_INTERFACE +}; + +#if CONFIG_DESC_SIZE_480 > CONFIG_DESC_SIZE_12 + __attribute__ ((section(".dmabuffers"), aligned(32))) + uint8_t usb_descriptor_buffer[CONFIG_DESC_SIZE_480]; +#else + __attribute__ ((section(".dmabuffers"), aligned(32))) + uint8_t usb_descriptor_buffer[CONFIG_DESC_SIZE_12]; +#endif + + +// ************************************************************** +// String Descriptors +// ************************************************************** + +// The descriptors above can provide human readable strings, +// referenced by index numbers. These descriptors are the +// actual string data + +/* defined in usb_names.h +struct usb_string_descriptor_struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t wString[]; +}; +*/ + +extern struct usb_string_descriptor_struct usb_string_manufacturer_name + __attribute__ ((weak, alias("usb_string_manufacturer_name_default"))); +extern struct usb_string_descriptor_struct usb_string_product_name + __attribute__ ((weak, alias("usb_string_product_name_default"))); +extern struct usb_string_descriptor_struct usb_string_serial_number + __attribute__ ((weak, alias("usb_string_serial_number_default"))); + +PROGMEM const struct usb_string_descriptor_struct string0 = { + 4, + 3, + {0x0409} +}; + +PROGMEM const struct usb_string_descriptor_struct usb_string_manufacturer_name_default = { + 2 + MANUFACTURER_NAME_LEN * 2, + 3, + MANUFACTURER_NAME +}; +PROGMEM const struct usb_string_descriptor_struct usb_string_product_name_default = { + 2 + PRODUCT_NAME_LEN * 2, + 3, + PRODUCT_NAME +}; + +struct usb_string_descriptor_struct usb_string_serial_number_default = { + 12, + 3, + {0,0,0,0,0,0,0,0,0,0} +}; +#ifdef MTP_INTERFACE +PROGMEM const struct usb_string_descriptor_struct usb_string_mtp = { + 2 + 3 * 2, + 3, + {'M','T','P'} +}; +#endif + +void usb_init_serialnumber(void) +{ + char buf[11]; + uint32_t i, num; + + num = HW_OCOTP_MAC0 & 0xFFFFFF; + // add extra zero to work around OS-X CDC-ACM driver bug + if (num < 10000000) num = num * 10; + ultoa(num, buf, 10); + for (i=0; i<10; i++) { + char c = buf[i]; + if (!c) break; + usb_string_serial_number_default.wString[i] = c; + } + usb_string_serial_number_default.bLength = i * 2 + 2; +} + + +// ************************************************************** +// Descriptors List +// ************************************************************** + +// This table provides access to all the descriptor data above. + +const usb_descriptor_list_t usb_descriptor_list[] = { + //wValue, wIndex, address, length + {0x0100, 0x0000, device_descriptor, sizeof(device_descriptor)}, + {0x0600, 0x0000, qualifier_descriptor, sizeof(qualifier_descriptor)}, + {0x0200, 0x0000, usb_config_descriptor_480, CONFIG_DESC_SIZE_480}, + {0x0700, 0x0000, usb_config_descriptor_12, CONFIG_DESC_SIZE_12}, +#ifdef SEREMU_INTERFACE + {0x2200, SEREMU_INTERFACE, seremu_report_desc, sizeof(seremu_report_desc)}, + {0x2100, SEREMU_INTERFACE, usb_config_descriptor_480+SEREMU_HID_DESC_OFFSET, 9}, +#endif +#ifdef KEYBOARD_INTERFACE + {0x2200, KEYBOARD_INTERFACE, keyboard_report_desc, sizeof(keyboard_report_desc)}, + {0x2100, KEYBOARD_INTERFACE, usb_config_descriptor_480+KEYBOARD_HID_DESC_OFFSET, 9}, +#endif +#ifdef MOUSE_INTERFACE + {0x2200, MOUSE_INTERFACE, mouse_report_desc, sizeof(mouse_report_desc)}, + {0x2100, MOUSE_INTERFACE, usb_config_descriptor_480+MOUSE_HID_DESC_OFFSET, 9}, +#endif +#ifdef JOYSTICK_INTERFACE + {0x2200, JOYSTICK_INTERFACE, joystick_report_desc, sizeof(joystick_report_desc)}, + {0x2100, JOYSTICK_INTERFACE, usb_config_descriptor_480+JOYSTICK_HID_DESC_OFFSET, 9}, +#endif +#ifdef RAWHID_INTERFACE + {0x2200, RAWHID_INTERFACE, rawhid_report_desc, sizeof(rawhid_report_desc)}, + {0x2100, RAWHID_INTERFACE, usb_config_descriptor_480+RAWHID_HID_DESC_OFFSET, 9}, +#endif +#ifdef FLIGHTSIM_INTERFACE + {0x2200, FLIGHTSIM_INTERFACE, flightsim_report_desc, sizeof(flightsim_report_desc)}, + {0x2100, FLIGHTSIM_INTERFACE, usb_config_descriptor_480+FLIGHTSIM_HID_DESC_OFFSET, 9}, +#endif +#ifdef KEYMEDIA_INTERFACE + {0x2200, KEYMEDIA_INTERFACE, keymedia_report_desc, sizeof(keymedia_report_desc)}, + {0x2100, KEYMEDIA_INTERFACE, usb_config_descriptor_480+KEYMEDIA_HID_DESC_OFFSET, 9}, +#endif +#ifdef MULTITOUCH_INTERFACE + {0x2200, MULTITOUCH_INTERFACE, multitouch_report_desc, sizeof(multitouch_report_desc)}, + {0x2100, MULTITOUCH_INTERFACE, usb_config_descriptor_480+MULTITOUCH_HID_DESC_OFFSET, 9}, +#endif +#ifdef MTP_INTERFACE + {0x0304, 0x0409, (const uint8_t *)&usb_string_mtp, 0}, +#endif +#ifdef EXPERIMENTAL_INTERFACE + {0x03EE, 0x0000, microsoft_os_string_desc, 18}, + {0x0000, 0xEE04, microsoft_os_compatible_id_desc, 40}, +#endif + {0x0300, 0x0000, (const uint8_t *)&string0, 0}, + {0x0301, 0x0409, (const uint8_t *)&usb_string_manufacturer_name, 0}, + {0x0302, 0x0409, (const uint8_t *)&usb_string_product_name, 0}, + {0x0303, 0x0409, (const uint8_t *)&usb_string_serial_number, 0}, + {0, 0, NULL, 0} +}; + + + + + +#endif // NUM_ENDPOINTS +//#endif // F_CPU >= 20 MHz diff --git a/patched_teensy_core/usb_desc.h b/patched_teensy_core/usb_desc.h new file mode 100644 index 0000000..9423677 --- /dev/null +++ b/patched_teensy_core/usb_desc.h @@ -0,0 +1,1114 @@ +/* Teensyduino Core Library + * http://www.pjrc.com/teensy/ + * Copyright (c) 2017 PJRC.COM, LLC. + * + * 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: + * + * 1. The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * 2. If the Software is incorporated into a build system that allows + * selection among a list of target devices, then similar target + * devices manufactured by PJRC.COM must be included in the list of + * target devices and selectable in the same manner. + * + * 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. + */ + +#pragma once + +// This header is NOT meant to be included when compiling +// user sketches in Arduino. The low-level functions +// provided by usb_dev.c are meant to be called only by +// code which provides higher-level interfaces to the user. + +#include +#include + +#define ENDPOINT_TRANSMIT_UNUSED 0x00020000 +#define ENDPOINT_TRANSMIT_ISOCHRONOUS 0x00C40000 +#define ENDPOINT_TRANSMIT_BULK 0x00C80000 +#define ENDPOINT_TRANSMIT_INTERRUPT 0x00CC0000 +#define ENDPOINT_RECEIVE_UNUSED 0x00000002 +#define ENDPOINT_RECEIVE_ISOCHRONOUS 0x000000C4 +#define ENDPOINT_RECEIVE_BULK 0x000000C8 +#define ENDPOINT_RECEIVE_INTERRUPT 0x000000CC + +/* +Each group of #define lines below corresponds to one of the +settings in the Tools > USB Type menu. This file defines what +type of USB device is actually created for each of those menu +options. + +Each "interface" is a set of functionality your PC or Mac will +use and treat as if it is a unique device. Within each interface, +the "endpoints" are the actual communication channels. Most +interfaces use 1, 2 or 3 endpoints. By editing only this file, +you can customize the USB Types to be any collection of interfaces. + +To modify a USB Type, delete the XYZ_INTERFACE lines for any +interfaces you wish to remove, and copy them from another USB Type +for any you want to add. + +Give each interface a unique number, and edit NUM_INTERFACE to +reflect the total number of interfaces. + +Next, assign unique endpoint numbers to all the endpoints across +all the interfaces your device has. You can reuse an endpoint +number for transmit and receive, but the same endpoint number must +not be used twice to transmit, or twice to receive. + +Most endpoints also require their maximum size, and some also +need an interval specification (the number of milliseconds the +PC will check for data from that endpoint). For existing +interfaces, usually these other settings should not be changed. + +Edit NUM_ENDPOINTS to be at least the largest endpoint number used. + +Edit the ENDPOINT*_CONFIG lines so each endpoint is configured +the proper way (transmit, receive, or both). + +If you are using existing interfaces (making your own device with +a different set of interfaces) the code in all other files should +automatically adapt to the new endpoints you specify here. + +If you need to create a new type of interface, you'll need to write +the code which sends and receives packets, and presents an API to +the user. Usually, a pair of files are added for the actual code, +and code is also added in usb_dev.c for any control transfers, +interrupt-level code, or other very low-level stuff not possible +from the packet send/receive functons. Code also is added in +usb_inst.c to create an instance of your C++ object. This message +gives a quick summary of things you will need to know: +https://forum.pjrc.com/threads/49045?p=164512&viewfull=1#post164512 + +You may edit the Vendor and Product ID numbers, and strings. If +the numbers are changed, Teensyduino may not be able to automatically +find and reboot your board when you click the Upload button in +the Arduino IDE. You will need to press the Program button on +Teensy to initiate programming. + +Some operating systems, especially Windows, may cache USB device +info. Changes to the device name may not update on the same +computer unless the vendor or product ID numbers change, or the +"bcdDevice" revision code is increased. + +If these instructions are missing steps or could be improved, please +let me know? http://forum.pjrc.com/forums/4-Suggestions-amp-Bug-Reports +*/ + + +#if defined(USB_SERIAL) + #define VENDOR_ID 0x16C0 + #define PRODUCT_ID 0x0483 + #define MANUFACTURER_NAME {'T','e','e','n','s','y','d','u','i','n','o'} + #define MANUFACTURER_NAME_LEN 11 + #define PRODUCT_NAME {'U','S','B',' ','S','e','r','i','a','l'} + #define PRODUCT_NAME_LEN 10 + #define EP0_SIZE 64 + #define NUM_ENDPOINTS 4 + #define NUM_USB_BUFFERS 12 + #define NUM_INTERFACE 3 + #define CDC_IAD_DESCRIPTOR 1 // Serial + #define CDC_STATUS_INTERFACE 0 + #define CDC_DATA_INTERFACE 1 + #define CDC_ACM_ENDPOINT 2 + #define CDC_RX_ENDPOINT 3 + #define CDC_TX_ENDPOINT 4 + #define CDC_ACM_SIZE 16 + #define CDC_RX_SIZE_480 512 + #define CDC_TX_SIZE_480 512 + #define CDC_RX_SIZE_12 64 + #define CDC_TX_SIZE_12 64 + #define EXPERIMENTAL_INTERFACE 2 + #define ENDPOINT2_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT3_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_UNUSED + #define ENDPOINT4_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_BULK + +#elif defined(USB_DUAL_SERIAL) + #define VENDOR_ID 0x16C0 + #define PRODUCT_ID 0x048B + #define MANUFACTURER_NAME {'T','e','e','n','s','y','d','u','i','n','o'} + #define MANUFACTURER_NAME_LEN 11 + #define PRODUCT_NAME {'D','u','a','l',' ','S','e','r','i','a','l'} + #define PRODUCT_NAME_LEN 11 + #define EP0_SIZE 64 + #define NUM_ENDPOINTS 5 + #define NUM_INTERFACE 4 + #define CDC_IAD_DESCRIPTOR 1 // Serial + #define CDC_STATUS_INTERFACE 0 + #define CDC_DATA_INTERFACE 1 + #define CDC_ACM_ENDPOINT 2 + #define CDC_RX_ENDPOINT 3 + #define CDC_TX_ENDPOINT 3 + #define CDC_ACM_SIZE 16 + #define CDC_RX_SIZE_480 512 + #define CDC_TX_SIZE_480 512 + #define CDC_RX_SIZE_12 64 + #define CDC_TX_SIZE_12 64 + #define CDC2_STATUS_INTERFACE 2 // SerialUSB1 + #define CDC2_DATA_INTERFACE 3 + #define CDC2_ACM_ENDPOINT 4 + #define CDC2_RX_ENDPOINT 5 + #define CDC2_TX_ENDPOINT 5 + #define ENDPOINT2_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT3_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK + #define ENDPOINT4_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT5_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK + +#elif defined(USB_TRIPLE_SERIAL) + #define VENDOR_ID 0x16C0 + #define PRODUCT_ID 0x048C + #define MANUFACTURER_NAME {'T','e','e','n','s','y','d','u','i','n','o'} + #define MANUFACTURER_NAME_LEN 11 + #define PRODUCT_NAME {'T','r','i','p','l','e',' ','S','e','r','i','a','l'} + #define PRODUCT_NAME_LEN 13 + #define EP0_SIZE 64 + #define NUM_ENDPOINTS 7 + #define NUM_INTERFACE 6 + #define CDC_IAD_DESCRIPTOR 1 // Serial + #define CDC_STATUS_INTERFACE 0 + #define CDC_DATA_INTERFACE 1 + #define CDC_ACM_ENDPOINT 2 + #define CDC_RX_ENDPOINT 3 + #define CDC_TX_ENDPOINT 3 + #define CDC_ACM_SIZE 16 + #define CDC_RX_SIZE_480 512 + #define CDC_TX_SIZE_480 512 + #define CDC_RX_SIZE_12 64 + #define CDC_TX_SIZE_12 64 + #define CDC2_STATUS_INTERFACE 2 // SerialUSB1 + #define CDC2_DATA_INTERFACE 3 + #define CDC2_ACM_ENDPOINT 4 + #define CDC2_RX_ENDPOINT 5 + #define CDC2_TX_ENDPOINT 5 + #define CDC3_STATUS_INTERFACE 4 // SerialUSB2 + #define CDC3_DATA_INTERFACE 5 + #define CDC3_ACM_ENDPOINT 6 + #define CDC3_RX_ENDPOINT 7 + #define CDC3_TX_ENDPOINT 7 + #define ENDPOINT2_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT3_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK + #define ENDPOINT4_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT5_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK + #define ENDPOINT6_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT7_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK + +#elif defined(USB_KEYBOARDONLY) + #define VENDOR_ID 0x16C0 + #define PRODUCT_ID 0x04D0 + #define MANUFACTURER_NAME {'T','e','e','n','s','y','d','u','i','n','o'} + #define MANUFACTURER_NAME_LEN 11 + #define PRODUCT_NAME {'K','e','y','b','o','a','r','d'} + #define PRODUCT_NAME_LEN 8 + #define EP0_SIZE 64 + #define NUM_ENDPOINTS 4 + #define NUM_USB_BUFFERS 14 + #define NUM_INTERFACE 3 + #define SEREMU_INTERFACE 1 // Serial emulation + #define SEREMU_TX_ENDPOINT 2 + #define SEREMU_TX_SIZE 64 + #define SEREMU_TX_INTERVAL 1 // TODO: is this ok for 480 Mbit speed + #define SEREMU_RX_ENDPOINT 2 + #define SEREMU_RX_SIZE 32 + #define SEREMU_RX_INTERVAL 2 // TODO: is this ok for 480 Mbit speed + #define KEYBOARD_INTERFACE 0 // Keyboard + #define KEYBOARD_ENDPOINT 3 + #define KEYBOARD_SIZE 8 // 8 = normal boot protocol, 16 = NKRO + #define KEYBOARD_INTERVAL 1 // TODO: is this ok for 480 Mbit speed + #define KEYMEDIA_INTERFACE 2 // Keyboard Media Keys + #define KEYMEDIA_ENDPOINT 4 + #define KEYMEDIA_SIZE 8 + #define KEYMEDIA_INTERVAL 4 // TODO: is this ok for 480 Mbit speed + #define ENDPOINT2_CONFIG ENDPOINT_RECEIVE_INTERRUPT + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT3_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT4_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + +#elif defined(USB_HID) + #define VENDOR_ID 0x16C0 + #define PRODUCT_ID 0x0482 + #define MANUFACTURER_NAME {'T','e','e','n','s','y','d','u','i','n','o'} + #define MANUFACTURER_NAME_LEN 11 + #define PRODUCT_NAME {'K','e','y','b','o','a','r','d','/','M','o','u','s','e','/','J','o','y','s','t','i','c','k'} + #define PRODUCT_NAME_LEN 23 + #define EP0_SIZE 64 + #define NUM_ENDPOINTS 6 + #define NUM_USB_BUFFERS 24 + #define NUM_INTERFACE 5 + #define SEREMU_INTERFACE 2 // Serial emulation + #define SEREMU_TX_ENDPOINT 2 + #define SEREMU_TX_SIZE 64 + #define SEREMU_TX_INTERVAL 1 + #define SEREMU_RX_ENDPOINT 2 + #define SEREMU_RX_SIZE 32 + #define SEREMU_RX_INTERVAL 2 + #define KEYBOARD_INTERFACE 0 // Keyboard + #define KEYBOARD_ENDPOINT 3 + #define KEYBOARD_SIZE 8 // 8 = normal boot protocol, 16 = NKRO + #define KEYBOARD_INTERVAL 1 + #define KEYMEDIA_INTERFACE 4 // Keyboard Media Keys + #define KEYMEDIA_ENDPOINT 4 + #define KEYMEDIA_SIZE 8 + #define KEYMEDIA_INTERVAL 4 + #define MOUSE_INTERFACE 1 // Mouse + #define MOUSE_ENDPOINT 5 + #define MOUSE_SIZE 8 + #define MOUSE_INTERVAL 1 + #define JOYSTICK_INTERFACE 3 // Joystick + #define JOYSTICK_ENDPOINT 6 + #define JOYSTICK_SIZE 12 // 12 = normal, 64 = extreme joystick + #define JOYSTICK_INTERVAL 2 + #define ENDPOINT2_CONFIG ENDPOINT_RECEIVE_INTERRUPT + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT3_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT4_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT5_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT6_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + +#elif defined(USB_SERIAL_HID) + #define VENDOR_ID 0x16C0 + #define PRODUCT_ID 0x0487 + #define DEVICE_CLASS 0xEF + #define DEVICE_SUBCLASS 0x02 + #define DEVICE_PROTOCOL 0x01 + #define MANUFACTURER_NAME {'T','e','e','n','s','y','d','u','i','n','o'} + #define MANUFACTURER_NAME_LEN 11 + #define PRODUCT_NAME {'S','e','r','i','a','l','/','K','e','y','b','o','a','r','d','/','M','o','u','s','e','/','J','o','y','s','t','i','c','k'} + #define PRODUCT_NAME_LEN 30 + #define EP0_SIZE 64 + #define NUM_ENDPOINTS 7 + #define NUM_INTERFACE 6 + #define CDC_IAD_DESCRIPTOR 1 + #define CDC_STATUS_INTERFACE 0 + #define CDC_DATA_INTERFACE 1 // Serial + #define CDC_ACM_ENDPOINT 2 + #define CDC_RX_ENDPOINT 3 + #define CDC_TX_ENDPOINT 3 + #define CDC_ACM_SIZE 16 + #define CDC_RX_SIZE_480 512 + #define CDC_TX_SIZE_480 512 + #define CDC_RX_SIZE_12 64 + #define CDC_TX_SIZE_12 64 + #define KEYBOARD_INTERFACE 2 // Keyboard + #define KEYBOARD_ENDPOINT 4 + #define KEYBOARD_SIZE 8 // 8 = normal boot protocol, 16 = NKRO + #define KEYBOARD_INTERVAL 1 + #define KEYMEDIA_INTERFACE 5 // Keyboard Media Keys + #define KEYMEDIA_ENDPOINT 5 + #define KEYMEDIA_SIZE 8 + #define KEYMEDIA_INTERVAL 4 + #define MOUSE_INTERFACE 3 // Mouse + #define MOUSE_ENDPOINT 6 + #define MOUSE_SIZE 8 + #define MOUSE_INTERVAL 2 + #define JOYSTICK_INTERFACE 4 // Joystick + #define JOYSTICK_ENDPOINT 7 + #define JOYSTICK_SIZE 12 // 12 = normal, 64 = extreme joystick + #define JOYSTICK_INTERVAL 1 + #define ENDPOINT2_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT3_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK + #define ENDPOINT4_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT5_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT6_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT7_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + +#elif defined(USB_TOUCHSCREEN) + #define VENDOR_ID 0x16C0 + #define PRODUCT_ID 0x04D3 + #define MANUFACTURER_NAME {'T','e','e','n','s','y','d','u','i','n','o'} + #define MANUFACTURER_NAME_LEN 11 + #define PRODUCT_NAME {'K','e','y','b','o','a','r','d','/','T','o','u','c','h','s','c','r','e','e','n'} + #define PRODUCT_NAME_LEN 20 + #define EP0_SIZE 64 + #define NUM_ENDPOINTS 5 + #define NUM_INTERFACE 4 + #define SEREMU_INTERFACE 1 // Serial emulation + #define SEREMU_TX_ENDPOINT 2 + #define SEREMU_TX_SIZE 64 + #define SEREMU_TX_INTERVAL 1 + #define SEREMU_RX_ENDPOINT 2 + #define SEREMU_RX_SIZE 32 + #define SEREMU_RX_INTERVAL 2 + #define KEYBOARD_INTERFACE 0 // Keyboard + #define KEYBOARD_ENDPOINT 3 + #define KEYBOARD_SIZE 8 // 8 = normal boot protocol, 16 = NKRO + #define KEYBOARD_INTERVAL 1 + #define KEYMEDIA_INTERFACE 2 // Keyboard Media Keys + #define KEYMEDIA_ENDPOINT 4 + #define KEYMEDIA_SIZE 8 + #define KEYMEDIA_INTERVAL 4 + #define MULTITOUCH_INTERFACE 3 // Touchscreen + #define MULTITOUCH_ENDPOINT 5 + #define MULTITOUCH_SIZE 9 + #define MULTITOUCH_FINGERS 10 + #define ENDPOINT2_CONFIG ENDPOINT_RECEIVE_INTERRUPT + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT3_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT4_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT5_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + +#elif defined(USB_HID_TOUCHSCREEN) + #define VENDOR_ID 0x16C0 + #define PRODUCT_ID 0x04D4 + #define MANUFACTURER_NAME {'T','e','e','n','s','y','d','u','i','n','o'} + #define MANUFACTURER_NAME_LEN 11 + #define PRODUCT_NAME {'K','e','y','b','o','a','r','d','/','M','o','u','s','e','/','T','o','u','c','h','s','c','r','e','e','n'} + #define PRODUCT_NAME_LEN 26 + #define EP0_SIZE 64 + #define NUM_ENDPOINTS 6 + #define NUM_INTERFACE 5 + #define SEREMU_INTERFACE 2 // Serial emulation + #define SEREMU_TX_ENDPOINT 2 + #define SEREMU_TX_SIZE 64 + #define SEREMU_TX_INTERVAL 1 + #define SEREMU_RX_ENDPOINT 2 + #define SEREMU_RX_SIZE 32 + #define SEREMU_RX_INTERVAL 2 + #define KEYBOARD_INTERFACE 0 // Keyboard + #define KEYBOARD_ENDPOINT 3 + #define KEYBOARD_SIZE 8 // 8 = normal boot protocol, 16 = NKRO + #define KEYBOARD_INTERVAL 1 + #define KEYMEDIA_INTERFACE 3 // Keyboard Media Keys + #define KEYMEDIA_ENDPOINT 4 + #define KEYMEDIA_SIZE 8 + #define KEYMEDIA_INTERVAL 4 + #define MOUSE_INTERFACE 1 // Mouse + #define MOUSE_ENDPOINT 6 + #define MOUSE_SIZE 8 + #define MOUSE_INTERVAL 2 + #define MULTITOUCH_INTERFACE 4 // Touchscreen + #define MULTITOUCH_ENDPOINT 5 + #define MULTITOUCH_SIZE 9 + #define MULTITOUCH_FINGERS 10 + #define ENDPOINT2_CONFIG ENDPOINT_RECEIVE_INTERRUPT + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT3_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT4_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT5_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT6_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + +#elif defined(USB_MIDI) + #define VENDOR_ID 0x16C0 + #define PRODUCT_ID 0x0485 + #define MANUFACTURER_NAME {'T','e','e','n','s','y','d','u','i','n','o'} + #define MANUFACTURER_NAME_LEN 11 + #define PRODUCT_NAME {'T','e','e','n','s','y',' ','M','I','D','I'} + #define PRODUCT_NAME_LEN 11 + #define EP0_SIZE 64 + #define NUM_ENDPOINTS 4 + #define NUM_INTERFACE 2 + #define SEREMU_INTERFACE 1 // Serial emulation + #define SEREMU_TX_ENDPOINT 2 + #define SEREMU_TX_SIZE 64 + #define SEREMU_TX_INTERVAL 1 + #define SEREMU_RX_ENDPOINT 2 + #define SEREMU_RX_SIZE 32 + #define SEREMU_RX_INTERVAL 2 + #define MIDI_INTERFACE 0 // MIDI + #define MIDI_NUM_CABLES 1 + #define MIDI_TX_ENDPOINT 3 + #define MIDI_TX_SIZE_12 64 + #define MIDI_TX_SIZE_480 512 + #define MIDI_RX_ENDPOINT 3 + #define MIDI_RX_SIZE_12 64 + #define MIDI_RX_SIZE_480 512 + #define ENDPOINT2_CONFIG ENDPOINT_RECEIVE_INTERRUPT + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT3_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK + +#elif defined(USB_MIDI4) + #define VENDOR_ID 0x16C0 + #define PRODUCT_ID 0x0485 + #define BCD_DEVICE 0x0211 + #define MANUFACTURER_NAME {'T','e','e','n','s','y','d','u','i','n','o'} + #define MANUFACTURER_NAME_LEN 11 + #define PRODUCT_NAME {'T','e','e','n','s','y',' ','M','I','D','I','x','4'} + #define PRODUCT_NAME_LEN 13 + #define EP0_SIZE 64 + #define NUM_ENDPOINTS 3 + #define NUM_INTERFACE 2 + #define SEREMU_INTERFACE 1 // Serial emulation + #define SEREMU_TX_ENDPOINT 2 + #define SEREMU_TX_SIZE 64 + #define SEREMU_TX_INTERVAL 1 + #define SEREMU_RX_ENDPOINT 2 + #define SEREMU_RX_SIZE 32 + #define SEREMU_RX_INTERVAL 2 + #define MIDI_INTERFACE 0 // MIDI + #define MIDI_NUM_CABLES 4 + #define MIDI_TX_ENDPOINT 3 + #define MIDI_TX_SIZE_12 64 + #define MIDI_TX_SIZE_480 512 + #define MIDI_RX_ENDPOINT 3 + #define MIDI_RX_SIZE_12 64 + #define MIDI_RX_SIZE_480 512 + #define ENDPOINT2_CONFIG ENDPOINT_RECEIVE_INTERRUPT + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT3_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK + +#elif defined(USB_MIDI16) + #define VENDOR_ID 0x16C0 + #define PRODUCT_ID 0x0485 + #define BCD_DEVICE 0x0212 + #define MANUFACTURER_NAME {'T','e','e','n','s','y','d','u','i','n','o'} + #define MANUFACTURER_NAME_LEN 11 + #define PRODUCT_NAME {'T','e','e','n','s','y',' ','M','I','D','I','x','1','6'} + #define PRODUCT_NAME_LEN 14 + #define EP0_SIZE 64 + #define NUM_ENDPOINTS 3 + #define NUM_INTERFACE 2 + #define SEREMU_INTERFACE 1 // Serial emulation + #define SEREMU_TX_ENDPOINT 2 + #define SEREMU_TX_SIZE 64 + #define SEREMU_TX_INTERVAL 1 + #define SEREMU_RX_ENDPOINT 2 + #define SEREMU_RX_SIZE 32 + #define SEREMU_RX_INTERVAL 2 + #define MIDI_INTERFACE 0 // MIDI + #define MIDI_NUM_CABLES 16 + #define MIDI_TX_ENDPOINT 3 + #define MIDI_TX_SIZE_12 64 + #define MIDI_TX_SIZE_480 512 + #define MIDI_RX_ENDPOINT 3 + #define MIDI_RX_SIZE_12 64 + #define MIDI_RX_SIZE_480 512 + #define ENDPOINT2_CONFIG ENDPOINT_RECEIVE_INTERRUPT + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT3_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK + +#elif defined(USB_MIDI_SERIAL) + #define VENDOR_ID 0x16C0 + #define PRODUCT_ID 0x0489 + #define MANUFACTURER_NAME {'T','e','e','n','s','y','d','u','i','n','o'} + #define MANUFACTURER_NAME_LEN 11 + #define PRODUCT_NAME {'T','e','e','n','s','y',' ','M','I','D','I'} + #define PRODUCT_NAME_LEN 11 + #define EP0_SIZE 64 + #define NUM_ENDPOINTS 4 + #define NUM_INTERFACE 3 + #define CDC_IAD_DESCRIPTOR 1 + #define CDC_STATUS_INTERFACE 0 + #define CDC_DATA_INTERFACE 1 // Serial + #define CDC_ACM_ENDPOINT 2 + #define CDC_RX_ENDPOINT 3 + #define CDC_TX_ENDPOINT 3 + #define CDC_ACM_SIZE 16 + #define CDC_RX_SIZE_480 512 + #define CDC_TX_SIZE_480 512 + #define CDC_RX_SIZE_12 64 + #define CDC_TX_SIZE_12 64 + #define MIDI_INTERFACE 2 // MIDI + #define MIDI_NUM_CABLES 1 + #define MIDI_TX_ENDPOINT 4 + #define MIDI_TX_SIZE_12 64 + #define MIDI_TX_SIZE_480 512 + #define MIDI_RX_ENDPOINT 4 + #define MIDI_RX_SIZE_12 64 + #define MIDI_RX_SIZE_480 512 + #define ENDPOINT2_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT3_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK + #define ENDPOINT4_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK + +#elif defined(USB_MIDI4_SERIAL) + #define VENDOR_ID 0x16C0 + #define PRODUCT_ID 0x0489 + #define BCD_DEVICE 0x0211 + #define MANUFACTURER_NAME {'T','e','e','n','s','y','d','u','i','n','o'} + #define MANUFACTURER_NAME_LEN 11 + #define PRODUCT_NAME {'T','e','e','n','s','y',' ','M','I','D','I','x','4'} + #define PRODUCT_NAME_LEN 13 + #define EP0_SIZE 64 + #define NUM_ENDPOINTS 4 + #define NUM_INTERFACE 3 + #define CDC_IAD_DESCRIPTOR 1 + #define CDC_STATUS_INTERFACE 0 + #define CDC_DATA_INTERFACE 1 // Serial + #define CDC_ACM_ENDPOINT 2 + #define CDC_RX_ENDPOINT 3 + #define CDC_TX_ENDPOINT 3 + #define CDC_ACM_SIZE 16 + #define CDC_RX_SIZE_480 512 + #define CDC_TX_SIZE_480 512 + #define CDC_RX_SIZE_12 64 + #define CDC_TX_SIZE_12 64 + #define MIDI_INTERFACE 2 // MIDI + #define MIDI_NUM_CABLES 4 + #define MIDI_TX_ENDPOINT 4 + #define MIDI_TX_SIZE_12 64 + #define MIDI_TX_SIZE_480 512 + #define MIDI_RX_ENDPOINT 4 + #define MIDI_RX_SIZE_12 64 + #define MIDI_RX_SIZE_480 512 + #define ENDPOINT2_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT3_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK + #define ENDPOINT4_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK + +#elif defined(USB_MIDI16_SERIAL) + #define VENDOR_ID 0x16C0 + #define PRODUCT_ID 0x0489 + #define BCD_DEVICE 0x0212 + #define MANUFACTURER_NAME {'T','e','e','n','s','y','d','u','i','n','o'} + #define MANUFACTURER_NAME_LEN 11 + #define PRODUCT_NAME {'T','e','e','n','s','y',' ','M','I','D','I','x','1','6'} + #define PRODUCT_NAME_LEN 14 + #define EP0_SIZE 64 + #define NUM_ENDPOINTS 4 + #define NUM_INTERFACE 3 + #define CDC_IAD_DESCRIPTOR 1 + #define CDC_STATUS_INTERFACE 0 + #define CDC_DATA_INTERFACE 1 // Serial + #define CDC_ACM_ENDPOINT 2 + #define CDC_RX_ENDPOINT 3 + #define CDC_TX_ENDPOINT 3 + #define CDC_ACM_SIZE 16 + #define CDC_RX_SIZE_480 512 + #define CDC_TX_SIZE_480 512 + #define CDC_RX_SIZE_12 64 + #define CDC_TX_SIZE_12 64 + #define MIDI_INTERFACE 2 // MIDI + #define MIDI_NUM_CABLES 16 + #define MIDI_TX_ENDPOINT 4 + #define MIDI_TX_SIZE_12 64 + #define MIDI_TX_SIZE_480 512 + #define MIDI_RX_ENDPOINT 4 + #define MIDI_RX_SIZE_12 64 + #define MIDI_RX_SIZE_480 512 + #define ENDPOINT2_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT3_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK + #define ENDPOINT4_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK + +#elif defined(USB_RAWHID) + #define VENDOR_ID 0x16C0 + #define PRODUCT_ID 0x0486 + #define RAWHID_USAGE_PAGE 0xFFAB // recommended: 0xFF00 to 0xFFFF + #define RAWHID_USAGE 0x0200 // recommended: 0x0100 to 0xFFFF + #define MANUFACTURER_NAME {'T','e','e','n','s','y','d','u','i','n','o'} + #define MANUFACTURER_NAME_LEN 11 + #define PRODUCT_NAME {'T','e','e','n','s','y','d','u','i','n','o',' ','R','a','w','H','I','D'} + #define PRODUCT_NAME_LEN 18 + #define EP0_SIZE 64 + #define NUM_ENDPOINTS 4 + #define NUM_INTERFACE 2 + #define RAWHID_INTERFACE 0 // RawHID + #define RAWHID_TX_ENDPOINT 3 + #define RAWHID_TX_SIZE 64 + #define RAWHID_TX_INTERVAL 1 // TODO: is this ok for 480 Mbit speed + #define RAWHID_RX_ENDPOINT 4 + #define RAWHID_RX_SIZE 64 + #define RAWHID_RX_INTERVAL 1 // TODO: is this ok for 480 Mbit speed + #define SEREMU_INTERFACE 1 // Serial emulation + #define SEREMU_TX_ENDPOINT 2 + #define SEREMU_TX_SIZE 64 + #define SEREMU_TX_INTERVAL 1 // TODO: is this ok for 480 Mbit speed + #define SEREMU_RX_ENDPOINT 2 + #define SEREMU_RX_SIZE 32 + #define SEREMU_RX_INTERVAL 2 // TODO: is this ok for 480 Mbit speed + #define ENDPOINT2_CONFIG ENDPOINT_RECEIVE_INTERRUPT + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT3_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT4_CONFIG ENDPOINT_RECEIVE_INTERRUPT + ENDPOINT_TRANSMIT_UNUSED + +#elif defined(USB_FLIGHTSIM) + #define VENDOR_ID 0x16C0 + #define PRODUCT_ID 0x0488 + #define MANUFACTURER_NAME {'T','e','e','n','s','y','d','u','i','n','o'} + #define MANUFACTURER_NAME_LEN 11 + #define PRODUCT_NAME {'T','e','e','n','s','y',' ','F','l','i','g','h','t',' ','S','i','m',' ','C','o','n','t','r','o','l','s'} + #define PRODUCT_NAME_LEN 26 + #define EP0_SIZE 64 + #define NUM_ENDPOINTS 3 + #define NUM_INTERFACE 2 + #define FLIGHTSIM_INTERFACE 0 // Flight Sim Control + #define FLIGHTSIM_TX_ENDPOINT 3 + #define FLIGHTSIM_TX_SIZE 64 + #define FLIGHTSIM_TX_INTERVAL 1 + #define FLIGHTSIM_RX_ENDPOINT 3 + #define FLIGHTSIM_RX_SIZE 64 + #define FLIGHTSIM_RX_INTERVAL 1 + #define SEREMU_INTERFACE 1 // Serial emulation + #define SEREMU_TX_ENDPOINT 2 + #define SEREMU_TX_SIZE 64 + #define SEREMU_TX_INTERVAL 1 + #define SEREMU_RX_ENDPOINT 2 + #define SEREMU_RX_SIZE 32 + #define SEREMU_RX_INTERVAL 2 + #define ENDPOINT2_CONFIG ENDPOINT_RECEIVE_INTERRUPT + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT3_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK + +#elif defined(USB_FLIGHTSIM_JOYSTICK) + #define VENDOR_ID 0x16C0 + #define PRODUCT_ID 0x0488 + #define BCD_DEVICE 0x0211 + #define MANUFACTURER_NAME {'T','e','e','n','s','y','d','u','i','n','o'} + #define MANUFACTURER_NAME_LEN 11 + #define PRODUCT_NAME {'T','e','e','n','s','y',' ','F','l','i','g','h','t',' ','S','i','m',' ','C','o','n','t','r','o','l','s'} + #define PRODUCT_NAME_LEN 26 + #define EP0_SIZE 64 + #define NUM_ENDPOINTS 4 + #define NUM_INTERFACE 3 + #define FLIGHTSIM_INTERFACE 0 // Flight Sim Control + #define FLIGHTSIM_TX_ENDPOINT 3 + #define FLIGHTSIM_TX_SIZE 64 + #define FLIGHTSIM_TX_INTERVAL 1 + #define FLIGHTSIM_RX_ENDPOINT 3 + #define FLIGHTSIM_RX_SIZE 64 + #define FLIGHTSIM_RX_INTERVAL 1 + #define SEREMU_INTERFACE 1 // Serial emulation + #define SEREMU_TX_ENDPOINT 2 + #define SEREMU_TX_SIZE 64 + #define SEREMU_TX_INTERVAL 1 + #define SEREMU_RX_ENDPOINT 2 + #define SEREMU_RX_SIZE 32 + #define SEREMU_RX_INTERVAL 2 + #define JOYSTICK_INTERFACE 2 // Joystick + #define JOYSTICK_ENDPOINT 4 + #define JOYSTICK_SIZE 12 // 12 = normal, 64 = extreme joystick + #define JOYSTICK_INTERVAL 1 + #define ENDPOINT2_CONFIG ENDPOINT_RECEIVE_INTERRUPT + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT3_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK + #define ENDPOINT4_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + +#elif defined(USB_MTPDISK) + #define VENDOR_ID 0x16C0 + #define PRODUCT_ID 0x04D1 + #define MANUFACTURER_NAME {'T','e','e','n','s','y','d','u','i','n','o'} + #define MANUFACTURER_NAME_LEN 11 + #define PRODUCT_NAME {'T','e','e','n','s','y',' ','M','T','P',' ','D','i','s','k'} + #define PRODUCT_NAME_LEN 15 + #define EP0_SIZE 64 + #define NUM_ENDPOINTS 4 + #define NUM_INTERFACE 2 + #define MTP_INTERFACE 1 // MTP Disk + #define MTP_TX_ENDPOINT 3 + #define MTP_TX_SIZE_12 64 + #define MTP_TX_SIZE_480 512 + #define MTP_RX_ENDPOINT 3 + #define MTP_RX_SIZE_12 64 + #define MTP_RX_SIZE_480 512 + #define MTP_EVENT_ENDPOINT 4 + #define MTP_EVENT_SIZE 32 + #define MTP_EVENT_INTERVAL_12 10 // 10 = 10 ms + #define MTP_EVENT_INTERVAL_480 7 // 7 = 8 ms + #define SEREMU_INTERFACE 0 // Serial emulation + #define SEREMU_TX_ENDPOINT 2 + #define SEREMU_TX_SIZE 64 + #define SEREMU_TX_INTERVAL 1 + #define SEREMU_RX_ENDPOINT 2 + #define SEREMU_RX_SIZE 32 + #define SEREMU_RX_INTERVAL 2 + #define ENDPOINT2_CONFIG ENDPOINT_RECEIVE_INTERRUPT + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT3_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK + #define ENDPOINT4_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + +#elif defined(USB_MTPDISK_SERIAL) + #define VENDOR_ID 0x16C0 + #define PRODUCT_ID 0x04D5 + #define MANUFACTURER_NAME {'T','e','e','n','s','y','d','u','i','n','o'} + #define MANUFACTURER_NAME_LEN 11 + #define PRODUCT_NAME {'T','e','e','n','s','y',' ','M','T','P',' ','D','i','s','k'} + #define PRODUCT_NAME_LEN 15 + #define EP0_SIZE 64 + #define NUM_ENDPOINTS 5 + #define NUM_INTERFACE 3 + #define CDC_IAD_DESCRIPTOR 1 + #define CDC_STATUS_INTERFACE 0 + #define CDC_DATA_INTERFACE 1 // Serial + #define CDC_ACM_ENDPOINT 2 + #define CDC_RX_ENDPOINT 3 + #define CDC_TX_ENDPOINT 3 + #define CDC_ACM_SIZE 16 + #define CDC_RX_SIZE_480 512 + #define CDC_TX_SIZE_480 512 + #define CDC_RX_SIZE_12 64 + #define CDC_TX_SIZE_12 64 + #define MTP_INTERFACE 2 // MTP Disk + #define MTP_TX_ENDPOINT 4 + #define MTP_TX_SIZE_12 64 + #define MTP_TX_SIZE_480 512 + #define MTP_RX_ENDPOINT 4 + #define MTP_RX_SIZE_12 64 + #define MTP_RX_SIZE_480 512 + #define MTP_EVENT_ENDPOINT 5 + #define MTP_EVENT_SIZE 32 + #define MTP_EVENT_INTERVAL_12 10 // 10 = 10 ms + #define MTP_EVENT_INTERVAL_480 7 // 7 = 8 ms + #define ENDPOINT2_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT3_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK + #define ENDPOINT4_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK + #define ENDPOINT5_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + +#elif defined(USB_AUDIO) + #define VENDOR_ID 0x16C0 + #define PRODUCT_ID 0x04D2 + #define MANUFACTURER_NAME {'T','e','e','n','s','y','d','u','i','n','o'} + #define MANUFACTURER_NAME_LEN 11 + #define PRODUCT_NAME {'T','e','e','n','s','y',' ','A','u','d','i','o'} + #define PRODUCT_NAME_LEN 12 + + #define EP0_SIZE 64 + #define NUM_ENDPOINTS 4 + #define NUM_INTERFACE 4 + #define SEREMU_INTERFACE 0 // Serial emulation + #define SEREMU_TX_ENDPOINT 2 + #define SEREMU_TX_SIZE 64 + #define SEREMU_TX_INTERVAL 1 + #define SEREMU_RX_ENDPOINT 2 + #define SEREMU_RX_SIZE 32 + #define SEREMU_RX_INTERVAL 2 + + #define AUDIO_INTERFACE 1 // Audio (uses 3 consecutive interfaces) + #define AUDIO_TX_ENDPOINT 3 + #define AUDIO_RX_ENDPOINT 3 + #define AUDIO_SYNC_ENDPOINT 4 + + #define ENDPOINT2_CONFIG ENDPOINT_RECEIVE_INTERRUPT + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT3_CONFIG ENDPOINT_RECEIVE_ISOCHRONOUS + ENDPOINT_TRANSMIT_ISOCHRONOUS + #define ENDPOINT4_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_ISOCHRONOUS + +#elif defined(USB_MIDI_AUDIO_SERIAL) + #define VENDOR_ID 0x16C0 + #define PRODUCT_ID 0x048A + #define MANUFACTURER_NAME {'T','e','e','n','s','y','d','u','i','n','o'} + #define MANUFACTURER_NAME_LEN 11 + #define PRODUCT_NAME {'T','e','e','n','s','y',' ','M','I','D','I','/','A','u','d','i','o'} + #define PRODUCT_NAME_LEN 17 + #define EP0_SIZE 64 + #define NUM_ENDPOINTS 6 + #define NUM_INTERFACE 6 + #define CDC_IAD_DESCRIPTOR 1 + #define CDC_STATUS_INTERFACE 0 + #define CDC_DATA_INTERFACE 1 // Serial + #define CDC_ACM_ENDPOINT 2 + #define CDC_RX_ENDPOINT 3 + #define CDC_TX_ENDPOINT 3 + #define CDC_ACM_SIZE 16 + #define CDC_RX_SIZE_480 512 + #define CDC_TX_SIZE_480 512 + #define CDC_RX_SIZE_12 64 + #define CDC_TX_SIZE_12 64 + #define AUDIO_INTERFACE 2 // Audio (uses 3 consecutive interfaces) + #define AUDIO_TX_ENDPOINT 5 + #define AUDIO_RX_ENDPOINT 5 + #define AUDIO_SYNC_ENDPOINT 6 + #define MIDI_INTERFACE 5 // MIDI (usb2 specification: interface number of midi must directly follow the audio interface numbers) + #define MIDI_NUM_CABLES 1 + #define MIDI_TX_ENDPOINT 4 + #define MIDI_TX_SIZE_12 64 + #define MIDI_TX_SIZE_480 512 + #define MIDI_RX_ENDPOINT 4 + #define MIDI_RX_SIZE_12 64 + #define MIDI_RX_SIZE_480 512 + #define ENDPOINT2_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT3_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK + #define ENDPOINT4_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK + #define ENDPOINT5_CONFIG ENDPOINT_RECEIVE_ISOCHRONOUS + ENDPOINT_TRANSMIT_ISOCHRONOUS + #define ENDPOINT6_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_ISOCHRONOUS + +#elif defined(USB_MIDI16_AUDIO_SERIAL) + #define VENDOR_ID 0x16C0 + #define PRODUCT_ID 0x048A + #define BCD_DEVICE 0x0212 + #define MANUFACTURER_NAME {'T','e','e','n','s','y','d','u','i','n','o'} + #define MANUFACTURER_NAME_LEN 11 + #define PRODUCT_NAME {'T','e','e','n','s','y',' ','M','I','D','I','x','1','6','/','A','u','d','i','o'} + #define PRODUCT_NAME_LEN 20 + #define EP0_SIZE 64 + #define NUM_ENDPOINTS 8 + #define NUM_INTERFACE 6 + #define CDC_IAD_DESCRIPTOR 1 + #define CDC_STATUS_INTERFACE 0 + #define CDC_DATA_INTERFACE 1 // Serial + #define CDC_ACM_ENDPOINT 2 + #define CDC_RX_ENDPOINT 3 + #define CDC_TX_ENDPOINT 3 + #define CDC_ACM_SIZE 16 + #define CDC_RX_SIZE_480 512 + #define CDC_TX_SIZE_480 512 + #define CDC_RX_SIZE_12 64 + #define CDC_TX_SIZE_12 64 + #define AUDIO_INTERFACE 2 // Audio (uses 3 consecutive interfaces) + #define AUDIO_TX_ENDPOINT 5 + #define AUDIO_RX_ENDPOINT 5 + #define AUDIO_SYNC_ENDPOINT 6 + #define MIDI_INTERFACE 5 // MIDI (usb2 specification: interface number of midi must directly follow the audio interface numbers) + #define MIDI_NUM_CABLES 16 + #define MIDI_TX_ENDPOINT 4 + #define MIDI_TX_SIZE_12 64 + #define MIDI_TX_SIZE_480 512 + #define MIDI_RX_ENDPOINT 4 + #define MIDI_RX_SIZE_12 64 + #define MIDI_RX_SIZE_480 512 + #define ENDPOINT2_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT + #define ENDPOINT3_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK + #define ENDPOINT4_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_BULK + #define ENDPOINT5_CONFIG ENDPOINT_RECEIVE_ISOCHRONOUS + ENDPOINT_TRANSMIT_ISOCHRONOUS + #define ENDPOINT6_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_ISOCHRONOUS + +#elif defined(USB_EVERYTHING) + #define VENDOR_ID 0x16C0 + #define PRODUCT_ID 0x0476 + #define RAWHID_USAGE_PAGE 0xFFAB // recommended: 0xFF00 to 0xFFFF + #define RAWHID_USAGE 0x0200 // recommended: 0x0100 to 0xFFFF + #define DEVICE_CLASS 0xEF + #define DEVICE_SUBCLASS 0x02 + #define DEVICE_PROTOCOL 0x01 + #define MANUFACTURER_NAME {'T','e','e','n','s','y','d','u','i','n','o'} + #define MANUFACTURER_NAME_LEN 11 + #define PRODUCT_NAME {'A','l','l',' ','T','h','e',' ','T','h','i','n','g','s'} + #define PRODUCT_NAME_LEN 14 + #define EP0_SIZE 64 + #define NUM_ENDPOINTS 15 + #define NUM_INTERFACE 13 + #define CDC_IAD_DESCRIPTOR 1 + #define CDC_STATUS_INTERFACE 0 + #define CDC_DATA_INTERFACE 1 // Serial + #define CDC_ACM_ENDPOINT 1 + #define CDC_RX_ENDPOINT 2 + #define CDC_TX_ENDPOINT 2 + #define CDC_ACM_SIZE 16 + #define CDC_RX_SIZE 64 + #define CDC_TX_SIZE 64 + #define KEYBOARD_INTERFACE 2 // Keyboard + #define KEYBOARD_ENDPOINT 4 + #define KEYBOARD_SIZE 8 + #define KEYBOARD_INTERVAL 1 + #define MOUSE_INTERFACE 3 // Mouse + #define MOUSE_ENDPOINT 5 + #define MOUSE_SIZE 8 + #define MOUSE_INTERVAL 2 + #define RAWHID_INTERFACE 4 // RawHID + #define RAWHID_TX_ENDPOINT 6 + #define RAWHID_TX_SIZE 64 + #define RAWHID_TX_INTERVAL 1 + #define RAWHID_RX_ENDPOINT 6 + #define RAWHID_RX_SIZE 64 + #define RAWHID_RX_INTERVAL 1 + #define FLIGHTSIM_INTERFACE 5 // Flight Sim Control + #define FLIGHTSIM_TX_ENDPOINT 9 + #define FLIGHTSIM_TX_SIZE 64 + #define FLIGHTSIM_TX_INTERVAL 1 + #define FLIGHTSIM_RX_ENDPOINT 9 + #define FLIGHTSIM_RX_SIZE 64 + #define FLIGHTSIM_RX_INTERVAL 1 + #define JOYSTICK_INTERFACE 6 // Joystick + #define JOYSTICK_ENDPOINT 10 + #define JOYSTICK_SIZE 12 // 12 = normal, 64 = extreme joystick + #define JOYSTICK_INTERVAL 1 +/* + #define MTP_INTERFACE 8 // MTP Disk + #define MTP_TX_ENDPOINT 11 + #define MTP_TX_SIZE 64 + #define MTP_RX_ENDPOINT 3 + #define MTP_RX_SIZE 64 + #define MTP_EVENT_ENDPOINT 11 + #define MTP_EVENT_SIZE 16 + #define MTP_EVENT_INTERVAL 10 +*/ + #define KEYMEDIA_INTERFACE 7 // Keyboard Media Keys + #define KEYMEDIA_ENDPOINT 12 + #define KEYMEDIA_SIZE 8 + #define KEYMEDIA_INTERVAL 4 + #define AUDIO_INTERFACE 8 // Audio (uses 3 consecutive interfaces) + #define AUDIO_TX_ENDPOINT 13 + #define AUDIO_RX_ENDPOINT 13 + #define AUDIO_SYNC_ENDPOINT 14 + #define MIDI_INTERFACE 11 // MIDI (usb2 specification: interface number of midi must directly follow the audio interface numbers) + #define MIDI_NUM_CABLES 16 + #define MIDI_TX_ENDPOINT 3 + #define MIDI_TX_SIZE 64 + #define MIDI_RX_ENDPOINT 3 + #define MIDI_RX_SIZE 64 + #define MULTITOUCH_INTERFACE 12 // Touchscreen + #define MULTITOUCH_ENDPOINT 15 + #define MULTITOUCH_SIZE 9 + #define MULTITOUCH_FINGERS 10 + #define ENDPOINT1_CONFIG ENDPOINT_TRANSMIT_ONLY + #define ENDPOINT2_CONFIG ENDPOINT_TRANSMIT_AND_RECEIVE + #define ENDPOINT3_CONFIG ENDPOINT_TRANSMIT_AND_RECEIVE + #define ENDPOINT4_CONFIG ENDPOINT_TRANSMIT_ONLY + #define ENDPOINT5_CONFIG ENDPOINT_TRANSMIT_ONLY + #define ENDPOINT6_CONFIG ENDPOINT_TRANSMIT_AND_RECEIVE + #define ENDPOINT7_CONFIG ENDPOINT_TRANSMIT_AND_RECEIVE + #define ENDPOINT8_CONFIG ENDPOINT_TRANSMIT_ONLY + #define ENDPOINT9_CONFIG ENDPOINT_TRANSMIT_AND_RECEIVE + #define ENDPOINT10_CONFIG ENDPOINT_TRANSMIT_ONLY + #define ENDPOINT11_CONFIG ENDPOINT_TRANSMIT_AND_RECEIVE + #define ENDPOINT12_CONFIG ENDPOINT_TRANSMIT_ONLY + #define ENDPOINT13_CONFIG (ENDPOINT_RECEIVE_ISOCHRONOUS|ENDPOINT_TRANSMIT_ISOCHRONOUS) + #define ENDPOINT14_CONFIG ENDPOINT_TRANSMIT_ISOCHRONOUS + #define ENDPOINT15_CONFIG ENDPOINT_TRANSMIT_ONLY + +#endif + +#ifdef USB_DESC_LIST_DEFINE +#if defined(NUM_ENDPOINTS) && NUM_ENDPOINTS > 0 +// NUM_ENDPOINTS = number of non-zero endpoints (0 to 7) +extern const uint32_t usb_endpoint_config_table[NUM_ENDPOINTS]; + +typedef struct { + uint16_t wValue; + uint16_t wIndex; + const uint8_t *addr; + uint16_t length; +} usb_descriptor_list_t; + +extern const usb_descriptor_list_t usb_descriptor_list[]; +#endif // NUM_ENDPOINTS +#endif // USB_DESC_LIST_DEFINE + +#ifdef AUDIO_INTERFACE + #define CEIL_DIV(x, y) (((x) + (y) - 1) / (y)) + #ifndef AUDIO_SUBSLOT_SIZE + #define AUDIO_SUBSLOT_SIZE 2 //size of an audio sample in bytes (possible values: 1,2,3 or 4) + #endif + #define AUDIO_BITRESOLUTION (8*AUDIO_SUBSLOT_SIZE) + + //Define in the usb audio 2 specification (Audio20 final.pdf) on page 26 + #define USB_AUDIO_CHANNEL_FL (1<<0) //front left + #define USB_AUDIO_CHANNEL_FR (1<<1) //front right + #define USB_AUDIO_CHANNEL_FC (1<<2) //front center + #define USB_AUDIO_CHANNEL_LFE (1<<3) //low frequency effects + #define USB_AUDIO_CHANNEL_RL (1<<4) //rear left sometimes called BL (back left) + #define USB_AUDIO_CHANNEL_RR (1<<5) //rear right sometimes called BR (back right) + #define USB_AUDIO_CHANNEL_FLC (1<<6) //front left of center + #define USB_AUDIO_CHANNEL_FRC (1<<7) //front right of center + #define USB_AUDIO_CHANNEL_RC (1<<8) //rear center + #define USB_AUDIO_CHANNEL_SL (1<<9) //side left + #define USB_AUDIO_CHANNEL_SR (1<<10) //side right + + #define USB_AUDIO_CHANNEL_TC (1<<11) //top center + #define USB_AUDIO_CHANNEL_TFL (1<<12) //top front left + #define USB_AUDIO_CHANNEL_TFC (1<<13) //top front center + #define USB_AUDIO_CHANNEL_TFR (1<<14) //top front right + #define USB_AUDIO_CHANNEL_TRL (1<<15) //top rear left + #define USB_AUDIO_CHANNEL_TRC (1<<16) //top rear center + #define USB_AUDIO_CHANNEL_TRR (1<<17) //top rear right + #define USB_AUDIO_CHANNEL_TFLC (1<<18) //top front left of center + #define USB_AUDIO_CHANNEL_TFRC (1<<19) //top front right of center + + #define USB_AUDIO_CHANNEL_LLFE (1<<20) //left low frequency effects + #define USB_AUDIO_CHANNEL_RLFE (1<<21) //right low frequency effects + #define USB_AUDIO_CHANNEL_TSL (1<<22) //top side left + #define USB_AUDIO_CHANNEL_TSR (1<<23) //top side right + #define USB_AUDIO_CHANNEL_BC (1<<24) //bottom center + #define USB_AUDIO_CHANNEL_RLC (1<<25) //rear left of center + #define USB_AUDIO_CHANNEL_RRC (1<<26) //rear right of center + #define USB_AUDIO_CHANNEL_RD (1<<27) //raw data + + //ASYNC_TX_ENDPOINT: if defined, the output behaves like an asynchronous endpoint, and like an adaptive one otherwise + //Asynchronous: The usb audio output sometimes sends a sample more or less to the host to prevent buffer over- and underruns. + //Adaptive: The usb audio output sends samples twice or discards samples to prevent buffer over- and underruns. + // Asynchronous should be better. However, in the original USB audio implementation, + // the endpoint was adaptive, so it is possible to switch back to adaptive by + // commenting out this define (e.g., for debugging purposes). + #define ASYNC_TX_ENDPOINT + + #define MS_PER_SEC 1000 + #define MAX_SAMPLES_PER_MS CEIL_DIV(AUDIO_SAMPLE_RATE_I, MS_PER_SEC) //ceil becaause e.g. for 44.1khz we want 45 samples per ms not 44 + #define RX_TX_ADD_SAMPLES 1 //we allow one additional samples per frame + + // defines for 480MBit ================================ + #if defined(AUDIO_USB_CHANNEL_COUNT) // channel count set on Tools menu... + #define USB_AUDIO_NO_CHANNELS_480 (((AUDIO_USB_CHANNEL_COUNT) < 2) ? 2 : ((AUDIO_USB_CHANNEL_COUNT) & ~1)) // ...use it. Make sure it's even and larger than 1 + #else + #define USB_AUDIO_NO_CHANNELS_480 8 // ...or hand-edit it + #endif // defined(AUDIO_USB_CHANNEL_COUNT) + + #if USB_AUDIO_NO_CHANNELS_480 == 2 + #define CHANNEL_CONFIG_480 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR) + #endif + #if USB_AUDIO_NO_CHANNELS_480 == 4 + #define CHANNEL_CONFIG_480 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR |USB_AUDIO_CHANNEL_RL | USB_AUDIO_CHANNEL_RR) + #endif + #if USB_AUDIO_NO_CHANNELS_480 == 6 + #define CHANNEL_CONFIG_480 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR |USB_AUDIO_CHANNEL_FC | USB_AUDIO_CHANNEL_LFE | USB_AUDIO_CHANNEL_SL | USB_AUDIO_CHANNEL_SR) + #endif + #if USB_AUDIO_NO_CHANNELS_480 == 8 + #define CHANNEL_CONFIG_480 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR |USB_AUDIO_CHANNEL_FC | USB_AUDIO_CHANNEL_LFE | USB_AUDIO_CHANNEL_RL | USB_AUDIO_CHANNEL_RR | USB_AUDIO_CHANNEL_SL | USB_AUDIO_CHANNEL_SR) + #endif + + #define RX_TX_BYTES_ADD_SAMPLES_480 (RX_TX_ADD_SAMPLES * USB_AUDIO_NO_CHANNELS_480 * AUDIO_SUBSLOT_SIZE) //bytes needed for the additional samples + #define availBytesPerFrame_480 (1024 - RX_TX_BYTES_ADD_SAMPLES_480) //high speed: we have 1024 bytes per frame minus the space for the additional samples (e.g. if the Teensy requests more samples from the host than expexted) + + #define BANDWIDTH_BYTES_PER_MSEC_480 (MAX_SAMPLES_PER_MS * AUDIO_SUBSLOT_SIZE * USB_AUDIO_NO_CHANNELS_480) + #if BANDWIDTH_BYTES_PER_MSEC_480 <= availBytesPerFrame_480 * 8 + //we can not handle more than 1024 bytes per mircro frame + //if the bandwidth is larger than that, AUDIO_POLLING_INTERVAL_480 will just not be defined and the program won't compile + #if BANDWIDTH_BYTES_PER_MSEC_480 > availBytesPerFrame_480 * 4 //more than roughly 1024bytes/250us -> we need 8 micro frames per ms + #define AUDIO_POLLING_INTERVAL_480 1 //1 -> 2^(1-1)=1 -> every micro-frame + #elif BANDWIDTH_BYTES_PER_MSEC_480 > availBytesPerFrame_480 * 2 //more than roughly 1024bytes/500us -> we need 4 micro frames per ms + #define AUDIO_POLLING_INTERVAL_480 2 //2 -> 2^(2-1)=2 -> every 2 micro-frames + #elif BANDWIDTH_BYTES_PER_MSEC_480 > availBytesPerFrame_480 //more than roughly 1024bytes/1000us -> we need 2 micro frames per ms + #define AUDIO_POLLING_INTERVAL_480 3 //3 -> 2^(3-1)=4 -> every 4 micro-frames + #else + #define AUDIO_POLLING_INTERVAL_480 4 //4 -> 2^(4-1)=8 -> every 8 micro-frames + #endif + #endif + + #define AUDIO_NUM_SUBFRAMES_PER_POLLING_480 (1<<(AUDIO_POLLING_INTERVAL_480-1)) + #define AUDIO_NUM_FRAMES_PER_SEC_480 (8000 / AUDIO_NUM_SUBFRAMES_PER_POLLING_480) //8000 is the maximum number of micro frames per second + #define AUDIO_POLLING_INTERVAL_480_SEC (AUDIO_NUM_SUBFRAMES_PER_POLLING_480 *125*1e-6) + + //we add RX_TX_ADD_SAMPLES samples to the rx and tx buffer in order to be e.g. able to request more samples per polling interval (e.g. the clock of the usb host runs slower than the teensy clock) + #define AUDIO_RX_TX_SIZE_SAMPLES_480 (CEIL_DIV(AUDIO_SAMPLE_RATE_I, AUDIO_NUM_FRAMES_PER_SEC_480) + RX_TX_ADD_SAMPLES) //buffer size in samples per channel + #define AUDIO_RX_SIZE_480 (AUDIO_RX_TX_SIZE_SAMPLES_480 * USB_AUDIO_NO_CHANNELS_480 * AUDIO_SUBSLOT_SIZE) //total buffer size in bytes + #define AUDIO_TX_SIZE_480 AUDIO_RX_SIZE_480 + // end of defines for 480MBit ================================ + + // defines for 12MBit ================================ + #if defined(AUDIO_USB_CHANNEL_COUNT) // channel count set on Tools menu... + #define USB_AUDIO_NO_CHANNELS_12 (((AUDIO_USB_CHANNEL_COUNT) < 2) ? 2 : ((AUDIO_USB_CHANNEL_COUNT) & ~1)) // ...use it. Make sure it's even and larger than 1 + #else + #define USB_AUDIO_NO_CHANNELS_12 2 // ...or hand-edit it + #endif // defined(AUDIO_USB_CHANNEL_COUNT) + + // At full speed we can't transmit or receive more than 1023 bytes per ms + #define AUDIO_POLLING_INTERVAL_12 1 //must be set to 1 (polling interval is 1ms for full speed endpoints) + + #define availBytesPerFrame_12 1023 //1023 bytes per ms is the maximum for full speed + + #define BANDWIDTH_BYTES_PER_MSEC_12 ((MAX_SAMPLES_PER_MS+RX_TX_ADD_SAMPLES) * AUDIO_SUBSLOT_SIZE * USB_AUDIO_NO_CHANNELS_12) + #if BANDWIDTH_BYTES_PER_MSEC_12 > availBytesPerFrame_12 + #undef USB_AUDIO_NO_CHANNELS_12 + #undef BANDWIDTH_BYTES_PER_MSEC_12 + #define USB_AUDIO_NO_CHANNELS_12 ((availBytesPerFrame_12 / ((MAX_SAMPLES_PER_MS+RX_TX_ADD_SAMPLES) * AUDIO_SUBSLOT_SIZE)) & ~1) //& ~1 to ensure that we get an even number of channels + #if USB_AUDIO_NO_CHANNELS_12 < 2 + // For AUDIO_SAMPLE_RATE_I >= 176.4kHz and AUDIO_SUBSLOT_SIZE==3, USB_AUDIO_NO_CHANNELS_12 won't be defined and the program won't compile + #undef USB_AUDIO_NO_CHANNELS_12 + #endif + #endif + + #if USB_AUDIO_NO_CHANNELS_12 == 2 + #define CHANNEL_CONFIG_12 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR) + #endif + #if USB_AUDIO_NO_CHANNELS_12 == 4 + #define CHANNEL_CONFIG_12 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR |USB_AUDIO_CHANNEL_RL | USB_AUDIO_CHANNEL_RR) + #endif + #if USB_AUDIO_NO_CHANNELS_12 == 6 + #define CHANNEL_CONFIG_12 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR |USB_AUDIO_CHANNEL_FC | USB_AUDIO_CHANNEL_LFE | USB_AUDIO_CHANNEL_SL | USB_AUDIO_CHANNEL_SR) + #endif + #if USB_AUDIO_NO_CHANNELS_12 == 8 + #define CHANNEL_CONFIG_12 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR |USB_AUDIO_CHANNEL_FC | USB_AUDIO_CHANNEL_LFE | USB_AUDIO_CHANNEL_RL | USB_AUDIO_CHANNEL_RR | USB_AUDIO_CHANNEL_SL | USB_AUDIO_CHANNEL_SR) + #endif + + #define AUDIO_NUM_SUBFRAMES_PER_POLLING_12 8 //must be 8 for full speed (8*125us = 1ms) + #define AUDIO_POLLING_INTERVAL_12_SEC (AUDIO_NUM_SUBFRAMES_PER_POLLING_12 *125*1e-6) + + #define AUDIO_RX_TX_SIZE_SAMPLES_12 (MAX_SAMPLES_PER_MS + RX_TX_ADD_SAMPLES) //buffer size in samples per channel + #define AUDIO_RX_SIZE_12 (AUDIO_RX_TX_SIZE_SAMPLES_12 * USB_AUDIO_NO_CHANNELS_12 * AUDIO_SUBSLOT_SIZE) //total buffer size in bytes + #define AUDIO_TX_SIZE_12 AUDIO_RX_SIZE_12 + // end of defines for 12MBit ================================ + + #if (USB_AUDIO_NO_CHANNELS_12) < (USB_AUDIO_NO_CHANNELS_480) + #define USB_AUDIO_MAX_NO_CHANNELS USB_AUDIO_NO_CHANNELS_480 + #else + #define USB_AUDIO_MAX_NO_CHANNELS USB_AUDIO_NO_CHANNELS_12 + #endif +#endif + diff --git a/patched_teensy_core/util/LastCall.h b/patched_teensy_core/util/LastCall.h new file mode 100644 index 0000000..1244e87 --- /dev/null +++ b/patched_teensy_core/util/LastCall.h @@ -0,0 +1,339 @@ + +#ifdef __cplusplus + +#ifndef lastCall_h_ +#define lastCall_h_ + +//#include //for debug prints + +#include +#include +#include +#include + +struct QR { + uint32_t q; + int32_t r; +}; + +template +static inline __attribute__((always_inline)) QR getQR(uint32_t d){ + QR qr; + qr.q = d / N; + qr.r = int32_t(d -qr.q* N); + return qr; +} + +template +static inline __attribute__((always_inline)) void subtract(const QR& q0, const QR& q1, QR& res){ + res.q = q0.q - q1.q; + res.r = q0.r - q1.r; + constexpr int32_t NSigned=int32_t(N); + if (res.r < 0) { + res.r += NSigned; + res.q--; + } +} + +template +static inline __attribute__((always_inline)) void add(const QR& q0, const QR& q1, QR& res){ + res.q = q0.q + q1.q; + res.r = q0.r + q1.r; + constexpr int32_t NSigned=int32_t(N); + if (res.r >=NSigned) { + res.r -= NSigned; + res.q++; + } +} + +//This class is used to estimate last call of a function that is called at a high frequency. +//It smoothes the time of the last call by means of history buffer that stores the time of the e.g. last 7 calls. +//Important: The class detects up to two outliers in the history and ignores them at the computation of the time of the last call +template +struct History{ + std::array data; + std::array dataQR; + std::array state; //-1...not set yet (after reset), 0...default, 1...evaluated + std::array errors; + std::array sortedIdx; + + QR com; + uint32_t comOffset; + bool valid=false; + int32_t currentWriteIndex=0; +}; +inline double toInt32Range(double x){ + constexpr double pow2_32 = double(UINT32_MAX)+1.; + constexpr double pow2_31 = 0.5*pow2_32; + if (x < -pow2_31){ + x += pow2_32; + } + if (x > pow2_31){ + x -= pow2_32; + } + return x; +} +template +static inline __attribute__((always_inline)) int32_t increment(int32_t n){ + return (n == N-1) ? 0 : (n + 1); +} +template +static inline __attribute__((always_inline)) int32_t decrement(int32_t n){ + return (n == 0) ? (N - 1) : (n - 1); +} + +template +class LastCall +{ + public: + LastCall() { + resetHistory(); + } + + void updateExpectedTimeStep(double expectedTimeStep, bool setBoundary=true) { + if (setBoundary){ + _boundaryFraction= uint32_t(UINT32_MAX- (3.*expectedTimeStep*N))/N; //3. is the safety factor + _boundary= _boundaryFraction*N; //boundary % N ==0 is handy because we then don't need to update the reminders + } + //make the perfect sequence + _comPerfect.q=0; + _comPerfect.r=0; + for (uint32_t i =0; i< N; i++){ + _dataPerfect[i] = uint32_t(i*expectedTimeStep+0.5); + _dataPerfectQR[i] = getQR(_dataPerfect[i]); + add(_comPerfect, _dataPerfectQR[i], _comPerfect); + } + } + void resetHistory() { + memset(_history.data.data(), 0, _history.data.size()*sizeof(_history.data[0])); + QR qr; + qr.q=0; + qr.r=0; + std::fill(_history.dataQR.begin(), _history.dataQR.end(), qr); + std::fill(_history.state.begin(), _history.state.end(), -1); + std::fill(_history.sortedIdx.begin(), _history.sortedIdx.end(), -1); + + _history.com.q=0; + _history.com.r=0; + _history.comOffset = 0; + _history.valid=false; + _history.currentWriteIndex=0; + } + void reset(double expectedTimeStep){ + updateExpectedTimeStep(expectedTimeStep); + resetHistory(); + } + + void addCall(uint32_t time){ + constexpr int32_t NSIGNED = int32_t(N); + + time -= _history.comOffset; + int32_t& w=_history.currentWriteIndex; + // Remember old oldest before overwriting + const uint32_t oldOldest = _history.data[w]; + + //remove the contribution of the old data point + QR& dataQr=_history.dataQR[w]; + subtract(_history.com, dataQr, _history.com); + _history.state[w] = 0; + //============================================== + dataQr = getQR(time); + _history.data[w]=time; + + if(!_history.valid){ + //we make sure that from the beginning on com is in the interval [_history.data[(w+1)%N], _history.data[w]] + //Otherwise com can be relatively small and an underrun can occur when _boundary is subtracted + _history.valid=true; + _history.com.q =time; + _history.com.r =0; + int32_t idx = w; + for (uint32_t i =1; i < N; i++){ + idx = increment(idx); + _history.dataQR[idx] = dataQr; + _history.data[idx]=time; + } + } + else { + add(_history.com, dataQr, _history.com); + } + w = increment(w); + + const uint32_t newOldest = _history.data[w]; + + // Two distinct reasons to "rebase" + const bool nearTop = (newOldest >= _boundary); + const bool wrapSplit = (oldOldest > newOldest); // rare "true wrap" indicator + + if (nearTop || wrapSplit) { + // Always rebase data by subtracting boundary (mod 2^32 rotation) + for (uint32_t i = 0; i < N; i++) { + _history.data[i] -= _boundary; + } + + // Update comOffset (external epoch correction) + _history.comOffset += _boundary; + + if (!wrapSplit) { + // Fast path: safe because all entries are >= boundary in linear sense + for (uint32_t i = 0; i < N; i++) { + _history.dataQR[i].q -= _boundaryFraction; + } + _history.com.q -= _boundary; // because boundary == N * boundaryFraction + // reminders unchanged since boundary % N == 0 + } + else { + // Slow but safe path: recompute everything from rebased data[] + _history.com.q = 0; + _history.com.r = 0; + + for (uint32_t i = 0; i < N; i++) { + const uint32_t t = _history.data[i]; + _history.dataQR[i] = getQR(t); + add(_history.com, _history.dataQR[i], _history.com); + } + + // If you cache errors via state/map, you must invalidate them here: + std::fill(_history.state.begin(), _history.state.end(), 0); + } + } + //========================== + } + + History getHistory() const { + return _history; + } + + bool isHistoryValid() const { + return _history.valid; + } + + uint32_t getLastDuration()const{ + constexpr int32_t NSIGNED = int32_t(N); + int32_t upperIdx = decrement(_history.currentWriteIndex); + int32_t lowerIdx = decrement(upperIdx); + return _history.data[upperIdx] - _history.data[lowerIdx]; + } + template + double getLastCall(History&h) const { + constexpr int32_t halfN = int32_t(N / 2); + constexpr int32_t NSigned = int32_t(N); + + int32_t idx= decrement(h.currentWriteIndex); + if (h.state[h.currentWriteIndex] ==-1){ + //h is not completely filled and the algorithm can't work properly -> we just return the latest value + return double(h.data[idx] + h.comOffset); + } + + //remove all non-evaluated data + int32_t validSortedEnd=0; + for (int32_t i =0; i < NSigned; i++){ + if (h.sortedIdx[i] != -1 && h.state[h.sortedIdx[i]] > 0) { + if (validSortedEnd < i) { + h.sortedIdx[validSortedEnd] = h.sortedIdx[i]; + } + validSortedEnd++; + } + } + // ============================= + + uint32_t shift = _comPerfect.q - h.com.q; + int32_t shiftReminder = _comPerfect.r - h.com.r; + if (shiftReminder > halfN){ + shift++; + } + else if (shiftReminder < -halfN) { + shift--; + } + + for (int32_t i =NSigned-1; i >=0; i--) { + if (h.state[idx] > 0) { + //already evaluated + break; + } + uint32_t dataS= h.data[idx] + shift; + uint32_t error = dataS - _dataPerfect[i]; + if (error > UINT32_MAX/2) { + error = _dataPerfect[i] - dataS; + } + h.errors[idx] = error; + h.state[idx] = 1; + + // add the new data point to the sorted index array + for (int32_t posSorted =0; posSorted< NSigned; posSorted++){ + if (posSorted == validSortedEnd){ + h.sortedIdx[posSorted] = idx; + validSortedEnd++; + break; + } + int32_t hIdx = h.sortedIdx[posSorted]; + if (h.errors[idx] > h.errors[hIdx]){ + if (posSorted < validSortedEnd) { + //shift all remaining indices back and make space for the new entry + size_t numBytes = sizeof(h.sortedIdx[0])*(validSortedEnd-posSorted); + memmove(&(h.sortedIdx[posSorted+1]), &(h.sortedIdx[posSorted]), numBytes); + } + h.sortedIdx[posSorted] = idx; + validSortedEnd++; + break; + } + } + idx=decrement(idx); + } + + if(h.errors[h.sortedIdx[0]] == 0){ + //the data seems to be perfect and we can just return the last sample + int32_t idx=decrement(h.currentWriteIndex); + return double(h.data[idx]+h.comOffset); + } + + //Now we compute the center of mass for a second time. + //It's more efficient to just remove the contributions of the outliers from the center of masses + QR com=h.com; + QR comPerfect =_comPerfect; + + for (uint32_t i =0; i < O; i++){ + int32_t outlierIdx = h.sortedIdx[i]; + subtract(com, h.dataQR[outlierIdx], com); + + int32_t idxPerfect=outlierIdx - h.currentWriteIndex; + if (idxPerfect < 0){ + idxPerfect += NSigned; + } + subtract(comPerfect, _dataPerfectQR[idxPerfect], comPerfect); + } + + constexpr double NInv = 1./double(N); + constexpr double f = double(N)/double(N-O); + + double shiftToCom = (com.q > comPerfect.q ? + double(com.q - comPerfect.q) : -1.*(comPerfect.q-com.q)) + +double(com.r-comPerfect.r)*NInv; + + double correctedSample = shiftToCom*f + _dataPerfect[N-1] + h.comOffset; + + constexpr double pow2_32= double(UINT32_MAX)+1.; + if (correctedSample > UINT32_MAX){ + correctedSample -= pow2_32; + } + else if(correctedSample < 0) { + correctedSample += pow2_32; + } + return correctedSample; + } + + template + double getLastCall() { + return getLastCall(_history); + } + private: + + History _history; + uint32_t _boundaryFraction; + uint32_t _boundary; + + std::array _dataPerfect; + std::array _dataPerfectQR; + QR _comPerfect; +}; +#endif +#endif \ No newline at end of file diff --git a/scripts/backup_teensy_audio.ps1 b/scripts/backup_teensy_audio.ps1 new file mode 100644 index 0000000..fd7ede0 --- /dev/null +++ b/scripts/backup_teensy_audio.ps1 @@ -0,0 +1,49 @@ +param( + [string]$teensyDir = "$env:LOCALAPPDATA\Arduino15\packages\teensy\hardware\avr\1.62.0", + [string]$backupDir = "$PSScriptRoot\..\backups\1.62.0" +) + +$ErrorActionPreference = "Stop" + +$coreDir = "$teensyDir\cores\teensy4" +$configDir = $teensyDir +$backupCoreDir = "$backupDir\cores\teensy4" +$backupConfigDir = $backupDir + +$files = @( + "$coreDir\usb_audio.h", + "$coreDir\usb_audio.cpp", + "$coreDir\usb_desc.h", + "$coreDir\usb_desc.c", + "$coreDir\usb.c", + "$configDir\platform.txt" +) + +Write-Host "Backing up Teensy audio library files to: $backupDir" + +$missing = @() +foreach ($f in $files) { + if (-not (Test-Path -LiteralPath $f)) { + $missing += $f + } +} +if ($missing.Count -gt 0) { + Write-Warning "The following files do not exist (will be skipped):" + foreach ($m in $missing) { Write-Warning " $m" } +} + +New-Item -ItemType Directory -Force -Path $backupCoreDir | Out-Null +New-Item -ItemType Directory -Force -Path $backupConfigDir | Out-Null + +foreach ($f in $files) { + if (Test-Path -LiteralPath $f) { + $rel = $f.Substring($teensyDir.Length).TrimStart('\') + $dest = "$backupDir\$rel" + $destDir = Split-Path -Parent $dest + New-Item -ItemType Directory -Force -Path $destDir | Out-Null + Copy-Item -LiteralPath $f -Destination $dest -Force + Write-Host " Saved: $rel" + } +} +Write-Host "Backup complete." +exit 0 diff --git a/scripts/restore_teensy_audio.ps1 b/scripts/restore_teensy_audio.ps1 new file mode 100644 index 0000000..543e2aa --- /dev/null +++ b/scripts/restore_teensy_audio.ps1 @@ -0,0 +1,38 @@ +param( + [string]$teensyDir = "$env:LOCALAPPDATA\Arduino15\packages\teensy\hardware\avr\1.62.0", + [string]$backupDir = "$PSScriptRoot\..\backups\1.62.0" +) + +$ErrorActionPreference = "Stop" + +if (-not (Test-Path -LiteralPath $backupDir)) { + Write-Error "Backup not found at: $backupDir. Run backup_teensy_audio.ps1 first." + exit 1 +} + +Write-Host "Restoring Teensy audio files from: $backupDir" + +$backupFiles = Get-ChildItem -LiteralPath $backupDir -File -Recurse | Where-Object { + $_.FullName -notmatch '\.bak\\' +} +if ($backupFiles.Count -eq 0) { + Write-Warning "No backup files found." + exit 0 +} + +foreach ($f in $backupFiles) { + $rel = $f.FullName.Substring($backupDir.Length).TrimStart('\') + $dest = "$teensyDir\$rel" + $destDir = Split-Path -Parent $dest + New-Item -ItemType Directory -Force -Path $destDir | Out-Null + Copy-Item -LiteralPath $f.FullName -Destination $dest -Force + Write-Host " Restored: $rel" +} + +$cacheDir = "$env:APPDATA\arduino-ide" +if (Test-Path -LiteralPath $cacheDir) { + Write-Host "`nClearing Arduino IDE 2.x cache..." + Remove-Item -LiteralPath "$cacheDir\*" -Recurse -Force -ErrorAction SilentlyContinue +} + +Write-Host "Restore complete." diff --git a/scripts/setup.ps1 b/scripts/setup.ps1 new file mode 100644 index 0000000..782c73a --- /dev/null +++ b/scripts/setup.ps1 @@ -0,0 +1,127 @@ +<# +.SYNOPSIS + First-time setup for OpenAudio multi-channel USB audio on Teensy. +.DESCRIPTION + 1. Detects Teensyduino installation + 2. Backs up original Teensy core USB audio files + 3. Applies patched core files for multi-channel USB audio + 4. Installs library in Arduino IDE's sketchbook folder + 5. Ensures library.properties exists (required by Arduino IDE 2.x) + 6. Validates the setup +#> + +$ErrorActionPreference = "Stop" +$repoDir = Resolve-Path "$PSScriptRoot\.." + +Write-Host "OpenAudio_ArduinoLibrary Setup" +Write-Host "================================" +Write-Host "" + +# --- Step 0: Ensure library.properties exists --- +$propFile = "$repoDir\library.properties" +if (-not (Test-Path -LiteralPath $propFile)) { + Write-Host "Step 0: Creating library.properties..." + @" +name=OpenAudio_ArduinoLibrary +version=1.0.0 +author=Chip Audette, Bob Larkin, and contributors +maintainer=Bob Larkin +sentence=OpenAudio F32 Arduino Library - Floating-point audio processing for Teensy. +paragraph=Extends the Teensy Audio Library with float32 audio blocks, including multi-channel USB Audio Class 2.0 with asynchronous feedback. +category=Signal Input/Output +url=https://github.com/chipaudette/OpenAudio_ArduinoLibrary +architectures=teensy +includes=OpenAudio_ArduinoLibrary.h +"@ | Out-File -FilePath $propFile -Encoding utf8 + Write-Host " Created: $propFile" +} else { + Write-Host "Step 0: library.properties found." +} + +# --- Detect Teensy installation --- +$possiblePaths = @( + "$env:LOCALAPPDATA\Arduino15\packages\teensy\hardware\avr\1.62.0", + "$env:LOCALAPPDATA\Arduino15\packages\teensy\hardware\avr\1.61.0", + "$env:LOCALAPPDATA\Arduino15\packages\teensy\hardware\avr\1.60.0", + "$env:LOCALAPPDATA\Arduino15\packages\teensy\hardware\avr\1.59.0", + "$env:LOCALAPPDATA\Arduino15\packages\teensy\hardware\avr\1.58.0", + "$env:LOCALAPPDATA\Arduino15\packages\teensy\hardware\avr\1.57.0", + "$env:LOCALAPPDATA\Arduino15\packages\teensy\hardware\avr\1.56.0", + "$env:LOCALAPPDATA\Arduino15\packages\teensy\hardware\avr\1.55.0", + "$env:LOCALAPPDATA\Arduino15\packages\teensy\hardware\avr\1.54.0", + "$env:LOCALAPPDATA\Arduino15\packages\teensy\hardware\avr\1.53.0", + "$env:LOCALAPPDATA\Arduino15\packages\teensy\hardware\avr\1.52.0", + "$env:LOCALAPPDATA\Arduino15\packages\teensy\hardware\avr\1.51.0", + "$env:LOCALAPPDATA\Arduino15\packages\teensy\hardware\avr\1.50.0" +) +$teensyDir = $null +foreach ($p in $possiblePaths) { + if (Test-Path -LiteralPath "$p\cores\teensy4\usb_desc.h") { + $teensyDir = $p + break + } +} +if (-not $teensyDir) { + Write-Error "Teensyduino installation not found. Checked:`n$($possiblePaths -join "`n")" + exit 1 +} +$version = Split-Path -Leaf $teensyDir +Write-Host "Teensyduino detected: $version" + +# --- Step 1: Backup --- +$backupDir = "$PSScriptRoot\..\backups\$version" +$backupStamp = "$backupDir\cores\teensy4\usb_desc.h" +if (Test-Path -LiteralPath $backupStamp) { + Write-Host "`nStep 1: Backup already exists at $backupDir (skipping)." +} else { + Write-Host "`nStep 1: Backing up original Teensy core files..." + & "$PSScriptRoot\backup_teensy_audio.ps1" -teensyDir $teensyDir -backupDir $backupDir + if ($LASTEXITCODE -ne 0) { Write-Error "Backup failed."; exit 1 } +} + +# --- Step 2: Patch core --- +Write-Host "`nStep 2: Applying patched Teensy core files..." +& "$PSScriptRoot\update_teensy_audio.ps1" -teensyDir $teensyDir +if ($LASTEXITCODE -ne 0) { Write-Error "Update failed."; exit 1 } + +# --- Step 3: Install into Arduino libraries folder --- +$arduinoLibDir = "$env:USERPROFILE\Documents\Arduino\libraries" +$arduinoLibPath = "$arduinoLibDir\OpenAudio_ArduinoLibrary" +Write-Host "`nStep 3: Installing library for Arduino IDE..." +if (-not (Test-Path -LiteralPath $arduinoLibDir)) { + New-Item -ItemType Directory -Path $arduinoLibDir | Out-Null +} +if (Test-Path -LiteralPath $arduinoLibPath) { + Write-Host " Updating existing library install..." + Remove-Item -LiteralPath $arduinoLibPath -Recurse -Force +} +Copy-Item -LiteralPath $repoDir -Destination $arduinoLibPath -Recurse -Force +Write-Host " Installed at: $arduinoLibPath" + +# --- Validate --- +Write-Host "`n--- Validation ---" +$ok = $true +$checkCore = "$teensyDir\cores\teensy4\usb_audio_interface.h" +if (Test-Path -LiteralPath $checkCore) { + Write-Host " [OK] Patched core files present" +} else { + Write-Host " [FAIL] Patched core files missing at $checkCore" + $ok = $false +} +$checkLib = "$arduinoLibPath\examples\USBAudioPassthrough_F32\USBAudioPassthrough_F32.ino" +if (Test-Path -LiteralPath $checkLib) { + Write-Host " [OK] Library examples accessible" +} else { + Write-Host " [WARN] Examples not found at $checkLib (library may not be linked)" + $ok = $false +} + +Write-Host "" +if ($ok) { + Write-Host "Setup complete! Restart Arduino IDE, then open:" + Write-Host " File > Examples > OpenAudio_ArduinoLibrary > USBAudioPassthrough_F32" + Write-Host "" + Write-Host "Backup: .\scripts\restore_teensy_audio.ps1" +} else { + Write-Host "Setup completed with warnings. See above." +} diff --git a/scripts/update_teensy_audio.ps1 b/scripts/update_teensy_audio.ps1 new file mode 100644 index 0000000..b7a6ea1 --- /dev/null +++ b/scripts/update_teensy_audio.ps1 @@ -0,0 +1,95 @@ +param( + [string]$teensyDir = "$env:LOCALAPPDATA\Arduino15\packages\teensy\hardware\avr\1.62.0", + [string]$repoRoot = "$PSScriptRoot\..", + [switch]$noCacheClear +) + +$ErrorActionPreference = "Stop" + +$coreDir = "$teensyDir\cores\teensy4" +$configDir = $teensyDir + +if (-not (Test-Path -LiteralPath $teensyDir)) { + Write-Error "Teensy installation not found at: $teensyDir" + exit 1 +} + +Write-Host "Updating Teensy audio files from: $repoRoot" + +# 1. Copy patched core files (including subdirectories like util/) +Write-Host "`nCopying core files..." +$patchDir = (Resolve-Path "$repoRoot\patched_teensy_core").Path +if (Test-Path -LiteralPath $patchDir) { + $coreFiles = Get-ChildItem -LiteralPath $patchDir -Recurse -File + $prefixLen = $patchDir.Length + 1 + foreach ($f in $coreFiles) { + $rel = $f.FullName.Substring($prefixLen) + if ($rel -notmatch "^config\\") { + $dest = Join-Path $coreDir $rel + $parent = Split-Path -Parent $dest + if (-not (Test-Path -LiteralPath $parent)) { + New-Item -ItemType Directory -Path $parent -Force | Out-Null + } + Copy-Item -LiteralPath $f.FullName -Destination $dest -Force + Write-Host " $rel" + } + } +} + +# 2. Copy config files +Write-Host "`nCopying config files..." +$configPatchDir = "$patchDir\config" +if (Test-Path -LiteralPath $configPatchDir) { + $configFiles = Get-ChildItem -LiteralPath $configPatchDir -File + $platformCopied = $false + foreach ($f in $configFiles) { + $name = $f.Name + if ($name -eq "BM-platform.txt") { + $dest = "$configDir\platform.txt" + Copy-Item -LiteralPath $f.FullName -Destination $dest -Force + Write-Host " $name -> platform.txt (Boards Manager)" + $platformCopied = $true + } elseif ($name -eq "TD-platform.txt") { + if (-not $platformCopied) { + $dest = "$configDir\platform.txt" + Copy-Item -LiteralPath $f.FullName -Destination $dest -Force + Write-Host " $name -> platform.txt (Teensyduino Installer)" + } + } else { + $dest = "$configDir\$name" + Copy-Item -LiteralPath $f.FullName -Destination $dest -Force + Write-Host " $name -> $([System.IO.Path]::GetFileName($dest))" + } + } +} + +# 3. Append menu entries to boards.txt (IDE 2.x may not read boards.local.txt) +$boardsFile = "$configDir\boards.txt" +$sentinel = "# OpenAudio menu additions" +$menuContent = Get-Content "$configPatchDir\boards.local.txt" -Raw +if ((Get-Content $boardsFile -Raw) -notmatch [regex]::Escape($sentinel)) { + Write-Host "`nAppending OpenAudio menus to boards.txt..." + Add-Content -Path $boardsFile -Value "`r`n`r`n$sentinel`r`n$menuContent" + Write-Host " Done." +} else { + Write-Host "`nOpenAudio menus already present in boards.txt." +} + +# 4. Kill stale backend processes and clear caches +if (-not $noCacheClear) { + # Kill running arduino-cli/language-server so they re-read boards.txt + Write-Host "`nStopping stale IDE backend processes..." + Get-Process -Name "arduino-cli","arduino-language-server" -ErrorAction SilentlyContinue | Stop-Process -Force + Write-Host " Done." + + # Arduino IDE 2.x cache + $ideCache = "$env:APPDATA\arduino-ide" + if (Test-Path -LiteralPath $ideCache) { + Write-Host "Clearing Arduino IDE 2.x cache: $ideCache" + Remove-Item -LiteralPath "$ideCache\*" -Recurse -Force -ErrorAction SilentlyContinue + } + Write-Host " Cache cleared." +} + +Write-Host "`nUpdate complete." +exit 0 diff --git a/utility/LastCall.h b/utility/LastCall.h new file mode 100644 index 0000000..1244e87 --- /dev/null +++ b/utility/LastCall.h @@ -0,0 +1,339 @@ + +#ifdef __cplusplus + +#ifndef lastCall_h_ +#define lastCall_h_ + +//#include //for debug prints + +#include +#include +#include +#include + +struct QR { + uint32_t q; + int32_t r; +}; + +template +static inline __attribute__((always_inline)) QR getQR(uint32_t d){ + QR qr; + qr.q = d / N; + qr.r = int32_t(d -qr.q* N); + return qr; +} + +template +static inline __attribute__((always_inline)) void subtract(const QR& q0, const QR& q1, QR& res){ + res.q = q0.q - q1.q; + res.r = q0.r - q1.r; + constexpr int32_t NSigned=int32_t(N); + if (res.r < 0) { + res.r += NSigned; + res.q--; + } +} + +template +static inline __attribute__((always_inline)) void add(const QR& q0, const QR& q1, QR& res){ + res.q = q0.q + q1.q; + res.r = q0.r + q1.r; + constexpr int32_t NSigned=int32_t(N); + if (res.r >=NSigned) { + res.r -= NSigned; + res.q++; + } +} + +//This class is used to estimate last call of a function that is called at a high frequency. +//It smoothes the time of the last call by means of history buffer that stores the time of the e.g. last 7 calls. +//Important: The class detects up to two outliers in the history and ignores them at the computation of the time of the last call +template +struct History{ + std::array data; + std::array dataQR; + std::array state; //-1...not set yet (after reset), 0...default, 1...evaluated + std::array errors; + std::array sortedIdx; + + QR com; + uint32_t comOffset; + bool valid=false; + int32_t currentWriteIndex=0; +}; +inline double toInt32Range(double x){ + constexpr double pow2_32 = double(UINT32_MAX)+1.; + constexpr double pow2_31 = 0.5*pow2_32; + if (x < -pow2_31){ + x += pow2_32; + } + if (x > pow2_31){ + x -= pow2_32; + } + return x; +} +template +static inline __attribute__((always_inline)) int32_t increment(int32_t n){ + return (n == N-1) ? 0 : (n + 1); +} +template +static inline __attribute__((always_inline)) int32_t decrement(int32_t n){ + return (n == 0) ? (N - 1) : (n - 1); +} + +template +class LastCall +{ + public: + LastCall() { + resetHistory(); + } + + void updateExpectedTimeStep(double expectedTimeStep, bool setBoundary=true) { + if (setBoundary){ + _boundaryFraction= uint32_t(UINT32_MAX- (3.*expectedTimeStep*N))/N; //3. is the safety factor + _boundary= _boundaryFraction*N; //boundary % N ==0 is handy because we then don't need to update the reminders + } + //make the perfect sequence + _comPerfect.q=0; + _comPerfect.r=0; + for (uint32_t i =0; i< N; i++){ + _dataPerfect[i] = uint32_t(i*expectedTimeStep+0.5); + _dataPerfectQR[i] = getQR(_dataPerfect[i]); + add(_comPerfect, _dataPerfectQR[i], _comPerfect); + } + } + void resetHistory() { + memset(_history.data.data(), 0, _history.data.size()*sizeof(_history.data[0])); + QR qr; + qr.q=0; + qr.r=0; + std::fill(_history.dataQR.begin(), _history.dataQR.end(), qr); + std::fill(_history.state.begin(), _history.state.end(), -1); + std::fill(_history.sortedIdx.begin(), _history.sortedIdx.end(), -1); + + _history.com.q=0; + _history.com.r=0; + _history.comOffset = 0; + _history.valid=false; + _history.currentWriteIndex=0; + } + void reset(double expectedTimeStep){ + updateExpectedTimeStep(expectedTimeStep); + resetHistory(); + } + + void addCall(uint32_t time){ + constexpr int32_t NSIGNED = int32_t(N); + + time -= _history.comOffset; + int32_t& w=_history.currentWriteIndex; + // Remember old oldest before overwriting + const uint32_t oldOldest = _history.data[w]; + + //remove the contribution of the old data point + QR& dataQr=_history.dataQR[w]; + subtract(_history.com, dataQr, _history.com); + _history.state[w] = 0; + //============================================== + dataQr = getQR(time); + _history.data[w]=time; + + if(!_history.valid){ + //we make sure that from the beginning on com is in the interval [_history.data[(w+1)%N], _history.data[w]] + //Otherwise com can be relatively small and an underrun can occur when _boundary is subtracted + _history.valid=true; + _history.com.q =time; + _history.com.r =0; + int32_t idx = w; + for (uint32_t i =1; i < N; i++){ + idx = increment(idx); + _history.dataQR[idx] = dataQr; + _history.data[idx]=time; + } + } + else { + add(_history.com, dataQr, _history.com); + } + w = increment(w); + + const uint32_t newOldest = _history.data[w]; + + // Two distinct reasons to "rebase" + const bool nearTop = (newOldest >= _boundary); + const bool wrapSplit = (oldOldest > newOldest); // rare "true wrap" indicator + + if (nearTop || wrapSplit) { + // Always rebase data by subtracting boundary (mod 2^32 rotation) + for (uint32_t i = 0; i < N; i++) { + _history.data[i] -= _boundary; + } + + // Update comOffset (external epoch correction) + _history.comOffset += _boundary; + + if (!wrapSplit) { + // Fast path: safe because all entries are >= boundary in linear sense + for (uint32_t i = 0; i < N; i++) { + _history.dataQR[i].q -= _boundaryFraction; + } + _history.com.q -= _boundary; // because boundary == N * boundaryFraction + // reminders unchanged since boundary % N == 0 + } + else { + // Slow but safe path: recompute everything from rebased data[] + _history.com.q = 0; + _history.com.r = 0; + + for (uint32_t i = 0; i < N; i++) { + const uint32_t t = _history.data[i]; + _history.dataQR[i] = getQR(t); + add(_history.com, _history.dataQR[i], _history.com); + } + + // If you cache errors via state/map, you must invalidate them here: + std::fill(_history.state.begin(), _history.state.end(), 0); + } + } + //========================== + } + + History getHistory() const { + return _history; + } + + bool isHistoryValid() const { + return _history.valid; + } + + uint32_t getLastDuration()const{ + constexpr int32_t NSIGNED = int32_t(N); + int32_t upperIdx = decrement(_history.currentWriteIndex); + int32_t lowerIdx = decrement(upperIdx); + return _history.data[upperIdx] - _history.data[lowerIdx]; + } + template + double getLastCall(History&h) const { + constexpr int32_t halfN = int32_t(N / 2); + constexpr int32_t NSigned = int32_t(N); + + int32_t idx= decrement(h.currentWriteIndex); + if (h.state[h.currentWriteIndex] ==-1){ + //h is not completely filled and the algorithm can't work properly -> we just return the latest value + return double(h.data[idx] + h.comOffset); + } + + //remove all non-evaluated data + int32_t validSortedEnd=0; + for (int32_t i =0; i < NSigned; i++){ + if (h.sortedIdx[i] != -1 && h.state[h.sortedIdx[i]] > 0) { + if (validSortedEnd < i) { + h.sortedIdx[validSortedEnd] = h.sortedIdx[i]; + } + validSortedEnd++; + } + } + // ============================= + + uint32_t shift = _comPerfect.q - h.com.q; + int32_t shiftReminder = _comPerfect.r - h.com.r; + if (shiftReminder > halfN){ + shift++; + } + else if (shiftReminder < -halfN) { + shift--; + } + + for (int32_t i =NSigned-1; i >=0; i--) { + if (h.state[idx] > 0) { + //already evaluated + break; + } + uint32_t dataS= h.data[idx] + shift; + uint32_t error = dataS - _dataPerfect[i]; + if (error > UINT32_MAX/2) { + error = _dataPerfect[i] - dataS; + } + h.errors[idx] = error; + h.state[idx] = 1; + + // add the new data point to the sorted index array + for (int32_t posSorted =0; posSorted< NSigned; posSorted++){ + if (posSorted == validSortedEnd){ + h.sortedIdx[posSorted] = idx; + validSortedEnd++; + break; + } + int32_t hIdx = h.sortedIdx[posSorted]; + if (h.errors[idx] > h.errors[hIdx]){ + if (posSorted < validSortedEnd) { + //shift all remaining indices back and make space for the new entry + size_t numBytes = sizeof(h.sortedIdx[0])*(validSortedEnd-posSorted); + memmove(&(h.sortedIdx[posSorted+1]), &(h.sortedIdx[posSorted]), numBytes); + } + h.sortedIdx[posSorted] = idx; + validSortedEnd++; + break; + } + } + idx=decrement(idx); + } + + if(h.errors[h.sortedIdx[0]] == 0){ + //the data seems to be perfect and we can just return the last sample + int32_t idx=decrement(h.currentWriteIndex); + return double(h.data[idx]+h.comOffset); + } + + //Now we compute the center of mass for a second time. + //It's more efficient to just remove the contributions of the outliers from the center of masses + QR com=h.com; + QR comPerfect =_comPerfect; + + for (uint32_t i =0; i < O; i++){ + int32_t outlierIdx = h.sortedIdx[i]; + subtract(com, h.dataQR[outlierIdx], com); + + int32_t idxPerfect=outlierIdx - h.currentWriteIndex; + if (idxPerfect < 0){ + idxPerfect += NSigned; + } + subtract(comPerfect, _dataPerfectQR[idxPerfect], comPerfect); + } + + constexpr double NInv = 1./double(N); + constexpr double f = double(N)/double(N-O); + + double shiftToCom = (com.q > comPerfect.q ? + double(com.q - comPerfect.q) : -1.*(comPerfect.q-com.q)) + +double(com.r-comPerfect.r)*NInv; + + double correctedSample = shiftToCom*f + _dataPerfect[N-1] + h.comOffset; + + constexpr double pow2_32= double(UINT32_MAX)+1.; + if (correctedSample > UINT32_MAX){ + correctedSample -= pow2_32; + } + else if(correctedSample < 0) { + correctedSample += pow2_32; + } + return correctedSample; + } + + template + double getLastCall() { + return getLastCall(_history); + } + private: + + History _history; + uint32_t _boundaryFraction; + uint32_t _boundary; + + std::array _dataPerfect; + std::array _dataPerfectQR; + QR _comPerfect; +}; +#endif +#endif \ No newline at end of file diff --git a/utility/usb_audio_defines.h b/utility/usb_audio_defines.h new file mode 100644 index 0000000..a1ba8bf --- /dev/null +++ b/utility/usb_audio_defines.h @@ -0,0 +1,76 @@ +#ifndef usb_audio_defines_h_ +#define usb_audio_defines_h_ + +#ifndef AUDIO_SUBSLOT_SIZE +#define AUDIO_SUBSLOT_SIZE 4 +#endif + +#define AUDIO_BITRESOLUTION (8 * AUDIO_SUBSLOT_SIZE) + +#define CEIL_DIV(x, y) (((x) + (y) - 1) / (y)) + +#define USB_AUDIO_CHANNEL_FL (1 << 0) +#define USB_AUDIO_CHANNEL_FR (1 << 1) +#define USB_AUDIO_CHANNEL_FC (1 << 2) +#define USB_AUDIO_CHANNEL_LFE (1 << 3) +#define USB_AUDIO_CHANNEL_RL (1 << 4) +#define USB_AUDIO_CHANNEL_RR (1 << 5) +#define USB_AUDIO_CHANNEL_FLC (1 << 6) +#define USB_AUDIO_CHANNEL_FRC (1 << 7) +#define USB_AUDIO_CHANNEL_RC (1 << 8) +#define USB_AUDIO_CHANNEL_SL (1 << 9) +#define USB_AUDIO_CHANNEL_SR (1 << 10) + +#define MS_PER_SEC 1000 +#define MAX_SAMPLES_PER_MS CEIL_DIV(AUDIO_SAMPLE_RATE_I, MS_PER_SEC) +#define RX_TX_ADD_SAMPLES 1 + +#if defined(AUDIO_USB_CHANNEL_COUNT) + #define USB_AUDIO_NO_CHANNELS_480 (((AUDIO_USB_CHANNEL_COUNT) < 2) ? 2 : ((AUDIO_USB_CHANNEL_COUNT) & ~1)) +#else + #define USB_AUDIO_NO_CHANNELS_480 8 +#endif + +#if USB_AUDIO_NO_CHANNELS_480 == 2 + #define CHANNEL_CONFIG_480 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR) +#elif USB_AUDIO_NO_CHANNELS_480 == 4 + #define CHANNEL_CONFIG_480 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR | USB_AUDIO_CHANNEL_RL | USB_AUDIO_CHANNEL_RR) +#elif USB_AUDIO_NO_CHANNELS_480 == 6 + #define CHANNEL_CONFIG_480 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR | USB_AUDIO_CHANNEL_FC | USB_AUDIO_CHANNEL_LFE | USB_AUDIO_CHANNEL_SL | USB_AUDIO_CHANNEL_SR) +#elif USB_AUDIO_NO_CHANNELS_480 == 8 + #define CHANNEL_CONFIG_480 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR | USB_AUDIO_CHANNEL_FC | USB_AUDIO_CHANNEL_LFE | USB_AUDIO_CHANNEL_RL | USB_AUDIO_CHANNEL_RR | USB_AUDIO_CHANNEL_SL | USB_AUDIO_CHANNEL_SR) +#endif + +#define AUDIO_NUM_SUBFRAMES_PER_POLLING_480 (1 << (AUDIO_POLLING_INTERVAL_480 - 1)) +#define AUDIO_RX_TX_SIZE_SAMPLES_480 (CEIL_DIV(AUDIO_SAMPLE_RATE_I, (8000 / AUDIO_NUM_SUBFRAMES_PER_POLLING_480)) + RX_TX_ADD_SAMPLES) +#define AUDIO_RX_SIZE_480 (AUDIO_RX_TX_SIZE_SAMPLES_480 * USB_AUDIO_NO_CHANNELS_480 * AUDIO_SUBSLOT_SIZE) +#define AUDIO_TX_SIZE_480 AUDIO_RX_SIZE_480 + +#if defined(AUDIO_USB_CHANNEL_COUNT) + #define USB_AUDIO_NO_CHANNELS_12 (((AUDIO_USB_CHANNEL_COUNT) < 2) ? 2 : ((AUDIO_USB_CHANNEL_COUNT) & ~1)) +#else + #define USB_AUDIO_NO_CHANNELS_12 2 +#endif + +#if USB_AUDIO_NO_CHANNELS_12 == 2 + #define CHANNEL_CONFIG_12 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR) +#elif USB_AUDIO_NO_CHANNELS_12 == 4 + #define CHANNEL_CONFIG_12 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR | USB_AUDIO_CHANNEL_RL | USB_AUDIO_CHANNEL_RR) +#elif USB_AUDIO_NO_CHANNELS_12 == 6 + #define CHANNEL_CONFIG_12 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR | USB_AUDIO_CHANNEL_FC | USB_AUDIO_CHANNEL_LFE | USB_AUDIO_CHANNEL_SL | USB_AUDIO_CHANNEL_SR) +#elif USB_AUDIO_NO_CHANNELS_12 == 8 + #define CHANNEL_CONFIG_12 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR | USB_AUDIO_CHANNEL_FC | USB_AUDIO_CHANNEL_LFE | USB_AUDIO_CHANNEL_RL | USB_AUDIO_CHANNEL_RR | USB_AUDIO_CHANNEL_SL | USB_AUDIO_CHANNEL_SR) +#endif + +#define AUDIO_NUM_SUBFRAMES_PER_POLLING_12 8 +#define AUDIO_RX_TX_SIZE_SAMPLES_12 (MAX_SAMPLES_PER_MS + RX_TX_ADD_SAMPLES) +#define AUDIO_RX_SIZE_12 (AUDIO_RX_TX_SIZE_SAMPLES_12 * USB_AUDIO_NO_CHANNELS_12 * AUDIO_SUBSLOT_SIZE) +#define AUDIO_TX_SIZE_12 AUDIO_RX_SIZE_12 + +#if (USB_AUDIO_NO_CHANNELS_12) < (USB_AUDIO_NO_CHANNELS_480) + #define USB_AUDIO_MAX_NO_CHANNELS USB_AUDIO_NO_CHANNELS_480 +#else + #define USB_AUDIO_MAX_NO_CHANNELS USB_AUDIO_NO_CHANNELS_12 +#endif + +#endif From 4920cfafe854b78b24cde22433efb4a9b5351ca2 Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Fri, 31 Jul 2026 16:49:16 -0700 Subject: [PATCH 02/11] Update USB bit depths, add 192kHz, rework USB input/output class initialization --- docs/MULTICHANNEL_USB_AUDIO.md | 42 ++++++++++++-- .../USBAudioMultiChannel_F32.ino | 31 +++++----- input_usb_f32.cpp | 26 +++++++++ input_usb_f32.h | 11 +--- keywords.txt | 6 -- output_usb_f32.cpp | 56 +++++++++++-------- output_usb_f32.h | 14 +---- patched_teensy_core/config/BM-platform.txt | 7 ++- patched_teensy_core/config/TD-platform.txt | 7 ++- patched_teensy_core/config/boards.local.txt | 36 ++++++++++++ patched_teensy_core/usb_audio_interface.cpp | 10 +++- patched_teensy_core/usb_desc.h | 29 +++++++++- scripts/update_teensy_audio.ps1 | 18 ++++-- 13 files changed, 207 insertions(+), 86 deletions(-) diff --git a/docs/MULTICHANNEL_USB_AUDIO.md b/docs/MULTICHANNEL_USB_AUDIO.md index fccc4fc..94480de 100644 --- a/docs/MULTICHANNEL_USB_AUDIO.md +++ b/docs/MULTICHANNEL_USB_AUDIO.md @@ -4,9 +4,9 @@ This extension adds multi-channel USB Audio Class 2.0 support with asynchronous ## Features -- **Multi-channel**: Up to 8 channels, configurable via `USB_AUDIO_CHANNELS` in Boards menu -- **Multiple sample rates**: 44.1, 48, or 96kHz operation -- **Multiple bit depths**: 16, 24, or 32 bit integer or 32 bit float formats +- **Multi-channel**: Up to 16 channels (2/4/6/8/10/12/14/16), configurable via `USB Audio Channels` in the Tools menu +- **Multiple sample rates**: 44.1, 48, 96, or 192kHz operation +- **Multiple bit depths**: 16, 24, or 32 bit integer formats - **Asynchronous feedback**: USB audio output (Teensy → Host) uses an isochronous feedback endpoint so the host adapts its sample rate to the Teensy's actual clock, preventing buffer under/overruns. - **PI controller**: A proportional-integral controller drives the feedback value, using a ring-buffer-based fill-rate estimator (`LastCall`) for stable rate tracking. - **AudioClass 2.0**: Complies with the USB Audio 2.0 specification for wide host compatibility. @@ -81,9 +81,10 @@ If you installed `boards.local.txt`, under the **Tools** menu you will also see #include #include "USB_Audio_F32.h" -// Use channel-count-specific classes matching Tools > USB Channels -AudioInputUSBOct_F32 usb_in; -AudioOutputUSBOct_F32 usb_out; +// Both classes automatically expose the channel count configured in Tools > USB channels. +// Connect one AudioConnection_F32 per channel. +AudioInputUSB_F32 usb_in; +AudioOutputUSB_F32 usb_out; // One connection per channel AudioConnection_F32 patch0(usb_in, 0, usb_out, 0); @@ -101,15 +102,44 @@ void loop() { } ``` +Both classes automatically use the channel count configured in **Tools → USB channels** (the port arrays are sized at compile time). `AudioInputUSB_F32` transmits every received channel; `AudioOutputUSB_F32` accepts the same count of input ports. The compile-time constant `AudioInputUSB_F32::getNumChannels()` (or `AudioOutputUSB_F32::getNumChannels()`) returns that count, so sketches can auto-create loopback connections in a loop. See `USBAudioMultiChannel_F32` for a full 16-channel passthrough. + ### Asynchronous vs Adaptive By default, the output endpoint (Teensy → Host) runs in asynchronous mode. The Teensy sends a feedback value to the host, which adjusts its transmission rate accordingly. This eliminates the need for sample-rate conversion on the host side. To switch to adaptive mode (simpler but may cause occasional glitches), comment out the `ASYNC_TX_ENDPOINT` define in `usb_desc.h`. +## Rates, Channels and Bit Depth Limits + +USB bandwidth is finite. High speed (480 Mbit) can carry about **8192 bytes/ms**; full speed (12 Mbit) only **1023 bytes/ms**. The required bandwidth is roughly `sample_rate × channels × bytes_per_sample / 1000`. + +Maximum bit depth that streams over **high speed**, per sample rate and channel count: + +| Channels | 44.1kHz | 48kHz | 96kHz | 192kHz | +|---|:---:|:---:|:---:|:---:| +| 2 | 32-bit | 32-bit | 32-bit | 32-bit | +| 4 | 32-bit | 32-bit | 32-bit | 32-bit | +| 6 | 32-bit | 32-bit | 32-bit | 32-bit | +| 8 | 32-bit | 32-bit | 32-bit | 32-bit | +| 10 | 32-bit | 32-bit | 32-bit | 32-bit | +| 12 | 32-bit | 32-bit | 32-bit | 24-bit | +| 14 | 32-bit | 32-bit | 32-bit | 16-bit | +| 16 | 32-bit | 32-bit | 32-bit | 16-bit | + +- If a combination exceeds the high-speed bandwidth, compilation fails with a missing `AUDIO_POLLING_INTERVAL_480` error — reduce channels or bit depth. +- On full speed, channels are automatically reduced to whatever fits (e.g. 16ch/48k/24-bit streams at 6ch over full speed). Use a high-speed link for the full channel count. +- At 176.4/192kHz with 24- or 32-bit samples, even 2 channels exceed full-speed bandwidth, so the full-speed descriptor falls back to 2 channels and streaming is only possible over a high-speed connection. +- When using more than 8 channels, the USB feature unit (volume/mute controls) is limited to the first 8 channels. + ## Troubleshooting +### New menu options not appearing after setup +- After running the setup script, the Arduino IDE must fully load and finish "reloading boards" before new **Tools** menu options (e.g. **USB bit depth**) appear. +- On some systems the IDE takes **30+ seconds** to start up and reload boards. Let it finish loading — a notification such as "Reloading boards" will pop up in the corner — and only then check the **Tools** menu. +- If options still don't appear, close the IDE completely, re-run `.\scripts\update_teensy_audio.ps1` (which now replaces the menu block in `boards.txt` on every run), then restart the IDE and wait for it to fully load. + ### Compilation errors about `USB_AUDIO_CHANNELS` - The `boards.local.txt` may not be installed; the default falls back to 8 channels in `usb_desc.h` - Check `usb_desc.h` for `USB_AUDIO_NO_CHANNELS_480` define diff --git a/examples/USBAudioMultiChannel_F32/USBAudioMultiChannel_F32.ino b/examples/USBAudioMultiChannel_F32/USBAudioMultiChannel_F32.ino index f587416..00ef650 100644 --- a/examples/USBAudioMultiChannel_F32/USBAudioMultiChannel_F32.ino +++ b/examples/USBAudioMultiChannel_F32/USBAudioMultiChannel_F32.ino @@ -2,29 +2,34 @@ #include #include "USB_Audio_F32.h" -AudioInputUSBOct_F32 usb_in; -AudioOutputUSBOct_F32 usb_out; +// Set audio parameters using Tools menu +// USB type must be Audio or Serial + MIDI + Audio +// Number of channels +// Sample rate +// Bit depth -AudioConnection_F32 patch0(usb_in, 0, usb_out, 0); -AudioConnection_F32 patch1(usb_in, 1, usb_out, 1); -AudioConnection_F32 patch2(usb_in, 2, usb_out, 2); -AudioConnection_F32 patch3(usb_in, 3, usb_out, 3); -AudioConnection_F32 patch4(usb_in, 4, usb_out, 4); -AudioConnection_F32 patch5(usb_in, 5, usb_out, 5); -AudioConnection_F32 patch6(usb_in, 6, usb_out, 6); -AudioConnection_F32 patch7(usb_in, 7, usb_out, 7); +// Both classes automatically expose the Tools > USB channels count. +AudioInputUSB_F32 usb_in; +AudioOutputUSB_F32 usb_out; + +// USB loopback connections are created automatically in setup(), +// one per configured channel (getNumChannels() is a compile-time constant). +constexpr int USB_CHANNELS = AudioInputUSB_F32::getNumChannels(); +AudioConnection_F32 *usb_patches[USB_CHANNELS]; // USB->USB requires an audio output ISR to drive the scheduler. #include "output_i2s_f32.h" AudioOutputI2S_F32 i2s_out; -AudioConnection_F32 patch8(usb_in, 0, i2s_out, 0); -AudioConnection_F32 patch9(usb_in, 1, i2s_out, 1); +AudioConnection_F32 s0(usb_in, 0, i2s_out, 0); +AudioConnection_F32 s1(usb_in, 1, i2s_out, 1); void setup() { + for (int i = 0; i < USB_CHANNELS; i++) { + usb_patches[i] = new AudioConnection_F32(usb_in, i, usb_out, i); + } AudioMemory(100); AudioMemory_F32(100); } void loop() { - } diff --git a/input_usb_f32.cpp b/input_usb_f32.cpp index 223c431..6e3712c 100644 --- a/input_usb_f32.cpp +++ b/input_usb_f32.cpp @@ -76,6 +76,7 @@ USBAudioInInterface::Status AudioInputUSB_F32::getStatus() const return _usbInterface.getStatus(); } +#if AUDIO_SUBSLOT_SIZE == 2 void AudioInputUSB_F32::copy_to_buffers(const uint8_t *src, uint16_t bIdx, uint16_t noChannels, unsigned int count, unsigned int len) { const int16_t *src16 = (const int16_t *)src; @@ -85,6 +86,31 @@ void AudioInputUSB_F32::copy_to_buffers(const uint8_t *src, uint16_t bIdx, uint1 } } } +#elif AUDIO_SUBSLOT_SIZE == 3 +void AudioInputUSB_F32::copy_to_buffers(const uint8_t *src, uint16_t bIdx, uint16_t noChannels, unsigned int count, unsigned int len) +{ + for (uint32_t i = 0; i < len; i++) { + for (uint16_t j = 0; j < noChannels; j++) { + uint32_t val = src[0] | ((uint32_t)src[1] << 8) | ((uint32_t)src[2] << 16); + if (val & 0x800000) val |= 0xFF000000; + rxBuffer_f32[bIdx][j]->data[count + i] = (int32_t)val / 8388608.0f; + src += 3; + } + } +} +#elif AUDIO_SUBSLOT_SIZE == 4 +void AudioInputUSB_F32::copy_to_buffers(const uint8_t *src, uint16_t bIdx, uint16_t noChannels, unsigned int count, unsigned int len) +{ + const int32_t *src32 = (const int32_t *)src; + for (uint32_t i = 0; i < len; i++) { + for (uint16_t j = 0; j < noChannels; j++) { + rxBuffer_f32[bIdx][j]->data[count + i] = *src32++ / 2147483648.0f; + } + } +} +#else +#error "Unsupported AUDIO_SUBSLOT_SIZE (supported: 2, 3, 4)" +#endif bool AudioInputUSB_F32::setBlockQuite(uint16_t bIdx, uint16_t channel) { diff --git a/input_usb_f32.h b/input_usb_f32.h index dc50228..ef8d664 100644 --- a/input_usb_f32.h +++ b/input_usb_f32.h @@ -14,6 +14,7 @@ class AudioInputUSB_F32 : public AudioStream_F32 AudioInputUSB_F32(const AudioSettings_F32 &settings, float kp = 400.f, float ki = 0.2f); virtual void update(void); void begin(void); + static constexpr int getNumChannels() { return USB_AUDIO_MAX_NO_CHANNELS; } float getBufferedSamples() const; float getBufferedSamplesSmooth() const; float getRequestedSamplingFrequ() const; @@ -31,15 +32,5 @@ class AudioInputUSB_F32 : public AudioStream_F32 USBAudioInInterface _usbInterface; }; -#if USB_AUDIO_NO_CHANNELS_480 >= 4 -class AudioInputUSBQuad_F32 : public AudioInputUSB_F32 { public: AudioInputUSBQuad_F32(float kp =400.f,float ki =.2f) : AudioInputUSB_F32(kp, ki) {} }; -#if USB_AUDIO_NO_CHANNELS_480 >= 6 -class AudioInputUSBHex_F32 : public AudioInputUSB_F32 { public: AudioInputUSBHex_F32(float kp =400.f,float ki =.2f) : AudioInputUSB_F32(kp, ki) {} }; -#if USB_AUDIO_NO_CHANNELS_480 >= 8 -class AudioInputUSBOct_F32 : public AudioInputUSB_F32 { public: AudioInputUSBOct_F32(float kp =400.f,float ki =.2f) : AudioInputUSB_F32(kp, ki) {} }; -#endif -#endif -#endif - #endif // AUDIO_INTERFACE #endif // input_usb_f32_h_ diff --git a/keywords.txt b/keywords.txt index 01aaf94..ac778f8 100644 --- a/keywords.txt +++ b/keywords.txt @@ -522,9 +522,3 @@ getStatus KEYWORD2 AudioOutputUSB_F32 KEYWORD1 getStatus KEYWORD2 -AudioInputUSBQuad_F32 KEYWORD1 -AudioInputUSBHex_F32 KEYWORD1 -AudioInputUSBOct_F32 KEYWORD1 -AudioOutputUSBQuad_F32 KEYWORD1 -AudioOutputUSBHex_F32 KEYWORD1 -AudioOutputUSBOct_F32 KEYWORD1 diff --git a/output_usb_f32.cpp b/output_usb_f32.cpp index 199ac17..ddccae9 100644 --- a/output_usb_f32.cpp +++ b/output_usb_f32.cpp @@ -10,8 +10,7 @@ audio_block_f32_t *AudioOutputUSB_F32::txBuffer_f32[USBAudioOutInterface::ringTx AudioOutputUSB_F32::AudioOutputUSB_F32(void) : AudioStream_F32(USB_AUDIO_MAX_NO_CHANNELS, inputQueueArray_f32), - _usbInterface(releaseBlocks, isBlockReady, copy_from_buffers), - numChannels(USB_AUDIO_MAX_NO_CHANNELS) + _usbInterface(releaseBlocks, isBlockReady, copy_from_buffers) { begin(); _usbInterface.begin(); @@ -19,26 +18,7 @@ AudioOutputUSB_F32::AudioOutputUSB_F32(void) AudioOutputUSB_F32::AudioOutputUSB_F32(const AudioSettings_F32 &settings) : AudioStream_F32(USB_AUDIO_MAX_NO_CHANNELS, inputQueueArray_f32), - _usbInterface(releaseBlocks, isBlockReady, copy_from_buffers), - numChannels(USB_AUDIO_MAX_NO_CHANNELS) -{ - begin(); - _usbInterface.begin(); -} - -AudioOutputUSB_F32::AudioOutputUSB_F32(int nch) - : AudioStream_F32(nch, inputQueueArray_f32), - _usbInterface(releaseBlocks, isBlockReady, copy_from_buffers), - numChannels(nch) -{ - begin(); - _usbInterface.begin(); -} - -AudioOutputUSB_F32::AudioOutputUSB_F32(const AudioSettings_F32 &settings, int nch) - : AudioStream_F32(nch, inputQueueArray_f32), - _usbInterface(releaseBlocks, isBlockReady, copy_from_buffers), - numChannels(nch) + _usbInterface(releaseBlocks, isBlockReady, copy_from_buffers) { begin(); _usbInterface.begin(); @@ -108,6 +88,7 @@ USBAudioOutInterface::Status AudioOutputUSB_F32::getStatus() const return _usbInterface.getStatus(); } +#if AUDIO_SUBSLOT_SIZE == 2 void AudioOutputUSB_F32::copy_from_buffers(uint8_t *dst, uint16_t bIdx, uint16_t noChannels, unsigned int count, unsigned int len) { int16_t *dst16 = (int16_t *)dst; @@ -120,6 +101,37 @@ void AudioOutputUSB_F32::copy_from_buffers(uint8_t *dst, uint16_t bIdx, uint16_t } } } +#elif AUDIO_SUBSLOT_SIZE == 3 +void AudioOutputUSB_F32::copy_from_buffers(uint8_t *dst, uint16_t bIdx, uint16_t noChannels, unsigned int count, unsigned int len) +{ + for (uint32_t i = 0; i < len; i++) { + for (uint16_t j = 0; j < noChannels; j++) { + float sample = txBuffer_f32[bIdx][j]->data[count + i]; + if (sample > 1.0f) sample = 1.0f; + else if (sample < -1.0f) sample = -1.0f; + int32_t val = (int32_t)(sample * 8388607.0f); + *dst++ = val & 0xFF; + *dst++ = (val >> 8) & 0xFF; + *dst++ = (val >> 16) & 0xFF; + } + } +} +#elif AUDIO_SUBSLOT_SIZE == 4 +void AudioOutputUSB_F32::copy_from_buffers(uint8_t *dst, uint16_t bIdx, uint16_t noChannels, unsigned int count, unsigned int len) +{ + int32_t *dst32 = (int32_t *)dst; + for (uint32_t i = 0; i < len; i++) { + for (uint16_t j = 0; j < noChannels; j++) { + float sample = txBuffer_f32[bIdx][j]->data[count + i]; + if (sample > 1.0f) sample = 1.0f; + else if (sample < -1.0f) sample = -1.0f; + *dst32++ = (int32_t)(sample * 2147483647.0f); + } + } +} +#else +#error "Unsupported AUDIO_SUBSLOT_SIZE (supported: 2, 3, 4)" +#endif void AudioOutputUSB_F32::releaseBlocks(uint16_t bIdx, uint16_t noChannels) { diff --git a/output_usb_f32.h b/output_usb_f32.h index 8c797a9..2e1a3e4 100644 --- a/output_usb_f32.h +++ b/output_usb_f32.h @@ -12,10 +12,9 @@ class AudioOutputUSB_F32 : public AudioStream_F32 public: AudioOutputUSB_F32(void); AudioOutputUSB_F32(const AudioSettings_F32 &settings); - AudioOutputUSB_F32(int nch); - AudioOutputUSB_F32(const AudioSettings_F32 &settings, int nch); virtual void update(void); void begin(void); + static constexpr int getNumChannels() { return USB_AUDIO_MAX_NO_CHANNELS; } float getBufferedSamples() const; float getBufferedSamplesSmooth() const; float getActualBIntervalUs() const; @@ -29,18 +28,7 @@ class AudioOutputUSB_F32 : public AudioStream_F32 static audio_block_f32_t *txBuffer_f32[USBAudioOutInterface::ringTxBufferSize][USB_AUDIO_MAX_NO_CHANNELS]; audio_block_f32_t *inputQueueArray_f32[USB_AUDIO_MAX_NO_CHANNELS]; USBAudioOutInterface _usbInterface; - int numChannels; }; -#if USB_AUDIO_NO_CHANNELS_480 >= 4 -class AudioOutputUSBQuad_F32 : public AudioOutputUSB_F32 { public: AudioOutputUSBQuad_F32(void) : AudioOutputUSB_F32(4) {} }; -#if USB_AUDIO_NO_CHANNELS_480 >= 6 -class AudioOutputUSBHex_F32 : public AudioOutputUSB_F32 { public: AudioOutputUSBHex_F32(void) : AudioOutputUSB_F32(6) {} }; -#if USB_AUDIO_NO_CHANNELS_480 >= 8 -class AudioOutputUSBOct_F32 : public AudioOutputUSB_F32 { public: AudioOutputUSBOct_F32(void) : AudioOutputUSB_F32(8) {} }; -#endif -#endif -#endif - #endif // AUDIO_INTERFACE #endif // output_usb_f32_h_ diff --git a/patched_teensy_core/config/BM-platform.txt b/patched_teensy_core/config/BM-platform.txt index 2ec4f34..4cc8aad 100644 --- a/patched_teensy_core/config/BM-platform.txt +++ b/patched_teensy_core/config/BM-platform.txt @@ -15,11 +15,12 @@ compiler.path={runtime.tools.teensy-compile.path}/ teensytools.path={runtime.tools.teensy-tools.path}/ build.flags.audioblocksize=128 -build.flags.audiorate=44100.0f -build.flags.audiorateI=44100 +build.flags.audiorate=48000.0f +build.flags.audiorateI=48000 build.flags.USBchannelcount=2 +build.flags.audiosubslotsize=3 -build.flags.audio=-DAUDIO_BLOCK_SAMPLES={build.flags.audioblocksize} -DAUDIO_SAMPLE_RATE_EXACT={build.flags.audiorate} -DAUDIO_SAMPLE_RATE_I={build.flags.audiorateI} -DAUDIO_USB_CHANNEL_COUNT={build.flags.USBchannelcount} +build.flags.audio=-DAUDIO_BLOCK_SAMPLES={build.flags.audioblocksize} -DAUDIO_SAMPLE_RATE_EXACT={build.flags.audiorate} -DAUDIO_SAMPLE_RATE_I={build.flags.audiorateI} -DAUDIO_USB_CHANNEL_COUNT={build.flags.USBchannelcount} -DAUDIO_SUBSLOT_SIZE={build.flags.audiosubslotsize} ## Debugger - Experimental #debug.executable={build.path}/{build.project_name}.elf diff --git a/patched_teensy_core/config/TD-platform.txt b/patched_teensy_core/config/TD-platform.txt index 593b7a8..4a8ee46 100644 --- a/patched_teensy_core/config/TD-platform.txt +++ b/patched_teensy_core/config/TD-platform.txt @@ -15,11 +15,12 @@ teensytools.path={runtime.hardware.path}/../tools/ #teensytools.path={runtime.tools.teensy-tools.path}/ build.flags.audioblocksize=128 -build.flags.audiorate=44100.0f -build.flags.audiorateI=44100 +build.flags.audiorate=48000.0f +build.flags.audiorateI=48000 build.flags.USBchannelcount=2 +build.flags.audiosubslotsize=3 -build.flags.audio=-DAUDIO_BLOCK_SAMPLES={build.flags.audioblocksize} -DAUDIO_SAMPLE_RATE_EXACT={build.flags.audiorate} -DAUDIO_SAMPLE_RATE_I={build.flags.audiorateI} -DAUDIO_USB_CHANNEL_COUNT={build.flags.USBchannelcount} +build.flags.audio=-DAUDIO_BLOCK_SAMPLES={build.flags.audioblocksize} -DAUDIO_SAMPLE_RATE_EXACT={build.flags.audiorate} -DAUDIO_SAMPLE_RATE_I={build.flags.audiorateI} -DAUDIO_USB_CHANNEL_COUNT={build.flags.USBchannelcount} -DAUDIO_SUBSLOT_SIZE={build.flags.audiosubslotsize} ## Debugger - Experimental #debug.executable={build.path}/{build.project_name}.elf diff --git a/patched_teensy_core/config/boards.local.txt b/patched_teensy_core/config/boards.local.txt index a61212a..3f93d6c 100644 --- a/patched_teensy_core/config/boards.local.txt +++ b/patched_teensy_core/config/boards.local.txt @@ -11,6 +11,9 @@ teensy41.menu.audiorate.48.build.flags.audiorateI=48000 teensy41.menu.audiorate.96=96kHz teensy41.menu.audiorate.96.build.flags.audiorate=96000.0f teensy41.menu.audiorate.96.build.flags.audiorateI=96000 +teensy41.menu.audiorate.192=192kHz +teensy41.menu.audiorate.192.build.flags.audiorate=192000.0f +teensy41.menu.audiorate.192.build.flags.audiorateI=192000 teensy40.menu.audiorate.44=44.1kHz teensy40.menu.audiorate.44.build.flags.audiorate=44100.0f teensy40.menu.audiorate.44.build.flags.audiorateI=44100 @@ -20,6 +23,9 @@ teensy40.menu.audiorate.48.build.flags.audiorateI=48000 teensy40.menu.audiorate.96=96kHz teensy40.menu.audiorate.96.build.flags.audiorate=96000.0f teensy40.menu.audiorate.96.build.flags.audiorateI=96000 +teensy40.menu.audiorate.192=192kHz +teensy40.menu.audiorate.192.build.flags.audiorate=192000.0f +teensy40.menu.audiorate.192.build.flags.audiorateI=192000 menu.audioblocksize=Audio block size teensy41.menu.audioblocksize.normal=128 samples (normal) @@ -44,6 +50,14 @@ teensy41.menu.USBchannelcount.6=6 teensy41.menu.USBchannelcount.6.build.flags.USBchannelcount=6 teensy41.menu.USBchannelcount.8=8 teensy41.menu.USBchannelcount.8.build.flags.USBchannelcount=8 +teensy41.menu.USBchannelcount.10=10 +teensy41.menu.USBchannelcount.10.build.flags.USBchannelcount=10 +teensy41.menu.USBchannelcount.12=12 +teensy41.menu.USBchannelcount.12.build.flags.USBchannelcount=12 +teensy41.menu.USBchannelcount.14=14 +teensy41.menu.USBchannelcount.14.build.flags.USBchannelcount=14 +teensy41.menu.USBchannelcount.16=16 +teensy41.menu.USBchannelcount.16.build.flags.USBchannelcount=16 teensy40.menu.USBchannelcount.2=2 teensy40.menu.USBchannelcount.2.build.flags.USBchannelcount=2 teensy40.menu.USBchannelcount.4=4 @@ -52,3 +66,25 @@ teensy40.menu.USBchannelcount.6=6 teensy40.menu.USBchannelcount.6.build.flags.USBchannelcount=6 teensy40.menu.USBchannelcount.8=8 teensy40.menu.USBchannelcount.8.build.flags.USBchannelcount=8 +teensy40.menu.USBchannelcount.10=10 +teensy40.menu.USBchannelcount.10.build.flags.USBchannelcount=10 +teensy40.menu.USBchannelcount.12=12 +teensy40.menu.USBchannelcount.12.build.flags.USBchannelcount=12 +teensy40.menu.USBchannelcount.14=14 +teensy40.menu.USBchannelcount.14.build.flags.USBchannelcount=14 +teensy40.menu.USBchannelcount.16=16 +teensy40.menu.USBchannelcount.16.build.flags.USBchannelcount=16 + +menu.USBbitdepth=USB bit depth +teensy41.menu.USBbitdepth.16=16 bit +teensy41.menu.USBbitdepth.16.build.flags.audiosubslotsize=2 +teensy41.menu.USBbitdepth.24=24 bit +teensy41.menu.USBbitdepth.24.build.flags.audiosubslotsize=3 +teensy41.menu.USBbitdepth.32=32 bit int +teensy41.menu.USBbitdepth.32.build.flags.audiosubslotsize=4 +teensy40.menu.USBbitdepth.16=16 bit +teensy40.menu.USBbitdepth.16.build.flags.audiosubslotsize=2 +teensy40.menu.USBbitdepth.24=24 bit +teensy40.menu.USBbitdepth.24.build.flags.audiosubslotsize=3 +teensy40.menu.USBbitdepth.32=32 bit int +teensy40.menu.USBbitdepth.32.build.flags.audiosubslotsize=4 diff --git a/patched_teensy_core/usb_audio_interface.cpp b/patched_teensy_core/usb_audio_interface.cpp index 53b8ce6..c493d8e 100644 --- a/patched_teensy_core/usb_audio_interface.cpp +++ b/patched_teensy_core/usb_audio_interface.cpp @@ -43,7 +43,9 @@ volatile uint32_t tx_underrun_count = 0; namespace { //variables used by USBAudioInInterface and AudioOutputUSB ================== - #if (USB_AUDIO_NO_CHANNELS_12*AUDIO_NUM_SUBFRAMES_PER_POLLING_12) < (USB_AUDIO_NO_CHANNELS_480*AUDIO_NUM_SUBFRAMES_PER_POLLING_480) + // the static buffers below must be large enough for the polling interval of either link speed + // (with many channels or high sample rates either the high- or the full-speed buffer can be the larger one) + #if AUDIO_RX_SIZE_480 > AUDIO_RX_SIZE_12 #define AUDIO_TX_SIZE AUDIO_TX_SIZE_480 #define AUDIO_RX_SIZE AUDIO_RX_SIZE_480 #else @@ -946,13 +948,15 @@ void usb_audio_configure(void) feedback_accumulator = feedback_accumulator_default; memset(&rx_transfer, 0, sizeof(rx_transfer)); - usb_config_rx_iso(AUDIO_RX_ENDPOINT, AUDIO_RX_SIZE, 1, rx_event); + //configure the endpoints for the actually connected link speed (the buffers are sized for either speed, but the + //max packet size must match the active speed's descriptor and is limited to 1024/1023 bytes for high/full speed) + usb_config_rx_iso(AUDIO_RX_ENDPOINT, usb_high_speed ? AUDIO_RX_SIZE_480 : AUDIO_RX_SIZE_12, 1, rx_event); rx_event(NULL); memset(&sync_transfer, 0, sizeof(sync_transfer)); usb_config_tx_iso(AUDIO_SYNC_ENDPOINT, usb_audio_sync_nbytes, 1, sync_event); sync_event(NULL); memset(&tx_transfer, 0, sizeof(tx_transfer)); - usb_config_tx_iso(AUDIO_TX_ENDPOINT, AUDIO_TX_SIZE, 1, tx_event); + usb_config_tx_iso(AUDIO_TX_ENDPOINT, usb_high_speed ? AUDIO_TX_SIZE_480 : AUDIO_TX_SIZE_12, 1, tx_event); tx_event(NULL); expectedIsrIntervalCycles = audioPollingIntervalSec *F_CPU_ACTUAL; lastCallReceiveIsr.reset(expectedIsrIntervalCycles); diff --git a/patched_teensy_core/usb_desc.h b/patched_teensy_core/usb_desc.h index 9423677..e626baa 100644 --- a/patched_teensy_core/usb_desc.h +++ b/patched_teensy_core/usb_desc.h @@ -1032,6 +1032,18 @@ extern const usb_descriptor_list_t usb_descriptor_list[]; #if USB_AUDIO_NO_CHANNELS_480 == 8 #define CHANNEL_CONFIG_480 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR |USB_AUDIO_CHANNEL_FC | USB_AUDIO_CHANNEL_LFE | USB_AUDIO_CHANNEL_RL | USB_AUDIO_CHANNEL_RR | USB_AUDIO_CHANNEL_SL | USB_AUDIO_CHANNEL_SR) #endif + #if USB_AUDIO_NO_CHANNELS_480 == 10 + #define CHANNEL_CONFIG_480 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR |USB_AUDIO_CHANNEL_FC | USB_AUDIO_CHANNEL_LFE | USB_AUDIO_CHANNEL_RL | USB_AUDIO_CHANNEL_RR | USB_AUDIO_CHANNEL_SL | USB_AUDIO_CHANNEL_SR | USB_AUDIO_CHANNEL_FLC | USB_AUDIO_CHANNEL_FRC) + #endif + #if USB_AUDIO_NO_CHANNELS_480 == 12 + #define CHANNEL_CONFIG_480 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR |USB_AUDIO_CHANNEL_FC | USB_AUDIO_CHANNEL_LFE | USB_AUDIO_CHANNEL_RL | USB_AUDIO_CHANNEL_RR | USB_AUDIO_CHANNEL_SL | USB_AUDIO_CHANNEL_SR | USB_AUDIO_CHANNEL_TFLC | USB_AUDIO_CHANNEL_TFRC | USB_AUDIO_CHANNEL_TFL | USB_AUDIO_CHANNEL_TFR) + #endif + #if USB_AUDIO_NO_CHANNELS_480 == 14 + #define CHANNEL_CONFIG_480 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR |USB_AUDIO_CHANNEL_FC | USB_AUDIO_CHANNEL_LFE | USB_AUDIO_CHANNEL_RL | USB_AUDIO_CHANNEL_RR | USB_AUDIO_CHANNEL_SL | USB_AUDIO_CHANNEL_SR | USB_AUDIO_CHANNEL_TFLC | USB_AUDIO_CHANNEL_TFRC | USB_AUDIO_CHANNEL_TFL | USB_AUDIO_CHANNEL_TFC | USB_AUDIO_CHANNEL_TFR | USB_AUDIO_CHANNEL_TRL | USB_AUDIO_CHANNEL_TRR) + #endif + #if USB_AUDIO_NO_CHANNELS_480 == 16 + #define CHANNEL_CONFIG_480 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR |USB_AUDIO_CHANNEL_FC | USB_AUDIO_CHANNEL_LFE | USB_AUDIO_CHANNEL_RL | USB_AUDIO_CHANNEL_RR | USB_AUDIO_CHANNEL_SL | USB_AUDIO_CHANNEL_SR | USB_AUDIO_CHANNEL_TFLC | USB_AUDIO_CHANNEL_TFRC | USB_AUDIO_CHANNEL_TFL | USB_AUDIO_CHANNEL_TFC | USB_AUDIO_CHANNEL_TFR | USB_AUDIO_CHANNEL_TRL | USB_AUDIO_CHANNEL_TRC | USB_AUDIO_CHANNEL_TRR) + #endif #define RX_TX_BYTES_ADD_SAMPLES_480 (RX_TX_ADD_SAMPLES * USB_AUDIO_NO_CHANNELS_480 * AUDIO_SUBSLOT_SIZE) //bytes needed for the additional samples #define availBytesPerFrame_480 (1024 - RX_TX_BYTES_ADD_SAMPLES_480) //high speed: we have 1024 bytes per frame minus the space for the additional samples (e.g. if the Teensy requests more samples from the host than expexted) @@ -1079,8 +1091,11 @@ extern const usb_descriptor_list_t usb_descriptor_list[]; #undef BANDWIDTH_BYTES_PER_MSEC_12 #define USB_AUDIO_NO_CHANNELS_12 ((availBytesPerFrame_12 / ((MAX_SAMPLES_PER_MS+RX_TX_ADD_SAMPLES) * AUDIO_SUBSLOT_SIZE)) & ~1) //& ~1 to ensure that we get an even number of channels #if USB_AUDIO_NO_CHANNELS_12 < 2 - // For AUDIO_SAMPLE_RATE_I >= 176.4kHz and AUDIO_SUBSLOT_SIZE==3, USB_AUDIO_NO_CHANNELS_12 won't be defined and the program won't compile + // For AUDIO_SAMPLE_RATE_I >= 176.4kHz with AUDIO_SUBSLOT_SIZE>=3, full speed bandwidth cannot carry even 2 channels. + // Fall back to 2 channels so the descriptor still compiles; full speed streaming at such rates is impossible anyway + // (the isochronous bandwidth would exceed the 1023 bytes/ms full speed limit). High speed operation is unaffected. #undef USB_AUDIO_NO_CHANNELS_12 + #define USB_AUDIO_NO_CHANNELS_12 2 #endif #endif @@ -1096,6 +1111,18 @@ extern const usb_descriptor_list_t usb_descriptor_list[]; #if USB_AUDIO_NO_CHANNELS_12 == 8 #define CHANNEL_CONFIG_12 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR |USB_AUDIO_CHANNEL_FC | USB_AUDIO_CHANNEL_LFE | USB_AUDIO_CHANNEL_RL | USB_AUDIO_CHANNEL_RR | USB_AUDIO_CHANNEL_SL | USB_AUDIO_CHANNEL_SR) #endif + #if USB_AUDIO_NO_CHANNELS_12 == 10 + #define CHANNEL_CONFIG_12 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR |USB_AUDIO_CHANNEL_FC | USB_AUDIO_CHANNEL_LFE | USB_AUDIO_CHANNEL_RL | USB_AUDIO_CHANNEL_RR | USB_AUDIO_CHANNEL_SL | USB_AUDIO_CHANNEL_SR | USB_AUDIO_CHANNEL_FLC | USB_AUDIO_CHANNEL_FRC) + #endif + #if USB_AUDIO_NO_CHANNELS_12 == 12 + #define CHANNEL_CONFIG_12 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR |USB_AUDIO_CHANNEL_FC | USB_AUDIO_CHANNEL_LFE | USB_AUDIO_CHANNEL_RL | USB_AUDIO_CHANNEL_RR | USB_AUDIO_CHANNEL_SL | USB_AUDIO_CHANNEL_SR | USB_AUDIO_CHANNEL_TFLC | USB_AUDIO_CHANNEL_TFRC | USB_AUDIO_CHANNEL_TFL | USB_AUDIO_CHANNEL_TFR) + #endif + #if USB_AUDIO_NO_CHANNELS_12 == 14 + #define CHANNEL_CONFIG_12 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR |USB_AUDIO_CHANNEL_FC | USB_AUDIO_CHANNEL_LFE | USB_AUDIO_CHANNEL_RL | USB_AUDIO_CHANNEL_RR | USB_AUDIO_CHANNEL_SL | USB_AUDIO_CHANNEL_SR | USB_AUDIO_CHANNEL_TFLC | USB_AUDIO_CHANNEL_TFRC | USB_AUDIO_CHANNEL_TFL | USB_AUDIO_CHANNEL_TFC | USB_AUDIO_CHANNEL_TFR | USB_AUDIO_CHANNEL_TRL | USB_AUDIO_CHANNEL_TRR) + #endif + #if USB_AUDIO_NO_CHANNELS_12 == 16 + #define CHANNEL_CONFIG_12 (USB_AUDIO_CHANNEL_FL | USB_AUDIO_CHANNEL_FR |USB_AUDIO_CHANNEL_FC | USB_AUDIO_CHANNEL_LFE | USB_AUDIO_CHANNEL_RL | USB_AUDIO_CHANNEL_RR | USB_AUDIO_CHANNEL_SL | USB_AUDIO_CHANNEL_SR | USB_AUDIO_CHANNEL_TFLC | USB_AUDIO_CHANNEL_TFRC | USB_AUDIO_CHANNEL_TFL | USB_AUDIO_CHANNEL_TFC | USB_AUDIO_CHANNEL_TFR | USB_AUDIO_CHANNEL_TRL | USB_AUDIO_CHANNEL_TRC | USB_AUDIO_CHANNEL_TRR) + #endif #define AUDIO_NUM_SUBFRAMES_PER_POLLING_12 8 //must be 8 for full speed (8*125us = 1ms) #define AUDIO_POLLING_INTERVAL_12_SEC (AUDIO_NUM_SUBFRAMES_PER_POLLING_12 *125*1e-6) diff --git a/scripts/update_teensy_audio.ps1 b/scripts/update_teensy_audio.ps1 index b7a6ea1..829750a 100644 --- a/scripts/update_teensy_audio.ps1 +++ b/scripts/update_teensy_audio.ps1 @@ -63,17 +63,23 @@ if (Test-Path -LiteralPath $configPatchDir) { } } -# 3. Append menu entries to boards.txt (IDE 2.x may not read boards.local.txt) +# 3. Replace OpenAudio menu block in boards.txt (IDE 2.x may not read boards.local.txt) $boardsFile = "$configDir\boards.txt" $sentinel = "# OpenAudio menu additions" $menuContent = Get-Content "$configPatchDir\boards.local.txt" -Raw -if ((Get-Content $boardsFile -Raw) -notmatch [regex]::Escape($sentinel)) { - Write-Host "`nAppending OpenAudio menus to boards.txt..." - Add-Content -Path $boardsFile -Value "`r`n`r`n$sentinel`r`n$menuContent" - Write-Host " Done." +$boardsText = Get-Content $boardsFile -Raw +if ($boardsText -match [regex]::Escape($sentinel)) { + Write-Host "`nReplacing existing OpenAudio menus in boards.txt..." + $idx = $boardsText.IndexOf($sentinel) + $head = $boardsText.Substring(0, $idx).TrimEnd("`r", "`n") + $newText = "$head`r`n`r`n$sentinel`r`n$menuContent" + [System.IO.File]::WriteAllText($boardsFile, $newText) } else { - Write-Host "`nOpenAudio menus already present in boards.txt." + Write-Host "`nAppending OpenAudio menus to boards.txt..." + $newText = "$boardsText`r`n`r`n$sentinel`r`n$menuContent" + [System.IO.File]::WriteAllText($boardsFile, $newText) } +Write-Host " Done." # 4. Kill stale backend processes and clear caches if (-not $noCacheClear) { From 413db81fb3026dd19cfca3a815b9477664f4467c Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Sat, 1 Aug 2026 15:02:51 -0700 Subject: [PATCH 03/11] add TDM8/16 inputs/outputs and examples --- OpenAudio_ArduinoLibrary.h | 4 + docs/TDM_AUDIO.md | 64 ++++++ examples/AudioUSB2TDM/AudioUSB2TDM.ino | 47 ++++ .../USBAudioMultiChannel_F32.ino | 13 +- input_tdm16_f32.cpp | 159 +++++++++++++ input_tdm16_f32.h | 68 ++++++ input_tdm8_f32.cpp | 158 +++++++++++++ input_tdm8_f32.h | 68 ++++++ keywords.txt | 16 ++ output_tdm16_f32.cpp | 215 ++++++++++++++++++ output_tdm16_f32.h | 72 ++++++ output_tdm8_f32.cpp | 214 +++++++++++++++++ output_tdm8_f32.h | 72 ++++++ readme.md | 2 + scripts/setup.ps1 | 4 +- 15 files changed, 1165 insertions(+), 11 deletions(-) create mode 100644 docs/TDM_AUDIO.md create mode 100644 examples/AudioUSB2TDM/AudioUSB2TDM.ino create mode 100644 input_tdm16_f32.cpp create mode 100644 input_tdm16_f32.h create mode 100644 input_tdm8_f32.cpp create mode 100644 input_tdm8_f32.h create mode 100644 output_tdm16_f32.cpp create mode 100644 output_tdm16_f32.h create mode 100644 output_tdm8_f32.cpp create mode 100644 output_tdm8_f32.h diff --git a/OpenAudio_ArduinoLibrary.h b/OpenAudio_ArduinoLibrary.h index 46f27be..336eeb7 100644 --- a/OpenAudio_ArduinoLibrary.h +++ b/OpenAudio_ArduinoLibrary.h @@ -35,6 +35,10 @@ #include "output_i2s_f32.h" #include "output_i2s_quad_f32.h" #include "output_spdif3_f32.h" +#include "input_tdm8_f32.h" +#include "input_tdm16_f32.h" +#include "output_tdm8_f32.h" +#include "output_tdm16_f32.h" #include "play_queue_f32.h" #include "record_queue_f32.h" #include "synth_pinknoise_f32.h" diff --git a/docs/TDM_AUDIO.md b/docs/TDM_AUDIO.md new file mode 100644 index 0000000..43fba3c --- /dev/null +++ b/docs/TDM_AUDIO.md @@ -0,0 +1,64 @@ +# TDM8 / TDM16 Audio for Teensy 4.x + +This extension adds TDM (time-division multiplexed) audio I/O to OpenAudio_ArduinoLibrary as Float32 modules. TDM is the standard way to move many channels over a single SAI data line: each frame carries N slots, and the frame sync line (SYNC) marks the start of each frame. + +Two module sets are provided, each as separate classes (no channel-count constructor): + +| Class | Channels | Slots per frame | Bit clock | +|---|---|---|---| +| `AudioInputTDM8_F32` / `AudioOutputTDM8_F32` | 8 | 8 × 32-bit | 256 × fs | +| `AudioInputTDM16_F32` / `AudioOutputTDM16_F32` | 16 | 16 × 32-bit | 512 × fs | + +All slots are **32 bits**. Float samples in `[-1, +1]` are converted to/from `int32_t`: +- Input: `int32` slot value × `1/(2^31−1)` → float. +- Output: float, clamped to ±1, × `(2^31−1)` → `int32` slot value. + +24-bit left-justified codecs take the top 24 bits of each 32-bit slot automatically, so both 24-bit and 32-bit slot codecs work without configuration. + +## Wiring (Teensy 4.0 / 4.1, SAI1) + +| Signal | Pin | +|---|---| +| TX_DATA0 | 7 | +| RX_DATA0 | 8 | +| MCLK | 23 | +| BCLK | 21 | +| SYNC | 20 | + +The Teensy is the clock master. The codec/DSP on the other end must be configured for 8- or 16-slot TDM with 32-bit slots, clocked at 256 × fs (TDM8) or 512 × fs (TDM16). + +> **Important:** TDM uses **SAI1**, the same peripheral as the I2S modules. You cannot use TDM and I2S at the same time on a Teensy 4.0/4.1. (The Teensy Audio library's TDM2 modules use SAI2, which is only available on the Teensy 4.1.) + +## Usage + +```cpp +#include +#include + +AudioInputTDM16_F32 tdm_in; +AudioOutputTDM16_F32 tdm_out; + +constexpr int TDM_CHANNELS = AudioInputTDM16_F32::getNumChannels(); +AudioConnection_F32 *patches[TDM_CHANNELS]; + +void setup() { + for (int i = 0; i < TDM_CHANNELS; i++) { + patches[i] = new AudioConnection_F32(tdm_in, i, tdm_out, i); + } + AudioMemory(16); + AudioMemory_F32(100); +} + +void loop() { +} +``` + +- `getNumChannels()` returns 8 or 16 depending on the class, so sketches can auto-create connections in a loop. +- Both classes support `AudioSettings_F32` (sample rate / block size) via the settings constructor. +- The TDM DMA ISR drives the audio scheduler (`update_responsibility`), so a standalone TDM in→TDM out chain runs without needing an I2S output. +- The SAI DMA uses a **ping-pong (double-buffered) half-buffer scheme** (`DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR`): the ISR always fills/reads the half the DMA is not currently transmitting/receiving, so the DMA never races a half-buffer being rewritten. This is the same structure as the Teensy core `output_tdm.cpp` / `output_i2s.cpp` and the F32 `output_i2s_f32.cpp` modules, and keeps the data path stable at high bit-clock rates (e.g. 24.576 MHz BCLK at 96 kHz). +- Only one instance of each TDM class should be used; the pins and SAI1 registers are shared. Creating both a TDM8 and TDM16 object in one sketch is not supported. + +## Examples + +- `AudioUSB2TDM` — USB ⇄ TDM8 8-channel passthrough, demonstrating the TDM8 F32 modules in a USB Audio 2.0 project. See `docs/MULTICHANNEL_USB_AUDIO.md` for USB setup. diff --git a/examples/AudioUSB2TDM/AudioUSB2TDM.ino b/examples/AudioUSB2TDM/AudioUSB2TDM.ino new file mode 100644 index 0000000..a20c50a --- /dev/null +++ b/examples/AudioUSB2TDM/AudioUSB2TDM.ino @@ -0,0 +1,47 @@ +/* + * USB<->TDM8 example for Teensy USB audio and OpenAudio_ArduinoLibrary + * + * Under Tools menu, set Audio sample rate (up to 96kHz), 8 channels, and bit depth. + * Set USB type to Audio or Serial + MIDI + Audio + * + * Each TDM slot is 32 bits (8 slots x 32 bits = 256 bit clocks per frame). + * Wiring to the Teensy 4.x SAI1 TDM pins: + * TX_DATA0 = pin 7, RX_DATA0 = pin 8 + * MCLK = pin 23, BCLK = pin 21, SYNC = pin 20 + * + * The codec/DSP on the other end must be configured for 8-slot TDM with + * 32-bit slots, clocked from the Teensy (master) at 256 x fs bit clock. + * Jump TX_DATA0 to RX_DATA0 to test loopback through USB out > TDM out > TDM in > USB in + * + * Note: TDM uses SAI1, the same peripheral as I2S, so I2S and TDM cannot be + * used at the same time. + */ + +#include +#include +#include "USB_Audio_F32.h" + +// Both classes automatically expose the Tools > USB channels count. +// TDM8 uses SAI1 (pins 7/8/20/21/23) and drives the audio scheduler itself. +AudioInputUSB_F32 usb_in; +AudioOutputUSB_F32 usb_out; +AudioInputTDM8_F32 tdm_in; +AudioOutputTDM8_F32 tdm_out; + + +const int NUM_CHANNELS = AudioInputTDM8_F32::getNumChannels(); +AudioConnection_F32 *in_patch[NUM_CHANNELS]; +AudioConnection_F32 *out_patch[NUM_CHANNELS]; + +void setup() { + for (int i=0; i < NUM_CHANNELS; i++){ + in_patch[i] = new AudioConnection_F32(usb_in, i, tdm_out, i); + out_patch[i] = new AudioConnection_F32(tdm_in, i, usb_out, i); + } + + AudioMemory(100); + AudioMemory_F32(100); +} + +void loop() { +} diff --git a/examples/USBAudioMultiChannel_F32/USBAudioMultiChannel_F32.ino b/examples/USBAudioMultiChannel_F32/USBAudioMultiChannel_F32.ino index 00ef650..491fca6 100644 --- a/examples/USBAudioMultiChannel_F32/USBAudioMultiChannel_F32.ino +++ b/examples/USBAudioMultiChannel_F32/USBAudioMultiChannel_F32.ino @@ -12,21 +12,16 @@ AudioInputUSB_F32 usb_in; AudioOutputUSB_F32 usb_out; -// USB loopback connections are created automatically in setup(), -// one per configured channel (getNumChannels() is a compile-time constant). -constexpr int USB_CHANNELS = AudioInputUSB_F32::getNumChannels(); -AudioConnection_F32 *usb_patches[USB_CHANNELS]; - // USB->USB requires an audio output ISR to drive the scheduler. #include "output_i2s_f32.h" +#include "input_i2s_f32.h" AudioOutputI2S_F32 i2s_out; +AudioInputI2S_F32 i2s_in; AudioConnection_F32 s0(usb_in, 0, i2s_out, 0); -AudioConnection_F32 s1(usb_in, 1, i2s_out, 1); +AudioConnection_F32 s1(usb_in, 1, usb_out, 1); +AudioConnection_F32 s2(i2s_in, 0, usb_out, 0); void setup() { - for (int i = 0; i < USB_CHANNELS; i++) { - usb_patches[i] = new AudioConnection_F32(usb_in, i, usb_out, i); - } AudioMemory(100); AudioMemory_F32(100); } diff --git a/input_tdm16_f32.cpp b/input_tdm16_f32.cpp new file mode 100644 index 0000000..c5e1251 --- /dev/null +++ b/input_tdm16_f32.cpp @@ -0,0 +1,159 @@ +/* + * input_tdm16_f32.cpp + * + * Audio Library for Teensy 3.X + * Copyright (c) 2017, Paul Stoffregen, paul@pjrc.com + * + * Development of this audio library was funded by PJRC.COM, LLC by sales of + * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * 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, development funding 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. + */ +/* + * Extended by OpenAudio_ArduinoLibrary, 2026 + * Converted to F32. TDM16: 16 channels, one per 32-bit slot. + * Each 32-bit slot is received as an int32 and scaled to float in [-1, +1] + * (dividing by 2^31 correctly normalizes both 24-bit left-justified and + * true 32-bit slot data). + */ + +#include +#include "input_tdm16_f32.h" +#include "output_tdm16_f32.h" +#include + +#if defined(__IMXRT1062__) + +// 2x buffer for ping-pong: DMA writes one half while the ISR reads the other +DMAMEM __attribute__((aligned(32))) +static uint32_t tdm_rx_buffer[AUDIO_BLOCK_SAMPLES * 16 * 2]; // 16 channels, one 32-bit slot each +audio_block_f32_t * AudioInputTDM16_F32::block_incoming[16] = { + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL +}; +bool AudioInputTDM16_F32::update_responsibility = false; +DMAChannel AudioInputTDM16_F32::dma(false); +float AudioInputTDM16_F32::sample_rate_Hz = AUDIO_SAMPLE_RATE; +int AudioInputTDM16_F32::audio_block_samples = AUDIO_BLOCK_SAMPLES; + +#define I32_TO_F32_NORM_FACTOR (4.656612875245797e-10) //which is 1/(2^31 - 1) + +void AudioInputTDM16_F32::begin(void) +{ + dma.begin(true); // Allocate the DMA channel first + + AudioOutputTDM16_F32::config_tdm(); + + CORE_PIN8_CONFIG = 3; //1:RX_DATA0 + IOMUXC_SAI1_RX_DATA0_SELECT_INPUT = 2; + + dma.TCD->SADDR = &I2S1_RDR0; + dma.TCD->SOFF = 0; + dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2); + dma.TCD->NBYTES_MLNO = 4; + dma.TCD->SLAST = 0; + dma.TCD->DADDR = tdm_rx_buffer; + dma.TCD->DOFF = 4; + dma.TCD->CITER_ELINKNO = audio_block_samples * 16 * 2; + dma.TCD->DLASTSGA = -((int32_t)(audio_block_samples * 16 * 2 * 4)); + dma.TCD->BITER_ELINKNO = audio_block_samples * 16 * 2; + // one interrupt per half buffer = one full block per channel per interrupt. + // The ISR always reads the half the DMA is NOT currently filling, so it + // never deinterleaves a half while the DMA is writing to it. + dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; + dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI1_RX); + + update_responsibility = update_setup(); + dma.enable(); + + // Note: do NOT touch I2S1_TCSR here. Enabling TE before the output module + // attaches its TX DMA makes the empty TX FIFO underrun and latch FEF, which + // blocks the TX DMA requests when the output module enables it afterwards. + // (Matches the proven IMXRT input_i2s pattern: RX only enables RCSR.) + I2S1_RCSR = I2S_RCSR_RE | I2S_RCSR_BCE | I2S_RCSR_FRDE | I2S_RCSR_FR; + dma.attachInterrupt(isr); +} + +void AudioInputTDM16_F32::isr(void) +{ + const int32_t *src; + unsigned int ch; + uint32_t daddr; + + daddr = (uint32_t)(dma.TCD->DADDR); + dma.clearInterrupt(); + + if (daddr < (uint32_t)tdm_rx_buffer + sizeof(tdm_rx_buffer) / 2) { + // DMA is receiving to the first half, so read the second half + src = (const int32_t *)(tdm_rx_buffer + audio_block_samples * 16); + } else { + // DMA is receiving to the second half, so read the first half + src = (const int32_t *)tdm_rx_buffer; + } + + #if IMXRT_CACHE_ENABLED >= 1 + arm_dcache_delete((void *)src, sizeof(tdm_rx_buffer) / 2); + #endif + + if (block_incoming[0] != NULL) { + // deinterleave: channel ch lives in slot ch of every 16-slot frame + for (ch = 0; ch < 16; ch++) { + float *dest = block_incoming[ch]->data; + const int32_t *s = src + ch; + for (int i = 0; i < audio_block_samples; i++) { + *dest++ = (float32_t)*s * I32_TO_F32_NORM_FACTOR; + s += 16; + } + } + } + if (update_responsibility) AudioStream_F32::update_all(); +} + +void AudioInputTDM16_F32::update(void) +{ + unsigned int i, j; + audio_block_f32_t *new_block[16]; + audio_block_f32_t *out_block[16]; + + // allocate 16 new blocks. If any fails, allocate none + for (i = 0; i < 16; i++) { + new_block[i] = AudioStream_F32::allocate_f32(); + if (new_block[i] == NULL) { + for (j = 0; j < i; j++) { + AudioStream_F32::release(new_block[j]); + } + memset(new_block, 0, sizeof(new_block)); + break; + } + } + __disable_irq(); + memcpy(out_block, block_incoming, sizeof(out_block)); + memcpy(block_incoming, new_block, sizeof(block_incoming)); + __enable_irq(); + if (out_block[0] != NULL) { + // if we got 1 block, all 16 are filled + for (i = 0; i < 16; i++) { + transmit(out_block[i], i); + AudioStream_F32::release(out_block[i]); + } + } +} + +#endif diff --git a/input_tdm16_f32.h b/input_tdm16_f32.h new file mode 100644 index 0000000..c02ad15 --- /dev/null +++ b/input_tdm16_f32.h @@ -0,0 +1,68 @@ +/* + * input_tdm16_f32.h + * + * Audio Library for Teensy 3.X + * Copyright (c) 2017, Paul Stoffregen, paul@pjrc.com + * + * Development of this audio library was funded by PJRC.COM, LLC by sales of + * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * 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, development funding 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. + */ +/* + * Extended by OpenAudio_ArduinoLibrary, 2026 + * Converted to F32. TDM16: 16 channels, one per 32-bit slot. + * Uses the Teensy 4.x SAI1 TDM pins (same as AudioInputTDM). + */ + +#if defined(__IMXRT1062__) +#ifndef _input_tdm16_f32_h_ +#define _input_tdm16_f32_h_ + +#include +#include +#include "AudioStream_F32.h" +#include "DMAChannel.h" + +class AudioInputTDM16_F32 : public AudioStream_F32 +{ +//GUI: inputs:0, outputs:16 //this line used for automatic generation of GUI node +public: + AudioInputTDM16_F32(void) : AudioStream_F32(0, NULL) { begin(); } //uses default AUDIO_SAMPLE_RATE and BLOCK_SIZE_SAMPLES from AudioStream.h + AudioInputTDM16_F32(const AudioSettings_F32 &settings) : AudioStream_F32(0, NULL) { + sample_rate_Hz = settings.sample_rate_Hz; + audio_block_samples = settings.audio_block_samples; + begin(); + } + static constexpr int getNumChannels() { return 16; } + virtual void update(void); + void begin(void); +protected: + static bool update_responsibility; + static DMAChannel dma; + static void isr(void); +private: + static audio_block_f32_t *block_incoming[16]; + static float sample_rate_Hz; + static int audio_block_samples; +}; + +#endif +#endif diff --git a/input_tdm8_f32.cpp b/input_tdm8_f32.cpp new file mode 100644 index 0000000..cef3613 --- /dev/null +++ b/input_tdm8_f32.cpp @@ -0,0 +1,158 @@ +/* + * input_tdm8_f32.cpp + * + * Audio Library for Teensy 3.X + * Copyright (c) 2017, Paul Stoffregen, paul@pjrc.com + * + * Development of this audio library was funded by PJRC.COM, LLC by sales of + * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * 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, development funding 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. + */ +/* + * Extended by OpenAudio_ArduinoLibrary, 2026 + * Converted to F32. TDM8: 8 channels, one per 32-bit slot. + * Each 32-bit slot is received as an int32 and scaled to float in [-1, +1] + * (dividing by 2^31 correctly normalizes both 24-bit left-justified and + * true 32-bit slot data). + */ + +#include +#include "input_tdm8_f32.h" +#include "output_tdm8_f32.h" +#include + +#if defined(__IMXRT1062__) + +// 2x buffer for ping-pong: DMA writes one half while the ISR reads the other +DMAMEM __attribute__((aligned(32))) +static uint32_t tdm_rx_buffer[AUDIO_BLOCK_SAMPLES * 8 * 2]; // 8 channels, one 32-bit slot each +audio_block_f32_t * AudioInputTDM8_F32::block_incoming[8] = { + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL +}; +bool AudioInputTDM8_F32::update_responsibility = false; +DMAChannel AudioInputTDM8_F32::dma(false); +float AudioInputTDM8_F32::sample_rate_Hz = AUDIO_SAMPLE_RATE; +int AudioInputTDM8_F32::audio_block_samples = AUDIO_BLOCK_SAMPLES; + +#define I32_TO_F32_NORM_FACTOR (4.656612875245797e-10) //which is 1/(2^31 - 1) + +void AudioInputTDM8_F32::begin(void) +{ + dma.begin(true); // Allocate the DMA channel first + + AudioOutputTDM8_F32::config_tdm(); + + CORE_PIN8_CONFIG = 3; //1:RX_DATA0 + IOMUXC_SAI1_RX_DATA0_SELECT_INPUT = 2; + + dma.TCD->SADDR = &I2S1_RDR0; + dma.TCD->SOFF = 0; + dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2); + dma.TCD->NBYTES_MLNO = 4; + dma.TCD->SLAST = 0; + dma.TCD->DADDR = tdm_rx_buffer; + dma.TCD->DOFF = 4; + dma.TCD->CITER_ELINKNO = audio_block_samples * 8 * 2; + dma.TCD->DLASTSGA = -((int32_t)(audio_block_samples * 8 * 2 * 4)); + dma.TCD->BITER_ELINKNO = audio_block_samples * 8 * 2; + // one interrupt per half buffer = one full block per channel per interrupt. + // The ISR always reads the half the DMA is NOT currently filling, so it + // never deinterleaves a half while the DMA is writing to it. + dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; + dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI1_RX); + + update_responsibility = update_setup(); + dma.enable(); + + // Note: do NOT touch I2S1_TCSR here. Enabling TE before the output module + // attaches its TX DMA makes the empty TX FIFO underrun and latch FEF, which + // blocks the TX DMA requests when the output module enables it afterwards. + // (Matches the proven IMXRT input_i2s pattern: RX only enables RCSR.) + I2S1_RCSR = I2S_RCSR_RE | I2S_RCSR_BCE | I2S_RCSR_FRDE | I2S_RCSR_FR; + dma.attachInterrupt(isr); +} + +void AudioInputTDM8_F32::isr(void) +{ + const int32_t *src; + unsigned int ch; + uint32_t daddr; + + daddr = (uint32_t)(dma.TCD->DADDR); + dma.clearInterrupt(); + + if (daddr < (uint32_t)tdm_rx_buffer + sizeof(tdm_rx_buffer) / 2) { + // DMA is receiving to the first half, so read the second half + src = (const int32_t *)(tdm_rx_buffer + audio_block_samples * 8); + } else { + // DMA is receiving to the second half, so read the first half + src = (const int32_t *)tdm_rx_buffer; + } + + #if IMXRT_CACHE_ENABLED >= 1 + arm_dcache_delete((void *)src, sizeof(tdm_rx_buffer) / 2); + #endif + + if (block_incoming[0] != NULL) { + // deinterleave: channel ch lives in slot ch of every 8-slot frame + for (ch = 0; ch < 8; ch++) { + float *dest = block_incoming[ch]->data; + const int32_t *s = src + ch; + for (int i = 0; i < audio_block_samples; i++) { + *dest++ = (float32_t)*s * I32_TO_F32_NORM_FACTOR; + s += 8; + } + } + } + if (update_responsibility) AudioStream_F32::update_all(); +} + +void AudioInputTDM8_F32::update(void) +{ + unsigned int i, j; + audio_block_f32_t *new_block[8]; + audio_block_f32_t *out_block[8]; + + // allocate 8 new blocks. If any fails, allocate none + for (i = 0; i < 8; i++) { + new_block[i] = AudioStream_F32::allocate_f32(); + if (new_block[i] == NULL) { + for (j = 0; j < i; j++) { + AudioStream_F32::release(new_block[j]); + } + memset(new_block, 0, sizeof(new_block)); + break; + } + } + __disable_irq(); + memcpy(out_block, block_incoming, sizeof(out_block)); + memcpy(block_incoming, new_block, sizeof(block_incoming)); + __enable_irq(); + if (out_block[0] != NULL) { + // if we got 1 block, all 8 are filled + for (i = 0; i < 8; i++) { + transmit(out_block[i], i); + AudioStream_F32::release(out_block[i]); + } + } +} + +#endif diff --git a/input_tdm8_f32.h b/input_tdm8_f32.h new file mode 100644 index 0000000..042a611 --- /dev/null +++ b/input_tdm8_f32.h @@ -0,0 +1,68 @@ +/* + * input_tdm8_f32.h + * + * Audio Library for Teensy 3.X + * Copyright (c) 2017, Paul Stoffregen, paul@pjrc.com + * + * Development of this audio library was funded by PJRC.COM, LLC by sales of + * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * 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, development funding 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. + */ +/* + * Extended by OpenAudio_ArduinoLibrary, 2026 + * Converted to F32. TDM8: 8 channels, one per 32-bit slot. + * Uses the Teensy 4.x SAI1 TDM pins (same as AudioInputTDM). + */ + +#if defined(__IMXRT1062__) +#ifndef _input_tdm8_f32_h_ +#define _input_tdm8_f32_h_ + +#include +#include +#include "AudioStream_F32.h" +#include "DMAChannel.h" + +class AudioInputTDM8_F32 : public AudioStream_F32 +{ +//GUI: inputs:0, outputs:8 //this line used for automatic generation of GUI node +public: + AudioInputTDM8_F32(void) : AudioStream_F32(0, NULL) { begin(); } //uses default AUDIO_SAMPLE_RATE and BLOCK_SIZE_SAMPLES from AudioStream.h + AudioInputTDM8_F32(const AudioSettings_F32 &settings) : AudioStream_F32(0, NULL) { + sample_rate_Hz = settings.sample_rate_Hz; + audio_block_samples = settings.audio_block_samples; + begin(); + } + static constexpr int getNumChannels() { return 8; } + virtual void update(void); + void begin(void); +protected: + static bool update_responsibility; + static DMAChannel dma; + static void isr(void); +private: + static audio_block_f32_t *block_incoming[8]; + static float sample_rate_Hz; + static int audio_block_samples; +}; + +#endif +#endif diff --git a/keywords.txt b/keywords.txt index ac778f8..9cdb0c4 100644 --- a/keywords.txt +++ b/keywords.txt @@ -522,3 +522,19 @@ getStatus KEYWORD2 AudioOutputUSB_F32 KEYWORD1 getStatus KEYWORD2 +AudioInputTDM8_F32 KEYWORD1 +begin KEYWORD2 +getNumChannels KEYWORD2 + +AudioOutputTDM8_F32 KEYWORD1 +begin KEYWORD2 +getNumChannels KEYWORD2 + +AudioInputTDM16_F32 KEYWORD1 +begin KEYWORD2 +getNumChannels KEYWORD2 + +AudioOutputTDM16_F32 KEYWORD1 +begin KEYWORD2 +getNumChannels KEYWORD2 + diff --git a/output_tdm16_f32.cpp b/output_tdm16_f32.cpp new file mode 100644 index 0000000..c1db8c8 --- /dev/null +++ b/output_tdm16_f32.cpp @@ -0,0 +1,215 @@ +/* + * output_tdm16_f32.cpp + * + * Audio Library for Teensy 3.X + * Copyright (c) 2017, Paul Stoffregen, paul@pjrc.com + * + * Development of this audio library was funded by PJRC.COM, LLC by sales of + * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * 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, development funding 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. + */ +/* + * Extended by OpenAudio_ArduinoLibrary, 2026 + * Converted to F32. TDM16: 16 channels, one per 32-bit slot. + * Float samples in [-1, +1] are scaled to int32 and written into the + * 32-bit TDM slots (24-bit left-justified codecs take the top 24 bits). + */ + +#include + +#if defined(__IMXRT1062__) + +#include "output_tdm16_f32.h" + +// 2x buffer for ping-pong: DMA transmits one half while the ISR fills the other +DMAMEM __attribute__((aligned(32))) +static uint32_t tdm_tx_buffer[AUDIO_BLOCK_SAMPLES * 16 * 2]; // 16 channels, one 32-bit slot each +DMAMEM __attribute__((aligned(32))) +static float zeros_f32[AUDIO_BLOCK_SAMPLES]; +audio_block_f32_t * AudioOutputTDM16_F32::block_input[16] = { + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL +}; +bool AudioOutputTDM16_F32::update_responsibility = false; +DMAChannel AudioOutputTDM16_F32::dma(false); +float AudioOutputTDM16_F32::sample_rate_Hz = AUDIO_SAMPLE_RATE; +int AudioOutputTDM16_F32::audio_block_samples = AUDIO_BLOCK_SAMPLES; + +#define F32_TO_I32_NORM_FACTOR (2147483647.0f) //which is 2^31 - 1 + +void AudioOutputTDM16_F32::begin(void) +{ + dma.begin(true); // Allocate the DMA channel first + + for (int i = 0; i < 16; i++) { + block_input[i] = NULL; + } + memset(zeros_f32, 0, sizeof(zeros_f32)); + memset(tdm_tx_buffer, 0, sizeof(tdm_tx_buffer)); + + config_tdm(); + + CORE_PIN7_CONFIG = 3; //1:TX_DATA0 + + dma.TCD->SADDR = tdm_tx_buffer; + dma.TCD->SOFF = 4; + dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2); + dma.TCD->NBYTES_MLNO = 4; + dma.TCD->SLAST = -((int32_t)(audio_block_samples * 16 * 2 * 4)); + dma.TCD->DADDR = &I2S1_TDR0; + dma.TCD->DOFF = 0; + dma.TCD->CITER_ELINKNO = audio_block_samples * 16 * 2; + dma.TCD->DLASTSGA = 0; + dma.TCD->BITER_ELINKNO = audio_block_samples * 16 * 2; + // one interrupt per half buffer = one full block per channel per interrupt. + // The ISR always fills the half the DMA is NOT currently transmitting, so + // the DMA never reads a half while it is being rewritten (no tearing). + dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; + dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI1_TX); + + update_responsibility = update_setup(); + dma.enable(); + + I2S1_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE; + I2S1_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE; + dma.attachInterrupt(isr); +} + +void AudioOutputTDM16_F32::isr(void) +{ + uint32_t *dest; + unsigned int ch; + uint32_t saddr; + + saddr = (uint32_t)(dma.TCD->SADDR); + dma.clearInterrupt(); + + if (saddr < (uint32_t)tdm_tx_buffer + sizeof(tdm_tx_buffer) / 2) { + // DMA is transmitting the first half, so we fill the second half + dest = tdm_tx_buffer + audio_block_samples * 16; + } else { + // DMA is transmitting the second half, so we fill the first half + dest = tdm_tx_buffer; + } + + if (update_responsibility) AudioStream_F32::update_all(); + + // interleave: channel ch lives in slot ch of every 16-slot frame + for (ch = 0; ch < 16; ch++) { + audio_block_f32_t *b = block_input[ch]; + const float *src = b ? b->data : zeros_f32; + int32_t *d = (int32_t *)dest + ch; + for (int i = 0; i < audio_block_samples; i++) { + float s = *src++; + if (s > 1.0f) s = 1.0f; + else if (s < -1.0f) s = -1.0f; + *d = (int32_t)(s * F32_TO_I32_NORM_FACTOR); + d += 16; + } + } + + #if IMXRT_CACHE_ENABLED >= 2 + arm_dcache_flush_delete(dest, sizeof(tdm_tx_buffer) / 2); + #endif + + for (ch = 0; ch < 16; ch++) { + if (block_input[ch]) { + AudioStream_F32::release(block_input[ch]); + block_input[ch] = NULL; + } + } +} + +void AudioOutputTDM16_F32::update(void) +{ + audio_block_f32_t *prev[16]; + unsigned int i; + + __disable_irq(); + for (i = 0; i < 16; i++) { + prev[i] = block_input[i]; + block_input[i] = receiveReadOnly_f32(i); + } + __enable_irq(); + for (i = 0; i < 16; i++) { + if (prev[i]) AudioStream_F32::release(prev[i]); + } +} + +void AudioOutputTDM16_F32::config_tdm(void) +{ + CCM_CCGR5 |= CCM_CCGR5_SAI1(CCM_CCGR_ON); + + // if either transmitter or receiver is enabled, do nothing + if (I2S1_TCSR & I2S_TCSR_TE) return; + if (I2S1_RCSR & I2S_RCSR_RE) return; + + int fs = AUDIO_SAMPLE_RATE_EXACT; + // PLL between 27*24 = 648MHz und 54*24=1296MHz + int n1 = 4; //SAI prescaler 4 => (n1*n2) = multiple of 4 + int n2 = 1 + (24000000 * 27) / (fs * 256 * n1); + + double C = ((double)fs * 256 * n1 * n2) / 24000000; + int c0 = C; + int c2 = 10000; + int c1 = C * c2 - (c0 * c2); + set_audioClock(c0, c1, c2); + // clear SAI1_CLK register locations + CCM_CSCMR1 = (CCM_CSCMR1 & ~(CCM_CSCMR1_SAI1_CLK_SEL_MASK)) + | CCM_CSCMR1_SAI1_CLK_SEL(2); // &0x03 // (0,1,2): PLL3PFD0, PLL5, PLL4 + + n1 = 1; // TDM16: 16 slots x 32 bits = 512 bit clocks per frame (2x the TDM8 clock) + + CCM_CS1CDR = (CCM_CS1CDR & ~(CCM_CS1CDR_SAI1_CLK_PRED_MASK | CCM_CS1CDR_SAI1_CLK_PODF_MASK)) + | CCM_CS1CDR_SAI1_CLK_PRED(n1-1) // &0x07 + | CCM_CS1CDR_SAI1_CLK_PODF(n2-1); // &0x3f + + IOMUXC_GPR_GPR1 = (IOMUXC_GPR_GPR1 & ~(IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL_MASK)) + | (IOMUXC_GPR_GPR1_SAI1_MCLK_DIR | IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL(0)); //Select MCLK + + // configure transmitter + int rsync = 0; + int tsync = 1; + + I2S1_TMR = 0; + I2S1_TCR1 = I2S_TCR1_RFW(4); + I2S1_TCR2 = I2S_TCR2_SYNC(tsync) | I2S_TCR2_BCP | I2S_TCR2_MSEL(1) + | I2S_TCR2_BCD | I2S_TCR2_DIV(0); + I2S1_TCR3 = I2S_TCR3_TCE; + I2S1_TCR4 = I2S_TCR4_FRSZ(15) | I2S_TCR4_SYWD(0) | I2S_TCR4_MF + | I2S_TCR4_FSE | I2S_TCR4_FSD; + I2S1_TCR5 = I2S_TCR5_WNW(31) | I2S_TCR5_W0W(31) | I2S_TCR5_FBT(31); + + I2S1_RMR = 0; + I2S1_RCR1 = I2S_RCR1_RFW(4); + I2S1_RCR2 = I2S_RCR2_SYNC(rsync) | I2S_TCR2_BCP | I2S_RCR2_MSEL(1) + | I2S_RCR2_BCD | I2S_RCR2_DIV(0); + I2S1_RCR3 = I2S_RCR3_RCE; + I2S1_RCR4 = I2S_RCR4_FRSZ(15) | I2S_RCR4_SYWD(0) | I2S_RCR4_MF + | I2S_RCR4_FSE | I2S_RCR4_FSD; + I2S1_RCR5 = I2S_RCR5_WNW(31) | I2S_RCR5_W0W(31) | I2S_RCR5_FBT(31); + + CORE_PIN23_CONFIG = 3; //1:MCLK + CORE_PIN21_CONFIG = 3; //1:RX_BCLK + CORE_PIN20_CONFIG = 3; //1:RX_SYNC +} + +#endif diff --git a/output_tdm16_f32.h b/output_tdm16_f32.h new file mode 100644 index 0000000..d52849e --- /dev/null +++ b/output_tdm16_f32.h @@ -0,0 +1,72 @@ +/* + * output_tdm16_f32.h + * + * Audio Library for Teensy 3.X + * Copyright (c) 2017, Paul Stoffregen, paul@pjrc.com + * + * Development of this audio library was funded by PJRC.COM, LLC by sales of + * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * 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, development funding 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. + */ +/* + * Extended by OpenAudio_ArduinoLibrary, 2026 + * Converted to F32. TDM16: 16 channels, one per 32-bit slot. + * Uses the Teensy 4.x SAI1 TDM pins (same as AudioOutputTDM). + */ + +#if defined(__IMXRT1062__) +#ifndef output_tdm16_f32_h_ +#define output_tdm16_f32_h_ + +#include +#include +#include "AudioStream_F32.h" +#include "DMAChannel.h" +#include // From Teensy Audio library. For set_audioClock(). + +class AudioOutputTDM16_F32 : public AudioStream_F32 +{ +//GUI: inputs:16, outputs:0 //this line used for automatic generation of GUI node +public: + AudioOutputTDM16_F32(void) : AudioStream_F32(16, inputQueueArray_f32) { begin(); } //uses default AUDIO_SAMPLE_RATE and BLOCK_SIZE_SAMPLES from AudioStream.h + AudioOutputTDM16_F32(const AudioSettings_F32 &settings) : AudioStream_F32(16, inputQueueArray_f32) { + sample_rate_Hz = settings.sample_rate_Hz; + audio_block_samples = settings.audio_block_samples; + begin(); + } + static constexpr int getNumChannels() { return 16; } + virtual void update(void); + void begin(void); + friend class AudioInputTDM16_F32; +protected: + static void config_tdm(void); + static audio_block_f32_t *block_input[16]; + static bool update_responsibility; + static DMAChannel dma; + static void isr(void); +private: + audio_block_f32_t *inputQueueArray_f32[16]; + static float sample_rate_Hz; + static int audio_block_samples; +}; + +#endif +#endif diff --git a/output_tdm8_f32.cpp b/output_tdm8_f32.cpp new file mode 100644 index 0000000..8cc40c5 --- /dev/null +++ b/output_tdm8_f32.cpp @@ -0,0 +1,214 @@ +/* + * output_tdm8_f32.cpp + * + * Audio Library for Teensy 3.X + * Copyright (c) 2017, Paul Stoffregen, paul@pjrc.com + * + * Development of this audio library was funded by PJRC.COM, LLC by sales of + * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * 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, development funding 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. + */ +/* + * Extended by OpenAudio_ArduinoLibrary, 2026 + * Converted to F32. TDM8: 8 channels, one per 32-bit slot. + * Float samples in [-1, +1] are scaled to int32 and written into the + * 32-bit TDM slots (24-bit left-justified codecs take the top 24 bits). + */ + +#include + +#if defined(__IMXRT1062__) + +#include "output_tdm8_f32.h" + +// 2x buffer for ping-pong: DMA transmits one half while the ISR fills the other +DMAMEM __attribute__((aligned(32))) +static uint32_t tdm_tx_buffer[AUDIO_BLOCK_SAMPLES * 8 * 2]; // 8 channels, one 32-bit slot each +DMAMEM __attribute__((aligned(32))) +static float zeros_f32[AUDIO_BLOCK_SAMPLES]; +audio_block_f32_t * AudioOutputTDM8_F32::block_input[8] = { + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL +}; +bool AudioOutputTDM8_F32::update_responsibility = false; +DMAChannel AudioOutputTDM8_F32::dma(false); +float AudioOutputTDM8_F32::sample_rate_Hz = AUDIO_SAMPLE_RATE; +int AudioOutputTDM8_F32::audio_block_samples = AUDIO_BLOCK_SAMPLES; + +#define F32_TO_I32_NORM_FACTOR (2147483647.0f) //which is 2^31 - 1 + +void AudioOutputTDM8_F32::begin(void) +{ + dma.begin(true); // Allocate the DMA channel first + + for (int i = 0; i < 8; i++) { + block_input[i] = NULL; + } + memset(zeros_f32, 0, sizeof(zeros_f32)); + memset(tdm_tx_buffer, 0, sizeof(tdm_tx_buffer)); + + config_tdm(); + + CORE_PIN7_CONFIG = 3; //1:TX_DATA0 + + dma.TCD->SADDR = tdm_tx_buffer; + dma.TCD->SOFF = 4; + dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2); + dma.TCD->NBYTES_MLNO = 4; + dma.TCD->SLAST = -((int32_t)(audio_block_samples * 8 * 2 * 4)); + dma.TCD->DADDR = &I2S1_TDR0; + dma.TCD->DOFF = 0; + dma.TCD->CITER_ELINKNO = audio_block_samples * 8 * 2; + dma.TCD->DLASTSGA = 0; + dma.TCD->BITER_ELINKNO = audio_block_samples * 8 * 2; + // one interrupt per half buffer = one full block per channel per interrupt. + // The ISR always fills the half the DMA is NOT currently transmitting, so + // the DMA never reads a half while it is being rewritten (no tearing). + dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; + dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI1_TX); + + update_responsibility = update_setup(); + dma.enable(); + + I2S1_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE; + I2S1_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE; + dma.attachInterrupt(isr); +} + +void AudioOutputTDM8_F32::isr(void) +{ + uint32_t *dest; + unsigned int ch; + uint32_t saddr; + + saddr = (uint32_t)(dma.TCD->SADDR); + dma.clearInterrupt(); + + if (saddr < (uint32_t)tdm_tx_buffer + sizeof(tdm_tx_buffer) / 2) { + // DMA is transmitting the first half, so we fill the second half + dest = tdm_tx_buffer + audio_block_samples * 8; + } else { + // DMA is transmitting the second half, so we fill the first half + dest = tdm_tx_buffer; + } + + if (update_responsibility) AudioStream_F32::update_all(); + + // interleave: channel ch lives in slot ch of every 8-slot frame + for (ch = 0; ch < 8; ch++) { + audio_block_f32_t *b = block_input[ch]; + const float *src = b ? b->data : zeros_f32; + int32_t *d = (int32_t *)dest + ch; + for (int i = 0; i < audio_block_samples; i++) { + float s = *src++; + if (s > 1.0f) s = 1.0f; + else if (s < -1.0f) s = -1.0f; + *d = (int32_t)(s * F32_TO_I32_NORM_FACTOR); + d += 8; + } + } + + #if IMXRT_CACHE_ENABLED >= 2 + arm_dcache_flush_delete(dest, sizeof(tdm_tx_buffer) / 2); + #endif + + for (ch = 0; ch < 8; ch++) { + if (block_input[ch]) { + AudioStream_F32::release(block_input[ch]); + block_input[ch] = NULL; + } + } +} + +void AudioOutputTDM8_F32::update(void) +{ + audio_block_f32_t *prev[8]; + unsigned int i; + + __disable_irq(); + for (i = 0; i < 8; i++) { + prev[i] = block_input[i]; + block_input[i] = receiveReadOnly_f32(i); + } + __enable_irq(); + for (i = 0; i < 8; i++) { + if (prev[i]) AudioStream_F32::release(prev[i]); + } +} + +void AudioOutputTDM8_F32::config_tdm(void) +{ + CCM_CCGR5 |= CCM_CCGR5_SAI1(CCM_CCGR_ON); + + // if either transmitter or receiver is enabled, do nothing + if (I2S1_TCSR & I2S_TCSR_TE) return; + if (I2S1_RCSR & I2S_RCSR_RE) return; + + int fs = AUDIO_SAMPLE_RATE_EXACT; + // PLL between 27*24 = 648MHz und 54*24=1296MHz + int n1 = 4; //SAI prescaler 4 => (n1*n2) = multiple of 4 + int n2 = 1 + (24000000 * 27) / (fs * 256 * n1); + + double C = ((double)fs * 256 * n1 * n2) / 24000000; + int c0 = C; + int c2 = 10000; + int c1 = C * c2 - (c0 * c2); + set_audioClock(c0, c1, c2); + // clear SAI1_CLK register locations + CCM_CSCMR1 = (CCM_CSCMR1 & ~(CCM_CSCMR1_SAI1_CLK_SEL_MASK)) + | CCM_CSCMR1_SAI1_CLK_SEL(2); // &0x03 // (0,1,2): PLL3PFD0, PLL5, PLL4 + + n1 = 2; // TDM8: 8 slots x 32 bits = 256 bit clocks per frame (double-speed vs I2S) + + CCM_CS1CDR = (CCM_CS1CDR & ~(CCM_CS1CDR_SAI1_CLK_PRED_MASK | CCM_CS1CDR_SAI1_CLK_PODF_MASK)) + | CCM_CS1CDR_SAI1_CLK_PRED(n1-1) // &0x07 + | CCM_CS1CDR_SAI1_CLK_PODF(n2-1); // &0x3f + + IOMUXC_GPR_GPR1 = (IOMUXC_GPR_GPR1 & ~(IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL_MASK)) + | (IOMUXC_GPR_GPR1_SAI1_MCLK_DIR | IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL(0)); //Select MCLK + + // configure transmitter + int rsync = 0; + int tsync = 1; + + I2S1_TMR = 0; + I2S1_TCR1 = I2S_TCR1_RFW(4); + I2S1_TCR2 = I2S_TCR2_SYNC(tsync) | I2S_TCR2_BCP | I2S_TCR2_MSEL(1) + | I2S_TCR2_BCD | I2S_TCR2_DIV(0); + I2S1_TCR3 = I2S_TCR3_TCE; + I2S1_TCR4 = I2S_TCR4_FRSZ(7) | I2S_TCR4_SYWD(0) | I2S_TCR4_MF + | I2S_TCR4_FSE | I2S_TCR4_FSD; + I2S1_TCR5 = I2S_TCR5_WNW(31) | I2S_TCR5_W0W(31) | I2S_TCR5_FBT(31); + + I2S1_RMR = 0; + I2S1_RCR1 = I2S_RCR1_RFW(4); + I2S1_RCR2 = I2S_RCR2_SYNC(rsync) | I2S_TCR2_BCP | I2S_RCR2_MSEL(1) + | I2S_RCR2_BCD | I2S_RCR2_DIV(0); + I2S1_RCR3 = I2S_RCR3_RCE; + I2S1_RCR4 = I2S_RCR4_FRSZ(7) | I2S_RCR4_SYWD(0) | I2S_RCR4_MF + | I2S_RCR4_FSE | I2S_RCR4_FSD; + I2S1_RCR5 = I2S_RCR5_WNW(31) | I2S_RCR5_W0W(31) | I2S_RCR5_FBT(31); + + CORE_PIN23_CONFIG = 3; //1:MCLK + CORE_PIN21_CONFIG = 3; //1:RX_BCLK + CORE_PIN20_CONFIG = 3; //1:RX_SYNC +} + +#endif diff --git a/output_tdm8_f32.h b/output_tdm8_f32.h new file mode 100644 index 0000000..eae2ca2 --- /dev/null +++ b/output_tdm8_f32.h @@ -0,0 +1,72 @@ +/* + * output_tdm8_f32.h + * + * Audio Library for Teensy 3.X + * Copyright (c) 2017, Paul Stoffregen, paul@pjrc.com + * + * Development of this audio library was funded by PJRC.COM, LLC by sales of + * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * 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, development funding 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. + */ +/* + * Extended by OpenAudio_ArduinoLibrary, 2026 + * Converted to F32. TDM8: 8 channels, one per 32-bit slot. + * Uses the Teensy 4.x SAI1 TDM pins (same as AudioOutputTDM). + */ + +#if defined(__IMXRT1062__) +#ifndef output_tdm8_f32_h_ +#define output_tdm8_f32_h_ + +#include +#include +#include "AudioStream_F32.h" +#include "DMAChannel.h" +#include // From Teensy Audio library. For set_audioClock(). + +class AudioOutputTDM8_F32 : public AudioStream_F32 +{ +//GUI: inputs:8, outputs:0 //this line used for automatic generation of GUI node +public: + AudioOutputTDM8_F32(void) : AudioStream_F32(8, inputQueueArray_f32) { begin(); } //uses default AUDIO_SAMPLE_RATE and BLOCK_SIZE_SAMPLES from AudioStream.h + AudioOutputTDM8_F32(const AudioSettings_F32 &settings) : AudioStream_F32(8, inputQueueArray_f32) { + sample_rate_Hz = settings.sample_rate_Hz; + audio_block_samples = settings.audio_block_samples; + begin(); + } + static constexpr int getNumChannels() { return 8; } + virtual void update(void); + void begin(void); + friend class AudioInputTDM8_F32; +protected: + static void config_tdm(void); + static audio_block_f32_t *block_input[8]; + static bool update_responsibility; + static DMAChannel dma; + static void isr(void); +private: + audio_block_f32_t *inputQueueArray_f32[8]; + static float sample_rate_Hz; + static int audio_block_samples; +}; + +#endif +#endif diff --git a/readme.md b/readme.md index fed38b0..08be042 100644 --- a/readme.md +++ b/readme.md @@ -20,6 +20,8 @@ The primary documentation is the Design Tool. Clicking on any of the classes br **Teensy 3 and 4** During 2020 this library has undergone revision to make it Teensy 4.x compatible. Much of this related to using multiple sampling rates with the I2S clocks and the interrupt/DMA code for data transfer. The files and associated classes output_i2s_f32.h, output_i2s_f32.cpp, input_i2s_f32.h, input_i2s_f32.cpp are now ready to be used for T3.x and T4.x. There are some restrictions, particularly this should be used with 16-bit I2S codec data. Codec sample rates can be varied. Variable block size is supported, but be sure the settings option is used. Thanks to Chip, @jcj83429 and all the Teensy development folks. +**TDM 8/16 Channel** Teensy 4.x TDM I/O is available as Float32 modules: `input_tdm8_f32`, `output_tdm8_f32`, `input_tdm16_f32`, `output_tdm16_f32` (classes `AudioInputTDM8_F32`, `AudioOutputTDM8_F32`, `AudioInputTDM16_F32`, `AudioOutputTDM16_F32`). Each uses 32-bit TDM slots on SAI1 (pins 7/8 TX/RX data, 23 MCLK, 21 BCLK, 20 SYNC). See `docs/TDM_AUDIO.md`. + **Tympan Project** Many of the classes in this library were put together as part of the [Tympan Project.](https://github.com/Tympan) That is oriented towards open-source hearing aid and hearing aid development tools. It has its own [Tympan Design Tool](https://tympan.github.io/Tympan_Audio_Design_Tool/) as well as some custom Teensy-based hardware. Additionally, there are a few classes in this library that use terminology and variables that are specific to audiology. It is intended that these, in time, be replaced by similar classes with more conventional descriptors. And, of course, if your interest is in hearing aids, you should spend time at the Tympan project! Notes diff --git a/scripts/setup.ps1 b/scripts/setup.ps1 index 782c73a..c6d4cf2 100644 --- a/scripts/setup.ps1 +++ b/scripts/setup.ps1 @@ -108,7 +108,7 @@ if (Test-Path -LiteralPath $checkCore) { Write-Host " [FAIL] Patched core files missing at $checkCore" $ok = $false } -$checkLib = "$arduinoLibPath\examples\USBAudioPassthrough_F32\USBAudioPassthrough_F32.ino" +$checkLib = "$arduinoLibPath\examples\AudioUSB2TDM\AudioUSB2TDM.ino" if (Test-Path -LiteralPath $checkLib) { Write-Host " [OK] Library examples accessible" } else { @@ -119,7 +119,7 @@ if (Test-Path -LiteralPath $checkLib) { Write-Host "" if ($ok) { Write-Host "Setup complete! Restart Arduino IDE, then open:" - Write-Host " File > Examples > OpenAudio_ArduinoLibrary > USBAudioPassthrough_F32" + Write-Host " File > Examples > OpenAudio_ArduinoLibrary > AudioUSB2TDM" Write-Host "" Write-Host "Backup: .\scripts\restore_teensy_audio.ps1" } else { From 89a3c54d8380c76666399eeeb9128d9b7c94c35c Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Sun, 2 Aug 2026 03:36:57 -0700 Subject: [PATCH 04/11] Implement I2S clock sink mode --- .../AudioOutputI2Ssink_F32_Example.ino | 52 +++++++++++++++++++ examples/AudioUSB2TDM/AudioUSB2TDM.ino | 3 +- input_i2s_f32.cpp | 29 ++++++++++- input_i2s_f32.h | 16 ++++-- keywords.txt | 6 +++ output_i2s_f32.cpp | 30 ++++++----- output_i2s_f32.h | 22 +++++--- output_i2s_quad_f32.cpp | 2 +- output_i2s_quad_f32.h | 4 +- 9 files changed, 132 insertions(+), 32 deletions(-) create mode 100644 examples/AudioOutputI2Ssink_F32_Example/AudioOutputI2Ssink_F32_Example.ino diff --git a/examples/AudioOutputI2Ssink_F32_Example/AudioOutputI2Ssink_F32_Example.ino b/examples/AudioOutputI2Ssink_F32_Example/AudioOutputI2Ssink_F32_Example.ino new file mode 100644 index 0000000..0a53c73 --- /dev/null +++ b/examples/AudioOutputI2Ssink_F32_Example/AudioOutputI2Ssink_F32_Example.ino @@ -0,0 +1,52 @@ +/* + * Demonstration of AudioOutputI2Ssink_F32: an I2S output that does NOT + * generate the bit clock (BCLK) or frame sync (FS). + * + * The audio data appears on pin 7 (SAI1_TX_DATA0). Pins 21 (BCLK) and + * 20 (FS) must be driven by an external clock SOURCE - a second Teensy + * running a normal I2S output (e.g. AudioOutputI2S_F32_Example), a codec + * in its clock-source role, an FPGA, etc. This Teensy only listens to + * the clocks and shifts out its data. Connect GND between the devices. + * + * The clock source must provide a bit clock of 64 x fs (2 slots x 32 bits) + * and a 1 x fs frame sync (left-justified, word-high-then-low), which is + * exactly what another Teensy's AudioOutputI2S_F32 produces. + * + * Wiring: + * Data out : pin 7 + * BCLK in : pin 21 (external clock source) + * FS in : pin 20 (external clock source) + * MCLK : pin 23 (not used in sink mode) + * + * The sink's DMA interrupt drives the audio block scheduler, so the audio + * is clocked by the external clock source. + * + * Expected result: the sine tone is present on pin 7 whenever the external + * clock source is running. You can watch it with a scope, or feed pin 7 + * into a second Teensy running AudioInputI2S_F32. + */ + +#include +#include +#include +#include + +AudioSynthWaveformSine_F32 tone0; +AudioOutputI2Ssink_F32 i2s_out; + +AudioConnection_F32 connect0(tone0, 0, i2s_out, 0); +AudioConnection_F32 connect1(tone0, 0, i2s_out, 1); + +void setup() { + delay(1000); + + AudioMemory_F32(10); + + tone0.amplitude(0.5); + tone0.frequency(440.0); + tone0.begin(); +} + +void loop() { + delay(1000); +} diff --git a/examples/AudioUSB2TDM/AudioUSB2TDM.ino b/examples/AudioUSB2TDM/AudioUSB2TDM.ino index a20c50a..12f5185 100644 --- a/examples/AudioUSB2TDM/AudioUSB2TDM.ino +++ b/examples/AudioUSB2TDM/AudioUSB2TDM.ino @@ -1,8 +1,9 @@ /* * USB<->TDM8 example for Teensy USB audio and OpenAudio_ArduinoLibrary * - * Under Tools menu, set Audio sample rate (up to 96kHz), 8 channels, and bit depth. + * Under Tools menu, set Audio sample rate (up to 96kHz), 8 channels, and desired USB bit depth. * Set USB type to Audio or Serial + MIDI + Audio + * When making changes to USB settings, you may need to uninstall the device in Device Manager and reconnect for USB audio to work properly * * Each TDM slot is 32 bits (8 slots x 32 bits = 256 bit clocks per frame). * Wiring to the Teensy 4.x SAI1 TDM pins: diff --git a/input_i2s_f32.cpp b/input_i2s_f32.cpp index 7e2189f..754b022 100644 --- a/input_i2s_f32.cpp +++ b/input_i2s_f32.cpp @@ -269,14 +269,14 @@ void AudioInputI2S_F32::update(void) /******************************************************************/ -void AudioInputI2Sslave_F32::begin(void) +void AudioInputI2Ssink_F32::begin(void) { dma.begin(true); // Allocate the DMA channel first //block_left_1st = NULL; //block_right_1st = NULL; - AudioOutputI2Sslave_F32::config_i2s(); + AudioOutputI2Ssink_F32::config_i2s(); #if defined(KINETISK) CORE_PIN13_CONFIG = PORT_PCR_MUX(4); // pin 13, PTC5, I2S0_RXD0 @@ -299,6 +299,31 @@ void AudioInputI2Sslave_F32::begin(void) I2S0_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE | I2S_RCSR_FRDE | I2S_RCSR_FR; I2S0_TCSR |= I2S_TCSR_TE | I2S_TCSR_BCE; // TX clock enable, because sync'd to TX dma.attachInterrupt(isr); +#elif defined(__IMXRT1062__) + CORE_PIN8_CONFIG = 3; //1:RX_DATA0 + IOMUXC_SAI1_RX_DATA0_SELECT_INPUT = 2; + + dma.TCD->SADDR = (void *)((uint32_t)&I2S1_RDR0 + 0); + dma.TCD->SOFF = 0; + dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2); + dma.TCD->NBYTES_MLNO = 4; + dma.TCD->SLAST = 0; + dma.TCD->DADDR = i2s_rx_buffer; + dma.TCD->DOFF = 4; + dma.TCD->CITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 4; + dma.TCD->DLASTSGA = -I2S_BUFFER_TO_USE_BYTES; + dma.TCD->BITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 4; + dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; + dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI1_RX); + + // enable only the receiver. Do NOT write I2S1_TCSR here: the SAI + // transmitter shares the SAI1_TCSR and enabling TE from an input sink + // latches TX FIFO underrun (FEF) when the TX FIFO is empty, which blocks + // SAI1_TX DMA requests. + I2S1_RCSR = I2S_RCSR_RE | I2S_RCSR_BCE | I2S_RCSR_FRDE | I2S_RCSR_FR; + update_responsibility = update_setup(); + dma.enable(); + dma.attachInterrupt(isr); #endif } diff --git a/input_i2s_f32.h b/input_i2s_f32.h index 2fb9c0e..a921853 100644 --- a/input_i2s_f32.h +++ b/input_i2s_f32.h @@ -66,26 +66,32 @@ class AudioInputI2S_F32 : public AudioStream_F32 void clear_isOutOfMemory(void) { flag_out_of_memory = 0; } //friend class AudioOutputI2S_F32; protected: - AudioInputI2S_F32(int dummy): AudioStream_F32(0, NULL) {} // to be used only inside AudioInputI2Sslave !! + AudioInputI2S_F32(int dummy): AudioStream_F32(0, NULL) {} // to be used only inside AudioInputI2Ssink !! static bool update_responsibility; static DMAChannel dma; static void isr_32(void); static void isr(void); virtual void update_1chan(int, audio_block_f32_t *&); +protected: + static float sample_rate_Hz; + static int audio_block_samples; private: static audio_block_f32_t *block_left_f32; static audio_block_f32_t *block_right_f32; - static float sample_rate_Hz; - static int audio_block_samples; static uint16_t block_offset; static int flag_out_of_memory; static unsigned long update_counter; }; -class AudioInputI2Sslave_F32 : public AudioInputI2S_F32 +// I2S "sink": receives audio on SAI1 (data in = pin 8) but does NOT generate the +// bit clock or frame sync. BCLK (pin 21) and FS (pin 20) must be driven by an +// external clock source. Expects a 64*fs bit clock (2x 32-bit slots). On +// Teensy 4.x this uses full 32-bit data in each slot; the SAI receiver is the +// only block enabled (no TCSR write), matching the proven F32 input pattern. +class AudioInputI2Ssink_F32 : public AudioInputI2S_F32 { public: - AudioInputI2Sslave_F32(void) : AudioInputI2S_F32(0) { begin(); } + AudioInputI2Ssink_F32(void) : AudioInputI2S_F32(0) { begin(); } void begin(void); friend void dma_ch1_isr(void); }; diff --git a/keywords.txt b/keywords.txt index 9cdb0c4..f624847 100644 --- a/keywords.txt +++ b/keywords.txt @@ -202,6 +202,8 @@ setFilterCoeff_Matlab KEYWORD2 AudioInputI2S_F32 KEYWORD1 +AudioInputI2Ssink_F32 KEYWORD1 + AudioLMSDenoiseNotch_F32 KEYWORD1 initializeLMS KEYWORD2 enable KEYWORD2 @@ -240,6 +242,8 @@ scale_f32_to_i24 KEYWORD2 scale_f32_to_i32 KEYWORD2 setI2SFreq_T3 KEYWORD2 +AudioOutputI2Ssink_F32 KEYWORD1 + AudioPlayQueue_F32 KEYWORD1 play KEYWORD2 playAudioBlock KEYWORD2 @@ -421,6 +425,8 @@ scale_i32_to_f32 KEYWORD2 get_isOutOfMemory KEYWORD2 clear_isOutOfMemory KEYWORD2 +AudioInputI2Ssink_F32 KEYWORD1 + AudioInputSPDIF3_F32 KEYWORD1 pllLocked KEYWORD2 diff --git a/output_i2s_f32.cpp b/output_i2s_f32.cpp index 1bab8a7..fc41d07 100644 --- a/output_i2s_f32.cpp +++ b/output_i2s_f32.cpp @@ -530,10 +530,12 @@ void AudioOutputI2S_F32::config_i2s(bool transferUsing32bit, float fs_Hz) /******************************************************************/ -// From Chip: The I2SSlave functionality has NOT been extended to +// From Chip: The I2Ssink functionality has NOT been extended to // allow for different block sizes or sample rates (2020-10-31) +// On Teensy 4.x this uses the same full 32-bit data DMA path as the +// master AudioOutputI2S_F32 (SSIZE/DSIZE=2, NBYTES=4, TDR0+0). -void AudioOutputI2Sslave_F32::begin(void) +void AudioOutputI2Ssink_F32::begin(void) { dma.begin(true); // Allocate the DMA channel first @@ -541,7 +543,7 @@ void AudioOutputI2Sslave_F32::begin(void) block_left_1st = NULL; block_right_1st = NULL; - AudioOutputI2Sslave_F32::config_i2s(); + AudioOutputI2Ssink_F32::config_i2s(); #if defined(KINETISK) CORE_PIN22_CONFIG = PORT_PCR_MUX(6); // pin 22, PTC1, I2S0_TXD0 @@ -564,19 +566,18 @@ void AudioOutputI2Sslave_F32::begin(void) #elif defined(__IMXRT1062__) CORE_PIN7_CONFIG = 3; //1:TX_DATA0 + dma.TCD->SADDR = i2s_tx_buffer; - dma.TCD->SOFF = 2; - dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); - dma.TCD->NBYTES_MLNO = 2; - dma.TCD->SLAST = -sizeof(i2s_tx_buffer); - //dma.TCD->DADDR = (void *)((uint32_t)&I2S1_TDR1 + 2); + dma.TCD->SOFF = 4; + dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2); + dma.TCD->NBYTES_MLNO = 4; + dma.TCD->SLAST = -I2S_BUFFER_TO_USE_BYTES; dma.TCD->DOFF = 0; - dma.TCD->CITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; + dma.TCD->CITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 4; dma.TCD->DLASTSGA = 0; - dma.TCD->BITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; - //dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI2_TX); - dma.TCD->DADDR = (void *)((uint32_t)&I2S1_TDR0 + 2); + dma.TCD->BITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 4; dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; + dma.TCD->DADDR = (void *)((uint32_t)&I2S1_TDR0 + 0); dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI1_TX); dma.enable(); @@ -591,7 +592,7 @@ void AudioOutputI2Sslave_F32::begin(void) } - void AudioOutputI2Sslave_F32::config_i2s(void) + void AudioOutputI2Ssink_F32::config_i2s(void) { #if defined(KINETISK) SIM_SCGC6 |= SIM_SCGC6_I2S; @@ -642,7 +643,8 @@ void AudioOutputI2Sslave_F32::begin(void) if (I2S1_TCSR & I2S_TCSR_TE) return; if (I2S1_RCSR & I2S_RCSR_RE) return; - // not using MCLK in slave mode - hope that's ok? + // not using MCLK in sink mode - the bit clock and frame sync come from an + // external clock source //CORE_PIN23_CONFIG = 3; // AD_B1_09 ALT3=SAI1_MCLK CORE_PIN21_CONFIG = 3; // AD_B1_11 ALT3=SAI1_RX_BCLK CORE_PIN20_CONFIG = 3; // AD_B1_10 ALT3=SAI1_RX_SYNC diff --git a/output_i2s_f32.h b/output_i2s_f32.h index b8fe324..03968a5 100644 --- a/output_i2s_f32.h +++ b/output_i2s_f32.h @@ -65,7 +65,7 @@ class AudioOutputI2S_F32 : public AudioStream_F32 void begin(void); void begin(bool); friend class AudioInputI2S_F32; - friend class AudioInputI2S_F32; + friend class AudioInputI2Ssink_F32; #if defined(__IMXRT1062__) friend class AudioOutputI2SQuad_F32; friend class AudioInputI2SQuad_F32; @@ -81,7 +81,7 @@ class AudioOutputI2S_F32 : public AudioStream_F32 static float setI2SFreq_T3(const float); // I2S clock for T3,x protected: - AudioOutputI2S_F32(int dummy): AudioStream_F32(2, inputQueueArray) {} // to be used only inside AudioOutputI2Sslave !! + AudioOutputI2S_F32(int dummy): AudioStream_F32(2, inputQueueArray) {} // to be used only inside AudioOutputI2Ssink !! static void config_i2s(void); static void config_i2s(bool); static void config_i2s(float); @@ -93,24 +93,32 @@ class AudioOutputI2S_F32 : public AudioStream_F32 static void isr_16(void); static void isr_32(void); static void isr(void); +protected: + static float sample_rate_Hz; + static int audio_block_samples; private: static audio_block_f32_t *block_left_2nd; static audio_block_f32_t *block_right_2nd; static uint16_t block_left_offset; static uint16_t block_right_offset; audio_block_f32_t *inputQueueArray[2]; - static float sample_rate_Hz; - static int audio_block_samples; volatile uint8_t enabled = 1; float outputScale = 1.0f; // Quick volume control }; -class AudioOutputI2Sslave_F32 : public AudioOutputI2S_F32 +// I2S "sink": outputs audio on SAI1 (data out = pin 7) but does NOT generate the +// bit clock or frame sync. BCLK (pin 21) and FS (pin 20) must be driven by an +// external clock source (another Teensy, a codec in its clock-source role, etc.). +// Expects a 64*fs bit clock (2x 32-bit slots). On Teensy 4.x this uses full +// 32-bit data in each slot. On Teensy 3.x it uses the original 16-bit-in-the- +// upper-half packing. The DMA ISR drives update_all(), so the audio block +// scheduler is clocked by the external clock source. +class AudioOutputI2Ssink_F32 : public AudioOutputI2S_F32 { public: - AudioOutputI2Sslave_F32(void) : AudioOutputI2S_F32(0) { begin(); } ; + AudioOutputI2Ssink_F32(void) : AudioOutputI2S_F32(0) { begin(); } ; void begin(void); - friend class AudioInputI2Sslave_F32; + friend class AudioInputI2Ssink_F32; friend void dma_ch0_isr(void); protected: static void config_i2s(void); diff --git a/output_i2s_quad_f32.cpp b/output_i2s_quad_f32.cpp index 6265885..5d5f3ce 100644 --- a/output_i2s_quad_f32.cpp +++ b/output_i2s_quad_f32.cpp @@ -332,7 +332,7 @@ void AudioOutputI2SQuad_F32::config_i2s(int fs_Hz) I2S1_RCR5 = I2S_RCR5_WNW((32 - 1)) | I2S_RCR5_W0W((32 - 1)) | I2S_RCR5_FBT((32 - 1)); } -// From Chip: The I2SSlave functionality has NOT been extended to +// From Chip: The I2Ssink functionality has NOT been extended to // allow for different block sizes or sample rates (2020-10-31) // Quad slave object not working yet! Greg Raven KF5N diff --git a/output_i2s_quad_f32.h b/output_i2s_quad_f32.h index 733cfc6..cbb608e 100644 --- a/output_i2s_quad_f32.h +++ b/output_i2s_quad_f32.h @@ -67,7 +67,7 @@ class AudioOutputI2SQuad_F32 : public AudioStream_F32 } protected: - AudioOutputI2SQuad_F32(int dummy) : AudioStream_F32(4, inputQueueArray) {} // to be used only inside AudioOutputI2Sslave !! + AudioOutputI2SQuad_F32(int dummy) : AudioStream_F32(4, inputQueueArray) {} // to be used only inside AudioOutputI2SQuadslave !! void begin(); inline static audio_block_f32_t *block_left_1st = nullptr; @@ -98,7 +98,7 @@ class AudioOutputI2SQuadslave_F32 : public AudioOutputI2SQuad_F32 public: AudioOutputI2SQuadslave_F32(void) : AudioOutputI2SQuad_F32(0) { begin(); }; void begin(void); - friend class AudioInputI2Sslave_F32; + friend class AudioInputI2Ssink_F32; friend void dma_ch0_isr(void); protected: From dc92135b98b9c173e08db269d01ba3f9d4edb57b Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Sun, 2 Aug 2026 03:48:06 -0700 Subject: [PATCH 05/11] remove library.properties all it does is generate a warning about teensy/avr compatibility --- docs/MULTICHANNEL_USB_AUDIO.md | 15 +++++++-------- library.properties | 10 ---------- scripts/setup.ps1 | 23 +---------------------- 3 files changed, 8 insertions(+), 40 deletions(-) delete mode 100644 library.properties diff --git a/docs/MULTICHANNEL_USB_AUDIO.md b/docs/MULTICHANNEL_USB_AUDIO.md index 94480de..36c71cf 100644 --- a/docs/MULTICHANNEL_USB_AUDIO.md +++ b/docs/MULTICHANNEL_USB_AUDIO.md @@ -38,13 +38,12 @@ From the OpenAudio library root: The script handles everything: -1. **`library.properties`** — Creates it if missing (required by Arduino IDE 2.x) -2. **Detect Teensyduino** — Finds your install under `%LOCALAPPDATA%\Arduino15\` -3. **Backup** — Saves originals to `backups//` -4. **Patch core** — Installs multi-channel `usb_desc.h`, `usb_desc.c`, `usb.c`, `usb_audio.*`, `usb_audio_interface.*` -5. **Config** — Adds `boards.local.txt` (for USB Audio Channels menu) and updates `platform.txt` -6. **Arduino IDE library** — Copies the library into `Documents\Arduino\libraries\` -7. **Cache** — Clears Arduino IDE 2.x cache +1. **Detect Teensyduino** — Finds your install under `%LOCALAPPDATA%\Arduino15\` +2. **Backup** — Saves originals to `backups//` +3. **Patch core** — Installs multi-channel `usb_desc.h`, `usb_desc.c`, `usb.c`, `usb_audio.*`, `usb_audio_interface.*` +4. **Config** — Adds `boards.local.txt` (for USB Audio Channels menu) and updates `platform.txt` +5. **Arduino IDE library** — Copies the library into `Documents\Arduino\libraries\` +6. **Cache** — Clears Arduino IDE 2.x cache After running, **restart Arduino IDE**. Examples appear under **File → Examples → OpenAudio_ArduinoLibrary**. @@ -64,7 +63,7 @@ After running, **restart Arduino IDE**. Examples appear under **File → Example ``` 4. **Restart Arduino IDE** -> **Note:** The library ships with `library.properties`, but the setup script will create it as a fallback. +> **Note:** The library ships without a `library.properties`, matching upstream OpenAudio (which is installed by zip, not Library Manager). ## Usage diff --git a/library.properties b/library.properties deleted file mode 100644 index 4e1e687..0000000 --- a/library.properties +++ /dev/null @@ -1,10 +0,0 @@ -name=OpenAudio_ArduinoLibrary -version=1.0.0 -author=Chip Audette, Bob Larkin, and contributors -maintainer=Bob Larkin -sentence=OpenAudio F32 Arduino Library - Floating-point audio processing for Teensy. -paragraph=Extends the Teensy Audio Library with float32 audio blocks, including multi-channel USB Audio Class 2.0 with asynchronous feedback. -category=Signal Input/Output -url=https://github.com/chipaudette/OpenAudio_ArduinoLibrary -architectures=teensy -includes=OpenAudio_ArduinoLibrary.h diff --git a/scripts/setup.ps1 b/scripts/setup.ps1 index c6d4cf2..6f8e851 100644 --- a/scripts/setup.ps1 +++ b/scripts/setup.ps1 @@ -6,8 +6,7 @@ 2. Backs up original Teensy core USB audio files 3. Applies patched core files for multi-channel USB audio 4. Installs library in Arduino IDE's sketchbook folder - 5. Ensures library.properties exists (required by Arduino IDE 2.x) - 6. Validates the setup + 5. Validates the setup #> $ErrorActionPreference = "Stop" @@ -17,26 +16,6 @@ Write-Host "OpenAudio_ArduinoLibrary Setup" Write-Host "================================" Write-Host "" -# --- Step 0: Ensure library.properties exists --- -$propFile = "$repoDir\library.properties" -if (-not (Test-Path -LiteralPath $propFile)) { - Write-Host "Step 0: Creating library.properties..." - @" -name=OpenAudio_ArduinoLibrary -version=1.0.0 -author=Chip Audette, Bob Larkin, and contributors -maintainer=Bob Larkin -sentence=OpenAudio F32 Arduino Library - Floating-point audio processing for Teensy. -paragraph=Extends the Teensy Audio Library with float32 audio blocks, including multi-channel USB Audio Class 2.0 with asynchronous feedback. -category=Signal Input/Output -url=https://github.com/chipaudette/OpenAudio_ArduinoLibrary -architectures=teensy -includes=OpenAudio_ArduinoLibrary.h -"@ | Out-File -FilePath $propFile -Encoding utf8 - Write-Host " Created: $propFile" -} else { - Write-Host "Step 0: library.properties found." -} # --- Detect Teensy installation --- $possiblePaths = @( From 6c0046d265012b1f0a9995a83b0ae1e81e93065e Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Sun, 2 Aug 2026 12:24:24 -0700 Subject: [PATCH 06/11] add AudioInputUSB_F32.volume() --- input_usb_f32.cpp | 5 +++++ input_usb_f32.h | 1 + 2 files changed, 6 insertions(+) diff --git a/input_usb_f32.cpp b/input_usb_f32.cpp index 6e3712c..52afc0e 100644 --- a/input_usb_f32.cpp +++ b/input_usb_f32.cpp @@ -76,6 +76,11 @@ USBAudioInInterface::Status AudioInputUSB_F32::getStatus() const return _usbInterface.getStatus(); } +float AudioInputUSB_F32::volume(void) +{ + return _usbInterface.volume(); +} + #if AUDIO_SUBSLOT_SIZE == 2 void AudioInputUSB_F32::copy_to_buffers(const uint8_t *src, uint16_t bIdx, uint16_t noChannels, unsigned int count, unsigned int len) { diff --git a/input_usb_f32.h b/input_usb_f32.h index ef8d664..f0978d1 100644 --- a/input_usb_f32.h +++ b/input_usb_f32.h @@ -20,6 +20,7 @@ class AudioInputUSB_F32 : public AudioStream_F32 float getRequestedSamplingFrequ() const; float getActualBIntervalUs() const; USBAudioInInterface::Status getStatus() const; + float volume(void); private: static void copy_to_buffers(const uint8_t *src, uint16_t bIdx, uint16_t noChannels, unsigned int count, unsigned int len); From b01907d68677e6da218983095be5ae9e824c7e39 Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Mon, 3 Aug 2026 10:01:49 -0700 Subject: [PATCH 07/11] Updates for GUI Add new blocks (TDM, I2Ssink, etc) Update some descriptions for new modifications (mostly USB channels and USB in .volume() function) Add pre-existing I2SQuad to GUI Add warnings for incompatible blocks (I2S in + TDM out, etc) --- docs/icons/error.png | Bin 0 -> 1191 bytes docs/index.html | 563 ++++++++++++++++++++++++++++-- docs/red/main.js | 2 + docs/red/ui/view.js | 46 ++- gui/DesignTool_F32.zip | Bin 1727120 -> 1723511 bytes keywords.txt | 9 + scripts/build_design_tool_zip.ps1 | 73 ++++ 7 files changed, 656 insertions(+), 37 deletions(-) create mode 100644 docs/icons/error.png create mode 100644 scripts/build_design_tool_zip.ps1 diff --git a/docs/icons/error.png b/docs/icons/error.png new file mode 100644 index 0000000000000000000000000000000000000000..cd08ae5447ec25ddac99c8e6777eea7d83dab653 GIT binary patch literal 1191 zcmV;Y1X%ltP)GM>TCZR zEZ^L?jq+sh%76tiAR4p+(}X*7?h6;+E!N*MY-o4u^URvngjawt1QURGAOYwQDn69k zn#V0!y788KCOD^NZ9{e;rWt_wGnnZM`G z?Mz(^eSKg9FovA_Cj(=^ByjBvjG0Jr+>AGFs*HVam8TZ(dH8YS7zWOQ%7M7lmU94P z^I5=6_k#=I_rRoiPx!m;opr;omL)5{pl&{#-VfOvs640wdYXV-3y&7+OKY# z@${%+jmw|f1O@2+6x=M>43GiGrCdxit1st4WkI>AK2w0+y`cMPT)1lMs9^f*HCq^e zR})9x2NgmhparxBVMWC2+`Z#3 zVU>>;b!3@0$cyPLauj5mh zZo(uzGw|6IO4%e0C=4-CgozU*5k#PA^&AMI<7)cpIU|{QI~OmZy=@iU2N>)Fp;LyX2MgAzf|h}hJF5)=l+f&vkX5)c`_ z00K&-C3Z59YXVw;lXSJcto@M-66~3@v(VAlNnX@&mo1_VHZj=PLSnI6(pPQ;(F#zg z)HmRua8P7M#Gv#q{B-_EY2DKeyboard Shortcuts + + + +
+ + + + @@ -2338,6 +2510,56 @@

Notes

+ + + + + + + + + +