I2C主从设备Acknowledgement生成机制及VHDL/Verilog检测方法咨询
I2C协议应答机制与硬件实现解答
1. I2C协议中ACK的生成逻辑
I2C总线的每一笔数据传输都是9个时钟周期为一组:前8个周期由发送方传输1字节数据,第9个周期用于接收方返回应答信号。
不管是主设备还是从设备,只要作为当前数据的发送方,都会在第8个时钟周期的下降沿之后主动释放SDA总线,把SDA的控制权交给接收方。接收方如果要返回ACK(应答),就会在第9个时钟的SCL高电平期间拉低SDA;如果没有拉低SDA,SDA保持高电平就代表返回NACK(非应答)。
2. 不同收发场景下的ACK/NACK生成规则
- 主设备向从设备发送数据的场景
主设备每发送完1字节数据,就会释放SDA等待从设备应答:- 从设备匹配到地址、接收缓冲区可用、数据校验无误的情况下,会返回ACK,主设备收到ACK后可以继续发送下一字节
- 从设备地址不匹配、接收缓冲区满、数据校验出错的情况下,会返回NACK,主设备收到NACK后可以选择终止传输或者发起重传
- 主设备从从设备接收数据的场景
从设备每发送完1字节数据,就会释放SDA等待主设备应答:- 主设备还需要继续接收下一字节时,返回ACK,从设备收到ACK后会继续发送下一字节
- 主设备已经接收完所有需要的数据,要终止传输时,返回NACK,从设备收到NACK后会释放SDA总线,等待主设备发起停止条件
3. Verilog/VHDL开发中的最优检测实现方法
核心设计原则
所有ACK/NACK的检测都要符合I2C的时序规范,核心要规避两个问题:异步信号的亚稳态风险、SDA跳变沿的采样错误。
通用实现步骤
- 第一步:异步信号同步:外部输入的SCL、SDA信号都先经过两级寄存器同步,消除跨时钟域的亚稳态风险,同步后的信号再参与逻辑运算
- 第二步:采样点选择:在SCL高电平的中间时刻采样SDA电平,这个时间段SDA电平最稳定,不会出现跳变,可以避免建立/保持时间违规导致的采样错误
- 第三步:状态机联动:将ACK检测逻辑挂载到传输状态机的对应分支,发送完8bit数据后跳转至
ACK_DETECT状态,在该状态下完成采样判断即可
代码示例
Verilog实现片段
// 两级同步逻辑 reg scl_sync1, scl_sync2; reg sda_sync1, sda_sync2; always @(posedge sys_clk or negedge rst_n) begin if(!rst_n) begin {scl_sync2, scl_sync1} <= 2'b11; {sda_sync2, sda_sync1} <= 2'b11; end else begin scl_sync1 <= scl_pin; scl_sync2 <= scl_sync1; sda_sync1 <= sda_pin; sda_sync2 <= sda_sync1; end end // ACK采样逻辑(scl_high_mid_pulse为SCL高电平中间的脉冲标志,由时钟分频计数生成) reg ack_valid; reg is_ack; // 1为收到ACK,0为收到NACK always @(posedge sys_clk or negedge rst_n) begin if(!rst_n) begin ack_valid <= 1'b0; is_ack <= 1'b0; end else if(state == ACK_DETECT && scl_high_mid_pulse) begin ack_valid <= 1'b1; is_ack <= ~sda_sync2; // SDA低电平对应ACK end else begin ack_valid <= 1'b0; end end
VHDL实现片段
-- 两级同步逻辑 process(sys_clk, rst_n) begin if rst_n = '0' then scl_sync <= "11"; sda_sync <= "11"; elsif rising_edge(sys_clk) then scl_sync <= scl_sync(0) & scl_pin; sda_sync <= sda_sync(0) & sda_pin; end if; end process; -- ACK采样逻辑 process(sys_clk, rst_n) begin if rst_n = '0' then ack_valid <= '0'; is_ack <= '0'; elsif rising_edge(sys_clk) then ack_valid <= '0'; if state = ACK_DETECT and scl_high_mid_pulse = '1' then ack_valid <= '1'; is_ack <= not sda_sync(1); end if; end if; end process;
注意事项
不要在SCL的上升沿/下降沿采样SDA,很容易采到SDA跳变的不稳定电平,导致误判;如果需要支持多速率I2C(标准模式、快速模式、高速模式),可以通过调整分频计数的方式动态调整SCL中间采样点的位置,适配不同速率的时序要求。
内容的提问来源于stack exchange,提问作者Ddraig77
相关产品推荐
相关产品推荐

