STM32F0+硬件I2C通讯

IIC(Inter—Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。在CPU与被控IC之间、IC与IC之间进行双向数据传送,高速IIC总线一般可达400kbps以上。

I2C通讯设备之间的常用连接方式:

STM32F0+硬件I2C通讯_第1张图片

I2C具有如下特点:

  1. 它是一个支持多设备的总线。“总线”指多个设备共用的信号线。在一个I2C通讯总线中,可连接多个I2C通讯设备,支持多个通讯主机及多个通讯从机。
  2. 一个 I2C 总线只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。
  3. 每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。
  4. 总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
  5. 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。
  6. 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制 。

STM32的 I2C 外设可用作通讯的主机及从机,支持 7位、10位设备地址,支持 DMA数据传输,并具有数据校验功能。它的 I2C外设还支持 SMBus2.0协议,SMBus 协议与 I2C类似,主要应用于笔记本电脑的电池管理中,本手册不展开,感兴趣的读者可参《SMBus20》文档了解。

STM32F030的I2C架构剖析:

STM32F0+硬件I2C通讯_第2张图片

①I2C通讯引脚

I2C 的所有硬件架构都是根据图中右侧 SCL 线和 SDA 线展开的。STM32 芯片有多个 I2C 外设,它们的 I2C 通讯信号引出到不同的 GPIO 引脚上,使用时必须配置到这些指定的引脚。

②数据控制逻辑

I2C 的 SDA信号主要连接到数据移位寄存器上。当向外发送数据的时候,数据移位寄存器以“数据寄存器”为数据源,把数据一位一位地通过 SDA 信号线发送出去;当从外部接收数据的时候,数据移位寄存器把 SDA信号线采样到的数据一位一位地存储到“数据寄存器”中。若使能了数据校验,接收到的数据会经过 PCE计算器运算,运算结果存储在“PEC寄存器”中。当 STM32 的 I2C工作在从机模式的时候,接收到设备地址信号时,数据移位寄存器会把接收到的地址与 STM32的自身的“I2C 地址寄存器”的值作比较,以便响应主机的寻址。STM32 的自身 I2C地址可通过修改“自身地址寄存器”修改,支持同时使用两个 I2C设备地址,两个地址分别存储在 OAR1和 OAR2中。

③时钟控制逻辑

I2C由一个独立的时钟源进行计时,该时钟源允许I2C独立于PCLK频率工作。这个独立的时钟可以选为SYSCLK系统时钟。I2C的IO口支持20毫安输出电流驱动器快速模式操作,这是通过SCL和SDA引脚的驱动来实现的。在启用外设之前,必须在I2C_TIMINGR寄存器中配置SCLH和SCLL的I2C主时钟。

SDA和SCL时钟配置公式如下:

I2C分频后的时钟周期:tPRESC  = (PRESC+1) * tI2CCLK

传输模式下,当SDA数据准备好,SCL上升沿到来的延时时间:tSCLDEL  = (SCLDEL+1) * tPRESC

传输模式下,SCL下降沿到来之后,SDA数据发生改变的延时时间:tSDADEL = SDADEL x tPRESC

SCL高电平持续时间:tSCLH  = (SCLH+1) x tPRESC

SCL低电平持续时间:tSCLL  = (SCLL+1) x tPRESC

tI2CCLK为I2C的时钟周期,PRESC、SCLDEL、SDADEL、SCLH、SCLL可以通过I2C_TIMINGR寄存器进行配置。

通讯过程:

使用 I2C外设通讯时,在通讯的不同阶段它会对“状态寄存器(ISR)”的不同数据位写入参数,我们通过读取这些寄存器标志来了解通讯状态。

I2C主机发送数据流程图如下(以发送两个字节数据为例):

STM32F0+硬件I2C通讯_第3张图片

 I2C主机发送数据流程:

  1. 控制产生起始信号(S),发送设备地址并等待应答信号(A)。
  2. 若设备地址发送成功,则产生事件“EV1”,这时I2C_ISR寄存器的“TXIS”位被置1表示设备地址发送成功。
  3. 紧接着往I2C的“数据寄存器TXDR”写入要发送的数据data1,等待应答信号(A)。
  4. 当数据发送完成后,产生事件“EV2”,这时I2C_ISR寄存器的“TXIS”位被置1表示数据发送成功。
  5. 当数据1发送完成后,以同样的方法发送数据data2,重复这个过程就可以发送多个字节了。
  6. 当我们发送数据完成后,在自动停止模式下,I2C设备产生一个停止信号,表示通讯结束。在软件停止模式下,数据发送完成后,产生事件“EV3”,这时I2C_ISR寄存器的“TC”位被置1表示数据传输完成,此时可以选择重新开始发送或者软件结束通讯。

I2C主机接收数据流程图如下(以接收两个字节数据为例):

STM32F0+硬件I2C通讯_第4张图片

I2C主机接收数据步骤:

  1. 主机产生起始信号(S),发送设备地址并等待应答信号(A)。
  2. 从机接收到地址后,开始向主机发送数据。当主机接收到数据data1后,会产生“EV1”事件,I2C_ISR寄存器的“RXNE”位被置1,表示接收数据寄存器非空,此时我们可以读取寄存器中数据,读取后我们控制I2C发送应答信号(A),继续接收数据。
  3. 以同样的方法接收数据data2,重复这个过程就可以接收多个字节了。数据接收完成后,控制I2C发送非应答信号(NA),表示停止传输。
  4. 当我们接收数据完成后,在自动停止模式下,I2C设备产生一个停止信号,表示通讯结束。在软件停止模式下,数据接收完成后,产生事件“EV3”,这时I2C_ISR寄存器的“TC”位被置1表示数据传输完成,此时可以选择重新开始发送或者软件结束通讯。

bsp_hard_i2c.c程序如下:

#include "bsp_hard_i2c.h"   

__IO uint32_t I2C2Timeout = I2C_LONG_TIMEOUT; 

// 初始化IIC2
void I2C2_Hard_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure; // 定义GPIO结构体
	I2C_InitTypeDef I2C_InitStruct;
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); // 打开GPIOB口时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE); // 使能I2C2时钟
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; // 复用功能
	GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; // 开漏
	GPIO_InitStructure.GPIO_Pin = Pin_SCL | Pin_SDA; // IIC对应IO口
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; // 上拉
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_Level_3; // 50MHZ
	GPIO_Init(GPIOB, &GPIO_InitStructure); // 初始化GPIO
	
	GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_1); // 将 PB10 映射为 I2C_SCL
	GPIO_PinAFConfig(GPIOB, GPIO_PinSource11, GPIO_AF_1); // 将 PB11 映射为 I2C_SDA
	
	SYSCFG_I2CFastModePlusConfig(SYSCFG_I2CFastModePlus_I2C1, ENABLE); // 配置I2C1快速模式(1MHz)和驱动能力	
	
	I2C_InitStruct.I2C_Ack = I2C_Ack_Enable; // I2C2应答使能
	I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; // 7位应答地址
	I2C_InitStruct.I2C_AnalogFilter = I2C_AnalogFilter_Enable; // I2C模拟滤波使能
	I2C_InitStruct.I2C_DigitalFilter = 0x00; // 数字滤波系数
	I2C_InitStruct.I2C_Mode = I2C_Mode_I2C; // 工作模式:选择模式为I2C
	I2C_InitStruct.I2C_OwnAddress1 = 0x00; // 设置I2C自身器件地址
	I2C_InitStruct.I2C_Timing = 0x00700818; // 设置I2C时间寄存器的值  1M:0x00700818    400k:0x00901850; 
	I2C_Init(I2C2,&I2C_InitStruct);
	
	I2C_Cmd(I2C2, ENABLE);
}

// 时间溢出返回函数
uint8_t I2C2_TIMEOUT_UserCallback()
{
	return 1;
}

// 写多字节函数
uint8_t I2C2_Write_NByte(const uint8_t Slave_Address, const uint16_t REG_Address, const uint8_t* REG_data, const uint8_t len)
{
	I2C2Timeout = I2C_LONG_TIMEOUT; 
	while(I2C_GetFlagStatus(I2C2,I2C_FLAG_BUSY) != RESET) // 设备忙等待
	{
		if((--I2C2Timeout) == 0) return I2C2_TIMEOUT_UserCallback();
	}
	// 配置从机设备地址 发送数据长度 发送模式 发送开始写信号
	I2C_TransferHandling(I2C2, Slave_Address, RegAddressBitWide, I2C_Reload_Mode, I2C_Generate_Start_Write);
	
	I2C2Timeout = I2C_LONG_TIMEOUT; 
	while(I2C_GetFlagStatus(I2C2,I2C_FLAG_TXIS) == RESET) // 等待发送状态复位
	{
		if((--I2C2Timeout) == 0) return I2C2_TIMEOUT_UserCallback();
	}	
	
	// 如果寄存器为16位 发送寄存器高8位地址
	if(RegAddressBitWide == Bit16)
	{
		I2C_SendData(I2C2, REG_Address >> 8); 
		
		I2C2Timeout = I2C_LONG_TIMEOUT; 
		while(I2C_GetFlagStatus(I2C2, I2C_FLAG_TXIS) == RESET) // 等待发送完成
		{
			if((--I2C2Timeout) == 0) return I2C2_TIMEOUT_UserCallback();
		}			
	}
	
  I2C_SendData(I2C2, REG_Address & 0xff); // 发送寄存器地址
	
	I2C2Timeout = I2C_LONG_TIMEOUT; 
	while(I2C_GetFlagStatus(I2C2, I2C_FLAG_TCR) == RESET) // 等待Reload模式下,数据发送完成
	{
		if((--I2C2Timeout) == 0) return I2C2_TIMEOUT_UserCallback();
	}
	// 配置从机设备地址 发送数据长度 发送模式 发送开始写信号
	I2C_TransferHandling(I2C2, Slave_Address, len, I2C_AutoEnd_Mode, I2C_No_StartStop);	
	
	uint8_t length = 0;
	while(length != len)
	{
    I2C2Timeout = I2C_LONG_TIMEOUT;
    while(I2C_GetFlagStatus(I2C2, I2C_ISR_TXIS) == RESET) // 等待发送完成
    {
      if((--I2C2Timeout) == 0) return I2C2_TIMEOUT_UserCallback();
    }  
    
    I2C_SendData(I2C2, (uint8_t)(REG_data[length]));
    
    length++;	
	}
	
  I2C2Timeout = I2C_LONG_TIMEOUT;
  while(I2C_GetFlagStatus(I2C2, I2C_ISR_STOPF) == RESET) // 等待AutoEnd模式下停止信号
  {
    if((--I2C2Timeout) == 0) return I2C2_TIMEOUT_UserCallback();
  }   
  
  I2C_ClearFlag(I2C2, I2C_ICR_STOPCF);
	return 0;	
}

// 读多字节函数
uint8_t I2C2_Read_NByte(const uint8_t Slave_Address, const uint16_t REG_Address, uint8_t *REG_data, const uint8_t len)
{
	I2C2Timeout = I2C_LONG_TIMEOUT; 
	while(I2C_GetFlagStatus(I2C2,I2C_FLAG_BUSY) != RESET) // 设备忙等待
	{
		if((--I2C2Timeout) == 0) return I2C2_TIMEOUT_UserCallback();
	}

	// 配置从机设备地址 发送数据长度 发送模式 发送开始写信号
	I2C_TransferHandling(I2C2, Slave_Address, RegAddressBitWide, I2C_SoftEnd_Mode, I2C_Generate_Start_Write);
	
	I2C2Timeout = I2C_LONG_TIMEOUT; 
	while(I2C_GetFlagStatus(I2C2,I2C_FLAG_TXIS) == RESET) // 等待发送状态复位
	{
		if((--I2C2Timeout) == 0) return I2C2_TIMEOUT_UserCallback();
	}

	// 如果寄存器为16位 发送寄存器高8位地址
	if(RegAddressBitWide == Bit16)
	{
		I2C_SendData(I2C2, REG_Address >> 8); 
		
		I2C2Timeout = I2C_LONG_TIMEOUT; 
		while(I2C_GetFlagStatus(I2C2,I2C_FLAG_TXIS) == RESET) // 等待发送完成
		{
			if((--I2C2Timeout) == 0) return I2C2_TIMEOUT_UserCallback();
		}			
	}	
	
	I2C_SendData(I2C2, REG_Address & 0xff); // 发送寄存器地址
	
	I2C2Timeout = I2C_LONG_TIMEOUT; 
	while(I2C_GetFlagStatus(I2C2,I2C_ISR_TC) == RESET) // 等待软件停止模式(I2C_SoftEnd_Mode)下,数据发送完成
	{
		if((--I2C2Timeout) == 0) return I2C2_TIMEOUT_UserCallback();
	}	
	
  // 配置从机设备地址 接收数据长度 发送模式 发送开始读信号
	I2C_TransferHandling(I2C2, Slave_Address, len, I2C_AutoEnd_Mode, I2C_Generate_Start_Read);	
	
	uint8_t length = 0;
	while( length != len)
	{
		I2C2Timeout = I2C_LONG_TIMEOUT; 
		while(I2C_GetFlagStatus(I2C2,I2C_ISR_RXNE) == RESET) // 等待接收寄存器非空
		{
			if((--I2C2Timeout) == 0) return I2C2_TIMEOUT_UserCallback();
		}

		REG_data[length] = I2C_ReceiveData(I2C2);
		length++;
	}
	
  I2C2Timeout = I2C_LONG_TIMEOUT;
  while(I2C_GetFlagStatus(I2C2, I2C_ISR_STOPF) == RESET) // 等待AutoEnd模式下停止信号
  {
    if((--I2C2Timeout) == 0) return I2C2_TIMEOUT_UserCallback();
  }   
  
  I2C_ClearFlag(I2C2, I2C_ICR_STOPCF);
	return 0;		
}

bsp_hard_i2c.h程序如下:

#ifndef _BSP_HARD_I2C_H_
#define _BSP_HARD_I2C_H_

#include "stm32f0xx.h"
#include 

 // IO口设置
#define	IIC_GPIOx  GPIOB
#define Pin_SCL		GPIO_Pin_10
#define Pin_SDA		GPIO_Pin_11

#define I2C_TIMEOUT       ((uint32_t)0x1000)
#define I2C_LONG_TIMEOUT  ((uint32_t)(10 * I2C_TIMEOUT))
#define Bit16							2
#define Bit8							1 
#define RegAddressBitWide Bit8 // 寄存器位宽	 1:8位    2:16位

void I2C2_Hard_Init(void);
uint8_t I2C2_TIMEOUT_UserCallback(void);

uint8_t I2C2_Write_NByte(const uint8_t Slave_Address, const uint16_t REG_Address, const uint8_t* REG_data, const uint8_t len);
uint8_t I2C2_Read_NByte(const uint8_t Slave_Address, const uint16_t REG_Address, uint8_t *REG_data, const uint8_t len);

#endif

 

你可能感兴趣的:(传感器开发)