原创 I2C程序读写

2010-8-10 20:54 5258 4 7 分类: FPGA/CPLD

 


 


注:在说明该I2C程序之前,笔者我有些不爽,不仅仅是因为我的入门课题(数字电视相关是与I2C有关,由于经验欠缺,做起来费了不少脑筋,而且课题最终没能如期完成着实令人沮丧。总结出的I2C协议的要点主要是:


      1.协议的读写,不仅是IC地址的区别,而且在读的过程中应答信号ACK往往是主器件被动获取的,而在读的过程中获取数据后的应答信号ACK是主动设置的。


      2.在I2C总线上由于数据线要双向通信,所以牵扯到一个总线的仲裁机制,也即总线的释放问题。正常情况可以进行主器件设置,当需要接受应答信号是,需要将总线置为高阻态,进而获取信号值。


    3.不同的I2C器件虽然总体上均遵循I2C协议,但是在具体的时序实现过程中,又存在有些许区别,所以一定要参考具体问题的手册,进行具体分析。


以下是小组中的一个教程,在工程中给出了具体的 ,讲解分明的时序实现过程,可以丰富我对I2C实现程序的思路和方法。


 


做本实验前,希望大家能先对IIC协议有足够的了解,最好是以前用C或者汇编做过IIC通信的实验。如果你对IIC不是很熟悉,那么建议你先去看《AT24C01~24C256   I2C中文资料.pdf》和《chinesi2c.pdf》(网络上搜吧),最好是去看看官方的E文版本的datasheet,毕竟那才是最权威的参考资料。


    下面把我们的实验所要达到的目的说一下:


    代码中分了两个模块,iic_com模块除了执行和IIC通信有关的代码设计外,还有案件检测部分;而led_seg7模块只是驱动数码管显示从EEPROM指定地址读出的数据。


Iic_com模块中设置了两个按键SW1和SW2,当SW1按下的时候执行写入操作,当SW2按下的时候执行读出操作。代码中有几个宏定义(verilog的宏和C是一样的):


`define    DEVICE_READ   8'b1010_0001  //被寻址器件地址(读操作)


`define    DEVICE_WRITE  8'b1010_0000  //被寻址器件地址(写操作)


`define    WRITE_DATA    8'b1101_0001  //写入EEPROM的数据


`define    BYTE_ADDR     8'b0000_0011  //写入/读出EEPROM的地址寄存器


    如果你熟悉IIC协议,上面这些参数我想就不用我废话了。因为我们只是一个实验,所以我们这些读写的地址都是固定的,用户可以更改上面的地址或者写入数据,然后观察数码管的数据变化。当然了上面的DEVICE_READ和DEVICE_WRITE是固定不可更改的。


    一种有效的观查方法:


    把代码烧入CPLD中,你会看到此时数码管一定是显示00,因为我们的显示值初始化的时候是dis_data=8’h00。然后你先按下SW2执行读操作,这时候你会看到一个数据是EEPROM该地址原来存放的数据。然后你再分别先后按下SW1和SW2键,你会看到此时的数据才是你在代码中设置的WRITE_DATA的值。如果你之前没有在该地址写过和WRITE_DATA一样的数据,那么他和你第一次按下SW2的值一般是不同的。想了解更多,自己专研了!~_~


`timescale 1ns / 1ps
////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date:
// Design Name:   
// Module Name:    iic_top
// Project Name:  
// Target Device: 
// Tool versions: 
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
////////////////////////////////////////////////////////////////////////////////
module iic_com(
   clk,rst_n,
   sw1,sw2,
   scl,sda,
   dis_data
  );


input clk;  // 50MHz
input rst_n; //复位信号,低有效
input sw1,sw2; //按键1、2,(1按下执行写入操作,2按下执行读操作)
output scl;  // 24C02的时钟端口
inout sda;  // 24C02的数据端口
output[7:0] dis_data; //数码管显示的数据


//--------------------------------------------
  //按键检测
reg sw1_r,sw2_r; //键值锁存寄存器,每20ms检测一次键值
reg[19:0] cnt_20ms; //20ms计数寄存器


always @ (posedge clk or negedge rst_n)
 if(!rst_n) cnt_20ms <= 20'd0;
 else cnt_20ms <= cnt_20ms+1'b1; //不断计数


always @ (posedge clk or negedge rst_n)
 if(!rst_n) begin
   sw1_r <= 1'b1; //键值寄存器复位,没有键盘按下时键值都为1
   sw2_r <= 1'b1;
  end
 else if(cnt_20ms == 20'hfffff) begin
   sw1_r <= sw1; //按键1值锁存
   sw2_r <= sw2; //按键2值锁存
  end


//---------------------------------------------
  //分频部分
reg[2:0] cnt; // cnt=0:scl上升沿,cnt=1:scl高电平中间,cnt=2:scl下降沿,cnt=3:scl低电平中间
reg[8:0] cnt_delay; //500循环计数,产生iic所需要的时钟
reg scl_r;  //时钟脉冲寄存器


always @ (posedge clk or negedge rst_n)
 if(!rst_n) cnt_delay <= 9'd0;
 else if(cnt_delay == 9'd499) cnt_delay <= 9'd0; //计数到10us为scl的周期,即100KHz
 else cnt_delay <= cnt_delay+1'b1; //时钟计数


always @ (posedge clk or negedge rst_n) begin
 if(!rst_n) cnt <= 3'd5;
 else begin
  case (cnt_delay)
   9'd124: cnt <= 3'd1; //cnt=1:scl高电平中间,用于数据采样
   9'd249: cnt <= 3'd2; //cnt=2:scl下降沿
   9'd374: cnt <= 3'd3; //cnt=3:scl低电平中间,用于数据变化
   9'd499: cnt <= 3'd0; //cnt=0:scl上升沿
   default: cnt <= 3'd5;
   endcase
  end
end



`define SCL_POS  (cnt==3'd0)  //cnt=0:scl上升沿
`define SCL_HIG  (cnt==3'd1)  //cnt=1:scl高电平中间,用于数据采样
`define SCL_NEG  (cnt==3'd2)  //cnt=2:scl下降沿
`define SCL_LOW  (cnt==3'd3)  //cnt=3:scl低电平中间,用于数据变化



always @ (posedge clk or negedge rst_n)
 if(!rst_n) scl_r <= 1'b0;
 else if(cnt==3'd0) scl_r <= 1'b1; //scl信号上升沿
    else if(cnt==3'd2) scl_r <= 1'b0; //scl信号下降沿


assign scl = scl_r; //产生iic所需要的时钟
//---------------------------------------------
  //需要写入24C02的地址和数据
    
`define DEVICE_READ  8'b1010_0001 //被寻址器件地址(读操作)
`define DEVICE_WRITE 8'b1010_0000 //被寻址器件地址(写操作)
`define WRITE_DATA  8'b1101_0001 //写入EEPROM的数据
`define BYTE_ADDR  8'b0000_0011 //写入/读出EEPROM的地址寄存器 
reg[7:0] db_r;  //在IIC上传送的数据寄存器
reg[7:0] read_data; //读出EEPROM的数据寄存器


//---------------------------------------------
  //读、写时序
parameter  IDLE  = 4'd0;
parameter  START1  = 4'd1;
parameter  ADD1  = 4'd2;
parameter  ACK1  = 4'd3;
parameter  ADD2  = 4'd4;
parameter  ACK2  = 4'd5;
parameter  START2  = 4'd6;
parameter  ADD3  = 4'd7;
parameter  ACK3 = 4'd8;
parameter  DATA  = 4'd9;
parameter  ACK4 = 4'd10;
parameter  STOP1  = 4'd11;
parameter  STOP2  = 4'd12;


reg[3:0] cstate; //状态寄存器
reg sda_r;  //输出数据寄存器
reg sda_link; //输出数据sda信号inout方向控制位  
reg[3:0] num; //



always @ (posedge clk or negedge rst_n) begin
 if(!rst_n) begin
   cstate <= IDLE;
   sda_r <= 1'b1;
   sda_link <= 1'b0;
   num <= 4'd0;
   read_data <= 8'b0000_0000;
  end
 else   
  case (cstate)
   IDLE: begin
     sda_link <= 1'b1;   //数据线sda为input
     sda_r <= 1'b1;
     if(!sw1_r || !sw2_r) begin //SW1,SW2键有一个被按下   
      db_r <= `DEVICE_WRITE; //送器件地址(写操作)
      cstate <= START1;  
      end
     else cstate <= IDLE; //没有任何键被按下
    end
   START1: begin
     if(`SCL_HIG) begin  //scl为高电平期间
      sda_link <= 1'b1; //数据线sda为output
      sda_r <= 1'b0;  //拉低数据线sda,产生起始位信号
      cstate <= ADD1;
      num <= 4'd0;  //num计数清零
      end
     else cstate <= START1; //等待scl高电平中间位置到来
    end
   ADD1: begin
     if(`SCL_LOW) begin
       if(num == 4'd8) begin 
         num <= 4'd0;   //num计数清零
         sda_r <= 1'b1;
         sda_link <= 1'b0;  //sda置为高阻态(input)
         cstate <= ACK1;
        end
       else begin
         cstate <= ADD1;
         num <= num+1'b1;
         case (num)
          4'd0: sda_r <= db_r[7];
          4'd1: sda_r <= db_r[6];
          4'd2: sda_r <= db_r[5];
          4'd3: sda_r <= db_r[4];
          4'd4: sda_r <= db_r[3];
          4'd5: sda_r <= db_r[2];
          4'd6: sda_r <= db_r[1];
          4'd7: sda_r <= db_r[0];
          default: ;
          endcase
       //  sda_r <= db_r[4'd7-num]; //送器件地址,从高位开始
        end
      end
   //  else if(`SCL_POS) db_r <= {db_r[6:0],1'b0}; //器件地址左移1bit
     else cstate <= ADD1;
    end
   ACK1: begin
     if(/*!sda*/`SCL_NEG) begin //注:24C01/02/04/08/16器件可以不考虑应答位
       cstate <= ADD2; //从机响应信号
       db_r <= `BYTE_ADDR; // 1地址  
      end
     else cstate <= ACK1;  //等待从机响应
    end
   ADD2: begin
     if(`SCL_LOW) begin
       if(num==4'd8) begin 
         num <= 4'd0;   //num计数清零
         sda_r <= 1'b1;
         sda_link <= 1'b0;  //sda置为高阻态(input)
         cstate <= ACK2;
        end
       else begin
         sda_link <= 1'b1;  //sda作为output
         num <= num+1'b1;
         case (num)
          4'd0: sda_r <= db_r[7];
          4'd1: sda_r <= db_r[6];
          4'd2: sda_r <= db_r[5];
          4'd3: sda_r <= db_r[4];
          4'd4: sda_r <= db_r[3];
          4'd5: sda_r <= db_r[2];
          4'd6: sda_r <= db_r[1];
          4'd7: sda_r <= db_r[0];
          default: ;
          endcase
       //  sda_r <= db_r[4'd7-num]; //送EEPROM地址(高bit开始)  
         cstate <= ADD2;     
        end
      end
   //  else if(`SCL_POS) db_r <= {db_r[6:0],1'b0}; //器件地址左移1bit
     else cstate <= ADD2;    
    end
   ACK2: begin
     if(/*!sda*/`SCL_NEG) begin  //从机响应信号
      if(!sw1_r) begin
        cstate <= DATA;  //写操作
        db_r <= `WRITE_DATA; //写入的数据       
       end 
      else if(!sw2_r) begin
        db_r <= `DEVICE_READ; //送器件地址(读操作),特定地址读需要执行该步骤以下操作
        cstate <= START2;  //读操作
       end
      end
     else cstate <= ACK2; //等待从机响应
    end
   START2: begin //读操作起始位
     if(`SCL_LOW) begin
      sda_link <= 1'b1; //sda作为output
      sda_r <= 1'b1;  //拉高数据线sda
      cstate <= START2;
      end
     else if(`SCL_HIG) begin //scl为高电平中间
      sda_r <= 1'b0;  //拉低数据线sda,产生起始位信号
      cstate <= ADD3;
      end 
     else cstate <= START2;
    end
   ADD3: begin //送读操作地址
     if(`SCL_LOW) begin
       if(num==4'd8) begin 
         num <= 4'd0;   //num计数清零
         sda_r <= 1'b1;
         sda_link <= 1'b0;  //sda置为高阻态(input)
         cstate <= ACK3;
        end
       else begin
         num <= num+1'b1;
         case (num)
          4'd0: sda_r <= db_r[7];
          4'd1: sda_r <= db_r[6];
          4'd2: sda_r <= db_r[5];
          4'd3: sda_r <= db_r[4];
          4'd4: sda_r <= db_r[3];
          4'd5: sda_r <= db_r[2];
          4'd6: sda_r <= db_r[1];
          4'd7: sda_r <= db_r[0];
          default: ;
          endcase         
        // sda_r <= db_r[4'd7-num]; //送EEPROM地址(高bit开始)  
         cstate <= ADD3;     
        end
      end
    // else if(`SCL_POS) db_r <= {db_r[6:0],1'b0}; //器件地址左移1bit
     else cstate <= ADD3;    
    end
   ACK3: begin
     if(/*!sda*/`SCL_NEG) begin
       cstate <= DATA; //从机响应信号
       sda_link <= 1'b0;
      end
     else cstate <= ACK3;   //等待从机响应
    end
   DATA: begin
     if(!sw2_r) begin  //读操作
       if(num<=4'd7) begin
        cstate <= DATA;
        if(`SCL_HIG) begin 
         num <= num+1'b1; 
         case (num)
          4'd0: read_data[7] <= sda;
          4'd1: read_data[6] <= sda; 
          4'd2: read_data[5] <= sda;
          4'd3: read_data[4] <= sda;
          4'd4: read_data[3] <= sda;
          4'd5: read_data[2] <= sda;
          4'd6: read_data[1] <= sda;
          4'd7: read_data[0] <= sda;
          default: ;
          endcase                  
     //    read_data[4'd7-num] <= sda; //读数据(高bit开始)
         end
    //    else if(`SCL_NEG) read_data <= {read_data[6:0],read_data[7]}; //数据循环右移
        end
       else if((`SCL_LOW) && (num==4'd8)) begin
        num <= 4'd0;   //num计数清零
        cstate <= ACK4;
        end
       else cstate <= DATA;
      end
     else if(!sw1_r) begin //写操作
       sda_link <= 1'b1; 
       if(num<=4'd7) begin
        cstate <= DATA;
        if(`SCL_LOW) begin
         sda_link <= 1'b1;  //数据线sda作为output
         num <= num+1'b1;
         case (num)
          4'd0: sda_r <= db_r[7];
          4'd1: sda_r <= db_r[6];
          4'd2: sda_r <= db_r[5];
          4'd3: sda_r <= db_r[4];
          4'd4: sda_r <= db_r[3];
          4'd5: sda_r <= db_r[2];
          4'd6: sda_r <= db_r[1];
          4'd7: sda_r <= db_r[0];
          default: ;
          endcase         
        // sda_r <= db_r[4'd7-num]; //写入数据(高bit开始)
         end
   //     else if(`SCL_POS) db_r <= {db_r[6:0],1'b0}; //写入数据左移1bit
         end
       else if((`SCL_LOW) && (num==4'd8)) begin
         num <= 4'd0;
         sda_r <= 1'b1;
         sda_link <= 1'b0;  //sda置为高阻态
         cstate <= ACK4;
        end
       else cstate <= DATA;
      end
    end
   ACK4: begin
     if(/*!sda*/`SCL_NEG) begin
//      sda_r <= 1'b1;
      cstate <= STOP1;      
      end
     else cstate <= ACK4;
    end
   STOP1: begin
     if(`SCL_LOW) begin
       sda_link <= 1'b1;
       sda_r <= 1'b0;
       cstate <= STOP1;
      end
     else if(`SCL_HIG) begin
       sda_r <= 1'b1; //scl为高时,sda产生上升沿(结束信号)
       cstate <= STOP2;
      end
     else cstate <= STOP1;
    end
   STOP2: begin
     if(`SCL_LOW) sda_r <= 1'b1;
     else if(cnt_20ms==20'hffff0) cstate <= IDLE;
     else cstate <= STOP2;
    end
   default: cstate <= IDLE;
   endcase
end


assign sda = sda_link ? sda_r:1'bz;
assign dis_data = read_data;


//---------------------------------------------


endmodule



 


`timescale 1ns / 1ps
////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date:
// Design Name:   
// Module Name:    iic_top
// Project Name:  
// Target Device: 
// Tool versions: 
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
////////////////////////////////////////////////////////////////////////////////
module iic_top(
   clk,rst_n,
   sw1,sw2,
   scl,sda,
   sm_cs1_n,sm_cs2_n,sm_db
  );
  
input clk;  // 50MHz
input rst_n; //复位信号,低有效
input sw1,sw2; //按键1、2,(1按下执行写入操作,2按下执行读操作)
output scl;  // 24C02的时钟端口
inout sda;  // 24C02的数据端口


output sm_cs1_n,sm_cs2_n; //数码管片选信号,低有效
output[6:0] sm_db; //7段数码管(不包括小数点)



wire[7:0] dis_data;  //在数码管上显示的16进制数


iic_com  iic_com(
    .clk(clk),
    .rst_n(rst_n),
    .sw1(sw1),
    .sw2(sw2),
    .scl(scl),
    .sda(sda),
    .dis_data(dis_data)
    );


led_seg7 led_seg7(
    .clk(clk),
    .rst_n(rst_n),
    .dis_data(dis_data),
    .sm_cs1_n(sm_cs1_n),
    .sm_cs2_n(sm_cs2_n),
    .sm_db(sm_db) 
    );
 
  


endmodule  


 


 


 

PARTNER CONTENT

文章评论3条评论)

登录后参与讨论

用户377235 2014-3-9 23:41

if(`SCL_LOW) begin sda_link <= 1'b1; //数据线sda作为output num <= num 1'b1; case (num) 4'd0: sda_r <= db_r[7]; 4'd1: sda_r <= db_r[6]; 4'd2: sda_r <= db_r[5]; 4'd3: sda_r <= db_r[4]; 4'd4: sda_r <= db_r[3]; 4'd5: sda_r <= db_r[2]; 4'd6: sda_r <= db_r[1]; 4'd7: sda_r <= db_r[0];会不会导致,在scl低点平得时候, sda变化了8次啊

用户377235 2014-3-9 23:41

if(`SCL_LOW) begin sda_link <= 1'b1; //数据线sda作为output num <= num 1'b1; case (num) 4'd0: sda_r <= db_r[7]; 4'd1: sda_r <= db_r[6]; 4'd2: sda_r <= db_r[5]; 4'd3: sda_r <= db_r[4]; 4'd4: sda_r <= db_r[3]; 4'd5: sda_r <= db_r[2]; 4'd6: sda_r <= db_r[1]; 4'd7: sda_r <= db_r[0];会不会导致,在scl低点平得时候, sda变化了8次啊

用户799937 2010-9-8 16:59

你好,能请教你一个问题吗? 我有个视频解码器支持四路视频输入,用IIC初始化配置信息,我用的是TVP5154 问题:因为有四个从设备,所以我想同时写入四个从设备的配置信息,应该如何做呀,盼回复,谢谢

朱玉龙 2009-10-22 16:24

不过呢,一般来说民用产品在55度以下,PC发热是集成度太高,一般环境恶劣比如工业环境或者汽车,航天,这方面是非常注重的

用户1275742 2009-10-22 16:04

呵呵,联想做PC 和NB产品对热分析和导热散热材料也很重视的。

朱玉龙 2009-10-21 09:16

好象是各分析各的,不过一般硬件要干很多工作,有专门的热分析的职位来做支持工作的,看行业不一样要求也不一样,据我所知飞利浦照明好像对这块比较重视的

用户1359678 2009-10-21 09:09

楼主,请问热设计分析是硬件工程师做的还是机械结构工程师做的?
相关推荐阅读
用户224985 2010-08-30 12:13
protel99se 多层电路板设计笔记(二)
   protel99 se多层电路板设计的流程(二) 多层电路板中中间层的作用:(包含内部电源和中间信号层),相对而言,多层板的走线方式更见灵活,同时由于使用了独立的电源层和地层,使得信号之间的隔离...
用户224985 2010-08-29 12:06
FPGA/CPLD数字电路设计经验分享
FPGA/CPLD数字电路设计经验分享 摘要:在数字电路的设计中,时序设计是一个系统性能的主要标志,在高层次设计方法中,对时序控制的抽象度也相应提高,因此在设计中较难把握,但在理解RTL电路时序模型的...
用户224985 2010-08-27 12:08
protel99se 多层电路板设计笔记(一)
.                   protel99 se多层电路板设计的流程(一): 1. (1)电路原理图的设计 :注意的是,此处绘制多层电路板的时候,一般采用层次原理图的绘制方式,其优点是将...
用户224985 2010-08-11 18:11
PCB中的几个问题
如何选择PCB板材?编辑:环基线路板 资料来自:互联网时间:2009年11月20日星期五 最近PCB市场的价格比较混乱,即使是同一种FR4的板材,价格也相差比较大,为什么会有那么大的差别呢?究其原因,...
用户224985 2010-08-11 18:08
高频头
什么是高频头?高频头:俗称调谐器,是电视高频信号公共通道的第一部分,目前电视机使用的高频头 分为数字信号高频头(简称数字高频头)和模拟信号高频头(简称模拟高频头)。 ; 数字高频头的作用是接收数字电视...
用户224985 2010-08-10 22:06
FPGA大公司面试笔试数电部分
1:什么是同步逻辑和异步逻辑?(汉王)同步逻辑是时钟之间有固定的因果关系。异步逻辑是各时钟之间没有固定的因果关系。 答案应该与上面问题一致〔补充〕:同步时序逻辑电路的特点:各触发器的时钟端全部连接在一...
EE直播间
更多
我要评论
3
4
关闭 站长推荐上一条 /3 下一条