Compare commits

..
12 Commits
Author SHA1 Message Date
Earle F. Philhower, III 7b296a4dc9 Remove pico-extras from package 2022-05-10 08:44:18 -07:00
Earle F. Philhower, III 61ac01c31b Update version 2022-05-10 08:42:22 -07:00
Earle F. Philhower, III 79a2128704 Remove doc refs to Pico-Extras
Fixes #578
2022-05-08 09:27:48 -07:00
Earle F. Philhower, III 0c9359cddd Fix picoprobe and pico-debug upload recipes (#577)
Need to munge the platform.txt on release.

Fixes #576
2022-05-07 18:53:02 -07:00
Arya11111andArya11111 dbf855e1aa Modify DFRobot pins_arduino.h (#573)
Co-authored-by: Arya11111 <xuepeng@dfrobot.com>
2022-05-05 11:35:20 -07:00
Earle F. Philhower, III 0afd6ae54f Fix order of bootsel IRQ/idle calls (#572)
Reverse the order of IRQ and idle restore to match the way
they were initiated.
2022-05-04 18:48:35 -07:00
Earle F. Philhower, III 07500e89db Rewrite I2S from scratch, add I2S input support (#569)
Rewrite the I2S code from scratch to eliminate the dependence on the
pico-extras implementation and to support I2S input as well.

8-bit, 16-bit, 24-bit, and 32-bit words are supported.

Multiple I2S ports are allowed (theoretically up to 6 because
2 DMA channels are required per port).

I2S input and I2S output are supported.

Add input example

Fixes #535
Fixes #99
Fixes #562
2022-05-04 18:43:27 -07:00
iKK001andStephan Korner 3adc1c5321 Correct Serial1 and Serial2 pinout definitions added (#570)
Co-authored-by: Stephan Korner <stephan.korner@trihow.com>
2022-05-04 13:04:01 -07:00
ogatatsu 55394a7dcd Fix compile error when configSUPPORT_STATIC_ALLOCATION is set to 1 (#564) (#566) 2022-04-30 19:07:21 -07:00
Earle F. Philhower, III 230758b1ee Add invert option to SWSerial (#563)
Thanks to @StefanKellerAC !
2022-04-28 08:17:12 -07:00
Earle F. Philhower, III d7e02c6f34 Fix SWSerial polarity. (#560) 2022-04-27 21:05:16 -07:00
Earle F. Philhower, III c32ad45724 Clean up some minor doc errors/formats (#559) 2022-04-27 16:07:27 -07:00
29 changed files with 1181 additions and 323 deletions
+2 -10
View File
@@ -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.
-3
View File
@@ -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
+3 -4
View File
@@ -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.
+1 -1
View File
@@ -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;
}
+2 -2
View File
@@ -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"
+1 -1
View File
@@ -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 -3
View File
@@ -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
View File
@@ -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.
-2
View File
@@ -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
View File
@@ -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
View File
@@ -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/>`_ .
+1
View File
@@ -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++;
+15 -1
View File
@@ -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
+3 -2
View File
@@ -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
+256
View File
@@ -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);
}
}
}
+69
View File
@@ -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
View File
@@ -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
View File
@@ -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;
};
+112
View File
@@ -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));
}
%}
+112
View File
@@ -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
View File
@@ -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",
+2
View File
@@ -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
Submodule pico-extras deleted from 77eae28366
+2 -2
View File
@@ -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=
+6 -6
View File
@@ -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"