基于FPGA的IIC控制EEPROM读写
文章目录
一、EEPROM简介
Microchip Technology公司生产的24XX04*型微芯片技术(编号24AA04/24LC04B)是一款4千比特电可擦可编程只读存储器(EPROM)。该器件采用双存储块结构,每个存储块包含256×8位内存,并配备双线串行接口。其低压设计支持1.7V以下工作电压,待机电流仅为1 μA,工作电流仅1 mA。该芯片还具备最多16字节的页写入功能。产品提供标准8引脚PDIP封装、表面贴装SOIC封装、TSSOP封装、2x3 DFN封装和MSOP封装等多种规格,同时也有5引脚SOT-23封装可供选择。
其可以兼容iic读写,iic基础知识以及实现过程详见基于FPGA的IIC控制EEPROM读写(1)
控制字节(iic中的地址)是主设备在接收到开始条件后发送的第一个字节,由四位控制码构成。对于24XX04芯片而言,读写操作时该位设置为二进制‘1010’。控制字节的后两位对24XX04芯片来说是“无关紧要”的,而最后一位B0位则由主设备用来选择要访问的两个256字节内存块中的哪一个——这个位实际上就是地址字的最高有效位。控制字节的最后一位决定了操作类型:当设置为‘1’时选择读取操作,当设置为‘0’时选择写入操作。在开始条件触发后,24XX04芯片会持续监控SDA总线,检查传输的设备类型标识符。一旦接收到‘1010’代码,从设备就会在SDA线上输出确认信号。根据读/写位的状态,24XX04芯片会选择执行读取或写入操作。
字节写:在主设备发出启动条件后,主设备发送器会将设备代码(4位)、块地址(3位)以及处于低电平的读/写位(R/W)同步传输至总线。这向被寻址的从设备接收器发出信号:当其在第九个时钟周期生成确认信号后,将接收到包含字地址的字节数据。因此,主设备接下来传输的字节即为该字地址,并会被写入24XX04的地址指针。当从设备再次返回确认信号后,主设备会将待写入目标内存位置的数据字节发送出去。此时24XX04会再次确认,主设备随即触发停止条件。这一操作启动内部写入周期,在此期间24XX04将不再发送确认信号
页写写控制字节、字地址和首个数据字节的传输方式与字节写入操作相同。但主设备不会触发停止条件,而是向24XX04发送最多16个数据字节,这些数据会暂存于片上页缓冲区,并在主设备发出停止条件后写入内存。每当接收到一个字时,地址指针的四个低位地址位会在内部递增‘1’,而字地址的高位7位保持不变。若主设备在触发停止条件前发送超过16个字,地址计数器将发生溢出,先前接收的数据会被覆盖。与字节写入操作类似,一旦接收到停止条件,系统就会启动内部写入周期。
读取操作的启动方式与写入操作相同,唯一的区别是将从地址的R/W位设置为“1”。有三种基本类型的读取操作:当前地址读取、随机读取和顺序读取。
当前地址读取 :24XX04芯片内置地址计数器,用于记录最近一次访问的地址,并通过内部递增‘1’来保持该地址。因此,如果前次访问(无论是读取还是写入操作)的目标地址是n,那么下一次当前地址读取操作将访问地址n + 1的数据。当接收到从设备地址且R/W位设置为‘1’时,24XX04会发出确认信号并传输8位数据字。主设备不会对此次传输进行确认,但会生成停止条件,此时24XX04将终止传输。
随机读取 :随机读取操作允许主设备以随机方式访问任意内存位置。执行此类读取操作时,首先需要设置字地址。该操作通过将字地址作为写入操作的一部分发送至24XX04芯片来实现。当字地址发送完成后,主设备会在收到确认信号后触发启动条件。这会终止写入操作,但在此之前内部地址指针已被设置。随后主设备再次发出控制字节,但此时R/W位被设置为‘1’。24XX04芯片随即发出确认信号并传输8位数据字。虽然主设备不会对传输进行确认,但会生成停止条件,此时24XX04芯片将停止传输。
顺序读取:顺序读取的启动方式与随机读取相同,但不同之处在于:当24XX04开始传输首个数据字节时,主设备会发出确认信号,而随机读取则会触发停止条件。这使得24XX04能够继续传输下一个按顺序寻址的8位字(图7-3)。为实现顺序读取功能,24XX04内置了一个地址指针,每次操作完成后该指针都会递增1。通过这个地址指针,整个内存内容可以在单次操作中完成串行读取。
二、代码实现——个人理解
1、状态机
基本实现流程与IIC协议类似,只不过在设计的时候将发送各个数据的时候多加入了几个应答状态;根据EEPROM的读写规则,在进行读操作时,要先进行发送写指令,并将要读取数据的地址写入,再次发送开始信号,再进行发送读指令,所以再发送字地址之后的ACK2应答状态完成后,跳回START状态,发送完控制字节后,在ACK1跳转到读状态,进行读操作。
2、仿真效果
接收到02开始信号,在4‘d1即开始状态,scl与sda符合iic协议的开始信号描述。
开始信号结束,跳转到发送控制字节的状态,信号有效时,依据iic的发送数据规则发送控制字节,由eeprom的简介可知,其内部地址为1010,最后一位是读写命令,倒数第二个为所操作的寄存器块,其他两位数据无关紧要,所以在此发送的控制字节为1010_0000为8’ha0,由仿真可看出符合iic发送数据的规则(scl高电平数据有效,低电平数据改变);发送完成进入应答位,等待应答,由于是仿真,所以应答是仿真所给的激励信号sda_in,一直为低,表有效,等待计数器计数完成,跳转至发送字地址。
这里设置操作的字地址为8’h10,所发数据正确,ack有应答,跳转至发送数据状态。
发送数据由随机数函数产生,此时为e8对应二进制位1110_1000发送正确,应答有效,跳转发送数据状态,继续发送;这里一共发送了6次随机数,不在展示后面的数据。
接收到8‘h03停止信号,发送正确,应答有效,之后跳到STOP状态,scl高电平期间sda产生由低到高的跳变,发送停止信号。发送完成跳回IDLE空闲状态。
接下来进行读操作,开始操作和写相同
由于仿真文件编写有瑕疵,控制字节发送时并未将真实的1010_0001写入,写入的为1111_1111,但不影响后续仿真。根据eeprom简介可知,要进行顺序读操作,第一次要发送写操作,所以在这里对真是发送过来的控制字节进行操作
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
ctrl_data <= 8'd0;
end
else if((state_c == CTRL_BYTE) && (!w_mod) )begin
ctrl_data <= data_in;
end
else if(state_c == CTRL_BYTE && w_mod ==1 && ctrl_flg)begin
ctrl_data <= {data_in[7:1],1'b0};
end
else if(state_c == CTRL_BYTE && w_mod ==1 && !ctrl_flg)begin
ctrl_data <= {ctrl_data[7:1],1'b1};
end
else begin
ctrl_data <= ctrl_data;
end
end
设置一个控制字节寄存器,当为读模式,且第一次进入发送控制字节状态(由ctrl_flg)控制,真实发送给从机的控制字节为数据前七位拼一个0发送给从机,收到应答后发送字地址。
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
w_mod <= 1'b0;
end
else if((state_c == CTRL_BYTE) && rx_vld )begin
w_mod <= data_in[0];
end
end
在接收到由电脑发送来的控制字节时,将“真”读写模式寄存下来,当发送完字地址,有应答后,检测到真读写模式为读后跳转到START状态发送起始信号,之后再次进入发送控制字节状态。
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
ctrl_flg <= 1'b1;
end
else if(ACK2_2_START)begin
ctrl_flg <= 1'b0;
end
else if(STOP_2_IDLE)begin
ctrl_flg <= 1'b1;
end
else begin
ctrl_flg <= ctrl_flg;
end
end
当从ACK2跳转到START时,将ctrl_flg改变,表示为第二次进入发送控制字节状态。
此时发送的控制字节为寄存的地址数据前七为拼一个1,发送给从机,发送完成,检测时第二次跳到发送控制字节状态,有应答后跳转到读数据模式。
由仿真可知,此时读出的数据为0011_1011为3b,接收数据有效,tx_vld信号拉高,提示uart_tx模块开始发送数据。
等待tx发送完数据(tx_done),再继续读操作。
当接收到tx发送的tx_done跳转至应答状态,由FPGA发送给从机应答,0表示应答有效,计数完成再次进入读数据模式。
对比数据,发送全部正确。
停止时由FPGA发送NACK,此时sda_out拉高,表示无应答;计数完成进入STOP状态,发送停止信号,结束。
3、上板验证
上电直接进行读操作 02(开始),a1(器件地址+1读命令),01(字地址),03(结束)。读出的数据为01.
改变字地址,读出的数据为02。
改变字地址,读出数据为23。
进行写操作,再字地址01,依次写入23 34 45 56 。
依次将数读出,数据正确,上板验证成功。
4、代码
top.v
module top (
input clk,
input rst_n,
input rx,
output tx,
// input sda_in,
// output sda_out,
output scl,
inout sda,
input [1:0] sw
);
wire [7:0] rx_data;
wire rx_done;
wire tx_done;
wire [7:0]data_out_m;
wire tx_vld_m;
uart_rx inst_uart_rx (
.clk(clk),
.rst_n(rst_n),
.rx(rx),
.sw(sw),
.rx_data(rx_data),
.rx_done(rx_done)
);
iic_master inst_iic_master (
.clk (clk) ,
.rst_n (rst_n) ,
.data_in (rx_data) ,
.rx_vld (rx_done) ,
// .sda_in (sda_in) ,
// .sda_out (sda_out) ,
.data_out (data_out_m),
.tx_vld (tx_vld_m) ,
.sda (sda) ,
.tx_done (tx_done) ,
.scl (scl)
);
uart_tx inst_uart_tx (
.clk (clk),
.rst_n (rst_n),
.tx_data (data_out_m),
.tx_start (tx_vld_m),
.sw (sw),
.tx (tx),
.tx_done (tx_done)
);
endmodule
iic_master.v
module iic_master (
input clk,
input rst_n,
input [7:0] data_in,
input rx_vld,
input tx_done,
inout sda,
// input sda_in,
// output reg sda_out,
output reg [7:0] data_out,
output tx_vld,
output reg scl
);
//三态门数据
wire sda_in;
reg sda_out;
reg sda_en;
assign sda_in = sda;
assign sda = sda_en ? sda_out : 1'bz;
localparam IDLE = 4'd0,
START = 4'd1,
CTRL_BYTE = 4'd2,
ACK1 = 4'd3,
SLAVE_ADDR = 4'd4,
ACK2 = 4'd5,
W_DATA = 4'd6,
R_DATA = 4'd7,
ACK3 = 4'd8,
STOP = 4'd9;
reg [3:0] state_c; //现态
reg [3:0] state_n; //次态
//数据缓存
reg [7:0] data;
reg [7:0] ctrl_data;
reg ctrl_flg;
wire IDLE_2_START ,
START_2_CTRL_BYTE ,
CTRL_BYTE_2_ACK1 ,
ACK1_2_SLAVE_ADDR ,
SLAVE_ADDR_2_ACK2 ,
ACK2_2_START ,
ACK2_2_W_DATA ,
W_DATA_2_ACK3 ,
ACK3_2_W_DATA ,
ACK1_2_R_DATA ,
R_DATA_2_ACK3 ,
ACK3_2_R_DATA ,
ACK3_2_STOP ,
STOP_2_IDLE ;
reg w_mod;
//400_000深度
reg [6:0] cnt_dp ;
reg add_cnt_dp ;
wire end_cnt_dp ;
parameter depth = 7'd125;
//bit计数
reg [2:0] cnt_bit ;
wire add_cnt_bit ;
wire end_cnt_bit ;
reg [7:0] max_bit;
reg ack;
//ack
always @(*)begin
case(state_c)
ACK1,ACK2,ACK3: begin ack = (sda_in == 0) ? 1'b0 : ack;
end
default: ack = 1'b1;
endcase
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
w_mod <= 1'b0;
end
else if((state_c == CTRL_BYTE) && rx_vld )begin
w_mod <= data_in[0];
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
data <= 8'd0;
end
else if(rx_vld )begin
data <= data_in;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
ctrl_flg <= 1'b1;
end
else if(ACK2_2_START)begin
ctrl_flg <= 1'b0;
end
else if(STOP_2_IDLE)begin
ctrl_flg <= 1'b1;
end
else begin
ctrl_flg <= ctrl_flg;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
ctrl_data <= 8'd0;
end
else if((state_c == CTRL_BYTE) && (!w_mod) )begin
ctrl_data <= data_in;
end
else if(state_c == CTRL_BYTE && w_mod ==1 && ctrl_flg)begin
ctrl_data <= {data_in[7:1],1'b0};
end
else if(state_c == CTRL_BYTE && w_mod ==1 && !ctrl_flg)begin
ctrl_data <= {ctrl_data[7:1],1'b1};
end
else begin
ctrl_data <= ctrl_data;
end
end
always @(*) begin
if((state_c == CTRL_BYTE) || (state_c == SLAVE_ADDR) || (state_c == W_DATA) || (state_c == R_DATA))begin
max_bit <= 8;
end
else begin
max_bit <= 1;
end
end
//速度计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_dp <= 0;
end
else if(add_cnt_dp)begin
if(end_cnt_dp)begin
cnt_dp <= 0;
end
else begin
cnt_dp <= cnt_dp + 1;
end
end
else begin
cnt_dp <= cnt_dp;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
add_cnt_dp <= 0;
end
else if(rx_vld && (state_c != IDLE && state_c != R_DATA)|| IDLE_2_START || ACK1_2_R_DATA || ACK3_2_R_DATA || ACK3_2_STOP || R_DATA_2_ACK3 || ACK2_2_START || ((state_c == CTRL_BYTE) && (ctrl_flg == 0)))begin
add_cnt_dp <= 1;
end
else if((state_c == START || state_c == ACK1 || state_c == ACK2 || state_c == ACK3 || state_c == R_DATA || state_c == STOP) && (end_cnt_bit))begin
add_cnt_dp <= 0;
end
else begin
add_cnt_dp <= add_cnt_dp;
end
end
assign end_cnt_dp = add_cnt_dp && cnt_dp == depth-1;
//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
else begin
cnt_bit <= cnt_bit;
end
end
assign add_cnt_bit = end_cnt_dp && (state_c != IDLE);
assign end_cnt_bit = add_cnt_bit && cnt_bit == (max_bit-1);
//状态机一段
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: state_n <= (IDLE_2_START) ? START : state_c;
START: state_n <= (START_2_CTRL_BYTE) ? CTRL_BYTE : state_c;
CTRL_BYTE: state_n <= (CTRL_BYTE_2_ACK1) ? ACK1 : state_c;
ACK1: state_n <= (ACK1_2_SLAVE_ADDR) ? SLAVE_ADDR : ((ACK1_2_R_DATA) ? R_DATA : state_c);
SLAVE_ADDR: state_n <= (SLAVE_ADDR_2_ACK2) ? ACK2 : state_c;
ACK2: state_n <= (ACK2_2_W_DATA) ? W_DATA : ((ACK2_2_START) ? START : state_c);
W_DATA: state_n <= (W_DATA_2_ACK3) ? ACK3 : state_c;
ACK3: begin
if(ACK3_2_STOP)begin
state_n <= STOP;
end
else if(ACK3_2_R_DATA)begin
state_n <= R_DATA;
end
else if(ACK3_2_W_DATA)begin
state_n <= W_DATA;
end
else begin
state_n <= state_c;
end
end
R_DATA: state_n <= (R_DATA_2_ACK3) ? ACK3 : state_c;
STOP: state_n <= (STOP_2_IDLE) ? IDLE : state_c;
default: state_n <= IDLE;
endcase
end
//描述跳转条件
assign IDLE_2_START = (state_c == IDLE ) && (data== 8'h02);
assign START_2_CTRL_BYTE = (state_c == START ) && (end_cnt_bit);
assign CTRL_BYTE_2_ACK1 = (state_c == CTRL_BYTE ) && (end_cnt_bit);
assign ACK1_2_SLAVE_ADDR = (state_c == ACK1 ) && (end_cnt_bit) && (!ack) && (ctrl_flg);
assign SLAVE_ADDR_2_ACK2 = (state_c == SLAVE_ADDR) && (end_cnt_bit);
assign ACK2_2_W_DATA = (state_c == ACK2 ) && (end_cnt_bit) && (!ack ) && (!w_mod);
assign W_DATA_2_ACK3 = (state_c == W_DATA ) && (cnt_bit == (max_bit-1)&& cnt_dp == 100);
assign ACK3_2_W_DATA = (state_c == ACK3 ) && (end_cnt_bit) && (!ack ) && (!w_mod);
assign ACK2_2_START = (state_c == ACK2 ) && (end_cnt_bit) && (!ack) && w_mod;
assign ACK1_2_R_DATA = (state_c == ACK1 ) && (end_cnt_bit) && (!ack ) && (w_mod) && (!ctrl_flg);
assign ACK3_2_STOP = (state_c == ACK3 ) && (end_cnt_bit) && ((!ack ) && (!w_mod) || ((w_mod) && (sda_out == 1))) && (data == 8'h03);
assign R_DATA_2_ACK3 = (state_c == R_DATA ) && tx_done;
assign ACK3_2_R_DATA = (state_c == ACK3 ) && (end_cnt_bit) && (w_mod) && (data != 8'h03);
assign STOP_2_IDLE = (state_c == STOP ) && (end_cnt_bit) ;
//scl描述
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
scl <= 1'b1;
end
else begin
case(state_c)
IDLE :scl <= 1'b1;
CTRL_BYTE,SLAVE_ADDR,W_DATA,R_DATA,ACK1,ACK2,ACK3 :begin
if((cnt_dp < 7'd31) || (cnt_dp > 7'd93))begin
scl <= 1'b0;
end
else begin
scl <= 1'b1;
end
end
STOP :begin
if(cnt_dp < 7'd31)begin
scl <= 1'b0;
end
else begin
scl <= 1'b1;
end
end
default:begin
scl <= 1'b1;
end
endcase
end
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sda_out <= 1'b1;
end
else begin
case(state_c)
IDLE: sda_out <= 1'b1;
START: sda_out <= (cnt_dp <= ((depth-1)>>1) && add_cnt_dp) ? 1'b1 : 1'b0;
CTRL_BYTE: begin sda_out <= ctrl_data[7-cnt_bit];
if(cnt_dp == 0)begin
sda_out <= 1'b0;
end
end
SLAVE_ADDR,W_DATA: begin sda_out <= data[7-cnt_bit];
if(cnt_dp == 0 )begin
sda_out <= 1'b0;
end
end
R_DATA: sda_out <= 1'b0;
ACK1,ACK2: sda_out <= 1'b0;
ACK3: begin
if(w_mod && (data == 8'h03))begin
sda_out <= 1'b1;
end
else if(!w_mod && (data == 8'h03))begin
sda_out <= 1'b0;
end
else begin
sda_out <= 1'b0;
end
end
STOP: sda_out <= (cnt_dp <= ((depth-1)>>1)) ? 1'b0 : 1'b1;
default: sda_out <= 1'b1;
endcase
end
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sda_en <= 1'b0;
end
else begin
case(state_c)
IDLE :sda_en <= 1'b1;
START,CTRL_BYTE,SLAVE_ADDR,W_DATA,STOP: sda_en <= 1'b1;
ACK1,ACK2: sda_en <=1'b0 ;// sda_en <= (!w_mod) ? 1'b0 : (first_ack) ? 1'b0 :1'b1;
ACK3: sda_en <= (!w_mod) ? 1'b0 : 1'b1;
R_DATA: sda_en <= 1'b0;
default: sda_en <= 1'b1;
endcase
end
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
data_out <= 8'b0;
end
else if((state_c == R_DATA) && (cnt_dp == (depth -1)>>1))begin
data_out[7-cnt_bit] <= sda_in;
end
end
assign tx_vld = (state_c == R_DATA)&& end_cnt_bit && w_mod;//(state_c == ACK) && end_cnt_bit && (!first_ack) && w_mod;
endmodule
uart
详见uart详解
三、代码实现——复用性较高的IIC模块
1、框架设计
同样由PC通过uart串口发送数据到FPGA,在iic协议描述时分为两个模块,一个为对数据以及命令操作的ctrl模块,和iic驱动模块。写操作时,PC只要发送需要写入的数据。读操作时,通过按键控制,按下一次,读出一位数据。
rw_flag为控制iic_driver读写开始的信号。
cmd为执行何种操作的指令。
wr_data为写入的数据。
rd_data为所读出的数据。
trans_done为读出数据结束的标志。
slave_ack为从机给出的应答信号。
2、状态机设计
考虑到复用性,在iic驱动编写时采用这种一帧一帧的数据传输,其分为以下几种情况:
1、带起始信号的写;
2、普通写;
3、带停止信号的读;
4、普通读;
5、带停止信号的读。
由eeprom的介绍可在iic的控制模块中,通过设置cmd来表示所发的数据具体有哪些。
例如,要进行字节写时,要发送3帧数据,分别为带起始信号写,普通写,带停止信号的写。
相对应的为起始信号+控制字节,字地址,数据+停止信号。对应的cmd为0011、0010、1010。
控制模块设计:
iic_ctrl模块能够实现对驱动的简单控制,实现字节写操作和随机地址读。
//---------<rw_flag cmd wr_data>-------------------------------------------------
always @(*)begin
if(state_c == WRITE)begin
case (cnt_byte)
0 : begin rw_flag = 1'b1; cmd = (CMD_START | CMD_SEND) ; wr_data = WR_ID; end
1 : begin rw_flag = 1'b1; cmd = CMD_SEND ; wr_data = cnt_wr_addr; end
2 : begin rw_flag = 1'b1; cmd = (CMD_SEND | CMD_STOP) ; wr_data = rx_data ; end
default : begin
wr_data = 8'd0;
rw_flag = 1'd0;
cmd = 4'd0;
end
endcase
end
else if(state_c == READ)begin
case (cnt_byte)
0: begin rw_flag = 1'b1; cmd =(CMD_START | CMD_SEND) ; wr_data = WR_ID; end
1: begin rw_flag = 1'b1; cmd =(CMD_SEND) ; wr_data = cnt_rd_addr; end
2: begin rw_flag = 1'b1; cmd =(CMD_START | CMD_SEND) ; wr_data = RD_ID; end
3: begin rw_flag = 1'b1; cmd =(CMD_RECV | CMD_STOP) ; wr_data = 8'h0; end
default : begin
wr_data = 8'd0;
rw_flag = 1'd0;
cmd = 4'd0;
end
endcase
end
else begin
wr_data = 8'd0;
rw_flag = 1'd0;
cmd = 4'd0;
end
end
为了方便,这里运用了一个简单的状态机,进行读和写的区分,这是第二段状态机,当进入到不同的读写模式时,进行相应的操作。
3、仿真效果
仿真逻辑:先发两个字节的数据,再依次读出
当检测到有数据输入,就有空闲状态跳转到start状态,由仿真可知开始信号发送正确,时钟计数器计满一个周期后跳转到写数据状态。
写8bit数据,由仿真波形可知数据发送成功。进入w_ack应答状态等待从机响应。
4状态为应答状态,从机模型给出应答,跳转至写状态,进行写字地址操作。写完继续跳转应答状态,有应答后继续跳转最后一带停止信号的写。
此时发送的数据就为写入的数据0010_0100为8’h24,发送完成跳转至停止状态,发送停止信号,完成后跳转至IDLE
下一个数据到来时继续以上操作。
进行读操作,起始信号之后发虚写的a0控制字节+0。
应答有效后,继续写入字地址0000_0000,有应答,发送带起始信号的写,此时写的数据为控制数据+1,有应答后跳转。
跳转到读数据模式,此时总线被主机拉低,接收从机发来的数据0010_0100,接收完成跳转到ack应答状态,由主机发送NACK给从机,发送完成,发送停止信号,此时数据传给uart_tx模块。
当tx将数据发送完成才继续读下一字节数据。仿真验证成功。
4、上板验证
连续按下按键,数据从0000_0000地址开始向后将读数据读出。
连续写入数据,写入也从0000_0000地址开始写入。
不按复位键继续按下读数据按键,其会继续上一个读地址加一继续向后读,按下复位,读取数据,其读取的三个地址的数据为刚刚覆写的数据。上板验证成功。
5、代码
top.v
module top (
input clk,
input rst_n,
input uart_rx,
output uart_tx,
input [0:0] key_in,
output scl,
inout sda
);
wire [7:0] rx_data;
wire rx_vld;
wire ready;
wire [7:0] tx_data;
wire tx_vld;
wire [0:0] key_down;
wire sda_en;
wire sda_in;
wire sda_out;
wire [1:0] sw;
wire tx_done;
assign sw=2'b11;
assign sda_in = sda;
assign sda = sda_en ? sda_out : 1'bz;
iic_top inst_iic_top(
.clk (clk ),
.rst_n (rst_n ),
.rx_data (rx_data ),
.rx_vld (rx_vld ),
.ready (ready ),
.tx_data (tx_data ),
.tx_vld (tx_vld ),
.key_down (key_down),
.sda_in (sda_in ),
.sda_out (sda_out ),
.sda_en (sda_en ),
.scl (scl )
);
fsm_key inst_fsm_key(
.clk (clk),
.rst_n (rst_n),
.key_in (key_in),
.key_down (key_down)
);
uart_rx inst_uart_rx(
.clk (clk),
.rst_n (rst_n),
.rx (uart_rx),
.sw (sw),
.rx_data (rx_data),
.rx_done (rx_vld)
);
uart_tx inst_uart_tx(
.clk (clk),
.rst_n (rst_n),
.tx_data (tx_data),
.tx_start (tx_vld),
.sw (sw),
.tx (uart_tx),
.tx_done (tx_done)
);
endmodule
iic_top.v
module iic_top (
input clk,
input rst_n,
//---------<rx>-------------------------------------------------
input [7:0] rx_data,
input rx_vld,
//---------<tx>-------------------------------------------------
input ready,
output [7:0] tx_data,
output tx_vld,
//---------<key>-------------------------------------------------
input key_down,
//---------<iic>-------------------------------------------------
input sda_in,
output sda_out,
output sda_en,
output scl
);
wire [7:0] rd_data;
wire trans_done;
wire slave_ack;
wire rw_flag;
wire [3:0] cmd;
wire [7:0] wr_data;
iic_ctrl inst_iic_ctrl(
.clk (clk ),
.rst_n (rst_n ),
.key_down (key_down ),
.rx_data (rx_data ),
.rx_vld (rx_vld ),
.ready (ready ),
.tx_data (tx_data ),
.tx_vld (tx_vld ),
.rd_data (rd_data ),
.trans_done (trans_done ),
.slave_ack (slave_ack ),
.rw_flag (rw_flag ),
.cmd (cmd ),
.wr_data (wr_data )
);
iic_driver inst_iic_driver (
.clk (clk ),
.rst_n (rst_n ),
.wr_data (wr_data ),
.cmd (cmd ),
.rw_flag (rw_flag ),
.sda_in (sda_in ),
.sda_out (sda_out ),
.sda_en (sda_en ),
.scl (scl ),
.rd_data (rd_data ),
.slave_ack (slave_ack ),
.trans_done (trans_done)
);
endmodule
iic_ctrl.v
module iic_ctrl (
input clk,
input rst_n,
//key
input key_down,
//uart_rx
input [7:0] rx_data,
input rx_vld,
//uart_tx
input ready,
output reg [7:0] tx_data,
output reg tx_vld,
//iic_driver
input [7:0] rd_data,
input trans_done,
input slave_ack,
output reg rw_flag,
output reg [3:0] cmd,
output reg [7:0] wr_data
);
//简易读写状态机
localparam IDLE = 2'b00,
WRITE = 2'b01,
READ = 2'b10,
DONE = 2'b11;
wire IDLE_2_WRITE,
IDLE_2_READ,
WRITE_2_DONE,
READ_2_DONE;
reg [1:0] state_c ;
reg [1:0] state_n ;
//字节计数器
reg [7:0] cnt_byte;
wire add_cnt_byte;
wire end_cnt_byte;
//写地址计数器
reg [7:0] cnt_wr_addr ;
wire add_cnt_wr_addr;
wire end_cnt_wr_addr;
//读地址计数器
reg [7:0] cnt_rd_addr ;
wire add_cnt_rd_addr;
wire end_cnt_rd_addr;
localparam CMD_START = 4'b0001, //开始命令
CMD_SEND = 4'b0010, //发送命令
CMD_RECV = 4'b0100, //接收命令
CMD_STOP = 4'b1000; //停止命令
localparam WR_ID = 8'ha0;
localparam RD_ID = 8'ha1;
//---------<State Machine>-------------------------------------------------
//第一段:同步时序描述状态转移
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(IDLE_2_WRITE)begin
state_n = WRITE;
end
else if(IDLE_2_READ)begin
state_n = READ;
end
else begin
state_n = state_c;
end
end
WRITE : state_n = (WRITE_2_DONE) ? DONE : state_c;
READ : state_n = (READ_2_DONE) ? DONE : state_c;
DONE : state_n = IDLE;
default : ;
endcase
end
assign IDLE_2_WRITE = (state_c == IDLE ) && rx_vld;
assign IDLE_2_READ = (state_c == IDLE ) && key_down;
assign WRITE_2_DONE = (state_c == WRITE) && end_cnt_byte ;
assign READ_2_DONE = (state_c == READ ) && end_cnt_byte ;
//---------<cnt_byte>-------------------------------------------------
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_byte <= 'd0;
end
else if(add_cnt_byte)begin
if(end_cnt_byte)begin
cnt_byte <= 'd0;
end
else begin
cnt_byte <= cnt_byte + 1'b1;
end
end
end
assign add_cnt_byte = trans_done && (state_c == WRITE || state_c == READ);
assign end_cnt_byte = add_cnt_byte && cnt_byte == ((state_c ==WRITE) ? (3-1) : (4-1));
//---------<cnt_wr_addr>-------------------------------------------------
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_wr_addr <= 'd0;
end
else if(add_cnt_wr_addr)begin
if(end_cnt_wr_addr)begin
cnt_wr_addr <= 'd0;
end
else begin
cnt_wr_addr <= cnt_wr_addr + 1'b1;
end
end
end
assign add_cnt_wr_addr = WRITE_2_DONE;
assign end_cnt_wr_addr = add_cnt_wr_addr && cnt_wr_addr == (8'hff-1);
//---------<cnt_rd_addr>-------------------------------------------------
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_rd_addr <= 'd0;
end
else if(add_cnt_rd_addr)begin
if(end_cnt_rd_addr)begin
cnt_rd_addr <= 'd0;
end
else begin
cnt_rd_addr <= cnt_rd_addr + 1'b1;
end
end
end
assign add_cnt_rd_addr = READ_2_DONE;
assign end_cnt_rd_addr = add_cnt_rd_addr && cnt_rd_addr == (8'hff-1);
//---------<rw_flag cmd wr_data>-------------------------------------------------
always @(*)begin
if(state_c == WRITE)begin
case (cnt_byte)
0 : begin rw_flag = 1'b1; cmd = (CMD_START | CMD_SEND) ; wr_data = WR_ID; end
1 : begin rw_flag = 1'b1; cmd = CMD_SEND ; wr_data = cnt_wr_addr; end
2 : begin rw_flag = 1'b1; cmd = (CMD_SEND | CMD_STOP) ; wr_data = rx_data ; end
default : begin
wr_data = 8'd0;
rw_flag = 1'd0;
cmd = 4'd0;
end
endcase
end
else if(state_c == READ)begin
case (cnt_byte)
0: begin rw_flag = 1'b1; cmd =(CMD_START | CMD_SEND) ; wr_data = WR_ID; end
1: begin rw_flag = 1'b1; cmd =(CMD_SEND) ; wr_data = cnt_rd_addr; end
2: begin rw_flag = 1'b1; cmd =(CMD_START | CMD_SEND) ; wr_data = RD_ID; end
3: begin rw_flag = 1'b1; cmd =(CMD_RECV | CMD_STOP) ; wr_data = 8'h0; end
default : begin
wr_data = 8'd0;
rw_flag = 1'd0;
cmd = 4'd0;
end
endcase
end
else begin
wr_data = 8'd0;
rw_flag = 1'd0;
cmd = 4'd0;
end
end
//---------<tx_data tx_vld>-------------------------------------------------
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_data <= 'd0;
tx_vld <= 'd0;
end
else if(READ_2_DONE)begin
tx_data <= rd_data;
tx_vld <= 1'b1;
end
else begin
tx_data <= tx_data;
tx_vld <=1'b0;
end
end
endmodule
iic_driver.v
module iic_driver #(parameter SYS_CLK = 50_000_000,
IIC_RATE = 200_000)(
input clk,
input rst_n,
input [7:0] wr_data,
input [3:0] cmd,
input rw_flag,
input sda_in,
output reg sda_out,
output reg sda_en,
output reg scl,
output reg [7:0] rd_data,
output reg slave_ack,
output trans_done
);
localparam CMD_START = 4'b0001, //开始命令
CMD_SEND = 4'b0010, //发送命令
CMD_RECV = 4'b0100, //接收命令
CMD_STOP = 4'b1000; //停止命令
//状态描述
localparam IDLE = 3'd0,
START = 3'd1,
SEND = 3'd2,
RECV = 3'd3,
R_ACK = 3'd4,
S_ACK = 3'd5,
STOP = 3'd6;
wire IDLE_2_START,
IDLE_2_SEND,
IDLE_2_RECV,
START_2_SEND,
START_2_RECV,
SEND_2_RACK,
RECV_2_SACK,
RACK_2_IDLE,
RACK_2_STOP,
SACK_2_IDLE,
SACK_2_STOP,
STOP_2_IDLE;
reg [2:0] state_c; //现态
reg [2:0] state_n; //次态
//时钟计数
reg [7:0] cnt_scl;
wire add_cnt_scl;
wire end_cnt_scl;
parameter CNT_SCL_MAX = SYS_CLK/IIC_RATE;
//bit计数
reg [2:0] cnt_bit;
wire add_cnt_bit;
wire end_cnt_bit;
parameter CNT_BIT_MAX = 4'd8;
//时钟计数器
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'b1;
end
end
else begin
cnt_scl <= cnt_scl;
end
end
assign add_cnt_scl = (state_c !== IDLE);
assign end_cnt_scl = add_cnt_scl && (cnt_scl == (CNT_SCL_MAX -1));
//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'b1;
end
end
else begin
cnt_bit <= cnt_bit;
end
end
assign add_cnt_bit = end_cnt_scl && (state_c == SEND || state_c == RECV);
assign end_cnt_bit = add_cnt_bit && (cnt_bit == (CNT_BIT_MAX-1));
//状态机一段
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 (IDLE_2_START) begin
state_n = START;
end
else if(IDLE_2_SEND)begin
state_n = SEND;
end
else if(IDLE_2_RECV)begin
state_n = RECV;
end
else begin
state_n = state_c;
end
end
START: begin
if (START_2_SEND) begin
state_n = SEND;
end
else if(START_2_RECV)begin
state_n = RECV;
end
else begin
state_n = state_c;
end
end
SEND: state_n = (SEND_2_RACK) ? R_ACK : state_c;
RECV: state_n = (RECV_2_SACK) ? S_ACK : state_c;
R_ACK: begin
if (RACK_2_IDLE) begin
state_n = IDLE;
end
else if(RACK_2_STOP)begin
state_n = STOP;
end
else begin
state_n = state_c;
end
end
S_ACK: begin
if (SACK_2_IDLE) begin
state_n = IDLE;
end
else if(SACK_2_STOP)begin
state_n = STOP;
end
else begin
state_n = state_c;
end
end
STOP: state_n = (STOP_2_IDLE) ? IDLE : state_c;
default : state_n = state_c;
endcase
end
//描述转移条件
assign IDLE_2_START = (state_c == IDLE) && (rw_flag) && cmd[0];
assign IDLE_2_SEND = (state_c == IDLE) && (rw_flag) && !cmd[0] && cmd[1];
assign IDLE_2_RECV = (state_c == IDLE) && (rw_flag) && !cmd[0] && cmd[2];
assign START_2_SEND = (state_c == START) && (end_cnt_scl) && cmd[1];
assign START_2_RECV = (state_c == START) && (end_cnt_scl) && cmd[2];
assign SEND_2_RACK = (state_c == SEND) && (end_cnt_bit) ;
assign RECV_2_SACK = (state_c == RECV) && (end_cnt_bit) ;
assign RACK_2_IDLE = (state_c == R_ACK) && (end_cnt_scl) && !cmd[3];
assign SACK_2_IDLE = (state_c == S_ACK) && (end_cnt_scl) && !cmd[3];
assign RACK_2_STOP = (state_c == R_ACK) && (end_cnt_scl) && cmd[3];
assign SACK_2_STOP = (state_c == S_ACK) && (end_cnt_scl) && cmd[3];
assign STOP_2_IDLE = (state_c == STOP) && (end_cnt_scl) ;
//scl描述
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
scl <= 1'b1;
end
else if(state_c != IDLE)begin
if(cnt_scl < ((CNT_SCL_MAX-1) >> 1))begin
scl <= 1'b0;
end
else begin
scl <= 1'b1;
end
end
else begin
scl <= scl;
end
end
//描述数据
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
sda_out <= 1'b1;
sda_en <= 1'b0;
rd_data <= 0;
slave_ack <= 1;
end
else begin
case(state_c)
IDLE: begin
sda_out <= 1'b1;
sda_en <= 1'b0;
end
START: begin
sda_en <= 1'b1;
if(cnt_scl >= (((CNT_SCL_MAX-1) >> 1) + ((CNT_SCL_MAX-1) >>2)))begin
sda_out <= 1'b0;
end
else begin
sda_out <= 1'b1;
end
end
SEND: begin
sda_en <= 1'b1;
if(cnt_scl == ((CNT_SCL_MAX-1)>>2))
sda_out <= wr_data[7-cnt_bit];
end
RECV: begin
sda_en <= 1'b0;
sda_out <= 1'b0;
if(cnt_scl == ((CNT_SCL_MAX-1) >>1)+((CNT_SCL_MAX-1) >>2))begin
rd_data[7-cnt_bit] <= sda_in;
end
end
R_ACK: begin
sda_en <= 1'b0;
sda_out <= 1'b0;
if(cnt_scl == ((CNT_SCL_MAX-1) >>1)+((CNT_SCL_MAX-1) >>2))
slave_ack <= sda_in;
end
S_ACK: begin
sda_en <= 1'b1;
sda_out <= cmd[3] ? 1'b1 : 1'b0;
end
STOP: begin
sda_en <= 1'b1;
if(cnt_scl == ((CNT_SCL_MAX-1) >>2))begin
sda_out <= 1'b0;
end
else if(cnt_scl == ((CNT_SCL_MAX-1) >>1)+((CNT_SCL_MAX-1) >>2))begin
sda_out <= 1'b1;
end
else begin
sda_out <= sda_out;
end
end
endcase
end
end
assign trans_done = RACK_2_IDLE || SACK_2_IDLE ||STOP_2_IDLE;
endmodule
fsm_key.v
/**************************************功能介绍***********************************
Description: 状态机实现按键消抖模板
Change history:
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module fsm_key#(parameter WIDTH = 1,TIME_20MS = 1000_000)(
input clk ,
input rst_n ,
input [WIDTH-1:0] key_in ,
output reg [WIDTH-1:0] key_down
);
//---------<参数定义>---------------------------------------------------------
//状态机参数 独热码编码
localparam IDLE = 4'b0001,//空闲状态
FILTER_DOWN = 4'b0010,//按键按下抖动状态
HOLD = 4'b0100,//按键稳定按下状态
FILTER_UP = 4'b1000;//按键释放抖动状态
//---------<内部信号定义>-----------------------------------------------------
reg [3:0] state_c ;//现态
reg [3:0] state_n ;//次态
reg [WIDTH-1:0] key_r0 ;//同步打拍
reg [WIDTH-1:0] key_r1 ;
reg [WIDTH-1:0] key_r2 ;
wire [WIDTH-1:0] n_edge ;//下降沿
wire [WIDTH-1:0] p_edge ;//上升沿
reg [19:0] cnt_20ms ;//延时计数器(20ms)
wire add_cnt_20ms ;
wire end_cnt_20ms ;
//状态转移条件信号
wire idle2filter_down ;
wire filter_down2idle ;
wire filter_down2hold ;
wire hold2filter_up ;
wire filter_up2hold ;
wire filter_up2idle ;
//****************************************************************
//--状态机
//****************************************************************
//第一段:时序逻辑描述状态转移
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(idle2filter_down)begin
state_n = FILTER_DOWN;
end
else begin
// state_n = IDLE;
state_n = state_c;
end
end
FILTER_DOWN : begin
if(filter_down2idle)begin
state_n = IDLE;
end
else if(filter_down2hold)begin
state_n = HOLD;
end
else begin
state_n = state_c;
end
end
HOLD : begin
if(hold2filter_up)begin
state_n = FILTER_UP;
end
else begin
state_n = state_c;
end
end
FILTER_UP : begin
if(filter_up2hold)begin
state_n = HOLD;
end
else if(filter_up2idle)begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
default: state_n = IDLE;
endcase
end
assign idle2filter_down = (state_c == IDLE) && n_edge;
assign filter_down2idle = (state_c == FILTER_DOWN) && p_edge;
assign filter_down2hold = (state_c == FILTER_DOWN) && end_cnt_20ms && !p_edge;
assign hold2filter_up = (state_c == HOLD) && p_edge;
assign filter_up2hold = (state_c == FILTER_UP) && n_edge;
assign filter_up2idle = (state_c == FILTER_UP) && end_cnt_20ms;
//****************************************************************
//--n_edge、p_edge
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_r0 <= {WIDTH{1'b1}};
key_r1 <= {WIDTH{1'b1}};
key_r2 <= {WIDTH{1'b1}};
end
else begin
key_r0 <= key_in;
key_r1 <= key_r0;
key_r2 <= key_r1;
end
end
assign n_edge = ~key_r1 & key_r2;//下降沿
assign p_edge = ~key_r2 & key_r1;//上升沿
//****************************************************************
//--cnt_20ms
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_20ms <= 'd0;
end
else if(add_cnt_20ms)begin
if(end_cnt_20ms || filter_down2idle || filter_up2hold)begin
cnt_20ms <= 'd0;
end
else begin
cnt_20ms <= cnt_20ms + 1'b1;
end
end
end
assign add_cnt_20ms = (state_c == FILTER_DOWN) || (state_c == FILTER_UP);
assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == TIME_20MS - 1;
//****************************************************************
//--key_down
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_down <= 'd0;
end
else begin
key_down <= filter_down2hold ? ~key_r2 : 1'b0;
end
end
endmodule
uart
详见uart详解
四、总结
两篇文章主要介绍了iic协议,以及去控制eeprom读写的操作,其中包括自己在学习中的个人理解,和一种复用性和可读性较高的iic写法,前者在学习的过程中较为容易去理解协议,但复用性与可读性较低,后者相反。