前一段时间对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设备写数据的过程。
//功能:将指定数量的数据写入到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从设备中,整个流程是参照手册的上的流程来编码的。
//功能:从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设备读取指定长度的数据。整个流程也是参考手册的流程来实现的。
读取数据的过程主要是注意要读取的数据还剩三个字节的时候CR1寄存器中ACK位和STOP位的变化情况,这里所说的情况是要读取的数据大于两个字节的情况。
如代码中红色标注的所示,此时倒数第三个字节在DR寄存器中,需要将ACK复位,然后读取该字节,但STOP位不能置1(务必要注意这一点,网上有在此时就将STOP位置1的,导致后续的操作失败)。当要读取倒数第二个字节时,将STOP位置1,如代码中紫色标注的部分。另外,为了后续正常读写数据,需要将ACK位再次置1,本次是在写数据函数内实现的。以上是操作I2C设备的三个重要的函数,针对测试的主函数就不再记录了。
上一篇:STM32的HAL库的 I2C和UART使用函数
下一篇:STM32 编译指令 #pragma pack 的配对使用
推荐阅读最新更新时间:2024-03-16 16:26
设计资源 培训 开发板 精华推荐
- 非常见问题解答第223期:如何在没有软启动方程的情况下测量和确定软启动时序?
- 兆易创新GD25/55全系列车规级SPI NOR Flash荣获ISO 26262 ASIL D功能安全认证证书
- 新型IsoVu™ 隔离电流探头:为电流测量带来全新维度
- 英飞凌推出简化电机控制开发的ModusToolbox™电机套件
- 意法半导体IO-Link执行器电路板为工业监控和设备厂商带来一站式参考设计
- Melexis采用无磁芯技术缩小电流感测装置尺寸
- 千丘智能侍淳博:用数字疗法,点亮“孤独症”儿童的光
- 数药智能冯尚:ADHD数字疗法正为儿童“多动症”提供更有效便捷服务
- Vicor高性能电源模块助力低空航空电子设备和 EVTOL的发展
- 创实技术electronica 2024首秀:加速国内分销商海外拓展之路