你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

基于STM32(ARM)开发经验分享

[复制链接]
攻城狮Melo 发布时间:2023-4-12 16:18
& m2 M+ u# o$ `7 _- l
微信图片_20230412161833.jpg
+ }- k% i( h$ `. Y) ?1 _2 a
本系列为ARM开发的进阶知识,讲解与ARM芯片进行通信的大部分高级外设的原理及使用方法,不局限于ARM芯片内部,实操性较强且晦涩,需要一定的读者具备一定代码阅读和理解能力。

0 p8 V- B3 J' c
  • STM32 I2C总线通信专题讲解
    % u; L- r9 r+ ^5 p
1 ]. g+ a# N; m! b6 V+ z
  • 总线介绍:I2C(Inter-Integrated Circuit)总线(也称IIC或I2C)是由PHILIPS公司开发的两线式串行总线(单双工),用于连接微控制器及其外围设备,在这两根线上可以挂很多设备,同一时刻只能有一个节点处于主机模式,其他节点处于从机模式,总线上数据的传送都由主机发起。I2C总线没有片选信号线,所以需要通过协议来找到对应操作的芯片。是微电子通信控制领域广泛采用的一种总线标准。它是同步通信的一种特殊形式,具有接口线少,控制方式简单,期间封装形式少,通信速率高等优点。
    / c& o/ w) Z% X6 p2 f- C% \

    & x0 B. j1 a" w# Z

! ~. b. e0 g- g8 U( L" x
  • 总线特征:
    . F" o5 v* U% x# Q! \, Y
1.两条总线线路:一条串行数据SDA,一条串行时钟线SCL(主从设备使用同一时钟,属于同步通信)来完成数据的传输及外围器件的扩展
# T% ?& ~" }1 \6 V
2.I2C总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址,通常是7位,有时候是10位

; ?5 S, Z/ Z  K1 P
3.I2C总线数据传输速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。在开发配置的时候,最好检查从设备的传输速率从而对主设备(一般是MCU)进行相应的配置。一般通过I2C总线接口可编程时钟来实现传输速率的调整,同时也跟所接的上拉电阻的阻值有关。
" J: H6 n( c7 c" H* {2 E
4.I2C总线上的主设备与从设备之间以字节(8位)为单位进行单双工的数据传输。

0 W) o3 C9 r8 n# ^* X! y. a: b4 k+ [
  • 拓扑结构——总线型
    # j# e# X* t2 V2 u
I2C 总线在物理连接上分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成,SCL由主机发出,SCL越快,通讯速率越快。通信原理是通过对SCL和SDA线高低电平时序的控制来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。
: ]* x# P( a: w( B
微信图片_20230412161906.png
- R( ~  O& R$ c7 f
  • I2C总线协议
    ! e! r/ ]: g. r3 T% i1 _
1.I2C协议规定:  总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生。
# Q5 v( ^: I& B: y. L& Q6 J
2.空闲状态:SCL和SDA都保持着高电平。

1 c8 ?0 u' w8 d, F
3.起始信号:  当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件,所有的从设备都能感受到这个跳变,做好准备等待被选择。
# w+ H- S; l. |1 A7 y9 U+ ?
4.结束信号:当SCL为高而SDA由低到高的跳变,表示产生一个 停止条件

  L! T! H2 d( x- l, M: b4 B- J
微信图片_20230412161928.png

1 I' h4 U1 _/ Z8 F2 K! g8 a* ^$ k/ n
5.数据传输:数据传输以字节为单位 , 主设备在SCL线上产生每个时钟脉冲的过程中将在SDA线上传输一个数据位,数据在时钟的高电平被采样这时候采集到是1就是1,是0就是0,所以在传输数据时,当时钟处于高电平时一定要保持稳定,时钟处于低电平时可以变换数据。(高电平采样,低电平变换)一个字节按数据位从高位到低位的顺序进行传输。主设备在传输有效数据之前 要先指定从设备的地址,一般为7位,然后再发生数据传输的方向位, 0表示主设备向从设备写数据,1表示主设备向从设备读数据。主从设备以字节为单位(8位)进行数据传输,开始传输数据时把从设备地址加上方向位组成一个8位的字节进行发送并接收一个应答。

" `5 r* X, |- D/ T/ P  s
6.应答信号:接收数据的器件在接收到 8bit 数据后,向发送数据的器件发出低电平的应答信号,表示已收到数据。这个信号可以是主控器件发出,也可以是从动器件发出。总之,由接收数据的器件发出。

# Q; ^4 ~. D2 }/ t6 }$ h2 p3 G. x3 n
微信图片_20230412161941.png

5 D! ]$ A- f" d5 d; _) z) M3 `: X
a.主设备向从设备写数据:
4 u# z& v6 V$ c
微信图片_20230412161956.png
/ {! v1 i  l, p% w* E( f
b.主设备读从设备的数据:
6 M; C& g5 Q( b
1.png

  @" a: r2 z, O7 T9 {8 b) x/ P  L
c.主设备读从设备的某个寄存器:读设备的寄存器首先应该对该设备发送写命令,很多设备都可以看成是一段内存,所以写命令写给从设备,指明要读取哪个地址(寄存器)的数据,接下来才是真正的读数据。不同的从设备是由区别的,在驱动I2C从设备时应当查明设备的时序图,又怎样的要求,不同的时序对应了不同的命令。
) Z" G5 F: N  a6 }3 J" U1 {
2.png

: e  ^7 |/ y' P# |* ]

+ d" |6 w. T4 A! k- Q# |0 ?3 V+ D/ T
  • STM32F4-I2C控制器特性

    " o5 h; g) z6 [7 i/ I
软件模拟I2C时序:由于直接控制 GPIO 引脚电平产生通讯时序时,需要由 CPU 控制每个时刻的引脚状态,所以称之为“软件模拟协议”方式。我们知道,驱动I2C设备只需要两根管脚,即使单片机上没有I2C控制器,根据协议控制每根管脚每一时刻的电平状态,一根模拟数据线,一根模拟时钟线,就可以驱动从设备,相对而言效率低,但是可以实现控制驱动。STM32内部具备专门的I2C控制器,使用时只需对其进行相应的配置即可。

6 q% d9 Z1 x7 `4 r
硬件控制产生I2C时序:STM32 的 I2C 片上外设专门负责实现 I2C 通讯协议,只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并缓存起来,CPU只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种由硬件外设处理I2C协议的方式减轻了 CPU 的工作,且使软件设计更加简单。
1 W4 X6 |" u& s" q) ~# h
控制器功能:配置主从模式(一般都把STM32当作主机使用,作为从机时应当对其赋一个地址),通过配置其内部的寄存器产生一些中断和错误信号,配置通信速率位标准模式、快速模式、超快速模式等
  h0 h2 L. _( O
STM32芯片有3组I2C外设,可以同时进行3组I2C传输。它们的I2C通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚。

7 O; P) W" Y  J/ M' ~, \7 I4 d
微信图片_20230412162110.png

3 g3 b* i) b4 d3 G+ w, D; S% \
  • EEPROM(AT24CXX)存储芯片介绍
    6 l5 m/ u5 g2 o1 A
一个典型的I2C接口的从设备,专门用于存储数据的芯片。EEPROM (Electrically ErasableProgrammable read only memory),带电可擦可编程只读存储器,一种掉电后数据不丢失的存储芯片。EEPROM可以在电脑上或专用设备上擦除已有信息,重新编程。
) a: v8 D: _" n' O9 \$ `
EEPROM常用来存储一些配置信息,以便系统重新上电的时候加载之,容量不会很高。EEPOM 芯片最常用的通讯方式就是I2C协议。XX表示容量,常用值为01、02、04、16、32、64等,单位Kbit。一般的存储芯片都具有写保护功能,对WP管脚加一个高电平就开启了写保护功能,就无法往芯片内写数据了。在开发中通常将该管脚接地,确保能够写数据
典型24CXX芯片引脚如下:
8 [! ^: C& c8 N) U5 Z" X7 a
微信图片_20230412162134.png
4 D& o0 S7 m7 W# S4 c
例:24C65的设备地址为7位,高4位恒定为1010,低3位取决于A0-A2的电平状态,般主机在读写24CXX都是把设备地址连同读写位组合成一个字节一起发送。
( m8 X2 V# [( M. x% G& V8 J
3.png
% q6 z2 T) ~' s. k" N. q# [$ c3 q
24C65的电气连线如下,根据电气连线可知,A0-A2均接地,因此读地址为1010 0001,即0xA1;写地址为10100000,即0xA0 ,且WP接地,用户随时可向芯片内部写入数据。

3 h2 p% G: J1 `3 C
4.png

* Q7 _5 A6 m' x! ]4 U6 y) Y7 h
24C65写时序:首先发送一个起始信号,接着发送从设备地址以及方向位,收到应答后,向从设备发送要写的存储区域的首地址,24C65的存储地址是16位,先发送高8位,收到应答后再发送低8位,再次收到应答后开始写数据。64Kbit大小位8K字节,需要13位即可表示,所以高3位固定定为0,如下图。
( h! Y) a& t, l8 J4 ]( K+ t) `9 X
这里是BYTE WRITE,一次写一个字节,此芯片还支持PAGE WRITE,一次写一页,也就是8个字节,如果想写更多,可设置一个for循环实现。
; i( K. H% Q6 ^4 K
5.png

( S" Z. q2 F2 C& b
24C65 读时序与写时序基本相同,只不过在读之前要发送再发送重复开始位进行读操作。

6 v, y0 q- X7 M$ ~
6.png

9 b4 w- h3 {1 B. Z% W1 m
  • I2C读写EEPROM实例

    ) O4 p6 K% F* b+ e6 S
0 |) E  u4 V1 |$ J9 \4 p3 ]
由电气原理图可知SCL和SDA分别接入了PB6和PB7管脚,读地址为1010 0001,即0xA1;写地址为10100000,即0xA0

7 {3 n+ w$ c& J+ S
步骤:
1.配置RCC

% h. q* A# ^& c) H$ \* Q- C# ~
2.配置PB6和PB7管脚

/ R8 M5 C) U$ V; b0 ~& @
7.jpg

  g5 ^8 V, [  G. _
3.配置I2C协议参数

0 t. e  c" P! T/ r+ c4 w
8.png
$ j4 g1 e+ S4 r( Z
4.编写代码
  1. 2 h3 y- D, Q5 ?+ e/ T% ~" y! E
  2. //mian.c- e/ w% m3 C$ B) N! J

  3. - \; m$ d% H9 K/ a
  4. #include "main.h"# t1 q8 D+ k1 Q$ A. b
  5. #include "stm32f4xx_hal.h"$ Z6 d3 a' H! ]* u$ b
  6. #include "i2c.h"
    + h2 q$ W5 Q# W3 x+ i; W/ n  q' |
  7. #include "usart.h"
    1 {2 Y# p5 l4 L
  8. #include "gpio.h"
    ! }+ `" c8 X3 F- m5 J* J0 }

  9. 6 e5 ?1 d7 |- r
  10. #define ReadAddr   0xA1
    ) j& }( B5 K: S
  11. #define WriteAddr  0xA0
    9 g1 V( w5 P8 w3 {9 A' p" e
  12. & O) i& J4 A$ E, h) W
  13. uint8_t Wbuf[20] = "EEPROM TEST OK!";
    8 o" k) _% M# y! \3 s* L
  14. uint8_t Rbuf[20] = {0};
    5 u- C& D+ g2 ^; q4 W' L3 E

  15. - ^: w& F$ U. X
  16. /*********  24C65写数据函数*****************************/2 c. s6 L- B! I, b3 k, x

  17. % O( r" Y& X* C% w8 o
  18. void  Eeprom_Write(uint16_t MemAddr, uint8_t *Wbuf, uint16_t len ){
    8 h* x9 @6 d1 E1 e% J, n
  19.         while(len--){- X5 |3 H3 z  T! q. `( R0 G
  20.         //I2C_MEMADD_SIZE_16BIT表示存储单元大小$ ~- W) k* z  O
  21.         //默认为两个参数,分别是I2C_MEMADD_SIZE_16BIT和I2C_MEMADD_SIZE_8BIT7 u% A. H$ `( c8 a* y0 Z) [" r
  22.         //由于24C65的存储地址是16位的
    - d( k$ a8 H+ M( K9 k( N, _
  23.         //所以我们选择I2C_MEMADD_SIZE_16BIT
    & s# _0 b+ `6 K8 U  p& ~
  24.         //1表示一次写一个字节
    - s$ C& P0 x: j1 U0 `
  25.                 while(HAL_I2C_Mem_Write(&hi2c1, WriteAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Wbuf, 1, 100) != HAL_OK){};
    % C* P' I; }) N; L. X$ G! a
  26. MemAddr++;. z9 q5 H0 k* X5 K0 m' o' A) H+ z
  27. Wbuf++;& A8 \- C  s: L% ?) {3 I
  28. }
    4 q" c* H( _  ^" f$ D
  29. }" t  H- z7 Y  f6 r8 u* E

  30. 0 i$ x: @, c) _
  31. ) z" [$ E3 c1 [6 u) m. K6 \( J
  32. /*********  24C65读数据函数*****************************/
    ! @' Y9 U9 F3 s* c

  33. 8 x. ?! T& E+ r5 w9 A& s( j
  34. void  Eeprom_Read(uint16_t MemAddr, uint8_t *Rbuf, uint16_t len ){
      L' s! x* B5 U# n
  35.         //可以连续读,所以无需循环
    2 j/ W$ ^3 [  C+ c
  36.         while(HAL_I2C_Mem_Read(&hi2c1, ReadAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Rbuf, len, 100) != HAL_OK );/ I9 R0 D5 m. y6 v, g' [
  37. }
    # E% r5 e: |; _( @6 G2 B0 e6 n
  38. int mian(){# ]( \) B0 x8 N. i
  39.       MX_GPIO_Init();
    5 d. a8 I  F& y/ U6 L8 {  X  Y
  40.       MX_I2C1_Init();
    5 m7 x* q0 X" ~" s
  41.       MX_USART1_UART_Init();3 j* V- k' J' R0 l
  42.       printf("this is i2c eeprom test\n");- g# k2 J# @) w# @% G

  43. 6 p" e, I4 l( ~# z) \# d
  44.       Eeprom_Write(0, Wbuf, sizeof(Wbuf) );, U4 P& O) L0 a, l  L, e* J
  45.       HAL_Delay(500);" g9 p5 b' P! Q6 V7 \8 W4 K! N
  46.       Eeprom_Read(0 , Rbuf, sizeof(Rbuf));
    . `% j$ u! f4 p% D
  47.       printf("READ:  %s\n", Rbuf);
    0 T( U8 F3 e2 \2 S+ \2 ~
  48. 3 |, K8 Q2 W' k- D
  49.       while(){
    8 G/ {& g( M. y8 x
  50. / }; l8 [3 U' E
  51.       }
    0 @+ I, M! P  A5 `0 H  E0 f
  52. }
复制代码
4 C# [2 @% ]( h
  • STM32 SPI总线通信专题讲解
    ( E& G% i0 X  y( q+ w; N
SPI接口是Motorola 首先提出的全双工三线同步串行外围接口,采用主从模式(Master Slave)架构;支持多slave模式应用,一般仅支持单Master。时钟由Master控制,在时钟移位脉冲下,数据按位传输,是高位在前还是低位在前是可以配置的,配置时根据从设备的通信进行相应配置,一般是高位在前,低位在后(MSB first)。SPI接口有2根单向数据线,为全双工通信,目前应用中的数据速率可达几Mbps的水平。
SPI总线被广泛地使用在FLASH、ADC、LCD等设备与MCU间,要求通讯速率较高的场合。

- b! M- T: H9 \6 G& M/ C. }' B7 e' x
微信图片_20230412162643.png

- R, p7 Q" s) I# X
SPI接口共有4根信号线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。

( e% S3 u# V5 U4 r% J, I( t! C
微信图片_20230412162658.png

0 j4 i$ q5 T, H! h
(1)MOSI:主器件数据输出,从器件数据输入,连接从机的MOSI,与串口不同,串口需要反着连接(Rx-----Tx)
(2)MISO:主器件数据输入,从器件数据输出,连接从机的MISO
(3)SCLK :时钟信号,由主器件产生
(4)/SS:从器件使能信号,由主器件控制(片选),一般情况下为地电平选中设备,高电平释放设备。

# B( D  N3 \2 Q+ a
  • SPI总线协议
    : T+ X3 b" j$ V1 v* I
  C0 B$ p* e! |% T
1.数据交换逻辑:主机和从机都包含一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节发起一次传输。寄存器通过MOSI信号线将字节传送给从机,从机也将自己的移位寄存器中的内容通过MISO信号线返回给主机。这样两个移位寄存器中的内容就被交换了。从机的写操作和读操作时同步完成的,因此SPI成为一个很有效的协议。
) S2 ~. `1 ?0 x9 }3 x
如果主机只想写不想读,只需把数据放在数据寄存器,SPI控制器会自动传给外设,同时忽略掉外设传过来的数据即可;如果主机只想读不想写,主机写给外设一个空字符或者随便写一个数据,外设就会把数据传过来,不管是只读还是只写,主机与外设的读和写都h会发生且同时进行。

; ~" r9 Z+ \9 p) M2 ]( t
微信图片_20230412162734.png

7 o/ C0 D% R* E, ~  S8 g1 u+ ^. q
2.起始信号:  NSS信号线由高变低,是SPI通讯的起始信号。

: E  ?; J" T5 o
3.结束信号:NSS信号由低变高,是SPI通讯的停止信号。
: d5 t+ I) }# B2 ^* E6 k6 G
4.数据传输:SPI使用MOSI及MISO信号线来传输数据,使用SCK信号线进行数据同步。MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,按位传输,且数据输入输出是同时进行的。SPI每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制,要么是8位,要么是16位,可以配置。

" A$ G" ?8 X7 D/ c5 C
微信图片_20230412162755.png

, R  y) m  U8 z
  • SPI的4种通信模式

    1 \& ^7 }0 T7 a2 |9 X% D5 o

7 T1 W2 S5 [( _9 S
在SPI操作中,最重要的两项设置就是时钟极性(CPOL)和时钟相位(CPHA)这两项即是主从设备间数据采样的约定方式。由CPOL及CPHA的不同状态,SPI分成了四种模式,主机与从机需要工作在相同的模式下才可以正常通讯,因此通常主机要按照从机支持的模式去设置。同样在配置时一定要弄明白从机支持什么通信模式进行相应的配置。

% Q* i% |' i+ ?" G/ x9 b7 f- ^
13.png
1 I* C" V6 _' [: T2 j! K
1.时钟极性CPOL : 设置时钟空闲时的电平:
a.当CPOL= 0 ,SCK引脚在空闲状态保持低电平;
b.当CPOL= 1 ,SCK引脚在空闲状态保持高电平。

. r7 P: }/ c. E
2.时钟相位CPHA :设置数据采样时的时钟沿:
a.当 CPHA=0时,MOSI或 MISO 数据线上的信号将会在 SCK时钟线的奇数边沿被采样
b.当 CPHA=1时, MOSI或 MISO 数据线上的信号将会在 SCK时钟线的偶数边沿被采样

' K# T& V% G: z( q: G
微信图片_20230412162837.png
2 K$ @9 p) Z3 r5 |# s+ `
11.png
  • STM32F4-SPI控制器特性
    ( W" D1 G1 l% \, g' ~# z  {
12.png

# Z0 x, b  _# n( U4 q  p
1.通讯引脚:
STM32F4芯片最多支持6个SPI外设控制器,它们的SPI通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚,以《STM32F4xx规格书》为准。f407只有SPI1、SPI2、SPI3。
( |) x* z/ S. m  h
微信图片_20230412163014.png

. N$ m. j2 Y6 K% A
其中SPI1、SPI4、SPI5、SPI6是APB2上的设备,最高通信速率达42Mbtis/s,SPI2、SPI3是APB1上的设备,最高通信速率为21Mbits/s。其它功能上没有差异。
9 _6 G! G( l$ ?) _; ]) E. a: @2 _
2.时钟控制逻辑:
SCK线的时钟信号,由波特率发生器根据“控制寄存器CR1”中的BR[0:2]位控制,该位是对f pclk 时钟的分频因子,对f pclk 的分频结果就是SCK引脚的输出时钟频率。

  y2 {: z' O7 d0 y/ s  N" ~  `
微信图片_20230412161739.png

& D9 i6 w0 f# b! t. N+ x# p- A
其中的fpclk 频率是指SPI所在的APB总线频率,APB1为fpclk1 ,APB2为fpckl2

" V6 K( p# K/ ~6 |
3.数据控制逻辑:
STM32F4的MOSI及MISO都连接到数据移位寄存器上,数据移位寄存器的数据来源来源于接收缓冲区及发送缓冲区。

& A- w2 s" ?' g9 n
a.通过写SPI的“数据寄存器DR”把数据填充到发送缓冲区中。

3 ], |! k3 L+ ?  g+ ~
b.通过读“数据寄存器DR”,可以获取接收缓冲区中的内容。

' r# F  f  R, D; q7 f6 z' T9 M( u
c.其中数据帧长度可以通过“控制寄存器CR1”的“DFF位”配置成8位及16位模式;配置“LSBFIRST位”可选择MSB先行(高位在前)还是LSB先行(低位在前)。

$ Q0 X! A8 p6 ?/ l
4.整体控制逻辑:
a.整体控制逻辑负责协调整个SPI外设,控制逻辑的工作模式根据“控制寄存器(CR1/CR2)”的参数而改变,基本的控制参数包括前面提到的SPI模式、波特率、LSB先行、主从模式、单双向模式(同时发送和接收、只发送关掉接收、只接收关掉发送)等等。

' s  b* T# R! Q8 |& C
b.在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,只要读取状态寄存器相关的寄存器位,就可以了解SPI的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生SPI中断信号、DMA请求及控制NSS信号线。

. b! Z0 P& a+ s: h' I
c.实际应用中,一般不使用STM32 SPI外设的标准NSS信号线,而是更简单地使用普通的GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号。
  • 串行FLASH_W25X16简介
    ; \& @; L! d. b( _7 a

4 y# M% c/ x6 |( i; m8 t
FLSAH 存储器又称闪存,它与EEPROM都是掉电后数据不丢失的存储器,但FLASH存储器容量普遍大于 EEPROM,现在基本取代了它的地位。我们生活中常用的 U盘、SD卡、SSD 固态硬盘以及我们 STM32 芯片内部用于存储程序的设备,都是 FLASH 类型的存储器。在存储控制上,最主要的区别是FLASH 芯片只能一大片一大片地擦写,而EEPROM可以单个字节擦写。

1 C' b! U$ @0 I& |
W25X16有8192个可编程页,每页256字节。用“页编程指令”每次就可以编程256个字节。用扇区擦除指令每次可以擦除16页,即一个扇区包含16页,用块擦除指令每次可以擦除256页,用整片擦除指令即可以擦除整个芯片。W25X16有512个可擦除扇区或32个可擦除块。

. M4 L7 s% L8 |, a
微信图片_20230412161735.png

: a( G3 m+ L) ~( r) U) }- _
1.W25X16的硬件连线如下:
3 h% |6 ]; @# Q% v& b8 E
微信图片_20230412161643.png

% t6 N' M& z. U4 k+ c. s# j
CS:    片选引脚,低电平有效,连接到STM32-PH2管脚
SO:   连接到STM32-PB4管脚(MISO)
SI:    连接到STM32-PB5管脚(MOSI)
CLK:  连接到STM32-PA5管脚(CLK)
WP:   写保护管脚,低电平有效,有效时禁止写入数据。接电源未使用
HOLD: HOLD 引脚可用于暂停通讯,该引脚为低电平时,通讯暂停,未使用
6 i% G$ X4 _# {; ~; \
2.W25X16控制指令:
我们需要了解如何对FLASH芯片进行读写。FLASH 芯片自定义了很多指令,我们通过控制 STM32利用 SPI总线向 FLASH 芯片发送指令,FLASH芯片收到后就会执行相应的操作。
而这些指令,对主机端(STM32)来说,只是它遵守最基本的 SPI通讯协议发送出的数据,但在设备端(FLASH 芯片)把这些数据解释成不同的意义,所以才成为指令。
9 B( w* L; j2 Q1 d( e9 p) B7 z
微信图片_20230412161719.png
4 n4 E* M9 x- n
a.读制造商/设备ID(90):该指令通常在调试程序的时候用到,判断SPI通信是否正常。该指令通过主器件拉低/CS片选使能器件开始传输,首先通过DI线传输“90H”指令,接着传输000000H的24位地址(A23-A0),之后从器件会通过DO线返回制造商ID(EFH)和设备ID。(注:SPI为数据交换通信,主器件在发送“90H”指令时也会接收到一个字节FFH,但此数据为无效数据)
" g9 k0 ^- E& O# v9 Z! Q
微信图片_20230412161715.png

4 c! m& ~6 Y$ t3 H; R
b.写使能命令(06H):在向 FLASH 芯片存储矩阵写入数据前,首先要使能写操作,通过“Write Enable”命令即可写使能。

1 ?8 r$ R( [4 {! B/ t
微信图片_20230412161711.png
' x8 V# L1 ?- D1 o! Q8 ]' T
c.扇区擦除(20H):由于 FLASH 存储器的特性决定了它只能把原来为“1”的数据位改写成“0”,而原来为“0”的数据位不能直接改写为“1”。所以这里涉及到数据“擦除”的概念。

' y# J$ w5 y4 `
在写入前,必须要对目标存储矩阵进行擦除操作,把矩阵中的数据位擦除为“1”,在数据写入的时候,如果要存储数据“1”,那就不修改存储矩阵 ,在要存储数据“0”时,才更改该位。
# C8 L2 b! Y1 a$ n5 O
微信图片_20230412161707.png
微信图片_20230412161704.png
) R6 o' _- c/ N0 Z. u+ K9 g$ X
d.读状态寄存器(05H):FLASH 芯片向内部存储矩阵写入数据需要消耗一定的时间,并不是在总线通讯结束的一瞬间完成的,所以在写操作后需要确认FLASH芯片“空闲”。我们只需要读取FLASH芯片内部的状态寄存器SRP的S0即可(当这个位为“1”时,表明 FLASH芯片处于忙碌状态,它可能正在对内部的存储矩阵进行“擦除”或“数据写入”的操作)
" l. U* X8 E' S$ }$ S6 ?  z
微信图片_20230412161658.png

/ ~2 {7 }/ Y5 Y" U$ k. s
微信图片_20230412161654.png

  j: o/ B" \+ G% t4 ^$ s4 I" A
e.读数据(03H):读数据指令可从存储器依次一个或多个数据字节,该指令通过主器件拉低/CS电平使能设备开始传输,然后传输“03H”指令,接着通过DI管脚传输24位芯片存储地址,从器件接到地址后,寻址存储器中的数据通过DO引脚输出。每传输一个字节地址自动递增,所以只要时钟继续传输,可以不断读取存储器中的数据。

7 b5 [2 ?. ~  b! ?- h+ }; ]4 [
微信图片_20230412161650.png
  l; x+ j8 Y2 g; J+ {
f.写数据——页编程(02H):页编程指令可以在已擦除的存储单元中写入256个字节。该指令先拉低/CS引脚电平,接着传输“02H”指令和24位地址。后面接着传输至少一个数据字节,最多256字节。
$ X9 x- e! |7 ~" v6 N% K
注:当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。

# g- W% V: _/ V
微信图片_20230412161646.png

5 C" U3 e/ _4 _0 x7 u$ X
  • STM32 SPI_FLASH基本配置和操作
    7 V. d0 j! V1 ]7 X( |4 [% l- Q5 Z

0 u( z$ ^% R4 `- i) ?; v! q4 W
根据如下的硬件连线图进行配置

2 p- {7 T3 D$ w
微信图片_20230412161729.png
6 o1 `1 |1 H7 C/ I) F
步骤:
1.使能时钟RCC
/ q; E: p% h3 {
2.使能SPI1,配置相应管脚
5 T. r, g' w3 v
微信图片_20230412161640.png
) ^! G' _/ B1 y& o1 u" E% u' V% u) C
微信图片_20230412161637.png

5 Y! [3 v' D  J. _! r4 ^
3.配置SPI协议

3 e) C2 j6 ~( Q
微信图片_20230412161632.png
/ G* a& X8 e9 q+ s

; \9 p. k1 L, ?3 T
4.编码
  1. //main.c
      V. h' O9 s9 W5 L" G# `4 \

  2. 9 v3 }" `/ s0 r* L5 d6 ?% I, K
  3. #include "w25x16.h"
    / K+ E+ Q1 G0 F* s9 F
  4. 5 S3 R. W  J, ]4 ?5 Z
  5. uint8_t RD_Buffer[5000] = {0};8 t8 M: _1 o  }  N* O& p$ f/ w
  6. uint8_t WR_Buffer[5000] = "SPI FLASH WRITE TEST\n";# T+ Q  Y$ [7 P" w& u

  7. ' J( \+ x; G" k* ]& [# {2 X- q( `7 y& p; M
  8. int main(){
    6 B' ~( W2 A; [7 O
  9.     uint16_t FLASH_ID = 0;
    5 K! \% R/ F' {  T* C
  10.     uint32_t i;
    & r% M$ |' F+ W" o5 U; z; k
  11.     # ~* N5 j4 z, C2 w/ I
  12.     MX_GPIO_Init();+ j# N2 S& K2 }3 q" r, [+ A. l( ]
  13.     MX_SPI1_Init();
    6 Q7 u: K) E1 C; H8 h( N: i
  14.     FLASH_ID = sFLASH_ReadID();
    9 k- B/ N  N4 p
  15.    
    5 i; A) V8 ?( f; N4 x: g1 e& _
  16.     /******测试擦除******/8 @. l% Z0 B6 }3 e2 g
  17.     sFLASH_EraseSector(4096*0);
    5 r. V6 L3 w. a- h* t) w& i
  18.     //sFLASH_EraseSector(4096*1);: w3 t7 D4 y5 U
  19.     sFLASH_ReadBuffer(RD_Buffer,0,4096);6 G2 e& m. y5 U. @& @6 m& y. i
  20.     printf("读数据开始\n");
      w2 x) B7 g2 d, d7 V/ B
  21.     for(i=0; i<4096; i++)/ Q/ ~" X7 V) H: y" z7 }
  22.     {
    - V: p$ A) K) q: D
  23.     printf("%x ",RD_Buffer[i]);
    $ Q2 l) }8 s4 w' c
  24.     }+ W( T. q3 ]1 R2 M
  25.     printf("读数据结束\n");
    # N1 C( q5 o3 p
  26. : ?7 w1 |6 B" F" X
  27. 6 V: C( x* [' J9 A) e
  28.     /******测试写操作1*****/
    + _" L( t( e4 I  _: j: e" _  C
  29.     //写之前都需要擦除扇区& E+ O$ n4 M  Z0 r9 g  i! _
  30.     sFLASH_EraseSector(4096*0);
    2 C/ w& P" C' }( }
  31.     sFLASH_WritePage(WR_Buffer,0, 20);) s" z( x# m; T0 {, S
  32.     sFLASH_ReadBuffer(RD_Buffer,0,20);
    3 h4 l7 V' X5 z7 S. [! C# X
  33.     printf("READ DATA: %s\n",RD_Buffer);
    ( ^, Q! w. q, K9 }: M1 M' g+ T
  34.    
    8 M' b' N8 ?6 N
  35.     /******测试写操作2*****/5 Z2 g# F6 ~& i: r3 k5 s2 c
  36.     //写之前都需要擦除扇区
    ( {% H6 b& C; n6 R6 l& S
  37.     sFLASH_EraseSector(4096*0);
    7 L  V0 S9 Y4 {/ l' C+ w
  38.     sFLASH_EraseSector(4096*1);                            7 y- I! d+ R  s6 G" k% q
  39.     for(i=0; i<4096; i++)
    8 O1 l# i4 }2 d/ }. `1 }0 Y
  40.     {( C; Q+ ]/ A6 x0 F& _+ s
  41.     WR_Buffer[i] = 0x55;9 `. y; Z0 K$ O, y) F; _
  42.     }
    % G' I- _3 O" G! T& |
  43.     sFLASH_WriteBuffer(WR_Buffer,4090, 1000);
    . v6 [% Z7 ~& Z; X
  44.     sFLASH_ReadBuffer(RD_Buffer,4090,1000);
    . n3 _. p# }6 x5 V6 x9 r
  45.     for(i=0; i<1000; i++)
    9 |1 D2 D! ?' J' E4 G
  46.     {( ?/ S4 }+ g9 `) Y" g3 N# w
  47.     printf("%x ",RD_Buffer[i]);
    ; ~1 W$ b! r8 V$ @+ N- R
  48.     }! v/ A. \6 a; `. A* l
  49.     /*****************/
    ! v2 i/ s$ l9 @5 a+ D
  50.     while(){}
    6 [5 F( U$ B/ j& _: R' m
  51. }
复制代码
  1. //w25x16.h
    " h$ }& K& W7 @; E* C$ @( E7 [

  2. . D/ ?8 ^; a7 h4 {, u6 i) Y
  3. #ifndef __W25X16_H5 s# k2 ]$ c- _0 I  `  {7 }
  4. #define __W25X16_H: T2 p+ R9 [- H/ ~
  5. - I7 \- w  W0 l, K; z( I* Y
  6. #include "stm32f4xx_hal.h"* V& p2 t9 `% O3 }5 e# i" `

  7.   c+ q2 P+ `' }: V% h% n
  8. //使用宏定义芯片指令% j4 c9 h$ F) O( g/ o+ L
  9. #define W25X_ManufactDeviceID  0x90  /* Read identification */4 c6 {1 R0 Y6 a/ \0 p1 ~$ w
  10. #define sFLASH_CMD_WREN        0x06/* Write enable instruction */
    $ G# G3 w8 R" v2 C6 P9 _
  11. #define sFLASH_CMD_RDSR        0x05/* Read Status Register instruction  */  w5 R0 m# ^2 D6 u
  12. #define sFLASH_CMD_SE          0x20/* Sector Erase instruction */
    9 o* C+ ~9 `1 v* `9 D: C
  13. #define sFLASH_CMD_WRITE       0x02  /* Write to Memory instruction */; a7 u/ E* x0 f4 p
  14. #define sFLASH_CMD_READ        0x03/* Read from Memory instruction */
    ! J6 H% C# W& w' J! N: K
  15. #define sFLASH_DUMMY_BYTE      0x00 //空字节,用于只读传回来的数据6 M3 V( @5 O) L) x/ K
  16. #define sFLASH_BUSY_FLAG       0x01# i: m# W7 S8 H- G
  17. #define sFLASH_SPI_PAGESIZE    0x100& y! u8 O' a- ?5 I% y) I

  18.   c4 B9 K+ D" B* e
  19. /* 选中芯片,拉低信号 */5 M5 \2 V+ H$ X5 y
  20. #define sFLASH_CS_LOW()       HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_RESET)
    $ Y$ O5 O6 m1 b
  21. /* 释放芯片,拉高信号 */. I+ U" V* D* E: E- a* Q
  22. #define sFLASH_CS_HIGH()      HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_SET)
    2 [/ L; a* W; K* a# U+ b& G# n

  23. ( f9 T0 x+ s( d
  24. //定义函数
    # L, P+ @& ]  w$ k9 E
  25. uint8_t sFLASH_SendByte(uint8_t byte);6 V$ r( N1 h' q, x3 K- A
  26. uint16_t sFLASH_ReadID(void);8 @. p' [: i+ J& F4 n
  27. void sFLASH_EraseSector(uint32_t SectorAddr);
    7 @5 z9 o3 n. y: }
  28. void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);
    8 i3 b6 U3 j' i2 S" j
  29. void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);% V. E- t" Z7 g+ a. u+ U
  30. void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead);. Z- w3 B* X( p. c3 l
  31. #endif
复制代码

  1. ; u1 w: ]5 Q, e+ A$ O2 F; s
  2. //w25x16.c
      ~# _% l  ]! @( ?! b2 ^! d+ R3 N+ `3 d
  3. ' W" S! h& f1 ]) ]
  4. #include "w25x16.h"
    9 U. O3 C, l: j, i# Z2 F3 S
  5. ( L# r8 P1 J4 V, P# {, e3 A" Y
  6. extern SPI_HandleTypeDef hspi1;
    $ j9 S' C' ]/ @" V
  7. 0 @6 Y3 Z$ E8 C8 W* G5 k; x
  8. /*读写一个字节函数,因为SPI读和写同时完成*/
    + l2 E# @2 t; @, K# r1 O
  9. /*发送数据一定会接收到一个数据*/4 ?/ D* B' S: d' h3 X. w6 j, w: [
  10. uint8_t sFLASH_SendByte(uint8_t byte); s8 V) N5 l6 I  L+ [
  11. {& D0 {" y/ e4 ^# k6 R5 Z
  12.     uint8_t TX_DATA = byte;# v3 p0 V. r5 y" @/ l- y  k
  13.     uint8_t RX_DATA = 0;9 Q) y! ~, K6 _& k9 \( d
  14.     HAL_SPI_TransmitReceive(&hspi1, &TX_DATA ,&RX_DATA , 1, 1000);* J1 F* x: h1 X" r
  15.     return RX_DATA;
    : G! [( I: I, h8 m8 o
  16. }# q) Q: \8 x/ E* P" o

  17. " P3 Q6 W/ P8 k, @2 }
  18. /*等待擦除或者写数据完成*/6 q% f5 F. h) @' R5 `5 M
  19. void sFLASH_WaitForEnd(void)
    & Z# W  `. F/ i
  20. {
    / A/ ?% R: N, @
  21.     uint8_t sr_value = 0;7 N' r* o$ O0 f8 |2 h1 C; l
  22.     sFLASH_CS_LOW();( i4 l5 L% i! a4 Z
  23.     sFLASH_SendByte(sFLASH_CMD_RDSR);
    * M! N8 ]9 J+ }7 Q% o1 d9 Q% G* B
  24.             //读S0的值,为1表示忙碌,为0表示停止          . Z6 Z8 L! x; D
  25.     do{% ]9 {( |( y% @/ E
  26.                 //发一个空字节,得到S0的值            
    ( p* a% _; E; D# v
  27.                 sr_value = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    6 o/ ~" R: g6 _+ C4 x1 L
  28.     }while( sr_value & sFLASH_BUSY_FLAG);
    5 U' v* P* d% \* k
  29. * i8 R* ?4 V9 c5 ?* Y5 [
  30.     sFLASH_CS_HIGH();
    0 @' z8 Z% z1 j1 t8 v2 e
  31. }# o: {& j; v) ?/ N4 T" k$ w* N

  32. 4 _2 ^& y: X' l, `( m
  33. void sFLASH_WriteEnable(void)8 X- q8 d) }8 V6 L  N! y1 s. _& x
  34. {
    ( x0 W6 l. ^1 P3 E
  35.     sFLASH_CS_LOW();& B3 i1 I/ ?' m7 A* ?/ G; d+ m- I
  36.     ! G2 g1 I" m1 P: {& Z- l
  37.     sFLASH_SendByte(sFLASH_CMD_WREN);
    ) _* S- E& D- \6 a/ y
  38.     5 k4 p+ D7 T, ?! {, c4 _+ T7 w
  39.     sFLASH_CS_HIGH();% g. U; e9 s. `; g. n" c
  40. }  \" S  ~5 R& n7 v8 y- l
  41. . K1 a& ?8 R" ~* W$ H4 c0 I
  42. # z( ?3 x1 b+ l! ?5 b# b8 J" D
  43. /*读设备ID*/ & G" R( Y" H+ {. C6 p
  44. uint16_t sFLASH_ReadID(void)# Z$ k$ ?( \: u% `
  45. {
    * |+ l, b6 _" `5 k0 v. A
  46.     uint16_t FLASH_ID;! u  }9 y( x+ _* b
  47.     uint8_t temp0,temp1;) i+ Y! v' m/ C+ C  j: W0 g
  48.    
    4 e2 A  ]6 Q8 L* E4 @" k8 m
  49.    
    $ b' _- w! F$ r4 o( `
  50.     sFLASH_CS_LOW();  S, |6 Y* |/ T! ?' r/ @( f
  51.     / S& g" a% l$ ~5 h) u
  52.     sFLASH_SendByte(W25X_ManufactDeviceID);
    , N5 z2 X7 y* Y6 |4 W
  53.     //读设备指令后要发24位地址,所以要发三次
    . o8 i. u; y+ _! {, A
  54.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);% \) j3 V  |8 R0 ^/ O: h
  55.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);% z  D6 `3 G% N" y
  56.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    1 e1 o. a4 C' [/ U' W
  57.     //制造商ID4 `4 j! ~. {6 G( {! V; c0 j. Z  V
  58.     temp0 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);+ Y! _7 g' a/ m, G6 h1 Z: c5 E
  59.     //设备商ID
    - a6 f! v+ U3 d1 Z0 Y, q" M. p6 D
  60.             temp1 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);" P, d) M  v7 I1 V+ P0 G
  61.     7 @/ G6 x  o% T& S, x3 s
  62.     sFLASH_CS_HIGH();" H8 y  }: X% J; _/ }) E
  63.     4 A# R; p. k& g
  64.     FLASH_ID = (temp0 << 8) | temp1;
    ! `1 p; _1 B  I, g: ?( C
  65.     " Q4 A4 u9 G* u; q4 s' |
  66.     return FLASH_ID;# A7 C' Q: q2 p$ |' v
  67. }
    9 I- i2 S  g  G( c

  68. 9 [5 g6 _3 k" K  ~. V
  69. //擦除扇区,擦除为1,因为只能由1变为0 ,不能0变1
    - g8 ^, x, R( I( w1 s- v
  70. void sFLASH_EraseSector(uint32_t SectorAddr)$ V) n! A2 Y6 u
  71. {( d$ ~0 A' x  k
  72.     //SectorAddr表示擦除第几个扇区
    4 J. Y( y; g! C' i, e
  73.     sFLASH_WriteEnable();  //开启写使能
      g8 l$ i7 w+ \+ ?
  74.     sFLASH_CS_LOW();//拉低,片选- Z! w- N% P; u8 O
  75.     //擦除命令1 ?5 X: z2 p: r. {
  76.     sFLASH_SendByte(sFLASH_CMD_SE);
    , D, F7 p8 @' l( A; _
  77.             //传24位地址# o- X6 s; ?: r( W+ R/ R2 l
  78.             //传送高8位,将中8位和低8位一共16位移出去,得到高8位                     g1 i2 A+ l- ?# m3 ^
  79.     sFLASH_SendByte( (SectorAddr>>16) & 0xff);   
    ' t. {- Q1 o. b  C* |" y
  80.     sFLASH_SendByte( (SectorAddr>>8) & 0xff);    //传送中8位2 d% K. G# T1 H" r
  81.     sFLASH_SendByte( (SectorAddr>>0) & 0xff);    //传送低8位
    8 {) O  t' C- i# A5 N7 z# j
  82.      
    ) B, E* k( l0 d; n) g
  83.     sFLASH_CS_HIGH();- ^- X" A* t1 E! m- l% \
  84.     : A+ I) k0 e/ h
  85.     /*读状态寄存器,等待擦除完成*/
    ' Q% f- j$ B6 m2 o, O2 W
  86.     sFLASH_WaitForEnd();, b9 C. A' V4 l+ k
  87. }
    ; r3 Z8 X# n" |( o
  88. " n& T1 ~+ J2 ]' H2 r
  89. //读数据# Z& c) z3 X  Z( Y2 v
  90. //读命令和读地址发送后,芯片内部会自动不断递增读数据
    ' R( d( L) Y5 {' ]2 @) k
  91. void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead)8 s( S: B1 L* t) l# P8 y& x3 @' D: T
  92. {
    7 `& {8 g- z' q0 q+ N
  93.     sFLASH_CS_LOW();
    / Y" }) q% e  p/ o( ^; M# I* u
  94.    
    : v0 N3 c  D: U, T) \
  95.     sFLASH_SendByte(sFLASH_CMD_READ);
    6 [- i& c& \) _% f3 l6 F- O- \1 c
  96.     sFLASH_SendByte( (ReadAddr>>16) & 0xff);   //传送高8位
    2 u  r, u" [* F$ r4 `( d9 L
  97.     sFLASH_SendByte( (ReadAddr>>8) & 0xff);    //传送中8位
    / \$ i: n- g  J! l& e+ Z) M
  98.     sFLASH_SendByte( (ReadAddr>>0) & 0xff);    //传送低8位! d  U! W0 p6 \& ]8 a" s
  99.    
    - C( R# l3 f2 B' o% ^
  100.     while(NumByteToRead--); F* Z4 o- J, O6 \6 }( e7 b; @
  101.     {9 j# F# G/ [4 B
  102.     * pBuffer = sFLASH_SendByte(sFLASH_DUMMY_BYTE);9 ^0 d+ U1 {! P) i0 G9 @3 a8 `
  103.     pBuffer++;% Q2 B2 r9 i$ q6 S7 N$ \
  104.     }; L: u9 _9 ~* T6 o4 w: @
  105.    
    * \# ~6 T# U2 I2 x# b6 g0 {
  106.     sFLASH_CS_HIGH();
    ' T! K2 o# r3 A8 g5 Q+ v, F- o6 _
  107. }
    0 Z) T8 @3 F, u; T* H5 [; c. A

  108. , M5 E# d* n; O8 L5 K& c
  109. //写一页最多只能写256个字节,一个扇区16页,一个块16个扇区                               # ^" V& }- \/ Q% Q* @  I9 V" o
  110. void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite); T  u( {9 ?& ^
  111. {
    " O1 T# ?* U- J* _( B. V6 C
  112.     if(NumByteToWrite > sFLASH_SPI_PAGESIZE )6 m! E# o: J" M, P, J
  113.     {0 Q  X5 ?' q% p- Z+ Y
  114.     NumByteToWrite = sFLASH_SPI_PAGESIZE;
    ! u, t3 l2 ~1 A# G8 b" L7 v
  115.    
      C  D" [1 |% ^$ Y+ e
  116.     printf("写数据量过大,超过一页大小\n");5 Z; D$ Q$ e5 @' P4 ]: ?/ u, _
  117.     }
    1 ^! x2 o3 R) f9 j8 a2 h( Z4 }
  118.    
    7 E, [6 I1 p, h7 Q5 I& R4 V
  119.     sFLASH_WriteEnable();  //开启写使能1 x( h- ?, l5 D. I+ R5 I7 ^
  120.     3 M- p9 q& A1 I. I6 C
  121.     sFLASH_CS_LOW();
    1 j  Y7 X- K& A
  122.    
    ; |0 G5 U+ ~" s" y4 J
  123.     sFLASH_SendByte(sFLASH_CMD_WRITE);
    ( [$ ^+ b9 l) I( V6 ]* h1 C5 g& @
  124.     sFLASH_SendByte( (WriteAddr>>16) & 0xff);   //传送高8位
    ( F( Y  o/ t5 i+ U/ I
  125.     sFLASH_SendByte( (WriteAddr>>8) & 0xff);     //传送中8位
    2 t" e, @/ u! J4 f6 p# B
  126.     sFLASH_SendByte( (WriteAddr>>0) & 0xff);      //传送低8位
    % @0 d, H9 h+ [9 P- b
  127.    
    7 @5 x- u- u( B$ K. ]# s: a8 X4 F
  128.     while(NumByteToWrite--)7 Z. a6 A2 c, L/ @- U0 B
  129.     {, L# x- @! l/ n* M0 H* e
  130.     sFLASH_SendByte(* pBuffer);2 F6 `$ y5 N/ }" k$ O- g& d, [
  131.     pBuffer++;# L& Z4 C+ r2 f) u4 |9 B  x0 ~# v
  132.     }
    " i! z8 E5 \8 V: x/ w
  133.    
    # S+ r. |3 k6 o8 `9 q. W; P+ j! k
  134.     sFLASH_CS_HIGH();
    ( G# e, ~8 A8 o% m! _+ P& g
  135.    
    ; |# e, v+ V# i8 S& z7 M
  136.     /*擦除和写数据都涉及到写动作,一定要等待完成*/
    , P3 Z9 ~* k1 Q% I3 w+ I* J
  137.     sFLASH_WaitForEnd();
    $ }, Q, U+ o# l. h) i
  138. }
    9 y6 \3 t2 m3 _7 F' E
  139. 5 v/ M2 c5 ?8 y' H6 U
  140. //写任意地址、任意长度
    5 ?9 @& q& L, J1 r1 k' V* {
  141. void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
    7 D  \( ?, u2 L/ z3 \. l: A
  142. {' r* y! T' C" @) Z
  143.     uint16_t NumOfPage, NumOfBytes, count, offset;
    5 V/ t2 u0 M3 M, x! b
  144.     //求WriteAddr在某一页的位置        
    7 u8 \; J7 ?. w' T" m  f
  145.     offset = WriteAddr % sFLASH_SPI_PAGESIZE;- P$ j% {" c6 |7 l/ A' w
  146.     //求某一页剩余的大小   j8 Q; D: h5 M& N; |6 G, H+ O  x8 E
  147.     count = sFLASH_SPI_PAGESIZE - offset;
    0 P8 b6 p3 o9 V/ W
  148.    
    4 w; R. g7 p- ~) ?$ e6 U) M: c
  149.     /*处理页不对齐的情况,防止页内覆盖*/) D# V& e5 G5 E3 m% T! z
  150.     //先把某一页剩下的部分写掉,之后的就能新页的起始处开始写        /*offset有值表示需要页对齐,如果要写的字节数小于某一页剩余的部分,那就无需对齐*/                  
    % g6 l: P" Y/ [% @9 A7 L  ~
  151.     /*这两个条件必须同时满足*/
    & s( U! i4 c% l
  152.     if(offset && (NumByteToWrite > count )); ?% H; _+ F" p2 ^) B  R
  153.     {# c8 j: w; \* L# m5 R8 Q
  154.     sFLASH_WritePage(pBuffer,WriteAddr,count);
    ; s% J% @/ e- |( O9 Z
  155.     NumByteToWrite -= count;//去掉已经写了的,从新页开始
    & F  g( ?. G0 w# R
  156.     pBuffer += count;
      X9 s/ m3 Q4 `' \* F' @8 J4 V
  157.     WriteAddr += count;4 b7 _. x( S4 G; k. j" C) |
  158.     }
    ( e. V8 ^: ^8 s" {/ q9 M
  159.    
    1 h/ L/ p( n+ F7 [3 i5 n) k
  160.     /*最多可分多少页*/# r4 y  n% c- K, d! q; @
  161.     NumOfPage = NumByteToWrite / sFLASH_SPI_PAGESIZE;( t& F  e# `8 h4 R" K
  162.     /*剩余多少字节*/7 U6 R+ ^: m3 a% N, r. r
  163.     NumOfBytes = NumByteToWrite % sFLASH_SPI_PAGESIZE;9 e# X/ g. M& Z' v+ g4 ~  ?
  164.     * S1 @+ }- ^. V7 R- M
  165.     if(NumOfPage)
    % R+ D5 [# j" Y0 h$ ?. G( j
  166.     {
    6 T: [' O; \& C4 N7 r- F
  167.         while(NumOfPage--), i9 e. U0 B9 D. A
  168.         {
    * p% ]4 r. w$ ?; p
  169.              //每一页都发起页编程2 n5 U) @. `" K4 I) t% d0 r+ t0 a: x
  170.              sFLASH_WritePage(pBuffer,WriteAddr,sFLASH_SPI_PAGESIZE);0 ^& K9 f* C1 i% `; I
  171.              pBuffer += sFLASH_SPI_PAGESIZE;
    6 ?+ w+ b8 F/ F9 O& r
  172.      WriteAddr += sFLASH_SPI_PAGESIZE;
    3 O. J- r/ }- P( g6 \! h& a
  173.          }7 H6 N5 F! R: @8 V
  174.     }
    & i$ Z/ Y' R9 W2 O% B
  175.     $ A+ q1 s1 E) `
  176.     if(NumOfBytes)  Q3 c; _2 O: i' Y8 D' b7 Q
  177.     {% c3 a7 }2 v) {" Y& N
  178.     sFLASH_WritePage(pBuffer,WriteAddr,NumOfBytes);
    0 k5 [( \6 D# O) x  T
  179.     }
    ! d3 p( ?- n) w- _1 A
  180. }
复制代码
) G3 \3 @/ t6 J/ }' j
  • 为什么会有两种写操作函数,是因为这里的写操作有两个特点:

    ( D9 J, `. D+ v* {- D& ~
    2 [' Q# X( b* s" D* D6 I5 X9 [

; f+ h: v+ q' ~! N, U) U% g
1.无法突破页限制,超过一页需要重新发起页编程信号。另外如果要写的数据大于剩余一页剩余的容量,那么超出的数据会写到当前页起始地址出。例如,初始输入的写地址为200,而要写的数据大小为100,那么要写的前56个字节会从地址200开始依次写入,剩下的44个字节会从当前页的0地址开始依次写入,这很有可能覆盖之前的数据。

, @; H: _" ]- }, X$ b' S& T5 M- g
2.无法突破扇区的限制,当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。

* ?& O! L6 Y  O$ a( i
转载自: 骆驼听海
  d5 S6 ~8 Y- m2 }4 Z& C/ H如有侵权请联系删除) r8 l# ~$ I& u: H' a+ x  I
% ^" |, A5 U8 ^) M& T# r
收藏 评论0 发布时间:2023-4-12 16:18

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版