使用 FPGA 播放音频(一)

这篇具有很好参考价值的文章主要介绍了使用 FPGA 播放音频(一)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

fpga音频,fpga开发,音视频

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

开篇第一步

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

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

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

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

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

fpga音频,fpga开发,音视频

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

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

  • WS = 0 – 通道 1(左)

  • WS = 1 – 通道 2(右)

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

fpga音频,fpga开发,音视频

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

fpga音频,fpga开发,音视频

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

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

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

  • I2S发送器

I2S发送器

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

fpga音频,fpga开发,音视频

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

entity I2S_Transmitter is
    Generic (   WIDTH   : INTEGER := 16
                );
    Port (  Clock   : in STD_LOGIC;
            nReset  : in STD_LOGIC;
            Ready   : out STD_LOGIC;
            Tx      : in STD_LOGIC_VECTOR(((2 * WIDTH) - 1) downto 0);
            LRCLK   : out STD_LOGIC;
            SCLK    : out STD_LOGIC;
            SD      : out STD_LOGIC
            );
end I2S_Transmitter;

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

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

architecture I2S_Transmitter_Arch of I2S_Transmitter is

    type State_t is (State_Reset, State_LoadWord, State_TransmitWord);

    signal CurrentState     : State_t      := State_Reset;

    signal Tx_Int  : STD_LOGIC_VECTOR(((2 * WIDTH) - 1) downto 0)  := (others => '0');
    signal Ready_Int        : STD_LOGIC    := '0';
    signal LRCLK_Int        : STD_LOGIC    := '1';
    signal SD_Int           : STD_LOGIC    := '0';
    signal Enable           : STD_LOGIC    := '0';

begin

    process
        variable BitCounter : INTEGER := 0;
    begin
        wait until falling_edge(Clock);

        case CurrentState is
            when State_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 转换器。

fpga音频,fpga开发,音视频

具有以下代码:

entity I2S is
    Generic (   RATIO   : INTEGER := 8;
                WIDTH   : INTEGER := 16
                );
    Port (  MCLK     : in STD_LOGIC;
            nReset   : in STD_LOGIC;
            LRCLK    : out STD_LOGIC;
            SCLK     : out STD_LOGIC;
            SD       : out STD_LOGIC
            );
end I2S;

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

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

fpga音频,fpga开发,音视频

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

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

architecture I2S_Arch of I2S is

    type State_t is (State_Reset, State_WaitForReady, State_IncreaseAddress, State_WaitForStart);

    signal CurrentState : State_t    := State_Reset;

    signal Tx  : STD_LOGIC_VECTOR(((2 * WIDTH) - 1) downto 0)      := (others => '0');
    signal ROM_Data : STD_LOGIC_VECTOR((WIDTH - 1) downto 0)       := (others => '0');
    signal ROM_Address  : STD_LOGIC_VECTOR(6 downto 0)             := (others => '0');

    signal Ready        : STD_LOGIC;
    signal SCLK_Int     : STD_LOGIC                                         := '0';

    component I2S_Transmitter is
        Generic (   WIDTH   : INTEGER := 16
                    );
        Port (  Clock   : in STD_LOGIC;
                nReset  : in STD_LOGIC;
                Ready   : out STD_LOGIC;
                Tx      : in STD_LOGIC_VECTOR(((2 * WIDTH) - 1) downto 0);
                LRCLK   : out STD_LOGIC;
                SCLK    : out STD_LOGIC;
                SD      : out STD_LOGIC
                );
    end component;

    component SineROM is
        Port (  Address : in STD_LOGIC_VECTOR(6 downto 0);
                Clock   : in STD_LOGIC;
                DataOut : out STD_LOGIC_VECTOR(15 downto 0)
                );
    end component SineROM;

begin

    Transmitter : I2S_Transmitter generic map(  WIDTH => WIDTH
                                                )
                                  port map(     Clock => SCLK_Int,
                                                nReset => nReset,
                                                Ready => Ready,
                                                Tx => Tx,
                                                LRCLK => LRCLK,
                                                SCLK => SCLK,
                                                SD => SD
                                                );

    ROM : SineROM port map (Clock => MCLK,
                            Address => ROM_Address,
                            DataOut => ROM_Data
                            );

    process
        variable Counter    : INTEGER := 0;
    begin
        wait until rising_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
    variable Counter    : INTEGER := 0;
begin
    wait until rising_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 模块一起实例化。

fpga音频,fpga开发,音视频
entity Top is
    Generic (   RATIO   : INTEGER := 8;
                WIDTH   : INTEGER := 16
                );
    Port (  Clock   : in STD_LOGIC;
            nReset  : in STD_LOGIC;
            MCLK    : out STD_LOGIC;
            LRCLK   : out STD_LOGIC;
            SCLK    : out STD_LOGIC;
            SD      : out STD_LOGIC;
            LED     : out STD_LOGIC_VECTOR(3 downto 0)
            );
end Top;

architecture Top_Arch of Top is

    signal nSystemReset : STD_LOGIC := '0';
    signal MCLK_DCM     : STD_LOGIC := '0';
    signal Locked       : STD_LOGIC := '0';

    component I2S is    
        Generic (   RATIO   : INTEGER := 8;
                    WIDTH   : INTEGER := 16
                    );
        Port (  MCLK    : in STD_LOGIC;
                nReset   : in STD_LOGIC;
                LRCLK    : out STD_LOGIC;
                SCLK     : out STD_LOGIC;
                SD       : out STD_LOGIC
                );
    end component;

    component AudioClock is
        Port (  ClockIn     : in STD_LOGIC;
                Locked      : out STD_LOGIC;
                MCLK        : out STD_LOGIC;
                nReset      : in STD_LOGIC
                );
    end component;

begin

    InputClock : AudioClock port map (  ClockIn => Clock,
                                        nReset => nReset,
                                        MCLK => MCLK_DCM,
                                        Locked => Locked
                                        );

    I2S_Module : I2S generic map (  RATIO => RATIO,
                                    WIDTH => WIDTH
                                    )
                          port map ( 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。可以用示波器检查总线和信号:

fpga音频,fpga开发,音视频

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

fpga音频,fpga开发,音视频

附件-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 卡中的音频文件。文章来源地址https://www.toymoban.com/news/detail-835070.html

到了这里,关于使用 FPGA 播放音频(一)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android音视频处理技术:音频混音与播放

    作者:禅与计算机程序设计艺术 在现代生活中,我们都会听到各种各样的声音。但是有的声音会相互抵消影响我们的正常生活,而有的声音则会增加我们的情绪快乐。人类为了能够真正体验到声音带来的快感,需要将不同类型的声音合并,再将它们再传达给大脑。音频混音与

    2024年02月08日
    浏览(42)
  • 音视频开发:ffplay使用ffmpeg滤镜实现倍速播放

    曾经为实现倍速播放使用过ffmpeg,对音频使用atempo滤镜即可实现变速不变调。但是当时效果并不是特别好,和soundtouch相比处理后的音质有明显的区别。最近用新版本的ffmpeg滤镜重新实现了倍速播放,发现效果变好,已经达到可接受的程度,所以在此分享具体实现。 ffmpeg倍速

    2024年02月03日
    浏览(102)
  • 音视频开发:音频编码原理+采集+编码实战

    消除冗余信息,压缩量最大,也叫有损压缩 剔除人耳听觉范围外的音频信号20Hz以下和20000Hz以上; 去除被掩蔽的音频信号,信号的遮蔽可以分为频域遮蔽和时域遮蔽; 频域遮蔽效应 屏蔽70分贝以下,20HZ以下,20000HZ以上 屏蔽分贝小,频率小的声音 两个频率相近发出的声音,

    2024年02月05日
    浏览(57)
  • 浏览器网页内嵌Qt-C++音视频播放器的实现,支持软硬解码,支持音频,支持录像截图,支持多路播放等,提供源码工程下载

        在浏览器中实现播放RTSP实时视频流,⼤体上有如下⼏个⽅案: ⽅案一:浏览器插件⽅案 ActiveX、NPAPI、PPAPI     ActiveX插件适用于IE浏览器,NPAPI与PPAPI插件适用于谷歌浏览器,不过这些插件都已经不被浏览器所支持。 ⽅案二:先转码再转流⽅案     ⼯作原理是架设一

    2024年01月17日
    浏览(94)
  • 音视频开发系列(10):基于qt的音频推流

    今天分享一下利用qt录制音频,然后再利用ffmpeg推流到nginx服务器,最后再利用vlc进行拉流的demo。 首先介绍一下如何利用qt来进行音频的录制,qt的音频录制主要利用qt的QAudioFormat先进行音频信息的配置。主要需要配置以下的信息: 然后使用QAudioDeviceInfo来获取是否支持改设置

    2024年02月02日
    浏览(56)
  • 【ESP32音视频传输】②通过I2S采集SPH0645麦克风音频数据并上传到服务端实时播放

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 本文章基于Arduino ESP32 2.07版本,因为2.04版本开始I2S驱动被更改了,所以相同代码可能效果不太同 本文主要参考了:https://atomic14.com/2020/09/12/esp32-audio-input.html ESP32有多种方式从外置麦克风中读取数据:

    2024年02月11日
    浏览(48)
  • 使用Qt进行音视频播放

      Qt对音视频的播放和控制,相机拍照,收音机等多媒体应用提供了强大的支持。Qt5使用了全新的Qt Multimedia模块来实现多媒体应用,而原来Qt4中用于实现多媒体功能的Phonon模块已经被移除。   新的Qt Multimedia模块提供了丰富的接口,使读者可以轻松地使用平台的多媒体功

    2024年02月03日
    浏览(43)
  • 【FFmpeg】音视频录制 ① ( 查询系统中 ffmpeg 可录制的音视频输入设备 | 使用 ffmpeg 命令录制音视频数据 | 录制视频数据命令 |录制音频数据| 同时录制音频和视频数据命令 )

    在 Windows 系统中 , 使用 ffmpeg 命令 录制 音视频 , 需要先获取 系统的 音视频设备 信息 , 录制 音视频 本质上是从 系统音视频设备 中获取数据 ; 执行 命令 , 可以获取 系统中 ffmpeg 可用的 DirectShow 音视频输入设备 ; 命令参数解析 : -list_devices true : 列出所有 ffmpeg 的 指定类型的可

    2024年04月25日
    浏览(89)
  • Qt音视频开发41-文件推流(支持网页和播放器播放并切换进度)

    本功能最初也是有一些人提过类似的需求,就是能不能将本地的音视频文件,通过纯Qt程序推流出去,然后用户可以直接在网页上播放,也可以用各种播放器播放,然后还可以任意切换播放进度,其实说白了就是个文件服务器,用户通过网络地址访问以后,告诉对方当前是媒

    2024年02月01日
    浏览(68)
  • opencv+ffmpeg+QOpenGLWidget开发的音视频播放器demo

        本篇文档的demo包含了 1.使用OpenCV对图像进行处理,对图像进行置灰,旋转,抠图,高斯模糊,中值滤波,部分区域清除置黑,背景移除,边缘检测等操作;2.单纯使用opencv播放显示视频;3.使用opencv和openGL播放显示视频;4.在ffmpeg解码后,使用opencv显示视频,并支持对视

    2024年02月12日
    浏览(66)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包