IIC协议
首先是比较好的博客与资料:
- https://www.cnblogs.com/xiaomeige/p/6509414.html
- https://www.cnblogs.com/microxiami/p/8527464.html
- I2C总线协议(中文版).pdf
https://download.csdn.net/download/weixin_43499278/12275402 - I2C协议的实现(判断SCL).pdf
https://download.csdn.net/download/weixin_43499278/12275402 - 夏宇闻《Verilog数字系统设计教程》——第16章
本次实践是I2C总线协议的硬件实现,其中本次实践只能用于<I2C单字节的读写功能,不支持主机仲裁>,这些功能可以后续加入。本次实践I2C总线设计可具体分为以下部分(下述按此框架讲述):
- 产生SCL
- 构建输入/输出性SDA
- 确定I2C总线协议的基础状态,以设计状态机跳转方式
- 编写I2C单字节读/写状态机,以控制SDA产生相应时序
其核心思想:利用状态机的跳转来控制SDA、SCL产生符合I2C协议的时序,属于时序电路控制组合电路输出。
1、产生SCL
从参考资料2中,可知I2C总线工作的速度分为标准模式(100kbit/s)、快速模式(400kbit/s),因此SCL的速度受到限制。本次实践中,我采用了比I2C工作速度高2倍的时钟作为时钟输入,并且经过时钟二分频后,即可得到满足要求的SCL,具体实现如下(相当于时钟分频器):
/*产生SCL时钟:二分频*/
//下降沿时钟跳转:SCL中点检测SDA的电平
always@(negedge clk or negedge rst_n)
begin
if(!rst_n)
SCL <= 1;
else
SCL <= ~SCL;
end
重点: 采用clk下降沿进行时钟分频,由此后面模块使用clk上升沿判断时,可达到在SCL电平中点检测SDA的电平状态。
2.构建输入/输出性SDA
SDA是一个可输入/输出类型引脚,因此使用三态门的结构进行构建,同时硬件外部是将SDA线上拉的,所以具体实现方式如下:
/*组合逻辑控制产生电路*/
assign SDA = (Link_SDA)? SDA_reg:1'bz;
3.确定IIC总线协议的基础状态,设计状态机跳转方式
由参考资料1,可以得到I2C总线协议的基础状态为:起始信号、传输数据(控制字节/数据字节)、应答、停止信号。
以下是I2C单字节的读写操作顺序:
-
I2C写操作:
起始信号->传输写控制字节->从机应答->传输数据字节->从机应答->停止信号
-
I2C读操作:
起始信号->传输写控制字节->从机应答->传输器件存储字节->从机应答->起始信号->传输读控制字节->读数据->主机非应答->停止信号
由以上的读写顺序,结合I2C总线协议的基础状态,可得到如下的状态机跳转图:
其中ack状态内包含了判断I2C总线协议读/写的跳转,为此状态机的要点。
4.编写IIC单字节读/写状态机,以控制SDA产生相应时序
此处的状态机,采用三段式状态机书写。其中重点是:
- 在trans_data状态中,判断读写,并将wr_flag/rd_flag赋值,用于ack状态内的判断和状态机的跳转判断。
- 应I2C的时序要求,在例如起始信号后必须传数据等,代码注释中有标注。
- 写/读数据都只能在SCL为低电平时,才能够变化。
- 使用任务task完成起始信号、串转并、并转串、停止信号。注意:使用task时须将从状态机复位。
<task中串转并、并转串不懂得可以参考我之前的博客>
具体硬件代码如下:
module I2C_Timing(
input clk, //时钟、复位
input rst_n,
input I2C_en, //I2C使能
input wr_en, //写使能
input rd_en, //读使能
inout SDA,
output reg SCL,
input [7:0] ctrl_in, //I2C控制字节输入
input [7:0] data_in, //I2C数据字节输入
output reg done, //测试信号输出
output reg [7:0] data_out //I2C数据输出
);
/*信号及寄存器定义*/
reg Link_SDA; //Link_SDA=1时,SDA为输出;Link_SDA=0时,SDA为输入
reg SDA_reg; //I2C的SDA输出寄存器
reg Finish; //从状态机完成标志位
reg wr_falg; //写标志位
reg [1:0]rd_falg; //读标志位
reg [7:0] write_buf; //数据写入缓冲寄存器
reg [7:0] read_buf; //数据读出缓冲寄存器
reg [4:0] current_state; //主状态机当前状态寄存器
reg [4:0]