数据断电存储在嵌入式开发中,可以说相当常见,在很多产品的开发中我们或多或少对数据掉电存储有现实性的需求,例如存储设备开机的logo画面、记忆机器的校准数据、保存上次关机前的状态等,在这些形形色色的需求背后,EEPROM和FLASH几乎是首选的器件,但如果预存数据过多且有固定格式保存例如.wav,.bmp等格式,那么这时候则会优先考虑使用SD卡,当然它们各有各的优缺点,那么在这一章节里,笔者就和大家一起去动手实践EEPROM、FLASH、SD卡读写时序逻辑,掌握这些也会对大家未来的工作带来很大的益处。
谈到断电存储,EEPROM可以说是专门为此需求而生的器件,经济实惠、读写灵活、电路简洁、擦写方便,使得它几乎适用于所有小数据量的断电存储。
举个现实中的例子,设备想下次开机后直接恢复上次关机前的数据和状态,这也是实际项目中经常遇到的问题,有一些典型的解决方案,如硬件上存在断电后的短暂供电电路,在即将完全掉电的时间内把各种需要保存的数据统一写到FLASH的一页或多页中,软件开机初始化时再读FLASH并重新赋值给相关变量即可,但这样增加了硬件设计的复杂度,同时还需要考虑设计余量使得SOC芯片在彻底掉电之前有足够的时间写入FLASH。
EEPROM的出现很好地解决这个问题,因为相比而言不论是片内FLASH还是片外FLASH,写入数据前都需要整页擦除,同时写入最小单位是整页内的数据,EEPROM写入小数据量的操作更为简单易行,另外十万到百万的擦写次数也完全支持较频繁地写入操作,所以在硬件电路的设计上一般不易经常改变的数据写入FLASH断电保存最佳,例如开机logo画面的二进制文件、仪器仪表的校准数据表等,而需要经常改变的少量数据则选择直接写入EERPOM即可。
如图1所示是豌豆开发板Artix7上24LC04电路,24LC04是一颗经典的EEPROM芯片,实际项目当中也经常用到,并拥有2片独立的256字节即512字节存储容量,完全可以满足小数据量的断电存储需求,在这个例程中,我们去实现如下一个功能:通过串口调试助手发送报文告诉FPGA是向EEPROM里写或者读数据,具体从哪个地址开始写或者读,如果是写操作报文应包含写数据的数据地址、数据数量、数据内容,如果是读操作报文中只用包含读数据的数据地址和数据数量即可,报文做的非常简单甚至没有CRC校验,当然大家在实战项目工程中完全可以根据需求把报文做得更加完善和漂亮,但核心要点和代码思路是一样的。
图1 豌豆开发板Artix7上24LC04电路
因为IIC总线协议在“LM75温度传感器”的例程中已经详细介绍过,所以在这里不再重复,而把主要篇幅放在阅读24LC04芯片手册,并提取程序设计上的关键信息和各个模块之间划分和信号交互上,这里更有一般性地设计规则,大家在学习实践完本例程后,对报文解析、数据缓存、复杂状态机设计等会有更深一步的理解。
如图2所示是对24LC04在IIC总线上通信的地址定义,大家可以清楚地看到对于7位的地址数据,高4位固定是4'b1010,后两位手册上标明不用关心,程序里我们索性都置为0,最后一位B0,在手册的开始说明了24LC04由2片独立的256字节存储空间组成的,所以对于B0置为0或者1代表SOC芯片要对24LC04哪一片存储空间进行操作,同样的对于SOC芯片在IIC总线上寻址所需发送的8位地址当中最后1位仍然标记了读写操作,其中0代表写操作,1代表读操作。
图2 对24LC04在IIC总线上通信的地址定义
如图3所示是对24LC04写数据的时序逻辑,对24LC04写数据,有字节写入和页面写入两种类型,前者只写入给定地址后的一个字节,而后者可以写入多个字节,但前期SOC芯片在IIC总线上发送的控制字节和写入地址都是一样的,唯一区别在于单字节写入,SOC芯片在单字节写入时,向24LC04写入一字节收到ACK后直接产生STOP停止信号,从而终止本次IIC通信,而多字节写入则写入多字节收到多个ACK,再产生STOP停止信号。
图3 对24LC04写数据的时序逻辑
如图4所示是对24LC04读数据的时序逻辑,对24LC04读数据,同样地有随机读取和连续读取两种方式,和写操作类似前期SOC芯片在IIC总线上发送的控制字节和读取地址都是一样的,唯一区别在于多字节读取,SOC芯片在单字节读取时,从24LC04读取一字节发送NO ACK后直接产生STOP停止信号,从而终止本次IIC通信,而多字节读取则读取多字节发送多个ACK,读完最后一个字节再发送NO ACK产生STOP停止信号。
图4 对24LC04读数据的时序逻辑
写EEPROM流程:
1. Master发起START
2. Master发送IIC ADDR(7bit)和写操作0(1bit),等待ACK
3. Slave发送ACK
4. Master发送WR ADDR(8bit),等待ACK
5. Slave发送ACK
6. Master发送DATA(8bit),即向EEPROM中写入的数据,等待ACK
7. Slave发送ACK
8. 第6步和第7步可以重复多次,即顺序写入多个数据
9. Master发起STOP
读EEPROM流程:
1. Master发送IIC ADDR(7bit)和写操作0(1bit),等待ACK
2. Slave发送ACK
3. Master发送RD ADDR(8bit),等待ACK
4. Slave发送ACK
5. Master发起START
6. Master发送IIC ADDR(7bit)和读1位,等待ACK
7. Slave发送ACK
8. Slave发送DATA(8bit),即从EEPROM中读取的数据
9. Master发送ACK
10. 第8步和第9步可以重复多次,即顺序读取多个数据
11. Master发送NACK
12. Master发起STOP
基于此我们可以把EEPROM的读写流程总结概括如上,有几个地方在代码设计时需要着重注意下,笔者在刚接触EEPROM读写设计的时候,也来回折腾了几天主要是因为没有搞清楚整个读写流程上的细节,盲目性地反复调试最后陷入了死循环,所以在这里强调几个容易忽视的地方望大家注意:
1.不论对于写操作还是读操作,都需要FPGA端在IIC总线上先发起START信号,接着发送IIC总线上从机EEPROM的写物理地址,即前面分析的8'hA0或者8'hA2,等待EERPOM回复1位的ACK信号0,然后发送对EERPOM的读写存储地址,芯片手册中介绍了24LC04是由2片独立的256字节存储空间组成,所以该字节0-255的范围代表了FPGA向存储空间的哪个地址进行读写操作,再去等待EERPOM回复1位的ACK信号0,大家在程序设计的时候,需要搞清楚这两个地址的实际意义;
2.读操作同样也需要经过于写操作相同的步骤,即发送IIC总线上从机EEPROM的写物理地址和EERPOM的读存储地址,当都得到EERPOM回复1位的ACK信号0后,再产生一个START信号,然后去发送EERPOM的读物理地址,即前面分析的8'hA1或者8'hA3,等待EERPOM回复1位的ACK信号0,才可以进行读数据操作,即写操作需要提前发送EEPROM的写物理地址、EERPOM的写存储地址,而读操作则需要提前发送EEPROM的写物理地址、EERPOM的读存储地址、EEPROM的读物理地址,笔者在工作中刚开始调试EEPROM时候卡壳,就是没有读透芯片手册,在读操作的时候发送地址顺序或内容操作有误,这也是非常容易犯的一个错误;
3.对于写操作,提前发送EEPROM的写物理地址、EERPOM的写存储地址,在均得到了EEPROM的ACK信号0回复后,FPGA端即可以从给定的写存储地址顺序向后写入数据,这时每写一个字节数据,都需要等待EEPROM的ACK信号0作为回复,等到写完了预定的数量后,FPGA端在IIC总线上产生一个STOP信号结束本次通信;
4.对于读操作,提前发送EEPROM的写物理地址、EERPOM的读存储地址、EERPOM的读物理地址,在均得到了EEPROM的ACK信号0回复后,FPGA端即可以从给定的读存储地址顺序向后读取数据,这时每读一个字节数据,FPGA端都需要向EEPROM发送ACK信号0作为回复,等到读完了预定的数量后,FPGA端都需要向EEPROM发送NACK信号1作为回复,再在IIC总线上产生一个STOP信号结束本次通信;
5.因为24LC04由2片独立的256字节存储空间组成的,所以在项目应用中,如果存在超过256字节的数据需要断电存储,大家可以通过切换读写物理地址来选择对哪一片存储空间进行操作,即写物理地址8'hA0、读物理地址8'hA1代表操作第一片存储空间,写物理地址8'hA2、读物理地址8'hA3代表操作第二片存储空间。
如表1所示是command_detect模块信号列表,这是一个报文解析模块也具有很好地实战参考价值,对于EEPROM的数据读写,例程实现的效果如下:通过RS232串口线从上位机端收发数据,串口发送固定格式的报文至FPGA,报文的具体格式是:8'hAA(固定报头)、8'h00(写操作)/8'h01(读操作)、读写EEPROM的存储地址、读写数据的数量(以字节为单位)、需要写入数据的内容(仅在写EEPROM时有效)。如报文:8'hAA 8'h00 8'h00 8'h05 8'h11 8'h22 8'h33 8'h44 8'h55,代表了向EEPROM的00地址内写入5个字节的数据:8'h11、8'h22、8'h33、8'h44、8'h55;报文:8'hAA 8'h01 8'h02 8'h03,则代表了从EEPROM的02地址内读出3个字节的数据,在这个模块中去实现对报文的解析处理和对数据的缓存操作。
如表2所示是i2c_eeprom模块信号列表,在这个模块里,我们则需要把上游command_detect模块中解析到的报文中关键信息例如:读写模式信号wrl_rdh_mode、读写数量信号wrl_rdh_num、读写地址信号wrl_rdh_addr和读写使能信号eeprom_wrrd_en例化关联到一起,该模块设计一个读写EEPROM的状态机即可完成,状态机的设计即还原了前面所介绍的读写EEPROM流程,大家结合具体代码就可以看明白,代码的整体设计上简单清晰。
信号列表 |
||
信号名 |
I/O |
位宽 |
clk |
I |
1 |
rst_n |
I |
1 |
wrfifo_rdy |
I |
1 |
din |
I |
8 |
din_vld |
I |
1 |
dout |
O |
8 |
dout_vld |
O |
1 |
eeprom_wrrd_en |
O |
1 |
wrl_rdh_mode |
O |
1 |
wrl_rdh_num |
O |
8 |
wrl_rdh_addr |
O |
8 |
表1 command_detect模块信号列表
信号列表 |
||
信号名 |
I/O |
位宽 |
clk |
I |
1 |
rst_n |
I |
1 |
rdfifo_rdy |
I |
1 |
eeprom_wrrd_en |
I |
1 |
wrl_rdh_addr |
I |
8 |
wrl_rdh_mode |
I |
1 |
wrl_rdh_num |
I |
8 |
wr_din |
I |
8 |
wr_din_vld |
I |
1 |
wr_byte_done |
O |
1 |
i2c_sda |
I/O |
1 |
i2c_scl |
O |
1 |
rd_dout |
O |
8 |
rd_dout_vld |
O |
1 |
表2 i2c_eeprom模块信号列表
如图5所示,是报文解析模块的代码设计,我们这里用计数器cnt0去计数报文前3个字节,包括了读写操作标识符8'h00或者8'h01,读写EEPROM的存储地址,读写EEPROM的字节数量,并分别赋值给相关信号,用计数器cnt1去计数写操作的时候,报文中的写数据,并把这些数据事先写入FIFO中,因为下游i2c_eeprom模块写入数据到EERPOM的时间显然和串口接收数据的时间是不同步的,同时这个模块里也用到两个flag_add用来标记cnt的计数条件,这些在之前的FPGA基础知识专栏里也系统说明过,忘记的同学可以复习下,在收到报头8'haa后, flag_add0置1,在cnt0计数3次后,flag_add0置0;在cnt0计数3次后且为写操作时flag_add1置1, 在cnt0计数wrl_rdh_num后,flag_add1置0。
图5 报文解析模块的代码设计
如图6所示,是IIC读写EEPROM模块的详细代码设计,这里只是把前面芯片手册所分析的EEPROM读写流程还原成verilog代码,读写状态机的设计遵守芯片手册的时序逻辑可能有些烦琐但搞清思路并不复杂,这里需要特殊注意两点:1.对于读操作,本模块内部含有FIFO,因为读EEPROM数据时与下游模块即串口发送模块是不同步的,所以会根据上游报文解析模块传来的wrl_rdh_num读取指定数量的数据后预先写入FIFO中,接着通过 rd_dout和rd_dout_vld传给下游模块;2.对于写操作,因为写EEPROM数据时与上游模块即报文解析模块也是不同步的,所以本模块需要产生rdfifo_rdy信号作为输入传给上游模块,触发上游模块把FIFO中缓存的需要写入数据通过wr_din和wr_din_vld传给本模块,所以在这里我们设计了wr_byte_done信号作为上游模块FIFO的rdfifo_rdy信号。
图6 IIC读写EEPROM模块的代码设计
如图7所示是EEPROM的串口读写顶层文件的例化,这里我们需要注意把command_detect模块的wrfifo_rdy信号和i2c_eeprom模块的rdfifo_rdy信号例化好,对于command_detect模块的wrfifo_rdy信号,需要和i2c_eeprom模块的wr_byte_done信号例化到一起,对于i2c_eeprom模块的rdfifo_rdy信号,需要和uart_transfer模块的txd_data_rdy信号例化到一起。
图7 EEPROM读写顶层文件的例化
如图8和图9所示是串口助手发送写EEPROM和读EEPROM报文指令的测试截图,如图8所示,串口助手发送报文8'hAA 8'h00 8'h00 8'h05 8'h11 8'h22 8'h33 8'h44 8'h55,代表向EEPROM的00地址写入5个字节的数据:8'h11、8'h22、8'h33、8'h44、8'h55;如图9所示,串口助手发送报文8'hAA 8'h01 8'h00 8'h05,则代表了从EEPROM的00地址内读出5个字节的数据,串口助手上即打印出从EERPOM里读到的5个字节的数据,即8'h11、8'h22、8'h33、8'h44、8'h55。笔者在整个工程的关键模块下添加了ILA,方便大家在线调试可以观察到重要信号的波形,并配合串口助手进行联调。
图8 串口助手发送写EEPROM报文指令
图9 串口助手发送读EEPROM报文指令
源工程代码下载链接(含datasheet):
链接:https://pan.baidu.com/s/1PTaBKyOP51vjzpQFyc_Eog
提取码:988m