SPI读写EEPROM实验[FPGA][verilog]
SPI读写EEPROM实验
本次实验采用的是皮赛电子ETL3-7A35T的来发版,使用的是XILINX公司的Artix-7系列芯片型号为 XC7A35T-2FGG484。使用的EEPROM的型号是93LC46。完整工程在最下面。
本次实验完成的功能有:
- 能够执行对板上EEROM读写;
- 使用拨码开关控制SPI工作方式,读或写;
- 时钟信号为开发板上时钟信号(晶振50MHz);
目录
EEPROM的信息
使用的EEPROM的芯片引脚为下图所示:
CS、CLK、DI、DO分别为片选信号、EPPROM芯片的工作时钟为(即SPI中的SCK)、DATA_INPUT、DATA_OUT。
根据下表,由于VCC为3.3V,故EEPROM工作在1MHz。
NU管脚悬空。
ORG为控制芯片工作模式的信号,ORG为1,工作在16bit;ORG为0,工作在8bit。(本次实验ORG为1,即读写的数据都是16bit)下图为ORG为1时的指令信息(不同的EEPROM的命令不一样,自己找手册看)
状态机的设计
为了实现读写,设计了一个状态机
状态机代码如下:
reg [5 : 0] wr_state_cnt = 6'b0;//状态计时器
reg [4 : 0] rd_state_cnt = 5'b0;//状态计时器
//state
always @ (posedge spi_clk)
begin
if(rst) state <= idle;
else
case(state)
idle:
begin
if(true_read) state <= s_read;//按键发来的读请求
else if(true_write) state <= s_write;//按键发来的写请求
end
s_read: state <= (rd_state_cnt == 5'd26) ? idle : s_read;
s_write: state <= (wr_state_cnt == 6'd37) ? idle : s_write;
endcase
end
写状态
先介绍芯片的写状态
每次上电时,要先写入EWEN指令,根据上面的表可得知指令为10011XXXX(不同的EEPROM的命令不一样,自己找手册看),这里的X可以随意赋值。
reg [8 : 0] ewen_data = 9'b100110000;//写使能
在EWEN指令写入后,会需要等一段时间后(不要立刻发下一段指令,根据ROM的手册,ROM在工作在1MHz时,在CS指令发送完成后,CS需要保持至少250ns的低电平,结尾有详细解释),才可以发送写WRITE指令,101为写命令,000000为写入数据的地址,0000_1111_1111_0001为写入的数据。
reg [24 : 0] write_data = 25'b101_000000_0000_1111_1111_0001;//写命令_地址_数据
在代码中的表现为:
if(rst) mo <= 1'b0;
else if(state == s_write)判断是否为写状态
begin
case(wr_state_cnt)//写状态计数器
6'd1: mo <= ewen_data[8];//EWEN指令
6'd2: mo <= ewen_data[7];//EWEN指令
6'd3: mo <= ewen_data[6];//EWEN指令
6'd4: mo <= ewen_data[5];//EWEN指令
6'd5: mo <= ewen_data[4];//EWEN指令
6'd6: mo <= ewen_data[3];//EWEN指令
6'd7: mo <= ewen_data[2];//EWEN指令
6'd8: mo <= ewen_data[1];//EWEN指令
6'd9: mo <= ewen_data[0];//EWEN指令
//停一段时间再发送下一个指令
6'd12 : mo <= write_data[24]; //写
6'd13 : mo <= write_data[23]; //写
6'd14 : mo <= write_data[22]; //写
6'd15 : mo <= write_data[21]; //写
6'd16 : mo <= write_data[20]; //写
6'd17 : mo <= write_data[19]; //写
6'd18 : mo <= write_data[18]; //写
6'd19 : mo <= write_data[17]; //写
6'd20 : mo <= write_data[16]; //写
6'd21 : mo <= write_data[15]; //写
6'd22 : mo <= write_data[14]; //写
6'd23 : mo <= write_data[13]; //写
6'd24 : mo <= write_data[12]; //写
6'd25 : mo <= write_data[11]; //写
6'd26 : mo <= write_data[10]; //写
6'd27 : mo <= write_data[9]; //写
6'd28 : mo <= write_data[8]; //写
6'd29 : mo <= write_data[7]; //写
6'd30 : mo <= write_data[6]; //写
6'd31 : mo <= write_data[5]; //写
6'd32 : mo <= write_data[4]; //写
6'd33 : mo <= write_data[3]; //写
6'd34 : mo <= write_data[2]; //写
6'd35 : mo <= write_data[1]; //写
6'd36 : mo <= write_data[0]; //写
default: mo <= 1'b0;
endcase
end
读状态
读状态比较简单:
根据上面的表格,读命令是下面这种,命令+地址。地址为000000。
reg [8 : 0] write_data_rd = 9'b110_000000;//读命令_地址
在代码中的表现为:
else if(state == s_read)//判断是否是读状态
begin
case(rd_state_cnt)
5'd1: mo <= write_data_rd[8];//读命令
5'd2: mo <= write_data_rd[7];//读命令
5'd3: mo <= write_data_rd[6];//读命令
5'd4: mo <= write_data_rd[5];//读命令
5'd5: mo <= write_data_rd[4];//读命令
5'd6: mo <= write_data_rd[3];//读命令
5'd7: mo <= write_data_rd[2];//读命令
5'd8: mo <= write_data_rd[1];//读命令
5'd9: mo <= write_data_rd[0];//读命令
endcase
end
发送完读命令后,我们将从EEPROM中收到数据,代码如下:
看这上下两段代码,这里需要注意的是当rd_state_cnt等于10时,没有任何操作,是因为此时EEPROM发送的数据是0,在下一个周期ROM才能发送有效数据。
always @ (posedge spi_clk)
begin
if(rst) read_data <= 8'b0;
else if(state == s_read)
begin
case(rd_state_cnt)
6'd11 : read_data[15] <=mi ;//EEPROM发送的数据
6'd12 : read_data[14] <=mi ;//EEPROM发送的数据
6'd13 : read_data[13] <=mi ;//EEPROM发送的数据
6'd14 : read_data[12] <=mi ;//EEPROM发送的数据
6'd15 : read_data[11] <=mi ;//EEPROM发送的数据
6'd16 : read_data[10] <=mi ;//EEPROM发送的数据
6'd17 : read_data[9] <=mi ;//EEPROM发送的数据
6'd18 : read_data[8] <=mi ;//EEPROM发送的数据
6'd19 : read_data[7] <=mi ;//EEPROM发送的数据
6'd20 : read_data[6] <=mi ;//EEPROM发送的数据
6'd21 : read_data[5] <=mi ;//EEPROM发送的数据
6'd22 : read_data[4] <=mi ;//EEPROM发送的数据
6'd23 : read_data[3] <=mi ;//EEPROM发送的数据
6'd24 : read_data[2] <=mi ;//EEPROM发送的数据
6'd25 : read_data[1] <=mi ;//EEPROM发送的数据
6'd26 : read_data[0] <=mi ;//EEPROM发送的数据
endcase
end
end
CS信号的生成
我们还需要CS信号的生成,根据此时状态机的状态和命令的执行情况生成CS。
代码如下:
always @ (posedge spi_clk)
begin
if(rst) cs <= 1'b0;
else if(wr_state_cnt == 6'd1) cs <= 1'b1;//刚进入写状态,准备发EWEN指令
else if(wr_state_cnt == 6'd10) cs <= 1'b0;//发完了EWEN指令
else if(wr_state_cnt == 6'd12) cs <= 1'b1;//我要发WRITE指令
else if(wr_state_cnt == 6'd37) cs <= 1'b0;//WRITE发完了
// else if(wr_state_cnt == 6'd39) cs <= 1'b1;//懒dog是不会发送EWDS指令的
// else if(wr_state_cnt == 6'd49) cs <= 1'b0;//懒dog是不会发送EWDS指令的
//
else if(rd_state_cnt == 5'd1) cs <= 1'b1;//我要开始读了
else if(rd_state_cnt == 5'd26) cs <= 1'b0;//我读完了
else cs <= cs;//我不想动
end
核心代码总结
至此核心代码已基本讲解完毕,核心代码如下:
//状态机
always @ (posedge spi_clk)
begin
if(rst) state <= idle;
else
case(state)
idle:
begin
if(true_read) state <= s_read;
else if(true_write) state <= s_write;
end
s_read: state <= (rd_state_cnt == 5'd26) ? idle : s_read;
s_write: state <= (wr_state_cnt == 6'd37) ? idle : s_write;
endcase
end
//CS信号
always @ (posedge spi_clk)
begin
if(rst) cs <= 1'b0;
else if(wr_state_cnt == 6'd1) cs <= 1'b1;//刚进入写状态,准备发EWEN指令
else if(wr_state_cnt == 6'd10) cs <= 1'b0;//发完了EWEN指令
else if(wr_state_cnt == 6'd12) cs <= 1'b1;//我要发WRITE指令
else if(wr_state_cnt == 6'd37) cs <= 1'b0;//WRITE发完了
// else if(wr_state_cnt == 6'd39) cs <= 1'b1;//蓝狗是不会发送EWDS指令的
// else if(wr_state_cnt == 6'd49) cs <= 1'b0;//蓝狗是不会发送EWDS指令的
//
else if(rd_state_cnt == 5'd1) cs <= 1'b1;//我要开始读了
else if(rd_state_cnt == 5'd26) cs <= 1'b0;//我读完了
else cs <= cs;//我不想动
end
//发给EEPROM的信号
always @ (posedge spi_clk)
begin
if(rst) mo <= 1'b0;
else if(state == s_write)
begin
case(wr_state_cnt)
6'd1: mo <= ewen_data[8];//EWEN指令
6'd2: mo <= ewen_data[7];//EWEN指令
6'd3: mo <= ewen_data[6];//EWEN指令
6'd4: mo <= ewen_data[5];//EWEN指令
6'd5: mo <= ewen_data[4];//EWEN指令
6'd6: mo <= ewen_data[3];//EWEN指令
6'd7: mo <= ewen_data[2];//EWEN指令
6'd8: mo <= ewen_data[1];//EWEN指令
6'd9: mo <= ewen_data[0];//EWEN指令
//
6'd12 : mo <= write_data[24]; //写
6'd13 : mo <= write_data[23]; //写
6'd14 : mo <= write_data[22]; //写
6'd15 : mo <= write_data[21]; //写
6'd16 : mo <= write_data[20]; //写
6'd17 : mo <= write_data[19]; //写
6'd18 : mo <= write_data[18]; //写
6'd19 : mo <= write_data[17]; //写
6'd20 : mo <= write_data[16]; //写
6'd21 : mo <= write_data[15]; //写
6'd22 : mo <= write_data[14]; //写
6'd23 : mo <= write_data[13]; //写
6'd24 : mo <= write_data[12]; //写
6'd25 : mo <= write_data[11]; //写
6'd26 : mo <= write_data[10]; //写
6'd27 : mo <= write_data[9]; //写
6'd28 : mo <= write_data[8]; //写
6'd29 : mo <= write_data[7]; //写
6'd30 : mo <= write_data[6]; //写
6'd31 : mo <= write_data[5]; //写
6'd32 : mo <= write_data[4]; //写
6'd33 : mo <= write_data[3]; //写
6'd34 : mo <= write_data[2]; //写
6'd35 : mo <= write_data[1]; //写
6'd36 : mo <= write_data[0]; //写
default: mo <= 1'b0;
endcase
end
else if(state == s_read)//判断是否是读状态
begin
case(rd_state_cnt)
5'd1: mo <= write_data_rd[8];//读命令
5'd2: mo <= write_data_rd[7];//读命令
5'd3: mo <= write_data_rd[6];//读命令
5'd4: mo <= write_data_rd[5];//读命令
5'd5: mo <= write_data_rd[4];//读命令
5'd6: mo <= write_data_rd[3];//读命令
5'd7: mo <= write_data_rd[2];//读命令
5'd8: mo <= write_data_rd[1];//读命令
5'd9: mo <= write_data_rd[0];//读命令
endcase
end
end
//对EEPROM发送给fpga的信号进行接受
always @ (posedge spi_clk)
begin
if(rst) read_data <= 8'b0;
else if(state == s_read)
begin
case(rd_state_cnt)
6'd11 : read_data[15] <=mi ;//EEPROM发送的数据
6'd12 : read_data[14] <=mi ;//EEPROM发送的数据
6'd13 : read_data[13] <=mi ;//EEPROM发送的数据
6'd14 : read_data[12] <=mi ;//EEPROM发送的数据
6'd15 : read_data[11] <=mi ;//EEPROM发送的数据
6'd16 : read_data[10] <=mi ;//EEPROM发送的数据
6'd17 : read_data[9] <=mi ;//EEPROM发送的数据
6'd18 : read_data[8] <=mi ;//EEPROM发送的数据
6'd19 : read_data[7] <=mi ;//EEPROM发送的数据
6'd20 : read_data[6] <=mi ;//EEPROM发送的数据
6'd21 : read_data[5] <=mi ;//EEPROM发送的数据
6'd22 : read_data[4] <=mi ;//EEPROM发送的数据
6'd23 : read_data[3] <=mi ;//EEPROM发送的数据
6'd24 : read_data[2] <=mi ;//EEPROM发送的数据
6'd25 : read_data[1] <=mi ;//EEPROM发送的数据
6'd26 : read_data[0] <=mi ;//EEPROM发送的数据
endcase
end
end
外围代码
我们还需要写分频器,晶振为50MHz,EEPROM的工作频率是1MHz。
还要写wr_state_cnt,rd_state_cnt累加
。
解释
上面有提到:在EWEN指令写入后,会需要等一段时间后(不要立刻发下一段指令,根据ROM的手册,ROM在工作在1MHz时,在CS指令发送完成后,CS需要保持至少250ns的低电平,结尾有详细解释)
下图为EWEN的时序图,图上的6对应下图的Tcsl。
结尾
完整代码。
module E2PROM_TOP(
input clkin,
input mi,
input write,
input read,
input sw1,
input sw2,
input sw3,
output sck,
output reg cs,
output reg mo,
// output TEST,
output reg [15:0] data_out
);
//generate spi clk
parameter div =49;
reg [5:0] div_cnt = 6'b0;
reg spi_clk = 1'b0;
always @ (posedge clkin)
begin
if(div_cnt == 49) div_cnt <= 6'b0;
else div_cnt <= div_cnt + 1;
end
always @ (posedge clkin)
begin
if(div_cnt == 24) spi_clk <= !spi_clk;
else if(div_cnt == div) spi_clk <= !spi_clk;
else spi_clk <= spi_clk;
end
assign sck = !spi_clk;
//generate rst
reg rst = 1'b0;
reg [7:0] rst_cnt = 8'b0;
always @ (posedge clkin)
begin
if(&rst_cnt) rst_cnt <= rst_cnt;
else rst_cnt <= rst_cnt + 1;
end
always @ (posedge clkin)
begin
if(&rst_cnt) rst <= 1'b0;
else rst <= 1'b1;
end
//消抖
//generate true_write
reg [1 : 0] write_s = 2'b0;
reg [29 : 0] write_cnt = 30'b0;
reg true_write = 1'b0;
always @ (posedge spi_clk)
begin
write_s <= {write_s,!write};
end
always @ (posedge spi_clk)
begin
if(rst) write_cnt <= 30'b0;
else if(write_s == 2'b11) write_cnt <= write_cnt + 1;
else write_cnt <= 30'b0;
end
//
always @ (posedge spi_clk)
begin
if(rst) true_write <= 1'b0;
else if(true_write) true_write <= 1'b0;
else if(&write_cnt[12:0]) true_write <= 1'b1;
else true_write <= 1'b0;
end
//消抖
//generate true_read
reg [1 : 0] read_s = 2'b0;
reg [29 : 0] read_cnt = 30'b0;
reg true_read = 1'b0;
always @ (posedge spi_clk)
begin
read_s <= {read_s,!read};
end
always @ (posedge spi_clk)
begin
if(rst) read_cnt <= 30'b0;
else if(read_s == 2'b11) read_cnt <= read_cnt + 1;
else read_cnt <= 30'b0;
end
always @ (posedge spi_clk)
begin
if(rst) true_read <= 1'b0;
else if(true_read) true_read <= 1'b0;
else if(&read_cnt[12:0]) true_read <= 1'b1;
else true_read <= 1'b0;
end
//***********************************************************
//state
parameter idle = 2'b00,
s_write = 2'b01,
s_read = 2'b10;
reg [1 : 0] state = 2'b0;
reg [8 : 0] ewen_data = 9'b100110000;//写使能
reg [24 : 0] write_data = 25'b101_000000_0000_1111_1111_0001;//写命令_地址_数据
//9+16
// reg [9 : 0] ewds_data = 10'b100_0000000;//没用
reg [8 : 0] write_data_rd = 9'b110_000000;//读命令_地址
reg [15 : 0] read_data;//读出的数据
reg [5 : 0] wr_state_cnt = 6'b0;//状态计时器
reg [4 : 0] rd_state_cnt = 5'b0;//状态计时器
//state
always @ (posedge spi_clk)
begin
if(rst) state <= idle;
else
case(state)
idle:
begin
if(true_read) state <= s_read;
else if(true_write) state <= s_write;
end
s_read: state <= (rd_state_cnt == 5'd26) ? idle : s_read;
s_write: state <= (wr_state_cnt == 6'd37) ? idle : s_write;
endcase
end
//wr_state_cnt
always @ (posedge spi_clk)
begin
if(rst) wr_state_cnt <= 6'b0;
else if(state == s_write) wr_state_cnt <= wr_state_cnt + 1;
else wr_state_cnt <= 6'b0;
end
//rd_state_cnt
always @ (posedge spi_clk)
begin
if(rst) rd_state_cnt <= 5'b0;
else if(state == s_read) rd_state_cnt <= rd_state_cnt + 1;
else rd_state_cnt <= 5'b0;
end
//cs
always @ (posedge spi_clk)
begin
if(rst) cs <= 1'b0;
else if(wr_state_cnt == 6'd1) cs <= 1'b1;//刚进入写状态,准备发EWEN指令
else if(wr_state_cnt == 6'd10) cs <= 1'b0;//发完了EWEN指令
else if(wr_state_cnt == 6'd12) cs <= 1'b1;//我要发WRITE指令
else if(wr_state_cnt == 6'd37) cs <= 1'b0;//WRITE发完了
// else if(wr_state_cnt == 6'd39) cs <= 1'b1;//蓝狗是不会发送EWDS指令的
// else if(wr_state_cnt == 6'd49) cs <= 1'b0;//蓝狗是不会发送EWDS指令的
//
else if(rd_state_cnt == 5'd1) cs <= 1'b1;//我要开始读了
else if(rd_state_cnt == 5'd26) cs <= 1'b0;//我读完了
else cs <= cs;//我不想动
end
//mo
always @ (posedge spi_clk)
begin
if(rst) mo <= 1'b0;
else if(state == s_write)
begin
case(wr_state_cnt)
6'd1: mo <= ewen_data[8];//EWEN指令
6'd2: mo <= ewen_data[7];//EWEN指令
6'd3: mo <= ewen_data[6];//EWEN指令
6'd4: mo <= ewen_data[5];//EWEN指令
6'd5: mo <= ewen_data[4];//EWEN指令
6'd6: mo <= ewen_data[3];//EWEN指令
6'd7: mo <= ewen_data[2];//EWEN指令
6'd8: mo <= ewen_data[1];//EWEN指令
6'd9: mo <= ewen_data[0];//EWEN指令
//
6'd12 : mo <= write_data[24]; //写
6'd13 : mo <= write_data[23]; //写
6'd14 : mo <= write_data[22]; //写
6'd15 : mo <= write_data[21]; //写
6'd16 : mo <= write_data[20]; //写
6'd17 : mo <= write_data[19]; //写
6'd18 : mo <= write_data[18]; //写
6'd19 : mo <= write_data[17]; //写
6'd20 : mo <= write_data[16]; //写
6'd21 : mo <= write_data[15]; //写
6'd22 : mo <= write_data[14]; //写
6'd23 : mo <= write_data[13]; //写
6'd24 : mo <= write_data[12]; //写
6'd25 : mo <= write_data[11]; //写
6'd26 : mo <= write_data[10]; //写
6'd27 : mo <= write_data[9]; //写
6'd28 : mo <= write_data[8]; //写
6'd29 : mo <= write_data[7]; //写
6'd30 : mo <= write_data[6]; //写
6'd31 : mo <= write_data[5]; //写
6'd32 : mo <= write_data[4]; //写
6'd33 : mo <= write_data[3]; //写
6'd34 : mo <= write_data[2]; //写
6'd35 : mo <= write_data[1]; //写
6'd36 : mo <= write_data[0]; //写
default: mo <= 1'b0;
endcase
end
else if(state == s_read)//判断是否是读状态
begin
case(rd_state_cnt)
5'd1: mo <= write_data_rd[8];//读命令
5'd2: mo <= write_data_rd[7];//读命令
5'd3: mo <= write_data_rd[6];//读命令
5'd4: mo <= write_data_rd[5];//读命令
5'd5: mo <= write_data_rd[4];//读命令
5'd6: mo <= write_data_rd[3];//读命令
5'd7: mo <= write_data_rd[2];//读命令
5'd8: mo <= write_data_rd[1];//读命令
5'd9: mo <= write_data_rd[0];//读命令
endcase
end
end
//mi
always @ (posedge spi_clk)
begin
if(rst) read_data <= 8'b0;
else if(state == s_read)
begin
case(rd_state_cnt)
6'd11 : read_data[15] <=mi ;//EEPROM发送的数据
6'd12 : read_data[14] <=mi ;//EEPROM发送的数据
6'd13 : read_data[13] <=mi ;//EEPROM发送的数据
6'd14 : read_data[12] <=mi ;//EEPROM发送的数据
6'd15 : read_data[11] <=mi ;//EEPROM发送的数据
6'd16 : read_data[10] <=mi ;//EEPROM发送的数据
6'd17 : read_data[9] <=mi ;//EEPROM发送的数据
6'd18 : read_data[8] <=mi ;//EEPROM发送的数据
6'd19 : read_data[7] <=mi ;//EEPROM发送的数据
6'd20 : read_data[6] <=mi ;//EEPROM发送的数据
6'd21 : read_data[5] <=mi ;//EEPROM发送的数据
6'd22 : read_data[4] <=mi ;//EEPROM发送的数据
6'd23 : read_data[3] <=mi ;//EEPROM发送的数据
6'd24 : read_data[2] <=mi ;//EEPROM发送的数据
6'd25 : read_data[1] <=mi ;//EEPROM发送的数据
6'd26 : read_data[0] <=mi ;//EEPROM发送的数据
endcase
end
end
// assign TEST = (read_data[15:0] == write_data[15:0]) ? 1'b1 : 1'b0;
// assign data_out = read_data[15:0];
always @(*) begin
if(sw1==1) begin
data_out=read_data[15:0];
end
else if(sw2==1) begin
data_out=78;
end
else if(sw3==1) begin
data_out=62;
end
else data_out=0;
end
endmodule
相关资料
板子部分资料
链接:https://pan.baidu.com/s/1oarA9cpeaE-JwNhMCIOjLA?pwd=cwfd
提取码:cwfd
EEPROM手册
链接:https://pan.baidu.com/s/1kVV-IIRtQs2mFBaWPSexkQ?pwd=uqib
提取码:uqib
完整工程
链接:https://pan.baidu.com/s/1346e-knfq8XkNupFZuZc9A?pwd=xfrr
提取码:xfrr
更多推荐
所有评论(0)