forked from Edzelf/Esp-radio
-
Notifications
You must be signed in to change notification settings - Fork 40
Expand file tree
/
Copy pathVS1053.cpp
More file actions
352 lines (315 loc) · 11.8 KB
/
VS1053.cpp
File metadata and controls
352 lines (315 loc) · 11.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
/**
* This is a driver library for VS1053 MP3 Codec Breakout
* (Ogg Vorbis / MP3 / AAC / WMA / FLAC / MIDI Audio Codec Chip).
* Adapted for Espressif ESP8266 and ESP32 boards.
*
* version 1.0.1
*
* Licensed under GNU GPLv3 <http://gplv3.fsf.org/>
* Copyright © 2018
*
* @authors baldram, edzelf, MagicCube, maniacbug
*
* Development log:
* - 2011: initial VS1053 Arduino library
* originally written by J. Coliz (github: @maniacbug),
* - 2016: refactored and integrated into Esp-radio sketch
* by Ed Smallenburg (github: @edzelf)
* - 2017: refactored to use as PlatformIO library
* by Marcin Szalomski (github: @baldram | twitter: @baldram)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License or later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "VS1053.h"
VS1053::VS1053(uint8_t _cs_pin, uint8_t _dcs_pin, uint8_t _dreq_pin)
: cs_pin(_cs_pin), dcs_pin(_dcs_pin), dreq_pin(_dreq_pin) {
}
uint16_t VS1053::read_register(uint8_t _reg) const {
uint16_t result;
control_mode_on();
SPI.write(3); // Read operation
SPI.write(_reg); // Register to write (0..0xF)
// Note: transfer16 does not seem to work
result = (SPI.transfer(0xFF) << 8) | // Read 16 bits data
(SPI.transfer(0xFF));
await_data_request(); // Wait for DREQ to be HIGH again
control_mode_off();
return result;
}
void VS1053::write_register(uint8_t _reg, uint16_t _value) const {
control_mode_on();
SPI.write(2); // Write operation
SPI.write(_reg); // Register to write (0..0xF)
SPI.write16(_value); // Send 16 bits data
await_data_request();
control_mode_off();
}
void VS1053::sdi_send_buffer(uint8_t *data, size_t len) {
size_t chunk_length; // Length of chunk 32 byte or shorter
data_mode_on();
while (len) // More to do?
{
await_data_request(); // Wait for space available
chunk_length = len;
if (len > vs1053_chunk_size) {
chunk_length = vs1053_chunk_size;
}
len -= chunk_length;
SPI.writeBytes(data, chunk_length);
data += chunk_length;
}
data_mode_off();
}
void VS1053::sdi_send_fillers(size_t len) {
size_t chunk_length; // Length of chunk 32 byte or shorter
data_mode_on();
while (len) // More to do?
{
await_data_request(); // Wait for space available
chunk_length = len;
if (len > vs1053_chunk_size) {
chunk_length = vs1053_chunk_size;
}
len -= chunk_length;
while (chunk_length--) {
SPI.write(endFillByte);
}
}
data_mode_off();
}
void VS1053::wram_write(uint16_t address, uint16_t data) {
write_register(SCI_WRAMADDR, address);
write_register(SCI_WRAM, data);
}
uint16_t VS1053::wram_read(uint16_t address) {
write_register(SCI_WRAMADDR, address); // Start reading from WRAM
return read_register(SCI_WRAM); // Read back result
}
bool VS1053::testComm(const char *header) {
// Test the communication with the VS1053 module. The result wille be returned.
// If DREQ is low, there is problably no VS1053 connected. Pull the line HIGH
// in order to prevent an endless loop waiting for this signal. The rest of the
// software will still work, but readbacks from VS1053 will fail.
int i; // Loop control
uint16_t r1, r2, cnt = 0;
uint16_t delta = 300; // 3 for fast SPI
if (!digitalRead(dreq_pin)) {
LOG("VS1053 not properly installed!\n");
// Allow testing without the VS1053 module
pinMode(dreq_pin, INPUT_PULLUP); // DREQ is now input with pull-up
return false; // Return bad result
}
// Further TESTING. Check if SCI bus can write and read without errors.
// We will use the volume setting for this.
// Will give warnings on serial output if DEBUG is active.
// A maximum of 20 errors will be reported.
if (strstr(header, "Fast")) {
delta = 3; // Fast SPI, more loops
}
LOG("%s", header); // Show a header
for (i = 0; (i < 0xFFFF) && (cnt < 20); i += delta) {
write_register(SCI_VOL, i); // Write data to SCI_VOL
r1 = read_register(SCI_VOL); // Read back for the first time
r2 = read_register(SCI_VOL); // Read back a second time
if (r1 != r2 || i != r1 || i != r2) // Check for 2 equal reads
{
LOG("VS1053 error retry SB:%04X R1:%04X R2:%04X\n", i, r1, r2);
cnt++;
delay(10);
}
yield(); // Allow ESP firmware to do some bookkeeping
}
return (cnt == 0); // Return the result
}
void VS1053::begin() {
pinMode(dreq_pin, INPUT); // DREQ is an input
pinMode(cs_pin, OUTPUT); // The SCI and SDI signals
pinMode(dcs_pin, OUTPUT);
digitalWrite(dcs_pin, HIGH); // Start HIGH for SCI en SDI
digitalWrite(cs_pin, HIGH);
delay(100);
LOG("\n");
LOG("Reset VS1053...\n");
digitalWrite(dcs_pin, LOW); // Low & Low will bring reset pin low
digitalWrite(cs_pin, LOW);
delay(500);
LOG("End reset VS1053...\n");
digitalWrite(dcs_pin, HIGH); // Back to normal again
digitalWrite(cs_pin, HIGH);
delay(500);
// Init SPI in slow mode ( 0.2 MHz )
VS1053_SPI = SPISettings(200000, MSBFIRST, SPI_MODE0);
// printDetails("Right after reset/startup");
delay(20);
// printDetails("20 msec after reset");
if (testComm("Slow SPI,Testing VS1053 read/write registers...\n")) {
//softReset();
// Switch on the analog parts
write_register(SCI_AUDATA, 44101); // 44.1kHz stereo
// The next clocksetting allows SPI clocking at 5 MHz, 4 MHz is safe then.
write_register(SCI_CLOCKF, 6 << 12); // Normal clock settings multiplyer 3.0 = 12.2 MHz
// SPI Clock to 4 MHz. Now you can set high speed SPI clock.
VS1053_SPI = SPISettings(4000000, MSBFIRST, SPI_MODE0);
write_register(SCI_MODE, _BV(SM_SDINEW) | _BV(SM_LINE1));
testComm("Fast SPI, Testing VS1053 read/write registers again...\n");
delay(10);
await_data_request();
endFillByte = wram_read(0x1E06) & 0xFF;
LOG("endFillByte is %X\n", endFillByte);
//printDetails("After last clocksetting") ;
delay(100);
}
}
void VS1053::setVolume(uint8_t vol) {
// Set volume. Both left and right.
// Input value is 0..100. 100 is the loudest.
uint8_t valueL, valueR; // Values to send to SCI_VOL
curvol = vol; // Save for later use
valueL = vol;
valueR = vol;
if (curbalance < 0) {
valueR = max(0, vol + curbalance);
} else if (curbalance > 0) {
valueL = max(0, vol - curbalance);
}
valueL = map(valueL, 0, 100, 0xFE, 0x00); // 0..100% to left channel
valueR = map(valueR, 0, 100, 0xFE, 0x00); // 0..100% to right channel
write_register(SCI_VOL, (valueL << 8) | valueR); // Volume left and right
}
void VS1053::setBalance(int8_t balance) {
if (balance > 100) {
curbalance = 100;
} else if (balance < -100) {
curbalance = -100;
} else {
curbalance = balance;
}
}
void VS1053::setTone(uint8_t *rtone) { // Set bass/treble (4 nibbles)
// Set tone characteristics. See documentation for the 4 nibbles.
uint16_t value = 0; // Value to send to SCI_BASS
int i; // Loop control
for (i = 0; i < 4; i++) {
value = (value << 4) | rtone[i]; // Shift next nibble in
}
write_register(SCI_BASS, value); // Volume left and right
}
uint8_t VS1053::getVolume() { // Get the currenet volume setting.
return curvol;
}
int8_t VS1053::getBalance() { // Get the currenet balance setting.
return curbalance;
}
void VS1053::startSong() {
sdi_send_fillers(10);
}
void VS1053::playChunk(uint8_t *data, size_t len) {
sdi_send_buffer(data, len);
}
void VS1053::stopSong() {
uint16_t modereg; // Read from mode register
int i; // Loop control
sdi_send_fillers(2052);
delay(10);
write_register(SCI_MODE, _BV(SM_SDINEW) | _BV(SM_CANCEL));
for (i = 0; i < 200; i++) {
sdi_send_fillers(32);
modereg = read_register(SCI_MODE); // Read status
if ((modereg & _BV(SM_CANCEL)) == 0) {
sdi_send_fillers(2052);
LOG("Song stopped correctly after %d msec\n", i * 10);
return;
}
delay(10);
}
printDetails("Song stopped incorrectly!");
}
void VS1053::softReset() {
LOG("Performing soft-reset\n");
write_register(SCI_MODE, _BV(SM_SDINEW) | _BV(SM_RESET));
delay(10);
await_data_request();
}
void VS1053::printDetails(const char *header) {
uint16_t regbuf[16];
uint8_t i;
LOG("%s", header);
LOG("REG Contents\n");
LOG("--- -----\n");
for (i = 0; i <= SCI_num_registers; i++) {
regbuf[i] = read_register(i);
}
for (i = 0; i <= SCI_num_registers; i++) {
delay(5);
LOG("%3X - %5X\n", i, regbuf[i]);
}
}
/**
* An optional switch.
* Most VS1053 modules will start up in MIDI mode. The result is that there is no audio when playing MP3.
* You can modify the board, but there is a more elegant way without soldering.
* No side effects for boards which do not need this switch. It means you can call it just in case.
*
* Read more here: http://www.bajdi.com/lcsoft-vs1053-mp3-module/#comment-33773
*/
void VS1053::switchToMp3Mode() {
wram_write(0xC017, 3); // GPIO DDR = 3
wram_write(0xC019, 0); // GPIO ODATA = 0
delay(100);
LOG("Switched to mp3 mode\n");
softReset();
}
/**
* A lightweight method to check if VS1053 is correctly wired up (power supply and connection to SPI interface).
*
* @return true if the chip is wired up correctly
*/
bool VS1053::isChipConnected() {
uint16_t status = read_register(SCI_STATUS);
return !(status == 0 || status == 0xFFFF);
}
/**
* Provides current decoded time in full seconds (from SCI_DECODE_TIME register value)
*
* When decoding correct data, current decoded time is shown in SCI_DECODE_TIME
* register in full seconds. The user may change the value of this register.
* In that case the new value should be written twice to make absolutely certain
* that the change is not overwritten by the firmware. A write to SCI_DECODE_TIME
* also resets the byteRate calculation.
*
* SCI_DECODE_TIME is reset at every hardware and software reset. It is no longer
* cleared when decoding of a file ends to allow the decode time to proceed
* automatically with looped files and with seamless playback of multiple files.
* With fast playback (see the playSpeed extra parameter) the decode time also
* counts faster. Some codecs (WMA and Ogg Vorbis) can also indicate the absolute
* play position, see the positionMsec extra parameter in section 10.11.
*
* @see VS1053b Datasheet (1.31) / 9.6.5 SCI_DECODE_TIME (RW)
*
* @return current decoded time in full seconds
*/
uint16_t VS1053::getDecodedTime() {
return read_register(SCI_DECODE_TIME);
}
/**
* Clears decoded time (sets SCI_DECODE_TIME register to 0x00)
*
* The user may change the value of this register. In that case the new value
* should be written twice to make absolutely certain that the change is not
* overwritten by the firmware. A write to SCI_DECODE_TIME also resets the
* byteRate calculation.
*/
void VS1053::clearDecodedTime() {
write_register(SCI_DECODE_TIME, 0x00);
write_register(SCI_DECODE_TIME, 0x00);
}