MAIN CODE (DEFINES ARE AT THE END)
There are probably quite a few bugs, did not have enough time to debug it super thoroughly, i.e. some edge cases that need to be taken care of.
/*
Phil Chen
MP3 Player Code
© 2011
There may be bugs!
Adapted from code by
David Sirkin
Wendy Ju
Matt Seale
Adafruit Industries
*/
/*** Includes ***/
#include <mp3.h>
#include <mp3conf.h>
#include <SD.h>
#include <EEPROM.h>
#include "TFTLCD.h"
#include "TouchScreen.h"
#include "constantDefs.h"
/*** Section End ***/
extern int __bss_end;
extern void *__brkval;
int get_free_memory()
{
int free_memory;
if((int)__brkval == 0)
free_memory = ((int)&free_memory) - ((int)&__bss_end);
else
free_memory = ((int)&free_memory) - ((int)__brkval);
return free_memory;
}
void printMem(){
int freemem = get_free_memory();
char buff[5];
itoa(freemem,buff,10);
wString(buff,120);
}
/*** Global Variables ***/
TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, 0);
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);
Sd2Card card;
SdVolume volume;
SdFile root;
/*** Section End ***/
/**** State variables ***/
//Looping. True/False
boolean repeat = true;
boolean shuffle = false;
//State of the MP3 Player. PLAY/PAUSE
int state = PLAY;
int plsize = 0;
//Song variables
unsigned char artist[32];
unsigned char album[32];
unsigned char title[32];
//Mp3 Volume Controls
int mp3_vol = 150;
int prev_vol = mp3_vol;
int volState = UNMUTE;
//TouchScreen Variables
Point p;
int values[2];
//BMP variables
File bmpFile;
int bmpWidth, bmpHeight;
uint8_t bmpDepth, bmpImageoffset;
/*** Section End ***/
/******************************/
/********** Methods *********/
/******************************/
/***** SD Card Methods *****/
int listFiles(SdFile *dir, int numToRead){
int count = 0;
int currPosition = 0;
dir_t p;
dir->rewind();
while (dir->readDir(&p) > 0 && count < numToRead) {
if (p.name[0] == DIR_NAME_FREE) break;
if (p.name[0] == DIR_NAME_DELETED || p.name[0] == '.') continue;
if (!DIR_IS_FILE_OR_SUBDIR(&p)) continue;
//if (!DIR_IS_FILE(&p)) continue;
currPosition = count*(MAX_NAME_LENGTH);
uint8_t pos = 0;
for (uint8_t i = 0; i < 11; i++) {
if (p.name[i] != ' ') {
EEPROM.write(currPosition + pos, p.name[i]);
pos++;
}
}
if (DIR_IS_SUBDIR(&p)) {
EEPROM.write(currPosition+pos, '/');
pos++;
}
EEPROM.write(currPosition+pos, '\0');
count++;
}
return count;
}
//listFiles
//Takes in an SdFile directory and writes the name of the files into EEPROM.
int listFiles(SdFile *dir) {
int count = 0;
int currPosition = 0;
dir_t p;
dir->rewind();
while (dir->readDir(&p) > 0 && count < MAX_FILE_COUNT) {
if (p.name[0] == DIR_NAME_FREE) break;
if (p.name[0] == DIR_NAME_DELETED || p.name[0] == '.') continue;
if (!DIR_IS_FILE_OR_SUBDIR(&p)) continue;
//if (!DIR_IS_FILE(&p)) continue;
currPosition = count*(MAX_NAME_LENGTH);
uint8_t pos = 0;
for (uint8_t i = 0; i < 11; i++) {
if (p.name[i] != ' ') {
EEPROM.write(currPosition + pos, p.name[i]);
pos++;
}
}
if (DIR_IS_SUBDIR(&p)) {
EEPROM.write(currPosition+pos, '/');
pos++;
}
EEPROM.write(currPosition+pos, '\0');
count++;
}
return count;
}
//getFilesname
//taking in a char buffer and a filenumber (index) and reads the filename from EEPROM into the buffer
int getFilename(char* filename, int filenum){
int namelen = MAX_NAME_LENGTH;
for(int j=0;j < MAX_NAME_LENGTH;j++){
filename[j] = EEPROM.read((filenum*MAX_NAME_LENGTH)+j);
// end of name
if (filename[j] == '\0') {
namelen = j;
j = MAX_NAME_LENGTH;
break;
}else if (filename[j] == '/') {
filename[0] = '\0';
namelen = 0;
j = MAX_NAME_LENGTH;
break;
}
}
if (namelen> 4) {
filename[namelen+1] = '\0';
for(int i=0;i<3;i++){
filename[namelen-i] = filename[namelen-1-i];
}
filename[namelen-3] = '.';
namelen++;
}
return namelen;
}
//readPlaylist
//Reads the songs of the playlists and plays them from Music.
void readPlaylists(){
//printMem();
clearMain();
SdFile playlists;
playlists.open(&root,(char*)"PL",O_READ);
int numFiles = listFiles(&playlists,NUMOP);
char files[numFiles][MAX_NAME_LENGTH+2];
for(int i=0;i<numFiles;i++){
char filename[MAX_NAME_LENGTH+2];
int namelen = getFilename(filename,i);
for(int z=0;z<namelen+1;z++){
files[i][z] = filename[z];
}
tft.drawString(0,40+20*i,(char*)files[i],WHITE,2);
}
int* nums;
while(1){
checkTouch(values);
if(values[0] != -1){
if(inRange(values[0],values[1],0,80,0,40))
break;
int index = findIndex(values[1]);
if(index>=0 && index<numFiles){
SdFile pl;
pl.open(&playlists,(char*)files[index],O_READ);
nums = readPL(&pl,7);
break;
}
}
}
if(nums){
pplay(nums);
}
}
void homePressed(boolean startup=false){
draw("B14.bmp",0,0);
if(!startup){
tft.drawString(80,0,"Back to Music",WHITE,1);
}
clearMain();
tft.drawString(0,40,"All Songs",WHITE,2);
tft.drawString(0,60,"Playlists",WHITE,2);
while(1){
//printMem();
checkTouch(values);
if(values[0] != -1){
if(inRange(values[0],values[1],80,160,0,40)&&!startup){
draw("B8.bmp",0,0);
tft.fillRect(80,0,80,40,BLACK);
clearMain();
printInfo();
return;
}
int index = findIndex(values[1]);
if(index == 0){
draw("B8.bmp",0,0);
SdFile dir;
dir.open(&root,(char*)"Music",O_READ);
dirPlay(&dir);
dir.close();
break;
}else if(index == 1){
draw("B8.bmp",0,0);
readPlaylists();
break;
}
}
}
}
int* readPL(SdFile* file, int num){
char snum[4];
char single[1];
unsigned int bytes_to_read;
file->seekSet(0);
boolean nl = false;
int currpos = 0;
int count = 0;
do{
bytes_to_read = file->read(single,1);
uint8_t n = single[0];
EEPROM.write(count*MAX_NAME_LENGTH+currpos,n);
currpos++;
if(single[0] == '\n'){
for(int j=currpos+count*MAX_NAME_LENGTH-1;j<(1+count)*MAX_NAME_LENGTH;j++){
EEPROM.write(j,'\0');
}
currpos = 0;
count++;
}
}
while(bytes_to_read != 0 && count < num);
char names[count][MAX_NAME_LENGTH];
int nums[count];
clearMain();
for(int i=0;i<count;i++){
getFilename(names[i],i);
nums[i] = atoi(names[i]);
}
plsize = count-1;
return nums;
}
/***** MP3 Player Methods *****/
//mp3Play
//Takes in an SdFile and reads the data into a buffer while the touchscreen senses touch
int mp3Play (SdFile *file) {
unsigned char bytes[read_buffer]; // buffer to send to the decoder
unsigned int bytes_to_read; // number of bytes to read from sd card
file->seekSet(0);
int rwff = NONE;
do {
bytes_to_read = file->read(bytes, read_buffer);
rwff = checkControl();
if(rwff == REWIND || rwff == FASTF || rwff == HOME){
return rwff;
}
Mp3.play(bytes, bytes_to_read);
}
while (bytes_to_read == read_buffer);
return 0;
}
int lFiles(SdFile* dir, int* nums) {
int count = plsize;
int currPosition = 0;
int ccount=0;
dir_t p;
char test[MAX_NAME_LENGTH+2];
for(int i=0; i<count; i++){
dir->rewind();
ccount = 0;
dir->readDir(&p);
while(ccount != nums[i]){
dir->readDir(&p);
ccount ++;
}
ccount++;
if (!(p.name[0] == DIR_NAME_FREE &&p.name[0] == DIR_NAME_DELETED && p.name[0] == '.' && !DIR_IS_FILE_OR_SUBDIR(&p))) {
currPosition = i*(MAX_NAME_LENGTH);
uint8_t pos = 0;
for (uint8_t n = 0; n < 11; n++) {
if (p.name[n] != ' ') {
EEPROM.write(currPosition + pos, p.name[n]);
pos++;
}
}
EEPROM.write(currPosition+pos, '\0');
}
}
return count;
}
void pplay (int* nums){
SdFile dir;
dir.open(&root,(char*)"Music",O_READ);
int numFiles = lFiles(&dir,nums);
for (int i=0; i<numFiles;i++){
if(shuffle)
i = rand() % numFiles;
char filename[MAX_NAME_LENGTH+2];
int namelen = getFilename(filename,i);
SdFile dataFile;
if (dataFile.open(dir, filename, O_RDONLY) > 0) {
if (!dataFile.isDir() || filename == 0) {
if ((filename[namelen-3] == 'M' && filename[namelen-2] == 'P' && filename[namelen-1] == '3') ||
(filename[namelen-3] == 'W' && filename[namelen-2] == 'A' && filename[namelen-1] == 'V')) {
printTags(&dataFile,filename);
int temp = mp3Play(&dataFile);
if(temp == REWIND){
i = i - 2;
if(i < -1){
if(repeat){
i = numFiles - 2;
}else{
i = -1;
}
}
}else if(temp == HOME){
return;
}else if(i == (numFiles-1) && repeat){
i = -1;
}
}
}
}
}
}
//dirPlay
//For the number of files in the directory, reads their names from the EEPROM and plays them
void dirPlay (SdFile *dir) {
int numFiles = listFiles(dir);
for (int i = 0; i < numFiles; i++) {
if(shuffle)
i = rand() % numFiles;
char filename[MAX_NAME_LENGTH+2];
int namelen = getFilename(filename,i);
SdFile dataFile;
if (dataFile.open(dir, filename, O_RDONLY) > 0) {
if (!dataFile.isDir() || filename == 0) {
if ((filename[namelen-3] == 'M' && filename[namelen-2] == 'P' && filename[namelen-1] == '3') ||
(filename[namelen-3] == 'W' && filename[namelen-2] == 'A' && filename[namelen-1] == 'V')) {
printTags(&dataFile,filename);
int temp = mp3Play(&dataFile);
if(temp == REWIND){
i = i - 2;
if(i < -1){
if(repeat){
i = numFiles - 2;
}else{
i = -1;
}
}
}else if(temp == HOME){
return;
}else if(i == (numFiles-1) && repeat){
i = -1;
}
}
}
}
}
}
//pID3Info
//prints the ID3 info for a single tag
void pID3Info(SdFile* file, int y, unsigned char* info){
char pb[4];
unsigned int bytes_read = file->read(pb,4);
//FatFs.read(&file, pb, 4, &bytes_read);
unsigned long slen = ((unsigned long) pb[0] << (8 * 3)) +
((unsigned long) pb[1] << (8 * 2)) +
((unsigned long) pb[2] << (8 * 1)) + pb[3];
slen--;
if (slen > 30) slen = 30; // title is too long
// skip 3 nulls, then read in title
bytes_read = file->read(pb,3);
bytes_read = file->read(info,slen);
info[slen] = '\0';
}
void printInfo(){
wString((char*)title,40);
wString((char*)album,50);
wString((char*)artist,60);
}
//printTags
//prints all the tags for the song
void printTags (SdFile* file, char* filename) {
clearMain();
unsigned char id3v2[3];
unsigned int bytes_read;
file->seekSet(0);
bytes_read = file->read(id3v2, 3);
if (id3v2[0] == 'I' && id3v2[1] == 'D' && id3v2[2] == '3') {
file->seekSet(0);
unsigned char pb[4];
unsigned char c[1];
boolean art = false;
boolean son = false;
boolean alb = false;
int count = 30000;
while (1) {
bytes_read = file->read(c,1);
pb[0] = pb[1];
pb[1] = pb[2];
pb[2] = pb[3];
pb[3] = c[0];
if(count == 0){
if(!son)
memcpy(title,"Could not be found",strlen("Could not be found")+1);
if(!art)
memcpy(artist,"Could not be found",strlen("Could not be found")+1);
if(!alb)
memcpy(album,"Could not be found",strlen("Could not be found")+1);
printInfo();
break;
}
if(checkArray(pb,'T','I','T','2')){
// }else if(pb[0] == 'T' && pb[1] == 'I' && pb[2] == 'T' && pb[3] == '2') {
pID3Info(file,40,title);
son = true;
}else if(checkArray(pb,'T','A','L','B')){
//}else if(pb[0] == 'T' && pb[1] == 'A' && pb[2] == 'L' && pb[3] == 'B') {
pID3Info(file,50,album);
alb = true;
}else if (checkArray(pb,'T','P','E','2')){
//if (pb[0] == 'T' && pb[1] == 'P' && pb[2] == 'E' && pb[3] == '2') {
pID3Info(file,60,artist);
art = true;
}else if (bytes_read == 0) {
clearWrite("EOF",40);
delay(100);
break;
}else if(son && art && alb){
printInfo();
break;
}
--count;
}
}
else {
clearWrite("tag",40);
}
}
/***** LCD/Touchscreen Methods *****/
//clearMain
//Redraws a CBLUE box in the center of the screen
void clearMain(){
tft.fillRect(0,40,240,200,CBLUE);
}
//clearWrite
//Clears and then writes a string on the left
void clearWrite(char* str, int y){
clearMain();
wString(str,y);
}
//wString
//Writes a string left aligned at height of y
void wString(char* str,int y){
tft.drawString(0,y,str,WHITE,1);
}
/***** Control Functions *****/
//inRange
//Helper function that determines whether a touch is within a certain range.
boolean inRange(int x, int y, int lowx, int highx, int lowy, int highy){
if(x >= lowx && x < highx && y >= lowy && y < highy)
return true;
return false;
}
boolean inRange(int x, int y, int lowx, int lowy){
return inRange(x,y,lowx,lowx+40,lowy,lowy+40);
}
//playPressed
//Play button is pressed. Pauses if currently playing, and plays if currently paused. Redraws the buttons
void playPressed(){
if(state == PLAY){
draw("B1.bmp",80,240);
state = PAUSE;
while(state == PAUSE){
//printMem();
checkControl();
}
}
else if(state == PAUSE){
draw("B2.bmp",80,240);
state = PLAY;
delay(50);
}
}
//volumeUp
//Increases the volume. Unmutes
void volumeUp(){
if(volState == MUTE)
unmute();
mp3_vol += 2;
if(mp3_vol > 255)
mp3_vol = 255;
Mp3.volume(mp3_vol);
updateVolume();
}
//volumeDown
//Decreases the volume. Unmutes
void volumeDown(){
if(volState == MUTE)
unmute();
mp3_vol -= 2;
if(mp3_vol < 0)
mp3_vol = 0;
Mp3.volume(mp3_vol);
updateVolume();
}
//unmute
//Unmutes the volume
void unmute(){
draw("B5.bmp",70,280);
mp3_vol = prev_vol;
volState = UNMUTE;
}
//volPressed
//Mutes if unmuted. Unmutes if muted
void volPressed(){
if(volState == UNMUTE){
draw("B9.bmp",70,280);
prev_vol = mp3_vol;
mp3_vol = 0;
volState = MUTE;
}else{
unmute();
}
Mp3.volume(mp3_vol);
updateVolume();
}
void loopPressed(){
if(repeat){
draw("B10.bmp",160,0);
}else{
draw("B11.bmp",160,0);
}
repeat = !repeat;
}
void shufflePressed(){
if(shuffle){
draw("B12.bmp",200,0);
}else{
draw("B13.bmp",200,0);
}
shuffle = !shuffle;
}
//checkControl
//Maps out the areas of the interface to methods based on whether or not the screen has been touched
int checkControl(){
//Setup
digitalWrite(13, HIGH);
p = ts.getPoint();
digitalWrite(13, LOW);
//pinMode(XP, OUTPUT);
pinMode(XM, OUTPUT);
pinMode(YP, OUTPUT);
//pinMode(YM, OUTPUT);
if(p.z > MINPRESSURE and p.z < MAXPRESSURE){
p.x = map(p.x, TS_MINX, TS_MAXX, tft.width, 0);
p.y = map(p.y, TS_MINY, TS_MAXY, tft.height, 0);
if(inRange(p.x,p.y,80,160,240,280))
playPressed();
if(inRange(p.x,p.y,120,280))
volumeUp();
if(inRange(p.x,p.y,20,280))
volumeDown();
if(inRange(p.x,p.y,70,280))
volPressed();
if(inRange(p.x,p.y,0,80,240,280))
return REWIND;
if(inRange(p.x,p.y,160,240,240,280))
return FASTF;
if(inRange(p.x,p.y,0,80,0,40))
return HOME;
if(inRange(p.x,p.y,160,0))
loopPressed();
if(inRange(p.x,p.y,200,0))
shufflePressed();
delay(5);
}
return 0;
}
//checkControl
//Maps out the areas of the interface to methods based on whether or not the screen has been touched
void checkTouch(int* val){
//Setup
digitalWrite(13, HIGH);
Point p = ts.getPoint();
digitalWrite(13, LOW);
//pinMode(XP, OUTPUT);
pinMode(XM, OUTPUT);
pinMode(YP, OUTPUT);
//pinMode(YM, OUTPUT);
if(p.z > MINPRESSURE and p.z < MAXPRESSURE){
p.x = map(p.x, TS_MINX, TS_MAXX, tft.width, 0);
p.y = map(p.y, TS_MINY, TS_MAXY, tft.height, 0);
val[0] = p.x;
val[1] = p.y;
}else{
val[0]=-1;
val[1]=-1;
}
}
//updateVolume
//Maps the range of the volume to a percetange and draws it on the screen
void updateVolume(){
tft.fillRect(160,280,79,39,BLACK);
int vol = map(mp3_vol,0,255,0,100);
char buff[4];
itoa(vol,buff,10);
tft.drawString(180,292,buff,WHITE,2);
}
//draw
//draws a bmp onto the screen
void draw(char* filename, int x, int y){
bmpFile = SD.open(filename);
if(!bmpFile)
while(1);
if(!bmpReadHeader(bmpFile))
return;
//tft.setRotation(0);
bmpFile.seek(bmpImageoffset);
uint32_t time = millis();
uint16_t p;
uint8_t g, b;
int i, j;
uint8_t sdbuffer[3 * BUFFPIXEL]; // 3 * pixels to buffer
uint8_t buffidx = 3*BUFFPIXEL;
for (i=0; i< bmpHeight; i++) {
tft.goTo(x, y+bmpHeight-i);
for (j=0; j<bmpWidth; j++) {
// read more pixels
if (buffidx >= 3*BUFFPIXEL) {
bmpFile.read(sdbuffer, 3*BUFFPIXEL);
buffidx = 0;
}
// convert pixel from 888 to 565
b = sdbuffer[buffidx++]; // blue
g = sdbuffer[buffidx++]; // green
p = sdbuffer[buffidx++]; // red
p >>= 3;
p <<= 6;
g >>= 2;
p |= g;
p <<= 5;
b >>= 3;
p |= b;
// write out the 16 bits of color
tft.writeData(p);
}
}
digitalWrite(10,HIGH);
bmpFile.close();
}
//findIndex
//Find where on the menu a touch is
int findIndex(int y){
y = y-40;
y /= 20;
return y;
}
//bmpReadHeader
//reads the header for a bmp file
boolean bmpReadHeader(File f) {
uint32_t tmp;
if (read16(f) != 0x4D42)
return false;
tmp = read32(f);
read32(f);
bmpImageoffset = read32(f);
tmp = read32(f);
bmpWidth = read32(f);
bmpHeight = read32(f);
if (read16(f) != 1)
return false;
bmpDepth = read16(f);
if (read32(f) != 0)
return false;
return true;
}
// LITTLE ENDIAN!
uint16_t read16(File f) {
uint16_t d;
uint8_t b;
b = f.read();
d = f.read();
d <<= 8;
d |= b;
return d;
}
// LITTLE ENDIAN!
uint32_t read32(File f) {
uint32_t d;
uint16_t b;
b = read16(f);
d = read16(f);
d <<= 16;
d |= b;
return d;
}
boolean checkArray(unsigned char* pb, unsigned char a, unsigned char b, unsigned char c, unsigned char d){
return (pb[0] == a && pb[1] == b && pb[2] == c && pb[3] == d);
}
/*** Section End ***/
/*** Setup ***/
void setup() {
randomSeed(1);
tft.reset();
uint16_t identifier = tft.readRegister(0x0);
if (identifier != 0x9325 && identifier != 0x9328)
while (1);
tft.initDisplay();
pinMode(10, OUTPUT);
digitalWrite(10, HIGH);
if (!SD.begin(10))
return;
tft.fillScreen(BLACK);
tft.fillRect(0,40,240,200,CBLUE);
draw("B14.bmp",0,0);
draw("B2.bmp",80,240);
draw("B3.bmp",160,240);
draw("B4.bmp",0,240);
draw("B5.bmp",70,280);
draw("B6.bmp",120,280);
draw("B7.bmp",20,280);
draw("B11.bmp",160,0);
draw("B12.bmp",200,0);
updateVolume();
if (!card.init(SPI_HALF_SPEED, SD_CS) || !volume.init(card) || !root.openRoot(&volume))
return;
Mp3.begin(mp3_cs,dcs,rst,dreq); // decoder cs, dcs, rst, dreq pin
Mp3.volume(mp3_vol); // default volume level is silent
//readPlaylists();
/*SdFile dir;
dir.open(&root,(char*)"Music",O_READ);
dirPlay(&dir);
dir.close();*/
while(1){
homePressed(true);
}
}
/*** Section End ***/
/*** Loop ***/
void loop(){
}
/*** Section End ***/
constantDefs.h
//File reading constants
#define MAX_FILE_COUNT 40
#define MAX_NAME_LENGTH 13
#define MAX_BYTE_COUNT 12
//LCD pins
#define LCD_RESET A4
#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0
//SD Chip Select
#define SD_CS 10
//Mp3 Chip Select
#define mp3_cs 0 // 'command chip select' to cs pin
//Mp3 Various other pins
#define dcs 18 // (Pin A0) 'data chip select' to bsync pin 14 -> 18 A4
#define rst 1 // 'reset' to decoder's reset pin
#define dreq 19 // (Pin A1) 'data request line' to dreq pin 15 -> 19 A5
#define read_buffer 256 // size of the microsd read buffer
//TouchScreen pins
#define YP A3 // must be an analog pin, use "An" notation!
#define XM A2 // must be an analog pin, use "An" notation!
#define YM 3 // can be a digital pin
#define XP 2 // can be a digital pin
//TouchScreen bounds
#define TS_MINX 150
#define TS_MINY 120
#define TS_MAXX 920
#define TS_MAXY 940
//LCD_RESET pin
//LCD Colors
#define BLACK 0x0000
#define CBLUE 0x0010
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
//TouchScreen pressure
#define MINPRESSURE 1
#define MAXPRESSURE 1000
//Pixel buffer
#define BUFFPIXEL 20
#define BDIM 40
#define PAUSE 0
#define PLAY 1
#define UNMUTE 0
#define MUTE 1
#define NONE 0
#define REWIND 1
#define FASTF 2
#define HOME 3
#define NUMOP 9
Comments (0)
You don't have permission to comment on this page.