JZ2440开发板——使用S3C2440操作Nand Flash

news2024/9/27 18:35:21

以下内容源于韦东山课程的学习与整理,如有侵权请告知删除。

本篇文章涉及以下文档资料:S3C2440数据手册、Nand Flash数据手册(有三份,看K9F2G08U0C即可)、JZ2440开发板原理图。

一、JZ2440上的Nand Flash

JZ2440开发板上板载了一个Nand Flash,型号为K9F2G08U0C,大小为256MB,连接在S3C2440的 Nand Flash 控制器上。 

1.1 数据线的复用 

我们知道,Nand Flash是一个存储芯片,那么类似于“读地址A的数据,把数据B写到地址A”这样的操作是合理的。那问题就来了!

问1:由下面Nand Flash的原理图可知,Nand Flash和S3C2440之间只有数据线和一些控制信号线,那怎样传输地址?

答:在数据线 LDATA0~LDATA7上既可以传输数据,又可以传输地址。

问2:下图是Nand Flash数据手册中给出的命令表格,由此可知,操作Nand Flash时需要先向它发出命令,那怎样传输命令?

答:在数据线 LDATA0~DATA7上既可以传输数据,又可以传输地址,也可以传输命令。

1.2 控制信号简介

问3:既然数据线上可以传输数据、地址和命令,那如何区分它们?

答:通过一些控制信号,比如ALE(地址锁存使能信号)、CLE(命令锁存使能信号)、WE(写使能信号)、RE(读写能信号)来区分。如下表所示,当ALE为高电平时传输的是地址,当CLE为高电平时传输的是命令,当ALE和CLE都为低电平时传输的是数据。

ALE

CLEWERE数据线的数据含义
---地址
---命令
-待写入Nand中的数据
-从Nand中读出的数据

S3C2440数据手册P50,对这些控制信号做了简单的说明,如下所示:

有时候,数据手册中会把 nFWE 写成 nWE 或者 \overline{WE}(尤其是在时序图中),但它们的意思是一样的;同理nFCE、nFRE、FRnB也是如此(把F去掉,变成nCE、nRE、RnB;又或者是\overline{CE}\overline{RE}R\overline{B})。信号前带有字母n,或者信号头上有横线,皆表示低电平有效。

问4:由JZ2440开发板原理图可知,Nand Flash、Nor Flash、SDRAM、DM9000等设备同时接到数据线上,如何避免它们互相干扰?

答:S3C2440要访问这些外接设备时,必须要先选中它。没有被选中的设备不会工作,就好像它根本就不存在一样,自然不会干扰到被选中的设备。对于Nand Flash芯片,它的片选引脚是nCE。

问5:假如需要烧写Nand Flash,把命令、地址、数据发给Nand Flash之后,它肯定不可能瞬间完成烧写的。那如何判断烧写完成?

答:通过 Nand Flash 状态引脚 RnB 来判断,高电平表示空闲或就绪(烧写完成),低电平表示正忙(还在烧写)。 

二、如何操作Nand Flash

2.1 Nand Flash的命令表格 

Nand Flash的本质是Flash,因此操作流程和Nor Flash基本相同,都是“发命令->发地址->发数据”这个流程。

下图是Nand Flash数据手册中给出的命令表格。

2.2 读ID操作(举例)

比如我们要进行“读ID操作”。所谓ID,是指 Nand Flash 芯片内嵌的厂商ID、设备ID等信息。

由于上面的命令表格不直观,我们看一下Nand Flash数据手册P26中“读ID操作”的时序图:

从上图可知,“读ID”操作包括以下步骤: 

1)选中芯片\overline{CE}拉低,表示选中Nand Flash芯片。

2)发出命令:CLE发出高脉冲,表示数据线上传输的是命令;在数据线上传输命令值0x90;\overline{WE}发出一个低脉冲,表示要将命令值写入Nand Flash。在\overline{WE}发出的低脉冲的上升沿时刻,Nand Flash获得数据线上的命令值0x90。

3)发出地址:ALE发出高脉冲,表示数据线上传输的是地址;在数据线上输出地址值0x00;\overline{WE}发出一个低脉冲,表示要将地址值写入Nand Flash。在\overline{WE}发出的低脉冲的上升沿时刻,Nand Flash获得数据线上的地址值0x00。

4)读数据:CLE、ALE保持低电平,表示数据线上传输的是数据;\overline{RE}周期地发出一些低脉冲,表示从数据线上周期地读取数据。

根据图示,第1个读周期会得到厂商ID(0xEC),第2个读周期得到设备ID(0xDA),第3个读周期读到数值0x10,第4个读周期读到数值0x15,第5个读周期读到数值0x44。至于第3~5个读周期读到的数值表示何意,见下面的2.4.1小节内容。

2.3 Nand Flash控制器简介

2.3.1 控制器的作用

在实际编程中,我们不需要自己编写代码来进行诸如拉低\overline{CE}、发出高脉冲这样的操作。

这是因为S3C2440内部有一个Nand Flash控制器(如下图所示),它与板载Nand Flash芯片相连,专门负责接收发出Nand Flash的控制信号脉冲。只要我们根据外接的Nand Flash芯片的性能,初始化好Nand Flash控制器(即通过设置Nand Flash控制器的相关寄存器,来配置Nand Flash控制器的时序参数),它就会帮我们发出符合时序关系的控制信号脉冲,然后我们在编程中直接发命令、发地址、发数据即可,无需理会这些控制信号脉冲复杂的时序关系。

Nand Flash 控制器的存在,大大简化了我们的编程工作。比如下面表中的相关操作,由于Nand Flash 控制器的存在,编程时我们执行右侧操作即可,不需要理会高低电平、脉冲等操作。

流程对Nand Flash的操作对Nand Flash控制器寄存器的设置
选中芯片nCE设为低电平NFCONT[1]=0b0

发命令

CLE设为高电平NFCMMD=命令值
在数据线上输出命令值
发出一个写脉冲
发地址ALE设为高电平NFADDR=地址值
在数据线上输出地址值
发出一个写脉冲
发数据ALE、CLE设为低电平NFDATA=数据值
在数据线上输出数据值
发出一个写脉冲
读数据ALE、CLE设为低电平val=NFDATA
发出读脉冲
读取数据线上的数据值

 2.3.2 相关寄存器简介

上面的表格涉及 Nand Flash 控制器的几个寄存器,这里简单介绍一下:

 (1)NFCONT 寄存器(0x4E000004)

因为数据线是共用的(比如SDRAM、DM9000、Nor Flash、Nand Flash都接到数据线上),所以Nand Flash不会一直使能。我们可以通过设置 NFCONT 寄存器,先关闭Nand Flash的片选信号,后续需要使能 Nand Flash时再打开。

(2)NFCMMD 寄存器(0x4E000008)

发送给Nand Flash的命令,需要写入到 Nand Flash控制器的 NFCMMD 寄存器中:  

(3)NFADDR 寄存器(0x4E00000C)

发送给Nand Flash的地址,需要写入到 Nand Flash控制器的 NFADDR 寄存器中:

(4)NFDATA 寄存器(0x4E000010)

发送给Nand Flash的数据,需要写入到 Nand Flash控制器的 NFDATA 寄存器中;从 Nand Flash 中读取出的数据也保存在 Nand Flash 控制器的 NFDATA 寄存器中:

2.4 在u-boot上进行读操作

u-boot的代码已经设置好 Nand Flash 控制器的时序参数,在启动过程中会初始化 Nand Flash 控制器(下面第三节的内容正是我们裸机自己写的初始化代码)。因此我们可以借助于uboot读写内存的能力,在uboot的shell中操作Nand Flash控制器的寄存器(给NFCMMD寄存器写入命令、给NFADDR寄存器写入地址、给NFDATA寄存器写入数据、读取NFDATA寄存器中的数据,等等),以测试Nand Flash的读写。

注意,由于数据线是共用的,uboot启动之后不会一直将Nand Flash使能(uboot已经通过设置 NFCONT 寄存器将片选信号关闭了),现在我们需要操作Nand Flash,因此需要先设置 NFCONT 寄存器以开启片选信号,也就是将 NFCONT[1] 设置为0b0。

2.4.1 读取ID

根据2.2中读ID操作的步骤,以及2.3.2中相关寄存器的简介,可以得到下面的对应表格:

操作步骤对Nand Flash控制器寄存器的设置u-boot上的操作
选中Nand FlashNFCONT[1]设置为0b0

md.l 0x4E000004 1

mw.l 0x4E000004 1

发出命令0x90NFCMMD=0X90mw.b 0x4E000008 0x90
发出地址0x00NFADDR=0x00mw.b 0x4E00000C 0x00
读数据得到厂商IDval=NFDATAmd.b 0x4E000010 1
读数据得到设备IDval=NFDATAmd.b 0x4E000010 1
退出读ID模式NFCMMD=0xffmw.b 0x4E000008 0xff

在u-boot的shell中操作与显示如下:

OpenJTAG> md.l 0x4E000004 1 
4e000004: 00000003    ....
OpenJTAG> mw.l 0x4E000004 1
OpenJTAG> mw.b 0x4E000008 0x90
OpenJTAG> mw.b 0x4E00000C 0x00
OpenJTAG> md.b 0x4E000010 1
4e000010: ec    .                //第1个读周期显示的内容
OpenJTAG> md.b 0x4E000010 1
4e000010: da    .                //第2个读周期显示的内容
OpenJTAG> md.b 0x4E000010 1
4e000010: 10    .                //第3个读周期显示的内容
OpenJTAG> md.b 0x4E000010 1
4e000010: 95    .                //第4个读周期显示的内容
OpenJTAG> md.b 0x4E000010 1
4e000010: 44    D                //第5个读周期显示的内容
OpenJTAG> 
OpenJTAG>md.b 0x4E000010 1      //这里想再次读,但会提示下面的信息
Unknown command 'md.b' - try 'help'
OpenJTAG>
OpenJTAG> mw.b 0x4E000008 0xff  //退出读ID模式
OpenJTAG>

每个读周期读到的内容,具有不同的含义。我们看一下Nand Flash数据手册P29的内容:

  • 第1个读周期,得到厂商ID(0xEC)。
  • 第2个读周期,得到设备ID(0xDA)。
  • 第3个读周期,读到的数值可以提供“Internal Chip Number, Cell Type, Number of Simultaneously Programmed Pages”这些信息。
  • 第4个读周期,读到的数值可以提供“页大小、块大小”等信息。
  • 第5个读周期,读到的数值可以提供“Plane Number,Plane Size”这些信息。

比如上面我们第4个读周期读到的数值是0x95,化为二进制即 0b 1001 0101,根据上表可知:

  • bit[1:0] 是01,表示 page size 是2KB。(关注)
  • bit[5:4] 是01,表示 block size 是128KB。(关注)
  • bit[7] 是1而bit[3] 是0,表示最小的串行访问时间是25ns。不关心这个参数,这里写出来只是为了表明这个表格怎么看。

但是Nand Flash数据手册中的“读ID”的时序图(见2.2),第4个读周期读到的数值为何是0x15?不过通过分析 0x15 = 0b 0001 0101的含义,得知它也表示页大小是2KB、块大小是128KB。 

2.4.2 读取 0 地址的数据

作为印证作用,首先使用u-boot中提供的命令来查看0地址的内容:

OpenJTAG> nand dump 0
Page 00000000 dump:
        17 00 00 ea 14 f0 9f e5  14 f0 9f e5 14 f0 9f e5
        14 f0 9f e5 14 f0 9f e5  14 f0 9f e5 14 f0 9f e5
        00 02 f8 33 60 02 f8 33  c0 02 f8 33 20 03 f8 33
        80 03 f8 33 a0 04 f8 33  c0 04 f8 33 ef be ad de
        00 00 f8 33 00 00 f8 33  bc 04 fb 33 14 77 fb 33
        de c0 ad 0b de c0 ad 0b  00 00 00 00 de c0 ad 0b
        //这里省略部分输出
OOB:
        ff ff ff ff ff ff ff ff
        ff ff ff ff ff ff ff ff
        ff ff ff ff ff ff ff ff
        ff ff ff ff ff ff ff ff
        ff ff ff ff ff ff ff ff
        9a 55 ab 3c 0c c3 fc 3f
        0f 5a 65 5b 56 56 ab 56
        a9 9b 55 a9 5b 3f 0c ff
OpenJTAG>

然后回看一下2.1节中的命令表格,可知“读操作”可知需要先后发出0x00、0x30命令。

我们继续看一下Nand Flash数据手册P22的 “读操作” 时序图,可见“发出地址”这环节发出了5个周期的地址序列。

这里为什么需要 5 个周期的地址序列?因为这款 Nand Flash 容量大小为256MB,需要28位地址来寻址,而可以传输地址的数据线只有8根,因此至少需要4个地址序列。为了兼容更大容量的 Nand Flash芯片,规定要发出5个周期的地址序列。

这5个周期的地址序列含义如下:

我们先不理会关于Nand Flash的行地址、列地址的概念(这部份内容将在第五节讲到),为了简单起见,我们假设读取0地址上的数据,则5个周期的地址序列全是0x00。得到下面的对应表格:

操作步骤对Nand Flash控制器寄存器的设置u-boot上的操作
选中Nand FlashNFCONT[1]设置为0b0

md.l 0x4E000004 1 (显示00000003)

mw.l 0x4E000004 1 (改为00000001)

发出命令0x00NFCMMD=0x00mw.b 0x4E000008 0x00
发出地址0x00 (1st)NFADDR=0x00mw.b 0x4E00000C 0x00
发出地址0x00 (2nd)NFADDR=0x00mw.b 0x4E00000C 0x00
发出地址0x00 (3rd)NFADDR=0x00mw.b 0x4E00000C 0x00
发出地址0x00 (4th)NFADDR=0x00mw.b 0x4E00000C 0x00
发出地址0x00 (5th)NFADDR=0x00mw.b 0x4E00000C 0x00
发出命令0x30NFCMMD=0x30mw.b 0x4E000008 0x30
读数据val=NFDATAmd.b 0x4E000010 1
读数据val=NFDATAmd.b 0x4E000010 1
读数据val=NFDATAmd.b 0x4E000010 1
读数据val=NFDATAmd.b 0x4E000010 1
………………
退出读数据模式NFCMMD=0xffmw.b 0x4E000008 0xff

在u-boot的shell中操作与显示如下:

OpenJTAG> md.l 0x4E000004 1  //显示一下原来的内容
4e000004: 00000003    ....
OpenJTAG> mw.l 0x4E000004 1     //使能片选信号
OpenJTAG> mw.b 0x4E000008 0x00  //发出命令0x00
OpenJTAG> mw.b 0x4E00000C 0x00  //发出地址1
OpenJTAG> mw.b 0x4E00000C 0x00  //发出地址2
OpenJTAG> mw.b 0x4E00000C 0x00  //发出地址3
OpenJTAG> mw.b 0x4E00000C 0x00  //发出地址4
OpenJTAG> mw.b 0x4E00000C 0x00  //发出地址5
OpenJTAG> mw.b 0x4E000008 0x30  //发出命令0x30
OpenJTAG>
OpenJTAG> md.b 0x4E000010 1 //读第1个数据
4e000010: 17    .
OpenJTAG> md.b 0x4E000010 1 //读第2个数据
4e000010: 00    .
OpenJTAG> md.b 0x4E000010 1 //读第3个数据
4e000010: 00    .
OpenJTAG> md.b 0x4E000010 1 //读第4个数据
4e000010: ea    .
OpenJTAG> md.b 0x4E000010 1 //读第5个数据
4e000010: 14    .
OpenJTAG> md.b 0x4E000010 1 //读第6个数据
4e000010: f0    .
OpenJTAG> md.b 0x4E000010 1 //读第7个数据
4e000010: 9f    .
OpenJTAG> md.b 0x4E000010 1 //读第8个数据
4e000010: e5    .
OpenJTAG> md.b 0x4E000010 1 //读第9个数据
4e000010: 14    .
OpenJTAG> md.b 0x4E000010 1 //读第10个数据
4e000010: f0    .
OpenJTAG> mw.b 0x4E000008 0xff  //退出读数据模式
OpenJTAG>

可见这里读到的数据,与使用u-boot自带的nand dump命令读到的数据,两者是一样的。 

三、初始化Nand Flash控制器

3.1 存储芯片的编程步骤

存储芯片的编程,主要包括以下步骤:

存储芯片的编程步骤【例子】Nand Flash存储芯片的编程
初始化主控芯片的Nand Flash控制器的初始化
识别读取ID信息 
读操作一次读一个页的数据(page)
写操作一次写一个页的数据(page)
擦除一次擦除一个块的数据(block)(1block=64 page)

接下来讲的就是初始化,即S3C2440的Nand Flash控制器的初始化。

3.2 设置 Nand Flash 的时序

由2.4节的u-boot的操作可知,通过设置Nand Flash控制器的寄存器(见2.3.2),就可以完成发命令、发地址、发数据/读数据这些操作,非常简单。这是因为u-boot启动过程中(回顾一下uboot的启动过程)已经把 Nand Flash 控制器初始化,所以可以直接这样操作。

我们在编写裸机程序时,需要自己来编写Nand Flash控制器的初始化代码。那到底如何初始化Nand Flash控制器呢?我们需要根据外接的Nand Flash芯片的时序参数,来设置 Nand Flash 控制器的相关寄存器,从而让 Nand Flash 控制器发出(符合外接 Nand Flash芯片时序要求的)控制信号。

3.2.1 查看 Nand Flash 芯片的时序参数

那Nand Flash芯片有哪些值得注意的时序参数?

在Nand Flash数据手册P19中有如下时序图(为啥是这个图?因为首先是发出命令,所以这里我们看命令锁存周期的时序图):

图中各个参数的含义与取值如下(P12):

直接从上面两个图只能得知 Nand Flash 芯片这边的一些时序要求,如果要想与 S3C2440 的 Nand Flash 控制器这边产生关联与适配,需要查看 S3C2440 的 Nand Flash 控制器这边的时序图,并找出两边的关联之处(一般是谁要超出谁多长时间这种关系)。 

3.2.2 设置 Nand Flash 控制器的时序参数

我们看一下 S3C2440 数据手册中关于CLE与ALE的时序图(P217):

其中HCLK在时钟体系那篇文章中已经设置为100MHz,则T=10ns。

图中有以下三个时序参数,从图中可以大概知道其含义(有时数据手册会说明这些参数的含义,但在S3C2440数据手册中没有搜到;不过在Nand Flash数据手册中可以反推这些参数的含义):

  • TACLS:表示发出CLE或ALE信号后,需要经过TACLS时间,才能发出nWE信号。
  • TWRPH0:表示nWE信号需要保持 TWRPH 时间(或者说nWE信号的脉冲宽度)。
  • TWRPH1:表示释放nWE信号之后,CLE或ALE还需要继续保持TWRPH时间。

这三个时序参数的值,可以在Nand Flash控制器的 NFCONF 寄存器中设置,如下所示:

NFCONF[10:8] 转化为十进制的 m,代入 HCLK x ( m + 1 ) 中,得到的值才是TWRPH0信号的持续时间,而不是我误解的 “m 表示TWRPH0信号的持续时间”。同理TWRPH1、TACLS也是如此。

这些时序参数的值,需要我们根据 3.2.1 小节中Nand Flash芯片的时序参数来设置。

(1)设置时序参数TACLS

由于TACLS表示发出CLE或ALE信号后需要经过多长时间才能发出nWE信号,根据 3.2.1 小节中的图可知 TACLS = Tcls - Twp,而且min(Tcls) = min(Twp) = 12ns,这意味着 TACLS = Tcls - Twp =0,也就是说CLE信号和nWE信号可以同时发出。

因为TACLS持续时间为0,所以 NFCONF[13:12] 应该设为 0b00。

(2)设置时序参数TWRPH0

由于TWRPH0表示nWE信号的脉冲宽度,根据 3.2.1 小节中的图可知 TWRPH0 = Twp ≥ 12ns。那么由NFCONF 寄存器中 TWRPH0 的描述可知,10ns*(m+1)≥ 12ns,得到 m≥1,所以m取1,所以 NFCONF[10:8] 应该设为 0b001,对应着nWE信号的脉冲宽度是20ns。

(3)设置时序参数TWRPH1

由于TWRPH1表示释放nWE信号之后CLE或ALE还需要继续保持多长时间,根据 3.2.1 小节中的图可知 TWRPH1  = Tclh ≥ 5ns。那么由10ns*(m+1)≥ 5ns 得到 m≥0,所以m取0,所以NFCONF[6:4] 应该设为 0b000,对应10ns。

3.3 使能Nand Flash控制器、关闭片选、初始化ECC

我们在NFCONT寄存器中,设置使能 Nand Flash 控制器、初始化ECC、关闭片选(先关闭,在需要操作Nand Flash时再打开)。

 

3.4 实现初始化函数

综上所述,Nand Flash控制器的初始化代码,可以写成这样:

void nand_init(void)
{
#define  TACLS   0
#define  TWRPH0  1
#define  TWRPH1  0
	/*设置NAND FLASH的时序*/
	NFCONF = (TACLS<<12) | (TWRPH0<<8) | (TWRPH1<<4);
	/*使能NAND FLASH控制器,初始化ECC,禁止片选*/
	NFCONT = (1<<4) | (1<<1) | (1<<0);
}

值得一提的是,由于只能以字节的形式来读取数据(因为只有8根数据线),需要对s3c2440_soc.h文件进行细节上的修改,如下所示,也就是将与在数据线传输有关的寄存器,其宏定义改为__REG_BYTE(表示8bit的长度):

#define     __REG(x)					(*(volatile unsigned int *)(x)) 
#define     __REG_BYTE(x)				(*(volatile unsigned char *)(x)) 

初始化Nand Flash之后,如果能够正确地读ID,则表示Nand Flash控制器的初始化是正确的,同时也表明这个Nand Flash芯片是可以工作的。所以接下来我们编写代码,测试读ID操作。

四、实现读ID函数

4.1 代码编写 

2.2节中已经给出了“读ID操作”的时序图,这里为了明朗直观方便,拷贝如下:

图中表明“读ID操作”需要:使能片选、发出命令0x90、发出地址0x00、读取数据。

我们根据这个时序图以及2.3中关于寄存器的描述,可以编写出“读ID操作”的代码,如下所示:

/*使能片选*/
void nand_select(void)
{
	NFCONT &=~(1<<1);
}

/*禁止片选*/
void nand_deselect(void)
{
	NFCONT |= (1<<1);
}

//发送命令
void nand_cmd(unsigned char cmd)
{
	volatile int i;
	NFCCMD = cmd;
	for(i=0; i<10; i++);//延时是为了保证数据的稳定
}

//发送地址
void nand_addr_byte(unsigned char addr)
{
	volatile int i;
	NFADDR = addr;
	for(i=0; i<10; i++);
}

//读取1字节的数据
unsigned char nand_data(void)
{
	return	NFDATA;
}

void nand_chip_id(void)
{ 
	unsigned char buf[5]={0};
	
	nand_select(); //使能片选
	nand_cmd(0x90); //发送命令
	nand_addr_byte(0x00);//发送地址

	buf[0] = nand_data();//开始读取数据
	buf[1] = nand_data();//数据会根据读次数而改变,而非一成不变	
	buf[2] = nand_data();
	buf[3] = nand_data();
	buf[4] = nand_data();	

	nand_deselect();//读完之后,禁止片选 	

	printf("maker   id  = 0x%x\n\r",buf[0]);//显示读取到的信息
	printf("device  id  = 0x%x\n\r",buf[1]);	
	printf("3rd byte    = 0x%x\n\r",buf[2]);		
	printf("4th byte    = 0x%x\n\r",buf[3]);			
	printf("page  size  = %d kb\n\r",1  <<  (buf[3] & 0x03));	
	printf("block size  = %d kb\n\r",64 << ((buf[3] >> 4) & 0x03));	
	printf("5th byte    = 0x%x\n\r",buf[4]);
}

这里解释一下代码中的移位操作代码,它是根据 2.4.1 中的图来编写的:

printf("page  size  = %d kb\n\r",1  <<  (buf[3] & 0x03));	
printf("block size  = %d kb\n\r",64 << ((buf[3] >> 4) & 0x03));	

对于page size,从图中我们可以得出这样的对应关系:buf[3]的低2bit的值n如果作为2的幂数,得到的数值2^n就是paze size的容量,而2^n在代码中的表示就是将1左移n位。这就是1 <<  (buf[3] & 0x03)的含义。

表示1左移多少位对应的容量低 2bit 的数值大小
0b1左移0位(0b1=1)1KB=2^0 KB0b00 即 0
0b1左移1位(0b10=2)2KB=2^1 KB0b01 即 1
0b1左移2位(0b100=4)4KB=2^2 KB0b10 即 2
0b1左移3位(0b1000=8)8KB=2^3 KB0b11 即 3

对于block size,其实和上表的对应关系类似。不过容量是在上表基础上乘以64,另外需要先把buf[3]先右移4位。

64 * ( 1  << ((buf[3] >>4) & 0x03) 其实就等于 64 << ((buf[3] >> 4) & 0x03))。

4.2 代码测试

注意上面生成的.bin文件大于4KB,如果烧写到 Nand Flash 中,Nand Flash控制器只会将 Nand Flash 中前4K的内容拷贝到内部 SRAM 中运行。因为目前还没有实现从Nand Flash读取数据的功能(将在第五节实现),就算你从Nand Flash启动,而且前4KB也有重定位的功能,你也无法读取 Nand Flash的数据,从而无法拷贝代码以实现重定位。

因此在还没有实现从Nand Flash读取数据的功能,而.bin文件又大于4KB时,需要将.bin文件烧写到Nor Flash中运行。

运行结果如下所示,可知页大小是2KB,块大小是128KB。

五、实现按页读取函数

第四节中我们实现了“读ID操作”,但所生成的.bin文件已经超过4KB,需要把它烧写到Nor Flash中才能运行。在4.2小节中说明了不能将.bin烧写到 Nand Flash 中的原因,是因为还没有实现从Nand Flash读取数据的功能。

那这节我们就来实现从Nand Flash中读取数据的功能,进而实现“ 程序超过4KB时也可以从Nand Flash启动 ”。

5.1 Nand Flash的内部结构

下图为Nand Flash 内部结构图(位于K9F2G08U0A数据手册P9)。

怎么理解这个图呢?首先一个 Nand Flash 芯片(这里叫做device)包含有很多个block,每个block又包含很多page。图中的阴影界面就表示一个page。许多page垒在一起形成block,许多block叠起来形成device。

这款Nand Flash一共包含2048个block,每个block包含64个page,每个page包含有2048字节的页数据、64字节的OOB区(所以每个页的大小为 2112字节),如下图所示:

5.2 关于行地址、列地址的概念

在2.4.2小节中曾提到 Nand Flash的行地址、列地址,它们的概念可以体现在下面这图中。

由此可知,所谓的行地址(row address),其实就是指第几个page;

所谓的列地址(col address),其实就是指每个page的页数据区的第几个字节(从0到2047)。

5.3 OOB区的作用 

如果CPU想读取Nand Flash中第2048个数据,那它读取的是哪个地方的数据?

答:是page1的第0个字节的数据,而非page0的OOB区中序号为2048的那个地方的数据。这是因为CPU使用某个地址访问数据时,它是在页数据空间来寻址的,根本就看不到OOB区。

Nand Flash 和 Nor Flash 相比有一个缺点,就是Nand Flash在读或写一页数据的时候,可能会发生位反转,里面可能有一位是错误的。为了解决这个问题,Nand Flash引入OOB区。在写页数据的时候,把数据写进页数据区的同时会生成一个校验码,把这个校验码写进OOB区里面;在读数据的时候,先读出一页数据,这读取到的一页数据中可能有某一位发生错误,于是它继续读出OOB区里面的校验码,使用OOB区里面的校验码来修正页数据里面的数据。也就是说,OOB区是为了解决Nand Flash 的缺陷而存在的。

下面是一段幽默的对话: 

        CPU大爷:小nand啊,你的性能比不上小nor啊,听说你有位反转的毛病?

        Nand:是的,大爷,位反转是我天生的毛病,时有时无!

        CPU大爷:靠,你说你价格便宜容量大,这不是害我嘛?

        Nand:没事,我有偏方,用OOB就可以解决这问题!

        CPU大爷:得得得,你那偏方是什么也别告诉我,我只管能读写正确的数据!

        Nand:好的,大爷,我这OOB偏方也就我自个私下使用。您老就像使用nor一样使唤我就可以了!

接下来要讲解的“读取页数据、擦除块数据、烧写数据”这些操作,在编程时都不涉及OOB区!

5.4 实现按页读取函数

在Nand Flash数据手册P22中,有如下“读Nand Flash操作”的时序图:

由图可知,其实从编程角度来看,我们只需要发出0x00命令、发出5个周期的地址、发出0x30命令,接下来就可以读数据了。

另外,5个周期的地址序列,是先发出2个Col Addr(列地址),再发出3个Row Addr(行地址)。 这5个周期的地址序列含义如下:

这里编写的 nand_read 函数,实现了Nand Flash的页数据读取功能。该函数的声明如下:

void nand_read(unsigned int addr, unsigned char *buf, unsigned int len)
  • 参数1,表示从Nand Flash 的哪个地址开始读;
  • 参数2,表示将读到的数据存放在哪里;
  • 参数3,表示读取多少的数据(以字节为单位)。 

下面是该函数的内部代码与注释: 

void nand_read(unsigned int addr, unsigned char *buf, unsigned int len)
{
	int i = 0;
    //行地址
	int page = addr / 2048; //每一页有2048个数据
	//列地址
    int col  = addr & (2048 - 1);//也就是addr%2048
                                //不过模运算可能不存在,所以写成这样

	nand_select(); //开启片选信号,表示选中Nand Flash

	while (i < len)//因为我们一次最多只能读1页的数据,
                   //如果要读取的数据长度超出1页则需要循环发出命令。
	{
		/* 发出00h命令 */
		nand_cmd(00);

		/* 发出地址 */
		/* col addr */  //根据地址序列图来发出地址
		nand_addr_byte(col & 0xff); //第1周期:发出列地址的低8bit
		nand_addr_byte((col>>8) & 0xff);//第2周期:发出列地址的高8bit

		/* row/page addr */
		nand_addr_byte(page & 0xff);//第3周期:发出行地址的低8bit
		nand_addr_byte((page>>8) & 0xff);//第4周期:发出行地址的高8bit
		nand_addr_byte((page>>16) & 0xff);//第5周期:发出行地址的最高8bit

		/* 发出30h命令 */
		nand_cmd(0x30);

		/* 等待就绪 */
		wait_ready();

		/* 读数据 */
		for (; (col < 2048) && (i < len); col++)
		{
			buf[i++] = nand_data();			
		}
		if (i == len)
			break;

        page++;  //当读完一行数据后(即读完1个page的数据后),
                 //重新从下一行(即下一个page)开始
                 //类似于换行
		col = 0; //当读完一行数据后(表明数到2048了),重新从0开始
                 //类似于回车
	}
	
	nand_deselect();//关闭片选信号
}

下面是对该函数的一些补充说明:

(1)“ addr & (2048 - 1) ”等同于“ addr % 2048 ”,这怎么理解? 

可以看这篇博文:求模运算(%)和按位与运算(&)。

强调一下:使用“ addr & (2048 - 1) ”来取余数,addr必须是2的幂次方整数。

(2)发出0x00命令、发出5个周期的地址、发出0x30命令,之后就可以读取数据了,但此时最多只能读取一页的数据。如果要继续读取下一页的数据,需要重新“发出0x00命令、发出5个周期的地址、发出0x30命令”。这就是上面代码中需要while循环的原因。

(3)wait_ready 函数用于等待Nand Flash空闲或就绪,代码如下:

void wait_ready(void)
{
	while (!(NFSTAT & 1));
}

NFSTAT寄存器位含义如下,bit[0]=0b1说明 Nand Flash 是空闲或就绪的,=0b0 则说明正忙,我们据此判断 Nand Flash是否繁忙。

(4)发出的地址,为何地址是下面这般写法?

        /* 发出地址 */
		/* col addr */  //根据地址序列图来发出地址
		nand_addr_byte(col & 0xff); //第1周期:发出列地址的低8bit
		nand_addr_byte((col>>8) & 0xff);//第2周期:发出列地址的高8bit

		/* row/page addr */
		nand_addr_byte(page & 0xff);//第3周期:发出行地址的低8bit
		nand_addr_byte((page>>8) & 0xff);//第4周期:发出行地址的中8bit
		nand_addr_byte((page>>16) & 0xff);//第5周期:发出行地址的高8bit

我们首先看一下5个周期的地址序列的含义:

这款Nand Flash容量是256MB,那么需要28位的地址来寻址。但由上图可知,这款 Nand Flash 需要29位的地址(A0~A28)来寻址,这是为何?莫非多个地址可以映射到同一个地方,或者因为28位没有把OOB计算在内,29位的计算在内了?不懂,先暂时不管。

这29位地址是这样分的:使用12bit(A0~A11)来表示列地址(Col Addr),使用17bit(A12~A28)来表示行地址(Row Addr)。

【1】由于可以传输地址的数据线只有8根,那么一个周期只能传送8个bit的地址。对于12bit长度的列地址,需要使用2个周期才能传完,但2个周期可以传输16bit的内容,16bit-12bit=4bit,则富余4bit。因此对于列地址是这样传输的:第1个周期传送的数据全部有意义,表示列地址的低8bit;第2个周期也传送了8bit的数据,但这8bit数据中只有低4bit有意义,高4bit必须为0。

这就好比用16bit来表示列地址,但最高4位必须为0,如下所示:

【2】对于17bit长度的行地址,需要使用3个周期才能传完,但3个周期可以传输24bit的内容,24bit-17bit=7bit,即富余7bit。因此对于行地址是这样传输的:第3个周期传送的数据全部有意义,表示行地址的低8bit;第4个周期传送的数据也全部有意义,表示行地址的中8bit;第5周期也传送了8bit的数据,但这8bit数据中只有 bit[0] 有意义,其他位必须为0。

注意,行地址用A12~A28表示,只是为了表示行地址和列地址加起来有28位,并不是说行地址与列地址是组合在一起的(列地址是bit[11:0]、行地址是bit[28:12],这种说法是错的)。实际上行地址和列地址是独立的两个地址,比如你完全可以用ADDR0~ADDR16来代替A12~A18这种写法。因此,A19~A12其实是行地址的低8位,在第3个周期发出;A27~A20其实就是行地址的中8位,在第4个周期发出;A28+7个0其实就是行地址的高8位(为了好理解,17bit的行地址其实可以看作24bit,但高7bit必须为0),在第5个周期发出。

5.5 实现从Nand Flash启动 

目前nand_read函数已经编写出来,那如何表明这个函数可以实现读操作呢?按道理我们应该继续完善nand_flash_test函数,即在输入“r”选项时对应一个函数,该函数调用nand_read函数,读到数据之后再把数据打印出来,这样就验证了读操作的成功。

但现在不想这么做。

由于第四节的“读ID操作”生成的.bin文件不能支持Nand启动,如果现在修改程序,让它支持Nand启动,也就验证了读操作的成功。因为支持Nand启动,意味着硬件自动将Nand Flash的前4KB内容拷贝到内部SRAM运行后,这4KB的内容能够顺利地从Nand Flash读取数据,将剩余的或全部内容拷贝到SDRAM中。可见这过程涉及“ 读取Nand Flash页数据 ”操作,那么如果可以从Nand Flash启动而且程序能正常显示菜单,就验证了已经实现读取Nand Flash页数据的功能。

实现从Nand Flash启动的代码如下(在init.c文件中添加下面的代码,修改一下Makefile文件,就可以将生成的.bin文件烧写到Nand Flash中并从Nand Flash启动,会正常显示菜单),代码中的copy2sdram函数会在start.S文件中被调用。

int isBootFromNorFlash(void)
{
	volatile int *p = (volatile int *)0;//将p指针指向0地址
                                        //int*表示4字节作为一个单位	
    int val;

	val = *p;//修改前先保存一下原来的值
    
    //意图写数据到0地址
	*p = 0x12345678;//意图将0地址开始的4字节赋值为0x12345678
                    //小端模式,则3地址存储0x12、2地址0x34、1地址0x56、0地址0x12

    //读出0地址的数据,看看是否与意图写进去的数据一样,
    //从而判断是否写成功,进而判断启动方式(Nor无法简单写,如果写成功,说明不是Nor启动)
	if (*p == 0x12345678)
	{
		/* 写成功, 是nand启动 */
		*p = val;
		return 0;
	}
	else
	{
        //如果是Nor启动,由于NOR不能像内存一样写,所以0地址的值根本就没被修改
		return 1;
	}
}

void copy2sdram(void)
{
	/* 要从lds文件中获得 __code_start, __bss_start
	 * 然后从0地址把数据复制到__code_start
	 */

	extern int __code_start, __bss_start;

	volatile unsigned int *dest = (volatile unsigned int *)&__code_start;
	volatile unsigned int *end = (volatile unsigned int *)&__bss_start;
	volatile unsigned int *src = (volatile unsigned int *)0;
	unsigned int len = (unsigned int)(&__bss_start) - (unsigned int)(&__code_start);

	if (isBootFromNorFlash())//从Nor Flash启动
	{
		while (dest < end)
		{
			*dest++ = *src++;//进行重定位
		}
	}
	else //从Nand Flash启动
	{
		nand_init();//先初始化Nand Flash控制器
                    //貌似在其他地方也调用这个行数进行初始化了,但重复操作应该没事
                    //而且读取Nand Flash上的数据时,本来就应该先初始化
		nand_read((unsigned int)src, dest, len);
	}
}

值得一提的是,当选择从Nand Flash启动时,要十分注意Makefile中.o文件的顺序,确保前4KB的内容可以完成代码重定位的操作,否则从Nand Flash启动时会无现象。

5.6 封装按页读取函数

这里封装的目的,主要是为了第八节的测试。

把从地址addr读取得到的64字节数据存放到buf缓冲区中,然后通过串口显示出来,代码如下图所示:

void do_read_nand_flash(void)
{
	unsigned int addr;
	volatile unsigned char *p;
	int i, j;
	unsigned char c;
	unsigned char str[16];
	unsigned char buf[64];
	
	/* 获得地址 */
	printf("Enter the address to read: ");
	addr = get_uint();

	nand_read(addr, buf, 64);
	p = (volatile unsigned char *)buf;

	printf("Data : \n\r");
	/* 长度固定为64 */
	for (i = 0; i < 4; i++)
	{
		/* 每行打印16个数据 */
		for (j = 0; j < 16; j++)
		{
			/* 先打印数值 */
			c = *p++;
			str[j] = c;
			printf("%02x ", c);
		}

		printf("   ; ");

		for (j = 0; j < 16; j++)
		{
			/* 后打印字符 */
			if (str[j] < 0x20 || str[j] > 0x7e)  /* 不可视字符 */
				putchar('.');
			else
				putchar(str[j]);
		}
		printf("\n\r");
	}
}

5.7 作业

1、实现NAND测试菜单中的“ [r] Read nand flash ”。

提示:可参考第六节的程序里的nand_flash.c文件。

2、改进nand_read函数,实现碰到坏块就跳过。

提示:可以通过读OOB区来检测坏块(如何访问),如果OOB区的 [2048] 不等于0xff,则说明这个block是坏的!代码可参考 lib_nand 。

六、实现按块擦除函数

6.1 块擦除时序图

Nand Flash数据手册P28,有如下所示的“块擦除操作”时序图:

由此可知,从编程角度来看,我们只需要发出0x60命令、发出3个周期的地址序列、发出0xD0命令,接下来就可以等待擦除的完成。 

图中的3个周期地址序列都是与行地址有关的,它们和5.4小节中对行地址的描述含义一致。

6.2 实现按块擦除函数 

这里编写的nand_erase函数,实现了Nand Flash的块擦除功能。该函数的声明如下:

int nand_erase(unsigned int addr, unsigned int len)
  • 参数1,表示从Nand Flash 的哪个地址开始擦除。
  • 参数2,表示要擦除多少数据(以字节为单位)。

下面是该函数的内部代码与注释: 

int nand_erase(unsigned int addr, unsigned int len)
{
	int page = addr / 2048;
    
    //擦除的起始地址必须是块的起始地址(如果不是整128k的话,则报错返回)
	if (addr & (0x1FFFF)) // 128*1024 -1 = 0x1FFFF                    
	{
		printf("nand_erase err, addr is not block align\n\r");
		return -1;
	}

	//如果要擦除的数据长度不是128KB整数倍的话,则报错返回
	if (len & (0x1FFFF)) 
	{
		printf("nand_erase err, len is not block align\n\r");
		return -1;
	}
	
	nand_select(); //先选中芯片

	while (1)//使用循环,是因为不知道len对应几个block(不知道要擦除几个block)
	{
		page = addr / 2048;
		
		nand_cmd(0x60);//发出60命令
		
		/* row/page addr */ //发出行地址
		nand_addr_byte(page & 0xff);
		nand_addr_byte((page>>8) & 0xff);
		nand_addr_byte((page>>16) & 0xff);

		nand_cmd(0xD0);//发出D0命令

		wait_ready();

		len -= (128*1024);//上述操作后已经擦除了一个block大小的数据
                          //一个block大小是 128KB = 128*1024 Byte
		if (len == 0)
			break;
		addr += (128*1024);//指向下一个block
	}
	
	nand_deselect();//取消片选
	return 0;
}

下面是对该函数的一些补充说明: 

(1)addr & (0x1FFFF) 这种表示方法,与5.4中代码解释(1)是类似的含义。 

(2)所谓的擦除,也就是将数据全部改为0xFF。

(3)代码中有这样的设定:擦除的起始地址必须是块的起始地址,不能是任意一个地址(否则报错返回);要擦除的数据长度必须是一个块容量(JZ2440开发板所使用的Nand Flash芯片,其一个块的大小是128KB)的整数倍(否则报错返回)。这个设定应该是基于“擦除是以块为单位进行的”这一事实。即使我们传入的参数len=1,它也会擦除一个块。

6.3 封装块擦除函数

这里封装的目的,主要是为了第八节的测试。

void do_erase_nand_flash(void)
{
	unsigned int addr;
	
	/* 获得地址 */
	printf("Enter the address of sector to erase: ");
	addr = get_uint();

	printf("erasing ...\n\r");
	nand_erase(addr, 128*1024);
}

七、实现按页烧写函数

7.1 页烧写时序图

Nand Flash数据手册P24,有如下所示的“页烧写操作”时序图:

由图可知,从编程角度来看,我们只需要首先发出0x80命令、发出5个周期的地址序列,然后发出待写进Nand Flash中的数据,最后发出0x10命令,接下来就可以等待烧写的完成。 

其中5个周期的地址序列,其含义与5.4(4)的含义一致。

7.2 实现按页烧写函数

这里编写的nand_write函数,实现了Nand Flash的烧写功能。该函数的声明如下:

void nand_write(unsigned int addr, unsigned char *buf, unsigned int len)
  • 参数1,表示要将数据烧写到哪个地址;
  • 参数2,表示从哪里获得待烧写的数据;
  • 参数3,表示要烧写多少字节的的数据。

下面是该函数的内部代码与注释: 

void nand_write(unsigned int addr, unsigned char *buf, unsigned int len)
{
    //addr表示要将数据烧写到哪个地址,下面两行表示将地址拆分为行地址和列地址
	int page = addr / 2048;       //行地址
	int col  = addr & (2048 - 1); //列地址
	int i = 0;

	nand_select(); //开启片选信号,即使能Nand Flash芯片

	while (1)
	{
		nand_cmd(0x80);//发出0x80命令

		/* 发出地址 */
		/* col addr */
		nand_addr_byte(col & 0xff);
		nand_addr_byte((col>>8) & 0xff);
		
		/* row/page addr */
		nand_addr_byte(page & 0xff);
		nand_addr_byte((page>>8) & 0xff);
		nand_addr_byte((page>>16) & 0xff);

		/* 发出数据 */ //发送一个页的数据
		for (; (col < 2048) && (i < len); )
		{
			nand_w_data(buf[i++]);
		}
		
		nand_cmd(0x10);//发出0x10命令表示烧写
		wait_ready();  //等待烧写完成

        //判断是否所有数据都烧写完成,没烧写完则进行下一个页的烧写
		if (i == len)
			break;
		else//
		{
			/* 开始下一个循环page */
			col = 0;
			page++;
		}
		
	}
	
	nand_deselect(); 	
}

下面是对该函数的一些补充说明:

(1)“发出0x80命令、发出5个周期地址序列、发出待烧写数据、发出0x10命令”这过程一趟下来,至多只能烧写1页的数据(至多烧写2KB的数据),如果待烧写的数据超过2KB,则需要循环这个过程。这就是while循环的意思所在。

(2)nand_w_data函数的代码如下。由2.3.2(4)可知,发送给Nand Flash的数据(这里是1个字节长度的数据),需要写入到 Nand Flash控制器的 NFDATA 寄存器中(这个寄存器是32bit的,但是它被__REG_BYTE这个宏修饰了,因此我们只能读取它的低8bit,见3.4节中的图片。这里为了直观明朗,拷贝在下面)。

void nand_w_data(unsigned char val)
{
	NFDATA = val;
}

(3)我们写数据时是按页写的,开始烧写的地址可能不是某个页的起始地址,所以下面的for循环没有col=0这个起始条件。

		/* 发出数据 */ //发送一个页的数据
		for (; (col < 2048) && (i < len); )
		{
			nand_w_data(buf[i++]);
		}

7.3 封装按页烧写函数

这里封装的目的,主要是为了第八节的测试。

void do_write_nand_flash(void)
{
	unsigned int addr;
	unsigned char str[100];
	int i, j;
	unsigned int val;
	
	/* 获得地址 */
	printf("Enter the address of sector to write: ");
	addr = get_uint();

	printf("Enter the string to write: ");
	gets(str);

	printf("writing ...\n\r");
	nand_write(addr, str, strlen(str)+1);

}

八、实现测试菜单函数

该函数的内容如下,其实就是提供一些菜单提示而已:

void nand_flash_test(void)
{
	char c;

	while (1)
	{
		/* 打印菜单, 供我们选择测试内容 */
		printf("[s] Scan nand flash\n\r");
		printf("[e] Erase nand flash\n\r");
		printf("[w] Write nand flash\n\r");
		printf("[r] Read nand flash\n\r");
		printf("[q] quit\n\r");
		printf("Enter selection: ");

		c = getchar();
		printf("%c\n\r", c);

		/* 测试内容:
		 * 1. 识别nand flash
		 * 2. 擦除nand flash某个扇区
		 * 3. 编写某个地址
		 * 4. 读某个地址
		 */
		switch (c)		 
		{
			case 'q':
			case 'Q':
				return;
				break;
				
			case 's':
			case 'S':
				nand_chip_id();
				break;

			case 'e':
			case 'E':
				do_erase_nand_flash();
				break;

			case 'w':
			case 'W':
				do_write_nand_flash();
				break;

			case 'r':
			case 'R':
				do_read_nand_flash();
				break;
			default:
				break;
		}
	}
}

这里多说一句,经过5.5之后的.bin文件,既可以烧写到Nand Flash中,也可以烧写到Nor Flash中。 

8.1 读数据测试

8.2 块擦除测试

​​​​​​​

 8.3 写数据测试

8.4 作业

1、改进nand_write实现碰到坏块就跳过。

2、百度了解一下NAND ECC。

参考博客

(1)S3C2440-裸机篇-10 | 使用S3C2440操作Nand Flash

(2)NAND Flash 读、写、擦除原理_flash擦写硬件原理-CSDN博客

(3)求模运算(%)和按位与运算(&)_求模和&-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2170687.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

部署wordpress项目

一、先部署mariadb 二、在远程登录工具上进行登录测试&#xff0c;端口号为30117&#xff0c;用户为 root&#xff0c;密码为123 三、使用测试工具&#xff1a; [rootk8s-master aaa]# kubectl exec -it pods/cluster-test0-58689d5d5d-7c49r -- bash 四、部署wordpress [root…

楼上还是楼下的暖气,谁家更好蹭?

前几天收到个私信&#xff0c;想了解楼上楼下哪一户开暖气对中间影响大。我看到后就想&#xff0c;妙啊&#xff0c;这样就不需要开暖气&#xff0c;让邻居家的热气传过来&#xff0c;得省多少取暖费&#xff1f;不过站在热力学的角度&#xff0c;我们今天就来研究一下这个问题…

Xcode报错:The request was denied by service delegate (SBMainWorkspace)

Xcode报错&#xff1a;The request was denied by service delegate (SBMainWorkspace) 造成的原因: &#xff08;1&#xff09;新的M2芯片的Mac电脑 (2) 此电脑首次安装启动Xcode的应用程序 (3&#xff09;此电脑未安装Rosetta 解决方法: &#xff08;1&#xff09;打开终端…

电商IM客服系统的主要功能 网站即时通讯软件源码or SaaS

电商IM客服系统在现代电商平台中扮演着至关重要的角色&#xff0c;提供了高效的客户服务解决方案。系统的多功能特性使其能够实时响应客户需求&#xff0c;解决问题并增加转化率。以下是电商IM客服系统的六大功能 1、实时在线聊天&#xff1a;支持顾客与客服实时沟通&#xff0…

php thinkphp 小程序发送订阅模板消息通知

小程序需要在我的模板中先选用模板 小程序需要先订阅模板 wx.requestSubscribeMessage({tmplIds: ["XII_0By8D9WabnUjVPB_8S1itsm2d4_xxx"],success:

C++:string类写时拷贝|引用计数

✨ Blog’s 主页: 白乐天_ξ( ✿&#xff1e;◡❛) &#x1f308; 个人Motto&#xff1a;他强任他强&#xff0c;清风拂山冈&#xff01; &#x1f4ab; 欢迎来到我的学习笔记&#xff01; 写时拷贝&#xff08;了解&#xff09; 参考博客&#xff1a;C写时拷贝的不同方案&…

前台项目启动/打包报错 Error: error:0308010C:digital envelope routines::unsupported

在package.json中修改启动/打包语句 如图&#xff0c;我这里是打包时候报错&#xff0c;就在build里前面加上 set NODE_OPTIONS--openssl-legacy-provider && 再次打包&#xff0c;成功。

了解HTTPS

目录 1.HTTP认识 2.HTTP请求 3.HTTP响应 4.URL 5.HTTP方法 面试题&#xff1a;POST 和 GET区别&#xff1f; 网上关于 GET 与 POST的差别 有待商议 关于请求报头 和 响应报头 6..Host &#xff1a; 7..USer-Agent&#xff08;简称UA&#xff09; 8.状态码 9.HTTPS 是…

使用Charles抓包Android App数据

版权归作者所有&#xff0c;如有转发&#xff0c;请注明文章出处&#xff1a;https://cyrus-studio.github.io/blog/ 抓包环境准备 1. 下载安装charles charles下载地址&#xff1a;https://www.charlesproxy.com/latest-release/download.do 2. SSL代理设置 3. http代理和…

七段 LED 显示器(7段数码管)

7 段 LED 显示器, 通常简称为 LED 数码管 或 数码管. 通过 菜单--绘制--数字芯片--添加 7 段 LED 显示器 可以引入它. 普通模式 它内部其实就是七盏长条状的 LED 灯, 有的横着放, 有的竖着放. 七个灯用 a b c d e f g 分别表示. 灯的位置从上到下, 从里到外顺时针下来, 如上图…

240925-GAN生成对抗网络

GAN生成对抗网络 GAN&#xff0c;顾名思义&#xff0c;gan……咳咳&#xff0c;就是干仗嘛&#xff08;听子豪兄的课讲说这个名字还真的源于中文这个字&#xff09;&#xff0c;对应的就有两方&#xff0c;放在这里就是有两个网络互相对抗互相学习。类比武林高手切磋&#xff…

iPhone手机备忘录如何克隆到其他手机?

很多苹果用户喜欢使用备忘录记事&#xff0c;它不仅方便用户记录日常事务&#xff0c;还能存储灵感和重要信息。然而&#xff0c;当用户需要更换手机时&#xff0c;尤其是跨系统更换&#xff0c;备忘录内容的迁移便成了一个难题。 为了解决这一问题&#xff0c;用户可以选择使…

亚马逊新手运营如何变优秀?——把简单的事情复杂化!

众所周知&#xff0c;电商运营的基本逻辑看似简单&#xff1a;流量、转化率和利润率的结合等于盈利。然而&#xff0c;这个等式背后隐藏的复杂性常常让新手运营者感到困惑。 他们可能会发现&#xff0c;尽管他们努力增加流量、提高转化率和调整利润率&#xff0c;但仍然无法实…

【可见的点——欧拉函数】

在数论&#xff0c;对正整数n&#xff0c;欧拉函数是小于或等于n的正整数中与n互质的数的数目&#xff08;不包括1&#xff09; 题目 思路 有三个点比较特殊&#xff08;因为一来这三个点一定可见&#xff0c;同时也无法用gcd 1判断&#xff09;&#xff1a;&#xff08;0&am…

自己偷偷玩!(NSFW)无内容审查大模型推荐

大家好&#xff0c;我是画画的小强 今天给大家推荐几个(NSFW)无内容审查的大模型&#xff0c;可以让你部署在本地电脑运行&#xff01; CausalLM-14B CausalLM-14B 是基于阿里通义实验室的大模型 Qwen-14B 加入其他中文数据集训练而来&#xff0c;经过量化和 DPO 算法的重构…

2025台球展,2025河南台球及配套设施展览会3月举办

阳春三月&#xff0c;年度招商季&#xff0c;壹肆柒中国国际台球产业博览会助力全国台球企业拓市场&#xff1b; 2025中国&#xff08;郑州&#xff09;国际台球产业博览会&#xff08;壹肆柒台球展&#xff09; The 2025 China (Zhengzhou) International Billiards Industry…

朋友圈内容折叠全解析:原因与对策

你是否遇到过精心编写的朋友圈动态被微信自动折叠成一行&#xff0c;甚至出现“叠中叠”现象&#xff0c;多条动态被压缩在一起&#xff1f;这种情况被称为“朋友圈折叠”&#xff0c;它影响着信息的曝光率和互动性。为了帮助你更好地管理朋友圈内容&#xff0c;我将为你详细解…

uni-app App版本更新

效果图&#xff1a; 前言 在移动应用开发中&#xff0c;确保用户能够及时更新到最新版本是非常重要的。本文将介绍如何在 uni-app 中实现 App 整包更新功能&#xff0c;并提供相关代码示例以帮助理解。 代码实现 2.1 引入模块 首先&#xff0c;我们需要引入用于处理更新的模块…

阿博图书馆管理:SpringBoot开发实践

第三章 系统分析 通过对系统功能模块分析可以得知&#xff0c;主要是对项目元素组合、分解和更换做出相应的单元&#xff0c;再通过系统模块来规划出一个原则&#xff0c;系统的设计首先是围绕用户需求进行开发设计的&#xff0c;主要是为了能够更好的管理信息和方便用户&#…

AI智能时代:哪款编程工具让你的工作效率翻倍?

引言 在日益繁忙的工作环境中&#xff0c;选择合适的编程工具已成为提升开发者工作效率的关键。不同的工具能够帮助我们简化代码编写、自动化任务、提升调试速度&#xff0c;甚至让团队协作更加顺畅。那么&#xff0c;哪款编程工具让你的工作效率翻倍&#xff1f;是智能的代码编…