-- -- Simple I2C controller -- -- 1) No multimaster -- 2) No slave mode -- 3) No fifo's -- -- notes: -- Every command is acknowledged. Do not set a new command before previous is acknowledged. -- Dout is available 1 clock cycle later as cmd_ack --
library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_arith.all;
package I2C is component simple_i2c is port ( clk : in std_logic; ena : in std_logic; nReset : in std_logic;
clk_cnt : in unsigned(7 downto 0); -- 4x SCL
-- input signals start, stop, read, write, ack_in : std_logic; Din : in std_logic_vector(7 downto 0);
-- output signals cmd_ack : out std_logic; ack_out : out std_logic; Dout : out std_logic_vector(7 downto 0);
-- i2c signals SCL : inout std_logic; SDA : inout std_logic ); end component simple_i2c; end package I2C;
library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_arith.all;
entity simple_i2c is port ( clk : in std_logic; ena : in std_logic; nReset : in std_logic;
clk_cnt : in unsigned(7 downto 0); -- 4x SCL
-- input signals start, stop, read, write, ack_in : std_logic; Din : in std_logic_vector(7 downto 0);
-- output signals cmd_ack : out std_logic; ack_out : out std_logic; Dout : out std_logic_vector(7 downto 0);
-- i2c signals SCL : inout std_logic; SDA : inout std_logic ); end entity simple_i2c;
architecture structural of simple_i2c is component i2c_core is port ( clk : in std_logic; nReset : in std_logic;
clk_cnt : in unsigned(7 downto 0);
cmd : in std_logic_vector(2 downto 0); cmd_ack : out std_logic; busy : out std_logic;
Din : in std_logic; Dout : out std_logic;
SCL : inout std_logic; SDA : inout std_logic ); end component i2c_core;
-- commands for i2c_core constant CMD_NOP : std_logic_vector(2 downto 0) := "000"; constant CMD_START : std_logic_vector(2 downto 0) := "010"; constant CMD_STOP : std_logic_vector(2 downto 0) := "011"; constant CMD_READ : std_logic_vector(2 downto 0) := "100"; constant CMD_WRITE : std_logic_vector(2 downto 0) := "101";
-- signals for i2c_core signal core_cmd : std_logic_vector(2 downto 0); signal core_ack, core_busy, core_txd, core_rxd : std_logic;
-- signals for shift register signal sr : std_logic_vector(7 downto 0); -- 8bit shift register signal shift, ld : std_logic;
-- signals for state machine signal go, host_ack : std_logic; begin -- hookup i2c core u1: i2c_core port map (clk, nReset, clk_cnt, core_cmd, core_ack, core_busy, core_txd, core_rxd, SCL, SDA);
-- generate host-command-acknowledge cmd_ack <= host_ack; -- generate go-signal go <= (read or write) and not host_ack;
-- assign Dout output to shift-register Dout <= sr;
-- assign ack_out output to core_rxd (contains last received bit) ack_out <= core_rxd;
-- generate shift register shift_register: process(clk) begin if (clk'event and clk = '1') then if (ld = '1') then sr <= din; elsif (shift = '1') then sr <= (sr(6 downto 0) & core_rxd); end if; end if; end process shift_register;
-- -- state machine -- statemachine : block type states is (st_idle, st_start, st_read, st_write, st_ack, st_stop); signal state : states; signal dcnt : unsigned(2 downto 0); begin -- -- command interpreter, translate complex commands into simpler I2C commands -- nxt_state_decoder: process(clk, nReset, state) variable nxt_state : states; variable idcnt : unsigned(2 downto 0); variable ihost_ack : std_logic; variable icore_cmd : std_logic_vector(2 downto 0); variable icore_txd : std_logic; variable ishift, iload : std_logic; begin -- 8 databits (1byte) of data to shift-in/out idcnt := dcnt;
-- no acknowledge (until command complete) ihost_ack := '0';
icore_txd := core_txd;
-- keep current command to i2c_core icore_cmd := core_cmd;
-- no shifting or loading of shift-register ishift := '0'; iload := '0';
-- keep current state; nxt_state := state; case state is when st_idle => if (go = '1') then if (start = '1') then nxt_state := st_start; icore_cmd := CMD_START; elsif (read = '1') then nxt_state := st_read; icore_cmd := CMD_READ; idcnt := "111"; else nxt_state := st_write; icore_cmd := CMD_WRITE; idcnt := "111"; iload := '1'; end if; end if;
when st_start => if (core_ack = '1') then if (read = '1') then nxt_state := st_read; icore_cmd := CMD_READ; idcnt := "111"; else nxt_state := st_write; icore_cmd := CMD_WRITE; idcnt := "111"; iload := '1'; end if; end if;
when st_write => if (core_ack = '1') then idcnt := dcnt -1; -- count down Data_counter icore_txd := sr(7); if (dcnt = 0) then nxt_state := st_ack; icore_cmd := CMD_READ; else ishift := '1'; -- icore_txd := sr(7); end if; end if;
when st_read => if (core_ack = '1') then idcnt := dcnt -1; -- count down Data_counter ishift := '1'; if (dcnt = 0) then nxt_state := st_ack; icore_cmd := CMD_WRITE; icore_txd := ack_in; end if; end if;
when st_ack => if (core_ack = '1') then -- generate command acknowledge signal ihost_ack := '1';
-- Perform an additional shift, needed for 'read' (store last received bit in shift register) ishift := '1';
-- check for stop; Should a STOP command be generated ? if (stop = '1') then nxt_state := st_stop; icore_cmd := CMD_STOP; else nxt_state := st_idle; icore_cmd := CMD_NOP; end if; end if;
when st_stop => if (core_ack = '1') then nxt_state := st_idle; icore_cmd := CMD_NOP; end if;
when others => -- illegal states nxt_state := st_idle; icore_cmd := CMD_NOP; end case;
-- generate registers if (nReset = '0') then core_cmd <= CMD_NOP; core_txd <= '0'; shift <= '0'; ld <= '0';
dcnt <= "111"; host_ack <= '0';
state <= st_idle; elsif (clk'event and clk = '1') then if (ena = '1') then state <= nxt_state;
dcnt <= idcnt; shift <= ishift; ld <= iload;
core_cmd <= icore_cmd; core_txd <= icore_txd;
host_ack <= ihost_ack; end if; end if; end process nxt_state_decoder;
end block statemachine;
end architecture structural;
-- -- -- I2C Core -- -- Translate simple commands into SCL/SDA transitions -- Each command has 5 states, A/B/C/D/idle -- -- start: SCL ~~~~~~~~~~\____ -- SDA ~~~~~~~~\______ -- x | A | B | C | D | i -- -- repstart SCL ____/~~~~\___ -- SDA __/~~~\______ -- x | A | B | C | D | i -- -- stop SCL ____/~~~~~~~~ -- SDA ==\____/~~~~~ -- x | A | B | C | D | i -- --- write SCL ____/~~~~\____ -- SDA ==X=========X= -- x | A | B | C | D | i -- --- read SCL ____/~~~~\____ -- SDA XXXX=====XXXX -- x | A | B | C | D | i --
-- Timing: Normal mode Fast mode ----------------------------------------------------------------- -- Fscl 100KHz 400KHz -- Th_scl 4.0us 0.6us High period of SCL -- Tl_scl 4.7us 1.3us Low period of SCL -- Tsu:sta 4.7us 0.6us setup time for a repeated start condition -- Tsu:sto 4.0us 0.6us setup time for a stop conditon -- Tbuf 4.7us 1.3us Bus free time between a stop and start condition --
library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_arith.all;
entity i2c_core is port ( clk : in std_logic; nReset : in std_logic;
clk_cnt : in unsigned(7 downto 0);
cmd : in std_logic_vector(2 downto 0); cmd_ack : out std_logic; busy : out std_logic;
Din : in std_logic; Dout : out std_logic;
SCL : inout std_logic; SDA : inout std_logic ); end entity i2c_core;
architecture structural of i2c_core is constant CMD_NOP : std_logic_vector(2 downto 0) := "000"; constant CMD_START : std_logic_vector(2 downto 0) := "010"; constant CMD_STOP : std_logic_vector(2 downto 0) := "011"; constant CMD_READ : std_logic_vector(2 downto 0) := "100"; constant CMD_WRITE : std_logic_vector(2 downto 0) := "101";
type cmds is (idle, start_a, start_b, start_c, start_d, stop_a, stop_b, stop_c, rd_a, rd_b, rd_c, rd_d, wr_a, wr_b, wr_c, wr_d); signal state : cmds; signal SDAo, SCLo : std_logic; signal txd : std_logic; signal clk_en, slave_wait :std_logic; signal cnt : unsigned(7 downto 0) := clk_cnt; begin -- whenever the slave is not ready it can delay the cycle by pulling SCL low slave_wait <= '1' when ((SCLo = '1') and (SCL = '0')) else '0';
-- generate clk enable signal gen_clken: process(clk, nReset) begin if (nReset = '0') then cnt <= (others => '0'); clk_en <= '1'; --'0'; elsif (clk'event and clk = '1') then if (cnt = 0) then clk_en <= '1'; cnt <= clk_cnt; else if (slave_wait = '0') then cnt <= cnt -1; end if; clk_en <= '0'; end if; end if; end process gen_clken;
-- generate statemachine nxt_state_decoder : process (clk, nReset, state, cmd, SDA) variable nxt_state : cmds; variable icmd_ack, ibusy, store_sda : std_logic; variable itxd : std_logic; begin
nxt_state := state;
icmd_ack := '0'; -- default no acknowledge ibusy := '1'; -- default busy
store_sda := '0';
itxd := txd;
case (state) is -- idle when idle => case cmd is when CMD_START => nxt_state := start_a; icmd_ack := '1'; -- command completed
when CMD_STOP => nxt_state := stop_a; icmd_ack := '1'; -- command completed
when CMD_WRITE => nxt_state := wr_a; icmd_ack := '1'; -- command completed itxd := Din;
when CMD_READ => nxt_state := rd_a; icmd_ack := '1'; -- command completed
when others => nxt_state := idle; -- don't acknowledge NOP command icmd_ack := '1'; -- command completed ibusy := '0'; end case;
-- start when start_a => nxt_state := start_b;
when start_b => nxt_state := start_c;
when start_c => nxt_state := start_d;
when start_d => nxt_state := idle; ibusy := '0'; -- not busy when idle
-- stop when stop_a => nxt_state := stop_b;
when stop_b => nxt_state := stop_c;
when stop_c => -- nxt_state := stop_d;
-- when stop_d => nxt_state := idle; ibusy := '0'; -- not busy when idle
-- read when rd_a => nxt_state := rd_b;
when rd_b => nxt_state := rd_c;
when rd_c => nxt_state := rd_d; store_sda := '1';
when rd_d => nxt_state := idle; ibusy := '0'; -- not busy when idle
-- write when wr_a => nxt_state := wr_b;
when wr_b => nxt_state := wr_c;
when wr_c => nxt_state := wr_d;
when wr_d => nxt_state := idle; ibusy := '0'; -- not busy when idle
end case;
-- generate regs if (nReset = '0') then state <= idle; cmd_ack <= '0'; busy <= '0'; txd <= '0'; Dout <= '0'; elsif (clk'event and clk = '1') then if (clk_en = '1') then state <= nxt_state; busy <= ibusy;
txd <= itxd; if (store_sda = '1') then Dout <= SDA; end if; end if;
cmd_ack <= icmd_ack and clk_en; end if; end process nxt_state_decoder;
-- -- convert states to SCL and SDA signals -- output_decoder: process (clk, nReset, state) variable iscl, isda : std_logic; begin case (state) is when idle => iscl := SCLo; -- keep SCL in same state isda := SDA; -- keep SDA in same state
-- start when start_a => iscl := SCLo; -- keep SCL in same state (for repeated start) isda := '1'; -- set SDA high
when start_b => iscl := '1'; -- set SCL high isda := '1'; -- keep SDA high
when start_c => iscl := '1'; -- keep SCL high isda := '0'; -- sel SDA low
when start_d => iscl := '0'; -- set SCL low isda := '0'; -- keep SDA low
-- stop when stop_a => iscl := '0'; -- keep SCL disabled isda := '0'; -- set SDA low
when stop_b => iscl := '1'; -- set SCL high isda := '0'; -- keep SDA low
when stop_c => iscl := '1'; -- keep SCL high isda := '1'; -- set SDA high
-- write when wr_a => iscl := '0'; -- keep SCL low -- isda := txd; -- set SDA isda := Din;
when wr_b => iscl := '1'; -- set SCL high -- isda := txd; -- set SDA isda := Din;
when wr_c => iscl := '1'; -- keep SCL high -- isda := txd; -- set SDA isda := Din;
when wr_d => iscl := '0'; -- set SCL low -- isda := txd; -- set SDA isda := Din;
-- read when rd_a => iscl := '0'; -- keep SCL low isda := '1'; -- tri-state SDA
when rd_b => iscl := '1'; -- set SCL high isda := '1'; -- tri-state SDA
when rd_c => iscl := '1'; -- keep SCL high isda := '1'; -- tri-state SDA
when rd_d => iscl := '0'; -- set SCL low isda := '1'; -- tri-state SDA end case;
-- generate registers if (nReset = '0') then SCLo <= '1'; SDAo <= '1'; elsif (clk'event and clk = '1') then if (clk_en = '1') then SCLo <= iscl; SDAo <= isda; end if; end if; end process output_decoder;
SCL <= '0' when (SCLo = '0') else 'Z'; -- since SCL is externally pulled-up convert a '1' to a 'Z'(tri-state) SDA <= '0' when (SDAo = '0') else 'Z'; -- since SDA is externally pulled-up convert a '1' to a 'Z'(tri-state) -- SCL <= SCLo; -- SDA <= SDAo;
end architecture structural;
|