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

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

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

8 C+ e. @% ~" I6 Z, E% f
微信图片_20230412161833.jpg
& q0 b2 X( }8 y  f" T
本系列为ARM开发的进阶知识,讲解与ARM芯片进行通信的大部分高级外设的原理及使用方法,不局限于ARM芯片内部,实操性较强且晦涩,需要一定的读者具备一定代码阅读和理解能力。

, t$ s  J+ |" ^' H# q$ u6 Y
  • STM32 I2C总线通信专题讲解

    ( y( f& G! l7 ?. m

. x8 b* w4 j5 ^2 z4 x* {9 B
  • 总线介绍:I2C(Inter-Integrated Circuit)总线(也称IIC或I2C)是由PHILIPS公司开发的两线式串行总线(单双工),用于连接微控制器及其外围设备,在这两根线上可以挂很多设备,同一时刻只能有一个节点处于主机模式,其他节点处于从机模式,总线上数据的传送都由主机发起。I2C总线没有片选信号线,所以需要通过协议来找到对应操作的芯片。是微电子通信控制领域广泛采用的一种总线标准。它是同步通信的一种特殊形式,具有接口线少,控制方式简单,期间封装形式少,通信速率高等优点。
    * {% y- x$ l& d  F. e( q8 D% n
    . Q) U. g% `) P2 ~+ e4 H
" C- t( e- }( ~0 d
  • 总线特征:

    : y, E9 c( p8 [. g' L6 x
1.两条总线线路:一条串行数据SDA,一条串行时钟线SCL(主从设备使用同一时钟,属于同步通信)来完成数据的传输及外围器件的扩展
; P& q% O; q: \( ^3 n& s! @& R* n
2.I2C总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址,通常是7位,有时候是10位

! l  c$ O% ]; f( q6 O
3.I2C总线数据传输速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。在开发配置的时候,最好检查从设备的传输速率从而对主设备(一般是MCU)进行相应的配置。一般通过I2C总线接口可编程时钟来实现传输速率的调整,同时也跟所接的上拉电阻的阻值有关。
. @: ]3 K$ X" }8 {4 P: J
4.I2C总线上的主设备与从设备之间以字节(8位)为单位进行单双工的数据传输。
2 T6 L( r! i  y
  • 拓扑结构——总线型
    ( B) o9 n+ E0 z
I2C 总线在物理连接上分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成,SCL由主机发出,SCL越快,通讯速率越快。通信原理是通过对SCL和SDA线高低电平时序的控制来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。

5 h( a# x( F# }1 E0 }3 L. B/ x
微信图片_20230412161906.png
7 U4 r% s" [5 {% O4 y' W+ ~
  • I2C总线协议

    $ \1 m, ~+ B- X+ [
1.I2C协议规定:  总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生。

5 c7 z3 m! [0 v, h5 p) z8 R! M
2.空闲状态:SCL和SDA都保持着高电平。
3 S6 y6 C' ]1 d. Y& J  Q9 o
3.起始信号:  当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件,所有的从设备都能感受到这个跳变,做好准备等待被选择。
% p# a5 g* f6 V8 K* c) o5 m7 t
4.结束信号:当SCL为高而SDA由低到高的跳变,表示产生一个 停止条件

1 g, f2 Z) ^  B+ t
微信图片_20230412161928.png

7 m( e" D3 s: L$ o
5.数据传输:数据传输以字节为单位 , 主设备在SCL线上产生每个时钟脉冲的过程中将在SDA线上传输一个数据位,数据在时钟的高电平被采样这时候采集到是1就是1,是0就是0,所以在传输数据时,当时钟处于高电平时一定要保持稳定,时钟处于低电平时可以变换数据。(高电平采样,低电平变换)一个字节按数据位从高位到低位的顺序进行传输。主设备在传输有效数据之前 要先指定从设备的地址,一般为7位,然后再发生数据传输的方向位, 0表示主设备向从设备写数据,1表示主设备向从设备读数据。主从设备以字节为单位(8位)进行数据传输,开始传输数据时把从设备地址加上方向位组成一个8位的字节进行发送并接收一个应答。
: @5 f9 k6 W- S
6.应答信号:接收数据的器件在接收到 8bit 数据后,向发送数据的器件发出低电平的应答信号,表示已收到数据。这个信号可以是主控器件发出,也可以是从动器件发出。总之,由接收数据的器件发出。
4 H9 P% t" r6 a4 V- Z: o
微信图片_20230412161941.png

; B. \* A" }% {5 l/ f
a.主设备向从设备写数据:

3 `0 H/ O! D* W! l( `2 s+ c
微信图片_20230412161956.png

) Q! V5 v! p/ G; {  X* ]) p6 I! C( ?
b.主设备读从设备的数据:

2 V, h4 {, ?$ J& l8 W  z
1.png
) f8 u: Q2 X. Q3 H1 w9 m) m
c.主设备读从设备的某个寄存器:读设备的寄存器首先应该对该设备发送写命令,很多设备都可以看成是一段内存,所以写命令写给从设备,指明要读取哪个地址(寄存器)的数据,接下来才是真正的读数据。不同的从设备是由区别的,在驱动I2C从设备时应当查明设备的时序图,又怎样的要求,不同的时序对应了不同的命令。
% w1 Z' J# A! E
2.png

+ J# W0 N$ U3 x
. s4 j- e5 p9 T6 U* t& s
  • STM32F4-I2C控制器特性

    & q8 t4 A; {& g6 H( E$ Q; h9 h0 l  N
软件模拟I2C时序:由于直接控制 GPIO 引脚电平产生通讯时序时,需要由 CPU 控制每个时刻的引脚状态,所以称之为“软件模拟协议”方式。我们知道,驱动I2C设备只需要两根管脚,即使单片机上没有I2C控制器,根据协议控制每根管脚每一时刻的电平状态,一根模拟数据线,一根模拟时钟线,就可以驱动从设备,相对而言效率低,但是可以实现控制驱动。STM32内部具备专门的I2C控制器,使用时只需对其进行相应的配置即可。

# J/ H: s% L! E$ ^8 y
硬件控制产生I2C时序:STM32 的 I2C 片上外设专门负责实现 I2C 通讯协议,只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并缓存起来,CPU只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种由硬件外设处理I2C协议的方式减轻了 CPU 的工作,且使软件设计更加简单。

9 K/ y# n2 W, X7 ?3 E$ U0 K- a
控制器功能:配置主从模式(一般都把STM32当作主机使用,作为从机时应当对其赋一个地址),通过配置其内部的寄存器产生一些中断和错误信号,配置通信速率位标准模式、快速模式、超快速模式等

' R* A4 I: ?2 y! g* [* M
STM32芯片有3组I2C外设,可以同时进行3组I2C传输。它们的I2C通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚。
4 M4 S1 ~8 a0 J3 y/ j! g
微信图片_20230412162110.png

, R% q2 R& |; u& A
  • EEPROM(AT24CXX)存储芯片介绍
    . L/ F$ H* ?6 x6 e# }$ g1 I
一个典型的I2C接口的从设备,专门用于存储数据的芯片。EEPROM (Electrically ErasableProgrammable read only memory),带电可擦可编程只读存储器,一种掉电后数据不丢失的存储芯片。EEPROM可以在电脑上或专用设备上擦除已有信息,重新编程。
/ T7 ~6 a8 z. g5 U3 O2 k
EEPROM常用来存储一些配置信息,以便系统重新上电的时候加载之,容量不会很高。EEPOM 芯片最常用的通讯方式就是I2C协议。XX表示容量,常用值为01、02、04、16、32、64等,单位Kbit。一般的存储芯片都具有写保护功能,对WP管脚加一个高电平就开启了写保护功能,就无法往芯片内写数据了。在开发中通常将该管脚接地,确保能够写数据
典型24CXX芯片引脚如下:

' A3 ]1 O( p( t4 S; Q7 [9 L/ P1 j
微信图片_20230412162134.png
0 `) U% N- P1 A# e2 {' Q7 ?
例:24C65的设备地址为7位,高4位恒定为1010,低3位取决于A0-A2的电平状态,般主机在读写24CXX都是把设备地址连同读写位组合成一个字节一起发送。

; K$ j# y% R& k# l* l
3.png

$ G' {& U1 ^5 Z! b6 s+ V! u
24C65的电气连线如下,根据电气连线可知,A0-A2均接地,因此读地址为1010 0001,即0xA1;写地址为10100000,即0xA0 ,且WP接地,用户随时可向芯片内部写入数据。
. m. ?' \- J; r
4.png

2 `% L+ r( ^/ P2 W+ M
24C65写时序:首先发送一个起始信号,接着发送从设备地址以及方向位,收到应答后,向从设备发送要写的存储区域的首地址,24C65的存储地址是16位,先发送高8位,收到应答后再发送低8位,再次收到应答后开始写数据。64Kbit大小位8K字节,需要13位即可表示,所以高3位固定定为0,如下图。

! x' v4 v, P/ U/ j' v3 }8 P5 K
这里是BYTE WRITE,一次写一个字节,此芯片还支持PAGE WRITE,一次写一页,也就是8个字节,如果想写更多,可设置一个for循环实现。
* t" Z& l  t. f$ O
5.png

) V: Q  @1 d/ _% Z* Z
24C65 读时序与写时序基本相同,只不过在读之前要发送再发送重复开始位进行读操作。
: w0 x6 i6 u/ u& G0 B! p
6.png
2 B' W! f. L; q/ U6 X: C3 ]/ A
  • I2C读写EEPROM实例

    5 T( ~, w* A( U) W9 W

" ~* o9 C- I6 _* _: C
由电气原理图可知SCL和SDA分别接入了PB6和PB7管脚,读地址为1010 0001,即0xA1;写地址为10100000,即0xA0
" K: B: }  C5 w+ h3 Y: P
步骤:
1.配置RCC

; q' q0 G4 Y# m0 ?" o9 P
2.配置PB6和PB7管脚

6 [" N8 c) Q5 S+ C+ e2 l
7.jpg

" z; T9 ]4 s% O1 n: X
3.配置I2C协议参数
1 L1 `! K: L8 u+ W9 {
8.png

4 E2 P# E9 _0 H9 b6 U
4.编写代码

  1. # Q1 n8 m+ g3 J1 B- o. I
  2. //mian.c1 I6 K5 U. r, g# I# c
  3. $ V, ~8 n6 m+ u" n+ G
  4. #include "main.h"9 M+ J0 _6 S6 w9 R
  5. #include "stm32f4xx_hal.h"
    ! s; |3 V3 j- Y* _: A. C) m; ~
  6. #include "i2c.h"
    - j! L: C- u8 X( ~# C
  7. #include "usart.h"5 `% S3 S$ a9 y5 k" i( ]2 X0 v' B
  8. #include "gpio.h"! v, {- M% h" H

  9. , x9 Q0 }; x, H# a- u; j
  10. #define ReadAddr   0xA1
    2 o6 b! H& Y$ v4 ~' M3 v  R5 U
  11. #define WriteAddr  0xA0
      v& S% Z7 F: q) d$ O

  12.   ]; P+ ]& ]& E0 g7 l- E: M" z
  13. uint8_t Wbuf[20] = "EEPROM TEST OK!";- a8 }* q) x, F1 i( ]- L
  14. uint8_t Rbuf[20] = {0};
    . R: ?- |' P4 Z  z% k2 ^3 z7 w3 x

  15. / M& K) {+ k% }7 E, B- w
  16. /*********  24C65写数据函数*****************************/
    ! `9 `2 p; M  X- U6 ^/ w; z
  17. , J. P7 H/ ?* a- \4 t
  18. void  Eeprom_Write(uint16_t MemAddr, uint8_t *Wbuf, uint16_t len ){( u) S& k( k. s  e. R
  19.         while(len--){
    ' K) G0 i7 @! v. T* j
  20.         //I2C_MEMADD_SIZE_16BIT表示存储单元大小% \) O5 }6 n! E- a1 y) W2 `- x4 V
  21.         //默认为两个参数,分别是I2C_MEMADD_SIZE_16BIT和I2C_MEMADD_SIZE_8BIT6 T9 s* s! |# L- R$ J3 Y
  22.         //由于24C65的存储地址是16位的/ X4 y- l& V" N1 g8 x( k7 }
  23.         //所以我们选择I2C_MEMADD_SIZE_16BIT
    6 Q0 g3 b( i2 d) U! E  ]
  24.         //1表示一次写一个字节4 t6 s7 _% l! l  z- w
  25.                 while(HAL_I2C_Mem_Write(&hi2c1, WriteAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Wbuf, 1, 100) != HAL_OK){};" F! M. E9 a/ k, x
  26. MemAddr++;
    8 l( Z7 a& ~7 |3 V) \- i" R  ^
  27. Wbuf++;* X2 r# u' o) m& S
  28. }
    # ~: E) C5 h- C
  29. }
    , Z1 o* l! k  ^! {
  30. ' K1 w3 i/ U* S# L
  31. ( Z4 }" l9 S0 `4 C2 c: Z( T! {
  32. /*********  24C65读数据函数*****************************/
    ' M  ]; v; }" R9 M$ `% R
  33. . d# H" v. J2 ~6 l- o. k" A! R
  34. void  Eeprom_Read(uint16_t MemAddr, uint8_t *Rbuf, uint16_t len ){, X' z; b$ M4 ^4 _( V- f. Q* J
  35.         //可以连续读,所以无需循环
    + _7 ^8 ^: |( x0 k( d
  36.         while(HAL_I2C_Mem_Read(&hi2c1, ReadAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Rbuf, len, 100) != HAL_OK );
    7 ^4 n5 G' L) R- @* \9 \, \+ d1 B& g
  37. }
    & `/ {* e. o8 x, o, O& F  K! Y
  38. int mian(){
    1 c3 c! e9 I+ [
  39.       MX_GPIO_Init();  C% A9 u' n  }* b( D5 q
  40.       MX_I2C1_Init();
      k; b' a0 v& ~& S) n. g& w
  41.       MX_USART1_UART_Init();
    ( Z/ O- W* e$ E) a: Q
  42.       printf("this is i2c eeprom test\n");. L, Z# m- w, M/ E/ ]

  43. 3 h9 _5 E  d' E& E
  44.       Eeprom_Write(0, Wbuf, sizeof(Wbuf) );! R3 k& _3 N% N+ M. B
  45.       HAL_Delay(500);
    # r) Y, i1 n6 q! Y" |8 `
  46.       Eeprom_Read(0 , Rbuf, sizeof(Rbuf));- R  D4 F9 q. R# I
  47.       printf("READ:  %s\n", Rbuf);
    0 u; L% |+ p& {9 y$ I4 f

  48. 5 S0 N. _5 a6 T* U# r9 z
  49.       while(){5 {" s0 I1 i# o6 d, {) a  [
  50. 0 o' l& ]2 s9 I1 x1 s& |
  51.       }
    6 |3 g& w3 b8 g5 W
  52. }
复制代码

, B9 v0 L* F0 O; @: }( `( w
  • STM32 SPI总线通信专题讲解
    ! f- [- a& @; G$ b5 r" c# S! ?: h
SPI接口是Motorola 首先提出的全双工三线同步串行外围接口,采用主从模式(Master Slave)架构;支持多slave模式应用,一般仅支持单Master。时钟由Master控制,在时钟移位脉冲下,数据按位传输,是高位在前还是低位在前是可以配置的,配置时根据从设备的通信进行相应配置,一般是高位在前,低位在后(MSB first)。SPI接口有2根单向数据线,为全双工通信,目前应用中的数据速率可达几Mbps的水平。
SPI总线被广泛地使用在FLASH、ADC、LCD等设备与MCU间,要求通讯速率较高的场合。
0 A% B  @* C8 i3 E0 t9 K$ ?0 o
微信图片_20230412162643.png
" r1 O) d% P4 v! ^5 }# [
SPI接口共有4根信号线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。

+ k! `7 Z2 ?6 H+ B2 o/ M8 `
微信图片_20230412162658.png

) e5 @8 I" X( s* f# q/ N% {2 {' C
(1)MOSI:主器件数据输出,从器件数据输入,连接从机的MOSI,与串口不同,串口需要反着连接(Rx-----Tx)
(2)MISO:主器件数据输入,从器件数据输出,连接从机的MISO
(3)SCLK :时钟信号,由主器件产生
(4)/SS:从器件使能信号,由主器件控制(片选),一般情况下为地电平选中设备,高电平释放设备。

. y' O. G& S3 @9 |
  • SPI总线协议

    % L0 @3 q7 y9 [9 g. z
! s( @0 g; F% D  ]; o" d) U
1.数据交换逻辑:主机和从机都包含一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节发起一次传输。寄存器通过MOSI信号线将字节传送给从机,从机也将自己的移位寄存器中的内容通过MISO信号线返回给主机。这样两个移位寄存器中的内容就被交换了。从机的写操作和读操作时同步完成的,因此SPI成为一个很有效的协议。
1 Q6 W# x4 E. K9 v' f0 p
如果主机只想写不想读,只需把数据放在数据寄存器,SPI控制器会自动传给外设,同时忽略掉外设传过来的数据即可;如果主机只想读不想写,主机写给外设一个空字符或者随便写一个数据,外设就会把数据传过来,不管是只读还是只写,主机与外设的读和写都h会发生且同时进行。

' d# A7 e# u& u  x! x: G# Z3 z- s
微信图片_20230412162734.png

! ?1 `' @/ r7 l3 ~
2.起始信号:  NSS信号线由高变低,是SPI通讯的起始信号。

7 t  r1 @9 K6 b) x7 m: M9 S7 F
3.结束信号:NSS信号由低变高,是SPI通讯的停止信号。

2 r- {+ p0 ?+ S. ?' R! r! n. m! k& ]/ ^
4.数据传输:SPI使用MOSI及MISO信号线来传输数据,使用SCK信号线进行数据同步。MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,按位传输,且数据输入输出是同时进行的。SPI每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制,要么是8位,要么是16位,可以配置。

# a* z$ i" _5 O  N- B' f
微信图片_20230412162755.png

7 v  y* j0 p, T, }
  • SPI的4种通信模式

    $ E+ ^; c0 @  j' T1 y* N# R, b0 Z7 Y
. P8 ?& o" ~9 q# U! q/ b: }
在SPI操作中,最重要的两项设置就是时钟极性(CPOL)和时钟相位(CPHA)这两项即是主从设备间数据采样的约定方式。由CPOL及CPHA的不同状态,SPI分成了四种模式,主机与从机需要工作在相同的模式下才可以正常通讯,因此通常主机要按照从机支持的模式去设置。同样在配置时一定要弄明白从机支持什么通信模式进行相应的配置。

! t% j1 R: G. _
13.png

: K5 P  g6 M/ M( q2 G& ~6 |" w3 x
1.时钟极性CPOL : 设置时钟空闲时的电平:
a.当CPOL= 0 ,SCK引脚在空闲状态保持低电平;
b.当CPOL= 1 ,SCK引脚在空闲状态保持高电平。

4 h9 W8 s9 M  U2 F8 ^& Z
2.时钟相位CPHA :设置数据采样时的时钟沿:
a.当 CPHA=0时,MOSI或 MISO 数据线上的信号将会在 SCK时钟线的奇数边沿被采样
b.当 CPHA=1时, MOSI或 MISO 数据线上的信号将会在 SCK时钟线的偶数边沿被采样

% X+ C" w0 V# h
微信图片_20230412162837.png

) u* Z' S$ @# S) G
11.png
  • STM32F4-SPI控制器特性

    " q5 W4 M" B* V1 R5 x1 o
12.png

* |# E9 J9 V4 o; @' O' A! E- ]
1.通讯引脚:
STM32F4芯片最多支持6个SPI外设控制器,它们的SPI通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚,以《STM32F4xx规格书》为准。f407只有SPI1、SPI2、SPI3。
! w/ ]$ n& N) u. i- o9 _- W
微信图片_20230412163014.png

2 V- I1 W$ n. m! f# ~. Z
其中SPI1、SPI4、SPI5、SPI6是APB2上的设备,最高通信速率达42Mbtis/s,SPI2、SPI3是APB1上的设备,最高通信速率为21Mbits/s。其它功能上没有差异。
2 a2 j  H9 v( ]
2.时钟控制逻辑:
SCK线的时钟信号,由波特率发生器根据“控制寄存器CR1”中的BR[0:2]位控制,该位是对f pclk 时钟的分频因子,对f pclk 的分频结果就是SCK引脚的输出时钟频率。
. ]* N8 c# _4 E; g; l
微信图片_20230412161739.png

/ k9 H) l0 O! P& z) h
其中的fpclk 频率是指SPI所在的APB总线频率,APB1为fpclk1 ,APB2为fpckl2

- B4 I; r2 m# [+ p( d0 {
3.数据控制逻辑:
STM32F4的MOSI及MISO都连接到数据移位寄存器上,数据移位寄存器的数据来源来源于接收缓冲区及发送缓冲区。
, k* F% ]% k1 x. z4 ^, R4 F
a.通过写SPI的“数据寄存器DR”把数据填充到发送缓冲区中。

# O3 {/ P8 [1 L* a% W5 c+ C
b.通过读“数据寄存器DR”,可以获取接收缓冲区中的内容。
* P3 @9 X) z$ u/ q; t5 n% l/ `- `
c.其中数据帧长度可以通过“控制寄存器CR1”的“DFF位”配置成8位及16位模式;配置“LSBFIRST位”可选择MSB先行(高位在前)还是LSB先行(低位在前)。
8 h6 V  }- I0 Q$ @; }
4.整体控制逻辑:
a.整体控制逻辑负责协调整个SPI外设,控制逻辑的工作模式根据“控制寄存器(CR1/CR2)”的参数而改变,基本的控制参数包括前面提到的SPI模式、波特率、LSB先行、主从模式、单双向模式(同时发送和接收、只发送关掉接收、只接收关掉发送)等等。

- \3 @: b1 v; ]# L/ V2 |
b.在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,只要读取状态寄存器相关的寄存器位,就可以了解SPI的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生SPI中断信号、DMA请求及控制NSS信号线。
" K7 h' ^9 n0 O# g6 K2 Q* h: v
c.实际应用中,一般不使用STM32 SPI外设的标准NSS信号线,而是更简单地使用普通的GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号。
  • 串行FLASH_W25X16简介

    # a, M! A9 _, W! V: `
! D# `+ x1 o$ c  j( f1 }
FLSAH 存储器又称闪存,它与EEPROM都是掉电后数据不丢失的存储器,但FLASH存储器容量普遍大于 EEPROM,现在基本取代了它的地位。我们生活中常用的 U盘、SD卡、SSD 固态硬盘以及我们 STM32 芯片内部用于存储程序的设备,都是 FLASH 类型的存储器。在存储控制上,最主要的区别是FLASH 芯片只能一大片一大片地擦写,而EEPROM可以单个字节擦写。

" h9 w! ]0 K( @: d
W25X16有8192个可编程页,每页256字节。用“页编程指令”每次就可以编程256个字节。用扇区擦除指令每次可以擦除16页,即一个扇区包含16页,用块擦除指令每次可以擦除256页,用整片擦除指令即可以擦除整个芯片。W25X16有512个可擦除扇区或32个可擦除块。
+ `, |7 `& [! S
微信图片_20230412161735.png

. K# [5 c6 {7 P6 S0 p
1.W25X16的硬件连线如下:
9 J# }3 @' f- L
微信图片_20230412161643.png
% m  l2 w( b) E. d! W( m
CS:    片选引脚,低电平有效,连接到STM32-PH2管脚
SO:   连接到STM32-PB4管脚(MISO)
SI:    连接到STM32-PB5管脚(MOSI)
CLK:  连接到STM32-PA5管脚(CLK)
WP:   写保护管脚,低电平有效,有效时禁止写入数据。接电源未使用
HOLD: HOLD 引脚可用于暂停通讯,该引脚为低电平时,通讯暂停,未使用
/ I. U3 O$ z3 D
2.W25X16控制指令:
我们需要了解如何对FLASH芯片进行读写。FLASH 芯片自定义了很多指令,我们通过控制 STM32利用 SPI总线向 FLASH 芯片发送指令,FLASH芯片收到后就会执行相应的操作。
而这些指令,对主机端(STM32)来说,只是它遵守最基本的 SPI通讯协议发送出的数据,但在设备端(FLASH 芯片)把这些数据解释成不同的意义,所以才成为指令。

: g0 J# _% l: D, s9 d
微信图片_20230412161719.png
( {& O% }- i8 Y2 \" G- n3 i) @
a.读制造商/设备ID(90):该指令通常在调试程序的时候用到,判断SPI通信是否正常。该指令通过主器件拉低/CS片选使能器件开始传输,首先通过DI线传输“90H”指令,接着传输000000H的24位地址(A23-A0),之后从器件会通过DO线返回制造商ID(EFH)和设备ID。(注:SPI为数据交换通信,主器件在发送“90H”指令时也会接收到一个字节FFH,但此数据为无效数据)

# f$ w3 R- G  L3 T6 V2 D
微信图片_20230412161715.png
- L" }5 `* W( l2 y2 M
b.写使能命令(06H):在向 FLASH 芯片存储矩阵写入数据前,首先要使能写操作,通过“Write Enable”命令即可写使能。

: @7 m9 M2 o8 G, b' R3 L) `
微信图片_20230412161711.png
! s6 I; o1 Y. U+ _' y( t$ s5 ~
c.扇区擦除(20H):由于 FLASH 存储器的特性决定了它只能把原来为“1”的数据位改写成“0”,而原来为“0”的数据位不能直接改写为“1”。所以这里涉及到数据“擦除”的概念。
0 V1 o+ V" b8 Q) y5 u/ }
在写入前,必须要对目标存储矩阵进行擦除操作,把矩阵中的数据位擦除为“1”,在数据写入的时候,如果要存储数据“1”,那就不修改存储矩阵 ,在要存储数据“0”时,才更改该位。
" b! O: S& p3 s+ f; n
微信图片_20230412161707.png
微信图片_20230412161704.png
$ ]/ ]1 G# p3 U4 E+ I0 x1 L5 m
d.读状态寄存器(05H):FLASH 芯片向内部存储矩阵写入数据需要消耗一定的时间,并不是在总线通讯结束的一瞬间完成的,所以在写操作后需要确认FLASH芯片“空闲”。我们只需要读取FLASH芯片内部的状态寄存器SRP的S0即可(当这个位为“1”时,表明 FLASH芯片处于忙碌状态,它可能正在对内部的存储矩阵进行“擦除”或“数据写入”的操作)

/ C( ]7 ?5 V2 W: w; b
微信图片_20230412161658.png

3 y5 i$ Q+ C9 {* p
微信图片_20230412161654.png
1 m1 t/ P1 f2 L( o: ^
e.读数据(03H):读数据指令可从存储器依次一个或多个数据字节,该指令通过主器件拉低/CS电平使能设备开始传输,然后传输“03H”指令,接着通过DI管脚传输24位芯片存储地址,从器件接到地址后,寻址存储器中的数据通过DO引脚输出。每传输一个字节地址自动递增,所以只要时钟继续传输,可以不断读取存储器中的数据。

. k3 y: W6 j* y  R
微信图片_20230412161650.png
( M. X; g# e9 G- K, F8 L, I
f.写数据——页编程(02H):页编程指令可以在已擦除的存储单元中写入256个字节。该指令先拉低/CS引脚电平,接着传输“02H”指令和24位地址。后面接着传输至少一个数据字节,最多256字节。
  C# U# b( _9 N3 X4 P2 B( Q7 h  _
注:当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。
9 @0 k7 V: D- h4 N) e5 ~$ q$ h+ m
微信图片_20230412161646.png

" V- B: {4 s# f* l. B  Y1 T
  • STM32 SPI_FLASH基本配置和操作

    4 h4 M) X1 G4 F" J

+ Y: s) s8 A2 h/ I
根据如下的硬件连线图进行配置
/ e$ V$ T4 n6 \5 h7 Y- x
微信图片_20230412161729.png
- f! F% a, O. A2 W+ m& r
步骤:
1.使能时钟RCC
/ A' f+ D& E8 ?& W) ?2 L8 B: ]
2.使能SPI1,配置相应管脚

7 B! Z+ H" C' M! ?  e( ?# a
微信图片_20230412161640.png
# \% V# V/ Q  v( z: o
微信图片_20230412161637.png

( g9 y- t: _2 m
3.配置SPI协议

3 l: |/ J( H, X
微信图片_20230412161632.png

/ Z) h( U2 e5 [: _2 ]* x0 ?
, H. g4 M/ K" E. E
4.编码
  1. //main.c
    $ e0 c: O; |3 h& b

  2. ' z. \0 ~2 ?: M8 Z+ T# c; }: d- r
  3. #include "w25x16.h"* |2 s, c9 U; _7 C' ?1 I

  4. " C/ P; J3 b& {: T* d; n! U
  5. uint8_t RD_Buffer[5000] = {0};
    " i/ f/ z1 N% ^2 ]4 F: I, A+ x% e
  6. uint8_t WR_Buffer[5000] = "SPI FLASH WRITE TEST\n";/ t- B' B- B4 n8 }8 g9 C4 \

  7. : u# I  P% I" K8 ^' l& L
  8. int main(){
    5 D: W" w0 _" p0 P8 {. S: X
  9.     uint16_t FLASH_ID = 0;/ T4 }& C( d$ J" u
  10.     uint32_t i;8 m. a: }; z, x- v8 S1 F
  11.    
      H9 f2 D; X7 J
  12.     MX_GPIO_Init();
    , F1 L  s; p6 K( N0 h) m
  13.     MX_SPI1_Init();9 c! T. Z1 u& f
  14.     FLASH_ID = sFLASH_ReadID();
    & f' ?$ b# i$ M
  15.     ! `. e" S3 m* |; M
  16.     /******测试擦除******/& A! c' \# e5 L/ A: l( x8 T: u" @
  17.     sFLASH_EraseSector(4096*0);
    7 g9 @6 r- M3 ^; V& y& Y( l
  18.     //sFLASH_EraseSector(4096*1);
    . i5 g7 ?1 m) T3 R5 B# O' g
  19.     sFLASH_ReadBuffer(RD_Buffer,0,4096);. d- u# @! n# ]0 M  O4 U% L' q
  20.     printf("读数据开始\n");
    # o# d1 X! D. V# N" j6 V
  21.     for(i=0; i<4096; i++)8 o% w$ p. v5 B) E% j! _7 g
  22.     {
    : k( a+ I$ A% K  G
  23.     printf("%x ",RD_Buffer[i]);
    $ d8 R# G$ B* g  M* `( m0 [
  24.     }
    . p5 T# N1 }$ F1 o: c
  25.     printf("读数据结束\n");
    & j: v4 _9 j- S1 j

  26. 8 ], X* h% I: Q+ S9 s) J8 D# r3 d1 u) n

  27. ( w2 E. s8 h: Q! \, Y0 ~
  28.     /******测试写操作1*****/, ]/ ]% y1 P* g
  29.     //写之前都需要擦除扇区8 [8 l" A; M. W/ g4 [
  30.     sFLASH_EraseSector(4096*0);
    & s9 }+ h5 i; ~  q
  31.     sFLASH_WritePage(WR_Buffer,0, 20);
    % b( t# L- N# y2 P8 _8 x
  32.     sFLASH_ReadBuffer(RD_Buffer,0,20);
    / m5 ]" r( _- x! E" R- L9 J
  33.     printf("READ DATA: %s\n",RD_Buffer);
    . v- K( x- v  o6 X
  34.    
    ) G5 E  t* s9 [$ ^- k. _
  35.     /******测试写操作2*****/
    + u5 S3 p$ \7 i6 B; f: J
  36.     //写之前都需要擦除扇区
    ( F% U5 J2 d. r  h& z/ [! ?, N
  37.     sFLASH_EraseSector(4096*0);! a3 z0 o: m( {$ x* C
  38.     sFLASH_EraseSector(4096*1);                           
    2 S# o  C# n0 d+ q+ |2 E" J
  39.     for(i=0; i<4096; i++)  K8 I- |8 n! f, Z
  40.     {: G2 i/ h" u7 W) s; O- v
  41.     WR_Buffer[i] = 0x55;
    ; V" ?' v# e5 l
  42.     }+ \$ `1 V: _: X* ~( B! o
  43.     sFLASH_WriteBuffer(WR_Buffer,4090, 1000);
    - t9 w4 P) C; `3 o
  44.     sFLASH_ReadBuffer(RD_Buffer,4090,1000);7 I: v4 r! S& m% b6 e
  45.     for(i=0; i<1000; i++)- c# c7 Z! J1 l' J" w; x6 X# b
  46.     {) m3 b+ z, M: i! u; L( p% u0 l
  47.     printf("%x ",RD_Buffer[i]);
    , Y+ y1 ^. P. j! i' g0 Q" z; M
  48.     }* A- a- v9 \( N4 y7 a' W
  49.     /*****************/: P( g1 r- g# ?- l9 z, k
  50.     while(){}  K  a3 s, ~. q0 W, f4 c
  51. }
复制代码
  1. //w25x16.h
    # `2 O2 \+ }8 K

  2. 4 h5 b) ~7 ~9 ]/ x- x$ c
  3. #ifndef __W25X16_H2 ?! j' y5 `$ \1 n4 I0 R$ ~
  4. #define __W25X16_H
    , K  C' ]; F+ \9 D+ O! G
  5. 5 L% }2 J( V, \
  6. #include "stm32f4xx_hal.h"
    & \) j/ a, B( H

  7. % ~4 e: U1 s$ z
  8. //使用宏定义芯片指令
      e; }' [, T5 w6 `8 q
  9. #define W25X_ManufactDeviceID  0x90  /* Read identification */; ]1 n' z: E+ a
  10. #define sFLASH_CMD_WREN        0x06/* Write enable instruction */) j" B4 X8 e5 l9 w0 C* @0 B' ]( W
  11. #define sFLASH_CMD_RDSR        0x05/* Read Status Register instruction  */
    ! w) `: `* j2 B: d8 V6 Q( {
  12. #define sFLASH_CMD_SE          0x20/* Sector Erase instruction */' ~4 o5 v& G  M  `- K# G
  13. #define sFLASH_CMD_WRITE       0x02  /* Write to Memory instruction */) y( _2 @7 c1 ]& ?* k3 l
  14. #define sFLASH_CMD_READ        0x03/* Read from Memory instruction */1 {/ N% ?+ |( c4 h
  15. #define sFLASH_DUMMY_BYTE      0x00 //空字节,用于只读传回来的数据/ Q- f+ a+ o0 B& J$ M- B: J, p. ~
  16. #define sFLASH_BUSY_FLAG       0x01+ d$ _6 M) b7 r, B5 {# L
  17. #define sFLASH_SPI_PAGESIZE    0x100
    # W3 e" Z! x" f3 M$ W

  18. ( k+ M# v+ U$ [* |, V" C
  19. /* 选中芯片,拉低信号 */
    / u% Y5 {; o7 ?' Y" n; F
  20. #define sFLASH_CS_LOW()       HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_RESET)
    0 B6 E' K4 w0 K
  21. /* 释放芯片,拉高信号 */
    ; w6 A2 [5 a. S4 B( r1 X9 J7 G1 j
  22. #define sFLASH_CS_HIGH()      HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_SET)
    5 s% o# l4 ?- W7 J7 W* v
  23. % D* |0 D) @: l3 ]& M
  24. //定义函数
    " q+ M% |# M; x: F4 ^! U
  25. uint8_t sFLASH_SendByte(uint8_t byte);, F) _1 d( t: K' k* _
  26. uint16_t sFLASH_ReadID(void);, c7 L: h" `, J8 {3 X! X8 t
  27. void sFLASH_EraseSector(uint32_t SectorAddr);8 g* [8 @. d2 _7 g. O
  28. void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);
    9 q7 y: s* @% j# W
  29. void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);1 B& u( f2 m- b: ~8 u9 |3 m
  30. void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead);( x- f4 B4 D& ?8 \- {$ J  X7 s  `
  31. #endif
复制代码
  1. : ?2 `# J$ j7 ?; v) V8 G% q& n' p# o/ e
  2. //w25x16.c
    4 {+ t/ ~7 O2 z% e
  3. * _  P( Z3 p; m9 g3 y; U
  4. #include "w25x16.h"" a6 L. f" v( x4 \7 S1 u

  5. 2 ~1 G8 E7 t, C) w7 D. j+ M- D
  6. extern SPI_HandleTypeDef hspi1;" a9 o7 f1 m- @$ J: s

  7. , D* M) E# p9 H, I
  8. /*读写一个字节函数,因为SPI读和写同时完成*/
    5 G- a' H  A5 W/ k; r0 [
  9. /*发送数据一定会接收到一个数据*/
    : J% v4 U/ y  I/ I- ?: N/ x
  10. uint8_t sFLASH_SendByte(uint8_t byte); Q- Z- o2 a- O" g! c2 e/ D% N
  11. {5 H. H& ^- D) [( C
  12.     uint8_t TX_DATA = byte;
    2 d) n: U3 G, o8 @
  13.     uint8_t RX_DATA = 0;6 i6 @$ W$ l4 x  p4 @4 }
  14.     HAL_SPI_TransmitReceive(&hspi1, &TX_DATA ,&RX_DATA , 1, 1000);6 J9 g* L: I. O& `" i
  15.     return RX_DATA;$ g8 M0 N! o8 V$ d& E1 y7 X
  16. }  E3 K) u. {8 K6 M3 i  p; I! j2 J
  17. % k) m; m9 k9 Z6 Q2 R2 _
  18. /*等待擦除或者写数据完成*/
    ( P( Z) s# }/ \! ~1 H
  19. void sFLASH_WaitForEnd(void)
    : W) n8 m3 q, D# a- [) O+ G4 q
  20. {
    4 P' x, x7 g& {2 z; n
  21.     uint8_t sr_value = 0;6 ?- K4 K. k6 i" T3 }3 `  \
  22.     sFLASH_CS_LOW();5 _8 X# C( @2 m. T9 w
  23.     sFLASH_SendByte(sFLASH_CMD_RDSR);
    + q, e4 x, o$ ^( v" ~# r' O: z
  24.             //读S0的值,为1表示忙碌,为0表示停止          + c2 Z* t1 ^+ q6 @
  25.     do{- {8 G0 j8 X1 W, f) b
  26.                 //发一个空字节,得到S0的值             4 R2 j! Z! z8 o0 W
  27.                 sr_value = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    " C5 A+ |# f, P. F' x& E2 J
  28.     }while( sr_value & sFLASH_BUSY_FLAG);2 f3 c$ I9 f0 E+ K6 f1 j) K

  29. & d/ W" [$ H) `/ T3 G- T
  30.     sFLASH_CS_HIGH();
    9 c+ {! Q' G& G1 E8 d; q4 N2 r& E
  31. }4 E8 c; Z& E. ?
  32. 0 m- N7 i' n1 x  j7 t
  33. void sFLASH_WriteEnable(void), u3 [9 m/ Z( D' K& Z* W- A
  34. {
    & j9 D! i: l5 f4 J3 R1 a
  35.     sFLASH_CS_LOW();
    % T: H; U0 i" J; b# \& r
  36.     9 A6 ?5 `6 O2 o0 ~
  37.     sFLASH_SendByte(sFLASH_CMD_WREN);7 ]+ `% X; d. l( }) ]  N; R9 t
  38.    
    $ o3 e2 l+ K: h$ u2 L, l
  39.     sFLASH_CS_HIGH();
    ) w+ ]9 r' E/ J; u' G
  40. }
    % J% X) M9 i8 _! k4 I6 G

  41. % C& |! \1 b- d9 m5 U3 [) q8 @+ z
  42. 9 v2 r  k, F1 ?2 c+ q- f' c
  43. /*读设备ID*/
    - x3 O3 ^9 L* M* b
  44. uint16_t sFLASH_ReadID(void)
    + U' b% F6 d+ L% B) K3 n# e  }
  45. {/ U/ D+ I3 z" q4 K  K
  46.     uint16_t FLASH_ID;- m4 s! T7 X( C6 U  F" M  s
  47.     uint8_t temp0,temp1;+ E. N- x+ @% p4 l. @# t" q0 {$ l" {, ^
  48.    
    ) ?+ a- v( p% |" o
  49.    
    ' [+ \, H: C1 y1 [6 E
  50.     sFLASH_CS_LOW();+ H# ^5 d& Q/ y4 Q& E8 {
  51.     : F) b& \; |1 ?. t# s
  52.     sFLASH_SendByte(W25X_ManufactDeviceID);
    & p" g0 o0 N$ n* ]# ]' |7 z
  53.     //读设备指令后要发24位地址,所以要发三次
    1 Q3 A: r/ @$ S! p4 X
  54.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);
      ?$ e) a+ L0 a+ V) s
  55.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);) D2 [( _, k' S& Q; T2 b
  56.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);3 M& C9 e7 p5 V
  57.     //制造商ID8 Q" z# O/ w: a( |& ?
  58.     temp0 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);6 A& _5 Y& d( A3 g" @- G; A/ c
  59.     //设备商ID/ H- h5 V" H& X+ t
  60.             temp1 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    ) x2 O5 |4 I% h4 \
  61.    
    4 d- k- A$ r. }" J5 P0 i$ j" f
  62.     sFLASH_CS_HIGH();
    . w2 ]9 r1 D$ \: o5 A) P; k. T
  63.     ! b' |" l! ?; g; ~7 h/ y5 E
  64.     FLASH_ID = (temp0 << 8) | temp1;3 x; j' N/ H6 ~! q7 A: u
  65.    
    / l" C/ k- d! l5 u4 a% g
  66.     return FLASH_ID;
    % O- B  C& _3 n
  67. }
    ! P7 g) S4 |  K$ S$ I6 o7 P
  68. 3 J/ y# k. }* X( ^0 O$ Q) e
  69. //擦除扇区,擦除为1,因为只能由1变为0 ,不能0变1
    $ b- b) x' y# y( j5 l# N7 J) c
  70. void sFLASH_EraseSector(uint32_t SectorAddr)
    / m, b) a' \. ^5 b# {) h
  71. {% h% p7 h' m+ N0 V6 u& X, H
  72.     //SectorAddr表示擦除第几个扇区
    ! ^* O3 {) q9 {& P/ k
  73.     sFLASH_WriteEnable();  //开启写使能
    , _4 o8 X' l$ N; \) T
  74.     sFLASH_CS_LOW();//拉低,片选) X" ?7 k1 h) g9 k! Q
  75.     //擦除命令4 |" [; k7 j' W0 ?* O7 Q. f2 i
  76.     sFLASH_SendByte(sFLASH_CMD_SE);
    3 H4 J. A- i. }1 v
  77.             //传24位地址
    / U2 U7 \: Y) z
  78.             //传送高8位,将中8位和低8位一共16位移出去,得到高8位                   4 b+ a; B- E5 O% {6 {" q: S  q
  79.     sFLASH_SendByte( (SectorAddr>>16) & 0xff);   * A( V5 w% t* Q; w7 O1 i
  80.     sFLASH_SendByte( (SectorAddr>>8) & 0xff);    //传送中8位
    ( u0 X* I& t: y6 e( s' y9 M
  81.     sFLASH_SendByte( (SectorAddr>>0) & 0xff);    //传送低8位3 w& j5 |! @' H) U: @9 F" \: J5 b9 v
  82.      
    % H# D- G5 A% S0 ~
  83.     sFLASH_CS_HIGH();
    3 N! K. v+ t% D' C
  84.    
    - e; o) B; N$ A
  85.     /*读状态寄存器,等待擦除完成*/
      F4 v2 j7 f1 p. _& j: t
  86.     sFLASH_WaitForEnd();
    7 ]2 W7 n  A1 \' O
  87. }
    7 y/ w, W% I3 N2 x

  88. $ v% I5 q0 v. Q2 W, m* ?
  89. //读数据
    ( [4 O" a3 h9 i
  90. //读命令和读地址发送后,芯片内部会自动不断递增读数据/ e# k  f0 [; N
  91. void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead)) ^6 N" \7 I" M! ~" a
  92. {
    8 v" u6 b3 j4 o0 B1 M  D( R
  93.     sFLASH_CS_LOW();
    ' g& M% f, C* e+ ~! M+ {
  94.    
    / v8 A2 n) z. F/ d+ s. g% H
  95.     sFLASH_SendByte(sFLASH_CMD_READ);- X/ o3 e& z2 H. H
  96.     sFLASH_SendByte( (ReadAddr>>16) & 0xff);   //传送高8位
      r2 {% v! Q4 j1 `  b
  97.     sFLASH_SendByte( (ReadAddr>>8) & 0xff);    //传送中8位
    3 c; _- n4 j1 `1 k0 u
  98.     sFLASH_SendByte( (ReadAddr>>0) & 0xff);    //传送低8位! }: A+ Z6 y, ~
  99.    
    ! e7 ~- g5 h$ v& z% }4 K( t2 O
  100.     while(NumByteToRead--)6 f0 F$ Z3 j. W9 K" g; H
  101.     {
    5 O, {) _# u" L# Q
  102.     * pBuffer = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    % D" K5 S5 N0 Z8 [& B/ z; ?
  103.     pBuffer++;
    8 K! I4 U+ }) k
  104.     }4 L6 f( V9 j- o9 i* D: X% U" i) }
  105.    
    5 R7 Z5 _6 k3 S; S% d( {# r' A0 Q% \
  106.     sFLASH_CS_HIGH();
    7 x) o' @+ P- D- w- n' O
  107. }8 r. W$ P; N- E; F
  108. - V: t. v1 U7 V3 N( K
  109. //写一页最多只能写256个字节,一个扇区16页,一个块16个扇区                               4 E, Z  _, p7 ^5 L2 Y
  110. void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)% o6 {) @4 }' a2 d* s
  111. {
    0 X5 O8 Y9 V# q
  112.     if(NumByteToWrite > sFLASH_SPI_PAGESIZE )6 H. N8 W- J& |$ r( \! q7 q, K
  113.     {
    ' `3 Y/ n% u- v) ?% x2 x
  114.     NumByteToWrite = sFLASH_SPI_PAGESIZE;
    " t9 h6 g% n/ ^. f
  115.    
    5 E" E5 C. N" K$ O  ?0 ^6 W* ?
  116.     printf("写数据量过大,超过一页大小\n");3 N  J. x2 l, |) h) @, I% v
  117.     }
    - Q1 A  Q, `; M4 o2 L+ j
  118.     3 ?% S3 l* R: S% j% w
  119.     sFLASH_WriteEnable();  //开启写使能" j2 _8 Z- F: c' U4 Z
  120.     4 ?: L* P' @' B" `/ n
  121.     sFLASH_CS_LOW();; E; k7 I% g/ p, \5 W! o- I. m
  122.    
    * @7 W3 D- F8 E1 s' \% x5 O' F
  123.     sFLASH_SendByte(sFLASH_CMD_WRITE);
    ' t3 j. U- q* I3 H4 t9 C& H% g# G
  124.     sFLASH_SendByte( (WriteAddr>>16) & 0xff);   //传送高8位. w# Y5 r6 x# J1 s$ W* p
  125.     sFLASH_SendByte( (WriteAddr>>8) & 0xff);     //传送中8位
    ' f  M( H7 k8 b
  126.     sFLASH_SendByte( (WriteAddr>>0) & 0xff);      //传送低8位- I7 v- _) Y: g4 j+ a  k' b  b
  127.    
    ' y2 o2 R5 E! n' u9 R  c
  128.     while(NumByteToWrite--)
    - R6 t3 S5 B0 o- [5 f
  129.     {; m/ `- v9 ?' }' S1 B
  130.     sFLASH_SendByte(* pBuffer);* H1 s  F, y" q0 S$ Z" e
  131.     pBuffer++;8 [! L( ^, x, B% |
  132.     }/ U& H2 r8 O0 P7 n0 v' X
  133.    
    + B: z' ?+ t  V) [( W3 H" @' p' T
  134.     sFLASH_CS_HIGH();
    ) V" a$ U0 e' t! r  W
  135.    
    6 m! n8 a, v2 z
  136.     /*擦除和写数据都涉及到写动作,一定要等待完成*/0 l. s4 x" B% z1 B+ t. @6 `. @/ s) N
  137.     sFLASH_WaitForEnd();: m7 Q' M$ o/ \# j9 a& U$ ]. N
  138. }
    3 i% H5 k3 o( O# c" K
  139. / k! j- J+ Y: \: w
  140. //写任意地址、任意长度
    7 d& K/ i; T0 ^- ]
  141. void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
    ( _6 Y! k1 l. K8 R6 A
  142. {. Z! v0 l4 o) A  L% W# X
  143.     uint16_t NumOfPage, NumOfBytes, count, offset;& @0 t6 F( U( T
  144.     //求WriteAddr在某一页的位置        0 g# P0 z6 A! r: f& w
  145.     offset = WriteAddr % sFLASH_SPI_PAGESIZE;
    1 I+ I* C. j/ t" u% |+ t! C
  146.     //求某一页剩余的大小 ; G8 D# \" }3 i& j' }8 X* b6 T
  147.     count = sFLASH_SPI_PAGESIZE - offset;
    1 ?. v! Q5 H: b$ H
  148.     9 ?" e% q" G) W3 d3 t8 s1 B* H4 ]5 C8 ?
  149.     /*处理页不对齐的情况,防止页内覆盖*/
    $ W& S* x, j% u- B, Y7 v0 A
  150.     //先把某一页剩下的部分写掉,之后的就能新页的起始处开始写        /*offset有值表示需要页对齐,如果要写的字节数小于某一页剩余的部分,那就无需对齐*/                  
    8 q2 g! Z, v  e
  151.     /*这两个条件必须同时满足*/5 f, c7 b4 ~* g( [5 S3 j9 ^
  152.     if(offset && (NumByteToWrite > count ))9 q# Q6 S5 x9 C7 Z( w
  153.     {
    - U; G% m1 l4 O6 i; S; @
  154.     sFLASH_WritePage(pBuffer,WriteAddr,count);, D6 U8 V5 i/ d! q% a7 m
  155.     NumByteToWrite -= count;//去掉已经写了的,从新页开始( m2 N( [4 u9 {" |% p
  156.     pBuffer += count;
    # L+ N, R/ V# E( x, C$ a/ z
  157.     WriteAddr += count;
    1 V) g  r# o6 _* q5 A
  158.     }
    4 S4 d9 z( @: K
  159.     , ~; t6 A! `+ v) P
  160.     /*最多可分多少页*/
    0 B6 |* Z  |$ Q  c7 M
  161.     NumOfPage = NumByteToWrite / sFLASH_SPI_PAGESIZE;( G, U& d  _$ Q9 D* K1 D
  162.     /*剩余多少字节*/8 R( _4 k! U# w- q
  163.     NumOfBytes = NumByteToWrite % sFLASH_SPI_PAGESIZE;+ ?" h; V' |) c9 W( o9 j9 m
  164.    
    , o' c+ q( F8 E* F5 J* a& l2 U
  165.     if(NumOfPage)2 C- N4 i$ Q( d7 Y( o% ^1 G
  166.     {
    8 h! \; R& N6 o4 Q$ S, n4 }
  167.         while(NumOfPage--). \& L0 Z/ h. D5 I
  168.         {, g6 z) Y% ], U3 M9 H* ~
  169.              //每一页都发起页编程
      {' c6 L5 d' G- x; U# N
  170.              sFLASH_WritePage(pBuffer,WriteAddr,sFLASH_SPI_PAGESIZE);3 ~8 H: m6 `; Y, @/ H0 A
  171.              pBuffer += sFLASH_SPI_PAGESIZE;
    ; u8 b1 o: v+ c  G% i* y$ ~
  172.      WriteAddr += sFLASH_SPI_PAGESIZE;
    + D1 |/ {+ r# y* c* o+ V
  173.          }
    8 x% F$ o, C( }+ N: l
  174.     }
    ) Z& u# d( {- g( F6 W! ?$ ?# q& s
  175.     5 I2 M5 ~3 k* W, ]
  176.     if(NumOfBytes)  j7 w0 U# J5 s6 V) x' T) w
  177.     {
    ) H& z# W7 L3 l/ r4 Q
  178.     sFLASH_WritePage(pBuffer,WriteAddr,NumOfBytes);! _* ^4 ?% E2 d4 `2 u
  179.     }
    + n- @8 a9 f  R: @. n  u; }% o
  180. }
复制代码
( i4 m. t$ B1 W1 _
  • 为什么会有两种写操作函数,是因为这里的写操作有两个特点:
    ' m& @7 m, S; z8 d) M
    5 I% ^( n1 t  j  ^5 q' ~) @+ d) I! b

8 X5 R* `5 Q8 V
1.无法突破页限制,超过一页需要重新发起页编程信号。另外如果要写的数据大于剩余一页剩余的容量,那么超出的数据会写到当前页起始地址出。例如,初始输入的写地址为200,而要写的数据大小为100,那么要写的前56个字节会从地址200开始依次写入,剩下的44个字节会从当前页的0地址开始依次写入,这很有可能覆盖之前的数据。
7 l. k9 i6 _/ k. y
2.无法突破扇区的限制,当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。

" [, X" }; t/ j* }$ [1 d
转载自: 骆驼听海
- P& i7 u8 p' _: P如有侵权请联系删除
% ]5 c  l" k7 q' i  M: t% C4 G  {0 G9 ?) H/ H  d+ f% j
收藏 评论0 发布时间:2023-4-12 16:18

举报

0个回答

所属标签

相似技术帖

官网相关资源

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