用F340 GPIO做I2C

在和Qinheng开发小尺寸点灯治具中,F340FPGA采用I2C通信,其中F340作为I2C的主机,I2C端口用自己的GPIO编写,总结遇到的问题及注意事项:

 

1.  F340端口及上拉电阻设置:

         要配成SCL/SDA的端口必须设置成opendrain结构,本例中设置如下:

         sbitSCL = P0^0;

sbit SDA = P0^2;               

 

void PORT_Init(void)

{

         P0MDOUT = 0x00;                     // P0 is open-drain                    

       XBR1= 0x40;             // Enable crossbarand weak pull-ups

}

 

上拉电阻搭配XILINXSPARTAN3EFPGA,经尝试发现用750ohm阻值的电阻,波形较好。1K2.2K的均可以通信。

 

2.   I2C时序控制:

         I2C读写的时序一定要准照协议,否则会出现SCLSDA线电平被拉、互相干扰等异常。本次开发中遇到问题的有下述几点。

         2.1 写时序时注意事项

                   写操作时,结束标志由主机发起,发起的位置要注意,是在读完最后一个ack响应后的一个clk上升沿之后,不是在读完ack后直接产生。从clk的个数上来看,从最后一个byte来数,是在第10clk上升沿之后。写操作正常时序如下图:

 

 

注意结束标志起来的位置,结束标志的产生需特别注意,参考代码。

 

                   写操作的代码如下:

                   /*I2C字节写 */

void i2c_write_byte(unsigned char b) 

{

         int i = 0;

         for(i=7; i>=0; i--)

         {

                   set_scl(0);

                   set_sda(b&(1<<i));  //从高位到低位依次准备数据进行发送

                   set_scl(0);

                   set_scl(0);

 

                   set_scl(1);

                   set_scl(1);

                   set_scl(0);

         }

         i2c_read_ack();                  //检查目标设备的ACK信号

}

 

/*   I2C读取ACK信号(写数据时使用)  */ 

unsigned char i2c_read_ack()

{

         unsigned char ack;

 

         set_sda(1);                // 主机释放SDA线,为读响应位做准备

 

         set_scl(0);

         set_scl(0);

         ack = SDA;                // 接收从机的ACK信号

         set_scl(0);

         set_scl(1);                  // 上升沿读取ACK信号

         set_scl(1);

 

         set_scl(0);                  // 读取完毕

 

     return ack;

}

 

/* I2C终止条件 */

void i2c_stop()

{

         // 结束标志前SCLSDA状态清零

         set_scl(0);

         set_sda(0);                // SCL低电平处将SDA拉低

         set_scl(0);

 

         // 产生结束标志

         set_scl(1);                  // 拉高SCL

         set_scl(1);

         set_scl(1);

 

         set_sda(1);                // 拉高SDA

 

         TimerDelay_us(1);

}

 

/* 

I2C写操作 

pSlave_addrI2C slave设备地址

pChip_addr: 目标设备地址            

buf:写缓冲区 

len:写入字节的长度

自己应用中,协议里的chipaddrifsel等参数可向该函数中添加参数 

 */

void i2c_write (U8 pSlave_addr, U8 pChip_addr, U8* buf, intlen) 

{

         int i; 

        unsigned char t; 

        i2c_start();                        //起始条件,开始数据通信

           

        // 发送slave地址和register地址,写方向 

         t = (pSlave_addr<< 1) | 0;                    //低位为0,表示写数据 

         i2c_write_byte(t);

        

         // 发送chipaddress

         t = pChip_addr;

         i2c_write_byte(t);

 

        // 写入数据 

         for (i=0; i<len;i++) 

                 i2c_write_byte(buf[i]);

                    

         i2c_stop();                     //终止条件,结束数据通信

                                    

}

 

另外就是注意主机写数据与读ack的操作,对sda线的控制问题:

主机8bit数据写完后与读ack的衔接,写数据时是主机控制sda线,读ack时要将sda线的控制权交出,作为opendrain接口,交出控制权的方法就是将该端口置1。所以ack操作时第一步是将sda1。观察波形可以看到如果和从机正常通信,从机响应ack(sda 0)交出sda控制权后,sda线仍会被拉高,如下图红框中所示。

open drain结构的特点就是:默认情况下,端口始终是高电平,但要控制总线时,可对其进行拉低操作。如要交出控制权,只需将自己的端口置1即可

 用F340 GPIO做I2C_第1张图片

 

 

         2.2 读时序注意事项:

                   在本例开发中读操作时序犯了三个错误:restart信号的格式;读操作结束时最后一个byte读取完毕后,要发送nack,之前读完每个byte后发送的是ack;每读完一个byte后发送ack后,要交出sda控制权,即要将自己端口的sda1

                   读操作的正确时序如下图所示:

 用F340 GPIO做I2C_第2张图片

 

                   读操作的代码如下:

 

/* 

I2C读操作 

pSlave_addrI2C slave设备地址

pChip_addr: 目标设备地址

pReg_addr_msb: 要读取寄存器地址高字节

pReg_addr_lsb:要读取寄存器地址低字节 

buf:读缓冲区 

len:读入字节的长度 

 */ 

void i2c_read(U8 pSlave_addr, U8 pChip_addr, U8 pReg_addr_msb, U8pReg_addr_lsb, U8* buf, int len) 

 { 

        int i; 

         unsigned char t; 

         i2c_start();                        //起始条件,开始数据通信

          

        //发送slave地址和register地址,写方向 

         t = (pSlave_addr<< 1) | 0;                    //低位为0,表示读数据 

         i2c_write_byte(t);

 

         // 发送chipaddress

         i2c_write_byte(pChip_addr);

 

         // 发送registeraddress

         i2c_write_byte(pReg_addr_msb);

         i2c_write_byte(pReg_addr_lsb);

 

         TimerDelay_us(I2C_READ_WAIT_FPGA);                        // 等待FPGA读取

 

         // set restart

         i2c_restart();

 

         //发送slave地址,读方向 

         t = (pSlave_addr<< 1) | 1;                    //低位为1,表示读数据 

         i2c_write_byte(t);

          

          //读入数据 

         for (i=0; i<len;i++)

         { 

                   if(i<(len-1))

                           buf[i] =i2c_read_byte();                    // 读取一个byte后发送ack

                   else

                            buf[i] = i2c_read_lastbyte();           // 读取完最后一个byte发送nack

         }

        i2c_stop();                    //终止条件,结束数据通信 

 }

 

/* I2C字节读 */ 

unsigned char i2c_read_byte()

{

         int i = 0;

         unsigned char rbyte =0;

         for(i=0; i<8; i++)

         {

                   set_scl(0);

                   set_scl(0);

 

                   rbyte =(rbyte<<1)|SDA;            //从高位到低位依次准备数据进行读取

                   set_scl(1);

                   set_scl(1);

 

                   set_scl(0);                 

         }

         i2c_send_ack();                   //向目标设备发送ACK信号

 

         return rbyte;

}

 

/* I2C 读最后一字节 */ 

unsigned char i2c_read_lastbyte()

{

         int i = 0;

         unsigned char rbyte =0;

         for(i=0; i<8; i++)

         {

                   set_scl(0);

                   set_scl(0);

 

                   rbyte =(rbyte<<1)|SDA;            //从高位到低位依次准备数据进行读取

                   set_scl(1);

                   set_scl(1);

 

                   set_scl(0);                          

         }

         i2c_send_nack();                 //向目标设备发送NACK信号

 

         return rbyte;

}

 

/* I2C发出ACK信号(读数据时使用) */ 

void i2c_send_ack()

{

         set_sda(0);                // 主机发送ack,并控制sda线

 

         set_scl(0);       

         set_scl(1);                  // 给从机提供上升沿读取ack

         set_scl(1);

         set_scl(0);

 

         set_sda(1);                //主机释放sda线,为读下一byte数据做准备

}

 

/* I2C发出NACK信号(读数据时使用) */ 

void i2c_send_nack()

{

         set_sda(1);                // 主机发送nack,并控制sda线

 

         set_scl(0);       

         set_scl(1);                  // 给从机提供上升沿读取ack

         set_scl(1);

         set_scl(0);

}

 

/* I2C restart */

void i2c_restart(void)

{

         set_scl(1);

         set_scl(1);

 

         set_sda(0);      

}

 

注意点:

Restart信号和之前I2C写操作的衔接。在上一步写操作完成后,会读取从机的ack响应,读取完毕后,sda线会被拉高,此时按程序所示产生restart信号。

I2C读操作过程中,多个字节读取时,前面的字节读取完毕发送ack信号,最后一个字节读取完毕一定要发送nack信号

多字节读取过程中,每个字节读取完毕发送ack信号(sda由主机置低)之后,务必要有交出sda线控制权的操作,即将主机的sda端口置高,如i2c_send_ack()函数中的set_sda(1)。不然会产生竞争,从机发出的数据高电平会出现被拉低的现象,如下图所示。

不加set_sda(1)

 用F340 GPIO做I2C_第3张图片

用F340 GPIO做I2C_第4张图片

 

 

set_sda(1)

用F340 GPIO做I2C_第5张图片

 

你可能感兴趣的:(用F340 GPIO做I2C)