Implement I2C in VHDL

Status
Not open for further replies.

Ironlord

Member level 3
Joined
Oct 16, 2018
Messages
63
Helped
1
Reputation
2
Reaction score
1
Trophy points
8
Activity points
713
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.

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.
 

Hi,

At first you should be aware of hardware, wiring and timing.
Draw a timing chart... how you expect the signals to be.
I've seen you copied code, but I don't see any effort on your own.

At least you should use a scope to see the current signals, their voltage levels and their timing.
Then describe what detail (only one detail, step by step) what you want to modify.

Then try to modify the existign code and use a scope to see what happens.

Klaus
 
Last edited:

I have been thinking about it for 1 week and I still have no idea why it is not working.

I want to work with the 50Mhz clock of the FPGA and generate a 100kHz pulse for the SCL. When I connect the oscilloscope I see a clock signal with the duty cycle in the "edge", close to 0% of duty. And the frequency uses to be about 30Mhz, I still don't know why does this happen. The voltage is around 1.3V.

For my application I know what I want to do and receive.
Start signal -> address+write bite->ACK->register to config the GPIO Pins (03h)->ACK->DATA(8bits)->ACK->stop (This configure the pins I/O of the expander)
Start signal -> address+write bite->ACK->register to config the write (01h)->ACK->DATA(8bits)->ACK->stop (This puts the pins to 1 or 0)

If I'm asking is because I don't know where to coninue and I am stucked.
 

Hi,

"Thinking for a week" won´t help.
You need to systematically analyze it.

You work on FPGa, thus I assume - and strongly recommend - to use the simulator.

***

Real world:
--> show a scope picture. It says more than all the words written above.

Then generate extra signals just to analyze your internal signal flow. Feed them to pins and take extra scope pictures.

***
The voltage is around 1.3V.
What voltage at whick signal.
Surely 1.3V is no valid HIGH level for I2C signals. If you didn´t know this before you need to read I2C specifications.
If this "1.3V" is not the HIGH level, then what else? An average value measured with a DVM? --> useless

For my application I know what I want to do and receive.
Yes, that´s the target task.
But you need to divide the problem in smaller parts. Then these smaller parts in even smaller parts.

*****
Generting clock basics:
If you want to generate 400kHz from a 50MHz clock:
50MHz means a period time of 20ns
400kHz means a priod time of 2500ns, but you need to generate a clock edge every 1250ns.

1250ns / 20ns = 62.5
I recommend to use the next highest integer value = 63 (it´s not exactly 400kHz but it´s not important. You may run the I2C with slower clock frequency, 400kHz is the usual upper limit)
Use a binary counter to count from 0 to 62 (this are 63 steps).
On "62": toggle the output clock signal, and prepare the "synchronous reset" of the counter on the next incoming clock.

Klaus
 

I know that theory, but...

The CLK_50 I generate it on my top-level entity. Must I generate also the SCL @ 400kHz on the top-level entity or does it go on another component?
I thought with that IP core I just had to put my clk and it would calculate the parameters to generate the SCL and to send the data on time.
 

Hi,

does it go on another component?
It will work on any other component, too.
this 400kHz clock does not need to be treated like the 50MHz clock (with FPGA clock dsitribution...) you may treat it is any other logic signal.
But it should be synchronized to the 50MHz clock with the use of a FF, not generated from a combinatorial logic. This ensures glitch free signal with predictable alignment to the 50MHz clock.

But if you need the clock on several components, there may be a benefit to distribute the "enable" for the 400kHz lock instead of the 400kHz clock itself.
Not always. In detail it depends on the requirements..

Klaus
 

If I'm asking is because I don't know where to coninue and I am stucked

Have you simulated your complete design (I2C master is there and a slave model exists) using the exact clocks?
If not first focus there!

Connect this https://github.com/oetr/FPGA-I2C-Slave to you I2C controller and observe the read/writes.
 
Last edited:

Have you simulated your complete design (I2C master is there and a slave model exists) using the exact clocks?
If not first focus there!

Connect this https://github.com/oetr/FPGA-I2C-Slave to you I2C controller and observe the read/writes.

I have not simulated it with an slave, but I did with a testbench of my own. I will try your way.

Also, if anybody has a working IP Core for I2C FPGA I'd be glad if you post it. Thanks.

- - - Updated - - -

View attachment i2c_master_slave_tb.zip
This is what I have tried, but I'm getting errors and can't run my TB. Any help?
 

After no finding solution with the code I shared here, I have made all the process on my own and I have created my implementation.
On this test I just want to write a value on the output, I still have to do the reading task for I2C, but it will be after finishing the writting part.

The problem is that on my simulation it seems to work fine, but when I program it on the FPGA, it doesn't works at all. Anyway, I can see the signals on the oscilloscope.

Code:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
use ieee.numeric_std.all;


entity masterI2C is
    Port( 
		CLK_50 	: IN  STD_LOGIC;					--50MHz Clock
		RST 	: IN  STD_LOGIC;
		ADD	: IN  STD_LOGIC_VECTOR (7 DOWNTO 0); 	--Address
		COM	: IN  STD_LOGIC_VECTOR (7 DOWNTO 0); 	--Command
		DAT	: IN  STD_LOGIC_VECTOR (7 DOWNTO 0); 	--Data
		GO	: IN  STD_LOGIC;						--Start signal.
		BUSY	: OUT STD_LOGIC;					--Working. Can't attend new queries.
		SCLK	: OUT STD_LOGIC;					--CLK Signal for sync. 100kHz.
		SDAT	: INOUT STD_LOGIC);					--Generated trace.
end masterI2C;


architecture a of masterI2C is

	--Status
	TYPE estados is (e0, e1, e2, e3, e4, e5, e6, e7, e8);
	SIGNAL ep : estados :=e0; 	--Actual Status.
	SIGNAL es : estados; 		--Next Status.
	
	--State Machine control signals
	signal idle 	: std_logic := '0';
	signal start 	: std_logic := '0';
	signal config	: std_logic := '0';
	signal cmd	: std_logic := '0';
	signal data	: std_logic := '0';
	signal ack 	: std_logic := '0';
	signal stop	: std_logic := '0';
	signal conf_rdy : std_logic := '0';
	signal cmd_rdy	: std_logic := '0';
	signal data_rdy : std_logic := '0';
	signal ack_st	: std_logic := '0';				--Acknowledge status.
	signal cBits	: integer range -1 to 7 :=7; 	--Bit counter.
	

	--Clk generation
	signal clk200k 	: std_logic :='0'; --200kHz clock. Used to create the start/stop conditions.
	signal clk100k	: std_logic :='0'; --100kHz clock. SCL signal for I2C protocol.
	signal cont	: std_logic_vector(7 downto 0) :="00000000";
	
	--Señales de datos para la placa
	signal data_addr	: std_logic_vector(7 downto 0); --Device Address(7 bits) + "Write" flag.
	signal data_cmd		: std_logic_vector(7 downto 0); --Command.
	signal data_info	: std_logic_vector(7 downto 0); --Command associated data.
	
	--Otras señales
	signal reset 	: std_logic;
	signal sdat_gen : std_logic:='1'; --Generated trace for SDAT.
	signal hold 	: std_logic:='1'; --Keeps the value in SDAT line during a cycle of SCLK.


begin

	reset	  <= RST;
	data_addr <= ADD;
	data_cmd  <= COM;
	data_info <= DAT;

	--Maquina de estados
	PROCESS(ep, reset, GO, conf_rdy, cmd_rdy, data_rdy)
	BEGIN
		IF(reset='1')then
			es<=e0;
		ELSE
			CASE ep IS
				WHEN e0 =>				--Wait status.
					IF(GO='1')THEN
						es<=e1;
					ELSE
						es<=e0;
					END IF;
				WHEN e1=>				--Start condition call.
					es<=e2;
				WHEN e2=>				--Configurate
					IF(conf_rdy='1')THEN
						es<=e3;
					ELSE
						es<=e2;
					END IF;
				WHEN e3 =>				--ACK
					--I'm ignoring the ACK.
					IF(ack='1')THEN
						es<=e4;
					ELSE
						es<=e4;
					END IF;
				WHEN e4=>				--CMD
					IF(cmd_rdy='1')THEN
						es<=e5;
					ELSE
						es<=e4;
					END IF;
				WHEN e5=>				--ACK
					IF(ack='1')THEN
						--Same, ignoring the ACK.
						es<=e6;
					ELSE
						es<=e6;
					END IF;
				WHEN e6=>				--DATA
					IF(data_rdy='1')THEN
						es<=e7;
					ELSE
						es<=e6;
					END IF;
				WHEN e7=>				--ACK
					IF(ack='1')THEN
						es<=e8;
					ELSE
						es<=e8;
					END IF;
				WHEN e8=>				--Stop condition call.
					es<=e0;
			END CASE;
		END IF;
	END PROCESS;
	
	--Status change
	PROCESS(clk100k)
	BEGIN
		if(clk100k='1')then
			ep<=es;
		end if;
	END PROCESS;
	
	--Control signals
	idle		<='1' when ep=e0 else '0';
	start		<='1' when ep=e1 else '0';
	config 		<='1' when ep=e2 else '0';
	cmd		<='1' when ep=e4 else '0';
	data	 	<='1' when ep=e6 else '0';
	ack_st		<='1' when ep=e3 or ep=e5 or ep=e7 else '0';
	stop		<='1' when ep=e8 else '0';

	--200kHz clock generation from 50 MHz
	process (CLK_50)
	begin
		if (rising_edge(CLK_50)) then
			if (cont<"1111101")then --Si es menor que 125
				clk200k<='0';
				cont<=cont+'1';
			else
				clk200k<='1';
				cont<=cont+'1';
			end if;
			if(cont="11111001")then 
				cont<="00000000";
			end if;
		end if;
	end process;

	--100kHz clock (SCLK)
	process(clk200k)
	begin
		if(rising_edge(clk200k))then
			clk100k<=not(clk100k);
		end if;
	end process;
	
	--Control Unit
	process (CLK_50)	--In this process I generate SDAT signal.
	begin
		if(rising_edge(CLK_50))then
			if(start='1')then					--Start condition.
				if(clk100k='1')then
					if(clk200k='0')then
						sdat_gen<='0';
					end if;
				end if;
			elsif(config='1')then				--vector "address" to sda_gen std_logic.
				if(clk100k='1')then
					if(clk200k='0')then
						sdat_gen<=data_addr(cBits);
						hold<='0';
					end if;
				elsif(clk100k='0' and cBits>-1 and hold='0')then
					cBits<=cBits-1;
					hold<='1';
				end if;
				if(cBits=-1)then
					cBits<=7;
					conf_rdy<='1';
				end if;
			elsif(cmd='1')then
				if(clk100k='1')then
					if(clk200k='0')then
						sdat_gen<=data_cmd(cBits);
						hold<='0';
					end if;
				elsif(clk100k='0' and cBits>-1 and hold='0')then
					cBits<=cBits-1;
					hold<='1';
				end if;
				if(cBits=-1)then
					cBits<=7;
					cmd_rdy<='1';
				end if;
			elsif(data='1')then
				if(clk100k='1')then
					if(clk200k='0')then
						sdat_gen<=data_info(cBits);
						hold<='0';
					end if;
				elsif(clk100k='0' and cBits>-1 and hold='0')then
					cBits<=cBits-1;
					hold<='1';
				end if;
				if(cBits=-1)then
					cBits<=7;
					data_rdy<='1';
				end if;
			elsif(ack_st='1')then
				if(clk200k='0')then
					sdat_gen<='0';
				end if;
				--QUESTION: ACK comes from device, which value should be sdat_gen here?
			elsif(stop='1')then				--Stop condition
				conf_rdy<='0';
				cmd_rdy<='0';
				data_rdy<='0';
				if(clk100k='1')then
					if(clk200k='0')then
						sdat_gen<='1';
					end if;
				end if;
			end if;
		end if;
	end process;
		
	--Outputs
	SDAT 	<= sdat_gen;
	SCLK 	<= clk100k;
	BUSY	<= not(idle);

end a;

This is my master. I commented it and there is a doubt about the ACK, I don't know what to do with it. I'm not even trying to caught the information from the device, I just wanna write on it.

And here is the code which invokes the master:

Code:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
use ieee.numeric_std.all;

ENTITY pruebaI2C is
	PORT(
			FPGA_CLK1_50 	: IN STD_LOGIC;
			KEY		: IN STD_LOGIC_VECTOR(0 DOWNTO 0);
			SCL		: OUT STD_LOGIC;
			SDA		: INOUT STD_LOGIC);	
END pruebaI2C;

architecture a of pruebaI2C is

	COMPONENT masterI2C is
   	 	Port( 
			CLK_50 	: IN  STD_LOGIC;
			RST 	: IN  STD_LOGIC;
			ADD		: IN  STD_LOGIC_VECTOR (7 DOWNTO 0); --Address
			COM		: IN  STD_LOGIC_VECTOR (7 DOWNTO 0); --Command
			DAT		: IN  STD_LOGIC_VECTOR (7 DOWNTO 0); --Data
			GO		: IN  STD_LOGIC;
			BUSY	: OUT STD_LOGIC;
			SCLK	: OUT STD_LOGIC;
			SDAT	: INOUT STD_LOGIC
			);
	end COMPONENT;


	--Estados
	TYPE estados is (e0, e1, e2);
	SIGNAL ep : estados :=e0;
	SIGNAL es : estados; 
	

	signal command, data : std_logic_vector(7 downto 0);
	
	--Senales de datos para la placa
	signal cmd_config	: std_logic_vector(7 downto 0) := "00000011"; 	--Device configuration command (03h)
	signal cmd_write	: std_logic_vector(7 downto 0) := "00000001"; 	--Device write command (01h)
	signal data_addr	: std_logic_vector(7 downto 0) := "11100100"; 	--Device Address + write flag
	signal data_ports	: std_logic_vector(7 downto 0) := "00001111"; 	--Definition of ports on device to be output and input. (0=out; 1=in;) Device is GPIO expander.
	signal data_out		: std_logic_vector(7 downto 0) := "10101010"; 	--Ports to write value.
	signal reset		: std_logic:='0';
	signal wait_count 	: std_logic_vector(13 downto 0):="00000000000000";
	signal change		: std_logic;
	SIGNAL go			: std_logic;
	signal ocupado		: std_logic;
	signal espera		: std_logic;
	signal cont			: std_logic_vector (6 downto 0):="0000000";
	signal clk100k		: std_logic;

BEGIN

	reset <= NOT(KEY(0));

	PROCESS(ep, reset, change)
	BEGIN
		IF(reset='1')then
			es<=e0;
		ELSE
			CASE ep IS
				WHEN e0 =>
					command<=cmd_config;
					data<=data_ports;
					es<=e1;
				WHEN e1 =>
					if(change='1')then
						es<=e2;
					end if;
				WHEN e2 =>
					command<=cmd_write;
					data<=data_out;
					es<=e2;
			END CASE;
		END IF;
	END PROCESS;
	espera<='1' when ep=e1 else '0';
	go<='1' when ocupado='0' else '0';

	PROCESS (FPGA_CLK1_50)
	BEGIN
		--Wait while data is sending
		IF(rising_edge(FPGA_CLK1_50))THEN
			IF (espera='1')THEN
				IF(wait_count<"11110010001011")THEN
					wait_count<=wait_count+'1';
					change<='0';
				ELSE
					wait_count<="00000000000000";
					change<='1';
				END IF;
			END IF;
		END IF;
	END PROCESS;


	PROCESS(clk100k)
	BEGIN
		if(clk100k='1')then
			ep<=es;
		end if;
	END PROCESS;


	inst1: masterI2C
    	Port Map( 
		CLK_50 	=> FPGA_CLK1_50,
		RST 	=> reset,
		ADD 	=> data_addr,
		COM	=> command,
		DAT 	=> data,
		GO	=> go,
		BUSY	=> ocupado,
		SCLK	=> clk100k,
		SDAT	=> SDA
	);

	SCL <= clk100k;

END a;

I have also been awared of the timings, and I am sending it with an offset in respect the SCL so the device reads on the middle of the cycle.
On the simulation I see the following:



I think the chronogram is right and my device should be working, but it isn't. Can anybody throw some light?

Thx in advance one more time.
 

I see SDA is LOW and HIGH, but in simulation it should be LOW and OPEN (HIGH-Z), never HIGH

So you mean '1' is 'Z'?
Is high-impedance synthesizable?
 

Hi,

I didn´t go through your code, but I had a look at your simulation picture.

I see SDA is LOW and HIGH, but in simulation it should be LOW and OPEN (HIGH-Z), never HIGH
I expect to that the simulation can show HIGH-Z state.

Klaus

- - - Updated - - -


**************
Sorry for the wrong order of posts....
**************


Hi,

I´m not familiar with VHDL...
But used an internet search...

What I found is this:

http://forums.xilinx.com/t5/General-Technical-Discussion/I2C-protocol-In-VHDL/td-p/301275

and within this discussion there is code with this line:

Code:
sda <= 'Z' when sda01 = '1' else '0'; -- converts sda01 from 0 or 1 to 0 or Z
												  -- Z being high impedence

How long did it take to find this...

Klaus
 

Yes, I have already done this after posting, probably I shuld have done it before :/

Anyway, on the simulation I get what you say, but it still not working at all. I still don't know what I'm doing wrong.

The message is clear, I do the start condition and I send the adress "1110010"+write bit '0'. Then I wait for ACK in 'Z' but I ignore it, I'm not implementing error comprobations yet.
After this I send the comand "00000011", which means configure the pins output. It is a GPIO expansor Board, then I send the data to decide hich of them are outputs and inputs.

In the second row I do the same but with the write command, so I should read 3.3V on my polimeter on the specified outputs which I set to 1 and are active.


 

Hi,

do you use the I2C mandatory pullup resistors at SDA as well as SCL?

Then I wait for ACK in 'Z'
You don´t have to "wait", (unless the slave uses clock stretching, which needs additional modification to your code). It´s just outputting a 9th bit as "Z" while reading in ACK.

I don´t see this 9th bit to be 'Z' in your simulation.

Klaus

added:
Your aim should be to get ACK=LOW by the slave.
If you don' t get it then maybe
* wrong I2C_Start
* wrong voltage levels
* wrong timing
* wrong slave_address
* wrong/no supply voltag efor the slave
* defective slave

Klaus
 

I see SDA is LOW and HIGH, but in simulation it should be LOW and OPEN (HIGH-Z), never HIGH
I expect to that the simulation can show HIGH-Z state.
You can drive std_logic 'H' value (weak '1') to SDA in the test bench. It overrides 'Z' and will be itself overridden by '0'.
 


Code VHDL - [expand]
1
2
3
4
5
6
7
--Status change
    PROCESS(clk100k)
    BEGIN
        if(clk100k='1')then
            ep<=es;
        end if;
    END PROCESS;

what the heck is this supposed to be? I really doubt a synthesis tool is going to produce a state regisiter with this code.
 

Yes, a sufficient explanation why the code doesn't work in hardware. Unfortunately it fakes a register in simulation due to the sensitivity list behavior.

You may want to run a gate level simulation to see that the register isn't real. Or pay just a little bit of attention to the synthesis warnings that surely come up when you run the FPGA tool.
 


That piece of code is to change the present status (ep) value. That's the way I learned to do it, what's wrong with it? Is there another way of doing so?
Be aware that I'm still learning VHDL, so probably I make mistakes.
Thx.
 
Last edited:

That's the way I learned to do it
I hope not. A register hat to be described by

Code VHDL - [expand]
1
2
3
if rising_edge(clk) then
  reg <= val;
end if;

 

Code:
	PROCESS(ep, reset, change, command, data)--I added "when others" statement and "command" and "data" to the sensitive list.
	BEGIN
		IF(reset='1')then
			es<=e0;
		ELSE
			CASE ep IS
				WHEN e0 =>
					command<=cmd_config;
					data<=data_ports;
					es<=e1;
				WHEN e1 =>
					if(change='1')then
						es<=e2;
					end if;
				WHEN e2 =>
					command<=cmd_write;
					data<=data_out;
					es<=e2;
				WHEN OTHERS =>
					command<=command;
					data<=data;
					es<=e1;
			END CASE;
		END IF;
	END PROCESS;

I changed the code trying to avoid latches, but these warnings appear on my compiler:


I try to avoid them and fix the errors, but I don't know what more can I do.
I also post the capture I got from the oscilloscope. I don't know about which voltage must have nor the SCL nor SDA of I2C protocol. I insist, I'm noob and I'm trying to learn.
 

I changed the code trying to avoid latches, but these warnings appear on my compiler:
Yes because they are not being driven within a clocked process. A clock is missing from the sensitivity list (not sure if it must be there as per your design).

One question, do you know the difference between writing sequential and combinatorial VHDL code in general?
Do you know when a register is inferred from a piece of VHDL code in general?
In the above is not clear to you, I would suggest you to return to these fundamentals first, try working with a simpler design and then work with I2C.
 

Status
Not open for further replies.
Cookies are required to use this site. You must accept them to continue using the site. Learn more…