Belle Danielle CHAVEZ
Published © MIT

Personal MP3 Player

Build a 3-threaded FreeRTOS MP3 player on RT-Spark using 4 buttons, a potentiometer volume knob, LCD display, and status-tracking RGB LEDs.

IntermediateShowcase (no instructions)2 hours160
Personal MP3 Player

Things used in this project

Hardware components

LED (generic)
LED (generic)
×3
Jumper wires (generic)
Jumper wires (generic)
×1
Rotary potentiometer (generic)
Rotary potentiometer (generic)
×1
PTS 645 Series Switch
C&K Switches PTS 645 Series Switch
×1
Resistor 220 ohm
Resistor 220 ohm
×1
RT-Thread STM32F407ZGT6
×1

Software apps and online services

STM32CUBEPROG
STMicroelectronics STM32CUBEPROG

Story

Read more

Schematics

Personal MP3 Player

Code

Personal MP3 Player

C/C++
/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file           : main.c
 * @brief          : Refactored Project-Based Final Exam - Personal MP3 Player
 * : Mindanao State University - IIT (CCS / CA)
 ******************************************************************************
 */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "cmsis_os.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <math.h>
#include "Song_def.h"
#include "drv_lcd.h"
#include "drv_es8388.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
typedef enum {
    AUDIO_PLAYBACK_HALTED,
    AUDIO_PLAYBACK_ACTIVE
} AudioPlayerState;
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define TOTAL_TRACKS 8
#define DMA_BUFFER_LIMIT 512
/* USER CODE END PD */

/* Private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc1;
I2C_HandleTypeDef hi2c2;
I2S_HandleTypeDef hi2s3;
DMA_HandleTypeDef hdma_spi3_tx;
TIM_HandleTypeDef htim3;
UART_HandleTypeDef huart1;
SRAM_HandleTypeDef hsram1;

/* Thread and Mutex Handles */
osThreadId lcdLedsTaskHandle;
osThreadId buttonsTaskHandle;
osThreadId volumeTaskHandle;
osMutexId lcdMutexHandle;

/* USER CODE BEGIN PV */
uint16_t i2s_audio_stream[DMA_BUFFER_LIMIT];
volatile float active_frequency = 0.0f;
float active_phase = 0.0f;

const Song* song_library[TOTAL_TRACKS] = {&song0, &song1, &song2, &song3, &song4, &song5, &song6, &song7};

volatile int current_track_id = 0;
volatile int queued_track_id = 0;
volatile int current_note_pos = 0;
volatile AudioPlayerState playback_status = AUDIO_PLAYBACK_HALTED;

volatile bool pending_song_confirmation = false;
volatile uint32_t confirm_timeout_deadline = 0;
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_FSMC_Init(void);
static void MX_I2C2_Init(void);
static void MX_I2S3_Init(void);
static void MX_TIM3_Init(void);
static void MX_USART1_UART_Init(void);
static void MX_ADC1_Init(void);

/* Required Thread Functions (Do NOT rename per instructions) */
void update_lcd_leds_thread(void const * argument);
void polling_buttons(void const * argument);
void adjust_volume(void const * argument);

/* USER CODE BEGIN PFP */
void Populate_Audio_Stream(uint16_t buffer_offset, uint16_t stream_length)
{
    float phase_step = 0.0f;
    if (active_frequency > 0.0f) {
        phase_step = (2.0f * 3.1415926535f * active_frequency) / 44100.0f;
    }

    for (uint16_t idx = 0; idx < stream_length; idx += 2)
    {
        int16_t audio_sample = 0;
        if (active_frequency > 0.0f) {
            audio_sample = (int16_t)(sinf(active_phase) * 8192.0f);
            active_phase += phase_step;
            if (active_phase >= 2.0f * 3.1415926535f) {
                active_phase -= 2.0f * 3.1415926535f;
            }
        } else {
            active_phase = 0.0f;
        }

        i2s_audio_stream[buffer_offset + idx]     = (uint16_t)audio_sample;
        i2s_audio_stream[buffer_offset + idx + 1] = (uint16_t)audio_sample;
    }
}

void HAL_I2S_TxHalfCpltCallback(I2S_HandleTypeDef *hi2s) {
    Populate_Audio_Stream(0, DMA_BUFFER_LIMIT / 2);
}

void HAL_I2S_TxCpltCallback(I2S_HandleTypeDef *hi2s) {
    Populate_Audio_Stream(DMA_BUFFER_LIMIT / 2, DMA_BUFFER_LIMIT / 2);
}
/* USER CODE END PFP */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  HAL_Init();
  SystemClock_Config();

  MX_GPIO_Init();
  MX_DMA_Init();
  MX_FSMC_Init();
  MX_I2C2_Init();
  MX_I2S3_Init();
  MX_TIM3_Init();
  MX_USART1_UART_Init();
  MX_ADC1_Init();

  /* USER CODE BEGIN 2 */
  /* Display Initialization */
  drv_lcd_init();
  lcd_clear(BLACK);
  lcd_set_color(BLACK, WHITE);

  /* Audio Codec Setup */
  es8388_init(&hi2c2, NULL, 0);
  es8388_start(ES_MODE_DAC_ADC);
  es8388_volume_set(100);

  /* DMA Configuration for I2S */
  hdma_spi3_tx.Init.Mode = DMA_CIRCULAR;
  hdma_spi3_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
  hdma_spi3_tx.Init.MemBurst = DMA_MBURST_SINGLE;
  hdma_spi3_tx.Init.PeriphBurst = DMA_PBURST_SINGLE;

  if (HAL_DMA_Init(&hdma_spi3_tx) != HAL_OK) {
      Error_Handler();
  }

  /* Transmit user instructions via UART */
  char* init_msg = "\r\n=== Personal MP3 Player Instructions ===\r\n"
                   "1. Hold BTN2, BTN3, BTN4 to select song in binary.\r\n"
                   "2. Press BTN1 while holding to select.\r\n"
                   "3. Press BTN1 again within 5s to CONFIRM.\r\n"
                   "4. Press Play/Pause (PG0) anytime to Play/Stop.\r\n"
                   "=========================================\r\n";
  HAL_UART_Transmit(&huart1, (uint8_t*)init_msg, strlen(init_msg), 1000);
  /* USER CODE END 2 */

  /* Operating System Objects */
  osMutexDef(lcdMutex);
  lcdMutexHandle = osMutexCreate(osMutex(lcdMutex));

  osThreadDef(lcdLedsTask, update_lcd_leds_thread, osPriorityNormal, 0, 256);
  lcdLedsTaskHandle = osThreadCreate(osThread(lcdLedsTask), NULL);

  osThreadDef(buttonsTask, polling_buttons, osPriorityNormal, 0, 128);
  buttonsTaskHandle = osThreadCreate(osThread(buttonsTask), NULL);

  osThreadDef(volumeTask, adjust_volume, osPriorityNormal, 0, 128);
  volumeTaskHandle = osThreadCreate(osThread(volumeTask), NULL);

  /* Start scheduler */
  osKernelStart();

  while (1)
  {
      /* Reduce power consumption while idle */
      HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
  }
}

/**
 * @brief Handles LCD output, RGB status, and Audio Timing
 */
void update_lcd_leds_thread(void const * argument)
{
    HAL_I2S_Transmit_DMA(&hi2s3, i2s_audio_stream, DMA_BUFFER_LIMIT);

    for(;;)
    {
        /* 1. Control the On-Board RGB LED based on current mode */
        if (pending_song_confirmation) {
            HAL_GPIO_WritePin(LED_G_GPIO_Port, LED_G_Pin, GPIO_PIN_SET);
            HAL_GPIO_WritePin(LED_R_GPIO_Port, LED_R_Pin, GPIO_PIN_RESET);
            HAL_GPIO_WritePin(LED_B_GPIO_Port, LED_B_Pin, GPIO_PIN_RESET);
        } else if (playback_status == AUDIO_PLAYBACK_ACTIVE) {
            HAL_GPIO_WritePin(LED_B_GPIO_Port, LED_B_Pin, GPIO_PIN_SET);
            HAL_GPIO_WritePin(LED_R_GPIO_Port, LED_R_Pin, GPIO_PIN_RESET);
            HAL_GPIO_WritePin(LED_G_GPIO_Port, LED_G_Pin, GPIO_PIN_RESET);
        } else {
            HAL_GPIO_WritePin(LED_R_GPIO_Port, LED_R_Pin, GPIO_PIN_SET);
            HAL_GPIO_WritePin(LED_G_GPIO_Port, LED_G_Pin, GPIO_PIN_RESET);
            HAL_GPIO_WritePin(LED_B_GPIO_Port, LED_B_Pin, GPIO_PIN_RESET);
        }

        /* 2. Update Graphical Interface safely via Mutex */
        if (osMutexWait(lcdMutexHandle, 10) == osOK)
        {
            char upper_line[32];
            char lower_line[32];

            if (pending_song_confirmation) {
                snprintf(upper_line, sizeof(upper_line), "CONFIRM TRACK?  ");
                snprintf(lower_line, sizeof(lower_line), "Sel: %-12s", song_library[queued_track_id]->name1);
            } else {
                snprintf(upper_line, sizeof(upper_line), "Status: %s  ", (playback_status == AUDIO_PLAYBACK_ACTIVE) ? "PLAYING" : "STOPPED");
                snprintf(lower_line, sizeof(lower_line), "Song %d: %-12s", current_track_id + 1, song_library[current_track_id]->name1);
            }

            lcd_show_string(10, 20, 16, upper_line);
            lcd_show_string(10, 50, 16, lower_line);
            osMutexRelease(lcdMutexHandle);
        }

        /* 3. Execute Song Notes and Timing Logic */
        if (playback_status == AUDIO_PLAYBACK_ACTIVE && !pending_song_confirmation)
        {
            const Song* track = song_library[current_track_id];
            uint32_t ms_per_beat = 60000 / track->tempo;

            if (current_note_pos < track->length)
            {
                active_frequency = track->note[current_note_pos];

                uint32_t note_duration = (uint32_t)(track->beat[current_note_pos] * ms_per_beat);
                uint32_t remaining_delay = (note_duration > 30) ? (note_duration - 30) : 1;

                while(remaining_delay > 0 && playback_status == AUDIO_PLAYBACK_ACTIVE && !pending_song_confirmation) {
                    uint32_t wait_slice = (remaining_delay > 15) ? 15 : remaining_delay;
                    osDelay(wait_slice);
                    remaining_delay -= wait_slice;
                }

                if (playback_status == AUDIO_PLAYBACK_ACTIVE && !pending_song_confirmation) {
                    active_frequency = 0.0f; // Silence gap between notes
                    osDelay(30);
                    current_note_pos++;
                } else {
                    active_frequency = 0.0f;
                }
            }
            else
            {
                /* Reached the end of the song */
                playback_status = AUDIO_PLAYBACK_HALTED;
                active_frequency = 0.0f;
                current_note_pos = 0;
            }
        }
        else
        {
            active_frequency = 0.0f;
            osDelay(50);
        }
    }
}

/**
 * @brief Checks GPIO state and manages the 5-second Track Confirmation mechanism
 */
void polling_buttons(void const * argument)
{
    bool btn1_last_state = false;
    bool user_btn_last_state = false;

    for(;;)
    {
        /* Read binary input buttons */
        bool btn_2 = (HAL_GPIO_ReadPin(BTN2_GPIO_Port, BTN2_Pin) == GPIO_PIN_RESET);
        bool btn_3 = (HAL_GPIO_ReadPin(BTN3_GPIO_Port, BTN3_Pin) == GPIO_PIN_RESET);
        bool btn_4 = (HAL_GPIO_ReadPin(BTN4_GPIO_Port, BTN4_Pin) == GPIO_PIN_RESET);

        int target_binary_id = (btn_2 ? 1 : 0) | (btn_3 ? 2 : 0) | (btn_4 ? 4 : 0);

        bool btn_1 = (HAL_GPIO_ReadPin(BTN1_GPIO_Port, BTN1_Pin) == GPIO_PIN_RESET);
        bool play_pause_btn = (HAL_GPIO_ReadPin(USER_BTN_GPIO_Port, USER_BTN_Pin) == GPIO_PIN_RESET);

        /* Selection and Confirmation Logic */
        if (btn_1 && !btn1_last_state)
        {
            if (!pending_song_confirmation)
            {
                queued_track_id = target_binary_id % TOTAL_TRACKS;
                pending_song_confirmation = true;
                confirm_timeout_deadline = osKernelSysTick() + 5000;
            }
            else
            {
                current_track_id = queued_track_id;
                current_note_pos = 0;
                playback_status = AUDIO_PLAYBACK_ACTIVE;
                pending_song_confirmation = false;
            }
        }
        btn1_last_state = btn_1;

        /* Auto-cancel if 5 seconds elapsed */
        if (pending_song_confirmation && (osKernelSysTick() > confirm_timeout_deadline))
        {
            pending_song_confirmation = false;
        }

        /* Toggle playback state */
        if (play_pause_btn && !user_btn_last_state)
        {
            if (playback_status == AUDIO_PLAYBACK_ACTIVE) {
                playback_status = AUDIO_PLAYBACK_HALTED

Credits

Belle Danielle CHAVEZ
7 projects • 1 follower

Comments