首先是配置I2C的GPIO,然后配置I2C参数。就是常规配置,按流程来写不会错。
/**
* @brief EEPROM IIC 配置
*/
void I2C_EE_config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
I2C_InitTypeDef I2C_InitStruct;
// 开启I2C GPIO时钟
EPROM_I2C_GPIO_APBxClkCmd(EEPROM_I2C_SCL_GPIO_CLK | EEPROM_I2C_SDA_GPIO_CLK, ENABLE);
// 开启I2C 外设时钟
EEPROM_I2C_APBxClkCmd(EEPROM_I2C_CLK, ENABLE);
// I2C的引脚配置为复用开漏输出
GPIO_InitStruct.GPIO_Pin = EPROM_I2C_SCL_GPIO_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(EPROM_I2C_SCL_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = EPROM_I2C_SDA_GPIO_PIN;
GPIO_Init(EPROM_I2C_SDA_GPIO_PORT, &GPIO_InitStruct);
// 配置I2C参数(时钟速度、模式、占空比、自身地址、应答使能、7位设备地址)
I2C_InitStruct.I2C_ClockSpeed = EEPROM_I2C_BAUDRATE;
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStruct.I2C_OwnAddress1 = STM32_I2C_OWN_ADDR;
I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
// 初始化
I2C_Init(EEPROM_I2C, &I2C_InitStruct);
// 使能串口
I2C_Cmd(EEPROM_I2C, ENABLE);
}
EEPROM写入一个字节的代码,这里唯一值得注意的是,在检测到EV8事件时的状态。此时移位寄存器正在发送数据,而数据寄存器DR为空。发送data会被暂存到DR,直到移位寄存器发送完,再由DR转移到移位寄存器。
// 向EEPROM写入一个字节(因为EEPROM设备地址固定为0XA0)
void EEPROM_Byte_Write(uint8_t addr, uint8_t data)
{
// 产生起始信号
I2C_GenerateSTART(EEPROM_I2C, ENABLE);
while(I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_MODE_SELECT) == ERROR);
// EV5事件被检测到,发送设备地址(直接填入EEPROM的设备地址:EEPROM_ADDR)
I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDR, I2C_Direction_Transmitter);
while(I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == ERROR);
// EV6事件被检测到,发送要操作的存储单元地址
I2C_SendData(EEPROM_I2C, addr);
while(I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTING) == ERROR);
// EV8事件被检测到(此时移位寄存器正在发送数据,而数据寄存器DR为空)
// (发送data会被暂存到DR,直到移位寄存器发送完,再由DR转移到移位寄存器)
I2C_SendData(EEPROM_I2C, data);
// DR不再填入新的数据,移位寄存器发送完最后一个字节,结束
while(I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED) == ERROR);
// 数据传输完成,产生结束信号
I2C_GenerateSTOP(EEPROM_I2C, ENABLE);
}
然后是读取n个字节的代码。核心思想有几个。第一,按照EEPROM的random read时序,并检测对应的事件;第二,在读入最后一个字节之前,要把应答使能设置为disable,通知EEPROM不要再发数据;第三,接收数据指针要不断递增,防止覆盖;第四,读操作完成后,重新开启应答使能。
// 从EEPROM读取多个字节
void EEPROM_Read(uint8_t addr, uint8_t *data, uint8_t numByteToRead)
{
// 产生起始信号
I2C_GenerateSTART(EEPROM_I2C, ENABLE);
while(I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_MODE_SELECT) == ERROR);
// EV5事件被检测到,发送设备地址(直接填入EEPROM的设备地址:EEPROM_ADDR)
I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDR, I2C_Direction_Transmitter);
while(I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == ERROR);
// EV6事件被检测到,发送要操作的存储单元地址
I2C_SendData(EEPROM_I2C, addr);
while(I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTING) == ERROR);
// 第二次产生起始信号
I2C_GenerateSTART(EEPROM_I2C, ENABLE);
while(I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_MODE_SELECT) == ERROR);
// EV5事件被检测到,发送设备地址,这里方向要选为接收
I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDR, I2C_Direction_Receiver);
while(I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) == ERROR);
// 判断是否为最后一个字节
while (numByteToRead)
{
if (numByteToRead == 1)
{
// 最后一个字节,产生非应答
I2C_AcknowledgeConfig(EEPROM_I2C, DISABLE);
}
while(I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_RECEIVED) == ERROR);
// EV7事件被检测到,即数据寄存器有新的有效数据
*data = I2C_ReceiveData(EEPROM_I2C);
data++; // 指针指向下一个字节
numByteToRead--;
}
// 数据传输完成,产生结束信号
I2C_GenerateSTOP(EEPROM_I2C, ENABLE);
// 重新配置Ack使能,以便下次通讯
I2C_AcknowledgeConfig(EEPROM_I2C, ENABLE);
}
接下来,就是硬件i2c要注意的几个地方。事实上,直接按如下代码操作,程序会直接卡死:
int main(void)
{
uint8_t readData[10] = {0};
USART_config();
I2C_EE_config();
printf("这是一个IIC通讯实验n");
// 写入一个字节
EEPROM_Byte_Write(11, 0X55);
// 读取数据
EEPROM_Read(11, readData, 1);
printf("n接收到的数据为: %#X", readData[0]);
while (1)
{
}
}
为什么?
因为我们是写完立刻读取,而没有缓冲时间。实际上,之前我在EEPROM那篇博客中,有强调一个问题。EEPROM需要写入时间,在这期间内,不响应外部发送的数据,因此,不会产生应答。而在我们的读取函数中,(实际上是发送起始信号后,再发送设备地址然后等待响应),由于EEPROM还在写入,不会响应读取函数中的应答,所以程序会在这里卡死掉while()死循环。
https://blog.csdn.net/dingyc_ee/article/details/100042665
程序卡死的地方:
// 从EEPROM读取多个字节
void EEPROM_Read(uint8_t addr, uint8_t *data, uint8_t numByteToRead)
{
// 产生起始信号
I2C_GenerateSTART(EEPROM_I2C, ENABLE);
while(I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_MODE_SELECT) == ERROR);
// EV5事件被检测到,发送设备地址(直接填入EEPROM的设备地址:EEPROM_ADDR)
I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDR, I2C_Direction_Transmitter);
// 程序会一直卡死在接下来这个while中
while(I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == ERROR);
解决的方案,就是上面提到的确认轮询。通过不断的产生起始 + 设备地址信号,并检测EEPROM的应答,来判断EEPROM是否已经写入完成,如果应答,则表示写入完成,产生结束条件。这里还有几个操蛋的地方,事件检测函数直接不能用(程序中注释的代码,一用就会出错,我也不知道为什么,好像说是因为这个函数会检测好几个标志位),只能检测响应的标志位。确认轮询的代码如下:
// 确认轮询,等待EEPROM完成写入时序的操作(不断产生:起始 + 设备地址)
void EEPROM_WaitForWriteEnd(void)
{
do
{
// 产生起始信号
I2C_GenerateSTART(EEPROM_I2C, ENABLE);
// 这个函数会出错 while(I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_MODE_SELECT) == ERROR);
while(I2C_GetFlagStatus(EEPROM_I2C, I2C_FLAG_SB) == RESET);
// EV5事件被检测到,发送设备地址(直接填入EEPROM的设备地址:EEPROM_ADDR)
I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDR, I2C_Direction_Transmitter);
}
// 如果EEPROM不响应,则一直产生起始信号,直到响应为止
// 这个函数会出错 while(I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == ERROR);
while (I2C_GetFlagStatus(EEPROM_I2C, I2C_FLAG_ADDR) == RESET);
// 如果检测到EEPROM的响应,认为内部写入时序完成,产生结束信号
I2C_GenerateSTOP(EEPROM_I2C, ENABLE);
}
然后在主函数中进行调用,结果正确:
int main(void)
{
uint8_t readData[10] = {0};
USART_config();
I2C_EE_config();
printf("这是一个IIC通讯实验n");
// 写入一个字节
EEPROM_Byte_Write(11, 0X55);
// 确认轮询操作,等待EEPROM写入完成
EEPROM_WaitForWriteEnd();
// 读取数据
EEPROM_Read(11, readData, 1);
printf("n接收到的数据为: %#X", readData[0]);
while (1)
{
}
}
串口输出数据如下:
测试读两个数据:
int main(void)
{
uint8_t readData[10] = {0};
USART_config();
I2C_EE_config();
printf("这是一个IIC通讯实验n");
// 写入一个字节
EEPROM_Byte_Write(11, 0X55);
// 确认轮询操作,等待EEPROM写入完成
EEPROM_WaitForWriteEnd();
EEPROM_Byte_Write(12, 0X56);
EEPROM_WaitForWriteEnd();
// 读取数据
EEPROM_Read(11, readData, 2);
printf("接收到的数据为: %#X, %#Xn", readData[0], readData[1]);
while (1);
}
结果如下,说明多字节读取函数正确。
接下来实现页写入的函数(每次不能超过8字节,否则会卷回来到本页起始,覆盖掉之前数据)
// 向EEPROM写入多个字节(页写入),最大不能超过8字节
void EEPROM_Page_Write(uint8_t addr, uint8_t *data, uint8_t numByteToRead)
{
// 产生起始信号
I2C_GenerateSTART(EEPROM_I2C, ENABLE);
while(I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_MODE_SELECT) == ERROR);
// EV5事件被检测到,发送设备地址(直接填入EEPROM的设备地址:EEPROM_ADDR)
I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDR, I2C_Direction_Transmitter);
while(I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == ERROR);
// EV6事件被检测到,发送要操作的存储单元地址
I2C_SendData(EEPROM_I2C, addr);
while(I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTING) == ERROR);
while (numByteToRead)
{
// EV8事件被检测到
I2C_SendData(EEPROM_I2C, *data);
// DR不再填入新的数据,移位寄存器发送完最后一个字节,结束
while(I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED) == ERROR);
data++; // 指针指向下一个字节
numByteToRead--;
}
// 数据传输完成,产生结束信号
I2C_GenerateSTOP(EEPROM_I2C, ENABLE);
}
测试代码:
int main(void)
{
uint8_t i = 0;
uint8_t readData[8] = {0};
uint8_t writeData[8] = {1, 2, 3, 4, 5, 6, 7, 8};
USART_config();
I2C_EE_config();
printf("这是一个IIC通讯实验n");
EEPROM_Page_Write(16, writeData, 8);
// 确认轮询操作,等待EEPROM写入完成
EEPROM_WaitForWriteEnd();
EEPROM_Read(16, readData, 8);
for (i = 0; i < 8; i++)
{
printf("%4d", readData[i]);
}
printf("n");
while (1);
}
测试结果:
接下来,演示的是一些很不好的操作,如果写入的数据超过一页,诗句是怎么被覆盖掉的?我们尝试向一页中写入10个数据,然后读取这一页(8)个数据,看看会有什么现象:
测试代码:
int main(void)
{
uint8_t i = 0;
uint8_t readData[8] = {0};
uint8_t writeData[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
USART_config();
I2C_EE_config();
printf("这是一个IIC通讯实验n");
EEPROM_Page_Write(16, writeData, 10);
// 确认轮询操作,等待EEPROM写入完成
EEPROM_WaitForWriteEnd();
EEPROM_Read(16, readData, 8);
for (i = 0; i < 8; i++)
{
printf("%4d", readData[i]);
}
printf("n");
while (1);
}
接下来就是见证奇迹的时刻,最后的9 10两个数据,把之前的1 2给覆盖掉了
还有一个问题,关于地址对齐。AT24C02每页为8字节,所以每页的起始地址为0 8 16...,如果我们以对齐的地址来写入,则数据和地址都是正确的。但是,如果我们错开地址,会如何?接下来,从地址2开始连续写入8字节,然后读取8字节,观察结果
测试代码:
int main(void)
{
uint8_t i = 0;
uint8_t readData[8] = {0};
uint8_t writeData[8] = {1, 2, 3, 4, 5, 6, 7, 8};
USART_config();
I2C_EE_config();
printf("这是一个IIC通讯实验n");
EEPROM_Page_Write(2, writeData, 8);
// 确认轮询操作,等待EEPROM写入完成
EEPROM_WaitForWriteEnd();
EEPROM_Read(2, readData, 8);
for (i = 0; i < 8; i++)
{
printf("%4d", readData[i]);
}
printf("n");
while (1);
}
测试结果:
可以看到,前面6个数据是正确的,而后2个数据是错误的。分析:后面两个数据,由于反卷,直接写到了第一页的开始两个字节,而我们读取到的数据,则是第二页的前2个数据,验证一下:
测试代码:
int main(void)
{
uint8_t i = 0;
uint8_t readData[8] = {0};
uint8_t writeData[8] = {1, 2, 3, 4, 5, 6, 7, 8};
USART_config();
I2C_EE_config();
printf("这是一个IIC通讯实验n");
// 读取第一页的前2个数据
EEPROM_Read(0, readData, 2);
// 读取第二页的前2个数据
EEPROM_Read(8, (readData + 2), 2);
printf("读取第一页的前2个数据n");
for (i = 0; i < 2; i++)
{
printf("%4d", readData[i]);
}
printf("n");
printf("读取第二页的前2个数据n");
for (i = 2; i < 4; i++)
{
printf("%4d", readData[i]);
}
printf("n");
while (1);
}
测试结果:
与设想的一致。7 8由于反卷,写入到了第一页的前两个字节。而我们读数据时,地址是直接递增的,所以在读取完前6个后,按顺序(不会反卷)读取第二页的数据,因此会导致出错。
那么,我们在使用EEPROM进行页写入时,一定要注意地址8字节对齐,否则会出错,而且极难被发现。
上一篇:stm32专题十七:AT24C02
下一篇: stm32专题十四:存储器介绍
推荐阅读
史海拾趣
人才是企业发展的根本动力。EMLSI公司深知这一点,因此始终将人才战略作为企业发展的重要支撑。公司建立了完善的人才培养体系,为员工提供广阔的发展空间和良好的职业前景。同时,EMLSI还注重引进外部优秀人才,为企业注入新的活力和创新力。这种对人才的重视和投入让EMLSI在激烈的市场竞争中始终保持领先地位。
为了进一步扩大市场份额,磁联达(CND-tek)公司决定拓展海外市场。公司组建了一支专业的国际销售团队,积极参加国际电子展会和交流活动,与全球各地的客户建立了紧密的合作关系。同时,公司还加大了对海外市场的投入,设立了多个海外办事处和仓库,为客户提供更加便捷的服务。这些举措使得磁联达(CND-tek)的产品迅速走向全球,赢得了广泛的赞誉。
在面临激烈的市场竞争时,Bellin Dynamic Systems选择与行业内的其他领军企业展开战略合作。通过与这些企业的合作,Bellin Dynamic Systems不仅获得了更多的技术资源和市场渠道,还共同推动了整个行业的发展。这种合作共赢的模式使得Bellin Dynamic Systems在竞争中保持了领先地位。
面对日新月异的电子行业,3D PLUS公司始终保持创新精神,不断推动3D技术的升级。公司研发团队成功研发出全方位彩色人体扫描仪,这一设备能够在极短的时间内实现360度人像扫描,且清晰度极高,为互联网人体应用提供了强有力的支持。此外,公司还不断在软件、算法等方面进行优化,提升3D技术的精度和效率,满足市场的不断需求。
EMBEST公司成立于XXXX年,早期便专注于ARM嵌入式软件的开发。在XXXX年,EMBEST发布了国内首个具有自主知识产权的ARM嵌入式软件开发工具Embest IDE For ARM,这一里程碑式的产品不仅填补了国内市场的空白,也为EMBEST在嵌入式领域的地位奠定了基础。通过持续的创新和研发,EMBEST逐步建立了自己的技术壁垒,为公司的长远发展奠定了坚实基础。
随着Crane Co.在电子行业的不断发展和壮大,公司开始积极参与国际展览和会议。在某次重要的国际展览上,Crane Co.展示了其全系列的流体处理产品,包括创新的阀门和泵类产品。这些产品凭借其卓越的性能和先进的技术,吸引了众多参展商和客户的关注。通过这次展览,Crane Co.进一步巩固了其在电子行业的地位,并为未来的市场拓展奠定了坚实基础。
通过以上五个故事,我们可以看到Crane Co.在电子行业发展的历程中,始终保持着对技术创新和产品质量的不懈追求。正是这些努力,使得Crane Co.能够在激烈的市场竞争中脱颖而出,成为行业内的佼佼者。
P4 Celeron 在与 INTEL 865PE、848P芯片组配合使用时, 只支持什么内存标准啊? 3.P4 Celeron 在与 INTEL 865PE、848P芯片组配合使用时, 只支持__C______内存标准。 A、DDR266 B、DDR333 C、DDR400 D、DDRII 400 某 ...… 查看全部问答∨ |
|
在linux嵌入式开发中,对做driver开发的人的硬件水平要求很高,要求的 硬件技术到底具体包含了什么内容? 他们与学校(大学)中的哪些课程相关? … 查看全部问答∨ |
|
前些天赶时间用硬的I2c1驱动a,b,c.a和b可以通信,就c不行,而b和c只是数据的定义不同,其它的都相同,碰到这个问题调试了几个小时,还是没调通,反而烧了硬件I2c(SDA始终是高电平),痛苦.不得已,只好用模拟的,调通了.今天正好有点空,想想有硬的不用, ...… 查看全部问答∨ |
|
MSP430G2553LaunchPad的中断问题,求助啊,纠结了两天了。。。 求解释 /* #include<msp430g2553.h>里面有这些。 vector = TIMER0_A0_VECTOR vector = TIMER0_A1_VECTOR vector = TIMER1_A0_VECTOR   ...… 查看全部问答∨ |
LM3S USB 书籍,目前我看过最优秀的USB书籍,推荐给USB爱好者。 《 深入浅出USB系统开发--基于ARM Cortex-M3》 淘宝、亚马逊、当当等都有售卖,pdf电子版的暂时没有。 基本信息 ISBN:9787512408722 作者:王川北 定价:52.00 ...… 查看全部问答∨ |