超级版主
 
- 积分
- 5280
- 金钱
- 5280
- 注册时间
- 2019-5-8
- 在线时间
- 1347 小时
|
|
第二十三章 IIC_EEPROM实验
1)实验平台:正点原子DNESP32P4开发板
2)章节摘自【正点原子】ESP32-P4开发指南— V1.0
3)购买链接:https://detail.tmall.com/item.htm?id=873309579825
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/esp32/ATK-DNESP32P4.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子DNESP32S3开发板技术交流群:132780729
本章将学习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芯片最大地址做了相关定义。
- <font size="3">#define AT_ADDR 0x50 /* 24c02设备地址 */</font>
- <font size="3">#define AT24C02 255</font>
复制代码 接下来,解析一下at24c02.c的程序,首先来看一下AT24C02器件的初始化函数at24c02_init,代码如下:
- <font size="3">/**</font>
- <font size="3"> * @brief 初始化AT24C02</font>
- <font size="3"> * [url=home.php?mod=space&uid=271674]@param[/url] 无</font>
- <font size="3"> * @retval ESP_OK:初始化成功</font>
- <font size="3"> */</font>
- <font size="3">esp_err_t at24c02_init(void)</font>
- <font size="3">{</font>
- <font size="3"> /* 未调用myiic_init初始化IIC */</font>
- <font size="3"> if (bus_handle == NULL)</font>
- <font size="3"> {</font>
- <font size="3"> ESP_ERROR_CHECK(myiic_init());</font>
- <font size="3"> }</font>
- <font size="3"> i2c_device_config_t eeprom_i2c_dev_conf = {</font>
- <font size="3"> .dev_addr_length = I2C_ADDR_BIT_LEN_7, /* 从机地址长度 */</font>
- <font size="3"> .scl_speed_hz = IIC_SPEED_CLK, /* 传输速率 */</font>
- <font size="3"> .device_address = AT_ADDR, /* 从机7位的地址 */</font>
- <font size="3"> };</font>
- <font size="3"> /* I2C总线上添加AT24C02设备 */</font>
- <font size="3">ESP_ERROR_CHECK(i2c_master_bus_add_device( bus_handle, &eeprom_i2c_dev_conf, </font>
- <font size="3">&eeprom_handle));</font>
- <font size="3"> return ESP_OK;</font>
- <font size="3">}</font>
复制代码 在AT24C02初始化函数中,首先对eeprom_i2c_dev_conf变量的成员进行赋值,设置AT24C02的地址长度、设备地址以及传输速率,然后调用i2c_master_bus_add_device函数对AT24C02设备进行初始化。
下面先看一下at24c02_write_one_byte函数,实现在AT24C02芯片指定地址写入一个数据,代码如下:
- <font size="3">/**</font>
- <font size="3"> * @brief 在AT24C02指定地址写入一个数据</font>
- <font size="3"> * @param addr: 写入数据的目的地址</font>
- <font size="3"> * @param data: 要写入的数据</font>
- <font size="3"> * @retval 无</font>
- <font size="3"> */</font>
- <font size="3">void at24c02_write_one_byte(uint8_t addr, uint8_t data)</font>
- <font size="3">{</font>
- <font size="3">uint8_t send_buf[2] = {0};</font>
- <font size="3"> send_buf[0] = addr % 256;</font>
- <font size="3"> send_buf[1] = data;</font>
- <font size="3"> ESP_ERROR_CHECK(i2c_master_transmit(eeprom_handle, send_buf, 2, -1));</font>
- <font size="3"> /* 由于AT24C02写入过慢,需延迟10ms左右时间 */</font>
- <font size="3"> esp_rom_delay_us(10000);</font>
- <font size="3">}</font>
复制代码 该函数的实现,主要调用i2c_master_transmit函数。在这里需要进行数据整合,把写入数据的目的地址和要写入的数据重新存放到一个buf。在这里需要注意存放顺序,写入数据的目的地址要在要写入数据的前面,这样子通过i2c_master_transmit函数发送出去的数据才符合AT24C02的写数据操作。这里需要注意:EEPROM写入比较慢,所以需要延时10ms,等待eeprom写入完毕。
继续看一下at24c02_read_one_byte,实现从AT24C02内存地址读取数据,代码如下:
- <font size="3">/**</font>
- <font size="3"> * @brief 在AT24C02指定地址读出一个数据</font>
- <font size="3"> * @param addr: 开始读数的地址</font>
- <font size="3"> * @retval 读到的数据</font>
- <font size="3"> */</font>
- <font size="3">uint8_t at24c02_read_one_byte(uint8_t addr)</font>
- <font size="3">{</font>
- <font size="3"> uint8_t data = 0;</font>
- <font size="3">ESP_ERROR_CHECK(i2c_master_transmit_receive(eeprom_handle, &addr, 1, &data,</font>
- <font size="3"> 1, -1));</font>
- <font size="3"> return data;</font>
- <font size="3">}</font>
复制代码 该函数的实现主要调用i2c_master_transmit_receive函数。从AT24C02的addr内存地址处读出data数据,作为函数返回值返回。
有了基本的读写函数接口,就可以写一个比较简单的检测函数at24c02_check,用来测试IIC总线上是否存在24C02或者说器件是否正常,代码如下所示。
- <font size="3">/**</font>
- <font size="3"> * @brief 检查AT24C02是否正常</font>
- <font size="3"> * [url=home.php?mod=space&uid=60778]@note[/url] 检测原理: 在器件的末地址写如0X55, 然后再读取, 如果读取值为0X55</font>
- <font size="3"> * 则表示检测正常. 否则,则表示检测失败.</font>
- <font size="3"> * @param 无</font>
- <font size="3"> * @retval 检测结果</font>
- <font size="3"> * 0: 检测成功</font>
- <font size="3"> * 1: 检测失败</font>
- <font size="3"> */</font>
- <font size="3">uint8_t at24c02_check(void)</font>
- <font size="3">{</font>
- <font size="3"> uint8_t temp;</font>
- <font size="3"> uint16_t addr = AT24C02;</font>
- <font size="3"> temp = at24c02_read_one_byte(addr); /* 避免每次开机都写AT24CXX */</font>
- <font size="3"> if (temp == 0X55) /* 读取数据正常 */</font>
- <font size="3"> {</font>
- <font size="3"> return 0;</font>
- <font size="3"> }</font>
- <font size="3"> else /* 排除第一次初始化的情况 */</font>
- <font size="3"> {</font>
- <font size="3"> at24c02_write_one_byte(addr, 0X55); /* 先写入数据 */</font>
- <font size="3"> temp = at24c02_read_one_byte(255); /* 再读取数据 */</font>
- <font size="3"> if (temp == 0X55)</font>
- <font size="3"> {</font>
- <font size="3"> return 0;</font>
- <font size="3"> }</font>
- <font size="3"> }</font>
- <font size="3"> return 1;</font>
- <font size="3">}</font>
复制代码 在这里,就是利用EEPROM芯片掉电不丢失的特性,在第一次写入了某个值之后,再去读一下看是否写入成功,这种方式就可以去检测芯片是否可以正常工作。
有时候操作单位往往不是单个字节,所以这里我们也提供了多字节写和多字节读的函数接口,代码如下所示。
- <font size="3">/**</font>
- <font size="3"> * @brief 在AT24C02里面的指定地址开始读出指定个数的数据</font>
- <font size="3"> * @param addr : 开始读出的地址 对24c02为0~255</font>
- <font size="3"> * @param pbuf : 数据数组首地址</font>
- <font size="3"> * @param datalen : 要读出数据的个数</font>
- <font size="3"> * @retval 无</font>
- <font size="3"> */</font>
- <font size="3">void at24c02_read(uint8_t addr, uint8_t *pbuf, uint8_t datalen)</font>
- <font size="3">{</font>
- <font size="3"> while (datalen--)</font>
- <font size="3"> {</font>
- <font size="3"> *pbuf++ = at24c02_read_one_byte(addr++);</font>
- <font size="3"> }</font>
- <font size="3">}</font>
- <font size="3">/**</font>
- <font size="3"> * @brief 在AT24C02里面的指定地址开始写入指定个数的数据</font>
- <font size="3"> * @param addr : 开始写入的地址 对24c02为0~255</font>
- <font size="3"> * @param pbuf : 数据数组首地址</font>
- <font size="3"> * @param datalen : 要写入数据的个数</font>
- <font size="3"> * @retval 无</font>
- <font size="3"> */</font>
- <font size="3">void at24c02_write(uint8_t addr, uint8_t *pbuf, uint8_t datalen)</font>
- <font size="3">{</font>
- <font size="3"> while (datalen--)</font>
- <font size="3"> {</font>
- <font size="3"> at24c02_write_one_byte(addr, *pbuf);</font>
- <font size="3"> addr++;</font>
- <font size="3"> pbuf++;</font>
- <font size="3"> }</font>
- <font size="3">}</font>
复制代码 以上两个函数都是基于单个字节读和单个字节写函数实现的,这里就不多讲了。
3. CMakeLists.txt文件
本例程的功能实现主要依靠IIC驱动和AT24C02驱动。要在main函数中,成功调用AT24C02文件中的内容,就得需要修改BSP文件夹下的CMakeLists.txt文件,修改如下:
- <font size="3">set(src_dirs</font>
- <font size="3"> LED</font>
- <font size="3"> LCD</font>
- <font size="3"> MYIIC</font>
- <font size="3"> XL9555</font>
- <font size="3"> AT24C02)</font>
- <font size="3">set(include_dirs</font>
- <font size="3"> LED</font>
- <font size="3"> LCD</font>
- <font size="3"> MYIIC</font>
- <font size="3"> XL9555</font>
- <font size="3"> AT24C02)</font>
- <font size="3">set(requires</font>
- <font size="3"> driver</font>
- <font size="3"> esp_lcd</font>
- <font size="3"> esp_common)</font>
- <font size="3">idf_component_register( SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})</font>
- <font size="3">component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)</font>
复制代码 4. main.c驱动代码
在main.c里面编写如下代码。
- <font size="3">void app_main(void)</font>
- <font size="3">{</font>
- <font size="3"> esp_err_t ret;</font>
- <font size="3"> uint8_t key = 0;</font>
- <font size="3"> uint16_t i = 0;</font>
- <font size="3"> uint8_t datatemp[TEXT_SIZE];</font>
- <font size="3"> ret = nvs_flash_init(); /* 初始化NVS */</font>
- <font size="3"> if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)</font>
- <font size="3"> {</font>
- <font size="3"> ESP_ERROR_CHECK(nvs_flash_erase());</font>
- <font size="3"> ESP_ERROR_CHECK(nvs_flash_init());</font>
- <font size="3"> }</font>
- <font size="3"> led_init(); /* LED初始化 */</font>
- <font size="3"> lcd_init(); /* LCD屏初始化 */</font>
- <font size="3"> xl9555_init(); /* 初始化按键 */</font>
- <font size="3"> at24c02_init(); /* 初始化24CXX */</font>
- <font size="3"> lcd_show_string(30, 50, 200, 16, 16, "ESP32-P4", RED);</font>
- <font size="3"> lcd_show_string(30, 70, 200, 16, 16, "EEPROM TEST", RED);</font>
- <font size="3"> lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);</font>
- <font size="3"> lcd_show_string(30, 110, 200, 16, 16, "KEY1:Write KEY0:Read", RED);</font>
- <font size="3"> while (at24c02_check()) /* 检测不到24c02 */</font>
- <font size="3"> {</font>
- <font size="3"> lcd_show_string(30, 130, 200, 16, 16, "24C02 Check Failed!", RED);</font>
- <font size="3"> vTaskDelay(pdMS_TO_TICKS(500));</font>
- <font size="3"> lcd_show_string(30, 130, 200, 16, 16, "Please Check! ", RED);</font>
- <font size="3"> vTaskDelay(pdMS_TO_TICKS(500));</font>
- <font size="3"> LED0_TOGGLE(); </font>
- <font size="3"> }</font>
- <font size="3"> lcd_show_string(30, 130, 200, 16, 16, "24C02 Ready!", RED);</font>
- <font size="3"> while (1)</font>
- <font size="3"> {</font>
- <font size="3"> key = xl9555_key_scan(0); /* 按键扫描 */</font>
- <font size="3"> if (key == KEY1_PRES) /* KEY1按下,写入24C02 */</font>
- <font size="3"> {</font>
- <font size="3"> lcd_fill(0, 150, 239, 319, WHITE); /* 清除显示 */</font>
- <font size="3"> lcd_show_string(30, 150, 200, 16, 16,"Start Write 24C02....", BLUE);</font>
- <font size="3"> at24c02_write(0, (uint8_t *)g_text_buf, TEXT_SIZE); /* 写数据 */</font>
- <font size="3"> lcd_show_string(30, 150, 200, 16, 16,"24C02 Write Finished!", BLUE);</font>
- <font size="3"> }</font>
- <font size="3"> if (key == KEY0_PRES) /* KEY0按下,读取字符串并显示 */</font>
- <font size="3"> {</font>
- <font size="3"> lcd_show_string(30, 150, 200, 16, 16,"Start Read 24C02.... ", BLUE);</font>
- <font size="3"> at24c02_read(0, datatemp, TEXT_SIZE); /* 读取数据 */ </font>
- <font size="3"> lcd_show_string(30, 150, 200, 16, 16,"The Data Readed Is: ", BLUE);</font>
- <font size="3"> lcd_show_string(30, 170, 200, 16, 16, (char *)datatemp, BLUE); </font>
- <font size="3"> }</font>
- <font size="3"> i++;</font>
- <font size="3"> if (i == 20)</font>
- <font size="3"> {</font>
- <font size="3"> LED0_TOGGLE();</font>
- <font size="3"> i = 0;</font>
- <font size="3"> }</font>
- <font size="3"> vTaskDelay(pdMS_TO_TICKS(10));</font>
- <font size="3"> }</font>
- <font size="3">}</font>
复制代码 在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实验程序运行效果图 |
|