当前位置:   article > 正文

使用 FPGA 播放音频(一)_fpga 音频处理

fpga 音频处理

55c325504440a2ed3afa89b635322395.png

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

开篇第一步

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

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

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

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

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

1fbe0308a45b882c291b8fb8d96909f9.png

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

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

  • WS = 0 – 通道 1(左)

  • WS = 1 – 通道 2(右)

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

b0dc0282b5ad045d72cd514b9bc5d7f8.png

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

ee7140cf96f6af7fd68c6a0111ed9e4b.png

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

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

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

  • I2S发送器

I2S发送器

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

ca4f1561c9aff759076e5a5a32b46582.png

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

  1. entity I2S_Transmitter is
  2.     Generic (   WIDTH   : INTEGER := 16
  3.                 );
  4.     Port (  Clock   : in STD_LOGIC;
  5.             nReset  : in STD_LOGIC;
  6.             Ready   : out STD_LOGIC;
  7.             Tx      : in STD_LOGIC_VECTOR(((2 * WIDTH) - 1) downto 0);
  8.             LRCLK   : out STD_LOGIC;
  9.             SCLK    : out STD_LOGIC;
  10.             SD      : out STD_LOGIC
  11.             );
  12. end I2S_Transmitter;

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

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

  1. architecture I2S_Transmitter_Arch of I2S_Transmitter is
  2.     type State_t is (State_Reset, State_LoadWord, State_TransmitWord);
  3.     signal CurrentState     : State_t      := State_Reset;
  4.     signal Tx_Int  : STD_LOGIC_VECTOR(((2 * WIDTH) - 1) downto 0)  := (others => '0');
  5.     signal Ready_Int        : STD_LOGIC    := '0';
  6.     signal LRCLK_Int        : STD_LOGIC    := '1';
  7.     signal SD_Int           : STD_LOGIC    := '0';
  8.     signal Enable           : STD_LOGIC    := '0';
  9. begin
  10.     process
  11.         variable BitCounter : INTEGER := 0;
  12.     begin
  13.         wait until falling_edge(Clock);
  14.         case CurrentState is
  15.             when State_Reset =>
  16.                 Ready_Int <= '0';
  17.                 LRCLK_Int <= '1';
  18.                 Enable <= '1';
  19.                 SD_Int <= '0';
  20.                 Tx_Int <= (others => '0');
  21.                 CurrentState <= State_LoadWord;
  22.             when State_LoadWord =>
  23.                 BitCounter := 0;
  24.                 Tx_Int <= Tx;
  25.                 LRCLK_Int <= '0';
  26.                 CurrentState <= State_TransmitWord;
  27.             when State_TransmitWord =>
  28.                 BitCounter := BitCounter + 1;
  29.                 if(BitCounter > (WIDTH - 1)) then
  30.                     LRCLK_Int <= '1';
  31.                 end if;
  32.                 if(BitCounter < ((2 * WIDTH) - 1)) then
  33.                     Ready_Int <= '0';
  34.                     CurrentState <= State_TransmitWord;
  35.                 else
  36.                     Ready_Int <= '1';
  37.                     CurrentState <= State_LoadWord;
  38.                 end if;
  39.                 Tx_Int <= Tx_Int(((2 * WIDTH) - 2) downto 0) & "0";
  40.                 SD_Int <= Tx_Int((2 * WIDTH) - 1);
  41.         end case;
  42.         if(nReset = '0') then
  43.             CurrentState <= State_Reset;        
  44.         end if;
  45.     end process;
  46.     Ready <= Ready_Int;
  47.     SCLK <= Clock and Enable;
  48.     LRCLK <= LRCLK_Int;
  49.     SD <= SD_Int;
  50. end I2S_Transmitter_Arch;

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

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

I2S模块

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

1ae9065f6d2a53bdeefd25571c9fef1e.png

具有以下代码:

  1. entity I2S is
  2.     Generic (   RATIO   : INTEGER := 8;
  3.                 WIDTH   : INTEGER := 16
  4.                 );
  5.     Port (  MCLK     : in STD_LOGIC;
  6.             nReset   : in STD_LOGIC;
  7.             LRCLK    : out STD_LOGIC;
  8.             SCLK     : out STD_LOGIC;
  9.             SD       : out STD_LOGIC
  10.             );
  11. end I2S;

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

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

99f562fb03b58d7d7736c28c7a79ef9e.png

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

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

  1. architecture I2S_Arch of I2S is
  2.     type State_t is (State_Reset, State_WaitForReady, State_IncreaseAddress, State_WaitForStart);
  3.     signal CurrentState : State_t    := State_Reset;
  4.     signal Tx  : STD_LOGIC_VECTOR(((2 * WIDTH) - 1) downto 0)      := (others => '0');
  5.     signal ROM_Data : STD_LOGIC_VECTOR((WIDTH - 1) downto 0)       := (others => '0');
  6.     signal ROM_Address  : STD_LOGIC_VECTOR(6 downto 0)             := (others => '0');
  7.     signal Ready        : STD_LOGIC;
  8.     signal SCLK_Int     : STD_LOGIC                                         := '0';
  9.     component I2S_Transmitter is
  10.         Generic (   WIDTH   : INTEGER := 16
  11.                     );
  12.         Port (  Clock   : in STD_LOGIC;
  13.                 nReset  : in STD_LOGIC;
  14.                 Ready   : out STD_LOGIC;
  15.                 Tx      : in STD_LOGIC_VECTOR(((2 * WIDTH) - 1) downto 0);
  16.                 LRCLK   : out STD_LOGIC;
  17.                 SCLK    : out STD_LOGIC;
  18.                 SD      : out STD_LOGIC
  19.                 );
  20.     end component;
  21.     component SineROM is
  22.         Port (  Address : in STD_LOGIC_VECTOR(6 downto 0);
  23.                 Clock   : in STD_LOGIC;
  24.                 DataOut : out STD_LOGIC_VECTOR(15 downto 0)
  25.                 );
  26.     end component SineROM;
  27. begin
  28.     Transmitter : I2S_Transmitter generic map(  WIDTH => WIDTH
  29.                                                 )
  30.                                   port map(     Clock => SCLK_Int,
  31.                                                 nReset => nReset,
  32.                                                 Ready => Ready,
  33.                                                 Tx => Tx,
  34.                                                 LRCLK => LRCLK,
  35.                                                 SCLK => SCLK,
  36.                                                 SD => SD
  37.                                                 );
  38.     ROM : SineROM port map (Clock => MCLK,
  39.                             Address => ROM_Address,
  40.                             DataOut => ROM_Data
  41.                             );
  42.     process
  43.         variable Counter    : INTEGER := 0;
  44.     begin
  45.         wait until rising_edge(MCLK);
  46.         if(Counter < ((RATIO / 2) - 1)) then
  47.             Counter := Counter + 1;
  48.         else
  49.             Counter := 0;
  50.             SCLK_Int <= not SCLK_Int;
  51.         end if;
  52.         if(nReset = '0') then
  53.             Counter := 0;
  54.             SCLK_Int <= '0';
  55.         end if;
  56.     end process;
  57.     process
  58.         variable WordCounter    : INTEGER := 0;
  59.     begin
  60.         wait until rising_edge(MCLK);
  61.         case CurrentState is
  62.             when State_Reset =>
  63.                 WordCounter := 0;
  64.                 CurrentState <= State_WaitForReady;
  65.             when State_WaitForReady =>
  66.                 if(Ready = '1') then
  67.                     CurrentState <= State_WaitForStart;
  68.                 else
  69.                     CurrentState <= State_WaitForReady;
  70.                 end if;
  71.             when State_WaitForStart =>
  72.                 ROM_Address <= STD_LOGIC_VECTOR(to_unsigned(WordCounter, ROM_Address'length));
  73.                 Tx <= x"0000" & ROM_Data;
  74.                 if(Ready = '0') then
  75.                     CurrentState <= State_IncreaseAddress;
  76.                 else
  77.                     CurrentState <= State_WaitForStart;
  78.                 end if;
  79.             when State_IncreaseAddress =>
  80.                 if(WordCounter < 99) then
  81.                     WordCounter := WordCounter + 1;
  82.                 else
  83.                     WordCounter := 0;
  84.                 end if;
  85.                 CurrentState <= State_WaitForReady;
  86.         end case;
  87.         if(nReset = '0') then
  88.             CurrentState <= State_Reset;
  89.         end if;
  90.     end process;
  91. end I2S_Arch;

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

  1. process
  2.     variable Counter    : INTEGER := 0;
  3. begin
  4.     wait until rising_edge(MCLK);
  5.     if(Counter < ((RATIO / 2) - 1)) then
  6.         Counter := Counter + 1;
  7.     else
  8.         Counter := 0;
  9.         SCLK_Int <= not SCLK_Int;
  10.     end if;
  11.     if(nReset = '0') then
  12.         Counter := 0;
  13.         SCLK_Int <= '0';
  14.     end if;
  15. 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 模块一起实例化。

c5e17230f6afa9d303a5f6402bad489a.png
  1. entity Top is
  2.     Generic (   RATIO   : INTEGER := 8;
  3.                 WIDTH   : INTEGER := 16
  4.                 );
  5.     Port (  Clock   : in STD_LOGIC;
  6.             nReset  : in STD_LOGIC;
  7.             MCLK    : out STD_LOGIC;
  8.             LRCLK   : out STD_LOGIC;
  9.             SCLK    : out STD_LOGIC;
  10.             SD      : out STD_LOGIC;
  11.             LED     : out STD_LOGIC_VECTOR(3 downto 0)
  12.             );
  13. end Top;
  14. architecture Top_Arch of Top is
  15.     signal nSystemReset : STD_LOGIC := '0';
  16.     signal MCLK_DCM     : STD_LOGIC := '0';
  17.     signal Locked       : STD_LOGIC := '0';
  18.     component I2S is    
  19.         Generic (   RATIO   : INTEGER := 8;
  20.                     WIDTH   : INTEGER := 16
  21.                     );
  22.         Port (  MCLK    : in STD_LOGIC;
  23.                 nReset   : in STD_LOGIC;
  24.                 LRCLK    : out STD_LOGIC;
  25.                 SCLK     : out STD_LOGIC;
  26.                 SD       : out STD_LOGIC
  27.                 );
  28.     end component;
  29.     component AudioClock is
  30.         Port (  ClockIn     : in STD_LOGIC;
  31.                 Locked      : out STD_LOGIC;
  32.                 MCLK        : out STD_LOGIC;
  33.                 nReset      : in STD_LOGIC
  34.                 );
  35.     end component;
  36. begin
  37.     InputClock : AudioClock port map (  ClockIn => Clock,
  38.                                         nReset => nReset,
  39.                                         MCLK => MCLK_DCM,
  40.                                         Locked => Locked
  41.                                         );
  42.     I2S_Module : I2S generic map (  RATIO => RATIO,
  43.                                     WIDTH => WIDTH
  44.                                     )
  45.                           port map ( MCLK => MCLK_DCM,
  46.                                      nReset => nSystemReset,
  47.                                      LRCLK => LRCLK,
  48.                                      SCLK => SCLK,
  49.                                      SD => SD
  50.                                      );
  51.     nSystemReset <= nReset and Locked;
  52.     LED(0) <= nReset;
  53.     LED(1) <= Locked;
  54.     LED(2) <= nSystemReset;
  55.     MCLK <= MCLK_DCM;
  56. end Top_Arch;

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

e4a07463c21813dee881b55f567f6d7c.png

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

5e6f32a578a345dd942f2d64cc943ff5.png

附件-Coe

  1. memory_initialization_radix=16;
  2. memory_initialization_vector=
  3. 0000,
  4. 0809,
  5. 100A,
  6. 17FB,
  7. 1FD4,
  8. 278D,
  9. 2F1E,
  10. 367F,
  11. 3DA9,
  12. 4495,
  13. 4B3B,
  14. 5196,
  15. 579E,
  16. 5D4E,
  17. 629F,
  18. 678D,
  19. 6C12,
  20. 7029,
  21. 73D0,
  22. 7701,
  23. 79BB,
  24. 7BF9,
  25. 7DBA,
  26. 7EFC,
  27. 7FBE,
  28. 7FFF,
  29. 7FBE,
  30. 7EFC,
  31. 7DBA,
  32. 7BF9,
  33. 79BB,
  34. 7701,
  35. 73D0,
  36. 7029,
  37. 6C12,
  38. 678D,
  39. 629F,
  40. 5D4E,
  41. 579E,
  42. 5196,
  43. 4B3B,
  44. 4495,
  45. 3DA9,
  46. 367F,
  47. 2F1E,
  48. 278D,
  49. 1FD4,
  50. 17FB,
  51. 100A,
  52. 0809,
  53. 0000,
  54. F7F7,
  55. EFF6,
  56. E805,
  57. E02C,
  58. D873,
  59. D0E2,
  60. C981,
  61. C257,
  62. BB6B,
  63. B4C5,
  64. AE6A,
  65. A862,
  66. A2B2,
  67. 9D61,
  68. 9873,
  69. 93EE,
  70. 8FD7,
  71. 8C30,
  72. 88FF,
  73. 8645,
  74. 8407,
  75. 8246,
  76. 8104,
  77. 8042,
  78. 8001,
  79. 8042,
  80. 8104,
  81. 8246,
  82. 8407,
  83. 8645,
  84. 88FF,
  85. 8C30,
  86. 8FD7,
  87. 93EE,
  88. 9873,
  89. 9D61,
  90. A2B2,
  91. A862,
  92. AE6A,
  93. B4C5,
  94. BB6B,
  95. C257,
  96. C981,
  97. D0E2,
  98. D873,
  99. E02C,
  100. E805,
  101. EFF6,
  102. F7F7,

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/340245
推荐阅读
相关标签
  

闽ICP备14008679号