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