You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FPGA上VHDL实现I2C主机:STOP条件生成及时序问题

关于I2C主机STOP条件生成及时序同步的问题

问题描述

我在FPGA上使用VHDL实现I2C主机,目前遇到STOP条件无法正常生成的问题——传输过程中其他环节均正常,但总线上始终没有出现STOP条件。

根据测试所用DS1307 RTC从设备的数据手册,STOP条件的定义为:当SCL处于高电平时,SDA线从低电平跳变到高电平。我有以下疑问:

  • 是否需要中断正常周期性SCL波形,手动强制SCL为高后再释放SDA?还是仅在SCL上升沿拉高SDA即可?
  • 如何确保整个状态机与SCL生成同步?
  • 如何保证SDA和SCL信号在状态转换间有足够的建立/保持时间?
  • 是否有通用的时序规范可供遵循?

参考信息:SCL频率为100kHz

当前实现代码

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity i2c is
    generic (
        scl_clk : integer := 500  -- SCL half-period (in system clocks)
    );
    port (
        clk    : in  std_logic; --100 MHz clk
        SDA    : inout std_logic; --data
        SCL    : out std_logic; --synch clk
        button : in  std_logic; --button
        ack_ok : out std_logic  --send ack after reading
    );
end entity;

architecture execute of i2c is
    --------------------------------------------------------------------
    -- Internal signals
    --------------------------------------------------------------------
    signal shift_reg : unsigned(7 downto 0) := (others => '0'); --for serial transmission through SDA
    signal bit_cnt   : integer range 0 to 7 := 7; --bit counter
    signal scl_count2 : integer := 0; 
    signal scl_count : integer := 0; --synch clk counter
    signal scl_trig  : std_logic := '0'; --trig signal 
    signal scl_en    : std_logic := '0'; -- internal signal for synch clk

    signal sda_bus : std_logic := '1';  -- 0 = drive SDA low, Z = release
    signal scl_bus : std_logic := '1';  -- 0 = drive SCL low, Z = release

     signal busy     : std_logic := '0'; 

    type byte_array is array (0 to 31) of std_logic_vector(7 downto 0); -- byte array for transmissions
    signal tx_buffer : byte_array;
    signal tx_len    : integer range 0 to 31 := 0; --how many bits we'll send
    signal tx_index  : integer range 0 to 31 := 0; --counter till we have gone through all bytes


-- states
    type statetype is (
        IDLE, START1, START2, SEND_BITS,
        NEXT_BYTE, READY_ACK, ACK_HIGH, ACK_CONFIRM,
        STOP1, STOP2
    );
    signal state : statetype := IDLE;
begin
    --------------------------------------------------------------------
    -- Open-drain outputs
    --------------------------------------------------------------------
    SDA <= '0' when sda_bus = '0' else 'Z';
    SCL <= '0' when scl_bus = '0' else 'Z';

    --------------------------------------------------------------------
    -- SCL clock generator
    --------------------------------------------------------------------
    clock_div : process(clk)
    begin
        if rising_edge(clk) then
            if scl_count = (scl_clk - 1) then
                scl_count <= 0;
                scl_en    <= not scl_en;
            else
                scl_count <= scl_count + 1;
            end if;
        end if;
    end process;

process(clk)
    begin
        if rising_edge(clk) then
            if scl_count2 = (scl_clk - 1) then
                scl_count2 <= 0;
                scl_trig  <= '1';
            else
                scl_count2 <= scl_count2 + 1;
                scl_trig  <= '0';
            end if;
        end if;
    end process;

    --------------------------------------------------------------------
    -- I2C state machine
    --------------------------------------------------------------------
    process(clk)
    begin
        if rising_edge(clk) then
            -- Trigger transmission
            if button = '1' and busy = '0' then
                bit_cnt         <= 7;
                tx_buffer(0)    <= "01111000";  -- 0x3C << 1 (OLED address)
                tx_buffer(1)    <= "10101111";  -- control byte: command
                tx_buffer(2)    <= "00000000";
                tx_buffer(3)    <= "11111111";
                tx_len          <= 2;
                tx_index        <= 0;
                shift_reg       <= unsigned(tx_buffer(0));
                state           <= START1;
            end if;

            -- State machine
            case state is
                when IDLE =>
                    scl_bus <= '1';
                    sda_bus <= '1';
                    busy    <= '0';

                when START1 =>
                    busy <= '1';
                    if scl_trig = '1' then
                        sda_bus <= '0';  -- Start: SDA falls while SCL is HIGH
                        state   <= START2;
                    end if;

                when START2 =>
                    if scl_trig = '1' then
                        scl_bus <= scl_en;
                        state   <= SEND_BITS;
                    end if;

                when SEND_BITS =>
                    scl_bus <= scl_en;
                    if scl_en = '0' and scl_trig = '1' then
                        sda_bus <= shift_reg(bit_cnt);
                    end if;

                   
                    if scl_trig = '1' and scl_en = '1' then
                        if bit_cnt = 0 then
                            state <= READY_ACK;
                        else
                            bit_cnt <= bit_cnt - 1;
                        end if;
                    end if;

                when READY_ACK =>
                    scl_bus <= scl_en;
                    if scl_trig = '1' then
                        state <= ACK_HIGH;
                    end if;

                when ACK_HIGH =>
                    scl_bus <= scl_en;
                    if scl_trig = '1' then
                        state <= ACK_CONFIRM;
                    end if;

                when ACK_CONFIRM =>
                    scl_bus <= scl_en;
                    if scl_bus = '1' then
                        ack_ok <= not SDA;  
                        if scl_trig = '1' then
                            if tx_index = tx_len then
                                state <= STOP1;
                            else
                                state <= NEXT_BYTE;
                            end if;
                        end if;
                    end if;

                when NEXT_BYTE =>
                    if scl_trig = '1' then
                        tx_index   <= tx_index + 1;
                        bit_cnt    <= 7;
                        shift_reg  <= unsigned(tx_buffer(tx_index + 1));
                        state      <= SEND_BITS;
                    end if;

                when STOP1 =>
                    if scl_trig = '1' then
                        scl_bus <= '1';
                        sda_bus <= '0';
                        state   <= STOP2;
                    end if;

                when STOP2 =>
                    sda_bus <= '1';
                    if scl_trig = '1' then
                        busy  <= '0';
                        state <= IDLE;
                    end if;

                    when others =>
                    state <= IDLE;
            end case;
        end if;
    end process;

end architecture;

问题分析与解决方案

1. STOP条件生成的核心问题

你当前的STOP状态逻辑存在时序错误:进入STOP2时直接拉高SDA,没有给SCL高电平留足够的稳定窗口,导致总线无法捕捉到SDA的跳变。

正确做法:必须中断正常SCL周期,手动强制SCL保持高电平,再完成SDA的拉高动作,且要保证SCL高电平持续足够时间,让从设备检测到STOP条件。不能仅在SCL上升沿拉高SDA——正常SCL高电平周期只有5us(100kHz下),跳变需要发生在SCL高电平的稳定阶段,而非上升沿瞬间。

修改后的STOP状态示例:

when STOP1 =>
    scl_bus <= '1'; -- 强制SCL保持高电平
    sda_bus <= '0'; -- 确保SDA处于低电平
    if scl_count = (scl_clk - 1) then -- 等待一个完整的SCL高电平周期(5us)
        state <= STOP2;
    end if;

when STOP2 =>
    scl_bus <= '1'; -- 继续保持SCL高电平
    sda_bus <= '1'; -- SDA从低跳变到高,生成STOP条件
    if scl_count = (scl_clk - 1) then -- 保持STOP条件足够时间
        busy <= '0';
        state <= IDLE;
    end if;

2. 状态机与SCL同步问题

你当前用了两个独立计数器scl_count和scl_count2生成scl_en和scl_trig,容易出现时序错位。应该复用同一个计数器,确保状态机触发信号与SCL时钟完全同步:

-- 合并后的SCL时钟生成和触发信号
clock_div : process(clk)
begin
    if rising_edge(clk) then
        if scl_count = (scl_clk - 1) then
            scl_count <= 0;
            scl_en <= not scl_en;
            scl_trig <= '1'; -- 每次计数器溢出时触发状态转换
        else
            scl_count <= scl_count + 1;
            scl_trig <= '0';
        end if;
    end if;
end process;

3. 建立/保持时间保证

  • 建立时间:发送数据时,要确保SDA在SCL上升沿到来前至少250ns(I2C标准模式t_SU_DAT)稳定。你当前在SCL低电平期间更新SDA的逻辑是正确的,符合规范。
  • 保持时间:SDA要在SCL下降沿后至少保持0ns(t_HD_DAT),实际实现中可留少量余量,避免信号提前变化。

4. 通用I2C时序规范(100kHz标准模式)

  • SCL高电平最小时间:t_HIGH = 4.7us
  • SCL低电平最小时间:t_LOW = 4.7us
  • START条件:SDA下降沿到SCL下降沿最小时间t_SU_STA = 4.7us
  • STOP条件:SDA上升沿到SCL下一个上升沿最小时间t_SU_STO = 4.7us
  • 数据建立时间:t_SU_DAT = 250ns,保持时间:t_HD_DAT = 0ns

内容的提问来源于stack exchange,提问作者Gaming x Juan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.11 21:44:49