目录
一丶I2C协议
1.I2C简介
I2C即Inter-Integrated Circuit(集成电路总线),是由Philips半导体公司(现在的NXP半导体公司)在八十年代初设计出来的一种简单、双向、二线制总线标准。多用于主机和从机在数据量不大且传输距离短的场合下的主从通信。主机启动总线,并产生时钟用于传送数据,此时任何接收数据的器件均被认为是从机。
I2C总线由数据线SDA和时钟线SCL构成通信线路,既可用于发送数据,也可接收数据。在主控与被控IC之间可进行双向数据传送数据的传输速率:
- 标准模式:100kbit/s
- 快速模式:400kbit/s
- 高速模式:3.4Mbit/s
各种被控器件均并联在总线上,通过器件地址(SLAVEADDR,具体可查器件手册)识别。结构示意图如下所示(出自原子哥):
图中的I2C_SCL
是串行时钟线,I2C_SDA
是串行数据线,由于I2C器件一般采用开漏结构与总线相连,所以I2C_SCL和I2C_SDA均需接上拉电阻,也正因此,当总线空闲时,这两条线路都处于高电平状态,当连到总线上的任一器件输出低电平,都将使总线拉低,即各器件的SDA及SCL都是“线与”关系。
I2C总线支持多主和主从两种工作方式,通常工作在主从工作方式,我们的开发板就采用主从工作方式。在主从工作方式中,系统中只有一个主机,其它器件都是具有I2C总线的外围从机。在主从工作方式中,主机启动数据的发送(发出启动信号)并产生时钟信号,数据发送完成后,发出停止信号。
I2C总线结构虽然简单,使用两线传输,然而要实现器件间的通信,需要通过控制SCL和SDA的时序,使其满足I2C的总线传输协议,方可实现器件间的数据传输。那么I2C协议的时序是怎样的呢?
2.I2C时序
①空闲状态:
在I2C器件开始通信(传输数据)之前,串行时钟线SCL和串行数据线SDA线由于上拉的原因处于高电平状态,此时I2C总线处于空闲状态。
②起始信号:
如果主机(此处指FPGA)想开始传输数据,只需在SCL为高电平时将SDA线拉低,产生一个起始信号,从机检测到起始信号后,准备接收数据
③停止信号:
当数据传输完成,主机只需产生一个停止信号,告诉从机数据传输结束,停止信号的产生是在SCL为高电平时,SDA从低电平跳变到高电平,从机检测到停止信号后,停止接收数据。
I2C整体时序如下图。起始信号之前为空闲状态,起始信号之后到停止信号之前的这一段为数据传输状态,主机可以向从机写数据,也可以读取从机输出的数据,数据的传输由双向数据线(SDA)完成。停止信号产生后,总线再次处于空闲状态。
了解到了整体时序之后,我们可能有疑问,
数据是以什么样的格式传输的呢?
满足怎样的时序要求呢?
是在任何时候改变都可以吗?
怎么知道从机有没有接收到数据呢?
带着这些疑问,我们继续学习I2C。
由于只有一根数据线进行数据的传输,如果不规定好传输规则肯定会导致信息错乱,如同在单条道路上驾驶,没有交通规则,再好的道路也会发生拥堵甚至更糟。采用两线结构的I2C虽然只有一根数据线,但由于还有一条时钟线,可以让数据线在时钟线的带领下有顺序的传送,就好像单条道路上的车辆在交警或信号指示灯的指示下有规则的通行。那么I2C遵循怎样的规则呢?
④数据传输:
我们在起始信号之后,主机开始发送传输的数据;在串行时钟线SCL为低电平状态时,SDA允许改变传输的数据位(1为高电平,0为低电平),在SCL为高电平状态时,SDA要求保持稳定,相当于一个时钟周期传输1bit数据,经过8个时钟周期后,传输了8bit数据,即一个字节。
⑤应答信号:
第8个时钟周期末,主机释放SDA以使从机应答,在第9个时钟周期,从机将SDA拉低以应答(NCK);如果第9个时钟周期,SCL为高电平时,SDA未被检测到为低电平,视为非应答(NACK),表明此次数据传输失败。第9个时钟周期末,从机释放SDA以使主机继续传输数据,如果主机发送停止信号,此次传输结束。我们要注意的是数据以8bit即一个字节为单位串行发出,其最先发送的是字节的最高位。
注意:若是接收端是主控端,则在它收到最后一个bit数据(发送完1字节数据)后,发送一个非应答(NACK)信号,以通知被控发送端结束数据发送,并释放SDA线,以便主控接收器发送停止信号。
二丶EEPROM存储器
1.有哪些存储器?
首先我们先弄清楚在计算机中有哪些存储器
ROM(只读存储器,Read only memory)指一旦写入,则无法擦除改写的存储器。此处,将ROM的定义做进一步的扩展,定义为非易失存储器,即器件掉电后,内部存储器内容仍保留,且支持电可擦除可改写的只读存储器。
电路设计中常用的ROM包括EEPROM(电可擦写可编程只读存储器)和FLASH(闪速存储器)
其它存储器介绍见存储器分类
由于我们开发板上的EEPROM采用两线串行接口的双向数据传输协议——I2C协议实现读写操作,所以这里我们主要分析对EEPROM的读写
2.手册分析及时序分析
又到了最痛苦的环节,阅读英文手册😨,没办法,一点一点来,不要放弃,看的多了自然就熟练了,同志们加油!!!😄
从头到尾提取出我们需要的信息:
1.查看手册对于开发板上EEPROM的描述
可以知道本次实验所用的EEPROM的存储容量为4Kbit,内部分成两块(block),每一块有256个字节,共有4096bit(256*2*8)
2.查看手册中的功能描述
总线由生成时钟信号(SCL)和起始结束条件的主控设备控制
3.查看总线特性
在数据传输期间,数据线(SDA)在时钟线(SCL)高电平期间保持稳定;而在时钟线高电平期间,数据线的改变作为通信的开始和结束条件
4.查看通信的起始和结束条件
起始信号:在时钟线(SCL)高电平期间,数据线(SDA)由高电平拉低,且起始信号必须在所有指令之前,也就是作为通信的开始
结束信号:在时钟线(SCL)高电平期间,数据线(SDA)由低电平拉高,且结束信号必须在所有指令操作之后,也就是以结束信号作为通信的结束
5.查看**“数据有效”**
重要信息:在对EEPROM进行写操作的时候,只会存储最后写进去的16个字节。这里不是很清晰我们在下面的PageWrite详细解释
6.查看**“应答位”**
i2C总线上的所有数据都是以字节传送的,发送端每发送一个字节,就必须在第9个SCL脉冲期间释放SDA,由接收端反馈一个应答信号。应答信号为低电平时,称为有效应答位(ACK),表示接收端成功接收了该字节;应答信号为高电平时,称为非应答位(NACK),表示接收端接收该字节失败。对于反馈有效应答位ACK的要求是,接收端在第9个时钟脉冲之前的低电平期间将SDA拉低,并且确保在该时钟周期的高电平期间保持低电平。如果接收端是主控端,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送端结束数据发送,并释放SDA线,以便主控接收器发送停止信号。
7.查看**“设备地址”**
主机在与从机通信之前,主机需要配置对应要访问的从机的地址。
- 所以主从机通信的第一个字节是控制字节,其中前四位是从的设备编码“1010”;
- 接下来的两位没实际作用;
- “B0”控制所访问从机的哪一块存储空间;
- “R/W”控制读写操作,0代表写操作,1代表读操作;
- 最后从机发送应答位(ACK)
8.查看 “写操作”
看到这里可以发现写操作分为两种:即 字节写 和 页写
①首先来看字节写:
- 首先是起始位,接着发送第一个字节(控制字节),注意最后一位设置为“0”,代表写操作;
- 之后从机发送应答位(ACK),主机接着发送第二个字节(要写数据的存储地址);
- 之后从机发送应答位(ACK),主机发送要写的数据;
- 之后从机发送应答位(ACK),主机发送停止位结束这一次写操作
②然后看页写:
很清晰的可以看出页写与字节写的区别只在于多写了15个字节的数据(一页最多写16字节)
另外这里需要注意一个重点:
翻译:
收到每个字后,组成字节计数器的四个低阶地址指针位在内部加1。字地址的高阶4位和B0位保持不变。
在写满16字节之后如果再写数据,地址指针将滚动,并且先前接收到的数据将被覆盖。(通俗来讲就是,低四位表示“1111”,再增加数据,加1进位,变成“0000”,也就是又从第一位开始写,这样会覆盖之前写入的数据)
9.查看 “读操作”
有三种基本的读操作:当前地址读、随机读和顺序读。
①当前地址读
翻译:
如果之前的访问(读或写操作)是为了访问地址n,那么下一个当前地址读操作将访问地址n + 1的数据
然后来看时序图
与写操作不同的是,控制字节的最后1bit是“1”,表示读操作;
之后从机发送8位数据,主机返回非应答(NACK)并发送停止位。
②随机地址读
随机地址读分为了两个部分
写操作:起始位→一个写控制字节→ACK→字地址→ACK
读操作:起始位→一个读控制字节→ACK→数据→NACK
③顺序地址读
顺序地址读的时序承接随即地址读,随机地址读只读出了一个字节数据主机就发送了NACK,顺序地址读不止读出一个数据。
到这里我们需要的信息基本提取完成!
三丶需求分析
系统功能需求
1、使用按键,模拟读写请求信号;
2、收到读写请求信号时,FPGA 向 EEPROM 芯片写入单字节数据或从 EEPROM 芯片读出单字节数据;
3、把写入或者读出的数据显示在数码管,并指示当前是读操作、写操作或者 空操作。
系统模块划分
1、I2C 接口模块:按照 I2C 协议时序将数据串行写入 EEPROM 或者从 EEPROM 读出串行数据; EEPROM
2、读写控制模块:根据接收到的按键信号请求以及 I2C 接口模块反馈信号,发送请求、指令以及数据给 I2C 接口模块或者从 I2C
3、接口模块读取数据; 数码管驱动模块:指示当前状态,并将写入或读出的数据显示在数码管上。
i2c_master状态机 - - - I2C接口模块
- IDLE:空闲状态
- START: 发送起始位
- WRITE:写数据
- READ:读数据
- R_ACK:接收应答
- S_ACK:发送应答
- STOP:发送停止位
这个状态机我们可以分为读和写操作分别分析
读:
以随机地址读为例
IDLE→START→WRITE(首先写读控制字节)→R_ACK
→IDLE→WRITE(写字节地址)→R_ACK
→IDLE→START→WRITE(写读控制字节)→R_ACK
→IDLE→READ→S_ACK→STOP
写:
以字节写为例
IDLE→START→WRITE(首先写读控制字节)→R_ACK
→IDLE→WRITE(写字节地址)→R_ACK
→IDLE→WRITE(写数据)→R_ACK→STOP
eeprom读写控制状态机 - - - 读写控制模块
- IDLE:空闲状态
- WR_REQ: 写传输 发送请求、命令、数据
- WAIT_WR:等待一个字节传完
- RD_REQ:读传输 发送请求、命令、数据
- WAIT_RD:等待一个字节传完
- DONE:一次读或写完成
四丶系统框图
五丶代码设计
1.顶层模块
top.v
module top (
input clk ,
input rst_n ,
input rx ,//uart接收端口
input key ,//按键输入
output tx ,//uart发送端口
output scl ,//IIC时钟线
inout sda //IIC数据线
);
//按键
wire key_done ;
//串口
wire [7:0] dout ;
wire dout_vld ;
wire [7:0] din ;
wire din_vld ;
wire ready ;
uart_rx u_uart_rx(
/* input wire */ .clk (clk ) ,
/* input wire */ .rst_n (rst_n ) ,
/* input wire */ .rx (rx ) ,
/* output reg [7:0] */ .dout (din ) , //由接收到的数据进行并转串输出给uart_tx模块
/* output reg */ .dout_vld (din_vld ) //接收完成标志位
);
uart_tx u_uart_tx(
/* input wire */ .clk (clk ) ,
/* input wire */ .rst_n (rst_n ) ,
/* input wire [7:0] */ .din (dout ) ,
/* input wire */ .din_vld (dout_vld ) ,
/* output reg */ .tx (tx ) ,
/* output reg */ .ready (ready )
);
key_debounce u_key_debounce(
/* input wire */ .clk (clk ) , //系统时钟 50MHz
/* input wire */ .rst_n (rst_n ) , //复位信号
/* input wire */ .key (key ) , //按键输入信号
/* output reg */ .key_done (key_done ) //消抖之后的按键信号
);
control u_control(
/* input */ .clk (clk ) ,
/* input */ .rst_n (rst_n ) ,
/* //串口 *
/* input [7:0] */ .din (din ) ,
/* input */ .din_vld (din_vld ) ,
/* output [7:0] */ .dout (dout ) ,
/* output */ .dout_vld (dout_vld ) ,
/* input */ .ready (ready ) ,
/* //按键 *
/* input */ .rd_en (key_done ) ,
/* //IIC信号 *
/* output */ .i2c_scl (scl ) ,//IIC时钟
/* inout */ .i2c_sda (sda ) //IIC数据
);
endmodule
2.串口模块
uart_rx
module uart_rx (
input wire clk,
input wire rst_n,
input wire rx,
output reg [7:0] dout, //由接收到的数据进行并转串输出给uart_tx模块
output reg dout_vld //接收完成标志位
);
reg flag; //rx下降沿来临flag拉高
reg [9:0] dout_r; //锁存rx接收到的数据(从起始位 到数据为 到停止位)
reg rx_r0; //同步
reg rx_r1; //打拍
wire nedge; //下降沿检测
//波特率计数器
reg [8:0] cnt_bps;
wire add_cnt_bps;
wire end_cnt_bps;
//bit计数器
reg [3:0] cnt_bit;
wire add_cnt_bit;
wire end_cnt_bit;
parameter BPS_115200 = 434;
//同步
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rx_r0<=1;
end
else
rx_r0<=rx;
end
//打拍
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rx_r1<=1;
end
else
rx_r1<=rx_r0;
end
assign nedeg=~rx_r0 & rx_r1; //下降沿检测,起始位为低电平
//flag
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
flag<=0;
end
else if(nedeg) begin
flag<=1;
end
else if(end_cnt_bit) begin
flag<=0;
end
end
//cnt_bps
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_bps<=0;
end
else if(add_cnt_bps) begin
if (end_cnt_bps) begin
cnt_bps<=0;
end
else
cnt_bps<=cnt_bps+1;
end
end
assign add_cnt_bps=flag;
assign end_cnt_bps=add_cnt_bps&&cnt_bps==BPS_115200-1;
//cnt_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
cnt_bit<=cnt_bit+1;
end
end
assign add_cnt_bit=end_cnt_bps;
assign end_cnt_bit=add_cnt_bit&&cnt_bit==10 -1;
//dout_r
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
dout_r<=0;
end
else if(cnt_bps==(BPS_115200>>1)) begin
dout_r[cnt_bit]<=rx_r0;
end
end
//dout
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
dout<=0;
end
else if(end_cnt_bit) begin
dout<=dout_r[8:1]; //只用把数据位传给uart_tx模块,所以取中间的8位
end
end
//dout_vld
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
dout_vld<=0;
end
else if(end_cnt_bit) begin
dout_vld<=1; //给发送模块的数据有效信号
end
else
dout_vld<=0;
end
endmodule //uart_rx
uart_tx
module uart_tx (
input wire clk,
input wire rst_n,
input wire [7:0] din,
input wire din_vld,
output reg tx,
output reg ready
);
//定义一个寄存器来锁存 din_vld 时的din
reg [9:0] data;
//波特率计数器
reg [8:0] cnt_bps;
wire add_cnt_bps;
wire end_cnt_bps;
//比特计数器
reg [4:0] cnt_bit;
wire add_cnt_bit;
wire end_cnt_bit;
reg flag; //计数器开启标志位
parameter BPS_115200=434; //发送一bit数据需要的周期数
//data
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data<=0;
end
else if(din_vld) begin
data<={1'b1,din,1'b0}; //拼接起始位和停止位
end
else
data<=data;
end
//发送数据 tx
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tx<=1'b1;
end
else if(cnt_bps==1) begin
tx<=data[cnt_bit];
end
end
//flag
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
flag<=0;
end
else if(din_vld) begin
flag<=1;
end
else if(end_cnt_bit) begin //发送完成关闭计数器
flag<=0;
end
else
flag<=flag;
end
//ready
always @(*) begin
if (!rst_n) begin
ready<=1;
end
else if(flag) begin
ready<=0;
end
else begin
ready<=1;
end
end
//cnt_bps
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_bps<=0;
end
else if(add_cnt_bps) begin
if (end_cnt_bps) begin
cnt_bps<=0;
end
else
cnt_bps<=cnt_bps+1;
end
end
assign add_cnt_bps=flag;
assign end_cnt_bps=add_cnt_bps&&cnt_bps==BPS_115200-1;
//cnt_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
cnt_bit<=cnt_bit+1;
end
end
assign add_cnt_bit=end_cnt_bps;
assign end_cnt_bit=add_cnt_bit&&cnt_bit==9;
endmodule //uart_tx
3.控制模块
control.v
module control (
input clk ,
input rst_n ,
//串口
input [7:0] din ,
input din_vld ,
output [7:0] dout ,
output dout_vld ,
input ready ,
//按键
input rd_en ,
//IIC信号
output i2c_scl ,//IIC时钟
inout i2c_sda //IIC数据
);
//信号定义
wire req ;
wire [3:0] cmd ;
wire [7:0] wr_data ;
wire [7:0] rd_data ;
wire done ;
wire slave_ack ;
wire i2c_sda_i ;
wire i2c_sda_o ;
wire i2c_sda_oe ;
//三态门
assign i2c_sda = i2c_sda_oe?i2c_sda_o:1'bz;
assign i2c_sda_i = i2c_sda;
//模块例化
eeprom_ctrl u_eeprom_ctrl(
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*input [7:0] */.din (din ),
/*input */.din_vld (din_vld ),
/*input */.rd_en (rd_en ),
/*output [7:0] */.dout (dout ),//控制器输出数据
/*output */.dout_vld(dout_vld ),
/*input */.ready (ready ),
/*output */.req (req ),
/*output [3:0] */.cmd (cmd ),
/*output [7:0] */.wr_data (wr_data ),
/*input [7:0] */.rd_data (rd_data ),
/*input */.done (done ) //传输完成标志
);
i2c_master u_i2c_master(
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*input */.req (req ),
/*input [3:0] */.cmd (cmd ),
/*input [7:0] */.din (wr_data ),
/*output [7:0] */.dout (rd_data ),
/*output */.done (done ),
/*output */.i2c_scl (i2c_scl ),
/*input */.i2c_sda_i (i2c_sda_i ),
/*output */.i2c_sda_o (i2c_sda_o ),
/*output */.i2c_sda_oe (i2c_sda_oe)
);
endmodule
4.IIC接口
i2c_master.v
`include "param.v"
module i2c_master (
input clk ,
input rst_n ,
//控制信号
input req ,//请求
input [3:0] cmd ,//命令
input [7:0] din ,//写数据
//IIC信号
output i2c_scl ,//SCL---200kHz
input i2c_sda_i ,//SDA---输入
output i2c_sda_o ,//SDA---输出
output i2c_sda_oe ,//SDA---输出使能
output [7:0] dout ,
output done
);
//状态机参数定义
localparam IDLE = 7'b000_0001,
START = 7'b000_0010,
WRITE = 7'b000_0100,
RACK = 7'b000_1000,
READ = 7'b001_0000,
SACK = 7'b010_0000,
STOP = 7'b100_0000;
reg [6:0] state_c ;//现态
reg [6:0] state_n ;//次态
reg [3:0] cmd_r ;//命令寄存器
reg [7:0] din_r ;//写数据寄存器
reg [7:0] dout_r ;//输出数据寄存器
reg slave_ack_r ;//接收应答寄存器
//IIC信号寄存
reg scl ;
reg sda_out ;
reg sda_out_en ;
//比特计数器
reg [3:0] cnt_bit ;
wire add_cnt_bit ;
wire end_cnt_bit ;
//IIC时钟计数器
reg [7:0] cnt_scl ;
wire add_cnt_scl ;
wire end_cnt_scl ;
//状态转移条件
wire idle2start ;
wire idle2write ;
wire idle2read ;
wire start2write ;
wire start2read ;
wire write2rack ;
wire rack2stop ;
wire rack2idle ;
wire read2sack ;
wire sack2stop ;
wire sack2idle ;
wire stop2idle ;
//状态机
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state_c <= IDLE;
end
else
state_c <= state_n;
end
always @(*) begin
case (state_c)
IDLE :begin
if(idle2start)
state_n = START;
else if (idle2write) begin
state_n = WRITE;
end
else if (idle2read) begin
state_n = READ;
end
else
state_n = state_c;
end
START :begin
if (start2write) begin
state_n = WRITE;
end
else if (start2read) begin
state_n = READ;
end
else
state_n = state_c;
end
WRITE :begin
if(write2rack)
state_n = RACK ;
else
state_n = state_c ;
end
RACK :begin
if(rack2stop)
state_n = STOP ;
else if(rack2idle)
state_n = IDLE ;
else
state_n = state_c ;
end
READ :begin
if(read2sack)
state_n = SACK ;
else
state_n = state_c ;
end
SACK :begin
if(sack2stop)
state_n = STOP ;
else if(sack2idle)
state_n = IDLE ;
else
state_n = state_c ;
end
STOP :begin
if(stop2idle)
state_n = IDLE ;
else
state_n = state_c ;
end
default : state_n = IDLE;
endcase
end
assign idle2start = state_c==IDLE && (req && (cmd & `CMD_START));
assign idle2write = state_c==IDLE && (req && (cmd & `CMD_WRITE)) ;
assign idle2read = state_c==IDLE && (req && (cmd & `CMD_READ)) ;
assign start2write = state_c==START && (end_cnt_bit && (cmd_r &`CMD_WRITE)) ;
assign start2read = state_c==START && (end_cnt_bit && (cmd_r &`CMD_READ)) ;
assign write2rack = state_c==WRITE && (end_cnt_bit) ;
assign read2sack = state_c==READ && (end_cnt_bit) ;
assign rack2stop = state_c==RACK && (end_cnt_bit && ((cmd_r & `CMD_STOP) || slave_ack_r));
assign sack2stop = state_c==SACK && (end_cnt_bit && (cmd_r & `CMD_STOP)) ;
assign rack2idle = state_c==RACK && (end_cnt_bit && (cmd_r & `CMD_STOP) == 0) ;
assign sack2idle = state_c==SACK && (end_cnt_bit && (cmd_r & `CMD_STOP) == 0) ;
assign stop2idle = state_c==STOP && (end_cnt_bit) ;
//scl
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
scl <= 1;
end
else if (idle2start | idle2write | idle2read) begin
scl <= 0;
end
else if (add_cnt_scl && cnt_scl == `SCL_HALF-1) begin
scl <= 1;
end
else if (end_cnt_scl && ~stop2idle) begin
scl <= 0;
end
end
//sda_out_en
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
sda_out_en <= 0;
end
else if(idle2start | idle2write | read2sack | rack2stop)begin
sda_out_en <= 1'b1;
end
else if(idle2read | start2read | write2rack | stop2idle)begin
sda_out_en <= 1'b0;
end
end
//sda_out
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
sda_out <= 1;
end
//发起始位
else if (state_c == START) begin
if (cnt_scl == `LOW_HLAF) begin
sda_out <= 1; //时钟低电平拉高数据线
end
else if (cnt_scl == `HIGH_HALF) begin
sda_out <= 0; //时钟高电平拉低数据线---保证可以检测到起始位
end
end
//发数据
else if (state_c == WRITE) begin
if (cnt_scl == `LOW_HLAF) begin
sda_out <= din_r[7-cnt_bit]; //时钟低电平发送数据---并串转换
end
end
//发应答位
else if (state_c == SACK) begin
if (cnt_scl ==`LOW_HLAF) begin
sda_out <= (cmd_r & `CMD_STOP ? 1 : 0); //时钟低电平发应答位
end
end
//发停止位
else if (state_c == STOP) begin
if (cnt_scl == `LOW_HLAF) begin
sda_out <= 0; //时钟低电平拉低数据线
end
else if (cnt_scl == `HIGH_HALF) begin
sda_out <= 1; //时钟高电平拉高数据线---保证可以检测到停止位
end
end
end
//cmd_r
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cmd_r <= 0;
end
else if (req) begin
cmd_r <= cmd;
end
end
//din_r
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
din_r <= 0;
end
else if (req) begin
din_r <= din;
end
end
//cnt_scl
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
cnt_scl <= cnt_scl+1;
end
end
assign add_cnt_scl = (state_c != IDLE);
assign end_cnt_scl = add_cnt_scl && cnt_scl == (`SCL_PERIOD) -1;
//cnt_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
cnt_bit <= cnt_bit+1;
end
end
assign add_cnt_bit = end_cnt_scl;
assign end_cnt_bit = add_cnt_bit && cnt_bit == ((state_c == READ || state_c == WRITE) ? 8 :1)-1;//除开读写数据需要传输8bit数据,其他都是1bit
//dout_r
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
dout_r <= 0;
end
else if (state_c == READ) begin
if (cnt_scl == `HIGH_HALF) begin
dout_r[7-cnt_bit] <= i2c_sda_i; //串并转换
end
end
end
//slave_ack_r
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
slave_ack_r <= 1;
end
else if (state_c == RACK) begin
if (cnt_scl == `HIGH_HALF) begin
slave_ack_r <= i2c_sda_i;
end
end
end
assign i2c_scl = scl;
assign i2c_sda_o = sda_out;
assign i2c_sda_oe = sda_out_en;
//assign slave_ack = slave_ack_r;
assign dout = dout_r;
assign done = rack2idle | sack2idle | stop2idle;//1字节数据传输完的标志---每传输1字节都有一个应答位
endmodule
5.eeprom控制器
eeptom_ctrl.v
`include "param.v"
module eeprom_ctrl (
input clk ,
input rst_n ,
//按键使能
input rd_en ,
//串口信号
input [7:0] din ,
input din_vld ,
output [7:0] dout ,
output dout_vld ,
input ready ,
//IIC接口信号
output req ,
output [3:0] cmd ,
output [7:0] wr_data ,//向EEPROM写数据
input [7:0] rd_data ,//从EEPROM读数据
input done
);
//状态机参数
localparam IDLE = 6'b00_0001 ,
WR_REQ = 6'b00_0010 ,//写传输 发送请求、命令、数据
WAIT_WR = 6'b00_0100 ,//等待一个字节传完
RD_REQ = 6'b00_1000 ,//读传输 发送请求、命令、数据
WAIT_RD = 6'b01_0000 ,//等待一个自己传完
DONE = 6'b10_0000 ;//一次读或写完成
//信号定义
reg [5:0] state_c;//现态
reg [5:0] state_n;//次态
reg [3:0] cnt_byte;
wire add_cnt_byte;
wire end_cnt_byte;
reg tx_req ;//请求
reg [3:0] tx_cmd ;
reg [7:0] tx_data ;
reg [8:0] wr_addr ;//写eeprom地址
reg [8:0] rd_addr ;//读eeprom地址
reg [7:0] dout_r ;
reg dout_vld_r ;
wire wfifo_rd ;
wire wfifo_wr ;
wire wfifo_empty ;
wire wfifo_full ;
wire [7:0] wfifo_qout ;
wire [5:0] wfifo_usedw ;
wire rfifo_rd ;
wire rfifo_wr ;
wire rfifo_empty ;
wire rfifo_full ;
wire [7:0] rfifo_qout ;
wire [5:0] rfifo_usedw ;
wire idle2wr_req ;
wire wr_req2wait_wr ;
wire wait_wr2wr_req ;
wire wait_wr2done ;
wire idle2rd_req ;
wire rd_req2wait_rd ;
wire wait_rd2rd_req ;
wire wait_rd2done ;
wire done2idle ;
//状态机
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state_c <= IDLE;
end
else
state_c <= state_n;
end
always @(*) begin
case(state_c)
IDLE :begin
if(idle2wr_req)
state_n = WR_REQ ;
else if(idle2rd_req)
state_n = RD_REQ ;
else
state_n = state_c ;
end
WR_REQ :begin
if(wr_req2wait_wr)
state_n = WAIT_WR ;
else
state_n = state_c ;
end
WAIT_WR :begin
if(wait_wr2wr_req)
state_n = WR_REQ ;
else if(wait_wr2done)
state_n = DONE ;
else
state_n = state_c ;
end
RD_REQ :begin
if(rd_req2wait_rd)
state_n = WAIT_RD ;
else
state_n = state_c ;
end
WAIT_RD :begin
if(wait_rd2rd_req)
state_n = RD_REQ ;
else if(wait_rd2done)
state_n = DONE ;
else
state_n = state_c ;
end
DONE :begin
if(done2idle)
state_n = IDLE ;
else
state_n = state_c ;
end
default : state_n = IDLE ;
endcase
end
assign idle2wr_req = state_c == IDLE && (wfifo_usedw > 0);
assign wr_req2wait_wr = state_c == WR_REQ && (1);
assign wait_wr2wr_req = state_c == WAIT_WR && (done & cnt_byte < `WR_BYTE-1);
assign wait_wr2done = state_c == WAIT_WR && (end_cnt_byte);
assign idle2rd_req = state_c == IDLE && (rd_en);
assign rd_req2wait_rd = state_c == RD_REQ && (1);
assign wait_rd2rd_req = state_c == WAIT_RD && (done & cnt_byte < `RD_BYTE-1);
assign wait_rd2done = state_c == WAIT_RD && (end_cnt_byte);
assign done2idle = state_c == DONE && (1);
//cnt_byte
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_byte <= 0;
end
else if(add_cnt_byte) begin
if (end_cnt_byte) begin
cnt_byte <= 0;
end
else
cnt_byte <= cnt_byte+1;
end
end
assign add_cnt_byte = ((state_c == WAIT_RD || state_c == WAIT_WR) && done);
assign end_cnt_byte = add_cnt_byte && cnt_byte == ((state_c == WAIT_WR) ? `WR_BYTE : `RD_BYTE) -1;
//输出
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
TX(1'b0,4'd0,8'd0);
end
else if(state_c==WR_REQ)begin
case(cnt_byte)
0 :TX(1'b1,{`CMD_START | `CMD_WRITE},{`I2C_ADR,wr_addr[8],`WR_BIT});//发起始位、写控制字
1 :TX(1'b1,`CMD_WRITE,wr_addr[7:0]); //发 写地址
`WR_BYTE-1 :TX(1'b1,{`CMD_WRITE | `CMD_STOP},wfifo_qout); //最后一个字节时 发数据、停止位
default :TX(1'b1,`CMD_WRITE,wfifo_qout); //中间发数据(如果有)
endcase
end
else if(state_c==RD_REQ)begin
case(cnt_byte)
0 :TX(1'b1,{`CMD_START | `CMD_WRITE},{`I2C_ADR,rd_addr[8],`WR_BIT});//发起始位、写控制字
1 :TX(1'b1,`CMD_WRITE,rd_addr[7:0]); //发 读地址
2 :TX(1'b1,{`CMD_START | `CMD_WRITE},{`I2C_ADR,rd_addr[8],`RD_BIT});//发起始位、读控制字
`RD_BYTE-1 :TX(1'b1,{`CMD_READ | `CMD_STOP},0); //最后一个字节时 读数据、发停止位
default :TX(1'b1,`CMD_READ,0); //中间读数据(如果有)
endcase
end
else begin
TX(1'b0,tx_cmd,tx_data);
end
end
//用task发送请求、命令、数据(地址+数据)
task TX;
input req ;
input [3:0] command ;
input [7:0] data ;
begin
tx_req = req;
tx_cmd = command;
tx_data = data;
end
endtask
//wr_addr rd_addr
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
wr_addr <= 0;
end
else if(wait_wr2done)begin
wr_addr <= wr_addr + `WR_BYTE-2;
end
end
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
rd_addr <= 0;
end
else if(wait_rd2done)begin
rd_addr <= rd_addr + `RD_BYTE - 3;
end
end
//dout_r dout_vld_r
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
dout_r <= 0;
dout_vld_r <= 0;
end
else begin
dout_r <= rfifo_qout;
dout_vld_r <= rfifo_rd;
end
end
//输出
assign req = tx_req ;
assign cmd = tx_cmd ;
assign wr_data = tx_data;
assign dout = dout_r;
assign dout_vld = dout_vld_r;
//fifo例化
wrfifo u_wrfifo (
.aclr (~rst_n ),
.clock (clk ),
.data (din ),
.rdreq (wfifo_rd ),
.wrreq (wfifo_wr ),
.empty (wfifo_empty),
.full (wfifo_full ),
.q (wfifo_qout ),
.usedw (wfifo_usedw)
);
assign wfifo_rd = state_c==WAIT_WR && done && cnt_byte > 1;//读使能(脉冲信号)---IIC接口传输完控制字节,字地址,cnt_byte==2时读出数据
assign wfifo_wr = ~wfifo_full & din_vld;//写使能(脉冲信号)---fifo非满且uart_rx模块数据有效
rdfifo u_rdfifo (
.aclr (~rst_n ),
.clock (clk ),
.data (rd_data ),
.rdreq (rfifo_rd ),
.wrreq (rfifo_wr ),
.empty (rfifo_empty),
.full (rfifo_full ),
.q (rfifo_qout ),
.usedw (rfifo_usedw)
);
assign rfifo_wr = ~rfifo_full && state_c == WAIT_RD && done && cnt_byte > 2;
assign rfifo_rd = ~rfifo_empty && ready;
endmodule
6.按键消抖
key_debounce.v
module key_debounce (
input wire clk, //系统时钟 50MHz
input wire rst_n, //复位信号
input wire key, //按键输入信号
output reg key_done //消抖之后的按键信号
);
reg key_r0; //同步信号(滤波作用,滤除小于一个周期的抖动)
reg key_r1; //打拍
reg flag; //标志位
wire nedge; //下降沿检测(检测到下降沿代表开始抖动)
//计时器定义
reg [19:0] cnt;
wire add_cnt; //计时器开启
wire end_cnt; //计时记满
parameter MAX_CNT=20'd1_000_000; //20ms延时
//同步
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
key_r0<=1'b1;
end
else
key_r0<=key;
end
//打拍
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
key_r1<=1'b1;
end
else
key_r1<=key_r0;
end
assign nedge = ~key_r0 & key_r1; //检测到下降沿拉高
//标志位
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
flag<=1'b0;
end
else if (nedge) begin
flag<=1'b1;
end
else if (end_cnt) begin
flag<=1'b0;
end
end
//延时模块
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt<=20'b0;
end
else if (add_cnt) begin
if (end_cnt) begin
cnt<=20'b0;
end
else
cnt<=cnt+1;
end
end
assign add_cnt=flag; //计时器开启
assign end_cnt=add_cnt&&cnt==MAX_CNT-1; //计时器关闭
//key_done输出
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
key_done<=1'b0;
end
else if (end_cnt) begin //延时满20ms采样
key_done<=~key_r0;
end
else
key_done<=1'b0;
end
endmodule
7.参数
//i2c时钟参数
`define SCL_PERIOD 250 //200khz
`define SCL_HALF 125
`define LOW_HLAF 65
`define HIGH_HALF 190
//i2c命令参数
`define CMD_START 4'b0001
`define CMD_WRITE 4'b0010
`define CMD_READ 4'b0100
`define CMD_STOP 4'b1000
//定义eeprom读写模式
`define BYTE_WRITE //字节写
//`define PAGE_WRITE //页写
`ifdef BYTE_WRITE
`define WR_BYTE 3
`elsif PAGE_WRITE
`define WR_BYTE 18
`endif
`define RANDOM_READ //随机地址读 每次写16字节
//`define SEQU_READ //顺序地址读 每次读16字节
`ifdef RANDOM_READ
`define RD_BYTE 4
`elsif SEQU_READ
`define RD_BYTE 19
`endif
//I2C外设地址参数定义
`define I2C_ADR 6'b1010_00 //6'b1010_00xy x:Block地址 y:读写控制位 WR_BIT/RD_BIT
`define WR_BIT 1'b0 //bit0
`define RD_BIT 1'b1 //bit0
`define STOP_BIT 1'b1
`define START_BIT 1'b0
六丶源码
链接:https://pan.baidu.com/s/12Ey9eZr1B6eKwrhubFFRDg?pwd=0pvo
提取码:0pvo
下载解压后进入doc目录可以查看芯片手册以及项目框图