0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

使用FPGA播放音频文件

OpenFPGA 来源:OpenFPGA 2024-01-17 09:26 次阅读

9b3d3e8c-b4d0-11ee-8b88-92fbcf53809c.png

让我们看一下I2S规范,并尝试用FPGA播放音频文件。

开篇第一步

Inter-IC Sound Interface(简称I2S)是由飞利浦公司开发,用于通过不同IC之间的串行接口(例如从处理器DAC)传输数字音频数据。该接口使用以下信号进行数据传输:

SCK (串行时钟)——用于数据传输的时钟。

SD (串行数据)- 每个数据字的各个位通过该线传输。

WS (字选择)- 定义传输数据字的长度。它用于标记右或左音频通道。

仅音频数据通过 I2S 传输。附加数据(例如各个总线用户的配置)通过其他接口传输。数据传输总是在两个总线之间沿一个方向进行,其中一路总线必须充当主机并负责生成时钟信号。在由多个发送器和接收器组成的复杂系统中,时钟信号由外部总线主控器生成,并且相应的发送器生成数据。

9b4c1128-b4d0-11ee-8b88-92fbcf53809c.png

所有数据均以二进制补码和 MSB 优先的方式传输。如果接收方和发送方的字宽存在正差(即一方的字宽小于另一方的字宽),则剩余位填充0。根据规范,数据可以同步于正时钟沿或负时钟沿,从而数据总是在负时钟沿读入。

WS信号选择活动通道,并将低或高相位内的所有数据分配给相应的通道:

WS = 0 – 通道 1(左)

WS = 1 – 通道 2(右)

‌WS信号必须在下一个数据字的 MSB 之前的一个时钟周期发生变化,以便接收器可以将数据读入正确的通道。WS信号的时钟频率通常对应于音频信号的采样频率。

9b593894-b4d0-11ee-8b88-92fbcf53809c.png

在这篇文章中,展示如何设计一个简单的 I2S 发射器,并使用 CS4344 立体声 D/A 转换器通过扬声器输出恒定的声音。

要输出的声音将存储在 FPGA 的block memory中,并由发送器读出,并将数据发送到 D/A 转换器。整个项目分为三个部分,将逐步讨论:

集成系统时钟和I2S模块的Top设计

集成ROM和I2S发送器的I2S模块

I2S发送器

I2S发送器

设计的最底层应该是 I2S 发送器,其任务是通过 I2S 接口发送各个数据字。

9b739d56-b4d0-11ee-8b88-92fbcf53809c.png

该框图产生了以下发送器:

entityI2S_Transmitteris
Generic(WIDTH:INTEGER:=16
);
Port(Clock:inSTD_LOGIC;
nReset:inSTD_LOGIC;
Ready:outSTD_LOGIC;
Tx:inSTD_LOGIC_VECTOR(((2*WIDTH)-1)downto0);
LRCLK:outSTD_LOGIC;
SCLK:outSTD_LOGIC;
SD:outSTD_LOGIC
);
endI2S_Transmitter;

数据字的大小通过WIDTH参数定义。

三级状态机控制发送器,描述如下:

architectureI2S_Transmitter_ArchofI2S_Transmitteris

typeState_tis(State_Reset,State_LoadWord,State_TransmitWord);

signalCurrentState:State_t:=State_Reset;

signalTx_Int:STD_LOGIC_VECTOR(((2*WIDTH)-1)downto0):=(others=>'0');
signalReady_Int:STD_LOGIC:='0';
signalLRCLK_Int:STD_LOGIC:='1';
signalSD_Int:STD_LOGIC:='0';
signalEnable:STD_LOGIC:='0';

begin

process
variableBitCounter:INTEGER:=0;
begin
waituntilfalling_edge(Clock);

caseCurrentStateis
whenState_Reset=>
Ready_Int<= '0';
                LRCLK_Int <= '1';
                Enable <= '1';
                SD_Int <= '0';
                Tx_Int <= (others =>'0');
CurrentState<= State_LoadWord;
            when State_LoadWord =>
BitCounter:=0;
Tx_Int<= Tx;
                LRCLK_Int <= '0';
                CurrentState <= State_TransmitWord;
            when State_TransmitWord =>
BitCounter:=BitCounter+1;
if(BitCounter>(WIDTH-1))then
LRCLK_Int<= '1';
                end if;
                if(BitCounter < ((2 * WIDTH) - 1)) then
                    Ready_Int <= '0';

                    CurrentState <= State_TransmitWord;
                else
                    Ready_Int <= '1';

                    CurrentState <= State_LoadWord;
                end if;
                Tx_Int <= Tx_Int(((2 * WIDTH) - 2) downto 0) & "0";
                SD_Int <= Tx_Int((2 * WIDTH) - 1);
        end case;
        if(nReset = '0') then
            CurrentState <= State_Reset;        
        end if;
    end process;
    Ready <= Ready_Int;
    SCLK <= Clock and Enable;
    LRCLK <= LRCLK_Int;
    SD <= SD_Int;
end I2S_Transmitter_Arch;

复位期间,输出信号被置位,SCLK时钟被停用。复位后,机器从State_Reset状态变为State_TransmitWord状态。在此状态下,机器Tx_Int通过 I2S 接口传输缓冲区的内容。

一旦开始传输最后一个数据位,Ready就设置为表示传输结束并准备好接受新数据。然后机器更改为 stateState_LoadWord状态,其中发送缓冲区填充有新的数据字并开始新的传输。

I2S模块

I2S 模块使用 I2S 发送器将数据从 ROM 传输到 D/A 转换器。

9b7cc9c6-b4d0-11ee-8b88-92fbcf53809c.png

具有以下代码:

entityI2Sis
Generic(RATIO:INTEGER:=8;
WIDTH:INTEGER:=16
);
Port(MCLK:inSTD_LOGIC;
nReset:inSTD_LOGIC;
LRCLK:outSTD_LOGIC;
SCLK:outSTD_LOGIC;
SD:outSTD_LOGIC
);
endI2S;

参数RATIO 定义了SCLK与MCLK WIDTH的比率以及每个通道的数据字的宽度。

除了 I2S 发送器之外,该模块还使用 ROM,该 ROM 可以通过block memory生成器创建并填充数据。两者都可以使用 Vivado 的 IP 来完成。

9b875b48-b4d0-11ee-8b88-92fbcf53809c.png

最后,通过其他选项使用正弦信号 coe 文件(参见附件)对 ROM 进行初始化。

I2S 模块使用状态机从 ROM 读取数据并将其传输到 I2S 发送器。

architectureI2S_ArchofI2Sis

typeState_tis(State_Reset,State_WaitForReady,State_IncreaseAddress,State_WaitForStart);

signalCurrentState:State_t:=State_Reset;

signalTx:STD_LOGIC_VECTOR(((2*WIDTH)-1)downto0):=(others=>'0');
signalROM_Data:STD_LOGIC_VECTOR((WIDTH-1)downto0):=(others=>'0');
signalROM_Address:STD_LOGIC_VECTOR(6downto0):=(others=>'0');

signalReady:STD_LOGIC;
signalSCLK_Int:STD_LOGIC:='0';

componentI2S_Transmitteris
Generic(WIDTH:INTEGER:=16
);
Port(Clock:inSTD_LOGIC;
nReset:inSTD_LOGIC;
Ready:outSTD_LOGIC;
Tx:inSTD_LOGIC_VECTOR(((2*WIDTH)-1)downto0);
LRCLK:outSTD_LOGIC;
SCLK:outSTD_LOGIC;
SD:outSTD_LOGIC
);
endcomponent;

componentSineROMis
Port(Address:inSTD_LOGIC_VECTOR(6downto0);
Clock:inSTD_LOGIC;
DataOut:outSTD_LOGIC_VECTOR(15downto0)
);
endcomponentSineROM;

begin

Transmitter:I2S_Transmittergenericmap(WIDTH=>WIDTH
)
portmap(Clock=>SCLK_Int,
nReset=>nReset,
Ready=>Ready,
Tx=>Tx,
LRCLK=>LRCLK,
SCLK=>SCLK,
SD=>SD
);

ROM:SineROMportmap(Clock=>MCLK,
Address=>ROM_Address,
DataOut=>ROM_Data
);

process
variableCounter:INTEGER:=0;
begin
waituntilrising_edge(MCLK);
if(Counter< ((RATIO / 2) - 1)) then
            Counter := Counter + 1;
        else
            Counter := 0;
            SCLK_Int <= not SCLK_Int;
        end if;

        if(nReset = '0') then
            Counter := 0;
            SCLK_Int <= '0';
        end if;
    end process;

    process
        variable WordCounter    : INTEGER := 0;
    begin
        wait until rising_edge(MCLK);
        case CurrentState is
            when State_Reset =>
WordCounter:=0;
CurrentState<= State_WaitForReady;
            when State_WaitForReady =>
if(Ready='1')then
CurrentState<= State_WaitForStart;
                else
                    CurrentState <= State_WaitForReady;
                end if;
            when State_WaitForStart =>
ROM_Address<= STD_LOGIC_VECTOR(to_unsigned(WordCounter, ROM_Address'length));
                Tx <= x"0000" & ROM_Data;
                if(Ready = '0') then
                    CurrentState <= State_IncreaseAddress;
                else
                    CurrentState <= State_WaitForStart;
                end if;
            when State_IncreaseAddress =>
if(WordCounter< 99) then
                    WordCounter := WordCounter + 1;
                else
                    WordCounter := 0;
                end if;
                CurrentState <= State_WaitForReady;

        end case;
        if(nReset = '0') then
            CurrentState <= State_Reset;
        end if;
    end process;
end I2S_Arch;

第一个过程用于从MCLK生成发送器所需的时钟信号SCLK 。

process
variableCounter:INTEGER:=0;
begin
waituntilrising_edge(MCLK);
if(Counter< ((RATIO / 2) - 1)) then
        Counter := Counter + 1;
    else
        Counter := 0;
        SCLK_Int <= not SCLK_Int;
    end if;

    if(nReset = '0') then
        Counter := 0;
        SCLK_Int <= '0';
    end if;
end process;

第二个进程负责状态机的处理。离开State_Reset状态后,机器在该State_WaitForReady状态下等待,直到发送器发出就绪信号Ready 。‌‌

一旦发送器准备就绪,机器就会更改State_WaitForStart状态。在此状态下,从 ROM 读取当前数据字并将其传输到发送器。

PS:此处显示的 ROM 仅包含一个通道的信息。第二个通道的数据需进行扩展。

一旦发送器清除就绪信号并开始发送数据,状态机就会更改为State_IncreaseAddress状态。该状态下ROM地址加1,然后切换回State_WaitForReady状态

top模块

最后一个组件是顶层设计,包括 I2S 模块和时钟PLL。

本示例使用以下参数来控制 CS4344:

MCLK:12.288MHz

SCLK:1.536 MHz

LRCLK:48kHz

比率:8

宽度:16

时钟PLL生成 12.288 MHz 时钟,并与之前代码中完成的 I2S 模块一起实例化。

9b96a652-b4d0-11ee-8b88-92fbcf53809c.png

entityTopis
Generic(RATIO:INTEGER:=8;
WIDTH:INTEGER:=16
);
Port(Clock:inSTD_LOGIC;
nReset:inSTD_LOGIC;
MCLK:outSTD_LOGIC;
LRCLK:outSTD_LOGIC;
SCLK:outSTD_LOGIC;
SD:outSTD_LOGIC;
LED:outSTD_LOGIC_VECTOR(3downto0)
);
endTop;

architectureTop_ArchofTopis

signalnSystemReset:STD_LOGIC:='0';
signalMCLK_DCM:STD_LOGIC:='0';
signalLocked:STD_LOGIC:='0';

componentI2Sis
Generic(RATIO:INTEGER:=8;
WIDTH:INTEGER:=16
);
Port(MCLK:inSTD_LOGIC;
nReset:inSTD_LOGIC;
LRCLK:outSTD_LOGIC;
SCLK:outSTD_LOGIC;
SD:outSTD_LOGIC
);
endcomponent;

componentAudioClockis
Port(ClockIn:inSTD_LOGIC;
Locked:outSTD_LOGIC;
MCLK:outSTD_LOGIC;
nReset:inSTD_LOGIC
);
endcomponent;

begin

InputClock:AudioClockportmap(ClockIn=>Clock,
nReset=>nReset,
MCLK=>MCLK_DCM,
Locked=>Locked
);

I2S_Module:I2Sgenericmap(RATIO=>RATIO,
WIDTH=>WIDTH
)
portmap(MCLK=>MCLK_DCM,
nReset=>nSystemReset,
LRCLK=>LRCLK,
SCLK=>SCLK,
SD=>SD
);

nSystemReset<= nReset and Locked;
    LED(0) <= nReset;
    LED(1) <= Locked;
    LED(2) <= nSystemReset;
    MCLK <= MCLK_DCM;

end Top_Arch;

最后就可以进行测试。理想情况下,D/A 转换器输出 480 Hz 正弦信号。因为来自 ROM 的信号模式的长度为 100 个样本,采样频率为 48 kHz。可以用示波器检查总线和信号:

9ba75fd8-b4d0-11ee-8b88-92fbcf53809c.png

此外,还可以检查音频信号(示波器的 FFT 功能是实现此目的的最佳工具)。

9bb38236-b4d0-11ee-8b88-92fbcf53809c.png

附件-Coe

memory_initialization_radix=16;
memory_initialization_vector=
0000,
0809,
100A,
17FB,
1FD4,
278D,
2F1E,
367F,
3DA9,
4495,
4B3B,
5196,
579E,
5D4E,
629F,
678D,
6C12,
7029,
73D0,
7701,
79BB,
7BF9,
7DBA,
7EFC,
7FBE,
7FFF,
7FBE,
7EFC,
7DBA,
7BF9,
79BB,
7701,
73D0,
7029,
6C12,
678D,
629F,
5D4E,
579E,
5196,
4B3B,
4495,
3DA9,
367F,
2F1E,
278D,
1FD4,
17FB,
100A,
0809,
0000,
F7F7,
EFF6,
E805,
E02C,
D873,
D0E2,
C981,
C257,
BB6B,
B4C5,
AE6A,
A862,
A2B2,
9D61,
9873,
93EE,
8FD7,
8C30,
88FF,
8645,
8407,
8246,
8104,
8042,
8001,
8042,
8104,
8246,
8407,
8645,
88FF,
8C30,
8FD7,
93EE,
9873,
9D61,
A2B2,
A862,
AE6A,
B4C5,
BB6B,
C257,
C981,
D0E2,
D873,
E02C,
E805,
EFF6,
F7F7,

下一篇文章,将向 I2S 发送器添加 AXI-Stream 接口,并将其与 ZYNQ 的处理系统连接,播放 SD 卡中的音频文件。

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 处理器
    +关注

    关注

    68

    文章

    19155

    浏览量

    229040
  • FPGA
    +关注

    关注

    1625

    文章

    21663

    浏览量

    601666
  • 接口
    +关注

    关注

    33

    文章

    8486

    浏览量

    150805
  • 音频
    +关注

    关注

    29

    文章

    2832

    浏览量

    81330

原文标题:使用 FPGA 播放音频(一)

文章出处:【微信号:Open_FPGA,微信公众号:OpenFPGA】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    请问如何使用PSoC4播放音频文件

    如何使用 PSoC4 播放音频文件
    发表于 07-02 07:19

    音频文件(WAV)特技播放的原理及实现

    本帖最后由 eehome 于 2013-1-5 10:08 编辑 音频文件播放是开发多媒体软件中的一个重要内容,但有时需要对它进行特技播放,如快速播放,慢速
    发表于 03-12 00:42

    LabVIEW 播放音频文件-

    LabVIEW 播放音频文件-
    发表于 01-20 17:30

    labview 数组转换成数组波形并写入音频文件重新播放

    我先对一个音频文件进行处理 将左右声道的波形分别进行量化编码调制解调解码了 请问 各位大神解码后 如果我想把一个数组转换回数组波形 并写入一个音频文件 重新播放 怎么实现?
    发表于 05-12 20:30

    FPGA+VS1003+flash方案,播放音频文件耳机没有任何声音

    大家好,公司项目采用的是FPGA+VS1003+flash方案,flash用于保存音频文件。目前播放遇到问题了,描述如下:首先我确认了以下几点:1.首先是硬件方面,目前已经完成了寄存器配置的测试
    发表于 01-16 23:10

    5509A播放音频文件感觉全是杂音

    我将音频文件放到SD卡中,用DSP5509A将其读取出来,并发送给AIC23,但是播放出来的音乐感觉全是杂音,请问是对AIC23的配置不对吗?我放在SD卡中的文件是MP3文件,比特率是
    发表于 02-12 11:41

    Matlab处理音频文件

    文章目录Matlab处理音频文件DAC播放DAC配置Tim配置开始应用层数据流向Matlab处理音频文件Audio_filename = 'Audio.wav';% 获取原音频数据[A
    发表于 08-17 06:36

    如何用STM32F103xx单片机通过外部I²S音频编解码器来播放音频文件

    如何用高密度STM32F103xx单片机通过外部 I²S音频编解码器来播放音频文件,本手册仅适用于STM32F103系列,可以实现音频播放功能
    发表于 12-01 07:42

    103xx通过外部I2S音频解码器播放音频文件

    103xx通过外部I2S音频解码器播放音频文件
    发表于 01-12 18:26 56次下载

    基于ESP32构建的音频播放

    在这里,我们将使用LM386 和一个带有 ESP32 的扬声器来播放音乐文件音频输出可能不大,但此应用程序显示了 ESP32 板播放音频文件的能力。
    发表于 08-08 15:37 2.3w次阅读
    基于ESP32构建的<b class='flag-5'>音频</b><b class='flag-5'>播放</b>器

    AN4309_将STM32L1xx微控制器与外部I2S音频编解码器连接起来播放音频文件

    AN4309_将STM32L1xx微控制器与外部I2S音频编解码器连接起来播放音频文件
    发表于 11-21 08:11 0次下载
    AN4309_将STM32L1xx微控制器与外部I2S<b class='flag-5'>音频</b>编解码器连接起来<b class='flag-5'>播放音频文件</b>

    AN2739 如何用高密度STM32F103xx单片机来播放音频文件

    AN2739 如何用高密度STM32F103xx单片机来播放音频文件
    发表于 11-24 08:30 4次下载
    AN2739 如何用高密度STM32F103xx单片机来<b class='flag-5'>播放音频文件</b>

    在Arduino中播放音频

    电子发烧友网站提供《在Arduino中播放音频.zip》资料免费下载
    发表于 06-25 15:16 0次下载
    在Arduino中<b class='flag-5'>播放音频</b>

    使用STM32L1xx微控制器与外部I2S音频编解码器播放音频文件

    电子发烧友网站提供《使用STM32L1xx微控制器与外部I2S音频编解码器播放音频文件.pdf》资料免费下载
    发表于 09-21 11:30 3次下载
    使用STM32L1xx微控制器与外部I2S<b class='flag-5'>音频</b>编解码器<b class='flag-5'>播放音频文件</b>

    如何使用音频接口播放音频文件

    ZDP1440是一款基于开源GUI引擎的图像显示专用驱动芯片,内部集成16MB显示内存、2D图形加速器、音频解码器等丰富多媒体功能,本文将介绍如何使用音频接口播放音频文件
    的头像 发表于 08-06 16:12 540次阅读
    如何使用<b class='flag-5'>音频</b>接口<b class='flag-5'>播放音频文件</b>