IIC

发布于 2023-04-05  463 次阅读


IIC简介

IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行总线,用于连接微
控制器以及其外围设备。它是由数据线 SDA 和时钟线 SCL 构成的串行总线,可发送和接收数
据,在 CPU 与被控 IC 之间、 IC 与 IC 之间进行双向传送。

特点

  • 总线由数据线 SDA 和时钟线 SCL 构成的串行总线,数据线用来传输数据,时钟线用来同步数据收发。
  • 总线上每一个器件都有一个唯一的地址识别,所以我们只需要知道器件的地址,根据时序就可以实现微控制器与器件之间的通信。
  • 数据线 SDA 和时钟线 SCL 都是双向线路,都通过一个电流源或上拉电阻连接到正的电压,所以当总线空闲的时候,这两条线路都是高电平。
  • 总线上数据的传输速率在标准模式下可达 100kbit/s 在快速模式下可达 400kbit/s 在高模式下可达3.4Mbit/s。
  • 总线支持设备连接。在使用 IIC 通信总线时,可以有多个具备 IIC 通信能力的设备挂载在上面,同时支持多个主机和多个从机,连接到总线的接口数量只由总线电容 400pF 的限制决定。 IIC 总线挂载多个器件的示意图,如下图所示:

协议

起始信号

当 SCL 为高电平期间, SDA 由高到低的跳变,起始信号是一种电平跳变时序信号,而不是一个电平信号。该信号由主机发出,在起始信号产生后,总线就处于被占用状态,准备数据传输。

数据有效性

IIC 总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。数据在 SCL 的上升沿到来之前就需准备好。并在下降沿到来之前必须稳定。

数据传输

在 IIC 总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在 SCL 串行时钟的配合下,在 SDA 上逐位地串行传送每一位数据。数据位的传输是边沿触发。

应答信号

发送器每发送一个字节,就在时钟脉冲 9 期间释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK 简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。

停止信号

当 SCL 为高电平期间, SDA 由低到高的跳变;停止信号也是一种电平跳变时序信号,而不是一个电平信号。该信号由主机发出,在停止信号发出后,总线就处于空闲状态。

空闲状态

IIC 总线的 SDA 和 SCL 两条信号线同时处于高电平时,规定为总线的空闲状态。

配置步骤

软件模拟IIC

定义

#ifndef __IIC_H_
#define __IIC_H_
#include "stm32f1xx.h"
#include "./SYSTEM/delay/delay.h"
/* 引脚 定义 */
#define IIC_SCL_GPIO_PORT GPIOB
#define IIC_SCL_GPIO_PIN GPIO_PIN_6
#define IIC_SCL_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)
#define IIC_SDA_GPIO_PORT GPIOB
#define IIC_SDA_GPIO_PIN GPIO_PIN_7
#define IIC_SDA_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)

/*内存地址大小*/
#define IIC_MEMADD_SIZE_8BIT            0x00000001U
#define IIC_MEMADD_SIZE_16BIT           0x00000010U
/* IO 操作 */
#define IIC_SCL(x) do{ x ? \
HAL_GPIO_WritePin(IIC_SCL_GPIO_PORT, IIC_SCL_GPIO_PIN, GPIO_PIN_SET):\
HAL_GPIO_WritePin(IIC_SCL_GPIO_PORT, IIC_SCL_GPIO_PIN, GPIO_PIN_RESET);\
}while(0) /* SCL */
#define IIC_SDA(x) do{ x ? \
HAL_GPIO_WritePin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN, GPIO_PIN_SET):\
HAL_GPIO_WritePin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN, GPIO_PIN_RESET);\
}while(0) /* SDA */
/* 读取 SDA */
#define IIC_READ_SDA HAL_GPIO_ReadPin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN)

extern void Iic_Init(void);
extern void iic_stop(void);
extern void iic_ack(void);
extern void iic_nack(void);
extern void iic_mem_write( uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData);

#endif

初始化IIC IO

/**
* @brief 初始化 IIC
* @param 无
* @retval 无
*/
void Iic_Init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    IIC_SCL_GPIO_CLK_ENABLE(); /* SCL 引脚时钟使能 */
    IIC_SDA_GPIO_CLK_ENABLE(); /* SDA 引脚时钟使能 */
    gpio_init_struct.Pin = IIC_SCL_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
    gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
    HAL_GPIO_Init(IIC_SCL_GPIO_PORT, &gpio_init_struct); /* SCL */
    /* SDA 引脚模式设置,开漏输出,上拉, 这样就不用再设置 IO 方向了, 开漏输出的时候(=1),
    也可以读取外部信号的高低电平 */
    gpio_init_struct.Pin = IIC_SDA_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD; /* 开漏输出 */
    HAL_GPIO_Init(IIC_SDA_GPIO_PORT, &gpio_init_struct); /* SDA */
    iic_stop(); /* 停止总线上所有设备 */
}

IIC延时函数

/**
* @brief IIC 延时函数,用于控制 IIC 读写速度
* @param 无
* @retval 无
*/
static void iic_delay(void)
{
    delay_us(2); /* 2us 的延时, 读写速度在 250Khz 以内 */
}

IIC起始信号

void iic_start(void)
{
    IIC_SDA(1);
    IIC_SCL(1);
    iic_delay();
    IIC_SDA(0); /* START 信号: 当 SCL 为高时, SDA 从高变成低, 表示起始信号 */
    iic_delay();
    IIC_SCL(0); /* 钳住 I2C 总线,准备发送或接收数据 */
    iic_delay();
}

IIC停止信号

/**
* @brief 产生 IIC 停止信号
* @param 无
* @retval 无
*/
void iic_stop(void)
{
    IIC_SDA(0); /* STOP 信号: 当 SCL 为高时, SDA 从低变成高, 表示停止信号 */
    iic_delay();
    IIC_SCL(1);
    iic_delay();
    IIC_SDA(1); /* 发送 I2C 总线结束信号 */
    iic_delay();
}

发送一个字节

/**
* @brief IIC 发送一个字节
* @param data: 要发送的数据
* @retval 无
*/
void iic_send_byte(uint8_t data)
{
    uint8_t t;
    for (t = 0; t < 8; t++)
    {
        IIC_SDA((data & 0x80) >> 7); /* 高位先发送 */
        iic_delay();
        IIC_SCL(1);
        iic_delay();
        IIC_SCL(0);
        data <<= 1; /* 左移 1 位,用于下一次发送 */
    }
    IIC_SDA(1); /* 发送完成, 主机释放 SDA 线 */
}

读取一个字节

/**
 * @brief IIC 读取一个字节
* @param ack: ack=1 时,发送 ack; ack=0 时,发送 nack
* @retval 接收到的数据
*/
uint8_t iic_read_byte(uint8_t ack)
{
    uint8_t i, receive = 0;
    for (i = 0; i < 8; i++ ) /* 接收 1 个字节数据 */
    {
    receive <<= 1; /* 高位先输出,所以先收到的数据位要左移 */
    IIC_SCL(1);
    iic_delay();
    if (IIC_READ_SDA)
    {
        receive++;
    }
        IIC_SCL(0);
        iic_delay();
    }
    if (!ack)
    {
        iic_nack(); /* 发送 nACK */
    }
    else
    {
        iic_ack(); /* 发送 ACK */
    }
    return receive;
}

等待应答

/**
* @brief 等待应答信号到来
* @param 无
* @retval 1,接收应答失败
* 0,接收应答成功
*/
uint8_t iic_wait_ack(void)
{
	uint8_t waittime = 0;
	uint8_t rack = 0;
	IIC_SDA(1); /* 主机释放 SDA 线(此时外部器件可以拉低 SDA 线) */
	iic_delay();
	IIC_SCL(1); /* SCL=1, 此时从机可以返回 ACK */
	iic_delay();
	while (IIC_READ_SDA) /* 等待应答 */
	{
		
	if (waittime > 250)
	{
		iic_stop();
		rack = 1;
	break;
	}
	}
		IIC_SCL(0); /* SCL=0, 结束 ACK 检查 */
		iic_delay();
		return rack;
}

产生ACK应答

/**
* @brief 产生 ACK 应答
* @param 无
* @retval 无
*/
void iic_ack(void)
{
	IIC_SDA(0); /* SCL 0 -> 1 时 SDA = 0,表示应答 */
	iic_delay();
	IIC_SCL(1); /* 产生一个时钟 */
	iic_delay();
	IIC_SCL(0);
	iic_delay();
	IIC_SDA(1); /* 主机释放 SDA 线 */
	iic_delay();
}

不产生ACK应答

/**
* @brief 不产生 ACK 应答
* @param 无
* @retval 无
*/
void iic_nack(void)
{
	IIC_SDA(1); /* SCL 0 -> 1 时 SDA = 1,表示不应答 */
	iic_delay();
	IIC_SCL(1); /* 产生一个时钟 */
	iic_delay();
	IIC_SCL(0);
	iic_delay();
}

IIC写数据函数

/*
IIC写数据函数
DevAddress:目标设备地址
MemAddress:目标设备内部读写的地址,也可认为是STM32与从机通讯时发送的第一个数据
MemAddSize:内存地址大小,可选预定义IIC_MEMADD_SIZE_8BIT 或 IIC_MEMADD_SIZE_16BIT ,在OLED驱动芯片SSD1306中,内存地址用8bit表示,选IIC_MEMADD_SIZE_8BIT
pData:指向传输数据的指针
*/
void iic_mem_write( uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData)
{
    iic_start();
    iic_send_byte(DevAddress);
    iic_wait_ack();
    iic_send_byte(MemAddress);
    iic_wait_ack();
    iic_send_byte(*pData);
    iic_wait_ack();
    iic_stop();
}

硬件IIC

初始化IIC

I2C_HandleTypeDef hi2c1;

/* I2C1 init function */
void MX_I2C1_Init(void)
{

  hi2c1.Instance = I2C1;
  hi2c1.Init.ClockSpeed = 100000;
  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;
  if (HAL_I2C_Init(&hi2c1) != HAL_OK)
  {
    Error_Handler();
  }

}

void HAL_I2C_MspInit(I2C_HandleTypeDef* i2cHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(i2cHandle->Instance==I2C1)
  {
  /* USER CODE BEGIN I2C1_MspInit 0 */

  /* USER CODE END I2C1_MspInit 0 */
  
    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**I2C1 GPIO Configuration    
    PB6     ------> I2C1_SCL
    PB7     ------> I2C1_SDA 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    /* I2C1 clock enable */
    __HAL_RCC_I2C1_CLK_ENABLE();
  /* USER CODE BEGIN I2C1_MspInit 1 */

  /* USER CODE END I2C1_MspInit 1 */
  }
}

void HAL_I2C_MspDeInit(I2C_HandleTypeDef* i2cHandle)
{

  if(i2cHandle->Instance==I2C1)
  {
  /* USER CODE BEGIN I2C1_MspDeInit 0 */

  /* USER CODE END I2C1_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_I2C1_CLK_DISABLE();
  
    /**I2C1 GPIO Configuration    
    PB6     ------> I2C1_SCL
    PB7     ------> I2C1_SDA 
    */
    HAL_GPIO_DeInit(GPIOB, GPIO_PIN_6|GPIO_PIN_7);

  /* USER CODE BEGIN I2C1_MspDeInit 1 */

  /* USER CODE END I2C1_MspDeInit 1 */
  }
} 

IIC写函数

HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
  • *hi2c 设置使用的是那个IIC 例:&hi2c2
  • DevAddress 写入的地址 设置写入数据的地址 例 0xA0
  • *pData 需要写入的数据
  • Size 要发送的字节数
  • Timeout 最大传输时间,超过传输时间将自动退出传输函数

IIC读函数

HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
  • *hi2c: 设置使用的是那个IIC 例:&hi2c2
  • DevAddress: 写入的地址 设置写入数据的地址 例 0xA0
  • *pDat:a 存储读取到的数据
  • Size: 发送的字节数
  • Timeout: 最大读取时间,超过时间将自动退出读取函数

IIC写数据函数

HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
  • *hi2c: I2C设备号指针,设置使用的是那个IIC 例:&hi2c2
  • DevAddress: 从设备地址 从设备的IIC地址 例E2PROM的设备地址 0xA0
  • MemAddress: 从机寄存器地址 ,每写入一个字节数据,地址就会自动+1
  • MemAddSize: 从机寄存器地址字节长度 8位或16位
    • 写入数据的字节类型 8位还是16位
      I2C_MEMADD_SIZE_8BIT
      I2C_MEMADD_SIZE_16BIT

使用HAL_I2C_Mem_Write等于先使用HAL_I2C_Master_Transmit传输第一个寄存器地址,再用HAL_I2C_Master_Transmit传输写入第一个寄存器的数据。

IIC读数据函数

HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
  • *hi2c: I2C设备号指针,设置使用的是那个IIC 例:&hi2c2
  • DevAddress: 从设备地址 从设备的IIC地址 例E2PROM的设备地址 0xA0
  • MemAddress: 从机寄存器地址 ,每写入一个字节数据,地址就会自动+1
  • MemAddSize: 从机寄存器地址字节长度 8位或16位
    • 写入数据的字节类型 8位还是16位
      I2C_MEMADD_SIZE_8BIT
      I2C_MEMADD_SIZE_16BIT

在传输过程,寄存器地址和源数据地址是会自加的。

至于读函数也是如此,因此用HAL_I2C_Mem_Write和HAL_I2C_Mem_Read,来写读指定设备的指定寄存器数据是十分方便的,让设计过程省了好多步骤。

如果只往某个外设中写数据,则用Master_Transmit。 如果是外设里面还有子地址,例如E2PROM,有设备地址,还有每个数据的寄存器存储地址。则用Mem_Write。Mem_Write是2个地址,Master_Transmit只有从机地址

  • alipay_img
  • wechat_img
想法不去做终究就只是想法
最后更新于 2023-04-05