깃허브: https://github.com/ijun17/Mbed-MP3-Player

이 프로젝트는 대학교 3학년 마이크로프로세서 수업의 최종 프로젝트이다. 사용자는 LCD 디스플레이로 어떤 노래가 있는지 확인할 수 있고, 노래를 재생, 일시정지, 중지를 할 수 있다. lcd 디스플레이는 텍스트를 출력할 수 있는 공간이 한정되어 있기 때문에 만약 노래 제목이 16자를 넘는다면 1초마다 왼쪽으로 시프트하여 전체 제목을 보여준다. 노래를 재생하면 부저에서 음악이 나오며 LCD 디스플레이에서 가사가 나온다.

객체 지향적 설계

이 프로젝트는 객체 지향적으로 설계해 유지보수가 쉽고, 각 객체 자신의 책임을 다하도록 하였다. Music 클래스, MP3Player 클래스가 있으며 메인 함수에서 아래처럼 실행된다.

#include "mbed.h"

#include "Music.h"
#include "MusicSample.h"
#include "MP3Player.h"

int main() 
{
    Music music1(name1,pitchs1,lyrics1,beats1,length1);
    Music music2(name2,pitchs2,lyrics2,beats2,length2);
    Music musicT(nameT,pitchsT,lyricsT,beatsT,lengthT);

    MP3Player mp3(D5, D14, D15, D3, D4);
    mp3.addMusic(music1);
    mp3.addMusic(music2);
    mp3.addMusic(musicT);
    mp3.start();
}

Music 클래스

Music 클래스는 다음의 정보를 가지고 있다.

그리고 아래의 기능을 담당한다.

아래는 Music 클래스의 음계를 주파수로 변환하는 함수이다. 이 주파수를 부저로 출력하는 것이다.

static const int PITCH[7];//{9,11,0,2,4,5,7}={A,B,C,D,E,F,G}
//pitchs[i]에서 옥타브 추출
int getOctave(int i){return pitches[i][pitches[i].length()-1]-'0';} 
//pitchs[i]에서 음계 추출
int getPitch(int i){return PITCH[pitches[i][0]-'A'] + (pitches[i][1]=='#');}
// 옥타브와 음계로 주파수 계산
float getFrequency(int i) {
    const float baseFreq = 32.70; //기준 주파수(1옥타브 도)
    return baseFreq * pow(2, getOctave(i)+getPitch(i)/12.0); // 주파수 반환
}

안정적인 폴링 방식

이 프로젝트는 저장된 노래를 재생하고 가사를 출력하는 MP3 player이다. 단순해 보이지만 피에조 부저로 음을 출력하고, 텍스트 LCD로 노래 가사를 출력하는 동시에 버튼으로 입력받아야 한다. 구현 방법에 따라 불안정한 시스템이 될 수도 있고, 안정적인 시스템이 될 수도 있다. 따라서 인터럽트가 아닌 폴링방식으로 구현을 하였다.

MP3Player는 무한 루프로 주기적으로 각 상태를 확인하고, 각 기능을 실행한다. 아래는 MP3Player의 start 함수이다.

void start() {
    mp3_state = menu;
    selectMusicId = 0;
    selectedMusicName = musicList[selectMusicId].getName();
    lcdUpdate("[MP3] menu", "> "+selectedMusicName);
    //lcdUdate(첫번째 줄, 두번째 줄)

    wait(1);
    nameTimer.start();
    while (true) {
        //버튼 1 : 다음 노래, 일시 중지
        if(button1.read()){
            if(mp3_state==menu)nextMusic();
            else pauseMusic();
            restartNameTimer();
        }
        //버튼 2 : 재생, 중지
        if(button2.read()){
            if(mp3_state==menu)playMusic();
            else stopMusic();
            restartNameTimer();
        }
        //가사 출력
        if(mp3_state==playing){ 
            if(musicList[selectMusicId].update(buzzer)){
                if(lcdBuffer[1].length()+musicList[selectMusicId].getLyric().length() <=16)
                    lcdUpdate("[MP3] playing", lcdBuffer[1]+musicList[selectMusicId].getLyric());
                else lcdUpdate("[MP3] playing", "> "+musicList[selectMusicId].getLyric());
            }
            if(!musicList[selectMusicId].isPlaying()){
                stopMusic();
                restartNameTimer();
            }
        }
        //긴 제목 오른쪽 시프트
        if(mp3_state==menu && selectedMusicName.length()>14){
            if(int(nameTimer) >= selectedMusicName.length())restartNameTimer();
            lcdUpdate("[MP3] menu", "> "+selectedMusicName.substr(int(nameTimer),14));
        }
    }
}

긴 제목 시프트

본 프로젝트에서 사용된 LCD 디스플레이는 16*2로 화면의 크기가 한정되어 있어 긴 문자열을 출력하기 어렵다. 따라서 제목을 전부 출력할 수 없다면 1초마다 왼쪽으로 시프트하여 전체 제목을 볼 수 있도록 하였다.

//긴 제목 오른쪽 시프트
if(mp3_state==menu && selectedMusicName.length()>14){
    if(int(nameTimer) >= selectedMusicName.length())restartNameTimer();
    lcdUpdate("[MP3] menu", "> "+selectedMusicName.substr(int(nameTimer),14));
    //lcdUdate(첫번째 줄, 두번째 줄)
}