DIY编程器网

 找回密码
 注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 11153|回复: 15
打印 上一主题 下一主题

NAND FLASH读写原理

[复制链接]
跳转到指定楼层
楼主
发表于 2013-10-16 23:48:50 | 只看该作者 回帖奖励 |正序浏览 |阅读模式

NAND FLASH读写原理

一、结构分析
S3C2410处理器集成了8位Nand Flash控制器。目前市场上常见的8位NandFlash有三星公司的k9f1208、k9f1g08、k9f2g08等。k9f1208、k9f1g08、k9f2g08的数据页大小分别为512Byte、2kByte、2kByte。它们在寻址方式上有一定差异,所以程序代码并不通用。本文以S3C2410处理器和k9f1208系统为例,讲述NandFlash的读写方法。

NandFlash的数据是以bit 的方式保存在memory cell里的,一般来说,一个cell 中只能存储一个bit,这些cell 以8 个或者16 个为单位,连成bit line,形成所谓的byte(x8)/word(x16),这就是NAND Device 的位宽。这些Line 组成Page, page 再组织形成一个Block。k9f1208的相关数据如下:

1block=32page;1page=528byte=512byte(Main Area)+16byte(Spare Area)。

总容量为=4096(block数量)*32(page/block)*512(byte/page)=64Mbyte

NandFlash以页为单位读写数据,而以块为单位擦除数据。按照k9f1208的组织方式可以分四类地址: Column Address、halfpage pointer、Page Address 、Block Address。A[0:25]表示数据在64M空间中的地址。

Column Address表示数据在半页中的地址,大小范围0~255,用A[0:7]表示;

halfpage pointer表示半页在整页中的位置,即在0~255空间还是在256~511空间,用A[8]表示;

Page Address表示页在块中的地址,大小范围0~31,用A[13:9]表示;

Block Address表示块在flash中的位置,大小范围0~4095,A[25:14] 表示;

二、读操作过程

K9f1208的寻址分为4个cycle。分别是:A[0:7]、A[9:16]、A[17:24]、A[25]。
读操作的过程为: 1、发送读取指令;2、发送第1个cycle地址;3、发送第2个cycle地址;4、发送第3个cycle地址;5、发送第4个cycle地址;6、读取数据至页末。

K9f1208提供了两个读指令,‘0x00’、‘0x01’。这两个指令区别在于‘0x00’可以将A[8]置为0,选中上半页;而‘0x01’可以将A[8]置为1,选中下半页。

虽然读写过程可以不从页边界开始,但在正式场合下还是建议从页边界开始读写至页结束。下面通过分析读取页的代码,阐述读过程。

static void ReadPage(U32 addr, U8 *buf)   //addr表示flash中的第几页,即‘flash地址>>9’
{
U16 i;
NFChipEn();   //使能NandFlash
WrNFCmd(READCMD0); //发送读指令‘0x00’,由于是整页读取,所以选用指令‘0x00’
WrNFAddr(0);   //写地址的第1个cycle,即Column Address,由于是整页读取所以取0
WrNFAddr(addr);   //写地址的第2个cycle,即A[9:16]
WrNFAddr(addr>>8);   //写地址的第3个cycle,即A[17:24]
WrNFAddr(addr>>16); //写地址的第4个cycle,即A[25]。
WaitNFBusy(); //等待系统不忙
for(i=0; i<512; i++)
   buf = RdNFDat(); //循环读出1页数据
NFChipDs();   //释放NandFlash
}

三、写操作过程

写操作的过程为: 1、发送写开始指令;2、发送第1个cycle地址;3、发送第2个cycle地址;4、发送第3个cycle地址;5、发送第4个cycle地址;6、写入数据至页末;7、发送写结束指令

下面通过分析写入页的代码,阐述读写过程。

static void WritePage(U32 addr, U8 *buf)   //addr表示flash中的第几页,即‘flash地址>>9’
{
U32 i;
NFChipEn();   //使能NandFlash
WrNFCmd(PROGCMD0);   //发送写开始指令’0x80’
WrNFAddr(0);   //写地址的第1个cycle
WrNFAddr(addr);   //写地址的第2个cycle
WrNFAddr(addr>>8); //写地址的第3个cycle
WrNFAddr(addr>>16); 写地址的第4个cycle
WaitNFBusy();   //等待系统不忙
for(i=0; i<512; i++)
   WrNFDat(buf); //循环写入1页数据
WrNFCmd(PROGCMD1); //发送写结束指令’0x10’
NFChipDs();   //释放NandFlash
}

四、总结

本文以S3C2410处理器和k9f1208系统为例讲述了nand flash的读写过程。在读写过程中没有考虑到坏块问题,有关ecc及坏块处理问题将在下个专题中讲述。

我的板子上使用的是SAMSUNG的K9F1208U0B,下面我将对此型号的NandFlash读取操作做一个讲解。
首先我们先从物理结构上来了解这颗芯片,结构图如下所示
正如硬盘的盘片被分为磁道,每个磁道又被分为若干扇区,一块Nand Flash被分为若干Block,每个Block又被分为若干Page。
由上图我们可以知道flash中Byte(字节),Page(页),Block(块)3个单位之间的关系为
1 Page =512 Bytes Data Field+ 16 Bytes Spare Field
1 Blcok="32" Pages
我们讨论的K9F1208U0B总共有4096 个Blocks,故我们可以知道这块flash的容量为4096 *(32 *528)= 69206016 Bytes = 66 MB
但事实上每个Page上的最后16Bytes是用于存贮检验码用的,并不能存放实际的数据,所以实际上我们可以操作的芯片容量为
4096 *(32 *512) = 67108864 Bytes = 64 MB
由上图所示,1个Page总共由528 Bytes组成,这528个字节按顺序由上而下以列为单位进行排列(1列代表一个Byte。第0行为第0 Byte ,第1行为第1 Byte,以此类推,每个行又由8个位组成,每个位表示1个Byte里面的1bit)。这528Bytes按功能分为两大部分,分别是Data Field和Spare Field,其中Spare Field占528Bytes里的16Bytes,这16Bytes是用于在读写操作的时候存放校验码用的,一般不用做普通数据的存储区,除去这16Bytes,剩下的512Bytes便是我们用于存放数据用的Data Field,所以一个Page上虽然有528个Bytes,但我们只按512Bytes进行容量的计算。
Data Field按位置关系又可分为两个部分,分别称为1st half与2nd half,每个half各占256个bytes。或许你会感到纳闷,为什么要把DataField分为两个部分?把他们看做一个整体进行操作不就好了吗?呵呵,凡事都有因果关系,这么分块自然有它的道理所在,但现在还不是告诉你答案的时候。我们还是先讨论一下它的操作吧。
对K9F1208U0B的操作是通过向Nand Flash命令寄存器(对于s3c2410来说此寄存器为NFCMD,内存映射地址为0x4e000004)发送命令队列进行的,为什么说是命令队列?就是因为要完成某个操作的时候发送的不是一条命令,而是连续几条命令或是一条命令加几个参数
下面是K9F1208U0B的操作命令集:
读命令有两个,分别是 Read1,Read2其中Read1用于读取Data Field的数据,而Read2则是用于读取Spare Field的数据。对于Nand Flash来说,读操作的最小操作单位为Page,也就是说当我们给定了读取的起始位置后,读操作将从该位置开始,连续读取到本Page的最后一个Byte为止(可以包括Spare Field)
Nand Flash的寻址
Nand Flash的地址寄存器把一个完整的Nand Flash地址分解成Column Address与Page Address.进行寻址
Column Address: 列地址。Column Address其实就是指定Page上的某个Byte,指定这个Byte其实也就是指定此页的读写起始地址。
Page Address:页地址。由于页地址总是以512Bytes对齐的,所以它的低9位总是0。确定读写操作是在Flash上的哪个页进行的。
Read1命令
当我们得到一个Nand Flash地址src_addr时我们可以这样分解出Column Address和Page Address
column_addr=src_addr%512;                           // column address
page_address=(src_addr>>9);                         // page address

也可以这么认为,一个Nand Flash地址的A0~A7是它的column_addr,A9~A25是它的Page Address。(注意地址位A8并没有出现,也就是A8被忽略,在下面你将了解到这是什么原因)
Read1命令的操作分为4个Cycle,发送完读命令00h或01h(00h与01h的区别请见下文描述)之后将分4个Cycle发送参数,1st.Cycle是发送Column Address。2nd.Cycle ,3rd.Cycle和4th.Cycle则是指定Page Address(每次向地址寄存器发送的数据只能是8位,所以17位的Page Address必须分成3次进行发送)。
4个Cycle
你是否还记得我上文提到过的Data Field被分为1st half 和2end half两个部分?而从上面的命令集我们看到Read1的命令里面出现了两个命令选项,分别是00h和01h。这里出现了两个读命是否令你意识到什么呢?是的,00h是用于读写1st half的命令,而01h是用于读取2nd half的命令。现在我可以结合上图给你说明为什么K9F1208U0B的DataField被分为2个half了。
如上文我所提及的,Read1的1st.Cycle是发送Column Address,假设我现在指定的Column Address是0,那么读操作将从此页的第0号Byte开始一直读取到此页的最后一个Byte(包括Spare Field),如果我指定的Column Address是127,情况也与前面一样,但不知道你发现没有,用于传递Column Address的数据线有8条(I/O0~I/O7,对应A0~A7,这也是A8为什么不出现在我们传递的地址位中),也就是说我们能够指定的Column Address范围为0~255,但不要忘了,1个Page的DataField是由512个Byte组成的,假设现在我要指定读命令从第256个字节处开始读取此页,那将会发生什么情景?我必须把Column Address设置为256,但Column Address最大只能是255,这就造成数据溢出。。。正是因为这个原因我们才把Data Field分为两个半区,当要读取的起始地址(Column Address)在0~255内时我们用00h命令,当读取的起始地址是在256~511时,则使用01h命令.假设现在我要指定从第256个byte开始读取此页,那么我将这样发送命令串
column_addr=256;
NF_CMD=0x01;                                          从2nd half开始读取
NF_ADDR=column_addr&0xff;                         1st Cycle
NF_ADDR=page_address&0xff;                        2nd.Cycle
NF_ADDR=(page_address>>8)&0xff;               3rd.Cycle
NF_ADDR=(page_address>>16)&0xff;             4th.Cycle
其中NF_CMD和NF_ADDR分别是NandFlash的命令寄存器和地址寄存器的地址解引用,我一般这样定义它们,
#define rNFCMD          (*(volatile unsigned char *)0x4e000004)          //NADD Flash command
#define rNFADDR          (*(volatile unsigned char *)0x4e000008)          //NAND Flash address
事实上,当NF_CMD=0x01时,地址寄存器中的第8位(A8)将被设置为1(如上文分析,A8位不在我们传递的地址中,这个位其实就是硬件电路根据01h或是00h这两个命令来置高位或是置低位),这样我们传递column_addr的值256随然由于数据溢出变为1,但A8位已经由于NF_CMD=0x01的关系被置为1了,所以我们传到地址寄存器里的值变成了
A0    A1    A2    A3    A4    A5    A6    A7    A8
1       0      0      0      0       0      0      0      1
这8个位所表示的正好是256,这样读操作将从此页的第256号byte(2nd half的第0号byte)开始读取数据。
Read2
Read2则是指定读取Spare Field的内容
其实Read1和Read2都是读命令,他们的区别相当于对一个读指针进行不同区域的定位。
nand_flash.c中包含3个函数
void nf_reset(void);
void nf_init(void);
void nf_read(unsigned int src_addr,unsigned    char *desc_addr,int size);
nf_reset()将被nf_init()调用。
nf_init()是nand_flash的初始化函数,在对nand flash进行任何操作之前,nf_init()必须被调用。
nf_read(unsigned int src_addr,unsigned    char *desc_addr,int size);为读函数,src_addr是nand flash上的地址,desc_addr是内存地址,size是读取文件的长度。
在nf_reset和nf_read函数中存在两个宏
NF_nFCE_L();
NF_nFCE_H();
你可以看到当每次对Nand Flash进行操作之前NF_nFCE_L()必定被调用,操作结束之时NF_nFCE_H()必定被调用。这两个宏用于启动和关闭Flash芯片的工作(片选/取消片选)。
至于nf_reset()中的
rNFCONF=(1<<15)|(1<<14)|(1<<13)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<<0);
这一行代码是对NandFlash的控制寄存器进行初始化配置,rNFCONF是Nand Flash的配置寄存器,各个位的具体功能请参阅s3c2410数据手册。
现在举一个例子,假设我要从Nand Flash中的第5000字节处开始读取1024个字节到内存的0x30000000处,我们这样调用read函数
nf_read(5000, 0x30000000,1024);
我们来分析5000这个src_addr.
根据
column_addr=src_addr%512;        
page_address=(src_addr>>9);        
我们可得出column_addr=5000%512=392
page_address=(5000>>9)=9
于是我们可以知道5000这个地址是在第9页的第392个字节处,于是我们的nf_read函数将这样发送命令和参数
column_addr=5000%512;
page_address=(5000>>9);
NF_CMD=0x01;                                             从2nd half开始读取
NF_ADDR= column_addr &0xff;                       1st Cycle
NF_ADDR=page_address&0xff;                        2nd.Cycle
NF_ADDR=(page_address>>8)&0xff;               3rd.Cycle
NF_ADDR=(page_address>>16)&0xff;             4th.Cycle
向NandFlash的命令寄存器和地址寄存器发送完以上命令和参数之后,我们就可以从rNFDATA寄存器(NandFlash数据寄存器)读取数据了.
我用下面的代码进行数据的读取.
for(i=column_addr;i<512;i++)
{
         *buf++=NF_RDDATA();
}
每当读取完一个Page之后,数据指针会落在下一个Page的0号Column(0号Byte).


Fisrt part :
NAND flash和NOR flash的不同

NOR flash采用位读写,因为它具有sram的接口,有足够的引脚来寻址,可以很容易的存取其内部的每一个字节。NAND flash使用复杂的I/O口来穿行地存取数据。8个引脚用来传送控制、地址和数据信息。NAND的读和写单位为512Byte的页,擦写单位为32页的块。
● NOR的读速度比NAND稍快一些。
  ● NAND的写入速度比NOR快很多。
  ● NAND的4ms擦除速度远比NOR的5s快。
  ● 大多数写入操作需要先进行擦除操作。
  ● NAND的擦除单元更小,相应的擦除电路更少。
在NOR器件上运行代码不需要任何的软件支持,在NAND器件上进行同样操作时,通常需要驱动程序,也就是内存技术驱动程序(MTD),NAND和NOR器件在进行写入和擦除操作时都需要MTD。
---------摘抄自网上流传很广的《NAND 和 NOR flash的区别》

Second part:
NAND Flash结构与驱动分析

一、NAND flash的物理组成
NAND Flash 的数据是以bit的方式保存在memory cell,一般来说,一个cell 中只能存储一个bit。这些cell 以8个或者16个为单位,连成bit line,形成所谓的byte(x8)/word(x16),这就是NAND Device的位宽。这些Line会再组成Page,(NAND Flash 有多种结构,我使用的NAND Flash 是K9F1208,下面内容针对三星的K9F1208U0M),每页528Bytes(512byte(Main Area)+16byte(Spare Area)),每32个page形成一个Block(32*528B)。具体一片flash上有多少个Block视需要所定。我所使用的三星k9f1208U0M具有4096个block,故总容量为4096*(32*528B)=66MB,但是其中的2MB是用来保存ECC校验码等额外数据的,故实际中可使用的为64MB。
NAND flash以页为单位读写数据,而以块为单位擦除数据。按照这样的组织方式可以形成所谓的三类地址:
Column Address:Starting Address of the Register. 翻成中文为列地址,地址的低8位
Page Address :页地址
Block Address :块地址
对于NAND Flash来讲,地址和命令只能在I/O[7:0]上传递,数据宽度是8位。

二、NAND Flash地址的表示
512byte需要9bit来表示,对于528byte系列的NAND,这512byte被分成1st half Page Register和2nd half Page Register,各自的访问由地址指针命令来选择,A[7:0]就是所谓的column address(列地址),在进行擦除操作时不需要它,why?因为以块为单位擦除。32个page需要5bit来表示,占用A[13:9],即该page在块内的相对地址。A8这一位地址被用来设置512byte的1st half page还是2nd half page,0表示1st,1表示2nd。Block的地址是由A14以上的bit来表示。
例如64MB(512Mb)的NAND flash(实际中由于存在spare area,故都大于这个值),共4096block,因此,需要12个bit来表示,即A[25:14],如果是128MB(1Gbit) 的528byte/page的NAND Flash,则block address用A[26:14]表示。而page address就是blcok address|page address in block NAND Flash 的地址表示为: Block Address|Page Address in block|halfpage pointer|Column Address 地址传送顺序是Column Address,Page Address,Block Address。
由于地址只能在I/O[7:0]上传递,因此,必须采用移位的方式进行。 例如,对于512Mbit x8的NAND flash,地址范围是0~0x3FF_FFFF,只要是这个范围内的数值表示的地址都是有效的。 以NAND_ADDR 为例:
第1 步是传递column address,就是NAND_ADDR[7:0],不需移位即可传递到I/O[7:0]上,而halfpage pointer即A8 是由操作指令决定的,即指令决定在哪个halfpage 上进行读
写,而真正的A8 的值是不需程序员关心的。
第2 步就是将NAND_ADDR 右移9位,将NAND_ADDR[16:9]传到I/O[7:0]上;
第3 步将NAND_ADDR[24:17]放到I/O上;
第4步需要将NAND_ADDR[25]放到I/O上;
因此,整个地址传递过程需要4 步才能完成,即4-step addressing。 如果NAND Flash 的容量是32MB(256Mbit)以下,那么,block adress最高位只到bit24,因此寻址只需要3步。
下面,就x16 的NAND flash 器件稍微进行一下说明。 由于一个page 的main area 的容量为256word,仍相当于512byte。但是,这个时候没有所谓的1st halfpage 和2nd halfpage 之分了,所以,bit8就变得没有意义了,也就是这个时候 A8 完全不用管,地址传递仍然和x8 器件相同。除了,这一点之外,x16 的NAND使用方法和 x8 的使用方法完全相同。

三、NAND flash驱动解读
以前由于做移植多一些,那些工作很简单(现在看来),从来都不用去关心驱动里面到底怎么实现的,这几次面试才发现真的是学的太浅了,似乎我还在学习仰泳而那些牛人基本都属于潜水级的了,潜的不知有多深。我对照着开发板所带的NAND flash驱动和k9f1208的芯片资料把这些代码通读了一遍,终于明白了NAND flash的读写过程是如何实现的了。我所参考的驱动是mizi公司为三星芯片所写的,我看看了,大概和官方2.4.18内核的nand.c差不多。
在s3c2410处理器中有专门的NAND flash控制器,他们位于SFR区,具体可以参看s3c2410用户手册。以下的这些代码均可以在vivi或者kernel里面找到,文中会标明程序出自何处。在vivi中,有关NAND flash的驱动都在driver/mtd/nand/下,该目录中包含的源文件:smc_core.c是NAND flash的主要驱动。
NAND flash 芯片定义了一个很长的结构,这个结构中包含了操作NAND flash的函数和一些必要的变量(include/mtd/nand.h)。
struct nand_chip {
#ifdef CONFIG_MTD_NANDY     /* =y */
    void (*hwcontrol)(int cmd);
    void (*write_cmd)(u_char val);
    void (*write_addr)(u_char val);
    u_char (*read_data)(void);
    void (*write_data)(u_char val);
    void (*wait_for_ready)(void);
    int page_shift;
    u_char *data_buf;
    u_char *data_cache;
    int    cache_page;
    struct nand_smc_dev *dev;
    u_char spare[SMC_OOB_SIZE];
#else    /* CONFIG_MTD_NANDY */
    ……
#ifdef CONFIG_MTD_NAND_ECC
    u_char ecc_code_buf[6];
    u_char reserved[2];
#endif
#endif    /* CONFIG_MTD_NANDY */
};
纵观对NAND flash的各种操作(read、write、erase),无外乎如下几种操作:
1.选择flash    nand_select()
2.发送命令     nand_command()
3.进行相应操作 read,write……
4.反选NAND flash   nand_deselect()

下面是以上四步的实现代码:
1、选择NAND flash
#define nand_select()   this->hwcontrol(NAND_CTL_SETNCE); \
                nand_command(mtd, NAND_CMD_RESET, -1, -1); \
                udelay (10);
hwcontrol(NAND_CTL_SETNCE)的作用是设置2410的NAND FLASH CONFIGURATION (NFCONF) REGISTER的NAND Flash Memory chip enable位为0,这位寄存器在自动重启后就被系统自动清零。如果要访问NAND flash的内存,这位必须置1。
nand_command(mtd, NAND_CMD_RESET, -1, -1);向flash发送命令,此命令为reset,即为重置NAND flash。
然后是10us的延迟,给flash个反应时间。
2、发送命令
Nand_command()同样在smc_core.c中实现。NAND flash的命令有如下几种:

    命令                         命令值                描述
NAND_CMD_READ0                    0                读操作
NAND_CMD_READ1                    1                读操作
NAND_CMD_PAGEPROG                0x10            页编程操作
NAND_CMD_READOOB                0x50            读写OOB
NAND_CMD_ERASE1                 0x60            读写操作
NAND_CMD_STATUS                    0x70            读取状态
NAND_CMD_STATUS_MULTI            0x71            读取状态
NAND_CMD_SEQIN                    0x80            写操作
NAND_CMD_READID                    0x90            读Flash ID号
NAND_CMD_ERASE2                 0xd0            擦写操作
NAND_CMD_RESET                    oxff                复位操作

按照程序的注释,可以将该函数的实现分为如下几步:
1、Begin command latch cycle
实现代码:
this->hwcontrol(NAND_CTL_SETCLE);
this->hwcontrol(NAND_CTL_DAT_OUT);
找到第二条语句的定义,发现什么都么做,不解!!希望达人解答。我猜想可能是一个数据读出的使能操作,允许数据读出。
Command Latch Enable(CLE) and Address Latch Enable(ALE) are used to multiplex command and address respectively, via the I/O pins. The CLE input controls the path activation for commands sent to the command register. When active high, commands are latched into the command register through the I/O ports on the rising edge of the nWE signal. 看了这段英文相信对第一条语句的作用已经十分清楚了,他就是用来控制向命令寄存(COMMAND SET (NFCMD) REGISTER)发送命令的。
2、 Write out the command to the device
这部分对于不同的命令来说,操作的步骤也不太相同,如果为写操作,那么还有根据flash不同的容量决定操作步骤,具体可以参看代码。如果为其他命令,那么就是简单的一行:
this->write_cmd (command);
将命令直接想到命令寄存器(NFCMD[7:0])中。
3、 Set ALE and clear CLE to start address cycle & Serially input address
1中已经提到了ALE和CLE的作用,现在开始发送地址。
实现代码:
this->hwcontrol(NAND_CTL_CLRCLE); // clear the command latch enable
this->hwcontrol(NAND_CTL_SETALE); // set the address latch enable
然后按位操作,是用函数write_addr()将地址写到NAND FLASH ADDRESS SET (NFADDR) REGISTER中。
4、 Latch in address
实现代码:
this->hwcontrol(NAND_CTL_CLRALE);
this->hwcontrol(NAND_CTL_DAT_IN);
地址发送完毕,清楚ALE。
5、 Pause for 15us
我使用的VIVI中,使用udelay (15)延时15us,但这个时间会因NAND Flash的不同而不同。
三、Operation
根据函数的不同,操作部分会不一样,但是主要的是对NAND FLASH DATA (NFDATA) REGISTER的操作,或写(编程)或者读。通过读或写函数的参数来返回或传递读出的值或写入的值。写得操作通常比较麻烦,他要将写到flash的内容重新读出后进行ECC校验,如果数据正确则在重新真正的写(编程),如果错误,则将数据写入flash的另一个块。读和写都是以页为单位进行操作。而擦除则以块为单位,三个周期发送完地址。擦除完毕后同样需要进行检察以确定是否擦除成功。
四、De-select the NAND device
实现代码:
#define nand_deselect() this->hwcontrol(NAND_CTL_CLRNCE);
反选flash吧,不知这样叫正确与否,跟select the NAND device相反,亦即使用完后将使能flash位清0,代码是NFCONF位于0x4e00_0000的位置(NFCONF |= NFCONF_nFCE_HIGH;),有兴趣的可以读读代码,看看这是怎么实现的,我的感觉就是关于寄存器的清置读起来都比较晕。

好了,到此flash操作的框架基本完成。


分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
收藏收藏3 分享分享 支持支持 反对反对
16#
发表于 2019-8-13 11:10:18 | 只看该作者
底层的东西比较麻烦,看看先
15#
发表于 2019-7-30 12:43:07 | 只看该作者
膜拜膜拜学习了
14#
发表于 2019-3-20 23:55:04 | 只看该作者
非常感谢楼主分22
13#
发表于 2019-3-16 13:57:07 | 只看该作者
崇拜啊~高手~学习学习~
12#
发表于 2019-3-15 15:18:04 | 只看该作者
这个有点复杂,先学习了。
11#
发表于 2019-1-13 09:17:41 | 只看该作者

新手没银子努力刷银子
10#
发表于 2016-10-10 21:05:06 | 只看该作者
学习了,谢谢分享
9#
发表于 2016-10-7 23:15:48 | 只看该作者
过来学习学习
您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|文字版|手机版|DIY编程器网 ( 桂ICP备14005565号-1 )

GMT+8, 2024-11-1 10:38 , 耗时 0.100392 秒, 20 个查询请求 , Gzip 开启.

各位嘉宾言论仅代表个人观点,非属DIY编程器网立场。

桂公网安备 45031202000115号

DIY编程器群(超员):41210778 DIY编程器

DIY编程器群1(满员):3044634 DIY编程器1

diy编程器群2:551025008 diy编程器群2

QQ:28000622;Email:libyoufer@sina.com

本站由桂林市临桂区技兴电子商务经营部独家赞助。旨在技术交流,请自觉遵守国家法律法规,一旦发现将做封号删号处理。

快速回复 返回顶部 返回列表