diff --git a/.github/workflows/arduino-lint.yml b/.github/workflows/arduino-lint.yml index 8141476..6b3ab62 100644 --- a/.github/workflows/arduino-lint.yml +++ b/.github/workflows/arduino-lint.yml @@ -17,11 +17,11 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v5 - name: Arduino Lint - uses: arduino/arduino-lint-action@v1 + uses: arduino/arduino-lint-action@v2 with: official: true project-type: library - library-manager: submit + library-manager: update diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index e1bb5ff..61ef8bc 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -34,11 +34,11 @@ jobs: - fqbn: arduino:mbed_giga:giga platforms: | - name: arduino:mbed_giga - source-url: https://downloads.arduino.cc/packages/package_mbed_index.json + artifact-name-suffix: arduino-mbed_giga-giga steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v5 - name: Compile examples uses: arduino/compile-sketches@v1 @@ -49,16 +49,15 @@ jobs: libraries: | # Install the library from the local path. - source-path: ./ - - source-url: https://github.com/facchinm/USBHostMbed5.git - version: test_hs_in_fs + - name: Arduino_USBHostMbed5 sketch-paths: | - examples enable-deltas-report: true sketches-report-path: ${{ env.SKETCHES_REPORTS_PATH }} - name: Save sketches report as workflow artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: if-no-files-found: error path: ${{ env.SKETCHES_REPORTS_PATH }} - name: ${{ env.SKETCHES_REPORTS_PATH }} + name: sketches-report-${{ matrix.board.artifact-name-suffix }} diff --git a/.github/workflows/report-size-deltas.yml b/.github/workflows/report-size-deltas.yml index 652be5d..39e2a0a 100644 --- a/.github/workflows/report-size-deltas.yml +++ b/.github/workflows/report-size-deltas.yml @@ -20,5 +20,5 @@ jobs: - name: Comment size deltas reports to PRs uses: arduino/report-size-deltas@v1 with: - # The name of the workflow artifact created by the sketch compilation workflow - sketches-reports-source: sketches-reports + # Regex matching the names of the workflow artifacts created by the "Compile Examples" workflow + sketches-reports-source: ^sketches-report-.+ diff --git a/.github/workflows/spell-check.yml b/.github/workflows/spell-check.yml index 3f6b03f..19f5d38 100644 --- a/.github/workflows/spell-check.yml +++ b/.github/workflows/spell-check.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v5 - name: Spell check uses: codespell-project/actions-codespell@master diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index 63c5b72..aac676e 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v5 - name: Download JSON schema for labels configuration file id: download-schema @@ -70,7 +70,7 @@ jobs: file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/${{ matrix.filename }} - name: Pass configuration files to next job via workflow artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: path: | *.yaml @@ -105,16 +105,16 @@ jobs: echo "flag=--dry-run" >> $GITHUB_OUTPUT - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v5 - name: Download configuration files artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v5 with: name: ${{ env.CONFIGURATIONS_ARTIFACT }} path: ${{ env.CONFIGURATIONS_FOLDER }} - name: Remove unneeded artifact - uses: geekyeggo/delete-artifact@v2 + uses: geekyeggo/delete-artifact@v5 with: name: ${{ env.CONFIGURATIONS_ARTIFACT }} diff --git a/README.md b/README.md index 1be29ca..f8b3fb8 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,7 @@ - + -`Arduino_AdvancedAnalog` 〰 -=========================== -[![Compile Examples status](https://github.com/arduino-libraries/Arduino_AdvancedAnalog/actions/workflows/compile-examples.yml/badge.svg)](https://github.com/arduino-libraries/Arduino_AdvancedAnalog/actions/workflows/compile-examples.yml) -[![Spell Check status](https://github.com/arduino-libraries/Arduino_AdvancedAnalog/actions/workflows/spell-check.yml/badge.svg)](https://github.com/arduino-libraries/Arduino_AdvancedAnalog/actions/workflows/spell-check.yml) -[![Sync Labels status](https://github.com/arduino-libraries/Arduino_AdvancedAnalog/actions/workflows/sync-labels.yml/badge.svg)](https://github.com/arduino-libraries/Arduino_AdvancedAnalog/actions/workflows/sync-labels.yml) -[![Arduino Lint](https://github.com/arduino-libraries/Arduino_AdvancedAnalog/workflows/Arduino%20Lint/badge.svg)](https://github.com/arduino-libraries/Arduino_AdvancedAnalog/actions?workflow=Arduino+Lint) +# 〰️ Arduino AdvancedAnalog Library -Advanced Analog Library for Arduino Giga. +The Arduino AdvancedAnalog library is designed to offer high performance DAC/ADC applications on boards based on the STM32H7 microcontroller. -## :mag_right: Resources - -* [How to install a library](https://www.arduino.cc/en/guide/libraries) -* [Help Center](https://support.arduino.cc/) -* [Forum](https://forum.arduino.cc) - -## :bug: Bugs & Issues - -If you want to report an issue with this library, you can submit it to the [issue tracker](https://github.com/arduino-libraries/Arduino_Braccio_plusplus/issues) of this repository. Remember to include as much detail as you can about your hardware set-up, code and steps for reproducing the issue. Make sure you're using an original Arduino board. - -## :technologist: Development - -There are many ways to contribute: - -* Improve documentation and examples -* Fix a bug -* Test open Pull Requests -* Implement a new feature -* Discuss potential ways to improve this library - -You can submit your patches directly to this repository as Pull Requests. Please provide a detailed description of the problem you're trying to solve and make sure you test on real hardware. - -## :yellow_heart: Donations - -This open-source code is maintained by Arduino with the help of the community. We invest a considerable amount of time in testing code, optimizing it and introducing new features. Please consider [donating](https://www.arduino.cc/en/donate/) or [sponsoring](https://github.com/sponsors/arduino) to support our work, as well as [buying original Arduino boards](https://store.arduino.cc/) which is the best way to make sure our effort can continue in the long term. - -## Known issues -* ADC is running at the slowest possible clock (PCLK), should probably set up a PLL and change the clock source. +📖 For more information about this library please read the documentation [here](./docs/). \ No newline at end of file diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..41e1a5c --- /dev/null +++ b/docs/api.md @@ -0,0 +1,590 @@ +# Arduino Advanced Analog Library + +## AdvancedADC + +### `AdvancedADC` + +Creates an ADC object using the specified pin(s). The ADC object can sample a single channel or multiple channels successively if more than one pin is passed to the constructor. In this case, data from multiple channels will be interleaved in sample buffers. Additionally, the ADC channel of the first pin determines the ADC instance used, and the remaining channels (if any) must all belong to the same ADC instance. + +#### Syntax + +``` +AdvancedADC adc(analogPin); +``` + +#### Parameters + +- Pin `A0` through `A11` can be used. + +#### Returns + +`void`. + +### `AdvancedADC.begin()` + +Initializes and configures the ADC with the specified parameters. To reconfigure the ADC, `stop()` must be called first. + +#### Syntax + +``` +adc0.begin(resolution, sample_rate, n_samples, n_buffers) +``` + +#### Parameters + +- `enum` - **resolution** the sampling resolution (can be 8, 10, 12, 14 or 16 bits). + - `AN_RESOLUTION_8` + - `AN_RESOLUTION_10` + - `AN_RESOLUTION_12` + - `AN_RESOLUTION_14` + - `AN_RESOLUTION_16` +- `int` - **sample_rate** - the sampling rate / frequency in Hertz, e.g. `16000`. +- `int` - **n_samples** - the number of samples per sample buffer. See [SampleBuffer](#samplebuffer) for more details. +- `int` - **n_buffers** - the number of sample buffers in the queue. See [SampleBuffer](#samplebuffer) for more details. +- `bool` - **start** - if true (the default) the ADC will start sampling immediately, otherwise `start()` can be called later to start the ADC. +- `enum` - **sample_time** - the sampling time in cycles (the default is 8.5 cycles). + - `AN_ADC_SAMPLETIME_1_5` + - `AN_ADC_SAMPLETIME_2_5` + - `AN_ADC_SAMPLETIME_8_5` + - `AN_ADC_SAMPLETIME_16_5` + - `AN_ADC_SAMPLETIME_32_5` + - `AN_ADC_SAMPLETIME_64_5` + - `AN_ADC_SAMPLETIME_387_5` + - `AN_ADC_SAMPLETIME_810_5` + +#### Returns + +1 on success, 0 on failure. + +### `AdvancedADC.available()` + +Checks if the ADC is readable. + +#### Syntax + +``` +if(adc0.available()){} +``` + +#### Parameters + +None. + +#### Returns + +Returns true, if there's at least one sample buffer in the read queue, otherwise returns false. + +### `AdvancedADC.read()` + +Returns a sample buffer from the queue for reading. + +### `AdvancedADC.start()` + +Starts the ADC sampling. + +#### Syntax + +``` +adc.start() +``` + +#### Returns + +1 on success, 0 on failure. + +### `AdvancedADC.stop()` + +Stops the ADC and releases all of its resources. + +#### Syntax + +``` +adc.stop() +``` + +#### Returns + +- `1` + +## AdvancedADCDual + +### `AdvancedADCDual` + +The AdvancedADCDual class enables the configuration of two ADCs in Dual ADC mode. In this mode, the two ADCs are synchronized, and can be sampled simultaneously, with one ADC acting as the master ADC. Note: This mode is only supported on ADC1 and ADC2, and they must be passed to `begin()` in that order. + +#### Syntax + +``` +AdvancedADCDual adc_dual(adc1, adc2); +``` + +#### Parameters + +- `AdvancedADC` - **adc1** - the first ADC (must be ADC1). +- `AdvancedADC` - **adc2** - the second ADC (must be ADC2). + +#### Returns + +`void`. + +### `AdvancedADCDual.begin()` + +Initializes and starts the two ADCs with the specified parameters. + +#### Syntax + +``` +adc_dual.begin(resolution, sample_rate, n_samples, n_buffers) +``` + +#### Parameters + +- `enum` - **resolution** the sampling resolution (can be 8, 10, 12, 14 or 16 bits). + - `AN_RESOLUTION_8` + - `AN_RESOLUTION_10` + - `AN_RESOLUTION_12` + - `AN_RESOLUTION_14` + - `AN_RESOLUTION_16` +- `int` - **sample_rate** - the sampling rate / frequency in Hertz, e.g. `16000`. +- `int` - **n_samples** - the number of samples per sample buffer. See [SampleBuffer](#samplebuffer) for more details. +- `int` - **n_buffers** - the number of sample buffers in the queue. See [SampleBuffer](#samplebuffer) for more details. +- `enum` - **sample_time** - the sampling time in cycles (the default is 8.5 cycles). + - `AN_ADC_SAMPLETIME_1_5` + - `AN_ADC_SAMPLETIME_2_5` + - `AN_ADC_SAMPLETIME_8_5` + - `AN_ADC_SAMPLETIME_16_5` + - `AN_ADC_SAMPLETIME_32_5` + - `AN_ADC_SAMPLETIME_64_5` + - `AN_ADC_SAMPLETIME_387_5` + - `AN_ADC_SAMPLETIME_810_5` + +#### Returns + +1 on success, 0 on failure. + +### `AdvancedADCDual.stop()` + +Stops the dual ADCs and releases all resources. + +## AdvancedDAC + +### `AdvancedDAC` + +Creates a DAC object using the specified pin. + +#### Syntax + +``` +AdvancedDAC dac0(A12); +AdvancedDAC dac1(A13); +``` + +#### Parameters + +- `A12` or `A13` (DAC0 or DAC1 channels). + +#### Returns + +`void`. + +### `AdvancedDAC.begin()` + +Initializes the DAC with the specified parameters. To reconfigure the DAC, `stop()` must be called first. The DAC has a special mode called _loop mode_ enabled by setting `loop` parameter to `true`. In loop mode, the DAC will start automatically after all buffers are filled, and continuously cycle through over all buffers. + +#### Syntax + +``` +dac.begin(resolution, frequency, n_samples, n_buffers, loop=false) +``` + +#### Parameters + +- `enum` - resolution (can be 8, 10, 12 bits). + - `AN_RESOLUTION_8` + - `AN_RESOLUTION_10` + - `AN_RESOLUTION_12` +- `int` - **frequency** - the output frequency in Hertz, e.g. `8000`. +- `int` - **n_samples** - the number of samples per sample buffer. See [SampleBuffer](#samplebuffer) for more details. +- `int` - **n_buffers** - the number of sample buffers in the queue. See [SampleBuffer](#samplebuffer) for more details. +- `bool`- **loop** - enables loop mode. + +#### Returns + +1 on success, 0 on failure. + +### `AdvancedDAC.available()` + +Checks if the DAC is writable. + +#### Syntax + +``` +if(dac0.available()){} +``` + +#### Parameters + +None. + +#### Returns + +Returns true, if there's at least one free sample buffer in the write queue, otherwise returns false. + +### `AdvancedDAC.dequeue()` + +Returns a sample buffer from the queue for writing. + +#### Syntax + +``` +SampleBuffer buf = dac.dequeue(); + +for (size_t i=0; i + +AdvancedADC adc1(A0); + +void setup() { + Serial.begin(9600); + + // Initialize ADC with: resolution, sample rate, number of samples per channel, queue depth + if (!adc1.begin(AN_RESOLUTION_16, 16000, 32, 64)) { + Serial.println("Failed to start ADC!"); + while (1); + } +} + +void loop() { + // Check if an ADC measurement is ready + if (adc1.available()) { + // Get read buffer + SampleBuffer buf = adc1.read(); + + // Print sample from read buffer + Serial.println(buf[0]); + + // Release read buffer + buf.release(); + } +} +``` + +#### ADC Multichannel (GIGA R1 WiFi) +This library supports concurrent usage of up to **three** ADCs (_ADC1_, _ADC2_ and _ADC3_). +Each ADC instance can handle up to **16** channels. + +**Note:** It's important to be aware that certain pins cannot be used across multiple ADCs or cannot share the same ADC. + +*Please ensure that you refer to tables below when configuring your project to avoid conflicts in pin assignments.* + +Below is a table illustrating the pin mapping for each ADC in **Arduino Giga R1 WiFi**: + +| Pin | ADC1 | ADC2 | ADC3 | +|-------|-------|-------|-------| +| A0 | X | X | | +| A1 | X | X | | +| A2 | X | X | | +| A3 | X | X | | +| A4 | X | X | | +| A5 | X | X | X | +| A6 | X | X | X | +| A7 | X | | | +| A8(*) | | | X | +| A9(*) | | | X | +| A10(*) | X | X | | +| A11(*) | X | X | | + +(*) _Currently not supported_ + +Here is a example for the Arduino GIGA R1 WiFi: + +```cpp +#include + +AdvancedADC adc_a(A0, A1); +/* Mapped to ADC1 */ + +AdvancedADC adc_b(A2); +/* Mapped to ADC2, because ADC1 is occupied by A0 and A1 */ + +void setup() { +... +``` + +#### ADC Multichannel (Portenta H7) + +Below is a table illustrating the pin mapping for each ADC in **Portenta H7**: + +| Pin | ADC1 | ADC2 | ADC3 | +|-------|-------|-------|-------| +| A0 | X | X | | +| A1 | X | X | | +| A2 | | | X | +| A3 | | | X | +| A4 | X | X | X | +| A5 | X | X | | +| A6 | X | X | | +| A7 | X | X | | + +Here is an example for the Portenta H7: + +```cpp +#include + +AdvancedADC adc_c(A2, A3, A4); +/* Mapped to ADC3 */ + +AdvancedADC adc_d(A5); +/* Mapped to ADC1 */ + +void setup() { +... +``` + +### DAC + +To use this library for DAC application, you must have a supported Arduino board and include the AdvancedAnalog library in your Arduino sketch. Here is a minimal example for the Arduino GIGA R1 WiFi: + +```cpp +#include + +AdvancedDAC dac1(A12); + +void setup() { + Serial.begin(9600); + + // Initialize DAC with: resolution, sample rate, number of samples per channel, queue depth + if (!dac1.begin(AN_RESOLUTION_12, 8000, 32, 64)) { + Serial.println("Failed to start DAC!"); + while (1); + } +} + +void loop() { + if (dac1.available()) { + + // Get a free buffer for writing + SampleBuffer buf = dac1.dequeue(); + + // Write data to buffer (Even position: 0, Odd position: 0xFFF) + for (int i=0; i + +// WS, CK, SDI, SDO, MCK +AdvancedI2S i2s(PG_10, PG_11, PG_9, PB_5, PC_4); + +void setup() { + Serial.begin(9600); + + // Resolution, sample rate, number of samples per channel, queue depth. + if (!i2s.begin(AN_I2S_MODE_IN, 32000, 512, 32)) { + Serial.println("Failed to start I2S"); + while (1); + } +} + +void loop() { + if (i2s.available()) { + SampleBuffer buf = i2s.read(); + // process samples. + buf.release(); + } +} +``` + +## Examples +- **[Beginner](../examples/Beginner):** This folder contains full applications, like audio playback and a waveform generator. +- **[Advanced](../examples/Advanced):** This folder contains more specific examples showing advanced API configurations. + +## API + +The API documentation can be found [here](./api.md). + +## License + +This library is released under the [LGPLv2.1 license](../LICENSE). + diff --git a/examples/Advanced/ADC_Dual_Mode/ADC_Dual_Mode.ino b/examples/Advanced/ADC_Dual_Mode/ADC_Dual_Mode.ino new file mode 100644 index 0000000..433285c --- /dev/null +++ b/examples/Advanced/ADC_Dual_Mode/ADC_Dual_Mode.ino @@ -0,0 +1,42 @@ +#include + +AdvancedADC adc1(A0, A1); +AdvancedADC adc2(A2, A3); +AdvancedADCDual adc_dual(adc1, adc2); +uint64_t last_millis = 0; + +void setup() { + Serial.begin(9600); + while (!Serial) { + } + + // Resolution, sample rate, number of samples per channel, queue depth. + if (!adc_dual.begin(AN_RESOLUTION_16, 16000, 32, 32)) { + Serial.println("Failed to start analog acquisition!"); + while (1); + } +} + +void loop() { + if (adc1.available()) { + SampleBuffer buf1 = adc1.read(); + SampleBuffer buf2 = adc2.read(); + + // Process the buffer. + if (millis() - last_millis > 1) { + Serial.println(buf1.timestamp()); // Print buffer timestamp + Serial.println(buf1[0]); // Print sample from first channel + Serial.println(buf1[1]); // Print sample from second channel + + Serial.println(buf2.timestamp()); // Print buffer timestamp + Serial.println(buf2[0]); // Print sample from first channel + Serial.println(buf2[1]); // Print sample from second channel + + last_millis = millis(); + } + + // Release the buffer to return it to the pool. + buf1.release(); + buf2.release(); + } +} diff --git a/examples/Advanced/ADC_Multi_Channel_Dynamic/ADC_Multi_Channel_Dynamic.ino b/examples/Advanced/ADC_Multi_Channel_Dynamic/ADC_Multi_Channel_Dynamic.ino new file mode 100644 index 0000000..488b072 --- /dev/null +++ b/examples/Advanced/ADC_Multi_Channel_Dynamic/ADC_Multi_Channel_Dynamic.ino @@ -0,0 +1,74 @@ +/* ADC Multi Channel sampling usage demo + * + * Queries for pin numbers to sample on the Serial Monitor, then records and prints three readings on + * each of those pins at a leisurely rate of 2 Hz. + */ + +#include + +AdvancedADC adc; +uint64_t last_millis = 0; +pin_size_t active_pins[AN_MAX_ADC_CHANNELS]; +int num_active_pins = 0; +const int samples_per_round = 3; + +void queryPins() { + Serial.println("Enter pins to sample (number only, e.g. 3,4 for A3, and A4). Enter to repeat previous round."); + + int old_num_active_pins = num_active_pins; + num_active_pins = 0; + String buf; + int c; + do { + c = Serial.read(); + if (c < 0) continue; + + if (c == ',' || c == '\n') { + buf.trim(); + if (buf.length()) { + active_pins[num_active_pins++] = buf.toInt() + A0; + buf = String(); + } + } else { + buf += (char) c; + } + } while (!(c == '\n' || num_active_pins >= AN_MAX_ADC_CHANNELS)); + + // No (valid) input? Repeat previous measurement cycle + if (!num_active_pins) { + num_active_pins = old_num_active_pins; + } +} + +void setup() { + Serial.begin(9600); + while (!Serial) {}; +} + +void loop() { + queryPins(); + if (num_active_pins) { + // Resolution, sample rate, number of samples per buffer per channel, queue depth, number of pins, array of pins. + if (!adc.begin(AN_RESOLUTION_16, 2, 1, samples_per_round, num_active_pins, active_pins)) { + Serial.println("Failed to start analog acquisition!"); + while (1); + } + + for (int i = 0; i < samples_per_round; ++i) { + while(!adc.available()) {}; // Your code could do something useful while waiting! + + SampleBuffer buf = adc.read(); + + for (int i = 0; i < num_active_pins; ++i) { + Serial.print(buf[i]); + Serial.print(" "); + } + Serial.println(); + + // Release the buffer to return it to the pool. + buf.release(); + } + + adc.stop(); + } +} diff --git a/examples/Advanced/DAC_Loop/DAC_Loop.ino b/examples/Advanced/DAC_Loop/DAC_Loop.ino new file mode 100644 index 0000000..27556ee --- /dev/null +++ b/examples/Advanced/DAC_Loop/DAC_Loop.ino @@ -0,0 +1,41 @@ +// This examples shows how to use the DAC in loop mode. In loop mode the +// DAC starts automatically after all buffers are filled, and continuously +// cycle through over all buffers. +#include + +AdvancedDAC dac1(A12); + +void setup() { + Serial.begin(9600); + + while (!Serial) { + + } + + // Start DAC in loop mode. + if (!dac1.begin(AN_RESOLUTION_12, 16000, 32, 16, true)) { + Serial.println("Failed to start DAC1 !"); + while (1); + } + + // Write all buffers. + uint16_t sample = 0; + while (dac1.available()) { + // Get a free buffer for writing. + SampleBuffer buf = dac1.dequeue(); + + // Write data to buffer. + for (int i=0; i + +AdvancedDAC dac1(A12); +// WS, CK, SDI, SDO, MCK +AdvancedI2S i2s(PG_10, PG_11, PG_9, PB_5, PC_4); + +#define N_SAMPLES (256) +#define SAMPLE_AVERAGE(s0, s1) (((int16_t) s0 / 2) + ((int16_t) s1 / 2)) + +void setup() { + Serial.begin(9600); + while (!Serial) { + } + + // Resolution, sample rate, number of samples per channel, queue depth. + if (!dac1.begin(AN_RESOLUTION_12, 32000, N_SAMPLES, 32)) { + Serial.println("Failed to start DAC1 !"); + while (1); + } + + // I2S mode, sample rate, number of samples per channel, queue depth. + if (!i2s.begin(AN_I2S_MODE_IN, 32000, N_SAMPLES, 32)) { + Serial.println("Failed to start I2S"); + while (1); + } +} + +void loop() { + if (i2s.available() && dac1.available()) { + SampleBuffer i2sbuf = i2s.read(); + SampleBuffer dacbuf = dac1.dequeue(); + + // Write data to buffer. + for (int i=0; i> 4; + } + + // Write the buffer to DAC. + dac1.write(dacbuf); + i2sbuf.release(); + } +} diff --git a/examples/Advanced/I2S_Full_Duplex/I2S_Full_Duplex.ino b/examples/Advanced/I2S_Full_Duplex/I2S_Full_Duplex.ino new file mode 100644 index 0000000..9446da6 --- /dev/null +++ b/examples/Advanced/I2S_Full_Duplex/I2S_Full_Duplex.ino @@ -0,0 +1,32 @@ +// This example demonstrates I2S in full-duplex mode. In the main loop, samples +// are continuously captured from the I2S input, and written back to I2S output. + +#include + +// WS, CK, SDI, SDO, MCK +AdvancedI2S i2s(PG_10, PG_11, PG_9, PB_5, PC_4); + +void setup() { + Serial.begin(9600); + while (!Serial) { + + } + + // Resolution, sample rate, number of samples per channel, queue depth. + if (!i2s.begin(AN_I2S_MODE_INOUT, 32000, 512, 32)) { + Serial.println("Failed to start I2S"); + while (1); + } +} + +void loop() { + if (i2s.available()) { + SampleBuffer rxbuf = i2s.read(); + SampleBuffer txbuf = i2s.dequeue(); + for (size_t i=0; i +#include #include -#include #include -AdvancedDAC dac1(A12); - USBHostMSD msd; mbed::FATFileSystem usb("USB_DRIVE"); -FILE * file = nullptr; -int sample_size = 0; -int samples_count = 0; - +WavReader wavreader; +AdvancedDAC dac1(A12); +#define N_SAMPLES (512) -void setup() -{ +void setup() { Serial.begin(115200); - while (!Serial); + while (!Serial) { + } - /* Enable power for HOST USB connector. */ + // Enable power for HOST USB connector. pinMode(PA_15, OUTPUT); digitalWrite(PA_15, HIGH); - Serial.println("Please connect a USB stick to the GIGA's USB port ..."); - while (!msd.connect()) delay(100); + Serial.println("Please connect a USB stick to the USB host port ..."); + while (!msd.connect()) { + delay(100); + } Serial.println("Mounting USB device ..."); int const rc_mount = usb.mount(&msd); - if (rc_mount) - { + if (rc_mount) { Serial.print("Error mounting USB device "); Serial.println(rc_mount); - return; + while (1); } Serial.println("Opening audio file ..."); - - /* 16-bit PCM Mono 16kHz realigned noise reduction */ - file = fopen("/USB_DRIVE/AUDIO_SAMPLE.wav", "rb"); - if (file == nullptr) - { - Serial.print("Error opening audio file: "); - Serial.println(strerror(errno)); - return; + if (!wavreader.begin("/USB_DRIVE/AUDIO_SAMPLE.wav", N_SAMPLES, 1, false)) { + Serial.print("Error opening audio file: "); + while (1); } - Serial.println("Reading audio header ..."); - struct wav_header_t - { - char chunkID[4]; //"RIFF" = 0x46464952 - unsigned long chunkSize; //28 [+ sizeof(wExtraFormatBytes) + wExtraFormatBytes] + sum(sizeof(chunk.id) + sizeof(chunk.size) + chunk.size) - char format[4]; //"WAVE" = 0x45564157 - char subchunk1ID[4]; //"fmt " = 0x20746D66 - unsigned long subchunk1Size; //16 [+ sizeof(wExtraFormatBytes) + wExtraFormatBytes] - unsigned short audioFormat; - unsigned short numChannels; - unsigned long sampleRate; - unsigned long byteRate; - unsigned short blockAlign; - unsigned short bitsPerSample; - }; - - wav_header_t header; - fread(&header, sizeof(header), 1, file); - - Serial.println("WAV File Header read:"); char msg[64] = {0}; - snprintf(msg, sizeof(msg), "File Type: %s", header.chunkID); - Serial.println(msg); - snprintf(msg, sizeof(msg), "File Size: %ld", header.chunkSize); - Serial.println(msg); - snprintf(msg, sizeof(msg), "WAV Marker: %s", header.format); - Serial.println(msg); - snprintf(msg, sizeof(msg), "Format Name: %s", header.subchunk1ID); - Serial.println(msg); - snprintf(msg, sizeof(msg), "Format Length: %ld", header.subchunk1Size); - Serial.println(msg); - snprintf(msg, sizeof(msg), "Format Type: %hd", header.audioFormat); - Serial.println(msg); - snprintf(msg, sizeof(msg), "Number of Channels: %hd", header.numChannels); - Serial.println(msg); - snprintf(msg, sizeof(msg), "Sample Rate: %ld", header.sampleRate); - Serial.println(msg); - snprintf(msg, sizeof(msg), "Sample Rate * Bits/Sample * Channels / 8: %ld", header.byteRate); - Serial.println(msg); - snprintf(msg, sizeof(msg), "Bits per Sample * Channels / 8: %hd", header.blockAlign); - Serial.println(msg); - snprintf(msg, sizeof(msg), "Bits per Sample: %hd", header.bitsPerSample); - Serial.println(msg); - - /* Find the data section of the WAV file. */ - struct chunk_t - { - char ID[4]; - unsigned long size; - }; - - chunk_t chunk; - snprintf(msg, sizeof(msg), "id\t" "size"); - Serial.println(msg); - /* Find data chunk. */ - while (true) - { - fread(&chunk, sizeof(chunk), 1, file); - snprintf(msg, sizeof(msg), "%c%c%c%c\t" "%li", chunk.ID[0], chunk.ID[1], chunk.ID[2], chunk.ID[3], chunk.size); - Serial.println(msg); - if (*(unsigned int *) &chunk.ID == 0x61746164) - break; - /* Skip chunk data bytes. */ - fseek(file, chunk.size, SEEK_CUR); - } + snprintf(msg, sizeof(msg), "Number of Channels: %hd", wavreader.channels()); Serial.println(msg); + snprintf(msg, sizeof(msg), "Sample Rate: %ld", wavreader.sample_rate()); Serial.println(msg); + snprintf(msg, sizeof(msg), "Bits per Sample: %hd", wavreader.resolution()); Serial.println(msg); + snprintf(msg, sizeof(msg), "Number of Samples = %i", wavreader.sample_count()); Serial.println(msg); - /* Determine number of samples. */ - sample_size = header.bitsPerSample / 8; - samples_count = chunk.size * 8 / header.bitsPerSample; - snprintf(msg, sizeof(msg), "Sample size = %i", sample_size); Serial.println(msg); - snprintf(msg, sizeof(msg), "Samples count = %i", samples_count); Serial.println(msg); - - /* Configure the advanced DAC. */ - if (!dac1.begin(AN_RESOLUTION_12, header.sampleRate, 256, 16)) - { + // Configure and start the DAC. + if (!dac1.begin(AN_RESOLUTION_12, wavreader.sample_rate(), N_SAMPLES, 32)) { Serial.println("Failed to start DAC1 !"); - return; + while (1); } } -void loop() -{ - if (dac1.available() && !feof(file)) - { - /* Read data from file. */ - uint16_t sample_data[256] = {0}; - fread(sample_data, sample_size, 256, file); - - /* Get a free buffer for writing. */ - SampleBuffer buf = dac1.dequeue(); - - /* Write data to buffer. */ - for (size_t i = 0; i < buf.size(); i++) - { - /* Scale down to 12 bit. */ - uint16_t const dac_val = ((static_cast(sample_data[i])+32768)>>4) & 0x0fff; - buf[i] = dac_val; +void loop() { + if (dac1.available() && wavreader.available()) { + // Get a free buffer for writing. + SampleBuffer dacbuf = dac1.dequeue(); + + // Read a samples buffer from the wav file. + SampleBuffer pcmbuf = wavreader.read(); + + // Process and write samples to the DAC buffer. + for (size_t i=0; i> 4; + } else { + // If the file has two channels set the average. + dacbuf[i] = ((unsigned int) ((((int16_t) pcmbuf[(i * 2)] + (int16_t) pcmbuf[(i * 2) + 1]) / 2) + 32768)) >> 4; + } } - /* Write the buffer to DAC. */ - dac1.write(buf); + // Write the buffer to DAC. + dac1.write(dacbuf); + pcmbuf.release(); } } diff --git a/examples/Beginner/Audio_Playback_I2S/AUDIO_SAMPLE.wav b/examples/Beginner/Audio_Playback_I2S/AUDIO_SAMPLE.wav new file mode 100644 index 0000000..0e956d9 --- /dev/null +++ b/examples/Beginner/Audio_Playback_I2S/AUDIO_SAMPLE.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c17298498cf2a99c95f8dcf246691be4852eaddf8da24268b62854e0056c796 +size 1426086 diff --git a/examples/Beginner/Audio_Playback_I2S/Audio_Playback_I2S.ino b/examples/Beginner/Audio_Playback_I2S/Audio_Playback_I2S.ino new file mode 100644 index 0000000..a012ef2 --- /dev/null +++ b/examples/Beginner/Audio_Playback_I2S/Audio_Playback_I2S.ino @@ -0,0 +1,80 @@ +// This example demonstrates how to playback a WAV file with I2S. +// To run this sketch, rename 'USB_DRIVE' to the name of your USB +// stick drive, and copy the provided audio sample to the drive. +#include +#include +#include +#include +#include + +USBHostMSD msd; +mbed::FATFileSystem usb("USB_DRIVE"); + +WavReader wav; +// WS, CK, SDI, SDO, MCK +AdvancedI2S i2s(PG_10, PG_11, PG_9, PB_5, PC_4); +#define N_SAMPLES (512) + +void setup() { + Serial.begin(9600); + while (!Serial) { + + } + + // Enable power for HOST USB connector. + pinMode(PA_15, OUTPUT); + digitalWrite(PA_15, HIGH); + + Serial.println("Please connect a USB stick to the USB host port..."); + while (!msd.connect()) { + delay(100); + } + + Serial.println("Mounting USB device..."); + int const rc_mount = usb.mount(&msd); + if (rc_mount) { + Serial.print("Error mounting USB device "); + Serial.println(rc_mount); + while (1); + } + + Serial.println("Opening audio file ..."); + if (!wav.begin("/USB_DRIVE/AUDIO_SAMPLE.wav", N_SAMPLES, 1, true)) { + Serial.print("Error opening audio file: "); + while (1); + } + + // Resolution, sample rate, number of samples per channel, queue depth. + if (!i2s.begin(AN_I2S_MODE_OUT, wav.sample_rate(), N_SAMPLES, 64)) { + Serial.println("Failed to start I2S"); + while (1); + } + Serial.println("Playing audio file ..."); +} + +void loop() { + if (i2s.available() && wav.available()) { + // Get a free I2S buffer for writing. + SampleBuffer i2s_buf = i2s.dequeue(); + + // Read a PCM samples buffer from the wav file. + SampleBuffer pcm_buf = wav.read(); + + // Write PCM samples to the I2S buffer. + for (size_t i = 0; i < N_SAMPLES * wav.channels(); i++) { + // Note I2S buffers are always 2 channels. + if (wav.channels() == 2) { + i2s_buf[i] = pcm_buf[i]; + } else { + i2s_buf[(i * 2) + 0] = ((int16_t) pcm_buf[i] / 2); + i2s_buf[(i * 2) + 1] = ((int16_t) pcm_buf[i] / 2); + } + } + + // Write back the I2S buffer. + i2s.write(i2s_buf); + + // Release the PCM buffer. + pcm_buf.release(); + } +} diff --git a/examples/Beginner/Waveform_Generator/Waveform_Generator.ino b/examples/Beginner/Waveform_Generator/Waveform_Generator.ino index 2f8dc89..a5dbb21 100644 --- a/examples/Beginner/Waveform_Generator/Waveform_Generator.ino +++ b/examples/Beginner/Waveform_Generator/Waveform_Generator.ino @@ -1,22 +1,43 @@ // This example generates different waveforms based on user input on A12/DAC1. #include +#include -#define N_SAMPLES (256) -#define DEFAULT_FREQUENCY (16000) +#define N_SAMPLES (64) +#define DEFAULT_FREQUENCY (32000) AdvancedDAC dac1(A12); uint8_t SAMPLES_BUFFER[N_SAMPLES]; -size_t dac_frequency = DEFAULT_FREQUENCY; -void generate_waveform(int cmd) -{ +uint32_t get_current_heap() { + mbed_stats_heap_t heap_stats; + mbed_stats_heap_get(&heap_stats); + return heap_stats.current_size; +} + +void print_menu() { + Serial.println(); + Serial.println("Enter a command:"); + Serial.println("t: Triangle wave"); + Serial.println("q: Square wave"); + Serial.println("s: Sine wave"); + Serial.println("r: Sawtooth wave"); + Serial.println("k: stop DAC"); + Serial.println("+: Increase frequency"); + Serial.println("-: Decrease frequency"); +} + +void generate_waveform(int cmd) { + static bool dac_started = false; + static size_t dac_frequency = DEFAULT_FREQUENCY; + static size_t starting_heap = get_current_heap(); + switch (cmd) { case 't': // Triangle wave Serial.print("Waveform: Triangle "); - for (int i=0; i 1000) { - dac_frequency /= 2; + dac_frequency /= 2; } else { - break; + break; } - + + // Change frequency. + dac1.frequency(dac_frequency * N_SAMPLES); + break; + + case 'k': dac1.stop(); - delay(500); - if (!dac1.begin(AN_RESOLUTION_8, dac_frequency * N_SAMPLES, N_SAMPLES, 32)) { - Serial.println("Failed to start DAC1 !"); - } - delay(500); + dac_started = false; break; - + default: Serial.print("Unknown command "); Serial.println((char) cmd); return; } - - Serial.print(dac_frequency/1000); - Serial.println("KHz"); + + if (cmd == 'k') { + Serial.println("DAC stopped!"); + print_menu(); + } else { + Serial.print(dac_frequency/1000); + Serial.println("KHz"); + + if (dac_started == false) { + // Initialize and start the DAC. + if (!dac1.begin(AN_RESOLUTION_8, dac_frequency * N_SAMPLES, N_SAMPLES, 32)) { + Serial.println("Failed to start DAC1 !"); + while (1); + } + dac_started = true; + } + } + + Serial.print("Used memory: "); + Serial.print(get_current_heap() - starting_heap); + Serial.println(" bytes"); } void setup() { @@ -81,32 +121,21 @@ void setup() { } - - Serial.println("Enter a command:"); - Serial.println("t: Triangle wave"); - Serial.println("q: Square wave"); - Serial.println("s: Sine wave"); - Serial.println("r: Sawtooth wave"); - Serial.println("+: Increase frequency"); - Serial.println("-: Decrease frequency"); - + // Print list of commands. + print_menu(); + + // Start generating a sine wave. generate_waveform('s'); - - // DAC initialization - if (!dac1.begin(AN_RESOLUTION_8, DEFAULT_FREQUENCY * N_SAMPLES, N_SAMPLES, 32)) { - Serial.println("Failed to start DAC1 !"); - while (1); - } } void loop() { if (Serial.available() > 0) { int cmd = Serial.read(); if (cmd != '\n') { - generate_waveform(cmd); + generate_waveform(cmd); } } - + if (dac1.available()) { // Get a free buffer for writing. SampleBuffer buf = dac1.dequeue(); diff --git a/library.properties b/library.properties index 7f5ced9..c719cab 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ name=Arduino_AdvancedAnalog -version=1.0.0 +version=1.5.0 author=Arduino maintainer=Arduino -sentence=Advanced analog functionalities for STM32H7 boards -paragraph= +sentence=Advanced Analog library for STM32H7 boards +paragraph=Enables high performance DAC, ADC and I2S applications on boards based on the STM32H7 microcontrollers category=Other url=https://github.com/arduino-libraries/Arduino_AdvancedAnalog architectures=mbed,mbed_portenta,mbed_nicla,mbed_giga diff --git a/src/AdvancedADC.cpp b/src/AdvancedADC.cpp index 9078be4..7c5b26e 100644 --- a/src/AdvancedADC.cpp +++ b/src/AdvancedADC.cpp @@ -22,6 +22,7 @@ #include "AdvancedADC.h" #define ADC_NP ((ADCName) NC) +#define ADC_PIN_ALT_MASK (uint32_t) (ALT0 | ALT1 ) struct adc_descr_t { ADC_HandleTypeDef adc; @@ -29,18 +30,19 @@ struct adc_descr_t { IRQn_Type dma_irqn; TIM_HandleTypeDef tim; uint32_t tim_trig; - uint32_t pin_alt; - DMABufferPool *pool; + DMAPool *pool; DMABuffer *dmabuf[2]; }; +static uint32_t adc_pin_alt[3] = {0, ALT0, ALT1}; + static adc_descr_t adc_descr_all[3] = { {{ADC1}, {DMA1_Stream1, {DMA_REQUEST_ADC1}}, DMA1_Stream1_IRQn, {TIM1}, ADC_EXTERNALTRIG_T1_TRGO, - 0, nullptr, {nullptr, nullptr}}, + nullptr, {nullptr, nullptr}}, {{ADC2}, {DMA1_Stream2, {DMA_REQUEST_ADC2}}, DMA1_Stream2_IRQn, {TIM2}, ADC_EXTERNALTRIG_T2_TRGO, - ALT0, nullptr, {nullptr, nullptr}}, + nullptr, {nullptr, nullptr}}, {{ADC3}, {DMA1_Stream3, {DMA_REQUEST_ADC3}}, DMA1_Stream3_IRQn, {TIM3}, ADC_EXTERNALTRIG_T3_TRGO, - ALT1, nullptr, {nullptr, nullptr}}, + nullptr, {nullptr, nullptr}}, }; static uint32_t ADC_RES_LUT[] = { @@ -95,6 +97,20 @@ static void dac_descr_deinit(adc_descr_t *descr, bool dealloc_pool) { } } +int AdvancedADC::id() { + if (descr) { + ADC_TypeDef *adc = descr->adc.Instance; + if (adc == ADC1) { + return 1; + } else if (adc == ADC2) { + return 2; + } else if (adc == ADC3) { + return 3; + } + } + return -1; +} + bool AdvancedADC::available() { if (descr != nullptr) { return descr->pool->readable(); @@ -108,26 +124,45 @@ DMABuffer &AdvancedADC::read() { while (!available()) { __WFI(); } - return *descr->pool->dequeue(); + return *descr->pool->alloc(DMA_BUFFER_READ); } return NULLBUF; } -int AdvancedADC::begin(uint32_t resolution, uint32_t sample_rate, size_t n_samples, size_t n_buffers) { +int AdvancedADC::begin(uint32_t resolution, uint32_t sample_rate, size_t n_samples, + size_t n_buffers, bool start, adc_sample_time_t sample_time) { + ADCName instance = ADC_NP; - // Sanity checks. - if (resolution >= AN_ARRAY_SIZE(ADC_RES_LUT)) { + if (resolution >= AN_ARRAY_SIZE(ADC_RES_LUT) || (descr && descr->pool)) { return 0; } + // Clear ALTx pin. + for (size_t i=0; ipool == nullptr) { - // Check if the first channel is connected to this ADC. - PinName pin = (PinName) (adc_pins[0] | descr->pin_alt); - instance = (ADCName) pinmap_peripheral(pin, PinMap_ADC); + for (size_t i=0; instance == ADC_NP && ipool == nullptr) { + ADCName tmp_instance = (ADCName) pinmap_peripheral(pin, PinMap_ADC); + if (descr->adc.Instance == ((ADC_TypeDef*) tmp_instance)) { + instance = tmp_instance; + adc_pins[0] = pin; + } + } } } @@ -138,79 +173,168 @@ int AdvancedADC::begin(uint32_t resolution, uint32_t sample_rate, size_t n_sampl } // Configure ADC pins. - for (size_t i=0; ipin_alt); - // All channels must share the same instance; if not, bail out - if (instance != pinmap_peripheral(adc_pins[i], PinMap_ADC)) { - return 0; + pinmap_pinout(adc_pins[0], PinMap_ADC); + + uint8_t ch_init = 1; + for (size_t i=1; ipool = new DMABufferPool(n_samples, n_channels, n_buffers); + descr->pool = new DMAPool(n_samples, n_channels, n_buffers); if (descr->pool == nullptr) { return 0; } - descr->dmabuf[0] = descr->pool->allocate(); - descr->dmabuf[1] = descr->pool->allocate(); + + // Allocate the two DMA buffers used for double buffering. + descr->dmabuf[0] = descr->pool->alloc(DMA_BUFFER_WRITE); + descr->dmabuf[1] = descr->pool->alloc(DMA_BUFFER_WRITE); // Init and config DMA. - hal_dma_config(&descr->dma, descr->dma_irqn, DMA_PERIPH_TO_MEMORY); + if (hal_dma_config(&descr->dma, descr->dma_irqn, DMA_PERIPH_TO_MEMORY) < 0) { + return 0; + } // Init and config ADC. - hal_adc_config(&descr->adc, ADC_RES_LUT[resolution], descr->tim_trig, adc_pins, n_channels); + if (hal_adc_config(&descr->adc, ADC_RES_LUT[resolution], descr->tim_trig, adc_pins, n_channels, sample_time) < 0) { + return 0; + } // Link DMA handle to ADC handle, and start the ADC. __HAL_LINKDMA(&descr->adc, DMA_Handle, descr->dma); - HAL_ADC_Start_DMA(&descr->adc, (uint32_t *) descr->dmabuf[0]->data(), descr->dmabuf[0]->size()); + if (HAL_ADC_Start_DMA(&descr->adc, (uint32_t *) descr->dmabuf[0]->data(), descr->dmabuf[0]->size()) != HAL_OK) { + return 0; + } // Re/enable DMA double buffer mode. + HAL_NVIC_DisableIRQ(descr->dma_irqn); hal_dma_enable_dbm(&descr->dma, descr->dmabuf[0]->data(), descr->dmabuf[1]->data()); + HAL_NVIC_EnableIRQ(descr->dma_irqn); + + if (start) { + return this->start(sample_rate); + } - // Init, config and start the ADC timer. + return 1; +} + +int AdvancedADC::start(uint32_t sample_rate){ + // Initialize and configure the ADC timer. hal_tim_config(&descr->tim, sample_rate); - HAL_TIM_Base_Start(&descr->tim); + + // Start the ADC timer. Note, if dual ADC mode is enabled, + // this will also start ADC2. + if (HAL_TIM_Base_Start(&descr->tim) != HAL_OK) { + return 0; + } + return 1; } -int AdvancedADC::stop() -{ +int AdvancedADC::stop() { dac_descr_deinit(descr, true); return 1; } -AdvancedADC::~AdvancedADC() -{ +void AdvancedADC::clear() { + if (descr && descr->pool) { + descr->pool->flush(); + } +} + +size_t AdvancedADC::channels() { + return n_channels; +} + +AdvancedADC::~AdvancedADC() { dac_descr_deinit(descr, true); } +int AdvancedADCDual::begin(uint32_t resolution, uint32_t sample_rate, size_t n_samples, + size_t n_buffers, adc_sample_time_t sample_time) { + // The two ADCs must have the same number of channels. + if (adc1.channels() != adc2.channels()) { + return 0; + } + + // Configure the ADCs. + if (!adc1.begin(resolution, sample_rate, n_samples, n_buffers, false, sample_time)) { + return 0; + } + + if (!adc2.begin(resolution, sample_rate, n_samples, n_buffers, false, sample_time)) { + adc1.stop(); + return 0; + } + + // Note only ADC1 (master) and ADC2 can be used in dual mode. + if (adc1.id() != 1 || adc2.id() != 2) { + adc1.stop(); + adc2.stop(); + return 0; + } + + // Enable dual ADC mode. + hal_adc_enable_dual_mode(true); + + // Start ADC1, note ADC2 is also automatically started. + return adc1.start(sample_rate); +} + +int AdvancedADCDual:: stop() { + adc1.stop(); + adc2.stop(); + // Disable dual mode. + hal_adc_enable_dual_mode(false); + return 1; +} + +AdvancedADCDual::~AdvancedADCDual() { + stop(); +} + extern "C" { + void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *adc) { adc_descr_t *descr = adc_descr_get(adc->Instance); // NOTE: CT bit is inverted, to get the DMA buffer that's Not currently in use. size_t ct = ! hal_dma_get_ct(&descr->dma); - // Timestamp the buffer. TODO: Should move to timer IRQ. - descr->dmabuf[ct]->timestamp(HAL_GetTick()); + // Timestamp the buffer. + descr->dmabuf[ct]->timestamp(us_ticker_read()); if (descr->pool->writable()) { // Make sure any cached data is discarded. descr->dmabuf[ct]->invalidate(); - // Move current DMA buffer to ready queue. - descr->pool->enqueue(descr->dmabuf[ct]); - + descr->dmabuf[ct]->release(); // Allocate a new free buffer. - descr->dmabuf[ct] = descr->pool->allocate(); - + descr->dmabuf[ct] = descr->pool->alloc(DMA_BUFFER_WRITE); // Currently, all multi-channel buffers are interleaved. if (descr->dmabuf[ct]->channels() > 1) { - descr->dmabuf[ct]->setflags(DMA_BUFFER_INTRLVD); + descr->dmabuf[ct]->set_flags(DMA_BUFFER_INTRLVD); } } else { - descr->dmabuf[ct]->setflags(DMA_BUFFER_DISCONT); + descr->dmabuf[ct]->set_flags(DMA_BUFFER_DISCONT); } // Update the next DMA target pointer. diff --git a/src/AdvancedADC.h b/src/AdvancedADC.h index 647250b..a290d4d 100644 --- a/src/AdvancedADC.h +++ b/src/AdvancedADC.h @@ -17,15 +17,24 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#include -#include "DMABuffer.h" -#include "AdvancedAnalog.h" +#ifndef __ADVANCED_ADC_H__ +#define __ADVANCED_ADC_H__ -#ifndef ARDUINO_ADVANCED_ADC_H_ -#define ARDUINO_ADVANCED_ADC_H_ +#include "AdvancedAnalog.h" struct adc_descr_t; +typedef enum { + AN_ADC_SAMPLETIME_1_5 = ADC_SAMPLETIME_1CYCLE_5, + AN_ADC_SAMPLETIME_2_5 = ADC_SAMPLETIME_2CYCLES_5, + AN_ADC_SAMPLETIME_8_5 = ADC_SAMPLETIME_8CYCLES_5, + AN_ADC_SAMPLETIME_16_5 = ADC_SAMPLETIME_16CYCLES_5, + AN_ADC_SAMPLETIME_32_5 = ADC_SAMPLETIME_32CYCLES_5, + AN_ADC_SAMPLETIME_64_5 = ADC_SAMPLETIME_64CYCLES_5, + AN_ADC_SAMPLETIME_387_5 = ADC_SAMPLETIME_387CYCLES_5, + AN_ADC_SAMPLETIME_810_5 = ADC_SAMPLETIME_810CYCLES_5, +} adc_sample_time_t; + class AdvancedADC { private: size_t n_channels; @@ -36,17 +45,53 @@ class AdvancedADC { template AdvancedADC(pin_size_t p0, T ... args): n_channels(0), descr(nullptr) { static_assert(sizeof ...(args) < AN_MAX_ADC_CHANNELS, - "A maximum of 5 channels can be sampled successively."); + "A maximum of 16 channels can be sampled successively."); for (auto p : {p0, args...}) { adc_pins[n_channels++] = analogPinToPinName(p); } } + AdvancedADC(): n_channels(0), descr(nullptr) { + } ~AdvancedADC(); + int id(); bool available(); SampleBuffer read(); - int begin(uint32_t resolution, uint32_t sample_rate, size_t n_samples, size_t n_buffers); + int begin(uint32_t resolution, uint32_t sample_rate, size_t n_samples, + size_t n_buffers, bool start=true, adc_sample_time_t sample_time=AN_ADC_SAMPLETIME_8_5); + int begin(uint32_t resolution, uint32_t sample_rate, size_t n_samples, + size_t n_buffers, size_t n_pins, pin_size_t *pins, bool start=true, + adc_sample_time_t sample_time=AN_ADC_SAMPLETIME_8_5) { + if (n_pins > AN_MAX_ADC_CHANNELS) { + n_pins = AN_MAX_ADC_CHANNELS; + } + for (size_t i = 0; i < n_pins; ++i) { + adc_pins[i] = analogPinToPinName(pins[i]); + } + + n_channels = n_pins; + return begin(resolution, sample_rate, n_samples, n_buffers, start, sample_time); + } + int start(uint32_t sample_rate); + int stop(); + void clear(); + size_t channels(); +}; + +class AdvancedADCDual { + private: + AdvancedADC &adc1; + AdvancedADC &adc2; + size_t n_channels; + + public: + AdvancedADCDual(AdvancedADC &adc1_in, AdvancedADC &adc2_in): + n_channels(0), adc1(adc1_in), adc2(adc2_in) { + } + ~AdvancedADCDual(); + int begin(uint32_t resolution, uint32_t sample_rate, size_t n_samples, + size_t n_buffers, adc_sample_time_t sample_time=AN_ADC_SAMPLETIME_8_5); int stop(); }; -#endif /* ARDUINO_ADVANCED_ADC_H_ */ +#endif // __ADVANCED_ADC_H__ diff --git a/src/AdvancedAnalog.h b/src/AdvancedAnalog.h index c9c55f6..0ffcb2c 100644 --- a/src/AdvancedAnalog.h +++ b/src/AdvancedAnalog.h @@ -21,7 +21,7 @@ #define __ADVANCED_ANALOG_H__ #include "Arduino.h" -#include "DMABuffer.h" +#include "api/DMAPool.h" #include "pinDefinitions.h" enum { @@ -35,7 +35,7 @@ enum { typedef uint16_t Sample; // Sample type used for ADC/DAC. typedef DMABuffer &SampleBuffer; -#define AN_MAX_ADC_CHANNELS (5) +#define AN_MAX_ADC_CHANNELS (16) #define AN_MAX_DAC_CHANNELS (1) #define AN_ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) diff --git a/src/AdvancedDAC.cpp b/src/AdvancedDAC.cpp index fc04461..1872489 100644 --- a/src/AdvancedDAC.cpp +++ b/src/AdvancedDAC.cpp @@ -29,18 +29,20 @@ struct dac_descr_t { TIM_HandleTypeDef tim; uint32_t tim_trig; uint32_t resolution; - DMABufferPool *pool; + uint32_t dmaudr_flag; + DMAPool *pool; DMABuffer *dmabuf[2]; + bool loop_mode; }; // NOTE: Both DAC channel descriptors share the same DAC handle. static DAC_HandleTypeDef dac = {0}; static dac_descr_t dac_descr_all[] = { - {&dac, DAC_CHANNEL_1, {DMA1_Stream4, {DMA_REQUEST_DAC1_CH1}}, DMA1_Stream4_IRQn, - {TIM4}, DAC_TRIGGER_T4_TRGO, DAC_ALIGN_12B_R, nullptr, {nullptr, nullptr}}, - {&dac, DAC_CHANNEL_2, {DMA1_Stream5, {DMA_REQUEST_DAC1_CH2}}, DMA1_Stream5_IRQn, - {TIM5}, DAC_TRIGGER_T5_TRGO, DAC_ALIGN_12B_R, nullptr, {nullptr, nullptr}}, + {&dac, DAC_CHANNEL_1, {DMA1_Stream4, {DMA_REQUEST_DAC1_CH1}}, DMA1_Stream4_IRQn, {TIM4}, + DAC_TRIGGER_T4_TRGO, DAC_ALIGN_12B_R, DAC_FLAG_DMAUDR1, nullptr, {nullptr, nullptr}, false}, + {&dac, DAC_CHANNEL_2, {DMA1_Stream5, {DMA_REQUEST_DAC1_CH2}}, DMA1_Stream5_IRQn, {TIM5}, + DAC_TRIGGER_T5_TRGO, DAC_ALIGN_12B_R, DAC_FLAG_DMAUDR2, nullptr, {nullptr, nullptr}, false}, }; static uint32_t DAC_RES_LUT[] = { @@ -73,10 +75,12 @@ static dac_descr_t *dac_descr_get(uint32_t channel) { } static void dac_descr_deinit(dac_descr_t *descr, bool dealloc_pool) { - if (descr) { + if (descr != nullptr) { HAL_TIM_Base_Stop(&descr->tim); HAL_DAC_Stop_DMA(descr->dac, descr->channel); + __HAL_DAC_CLEAR_FLAG(descr->dac, descr->dmaudr_flag); + for (size_t i=0; idmabuf); i++) { if (descr->dmabuf[i]) { descr->dmabuf[i]->release(); @@ -89,13 +93,17 @@ static void dac_descr_deinit(dac_descr_t *descr, bool dealloc_pool) { delete descr->pool; } descr->pool = nullptr; + } else { + descr->pool->flush(); } - } } bool AdvancedDAC::available() { if (descr != nullptr) { + if (__HAL_DAC_GET_FLAG(descr->dac, descr->dmaudr_flag)) { + dac_descr_deinit(descr, false); + } return descr->pool->writable(); } return false; @@ -107,35 +115,45 @@ DMABuffer &AdvancedDAC::dequeue() { while (!available()) { __WFI(); } - return *descr->pool->allocate(); + return *descr->pool->alloc(DMA_BUFFER_WRITE); } return NULLBUF; } void AdvancedDAC::write(DMABuffer &dmabuf) { + static uint32_t buf_count = 0; + + if (descr == nullptr) { + return; + } + // Make sure any cached data is flushed. dmabuf.flush(); - descr->pool->enqueue(&dmabuf); + dmabuf.release(); - if (descr->dmabuf[0] == nullptr && descr->pool->readable() > 2) { - descr->dmabuf[0] = descr->pool->dequeue(); - descr->dmabuf[1] = descr->pool->dequeue(); + if (!descr->dmabuf[0] && + ((descr->loop_mode && !descr->pool->writable()) || + (!descr->loop_mode && (++buf_count % 3 == 0)))) { + descr->dmabuf[0] = descr->pool->alloc(DMA_BUFFER_READ); + descr->dmabuf[1] = descr->pool->alloc(DMA_BUFFER_READ); // Start DAC DMA. HAL_DAC_Start_DMA(descr->dac, descr->channel, (uint32_t *) descr->dmabuf[0]->data(), descr->dmabuf[0]->size(), descr->resolution); // Re/enable DMA double buffer mode. + HAL_NVIC_DisableIRQ(descr->dma_irqn); hal_dma_enable_dbm(&descr->dma, descr->dmabuf[0]->data(), descr->dmabuf[1]->data()); + HAL_NVIC_EnableIRQ(descr->dma_irqn); // Start trigger timer. HAL_TIM_Base_Start(&descr->tim); } } -int AdvancedDAC::begin(uint32_t resolution, uint32_t frequency, size_t n_samples, size_t n_buffers) { +int AdvancedDAC::begin(uint32_t resolution, uint32_t frequency, size_t n_samples, size_t n_buffers, bool loop) { // Sanity checks. - if (resolution >= AN_ARRAY_SIZE(DAC_RES_LUT)) { + if (resolution >= AN_ARRAY_SIZE(DAC_RES_LUT) || descr != nullptr) { return 0; } @@ -147,15 +165,18 @@ int AdvancedDAC::begin(uint32_t resolution, uint32_t frequency, size_t n_samples uint32_t function = pinmap_function(dac_pins[0], PinMap_DAC); descr = dac_descr_get(DAC_CHAN_LUT[STM_PIN_CHANNEL(function) - 1]); - if (descr == nullptr || descr->pool) { + if (descr == nullptr) { return 0; } // Allocate DMA buffer pool. - descr->pool = new DMABufferPool(n_samples, n_channels, n_buffers); + descr->pool = new DMAPool(n_samples, n_channels, n_buffers); if (descr->pool == nullptr) { + descr = nullptr; return 0; } + + descr->loop_mode = loop; descr->resolution = DAC_RES_LUT[resolution]; // Init and config DMA. @@ -176,14 +197,23 @@ int AdvancedDAC::begin(uint32_t resolution, uint32_t frequency, size_t n_samples return 1; } -int AdvancedDAC::stop() -{ - dac_descr_deinit(descr, true); +int AdvancedDAC::stop() { + if (descr != nullptr) { + dac_descr_deinit(descr, true); + descr = nullptr; + } return 1; } -AdvancedDAC::~AdvancedDAC() -{ +int AdvancedDAC::frequency(uint32_t const frequency) { + if (descr != nullptr) { + // Reconfigure the trigger timer. + dac_descr_deinit(descr, false); + hal_tim_config(&descr->tim, frequency); + } +} + +AdvancedDAC::~AdvancedDAC() { dac_descr_deinit(descr, true); } @@ -191,13 +221,18 @@ extern "C" { void DAC_DMAConvCplt(DMA_HandleTypeDef *dma, uint32_t channel) { dac_descr_t *descr = dac_descr_get(channel); + // Release the DMA buffer that was just done, allocate a new one, // and update the next DMA memory address target. - if (descr->pool->readable()) { + if (descr && descr->pool->readable()) { // NOTE: CT bit is inverted, to get the DMA buffer that's Not currently in use. size_t ct = ! hal_dma_get_ct(dma); descr->dmabuf[ct]->release(); - descr->dmabuf[ct] = descr->pool->dequeue(); + descr->dmabuf[ct] = descr->pool->alloc(DMA_BUFFER_READ); + if (descr->loop_mode) { + // Move a buffer from the write queue to the read queue. + descr->pool->alloc(DMA_BUFFER_WRITE)->release(); + } hal_dma_update_memory(dma, descr->dmabuf[ct]->data()); } else { dac_descr_deinit(descr, false); diff --git a/src/AdvancedDAC.h b/src/AdvancedDAC.h index 57707ee..99d6176 100644 --- a/src/AdvancedDAC.h +++ b/src/AdvancedDAC.h @@ -17,12 +17,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#include -#include "DMABuffer.h" -#include "AdvancedAnalog.h" +#ifndef __ADVANCED_DAC_H__ +#define __ADVANCED_DAC_H__ -#ifndef ARDUINO_ADVANCED_DAC_H_ -#define ARDUINO_ADVANCED_DAC_H_ +#include "AdvancedAnalog.h" struct dac_descr_t; @@ -47,8 +45,9 @@ class AdvancedDAC { bool available(); SampleBuffer dequeue(); void write(SampleBuffer dmabuf); - int begin(uint32_t resolution, uint32_t frequency, size_t n_samples=0, size_t n_buffers=0); + int begin(uint32_t resolution, uint32_t frequency, size_t n_samples=0, size_t n_buffers=0, bool loop=false); int stop(); + int frequency(uint32_t const frequency); }; -#endif /* ARDUINO_ADVANCED_DAC_H_ */ +#endif // __ADVANCED_DAC_H__ diff --git a/src/AdvancedI2S.cpp b/src/AdvancedI2S.cpp new file mode 100644 index 0000000..812e035 --- /dev/null +++ b/src/AdvancedI2S.cpp @@ -0,0 +1,409 @@ +/* + This file is part of the Arduino_AdvancedAnalog library. + Copyright (c) 2024 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "Arduino.h" +#include "HALConfig.h" +#include "AdvancedI2S.h" + +struct i2s_descr_t { + I2S_HandleTypeDef i2s; + DMA_HandleTypeDef dmatx; + IRQn_Type dmatx_irqn; + DMAPool *dmatx_pool; + DMABuffer *dmatx_buf[2]; + DMA_HandleTypeDef dmarx; + IRQn_Type dmarx_irqn; + DMAPool *dmarx_pool; + DMABuffer *dmarx_buf[2]; +}; + +static i2s_descr_t i2s_descr_all[] = { + { + {SPI1}, + {DMA2_Stream1, {DMA_REQUEST_SPI1_TX}}, DMA2_Stream1_IRQn, nullptr, {nullptr, nullptr}, + {DMA2_Stream2, {DMA_REQUEST_SPI1_RX}}, DMA2_Stream2_IRQn, nullptr, {nullptr, nullptr}, + }, + { + {SPI2}, + {DMA2_Stream3, {DMA_REQUEST_SPI2_TX}}, DMA2_Stream3_IRQn, nullptr, {nullptr, nullptr}, + {DMA2_Stream4, {DMA_REQUEST_SPI2_RX}}, DMA2_Stream4_IRQn, nullptr, {nullptr, nullptr}, + }, + { + {SPI3}, + {DMA2_Stream5, {DMA_REQUEST_SPI3_TX}}, DMA2_Stream5_IRQn, nullptr, {nullptr, nullptr}, + {DMA2_Stream6, {DMA_REQUEST_SPI3_RX}}, DMA2_Stream6_IRQn, nullptr, {nullptr, nullptr}, + }, +}; + +static const PinMap PinMap_SPI_MCK[] = { + {PC_4, SPI_1, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_NOPULL, GPIO_AF5_SPI1)}, + {PC_6, SPI_2, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_NOPULL, GPIO_AF5_SPI2)}, + {PC_7, SPI_3, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_NOPULL, GPIO_AF6_SPI3)}, + {NC, NC, 0} +}; + +extern "C" { + +void DMA2_Stream1_IRQHandler() { + HAL_DMA_IRQHandler(&i2s_descr_all[0].dmatx); +} + +void DMA2_Stream2_IRQHandler() { + HAL_DMA_IRQHandler(&i2s_descr_all[0].dmarx); +} + +void DMA2_Stream3_IRQHandler() { + HAL_DMA_IRQHandler(&i2s_descr_all[1].dmatx); +} + +void DMA2_Stream4_IRQHandler() { + HAL_DMA_IRQHandler(&i2s_descr_all[1].dmarx); +} + +void DMA2_Stream5_IRQHandler() { + HAL_DMA_IRQHandler(&i2s_descr_all[2].dmatx); +} + +void DMA2_Stream6_IRQHandler() { + HAL_DMA_IRQHandler(&i2s_descr_all[2].dmarx); +} + +} // extern C + +static uint32_t i2s_hal_mode(i2s_mode_t i2s_mode) { + if (i2s_mode == AN_I2S_MODE_OUT) { + return I2S_MODE_MASTER_TX; + } else if (i2s_mode == AN_I2S_MODE_IN) { + return I2S_MODE_MASTER_RX; + } else { + return I2S_MODE_MASTER_FULLDUPLEX; + } +} + +static i2s_descr_t *i2s_descr_get(SPI_TypeDef *i2s) { + if (i2s == SPI1) { + return &i2s_descr_all[0]; + } else if (i2s == SPI2) { + return &i2s_descr_all[1]; + } else if (i2s == SPI3) { + return &i2s_descr_all[2]; + } + return NULL; +} + +static void i2s_descr_deinit(i2s_descr_t *descr, bool dealloc_pool) { + if (descr != nullptr) { + HAL_I2S_DMAStop(&descr->i2s); + + for (size_t i=0; idmatx_buf); i++) { + if (descr->dmatx_buf[i]) { + descr->dmatx_buf[i]->release(); + descr->dmatx_buf[i] = nullptr; + } + } + + for (size_t i=0; idmarx_buf); i++) { + if (descr->dmatx_buf[i]) { + descr->dmatx_buf[i]->release(); + descr->dmatx_buf[i] = nullptr; + } + } + + if (dealloc_pool) { + if (descr->dmarx_pool) { + delete descr->dmarx_pool; + } + descr->dmarx_pool = nullptr; + + if (descr->dmatx_pool) { + delete descr->dmatx_pool; + } + descr->dmatx_pool = nullptr; + } else { + if (descr->dmarx_pool) { + descr->dmarx_pool->flush(); + } + + if (descr->dmatx_pool) { + descr->dmatx_pool->flush(); + } + } + } +} + +static int i2s_start_dma_transfer(i2s_descr_t *descr, i2s_mode_t i2s_mode) { + uint16_t *tx_buf = NULL; + uint16_t *rx_buf = NULL; + uint16_t buf_size = 0; + + if (i2s_mode & AN_I2S_MODE_IN) { + // Start I2S DMA. + descr->dmarx_buf[0] = descr->dmarx_pool->alloc(DMA_BUFFER_WRITE); + descr->dmarx_buf[1] = descr->dmarx_pool->alloc(DMA_BUFFER_WRITE); + rx_buf = (uint16_t *) descr->dmarx_buf[0]->data(); + buf_size = descr->dmarx_buf[0]->size(); + HAL_NVIC_DisableIRQ(descr->dmarx_irqn); + } + + if (i2s_mode & AN_I2S_MODE_OUT) { + descr->dmatx_buf[0] = descr->dmatx_pool->alloc(DMA_BUFFER_READ); + descr->dmatx_buf[1] = descr->dmatx_pool->alloc(DMA_BUFFER_READ); + tx_buf = (uint16_t *) descr->dmatx_buf[0]->data(); + buf_size = descr->dmatx_buf[0]->size(); + HAL_NVIC_DisableIRQ(descr->dmatx_irqn); + } + + // Start I2S DMA. + if (i2s_mode == AN_I2S_MODE_IN) { + if (HAL_I2S_Receive_DMA(&descr->i2s, rx_buf, buf_size) != HAL_OK) { + return 0; + } + } else if (i2s_mode == AN_I2S_MODE_OUT) { + if (HAL_I2S_Transmit_DMA(&descr->i2s, tx_buf, buf_size) != HAL_OK) { + return 0; + } + } else { + if (HAL_I2SEx_TransmitReceive_DMA(&descr->i2s, tx_buf, rx_buf, buf_size) != HAL_OK) { + return 0; + } + } + HAL_I2S_DMAPause(&descr->i2s); + // Re/enable DMA double buffer mode. + if (i2s_mode & AN_I2S_MODE_IN) { + hal_dma_enable_dbm(&descr->dmarx, descr->dmarx_buf[0]->data(), descr->dmarx_buf[1]->data()); + HAL_NVIC_EnableIRQ(descr->dmarx_irqn); + } + + if (i2s_mode & AN_I2S_MODE_OUT) { + hal_dma_enable_dbm(&descr->dmatx, descr->dmatx_buf[0]->data(), descr->dmatx_buf[1]->data()); + HAL_NVIC_EnableIRQ(descr->dmatx_irqn); + } + HAL_I2S_DMAResume(&descr->i2s); + return 1; +} + +bool AdvancedI2S::available() { + if (descr != nullptr) { + if (i2s_mode == AN_I2S_MODE_IN && descr->dmarx_pool) { + return descr->dmarx_pool->readable(); + } else if (i2s_mode == AN_I2S_MODE_OUT && descr->dmatx_pool) { + return descr->dmatx_pool->writable(); + } else if (descr->dmatx_pool && descr->dmarx_pool) { + return descr->dmarx_pool->readable() && descr->dmatx_pool->writable(); + } + } + return false; +} + +DMABuffer &AdvancedI2S::read() { + static DMABuffer NULLBUF; + if (descr && descr->dmarx_pool) { + while (!descr->dmarx_pool->readable()) { + __WFI(); + } + return *descr->dmarx_pool->alloc(DMA_BUFFER_READ); + } + return NULLBUF; +} + +DMABuffer &AdvancedI2S::dequeue() { + static DMABuffer NULLBUF; + if (descr && descr->dmatx_pool) { + while (!descr->dmatx_pool->writable()) { + __WFI(); + } + return *descr->dmatx_pool->alloc(DMA_BUFFER_WRITE); + } + return NULLBUF; +} + +void AdvancedI2S::write(DMABuffer &dmabuf) { + static uint32_t buf_count = 0; + + if (descr == nullptr) { + return; + } + + // Make sure any cached data is flushed. + dmabuf.flush(); + dmabuf.release(); + + if (descr->dmatx_buf[0] == nullptr && (++buf_count % 3) == 0) { + i2s_start_dma_transfer(descr, i2s_mode); + } +} + +int AdvancedI2S::begin(i2s_mode_t i2s_mode, uint32_t sample_rate, size_t n_samples, size_t n_buffers) { + this->i2s_mode = i2s_mode; + + // Sanity checks. + if (sample_rate < 8000 || sample_rate > 192000 || descr != nullptr) { + return 0; + } + + // Configure I2S pins. + uint32_t i2s = NC; + const PinMap *i2s_pins_map[] = { + PinMap_SPI_SSEL, PinMap_SPI_SCLK, PinMap_SPI_MISO, PinMap_SPI_MOSI, PinMap_SPI_MCK + }; + + for (size_t i=0; idmarx_pool = new DMAPool(n_samples, 2, n_buffers); + if (descr->dmarx_pool == nullptr) { + descr = nullptr; + return 0; + } + // Init and config DMA. + if (hal_dma_config(&descr->dmarx, descr->dmarx_irqn, DMA_PERIPH_TO_MEMORY) != 0) { + return 0; + } + __HAL_LINKDMA(&descr->i2s, hdmarx, descr->dmarx); + } + + if (i2s_mode & AN_I2S_MODE_OUT) { + // Allocate DMA buffer pool. + descr->dmatx_pool = new DMAPool(n_samples, 2, n_buffers); + if (descr->dmatx_pool == nullptr) { + descr = nullptr; + return 0; + } + // Init and config DMA. + if (hal_dma_config(&descr->dmatx, descr->dmatx_irqn, DMA_MEMORY_TO_PERIPH) != 0) { + return 0; + } + __HAL_LINKDMA(&descr->i2s, hdmatx, descr->dmatx); + } + + // Init and config I2S. + if (hal_i2s_config(&descr->i2s, sample_rate, i2s_hal_mode(i2s_mode), i2s_pins[4] != NC) != 0) { + return 0; + } + + if (i2s_mode == AN_I2S_MODE_IN) { + return i2s_start_dma_transfer(descr, i2s_mode); + } + + if (i2s_mode == AN_I2S_MODE_INOUT) { + // The transmit pool has to be primed with a few buffers first, before the + // DMA can be started in full-duplex mode. + for (int i=0; i<3; i++) { + SampleBuffer outbuf = dequeue(); + memset(outbuf.data(), 0, outbuf.bytes()); + write(outbuf); + } + } + return 1; +} + +int AdvancedI2S::stop() { + i2s_descr_deinit(descr, true); + descr = nullptr; + return 1; +} + +AdvancedI2S::~AdvancedI2S() { + i2s_descr_deinit(descr, true); +} + +extern "C" { + +void HAL_I2S_TxCpltCallback(I2S_HandleTypeDef *i2s) { + i2s_descr_t *descr = i2s_descr_get(i2s->Instance); + + if (descr == nullptr) { + return; + } + + // NOTE: CT bit is inverted, to get the DMA buffer that's Not currently in use. + size_t ct = ! hal_dma_get_ct(&descr->dmatx); + + // Release the DMA buffer that was just used, dequeue the next one, and update + // the next DMA memory address target. + if (descr->dmatx_pool->readable()) { + descr->dmatx_buf[ct]->release(); + descr->dmatx_buf[ct] = descr->dmatx_pool->alloc(DMA_BUFFER_READ); + hal_dma_update_memory(&descr->dmatx, descr->dmatx_buf[ct]->data()); + } else { + i2s_descr_deinit(descr, false); + } +} + +void HAL_I2S_RxCpltCallback(I2S_HandleTypeDef *i2s) { + i2s_descr_t *descr = i2s_descr_get(i2s->Instance); + + if (descr == nullptr) { + return; + } + + // NOTE: CT bit is inverted, to get the DMA buffer that's Not currently in use. + size_t ct = ! hal_dma_get_ct(&descr->dmarx); + + // Update the buffer's timestamp. + descr->dmarx_buf[ct]->timestamp(us_ticker_read()); + + // Flush the DMA buffer that was just used, move it to the ready queue, and + // allocate a new one. + if (descr->dmarx_pool->writable()) { + // Make sure any cached data is discarded. + descr->dmarx_buf[ct]->invalidate(); + // Move current DMA buffer to ready queue. + descr->dmarx_buf[ct]->release(); + // Allocate a new free buffer. + descr->dmarx_buf[ct] = descr->dmarx_pool->alloc(DMA_BUFFER_WRITE); + // Currently, all multi-channel buffers are interleaved. + if (descr->dmarx_buf[ct]->channels() > 1) { + descr->dmarx_buf[ct]->set_flags(DMA_BUFFER_INTRLVD); + } + } else { + descr->dmarx_buf[ct]->set_flags(DMA_BUFFER_DISCONT); + } + + // Update the next DMA target pointer. + // NOTE: If the pool was empty, the same buffer is reused. + hal_dma_update_memory(&descr->dmarx, descr->dmarx_buf[ct]->data()); +} + +void HAL_I2SEx_TxRxCpltCallback(I2S_HandleTypeDef *i2s) { + HAL_I2S_RxCpltCallback(i2s); + HAL_I2S_TxCpltCallback(i2s); +} + +} // extern C diff --git a/src/AdvancedI2S.h b/src/AdvancedI2S.h new file mode 100644 index 0000000..99614d7 --- /dev/null +++ b/src/AdvancedI2S.h @@ -0,0 +1,56 @@ +/* + This file is part of the Arduino_AdvancedAnalog library. + Copyright (c) 2023-2024 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef __ADVANCED_I2S_H__ +#define __ADVANCED_I2S_H__ + +#include "AdvancedAnalog.h" + +struct i2s_descr_t; + +typedef enum { + AN_I2S_MODE_IN = (1U << 0U), + AN_I2S_MODE_OUT = (1U << 1U), + AN_I2S_MODE_INOUT = (AN_I2S_MODE_IN | AN_I2S_MODE_OUT), +} i2s_mode_t; + +class AdvancedI2S { + private: + i2s_descr_t *descr; + PinName i2s_pins[5]; + i2s_mode_t i2s_mode; + + public: + AdvancedI2S(PinName ws, PinName ck, PinName sdi, PinName sdo, PinName mck): + descr(nullptr), i2s_pins{ws, ck, sdi, sdo, mck} { + } + + AdvancedI2S() { + } + + ~AdvancedI2S(); + + bool available(); + SampleBuffer read(); + SampleBuffer dequeue(); + void write(SampleBuffer dmabuf); + int begin(i2s_mode_t i2s_mode, uint32_t sample_rate, size_t n_samples, size_t n_buffers); + int stop(); +}; + +#endif // __ADVANCED_I2S_H__ diff --git a/src/Arduino_AdvancedAnalog.h b/src/Arduino_AdvancedAnalog.h index 6aec2b3..ac89e8b 100644 --- a/src/Arduino_AdvancedAnalog.h +++ b/src/Arduino_AdvancedAnalog.h @@ -17,18 +17,12 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef ADVANCEDANALOGREDUX_ARDUINO_ADVANCEDANALOG_H -#define ADVANCEDANALOGREDUX_ARDUINO_ADVANCEDANALOG_H - -/************************************************************************************** - * INCLUDE - **************************************************************************************/ - -#ifdef ARDUINO_PORTENTA_H7_M4 -# error "This library only works on the M7 core of any STM32H747 based board (Portenta H7, Giga)." -#endif +#ifndef __ARDUINO_ADVANCED_ANALOG_H__ +#define __ARDUINO_ADVANCED_ANALOG_H__ #include "AdvancedADC.h" #include "AdvancedDAC.h" +#include "AdvancedI2S.h" +#include "WavReader.h" -#endif /* ADVANCEDANALOGREDUX_ARDUINO_ADVANCEDANALOG_H */ +#endif // __ARDUINO_ADVANCED_ANALOG_H__ diff --git a/src/DMABuffer.h b/src/DMABuffer.h deleted file mode 100644 index ac85ccd..0000000 --- a/src/DMABuffer.h +++ /dev/null @@ -1,210 +0,0 @@ -/* - This file is part of the Arduino_AdvancedAnalog library. - Copyright (c) 2023 Arduino SA. All rights reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef __DMA_BUFFER_H__ -#define __DMA_BUFFER_H__ - -#include "Arduino.h" -#include "Queue.h" - -#ifndef __SCB_DCACHE_LINE_SIZE -#define __SCB_DCACHE_LINE_SIZE 32 -#endif - -template class AlignedAlloc { - // AlignedAlloc allocates extra memory before the aligned memory start, and uses that - // extra memory to stash the pointer returned from malloc. Note memory allocated with - // Aligned::alloc must be free'd later with Aligned::free. - public: - static void *malloc(size_t size) { - void **ptr, *stashed; - size_t offset = A - 1 + sizeof(void *); - if ((A % 2) || !((stashed = ::malloc(size + offset)))) { - return nullptr; - } - ptr = (void **) (((uintptr_t) stashed + offset) & ~(A - 1)); - ptr[-1] = stashed; - return ptr; - } - - static void free(void *ptr) { - if (ptr != nullptr) { - ::free(((void **) ptr)[-1]); - } - } - - static size_t round(size_t size) { - return ((size + (A-1)) & ~(A-1)); - } -}; - -enum { - DMA_BUFFER_DISCONT = (1 << 0), - DMA_BUFFER_INTRLVD = (1 << 1), -}; - -template class DMABufferPool; - -template class DMABuffer { - typedef DMABufferPool Pool; - - private: - Pool *pool; - size_t n_samples; - size_t n_channels; - T *ptr; - uint32_t ts; - uint32_t flags; - - public: - DMABuffer *next; - - DMABuffer(Pool *pool=nullptr, size_t samples=0, size_t channels=0, T *mem=nullptr): - pool(pool), n_samples(samples), n_channels(channels), ptr(mem), ts(0), flags(0), next(nullptr) { - } - - T *data() { - return ptr; - } - - size_t size() { - return n_samples * n_channels; - } - - size_t bytes() { - return n_samples * n_channels * sizeof(T); - } - - void flush() { - if (ptr) { - SCB_CleanDCache_by_Addr(data(), bytes()); - } - } - - void invalidate() { - if (ptr) { - SCB_InvalidateDCache_by_Addr(data(), bytes()); - } - } - - uint32_t timestamp() { - return ts; - } - - void timestamp(uint32_t ts) { - this->ts = ts; - } - - uint32_t channels() { - return n_channels; - } - - void release() { - if (pool && ptr) { - pool->release(this); - } - } - - void setflags(uint32_t f) { - flags |= f; - } - - bool getflags(uint32_t f) { - return flags & f; - } - - void clrflags(uint32_t f=0xFFFFFFFFU) { - flags &= (~f); - } - - T& operator[](size_t i) - { - assert(ptr && i < size()); - return ptr[i]; - } - - const T& operator[](size_t i) const - { - assert(ptr && i < size()); - return ptr[i]; - } - - operator bool() const { - return (ptr != nullptr); - } -}; - -template class DMABufferPool { - private: - Queue*> wr_queue; - Queue*> rd_queue; - std::unique_ptr[]> buffers; - std::unique_ptr::free)> pool; - - public: - DMABufferPool(size_t n_samples, size_t n_channels, size_t n_buffers): - buffers(nullptr), pool(nullptr, AlignedAlloc::free) { - // Round up to next multiple of alignment. - size_t bufsize = AlignedAlloc::round(n_samples * n_channels * sizeof(T)); - if (bufsize) { - // Allocate non-aligned memory for the DMA buffers objects. - buffers.reset(new DMABuffer[n_buffers]); - - // Allocate aligned memory pool for DMA buffers pointers. - pool.reset((uint8_t *) AlignedAlloc::malloc(n_buffers * bufsize)); - } - if (buffers && pool) { - // Init DMA buffers using aligned pointers to dma buffers memory. - for (size_t i=0; i(this, n_samples, n_channels, (T *) &pool.get()[i * bufsize]); - wr_queue.push(&buffers[i]); - } - } - } - - size_t writable() { - return wr_queue.size(); - } - - size_t readable() { - return rd_queue.size(); - } - - DMABuffer *allocate() { - // Get a DMA buffer from the free queue. - return wr_queue.pop(); - } - - void release(DMABuffer *buf) { - // Return DMA buffer to the free queue. - buf->clrflags(); - wr_queue.push(buf); - } - - void enqueue(DMABuffer *buf) { - // Add DMA buffer to the ready queue. - rd_queue.push(buf); - } - - DMABuffer *dequeue() { - // Return a DMA buffer from the ready queue. - return rd_queue.pop(); - } -}; -#endif //__DMA_BUFFER_H__ diff --git a/src/HALConfig.cpp b/src/HALConfig.cpp index 7c2de12..2493211 100644 --- a/src/HALConfig.cpp +++ b/src/HALConfig.cpp @@ -71,10 +71,11 @@ int hal_tim_config(TIM_HandleTypeDef *tim, uint32_t t_freq) { int hal_dma_config(DMA_HandleTypeDef *dma, IRQn_Type irqn, uint32_t direction) { // Enable DMA clock __HAL_RCC_DMA1_CLK_ENABLE(); + __HAL_RCC_DMA2_CLK_ENABLE(); // DMA Init dma->Init.Mode = DMA_DOUBLE_BUFFER_M0; - dma->Init.Priority = DMA_PRIORITY_LOW; + dma->Init.Priority = DMA_PRIORITY_VERY_HIGH; dma->Init.Direction = direction; dma->Init.FIFOMode = DMA_FIFOMODE_ENABLE; dma->Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; @@ -91,7 +92,7 @@ int hal_dma_config(DMA_HandleTypeDef *dma, IRQn_Type irqn, uint32_t direction) { } // NVIC configuration for DMA Input data interrupt. - HAL_NVIC_SetPriority(irqn, 1, 0); + HAL_NVIC_SetPriority(irqn, 0, 0); HAL_NVIC_EnableIRQ(irqn); return 0; @@ -163,10 +164,14 @@ int hal_dac_config(DAC_HandleTypeDef *dac, uint32_t channel, uint32_t trigger) { } static uint32_t ADC_RANK_LUT[] = { - ADC_REGULAR_RANK_1, ADC_REGULAR_RANK_2, ADC_REGULAR_RANK_3, ADC_REGULAR_RANK_4, ADC_REGULAR_RANK_5 + ADC_REGULAR_RANK_1, ADC_REGULAR_RANK_2, ADC_REGULAR_RANK_3, ADC_REGULAR_RANK_4, + ADC_REGULAR_RANK_5, ADC_REGULAR_RANK_6, ADC_REGULAR_RANK_7, ADC_REGULAR_RANK_8, + ADC_REGULAR_RANK_9, ADC_REGULAR_RANK_10, ADC_REGULAR_RANK_11, ADC_REGULAR_RANK_12, + ADC_REGULAR_RANK_13, ADC_REGULAR_RANK_14, ADC_REGULAR_RANK_15, ADC_REGULAR_RANK_16 }; -int hal_adc_config(ADC_HandleTypeDef *adc, uint32_t resolution, uint32_t trigger, PinName *adc_pins, uint32_t n_channels) { +int hal_adc_config(ADC_HandleTypeDef *adc, uint32_t resolution, uint32_t trigger, + PinName *adc_pins, uint32_t n_channels, uint32_t sample_time) { // Set ADC clock source. __HAL_RCC_ADC_CONFIG(RCC_ADCCLKSOURCE_CLKP); @@ -195,22 +200,80 @@ int hal_adc_config(ADC_HandleTypeDef *adc, uint32_t resolution, uint32_t trigger adc->Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING; adc->Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_CIRCULAR; - HAL_ADC_Init(adc); - HAL_ADCEx_Calibration_Start(adc, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED); + if (HAL_ADC_Init(adc) != HAL_OK + || HAL_ADCEx_Calibration_Start(adc, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED) != HAL_OK) { + return -1; + } ADC_ChannelConfTypeDef sConfig = {0}; sConfig.Offset = 0; sConfig.OffsetNumber = ADC_OFFSET_NONE; sConfig.SingleDiff = ADC_SINGLE_ENDED; - sConfig.SamplingTime = ADC_SAMPLETIME_8CYCLES_5; + sConfig.SamplingTime = sample_time; for (size_t rank=0; rankInstance == SPI1) { + __HAL_RCC_SPI1_CLK_ENABLE(); + } else if (i2s->Instance == SPI2) { + __HAL_RCC_SPI2_CLK_ENABLE(); + } else if (i2s->Instance == SPI3) { + __HAL_RCC_SPI3_CLK_ENABLE(); + } + + i2s->Init.Mode = mode; + i2s->Init.Standard = I2S_STANDARD_PHILIPS; + i2s->Init.DataFormat = I2S_DATAFORMAT_16B_EXTENDED; + i2s->Init.MCLKOutput = mck_enable ? I2S_MCLKOUTPUT_ENABLE : I2S_MCLKOUTPUT_DISABLE; + i2s->Init.AudioFreq = sample_rate; + i2s->Init.CPOL = I2S_CPOL_LOW; + i2s->Init.FirstBit = I2S_FIRSTBIT_MSB; + i2s->Init.WSInversion = I2S_WS_INVERSION_DISABLE; + i2s->Init.Data24BitAlignment = I2S_DATA_24BIT_ALIGNMENT_RIGHT; + i2s->Init.MasterKeepIOState = I2S_MASTER_KEEP_IO_STATE_DISABLE; + + HAL_I2S_DeInit(i2s); + if (HAL_I2S_Init(i2s) != HAL_OK) { + return -1; + } + return 0; +} diff --git a/src/HALConfig.h b/src/HALConfig.h index f366557..a3fc0fd 100644 --- a/src/HALConfig.h +++ b/src/HALConfig.h @@ -29,6 +29,9 @@ size_t hal_dma_get_ct(DMA_HandleTypeDef *dma); void hal_dma_enable_dbm(DMA_HandleTypeDef *dma, void *m0 = nullptr, void *m1 = nullptr); void hal_dma_update_memory(DMA_HandleTypeDef *dma, void *addr); int hal_dac_config(DAC_HandleTypeDef *dac, uint32_t channel, uint32_t trigger); -int hal_adc_config(ADC_HandleTypeDef *adc, uint32_t resolution, uint32_t trigger, PinName *adc_pins, uint32_t n_channels); +int hal_adc_config(ADC_HandleTypeDef *adc, uint32_t resolution, uint32_t trigger, + PinName *adc_pins, uint32_t n_channels, uint32_t sample_time); +int hal_adc_enable_dual_mode(bool enable); +int hal_i2s_config(I2S_HandleTypeDef *i2s, uint32_t sample_rate, uint32_t mode, bool mck_enable); #endif // __HAL_CONFIG_H__ diff --git a/src/Queue.h b/src/Queue.h deleted file mode 100644 index de2dc1b..0000000 --- a/src/Queue.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - This file is part of the Arduino_AdvancedAnalog library. - Copyright (c) 2023 Arduino SA. All rights reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef __QUEUE_H__ -#define __QUEUE_H__ - -#include "Arduino.h" - -template class Queue { - private: - T head; - T tail; - size_t _size; - - public: - Queue(): head(nullptr), tail(nullptr), _size(0) { - - } - - size_t size() { - return _size; - } - - bool empty() { - return !_size; - } - - void push(T value) { - _size++; - value->next = nullptr; - if (head == nullptr) { - head = value; - } - if (tail) { - tail->next = value; - } - tail = value; - } - - T pop() { - _size--; - T value = head; - if (head) { - head = head->next; - } - if (head == nullptr) { - tail = nullptr; - } - return value; - } -}; -#endif //__QUEUE_H__ diff --git a/src/WavReader.cpp b/src/WavReader.cpp new file mode 100644 index 0000000..9040d1c --- /dev/null +++ b/src/WavReader.cpp @@ -0,0 +1,111 @@ +/* + This file is part of the Arduino_AdvancedAnalog library. + Copyright (c) 2024 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "Arduino.h" +#include "WavReader.h" + +WavReader::~WavReader() { + stop(); +} + +int WavReader::begin(const char *path, size_t n_samples, size_t n_buffers, bool loop) { + this->loop = loop; + + if ((file = fopen(path, "rb")) == nullptr) { + return 0; + } + + // Read file header + fread(&header, sizeof(header), 1, file); + + // Add more sanity checks if needed. + if (memcmp(header.chunk_id, "RIFF", 4) != 0 || + memcmp(header.format, "WAVEfmt", 7) != 0 || + ((sizeof(Sample) * 8) < header.bits_per_sample) || + (n_samples * header.num_channels) > sample_count()) { + stop(); + return 0; + } + + // Allocate the DMA buffer pool. + pool = new DMAPool(n_samples, header.num_channels, n_buffers); + if (pool == nullptr) { + stop(); + return 0; + } + return 1; +} + +void WavReader::stop() { + if (file) { + fclose(file); + } + if (pool) { + delete pool; + } + pool = nullptr; + file = nullptr; +} + +bool WavReader::available() { + if (file != nullptr && pool != nullptr) { + return pool->writable(); + } + return false; +} + +DMABuffer &WavReader::read() { + while (!available()) { + __WFI(); + } + + DMABuffer *buf = pool->alloc(DMA_BUFFER_WRITE); + size_t offset = 0; + Sample *rawbuf = buf->data(); + size_t n_samples = buf->size(); + + while (offset < n_samples) { + offset += fread(&rawbuf[offset], sizeof(Sample), n_samples - offset, file); + if (offset < n_samples) { + if (loop) { + rewind(); + } else { + for (size_t i=offset; iclr_flags(); + buf->set_flags(DMA_BUFFER_READ); + return *buf; +} + +int WavReader::rewind() { + if (file == nullptr) { + return 0; + } + if (fseek(file, sizeof(WavHeader), SEEK_SET) == -1) { + return 0; + } + return 1; +} diff --git a/src/WavReader.h b/src/WavReader.h new file mode 100644 index 0000000..2bbc840 --- /dev/null +++ b/src/WavReader.h @@ -0,0 +1,74 @@ +/* + This file is part of the Arduino_AdvancedAnalog library. + Copyright (c) 2024 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __ADVANCED_WAV_READER_H__ +#define __ADVANCED_WAV_READER_H__ + +#include "AdvancedAnalog.h" + +class WavReader { + typedef struct { + char chunk_id[4]; + unsigned int chunk_size; + char format[4]; + char subchunk1_id[4]; + unsigned int subchunk1_size; + unsigned short audio_format; + unsigned short num_channels; + unsigned int sample_rate; + unsigned int byte_rate; + unsigned short block_align; + unsigned short bits_per_sample; + char subchunk2_id[4]; + unsigned int subchunk2_size; + } WavHeader; + + private: + FILE *file; + bool loop; + WavHeader header; + DMAPool *pool; + + public: + WavReader(): file(nullptr), loop(false), pool(nullptr) { + } + ~WavReader(); + size_t channels() { + return header.num_channels; + } + + size_t resolution() { + return header.bits_per_sample; + } + + size_t sample_rate() { + return header.sample_rate; + } + + size_t sample_count() { + return (header.subchunk2_size * 8) / header.bits_per_sample; + } + + int begin(const char *path, size_t n_samples, size_t n_buffers, bool loop=false); + void stop(); + bool available(); + SampleBuffer read(); + int rewind(); +}; +#endif // __ADVANCED_WAV_READER_H__