Initial commit
This commit is contained in:
commit
b7d6c8ac2d
33
README.md
Normal file
33
README.md
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Tapedeck Player
|
||||||
|
|
||||||
|
Replace the old electronics of your tapedeck with an mp3 player, all while keeping the retro controls working!
|
||||||
|
|
||||||
|
![TapeDeck Player](player.jpg)
|
||||||
|
|
||||||
|
## Hardware
|
||||||
|
|
||||||
|
- Tapedeck. Anything will do, I used a [Universum Cassetten Recorder - Stereo Tape Deck CT 2746](https://www.radiomuseum.org/r/quelle_ct2746.html) from 1974.
|
||||||
|
- Arduino
|
||||||
|
- [DFPlayer Mini MP3 Player](https://wiki.dfrobot.com/DFPlayer_Mini_SKU_DFR0299) with an SD card
|
||||||
|
- OLED Display. I used a 1.3" I2C display, 128x64 pixels, SH1106 controller.
|
||||||
|
- audio amplifier to drive the speaker
|
||||||
|
- one microswitch to detect the fast-forward button being pressed
|
||||||
|
|
||||||
|
I reused a lot of the original components from the tapedeck (including wires), namely the user-facing electronics: volume and bass/treble potentiometers, mode-select switches, lights, speaker, power-on switch, audio input/output DIN connectors. And of course the tape motor.
|
||||||
|
|
||||||
|
## How This Works
|
||||||
|
|
||||||
|
Everything gets powered when a button on the cassette drive is pressed, activating the motor among other things. After the Arduino and the MP3 player boots (after a few seconds), the OLED display activates and the music starts. When the player is stopped, power is cut and the whole device is truly off.
|
||||||
|
|
||||||
|
Every power-on is a fresh boot for the MCU. Therefore it keeps track of what song to play in its EEPROM; whenever a song finishes, the song index is increased and stored.
|
||||||
|
|
||||||
|
The Fast-Forward button has a microswitch that tells the MCU to only play a short beginning of each song, skipping through them quickly.
|
||||||
|
|
||||||
|
The OLED display shows the song time, song index and the number of songs on the SD card.
|
||||||
|
|
||||||
|
The volume potentiometer controls the volume (gain) on the audio amplifier.
|
||||||
|
|
||||||
|
The MCU scans the tone potentiometer and changes the equalizer preset on the MP3 player: BASS, NORMAL, POP.
|
||||||
|
|
||||||
|
The "chrome cassette" switch overrides the selected equalizer ROCK.
|
||||||
|
|
BIN
player.jpg
Executable file
BIN
player.jpg
Executable file
Binary file not shown.
After Width: | Height: | Size: 173 KiB |
319
tapedeck-player.ino
Executable file
319
tapedeck-player.ino
Executable file
@ -0,0 +1,319 @@
|
|||||||
|
// === OVERVIEW ===
|
||||||
|
// Tape Deck player replacement
|
||||||
|
//
|
||||||
|
// VU meter --> OLED display
|
||||||
|
// Cassette player --> MP3 player
|
||||||
|
|
||||||
|
// === MP3 PLAYER ===
|
||||||
|
/***************************************************
|
||||||
|
DFPlayer - A Mini MP3 Player For Arduino
|
||||||
|
<https://www.dfrobot.com/product-1121.html>
|
||||||
|
|
||||||
|
***************************************************
|
||||||
|
This example shows the basic function of library for DFPlayer.
|
||||||
|
|
||||||
|
Created 2016-12-07
|
||||||
|
By [Angelo qiao](Angelo.qiao@dfrobot.com)
|
||||||
|
|
||||||
|
GNU Lesser General Public License.
|
||||||
|
See <http://www.gnu.org/licenses/> for details.
|
||||||
|
All above must be included in any redistribution
|
||||||
|
****************************************************/
|
||||||
|
|
||||||
|
/***********Notice and Trouble shooting***************
|
||||||
|
1.Connection and Diagram can be found here
|
||||||
|
<https://www.dfrobot.com/wiki/index.php/DFPlayer_Mini_SKU:DFR0299#Connection_Diagram>
|
||||||
|
2.This code is tested on Arduino Uno, Leonardo, Mega boards.
|
||||||
|
****************************************************/
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "SoftwareSerial.h"
|
||||||
|
#include "DFRobotDFPlayerMini.h"
|
||||||
|
#include "EEPROM.h"
|
||||||
|
|
||||||
|
#define PIN_EQ A1
|
||||||
|
#define PIN_CHROME 6
|
||||||
|
#define PIN_FORWARD 10
|
||||||
|
#define EEPROM_SONG_INDEX 1
|
||||||
|
#define SKIP_LIMIT 250
|
||||||
|
#define EEPROM_SKIP 2
|
||||||
|
|
||||||
|
SoftwareSerial mySoftwareSerial(9, 8); // RX, TX
|
||||||
|
DFRobotDFPlayerMini myDFPlayer;
|
||||||
|
void printDetail(uint8_t type, int value);
|
||||||
|
|
||||||
|
int song_index = 0;
|
||||||
|
unsigned long song_started = 0;
|
||||||
|
int files_max = -1;
|
||||||
|
bool skipping = false;
|
||||||
|
|
||||||
|
void setup_player()
|
||||||
|
{
|
||||||
|
pinMode(PIN_EQ, INPUT);
|
||||||
|
pinMode(PIN_CHROME, INPUT);
|
||||||
|
pinMode(PIN_FORWARD, INPUT);
|
||||||
|
analogReference(DEFAULT);
|
||||||
|
|
||||||
|
Serial.begin(9600);
|
||||||
|
while (!Serial && millis() < 2000) { delay(10); }
|
||||||
|
|
||||||
|
song_index = EEPROM.read(EEPROM_SONG_INDEX);
|
||||||
|
unsigned int song_skip = EEPROM.read(EEPROM_SKIP);
|
||||||
|
if (song_skip > SKIP_LIMIT) {
|
||||||
|
song_skip = 0;
|
||||||
|
}
|
||||||
|
song_index += song_skip;
|
||||||
|
if (song_index >= 255) {
|
||||||
|
song_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println();
|
||||||
|
Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)"));
|
||||||
|
mySoftwareSerial.begin(9600);
|
||||||
|
if (!myDFPlayer.begin(mySoftwareSerial)) {
|
||||||
|
Serial.println(F("Unable to begin:"));
|
||||||
|
Serial.println(F("1.Please recheck the connection!"));
|
||||||
|
Serial.println(F("2.Please insert the SD card!"));
|
||||||
|
while(true){
|
||||||
|
delay(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Serial.println(F("DFPlayer Mini online."));
|
||||||
|
|
||||||
|
myDFPlayer.volume(20); //Set volume value. From 0 to 30
|
||||||
|
myDFPlayer.play(song_index+1); //Play the first mp3
|
||||||
|
do {
|
||||||
|
delay(100);
|
||||||
|
files_max = myDFPlayer.readFileCounts();
|
||||||
|
} while (files_max <= -1);
|
||||||
|
|
||||||
|
if (digitalRead(PIN_FORWARD) == 1) {
|
||||||
|
// FastForward
|
||||||
|
skipping = true;
|
||||||
|
while (true) {
|
||||||
|
delay(500);
|
||||||
|
if (digitalRead(PIN_FORWARD) == 1) {
|
||||||
|
song_skip = min(song_skip + 1, SKIP_LIMIT);
|
||||||
|
EEPROM.update(EEPROM_SKIP, song_skip);
|
||||||
|
Serial.print("Skipping +1 ... ");
|
||||||
|
} else {
|
||||||
|
Serial.print("Skipping +0 ... ");
|
||||||
|
}
|
||||||
|
if (song_index + song_skip >= files_max) {
|
||||||
|
song_index = 0;
|
||||||
|
song_skip = 0;
|
||||||
|
}
|
||||||
|
myDFPlayer.play(song_index + song_skip + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
myDFPlayer.EQ(DFPLAYER_EQ_NORMAL);
|
||||||
|
|
||||||
|
if (files_max > 0 && song_index >= files_max) {
|
||||||
|
Serial.println("Song index out of bounds, skipping to next.");
|
||||||
|
nextSong();
|
||||||
|
}
|
||||||
|
|
||||||
|
song_started = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
int eq_now = DFPLAYER_EQ_NORMAL;
|
||||||
|
|
||||||
|
void loop_player()
|
||||||
|
{
|
||||||
|
static unsigned long eq_timer = millis();
|
||||||
|
|
||||||
|
if (millis() - eq_timer > 100) {
|
||||||
|
eq_timer = millis();
|
||||||
|
int eq_next = eq_now;
|
||||||
|
int eqChrome = digitalRead(PIN_CHROME);
|
||||||
|
if (eqChrome == 0) {
|
||||||
|
//analogReference(DEFAULT);
|
||||||
|
int eqRaw = analogRead(PIN_EQ);
|
||||||
|
//Serial.print("EQ: ");
|
||||||
|
//Serial.println(eqRaw);
|
||||||
|
if (eqRaw < 512) {
|
||||||
|
eq_next = DFPLAYER_EQ_BASS;
|
||||||
|
//Serial.println("EQ: Bass");
|
||||||
|
} else if (eqRaw < 900) {
|
||||||
|
eq_next = DFPLAYER_EQ_NORMAL;
|
||||||
|
//Serial.println("EQ: Normal");
|
||||||
|
} else {
|
||||||
|
eq_next = DFPLAYER_EQ_POP;
|
||||||
|
//Serial.println("EQ: POP");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eq_next = DFPLAYER_EQ_ROCK;
|
||||||
|
//Serial.println("EQ: Rock");
|
||||||
|
}
|
||||||
|
if (eq_now != eq_next) {
|
||||||
|
eq_now = eq_next;
|
||||||
|
myDFPlayer.EQ(eq_now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (myDFPlayer.available()) {
|
||||||
|
printDetail(myDFPlayer.readType(), myDFPlayer.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void nextSong() {
|
||||||
|
int files_max = myDFPlayer.readFileCounts();
|
||||||
|
song_index = (song_index + 1) % files_max;
|
||||||
|
EEPROM.update(EEPROM_SONG_INDEX, song_index);
|
||||||
|
EEPROM.update(EEPROM_SKIP, 0);
|
||||||
|
myDFPlayer.play(song_index + 1);
|
||||||
|
song_started = millis();
|
||||||
|
Serial.println("Next song...");
|
||||||
|
}
|
||||||
|
|
||||||
|
void printDetail(uint8_t type, int value){
|
||||||
|
switch (type) {
|
||||||
|
case TimeOut:
|
||||||
|
Serial.println(F("Time Out!"));
|
||||||
|
break;
|
||||||
|
case WrongStack:
|
||||||
|
Serial.println(F("Stack Wrong!"));
|
||||||
|
break;
|
||||||
|
case DFPlayerCardInserted:
|
||||||
|
Serial.println(F("Card Inserted!"));
|
||||||
|
break;
|
||||||
|
case DFPlayerCardRemoved:
|
||||||
|
Serial.println(F("Card Removed!"));
|
||||||
|
myDFPlayer.stop();
|
||||||
|
break;
|
||||||
|
case DFPlayerCardOnline:
|
||||||
|
Serial.println(F("Card Online!"));
|
||||||
|
myDFPlayer.start();
|
||||||
|
break;
|
||||||
|
case DFPlayerUSBInserted:
|
||||||
|
Serial.println("USB Inserted!");
|
||||||
|
break;
|
||||||
|
case DFPlayerUSBRemoved:
|
||||||
|
Serial.println("USB Removed!");
|
||||||
|
break;
|
||||||
|
case DFPlayerPlayFinished:
|
||||||
|
Serial.print(F("Number:"));
|
||||||
|
//Serial.print(value);
|
||||||
|
Serial.println(F(" Play Finished!"));
|
||||||
|
nextSong();
|
||||||
|
break;
|
||||||
|
case DFPlayerError:
|
||||||
|
Serial.print(F("DFPlayerError:"));
|
||||||
|
switch (value) {
|
||||||
|
case Busy:
|
||||||
|
Serial.println(F("Card not found"));
|
||||||
|
break;
|
||||||
|
case Sleeping:
|
||||||
|
Serial.println(F("Sleeping"));
|
||||||
|
break;
|
||||||
|
case SerialWrongStack:
|
||||||
|
Serial.println(F("Get Wrong Stack"));
|
||||||
|
break;
|
||||||
|
case CheckSumNotMatch:
|
||||||
|
Serial.println(F("Check Sum Not Match"));
|
||||||
|
break;
|
||||||
|
case FileIndexOut:
|
||||||
|
Serial.println(F("File Index Out of Bound"));
|
||||||
|
delay(1000);
|
||||||
|
song_index = 0;
|
||||||
|
myDFPlayer.play(song_index + 1);
|
||||||
|
break;
|
||||||
|
case FileMismatch:
|
||||||
|
Serial.println(F("Cannot Find File"));
|
||||||
|
break;
|
||||||
|
case Advertise:
|
||||||
|
Serial.println(F("In Advertise"));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// === DISPLAY ===
|
||||||
|
#include <SPI.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
#include <Adafruit_GFX.h>
|
||||||
|
#include <Adafruit_SH110X.h>
|
||||||
|
|
||||||
|
#define PIN_VU A0
|
||||||
|
#define VU_SCALE 1
|
||||||
|
#define VU_WARN 700
|
||||||
|
#define VU_CRIT 900
|
||||||
|
#define VU_SLOWDOWN 10
|
||||||
|
#define VU_ATTACK 1
|
||||||
|
|
||||||
|
/* Uncomment the initialize the I2C address , uncomment only one, If you get a totally blank screen try the other*/
|
||||||
|
#define i2c_Address 0x3c //initialize with the I2C addr 0x3C Typically eBay OLED's
|
||||||
|
//#define i2c_Address 0x3d //initialize with the I2C addr 0x3D Typically Adafruit OLED's
|
||||||
|
|
||||||
|
#define SCREEN_WIDTH 128 // OLED display width, in pixels
|
||||||
|
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
|
||||||
|
#define OLED_RESET -1 // QT-PY / XIAO
|
||||||
|
Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
|
||||||
|
|
||||||
|
void setup_display() {
|
||||||
|
//Serial.begin(9600);
|
||||||
|
|
||||||
|
delay(250); // wait for the OLED to power up
|
||||||
|
display.begin(i2c_Address, true); // Address 0x3C default
|
||||||
|
display.setContrast (0); // dim display
|
||||||
|
|
||||||
|
// Clear the buffer.
|
||||||
|
display.clearDisplay();
|
||||||
|
display.display();
|
||||||
|
|
||||||
|
display.setTextSize(2);
|
||||||
|
display.setTextColor(SH110X_WHITE);
|
||||||
|
display.setCursor(0, 20);
|
||||||
|
display.println(" UNIVERSUM");
|
||||||
|
display.setTextSize(1);
|
||||||
|
display.display();
|
||||||
|
delay(500);
|
||||||
|
display.clearDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop_display()
|
||||||
|
{
|
||||||
|
display.clearDisplay();
|
||||||
|
|
||||||
|
display.setTextSize(2);
|
||||||
|
display.setTextColor(SH110X_WHITE);
|
||||||
|
display.setCursor(35, 20);
|
||||||
|
unsigned long song_time = (millis() - song_started) / 1000;
|
||||||
|
char song_time_text[16];
|
||||||
|
sprintf(song_time_text, "%02d", song_time / 60);
|
||||||
|
display.print(song_time_text);
|
||||||
|
display.print(":");
|
||||||
|
sprintf(song_time_text, "%02d", song_time % 60);
|
||||||
|
display.println(song_time_text);
|
||||||
|
|
||||||
|
display.setTextSize(1);
|
||||||
|
display.setCursor(37, 50);
|
||||||
|
char song_index_text[16];
|
||||||
|
sprintf(song_index_text, "%03d", song_index+1);
|
||||||
|
display.print(song_index_text);
|
||||||
|
display.print(" / ");
|
||||||
|
char song_count_text[16];
|
||||||
|
sprintf(song_count_text, "%03d", files_max);
|
||||||
|
display.print(song_count_text);
|
||||||
|
|
||||||
|
display.display();
|
||||||
|
}
|
||||||
|
|
||||||
|
// === MAIN ===
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
setup_player();
|
||||||
|
setup_display();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
loop_player();
|
||||||
|
loop_display();
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user