STM32之I2C模块调试总结

       前一段时间对STM32的I2C模块进行了调试,今天作一个总结。关于I2C协议的知识,这里就再也不赘述,网上有不少介绍I2C协议的文章。目前实现I2C协议的方式有两种,一是采用GPIO口来模拟I2C协议,另一种是使用STM32自带的I2C模块。虽然说使用GPIO口模拟I2C协议较为复杂,须要详细了解I2C协议的内容,可是实现这种方式的资料也很是多,网上都有对应的源码实现,只须要简单修改,就能够实现功能。而针对使用STM32自带的I2C模块,网络上贬斥的声音较多,说是模块自己自带bug,容易出问题,甚至还有人说是史上最难调的I2C模块。固然了,这些问题我本身目前尚未遇到,可能须要之后来验证了。好了,言归正传,今天主要记录一下调试过程以及须要注意的地方。      数组

//功能:初始化IIC接口
void SSX1207_Init(void)
{
 GPIO_InitTypeDef  GPIO_InitStructure;
 I2C_InitTypeDef   I2C_InitStructure;
 
 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟
 //GPIOB6,B7初始化设置,PB6=SCL,PB7=SDA
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用模式
 GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;//开漏模式
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//50MHz
 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//
 GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化安全

 GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_I2C1);//将PB6链接到SCL
 GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_I2C1);//将PB7链接到SDA网络

 RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);//使能I2C1时钟
 //配置I2C参数
 I2C_InitStructure.I2C_Mode=I2C_Mode_I2C;
 I2C_InitStructure.I2C_DutyCycle=I2C_DutyCycle_2;
 I2C_InitStructure.I2C_ClockSpeed=I2C_Standard_Speed;
 I2C_InitStructure.I2C_OwnAddress1 = 0x00;
 I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
 I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
 I2C_Init(I2C1,&I2C_InitStructure);//初始化
 I2C_Cmd(I2C1,ENABLE);//使能I2C
}函数

该函数完成对I2C模块的初始化,首先要肯定使用的GPIO口,而后对GPIO口的参数进行配置,这一部分也是参数网上的一下资料进行配置的,须要注意的是GPIO_Mode要配置成复用模式,GPIO_OType要配置成开漏模式,若是使用别的库可能对应的参数有差别,但基本功能应该是同样的,须要将GPIO口配置成复用开漏模式。上面只是对要使用到的GPIO口的配置,还须要配置I2C的参数,这部分只须要注意一下OwnAddress1便可,该参数只须要跟总线上的其余I2C设备的地址不同就好了,是用户本身定义的。测试

    初始化函数记录完成以后,下面记录主I2C设备对从I2C设备写数据的过程。ui

//功能:将指定数量的数据写入到IIC设备中
//参数:
//  pBuffer--要写入的数据数组
//  NumToWrite--写入数据的个数
void SSX1207_WriteByteArray(u8 *pBuffer,u16 NumToWrite)
{
 uint32_t flag=0;
 I2C_AcknowledgeConfig(I2C1,ENABLE);//ACK置1
 I2C_GenerateSTART(I2C1,ENABLE);//产生一个启动信号

 timeout=0x1000;
 while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT))//EV5事件
 {
  if((--timeout)==0)
  {
   printf("EV5 fail\r\n");
   break;
  } 
 }
 flag = I2C_GetLastEvent(I2C1);
 printf("flag=%x\r\n",flag);
 if(timeout!=0)
 printf("EV5 sucess\r\n");
 I2C_Send7bitAddress(I2C1,0X50,I2C_Direction_Transmitter);//发送安全芯片地址和写命令
 timeout=0x1000;
 while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))//EV6事件
 {
  if((--timeout)==0)
  {
   printf("EV6 fail\r\n");
   break;
  } 
 }
 flag = I2C_GetLastEvent(I2C1);
 printf("flag=%x\r\n",flag);
 if(timeout!=0)
 printf("EV6 sucess\r\n");
 while(NumToWrite--)
 {
  timeout=0x1000;
  while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTING))//EV8事件
  {
   if((--timeout)==0)
   {
   printf("EV8 fail\r\n");
   break;
   } 
  }
  flag = I2C_GetLastEvent(I2C1);
  printf("flag=%x\r\n",flag);
  if(timeout!=0)
  printf("EV8 sucess\r\n");
  I2C_SendData(I2C1,*pBuffer);
  pBuffer++;
 }
 timeout=0x1000;
 while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED))//EV8_2事件
 {
  if((--timeout)==0)
  {
   printf("EV8_2 fail\r\n");
   break;
  } 
 }
 flag = I2C_GetLastEvent(I2C1);
 printf("flag=%x\r\n",flag);
 if(timeout!=0)
 printf("EV8_2 sucess\r\n");
 I2C_GenerateSTOP(I2C1,ENABLE);//产生一个中止信号
}编码

该函数是将指定数量的数据写入到I2C从设备中,整个流程是参照手册的上的流程来编码的。spa

//功能:从IIC设备读取指定长度的数据
//参数:
//  pBuffer:要读取数据的存放数组
//  NumToRead:读取数据的数量
void SSX1207_ReadByteArray(u8 *pBuffer,u16 NumToRead)
{
 uint32_t flag=0;
 u16 NumToRead_FLAG=NumToRead;
 
 if(NumToRead_FLAG==8)
 {
  I2C_GenerateSTART(I2C1,ENABLE);//产生一个启动信号
  timeout=0x1000;
  while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT))//EV5事件
  {
   if((--timeout)==0)
   {
    printf("EV5 fail!\r\n");
    break;
   } 
  }
  flag = I2C_GetLastEvent(I2C1);
  printf("flag=%x\r\n",flag);
  if(timeout!=0)
  printf("EV5 sucess!\r\n");
  I2C_Send7bitAddress(I2C1,0X50,I2C_Direction_Receiver);//发送安全芯片地址和读命令
  timeout=0x1000;
  while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))//EV6事件
  {
   if((--timeout)==0)
   {
    printf("EV6 fail!\r\n");
    break;
   } 
  }
  flag = I2C_GetLastEvent(I2C1);
  printf("flag=%x\r\n",flag);
  if(timeout!=0)
  printf("EV6 sucess!\r\n");
 }
 while(NumToRead)
 {
  printf("len=%d\r\n",NumToRead);
  NumToRead--;
  if((NumToRead==2)&&(NumToRead_FLAG!=8))
  {
   I2C_AcknowledgeConfig(I2C1,DISABLE);//ACK位清零 
  }

  if((NumToRead==1)&&(NumToRead_FLAG!=8))
  {
   I2C_GenerateSTOP(I2C1,ENABLE);//产生一个中止信号
  }

  timeout=0x1000;
  while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED))//EV7事件
  {
   if((--timeout)==0)
   {
    printf("EV7 fail!\r\n");
    break;
   } 
  }
  *pBuffer=I2C_ReceiveData(I2C1);
  flag = I2C_GetLastEvent(I2C1);
  printf("flag=%x\r\n",flag);
  if(timeout!=0)
  printf("EV7 sucess! *pBuffer=%02x\r\n",*pBuffer);
  pBuffer++; 
 }
 printf("NumToRead=%d\r\n",NumToRead);
}调试

该函数是主设备向从I2C设备读取指定长度的数据。整个流程也是参考手册的流程来实现的。blog

读取数据的过程主要是注意要读取的数据还剩三个字节的时候CR1寄存器中ACK位和STOP位的变化状况,这里所说的状况是要读取的数据大于两个字节的状况。

如代码中红色标注的所示,此时倒数第三个字节在DR寄存器中,须要将ACK复位,而后读取该字节,但STOP位不能置1(务必要注意这一点,网上有在此时就将STOP位置1的,致使后续的操做失败)。当要读取倒数第二个字节时,将STOP位置1,如代码中紫色标注的部分。另外,为了后续正常读写数据,须要将ACK位再次置1,本次是在写数据函数内实现的。以上是操做I2C设备的三个重要的函数,针对测试的主函数就再也不记录了。