I2C介绍及verilog实现(主机/从机可综合)
目录
I2C是一种只有2条线的串行通信协议。可用于IC内部通信,也可以用于IC间的通信,广泛用于开关电源、触控芯片、简单的显示芯片等。
(1)2条通信线,SDA数据线,SCL时钟线。
(2)串行的8-bit双向数据传输,速率分为:
(3)在超快速模式下(UFm),最快可达5Mbit/s(单向传输)。
(4)可支持多mater,多slave。

在bus上可以接入多个主机、多个从机,需要接入上拉电阻,在无操作时,线上电压为高电平。
(本文介绍的所有内容仅牵涉单主机,多主机内容和参考附件1)
在F/S模式和Hs模式下的数据格式不同,其中又分读数据格式、写数据格式。


数据传输从开始位开始,接着传输一个7位地址(slave address,这里不介绍10bits地址)和1bit读写位(0:写,1:读)。这里进行写操作,因此读写位为0。接着主机会接收来至从机的1bit响应位(1:未响应,0:响应ok)。若此时从机响应,则主机接着传输需要写入的数据(8 bits),未响应则进入停止位。每一字节传输后都需要接收一次响应,若从机响应再继续传输,若从机未响应,则主机直接进入停止位。最后,当数据传输完成后,主机进入停止位,完成传输。
slave address用于寻找对应的芯片,例如,一个电路板上有多个开关电源,但我们只想用一个I2C主机控制,则可以将所有的SDA和SCL分别接到一起,并给不同的开关电源分配不同的地址。在主机发出开始位和地址位时,所有的开关电源(从机)都会接收到该信号,并将接收的地址同设置好自身的地址对比,若相同,则在响应位拉低SDA,响应主机,否则保持高电平。

Hs模式和F/S模式的数据格式基本相同,只是在开始位后会先发送master code。发送完master code后不需要响应,接着进入Hs模式。传输完成后,可发送停止位或者发送再开始位(Sr)进入下一次传输。
master code为可理解成Hs的标志,传输完master code后就可认为主机进入了Hs模式,若从机支持Hs模式,也应该进入Hs模式。master code是一个8位数据(0000_1xxx),应该注意0000_1000是测试用的保留字段,不能作为master code。同时也应该注意,slave address需避开master code的特征(参考文献1)。


从图中可看出,开始位需要满足的条件:SCL高电平时,SDA由高变低;停止位需要满足的条件:SCL为高电平时,SDA由低变高。(Sr和S的时序一致)


结合5.1可看出,SDA电平变化不能在SCL为高电平时进行,否则可能会被误认为成开始位或者停止位。因此SDA只能在SCL低电平时改变。

在字节内,先传输MSB。

关于I2C介绍网上资料很多,这里介绍了一些重点知识点,朋友可自行网上学习。下面开始说明verilog的具体实现。
(1)实现将fifo传过来的8bits数据转换成I2C串行数据输出的主机和接收数据从机。
(2) 这里用到的fifo由vivado ip直接产生。主机使用一个时钟wclk,一个异步复位(不采用I2C协议的软复位)。wclk分频模块分频后用于产生SCL,分频系数可配。
(3)在设计从机时,可以使用SCL和SDA设计时序电路实现数据接收,但这种设计并不灵活,很难添加其它功能,因此在此设计仍采用单独时钟用于接收数据。
(4)通常slave会先将SDA和SCL先滤波,这里不设计(后面单独讲一下如何滤波)。
(5)计划分4个模块,一个fifo发送模块,一个fifo接收模块,一个I2C主机模块,一个I2C从机模块。
特征:
从数据格式可看出,一次完整的I2C传输至少包含起始位、停止位、响应位、数据传输位,Hs模式时还需要传输master code位,因此I2C的状态也至少包含以上这些状态。另外,由于时序特征,SDA只能在SCL低电平时改变,因此在设计时,可以再细分三个状态将1bit数据分三段传输,开始位和停止位可分为2段。该设计则是用一个计数器的方式控制SDA、SCL的电平,并为再细分。

关于状态机的具体设计,可参考如下图,该状态机基本可满足现阶段常用的I2C从机。这里不做具体分析,建议配合代码理解。

这里同样给出从机的状态机

如代码所示,在设计时,sda、scl分为输入和输出4条线,但芯片外部却只有2条线。这需要如何实现呢?这就牵涉inout类型端口处理。
我们看下面这张图,out信号可以通过en0使能信号控制输出。对应到i2c,总线是有上拉电阻的,所以不操作总线时,总线就是高电平,那么我们只需要在out输出低电平时打开使能即可,即en0 = ~out。对于输入,设想当out为0时,en1也打开,那就没办法接收slave传回的真正信号了,因为不管slave输出什么,此时in肯定时0。那么en1需要在out为高时打开,及en1 = out。这就是为什么有人说只要master和slave一个为低,总线也为低。其实协议的响应位也是考虑了这个因素,理解了这层意思,才能更好理解i2c协议,在设计时,也能考虑清楚各个状态机下的电平赋值。

在fpga设计中代码如下:
assign sda = (sda_out)? 1'bz:sda_out;
assign sda_in = (sda_out)?sda:1'b1;
在ic设计中,pad有专门的模块,对设计者来说只需要生成对应的en信号即可。具体实现方式和上图类似,只是不需要设计人员写代码,而是用专门的cell模块处理。
详细代码较长,放在下载区:
I2C主机及从机Verilog代码实现.zip-硬件开发文档类资源-CSDN下载
通过vivado搭建仿真平台,生成fifo ip,代码结构如图(仿真时未使用master的rx):

前8位为master code,然后进入hs模式,master传输6个数据,接着master读6个数据。
测试代码如下:
`timescale 1ns/1ps
module i2c_test();
//assign sda = (msda_out)? 1'bz:msda_out;
//assign msda_in = (msda_out)?sda:1'b1;
reg wclk ;
reg rst_wclk_n ;
reg i2c_en ;
wire txfifo_empty ;
wire [7:0]tx_data ;
wire sda_out ;
wire scl_out ;
wire txfifo_rd_en ;
wire [7:0]rx_data ;
reg wr_en ;
reg [7:0] din;
reg swr_en;
reg [7:0] sdin;
wire [7:0] stx_fifo_dat;
wire [7:0] s_rx_dat;
//clock
initial
begin
wclk = 1'b0;
forever #50 wclk = ~wclk;
end
//reset
initial
begin
rst_wclk_n = 1'b0;
#5000;
rst_wclk_n = 1'b1;
end
//master control
initial
begin
i2c_en = 1'b0 ;
#6000;
repeat(6)
begin
@(posedge wclk)begin
din <= $random%255;
wr_en <= 1'b1;
end
end
repeat(6)
begin
@(posedge wclk)begin
sdin <= $random%255;
swr_en <= 1'b1;
end
end
@(posedge wclk)
begin
i2c_en <= 1'b1;
wr_en <= 1'b0;
end
#20000;
force i2c_master.scl_in = 1'b0;
#500;
release i2c_master.scl_in;
end
i2c_master i2c_master(
.wclk (wclk ),
.rst_wclk_n (rst_wclk_n ),
.i2c_en (i2c_en ),
.txfifo_empty(txfifo_empty ),
.hs_mode (1'b1 ),
.data_wr_rd (1'b1 ),
.master_code (8'b1001_0001 ),
.slave_addr (8'b1010_1010 ),
.tx_data (tx_data ),
.i2c_comb_wr (1'b1 ),
.rx_byte_num (8'd4 ),
.div_num (8'd100 ),
.hs_div (8'd10 ),
.sda_in (ssda_out ),
.scl_in (sscl_out ),
.sda_out (sda_out ),
.scl_out (scl_out ),
.txfifo_rd_en(txfifo_rd_en ),
.rx_data (rx_data ),
.rx_wr_en_out(rx_wr_en_out )
);
i2c_slave i2c_slave(
.sscl_in (scl_out ),
.ssda_in (sda_out ),
.wclk (wclk ),
.rst_wclk_n (rst_wclk_n ),
.slave_en (1'b1 ),
.stxfifo_empty (stxfifo_empty ),
// .srxfifo_full (srxfifo_full ),
.stx_fifo_dat (stx_fifo_dat ),
.rxfifo_almost_full(rxfifo_almost_full),
.master_code (8'b1001_0001 ),
.slave_addr (8'b1010_1011 ),
.sscl_out (sscl_out ),
.ssda_out (ssda_out ),
.rx_sdat (s_rx_dat ),
.srxfifo_en (srxfifo_en ),
.stx_fifo_rd_en (stx_fifo_rd_en )
);
i2c_txfifo master_txfifo (
.clk(wclk), // input wire clk
.srst(~rst_wclk_n), // input wire srst
.din(din), // input wire [7 : 0] din
.wr_en(wr_en), // input wire wr_en
.rd_en(txfifo_rd_en), // input wire rd_en
.dout(tx_data), // output wire [7 : 0] dout
.full(), // output wire full
.empty(txfifo_empty) // output wire empty
);
slave_rxfifo slave_rxfifo(
.clk(wclk), // input wire clk
.srst(~rst_wclk_n), // input wire srst
.din(s_rx_dat ), // input wire [7 : 0] din
.wr_en(srxfifo_en), // input wire wr_en
.rd_en(rd_en), // input wire rd_en
.dout(dout), // output wire [7 : 0] dout
.full(srxfifo_full), // output wire full
.almost_full(rxfifo_almost_full), // output wire almost_full
.empty(empty) // output wire empty
);
slave_txfifo slave_txfifo (
.clk(wclk), // input wire clk
.srst(~rst_wclk_n), // input wire srst
.din(sdin), // input wire [7 : 0] din
.wr_en(swr_en), // input wire wr_en
.rd_en(stx_fifo_rd_en), // input wire rd_en
.dout(stx_fifo_dat), // output wire [7 : 0] dout
.full(full), // output wire full
.empty(stxfifo_empty) // output wire empty
);
endmodule
附件1:I2C-bus specification and user manual(忘了免费下载地址了……🤦)
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r
我有一个存储主机名的Ruby数组server_names。如果我打印出来,它看起来像这样:["hostname.abc.com","hostname2.abc.com","hostname3.abc.com"]相当标准。我想要做的是获取这些服务器的IP(可能将它们存储在另一个变量中)。看起来IPSocket类可以做到这一点,但我不确定如何使用IPSocket类遍历它。如果它只是尝试像这样打印出IP:server_names.eachdo|name|IPSocket::getaddress(name)pnameend它提示我没有提供服务器名称。这是语法问题还是我没有正确使用类?输出:ge
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复
在ruby中,你可以这样做:classThingpublicdeff1puts"f1"endprivatedeff2puts"f2"endpublicdeff3puts"f3"endprivatedeff4puts"f4"endend现在f1和f3是公共(public)的,f2和f4是私有(private)的。内部发生了什么,允许您调用一个类方法,然后更改方法定义?我怎样才能实现相同的功能(表面上是创建我自己的java之类的注释)例如...classThingfundeff1puts"hey"endnotfundeff2puts"hey"endendfun和notfun将更改以下函数定