打印
[MM32软件]

基于MM32F3270 I2S 使用

[复制链接]
964|14
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
音响数据的采集、处理和传输多媒体技术的重要组成部分。众多的数字音频系统已经进入消费市场,例如数字音频录音带、数字声音处理器。对于设备和生产厂家来说,标准化的信息传输结构可以提高系统的适应性。
I2S(Inter—IC Sound)总线是飞利浦公司为数字音频设备之间的音频数据传输而制定的一种总线标准,该总线专责于音频设备之间的数据传输,广泛应用于各种多媒体系统。它采用了沿独立的导线传输时钟与数据信号的设计,通过将数据和时钟信号分离,避免了因时差诱发的失真,为用户节省了购买抵抗音频抖动的专业设备的费用。在飞利浦公司的 I2S 标准中,既规定了硬件接口规范,也规定了数字音频数据的格式。

I2S 总线接口有 3 个主要信号,但只能实现数据半双工传输,后来为实现全双工传输有些设备增加了扩展数据引脚。

MM32F3270 系列控制器支持 I2S 总线接口,本章节在接下来会对MM32F3270 I2S进行介绍,并使用MM32F3270和CS4344芯片进行I2S通信来演示播放MP3。

01 I2S 主要特征
1)半双工通信(仅发射机或接收机)
2)主操作或从操作
3)8 位可编程线性预分频器,以达到精确的音频采样频率( 8KHz 到 192KHz)
4)数据格式可以是 16 位、 24 位或 32 位
5)数据包帧固定为 16 位(16 位数据帧)或 32 位(16 位、 24 位、 32 位数据帧)
6)可编程时钟极性(稳定状态)
7)发射模式下的下溢标志(仅从机),接收模式下的上溢标志(主和从机)和接收/发射模式下的帧错误标志(仅从机)
8)用于传输和接收的 32 位寄存器为两个声道分时复用
9)支持 I2S 协议:

    – 飞利浦标准
    – MSB 对齐标准(左对齐)
    – LSB 对齐标准(右对齐)
    – PCM 标准(在 16 位信道帧上具有短帧和长帧同步或扩展到 32 位信道帧的 16 位数据帧)
10)数据方向始终是 MSB 优先

11)DMA 传输能力( 32 位宽)

12)可配置输出 MCLK 来驱动外部音频组件,比率固定在 256× FS(其中 FS 为音频采样频率)

使用特权

评论回复
沙发
两只袜子|  楼主 | 2022-1-11 14:33 | 只看该作者
02 I2S 总线接口
I2S 与 SPI 共用三个公共管脚:
1)SD:串行数据(映射在 MOSI 管脚上),用于发送或接收两次多路数据通道(仅在半双工模式下)。

2)WS:声道选择(映射在 NSS 引脚上),是 master 中的数据控制信号输出模式和从模式输入。
3)CK:串行时钟(映射在 SCK 引脚上),是主模式下的串行时钟输出以及从机模式下的串行时钟输入。
4)当某些外部设备需要主时钟输入时,可以使用一个附加的管脚输出时钟到音频设备。
5)MCK:驱动时钟(映射在 MISO 引脚上),用于驱动外部音频组件,仅主模式时使用。

03 I2S 数据格式
三线总线处理音频数据的线路必须经过分时复用两个声道:右声道和左声道。但是只有一个 32 位寄存器用于传输或接收。所以由软件依次配置寄存器 TXREG 为每个声道侧的值,或依次读取寄存器 RXREG的数据。总是先发送左声道,然后发送右声道( CHSIDE 对 PCM 协议没有意义)。

数据可采用以下格式发送:

1) 16 位数据打包在 16 位帧中

2) 16 位数据打包在 32 位帧中
3) 24 位数据打包在 32 位帧中
4) 32 位数据打包在 32 位帧中

当使用 32 位帧上发送 16 位数据时,前 16 位(MSB)是有效的位,16 位 LSB 制为 0,无需任何软件操作,通过硬件实现。其他格式相似。

04 通信标准
对于所有数据格式和通信标准,总是先发送最高位( MSB 优先)。I2S 接口支持四种音频标准,可通过配置 SPI_I2S_I2SCFGR 寄存器的 I2SSTD[1:0]和 PCMSYNC 进行切换。

使用特权

评论回复
板凳
两只袜子|  楼主 | 2022-1-11 14:34 | 只看该作者
飞利浦标准
对于本标准, WS 信号用于指示正在传输的声道。发射器在 CK 的下降沿锁存数据,接收器并在 CK的上升读取数据。WS 信号也在 CK 的下降沿被锁定。对于这种标准 I2S 格式的信号,无论有多少位有效数据,数据的最高位总是出现在 WS 变化(也就是一帧开始)后的第 2 个 CK 脉冲处。


飞利浦标准示意图

MSB 对齐标准
对于这个标准,第一个数据在 WS 变化后的第一个沿有效。

MSB 对齐标准示意图



使用特权

评论回复
地板
两只袜子|  楼主 | 2022-1-11 14:34 | 只看该作者
LSB 对齐标准


LSB 对齐标准示意图

PCM 标准
于 PCM 标准,不需要使用声道信息。PCM 有两个模式:短帧模式和长帧模式,通过配置SPI_I2S_I2SCFGR 寄存器的 PCMSYNC 位进行切换。在 PCM 模式下,输出信号(WS, SD)在 CK 信号的上升沿进行采样。输入信号(WS, SD)在 CK 下降沿被捕获。注意在主模式下, CK 和 WS 被配置为输出。


PCM 标准示意图

05 基于MM32F3270的音频播放实验
CS4344芯片是实现本次实验功能的重要器件之一。CS4344是一种立体声音频数模转换器 (DAC) ,可使用单个 +3.3 V 或 +5 V 电源,仅需要最小的支持电路。该系列线性模拟低通滤波器和自动速度模式检测,当自动选择 2 kHz 和 200 kHz 之间的采样率,使用采样率和主时钟速率方法。


使用特权

评论回复
5
两只袜子|  楼主 | 2022-1-11 14:36 | 只看该作者
本实验的基本原理是MM32F3270 读取SD卡中的MP3文件,并对其解码得到PCM信号,通过I2S接口将PCM信号传输给CS4344,由CS4344进行DA转换输出模拟信号,再经过TS4871(音频功率放大器)连接到耳机接口,可以接入耳机等音频播放装置。

硬件设计
如图是MB-039的I2S部分,完整原理图可以通过官网下载。

各个信号引脚对应如下:

程序设计
根据接口电路配置GPIO初始化
static void I2S3_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    // I2S  MCLK, SD, CK and WS pins configuration
    RCC_AHBPeriphclockCmd(RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_GPIOB, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1ENR_SPI3, ENABLE);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource15, GPIO_AF_6); //I2S WS
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_6);      //I2S CK  I2S_SCK
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_6);     //I2S SD  I2S_DATAOUT  MOSI
    GPIO_PinAFConfig(GPIOC, GPIO_PinSource7, GPIO_AF_5);  //I2S MCK I2S_MCLK

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);


    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOC, &GPIO_InitStructure);

    // config as the control I/O for power on or enter standby
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
}



使用特权

评论回复
6
两只袜子|  楼主 | 2022-1-11 14:38 | 只看该作者
I2S配置初始化   static void I2S_Mode_Config(SPI_I2S_STANDARD_TypeDef usStandard, SPI_I2S_DATAFORMAT_TypeDef usWordLen, SPI_I2S_audio_FREQ_TypeDef usAudioFreq, SPI_I2S_TRANS_MODE_TypeDef usMode)
{
    I2S_InitTypeDef I2S_InitStructure;

    IF ((usMode == I2S_Mode_SlaveTx) && (usMode == I2S_Mode_SlaveRx)) {
        return;
    }
    RCC_APB1PeriphClockCmd(RCC_APB1ENR_SPI3, ENABLE);
    SPI_DeInit(SPI3);
    RCC_APB1PeriphClockCmd(RCC_APB1ENR_SPI3, ENABLE);
    if (usMode == I2S_Mode_MasterTx) {
        I2S_InitStructure.I2S_Mode = I2S_Mode_MasterTx;
        I2S_InitStructure.I2S_Standard = usStandard;
        I2S_InitStructure.I2S_DataFormat = usWordLen;
        I2S_InitStructure.I2S_MCLKOutput = I2S_MCLKOutput_Enable;
        I2S_InitStructure.I2S_AudioFreq = usAudioFreq;
        I2S_InitStructure.I2S_CPOL = I2S_CPOL_Low;
        I2S_Init(SPI3, &I2S_InitStructure);
    }
    else if (usMode == I2S_Mode_MasterRx) {
        I2S_InitStructure.I2S_Mode = I2S_Mode_MasterRx;
        I2S_InitStructure.I2S_Standard = usStandard;
        I2S_InitStructure.I2S_DataFormat = usWordLen;
        I2S_InitStructure.I2S_MCLKOutput = I2S_MCLKOutput_Enable;
        I2S_InitStructure.I2S_AudioFreq = usAudioFreq;
        I2S_InitStructure.I2S_CPOL = I2S_CPOL_Low;
        I2S_Init(SPI3, &I2S_InitStructure);
    }

    SPI_DMACmd(SPI3, ENABLE);
    I2S_Cmd(SPI3, ENABLE);
}

(1)I2S_Mode:I2S 模式选择,可选主机发送、主机接收、从机发送以及从机接收模式,它设定SPI_I2S_GCTL寄存器MODE位的值。一般设置 MM32 控制器为主机模式,当播放声音时选择发送模式,当录制声音时选择接收模式。
(2) I2S_Standard:通信标准格式选择,可选 I2S Philips 标准、左对齐标准、右对齐标准、 PCM 短帧标准或 PCM 长帧标准,它设定SPI_I2S_I2SCFGR 寄存器 I2SSTD位和 PCMSYNC位的值。一般设置为 I2S Philips 标准即可。
(3)I2S_DataFormat:数据格式选择,设定有效数据长度和帧长度,可选标准 16bit 格式、扩展16bit(32bit 帧长度) 格式、 24bit 格式和 32bit 格式,它设定 SPI_I2SCFGR 寄存器 DATLEN 位和CHLEN 位的值。对应 16bit 数据长度可选 16bit 或 32bit 帧长度,其他都是 32bit 帧长度。
(4)I2S_MCLKOutput:主时钟输出使能控制,可选使能输出或禁止输出,它设定 SPI_I2SPR 寄存器 MCKOE 位的值。为提高系统性能一般使能主时钟输出。
(5)I2S_AudioFreq:采样频率设置,标准库提供采样采样频率选择,分别为 4KHz、8kHz、 11kHz、12KHz、16kHz、22kHz、32kHz、44kHz、48kHz、96kHz、192kHz 以及默认 2Hz,它设定 SPI_I2S_SPBRG 寄存器的值。
(6)I2S_CPOL:空闲状态的 CK 线电平,可选高电平或低电平,它设定 SPI_I2S_CCTL 寄存器 CPOL位的值。一般设置为低电平即可。

使用特权

评论回复
7
两只袜子|  楼主 | 2022-1-11 14:40 | 只看该作者
在I2S_StartPlay()函数中调用I2S_Mode_Config()函数,   void I2S_StartPlay(SPI_I2S_STANDARD_TypeDef usStandard, SPI_I2S_DATAFORMAT_TypeDef usWordLen, SPI_I2S_AUDIO_FREQ_TypeDef usAudioFreq)
{
    // config I2S inteRFace as standard, bit length, frequence ,the Master Tx mode
    I2S_Mode_Config(usStandard, usWordLen, usAudioFreq, I2S_Mode_MasterTx);
    SPI3->GCTL |= 0xF;
}

在PlayMP3FiLEDemo()函数中调用I2S_StartPlay()函数,并配置传输模式为主机发送I2S_Mode_MasterTx,选择Phillips标准,16位数据长度,采样频率配置为44KHz。    I2S_StartPlay(I2S_Standard_Phillips, I2S_DataFormat_16b, I2S_AudioFreq_44k);

PlayMP3File()函数是 MP3 播放器的实现函数,定义如下:   void PlayMP3File(void)
{
    DIR dirs;
    FILINFO finfo;
    FRESULT res;
    static UINT br;

    DELAY_Init();
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_GPIOB | RCC_AHBPeriph_GPIOC | RCC_AHBPeriph_GPIOD | RCC_AHBPeriph_GPIOE, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBENR_SDIO, ENABLE);
    CONSOLE_Init(115200);

    SDIO_ConfigInit();
    printf("SDCARD TEST\r\n");
    while(SD_Init()) {
        printf("SD Card Error!\r\n");
    }
    MX_fatfs_Init();
    f_mount(&SDFatFS, (TCHAR const*)SDPath, 0);

    u8 buff[2] = {0x01, 0x02};
    I2S_TX_DMA_Init(&buff[0], 1);
    DMA_Cmd(DMA2_Channel2, ENABLE);

//  while(1){
    if (f_opendir(&dirs, "") == FR_OK) {              //success to open directory
        while (f_readdir(&dirs, &finfo) == FR_OK) {       //if there is file in this directory
            if (finfo.fattrib & AM_ARC) {
                if(!finfo.fname[0])
                    break;
                printf("\r\n Now Playing:[");
                printf(finfo.fname);
                printf("]\r\n");
                res = f_open(&fsrc, finfo.fname, FA_OPEN_EXISTING | FA_READ);
                SET_BIT(SPI3->GCR, SPI_GCR_SPIEN);
                MODIFY_REG(DMA2_Channel2->CCR, DMA_CCR_EN, ENABLE << DMA_CCR_EN_Pos);

                hMP3Decoder = MP3InitDecoder();

                readPtr = readBuf;
                res = f_read(&fsrc, readBuf, READBUF_SIZE, &br);
                bytesLeft += br;

                buffer_switch = 0;
                while(1) {
                    offset = MP3FindSyncWord(readPtr, bytesLeft); //assume EOF if no sync found
                    if(offset < 0)break;
                    readPtr += offset; //data start point
                    bytesLeft -= offset; //in buffer
                    if(bytesLeft < READBUF_SIZE) {
                        memmove(readBuf, readPtr, bytesLeft);
                        res = f_read(&fsrc, readBuf + bytesLeft, READBUF_SIZE - bytesLeft, &br);
                        if((res) || (br == 0)) break;
                        if(br < READBUF_SIZE - bytesLeft)
                            memset(readBuf + bytesLeft + br, 0, READBUF_SIZE - bytesLeft - br);
                        bytesLeft = READBUF_SIZE;
                        readPtr = readBuf;
                    }
                    MP3GetLastFrameInfo(hMP3Decoder, &mp3FrameInfo);

                    if((samprate != mp3FrameInfo.samprate) && (mp3FrameInfo.samprate != 0)) {
//                                               wm8978_CfgAudioIF(I2S_Standard_Phillips, mp3FrameInfo.bitsPerSample, SPI_Mode_Master);
                        //   I2S_StartPlay((SPI_I2S_STANDARD_TypeDef)I2S_Standard_Phillips, (SPI_I2S_DATAFORMAT_TypeDef)mp3FrameInfo.bitsPerSample, (SPI_I2S_AUDIO_FREQ_TypeDef)mp3FrameInfo.samprate);
                        samprate = (SPI_I2S_AUDIO_FREQ_TypeDef) mp3FrameInfo.samprate;
                    }
                    while(1) {
                        if(DMA_GetITStatus(DMA2_IT_TC2) == SET) {
                            DMA_ClearITPendingBit(DMA2_IT_TC2);

                            if(buffer_switch == 0) {
                                Audio_MAL_Play((u32)buffer4, BUFF_SIZE);
                                MP3Decode(hMP3Decoder, &readPtr, &bytesLeft, buffer3, 0);
                                buffer_switch = 1;
                                break;
                            }
                            else {
                                Audio_MAL_Play((u32)buffer3, BUFF_SIZE);
                                MP3Decode(hMP3Decoder, &readPtr, &bytesLeft, buffer4, 0);
                                buffer_switch = 0;
                                break;
                            }
                        }
                    }

                }
                CLEAR_BIT(SPI3->GCR, SPI_GCR_SPIEN);
                MODIFY_REG(DMA2_Channel2->CCR, DMA_CCR_EN, DISABLE << DMA_CCR_EN_Pos);
                f_close(&fsrc);
                bytesLeft = 0;
            }
        }
    }
    while(1);
//   }

}

使用特权

评论回复
8
两只袜子|  楼主 | 2022-1-11 14:41 | 只看该作者
MP3文件是经过压缩算法压缩而存在的,为得到 PCM 信号,需要对 MP3 文件进行解码。本实验使用Helix MP3解码器,Helix MP3 解码器的源代码是开源代码,受制于源代码随附文件中描述的许可协议。该算法支持浮点和定点实现,可移植到任意32位定点处理器上运行,提供对 MPEG-1、 MPEG-2 以及 MPEG-2.5 标准的 Layer3 解码,以及支持可变位速率、恒定位速率,以及立体声和单声道音频格式。关于Helix MP3解码器的移植,在本文中不做重点讲述,更多信息可访问网站:
https://datatype.helixcommunity.org/Mp3dec  

f_open 函数用于打开文件,如果文件打开失败则直接退出播放。

MP3InitDecoder 函数用于初始化Helix 解码器,分配解码器必须内存空间,如果初始化解码器失败直接退出播放。

f_read 函数从 SD 卡读取 MP3 文件数据,存放在 readBuf缓冲区中, br变量保存实际读取到的数据的字节数。如果读取数据失败则运行 MP3FreeDecoder 函数关闭解码器后退出播放器。

MP3Decode 函数开始对源数据缓冲区中帧数据进行解码,通过函数返回值可判断得到解码状态,如果发生解码错误则执行对应的代码。

读取到文件末尾就退出循环, 此时MP3文件已经完整播放。

实验演示
SD卡中存储有MP3文件,并将SD卡、耳机设备接入MB-039开发板,运行程序,就可以听到音乐播放。

本次实验的例程可以通过MindMotion的官网下载MM32F3270 lib_Samples:
https://www.mindmotion.com.cn/pr ... instream/mm32f3270/
工程路径如下:
~MM32F327x_Samples\Demo_app\PlayWave_Demo\SPI_I2S_SDIO_FatFs\MP3_CS4344_Demo
可以看到详细的样例与功能操作。

使用特权

评论回复
9
tpgf| | 2022-2-2 17:45 | 只看该作者
现在这种应用是非常广泛的

使用特权

评论回复
10
wakayi| | 2022-2-2 17:51 | 只看该作者
总感觉现在的音频效果都不是很理想

使用特权

评论回复
11
wowu| | 2022-2-2 17:57 | 只看该作者
现在音频的芯片真的是很多

使用特权

评论回复
12
xiaoqizi| | 2022-2-2 18:03 | 只看该作者
这种芯片可以用在低成本的产品上边

使用特权

评论回复
13
木木guainv| | 2022-2-2 18:08 | 只看该作者
它的优点主要是什么呢

使用特权

评论回复
14
磨砂| | 2022-2-2 18:13 | 只看该作者
这个是主推芯片吗

使用特权

评论回复
15
Younique| | 2022-2-22 17:00 | 只看该作者
之前用过,效果并不理想,用STM32F405发送固定数据给它,接收到的数据都是抖动的。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

2055

主题

7455

帖子

10

粉丝