Skip to content

Commit 0fd2d04

Browse files
Replace external SPIRAM lib w/internal optimized (#250)
* Replace external SPIRAM lib w/internal optimized Instead of requiring everyone to download and install another Arduino library to use this lib, include an optimized, self-contained one from the 8266 VM PR: esp8266/Arduino#6994 Does change the API as it now requires a HW chip select, so that parameter is removed from the SpiRAM buffer object. * Redo the FIFO to try and be faster * Fix the SPI interface, buffering. SPI FIFO needs special care so do all work in a RAM copy and the copy it over manually w/32b writes. Ensure the SPI command is done before touching the FIFO. Fix buffering organization. Make the buffer report status as a #### bar. * Fix infinite wait on EOF * Add SW chip select support * Update URL to one that's live today * Clean up metadata printout * Clean up readme refs to obsolete lib * Slow down the DIO->SIO reset sequence * Update readme to remove old SPIRam library ref Also add the resistor to the 1T circuit to avoid transistor overheating.
1 parent be0ee82 commit 0fd2d04

File tree

5 files changed

+376
-136
lines changed

5 files changed

+376
-136
lines changed

README.md

+10-22
Original file line numberDiff line numberDiff line change
@@ -30,29 +30,16 @@ A neat MQTT-driven ESP8266 light-and-sound device (alarm? toy? who can say!) was
3030
A very interesting "linear clock" with a stepper motor, NTP time keeping, and configurable recorded chimes with schematics, 3D printer plans, and source code, is now available http://home.kpn.nl/bderogee1980/projects/linear_clock/linear_clock.html
3131

3232
## Prerequisites
33-
First, make sure you are running the 2.4 or GIT head version of the Arduino libraries for ESP8266, or the latest ESP32 SDK from Espressif.
33+
First, make sure you are running the 2.6.3/later or GIT head version of the Arduino libraries for ESP8266, or the latest ESP32 SDK from Espressif.
3434

3535
You can use GIT to pull right from GitHub: see [this README](https://github.com/esp8266/Arduino/blob/master/README.md#using-git-version) for detailed instructions.
3636

37-
## ESP-32 SPIFFS Errors
38-
The latest official release of the ESP32-Arduino seems to have broken SPIFFS, but a patch has just been committed to git head. If you want to run SPIFFS, please follow the directions below, courtesy of @rfestag:
39-
```sh
40-
cd ~/Arduino/hardware/espressif/esp32 # Or wherever you have it installed
41-
git pull # Update to the latest
42-
cd tools
43-
python get.py # On my system, I have python3 installed by default, so I had to run python2.7 get.py
44-
# Re-upload files using the new mkspiffs that is installed
45-
# Then reload your sketch
46-
```
47-
Be sure to use the [ESP32 SPIFFS](https://github.com/me-no-dev/arduino-esp32fs-plugin) upload plugin before running your sketch to upload the data files once the fixed IDE is set up.
48-
4937
## Installation
50-
Install the library and the SPI driver library in your ~/Arduino/libraries
38+
Install the library in your ~/Arduino/libraries
5139
```sh
5240
mkdir -p ~/Arduino/libraries
5341
cd ~/Arduino/libraries
5442
git clone https://github.com/earlephilhower/ESP8266Audio
55-
git clone https://github.com/Gianbacchio/ESP8266_Spiram
5643
```
5744

5845
When in the IDE please select the following options on the ESP8266:
@@ -185,7 +172,7 @@ There are many other variants out there, and they should all work reasonably wel
185172
## Software I2S Delta-Sigma DAC (i.e. playing music with a single transistor and speaker)
186173
For the best fidelity, and stereo to boot, spend the money on a real I2S DAC. Adafruit makes a great mono one with amplifier, and you can find stereo unamplified ones on eBay or elsewhere quite cheaply. However, thanks to the software delta-sigma DAC with 32x oversampling (up to 128x if the audio rate is low enough) you can still have pretty good sound!
187174
188-
Use the AudioOutputI2S*No*DAC object instead of the AudioOutputI2S in your code, and the following schematic to drive a 2-3W speaker using a single $0.05 NPN 2N3904 transistor:
175+
Use the `AudioOutputI2S*No*DAC` object instead of the `AudioOutputI2S` in your code, and the following schematic to drive a 2-3W speaker using a single $0.05 NPN 2N3904 transistor and ~1K resistor:
189176
190177
```
191178
2N3904 (NPN)
@@ -197,7 +184,7 @@ Use the AudioOutputI2S*No*DAC object instead of the AudioOutputI2S in your code,
197184
| | | A|
198185
ESP8266-GND ------------------+ | +------+ K|
199186
| | | E|
200-
ESP8266-I2SOUT (Rx) -------------+ | \ R|
187+
ESP8266-I2SOUT (Rx) -----/\/\/\--+ | \ R|
201188
| +-|
202189
USB 5V -----------------------------+
203190

@@ -207,15 +194,15 @@ If you don't have a 5V source available on your ESP model, you can use the 5V fr
207194
208195
Connections are as a follows:
209196
```
210-
ESP8266-RX(I2S tx) -- 2N3904 Base
197+
ESP8266-RX(I2S tx) -- Resistor (~1K ohm, not critical) -- 2N3904 Base
211198
ESP8266-GND -- 2N3904 Emitter
212199
USB-5V -- Speaker + Terminal
213200
2N3904-Collector -- Speaker - Terminal
214201
```
215202
216-
Basically the transistor acts as a switch and requires only a drive of 1/beta (~1/1000 for the transistor specified) times the speaker current. As shown you've got a max current of (5-0.7)/8=540mA and a power of 0.54^2 * 8 = ~2.3W into the speaker.
203+
*NOTE*: A prior version of this schematic had a direct connection from the ESP8266 to the base of the transistor. While this does provide the maximum amplitude, it also can draw more current from the 8266 than is safe, and can also cause the transistor to overheat.
217204
218-
When using the software delta-sigma DAC, even though our playback circuit is not using the LRCLK or BCLK pins, the ESP8266 internal hardware *will* be driving them. So these pins cannot be used as outputs in your application. However, you can use the LRCLK and BCLK pins as *inputs*. Simply start playback, then use the standard pinMode(xxx, INPUT/INPUT_PULLUP) Arduino commands and you can, for example, use those two pins to read a button or sensor.
205+
As of the latest ESP8266Audio release, with the software delta-sigma DAC the LRCLK and BCLK pins *can* be used by an application. Simply use normal `pinMode` and `dicitalWrite` or `digitalRead` as desired.
219206
220207
### High pitched buzzing with the 1-T circuit
221208
The 1-T amp can _NOT_ drive any sort of amplified speaker. If there is a power or USB input to the speaker, or it has lights or Bluetooth or a battery, it can _NOT_ be used with this circuit.
@@ -251,8 +238,9 @@ Ground ---------------------+
251238
For ESP8266 with red LED (~1.9Vf drop) you need minimum 150Ohm resistor (12mA max per pin), and output pin is fixed (GPIO3/RX0).On ESP32 it is confgurable with `AudioOutputSPDIF(gpio_num)`.
252239
253240
## Using external SPI RAM to increase buffer
254-
A class allows you to use a 23lc1024 SPI RAM from Microchip as input buffer. This chip connects to ESP8266 HSPI port and uses an [external SPI RAM library](https://github.com/Gianbacchio/ESP8266_Spiram).
255-
You need to choose another pin than GPIO15 for Cs as this pin is already used by the I2S port. Here is an example with the Cs pin plugged to GPIO00 on NodeMCU board.
241+
A class allows you to use a 23lc1024 SPI RAM from Microchip as input buffer. This chip connects to ESP8266 HSPI port and provides a large buffer to help avoid hiccus in playback of web streams.
242+
243+
The current version allows for using the standard hardware CS (GPIO15) or any other pin via software at slightly less performance. The following schematic shows one example:
256244
257245
![Example of SPIRAM Schematic](examples/StreamMP3FromHTTP_SPIRAM/Schema_Spiram.png)
258246

examples/StreamMP3FromHTTP_SPIRAM/StreamMP3FromHTTP_SPIRAM.ino

+13-14
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const char *SSID = ".....";
1616
const char *PASSWORD = ".....";
1717

1818
// Randomly picked URL
19-
const char *URL="http://streaming.shoutcast.com/80sPlanet?lang=en-US";
19+
const char *URL="http://kvbstreams.dyndns.org:8000/wkvi-am";
2020

2121
AudioGeneratorMP3 *mp3;
2222
AudioFileSourceICYStream *file;
@@ -29,25 +29,24 @@ void MDCallback(void *cbData, const char *type, bool isUnicode, const char *stri
2929
const char *ptr = reinterpret_cast<const char *>(cbData);
3030
(void) isUnicode; // Punt this ball for now
3131
// Note that the type and string may be in PROGMEM, so copy them to RAM for printf
32-
char s1[32], s2[64];
33-
strncpy_P(s1, type, sizeof(s1));
34-
s1[sizeof(s1)-1]=0;
35-
strncpy_P(s2, string, sizeof(s2));
36-
s2[sizeof(s2)-1]=0;
37-
Serial.printf("METADATA(%s) '%s' = '%s'\n", ptr, s1, s2);
32+
Serial.printf_P(PSTR("METADATA(%s) '%s' = '%s'\n"), ptr, type, string);
3833
Serial.flush();
3934
}
4035

36+
4137
// Called when there's a warning or error (like a buffer underflow or decode hiccup)
4238
void StatusCallback(void *cbData, int code, const char *string)
4339
{
4440
const char *ptr = reinterpret_cast<const char *>(cbData);
45-
// Note that the string may be in PROGMEM, so copy it to RAM for printf
46-
char s1[64];
47-
strncpy_P(s1, string, sizeof(s1));
48-
s1[sizeof(s1)-1]=0;
49-
Serial.printf("STATUS(%s) '%d' = '%s'\n", ptr, code, s1);
50-
Serial.flush();
41+
static uint32_t lastTime = 0;
42+
static int lastCode = -99999;
43+
uint32_t now = millis();
44+
if ((lastCode != code) || (now - lastTime > 1000)) {
45+
Serial.printf_P(PSTR("STATUS(%s) '%d' = '%s'\n"), ptr, code, string);
46+
Serial.flush();
47+
lastTime = now;
48+
lastCode = code;
49+
}
5150
}
5251

5352
void setup()
@@ -73,7 +72,7 @@ void setup()
7372
file = new AudioFileSourceICYStream(URL);
7473
file->RegisterMetadataCB(MDCallback, (void*)"ICY");
7574
// Initialize 23LC1024 SPI RAM buffer with chip select ion GPIO4 and ram size of 128KByte
76-
buff = new AudioFileSourceSPIRAMBuffer(file, 4, 131072);
75+
buff = new AudioFileSourceSPIRAMBuffer(file, 4, 128*1024);
7776
buff->RegisterStatusCB(StatusCallback, (void*)"buffer");
7877
out = new AudioOutputI2SNoDAC();
7978
mp3 = new AudioGeneratorMP3();

src/AudioFileSourceSPIRAMBuffer.cpp

+101-90
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
Copyright (C) 2017 Sebastien Decourriere
66
Based on AudioFileSourceBuffer class from Earle F. Philhower, III
77
8+
Copyright (C) 2020 Earle F. Philhower, III
9+
Rewritten for speed and functionality
10+
811
This program is free software: you can redistribute it and/or modify
912
it under the terms of the GNU General Public License as published by
1013
the Free Software Foundation, either version 3 of the License, or
@@ -26,131 +29,139 @@
2629

2730
AudioFileSourceSPIRAMBuffer::AudioFileSourceSPIRAMBuffer(AudioFileSource *source, uint8_t csPin, uint32_t buffSizeBytes)
2831
{
29-
Spiram = new ESP8266Spiram(csPin, 40e6);
30-
Spiram->begin();
31-
Spiram->setSeqMode();
32-
ramSize = buffSizeBytes;
33-
writePtr = 0;
34-
readPtr = 0;
35-
src = source;
36-
length = 0;
37-
filled = false;
38-
bytesAvailable = 0;
39-
audioLogger->printf_P(PSTR("SPI RAM buffer size: %u Bytes\n"), ramSize);
32+
ram.begin(40, csPin);
33+
ramSize = buffSizeBytes;
34+
writePtr = 0;
35+
readPtr = 0;
36+
filled = false;
37+
src = source;
38+
audioLogger->printf_P(PSTR("SPI RAM buffer size: %u Bytes\n"), ramSize);
4039
}
4140

4241
AudioFileSourceSPIRAMBuffer::~AudioFileSourceSPIRAMBuffer()
4342
{
43+
ram.end();
4444
}
4545

4646
bool AudioFileSourceSPIRAMBuffer::seek(int32_t pos, int dir)
4747
{
48-
// Invalidate
49-
readPtr = 0;
50-
writePtr = 0;
51-
length = 0;
52-
return src->seek(pos, dir);
48+
// Invalidate
49+
readPtr = 0;
50+
writePtr = 0;
51+
filled = false;
52+
return src->seek(pos, dir);
5353
}
5454

5555
bool AudioFileSourceSPIRAMBuffer::close()
5656
{
57-
return src->close();
57+
return src->close();
5858
}
5959

6060
bool AudioFileSourceSPIRAMBuffer::isOpen()
6161
{
62-
return src->isOpen();
62+
return src->isOpen();
6363
}
6464

6565
uint32_t AudioFileSourceSPIRAMBuffer::getSize()
6666
{
67-
return src->getSize();
67+
return src->getSize();
6868
}
6969

7070
uint32_t AudioFileSourceSPIRAMBuffer::getPos()
7171
{
72-
return src->getPos();
72+
return src->getPos() - (writePtr - readPtr);
7373
}
7474

7575
uint32_t AudioFileSourceSPIRAMBuffer::read(void *data, uint32_t len)
7676
{
77-
uint32_t bytes = 0;
78-
// Check if the buffer isn't empty, otherwise we try to fill completely
79-
if (!filled) {
80-
uint8_t buffer[256];
81-
writePtr = readPtr = 0;
82-
uint16_t toRead = sizeof(buffer);
83-
// Fill up completely before returning any data at all
84-
audioLogger->printf_P(PSTR("Buffering...\n"));
85-
while (bytesAvailable!=ramSize) {
86-
length = src->read(buffer, toRead);
87-
if(length>0) {
88-
Spiram->write(writePtr, buffer, length);
89-
bytesAvailable+=length;
90-
writePtr = bytesAvailable % ramSize;
91-
if ((ramSize-bytesAvailable)<toRead) {
92-
toRead=ramSize-bytesAvailable;
93-
}
94-
} else {
95-
// EOF, break out of read loop
96-
break;
97-
}
98-
}
99-
writePtr = bytesAvailable % ramSize;
100-
filled = true;
101-
audioLogger->printf_P(PSTR("Filling Done !\n"));
102-
}
103-
104-
// audioLogger->printf("Buffer: %u%\n", bytesAvailable*100/ramSize);
105-
106-
uint8_t *ptr = reinterpret_cast<uint8_t*>(data);
107-
uint32_t toReadFromBuffer = (len < bytesAvailable) ? len : bytesAvailable;
108-
if (toReadFromBuffer>0) {
109-
// Pull from buffer until we've got none left or we've satisfied the request
110-
Spiram->read(readPtr, ptr, toReadFromBuffer);
111-
readPtr = (readPtr+toReadFromBuffer) % ramSize;
112-
bytes = toReadFromBuffer;
113-
bytesAvailable-=toReadFromBuffer;
114-
len-=toReadFromBuffer;
115-
ptr += toReadFromBuffer;
116-
}
117-
118-
// If len>O there is no data left in buffer and we try to read more directly from source.
119-
// Then, we trigger a complete buffer refill
120-
if (len) {
121-
bytes += src->read(ptr, len);
122-
bytesAvailable = 0;
123-
filled = false;
124-
}
125-
return bytes;
77+
uint32_t bytes = 0;
78+
79+
// Check if the buffer isn't empty, otherwise we try to fill completely
80+
if (!filled) {
81+
cb.st(999, PSTR("Filling buffer..."));
82+
uint8_t buffer[256];
83+
writePtr = 0;
84+
readPtr = 0;
85+
// Fill up completely before returning any data at all
86+
do {
87+
int toRead = std::min(ramSize - (writePtr - readPtr), sizeof(buffer));
88+
int length = src->read(buffer, toRead);
89+
if (length > 0) {
90+
#ifdef FAKERAM
91+
for (size_t i=0; i<length; i++) fakeRAM[(i+writePtr)%ramSize] = buffer[i];
92+
#else
93+
ram.writeBytes(writePtr % ramSize, buffer, length);
94+
#endif
95+
writePtr += length;
96+
} else {
97+
// EOF, break out of read loop
98+
break;
99+
}
100+
} while ((writePtr - readPtr) < ramSize);
101+
filled = true;
102+
cb.st(999, PSTR("Buffer filled..."));
103+
}
104+
105+
// Read up to the entire buffer from RAM
106+
uint32_t toReadFromBuffer = std::min(len, writePtr - readPtr);
107+
uint8_t *ptr = reinterpret_cast<uint8_t*>(data);
108+
if (toReadFromBuffer > 0) {
109+
#ifdef FAKERAM
110+
for (size_t i=0; i<toReadFromBuffer; i++) ptr[i] = fakeRAM[(i+readPtr)%ramSize];
111+
#else
112+
ram.readBytes(readPtr % ramSize, ptr, toReadFromBuffer);
113+
#endif
114+
readPtr += toReadFromBuffer;
115+
ptr += toReadFromBuffer;
116+
bytes += toReadFromBuffer;
117+
len -= toReadFromBuffer;
118+
}
119+
120+
// If len>0 there is no data left in buffer and we try to read more directly from source.
121+
// Then, we trigger a complete buffer refill
122+
if (len) {
123+
bytes += src->read(data, len);
124+
filled = false;
125+
}
126+
return bytes;
126127
}
127128

128129
void AudioFileSourceSPIRAMBuffer::fill()
129130
{
130-
// Make sure the buffer is pre-filled before make partial fill.
131-
if (!filled) return;
132-
133-
// Now trying to refill SPI RAM Buffer
134-
uint8_t buffer[128];
135-
// Make sure there is at least buffer size free in RAM
136-
if ((ramSize - bytesAvailable)<sizeof(buffer)) {
137-
return;
138-
}
139-
uint16_t cnt = src->readNonBlock(buffer, sizeof(buffer));
140-
if (cnt) {
141-
Spiram->write(writePtr, buffer, cnt);
142-
bytesAvailable+=cnt;
143-
writePtr = (writePtr + cnt) % ramSize;
144-
#ifdef SPIBUF_DEBUG
145-
audioLogger->printf_P(PSTR("SockRead: %u | RamAvail: %u\n"), cnt, bytesAvailable);
131+
// Make sure the buffer is pre-filled before make partial fill.
132+
if (!filled) return;
133+
134+
for (auto i=0; i<5; i++) {
135+
// Make sure there is at least buffer size free in RAM
136+
uint8_t buffer[128];
137+
if ((ramSize - (writePtr - readPtr)) < sizeof(buffer)) {
138+
return;
139+
}
140+
141+
int cnt = src->readNonBlock(buffer, sizeof(buffer));
142+
if (cnt) {
143+
#ifdef FAKERAM
144+
for (size_t i=0; i<cnt; i++) fakeRAM[(i+writePtr)%ramSize] = buffer[i];
145+
#else
146+
ram.writeBytes(writePtr % ramSize, buffer, cnt);
146147
#endif
147-
}
148-
return;
148+
writePtr += cnt;
149+
}
150+
}
149151
}
150152

151153
bool AudioFileSourceSPIRAMBuffer::loop()
152154
{
153-
if (!src->loop()) return false;
154-
fill();
155-
return true;
155+
static uint32_t last = 0;
156+
if (!src->loop()) return false;
157+
fill();
158+
if ((ESP.getCycleCount() - last) > microsecondsToClockCycles(1000000)) {
159+
last = ESP.getCycleCount();
160+
char str[65];
161+
memset(str, '#', 64);
162+
str[64] = 0;
163+
str[((writePtr - readPtr) * 64)/ramSize] = 0;
164+
cb.st(((writePtr - readPtr) * 100)/ramSize, str);
165+
}
166+
return true;
156167
}

0 commit comments

Comments
 (0)