. M7 d. X+ {0 v. H2 F
* s/ H E; ?3 z' o本系列为ARM开发的进阶知识,讲解与ARM芯片进行通信的大部分高级外设的原理及使用方法,不局限于ARM芯片内部,实操性较强且晦涩,需要一定的读者具备一定代码阅读和理解能力。 - b- C7 R: `/ s! U
STM32 I2C总线通信专题讲解 , u+ s7 I Y. M- v
1 e) f2 c) x0 M& z
/ v. {1 H. {/ @$ f* q! k$ O5 [
总线特征: # ?3 n$ C4 r6 d, M6 _
1.两条总线线路:一条串行数据SDA,一条串行时钟线SCL(主从设备使用同一时钟,属于同步通信)来完成数据的传输及外围器件的扩展 - B" G; V4 c0 S% Q1 F3 ^
2.I2C总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址,通常是7位,有时候是10位 * t- m9 j) b o# G7 b& X
3.I2C总线数据传输速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。在开发配置的时候,最好检查从设备的传输速率从而对主设备(一般是MCU)进行相应的配置。一般通过I2C总线接口可编程时钟来实现传输速率的调整,同时也跟所接的上拉电阻的阻值有关。 2 f, h, g' f* {) X0 ~
4.I2C总线上的主设备与从设备之间以字节(8位)为单位进行单双工的数据传输。
% K% A O2 d* {; @- X e
拓扑结构——总线型 - a0 y9 C/ N, H( \' v; H
I2C 总线在物理连接上分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成,SCL由主机发出,SCL越快,通讯速率越快。通信原理是通过对SCL和SDA线高低电平时序的控制来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。 3 }% J: H4 `1 {9 `" q% Q7 o
: C4 W8 {9 |+ \5 i0 i1 z( c0 q
I2C总线协议 " H7 S; r, W5 g) Q! ?
1.I2C协议规定: 总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生。
b0 e& q) J- P, l: a% u
2.空闲状态:SCL和SDA都保持着高电平。
7 U$ E2 P6 J. ~. }) Q S
3.起始信号: 当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件,所有的从设备都能感受到这个跳变,做好准备等待被选择。
8 h/ s4 X/ t1 e A
4.结束信号:当SCL为高而SDA由低到高的跳变,表示产生一个 停止条件 6 C! H- q" o ^' j0 S) |4 y' ?
% W1 k/ @/ p, ?/ m6 k2 ^
5.数据传输:数据传输以字节为单位 , 主设备在SCL线上产生每个时钟脉冲的过程中将在SDA线上传输一个数据位,数据在时钟的高电平被采样这时候采集到是1就是1,是0就是0,所以在传输数据时,当时钟处于高电平时一定要保持稳定,时钟处于低电平时可以变换数据。(高电平采样,低电平变换)一个字节按数据位从高位到低位的顺序进行传输。主设备在传输有效数据之前 要先指定从设备的地址,一般为7位,然后再发生数据传输的方向位, 0表示主设备向从设备写数据,1表示主设备向从设备读数据。主从设备以字节为单位(8位)进行数据传输,开始传输数据时把从设备地址加上方向位组成一个8位的字节进行发送并接收一个应答。
I5 m+ y. ?5 v+ `5 I; p+ H+ ^# @
6.应答信号:接收数据的器件在接收到 8bit 数据后,向发送数据的器件发出低电平的应答信号,表示已收到数据。这个信号可以是主控器件发出,也可以是从动器件发出。总之,由接收数据的器件发出。
! W4 r: Q/ s4 c+ @. q9 }
3 I& e0 M1 R( \3 O* n
a.主设备向从设备写数据:
3 h7 {; M w0 E2 q" l
/ }$ q5 K* E1 i& } F E! g9 b
b.主设备读从设备的数据: # ^: q6 E$ j o( s
; R( s, `# v+ ~1 \9 I
c.主设备读从设备的某个寄存器:读设备的寄存器首先应该对该设备发送写命令,很多设备都可以看成是一段内存,所以写命令写给从设备,指明要读取哪个地址(寄存器)的数据,接下来才是真正的读数据。不同的从设备是由区别的,在驱动I2C从设备时应当查明设备的时序图,又怎样的要求,不同的时序对应了不同的命令。 & ]' A0 d2 f, L
6 d4 b' I& Y d- y; b8 D2 P. v9 J2 O1 V) G+ w. h1 q0 L: G& _4 e: j
STM32F4-I2C控制器特性 ' Q. _! M7 S3 ^. }' w: B9 y
软件模拟I2C时序:由于直接控制 GPIO 引脚电平产生通讯时序时,需要由 CPU 控制每个时刻的引脚状态,所以称之为“软件模拟协议”方式。我们知道,驱动I2C设备只需要两根管脚,即使单片机上没有I2C控制器,根据协议控制每根管脚每一时刻的电平状态,一根模拟数据线,一根模拟时钟线,就可以驱动从设备,相对而言效率低,但是可以实现控制驱动。STM32内部具备专门的I2C控制器,使用时只需对其进行相应的配置即可。
' S( q$ t" ^1 x9 R& X
硬件控制产生I2C时序:STM32 的 I2C 片上外设专门负责实现 I2C 通讯协议,只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并缓存起来,CPU只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种由硬件外设处理I2C协议的方式减轻了 CPU 的工作,且使软件设计更加简单。
0 _( @5 A! _7 F' d2 E
控制器功能:配置主从模式(一般都把STM32当作主机使用,作为从机时应当对其赋一个地址),通过配置其内部的寄存器产生一些中断和错误信号,配置通信速率位标准模式、快速模式、超快速模式等
& e& O0 I; O U
STM32芯片有3组I2C外设,可以同时进行3组I2C传输。它们的I2C通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚。
5 a* M$ N( A6 r' \2 j; ]) G9 W
2 Z0 [( |! R& u7 b* t$ C
EEPROM(AT24CXX)存储芯片介绍 ( Z8 d4 f. S/ v/ o# I) o9 }/ X
一个典型的I2C接口的从设备,专门用于存储数据的芯片。EEPROM (Electrically ErasableProgrammable read only memory),带电可擦可编程只读存储器,一种掉电后数据不丢失的存储芯片。EEPROM可以在电脑上或专用设备上擦除已有信息,重新编程。
6 Y* ]' M2 T- U
EEPROM常用来存储一些配置信息,以便系统重新上电的时候加载之,容量不会很高。EEPOM 芯片最常用的通讯方式就是I2C协议。XX表示容量,常用值为01、02、04、16、32、64等,单位Kbit。一般的存储芯片都具有写保护功能,对WP管脚加一个高电平就开启了写保护功能,就无法往芯片内写数据了。在开发中通常将该管脚接地,确保能够写数据 典型24CXX芯片引脚如下: Y6 U+ o f6 g" `) b z5 b
/ v2 B2 e8 d( P! b# k7 _
例:24C65的设备地址为7位,高4位恒定为1010,低3位取决于A0-A2的电平状态,般主机在读写24CXX都是把设备地址连同读写位组合成一个字节一起发送。
( t7 C/ U/ q+ V4 x4 L) \
7 o' m* {* [5 I* r! Y
24C65的电气连线如下,根据电气连线可知,A0-A2均接地,因此读地址为1010 0001,即0xA1;写地址为10100000,即0xA0 ,且WP接地,用户随时可向芯片内部写入数据。 4 B5 i# n( c& U6 C' X0 x T7 n1 q" B
& I" W( C% U+ ^+ {& l
24C65写时序:首先发送一个起始信号,接着发送从设备地址以及方向位,收到应答后,向从设备发送要写的存储区域的首地址,24C65的存储地址是16位,先发送高8位,收到应答后再发送低8位,再次收到应答后开始写数据。64Kbit大小位8K字节,需要13位即可表示,所以高3位固定定为0,如下图。 7 p. H1 G% I3 E9 V
这里是BYTE WRITE,一次写一个字节,此芯片还支持PAGE WRITE,一次写一页,也就是8个字节,如果想写更多,可设置一个for循环实现。
s# N, H, E7 f0 J' H" @, w
$ H* m9 i. f% z6 z1 V' _
24C65 读时序与写时序基本相同,只不过在读之前要发送再发送重复开始位进行读操作。
7 V4 v" ~1 G5 z' p+ l9 Z# E: W) R% e
5 k9 F n' _9 w: S+ T6 w
I2C读写EEPROM实例
o2 Q! g1 w+ B4 Y6 t
3 C+ I0 T) A) R: u, q7 m& h1 y
由电气原理图可知SCL和SDA分别接入了PB6和PB7管脚,读地址为1010 0001,即0xA1;写地址为10100000,即0xA0 4 t7 g U% ~! Q( Q
步骤: 1.配置RCC 1 N0 P8 A6 i1 C; r; j4 |
2.配置PB6和PB7管脚
2 Y2 j7 J0 {! I0 K2 W8 N
9 k4 b# s/ b0 r, N) z
3.配置I2C协议参数 + i0 C9 a. t2 Y2 o6 I: _
! I; I0 x8 e5 u) v' l& s `- K2 }- ` l* x% F
4.编写代码 - 9 {3 Y& k4 s, w, [
- //mian.c: M; i4 ^8 u1 ?! Y+ W& b
" f" z1 R& t2 `$ z7 h. h9 G: D- #include "main.h"
( U& B4 I3 W6 ~2 R3 ?1 p2 a0 \, g - #include "stm32f4xx_hal.h"
$ c( X8 ]! _( I. `' T2 i3 G - #include "i2c.h"8 ]9 f, A: M; K A6 B
- #include "usart.h", J% Q- e2 e* W7 ^
- #include "gpio.h"
% _3 |$ |# R/ _( c6 S/ E - 8 ]# |" A$ {# j1 o
- #define ReadAddr 0xA1
, @ p8 ^# `% T) c8 r2 L - #define WriteAddr 0xA05 N. O, P* f! n ]
- 5 T; d3 e$ m9 r) j- ^0 I# ?
- uint8_t Wbuf[20] = "EEPROM TEST OK!";. {" b! j# M0 x! v' A
- uint8_t Rbuf[20] = {0};
# u# l. p7 x) }9 \$ a: V - `( M- q! Y& Y4 c" z% ]5 |1 r( X
- /********* 24C65写数据函数*****************************/
! R6 _! @8 i( G% v9 L+ r: c! }& `
: E! g- o6 j! z# ^9 ~% |- void Eeprom_Write(uint16_t MemAddr, uint8_t *Wbuf, uint16_t len ){: {" L: j. f9 ~5 M0 S* J2 {
- while(len--){" N' B) _# {: y& v
- //I2C_MEMADD_SIZE_16BIT表示存储单元大小6 e7 A( l8 |0 w5 p
- //默认为两个参数,分别是I2C_MEMADD_SIZE_16BIT和I2C_MEMADD_SIZE_8BIT o% [( B [8 C# T2 m
- //由于24C65的存储地址是16位的. C. w' T, K3 | C6 y, s, i9 t" M1 f
- //所以我们选择I2C_MEMADD_SIZE_16BIT% A' J) _3 \2 n8 z* V; b# \: D
- //1表示一次写一个字节
/ o4 O/ m1 ?" W' t* w" c: ~ - while(HAL_I2C_Mem_Write(&hi2c1, WriteAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Wbuf, 1, 100) != HAL_OK){};
% y; f; o5 s, s. P% I- Q - MemAddr++;- O c$ Q* D/ O
- Wbuf++;
3 S1 J: _( ]2 b5 E$ [2 c1 `, f1 a - }
- _* X- e0 y- T, h - }# `$ H) P, W3 j& l2 i. J# t3 x
- % B. L; x) ?- m h7 P$ g
8 I. o3 H& r) m- /********* 24C65读数据函数*****************************/2 t7 ]1 w- m2 K- n1 I
- `3 l! f: c0 R- void Eeprom_Read(uint16_t MemAddr, uint8_t *Rbuf, uint16_t len ){
6 S8 W* Z" A2 Z* r4 v% |* a - //可以连续读,所以无需循环
1 ?& U! p5 y( o& Y - while(HAL_I2C_Mem_Read(&hi2c1, ReadAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Rbuf, len, 100) != HAL_OK );9 V8 j2 y8 E6 ]$ Y9 g1 y9 w
- }) p1 |$ m' m. `. c/ H
- int mian(){1 V4 P' w4 I/ v9 f, B- i
- MX_GPIO_Init();
# t- L; X; E! _" X: I2 N, {, U - MX_I2C1_Init();1 G% t6 [2 q5 U! l' F* [9 V/ @
- MX_USART1_UART_Init();
# q* J* R6 y% G3 Y s! A8 ~) _$ t - printf("this is i2c eeprom test\n");
& m- b. E8 ^' {8 r2 n% D3 K' @ - 1 N$ N; I, c4 g. Z2 h: _3 n; ^
- Eeprom_Write(0, Wbuf, sizeof(Wbuf) );! |$ g/ R* z; I( t- o- f$ P8 D
- HAL_Delay(500);, C! E. `5 O( C s3 b
- Eeprom_Read(0 , Rbuf, sizeof(Rbuf));
& C4 @0 @% G' Z8 g) K; h - printf("READ: %s\n", Rbuf);0 G1 M' ` c! z0 x
- $ P6 E2 f# b l) b% s h
- while(){
" `+ Q! k( a' O1 t h# ~& k; n - 8 b: [, \# v; A
- } e* W# _6 H. v' y
- }
复制代码
4 `: X* X' S# a
STM32 SPI总线通信专题讲解
0 Y4 H2 H* \8 w
SPI接口是Motorola 首先提出的全双工三线同步串行外围接口,采用主从模式(Master Slave)架构;支持多slave模式应用,一般仅支持单Master。时钟由Master控制,在时钟移位脉冲下,数据按位传输,是高位在前还是低位在前是可以配置的,配置时根据从设备的通信进行相应配置,一般是高位在前,低位在后(MSB first)。SPI接口有2根单向数据线,为全双工通信,目前应用中的数据速率可达几Mbps的水平。 SPI总线被广泛地使用在FLASH、ADC、LCD等设备与MCU间,要求通讯速率较高的场合。 - y, J7 }2 T$ t
( H9 [) \' W; e7 ]9 M4 A
SPI接口共有4根信号线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。
! ?+ Y, J. k( G/ X* T( a4 A" _
$ p9 l5 x) L0 V1 ^0 @. v
(1)MOSI:主器件数据输出,从器件数据输入,连接从机的MOSI,与串口不同,串口需要反着连接(Rx-----Tx) (2)MISO:主器件数据输入,从器件数据输出,连接从机的MISO (3)SCLK :时钟信号,由主器件产生 (4)/SS:从器件使能信号,由主器件控制(片选),一般情况下为地电平选中设备,高电平释放设备。 0 b% S9 r# d& B0 y- B9 \3 d
SPI总线协议
5 R; C; t) D# p( O& N/ B& e% ^
1 r, }7 T2 M F2 H# D
1.数据交换逻辑:主机和从机都包含一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节发起一次传输。寄存器通过MOSI信号线将字节传送给从机,从机也将自己的移位寄存器中的内容通过MISO信号线返回给主机。这样两个移位寄存器中的内容就被交换了。从机的写操作和读操作时同步完成的,因此SPI成为一个很有效的协议。 & V3 E, X" t$ t) G( p. {: K4 d
如果主机只想写不想读,只需把数据放在数据寄存器,SPI控制器会自动传给外设,同时忽略掉外设传过来的数据即可;如果主机只想读不想写,主机写给外设一个空字符或者随便写一个数据,外设就会把数据传过来,不管是只读还是只写,主机与外设的读和写都h会发生且同时进行。 Y2 u8 k& ]; ^# O' \4 n" C5 c
, x# \+ T9 v' W
2.起始信号: NSS信号线由高变低,是SPI通讯的起始信号。 , ` |) f' F- ]+ I
3.结束信号:NSS信号由低变高,是SPI通讯的停止信号。
! }- {" W2 z1 P+ w9 w
4.数据传输:SPI使用MOSI及MISO信号线来传输数据,使用SCK信号线进行数据同步。MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,按位传输,且数据输入输出是同时进行的。SPI每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制,要么是8位,要么是16位,可以配置。
$ a4 D8 w" j: h2 i1 J
7 r! a" y f8 I3 }$ Z, n T* E5 d
SPI的4种通信模式 . S. v+ v f) d% K0 T& o7 ~
/ ?3 e$ n# \ P4 a0 j4 r; W7 u
在SPI操作中,最重要的两项设置就是时钟极性(CPOL)和时钟相位(CPHA)这两项即是主从设备间数据采样的约定方式。由CPOL及CPHA的不同状态,SPI分成了四种模式,主机与从机需要工作在相同的模式下才可以正常通讯,因此通常主机要按照从机支持的模式去设置。同样在配置时一定要弄明白从机支持什么通信模式进行相应的配置。 ! q1 U$ t5 V' S) I5 M) \: n
7 q$ r* Q3 T$ A, Z* X
1.时钟极性CPOL : 设置时钟空闲时的电平: a.当CPOL= 0 ,SCK引脚在空闲状态保持低电平; b.当CPOL= 1 ,SCK引脚在空闲状态保持高电平。
: ^+ n- |2 z4 E' Z7 [- [$ @! o5 b
2.时钟相位CPHA :设置数据采样时的时钟沿: a.当 CPHA=0时,MOSI或 MISO 数据线上的信号将会在 SCK时钟线的奇数边沿被采样 b.当 CPHA=1时, MOSI或 MISO 数据线上的信号将会在 SCK时钟线的偶数边沿被采样
" Z" V( w0 t+ D( ~- r: W4 w
( X( I: v& G H2 J& ]$ {
STM32F4-SPI控制器特性
3 a# {4 t9 U6 u0 m6 S7 G7 P
; r5 ?7 B) P7 p2 D. c
1.通讯引脚: STM32F4芯片最多支持6个SPI外设控制器,它们的SPI通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚,以《STM32F4xx规格书》为准。f407只有SPI1、SPI2、SPI3。
8 r+ {$ }, a/ ^9 t
# g, n7 F( D) a* ?1 u
其中SPI1、SPI4、SPI5、SPI6是APB2上的设备,最高通信速率达42Mbtis/s,SPI2、SPI3是APB1上的设备,最高通信速率为21Mbits/s。其它功能上没有差异。
9 S3 T8 O3 \6 s" B! j. D; w1 V
2.时钟控制逻辑: SCK线的时钟信号,由波特率发生器根据“控制寄存器CR1”中的BR[0:2]位控制,该位是对f pclk 时钟的分频因子,对f pclk 的分频结果就是SCK引脚的输出时钟频率。
U0 x) U. V3 C% V I; K
* J/ F, ]& b {9 s1 `( Z- V4 Y
其中的fpclk 频率是指SPI所在的APB总线频率,APB1为fpclk1 ,APB2为fpckl2
( N/ p6 E, A* z
3.数据控制逻辑: STM32F4的MOSI及MISO都连接到数据移位寄存器上,数据移位寄存器的数据来源来源于接收缓冲区及发送缓冲区。
' R! D) x/ i6 O/ v1 A1 C! Q
a.通过写SPI的“数据寄存器DR”把数据填充到发送缓冲区中。 - }, {0 l0 R; C: m
b.通过读“数据寄存器DR”,可以获取接收缓冲区中的内容。 " G! B; i9 g J& X, G5 o
c.其中数据帧长度可以通过“控制寄存器CR1”的“DFF位”配置成8位及16位模式;配置“LSBFIRST位”可选择MSB先行(高位在前)还是LSB先行(低位在前)。
! d9 D: e7 B; y/ K4 b3 p
4.整体控制逻辑: a.整体控制逻辑负责协调整个SPI外设,控制逻辑的工作模式根据“控制寄存器(CR1/CR2)”的参数而改变,基本的控制参数包括前面提到的SPI模式、波特率、LSB先行、主从模式、单双向模式(同时发送和接收、只发送关掉接收、只接收关掉发送)等等。 / O% a/ o1 z+ K1 Q# o2 y
b.在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,只要读取状态寄存器相关的寄存器位,就可以了解SPI的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生SPI中断信号、DMA请求及控制NSS信号线。
- P( G0 O8 T6 C& ^. s+ B+ i* a
c.实际应用中,一般不使用STM32 SPI外设的标准NSS信号线,而是更简单地使用普通的GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号。 串行FLASH_W25X16简介
) ^3 l! x" q) z, o9 q
0 ]( h: _+ X# T
FLSAH 存储器又称闪存,它与EEPROM都是掉电后数据不丢失的存储器,但FLASH存储器容量普遍大于 EEPROM,现在基本取代了它的地位。我们生活中常用的 U盘、SD卡、SSD 固态硬盘以及我们 STM32 芯片内部用于存储程序的设备,都是 FLASH 类型的存储器。在存储控制上,最主要的区别是FLASH 芯片只能一大片一大片地擦写,而EEPROM可以单个字节擦写。 8 e. T2 V" w/ L& X$ k+ P
W25X16有8192个可编程页,每页256字节。用“页编程指令”每次就可以编程256个字节。用扇区擦除指令每次可以擦除16页,即一个扇区包含16页,用块擦除指令每次可以擦除256页,用整片擦除指令即可以擦除整个芯片。W25X16有512个可擦除扇区或32个可擦除块。 & g6 ?+ a, R% e$ C5 D' b; [8 y
# g/ C1 i! J3 E l3 N9 n
1.W25X16的硬件连线如下:
8 h. A' A# {8 h; B3 |: ^6 [5 M! z6 i, o$ ?1 `+ E
CS: 片选引脚,低电平有效,连接到STM32-PH2管脚 SO: 连接到STM32-PB4管脚(MISO) SI: 连接到STM32-PB5管脚(MOSI) CLK: 连接到STM32-PA5管脚(CLK) WP: 写保护管脚,低电平有效,有效时禁止写入数据。接电源未使用 HOLD: HOLD 引脚可用于暂停通讯,该引脚为低电平时,通讯暂停,未使用
( _% N, s) d2 B+ a! ^$ r' g
2.W25X16控制指令: 我们需要了解如何对FLASH芯片进行读写。FLASH 芯片自定义了很多指令,我们通过控制 STM32利用 SPI总线向 FLASH 芯片发送指令,FLASH芯片收到后就会执行相应的操作。 而这些指令,对主机端(STM32)来说,只是它遵守最基本的 SPI通讯协议发送出的数据,但在设备端(FLASH 芯片)把这些数据解释成不同的意义,所以才成为指令。
( a, a) X# N; R& ~5 @1 L I
' s6 `9 V4 q$ {! b! A
a.读制造商/设备ID(90):该指令通常在调试程序的时候用到,判断SPI通信是否正常。该指令通过主器件拉低/CS片选使能器件开始传输,首先通过DI线传输“90H”指令,接着传输000000H的24位地址(A23-A0),之后从器件会通过DO线返回制造商ID(EFH)和设备ID。(注:SPI为数据交换通信,主器件在发送“90H”指令时也会接收到一个字节FFH,但此数据为无效数据) . l; ] c x+ ~2 Q q S; ]
) E+ T4 o! _' ]9 I
b.写使能命令(06H):在向 FLASH 芯片存储矩阵写入数据前,首先要使能写操作,通过“Write Enable”命令即可写使能。
) ?# E& b% ~: A5 C- j9 ]# I
6 A+ P* e0 a( L' { F6 O
c.扇区擦除(20H):由于 FLASH 存储器的特性决定了它只能把原来为“1”的数据位改写成“0”,而原来为“0”的数据位不能直接改写为“1”。所以这里涉及到数据“擦除”的概念。
! y* b7 |4 g5 T4 b$ J
在写入前,必须要对目标存储矩阵进行擦除操作,把矩阵中的数据位擦除为“1”,在数据写入的时候,如果要存储数据“1”,那就不修改存储矩阵 ,在要存储数据“0”时,才更改该位。
0 `7 r2 e% {# c+ X3 ]
) v: S; D) H1 ?) N+ c4 O
d.读状态寄存器(05H):FLASH 芯片向内部存储矩阵写入数据需要消耗一定的时间,并不是在总线通讯结束的一瞬间完成的,所以在写操作后需要确认FLASH芯片“空闲”。我们只需要读取FLASH芯片内部的状态寄存器SRP的S0即可(当这个位为“1”时,表明 FLASH芯片处于忙碌状态,它可能正在对内部的存储矩阵进行“擦除”或“数据写入”的操作)
# c: Q( V. r3 S1 q* j6 x . j+ Z' u/ K7 K# j0 _) Q; [
" x! _( l, ~5 o
e.读数据(03H):读数据指令可从存储器依次一个或多个数据字节,该指令通过主器件拉低/CS电平使能设备开始传输,然后传输“03H”指令,接着通过DI管脚传输24位芯片存储地址,从器件接到地址后,寻址存储器中的数据通过DO引脚输出。每传输一个字节地址自动递增,所以只要时钟继续传输,可以不断读取存储器中的数据。 - y) o9 }. ^. F5 D5 K; z9 F2 i
; F6 b" x0 F3 B) E& p; L' e
f.写数据——页编程(02H):页编程指令可以在已擦除的存储单元中写入256个字节。该指令先拉低/CS引脚电平,接着传输“02H”指令和24位地址。后面接着传输至少一个数据字节,最多256字节。 # g0 `' h" a; z% z; ?- n- O
注:当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。 8 T. N' K3 z+ v1 F8 d3 s- g
* [1 w# q5 B! H, q) c E
STM32 SPI_FLASH基本配置和操作
) a( X8 i! a( w
( _( o7 e; w! W
根据如下的硬件连线图进行配置 " y9 E9 S8 J. ^
* b( Y. n' i5 l2 G \
步骤: 1.使能时钟RCC - L. q" y- ]6 K6 ^( u/ `" X
2.使能SPI1,配置相应管脚 5 a& n: h9 F7 O% d1 ^" @
( z# o6 E* L9 R
* v9 r$ L5 t7 h/ I: S, m8 u
3.配置SPI协议 9 K+ Y% Q2 F3 ?% r- B, d2 j; Z
: Y0 @$ d: e$ L) @$ D9 D1 L1 [& G( r k$ {! e# R1 m
4.编码 - //main.c3 a0 z4 L- } B# }: T6 N; }% Z' w
- 0 R3 y. j1 G0 o9 o4 F! M
- #include "w25x16.h"
; |8 A+ l: k4 w+ X' d$ m, f5 b0 X. A
4 w- s6 u- F" n) F) p- uint8_t RD_Buffer[5000] = {0};2 Q6 n+ i. G( s- Y9 }" w- `
- uint8_t WR_Buffer[5000] = "SPI FLASH WRITE TEST\n";
% V* t2 U7 C# o/ A/ I8 S9 O - $ P% {* K9 F8 Q. t
- int main(){5 L* @* Y% W: e n% K) H
- uint16_t FLASH_ID = 0;
, V% J3 n8 m) n* u# M; @3 V Z4 @ - uint32_t i;, M N' v2 y' ?* T- F1 X# R
- 7 u! r( I! P& j2 H8 Z
- MX_GPIO_Init();
' d+ x4 O) _2 J: _9 M8 q- m - MX_SPI1_Init();, k2 `# q; D4 Z' T7 F4 N
- FLASH_ID = sFLASH_ReadID();
$ m. H- m, x1 t3 f1 J- ~ -
0 Z# c( ~( K/ f8 j8 A - /******测试擦除******/9 c) S# ^. Z( I2 J- d5 T
- sFLASH_EraseSector(4096*0);
5 W9 R- V2 Q0 y+ I% M5 D - //sFLASH_EraseSector(4096*1);
( G; f" s) u" ^$ S f$ S( E - sFLASH_ReadBuffer(RD_Buffer,0,4096);
9 K2 u/ Y/ }, L - printf("读数据开始\n");8 v1 X( K# B- @7 A& K U
- for(i=0; i<4096; i++)
3 A2 s/ V$ v. v' B; ^0 t. f - {
% E' X+ H0 p( q- f - printf("%x ",RD_Buffer[i]);( }: J* W- H$ K* c. e& J! A
- }4 W. T, B+ w: r; F. p
- printf("读数据结束\n");
6 I3 c0 L l, T6 |) V* Z; R8 c/ U+ ~3 J* \8 F
- G# i" w y2 E9 ~) Y7 x* u- s- : w' }, R) ^ N. B3 Y3 y+ r! ?
- /******测试写操作1*****/+ g9 Z! p0 e3 |
- //写之前都需要擦除扇区7 U/ ^' |# a( G; P) x4 }. w
- sFLASH_EraseSector(4096*0);: t) A& y5 P$ J- `+ E
- sFLASH_WritePage(WR_Buffer,0, 20);
1 I6 P1 r" g( C5 Y# s - sFLASH_ReadBuffer(RD_Buffer,0,20);
* ?1 \2 R9 L7 F2 U3 g - printf("READ DATA: %s\n",RD_Buffer);& Y0 `5 i5 K- F2 U, g
-
6 F, {( l, f0 a3 r H - /******测试写操作2*****/
/ q7 u. L. W, N( x7 s7 O: l$ m - //写之前都需要擦除扇区
0 f& d0 a8 y5 m$ B% F - sFLASH_EraseSector(4096*0);/ x# y& Z) F( e9 {( e
- sFLASH_EraseSector(4096*1);
; }8 J# g$ ^" v$ l7 [* a: ? - for(i=0; i<4096; i++)
, n0 \1 M3 h6 k! k - {
5 C* U, L8 ^: `5 e- @$ F! u - WR_Buffer[i] = 0x55;, z2 g+ U6 ?; l5 g; [! b
- }( |) U6 L. r3 o* D" A1 k; B9 j
- sFLASH_WriteBuffer(WR_Buffer,4090, 1000);
& A9 U% |- Y m# F) ?+ D' a - sFLASH_ReadBuffer(RD_Buffer,4090,1000);
$ K4 K) H! R, `& @. B# A ^ - for(i=0; i<1000; i++)
4 c7 G7 y0 r. V9 G - {
4 a$ i' t, ~, w9 @7 N: a - printf("%x ",RD_Buffer[i]);
3 ^2 `) q( b+ v9 s - }: y" T) E/ }$ f7 ~( R
- /*****************/1 F; M5 f- V( W6 P
- while(){}
- m3 w% i$ ?4 J6 k c' f& }9 X - }
复制代码- //w25x16.h% Y# k2 T u$ \6 t
- 0 A6 m# S8 q5 x" r. p
- #ifndef __W25X16_H1 J+ X3 b) G+ @1 Q2 e
- #define __W25X16_H
2 g( W) M$ o$ Q) j - ! h1 Q8 u; q0 j6 ~1 b9 T
- #include "stm32f4xx_hal.h"; \2 J0 D) L9 F8 p# P; ]
- ( T3 h3 e, L8 u. E2 E4 v+ ~
- //使用宏定义芯片指令
6 m0 C( g ?9 |& F$ X. m: N! | - #define W25X_ManufactDeviceID 0x90 /* Read identification */0 J( D! B9 G* f
- #define sFLASH_CMD_WREN 0x06/* Write enable instruction */! [! I& O* }$ ^ T% b: V$ O
- #define sFLASH_CMD_RDSR 0x05/* Read Status Register instruction */' R9 l$ z3 j" Z! L
- #define sFLASH_CMD_SE 0x20/* Sector Erase instruction */
' g4 P2 l$ f9 X ~ - #define sFLASH_CMD_WRITE 0x02 /* Write to Memory instruction */9 K9 ?$ q; m8 U/ K
- #define sFLASH_CMD_READ 0x03/* Read from Memory instruction */
+ f5 h, d( z q' U, |2 p) Q0 x6 y! L: M - #define sFLASH_DUMMY_BYTE 0x00 //空字节,用于只读传回来的数据; i" G. B+ w: Y* Y& z1 k' F
- #define sFLASH_BUSY_FLAG 0x01- [( N' ~) g# Q a s5 \# C
- #define sFLASH_SPI_PAGESIZE 0x100
0 l- x, J" G) I" I
/ |) k# g) `7 z- /* 选中芯片,拉低信号 */
# W- E1 F1 ^2 d - #define sFLASH_CS_LOW() HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_RESET)! p/ w4 r1 U! J& g
- /* 释放芯片,拉高信号 */
# M/ ?& T( C" n ?+ |5 W! A. _* Y - #define sFLASH_CS_HIGH() HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_SET)
5 `9 ^5 A q) m. n6 Q' U - / i# E0 i9 Q; E" _! o" P1 l1 D; A2 {
- //定义函数! b. d1 k4 n8 \. u
- uint8_t sFLASH_SendByte(uint8_t byte);* ^( Y& C% l4 q' B4 _- j
- uint16_t sFLASH_ReadID(void);0 W1 p% S$ x8 j# w! l( A1 k. X' ]
- void sFLASH_EraseSector(uint32_t SectorAddr);
" b9 l, _7 U, N$ } - void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);
6 l( K, [7 A* a. {0 q - void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);" S& L$ l0 ]1 q5 S/ g
- void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead);( M( M% n% q- {# ^) M* e
- #endif
复制代码
) s# U4 ~: P7 [6 I$ X- //w25x16.c! ^) e$ j! C- X6 f8 B4 K
- ) P8 v1 S6 X) b# d
- #include "w25x16.h"
6 w% x# t6 @9 O
7 Q) q) t% \" W/ m- extern SPI_HandleTypeDef hspi1;7 l( g% Y# R8 w- [ I. K
- - [; D- R+ X% w8 _8 ^0 F4 ?) U( m6 j
- /*读写一个字节函数,因为SPI读和写同时完成*/
- f: m4 m- S* N# m% p - /*发送数据一定会接收到一个数据*/
# h5 K8 I: O6 d. E! |% g, n/ a% w9 V; ? - uint8_t sFLASH_SendByte(uint8_t byte)! d. V( }8 p$ I9 j
- {# {; @2 B/ L+ i$ ?
- uint8_t TX_DATA = byte;
3 n/ j4 {& m1 }! o$ B - uint8_t RX_DATA = 0;
- z; Y; U" ]% N4 T- q" e - HAL_SPI_TransmitReceive(&hspi1, &TX_DATA ,&RX_DATA , 1, 1000);
, e) B2 F5 [! f! [5 r6 w - return RX_DATA;
7 A7 m9 V2 w9 ?) r+ w0 J# {6 s0 d - }0 D3 b+ i$ Q2 L6 s# @
/ f! f) {' ?8 |2 `# f4 O- /*等待擦除或者写数据完成*/
# j& X! ?4 s6 b( r( X - void sFLASH_WaitForEnd(void)
+ {$ l; J* D+ |. f) _ I' ~ e - {
- x# `5 @: [0 j - uint8_t sr_value = 0;
) n; {2 v% z1 @6 Y/ e! Z - sFLASH_CS_LOW();
* G, D- Y K/ { - sFLASH_SendByte(sFLASH_CMD_RDSR);
' K, g- k+ a R1 @+ n" t! [ - //读S0的值,为1表示忙碌,为0表示停止 B% }$ ?; L: s7 d5 P
- do{
) U0 O& K, T* U$ H2 C - //发一个空字节,得到S0的值 2 J4 @( X3 A5 @; X
- sr_value = sFLASH_SendByte(sFLASH_DUMMY_BYTE);6 }$ n% j2 K, ] V& T( P
- }while( sr_value & sFLASH_BUSY_FLAG);' h3 f$ O. u5 w$ h
" M Y/ P! x6 ? X6 \- sFLASH_CS_HIGH();2 Z5 q C: @4 D1 }8 w1 N
- }6 x Q+ S1 F. e U9 d0 O& ]
9 z {+ n7 w7 _- f6 W2 e0 k- void sFLASH_WriteEnable(void)% u' z. b0 }1 l* ? Y
- {
' e' T% B0 p' _ d' w - sFLASH_CS_LOW();5 }/ q; ^7 M7 n/ X3 K! G
- - h( V, Y H! G" S4 X
- sFLASH_SendByte(sFLASH_CMD_WREN);2 ~: V) Q, U2 ?; j9 B* ~ o: D2 h
- & t6 H+ Z2 \- G. g& A) k E1 h8 {
- sFLASH_CS_HIGH();2 D* j* A" x" J, `$ }
- }8 H& m, D0 l4 `0 J; ^. m
8 X, S: z8 Y. q- L5 ]( m' I
& x# ?+ q2 R) O! a s" {: b- /*读设备ID*/ ( d8 D$ B2 E/ x
- uint16_t sFLASH_ReadID(void)
1 f8 Y/ \; b6 }# J: W2 c1 e+ O - {
5 u4 x. a3 u3 K; V - uint16_t FLASH_ID;
8 ^% L& n1 r3 |: [& P6 ? - uint8_t temp0,temp1;
* b2 V3 V. E+ K* {2 A7 Z -
, C A( e0 A' V! ]& j - : E8 j5 y+ Z/ M+ w
- sFLASH_CS_LOW();
9 z% p; U4 M X5 u. e -
0 h( T( m6 l# X. f' c4 S - sFLASH_SendByte(W25X_ManufactDeviceID);$ f, z9 y/ ^) ?; D
- //读设备指令后要发24位地址,所以要发三次
- ~' H$ o( n) j2 l5 |0 d+ Y0 X - sFLASH_SendByte(sFLASH_DUMMY_BYTE);
3 i: c6 {0 ~5 K+ @ i% @* G - sFLASH_SendByte(sFLASH_DUMMY_BYTE);
) j5 r" o4 H _+ K; S - sFLASH_SendByte(sFLASH_DUMMY_BYTE);3 u/ e! ` m! X3 z( ]7 R" Z
- //制造商ID
( _; @9 T) x. Z |) ]7 C9 k - temp0 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
' O! e4 \! X) j; A - //设备商ID
$ l6 C, r* W7 T7 d, N, a3 R - temp1 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
3 f6 `" J- q6 s5 o+ w$ z6 `$ B - 9 T1 y/ l2 C) X! ^, w
- sFLASH_CS_HIGH();* Z% }$ R& X8 s5 a2 H# s; M0 L
-
9 H+ p1 P% k. ^0 H) x - FLASH_ID = (temp0 << 8) | temp1;- L0 z k- f8 D1 j- L, f
-
- d2 g# ?/ G- I/ a - return FLASH_ID;! F; O* s- a( j; A3 k; E n
- }- u$ k& T6 Y$ ]) K' u
& d7 O& O/ P4 E, e* R5 ~- //擦除扇区,擦除为1,因为只能由1变为0 ,不能0变1
) c' \5 S' [9 S. O - void sFLASH_EraseSector(uint32_t SectorAddr)
5 @# H4 e) n1 q - {9 w% c% d* f( {# r4 ]( R. O$ Y3 x
- //SectorAddr表示擦除第几个扇区0 p# I# M. V* q, D9 X' ^' q
- sFLASH_WriteEnable(); //开启写使能
( c* g4 T9 d# L! J4 o" C6 O, ^ - sFLASH_CS_LOW();//拉低,片选
5 x+ F4 H' Z4 Y) z! R: s7 w: Z - //擦除命令% O# Q2 u' {" v6 K: ~1 e- p" ~( R' M
- sFLASH_SendByte(sFLASH_CMD_SE);
# Q3 @+ f- J7 B) V/ o2 t7 }% q - //传24位地址; J4 Z% q2 S& q: Q+ a( l
- //传送高8位,将中8位和低8位一共16位移出去,得到高8位
! `) a. |1 X5 o) n, Z% ? - sFLASH_SendByte( (SectorAddr>>16) & 0xff); # Z+ r# M: m# [! f" M
- sFLASH_SendByte( (SectorAddr>>8) & 0xff); //传送中8位
! ]0 O' E9 z* f: i5 s% q - sFLASH_SendByte( (SectorAddr>>0) & 0xff); //传送低8位3 f" d6 s; L$ l5 U
- / w$ C9 k% _ z( P( y
- sFLASH_CS_HIGH();
* z4 I1 y5 H) k$ } - ; E) H. E3 n0 p* Y0 T3 _; d) @, Z
- /*读状态寄存器,等待擦除完成*/
7 w3 q3 y# L: q0 L' h, D9 Q7 s7 | - sFLASH_WaitForEnd();& t( o2 [% d8 m
- }
+ t! A5 o8 t0 [1 \4 i- J3 A. }
7 Z7 P. L5 j/ ]- //读数据
4 A) ^4 P! p! F1 R1 |: V - //读命令和读地址发送后,芯片内部会自动不断递增读数据 l; `# e9 O" d
- void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead)5 Q6 k9 y: S2 j
- {
- L$ S7 E6 y( G9 H - sFLASH_CS_LOW();3 P+ q4 w% [ ~% J( @
- - u% X/ Y: x3 Y' T; l
- sFLASH_SendByte(sFLASH_CMD_READ);( v- u3 a5 g. Z( s! p* }
- sFLASH_SendByte( (ReadAddr>>16) & 0xff); //传送高8位3 o5 E& H; T9 b4 e+ J
- sFLASH_SendByte( (ReadAddr>>8) & 0xff); //传送中8位
; y8 P) n8 Q2 A. I- E - sFLASH_SendByte( (ReadAddr>>0) & 0xff); //传送低8位 M0 }7 b: l) r# B6 [
- ' S2 X% d5 n7 k4 H: O& U* X& Q
- while(NumByteToRead--)6 j' ?) Q+ p$ I. F/ ^# K0 [' }
- {
) a0 O! A5 Q: X. n- R - * pBuffer = sFLASH_SendByte(sFLASH_DUMMY_BYTE);# L N; a. [$ w7 T$ H, J% N
- pBuffer++;4 S8 L m7 Q7 |+ ]! e6 P' |3 [
- }
. [" [5 A7 O4 K5 y( D -
3 b5 c) |6 a7 [1 x p6 {0 T! d - sFLASH_CS_HIGH();1 l" J; W( k9 H2 _2 R# l0 Q) g
- }
- L, f; V$ L1 t6 ^8 w6 V! P7 w - . Z+ v% q0 w5 L% B2 I. F q
- //写一页最多只能写256个字节,一个扇区16页,一个块16个扇区 . r" S9 v& B4 x+ y* U
- void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
" \" k4 @, \- w3 H# E4 } - {
# Y) h! K9 o) r2 t6 M' ^ - if(NumByteToWrite > sFLASH_SPI_PAGESIZE )
9 F- k% V7 V5 j) r9 g. g - {! `- M2 }" H0 H5 w
- NumByteToWrite = sFLASH_SPI_PAGESIZE;
" y4 d/ C% ~) U, u& K -
5 v6 t+ @$ |9 } - printf("写数据量过大,超过一页大小\n");
% ^! L! a9 [7 L6 K( Z - }9 u! V1 `* Q3 ? n7 N8 f3 v) n
-
) L# |( q/ l# ]3 u4 \8 T5 v - sFLASH_WriteEnable(); //开启写使能" N7 z( j; @( X! a6 J. S6 l
-
! Q& S( ? q8 Y/ Y! Z - sFLASH_CS_LOW();& P5 x& l' S7 o8 _: Y9 f
- 6 ?8 G. w) ^# N; H) j/ i2 ]
- sFLASH_SendByte(sFLASH_CMD_WRITE);+ x1 ~3 C6 y! z' y
- sFLASH_SendByte( (WriteAddr>>16) & 0xff); //传送高8位
. N0 T8 w7 Z; a: ^0 ?7 g - sFLASH_SendByte( (WriteAddr>>8) & 0xff); //传送中8位
G: k1 i& O/ Q; \$ r- G - sFLASH_SendByte( (WriteAddr>>0) & 0xff); //传送低8位
5 R+ ~; \( E4 o6 A -
* i9 i" `: O) }! D: J. p - while(NumByteToWrite--)$ X& F l! h7 g0 l
- {9 y, M9 ?& h! A& W4 [
- sFLASH_SendByte(* pBuffer);3 y# r' S: V4 b0 V) R* Y8 ?; _
- pBuffer++;
6 ~! ~+ R5 T6 ]4 k$ I+ U. G) l - }
. @2 o) ], Y! a. b - . a" ?( z: [( Q! h4 T! F" e) `2 y
- sFLASH_CS_HIGH();
2 u$ R4 {( V% H6 T; V: V -
0 T- m& ?; a5 p* W: c; b3 O - /*擦除和写数据都涉及到写动作,一定要等待完成*/, b! N W/ H- ]! D8 b. U
- sFLASH_WaitForEnd();
; n! R+ r7 g5 g# B/ o5 N* o - }
) x' _$ ~- F2 F* Q) ^) ~% ] - ! l+ {+ t2 j( Y) h1 O5 P
- //写任意地址、任意长度9 m: i9 R, a% K |, Y+ b* t5 V" \( ]
- void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
8 @& n& v; k; G. \* ` - {; V( E J- A! d
- uint16_t NumOfPage, NumOfBytes, count, offset;
& v' Y) u9 O( v1 v3 v - //求WriteAddr在某一页的位置 & V8 X+ `( }0 }. Q. ?
- offset = WriteAddr % sFLASH_SPI_PAGESIZE;
; w# I7 Y1 z5 x4 y8 e% m5 L - //求某一页剩余的大小 0 V: @/ z- A5 I# O7 [) I
- count = sFLASH_SPI_PAGESIZE - offset;
; A8 P/ k4 p7 J# V; v -
j) E0 F& _3 f - /*处理页不对齐的情况,防止页内覆盖*/
. @. ]& ?- O; w - //先把某一页剩下的部分写掉,之后的就能新页的起始处开始写 /*offset有值表示需要页对齐,如果要写的字节数小于某一页剩余的部分,那就无需对齐*/ 5 M) C' _. [: r6 y8 [7 {1 P
- /*这两个条件必须同时满足*/
" A1 w* N6 ~" L( X; @ - if(offset && (NumByteToWrite > count ))
& q8 q7 L' i* X; A |8 j - {( w/ G; v5 B( `2 T& u5 f( ?
- sFLASH_WritePage(pBuffer,WriteAddr,count);
- ?% f$ g9 ]. Y+ D& S7 b; C' e - NumByteToWrite -= count;//去掉已经写了的,从新页开始
8 _* I4 d3 R* d4 U2 K3 J$ G - pBuffer += count; ]. ?* x/ ^! T- o
- WriteAddr += count;# X: Q+ q: s% V! @. @
- }8 e- I' X2 H) t3 J" L
- . f \4 w; y4 R( K+ `& w" A5 N
- /*最多可分多少页*/1 z1 O, K1 \0 ~8 R. }: G& g
- NumOfPage = NumByteToWrite / sFLASH_SPI_PAGESIZE;7 H& C' }8 `- m, o% S- C3 C
- /*剩余多少字节*/7 q! L" L7 L$ P( q+ V
- NumOfBytes = NumByteToWrite % sFLASH_SPI_PAGESIZE;, Z, X0 i2 P* i; u9 Q3 S
-
) W# @; \% @! i2 D; F: R8 J" j - if(NumOfPage)' X. a: G" G2 c. t
- {$ F' i0 b! s" x, O/ }4 x" v
- while(NumOfPage--)
1 E! v9 G p5 N2 F2 H. g p - {
M) q2 T- J7 W* F. `' a/ @, i7 y9 O - //每一页都发起页编程
0 e6 I% @& c$ U" M! Y( T6 O - sFLASH_WritePage(pBuffer,WriteAddr,sFLASH_SPI_PAGESIZE);
6 Q, I+ W4 Q: x - pBuffer += sFLASH_SPI_PAGESIZE;- t! h$ p7 N; I
- WriteAddr += sFLASH_SPI_PAGESIZE;4 \6 l% I8 x3 w# w
- }7 \8 E" _4 w" a/ L
- }
7 S8 W/ Q* v2 @ F) j5 {8 n -
/ h* {: j! t! K" \4 K - if(NumOfBytes)
% V3 a v& H% |" a/ C t' b& b, q - {$ m( A3 L! F# v! F. z
- sFLASH_WritePage(pBuffer,WriteAddr,NumOfBytes);+ B" G7 z& K3 H! i: f) C0 q8 C: m
- }
1 S) A2 g, x& I - }
复制代码 * G% F! m2 y" J% M% U! c" k
9 v/ o. v( }; y$ C* R+ i C
1.无法突破页限制,超过一页需要重新发起页编程信号。另外如果要写的数据大于剩余一页剩余的容量,那么超出的数据会写到当前页起始地址出。例如,初始输入的写地址为200,而要写的数据大小为100,那么要写的前56个字节会从地址200开始依次写入,剩下的44个字节会从当前页的0地址开始依次写入,这很有可能覆盖之前的数据。 8 X- I) r+ ?0 k
2.无法突破扇区的限制,当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。 ' j6 O3 {1 h# b; |
转载自: 骆驼听海
) Y) H! u: R% s# i8 i5 [如有侵权请联系删除- h: L2 }( [/ V+ I- ~, _) ]% E
' ^' H! k6 c/ L. ~9 ` |