9. 基于I2C协议的EEPROM驱动控制

您所在的位置:网站首页 spi接口的eeprom 9. 基于I2C协议的EEPROM驱动控制

9. 基于I2C协议的EEPROM驱动控制

2024-04-19 15:12| 来源: 网络整理| 查看: 265

9.3.3. 程序设计¶ 9.3.3.1. 整体说明¶

由“实验目标”小节,我们知道们实验工程是要设计一个使用I2C通讯协议的EEPROM读写控制器,使用按键控制数据的写入或读出,并将读出数据显示在数码管上。结合实验目标,运用前面学到的设计方法和相关理论知识,我们开始实验工程的设计。

首先,实验目标要求要使用I2C通讯协议,那么工程中要包含一个I2C驱动控制模块;其次,使用按键控制数据读/写,并要求将读出数据显示到数码管上,我们可以直接调用前面章节的按键消抖模块和数码管动态显示模块;再次,我们需要设计一个数据收发模块控制数据的收发;最后,需要顶层模块将各子功能模块例化起来,连接个 功能模块对应信号。综上所述,实验工程整体框图,具体见图 39‑16;模块功能简介,具体见表格 39‑1。

图 39‑16 EEPROM字节读写整体框图

表格 39‑1 模块功能简介

模块名称

功能描述

key_filter

按键消抖模块,将物理按键传入的读/写触发信号作消抖处理

i2c_rw_data

数据收发模块,生成eeprom待写入数据,暂存eeprom读出数据

i2c_ctrl

I2C驱动模块,按照I2C协议对I2C设备进行数据读写操作

seg_595_dynamic

数码管动态显示模块,显示读出eeprom的数据

eeprom_byte_rd_wr

顶层模块,实例化各子功能模块,连接各模块对应信号

结合图表,简述一下本实验工程的具体流程。

按下数据写操作按键,写触发信号传入按键消抖模块(key_filter),经消抖处理后的写触发信号传入数据收发模块(i2c_rw_data),模块接收到有效的写触发信号后,生成写使能信号、待写入数据、数据地址传入I2C驱动模块(i2c_ctrl),I2C驱动模块按照I2C协议将数据写入EEPROM存储 芯片;

数据写入完成后,按下数据读操作按键,读触发信号传入按键消抖模块(key_filter),经消抖处理后的读触发信号传入数据收发模块(i2c_rw_data),模块接收到有效的读触发信号后,生成读使能信号、数据地址传入I2C驱动模块(i2c_ctrl),I2C驱动模块自EEPROM存储芯片读取数据,将读 取到的数据回传给数据收发模块(i2c_rw_data),数据收发模块将数据暂存,待所有数据均读取完成后,将数据传至数码管动态显示模块(seg_595_dynamic),自EEPROM中读取的数据在数码管显示出来。

经过本小节的讲解,相信读者对本实验工程的整体框架有了简单了解,接下来我们对实验工程的各子功能模块分别进行详细讲解,帮助更加深入理解实验工程。

9.3.3.2. I2C驱动模块¶

模块框图

I2C驱动模块的主要功能是按照I2C协议对EERPROM存储芯片执行数据读写操作。I2C驱动模块框图和输入输出端口简介,具体见图 39‑17、表格 39‑2。

图 39‑17 I2C驱动模块框图

表格 39‑2 I2C驱动模块输入输出信号简介

信号

位宽

类型

功能描述

sys_clk

1Bit

Input

系统时钟50MHz

sys_rst_n

1Bit

Input

复位信号,低有效

wr_en

1Bit

Input

写使能信号

rd_en

1Bit

Input

读使能信号

i2c_start

1Bit

Input

单字节数据读/写开始信号

addr_num

1Bit

Input

数据存储地址字节数标志信号

byte_addr

16Bit

Input

数据存储地址

wr_data

8Bit

Input

待写入EEPROM字节数据

i2c_clk

1Bit

Output

工作时钟

i2c_end

1Bit

Output

单字节数据读/写结束信号

rd_data

8Bit

Output

自EEPROM中读出的单字节数据

i2c_scl

1Bit

Output

I2C串行时钟信号SCL

i2c_sda

1Bit

Output

I2C串行数据信号SDA

由图表可知,I2C驱动模块包括13路输入输出信号,其中输入信号8路、输出信号5路。输入信号中,sys_clk、sys_rst_n是必不可少的系统时钟和复位信号;wr_en、rd_en为写使能信号,由数据收发模块生成并传入,高电平有效;i2c_start信号为单字节数据读/写开始信号;与i2c_sta rt信号同时传入的还有数据存储地址byte_addr和待写入字节数据wr_data;当写使能wr_en和i2c_start信号同时有效,模块执行单字节数据写操作,按照数据存储地址byte_addr,向EEPROM对应地址写入数据wr_data;当读使能信号rd_en和i2c_start信号同时有效, 模块执行单字节数据读操作,按照数据存储地址byte_addr读取EEPROM对应地址中的数据;前文中我们提到, I2C设备存储地址有单字节和2字节两种,为了应对这一情况,我们向模块输入addr_num信号,当信号为低电平时,表示I2C设备存储地址为单字节,在进行数据读写操作时只写入数据存储地址byt e_addr的低8位;当信号为高电平时,表示I2C设备存储地址为2字节,在进行数据读写操作时要写入数据存储地址byte_addr的全部16位。

输出信号中,i2c_clk是本模块的工作时钟,由系统时钟sys_clk分频而来,它的时钟频率为串行时钟i2c_scl频率的4倍,时钟信号i2c_clk要传入数据收发模块(i2c_rw_data)作为模块的工作时钟;输出给数据收发模块(i2c_rw_data)的单字节数据读/写结束信号i2c_end, 高电平有效,表示一次单字节数据读/写操作完成;rd_data信号表示自EEPROM读出的单字节单字节数据,输出至数据收发模块(i2c_rw_data);i2c_scl、i2c_sda分别是串行时钟信号和串行数据信号,由模块产生传入EEPROM存储芯片。

注:对EERPROM的数据读写操作均使用单字节读/写操作,即每次操作只读/写单字节数据;若想要实现数据的连续读/写,可持续拉高读/写使能rd_en/wr_en,并输入有效的单字节数据读/写开始信号i2c_start即可。

波形图绘制

在“模块框图”小节,我们结合图表对I2C驱动模块的具体功能和输入输出端口做了说明。那么如何利用输入信号实现模块功能,并输出正确信号呢?在本小节,我们会通过绘制模块波形图,对模块功能以及各信号波形的设计与实现作出详细讲解。

在绘制波形图之前我们回想一下前面讲到的I2C设备单字节写操作和随机读操作的操作流程,结合前面学到的知识,我们发现使用状态机来实现I2C设备的读/写操作是十分方便的。参照I2C设备单字节写操作和随机读操作的操作流程,我们绘制I2C读/写操作状态转移图如下。

图 39‑18 I2C读/写操作状态转移图

有图可知,状态机中共包含16个状态,将单字节写操作和随机读操作相结合,可以实现I2C设备单字节写操作和随机读操作的状态跳转。

系统上电后,状态机处于IDLE(初始状态),接收到有效的单字节数据读/写开始信号i2c_start后,状态机跳转到START_1(起始状态);FPGA向EEPROM存储芯片发送起始信号;随后状态机跳转到SEND_D_ADDR(发送器件地址状态),在此状态下向EEPROM存储芯片写入控制指令,控制指令 高7位为器件地址,最低位为读写控制字,写入“0”,表示执行写操作;控制指令写入完毕后,状态机跳转到ACK_1(应答状态)。

在ACK_1(应答状态)状态下,要根据存储地址字节数进行不同状态的跳转。当FPGA接收到EEPROM回传的应答信号且存储地址字节为2字节,状态机跳转到SEND_B_ADDR_H(发送高字节地址状态),将存储地址的高8位写入EEPROM,写入完成后,状态机跳转到ACK_2(应答状态);FPGA接收到应 答信号后,状态机跳转到SEND_B_ADDR_L(发送低字节地址状态);当FPGA接收到EEPROM回传的应答信号且存储地址字节为单字节,状态机状态机直接跳转到SEND_B_ADDR_L(发送低字节地址状态);在此状态低8位存储地址或单字节存储地址写入完成后,状态机跳转到ACK_3(应答状态)。

在ACK_3(应答状态)状态下,要根据读/写使能信号做不同的状态跳转。当FPGA接收到应答信号且写使能信号有效,状态机跳转到WR_DATA(写数据状态);在写数据状态,向EEPROM写入单字节数据后,状态机跳转到ACK_4(应答状态);待FPGA接收到有效应答信号后,状态机跳转到STOP(停止状态) ;当FPGA接收到应答信号且读使能信号有效,状态机跳转到START_2(起始状态);再次向EEPROM写入起始信号,状态跳转到SEND_RD_ADDR(发送读控制状态);再次向EEPROM写入控制字节,高7位器件地址不变,读写控制位写入“1”,表示进行读操作,控制字节写入完毕后,状态机跳转到ACK_ 5(应答状态);待FPGA接收到有效应答信号后,状态机跳转到RD_DATA(读数据状态);在RD_DATA(读数据状态)状态,EEPROM向FPGA发送存储地址对应存储单元下的单字节数据,待数据读取完成户,状态机跳转到N_ACK(无应答状态),在此状态下向EEPROM写入一个时钟的高电平,表示数据读 取完成,随后状态机跳转到STOP(停止状态)。

在STOP(停止状态)状态,FPGA向EEPROM发送停止信号,一次单字节数据读/写操作完成,随后状态机跳回IDLE(初始状态),等待下一次单字节数据读/写开始信号i2c_start。

使用状态机实现I2C驱动模块功能是模块的大体思路,结合前面讲解的I2C通讯协议的相关知识和相关设计方法,我们开始模块波形图的绘制。I2C驱动模块整体波形图,具体见图 39‑19、图 39‑20、图 39‑21、图 39‑22、图 39‑23。

图 39‑19 单字节写操作局部波形图(一)

图 39‑20 单字节写操作局部波形图(二)

图 39‑21 随机读操作局部波形图(一)

图 39‑22 随机读操作局部波形图(二)

图 39‑23 随机读操作局部波形图(三)

由于篇幅原因,我们将波形图分开展示,为了便于理解,我们将单字节写操作和随机读操作读操作分开讲解,对于各信号波形的设计与实现进行详细说明。

首先,先来看一下单字节写操作。

第一部分:输入信号说明

本模块的输入信号有8路,其中7路信号与单字节写操作有关。系统时钟信号sys_clk和复位信号sys_rst_n不必多说,这是模块正常工作必不可少的;写使能信号wr_en、 单字节数据读/写开始信号i2c_start,只有在两信号同时有效时,模块才会执行单字节数据写操作,若wr_en有效时,i2c_s tart信号n次有效输入,可以实现n个字节的连续写操作;addr_num信号为存储地址字节数标志信号,赋值为0时,表示I2C设备存储地址为单字节,赋值为1时,表示2C设备存储地址为2字节,本实验使用的EEPROM存储芯片的存储地址位2字节,此信号恒为高电平;信号byte_addr为存储地址;wr_d ata表示要写入该地址的单字节数据。

第二部分:时钟信号计数器cnt_clk和输出信号i2c_clk的设计与实现

本实验对EEPROM读写操作的串行时钟scl的频率为250KHz,且只在数据读写操作时时钟信号才有效,其他时刻scl始终保持高电平。若直接使用系统时钟生成串行时钟scl,计数器要设置较大的位宽,较为麻烦,我们这里先将系统时钟分频为频率较小的时钟,在使用新分频的时钟来生成串行时钟scl。

所以,在这里声明一个新的计数器cnt_clk对系统时钟sys_clk进行计数,利用计数器cnt_clk生成新的时钟i2c_clk。

串行时钟scl的时钟频率为250KHz,我们要生成的新时钟i2c_clk的频率要是scl的4倍,之所以这样是为了后面更好的生成scl和sda,所以i2c_clk的时钟频率为1MHz。经计算,cnt_clk要在0-24内循环计数,每个系统时钟周期自加1;cnt_clk每计完一个周期,i2c_clk进行 一次取反,最后得到i2c_clk为频率1MHz的时钟,本模块中其他信号的生成都以此信号为同步时钟。两信号波形图如下。

图 39‑24 cnt_clk、i2c_clk 信号波形图

注:由于系统时钟sys_clk与时钟i2c_clk时钟频率相差较大,sys_clk信号用虚线表示。

第三部分:状态机相关信号波形的设计与实现

前文理论部分提到,输出至EEPROM的串行时钟scl与串行数据sda只有在进行数据读写操作时有效,其他时刻始终保持高电平。由前文状态机相关讲解可知,除IDLE(初始状态)状态之外的其他状态均属于数据读写操作的有效部分,所以声明一个使能信号cnt_i2c_clk_en,在除IDLE(初始状态)状态之外 的其他状态保持有效高电平,作为I2C数据读写操作使能信号。

我们使用50MHz系统时钟生成了1MHz时钟i2c_clk,但输出至EEPROM的串行时钟scl的时钟频率为250KHz,我们声明时钟信号计数器cnt_i2c_clk,作为分频计数器,对时钟i2c_clk时钟信号进行计数,初值为0,计数范围为0-3,计数时钟为i2c_clk时钟,每个时钟周期自加1, 实现时钟i2c_clk信号的4分频,生成串行时钟scl。同时计数器cnt_i2c_clk也可作为生成串行数据sda的约束条件,以及状态机跳转条件。

计数器cnt_i2c_clk循环计数一个周期,对应串行时钟scl的1个时钟周期以及串行数据sda的1位数据保持时间,进行数据读写操作时,传输的指令、地址以及数据,位宽为固定的8位数据,我们声明一个比特计数器cnt_bit,对计数器cnt_i2c_clk的计数周期进行计数,可以辅助串行数据sda的生成 ,同时作为状态机状态跳转的约束条件。

输出的串行数据sda作为一个双向端口,主机通过它向从机发送控制指令、地址以及数据,接收从机回传的应答信号和读取数据。回传给主机的应答信号是实现状态机跳转的条件之一。声明信号sda_in作为串行数据sda 缓存,声明ack信号作为应答信号,ack信号只在状态机处于各应答状态时由sda_in信号赋值,此时为从机回传的应答信号,其他状态时钟保持高电平。

状态机状态跳转的各约束条件均已介绍完毕,声明状态变量state,结合各约束信号,单字节写操作状态机跳转流程如下:

系统上电后,状态机处于IDLE(初始状态),接收到有效的单字节数据读/写开始信号i2c_start后,状态机跳转到START_1(起始状态),同时使能信号cnt_i2c_clk_en拉高、计数器cnt_i2c_clk、cnt_bit开始计数,开始数据读写操作;

在START_1(起始状态)状态保持一个串行时钟周期,期间FPGA向EEPROM存储芯片发送起始信号,一个时钟周期过后,计数器cnt_ i2c_clk完成一个周期计数,计数器cnt_ i2c_clk计数到最大值3,状态机跳转到SEND_D_ADDR(发送器件地址状态);

计数器cnt_i2c_clk、cnt_bit同时归0,重新计数,计数器cnt_i2c_clk每计完一个周期,cnt_bit自加1,当计数器cnt_i2c_clk完成8个计数周期后,cnt_bit计数到7,实现8个比特计数,器件FPGA按照时序向EEPROM存储芯片写入控制指令,控制指令高7位为器件地 址,最低位为读写控制字,写入“0”,表示执行写操作。当计数器cnt_ i2c_clk计数到最大值3、cnt_bit计数到7,两计数器同时归0,状态机跳转到转到ACK_1(应答状态);

在ACK_1(应答状态)状态下,计数器cnt_i2c_clk、cnt_bit重新计数,当计数器cnt_ i2c_clk计数到最大值3,且应答信号ack为有效的低电平,状态机跳转到SEND_B_ADDR_H(发送高字节地址状态),两计数器清0;

此状态下,FPGA将存储地址的高8位按时序写入EEPROM,当计数器cnt_ i2c_clk计数到3、cnt_bit计数到7,状态机跳转到ACK_2(应答状态), 两计数器清0;

ACK_2状态下,当计数器cnt_ i2c_clk计数到3,且应答信号ack为有效的低电平,状态机跳转到SEND_B_ADDR_L(发送低字节地址状态) ,两计数器清0;

在此状态下,低8位存储地址按时序写入EEPROM,计数器cnt_ i2c_clk计数到3、cnt_bit计数到7,状态机跳转到ACK_3(应答状态);

在ACK_3(应答状态)状态下,当cnt_ i2c_clk计数3、应答信号ack有效,且写使能信号wr_en有效,状态机跳转到WR_DATA(写数据状态);

在写数据状态,按时序向EEPROM写入单字节数据,计数器cnt_ i2c_clk计数到3、cnt_bit计数到7,状态机跳转到ACK_4(应答状态);

在ACK_4(应答状态)状态下,当cnt_ i2c_clk计数3、应答信号ack有效,状态机跳转到STOP(停止状态)状态;

在STOP(停止状态)状态,FPGA向EEPROM发送停止信号,一次单字节数据读/写操作完成,随后状态机跳回IDLE(初始状态),等待下一次单字节数据读/写开始信号i2c_start。

状态机相关信号波形如下。

图 39‑25 状态机相关信号波形图(一)

图 39‑26 状态机相关信号波形图(二)

第四部分:输出串行时钟i2c_scl、串行数据信号i2c_sda及相关信号的波形设计与实现

串口数据sda端口作为一个双向端口,在单字节读取操作中,主机只在除应答状态之外的其他状态拥有它的控制权,在应答状态下主机只能接收由从机通过sda传入的应答信号。声明使能信号sda_en,只在除应答状态之外的其他状态赋值为有效的高电平,sda_en有效时,主机拥有对sda的控制权。

声明i2c_sda_reg作为输出i2c_sda信号的数据缓存,在sda_en有效时,将i2c_sda_reg的值赋值给输出串口数据i2c_sda,sda_en无效时,输出串口数据i2c_sda为高阻态,主机放弃其控制权,接收其传入的应答信号。

i2c_sda_reg在使能信号sda_en无效时始终保持高电平,在使能sda_en有效时,在状态机对应状态下,以计数器cnt_ i2c_clk、cnt_bit为约束条件,对应写入起始信号、控制指令、存储地址、写入数据、停止信号。

对于输出的串行时钟i2c_clk,由I2C通讯协议可知,I2C设备只在串行时钟为高电平时进行数据采集,在串行时钟低电平时实现串行数据更新。我们使用计数器cnt_ i2c_clk、cnt_bit以及状态变量state为约束条件,结合I2C通讯协议,生成满足时序要求的输出串行时钟i2c_clk。

输出串行时钟i2c_scl、串行数据信号i2c_sda及相关信号的波形图如下。

图 39‑27 i2c_scl、i2c_sda及相关信号波形图(一)

图 39‑28 i2c_scl、i2c_sda及相关信号波形图(二)

单字节写操作部分涉及的各信号波形的设计与实现讲解完毕,下面开始随机读操作部分的讲解。单字节写操作和随机读操作所涉及的各信号大体相同,在随机读操作,我们只讲解差别较大之处,两操作相同或相似之处不再说明,读者可回顾单字节写操作部分的介绍。

第一部分:输入信号说明

本模块的输入信号有8路,其中6路信号与随机读操作有关。系统时钟信号sys_clk和复位信号sys_rst_n不必多说,这是模块正常工作必不可少的;读使能信号rd_en、 单字节数据读/写开始信号i2c_start,只有在两信号同时有效时,模块才会执行随机读操作,若rd_en有效时,i2c_start 信号n次有效输入,可以实现n个字节的连续读操作;addr_num信号为存储地址字节数标志信号,赋值为0时,表示I2C设备存储地址为单字节,赋值为1时,表示2C设备存储地址为2字节,本实验使用的EEPROM存储芯片的存储地址位2字节,此信号恒为高电平;信号byte_addr为存储地址。

第二部分:状态机相关信号波形的设计与实现

状态机状态跳转的各约束条件,读者可回顾单字节写操作部分介绍。声明状态变量state,结合各约束信号,单字节写操作状态机跳转流程如下:

系统上电后,状态机处于IDLE(初始状态),接收到有效的单字节数据读/写开始信号i2c_start后,状态机跳转到START_1(起始状态),同时使能信号cnt_i2c_clk_en拉高、计数器cnt_i2c_clk、cnt_bit开始计数,开始数据读写操作;

在START_1(起始状态)状态保持一个串行时钟周期,期间FPGA向EEPROM存储芯片发送起始信号,一个时钟周期过后,计数器cnt_ i2c_clk完成一个周期计数,计数器cnt_ i2c_clk计数到最大值3,状态机跳转到SEND_D_ADDR(发送器件地址状态);

计数器cnt_i2c_clk、cnt_bit同时归0,重新计数,计数器cnt_i2c_clk每计完一个周期,cnt_bit自加1,当计数器cnt_i2c_clk完成8个计数周期后,cnt_bit计数到7,实现8个比特计数,器件FPGA按照时序向EEPROM存储芯片写入控制指令,控制指令高7位为器件地 址,最低位为读写控制字,写入“0”,表示执行写操作。当计数器cnt_ i2c_clk计数到最大值3、cnt_bit计数到7,两计数器同时归0,状态机跳转到转到ACK_1(应答状态);

在ACK_1(应答状态)状态下,计数器cnt_i2c_clk、cnt_bit重新计数,当计数器cnt_ i2c_clk计数到最大值3,且应答信号ack为有效的低电平,状态机跳转到SEND_B_ADDR_H(发送高字节地址状态),两计数器清0;

此状态下,FPGA将存储地址的高8位按时序写入EEPROM,当计数器cnt_ i2c_clk计数到3、cnt_bit计数到7,状态机跳转到ACK_2(应答状态), 两计数器清0;

ACK_2状态下,当计数器cnt_ i2c_clk计数到3,且应答信号ack为有效的低电平,状态机跳转到SEND_B_ADDR_L(发送低字节地址状态) ,两计数器清0;

在此状态下,低8位存储地址按时序写入EEPROM,计数器cnt_ i2c_clk计数到3、cnt_bit计数到7,状态机跳转到ACK_3(应答状态);

在ACK_3(应答状态)状态下,当cnt_ i2c_clk计数3、应答信号ack有效,且读使能信号rd_en有效,状态机跳转到START_2(起始状态);

在START_2(起始状态)状态保持一个串行时钟周期,期间FPGA再次向EEPROM存储芯片发送起始信号,一个时钟周期过后,计数器cnt_ i2c_clk完成一个周期计数,计数器cnt_ i2c_clk计数到3,状态机跳转到SEND_RD_ADDR(发送读控制状态);

在此状态下,按时序向EEPROM写入控制指令,控制指令高7位为器件地址,最低位为读写控制字,写入“1”,表示执行读操作。当计数器cnt_ i2c_clk计数到3、cnt_bit计数到7,两计数器同时归0,状态机跳转到ACK_5(应答状态);

在ACK_5(应答状态)状态下,当cnt_ i2c_clk计数3、应答信号ack有效,状态机跳转到RD_DATA(读数据状态);读数据状态下,主机读取从机发送的单字节数据,当计数器cnt_ i2c_clk计数到3、cnt_bit计数到7,数据读取完成,计数器清0,状态机跳转到N_ACK(非应答状态);在非应答状态下,向EEPROM写入一个时钟的高电平,当cnt_ i2c_clk计数3,状态机跳转到STOP(停止状态)。

在STOP(停止状态)状态,FPGA向EEPROM发送停止信号,一次随机数据读操作完成,随后状态机跳回IDLE(初始状态),等待下一次单字节数据读/写开始信号i2c_start。

状态机相关信号波形如下。

图 39‑29 状态机相关信号波形图(一)

图 39‑30 状态机相关信号波形图(二)

图 39‑31 状态机相关信号波形图(三)

第三部分:输出串行时钟i2c_scl、串行数据信号i2c_sda、读出数据rd_data及相关信号的波形设计与实现

串口数据sda端口作为一个双向端口,在随机读操作中,主机只在除应答状态、读数据状态之外的其他状态拥有它的控制权,在应答状态下主机接收由从机通过sda传入的应答信号,在读数据状态下主机接收由从机传入的单字节数据。声明使能信号sda_en,只在除应答状态、读数据状态之外的其他状态赋值为有效的高电平,sd a_en有效时,主机拥有对sda的控制权。

声明i2c_sda_reg作为输出i2c_sda信号的数据缓存;声明rd_data_reg作为EEPROM读出数据缓存。

i2c_sda_reg在使能信号sda_en无效时始终保持高电平,在使能sda_en有效时,在状态机对应状态下,以计数器cnt_ i2c_clk、cnt_bit为约束条件,对应写入起始信号、控制指令、存储地址、写入数据、停止信号;在状态机处于读数据状态时,变量rd_data_reg由输入信号sda_in赋值,暂存EEPROM读取数据。

当sda_en有效时,将i2c_sda_reg赋值给i2c_sda;当sda_en无效时,i2c_sda保持高阻态。主机放弃对sda端口的控制;在状态机处于读数据状态时,变量rd_data_reg暂存EEPROM读取数据,读数据状态结束后,将暂存数据赋值给输出信号rd_data。

对于输出的串行时钟i2c_clk,由I2C通讯协议可知,I2C设备只在串行时钟为高电平时进行数据采集,在串行时钟低电平时实现串行数据更新。我们使用计数器cnt_ i2c_clk、cnt_bit以及状态变量state为约束条件,结合I2C通讯协议,生成满足时序要求的输出串行时钟i2c_clk。

输出串行时钟i2c_scl、串行数据信号i2c_sda、读出数据rd_data及相关信号的波形图如下。

图 39‑32 i2c_scl、i2c_sda、rd_data及相关信号波形图(一)

图 39‑33 i2c_scl、i2c_sda、rd_data及相关信号波形图(二)

图 39‑34 i2c_scl、i2c_sda、rd_data及相关信号波形图(三)

代码编写

参考波形图绘制完毕,参照参考波形图进行代码编写,I2C驱动模块参考代码,具体见代码清单 39‑1。

代码清单 39‑1 I2C驱动模块参考代码(i2c_ctrl.v)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338module i2c_ctrl #( parameter DEVICE_ADDR = 7'b1010_000 , //i2c设备地址 parameter SYS_CLK_FREQ = 26'd50_000_000 , //输入系统时钟频率 parameter SCL_FREQ = 18'd250_000 //i2c设备scl时钟频率 ) ( input wire sys_clk , //输入系统时钟,50MHz input wire sys_rst_n , //输入复位信号,低电平有效 input wire wr_en , //输入写使能信号 input wire rd_en , //输入读使能信号 input wire i2c_start , //输入i2c触发信号 input wire addr_num , //输入i2c字节地址字节数 input wire [15:0] byte_addr , //输入i2c字节地址 input wire [7:0] wr_data , //输入i2c设备数据 output reg i2c_clk , //i2c驱动时钟 output reg i2c_end , //i2c一次读/写操作完成 output reg [7:0] rd_data , //输出i2c设备读取数据 output reg i2c_scl , //输出至i2c设备的串行时钟信号scl inout wire i2c_sda //输出至i2c设备的串行数据信号sda ); //// //\* Parameter and Internal Signal \// //// // parameter define parameter CNT_CLK_MAX = (SYS_CLK_FREQ/SCL_FREQ) >> 2'd3 ; //cnt_clk计数器计数最大值 parameter CNT_START_MAX = 8'd100; //cnt_start计数器计数最大值 parameter IDLE = 4'd00, //初始状态 START_1 = 4'd01, //开始状态1 SEND_D_ADDR = 4'd02, //设备地址写入状态 + 控制写 ACK_1 = 4'd03, //应答状态1 SEND_B_ADDR_H = 4'd04, //字节地址高八位写入状态 ACK_2 = 4'd05, //应答状态2 SEND_B_ADDR_L = 4'd06, //字节地址低八位写入状态 ACK_3 = 4'd07, //应答状态3 WR_DATA = 4'd08, //写数据状态 ACK_4 = 4'd09, //应答状态4 START_2 = 4'd10, //开始状态2 SEND_RD_ADDR = 4'd11, //设备地址写入状态 + 控制读 ACK_5 = 4'd12, //应答状态5 RD_DATA = 4'd13, //读数据状态 N_ACK = 4'd14, //非应答状态 STOP = 4'd15; //结束状态 // wire define wire sda_in ; //sda输入数据寄存 wire sda_en ; //sda数据写入使能信号 // reg define reg [7:0] cnt_clk ; //系统时钟计数器,控制生成clk_i2c时钟信号 reg [3:0] state ; //状态机状态 reg cnt_i2c_clk_en ; //cnt_i2c_clk计数器使能信号 reg [1:0] cnt_i2c_clk ; //clk_i2c时钟计数器,控制生成cnt_bit信号 reg [2:0] cnt_bit ; //sda比特计数器 reg ack ; //应答信号 reg i2c_sda_reg ; //sda数据缓存 reg [7:0] rd_data_reg ; //自i2c设备读出数据 //// //\* Main Code \// //// // cnt_clk:系统时钟计数器,控制生成clk_i2c时钟信号 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_clk


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3