导读:本实验通过使用开源软件 opencores 上的 I2C master 控制器去控制 I2C 接口的 EEPROM 读写,练习如何有效的使用开源代码提升开发效率。同时,附带学习基于 Fabric Debugger 在线调试方法。
文章目录
- 一、实验原理
-
- 1.1、硬件电路
- 1.2、I2C的总线协议和时序
- 二、程序设计
-
- 2.1、I2C控制器源码获取
- 2.2、读写EEPROM程序设计
- 三、下载实验
- 四、使用在线调试 Fabric Debugger观察信号
- 参考
一、实验原理
1.1、硬件电路
PGL22G 开发板板载了一片 EEPROM,型号为 24LC04,容量为:4Kbit(2*256*8
bit),由 2 个 256byte 的 block 组成,通过 IIC 总线进行通信。板载 EEPROM 是为了学习 IIC 总线的通信方式,EEPROM 的 I2C 信号与FPGA 的 IO 口连接示意图如下所示:
从上图可以看到,FPGA 芯片通过 I2C 总线连接 EEPROM 24LC04,I2C 的两根总线各上拉一个 4.7K的电阻到 3.3V,所以当总线上没有输出时会被拉高。24LC04 模块外围电路如下图所示:
从上图可以看出,24LC04 的写保护引脚(WP)没有使能,不然 FPGA 会无法写入数据。另外,24LC04 的三根地址线A0~A2
在电路上都为低,所以 24LC04 的设备地址为 0xA0
。还有特别重要的的一点,24LC04的单页为16
个字节!
1.2、I2C的总线协议和时序
I2C 标准模式速率为 100kbit/s
,快速模式速率 400kbit/s
,支持多机通讯, 支持多主控模块,但同一时刻只允许有一个主控。由数据线 SDA 和时钟 SCL 构成串行总线,每个电路和模块都有唯一的地址。
I2C 设备的操作可分为写单个存储字节,写多个存储字节,读单个存储字节和读多个存储字节,各个操作如下图所示:
下面对 I2C 总线通信过程中出现的几种信号状态和时序进行分析。
- 1、总线空闲状态
I2C 总线总线的 SDA 和 SCL 两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
- 2、启动信号(Start)
在时钟线 SCL 保持高电平期间,数据线 SDA 上的电平被拉低(即负跳变),定义为 I2C 总线总线的启动信号,它标志着一次数据传输的开始。启动信号是由主控器主动建立的,在建立该信号之前 I2C 总线必须处于空闲状态,如下图所示。
- 3、停止信号(Stop)
在时钟线 SCL 保持高电平期间,数据线 SDA 被释放,使得 SDA 返回高电平(即正跳变),称为 I2C 总线的停止信号,它标志着一次数据传输的终止。停止信号也是由主控器主动建立的,建立该信号之后,I2C 总线将返回空闲状态。
- 4、数据位传送
在 I2C 总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在 SCL 串行时钟的配合下,在 SDA 上逐位地串行传送每一位数据。进行数据传送时,在 SCL 呈现高电平期间,SDA 上的电平必须保持稳定,低电平为数据 0,高电平为数据 1。只有在 SCL 为低电平期间,才允许 SDA 上的电平改变状态。
- 5、应答信号(ACK 和 NACK)
I2C 总线上的所有数据都是以 8 位字节传送的,发送器每发送一个字节,就在第9个时钟脉冲期间释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK 简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。对于反馈有效应答位 ACK 的要求是,接收器在第 9 个时钟脉冲之前的低电平期间将 SDA 线拉低,并且确保在该时钟的高电平期间为稳定的低电平。如果接收器是主控器,则在它收到最后一个字节后,发送一个 NACK 信号,以通知被控发送器结束数据发送,并释放 SDA 线,以便主控接收器发送一个停止信号。
二、程序设计
2.1、I2C控制器源码获取
I2C 时序虽然简单,但是写的不好也会出现很多问题,在开源网站 http://opencores.org/ 上我们可以找到很多非常好的代码,这些代码大部分都提供详细的文档和仿真。俗话说,他山之石,可以攻玉,恰当的使用开源代码,不光能提升我们的开发效率,也能学习别人的开发思路。由于代码大部分都是经过很长时间反复修改,反复精炼后的,所以有些代码理解起来可能比较困难,在不能很好的理解别人代码的时候,最好的办法就是仿真。
如何下载开源的i2c控制器代码呢?且听娓娓道来。
注:下载前需要先注册账号!
- 1、进入 opencores 后,点击左侧的
PROJECTS
,如下图所示:
- 2、打开其中的
Communication controller
,如下所示:
- 3、找到
I2C controller core
,并点击进入选择download
,进行下载,如下图所示:
2.2、读写EEPROM程序设计
从 IP core 文档得知,i2c_master_byte_ctrl
模块主要完成一个字节的读写,我们只需要按照 I2C 读写的要求,完成设备地址、寄存器地址、数据等读写即可。
而 i2c_master_top
模块是我们对 i2c_master_byte_ctrl
模块的再次封装,完成一个寄存器的读写,由于不同的设备寄存器可能是 8bit,也可能是 16bit,这里 i2c_addr_2byte
信号来控制寄存器地址是 8 位还是 16 位。对于i2c_master_top
模块状态机来说,读写寄存器操作流程如下:
- 如果是写操作:先写一个字节设备地址(写操作),再写 1 个字节或 2 个字节的寄存器地址,再写一个字节的数据;
- 如果是读操作:先写一个字节的设备地址(写操作),再写 1 个字节或 2 字节的寄存器地址,完成地址的写入,再次写设备地址(读操作),然后读取一个字节的数据。
不管怎么说,程序设计都是要满足芯片时序要求的,所以在阅读程序之前最好先把芯片的数据手册仔细阅读一遍。
i2c_master_top 状态机的状态转换图如下图所示:
i2c_master_top 模块端口列表如下:
信号名称 | 方向 | 说明 |
---|---|---|
clk | in | 时钟输入 |
rst | in | 异步复位输入,高复位 |
- | - | - |
clk_div_cnt | in | I2C 时钟分频因子,等于系统时钟频率/(5 * I2C 时钟频率) - 1。例如 50Mhz 系统时钟,100Khz 的I2C,配置为 99,400Khz 的 I2C,配置为 24 |
- | - | - |
scl_pad_i | in | I2C 时钟数据输入,本实验可忽略 |
scl_pad_o | out | I2C 时钟输出 |
scl_padoen_o | out | I2C 时钟输出使能,低有效,I2C 外部有上拉电阻,如果输出高阻态,则会被拉到高电平,在本实验中,高电平输出时输出高阻 |
- | - | - |
sda_pad_i | in | I2C 数据输入 |
sda_pad_o | out | I2C 数据输出 |
sda_padoen_o | out | I2C 数据输出使能,低有效。在本实验中,高电平输出时输出高阻。 |
- | - | - |
i2c_addr_2byte | in | 寄存器地址是 8 位还是 16 位,1表示 16 位,0表示 8 位 |
- | - | - |
i2c_read_req | in | I2C 寄存器读请求 |
i2c_read_req_ack | out | I2C 寄存器读请求应答 |
- | - | - |
i2c_write_req | in | I2C 寄存器写请求 |
i2c_write_req_ack | out | I2C 寄存器写请求应答 |
- | - | - |
i2c_slave_dev_addr | in | I2C 设备地址,8bit,最低位忽略,有效数据位是高 7位。 |
i2c_slave_reg_addr | in | 寄存器地址,8 位地址时,低 8 位有效 |
- | - | - |
i2c_write_data | in | 写寄存器数据 |
i2c_read_data | out | 读寄存器数据 |
- | - | - |
error | out | 设备无应答错误 |
i2c_eeprom_test
模块完成 EEPROM 的读写,EEPROM 设备地址是 A0
,程序中将地址 00
的数据读出,然后通过 LED 显示,在 KEY2 按下时,数字加一并再次写入 EEPROM 并显示出来。在 I2C控制器中,代码的大部分功能在备注中也有很多批注。
三、下载实验
下载实验程序后,可以看到 LED 显示一个二进制数字,这个数字是存储在 EEPROM 中 00 地址的数据,数据是随机的,这个时候按键 KEY2 按下,数字加一,并写入了 EEPROM,再次下载程序,可以看到直接显示更新后的数据。
四、使用在线调试 Fabric Debugger观察信号
使用 Fabric Debugger
可以非常直观的看到程序在开发板上运行时各个信号的变化,在本例程中添加一个 Debug Core
来观察程序运行时各数据线的变化情况。具体操作步骤如下:
- 1、打开PDS软件,选择工具栏的“
Inserter
”,如下图所示:
- 2、在弹出界面选择
New DebugCore Unit
来新建一个Debug Core
,如下图所示:
- 3、单击
U0:DebugCore
,会跳到参数设置界面,在"Trigger Parameters
"栏中根据需求选择采样深度Sample Depth
。这里全部保持默认,如下图所示:
- 4、在"
Net Connections
"栏中选择Modify Connections
,进行Net
修改;
- 5、弹出的界面中,首先在
TriggerPort
栏中进行观测信号关联。本例中,选择左侧read_data
,然后单击Make Connections
,可以在TriggerPort
栏中看到关联的观测信号read_data
,如下图所示:
注:read_data有七路信号,所以占了7位。
- 6、以同样的方式对
Clock
栏中时钟信号关联,选择左侧nt_sys_clk
,然后单击Make Connections
,可以在Clock
栏中看到关联的观测信号nt_sys_clk
,然后单击 OK,如下图所示:
- 7、回到如下界面,直接关闭即可,如下图所示:
- 8、接下来重新生成
.sbit
文件。右击Generate Bitstream
,选择Rerun All
,在弹出的窗口选择Yes
后,等待全编译完成,会生成带有在线调试DebugCore
的比特流文件,如下图所示:
- 9、上面的工作准备好后,接下来才是正式开始调试, 单击
Debugger
(确保开发板上电并连接了下载器),如下图所示:
- 10、单击 JTAG 扫描按钮,在找到后单击
OK
,如下图所示:
- 11、右击
DEV:0MyDevice0
,在下拉菜单中单击Configure Bitstream File
,如下图所示:
- 12、在弹出窗口,选择刚刚编译生成的比特流文件,点击
Open
,最后点击OK
,如下图所示:
- 13、到此,程序已下载到 FPGA 中,可以在线观察所需要的信号,选择
Waveform
,选择触发模式为连续,并单击run
,如下图所示:
- 14、可以看到
read_data
数据,每按一次KEY2
键运行一次可以看到数据增加1
。
参考
- 【蓝桥杯单片机组模块】5、EEPROM模块
- 【紫光同创国产FPGA教程】