【沁恒CH585开发板免费试用体验】I2C 读写EEPROM (二) - RISC-V MCU技术社区 - 电子技术论坛 - 广受欢迎的专业电子论坛
分享 收藏 返回

[文章]

【沁恒CH585开发板免费试用体验】I2C 读写EEPROM (二)

开发环境:

IDE:MounRiver Studio

MCU:CH585

4 硬件I2C

4.1 具体代码实现

首先看看I2C的初始化。这有两部分。

一部分是I2C的GPIO初始化。

/**
  * [url=home.php?mod=space&uid=2666770]@Brief[/url]  I2C1 I/O配置
  * [url=home.php?mod=space&uid=3142012]@param[/url]  无
  * @retval 无
  */
static void I2C_GPIO_Config(void)
{
    // I2C
    GPIOB_ModeCfg(GPIO_Pin_12 | GPIO_Pin_13, GPIO_ModeIN_PU);
}

另外一部分就是配置I2C的参数。

/**
  * @brief  I2C 工作模式配置
  * @param  无
  * @retval 无
  */
static void I2C_Mode_Config(void)
{
    // I2C host mode
    I2C_Init(I2C_Mode_I2C, 100000, I2C_DutyCycle_16_9, I2C_Ack_Enable, I2C_AckAddr_7bit, MASTER_ADDR);
    while(I2C_GetFlagStatus(I2C_FLAG_BUSY) != RESET);
}

主要配置I2C模式、低电平占空比、I2C寻址模式以及通信速率,最后使能I2C设备。

初始化完成后就是对AT24C02的读写操作,严格按照相应的时序操作就行。

  • 字节写

在字节写模式下,向AT24C02中写数据时序如下:

175440214441456dmd4qme1

Figure 4‑1 字节写操作

操作时序如下:

1.MCU先发送一个开始信号(START)启动总线

2.接着跟上首字节,发送器件写操作地址(DEVICE ADDRESS)+写数据(0xA0)

3.等待应答信号(ACK)

4.发送数据的存储地址。24C02一共有256个字节的存储空间,地址从0x00~0xFF,想把数据存储在哪个位置,此刻写的就是哪个地址。

5.发送要存储的数据,在写数据的过程中,AT24C02会回应一个“应答位0”,则表明写AT24C02数据成功,如果没有回应答位,说明写入不成功。

6.发送结束信号(STOP)停止总线。

代码很简单,跟着时序来就行。

/**
  * @brief   写一个字节到I2C EEPROM中
  * @param
  * [url=home.php?mod=space&uid=2583231]@arg[/url] pBuffer:缓冲区指针
  * @arg WriteAddr:写地址
  * @retval  无
  */
void I2C_EE_ByteWrite(uint8_t* pBuffer, uint8_t WriteAddr)
{
    /* Send STRAT condition */
    I2C_GenerateSTART(ENABLE);

    while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));

    /* Send EEPROM address for write */
    I2C_Send7bitAddress(EEPROM_ADDRESS, I2C_Direction_Transmitter);

    /* Test on EV6 and clear it */
    while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

    /* Send the EEPROM's internal address to write to */
    I2C_SendData(WriteAddr);

    while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED));

    /* Send the byte to be written */
    I2C_SendData(*pBuffer);

    while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED));

    /* Send STOP condition */
    I2C_GenerateSTOP(ENABLE);
}
  • 页写

用页写,AT24C01可一次写入8 个字节数据,AT24C02/04/08/16可以一次写入16个字节的数据。__页写操作的启动和字节写一样,不同在于传送了一字节数据后并不产生停止信号。__每发送一个字节数据后AT24Cxx产生一个应答位并将字节地址低位加1,高位保持不变。

如果在发送停止信号之前主器件发送超过P+1个字节,地址计数器将自动翻转,先前写入的数据被覆盖。

接收到P+1字节数据和主器件发送的停止信号后,AT24Cxx启动内部写周期将数据写到数据区。所有接收的数据在一个写周期内写入AT24Cxx。

1754402144556jo1hpn1qu2

Figure 4‑2 页写时序

代码很简单,和字节写不同的是,数据会一直发,直到主机发送停止信号。

/**
  * @brief   在EEPROM的一个写循环中可以写多个字节,但一次写入的字节数
  *          不能超过EEPROM页的大小,AT24C02每页有8个字节
  * @param
  * @arg pBuffer:缓冲区指针
  * @arg WriteAddr:写地址
  *     @arg NumByteToWrite:写的字节数
  * @retval  无
  */
void I2C_EE_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint8_t NumByteToWrite)
{
    while(I2C_GetFlagStatus(I2C_FLAG_BUSY)); // Added by Najoua 27/08/2008

    /* Send START condition */
    I2C_GenerateSTART(ENABLE);

    /* Test on EV5 and clear it */
    while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));

    /* Send EEPROM address for write */
    I2C_Send7bitAddress(EEPROM_ADDRESS, I2C_Direction_Transmitter);

    /* Test on EV6 and clear it */
    while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

    /* Send the EEPROM's internal address to write to */
    I2C_SendData(WriteAddr);

    /* Test on EV8 and clear it */
    while(! I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED));

    /* While there is data to be written */
    while(NumByteToWrite--)
    {
        /* Send the current byte */
        I2C_SendData(*pBuffer);

        /* Point to the next byte to be written */
        pBuffer++;

        /* Test on EV8 and clear it */
        while (!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED));
    }

    /* Send STOP condition */
    I2C_GenerateSTOP(ENABLE);
}
  • 任意写

在实际过程中,我们经常需要任意写数据,这里就调用页写的操作,来实现任意字节的写操作。

/**
  * @brief   将缓冲区中的数据写到I2C EEPROM中
  * @param
  * @arg pBuffer:缓冲区指针
  * @arg WriteAddr:写地址
  *     @arg NumByteToWrite:写的字节数
  * @retval  无
  */
void I2C_EE_BufferWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite)
{
    uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;

    Addr = WriteAddr % I2C_PageSize;
    count = I2C_PageSize - Addr;
    NumOfPage =  NumByteToWrite / I2C_PageSize;
    NumOfSingle = NumByteToWrite % I2C_PageSize;

    /* If WriteAddr is I2C_PageSize aligned  */
    if(Addr == 0)
    {
        /* If NumByteToWrite < I2C_PageSize */
        if(NumOfPage == 0)
        {
            I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
            I2C_EE_WaitEepromStandbyState();
        }
        /* If 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();
            }
        }
    }
    /* If WriteAddr is not I2C_PageSize aligned  */
    else
    {
        /* If NumByteToWrite < I2C_PageSize */
        if(NumOfPage== 0)
        {
            I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
            I2C_EE_WaitEepromStandbyState();
        }
        /* If NumByteToWrite > I2C_PageSize */
        else
        {
            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();
            }
        }
    }
}

主要分为两种情况,写的地址正好是一页的开始,另外一种是在一页的中间。不管如何,始终遵循的原则就是最大智能写一页,可以从一页的中间开始。

  • 读字节

读操作允许主器件对寄存器的任意字节进行读操作,主器件首先通过发送起始信号、从器件地址和它想读取的字节数据的地址执行一个写操作。在AT24Cxx应答之后,主器件重新发送起始信号和从器件地址,此时R/W位置1,AT24Cxx响应并发送应答信号,然后输出所要求的一个8位字节数据,主器件不发送应答信号但产生一个停止信号。

17544021446773yjyu994mu

Figure 4‑3 读取字节的时序

读取字节的时序如下:

1.MCU先发送一个开始信号(START)启动总线

2.接着跟上首字节,发送器件写操作地址(DEVICE ADDRESS)+写数据(0xA0)

注意:这里写操作是为了要把所要读的数据的存储地址先写进去,告诉AT24Cxx要读取哪个地址的数据。

3.发送要读取内存的地址(WORD ADDRESS),通知AT24Cxx读取要哪个地址的信息。

4.重新发送开始信号(START)。

5.发送设备读操作地址(DEVICE ADDRESS)对AT24Cxx进行读操作 (0xA1)。

6.AT24Cxx会自动向主机发送数据,主机读取从器件发回的数据,在读一个字节后,MCU会回应一个应答信号(ACK)。

7.发送一个“非应答位NAK(1)”。发送结束信号(STOP)停止总线。

  • 顺序读

在AT24Cxx发送完一个8位字节数据后,主器件产生一个应答信号来响应,告知AT24Cxx主器件要求更多的数据,对应每个主机产生的应答信号AT24Cxx将发送一个8位数据字节。当主器件不发送应答信号而发送停止位时结束此操作。

从AT24Cxx输出的数据按顺序由N到N+1输出。读操作时地址计数器在AT24Cxx整个地址内增加,这样整个寄存器区域可在一个读操作内全部读出,当读取的字节超过E(对于24WC01,E=127;对24C02,E=255;对24C04,E=511;对24C08,E=1023;对24C16,E=2047)计数器将翻转到零并继续输出数据字节。

1754402144805xy99pfc9xz

Figure 4‑4 顺序读时序

我们常用的方式就是连续读取,代码很简单。

/**
  * @brief   从EEPROM里面读取一块数据
  * @param
  * @arg pBuffer:存放从EEPROM读取的数据的缓冲区指针
  * @arg WriteAddr:接收数据的EEPROM的地址
  *     @arg NumByteToWrite:要从EEPROM读取的字节数
  * @retval  无
  */
void I2C_EE_BufferRead(uint8_t* pBuffer, uint8_t ReadAddr, uint16_t NumByteToRead)
{
    //*((u8 *)0x4001080c) |=0x80;
    while(I2C_GetFlagStatus(I2C_FLAG_BUSY)); // Added by Najoua 27/08/2008

    /* Send START condition */
    I2C_GenerateSTART(ENABLE);
    //*((u8 *)0x4001080c) &=~0x80;

    /* Test on EV5 and clear it */
    while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));

    /* Send EEPROM address for write */
    I2C_Send7bitAddress(EEPROM_ADDRESS, I2C_Direction_Transmitter);

    /* Test on EV6 and clear it */
    while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

    /* Clear EV6 by setting again the PE bit */
    I2C_Cmd(ENABLE);

    /* Send the EEPROM's internal address to write to */
    I2C_SendData(ReadAddr);

    /* Test on EV8 and clear it */
    while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED));

    /* Send STRAT condition a second time */
    I2C_GenerateSTART(ENABLE);

    /* Test on EV5 and clear it */
    while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));

    /* Send EEPROM address for read */
    I2C_Send7bitAddress(EEPROM_ADDRESS, I2C_Direction_Receiver);

    /* Test on EV6 and clear it */
    while(!I2C_CheckEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));

    /* While there is data to be read */
    while(NumByteToRead)
    {
        if(NumByteToRead == 1)
        {
            /* Disable Acknowledgement */
            I2C_AcknowledgeConfig(DISABLE);

            /* Send STOP Condition */
            I2C_GenerateSTOP(ENABLE);
        }

        /* Test on EV7 and clear it */
        if(I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_RECEIVED))
        {
            /* Read a byte from the EEPROM */
            *pBuffer = I2C_ReceiveData();

            /* Point to the next location where the byte read will be saved */
            pBuffer++;

            /* Decrement the read bytes counter */
            NumByteToRead--;
         }
    }

    /* Enable Acknowledgement to be ready for another reception */
    I2C_AcknowledgeConfig(ENABLE);
}

最后看下主函数吧。

/*********************************************************************
 * @fn      main
 *
 * @brief   主函数
 *
 * [url=home.php?mod=space&uid=1141835]@Return[/url]  none
 */
int main()
{
    HSECFG_Capacitance(HSECap_18p);
    SetSysClock(CLK_SOURCE_HSE_PLL_62_4MHz);

    BSP_UART_Init();

    printf("I2C Test\r\n");

    /* I2C 外设初(AT24C02)始化 */
    I2C_EE_Init();

    I2C_Test();

    while(1)
    {

    }
}

很简单,往AT24C02中写入数据,然后再读取数据,读写测试的函数如下:

/**
  * @brief  I2C(AT24C02)读写测试
  * @param  无
  * @retval 无
  */
void I2C_Test(void)
{
    uint16_t i;

    printf("Write data\r\n");

    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);

    printf("\r\nWrite success\r\n");
    printf("\r\nRead data\r\n");
    //将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]);
            printf("Error: Inconsistent data between I2C EEPROM writing and reading\n\r");
            return;
        }
        printf("0x%02X ", I2c_Buf_Read[i]);
        if(i%16 == 15)
        {
            printf("\r\n");
        }
    }
    printf("I2C(AT24C02) Read and write test successful\r\n");
}

4.2 实验现象

下载好程序后,打开串口助手,可以看到如下信息。

2.png

Figure 4‑5 硬件I2C实现现象

最后,我们使用逻辑分析来查看数据。

175440214528888y8rr0ui2

Figure 4‑6 I2C具体时序

我这里使用的100kHz的速率,可以看到数据的写操作和前面分析的时序是一样的,完全吻合。

更多回帖

×
发帖