Forum Discussion

Altera_Forum's avatar
Altera_Forum
Icon for Honored Contributor rankHonored Contributor
11 years ago

I2C Protocol help

Hi,

I am new to VHDL. I am trying to implement an I2C master for practice (synthesizable code). At the moment , I have code written to send the start bit and then 8 data bits. The SDA should change only when the SCL is low and read when SCL is high. Right now my code changes the SDA on the same rising edge time as SCL which is more than likely incorrect. How would I modify my code below to change the SDA after the SCL goes low, preferably in the middle of the pulse? Adding delays such as using WAIT and AFTER are only for non-synthesizable code correct?

Also, how would I let the SDA line float to wait for acknowledgement from a slave, i2c_sda<= 'X' ?

Any help is much appreciated. Thanks in advance. .


Library IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
use ieee.numeric_std.all;
entity I2C is
  port (in_clk : in std_logic; 
        i2c_scl : out std_logic;
        i2c_sda : out std_logic
        );
end I2C;
-------------------------------------------------------------------------
        
architecture behaviour of I2C is
  
  signal count: integer:= 0;
  signal data_count: integer:= 0;
  signal out_clk : std_logic:= '1';  -- connected to scl
  signal start_scl : std_logic := '0'; -- signal to start sending data if '1'
  
begin
i2c_scl<=out_clk; 
    process(in_clk)
    begin
        if (in_clk'event and in_clk = '1') then
            count<= count + 1;
            
            if (count = 0) then
                i2c_sda<= '1';
            elsif (count = 1) then
                i2c_sda<= '0';
            elsif (count = 2) then
                out_clk<= '0';  -- start condition satisfied
                start_scl<= '1';
            elsif ( count > 2 and count < 10 ) then
                out_clk<= not(out_clk); -- start scl clock
            else
                out_clk<= '1';
            end if;
              
            if (start_scl = '1') then
                if (out_clk = '0') then
                    data_count<= data_count +1;
                    case data_count is
                        when 0 =>
                             i2c_sda<= '1';
                        when 1 =>
                             i2c_sda<= '0';
                        when 2 =>
                             i2c_sda<= '1';
                        when 3 =>
                             i2c_sda<= '0';
                        when 4 =>
                             i2c_sda<= '1';
                        when 5 =>
                             i2c_sda<= '0';
                        when 6 =>
                             i2c_sda<= '1';
                        when 7 =>
                             i2c_sda<= '0';
                        when 8 =>
                             i2c_sda<= '1';
                        when others =>
                             null;
                    end case;
            end if;
        end if;
    end if;
    end process;
    
end behaviour;
------------------------------------------------------------------------

8 Replies

  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    Hi,

    There's an I2C controller on the AlteraWiki

    http://www.alterawiki.com/wiki/i2c_%28opencores%29

    This is the core from OpenCores.org, with a Qsys _hw.tcl wrapper. I have not used this core.

    I noticed that Altera has used this IP in their DisplayPort code, eg., look in your Quartus install

    14.0/ip/altera/altera_dp/hw_demo/altera_avalon_i2c/

    There is a _hw.tcl file in there, but the component will not show up in Qsys since it has INTERNAL set to true.

    I'm pretty sure you could copy that folder to ip/ in a project, change INTERNAL to false, and Qsys will show it.

    Cheers,

    Dave
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    Hi,

    Thanks for reply. I have checked that out but I see only Verilog code there. I don't wish to copy any code anyways. I wish to try and create an I2C master from scratch for VHDL learning purposes.

    Cheers,

    Paul
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    There's nothing against writing an I2C master interface from the scratch, but to do so, you would start with studying the NXP I2C specification, particularly the required signal timing. Then implement hierarchical state machines for bit timing, byte read/write, overall control.

    SDA must be a bidirectional inout port, SCL can be output only for simple masters that don't support clock stretching. To tristate SDA, you'll assign 'Z' state to it.
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    I understand the I2C specification. By simulating my code , it looks like I am on the right track but how can I add a delay that can be synthesizable so that the SDA changes in the middle of a low SCL and not on the edge? Cheers

  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    I'm not sure this is the best way to do it, but you could try using a base clock to control the data signals and then generate the SCL internally instead of using the same clock. This way you would have total control on the rising and falling edges of both SDA and SCL

    Best regards,

    Thiago
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    How would I generate a different clock at a different phase if I have only one crystal on the board. Would anyone have a code example? Like I say, I am new to VHDL. Thanks

  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    You shouldn't need a different phase clock. The clock from the crystal is the reference and will be used to drive the SDA and SCL signals. In I2C the SCL isn't a free running clock and you can control its level based on your reference clock.

    For example, let's suppose that the crystal clock is 200KHz. If you toggle the SCL signal (scl <= not scl) at every rising edge of the crystal clock, the SCL would be a 100KHz clock. Since your module is using the 200KHz as reference it is now possible to control the SDA signal between each edge of the SCL.
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    Realistically, a typical FPGA system clock frequency (crystal or PLL generated clock) will be rather 20 or 50 MHz than 200 kHz. Respectively, the bit level state machine won't advance every clock cycle but every Nth cycle after expiration of a delay counter.