×
单片机 > 单片机程序设计 > 详情

标准AVR单片机模拟I2C总线的主机程序

发布时间:2020-05-27 发布时间:
|

.H文件预处理

 

typedef unsigned char INT8U;   //0~255

typedef signed char INT8S;     //-128~127

typedef unsigned int INT16U;   //0~65535

typedef signed int INT16S;     //-32768~32767

typedef unsigned long INT32U;  //0~0xFFFFFFFF

typedef signed long INT32S;    //0x8000 0000~7FFFFFFF

typedef float FP32;           //Single precision floating point

typedef double FP64;         //Double precision floating point

 

 

#define XTAL 1  //晶振频率,单位MHz

#define m_delayus(x)   __delay_cycles((unsigned long)(x*XTAL))

#define m_delayms(x)  __delay_cycles((unsigned long)(x*XTAL*1000UL))

#define m_delays(x)    __delay_cycles((unsigned long)(x*XTAL*1000000UL))

 


#define BIT0 0x01

#define BIT1 0x02

#define BIT2 0x04

#define BIT3 0x08

#define BIT4 0x10

#define BIT5 0x20

#define BIT6 0x40

#define BIT7 0x80

//假设SCL是端口PD6,SDA是端口PD7,WP是端口PD5

#define m_EnE2pWrite   PORTD&=~BIT5  //允许EEPROM读写

#define m_DiE2pWrite   PORTD|=BIT5    //EEPROM只读

#define m_I2CWritePort  DDRD|=BIT7    //I2C写时,SDA端口方向为输出

#define m_I2CReadPort  DDRD&=~BIT7   //I2C读时,SDA端口方向为输入

#define m_SetSCL  PORTD|=BIT6        //PD6=1,SCL置位

#define m_ClrSCL  PORTD&=~BIT6       //PD6=0,SCL清0

#define m_SetSDA  PORTD|=BIT7        //PD7=1,SDA置位

#define m_ClrSDA  PORTD&=~BIT7       //PD7=0,SDA清0

#define m_SDAIn  (PIND&BIT7)          //SDA读入 

#define m_I2C_Delay  m_delayus(5)      //I2C延时,不同器件可能不同

#define m_I2C_StopDelay  m_delayms(10)  //在下一次产生Start之前,总线空闲时间

 

 

 

void I2C_Start();//产生I2C总线的起始状态

void I2C_Write(INT8U dat);//向I2C总线写1个字节的数据

INT8U I2C_Read();//从从机读取1个字节的数据

INT8U I2C_GetAck();//读取从机应答位

void I2C_PutAck(INT8U ack);//主机产生应答位或非应答位

void I2C_Stop();//产生I2C总线的停止状态

INT8U I2C_Puts(INT8U SlaveAddr,INT16U SubAddr,INT8U SubMod,INT8U *dat,INT16U Size);//I2C总线综合发送函数,向从机发送多个字节的数据

INT8U I2C_Gets(INT8U SlaveAddr,INT16U SubAddr,INT8U SubMod,INT8U *dat,INT16U Size);//I2C总线综合接收函数,从从机接收多个字节的数据

INT8U I2C_DigitalPot(INT8U SlaveAddr,INT8U dat);//I2C总线数字电位器发送程序


.C文件

 

void I2C_Start()

{

  m_I2CWritePort;  //端口方向为输出

  m_SetSDA;

  m_I2C_Delay;

  m_SetSCL;

  m_I2C_Delay;

  m_ClrSDA;

  m_I2C_Delay;

  m_ClrSCL;

  m_I2C_Delay;

}

 

void I2C_Write(INT8U dat)

{

  m_I2CWritePort;  //端口方向为输出

  for(INT8U t=0;t<8;t++)

  {

    if ((dat&0x80)!=0)

    {

      m_SetSDA;  //SDA=1

    }

    else

    {

      m_ClrSDA;  //SDA=0

    }

    m_I2C_Delay;

    dat<<=1;

    m_SetSCL;  //SCK=1

    m_I2C_Delay;

    m_ClrSCL;  //SCK=0

    m_I2C_Delay;

  }

}

 

INT8U I2C_Read()

{

  INT8U dat;

  m_I2CReadPort;  //端口方向为输入

  for(INT8U t=0;t<8;t++)

  {

    m_SetSCL;  //SCK=1

dat<<=1;

    if(m_SDAIn)

{

  dat|=0x01;

}

m_ClrSCL;  //SCK=0

m_I2C_Delay;

  }

  return dat;

}


INT8U I2C_GetAck()

{

  INT8U ack=0;

  m_I2CReadPort;  //端口方向为输入

  //总线准备,接受应答

  m_SetSDA;  

  m_I2C_Delay;

  m_SetSCL;  

  m_I2C_Delay;

  if(m_SDAIn!=0)

    ack = 1;

  m_ClrSCL;

  m_I2C_Delay;

  return ack;

}


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

函数:I2C_PutAck()

功能:主机产生应答位或非应答位

参数:

ack=0:主机产生应答位; ack=1:主机产生非应答位

说明:

主机在接收完每一个字节的数据后,都应当产生应答位

主机在接收完最后一个字节的数据后,应当产生非应答位

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

void I2C_PutAck(INT8U ack)

{

  m_I2CWritePort;  //端口方向为输出

  if(ack==0)

  {

    m_ClrSDA;

  }

  else

  {

    m_SetSDA;

  }

  m_I2C_Delay;

  m_SetSCL;    

  m_I2C_Delay;

  m_ClrSCL;    

  m_I2C_Delay;

  m_SetSDA;

}

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

函数:I2C_Stop()

功能:产生I2C总线的停止状态

说明:

SCL处于高电平期间,当SDA出现上升沿时停止I2C总线

不论SDA和SCL处于什么电平状态,本函数总能正确产生停止状态

本函数执行后,I2C总线处于空闲状态

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

void I2C_Stop()

{

  m_I2CWritePort;  //端口方向为输出

  m_ClrSDA;  

  m_I2C_Delay;

  m_SetSCL;  

  m_I2C_Delay;

  m_SetSDA;

  m_I2C_StopDelay;

}


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

函数:I2C_Puts()

功能:I2C总线综合发送函数,向从机发送多个字节的数据

参数:

SlaveAddr:从机地址(7位纯地址,不含读写位,0xxx xxxx)

SubAddr:从机的子地址

SubMod:子地址模式,0-无子地址,1-单字节子地址,2-双字节子地址

*dat:要发送的数据

Size:数据的字节数

返回:

0:发送成功

1:在发送过程中出现异常

说明:

本函数能够很好地适应所有常见的I2C器件,不论其是否有子地址

当从机没有子地址时,参数SubAddr任意,而SubMod应当为0

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

INT8U I2C_Puts(INT8U SlaveAddr,INT16U SubAddr,INT8U SubMod,INT8U *dat,INT16U Size)

{

  m_EnE2pWrite;     //允许EEPROM读写

  INT8U a[3];

  if (Size==0) return 1;  //检查长度,在接收过程中出现异常

  a[0]=(SlaveAddr<<1);  //准备从机地址

  if (SubMod>2) SubMod = 2;  //检查子地址模式

 

  //确定子地址

  switch (SubMod)

  {

  case 0:

    break;

  case 1:

    a[1]=(INT8U)(SubAddr);

    break;

  case 2:

    a[1]=(INT8U)(SubAddr >> 8);

    a[2]=(INT8U)(SubAddr);

    break;

  default:

    break;

  }


  //主机发送从机地址(a[0]),接着发送子地址(如果有子地址的话)(a[1],a[2])

  I2C_Start();

  for (INT8U t=0;t<=SubMod;t++)

  {

    I2C_Write(a[t]);

    if (I2C_GetAck())

    {

      I2C_Stop();

      m_DiE2pWrite; //EEPROM只读

      return 1;

    }

  }

 

  //主机发送数据

  while(Size--)

  {

    I2C_Write(*dat++);

    if (I2C_GetAck())

    {

      I2C_Stop();

      m_DiE2pWrite; //EEPROM只读

      return 1;

    }

  }

 

  //发送完毕,停止I2C总线,并返回结果

  I2C_Stop();

  m_DiE2pWrite; //EEPROM只读

  return 0;//发送成功

}


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

函数:I2C_Gets()

功能:I2C总线综合接收函数,从从机接收多个字节的数据

参数:

SlaveAddr:从机地址(7位纯地址,不含读写位,0xxx xxxx)

SubAddr:从机的子地址

SubMod:子地址模式,0-无子地址,1-单字节子地址,2-双字节子地址

*dat:保存接收到的数据

Size:数据的字节数

返回:

0:接收成功

1:在接收过程中出现异常

说明:

本函数能够很好地适应所有常见的I2C器件,不论其是否有子地址

当从机没有子地址时,参数SubAddr任意,而SubMod应当为0

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

INT8U I2C_Gets(INT8U SlaveAddr,INT16U SubAddr,INT8U SubMod,INT8U *dat,INT16U Size)

{

  m_EnE2pWrite;     //允许EEPROM读写

  INT8U a[3];

  if(Size==0) return 1;//检查长度,在接收过程中出现异常

  a[0]=(SlaveAddr<<1);//准备从机地址

  if(SubMod>2) SubMod=2;//检查子地址模式

 

 //如果是有子地址的从机,则主机要先发送从机地址和子地址

  if(SubMod!=0)

  {

    //确定子地址

    if(SubMod==1)

    {

      a[1]=(INT8U)(SubAddr);

    }

    else

    {

      a[1]=(INT8U)(SubAddr>>8);

      a[2]=(INT8U)(SubAddr);

    }

    //发送从机地址写,接着发送子地址

    I2C_Start();

    for(INT8U t=0;t<=SubMod;t++)

    {

      I2C_Write(a[t]);

      if(I2C_GetAck())

      {

        I2C_Stop();

        m_DiE2pWrite; //EEPROM只读

        return 1;

      }

    }

  }

 

  //这里的I2C_Start()对于有子地址的从机是重复起始状态

  //对于无子地址的从机则是正常的起始状态

  I2C_Start();

  //发送从机地址读

  I2C_Write(a[0]+1);

  if(I2C_GetAck())

  {

I2C_Stop();

m_DiE2pWrite; //EEPROM只读

    return 1;

  }

 

  //主机接收数据

  while(1)

  {

    *dat++ = I2C_Read();

    if(--Size==0)

    {

      I2C_PutAck(1);

      break;

    }

    I2C_PutAck(0);

  }

 

  //接收完毕,停止I2C总线,并返回结果

  I2C_Stop();

  m_DiE2pWrite; //EEPROM只读

  return 0;

}

 


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

函数:I2C_DigitalPot()

功能:I2C总线数字电位器发送程序

参数:

SlaveAddr:从机地址(7位纯地址,不含读写位,0xxx xxxx)

dat:要发送的数据

返回:

0:发送成功

1:在发送过程中出现异常

说明:

本函数能够很好地适应常见的数字电位器,如AD5245,AD5241

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

INT8U I2C_DigitalPot(INT8U SlaveAddr,INT8U dat)

{

  I2C_Start();

 

  //主机发送从机地址

  I2C_Write(SlaveAddr<<1);

  if(I2C_GetAck())

  {

    I2C_Stop();

    return 1;

  }

 

  //主机发送控制字

  I2C_Write(0x00);

  if(I2C_GetAck())

  {

    I2C_Stop();

    return 1;

  }

 

  //主机发送数据

  I2C_Write(dat);

  if(I2C_GetAck())

  {

    I2C_Stop();

    return 1;

  }

 

  //接收完毕,停止I2C总线,并返回结果

  I2C_Stop();

  return 0;

}


使用范例:

1.  写EEPROM,从机地址1010 000?,子地址0x00(单字节),发送数据a (2字节)

 I2C_Puts (0x50,0, 1, &a, 2);

 

2. 读EEPROM,从机地址1010 000?,子地址0x00(单字节),接受数据b (2字节)

 I2C_Gets (0x50,0, 1, &b, 2);

 

3.  写数字电位器,地址0101 100?,写入数据0xFF

 I2C_DigitalPot ( 0x2C, 0xFF);


I2C读写EEPROM流程图


关键字:AVR单片机  模拟I2C总线  主机程序 

『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
STM32中断向量表的位置.重定向