add mp3 player code
This commit is contained in:
		
							parent
							
								
									95a528d5b7
								
							
						
					
					
						commit
						4c8b136686
					
				|  | @ -0,0 +1,4 @@ | |||
| .idea | ||||
| cmake-* | ||||
| build | ||||
| 
 | ||||
|  | @ -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) | ||||
|  | @ -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   | ||||
| 
 | ||||
| 
 | ||||
|  | @ -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}) | ||||
| 
 | ||||
|  | @ -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
 | ||||
|  | @ -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; | ||||
| } | ||||
|  | @ -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 | ||||
|  | @ -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 | ||||
| }; | ||||
|  | @ -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 | ||||
| }; | ||||
|  | @ -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 | ||||
| }; | ||||
|  | @ -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 | ||||
| }; | ||||
|  | @ -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 | ||||
| 	}; | ||||
|  | @ -0,0 +1,673 @@ | |||
| #include <cstdio> | ||||
| #include <cstring> | ||||
| #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--; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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 <cassert> | ||||
| #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
 | ||||
|  | @ -0,0 +1,208 @@ | |||
| #include "mp3_decoder_task.h" | ||||
| #include <memory.h> | ||||
| #include <cstdio> | ||||
| 
 | ||||
| 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; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -0,0 +1,39 @@ | |||
| #ifndef MP3_DECODER_TASK_H | ||||
| #define MP3_DECODER_TASK_H | ||||
| 
 | ||||
| #include <cstdint> | ||||
| #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
 | ||||
|  | @ -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 <cstdio> | ||||
| #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(); | ||||
| } | ||||
|  | @ -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); | ||||
| } | ||||
|  | @ -0,0 +1,50 @@ | |||
| #ifndef SD_READER_TASK_H | ||||
| #define SD_READER_TASK_H | ||||
| 
 | ||||
| #include <cstdint> | ||||
| #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<lock_base_rp2040>               _execute_mutex; | ||||
|     condition_variable<lock_base_rp2040>  _execute_cv; | ||||
| 
 | ||||
|     mutex<lock_base_rp2040>               _caller_mutex; | ||||
|     condition_variable<lock_base_rp2040>  _caller_cv; | ||||
| }; | ||||
| 
 | ||||
| #endif // SD_READER_TASK_H
 | ||||
		Loading…
	
		Reference in New Issue