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