ECE 4180 Final Project

mbed Bluetooth Music Player

Introduction

This notebook explores the design of a Bluetooth music player that leverages the mbed hardware and library. Specifically, an LPC1768 microcontroller controls a number of peripheral components to emulate a standard music player interface and experience for the user. Features include the ability to control music playback, volume, and view song status on an LCD screen. The user has the option to control music playback via the Adafruit “Bluefruit” app and volume with either an app or potentiometer.

Design Block Diagram
diagram

Picture of Design

board


Components

Our design made use of the following components:

Pin Connection Table

LPC1768 PinComponentPin NameMisc.
p5uSD File SystemSPI MOSI-
p6uSD File SystemSPI MISO-
p7uSD File SystemSPI SCLK-
p8uSD File SystemCS-
VOUTuSD File SystemVCC-
GNDuSD File SystemGND-
p9uLCD ScreenRX-
p10uLCD ScreenTX-
p11uLCD ScreenReset-
-uLCD Screen5V5V Supply
GNDuLCD ScreenGND-
p13Bluetooth ModuleRXI-
p14Bluetooth ModuleTXO-
-Bluetooth ModuleVin5V Supply
GNDBluetooth ModuleCTS-
GNDBluetooth ModuleGND-
p18Class D Amplifierin+-
-Class D Amplifierpwr+5V Supply
GNDClass D Amplifierin--
GNDClass D Amplifierpwr--
-Class D Amplifierout+Speaker +
-Class D Amplifierout-Speaker -


The Class D Amplifier amplifies the PWM signal that contains the music data. A potentiometer is used in combination with the amplifier to control the volume of the audio output. The amplifier sends the amplified music to a speaker.

amp speaker

The Bluetooth control of the music player is accomplished using the Adafruit Bluetooth module shown below. Music playback is controlled as follows: the left arrow skips backward 1 song, the right arrow skips forward 1 song, the up arrow increases volume, the down arrow decreases volume, the (1) button plays the song, the (2) button pauses the song, the (3) button mutes the song, and the (4) button jumps to a random song.

blue

app

The LCD screen displays the song title, progress (via a progress bar with time stamps), and status (playing/stopped).

lcd

The external 5V power supply is necessary due to all of the external components of the design; the mbed wouldn’t be able to supply enough power to everything in the design using its 5V output pin.


Software Used

We make use of the “wave_player” library https://os.mbed.com/users/sravet/code/wave_player/ with modifications to better control music playback.
The code for our music player design is shown below:

#include "mbed.h"
#include "rtos.h"
#include "SDFileSystem.h"
#include "wave_player.h"
#include "uLCD_4DGL.h"
#include <string>

#define SONGS 5     // number of songs on SD card
#define SCALE 0.83  // Scaling factor due to slow play rate of speaker

// System I/O
SDFileSystem sd(p5, p6, p7, p8, "sd"); 
uLCD_4DGL uLCD(p9,p10,p11);
Serial blue(p13,p14);
DigitalOut led1(LED1);
AnalogOut DACout(p18);
wave_player waver(&DACout);

Timer t;                    // Track song time
volatile int songID = 0;    // Track song ID
float song_len[SONGS] = {12, 12, 27, 69, 87};
string song_title[SONGS] = {"20thfox", "Hawaii", "Pokemon", "Doll", "Blues"};
FILE *wave_file;

Mutex serial_mtx;           // Mutex for LCD and Bluetooth
Mutex timer_mtx;            // Timer mutex
Mutex file_mtx;

// Control playback
volatile bool play = false;
volatile bool skip = false;

// Display progress bar and status
void LCD_ctrl(void const *argument)
{   
    int ind;  
    bool oldplay = true;    // only draw stuff as needed

    while(1)
    {
        // Calculate progress bar length
        timer_mtx.lock();
        ind = (((float) t.read() / song_len[songID]) * 112.0 * SCALE) + 8;
        timer_mtx.unlock();
        
        // Draw play/pause icon and progress bar
        serial_mtx.lock();
        uLCD.circle(64, 40, 20, WHITE);
        
        // If play status changes, redraw symbol
        if (oldplay != play)
            uLCD.filled_circle(64, 40, 18, BLACK);

        // Draw icon
        if (play)            
            uLCD.triangle(56, 30, 56, 50, 76, 40, WHITE);
        else {                
            uLCD.rectangle(56, 30, 60, 50, WHITE);
            uLCD.rectangle(68, 30, 72, 50, WHITE);
        } 
        
        // Print timestamp
        uLCD.locate(4, 13);
        timer_mtx.lock();
        uLCD.printf("%d:%02d/%d:%02d", (int) t.read()/60, (int) t.read() % 60,
                    (int) (song_len[songID]/SCALE) / 60, (int) (song_len[songID]/SCALE) % 60);
        timer_mtx.unlock();
        
        // Print progress bar
        uLCD.rectangle(8, 116, 120, 120, WHITE);
        uLCD.filled_rectangle(9, 117, ind, 119, WHITE);
            
        serial_mtx.unlock();
        
        // Run every 0.25 sec
        oldplay = play; 
        Thread::wait(250);
    }
}

// Play the music for the current song ID 
void playsong(void const *argument)
{
    string path;
    
    while(1)
    {
        if (play)
        {            
            // Get song file from SD card
            path = "/sd/mydir/";
            path += song_title[songID];
            path += ".wav";
            
            file_mtx.lock();
            wave_file=fopen(path.c_str(),"r");
            file_mtx.unlock();
            
            // Erase previous name
            serial_mtx.lock();
            uLCD.filled_rectangle(5, 75, 127, 100, BLACK);
            serial_mtx.unlock();
            
            // Calculate middle position for song title on screen
            int len = song_title[songID].length();
            int pixels = 128 - (len * 8);
            
            // Print song name on screen
            serial_mtx.lock();
            uLCD.locate(pixels/14, 10);
            uLCD.printf(song_title[songID].c_str());
            serial_mtx.unlock();
            
            // Start the timer if moving from 1 song to next
            timer_mtx.lock();
            t.start();
            timer_mtx.unlock();
            
            // Play the song
            waver.play(wave_file);
            
            if (!skip)
                // Increment songID to next song
                songID = (songID < SONGS - 1) ? (songID + 1) : 0; 
            else
            {
                waver.quit_playing(false);
                skip = false;
            }
            fclose(wave_file);
            
            // Erase progress bar
            serial_mtx.lock();
            uLCD.filled_rectangle(8, 116, 127, 120, BLACK);
            serial_mtx.unlock();
            
            // Reset timer
            timer_mtx.lock();
            t.reset();
            timer_mtx.unlock();            
        }
        
        // Execute every 0.1 sec
        Thread::wait(100);      
    }
}

int main() 
{    
    // Initialize LCD settings
    uLCD.cls();
    uLCD.baudrate(9600);
    
    // Start threads
    Thread t1(LCD_ctrl);
    Thread t2(playsong);
    
    char bnum=0;
    char bhit=0;
    
    // Poll bluetooth module
    while(1) {
        // If it's valid
        if(blue.readable()) {
            serial_mtx.lock();
            if (blue.getc()=='!') {
                if (blue.getc()=='B') { //button data packet
                    bnum = blue.getc(); //button number
                    bhit = blue.getc(); //1=hit, 0=release
                    if (blue.getc()==char(~('!' + 'B' + bnum + bhit))) { //checksum OK?
                        switch (bnum) {
                            case '1': // Play
                                if (bhit=='1' && !play) {
                                    led1 = 1;
                                    waver.set_stop(false);  // Enable wave player
                                    
                                    timer_mtx.lock();
                                    t.start();              // Enable timer
                                    timer_mtx.unlock();
                                    play = true;
                                }
                                break;
                            case '2': // Pause
                                if (bhit=='1' && play) {
                                    led1 = 0;
                                    waver.set_stop(true);   // Disable wave player
                                    
                                    timer_mtx.lock();
                                    t.stop();               // Stop the timer
                                    timer_mtx.unlock();
                                    play = false;
                                }
                                break;
                            case '3': // mute
                                if (bhit=='1') {
                                    waver.mute();
                                }
                                break;
                            case '4': // random song
                                if (bhit=='1') {
                                    timer_mtx.lock();
                                    srand(t.read());        // seed
                                    timer_mtx.unlock();
                                    
                                    int id;
                                    
                                    // Don't play same song twice
                                    while(songID == (id = rand() % 5)) {;}
                                    
                                    songID = id;
                                    
                                    // Treat like a skip
                                    skip = true; 
                                    waver.set_stop(false);                                    
                                    waver.quit_playing(true);
                                }
                                break;
                            case '5': // Increase vol (up arrow)
                                if (bhit=='1')
                                    waver.inc_vol();
                                break;
                            case '6': // Decrease vol (down arrow)
                                if (bhit=='1')
                                    waver.dec_vol();
                                break;
                            case '7': // Skip back (left arrow)
                                if (bhit=='1') {
                                    // If not playing, ignore command
                                    if (!play)
                                        break;
                                    // Go back 1 song
                                    songID = (songID == 0) ? SONGS - 1 : songID - 1;
                                    skip = true;                            
                                    waver.quit_playing(true);
                                }
                                break;
                            case '8': // Skip forward (right arrow)
                                if (bhit=='1') {
                                    if (!play)
                                        break;
                                    // Go forward 1 song
                                    songID = (songID >= SONGS - 1) ? 0 : songID + 1;
                                    skip = true;                                   
                                    waver.quit_playing(true);
            
                                }
                                break;
                            default:
                                break;
                        }
                    }
                }
            }
            serial_mtx.unlock();
            
            // Poll every 0.075 sec
            Thread::wait(75);
        }
    }
    
}


Video

This video demonstrates a few features of the music player.


Conclusions

With this project, we demonstrated the capability of the mbed library to control a fairly sophisticated embedded system with the appropriate supported hardware. We took advantage of the mbed RTOS library to control various components of the design while sharing hardware resources via mutex locks.

We encountered problems with library compatibility between the various components we used but found some workarounds by using older libraries. Even with protecting shared resources via mutex locks, we found that the timing of each thread's while() loop could result in some bugs depending on which thread yielded first. We found it was best to make the main thread poll the Bluetooth app fastest (every 0.075 s) while the music playing thread was the second fastest (every 0.1 s), and the LCD thread was the slowest (every 0.25 s).

Additionally, to control music playback we had to explicitly modify the wave_player library so that we could exit the loop that played the music when necessary (toggling a flag that returns from the function and adding some cleanup code for safety when a song is skipped), control playback (pause and resume music playing by toggling a flag), and control volume (writing a scaled unsigned short value to the given AnalogOut pin).


Please log in to post comments.