這裡選擇磁區5進行flash的讀寫
/**
讀取flash
address 讀取起始地址
readBuf 讀取內容存放位置
size 讀取的大小
**/
void readFlash(uint32_t address,uint8_t *readBuf,uint16_t size)
{
uint16_t i;
uint16_t tmpBuf;
printf("開始讀flash\r\n");
for(i=0;i<size;i+=2)
{
tmpBuf=*(__IO uint16_t *)(address+i);
//低八位
readBuf[i]=tmpBuf&0xff;
//高八位
readBuf[i+1]=tmpBuf>>8;
}
printf("讀flash完畢\r\n");
}
/**
寫flash
address 起始地址
data 資料
size 資料大小
**/
void writeFlash(u32 address,u8 *data,u16 size)
{
FLASH_EraseInitTypeDef FlashSet;
HAL_StatusTypeDef FlashStatus = HAL_OK;
u16 tmpbuf;
u16 i;
u32 PageError = 0;
//解鎖FLASH
HAL_FLASH_Unlock();
//擦除FLASH
//第一次寫入之前呼叫擦除函數進行擦除,後續無需再呼叫,否則會吧之前寫入的資料擦除掉
//除非需要覆蓋之前的flash
//初始化FLASH_EraseInitTypeDef
//擦除方式
FlashSet.TypeErase = FLASH_TYPEERASE_SECTORS;
//擦除起始頁
FlashSet.Sector = 5;
//擦除結束頁
FlashSet.NbSectors = 6;
FlashSet.VoltageRange = FLASH_VOLTAGE_RANGE_3;
printf("擦除\r\n");
//呼叫擦除函數
HAL_FLASHEx_Erase(&FlashSet, &PageError);
FlashStatus = FLASH_WaitForLastOperation(1000); //等待上次操作完成
//對FLASH燒寫
printf("開始寫flash\r\n");
for( i= 0;i< size ;i+=2)
{
//把8位元轉16位元,得到半個位元組
//每兩個8位元,一個當低八位,一個當高八位
tmpbuf = (*(data + i + 1) << 8) + (*(data + i));
//寫半個位元組
HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD , address + i , tmpbuf);
}
//鎖住FLASH
HAL_FLASH_Lock();
}
參考前面的部落格Python提取wav格式檔案的data並轉為十六進位制格式重定向輸出提取資料,再分批次進行flash寫入。
通過I2S介面和MCU進行音訊資料的傳輸 將得到的wav資料(PCM編碼)丟給WM8978(codec)即可播放聲音
/**
播放歡迎光臨提示
從flash讀取音訊檔
**/
void hello(){
u8 res;
//每次讀取flash大小
u32 eachSize=8192;
//音訊檔大小
u32 fillnum=data_size;
//flash起始地址
u32 address=(u32)0x08020000;
//分配記憶體
audiodev.i2sbuf1=mymalloc(SRAMIN,eachSize);
audiodev.i2sbuf2=mymalloc(SRAMIN,eachSize);
audiodev.tbuf=mymalloc(SRAMIN,eachSize);
WM8978_I2S_Cfg(2,0); //飛利浦標準,16位元資料長度
I2S2_Init(I2S_STANDARD_PHILIPS,I2S_MODE_MASTER_TX,I2S_CPOL_LOW,I2S_DATAFORMAT_16B_EXTENDED); //飛利浦標準,主機傳送,時鐘低電平有效,16位元擴充套件幀長度
I2S2_SampleRate_Set(8000);//設定取樣率
I2S2_TX_DMA_Init(audiodev.i2sbuf1,audiodev.i2sbuf2,eachSize/2); //設定TX DMA
i2s_tx_callback=wav_i2s_dma_tx_callback; //回撥函數指wav_i2s_dma_callback
audio_start();
printf("開始播放\r\n");
while(1)
{
//根據當前剩餘資料大小和每次播放資料大小來確定每次讀取buf的大小
if(fillnum>=eachSize){
fillnum-=eachSize;
}
else{
eachSize=fillnum;
}
//讀取flash
readFlash(address,audiodev.i2sbuf1,eachSize);
readFlash(address,audiodev.i2sbuf2,eachSize);
//根據當前剩餘資料大小和每次播放資料大小來計算讀取地址
if(eachSize!=fillnum){
address+=eachSize;
}
else{
break;
}
while(wavtransferend==0);//等待wav傳輸完成;
wavtransferend=0;
//播放轉換完畢的buf
audiodev.status=1;
while(1)
{
//判斷是否播放完當前buf
if((audiodev.status&0X01)==0)delay_ms(10);
else break;
}
}
//停止播放
audio_stop();
printf("停止播放\r\n");
myfree(SRAMIN,audiodev.tbuf); //釋放記憶體
myfree(SRAMIN,audiodev.i2sbuf1);//釋放記憶體
myfree(SRAMIN,audiodev.i2sbuf2);//釋放記憶體
}
基於正點原子的 音樂播放器實驗-HAL庫函數版程式碼進行修改。先掃描是否存在SD卡,如果存在則掃描指定檔案裡面的歌曲資訊,歌曲格式為wav格式。如果存在歌曲,則把所有的歌曲資訊進行儲存,並且預設從第一首開始播放。播放原理就是通過讀取每次讀取指定大小的buf,然後通過SPI進行轉換,轉換為PCM編碼,再通過PCM模組進行播放。這裡只展示部分程式碼。最終程式碼見原始碼連結。
//播放音樂
void audio_play(void)
{
u8 res;
DIR wavdir; //目錄
FILINFO *wavfileinfo; //檔案資訊
u8 *pname; //帶路徑的檔名
u16 totwavnum; //音樂檔案總數
u16 curindex; //當前索引
u8 key; //鍵值
u32 temp;
u32 *wavoffsettbl; //音樂offset索引表
WM8978_ADDA_Cfg(1,0); //開啟DAC
WM8978_Input_Cfg(0,0,0);//關閉輸入通道
WM8978_Output_Cfg(1,0); //開啟DAC輸出
//播放預設音訊
hello();
while(f_opendir(&wavdir,"0:/MUSIC"))//開啟音樂資料夾
{
//Show_Str(60,190,240,16,"MUSIC資料夾錯誤!",16,0);
printf("MUSIC資料夾錯誤\r\n");
delay_ms(200);
//LCD_Fill(60,190,240,206,WHITE);//清除顯示
delay_ms(200);
}
totwavnum=audio_get_tnum("0:/MUSIC"); //得到總有效檔案數
while(totwavnum==NULL)//音樂檔案總數為0
{
printf("沒有音樂檔案\r\n");
//Show_Str(60,190,240,16,"沒有音樂檔案!",16,0);
delay_ms(200);
//LCD_Fill(60,190,240,146,WHITE);//清除顯示
delay_ms(200);
}
wavfileinfo=(FILINFO*)mymalloc(SRAMIN,sizeof(FILINFO)); //申請記憶體
pname=mymalloc(SRAMIN,_MAX_LFN*2+1); //為帶路徑的檔名分配記憶體
wavoffsettbl=mymalloc(SRAMIN,4*totwavnum); //申請4*totwavnum個位元組的記憶體,用於存放音樂檔案off block索引
while(!wavfileinfo||!pname||!wavoffsettbl)//記憶體分配出錯
{
printf("記憶體分配失敗\r\n");
//Show_Str(60,190,240,16,"記憶體分配失敗!",16,0);
delay_ms(200);
//LCD_Fill(60,190,240,146,WHITE);//清除顯示
delay_ms(200);
}
//記錄索引
res=f_opendir(&wavdir,"0:/MUSIC"); //開啟目錄
if(res==FR_OK)
{
curindex=0;//當前索引為0
while(1)//全部查詢一遍
{
temp=wavdir.dptr; //記錄當前index
res=f_readdir(&wavdir,wavfileinfo); //讀取目錄下的一個檔案
if(res!=FR_OK||wavfileinfo->fname[0]==0)break; //錯誤了/到末尾了,退出
res=f_typetell((u8*)wavfileinfo->fname);
if((res&0XF0)==0X40)//取高四位,看看是不是音樂檔案
{
wavoffsettbl[curindex]=temp;//記錄索引
curindex++;
}
}
}
curindex=0; //從0開始顯示
res=f_opendir(&wavdir,(const TCHAR*)"0:/MUSIC"); //開啟目錄
while(res==FR_OK)//開啟成功
{
dir_sdi(&wavdir,wavoffsettbl[curindex]); //改變當前目錄索引
res=f_readdir(&wavdir,wavfileinfo); //讀取目錄下的一個檔案
if(res!=FR_OK||wavfileinfo->fname[0]==0)break; //錯誤了/到末尾了,退出
strcpy((char*)pname,"0:/MUSIC/"); //複製路徑(目錄)
strcat((char*)pname,(const char*)wavfileinfo->fname); //將檔名接在後面
//LCD_Fill(60,190,lcddev.width-1,190+16,WHITE); //清除之前的顯示
//Show_Str(60,190,lcddev.width-60,16,(u8*)wavfileinfo->fname,16,0);//顯示歌曲名字
//audio_index_show(curindex+1,totwavnum);
key=audio_play_song(pname); //播放這個音訊檔
if(key==KEY2_PRES) //上一曲
{
if(curindex)curindex--;
else curindex=totwavnum-1;
}else if(key==KEY0_PRES)//下一曲
{
curindex++;
if(curindex>=totwavnum)curindex=0;//到末尾的時候,自動從頭開始
}else break; //產生了錯誤
}
myfree(SRAMIN,wavfileinfo); //釋放記憶體
myfree(SRAMIN,pname); //釋放記憶體
myfree(SRAMIN,wavoffsettbl); //釋放記憶體
}
//播放某個WAV檔案
//fname:wav檔案路徑.
//返回值:
//KEY0_PRES:下一曲
//KEY2_PRES:上一曲
//其他:錯誤
u8 wav_play_song(u8* fname)
{
u8 key;
u8 key2;
u8 t=0;
u8 res;
u32 fillnum;
audiodev.file=(FIL*)mymalloc(SRAMIN,sizeof(FIL));
audiodev.i2sbuf1=mymalloc(SRAMIN,WAV_I2S_TX_DMA_BUFSIZE);
audiodev.i2sbuf2=mymalloc(SRAMIN,WAV_I2S_TX_DMA_BUFSIZE);
audiodev.tbuf=mymalloc(SRAMIN,WAV_I2S_TX_DMA_BUFSIZE);
if(audiodev.file&&audiodev.i2sbuf1&&audiodev.i2sbuf2&&audiodev.tbuf)
{
res=wav_decode_init(fname,&wavctrl);//得到檔案的資訊
if(res==0)//解析檔案成功
{
// printf("%s",fname);
if(wavctrl.bps==16)
{
WM8978_I2S_Cfg(2,0); //飛利浦標準,16位元資料長度
I2S2_Init(I2S_STANDARD_PHILIPS,I2S_MODE_MASTER_TX,I2S_CPOL_LOW,I2S_DATAFORMAT_16B_EXTENDED); //飛利浦標準,主機傳送,時鐘低電平有效,16位元擴充套件幀長度
}else if(wavctrl.bps==24)
{
WM8978_I2S_Cfg(2,2); //飛利浦標準,24位元資料長度
I2S2_Init(I2S_STANDARD_PHILIPS,I2S_MODE_MASTER_TX,I2S_CPOL_LOW,I2S_DATAFORMAT_24B); //飛利浦標準,主機傳送,時鐘低電平有效,24位元長度
}
I2S2_SampleRate_Set(wavctrl.samplerate);//設定取樣率
I2S2_TX_DMA_Init(audiodev.i2sbuf1,audiodev.i2sbuf2,WAV_I2S_TX_DMA_BUFSIZE/2); //設定TX DMA
i2s_tx_callback=wav_i2s_dma_tx_callback; //回撥函數指wav_i2s_dma_callback
audio_stop();
res=f_open(audiodev.file,(TCHAR*)fname,FA_READ); //開啟檔案
if(res==0)
{
f_lseek(audiodev.file, wavctrl.datastart); //跳過檔案頭
fillnum=wav_buffill(audiodev.i2sbuf1,WAV_I2S_TX_DMA_BUFSIZE,wavctrl.bps);
fillnum=wav_buffill(audiodev.i2sbuf2,WAV_I2S_TX_DMA_BUFSIZE,wavctrl.bps);
audio_start();
while(res==0)
{
while(wavtransferend==0);//等待wav傳輸完成;
wavtransferend=0;
if(fillnum!=WAV_I2S_TX_DMA_BUFSIZE)//播放結束?
{
res=KEY0_PRES;
break;
}
if(wavwitchbuf)
fillnum=wav_buffill(audiodev.i2sbuf2,WAV_I2S_TX_DMA_BUFSIZE,wavctrl.bps);//填充buf2
else
fillnum=wav_buffill(audiodev.i2sbuf1,WAV_I2S_TX_DMA_BUFSIZE,wavctrl.bps);//填充buf1
while(1)
{
//掃描紅外遙控
key2=Remote_Scan();
//掃描按鍵
key=KEY_Scan(0);
if(key==WKUP_PRES || key2==56)//暫停
{
printf("暫停\r\n");
if(audiodev.status&0X01)audiodev.status&=~(1<<0);
else audiodev.status|=0X01;
}
if(key==KEY2_PRES||key2==24 )//上一曲
{
// printf("切換歌曲\r\n");
// key=key2;
printf("切換上一首歌曲\r\n");
res=KEY2_PRES;
break;
}
if(key==KEY0_PRES||key2==74 )//下一曲
{
// printf("切換歌曲\r\n");
// key=key2;
printf("切換下一首歌曲\r\n");
res=KEY0_PRES;
break;
}
wav_get_curtime(audiodev.file,&wavctrl);//得到總時間和當前播放的時間
//audio_msg_show(wavctrl.totsec,wavctrl.cursec,wavctrl.bitrate);
t++;
if(t==20)
{
t=0;
LED0=!LED0;
}
if((audiodev.status&0X01)==0)delay_ms(10);
else break;
}
}
audio_stop();
}else res=0XFF;
}
else {res=0XFF;}
}else res=0XFF;
myfree(SRAMIN,audiodev.tbuf); //釋放記憶體
myfree(SRAMIN,audiodev.i2sbuf1);//釋放記憶體
myfree(SRAMIN,audiodev.i2sbuf2);//釋放記憶體
myfree(SRAMIN,audiodev.file); //釋放記憶體
return res;
}
(1)8位元地址和8位元指令長度;
(2)地址和命令2次傳輸(確保可靠性)
(3)PWM脈衝寬度調變,以發射紅外載波的佔空比代表「0」和「1」;
(4)載波頻率為38Khz;
(5)位時間為1.125ms或2.25ms。
一個脈衝對應560us的連續載波,一個邏輯1傳輸需要2.25ms(560us脈衝+1680us低電平),一個邏輯0的傳輸需要1.125ms(560us脈衝+560us低電平)。而遙控接收頭在收到脈衝的時候為低電平,在沒有脈衝的時候為高電平,這樣,我們在接收頭端收到的訊號為:邏輯1應該是560us低+1680us高,邏輯0應該是560us低+560us高。
具體詳情可以參考部落格https://blog.csdn.net/li_little7/article/details/89950161
基於正點原子紅外遙控解碼驅動程式碼進行修改。使用定時器設定定時中斷去掃描對面通道的值。根據掃描的值進行對應轉換得到按鍵資訊。
//處理紅外來鍵盤
//返回值:
// 0,沒有任何按鍵按下
//其他,按下的按鍵鍵值.
u8 Remote_Scan(void)
{
u8 sta=0;
u8 t1,t2;
if(RmtSta&(1<<6))//得到一個按鍵的所有資訊了
{
t1=RmtRec>>24; //得到地址碼
t2=(RmtRec>>16)&0xff; //得到地址反碼
if((t1==(u8)~t2)&&t1==REMOTE_ID)//檢驗遙控識別碼(ID)及地址
{
t1=RmtRec>>8;
t2=RmtRec;
if(t1==(u8)~t2)sta=t1;//鍵值正確
}
if((sta==0)||((RmtSta&0X80)==0))//按鍵資料錯誤/遙控已經沒有按下了
{
RmtSta&=~(1<<6);//清除接收到有效按鍵標識
RmtCnt=0; //清除按鍵次數計數器
}
}
return sta;
}
https://github.com/TangtangSix/MusicPlayer
https://gitee.com/tangtangsix/MusicPlayer