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

[文章]

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

开发环境:

IDE:MounRiver Studio

MCU:CH585

5 软件I2C

5.1 具体代码实现

首先实现I2C的协议。

/**
  * [url=home.php?mod=space&uid=2666770]@Brief[/url]  I2C_Delay, I2C总线位延迟,最快400KHz
  * [url=home.php?mod=space&uid=3142012]@param[/url]  None
  * @retval None
  */
static void I2C_Delay(void)
{
    uint8_t i;

    for (i = 0; i < 10; i++);
}

/**
  * @brief  I2C_Start, CPU发起I2C总线启动信号
  * @param  None
  * @retval None
  */
void I2C_Start(void)
{
    /* 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 */
    I2C_SDA_1();
    I2C_SCL_1();
    I2C_Delay();
    I2C_SDA_0();//START:when CLK is high,DATA change form high to low 
    I2C_Delay();
    I2C_SCL_0();
    I2C_Delay();
}

/**
  * @brief  I2C_Stop, CPU发起I2C总线停止信号
  * @param  None
  * @retval None
  */
void I2C_Stop(void)
{
    /* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */
    I2C_SDA_0();//STOP:when CLK is high DATA change form low to high
    I2C_SCL_1();
    I2C_Delay();
    I2C_SDA_1();
}

/**
  * @brief  I2C_SendByte, CPU向I2C总线设备发送8bit数据
  * @param  _ucByte : 等待发送的字节
  * @retval None
  */
void I2C_SendByte(uint8_t _ucByte)
{
    uint8_t i;

    /* 先发送字节的高位bit7 */
    for (i = 0; i < 8; i++)
    {        
        if (_ucByte & 0x80)
        {
            I2C_SDA_1();
        }
        else
        {
            I2C_SDA_0();
        }
        I2C_Delay();
        I2C_SCL_1();
        I2C_Delay();    
        I2C_SCL_0();
        if (i == 7)
        {
            I2C_SDA_1(); // 释放总线
        }
        _ucByte <<= 1;    /* 左移一个bit */
        I2C_Delay();
    }
}

/**
  * @brief  I2C_ReadByte, CPU从I2C总线设备读取8bit数据
  * @param  None
  * @retval 读到的数据
  */
uint8_t I2C_ReadByte(void)
{
    uint8_t i;
    uint8_t value;

    /* 读到第1个bit为数据的bit7 */
    value = 0;
    for (i = 0; i < 8; i++)
    {
        value <<= 1;
        I2C_SCL_1();
        I2C_Delay();
        if (I2C_SDA_READ())
        {
            value++;
        }
        I2C_SCL_0();
        I2C_Delay();
    }
    return value;
}

/**
  * @brief  I2C_WaitAck, CPU产生一个时钟,并读取器件的ACK应答信号
  * @param  None
  * @retval 返回0表示正确应答,1表示无器件响应
  */
uint8_t I2C_WaitAck(void)
{
    uint8_t re;

    I2C_SDA_1();    /* CPU释放SDA总线 */
    I2C_Delay();
    I2C_SCL_1();    /* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
    I2C_Delay();
    if (I2C_SDA_READ())    /* CPU读取SDA口线状态 */
    {
        re = 1;
    }
    else
    {
        re = 0;
    }
    I2C_SCL_0();
    I2C_Delay();
    return re;
}

/**
  * @brief  I2C_Ack, CPU产生一个ACK信号
  * @param  None
  * @retval None
  */
void I2C_Ack(void)
{
    I2C_SDA_0();    /* CPU驱动SDA = 0 */
    I2C_Delay();
    I2C_SCL_1();    /* CPU产生1个时钟 */
    I2C_Delay();
    I2C_SCL_0();
    I2C_Delay();
    I2C_SDA_1();    /* CPU释放SDA总线 */
}

/**
  * @brief  iI2C_NAck, CPU产生1个NACK信号
  * @param  None
  * @retval None
  */
void I2C_NAck(void)
{
    I2C_SDA_1();    /* CPU驱动SDA = 1 */
    I2C_Delay();
    I2C_SCL_1();    /* CPU产生1个时钟 */
    I2C_Delay();
    I2C_SCL_0();
    I2C_Delay();    
}

/**
  * @brief  I2C_Cfg_GPIO, 配置I2C总线的GPIO,采用模拟IO的方式实现
  * @param  None
  * @retval None
  */
static void I2C_Cfg_GPIO(void)
{
    GPIOB_ModeCfg(I2C_SCL_PIN, GPIO_ModeOut_PP_5mA);
    GPIOB_ModeCfg(I2C_SDA_PIN, GPIO_ModeOut_PP_5mA);
    GPIOB_SetBits(I2C_SCL_PIN);
    GPIOB_SetBits(I2C_SDA_PIN);

    /* 给一个停止信号, 复位I2C总线上的所有设备到待机模式 */
    I2C_Stop();
}

/**
  * @brief  i2c_CheckDevice, 检测I2C总线设备,CPU向发送设备地址,然后读取设备应答来判断该设备是否存在
  * @param  _Address:设备的I2C总线地址
  * @retval 返回值 0 表示正确, 返回1表示未探测到
  */
uint8_t I2C_CheckDevice(uint8_t _Address)
{
    uint8_t ucAck;

    I2C_Cfg_GPIO();        /* 配置GPIO */

    I2C_Start();        /* 发送启动信号 */

    /* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
    I2C_SendByte(_Address | I2C_WR);
    ucAck = I2C_WaitAck();    /* 检测设备的ACK应答 */

    I2C_Stop();            /* 发送停止信号 */

    return ucAck;
}

注释很清楚,对照I2C的协议看就行。

接着就是实现AT2C02的读写操作。

/**
  * @brief  EEPROM_CheckOk, 判断串行EERPOM是否正常
  * @param  None
  * @retval 1 表示正常, 0 表示不正常
  */
uint8_t EEPROM_CheckOk(void)
{
    if (I2C_CheckDevice(EEPROM_DEV_ADDR) == 0)
    {
        return 1;
    }
    else
    {
        /* 失败后,切记发送I2C总线停止信号 */
        I2C_Stop();
        return 0;
    }
}

/**
  * @brief  EEPROM_ReadBytes, 从串行EEPROM指定地址处开始读取若干数据
  * @param  _usAddress : 起始地址
  *            _usSize : 数据长度,单位为字节
  *         _pReadBuf : 存放读到的数据的缓冲区指针
  * @retval 0 表示失败,1表示成功
  */
uint8_t EEPROM_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize)
{
    uint16_t i;

    /* 采用串行EEPROM随即读取指令序列,连续读取若干字节 */

    /* 第1步:发起I2C总线启动信号 */
    I2C_Start();

    /* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
    I2C_SendByte(EEPROM_DEV_ADDR | I2C_WR);    /* 此处是写指令 */

    /* 第3步:发送ACK */
    if (I2C_WaitAck() != 0)
    {
        goto cmd_fail;    /* EEPROM器件无应答 */
    }

    /* 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址 */
    I2C_SendByte((uint8_t)_usAddress);
    
    /* 第5步:发送ACK */
    if (I2C_WaitAck() != 0)
    {
        goto cmd_fail;    /* EEPROM器件无应答 */
    }

    /* 第6步:重新启动I2C总线。前面的代码的目的向EEPROM传送地址,下面开始读取数据 */
    I2C_Start();

    /* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
    I2C_SendByte(EEPROM_DEV_ADDR | I2C_RD);    /* 此处是读指令 */

    /* 第8步:发送ACK */
    if (I2C_WaitAck() != 0)
    {
        goto cmd_fail;    /* EEPROM器件无应答 */
    }    

    /* 第9步:循环读取数据 */
    for (i = 0; i < _usSize; i++)
    {
        _pReadBuf[i] = I2C_ReadByte();    /* 读1个字节 */
        
        /* 每读完1个字节后,需要发送Ack, 最后一个字节不需要Ack,发Nack */
        if (i != _usSize - 1)
        {
            I2C_Ack();    /* 中间字节读完后,CPU产生ACK信号(驱动SDA = 0) */
        }
        else
        {
            I2C_NAck();    /* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */
        }
    }
    /* 发送I2C总线停止信号 */
    I2C_Stop();
    return 1;    /* 执行成功 */

cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
    /* 发送I2C总线停止信号 */
    I2C_Stop();
    return 0;
}

/**
  * @brief  EEPROM_WriteBytes, 向串行EEPROM指定地址写入若干数据,采用页写操作提高写入效率
  * @param  _usAddress : 起始地址
  *            _usSize : 数据长度,单位为字节
  *         _pWriteBuf : 存放读到的数据的缓冲区指针
  * @retval 0 表示失败,1表示成功
  */
uint8_t EEPROM_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize)
{
    uint16_t i,m;
    uint16_t usAddr;

    /* 
        写串行EEPROM不像读操作可以连续读取很多字节,每次写操作只能在同一个page。
        对于24xx02,page size = 8
        简单的处理方法为:按字节写操作模式,没写1个字节,都发送地址
        为了提高连续写的效率: 本函数采用page wirte操作。
    */

    usAddr = _usAddress;
    for (i = 0; i < _usSize; i++)
    {
        /* 当发送第1个字节或是页面首地址时,需要重新发起启动信号和地址 */
        if ((i == 0) || (usAddr & (EEPROM_PAGE_SIZE - 1)) == 0)
        {
            /* 第0步:发停止信号,启动内部写操作 */
            I2C_Stop();
            
            /* 通过检查器件应答的方式,判断内部写操作是否完成, 一般小于 10ms
                CLK频率为200KHz时,查询次数为30次左右
            */
            for (m = 0; m < 100; m++)
            {
                /* 第1步:发起I2C总线启动信号 */
                I2C_Start();

                /* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
                I2C_SendByte(EEPROM_DEV_ADDR | I2C_WR);    /* 此处是写指令 */
                
                /* 第3步:发送一个时钟,判断器件是否正确应答 */
                if (I2C_WaitAck() == 0)
                {
                    break;
                }
            }
            if (m  == 1000)
            {
                goto cmd_fail;    /* EEPROM器件写超时 */
            }

            /* 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址 */
            I2C_SendByte((uint8_t)usAddr);
            
            /* 第5步:发送ACK */
            if (I2C_WaitAck() != 0)
            {
                goto cmd_fail;    /* EEPROM器件无应答 */
            }
        }

        /* 第6步:开始写入数据 */
        I2C_SendByte(_pWriteBuf[i]);
    
        /* 第7步:发送ACK */
        if (I2C_WaitAck() != 0)
        {
            goto cmd_fail;    /* EEPROM器件无应答 */
        }

        usAddr++;    /* 地址增1 */
    }
    
    /* 命令执行成功,发送I2C总线停止信号 */
    I2C_Stop();
    return 1;

cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
    /* 发送I2C总线停止信号 */
    I2C_Stop();
    return 0;
}

/**
  * @brief  EEPROM_Erase
  * @param  None
  * @retval None
  */
void EEPROM_Erase(void)
{
    uint16_t i;
    uint8_t buf[EEPROM_SIZE];
    
    /* 填充缓冲区 */
    for (i = 0; i < EEPROM_SIZE; i++)
    {
        buf[i] = 0xFF;
    }

    /* 写EEPROM, 起始地址 = 0,数据长度为 256 */
    if (EEPROM_WriteBytes(buf, 0, EEPROM_SIZE) == 0)
    {
        printf("擦除eeprom出错!\r\n");
        return;
    }
    else
    {
        printf("擦除eeprom成功!\r\n");
    }
}

/**
  * @brief  EE_Delay
  * @param  nCount
  * @retval None
  */
static void EEPROM_Delay(__IO uint32_t nCount)     //简单的延时函数
{
    for(; nCount != 0; nCount--);
}

/**
  * @brief  AT24C02 读写测试
  * @param  None
  * @retval None
  */
void EEPROM_Test(void)
{
    uint16_t i;
    uint8_t write_buf[EEPROM_SIZE];
    uint8_t read_buf[EEPROM_SIZE];

    /*-----------------------------------------------------------------------------------*/
    if (EEPROM_CheckOk() == 0)
    {
        /* 没有检测到EEPROM */
        printf("Serial EEPROM not detected!\r\n");
        while (1);    /* 停机 */
    }
    /*------------------------------------------------------------------------------------*/
    /* 填充测试缓冲区 */
    for (i = 0; i < EEPROM_SIZE; i++)
    {
        write_buf[i] = i;
    }
    /*------------------------------------------------------------------------------------*/
    if (EEPROM_WriteBytes(write_buf, 0, EEPROM_SIZE) == 0)
    {
        printf("Error writing eeprom!\r\n");
        return;
    }
    else
    {
        printf("Successfully written eeprom!\r\n");
    }

    /*写完之后需要适当的延时再去读,不然会出错*/
    EEPROM_Delay(0x0FFFFF);
    /*-----------------------------------------------------------------------------------*/
    if (EEPROM_ReadBytes(read_buf, 0, EEPROM_SIZE) == 0)
    {
        printf("Error reading eeprom!\r\n");
        return;
    }
    else
    {
        printf("Read eeprom successfully, with the following data: \r\n");
    }
    /*-----------------------------------------------------------------------------------*/
    for (i = 0; i < EEPROM_SIZE; i++)
    {
        if(read_buf[i] != write_buf[i])
        {
            printf("0x%02X ", read_buf[i]);
            printf("Error: Inconsistent data between I2C EEPROM writing and reading\n\r");
            return;
        }
        printf(" %02X", read_buf[i]);

        if ((i & 15) == 15)
        {
            printf("\r\n");
        }
    }
    printf("I2C(AT24C02) Read and write test successful\r\n");
    while(1);
}

代码很简单,和使用硬件I2C的逻辑是一样的。

最后看下主函数吧。

/*********************************************************************
 * @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");

    EEPROM_Test();

    while(1)
    {

    }
}

主函数中重点关注EEPROM_Test()函数,这就是对AT24C02的读写操作。

5.2 实验现象

下载程序,连接串口打印信息如下。

3.png

Figure 5‑1 软件I2C实现现象

更多回帖

×
发帖