I2C的主机从机模拟

来源:互联网 发布:js代码 引号 换行 编辑:程序博客网 时间:2024/05/16 14:23

     好久没有在csdn上面做笔记了,主要是最近琐碎的事情太多,乱七八糟的事情让自己不能坚定下来做自己喜欢做的事情。上了星期花了两天的时间模拟了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               1BYTE ACK_Flag = 0;BYTE I2C_count;//计数器BYTE receive_slave[100] = {0x00}; //接收从机的字节BYTE send_slave[5] = {0xaa,0x55,0xbb,0x55,0xaa}; //发送字节给从机/*初始化I2C*/void SDA_(BYTE input){    if(input == 1)        //SDA输出,p1.3        P1DIR |= 0X08;    else        P1DIR &= 0XF7;    //SDA输入,p1.3}void SCL_(BYTE input){    if(input == 1)        //SCL输出,P1.2        P1DIR |= 0X04;    else                  //SCL输入,P1.2        P1DIR &= 0XFB;    }/*启动I2C工作*/void START_I2C(void){  SDA = 1;           SCL = 0;//  Delay_us(20);  //这个没有多大影响,可以不要  SCL = 1;  Delay_us(10);    //最开始50,5us太短了,不能判断,10us可以。  SDA = 0;    Delay_us(2);   //最开始50,  SCL = 0;    Delay_us(5);  //最开始50,这个延时和上面的延时可以不要,但是为了SLAVE有足够时间退出中断,就加上}/*停止I2C工作*/void STOP_I2C(void){//   SDA_OUT;    SDA = 0;   Delay_us(50);;   SCL = 1;   Delay_us(50);;   SDA = 1;   Delay_us(50);;   SCL = 0;   Delay_us(50);;}/*收到从器件的ACK帧,用于写完一个字节后检查*/void Receive_SLAVE_ACK(void){               SCL = 0;//        Delay_us(50);     //这里没有必要        SDA = 1;         SDA_(IN);        SCL = 1;         Delay_us(20);      //15us短了,经常出错                if(1 == SDA)    // 若SDA=1表明非应答,置位非应答标志ACK_Flag                ACK_Flag = 1;        SDA_(OUT);        SCL = 0;//        Delay_us(50); //这里也没有必要}/*主器件往从器件里写一个字节*/void WriteByte(BYTE writedata){  //SDA_OUT;   SCL = 0;            //SCL为低电平的时候可以改变数据状态  for(int i=0;i<8;i++){         if(((writedata>>7)&0x01) == 0x01){//先写最高位      SDA = 1;    }    else{      SDA = 0;    }//    Delay_us(10);              //这个可以不要    SCL = 1;    Delay_us(20);               //这个是等待SLAVE进入中断并接收数据,退出中断,15us不行,要20us    writedata = writedata << 1;//写完一位后将低位移到高位        SCL = 0;//    Delay_us(50);            //这个也可以不要  }//    Delay_us(50);           //这个其实可以不要    SCL = 0;}/*主器件从从器件里面读取一个字节*/BYTE ReadByte(void){  BYTE TempData = 0;  SCL = 0;  for(int i=0;i<8;i++){    SDA = 1;    SDA_(IN);//    Delay_us(10);           //这个没有什么影响    SCL = 1;    Delay_us(150);           //这个时间不能太短,不然的话就会读错1位    TempData <<= 1;    if(1 == SDA)        TempData |= 0x01;    else        TempData |= 0x00;      SCL = 0;  }  SCL = 0;  SDA_(OUT);//  Delay_us(50);  return (TempData);}void I2C_Bytes_Test(void){/***********************写单字节正常*****************************/#if 0     START_I2C();    WriteByte(0xa4);//写单字节的命令    Receive_SLAVE_ACK();    if(ACK_Flag == 1){        return;     }    WriteByte(0xaa);    Receive_SLAVE_ACK();    if(ACK_Flag == 1){      return;    }    STOP_I2C();#endif/***********************************************************/ /*****************************写多字节********************/#if 0     START_I2C();    WriteByte(0xa6);//写字多节的命令    Receive_SLAVE_ACK();    if(ACK_Flag == 1){        return;     }    WriteByte(0x05);//写多字节长度    Receive_SLAVE_ACK();    if(ACK_Flag == 1){        return;     }    for(int i=0;i<5;i++){   //开始写多字节       WriteByte(send_slave[i]);       Receive_SLAVE_ACK();       if(ACK_Flag == 1){          return;       }    }    STOP_I2C();#endif    /**********************************************************//*****************I2C读正常**************************/    START_I2C();    WriteByte(0xa5);    Receive_SLAVE_ACK();    if(ACK_Flag == 1){           return;    }    WriteByte(0x03);            //读的长度    Receive_SLAVE_ACK();    if(ACK_Flag == 1){      return;    }    for(int i=0;i<3;i++){        receive_slave[i] = ReadByte();        Receive_SLAVE_ACK();        if(ACK_Flag == 1)          return;//        UART1_Send_BYTE(receive_slave[i]);          }    STOP_I2C();    LED0 = 0;    UART1_Send_String(receive_slave,3);/**********************************************************/}



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 = 0;BYTE address = 0;          //接收到的从机地址BYTE count = 0;            //接收到一位计数,产生一个字节的计数BYTE receive_BYTE;         //从机接收主机单个字节BYTE receive_buf[20] = {0x00};         //从机接收主机数据的bufferBYTE write_buf[20] = {0x47,0x55,0x11};//从机发送数据给主机的bufferBYTE receive_len = 0;     //从接接收主机多字节时的长度BYTE send_len = 0;        //从机要发送给主机字节的长度BYTE write_end = 0;       //主机是否对从机写完BYTE read_end = 0;        //主机是否接收完从机发送的数据BYTE length_ACK = 0;      //从机是否接收到了要发送数据给主机的长度BYTE read_num = 0;BYTE Temp = 0;    void PORT1_InterruptInit(void){  EA = 1;  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;          //清中断标志 */   if(P1IFG>0)   {      P1IFG = 0;     switch(STATE){        case START_STATE:              SDA_(IN);              if(SDA){                  while(SDA);                                while(SCL);                  STATE = CONTROL_STATE;                                 }              break;        case CONTROL_STATE:              address <<= 1;              if(1 == SDA)                  address |= 0x01;              else                  address |= 0x00;                               count++;              if(8 == count){                  count = 0;                                      if((address & 0xfc) == 0xa4){                      STATE = ACK_STATE;                                                              }                  else                      STATE = NOACK_STATE;                                                }              break;        case ACK_STATE:                            SDA_(OUT);//SDA设置为输出              SDA = 0;              if((write_end == 1) || (read_end == 1) ){  //已经读完或者写完                  STATE = STOP_STATE;                  write_end = 0;                  read_end = 0;              }              else{                  if((address & 0x01) == 0x00)       //主机写SLAVE                      STATE = WRITE_STATE;                  else{                      STATE = READ_STATE;//                      UART1_Send_BYTE(STATE);                  }              }              break;        case NOACK_STATE:              SDA_(OUT);              SDA = 1;              address = 0;                  STATE = START_STATE;              break;        case WRITE_STATE:                      //主机写从机              SDA_(IN);                        //这里将SDA置为输入,是因为发送ACK时候置为输出了              if((address & 0x02) == 0x00){    //写单字节                  receive_BYTE <<= 1;                  if(1 == SDA)                      receive_BYTE |= 0X01;                  else                      receive_BYTE |= 0X00;                                    count++;                  if(8 == count){                      STATE = ACK_STATE;//                      UART1_Send_BYTE(receive_BYTE);                      count = 0;                      write_end = 1;                  }                           }              else{                  receive_buf[receive_len] <<= 1;                  if(1 == SDA)                      receive_buf[receive_len] |= 0x01;                  else                      receive_buf[receive_len] |= 0x00;                  count++;                  if(8 == count){                     //接收到了8个字节                      count = 0;                      receive_len++;                      UART1_Send_BYTE(receive_buf[receive_len-1]);                      if(receive_len >= (receive_buf[0]+1)){  //这里+1是因为要先写长度                          write_end = 1;                          receive_len = 0;                      }                          STATE = ACK_STATE;                  }              }              break;         case READ_STATE:                            //主机读从机              if(!length_ACK){                      //主机发送过从机长度                                    SDA_(IN);                                            send_len <<= 1;                  if(1 == SDA)                      send_len |= 0x01;                  else                      send_len |= 0x00;                                    count++;                  if(8 == count){                      length_ACK = 1;           //主机发送长度给从机                            LED0 = 0;//                      UART1_Send_BYTE(send_len);                      count = 0;                      STATE = ACK_STATE;                  }              }              else{                  SDA_(OUT);                  Temp = write_buf[read_num];                  Temp <<= count;                  UART1_Send_BYTE(Temp);                  if((Temp & 0x80) == 0x80)                      SDA = 1;                  else                      SDA = 0;                  count++;                  if(count == 8){    //移了7位,正好读一个字节                      count = 0;                      read_num++;                      if(read_num >= send_len){//读完了所有数据                          read_num = 0;                          read_end = 1;                          length_ACK = 0;    //将接收长度置0                      }                          STATE = ACK_STATE;                  }                 }              break;        case STOP_STATE:              SDA_(IN);              while(!SDA);                            address = 0;//              receive_BYTE = 0;              receive_len = 0;              send_len = 0;              LED1 = ~LED1;              STATE = START_STATE;              break;        default:                      break;     }   }   P1IF = 0; }






   

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