以EEPROM为例的硬件IIC的使用

目录

参考调试MPU6050与EEPROM的经验,整合了目标内存/寄存器地址是否为16位的情况,合并了单字节与多字节间的操作,添加了返回值与读写超时功能:硬件IIC的7位从机地址查询方式读写参考代码 - JayWell - 博客园 (cnblogs.com)

根据手上这片EEPROM的型号,24LC515,自行搜索了手册,发现其读写时序和之前调的MPU6050使用的并无很大区别,只是对于MPU6050来说,读写的内容是8位寄存器地址中的值,而对于手里的这片EEPROM来说,读写的内容体现在代码上是16位存储内存地址中的值(第15位最高位是无效位,实际使用15位)。24XX515,结合A0A1A2三根线上的信号,8片组合起来能存储512K数据。一片EEPROM内有两块,每个块内寻址需要15位二进制数据。(IIC的重映射会有点问题,建议先使用PB12、13的硬件IIC)

手册参考网页:24LC515 datasheet(1/22 Pages) MICROCHIP | 512K I2C CMOS Serial EEPROM (alldatasheetcn.com)

进行了如下改写↓

//从从机的某寄存器,读取一个字节的数据
void IIC_16b_read_byte(uint8_t addr, uint16_t mem, uint8_t *des)
{
//主机通知从机要读取它的哪块内存
    while(I2C_GetFlagStatus(I2C_FLAG_BUSY));                            //IIC主机判忙
    I2C_GenerateSTART(ENABLE);                                          //起始信号
    while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));               //判断BUSY, MSL and SB flags
    I2C_Send7bitAddress(addr, I2C_Direction_Transmitter);               //发送器件地址+最低位0表示为“写”

    while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //判断BUSY, MSL, ADDR, TXE and TRA flags
    I2C_SendData((uint8_t)(mem>>8));                                    //发送内存地址的高8位

    while(!I2C_GetFlagStatus(I2C_FLAG_TXE));                            //获取TxE的状态    数据寄存器为空标志位,可以向其中写数据
    I2C_SendData((uint8_t)mem);                                         //发送内存地址的低8位
    while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED));          //判断TRA, BUSY, MSL, TXE and BTF flags

//直接产生一个重起始信号即可开始读的过程
    I2C_GenerateSTART(ENABLE);                                          //重起始信号
    while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));               //判断BUSY, MSL and SB flags
    I2C_Send7bitAddress(addr, I2C_Direction_Receiver);                  //发送地址+最低位1表示为“读”
    while(!I2C_CheckEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));    //判断BUSY, MSL and ADDR flags

    I2C_GenerateSTOP(DISABLE);
  //关闭停止信号使能(某些情况下可能会在此清除PE,这一行可以去掉) I2C_AcknowledgeConfig(DISABLE);
  //关闭ACK使能,接收一个字节数据后,主机就回NACK表示不再接收数据
  //(某些情况下可能会在此清除PE,这一行可以去掉,但逻辑分析仪抓时序,会多接收一个字节数据)
                         while(!I2C_GetFlagStatus(I2C_FLAG_RXNE)); //获取RxEN的状态,等待收到数据 *des = I2C_ReceiveData(); //获得从机的寄存器中的数据 I2C_GenerateSTOP(ENABLE); //停止信号 I2C_AcknowledgeConfig(ENABLE); //传输完毕,再次打开ACK使能 } //从从机的某寄存器起始,连续读取n个字节的数据 void IIC_16b_read_nBytes(uint8_t addr, uint16_t mem, uint8_t *des, uint8_t len) { //主机通知从机要读取它的哪块内存 while(I2C_GetFlagStatus(I2C_FLAG_BUSY)); //IIC主机判忙 I2C_GenerateSTART(ENABLE); //起始信号 while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags I2C_Send7bitAddress(addr, I2C_Direction_Transmitter); //发送器件地址+最低位0表示为“写” while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //判断BUSY, MSL, ADDR, TXE and TRA flags I2C_SendData((uint8_t)(mem>>8)); //发送内存地址的高8位 while(!I2C_GetFlagStatus(I2C_FLAG_TXE)); //获取TxE的状态 数据寄存器为空标志位,可以向其中写数据 I2C_SendData((uint8_t)mem); //发送内存地址的低8位 while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //判断TRA, BUSY, MSL, TXE and BTF flags //直接产生一个重起始信号即可开始读的过程 I2C_GenerateSTART(ENABLE); //重起始信号 while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags I2C_Send7bitAddress(addr, I2C_Direction_Receiver); //发送地址+最低位1表示为“读” while(!I2C_CheckEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); //判断BUSY, MSL and ADDR flags I2C_GenerateSTOP(DISABLE);
  
//关闭停止信号使能(某些情况下可能会在此清除PE,这一行可以去掉) for(uint8_t i=0; i<len; i++) { if(i == len-1) I2C_AcknowledgeConfig(DISABLE);   
    //清除ACK位(某些情况下可能会在此清除PE,这一行可以去掉,但逻辑分析仪抓时序,会多接收一字节数据)     //主机为了能在收到最后一个字节后产生一个NACK信号,必须在读取倒数第二个字节之后(倒数第二个RxNE 事件之后)清除ACK位(ACK=0) while(!I2C_GetFlagStatus(I2C_FLAG_RXNE)); //获取RxEN的状态,等待收到数据 *(des+i) = I2C_ReceiveData(); //获得从机的寄存器中的数据 } I2C_GenerateSTOP(ENABLE); //使能停止信号 I2C_AcknowledgeConfig(ENABLE); //传输完毕,再次打开ACK使能 } //向从机的某内存地址写入1个字节的数据 void IIC_16b_write_byte(uint8_t addr, uint16_t mem, uint8_t data) { //主机通知从机要写它的哪块内存 while(I2C_GetFlagStatus(I2C_FLAG_BUSY)); //IIC主机判忙 I2C_GenerateSTART(ENABLE); //起始信号 while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags I2C_Send7bitAddress(addr, I2C_Direction_Transmitter); //发送地址+最低位0表示为“写” while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //判断BUSY, MSL, ADDR, TXE and TRA flags I2C_SendData((uint8_t)(mem>>8)); //发送内存地址的高8位 while(!I2C_GetFlagStatus(I2C_FLAG_TXE)); //获取TxE的状态 数据寄存器为空标志位,可以向其中写数据 I2C_SendData((uint8_t)mem); //发送内存地址的低8位 //ACK之后直接写入数据 while(!I2C_GetFlagStatus(I2C_FLAG_TXE)); //获取TxE的状态 数据寄存器为空标志位,可以向其中写数据 I2C_SendData(data); //发送数据 while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //判断TRA, BUSY, MSL, TXE and BTF flags I2C_GenerateSTOP(ENABLE); //停止信号 } //向从机某寄内存地址起始,连续写入n个字节的数据 void IIC_16b_write_nBytes(uint8_t addr, uint16_t mem, uint8_t *src, uint8_t len) { //主机通知从机要写它的哪块内存 while(I2C_GetFlagStatus(I2C_FLAG_BUSY)); //IIC主机判忙 I2C_GenerateSTART(ENABLE); //起始信号 while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags I2C_Send7bitAddress(addr, I2C_Direction_Transmitter); //发送地址+最低位0表示为“写” while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //判断BUSY, MSL, ADDR, TXE and TRA flags I2C_SendData((uint8_t)(mem>>8)); //发送内存地址的高8位 while(!I2C_GetFlagStatus(I2C_FLAG_TXE)); //获取TxE的状态 数据寄存器为空标志位,可以向其中写数据 I2C_SendData((uint8_t)mem); //发送内存地址的低8位 //ACK之后直接写入数据 for(uint8_t i=0; i<len; i++) { while(!I2C_GetFlagStatus(I2C_FLAG_TXE)); //获取TxE的状态 数据寄存器为空标志位,可以向其中写数据 I2C_SendData(*(src+i)); //发送数据 } while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //判断TRA, BUSY, MSL, TXE and BTF flags I2C_GenerateSTOP(ENABLE); //停止信号 }

然后就出了BUG,连续写可以往里写,连续读数据时,故意将地址往后偏移了几个单位,读的值却和没偏移一样,往后偏移64个单位也一样,似乎只能“按扇区读写”乃至“按块读写”。连续写后单字节读也是类似的现象,地址往后稍微偏移,也只能读出最近一次写入的第一个字节的数据。逻辑分析仪抓包,与串口打印数据一致,ACK/NACK都正常。

试用8位内存地址寻址,不用16位寻址,结果居然一切正常……可是8位地址+块选择控制位,怎么也不能达到1片EEPROM的64K的存储量呀……手册中的读写时序也都是16位内存地址……

 

 

 

现象就是这么个现象,可能是笔者忽略了一些东西,又或者买到了假EEPROM……总之,代码直接贴出,该篇博客均是在从机设备地址为7bit的情况下编写的代码,可在CH582上用该代码交互其他IIC从机设备。

//从从机的某寄存器,读取一个字节的数据
void IIC_8b_read_byte(uint8_t addr, uint8_t mem, uint8_t *des)
{
//主机通知从机要读取它的哪块内存
    while(I2C_GetFlagStatus(I2C_FLAG_BUSY));                            //IIC主机判忙
    I2C_GenerateSTART(ENABLE);                                          //起始信号
    while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));               //判断BUSY, MSL and SB flags
    I2C_Send7bitAddress(addr, I2C_Direction_Transmitter);               //发送器件地址+最低位0表示为“写”

    while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //判断BUSY, MSL, ADDR, TXE and TRA flags
    I2C_SendData(mem);                                                  //发送内存地址
    while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED));          //判断TRA, BUSY, MSL, TXE and BTF flags

//直接产生一个重起始信号即可开始读的过程
    I2C_GenerateSTART(ENABLE);                                          //重起始信号
    while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));               //判断BUSY, MSL and SB flags
    I2C_Send7bitAddress(addr, I2C_Direction_Receiver);                  //发送地址+最低位1表示为“读”
    while(!I2C_CheckEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));    //判断BUSY, MSL and ADDR flags

    I2C_GenerateSTOP(DISABLE);                                          
  
//关闭停止信号使能(某些情况下可能会在此清除PE,这一行可以去掉) I2C_AcknowledgeConfig(DISABLE);
  //关闭ACK使能,接收一个字节数据后,主机就回NACK表示不再接收数据
  //(某些情况下可能会在此清除PE,这一行可以去掉,但逻辑分析仪抓时序,会多接收一个字节数据)
while(!I2C_GetFlagStatus(I2C_FLAG_RXNE)); //获取RxEN的状态,等待收到数据 *des = I2C_ReceiveData(); //获得从机的寄存器中的数据 I2C_GenerateSTOP(ENABLE); //停止信号 I2C_AcknowledgeConfig(ENABLE); //传输完毕,再次打开ACK使能 } //从从机的某寄存器起始,连续读取n个字节的数据 void IIC_8b_read_nBytes(uint8_t addr, uint8_t mem, uint8_t *des, uint8_t len) { //主机通知从机要读取它的哪块内存 while(I2C_GetFlagStatus(I2C_FLAG_BUSY)); //IIC主机判忙 I2C_GenerateSTART(ENABLE); //起始信号 while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags I2C_Send7bitAddress(addr, I2C_Direction_Transmitter); //发送器件地址+最低位0表示为“写” while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //判断BUSY, MSL, ADDR, TXE and TRA flags I2C_SendData(mem); //发送内存地址 while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //判断TRA, BUSY, MSL, TXE and BTF flags //直接产生一个重起始信号即可开始读的过程 I2C_GenerateSTART(ENABLE); //重起始信号 while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags I2C_Send7bitAddress(addr, I2C_Direction_Receiver); //发送地址+最低位1表示为“读” while(!I2C_CheckEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); //判断BUSY, MSL and ADDR flags I2C_GenerateSTOP(DISABLE);
  
//关闭停止信号使能(某些情况下可能会在此清除PE,这一行可以去掉) for(uint8_t i=0; i<len; i++) { if(i == len-1) I2C_AcknowledgeConfig(DISABLE);
  //清除ACK位(某些情况下可能会在此清除PE,这一行可以去掉,但是逻辑分析仪抓时序,会多接收一个字节数据)   //主设备为了能在收到最后一个字节后产生一个NACK信号,必须在读取倒数第二个字节之后(倒数第二个RxNE 事件之后)清除ACK位(ACK=0) while(!I2C_GetFlagStatus(I2C_FLAG_RXNE)); //获取RxEN的状态,等待收到数据 *(des+i) = I2C_ReceiveData(); //获得从机的寄存器中的数据 } I2C_GenerateSTOP(ENABLE); //使能停止信号 I2C_AcknowledgeConfig(ENABLE); //传输完毕,再次打开ACK使能 } //向从机的某内存地址写入1个字节的数据 void IIC_8b_write_byte(uint8_t addr, uint8_t mem, uint8_t data) { //主机通知从机要写它的哪块内存 while(I2C_GetFlagStatus(I2C_FLAG_BUSY)); //IIC主机判忙 I2C_GenerateSTART(ENABLE); //起始信号 while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags I2C_Send7bitAddress(addr, I2C_Direction_Transmitter); //发送地址+最低位0表示为“写” while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //判断BUSY, MSL, ADDR, TXE and TRA flags I2C_SendData(mem); //发送内存地址 //ACK之后直接写入数据 while(!I2C_GetFlagStatus(I2C_FLAG_TXE)); //获取TxE的状态 数据寄存器为空标志位,可以向其中写数据 I2C_SendData(data); //发送数据 while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //判断TRA, BUSY, MSL, TXE and BTF flags I2C_GenerateSTOP(ENABLE); //停止信号 } //向从机某寄内存地址起始,连续写入n个字节的数据 void IIC_8b_write_nBytes(uint8_t addr, uint8_t mem, uint8_t *src, uint8_t len) { //主机通知从机要写它的哪块内存 while(I2C_GetFlagStatus(I2C_FLAG_BUSY)); //IIC主机判忙 I2C_GenerateSTART(ENABLE); //起始信号 while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags I2C_Send7bitAddress(addr, I2C_Direction_Transmitter); //发送地址+最低位0表示为“写” while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //判断BUSY, MSL, ADDR, TXE and TRA flags I2C_SendData(mem); //发送内存地址 //ACK之后直接写入数据 for(uint8_t i=0; i<len; i++) { while(!I2C_GetFlagStatus(I2C_FLAG_TXE)); //获取TxE的状态 数据寄存器为空标志位,可以向其中写数据 I2C_SendData(*(src+i)); //发送数据 } while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //判断TRA, BUSY, MSL, TXE and BTF flags I2C_GenerateSTOP(ENABLE); //停止信号 }
posted @ 2022-06-22 10:09  JayWell  阅读(844)  评论(0编辑  收藏  举报