《ESP32-P4开发指南—V1.0》第二十三章 IIC_EEPROM实验

第二十三章 IIC_EEPROM实验


       本章将学习ESP32-P4的硬件IIC接口去驱动24C02器件。在本章节,实现和24C02之间的双向通信,并把结果显示在LCD模块上。

       本章分为如下几个小节:

       23.1 24C02介绍

       23.2 硬件设计

       23.3 程序设计

       23.4 下载验证


        23.1 24C02介绍

       关于IIC的描述,请回顾第十九章IIC_EXIO实验,本小节主要介绍24C02,首先科普一下何为EEPROM存储器?

       其实EEPROM全程是“电可擦除可编程只读存储器”,即“Electrically Erasable Programmable Read-Only Memory”,特性就是数据掉电不丢失。

       24C02是一个2K bit的串行EEPROM存储器,内部含有256个字节。在24C02里面还有一个8字节的页写缓冲器。该设备的通信方式IIC,通过其SCL和SDA与其他设备通信,芯片的引脚图如下图所示。


图23.1.1 24C02引脚图


       上图的WP引脚是写保护引脚,接高电平只读,接地允许读和写,我们的板子设计是把该引脚接地。每一个设备都有自己的设备地址,24C02也不例外。前面提及到有7位寻址、11位寻址,这里的位数就是设备地址位数,24C02的设备地址就是7位的,具体格式如下图所示。


图23.1.2 24C02地址格式


       24C02的设备地址是包括不可编程部分和可编程部分,不可编程部分也就是“1010”,可编程部分是根据上图的硬件引脚A0、A1和A2所决定。根据我们的板子设计,A0、A1和A2均接地处理,所以24C02设备地址为“1010000”即0x50。

       这里还会涉及到24C02通信地址的概念,通信地址就是写操作地址和读操作地址,简单来说,就是设备地址和一个读写位的配合。上图中的地址格式最后一位R/W用于设置数据的传输方向,即读操作/写操作,0是写操作,1是读操作,所以24C02的读操作地址为:0xA1(0x50 << 1 | 1),写操作地址为:0xA0(0x50 << 1 | 0)。

       下面把实验中到的数据传输时序讲解一下,分别是对24C02的写时序和读时序。24C02写时序图如下图所示。


图23.1.3 24C02写时序图


       上图展示的主机向24C02写操作时序图,主机在IIC总线发送第1个字节的数据为24C02的写操作地址0xA0(设备地址0x50 << 1 | 0),用于寻找总线上找到24C02,在获得24C02的应答信号之后,继续发送第2个字节数据,该字节数据是24C02的内存地址,再等到24C02的应答信号,主机继续发送第3字节数据,这里的数据即是写入在第2字节内存地址的数据。主机完成写操作后,可以发出停止信号,终止数据传输。

       上面的写操作只能单字节写入到24C02,效率比较低,所以24C02有页写入时序,大大提高了写入效率,下面看一下24C02页写时序图,如下图所示。


图23.1.4 24C02页写时序


       在单字节写时序时,每次写入数据时都需要先写入设备的内存地址才能实现,在页写时序中,只需要告诉24C02第一个内存地址1,后面数据会按照顺序写入到内存地址2,内存地址3等,大大节省了通信时间,提高了时效性。因为24C02每次只能8bit数据,所以它的页大小也就是1字节。页写时序的操作方式跟上面的单字节写时序差不多,所以不作过多解释了。参考以上说明去理解页写时序。

       说完两种写入方式之后,下图是关于24C02的读时序。


图23.1.5 24C02读时序图


       24C02读取数据的过程是一个复合的时序,其中包含写时序和读时序。先看第一个通信过程,这里是写时序,起始信号产生后,主机发送24C02的写操作地址0xA0(设备地址0x50 << 1 | 0),获取从机应答信号后,接着发送需要读取的内存地址;在读时序中,起始信号产生后,主机发送24C02的读操作地址0xA1(设备地址0x50 << 1 | 1),获取从机应答信号后,接着从机返回刚刚在写时序中内存地址的数据,以字节为单位传输在总线上,假如主机获取数据后返回的是应答信号,那么从机会一直传输数据,当主机发出的是非应答信号并以停止信号发出为结束,从机就会结束传输。


        23.2 硬件设计


       23.2.1 例程功能

       每按下KEY1,MCU通过IIC总线向24C02写入数据,通过按下KEY0来控制24C02读取数据。同时在LCD上面显示相关信息。LED0闪烁用于提示程序正在运行。


       23.2.2 硬件资源


       1)LED灯

              LED 0 - IO51


       2)RGBLCD / MIPILCD(引脚太多,不罗列出来)


       3)XL9555

              IIC_INT - IO36

              IIC_SDA - IO33

              IIC_SCL - IO32

              EXIO_8 - KEY0

              EXIO_9 - KEY1


       4)24C02

              IIC_SDA - IO33

              IIC_SCL - IO32


       23.2.3 原理图

       24C02器件相关原理图,如下图所示。


图23.2.3.1 24C02硬件原理图


        23.3 程序设计

       IIC外设驱动已经在第十九章19.3.1小节做了说明,这里就不再赘述了。


       23.3.1 程序流程图


图23.3.1.1 IIC_EEPROM实验程序流程图


       23.3.2 程序解析

       在14_iic_eeprom例程中,作者在14_iic_eeprom\components\BSP路径下新建了1个文件夹AT24C02,并且需要更改CMakeLists.txt内容,以便在其他文件上调用。

       IIC驱动代码跟19.3.3小节的IIC驱动代码部分是一样的,这里就不再赘述了,直接对at24c02驱动做介绍。


       1. AT24C02驱动代码

       这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。AT24CXX驱动源码包括两个文件:at24c02.c和at24c02.h。

       下面先解析at24c02.h的程序。对AT24C02的器件地址和AT24C02芯片最大地址做了相关定义。

#define AT_ADDR     0x50  /* 24c02设备地址 */
#define AT24C02     255

       接下来,解析一下at24c02.c的程序,首先来看一下AT24C02器件的初始化函数at24c02_init,代码如下:

/**
 * @brief      初始化AT24C02
 * @param      无
 * @retval      ESP_OK:初始化成功
 */
esp_err_t at24c02_init(void)
{
    /* 未调用myiic_init初始化IIC */
    if (bus_handle == NULL)
    {
        ESP_ERROR_CHECK(myiic_init());
    }
 
    i2c_device_config_t eeprom_i2c_dev_conf = {
        .dev_addr_length = I2C_ADDR_BIT_LEN_7,  /* 从机地址长度 */
        .scl_speed_hz    = IIC_SPEED_CLK,       /* 传输速率 */
        .device_address  = AT_ADDR,             /* 从机7位的地址 */
    };
    /* I2C总线上添加AT24C02设备 */
ESP_ERROR_CHECK(i2c_master_bus_add_device( bus_handle, &eeprom_i2c_dev_conf, 
&eeprom_handle));
 
    return ESP_OK;
}

       在AT24C02初始化函数中,首先对eeprom_i2c_dev_conf变量的成员进行赋值,设置AT24C02的地址长度、设备地址以及传输速率,然后调用i2c_master_bus_add_device函数对AT24C02设备进行初始化。

       下面先看一下at24c02_write_one_byte函数,实现在AT24C02芯片指定地址写入一个数据,代码如下:

/**
 * @brief   在AT24C02指定地址写入一个数据
 * @param      addr: 写入数据的目的地址
 * @param      data: 要写入的数据
 * @retval     无
 */
void at24c02_write_one_byte(uint8_t addr, uint8_t data)
{
uint8_t send_buf[2] = {0};
 
    send_buf[0] = addr % 256;
    send_buf[1] = data;
 
    ESP_ERROR_CHECK(i2c_master_transmit(eeprom_handle, send_buf, 2, -1));
 
    /* 由于AT24C02写入过慢,需延迟10ms左右时间 */
    esp_rom_delay_us(10000);
}

       该函数的实现,主要调用i2c_master_transmit函数。在这里需要进行数据整合,把写入数据的目的地址和要写入的数据重新存放到一个buf。在这里需要注意存放顺序,写入数据的目的地址要在要写入数据的前面,这样子通过i2c_master_transmit函数发送出去的数据才符合AT24C02的写数据操作。这里需要注意:EEPROM写入比较慢,所以需要延时10ms,等待eeprom写入完毕。

       继续看一下at24c02_read_one_byte,实现从AT24C02内存地址读取数据,代码如下:

/**
 * @brief      在AT24C02指定地址读出一个数据
 * @param     addr: 开始读数的地址
 * @retval    读到的数据
 */
uint8_t at24c02_read_one_byte(uint8_t addr)
{
    uint8_t data = 0;
ESP_ERROR_CHECK(i2c_master_transmit_receive(eeprom_handle, &addr, 1, &data,
        1, -1));
    return data;
}

       该函数的实现主要调用i2c_master_transmit_receive函数。从AT24C02的addr内存地址处读出data数据,作为函数返回值返回。

       有了基本的读写函数接口,就可以写一个比较简单的检测函数at24c02_check,用来测试IIC总线上是否存在24C02或者说器件是否正常,代码如下所示。

/**
 * @brief     检查AT24C02是否正常
 * @note       检测原理: 在器件的末地址写如0X55, 然后再读取, 如果读取值为0X55
 *             则表示检测正常. 否则,则表示检测失败.
 * @param      无
 * @retval      检测结果
 *             0: 检测成功
 *              1: 检测失败
 */
uint8_t at24c02_check(void)
{
    uint8_t temp;
    uint16_t addr = AT24C02;
 
    temp = at24c02_read_one_byte(addr);     /* 避免每次开机都写AT24CXX */
 
    if (temp == 0X55)                       /* 读取数据正常 */
    {
        return 0;
    }
    else                                    /* 排除第一次初始化的情况 */
    {
        at24c02_write_one_byte(addr, 0X55); /* 先写入数据 */
        temp = at24c02_read_one_byte(255);  /* 再读取数据 */
        if (temp == 0X55)
        {
            return 0;
        }
    }
 
    return 1;
}

       在这里,就是利用EEPROM芯片掉电不丢失的特性,在第一次写入了某个值之后,再去读一下看是否写入成功,这种方式就可以去检测芯片是否可以正常工作。

       有时候操作单位往往不是单个字节,所以这里我们也提供了多字节写和多字节读的函数接口,代码如下所示。

/**
 * @brief      在AT24C02里面的指定地址开始读出指定个数的数据
 * @param      addr    : 开始读出的地址 对24c02为0~255
 * @param     pbuf    : 数据数组首地址
 * @param      datalen : 要读出数据的个数
 * @retval     无
 */
void at24c02_read(uint8_t addr, uint8_t *pbuf, uint8_t datalen)
{
    while (datalen--)
    {
        *pbuf++ = at24c02_read_one_byte(addr++);
    }
}
 
/**
 * @brief      在AT24C02里面的指定地址开始写入指定个数的数据
 * @param      addr    : 开始写入的地址 对24c02为0~255
 * @param      pbuf    : 数据数组首地址
 * @param     datalen : 要写入数据的个数
 * @retval     无
 */
void at24c02_write(uint8_t addr, uint8_t *pbuf, uint8_t datalen)
{
    while (datalen--)
    {
        at24c02_write_one_byte(addr, *pbuf);
        addr++;
        pbuf++;
    }
}

       以上两个函数都是基于单个字节读和单个字节写函数实现的,这里就不多讲了。


       3. CMakeLists.txt文件

       本例程的功能实现主要依靠IIC驱动和AT24C02驱动。要在main函数中,成功调用AT24C02文件中的内容,就得需要修改BSP文件夹下的CMakeLists.txt文件,修改如下:

set(src_dirs
            LED
LCD
            MYIIC
           XL9555
           AT24C02)
 
set(include_dirs
           LED
LCD
            MYIIC
           XL9555
           AT24C02)
 
set(requires
            driver
            esp_lcd
            esp_common)
 
idf_component_register( SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})
 
component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)


       4. main.c驱动代码

       在main.c里面编写如下代码。

void app_main(void)
{
    esp_err_t ret;
    uint8_t key = 0;
    uint16_t i = 0;
    uint8_t datatemp[TEXT_SIZE];
 
    ret = nvs_flash_init();     /* 初始化NVS */
    if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ESP_ERROR_CHECK(nvs_flash_init());
    }
 
    led_init();           /* LED初始化 */
    lcd_init();           /* LCD屏初始化 */
    xl9555_init();        /* 初始化按键 */
    at24c02_init();       /* 初始化24CXX */
 
    lcd_show_string(30, 50,  200, 16, 16, "ESP32-P4", RED);
    lcd_show_string(30, 70,  200, 16, 16, "EEPROM TEST", RED);
    lcd_show_string(30, 90,  200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 110, 200, 16, 16, "KEY1:Write  KEY0:Read", RED);
 
    while (at24c02_check())     /* 检测不到24c02 */
    {
        lcd_show_string(30, 130, 200, 16, 16, "24C02 Check Failed!", RED);
        vTaskDelay(pdMS_TO_TICKS(500));
        lcd_show_string(30, 130, 200, 16, 16, "Please Check!      ", RED);
        vTaskDelay(pdMS_TO_TICKS(500));
        LED0_TOGGLE();          
    }
 
    lcd_show_string(30, 130, 200, 16, 16, "24C02 Ready!", RED);
 
    while (1)
    {
        key = xl9555_key_scan(0);   /* 按键扫描  */
 
        if (key == KEY1_PRES)       /* KEY1按下,写入24C02 */
        {
            lcd_fill(0, 150, 239, 319, WHITE);        /* 清除显示 */
            lcd_show_string(30, 150, 200, 16, 16,"Start Write 24C02....", BLUE);
            at24c02_write(0, (uint8_t *)g_text_buf, TEXT_SIZE); /* 写数据 */
            lcd_show_string(30, 150, 200, 16, 16,"24C02 Write Finished!", BLUE);
        }
 
        if (key == KEY0_PRES)       /* KEY0按下,读取字符串并显示 */
        {
            lcd_show_string(30, 150, 200, 16, 16,"Start Read 24C02.... ", BLUE);
            at24c02_read(0, datatemp, TEXT_SIZE);       /* 读取数据 */ 
            lcd_show_string(30, 150, 200, 16, 16,"The Data Readed Is:  ", BLUE);
            lcd_show_string(30, 170, 200, 16, 16, (char *)datatemp, BLUE);      
        }
 
        i++;
 
        if (i == 20)
        {
            LED0_TOGGLE();
            i = 0;
        }
 
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

       在app_main函数中,初始化AT24C02后,检测AT24C02是否存在。若AT24C02正常,则会不断等待按键输入。通过KEY0去读取0地址存放的数据并把数据显示在LCD上;通过KEY1向0地址处写入g_text_buf数据并在LCD上显示传输中,完成后并显示“24C02 Write Finished!”。


        23.4 下载验证

       将程序下载到开发板后,可以看到LED0不停闪烁,提示程序已经在运行了,先按下KEY1写入数据,然后再按KEY0读取数据,最终LCD显示的内容如图23.4.1所示:


图23.4.1 IIC_EEPROM实验程序运行效果图


请使用浏览器的分享功能分享到微信等