一.I2C 协议简介
I2C 通讯协议 (Inter - Integrated Circuit) 是由 Phiilps 公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要 USART、CAN 等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路 (IC) 间的通讯。
IIC协议采用半双工(也有资料称其为全双工)工作模式,即在同一时间只能进行单向的数据传输。它由两条信号线组成:一条是数据线SDA(Serial Data Line),用于传输数据;另一条是时钟线SCL(Serial Clock Line),由主设备提供时钟信号,以确保所有连接到总线的设备同步进行数据交换。在I2C总线上,每个从设备都有一个唯一的地址,主设备通过发送这个地址来选择与其通信的目标设备。
STM32 的 I2C 外设可用作通讯的主机及从机,支持 100Kbit/s 和 400Kbit/s 的速率,支持 7 位、10位设备地址,支持 DMA 数据传输,并具有数据校验功能。
二.AT24C16 eeprom读写
1.AT24C16概述
AT24c16 一共16Kbit,也就是2K Byte;每页大小16byte,一共128页。
2.AT24C16读写
(1)AT24C16不用外部硬件引脚控制A2 A1 A0;全部由软件控制
(2)软件内需要设置8个地址,每个地址内可以读写256个byte的数据
3.代码实现如下:
使用HAL库方式读取。
(1)iic.h
#ifndef __I2C_EE_H
#define __I2C_EE_H
#include "stm32f4xx.h"
/* AT24C04/08A/16A每页有16个字节 */
#define EEPROM_PAGESIZE 16
#define I2C_OWN_ADDRESS7 0X1A
#define I2Cx I2C1
#define I2Cx_CLK_ENABLE() __HAL_RCC_I2C1_CLK_ENABLE()
#define I2Cx_SDA_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()
#define I2Cx_SCL_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()
#define I2Cx_FORCE_RESET() __HAL_RCC_I2C1_FORCE_RESET()
#define I2Cx_RELEASE_RESET() __HAL_RCC_I2C1_RELEASE_RESET()
/* Definition for I2Cx Pins */
#define I2Cx_SCL_PIN GPIO_PIN_8
#define I2Cx_SCL_GPIO_PORT GPIOB
#define I2Cx_SCL_AF GPIO_AF4_I2C1
#define I2Cx_SDA_PIN GPIO_PIN_9
#define I2Cx_SDA_GPIO_PORT GPIOB
#define I2Cx_SDA_AF GPIO_AF4_I2C1
/*等待超时时间*/
#define I2CT_FLAG_TIMEOUT ((uint32_t)0x1000)
#define I2CT_LONG_TIMEOUT ((uint32_t)(10 * I2CT_FLAG_TIMEOUT))
#define I2Cx_TIMEOUT_MAX 300
/* Maximum number of trials for HAL_I2C_IsDeviceReady() function */
#define EEPROM_MAX_TRIALS 300
/*信息输出*/
#define EEPROM_DEBUG_ON 0
#define EEPROM_INFO(fmt,arg...) printf("<<-EEPROM-INFO->> "fmt"\n",##arg)
#define EEPROM_ERROR(fmt,arg...) printf("<<-EEPROM-ERROR->> "fmt"\n",##arg)
#define EEPROM_DEBUG(fmt,arg...) do{\
if(EEPROM_DEBUG_ON)\
printf("<<-EEPROM-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
}while(0)
/* EEPROM Addresses defines */
#define EEPROM_Block0_ADDRESS 0xA0 /* E2 = 0 */
#define EEPROM_Block1_ADDRESS 0xA2 /* E2 = 0 */
#define EEPROM_Block2_ADDRESS 0xA4 /* E2 = 0 */
#define EEPROM_Block3_ADDRESS 0xA6 /* E2 = 0 */
#define EEPROM_Block4_ADDRESS 0xA8 /* E2 = 0 */
#define EEPROM_Block5_ADDRESS 0xAa /* E2 = 0 */
#define EEPROM_Block6_ADDRESS 0xAc /* E2 = 0 */
#define EEPROM_Block7_ADDRESS 0xAe /* E2 = 0 */
extern uint8_t EEPROM_ADDRESS;
void I2C_EE_Init(void);
void I2C_EE_BufferWrite(uint8_t* pBuffer, uint16_t WriteAddr, uint16_t NumByteToWrite);
uint32_t I2C_EE_ByteWrite(uint8_t* pBuffer, uint16_t WriteAddr);
uint32_t I2C_EE_PageWrite(uint8_t* pBuffer, uint16_t WriteAddr, uint16_t NumByteToWrite);
uint32_t I2C_EE_BufferRead(uint8_t* pBuffer, uint16_t ReadAddr, uint16_t NumByteToRead);
(2)iic.c
#include "iic.h"
uint8_t EEPROM_ADDRESS;
I2C_HandleTypeDef I2C_Handle;
/**
* @brief I2C MSP Initialization
* This function configures the hardware resources used in this example:
* - Peripheral's clock enable
* - Peripheral's GPIO Configuration
* - DMA configuration for transmission request by peripheral
* - NVIC configuration for DMA interrupt request enable
* @param hi2c: I2C handle pointer
* @retval None
*/
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_FAST;
GPIO_InitStruct.Alternate = I2Cx_SCL_AF;
HAL_GPIO_Init(I2Cx_SCL_GPIO_PORT, &GPIO_InitStruct);
/* I2C RX GPIO pin configuration */
GPIO_InitStruct.Pin = I2Cx_SDA_PIN;
GPIO_InitStruct.Alternate = I2Cx_SDA_AF;
HAL_GPIO_Init(I2Cx_SDA_GPIO_PORT, &GPIO_InitStruct);
/* Force the I2C peripheral clock reset */
I2Cx_FORCE_RESET() ;
/* Release the I2C peripheral clock reset */
I2Cx_RELEASE_RESET();
}
/**
* @brief I2C 工作模式配置
* @param 无
* @retval 无
*/
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, I2C_ANALOGFILTER_ENABLE);
}
/**
* @brief I2C 外设(EEPROM)初始化
* @param 无
* @retval 无
*/
void I2C_EE_Init(void)
{
I2C_Mode_Config();
EEPROM_ADDRESS = EEPROM_Block0_ADDRESS;
}
/**
* @brief 将缓冲区中的数据写到I2C EEPROM中
* @param
* @arg pBuffer:缓冲区指针
* @arg WriteAddr:写地址
* @arg NumByteToWrite:写的字节数
* @retval 无
*/
void I2C_EE_BufferWrite(uint8_t* pBuffer, uint16_t WriteAddr, uint16_t NumByteToWrite)
{
printf("I2C_EE_BufferWrite addr: %d\r\n", WriteAddr);
uint16_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, uint16_t WriteAddr)
{
HAL_StatusTypeDef status = HAL_OK;
status = HAL_I2C_Mem_Write(&I2C_Handle, g_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, g_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, uint16_t WriteAddr, uint16_t NumByteToWrite)
{
HAL_StatusTypeDef status = HAL_OK;
/* Write EEPROM_PAGESIZE */
status=HAL_I2C_Mem_Write(&I2C_Handle, g_EEPROM_ADDRESS,WriteAddr, I2C_MEMADD_SIZE_8BIT, 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, g_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, uint16_t ReadAddr, uint16_t NumByteToRead)
{
HAL_StatusTypeDef status = HAL_OK;
status=HAL_I2C_Mem_Read(&I2C_Handle,g_EEPROM_ADDRESS,ReadAddr, I2C_MEMADD_SIZE_8BIT, (uint8_t *)pBuffer, NumByteToRead,1000);
return status;
}