您的位置:首页 > 其它

GPIO模拟I2C程序实现

2014-10-09 23:23 441 查看
GPIO模拟I2C程序实现.
I2C是由Philips公司发明的一种串行数据通信协议,仅使用两根信号线:SerialClock(简称SCL)和SerialData(简称SDA)。I2C是总线结构,1个Master,1个或多个Slave,各Slave设备以7位地址区分,地址后面再跟1位读写位,表示读(=1)或者写(=0),所以我们有时也可看到8位形式的设备地址,此时每个设备有读、写两个地址,高7位地址其实是相同的。

I2C数据格式如下:

无数据:SCL=1,SDA=1;

开始位(Start):当SCL=1时,SDA由1向0跳变;

停止位(Stop):当SCL=1时,SDA由0向1跳变;

数据位:当SCL由0向1跳变时,由发送方控制SDA,此时SDA为有效数据,不可随意改变SDA;

当SCL保持为0时,SDA上的数据可随意改变;

地址位:定义同数据位,但只由Master发给Slave;

应答位(ACK):当发送方传送完8位时,发送方释放SDA,由接收方控制SDA,且SDA=0;

否应答位(NACK):当发送方传送完8位时,发送方释放SDA,由接收方控制SDA,且SDA=1。

当数据为单字节传送时,格式为:

开始位,8位地址位(含1位读写位),应答,8位数据,应答,停止位。

当数据为一串字节传送时,格式为:

开始位,8位地址位(含1位读写位),应答,8位数据,应答,8位数据,应答,……,8位数据,应答,停止位。

需要注意的是:

1,SCL一直由Master控制,SDA依照数据传送的方向,读数据时由Slave控制SDA,写数据时由Master控制SDA。当8位数据传送完毕之后,应答位或者否应答位的SDA控制权与数据位传送时相反。

2,开始位“Start”和停止位“Stop”,只能由Master来发出。

3,地址的8位传送完毕后,成功配置地址的Slave设备必须发送“ACK”。否则否则一定时间之后Master视为超时,将放弃数据传送,发送“Stop”。

4,当写数据的时候,Master每发送完8个数据位,Slave设备如果还有空间接受下一个字节应该回答“ACK”,Slave设备如果没有空间接受更多的字节应该回答“NACK”,Master当收到“NACK”或者一定时间之后没收到任何数据将视为超时,此时Master放弃数据传送,发送“Stop”。

5,当读数据的时候,Slave设备每发送完8个数据位,如果Master希望继续读下一个字节,Master应该回答“ACK”以提示Slave准备下一个数据,如果Master不希望读取更多字节,Master应该回答“NACK”以提示Slave设备准备接收Stop信号。

6,当Master速度过快Slave端来不及处理时,Slave设备可以拉低SCL不放(SCL=0将发生“线与”)以阻止Master发送更多的数据。此时Master将视情况减慢或结束数据传送。

7,I2C规程运用主/从双向通讯。器件发送数据到总线上,则定义为发送器,器件接收据

定义为接收器。主器件和从器件都可以工作于接收和发送状态。 总线必须由主器件(通常
为微控制器)控制,主器件产生串行时钟(SCL)控制总线的传输方向,并产生起始和停止
条件。SDA线上的数据状态仅在SCL为低电平的期间才能改变,SCL为高电平的期间,SDA
状态的改变被用来表示起始和停止条件。
 

在实际应用中,并没有强制规定数据接收方必须对于发送的8位数据做出回应,尤其是在Master和Slave端都是用GPIO软件模拟的方法来实现的情况下,编程者可以事先约定数据传送的长度,不发送ACK,有时可以起到减少系统开销的效果。

源码:

/********************************************************************/

void i2c_init(void)

{

PACNT_init;

PADDR_init;

PADAT_init;

SCL_high;

SDA_high;

}

/********************************************************************/

uint8 i2c_write(uint8 slave_address, uint8 *buffer, int byte_count, int freq)

{

    uint8 out_mask = 0x80;

    uint8 value = 0x00;

  uint8 send_byte = 0x00;

  uint8 status = 0x81;

  int count = 8;

  int clk_count = 0;

int i = 0;

/* Set delay value based on frequency. */

int D = (int) ((4000/freq) - 14);

slave_address = (slave_address & 0xFE);

         

  i2c_start();

  delay(500);

  

   send_byte = slave_address;

        

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

   {    

    count = 8;  

    out_mask = 0x80;

         

    /* Send data bytes one bit at a time. */   

    while(count > 0)

    {            

     value = ((send_byte & out_mask) ? 1 : 0);

      if (value == 1)

      {

       PADAT_init;

        SDA_high;}

      else

      {

       PADAT_init;

       SDA_low;}

       

      delay(D);     

                   

        PADAT_init;

      SCL_high;

      

      /* Clock stretching wait statement.  Wait until clock is released

      by slave.  Only effects program on first iteration.  */

   while (((GPIO_PADAT & 0x0200) ? 1 : 0) == 0){;}

      delay(2*D);  

       

      PADAT_init;

      SCL_low;

      delay(D); 

       

      out_mask >>= 1; 

      count--;   

     }

     

     PADAT_init;

     SDA_high;  /* Let go of data pin. */

     delay(D); 

      

     if (((GPIO_PADAT & 0x0400) ? 1 : 0) == 1)

     { 

      status = 0xA1; /* Transfer complete, bus busy, acknowledge not received. */

      break; } /* If not acknowledged, exit loop. */

     

    PADAT_init;

     SCL_high;

     delay(2*D);  

    

     PADAT_init;

     SCL_low;

     status = 0xA0;  /* Transfer complete, bus busy, acknowledge received. */

     delay(D); 

     

     send_byte = buffer[i];      

    }

    

    PADAT_init;

    SDA_high;

    SCL_low;   

delay(100);

return(status);

}

/********************************************************************/

uint8 i2c_read(uint8 slave_address, uint8 *buffer, int byte_count, int freq)

{

uint8 input_byte = 0x00;

uint8 value = 0x00;

uint8 out_mask = 0x80;

uint8 status = 0x81;

int count = 8;

int clk_count = 0;

int i = 0;

/* Set delay value based on frequency. */

int D = (int) ((4000/freq) - 14);

slave_address = (slave_address | 0x01);

   

i2c_start();

delay(500);

               

/**********  Write Address Procedure **********/

  

   while(count > 0)

   {

    value = ((slave_address & out_mask) ? 1 : 0);

     if (value == 1)

     {

      PADAT_init;

       SDA_high;}

     else

     {

      PADAT_init;

      SDA_low;}

      delay(D);      

                   

      PADAT_init;

     SCL_high;

      

     /* Clock stretching wait.  Wait until clock is released

     by slave.  */

     while (((GPIO_PADAT & 0x0200) ? 1 : 0) == 0){;}

     delay(2*D); 

       

     PADAT_init;

     SCL_low;

     delay(D);  

       

     out_mask >>= 1; 

     count--;         

  }

     

    PADAT_init;

  SDA_high;  /* Let go of data pin. */

    delay(D);

    SCL_high;

    delay(2*D);  

    

    /* If not acknowleged, set status accordingly and exit read process. */

    if (((GPIO_PADAT & 0x0400) ? 1 : 0) == 1)

    {

     status = 0xA1;

     return(status);}

    

    PADAT_init;

    SCL_low;

    delay(D);

/**********  Begin Read Procedure **********/

/* Release SDA and SCL to initiate transfer. */

PADAT_init;    

SDA_high;

SCL_high;

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

{

  count = 8;

  input_byte = 0x00;

   

  PADAT_init;

  SCL_high;

   

  /* Clock stretching wait.  Wait until clock is released

     by slave.  */

  while (((GPIO_PADAT & 0x0200) ? 1 : 0) == 0){;}

           

  /* Loop for bit-by-bit read of data. */

  while(count > 0) 

  {

    PADAT_init;

    SCL_high;

    delay(D);  

    delay(4); /* Required to make read and write clocks the same freq. */

    

   if ((GPIO_PADAT & 0x0600) == 0x0600)

    input_byte++;

  

   delay(D);  

   

   PADAT_init;

   SCL_low;

   delay(2*D); 

      

   if (count == 1)

    break;

   else  

    input_byte <<= 1;

  

   count--;

  }

  /* Write input byte to "read_buffer". */

  buffer[i] = input_byte;

    

  if(i == (byte_count - 1))

   break;

     

     /* Below is the acknowledge procedure. */     

     PADAT_init;

     SDA_low;

     delay(D);  

     SCL_high;     

  delay(2*D);  

    

     PADAT_init;

     SCL_low;

     delay(D);  

     SDA_high; 

     status = 0xA0; 

}     

/* Standard protocol calls for the last read byte to

    not receive an acknowledge from the master. */     

PADAT_init;

    SDA_high;

    SCL_high;     

delay(2*D); 

     

   PADAT_init;

   SCL_low;

   delay(D);  

   SDA_high;

   status = 0xA1;

   return(status);

}

/********************************************************************/

void i2c_start(void)

{

int clk_count = 0;

uint8 compare = 0x00;

PADAT_init;  

SDA_high;

delay(100);

PADAT_init;  

SCL_high;

delay(100);

/* Clock stretching wait.  Wait until clock is released

    by slave.  */

while (((GPIO_PADAT & 0x0200) ? 1 : 0) == 0){;}    

  

PADAT_init;

SDA_low;

delay(100);

     

    PADAT_init;  

SCL_low;

delay(100);

}

/********************************************************************/

uint8 i2c_stop(void)

{

uint8 status = 0x00;

int clk_count = 0;

  

PADAT_init;

SCL_low;

delay(100);

PADAT_init;

SDA_low;

delay(100);

    

    PADAT_init; 

SCL_high;

/* Clock stretching wait statement.  Wait until clock is released

    by slave.  */

while (((GPIO_PADAT & 0x0200) ? 1 : 0) == 0){;}

      

delay(100);

    

    PADAT_init; 

SDA_high;

status = 0x81; /* Set bus idle. */

return(status);

}

/********************************************************************/

void delay(int value)

{

int clk_count = 0;

while (clk_count < value)

{clk_count++;}

}

/********************************************************************/

 

第二个例子

函数定义:

gpio_iic.h:

#ifndef __IIC_GPIO__

#define __IIC_GPIO__

void delay();

/*

设置scl引脚电平,0低电平,1高电平,其他值无效,返回值一直为0,留着它用。

*/

int set_scl( int value );

/*

得到scl引脚电平,0低电平,1高电平,必须是这两个值,其他函数需要调用。

*/

int get_scl();

/*

设置sda引脚电平,0低电平,1高电平,其他值无效,返回值一直为0,留着它用。

*/

int set_sda( int value );

/*

得到sda引脚电平,0低电平,1高电平,必须是这两个值,其他函数需要调用。

*/

int get_sda();

/*

重新发送iic start位,这个是在传送数据过程中使用。

*/

void iic_restart();

/*

发送iic start位,这里假设总线空闲,此时SDL与SCL都为高电平。

*/

void iic_start();

/*

发送stop位,这里假设scl此时为低电平。

*/

void iic_stop();

/*

发送一个bit0,这里假设scl此时为低电平,sda电平不定。

*/

void send_bit0();

/*

发送一个bit 1,这里假设scl此时为低电平,sda电平不定。

 */

void send_bit1();

/*

接收一个bit位,返回值只能是0或1。

 */

int receive_bit();

/*

发送ACK,实际上是发送一个bit0.

*/

void send_ack();

/*

接收ACK。

*/

int receive_ack();

/*

接收一个字节。

*/

char receive_byte();

/*

接收一个buf,返回值总是为0,它不能保证从器件一定能收到ACK,也不能保证从器件正在工作。

*/

int receive_buf( char *buf, int buf_size );

/*

发送一个字
e249
节,返回ACK的值,发送时,没有收到ACK会重试n次,这是常。

 */

int send_byte( char data_byte );

/*

发送一个buf,返回值是成功发送,收到ACK的字节数量。

*/

int send_buf( char *buf, int buf_size );

#endif

gpio_iic.c:

#include "gpio_iic.h"

//#define __80C52__

#define __MINI2440__

#ifdef __MINI2440__

#include <linux/gpio.h>

#include <linux/delay.h>

#include <mach/regs-gpio.h>

#endif

#ifdef __80C52__

#include <reg52.h>

sbit SCL  = P1^0;

sbit SDA    = P1^1;

#endif

void delay()

{

#ifdef __MINI2440__

     udelay(1);

#endif

#ifdef __80C52__

     int i = 0;

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

#endif

}

/*

设置scl引脚电平,0低电平,1高电平,其他值无效,返回值一直为0,留着它用。

*/

int set_scl( int value )

{

#ifdef __MINI2440__

//    s3c2410_gpio_cfgpin(S3C2410_GPE(15), S3C2410_GPE15_IICSDA);

//     s3c2410_gpio_cfgpin(S3C2410_GPE(14), S3C2410_GPE14_IICSCL);

     s3c2410_gpio_cfgpin(S3C2410_GPE(14), S3C2410_GPIO_OUTPUT);

     switch( value )

     {

     case 0:

      s3c2410_gpio_setpin(S3C2410_GPE(14), 0); // IICSCL

      break;

     case 1:

      s3c2410_gpio_setpin(S3C2410_GPE(14), 1); // IICSCL

      break;

     default:

      break;

     }

#endif

#ifdef __80C52__

     if ( 0 == value )

      SCL = 0;

     else if ( 1 == value )

      SCL = 1;

#endif

     return 0;                  

}

/*

得到scl引脚电平,0低电平,1高电平,必须是这两个值,其他函数需要调用。

*/

int get_scl()

{

#ifdef __MINI2440__

     s3c2410_gpio_cfgpin(S3C2410_GPE(14), S3C2410_GPIO_INPUT);

     return s3c2410_gpio_getpin(S3C2410_GPE(14)) > 0 ? 1: 0;

#endif

#ifdef __80C52__

     return SCL;

#endif

}

 

/*

设置sda引脚电平,0低电平,1高电平,其他值无效,返回值一直为0,留着它用。

*/

int set_sda( int value )

{

#ifdef __MINI2440__

     s3c2410_gpio_cfgpin(S3C2410_GPE(15), S3C2410_GPIO_OUTPUT);

     switch( value )

     {

     case 0:

      s3c2410_gpio_setpin(S3C2410_GPE(15), 0); // IICSDA

      break;

     case 1:

      s3c2410_gpio_setpin(S3C2410_GPE(15), 1); // IICSDA

      break;

     default:

      break;

     }

#endif

#ifdef __80C52__

     if ( 0 == value )

      SDA = 0;

     else if ( 1 == value )

      SDA = 1;    

#endif        

     return 0;

}

 

/*

得到sda引脚电平,0低电平,1高电平,必须是这两个值,其他函数需要调用。

*/

int get_sda()

{

#ifdef __MINI2440__

     int sda_pin = 0;

     s3c2410_gpio_cfgpin(S3C2410_GPE(15), S3C2410_GPIO_INPUT);

     sda_pin = s3c2410_gpio_getpin(S3C2410_GPE(15)) > 0 ? 1:0;

     return sda_pin;

#endif

#ifdef __80C52__

     return SDA;

#endif

}

/*

重新发送iic start位,这个是在传送数据过程中使用。

*/

void iic_restart()

{

     set_scl( 0 );

     delay();

     set_sda( 1 );

     delay();

     set_scl( 1 );

     delay();

     set_sda( 0 );

     delay();

     set_scl( 0 );

     delay();

}

/*

发送iic start位,这里假设总线空闲,此时SDL与SCL都为高电平。

*/

void iic_start()

{

     set_sda( 0 );

     delay();

     set_scl( 0 );

     delay();

}

/*

发送stop位,这里假设scl此时为低电平。

*/

void iic_stop()

{

     set_sda( 0 );

     delay();

     set_scl( 1 );

     delay();

     set_sda( 1 );        

     delay();

}

/*

发送一个bit0,这里假设scl此时为低电平,sda电平不定。

*/

void send_bit0()

{

     set_sda( 0 );

     delay();

     set_scl( 1 );

     delay();

     set_scl( 0 );

     delay();

}

/*

发送一个bit 1,这里假设scl此时为低电平,sda电平不定。

 */

void send_bit1()

{

     set_sda( 1 );

     delay();

     set_scl( 1 );

     delay();

     set_scl( 0 );

     delay();

}

/*

接收一个bit位,返回值只能是0或1。

 */

int receive_bit()

{

     int value = -1;

     set_sda( 1 );

     delay();

     set_scl( 0 );

     delay();

     set_scl( 1 );

     delay();

     value = get_sda();

     set_scl( 0 );

     return value;    

}

/*

发送ACK,实际上是发送一个bit0.

*/

void send_ack( )

{

     send_bit0();

}

/*

接收ACK。

*/

int receive_ack()

{

     int ack = 1;

     set_sda( 1 );

     delay();

     set_scl( 1 );

     delay();

     ack = get_sda();        

     delay();

     set_scl( 0 );

     return ack;

}

/*

接收一个字节。

*/

char receive_byte( )

{

     int i = 0;

     int recv_data = 0;

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

     {

      recv_data = recv_data | receive_bit();

      if ( 7 == i )

           break;

      recv_data <<= 1;

     }

     send_ack();

     return recv_data;    

}

/*

接收一个buf,返回值总是为0,它不能保证从器件一定能收到ACK,也不能保证从器件正在工作。

*/

int receive_buf( char *buf, int buf_size )

{

     int i = 0;

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

     {

      buf[i] = receive_byte();

     }

     return buf_size;

}

/*

发送一个字节,返回ACK的值,发送时,没有收到ACK会重试n次,这是常。

 */

int send_byte( char data_byte )

{

     int retry_count = 8;//重试次数。

     int i = 0;

     int ack = 1;        

     char send_data = 0;

     send_data = data_byte;

     do{

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

      {

           if ( 0x80 & send_data )

            send_bit1();

           else

            send_bit0();

           send_data <<= 1;

      }

      ack = receive_ack();

      if ( 0 == ack )

           break;

      send_data = data_byte;

      retry_count --;

     }

     while( retry_count >= 0 );

     return ack;

}

/*

发送一个buf,返回值是成功发送,收到ACK的字节数量。

*/

int send_buf( char *buf, int buf_size )

{

     int i = 0;

     int count = 0;

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

     {

      if (0 != send_byte( buf[i] ) )

           break;

      count ++;

     }

     return count;        

}

 

下面是可以用来读写at24c02的测试代码,在mini2440板测试通过,

static void m24c02_send( int addr, char *buf,  int buf_size)

{

     int count = 0;

     int rev = -1;

    iic_start();

    rev = send_byte( 0xa0 );

    rev = send_byte( (addr >> 0) & 0xff );

    count = send_buf( buf, buf_size );

    iic_stop();

 

}

static void m24c02_recv( int addr , char *buf, int buf_size )

{

      iic_start();

    send_byte( 0xa0 );

    send_byte( (addr >> 0) & 0xff );

      iic_restart();

    send_byte( 0xa1 );

    receive_buf( buf, buf_size );

    iic_stop();



 

 

(1)基础宏定义

#define GPIO_SCL             S3C2410_GPF3
#define GPIO_SDA             S3C2410_GPF0
#define GPIO_SDA_OUTP   S3C2410_GPF0_OUTP  //设定SDA输出
#define GPIO_SDA_INP      S3C2410_GPF0_INP     //设定SDA输入
#define GPIO_SCL_OUTP   S3C2410_GPF3_OUTP  //设定SCL输出

void I2C_SCL_OUTP( void )
{
      s3c2410_gpio_cfgpin(GPIO_SCL,GPIO_SCL_OUTP);
}

void I2C_SCL_Output(u8 value)
{
      if(value)
      {                                                
            s3c2410_gpio_setpin(GPIO_SCL,value);  
      }
      else
      {
            s3c2410_gpio_setpin(GPIO_SCL,value );  
      } 
}

void I2C_SDA_Mode(u8 v_mode)   //SDA输出方向
{
       if(v_mode)
       {                                                
              s3c2410_gpio_cfgpin(GPIO_SDA, GPIO_SDA_OUTP);   
       }
       else
       {
              s3c2410_gpio_cfgpin(GPIO_SDA, GPIO_SDA_INP);   
       }
}

void I2C_SDA_Output(u8 value)
{
       if(value)
       {                                                
               s3c2410_gpio_setpin(GPIO_SDA,value);  
       }
       else
       {
                s3c2410_gpio_setpin(GPIO_SDA,value );  
       }
}

u8 I2C_SDA_Read(void)    //SDA读数据
{
       return s3c2410_gpio_getpin(GPIO_SDA);  
}

(2)基础段

void I2C_Init(void)

      I2C_SDA_Output(1);
      I2C_SCL_Output(1);      //默认拉高
}

void I2C_Wait(void)
{
      u16 i;
      for(i=0;i<200;i++);
}

void I2C_Start(void) 
{
      I2C_SDA_Output(1);
      I2C_SCL_Output(1);
      I2C_Wait();
      I2C_SDA_Output(0);
      I2C_Wait();
      I2C_SCL_Output(0);
}
void I2C_Stop(void) 
{
      I2C_SDA_Output(0);
      I2C_Wait();
      I2C_SCL_Output(1);
      I2C_Wait();
      I2C_SDA_Output(1);
}

 

(3)读写单个字节的段

u8 I2C_Send_Byte(u8 bytedata) 
{
      u8 i,ack;
      I2C_SDA_Mode(1);  //SDA输出
      I2C_SCL_OUTP(); 

      for (i = 0; i < 8; i++)  
      {
              if (bytedata & 0x80) 
              {
                     I2C_SDA_Output(1);
              }
              else
              {
                    I2C_SDA_Output(0);
              }
              bytedata <<= 1;
             
              I2C_SCL_Output(1);
              udelay(3);
              I2C_SCL_Output(0);
              udelay(1);
       }     
   
        I2C_SDA_Output(1);  //release
        udelay(3);
        
        I2C_SDA_Mode(0);  //设定SDA输入
        I2C_SCL_Output(1);   
        udelay(3);
        ack = I2C_SDA_Read();   //读应答
        I2C_SDA_Mode(1); 
        I2C_SCL_Output(0); 
        udelay(3);
       
        return ack;   
}

u8 I2C_Receive_Byte(void)  
{
       u8 i;
       u8 bytedata = 0x00;
       u8 temp;

       I2C_SDA_Mode(0); 
       for ( i = 0; i < 8; i++)
       {
             I2C_SCL_Output(1);
             udelay(3);

             bytedata <<= 1;
             temp = I2C_SDA_Read(); 
             printk("reda SDA'value is:%d\n",temp);

             if (temp)
                   bytedata |= 0x01; 
             printk("  bytedata is:%x\n",bytedata);
             I2C_SCL_Output(0);
             udelay(1);
       }
       I2C_SDA_Mode(1); 
       return bytedata;
}

(4)读写单个字节的I2C应用函数

u8 I2C_Byte_Write(u8 device_ID,u8 address,u8 bytedata)
{   
       u8 ack;
       printk("device_ID is:%x\n",device_ID);
       printk("address is:%x\n",address);
       printk("date is:%x\n",bytedata);
       I2C_Start();  
       ack=I2C_Send_Byte(device_ID);
       printk("ack is:%d\n",ack);
       if(ack) 

             I2C_Stop();
       I2C_Send_Byte(address);
       I2C_Send_Byte(bytedata);
       I2C_Stop();
       I2C_Wait(); 
       return 0;
}

u8 I2C_Byte_Read(u8 device_ID,u8 address)
{   
       u8 bytedata;

       I2C_Start();
       I2C_Send_Byte(device_ID);
       I2C_Send_Byte(address);
       I2C_Start();
       I2C_Send_Byte(device_ID+1);
       bytedata = I2C_Receive_Byte();  //读单个字节,不需要再发应答
       I2C_Stop();    
       return bytedata;
}

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: