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

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

[复制链接]
攻城狮Melo 发布时间:2023-4-12 16:18
( |; o- ~; v% c- y4 X! \
微信图片_20230412161833.jpg

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

: U" W: G2 c  R( u
  • STM32 I2C总线通信专题讲解
    8 I( _/ u2 }2 {& g

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

    ! B5 w9 M2 X2 q* w

; H5 c% t" k- n( {2 ~6 |8 s3 k
  • 总线特征:

    5 r" ?0 q1 \% n% m, {- @. h& j: [5 H
1.两条总线线路:一条串行数据SDA,一条串行时钟线SCL(主从设备使用同一时钟,属于同步通信)来完成数据的传输及外围器件的扩展

$ U& o8 @; v8 y# v' x0 {
2.I2C总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址,通常是7位,有时候是10位

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

( j2 y& i3 u# H+ U
4.I2C总线上的主设备与从设备之间以字节(8位)为单位进行单双工的数据传输。

: h$ |+ `3 ^2 z$ T
  • 拓扑结构——总线型
      {1 ], _$ Q5 @1 G( p
I2C 总线在物理连接上分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成,SCL由主机发出,SCL越快,通讯速率越快。通信原理是通过对SCL和SDA线高低电平时序的控制来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。

$ g, P2 s# o. ?5 a3 ], c/ H
微信图片_20230412161906.png

/ y& y" [* T' ]3 p% I. v
  • I2C总线协议

    & d; W' @2 Q1 L; j, i
1.I2C协议规定:  总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生。
6 U% \/ i+ p- ~0 ^9 i- B
2.空闲状态:SCL和SDA都保持着高电平。

% a8 E/ e( _$ C" D) D
3.起始信号:  当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件,所有的从设备都能感受到这个跳变,做好准备等待被选择。
" v# U' o6 Y3 Y1 ]) D. _+ a4 b
4.结束信号:当SCL为高而SDA由低到高的跳变,表示产生一个 停止条件

0 C$ @$ M5 h  X" J: e/ d
微信图片_20230412161928.png

" f* R( y$ d( t6 ?2 E' b- Y8 B
5.数据传输:数据传输以字节为单位 , 主设备在SCL线上产生每个时钟脉冲的过程中将在SDA线上传输一个数据位,数据在时钟的高电平被采样这时候采集到是1就是1,是0就是0,所以在传输数据时,当时钟处于高电平时一定要保持稳定,时钟处于低电平时可以变换数据。(高电平采样,低电平变换)一个字节按数据位从高位到低位的顺序进行传输。主设备在传输有效数据之前 要先指定从设备的地址,一般为7位,然后再发生数据传输的方向位, 0表示主设备向从设备写数据,1表示主设备向从设备读数据。主从设备以字节为单位(8位)进行数据传输,开始传输数据时把从设备地址加上方向位组成一个8位的字节进行发送并接收一个应答。
' t# B( l9 ?6 h$ r, h2 W5 R4 l
6.应答信号:接收数据的器件在接收到 8bit 数据后,向发送数据的器件发出低电平的应答信号,表示已收到数据。这个信号可以是主控器件发出,也可以是从动器件发出。总之,由接收数据的器件发出。

2 B. N7 P, f$ G
微信图片_20230412161941.png

: U( [; i9 ]3 Q7 _
a.主设备向从设备写数据:
8 v" E( y& m8 J8 O7 V# v
微信图片_20230412161956.png
  ?. l" r& O6 }
b.主设备读从设备的数据:
, N$ w0 r# O5 Q  P# F" s
1.png
; ?. ]6 _* C$ x. ?- b
c.主设备读从设备的某个寄存器:读设备的寄存器首先应该对该设备发送写命令,很多设备都可以看成是一段内存,所以写命令写给从设备,指明要读取哪个地址(寄存器)的数据,接下来才是真正的读数据。不同的从设备是由区别的,在驱动I2C从设备时应当查明设备的时序图,又怎样的要求,不同的时序对应了不同的命令。

' \' B* ~7 p/ |3 }$ i
2.png

1 \; R7 z+ F* t3 Z8 J' y. W
) G) O* a/ n- F
  • STM32F4-I2C控制器特性

    $ X( S: p6 ~9 N3 ?9 t9 @
软件模拟I2C时序:由于直接控制 GPIO 引脚电平产生通讯时序时,需要由 CPU 控制每个时刻的引脚状态,所以称之为“软件模拟协议”方式。我们知道,驱动I2C设备只需要两根管脚,即使单片机上没有I2C控制器,根据协议控制每根管脚每一时刻的电平状态,一根模拟数据线,一根模拟时钟线,就可以驱动从设备,相对而言效率低,但是可以实现控制驱动。STM32内部具备专门的I2C控制器,使用时只需对其进行相应的配置即可。
$ H% g& L; `* u
硬件控制产生I2C时序:STM32 的 I2C 片上外设专门负责实现 I2C 通讯协议,只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并缓存起来,CPU只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种由硬件外设处理I2C协议的方式减轻了 CPU 的工作,且使软件设计更加简单。

; {8 w% F% {% b
控制器功能:配置主从模式(一般都把STM32当作主机使用,作为从机时应当对其赋一个地址),通过配置其内部的寄存器产生一些中断和错误信号,配置通信速率位标准模式、快速模式、超快速模式等
: J* b- r# D2 ^+ N. A/ g( }
STM32芯片有3组I2C外设,可以同时进行3组I2C传输。它们的I2C通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚。

  ]* f6 j% a( Y  H; W6 u
微信图片_20230412162110.png
2 q" i" F; v; c6 m" g8 j
  • EEPROM(AT24CXX)存储芯片介绍

    / r, ^- b! r' u' ?9 Y  X
一个典型的I2C接口的从设备,专门用于存储数据的芯片。EEPROM (Electrically ErasableProgrammable read only memory),带电可擦可编程只读存储器,一种掉电后数据不丢失的存储芯片。EEPROM可以在电脑上或专用设备上擦除已有信息,重新编程。

5 x- J& }0 c( J! o3 E" T' ?7 T
EEPROM常用来存储一些配置信息,以便系统重新上电的时候加载之,容量不会很高。EEPOM 芯片最常用的通讯方式就是I2C协议。XX表示容量,常用值为01、02、04、16、32、64等,单位Kbit。一般的存储芯片都具有写保护功能,对WP管脚加一个高电平就开启了写保护功能,就无法往芯片内写数据了。在开发中通常将该管脚接地,确保能够写数据
典型24CXX芯片引脚如下:

7 p+ g* A, V: y3 F( E: |# K- K
微信图片_20230412162134.png

- j+ K3 l, }( v$ o
例:24C65的设备地址为7位,高4位恒定为1010,低3位取决于A0-A2的电平状态,般主机在读写24CXX都是把设备地址连同读写位组合成一个字节一起发送。

  {, S7 [2 w/ E! ]
3.png

- |# U1 [' d2 l" _
24C65的电气连线如下,根据电气连线可知,A0-A2均接地,因此读地址为1010 0001,即0xA1;写地址为10100000,即0xA0 ,且WP接地,用户随时可向芯片内部写入数据。

: u* u- h/ {% n; Y; N$ ?9 R
4.png
7 h) h, Y, I. b  e  ~
24C65写时序:首先发送一个起始信号,接着发送从设备地址以及方向位,收到应答后,向从设备发送要写的存储区域的首地址,24C65的存储地址是16位,先发送高8位,收到应答后再发送低8位,再次收到应答后开始写数据。64Kbit大小位8K字节,需要13位即可表示,所以高3位固定定为0,如下图。

3 K" ?3 }, F( s6 c' o; p0 f) A
这里是BYTE WRITE,一次写一个字节,此芯片还支持PAGE WRITE,一次写一页,也就是8个字节,如果想写更多,可设置一个for循环实现。
2 ?( G& {, b- `2 E" j' W/ h
5.png
6 C( k( R8 n9 e) |8 w- l
24C65 读时序与写时序基本相同,只不过在读之前要发送再发送重复开始位进行读操作。

8 _( X* C6 B' a1 G. v: S
6.png

0 O' o# ~/ y! J! D
  • I2C读写EEPROM实例
    / x# Q. |. x+ C

- c8 {6 O' y, @  j+ `8 I
由电气原理图可知SCL和SDA分别接入了PB6和PB7管脚,读地址为1010 0001,即0xA1;写地址为10100000,即0xA0
. J  x! d  G. a+ w6 L
步骤:
1.配置RCC
, T) y$ V. c  Q( @% G
2.配置PB6和PB7管脚
4 ~6 G" s- R3 l6 |# `; ]; h% i& a; @
7.jpg
3 H$ Y5 o+ r1 J7 @/ X
3.配置I2C协议参数
1 h' k, O2 a0 N& ?6 c  \
8.png

* A; ^1 a. p1 z! f/ [
4.编写代码
  1. 6 @( b$ s3 M7 j+ W, x: [( \( f, k
  2. //mian.c7 \. q% V: \8 o# K6 ]
  3. 0 ]3 `! O" N0 C) a
  4. #include "main.h"
    ) p$ H. h! j6 ~
  5. #include "stm32f4xx_hal.h"
    9 y4 i9 d7 T# }* Y
  6. #include "i2c.h") F& d0 H; T' e- L2 I
  7. #include "usart.h"" ^0 \8 F. S; m
  8. #include "gpio.h"0 [  Z8 w. M& O- }$ V" |1 ~6 h
  9. 7 ^7 y3 k* h; L4 e
  10. #define ReadAddr   0xA1& L7 X3 {( ^; X# i" H' ]
  11. #define WriteAddr  0xA0# f) j2 e# |' X5 s

  12. + o3 M. @( K- a$ ~( u& q% C* v
  13. uint8_t Wbuf[20] = "EEPROM TEST OK!";
    0 y* D7 e0 d* D) H9 g& h
  14. uint8_t Rbuf[20] = {0};
    1 M6 l* [2 l% ^; t9 y" V6 O: S
  15.   u5 t3 E9 P6 J5 b" O
  16. /*********  24C65写数据函数*****************************/
    ( Q' `! E6 Z( p) Z) |
  17. 5 h+ P7 f( c5 p& I% R
  18. void  Eeprom_Write(uint16_t MemAddr, uint8_t *Wbuf, uint16_t len ){6 ]* M1 G' k$ J7 j/ I' O9 c
  19.         while(len--){
    & |5 j7 V5 Z% z  ^9 N$ x
  20.         //I2C_MEMADD_SIZE_16BIT表示存储单元大小
    6 v" M' r8 ~0 v: k' n! [6 {
  21.         //默认为两个参数,分别是I2C_MEMADD_SIZE_16BIT和I2C_MEMADD_SIZE_8BIT6 R0 x: {! z# J5 F1 M
  22.         //由于24C65的存储地址是16位的
      x( K6 G1 C% q, c
  23.         //所以我们选择I2C_MEMADD_SIZE_16BIT; n+ D; G! J) z$ K) a5 C7 ?
  24.         //1表示一次写一个字节+ b) V  u# {' D# ?: {/ O1 `$ K
  25.                 while(HAL_I2C_Mem_Write(&hi2c1, WriteAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Wbuf, 1, 100) != HAL_OK){};
    3 x0 h$ g; A" F: t- p9 O/ q, k
  26. MemAddr++;
    ; u7 k9 s& [$ G6 t
  27. Wbuf++;
    " m& b9 c  f: L' G8 T% s
  28. }
    + z* L0 I; U" m
  29. }9 r: P' s* \$ Z; @0 n7 E: O, l

  30. ( a) ~. S( A; k& w4 F! T

  31. / R$ x2 Q! |2 L. k3 R$ {+ m/ B! U
  32. /*********  24C65读数据函数*****************************/4 P  a8 H7 z1 T' t" h0 |* k& e1 x6 s1 Y
  33.   a# u8 y3 z1 N+ p
  34. void  Eeprom_Read(uint16_t MemAddr, uint8_t *Rbuf, uint16_t len ){' s8 E- |5 D1 o+ X0 K' j  r. j
  35.         //可以连续读,所以无需循环, ^) j9 L9 r6 l' ]6 l% ?  m' L6 M) d
  36.         while(HAL_I2C_Mem_Read(&hi2c1, ReadAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Rbuf, len, 100) != HAL_OK );4 N( g6 |/ Y. L* a
  37. }
    4 r; F( H  t! f6 u
  38. int mian(){* U% ?: S2 ]% O, ~
  39.       MX_GPIO_Init();
      H9 p, S& w9 Y* I
  40.       MX_I2C1_Init();
    & J( F# Z( T) V. B* S
  41.       MX_USART1_UART_Init();" _1 j" k+ t3 E' T, B3 r
  42.       printf("this is i2c eeprom test\n");4 j1 P& |5 `9 S
  43. # L9 m# n0 l1 F8 Z* D
  44.       Eeprom_Write(0, Wbuf, sizeof(Wbuf) );% K/ D9 U( @" s- w8 C9 e& Z
  45.       HAL_Delay(500);) G) |. ^. O/ f( K
  46.       Eeprom_Read(0 , Rbuf, sizeof(Rbuf));: p7 n, R8 S9 b& w7 H
  47.       printf("READ:  %s\n", Rbuf);( A$ I1 p, h; w% B

  48. - W& s0 b5 ^1 I6 {4 l* |- N$ E8 p
  49.       while(){
    * k5 `  r- R8 u9 r

  50. 1 s4 ?* W  G3 y$ E  m, H6 e
  51.       }2 ?! b( O3 Q; W: A6 E
  52. }
复制代码
: l3 I1 |2 g" y- `
  • STM32 SPI总线通信专题讲解

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

0 C9 U: B$ f$ R  H0 @
SPI接口共有4根信号线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。
) f" T4 X. k+ U" c/ m* ^* y
微信图片_20230412162658.png
2 o/ J5 L$ e2 N2 J& D& o
(1)MOSI:主器件数据输出,从器件数据输入,连接从机的MOSI,与串口不同,串口需要反着连接(Rx-----Tx)
(2)MISO:主器件数据输入,从器件数据输出,连接从机的MISO
(3)SCLK :时钟信号,由主器件产生
(4)/SS:从器件使能信号,由主器件控制(片选),一般情况下为地电平选中设备,高电平释放设备。

9 I9 k2 M1 T5 m$ S4 p  ]
  • SPI总线协议

    , v) s; j; `2 p5 R

* B% u& H3 G* X/ b2 S
1.数据交换逻辑:主机和从机都包含一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节发起一次传输。寄存器通过MOSI信号线将字节传送给从机,从机也将自己的移位寄存器中的内容通过MISO信号线返回给主机。这样两个移位寄存器中的内容就被交换了。从机的写操作和读操作时同步完成的,因此SPI成为一个很有效的协议。
* S$ Z9 d) @) S* t3 O" F& {
如果主机只想写不想读,只需把数据放在数据寄存器,SPI控制器会自动传给外设,同时忽略掉外设传过来的数据即可;如果主机只想读不想写,主机写给外设一个空字符或者随便写一个数据,外设就会把数据传过来,不管是只读还是只写,主机与外设的读和写都h会发生且同时进行。

" i; R) i: }; F- M6 L
微信图片_20230412162734.png

& N7 M1 u$ f  ^6 w. i+ n$ G
2.起始信号:  NSS信号线由高变低,是SPI通讯的起始信号。

- M0 Y- e& c' s9 j5 r" X
3.结束信号:NSS信号由低变高,是SPI通讯的停止信号。
& S7 X9 e' A3 a% G' s' k  H
4.数据传输:SPI使用MOSI及MISO信号线来传输数据,使用SCK信号线进行数据同步。MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,按位传输,且数据输入输出是同时进行的。SPI每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制,要么是8位,要么是16位,可以配置。

- d9 h- H4 H4 G# |: F+ m- N
微信图片_20230412162755.png
" p/ e' s, s2 B: \0 j
  • SPI的4种通信模式
    $ E) k% ]( g" C# x# T! z2 U2 W

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

/ ~5 R  U  o! O6 R
13.png
6 _& {8 A+ Y2 w" N" l$ N4 v! y$ @
1.时钟极性CPOL : 设置时钟空闲时的电平:
a.当CPOL= 0 ,SCK引脚在空闲状态保持低电平;
b.当CPOL= 1 ,SCK引脚在空闲状态保持高电平。

, X0 o+ f: z4 @( V: K4 O# x
2.时钟相位CPHA :设置数据采样时的时钟沿:
a.当 CPHA=0时,MOSI或 MISO 数据线上的信号将会在 SCK时钟线的奇数边沿被采样
b.当 CPHA=1时, MOSI或 MISO 数据线上的信号将会在 SCK时钟线的偶数边沿被采样

2 I) O# e, }. x# c  h
微信图片_20230412162837.png
9 b, c' }, z( _0 G' i) P: j
11.png
  • STM32F4-SPI控制器特性

    ) y0 M; I8 X# @8 j0 `( M) h2 r
12.png
9 U1 q  ?8 U4 k: y
1.通讯引脚:
STM32F4芯片最多支持6个SPI外设控制器,它们的SPI通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚,以《STM32F4xx规格书》为准。f407只有SPI1、SPI2、SPI3。
9 M0 m( H! q8 k, n
微信图片_20230412163014.png

( b3 E/ M' `/ X$ z1 N: Q6 @
其中SPI1、SPI4、SPI5、SPI6是APB2上的设备,最高通信速率达42Mbtis/s,SPI2、SPI3是APB1上的设备,最高通信速率为21Mbits/s。其它功能上没有差异。
2 f2 ^7 u/ ^# o( S
2.时钟控制逻辑:
SCK线的时钟信号,由波特率发生器根据“控制寄存器CR1”中的BR[0:2]位控制,该位是对f pclk 时钟的分频因子,对f pclk 的分频结果就是SCK引脚的输出时钟频率。

) d) D2 J, g$ ^+ e; y
微信图片_20230412161739.png
; w5 Y- s1 H$ F
其中的fpclk 频率是指SPI所在的APB总线频率,APB1为fpclk1 ,APB2为fpckl2

0 I$ t. }1 I! ^, v
3.数据控制逻辑:
STM32F4的MOSI及MISO都连接到数据移位寄存器上,数据移位寄存器的数据来源来源于接收缓冲区及发送缓冲区。

5 J# U( M  e, q: B( l5 Y/ s5 q
a.通过写SPI的“数据寄存器DR”把数据填充到发送缓冲区中。
3 B( e( e: d  o9 [3 \. Y  M; L( T( A
b.通过读“数据寄存器DR”,可以获取接收缓冲区中的内容。
$ u+ Y7 @4 S& E+ Q8 o
c.其中数据帧长度可以通过“控制寄存器CR1”的“DFF位”配置成8位及16位模式;配置“LSBFIRST位”可选择MSB先行(高位在前)还是LSB先行(低位在前)。

9 d5 Q9 I5 L  D/ J. ]6 f5 a, Z
4.整体控制逻辑:
a.整体控制逻辑负责协调整个SPI外设,控制逻辑的工作模式根据“控制寄存器(CR1/CR2)”的参数而改变,基本的控制参数包括前面提到的SPI模式、波特率、LSB先行、主从模式、单双向模式(同时发送和接收、只发送关掉接收、只接收关掉发送)等等。
8 H6 \) H0 i* |# ]; ~
b.在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,只要读取状态寄存器相关的寄存器位,就可以了解SPI的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生SPI中断信号、DMA请求及控制NSS信号线。
- A/ Y3 K5 W0 @. b
c.实际应用中,一般不使用STM32 SPI外设的标准NSS信号线,而是更简单地使用普通的GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号。
  • 串行FLASH_W25X16简介
    . |" H& [3 L9 L/ K

) E# U# N8 V) x+ O: R; ~" F0 h. W
FLSAH 存储器又称闪存,它与EEPROM都是掉电后数据不丢失的存储器,但FLASH存储器容量普遍大于 EEPROM,现在基本取代了它的地位。我们生活中常用的 U盘、SD卡、SSD 固态硬盘以及我们 STM32 芯片内部用于存储程序的设备,都是 FLASH 类型的存储器。在存储控制上,最主要的区别是FLASH 芯片只能一大片一大片地擦写,而EEPROM可以单个字节擦写。
6 b" T" F! F4 f9 s# x
W25X16有8192个可编程页,每页256字节。用“页编程指令”每次就可以编程256个字节。用扇区擦除指令每次可以擦除16页,即一个扇区包含16页,用块擦除指令每次可以擦除256页,用整片擦除指令即可以擦除整个芯片。W25X16有512个可擦除扇区或32个可擦除块。
5 |" _2 z$ ~1 u# F' z8 \0 [5 V, u1 t
微信图片_20230412161735.png

# S6 L# ?# ]& M9 g% R3 t7 E7 ~% r
1.W25X16的硬件连线如下:
. y( t! ]' h; t% s' x
微信图片_20230412161643.png
  N- N. b. u& T# U$ V2 w0 w
CS:    片选引脚,低电平有效,连接到STM32-PH2管脚
SO:   连接到STM32-PB4管脚(MISO)
SI:    连接到STM32-PB5管脚(MOSI)
CLK:  连接到STM32-PA5管脚(CLK)
WP:   写保护管脚,低电平有效,有效时禁止写入数据。接电源未使用
HOLD: HOLD 引脚可用于暂停通讯,该引脚为低电平时,通讯暂停,未使用

4 u( K5 M) G' B8 p/ e4 ?! U: v# l
2.W25X16控制指令:
我们需要了解如何对FLASH芯片进行读写。FLASH 芯片自定义了很多指令,我们通过控制 STM32利用 SPI总线向 FLASH 芯片发送指令,FLASH芯片收到后就会执行相应的操作。
而这些指令,对主机端(STM32)来说,只是它遵守最基本的 SPI通讯协议发送出的数据,但在设备端(FLASH 芯片)把这些数据解释成不同的意义,所以才成为指令。
' b! v8 t& q! P
微信图片_20230412161719.png

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

2 @6 K  A# d2 v& A# P
微信图片_20230412161715.png

/ Z8 S) h, [# c8 ]6 _4 G1 F2 O
b.写使能命令(06H):在向 FLASH 芯片存储矩阵写入数据前,首先要使能写操作,通过“Write Enable”命令即可写使能。

  y, P' [- I' d* L
微信图片_20230412161711.png

  a$ y0 ^4 Q5 [, p1 C& |
c.扇区擦除(20H):由于 FLASH 存储器的特性决定了它只能把原来为“1”的数据位改写成“0”,而原来为“0”的数据位不能直接改写为“1”。所以这里涉及到数据“擦除”的概念。
, K& \4 B% U) i' t8 g% \
在写入前,必须要对目标存储矩阵进行擦除操作,把矩阵中的数据位擦除为“1”,在数据写入的时候,如果要存储数据“1”,那就不修改存储矩阵 ,在要存储数据“0”时,才更改该位。
& k5 z4 t; i4 a( e' U3 @
微信图片_20230412161707.png
微信图片_20230412161704.png
3 D' k7 {4 ~4 K. k  Z
d.读状态寄存器(05H):FLASH 芯片向内部存储矩阵写入数据需要消耗一定的时间,并不是在总线通讯结束的一瞬间完成的,所以在写操作后需要确认FLASH芯片“空闲”。我们只需要读取FLASH芯片内部的状态寄存器SRP的S0即可(当这个位为“1”时,表明 FLASH芯片处于忙碌状态,它可能正在对内部的存储矩阵进行“擦除”或“数据写入”的操作)
1 m5 T$ T! J- G0 Y" q: J- R3 v( v
微信图片_20230412161658.png
3 M2 M1 ?. [$ P: u6 i$ A
微信图片_20230412161654.png

/ Z# B1 E2 ~9 C1 z8 [- ^
e.读数据(03H):读数据指令可从存储器依次一个或多个数据字节,该指令通过主器件拉低/CS电平使能设备开始传输,然后传输“03H”指令,接着通过DI管脚传输24位芯片存储地址,从器件接到地址后,寻址存储器中的数据通过DO引脚输出。每传输一个字节地址自动递增,所以只要时钟继续传输,可以不断读取存储器中的数据。
/ R" n; K3 C7 ?: f+ P
微信图片_20230412161650.png
- Y; J) G" S* X
f.写数据——页编程(02H):页编程指令可以在已擦除的存储单元中写入256个字节。该指令先拉低/CS引脚电平,接着传输“02H”指令和24位地址。后面接着传输至少一个数据字节,最多256字节。
/ [0 b$ K0 `. b. C5 O. F
注:当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。

, R0 t6 e" K5 Q
微信图片_20230412161646.png
! v/ F! O$ e. Z2 Y" m# T. {
  • STM32 SPI_FLASH基本配置和操作

    * k5 `8 _6 |6 i" J) `1 w, G
% ~# q) ^# d5 p9 m
根据如下的硬件连线图进行配置
: m8 b: I4 D/ Y3 F* N4 j
微信图片_20230412161729.png

3 b, O( n4 U/ X
步骤:
1.使能时钟RCC
! u5 y8 ]) `$ _
2.使能SPI1,配置相应管脚
3 z2 v2 e" a$ `  |3 o
微信图片_20230412161640.png

( J4 ~. `* Y+ |4 g5 z$ [
微信图片_20230412161637.png

# A2 e$ y: y- a. I
3.配置SPI协议
. @) V# f! ?0 H0 W7 {3 l! q
微信图片_20230412161632.png

2 N3 {6 s: i; i9 A* N4 _5 H7 \

' M1 i& p/ f9 U7 T
4.编码
  1. //main.c
    ( R  {6 [# f. q/ {3 [: @
  2. , ]& q/ x8 P$ D) u4 p, e) F
  3. #include "w25x16.h"2 Q7 Y7 v( p# B4 D

  4. $ P, @8 @' N) L1 q" Q% |
  5. uint8_t RD_Buffer[5000] = {0};; O& n1 i% I+ t; }# ?7 F
  6. uint8_t WR_Buffer[5000] = "SPI FLASH WRITE TEST\n";
    - D" Q# R; \; i; p0 `# O
  7. , P! T1 f+ f3 z; m
  8. int main(){+ p7 r. O" B- A6 C% G4 [
  9.     uint16_t FLASH_ID = 0;) l& b# _6 x9 v" N5 q% d
  10.     uint32_t i;
    & W' p( F1 i( @3 P' s
  11.    
    8 B8 A3 `$ }& Q7 S3 ]4 {/ L
  12.     MX_GPIO_Init();
      |9 h$ N3 M. N# ^! k8 F
  13.     MX_SPI1_Init();
    / v& I/ C2 N8 g3 e( n5 R
  14.     FLASH_ID = sFLASH_ReadID();; U' D' S/ P/ F) E$ P/ |  h
  15.     % _# I! D- J; Q# d" v
  16.     /******测试擦除******/
    ( h8 p  F. }( M* f- Y4 }
  17.     sFLASH_EraseSector(4096*0);9 v  F6 X9 K% j7 G! D
  18.     //sFLASH_EraseSector(4096*1);: z; h+ w, a( X1 J
  19.     sFLASH_ReadBuffer(RD_Buffer,0,4096);
    4 g( F( N* D0 f
  20.     printf("读数据开始\n");
    # V' p7 R- ]9 E0 m1 O
  21.     for(i=0; i<4096; i++)
    4 q  y, |* ^9 C4 v  o% U% t  p
  22.     {
    # w$ B+ l5 d. J# V6 w3 |
  23.     printf("%x ",RD_Buffer[i]);; _- K5 R- s- }2 b
  24.     }% ^0 l% m& N+ t/ T
  25.     printf("读数据结束\n");! ?7 _9 q2 I# ?$ D, U

  26. . y$ `! @" y, K- j. j/ \
  27. $ k" f- s4 q: B9 e0 B: w  n; ]$ {. ]
  28.     /******测试写操作1*****/
    + q6 c) c+ g! A. Z! T
  29.     //写之前都需要擦除扇区# q( u) \4 N. e( S. {* e
  30.     sFLASH_EraseSector(4096*0);
    0 L% o; P% G! V2 s$ X/ r/ Y8 r
  31.     sFLASH_WritePage(WR_Buffer,0, 20);
    2 W( s+ Y/ p* _5 h/ r, m  Q
  32.     sFLASH_ReadBuffer(RD_Buffer,0,20);
    4 x4 {4 X8 w; N- e" D6 W. I
  33.     printf("READ DATA: %s\n",RD_Buffer);
      B2 y1 f) J0 T& O" H+ N
  34.     9 _; J' @% r* D, _1 Y  j
  35.     /******测试写操作2*****/
    ! D8 k9 G8 I  v& j
  36.     //写之前都需要擦除扇区/ _3 }  L& \/ a, e2 I1 n: ?: M
  37.     sFLASH_EraseSector(4096*0);5 [4 j  f' ^. }  L* k& n) Z
  38.     sFLASH_EraseSector(4096*1);                            2 a" g( y- y2 v' k8 z
  39.     for(i=0; i<4096; i++)
      C( P& s9 C3 X- P6 e, B. D: ]# E
  40.     {
    ; j9 h# A2 g' ]
  41.     WR_Buffer[i] = 0x55;3 u0 c% ]& n) O7 d5 L5 V
  42.     }2 `( G9 N' O7 r* i7 B  h' o
  43.     sFLASH_WriteBuffer(WR_Buffer,4090, 1000);
    - }2 F4 W! [2 D% Z; g6 Y* |
  44.     sFLASH_ReadBuffer(RD_Buffer,4090,1000);) X- k. M+ h: }+ J; [5 M, W7 g
  45.     for(i=0; i<1000; i++)
    0 W: P  t7 W' g) V( o
  46.     {
    5 F1 ]" V' F  X. f1 ^% H. J5 z
  47.     printf("%x ",RD_Buffer[i]);9 f& ^( r7 _7 x# e5 t: y
  48.     }3 Z/ ]9 z) w5 z/ O* `" r6 r9 c
  49.     /*****************/
    ' _0 I# q5 Q  w5 b; X
  50.     while(){}$ l9 E4 ]/ a6 O/ K
  51. }
复制代码
  1. //w25x16.h
    " K5 m8 \: Z6 S

  2. # d9 q6 r, l8 X! \
  3. #ifndef __W25X16_H
    * r4 ^8 N; h/ c6 O. q
  4. #define __W25X16_H
    , C' l) x; _( r' a( `
  5. * ~+ V; W+ J9 H& V: L, m
  6. #include "stm32f4xx_hal.h"7 E# E  W7 ]+ h  Z* q; X0 }
  7. # ^. V" b- C5 y9 d8 m2 j) a: m" b
  8. //使用宏定义芯片指令
    ! s7 I' p+ o& `4 p( }: D- E& H
  9. #define W25X_ManufactDeviceID  0x90  /* Read identification */
    ' u% z! Y- O& D. Z, c0 ~
  10. #define sFLASH_CMD_WREN        0x06/* Write enable instruction */! j7 F6 ~3 `, w4 u+ L! ]6 C) x
  11. #define sFLASH_CMD_RDSR        0x05/* Read Status Register instruction  */( ?" D6 A6 {" h; r1 U* P
  12. #define sFLASH_CMD_SE          0x20/* Sector Erase instruction */
    9 V( d- S7 P( p8 r
  13. #define sFLASH_CMD_WRITE       0x02  /* Write to Memory instruction */
    0 k- J$ H* Q' \  o  ?  x
  14. #define sFLASH_CMD_READ        0x03/* Read from Memory instruction */+ _# G7 [# Z: E' N) x
  15. #define sFLASH_DUMMY_BYTE      0x00 //空字节,用于只读传回来的数据- b$ J7 r" T6 {5 a
  16. #define sFLASH_BUSY_FLAG       0x01
    2 I" s9 H- z. f) ^
  17. #define sFLASH_SPI_PAGESIZE    0x1000 V8 `7 G/ {1 [' N

  18. . I' Q, W: N( B. ]1 A* {
  19. /* 选中芯片,拉低信号 */
    + R9 S8 q( ?- C6 f
  20. #define sFLASH_CS_LOW()       HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_RESET)
    . ], I9 ~# s! u% x& |; W) s
  21. /* 释放芯片,拉高信号 */) S9 N8 [3 M8 v; r
  22. #define sFLASH_CS_HIGH()      HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_SET)
    * x3 S- V) t: d& I+ Y& R4 E; }

  23. / V' i2 {* Z8 P+ l1 N/ J" ]7 I9 {4 u8 A
  24. //定义函数
    * u* X7 J8 m0 ]* j
  25. uint8_t sFLASH_SendByte(uint8_t byte);
    2 J* \$ B2 V$ k5 b' C' }' P! F2 }2 P# ^
  26. uint16_t sFLASH_ReadID(void);! Y' E8 p7 q' c$ y4 H: ^$ T" j
  27. void sFLASH_EraseSector(uint32_t SectorAddr);. g* a' M  I& e* J' {
  28. void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);8 q* k3 }! f4 U+ P5 e+ y: w
  29. void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);
      j$ M  F" j6 V2 M
  30. void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead);
    7 Q$ e! v& K$ Q; j3 R8 j! u2 Z
  31. #endif
复制代码

  1. 0 d+ K. {( n  f% e' O
  2. //w25x16.c4 L4 |2 r* D  U% e  M0 V
  3. . T# c2 }3 u6 w
  4. #include "w25x16.h"+ i& ^3 R( O% ?! g8 @2 D

  5.   D7 }( b4 f5 \% M. B8 j, J
  6. extern SPI_HandleTypeDef hspi1;5 i; L! q$ {5 S2 ]

  7. 4 o; M% ?% E: ~6 ^4 O) H0 c
  8. /*读写一个字节函数,因为SPI读和写同时完成*/+ F6 d( O8 a) @$ C; L5 J
  9. /*发送数据一定会接收到一个数据*/
    6 `" F1 g4 N  v& V5 t# Q& k" w( [
  10. uint8_t sFLASH_SendByte(uint8_t byte)) E- D6 T- h6 }3 l0 C% a( N
  11. {
    8 T: p& @8 G  A! L" d' l
  12.     uint8_t TX_DATA = byte;
    ( {2 a9 D& T: R& k: n
  13.     uint8_t RX_DATA = 0;
    9 B1 p. w5 }: d
  14.     HAL_SPI_TransmitReceive(&hspi1, &TX_DATA ,&RX_DATA , 1, 1000);
    " r5 t4 L1 t( U- u6 j6 \0 j, l: }
  15.     return RX_DATA;
    ; x8 `) Q! c! p; W; }( B5 z
  16. }5 W' `. {7 j( P3 C$ c/ B/ i' }

  17. " S3 ?1 x: G" Y+ h1 e/ r' w
  18. /*等待擦除或者写数据完成*/7 H/ v" I2 ~  F% e% a' U4 T
  19. void sFLASH_WaitForEnd(void); m0 w% w  g( v
  20. {6 o, d) E2 \7 {/ ]& ]  {# ]' T
  21.     uint8_t sr_value = 0;% F  D$ ?" Q, O. t
  22.     sFLASH_CS_LOW();
    # X; N2 Y: J; c0 U! G5 B
  23.     sFLASH_SendByte(sFLASH_CMD_RDSR);  q+ V) C! n8 J  ]! s: ?  N
  24.             //读S0的值,为1表示忙碌,为0表示停止         
    6 Y- U0 b4 _3 D4 K5 I3 U& A  X
  25.     do{- n( P) {& D0 |  ^/ |0 \/ _
  26.                 //发一个空字节,得到S0的值            
    5 ]/ B" n) \0 X: K" g3 H
  27.                 sr_value = sFLASH_SendByte(sFLASH_DUMMY_BYTE);" S2 A. \: n' h0 _$ R7 q1 L/ ]- o
  28.     }while( sr_value & sFLASH_BUSY_FLAG);
    9 R% q% k' }  q" L) g* [$ T

  29. ) o2 Q5 g* z; u; i3 `6 g) m8 E
  30.     sFLASH_CS_HIGH();. }  [7 `8 Z' N/ B3 i
  31. }/ Y8 C. T5 P/ Y4 ^0 _

  32. ; U' i& p* z; ^2 I( @6 v
  33. void sFLASH_WriteEnable(void)2 \7 L$ u/ W4 }# y# U) `) \
  34. {* z# p& B7 I' K
  35.     sFLASH_CS_LOW();! |: X  l  L7 _
  36.    
    * Y7 l  K6 e. X/ W/ L( ?6 Y  M  \
  37.     sFLASH_SendByte(sFLASH_CMD_WREN);9 D$ G/ x, f7 ?; i4 C: z
  38.     5 @, i" @: o" }  \0 w1 N* H) g
  39.     sFLASH_CS_HIGH();
    . \" r, |$ w3 q+ l$ J
  40. }
    / C6 u4 o$ J0 @+ h) N" D

  41. ! O5 `' ]" p! ~0 E6 K! Q9 R
  42. 8 ~8 Q" r; f/ b. H( D3 ^2 c
  43. /*读设备ID*/ ( h! ^0 ?7 D* B: R3 a+ f
  44. uint16_t sFLASH_ReadID(void)% D- F  b; z' ?$ a0 t8 S
  45. {' e! r* T/ `4 f* B2 v
  46.     uint16_t FLASH_ID;1 i; @4 c$ q; D  k+ ^/ z5 d2 o
  47.     uint8_t temp0,temp1;0 b) I% y+ `* U! p2 O% a3 ?: B! W* T
  48.     / {7 G. d% l7 q/ x9 v- P
  49.     - X; ?# e  r2 y/ M, U
  50.     sFLASH_CS_LOW();
    5 _" y8 P$ j8 }* f6 w  B2 m6 }' Y
  51.    
    ( o3 Z! k; U1 A4 J. o
  52.     sFLASH_SendByte(W25X_ManufactDeviceID);6 K* g+ w' [) [0 N8 k
  53.     //读设备指令后要发24位地址,所以要发三次7 L4 u9 }4 d9 W8 q" I
  54.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);/ X1 b% [, T( q7 G
  55.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    & X, g) t0 J; {9 s/ A; N
  56.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    " p! F) _4 c# M! Z/ z) \
  57.     //制造商ID
    " y' K& g$ H, P! h* q. k
  58.     temp0 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);2 O( ~- l6 o- G+ v
  59.     //设备商ID, [" n9 y5 Z! O2 G$ J' W
  60.             temp1 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);& t' y, @& S+ w  Q& M) _
  61.     / W7 A: A+ r+ R) ]
  62.     sFLASH_CS_HIGH();
    9 Z% }) v2 V$ R9 z/ \: m
  63.     6 w/ n3 U- X# s# h
  64.     FLASH_ID = (temp0 << 8) | temp1;3 K) }$ K6 t7 m! R9 P
  65.     6 S% k; \" H& r! F* ^- `6 |
  66.     return FLASH_ID;
    * n9 N2 Q! r$ ~
  67. }7 f7 r# X3 I9 R! u' C# e- ?
  68. , s: K% n8 J" @$ p1 F8 y- g
  69. //擦除扇区,擦除为1,因为只能由1变为0 ,不能0变1+ D- K' X' ~: n( j5 r2 q1 Q
  70. void sFLASH_EraseSector(uint32_t SectorAddr)* |  @/ z  P; p$ \7 ~  U7 A, h
  71. {  N+ N5 e- |7 [2 D0 n
  72.     //SectorAddr表示擦除第几个扇区5 ]! }! @6 J. A2 S8 n& X0 e
  73.     sFLASH_WriteEnable();  //开启写使能6 Q9 t. s6 d; E4 s- v) ?, q. M! z! v$ p
  74.     sFLASH_CS_LOW();//拉低,片选
    8 p" ]/ `2 }& _  D' M
  75.     //擦除命令) e; }7 e& u) x: i0 r8 w) P. x
  76.     sFLASH_SendByte(sFLASH_CMD_SE);
    3 u* A4 f6 Q% c1 F  _
  77.             //传24位地址
    . K& y6 \  ?1 Y/ `; Y) F" e7 [3 h
  78.             //传送高8位,将中8位和低8位一共16位移出去,得到高8位                  
    & |, z& w8 u, o5 \% a' ~
  79.     sFLASH_SendByte( (SectorAddr>>16) & 0xff);   
    . V4 t. p3 x# k* U6 t  Z4 ^* u
  80.     sFLASH_SendByte( (SectorAddr>>8) & 0xff);    //传送中8位7 ~, E' l4 B1 J
  81.     sFLASH_SendByte( (SectorAddr>>0) & 0xff);    //传送低8位
    # ]6 C* }% d# h" Q8 |, e
  82.      
    4 o+ Q6 Y% Y& x5 t, O" C3 W
  83.     sFLASH_CS_HIGH();
    8 v! M* c3 i: |
  84.     " [6 L4 \& m; b; y1 F
  85.     /*读状态寄存器,等待擦除完成*/( ?, z" y# e  |! Y5 r7 G& s1 I
  86.     sFLASH_WaitForEnd();: l; U7 E  P# e# C# \
  87. }
    ! {1 S% V% P* I7 w! u0 B* ]9 w
  88. : T7 N0 L0 B, T2 h9 Z. u$ z, V
  89. //读数据# @6 T. A9 G6 [+ I& Y
  90. //读命令和读地址发送后,芯片内部会自动不断递增读数据/ D% A# i- J5 j' V9 c" @0 T9 h
  91. void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead)9 I$ ]( x  A# Z3 T, ?
  92. {
    ' Y' G0 g' O% y, `# S( q
  93.     sFLASH_CS_LOW();0 W* |( i3 w, Q; h
  94.    
    6 c' Y9 Y/ u9 @/ _
  95.     sFLASH_SendByte(sFLASH_CMD_READ);/ ?0 z" U" @( u5 {. S
  96.     sFLASH_SendByte( (ReadAddr>>16) & 0xff);   //传送高8位
    ) j4 \; \+ `" t. `1 R# F
  97.     sFLASH_SendByte( (ReadAddr>>8) & 0xff);    //传送中8位/ j# A" w( ^( e0 X& n
  98.     sFLASH_SendByte( (ReadAddr>>0) & 0xff);    //传送低8位
    , W! P, ~6 z/ w9 w
  99.     0 J7 f8 R) ?9 M
  100.     while(NumByteToRead--)- S; f+ p; d3 H, t+ H1 k- `1 g
  101.     {
    ) S1 a, O! ^; i7 i+ P$ x; y
  102.     * pBuffer = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    ; i, {& I; p5 D
  103.     pBuffer++;8 [( R9 [7 [% `; z  V) N0 p6 ~
  104.     }
    - ~+ K4 |6 }. D! T. N4 V! ^* _
  105.    
    7 h/ @. B4 D  }& l5 b; I; S6 j
  106.     sFLASH_CS_HIGH();
    ! c! R  M, v- s
  107. }
    & W. G8 z+ l# a
  108. 0 K: t0 Y; O4 l" V
  109. //写一页最多只能写256个字节,一个扇区16页,一个块16个扇区                               + S# i( @% K1 y% S$ x
  110. void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite): b& ?: t0 s- H) {
  111. {5 T( @3 c' F6 S3 b% e& Z4 s0 O
  112.     if(NumByteToWrite > sFLASH_SPI_PAGESIZE )" L* I- i' N8 P. Y: o3 @% _) D$ n
  113.     {
    ( F( K+ d! c8 j
  114.     NumByteToWrite = sFLASH_SPI_PAGESIZE;
    ) l* w+ X( C. o( K. S# |9 E; z
  115.    
    % ^, r9 E; _2 [2 M
  116.     printf("写数据量过大,超过一页大小\n");
    ; D5 [, |7 n, q( e! k5 I( B) J+ @9 _
  117.     }; K' i" c8 X4 O% T
  118.     % b) A3 _9 S; f  d
  119.     sFLASH_WriteEnable();  //开启写使能
    4 `  d8 `! C! O9 [1 S! [
  120.     - b5 `8 E6 @/ a/ O0 I/ ^0 [) J
  121.     sFLASH_CS_LOW();4 F5 i: B/ Z' x# Q; Z8 n
  122.    
    + G9 G4 @# D& m6 J5 n  m1 m/ m" b
  123.     sFLASH_SendByte(sFLASH_CMD_WRITE);8 ]4 c9 e% o$ y8 @0 _1 Z
  124.     sFLASH_SendByte( (WriteAddr>>16) & 0xff);   //传送高8位
    , G" ^+ j6 R, v  ]7 ]6 Q
  125.     sFLASH_SendByte( (WriteAddr>>8) & 0xff);     //传送中8位
    # R4 d2 \  R- h5 H
  126.     sFLASH_SendByte( (WriteAddr>>0) & 0xff);      //传送低8位
    ; k; w4 F' V4 V& J: ]
  127.    
    ! s, ?1 \' M8 {2 `2 P- l# P1 ]% T
  128.     while(NumByteToWrite--)
    1 ^3 J: O/ I, m' H5 p
  129.     {
    - l8 g% V, n% b
  130.     sFLASH_SendByte(* pBuffer);
    4 z; C2 g! g' j1 P$ x; O
  131.     pBuffer++;  s, z8 j9 b# s( X/ m
  132.     }) m: |7 K* \/ {6 d% P7 J0 Y6 b
  133.    
    . N% ?# A; x  G! ~3 l
  134.     sFLASH_CS_HIGH();
    1 b) W4 E$ R  A4 B0 t! K2 p
  135.    
      D7 d& D) \2 M- |
  136.     /*擦除和写数据都涉及到写动作,一定要等待完成*/
    8 \! ?! j1 d' `: P& B
  137.     sFLASH_WaitForEnd();1 O# g" y/ V& `' Y0 c
  138. }) J1 E6 D2 K) P2 P1 J" o4 C

  139. 2 L; m) T  T! c9 T: N: y' P" B
  140. //写任意地址、任意长度+ `2 s9 t, A# \) h* y/ A8 E9 t' E
  141. void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite); f. W/ s" _5 p
  142. {
    0 s& t0 e+ c/ s; }
  143.     uint16_t NumOfPage, NumOfBytes, count, offset;6 h- n& S/ T4 f% e
  144.     //求WriteAddr在某一页的位置          l5 N) I% Q) g) Z; |- z, J
  145.     offset = WriteAddr % sFLASH_SPI_PAGESIZE;
    2 H" t6 g; L: Q
  146.     //求某一页剩余的大小
    ' M+ U$ I6 B& o( [! i) t
  147.     count = sFLASH_SPI_PAGESIZE - offset;% F# X, C! {5 }' Q( r
  148.    
    2 Y, O8 f! ~7 v5 w8 H  L% f/ H
  149.     /*处理页不对齐的情况,防止页内覆盖*/
    7 @& [2 h0 c4 r3 I" T5 l/ n/ m
  150.     //先把某一页剩下的部分写掉,之后的就能新页的起始处开始写        /*offset有值表示需要页对齐,如果要写的字节数小于某一页剩余的部分,那就无需对齐*/                  
    ( _6 u7 M; W5 A! b
  151.     /*这两个条件必须同时满足*/' V+ Y' [4 H* p
  152.     if(offset && (NumByteToWrite > count ))
    - d, r$ _! f. I& Z; C8 z9 w
  153.     {
    / C: T( k, B. D8 P$ @
  154.     sFLASH_WritePage(pBuffer,WriteAddr,count);4 ]8 ?% U+ r* A
  155.     NumByteToWrite -= count;//去掉已经写了的,从新页开始3 k/ n; D$ }3 ]7 |) o
  156.     pBuffer += count;+ u8 w% g. n. a% `% E+ K. L3 Z
  157.     WriteAddr += count;
    8 J: n! \1 t& c6 ^9 y" |
  158.     }( r# \  P" }8 R& I* N
  159.    
    - t- d5 E/ n7 G- O5 `- j! o
  160.     /*最多可分多少页*// p" \* ?$ c% x& X$ |5 H
  161.     NumOfPage = NumByteToWrite / sFLASH_SPI_PAGESIZE;, L* K; i2 r/ ~  {5 k/ K* ~
  162.     /*剩余多少字节*/$ G% ?! r9 l1 w' X
  163.     NumOfBytes = NumByteToWrite % sFLASH_SPI_PAGESIZE;6 I7 j9 k; U$ A7 x7 d8 _2 `: u: R" t
  164.     2 v- X$ k* G, h1 |/ S+ G- d
  165.     if(NumOfPage)7 N. Y; `1 c5 i. U% ~; {, }
  166.     {
    ) }" s( m! x9 x, y
  167.         while(NumOfPage--)
    / v. j- E( \+ Z) y, q& I+ y
  168.         {
    $ b& o/ ]( A+ K4 E; {, U
  169.              //每一页都发起页编程8 y/ L- M7 G+ I; V
  170.              sFLASH_WritePage(pBuffer,WriteAddr,sFLASH_SPI_PAGESIZE);
    " j0 @/ q# Z  _
  171.              pBuffer += sFLASH_SPI_PAGESIZE;* S+ a2 w  `' ~4 v
  172.      WriteAddr += sFLASH_SPI_PAGESIZE;
    " G/ ~- d/ m- u# {2 ?( w+ Y
  173.          }
    + A" G9 r5 v3 O2 `& L1 B* n# w9 ~) T
  174.     }8 G9 Z- [+ P% @+ I" x( `
  175.     & U& C0 {* P/ b! H* J. i+ s
  176.     if(NumOfBytes)
    - @2 t% c/ a# g! ^
  177.     {% j* t8 |6 k4 ~+ r8 s) _
  178.     sFLASH_WritePage(pBuffer,WriteAddr,NumOfBytes);! F& b, S+ u; B5 F  k
  179.     }
    7 y6 W, p: Y9 r3 r* k4 h
  180. }
复制代码

: U* H9 j1 ~* {6 q$ }. X9 w+ o2 }
  • 为什么会有两种写操作函数,是因为这里的写操作有两个特点:
    # C2 ^, [5 z9 X0 n

    + L. c3 u' f* d  t$ u

  Q4 @: D6 y9 q6 s$ Q. f  ^
1.无法突破页限制,超过一页需要重新发起页编程信号。另外如果要写的数据大于剩余一页剩余的容量,那么超出的数据会写到当前页起始地址出。例如,初始输入的写地址为200,而要写的数据大小为100,那么要写的前56个字节会从地址200开始依次写入,剩下的44个字节会从当前页的0地址开始依次写入,这很有可能覆盖之前的数据。

" v" K2 m- I) D( l% ]! J
2.无法突破扇区的限制,当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。
% j, j, ]2 y: m; n/ z  A) U
转载自: 骆驼听海0 C6 P! d" p! w6 z7 o
如有侵权请联系删除
' V" p# e9 g' M2 w5 l) z0 u, i& r& }. [6 Z+ N. U, u+ v6 h% x
收藏 评论0 发布时间:2023-4-12 16:18

举报

0个回答

所属标签

相似分享

官网相关资源

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