第18章 硬件I2C
第十八章 硬件I2C
EEPROM是一种掉电后数据不丢失的存储器,常用来存储一些配置信息,以便系统重新上电的时候加载之。 EEPOM芯片最常用的通讯方式就是I2C协议, 本小节以EEPROM的读写实验为大家讲解STM32的I2C使用方法。 实验中STM32的I2C外设采用主模式,分别用作主发送器和主接收器,通过查询事件的方式来确保正常通讯。
1. 硬件设计
本实验板中的EEPROM芯片(型号:AT24C02)的SCL及SDA引脚连接到了STM32对应的I2C引脚中,结合上拉电阻, 构成了I2C通讯总线,它们通过I2C总线交互。EEPROM芯片的设备地址一共有7位,其中高4位固定为:1010 b, 低3位则由A0/A1/A2信号线的电平决定,图中的R/W是读写方向位,与地址无关。
按照我们此处的连接,A0/A1/A2均为0,所以EEPROM的7位设备地址是:101 0000b,即0x50。 由于I2C通讯时常常是地址跟读写方向连在一起构成一个8位数,且当R/W位为0时,表示写方向, 所以加上7位地址,其值为“0xA0”,常称该值为I2C设备的“写地址”;当R/W位为1时,表示读方向, 加上7位地址,其值为“0xA1”,常称该值为“读地址”。
EEPROM芯片中还有一个WP引脚,具有写保护功能,当该引脚电平为高时,禁止写入数据,当引脚为低电平时, 可写入数据,我们直接接地,不使用写保护功能。
2. 软件设计
2.1 编程大纲
-
I2C及EEPROM相关参数宏定义,配置I2C GPIO及工作模式
-
编写I2C EEPROM进入待机模式和错误代码回调函数
-
编写I2C读写EEPROM基本函数
-
编写测试程序,对读写数据进行校验。
2.2 代码分析
2.2.1 I2C及EEPROM相关参数宏定义
// I2C相关参数宏定义
#define I2Cx I2C1
#define I2C_SCL_PORT GPIOB
#define I2C_SCL_PIN GPIO_Pin_6
#define I2C_SDA_PORT GPIOB
#define I2C_SDA_PIN GPIO_Pin_7
#define I2C_Speed 400000
// 这个地址只要与STM32外挂的I2C器件地址不一样即可
#define I2Cx_OWN_ADDRESS7 0X0A
// 等待超时时间
#define I2CT_FLAG_TIMEOUT ((uint32_t)0x1000)
#define I2CT_LONG_TIMEOUT ((uint32_t)(10 * I2CT_FLAG_TIMEOUT))
// 信息输出
#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)
// AT24C01/02每页有8个字节
#define I2C_PageSize 8
/*
AT24C02 2kb = 2048bit = 2048/8 B = 256 B
32 pages of 8 bytes each
Device Address
1 0 1 0 A2 A1 A0 R/W
1 0 1 0 0 0 0 0 = 0XA0
1 0 1 0 0 0 0 1 = 0XA1
*/
// EEPROM地址宏定义
#define EEPROM_Block0_ADDRESS 0xA0
//#define EEPROM_Block1_ADDRESS 0xA2
//#define EEPROM_Block2_ADDRESS 0xA4
//#define EEPROM_Block3_ADDRESS 0xA6
以上代码根据硬件连接,把与EEPROM通讯使用的I2C号 、引脚号都以宏封装起来,并且定义了自身的I2C地址及通讯速率,以便配置模式的时候使用。
2.2.2 初始化I2C配置工作参数
// I2C GPIO配置
void I2C_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
// I2C_SCL、I2C_SDA配置
GPIO_InitStructure.GPIO_Pin = I2C_SCL_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_Init(I2C_SCL_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = I2C_SDA_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_Init(I2C_SDA_PORT, &GPIO_InitStructure);
}
// I2C工作参数配置
static void I2C_Mode_Configu(void)
{
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; // I2C模式
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; // 400khz
I2C_InitStructure.I2C_OwnAddress1 = I2Cx_OWN_ADDRESS7; // 设置I2C自己的地址
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; // 使能应答
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; // 7位地址模式
I2C_InitStructure.I2C_ClockSpeed = I2C_Speed; // 快速模式
// I2C 初始化
I2C_Init(I2Cx, &I2C_InitStructure);
I2C_Cmd(I2Cx, ENABLE);
}
// I2C 外设(EEPROM)初始化
void I2C_EE_Init(void)
{
I2C_GPIO_Config();
I2C_Mode_Configu();
// 根据头文件I2C.h中的定义来选择EEPROM要写入的地址
#ifdef EEPROM_Block0_ADDRESS
EEPROM_ADDRESS = EEPROM_Block0_ADDRESS; // 选择 EEPROM Block0 来写入
#endif
#ifdef EEPROM_Block1_ADDRESS
EEPROM_ADDRESS = EEPROM_Block1_ADDRESS;
#endif
#ifdef EEPROM_Block2_ADDRESS
EEPROM_ADDRESS = EEPROM_Block2_ADDRESS;
#endif
#ifdef EEPROM_Block3_ADDRESS
EEPROM_ADDRESS = EEPROM_Block3_ADDRESS;
#endif
}
2.2.3 错误信息回调及I2C待机模式函数
// 等待I2C EEPROM进入待机模式
void I2C_EE_WaitEepromStandbyState(void)
{
vu16 Temp_SR = 0;
do
{
I2C_GenerateSTART(I2Cx, ENABLE); // 发送I2C开始信号
Temp_SR = I2C_ReadRegister(I2Cx, I2C_Register_SR1); // 读取I2C状态寄存器1
I2C_Send7bitAddress(I2Cx, EEPROM_ADDRESS, I2C_Direction_Transmitter); // 发送I2C地址信号
}while(!(I2C_ReadRegister(I2Cx, I2C_Register_SR1) & 0x0002)); // 等待I2C状态寄存器1的BUSY位清零
I2C_ClearFlag(I2Cx, I2C_FLAG_AF); // 清除I2C忙标志
I2C_GenerateSTOP(I2Cx, ENABLE); // I2C停止信号
}
// 错误代码回调函数
uint32_t I2C_TIMEOUT_UserCallback(uint8_t errorCode)
{
EEPROM_ERROR("I2C 等待超时!errorCode = %d",errorCode); // 通过不同的错误码,可以判断哪部分超时
return 0;
}
- 读取 I2C 状态寄存器1的值,并将其存储在
Temp_SR
中。SR1
状态寄存器包含了 I2C 总线的一些状态信息,例如总线是否忙(BUSY 标志)、是否发生应答失败(AF 标志)等。 - 发送 7 位的设备地址(
EEPROM_ADDRESS
)和操作方向(I2C_Direction_Transmitter
),指定此操作是对 EEPROM 进行写操作。地址后面带有一个方向位,I2C_Direction_Transmitter
表示发送数据。
2.2.4 I2C写入EEPROM单个字节函数
初始化好I2C外设后,就可以使用I2C通讯,我们看看如何向EEPROM写入一个字节的数据
/*
I2C写数据基本流程:
1.发送I2C开始信号
2.发送I2C写命令
3.发送要写入的地址
4.发送要写入的数据
5.发送I2C停止信号,进入待机模式
*/
// 通过I2C从EEPROM中写入一个字节
// 函数参数:pBuffer,要写入的数据,WriteAddr,要写入的地址
uint32_t I2C_EE_ByteWrite(u8* pBuffer, u8 WriteAddr)
{
I2C_GenerateSTART(I2Cx, ENABLE); // 发送I2C开始信号
I2CTimeout = I2CT_FLAG_TIMEOUT; // 超时时间初始化
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT)) // 等待I2C空闲状态
{
if((I2CTimeout--) == 0)
return I2C_TIMEOUT_UserCallback(0); // 返回错误码0,代表发送I2C开始信号超时
}
I2CTimeout = I2CT_FLAG_TIMEOUT; // 超时时间初始化
I2C_Send7bitAddress(I2Cx, EEPROM_ADDRESS, I2C_Direction_Transmitter); // 发送I2C写命令
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
if((I2CTimeout--) == 0)
return I2C_TIMEOUT_UserCallback(1); // 返回错误码1,代表发送I2C地址信号时超时
}
I2C_SendData(I2Cx, WriteAddr); // 发送要写入的地址
I2CTimeout = I2CT_FLAG_TIMEOUT;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if((I2CTimeout--) == 0)
return I2C_TIMEOUT_UserCallback(2); // 返回错误码2,代表发送I2C要写入的地址时超时
}
I2C_SendData(I2Cx, *pBuffer); // 发送要写入的字节
I2CTimeout = I2CT_FLAG_TIMEOUT;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if((I2CTimeout--) == 0)
return I2C_TIMEOUT_UserCallback(3); // 返回错误码3,代表发送I2C要写入的字节时超时
}
I2C_GenerateSTOP(I2Cx, ENABLE); // 发送字节字节完毕,停止信号
return 1;
}
分析I2C_EE_ByteWrite函数,这个函数实现了前面讲的I2C主发送器通讯流程
-
使用库函数I2C_GenerateSTART产生I2C起始信号, 其中的I2Cx宏是前面硬件定义相关的I2C编号;
-
对I2CTimeout变量赋值为宏I2CT_FLAG_TIMEOUT,这个I2CTimeout变量在下面的while循环中每次循环减1, 该循环通过调用库函数I2C_CheckEvent检测事件,若检测到事件,则进入通讯的下一阶段,若未检测到事件则停留在此处一直检测, 当检测I2CT_FLAG_TIMEOUT次都还没等待到事件则认为通讯失败,调用前面的I2C_TIMEOUT_UserCallback输出调试信息,并退出通讯;
-
调用库函数I2C_Send7bitAddress发送EEPROM的设备地址, 并把数据传输方向设置为I2C_Direction_Transmitter(即发送方向), 这个数据传输方向就是通过设置I2C通讯中紧跟地址后面的R/W位实现的。发送地址后以同样的方式检测EV6标志;
-
调用库函数I2C_SendData向EEPROM发送要写入的内部地址, 该地址是I2C_EE_ByteWrite函数的输入参数,发送完毕后等待EV8事件。 要注意这个内部地址跟上面的EEPROM地址不一样,上面的是指I2C总线设备的独立地址,而此处的内部地址是指EEPROM内数据组织的地址, 也可理解为EEPROM内存的地址或I2C设备的寄存器地址;
-
调用用库函数I2C_SendData向EEPROM发送要写入的数据, 该数据是I2C_EE_ByteWrite函数的输入参数,发送完毕后等待EV8事件;
-
一个I2C通讯过程完毕, 调用I2C_GenerateSTOP发送停止信号。
在这个通讯过程中,STM32实际上通过I2C向EEPROM发送了两个数据,但为何第一个数据被解释为EEPROM的内存地址,这是由EEPROM的自己定义的单字节写入时序
EEPROM的单字节时序规定,向它写入数据的时候,第一个字节为内存地址,第二个字节是要写入的数据内容。 所以我们需要理解:命令、地址的本质都是数据,对数据的解释不同,它就有了不同的功能。
总结:
这个函数通过 I2C 协议向 EEPROM 写入一个字节。整个过程包括:
- 启动 I2C 通信。
- 等待 I2C 总线空闲,并发送 EEPROM 的地址。
- 发送 EEPROM 内部的写入地址。
- 发送数据字节。
- 发送停止信号结束通信。
2.2.5 I2C写入EEPROM多个字节函数
// 在EEPROM的一个写循环中可以写多个字节,但一次写入的字节数,不能超过EEPROM页的大小,AT24C02每页有8个字节
// 函数参数:pBuffer,要写入的数据,WriteAddr,要写入的地址,NumByteToWrite,要写入的字节数
uint32_t I2C_EE_PageWrite(u8* pBuffer, u8 WriteAddr, u8 NumByteToWrite)
{
I2CTimeout = I2CT_LONG_TIMEOUT; // 超时时间初始化
while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY)) // 等待I2C空闲状态
{
if((I2CTimeout--) == 0)
return I2C_TIMEOUT_UserCallback(4); // 返回错误码4,代表写入多个数据时等待I2C空闲状态超时
}
I2C_GenerateSTART(I2Cx, ENABLE); // I2C开始信号
I2CTimeout = I2CT_FLAG_TIMEOUT;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
{
if((I2CTimeout--) == 0)
return I2C_TIMEOUT_UserCallback(5); // 返回错误码5,代表写入多个数据发送I2C开始信号超时
}
I2C_Send7bitAddress(I2Cx, EEPROM_ADDRESS, I2C_Direction_Transmitter); // 发送I2C写命令
I2CTimeout = I2CT_FLAG_TIMEOUT;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
if((I2CTimeout--) == 0)
return I2C_TIMEOUT_UserCallback(6); // 返回错误码6,代表写入多个数据发送I2C地址信号超时
}
I2C_SendData(I2Cx, WriteAddr); // 发送要写入的地址
I2CTimeout = I2CT_FLAG_TIMEOUT;
while(! I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if((I2CTimeout--) == 0)
return I2C_TIMEOUT_UserCallback(7); // 返回错误码7,代表写入多个数据发送I2C要写入的地址超时
}
while(NumByteToWrite--) // 写入多个字节
{
I2C_SendData(I2Cx, *pBuffer); // 发送要写入的字节
pBuffer++; // 数据指针递增
I2CTimeout = I2CT_FLAG_TIMEOUT;
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if((I2CTimeout--) == 0)
return I2C_TIMEOUT_UserCallback(8); // 返回错误码8,代表写入多个数据发送I2C要写入的字节超时
}
}
I2C_GenerateSTOP(I2Cx, ENABLE); // 发送多字节完毕,停止信号
return 1;
}
这个函数支持通过 I2C 向 EEPROM 写入多个字节数据,主要步骤如下:
- 等待 I2C 总线空闲,发送开始信号。
- 发送设备地址和写命令。
- 发送写入地址,确定写入起始位置。
- 在
NumByteToWrite
次循环中写入多个字节。 - 完成后发送停止信号,结束通信。
2.2.7 将缓冲区的数据写入到EEPROM中
// 将缓冲区中的数据写到I2C EEPROM中
// 函数参数:pBuffer,要写入的数据,WriteAddr,要写入的地址,NumByteToWrite,要写入的字节数
void I2C_EE_BufferWrite(u8* pBuffer, u8 WriteAddr, u16 NumByteToWrite)
{
u8 NumOfPage = 0; // 页数
u8 NumOfSingle = 0; // 单独的字节
u8 Addr = 0; // 地址
u8 count = 0; // 计数器
Addr = WriteAddr % I2C_PageSize; // 计算要写入的地址在页中的偏移
count = I2C_PageSize - Addr; // 计算页剩余空间大小
NumOfPage = NumByteToWrite / I2C_PageSize; // 计算页数
NumOfSingle = NumByteToWrite % I2C_PageSize; // 计算单独的字节
if(Addr == 0) // 如果一页可以写入完
{
if(NumOfPage == 0) // 如果NumByteToWrite < I2C_PageSize
{
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle); // 写入单独的字节
I2C_EE_WaitEepromStandbyState(); // 等待EEPROM进入待机模式
}
else // 如果NumByteToWrite > I2C_PageSize
{
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();
}
}
}
else // 如果一页不能写入完
{
if(NumOfPage== 0) // 如果If NumByteToWrite < I2C_PageSize
{
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
I2C_EE_WaitEepromStandbyState();
}
else // If NumByteToWrite > I2C_PageSize
{
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / I2C_PageSize;
NumOfSingle = NumByteToWrite % I2C_PageSize;
if(count != 0)
{
I2C_EE_PageWrite(pBuffer, WriteAddr, count);
I2C_EE_WaitEepromStandbyState();
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();
}
}
}
}
2.2.8 读取缓冲区的数据到EEPROM中
// 从I2C EEPROM中读取数据到缓冲区中
// 函数参数:pBuffer,要读取的数据,ReadAddr,要读取的地址,NumByteToRead,要读取的字节数
uint32_t I2C_EE_BufferRead(u8* pBuffer, u8 ReadAddr, u16 NumByteToRead)
{
I2CTimeout = I2CT_LONG_TIMEOUT; // 超时时间初始化
while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY)) // 等待I2C空闲状态
{
if((I2CTimeout--) == 0)
return I2C_TIMEOUT_UserCallback(9); // 返回错误码9,代表读取多个数据时等待I2C空闲状态超时
}
I2C_GenerateSTART(I2Cx, ENABLE); // I2C开始信号
I2CTimeout = I2CT_FLAG_TIMEOUT;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
{
if((I2CTimeout--) == 0)
return I2C_TIMEOUT_UserCallback(10); // 返回错误码10,代表读取多个数据发送I2C开始信号超时
}
I2C_Send7bitAddress(I2Cx, EEPROM_ADDRESS, I2C_Direction_Transmitter); // 发送I2C读命令
I2CTimeout = I2CT_FLAG_TIMEOUT;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
if((I2CTimeout--) == 0)
return I2C_TIMEOUT_UserCallback(11); // 返回错误码11,代表读取多个数据发送I2C地址信号超时
}
I2C_Cmd(I2Cx, ENABLE); // 使能I2C
I2C_SendData(I2Cx, ReadAddr); // 发送要读取的地址
I2CTimeout = I2CT_FLAG_TIMEOUT;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if((I2CTimeout--) == 0)
return I2C_TIMEOUT_UserCallback(12); // 返回错误码12,代表读取多个数据发送I2C要读取的地址超时
}
I2C_GenerateSTART(I2Cx, ENABLE); // 发送I2C开始信号
I2CTimeout = I2CT_FLAG_TIMEOUT;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
{
if((I2CTimeout--) == 0)
return I2C_TIMEOUT_UserCallback(13); // 返回错误码13,代表读取多个数据发送I2C开始信号超时
}
I2C_Send7bitAddress(I2Cx, EEPROM_ADDRESS, I2C_Direction_Receiver); // 发送I2C读命令
I2CTimeout = I2CT_FLAG_TIMEOUT;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
{
if((I2CTimeout--) == 0)
return I2C_TIMEOUT_UserCallback(14); // 返回错误码14,代表读取多个数据发送I2C地址信号超时
}
while(NumByteToRead) // 循环读入数据
{
if(NumByteToRead == 1) // 最后一次读入数据
{
I2C_AcknowledgeConfig(I2Cx, DISABLE); // 禁止I2C应答
I2C_GenerateSTOP(I2Cx, ENABLE); // 发送I2C停止信号
}
I2CTimeout = I2CT_LONG_TIMEOUT;
while(I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED) == 0)
{
if((I2CTimeout--) == 0)
return I2C_TIMEOUT_UserCallback(3); // 返回错误码3,代表读取多个数据接收I2C数据超时
}
{
*pBuffer = I2C_ReceiveData(I2Cx); // 接收I2C数据
pBuffer++; // 数据指针递增
NumByteToRead--; // 读取字节数递减
}
}
I2C_AcknowledgeConfig(I2Cx, ENABLE); // 使能I2C应答
return 1;
}
这个函数实现了通过 I2C 从 EEPROM 中读取多个字节的数据,并将其存储到缓冲区。函数步骤如下:
- 检查 I2C 总线是否空闲。
- 发送开始信号、设备地址以及读取地址,初始化 I2C 通信。
- 再次发送开始信号并切换到接收模式。
- 循环接收数据并存储到缓冲区中,直到所有字节读取完成。
- 处理最后一个字节的读取和停止信号。
2.2.9 main()测试
完成基本的读写函数后,接下来我们编写一个读写测试函数来检验驱动程序
#include "stm32f10x.h"
#include "led.h"
#include "usart.h"
#include "I2C.h"
#include <string.h>
#define EEP_Firstpage 0x00 // EEPROM的起始地址
uint8_t I2c_Buf_Write[256]; // 写入缓冲区
uint8_t I2c_Buf_Read[256]; // 读取缓冲区
uint8_t I2C_Test(void);
int main(void)
{
LED_Init();
LED_BLUE();
USART_Config();
I2C_EE_Init();
if(I2C_Test() == 1)
{
LED_GREEN();
}
else
{
LED_RED();
}
while (1)
{
}
}
// I2C EEPROM读写测试
uint8_t I2C_Test(void)
{
uint16_t i;
printf("要写入的数据n\r");
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_EE_BufferWrite(I2c_Buf_Write, EEP_Firstpage, 256); // 写入缓冲区的数据到EEPROM
EEPROM_INFO("\n\r写入成功\r");
EEPROM_INFO("\n\rEEPROM读出的数据n\r");
I2C_EE_BufferRead(I2c_Buf_Read, EEP_Firstpage, 256);
for(i = 0; i < 256; i++) // 打印出来,看是否数据读写相同
{
if(I2c_Buf_Read[i] != I2c_Buf_Write[i]) // 如果数据不相同
{
EEPROM_ERROR("0x%02X ", I2c_Buf_Read[i]);
EEPROM_ERROR("错误:两次读出的数据不相同\n\r");
return 0;
}
printf("0x%02X ", I2c_Buf_Read[i]); // 打印出来读出的数据
if(i % 16 == 15)
printf("\n\r");
}
return 1;
}
代码中先填充一个数组,数组的内容为1,2,3至N,接着把这个数组的内容写入到EEPROM中,写入时可以采用单字节写入的方式或页写入的方式。 写入完毕后再从EEPROM的地址中读取数据,把读取得到的与写入的数据进行校验,若一致说明读写正常,否则读写过程有问题或者EEPROM芯片不正常。 其中代码用到的EEPROM_INFO跟EEPROM_ERROR宏类似,都是对printf函数的封装,使用和阅读代码时把它直接当成printf函数就好。
3. 小结
啊,说了怎么多,本质还是一个配置的事,因为stm32标准库的强大,我们想要使用功能只用配置就好了,接下来的一个模拟I2C可能就没那么顺利了。根据传统,现在我们需要总结一下程序了:
-
首先是要配置好串口和led了,目的是方便观察实验效果,串口和led我们都已经见过啦
-
接下来就是I2C和外设的宏定义了,和以往不同的是,我们还宏定义了信息输出模板
/*信息输出*/
#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)
-
正式开始配置:GPIO基础配置、I2C基础配置、I2C工作模式配置、外设(EEPROM)初始化
-
配置里面当然包含外设有关函数:写入数据、写入字节、循环写入、取出数据(如果搞不懂,这些我们复制粘贴就好了)
-
主函数测试:调用我们写好的函数,再写一个测试函数,外设正常绿灯亮、错误红灯亮
2024.9.4 第一次修订,后期不再维护
2025.1.16 程序大修,内容简化
本文作者:hazy1k
本文链接:https://www.cnblogs.com/hazy1k/p/18395964
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步