Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b296a4dc9 | ||
|
|
61ac01c31b | ||
|
|
79a2128704 | ||
|
|
0c9359cddd | ||
|
|
dbf855e1aa | ||
|
|
0afd6ae54f | ||
|
|
07500e89db | ||
|
|
3adc1c5321 | ||
|
|
55394a7dcd | ||
|
|
230758b1ee | ||
|
|
d7e02c6f34 | ||
|
|
c32ad45724 |
@@ -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
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ jobs:
|
||||
CI_GITHUB_API_KEY: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
(cd pico-sdk && git submodule update --init)
|
||||
(cd pico-extras && git submodule update --init)
|
||||
(cd ./package && bash ./build_boards_manager_package.sh)
|
||||
pip3 install PyGithub
|
||||
# Create a draft release and upload the ZIP and JSON files.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 <earlephilhower@ya
|
||||
# Licensing and Credits
|
||||
* The [Arduino IDE and ArduinoCore-API](https://arduino.cc) are developed and maintained by the Arduino team. The IDE is licensed under GPL.
|
||||
* The [RP2040 GCC-based toolchain](https://github.com/earlephilhower/pico-quick-toolchain) is licensed under under the GPL.
|
||||
* The [Pico-SDK](https://github.com/raspberrypi/pico-sdk) and [Pico-Extras](https://github.com/raspberrypi/pico-extras) are by Raspberry Pi (Trading) Ltd and licensed under the BSD 3-Clause license.
|
||||
* The [Pico-SDK](https://github.com/raspberrypi/pico-sdk) is by Raspberry Pi (Trading) Ltd and licensed under the BSD 3-Clause license.
|
||||
* [Arduino-Pico](https://github.com/earlephilhower/arduino-pico) core files are licensed under the LGPL.
|
||||
* [LittleFS](https://github.com/ARMmbed/littlefs) library written by ARM Limited and released under the [BSD 3-clause license](https://github.com/ARMmbed/littlefs/blob/master/LICENSE.md).
|
||||
* [UF2CONV.PY](https://github.com/microsoft/uf2) is by Microsoft Corporation and licensed under the MIT license.
|
||||
|
||||
@@ -47,8 +47,8 @@ static bool __no_inline_not_in_flash_func(get_bootsel_button)() {
|
||||
GPIO_OVERRIDE_NORMAL << IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_LSB,
|
||||
IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_BITS);
|
||||
|
||||
interrupts();
|
||||
rp2040.resumeOtherCore();
|
||||
interrupts();
|
||||
|
||||
return button_state;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#pragma once
|
||||
#define ARDUINO_PICO_MAJOR 2
|
||||
#define ARDUINO_PICO_MINOR 0
|
||||
#define ARDUINO_PICO_REVISION 0
|
||||
#define ARDUINO_PICO_VERSION_STR "2.0.0"
|
||||
#define ARDUINO_PICO_REVISION 1
|
||||
#define ARDUINO_PICO_VERSION_STR "2.0.1"
|
||||
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
// Not to be called by users, only from the IRQ handler. In public so that the C-language IQR callback can access it
|
||||
void _handleIRQ();
|
||||
|
||||
private:
|
||||
protected:
|
||||
bool _running = false;
|
||||
pin_size_t _tx, _rx;
|
||||
int _baud;
|
||||
|
||||
@@ -25,13 +25,35 @@
|
||||
class SoftwareSerial : public SerialPIO {
|
||||
public:
|
||||
// Note the rx/tx pins are swapped in PIO vs SWSerial
|
||||
SoftwareSerial(pin_size_t rx, pin_size_t tx, bool invert = true) : SerialPIO(tx, rx) {
|
||||
if (invert) {
|
||||
panic("SoftwareSerial inverted operation not supported\n");
|
||||
SoftwareSerial(pin_size_t rx, pin_size_t tx, bool invert = false) : SerialPIO(tx, rx) {
|
||||
_invert = invert;
|
||||
}
|
||||
|
||||
~SoftwareSerial() {
|
||||
if (_invert) {
|
||||
gpio_set_outover(_tx, 0);
|
||||
gpio_set_outover(_rx, 0);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void begin(unsigned long baud = 115200) override {
|
||||
begin(baud, SERIAL_8N1);
|
||||
};
|
||||
|
||||
void begin(unsigned long baud, uint16_t config) override {
|
||||
SerialPIO::begin(baud, config);
|
||||
if (_invert) {
|
||||
gpio_set_outover(_tx, GPIO_OVERRIDE_INVERT);
|
||||
gpio_set_inover(_rx, GPIO_OVERRIDE_INVERT);
|
||||
}
|
||||
}
|
||||
|
||||
void listen() { /* noop */ }
|
||||
|
||||
bool isListening() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool _invert;
|
||||
};
|
||||
|
||||
+144
-52
@@ -1,89 +1,181 @@
|
||||
I2S (Digital Audio) Output Library
|
||||
==================================
|
||||
I2S (Digital Audio) Audio Library
|
||||
=================================
|
||||
|
||||
While the RP2040 chip on the Raspberry Pi Pico does not include a hardware
|
||||
I2S device, it is possible to use the PIO (Programmable I/O) state machines
|
||||
to implement one dynamically.
|
||||
|
||||
This I2S library uses the ``pico-extras`` I2S audio library and wraps it in
|
||||
an Arduino I2S library. It supports 16 bits/sample and frequencies of up
|
||||
to 48kHZ, with configurable BCLK, LRCLK(always pin "BCLK + 1"), and DOUT pins.
|
||||
Digital audio input and output are supported at 8, 16, 24, and 32 bits per
|
||||
sample.
|
||||
|
||||
Theoretically up to 6 I2S ports may be created, but in practice there
|
||||
may not be enough resources (DMA, PIO SM) to actually create and use so
|
||||
many.
|
||||
|
||||
Create an I2S port by instantiating a variable of the I2S class
|
||||
specifying the direction. Configure it using API calls below before
|
||||
using it.
|
||||
|
||||
**Note:** This I2S device takes over the entire PIO1 (second) unit and adjusts
|
||||
its clock frequency to meet the I2S needs. That means when only 4 Tones
|
||||
or only 4 Servos may be used when the I2S device is used.
|
||||
|
||||
I2S Class API
|
||||
-------------
|
||||
|
||||
I2S(OUTPUT)
|
||||
~~~~~~~~~~~
|
||||
Creates an I2S output port. Needs to be connected up to the
|
||||
desired pins (see below) and started before any output can happen.
|
||||
|
||||
I2S(INPUT)
|
||||
~~~~~~~~~~
|
||||
Creates an I2S input port. Needs to be connected up to the
|
||||
desired pins (see below) and started before any input can happen.
|
||||
|
||||
bool setBCLK(pin_size_t pin)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Sets the BCLK pin of the I2S device. The LRCLK/word clock will be ``pin + 1``
|
||||
due to limitations of the PIO state machines. Call this before ``I2S.begin()``
|
||||
Default BCLK = 26, LRCLK = 27
|
||||
due to limitations of the PIO state machines. Call this before ``I2S::begin()``
|
||||
|
||||
bool setDOUT(pin_size_t pin)
|
||||
bool setDATA(pin_size_t pin)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Sets the DOUT pin of the I2S device. Any pin may be used. Default DOUT = 28
|
||||
Call before ``I2S.begin()``
|
||||
Sets the DOUT or DIN pin of the I2S device. Any pin may be used.
|
||||
Call before ``I2S::begin()``
|
||||
|
||||
bool begin(long sampleRate)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Start the I2S device up with the given sample rate. The pins selected above
|
||||
will be turned to output and the I2S will begin to drive silence frames (all
|
||||
zero) out.
|
||||
bool setBitsPerSample(int bits)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Specify how many bits per audio sample to read or write. Note that
|
||||
for 24-bit samples, audio samples must be left-aligned (i.e. bits 31...8).
|
||||
Call before ``I2S::begin()``
|
||||
|
||||
bool setBuffers(size_t buffers, size_t bufferWords, int32_t silenceSample = 0)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Set the number of DMA buffers and their size in 32-bit words as well as
|
||||
the word to fill when no data is available to send to the I2S hardware.
|
||||
Call before ``I2S::begin()``.
|
||||
|
||||
bool setFrequency(long sampleRate)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Sets the word clock frequency, but does not start the I2S device if not
|
||||
already running. May be called after ``I2S::begin()`` to change the
|
||||
sample rate on-the-fly.
|
||||
|
||||
bool begin()/begin(long sampleRate)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Start the I2S device up with the given sample rate, or with the value set
|
||||
using the prior ``setFrequency`` call.
|
||||
|
||||
void end()
|
||||
~~~~~~~~~~
|
||||
Stops the I2S device. **Note, at present the memory allocated for I2S buffers
|
||||
is not freed leading to a memory leak when ``end()`` is called. This is due
|
||||
to the state of the ``pico-extras`` release which this code uses.**
|
||||
Stops the I2S device.
|
||||
|
||||
void flush()
|
||||
~~~~~~~~~~~~
|
||||
Sends any partial frames in memory to the I2S output device. They may NOT play
|
||||
immediately. Potential use case for this call would be when the frequency of
|
||||
the output will be changing.
|
||||
Waits until all the I2S buffers have been output.
|
||||
|
||||
size_t write(uint8_t)
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
Provided for compatibility, but not very useful. Writes a sample from 0...255
|
||||
to the I2S buffer. See ``write(int16_t)`` for a better one
|
||||
size_t write(uint8_t/int8_t/int16_t/int32_t)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Writes a single sample of ``bitsPerSample`` to the buffer. It is up to the
|
||||
user to keep track of left/right channels. Note this writes data equivalent
|
||||
to one channel's data, not the size of the passed in variable (i.e. if you have
|
||||
a 16-bit sample size and ``write((int8_t)-5); write((int8_t)5);`` you will have
|
||||
written **2 samples** to the I2S buffer of whatever the I2S size, not a single
|
||||
16-bit sample.
|
||||
|
||||
This call will block (wait) until space is available to actually write
|
||||
the data.
|
||||
|
||||
size_t write(int32_t val, bool sync)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Writes 32 bits of data to the I2S buffer (regardless of the configured I2S
|
||||
bit size). When ``sync`` is true, it will not return until the data has
|
||||
been writte. When ``sync`` is false, it will return ``0`` immediately if
|
||||
there is no space present in the I2S buffer.
|
||||
|
||||
size_t write(const uint8_t \*buffer, size_t size)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Transfers number of bytes from an application buffer to the I2S output buffer.
|
||||
Be aware that ``size`` is in *bytes** and not samples. Size must be a multiple
|
||||
of **4 bytes** (i.e. one left/right sample). Will not block, so check
|
||||
the return value to find out how many bytes were actually written.
|
||||
of **4 bytes**. Will not block, so check the return value to find out how
|
||||
many bytes were actually written.
|
||||
|
||||
int availableForWrite()
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Returns the number of **32-bit L/R samples** that can be written without
|
||||
Returns the number of L/R samples that can be written without
|
||||
potentially blocking.
|
||||
|
||||
bool setFrequency(int newFreq)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Adjusts the I2S output frequency. When changing frequency while I2S output
|
||||
is underway, be sure to use ``I2S.flush()`` before changing the frequency to
|
||||
ensure the older data is played at the right frequency.
|
||||
int read()
|
||||
~~~~~~~~~~
|
||||
Reads a single sample of I2S data, whatever the I2S sample size is configured.
|
||||
Will not return until data is available.
|
||||
|
||||
size_t write(int16_t)
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
Writes a single 16-bit left or right sample to the I2S output buffer. The
|
||||
user is required to ensure that an even number of samples is written (i.e.
|
||||
left and right) over the application lifetime. The application also needs
|
||||
to track which sample is next to be written (right/left). For this reason,
|
||||
the ``write(void *b, size_t lrsamples)`` call may be easier and faster to use.
|
||||
int peek()
|
||||
~~~~~~~~~~
|
||||
Returns the next sample to be read from the I2S buffer (without actually
|
||||
removing it).
|
||||
|
||||
size_t write(const void \*buffer, size_t lrsamples)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Writes up to size left+right packed samples to the I2S device. Non-blocking,
|
||||
will writefrom 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.)
|
||||
void onTransmit(void (\*fn)(void))
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Sets a callback to be called when an I2S DMA buffer is fully transmitted.
|
||||
Will be in an interrupt context so the specified function must operate
|
||||
quickly and not use blocking calls like delay() or write to the I2S.
|
||||
|
||||
The ``onTransmit`` callback is not supported.
|
||||
void onReceive(void (\*fn)(void))
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Sets a callback to be called when an I2S DMA buffer is fully read in.
|
||||
Will be in an interrupt context so the specified function must operate
|
||||
quickly and not use blocking calls like delay() or read from the I2S.
|
||||
|
||||
See the `ESP8266Audio <https://github.com/earlephilhower/ESP8266Audio>`_ 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.
|
||||
|
||||
@@ -40,8 +40,6 @@ To install via GIT (for latest and greatest versions):
|
||||
git submodule update --init
|
||||
cd pico-sdk
|
||||
git submodule update --init
|
||||
cd pico-extras
|
||||
git submodule update --init
|
||||
cd ../tools
|
||||
python3 ./get.py
|
||||
|
||||
|
||||
+4
-1
@@ -29,5 +29,8 @@ A ``SoftwareSerial`` wrapper is included to provide plug-and-play compatibility
|
||||
with the Arduino `Software Serial <https://docs.arduino.cc/learn/built-in-libraries/software-serial>`_
|
||||
library. Use the normal ``#include <SoftwareSerial.h>`` to include it. The following
|
||||
differences from the Arduino standard are present:
|
||||
|
||||
* Inverted mode is not supported
|
||||
* All ports are always listening. The ``listen`` call is a no-op, and ``isListening()`` always returns ``true`` .
|
||||
* All ports are always listening
|
||||
* ``listen`` call is a no-op
|
||||
* ``isListening()`` always returns ``true``
|
||||
|
||||
+3
-4
@@ -32,15 +32,14 @@ using the ``setFIFOSize`` call prior to calling ``begin()``
|
||||
Serial1.begin(baud);
|
||||
|
||||
The FIFO is normally handled via an interrupt, which reduced CPU load and
|
||||
makes it less likely to lose characters. However, the FIFO introduces up
|
||||
to 32 bit-times of delay before serial data is available to the application.
|
||||
makes it less likely to lose characters.
|
||||
|
||||
For applications where this is an issue (i.e. very low baud), use
|
||||
For applications where an IRQ driven serial port is not appropriate, use
|
||||
``setPollingMode(true)`` before calling ``begin()``
|
||||
|
||||
.. code:: cpp
|
||||
Serial1.setPollingMode(true);
|
||||
Serial1.begin(110)
|
||||
Serial1.begin(300)
|
||||
|
||||
For detailed information about the Serial ports, see the
|
||||
Arduino `Serial Reference <https://www.arduino.cc/reference/en/language/functions/communication/serial/>`_ .
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#define configUSE_QUEUE_SETS 1
|
||||
#define configSUPPORT_DYNAMIC_ALLOCATION 1
|
||||
#define configSUPPORT_STATIC_ALLOCATION 0
|
||||
#define configSTACK_DEPTH_TYPE uint32_t
|
||||
|
||||
/* Run time stats related definitions. */
|
||||
void vMainConfigureTimerForRunTimeStats( void );
|
||||
|
||||
@@ -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.h>
|
||||
|
||||
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 */
|
||||
}
|
||||
@@ -5,56 +5,50 @@
|
||||
|
||||
created 17 November 2016
|
||||
by Sandeep Mistry
|
||||
modified for ESP8266 by Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
modified for RP2040 by Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
|
||||
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 <I2S.h>
|
||||
|
||||
// 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++;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
name=I2S
|
||||
version=1.0
|
||||
version=2.0
|
||||
author=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
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
|
||||
|
||||
|
||||
@@ -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 <earlephilhower@yahoo.com>
|
||||
|
||||
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 <Arduino.h>
|
||||
#include <vector>
|
||||
#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <earlephilhower@yahoo.com>
|
||||
|
||||
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 <Arduino.h>
|
||||
#include <vector>
|
||||
|
||||
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<AudioBuffer*> _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;
|
||||
};
|
||||
+280
-156
@@ -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 <earlephilhower@yahoo.com>
|
||||
Copyright (c) 2022 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
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 <Arduino.h>
|
||||
#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 <Adafruit_TinyUSB.h>
|
||||
#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();
|
||||
}
|
||||
|
||||
+80
-42
@@ -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 <earlephilhower@yahoo.com>
|
||||
Copyright (c) 2022 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
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 <Arduino.h>
|
||||
#include "pico/audio_i2s.h"
|
||||
#include <vector>
|
||||
#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;
|
||||
};
|
||||
|
||||
@@ -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 <earlephilhower@yahoo.com>
|
||||
;
|
||||
; 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));
|
||||
}
|
||||
|
||||
%}
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "framework-arduinopico",
|
||||
"version": "1.20000.0",
|
||||
"version": "1.20001.0",
|
||||
"description": "Arduino Wiring-based Framework (RPi Pico RP2040)",
|
||||
"keywords": [
|
||||
"framework",
|
||||
|
||||
@@ -110,6 +110,8 @@ sed 's/^tools.uf2conv.cmd=.*//g' | \
|
||||
sed 's/^#tools.uf2conv.cmd=/tools.uf2conv.cmd=/g' | \
|
||||
sed 's/^tools.picoprobe.cmd=.*//g' | \
|
||||
sed 's/^#tools.picoprobe.cmd=/tools.picoprobe.cmd=/g' | \
|
||||
sed 's/^tools.picodebug.cmd=.*//g' | \
|
||||
sed 's/^#tools.picodebug.cmd=/tools.picodebug.cmd=/g' | \
|
||||
sed "s/version=.*/version=$ver/g" |\
|
||||
sed -E "s/name=([a-zA-Z0-9\ -]+).*/name=\1($ver)/g"\
|
||||
> $outdir/platform.txt
|
||||
|
||||
-1
Submodule pico-extras deleted from 77eae28366
+2
-2
@@ -20,7 +20,7 @@
|
||||
# https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5---3rd-party-Hardware-specification
|
||||
|
||||
name=Raspberry Pi RP2040 Boards
|
||||
version=2.0.0
|
||||
version=2.0.1
|
||||
|
||||
runtime.tools.pqt-gcc.path={runtime.platform.path}/system/arm-none-eabi
|
||||
runtime.tools.pqt-python3.path={runtime.platform.path}/system/python3
|
||||
@@ -146,7 +146,6 @@ tools.uf2conv.upload.params.verbose=
|
||||
tools.uf2conv.upload.params.quiet=
|
||||
tools.uf2conv.upload.pattern="{cmd}" -I "{runtime.platform.path}/tools/uf2conv.py" --serial "{serial.port}" --family RP2040 --deploy "{build.path}/{build.project_name}.uf2"
|
||||
|
||||
|
||||
#tools.picoprobe.cmd={runtime.tools.pqt-openocd.path}
|
||||
tools.picoprobe.cmd={runtime.platform.path}/system/openocd
|
||||
tools.picoprobe.upload.protocol=picoprobe
|
||||
@@ -154,6 +153,7 @@ tools.picoprobe.upload.params.verbose=
|
||||
tools.picoprobe.upload.params.quiet=
|
||||
tools.picoprobe.upload.pattern="{cmd}/bin/openocd" -f "interface/picoprobe.cfg" -f "target/rp2040.cfg" -s "{cmd}/share/openocd/scripts" -c "program {{build.path}/{build.project_name}.elf} verify reset exit"
|
||||
|
||||
#tools.picodebug.cmd={runtime.tools.pqt-openocd.path}
|
||||
tools.picodebug.cmd={runtime.platform.path}/system/openocd
|
||||
tools.picodebug.upload.protocol=pico-debug
|
||||
tools.picodebug.upload.params.verbose=
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
#define PIN_NEOPIXEL (12u)
|
||||
#define NEOPIXEL_POWER (11u)
|
||||
|
||||
// Serial1 not pinned out
|
||||
#define PIN_SERIAL1_TX (31u)
|
||||
#define PIN_SERIAL1_RX (31u)
|
||||
// Serial1
|
||||
#define PIN_SERIAL1_TX (28u) // marked A1 on the Board
|
||||
#define PIN_SERIAL1_RX (29u) // marked A0 on the Board
|
||||
|
||||
// Serial2 is the only one labelled
|
||||
#define PIN_SERIAL2_TX (20u)
|
||||
#define PIN_SERIAL2_RX (5u)
|
||||
// Serial2
|
||||
#define PIN_SERIAL2_TX (20u) // marked TX on the Board
|
||||
#define PIN_SERIAL2_RX (5u) // marked RX on the Board
|
||||
|
||||
// SPI
|
||||
#define PIN_SPI0_MISO (4u)
|
||||
|
||||
@@ -37,9 +37,7 @@
|
||||
#define SPI_HOWMANY (1u)
|
||||
#define WIRE_HOWMANY (2u)
|
||||
|
||||
#define __PIN_A0 (29u)
|
||||
#define __PIN_A1 (28u)
|
||||
#define __PIN_A2 (31u)
|
||||
#define __PIN_A3 (31u)
|
||||
#define __PIN_A0 (31u)
|
||||
#define __PIN_A1 (31u)
|
||||
|
||||
#include "../generic/common.h"
|
||||
|
||||
Reference in New Issue
Block a user