Skip to content

Commit 1eb48f7

Browse files
Add I2S output support and I2S class/library (earlephilhower#73)
Using the PIO-driven I2S from pico-extras, add I2S output support. Be sure to `git submodule update --init` to get the new directories.
1 parent 29f272e commit 1eb48f7

File tree

12 files changed

+436
-7
lines changed

12 files changed

+436
-7
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@
77
[submodule "system/pyserial"]
88
path = tools/pyserial
99
url = https://github.com/pyserial/pyserial.git
10+
[submodule "pico-extras"]
11+
path = pico-extras
12+
url = https://github.com/raspberrypi/pico-extras.git

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ To install via GIT (for latest and greatest versions):
4040
mkdir -p ~/Arduino/hardware/pico
4141
git clone https://github.com/earlephilhower/arduino-pico.git ~/Arduino/hardware/pico/rp2040
4242
cd ~/Arduino/hardware/pico/rp2040
43-
git submodule init
44-
git submodule update
43+
git submodule update --init
4544
cd pico-sdk
46-
git submodule init
47-
git submodule update
45+
git submodule update --init
46+
cd pico-extras
47+
git submodule update --init
4848
cd ../tools
4949
python3 ./get.py
5050
`````
@@ -97,17 +97,18 @@ Relatively stable and very functional, but bug reports and PRs always accepted.
9797
* Hardware UART
9898
* Servo
9999
* Overclocking and underclocking from the menus
100+
* I2S audio output
100101
* printf (i.e. debug) output over USB serial
101102
102103
The RP2040 PIO state machines (SMs) are used to generate jitter-free:
103104
* Servos
104105
* Tones
106+
* I2S Output
105107
106108
# Todo
107109
Some major features I want to add are:
108110
* Installable filesystem support (SD, LittleFS, etc.)
109111
* Updated debug infrastructure
110-
* I2S port from pico-extras
111112
112113
# Tutorials from Across the Web
113114
Here are some links to coverage and additional tutorials for using `arduino-pico`

lib/libpico.a

153 KB
Binary file not shown.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
This example generates a square wave based tone at a specified frequency
3+
and sample rate. Then outputs the data using the I2S interface to a
4+
MAX08357 I2S Amp Breakout board.
5+
6+
created 17 November 2016
7+
by Sandeep Mistry
8+
modified for ESP8266 by Earle F. Philhower, III <earlephilhower@yahoo.com>
9+
*/
10+
11+
#include <I2S.h>
12+
13+
const int frequency = 440; // frequency of square wave in Hz
14+
const int amplitude = 500; // amplitude of square wave
15+
const int sampleRate = 8000; // sample rate in Hz
16+
17+
const int halfWavelength = (sampleRate / frequency); // half wavelength of square wave
18+
19+
short sample = amplitude; // current sample value
20+
int count = 0;
21+
22+
void setup() {
23+
Serial.begin(115200);
24+
Serial.println("I2S simple tone");
25+
26+
// start I2S at the sample rate with 16-bits per sample
27+
if (!I2S.begin(sampleRate)) {
28+
Serial.println("Failed to initialize I2S!");
29+
while (1); // do nothing
30+
}
31+
}
32+
33+
void loop() {
34+
if (count % halfWavelength == 0) {
35+
// invert the sample every half wavelength count multiple to generate square wave
36+
sample = -1 * sample;
37+
}
38+
39+
// write the same sample twice, once for left and once for the right channel
40+
I2S.write(sample);
41+
I2S.write(sample);
42+
43+
// increment the counter for the next sample
44+
count++;
45+
}
46+

libraries/I2S/keywords.txt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#######################################
2+
# Syntax Coloring Map I2S
3+
#######################################
4+
5+
#######################################
6+
# Datatypes (KEYWORD1)
7+
#######################################
8+
9+
I2S KEYWORD1
10+
11+
#######################################
12+
# Methods and Functions (KEYWORD2)
13+
#######################################
14+
begin KEYWORD2
15+
end KEYWORD2
16+
17+
onReceive KEYWORD2
18+
onTransmit KEYWORD2
19+
20+
#######################################
21+
# Constants (LITERAL1)
22+
#######################################
23+
I2S_PHILIPS_MODE LITERAL1

libraries/I2S/library.properties

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
name=I2S
2+
version=1.0
3+
author=Earle F. Philhower, III <earlephilhower@yahoo.com>
4+
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
5+
sentence=Enables the communication with devices that use the Inter-IC Sound (I2S) Bus. Specific implementation for RP2040, based off of pico-extras samples.
6+
paragraph=
7+
category=Communication
8+
url=http://www.arduino.cc/en/Reference/I2S
9+
architectures=rp2040

libraries/I2S/src/I2S.cpp

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/*
2+
* I2S Master library for the Raspberry Pi Pico RP2040
3+
*
4+
* Copyright (c) 2021 Earle F. Philhower, III <earlephilhower@yahoo.com>
5+
*
6+
* This library is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 2.1 of the License, or (at your option) any later version.
10+
*
11+
* This library is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public
17+
* License along with this library; if not, write to the Free Software
18+
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19+
*/
20+
21+
#include <Arduino.h>
22+
#include "I2S.h"
23+
24+
I2SClass::I2SClass() {
25+
_running = false;
26+
_pool = nullptr;
27+
_curBuff = nullptr;
28+
_bps = 0;
29+
_writtenHalf = false;
30+
}
31+
32+
bool I2SClass::begin(long sampleRate, pin_size_t sck, pin_size_t data) {
33+
if (_running) {
34+
return false;
35+
}
36+
37+
bzero(&_audio_format, sizeof(_audio_format));
38+
_audio_format.sample_freq = (uint32_t)sampleRate;
39+
_audio_format.format = AUDIO_BUFFER_FORMAT_PCM_S16;
40+
_audio_format.channel_count = 2;
41+
42+
bzero(&_producer_format, sizeof(_producer_format));
43+
_producer_format.format = &_audio_format;
44+
_producer_format.sample_stride = 4;
45+
46+
if (!_pool) {
47+
_pool = audio_new_producer_pool(&_producer_format, 3, 256);
48+
}
49+
50+
bzero(&_config, sizeof(_config));
51+
_config.data_pin = data;
52+
_config.clock_pin_base = sck;
53+
_config.dma_channel = 0;
54+
_config.pio_sm = 1;
55+
56+
if (!audio_i2s_setup(&_audio_format, &_config)) {
57+
return false;
58+
}
59+
60+
if (!audio_i2s_connect(_pool)) {
61+
return false;
62+
}
63+
64+
audio_i2s_set_enabled(true);
65+
66+
_curBuff = take_audio_buffer(_pool, true);
67+
_curBuff->sample_count = 0;
68+
69+
_bps = 16;
70+
_freq = sampleRate;
71+
_running = true;
72+
return true;
73+
}
74+
75+
void I2SClass::end() {
76+
if (_running) {
77+
audio_i2s_set_enabled(false);
78+
if (_curBuff) {
79+
release_audio_buffer(_pool, _curBuff);
80+
_curBuff = nullptr;
81+
}
82+
}
83+
_running = false;
84+
_bps = 0;
85+
_freq = 0;
86+
_writtenHalf = false;
87+
}
88+
89+
int I2SClass::availableForWrite() {
90+
if (!_running) {
91+
return 0;
92+
}
93+
// Can we get a whole new buffer to work with?
94+
if (!_curBuff) {
95+
_curBuff = take_audio_buffer(_pool, false);
96+
_curBuff->sample_count = 0;
97+
}
98+
if (!_curBuff) {
99+
return false;
100+
}
101+
return _curBuff->max_sample_count - _curBuff->sample_count;
102+
}
103+
104+
void I2SClass::flush() {
105+
if (!_curBuff || !_curBuff->sample_count) {
106+
return;
107+
}
108+
_audio_format.sample_freq = _freq;
109+
give_audio_buffer(_pool, _curBuff);
110+
_curBuff = nullptr;
111+
}
112+
113+
bool I2SClass::setFrequency(int newFreq) {
114+
if (newFreq != _freq) {
115+
flush();
116+
_freq = newFreq;
117+
}
118+
return true;
119+
}
120+
121+
size_t I2SClass::write(uint8_t s) {
122+
return write((int16_t)s);
123+
}
124+
125+
size_t I2SClass::write(const uint8_t *buffer, size_t size) {
126+
return write((const void *)buffer, size);
127+
}
128+
129+
size_t I2SClass::write(int16_t s) {
130+
if (!_running) {
131+
return 0;
132+
}
133+
134+
// Because our HW really wants 32b writes, store any 16b writes until another
135+
// 16b write comes in and then send the combined write down.
136+
if (_bps == 16) {
137+
if (_writtenHalf) {
138+
_writtenData <<= 16;
139+
_writtenData |= 0xffff & s;
140+
_writtenHalf = false;
141+
if (!_curBuff) {
142+
_curBuff = take_audio_buffer(_pool, true);
143+
_curBuff->sample_count = 0;
144+
}
145+
int32_t *samples = (int32_t *)_curBuff->buffer->bytes;
146+
samples[_curBuff->sample_count++] = _writtenData;
147+
if (_curBuff->sample_count == _curBuff->max_sample_count) {
148+
_audio_format.sample_freq = _freq;
149+
give_audio_buffer(_pool, _curBuff);
150+
_curBuff = nullptr;
151+
}
152+
} else {
153+
_writtenHalf = true;
154+
_writtenData = s & 0xffff;
155+
}
156+
}
157+
return 1;
158+
}
159+
160+
// Mostly non-blocking
161+
size_t I2SClass::write(const void *buffer, size_t size) {
162+
if (!_running) {
163+
return 0;
164+
}
165+
// We have no choice here because we need to write at least 1 byte...
166+
if (!_curBuff) {
167+
_curBuff = take_audio_buffer(_pool, true);
168+
_curBuff->sample_count = 0;
169+
}
170+
171+
int32_t *inSamples = (int32_t *)buffer;
172+
int written = 0;
173+
int wantToWrite = size / 4;
174+
while (wantToWrite) {
175+
if (!_curBuff) {
176+
_curBuff = take_audio_buffer(_pool, false);
177+
if (_curBuff) {
178+
_curBuff->sample_count = 0;
179+
} else {
180+
break;
181+
}
182+
}
183+
184+
int avail = _curBuff->max_sample_count - _curBuff->sample_count;
185+
int writeSize = (avail > wantToWrite) ? wantToWrite : avail;
186+
int32_t *samples = (int32_t *)_curBuff->buffer->bytes;
187+
memcpy(samples + _curBuff->sample_count, inSamples, writeSize * 4);
188+
_curBuff->sample_count += writeSize;
189+
inSamples += writeSize;
190+
written += writeSize;
191+
wantToWrite -= writeSize;
192+
if (_curBuff->sample_count == _curBuff->max_sample_count) {
193+
_audio_format.sample_freq = _freq;
194+
give_audio_buffer(_pool, _curBuff);
195+
_curBuff = nullptr;
196+
}
197+
}
198+
return written;
199+
}
200+
201+
I2SClass I2S;

0 commit comments

Comments
 (0)