一、 概述
我们在上一章节根据IIC协议实现了模拟IIC主机的读时序,这个读时序其实是上电后电脑主机发起的IIC读时序,是为了读取我们板卡上的EDID信息,主机再根据这个EDID信息来输出对应的视频流。本次章节我们来实现FPGA端的IIC读时序的应答过程。接下来使用FPGA模拟EEPROM编写读应答的代码,回应的数据就为之前储存在ROM中的EDID数据。
二、 发送EDID数据模块

在设计状态图之前,先来了解一下几个关键位置的时序:
主机IIC发来的开始与结束标志:
当SCL为高电平时,SDA由高变低表示开始信号。SCL为高电平时,SDA由低变高为停止信号。scl_shift把SCL的电平状态从低位往高位寄存,scl_shift为1111时,代表SCL为高。sad_shift把SDA的电平状态从低位往高位寄存,寄存器sda_shift为1100时表示SDA由高变低,产生开始标志。当寄存器sda_shift为0011时表示SDA由低变高,产生结束标志。


规定SDA在SCL为低时改变状态。根据iic协议的时序规范,SCL低电平时间最少4.7us,SCL高电平时间最少4.0us。我们的系统时钟每个周期20ns,最少要200个周期SCL线才会反转。先寻找SCL的上升沿或下降沿,再用sclk_count计数,计数到128时保持数值,在下一次的SCL电平改变后重新计数,sclk_count计数到128时做一次标志信号scl_sync。无论SCL为高还是低,都会有scl_sync标志,SCL为高时为scl_h_sync,SCL为低时为scl_l_sync。这样可以尽量保证SDA数据改变时正处在SCL低电平的中心位置。
怎样找到sclk_count为128的时刻呢,我们可以添加一个寄存器scl_sync_shift,把sclk_count的最高位移位到scl_sync_shift中,这样当sclk_count计数到128时就会把最高位的1移位进去,scl_sync_shift为01时就表示sclk_count的最高位为1了,也就表明他的值为128。

有了这些关键位置的时序后,就可以往下设计了,下图4为EEPROM往主机发送数据的状态机:

黄色模块为空闲状态,start_sign为产生开始标志后的记号,开始标志产生后,它保持为1,当SCL拉低后,在scl_l_sync =1处状态跳转到ALAVE_IDENTIFY。
ALAVE_IDENTIFY状态时EEPROM会判断IIC传来的器件地址是不是自己的地址,如果是则与IIC通信,如果不是则忽略发来的数据,返回到空闲状态。Check_en为核对地址的使能,当使能Check_en有效时,Sda_byte上的数据就是主机发来的器件地址,判断IIC主机发来的数据是否为自己的器件地址,因为最低位为读写位,所以判断条件为器件地址的高7位。再给对方发送ACK应答,这是状态跳转到SEND_ACK。
SEND_ACK状态时给IIC发送ACK应答,ack_end在应答后的第一个SCL低电平时产生,read_write为判断读或写,如过读写位为1,则表示IIC想要从EEPROM中读取数据,如果读写位为0,则代表写,接下来状态跳转到REC_BYTEADDR。
REC_BYTEADDR状态为IIC发送的EEPROM的寄存器起始地址,表示准备往EEPROM的哪个位置写,在这里因为IIC发送的是顺序读取的时序,所以这只是一次伪写,不真正往里写入数据,伪写的目的是接下来的读操作打算从哪个地址开始读取数据,这里需要记一下IIC发来的寄存器起始地址,后边要用到。Rec_end只在REC_BYTEADDR状态时产生,代表当前状态的结束标志,接收完8个字节数据后,状态跳转到SEND_BYTEADDR_ACK。
SEND_BYTEADDR_ACK状态为发送寄存器地址的ACK响应,发送完后返回到IDLE状态,准备接收重开始后IIC发来的读指令。
IDLE_STATE状态时主机会重新发送开始标志,发送器件地址,从机发送ACK应答,这一次主机发来的器件地址后的读写位为1,主机开始从EEPROM读取数据,读取的数据是从ROM模块中输出的,根据read_addr的值来输出data_out,data_out为hex文件中的数据,read_addr为0时输出hex文件中的第0行的值,根据read_addr的递增输出总共128个data_out数据,这128个data_out数据就是我们之前编写的edid编码。Read_addr为寄存器起始地址+data_count,每发送一个字节的数据(byte_end=1),主机都会返回一次ACK应答,应答有效(ack_ok=1)时继续发送数据,发送完最128个字节数据后,主机会产生不应答信号和停止信号,停止传输。
时序说明:

图5 为接收到IIC传来的EEPROM器件地址关键时序,产生开始标志后,SDA线上每接收1个bit数据,scl_count+1,发送一个字节的数据后,判断sda_byte的高7位是否为EEPROM的器件地址,在scl_count为8时发送ACK应答,然后接收IIC传输的寄存器地址。

图6为接收的寄存器地址部分时序,接收到寄存地址后产生ACK应答信号,再返回IDLE_STATE状态,准备接收器件地址。

图7的作用是让大家知道rec_end标志的产生依据,和scl_count每计数到7和8的最后时刻,会产生一个byte_end和ack_end,byte表示一个字节传输结束,ack_end表示ACK应答位传输结束。

图8中主机发送的器件地址为HA1,最低位为1表示从寄存器读,所以把read_write拉高,check_en为核对器件地址的标志,核对正确后给主机发送ACK应答,应答发送后read_write为1则开始发送EDID数据。

图9为给IIC发送数据的时序,在发送数据期间,每收到一次ACK应答,data_count的值+1,发送的数据data_out来源是ROM模块,hex文件中行数为0-127,所以read_addr的值也为0-127,数据线sda_out的值为data_out_shift的最高位,地址每增加一次,数据data_out都会赋值给data_out_shift寄存器,再根据scl_l_sync来移位,移位7次正好可以把data_out的数值从高到低传输完成。

图10为传输结束的部分时序,在传输完最后一位数据后,主机不产生ACK应答,并且产生停止信号来结束传输。
最后我们还需要产生热插拔信号,上电时,把热插拔信号HDMI_RX_PHA置低,延时一秒后再置高, TX_EN信号上电后为1,这样主机就会通过HDMI的DDC通道读取数据。
三、 总结
本章节讲述了使用FPGA模拟IIC时序的读应答的内容,下一章节我们通过仿真来观察IIC读时序来读EDID数据的过程。
本文章由威三学社出品
对课程感兴趣可以私信联系