1. I2C 固件库简介
I2C 初始化结构体
typedef struct {
uint32_t I2C_ClockSpeed; /*!< 设置 SCL 时钟频率,此值要低于 400000*/
uint16_t I2C_Mode; /*!< 指定工作模式,可选 I2C 模式及 SMBUS 模式 */
uint16_t I2C_DutyCycle; /*指定时钟占空比,可选 low/high = 2:1 及 16:9 模式*/
uint16_t I2C_OwnAddress1; /*!< 指定自身的 I2C 设备地址 */
uint16_t I2C_Ack; /*!< 使能或关闭响应(一般都要使能) */
uint16_t I2C_AcknowledgedAddress; /*!< 指定地址的长度,可为 7 位及 10 位 */
} I2C_InitTypeDef;
2. I2C—读写 EEPROM 实验
2.1 硬件设计
2.2 软件设计
2.2.1 编程要点
(1) 配置通讯使用的目标引脚为开漏模式;
(2) 使能 I2C 外设的时钟;
(3) 配置 I2C 外设的模式、地址、速率等参数并使能 I2C 外设;
(4) 编写基本 I2C 按字节收发的函数;
(5) 编写读写 EEPROM 存储内容的函数;
(6) 编写测试程序,对读写数据进行校验。
2.2.2 代码分析
I2C 硬件配置相关的宏
/**************************I2C 参数定义,I2C1 或 I2C2*********************/
#define EEPROM_I2Cx I2C1
#define EEPROM_I2C_APBxClock_FUN RCC_APB1PeriphClockCmd
#define EEPROM_I2C_CLK RCC_APB1Periph_I2C1
#define EEPROM_I2C_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
#define EEPROM_I2C_GPIO_CLK RCC_APB2Periph_GPIOB
#define EEPROM_I2C_SCL_PORT GPIOB
#define EEPROM_I2C_SCL_PIN GPIO_Pin_6
#define EEPROM_I2C_SDA_PORT GPIOB
#define EEPROM_I2C_SDA_PIN GPIO_Pin_7
/* STM32 I2C 快速模式 */
#define I2C_Speed 400000 //*
/* 这个地址只要与 STM32 外挂的 I2C 器件地址不一样即可 */
#define I2Cx_OWN_ADDRESS7 0X0A
/* AT24C01/02 每页有 8 个字节 */
#define I2C_PageSize 8
初始化 I2C 的 GPIO
static void I2C_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 使能与 I2C 有关的时钟 */
EEPROM_I2C_APBxClock_FUN ( EEPROM_I2C_CLK, ENABLE );
EEPROM_I2C_GPIO_APBxClock_FUN ( EEPROM_I2C_GPIO_CLK, ENABLE );
/* I2C_SCL、I2C_SDA*/
GPIO_InitStructure.GPIO_Pin = EEPROM_I2C_SCL_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 开漏输出
GPIO_Init(EEPROM_I2C_SCL_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = EEPROM_I2C_SDA_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 开漏输出
GPIO_Init(EEPROM_I2C_SDA_PORT, &GPIO_InitStructure);
}
配置 I2C 的模式
/**
* @brief I2C 工作模式配置
* @param 无
* @retval 无
*/
static void I2C_Mode_Configu(void)
{
I2C_InitTypeDef I2C_InitStructure;
/* I2C 配置 */
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
/* 高电平数据稳定,低电平数据变化 SCL 时钟线的占空比 */
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_OwnAddress1 =I2Cx_OWN_ADDRESS7;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;
/* I2C 的寻址模式 */
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
/* 通信速率 */
I2C_InitStructure.I2C_ClockSpeed = I2C_Speed;
/* I2C 初始化 */
I2C_Init(EEPROM_I2Cx, &I2C_InitStructure);
/* 使能 I2C */
I2C_Cmd(EEPROM_I2Cx, ENABLE);
}
/**
* @brief I2C 外设(EEPROM)初始化
* @param 无
* @retval 无
*/
void I2C_EE_Init(void)
{
I2C_GPIO_Config();
I2C_Mode_Configu();
/* 根据头文件 i2c_ee.h 中的定义来选择 EEPROM 要写入的设备地址 */
/* 选择 EEPROM Block0 来写入 */
EEPROM_ADDRESS = EEPROM_Block0_ADDRESS;
}
向 EEPROM 写入一个字节的数据
/***************************************************************/
/**
* @brief I2C 等待事件超时的情况下会调用这个函数来处理
* @param errorCode:错误代码,可以用来定位是哪个环节出错.
* @retval 返回 0,表示 IIC 读取失败.
*/
static uint32_t I2C_TIMEOUT_UserCallback(uint8_t errorCode)
{
/* 使用串口 printf 输出错误信息,方便调试 */
EEPROM_ERROR("I2C 等待超时!errorCode = %d",errorCode);
return 0;
}
/**
* @brief 写一个字节到 I2C EEPROM 中
* @param pBuffer:缓冲区指针
* @param WriteAddr:写地址
* @retval 正常返回 1,异常返回 0
*/
uint32_t I2C_EE_ByteWrite(u8* pBuffer, u8 WriteAddr)
{
/* 产生 I2C 起始信号 */
I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);
/*设置超时等待时间*/
I2CTimeout = I2CT_FLAG_TIMEOUT;
/* 检测 EV5 事件并清除标志*/
while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
{
if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(0);
}
/* 发送 EEPROM 设备地址 */
I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS, I2C_Direction_Transmitter);
I2CTimeout = I2CT_FLAG_TIMEOUT;
/* 检测 EV6 事件并清除标志*/
while (!I2C_CheckEvent(EEPROM_I2Cx,
I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(1);
}
/* 发送要写入的 EEPROM 内部地址(即 EEPROM 内部存储器的地址) */
I2C_SendData(EEPROM_I2Cx, WriteAddr);
I2CTimeout = I2CT_FLAG_TIMEOUT;
/* 检测 EV8 事件并清除标志*/
while (!I2C_CheckEvent(EEPROM_I2Cx,
I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(2);
}
/* 发送一字节要写入的数据 */
I2C_SendData(EEPROM_I2Cx, *pBuffer);
I2CTimeout = I2CT_FLAG_TIMEOUT;
/* 检测 EV8 事件并清除标志*/
while (!I2C_CheckEvent(EEPROM_I2Cx,
I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3);
}
/* 发送停止信号 */
I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);
return 1;
}
多字节写入及状态等待
/**
* @brief 将缓冲区中的数据写到 I2C EEPROM 中,采用单字节写入的方式,
速度比页写入慢
* @param pBuffer:缓冲区指针
* @param WriteAddr:写地址
* @param NumByteToWrite:写的字节数
* @retval 无
*/
uint8_t I2C_EE_ByetsWrite(uint8_t* pBuffer,uint8_t WriteAddr,
uint16_t NumByteToWrite)
{
uint16_t i;
uint8_t res;
/*每写一个字节调用一次 I2C_EE_ByteWrite 函数*/
for (i=0; i<NumByteToWrite; i++)
{
/*等待 EEPROM 准备完毕*/
I2C_EE_WaitEepromStandbyState();
/*按字节写入数据*/
res = I2C_EE_ByteWrite(pBuffer++,WriteAddr++);
}
return res;
}
/**
* @brief 等待 EEPROM 到准备状态
* @param 无
* @retval 无
*/
void I2C_EE_WaitEepromStandbyState(void)
{
vu16 SR1_Tmp = 0;
do {
/* 发送起始信号 */
I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);
/* 读 I2C1 SR1 寄存器 */
SR1_Tmp = I2C_ReadRegister(EEPROM_I2Cx, I2C_Register_SR1);
/* 发送 EEPROM 地址 + 写方向 */
I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS,
I2C_Direction_Transmitter);
}
// SR1 位 1 ADDR:1 表示地址发送成功,0 表示地址发送没有结束
// 等待地址发送成功
while (!(I2C_ReadRegister(EEPROM_I2Cx, I2C_Register_SR1) & 0x0002));
/* 清除 AF 位 */
I2C_ClearFlag(EEPROM_I2Cx, I2C_FLAG_AF);
/* 发送停止信号 */
I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);
}
EEPROM 的页写入
/**
* @brief 在 EEPROM 的一个写循环中可以写多个字节,但一次写入的字节数
* 不能超过 EEPROM 页的大小,AT24C02 每页有 8 个字节
* @param
* @param pBuffer:缓冲区指针
* @param WriteAddr:写地址
* @param NumByteToWrite:要写的字节数要求 NumByToWrite 小于页大小
* @retval 正常返回 1,异常返回 0
*/
uint8_t I2C_EE_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr,
uint8_t NumByteToWrite)
{
I2CTimeout = I2CT_LONG_TIMEOUT;
while (I2C_GetFlagStatus(EEPROM_I2Cx, I2C_FLAG_BUSY))
{
if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(4);
}
/* 产生 I2C 起始信号 */
I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);
I2CTimeout = I2CT_FLAG_TIMEOUT;
/* 检测 EV5 事件并清除标志 */
while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
{
if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(5);
}
/* 发送 EEPROM 设备地址 */
I2C_Send7bitAddress(EEPROM_I2Cx,EEPROM_ADDRESS,I2C_Direction_Transmitter);
I2CTimeout = I2CT_FLAG_TIMEOUT;
/* 检测 EV6 事件并清除标志*/
while (!I2C_CheckEvent(EEPROM_I2Cx,
I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(6);
}
/* 发送要写入的 EEPROM 内部地址(即 EEPROM 内部存储器的地址) */
I2C_SendData(EEPROM_I2Cx, WriteAddr);
I2CTimeout = I2CT_FLAG_TIMEOUT;
/* 检测 EV8 事件并清除标志*/
while (! I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(7);
}
/* 循环发送 NumByteToWrite 个数据 */
while (NumByteToWrite--)
{
/* 发送缓冲区中的数据 */
I2C_SendData(EEPROM_I2Cx, *pBuffer);
/* 指向缓冲区中的下一个数据 */
pBuffer++;
I2CTimeout = I2CT_FLAG_TIMEOUT;
/* 检测 EV8 事件并清除标志*/
while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(8);
}
}
/* 发送停止信号 */
I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);
return 1;
}
快速写入多字节
/**
* @brief 将缓冲区中的数据写到 I2C EEPROM 中
* @param
* @arg pBuffer:缓冲区指针
* @arg WriteAddr:写地址
* @arg NumByteToWrite:写的字节数
* @retval 无
*/
void I2C_EE_BufferWrite(u8* pBuffer, u8 WriteAddr, u16 NumByteToWrite)
{
u8 NumOfPage=0,NumOfSingle=0,Addr =0,count=0,temp =0;
/*mod 运算求余,若 writeAddr 是 I2C_PageSize 整数倍,
运算结果 Addr 值为 0*/
Addr = WriteAddr % I2C_PageSize;
/*差 count 个数据值,刚好可以对齐到页地址*/
count = I2C_PageSize - Addr;
/*计算出要写多少整数页*/
NumOfPage = NumByteToWrite / I2C_PageSize;
/*mod 运算求余,计算出剩余不满一页的字节数*/
NumOfSingle = NumByteToWrite % I2C_PageSize;
// Addr=0,则 WriteAddr 刚好按页对齐 aligned
// 这样就很简单了,直接写就可以,写完整页后
// 把剩下的不满一页的写完即可
if (Addr == 0) {
/* 如果 NumByteToWrite < I2C_PageSize */
if (NumOfPage == 0) {
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
I2C_EE_WaitEepromStandbyState();
}
/* 如果 NumByteToWrite > I2C_PageSize */
else {
/*先把整数页都写了*/
while (NumOfPage--) {
I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);
I2C_EE_WaitEepromStandbyState();
WriteAddr += I2C_PageSize;
pBuffer += I2C_PageSize;
}
/*若有多余的不满一页的数据,把它写完*/
if (NumOfSingle!=0) {
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
I2C_EE_WaitEepromStandbyState();
}
}
}
// 如果 WriteAddr 不是按 I2C_PageSize 对齐
// 那就算出对齐到页地址还需要多少个数据,然后
// 先把这几个数据写完,剩下开始的地址就已经对齐
// 到页地址了,代码重复上面的即可
else {
/* 如果 NumByteToWrite < I2C_PageSize */
if (NumOfPage== 0) {
/*若 NumOfSingle>count,当前面写不完,要写到下一页*/
if (NumOfSingle > count) {
// temp 的数据要写到写一页
temp = NumOfSingle - count;
I2C_EE_PageWrite(pBuffer, WriteAddr, count);
I2C_EE_WaitEepromStandbyState();
WriteAddr += count;
pBuffer += count;
I2C_EE_PageWrite(pBuffer, WriteAddr, temp);
I2C_EE_WaitEepromStandbyState();
} else { /*若 count 比 NumOfSingle 大*/
I2C_EE_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
I2C_EE_WaitEepromStandbyState();
}
}
/* 如果 NumByteToWrite > I2C_PageSize */
else {
/*地址不对齐多出的 count 分开处理,不加入这个运算*/
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / I2C_PageSize;
NumOfSingle = NumByteToWrite % I2C_PageSize;
/*先把 WriteAddr 所在页的剩余字节写了*/
if (count != 0) {
I2C_EE_PageWrite(pBuffer, WriteAddr, count);
I2C_EE_WaitEepromStandbyState();
/*WriteAddr 加上 count 后,地址就对齐到页了*/
WriteAddr += count;
pBuffer += count;
}
/*把整数页都写了*/
while (NumOfPage--) {
I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);
I2C_EE_WaitEepromStandbyState();
WriteAddr += I2C_PageSize;
pBuffer += I2C_PageSize;
}
/*若有多余的不满一页的数据,把它写完*/
if (NumOfSingle != 0) {
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
I2C_EE_WaitEepromStandbyState();
}
}
}
}
从 EEPROM 读取数据
/**
* @brief 从 EEPROM 里面读取一块数据
* @param pBuffer:存放从 EEPROM 读取的数据的缓冲区指针
* @param ReadAddr:接收数据的 EEPROM 的地址
* @param NumByteToRead:要从 EEPROM 读取的字节数
* @retval 正常返回 1,异常返回 0
*/
uint8_t I2C_EE_BufferRead(uint8_t* pBuffer, uint8_t ReadAddr,
u16 NumByteToRead)
{
I2CTimeout = I2CT_LONG_TIMEOUT;
while (I2C_GetFlagStatus(EEPROM_I2Cx, I2C_FLAG_BUSY))
{
if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(9);
}
/* 产生 I2C 起始信号 */
I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);
I2CTimeout = I2CT_FLAG_TIMEOUT;
/* 检测 EV5 事件并清除标志*/
while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
{
if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(10);
}
/* 发送 EEPROM 设备地址 */
I2C_Send7bitAddress(EEPROM_I2Cx,EEPROM_ADDRESS,I2C_Direction_Transmitter);
I2CTimeout = I2CT_FLAG_TIMEOUT;
/* 检测 EV6 事件并清除标志*/
while (!I2C_CheckEvent(EEPROM_I2Cx,
I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(11);
}
/*通过重新设置 PE 位清除 EV6 事件 */
I2C_Cmd(EEPROM_I2Cx, ENABLE);
/* 发送要读取的 EEPROM 内部地址(即 EEPROM 内部存储器的地址) */
I2C_SendData(EEPROM_I2Cx, ReadAddr);
I2CTimeout = I2CT_FLAG_TIMEOUT;
/* 检测 EV8 事件并清除标志*/
while (!I2C_CheckEvent(EEPROM_I2Cx,I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(12);
}
/* 产生第二次 I2C 起始信号 */
I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);
I2CTimeout = I2CT_FLAG_TIMEOUT;
/* 检测 EV5 事件并清除标志*/
while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
{
if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(13);
}
/* 发送 EEPROM 设备地址 */
I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS, I2C_Direction_Receiver);
I2CTimeout = I2CT_FLAG_TIMEOUT;
/* 检测 EV6 事件并清除标志*/
while (!I2C_CheckEvent(EEPROM_I2Cx,
I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
{
if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(14);
}
/* 读取 NumByteToRead 个数据*/
while (NumByteToRead)
{
/*若 NumByteToRead=1,表示已经接收到最后一个数据了,
发送非应答信号,结束传输*/
if (NumByteToRead == 1)
{
/* 发送非应答信号 */
I2C_AcknowledgeConfig(EEPROM_I2Cx, DISABLE);
/* 发送停止信号 */
I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);
}
I2CTimeout = I2CT_LONG_TIMEOUT;
while (I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)==0)
{
if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3);
}
{
/*通过 I2C,从设备中读取一个字节的数据 */
*pBuffer = I2C_ReceiveData(EEPROM_I2Cx);
/* 存储数据的指针指向下一个地址 */
pBuffer++;
/* 接收数据自减 */
NumByteToRead--;
}
}
/* 使能应答,方便下一次 I2C 传输 */
I2C_AcknowledgeConfig(EEPROM_I2Cx, ENABLE);
return 1;
}
EEPROM 读写测试函数
#define EEP_Firstpage 0x00
uint8_t I2c_Buf_Write[256];
uint8_t I2c_Buf_Read[256];
uint8_t I2C_Test(void);
/**
* @brief I2C(AT24C02)读写测试
* @param 无
* @retval 正常返回 1 ,不正常返回 0
*/
uint8_t I2C_Test(void)
{
u16 i;
EEPROM_INFO("写入的数据");
for ( i=0; i<=255; i++ ) //填充缓冲
{
I2c_Buf_Write[i] = i;
printf("0x%02X ", I2c_Buf_Write[i]);
if (i%16 == 15)
printf("\n\r");
}
//将 I2c_Buf_Write 中顺序递增的数据写入 EERPOM 中
//页写入方式
// I2C_EE_BufferWrite( I2c_Buf_Write, EEP_Firstpage, 256);
//字节写入方式
I2C_EE_ByetsWrite( I2c_Buf_Write, EEP_Firstpage, 256);
EEPROM_INFO("写结束");
EEPROM_INFO("读出的数据");
//将 EEPROM 读出数据顺序保持到 I2c_Buf_Read 中
I2C_EE_BufferRead(I2c_Buf_Read, EEP_Firstpage, 256);
//将 I2c_Buf_Read 中的数据通过串口打印
for (i=0; i<256; i++)
{
if (I2c_Buf_Read[i] != I2c_Buf_Write[i])
{
printf("0x%02X ", I2c_Buf_Read[i]);
EEPROM_ERROR("错误:I2C EEPROM 写入与读出的数据不一致");
return 0;
}
printf("0x%02X ", I2c_Buf_Read[i]);
if (i%16 == 15)
printf("\n\r");
}
EEPROM_INFO("I2C(AT24C02)读写测试成功");
return 1;
}
main 函数
int main(void)
{
LED_GPIO_Config();
LED_BLUE;
/*初始化 USART1*/
USART_Config();
printf("\r\n 欢迎使用野火 STM32 F103 开发板。\r\n");
printf("\r\n 这是一个 I2C 外设(AT24C02)读写测试例程 \r\n");
/* I2C 外设(AT24C02)初始化 */
I2C_EE_Init();
if (I2C_Test() ==1)
{
LED_GREEN;
}
else
{
LED_RED;
}
while (1)
{
}
}
实测有效