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”
import serial
from serial.serialutil import EIGHTBITS, PARITY_NONE, STOPBITS_ONE
import cv2 as cv
import time

if __name__ == '__main__':
    # serial setup
    ser = serial.Serial()
    ser.port = 'COM5'
    ser.baudrate = 3000000
    ser.bytesize = EIGHTBITS
    ser.parity = PARITY_NONE
    ser.stopbits = STOPBITS_ONE
    ser.timeout = None
    ser.xonxoff = False
    ser.rtscts = False
    ser.write_timeout = None
    ser.dsrdtr = False
    ser.open()

    h = 512
    w = 640
    for p in range(200):
        # send request
        btArr = bytearray(16)
        blockAddrLo = (p*5)%256
        blockAddrHi = int((p*5)/256)
        btArr[0:4] = [0xAA, 0xAA, 0xAA, 0xAA]
        btArr[4:8] = [blockAddrLo, blockAddrHi, 0, 0]
        btArr[8:12] = [blockAddrLo, blockAddrHi, 0, 0]
        btArr[12:16] = [0xAA, 0xAA, 0xAA, 0xAA]
        ser.write(btArr)

        # waiting for response
        while True:
            rdData = ser.read(4)
            if rdData == bytes([0x55, 0x55, 0x55, 0x55]):
                break
        
        #send data
        sendTimes = 0
        for q in range(2):
            img = cv.imread('./images/'+str(2*p+q)+'.bmp')
            btArr = bytearray()
            for i in range(h):
                for j in range(w):
                    btArr.append(img.item(i,j,0))
                    # send group, there are 16 bytes in a group
                    if len(btArr) == 16:
                        ser.write(btArr)
                        sendTimes = sendTimes + 1

                        if sendTimes%128 == 0:
                            sendTimes = 0
                            # waiting for flash program finish, don't send too fast
                            time.sleep(0.2)
                        btArr = bytearray()
            print('Frame '+str(2*p+q)+' Finish')

        # waiting for the finishing response
        while True:
            rdData = ser.read(4)
            if rdData == bytes([0xAA, 0xAA, 0xAA, 0xAA]):
                print('Finish Group '+str(p+1))
                break

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通道使能:

// Enable Edma channel
CSL_edma3ClearDMAChannelEvent(hEdma3, CSL_EDMA3_REGION_GLOBAL, UART_RX_CHANNEL);
CSL_edma3DMAChannelEnable(hEdma3, CSL_EDMA3_REGION_GLOBAL, UART_RX_CHANNEL);
// Disable receive interrupt
CSL_cpintcDisableSysInterrupt(hCpintc0, UART_RXINT_NUM);

关EDMA通道使能,打开UART接收中断:

// Disable Edma channel
CSL_edma3DMAChannelDisable(hEdma3, CSL_EDMA3_REGION_GLOBAL, UART_RX_CHANNEL);

// Enable receive interrupt
CSL_cpintcClearSysInterrupt(hCpintc0, UART_RXINT_NUM);
CSL_cpintcEnableSysInterrupt(hCpintc0, UART_RXINT_NUM);

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字段设置成对方的偏移地址。

for(i = 1; i<257; i++){
		if(i == 128 || i == 256){
			paramSetup.option = CSL_FMK(TPCC_PARAM_OPT_PRIV, (Uint32)1) | 		//PRIV read only 1
								CSL_FMK(TPCC_PARAM_OPT_PRIVID, 0) | 	//PRIVID read only 0
								CSL_FMK(TPCC_PARAM_OPT_ITCCHEN, 0) | 	//Intermediate transfer completion chaining enable
								CSL_FMK(TPCC_PARAM_OPT_TCCHEN, 0) | 	//Transfer complete chaining enable
								CSL_FMK(TPCC_PARAM_OPT_ITCINTEN, 0) | 	//Intermediate transfer completion interrupt enable
								CSL_FMK(TPCC_PARAM_OPT_TCINTEN, 1) | 	//Transfer complete interrupt enable
								CSL_FMK(TPCC_PARAM_OPT_TCC, UART_RX_CHANNEL) | 		//Transfer complete code
								CSL_FMK(TPCC_PARAM_OPT_TCCMOD, 0) | 	//Transfer complete code mode; 0 - Normal, 1 - Early completion
								CSL_FMKT(TPCC_PARAM_OPT_FWID, 32) | 	//FIFO width in CONST mode; 8/16/32/64/128/256
								CSL_FMK(TPCC_PARAM_OPT_STATIC, 0) | 	//Static set; 0 - Set is not static, 1 - Set is static
								CSL_FMK(TPCC_PARAM_OPT_SYNCDIM, 1) | 	//Transfer synchronization dimension; 0 - A mode; 1 - AB mode
								CSL_FMK(TPCC_PARAM_OPT_DAM, 0) | 		//Destination address mode 0-INCR; 1-CONST
								CSL_FMK(TPCC_PARAM_OPT_SAM, 1);			//Source address mode 0-INCR; 1-CONST
		}
		else{
			paramSetup.option = CSL_FMK(TPCC_PARAM_OPT_PRIV, (Uint32)1) | 		//PRIV read only 1
								CSL_FMK(TPCC_PARAM_OPT_PRIVID, 0) | 	//PRIVID read only 0
								CSL_FMK(TPCC_PARAM_OPT_ITCCHEN, 0) | 	//Intermediate transfer completion chaining enable
								CSL_FMK(TPCC_PARAM_OPT_TCCHEN, 0) | 	//Transfer complete chaining enable
								CSL_FMK(TPCC_PARAM_OPT_ITCINTEN, 0) | 	//Intermediate transfer completion interrupt enable
								CSL_FMK(TPCC_PARAM_OPT_TCINTEN, 0) | 	//Transfer complete interrupt enable
								CSL_FMK(TPCC_PARAM_OPT_TCC, 0) | 		//Transfer complete code
								CSL_FMK(TPCC_PARAM_OPT_TCCMOD, 0) | 	//Transfer complete code mode; 0 - Normal, 1 - Early completion
								CSL_FMKT(TPCC_PARAM_OPT_FWID, 32) | 	//FIFO width in CONST mode; 8/16/32/64/128/256
								CSL_FMK(TPCC_PARAM_OPT_STATIC, 0) | 	//Static set; 0 - Set is not static, 1 - Set is static
								CSL_FMK(TPCC_PARAM_OPT_SYNCDIM, 1) | 	//Transfer synchronization dimension; 0 - A mode; 1 - AB mode
								CSL_FMK(TPCC_PARAM_OPT_DAM, 0) | 		//Destination address mode 0-INCR; 1-CONST
								CSL_FMK(TPCC_PARAM_OPT_SAM, 1);			//Source address mode 0-INCR; 1-CONST
		}
		paramSetup.srcAddr = (Uint32)&(hUart->DATA);
		paramSetup.aCntbCnt = 0x00010010;//aCnt = 16, bCnt = 1
		paramSetup.dstAddr = (Uint32)(buffer+((i-1)<<4));
		paramSetup.srcDstBidx = 0x00100000;
		if(i == 256){
			paramSetup.linkBcntrld = 0x00044020;
		}
		else{
			paramSetup.linkBcntrld = 0x00044000+((i+1)<<5);
		}
		paramSetup.srcDstCidx = 0x00000000;
		paramSetup.cCnt = 0x00000001;
		if(i == 1){
			hParam = CSL_edma3GetParamHandle(hEdma3, 0, &status);
			CSL_edma3ParamSetup(hParam, &paramSetup);
		}
		hParam = CSL_edma3GetParamHandle(hEdma3, i, &status);
		CSL_edma3ParamSetup(hParam, &paramSetup);
	}

  在上面的代码中,我设置了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 中。
在这里插入图片描述

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
NAND Flash数据丢失可能由多种原因引起,包括硬件故障、软件错误、电力问题等。下面是一些常见的解决方法和预防措施: 1. 数据备份:在使用NAND Flash存储数据时,始终要保持定期备份重要数据的习惯。这样即使发生数据丢失,可以通过备份文件来恢复数据。 2. 电力稳定性:确保NAND Flash设备在正常使用时有稳定的电源供应。使用UPS(不间断电源)可以防止由于电力故障引起的数据丢失。 3. 错误检测与纠正:在使用NAND Flash时,可以启用错误检测与纠正(ECC)功能。ECC功能可以检测和纠正存储器中的位错误,提高数据完整性。 4. 硬件检查:如果发生数据丢失,首先要检查NAND Flash硬件是否存在问题。可以尝试更换NAND Flash芯片或者连接线,确保硬件连接稳定。 5. 软件修复:如果数据丢失是由于软件错误引起的,可以尝试修复软件问题。这可能包括修复文件系统错误、修复软件程序中的漏洞等。 6. 数据恢复专业服务:如果以上方法无法解决数据丢失问题,可以寻求专业的数据恢复服务。专业的数据恢复公司可能有更高级别的技术和设备,可以尝试从损坏的NAND Flash中恢复数据。 要避免NAND Flash数据丢失,除了上述措施外,还应定期检查和维护NAND Flash设备,避免物理损坏和软件问题的发生。此外,定期更新固件和备份数据也是非常重要的预防措施。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小裘HUST

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值