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.

wave_player_main.cpp

Committer:
mimi3
Date:
2019-01-15
Revision:
0:9a5c2fb0d96f
Child:
5:4aa4cc29d5ef

File content as of revision 0:9a5c2fb0d96f:

#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]
}