-------------------------------------------------------------------------------- -- -- FileName: i2c_master.vhd -- Dependencies: none -- Design Software: Quartus II 64-bit Version 13.1 Build 162 SJ Full Version -- -- HDL CODE IS PROVIDED "AS IS." DIGI-KEY EXPRESSLY DISCLAIMS ANY -- WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING BUT NOT -- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -- PARTICULAR PURPOSE, OR NON-INFRINGEMENT. IN NO EVENT SHALL DIGI-KEY -- BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR CONSEQUENTIAL -- DAMAGES, LOST PROFITS OR LOST DATA, HARM TO YOUR EQUIPMENT, COST OF -- PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY OR SERVICES, ANY CLAIMS -- BY THIRD PARTIES (INCLUDING BUT NOT LIMITED TO ANY DEFENSE THEREOF), -- ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, OR OTHER SIMILAR COSTS. -- -- Version History -- Version 1.0 11/01/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 -- Version 2.2 02/05/2015 Scott Larson -- Corrected small SDA glitch introduced in version 2.1 -- -------------------------------------------------------------------------------- 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 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 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 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 WITH state SELECT sda_ena_n <= data_clk_prev WHEN start, --generate start condition NOT data_clk_prev 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;