IIC读写接口驱动模块
写在前面
FPGA实现IIC协议读写EEPROM相关文章:
IIC通信协议
【FPGA】FPGA实现IIC协议读写EEPROM(一) ----- IIC接口驱动实现
【FPGA】FPGA实现IIC协议读写EEPROM(二) -----EEPROM读写控制模块实现
【FPGA】FPGA实现IIC协议读写EEPROM(三) ----- 汇总篇
在上篇文章中已经对IIC协议进行了详细介绍,本文介绍IIC读写接口驱动模块的实现。
一、功能分析
IIC接口驱动模块功能是按照IIC协议时序将数据写入EEPROM或者从EEPROM读取数据。根据上一篇IIC协议文章介绍我们不难分析出通过IIC协议单字节读写EEPROM的过程。
1、单字节写操作
(1)SDA总线拉低发送起始信号,开始传输数据。
(2)发送写控制字(4位器件地址+3位片选位+写标志0),接收从机应答ACK。
(3)发送寄存器地址,接收从机应答ACK。
(4)发送数据字节,接收从机应答ACK。
(5)SDA总线拉高,发送停止位,数据传输结束。
2、单字节读操作(当前地址读)
(1)SDA总线拉低发送起始信号,开始传输数据。
(2)发送读控制字(4位器件地址+3位片选位+读标志1),接收从机应答ACK。
(3)接收读回的单字节数据,发送非应答信号NACK。
(4)SDA总线拉高,发送停止位,数据传输结束。
3、随机读
(1)SDA总线拉低发送起始信号,开始传输数据。
(2)虚写:发送写控制字(4位器件地址+3位片选位+写标志0),接收从机应答ACK。
(3)发送寄存器地址,接收从机应答ACK。
(4)SDA总线拉低发送起始信号,开始传输数据。
(5)实际读操作:发送读控制字(4位器件地址+3位片选位+读标志1),接收从机应答ACK。
(6)接收读回的单字节数据,发送非应答信号NACK。
(7)SDA总线拉高,发送停止位,数据传输结束。
4、顺序读
顺序读操作是在随机读的操作之后进行的,当收到读回的数据时,主机发送应答信号ACK给从机,表示正确接收到了该字节数据,然后进行下一字节数据的接收、发送ACK,当不需要接收数据时,发送非应答信号NACK,SDA总线拉高,发送停止位,数据传输结束。
二、输入/输出信号
IIC接口驱动模块如下:
信号说明:
信号 | I/O类型 | 说明 |
---|---|---|
clk | input | 50MHz系统时钟 |
rdout[7:0] | ouput | 读回的数据 |
rdout_vld | ouput | 读回的数据有效标志 |
rw_done | ouput | 读写一字节数据完成标志 |
scl | ouput | IIC时钟总线,最大400KHz,本工程使用200KHz |
sda | inout | IIC双向数据总线 |
req | input | 读写请求 |
cmd[3:0] | input | 传输操作控制命令组合(写、读、起始、停止、应答、非应答) |
wr_din[7:0] | input | 发送一字节数据 |
三、IIC接口驱动状态机
IIC接口驱动模块状态转移图如下:
状态说明:
IDLE:空闲状态,等待读写请求。
START:发送起始位。当控制命令cmd[5:0]中含有起始命令时,进行该状态发送起始位。
WR_DATA:写数据状态。在写数据状态期间,将wr_din[7:0]按照IIC协议时序写入EEPROM。
RD_DATA:读数据状态。在读数据状态期间,按照IIC协议时序从EEPROM中读取数据。
REC_ACK:接收从机发送的ACK应答信号。
SEND_ACK:发送ACK或者NACK信号。
STOP:发送停止位。当控制命令cmd[5:0]中含有停止命令时,进行该状态发送停止位。
四、IIC接口驱动实现
i2c_interface接口驱动verilog代码如下:
// **************************************************************
// Author: Zhang JunYi
// Create Date: 2022.11.13
// Design Name: i2c_eeprom
// Module Name: i2c_interface
// Target Device: Cyclone IV E (EP4CE6F17C8)
// Tool versions: Quartus Prime 18.1
// Description: I2C接口驱动模块
// **************************************************************
`include "param.v"
module i2c_interface (
input clk ,
input rst_n ,
// eeprom_ctrl
input req , // 读写请求
input [7:0] wr_din , // 需要发送的一字节数据
input [3:0] cmd , // 控制命令组合
output [7:0] rdout , // 读取的数据
output rdout_vld , // 读取数据有效标志
output rw_done , // 读写一字节完成标志
// EEPROM
input sda_in ,
output sda_out ,
output sda_out_en ,
output scl
);
// 参数定义
localparam IDLE = 7'b000_0001 ,
START = 7'b000_0010 ,
WR_DATA = 7'b000_0100 ,
RD_DATA = 7'b000_1000 ,
REC_ACK = 7'b001_0000 ,
SEND_ACK= 7'b010_0000 ,
STOP = 7'b100_0000 ;
// 信号定义
reg [6:0] state_c ;
reg [6:0] state_n ;
reg [7:0] cnt_scl ; // scl周期计数器
wire add_cnt_scl ;
wire end_cnt_scl ;
reg [3:0] bit_num ; // bit数
reg [3:0] cnt_bit ; // bit计数器
wire add_cnt_bit ;
wire end_cnt_bit ;
reg [7:0] dout_data ; // 发送数据寄存
reg [3:0] command ; // 控制命令寄存
reg scl_dout ; // SDA寄存
reg sda_dout ;
reg sda_dout_en ;
reg [7:0] rx_data ; // 接收读回的数据
reg ack_flag ; // ack响应标志
// 状态转移条件
wire idle2start ;
wire idle2wrdata ;
wire idle2rddata ;
wire start2wrdata ;
wire start2rddata ;
wire wrdata2recack ;
wire rddata2sendack ;
wire recack2idle ;
wire recack2stop ;
wire sendack2idle ;
wire sendack2stop ;
wire stop2idle ;
// 状态机
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE ;
end
else begin
state_c <= state_n ;
end
end
always @(*)begin
case (state_c)
IDLE: begin
if(idle2start)
state_n = START ;
else if(idle2wrdata)
state_n = WR_DATA ;
else if(idle2rddata)
state_n = RD_DATA ;
else
state_n = state_c ;
end
START: begin
if(start2wrdata)
state_n = WR_DATA ;
else if(start2rddata)
state_n = RD_DATA ;
else
state_n = state_c ;
end
WR_DATA: begin
if(wrdata2recack)
state_n = REC_ACK ;
else
state_n = state_c ;
end
RD_DATA: begin
if(rddata2sendack)
state_n = SEND_ACK ;
else
state_n = state_c ;
end
REC_ACK: begin
if(recack2idle)
state_n = IDLE ;
else if(recack2stop)
state_n = STOP ;
else
state_n = state_c ;
end
REC_ACK: begin
if(sendack2idle)
state_n = IDLE ;
else if(sendack2stop)
state_n = STOP ;
else
state_n = state_c ;
end
STOP: begin
if(stop2idle)
state_n = IDLE ;
else
state_n = state_c ;
end
default: state_n = IDLE ;
endcase
end
// 状态转移条件
assign idle2start = state_c == IDLE && req && (cmd & `STA) ;
assign idle2wrdata = state_c == IDLE && req && (cmd & `WRITE) ;
assign idle2rddata = state_c == IDLE && req && (cmd & `READ) ;
assign start2wrdata = state_c == START && end_cnt_bit && (command & `WRITE) ;
assign start2rddata = state_c == START && end_cnt_bit && (command & `READ) ;
assign wrdata2recack = state_c == WR_DATA && end_cnt_bit ;
assign rddata2sendack = state_c == RD_DATA && end_cnt_bit ;
assign recack2idle = state_c == REC_ACK && end_cnt_bit && (command & `STO) == 0 ;
assign recack2stop = state_c == REC_ACK && end_cnt_bit && (command & `STO) ;
assign sendack2idle = state_c == SEND_ACK && end_cnt_bit && (command & `STO) == 0 ;
assign sendack2stop = state_c == SEND_ACK && end_cnt_bit && (command & `STO) ;
assign stop2idle = state_c == STOP && end_cnt_bit ;
// cnt_scl 200KHz 一个SCL周期为250个系统时钟周期
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_scl <= 0 ;
end
else if(add_cnt_scl)begin
if(end_cnt_scl)begin
cnt_scl <= 0 ;
end
else begin
cnt_scl <= cnt_scl + 1 ;
end
end
end
assign add_cnt_scl = state_c != IDLE ;
assign end_cnt_scl = add_cnt_scl && (cnt_scl == `SCL_PERIOD - 1) ;
// bit_num
always @(*)begin
if(state_c == RD_DATA || state_c == WR_DATA)begin
bit_num = 8 ;
end
else begin
bit_num = 1 ;
end
end
// cnt_bit
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 0 ;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 0 ;
end
else begin
cnt_bit <= cnt_bit + 1 ;
end
end
end
assign add_cnt_bit = end_cnt_scl ;
assign end_cnt_bit = add_cnt_bit && (cnt_bit == bit_num - 1) ;
// dout_data
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
dout_data <= 0 ;
end
else if(req)begin
dout_data <= wr_din ;
end
end
// command
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
command <= 0 ;
end
else if(req)begin
command <= cmd ;
end
end
// scl_dout
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
scl_dout <= 1'b1 ;
end
else if(idle2start | idle2wrdata | idle2rddata)begin
scl_dout <= 1'b0 ;
end
else if(add_cnt_scl && cnt_scl == `SCL_HALF)begin
scl_dout <= 1'b1 ;
end
else if(end_cnt_scl && ~stop2idle)begin
scl_dout <= 1'b0 ;
end
end
// sda_dout_en
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sda_dout_en <= 1'b0 ;
end
else if(idle2start || idle2wrdata || rddata2sendack || sendack2stop || recack2stop)begin
sda_dout_en <= 1'b1 ;
end
else if(idle2rddata || start2rddata || wrdata2recack || stop2idle)begin
sda_dout_en <= 1'b0 ;
end
end
// sda_dout
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sda_dout <= 1'b1 ;
end
else if(state_c == START)begin
if(cnt_scl == `HIGH_HALF)begin
sda_dout <= 1'b0 ;
end
else if(cnt_scl == `LOW_HALF)begin
sda_dout <= 1'b1 ;
end
end
else if(state_c == WR_DATA && cnt_scl == `LOW_HALF)begin
sda_dout <= dout_data[7 - cnt_bit] ;
end
else if(state_c == SEND_ACK && cnt_scl == `LOW_HALF)begin
sda_dout <= (command & `STO) ? 1'b1 : 1'b0 ;
end
else if(state_c == STOP)begin
if(cnt_scl == `LOW_HALF)begin
sda_dout <= 1'b0;
end
else if(cnt_scl == `HIGH_HALF)begin
sda_dout <= 1'b1;
end
end
else if(wrdata2recack | rddata2sendack)begin
sda_dout <= 1'b1;
end
end
// rx_data
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_data <= 0 ;
end
else if(state_c == RD_DATA && cnt_scl == `HIGH_HALF)begin
rx_data[7 - cnt_bit] <= sda_in ;
end
end
// ack_flag
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
ack_flag <= 1'b0 ;
end
else if(state_c == REC_ACK && cnt_scl == `HIGH_HALF)begin
ack_flag <= ~sda_in ;
end
else begin
ack_flag <= 1'b0 ;
end
end
// 输出
assign rdout = rx_data ;
assign rdout_vld = rddata2sendack ;
assign rw_done = stop2idle | sendack2idle | recack2idle ;
assign sda_out = sda_dout ;
assign sda_out_en = sda_dout_en ;
assign scl = scl_dout ;
endmodule
param.v参数:
// IIC时钟参数
`define SCL_PERIOD 250
`define SCL_HALF 125
`define LOW_HALF 65
`define HIGH_HALF 190
// 控制命令
`define WRITE 4'b1000 // 写
`define READ 4'b0100 // 读
`define STA 4'b0010 // 起始位
`define STO 4'b0001 // 停止位
五、仿真测试
写数据:
读数据:
注意:
1. 这里仿真使用的是24LC04B的仿真模型,读数据的时候只能读0,读1显示的是高阻态。
2. 写周期是5ms,写完数据之后需要延时一段时间才能发送读请求,否则无法读回数据,这里我仿真延时300000个时钟周期。