Ironlord
Member level 3
Hello.
I want to connect a GPIO Expander to my FPGA using I2C. I have seen this thread and I was using the same code, but I have the same problem and it is not solved.
https://www.edaboard.com/showthread.php?367971-i2c-vhdl-example-code
My development board is a Terasic DE10-Nano, with an Altera Cyclone V FPGA. The GPIO Expander is a PA9538A, the address of the device is "1110010.
I will show you the code I'm using and the code I generated.
And this last code is which is my top-level entity.
As I have readed in the thread I commented before, this code doesn't generate de clock signal, but I don't know how to do this right.
Thank you in advance. I’m looking forward to your replies.
I want to connect a GPIO Expander to my FPGA using I2C. I have seen this thread and I was using the same code, but I have the same problem and it is not solved.
https://www.edaboard.com/showthread.php?367971-i2c-vhdl-example-code
My development board is a Terasic DE10-Nano, with an Altera Cyclone V FPGA. The GPIO Expander is a PA9538A, the address of the device is "1110010.
I will show you the code I'm using and the code I generated.
Code:
--------------------------------------------------------------------------------
--
-- FileName: i2c_master.vhd
--
-- Version History
-- Version 1.0 11/1/2012 Scott Larson
-- Initial Public Release
-- Version 2.0 06/20/2014 Scott Larson
-- Added ability to interface with different slaves in the same transaction
-- Corrected ack_error bug where ack_error went 'Z' instead of '1' on error
-- Corrected timing of when ack_error signal clears
-- Version 2.1 10/21/2014 Scott Larson
-- Replaced gated clock with clock enable
-- Adjusted timing of SCL during start and stop conditions
--
--------------------------------------------------------------------------------
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.std_logic_unsigned.all;
ENTITY i2c_master IS
GENERIC(
input_clk : INTEGER := 50_000_000; --input clock speed from user logic in Hz
bus_clk : INTEGER := 400_000); --speed the i2c bus (scl) will run at in Hz
PORT(
clk : IN STD_LOGIC; --system clock
reset_n : IN STD_LOGIC; --active low reset
ena : IN STD_LOGIC; --latch in command
addr : IN STD_LOGIC_VECTOR(6 DOWNTO 0); --address of target slave
rw : IN STD_LOGIC; --'0' is write, '1' is read
data_wr : IN STD_LOGIC_VECTOR(7 DOWNTO 0); --data to write to slave
busy : OUT STD_LOGIC; --indicates transaction in progress
data_rd : OUT STD_LOGIC_VECTOR(7 DOWNTO 0); --data read from slave
ack_error : BUFFER STD_LOGIC; --flag if improper acknowledge from slave
sda : INOUT STD_LOGIC; --serial data output of i2c bus
scl : INOUT STD_LOGIC); --serial clock output of i2c bus
END i2c_master;
ARCHITECTURE logic OF i2c_master IS
CONSTANT divider : INTEGER := (input_clk/bus_clk)/4; --number of clocks in 1/4 cycle of scl
TYPE machine IS(ready, start, command, slv_ack1, wr, rd, slv_ack2, mstr_ack, stop); --needed states
SIGNAL state : machine; --state machine
SIGNAL data_clk : STD_LOGIC; --data clock for sda
SIGNAL data_clk_prev : STD_LOGIC; --data clock during previous system clock
SIGNAL scl_clk : STD_LOGIC; --constantly running internal scl
SIGNAL scl_ena : STD_LOGIC := '0'; --enables internal scl to output
SIGNAL sda_int : STD_LOGIC := '1'; --internal sda
SIGNAL sda_ena_n : STD_LOGIC; --enables internal sda to output
SIGNAL addr_rw : STD_LOGIC_VECTOR(7 DOWNTO 0); --latched in address and read/write
SIGNAL data_tx : STD_LOGIC_VECTOR(7 DOWNTO 0); --latched in data to write to slave
SIGNAL data_rx : STD_LOGIC_VECTOR(7 DOWNTO 0); --data received from slave
SIGNAL bit_cnt : INTEGER RANGE 0 TO 7 := 7; --tracks bit number in transaction
SIGNAL stretch : STD_LOGIC := '0'; --identifies if slave is stretching scl
signal data_clk_m : std_logic;
BEGIN
--generate the timing for the bus clock (scl_clk) and the data clock (data_clk)
PROCESS(clk, reset_n)
VARIABLE count : INTEGER RANGE 0 TO divider*4; --timing for clock generation
BEGIN
IF(reset_n = '0') THEN --reset asserted
stretch <= '0';
count := 0;
ELSIF(clk'EVENT AND clk = '1') THEN
data_clk_prev <= data_clk; --store previous value of data clock
IF(count = divider*4-1) THEN --end of timing cycle
count := 0; --reset timer
ELSIF(stretch = '0') THEN --clock stretching from slave not detected
count := count + 1; --continue clock generation timing
END IF;
CASE count IS
WHEN 0 TO divider-1 => --first 1/4 cycle of clocking
scl_clk <= '0';
data_clk <= '0';
WHEN divider TO divider*2-1 => --second 1/4 cycle of clocking
scl_clk <= '0';
data_clk <= '1';
WHEN divider*2 TO divider*3-1 => --third 1/4 cycle of clocking
scl_clk <= '1'; --release scl
IF(scl = '0') THEN --detect if slave is stretching clock
stretch <= '1';
ELSE
stretch <= '0';
END IF;
data_clk <= '1';
WHEN OTHERS => --last 1/4 cycle of clocking
scl_clk <= '1';
data_clk <= '0';
END CASE;
END IF;
END PROCESS;
--state machine and writing to sda during scl low (data_clk rising edge)
PROCESS(clk, reset_n)
BEGIN
IF(reset_n = '0') THEN --reset asserted
state <= ready; --return to initial state
busy <= '1'; --indicate not available
scl_ena <= '0'; --sets scl high impedance
sda_int <= '1'; --sets sda high impedance
ack_error <= '0'; --clear acknowledge error flag
bit_cnt <= 7; --restarts data bit counter
data_rd <= "00000000"; --clear data read port
ELSIF(clk'EVENT AND clk = '1') THEN
IF(data_clk = '1' AND data_clk_prev = '0') THEN --data clock rising edge
CASE state IS
WHEN ready => --idle state
IF(ena = '1') THEN --transaction requested
busy <= '1'; --flag busy
addr_rw <= addr & rw; --collect requested slave address and command
data_tx <= data_wr; --collect requested data to write
state <= start; --go to start bit
ELSE --remain idle
busy <= '0'; --unflag busy
state <= ready; --remain idle
END IF;
WHEN start => --start bit of transaction
busy <= '1'; --resume busy if continuous mode
sda_int <= addr_rw(bit_cnt); --set first address bit to bus
state <= command; --go to command
WHEN command => --address and command byte of transaction
IF(bit_cnt = 0) THEN --command transmit finished
sda_int <= '1'; --release sda for slave acknowledge
bit_cnt <= 7; --reset bit counter for "byte" states
state <= slv_ack1; --go to slave acknowledge (command)
ELSE --next clock cycle of command state
bit_cnt <= bit_cnt - 1; --keep track of transaction bits
sda_int <= addr_rw(bit_cnt-1); --write address/command bit to bus
state <= command; --continue with command
END IF;
WHEN slv_ack1 => --slave acknowledge bit (command)
IF(addr_rw(0) = '0') THEN --write command
sda_int <= data_tx(bit_cnt); --write first bit of data
state <= wr; --go to write byte
ELSE --read command
sda_int <= '1'; --release sda from incoming data
state <= rd; --go to read byte
END IF;
WHEN wr => --write byte of transaction
busy <= '1'; --resume busy if continuous mode
IF(bit_cnt = 0) THEN --write byte transmit finished
sda_int <= '1'; --release sda for slave acknowledge
bit_cnt <= 7; --reset bit counter for "byte" states
busy<='0'; --modified
state <= slv_ack2; --go to slave acknowledge (write)
ELSE --next clock cycle of write state
bit_cnt <= bit_cnt - 1; --keep track of transaction bits
sda_int <= data_tx(bit_cnt-1); --write next bit to bus
state <= wr; --continue writing
END IF;
WHEN rd => --read byte of transaction
busy <= '1'; --resume busy if continuous mode
IF(bit_cnt = 0) THEN --read byte receive finished
IF(ena = '1' AND addr_rw = addr & rw) THEN --continuing with another read at same address
sda_int <= '0'; --acknowledge the byte has been received
ELSE --stopping or continuing with a write
sda_int <= '1'; --send a no-acknowledge (before stop or repeated start)
END IF;
bit_cnt <= 7; --reset bit counter for "byte" states
data_rd <= data_rx; --output received data
state <= mstr_ack; --go to master acknowledge
ELSE --next clock cycle of read state
bit_cnt <= bit_cnt - 1; --keep track of transaction bits
state <= rd; --continue reading
END IF;
WHEN slv_ack2 => --slave acknowledge bit (write)
IF(ena = '1') THEN --continue transaction
-- busy <= '0'; --continue is accepted
addr_rw <= addr & rw; --collect requested slave address and command
data_tx <= data_wr; --collect requested data to write
IF(addr_rw = addr & rw) THEN --continue transaction with another write
busy <= '1';
sda_int <= data_wr(bit_cnt); --write first bit of data
state <= wr; --go to write byte
ELSE --continue transaction with a read or new slave
state <= start; --go to repeated start
END IF;
ELSE --complete transaction
state <= stop; --go to stop bit
END IF;
WHEN mstr_ack => --master acknowledge bit after a read
IF(ena = '1') THEN --continue transaction
busy <= '0'; --continue is accepted and data received is available on bus
addr_rw <= addr & rw; --collect requested slave address and command
data_tx <= data_wr; --collect requested data to write
IF(addr_rw = addr & rw) THEN --continue transaction with another read
sda_int <= '1'; --release sda from incoming data
state <= rd; --go to read byte
ELSE --continue transaction with a write or new slave
state <= start; --repeated start
END IF;
ELSE --complete transaction
state <= stop; --go to stop bit
END IF;
WHEN stop => --stop bit of transaction
busy <= '0'; --unflag busy
state <= ready; --go to idle state
END CASE;
ELSIF(data_clk = '0' AND data_clk_prev = '1') THEN --data clock falling edge
CASE state IS
WHEN start =>
IF(scl_ena = '0') THEN --starting new transaction
scl_ena <= '1'; --enable scl output
ack_error <= '0'; --reset acknowledge error output
END IF;
WHEN slv_ack1 => --receiving slave acknowledge (command)
IF(sda /= '0' OR ack_error = '1') THEN --no-acknowledge or previous no-acknowledge
ack_error <= '1'; --set error output if no-acknowledge
END IF;
WHEN rd => --receiving slave data
data_rx(bit_cnt) <= sda; --receive current slave data bit
WHEN slv_ack2 => --receiving slave acknowledge (write)
IF(sda /= '0' OR ack_error = '1') THEN --no-acknowledge or previous no-acknowledge
ack_error <= '1'; --set error output if no-acknowledge
END IF;
WHEN stop =>
scl_ena <= '0'; --disable scl
WHEN OTHERS =>
NULL;
END CASE;
END IF;
END IF;
END PROCESS;
--set sda output
data_clk_m<=data_clk_prev and data_clk;
WITH state SELECT
sda_ena_n <= data_clk WHEN start, --generate start condition
NOT data_clk_m WHEN stop, --generate stop condition
sda_int WHEN OTHERS; --set to internal sda signal
--set scl and sda outputs
scl <= '0' WHEN (scl_ena = '1' AND scl_clk = '0') ELSE 'Z';
sda <= '0' WHEN sda_ena_n = '0' ELSE 'Z';
END logic;
Code:
----------------------------------------------------------------------------------
-- FileName: i2c_controller.vhd
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.std_logic_unsigned.all;
entity i2c_controller is
Generic (slave_addr : std_logic_vector(6 downto 0) := "1110010");
Port ( Clock : in STD_LOGIC;
dataIn : in STD_LOGIC_VECTOR (15 downto 0);
oSDA : inout STD_LOGIC;
oSCL : inout STD_LOGIC);
end i2c_controller;
architecture Behavioral of i2c_controller is
component i2c_master IS
GENERIC(
input_clk : INTEGER := 50_000_000; --input clock speed from user logic in Hz
bus_clk : INTEGER := 400_000); --speed the i2c bus (scl) will run at in Hz
PORT(
clk : IN STD_LOGIC; --system clock
reset_n : IN STD_LOGIC; --active low reset
ena : IN STD_LOGIC; --latch in command
addr : IN STD_LOGIC_VECTOR(6 DOWNTO 0); --address of target slave
rw : IN STD_LOGIC; --'0' is write, '1' is read
data_wr : IN STD_LOGIC_VECTOR(7 DOWNTO 0); --data to write to slave
busy : OUT STD_LOGIC; --indicates transaction in progress
data_rd : OUT STD_LOGIC_VECTOR(7 DOWNTO 0); --data read from slave
ack_error : BUFFER STD_LOGIC; --flag if improper acknowledge from slave
sda : INOUT STD_LOGIC; --serial data output of i2c bus
scl : INOUT STD_LOGIC); --serial clock output of i2c bus
END component i2c_master;
signal regBusy,sigBusy,reset,enable,readwrite,nack : std_logic;
signal regData : std_logic_vector(15 downto 0);
signal dataOut : std_logic_vector(7 downto 0);
signal byteChoice : integer := 1;
signal byteChoiceMax : integer := 13;
signal initialCount : integer := 0;
type state_type is (start,write,stop);
signal State : state_type := start;
signal address : std_logic_vector(6 downto 0);
signal Cnt : integer := 16383;
begin
output: i2c_master
port map (
clk=>Clock,
reset_n=>reset,
ena=>enable,
addr=>address,
rw=>readwrite,
data_wr=>dataOut,
busy=>sigBusy,
data_rd=>OPEN,
ack_error=>nack,
sda=>oSDA,
scl=>oSCL);
StateChange: process (Clock)
begin
if rising_edge(Clock) then
case State is
when start =>
if Cnt /= 0 then
Cnt<=Cnt-1;
reset<='0';
State<=start;
enable<='0';
else
reset<='1';
enable<='1';
address<=slave_addr;
readwrite<='0';
State<=write;
end if;
when write=>
regBusy<=sigBusy;
regData<=dataIn;
if regBusy/=sigBusy and sigBusy='0' then
if byteChoice /= byteChoiceMax then
byteChoice<=byteChoice+1;
State<=write;
else
byteChoice<=8;
State<=stop;
end if;
end if;
when stop=>
enable<='0';
if regData/=dataIn then
State<=start;
else
State<=stop;
end if;
end case;
end if;
end process;
process(byteChoice,Clock)
begin
case byteChoice is
when 1 => dataOut <= x"76";
when 2 => dataOut <= x"76";
when 3 => dataOut <= x"76";
when 4 => dataOut <= x"7A";
when 5 => dataOut <= x"FF";
when 6 => dataOut <= x"77";
when 7 => dataOut <= x"00";
when 8 => dataOut <= x"79";
when 9 => dataOut <= x"00";
when 10 => dataOut <= x"0" & dataIn(15 downto 12);
when 11 => dataOut <= x"0" & dataIn(11 downto 8);
when 12 => dataOut <= x"0" & dataIn(7 downto 4);
when 13 => dataOut <= x"0" & dataIn(3 downto 0);
when others => dataOut <= x"76";
end case;
end process;
end Behavioral;
And this last code is which is my top-level entity.
Code:
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.std_logic_unsigned.all;
entity pruebaI2C is
port(
FPGA_CLK1_50: in std_logic;
SDA : inout std_logic;
SCL : inout std_logic
);
end pruebaI2C;
architecture a of pruebaI2C is
component i2c_controller is
Generic (slave_addr : std_logic_vector(6 downto 0) := "1110010");
Port ( Clock : in STD_LOGIC;
dataIn : in STD_LOGIC_VECTOR (15 downto 0);
oSDA : inout STD_LOGIC;
oSCL : inout STD_LOGIC);
end component;
signal Clock, oSDA, oSCL : std_logic;
signal slave_addr : std_logic_vector(6 downto 0);
signal dataIn : std_logic_vector(15 downto 0);
signal cont : std_logic_vector(31 downto 0);
begin
inst1 : i2c_controller
--GENERIC MAP (slave_addr => slave_addr)
PORT MAP(
Clock => Clock,
dataIn => dataIn,
oSDA => oSDA,
oSCL => oSCL
);
process (FPGA_CLK1_50)
begin
if (cont < "11111111111111111111111100000000") then
dataIn<="0000001101010101";
else
dataIn<="0000000111110000";
end if;
if (cont = "11111111111111111111111111111111") then
cont <= "00000000000000000000000000000000";
end if;
end process;
end a;
As I have readed in the thread I commented before, this code doesn't generate de clock signal, but I don't know how to do this right.
Thank you in advance. I’m looking forward to your replies.