基于FPGA的IIC控制EEPROM读写(2)
基于FPGA的IIC控制EEPROM读写
文章目录
- 基于FPGA的IIC控制EEPROM读写
- 一、EEPROM简介
- 二、代码实现——个人理解
- 1、状态机
- 2、仿真效果
- 3、上板验证
- 4、代码
- top.v
- iic_master.v
- uart
- 三、代码实现——复用性较高的IIC模块
- 1、框架设计
- 2、状态机设计
- 3、仿真效果
- 4、上板验证
- 5、代码
- top.v
- iic_top.v
- iic_ctrl.v
- iic_driver.v
- fsm_key.v
- uart
- 四、总结
一、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)beginif(!rst_n)beginctrl_data <= 8'd0;endelse if((state_c == CTRL_BYTE) && (!w_mod) )beginctrl_data <= data_in;endelse if(state_c == CTRL_BYTE && w_mod ==1 && ctrl_flg)beginctrl_data <= {data_in[7:1],1'b0};endelse if(state_c == CTRL_BYTE && w_mod ==1 && !ctrl_flg)beginctrl_data <= {ctrl_data[7:1],1'b1};endelse beginctrl_data <= ctrl_data;end
end
设置一个控制字节寄存器,当为读模式,且第一次进入发送控制字节状态(由ctrl_flg)控制,真实发送给从机的控制字节为数据前七位拼一个0发送给从机,收到应答后发送字地址。
always@(posedge clk or negedge rst_n)beginif(!rst_n)beginw_mod <= 1'b0;endelse if((state_c == CTRL_BYTE) && rx_vld )beginw_mod <= data_in[0];end
end
在接收到由电脑发送来的控制字节时,将“真”读写模式寄存下来,当发送完字地址,有应答后,检测到真读写模式为读后跳转到START状态发送起始信号,之后再次进入发送控制字节状态。
always @(posedge clk or negedge rst_n)beginif(!rst_n)beginctrl_flg <= 1'b1;endelse if(ACK2_2_START)beginctrl_flg <= 1'b0;endelse if(STOP_2_IDLE)beginctrl_flg <= 1'b1;endelse beginctrl_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 @(*)begincase(state_c)ACK1,ACK2,ACK3: begin ack = (sda_in == 0) ? 1'b0 : ack; enddefault: ack = 1'b1;endcase
end always@(posedge clk or negedge rst_n)beginif(!rst_n)beginw_mod <= 1'b0;endelse if((state_c == CTRL_BYTE) && rx_vld )beginw_mod <= data_in[0];end
endalways @(posedge clk or negedge rst_n) beginif(!rst_n)begindata <= 8'd0;endelse if(rx_vld )begindata <= data_in;end
endalways @(posedge clk or negedge rst_n)beginif(!rst_n)beginctrl_flg <= 1'b1;endelse if(ACK2_2_START)beginctrl_flg <= 1'b0;endelse if(STOP_2_IDLE)beginctrl_flg <= 1'b1;endelse beginctrl_flg <= ctrl_flg;end
endalways @(posedge clk or negedge rst_n)beginif(!rst_n)beginctrl_data <= 8'd0;endelse if((state_c == CTRL_BYTE) && (!w_mod) )beginctrl_data <= data_in;endelse if(state_c == CTRL_BYTE && w_mod ==1 && ctrl_flg)beginctrl_data <= {data_in[7:1],1'b0};endelse if(state_c == CTRL_BYTE && w_mod ==1 && !ctrl_flg)beginctrl_data <= {ctrl_data[7:1],1'b1};endelse beginctrl_data <= ctrl_data;end
endalways @(*) beginif((state_c == CTRL_BYTE) || (state_c == SLAVE_ADDR) || (state_c == W_DATA) || (state_c == R_DATA))beginmax_bit <= 8;endelse beginmax_bit <= 1;end
end//速度计数器
always @(posedge clk or negedge rst_n) beginif(!rst_n)begincnt_dp <= 0;endelse if(add_cnt_dp)beginif(end_cnt_dp)begincnt_dp <= 0;endelse begincnt_dp <= cnt_dp + 1;endendelse begincnt_dp <= cnt_dp;end
endalways @(posedge clk or negedge rst_n) beginif(!rst_n)beginadd_cnt_dp <= 0;endelse 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)))beginadd_cnt_dp <= 1;endelse if((state_c == START || state_c == ACK1 || state_c == ACK2 || state_c == ACK3 || state_c == R_DATA || state_c == STOP) && (end_cnt_bit))beginadd_cnt_dp <= 0;endelse beginadd_cnt_dp <= add_cnt_dp;end
endassign end_cnt_dp = add_cnt_dp && cnt_dp == depth-1; //bit计数器
always @(posedge clk or negedge rst_n) beginif(!rst_n)begincnt_bit <= 0;endelse if(add_cnt_bit)beginif(end_cnt_bit)begincnt_bit <= 0;endelse begincnt_bit <= cnt_bit + 1;endendelse begincnt_bit <= cnt_bit;end
endassign 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) beginif(!rst_n)beginstate_c <= IDLE;endelse beginstate_c <= state_n;end
end//状态机二段
always @(*) begincase(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: beginif(ACK3_2_STOP)beginstate_n <= STOP;endelse if(ACK3_2_R_DATA)beginstate_n <= R_DATA;endelse if(ACK3_2_W_DATA)beginstate_n <= W_DATA;endelse beginstate_n <= state_c;endendR_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) beginif(!rst_n)beginscl <= 1'b1;endelse begincase(state_c) IDLE :scl <= 1'b1;CTRL_BYTE,SLAVE_ADDR,W_DATA,R_DATA,ACK1,ACK2,ACK3 :beginif((cnt_dp < 7'd31) || (cnt_dp > 7'd93))beginscl <= 1'b0;endelse beginscl <= 1'b1;endendSTOP :beginif(cnt_dp < 7'd31)beginscl <= 1'b0;endelse beginscl <= 1'b1;endenddefault:beginscl <= 1'b1;endendcaseend
endalways@(posedge clk or negedge rst_n)beginif(!rst_n)beginsda_out <= 1'b1;endelse begincase(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)beginsda_out <= 1'b0;endendSLAVE_ADDR,W_DATA: begin sda_out <= data[7-cnt_bit];if(cnt_dp == 0 )beginsda_out <= 1'b0;endendR_DATA: sda_out <= 1'b0;ACK1,ACK2: sda_out <= 1'b0;ACK3: beginif(w_mod && (data == 8'h03))begin sda_out <= 1'b1;endelse if(!w_mod && (data == 8'h03))beginsda_out <= 1'b0;endelse beginsda_out <= 1'b0;end endSTOP: sda_out <= (cnt_dp <= ((depth-1)>>1)) ? 1'b0 : 1'b1;default: sda_out <= 1'b1;endcaseend
end
always@(posedge clk or negedge rst_n)beginif(!rst_n)beginsda_en <= 1'b0;endelse begincase(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;endcaseend
endalways@(posedge clk or negedge rst_n)beginif(!rst_n)begindata_out <= 8'b0;endelse if((state_c == R_DATA) && (cnt_dp == (depth -1)>>1))begindata_out[7-cnt_bit] <= sda_in;end
endassign 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 @(*)beginif(state_c == WRITE)begincase (cnt_byte)0 : begin rw_flag = 1'b1; cmd = (CMD_START | CMD_SEND) ; wr_data = WR_ID; end1 : begin rw_flag = 1'b1; cmd = CMD_SEND ; wr_data = cnt_wr_addr; end2 : begin rw_flag = 1'b1; cmd = (CMD_SEND | CMD_STOP) ; wr_data = rx_data ; enddefault : beginwr_data = 8'd0;rw_flag = 1'd0;cmd = 4'd0; endendcaseendelse if(state_c == READ)begincase (cnt_byte)0: begin rw_flag = 1'b1; cmd =(CMD_START | CMD_SEND) ; wr_data = WR_ID; end1: begin rw_flag = 1'b1; cmd =(CMD_SEND) ; wr_data = cnt_rd_addr; end2: begin rw_flag = 1'b1; cmd =(CMD_START | CMD_SEND) ; wr_data = RD_ID; end3: begin rw_flag = 1'b1; cmd =(CMD_RECV | CMD_STOP) ; wr_data = 8'h0; enddefault : beginwr_data = 8'd0;rw_flag = 1'd0;cmd = 4'd0; endendcaseendelse beginwr_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,//keyinput key_down,//uart_rxinput [7:0] rx_data,input rx_vld,//uart_txinput ready,output reg [7:0] tx_data,output reg tx_vld,//iic_driverinput [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)beginstate_c <= IDLE;end else begin state_c <= state_n;end
end//第二段:组合逻辑判断状态转移条件,描述状态转移规律
always @(*) begincase(state_c)IDLE : beginif(IDLE_2_WRITE)beginstate_n = WRITE;endelse if(IDLE_2_READ)beginstate_n = READ;endelse beginstate_n = state_c;endendWRITE : state_n = (WRITE_2_DONE) ? DONE : state_c;READ : state_n = (READ_2_DONE) ? DONE : state_c;DONE : state_n = IDLE;default : ;endcase
endassign 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)begincnt_byte <= 'd0;end else if(add_cnt_byte)begin if(end_cnt_byte)begin cnt_byte <= 'd0;endelse 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)begincnt_wr_addr <= 'd0;end else if(add_cnt_wr_addr)begin if(end_cnt_wr_addr)begin cnt_wr_addr <= 'd0;endelse 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)begincnt_rd_addr <= 'd0;end else if(add_cnt_rd_addr)begin if(end_cnt_rd_addr)begin cnt_rd_addr <= 'd0;endelse 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 @(*)beginif(state_c == WRITE)begincase (cnt_byte)0 : begin rw_flag = 1'b1; cmd = (CMD_START | CMD_SEND) ; wr_data = WR_ID; end1 : begin rw_flag = 1'b1; cmd = CMD_SEND ; wr_data = cnt_wr_addr; end2 : begin rw_flag = 1'b1; cmd = (CMD_SEND | CMD_STOP) ; wr_data = rx_data ; enddefault : beginwr_data = 8'd0;rw_flag = 1'd0;cmd = 4'd0; endendcaseendelse if(state_c == READ)begincase (cnt_byte)0: begin rw_flag = 1'b1; cmd =(CMD_START | CMD_SEND) ; wr_data = WR_ID; end1: begin rw_flag = 1'b1; cmd =(CMD_SEND) ; wr_data = cnt_rd_addr; end2: begin rw_flag = 1'b1; cmd =(CMD_START | CMD_SEND) ; wr_data = RD_ID; end3: begin rw_flag = 1'b1; cmd =(CMD_RECV | CMD_STOP) ; wr_data = 8'h0; enddefault : beginwr_data = 8'd0;rw_flag = 1'd0;cmd = 4'd0; endendcaseendelse beginwr_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)begintx_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
endendmodule
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) beginif (!rst_n) begincnt_scl <= 0;end else if(add_cnt_scl)beginif (end_cnt_scl) begincnt_scl <= 0;endelse begincnt_scl <= cnt_scl + 1'b1;end endelse begincnt_scl <= cnt_scl;end
endassign 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)begincnt_bit <= 0;endelse if(add_cnt_bit)beginif(end_cnt_bit)begincnt_bit <= 0;endelse begincnt_bit <= cnt_bit + 1'b1;endendelse begincnt_bit <= cnt_bit;end
endassign 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) beginif (!rst_n) beginstate_c <= IDLE;end else beginstate_c <= state_n;end
end//状态机二段
always @(*)begincase(state_c)IDLE: beginif (IDLE_2_START) beginstate_n = START;end else if(IDLE_2_SEND)beginstate_n = SEND;endelse if(IDLE_2_RECV)beginstate_n = RECV;endelse beginstate_n = state_c;endendSTART: begin if (START_2_SEND) beginstate_n = SEND;end else if(START_2_RECV)beginstate_n = RECV;endelse beginstate_n = state_c;endendSEND: state_n = (SEND_2_RACK) ? R_ACK : state_c;RECV: state_n = (RECV_2_SACK) ? S_ACK : state_c;R_ACK: beginif (RACK_2_IDLE) beginstate_n = IDLE;endelse if(RACK_2_STOP)beginstate_n = STOP;endelse beginstate_n = state_c;endendS_ACK: beginif (SACK_2_IDLE) beginstate_n = IDLE;endelse if(SACK_2_STOP)beginstate_n = STOP;endelse beginstate_n = state_c;endendSTOP: 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) beginif(!rst_n)beginscl <= 1'b1;end else if(state_c != IDLE)beginif(cnt_scl < ((CNT_SCL_MAX-1) >> 1))beginscl <= 1'b0;endelse beginscl <= 1'b1;endendelse beginscl <= scl;end
end//描述数据
always @(posedge clk or negedge rst_n) beginif(!rst_n)beginsda_out <= 1'b1;sda_en <= 1'b0;rd_data <= 0;slave_ack <= 1;endelse begincase(state_c)IDLE: beginsda_out <= 1'b1;sda_en <= 1'b0;endSTART: beginsda_en <= 1'b1;if(cnt_scl >= (((CNT_SCL_MAX-1) >> 1) + ((CNT_SCL_MAX-1) >>2)))beginsda_out <= 1'b0;endelse beginsda_out <= 1'b1;endendSEND: begin sda_en <= 1'b1;if(cnt_scl == ((CNT_SCL_MAX-1)>>2))sda_out <= wr_data[7-cnt_bit];endRECV: beginsda_en <= 1'b0;sda_out <= 1'b0;if(cnt_scl == ((CNT_SCL_MAX-1) >>1)+((CNT_SCL_MAX-1) >>2))beginrd_data[7-cnt_bit] <= sda_in;endendR_ACK: beginsda_en <= 1'b0;sda_out <= 1'b0;if(cnt_scl == ((CNT_SCL_MAX-1) >>1)+((CNT_SCL_MAX-1) >>2))slave_ack <= sda_in;endS_ACK: beginsda_en <= 1'b1;sda_out <= cmd[3] ? 1'b1 : 1'b0;endSTOP: beginsda_en <= 1'b1;if(cnt_scl == ((CNT_SCL_MAX-1) >>2))beginsda_out <= 1'b0;endelse if(cnt_scl == ((CNT_SCL_MAX-1) >>1)+((CNT_SCL_MAX-1) >>2))beginsda_out <= 1'b1;endelse beginsda_out <= sda_out;endendendcase end
endassign 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)beginstate_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;endelse begin // state_n = IDLE;state_n = state_c;endendFILTER_DOWN : begin if(filter_down2idle)begin state_n = IDLE;endelse if(filter_down2hold)begin state_n = HOLD;endelse begin state_n = state_c;endendHOLD : begin if(hold2filter_up)begin state_n = FILTER_UP;endelse begin state_n = state_c;endendFILTER_UP : begin if(filter_up2hold)begin state_n = HOLD;endelse if(filter_up2idle)begin state_n = IDLE;endelse begin state_n = state_c;endenddefault: state_n = IDLE;endcaseendassign 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)beginkey_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 endassign 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)begincnt_20ms <= 'd0;end else if(add_cnt_20ms)begin if(end_cnt_20ms || filter_down2idle || filter_up2hold)begin cnt_20ms <= 'd0;endelse begin cnt_20ms <= cnt_20ms + 1'b1;end endend 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)beginkey_down <= 'd0;end else begin key_down <= filter_down2hold ? ~key_r2 : 1'b0;endendendmodule
uart
详见uart详解
四、总结
两篇文章主要介绍了iic协议,以及去控制eeprom读写的操作,其中包括自己在学习中的个人理解,和一种复用性和可读性较高的iic写法,前者在学习的过程中较为容易去理解协议,但复用性与可读性较低,后者相反。