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

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

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

- u/ D9 V; a6 q( V9 E
微信图片_20230412161833.jpg

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
  • 总线介绍:I2C(Inter-Integrated Circuit)总线(也称IIC或I2C)是由PHILIPS公司开发的两线式串行总线(单双工),用于连接微控制器及其外围设备,在这两根线上可以挂很多设备,同一时刻只能有一个节点处于主机模式,其他节点处于从机模式,总线上数据的传送都由主机发起。I2C总线没有片选信号线,所以需要通过协议来找到对应操作的芯片。是微电子通信控制领域广泛采用的一种总线标准。它是同步通信的一种特殊形式,具有接口线少,控制方式简单,期间封装形式少,通信速率高等优点。
    9 d- q8 r! v3 ~4 Y# M
    & ^1 W% e, ~. N% c

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
微信图片_20230412161906.png
! 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
微信图片_20230412161928.png

: 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
微信图片_20230412161941.png
- I4 [' J) G, h
a.主设备向从设备写数据:
2 _! \: U9 m. h5 t- E" R
微信图片_20230412161956.png

& [) ~- r/ K$ G1 C. q3 l% f0 G4 r
b.主设备读从设备的数据:
# I* p0 B0 Z. e: o
1.png

% O& z/ K0 ~$ z! u
c.主设备读从设备的某个寄存器:读设备的寄存器首先应该对该设备发送写命令,很多设备都可以看成是一段内存,所以写命令写给从设备,指明要读取哪个地址(寄存器)的数据,接下来才是真正的读数据。不同的从设备是由区别的,在驱动I2C从设备时应当查明设备的时序图,又怎样的要求,不同的时序对应了不同的命令。

' ~) }+ g' Y0 x/ V
2.png

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
微信图片_20230412162110.png

  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
微信图片_20230412162134.png
- R( f7 ~) ^4 `- d
例:24C65的设备地址为7位,高4位恒定为1010,低3位取决于A0-A2的电平状态,般主机在读写24CXX都是把设备地址连同读写位组合成一个字节一起发送。

/ e0 Y5 v  t* q
3.png

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
4.png
  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
5.png
+ \+ B1 G( u; v( t- |8 U* `/ I) ^
24C65 读时序与写时序基本相同,只不过在读之前要发送再发送重复开始位进行读操作。
: n. F: Z/ c5 G1 [
6.png

( _# 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" @
7.jpg

% A* n( M% l  E' ]3 e
3.配置I2C协议参数
/ s2 ]& ?6 Y+ j6 q
8.png

  _4 P& b2 b% d) E, x9 |# Z6 P
4.编写代码

  1. ! W) c8 d2 p: A  ]4 j
  2. //mian.c8 I/ D( N: t# e% ~, \+ Y* c' C+ \3 f
  3. 9 V+ \8 V7 |/ R5 r
  4. #include "main.h"
    ; r! L8 m! K3 ^: g
  5. #include "stm32f4xx_hal.h". v; e$ L' x( v* d: Q$ T6 `) p
  6. #include "i2c.h"! f3 \4 S4 {# t' o3 c! Q1 y: [
  7. #include "usart.h") q$ |7 y4 \, e8 @- j6 S5 _1 b1 Y
  8. #include "gpio.h", n% ?4 M" ?, G  X0 p. T: n
  9. " a: r3 I7 C1 R2 {, X* j
  10. #define ReadAddr   0xA16 x/ m% s: M9 O+ m1 R, `8 M
  11. #define WriteAddr  0xA0
    ' j0 ^4 M9 A: X2 f1 {

  12. ! G+ w0 e) C5 n. J1 L
  13. uint8_t Wbuf[20] = "EEPROM TEST OK!";/ \* q% a" {0 c3 @! w5 W8 x5 u8 [
  14. uint8_t Rbuf[20] = {0};
    5 I/ `) Q0 R8 C7 \1 e
  15. 6 ]( ?/ I& R' e- S
  16. /*********  24C65写数据函数*****************************/* y3 F. T+ i* J5 O0 K
  17. 5 N9 ?0 |7 R" j# D$ a  t$ p
  18. void  Eeprom_Write(uint16_t MemAddr, uint8_t *Wbuf, uint16_t len ){
    * R1 e. M- O: K
  19.         while(len--){
    1 D& s( V7 }$ o. A
  20.         //I2C_MEMADD_SIZE_16BIT表示存储单元大小
    9 p) t4 ?& x% }6 K$ f6 K6 e1 E
  21.         //默认为两个参数,分别是I2C_MEMADD_SIZE_16BIT和I2C_MEMADD_SIZE_8BIT
    2 V" y; f: O7 ~" K7 K  N5 X
  22.         //由于24C65的存储地址是16位的
    9 p( q, J0 r' y. S# S5 l9 K
  23.         //所以我们选择I2C_MEMADD_SIZE_16BIT
    # T2 S7 ]! |1 j1 c
  24.         //1表示一次写一个字节
    6 D# Q5 k7 i( Q9 [; B0 D
  25.                 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. |
  26. MemAddr++;
    - S* E8 v6 T2 ]/ j& V" i
  27. Wbuf++;
    , s( M  W& \. X
  28. }5 R1 |+ l% c: ~. T3 g' v  X
  29. }
    + S% q( P5 o* z. O: E) G2 U

  30. + {1 \' h7 R6 \  O# p3 p

  31. 8 R9 ], d! D/ x; Q8 q
  32. /*********  24C65读数据函数*****************************/. H* g; m1 A- Q. C+ C3 C& I. N& n3 q
  33. " O5 |5 W% F' x- ~
  34. void  Eeprom_Read(uint16_t MemAddr, uint8_t *Rbuf, uint16_t len ){9 v% i. ^8 m0 J: l0 H' C" n6 C8 ]
  35.         //可以连续读,所以无需循环
    & M! a$ ?0 M$ x! `: A% G2 T
  36.         while(HAL_I2C_Mem_Read(&hi2c1, ReadAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Rbuf, len, 100) != HAL_OK );) C; n4 L& c! U+ S
  37. }& ]) x" C, Q. w2 L; G
  38. int mian(){& Q$ F. C) V# ^3 j3 ?3 a' p
  39.       MX_GPIO_Init();
    8 y% x/ l, {& G% d8 ^3 Q4 m' g9 U
  40.       MX_I2C1_Init();
    , B4 W0 w1 ?1 v% \1 n% U' j" g7 B( m
  41.       MX_USART1_UART_Init();
    9 {' V# c% f& s, s
  42.       printf("this is i2c eeprom test\n");
    3 O$ a( r# M) R7 S8 Y0 h

  43. ! D5 v2 e8 s3 b7 x# R/ m$ t5 d- Y
  44.       Eeprom_Write(0, Wbuf, sizeof(Wbuf) );
    5 g+ k9 i2 ?- N# N* ~% @) T7 Y
  45.       HAL_Delay(500);
    : n4 D$ Y' i% {4 x0 ^- {% q
  46.       Eeprom_Read(0 , Rbuf, sizeof(Rbuf));
    1 |/ q. {0 C0 E  e) r4 i
  47.       printf("READ:  %s\n", Rbuf);8 X( @$ ]% g! _1 J6 o

  48. / ?! j7 I, I: X' [; l% o' }- _" Q
  49.       while(){+ V& v$ t# g, f. o9 C0 E- B" s
  50. 6 l4 x1 K1 s5 C* V
  51.       }
    , Z/ i* P: C8 L8 g- d
  52. }
复制代码

/ 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
微信图片_20230412162643.png

7 L( Y% R! N# J3 y
SPI接口共有4根信号线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。
* n2 ~7 l4 C/ H# J0 D
微信图片_20230412162658.png

: 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
微信图片_20230412162734.png

; 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
微信图片_20230412162755.png
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
13.png
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
微信图片_20230412162837.png

, \! n/ g6 j9 L2 k! Q
11.png
  • STM32F4-SPI控制器特性

    9 Z% y) d$ n+ ^1 N! Z
12.png

3 `+ E* s  `7 _$ d  s
1.通讯引脚:
STM32F4芯片最多支持6个SPI外设控制器,它们的SPI通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚,以《STM32F4xx规格书》为准。f407只有SPI1、SPI2、SPI3。

3 x) T( p: c9 @9 ?
微信图片_20230412163014.png

# 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: [
微信图片_20230412161739.png
: {* 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
微信图片_20230412161735.png
- Q% K/ m2 D4 K/ Q3 m8 O
1.W25X16的硬件连线如下:
3 V$ {3 f- c3 J& C. `) m. d8 }
微信图片_20230412161643.png
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
微信图片_20230412161719.png
$ 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
微信图片_20230412161715.png

. C# v1 _& E9 E* E$ z. d
b.写使能命令(06H):在向 FLASH 芯片存储矩阵写入数据前,首先要使能写操作,通过“Write Enable”命令即可写使能。
6 n4 `& q+ B/ j1 R7 y8 h
微信图片_20230412161711.png

% ~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
微信图片_20230412161707.png
微信图片_20230412161704.png

% p3 B8 \$ @" M1 k, H
d.读状态寄存器(05H):FLASH 芯片向内部存储矩阵写入数据需要消耗一定的时间,并不是在总线通讯结束的一瞬间完成的,所以在写操作后需要确认FLASH芯片“空闲”。我们只需要读取FLASH芯片内部的状态寄存器SRP的S0即可(当这个位为“1”时,表明 FLASH芯片处于忙碌状态,它可能正在对内部的存储矩阵进行“擦除”或“数据写入”的操作)
, f( {4 d/ q: p0 `. j
微信图片_20230412161658.png

6 F( T! J. d7 f, [" L8 X1 o* f3 U
微信图片_20230412161654.png

& E9 s' [6 v; B  A* j( T! ~
e.读数据(03H):读数据指令可从存储器依次一个或多个数据字节,该指令通过主器件拉低/CS电平使能设备开始传输,然后传输“03H”指令,接着通过DI管脚传输24位芯片存储地址,从器件接到地址后,寻址存储器中的数据通过DO引脚输出。每传输一个字节地址自动递增,所以只要时钟继续传输,可以不断读取存储器中的数据。

) U: s- Y* w9 B8 X- z
微信图片_20230412161650.png
/ \* 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
微信图片_20230412161646.png
/ 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
微信图片_20230412161729.png

% 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
微信图片_20230412161640.png
3 I: i9 Q0 m6 S' D; r) w* s
微信图片_20230412161637.png

% N8 E* M, b/ Z6 d4 M. z! x
3.配置SPI协议
( z5 \6 i8 m( {2 T) F
微信图片_20230412161632.png
. U7 l8 H3 s% S4 c- \7 B
4 h5 ]7 w! a9 t5 m3 Z0 ^$ o
4.编码
  1. //main.c9 w3 d) ]9 D  U
  2. * S4 F( c: n" c; v5 }) t
  3. #include "w25x16.h"
    3 m# N, ~9 w# J5 {4 p) o

  4.   x  G0 q7 X' g( Z7 n
  5. uint8_t RD_Buffer[5000] = {0};
    1 R; d2 z3 n) L/ j2 y8 y
  6. uint8_t WR_Buffer[5000] = "SPI FLASH WRITE TEST\n";3 ^5 Y# r4 @6 [) O/ l7 S

  7. / u, ]' v8 p5 s% O7 k6 s6 z
  8. int main(){6 o# m( R* R$ y6 A. t, S* r$ L6 i
  9.     uint16_t FLASH_ID = 0;
    / ?  J" a: B) Y6 Q7 V6 l' h$ k" ]
  10.     uint32_t i;9 H; q$ C( g+ C& T# R2 j
  11.     % u+ J4 `- ?6 C0 l
  12.     MX_GPIO_Init();
    * j4 Z: l9 {6 S; q7 v& j) Z# S' Z. I
  13.     MX_SPI1_Init();
    - e6 S$ Z4 Q! j& L2 }: y" B
  14.     FLASH_ID = sFLASH_ReadID();! I6 M* m, h3 D, o3 e
  15.     2 N! t  R' r$ ]0 J
  16.     /******测试擦除******/  m& o1 I  _2 X2 ~
  17.     sFLASH_EraseSector(4096*0);/ {' J8 H5 w: M- B( A% x4 p# [
  18.     //sFLASH_EraseSector(4096*1);
    7 X4 x4 u$ ]; u
  19.     sFLASH_ReadBuffer(RD_Buffer,0,4096);
    & \* L& `8 X5 K2 X! i
  20.     printf("读数据开始\n");
    4 _" j# D$ D1 D) X! S# U
  21.     for(i=0; i<4096; i++)
    1 U7 m% \3 N' h" r; f1 h" i
  22.     {& h5 }; c9 @) o. Z. f6 d" W' V( V  a
  23.     printf("%x ",RD_Buffer[i]);
    ! X# l- V0 ?6 j# u
  24.     }
    ) u6 I" p* q  @) k5 V
  25.     printf("读数据结束\n");
    ( q7 j/ r; `/ C& r2 e
  26. ' B' L4 m+ c/ M* v2 r" M/ Q

  27. : ?) {  v) v+ K8 s! Y
  28.     /******测试写操作1*****/
    % G$ {' x7 p9 I, z% }3 w
  29.     //写之前都需要擦除扇区
    6 T& {" P8 q2 S0 w( q8 `$ b! `9 }
  30.     sFLASH_EraseSector(4096*0);
    / ^" X9 R5 \$ L4 h; I4 h/ `+ c9 q* h8 Q
  31.     sFLASH_WritePage(WR_Buffer,0, 20);
    1 E, R% ~; g6 j: [2 i9 l
  32.     sFLASH_ReadBuffer(RD_Buffer,0,20);
      s6 r! A; ^+ s  O
  33.     printf("READ DATA: %s\n",RD_Buffer);
    / Z. j- v8 n% M! v" F0 F
  34.    
    4 n' w3 J" X  {
  35.     /******测试写操作2*****/
    ; J: n0 w8 l/ Q
  36.     //写之前都需要擦除扇区
    , I  v2 [- @" y+ v) S# v2 d
  37.     sFLASH_EraseSector(4096*0);# P- H+ ?4 y0 E  B  b5 o
  38.     sFLASH_EraseSector(4096*1);                            + F/ t) V( R; r0 [2 \
  39.     for(i=0; i<4096; i++)
    , n1 @! n( i) K- B* X5 p( V; G  g
  40.     {2 E# Q/ I1 p/ v7 p$ b  N. m
  41.     WR_Buffer[i] = 0x55;$ \. V% t6 K1 J) l4 L  N  C& E0 T
  42.     }
    7 J2 ^- X" G" t6 h/ g7 F
  43.     sFLASH_WriteBuffer(WR_Buffer,4090, 1000);
    2 R# `3 {( W) z, C+ i' P( C1 s
  44.     sFLASH_ReadBuffer(RD_Buffer,4090,1000);
    : |5 T. i8 W5 E: M+ W
  45.     for(i=0; i<1000; i++)/ H' k8 y. D0 Z
  46.     {
    , e# W+ J. E8 I( C! F
  47.     printf("%x ",RD_Buffer[i]);1 \- j+ j* J$ W5 Z+ A$ M: c) G- v
  48.     }
    . ]- }) E- G/ s! n
  49.     /*****************/4 W- a5 A( J* }# i  J% P
  50.     while(){}
    , Z7 R$ u% b, }5 \
  51. }
复制代码
  1. //w25x16.h
    $ W2 [& f' l0 h3 H% \

  2. 0 L2 q8 _; S" o6 G
  3. #ifndef __W25X16_H) N% Q6 T4 E4 g# Q2 n
  4. #define __W25X16_H' x; Y7 C/ W+ b7 G1 G

  5. ; N! E' Z( f9 \4 r
  6. #include "stm32f4xx_hal.h": a3 N. a4 b  Y  R/ c1 B) h

  7. 1 u: e0 i8 R0 {1 N. F4 O7 C- W
  8. //使用宏定义芯片指令
    ; ~0 C$ y6 [. U1 x- v
  9. #define W25X_ManufactDeviceID  0x90  /* Read identification */
    ( |1 X& d- x. C# Z) j
  10. #define sFLASH_CMD_WREN        0x06/* Write enable instruction */
    8 c. _) t% E# l4 Y0 z1 J
  11. #define sFLASH_CMD_RDSR        0x05/* Read Status Register instruction  */0 ^1 f+ |6 i5 Q, U3 i5 B/ _
  12. #define sFLASH_CMD_SE          0x20/* Sector Erase instruction */
    9 Q; n1 i+ Q  S
  13. #define sFLASH_CMD_WRITE       0x02  /* Write to Memory instruction */! L5 }$ R" |  z( l/ a
  14. #define sFLASH_CMD_READ        0x03/* Read from Memory instruction */
    - ]# X& R) C# U3 k
  15. #define sFLASH_DUMMY_BYTE      0x00 //空字节,用于只读传回来的数据1 c) b' U1 o& v  F
  16. #define sFLASH_BUSY_FLAG       0x01
    : D! Y- p7 {0 P8 X/ [- `% l
  17. #define sFLASH_SPI_PAGESIZE    0x100
      ?% M; g4 h# |1 ^

  18. ! y! K2 \6 I6 z4 L& K
  19. /* 选中芯片,拉低信号 */
    ! \* D7 h' }( o+ q2 D. g; g8 {/ m
  20. #define sFLASH_CS_LOW()       HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_RESET)
    . E+ ~. a* y1 ^5 d$ z4 M
  21. /* 释放芯片,拉高信号 */+ q% ?) G0 i4 F* y
  22. #define sFLASH_CS_HIGH()      HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_SET)
    # D" L4 e3 W; }4 d
  23. ; x, W& H8 s: [6 b  D: o. _
  24. //定义函数* e* K/ C; W4 {+ R
  25. uint8_t sFLASH_SendByte(uint8_t byte);0 N" _) u0 p  c2 e  V" D
  26. uint16_t sFLASH_ReadID(void);' n6 F5 n1 j9 v. H" e6 T
  27. void sFLASH_EraseSector(uint32_t SectorAddr);
      T( P$ z# ?" \$ X& W
  28. void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);# N# F1 K9 ^6 ?6 m8 U8 m3 s
  29. void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);" H, A' C( @6 d: m
  30. void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead);5 V0 l+ Z5 P3 L3 I( c- n0 b& P
  31. #endif
复制代码

  1. ' t. Z5 L+ o& f" t
  2. //w25x16.c
    : x% i4 P+ ~! T7 }9 _  [

  3. # ^# Z: i- s* v2 s! V+ y0 w) B
  4. #include "w25x16.h"" d. N7 U, U( ~, S

  5. - t, S" f: g; f1 t4 F# Q
  6. extern SPI_HandleTypeDef hspi1;
    - o8 l! _  N  l
  7. * x7 {# Y) k' s' k/ \$ U
  8. /*读写一个字节函数,因为SPI读和写同时完成*/. F0 i/ q& d) R! {" [' [
  9. /*发送数据一定会接收到一个数据*/1 F& A; q! w9 J9 S" _. q9 ^2 w: X
  10. uint8_t sFLASH_SendByte(uint8_t byte)
    : }0 O. B# E6 D- I
  11. {+ O5 N+ g2 ?5 P3 h; {' u
  12.     uint8_t TX_DATA = byte;. j! B1 g! J2 m% m) I' ^0 O
  13.     uint8_t RX_DATA = 0;. y4 S0 T% c1 [/ Q; m
  14.     HAL_SPI_TransmitReceive(&hspi1, &TX_DATA ,&RX_DATA , 1, 1000);: n4 b. N9 w3 d6 ~1 w
  15.     return RX_DATA;; U% B  K6 J0 v; a. ^, v% G5 e7 o
  16. }
    9 w! J( F# _* }& ~& }. _; x8 {. ]3 Z

  17. 0 X, k/ p  S$ C# `4 i7 R, R
  18. /*等待擦除或者写数据完成*/
    ' I& d" `' F7 a
  19. void sFLASH_WaitForEnd(void)
    ( u6 n- F" w; v4 K  S' Z1 X1 E
  20. {' `, I+ p5 r( E, m
  21.     uint8_t sr_value = 0;
    & w' C! P% {+ ]6 H7 F, F
  22.     sFLASH_CS_LOW();8 |' B' a0 W% X8 C8 J; d
  23.     sFLASH_SendByte(sFLASH_CMD_RDSR);3 E" R0 w& u% C& F! x! X% s9 F
  24.             //读S0的值,为1表示忙碌,为0表示停止          5 C; y- Y5 l! ~* ?. x
  25.     do{7 {; z0 a7 T- T0 v" g
  26.                 //发一个空字节,得到S0的值             4 U$ Q' _: _" q8 s, F9 k5 P
  27.                 sr_value = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    / i3 ~; Q( W& E. a6 y
  28.     }while( sr_value & sFLASH_BUSY_FLAG);6 O& c- o, m+ q: Q
  29. . g! ?; |* e1 N
  30.     sFLASH_CS_HIGH();
    * ?$ \3 M% y4 N; m9 ^7 \8 d! U
  31. }. W" q, y2 ^0 V; |

  32. + b3 g, G. H7 v. L# P" G
  33. void sFLASH_WriteEnable(void)+ \7 _+ n6 O+ x( z( z1 R. \
  34. {" c  b0 z5 F# ~7 I& S# Y
  35.     sFLASH_CS_LOW();/ n- v0 Q( q  l0 F; r) H/ z
  36.    
    2 ]% Y; t5 ?7 q& W! X8 U* t
  37.     sFLASH_SendByte(sFLASH_CMD_WREN);  p( e! e4 A: l$ g: y+ c7 p
  38.    
    1 ?7 p/ [, X+ a9 m7 M" h
  39.     sFLASH_CS_HIGH();
    , V7 t3 C6 o# ~& \
  40. }
    ' l$ ]2 ?" O8 H, o0 _% o6 S

  41. ! Y4 A1 Q2 x) p7 z; M5 _
  42. % M! T0 ^5 i/ p& e) {
  43. /*读设备ID*/ 3 p, ?# l4 ^2 l' L! [
  44. uint16_t sFLASH_ReadID(void)2 z5 a8 g5 ^- c, c- C
  45. {3 S/ |8 e0 j/ j. j1 X; J
  46.     uint16_t FLASH_ID;  y7 _4 P# I* F  V& p
  47.     uint8_t temp0,temp1;$ ]. L, Y" g! A
  48.    
    4 S# Z1 x) i% U7 K2 m. g9 v
  49.    
    : K- e1 t5 Z& d# d4 [8 [: j
  50.     sFLASH_CS_LOW();; m# e4 W+ n- F' q: t$ M4 R3 K- F
  51.    
    ( i& r$ |* x2 ?1 p+ K2 n
  52.     sFLASH_SendByte(W25X_ManufactDeviceID);2 H$ @/ T; k' f: D" V3 M' b, W! B. \
  53.     //读设备指令后要发24位地址,所以要发三次
    2 e) H6 b% Y" f5 F7 w
  54.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);: b) _! d8 o; _4 F+ j1 G
  55.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);* }0 s2 _" d  F; S
  56.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    ; D) K1 O  o  m( C4 P; U
  57.     //制造商ID- Z" J: Q' b/ r, w1 S( j3 e; z6 c
  58.     temp0 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    & e% u7 H" c7 F3 K
  59.     //设备商ID# ^$ ?  {8 _* ^6 E$ O& |6 J. w
  60.             temp1 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    : j: D/ t) E3 |+ J2 f) ]9 h/ b7 F4 A9 [: h
  61.     9 t" b, w9 ]" J
  62.     sFLASH_CS_HIGH();
    4 k6 h1 I" {0 t8 a
  63.     3 P: }+ @0 p: N; e) U& n
  64.     FLASH_ID = (temp0 << 8) | temp1;; h, \! p3 S: p6 w; A! Y- P
  65.    
    * q! n5 R8 ?/ r* q9 R, u+ [* ^' X# |
  66.     return FLASH_ID;
    8 _( @) X% n& l* ^0 A# u, ?0 v
  67. }
    3 M& a" ~  G$ [3 `1 U3 C' K1 l! I3 y
  68. $ e- U/ v# h# m7 a8 M
  69. //擦除扇区,擦除为1,因为只能由1变为0 ,不能0变10 l# l8 A/ ^, Z% ]+ i
  70. void sFLASH_EraseSector(uint32_t SectorAddr)
    * h6 f6 L) M2 f# z! z
  71. {
    . G: l) _# [- ?! E& |5 t
  72.     //SectorAddr表示擦除第几个扇区
    ' e9 r0 ]+ @% i# P" ]) \, U
  73.     sFLASH_WriteEnable();  //开启写使能; t1 H, w6 W5 N' F4 T, h2 q3 ?6 [, j
  74.     sFLASH_CS_LOW();//拉低,片选
    $ x2 F2 I. Y) e! ]& N2 y- Q
  75.     //擦除命令
    7 Y% h' S; J5 M( \( K( U. H7 u
  76.     sFLASH_SendByte(sFLASH_CMD_SE);
    + W) n. M# l% C7 ]( h: Q
  77.             //传24位地址
    " t9 i5 M- s- t1 N
  78.             //传送高8位,将中8位和低8位一共16位移出去,得到高8位                  
    & M6 e5 Y9 s  h1 ~2 L
  79.     sFLASH_SendByte( (SectorAddr>>16) & 0xff);   ! f9 Z% `1 b: y, M2 M
  80.     sFLASH_SendByte( (SectorAddr>>8) & 0xff);    //传送中8位" b$ |5 X- G  N4 L
  81.     sFLASH_SendByte( (SectorAddr>>0) & 0xff);    //传送低8位
    * ?; ^$ Q8 l. D1 n1 `. r
  82.      
    / ?0 @, ?: X( B) }/ N
  83.     sFLASH_CS_HIGH();+ g% A. f( l3 s# X
  84.    
    # L4 o4 I7 v3 R2 E, i0 y
  85.     /*读状态寄存器,等待擦除完成*/
    ( ^5 z9 e* T( m9 X6 g) \3 y: h
  86.     sFLASH_WaitForEnd();
    6 D& Y& J, [! r7 \0 i$ T- b
  87. }
    . T, _- h: d. \* D

  88. ) D3 q* Q* R' Q# R7 y
  89. //读数据
    1 k& b4 N7 d  q9 A5 D1 E+ w
  90. //读命令和读地址发送后,芯片内部会自动不断递增读数据
    * J* ]& K/ w: @# c" y# E
  91. void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead)
    ( x4 d$ `% e3 _: v4 `! x
  92. {* V# H! J% m/ k/ g: N! R# u' Y7 }
  93.     sFLASH_CS_LOW();+ H1 V6 A% x( [# J. a
  94.    
    5 O& n0 X" u6 x" ?$ }8 e  N0 C1 B
  95.     sFLASH_SendByte(sFLASH_CMD_READ);
    5 }. @- e( E$ b, r% b' G) P% a
  96.     sFLASH_SendByte( (ReadAddr>>16) & 0xff);   //传送高8位4 _& i* D3 }6 X7 u0 _
  97.     sFLASH_SendByte( (ReadAddr>>8) & 0xff);    //传送中8位9 W% E- v7 N2 r5 |! Z
  98.     sFLASH_SendByte( (ReadAddr>>0) & 0xff);    //传送低8位6 m2 d- d2 d' R+ }) V4 n8 W
  99.     % S2 x+ S6 A; i9 X& X, |% E5 r: |
  100.     while(NumByteToRead--)
    ( x  O+ S; W' m* h$ I5 u' b+ i3 S" W
  101.     {
    ! g1 T! Y" T, q& @: {% s
  102.     * pBuffer = sFLASH_SendByte(sFLASH_DUMMY_BYTE);! B* s% C& Q+ s% w7 M
  103.     pBuffer++;
    , W; C) g- p# Z; p- l1 j
  104.     }
    % {8 t9 U2 l: V2 o! [
  105.     # q0 W8 b0 x9 ?* `) k
  106.     sFLASH_CS_HIGH();
    % D7 L, h6 Q$ Z1 a
  107. }/ G! I5 B4 C1 Y( \; N
  108. : J0 U9 x5 ]* V. i% D% W
  109. //写一页最多只能写256个字节,一个扇区16页,一个块16个扇区                               ; P, G4 K" A9 g
  110. void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)" k- [) a: V: @0 D% V9 y
  111. {! g9 o1 k; @) ?8 t
  112.     if(NumByteToWrite > sFLASH_SPI_PAGESIZE )
    & x/ |# d' k! I* k  ?: A, W
  113.     {
    1 A1 p! m* z) k# x
  114.     NumByteToWrite = sFLASH_SPI_PAGESIZE;
    5 q1 m- O8 j! c5 N* L
  115.    
    # B, T# J$ }- C7 q
  116.     printf("写数据量过大,超过一页大小\n");
    " ?; R# k1 H' q" u  n) g
  117.     }
    - z0 C3 S3 F2 G+ h, h% M
  118.    
    & W# Z1 G9 [9 r# g: S" @# X! U
  119.     sFLASH_WriteEnable();  //开启写使能
    6 Y* Q- S. A. ~0 }8 n1 e. h
  120.       q, Q, Z# s! Z
  121.     sFLASH_CS_LOW();
    $ g0 L  G$ B1 D. @
  122.    
    6 C7 H' V: p# q- b7 h- h
  123.     sFLASH_SendByte(sFLASH_CMD_WRITE);" W$ T0 t; u" a* p* D9 ]/ C
  124.     sFLASH_SendByte( (WriteAddr>>16) & 0xff);   //传送高8位5 Z: ~5 J& p9 @$ r
  125.     sFLASH_SendByte( (WriteAddr>>8) & 0xff);     //传送中8位$ U& r9 e% j& i8 Q- L% P0 N
  126.     sFLASH_SendByte( (WriteAddr>>0) & 0xff);      //传送低8位
    $ J8 t5 M. Q/ L/ \/ |- R1 Q3 l/ ~. O
  127.     2 u: l4 ^+ ]' }
  128.     while(NumByteToWrite--)! U6 R  d* T4 t8 e! B! X
  129.     {
    % L0 Q2 [6 R/ j1 l7 e5 Z( z( Q
  130.     sFLASH_SendByte(* pBuffer);
    ( x8 ^# A& e+ W4 @8 M
  131.     pBuffer++;
    ' Q; N+ j8 E8 v$ f; x9 P+ Z1 j
  132.     }
      e" }- p+ j+ {5 A& M
  133.     ! D4 [  H( H) F8 F! J( n, ^3 k( G
  134.     sFLASH_CS_HIGH();
    # v1 k; v' h* M, I) p' T/ P
  135.     + U" ^6 G, ?" I2 O# b$ G0 C
  136.     /*擦除和写数据都涉及到写动作,一定要等待完成*/
    ( o& b/ f5 p6 w6 Y: i) w
  137.     sFLASH_WaitForEnd();
    7 f5 Q7 S( l2 g& \2 J* D2 c
  138. }5 W# h% D4 L  I, b7 m1 }
  139. % u& V* B+ d7 Z# d3 F6 B# K+ W3 c  T
  140. //写任意地址、任意长度
    & v8 v+ B" k, c' R: ?' D: w3 Q9 T
  141. void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)* k  e5 D( f( ^1 U) D9 G
  142. {
    ; ]% W4 G* Q1 j; X% l% Y% A
  143.     uint16_t NumOfPage, NumOfBytes, count, offset;
    " Z. k2 G, V1 @( d! W
  144.     //求WriteAddr在某一页的位置        
    & i0 K! I1 `& Q) Y  `; Y
  145.     offset = WriteAddr % sFLASH_SPI_PAGESIZE;
    4 S  M! F. m4 H7 U. c% `$ q2 C
  146.     //求某一页剩余的大小 # L6 E3 p7 @7 S) {: T
  147.     count = sFLASH_SPI_PAGESIZE - offset;
    - T% h$ M' e4 V4 B
  148.     2 [. |# V, M1 [- ^1 |, k$ Q: ?6 x
  149.     /*处理页不对齐的情况,防止页内覆盖*/
    : K3 W7 i9 A9 d
  150.     //先把某一页剩下的部分写掉,之后的就能新页的起始处开始写        /*offset有值表示需要页对齐,如果要写的字节数小于某一页剩余的部分,那就无需对齐*/                   2 x  I6 s: z( O! R# ^
  151.     /*这两个条件必须同时满足*/: N$ L; u& R$ x8 l9 G' e
  152.     if(offset && (NumByteToWrite > count ))
    5 D# n8 C! q+ f$ t* a
  153.     {
    , O' F) }, R2 o. R8 t
  154.     sFLASH_WritePage(pBuffer,WriteAddr,count);
    - }2 J# s: ?/ N. x
  155.     NumByteToWrite -= count;//去掉已经写了的,从新页开始
    3 ^: I4 c& F0 y
  156.     pBuffer += count;
    ( L7 ]7 ^" }( d9 j. `" W& b; u
  157.     WriteAddr += count;' Q/ U: m9 r* C/ I1 [4 r" _/ |7 D
  158.     }  }; v) s/ W) W: I. |
  159.     / d3 [/ Q0 b* x% L7 y% C6 C
  160.     /*最多可分多少页*/
    3 D2 s: A" \5 j" S# y/ g
  161.     NumOfPage = NumByteToWrite / sFLASH_SPI_PAGESIZE;2 m( g( |8 |* j2 W4 B4 F
  162.     /*剩余多少字节*/
    # W% ?8 u" g4 n( e
  163.     NumOfBytes = NumByteToWrite % sFLASH_SPI_PAGESIZE;
    & u2 w5 F7 R# |6 _9 M* J! O
  164.     7 B) V7 g; J9 T: J
  165.     if(NumOfPage)# r0 _( l# t6 k5 s8 ]0 l0 {
  166.     {
    ; }4 m& |% ?7 p7 h" X# F3 J
  167.         while(NumOfPage--)
    9 U# l1 e! h) m, N1 a
  168.         {/ V/ W$ U( E0 y$ c/ Q1 k( z' m$ K
  169.              //每一页都发起页编程
    6 `, I+ `1 \2 C# l) Z
  170.              sFLASH_WritePage(pBuffer,WriteAddr,sFLASH_SPI_PAGESIZE);
    ! S8 h- |" v7 e) T0 s) R. V- o8 ^
  171.              pBuffer += sFLASH_SPI_PAGESIZE;, }$ L  R2 O, [+ P  m/ G
  172.      WriteAddr += sFLASH_SPI_PAGESIZE;% v$ Q& R/ r+ E0 M( w2 a6 \
  173.          }
    # V! w. I7 X" A; I- ?( q
  174.     }1 m( J6 b: O3 ?/ e9 w# Z
  175.     * t+ H$ L8 J$ I
  176.     if(NumOfBytes)
    # T6 L" P9 V2 w% i+ L4 O
  177.     {& z# u5 t# C4 @- B* H& L( P
  178.     sFLASH_WritePage(pBuffer,WriteAddr,NumOfBytes);% `: Q8 y  D8 R( T
  179.     }' C6 @8 U5 g, E+ e* X
  180. }
复制代码

2 X/ j' j! p8 @# _1 }
  • 为什么会有两种写操作函数,是因为这里的写操作有两个特点:

    % W7 p6 t1 ?/ Y: {, J( [0 X
    ! @4 _& H) M4 |: i* ]
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
收藏 评论0 发布时间:2023-4-12 16:18

举报

0个回答

所属标签

相似分享

官网相关资源

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