【STM32】入门(七):I2C硬件控制方式

1、简介

之所以叫“I2C硬件控制方式”是与“软件控制方式”相对。I2C软件控制,就是写程序直接操作两个GPIO引脚,分别作为时钟线SCL和数据线SDA,按照I2C协议的时序要求,操作GPIO输入、输出、高电平、低电平。
听着就很复杂,好在STM32中有I2C的硬件实现,即通过简单的操作寄存器即可实现收发数据。

2、手册

2.1 寄存器功能框图

在这里插入图片描述

2.2 I2C引脚

STM32F407ZGT6中有3个I2C总线,对应的引脚如下图所示。
其中I2C1默认引脚是PB6、PB7,可以重映射到PB8、PB9上。
在这里插入图片描述

2.3 寄存器

寄存器地址:
在这里插入图片描述
在这里插入图片描述

3、代码详解

I2C基本编程步骤:初始化时钟、配置引脚、起始信号、读、写、终止信号

3.1 I2C初始化

3.1.1 初始化时钟

I2C在APB1总线上,并且I2C1的引脚位PB6、PB7,因此使能使能GPIOB时钟,以及I2C的时钟

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); 
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); 

在这里插入图片描述

3.1.2 配置引脚位开漏输出

static void I2C_GPIO_Config(void)
{
  GPIO_InitTypeDef  GPIO_InitStructure; 

  #a)使能与 I2C 有关的时钟
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); 
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
    
  #b)配置SCL和SDA引脚位开漏输出GPIO_Mode_AF_OD
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
  GPIO_Init(GPIOB, &GPIO_InitStructure);
	
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
  GPIO_Init(GPIOB, &GPIO_InitStructure);	
}

3.1.3 设置I2C工作模式

设置工作模式、占空比、地址7位或10位、通信速率等

工作模式有三种:I2C、SMBusDevice、SMBusHost。

SMBus (System Management Bus,系统管理总线)和I2C类似,但是在时序特性上有一些差异:

首先,SMBus需要一定数据保持时间,而 I2C总线则是从内部延长数据保持时间。
SMBus具有超时功能,因此当SCL太低而超过35 ms时,从器件将复位正在进行的通信。相反,I2C采用硬件复位。
SMBus具有一种警报响应地址(ARA),因此当从器件产生一个中断时,
	它不会马上清除中断,而是一直保持到其收到一个由主器件发送的含有其地址的ARA为止。
SMBus只工作在从10kHz到最高100kHz。最低工作频率10kHz是由SMBus超时功能决定的。
static void I2C_Mode_Configu(void)
{
  I2C_InitTypeDef  I2C_InitStructure; 

  #a)设置位I2C模式
  I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
	
  #b)设置高低电平占空比,这个不用纠结,可以随意设置,一般设备兼容性都没问题
  I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;

  #c)设置自己的地址为7
  I2C_InitStructure.I2C_OwnAddress1 =I2Cx_OWN_ADDRESS7; 
  
  #d)默认使能ACK,当需要发送NACK时,再重新设置
  I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;
	 
  #e)设置I2C的寻址地址为7
  I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
	
  #f)设置时钟为:400000
  I2C_InitStructure.I2C_ClockSpeed = 400000;
  
  #g)初始化I2C1:(APB1PERIPH_BASE + 0x5400)
  I2C_Init(I2C1, &I2C_InitStructure);
  
  #h)使能 I2C1
  I2C_Cmd(I2C1, ENABLE);   
}

3.2 写操作

在这里插入图片描述

uint32_t I2C_EE_ByteWrite(u8* pBuffer, u8 WriteAddr) 
{
  #a)发送起始信号“S”
  I2C_GenerateSTART(I2C1, ENABLE);

  I2CTimeout = I2CT_FLAG_TIMEOUT;  
  #b)等待起始信号发送成功:测试 EV5 即可
  while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT))  
  {
    if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(0);
  } 
  
  I2CTimeout = I2CT_FLAG_TIMEOUT;
  #c)发送从地址
  I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter);
  
  #d)等待从地址发送成功:测试 EV6 即可
  while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
  {
    if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(1);
  }  
  #e)发送需要写入EEPROM的地址(本质也是数据)
  I2C_SendData(I2C1, WriteAddr);
  
  I2CTimeout = I2CT_FLAG_TIMEOUT;
  #f)等待数据发送成功:测试 EV8 即可
  while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
  {
    if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(2);
  } 
  
  #g)发送需要写入的数据
  I2C_SendData(I2C1, *pBuffer); 
  
  I2CTimeout = I2CT_FLAG_TIMEOUT;  
  #h)等待数据发送成功:测试 EV8 即可
  while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
  {
    if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3);
  } 
  
  #i)发送停止信号“P”
  I2C_GenerateSTOP(I2C1, ENABLE);
  
  return 1;
}

3.3 读操作

在这里插入图片描述

uint32_t I2C_EE_BufferRead(u8* pBuffer, u8 ReadAddr, u16 NumByteToRead)
{  
  I2CTimeout = I2CT_LONG_TIMEOUT;
  
  #a)等待是否可以读
  //*((u8 *)0x4001080c) |=0x80; 
  while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY))
  {
    if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(9);
  }
  
  #b)发送起始信号“S”
  I2C_GenerateSTART(I2C1, ENABLE);
  //*((u8 *)0x4001080c) &=~0x80;
  
  I2CTimeout = I2CT_FLAG_TIMEOUT;
  #c)等待起始信号发送成功:测试 EV5 即可
  while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT))
  {
    if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(10);
  }

  #d)发送从地址
  I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter);

  I2CTimeout = I2CT_FLAG_TIMEOUT;
  #e)等待从地址发送成功:测试 EV6 即可
  while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
  {
    if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(11);
  }
    
  #f)EV6后执行EV6_1:清除EV6 (再次设置PE位)
  I2C_Cmd(I2C1, ENABLE);

  #g)发送需要读取的地址
  I2C_SendData(I2C1, ReadAddr);  

   
  I2CTimeout = I2CT_FLAG_TIMEOUT;
  #h)等待数据发送成功:测试 EV8 即可
  while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
  {
    if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(12);
  }
    
  #i)复合命令:再次发送起始信号“S”
  I2C_GenerateSTART(I2C1, ENABLE);
  
  I2CTimeout = I2CT_FLAG_TIMEOUT;
  #j)等待起始信号发送成功:测试 EV5 即可
  while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT))
  {
    if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(13);
  }
    
  #k)发送从地址
  I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Receiver);
  
  I2CTimeout = I2CT_FLAG_TIMEOUT;
  #l)等待从地址发送成功:测试 EV6 即可
  while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
  {
    if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(14);
  }
  
  #m)循环读
  while(NumByteToRead)  
  {
    if(NumByteToRead == 1)
    {
      
      #n)读取完毕,发送NACK
      I2C_AcknowledgeConfig(I2C1, DISABLE);
      
      #o)发送停止信号“P”
      I2C_GenerateSTOP(I2C1, ENABLE);
    }

    #p)每次读取一个字节前,先测试 EV7    
    I2CTimeout = I2CT_LONG_TIMEOUT;
    while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)==0)  
	{
		if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3);
	} 
    {      
      #q)读取一字节
      *pBuffer = I2C_ReceiveData(I2C1);
      pBuffer++; 
      NumByteToRead--;        
    }   
  }

  #r)为下一次读取做准备,即将ACK设置为1
  I2C_AcknowledgeConfig(I2C1, ENABLE);
  
  return 1;
}

3.4 待机状态

向EEPROM写入数据后,调用这个函数等待EEPROM 内部擦写完毕。

这个函数主要实现是向EEPROM 发送它设备地址,检测EEPROM 的响应,若EEPROM 接收到地址后返回应答信号,则表示EEPROM 已经准备好,可以开始下一次通讯。函数中检测响应是通过读取STM32 的SR1 寄存器的ADDR 位及AF 位来实现的,当I2C 设备响应了地址的时候,ADDR 会置1,若应答失败,AF 位会置1。

void I2C_EE_WaitEepromStandbyState(void)      
{
  vu16 SR1_Tmp = 0;

  do
  {
    #a)发送起始信号“S”
    I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);
    #b)读取I2C1 SR1 寄存器
    SR1_Tmp = I2C_ReadRegister(EEPROM_I2Cx, I2C_Register_SR1);
    #c)发送从地址
    I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS, I2C_Direction_Transmitter);
  }while(!(I2C_ReadRegister(EEPROM_I2Cx, I2C_Register_SR1) & 0x0002));
  
  #d)清除AF信号
  I2C_ClearFlag(EEPROM_I2Cx, I2C_FLAG_AF);
  #e)发送停止信号“P”    
  I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE); 
}
  • 14
    点赞
  • 73
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
下面是使用hal库在STM32中实现硬件I2C的示例代码: ```c #include "stm32f4xx_hal.h" I2C_HandleTypeDef hi2c1; // I2C句柄变量 void I2C1_Init(void) { // I2C1 GPIO初始化 GPIO_InitTypeDef GPIO_InitStruct; __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; // 开漏输出 GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF4_I2C1; // PB6-SCL, PB7-SDA HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // I2C1初始化 __HAL_RCC_I2C1_CLK_ENABLE(); hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; // I2C时钟频率100kHz hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; HAL_I2C_Init(&hi2c1); } void I2C1_Write(uint8_t addr, uint8_t reg, uint8_t data) { HAL_I2C_Mem_Write(&hi2c1, addr<<1, reg, I2C_MEMADD_SIZE_8BIT, &data, 1, 1000); } uint8_t I2C1_Read(uint8_t addr, uint8_t reg) { uint8_t data; HAL_I2C_Mem_Read(&hi2c1, addr<<1, reg, I2C_MEMADD_SIZE_8BIT, &data, 1, 1000); return data; } ``` 这段代码实现了使用PB6和PB7引脚作为I2C总线的硬件I2C功能,使用了STM32 HAL库的I2C API函数进行读写操作。其中,`I2C1_Init()`函数初始化了I2C总线和GPIO引脚,`I2C1_Write()`和`I2C1_Read()`函数分别用于写入和读取I2C从设备的数据。在具体应用中,需要根据实际需求进行修改。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

郭老二

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值