《DNESP32P4开发指南_V1.0》第二十三章 IIC_EEPROM实验

14 阅读13分钟

第二十三章 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与其他设备通信,芯片的引脚图如下图所示。

image001.png

图23.1.1 24C02引脚图

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

image003.png

图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写时序图如下图所示。

image005.png

图23.1.3 24C02写时序图

上图展示的主机向24C02写操作时序图,主机在IIC总线发送第1个字节的数据为24C02的写操作地址0xA0(设备地址0x50 << 1 | 0),用于寻找总线上找到24C02,在获得24C02的应答信号之后,继续发送第2个字节数据,该字节数据是24C02的内存地址,再等到24C02的应答信号,主机继续发送第3字节数据,这里的数据即是写入在第2字节内存地址的数据。主机完成写操作后,可以发出停止信号,终止数据传输。
上面的写操作只能单字节写入到24C02,效率比较低,所以24C02有页写入时序,大大提高了写入效率,下面看一下24C02页写时序图,如下图所示。

image007.png

图23.1.4 24C02页写时序

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

image009.png

图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器件相关原理图,如下图所示。

image011.png

图23.2.3.1 24C02硬件原理图

23.3 程序设计

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

23.3.1 程序流程图

image014.png

图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++;
    }
}

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

2. 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)

3. 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所示:

image015.png

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