diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index d227d986..2c5020f4 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -20,8 +20,8 @@ jobs: - name: Run codespell uses: codespell-project/actions-codespell@master with: - skip: ./pico-extras,./ArduinoCore-API,./libraries/ESP8266SdFat,./libraries/Adafruit_TinyUSB_Arduino,./libraries/LittleFS/lib,./tools/pyserial,./pico-sdk,./.github,./docs/i2s.rst,./cores/rp2040/api,./libraries/FreeRTOS - ignore_words_list: ser,DOUT + skip: ./ArduinoCore-API,./libraries/ESP8266SdFat,./libraries/Adafruit_TinyUSB_Arduino,./libraries/LittleFS/lib,./tools/pyserial,./pico-sdk,./.github,./docs/i2s.rst,./cores/rp2040/api,./libraries/FreeRTOS + ignore_words_list: ser,dout # Consistent style astyle: @@ -81,8 +81,6 @@ jobs: run: | cd pico-sdk git submodule update --init - cd ../pico-extras - git submodule update --init cd .. bash ./tests/build.sh @@ -111,8 +109,6 @@ jobs: run: | cd pico-sdk git submodule update --init - cd ../pico-extras - git submodule update --init cd .. bash ./tests/build-tinyusb.sh @@ -147,8 +143,6 @@ jobs: try { Get-Command python3 } catch { copy (get-command python).source (get-command python).source.Replace("python.exe", "python3.exe") } cd pico-sdk git submodule update --init - cd ../pico-extras - git submodule update --init cd .. bash ./tests/build.sh @@ -181,8 +175,6 @@ jobs: run: | cd pico-sdk git submodule update --init - cd ../pico-extras - git submodule update --init cd .. bash ./tests/build.sh diff --git a/.gitmodules b/.gitmodules index 3116128d..5c045088 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,9 +13,6 @@ [submodule "libraries/SdFat"] path = libraries/ESP8266SdFat url = https://github.com/earlephilhower/ESP8266SdFat.git -[submodule "pico-extras"] - path = pico-extras - url = https://github.com/raspberrypi/pico-extras.git [submodule "libraries/Keyboard"] path = libraries/Keyboard url = https://github.com/earlephilhower/Keyboard diff --git a/README.md b/README.md index fec653fa..873c9ac0 100644 --- a/README.md +++ b/README.md @@ -70,8 +70,6 @@ cd ~/Arduino/hardware/pico/rp2040 git submodule update --init cd pico-sdk git submodule update --init -cd ../pico-extras -git submodule update --init cd ../tools python3 ./get.py ````` @@ -145,12 +143,13 @@ The installed tools include a version of OpenOCD (in the pqt-openocd directory) * Multicore support (setup1() and loop1()) * Overclocking and underclocking from the menus * digitalWrite/Read, shiftIn/Out, tone, analogWrite(PWM)/Read, temperature -* Peripherals: SPI master, Wire(I2C) master/slave, dual UART, emulated EEPROM, I2S audio output, Servo +* Peripherals: SPI master, Wire(I2C) master/slave, dual UART, emulated EEPROM, I2S audio input, I2S audio output, Servo * printf (i.e. debug) output over USB serial The RP2040 PIO state machines (SMs) are used to generate jitter-free: * Servos * Tones +* I2S Input * I2S Output * Software UARTs (Serial ports) @@ -168,7 +167,7 @@ If you want to contribute or have bugfixes, drop me a note at `_ library -for a working example, or look at the included sample tone generator. +Sample Writing/Reading API +-------------------------- +Because I2S streams consist of a natural left and right sample, it is often +convenient to write or read both with a single call. The following calls +allow applications to read or write both samples at the same time, and +explicitly indicate the bit widths required (to avoid potential issues with +type conversion on calls). + +size_t write8(int8_t l, int8_t r) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Writes a left and right 8-bit sample to the I2S buffers. Blocks until space +is available. + +size_t write16(int16_t l, int16_t r) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Writes a left and right 16-bit sample to the I2S buffers. Blocks until space +is available. + +size_t write24(int32_t l, int32_t r) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Writes a left and right 24-bit sample to the I2S buffers. See note below +about 24-bit mode. Blocks until space is available. + +size_t write32(int32_t l, int32_t r) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Writes a left and right 32-bit sample to the I2S buffers. Blocks until space +is available. + +bool read8(int8_t \*l, int8_t \*r) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Reads a left and right 8-bit sample and returns ``true`` on success. Will block +until data is available. + +bool read16(int16_t \*l, int16_t \*r) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Reads a left and right 16-bit sample and returns ``true`` on success. Will block +until data is available. + +bool read24(int32_t \*l, int32_t \*r) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Reads a left and right 24-bit sample and returns ``true`` on success. See note below +about 24-bit mode. Will block until data is available. + +bool read32(int32_t \*l, int32_t \*r) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Reads a left and right 32-bit sample and returns ``true`` on success. Will block +until data is available. + + +Note About 24-bit Samples +------------------------- +24-bit samples are stored as left-aligned 32-bit values with bits 7..0 +ignored. Only the upper 24 bits 31...8 will be transmitted or +received. The actual I2S protocol will only transmit or receive 24 bits +in this mode, even though the data is 32-bit packed. diff --git a/libraries/I2S/examples/I2SInput/I2SInput.ino b/libraries/I2S/examples/I2SInput/I2SInput.ino new file mode 100644 index 00000000..883970af --- /dev/null +++ b/libraries/I2S/examples/I2SInput/I2SInput.ino @@ -0,0 +1,37 @@ +/* + I2S stereo microphone (input) example + Run using the Arduino Serial Plotter to see waveform. + Released to the Public Domain by Earle F. Philhower, III + + For the Google AIY Voice Hat Microphone daughterboard, part + of the Raspberry Pi AIY cardboard box, the I2S stereo pinout + looking at the board top with the RPI logo on the left hand + side: + +-- ------------------------------------ --+ + left RPI | (1) GND (2) DIN (3) BCLK (4) LRCLK (5) 3.3V | AIY right + logo +---------------------------------------------+ logo +*/ + +#include + +I2S i2s(INPUT); + +void setup() { + Serial.begin(115200); + + i2s.setDATA(0); + i2s.setBCLK(1); // LRCLK = GP2 + i2s.setBitsPerSample(16); + i2s.setFrequency(22050); + i2s.begin(); + + while (1) { + int16_t l, r; + i2s.read16(&l, &r); + Serial.printf("%d %d\n", l, r); + } +} + +void loop() { + /* Nothing here */ +} diff --git a/libraries/I2S/examples/SimpleTone/SimpleTone.ino b/libraries/I2S/examples/SimpleTone/SimpleTone.ino index 20adaca0..f5d686cb 100644 --- a/libraries/I2S/examples/SimpleTone/SimpleTone.ino +++ b/libraries/I2S/examples/SimpleTone/SimpleTone.ino @@ -5,56 +5,50 @@ created 17 November 2016 by Sandeep Mistry - modified for ESP8266 by Earle F. Philhower, III + modified for RP2040 by Earle F. Philhower, III - Only 16 bitsPerSample are allowed by the PIO code. Only write, no read. - - bool setBCLK(pin_size_t pin); // Must be 28 or less. Default is 26 - // This assigns two adjacent pins - the pin after this one (one greater) is the WS (word select) signal, - // which toggles before the sample for each channel is sent - - bool setDOUT(pin_size_t pin); // Must be 29 or less. Default is 28 - + bool setBCLK(pin_size_t pin); + - This assigns two adjacent pins - the pin after this one (one greater) + is the WS (word select) signal, which toggles before the sample for + each channel is sent + bool setDATA(pin_size_t pin); + - Sets the DOUT pin, can be any valid GPIO pin */ #include -// GPIO pin numbers +// Create the I2S port using a PIO state machine +I2S i2s(OUTPUT); -//define pBCLK 26 +// GPIO pin numbers #define pBCLK 20 #define pWS (pBCLK+1) -//define pDOUT 28 #define pDOUT 22 const int frequency = 440; // frequency of square wave in Hz const int amplitude = 500; // amplitude of square wave -//const int sampleRate = 8000; // sample rate in Hz const int sampleRate = 16000; // minimum for UDA1334A const int halfWavelength = (sampleRate / frequency); // half wavelength of square wave -short sample = amplitude; // current sample value +int16_t sample = amplitude; // current sample value int count = 0; - void setup() { pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, 1); - Serial.begin(9600); + Serial.begin(115200); Serial.println("I2S simple tone"); - I2S.setBCLK(pBCLK); // Must be 28 or less. Default is 26 - // This assigns two adjacent pins - the pin after this one (one greater) is the WS (word select) signal, - // which toggles before the sample for each channel is sent - - I2S.setDOUT(pDOUT); // Must be 29 or less. Default is 28 + i2s.setBCLK(pBCLK); + i2s.setDATA(pDOUT); + i2s.setBitsPerSample(16); // start I2S at the sample rate with 16-bits per sample - if (!I2S.begin(sampleRate)) { + if (!i2s.begin(sampleRate)) { Serial.println("Failed to initialize I2S!"); while (1); // do nothing } @@ -68,8 +62,8 @@ void loop() { } // write the same sample twice, once for left and once for the right channel - I2S.write(sample); - I2S.write(sample); + i2s.write(sample); + i2s.write(sample); // increment the counter for the next sample count++; diff --git a/libraries/I2S/keywords.txt b/libraries/I2S/keywords.txt index ad80bcb4..203e9a54 100644 --- a/libraries/I2S/keywords.txt +++ b/libraries/I2S/keywords.txt @@ -14,10 +14,24 @@ I2S KEYWORD1 begin KEYWORD2 end KEYWORD2 +setBCLK KEYWORD2 +setDATA KEYWORD2 +setBitsPerSample KEYWORD2 +setFrequency KEYWORD2 + +read8 KEYWORD2 +read16 KEYWORD2 +read24 KEYWORD2 +read32 KEYWORD2 + +write8 KEYWORD2 +write16 KEYWORD2 +write24 KEYWORD2 +write32 KEYWORD2 + onReceive KEYWORD2 onTransmit KEYWORD2 ####################################### # Constants (LITERAL1) ####################################### -I2S_PHILIPS_MODE LITERAL1 diff --git a/libraries/I2S/library.properties b/libraries/I2S/library.properties index dabdf370..a8bb0af9 100644 --- a/libraries/I2S/library.properties +++ b/libraries/I2S/library.properties @@ -1,9 +1,10 @@ name=I2S -version=1.0 +version=2.0 author=Earle F. Philhower, III maintainer=Earle F. Philhower, III -sentence=Enables the communication with devices that use the Inter-IC Sound (I2S) Bus. Specific implementation for RP2040, based off of pico-extras samples. +sentence=Enables the communication with devices that use the Inter-IC Sound (I2S) Bus. paragraph= category=Communication url=http://www.arduino.cc/en/Reference/I2S architectures=rp2040 + diff --git a/libraries/I2S/src/AudioRingBuffer.cpp b/libraries/I2S/src/AudioRingBuffer.cpp new file mode 100644 index 00000000..83f4d7a5 --- /dev/null +++ b/libraries/I2S/src/AudioRingBuffer.cpp @@ -0,0 +1,256 @@ +/* + AudioRingBuffer for Raspnerry Pi Pico RP2040 + Implements a ring buffer for PIO DMA for I2S read or write + + Copyright (c) 2022 Earle F. Philhower, III + + 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 St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include +#include "hardware/dma.h" +#include "hardware/irq.h" +#include "hardware/pio.h" +#include "pio_i2s.pio.h" +#include "AudioRingBuffer.h" + +static int __channelCount = 0; // # of channels left. When we hit 0, then remove our handler +static AudioRingBuffer* __channelMap[12]; // Lets the IRQ handler figure out where to dispatch to + +AudioRingBuffer::AudioRingBuffer(size_t bufferCount, size_t bufferWords, int32_t silenceSample, PinMode direction) { + _running = false; + _silenceSample = silenceSample; + _bufferCount = bufferCount; + _wordsPerBuffer = bufferWords; + _isOutput = direction == OUTPUT; + _overunderflow = false; + _callback = nullptr; + _userBuffer = -1; + _userOff = 0; + for (size_t i = 0; i < bufferCount; i++) { + auto ab = new AudioBuffer; + ab->buff = new uint32_t[_wordsPerBuffer]; + ab->empty = true; + _buffers.push_back(ab); + } +} + +AudioRingBuffer::~AudioRingBuffer() { + if (_running) { + for (auto i = 0; i < 2; i++) { + dma_channel_set_irq0_enabled(_channelDMA[i], false); + dma_channel_unclaim(_channelDMA[i]); + __channelMap[_channelDMA[i]] = nullptr; + } + while (_buffers.size()) { + auto ab = _buffers.back(); + _buffers.pop_back(); + delete[] ab->buff; + delete ab; + } + __channelCount--; + if (!__channelCount) { + irq_set_enabled(DMA_IRQ_0, false); + // TODO - how can we know if there are no other parts of the core using DMA0 IRQ?? + irq_remove_handler(DMA_IRQ_0, _irq); + } + } +} + +void AudioRingBuffer::setCallback(void (*fn)()) { + _callback = fn; +} + +bool AudioRingBuffer::begin(int dreq, volatile void *pioFIFOAddr) { + _running = true; + // Set all buffers to silence, empty + for (auto buff : _buffers) { + buff->empty = true; + if (_isOutput) { + for (uint32_t x = 0; x < _wordsPerBuffer; x++) { + buff->buff[x] = _silenceSample; + } + } + } + // Get ping and pong DMA channels + for (auto i = 0; i < 2; i++) { + _channelDMA[i] = dma_claim_unused_channel(true); + if (_channelDMA[i] == -1) { + if (i == 1) { + dma_channel_unclaim(_channelDMA[0]); + } + return false; + } + } + bool needSetIRQ = __channelCount == 0; + // Need to know both channels to set up ping-pong, so do in 2 stages + for (auto i = 0; i < 2; i++) { + dma_channel_config c = dma_channel_get_default_config(_channelDMA[i]); + channel_config_set_transfer_data_size(&c, DMA_SIZE_32); // 32b transfers into PIO FIFO + if (_isOutput) { + channel_config_set_read_increment(&c, true); // Reading incrementing addresses + channel_config_set_write_increment(&c, false); // Writing to the same FIFO address + } else { + channel_config_set_read_increment(&c, false); // Reading same FIFO address + channel_config_set_write_increment(&c, true); // Writing to incrememting buffers + } + channel_config_set_dreq(&c, dreq); // Wait for the PIO TX FIFO specified + channel_config_set_chain_to(&c, _channelDMA[i ^ 1]); // Start other channel when done + channel_config_set_irq_quiet(&c, false); // Need IRQs + + if (_isOutput) { + dma_channel_configure(_channelDMA[i], &c, pioFIFOAddr, _buffers[i]->buff, _wordsPerBuffer, false); + } else { + dma_channel_configure(_channelDMA[i], &c, _buffers[i]->buff, pioFIFOAddr, _wordsPerBuffer, false); + } + dma_channel_set_irq0_enabled(_channelDMA[i], true); + __channelMap[_channelDMA[i]] = this; + __channelCount++; + } + if (needSetIRQ) { + irq_add_shared_handler(DMA_IRQ_0, _irq, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); + irq_set_enabled(DMA_IRQ_0, true); + } + _curBuffer = 0; + _nextBuffer = 2 % _bufferCount; + dma_channel_start(_channelDMA[0]); + return true; +} + +bool AudioRingBuffer::write(uint32_t v, bool sync) { + if (!_running || !_isOutput) { + return false; + } + if (_userBuffer == -1) { + // First write or overflow, pick spot 2 buffers out + _userBuffer = (_nextBuffer + 2) % _bufferCount; + _userOff = 0; + } + if (!_buffers[_userBuffer]->empty) { + if (!sync) { + return false; + } else { + while (!_buffers[_userBuffer]->empty) { + /* noop busy wait */ + } + } + } + if (_userBuffer == _curBuffer) { + if (!sync) { + return false; + } else { + while (_userBuffer == _curBuffer) { + /* noop busy wait */ + } + } + } + _buffers[_userBuffer]->buff[_userOff++] = v; + if (_userOff == _wordsPerBuffer) { + _buffers[_userBuffer]->empty = false; + _userBuffer = (_userBuffer + 1) % _bufferCount; + _userOff = 0; + } + return true; +} + +bool AudioRingBuffer::read(uint32_t *v, bool sync) { + if (!_running || _isOutput) { + return false; + } + if (_userBuffer == -1) { + // First write or overflow, pick last filled buffer + _userBuffer = (_curBuffer - 1 + _bufferCount) % _bufferCount; + _userOff = 0; + } + if (_buffers[_userBuffer]->empty) { + if (!sync) { + return false; + } else { + while (_buffers[_userBuffer]->empty) { + /* noop busy wait */ + } + } + } + if (_userBuffer == _curBuffer) { + if (!sync) { + return false; + } else { + while (_userBuffer == _curBuffer) { + /* noop busy wait */ + } + } + } + auto ret = _buffers[_userBuffer]->buff[_userOff++]; + if (_userOff == _wordsPerBuffer) { + _buffers[_userBuffer]->empty = true; + _userBuffer = (_userBuffer + 1) % _bufferCount; + _userOff = 0; + } + *v = ret; + return true; +} + +bool AudioRingBuffer::getOverUnderflow() { + bool hold = _overunderflow; + _overunderflow = false; + return hold; +} + +int AudioRingBuffer::available() { + if (!_running) { + return 0; + } + int avail; + avail = _wordsPerBuffer - _userOff; + avail += ((_bufferCount + _curBuffer - _userBuffer) % _bufferCount) * _wordsPerBuffer; + return avail; +} + +void AudioRingBuffer::flush() { + while (_curBuffer != _userBuffer) { + // busy wait + } +} + +void __not_in_flash_func(AudioRingBuffer::_dmaIRQ)(int channel) { + if (_isOutput) { + for (uint32_t x = 0; x < _wordsPerBuffer; x++) { + _buffers[_curBuffer]->buff[x] = _silenceSample; + } + _buffers[_curBuffer]-> empty = true; + _overunderflow = _overunderflow | _buffers[_nextBuffer]->empty; + dma_channel_set_read_addr(channel, _buffers[_nextBuffer]->buff, false); + } else { + _buffers[_curBuffer]-> empty = false; + _overunderflow = _overunderflow | !_buffers[_nextBuffer]->empty; + dma_channel_set_write_addr(channel, _buffers[_nextBuffer]->buff, false); + } + dma_channel_set_trans_count(channel, _wordsPerBuffer, false); + _curBuffer = (_curBuffer + 1) % _bufferCount; + _nextBuffer = (_nextBuffer + 1) % _bufferCount; + dma_channel_acknowledge_irq0(channel); + if (_callback) { + _callback(); + } +} + +void __not_in_flash_func(AudioRingBuffer::_irq)() { + for (size_t i = 0; i < sizeof(__channelMap); i++) { + if (dma_channel_get_irq0_status(i) && __channelMap[i]) { + __channelMap[i]->_dmaIRQ(i); + } + } +} diff --git a/libraries/I2S/src/AudioRingBuffer.h b/libraries/I2S/src/AudioRingBuffer.h new file mode 100644 index 00000000..5a4d2dde --- /dev/null +++ b/libraries/I2S/src/AudioRingBuffer.h @@ -0,0 +1,69 @@ +/* + AudioRingBuffer for Rasperry Pi Pico + Implements a ring buffer for PIO DMA for I2S read or write + + Copyright (c) 2022 Earle F. Philhower, III + + 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 St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#pragma once +#include +#include + +class AudioRingBuffer { +public: + AudioRingBuffer(size_t bufferCount, size_t bufferWords, int32_t silenceSample, PinMode direction = OUTPUT); + ~AudioRingBuffer(); + + void setCallback(void (*fn)()); + + bool begin(int dreq, volatile void *pioFIFOAddr); + + bool write(uint32_t v, bool sync = true); + bool read(uint32_t *v, bool sync = true); + void flush(); + + bool getOverUnderflow(); + int available(); + +private: + void _dmaIRQ(int channel); + static void _irq(); + + typedef struct { + uint32_t *buff; + volatile bool empty; + } AudioBuffer; + + bool _running = false; + std::vector _buffers; + volatile int _curBuffer; + volatile int _nextBuffer; + size_t _chunkSampleCount; + int _bitsPerSample; + size_t _wordsPerBuffer; + size_t _bufferCount; + bool _isOutput; + int32_t _silenceSample; + int _channelDMA[2]; + void (*_callback)(); + + bool _overunderflow; + + // User buffer pointer + int _userBuffer = -1; + size_t _userOff = 0; +}; diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index ef6ab754..9c57e95f 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -1,7 +1,8 @@ /* - I2S Master library for the Raspberry Pi Pico RP2040 + I2SIn and I2SOut for Raspberry Pi Pico + Implements one or more I2S interfaces using DMA - Copyright (c) 2021 Earle F. Philhower, III + Copyright (c) 2022 Earle F. Philhower, III This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -17,30 +18,30 @@ License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ - #include #include "I2S.h" +#include "pio_i2s.pio.h" -#ifdef USE_TINYUSB -// For Serial when selecting TinyUSB. Can't include in the core because Arduino IDE -// will not link in libraries called from the core. Instead, add the header to all -// the standard libraries in the hope it will still catch some user cases where they -// use these libraries. -// See https://github.com/earlephilhower/arduino-pico/issues/167#issuecomment-848622174 -#include -#endif -I2SClass::I2SClass() { +I2S::I2S(PinMode direction) { _running = false; - _pool = nullptr; - _curBuff = nullptr; - _bps = 0; + _bps = 16; _writtenHalf = false; _pinBCLK = 26; _pinDOUT = 28; + _freq = 48000; + _arb = nullptr; + _isOutput = direction == OUTPUT; + _cb = nullptr; + _buffers = 8; + _bufferWords = 16; + _silenceSample = 0; } -bool I2SClass::setBCLK(pin_size_t pin) { +I2S::~I2S() { +} + +bool I2S::setBCLK(pin_size_t pin) { if (_running || (pin > 28)) { return false; } @@ -48,7 +49,7 @@ bool I2SClass::setBCLK(pin_size_t pin) { return true; } -bool I2SClass::setDOUT(pin_size_t pin) { +bool I2S::setDATA(pin_size_t pin) { if (_running || (pin > 29)) { return false; } @@ -56,173 +57,296 @@ bool I2SClass::setDOUT(pin_size_t pin) { return true; } -bool I2SClass::begin(long sampleRate) { +bool I2S::setBitsPerSample(int bps) { + if (_running || ((bps != 8) && (bps != 16) && (bps != 24) && (bps != 32))) { + return false; + } + _bps = bps; + return true; +} + +bool I2S::setBuffers(size_t buffers, size_t bufferWords, int32_t silenceSample) { + if (_running || (buffers < 3) || (bufferWords < 8)) { + return false; + } + _buffers = buffers; + _bufferWords = bufferWords; + _silenceSample = silenceSample; + return true; +} + +bool I2S::setFrequency(int newFreq) { + _freq = newFreq; if (_running) { - return false; + float bitClk = _freq * _bps * 2.0; + pio_sm_set_clkdiv(_pio, _sm, (float)clock_get_hz(clk_sys) / bitClk); } + return true; +} - bzero(&_audio_format, sizeof(_audio_format)); - _audio_format.sample_freq = (uint32_t)sampleRate; - _audio_format.format = AUDIO_BUFFER_FORMAT_PCM_S16; - _audio_format.channel_count = 2; - - bzero(&_producer_format, sizeof(_producer_format)); - _producer_format.format = &_audio_format; - _producer_format.sample_stride = 4; - - if (!_pool) { - _pool = audio_new_producer_pool(&_producer_format, 3, 256); +void I2S::onTransmit(void(*fn)(void)) { + if (_isOutput) { + _cb = fn; + if (_running) { + _arb->setCallback(_cb); + } } +} - bzero(&_config, sizeof(_config)); - _config.data_pin = _pinDOUT; - _config.clock_pin_base = _pinBCLK; - _config.dma_channel = 0; - _config.pio_sm = 1; - - if (!audio_i2s_setup(&_audio_format, &_config)) { - return false; +void I2S::onReceive(void(*fn)(void)) { + if (!_isOutput) { + _cb = fn; + if (_running) { + _arb->setCallback(_cb); + } } +} - if (!audio_i2s_connect(_pool)) { - return false; - } - - audio_i2s_set_enabled(true); - - _curBuff = take_audio_buffer(_pool, true); - _curBuff->sample_count = 0; - - _bps = 16; - _freq = sampleRate; +bool I2S::begin() { _running = true; + _hasPeeked = false; + int off = 0; + _i2s = new PIOProgram(_isOutput ? &pio_i2s_out_program : &pio_i2s_in_program); + _i2s->prepare(&_pio, &_sm, &off); + if (_isOutput) { + pio_i2s_out_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps); + } else { + pio_i2s_in_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps); + } + setFrequency(_freq); + if (_bps == 8) { + uint8_t a = _silenceSample & 0xff; + _silenceSample = (a << 24) | (a << 16) | (a << 8) | a; + } else if (_bps == 16) { + uint16_t a = _silenceSample & 0xffff; + _silenceSample = (a << 16) | a; + } + _arb = new AudioRingBuffer(_buffers, _bufferWords, _silenceSample, _isOutput ? OUTPUT : INPUT); + _arb->begin(pio_get_dreq(_pio, _sm, _isOutput), _isOutput ? &_pio->txf[_sm] : (volatile void*)&_pio->rxf[_sm]); + _arb->setCallback(_cb); + pio_sm_set_enabled(_pio, _sm, true); + return true; } -void I2SClass::end() { - if (_running) { - audio_i2s_set_enabled(false); - if (_curBuff) { - release_audio_buffer(_pool, _curBuff); - _curBuff = nullptr; - } - } +void I2S::end() { _running = false; - _bps = 0; - _freq = 0; - _writtenHalf = false; + delete _arb; + _arb = nullptr; + delete _i2s; + _i2s = nullptr; } -int I2SClass::availableForWrite() { - if (!_running) { +int I2S::available() { + if (!_running || _isOutput) { return 0; } - // Can we get a whole new buffer to work with? - if (!_curBuff) { - _curBuff = take_audio_buffer(_pool, false); - _curBuff->sample_count = 0; - } - if (!_curBuff) { - return false; - } - return _curBuff->max_sample_count - _curBuff->sample_count; + return _arb->available(); } -void I2SClass::flush() { - if (!_curBuff || !_curBuff->sample_count) { - return; - } - _audio_format.sample_freq = _freq; - give_audio_buffer(_pool, _curBuff); - _curBuff = nullptr; -} - -bool I2SClass::setFrequency(int newFreq) { - if (newFreq != _freq) { - flush(); - _freq = newFreq; - } - return true; -} - -size_t I2SClass::write(uint8_t s) { - return write((int16_t)s); -} - -size_t I2SClass::write(const uint8_t *buffer, size_t size) { - return write((const void *)buffer, size); -} - -size_t I2SClass::write(int16_t s) { - if (!_running) { +int I2S::read() { + if (!_running || _isOutput) { return 0; } - // Because our HW really wants 32b writes, store any 16b writes until another - // 16b write comes in and then send the combined write down. - if (_bps == 16) { - if (_writtenHalf) { - _writtenData <<= 16; - _writtenData |= 0xffff & s; - _writtenHalf = false; - if (!_curBuff) { - _curBuff = take_audio_buffer(_pool, true); - _curBuff->sample_count = 0; - } - int32_t *samples = (int32_t *)_curBuff->buffer->bytes; - samples[_curBuff->sample_count++] = _writtenData; - if (_curBuff->sample_count == _curBuff->max_sample_count) { - _audio_format.sample_freq = _freq; - give_audio_buffer(_pool, _curBuff); - _curBuff = nullptr; - } + if (_hasPeeked) { + _hasPeeked = false; + return _peekSaved; + } + + if (_wasHolding <= 0) { + read(&_holdWord, true); + _wasHolding = 32; + } + + int ret; + switch (_bps) { + case 8: + ret = _holdWord >> 24; + _holdWord <<= 8; + _wasHolding -= 8; + return ret; + case 16: + ret = _holdWord >> 16; + _holdWord <<= 16; + _wasHolding -= 32; + return ret; + case 24: + case 32: + default: + ret = _holdWord; + _wasHolding = 0; + return ret; + } +} + +int I2S::peek() { + if (!_running || _isOutput) { + return 0; + } + if (!_hasPeeked) { + _peekSaved = read(); + _hasPeeked = true; + } + return _peekSaved; +} + +void I2S::flush() { + if (_running) { + _arb->flush(); + } +} + +size_t I2S::_writeNatural(int32_t s) { + if (!_running || !_isOutput) { + return 0; + } + switch (_bps) { + case 8: + _holdWord |= s & 0xff; + if (_wasHolding >= 24) { + auto ret = write(_holdWord, true); + _holdWord = 0; + _wasHolding = 0; + return ret; } else { - _writtenHalf = true; - _writtenData = s & 0xffff; + _holdWord <<= 8; + _wasHolding += 8; + return 1; } + case 16: + _holdWord |= s & 0xffff; + if (_wasHolding) { + auto ret = write(_holdWord, true); + _holdWord = 0; + _wasHolding = 0; + return ret; + } else { + _holdWord <<= 16; + _wasHolding = 16; + return 1; + } + case 24: + case 32: + default: + return write(s, true); } +} + +size_t I2S::write(int32_t val, bool sync) { + if (!_running || !_isOutput) { + return 0; + } + return _arb->write(val, sync); +} + +size_t I2S::write8(int8_t l, int8_t r) { + if (!_running || !_isOutput) { + return 0; + } + int16_t o = (l << 8) | (r & 0xff); + return write((int16_t) o); +} + +size_t I2S::write16(int16_t l, int16_t r) { + if (!_running || !_isOutput) { + return 0; + } + int32_t o = (l << 16) | (r & 0xffff); + return write((int32_t)o, true); +} + +size_t I2S::write24(int32_t l, int32_t r) { + return write32(l, r); +} + +size_t I2S::write32(int32_t l, int32_t r) { + if (!_running || !_isOutput) { + return 0; + } + write((int32_t)l); + write((int32_t)r); return 1; } -// Mostly non-blocking -size_t I2SClass::write(const void *buffer, size_t size) { - if (!_running) { +size_t I2S::read(int32_t *val, bool sync) { + if (!_running || _isOutput) { return 0; } - // We have no choice here because we need to write at least 1 byte... - if (!_curBuff) { - _curBuff = take_audio_buffer(_pool, true); - _curBuff->sample_count = 0; - } - - int32_t *inSamples = (int32_t *)buffer; - int written = 0; - int wantToWrite = size / 4; - while (wantToWrite) { - if (!_curBuff) { - _curBuff = take_audio_buffer(_pool, false); - if (_curBuff) { - _curBuff->sample_count = 0; - } else { - break; - } - } - - int avail = _curBuff->max_sample_count - _curBuff->sample_count; - int writeSize = (avail > wantToWrite) ? wantToWrite : avail; - int32_t *samples = (int32_t *)_curBuff->buffer->bytes; - memcpy(samples + _curBuff->sample_count, inSamples, writeSize * 4); - _curBuff->sample_count += writeSize; - inSamples += writeSize; - written += writeSize; - wantToWrite -= writeSize; - if (_curBuff->sample_count == _curBuff->max_sample_count) { - _audio_format.sample_freq = _freq; - give_audio_buffer(_pool, _curBuff); - _curBuff = nullptr; - } - } - return written; + return _arb->read((uint32_t *)val, sync); } -I2SClass I2S; +bool I2S::read8(int8_t *l, int8_t *r) { + if (!_running || _isOutput) { + return false; + } + if (_wasHolding) { + *l = (_holdWord >> 8) & 0xff; + *r = (_holdWord >> 0) & 0xff; + _wasHolding = 0; + } else { + read(&_holdWord, true); + _wasHolding = 16; + *l = (_holdWord >> 24) & 0xff; + *r = (_holdWord >> 16) & 0xff; + } + return true; +} + +bool I2S::read16(int16_t *l, int16_t *r) { + if (!_running || _isOutput) { + return false; + } + int32_t o; + read(&o, true); + *l = (o >> 16) & 0xffff; + *r = (o >> 0) & 0xffff; + return true; +} + +bool I2S::read24(int32_t *l, int32_t *r) { + if (!_running || _isOutput) { + return false; + } + read32(l, r); + // 24-bit samples are read right-aligned, so left-align them to keep the binary point between 33.32 + *l <<= 8; + *r <<= 8; + return true; +} + +bool I2S::read32(int32_t *l, int32_t *r) { + if (!_running || _isOutput) { + return false; + } + read(l, true); + read(r, true); + return true; +} + +size_t I2S::write(const uint8_t *buffer, size_t size) { + // We can only write 32-bit chunks here + if (size & 0x3) { + return 0; + } + size_t writtenSize = 0; + int32_t *p = (int32_t *)buffer; + while (size) { + if (!write((int32_t)*p)) { + // Blocked, stop write here + return writtenSize; + } else { + p++; + size -= 4; + writtenSize += 4; + } + } + return writtenSize; +} + +int I2S::availableForWrite() { + if (!_running || !_isOutput) { + return 0; + } + return _arb->available(); +} diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index 96a4cfe1..97100a68 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -1,7 +1,8 @@ /* - I2S Master library for the Raspberry Pi Pico RP2040 + I2SIn and I2SOut for Raspberry Pi Pico + Implements one or more I2S interfaces using DMA - Copyright (c) 2021 Earle F. Philhower, III + Copyright (c) 2022 Earle F. Philhower, III This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -19,72 +20,109 @@ */ #pragma once - #include -#include "pico/audio_i2s.h" +#include +#include "AudioRingBuffer.h" -class I2SClass : public Stream { +class I2S : public Stream { public: - I2SClass(); + I2S(PinMode direction = OUTPUT); + virtual ~I2S(); - // Only 16 bitsPerSample are allowed by the PIO code. Only write, no read. bool setBCLK(pin_size_t pin); - bool setDOUT(pin_size_t pin); - bool begin(long sampleRate); + bool setDATA(pin_size_t pin); + bool setBitsPerSample(int bps); + bool setBuffers(size_t buffers, size_t bufferWords, int32_t silenceSample = 0); + bool setFrequency(int newFreq); + + bool begin(long sampleRate) { + setFrequency(sampleRate); + return begin(); + } + + bool begin(); void end(); // from Stream - virtual int available() override { - return -1; - } - virtual int read() override { - return -1; - } - virtual int peek() override { - return -1; - } + virtual int available() override; + virtual int read() override; + virtual int peek() override; virtual void flush() override; // from Print (see notes on write() methods below) - virtual size_t write(uint8_t) override; virtual size_t write(const uint8_t *buffer, size_t size) override; virtual int availableForWrite() override; - bool setFrequency(int newFreq); + // Try and make I2S::write() do what makes sense, namely write + // one sample (L or R) at the I2S configured bit width + virtual size_t write(uint8_t s) override { + return _writeNatural(s & 0xff); + } + size_t write(int8_t s) { + return write((uint8_t)s); + } + size_t write(uint16_t s) { + return _writeNatural(s & 0xffff); + } + size_t write(int16_t s) { + return write((uint16_t)s); + } + size_t write(uint32_t s) { + return _writeNatural(s); + } + size_t write(int32_t s) { + return write((uint32_t)s); + } - // Write a single L:R sample to the I2S device. Blocking until write succeeds - size_t write(int16_t); - // Write up to size samples to the I2S device. Non-blocking, will write - // from 0...size samples and return that count. Be sure your app handles - // partial writes (i.e. by yield()ing and then retrying to write the - // remaining data. - size_t write(const void *buffer, size_t lrsamples); + // Write 32 bit value to port, user responsbile for packing/alignment, etc. + size_t write(int32_t val, bool sync); + + // Write sample to I2S port, will block until completed + size_t write8(int8_t l, int8_t r); + size_t write16(int16_t l, int16_t r); + size_t write24(int32_t l, int32_t r); // Note that 24b must have values left-aligned (i.e. 0xABCDEF00) + size_t write32(int32_t l, int32_t r); + + // Read 32 bit value to port, user responsbile for packing/alignment, etc. + size_t read(int32_t *val, bool sync); + + // Read samples from I2S port, will block until data available + bool read8(int8_t *l, int8_t *r); + bool read16(int16_t *l, int16_t *r); + bool read24(int32_t *l, int32_t *r); // Note that 24b reads will be left-aligned (see above) + bool read32(int32_t *l, int32_t *r); // Note that these callback are called from **INTERRUPT CONTEXT** and hence - // must be both stored in IRAM and not perform anything that's not legal in - // an interrupt - //void onTransmit(void(*)(void)); -- Not yet implemented, need to edit pico-extra to get callback - //void onReceive(void(*)(void)); -- no I2S input yet + // should be in RAM, not FLASH, and should be quick to execute. + void onTransmit(void(*)(void)); + void onReceive(void(*)(void)); private: pin_size_t _pinBCLK; pin_size_t _pinDOUT; int _bps; int _freq; + size_t _buffers; + size_t _bufferWords; + int32_t _silenceSample; + bool _isOutput; + bool _running; - // Audio format/pool config - audio_format_t _audio_format; - audio_buffer_format_t _producer_format; - audio_i2s_config_t _config; + bool _hasPeeked; + int32_t _peekSaved; - // We manage our own buffer pool here... - audio_buffer_pool_t *_pool; - audio_buffer_t *_curBuff; - - // Support for ::write(x) on 16b quantities + size_t _writeNatural(int32_t s); uint32_t _writtenData; bool _writtenHalf; -}; -extern I2SClass I2S; + int32_t _holdWord = 0; + int _wasHolding = 0; + + void (*_cb)(); + + AudioRingBuffer *_arb; + PIOProgram *_i2s; + PIO _pio; + int _sm; +}; diff --git a/libraries/I2S/src/pio_i2s.pio b/libraries/I2S/src/pio_i2s.pio new file mode 100644 index 00000000..2b09c2cd --- /dev/null +++ b/libraries/I2S/src/pio_i2s.pio @@ -0,0 +1,112 @@ +; pio_i2s for the Raspberry Pi Pico RP2040 +; +; Based loosely off of the MicroPython I2S code in +; https://github.com/micropython/micropython/blob/master/ports/rp2/machine_i2s.c +; +; Copyright (c) 2022 Earle F. Philhower, III +; +; 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 St, Fifth Floor, Boston, MA 02110-1301 USA + + +.program pio_i2s_out +.side_set 2 ; 0 = bclk, 1=wclk + +; The C code should place (number of bits/sample - 2) in Y and +; also update the SHIFTCTRL to be 24 or 32 as appropriate + +; +----- WCLK +; |+---- BCLK + mov x, y side 0b01 +left: + out pins, 1 side 0b00 + jmp x--, left side 0b01 + out pins, 1 side 0b10 ; Last bit of left has WCLK change per I2S spec + + mov x, y side 0b11 +right: + out pins, 1 side 0b10 + jmp x--, right side 0b11 + out pins, 1 side 0b00 ; Last bit of right also has WCLK change + ; Loop back to beginning... + + +.program pio_i2s_in ; Note this is the same as _out, just "in" and not "out" +.side_set 2 ; 0 = bclk, 1=wclk + +; The C code should place (number of bits/sample - 2) in Y and +; also update the SHIFTCTRL to be 24 or 32 as appropriate + +; +----- WCLK +; |+---- BCLK + mov x, y side 0b00 +left: + in pins, 1 side 0b01 + jmp x--, left side 0b00 + in pins, 1 side 0b11 ; Last bit of left has WCLK change per I2S spec + + mov x, y side 0b10 +right: + in pins, 1 side 0b11 + jmp x--, right side 0b10 + in pins, 1 side 0b01 ; Last bit of right also has WCLK change + ; Loop back to beginning... + + + +% c-sdk { + +static inline void pio_i2s_out_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base, uint bits) { + pio_gpio_init(pio, data_pin); + pio_gpio_init(pio, clock_pin_base); + pio_gpio_init(pio, clock_pin_base + 1); + + pio_sm_config sm_config = pio_i2s_out_program_get_default_config(offset); + + sm_config_set_out_pins(&sm_config, data_pin, 1); + sm_config_set_sideset_pins(&sm_config, clock_pin_base); + sm_config_set_out_shift(&sm_config, false, true, (bits <= 16) ? 2 * bits : bits); + sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_TX); + + pio_sm_init(pio, sm, offset, &sm_config); + + uint pin_mask = (1u << data_pin) | (3u << clock_pin_base); + pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask); + pio_sm_set_pins(pio, sm, 0); // clear pins + + pio_sm_exec(pio, sm, pio_encode_set(pio_y, bits - 2)); +} + +static inline void pio_i2s_in_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base, uint bits) { + pio_gpio_init(pio, data_pin); + pio_gpio_init(pio, clock_pin_base); + pio_gpio_init(pio, clock_pin_base + 1); + + pio_sm_config sm_config = pio_i2s_in_program_get_default_config(offset); + + sm_config_set_in_pins(&sm_config, data_pin); + sm_config_set_sideset_pins(&sm_config, clock_pin_base); + sm_config_set_in_shift(&sm_config, false, true, (bits <= 16) ? 2 * bits : bits); + sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_RX); + + pio_sm_init(pio, sm, offset, &sm_config); + + uint pin_mask = 3u << clock_pin_base; + pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask); + pio_sm_set_pins(pio, sm, 0); // clear pins + + pio_sm_exec(pio, sm, pio_encode_set(pio_y, bits - 2)); +} + +%} diff --git a/libraries/I2S/src/pio_i2s.pio.h b/libraries/I2S/src/pio_i2s.pio.h new file mode 100644 index 00000000..e9a16fad --- /dev/null +++ b/libraries/I2S/src/pio_i2s.pio.h @@ -0,0 +1,112 @@ +// -------------------------------------------------- // +// This file is autogenerated by pioasm; do not edit! // +// -------------------------------------------------- // + +#pragma once + +#if !PICO_NO_HARDWARE +#include "hardware/pio.h" +#endif + +// ----------- // +// pio_i2s_out // +// ----------- // + +#define pio_i2s_out_wrap_target 0 +#define pio_i2s_out_wrap 7 + +static const uint16_t pio_i2s_out_program_instructions[] = { + // .wrap_target + 0xa822, // 0: mov x, y side 1 + 0x6001, // 1: out pins, 1 side 0 + 0x0841, // 2: jmp x--, 1 side 1 + 0x7001, // 3: out pins, 1 side 2 + 0xb822, // 4: mov x, y side 3 + 0x7001, // 5: out pins, 1 side 2 + 0x1845, // 6: jmp x--, 5 side 3 + 0x6001, // 7: out pins, 1 side 0 + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program pio_i2s_out_program = { + .instructions = pio_i2s_out_program_instructions, + .length = 8, + .origin = -1, +}; + +static inline pio_sm_config pio_i2s_out_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + pio_i2s_out_wrap_target, offset + pio_i2s_out_wrap); + sm_config_set_sideset(&c, 2, false, false); + return c; +} +#endif + +// ---------- // +// pio_i2s_in // +// ---------- // + +#define pio_i2s_in_wrap_target 0 +#define pio_i2s_in_wrap 7 + +static const uint16_t pio_i2s_in_program_instructions[] = { + // .wrap_target + 0xa022, // 0: mov x, y side 0 + 0x4801, // 1: in pins, 1 side 1 + 0x0041, // 2: jmp x--, 1 side 0 + 0x5801, // 3: in pins, 1 side 3 + 0xb022, // 4: mov x, y side 2 + 0x5801, // 5: in pins, 1 side 3 + 0x1045, // 6: jmp x--, 5 side 2 + 0x4801, // 7: in pins, 1 side 1 + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program pio_i2s_in_program = { + .instructions = pio_i2s_in_program_instructions, + .length = 8, + .origin = -1, +}; + +static inline pio_sm_config pio_i2s_in_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + pio_i2s_in_wrap_target, offset + pio_i2s_in_wrap); + sm_config_set_sideset(&c, 2, false, false); + return c; +} + +static inline void pio_i2s_out_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base, uint bits) { + pio_gpio_init(pio, data_pin); + pio_gpio_init(pio, clock_pin_base); + pio_gpio_init(pio, clock_pin_base + 1); + pio_sm_config sm_config = pio_i2s_out_program_get_default_config(offset); + sm_config_set_out_pins(&sm_config, data_pin, 1); + sm_config_set_sideset_pins(&sm_config, clock_pin_base); + sm_config_set_out_shift(&sm_config, false, true, (bits <= 16) ? 2 * bits : bits); + sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_TX); + pio_sm_init(pio, sm, offset, &sm_config); + uint pin_mask = (1u << data_pin) | (3u << clock_pin_base); + pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask); + pio_sm_set_pins(pio, sm, 0); // clear pins + pio_sm_exec(pio, sm, pio_encode_set(pio_y, bits - 2)); +} +static inline void pio_i2s_in_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base, uint bits) { + pio_gpio_init(pio, data_pin); + pio_gpio_init(pio, clock_pin_base); + pio_gpio_init(pio, clock_pin_base + 1); + pio_sm_config sm_config = pio_i2s_in_program_get_default_config(offset); + sm_config_set_in_pins(&sm_config, data_pin); + sm_config_set_sideset_pins(&sm_config, clock_pin_base); + sm_config_set_in_shift(&sm_config, false, true, (bits <= 16) ? 2 * bits : bits); + sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_RX); + pio_sm_init(pio, sm, offset, &sm_config); + uint pin_mask = 3u << clock_pin_base; + pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask); + pio_sm_set_pins(pio, sm, 0); // clear pins + pio_sm_exec(pio, sm, pio_encode_set(pio_y, bits - 2)); +} + +#endif + diff --git a/pico-extras b/pico-extras deleted file mode 160000 index 77eae283..00000000 --- a/pico-extras +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 77eae2836638baf2f61b321eb61125da99bb4445