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

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

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

  {1 ?* T( Y9 @! H  O4 s/ R
微信图片_20230412161833.jpg

$ y; D7 m# M- H2 L- f0 W! n
本系列为ARM开发的进阶知识,讲解与ARM芯片进行通信的大部分高级外设的原理及使用方法,不局限于ARM芯片内部,实操性较强且晦涩,需要一定的读者具备一定代码阅读和理解能力。

1 R8 R! I- j9 `) B" `$ l
  • STM32 I2C总线通信专题讲解

    2 |! S: |' E/ n  P

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

    ' m6 h2 ~5 P; S

+ v9 k  p& I' K; p5 s% {
  • 总线特征:
    5 a2 k- B: f  @; E
1.两条总线线路:一条串行数据SDA,一条串行时钟线SCL(主从设备使用同一时钟,属于同步通信)来完成数据的传输及外围器件的扩展

8 k  N6 d7 F8 K+ v
2.I2C总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址,通常是7位,有时候是10位

* d( y# L/ B; f5 y7 |0 j5 C  H, m( S
3.I2C总线数据传输速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。在开发配置的时候,最好检查从设备的传输速率从而对主设备(一般是MCU)进行相应的配置。一般通过I2C总线接口可编程时钟来实现传输速率的调整,同时也跟所接的上拉电阻的阻值有关。

* b  S8 r# N, N' j9 S6 N+ N
4.I2C总线上的主设备与从设备之间以字节(8位)为单位进行单双工的数据传输。

( n6 J1 d. ~9 Q( n/ n; z
  • 拓扑结构——总线型
    ! [0 l/ v' j/ W: u+ l
I2C 总线在物理连接上分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成,SCL由主机发出,SCL越快,通讯速率越快。通信原理是通过对SCL和SDA线高低电平时序的控制来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。
" y+ [" c( ?$ r. l/ ^
微信图片_20230412161906.png

5 C3 Y9 o' t6 S: R# T( K! c
  • I2C总线协议

    ) F! h/ f+ q; u5 Q7 k' }
1.I2C协议规定:  总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生。
# q  \/ z% D& I7 X, M2 V
2.空闲状态:SCL和SDA都保持着高电平。

5 z. @( o7 G1 L9 j, f
3.起始信号:  当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件,所有的从设备都能感受到这个跳变,做好准备等待被选择。

. _8 n- t* s: ^! l- H
4.结束信号:当SCL为高而SDA由低到高的跳变,表示产生一个 停止条件

4 J6 g' e- W' L* L; ?, f1 L
微信图片_20230412161928.png

! Z1 Z, d1 |2 K/ M
5.数据传输:数据传输以字节为单位 , 主设备在SCL线上产生每个时钟脉冲的过程中将在SDA线上传输一个数据位,数据在时钟的高电平被采样这时候采集到是1就是1,是0就是0,所以在传输数据时,当时钟处于高电平时一定要保持稳定,时钟处于低电平时可以变换数据。(高电平采样,低电平变换)一个字节按数据位从高位到低位的顺序进行传输。主设备在传输有效数据之前 要先指定从设备的地址,一般为7位,然后再发生数据传输的方向位, 0表示主设备向从设备写数据,1表示主设备向从设备读数据。主从设备以字节为单位(8位)进行数据传输,开始传输数据时把从设备地址加上方向位组成一个8位的字节进行发送并接收一个应答。
. E& i' ]4 _$ d% _8 m
6.应答信号:接收数据的器件在接收到 8bit 数据后,向发送数据的器件发出低电平的应答信号,表示已收到数据。这个信号可以是主控器件发出,也可以是从动器件发出。总之,由接收数据的器件发出。
& w6 o% Y6 F" X: B
微信图片_20230412161941.png
7 L, V2 G: `5 G/ r8 Y/ i
a.主设备向从设备写数据:

$ }2 N2 \1 a/ D: x
微信图片_20230412161956.png
8 P! H/ N* r' g5 z, a  _
b.主设备读从设备的数据:
' t% k% x; K: v6 E
1.png

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

2 G( }, S3 S1 @
2.png
! `5 o. s0 f: `4 j: `

# m: j( w6 W, h9 p" K: b
  • STM32F4-I2C控制器特性
    2 [' Q$ A6 Z5 O# H+ l7 u
软件模拟I2C时序:由于直接控制 GPIO 引脚电平产生通讯时序时,需要由 CPU 控制每个时刻的引脚状态,所以称之为“软件模拟协议”方式。我们知道,驱动I2C设备只需要两根管脚,即使单片机上没有I2C控制器,根据协议控制每根管脚每一时刻的电平状态,一根模拟数据线,一根模拟时钟线,就可以驱动从设备,相对而言效率低,但是可以实现控制驱动。STM32内部具备专门的I2C控制器,使用时只需对其进行相应的配置即可。

1 v9 Y$ [: Z$ C4 q& S( n. R
硬件控制产生I2C时序:STM32 的 I2C 片上外设专门负责实现 I2C 通讯协议,只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并缓存起来,CPU只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种由硬件外设处理I2C协议的方式减轻了 CPU 的工作,且使软件设计更加简单。

' p9 u  q6 ?1 ]5 |
控制器功能:配置主从模式(一般都把STM32当作主机使用,作为从机时应当对其赋一个地址),通过配置其内部的寄存器产生一些中断和错误信号,配置通信速率位标准模式、快速模式、超快速模式等

# g* ]" j7 v6 J% J/ H; i( q; P
STM32芯片有3组I2C外设,可以同时进行3组I2C传输。它们的I2C通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚。

) Y2 z5 G* {# Z) O- Z. d- q8 d! b
微信图片_20230412162110.png
% O- Q7 e% V: \- _; I1 w' z
  • EEPROM(AT24CXX)存储芯片介绍
    . A% I) x: G% P+ t: y5 ?
一个典型的I2C接口的从设备,专门用于存储数据的芯片。EEPROM (Electrically ErasableProgrammable read only memory),带电可擦可编程只读存储器,一种掉电后数据不丢失的存储芯片。EEPROM可以在电脑上或专用设备上擦除已有信息,重新编程。
6 V0 x- b( S) L  Y: H7 R
EEPROM常用来存储一些配置信息,以便系统重新上电的时候加载之,容量不会很高。EEPOM 芯片最常用的通讯方式就是I2C协议。XX表示容量,常用值为01、02、04、16、32、64等,单位Kbit。一般的存储芯片都具有写保护功能,对WP管脚加一个高电平就开启了写保护功能,就无法往芯片内写数据了。在开发中通常将该管脚接地,确保能够写数据
典型24CXX芯片引脚如下:
9 V+ A5 n/ ~% v/ T. S; p
微信图片_20230412162134.png
2 N8 [. n$ p- @- f6 N
例:24C65的设备地址为7位,高4位恒定为1010,低3位取决于A0-A2的电平状态,般主机在读写24CXX都是把设备地址连同读写位组合成一个字节一起发送。
7 k2 J# N% y" A- _' l9 V  X
3.png

' d2 v+ b* j* z/ W& ]9 h
24C65的电气连线如下,根据电气连线可知,A0-A2均接地,因此读地址为1010 0001,即0xA1;写地址为10100000,即0xA0 ,且WP接地,用户随时可向芯片内部写入数据。

& {5 t1 V4 d& t/ Y, L, \( h
4.png

' Y: p1 V2 `0 Z! t1 K" _
24C65写时序:首先发送一个起始信号,接着发送从设备地址以及方向位,收到应答后,向从设备发送要写的存储区域的首地址,24C65的存储地址是16位,先发送高8位,收到应答后再发送低8位,再次收到应答后开始写数据。64Kbit大小位8K字节,需要13位即可表示,所以高3位固定定为0,如下图。
2 t: Z- i* d' a9 z7 E! `$ h
这里是BYTE WRITE,一次写一个字节,此芯片还支持PAGE WRITE,一次写一页,也就是8个字节,如果想写更多,可设置一个for循环实现。

5 l8 h4 v5 s$ F+ v
5.png

6 {  W. o2 H" J8 g6 v2 h! c, A
24C65 读时序与写时序基本相同,只不过在读之前要发送再发送重复开始位进行读操作。
$ x. ~7 W% ~; r
6.png
' X( t# U+ b0 h  x1 p; C
  • I2C读写EEPROM实例

    0 Y9 v6 k- V* P
6 s5 b; k' t. {7 v  `
由电气原理图可知SCL和SDA分别接入了PB6和PB7管脚,读地址为1010 0001,即0xA1;写地址为10100000,即0xA0

! h# k2 o+ d" O
步骤:
1.配置RCC

0 X8 d6 x7 V6 g. h  m6 j
2.配置PB6和PB7管脚
3 R. d. `9 Z; h% f" U+ ^  ?/ P
7.jpg
% W6 i; A: m- L
3.配置I2C协议参数
) l1 Z* m+ ^0 [! d% }' c* I
8.png
- g0 I7 }7 R% O& Z5 ]
4.编写代码
  1. 3 b5 [- I8 r" E8 d* u, Q
  2. //mian.c, V5 C, T- v/ d9 n# P5 g4 N

  3. . i9 n" s) x% j* u5 t* [
  4. #include "main.h"
    & t3 X" p5 s# a. m' e* y
  5. #include "stm32f4xx_hal.h"
    , \2 z: @' `+ H1 H2 j) ?
  6. #include "i2c.h"( }2 r; E7 F) T7 Z- c) w
  7. #include "usart.h"
    " J( N5 g7 R8 I0 F) D( N/ `# Z
  8. #include "gpio.h"$ Z* f) e! [: q" x% V# k) }$ M

  9. ! ]4 m/ A: ?% L9 _4 e, T+ t% R
  10. #define ReadAddr   0xA1
    & i) ?+ j" R0 o6 U
  11. #define WriteAddr  0xA0
    . s2 c6 \) n- W5 b8 a6 C0 t- M

  12. & f! v3 ^2 r, W: a- h4 k& Y8 I
  13. uint8_t Wbuf[20] = "EEPROM TEST OK!";+ Z. q) C; S3 y% ^4 f
  14. uint8_t Rbuf[20] = {0};
    3 M+ j0 ?: B$ @  ?" X' P

  15. , \1 s/ j  Y& y  E
  16. /*********  24C65写数据函数*****************************/
    9 r/ \& o) o6 [. q- M
  17.   \. c' i; _; G' w
  18. void  Eeprom_Write(uint16_t MemAddr, uint8_t *Wbuf, uint16_t len ){
      f7 L) U4 p5 N9 V1 c+ h5 T
  19.         while(len--){
    5 h5 s- x, k5 |4 t: b
  20.         //I2C_MEMADD_SIZE_16BIT表示存储单元大小8 v$ x- U/ Z$ ^; |2 V5 }( Z
  21.         //默认为两个参数,分别是I2C_MEMADD_SIZE_16BIT和I2C_MEMADD_SIZE_8BIT
    . m" W+ h/ c9 {. v
  22.         //由于24C65的存储地址是16位的
    + ?( S; F0 h8 ]6 w8 M" ?! x. ^
  23.         //所以我们选择I2C_MEMADD_SIZE_16BIT. W  q% X- i. e& O5 A
  24.         //1表示一次写一个字节7 L2 H4 W% a9 `8 F
  25.                 while(HAL_I2C_Mem_Write(&hi2c1, WriteAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Wbuf, 1, 100) != HAL_OK){};
    4 }* C6 w$ j: S7 E
  26. MemAddr++;
    1 f: o& ]! F, F% n7 B
  27. Wbuf++;
    8 u- K! \0 c  I+ P
  28. }
    2 w/ E- C/ h( K; K' R, x  U
  29. }
    1 R8 T: b4 z9 v' T! e. V
  30. : I! d  C2 R; S% t6 `* ~5 p/ i( D
  31. 1 t- h5 J& n6 D0 K6 F! ]4 V
  32. /*********  24C65读数据函数*****************************/; R# h6 `9 E0 H; h2 o/ q

  33. " ]. k  Y% D4 ]3 Y8 v
  34. void  Eeprom_Read(uint16_t MemAddr, uint8_t *Rbuf, uint16_t len ){. P) S2 R/ U! q) _0 b2 {2 x8 I
  35.         //可以连续读,所以无需循环
    + b" ?# n! j9 s) u& ]
  36.         while(HAL_I2C_Mem_Read(&hi2c1, ReadAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Rbuf, len, 100) != HAL_OK );# j2 ?; G  ^* I* N& e
  37. }
    & S; X6 L' B# l! T" }. ?
  38. int mian(){+ F- c- \/ u5 Y) {. ?, \2 K* M7 u% Z
  39.       MX_GPIO_Init();0 f. \& D8 h  }
  40.       MX_I2C1_Init();7 s4 p2 |# z0 ]' \3 @. T, R) M5 H
  41.       MX_USART1_UART_Init();
    5 S. Y$ s( ]' B* h# a* N
  42.       printf("this is i2c eeprom test\n");6 ^+ f3 d3 v7 Y, t5 A" Z

  43. # S" o+ v5 R6 K, S- h
  44.       Eeprom_Write(0, Wbuf, sizeof(Wbuf) );
    ; R/ w" H- x8 ^4 H+ G8 E! W+ r
  45.       HAL_Delay(500);
    2 Z8 C0 Y) r1 ~3 b) v) D3 P
  46.       Eeprom_Read(0 , Rbuf, sizeof(Rbuf));
    " Q. s. p" W# F6 K* o
  47.       printf("READ:  %s\n", Rbuf);/ Z" Q8 f4 b- R  E% f# e, C$ G
  48. 2 J" z3 }3 F5 M' _, u' x8 T
  49.       while(){3 W# `+ g1 e0 P7 s7 l  ^) G- u

  50. ' e0 M( k, L: ]# l" ?* B' r7 f
  51.       }+ c/ U/ ~- ~6 W$ s
  52. }
复制代码
8 \* O/ ]# Z( o4 i& Q6 c
  • STM32 SPI总线通信专题讲解

    , `- r& o+ D  a  F0 h
SPI接口是Motorola 首先提出的全双工三线同步串行外围接口,采用主从模式(Master Slave)架构;支持多slave模式应用,一般仅支持单Master。时钟由Master控制,在时钟移位脉冲下,数据按位传输,是高位在前还是低位在前是可以配置的,配置时根据从设备的通信进行相应配置,一般是高位在前,低位在后(MSB first)。SPI接口有2根单向数据线,为全双工通信,目前应用中的数据速率可达几Mbps的水平。
SPI总线被广泛地使用在FLASH、ADC、LCD等设备与MCU间,要求通讯速率较高的场合。

# N4 h2 A% Z2 V% ]( ^) w
微信图片_20230412162643.png

2 W% I7 f  M) h, m; \7 G' G* m7 J
SPI接口共有4根信号线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。
) f$ C: {  W! e; s' I5 D
微信图片_20230412162658.png

. O9 }, g# [- o- {2 c; p; R5 G
(1)MOSI:主器件数据输出,从器件数据输入,连接从机的MOSI,与串口不同,串口需要反着连接(Rx-----Tx)
(2)MISO:主器件数据输入,从器件数据输出,连接从机的MISO
(3)SCLK :时钟信号,由主器件产生
(4)/SS:从器件使能信号,由主器件控制(片选),一般情况下为地电平选中设备,高电平释放设备。

7 s8 J; @+ l2 C7 C7 f, `5 m' Y
  • SPI总线协议

    0 S9 D: w( V9 u1 g  X* R0 V

4 Y  ]8 ~2 V% \" g* V  Y
1.数据交换逻辑:主机和从机都包含一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节发起一次传输。寄存器通过MOSI信号线将字节传送给从机,从机也将自己的移位寄存器中的内容通过MISO信号线返回给主机。这样两个移位寄存器中的内容就被交换了。从机的写操作和读操作时同步完成的,因此SPI成为一个很有效的协议。

- t( J% Z+ I/ `* k/ l5 w" p
如果主机只想写不想读,只需把数据放在数据寄存器,SPI控制器会自动传给外设,同时忽略掉外设传过来的数据即可;如果主机只想读不想写,主机写给外设一个空字符或者随便写一个数据,外设就会把数据传过来,不管是只读还是只写,主机与外设的读和写都h会发生且同时进行。

8 {# P" S% e% }- d4 `! m5 B3 H4 I
微信图片_20230412162734.png
1 e% N2 s: ^7 p7 p* b
2.起始信号:  NSS信号线由高变低,是SPI通讯的起始信号。
- M+ ~; |6 Y; F& b
3.结束信号:NSS信号由低变高,是SPI通讯的停止信号。

* z0 `1 S) N! k1 m. x5 A
4.数据传输:SPI使用MOSI及MISO信号线来传输数据,使用SCK信号线进行数据同步。MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,按位传输,且数据输入输出是同时进行的。SPI每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制,要么是8位,要么是16位,可以配置。

$ B! ^' t& I2 x7 q9 S% N/ l+ ^
微信图片_20230412162755.png
+ U; o1 y' }! I; {; @" A% X! d
  • SPI的4种通信模式

    " i+ `7 v. |0 I5 s) _
& d6 X. e" ?- u$ L
在SPI操作中,最重要的两项设置就是时钟极性(CPOL)和时钟相位(CPHA)这两项即是主从设备间数据采样的约定方式。由CPOL及CPHA的不同状态,SPI分成了四种模式,主机与从机需要工作在相同的模式下才可以正常通讯,因此通常主机要按照从机支持的模式去设置。同样在配置时一定要弄明白从机支持什么通信模式进行相应的配置。
7 [  s. O( N  Y. h9 h0 p) C9 K: g
13.png

& V5 |1 m. l  @1 j
1.时钟极性CPOL : 设置时钟空闲时的电平:
a.当CPOL= 0 ,SCK引脚在空闲状态保持低电平;
b.当CPOL= 1 ,SCK引脚在空闲状态保持高电平。

1 R' S/ f+ W- d
2.时钟相位CPHA :设置数据采样时的时钟沿:
a.当 CPHA=0时,MOSI或 MISO 数据线上的信号将会在 SCK时钟线的奇数边沿被采样
b.当 CPHA=1时, MOSI或 MISO 数据线上的信号将会在 SCK时钟线的偶数边沿被采样
, A( l7 Q, t5 S, M1 T
微信图片_20230412162837.png
& e! d5 G0 @& \0 B$ z. q
11.png
  • STM32F4-SPI控制器特性

    1 p8 M3 D& }( ]# p, F7 E
12.png

8 {1 ]. R% e, v% y, C1 ^/ a
1.通讯引脚:
STM32F4芯片最多支持6个SPI外设控制器,它们的SPI通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚,以《STM32F4xx规格书》为准。f407只有SPI1、SPI2、SPI3。
+ ^+ R$ [& _' E# k( C) O, g8 q
微信图片_20230412163014.png

! V3 w% X7 |: i8 f
其中SPI1、SPI4、SPI5、SPI6是APB2上的设备,最高通信速率达42Mbtis/s,SPI2、SPI3是APB1上的设备,最高通信速率为21Mbits/s。其它功能上没有差异。
7 a% ]% b% h1 s4 i* `* c
2.时钟控制逻辑:
SCK线的时钟信号,由波特率发生器根据“控制寄存器CR1”中的BR[0:2]位控制,该位是对f pclk 时钟的分频因子,对f pclk 的分频结果就是SCK引脚的输出时钟频率。
9 B+ r7 k  Q+ f% y! l
微信图片_20230412161739.png

0 {1 j7 ?" m! a) u$ o( w; g( K+ z$ U+ M
其中的fpclk 频率是指SPI所在的APB总线频率,APB1为fpclk1 ,APB2为fpckl2
0 _4 I0 Z$ O* T8 X1 |8 S
3.数据控制逻辑:
STM32F4的MOSI及MISO都连接到数据移位寄存器上,数据移位寄存器的数据来源来源于接收缓冲区及发送缓冲区。
) F; h* o& ?! i
a.通过写SPI的“数据寄存器DR”把数据填充到发送缓冲区中。
+ L* z  F/ h/ L
b.通过读“数据寄存器DR”,可以获取接收缓冲区中的内容。

2 ^( Y( ]; `* J* p6 t& y* S. k  W* h
c.其中数据帧长度可以通过“控制寄存器CR1”的“DFF位”配置成8位及16位模式;配置“LSBFIRST位”可选择MSB先行(高位在前)还是LSB先行(低位在前)。
- d' n  Z7 {! E; ~/ m; h/ G6 _9 w8 J
4.整体控制逻辑:
a.整体控制逻辑负责协调整个SPI外设,控制逻辑的工作模式根据“控制寄存器(CR1/CR2)”的参数而改变,基本的控制参数包括前面提到的SPI模式、波特率、LSB先行、主从模式、单双向模式(同时发送和接收、只发送关掉接收、只接收关掉发送)等等。
, R1 T0 U+ o) S; G1 v) h
b.在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,只要读取状态寄存器相关的寄存器位,就可以了解SPI的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生SPI中断信号、DMA请求及控制NSS信号线。

+ q. z0 ^6 p5 s$ T/ k8 b- x" z: r* h
c.实际应用中,一般不使用STM32 SPI外设的标准NSS信号线,而是更简单地使用普通的GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号。
  • 串行FLASH_W25X16简介
      A( f. P$ n9 k7 w) F
' i' ~/ f; r. j* O
FLSAH 存储器又称闪存,它与EEPROM都是掉电后数据不丢失的存储器,但FLASH存储器容量普遍大于 EEPROM,现在基本取代了它的地位。我们生活中常用的 U盘、SD卡、SSD 固态硬盘以及我们 STM32 芯片内部用于存储程序的设备,都是 FLASH 类型的存储器。在存储控制上,最主要的区别是FLASH 芯片只能一大片一大片地擦写,而EEPROM可以单个字节擦写。
. ^# v  y2 E- \; k5 |0 Y
W25X16有8192个可编程页,每页256字节。用“页编程指令”每次就可以编程256个字节。用扇区擦除指令每次可以擦除16页,即一个扇区包含16页,用块擦除指令每次可以擦除256页,用整片擦除指令即可以擦除整个芯片。W25X16有512个可擦除扇区或32个可擦除块。
- V: S( O+ Q3 G% S" G& O" V
微信图片_20230412161735.png

- ~) O# w3 X3 S. P- U: i
1.W25X16的硬件连线如下:
1 l* O% H( d5 @8 }9 l2 A) e
微信图片_20230412161643.png

; g' ?0 g/ P. s8 i* P# g% ?1 _
CS:    片选引脚,低电平有效,连接到STM32-PH2管脚
SO:   连接到STM32-PB4管脚(MISO)
SI:    连接到STM32-PB5管脚(MOSI)
CLK:  连接到STM32-PA5管脚(CLK)
WP:   写保护管脚,低电平有效,有效时禁止写入数据。接电源未使用
HOLD: HOLD 引脚可用于暂停通讯,该引脚为低电平时,通讯暂停,未使用

" R9 x. m# U8 X# a
2.W25X16控制指令:
我们需要了解如何对FLASH芯片进行读写。FLASH 芯片自定义了很多指令,我们通过控制 STM32利用 SPI总线向 FLASH 芯片发送指令,FLASH芯片收到后就会执行相应的操作。
而这些指令,对主机端(STM32)来说,只是它遵守最基本的 SPI通讯协议发送出的数据,但在设备端(FLASH 芯片)把这些数据解释成不同的意义,所以才成为指令。

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

. V% O4 z1 e7 s# c  s
微信图片_20230412161715.png
% v) ^( a7 Z/ `+ V6 ]2 z
b.写使能命令(06H):在向 FLASH 芯片存储矩阵写入数据前,首先要使能写操作,通过“Write Enable”命令即可写使能。
2 g! e: c! ~( P; Q4 {5 \5 j3 E
微信图片_20230412161711.png
$ T: s, [; g+ |
c.扇区擦除(20H):由于 FLASH 存储器的特性决定了它只能把原来为“1”的数据位改写成“0”,而原来为“0”的数据位不能直接改写为“1”。所以这里涉及到数据“擦除”的概念。

0 y1 W/ ^( w6 r2 @2 X9 t
在写入前,必须要对目标存储矩阵进行擦除操作,把矩阵中的数据位擦除为“1”,在数据写入的时候,如果要存储数据“1”,那就不修改存储矩阵 ,在要存储数据“0”时,才更改该位。
# O6 B6 d5 a( }# a# o/ }
微信图片_20230412161707.png
微信图片_20230412161704.png

) W8 ?' T$ a5 X# e
d.读状态寄存器(05H):FLASH 芯片向内部存储矩阵写入数据需要消耗一定的时间,并不是在总线通讯结束的一瞬间完成的,所以在写操作后需要确认FLASH芯片“空闲”。我们只需要读取FLASH芯片内部的状态寄存器SRP的S0即可(当这个位为“1”时,表明 FLASH芯片处于忙碌状态,它可能正在对内部的存储矩阵进行“擦除”或“数据写入”的操作)

$ M' v  p: k: [6 l. T% H
微信图片_20230412161658.png
7 V* ~" [8 L5 h% O7 a
微信图片_20230412161654.png
3 {( q' ]% O6 W2 u! G2 k
e.读数据(03H):读数据指令可从存储器依次一个或多个数据字节,该指令通过主器件拉低/CS电平使能设备开始传输,然后传输“03H”指令,接着通过DI管脚传输24位芯片存储地址,从器件接到地址后,寻址存储器中的数据通过DO引脚输出。每传输一个字节地址自动递增,所以只要时钟继续传输,可以不断读取存储器中的数据。
0 ~, C! j9 q, t" {; q
微信图片_20230412161650.png

! o2 H- A( @+ d
f.写数据——页编程(02H):页编程指令可以在已擦除的存储单元中写入256个字节。该指令先拉低/CS引脚电平,接着传输“02H”指令和24位地址。后面接着传输至少一个数据字节,最多256字节。

7 P# m/ z2 ^+ \1 v1 w* {( G# h
注:当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。
% S& F7 T8 `- i4 R
微信图片_20230412161646.png
$ o" x( \/ T! [  f& I/ h. d8 r& z7 r
  • STM32 SPI_FLASH基本配置和操作
    # t2 S( D3 ~% k* r# x
7 b) I/ v$ K& ]5 T3 C. `
根据如下的硬件连线图进行配置

" `3 P( [8 i0 |% i- c& g/ F2 C
微信图片_20230412161729.png

0 @, s; U0 k2 k5 |5 _$ C- b
步骤:
1.使能时钟RCC

; x: q8 p- \+ h. L6 U
2.使能SPI1,配置相应管脚

! W  ^7 }$ w4 m
微信图片_20230412161640.png
/ t, _$ {! W0 q2 z& G
微信图片_20230412161637.png

2 r3 w' c+ `- V+ F& s- {8 \2 D
3.配置SPI协议

+ |& s7 p  J- X, S& y) S
微信图片_20230412161632.png

3 m/ R& {: W& r/ j9 A  _0 W0 S

. a8 f" |. J1 J8 l
4.编码
  1. //main.c
    ) C5 \$ ?+ T+ s5 p; v6 ^

  2.   @, |1 W- b/ J7 ?
  3. #include "w25x16.h"
    3 y" v8 {3 s; w; m7 n" q1 l6 L
  4. 1 K: X" K0 \" L" @7 n
  5. uint8_t RD_Buffer[5000] = {0};; O3 C. b/ {2 D( K9 a/ e4 b7 n8 c: W
  6. uint8_t WR_Buffer[5000] = "SPI FLASH WRITE TEST\n";
    + H0 O3 B1 X/ |1 k
  7. 1 Y# C3 \, J; a2 e6 A! x$ z
  8. int main(){
    0 x: C( ~  ]/ W! T! Z
  9.     uint16_t FLASH_ID = 0;1 x5 e5 Q/ A0 U) y2 h% `
  10.     uint32_t i;
      a, ?+ r# S7 |. q0 N
  11.     ; a# H  o1 R5 J& B: v3 \5 F# p8 P
  12.     MX_GPIO_Init();4 c* ?( j5 d! @9 U2 P
  13.     MX_SPI1_Init();
    4 O: i! L- ?6 C2 i
  14.     FLASH_ID = sFLASH_ReadID();- h7 w$ E  g0 u- }! H: N" v( j3 ^: i
  15.     : r3 F, X, i& M
  16.     /******测试擦除******/2 b. [5 }$ Y- ?% d
  17.     sFLASH_EraseSector(4096*0);: V. k9 ^' B& \: H; j
  18.     //sFLASH_EraseSector(4096*1);
    9 e: w- K5 q; i) R3 k  w
  19.     sFLASH_ReadBuffer(RD_Buffer,0,4096);  [  z6 y3 ?4 ]6 ?) f# d! o/ l
  20.     printf("读数据开始\n");% q$ T. ]* X8 \5 ~# f
  21.     for(i=0; i<4096; i++)
    - x9 c' u: o1 _& E3 ?6 C
  22.     {3 o: M" D) p: V2 L$ V* u- d3 H
  23.     printf("%x ",RD_Buffer[i]);
    : l  ], l% \6 S2 M
  24.     }
    % D. `% ?: a; ^, X& y
  25.     printf("读数据结束\n");2 U, {0 k8 k  J; m  ]% \. H
  26. 0 U3 ?1 M5 |$ R1 h8 _! w& \; ?
  27. # F7 _6 ?5 h9 m( ]- B$ `
  28.     /******测试写操作1*****/7 J& Q8 R) V6 [9 I9 r
  29.     //写之前都需要擦除扇区* `2 Z2 r# _4 K# l1 Y
  30.     sFLASH_EraseSector(4096*0);
    $ U% j; z% Q! w9 ?& |# M
  31.     sFLASH_WritePage(WR_Buffer,0, 20);
    6 [1 v7 S: k* X" g: g  l
  32.     sFLASH_ReadBuffer(RD_Buffer,0,20);
    6 i& x- a# R* z; ?+ k6 w. Y# n9 S
  33.     printf("READ DATA: %s\n",RD_Buffer);2 K' |6 ]& G% u9 O6 L/ Z
  34.    
    % H! K4 k$ Y, W: t6 {3 w4 [2 r8 |  l
  35.     /******测试写操作2*****/
    ; t4 w, e% L6 b6 ?9 ~/ K
  36.     //写之前都需要擦除扇区
    9 V& t$ D7 U4 X# x8 M& g, T
  37.     sFLASH_EraseSector(4096*0);
    3 r& w! r* E4 {! a7 b0 ?
  38.     sFLASH_EraseSector(4096*1);                            7 H! y4 T% D5 i" s' Y% x8 g2 e
  39.     for(i=0; i<4096; i++)* |; j$ ^. N2 h3 ?1 @- s+ d! ?; |' c
  40.     {3 o) F6 k# v6 k; D9 X1 {4 S$ T( R
  41.     WR_Buffer[i] = 0x55;
      P! T  s3 j% s/ V& z$ A
  42.     }% Y3 B1 i0 i0 `6 M* \+ V, `. t' x
  43.     sFLASH_WriteBuffer(WR_Buffer,4090, 1000);( H7 q- H5 d" [+ s6 X* v! Q
  44.     sFLASH_ReadBuffer(RD_Buffer,4090,1000);
    # }; ], [: ~" Y, i1 F) R; Z3 v+ L
  45.     for(i=0; i<1000; i++)
    , @7 e+ a# h; y6 a- w* ]& q% j
  46.     {5 Q: u1 O8 Z* F9 U1 O, q3 A/ p
  47.     printf("%x ",RD_Buffer[i]);
    * b# @, M' M  K5 i/ i" h* O
  48.     }
    ( u/ ~$ \9 k+ j0 y  v) Z5 X9 Q
  49.     /*****************/' P) U5 {$ G: s/ V
  50.     while(){}
    ( |8 n/ J  x5 t5 l  ~# G& {1 n/ ]
  51. }
复制代码
  1. //w25x16.h
    & |1 x  L& P( K0 ^4 h

  2. " k( S) z# H2 K0 e" ~
  3. #ifndef __W25X16_H* |) h4 M4 g7 [, J! Z+ w" j
  4. #define __W25X16_H
    2 @4 h8 g# c9 f
  5. 7 [. O0 w! n9 k  {5 k" z+ d
  6. #include "stm32f4xx_hal.h"1 ~9 C2 N5 h/ d! |/ i  c

  7.   f, e5 K9 ^7 C+ j" y- J
  8. //使用宏定义芯片指令
    . |# M4 Q  d4 |% {5 {
  9. #define W25X_ManufactDeviceID  0x90  /* Read identification */8 q) Q4 H+ a' z, Z7 K2 f
  10. #define sFLASH_CMD_WREN        0x06/* Write enable instruction */0 h9 ^* C% h& l. z  A1 W
  11. #define sFLASH_CMD_RDSR        0x05/* Read Status Register instruction  */
    ' ^  k7 G" `9 k  ~/ k
  12. #define sFLASH_CMD_SE          0x20/* Sector Erase instruction */
    & U' M+ A6 N& ^" U% j9 x
  13. #define sFLASH_CMD_WRITE       0x02  /* Write to Memory instruction */$ k  Z. P4 V7 q
  14. #define sFLASH_CMD_READ        0x03/* Read from Memory instruction */
    , O, S8 h! ]+ ?* u! Z
  15. #define sFLASH_DUMMY_BYTE      0x00 //空字节,用于只读传回来的数据7 z5 L$ s3 V2 w& c
  16. #define sFLASH_BUSY_FLAG       0x01' T: v  p: c( p7 b9 \  M' p
  17. #define sFLASH_SPI_PAGESIZE    0x100
    - _, Z2 R- h: R$ y

  18. : \+ o0 [; ~- n4 g
  19. /* 选中芯片,拉低信号 */
    5 \8 ]. W! K$ }2 T3 s# |$ I9 h
  20. #define sFLASH_CS_LOW()       HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_RESET). a" F* y) M% L/ G3 S( x( W# n0 o+ U
  21. /* 释放芯片,拉高信号 */
    ; `& y& m9 y/ E3 S! c; C
  22. #define sFLASH_CS_HIGH()      HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_SET)  H6 l  p6 E7 [" I

  23. 9 q9 q. K9 o- _4 l
  24. //定义函数
    2 ]& @. }1 C6 D& _7 _% U! ?; F* y
  25. uint8_t sFLASH_SendByte(uint8_t byte);
    $ u( i9 V2 a2 V* Y9 I2 U
  26. uint16_t sFLASH_ReadID(void);; {  f. {: G3 N; c/ h0 O7 W+ _
  27. void sFLASH_EraseSector(uint32_t SectorAddr);
    ! T5 z2 t) }/ z
  28. void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);
    8 H5 i% O; O$ A, ^
  29. void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);
    5 _7 r+ M! G- C2 T
  30. void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead);
    / M- ^) z; T) d# F) r
  31. #endif
复制代码
  1. / ?# p+ }2 Z! W7 Z+ d
  2. //w25x16.c
    ! r* ]$ A, n& R0 d3 Q1 b4 R
  3. ; V9 l/ g% o0 M- r0 i
  4. #include "w25x16.h"1 H- l2 F: w6 L9 C- K

  5. $ m4 C- }- j. `: r7 B
  6. extern SPI_HandleTypeDef hspi1;
    ' C# }/ `& S6 t8 R8 a3 S

  7. ) u2 U5 L! }! s, S3 K% U4 q& P
  8. /*读写一个字节函数,因为SPI读和写同时完成*/
    1 R* B: U8 _" O' g5 m$ b  P7 Z
  9. /*发送数据一定会接收到一个数据*/
    4 _/ ?: V& }6 c- t) B( b
  10. uint8_t sFLASH_SendByte(uint8_t byte)( U5 T8 g% ^( x" r( d
  11. {
    ( i5 [; E8 ]5 K( S' N% `0 s% ?9 K
  12.     uint8_t TX_DATA = byte;
    6 N" w% C1 Z7 ~" Q8 C: ?: B+ G
  13.     uint8_t RX_DATA = 0;1 I3 }; z0 i4 _$ x+ h% f& ^
  14.     HAL_SPI_TransmitReceive(&hspi1, &TX_DATA ,&RX_DATA , 1, 1000);
    ! L( L8 J7 u5 ^! e
  15.     return RX_DATA;' M5 u! P* R. B6 l
  16. }
    $ p9 T2 N% J1 n0 c* T' @* T. a
  17. % @; {6 p9 m5 E9 K5 c" z
  18. /*等待擦除或者写数据完成*/: F  |3 Q6 N8 ~2 H% M) d- Q  P9 t1 [
  19. void sFLASH_WaitForEnd(void)
    ' m: j. n5 D- M) V8 \
  20. {  B( w' W) U" z! B( U
  21.     uint8_t sr_value = 0;" S7 z, S' B. ~( g+ z7 P7 v8 E2 e
  22.     sFLASH_CS_LOW();4 o1 V) _1 c/ k/ I
  23.     sFLASH_SendByte(sFLASH_CMD_RDSR);
    ! S+ a* X" P& D
  24.             //读S0的值,为1表示忙碌,为0表示停止          1 T; ?4 N! G3 r6 Z' d+ N# y
  25.     do{9 v; u  k1 s2 d5 C! }/ l
  26.                 //发一个空字节,得到S0的值             - O6 Z* \' g( U8 t
  27.                 sr_value = sFLASH_SendByte(sFLASH_DUMMY_BYTE);, Y# @, w7 E' N- d
  28.     }while( sr_value & sFLASH_BUSY_FLAG);
    ; b6 G" A1 ?: y3 `: ^
  29. # {5 |+ Q' r- e% ^* f0 R1 ~
  30.     sFLASH_CS_HIGH();1 [! N2 ^' \& F" f
  31. }
    * E/ I% C+ q1 l" P$ L5 ]

  32. 5 n& P7 e! A: t0 o
  33. void sFLASH_WriteEnable(void)% b/ j( p) O7 W7 b
  34. {- j) A  o' U1 D: X# {
  35.     sFLASH_CS_LOW();
    : ~) q! K# v$ }7 w! A- R$ {2 _
  36.    
    & Z$ b. R6 A' v! m" R
  37.     sFLASH_SendByte(sFLASH_CMD_WREN);
    0 l' b9 `- ]8 M" h* d6 r
  38.     4 E5 [! n- f  T/ }, Z
  39.     sFLASH_CS_HIGH();/ X5 v# [$ c! C; W0 d
  40. }
    - c) T+ A( c9 n1 d2 C) o
  41. * \% s' t6 G8 y2 \- N7 r  W
  42. " N" D; v; P) A& _) L' ], M# p- I5 B& M5 E
  43. /*读设备ID*/
    ( f8 }& W* K8 F8 w$ E
  44. uint16_t sFLASH_ReadID(void)- N  m. Z/ ~. J5 |3 l# i0 B: {
  45. {
    / D8 o- @: w+ q; `1 I2 \
  46.     uint16_t FLASH_ID;) R0 g4 r' i, W* J: F
  47.     uint8_t temp0,temp1;
    + S' M5 W* Q& z+ E# e, L8 f0 h
  48.    
      I5 ~4 W# J1 W+ l5 l
  49.     + h- H1 e; p1 H8 ]1 r, W& h7 y
  50.     sFLASH_CS_LOW();6 h6 w3 x+ X% G6 U; @
  51.    
    : j8 ?, b6 m8 [2 R+ o. i
  52.     sFLASH_SendByte(W25X_ManufactDeviceID);' _- K1 Q3 \( g$ g; _
  53.     //读设备指令后要发24位地址,所以要发三次. W+ T  }8 ?8 A% s1 M
  54.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);" j7 M6 d! _5 i, v
  55.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);% M: K: ?( u  L" v3 g7 h$ O6 @  B' b
  56.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    6 Z/ a0 J- i; s/ ]2 O
  57.     //制造商ID
    / l- W$ x0 k3 c, m0 g/ ~6 }% \7 z
  58.     temp0 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);% k6 N1 B: ~6 q! z, A2 C8 `
  59.     //设备商ID
    . I& V% x4 g* M
  60.             temp1 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    & ]! m: h0 f% B
  61.     + N8 z, t9 N% `* O% c& B
  62.     sFLASH_CS_HIGH();4 Y- j' I1 [- l+ u9 A) s+ t
  63.    
    7 I! }7 u* l+ o# b" M, J
  64.     FLASH_ID = (temp0 << 8) | temp1;
    3 d) t4 d; e2 k" Q2 R
  65.     . W" x4 U( K6 _( X
  66.     return FLASH_ID;
    8 @$ E4 e2 O. E; m
  67. }
    ' ~+ {6 H# i& {7 y3 g) Z

  68. ; `) |' o+ J5 y3 [; G9 n  G
  69. //擦除扇区,擦除为1,因为只能由1变为0 ,不能0变1" ~. S! d( o0 ]6 W1 X$ N
  70. void sFLASH_EraseSector(uint32_t SectorAddr)1 k5 ~9 s* k% a; O: R
  71. {
    0 a9 E- c3 _  {: U( V9 Z' m: [  |0 P
  72.     //SectorAddr表示擦除第几个扇区) W& b! J% a) \4 ^% p6 O
  73.     sFLASH_WriteEnable();  //开启写使能
    ) @0 S& T0 h3 O3 h1 C+ I7 |- @
  74.     sFLASH_CS_LOW();//拉低,片选
    4 H$ C$ n$ [3 I3 ?! c5 @* w" f
  75.     //擦除命令
    . I3 i/ `$ x) T: m
  76.     sFLASH_SendByte(sFLASH_CMD_SE);/ q$ a! h% u; m$ ]
  77.             //传24位地址
    : o0 o) @" b& F9 g
  78.             //传送高8位,将中8位和低8位一共16位移出去,得到高8位                   2 [" B* K* v: P9 Z( F1 Z- Y
  79.     sFLASH_SendByte( (SectorAddr>>16) & 0xff);   ) |+ z9 e; B$ R  H" o# K9 ]' ?  q- M2 |
  80.     sFLASH_SendByte( (SectorAddr>>8) & 0xff);    //传送中8位6 ?. }3 M! t0 C$ E8 x
  81.     sFLASH_SendByte( (SectorAddr>>0) & 0xff);    //传送低8位
    ( m# `1 i* Y6 u. `- C
  82.      6 Y9 Z( |* E; H* p# u
  83.     sFLASH_CS_HIGH();' M; }7 o/ _7 X
  84.     8 w5 @: |; U0 D, r4 n* O: d
  85.     /*读状态寄存器,等待擦除完成*/
    % C% X9 b; Q% r; o1 K8 r8 J1 q8 h# x
  86.     sFLASH_WaitForEnd();3 n6 }$ ?. ]6 [! q& u% h' g" h
  87. }3 C4 f. b- }! T# T# `) U0 d6 b6 g

  88. 7 y7 E0 ?& G, i5 f8 m, m, |) ^
  89. //读数据# k4 Q& K  ^. @: Z' k
  90. //读命令和读地址发送后,芯片内部会自动不断递增读数据/ G. `; i; R3 d5 n' x
  91. void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead)3 R# ?$ Y* r" o6 \8 ~% n- x. R
  92. {
    , B* |; _6 O" r0 L! q% A
  93.     sFLASH_CS_LOW();- z( ^2 A, h6 s+ [/ A
  94.     $ Y/ f6 e2 y7 x* F4 Z- v
  95.     sFLASH_SendByte(sFLASH_CMD_READ);
    - \9 k, e/ H2 A, W0 H4 Y; ?4 B/ [
  96.     sFLASH_SendByte( (ReadAddr>>16) & 0xff);   //传送高8位4 J2 s" m- C# s: J
  97.     sFLASH_SendByte( (ReadAddr>>8) & 0xff);    //传送中8位
    ; ]/ |! Z( U* _8 ?
  98.     sFLASH_SendByte( (ReadAddr>>0) & 0xff);    //传送低8位* z9 u3 q1 `6 G- A! u6 y
  99.    
    . S: D# k& B$ l" g0 L+ u
  100.     while(NumByteToRead--)
    ! }0 E7 d9 B- H* `/ M+ q: D! P5 Y
  101.     {$ q. R( ^& {) c3 y! C9 g
  102.     * pBuffer = sFLASH_SendByte(sFLASH_DUMMY_BYTE);" Q  D2 d& `/ a$ l& _& J" ?
  103.     pBuffer++;
      N6 W, F( E) V' E5 U
  104.     }
    ( H' p7 `( }  [. A% z" a
  105.    
    $ P- w. G. y: R! N3 D
  106.     sFLASH_CS_HIGH();
    , F/ V: t5 r' G' F
  107. }
    9 J: g$ H, h8 W/ _6 P: e
  108. * \# @% i( r# Y: |2 @! M5 u
  109. //写一页最多只能写256个字节,一个扇区16页,一个块16个扇区                               # ^- m; e9 `9 X+ q2 e$ u5 X- W
  110. void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
    6 ~$ v7 _5 y$ }* \' ~; V4 w7 q
  111. {
    , A+ `( p. W* [; H9 h' k0 G
  112.     if(NumByteToWrite > sFLASH_SPI_PAGESIZE )3 a  T4 g  H6 e* Q0 W
  113.     {
    6 Q/ W1 |8 [" ~- L# D
  114.     NumByteToWrite = sFLASH_SPI_PAGESIZE;
    - o. i; g  d; T
  115.    
    1 u$ D0 o2 v# A) q
  116.     printf("写数据量过大,超过一页大小\n");
    ) s2 q. d; Q9 g/ G( c: w# J
  117.     }
    , G7 W) ^& R7 y5 L2 B' v4 f% M
  118.     2 h  J* a1 S7 J
  119.     sFLASH_WriteEnable();  //开启写使能$ b) N8 m6 [8 ^  ~+ U
  120.    
    % S# I5 z; {. _5 ]. l0 o
  121.     sFLASH_CS_LOW();5 k. F! T# J; T$ I4 {
  122.     + n! V; O: b9 C! E0 k3 x4 }2 n
  123.     sFLASH_SendByte(sFLASH_CMD_WRITE);
    " d4 a+ z' h  z/ H/ R+ J% g
  124.     sFLASH_SendByte( (WriteAddr>>16) & 0xff);   //传送高8位
    , z4 A  Y! o' m+ u7 m
  125.     sFLASH_SendByte( (WriteAddr>>8) & 0xff);     //传送中8位" P$ s; ]5 v+ ^: g
  126.     sFLASH_SendByte( (WriteAddr>>0) & 0xff);      //传送低8位. K' I. H1 \3 p# P
  127.     ) A8 c* |7 A, E8 l: `8 |
  128.     while(NumByteToWrite--)
    0 V: k! q9 F! k/ ]7 _( b; g
  129.     {
    * C& Q$ R8 G" X! C
  130.     sFLASH_SendByte(* pBuffer);
    4 C3 V6 @- R2 e6 u: `( [
  131.     pBuffer++;" v& T1 X, ^5 s! t- _$ c( x
  132.     }
    4 B+ E. L2 i& Y9 H+ {& J
  133.    
    : Y& k9 ^6 q( E& U8 J
  134.     sFLASH_CS_HIGH();
      Q$ ?& D1 |8 C3 A1 s9 {+ x
  135.    
    3 o6 b8 {& M3 F
  136.     /*擦除和写数据都涉及到写动作,一定要等待完成*/
    , U& v; C# R1 c" w0 M! x2 g
  137.     sFLASH_WaitForEnd();
    1 r+ s( E' v) }4 G: a
  138. }$ u. A; C* w% k  M0 c3 v

  139. ' w$ [2 o1 W! j+ F  Q
  140. //写任意地址、任意长度; u$ U" L: T% ^" I' x2 a
  141. void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)8 v" d# C9 E% b' t& C) _1 U) W
  142. {
    ) S1 S5 C0 z7 t! v9 S+ h8 I! Q7 ~: h
  143.     uint16_t NumOfPage, NumOfBytes, count, offset;7 t# }2 B( |% I0 y/ }, }
  144.     //求WriteAddr在某一页的位置        8 U" [; c/ v4 S( Z* S. G9 h; p
  145.     offset = WriteAddr % sFLASH_SPI_PAGESIZE;
    # H9 G7 I9 K9 Y. _: E& V8 C5 o
  146.     //求某一页剩余的大小 3 c. ?4 n) n7 K! r, F1 ?" n6 D6 \
  147.     count = sFLASH_SPI_PAGESIZE - offset;
    : v9 c0 g4 B, t+ F5 H- @- l
  148.     ! B5 O+ j* d# S4 \
  149.     /*处理页不对齐的情况,防止页内覆盖*/
    8 E8 ]# U5 ?  v% d) Q) x8 b
  150.     //先把某一页剩下的部分写掉,之后的就能新页的起始处开始写        /*offset有值表示需要页对齐,如果要写的字节数小于某一页剩余的部分,那就无需对齐*/                  
    8 \( r& t/ p3 z9 h9 Z
  151.     /*这两个条件必须同时满足*/
    # i" _! g2 P& r( U' V+ O
  152.     if(offset && (NumByteToWrite > count ))
    6 H! Z+ r; d* w  V8 h$ g5 o
  153.     {
    ) N0 L4 L$ B$ x* h" r4 x
  154.     sFLASH_WritePage(pBuffer,WriteAddr,count);
    " z; Q6 z+ V4 P' G
  155.     NumByteToWrite -= count;//去掉已经写了的,从新页开始
    5 b) C# e+ B3 p  S5 D9 T$ R
  156.     pBuffer += count;
    7 }4 c1 b$ a( x2 N
  157.     WriteAddr += count;3 v& `) l& j; w4 C! d& J
  158.     }# m& b4 @5 O6 Y
  159.    
    1 I/ G& q3 \- \' V1 x0 k
  160.     /*最多可分多少页*/
    / m) O$ ], n9 I3 S5 t6 A4 L! [
  161.     NumOfPage = NumByteToWrite / sFLASH_SPI_PAGESIZE;
    3 P; a# p# w, C, t( O) ?8 n
  162.     /*剩余多少字节*/
    $ A7 D. k/ l( H( _. A) R  a
  163.     NumOfBytes = NumByteToWrite % sFLASH_SPI_PAGESIZE;
    " B/ S5 e, _: J- c' x
  164.    
    $ }  l7 _# x" o' L1 v
  165.     if(NumOfPage)' a4 |- S+ |/ v: B  Z
  166.     {
    4 F6 x+ c& l% U% V8 y4 L& J
  167.         while(NumOfPage--)8 n# G1 l1 T5 T  E; M: k
  168.         {
    - R' `( e. u3 p2 A! y% I4 Y4 O
  169.              //每一页都发起页编程  }1 p) T7 [2 ^+ X: b/ x5 a
  170.              sFLASH_WritePage(pBuffer,WriteAddr,sFLASH_SPI_PAGESIZE);
    2 v7 g8 K3 e2 ^2 U  R
  171.              pBuffer += sFLASH_SPI_PAGESIZE;
      K2 y- A- d: m& o0 U4 p8 E) Y
  172.      WriteAddr += sFLASH_SPI_PAGESIZE;$ l/ [7 |( i8 `7 \# f
  173.          }
    " L0 q6 B, T- P. S2 w
  174.     }
    - F' y5 i- P9 h! j* U% p
  175.     & E5 o; B/ W& `, O
  176.     if(NumOfBytes)- ~7 i/ U: j8 v5 x. Q2 |+ b* C
  177.     {& K0 |5 b' h9 h6 _& z( E0 J5 Y7 m# C
  178.     sFLASH_WritePage(pBuffer,WriteAddr,NumOfBytes);9 Y0 s$ R/ G6 b% D5 G
  179.     }
    3 B1 b) `. n9 ]0 r. S5 w, o
  180. }
复制代码

2 U% R9 h1 k, |% @* D$ z' r0 d3 E2 p2 B
  • 为什么会有两种写操作函数,是因为这里的写操作有两个特点:
    % j, e4 P2 a% S; P8 c
    ' S' X" `% D- K
- k" l! }0 A% X3 B% t: o
1.无法突破页限制,超过一页需要重新发起页编程信号。另外如果要写的数据大于剩余一页剩余的容量,那么超出的数据会写到当前页起始地址出。例如,初始输入的写地址为200,而要写的数据大小为100,那么要写的前56个字节会从地址200开始依次写入,剩下的44个字节会从当前页的0地址开始依次写入,这很有可能覆盖之前的数据。

: M% }) C  P7 d+ F
2.无法突破扇区的限制,当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。

1 b9 T% o/ l7 e; \& E
转载自: 骆驼听海
" T4 {. w9 C2 {8 q9 Q- x& L3 @如有侵权请联系删除$ D5 V' i3 x7 C, \2 H/ B2 ]" f
7 G" Z; {9 l4 g# R" h) j( e& c
赞 收藏 评论0 发布时间:2023-4-12 16:18

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版