youzizhile

【嵌入式】如何利用MCU的GPIO的进行I2C总线主接口设计

0
阅读(9795)
     在现代电子系统中,有为数众多的IC需要进行相互之间以及与外界的通信。为了简化电路的设计,Philips公司开发了一种用于内部IC控制的简单的双向两线串行总线PC (Intel-Integrated Circuit bus)。该总线具有接口线少、通讯效率高等特点。

    在进行基于MCU的电路设计时,经常需要和外围提供I2C接口的芯片通信,虽然市场上有专用I2C总线接口芯片,但是地址可选范围小、性能指标固定、功能单一、使用不方便。根据I2C总线的电气特性及其通讯协议,利用MCU的GPIO可以很方便地实现I2C总线的通讯接口,且具有高速、易调试、可以灵活地实现地在线配置等优点,同时大大地减少了系统的开发周期。

    I2C总线系统由两根总线即SCL(串行时钟)线和SDA(串行数据)线构成。I2C总线主从器件之间传送的一次数据称为一帧,由启动信号、地址码、若干数据字节、应答位以及停止信号等组成。通讯启动时,主器件发送一个启动信号(当SCL线上是高电平时,SDA线上产生一个下跳沿)、从器件的地址(唯一的7位地址码)和1位读写方向标志位;通讯停止时,主器件发送一个停止信号(当SCL线上是高电平时,SDA线上产生一个上跳沿)。在数据传送过程中。当SCL线上是高电平时,必须保证SDA线上的数据稳定,传完一个字节的数据,必须由从器件送回一个应答信号。总线的传输速率为100kbit/s(标准)-400kbit/s(快速)。

    具体的协议细节可以参考I2C标准协议规范进行查看。以下的连接为I2C标准协议规范,感兴趣的可以下载。

    http://pan.baidu.com/share/link?shareid=482125&uk=3355540905

    本文利用新唐的M051单片机来模拟I2C总线,可以实时更新I2C器件的地址,并根据不同的器件地址宽度或者数据宽度进行更新程序顶层应用,很方便的实现想要的功能。本模拟程序已成功应用于各个项目中。

    1.先定义宏变量,方便使用。

#define Set_SDA  DrvGPIO_SetBit(E_PORT2, 6) //设置p2.6为SDA管脚 并置1
#define Clr_SDA  DrvGPIO_ClrBit(E_PORT2, 6) //SDA置0
#define Set_SCL  DrvGPIO_SetBit(E_PORT2, 5) //设置p2.6为SCL管脚 SCL置1
#define Clr_SCL  DrvGPIO_ClrBit(E_PORT2, 5)// SCL置0

#define  SET_SDA_OUTPUT    DrvGPIO_Open(E_PORT2, 6,E_IO_OUTPUT) //SDA管脚设置为输出状态
#define  SET_SDA_INPUT     DrvGPIO_Open(E_PORT2, 6,E_IO_INPUT)  //SDA管脚设置为输入状态
#define  GET_SDA_DATA      DrvGPIO_GetBit(E_PORT2, 6)                  //读取SDA管脚数据状态

    2.START状态的 实现

void I2C_Start(void)
{
/*   SDA -------_________
     SCL ------------____ */
    SET_SDA_OUTPUT;
  Set_SDA;  
  Set_SCL;     
  delay(DELAY_TIME);
  Clr_SDA;    
  delay(DELAY_TIME);
  Clr_SCL;      
  delay(DELAY_TIME);
}
3.发送单个字节

    void I2C_Send_Byte(unsigned char data)
{   
/*                 MSB
    SDA  ______-------------__________
    SCL  _________-------_____________ */ 
   unsigned char i = 8; 
   SET_SDA_OUTPUT;
   Clr_SDA;
   Clr_SCL;
   delay(DELAY_TIME);
   while( i-- )
   { 
     if (data&0x80) 
        Set_SDA;           
     else 
        Clr_SDA;    
     delay(DELAY_TIME);
     Set_SCL;   
     delay(DELAY_TIME);
     Clr_SCL; 
     delay(DELAY_TIME);
     Clr_SDA;  
     delay(DELAY_TIME);
     data = data << 1;
   }
}

4.检查应答位


void Check_Ack(void)
{
 /*   SDA ____zzzzzzzzzzzzz
      SCL ____------_______ */
   unsigned char a=1;
   SET_SDA_INPUT;
   a = 1;
   Set_SCL;   // SCL = 1
   delay(DELAY_TIME); 
   a=GET_SDA_DATA;
   delay(DELAY_TIME); 
  if(a==1)
    uart_printf("not receive ack\r\n");
  else
    uart_printf("receive ack\r\n");
   while(a)
   {
      a=GET_SDA_DATA;
     delay(DELAY_TIME);
   } 
   Clr_SCL;
   SET_SDA_OUTPUT;
   Clr_SDA;
   delay(DELAY_TIME);
}

5.停止位实现

void I2C_Stop(void)
{
 /*   SDA _________--------
      SCL ____------------- */
  SET_SDA_OUTPUT;
  delay(DELAY_TIME);
  Clr_SDA; 
  Clr_SCL;  
  delay(DELAY_TIME);
  Set_SCL; 
  delay(DELAY_TIME);
  Set_SDA; 
  delay(DELAY_TIME);
}
6.接收一个字节

unsigned char I2C_Receive_Byte(void)
{
   unsigned char rn = 0, data=0,rr=0x80;
   SET_SDA_INPUT;
   Clr_SCL;
   delay(DELAY_TIME);
   for(rn=0;rn<8;rn++)
   {
      Set_SCL;
      delay(DELAY_TIME);
    if( GET_SDA_DATA)
          data|=rr;
      else
          data&=~rr;
      delay(DELAY_TIME);
      rr>>=1;
      Clr_SCL;
      delay(DELAY_TIME); 
   }
   delay(DELAY_TIME);
   return data;
}

7.N_ack实现

void I2C_Nack(void)
{
 /*   SDA ___--------_____
      SCL ____------_______ */
  SET_SDA_OUTPUT;
  Set_SDA;
  delay(DELAY_TIME);
  Set_SCL;
  delay(DELAY_TIME);
  Clr_SCL;
  delay(DELAY_TIME);
  Clr_SDA;
  delay(DELAY_TIME);
}

8.延时函数

void delay(unsigned int value)
{
   unsigned int i;
   for(i=0;i<value;i++)
      ;
}


几个基本的函数实现了,现在针对不同的器件来实现不同的扩展应用。


1 读写 24C64系列EEPROM

unsigned char write_e2prom(unsigned int addr, unsigned char wdata)
{    

        unsigned char H_ADD,L_ADD;
    H_ADD=addr<<8;
    L_ADD=addr&0xff;

    I2C_Start();
    I2C_Send_Byte(0xa0);//24C64地址
    Check_Ack();
    I2C_Send_Byte(H_ADD); //地址高字节
    Check_Ack();
    I2C_Send_Byte(L_ADD);//地址
    Check_Ack();
    I2C_Send_Byte(wdata);
    Check_Ack();
    I2C_Stop();
}

unsigned char read_e2prom(unsigned int rdaddr)
{

    unsigned char rdata;
     unsigned char H_ADD,L_ADD;
    H_ADD=addr<<8;
    L_ADD=addr&0xff;   //16位地址宽度
    I2C_Start();
    I2C_Send_Byte(0xa0);
    Check_Ack();
    I2C_Send_Byte(H_ADD);
    Check_Ack();
    I2C_Send_Byte(L_ADD);
    Check_Ack();
    I2C_Start();
    I2C_Send_Byte(0xa1);
    Check_Ack();
    rdata = I2C_Receive_Byte();
    I2C_Nack();
    I2C_Stop(); 
    return(rdata);
}
2.读写ADV7611

void write_adv7611register(unsigned char device_addr,unsigned char register_addr,unsigned char udata)
{
    I2C_Start();
    I2C_Send_Byte(device_addr);
    Check_Ack();
    I2C_Send_Byte(register_addr);
    Check_Ack();
    I2C_Send_Byte(udata);
    Check_Ack();
    I2C_Stop();
}
unsigned char read_adv7611register(unsigned char wr_device_addr,unsigned char register_addr)
{

unsigned char rdata=0;
    I2C_Start();
    I2C_Send_Byte(wr_device_addr); //8位地址宽度
    Check_Ack();
    I2C_Send_Byte(register_addr);
    Check_Ack();
    I2C_Start();
    I2C_Send_Byte(wr_device_addr|0x01);
    Check_Ack();
    rdata = I2C_Receive_Byte();
    I2C_Nack();
    I2C_Stop();
    return(rdata);
}

根据不同的应用范围可以根据实际情况调整delay的参数值改变I2C的速率。而对于不同的器件的地址和数据宽度,可以自由扩展,顶层应用,在实际项目使用中方便,并且易于调试。