------------------------------------------------------------------------------------------------------------------ -- -- Projekt I2C SLAVE -- mit Anschluss an existierendes Design -- -- Autor Jörg Betz -- -- Datum 13.09.2005 -- -- -- Info *** I2C-Slave-Funktion *** -- -- 1. )Eigenschaften: -- - frei waehlbare Slave-Adresse -- - schreib- und lesbar -- -- 2.) Bedeutung der Signale: -- -- clk : Systemtakt, sollte ausreichend groesser als I2C-Freq sein -- reset : Hi-aktiv, asynchroner Reset -- -- scl : I2C-Clock, nur Eingang -- sda : I2C-Daten, wird fuer Acknowledge und Datentransfer bidirektional benutzt -- slv_adr : Slave-Adresse, frei konfigurierbar, 0 ... 127 -- : kann auch im laufenden Betrieb geaendert werden -- -- tx_data : Datenbytes, die zum Master gesendet werden sollen -- tx_wr : Write-Strobe, damit wird ein Byte eingeschrieben, -- wenn tx_empty aktiv ist -- tx_empty : aktiv, wenn der TX-buffer leer ist -- -- rx_data : Datenbytes, die vom Master empfangen wurden -- rx_vld : aktiv, wenn gueltiges Byte im RX-Buffer -- rx_ack : Signal sollte gesetzt werden, wenn ein Byte aus dem -- RX-Buffer gelesen wurde, damit wird rx_vld inaktiv -- und der RX-Buffer ist wieder frei -- -- busy : wird gesetzt, wenn der Slave einen Transfer beginnt, -- am Ende des Transfers entsprechend zurueckgesetzt -- busy_write : wird gesetzt, wenn der Slave Daten vom Master empfängt -- -- rd_n_wr_q : zeigt die Richtung des letzten Transfers an -- -- 3.) Das Einlesen von SDA kann (1 .. 16)+3 Clocks vor dem Erkennen der fallenden Flanke von -- SCL erfolgen und wird mit SDA_DELAY eingestellt -- -- -- Historie ------------------------------------------------------------------------------------------------------------------ library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_arith.all; use work.i2c_syn_pkg.all; entity i2c_slave is generic (SDA_DELAY : integer range 1 to 16 := 5); port ( clk : in std_logic; reset : in std_logic; -- I2C Takt und Daten (SDA ist open Drain) scl : in std_logic; -- sda : inout std_logic; sda_in : in std_logic; sda_out : out std_logic; -- I2C Slave-Adresse, 7 Bit slv_adr : in SLV_ADR_TYPE; -- TX-Buffer-Signale tx_data : in BYTE; tx_wr : in std_logic; tx_empty : out std_logic; -- RX-Buffer-Signale rx_data : out BYTE; rx_vld : buffer std_logic; rx_ack : in std_logic; -- Slave-Status busy : out std_logic; busy_write : out std_logic ); end; architecture behave of i2c_slave is signal rx_buf_ena_q : std_logic; signal sda_q : std_logic; signal rx_shiftreg_q : BYTE; signal tx_shiftreg_q : BYTE; -- Event-Signale signal sda_1_q : std_logic; signal sda_2_q : std_logic; signal scl_1_q : std_logic; signal scl_2_q : std_logic; signal start_cnd_q : boolean; signal stop_cnd_q : boolean; signal scl_f_q : boolean; signal sda_del_in_q : std_logic; signal tx_empty_q : boolean; signal busy_q : boolean; signal busy_write_q :boolean; begin -- SDA ist Open Drain -- sda <= 'Z' when sda_q='1' else '0'; sda_out <= sda_q; ------------------------------------------------------------------------------ -- Verzoegerung von SDA um eine feste Anzahl Clocks, -- damit SDA gelesen wird, waehrend SCL High ist -- -> kann durch FPGA-spezifische Elemente (SRG_16...) optimiert werden ------------------------------------------------------------------------------ DELAY_SDA : block signal delay_line : std_logic_vector(SDA_DELAY-1 downto 0); begin process(reset, clk) begin if reset='1' then delay_line <= (others=>'0'); elsif rising_edge(clk) then delay_line <= delay_line(SDA_DELAY-2 downto 0) & sda_2_q; -- Links schieben end if; end process; sda_del_in_q <= delay_line(delay_line'HIGH); -- MSB = Ausgang end block; ----------------------------------------------------- -- SDA und SCL einclocken, -- Events Start, Stop, Falling_Edge_Scl ----------------------------------------------------- process(reset, clk) variable sda_rising : boolean; variable sda_falling : boolean; begin if reset='1' then scl_1_q <= '1'; scl_2_q <= '1'; sda_1_q <= '1'; sda_2_q <= '1'; start_cnd_q <= false; stop_cnd_q <= false; scl_f_q <= false; elsif rising_edge(clk) then -- SDA und SCL jeweils in 2 aufeinanderfolgende FFs einschieben scl_1_q <= to_X01(scl); scl_2_q <= scl_1_q; sda_1_q <= to_X01(sda_in); sda_2_q <= sda_1_q; sda_rising := sda_1_q='1' and sda_2_q='0'; sda_falling := sda_1_q='0' and sda_2_q='1'; -- Start Condition start_cnd_q <= sda_falling and scl_1_q='1'; -- Stop Condition stop_cnd_q <= sda_rising and scl_1_q='1'; -- Falling Edge SCL scl_f_q <= scl_1_q='0' and scl_2_q ='1'; end if; end process; -- Statemachine I2C-Slave states : block type STATE_TYPE is (IDLE, WT_SCL_LO, RD_SLV_ADR, CHECK_STS_FOR_ACK, CHECK_ACK, BRANCH, MSTR_WR, MSTR_RD, WR_ACK, CHECK_STOP); signal state_q : STATE_TYPE; signal bit_cnt_q : integer range 0 to 8; signal slv_ack_q : std_logic; signal ld_tx_2_q : boolean; signal ld_tx_3_q : boolean; signal ld_tx_shiftreg_q : boolean; begin process(reset, clk) variable ld_bit_cnt : boolean; begin if reset='1' then state_q <= IDLE; rx_shiftreg_q <= (others=>'0'); tx_shiftreg_q <= (others=>'0'); rx_data <= (others=>'0'); ld_tx_shiftreg_q <= false; ld_tx_2_q <= false; ld_tx_3_q <= false; busy_q <= false; busy_write_q <= false; tx_empty_q <= true; sda_q <= '1'; slv_ack_q <= '1'; rx_buf_ena_q <= '0'; rx_vld <= '0'; bit_cnt_q <= 0; elsif rising_edge(clk) then -- Defaults fuer Variablen ld_bit_cnt := false; case state_q is when IDLE => busy_q <= false; slv_ack_q <= '1'; -- Default if stop_cnd_q then state_q <= IDLE; elsif start_cnd_q then busy_q <= true; state_q <= WT_SCL_LO; end if; when WT_SCL_LO => if stop_cnd_q then state_q <= IDLE; elsif start_cnd_q then state_q <= WT_SCL_LO; elsif scl_f_q then -- SCL geht Lo state_q <= RD_SLV_ADR; ld_bit_cnt := true; end if; when RD_SLV_ADR => if stop_cnd_q then state_q <= IDLE; elsif start_cnd_q then state_q <= WT_SCL_LO; elsif bit_cnt_q = 0 then -- Pruefen, ob Slave-Adresse stimmt if slv_adr = conv_integer(unsigned(rx_shiftreg_q(7 downto 1))) then state_q <= CHECK_STS_FOR_ACK; else -- Zurueck, falls andere Adresse state_q <= IDLE; end if; end if; when CHECK_STS_FOR_ACK => -- Pruefen, ob die benötigten Buffer frei sind -- beim Schreiben Master -> Slave : RX-Buffer -- beim Lesen Master <- Slave : TX-Buffer if stop_cnd_q then state_q <= IDLE; elsif start_cnd_q then state_q <= WT_SCL_LO; elsif (rx_shiftreg_q(0)='1' and tx_empty_q) or -- Master Read (rx_shiftreg_q(0)='0' and rx_vld='1') then -- Master Write state_q <= IDLE; -- Buffer fuer den Transfer nicht frei -- oder keine TX-Daten vorhanden else slv_ack_q <= '0'; -- Acknowledge senden state_q <= BRANCH; end if; when BRANCH => -- Verzweigen je nach Transfer Master Read oder Write if stop_cnd_q then state_q <= IDLE; slv_ack_q <= '1'; elsif start_cnd_q then state_q <= WT_SCL_LO; slv_ack_q <= '1'; elsif scl_f_q then slv_ack_q <= '1'; if rx_shiftreg_q(0) = '0' then -- Master Write state_q <= MSTR_WR; busy_write_q <= true; ld_bit_cnt := true; else state_q <= MSTR_RD; -- Master Read ld_bit_cnt := true; ld_tx_shiftreg_q <= true; end if; end if; when MSTR_WR => if stop_cnd_q then state_q <= IDLE; busy_write_q <= false; elsif start_cnd_q then state_q <= WT_SCL_LO; busy_write_q <= false; elsif bit_cnt_q = 0 then if rx_vld ='1' then -- letztes Byte nicht abgeholt state_q <= IDLE; busy_write_q <= false; else rx_buf_ena_q <= '1'; state_q <= WR_ACK; slv_ack_q <= '0'; end if; end if; when MSTR_RD => ld_tx_shiftreg_q <= false; if stop_cnd_q then state_q <= IDLE; elsif start_cnd_q then state_q <= WT_SCL_LO; elsif bit_cnt_q = 0 then state_q <= CHECK_ACK; end if; when CHECK_ACK => if stop_cnd_q then state_q <= IDLE; elsif start_cnd_q then state_q <= WT_SCL_LO; elsif scl_f_q then if sda_del_in_q = '0' then -- Ack vom Master state_q <= MSTR_RD; ld_tx_shiftreg_q <= true; ld_bit_cnt := true; else -- kein Ack vom Master, es wird geprueft, ob eine Stop-Cond folgt state_q <= CHECK_STOP; -- kein Master-Ack end if; end if; when CHECK_STOP => if stop_cnd_q then state_q <= IDLE; elsif start_cnd_q then state_q <= WT_SCL_LO; end if; when WR_ACK => rx_buf_ena_q <= '0'; if stop_cnd_q then state_q <= IDLE; busy_write_q <= false; elsif start_cnd_q then state_q <= WT_SCL_LO; busy_write_q <= false; elsif scl_f_q then slv_ack_q <= '1'; state_q <= MSTR_WR; ld_bit_cnt := true; end if; end case; -- Zaehler fuer Anzahl fallende Flanken SCL if ld_bit_cnt then bit_cnt_q <= 8; elsif bit_cnt_q > 0 and scl_f_q then bit_cnt_q <= bit_cnt_q -1; end if; -- Schieberegister fuer RX-Daten if scl_f_q then -- fallende Flanke SCL rx_shiftreg_q(7 downto 0) <= rx_shiftreg_q(6 downto 0) & sda_del_in_q; end if; -- Steuersignal fuer TX-SRG um 2 Clocks verzoegern, damit -- auf das Signal tx_empty (?) reagiert werden kann ld_tx_2_q <= ld_tx_shiftreg_q; ld_tx_3_q <= ld_tx_2_q; -- RX-Buffer schreiben (nur wenn Daten abgeholt wurden!) if rx_buf_ena_q='1' and rx_vld='0' then rx_data <= rx_shiftreg_q; rx_vld <= '1'; elsif rx_ack='1' then rx_vld <= '0'; end if; -- TX-Teil ist doppelt gepuffert, damit kann schon ein Byte geschrieben -- werden, während das vorherige noch zum Master übertragen wird -- 1. TX-Buffer vom ext. Controller/FSM schreiben if tx_wr='1' then tx_empty_q <= false; elsif ld_tx_3_q then tx_empty_q <= true; end if; -- 2. TX-Buffer : Schieberegister fuer TX-Daten -- Laden mit dem um 2 Clocks verzoegerten ld-signal if ld_tx_3_q then tx_shiftreg_q <= tx_data; elsif scl_f_q then tx_shiftreg_q(7 downto 1) <= tx_shiftreg_q(6 downto 0); -- linksschieben end if; -- Multiplexer fuer Slave-Acknowledge und Slave-Daten if state_q = MSTR_RD then -- Daten anlegen sda_q <= tx_shiftreg_q(7); else sda_q <= slv_ack_q; end if; end if; end process; end block; -- interne Signale mit Entity verbinden tx_empty <= '1' when tx_empty_q else '0'; busy <= '1' when busy_q else '0'; busy_write <= '1' when busy_write_q else '0'; end;