加入星计划,您可以享受以下权益:

  • 创作内容快速变现
  • 行业影响力扩散
  • 作品版权保护
  • 300W+ 专业用户
  • 1.5W+ 优质创作者
  • 5000+ 长期合作伙伴
立即加入
  • 正文
    • 1、I2C(Inter-Integrated Circuit)通信协议及FPGA模块设计
  • 相关推荐
  • 电子产业图谱
申请入驻 产业图谱

I2C通信模块的设计和“AT24C64 型号的EEPROM 芯片通信”实践

13小时前
276
阅读需 15 分钟
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

# 期望通过本文掌握I2C模块的FPGA软件设计和实践 #

1、I2C(Inter-Integrated Circuit)通信协议及FPGA模块设计

I2C 是很常见的一种总线协议,使用两条线在主控制器和从机之间进行数据通信。一条是 SCL(串行时钟线),另外一条是 SDA(串行数据线)。这两条线都需要接上拉电 阻。因为仅有一根数据线,所以I2C通信是半双工的。

I2C 总线有标准模式(100kb/s)和快速模式(400kb/s)两种。

“总线”指多个设备共用的信号线。在一个I2C总线中,支持多个从设备。不同的从设 备有不同的器件地址,这样 I2C 主控制器就可以通过 I2C 设备的器件地址访问指定的 I2C 设备了,一个I2C总线连接多个 I2C 设备,如下所示,

I2C协议基本术语

起始信号: I2C 通信起始标志。主机告诉从机,要开始进行 I2C 通信了。在 SCL 为高电平期间, SDA 出现下降沿就表示产生起始信号。起始信号产生后总线处于占用状态。

停止信号:I2C 停止通信的标志。在 SCL 为高电平期间, SDA 出现上升沿就表示为停止信号。停止信号产生后总线被释放,处于空闲状态。

数据传输:I2C 总线在进行数据传输时要保证在 SCL 高电平期间, SDA 上的数据稳定,因此 SDA 上的数据变化只能在 SCL 低电平期间发生。

数据传送时,先传送最高位,后传送低位。

应答信号:当 I2C 主机发送完 8bit 数据后会将 SDA 设置为输入状态,等待 I2C

从机应答,也就是等到 I2C 从机告诉主机它接收到 8bit 数据了。

应答信号是由从机发出的,主机需要提供应答信号所需的时钟。主机发送完数据以后的下一个时钟信号就是给应答信号使用的。从机通过将 SDA 拉低来表示发出应答信号(ACK ,低有效),表示通信成功,否则表示通信失败(NACK)。

设计一个 I2C 模块,要求如下所示,

    • 支持总线仲裁丢失检测;
    • 支持总线忙状态检测;
    • 支持不同的 I2C 通信模式:
    • 标准模式(100kHz);
    • 快速模式(400kHz);
    • 支持产生起始、终止、重复起始和应答信息;
    • 支持起始、终止和重复起始检测;
    • 支持 7 位寻址模式;
    • 支持中断;

主要包括 8 个 8 位宽的寄存器,如下所示

    • I2C 分频值低字节寄存器 I2C 分频值高字节寄存器 I2C 控制寄存器
    • I2C 发送数据寄存器
    • I2C 接受数据寄存器
    • I2C 命令寄存器
    • I2C 状态寄存器
    • I2C 总线死锁时间寄存器

根据 I2C 协议,我们需要设计一个状态机,对应 I2C 的通信过程,各个状态如下所示,

根据 I2C 寄存器的配置以及状态完成标志进行上述状态的切换,如下所示(仅展示部分代码),

每个状态都需要持续多个周期,所以针对每个状态细分了几个子状态,如下所示,

I2C 有 2 个外部接口,分别是 I2C_SCL I2C_SDA 。对于 I2C_SCL ,这里我们只设计 I2C 主模式,所以对于 FPGA 来说, I2C_SCL 始终是输出。对于 I2C_SDA ,在主模式下,在接收响应的状态下是输入,其他情况下为输出。为了便于接口管理,我们将 I2C_SCL I2C_SDA 都设计为 IOBUF 。如下所示,

主机读写数据的时序操作如下所示,

主机写数据

  1. 主机操作命令寄存器,使能开始命令,使 I2C 总线发送开始信号。
  2. 主机操作发送数据寄存器,写入从机地址 + 读写位,决定访问哪个从机。这是 一个8位的数据,其中高 7 位是从机地址,最后 1 位是读写位。 1 表示读操作,0 表示写操作(对主机而言)。这里读写位为 0 。
  3. 主机操作命令寄存器,使能写命令,使 I2C 总线开始传输数据。
  4. 主机读取状态寄存器的 TIP 位,以确保命令执行完毕。
  5. 主机操作发送数据寄存器,写入从机存储地址,决定待发送数据存储在从机哪里。
  6. 主机操作命令寄存器,使能写命令,使 I2C 总线开始传输数据。
  7. 主机读取状态寄存器的 TIP 位,以确保命令执行完毕。
  8. 主机操作发送数据寄存器,写入 8 bit 的待发送数据。
  9. 主机操作命令寄存器,使能写命令,使 I2C 总线开始传输数据。
  10. 主机读取状态寄存器的 TIP 位,以确保命令执行完毕。
  11. 重复步骤 8 到 10,不断向从机写数据;
  12. 主机操作命令寄存器,使能结束命令,使 I2C 总线结束传输数据。
  13. 每次传输结束后需要延时,保证下次能正常开始传输。

主机读数据

  1. 主机操作命令寄存器,使能开始命令,使 I2C 总线发送开始信号。
  2. 主机操作发送数据寄存器,写入从机地址 + 读写位,决定访问哪个从机。这是 一个8位的数据,其中高 7 位是从机地址,最后 1 位是读写位。 1 表示读操作,
    0 表示写操作(对主机而言)。这里读写位为 0 。
  3. 主机操作命令寄存器,使能写命令,使 I2C 总线开始传输数据。
  4. 主机读取状态寄存器的 TIP 位,以确保命令执行完毕。
  5. 主机操作发送数据寄存器,写入从机存储地址,主机将会从该地址读取数据。
  6. 主机操作命令寄存器,使能写命令,使 I2C 总线开始传输数据。
  7. 主机读取状态寄存器的 TIP 位,以确保命令执行完毕。
  8. 主机操作命令寄存器,使能开始命令(这种情况是重复起始),使 I2C 总线发送开始信号。
  9. 主机操作发送数据寄存器,写入从机地址 + 读写位。这里读写位为 1。
  10. 主机操作命令寄存器,使能写命令,使 I2C 总线开始传输数据。
  11. 主机读取状态寄存器的 TIP 位,以确保命令执行完毕。
  12. 主机操作命令寄存器,使能读命令和应答命令,使 I2C 总线开始接收数据。
  13. 主机操作接收数据寄存器,读取接收到的 8 bit 的数据。
  14. 重复步骤 12 到 13,不断从从机读数据;
  15. 当主机需要停止从从机读数据时,操作命令寄存器,使能读命令,但不使能应答命令,读取最后一个字节的数据。

我们可以将 I2C 模块作为一个 APB 外设,挂在 APB 总线上。那么就需要设计一个 APB 接口来对寄存器进行读写操作。我们需要对每个 APB 接口分配一个地址,这样才能通过译码电路区分开来不同的 APB 。在 config.h 文件中定了 9 路 APB 地址,默认使用 APB0 作为 GPIO ,现在我们为 I2C 模块分配 APB5 ,对应地址为 0xbfe90000 ,如下所示,

//APB0
`define APB_SLV0_ADDR_BASE 32'hbfeb0000 //APB0 base address
`define APB_SLV0_ADDR_LEN 32'h0000ffff //APB0 length
//APB1
`define APB_SLV1_ADDR_BASE 32'hbfec0000 //APB1 base address
`define APB_SLV1_ADDR_LEN 32'h0000ffff //APB1 length
//APB2
`define APB_SLV2_ADDR_BASE 32'hbfed0000 //APB2 base address
`define APB_SLV2_ADDR_LEN 32'h0000ffff //APB3 length
//APB3
`define APB_SLV3_ADDR_BASE 32'hbfea0000 //APB3 base address
`define APB_SLV3_ADDR_LEN 32'h0000ffff //APB3 length
//APB4
`define APB_SLV4_ADDR_BASE 32'hbfe88000 //APB4 base address
`define APB_SLV4_ADDR_LEN 32'h00000fff //APB4 length
//APB5
`define APB_SLV5_ADDR_BASE 32'hbfe90000 //APB5 base address
`define APB_SLV5_ADDR_LEN 32'h0000ffff //APB5 length

最后实现的结构框图如下所示,

在顶层文件 godson_mcu_top.v 中例化我们设计的模块,如下所示,

在约束文件中,将例化好的 I2C 的输出引脚原理图上的合适引脚进行连接即可,如下所示,

2. 软件设计,基于 I2C 模块与 AT24C64 的EEPROM 芯片通信

既然已经设计好了硬件电路,我们就可以进行软件程序的编写了。在硬件 I2C 模块设计过程中我们为 APB 分配的地址是 0xbfe90000 ,并且 I2C 相关寄存器的偏移地址是 0x00 0x01 0x02 0x03 0x04,所以软件上需要对应好。一个不错的方法是用结构体指针来访问寄存器。由于这个结构体指针使用频率很高,所以通过宏定义进行重命名 (I2C),如下所示,

编写的函数声明如下所示,

然后可以基于 I2C 相函数编写 I2C 读写 AT24C64 的函数,如下所示,

返回值:无
说 明:无
****************************************************************/ void AT24CXX_WriteByte(uint16_t u16Addr, uint8_t u8Data)
{
soc_I2C_GenerateSTART(ENABLE);// 起始信号 soc_I2C_SendData(DEV_ADDR | WRITE_CMD);// 器件寻址+读/写选择 soc_I2C_wait();
soc_I2C_SendData((uint8_t)((u16Addr >> 8) & 0xFF)); soc_I2C_wait();
soc_I2C_SendData((uint8_t)(u16Addr & 0xFF)); soc_I2C_wait();
soc_I2C_SendData(u8Data); soc_I2C_wait(); soc_I2C_GenerateSTOP(ENABLE);// 停止信号
soc_I2C_delay(20);// 需要延时 2u ,保证下次能正常开始传输
}
/****************************************************************
函数名:x24Cxx_ReadByte
功 能:读一个字节
参 数:u16Addr要读取的地址
返回值:u8Data读出的数据
说 明:无
****************************************************************/ uint8_t AT24CXX_ReadByte(uint16_t u16Addr)

{

uint8_t u8Data = 0; soc_I2C_GenerateSTART(ENABLE);// 起始信号

soc_I2C_SendData(DEV_ADDR | WRITE_CMD);// 器件寻址+读/写选择

这里需要注意的是,对芯片完成单字节写入或者页面写入命令后需要延时 10ms 。因为设备在此期间将不会对新的命令作出响应。延时之后再进行读命令才可以读到刚写入的数据。

我们可以在 main.c 中调用相应函数验证 I2C 通信,如下所示,

相关推荐

电子产业图谱