草庐IT

I2C介绍及verilog实现(主机/从机可综合)

swear蛋 2023-04-21 原文

I2C介绍及verilog实现(主机/从机可综合)

目录

1.简介

2.基本特征

3.物理连接

4.数据格式

4.1快速模式和低速模式(F/S)写

 4.2快速模式和低速模式(F/S)读

4.3高速模式(Hs)读/写

 4.4连续多次读/写

5.时序

5.1开始位和停止位

 5.2字节传输时序

5.3字节内传输顺序

6.功能描述及模块分析

7.具体设计

7.1主机模块

7.1.1设计思路

7.1.2master状态机

7.2.从机模块

8.I2C顶层接inout口处理

9.代码及仿真

9.1测试模型结构

 9.2测试结果


1.简介

I2C是一种只有2条线的串行通信协议。可用于IC内部通信,也可以用于IC间的通信,广泛用于开关电源、触控芯片、简单的显示芯片等。

2.基本特征

(1)2条通信线,SDA数据线,SCL时钟线。

(2)串行的8-bit双向数据传输,速率分为:

  • 低速模式/标准模式(standard-mode),100 kbit/s;
  • 快速模式(fast-mode),400 kbit/s;
  • 加强快速模式(Fast-mode Plus),1 Mbit/s;
  • 高速模式(high-speed),3.4 Mbit/s;

(3)在超快速模式下(UFm),最快可达5Mbit/s(单向传输)。

(4)可支持多mater,多slave。

3.物理连接

 在bus上可以接入多个主机、多个从机,需要接入上拉电阻,在无操作时,线上电压为高电平。

(本文介绍的所有内容仅牵涉单主机,多主机内容和参考附件1)

4.数据格式

在F/S模式和Hs模式下的数据格式不同,其中又分读数据格式、写数据格式。

  •  白心方框——从机传输给主机
  • 灰色方框——主机传输给从机
  • A       ——响应位
  • A       ——不响应位
  • S       ——开始位
  • P       ——停止位

4.1快速模式和低速模式(F/S)写

       数据传输从开始位开始,接着传输一个7位地址(slave address,这里不介绍10bits地址)和1bit读写位(0:写,1:读)。这里进行写操作,因此读写位为0。接着主机会接收来至从机的1bit响应位(1:未响应,0:响应ok)。若此时从机响应,则主机接着传输需要写入的数据(8 bits),未响应则进入停止位。每一字节传输后都需要接收一次响应,若从机响应再继续传输,若从机未响应,则主机直接进入停止位。最后,当数据传输完成后,主机进入停止位,完成传输。

        slave address用于寻找对应的芯片,例如,一个电路板上有多个开关电源,但我们只想用一个I2C主机控制,则可以将所有的SDA和SCL分别接到一起,并给不同的开关电源分配不同的地址。在主机发出开始位地址位时,所有的开关电源(从机)都会接收到该信号,并将接收的地址同设置好自身的地址对比,若相同,则在响应位拉低SDA,响应主机,否则保持高电平。

 4.2快速模式和低速模式(F/S)读

4.3高速模式(Hs)读/写

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)

 4.4连续多次读/写

5.时序

5.1开始位和停止位

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

 5.2字节传输时序

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

5.3字节内传输顺序

 在字节内,先传输MSB。

6.功能描述及模块分析

关于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主机读写、I2C从机读写
  • 支持Hs、F/S模式
  • 支持分频系数可配
  • 支持读写连续帧
  • 从机被主机读时,若从机数据没准备好,可进入等待状态,同时拉低SCL,直到slave的txfifo有数据写入
  • 从机被写入数据时,若slave的rxfifo满时,可进入等待状态,直到rxfifo的数据被读出

7.具体设计

7.1主机模块

7.1.1设计思路

从数据格式可看出,一次完整的I2C传输至少包含起始位、停止位、响应位、数据传输位,Hs模式时还需要传输master code位,因此I2C的状态也至少包含以上这些状态。另外,由于时序特征,SDA只能在SCL低电平时改变,因此在设计时,可以再细分三个状态将1bit数据分三段传输,开始位和停止位可分为2段。该设计则是用一个计数器的方式控制SDA、SCL的电平,并为再细分。

7.1.2master状态机

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

7.2.从机模块

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

8.I2C顶层接inout口处理

如代码所示,在设计时,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模块处理。

9.代码及仿真

详细代码较长,放在下载区:

I2C主机及从机Verilog代码实现.zip-硬件开发文档类资源-CSDN下载

9.1测试模型结构

通过vivado搭建仿真平台,生成fifo ip,代码结构如图(仿真时未使用master的rx):

 

 9.2测试结果

前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(忘了免费下载地址了……🤦‍)

 

有关I2C介绍及verilog实现(主机/从机可综合)的更多相关文章

  1. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  2. ruby - 即使失败也继续进行多主机测试 - 2

    我已经构建了一些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

  3. ruby - 从 Ruby 中的主机名获取 IP 地址 - 2

    我有一个存储主机名的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

  4. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  5. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  6. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  7. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  8. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  9. ruby - Arrays Sets 和 SortedSets 在 Ruby 中是如何实现的 - 2

    通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复

  10. ruby - "public/protected/private"方法是如何实现的,我该如何模拟它? - 2

    在ruby中,你可以这样做:classThingpublicdeff1puts"f1"endprivatedeff2puts"f2"endpublicdeff3puts"f3"endprivatedeff4puts"f4"endend现在f1和f3是公共(public)的,f2和f4是私有(private)的。内部发生了什么,允许您调用一个类方法,然后更改方法定义?我怎样才能实现相同的功能(表面上是创建我自己的java之类的注释)例如...classThingfundeff1puts"hey"endnotfundeff2puts"hey"endendfun和notfun将更改以下函数定

随机推荐