I2C的主机从机模拟

时间:2023-03-09 16:22:34
I2C的主机从机模拟

好久没有在****上面做笔记了,主要是最近琐碎的事情太多,乱七八糟的事情让自己不能坚定下来做自己喜欢做的事情。上了星期花了两天的时间模拟了I2C的主机和从机通信。一般都是主机模拟,从机直接用硬件I2C的,但是由于所谓的项目里面没有I2C,但是要用到I2C了,因此就不得不用I/O口去模拟I2C了。

1、I2C协议

  I2C的协议相信网上已经有很多资料了,这里就不做详细介绍,只做简单说明即可。  

  a、I2C协议有两根总线:SDA和SCL。SDA为数据线,而SCL就是主机的时钟线。

  b、I2C是主机控制从机,时钟线只能主机改变。

  c、每个从机都有唯一的地址,主机通过发送从机地址来选择从机。

  d、I2C开始信号:SCL为高电平的时候,SDA由高电平向低电平跳变。

  e、I2C结束信号:SCL为高电平的时候,SDA由低电平向高电平跳变。

  f、主机传输信号的时候,SCL为高电平的时候,传输信号,SCL为低电平的时候改变信号。

  g、主机接收信号的时候,SCL为高电平的时候,接收信号。

2、如果用I/O模拟I2C的时候,一定要记住,是主机控制从机,从机根据主机SCL信号的改变而改变。

3、主机代码:

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

I2C模拟条件:
1、HOST先发地址和控制命令给SLAVE;
2、地址和控制命令占一个字节;
3、字节格式:
    7~2             1                     0
   地址       单/多字节(0/1)       读/写(1/0)
4、发送多字节时候,第一个字节是地址和控制命令、第二个字节是长度、接下来是数据
5、发送多字节时候,第一个字节是地址和控制命令、第二个字节是要发送的

******************************************************************************/
#include "ioCC1110.h"
#include "hal.h"

#define SCL               P1_2
#define SDA               P1_3
#define IN                  0
#define OUT               1
BYTE ACK_Flag = ;
BYTE I2C_count;//计数器
BYTE receive_slave[] = {0x00}; //接收从机的字节
BYTE send_slave[] = {0xaa,0x55,0xbb,0x55,0xaa}; //发送字节给从机
/*初始化I2C*/

void SDA_(BYTE input)
{
    )        //SDA输出,p1.3
        P1DIR |= 0X08;
    else
        P1DIR &= 0XF7;    //SDA输入,p1.3
}

void SCL_(BYTE input)
{
    )        //SCL输出,P1.2
        P1DIR |= 0X04;
    else                  //SCL输入,P1.2
        P1DIR &= 0XFB;
}
/*启动I2C工作*/
void START_I2C(void)
{
  SDA = ;
  SCL = ;
//  Delay_us(20);  //这个没有多大影响,可以不要
  SCL = ;
  Delay_us();    //最开始50,5us太短了,不能判断,10us可以。
  SDA = ;
  Delay_us();   //最开始50,
  SCL = ;
  Delay_us();  //最开始50,这个延时和上面的延时可以不要,但是为了SLAVE有足够时间退出中断,就加上

}

/*停止I2C工作*/
void STOP_I2C(void)
{
//   SDA_OUT;
   SDA = ;
   Delay_us();;
   SCL = ;
   Delay_us();;
   SDA = ;
   Delay_us();;
   SCL = ;
   Delay_us();;
}

/*收到从器件的ACK帧,用于写完一个字节后检查*/
void Receive_SLAVE_ACK(void)
{
        SCL = ;
//        Delay_us(50);     //这里没有必要
        SDA = ;
        SDA_(IN);
        SCL = ;
        Delay_us();      //15us短了,经常出错

         == SDA)    // 若SDA=1表明非应答,置位非应答标志ACK_Flag
                ACK_Flag = ;
        SDA_(OUT);
        SCL = ;
//        Delay_us(50); //这里也没有必要
}

/*主器件往从器件里写一个字节*/
void WriteByte(BYTE writedata)
{
//SDA_OUT;
  SCL = ;            //SCL为低电平的时候可以改变数据状态
  ;i<;i++){
    )&0x01) == 0x01){//先写最高位
      SDA = ;
    }
    else{
      SDA = ;
    }
//    Delay_us(10);              //这个可以不要
    SCL = ;
    Delay_us();               //这个是等待SLAVE进入中断并接收数据,退出中断,15us不行,要20us
    writedata = writedata << ;//写完一位后将低位移到高位
    SCL = ;
//    Delay_us(50);            //这个也可以不要
  }
//    Delay_us(50);           //这个其实可以不要
    SCL = ;
}

/*主器件从从器件里面读取一个字节*/
BYTE ReadByte(void)
{
  BYTE TempData = ;
  SCL = ;
  ;i<;i++){
    SDA = ;
    SDA_(IN);
//    Delay_us(10);           //这个没有什么影响
    SCL = ;
    Delay_us();           //这个时间不能太短,不然的话就会读错1位
    TempData <<= ;
     == SDA)
        TempData |= 0x01;
    else
        TempData |= 0x00;
    SCL = ;
  }
  SCL = ;
  SDA_(OUT);
//  Delay_us(50);
  return (TempData);
}

void I2C_Bytes_Test(void)
{

/***********************写单字节正常*****************************/
#if 0
    START_I2C();
    WriteByte(0xa4);//写单字节的命令
    Receive_SLAVE_ACK();
    ){
        return;
    }
    WriteByte(0xaa);
    Receive_SLAVE_ACK();
    ){
      return;
    }
    STOP_I2C();
#endif
/***********************************************************/
 /*****************************写多字节********************/
#if 0
    START_I2C();
    WriteByte(0xa6);//写字多节的命令
    Receive_SLAVE_ACK();
    ){
        return;
    }
    WriteByte(0x05);//写多字节长度
    Receive_SLAVE_ACK();
    ){
        return;
    }
    ;i<;i++){   //开始写多字节
       WriteByte(send_slave[i]);
       Receive_SLAVE_ACK();
       ){
          return;
      }
    }
    STOP_I2C();
#endif
/**********************************************************/
/*****************I2C读正常**************************/

    START_I2C();
    WriteByte(0xa5);
    Receive_SLAVE_ACK();
    ){

      return;
    }
    WriteByte(0x03);            //读的长度
    Receive_SLAVE_ACK();
    ){
      return;
    }
    ;i<;i++){
        receive_slave[i] = ReadByte();
        Receive_SLAVE_ACK();
        )
          return;
//        UART1_Send_BYTE(receive_slave[i]);
    }
    STOP_I2C();
    LED0 = ;
    UART1_Send_String(receive_slave,);

/**********************************************************/
}

4、从机是在中断里面接收的,每次SCL上升沿的时候进入中断。代码:

#define START_STATE  0           //开始
#define CONTROL_STATE 1        //控制命令
#define ACK_STATE    2           //ACK应答
#define NOACK_STATE 3        //非ACK应答
#define WRITE_STATE 4       //写从机
#define READ_STATE 5        //读从机
#define STOP_STATE 6        //停止

BYTE STATE = ;
BYTE address = ;          //接收到的从机地址
BYTE count = ;            //接收到一位计数,产生一个字节的计数
BYTE receive_BYTE;         //从机接收主机单个字节
BYTE receive_buf[] = {0x00};         //从机接收主机数据的buffer
BYTE write_buf[] = {0x47,0x55,0x11};//从机发送数据给主机的buffer
BYTE receive_len = ;     //从接接收主机多字节时的长度
BYTE send_len = ;        //从机要发送给主机字节的长度
BYTE write_end = ;       //主机是否对从机写完
BYTE read_end = ;        //主机是否接收完从机发送的数据
BYTE length_ACK = ;      //从机是否接收到了要发送数据给主机的长度
BYTE read_num = ;
BYTE Temp = ;    

void PORT1_InterruptInit(void)
{
  EA = ;
  P1DIR &= 0XFB;//P1.2输入,scl
  IEN2 |= 0X10;//P1中断使能,scl
  P1IEN |= 0x04;//P1.2中断使能,scl
  PICTL &= ~0X02; //P1上升沿中断,0:rising edge
  P1IFG &= ~0x04;
}

#pragma vector = P1INT_VECTOR
 __interrupt void P1_ISR(void)
{
/*        if(P1IFG>0)         //按键中断
        {
          P1IFG = 0;
          LED1 = ~LED1;
        }
        P1IF = 0;          //清中断标志
 */
   )
   {
     P1IFG = ;
     switch(STATE){
        case START_STATE:
              SDA_(IN);
              if(SDA){
                  while(SDA);
                  while(SCL);
                  STATE = CONTROL_STATE; 

              }
              break;
        case CONTROL_STATE:
              address <<= ;
               == SDA)
                  address |= 0x01;
              else
                  address |= 0x00;
              count++;
               == count){
                  count = ;  

                  if((address & 0xfc) == 0xa4){
                      STATE = ACK_STATE;
                  }
                  else
                      STATE = NOACK_STATE;
              }
              break;
        case ACK_STATE:

              SDA_(OUT);//SDA设置为输出
              SDA = ;
              ) || (read_end == ) ){  //已经读完或者写完
                  STATE = STOP_STATE;
                  write_end = ;
                  read_end = ;
              }
              else{
                  if((address & 0x01) == 0x00)       //主机写SLAVE
                      STATE = WRITE_STATE;
                  else{
                      STATE = READ_STATE;
//                      UART1_Send_BYTE(STATE);
                  }
              }
              break;
        case NOACK_STATE:
              SDA_(OUT);
              SDA = ;
              address = ;
                  STATE = START_STATE;
              break;
        case WRITE_STATE:                      //主机写从机
              SDA_(IN);                        //这里将SDA置为输入,是因为发送ACK时候置为输出了
              if((address & 0x02) == 0x00){    //写单字节
                  receive_BYTE <<= ;
                   == SDA)
                      receive_BYTE |= 0X01;
                  else
                      receive_BYTE |= 0X00;

                  count++;
                   == count){
                      STATE = ACK_STATE;
//                      UART1_Send_BYTE(receive_BYTE);
                      count = ;
                      write_end = ;
                  }
              }
              else{
                  receive_buf[receive_len] <<= ;
                   == SDA)
                      receive_buf[receive_len] |= 0x01;
                  else
                      receive_buf[receive_len] |= 0x00;
                  count++;
                   == count){                     //接收到了8个字节
                      count = ;
                      receive_len++;
                      UART1_Send_BYTE(receive_buf[receive_len-]);
                      ]+)){  //这里+1是因为要先写长度
                          write_end = ;
                          receive_len = ;
                      }
                      STATE = ACK_STATE;
                  }
              }
              break;
        case READ_STATE:                            //主机读从机
              if(!length_ACK){                      //主机发送过从机长度

                  SDA_(IN);
                  send_len <<= ;
                   == SDA)
                      send_len |= 0x01;
                  else
                      send_len |= 0x00;

                  count++;
                   == count){
                      length_ACK = ;           //主机发送长度给从机
                      LED0 = ;
//                      UART1_Send_BYTE(send_len);
                      count = ;
                      STATE = ACK_STATE;
                  }
              }
              else{
                  SDA_(OUT);
                  Temp = write_buf[read_num];
                  Temp <<= count;
                  UART1_Send_BYTE(Temp);
                  if((Temp & 0x80) == 0x80)
                      SDA = ;
                  else
                      SDA = ;
                  count++;
                  ){    //移了7位,正好读一个字节
                      count = ;
                      read_num++;
                      if(read_num >= send_len){//读完了所有数据
                          read_num = ;
                          read_end = ;
                          length_ACK = ;    //将接收长度置0
                      }
                      STATE = ACK_STATE;
                  }
              }
              break;
        case STOP_STATE:
              SDA_(IN);
              while(!SDA);
              address = ;
//              receive_BYTE = 0;
              receive_len = ;
              send_len = ;
              LED1 = ~LED1;
              STATE = START_STATE;
              break;
        default:
              break;
     }
   }
   P1IF = ;
 }