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

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

[复制链接]
攻城狮Melo 发布时间:2023-4-12 16:18

/ s. k! ?8 y  [9 \) ]
微信图片_20230412161833.jpg
* h6 e6 x7 @' h$ Y/ _
本系列为ARM开发的进阶知识,讲解与ARM芯片进行通信的大部分高级外设的原理及使用方法,不局限于ARM芯片内部,实操性较强且晦涩,需要一定的读者具备一定代码阅读和理解能力。

# ^, \4 Q% \6 \# H/ u
  • STM32 I2C总线通信专题讲解

    ; ]' R1 I9 e% ^! f6 @  M0 K8 u
; h5 w' Y* }, W; X. Q- F) ]" z+ l7 ^
  • 总线介绍:I2C(Inter-Integrated Circuit)总线(也称IIC或I2C)是由PHILIPS公司开发的两线式串行总线(单双工),用于连接微控制器及其外围设备,在这两根线上可以挂很多设备,同一时刻只能有一个节点处于主机模式,其他节点处于从机模式,总线上数据的传送都由主机发起。I2C总线没有片选信号线,所以需要通过协议来找到对应操作的芯片。是微电子通信控制领域广泛采用的一种总线标准。它是同步通信的一种特殊形式,具有接口线少,控制方式简单,期间封装形式少,通信速率高等优点。
    ! F0 n) R4 B) E' K- U, T- R# a

    / g& q, }( S) e& Y1 B5 C& W4 |

% B/ o% g, a; P. k0 R) v7 u
  • 总线特征:

      J# W) Y& K- m9 T6 U! ]- L! m
1.两条总线线路:一条串行数据SDA,一条串行时钟线SCL(主从设备使用同一时钟,属于同步通信)来完成数据的传输及外围器件的扩展

* W) {1 h" a) }  Z  d
2.I2C总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址,通常是7位,有时候是10位

- V0 W. Q' S( Z# K
3.I2C总线数据传输速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。在开发配置的时候,最好检查从设备的传输速率从而对主设备(一般是MCU)进行相应的配置。一般通过I2C总线接口可编程时钟来实现传输速率的调整,同时也跟所接的上拉电阻的阻值有关。

: }; P7 _& L% ?" v- M& K0 t
4.I2C总线上的主设备与从设备之间以字节(8位)为单位进行单双工的数据传输。
  I' j6 v, c% G3 ?" Q1 d: j! J
  • 拓扑结构——总线型
    , y3 `+ f4 i! r5 R
I2C 总线在物理连接上分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成,SCL由主机发出,SCL越快,通讯速率越快。通信原理是通过对SCL和SDA线高低电平时序的控制来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。

2 K9 Z7 M3 k* a' n  C
微信图片_20230412161906.png

% |% u  V) p  t; G4 [+ F% Z
  • I2C总线协议
    - a. O! Y( n6 U9 i7 t7 J
1.I2C协议规定:  总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生。

3 b- n1 ]! b) w- D; j( s# N4 V5 P
2.空闲状态:SCL和SDA都保持着高电平。
0 ]2 I+ w) `. J/ [6 T& H
3.起始信号:  当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件,所有的从设备都能感受到这个跳变,做好准备等待被选择。
) k9 N  L3 D$ F
4.结束信号:当SCL为高而SDA由低到高的跳变,表示产生一个 停止条件

4 P8 F+ l- D: t0 x
微信图片_20230412161928.png

; A- m! S  }, G0 T8 ]& l- S- G$ V
5.数据传输:数据传输以字节为单位 , 主设备在SCL线上产生每个时钟脉冲的过程中将在SDA线上传输一个数据位,数据在时钟的高电平被采样这时候采集到是1就是1,是0就是0,所以在传输数据时,当时钟处于高电平时一定要保持稳定,时钟处于低电平时可以变换数据。(高电平采样,低电平变换)一个字节按数据位从高位到低位的顺序进行传输。主设备在传输有效数据之前 要先指定从设备的地址,一般为7位,然后再发生数据传输的方向位, 0表示主设备向从设备写数据,1表示主设备向从设备读数据。主从设备以字节为单位(8位)进行数据传输,开始传输数据时把从设备地址加上方向位组成一个8位的字节进行发送并接收一个应答。

# G& q0 Y' G9 {& r% I+ @
6.应答信号:接收数据的器件在接收到 8bit 数据后,向发送数据的器件发出低电平的应答信号,表示已收到数据。这个信号可以是主控器件发出,也可以是从动器件发出。总之,由接收数据的器件发出。
5 l( ]" a7 {9 T
微信图片_20230412161941.png
- @0 N0 N2 x  B$ k+ i; h5 {( Q
a.主设备向从设备写数据:
3 R) e2 v: ]6 f& ?* ^9 a, }
微信图片_20230412161956.png

" i/ V+ T6 `1 R% g3 o
b.主设备读从设备的数据:

8 u8 x2 d1 @& I/ ~+ ^- }
1.png
% T8 e4 |+ I: F
c.主设备读从设备的某个寄存器:读设备的寄存器首先应该对该设备发送写命令,很多设备都可以看成是一段内存,所以写命令写给从设备,指明要读取哪个地址(寄存器)的数据,接下来才是真正的读数据。不同的从设备是由区别的,在驱动I2C从设备时应当查明设备的时序图,又怎样的要求,不同的时序对应了不同的命令。
! E) C/ e% }* _4 G2 \5 ~: {4 a
2.png

+ p: d1 H  h8 u) _  }4 V% u1 ^# W

* d; P. W. J* d
  • STM32F4-I2C控制器特性
    9 R# }3 v0 _; A* Y
软件模拟I2C时序:由于直接控制 GPIO 引脚电平产生通讯时序时,需要由 CPU 控制每个时刻的引脚状态,所以称之为“软件模拟协议”方式。我们知道,驱动I2C设备只需要两根管脚,即使单片机上没有I2C控制器,根据协议控制每根管脚每一时刻的电平状态,一根模拟数据线,一根模拟时钟线,就可以驱动从设备,相对而言效率低,但是可以实现控制驱动。STM32内部具备专门的I2C控制器,使用时只需对其进行相应的配置即可。

, W( A; z; `: x4 Q8 v1 C
硬件控制产生I2C时序:STM32 的 I2C 片上外设专门负责实现 I2C 通讯协议,只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并缓存起来,CPU只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种由硬件外设处理I2C协议的方式减轻了 CPU 的工作,且使软件设计更加简单。
5 o- q: u8 e3 }. C
控制器功能:配置主从模式(一般都把STM32当作主机使用,作为从机时应当对其赋一个地址),通过配置其内部的寄存器产生一些中断和错误信号,配置通信速率位标准模式、快速模式、超快速模式等

9 w+ H* I1 F2 H: L- f4 Z3 k" E
STM32芯片有3组I2C外设,可以同时进行3组I2C传输。它们的I2C通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚。
" a; T, Y* Y7 K: G/ Y
微信图片_20230412162110.png
0 E6 L4 F& n/ y2 u2 q% f
  • EEPROM(AT24CXX)存储芯片介绍
    5 I: J* j6 r4 a8 {' s+ f' K
一个典型的I2C接口的从设备,专门用于存储数据的芯片。EEPROM (Electrically ErasableProgrammable read only memory),带电可擦可编程只读存储器,一种掉电后数据不丢失的存储芯片。EEPROM可以在电脑上或专用设备上擦除已有信息,重新编程。

7 B7 H# F, L  T
EEPROM常用来存储一些配置信息,以便系统重新上电的时候加载之,容量不会很高。EEPOM 芯片最常用的通讯方式就是I2C协议。XX表示容量,常用值为01、02、04、16、32、64等,单位Kbit。一般的存储芯片都具有写保护功能,对WP管脚加一个高电平就开启了写保护功能,就无法往芯片内写数据了。在开发中通常将该管脚接地,确保能够写数据
典型24CXX芯片引脚如下:
2 e) G2 r/ `0 k! B
微信图片_20230412162134.png
6 `, K. f$ P' v7 A  m3 E" u& i0 `, ^
例:24C65的设备地址为7位,高4位恒定为1010,低3位取决于A0-A2的电平状态,般主机在读写24CXX都是把设备地址连同读写位组合成一个字节一起发送。
6 F- I6 J4 p) a$ R. c
3.png
; I5 `" D0 _) x' g: Z
24C65的电气连线如下,根据电气连线可知,A0-A2均接地,因此读地址为1010 0001,即0xA1;写地址为10100000,即0xA0 ,且WP接地,用户随时可向芯片内部写入数据。

& k- F- n  o6 G4 m
4.png

( @4 G8 r2 J- D1 ]
24C65写时序:首先发送一个起始信号,接着发送从设备地址以及方向位,收到应答后,向从设备发送要写的存储区域的首地址,24C65的存储地址是16位,先发送高8位,收到应答后再发送低8位,再次收到应答后开始写数据。64Kbit大小位8K字节,需要13位即可表示,所以高3位固定定为0,如下图。
, ?- B+ t5 F2 X  P* @
这里是BYTE WRITE,一次写一个字节,此芯片还支持PAGE WRITE,一次写一页,也就是8个字节,如果想写更多,可设置一个for循环实现。

) Y( O: p0 L6 J
5.png

5 T- U9 h9 L  P: Z6 p  b( O
24C65 读时序与写时序基本相同,只不过在读之前要发送再发送重复开始位进行读操作。
0 V& z' K( Z: [/ D$ R# z
6.png

1 p! b- ^( n. r  o- K) `: {
  • I2C读写EEPROM实例
    5 ?4 i. k$ b6 x5 b$ W! M

! d* y$ E, r, N3 x0 y+ z3 z. L
由电气原理图可知SCL和SDA分别接入了PB6和PB7管脚,读地址为1010 0001,即0xA1;写地址为10100000,即0xA0

% x# V- }* n1 w4 ^
步骤:
1.配置RCC
+ I# ]; `" n8 e* {, {" w( m
2.配置PB6和PB7管脚

& L2 g1 g5 C8 c) Z6 Q! Z' T
7.jpg

9 {) a9 s4 P% r, |4 R
3.配置I2C协议参数
# x- x. _$ g7 O4 |( t: C% ^
8.png
5 R% p2 `9 o% {% u$ V5 C
4.编写代码
  1. ! [& A- q4 m; H7 O' ]) n' w
  2. //mian.c
    1 \$ b1 w+ k: M6 U: X8 Y
  3. 7 |0 Z6 k2 n1 f( m
  4. #include "main.h"  n. ~$ u% ^- F4 }5 b5 x$ I% y
  5. #include "stm32f4xx_hal.h"
    ( f/ a. y; O1 N. \; M  c8 p3 c; b
  6. #include "i2c.h"
    8 p0 {# D# e+ s- ~/ B" {/ x
  7. #include "usart.h"/ x) G! X5 r% A0 F
  8. #include "gpio.h"
    7 K) w! y! N, k# I2 K2 y+ Z

  9. 4 ~$ s' F% r* ?, J
  10. #define ReadAddr   0xA19 Y- l% ?6 M& E3 z1 L7 L0 u
  11. #define WriteAddr  0xA0: H4 S; M" a1 j2 c

  12. / s5 D+ F5 i$ y9 |
  13. uint8_t Wbuf[20] = "EEPROM TEST OK!";& I0 @& K# W/ f9 [0 [0 K
  14. uint8_t Rbuf[20] = {0};
    3 C2 D- @8 }! g; r; ?9 ~0 Z! W0 W* W# v

  15.   S* r# J1 _# c: ^
  16. /*********  24C65写数据函数*****************************/
    7 `" f" T6 @5 z; O; A. o# G

  17. 3 M# g& D1 U. ^3 v& N) [. p
  18. void  Eeprom_Write(uint16_t MemAddr, uint8_t *Wbuf, uint16_t len ){+ v' u& S+ Z+ m6 e
  19.         while(len--){
    : a% J" }( M8 R, ^
  20.         //I2C_MEMADD_SIZE_16BIT表示存储单元大小
    ! H2 p; e5 i( s. r
  21.         //默认为两个参数,分别是I2C_MEMADD_SIZE_16BIT和I2C_MEMADD_SIZE_8BIT* K0 x4 a* ]! I% N) t# ^
  22.         //由于24C65的存储地址是16位的
    ) x5 W3 C3 Y1 w0 B
  23.         //所以我们选择I2C_MEMADD_SIZE_16BIT5 x) O* l8 l2 M0 K& o5 [
  24.         //1表示一次写一个字节
    7 {1 h+ x* E& G+ q  X
  25.                 while(HAL_I2C_Mem_Write(&hi2c1, WriteAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Wbuf, 1, 100) != HAL_OK){};
    ( [  c3 b; {" e: v6 B) U: S; h$ F8 g
  26. MemAddr++;; E' F6 n: m, j1 ?, e
  27. Wbuf++;
    8 U/ U0 V- _5 b& y0 B
  28. }
    * O9 u  ~" `8 n6 I7 [$ H2 s& b
  29. }
    8 x. b2 ~3 ~% ?& ?
  30. 8 z# D0 ~1 d5 ^2 D

  31. 8 H$ {. _* [: d. j" F8 C
  32. /*********  24C65读数据函数*****************************/
    3 G2 V6 P- i5 i' A, {, k

  33. 3 t# r2 g5 C! L
  34. void  Eeprom_Read(uint16_t MemAddr, uint8_t *Rbuf, uint16_t len ){( B$ t( v! X# I: o1 b5 Z
  35.         //可以连续读,所以无需循环/ C6 g4 V) |# ^; P
  36.         while(HAL_I2C_Mem_Read(&hi2c1, ReadAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Rbuf, len, 100) != HAL_OK );
    ) j9 U9 A+ M) A4 J
  37. }' Y7 B! w+ V; g0 N) L) b
  38. int mian(){8 y7 J2 S. S7 @3 u. P
  39.       MX_GPIO_Init();
    " H% Y; |: I; e. l; G% J1 \+ `
  40.       MX_I2C1_Init();
    ( T6 x+ d- q, Z" E5 ^0 i; c- N/ t, e
  41.       MX_USART1_UART_Init();- C9 O, {) H3 s8 u8 N
  42.       printf("this is i2c eeprom test\n");
    / S& I- F8 U0 X# d% j* J9 l9 s

  43. 2 |- i5 F: J5 g: z& h6 I
  44.       Eeprom_Write(0, Wbuf, sizeof(Wbuf) );# G; h! R. q3 a& {4 }; b1 `8 n% t
  45.       HAL_Delay(500);1 d: [7 F0 L" O' o0 [
  46.       Eeprom_Read(0 , Rbuf, sizeof(Rbuf));+ O# S0 C8 y6 S5 s/ ]. X3 `2 \
  47.       printf("READ:  %s\n", Rbuf);1 _/ w& G* N4 [0 r+ {8 u6 k6 T
  48. 4 V4 P; M4 r! I( S, y
  49.       while(){- L2 Z2 f9 v8 ^* R

  50. * d8 r+ i8 y: l6 D9 c6 Z7 W$ A3 F
  51.       }
    0 W: _/ d: o" W0 y/ F
  52. }
复制代码

6 W$ Z4 ^9 E; W5 I# [! r
  • STM32 SPI总线通信专题讲解

    . M- s0 t1 {* |, l
SPI接口是Motorola 首先提出的全双工三线同步串行外围接口,采用主从模式(Master Slave)架构;支持多slave模式应用,一般仅支持单Master。时钟由Master控制,在时钟移位脉冲下,数据按位传输,是高位在前还是低位在前是可以配置的,配置时根据从设备的通信进行相应配置,一般是高位在前,低位在后(MSB first)。SPI接口有2根单向数据线,为全双工通信,目前应用中的数据速率可达几Mbps的水平。
SPI总线被广泛地使用在FLASH、ADC、LCD等设备与MCU间,要求通讯速率较高的场合。
$ t% N% S( F! b
微信图片_20230412162643.png

: a3 G" h( F! k7 L1 G% e
SPI接口共有4根信号线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。
) g- K+ ]8 J2 [5 y( J; n
微信图片_20230412162658.png
) H* w% ]! j( ]: }
(1)MOSI:主器件数据输出,从器件数据输入,连接从机的MOSI,与串口不同,串口需要反着连接(Rx-----Tx)
(2)MISO:主器件数据输入,从器件数据输出,连接从机的MISO
(3)SCLK :时钟信号,由主器件产生
(4)/SS:从器件使能信号,由主器件控制(片选),一般情况下为地电平选中设备,高电平释放设备。
$ \! x, C! k( j6 B$ b
  • SPI总线协议

    - a5 c. V5 D: U

. ?; |1 G" n+ w' g1 |6 s
1.数据交换逻辑:主机和从机都包含一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节发起一次传输。寄存器通过MOSI信号线将字节传送给从机,从机也将自己的移位寄存器中的内容通过MISO信号线返回给主机。这样两个移位寄存器中的内容就被交换了。从机的写操作和读操作时同步完成的,因此SPI成为一个很有效的协议。

/ E" a6 N" ?0 [) X! i  p
如果主机只想写不想读,只需把数据放在数据寄存器,SPI控制器会自动传给外设,同时忽略掉外设传过来的数据即可;如果主机只想读不想写,主机写给外设一个空字符或者随便写一个数据,外设就会把数据传过来,不管是只读还是只写,主机与外设的读和写都h会发生且同时进行。
8 i4 U9 q  a" u
微信图片_20230412162734.png

+ T" X/ S- i1 n$ X5 M/ @
2.起始信号:  NSS信号线由高变低,是SPI通讯的起始信号。
9 s5 y. W# P5 y7 ^
3.结束信号:NSS信号由低变高,是SPI通讯的停止信号。
1 S9 C5 r1 s7 O" j
4.数据传输:SPI使用MOSI及MISO信号线来传输数据,使用SCK信号线进行数据同步。MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,按位传输,且数据输入输出是同时进行的。SPI每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制,要么是8位,要么是16位,可以配置。

: v4 O! ~* _# f5 h# V, s# e7 j  \
微信图片_20230412162755.png

" u' a2 W; W6 ?$ }+ s- ?
  • SPI的4种通信模式
    9 g8 U' y+ C" F/ \

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

/ k' S+ P- U8 j2 g  j) R
13.png
) I3 G7 w2 G9 {& T8 B
1.时钟极性CPOL : 设置时钟空闲时的电平:
a.当CPOL= 0 ,SCK引脚在空闲状态保持低电平;
b.当CPOL= 1 ,SCK引脚在空闲状态保持高电平。
! G2 B2 ?$ |9 \1 [
2.时钟相位CPHA :设置数据采样时的时钟沿:
a.当 CPHA=0时,MOSI或 MISO 数据线上的信号将会在 SCK时钟线的奇数边沿被采样
b.当 CPHA=1时, MOSI或 MISO 数据线上的信号将会在 SCK时钟线的偶数边沿被采样
( _, ^' G$ r/ b7 q* Y
微信图片_20230412162837.png

+ |) @  y+ X( {( K
11.png
  • STM32F4-SPI控制器特性

    5 T3 Y4 T% }' ]
12.png

4 x- K! p  d) E, g( l
1.通讯引脚:
STM32F4芯片最多支持6个SPI外设控制器,它们的SPI通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚,以《STM32F4xx规格书》为准。f407只有SPI1、SPI2、SPI3。

& m8 i9 _/ u& ?# R* T
微信图片_20230412163014.png

: y2 _" r$ g. z) P3 o$ B, L
其中SPI1、SPI4、SPI5、SPI6是APB2上的设备,最高通信速率达42Mbtis/s,SPI2、SPI3是APB1上的设备,最高通信速率为21Mbits/s。其它功能上没有差异。

( g$ a* v# r( l7 e* j
2.时钟控制逻辑:
SCK线的时钟信号,由波特率发生器根据“控制寄存器CR1”中的BR[0:2]位控制,该位是对f pclk 时钟的分频因子,对f pclk 的分频结果就是SCK引脚的输出时钟频率。
: N( h1 Q0 B: ^( J5 r8 U& ?1 M, T
微信图片_20230412161739.png

* b& y* [1 g/ M% q2 ]7 w
其中的fpclk 频率是指SPI所在的APB总线频率,APB1为fpclk1 ,APB2为fpckl2

  d* o9 ]! a  b' X' W
3.数据控制逻辑:
STM32F4的MOSI及MISO都连接到数据移位寄存器上,数据移位寄存器的数据来源来源于接收缓冲区及发送缓冲区。

6 d7 m2 U; T2 u$ N  q7 ]4 R# U
a.通过写SPI的“数据寄存器DR”把数据填充到发送缓冲区中。

! n( y6 D# V" [6 z
b.通过读“数据寄存器DR”,可以获取接收缓冲区中的内容。

+ X0 f8 p0 k- y$ I: M
c.其中数据帧长度可以通过“控制寄存器CR1”的“DFF位”配置成8位及16位模式;配置“LSBFIRST位”可选择MSB先行(高位在前)还是LSB先行(低位在前)。

1 B, c" {, X( h" `
4.整体控制逻辑:
a.整体控制逻辑负责协调整个SPI外设,控制逻辑的工作模式根据“控制寄存器(CR1/CR2)”的参数而改变,基本的控制参数包括前面提到的SPI模式、波特率、LSB先行、主从模式、单双向模式(同时发送和接收、只发送关掉接收、只接收关掉发送)等等。
1 B( G4 V7 Q/ V' B. y
b.在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,只要读取状态寄存器相关的寄存器位,就可以了解SPI的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生SPI中断信号、DMA请求及控制NSS信号线。

6 Y! ~  H, B. D# W6 D  I
c.实际应用中,一般不使用STM32 SPI外设的标准NSS信号线,而是更简单地使用普通的GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号。
  • 串行FLASH_W25X16简介

    5 V0 q* d& Y" h8 {3 [

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

- h1 i9 g6 B  [6 }+ j- N
W25X16有8192个可编程页,每页256字节。用“页编程指令”每次就可以编程256个字节。用扇区擦除指令每次可以擦除16页,即一个扇区包含16页,用块擦除指令每次可以擦除256页,用整片擦除指令即可以擦除整个芯片。W25X16有512个可擦除扇区或32个可擦除块。

* ~3 {0 w5 E+ q1 `
微信图片_20230412161735.png
  k# S5 |" [6 A4 Y
1.W25X16的硬件连线如下:
+ k6 w- Y5 b# D- w, W1 @
微信图片_20230412161643.png
( d- z6 }! B9 c* G
CS:    片选引脚,低电平有效,连接到STM32-PH2管脚
SO:   连接到STM32-PB4管脚(MISO)
SI:    连接到STM32-PB5管脚(MOSI)
CLK:  连接到STM32-PA5管脚(CLK)
WP:   写保护管脚,低电平有效,有效时禁止写入数据。接电源未使用
HOLD: HOLD 引脚可用于暂停通讯,该引脚为低电平时,通讯暂停,未使用
+ a8 c+ p% t6 x' J7 X  y! h
2.W25X16控制指令:
我们需要了解如何对FLASH芯片进行读写。FLASH 芯片自定义了很多指令,我们通过控制 STM32利用 SPI总线向 FLASH 芯片发送指令,FLASH芯片收到后就会执行相应的操作。
而这些指令,对主机端(STM32)来说,只是它遵守最基本的 SPI通讯协议发送出的数据,但在设备端(FLASH 芯片)把这些数据解释成不同的意义,所以才成为指令。

6 P3 T$ I7 ~# c) w, w- z
微信图片_20230412161719.png
7 t  r7 E0 h: F
a.读制造商/设备ID(90):该指令通常在调试程序的时候用到,判断SPI通信是否正常。该指令通过主器件拉低/CS片选使能器件开始传输,首先通过DI线传输“90H”指令,接着传输000000H的24位地址(A23-A0),之后从器件会通过DO线返回制造商ID(EFH)和设备ID。(注:SPI为数据交换通信,主器件在发送“90H”指令时也会接收到一个字节FFH,但此数据为无效数据)

; C0 [/ k7 y1 [6 `1 p. v
微信图片_20230412161715.png

& f( z" w0 J( {
b.写使能命令(06H):在向 FLASH 芯片存储矩阵写入数据前,首先要使能写操作,通过“Write Enable”命令即可写使能。

: o( U, W( i2 s+ @% C
微信图片_20230412161711.png

2 q  i* @, w4 p+ H1 F4 {  A# i
c.扇区擦除(20H):由于 FLASH 存储器的特性决定了它只能把原来为“1”的数据位改写成“0”,而原来为“0”的数据位不能直接改写为“1”。所以这里涉及到数据“擦除”的概念。

+ @7 q+ }* x. k
在写入前,必须要对目标存储矩阵进行擦除操作,把矩阵中的数据位擦除为“1”,在数据写入的时候,如果要存储数据“1”,那就不修改存储矩阵 ,在要存储数据“0”时,才更改该位。

$ s% r) G6 d& X+ W5 @0 A' F
微信图片_20230412161707.png
微信图片_20230412161704.png
+ v# ?3 b8 `# I% e2 J  q7 Q
d.读状态寄存器(05H):FLASH 芯片向内部存储矩阵写入数据需要消耗一定的时间,并不是在总线通讯结束的一瞬间完成的,所以在写操作后需要确认FLASH芯片“空闲”。我们只需要读取FLASH芯片内部的状态寄存器SRP的S0即可(当这个位为“1”时,表明 FLASH芯片处于忙碌状态,它可能正在对内部的存储矩阵进行“擦除”或“数据写入”的操作)

  C$ d  v6 Z6 h+ t& ^  }
微信图片_20230412161658.png
) w! f  c4 _) q) R. h% \! |; E
微信图片_20230412161654.png
' \2 K4 s* ?$ w$ U
e.读数据(03H):读数据指令可从存储器依次一个或多个数据字节,该指令通过主器件拉低/CS电平使能设备开始传输,然后传输“03H”指令,接着通过DI管脚传输24位芯片存储地址,从器件接到地址后,寻址存储器中的数据通过DO引脚输出。每传输一个字节地址自动递增,所以只要时钟继续传输,可以不断读取存储器中的数据。
: ^: c' F2 u* J; D) F" S3 b+ }
微信图片_20230412161650.png
  B5 R& d9 g5 y. M, k+ }& l
f.写数据——页编程(02H):页编程指令可以在已擦除的存储单元中写入256个字节。该指令先拉低/CS引脚电平,接着传输“02H”指令和24位地址。后面接着传输至少一个数据字节,最多256字节。

' J) L' \9 K6 f0 ]7 l5 E, V* q, b
注:当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。

  c2 L( b- @( N( M* l5 h) }
微信图片_20230412161646.png
$ C6 p/ [9 C  E# u( {
  • STM32 SPI_FLASH基本配置和操作

    : `3 o. s$ n7 H( n

. X3 b5 V3 w6 t' z( b, Q
根据如下的硬件连线图进行配置
) K# t) R8 ^+ A' W. R' q$ a9 _
微信图片_20230412161729.png

1 s1 ?: L! E. a0 Y' s, ~8 e
步骤:
1.使能时钟RCC

( W! u9 @9 D6 W* P# e
2.使能SPI1,配置相应管脚
0 B. ]9 y- U- b, h
微信图片_20230412161640.png
' z+ P" S0 x% g3 p1 c2 J' |
微信图片_20230412161637.png
0 _$ a& i7 }* L2 W: J6 r8 h% X
3.配置SPI协议

0 U  L  I& ]* b+ F) i1 w& E
微信图片_20230412161632.png

. _4 ^# ?9 f: S' C# E/ F
" N$ j9 L6 K5 {! e* E1 o1 n
4.编码
  1. //main.c
    # o* h( w7 T* l( T0 O

  2. 7 v" ?2 G+ h/ Z- [* l7 Y
  3. #include "w25x16.h"9 c8 H0 n2 I" x0 o8 B! K& U: W( W/ f

  4. % R! K8 |* z  M- C" R: {3 \
  5. uint8_t RD_Buffer[5000] = {0};7 b) ?1 p2 Y- I
  6. uint8_t WR_Buffer[5000] = "SPI FLASH WRITE TEST\n";
    ) w7 h9 X) x$ u8 A% h" R1 i( m* a
  7. 5 N5 o3 X" V! y7 }" [$ U0 j4 U: W
  8. int main(){
    ! k5 K, C9 R2 [7 y$ _- t
  9.     uint16_t FLASH_ID = 0;6 X% q3 j- c0 A6 V. W2 ?) q
  10.     uint32_t i;
    * ?7 n' Y/ o8 a6 u, Y
  11.     3 a$ g5 j1 _/ q% t2 {/ ?+ b1 V" j6 j0 a
  12.     MX_GPIO_Init();+ `, N3 P3 d) n' J" A% L% H) ~1 a
  13.     MX_SPI1_Init();
    / C  z; T; d1 K
  14.     FLASH_ID = sFLASH_ReadID();
    6 d( l6 G! t) B0 k6 T( \
  15.    
    1 ]4 ^  o( m. s# v
  16.     /******测试擦除******/
    . K  d3 m& p3 o0 c/ G
  17.     sFLASH_EraseSector(4096*0);
    / r" f6 k6 L9 P
  18.     //sFLASH_EraseSector(4096*1);
    8 K, ?+ G$ q% a: E5 K" `, P
  19.     sFLASH_ReadBuffer(RD_Buffer,0,4096);% G2 z& D, x5 o' w, O: {
  20.     printf("读数据开始\n");
    - B: C& t. F% X! t
  21.     for(i=0; i<4096; i++)
    ! G+ }! r5 |* b
  22.     {- V' S8 D! y  ?6 O
  23.     printf("%x ",RD_Buffer[i]);
    & o9 J+ ~2 S4 T) V# }
  24.     }
    8 a4 g1 }2 ?4 \
  25.     printf("读数据结束\n");+ l* g* Z4 f+ c  D2 C! Z3 M
  26. ' `/ n& K6 E6 ], x7 l2 s5 U. k+ A

  27. * D5 a' e! s& s# x5 D) K5 m5 k
  28.     /******测试写操作1*****/% c+ ~/ K. _3 p  Q1 n, P
  29.     //写之前都需要擦除扇区9 ~$ V8 }! S* N+ B. B8 D
  30.     sFLASH_EraseSector(4096*0);: _5 o& W) T/ T. j: X
  31.     sFLASH_WritePage(WR_Buffer,0, 20);
    9 b" s: O, E7 l6 o( g
  32.     sFLASH_ReadBuffer(RD_Buffer,0,20);
    - ~: Y; E' M4 C2 w2 R
  33.     printf("READ DATA: %s\n",RD_Buffer);  W0 C8 ^% @; O$ G6 c, y" b, [
  34.     9 r4 k# }+ Y! g/ I
  35.     /******测试写操作2*****/
    * `! I; ~$ v9 J* N: c& J
  36.     //写之前都需要擦除扇区- M; `* N5 Q. b5 V5 k
  37.     sFLASH_EraseSector(4096*0);# `3 [) ~$ a" i4 r  K9 i
  38.     sFLASH_EraseSector(4096*1);                           
    ) M" k9 ?3 J& ^. f6 `2 b) |9 D
  39.     for(i=0; i<4096; i++)
    3 I5 L- x, l2 L
  40.     {
    % f0 _- G+ p/ l. r$ K
  41.     WR_Buffer[i] = 0x55;5 [, s& T- B6 B- o1 Y
  42.     }
    & `! y  q  |& {8 B) t) u! ]& z
  43.     sFLASH_WriteBuffer(WR_Buffer,4090, 1000);
    - ^! T: C" f$ m9 j. N
  44.     sFLASH_ReadBuffer(RD_Buffer,4090,1000);. E, E" d9 |; K
  45.     for(i=0; i<1000; i++)
    - v+ E. J8 q3 _$ u6 J' q
  46.     {
    $ G5 U$ `. `7 R1 o4 y( @
  47.     printf("%x ",RD_Buffer[i]);& C8 s9 Z$ j$ C1 e
  48.     }' _) D, C3 X* w1 r  l' J# }0 i
  49.     /*****************/
    % C7 c) y. R' R& r/ m1 k& Z, G7 z
  50.     while(){}1 b; K7 x9 C4 }$ S0 l0 d
  51. }
复制代码
  1. //w25x16.h; h+ H) r+ g$ S3 N
  2. 9 j& ?, ^( ^3 x
  3. #ifndef __W25X16_H7 L: {% h) E0 B/ C
  4. #define __W25X16_H$ ?6 i: D+ K5 Z8 q

  5. 0 F; v1 q! y) ]0 T1 o# p
  6. #include "stm32f4xx_hal.h") B" ?) F7 ?  `  C4 J3 i

  7. 3 j$ X$ x; }- t  E- a
  8. //使用宏定义芯片指令: q- Z5 A! D. z$ q, n" N8 z" V
  9. #define W25X_ManufactDeviceID  0x90  /* Read identification */5 @" `1 @4 b* J( L* s
  10. #define sFLASH_CMD_WREN        0x06/* Write enable instruction */! P& A* ^( Z* ?
  11. #define sFLASH_CMD_RDSR        0x05/* Read Status Register instruction  */: V# V: L: p, Q
  12. #define sFLASH_CMD_SE          0x20/* Sector Erase instruction */
    % {6 P4 Z& n* Q0 A/ p: `4 g8 q! {
  13. #define sFLASH_CMD_WRITE       0x02  /* Write to Memory instruction */
    , R2 o' }# M) B" J& A1 x" Z8 A
  14. #define sFLASH_CMD_READ        0x03/* Read from Memory instruction */; o0 ~, i0 R& c# k
  15. #define sFLASH_DUMMY_BYTE      0x00 //空字节,用于只读传回来的数据
    - R* o, R. [% Q# _/ R
  16. #define sFLASH_BUSY_FLAG       0x015 w+ T3 G& ~. B4 N
  17. #define sFLASH_SPI_PAGESIZE    0x100
    / S( C' M. Q2 b
  18. 9 d& z) A0 k, n1 D& u4 R% o
  19. /* 选中芯片,拉低信号 */8 C6 f6 u( a2 t1 Y7 }- x
  20. #define sFLASH_CS_LOW()       HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_RESET)
    ; T& B! h/ t; o. t5 Z) E6 C5 f2 u" k
  21. /* 释放芯片,拉高信号 */) B9 d9 J& p( S! ~) K
  22. #define sFLASH_CS_HIGH()      HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_SET)' w, }& y( o$ A/ W5 `4 d  Y
  23. : e' A+ {  K  ?- S
  24. //定义函数
    # w0 Y2 P$ M9 l" K, y  W) _
  25. uint8_t sFLASH_SendByte(uint8_t byte);, j3 Q$ b2 F$ {5 Z- O+ ?
  26. uint16_t sFLASH_ReadID(void);8 d7 r1 G) \* n3 p7 p# b
  27. void sFLASH_EraseSector(uint32_t SectorAddr);
    . ^( P$ @: N: R$ F3 ~# h- }3 F
  28. void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);
    + h( g' X: B/ s* B/ f( P
  29. void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);3 K; E4 C$ b% J! d, y) Q
  30. void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead);4 V! C5 F% d( J5 _
  31. #endif
复制代码

  1. 8 v" t) Y9 g1 C7 |# ~, d! G
  2. //w25x16.c- u" S: Z, V( w% L2 e0 m% e* ]
  3. 7 _+ I5 x: J+ `
  4. #include "w25x16.h"" G  F$ |3 }" f2 O/ i
  5. 8 x: `! q$ B! ^6 \7 M
  6. extern SPI_HandleTypeDef hspi1;
    $ l9 W  w: `$ K0 f% |

  7. 3 x+ {* [/ R: x# H" C' ]
  8. /*读写一个字节函数,因为SPI读和写同时完成*/
    % ^& q$ L- h/ f* C
  9. /*发送数据一定会接收到一个数据*/
    , {$ j/ _: j% i) y
  10. uint8_t sFLASH_SendByte(uint8_t byte)0 ?0 m! u* k% R; E9 W
  11. {
    9 Y3 B: ?# ?6 x: c$ q5 _1 k% W
  12.     uint8_t TX_DATA = byte;' j$ l! `8 D, J3 C! c. z9 E
  13.     uint8_t RX_DATA = 0;* m7 g: _& \  B2 s
  14.     HAL_SPI_TransmitReceive(&hspi1, &TX_DATA ,&RX_DATA , 1, 1000);! n: H; s6 y) d* f5 d* O3 U- k
  15.     return RX_DATA;. T- ~/ ?/ k" }- l
  16. }/ k- F1 [1 C7 V, d! ^' N
  17. - Z* ^. S3 f, L: u0 f' B
  18. /*等待擦除或者写数据完成*/
    7 k+ T# [' [. D6 w9 b3 T7 K
  19. void sFLASH_WaitForEnd(void)+ O9 l5 ]& ?  `. V
  20. {( `: d9 W% |# H/ a; a3 z* Y
  21.     uint8_t sr_value = 0;
    * X0 y3 m: i3 N2 {
  22.     sFLASH_CS_LOW();
    0 P( e. H) c4 ~; q
  23.     sFLASH_SendByte(sFLASH_CMD_RDSR);6 `* o$ g8 }; V0 C, U
  24.             //读S0的值,为1表示忙碌,为0表示停止         
    , w7 B. e6 C' v& u8 y
  25.     do{
    - e. J* M9 K. i( A1 \3 @/ A
  26.                 //发一个空字节,得到S0的值             2 Q2 \6 Y& v! ~
  27.                 sr_value = sFLASH_SendByte(sFLASH_DUMMY_BYTE);: ~1 |- {8 T: |- G: G  B; T
  28.     }while( sr_value & sFLASH_BUSY_FLAG);$ r+ [. ^/ b8 G( t

  29. ! S1 P5 _4 [, e+ _  I: b, r
  30.     sFLASH_CS_HIGH();
    $ y4 i& i3 M  I  i
  31. }
    - H* R7 O- P3 o6 E: q% C

  32. # _! q3 R( a8 P. I- G+ I" ~( D
  33. void sFLASH_WriteEnable(void)
    # a8 r. n( t0 H/ ^$ n  D, w
  34. {% `( ?# r# U/ O* m' k- P
  35.     sFLASH_CS_LOW();7 @. G. x& Z& t
  36.    
    ( |; k2 K& x" Y) n8 ]1 O
  37.     sFLASH_SendByte(sFLASH_CMD_WREN);2 I5 z& i* A+ s4 W! |- u
  38.    
    4 _" e/ b( c% h
  39.     sFLASH_CS_HIGH();
    ' w$ W" H8 A; w3 |/ R* P9 g
  40. }% r4 l- _8 p  P+ _  \& s6 v
  41. 0 J' X5 Y! `8 G' m6 A, H
  42. # q/ y/ M3 \# T, W( W
  43. /*读设备ID*/
    / A7 m% G% h1 T; `, r' h7 C7 N8 F
  44. uint16_t sFLASH_ReadID(void)/ e5 V% c) ^1 g
  45. {
    . T' F2 o: v8 W! U
  46.     uint16_t FLASH_ID;
    6 x3 y- C8 P9 C7 }8 w$ N
  47.     uint8_t temp0,temp1;
    + L8 d! G+ B& q% w# y2 I
  48.    
    9 {. [/ t( w- u( s' k( }; V
  49.    
    " }6 F1 `; p) M! r5 V
  50.     sFLASH_CS_LOW();
    ) o" @/ s. [& i) Q1 Y! M# _
  51.     + q' v3 k5 l# T: k: t2 K
  52.     sFLASH_SendByte(W25X_ManufactDeviceID);
    6 g* y1 K, f7 h$ \0 J
  53.     //读设备指令后要发24位地址,所以要发三次
    # D' Z  a; I) q
  54.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    0 p7 L# F- I/ c5 f6 U
  55.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    " Q* R! U7 h2 I3 y+ s" v! H/ [" u
  56.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);/ p# \' w2 |# l0 p1 D) J7 v- s' i
  57.     //制造商ID  S" t1 }5 r) |* i4 J
  58.     temp0 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);! p  o2 u9 N0 \7 o- F
  59.     //设备商ID+ Z2 U$ K; Y& r; B$ M. g  T
  60.             temp1 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    0 o% O5 W9 [6 j6 h% @! z; B) \  ?0 c
  61.     % q/ i7 i$ T9 |6 B  ~7 y  n8 I6 y$ L* \
  62.     sFLASH_CS_HIGH();0 y( d3 U9 s0 S
  63.     6 a6 y# i2 v: C1 `; [/ W; D/ e
  64.     FLASH_ID = (temp0 << 8) | temp1;( v0 O. P7 W6 L5 w( r
  65.    
    8 _' n  G1 M* P6 W% m# [
  66.     return FLASH_ID;
    ' |* c+ e: c9 @" H. i
  67. }
    ' A8 j; }4 R  s4 _. j

  68. ; x) w# ?3 ^# ]8 w1 d
  69. //擦除扇区,擦除为1,因为只能由1变为0 ,不能0变1
    : E6 I) D& q- o3 z- ]# B
  70. void sFLASH_EraseSector(uint32_t SectorAddr)* J: G6 F1 w; n- d  z* h- D
  71. {
    5 M, a$ o% c4 L( z
  72.     //SectorAddr表示擦除第几个扇区
    ( T! [* b- R. V5 o. T
  73.     sFLASH_WriteEnable();  //开启写使能
    8 x0 {2 @5 z* D- j4 v4 W- o2 Q  `& @
  74.     sFLASH_CS_LOW();//拉低,片选
    : M  f; C( d( a" `
  75.     //擦除命令
    1 R  Q' V. U- }. e- a
  76.     sFLASH_SendByte(sFLASH_CMD_SE);
    8 N9 a* }+ ~6 q! [/ B
  77.             //传24位地址
    ! h. ?) F. V+ }. _7 @( C
  78.             //传送高8位,将中8位和低8位一共16位移出去,得到高8位                  
    ! B+ X2 I, n- O9 s. f2 ]. A
  79.     sFLASH_SendByte( (SectorAddr>>16) & 0xff);   4 H1 `  b5 T' a" w- I6 C$ f
  80.     sFLASH_SendByte( (SectorAddr>>8) & 0xff);    //传送中8位. ]/ g+ e0 k% y1 a7 X
  81.     sFLASH_SendByte( (SectorAddr>>0) & 0xff);    //传送低8位' C1 o  |: u) G0 n! F% T
  82.      9 b" t3 ~2 ?( v7 \1 z
  83.     sFLASH_CS_HIGH();! C7 [2 k" Q) Z/ ^
  84.     $ ?  M- U& \1 A7 i/ j( M4 Y: {
  85.     /*读状态寄存器,等待擦除完成*/1 ]: ^0 F9 k. n" [
  86.     sFLASH_WaitForEnd();0 B  |# i5 l. U3 Z4 k  l1 `1 Y
  87. }2 H4 r6 i0 c+ J

  88. " Y, y. R/ e. F$ u
  89. //读数据7 x5 p+ {6 b+ i5 m4 J0 S
  90. //读命令和读地址发送后,芯片内部会自动不断递增读数据0 _' B" G" T! N
  91. void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead)/ w; N' u  t( B( {
  92. {2 H" G- F9 f5 @, }2 \
  93.     sFLASH_CS_LOW();
    ! B% D  M1 ~; Q( t
  94.     0 N% z4 O7 K& ?  d6 l! L
  95.     sFLASH_SendByte(sFLASH_CMD_READ);
    * H0 U& D+ ~) c4 W8 }, b: R
  96.     sFLASH_SendByte( (ReadAddr>>16) & 0xff);   //传送高8位" u1 O5 D0 ^( O0 Z. z
  97.     sFLASH_SendByte( (ReadAddr>>8) & 0xff);    //传送中8位# A0 T4 h. U/ q( ?# m
  98.     sFLASH_SendByte( (ReadAddr>>0) & 0xff);    //传送低8位
    6 T3 z. h; F' G
  99.    
    * ^# a6 {# i+ b* @3 e& o3 ~
  100.     while(NumByteToRead--)3 _2 l" ~3 C5 \' Q/ H3 z
  101.     {
    6 f) F! F8 @8 P' r5 }: \: L$ G3 E% S
  102.     * pBuffer = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    4 f& H* o& G4 l8 e6 p
  103.     pBuffer++;
    ; |1 A0 B3 d. n1 v% |4 S
  104.     }% J0 w8 h& B$ Z+ e
  105.    
    9 \' [( \. `. R2 f+ m
  106.     sFLASH_CS_HIGH();
    6 U# q4 @& C# O7 h9 h# d
  107. }
    ! t, n6 C; {! M( [$ u" Q% N5 j
  108. ; L, G; r, X7 J- g8 r0 Q
  109. //写一页最多只能写256个字节,一个扇区16页,一个块16个扇区                               ( m0 @4 o' o/ ]8 m6 j! ^" B5 N
  110. void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
    % ~( J! a% R! S  W
  111. {
    ( u" G# X4 c: _  ?, ~4 Q
  112.     if(NumByteToWrite > sFLASH_SPI_PAGESIZE )- {& W% H+ E+ d" D  ]! {, p
  113.     {
    . J0 x9 H* s# @/ I1 Y
  114.     NumByteToWrite = sFLASH_SPI_PAGESIZE;
    9 m. h. w! a3 n3 p& b# h
  115.     + e8 z8 E0 m% [
  116.     printf("写数据量过大,超过一页大小\n");" c7 s" C) \0 M2 ^. Q) J
  117.     }: ?7 T' G5 E5 f& a& n
  118.    
    # i8 B7 w4 |! x. z
  119.     sFLASH_WriteEnable();  //开启写使能
    5 z5 [; [7 h. D
  120.     0 d. @6 y5 S8 B
  121.     sFLASH_CS_LOW();' c0 v0 I3 t2 e7 a# l7 Z8 S: I
  122.    
    : F& D6 s; Q( T& x. n) e& z7 Y! B
  123.     sFLASH_SendByte(sFLASH_CMD_WRITE);
    5 e$ n  w' _, S3 k' R& y) d' d9 m- Q
  124.     sFLASH_SendByte( (WriteAddr>>16) & 0xff);   //传送高8位
    3 H, X+ \# g; T( q
  125.     sFLASH_SendByte( (WriteAddr>>8) & 0xff);     //传送中8位- I1 v) R" [0 C( f  U
  126.     sFLASH_SendByte( (WriteAddr>>0) & 0xff);      //传送低8位
    ! @  w3 r7 H- b3 \+ @
  127.     % r+ K' C! [; _6 @" T! \
  128.     while(NumByteToWrite--)( u& J7 Z7 d: j
  129.     {# {5 ^& J+ X8 `$ o9 z
  130.     sFLASH_SendByte(* pBuffer);# f3 P, U/ {/ Q' a+ k' B
  131.     pBuffer++;
    7 c+ \" o7 ~- F+ o' N
  132.     }, H+ J' F9 _% K. H8 m' T7 [2 A8 I
  133.    
    - R1 \) Z, {; _# v
  134.     sFLASH_CS_HIGH();8 s! e% `# w3 z
  135.    
    ' E, t' E; t% L( u; @) u
  136.     /*擦除和写数据都涉及到写动作,一定要等待完成*/. r. ]6 _# m6 O6 M2 }( C
  137.     sFLASH_WaitForEnd();" o' S% K& e5 ~) c7 z% ]; r
  138. }4 {, I1 w; d6 G6 h: N- |

  139. ' F1 l/ {, Q7 F8 x4 I. b& \
  140. //写任意地址、任意长度
    2 P" n+ j; X. i2 Q0 E* i
  141. void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)# U. ]) Y5 p* ?7 I5 \$ F# p" N* {
  142. {  s' n7 ]5 d* ?. m) Z7 S
  143.     uint16_t NumOfPage, NumOfBytes, count, offset;
    5 L5 M4 C6 k. B0 H, E  q( F: p( u
  144.     //求WriteAddr在某一页的位置        
    * d$ Y, R$ K6 J- |1 _: m
  145.     offset = WriteAddr % sFLASH_SPI_PAGESIZE;
    # Z+ S0 X: }8 I
  146.     //求某一页剩余的大小
      q  o! X8 ^2 R2 F, e" Y
  147.     count = sFLASH_SPI_PAGESIZE - offset;
    - u- p; C+ E% {9 z4 a. k
  148.    
    9 D8 E0 `  O2 p8 Y+ o5 J
  149.     /*处理页不对齐的情况,防止页内覆盖*/
    2 P; u: ]$ Z( x3 b) q
  150.     //先把某一页剩下的部分写掉,之后的就能新页的起始处开始写        /*offset有值表示需要页对齐,如果要写的字节数小于某一页剩余的部分,那就无需对齐*/                   : {( P/ M+ F, `  @% \
  151.     /*这两个条件必须同时满足*/
    % [8 t& c# G0 [& r" Q
  152.     if(offset && (NumByteToWrite > count ))- y( d  A4 `' R* @/ o' w. @$ y! {! j
  153.     {
    ! M1 E& n( x1 o7 O6 b7 W3 @
  154.     sFLASH_WritePage(pBuffer,WriteAddr,count);8 r1 ~1 X+ Y6 i* n! R6 z2 @
  155.     NumByteToWrite -= count;//去掉已经写了的,从新页开始) O. h& y+ d( y, L3 z* Y+ W. ?
  156.     pBuffer += count;
    % E- z# P. w" o
  157.     WriteAddr += count;3 a$ M- z$ O( E  [2 `. a! e
  158.     }
    # D+ x  O: {# z! e  D( ?
  159.       d; \2 z% G9 e$ V# b; c4 c% \
  160.     /*最多可分多少页*/5 @7 N$ s, h0 k" ^. V# i4 }
  161.     NumOfPage = NumByteToWrite / sFLASH_SPI_PAGESIZE;
    4 ?; s+ ~- Y0 K, u, y; r) e
  162.     /*剩余多少字节*/8 t) r3 f# X  k4 n# f' F1 W5 ?0 ^
  163.     NumOfBytes = NumByteToWrite % sFLASH_SPI_PAGESIZE;
    * e( ?4 c% W0 _) I' a" |' T9 K
  164.    
    ' \+ V4 m- P9 ]
  165.     if(NumOfPage)' O+ \- T# R. M# f
  166.     {( Z) _; w- g( {( @' c
  167.         while(NumOfPage--)
    - d4 i1 B1 S3 W* _" r" Y0 p
  168.         {- b7 M7 {7 ^/ c: t0 e
  169.              //每一页都发起页编程
    * k% F) k' Z4 z, ^% h% \
  170.              sFLASH_WritePage(pBuffer,WriteAddr,sFLASH_SPI_PAGESIZE);
    ' W* m8 M4 A0 {% E% {
  171.              pBuffer += sFLASH_SPI_PAGESIZE;# B% ~! W! ]+ M0 \" |0 o/ C- n
  172.      WriteAddr += sFLASH_SPI_PAGESIZE;& f/ C* ]. @; N# a- [* l
  173.          }, o8 y8 V0 ?; X+ e2 ~2 j
  174.     }
    1 ~3 e$ N% L- {4 w; {( O
  175.     ; o% ^3 b- s! p* F
  176.     if(NumOfBytes)$ Z( ?2 ?, i& }5 _. h6 _' Q9 A
  177.     {& ^/ G0 o3 p+ S) o5 U  W" u' h* Q+ A
  178.     sFLASH_WritePage(pBuffer,WriteAddr,NumOfBytes);
    , M( j  T7 @# V& r! f
  179.     }
    " |( E8 @+ S3 m9 M* T3 w
  180. }
复制代码
9 ^* j+ s# K) L8 n1 j
  • 为什么会有两种写操作函数,是因为这里的写操作有两个特点:

    6 n. Z! ?; o! B: f, w4 u; d, w& R* O( a2 z( M7 }

# z5 i! k. U0 p4 w- h- \9 W
1.无法突破页限制,超过一页需要重新发起页编程信号。另外如果要写的数据大于剩余一页剩余的容量,那么超出的数据会写到当前页起始地址出。例如,初始输入的写地址为200,而要写的数据大小为100,那么要写的前56个字节会从地址200开始依次写入,剩下的44个字节会从当前页的0地址开始依次写入,这很有可能覆盖之前的数据。
9 G; p. q- [7 m' e; A! g
2.无法突破扇区的限制,当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。
5 O. Z# q  G: C1 i
转载自: 骆驼听海8 c) S8 j1 d& b
如有侵权请联系删除* Q/ I  M# d7 c/ V, @4 @
0 |8 c6 Q. I+ i4 p
收藏 评论0 发布时间:2023-4-12 16:18

举报

0个回答

所属标签

相似技术帖

官网相关资源

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