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
Picture of Design
Components
Our design made use of the following components:
- mbed Microcontroller (LPC1768) https://os.mbed.com/platforms/mbed-LPC1768/
- Class D Amplifier https://os.mbed.com/users/4180_1/notebook/tpa2005d1-class-d-audio-amp/
- Bluetooth Module https://os.mbed.com/users/4180_1/notebook/adafruit-bluefruit-le-uart-friend---bluetooth-low-/
- Speaker https://os.mbed.com/users/4180_1/notebook/using-a-speaker-for-audio-output/
- LCD Screen https://os.mbed.com/users/4180_1/notebook/ulcd-144-g2-128-by-128-color-lcd/
- uSD Card File System https://os.mbed.com/cookbook/SD-Card-File-System
- 5V Power Supply (and adapter jack)
- Potentiometer
The microcontroller is responsible for running the program software that interfaces with the various I/O components of the design. Because there are several components, we use the RTOS library to implement and manage threads for each sub process (LCD screen control, song control, Bluetooth, etc.).
Pin Connection Table
LPC1768 Pin | Component | Pin Name | Misc. |
---|---|---|---|
p5 | uSD File System | SPI MOSI | - |
p6 | uSD File System | SPI MISO | - |
p7 | uSD File System | SPI SCLK | - |
p8 | uSD File System | CS | - |
VOUT | uSD File System | VCC | - |
GND | uSD File System | GND | - |
p9 | uLCD Screen | RX | - |
p10 | uLCD Screen | TX | - |
p11 | uLCD Screen | Reset | - |
- | uLCD Screen | 5V | 5V Supply |
GND | uLCD Screen | GND | - |
p13 | Bluetooth Module | RXI | - |
p14 | Bluetooth Module | TXO | - |
- | Bluetooth Module | Vin | 5V Supply |
GND | Bluetooth Module | CTS | - |
GND | Bluetooth Module | GND | - |
p18 | Class D Amplifier | in+ | - |
- | Class D Amplifier | pwr+ | 5V Supply |
GND | Class D Amplifier | in- | - |
GND | Class D Amplifier | pwr- | - |
- | Class D Amplifier | out+ | Speaker + |
- | Class D Amplifier | out- | 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.
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.
The LCD screen displays the song title, progress (via a progress bar with time stamps), and status (playing/stopped).
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.