16bit resolution PWM wave player with SD card, super lite version.

Dependencies:   mbed DirectSPI FastPWM

Supported boards (confirmed):
Nucleo-F030R8
Nucleo-L152RE
Nucleo-F401RE
Nucleo-F411RE

Only compilation is OK (unchecked, but may work):
Nucleo-L073RZ
Nucleo-F334R8
Nucleo-F303RE
Nucleo-F429ZI
Nucleo-F446RE
Nucleo-F446ZE
Nucleo-L476RG

Supported SD card:
SDSC/SDHC card,
FAT16 and FAT32.
(1) At first, format SD card using SD Card Formatter
https://www.sdcard.org/downloads/formatter_4/index.html
(2) Copy PCM wav files to the SD card.
Supported file:
PCM wave file that have file extension ".wav" on root directory.
16bit/8bit, fs(sampling rate)=32kHz,44.1kHz,48kHz.
Stereo/Mono.

Hardware setting:
Refer to the file port_setting.txt
PWM output port:
Left upper(Hi) PWM 8bit out: PB_5 (TM3_CH2)
Right upper(Hi) PWM 8bit out: PB4 (TM3_CH1)
Left lower(Low) PWM 8bit out: PC_9 (TM3_CH4)
Right lower(Low) PWM 8bit out: PC_8 (TM3_CH3)
http://mpu.up.seesaa.net/image/16bit-wave-player-output-schema.png
USER_BUTTON: PC_13(default button)
Next song: One click in Play mode.
Pause : Push long time .
Play : One click from Pause.

Revision:
0:9a5c2fb0d96f
Child:
5:4aa4cc29d5ef
diff -r 000000000000 -r 9a5c2fb0d96f wave_player_main.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wave_player_main.cpp	Tue Jan 15 12:45:24 2019 +0000
@@ -0,0 +1,351 @@
+#include "sys.h"
+#include "sd_card.h"
+#include "fat_lib.h"
+#include "pwm_lib.h"
+#include "timers.h"
+
+#define CH_STEREO          2 // 2:STEREO, 1:MONO
+#define wREAD_COUNT        512
+#define bHEADER_COUNT      44
+#define send_ff()          spi_read()
+#define send_ff16()        spi_read16()
+#define CUT_LAST_TAG_NOISE (30*1000)
+#if (HAVE_LED_INDICATOR==1) || (HAVE_LED_INDICATOR_2==1)
+    DigitalOut led(IND_LED);
+#endif
+
+/******************
+ varialble definitions
+*******************/
+#define LED_PERIOD_PLAYNG    75  //[ * 10 msec]
+#define LED_PERIOD_PAUSING   10  //[ * 10 msec]
+dword ldwSongFileSectors;
+dword ldwSample_freq;
+word  lwReadCount;
+byte  lbSample_bits = 16;
+byte  lbCh_mode     = CH_STEREO;
+byte  bVolumeUpFactor;
+sbit  fPlaying;
+sbit  lfEndOneSong  = true ; //to start music automatically
+
+DigitalIn btn_bit_now(USER_BUTTON,PullUp);
+
+#if TEST_PORT_ENABLE
+    DigitalOut test_port(TEST_PORT);
+#endif
+
+/**********************
+  Period Timer Interrupt
+ **********************/
+/* HAL callback for PWM Timer Intrrupt * */
+void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
+{
+    if( htim->Instance == TIM_PWM ) {
+        #if TEST_PORT_ENABLE
+            test_port =1;
+        #endif
+        fPlaying = true;
+        #if PWM16BIT
+            word wL_low,wL_hi,wR_low,wR_hi;
+            dword dw;
+            if( lbSample_bits == 16 ) {
+                dw = ((word)(send_ff() + (send_ff()<<8) + 0x8000)) << bVolumeUpFactor;
+                wL_low = wR_low = (dw & 0xff); // get lower 8bit
+                wL_hi  = wR_hi = (dw >>8);     // get upper 10bit
+                lwReadCount -= 2;
+                if( lbCh_mode == CH_STEREO ) {
+                    dw = ((word)(send_ff() + (send_ff()<<8) + 0x8000)) << bVolumeUpFactor;
+                    wR_low = (dw & 0xff);
+                    wR_hi  = (dw >> 8);
+                    lwReadCount -= 2;
+                }
+            }
+            #if DATA_8BIT_SUPPORT
+                else {                                            /* 8bit data */
+                    wL_hi  = wR_hi = send_ff() << bVolumeUpFactor; // volume up to 10bit (x4) from 8bit
+                    wL_low = 0;
+                    wR_low = 0;
+                    lwReadCount -= 1;
+                    if( lbCh_mode == CH_STEREO ) {                /* for stereo */
+                        wR_hi  = send_ff() << 2;
+                        wR_low = 0;
+                        lwReadCount -= 1;
+                    }
+                }
+            #endif
+            //# change  pwm duties
+            pwm_dutyL_hi(  wL_hi ) ;
+            pwm_dutyL_low( wL_low ) ;
+            pwm_dutyR_hi(  wR_hi ) ;
+            pwm_dutyR_low( wR_low ) ;
+
+        #else // not PWM16BIT
+            word wL =0,wR = 0;
+            if( lbSample_bits == 16 ) {
+                wL = wR = ((word)(send_ff() + (send_ff()<<8) + 0x8000))>>gPcmShifNum;
+                lwReadCount -= 2;
+                if( lbCh_mode == CH_STEREO ) {
+                    wR = (word)(send_ff() + (send_ff()<<8) + 0x8000)>>gPcmShifNum;
+                    lwReadCount -= 2;
+                }
+            }
+            #if DATA_8BIT_SUPPORT
+                else {                                            /* 8bit data */
+                    wL = wR = send_ff() << bVolumeUpFactor; // volume up to 10bit (x4) from 8bit
+                    lwReadCount -= 1;
+                    if( lbCh_mode == CH_STEREO ) {                /* for stereo */
+                        wR = send_ff() << bVolumeUpFactor;
+                        lwReadCount -= 1;
+                    }
+                }
+            #endif
+            //# change  pwm duties
+            pwm_dutyL_hi( wL ) ;
+            pwm_dutyR_hi( wR ) ;
+        #endif
+
+        //; check 512byte sector boundery
+        if( lwReadCount == 0){
+            //; end 1 sector
+            send_ff() ;// dummy read 1st. discard CRC
+            send_ff() ;// 2nd.
+            while( send_ff() != 0xFE) {}
+            lwReadCount = wREAD_COUNT;
+            ldwSongFileSectors -= 1;
+        }
+        #if TEST_PORT_ENABLE
+            test_port =0;
+        #endif
+    }
+}
+
+/******************
+ wave_player_main
+*******************/
+void wave_player_main(){
+    bVolumeUpFactor = calcPcmValidBits() - 8;
+    word wBtnLowCount = 0;
+    #if HAVE_LED_INDICATOR_2
+        byte bTimeout_led = 0;
+    #endif
+    sbit btn_bit_prev = true, btn_short_on = false;
+    sbit btn_long_on  = false;
+    #if HAVE_POWER_OFF_MODE
+         btn_long_on2 = false;
+    #endif
+    sbit btn_pause_prev = false;
+    #define btn_next_song_on  btn_short_on
+    #define btn_pause_on      btn_long_on
+    #define btn_power_off_on  btn_long_on2
+
+    #if HAVE_LED_INDICATOR // pseudo PWM setting
+        const byte LED_OFF_DELAY_PAUSE  = 5;
+        const byte LED_OFF_DELAY_PLAY   = 50;
+        const sbyte PRD_COUNT           = 0x6f  ; //max 0x7f
+        const sbyte PAUSE_DUTY_MAX      = PRD_COUNT/4;
+        sbyte sbDuty=0, sbCurrent_pos=0, sbDir=1;
+        sbyte sbMode_duty     = PRD_COUNT ; //set duty playing
+        sbyte sbPseudo_period = PRD_COUNT ; //period for pseudo PWM
+        byte bOff_delay = LED_OFF_DELAY_PAUSE;
+    #endif
+
+    init_tickTimer();
+
+    while(1){
+        //; wait 10msec Ticker flag
+        while ( !isTickTimer_IF() ) {
+            if( ldwSongFileSectors == 0 ){  //; found end of file
+                 break;                     //promptly exit and prepare next song
+            }
+
+            #if HAVE_LED_INDICATOR
+                /*---------------------
+                 pseudo PWM for LED
+                ---------------------*/
+                if( sbCurrent_pos < sbDuty ){
+                    if( sbDir == 0 ){
+                        led = 0;
+                    }else{
+                        led = 1;
+                    }
+                } else {
+                    led = 0;
+                }
+                sbCurrent_pos   = sbCurrent_pos   + 1;
+                sbPseudo_period = sbPseudo_period - 1;
+                if( sbPseudo_period == 0 ){
+                    sbPseudo_period = PRD_COUNT;
+                    sbCurrent_pos   = 0;
+                }
+            #endif
+        } //; end. wait 10msec Ticker flag
+
+        //-------------------
+        tickTimer_IF_clear();
+        //-------------------
+
+        #if HAVE_BUTTON_SW
+            /*-------------------
+             Pause release
+            -------------------*/
+            if( btn_next_song_on){
+                if( !fPlaying){ //          ; if during pause
+                    fPlaying         = true ; //release pause
+                    btn_next_song_on = false;
+                    btn_pause_on     = false;
+                    #if HAVE_LED_INDICATOR
+                        sbDuty = 0;
+                        sbDir  = 1;
+                    #endif
+                    pwm_period_timer_start();
+                }
+            }
+
+            /*-------------------
+             Pause enter
+            -------------------*/
+            if( btn_pause_prev ^ btn_pause_on ){
+                if( btn_pause_on ){
+                    fPlaying = false;
+                    pwm_period_timer_stop();
+                    #if HAVE_LED_INDICATOR
+                        sbDuty = PAUSE_DUTY_MAX/2;
+                        sbDir  = -1;
+                    #endif
+                }
+            }
+            btn_pause_prev = btn_pause_on;
+        #endif
+
+        /*-------------------
+         Next song and start
+        -------------------*/
+        if( lfEndOneSong){
+            lfEndOneSong = false;
+            searchNextFile();
+            //; delete about last 30Kbyte to cut tag data in *.wav file
+            ldwSongFileSectors = ( (gdwBPB_FileSize - CUT_LAST_TAG_NOISE) )>>9 ; //512 , change to sector
+            //## Seek to Target file sector
+            sd_start_read( gdwTargetFileSector );
+
+            //### get wav header info ###
+            sd_read_pulse_byte(22);// ## Skip part of WAV header
+            lbCh_mode = sd_data_byte();
+            sd_data_byte(); //# dummy read
+            ldwSample_freq = sd_data_byte() + (sd_data_byte() << 8)  +
+                                              (sd_data_byte() << 16) +
+                                              (sd_data_byte() << 24);
+            setPwmPeriod(ldwSample_freq);
+            sd_read_pulse_byte(6);// ## Skip some headers
+            lbSample_bits = sd_data_byte();
+            sd_read_pulse_byte(9) ;//## Skip to last position of header
+            //###########################
+
+            lwReadCount = wREAD_COUNT - bHEADER_COUNT;
+            //; music start
+            pwm_period_timer_start();
+
+            #if HAVE_LED_INDICATOR
+                sbDuty = 0;
+                sbDir  = 1;
+            #endif
+        }
+
+        /*-------------------
+         wait end of one song
+        -------------------*/
+        if ( ( ldwSongFileSectors == 0 ) | btn_next_song_on ){
+            pwm_period_timer_stop();
+            fPlaying = false;
+            btn_next_song_on = false;
+            lfEndOneSong  = true;
+            sd_stop_read();
+        }
+
+        /*-------------------
+         LED indicator 1  --- pseudo PWM
+        -------------------*/
+        #if HAVE_LED_INDICATOR
+            if( fPlaying ){
+                sbMode_duty = PRD_COUNT       ; //during Playing
+            }else{
+                #if HAVE_LED_PAUSE_INDICATOR
+                    sbMode_duty = PAUSE_DUTY_MAX ;// during Pause
+                #else
+                    sbMode_duty = 1;
+                #endif
+            }
+            //--- duty control
+            sbDuty = sbDuty + sbDir;
+            if(sbDuty == sbMode_duty ){
+                sbDir = -1;
+            }
+            if( sbDuty == 0){
+                sbDir  = 0;
+                sbDuty = 1;
+                bOff_delay = LED_OFF_DELAY_PAUSE    ;// during pause
+                if( fPlaying ){
+                    bOff_delay = LED_OFF_DELAY_PLAY ;// during play
+                }
+            }
+            if( sbDir == 0 ){ //; wait for a while at LED OFF
+                if( bOff_delay > 0 ){
+                    bOff_delay = bOff_delay - 1;
+                }else{
+                    sbDir = 1;
+                }
+            }
+        #endif
+
+        /*-------------------
+         LED indicator 2 --- simple ON/OFF
+        -------------------*/
+        #if HAVE_LED_INDICATOR_2
+            if (bTimeout_led == 0){
+                if( fPlaying ){
+                    bTimeout_led = LED_PERIOD_PLAYNG       ;// during Playing, on/off
+                    led = !led;
+                }else{
+                    #if HAVE_LED_PAUSE_INDICATOR
+                        bTimeout_led = LED_PERIOD_PAUSING  ;// during Pause,   on/off
+                        led = !led;
+                    #else
+                        bTimeout_led = 1;
+                        led = 0;
+                    #endif
+                }
+            }
+            bTimeout_led = bTimeout_led - 1;
+        #endif
+
+        /*-------------------
+         button sw input
+        -------------------*/
+        #if HAVE_BUTTON_SW
+            if( btn_bit_prev ^ btn_bit_now) {
+                if (btn_bit_now){ // ; 0 --> 1: btn released
+                    if( (wBtnLowCount > 10) && (wBtnLowCount < 130)){//  ; 100msec < x < 1.3sec
+                        btn_short_on = true;
+                    }
+                    wBtnLowCount = 0;
+                }
+            }
+            btn_bit_prev = btn_bit_now;
+            if (btn_bit_now == false){
+                wBtnLowCount = wBtnLowCount + 1;
+            }
+            //; recognized pause on
+            if  (wBtnLowCount > 120) { //  ; 1.2sec >
+                btn_long_on = true;
+            }
+            #if HAVE_POWER_OFF_MODE
+                if (wBtnLowCount > 400){   //; 4sec >
+                    //; "long on2" is meaning go to sleep mode
+                    btn_long_on2 = true;
+                }
+            #endif
+        #endif
+    } //; [forever loop end]
+}
+
+