I2C功能框图(F1系列)
STM32 的 I2C 外设可用作通讯的主机及从机,支持 100Kbit/s 和 400Kbit/s 的速率,支持 7 位、 10
位设备地址,支持 DMA 数据传输,并具有数据校验功能,I2C 外设还支持 SMBus2.0 协议。
I2C 初始化结构体详解
typedef struct {
uint32_t ClockSpeed; /*!< 设置 SCL 时钟频率,此值要低于 40 0000*/
uint32_t DutyCycle; /* 指定时钟占空比,可选 low/high = 2:1 及 16:9 模式 */
uint32_t OwnAddress1; /* 指定自身的 I2C 设备地址 1,可以是 7-bit 或者 10-,→bit*/
uint32_t AddressingMode; /* 指定地址的长度模式,可以是 7bit 模式或者 10bit 模式 */
uint32_t DualAddressMode; /* 设置双地址模式 */
uint32_t OwnAddress2; /* 指定自身的 I2C 设备地址 2,只能是 7-bit */
uint32_t GeneralCallMode; /* 指定广播呼叫模式 */
uint32_t NoStretchMode; /* 指定禁止时钟延长模式 */
} I2C_InitTypeDef;
(1) ClockSpeed
本成员设置的是 I2C 的传输速率,在调用初始化函数时,函数会根据我们输入的数值写入到 I2C的时钟控制寄存器 CCR。
(2) DutyCycle
本成员设置的是 I2C 的 SCL 线时钟的占空比。该配置有两个选择,分别为低电平时间比高电平时间为 2: 1 (I2C_DUTYCYCLE_2) 和 16: 9 (I2C_DUTYCYCLE_16_9)。其实这两个模式的比例差别并不大,一般要求都不会如此严格,这里随便选就可以了。
(3) OwnAddress1
本成员配置的是 STM32 的 I2C 设备自身地址 1,每个连接到 I2C 总线上的设备都要有一个自己的地址,作为主机也不例外。地址可设置为 7 位或 10 位 (受下面 (3) AddressingMode 成员决定),只要该地址是 I2C 总线上唯一的即可。STM32 的 I2C 外设可同时使用两个地址,即同时对两个地址作出响应,这个结构成员 OwnAddress1 配置的是默认的、 OAR1 寄存器存储的地址,若需要设置第二个地址寄存器 OAR2,可使用 DualAddressMode 成员使能,然后设置 OwnAddress2 成员即可, OAR2 不支持 10 位地址。
(4) AddressingMode
本成员选择 I2C 的寻址模式是 7 位还是 10 位地址。这需要根据实际连接到 I2C 总线上设备的地址进行选择,这个成员的配置也影响到 OwnAddress1 成员,只有这里设置成 10 位模式时, OwnAddress1 才支持 10 位地址。
(5) DualAddressMode
本成员配置的是 STM32 的 I2C 设备自己的地址,每个连接到 I2C 总线上的设备都要有一个自己的地址,作为主机也不例外。地址可设置为 7 位或 10 位 (受下面 I2C_dual_addressing_mode 成员决定),只要该地址是 I2C 总线上唯一的即可。
STM32 的 I2C 外设可同时使用两个地址,即同时对两个地址作出响应,这个结构成员I2C_OwnAddress1 配置的是默认的、 OAR1 寄存器存储的地址,若需要设置第二个地址寄存器OAR2,可使用 I2C_OwnAddress2Config 函数来配置, OAR2 不支持 10 位地址。
(6) OwnAddress2
本成员配置的是 STM32 的 I2C 设备自身地址 2,每个连接到 I2C 总线上的设备都要有一个自己的地址,作为主机也不例外。地址可设置为 7 位,只要该地址是 I2C 总线上唯一的即可。
(7) GeneralCallMode
本成员是关于 I2C 从模式时的广播呼叫模式设置。
(8) NoStretchMode
本成员是关于 I2C 禁止时钟延长模式设置,用于在从模式下禁止时钟延长。它在主模式下必须保持关闭。配置完这些结构体成员值,调用库函数 HAL_I2C_Init 即可把结构体的配置写入到寄存器中。
Tips:
什么是I2C时钟延展(SCLStretching)?在I2C的主从通信过程中,总线上的SCL时钟总是由主机来产生和控制的,但如果从机跟不上主机的速率,I2C协议规定从机是可以通过将SCL时钟线拉低来暂停一个传输的,直到从机释放掉SCL线,传输继续进行。
I2C读写EEPROM
I2C初始化代码
static void I2C_Mode_Config(void)
{I2C_Handle.Instance = I2Cx;I2C_Handle.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;I2C_Handle.Init.ClockSpeed = 400000;I2C_Handle.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;I2C_Handle.Init.DutyCycle = I2C_DUTYCYCLE_2;I2C_Handle.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;I2C_Handle.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;I2C_Handle.Init.OwnAddress1 = I2C_OWN_ADDRESS7 ;I2C_Handle.Init.OwnAddress2 = 0; /* Init the I2C */HAL_I2C_Init(&I2C_Handle);
// HAL_I2CEx_AnalogFilter_Config(&I2C_Handle, ENABLE); 开启过滤器过滤噪声
}
void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c)
{ GPIO_InitTypeDef GPIO_InitStruct;/*##-1- Enable peripherals and GPIO Clocks #################################*//* Enable GPIO TX/RX clock */I2Cx_SCL_GPIO_CLK_ENABLE();I2Cx_SDA_GPIO_CLK_ENABLE();/* Enable I2C1 clock */I2Cx_CLK_ENABLE(); /*##-2- Configure peripheral GPIO ##########################################*/ /* I2C TX GPIO pin configuration */GPIO_InitStruct.Pin = I2Cx_SCL_PIN;GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(I2Cx_SCL_GPIO_PORT, &GPIO_InitStruct);/* I2C RX GPIO pin configuration */GPIO_InitStruct.Pin = I2Cx_SDA_PIN; HAL_GPIO_Init(I2Cx_SDA_GPIO_PORT, &GPIO_InitStruct);/* Force the I2C peripheral clock reset */ __HAL_RCC_I2C1_FORCE_RESET();/* Release the I2C peripheral clock reset */ __HAL_RCC_I2C1_RELEASE_RESET();
}
注意:在HAL库中,在HAL_I2C_Init(&I2C_Handle);这个函数内会自动调用HAL_I2C_MspInit,这也是HAL库提供给用户的配置I2C的GPIO函数。
I2C写EEPROM函数
/*该函数会自动判断是否超页,如超页自动换页*/
/*** @brief 将缓冲区中的数据写到I2C EEPROM中* @param * @arg pBuffer:缓冲区指针* @arg WriteAddr:写地址* @arg NumByteToWrite:写的字节数* @retval 无*/
void I2C_EE_BufferWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite)
{uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;Addr = WriteAddr % EEPROM_PAGESIZE;count = EEPROM_PAGESIZE - Addr;NumOfPage = NumByteToWrite / EEPROM_PAGESIZE;NumOfSingle = NumByteToWrite % EEPROM_PAGESIZE;/* If WriteAddr is I2C_PageSize aligned */if(Addr == 0) {/* If NumByteToWrite < I2C_PageSize */if(NumOfPage == 0) {I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);}/* If NumByteToWrite > I2C_PageSize */else {while(NumOfPage--){I2C_EE_PageWrite(pBuffer, WriteAddr, EEPROM_PAGESIZE); WriteAddr += EEPROM_PAGESIZE;pBuffer += EEPROM_PAGESIZE;}if(NumOfSingle!=0){I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);}}}/* If WriteAddr is not I2C_PageSize aligned */else {/* If NumByteToWrite < I2C_PageSize */if(NumOfPage== 0) {I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);}/* If NumByteToWrite > I2C_PageSize */else{NumByteToWrite -= count;NumOfPage = NumByteToWrite / EEPROM_PAGESIZE;NumOfSingle = NumByteToWrite % EEPROM_PAGESIZE; if(count != 0){ I2C_EE_PageWrite(pBuffer, WriteAddr, count);WriteAddr += count;pBuffer += count;} while(NumOfPage--){I2C_EE_PageWrite(pBuffer, WriteAddr, EEPROM_PAGESIZE);WriteAddr += EEPROM_PAGESIZE;pBuffer += EEPROM_PAGESIZE; }if(NumOfSingle != 0){I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle); }}}
}/*** @brief 写一个字节到I2C EEPROM中* @param * @arg pBuffer:缓冲区指针* @arg WriteAddr:写地址 * @retval 无*/
uint32_t I2C_EE_ByteWrite(uint8_t* pBuffer, uint8_t WriteAddr)
{HAL_StatusTypeDef status = HAL_OK;status = HAL_I2C_Mem_Write(&I2C_Handle, EEPROM_ADDRESS, (uint16_t)WriteAddr, I2C_MEMADD_SIZE_8BIT, pBuffer, 1, 100); /* Check the communication status */if(status != HAL_OK){/* Execute user timeout callback *///I2Cx_Error(Addr);}while (HAL_I2C_GetState(&I2C_Handle) != HAL_I2C_STATE_READY){}/* Check if the EEPROM is ready for a new operation */while (HAL_I2C_IsDeviceReady(&I2C_Handle, EEPROM_ADDRESS, EEPROM_MAX_TRIALS, I2Cx_TIMEOUT_MAX) == HAL_TIMEOUT);/* Wait for the end of the transfer */while (HAL_I2C_GetState(&I2C_Handle) != HAL_I2C_STATE_READY){}return status;
}/*** @brief 在EEPROM的一个写循环中可以写多个字节,但一次写入的字节数* 不能超过EEPROM页的大小,AT24C02每页有8个字节* @param * @arg pBuffer:缓冲区指针* @arg WriteAddr:写地址* @arg NumByteToWrite:写的字节数* @retval 无*/
uint32_t I2C_EE_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint8_t NumByteToWrite)
{HAL_StatusTypeDef status = HAL_OK;/* Write EEPROM_PAGESIZE */status=HAL_I2C_Mem_Write(&I2C_Handle, EEPROM_ADDRESS,WriteAddr, I2C_MEMADD_SIZE_8BIT, (uint8_t*)(pBuffer),NumByteToWrite, 100);while (HAL_I2C_GetState(&I2C_Handle) != HAL_I2C_STATE_READY){}/* Check if the EEPROM is ready for a new operation */while (HAL_I2C_IsDeviceReady(&I2C_Handle, EEPROM_ADDRESS, EEPROM_MAX_TRIALS, I2Cx_TIMEOUT_MAX) == HAL_TIMEOUT);/* Wait for the end of the transfer */while (HAL_I2C_GetState(&I2C_Handle) != HAL_I2C_STATE_READY){}return status;
}/*** @brief 从EEPROM里面读取一块数据 * @param * @arg pBuffer:存放从EEPROM读取的数据的缓冲区指针* @arg WriteAddr:接收数据的EEPROM的地址* @arg NumByteToWrite:要从EEPROM读取的字节数* @retval 无*/
uint32_t I2C_EE_BufferRead(uint8_t* pBuffer, uint8_t ReadAddr, uint16_t NumByteToRead)
{HAL_StatusTypeDef status = HAL_OK;status=HAL_I2C_Mem_Read(&I2C_Handle,EEPROM_ADDRESS,ReadAddr, I2C_MEMADD_SIZE_8BIT, (uint8_t *)pBuffer, NumByteToRead,1000);return status;
}