前几天在学习STM32的485通信,基本搞清楚了原理,也实际操作成功---后续还将进一步学习---与变频器通信,从而去控制380V电机!
现在打算彻底搞清楚STM32的I2C总线通信----首先是对AT24C02的读写----手上有PCF8574的IO扩展芯片,也是I2C协议的,希望实现多个控制!
STM32的I2C有自带的硬件驱动,也可以使用GPIO模拟-----先总结一下硬件驱动下的问题。
----------------------------------硬件下-----以AT24C02与PCF8574为例---------------
------第一部分是简单宏定义-------
#define I2C_Speed 300000 //传输速率--挂载原件多时要求降低速率,自己在调试过程中出现过这样问题
#define I2C1_OWN_ADDRESS7 0x0A //主机自定义地址--总线上每个设备都需要地址---包括主机STM32
#define I2C_PageSize 8 // AT24C02每页有8个字节
#define EEP_Firstpage 0x00 //写起始地址
#define EEPROM_ADDRESS 0xA0 //AT24C02从机地址--后面赋值
#define PCF8574_ADDRESS 0x70 //PCF8574从机地址--后面赋值
uint8_t I2c_Buf_Write[256]; //写缓存
uint8_t I2c_Buf_Read[256]; //读缓存
------第二部分是IO---时钟---I2C硬件配置-------
static void I2C_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
I2C_InitTypeDef I2C_InitStructure;
//----时钟-----
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
// -----GPIO设置-----PB6-I2C1_SCL-----PB7-I2C1_SDA----
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 开漏输出
GPIO_Init(GPIOB, &GPIO_InitStructure);
//-- I2C 配置 --------------------------
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;//选择I2C模式
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;//高低电平占空比为2:1
I2C_InitStructure.I2C_OwnAddress1 =I2C1_OWN_ADDRESS7;//本机地址
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ; //应答允许
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//寻址模式为7位---(还有10位的选择)
I2C_InitStructure.I2C_ClockSpeed = I2C_Speed;//传输速率30000--挂载原件多时要求降低速率,自己在调试过程中出现过这样问题
I2C_Cmd(I2C1, ENABLE); // 使能 I2C1
I2C_Init(I2C1, &I2C_InitStructure); //I2C1 初始化
}
------第三部分是----I2C写AT24C02一个字节----关于while循环不去具体研究,主要是判断各步骤是否完成
void I2C_EE_ByteWrite(u8* pBuffer, u8 WriteAddr)
{
u8 i=0;
I2C_GenerateSTART(I2C1, ENABLE); //----(1.)----产生通信起始信号
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT))
{
i++;
if(i>100)//防止器件损坏引起死循环
break;
}
I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS,I2C_Direction_Transmitter);//---(2.)---发送地址寻找匹配-- I2C_Direction_Transmitter--表示数据传输方向为STM32发送-- I2C_Direction_Receiver--表示数据传输方向为STM32接收
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
i++;
if(i>100)//防止器件损坏引起死循环
break;
}
I2C_SendData(I2C1, WriteAddr); //---(3.)---写起始地址
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
i++;
if(i>100)//防止器件损坏引起死循环
break;
}
I2C_SendData(I2C1, *pBuffer); //---(4.)---写数据
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
i++;
if(i>100)//防止器件损坏引起死循环
break;
}
I2C_GenerateSTOP(I2C1, ENABLE); //---(5.)---产生结束信号
}
-----第四部分---I2C写AT24C02多个字节,但是不超过AT24C02的一页字节数---8个----关于while循环不去具体研究,主要是判断各步骤是否完成
void I2C_EE_PageWrite(u8* pBuffer, u8 WriteAddr, u8 NumByteToWrite)
{
u8 i=0;
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY))//---(1.)---检测总线状态
{
i++;
if(i>100)
break;
}
I2C_GenerateSTART(I2C1, ENABLE);//---(2.)---起始信号
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT))
{
i++;
if(i>100)
break;
}
I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter); //---(3.)---发送地址寻址匹配
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
i++;
if(i>100)
break;
}
I2C_SendData(I2C1, WriteAddr); //---(4.)---写起始地址
while(! I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
i++;
if(i>100)
break;
}
while(NumByteToWrite--) //按照写入个数依次写入
{
I2C_SendData(I2C1, *pBuffer); //---(5.)---写数据
pBuffer++;
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
i++;
if(i>100)
break;
}
}
I2C_GenerateSTOP(I2C1, ENABLE);//---(6.)---产生结束信号
}
-----第五部分---I2C读AT24C02多个字节----关于while循环不去具体研究,主要是判断各步骤是否完成
void I2C_EE_BufferRead(u8* pBuffer, u8 ReadAddr, u16 NumByteToRead)
{
u8 i=0;
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY))//---(1.)---忙信号
{
i++;
if(i>100)
break;
}
I2C_GenerateSTART(I2C1, ENABLE);//---(2.)---起始信号
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT))
{
i++;
if(i>100)
break;
}
I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter);//---(3.)---地址寻址匹配
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
i++;
if(i>100)
break;
}
I2C_Cmd(I2C1, ENABLE);
I2C_SendData(I2C1, ReadAddr); //---(4.)---读取数据的起始地址
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
i++;
if(i>100)
break;
}
I2C_GenerateSTART(I2C1, ENABLE);//---(5.)---读数据特别之处---重新发送起始信号
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT))
{
i++;
if(i>100)
break;
}
I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Receiver);//---(6.)---地址寻址匹配
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
{
i++;
if(i>100)
break;
}
while(NumByteToRead)
{
if(NumByteToRead == 1)
{
I2C_AcknowledgeConfig(I2C1, DISABLE);//关闭应答
I2C_GenerateSTOP(I2C1, ENABLE);//停止信号
}
if(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED))
{
*pBuffer = I2C_ReceiveData(I2C1); //---(7.)---依次读取数据
pBuffer++;
NumByteToRead--;
}
}
I2C_AcknowledgeConfig(I2C1, ENABLE);
}
-----第六部分---I2C写入PCF8574一个字节数据----关于while循环不去具体研究,主要是判断各步骤是否完成
void I2C_PCF8574_ByteWrite(u8 PCF_ADD, u8 WriteData)
{
u8 i=0;
I2C_GenerateSTART(I2C1, ENABLE); //----(1.)----产生通信起始信号
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT))
{
i++;
if(i>100)
break;
}
i=0;
I2C_Send7bitAddress(I2C1, PCF_ADD, I2C_Direction_Transmitter); //----(2.)----发送地址寻找匹配
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
i++;
if(i>100)
break;
}
i=0;
I2C_SendData(I2C1, WriteData); //----(3.)----写数据
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
i++;
if(i>100)
break;
}
I2C_GenerateSTOP(I2C1, ENABLE); //----(4.)----产生结束信号
}
-----第七部分----
int main(void)
{
u8 i,led=0x01;
USART1_Config();
GPIO_Config();
I2C_All_Init();
while (1)
{
led++;
if(led>98)
led=0x01;
I2C_PCF8574_ByteWrite(0x70,led);
Delay(0xffffe);
printf("\r\n读出的数据\r\n");
I2C_EE_BufferRead(I2c_Buf_Read, 0, 100); //将EEPROM读出数据顺序保持到I2c_Buf_Read中
printf("0x%02X \r\n", I2c_Buf_Read[led]);
printf("0x%02X \r\n ", I2c_Buf_Read[led+1]);
printf("\r\nI2C(AT24C02)读写测试成功\r\n");
Delay(0xffffe);
}
}
---------------------由上面总结一下---------------------
对于I2C总线的写主要注意下面步骤
-------(1.)----检测忙信号----
-------(2.)----发送起始信号
-------(3.)----发送地址寻址匹配信号
-------(4.)----发送或者接受数据----有时候要根据芯片手册来看----比如AT24C02就将此时发送的数据的第一个数据理解为读写的起始地址-----而PCF8574则没有,是直接解释为数据进行读取!
-------(5.)----发送总线结束信号
对于I2C总线的读主要注意下面步骤----这里是以AT24C02为例--其他芯片简单差异
-------(1.)----检测忙信号----
-------(2.)----发送起始信号
-------(3.)----发送地址寻址匹配信号
-------(4.)----发送读的起始地址
-------(5.)----重新发送起始信号
-------(6.)----重新发送地址寻址匹配信号
-------(7.)----依次读数据
-------(8.)----发送总线结束信号
----------------------------------GPIO软件模拟下-----以AT24C02与PCF8574为例------------
----1.----I2C总线GPIO配置----
static void i2c_CfgGpio(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_I2C_PORT, ENABLE);
GPIO_InitStructure.GPIO_Pin = I2C_SCL_PIN | I2C_SDA_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; //开漏输出
GPIO_Init(GPIO_PORT_I2C, &GPIO_InitStructure);
i2c_Stop(); //发送一个停止信号,复位总线上所有设备
}
----2.----I2C总线启动信号----
void i2c_Start(void) //当SCL为高电平时,SDA出现一个下跳沿表示I2C总线启动信号
{
I2C_SDA_1();
I2C_SCL_1();
i2c_Delay();
I2C_SDA_0();
i2c_Delay();
I2C_SCL_0();
i2c_Delay();
}
----3.----I2C总线停止信号----
void i2c_Stop(void) //当SCL为高电平时,SDA出现一个上跳沿表示I2C总线停止信号
{
I2C_SDA_0();
I2C_SCL_1();
i2c_Delay();
I2C_SDA_1();
}
----4.----向I2C总线发送8Bit数据----
void i2c_SendByte(uint8_t _ucByte)
{
uint8_t i;
for (i = 0; i < 8; i++) //先发送高位
{
if (_ucByte & 0x80)
I2C_SDA_1();
else
I2C_SDA_0();
i2c_Delay();
I2C_SCL_1();
i2c_Delay();
I2C_SCL_0();
if (i == 7)
I2C_SDA_1();
_ucByte <<= 1;
i2c_Delay();
}
}
----5.----从I2C总线读取8Bit数据----
uint8_t i2c_ReadByte(void)
{
uint8_t i;
uint8_t value;
value = 0;
for (i = 0; i < 8; i++) //先读取的是高位
{
value <<= 1;
I2C_SCL_1();
i2c_Delay();
if (I2C_SDA_READ())
value++;
I2C_SCL_0();
i2c_Delay();
}
return value;
}
----6.----产生一个ACK信号给I2C----
void i2c_Ack(void)
{
I2C_SDA_0();
i2c_Delay();
I2C_SCL_1();
i2c_Delay();
I2C_SCL_0();
i2c_Delay();
I2C_SDA_1();
}
----7.----产生一个NACK信号给I2C----
void i2c_NAck(void)
{
I2C_SDA_1();
i2c_Delay();
I2C_SCL_1();
i2c_Delay();
I2C_SCL_0();
i2c_Delay();
}
----8.----检测I2C总线设备,发送设备地址,读取设备返回应答来判断这个地址的设备是否存在----
uint8_t i2c_CheckDevice(uint8_t _Address)
{
uint8_t ucAck;
i2c_CfgGpio();//配置GPIO
i2c_Start();//启动信号
i2c_SendByte(_Address | I2C_WR);//发送设备地址与读写控制--最后一位表示读写选择
ucAck = i2c_WaitAck();//检测应答
i2c_Stop(); //发送停止信号
return ucAck;
}
----9.----这里就不用AT24C02来举例了,因为代码稍微多一点,这里用PCF8574举例------
uint8_t I2C_PCF8574_ByteWrite(, uint16_tPCF_ADD,uint8_t *_pWriteBuf)
{
uint16_t i,m;
i2c_Stop();
for (m = 0; m < 100; m++) //多次检测---超过时间退出
{
i2c_Start();
i2c_SendByte(PCF_ADD); //此处是写地址指令
if (i2c_WaitAck() == 0)
break;
}
if (m == 100)
goto cmd_fail;//I2C设备出故障,无应答,跳转
i2c_SendByte((uint8_t)_pWriteBuf);
if (i2c_WaitAck() != 0)
goto cmd_fail;//I2C设备出故障,无应答,跳转
i2c_Stop(); //命令执行成功,发送I2C总线停止信号
return 1;
cmd_fail: //命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备
i2c_Stop(); //发送I2C总线停止信号
return 0;
}
----10.----主函数简单调用----控制PCF8574去开关LED灯---
int main(void)
{
u8 led=0x00;
USART1_Config();
while(1)
{
led++;
if(led>255)
led=0x00;
I2C_PCF8574_ByteWrite(led,0x70); //根据led不同值去点亮不同LED灯
}
}
---------------------总结如下----------
步骤都是一样,主要是要注意停止总线信号----与时刻检测ACK应答---
---------------------------一点图片---------------------
--------------------------0--------------------------------
--------------------------1---------------------------------------------
---------------------------------------------2------------------------
-----------------------------3---------------------------------