一.协议介绍
1.IIC概念
IIC是一种两线式串行总线,由数据线SDA和时钟线SCL构成通信线路,既可用于发送数据,也可接受数据,是一种半双工通信协议。
总线上的主设备和从设备之间以字节为单位进行双向的数据传输。
多用于主机和从机在数据量不大且传输距离短的场合下的主从通信。主机启动总线,并产生时钟用于传送数据,此时任何接收数据的器件均被认为是从机。I2C器件一般采用开漏结构与总线相连,所以I2C_SCL和I2C_SDA均需接上拉电阻,也正因此,当总线空闲时,这两条线路都处于高电平状态,当连到总线上的任一器件输出低电平,都将使总线拉低。
总线上的每一个设备都可以作为主设备或从设备,而且每一个设备都会对应一个唯一的地址(可以从12C器件数据手册得知),主从设备之间就是通过这个地址来确定与哪个器件进行通信。
2.时序要求
-
空闲状态
空闲状态,IIC两条总线被规定都处在高电平,此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。 -
起始信号
在SCL保持高电平期间,SDA的电平被拉低,称为 I2C总线的起始信号,标志着一次数据传输的开始。起始信号由主机主动建立,在建立该信号之前I2C总线必须处于空闲状态。 -
停止信号
在SCL处在高电平期间。SDA被释放,被上拉到高电平,成为IIC总线停止信号,标志着一次数据传输终止。 停止信号由主机主动发送,结束后,IIC总线返回空闲状态。 -
数据传输
在IIC总线传输数据时,必须在SCL处在低电平期间才允许SDA数据改变,在SCL处在高电平期间,SDA被要求必须保持稳定,在SCL高电平期间进行采样,如果SDA处于高电平则为1,为低电平则为0 -
应答信号
12C总线上的所有数据都是以字节传送的,发送端每发送一个字节,就必须在第9个SCL脉冲期间释放SDA,由接收端反馈一个应答信号。应答信号为低电平时,称为有效应答位(ACK),表示接收端成功接收了该字节;应答信号为高电平时,称为非应答位(NACK),表示接收端接收该字节失败。对于反馈有效应答位ACK的要求是,接收端在第9个时钟脉冲之前的低电平期间将SDA拉低,并且确保在该时钟周期的高电平期间保持低电平。如果接收端是主控端,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送端结束数据发送,并释放SDA线,以便主控接收器发送停止信号。
二.EEPROM
1.芯片简介
1.读写时序
1.写操作
byte写:起始位——>设备地址(4bit+3bit(2bit不关心+1bit块选择位)+1bit读/写)——>相应信号——>数据地址(8bits)——>相应信号——>8bits数据——>响应信号——>停止位
页写:起始位——>设备地址(4bit+3bit(2bit不关心+1bit块选择位)+1bit读/写)——>相应信号——>数据地址(8bits)——>相应信号——>8bits数据——>响应信号——>……——>>8bits数据——>响应信号——>停止位;
注意:页写中每一页最多写入16byte数据,如果超过的的内部指针会滚动,将之前写入的数据从头向后覆盖
2.读操作(当前地址读,随机读,顺序读)
当前地址读:byte写:起始位——>设备地址(4bit+3bit(2bit不关心+1bit块选择位)+1bit读/写)——>响应信号——>接收数据8bits——>不发送响应信号——>发送停止位
(注意当前地址读是在上一个读写操作的地址上+1,也就是上次读写地址的写一个地址)
随机读:起始位——>设备地址(4bit+3bit(2bit不关心+1bit块选择位)+1bit读/写)——>相应信号——>数据地址(8bits)——>起始位——>设备地址(4bit+3bit(2bit不关心+1bit块选择位)+1bit读/写)——>相应信号——>接收8bits数据——>不发送响应信号——>停止位
页读:起始位——>设备地址(4bit+3bit(2bit不关心+1bit块选择位)+1bit读/写)——>相应信号——>数据地址(8bits)——>起始位——>设备地址(4bit+3bit(2bit不关心+1bit块选择位)+1bit读/写)——>相应信号——>接收8bits数据——>发送响应信号——>……——>>8bits数据——>不发送响应信号——>停止位
三.项目设计
1.状态机设计
这里还是采用主从状态机实现,通过接口模块,和控制接口模块来达到读写eeprom
控制模块(主状态机模块),仅考虑操作类型,读操作或者写操作,不关系数据如何发送,只需要发送操作指令,等待接口模块发送完成返回结束信号。
接口模块(从状态机),当操作指令到来后,只需要按照时序发送或者接收数据,之后返还一个结束信号
2.代码实现
1.接口模块
/**************************************功能介绍***********************************
Date :
Author : WZY.
Version :
Description: 接口命令(cmd)
0 1
bit0 (起始位) NO YES
bit1 (写数据) NO YES
bit2 (读数据) NO YES
bit3 (停止位) NO YES
bit4 (相应位) ACK NO_ACK
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module iic_dirver#
(
parameter T = "100k",
SYSTEM_CLOCK = 50_000_000
)
(
input wire clk ,
input wire rst_n ,
//控制接口
input wire [4:0] cmd ,
input wire cmd_vld ,
output wire done ,
//数据接口
input wire [7:0] wr_data ,
output wire [7:0] rd_data ,
output wire rd_data_vld,
output wire iic_scl ,
inout wire iic_sda
);
//---------<参数定义>---------------------------------------------------------
//状态机参数定义
localparam IDLE = 7'b0000001,
START = 7'b0000010,
WRITE = 7'b0000100,
READ = 7'b0001000,
R_ACK = 7'b0010000,
S_ACK = 7'b0100000,
STOP = 7'b1000000;
//时间参数定义
parameter IICT = (T == "100k")?SYSTEM_CLOCK/100_000:
(T == "400k")?SYSTEM_CLOCK/400_000:
SYSTEM_CLOCK/3_400_000;
// parameter IICT = 500;
parameter IICT_1_4 = IICT >> 2,
IICT_1_2 = IICT >> 1,
IICT_3_4 = IICT - IICT_1_4;
//指令定义
`define START_BIT 5'b00001
`define WRITE_BIT 5'b00010
`define READ_BIT 5'b00100
`define STOP_BIT 5'b01000
`define ACK_BIT 5'b10000
//响应信号定义
`define ACK 0
`define NO_ACK 1
//---------<内部信号定义>-----------------------------------------------------
reg [6:0] cstate ;//现态
reg [6:0] nstate ;//次态
wire idle2start ;
wire idle2write ;
wire idle2read ;
wire start2write ;
wire write2r_ack ;
wire read2s_ack ;
wire r_ack2idle ;
wire r_ack2stop ;
wire s_ack2idle ;
wire s_ack2stop ;
wire stop2idle ;
//计数器参数定义
reg [8:0] cnt_1bit ;
wire add_cnt_1bit ;
wire end_cnt_1bit ;
reg [2:0] cnt_num ;
wire add_cnt_num ;
wire end_cnt_num ;
reg [3:0] num;
//命令信号寄存
reg [4:0] cmd_r ;
//存在信号
reg rev_ack ;
//三态门参数
reg OE;
wire din;
reg dout;
//数据寄存
reg [7:0] rd_data_r ;
reg [7:0] wr_data_r ;
//****************************************************************
// 数据寄存
//****************************************************************
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wr_data_r <= 8'd0;
end
else if (cmd_vld) begin
wr_data_r <= wr_data;
end
end
//****************************************************************
// 状态机
//****************************************************************
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cstate <= IDLE;
end
else begin
cstate <= nstate;
end
end
//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begin
case(cstate)
IDLE : begin
if (idle2start) begin
nstate = START;
end
else if (idle2write) begin
nstate = WRITE;
end
else if (idle2read) begin
nstate = READ;
end
else begin
nstate = cstate;
end
end
START : begin
if (start2write) begin
nstate = WRITE;
end
else begin
nstate = cstate;
end
end
WRITE : begin
if (write2r_ack) begin
nstate = R_ACK;
end
else begin
nstate = cstate;
end
end
READ : begin
if (read2s_ack) begin
nstate = S_ACK;
end
else begin
nstate = cstate;
end
end
R_ACK : begin
if (r_ack2idle) begin
nstate = IDLE;
end
else if (r_ack2stop) begin
nstate = STOP;
end
else begin
nstate = cstate;
end
end
S_ACK : begin
if (s_ack2stop) begin
nstate = STOP;
end
else if (s_ack2idle) begin
nstate = IDLE;
end
else begin
nstate = cstate;
end
end
STOP : begin
if (stop2idle) begin
nstate = IDLE;
end
else begin
nstate = cstate;
end
end
default : ;
endcase
end
assign idle2start = cstate == IDLE && cmd_vld && (cmd & `START_BIT);//接收到命令信号并且命令中包含开始命令
assign idle2write = cstate == IDLE && cmd_vld && (cmd & `WRITE_BIT);//接收到命令信号并且命令中没有开始命令,有写命令
assign idle2read = cstate == IDLE && cmd_vld && (cmd & `READ_BIT );//接收到命令信号并且命令中没有开始命令,有读命令
assign start2write = cstate == START && end_cnt_num && (cmd_r & `WRITE_BIT);
assign write2r_ack = cstate == WRITE && end_cnt_num;
assign read2s_ack = cstate == READ && end_cnt_num;
assign r_ack2idle = cstate == R_ACK && end_cnt_num && !(cmd_r & `STOP_BIT);//接收到响应,并且命令中没有停止命令
assign r_ack2stop = cstate == R_ACK && end_cnt_num && (cmd_r & `STOP_BIT);//接收到相应信号,并且命令中有停止命令
assign s_ack2idle = cstate == S_ACK && end_cnt_num && !(cmd_r & `STOP_BIT);//等待1bit时间并且没有停止命令
assign s_ack2stop = cstate == S_ACK && end_cnt_num && (cmd_r & `STOP_BIT);//等待1bit时间并且有停止命令
assign stop2idle = cstate == STOP && end_cnt_num;
//第三段:描述输出,时序逻辑或组合逻辑皆可
//****************************************************************
// 命令寄存
//****************************************************************
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cmd_r <= 0;
end
else if (cmd_vld) begin//每次接收到命令使能更新命令
cmd_r <= cmd;
end
end
//****************************************************************
// 计数器复用
//****************************************************************
//1bit计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_1bit <= 9'd0;
end
else if(add_cnt_1bit)begin
if(end_cnt_1bit)begin
cnt_1bit <= 'd0;
end
else begin
cnt_1bit <= cnt_1bit + 1'b1;
end
end
end
assign add_cnt_1bit = cstate != IDLE;
assign end_cnt_1bit = add_cnt_1bit && cnt_1bit == IICT-1;
//计数器复用
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_num <= 'd0;
end
else if(add_cnt_num)begin
if(end_cnt_num)begin
cnt_num <= 'd0;
end
else begin
cnt_num <= cnt_num + 1'b1;
end
end
end
assign add_cnt_num = end_cnt_1bit;
assign end_cnt_num = add_cnt_num && cnt_num == num - 1;
always @(*) begin
case (cstate)
START : num = 1 ;
WRITE : num = 8 ;
READ : num = 8 ;
R_ACK : num = 1 ;
S_ACK : num = 1 ;
STOP : num = 1 ;
default: num = 1;
endcase
end
//****************************************************************
// 三态门
//****************************************************************
//OE使能控制
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
OE <= 0;
end
else if (idle2start||idle2write||start2write||r_ack2stop||read2s_ack||s_ack2stop) begin
OE <= 1;
end
else if (idle2read||write2r_ack||stop2idle||s_ack2idle||r_ack2idle) begin
OE <= 0;
end
end
assign iic_sda = OE?dout:1'bz;
assign din = iic_sda;
assign iic_scl = (cnt_1bit >= (IICT >>1))?1:0;//发送周期波形
// SDA信号控制
always @(*) begin
if (!rst_n) begin
dout = 1;
end
else case (cstate)
IDLE : begin
dout = 1;
end
START : begin
if (cnt_1bit == IICT_3_4) begin
dout = 0;
end