×
单片机 > 单片机程序设计 > 详情

STM32 I2C总线 自我总结学习

发布时间:2020-06-06 发布时间:
|

前几天在学习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---------------------------------





『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
单片机制作超级流水灯