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