中山网站制作系统互联网平台推广
FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法(1)
- 单字节写时序
- 单字节读时序
- I2C 控制器设计
- 模块框图
- scl_high 和 scl_low 产生的时序图
- 状态转移图
- Verilog代码
FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法(1)
FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法(2)
FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法(3)
单字节写时序
EEPROM器件或其他不同器件,I2C 器件地址字节不同,这样对于 I2C 单字节写时序就会有所差别,如下图分别为 1 字节地址段器件和 2 字节地址段器件单字节写时序图。
根据时序图,从主机角度来描述一次写入单字节数据过程如下:
主机设置 SDA 为输出;
主机发起起始信号;
主机传输器件地址字节,其中最低位为 0,表明为写操作;
主机设置 SDA 为三态门输入,读取从机应答信号;
读取应答信号成功,主机设置 SDA 为输出,传输 1 字节地址数据;
主机设置 SDA 为三态门输入,读取从机应答信号;
读取应答信号成功,对于两字节地址段器件,传输地址数据低字节,对于 1字节地址段器件,主机设置 SDA 为输出,传输待写入的数据;
设置 SDA 为三态门输入,读取从机应答信号,对于两字节地址段器件,接着步骤 i;对于 1 字节地址段器件,直接跳转到步骤 k;
读取应答信号成功,主机设置 SDA 为输出,传输待写入的数据(对于两字节地址段器件);
设置 SDA 为三态门输入,读取从机应答信号(两字节地址段器件);
读取应答信号成功,主机产生 STOP 位,终止传输。
单字节读时序
根据时序图,从主机角度描述一次读数据过程,如下:
主机设置 SDA 为输出;
主机发起起始信号;
主机传输器件地址字节,其中最低位为 0,表明为写操作;
主机设置 SDA 为三态门输入,读取从机应答信号;
读取应答信号成功,主机设置 SDA 输出,传输 1 字节地址数据;
主机设置 SDA 为三态门输入,读取从机应答信号;
读取应答信号成功,主机设置 SDA 输出,对于两字节地址段器件,传输低字节地址数据;对于 1 字节地址段器件,无此步骤,直接跳转到步骤 h;
主机发起起始信号;
主机传输器件地址字节,其中最低位为 1,表明为读操作;
设置 SDA 为三态门输入,读取从机应答信号;
读取应答信号成功,主机设置 SDA 为三态门输入,读取 SDA 总线上的一个字节的数据;
产生无应答信号(高电平)(无需设置为输出高电平,因为总线会被自动拉高);
主机产生 STOP 位,终止传输。
I2C 控制器设计
模块框图
状态机里面线性序列机思路
scl_high 和 scl_low 产生的时序图
通过上述的讲述,对 I2C 读写器件数据时序有了一定的了解,下面将开始进行控制程序的设计。根据上面 I2C 的基本概念中有关读写时SDA 与 SCL 时序,不管对于从机还是主机,SDA 上的每一位数据在 SCL 的高电平期间保持不变,而数据的改变总是在 SCL 的低电平期间发生。因此,我们可以选用 2 个标志位对时钟 SCL 的高电平和低电平进行标记,如下图所示:scl_high 对 SCL 高电平期间进行标志,scl_low 对 SCL 低电平期间进行标志。这样就可以在 scl_high 有效时读 SDA 数据,在 scl_low 有效时改变数据。
如下图所示
在状态机中,从主机角度来看,SDA 数据线上在写控制、写数据、读控制状态过程是需要串行输出数据,而在读数据状态过程是需要串行输入数据。根据数据在时钟高电平期间保持不变,改变数据在低电平时期的规则,本设计对时钟信号的高低电平进行计数,从而在指定的计数值进行输出或读取数据实现数据的串行输出和串行输入。串行输出和串行输入数据采用任务的形式进行表示,便于在主状态机中多次的调用。图下为计数的过程以及特定状态变化的时序图,这里的特定状态主要是指读/写控制、读/写地址和读/写数据状态。
状态转移图
定义
独热码
为了节省资源,可以通过上一个状态一定会执行下一个状态进而连贯在一起弄一个组合拳法。
创建任务的思想
对于计数器 halfbit_cnt 只在写控制、写数据、读控制、读数据状态下才进行计数,其他状态为零。代码中 FF 是进行串行输出或输入任务的标志位,当 FF 为1 时表示退出任务,FF 为 0 时表示进入任务。这样便于在状态机中对任务的调用,以及在指定的时间退出任务。
状态机里面线性序列机思路
Verilog代码
module I2C(Clk,Rst_n,Rddata_num,Wrdata_num,Wdaddr_num,Device_addr,Word_addr,Wr,Wr_data,Wr_data_vaild,Rd,Rd_data,Rd_data_vaild,Scl,Sda,Done
);//系统时钟采用50MHz
parameter SYS_CLOCK = 50_000_000;
//SCL总线时钟采用400kHz
parameter SCL_CLOCK = 400_000;
//产生时钟SCL计数器最大值
localparam SCL_CNT_M = SYS_CLOCK/SCL_CLOCK;input Clk; //系统时钟input Rst_n; //系统复位信号input [5:0] Rddata_num; //I2C总线连续读取数据字节数input [5:0] Wrdata_num; //I2C总线连续读取数据字节数input [1:0] Wdaddr_num; //I2C器件数据地址字节数input [2:0] Device_addr; //I2C器件地址input [15:0] Word_addr; //I2C寄存器地址input Wr; //I2C器件写使能input [7:0] Wr_data; //I2C器件写数据output Wr_data_vaild;//I2C器件写数据有效标志位input Rd; //I2C器件读使能output reg[7:0] Rd_data; //I2C器件读数据output reg Rd_data_vaild;//I2C器件读数据有效标志位output reg Scl; //I2C时钟线inout Sda; //I2C数据线output reg Done; //对I2C器件读写完成标识位//主状态机状态localparam IDLE = 9'b0_0000_0001,//空闲状态WR_START = 9'b0_0000_0010,//写开始状态WR_CTRL = 9'b0_0000_0100,//写控制状态WR_WADDR = 9'b0_0000_1000,//写地址状态WR_DATA = 9'b0_0001_0000,//写数据状态RD_START = 9'b0_0010_0000,//读开始状态RD_CTRL = 9'b0_0100_0000,//读控制状态RD_DATA = 9'b0_1000_0000,//读数据状态STOP = 9'b1_0000_0000;//停止状态reg [8:0] main_state; //主状态机状态寄存器reg sda_en; //sda数据总线控制位reg sda_reg; //sda数据输出寄存器reg W_flag; //IIC写操作标志位reg R_flag; //IIC读操作标志位reg FF; //串行输出输入任务执行标志位wire[7:0] wr_ctrl_word; //写控制数据寄存器wire[7:0] rd_ctrl_word; //读控制数据寄存器reg [15:0]scl_cnt; //SCL时钟计数器reg scl_vaild; //IIC非空闲时期reg scl_high; //SCL时钟高电平中部标志位reg scl_low; //SCL时钟低电平中部标志位 reg [7:0] halfbit_cnt; //串行数据传输计数器reg ack; //串行输出输入高低电平计数完成标志位reg [1:0] waddr_cnt; //地址字节数计数器reg [7:0] wdata_cnt; //写数据字节数计数器reg [7:0] rdata_cnt; //读数据字节数计数器reg [7:0] sda_data_out; //待输出SDA串行数据reg [7:0] sda_data_in; //SDA串行输入后数据wire rdata_vaild_r; //读写控制字assign wr_ctrl_word = {4'b1010, Device_addr,1'b0};assign rd_ctrl_word = {4'b1010, Device_addr,1'b1};//I2C数据线采用三态门传输assign Sda = sda_en ? sda_reg : 1'bz;//I2C非空闲时期scl_vaild的产生always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)scl_vaild <= 1'b0;else if(Wr | Rd)scl_vaild <= 1'b1;else if(Done)scl_vaild <= 1'b0;elsescl_vaild <= scl_vaild;end//scl时钟计数器always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)scl_cnt <= 16'd0;else if(scl_vaild)beginif(scl_cnt == SCL_CNT_M - 1)scl_cnt <= 16'd0;elsescl_cnt <= scl_cnt + 16'd1;endelsescl_cnt <= 16'd0;end//scl时钟,在计数器值到达最大值一半和0时翻转always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)Scl <= 1'b1;else if(scl_cnt == SCL_CNT_M >>1)Scl <= 1'b0;else if(scl_cnt == 16'd0)Scl <= 1'b1;elseScl <= Scl;end //scl时钟高低电平中部标志位always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)scl_high <= 1'b0;else if(scl_cnt == (SCL_CNT_M>>2))scl_high <= 1'b1;elsescl_high <= 1'b0;end//scl时钟低电平中部标志位//(SCL_CNT_M>>2)和(SCL_CNT_M>>1)+(SCL_CNT_M>>2)分别为 1/4 的 SCL_CNT_M 和 3/4 的SCL_CNT_M 的计数值。always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)scl_low <= 1'b0;else if(scl_cnt == (SCL_CNT_M>>1)+(SCL_CNT_M>>2))scl_low <= 1'b1;elsescl_low <= 1'b0;end //主状态机always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)beginmain_state <= IDLE;sda_reg <= 1'b1;//三态控制信号W_flag <= 1'b0;//写标注 R_flag <= 1'b0;//读标注 Done <= 1'b0;//完成waddr_cnt <= 2'd1;//地址 wdata_cnt <= 8'd1;//写数据rdata_cnt <= 8'd1;//读数据endelse begincase(main_state)IDLE:begin//空闲状态sda_reg <= 1'b1;W_flag <= 1'b0;R_flag <= 1'b0;Done <= 1'b0;waddr_cnt <= 2'd1;wdata_cnt <= 8'd1;rdata_cnt <= 8'd1;if(Wr)beginmain_state <= WR_START;W_flag <= 1'b1;endelse if(Rd)beginmain_state <= WR_START;R_flag <= 1'b1;endelsemain_state <= IDLE;endWR_START:begin//写开始状态if(scl_low)begin//在scl_low寄存器下个周期发送起始信号main_state <= WR_CTRL;sda_data_out <= wr_ctrl_word;FF <= 1'b0;endelse if(scl_high)beginsda_reg <= 1'b0;main_state <= WR_START;endelsemain_state <= WR_START;endWR_CTRL:begin//写控制状态if(FF == 1'b0)send_8bit_data;else beginif(ack == 1'b1) begin//收到响应if(scl_low)beginmain_state <= WR_WADDR;FF <= 1'b0;if(Wdaddr_num == 2'b1)sda_data_out <= Word_addr[7:0];elsesda_data_out <= Word_addr[15:8];endelsemain_state <= WR_CTRL;endelse//未收到响应main_state <= IDLE;endendWR_WADDR:begin//写地址状态if(FF == 1'b0)send_8bit_data;else beginif(ack == 1'b1) begin//收到响应if(waddr_cnt == Wdaddr_num)beginif(W_flag && scl_low)beginmain_state <= WR_DATA;sda_data_out <= Wr_data;waddr_cnt <= 2'd1;FF <= 1'b0;endelse if(R_flag && scl_low)beginmain_state <= RD_START;sda_reg <= 1'b1;endelsemain_state <= WR_WADDR;endelse beginif(scl_low)beginwaddr_cnt <= waddr_cnt + 2'd1;main_state <= WR_WADDR;sda_data_out <= Word_addr[7:0];FF <= 1'b0;endelsemain_state <= WR_WADDR;endendelse//未收到响应main_state <= IDLE;endendWR_DATA:begin//写数据状态if(FF == 1'b0)send_8bit_data;else beginif(ack == 1'b1) begin//收到响应if(wdata_cnt == Wrdata_num)beginif(scl_low)beginmain_state <= STOP;sda_reg <= 1'b0;wdata_cnt <= 8'd1;endelsemain_state <= WR_DATA;endelse beginif(scl_low)beginwdata_cnt <= wdata_cnt + 8'd1;main_state <= WR_DATA;sda_data_out <= Wr_data;FF <= 1'b0;endelsemain_state <= WR_DATA;endendelse//未收到响应main_state <= IDLE;endendRD_START:begin//读开始状态if(scl_low)beginmain_state <= RD_CTRL;sda_data_out <= rd_ctrl_word;FF <= 1'b0;endelse if(scl_high)beginmain_state <= RD_START;sda_reg <= 1'b0;endelsemain_state <= RD_START;endRD_CTRL:begin//读控制状态if(FF == 1'b0)send_8bit_data;else beginif(ack == 1'b1) begin//收到响应if(scl_low)beginmain_state <= RD_DATA;FF <= 1'b0;endelsemain_state <= RD_CTRL;endelse//未收到响应main_state <= IDLE;endendRD_DATA:begin//读数据状态if(FF == 1'b0)receive_8bit_data;else beginif(rdata_cnt == Rddata_num)beginsda_reg <= 1'b1;if(scl_low)beginmain_state <= STOP;sda_reg <= 1'b0;endelsemain_state <= RD_DATA;endelse beginsda_reg <= 1'b0;if(scl_low)beginrdata_cnt <= rdata_cnt + 8'd1;main_state <= RD_DATA;FF <= 1'b0;endelsemain_state <= RD_DATA;endendendSTOP:begin//结束操作if(scl_high)beginsda_reg <= 1'b1;main_state <= IDLE;Done <= 1'b1;endelsemain_state <= STOP;enddefault:main_state <= IDLE;endcaseendend//sda串行接收与发送时scl高低电平计数器根据中计数器 halfbit_cnt 和数据接收方对发送的响应检测标志位 ack 以及串行输出、输入数据任务always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)halfbit_cnt <= 8'd0;else if((main_state == WR_CTRL)||(main_state == WR_WADDR)||(main_state == WR_DATA)||(main_state == RD_CTRL)||(main_state == RD_DATA))beginif(scl_low | scl_high)beginif(halfbit_cnt == 8'd17)halfbit_cnt <= 8'd0;elsehalfbit_cnt <= halfbit_cnt + 8'd1;endelsehalfbit_cnt <= halfbit_cnt;endelsehalfbit_cnt <= 8'd0;end//数据接收方对发送的响应检测标志位always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)ack <= 1'b0;else if((halfbit_cnt == 8'd16)&&scl_high&&(Sda==1'b0))ack <= 1'b1;else if((halfbit_cnt == 8'd17)&&scl_low)ack <= 1'b0;elseack <= ack;end//输出串行数据任务task send_8bit_data;if(scl_high && (halfbit_cnt == 8'd16))FF <= 1;else if(halfbit_cnt < 8'd17)beginsda_reg <= sda_data_out[7];if(scl_low)sda_data_out <= {sda_data_out[6:0],1'b0};elsesda_data_out <= sda_data_out;endelse;endtask//串行数据输入任务task receive_8bit_data;if(scl_low && (halfbit_cnt == 8'd15))FF <= 1;else if((halfbit_cnt < 8'd15))beginif(scl_high)sda_data_in <= {sda_data_in[6:0],Sda};else beginsda_data_in <= sda_data_in;endendelse;endtask//sda三态使能信号sda_enalways@(*)begincase(main_state)IDLE:sda_en = 1'b0;WR_START,RD_START,STOP:sda_en = 1'b1;WR_CTRL,WR_WADDR,WR_DATA,RD_CTRL:if(halfbit_cnt < 16)sda_en = 1'b1;elsesda_en = 1'b0;RD_DATA:if(halfbit_cnt < 16)sda_en = 1'b0;elsesda_en = 1'b1; default:sda_en = 1'b0; endcaseend//写数据有效标志位assign Wr_data_vaild = ((main_state==WR_WADDR)&&(waddr_cnt==Wdaddr_num)&&(W_flag && scl_low)&&(ack == 1'b1))||((main_state == WR_DATA)&&(ack == 1'b1)&&(scl_low)&&(wdata_cnt != Wrdata_num));//读数据有效标志位前寄存器assign rdata_vaild_r = (main_state == RD_DATA)&&(halfbit_cnt == 8'd15)&&scl_low;//读出数据有效标志位always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)Rd_data_vaild <= 1'b0;else if(rdata_vaild_r)Rd_data_vaild <= 1'b1;elseRd_data_vaild <= 1'b0;end//读出的有效数据always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)Rd_data <= 8'd0;else if(rdata_vaild_r)Rd_data <= sda_data_in;elseRd_data <= Rd_data;endendmodule