2.1 IIC协议的FPGA实现
2.1.2 IIC协议的FPGA实现
图2 13 IIC模块的建模图 图2 13是 IIC 储存模块的建模图,左边是顶层信号,右边则是沟通用的问答信号,写入地址 iAddr,写入数据 iData,还有读出数据 oData。Call/Done 有两位,即表示该模块有读功能还有些功能。具体内容,我们还是来看代码吧: 代码2 1 IIC代码声明
代码语言:javascript复制1. parameter FCLK = 10'd125, FHALF = 10'd62, FQUARTER = 10'd31;
2. parameter THIGH = 10'd30, TLOW = 10'd65, TR = 10'd15, TF = 10'd15;
3. parameter THD_STA = 10'd30, TSU_STA = 10'd30, TSU_STO = 10'd30;
如代码2 1 所示, FCLK 表示 400Khz 的周期, FHALF 表示 1/2 周期, FQUARTER 表示 1/4 周期。
图2 14 起始位 首先让我们先瞧瞧起始位这枚拼图。如图2 14所示,左图是起始位的理想时序,右图是起始位的物理时序。IIC 总线的起始位也就类似串口或者 PS/2 等传输协议的起始位,然而不同的是, IIC 总线的起始位是 SCL 拉高 TR TSU_STA THD_STA TF 之久,换之 SDA 则是拉高 TR THIGH 然后拉低 TF TLOW。起始位总和所用掉的时间,恰恰好有一个速率的周期。对此, Verilog 则可以这样描述,结果如下所示: 代码2 2 IIC起始位产生代码
代码语言:javascript复制1. begin
2. isQ = 1;
3. rSCL <= 1'b1;
4. if( C1 == 0 ) rSDA <= 1'b1;
5. else if( C1 == (TR THIGH) ) rSDA <= 1'b0;
6. if( C1 == (FCLK) -1) begin C1 <= 10'd0; i <= i 1'b1; end
7. else C1 <= C1 1'b1;
8. end
如代码2 2所示,第 2 行的 isQ = 1 表示设置 SDA 为输出状态(即时结果),第 3 行则表示 SCL 一直持续拉高状态,第 4~5 行表示 C1 为 0 的时候 SDA 拉高,直到 C1 为TR THIGH 才拉低 SDA。第 6~7 行表示一个步骤所逗留的时间。
图2 15 结束位 图2 15是结束位的时序图, IIC 设备的操作好坏一般都取决结束位。保险起见, SCL 与SDA 都事先拉低 1/4 周期,紧接着 SCL 会拉高 TR TSU_STO(或者 1/2 周期),最后又保持高电平 1/2 周期。反之, SDA 会拉低 1/2 周期,随之拉高 TR THIGH(或者 1/2周期)。对此, Verilog 可以这样表示,结果如代码2 3所示: 代码2 3 IIC结束位代码实现
代码语言:javascript复制1. begin
2. isQ = 1'b1;
3.
4. if( C1 == 0 ) rSCL <= 1'b0;
5. else if( C1 == FQUARTER ) rSCL <= 1'b1;
6.
7. if( C1 == 0 ) rSDA <= 1'b0;
8. else if( C1 == (FQUARTER TR TSU_STO ) ) rSDA <= 1'b1;
9.
10. if( C1 == (FQUARTER FCLK) -1 ) begin C1 <= 10'd0; i <= i 1'b1; end
11. else C1 <= C1 1'b1;
12. end
如代码2 3所示,第 2 行表示 SDA 为输出状态(即时),第 3~4 行表示 C1 为 0 拉高SCL, C1 为 1/4 周期就拉高。第 5~6 行表示, C1 为 0 拉低 SDA, C1 为 1/4 周期 TR TSU_STO 就拉高 SDA。第 7~8 行表示该步骤所逗留的时间。
图2 16 释放总线 此外,结束位还有 Bus Free Time 这个时序参数,IIC 总线在闲置的状态下 SCL 与 SDA等信号都持续高电平。主机发送结束位以示结束操作,然而主机持续拉高 SCL 信号与SDA 信号 TBUF 以示总线释放。TBUF 的有效时间从 SCL 信号与 SDA 信号拉高那一刻开始算起 根据表2 2 所示, TBUF 是 65 个时钟,结果如图 16.6 所示, SDA 信号拉高之后, SCL与 SDA 信号只要持续保持 1/2 周期(即 62 个时),基本上就能满足 TBUF。如果笔者是一位紧密控时狂人,可能无法接受这样的结果,因为满足 TBUF 少了 3 个时钟,为此代码2 3需要更动一下: 代码 2 4 IIC结束位代码修改
代码语言:javascript复制1. if( C1 == ( FQUARTER FCLK 3) -1 )
2. begin C1 <= 10'd0; i <= i 1'b1; end
3. else C1 <= C1 1'b1;
如代码 2 4所示,笔者为第 1 行写下 3 表示该步骤多逗留 3 个时钟,以致满足 TBUF。
不管对象是设备地址,数据地址,写入数据,读出数据,还是应答位,大伙都视为数据位。IIC 总线类似其他传输协议,它有时钟信号也有上升沿与下降沿。如图 16.7 所示,SCL 信号的下降沿导致设备设置(更新)数据,上升沿则是锁存(读取)数据。期间,TF TLOW 表示时钟信号的前半周期, TR THIGH 则表示后半周期。此外,为了确保数据成功打入寄存器,数据被上升沿锁存哪一刻起, TSU_DAT 还有 THD_DAT 必须得到满足。
图2 17 数据位更新有效 除此之外,为了确保数据有效被更新,也必须确保 TAA 得到满足,结果如图2 17所示。理解完毕以后,就可以开始学习,写一字节数据与读一字节数据,还有应答位。
图2 18 写一字节 IIC 总线一般都是一个字节一个字节读写数据,如图2 18 所示,那是写一字节的理想时序图,一字节数据是从最高位开始写起。对此, Verilog 可以这样描述,结果如代码2 5所示: 代码2 5 IIC 总线写一个字节
代码语言:javascript复制1. 7,8,9,10,11,12,13,14:
2. begin
3. isQ = 1'b1;
4. rSDA <= D1[14-i];
5. if( C1 == 0 ) rSCL <= 1'b0;
6. else if( C1 == (TF TLOW) ) rSCL <= 1'b1;
7. if( C1 == FCLK -1 ) begin C1 <= 10'd0; i <= i 1'b1; end
8. else C1 <= C1 1'b1;
9. end
如代码2 5 所示,第 1 行有 8 个步骤,表示写一个字节。第 3 行 isQ 为 1 表示 SDA 为输出状态。第 4 行表示从最高位开始更新 SDA 的数据位。第 5~6 行表示, C1 为 0 拉低SCL, C1 为 TF TLOW 则拉高 SCL。第 7~8 行表示该步骤逗留一个周期的时间。
图2 19 应答位 应答位是从机给予主机的回答, 0 为是,1 为否。然而,从旁观看,读取应答位也是读取一位数据位。当主机完成写入一个字节或者读取一个字节数据的时候,从机都会产生应答位。主机拉低 SCL 那刻,从机便会发送应答位,然后主机会借由上升沿读取应答位。如图2 19 所示,上升沿会产生在 TF TLOW 之后,也是 1/2 周期。对此, Verilog 可以这样表示,结果如代码2 6所示: 代码2 6 IIC应答位
代码语言:javascript复制1. begin
2. isQ = 1'b0;
3.
4. if( C1 == FHALF ) isAck <= SDA;
5.
6. if( C1 == 0 ) rSCL <= 1'b0;
7. else if( C1 == FHALF ) rSCL <= 1'b1;
8.
9. if( C1 == FCLK -1 ) begin C1 <= 10'd0; i <= i 1'b1; end
10. else C1 <= C1 1'b1;
11. end
如代码2 6所示,第 2 行表示 SDA 为输入状态。第 4~5 行表示, C1 为 0 拉低 SCL,C1 为 1/2 周期则拉高 SCL。第 3 行表示, C1 为 1/2 周期的时候读取应答位。第 6~7 行表示该步骤逗留 1 个周期的时间。
图2 20 读一字节 所谓读一字节数据就是重复读取 8 次应答位。如图2 20所示, SCL 的下降沿导致从机更新数据,然后主机在 SCL 的上升沿读取数据。此外,从机也会由高至低更新数据位。至于 Verilog 则可以这样表示,结果如代码2 7所示: 代码2 7 IIC读一字节
代码语言:javascript复制1. 19,20,21,22,23,24,25,26: // Read
2. begin
3. isQ = 1'b0;
4. if( C1 == FHALF ) D1[26-i] <= SDA;
5.
6. if( C1 == 0 ) rSCL <= 1'b0;
7. else if( C1 == FHALF ) rSCL <= 1'b1;
8.
9. if( C1 == FCLK -1 ) begin C1 <= 10'd0; i <= i 1'b1; end
10. else C1 <= C1 1'b1;
11. end
如代码2 7所示,第 1 行表示读取一字节。第 3 行表示 SDA 为输入状态,第 5~6 行表示, C1 为 0 拉低 SCL, C1 为 1/2 周期则拉高 SCL。第 4 行表示, C1 为 1/2 周期的时候读取数据,而且数据位由高至低存入 D1。第 7~8 行表示该步骤逗留一个周期的时间。
图2 21 第二次起始位 知道主机向从机读取数据的时候,它必须改变设备地址的方向,因此读操作又第二次起始位。如图2 21所示,感觉上第二次起始位也是第一次起始位,不过为了促使改变方向成功,第二次起始位相较第一次起始位的前后都拉低 1/4 周期。对此, Verilog 可以这样表示,结果如代码2 8所示: 代码2 8 IIC第二次起始位
代码语言:javascript复制1. begin
2. isQ = 1'b1;
3. if( C1 == 0 ) rSCL <= 1'b0;
4. else if( C1 == FQUARTER ) rSCL <= 1'b1;
5. else if( C1 == (FQUARTER TR TSU_STA THD_STA TF) ) rSCL <= 1'b0;
6.
7. if( C1 == 0 ) rSDA <= 1'b0;
8. else if( C1 == FQUARTER ) rSDA <= 1'b1;
9. else if( C1 == ( FQUARTER TR THIGH) ) rSDA <= 1'b0;
10.
11. if( C1 == (FQUARTER FCLK FQUARTER) -1 ) begin C1 <= 10'd0; i <= i 1'b1; end
12. else C1 <= C1 1'b1;
13. end
如代码2 8 所示,第 2 行表示 SDA 为输出状态。第 3~5 行表示, C1 为 0 拉低 SCL,C1 为 1/4 周期拉高 SCL, C1 为 1/4 周期 TR TSU_STA THD_STA TF 便拉低SCL。第 7~9 行表示, C1 为 0 拉低 SDA, C1 为 1/4 周期拉高 SDA, C1 为 1/4 周期 TR THIGH 便拉低 SDA。第 11~12 行表示该步骤停留一个周期的时间。 接下来是仿真验证,结果如下:
图 2 22 IIC总线仿真时序图 结合上述仿真波形图和程序可以看出: 起始位:SCLK为高电平时,SDAT由高到低,指示IIC总线传输数据的开始; 之后,传送一个字节的数据,即4A,为从机的地址,随后,跟了一个高电平,为应答位; 之后,传送一个字节的数据,即01,为从机地址的子地址,随后,跟了一个高电平,为应答位; 之后,传送一个字节的数据,即08,为上面子地址寄存器配置的数据,随后,跟了一个高电平,为应答位; 最后,为停止位,SCLK为高电平时,SDAT由低到高,指示该次IIC总线传输数据的结束。 由仿真结果可知,当传送完一个字节后,SDAT为一个脉冲的高电平,而不是从器件先将SDAT拉低再拉高,这样也是可以的。