NAND Flash批量数据烧录
NAND Flash的读写之前已经写过一篇NAND Flash驱动相关的文章了,处理器用的是TI的8核DSP TMS320C6678,为了后续用它做大批量的数据处理,而现在有苦于暂时没有数据源,所以想先在Flash里存好待处理的数据,后面用起来方便一点。
为了准备尽可能多的数据,128MB容量的NAND Flash,我准备往里面写125MB。
一开始我特别激动,我就准备把这些数据写成常量,然后放到工程里一起编译,最后用仿真器下载进去。这一个数据文件就得几百兆。
后来试了一下发现,这文件太大了,CCS也扛不住,CCS还提示了“JVM heap low detected”,后来我还不死心,想办法提高CCS的heap size,但都没有很好的效果。125M数据确实有点多。
为了解决这个问题,我就想用串口吧。因为现在板子上6678能跟上位机直接通信的除了JTAG,也就只有串口了。但串口的速度又快不上去,虽然能够做,但是做出来还是让人哭笑不得。
最后我用了3M的波特率(USB转422用的FT232,最高只支持3M波特率),然后实际传输的过程中受到NAND Flash写入速度的限制,大概只做到了9kB/s,我花了4个小时把125MB的数据写到了NAND Flash里。
现在回想了一下,自己在写Flash 的时候一直用的是单个的写Page,没有用到Flash里的Cache。如果每次用串口收到一个Block的数据之后再用写Cache的方法写入Flash应该可以更快。
整体方案
整体方案如上图所示,大批量的数据传输,很容易出现发送的数据量和接收的数据量对不上的情况。所以这个整体方案有了一个握手的机制,每次“发送请求-相应”之后只能发送一定量的数据。
具体情况是这样的,上位机发送图像数据,图像是640×512字节/帧的图像数据,而NAND Flash一个Block是2048×64字节。简单计算可知,两帧图像正好可以放在5个Block里。所以我就一次发两帧图的数据。
每次上位机发送请求的时候,可以包含目的Flash的Block首地址,之后发送的数据就写到以这个Block首地址开始的5个Block里。
6678的UART每收到16个字节的数据可以产生中断,因此发送请求的长度为16字节,然后6678的响应为4字节,因为UART的数据寄存器就是4字节位宽的深度为32字节的FIFO。
上位机实现
上位机用python实现是最方便的,我一开始用的win10的串口调试助手,win10的应用商店里就有,用起来也非常方便。但是后来发现它发文件的时候,是把文件中的每个字符单独发送,而大于127的ASCII码都是不能打印的字符,也就不能写在文本文档里,所以这个不能用来以文件发送图像数据。
用opencv-python读取图像中每个像素的数据,然后用pySerial逐个字节发送,实现简单,灵活性高。具体实现的功能如下:
- 共发送“./image/”目录下的400帧图像,每组发两帧图,共发送200组
- 串口波特率3000000Baud,无奇偶校验,一位停止位
- 发送请求包含四个word,依次是“0xAAAAAAAA, blockBaseAddr, blockBaseAddr, 0xAAAAAAAA”
- DSP响应“0x55555555”
- 每发送2048字节,等待0.2秒,确保Flash烧录完成,不会落下EMDA完成中断
- DSP完成后接收发送响应“0xAAAAAAAA”
1 | import serial |
DSP具体实现细节
6678上具体实现的功能大致如上图所示,还有一些初始化、中断、发送响应等没有在上图中画出。UART在接收到16字节数据后可以产生中断或者DMA事件,合理地配置DMA的PaRAM,就可以让DMA自动完成从数据寄存器到ping-pong缓冲区的数据搬运。在每次DMA传完一个缓冲区后,产生DMA完成中断,告知CPU把缓冲区的数据写到Flash里。
UART
UART是一次发送或者接收一个字节,但数据寄存器是4字节的。所以会有这样的现象:在UART发数据的时候,往寄存器写一次数据,TX信号上就会有4个字节被发送。接收也是一样,是4个字节4个字节接收。
UART有一个状态寄存器,从里面可以看到当前接收FIFO内的数据个数,当设置了接收中断使能和接收FIFO半满使能时,数据达到16字节和32字节时各会产生一次中断,但如果不把数据读走,之后就不会再产生接收中断了。
UART的中断和EDMA事件的关系:在要用EDMA时间的时候一定要开UART的中断使能。我的代码一开始是这样写的:我想在DSP等上位机发请求的时候使能UART的接收中断,在收到中断后关闭中断使能(这样后面接收数据都会由DMA去处理)。结果这样DMA也没法收到这个事件了。所以这个中断使能不能关,而是应该关系统中断的使能。
在开DMA通道使能之前一定要先清DMA事件!!开UART接收的系统中断使能之前也需要清挂起的系统中断标识!!
关UART接收中断,打开EDMA通道使能:
1 | // Enable Edma channel |
关EDMA通道使能,打开UART接收中断:
1 | // Disable Edma channel |
EDMA
EDMA在使用的时候要特别注意:当数据源或者数据目的在L2空间的时候,一定要用L2的全局地址,不然不会有任何报错,只会发现DMA传输正常完成,然而数据又不在想要的地方,就非常诡异。
link
DMA的PaRAM有一个link的功能,就是在一个PaRAM中的数据搬运完成之后,这个PaRAM中的设置可以更新为另一个预先设置好的PaRAM的设置。link字段是一个16bit的相对于EDMA基址寄存器的偏移地址,第一个PaRAM的偏移地址是“0x4000”,然后第二个是“0x4020”,……,因为一个PaRAM的大小是32字节。这个link的功能就可以实现PaRAM的自动更新,非常有用!!
现在仔细想想,EMDA每次完成一个PaRAM,这个PaRAM可以变得无效(link=“0xFFFF”),或者保持不变(OPT中的static字段为1),或者用link来变成任意其他设置,可以说是非常全面了。
设置PaRAM实现ping-pong传输。这个在6678的DMA文档里有例子,要实现一个最简单的ping-pong传输,至少要设置3个PaRAM,其中一个用作和对应事件绑定的PaRAM,并把它的link字段链接到另外两个PaRAM的其中一个;剩下两个分别设置成从相同的源数据地址搬运数据到不同的缓冲区(ping-pong缓冲区),然后把里面的link字段设置成对方的偏移地址。
1 | for(i = 1; i<257; i++){ |
在上面的代码中,我设置了257个PaRAM,第0个PaRAM用来和通道绑定,剩下256个PaRAM用来被link,并且是循环link。DMA每次传16个字节,需要传128次才能从UART数据寄存器读到2048个字节。
EDMA中断
PaRAM的OPT中有一个TCC字段,用来指定完成中断号,或者chained EDMA事件。
总共有64个EDMA中断号,本来应该是对应64个EDMA通道的,但实际上它可以任意指定,就比如我42号通道产生一个0号的EDMA中断,但是一定要把对应的中断使能才行,多个不同的通道也可以产生同一个中断,这样方便用同一个中断服务函数去处理不同的事件。
EDMA中断还分为内部完成中断和最终完成中断,看上面的表格就很清楚。每个PaRAM的传输任务实际上是分几次完成的。A-Synchronized模式,传输控制器TC总共要以ACNT的大小,发出BCNT×CCNT次传输请求;AB-Synchronized模式,传输控制器TC总共发CCNT次传输请求。TC的每次传输请求都可以产生中断,TCINTEN可以使能最后一次传输完成中断,ITCINTEN可以使能除了最后一次意外的前几次传输完成中断。
所以我在上面的代码中对第128个和256个PaRAM设置了传输完成中断,CPU根据这个DMA中断开始写Flash。
静态地址
EDMA要访问FIFO有一个要求,就是要地址是256bit对齐的,就是地址线的低5bit都是0。还要求BIDX也是32字节(256bit)的整数倍,这个要求很好理解,因为地址线已经要求256bit对齐了,如果BIDX不是32字节对齐的,那么每次地址线变化BIDX,那地址线就不能满足要求了。
但是它为什么会有这个要求呢?看到OPT里的FWID大概就能理解了,6678支持FIFO宽度最大为256字节。从FIFO读数据,读一次之后FIFO端口就出现下一个数据,所以如果真的遇到一个32字节位宽的FIFO,6678要能够一次性读写32字节数据,可能就是因为这个所以把地址线限制在256bit对齐。
所以这个地址CONST模式,我理解的最关键的就是它能够让6678将FWID长度的数据作为一个整体进行读写。
比如现在的UART数据寄存器,就是一个数据位宽32bit,深度为8的一个FIFO。要从里面读16字节数据有很多种方法。
- 首先这个数据寄存器的地址低5bit是0,满足256bit对齐
- 可以把这个地址设置为CONST,FWID为32bit。ACNT = 0x0010,BCNT=0x0001,一次读16个字节。因为已经设置了CONST地址,所以TC会自动地每次读4字节,然后读4次。
- 也可以不设置成CONST,然后ACNT=0x0004,BCNT=0x0004,这样也能实现同样的效果。
- 把地址设置成CONST后,还有2种设置方法,比如ACNT=0x0002,BCNT=0x0008;ACNT=0x0004,BCNT=0x0004实现的也都是相同的功能,但是没必要这样设置。
最终结果
用四小时从串口发了125M的图像数据,写入到DSP外接的NAND Flash 中。