FPGA 模拟I2C 控制器

eeleader   2011-2-23 16:04 楼主
--
-- 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;

回复评论 (6)

好东西,没人顶!自己顶!

一个为理想不懈前进的人,一个永不言败人! http://shop57496282.taobao.com/ 欢迎光临网上店铺!
点赞  2011-3-26 16:54
楼主V5
点赞  2011-8-24 23:23

回复 沙发 eeleader 的帖子

楼主啊,显然你是强人啊
点赞  2011-10-2 17:05

回复 楼主 eeleader 的帖子

好东西,顶啊
点赞  2011-10-21 19:58
淘汰了!
点赞  2011-11-4 15:52
我来顶 我来顶
点赞  2011-11-4 16:09
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复