芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
十六、IIC 协议详解+Uart 串口读写 EEPROM
本文由杭电网友曾凯峰根据小梅哥 FPGA IIC 协议基本概念公开课内容整理并最终编写
Verilog 代码实现使用串口读写 EEPROM 的功能。
以下为原文内容:
在看完小梅哥讲解 IIC 总线基本概念后,就有种想跃跃欲试的想法,下面先复习下梅哥
讲解的 IIC 总线若干基本概念。以下基本概念均为小梅哥总结,我就直接拿过来供大家参考
学习。
IIC 基本特性
总线信号
SDA:串行数据线
SCL:串行数据时钟
总线空闲状态
SDA:高电平
SCL:高电平
IIC 协议起始位
SCL 为高电平时,SDA 出现下降沿,产生一个起始位。
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
IIC 协议结束位
SCL 为高电平时,SDA 出现上升沿,产生一个结束位。
IIC 读写单字节时序
IIC 主机对 IIC 从机写入数据时,SDA 上的每一位数据在 SCL 的高电平期间被写入从机
中。对于主机,在 SCL 的低电平期间改变要写入的数据。
IIC 主机从 IIC 从机中读出数据时,从机在 SCL 的低电平期间将数据输出到 SDA 总线上,在
SCL 的高电平期间保持数据稳定。对于主机,在 SCL 的高电平期间将 SDA 线上的数据读取
并存储。
数据接收方对数据发送方的响应
每当一个字节的数据或命令传输完成时,都会有一位的应答位。需要应答位时,数据发
出方将 SDA 总线设置为 3 态输入,由于 IIC 总线上都有上拉电阻,因此此时总线默认为高电
平,若数据接收方正确接收到数据,则数据接收方将 SDA 总线拉低,以示正确应答。
例如当 IIC 主机对 IIC 从机写入数据或命令时,每个字节都需要从机产生应答信号以告
诉主机数据或命令成功被写入。所以,当 IIC 主机将 8 位的数据或命令传出后,会将 SDA 信
号设置为输入,等待从机应答(等待 SDA 被从机拉低为低电平),若从机正确应答,表明当
前数据或命令传输成功,可以结束或开始下一个命令/数据的传输,否则表明数据/命令写入
失败,主机就可以决定是否放弃写入或者重新发起写入。
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
IIC 器件地址
每个 IIC 器件都有一个器件地址,有的器件地址在出厂时地址就设置好了,用户不可以
更改(ov7670:0x42),有的确定了几位,剩下几位由硬件确定(比如有三位由用户确定,就
留有 3 个控制地址的引脚,最常见的为 IIC 接口的 EEPROM 存储器),此类较多;还有的有
地址寄存器。
严格讲,主机不是向从机发送地址,而是主机往总线上发送地址,所有的从机都能接收
到主机发出的地址,然后每个从机都将主机发出的地址与自己的地址比较,如果匹配上了,
这个从机就会向主机发出一个响应信号。主机收到响应信号后,开始向总线上发送数据,与
这个从机的通讯就建立起来了。如果主机没有收到响应信号,则表示寻址失败。
通常情况下,主从器件的角色是确定的,也就是说从机一直工作在从机模式。不同的器
件定义地址的方式是不同的,有的是软件定义,有的是硬件定义。例如某些单片机的 IIC 接
口作为从机时,其器件地址是可以通过软件修改从机地址寄存器确定的。而对于一些其他器
件,如 CMOS 图像传感器、EEPROM 存储器,其器件地址在出厂时就已经设定好了,具体
值可以在对应的数据手册中查到。
对于 AT24C64 这样一颗 EEPROM 器件,其器件地址为 1010 加 3 位的片选信号。3 位
片选信号由硬件连接决定。例如 SOIC 封装的该芯片 pin1、pin2、pin3 为片选地址。当硬件
电路上分别将这三个 pin 连接到 GND 或 VCC 时,就实现了设置不通的片选地址。
IIC 协议在进行数据传输时,主机需要首先向总线上发出控制命令,其中,控制命令就
包含了从机地址/片选信号+读写控制。然后等待从机响应。以下为 IIC 控制命令传输的数据
格式。
IIC 传输时,按照从高到低的位序进行传输。控制字节的最低位为读写控制位,当该位
为 0 时表示主机对从机进行写操作,当该位为 1 时表示主机对从机进行读操作。例如,当需
要对片选地址为 100 的 AT24LC64 发起写操作,则控制字节应该为 CtrlCode = 1010_100_0。
若要读,则控制字节应该为 CtrlCode = 1010_100_1。
IIC 存储器地址
每个支持 IIC 协议的器件,内部总会有一些可供读写的寄存器或存储器,例如,对于我
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
们用到的 EEPROM 存储器,内部就是顺序编址的一系列存储单元。对于我们常接触的 CMOS
摄像头如 OV7670(OV7670 的该接口叫 SCCB 接口,其实质也是一种特殊的 IIC 协议,可以
直接兼容 IIC 协议),其内部就是一系列编址的可供读写的寄存器。因此,我们要对一个器件
中的存储单元(寄存器和存储器以下简称存储单元)进行读写,就必须要能够指定存储单元
的地址。IIC 协议设计了有从机存储单元寻址地址段,该地址段为一个字节或两个字节长度,
在主机确认收到从机返回的控制字节响应后,由主机发出。地址段长度视不同的器件类型,
长度不同,例如同是 EEPROM 存储器,AT24C04 的址段长度为一个字节,而 AT24C64 的地
址段长度为两个字节。具体是一个字节还是两个字节,与器件的存储单元数量有关。
AT24C01 地址段:
AT24C64 地址段:
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
IIC 读写时序
IIC 单字节写时序
1 字节地址段器件单字节写时序
2 字节地址段器件单字节写时序
从主机角度看一次写入过程
a. 主机设置 SDA 为输出
b. 主机发起起始信号
c. 主机传输器件地址字节,其中最低为 0,表明为写操作。
d. 主机设置 SDA 为输入三态,读取从机应答信号。
e. 读取应答信号成功,传输 1 字节地址数据
f. 主机设置 SDA 为输入三态,读取从机应答信号。
g. 对于两字节地址段器件,传输地址数据低字节,对于 1 字节地址段器件,传输待写
入的数据
h. 设置 SDA 为输入三态,读取从机应答信号。
i. 对于两字节地址段器件,传输待写入的数据(2 字节地址段器件可选)
j. 设置 SDA 为输入三态,读取从机应答信号(2 字节地址段器件可选)。
k. 主机产生 STOP 位,终止传输。
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
IIC 连续写时序(页写时序)
注:IIC 连续写时序仅部分器件支持。
1 字节地址段器件多字节写时序
2 字节地址段器件多字节写时序
从主机角度看一次写入过程
a. 主机设置 SDA 为输出
b. 主机发起起始信号
c. 主机传输器件地址字节,其中最低为 0,表明为写操作。
d. 主机设置 SDA 为输入三态,读取从机应答信号。
e. 读取应答信号成功,传输 1 字节地址数据
f. 主机设置 SDA 为输入三态,读取从机应答信号。
g. 对于两字节地址段器件,传输低字节地址数据,对于 1 字节地址段器件,传输待写
入的第一个数据
h. 设置 SDA 为输入三态,读取从机应答信号。
i. 写入待写入的第 2 至第 n 个数据并读取应答信号。对于 AT24Cxx,一次可写入的最
大长度为 32 字节。
j. 主机产生 STOP 位,终止传输。
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
IIC 单字节读时序
1 字节地址段器件单节读时序
2 字节地址段器件单节读时序
从主机角度看一次读取过程
a. 主机设置 SDA 为输出
b. 主机发起起始信号
c. 主机传输器件地址字节,其中最低为 0,表明为写操作。
d. 主机设置 SDA 为输入三态,读取从机应答信号。
e. 读取应答信号成功,传输 1 字节地址数据
f. 主机设置 SDA 为输入三态,读取从机应答信号。
g. 对于两字节地址段器件,传输低字节地址数据,对于 1 字节地址段器件,无此段数
据传输。
h. 主机发起起始信号
i. 主机传输器件地址字节,其中最低为 1,表明为写操作。
j. 设置 SDA 为输入三态,读取从机应答信号。
k. 读取 SDA 总线上的一个字节的数据
l. 产生无应答信号(高电平)(无需设置为输出高点片,因为总线会被自动拉高)
m. 主机产生 STOP 位,终止传输。
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
IIC 多字节连续读时序(页读取)
1 字节地址段器件多字节读时序
2 字节地址段器件多字节读时序
从主机角度看一次读取过程
a. 主机设置 SDA 为输出
b. 主机发起起始信号
c. 主机传输器件地址字节,其中最低为 0,表明为写操作。
d. 主机设置 SDA 为输入三态,读取从机应答信号。
e. 读取应答信号成功,传输 1 字节地址数据
f. 主机设置 SDA 为输入三态,读取从机应答信号。
g. 对于两字节地址段器件,传输低字节地址数据,对于 1 字节地址段器件,无此段数据
传输。
h. 主机发起起始信号
i. 主机传输器件地址字节,其中最低为 1,表明为写操作。
j. 设置 SDA 为输入三态,读取从机应答信号。
k. 读取 SDA 总线上的 n 个字节的数据(对于 AT24Cxx,一次读取长度最大为 32 字节)
l. 产生无应答信号(高电平)(无需设置为输出高点片,因为总线会被自动拉高)
主机产生 STOP 位,终止传输。
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
EEPROM 读写控制程序 设计
硬件平台分析
下面就小梅哥开发板上的 EEPROM 芯片编写 IIC 协议读写 EEPROM 里的数据,首先查
看小梅哥开发板中 EEPROM 存储器芯片的型号为 AT24C64,其存储器容量为 64kbit,器件
片选地址有 3 位,A2、A1、A0。数据存储地址是 13 位,属于 2 字节地址段器件。
这里 EEPROM 读写控制模块的设计主要是针对读写 EEPROM 单字节数据,总的来说就
是 2 字节地址段器件单节读写控制模块的设计。有关多字节的数据的读写读者可以自己学
习设计。
根据上面 IIC 的基本概念中有关读写时 SDA 与 SCL 时序,不管对于从机还是主机 SDA
上的每一位数据在 SCL 的高电平期间为有效数据,在 SCL 的低电平期间是要改变的数据。
根据这个用 2 个标志位对时钟 SCL 的高电平和低电平进行标记,如下图所示:scl_high 对
SCL 高电平中间进行标志,scl_low 对 SCL 低电平中间进行标志。这个在具体的实现中也不
难实现。
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
SCL 时钟设计
首先考虑到 SCL 最大时钟周期为 400kHz,这里 SCL 就实现周期为 200kHz 的时钟,这
个具体读者可以做修改,系统时钟直接采用小梅哥开发板的 50MHz 外部时钟,采用计数器
方法产生 SCL,这样计数器最大计数值 SCL_CNT_M = 500_000_000/200_000 - 1 =249。只
需在计数到最大值一半值和最大值进行翻转 SCL 就可实现,具体实现代码如下:
parameter SYS_CLOCK = 50_000_000; //系统时钟采用 50MHz
parameter SCL_CLOCK = 200_000; //scl 总线时钟采用 200kHz
reg [7:0]scl_cnt;
parameter SCL_CNT_M = SYS_CLOCK/SCL_CLOCK; //最大计数
reg scl_cnt_state;
//产生 SCL 时钟状态标志 scl_cnt_state,为 1 表示 IIC 总线忙,为 0 表示总线闲
always@(posedge clk50M or negedge reset)
begin
if(!reset)
scl_cnt_state <= 1'b0;
else if(iic_en)
scl_cnt_state <= 1'b1;
else if(done)
scl_cnt_state <= 1'b0;
else
scl_cnt_state <= scl_cnt_state;
end
//scl 时钟总线产生计数器
always@(posedge clk50M or negedge reset)
begin
if(!reset)
scl_cnt <= 8'b0;
else if(scl_cnt_state)
begin
if(scl_cnt == SCL_CNT_M - 1)
scl_cnt <= 8'b0;
else
scl_cnt <= scl_cnt + 8'b1;
end
else
scl_cnt <= 8'b0;
end
//scl 时钟总线产生
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
always@(posedge clk50M or negedge reset)
begin
if(!reset)
scl <= 1'b1;
else if(scl_cnt == (SCL_CNT_M>>1)-1)
scl <= 1'b0;
else if(scl_cnt == SCL_CNT_M - 1)
scl <= 1'b1;
else
scl <= scl;
end
上述代码中 iic_en 信号为 IIC 通信使能信号,done 信号为一次 IIC 读/写数据完成标
志位。 在此基础上,对 SCL 高电平中间标志位 scl_high 和低电平标志位 scl_low 就很容易实
现,只需在计数到四分之一和四分之三时分别置 1 就能得到。具体代码如下:
//scl 时钟电平中部标志位
reg scl_high;
reg scl_low;
always@(posedge clk50M or negedge reset)
begin
if(!reset)
begin
scl_high <= 1'b0;
scl_low <= 1'b0;
end
else if(scl_cnt == (SCL_CNT_M>>2)) //四分之一最大计数值
scl_high <= 1'b1;
else if(scl_cnt == (SCL_CNT_M>>1)+(SCL_CNT_M>>2)) //四分之三最
大计数值
scl_low <= 1'b1;
else
begin
scl_high <= 1'b0;
scl_low <= 1'b0;
end
end
IIC 读写状态机设计
SCL 时钟总线以及其高低电平中间标志位产生完成后其后就是 SDA 数据线的产生,这
个需要根据具体的读写操作完成。这里主要采用状态机实现,根据上面 IIC 基本概念中 2 字
节地址段器件单字节读和写的过程设计大致的状态机如下,具体的有些条件和输出等没有
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
写。这是按照小梅哥讲解 IIC 读写过程想到的,对于实现单字节的读写是没有问题,但对于
读写多字节还需要做修改,这个后续再做优化。
Idle
Wr_ctrl
Wr_start
Ack1
Wr_addr
1
Ack2
Wr_addr
2
Ack3
Wr_data
Ack4
Stop
Rd_start
Rd_ctrl
Ack5
Rd_data
Nack
R_flag
W_flag
!SDA
!SDA
!SDA
!SDA
!SDA
!SDA
整个模块的具体实现代码如下:
//模块文件名:IIC.v
//模块功能:实现 IIC 总线协议控制
//时间:2016.11.2
module IIC_24LC64(
clk50M,
reset,
iic_en,
cs_bit,
address,
write,
write_data,
read,
read_data,
scl,
sda,
done
);
input clk50M; //系统时钟 50MHz
input reset; //异步复位信号
input iic_en; //使能信号
input [2:0]cs_bit; //器件选择地址
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
input [12:0]address; //13 位数据读写地址,24LC64 有 13 位数据存储
地址
input write; //写数据信号
input [7:0]write_data; //写数据
input read; //读数据信号
output reg[7:0]read_data; //读数据
output reg scl; //IIC 时钟信号
inout sda; //IIC 数据总线
output reg done; //一次 IIC 读写完成
parameter SYS_CLOCK = 50_000_000; //系统时钟采用 50MHz
parameter SCL_CLOCK = 200_000; //scl 总线时钟采用 200kHz
//状态
parameter
Idle = 16'b0000_0000_0000_0001,
Wr_start = 16'b0000_0000_0000_0010,
Wr_ctrl = 16'b0000_0000_0000_0100,
Ack1 = 16'b0000_0000_0000_1000,
Wr_addr1 = 16'b0000_0000_0001_0000,
Ack2 = 16'b0000_0000_0010_0000,
Wr_addr2 = 16'b0000_0000_0100_0000,
Ack3 = 16'b0000_0000_1000_0000,
Wr_data = 16'b0000_0001_0000_0000,
Ack4 = 16'b0000_0010_0000_0000,
Rd_start = 16'b0000_0100_0000_0000,
Rd_ctrl = 16'b0000_1000_0000_0000,
Ack5 = 16'b0001_0000_0000_0000,
Rd_data = 16'b0010_0000_0000_0000,
Nack = 16'b0100_0000_0000_0000,
Stop = 16'b1000_0000_0000_0000;
//sda 数据总线控制位
reg sda_en;
//sda 数据输出寄存器
reg sda_reg;
assign sda = sda_en ? sda_reg : 1'bz;
//状态寄存器
reg [15:0]state;
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
//读写数据标志位
reg W_flag;
reg R_flag;
//写数据到 sda 总线缓存器
reg [7:0]sda_data_out;
reg [7:0]sda_data_in;
reg [3:0]bit_cnt;
reg [7:0]scl_cnt;
parameter SCL_CNT_M = SYS_CLOCK/SCL_CLOCK; //计数最大值
reg scl_cnt_state;
//产生 SCL 时钟状态标志 scl_cnt_state,为 1 表示 IIC 总线忙,为 0 表示总线闲
always@(posedge clk50M or negedge reset)
begin
if(!reset)
scl_cnt_state <= 1'b0;
else if(iic_en)
scl_cnt_state <= 1'b1;
else if(done)
scl_cnt_state <= 1'b0;
else
scl_cnt_state <= scl_cnt_state;
end
//scl 时钟总线产生计数器
always@(posedge clk50M or negedge reset)
begin
if(!reset)
scl_cnt <= 8'b0;
else if(scl_cnt_state)
begin
if(scl_cnt == SCL_CNT_M - 1)
scl_cnt <= 8'b0;
else
scl_cnt <= scl_cnt + 8'b1;
end
else
scl_cnt <= 8'b0;
end
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
//scl 时钟总线产生
always@(posedge clk50M or negedge reset)
begin
if(!reset)
scl <= 1'b1;
else if(scl_cnt == (SCL_CNT_M>>1)-1)
scl <= 1'b0;
else if(scl_cnt == SCL_CNT_M - 1)
scl <= 1'b1;
else
scl <= scl;
end
//scl 时钟电平中部标志位
reg scl_high;
reg scl_low;
always@(posedge clk50M or negedge reset)
begin
if(!reset)
begin
scl_high <= 1'b0;
scl_low <= 1'b0;
end
else if(scl_cnt == (SCL_CNT_M>>2))
scl_high <= 1'b1;
else if(scl_cnt == (SCL_CNT_M>>1)+(SCL_CNT_M>>2))
scl_low <= 1'b1;
else
begin
scl_high <= 1'b0;
scl_low <= 1'b0;
end
end
//状态机
always@(posedge clk50M or negedge reset)
begin
if(!reset)
begin
state <= Idle;
sda_en <= 1'b0;
sda_reg <= 1'b1;
W_flag <= 1'b0;
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
R_flag <= 1'b0;
done <= 1'b0;
end
else
case(state)
Idle:
begin
done <= 1'b0;
W_flag <= 1'b0;
R_flag <= 1'b0;
sda_en <= 1'b0;
sda_reg <= 1'b1;
if(iic_en && write) //使能 IIC 并且为写操作
begin
W_flag <= 1'b1; //写标志位置 1
sda_en <= 1'b1; //设置 SDA 为输出模式
sda_reg <= 1'b1; //SDA 输出高电平
state <= Wr_start; //跳转到起始状态
end
else if(iic_en && read) //使能 IIC 并且为读操作
begin
R_flag <= 1'b1; //读标志位置 1
sda_en <= 1'b1; //设置 SDA 为输出模式
sda_reg <= 1'b1; //SDA 输出高电平
state <= Wr_start; //跳转到起始状态
end
else
state <= Idle;
end
Wr_start:
begin
if(scl_high)
begin
sda_reg <= 1'b0;
state <= Wr_ctrl;
sda_data_out <= {4'b1010, cs_bit,1'b0};
bit_cnt <= 4'd8;
end
else
begin
sda_reg <= 1'b1;
state <= Wr_start;
end
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
end
Wr_ctrl: //写控制字节 4'b1010+3 位片选地址+1 位写控制
begin
if(scl_low)
begin
bit_cnt <= bit_cnt -4'b1;
sda_reg <= sda_data_out[7];
sda_data_out <= {sda_data_out[6:0],1'b0};
if(bit_cnt == 0)
begin
state <= Ack1;
sda_en <= 1'b0;
end
else
state <= Wr_ctrl;
end
else
state <= Wr_ctrl;
end
Ack1: //通过判断 SDA 是否拉低来判断是否有从机响应
begin
if(scl_high)
if(sda == 1'b0)
begin
state <= Wr_addr1;
sda_data_out <= {3'bxxx,address[12:8]};
bit_cnt <= 4'd8;
end
else
state <= Idle;
else
state <= Ack1;
end
Wr_addr1: //写 2 字节地址中的高地址字节中的低五位
begin
if(scl_low)
begin
sda_en <= 1'b1;
bit_cnt <= bit_cnt -4'b1;
sda_reg <= sda_data_out[7];
sda_data_out <= {sda_data_out[6:0],1'b0};
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
if(bit_cnt == 0)
begin
state <= Ack2;
sda_en <= 1'b0;
end
else
state <= Wr_addr1;
end
else
state <= Wr_addr1;
end
Ack2: //通过判断 SDA 是否拉低来判断是否有从机响应
begin
if(scl_high)
if(sda == 1'b0)
begin
state <= Wr_addr2;
sda_data_out <= address[7:0];
bit_cnt <= 4'd8;
end
else
state <= Idle;
else
state <= Ack2;
end
Wr_addr2: //写 2 字节地址中的低地址字节
begin
if(scl_low)
begin
sda_en <= 1'b1;
bit_cnt <= bit_cnt -4'b1;
sda_reg <= sda_data_out[7];
sda_data_out <= {sda_data_out[6:0],1'b0};
if(bit_cnt == 0)
begin
state <= Ack3;
sda_en <= 1'b0;
end
else
state <= Wr_addr2;
end
else
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
state <= Wr_addr2;
end
Ack3: //通过判断 SDA 是否拉低来判断是否有从机响应
begin
if(scl_high)
if(sda == 1'b0) //有响应就判断是读还是写操作
begin
if(W_flag) //如果是写数据操作,进入写数据状态
begin
sda_data_out <= write_data;
bit_cnt <= 4'd8;
state <= Wr_data;
end
else if(R_flag) //如果是读数据操作,进入读数据开始状
态
begin
state <= Rd_start;
sda_reg <= 1'b1;
end
end
else
state <= Idle;
else
state <= Ack3;
end
Wr_data: //写数据状态,向 EEPROM 写入数据
begin
if(scl_low)
begin
sda_en <= 1'b1;
bit_cnt <= bit_cnt -4'b1;
sda_reg <= sda_data_out[7];
sda_data_out <= {sda_data_out[6:0],1'b0};
if(bit_cnt == 0)
begin
state <= Ack4;
sda_en <= 1'b0;
end
else
state <= Wr_data;
end
else
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
state <= Wr_data;
end
Ack4: //通过判断 SDA 是否拉低来判断是否有从机响应
begin
if(scl_high)
if(sda == 1'b0) //有响应就进入停止状态
begin
sda_reg <= 1'b0;
state <= Stop;
end
else
state <= Idle;
else
state <= Ack4;
end
Rd_start: //读数据的开始操作
begin
if(scl_low)
begin
sda_en <= 1'b1;
end
else if(scl_high)
begin
sda_reg <= 1'b0;
state <= Rd_ctrl;
sda_data_out <= {4'b1010, cs_bit,1'b1};
bit_cnt <= 4'd8;
end
else
begin
sda_reg <= 1'b1;
state <= Rd_start;
end
end
Rd_ctrl: //写控制字节 4'b1010+3 位片选地址+1 位读控制
begin
if(scl_low)
begin
bit_cnt <= bit_cnt -4'b1;
sda_reg <= sda_data_out[7];
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
sda_data_out <= {sda_data_out[6:0],1'b0};
if(bit_cnt == 0)
begin
state <= Ack5;
sda_en <= 1'b0;
end
else
state <= Rd_ctrl;
end
else
state <= Rd_ctrl;
end
Ack5: //通过判断 SDA 是否拉低来判断是否有从机响应
begin
if(scl_high)
if(sda == 1'b0) //有响应就进入读数据状态
begin
state <= Rd_data;
sda_en <= 1'b0; //SDA 总线设置为 3 态输入
bit_cnt <= 4'd8;
end
else
state <= Idle;
else
state <= Ack5;
end
Rd_data: //读数据状态
begin
if(scl_high) //在时钟高电平读取数据
begin
sda_data_in <= {sda_data_in[6:0],sda};
bit_cnt <= bit_cnt - 4'd1;
state <= Rd_data;
end
else if(scl_low && bit_cnt == 0) //数据接收完成进入无应答
响应状态
begin
state <= Nack;
end
else
state <= Rd_data;
end
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
Nack: //不做应答响应
begin
read_data <= sda_data_in;
if(scl_high)
begin
state <= Stop;
sda_reg <= 1'b0;
end
else
state <= Nack;
end
Stop: //停止操作,在时钟高电平,SDA 上升沿
begin
if(scl_low)
begin
sda_en <= 1'b1;
end
else if(scl_high)
begin
sda_en <= 1'b1;
sda_reg <= 1'b1;
state <= Idle;
done <= 1'b1;
end
else
state <= Stop;
end
default:
begin
state <= Idle;
sda_en <= 1'b0;
sda_reg <= 1'b1;
W_flag <= 1'b0;
R_flag <= 1'b0;
done <= 1'b0;
end
endcase
end
endmodule
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
IIC 读写状态机仿真
仿真验证需要编写一个模块模拟 EEPROM 的存储器,这里仿真片选地址就省掉不做考
虑。模拟 EEPROM 的存储器的主要功能是接收存储器数据地址,根据开始信号的次数(主
机写数据有一次开始信号,主机读数据有 2 次开始信号)判断是读还是写数据,进而做出数
据的输出/接收。这个仿真模型我是借用夏宇闻 Verilog 数字系统设计教程一书中的代码稍作
修改以满足 AT24C64 芯片 2 字节地址段的情况。
`timescale 1ns/1ns
`define timeslice 1250
module EEPROM_AT24C64(
scl,
sda
);
input scl; //串行时钟线
inout sda; //串行数据线
reg out_flag; //SDA 数据输出的控制信号
reg[7:0] memory[8191:0]; //数组模拟存储器
reg[12:0]address; //地址总线
reg[7:0]memory_buf; //数据输入输出寄存器
reg[7:0]sda_buf; //SDA 数据输出寄存器
reg[7:0]shift; //SDA 数据输入寄存器
reg[7:0]addr_byte_h; //EEPROM 存储单元地址高字节寄存器
reg[7:0]addr_byte_l; //EEPROM 存储单元地址低字节寄存器
reg[7:0]ctrl_byte; //控制字寄存器
reg[1:0]State; //状态寄存器
integer i;
//---------------------------
parameter
r7 = 8'b1010_1111, w7 = 8'b1010_1110, //main7
r6 = 8'b1010_1101, w6 = 8'b1010_1100, //main6
r5 = 8'b1010_1011, w5 = 8'b1010_1010, //main5
r4 = 8'b1010_1001, w4 = 8'b1010_1000, //main4
r3 = 8'b1010_0111, w3 = 8'b1010_0110, //main3
r2 = 8'b1010_0101, w2 = 8'b1010_0100, //main2
r1 = 8'b1010_0011, w1 = 8'b1010_0010, //main1
r0 = 8'b1010_0001, w0 = 8'b1010_0000; //main0
//---------------------------
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
assign sda = (out_flag == 1) ? sda_buf[7] : 1'bz;
//------------寄存器和存储器初始化---------------
initial
begin
addr_byte_h = 0;
addr_byte_l = 0;
ctrl_byte = 0;
out_flag = 0;
sda_buf = 0;
State = 2'b00;
memory_buf = 0;
address = 0;
shift = 0;
for(i=0;i<=8191;i=i+1)
memory[i] = 0;
end
//启动信号
always@(negedge sda)
begin
if(scl == 1)
begin
State = State + 1;
if(State == 2'b11)
disable write_to_eeprom;
end
end
//主状态机
always@(posedge sda)
begin
if(scl == 1) //停止操作
stop_W_R;
else
begin
casex(State)
2'b01:begin
read_in;
if(ctrl_byte == w7 || ctrl_byte == w6
|| ctrl_byte == w5 || ctrl_byte == w4
|| ctrl_byte == w3 || ctrl_byte == w2
|| ctrl_byte == w1 || ctrl_byte == w0)
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
begin
State = 2'b10;
write_to_eeprom; //写操作
end
else
State = 2'b00;
end
2'b11:
read_from_eeprom;
default:
State = 2'b00;
endcase
end
end //主状态机结束
//操作停止
task stop_W_R;
begin
State = 2'b00;
addr_byte_h = 0;
addr_byte_l = 0;
ctrl_byte = 0;
out_flag = 0;
sda_buf = 0;
end
endtask
//读进控制字和存储单元地址
task read_in;
begin
shift_in(ctrl_byte);
shift_in(addr_byte_h);
shift_in(addr_byte_l);
end
endtask
//EEPROM 的写操作
task write_to_eeprom;
begin
shift_in(memory_buf);
address = {addr_byte_h[4:0], addr_byte_l};
memory[address] = memory_buf;
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
State = 2'b00;
end
endtask
//EEPROM 的读操作
task read_from_eeprom;
begin
shift_in(ctrl_byte);
if(ctrl_byte == r7 || ctrl_byte == w6
|| ctrl_byte == r5 || ctrl_byte == r4
|| ctrl_byte == r3 || ctrl_byte == r2
|| ctrl_byte == r1 || ctrl_byte == r0)
begin
address = {addr_byte_h[4:0], addr_byte_l};
sda_buf = memory[address];
shift_out;
State = 2'b00;
end
end
endtask
//SDA 数据线上的数据存入寄存器,数据在 SCL 的高电平有效
task shift_in;
output[7:0]shift;
begin
@(posedge scl) shift[7] = sda;
@(posedge scl) shift[6] = sda;
@(posedge scl) shift[5] = sda;
@(posedge scl) shift[4] = sda;
@(posedge scl) shift[3] = sda;
@(posedge scl) shift[2] = sda;
@(posedge scl) shift[1] = sda;
@(posedge scl) shift[0] = sda;
@(negedge scl)
begin
#`timeslice;
out_flag = 1; //应答信号输出
sda_buf = 0;
end
@(negedge scl)
begin
#`timeslice;
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
out_flag = 0;
end
end
endtask
//EEPROM 存储器中的数据通过 SDA 数据线输出,数据在 SCL 低电平时变化
task shift_out;
begin
out_flag = 1;
for(i=6; i>=0; i=i-1)
begin
@(negedge scl);
#`timeslice;
sda_buf = sda_buf << 1;
end
@(negedge scl) #`timeslice sda_buf[7] = 1; //非应答信号输出
@(negedge scl) #`timeslice out_flag = 0;
end
endtask
endmodule
//eeprom.v 文件结束
IIC 读写状态机仿真测试顶层设计
仿真验证的框图如下,将 EEPROM 读写控制模块与 EEPROM 模型相连接就行了。
IIC_24LC64
clk50M
reset
iic_en
scl
address[12:0]
write
write_data[7:0]
read_data[7:0]
read
sda
done
cs_bit[2:0] EEPROM_24LC64
代码如下:
`timescale 1ns/1ns
`define clk_period 20
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
module IIC_AT24C64_tb;
reg clk50M;
reg reset;
reg iic_en;
reg [12:0]address;
reg write;
reg [7:0]write_data;
reg read;
wire [7:0]read_data;
wire scl;
wire sda;
wire done;
integer i;
IIC_AT24C64 IIC_AT24C64(
.clk50M(clk50M),
.reset(reset),
.iic_en(iic_en),
.cs_bit(3'b001),
.address(address),
.write(write),
.write_data(write_data),
.read(read),
.read_data(read_data),
.scl(scl),
.sda(sda),
.done(done)
);
EEPROM_AT24C64 EEPROM(
.scl(scl),
.sda(sda)
);
initial clk50M = 1'b1;
always #(`clk_period/2)clk50M = ~clk50M;
initial
begin
reset = 1'b0;
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
iic_en = 1'b0;
address = 13'h0;
write = 1'b0;
write_data = 1'b0;
read = 1'b0;
#(`clk_period*200 + 1)
reset = 1'b1;
#200;
//写数据,写 200 个数据
write = 1'b1;
address = 200;
write_data = 200;
iic_en = 1'b1;
#(`clk_period)
iic_en = 1'b0;
for(i=199;i>0;i=i-1)
begin
@(posedge done);
#2000;
address = i;
write_data = i;
iic_en = 1'b1;
#(`clk_period)
iic_en = 1'b0;
end
@(posedge done);
#2000;
write = 1'b0;
#5000;
//读数据,读取写入的 200 个数据
read = 1'b1;
iic_en = 1'b1;
#(`clk_period)
iic_en = 1'b0;
for(i=200;i>0;i=i-1)
begin
@(posedge done);
#2000;
address = i;
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
iic_en = 1'b1;
#(`clk_period)
iic_en = 1'b0;
end
@(posedge done);
#20000;
read = 1'b0;
#5000;
$stop;
end
endmodule
IIC 读写逻辑仿真结果分析
仿真验证主要是对模拟的 EEPROM 存储器器写入 200 个数据,然后读出写入的 200 个
数据,观察波形情况及读写是否和预期一样。
仿真波形如下:
写数据和读数据总体情况波形如下:
向 EEPROM 写数据波形图如下:
读取 EEPROM 数据波形如下:
由波形可以看出我们的设计是满足要求的。
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
串口读写 EEPROM 应用系统设计
接下来就是设计通过串口发送地址和写入数据,通过一定的数据处理后对 EEPROM 进
行读写操作。设计的框图如下:
IIC_24LC64
clk50M
reset
iic_en
scl
address[12:0]
write
write_data[7:0]
read_data[7:0]
read
sda
done
cs_bit[2:0]
UART_TX
uart_tx
tx_data[7:0]
rx_don
e
UART_RX
clk50M
reset
uart_rx
rx_data[7:0]
rx_done
baud_set[2:0]
DATA_TRF
tx_en
KEY_WR
KEY_EN
key_en
key_wr
clk50M
reset
iic_wr
读写控制位是通过一个按键来切换的,EEPROM 读写使能也是通过一个按键来使能的,
前期为了方便测试就是按键按下一次进行一次读写,在调试完成后可以将这个按键去掉,通
过发送的数据进行一定的判断后使能。按键和串口收发模块的代码都是学习小梅哥课程上
的,在芯航线 FPGA 数字系统设计教程+实例解析教程上有详细的设计过程。
数据转发模块设计
下面主要是数据转换模块的设计。前面 EEPROM 设计的是 2 字节地址段单字节读写,
串口发送一组数据包括 2 个字节地址和 1 字节写数据(如果是读操作该字节可以为任意数),
就相当于串口每次读写 EEPROM 时发送 3 个字节,数据处理模块将接收的 3 个字节数赋值
个读写地址和写数据(读数据时第个字节可以为任意数)。具体实现代码如下:
module DATA_TRF(
clk50M,
reset,
rx_data,
rx_done,
tx_data,
tx_en,
iic_wr,
address,
write_data,
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
read_data,
done
);
input clk50M; //系统时钟
input reset; //系统复位
input [7:0]rx_data; //从串口接收数据
input rx_done; //串口接收完成标志位
output [7:0]tx_data; //串口发送数据
output tx_en; //串口发送数据使能
input iic_wr; //IIC 读写控制位,1 为写,0 为读
output reg [12:0]address; //IIC 读写数据地址
output reg [7:0]write_data; //IIC 写操作数据
input [7:0]read_data; //IIC 读操作读取数据
input done; //IIC 一次读写完成标志位
//串口接收数据寄存器
reg [23:0] rx_data_buf;
//串口接收字节数
reg [1:0]rx_bit_cnt;
always@(posedge clk50M or negedge reset)
begin
if(!reset)
rx_bit_cnt <= 2'b0;
else if(done)
rx_bit_cnt <= 2'b0;
else if(rx_done)
rx_bit_cnt <= rx_bit_cnt + 2'b1;
else
rx_bit_cnt <= rx_bit_cnt;
end
//串口接收数据寄存器
always@(posedge clk50M or negedge reset)
begin
if(!reset)
rx_data_buf <= 24'h0;
else if(rx_done)
rx_data_buf <= {rx_data_buf[15:0],rx_data};
else
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
rx_data_buf <= rx_data_buf;
end
//接收的串口寄存器赋给 IIC 地址和写数据
always@(posedge clk50M or negedge reset)
begin
if(!reset)
begin
address <= 13'd0;
write_data <= 8'd0;
end
else if(rx_bit_cnt == 2'd3)
begin
address <= rx_data_buf[20:8];
write_data <= rx_data_buf[7:0];
end
else
begin
address <= address;
write_data <= write_data;
end
end
//读取 IIC 数据,并通过串口发送出去
assign tx_en = (iic_wr == 1'b0) && done;
assign tx_data = read_data;
endmodule
大致的思路是,,串口接收数据存入一个 3 字节缓存器中,,当接受到 3 个字节后就将缓存
器中的数据赋值给 EEPROM 读写控制模块的地址 address 和写数据 write_data。这样再通过
外部的按键使能这次的读/写操作就可完成一次读/写操作。
数据转发模块仿真文件设计
下面对数据转换模块进行仿真验证,仿真利用上面已经编写和的 EEPROM 读写控制模
块和 EEPROM 模块进行仿真,代码如下:
`timescale 1ns/1ns
`define clk_period 20
module DATA_TRF_tb;
reg clk50M; //系统时钟
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
reg reset; //系统复位信号
reg [7:0]rx_data; //串口接收数据
reg rx_done; //串口接收完成标志位
wire [7:0]tx_data; //串口发送数据
wire tx_en; //串口发送完成标志位
reg iic_wr; //IIC 读写控制
wire [12:0]address; //IIC 读写地址
wire [7:0]write_data;//IIC 写数据
wire [7:0]read_data; //IIC 读数据
wire done; //IIC 一次读写数据完成标志位
reg iic_en;
wire scl;
wire sda;
integer i;
//数据转换模块例化
DATA_TRF DATA_TRF(
.clk50M(clk50M),
.reset(reset),
.rx_data(rx_data),
.rx_done(rx_done),
.tx_data(tx_data),
.tx_en(tx_en),
.iic_wr(iic_wr),
.address(address),
.write_data(write_data),
.read_data(read_data),
.done(done)
);
//EEPROM 读写控制模块例化
IIC_AT24C64 IIC_AT24C64(
.clk50M(clk50M),
.reset(reset),
.iic_en(iic_en),
.cs_bit(3'b001),
.address(address),
.write(iic_wr),
.write_data(write_data),
.read(~iic_wr),
.read_data(read_data),
.scl(scl),
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
.sda(sda),
.done(done)
);
//EEPROM 模块例化
EEPROM_AT24C64 EEPROM(
.scl(scl),
.sda(sda)
);
initial clk50M = 1'b1;
always #(`clk_period/2) clk50M = ~clk50M;
initial
begin
//初始化
reset = 1'b0;
rx_data = 8'h0;
rx_done = 1'b0;
iic_wr = 1'b0;
iic_en = 1'b0;
#(`clk_period*200 + 1)
reset = 1'b1;
#2000;
//写数据,写入 200 个数据
iic_wr = 1'b1;
rx({16'd200,8'd200},3);
#2000;
iic_en = 1'b1;
#(`clk_period)
iic_en = 1'b0;
for(i=199;i>0;i=i-1)
begin
@(posedge done);
#2000;
rx({i[15:0],i[7:0]},3);
#2000;
iic_en = 1'b1;
#(`clk_period)
iic_en = 1'b0;
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
end
@(posedge done);
#2000;
//读数据,读取写入的 200 个数据
iic_wr = 1'b0;
rx({16'd200,8'd200},3);
#2000;
iic_en = 1'b1;
#(`clk_period)
iic_en = 1'b0;
for(i=199;i>0;i=i-1)
begin
@(posedge done);
#2000;
rx({i[15:0],i[7:0]},3);
#2000;
iic_en = 1'b1;
#(`clk_period)
iic_en = 1'b0;
end
@(posedge done);
#2000;
$stop;
end
//模拟串口一次发送多个字节的情况
task rx;
input [23:0]data;
input [1:0] data_cnt;
repeat(data_cnt)
begin
rx_data = data[23:16];
rx_done = 1'b1;
#(`clk_period)
rx_done = 1'b0;
#2000;
data = data << 8;
end
endtask
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
endmodule
数据转发模块仿真结果分析
仿真波形如下:
写数据和读数据总的波形图如下:
写数据波形图如下:
读数据波形如下:
观察波形,与预期效果一致。
系统顶层文件设计
下面就是顶层文件的设计,根据上面的框图很容易写出顶层文件,代码如下:
module IIC_top(
clk50M,
reset,
key_iic_en,
key_iic_wr,
rs232_rx,
rs232_tx,
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
scl,
sda,
led
);
input clk50M; //系统时钟
input reset; //系统复位
input key_iic_en; //IIC 读写使能按键控制
input key_iic_wr; //IIC 读写控制
input rs232_rx; //串口接收
output rs232_tx; //串口发送
output scl; //IIC 时钟总线
inout sda; //IIC 数据总线
output [1:0]led; //LED 读写控制指示灯,亮为写,灭为读
wire key_flag_en;
wire key_state_en;
wire key_flag_wr;
wire key_state_wr;
wire iic_en;
reg iic_wr;
wire [7:0]rx_data;
wire rx_done;
wire[7:0]tx_data;
wire tx_en;
wire [12:0]address;
wire [7:0]write_data;
wire [7:0]read_data;
wire done;
//按键控制 IIC 读写使能
key_filter key_en(
.Clk(clk50M),
.Rst_n(reset),
.key_in(key_iic_en),
.key_flag(key_flag_en),
.key_state(key_state_en)
);
//IIC 读写使能信号
assign iic_en = key_flag_en && (~key_state_en);
assign led[1] = key_state_en;
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
//按键切换读写控制
key_filter key_wr(
.Clk(clk50M),
.Rst_n(reset),
.key_in(key_iic_wr),
.key_flag(key_flag_wr),
.key_state(key_state_wr)
);
//按键按一次切换一次,初始默认写
always@(posedge clk50M or negedge reset)
begin
if(!reset)
iic_wr <= 1'b1;
else if(key_flag_wr && (~key_state_wr))
iic_wr <= ~iic_wr;
else
iic_wr <= iic_wr;
end
//LED 指示读写控制,亮代表写操作,灭代表读操作
assign led[0] = ~iic_wr;
//数据转换模块的例化
DATA_TRF DATA_TRF(
.clk50M(clk50M),
.reset(reset),
.rx_data(rx_data),
.rx_done(rx_done),
.tx_data(tx_data),
.tx_en(tx_en),
.iic_wr(iic_wr),
.address(address),
.write_data(write_data),
.read_data(read_data),
.done(done)
);
//IIC 读写控制模块的例化
IIC_AT24C64 IIC_AT24C64(
.clk50M(clk50M),
.reset(reset),
.iic_en(iic_en),
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
.cs_bit(3'b001),
.address(address),
.write(iic_wr),
.write_data(write_data),
.read(~iic_wr),
.read_data(read_data),
.scl(scl),
.sda(sda),
.done(done)
);
//串口发送模块
uart_byte_tx uart_byte_tx(
.Clk(clk50M),
.Rst_n(reset),
.send_en(tx_en),
.baud_set(3'b0), //波特率 9600
.Data_Byte(tx_data),
.Rs232_Tx(rs232_tx),
.Tx_Done(),
.uart_state()
);
//串口接收模块
uart_byte_rx uart_byte_rx(
.Clk(clk50M),
.Rst_n(reset),
.Rs232_rx(rs232_rx),
.baud_set(3'b0), //波特率 9600
.Data_Byte(rx_data),
.Rx_Done(rx_done)
);
endmodule
在顶层文设计中增加了两个 LED,LED0 用指示当前的读/写操作状态,亮表示写 EEPROM 操
作状态,灭表示读 EEPROM 数据操作状态;LED1 表示 EEPROM 读/写使能按键按下指示,
按键按下灯亮,按键松开灯灭。完成后进行引脚分配,编译,板级下载后测试。
系统测试
测试如下:
芯航线 FPGA 开发板,一套让你真正从零基础到成长为具备独立开发能力的 FPGA 学习平台
https://xiaomeige.taobao.com/ 芯航线电子工作室
初始默认状态下是写 EEPROM 操作(通过 LED0 亮可以观察),此时,用串口工具发送
十六进制数 00 00 56 ,然后按下引脚 key_icc_en 对应的按键 key0,使能 EEPROM 读写操作,
完成了一次写操作。可以多次使用同样的方法写入不同地址中的数据。这里我再写入 2 组数
据,分别为 00 AB 39 、00 B1 AB 。写操作完成后按下引脚 key_icc_wr 对应的按键 key1,此
时 LED0 灭,表示可以进行读操作。这里我们依次对上面写入数据的地址进行读操作,同样
在串口发送个字节,前两个字节中低 13 位表示的地址,第 3 个字节可以为任意数都行(这
里具体可以看代码理解,读者也可以想其他方法实现),串口工具依次发送 00 00 12 后按下
按键 key0;发送 00 AB 23 后按下按键 key0;发送 00 B1 AB 后按下按键 key0;每次按下按
键会接收到读出的数据,如下图所示:
这里串口每次发送 3 个字节,然后按一下使能按键 key0,完成一次读写操作,写操作中第
3 个字节为写入的数据,在读操作中,第 3 个字节是可以为任意值,但是不能省掉,要省掉
可以更改设计进行优化,读者可以自己寻找优化方法。至于每次按键一次才进行一次读写操
作有点麻烦,读者也可以进行修改优化。本次的是除了用 modelsim 仿真验证以外,还用到
了 SignalTap II Logic Analyzer 工具对波形进行了抓取分析
如有更多问题,欢迎加入芯航线 FPGA 技术支持群交流学习:472607506
小梅哥
芯航线电子工作室
关于学习资料,小梅哥系列所有能够开放的资料和更新(包括视频教程,程序代码,教程文
档 , 工 具 软 件 , 开 发 板 资 料 ) 都 会 发 布 在 我 的 云 分 享 。( 记 得 订 阅 ) 链 接 :
http://yun.baidu.com/share/home?uk=402885837&view=share#category/type=0
赠 送 芯 航 线 AC6102 型 开 发 板 配 套 资 料 预 览 版 下 载 链 接 : 链 接 :
http://pan.baidu.com/s/1slW2Ojj 密码:9fn3
赠送 SOPC 公开课链接和 FPGA 进阶视频教程。链接:http://pan.baidu.com/s/1bEzaFW 密
码:rsyh