kings669669 · 2022年08月26日 · 江西

【MM32F5270开发板试用】播放TF卡WAV格式音乐,I2S驱动CS4344

【MM32F5270开发板试用】播放TF卡WAV格式音乐,I2S驱动CS4344

上四篇文章:
【MM32F5270开发板试用】一、依靠SPI_SD,移植FatFs文件系统
【MM32F5270开发板试用】SysTick+Scheduler轮询
【MM32F5270开发板试用】如何将数据存放在DTCM
【MM32F5270开发板试用】依靠SPI_SD,移植FatFs文件系统(优化版本)
本次所有代码按照以前习惯全部开源:我的Github地址是:https://github.com/kings669/M...

代码比较多,一篇文章也放不下,大家可以直接Clone下代码看就行。

一、I2S总线

I2S(Inter—IC Sound)总线, 又称 集成电路内置音频总线,是飞利浦公司为数字音频设备之间的音频数据传输而制定的一种总线标准,该总线专门用于音频设备之间的数据传输,广泛应用于各种多媒体系统。它采用了沿独立的导线传输时钟与数据信号的设计,通过将数据和时钟信号分离,避免了因时差诱发的失真,为用户节省了购买抵抗音频抖动的专业设备的费用。

在MM32F5270中,SPI与I2S共用引脚。在I2S的描述中,支持半双工通信,也支持全双工模式(通过配置 SPI_I2S_I2SCFGR.HDSEL 位来决定)。
image.png
在看一下引脚定义:
image.png
我们发送给CS4344的格式是飞利浦标准
image.png
我们需要配置为主模式,具体流程可以参考手册。
image.png
驱动代码部分

void I2S_Configure(I2S_Protocol_Type   Standard,
                         I2S_DataWidth_Type DataFormat,
                         uint32_t AudioFreq,
                         I2S_XferMode_Type Mode)
{
    /* Setup the I2S. */
    I2S_Master_Init_Type i2s_master_init;
        
    i2s_master_init.ClockFreqHz  = CLOCK_APB1_FREQ;
    i2s_master_init.SampleRate   = AudioFreq;
    i2s_master_init.DataWidth    = DataFormat;
    i2s_master_init.Protocol     = Standard;
    i2s_master_init.EnableMCLK   = true;
    i2s_master_init.Polarity     = I2S_Polarity_1;
    i2s_master_init.XferMode     = Mode;
    
        I2S_InitMaster(SPI2, &i2s_master_init);
        I2S_EnableDMA(SPI2, true);
        I2S_Enable(SPI2, true);
}

void I2S_DMA_Transfer(uint16_t *Buffer, uint32_t BufferSize)
{
      /* Setup the DMA for I2S RX. */
    DMA_Channel_Init_Type dma_channel_init;

    dma_channel_init.MemAddr           = (uint32_t)(Buffer);
    dma_channel_init.MemAddrIncMode    = DMA_AddrIncMode_IncAfterXfer;
    dma_channel_init.PeriphAddr        = I2S_GetTxDataRegAddr(SPI2);  /* use tx data register here. */
    dma_channel_init.PeriphAddrIncMode = DMA_AddrIncMode_StayAfterXfer;
    dma_channel_init.Priority          = DMA_Priority_Highest;
    dma_channel_init.XferCount         = BufferSize;
    dma_channel_init.XferMode          = DMA_XferMode_MemoryToPeriph;
    dma_channel_init.ReloadMode        = DMA_ReloadMode_AutoReload;             /* DMA_AutoReloadMode_Circular */
    dma_channel_init.XferWidth         = DMA_XferWidth_16b;
    DMA_InitChannel(DMA1, DMA_REQ_DMA1_SPI2_TX, &dma_channel_init);
    
    /* Enable DMA transfer done interrupt. */
    DMA_EnableChannelInterrupts(DMA1, DMA_REQ_DMA1_SPI2_TX, DMA_CHN_INT_XFER_DONE, true);
        DMA_EnableChannelInterrupts(DMA1, DMA_REQ_DMA1_SPI2_TX, DMA_CHN_INT_XFER_HALF_DONE, true);
    NVIC_EnableIRQ(DMA1_CH5_IRQn);
    
        DMA_EnableChannel(DMA1, DMA_REQ_DMA1_SPI2_TX, true);
}

二、WAV文件

WAV是最常见的声音文件格式之一,是微软公司专门为Windows开发的一种标准数字音频文件,该文件能记录各种单声道或立体声的声音信息,并能保证声音不失真。但WAV文件有一个致命的缺点,就是它所占用的磁盘空间太大(每分钟的音乐大约需要12兆磁盘空间)。它符合资源互换文件格式(RIFF)规范,用于保存Windows平台的音频信息资源,被Windows平台及其应用程序所广泛支持。Wave格式支持MSADPCM、CCITT A律、CCITT μ律和其他压缩算法,支持多种音频位数、采样频率和声道,是PC机上最为流行的声音文件格式;但其文件尺寸较大,多用于存储简短的声音片段。

网上也有许多关于WAV的讲解,这里就不展开讲解。刚好我在查找资料的时候发现了有类似工程的,我这里直接使用了他的播放部分,由于我没有购置屏幕,所以选择使用串口来打印信息。

#include "wav.h"
#include "i2s_port.h"

FIL     WAV_File;
UINT    WAV_BR[2];
FRESULT WAV_RES;

DTCM_RAM uint8_t WAV_DataBuffer[2][WAV_BUFFER_SIZE];
uint8_t WAV_NextIndex = 0;
uint8_t WAV_PlayEnded = 0;

uint32_t WAV_PlaybackTotal     = 0;
uint32_t WAV_PlaybackProgress  = 0;

static uint8_t WAV_DecodeFile(WAV_TypeDef *pWav, char *Path, char *Name)
{
    static ChunkRIFF_TypeDef *WAV_RIFF;
    static ChunkFMT_TypeDef  *WAV_FMT ;
    static ChunkFACT_TypeDef *WAV_FACT;
    static ChunkDATA_TypeDef *WAV_DATA;

        static uint8_t WAV_HeadBuffer[512];

    static uint8_t Result = 0;
    static char FilePath[ 100];

    memset( FilePath, 0x00, sizeof(FilePath));
    sprintf(FilePath, "%s%s",   Path,   Name);
        printf("%s\r\n",FilePath);
    WAV_RES = f_open(&WAV_File, FilePath, FA_READ);

    if(WAV_RES == FR_OK)
    {
        /* ¶ÁÈ¡512×Ö½ÚÔÚÊý¾Ý */
        WAV_RES = f_read(&WAV_File, WAV_HeadBuffer, 512, &WAV_BR[0]);

        if((WAV_RES == FR_OK) && (WAV_BR[0] != 0))
        {
            /* »ñÈ¡RIFF¿é */
            WAV_RIFF = (ChunkRIFF_TypeDef *)WAV_HeadBuffer;

            /* ÊÇWAV¸ñʽÎļþ */
            if(WAV_RIFF->Format == 0x45564157)
            {
                /* »ñÈ¡FMT¿é */
                WAV_FMT  = (ChunkFMT_TypeDef *)(WAV_HeadBuffer+12);

                /* ¶ÁÈ¡FACT¿é */
                WAV_FACT = (ChunkFACT_TypeDef *)(WAV_HeadBuffer+12+8+WAV_FMT->ChunkSize);

                if((WAV_FACT->ChunkID == 0x74636166) || (WAV_FACT->ChunkID == 0x5453494C))
                {
                    /* ¾ßÓÐFACT/LIST¿éµÄʱºò(δ²âÊÔ) */
                    pWav->DataStart=12+8+WAV_FMT->ChunkSize+8+WAV_FACT->ChunkSize;
                }
                else
                {
                    pWav->DataStart=12+8+WAV_FMT->ChunkSize;
                }

                /* ¶ÁÈ¡DATA¿é */
                WAV_DATA = (ChunkDATA_TypeDef *)(WAV_HeadBuffer+pWav->DataStart);

                /* ½âÎö³É¹¦ */
                if(WAV_DATA->ChunkID == 0x61746164)
                {
                    pWav->AudioFormat   = WAV_FMT->AudioFormat;     /* ÒôƵ¸ñʽ */
                    pWav->nChannels     = WAV_FMT->NumOfChannels;   /* ͨµÀÊý */
                    pWav->SampleRate    = WAV_FMT->SampleRate;      /* ²ÉÑùÂÊ */
                    pWav->BitRate       = WAV_FMT->ByteRate*8;      /* µÃµ½Î»ËÙ */
                    pWav->BlockAlign    = WAV_FMT->BlockAlign;      /* ¿é¶ÔÆë */
                    pWav->BitsPerSample = WAV_FMT->BitsPerSample;   /* λÊý,16/24/32λ */

                    pWav->DataSize      = WAV_DATA->ChunkSize;      /* Êý¾Ý¿é´óС */
                    pWav->DataStart     = pWav->DataStart+8;        /* Êý¾ÝÁ÷¿ªÊ¼µÄµØ·½ */

                    printf("\r\npWav->AudioFormat   : %d", pWav->AudioFormat);
                    printf("\r\npWav->nChannels     : %d", pWav->nChannels);
                    printf("\r\npWav->SampleRate    : %d", pWav->SampleRate);
                    printf("\r\npWav->BitRate       : %d", pWav->BitRate);
                    printf("\r\npWav->BlockAlign    : %d", pWav->BlockAlign);
                    printf("\r\npWav->BitsPerSample : %d", pWav->BitsPerSample);
                    printf("\r\npWav->DataSize      : %d", pWav->DataSize);
                    printf("\r\npWav->DataStart     : %d", pWav->DataStart);

                    WAV_PlaybackTotal = pWav->DataSize;
                }
                else
                {
                    Result = 3; /* DATAÇøÓòδÕÒµ½ */
                }
            }
            else
            {
                Result = 2;     /* ·ÇWAV¸ñʽÎļþ */
            }
        }

       f_close(&WAV_File);
    }
    else
    {
        Result = 1;             /* ´ò¿ªÎļþ´íÎó */
    }

    printf("\r\n\r\nWAV Decode File Result : %d\r\n\r\n", Result);

    return Result;
}

void WAV_PrepareData(void)
{
    if(WAV_NextIndex == 0)
    {
        WAV_RES = f_read(&WAV_File, WAV_DataBuffer[WAV_NextIndex], WAV_BUFFER_SIZE, &WAV_BR[WAV_NextIndex]);
        if(WAV_BR[0] == 0) WAV_PlayEnded = 1;
    }
    else
    {
        WAV_RES = f_read(&WAV_File, WAV_DataBuffer[WAV_NextIndex], WAV_BUFFER_SIZE, &WAV_BR[WAV_NextIndex]);
        if(WAV_BR[1] == 0) WAV_PlayEnded = 1;
    }
}

void WAV_PlayHandler(void)
{
    if(WAV_PlayEnded == 0)
    {
        WAV_PlaybackProgress += WAV_BR[WAV_NextIndex];

        I2S_DMA_Transfer((uint16_t *)&WAV_DataBuffer[WAV_NextIndex][0], (WAV_BR[WAV_NextIndex] /2));
            
    }
    else
    {
        DMA_EnableChannel(DMA1,DMA_REQ_DMA1_SPI2_TX,false);
        f_close(&WAV_File); 
                I2S_PowerON(0);
        printf("\r\nWAV Play Finish!\r\n");
    }
//WAV_NextIndex = 1;
    if(WAV_NextIndex == 0) WAV_NextIndex = 1;
    else                   WAV_NextIndex = 0;
}

void WAV_PlaySong(char *Path, char *Name)
{
    WAV_TypeDef WaveFile;
    char FilePath[100];

    /* »ñÈ¡WAVÎļþµÄÐÅÏ¢ */
    if(WAV_DecodeFile(&WaveFile, Path, Name) == 0)
    {
        if((WaveFile.BitsPerSample == 16) && (WaveFile.nChannels == 2) &&
           (WaveFile.SampleRate  > 44000) && (WaveFile.SampleRate < 48100))
        {
                    I2S_PowerON(1);
                    I2S_Configure(I2S_Protocol_PHILIPS, I2S_DataWidth_16b,I2S_AudioFreq_44k, I2S_XferMode_TxOnly);
        }
        else
        {
            printf("\r\nWAV File Error!\r\n");  return;
        }
    }
    else
    {
        printf("\r\nNot WAV File!\r\n");    return;
    }

    memset( FilePath, 0x00, sizeof(FilePath));
    sprintf(FilePath, "%s%s",   Path,   Name);
        printf(">f_open WAV Start...\r\n");
    WAV_RES = f_open(&WAV_File, FilePath, FA_READ);

    if(WAV_RES == FR_OK)
    {
                printf(">f_open WAV Done!\r\n");
        WAV_NextIndex = 0;
        WAV_PlayEnded = 0;

        WAV_PlaybackProgress = 0;

        WAV_PrepareData();
        WAV_PlayHandler();
    }
    else
    {
        printf("\r\nWAV File Open Error : %d", WAV_RES);
    }
}

三、效果

https://www.bilibili.com/vide...

总结

能够加上DMA的地方都已经使用,参考了最大吞吐率的那篇文章,数据放在了DTCM区,通过DMA传输给I2S。感觉播放效果还行。现在代码播放mp3文件还有些问题,正在排查。在测试的时候,播放wav一直没有声音,之后将SPI改为硬件后效果直接上来了。从接触新板子开始,已经感受到国产MCU进步之快,希望有一天能够干翻ST。😜
参考工程:https://bbs.21ic.com/icview-3... (据说作者就是官方的工作人员😀)

推荐阅读
关注数
6143
内容数
276
灵动MM32 MCU相关技术知识,欢迎关注~
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息