关于IIC通信用一个具体实例来进行说明(含设计代码)
几个重要的信号
空闲状态:SDA,SCL均处于高电平状态。
起始信号:SCL为高电平时SDA由高到低跳变
停止信号:SCL为高电平时SDA由低到高跳变
数据有效性:在SCL为高期间,SDA信号必须保持稳定不能发生变化,只有在SCL为低电平时SDA信号才能发生变化。
应答信号ACK:要求在发送或者接受时候每第8个周期下降沿后的低低电平将SDA释放同时拉低SDA,在SCL在第9个时钟周期高电平时SDA为低电平说明主机或者从机接受到数据,为高说明未接受或者不想接受了。
下面以EEPROM为例型号为AT24C64A这是一个存储容量为65536bit的存储芯片,地址为8192个地址位是13位,每位一个字节。
共有8个引脚其中在控制器设计时候只需要操控SDA和SCL这两根线,A0-A2是它的地址位低三位,可以扩展8片,高四位是固定的为:1010。WP是写保护位,此位置高则写无效。
时序参数如图:
总线时序图:
SDA在SCL为低的时候才可以变化,且在SCL为高时禁止变化。在eeprom工作之前SCL和SDA必须为高,当SDA由高到底后才标志着器件可以工作,而结束位是在SCL为高时SDA由低变高。在传入地址或者数据时接收方在地址或者数据接收到后必须发送应答位,应答位为低,如果是高说明没有正确接收或者不想接受了。我一开始不解为什么在从机发送数据时主机释放总线SDA,那接收的数据由谁判断呢?最后才明白,这个“释放”是从主机给输出一端的信号是高阻态,这时sda_out并不输出到sda总线,sda_en让sda为高阻了,这个高阻一般是OC门或者集电极开路状态。这时总线的状态就不由sda_out来控制了,此时eeprom是发送设备,而控制器也就是FPGA这时是接收器,可以接收此时sda上的电平高低来判断是ACK(低)还是NO ACK(高),由sda_in接收。可见数电学好很有必要,阎石老师的数字电路基础第五版的第三章有详细讲OC门和各种CMOS电路。
在设计中代码已经给了比较详细的注释,其中分频时钟为了仿真分析方便没有按照芯片要求的范围进行设计,如有需要可以自行修改。注意下面这种scl时钟一定是像I2C这种在低速(不是I2C传输模式中的低速,而是I2C所有模式相对来说都是低速)传输的条件下才能以这种形式产生!
module iic_driver(
input sclk,//系统时钟50MHz
input rst_n,//复位信号低有效
input dri_en,//驱动器使能信号
input wr,//读写信号
input [15:0] addr,//输入的地址
input [7:0] w_data,//写入的数据
output reg [7:0] rd_data,//读出的数据
output i2c_done,//完成标志
output scl_o,//输出的时钟
inout sda//总线
);
reg [15:0] addr_r; //输入地址寄存器,用于并转串
reg [7:0] w_data_r; //写数据寄存器
reg st_done; //一次操作完标志
reg d_clk; //驱动器的时钟
reg wr_flag; //读写标志,低表示写,高表示读
reg sda_en; //控制总线的三态输出
reg [6:0] cnt; //每一个状态进度计数
reg [9:0] clk_cnt; //分频计数
reg sda_out; //总线数据输出:控制器到eeprom
reg [3:0] state; //状态
reg [7:0] rd_data_r; //读取数据寄存器
reg scl; //用于生成eeprom的时钟
reg i2c_done_r; //产生结束标志寄存器
reg sda_in;//wire sda_in; //从eeprom读出的数据或者ACK OR
NOACK,为了仿真暂时不设为
wire类型
reg [9:0] wr_cyc;//写等待5ms
reg wdone;//写完标志
localparam IDLE = 4'b0000; //默认状态
localparam SADDR = 4'b0001; //发送从设备地址
localparam ADDR_H = 4'b0010; //加载读或者写的地址高位
localparam ADDR_L = 4'b0011; //加载地址低位
localparam WRITE = 4'b0100; //写状态
localparam READ = 4'b0101; //读状态
localparam DATA_RD = 4'b0110; //读出数据串转并
localparam STOP = 4'b0111; //结束状态
parameter SLAVE_ADDR = 7'b101_0000;//eeprom的器件地址
assign sda = sda_en ? sda_out : 1'bz; // sda_en 为高SDA数据输出,为低主机释放sda总线
//assign sda_in = sda ; // SDA数据输入,从eeprom中读取的数据
assign scl_o = scl;
assign i2c_done = i2c_done_r;
//分频模块
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0 )
begin
d_clk <= 1'b1;
clk_cnt <= 'd0;
end
else if(clk_cnt == 3'd7)
begin
d_clk <= ~d_clk;
clk_cnt <= 'd0;
end
else
clk_cnt <= clk_cnt + 1'b1;
always @(posedge d_clk or negedge rst_n)
if(rst_n == 1'b0 )
begin
wr_cyc <= 'd0;
wdone <= 1'b0;
end
else if(state == STOP)
begin
if(wr_cyc == 10'd200)
begin
wr_cyc <= 'd0;
wdone <= 1'b1;
end
else
begin
wr_cyc <= wr_cyc + 1'b1;
wdone <= 1'b0;
end
end
always @(posedge d_clk or negedge rst_n)
if(rst_n == 1'b0 )
begin
scl <= 1'b1;
sda_out <= 1'b1;
sda_en <= 1'b1;
i2c_done_r <= 1'b0;
cnt <= 'd0;
st_done <= 1'b0;
rd_data_r <= 'd0;
wr_flag <= 1'b0;
addr_r <= 'd0;
w_data_r<='d0;
state <= IDLE;
sda_in <= 1'b0;
end
else
begin
cnt <= cnt + 1'b1;
st_done <= 1'b0;
case (state)
IDLE : if(dri_en == 1'b0)
begin
state <= IDLE;
end
else
begin
addr_r <= addr;
wr_flag <= wr;
w_data_r <= w_data;
state <= SADDR;
end
SADDR : if(st_done == 1'b0)
begin
state <= SADDR;
case (cnt)
7'd1 : sda_out <= 1'b0;// 开始I2C
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= SLAVE_ADDR[6];// 传送器件地址
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= SLAVE_ADDR[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: sda_out <= 1'b0;// 0:写
7'd33: scl <= 1'b1;
7'd35: scl <= 1'b0;
7'd36: begin
sda_en <= 1'b0;// 主机释放sda总线从机应答
sda_out <= 1'b1;//将输出拉高
end
7'd37: scl <= 1'b1;
7'd38: st_done <= 1'b1;
7'd39: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
else begin
if(sda_in == 1'b0)
begin
state <= ADDR_H;//应答位位低电平说明收到数据
scl <= 1'b0;
cnt <= 1'b0;
end
else
state <= SADDR; //没有收到完整数据,重新传输器件地址
end
ADDR_H : if(st_done == 1'b0)
begin
case(cnt)
7'd0 : begin
sda_en <= 1'b1;//主机重新控制总线
sda_out <= addr_r[15];// 传送字地址高8位
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= addr_r[14];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= addr_r[13];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= addr_r[12];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= addr_r[11];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= addr_r[10];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= addr_r[9];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= addr_r[8];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_en <= 1'b0;// 从机应答
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: st_done <= 1'b1;
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
else begin
if(sda_in == 1'b0)begin
state <= ADDR_L;
scl <= 1'b0;
cnt <= 1'b0;
end
else
state <= ADDR_H;
end
ADDR_L : if(st_done == 1'b0)
begin
case(cnt)
7'd0: begin
sda_en <= 1'b1;//主机重新控制总线
sda_out <= addr_r[7]; // 字地址
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= addr_r[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= addr_r[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= addr_r[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= addr_r[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= addr_r[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= addr_r[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= addr_r[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_en <= 1'b0;//主机释放sda线,从机应答,应答信号由sda线传给sda_in,由主机判断应答信号高低
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: st_done <= 1'b1;
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
wr_flag <= wr;
end
default : ;
endcase
end
else begin
if(sda_in == 1'b0)
begin
if(wr_flag == 1'b0)begin
state <= WRITE;
scl <= 1'b0;
cnt <= 1'b0;
wr_flag <= wr;
end
else
state <= READ;
end
else
state <= ADDR_L;
end
WRITE : if(st_done == 1'b0)
begin // 写数据(8 bit)
case(cnt)
7'd0: begin
sda_out <= w_data_r[7]; // I2C写8位数据
sda_en <= 1'b1;
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= w_data_r[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= w_data_r[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= w_data_r[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= w_data_r[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= w_data_r[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= w_data_r[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= w_data_r[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_en <= 1'b0; // 从机应答
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: st_done <= 1'b1;
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
else
begin
if(sda_in == 1'b0)begin
state <= STOP;
scl <= 1'b0;
cnt <= 1'b0;
end
else
state <= WRITE;
end
READ : if(st_done == 1'b0)
begin // 写地址以进行读数据
case(cnt)
7'd0 : begin
sda_en <= 1'b1;
sda_out <= 1'b1;
end
7'd1 : scl <= 1'b1;
7'd2 : sda_out <= 1'b0; // 重新开始
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= SLAVE_ADDR[6]; // 传送器件地址
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= SLAVE_ADDR[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: sda_out <= 1'b1; // 1:读
7'd33: scl <= 1'b1;
7'd35: scl <= 1'b0;
7'd36: begin
sda_en <= 1'b0; // 从机应答
sda_out <= 1'b1;
end
7'd37: scl <= 1'b1;
7'd38: st_done <= 1'b1;
7'd39: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
else
begin
if(sda_in == 1'b0)begin
state <= DATA_RD;
scl <= 1'b0;
cnt <= 1'b0;
end
else
state <= READ;
end
DATA_RD : if(st_done == 1'b0)
begin // 读取数据(8 bit)
case(cnt)
7'd0: sda_en <= 1'b0;
7'd1: begin
rd_data_r[7] <= sda_in;
scl <= 1'b1;
end
7'd3: scl <= 1'b0;
7'd5: begin
rd_data_r[6] <= sda_in ;
scl <= 1'b1 ;
end
7'd7: scl <= 1'b0;
7'd9: begin
rd_data_r[5] <= sda_in;
scl <= 1'b1 ;
end
7'd11: scl <= 1'b0;
7'd13: begin
rd_data_r[4] <= sda_in;
scl <= 1'b1 ;
end
7'd15: scl <= 1'b0;
7'd17: begin
rd_data_r[3] <= sda_in;
scl <= 1'b1 ;
end
7'd19: scl <= 1'b0;
7'd21: begin
rd_data_r[2] <= sda_in;
scl <= 1'b1 ;
end
7'd23: scl <= 1'b0;
7'd25: begin
rd_data_r[1] <= sda_in;
scl <= 1'b1 ;
end
7'd27: scl <= 1'b0;
7'd29: begin
rd_data_r[0] <= sda_in;
scl <= 1'b1 ;
end
7'd31: scl <= 1'b0;
7'd32: begin
sda_en <= 1'b1; // 非应答
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: st_done <= 1'b1;
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
rd_data <= rd_data_r;
end
default : ;
endcase
end
else
begin
if(sda_in == 1'b0)begin
state <= STOP;
scl <= 1'b0;
cnt <= 1'b0;
rd_data <= rd_data_r;
end
else
state <= DATA_RD;
end
STOP :
begin // 结束I2C操作
case(cnt)
7'd0: begin
sda_en <= 1'b1;
sda_out <= 1'b0;
sda_en <= 1'b1;//主机控制sda总线
sda_out <= 1'b0;
end
7'd1 : scl <= 1'b1;
7'd3 : sda_out <= 1'b1;//sda信号在scl为高时由低变高产生结束信号
7'd15: st_done <= 1'b1;
7'd16: begin
cnt<= 1'b0;
i2c_done_r <= 1'b1;// 读或写结束
end
default : ;
endcase
if(wdone == 1'b1)
state <= IDLE;
else
state <= STOP;
end
endcase
end
endmodule
仿真图;写入的地址为15(0_0000_0000_1111)状态为2的时候sda写入地址高八位准确说是高5位,在状态3sda写入地址低八位00001111,在状态4可以看到sda写入数据为170(1010_1010)。