diff --git a/Code/MP3Player/.gitignore b/Code/MP3Player/.gitignore new file mode 100644 index 0000000..6d5d435 --- /dev/null +++ b/Code/MP3Player/.gitignore @@ -0,0 +1,4 @@ +.idea +cmake-* +build + diff --git a/Code/MP3Player/CMakeLists.txt b/Code/MP3Player/CMakeLists.txt new file mode 100644 index 0000000..184aaf1 --- /dev/null +++ b/Code/MP3Player/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.16) + +# Set the target board +set(YAHAL_BOARD "rpi-pico") +# set(YAHAL_DIR ~/gitlab/YAHAL) + +# Locate the YAHAL library folder +include(YAHAL_import.cmake) + +project(pico-mp3-player) + +add_executable(pico-mp3-player + pico_mp3_player.cpp + main_task.cpp + sd_reader_task.cpp + mp3_decoder_task.cpp + i2ckbd.cpp +) + +# Add YAHAL, MAD and FatFs libraries +yahal_add_me(pico-mp3-player) +yahal_add_library(pico-mp3-player MAD) +yahal_add_library(pico-mp3-player FatFs) +yahal_add_library(pico-mp3-player uGUI) + +yahal_add_custom_targets(pico-mp3-player) +yahal_add_extra_outputs(pico-mp3-player) diff --git a/Code/MP3Player/README.md b/Code/MP3Player/README.md new file mode 100644 index 0000000..d6c205f --- /dev/null +++ b/Code/MP3Player/README.md @@ -0,0 +1,58 @@ +# Picocalc simple mp3 player + +## toolchain + +``` +arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi +``` + +YAHAL + +https://github.com/cuu/YAHAL.git branch picocalc + +## How to compile + +### Get YAHAL first +``` +git clone -b picocalc https://github.com/cuu/YAHAL.git +``` +### Get arm toolchain + +``` +wget https://developer.arm.com/-/media/Files/downloads/gnu/13.3.rel1/binrel/arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi.tar.xz + +tar xvf arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi.tar.xz +``` + +### Compile + +set **YAHAL_DIR** to the path of your local YAHAL location +```bash +export YAHAL_DIR=/wherever/yahal/is +``` +set **arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi/bin** in your $PATH + +```bash +export PATH=/wherever/arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi/bin/is:$PATH +``` + +then + +``` +mkdir build +cd build +cmake .. +make +``` + +Copy pico-mp3-player.uf2 to picocalc + +### Notes +Currently only supports up to 64 mp3s + +Project original address: https://git.fh-aachen.de/Terstegge/YAHAL + +Thanks to YAHAL +Without this great project YAHAL, mp3 playback on pico would be a pain + + diff --git a/Code/MP3Player/YAHAL_import.cmake b/Code/MP3Player/YAHAL_import.cmake new file mode 100644 index 0000000..5c4f6e4 --- /dev/null +++ b/Code/MP3Player/YAHAL_import.cmake @@ -0,0 +1,30 @@ +# This script tries to locate the YAHAL root +# folder and reads the YAHAL_init.cmake file. + +# Check if we can get the path from a environment variable +if (DEFINED ENV{YAHAL_DIR} AND (NOT YAHAL_DIR)) + set(YAHAL_DIR $ENV{YAHAL_DIR}) + message("Using YAHAL_DIR from environment ('${YAHAL_DIR}')") +endif() + +# Check if we need to locate the YAHAL root directory +if (NOT YAHAL_DIR) + message("Trying to find YAHAL ...") + find_path(YAHAL_DIR .yahal_version . .. ../.. ../../.. ../../../..) +endif () + +# Try to resolve a relative path +get_filename_component(YAHAL_DIR "${YAHAL_DIR}" REALPATH BASE_DIR "${CMAKE_CURRENT_LIST_DIR}") +if (NOT EXISTS ${YAHAL_DIR}) + message(FATAL_ERROR "Directory '${YAHAL_DIR}' not found") +endif () + +# Check if YAHAL_DIR points to the correct folder. +set(YAHAL_INIT_CMAKE_FILE ${YAHAL_DIR}/cmake/YAHAL_init.cmake) +if (NOT EXISTS ${YAHAL_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${YAHAL_DIR}' does not appear to be a YAHAL root folder") +endif () + +# Finally include the init-script +include(${YAHAL_INIT_CMAKE_FILE}) + diff --git a/Code/MP3Player/config.h b/Code/MP3Player/config.h new file mode 100644 index 0000000..5e4ce9e --- /dev/null +++ b/Code/MP3Player/config.h @@ -0,0 +1,43 @@ +#ifndef CONFIG_H +#define CONFIG_H + +// GPIOs for SPI interface (SD card) +#define SD_SPI0 0 +#define SD_SCLK_PIN 18 +#define SD_MOSI_PIN 19 +#define SD_MISO_PIN 16 +#define SD_CS_PIN 17 + +#define LCD_SPI1 1 +#define LCD_SCK_PIN 10 +#define LCD_MOSI_PIN 11 +#define LCD_MISO_PIN 12 +#define LCD_CS_PIN 13 +#define LCD_DC_PIN 14 +#define LCD_RST_PIN 15 + +// GPIOs for audio output +#define AUDIO_LEFT 28 +#define AUDIO_RIGHT 27 + +// GPIOs for buttons +#define NEXT_BUTTON 2 +#define PART_BUTTON 3 + +// Pico-internal GPIOs +#define PICO_PS 23 +#define LED_PIN 25 + +// Task stack sizes and priorities +#define MAIN_STACKSIZE 6000 +#define MP3_DECODER_STACKSIZE 8200 +#define SD_READER_STACKSIZE 500 +#define MAIN_PRIORITY 50 + +// Buffer sizes +#define MP3_FRAME_SIZE 2881 +#define MP3_BUF_SIZE 4096 + +#define MAX_FILES 64 +#define ITEMS_PER_PAGE 12 +#endif // CONFIG_H diff --git a/Code/MP3Player/i2ckbd.cpp b/Code/MP3Player/i2ckbd.cpp new file mode 100644 index 0000000..ef120a0 --- /dev/null +++ b/Code/MP3Player/i2ckbd.cpp @@ -0,0 +1,41 @@ +#include "i2ckbd.h" + +i2c_kbd::i2c_kbd() + :_master(I2C_KBD_MOD,I2C_KBD_SDA, I2C_KBD_SCL,0), + c(0), + _stat(0){ + _master.setSpeed(I2C_KBD_SPEED);//also inited +} + +i2c_kbd::~i2c_kbd() { + +} + +int i2c_kbd::write_i2c_kbd() { + unsigned char msg[2]; + msg[0] = 0x09; + _master.i2cWrite(I2C_KBD_ADDR,msg,1); + return 0; +} + +int i2c_kbd::read_i2c_kbd() { + uint16_t buff = 0; + + _master.i2cRead(I2C_KBD_ADDR,(uint8_t*)&buff,2); + + if(buff!=0) { + return buff; + } + return -1; +} + +int i2c_kbd::I2C_Send_RegData(int i2caddr, int reg, char command) { + int retval; + unsigned char I2C_Send_Buffer[2]; + I2C_Send_Buffer[0]=reg; + I2C_Send_Buffer[1]=command; + uint8_t I2C_Sendlen=2; + + retval = _master.i2cWrite(i2caddr,I2C_Send_Buffer,I2C_Sendlen); + return retval; +} diff --git a/Code/MP3Player/i2ckbd.h b/Code/MP3Player/i2ckbd.h new file mode 100644 index 0000000..095266c --- /dev/null +++ b/Code/MP3Player/i2ckbd.h @@ -0,0 +1,29 @@ +#ifndef _I2C_KBD_H +#define _I2C_KBD_H + +#include "task.h" +#include "gpio_rp2040.h" +#include "i2c_rp2040.h" + +#define I2C_KBD_MOD 1 +#define I2C_KBD_SDA 6 +#define I2C_KBD_SCL 7 +#define I2C_KBD_SPEED 400000 +#define I2C_KBD_ADDR 0x1F + +class i2c_kbd { +public: + i2c_kbd(); + + int write_i2c_kbd(); + int read_i2c_kbd(); + int I2C_Send_RegData(int i2caddr, int reg, char command); + + virtual ~i2c_kbd(); +private: + i2c_rp2040 _master; + int c; + int _stat; +}; + +#endif \ No newline at end of file diff --git a/Code/MP3Player/icon_enter.h b/Code/MP3Player/icon_enter.h new file mode 100644 index 0000000..be5c471 --- /dev/null +++ b/Code/MP3Player/icon_enter.h @@ -0,0 +1,45 @@ + +static unsigned int enter_width = 24; +static unsigned int enter_height = 13; + +/* Call this macro repeatedly. After each use, the pixel data can be extracted */ + +#define ENTER_PIXEL(data,pixel) {\ +pixel[0] = enter_header_data_cmap[(unsigned char)data[0]][0]; \ +pixel[1] = enter_header_data_cmap[(unsigned char)data[0]][1]; \ +pixel[2] = enter_header_data_cmap[(unsigned char)data[0]][2]; \ +data ++; } + +static unsigned char enter_header_data_cmap[3][3] = { + {153,153,153}, + {255,255,255}, + { 0, 0, 0} +}; +static unsigned char enter_header_data[] = { + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 2,2,2,2,2,2,2,2, + 2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,2,2, + 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,2, + 2,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0, + 0,0,1,0,0,0,0,2, + 2,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0, + 0,0,1,0,0,0,0,2, + 2,0,0,0,1,0,0,0,0,0,0,1,1,1,1,0, + 0,1,1,1,1,0,0,2, + 2,0,0,0,1,1,1,1,1,0,0,1,0,0,0,1, + 0,0,1,0,0,0,0,2, + 2,0,0,0,1,0,0,0,0,0,0,1,0,0,0,1, + 0,0,1,0,0,0,0,2, + 2,0,0,0,1,0,0,0,0,0,0,1,0,0,0,1, + 0,0,1,0,0,0,0,2, + 2,0,0,0,1,1,1,1,1,0,0,1,0,0,0,1, + 0,0,1,1,1,0,0,2, + 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,2, + 2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,2,2, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 2,2,2,2,2,2,2,2 +}; diff --git a/Code/MP3Player/icon_esc.h b/Code/MP3Player/icon_esc.h new file mode 100644 index 0000000..a4f01cc --- /dev/null +++ b/Code/MP3Player/icon_esc.h @@ -0,0 +1,45 @@ + +static unsigned int esc_width = 23; +static unsigned int esc_height = 13; + +/* Call this macro repeatedly. After each use, the pixel data can be extracted */ + +#define ESC_PIXEL(data,pixel) {\ +pixel[0] = esc_header_data_cmap[(unsigned char)data[0]][0]; \ +pixel[1] = esc_header_data_cmap[(unsigned char)data[0]][1]; \ +pixel[2] = esc_header_data_cmap[(unsigned char)data[0]][2]; \ +data ++; } + +static unsigned char esc_header_data_cmap[3][3] = { + {153,153,153}, + {255,255,255}, + { 0, 0, 0} +}; +static unsigned char esc_header_data[] = { + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 2,2,2,2,2,2,2, + 2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,2,2, + 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,2, + 2,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,2, + 2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,2, + 2,0,0,1,0,0,0,0,0,0,0,1,1,1,1,0, + 0,1,1,1,0,0,2, + 2,0,0,1,1,1,1,1,0,0,1,0,0,0,0,0, + 1,0,0,0,0,0,2, + 2,0,0,1,0,0,0,0,0,0,0,1,1,1,1,0, + 1,0,0,0,0,0,2, + 2,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0, + 1,0,0,0,0,0,2, + 2,0,0,1,1,1,1,1,0,0,1,1,1,1,0,0, + 0,1,1,1,0,0,2, + 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,2, + 2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,2,2, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 2,2,2,2,2,2,2 +}; diff --git a/Code/MP3Player/icon_nav.h b/Code/MP3Player/icon_nav.h new file mode 100644 index 0000000..68d4135 --- /dev/null +++ b/Code/MP3Player/icon_nav.h @@ -0,0 +1,29 @@ +static unsigned int nav_width = 11; +static unsigned int nav_height = 11; + +/* Call this macro repeatedly. After each use, the pixel data can be extracted */ + +#define NAV_PIXEL(data,pixel) {\ +pixel[0] = nav_header_data_cmap[(unsigned char)data[0]][0]; \ +pixel[1] = nav_header_data_cmap[(unsigned char)data[0]][1]; \ +pixel[2] = nav_header_data_cmap[(unsigned char)data[0]][2]; \ +data ++; } + +static unsigned char nav_header_data_cmap[3][3] = { + {153,153,153}, + {255,255,255}, + { 0, 0, 0} +}; +static unsigned char nav_header_data[] = { + 2,0,0,0,0,0,0,0,0,0,2, + 0,2,0,0,0,1,0,0,0,2,0, + 0,0,2,0,1,1,1,0,2,0,0, + 0,0,0,2,0,0,0,2,0,0,0, + 0,0,1,0,2,0,2,0,1,0,0, + 0,1,1,0,0,2,0,0,1,1,0, + 0,0,1,0,2,0,2,0,1,0,0, + 0,0,0,2,0,0,0,2,0,0,0, + 0,0,2,0,1,1,1,0,2,0,0, + 0,2,0,0,0,1,0,0,0,2,0, + 2,0,0,0,0,0,0,0,0,0,2 +}; diff --git a/Code/MP3Player/icon_p.h b/Code/MP3Player/icon_p.h new file mode 100644 index 0000000..b4713f1 --- /dev/null +++ b/Code/MP3Player/icon_p.h @@ -0,0 +1,32 @@ + +static unsigned int icon_p_width = 13; +static unsigned int icon_p_height = 13; + +/* Call this macro repeatedly. After each use, the pixel data can be extracted */ + +#define ICON_P_PIXEL(data,pixel) {\ +pixel[0] = icon_p_header_data_cmap[(unsigned char)data[0]][0]; \ +pixel[1] = icon_p_header_data_cmap[(unsigned char)data[0]][1]; \ +pixel[2] = icon_p_header_data_cmap[(unsigned char)data[0]][2]; \ +data ++; } + +static unsigned char icon_p_header_data_cmap[3][3] = { + {153,153,153}, + {255,255,255}, + { 0, 0, 0} +}; +static unsigned char icon_p_header_data[] = { + 2,2,2,2,2,2,2,2,2,2,2,2,2, + 2,2,0,0,0,0,0,0,0,0,0,2,2, + 2,0,0,0,0,0,0,0,0,0,0,0,2, + 2,0,0,0,1,1,1,1,0,0,0,0,2, + 2,0,0,0,1,0,0,0,1,0,0,0,2, + 2,0,0,0,1,0,0,0,1,0,0,0,2, + 2,0,0,0,1,1,1,1,0,0,0,0,2, + 2,0,0,0,1,0,0,0,0,0,0,0,2, + 2,0,0,0,1,0,0,0,0,0,0,0,2, + 2,0,0,0,1,0,0,0,0,0,0,0,2, + 2,0,0,0,0,0,0,0,0,0,0,0,2, + 2,2,0,0,0,0,0,0,0,0,0,2,2, + 2,2,2,2,2,2,2,2,2,2,2,2,2 +}; diff --git a/Code/MP3Player/logo50.h b/Code/MP3Player/logo50.h new file mode 100644 index 0000000..ff8342c --- /dev/null +++ b/Code/MP3Player/logo50.h @@ -0,0 +1,234 @@ +/* GIMP header image file format (INDEXED): /home/guu/Pictures/logo50.h */ + +static unsigned int logo_width = 50; +static unsigned int logo_height = 50; + +/* Call this macro repeatedly. After each use, the pixel data can be extracted */ + +#define HEADER_PIXEL(data,pixel) {\ +pixel[0] = header_data_cmap[(unsigned char)data[0]][0]; \ +pixel[1] = header_data_cmap[(unsigned char)data[0]][1]; \ +pixel[2] = header_data_cmap[(unsigned char)data[0]][2]; \ +data ++; } + +static unsigned char header_data_cmap[17][3] = { + { 0, 0, 0}, + { 36, 36, 36}, + { 63, 63, 63}, + { 84, 84, 84}, + {103,103,103}, + {120,120,120}, + {135,135,135}, + {150,150,150}, + {163,163,163}, + {176,176,176}, + {189,189,189}, + {201,201,201}, + {213,213,213}, + {224,224,224}, + {235,235,235}, + {245,245,245}, + {255,255,255} + }; +static unsigned char header_data[] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,3,4,7,8,8,8,4,4,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,3,8,12,16,16,16,16,16,16,16,16,16,16,14,10, + 5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,6, + 14,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,15,8,2,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,7,15,16, + 16,16,16,16,13,10,8,5,4,4,4,8,9,12,16,16, + 16,16,16,16,9,1,0,0,0,0,0,0,0,0,0,0, + 0,0, + 0,0,0,0,0,0,0,0,0,0,0,3,13,16,16,16, + 16,12,6,1,0,0,0,0,0,0,0,0,0,0,0,5, + 10,16,16,16,16,15,5,0,0,0,0,0,0,0,0,0, + 0,0, + 0,0,0,0,0,0,0,0,0,0,7,16,16,16,16,10, + 3,0,1,6,14,16,16,16,16,16,16,16,12,10,6,1, + 0,1,8,15,16,16,16,11,1,0,0,0,0,0,0,0, + 0,0, + 0,0,0,0,0,0,0,0,0,10,16,16,16,13,4,0, + 2,9,15,16,16,16,16,16,16,16,16,16,16,16,16,16, + 10,4,0,2,11,16,16,16,13,1,0,0,0,0,0,0, + 0,0, + 0,0,0,0,0,0,0,0,10,16,16,16,9,0,1,9, + 16,16,16,16,16,16,12,9,8,8,8,8,11,14,16,16, + 16,16,12,2,0,5,15,16,16,13,1,0,0,0,0,0, + 0,0, + 0,0,0,0,0,0,0,10,16,16,16,6,0,5,15,16, + 16,16,16,12,6,0,0,0,0,0,0,0,0,0,3,10, + 16,16,16,16,8,0,3,15,16,16,13,1,0,0,0,0, + 0,0, + 0,0,0,0,0,0,8,16,16,16,4,0,8,16,16,16, + 16,15,5,0,0,0,0,0,0,0,0,0,1,8,2,0, + 3,13,16,16,16,11,1,1,13,16,16,12,0,0,0,0, + 0,0, + 0,0,0,0,0,4,16,16,16,6,0,10,16,16,16,16, + 13,2,0,0,0,0,0,0,0,0,0,0,3,16,16,7, + 0,1,13,16,16,16,13,1,3,15,16,16,8,0,0,0, + 0,0, + 0,0,0,0,1,14,16,16,8,0,9,16,16,16,16,15, + 3,0,0,0,0,0,0,0,0,0,0,0,0,4,13,16, + 8,0,3,15,16,16,16,13,0,4,16,16,16,3,0,0, + 0,0, + 0,0,0,0,8,16,16,12,0,5,16,16,16,16,16,6, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,13, + 16,5,0,8,16,16,16,16,9,0,8,16,16,12,0,0, + 0,0, + 0,0,0,1,15,16,16,3,1,15,16,16,16,16,14,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, + 15,12,0,1,15,16,16,16,16,4,0,14,16,16,4,0, + 0,0, + 0,0,0,8,16,16,10,0,10,16,16,16,16,16,7,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 2,5,2,0,9,16,16,16,16,14,0,6,16,16,12,0, + 0,0, + 0,0,0,15,16,16,2,2,16,16,16,16,16,16,2,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,16,10,0,3,16,16,16,16,16,8,0,14,16,16,3, + 0,0, + 0,0,6,16,16,10,0,10,16,16,16,16,16,14,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,5,2,0,0,14,16,16,16,16,14,0,6,16,16,9, + 0,0, + 0,0,10,16,16,5,1,16,16,16,16,16,16,9,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,11,16,16,16,16,16,5,1,16,16,14, + 0,0, + 0,0,15,16,15,0,7,16,16,16,16,15,7,11,12,9, + 5,0,3,0,0,0,0,1,10,12,9,5,9,16,16,14, + 9,3,0,0,0,8,16,16,16,16,16,10,0,11,16,16, + 3,0, + 0,3,16,16,11,0,11,16,16,16,15,6,13,13,6,11, + 16,13,10,16,13,5,1,13,15,7,10,15,15,6,16,16, + 16,16,9,0,0,6,16,16,16,16,16,15,0,7,16,16, + 7,0, + 0,5,16,16,8,0,15,16,16,16,7,14,12,8,0,0, + 8,16,9,14,16,9,13,14,10,0,0,5,16,12,10,16, + 16,16,16,6,0,4,16,16,16,16,16,16,3,4,16,16, + 9,0, + 0,8,16,16,4,1,16,16,16,16,5,16,8,13,0,0, + 1,14,14,10,16,6,16,8,16,0,0,0,11,16,3,8, + 8,8,8,6,2,5,15,16,16,16,16,16,5,0,16,16, + 12,0, + 0,8,16,16,4,4,16,16,16,16,4,16,8,11,0,0, + 0,9,16,8,16,4,16,12,13,0,0,0,5,16,6,16, + 16,16,16,16,16,12,11,16,16,16,16,16,8,0,16,16, + 12,0, + 0,11,16,16,3,4,16,16,16,16,7,16,10,14,0,0, + 0,12,16,8,16,6,16,10,16,2,0,0,8,16,8,16, + 16,16,16,16,16,10,14,16,16,16,16,16,8,0,13,16, + 12,0, + 0,12,16,16,1,4,16,16,16,16,7,15,14,12,6,0, + 1,14,12,12,16,7,14,16,11,12,0,0,11,15,4,8, + 10,12,12,4,1,0,16,16,16,16,16,16,8,0,12,16, + 12,0, + 0,9,16,16,4,4,16,16,16,16,14,7,16,13,6,2, + 13,16,7,16,10,1,4,16,14,14,3,11,16,7,15,16, + 16,14,5,0,0,1,16,16,16,16,16,16,8,0,15,16, + 12,0, + 0,8,16,16,4,4,16,16,16,16,16,13,7,15,16,16, + 15,5,2,1,0,0,0,4,14,16,16,15,6,10,12,9, + 5,0,0,0,0,4,16,16,16,16,16,16,7,0,16,16, + 12,0, + 0,8,16,16,6,0,16,16,16,16,16,16,15,7,4,4, + 0,0,0,0,0,3,8,8,3,3,4,0,0,0,0,0, + 0,0,0,0,0,4,16,16,16,16,16,16,4,2,16,16, + 11,0, + 0,4,16,16,9,0,14,16,16,16,16,16,16,12,0,0, + 0,0,0,2,11,16,16,16,16,12,2,0,0,0,0,0, + 0,0,0,0,0,4,16,16,16,16,16,16,1,4,16,16, + 8,0, + 0,1,16,16,13,0,10,16,16,16,16,16,16,13,0,0, + 0,0,7,15,16,16,16,16,16,16,16,10,4,0,0,0, + 0,0,0,0,0,1,16,16,16,16,16,14,0,9,16,16, + 5,0, + 0,0,14,16,16,2,5,16,16,16,16,16,16,12,0,0, + 1,13,16,16,16,16,16,16,16,16,16,16,16,12,0,0, + 0,0,0,0,0,0,16,16,16,16,16,9,0,13,16,16, + 1,0, + 0,0,9,16,16,7,0,14,16,16,16,16,16,10,0,0, + 10,14,9,14,16,16,16,16,16,16,11,6,6,16,7,0, + 0,0,0,0,0,0,11,16,16,16,16,3,2,16,16,13, + 0,0, + 0,0,3,16,16,14,0,8,16,16,16,16,16,6,0,0, + 11,13,2,0,2,4,4,4,3,0,0,5,13,16,6,1, + 1,0,0,0,0,0,6,16,16,16,12,0,9,16,16,7, + 0,0, + 0,0,0,14,16,16,4,1,15,16,16,16,16,1,0,0, + 1,13,16,12,8,8,8,8,8,12,15,16,16,9,0,11, + 7,0,0,0,0,0,0,14,16,16,4,1,15,16,15,1, + 0,0, + 0,0,0,6,16,16,13,0,7,16,16,16,10,0,0,4, + 14,4,8,16,16,16,16,16,16,16,16,10,2,1,10,16, + 13,0,0,0,0,0,0,6,16,11,0,8,16,16,10,0, + 0,0, + 0,0,0,0,14,16,16,7,0,12,16,16,2,0,0,7, + 16,16,6,4,12,16,16,16,16,12,4,1,9,15,16,16, + 16,2,0,0,0,0,0,0,12,1,3,16,16,16,2,0, + 0,0, + 0,0,0,0,5,16,16,15,2,1,15,10,0,0,0,9, + 16,16,16,6,0,4,11,10,6,0,7,15,16,16,16,16, + 16,7,0,0,0,0,0,0,0,0,13,16,16,8,0,0, + 0,0, + 0,0,0,0,0,11,16,16,13,1,3,1,0,0,0,12, + 16,16,16,16,10,3,0,1,5,12,16,16,16,16,16,16, + 16,13,0,0,0,0,0,0,0,9,16,16,14,1,0,0, + 0,0, + 0,0,0,0,0,1,14,16,16,10,0,0,0,0,0,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,2,0,0,0,0,0,6,16,16,16,3,0,0,0, + 0,0, + 0,0,0,0,0,0,3,15,16,16,10,0,0,0,3,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,8,0,0,0,0,6,16,16,16,6,0,0,0,0, + 0,0, + 0,0,0,0,0,0,0,6,16,16,16,12,1,0,5,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,14,0,0,0,9,16,16,16,10,0,0,0,0,0, + 0,0, + 0,0,0,0,0,0,0,0,6,16,16,16,14,4,0,3, + 12,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,14,6,0,2,12,16,16,16,10,0,0,0,0,0,0, + 0,0, + 0,0,0,0,0,0,0,0,0,3,15,16,16,16,10,2, + 0,3,10,15,16,16,16,16,16,16,16,16,16,16,16,11, + 5,0,1,8,15,16,16,16,7,0,0,0,0,0,0,0, + 0,0, + 0,0,0,0,0,0,0,0,0,0,2,13,16,16,16,16, + 10,2,0,0,3,7,8,12,12,12,12,9,8,4,0,0, + 1,8,15,16,16,16,15,4,0,0,0,0,0,0,0,0, + 0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,8,15,16,16, + 16,16,13,8,4,0,0,0,0,0,0,0,0,3,7,11, + 16,16,16,16,16,9,1,0,0,0,0,0,0,0,0,0, + 0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,2,10,16, + 16,16,16,16,16,16,14,12,12,12,12,13,16,16,16,16, + 16,16,16,10,2,0,0,0,0,0,0,0,0,0,0,0, + 0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, + 7,14,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 14,9,2,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,2,7,10,12,16,16,16,16,16,16,13,11,8,3, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0 + }; diff --git a/Code/MP3Player/main_task.cpp b/Code/MP3Player/main_task.cpp new file mode 100644 index 0000000..4ecc01a --- /dev/null +++ b/Code/MP3Player/main_task.cpp @@ -0,0 +1,673 @@ +#include +#include +#include "main_task.h" +#include "logo50.h" +#include "icon_enter.h" +#include "icon_esc.h" +#include "icon_nav.h" +#include "icon_p.h" + +main_task::main_task() + : task("Main", MAIN_STACKSIZE), + _cs(SD_CS_PIN), + _spi(SD_SPI0, SD_MISO_PIN, SD_MOSI_PIN, SD_SCLK_PIN, _cs), + _sd(_spi), + _next(NEXT_BUTTON), + _part(PART_BUTTON), + _ps(PICO_PS), + _fs(_sd), + _lcd_rst(LCD_RST_PIN), + _lcd_dc(LCD_DC_PIN), + _lcd_cs(LCD_CS_PIN), + _lcd_spi(LCD_SPI1, LCD_MISO_PIN, LCD_MOSI_PIN, LCD_SCK_PIN, _lcd_cs), + _lcd(_lcd_spi,_lcd_rst,_lcd_dc,ili9488_drv::PICO320), + _gui(_lcd), + _sel_index(0), + _page_index(0), + _last_page_index(0), + _last_sel_index(0), + update_sel(0), + update_required(1), + playing(0), + last_play_pos(0), + play_pos(0), + num_files(0), + pause_flag(0), + play_flag(0){ + + _next.gpioMode(GPIO::INPUT | GPIO::PULLUP); + _part.gpioMode(GPIO::INPUT | GPIO::PULLUP); + _ps.gpioMode(GPIO::OUTPUT | GPIO::INIT_HIGH); + _gui.FontSelect(&FONT_8X14); + _gui.SetForecolor(C_GAINSBORO); + + for(auto & menu_item : menu_items){ + menu_item.fsize = 0; + menu_item.fname = nullptr; + menu_item.x = 0; + menu_item.y = 0; + } +} + +void main_task::draw_string(int x,int y,const char*str,uint8_t fnt = 1){ + + if(fnt == 0) { + _gui.FontSelect(&FONT_6X8); + } + if(fnt == 1) { + _gui.FontSelect(&FONT_8X14); + } + if(fnt == 2) { + _gui.FontSelect(&FONT_12X20); + } + + _gui.SetForecolor(C_GAINSBORO); + _gui.SetBackcolor(C_BLACK); + _gui.PutString(x,y,str); +} +void main_task::draw_highlight_string(int x,int y,const char*str){ + _gui.FontSelect(&FONT_8X14); + _gui.SetForecolor(C_BLACK); + _gui.SetBackcolor(C_GAINSBORO); + _gui.PutString(x,y,str); +} + +void main_task::draw_char(int x,int y ,char c) { + _gui.FontSelect(&FONT_8X14); + _gui.PutChar(c,x,y,C_GAINSBORO,C_BLACK, true); +} + +void main_task::draw_big_char(int x,int y ,char c) { + _gui.FontSelect(&FONT_12X20); + _gui.PutChar(c,x,y,C_GAINSBORO,C_BLACK, true); +} +void main_task::draw_bar(int x1, int x2,UG_COLOR c) { + uint8_t offsetx = 10; + _gui.FillFrame(x1+offsetx,280,x2+offsetx,285,c); +} + + +#define SCREEN_WIDTH 320 +#define SCREEN_HEIGHT 280 +#define FONT_WIDTH 12 +#define FONT_HEIGHT 20 +#define MAX_CHARS_PER_LINE (SCREEN_WIDTH / FONT_WIDTH) +#define MAX_LINES 2 + +void main_task::draw_wrap_text(const char *text) { + int line_count = 0; + const char *ptr = text; + + + char lines[MAX_LINES][MAX_CHARS_PER_LINE + 1] = {{0}}; + while (*ptr && line_count < MAX_LINES) { + int i = 0; + while (*ptr && i < MAX_CHARS_PER_LINE) { + lines[line_count][i++] = *ptr++; + } + lines[line_count][i] = '\0'; + line_count++; + } + + + int text_height = line_count * FONT_HEIGHT; + int y_offset = (SCREEN_HEIGHT - text_height) / 2; + + + for (int line = 0; line < line_count; line++) { + int char_count = strlen(lines[line]); + int x_offset = (SCREEN_WIDTH - char_count * FONT_WIDTH) / 2; + for (int j = 0; j < char_count; j++) { + draw_big_char(x_offset + j * FONT_WIDTH, y_offset + line * FONT_HEIGHT, lines[line][j]); + } + } +} + +int main_task::enum_files() { + char tmp[16]; + _gui.FontSelect(&FONT_8X14); + _gui.ConsoleSetForecolor(C_GAINSBORO); + _gui.ConsoleSetBackcolor(C_BLACK); + _gui.ConsolePutString("Init FatFS"); + FatFs::FRESULT res = _fs.mount(0); + if(res == FatFs::FR_OK){ + _gui.ConsolePutString("[OK]"); + }else{ + sprintf(tmp,"[NG] code=0x%x",res); + _gui.ConsolePutString(tmp); + return -1; + } + _gui.ConsolePutString("\n"); + _gui.ConsolePutString("File List"); + + num_files = 0; + res = _fs.findfirst(&_dir, &_finfo, "", "*.mp3"); + // Loop over all MP3 files + while (res == FatFs::FR_OK && _finfo.fname[0] && strlen(_finfo.fname) > 4 ) { + printf("%s\n",_finfo.fname); + menu_items[num_files].fname = (char*)malloc(strlen(_finfo.fname) + 1); + strcpy(menu_items[num_files].fname, _finfo.fname); + menu_items[num_files].fsize = _finfo.fsize; + + res = _fs.findnext(&_dir, &_finfo); + num_files++; + if(num_files>=MAX_FILES){ + break; + } + } + if (res != FatFs::FR_OK) { + sprintf(tmp, "[NG] code=0x%x", (int)res); + _gui.ConsolePutString( tmp); + return -1; + } + _fs.closedir(&_dir); + _gui.ConsolePutString("[OK]"); + return num_files; +} + +//In ffconf.h, +// set +// #define FF_MAX_LFN 33 +// #define FF_LFN_BUF 33 +/* +void truncate_string(const char *input, char *output, size_t max_length) { + size_t input_len = strlen(input); + + if (input_len <= max_length) { + strcpy(output, input); + } else { + strncpy(output, input, max_length - 3); + output[max_length - 3] = '\0'; + strcat(output, "..."); + } +} +*/ +#define HEADER_HEIGHT 20 +int main_task::select_mp3() { + char tmp[34]; + uint16_t start_x = 11; + uint16_t start_y= HEADER_HEIGHT+3; + /* header + * ------------------------------------------ + * 3px + * [item] + * 3px + * [item] + * 3px + * [item] + * 3px + */ + + for (int i = _page_index; i < _page_index+ITEMS_PER_PAGE;i++) { + if(i >= num_files) break; + //truncate_string(file_list[_page_index+i],tmp,33); + menu_items[i].x = start_x; + menu_items[i].y = start_y; + + draw_string(menu_items[i].x, menu_items[i].y, menu_items[i].fname); + start_y+=23; + } + + return 0; +} +void main_task::draw_cursor() { + char buf[12]; + //sprintf(buf, "%02d", _sel_index + 1); + //draw_string(0,_lcd.getSizeY()-20,buf); + printf("%d %d %d\n",_last_sel_index,_sel_index,_page_index); + uint16_t x,y; + + if(update_sel){ + x = menu_items[_last_sel_index % ITEMS_PER_PAGE].x; + y = menu_items[_last_sel_index % ITEMS_PER_PAGE].y; + + _gui.FillFrame(10, y - 2, _lcd.getSizeX() - 10, y + 15, C_BLACK); + draw_string(x, y, menu_items[_last_sel_index].fname); + } + x = menu_items[_sel_index % ITEMS_PER_PAGE].x; + y = menu_items[_sel_index % ITEMS_PER_PAGE].y; + + _gui.FillFrame(10,y-2,_lcd.getSizeX()-10,y+15,C_GAINSBORO); + //draw_string(0, (_sel_index % ITEMS_PER_PAGE) * 20+20, "=>"); + draw_highlight_string(11, y, menu_items[_sel_index].fname); + +} + +void main_task::clear_screen() { + _lcd.clearScreen(C_BLACK); +} +void main_task::clear_menu() { + //clear menu section area, not whole screen + _lcd.fillArea(0,20,_lcd.getSizeX()-1,_lcd.getSizeY()-20,C_BLACK); +} +void main_task::boot_menu() { + _page_index = (_sel_index / ITEMS_PER_PAGE) * ITEMS_PER_PAGE; + if(_page_index!= _last_page_index){ + clear_menu(); + _last_page_index = _page_index; + update_required = 1; + update_sel = 0; + } + + if(update_required) { + clear_menu(); + draw_header("Playlist"); + select_mp3(); + draw_cursor(); + draw_footer(1); + update_required = 0; + last_play_pos = 0; + play_pos = 0; + + } + + if(update_sel){ + draw_cursor(); + update_sel = 0; + } +} + +void main_task::menu_up() { + _last_sel_index = _sel_index; + if (_sel_index == 0) { + _sel_index = num_files - 1; + } else { + _sel_index--; + } + update_sel = 1; +} +void main_task::menu_down() { + _last_sel_index = _sel_index; + if (_sel_index == num_files - 1) { + _sel_index = 0; + } else { + _sel_index++; + } + update_sel = 1; +} + +void main_task::menu_start(sd_reader_task&sd_reader,mp3_decoder_task &decoder,pcm_pwm_rp2040_drv &pcm_drv) { + FatFs::FRESULT res = FatFs::FR_OK; + printf("start play: %s\n",menu_items[_sel_index].fname); + res = _fs.open(&_file, menu_items[_sel_index].fname, FA_OPEN_EXISTING | FA_READ); + assert(res == FatFs::FR_OK); + sd_reader.start(&_fs, &_file); + decoder.reset(); + decoder.start(); + update_required = 1; + playing =1; + last_play_pos = 0; + play_pos = 0; + pause_flag = 0; + +} + +//play through the list start from sel_index; +void main_task::menu_start_all(sd_reader_task &sd_reader, mp3_decoder_task &decoder, pcm_pwm_rp2040_drv &pcm_drv) { + FatFs::FRESULT res = FatFs::FR_OK; + printf("start play all: %d\n",_sel_index); + res = _fs.open(&_file, menu_items[_sel_index].fname, FA_OPEN_EXISTING | FA_READ); + assert(res == FatFs::FR_OK); + sd_reader.start(&_fs, &_file); + decoder.reset(); + decoder.start(); + update_required = 1; + playing =1; + last_play_pos = 0; + play_pos = 0; + pause_flag = 0; + play_flag = 1; +} + +void main_task::draw_logo(){ + uint16_t start_x,start_y; + + start_x = (_lcd.getSizeX() - logo_width)/2; + start_y = (_lcd.getSizeY() - logo_height)/2; + + unsigned char *data = (unsigned char *)header_data; + for (unsigned int y = 0; y < logo_height; ++y) { + for (unsigned int x = 0; x < logo_width; ++x) { + unsigned char pixel[3]; + HEADER_PIXEL(data, pixel); + + uint8_t r = pixel[0]; + uint8_t g = pixel[1]; + uint8_t b = pixel[2]; + + uint32_t color = (r << 16) | (g << 8) | b; // RGB888 + _lcd.drawPixel(start_x+x,start_y+y, color); + } + } +} +//header Height 20 +void main_task::draw_header(char*title) { + _gui.FontSelect(&FONT_8X14); + _gui.SetForecolor(C_GAINSBORO); + _gui.SetBackcolor(C_BLACK); + _gui.PutString(11,(20-14)/2," "); + _gui.PutString(11,(20-14)/2,title); + _lcd.drawHLine(10,19,_lcd.getSizeX()-10,C_LIGHT_GRAY); +} + +void main_task::draw_footer_esc() { + uint16_t start_x,start_y; + + start_x = 261; + start_y = _lcd.getSizeY() - esc_height-5; + + unsigned char *data = (unsigned char *)esc_header_data; + for (unsigned int y = 0; y < esc_height; ++y) { + for (unsigned int x = 0; x < esc_width; ++x) { + unsigned char pixel[3]; + ESC_PIXEL(data, pixel); + + uint8_t r = pixel[0]; + uint8_t g = pixel[1]; + uint8_t b = pixel[2]; + + uint32_t color = (r << 16) | (g << 8) | b; // RGB888 + _lcd.drawPixel(start_x+x,start_y+y, color); + } + } + draw_string(285,start_y+3,"Stop",0); +} + +void main_task::draw_footer_enter() { + uint16_t start_x,start_y; + + start_x = 260; + start_y = _lcd.getSizeY() - enter_height-5; + + unsigned char *data = (unsigned char *)enter_header_data; + for (unsigned int y = 0; y < enter_height; ++y) { + for (unsigned int x = 0; x < enter_width; ++x) { + unsigned char pixel[3]; + ENTER_PIXEL(data, pixel); + + uint8_t r = pixel[0]; + uint8_t g = pixel[1]; + uint8_t b = pixel[2]; + + uint32_t color = (r << 16) | (g << 8) | b; // RGB888 + _lcd.drawPixel(start_x+x,start_y+y, color); + } + } + draw_string(285,start_y+3,"Play",0); +} + +void main_task::draw_footer_nav() { + + uint16_t start_x,start_y; + + start_x = 10; + start_y = _lcd.getSizeY() - nav_height-6; + + unsigned char *data = (unsigned char *)nav_header_data; + for (unsigned int y = 0; y < nav_height; ++y) { + for (unsigned int x = 0; x < nav_width; ++x) { + unsigned char pixel[3]; + NAV_PIXEL(data, pixel); + + uint8_t r = pixel[0]; + uint8_t g = pixel[1]; + uint8_t b = pixel[2]; + + uint32_t color = (r << 16) | (g << 8) | b; // RGB888 + _lcd.drawPixel(start_x+x,start_y+y, color); + } + } + draw_string(23,start_y+3,"Nav.",0); + +} + +void main_task::draw_footer_pause() { + uint16_t start_x,start_y; + + start_x = 210; + start_y = _lcd.getSizeY() - icon_p_height-5; + + unsigned char *data = (unsigned char *)icon_p_header_data; + for (unsigned int y = 0; y < icon_p_height; ++y) { + for (unsigned int x = 0; x < icon_p_width; ++x) { + unsigned char pixel[3]; + ICON_P_PIXEL(data, pixel); + + uint8_t r = pixel[0]; + uint8_t g = pixel[1]; + uint8_t b = pixel[2]; + + uint32_t color = (r << 16) | (g << 8) | b; // RGB888 + _lcd.drawPixel(start_x+x,start_y+y, color); + } + } + draw_string(225,start_y+3,"Pause",0); +} + +void main_task::draw_footer_pagenumber() { + // page1/4 + + char tmp[12]; + int total_page = (num_files / ITEMS_PER_PAGE)+1; + int cur_page = (_sel_index / ITEMS_PER_PAGE)+1; + if(total_page > 1) { + sprintf(tmp, "%d/%d", cur_page, total_page); + draw_string(150, _lcd.getSizeY() - nav_height-2, tmp, 0); + } +} + +void main_task::draw_footer(uint8_t stat) { + //stat 1 == menu_list ==> nav icon, Enter icon + //stat 2 == playing music ==> only Esc icon + _gui.FillFrame(0,_lcd.getSizeY()-20,_lcd.getSizeX()-1,_lcd.getSizeY()-1,C_BLACK); + if(stat == 1) { + draw_footer_nav(); + draw_footer_enter(); + draw_footer_pagenumber(); + } + if(stat == 2) { + draw_footer_pause(); + draw_footer_esc(); + } + + _lcd.drawHLine(10,_lcd.getSizeY()-20,_lcd.getSizeX()-10,C_LIGHT_GRAY); +} + +void main_task::draw_playing() { + + char * song_name = menu_items[_sel_index].fname; + draw_wrap_text(song_name); + +} + + +void main_task::run() { + char tmp[34]; + i2c_kbd _kbd; + + pcm_pwm_rp2040_drv pcm_drv(AUDIO_LEFT, AUDIO_RIGHT); + sd_reader_task sd_reader; + mp3_decoder_task decoder(pcm_drv, sd_reader); + + uint8_t keycheck = 0; + uint8_t keyread = 0; + uint8_t key_stat = 0;//press,release, or hold + int kbd_ret = 0; + int c; + static int ctrlheld = 0; + ///const char spinner[] = {'/', '-', '|', '\\'}; + uint8_t spin_i = 0; + uint8_t spin_timer = 0; + + int play_pos_diff = 0; + + _lcd.clearScreen(C_BLACK); + draw_logo(); + task::sleep_ms(1000); + clear_screen(); + + num_files = enum_files(); + if (num_files <= 0) { + printf("num_files <=0\n"); + return; + } + clear_screen(); + draw_header("Playlist"); + while (true){ + if(playing){ + if (sd_reader.isAlive() && decoder.isAlive()) { + //printf("playing now\n"); + + if(update_required) { + clear_menu(); + //truncate_string(fname_list[_sel_index],tmp,33); + if(play_flag == 1) { + draw_header("Now Playing All"); + }else{ + draw_header("Now Playing"); + } + draw_playing(); + draw_footer(2); + update_required = 0; + } + if(spin_timer >= 100) { + //(bitrate*8/)filesize + //draw bar use last_play_pos + //draw_bar(0,last_play_pos,C_BLACK); + play_pos = decoder.get_position(menu_items[_sel_index].fsize,_lcd.getSizeY()-20); + ///printf("play_pos %d\n",play_pos); + //draw bar use play_pos + play_pos_diff = play_pos - last_play_pos; + if(play_pos_diff < 0) {// VBR + draw_bar(play_pos,last_play_pos,C_BLACK); + }else{ //>= 0 + //draw_bar(last_play_pos,play_pos,C_BLACK); + draw_bar(play_pos,play_pos+play_pos_diff,C_GAINSBORO); + } + uint32_t seconds = decoder.get_total_seconds(); + + int minutes = seconds / 60; + int remaining_seconds = seconds % 60; + + // "MM:SS" + snprintf(tmp, 6, "%02d:%02d", minutes, remaining_seconds); + draw_string(10,288,tmp,0); + ///draw_char(20, 300, spinner[spin_i % 4]); + spin_i++; + spin_timer = 0; + last_play_pos = play_pos; + } + spin_timer++; + }else{ + printf("not playing...auto exit\n"); + _fs.close(&_file); + playing =0; + update_required =1; + if(play_flag == 1) { + menu_down(); + menu_start_all(sd_reader,decoder,pcm_drv); + } + } + }else{ + boot_menu(); + } + + if(keycheck == 0) { + if(keyread == 0) { + kbd_ret = _kbd.write_i2c_kbd(); + keyread = 1; + }else{ + kbd_ret = _kbd.read_i2c_kbd(); + keyread = 0; + } + keycheck = 16; + } + + if(kbd_ret){ + if (kbd_ret == 0xA503)ctrlheld = 0; + else if (kbd_ret == 0xA502) { + ctrlheld = 1; + } else if ((kbd_ret & 0xff) == 1) {//pressed + key_stat = 1; + } else if ((kbd_ret & 0xff) == 3) { + key_stat = 3; + } + c = kbd_ret >> 8; + int realc = -1; + switch (c) { + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + realc = -1;//skip shift alt ctrl keys + break; + default: + realc = c; + break; + } + c = realc; + if (c >= 'a' && c <= 'z' && ctrlheld)c = c - 'a' + 1; + switch (c) { + case 0xb5://UP + if(key_stat == 1 && !playing) { + menu_up(); + } + break; + case 0xb6://DOWN + if(key_stat == 1 && !playing) { + menu_down(); + } + break; + case 0xb4://LEFT + break; + case 0xb7://RIGHT + break; + case 0x0A://ENTER + if(key_stat == 1 && !playing) { + menu_start(sd_reader, decoder, pcm_drv); + } + break; + case 'a': + if(key_stat == 1 && !playing) { + menu_start_all(sd_reader,decoder,pcm_drv); + } + break; + case 0xB1://ESC + if(key_stat == 1 && playing) { + playing = 0; + if(decoder.getState()==task::SUSPENDED){//maybe is suspended + decoder.resume(); + } + sd_reader.force_eof(); + printf("stop playing\n"); + pause_flag = 0; + update_required = 1; + play_flag = 0; + } + break; + case 'p': + if(key_stat == 1 && playing) { + if(pause_flag == 0) { + pause_flag = 1; + decoder.suspend(); + draw_header("Paused"); + }else{ + pause_flag = 0; + decoder.resume(); + draw_header("Now Playing"); + } + printf("pause playing mp3\n"); + } + break; + default: + break; + } + kbd_ret = 0; + } + + task::sleep_ms(1); + if(keycheck) keycheck--; + } + +} diff --git a/Code/MP3Player/main_task.h b/Code/MP3Player/main_task.h new file mode 100644 index 0000000..640b48c --- /dev/null +++ b/Code/MP3Player/main_task.h @@ -0,0 +1,118 @@ +/////////////////////////////////////////////////// +// MP3 player for RPi Pico and GPIO audio output // +/////////////////////////////////////////////////// +// +// This program will read MP3 files from a SD card and +// play them on the RP2040 launchpad. The MP3 decoder is +// implemented in software, so only some kind of PCM +// output is necessary, which is provided by a driver +// which outputs the PCM data as PWM GPIO signals. +// +// The main task will loop over all *.mp3 files and play +// them. The next-button can be used to skip to the next +// song. The paart-button is used to select the partition +// of the SD card during startup. +// +// For every new song, the SD reader and MP3 decoder +// tasks are started again. +// +// Make sure you connect the SD-card PCB to the correct +// SPI interface (see pin assignments in config.h), and +// provide some MP3 files without too high quality +// (128 kbps works fine!) + +#ifndef MAIN_TASK_H +#define MAIN_TASK_H + +#include "config.h" +#include "gpio_rp2040.h" +#include "spi_rp2040.h" +#include "sd_spi_drv.h" +#include "pcm_pwm_rp2040_drv.h" +#include "task.h" +#include "sd_reader_task.h" +#include "mp3_decoder_task.h" +#include "ff.h" +#include +#include "uGUI.h" +#include "uGUI_colors.h" +#include "font_6x8.h" +#include "font_8x14.h" +#include "font_12x20.h" +#include "ili9488_drv.h" +#include "i2ckbd.h" + +typedef struct menu_item { + uint16_t x; + uint16_t y; + char *fname; ///pointer to a malloced char buf + int fsize; +}MENU_ITEM; + + +class main_task : public task +{ +public: + main_task(); + void run() override; + void draw_string(int,int,const char*,uint8_t); + void draw_highlight_string(int,int,const char*); + void draw_char(int x,int y ,char c); + void draw_big_char(int x,int y ,char c); + void draw_bar(int x1,int x2,UG_COLOR c); + int enum_files(); + int select_mp3(); + void draw_cursor(); + void boot_menu(); + void menu_up(); + void menu_down(); + void menu_start(sd_reader_task&sd_reader,mp3_decoder_task &decoder,pcm_pwm_rp2040_drv &pcm_drv); + void menu_start_all(sd_reader_task&sd_reader,mp3_decoder_task &decoder,pcm_pwm_rp2040_drv &pcm_drv); + void draw_logo(); + void clear_screen(); + void clear_menu(); + void draw_header(char*); + void draw_footer(uint8_t); + void draw_footer_enter(); + void draw_footer_esc(); + void draw_footer_nav(); + void draw_footer_pause(); + void draw_footer_pagenumber(); + void draw_playing(); + void draw_wrap_text(const char *); +private: + gpio_rp2040_pin _cs; // CS Line of SD card SPI interface + spi_rp2040 _spi; // SPI interface used for the SD card reader + sd_spi_drv _sd; // SD card low level driver + + gpio_rp2040_pin _next; // Next button + gpio_rp2040_pin _part; // Partition button + gpio_rp2040_pin _ps; // Pico Power save pin + + FatFs _fs; + FatFs::DIR _dir{}; + FatFs::FILEINFO _finfo{}; + FatFs::FILE _file{}; // MP3 file on SD + + gpio_rp2040_pin _lcd_rst; + gpio_rp2040_pin _lcd_dc; + gpio_rp2040_pin _lcd_cs; + spi_rp2040 _lcd_spi; + ili9488_drv _lcd; + uGUI _gui; + int _sel_index; + int _page_index; + int _last_page_index; + int _last_sel_index; + uint8_t update_sel; + uint8_t update_required; + uint8_t playing; + uint16_t last_play_pos; + uint16_t play_pos; + MENU_ITEM menu_items[MAX_FILES]{}; + uint16_t num_files; + uint8_t pause_flag ; + uint8_t play_flag;//default:0, play_all = 1,play_shuffle = 2 +}; + +#endif // MAIN_TASK_H diff --git a/Code/MP3Player/mp3_decoder_task.cpp b/Code/MP3Player/mp3_decoder_task.cpp new file mode 100644 index 0000000..9d3bc14 --- /dev/null +++ b/Code/MP3Player/mp3_decoder_task.cpp @@ -0,0 +1,208 @@ +#include "mp3_decoder_task.h" +#include +#include + +mp3_decoder_task::mp3_decoder_task(pcm_audio_interface & pcm_if, sd_reader_task & sd) : + task("MP3 decoder", MP3_DECODER_STACKSIZE), + _pcm_if(pcm_if), _sd_reader(sd), _led(LED_PIN), _pcm_rate(0),_bitrate(0),_total_time(0) +{ + mad_timer_reset(&_timer); + _led.gpioMode(GPIO::OUTPUT); +} +void mp3_decoder_task::reset() { + _total_time = 0; + _bitrate = 0; + mad_timer_reset(&_timer); +} +void mp3_decoder_task::run() { + + mad_decoder_init(&_decoder, // the decoder object + this, // parameter for callback functions + input, // input callback + header, // header callback + nullptr, // filter callback + output, // output callback + error, // error callback + nullptr); // message callback + + + mad_decoder_run(&_decoder, MAD_DECODER_MODE_SYNC); + mad_decoder_finish(&_decoder); +} + +/////////////////////////// +// libmad input callback // +/////////////////////////// +enum mad_flow mp3_decoder_task::input(void *data, struct mad_stream *stream) { + + // Cast user defined data. Here we get a pointer + // to the decoder task! + auto * _this = (mp3_decoder_task *)data; + sd_reader_task & sd = _this->_sd_reader; + + // The following code is inspired by this article: + // https://stackoverflow.com/questions/39803572/ + int keep; // Number of bytes to keep from the previous buffer. + int len; // Length of the new buffer. + int eof; // Whether this is the last buffer that we can provide. + + // Figure out how much data we need to move from the end of + // the previous buffer into the start of the new buffer. + if (stream->error != MAD_ERROR_BUFLEN) { + // All data has been consumed, or this is the first call. + keep = 0; + } else if (stream->next_frame != nullptr) { + // The previous buffer was consumed partially. + // Move the unconsumed portion into the new buffer. + keep = stream->bufend - stream->next_frame; + } else if ((stream->bufend - stream->buffer) < MP3_BUF_SIZE) { + // No data has been consumed at all, but our read buffer + // isn't full yet, so let's just read more data first. + keep = stream->bufend - stream->buffer; + } else { + // No data has been consumed at all, and our read buffer is already full. + // Shift the buffer to make room for more data, in such a way that any + // possible frame position in the file is completely in the buffer at least + // once. + keep = MP3_BUF_SIZE - MP3_FRAME_SIZE; + } + + // Shift the end of the previous buffer to the start of the + // new buffer if we want to keep any bytes. + if (keep) { + memmove(_this->_mp3_buf, stream->bufend - keep, keep); + } + + // Append new data to the buffer. + uint16_t br; + FatFs::FRESULT res = sd.read_data(_this->_mp3_buf + keep, MP3_BUF_SIZE - keep, &br); + _this->_led.gpioToggle(); + + if (res) { + // Read error. + return MAD_FLOW_STOP; + } else if (sd.eof()) { + // End of file. Append MAD_BUFFER_GUARD zero bytes to make + // sure that the last frame is properly decoded. + if (keep + MAD_BUFFER_GUARD <= MP3_BUF_SIZE) { + // Append all guard bytes and stop decoding after this buffer. + memset(_this->_mp3_buf + keep, 0, MAD_BUFFER_GUARD); + len = keep + MAD_BUFFER_GUARD; + eof = 1; + } else { + // The guard bytes don't all fit in our buffer, so we need to continue + // decoding and write all fo the guard bytes in the next call to input(). + memset(_this->_mp3_buf + keep, 0, MP3_BUF_SIZE - keep); + len = MP3_BUF_SIZE; + eof = 0; + } + } else { + // New buffer length is amount of bytes that we kept from the + // previous buffer plus the bytes that we read just now. + len = keep + br; + eof = 0; + } + + // Pass the new buffer information to libmad + mad_stream_buffer(stream, _this->_mp3_buf, len); + return eof ? MAD_FLOW_STOP : MAD_FLOW_CONTINUE; +} + +//////////////////////////// +// libmad header callback // +//////////////////////////// +enum mad_flow mp3_decoder_task::header(void *data, struct mad_header const * header) { + auto * _this = (mp3_decoder_task *)data; + if (header->samplerate != _this->_pcm_rate) { + _this->_pcm_rate = header->samplerate; + _this->_pcm_if.setPcmRate( header->samplerate ); + } + _this->_bitrate = header->bitrate; + + mad_timer_add(&_this->_timer, header->duration); + _this->_total_time = mad_timer_count(_this->_timer, MAD_UNITS_MILLISECONDS); + //printf("bitrate: %ld ,%ld\n,",_this->_bitrate, _this->_total_time); + + return MAD_FLOW_CONTINUE; +} + +//////////////////////////// +// libmad output callback // +//////////////////////////// +enum mad_flow mp3_decoder_task::output(void *data, mad_header const *header, mad_pcm *pcm) +{ + (void)(header); + auto * _this = (mp3_decoder_task *)data; + // Wait until the PCM result can be written + while (_this->_pcm_if.pcmFifoAvailablePut() < pcm->length) { + task::sleep_ms(5); + } + // Copy PCM samples to PCM fifo + mad_fixed_t const * left_ch = pcm->samples[MAD_PCM_CHANNEL_STEREO_LEFT]; + mad_fixed_t const * right_ch = pcm->samples[MAD_PCM_CHANNEL_STEREO_RIGHT]; + pcm_value_t pcm_value; + for (int i=0; i < pcm->length; ++i) { + pcm_value.left = scale(left_ch[i]); + pcm_value.right = scale(right_ch[i]); + _this->_pcm_if.pcmFifoPut(pcm_value); + } + return MAD_FLOW_CONTINUE; +} + +/////////////////////////// +// libmad error callback // +/////////////////////////// +enum mad_flow mp3_decoder_task::error(void *data,mad_stream *stream, mad_frame *frame) +{ + (void)(data); + (void)(frame); + printf("Decoding error 0x%04x (%s) at byte offset %lu\n", + stream->error, mad_stream_errorstr(stream), + stream->this_frame - stream->buffer); + // return MAD_FLOW_BREAK to stop decoding + return MAD_FLOW_CONTINUE; +} + +int16_t mp3_decoder_task::scale(mad_fixed_t sample) +{ + // libmad does a good job calculating the correct + // values in the range between -MAD_F_ONE and MAD_F_ONE. + // Therefore rounding and clipping is normally not + // necessary! + // round + sample += (1L << (MAD_F_FRACBITS - 16)); + // clip + if (sample >= MAD_F_ONE) + sample = MAD_F_ONE - 1; + else if (sample < -MAD_F_ONE) + sample = -MAD_F_ONE; + // Convert to a standard 16 bit PCM value + // (signed) in the range of -32768...32767 + sample >>= (MAD_F_FRACBITS + 1 - 16); + return (int16_t)sample; +} + +uint16_t mp3_decoder_task::get_position(unsigned long fsize, int max_pos) { + + unsigned long part1 = fsize / _bitrate; + unsigned long part2 = fsize % _bitrate; + unsigned long t_tm = part1 * 8 + (part2 * 8) / _bitrate; + + unsigned long numerator = _total_time; + unsigned long input = max_pos; + + unsigned long intermediate1 = numerator / 1000; + unsigned long intermediate2 = numerator % 1000; + + unsigned long cur_pos = ((intermediate1 * input) / t_tm) + (((intermediate2 * input) / 1000) / t_tm); + + //printf("bitrate %d,fsize %d,t_time %lu ,cur_pos %lu\n",_bitrate,fsize,t_tm,cur_pos); + + return (uint16_t)cur_pos; +} + +uint32_t mp3_decoder_task::get_total_seconds() { + return _total_time/1000; +} + + diff --git a/Code/MP3Player/mp3_decoder_task.h b/Code/MP3Player/mp3_decoder_task.h new file mode 100644 index 0000000..316cae1 --- /dev/null +++ b/Code/MP3Player/mp3_decoder_task.h @@ -0,0 +1,39 @@ +#ifndef MP3_DECODER_TASK_H +#define MP3_DECODER_TASK_H + +#include +#include "config.h" +#include "pcm_audio_interface.h" +#include "sd_reader_task.h" +#include "gpio_rp2040.h" +#include "task.h" +#include "mad.h" + +class mp3_decoder_task : public task +{ +public: + mp3_decoder_task(pcm_audio_interface & pcm_if, sd_reader_task & sd); + + void run() override; + void reset(); + uint16_t get_position(unsigned long fsize,int max_pos); + uint32_t get_total_seconds(); +private: + static enum mad_flow input (void *data, struct mad_stream *stream); + static enum mad_flow header(void *data, struct mad_header const *); + static enum mad_flow output(void *data, mad_header const *header, mad_pcm *pcm); + static enum mad_flow error (void *data, mad_stream *stream, mad_frame *frame); + static int16_t scale (mad_fixed_t sample); + + pcm_audio_interface & _pcm_if; + sd_reader_task & _sd_reader; + mad_decoder _decoder{}; + gpio_rp2040_pin _led; + uint8_t _mp3_buf[MP3_BUF_SIZE]{}; + uint32_t _pcm_rate; + mad_timer_t _timer; + unsigned long _bitrate; + unsigned long _total_time; +}; + +#endif // MP3_DECODER_TASK_H diff --git a/Code/MP3Player/pico_mp3_player.cpp b/Code/MP3Player/pico_mp3_player.cpp new file mode 100644 index 0000000..3b73a75 --- /dev/null +++ b/Code/MP3Player/pico_mp3_player.cpp @@ -0,0 +1,25 @@ +///////////////////////////// +// MP3 player for RPi Pico // +///////////////////////////// +// +// main() only starts the multitasking and +// runs the main task (see main_task.h/cpp) +// +#include +#include "main_task.h" +#include "uart_rp2040.h" +#include "posix_io.h" +int main() +{ + uart_rp2040 uart; + posix_io::inst.register_stdin ( uart ); + posix_io::inst.register_stdout( uart ); + posix_io::inst.register_stderr( uart ); + + task::sleep_ms(2000);//really neccessary form uart debug + // Start Main task as privileged task + main_task Main; + Main.start(MAIN_PRIORITY, true); + // Start the multitasking + task::start_scheduler(); +} diff --git a/Code/MP3Player/sd_reader_task.cpp b/Code/MP3Player/sd_reader_task.cpp new file mode 100644 index 0000000..e8f39ee --- /dev/null +++ b/Code/MP3Player/sd_reader_task.cpp @@ -0,0 +1,52 @@ +#include "config.h" +#include "sd_reader_task.h" + +sd_reader_task::sd_reader_task() : task("SD reader", SD_READER_STACKSIZE), + _fs(nullptr), + _file(nullptr), + _req_buff(nullptr), + _req_btr(0), + _req_br(nullptr), + _req_result(FatFs::FR_OK), + _eof(false), + _force_eof(false), + _execute(false) +{ } + +void sd_reader_task::start(FatFs * fs, FatFs::FILE * file) { + _fs = fs; + _file = file; + _eof = false; + _force_eof = false; + _execute = false; + task::start(); +} + +FatFs::FRESULT sd_reader_task::read_data(uint8_t* buff, uint16_t btr, uint16_t* br) { + // Copy request parameters + _req_buff = buff; + _req_btr = btr; + _req_br = br; + // Trigger task execution + _execute_mutex.lock(); + _execute = true; + _execute_mutex.unlock(); + _execute_cv.notify_one(); + // Wait for execution to complete + _caller_cv.wait(_caller_mutex, [this]() {return !_execute;}); + // Return the result of the execution + return _req_result; +} + +void sd_reader_task::run() { + do { + // Wait for a new request + _execute_cv.wait(_execute_mutex, [this]() {return _execute;}); + // Read in the requested bytes + _req_result = _fs->read(_file, _req_buff, _req_btr, _req_br); + _eof = f_eof (_file); + // Notify end of execution + _execute = false; + _caller_cv.notify_one(); + } while (!_eof && !_force_eof); +} diff --git a/Code/MP3Player/sd_reader_task.h b/Code/MP3Player/sd_reader_task.h new file mode 100644 index 0000000..1bffb93 --- /dev/null +++ b/Code/MP3Player/sd_reader_task.h @@ -0,0 +1,50 @@ +#ifndef SD_READER_TASK_H +#define SD_READER_TASK_H + +#include +#include "task.h" +#include "lock_base_rp2040.h" +#include "mutex.h" +#include "condition_variable.h" +#include "ff.h" + +class sd_reader_task : public task +{ +public: + sd_reader_task(); + + void run() override; + + // Start the SD reading with a new file + void start(FatFs * fs, FatFs::FILE * file); + // Read some data from the file + FatFs::FRESULT read_data(uint8_t* buff, uint16_t btr, uint16_t* br); + // EOF handling + inline bool eof() const { + return _eof || _force_eof; + } + inline void force_eof() { + _force_eof = true; + } + +private: + + FatFs * _fs; // FatFs object + FatFs::FILE * _file; // MP3 file on SD + + uint8_t * _req_buff; + uint16_t _req_btr; + uint16_t * _req_br; + FatFs::FRESULT _req_result; + bool _eof; + bool _force_eof; + + volatile bool _execute; + mutex _execute_mutex; + condition_variable _execute_cv; + + mutex _caller_mutex; + condition_variable _caller_cv; +}; + +#endif // SD_READER_TASK_H