赞
踩
让我们看一下I2S规范,并尝试用FPGA播放音频文件。
Inter-IC Sound Interface(简称I2S)是由飞利浦公司开发,用于通过不同IC之间的串行接口(例如从处理器到DAC)传输数字音频数据。该接口使用以下信号进行数据传输:
SCK (串行时钟)——用于数据传输的时钟。
SD (串行数据)- 每个数据字的各个位通过该线传输。
WS (字选择)- 定义传输数据字的长度。它用于标记右或左音频通道。
仅音频数据通过 I2S 传输。附加数据(例如各个总线用户的配置)通过其他接口传输。数据传输总是在两个总线之间沿一个方向进行,其中一路总线必须充当主机并负责生成时钟信号。在由多个发送器和接收器组成的复杂系统中,时钟信号由外部总线主控器生成,并且相应的发送器生成数据。
所有数据均以二进制补码和 MSB 优先的方式传输。如果接收方和发送方的字宽存在正差(即一方的字宽小于另一方的字宽),则剩余位填充0。根据规范,数据可以同步于正时钟沿或负时钟沿,从而数据总是在负时钟沿读入。
WS信号选择活动通道,并将低或高相位内的所有数据分配给相应的通道:
WS = 0 – 通道 1(左)
WS = 1 – 通道 2(右)
WS信号必须在下一个数据字的 MSB 之前的一个时钟周期发生变化,以便接收器可以将数据读入正确的通道。WS信号的时钟频率通常对应于音频信号的采样频率。
在这篇文章中,展示如何设计一个简单的 I2S 发射器,并使用 CS4344 立体声 D/A 转换器通过扬声器输出恒定的声音。
要输出的声音将存储在 FPGA 的block memory中,并由发送器读出,并将数据发送到 D/A 转换器。整个项目分为三个部分,将逐步讨论:
集成系统时钟和I2S模块的Top设计
集成ROM和I2S发送器的I2S模块
I2S发送器
设计的最底层应该是 I2S 发送器,其任务是通过 I2S 接口发送各个数据字。
该框图产生了以下发送器:
- 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 发送器将数据从 ROM 传输到 D/A 转换器。
具有以下代码:
- 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 来完成。
最后,通过其他选项使用正弦信号 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状态
最后一个组件是顶层设计,包括 I2S 模块和时钟PLL。
本示例使用以下参数来控制 CS4344:
MCLK:12.288MHz
SCLK:1.536 MHz
LRCLK:48kHz
比率:8
宽度:16
时钟PLL生成 12.288 MHz 时钟,并与之前代码中完成的 I2S 模块一起实例化。
- 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。可以用示波器检查总线和信号:
此外,还可以检查音频信号(示波器的 FFT 功能是实现此目的的最佳工具)。
- 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 卡中的音频文件。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。