2500|2

22

帖子

0

TA的资源

一粒金砂(中级)

楼主
 

国产FPGA测评【六】FPGA实现I2C从机与上位机通信 [复制链接]

 

声明:1.本帖如有对引用其他网站资源,均附上了网址,针对本帖中可能出现的侵权行为,请及时联系本人修改或删除。

2.未经本人允许,请勿转载。若本帖存在错误或不足之处,烦请指正,本人会及时修改。


 

0.说明


上个帖子介绍了FPGA作为I2C主机遍历EEPROM的相关知识。我们通常使用的开发板,FPGA往往作为主器件,因此一般都是使用的I2C MASTER模块作为I2C驱动,与其他模块进行交互。

对于另一种常见的使用场景,主板上往往还有ARM芯片、X86芯片或者BMC,FPGA可能只是作为辅助器件承担主板的一部分工作,这时候FPGA往往作为I2C从机与CPU、BMC等进行数据、命令交互。本帖介绍I2C从模块,分为两个部分。第一部分介绍紫光同创的I2C硬核,以及紫光FPGA作为从机通过I2C在线升级固件的方法;第二部分将介绍I2C SLAVE驱动源码,结合I2C时序对其进行分析,方便大家对类似模块的开发和运用。

 

1.紫光I2C硬核及在线升级固件方法


下图为紫光COMPACT系列器件,不同型号的FPGA均有两个I2C硬核。

如果我们需要使用这个I2C硬核,需要在紫光的软件中设置FEATURE VALUE,使能其I2C功能,而不能作为GPIO去使用。下图介绍了设置FEATURE VALUE使能I2C硬核的方法。

如果需要使用I2C硬核,记得选择Dedicated IO,并设置好I2C地址。当我们选择不同的类型时,FEATURE VALUE也是会跟着改变。我在使用COMPACT系列的FPGA时,一般将其用作普通GPIO,并且将FEATURE设置为200389c3。

 

设置使能I2C硬核并设置好I2C地址后(FPGA硬核默认实现的作为从机使用),根据紫光官方提供的文档《Compact系列CPLD器件从I2C接口配置和编程应用指南》,大家可以学习如何通过I2C硬核对FPGA在线升级固件。

简单来说,就是按照手册配置好FPGA参数后,I2C主器件(CPU、PCH、BMC、ARM芯片)等需要调用紫光提供的.c驱动文件,将生成的.sbit通过I2C接口向FPGA内部的FLASH更新并固化固件。

 

2.I2C SLAVE驱动及I2C协议


本帖默认大家理解了I2C协议原理,直接对I2C SLAVE驱动代码进行介绍。

下面是I2C通信的几个关键信号:

起始信号:SCL为高时SDA为下降沿;

重复起始信号:SCL为高时SDA为下降沿;

停止信号:SCL为高时SDA为上升沿。

// start:   SCL ~~~~~~~~~~\____
//  SDA ~~~~~~~~\______
//       x | A | B | C | D | i

// repstart SCL ____/~~~~\___
//  SDA __/~~~\______
//       x | A | B | C | D | i

// stop SCL ____/~~~~~~~~
//  SDA ==\____/~~~~~
//       x | A | B | C | D | i

//- write   SCL ____/~~~~\____
//  SDA ==X=========X=
//       x | A | B | C | D | i

//- read    SCL ____/~~~~\____
//  SDA XXXX=====XXXX
//       x | A | B | C | D | i

本帖I2C SLAVE模块作者为德国的Yiğit Süoğlu 于22年所写,该模块结代码简练,思路清晰,质量非常高。本文将对其写的I2C SLAVE驱动进行详细介绍。

2.1 模块端口声明

module i2c_slave(
  input clk,
  input rst,
  //Config & Control
  output busy,
  output data_available, //New data avaible 
  output data_request,
  //Data interface
  input [6:0] addr, //I2C address for slave
  input [7:0] data_i, //Data in
  output reg [7:0] data_o, //Data Out
  //I2C pins
  input SCL,
  inout SDA/* synthesis keep = 1 */);

各端口的含义如下:

其中,rst为高电平有效,在我们一般设计中多使用低电平有效,程序移植时注意处理;

addr为器件地址,当I2C SLAVE模块接收到地址后,可以与我们设置的器件地址进行比较,如果相同我们可以对主机进行响应,并控制状态机进行读写操作等;

data_i为我们接收到I2C MASTER的单字节数据;

data_o为我们将发给I2C MASTER的单字节数据;

SCL和SDA是FPGA与外部IC的I2C通信引脚,作者Yiğit Süoğlu 已经验证过当SCL为100k时通信是正常的。,

 

2.2状态机和寄存器变量定义

 localparam  IDLE = 3'b000,
             ADDRS = 3'b001,
         ADDRS_ACK = 3'b011,
             WRITE = 3'b110,
         WRITE_ACK = 3'b010,
              READ = 3'b111,
          READ_ACK = 3'b101,
         WAIT_STOP = 3'b100;
  reg [7:0] send_buffer, receive_buffer;
  reg SDA_d, SCL_d, in_READ_d;
  reg [2:0] state;
  reg [2:0] counter;
  wire counterDONE;
  reg start_condition_reg;

作者使用的状态机编码方式为格雷码,每次只变化一个bit,降低了程序出错的风险,建议大家再运用状态机时也使用格雷码。

定义的send_buffer, receive_buffer;比较容易理解,将接收到的数据和准备发送的数据寄存起来;

state为状态机变量,大家平时见到的可能都是current state和next state,这种两个变量还需要进行相互赋值,使用一个state作为状态机变量,程序更直观,不易出错;

counterDONE为计数完成信号,我们常传输一个字节信号,当SCL上升沿进行采样数据后,连续8次就会将这个信号置位,以便让我们知道已经接收或者发送了一个字节的数据;

 

2.3信号同步和状态定义

//I2C signal edges into system clock domain
  always@(posedge clk) begin
    SDA_d <= SDA;
    SCL_d <= SCL;
    in_READ_d <= in_READ;
  end
  wire SDA_negedge = SDA_d & ~SDA;
  wire SDA_posedge = ~SDA_d & SDA;
  wire SCL_negedge = SCL_d & ~SCL;
  wire SCL_posedge = ~SCL_d & SCL;
  wire in_READ_pulse = ~in_READ_d & in_READ;

  //Conditions
  wire start_condition = SCL & SDA_negedge;
  wire stop_condition = SCL & SDA_posedge;
  always@(posedge clk or posedge rst) begin
    if(rst)begin
      start_condition_reg <= 1'b0;
    end else begin
      case(start_condition_reg)
        1'b0: start_condition_reg <= start_condition;
        1'b1: start_condition_reg <= in_IDLE;
      endcase
    end
  end

SCL和SDA信号需要同步到FPGA的系统时钟,避免竞争和冒险问题;

SCL和SDA的上升沿和下降沿是我们经常用到的,因此需要定义出来;

状态定义可以看出I2C起始和结束标志信号都已经根据I2C协议定义好了;

上面的always语句是为了在FPGA复位时给状态机一个初始状态。

 

2.4设定标志位

 //Decode states
  wire in_IDLE = (state == IDLE);
  wire in_ADDRS_ACK = (state == ADDRS_ACK);
  wire in_ADDRS = (state == ADDRS);
  wire in_WRITE = (state == WRITE);
  wire in_WRITE_ACK = (state == WRITE_ACK);
  wire in_READ = (state == READ);
  wire in_READ_ACK = (state == READ_ACK);
  wire in_Get_Data = in_ADDRS | in_WRITE;
  assign busy = ~in_IDLE;

  //Flags
  wire addressed = (addr == receive_buffer[7:1]);
  wire read_nwrite = receive_buffer[0];
  assign data_available = in_WRITE_ACK;
  assign data_request = (in_ADDRS_ACK & addressed) | (in_READ_ACK & ~SDA);

上述代码根据状态机当前所处的状态,定义了wire变量,告诉我们当前状态机处于什么状态;

 

addressed

read_nwrite

data_available

data_request

上述标志位分别定义了:接收地址中、读写标志位、一个字节数据接收完成和地址匹配后请求主机发送数据过来。

 

2.5状态机跳转

状态机跳转就比较简单了,根据I2C的读写时序,写出状态跳转逻辑即可。

 //State transactions
  always@(posedge clk or posedge rst) begin
    if(rst) begin
      state <= IDLE;
    end else begin
      case(state)
        WAIT_STOP: begin
          state <= (stop_condition) ? IDLE : state;
        end
        IDLE: begin
          state <= (start_condition_reg & SCL_negedge) ? ADDRS : state;
        end
        ADDRS: begin
          state <= (counterDONE & SCL_negedge) ? ADDRS_ACK : state;
        end
        ADDRS_ACK: begin
          state <= (SCL_negedge) ? ((addressed) ? ((read_nwrite) ? READ : WRITE) : WAIT_STOP) : state;
        end
        READ: begin
          state <= (counterDONE & SCL_negedge) ? READ_ACK : state;
        end
        READ_ACK: begin
          state <= (SCL_negedge) ? READ : (((SDA_posedge & SDA)? WAIT_STOP : state));
        end
        WRITE: begin
          state <= (stop_condition) ? IDLE : ((counterDONE & SCL_negedge) ? WRITE_ACK : state);
        end
        WRITE_ACK: begin
          state <= (SCL_negedge) ? WRITE : state;
        end
        default: begin
          state <= IDLE;
        end
      endcase
    end
  end

2.6 其他逻辑

  //Data line handling
  wire SDA_Claim = in_READ | (in_ADDRS_ACK & addressed) | in_WRITE_ACK;
  wire SDA_Write = (in_READ) ? send_buffer[7] : 1'b0;
  assign SDA = (SDA_Claim) ? SDA_Write : 1'bZ;

  //sample at posedge of SCL
  always@(posedge SCL) begin
    receive_buffer <= (in_Get_Data) ? {receive_buffer[6:0], SDA} : receive_buffer;
  end
  
  //Data out buffer
  always@(posedge clk) begin
    data_o <= (in_WRITE_ACK) ? receive_buffer : data_o;
  end
  
  //send_buffer
  always@(negedge SCL or posedge in_READ_pulse) begin
    if(in_READ_pulse) begin
      send_buffer <= data_i;
    end else begin
      send_buffer <= (in_READ) ? {send_buffer[6:0],1'b1} : send_buffer;
    end
  end

  //Count posedges
  assign counterDONE = &counter;
  always@(posedge SCL or posedge start_condition) begin
    if(start_condition) begin
      counter <= 3'b111;
    end else begin
      counter <= counter + {2'd0, (in_Get_Data | in_READ)};
    end
  end

介绍下counter计数器,理解稍微难一些:

counter <= counter + {2'd0, (in_Get_Data | in_READ)};该语句表示当状态机处于READ、WRITE或者ADDRESS状态时,计数器就会+1,直到8bit传输完成,计数器就会清0.

 

3.实物仿真


写本帖之前,是打算购买一个USB转I2C模块的,该模块使用的是CH340G芯片,淘宝可以搜到。但是问了多个店铺,均没有win11的驱动,也就是说无法向串口助手那样调试给FPGA发数据调试I2C。

如果大家手里有DSP或者ARM的开发板,也可以将DSP或者ARM的开发板作为主机,通过I2C与FPGA开发板进行通信,由于手边没有其他开发板,工作也较忙,实物仿真暂且搁置了。

最新回复

  send_buffer, receive_buffer;比较容易理解,将接收到的数据和准备发送的数据寄存起来;   state为状态机变量,大家平时见到的可能都是current state和next state,这种两个变量还需要进行相互赋值,使用一个state作为状态机变量,程序更直观,不易出错;   counterDONE为计数完成信号,我们常传输一个字节信号,当SCL上升沿进行采样数据后,连续8次就会将这个信号置位,以便让我们知道已经接收或者发送了一个字节的数据;   详情 回复 发表于 2023-2-12 08:04
点赞 关注(1)
 
 

回复
举报

6968

帖子

11

TA的资源

版主

沙发
 

 

  • send_buffer, receive_buffer;比较容易理解,将接收到的数据和准备发送的数据寄存起来;

 

  • state为状态机变量,大家平时见到的可能都是current state和next state,这种两个变量还需要进行相互赋值,使用一个state作为状态机变量,程序更直观,不易出错;

 

  • counterDONE为计数完成信号,我们常传输一个字节信号,当SCL上升沿进行采样数据后,连续8次就会将这个信号置位,以便让我们知道已经接收或者发送了一个字节的数据;

点评

counterDONE相当于一个标志位,当标志位置位,决定了状态机向下一个状态跳转  详情 回复 发表于 2023-2-12 08:59
 
 
 

回复

22

帖子

0

TA的资源

一粒金砂(中级)

板凳
 
lugl4313820 发表于 2023-2-12 08:04   send_buffer, receive_buffer;比较容易理解,将接收到的数据和准备发送的数据寄存起来; ...

counterDONE相当于一个标志位,当标志位置位,决定了状态机向下一个状态跳转


 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
推荐帖子
半导体供应商竞争焦点---下一代IP业务

宽带网络的广泛部署和VoIP应用的普及相辅相成。除了将企业应用作为主流市场外,VoIP还在政策缺失、运营商封杀的不利情况下逐渐向 ...

诺基亚N92(DVB-H手机).pdf

诺基亚N92(DVB-H手机).pdf

[DIY]二十四小时的感动(古典版I) 自制QS30-1辉光管电子时钟【附百张图】

辉光管是电子管的一种,属于很古老的数码显示器件,在没有LED的年代,显示数字和符号就靠它。这种管子现在应该算古董了,这次DI ...

MSP430单片机的开关电源

作者:Freedomz 1 引 言 MSP430系列单片机是美国TI公司生产的新一代16位单片机,是一种超低功耗的混合信号处理器(MixedSigna ...

基于msp430的触摸键盘到啦!!!!!拆箱焊板!

这是前几天画的基于MSP430G2553触摸键盘的板子,板子上共有20个触摸按键,通过I2C与外界通信,另外还有一个按键中断引脚,一个 ...

宝马冷却系统及电动冷却液泵部件(电子水泵)功能特性及标准

464772 宝马发动机的冷却系统由冷却液冷却系统和发动机机油冷却系统组成。根据宝马车辆规格,使用不同类型的发动机机油冷却系 ...

关于TPS5405的电路元件

TPS5405的手册有电路图,却没写元件型号,这是为什么?遇到这类芯片,应该怎么知道对应的元件型号? 538656

请问 MTK 2625 和 STM32 在硬件上相比,是不是除了基本的硬件之外,只多了基带部分

请问 (1) MTK 2625 和 STM32 在硬件上相比,是不是除了基本的硬件之外,只多了基带部分的功能 (2)移远的BC26 相 ...

传感器通过蓝牙链接 然后读出mpu的数据

545979545980

HooRii Console 开启内测报名,和众科技助力 Matter 开发者

近日,HooRii Technology(和众科技)发布上线综合型开发者平台 HooRii Console ,即日起对外接受内测申请。通过 HooRii Conso ...

关闭
站长推荐上一条 1/8 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表