前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >FPGA零基础学习之Vivado-EEPROM驱动设计

FPGA零基础学习之Vivado-EEPROM驱动设计

原创
作者头像
FPGA技术江湖
发布2023-08-22 18:09:49
4301
发布2023-08-22 18:09:49
举报
文章被收录于专栏:FPGA技术江湖

FPGA零基础学习之Vivado-EEPROM驱动设计

本系列将带来FPGA的系统性学习,从最基本的数字电路基础开始,最详细操作步骤,最直白的言语描述,手把手的“傻瓜式”讲解,让电子、信息、通信类专业学生、初入职场小白及打算进阶提升的职业开发者都可以有系统性学习的机会。

系统性的掌握技术开发以及相关要求,对个人就业以及职业发展都有着潜在的帮助,希望对大家有所帮助。本次带来Vivado系列,EEPROM驱动设计。话不多说,上货。

EEPROM (Electrically Erasable Programmable read only memory)是指带电可擦可编程只读存储器。是一种掉电后数据不丢失的存储芯片。EEPROM可以在电脑上或专用设备上擦除已有信息,重新编程。一般用在即插即用。

EEPROM(带电可擦可编程只读存储器)是用户可更改的只读存储器(ROM),其可通过高于普通电压的作用来擦除和重编程(重写)。不像EPROM芯片,EEPROM不需从计算机中取出即可修改。在一个EEPROM中,当计算机在使用的时候可频繁地反复编程,因此EEPROM的寿命是一个很重要的设计考虑参数。EEPROM是一种特殊形式的闪存,其应用通常是个人电脑中的电压来擦写和重编程。我们以SANXIN-B04板卡上的EEPROM为例,向大家介绍一下其驱动方式。EEPROM型号为24LC64。

编辑切换为居中

添加图片注释,不超过 140 字(可选)

从特征中我们可以看出一些信息:

1、两线串行总线,使用IIC接口协议。

2、有自擦除/写循环时间。

3、有32字节页写和单字节写模式。

4、最大5ms的写循环时间。

知道了以上信息之后,那么我们继续往下看手册。

编辑切换为居中

添加图片注释,不超过 140 字(可选)

在这段描述中,我们可以看出,24LC64是一个8K*8的一个存储器,总共64K bit的容量。因为存储深度为8K,也就意味着芯片的地址有需要有8K个,也就是有8192个,从0开始到8191,那么地址线的位宽就是13位。这也就是我们在写时序图中展示的高字节地址和低字节地址。此外,芯片的供电范围是1.8V到5.5V。芯片有页写功能,最多可写32字节。允许最多有8个芯片连接到相同的总线上。

管脚及功能介绍:

编辑切换为居中

添加图片注释,不超过 140 字(可选)

编辑切换为居中

添加图片注释,不超过 140 字(可选)

A0、A1、A2三个端口为片选输入,当这三个端口的电平与从机地址相同时,该芯片被选中,这些管脚必须接电源或者地。

编辑切换为居中

添加图片注释,不超过 140 字(可选)

SDA是一个双向总线用于传输地址和数据,在SCL为低电平的时候,正常的数据传输SDA是允许被改变的,如果在SCL为高电平期间改变就会被认为是开始和停止条件。

编辑切换为居中

添加图片注释,不超过 140 字(可选)

SCL为串行时钟,用于同步数据的传输,由主机发出。

编辑切换为居中

添加图片注释,不超过 140 字(可选)

写保护管脚,这个管脚可以连接地、电源或悬空。如果悬空,这个管脚上的下拉电阻会让芯片保持不被保护的状态。如果连接到了地或者悬空,正常的存储器操作是有效地,如果连接到了电源,写操作是禁止的,读操作不受影响。

编辑切换为居中

添加图片注释,不超过 140 字(可选)

在SCL为高电平时,SDA由高变低被视为开始条件,所有的命令必须在开始条件之后。

在SCL为高电平时,SDA由低变高被视为停止条件,所有的操作必须以停止条件结束。

编辑切换为居中

添加图片注释,不超过 140 字(可选)

端口介绍完,这里要着重说明一下应答信号。每次接收完8bit数据,从机需要产生一个应答信号,主机必须产生一个额外的时钟周期伴随着应答信号。注释:芯片如果有内部循环在进程中的话,是不会产生应答信号的。在时钟为高电平的时候,芯片的应答信号必须拉低SDA,并且在SCL低电平期间保持稳定。

编辑切换为居中

添加图片注释,不超过 140 字(可选)

上图为IIC总线的示意图,其中开始条件与停止条件在上文已经描述过。其次是数据的读写,在图中,SCL为高电平时,地址或应答有效,那么我们就在数据有效的时候进行采数;在SCL为低电平时,数据允许被改变,那么我们发送数据就在此时间段。

在了解了端口的作用及功能之后,我们做一个总结:

1、起始条件:SCL为高电平,SDA由高变低。

2、停止条件:SCL为低电平,SDA由低变高。

3、数据接收在SCL高电平期间。

4、数据发送在SCL低电平期间。

5、A0、A1、A2、WP在电路图中已经全部接地,在代码中我们不需要关注。

编辑切换为居中

添加图片注释,不超过 140 字(可选)

电路图中可以看出,我们的EEPROM的控制管脚只有SCL和SDA。

设计框架

编辑切换为居中

添加图片注释,不超过 140 字(可选)

按照上面的框架,完成各部分代码,最后在此框架的基础上,再加上数码管模块,用来显示从EEPROM里面读出的数据。

新建工程:

编辑切换为居中

添加图片注释,不超过 140 字(可选)

编辑切换为居中

添加图片注释,不超过 140 字(可选)

新建好工程后,新建文件,开始写代码,首先是写模块。

写时序如下图。

编辑切换为居中

添加图片注释,不超过 140 字(可选)

我们以单字节写为例,因为在写的工程中,没写8bit,就要读一次ACK,所以在此我得做法是,读模块只写8bit的线性序列机,代码如下:

代码语言:javascript
复制
1   module iic_wr(
2 
3     input   wire        clk,
4     input   wire        rst_n,
5     
6     input   wire        wr_en,
7     input   wire  [7:0] wr_data,
8     
9     output  reg         wr_scl,
10    output  reg         wr_sda,
11    output  wire        wr_done
12  );
13
14    parameter f_clk = 50_000_000;
15    parameter f = 100_000;
16    parameter t = f_clk / f / 4;
17    
18    reg   [11:0]    cnt;
19    
20    always @ (posedge clk, negedge rst_n)
21    begin
22    if(rst_n == 1'b0)
23      cnt <= 12'd0;
24    else if(wr_en)
25      begin
26      if(cnt == 32 * t - 1)
27        cnt <= 12'd0;
28      else
29        cnt <= cnt + 1'b1;
30      end
31    else
32      cnt <= 12'd0;
33    end
34    
35    always @ (posedge clk, negedge rst_n)
36    begin
37    if(rst_n == 1'b0)
38      begin
39      wr_scl <= 1'b0;
40      wr_sda <= 1'b0;
41      end
42    else if(wr_en)
43      case(cnt)
44      0         : begin wr_scl <= 1'b0; wr_sda <= wr_data[7]; end
45      1 * t - 1 : begin wr_scl <= 1'b1; end
46      3 * t - 1 : begin wr_scl <= 1'b0; end
47      4 * t - 1 : begin wr_sda <= wr_data[6]; end
48      5 * t - 1 : begin wr_scl <= 1'b1; end
49      7 * t - 1 : begin wr_scl <= 1'b0; end
50      8 * t - 1 : begin wr_sda <= wr_data[5]; end
51      9 * t - 1 : begin wr_scl <= 1'b1; end
52      11* t - 1 : begin wr_scl <= 1'b0; end
53      12* t - 1 : begin wr_sda <= wr_data[4]; end
54      13* t - 1 : begin wr_scl <= 1'b1; end
55      15* t - 1 : begin wr_scl <= 1'b0; end
56      16* t - 1 : begin wr_sda <= wr_data[3]; end
57      17* t - 1 : begin wr_scl <= 1'b1; end
58      19* t - 1 : begin wr_scl <= 1'b0; end
59      20* t - 1 : begin wr_sda <= wr_data[2]; end
60      21* t - 1 : begin wr_scl <= 1'b1; end
61      23* t - 1 : begin wr_scl <= 1'b0; end
62      24* t - 1 : begin wr_sda <= wr_data[1]; end
63      25* t - 1 : begin wr_scl <= 1'b1; end
64      27* t - 1 : begin wr_scl <= 1'b0; end
65      28* t - 1 : begin wr_sda <= wr_data[0]; end
66      29* t - 1 : begin wr_scl <= 1'b1; end
67      31* t - 1 : begin wr_scl <= 1'b0; end
68      default : ;
69      endcase
70    end 
71    
72    assign wr_done = (cnt == 32 * t - 1) ? 1'b1 : 1'b0;
73
74  endmodule

在写模块中,我们选择在SCL低电平中心位置发送数据,那么为了方便我们写线性序列机,我们将四分之一SCL周期看做是一个单位时间T,总共8bit时间,所以计数器需要计数32个T。

读模块代码如下:

代码语言:javascript
复制
1   module iic_rd(
2 
3     input   wire          clk,
4     input   wire          rst_n,
5     
6     input   wire          rd_sda,
7     input   wire          rd_en,
8     
9     output  reg           rd_scl,
10    output  reg   [7:0]   rd_data,
11    output  wire          rd_done
12  );
13
14    parameter f_clk = 50_000_000;
15    parameter f = 100_000;
16    parameter t = f_clk / f / 4;
17    
18    reg   [11:0]    cnt;
19    
20    always @ (posedge clk, negedge rst_n)
21    begin
22    if(rst_n == 1'b0)
23      cnt <= 12'd0;
24    else if(rd_en)
25      begin
26      if(cnt == 32 * t - 1)
27        cnt <= 12'd0;
28      else
29        cnt <= cnt + 1'b1;
30      end
31    else
32      cnt <= 12'd0;
33    end
34
35    always @ (posedge clk, negedge rst_n)
36    begin
37    if(rst_n == 1'b0)
38      begin
39      rd_scl <= 1'b0;
40      rd_data <= 8'd0;
41      end
42    else if(rd_en)
43      case(cnt)
44      0         : begin rd_scl <= 1'b0; end
45      1 * t - 1 : begin rd_scl <= 1'b1; end
46      2 * t - 1 : begin rd_data[7] <= rd_sda; end
47      3 * t - 1 : begin rd_scl <= 1'b0; end
48      5 * t - 1 : begin rd_scl <= 1'b1; end
49      6 * t - 1 : begin rd_data[6] <= rd_sda; end
50      7 * t - 1 : begin rd_scl <= 1'b0; end
51      9 * t - 1 : begin rd_scl <= 1'b1; end
52      10* t - 1 : begin rd_data[5] <= rd_sda; end
53      11* t - 1 : begin rd_scl <= 1'b0; end
54      13* t - 1 : begin rd_scl <= 1'b1; end
55      14* t - 1 : begin rd_data[4] <= rd_sda; end
56      15* t - 1 : begin rd_scl <= 1'b0; end
57      17* t - 1 : begin rd_scl <= 1'b1; end
58      18* t - 1 : begin rd_data[3] <= rd_sda; end
59      19* t - 1 : begin rd_scl <= 1'b0; end
60      21* t - 1 : begin rd_scl <= 1'b1; end
61      22* t - 1 : begin rd_data[2] <= rd_sda; end
62      23* t - 1 : begin rd_scl <= 1'b0; end
63      25* t - 1 : begin rd_scl <= 1'b1; end
64      26* t - 1 : begin rd_data[1] <= rd_sda; end
65      27* t - 1 : begin rd_scl <= 1'b0; end
66      29* t - 1 : begin rd_scl <= 1'b1; end
67      30* t - 1 : begin rd_data[0] <= rd_sda; end
68      31* t - 1 : begin rd_scl <= 1'b0; end
69      default : ;
70      endcase
71    end
72    
73    assign rd_done = (cnt == 32 * t - 1) ? 1'b1 : 1'b0;
74
75  endmodule

在控制模块中,我们可以将每个bit分解开进行写,比如起始条件:

编辑切换为居中

添加图片注释,不超过 140 字(可选)

类比以上写法,停止条件也是如此。

编辑

添加图片注释,不超过 140 字(可选)

在控制模块中我们使用状态机来完成数据的写入以及ACK的判断。我们在第一个状态中,可以加入一个按键来启动整个过程的开始。

在ACK读取的状态,我们在SCL高电平中心读取ACK的值,在此状态结束时判断ACK的值是否为0,如果为0说明响应正确,状态继续往下,否则返回起始条件状态。

编辑

添加图片注释,不超过 140 字(可选)

在顶层代码中,我们需要加入三态门来控制双端口。

编辑

添加图片注释,不超过 140 字(可选)

在写好代码之后,出现了一个问题,那就是我们无法仿真,因为单纯的看波形,会发现我们得不到正确的ACK,导致状态一直无法继续。因此我们将使用仿真模型来进行仿真,在仿真模型中,端口为芯片的控制端口。

编辑切换为居中

添加图片注释,不超过 140 字(可选)

在此模型中需要我们注意的一点就是,写循环时间定义的为5ms时间,如果大家觉得时间太长,可以自行修改。

编辑切换为居中

添加图片注释,不超过 140 字(可选)

仿真代码如下:

代码语言:javascript
复制
1   `timescale 1ns/1ps
2 
3   module iic_tb;
4     
5     reg            clk;
6     reg            rst_n;
7     reg            key;
8     
9     wire  [2:0]     sel;
10    wire  [7:0]     seg;
11    wire            SCL;
12    wire            SDA;
13    
14    defparam iic_inst.jitter_inst.t = 10;
15    
16    pullup(SDA);
17    
18    initial begin
19    clk = 0;
20    rst_n = 0;
21    key = 1;
22    #100;
23    rst_n = 1;
24    #1000;
25    key = 0;
26    #1000;
27    key = 1;
28    #1000000;
29    $stop;
30    end
31    
32    always #10 clk = ~clk;
33    
34    iic iic_inst(
35
36    .clk        (clk),
37    .rst_n      (rst_n),
38    .key        (key),
39    
40    .sel        (sel),
41    .seg        (seg),
42    .SCL        (SCL),
43    .SDA        (SDA)
44  );
45
46    M24LC64 M24LC64_inst(
47    .A0       (1'b0), 
48    .A1       (1'b0), 
49    .A2       (1'b0), 
50    .WP       (1'b0), 
51    .SDA      (SDA), 
52    .SCL      (SCL)
53    );
54
55  endmodule

写好代码之后,我们打开仿真波形进行观察。

编辑切换为居中

添加图片注释,不超过 140 字(可选)

在仿真波形中可以看出,我们读出来的数据跟写入的数据时一致的,即表明驱动正确。需要注意的是,在写循环时间内,我们发送的命令,芯片是不接收的,所以会出现起始条件、写控制字、ACK三个状态一直循环,直到数据写入完成,仿真模型才会返回ACK。此时,状态继续往下进行,直至结束。

我们从仿真模型中读出的数据,高电平是为高阻的,模型无法拉高,所以我们在仿真中可以自行上拉,这样才能看到完整且正确的数据。

编辑

添加图片注释,不超过 140 字(可选)

至此,我们实验正确。

后续会持续更新,带来Vivado、 ISE、Quartus II 、candence等安装相关设计教程,学习资源、项目资源、好文推荐等,希望大侠持续关注。

江湖偌大,继续闯荡,愿大侠一切安好,有缘再见!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • FPGA零基础学习之Vivado-EEPROM驱动设计
  • 设计框架
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档