Skip to content

Commit

Permalink
Added i2s peripheral (#77)
Browse files Browse the repository at this point in the history
* added i2s peripheral

* Update README.md
  • Loading branch information
EmmanuelRGuesser authored Sep 10, 2024
1 parent 31f4244 commit 925ea7e
Show file tree
Hide file tree
Showing 37 changed files with 3,643 additions and 0 deletions.
70 changes: 70 additions & 0 deletions peripherals/I2S/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Protocolo de comunicação I2S

O protocolo I²S (ou I2S, como também é conhecido) é uma interface serial utilizada para transmitir áudio digital de dois canais em formato de modulação por código de pulso (PCM) entre componentes de circuitos integrados em dispositivos eletrônicos.

### O que é o I²S?

- O I²S (Inter-Integrated Circuit Sound) é um protocolo de comunicação serial síncrono. Ele foi introduzido em 1986 pela Philips Semiconductor (agora conhecida como NXP Semiconductors) e tem sido amplamente utilizado em dispositivos de áudio e sistemas embarcados.
- A principal finalidade do I²S é transmitir dados de áudio digital entre componentes, como microcontroladores, processadores de sinal digital (DSPs), conversores analógico-digital (ADCs) e conversores digital-analógico (DACs).

### Como funciona o I²S?
O barramento I²S utiliza três linhas principais:
- Serial Clock (SCK): Também conhecido como bit clock (BCLK), é responsável por sincronizar a transmissão dos dados.
- Word Select (WS): Também chamado de left-right clock (LRCLK) ou frame sync (FS), indica qual canal (esquerdo ou direito) está sendo transmitido. O WS tem uma frequência igual à taxa de amostragem.
- Serial Data (SD): Transmite os dados de áudio.

![alt text](imgs/interface_timing.png)

### Funcionamento

Para o funcionamento do periférico deve se atentar que:
- O I2S desenvolvido possui uma resolução de 32 bits e como o microfone usado nos testes era de 24 bits foi necessário ajuste na leitura (processo ready em i2s.vhd), então ele deve ser ajustado conforme o mic usado para garantir a leitura correta.

- A taxa de amostragem é definida por $ clk/64 $, nos testes foi usado um clk de 1Mhz resultando em uma taxa de amostragem de 15625Hz. Caso for utilizar um clk menor, o áudio captado poderá ser prejudicado, visto que devemos ter uma taxa de amostragem de pelo menos o dobro da maior frequência que será captada para garantir a integridade do sinal.

- Para o funcionamento também é necessário ter nível lógico alto no pino de enable.

- O áudio captado é escrito na memória desenvolvidada, ela escreve de maneira circular, ou seja, sempre o dado será escrito sob o antigo, ela suporta 16384 palavras de 32 bits, logo é salvo aproximamente 1 segundo. Também deve ser resaltado que é gravado somente o dado do microfone direito, pois somente ele esta transmitindo nos testes.

### Gravando um áudio

Foi utilizado kit DE10-Lite, baseado na FPGA MAX10 para gravar um áudio, deve-se conectar o mic do seguinte modo:

ARDUINO_IO(0) <= sck;
ARDUINO_IO(2) <= ws;
ARDUINO_IO(4) <= sd;

Com a hardware já em funcionamento use o In-System Memory Contet Editor para ler a memória e exportar o áudio, verá algo semelhate a isso:

![alt text](imgs/memoria.png)

Como o áudio é exportado em .hex e a parte significativa do áudio são os 24 bits menos significativos, iremos converter à inteiro sinalizado com um ganho, dessa forma podemos ouvir o áudio gravado. Para isso foi usado um script python para fazer a conversão.

# Lê o arquivo original
with open('audio.hex', 'r') as arquivo_original:
linhas = arquivo_original.readlines()

# Extrai os caracteres de cada linha
ultimos_caracteres = [linha.strip()[-10:-2] for linha in linhas]

# Cria um novo arquivo para salvar os caracteres
with open('audio.txt', 'w') as novo_arquivo:
for caracteres in ultimos_caracteres:
inteiro = int(caracteres,16)
if inteiro &(1 << (31)):
inteiro -= 1 << 32
novo_arquivo.write(str(inteiro*256) + '\n')

Para a análise da gravação foi utilizado o Ocenaudio.
Abaixo tem o espectro do áudio gravado quando é emitido um som na frequência de 1KHz.

![alt text](imgs/1Khz_test.png)

Testando o microfone com voz se tem o seguinte resultado:

![alt text](imgs/audio_test.png)

### Pendências

- Integrar com o softcore;
- Ajustar os timings do sinal de WS e gravação na memória.
134 changes: 134 additions & 0 deletions peripherals/I2S/i2s.vhd
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
-------------------------------------------------------------------
-- Name : i2s.vhd
-- Author : Emmanuel Reitz Guesser
-- Version : 0.1
-- Copyright : Departamento de Eletrônica, Florianópolis, IFSC
-- Description : Este projeto descreve o funcionamento da comunicação i2s entre a FPGA e um microfone.
-------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity I2S is

Port (
clk : in std_logic;
sck : out std_logic;
rst : in std_logic;
ws : out std_logic;
sd : in std_logic;
enable : in std_logic;
left_channel : out std_logic_vector(31 downto 0) := (others => '0');
right_channel : out std_logic_vector(31 downto 0) := (others => '0')
);
end I2S;

architecture RTL of I2S is
type state_type is (IDLE, LEFT, RIGHT);
signal state : state_type := IDLE;
signal bit_count : integer range 0 to 31 := 0;
signal right_data : std_logic_vector(31 downto 0);
signal left_data : std_logic_vector(31 downto 0);

begin

-- Processo que realiza a transição de estados da maquina e um contador que sera usado
-- para direcionar os bits de entrada no bufer, ele é sincronizado por borda
-- de descida porque envio do dado do mic é em borda de descida.
state_transition : process(clk, rst)
begin
if rst = '1' then
state <= IDLE;
bit_count <= 31;

elsif falling_edge(clk) then
case state is
when IDLE =>
if enable = '1' then
state <= LEFT;
end if;

when LEFT =>
if enable = '0' then
state <= IDLE;
elsif bit_count = 0 then
bit_count <= 31;
state <= RIGHT;
else
bit_count <= bit_count - 1;
end if;

when RIGHT =>
if enable = '0' then
state <= IDLE;
elsif bit_count = 0 then
bit_count <= 31;
state <= LEFT;
else
bit_count <= bit_count - 1;
end if;
end case;
end if;
end process state_transition;

-- Processo que envia a sinal de "word select" e o clock que sincroniza o envio de dados do mic.
outs : process(state, clk) is
begin
case state is
when IDLE =>
ws <= '0';
sck <= '1';
when LEFT =>
ws <= '0';
sck <= clk;
when RIGHT =>
ws <= '1';
sck <= clk;
end case;
end process outs;

-- Processo que armazena o dado enviado pelo mic em um bufer. Rescebendo os 32 bits, ele armazena os dados do bufer, como a resolução
-- do mic usado é 24 bits os ultimos 8 bits enviados são lixo, então ele descarta e completa o registrador de 32 bits com o MSB enviado
-- para manter a sinalização do dado. Devido ao atraso do envio de 1 ciclo apos o sinal de ws ignorasse o primeiro bit do buffer.
-- A leitura é sincroniza por borda de subida, para que haja menos interferencia da transição de bit.
ready : process (clk, rst) is
begin
if rst = '1' then
right_data <= (others => '0');
left_data <= (others => '0');
left_channel <= (others => '0');
right_channel <= (others => '0');

elsif rising_edge(clk) then
case state is
when IDLE =>
left_data <= (others => '0');
right_data <= (others => '0');

when LEFT =>
left_data(bit_count) <= sd;
if bit_count = 0 then
left_channel(23 downto 0) <= left_data(30 downto 7);
if left_data(30) = '1' then
left_channel(31 downto 24) <= (others => '1');
else
left_channel(31 downto 24) <= (others => '0');
end if;
right_data <= (others => '0');
end if;

when RIGHT =>
right_data(bit_count) <= sd;
if bit_count = 0 then
right_channel(23 downto 0) <= right_data(30 downto 7);
if right_data(30) = '1' then
right_channel(31 downto 24) <= (others => '1');
else
right_channel(31 downto 24) <= (others => '0');
end if;
left_data <= (others => '0');
end if;
end case;
end if;
end process ready;

end RTL;
92 changes: 92 additions & 0 deletions peripherals/I2S/i2s_testbench.vhd
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity test_bench is
end test_bench;

architecture Test of test_bench is
signal clk_tb : std_logic;
signal sck_tb : std_logic;
signal rst_tb : std_logic;
signal ws_tb : std_logic;
signal sd_tb : std_logic;
signal enable_tb : std_logic;
signal left_channel_tb : std_logic_vector(31 downto 0);
signal right_channel_tb : std_logic_vector(31 downto 0);
signal q_tb: std_logic_vector(31 downto 0);


begin
I2S_inst : entity work.I2S
port map(
clk => clk_tb,
sck => sck_tb,
rst => rst_tb,
ws => ws_tb,
sd => sd_tb,
enable => enable_tb,
left_channel => left_channel_tb,
right_channel => right_channel_tb
);

men_cycle_inst : entity work.men_cycle
port map(
clk => clk_tb,
rst => rst_tb,
data => right_channel_tb,
wren => ws_tb,
q => q_tb
);

clock_driver : process
constant period : time := 10 ns;
begin
clk_tb <= '1';
wait for period / 2;
clk_tb <= '0';
wait for period / 2;
end process clock_driver;

rst : process is
begin
rst_tb <= '1';
wait for 2 ns;
rst_tb <= '0';
wait;
end process rst;

sign : process is -------
constant data_1 : std_logic_vector(31 downto 0) := "00110001010010101100100110011010";
constant data_2 : std_logic_vector(31 downto 0) := "01000100011100001111001000101101";
variable i : integer;

begin
enable_tb <= '1';
sd_tb <= '0';

i := 31;
while i /= -1 loop
wait until sck_tb = '0';
sd_tb <= data_1(i);
i := i - 1;
end loop;

i := 31;
while i /= -1 loop
wait until sck_tb = '0';
sd_tb <= data_2(i);
i := i - 1;
end loop;

i := 31;
while i /= -1 loop
wait until sck_tb = '0';
sd_tb <= data_1(i);
i := i - 1;
end loop;


end process sign;

end architecture Test;
Binary file added peripherals/I2S/imgs/1Khz_test.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added peripherals/I2S/imgs/audio_test.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added peripherals/I2S/imgs/interface_timing.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added peripherals/I2S/imgs/memoria.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 49 additions & 0 deletions peripherals/I2S/men_cycle.vhd
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity men_cycle is
port(
clk : in std_logic;
rst : in std_logic;
data : in std_logic_vector(31 downto 0);
wren : in std_logic;
q : out std_logic_vector(31 downto 0)
);
end entity men_cycle;

architecture RTL of men_cycle is

signal address : unsigned(13 downto 0) := (others => '0');

component ram
PORT
(
address : IN STD_LOGIC_VECTOR (13 DOWNTO 0);
clock : IN STD_LOGIC := '1';
data : IN STD_LOGIC_VECTOR (31 DOWNTO 0);
wren : IN STD_LOGIC ;
q : OUT STD_LOGIC_VECTOR (31 DOWNTO 0)
);
end component;
begin
ram_inst : component ram
port map(
address => std_logic_vector(address),
clock => clk,
data => data,
wren => wren,
q => q
);

addr_cycle : process (wren, rst) is
begin
if rst = '1' then
address <= (others => '0');
elsif rising_edge(wren) then
address <= address + 1;
end if;
end process addr_cycle;

end architecture RTL;

Loading

0 comments on commit 925ea7e

Please sign in to comment.