通过FPGA实现I2C

特别说明:以下所涉及代码参考网络、相关开发板厂家。只用于自己学习、理解和感悟。
I2C总线是由Philips公司开发的一种简单、双向二进制串行总线。只需要两根线即可在连接于总线上的器件之间信息传递。
I2C的标准速率为100kbit/s,快速模式时速率为400kbit/s,支持多机通讯,支持多主控模块,但同一时刻只允许有一个主控。由数据线SDA和时钟线SCL构成串行总线。每个电路和模块都有唯一的地址。
以下以AT24C04为例说明I2C的基本读写操作,I2C设备的操作可分为读写单个存储字节和多个存储字节。读写时序示意图如下所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如上图所示,I2C通信分为空闲状态、开始态、信息传输态、停止态。传输信息分为:设备地址、读写标志、应答信号、存储地址、数据。
空闲态:
SDA和SCL同时处于高电平(硬件电路中一般SDA和SCL线通过电阻上拉),进行总线释放。
开始态:
开始态时,SDA和SCL处于高电平状,当数据线SDA由高电平跳变到低电平后进入开始态。开始态代表一次数据传输的开始。
信息传输态:
I2C总线上,每一位数据都与一个时钟脉冲对应,即在每个数据位在相对应的时钟沿进行串行传送。在I2C总线上,时钟的高电平时,数据线上的数据保持稳定;时钟的低电平时,才允许电平改变状态。 数据信息传输过程中伴随着应答信号的发生。应答分位ACK和NACK。I2C总线上的数据都是以8位字节传送的。发送器每发送一个字节后,在第9个时钟脉冲期间释放数据线,同时等待接收器所反馈的应答信号。如果应答信号为低电平时,表示为有效应答(ACK);否则为无效应答(NACK)。无应答表示接收失败。如果主控制器在接收到最后一个字节后,发送一个NACK信号通知从设备结束数据发送,并释放SDA线,以便主设备发送一个停止信号。
停止态:
在时钟线保持高电平期间,数据线返回高电平即数据线被释放,称为I2C总线上的停止信号。接收到停止信号后I2C总线进入空闲状态。
读写时序如下所示:
1、写时序:
在这里插入图片描述
2、读时序:
在这里插入图片描述
在FPGA中,代码结构如下所示:
在这里插入图片描述

代码如下:
1、timescale.v代码

`timescale 1ns / 10ps

2、i2c_master_defines.v代码

`define I2C_CMD_NOP     4'b0000

`define I2C_CMD_START   4'b0001

`define I2C_CMD_STOP    4'b0010

`define I2C_CMD_WRITE   4'b0100

`define I2C_CMD_READ    4'b1000

3、i2c_master_bit_ctrl.v代码

`include "timescale.v"

`include "i2c_master_defines.v"
//位控制部分
//发送简单命令到SCL/SDA转换器
//每个命令有5个状态,A/B/C/D/idle。
module i2c_master_bit_ctrl(
 input   clk,        //系统时钟
 input   rst,        //同步使能高复位
 input   nReset,     //异步使能低复位
 input   ena,        //内核使能信号

 input[15:0] clk_cnt,    //时钟预分频值
	input[3:0]   cmd,       //从控制器来的命令
	output reg  cmd_ack,    //应答命令
	output reg  busy,       //i2c总线忙
	output reg  a1,         //i2c总线仲裁丢失

	input   din,            //数据输入
	output  reg dout,       //数据输出

 input   scl_i,          //i2c时钟线输入
	output  scl_o,          //i2c时钟线输出
	output  reg scl_oen,    //i2c时钟线输出使能(低使能)
	input   sda_i,          //i2c数据线输入
	output  sda_o,          //i2c数据线输出
	output  reg sda_oen     //i2c数据线输出使能(第使能)
);
reg[1:0]    cSCL,cSDA;      //采集SCL,SDA
reg[2:0]    fSCL,fSDA;      //SCL,SDA滤波器输入
reg     sSCL,sSDA;          //滤波和同步后的SCL,SDA输入
reg     dSCL,dSDA;          //延时变化后的dSCL,dSDA
reg     dscl_oen;           //延时变化后的时钟使能信号
reg     sda_chk;            //检查SDA输出(多主机仲裁)
reg     clk_en;             //时钟产生信号
reg     slave_wait;         //从设备就绪等待
reg[15:0]   cnt;            //时钟分频计数器(同步)
reg[13:0]   filter_cnt;     //滤波器时钟分频
//状态机变量
reg [17:0]  c_state;        //逻辑综合枚举状态
//在从设备没有准备好的时候,通过拉低SCL来达到延时的目的
//延时使能SCL
always @(posedge clk)
	dscl_oen <= #1 scl_oen;
// slave_wait is asserted when master wants to drive SCL high, 
//but the slave pulls it low 
// slave_wait remains asserted until the slave releases SCL
always @(posedge clk or negedge nReset)
 if(!nReset) slave_wait <= 1'b0;
	else        slave_wait <= (scl_oen & ~dscl_oen & ~sSCL) 
| (slave_wait & ~sSCL); 
//主设备SCL为高,但另一个主设备为低
//主设备开始倒计时(进行时钟同步)
wire    scl_sync = dSCL & ~sSCL & scl_oen;
//产生时钟使能信号  
always @(posedge clk or negedge nReset)
 if(~nReset)
   	 begin
       	 	cnt <= #1 16'h0;
       	 	clk_en <= 1'b1;
    	end
	else if(rst || ~|cnt || !ena || scl_sync)
    	begin
        	cnt <= #1 clk_cnt;
        	clk_en <= #1 1'b1;
    	end
	else if(slave_wait)
    	begin
        	cnt <= #1 cnt;
        	clk_en <= #1 1'b0;
    	end
	else
    	begin
        	cnt <= #1 cnt - 16'h1;
        	clk_en <= #1 1'b0;
    	end
//信号采集 
//降低亚稳态风险 
always @(posedge clk or negedge nReset)
 if(!nReset)
   		 begin
        		cSCL <= #1 2'b00;
       		 cSDA <= #1 2'b00;
   		 end 
	else if(rst)
    begin
        cSCL <= #1 2'b00;
        cSDA <= #1 2'b00;
    end
else
    begin
        cSCL <= {cSCL[0],scl_i};
        cSDA <= {cSDA[0],sda_i};
    end
//滤波SCL和SDA信号,(尝试)排除故障
always @(posedge clk or negedge nReset)
	 if(!nReset) filter_cnt <= 14'h0;
	else if(rst||!ena)  filter_cnt <= 14'h0;
	else if(~|filter_cnt) filter_cnt <= clk_cnt >> 2;
	else    filter_cnt <= filter_cnt - 1;
//滤波,filter_cnt频率是fSCLK频率的四倍
always @(posedge clk or negedge nReset)
	if(!nReset)
    		begin
        		fSCL <= 3'b111;
        		fSDA <= 3'b111; 
   		 en
  • 9
    点赞
  • 65
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
要在FPGA实现I2C驱动,可以编写Verilog代码来实现。可以创建两个Verilog文件,一个是顶层模块(i2c_top),另一个是读写模块(i2c_rw)。在顶层模块中,可以实例化读写模块(i2c_rw)和驱动模块(i2c_drive)。读写模块(i2c_rw)可以调用IIC驱动对从机AT24C64模型进行写入和读取操作,并通过比对结果来验证主机对从机的IIC写读是否成功。\[1\] 在I2C总线上,通常有多个设备,其中FPGA是主机器件,而EEPROM存储器、RCT时钟芯片和音频解码芯片属于从机芯片。I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线,只需要两根线即可在连接于总线上的器件之间传送信息。\[2\]\[3\] 因此,通过编写Verilog代码,可以在FPGA实现I2C驱动,实现主机与从机之间的数据传输和通信。 #### 引用[.reference_title] - *1* [FPGA实现IIC协议(二)----IIC总线的FPGA实现(单次读写驱动)](https://blog.csdn.net/wuzhikaidetb/article/details/120752864)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [FPGAI2C的原理及应用(含有源码)](https://blog.csdn.net/weixin_44448586/article/details/118050395)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [【FPGAFPGA基于i2c的eeprom读写](https://blog.csdn.net/weixin_45888898/article/details/122889135)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值