单片机
返回首页

NandFlash读写过程

2016-11-27 来源:eefocus

一、结构分析


S3C2410处理器集成了8位NandFlash控制器。目前市场上常见的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[i] = 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[i]); //循环写入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操作的框架基本完成。


进入单片机查看更多内容>>
相关视频
  • RISC-V嵌入式系统开发

  • SOC系统级芯片设计实验

  • 云龙51单片机实训视频教程(王云,字幕版)

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

精选电路图
  • PIC单片机控制的遥控防盗报警器电路

  • 使用ESP8266从NTP服务器获取时间并在OLED显示器上显示

  • 用NE555制作定时器

  • 如何构建一个触摸传感器电路

  • 基于ICL296的大电流开关稳压器电源电路

  • 基于TDA2003的简单低功耗汽车立体声放大器电路

    相关电子头条文章