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

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

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

+ V8 q* j# i0 e+ G( h
微信图片_20230412161833.jpg

! Y& U: |9 m% G" h
本系列为ARM开发的进阶知识,讲解与ARM芯片进行通信的大部分高级外设的原理及使用方法,不局限于ARM芯片内部,实操性较强且晦涩,需要一定的读者具备一定代码阅读和理解能力。
$ ?% Q( W: v  l2 o* H2 v( f
  • STM32 I2C总线通信专题讲解
    + d# ^- O( O# Z# ~6 j4 l
8 q5 k' a) G  u- z
  • 总线介绍:I2C(Inter-Integrated Circuit)总线(也称IIC或I2C)是由PHILIPS公司开发的两线式串行总线(单双工),用于连接微控制器及其外围设备,在这两根线上可以挂很多设备,同一时刻只能有一个节点处于主机模式,其他节点处于从机模式,总线上数据的传送都由主机发起。I2C总线没有片选信号线,所以需要通过协议来找到对应操作的芯片。是微电子通信控制领域广泛采用的一种总线标准。它是同步通信的一种特殊形式,具有接口线少,控制方式简单,期间封装形式少,通信速率高等优点。6 ~& H1 }6 c" w" r9 z

    9 J' A+ t! S8 v2 [% e$ s5 Z! t: Z

  v8 W  r% |, ~, \
  • 总线特征:

    ; |* k6 k! M1 d5 g* q+ g7 K3 d
1.两条总线线路:一条串行数据SDA,一条串行时钟线SCL(主从设备使用同一时钟,属于同步通信)来完成数据的传输及外围器件的扩展
6 F+ U8 P  g0 d3 W% x" R
2.I2C总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址,通常是7位,有时候是10位
) z8 p/ Y: x0 y3 j3 m
3.I2C总线数据传输速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。在开发配置的时候,最好检查从设备的传输速率从而对主设备(一般是MCU)进行相应的配置。一般通过I2C总线接口可编程时钟来实现传输速率的调整,同时也跟所接的上拉电阻的阻值有关。

7 w) t& `  W+ f! Q; }' n6 V6 `
4.I2C总线上的主设备与从设备之间以字节(8位)为单位进行单双工的数据传输。
8 i+ H6 F3 q) a$ e5 ^0 x
  • 拓扑结构——总线型
    , J7 j8 h0 Y, ^) _* t8 A% G
I2C 总线在物理连接上分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成,SCL由主机发出,SCL越快,通讯速率越快。通信原理是通过对SCL和SDA线高低电平时序的控制来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。
& f4 {- |2 w+ R+ ]5 ^9 C
微信图片_20230412161906.png
2 U: v2 {0 V$ ]! D6 g0 @8 `
  • I2C总线协议

      `  B/ r6 w$ M6 p) Y
1.I2C协议规定:  总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生。
- v+ V) }) P; O( M6 g
2.空闲状态:SCL和SDA都保持着高电平。
$ a, E( P+ f$ D  u( Y
3.起始信号:  当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件,所有的从设备都能感受到这个跳变,做好准备等待被选择。

5 M- H; A7 r" T, a/ D3 A- T
4.结束信号:当SCL为高而SDA由低到高的跳变,表示产生一个 停止条件
6 f0 F- ?, ^* P! |. t7 {5 ?! f
微信图片_20230412161928.png
( G- P& f0 n- l
5.数据传输:数据传输以字节为单位 , 主设备在SCL线上产生每个时钟脉冲的过程中将在SDA线上传输一个数据位,数据在时钟的高电平被采样这时候采集到是1就是1,是0就是0,所以在传输数据时,当时钟处于高电平时一定要保持稳定,时钟处于低电平时可以变换数据。(高电平采样,低电平变换)一个字节按数据位从高位到低位的顺序进行传输。主设备在传输有效数据之前 要先指定从设备的地址,一般为7位,然后再发生数据传输的方向位, 0表示主设备向从设备写数据,1表示主设备向从设备读数据。主从设备以字节为单位(8位)进行数据传输,开始传输数据时把从设备地址加上方向位组成一个8位的字节进行发送并接收一个应答。
  x8 _% c( _, I9 R) }$ H1 o, G/ J
6.应答信号:接收数据的器件在接收到 8bit 数据后,向发送数据的器件发出低电平的应答信号,表示已收到数据。这个信号可以是主控器件发出,也可以是从动器件发出。总之,由接收数据的器件发出。

; W4 ]8 X& ?' j
微信图片_20230412161941.png

2 d0 J( J( ^& E- ?* |! e( M9 s. n
a.主设备向从设备写数据:
$ j$ R  o( q' t
微信图片_20230412161956.png
3 P+ r6 l0 C5 ?& B9 \$ c
b.主设备读从设备的数据:
4 M: H* a, n& X- m3 {
1.png

+ ]2 e8 z. e8 a1 U
c.主设备读从设备的某个寄存器:读设备的寄存器首先应该对该设备发送写命令,很多设备都可以看成是一段内存,所以写命令写给从设备,指明要读取哪个地址(寄存器)的数据,接下来才是真正的读数据。不同的从设备是由区别的,在驱动I2C从设备时应当查明设备的时序图,又怎样的要求,不同的时序对应了不同的命令。
6 @" s$ Q- R" c. D, X( b
2.png
: `' o$ v. I$ Q1 O6 c
2 I9 J9 N) l) b$ `0 j+ j
  • STM32F4-I2C控制器特性

    2 {) J4 b7 J! k
软件模拟I2C时序:由于直接控制 GPIO 引脚电平产生通讯时序时,需要由 CPU 控制每个时刻的引脚状态,所以称之为“软件模拟协议”方式。我们知道,驱动I2C设备只需要两根管脚,即使单片机上没有I2C控制器,根据协议控制每根管脚每一时刻的电平状态,一根模拟数据线,一根模拟时钟线,就可以驱动从设备,相对而言效率低,但是可以实现控制驱动。STM32内部具备专门的I2C控制器,使用时只需对其进行相应的配置即可。
' f. W: y5 n  H; ]5 C! x8 w4 C
硬件控制产生I2C时序:STM32 的 I2C 片上外设专门负责实现 I2C 通讯协议,只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并缓存起来,CPU只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种由硬件外设处理I2C协议的方式减轻了 CPU 的工作,且使软件设计更加简单。

3 C0 |7 i/ R9 G5 ~
控制器功能:配置主从模式(一般都把STM32当作主机使用,作为从机时应当对其赋一个地址),通过配置其内部的寄存器产生一些中断和错误信号,配置通信速率位标准模式、快速模式、超快速模式等
  a7 l6 i# D( b! q4 g
STM32芯片有3组I2C外设,可以同时进行3组I2C传输。它们的I2C通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚。

1 y6 B* x6 {7 }/ P! j
微信图片_20230412162110.png

% g+ G) E! [* R/ h
  • EEPROM(AT24CXX)存储芯片介绍

      `  d. V* T! m3 M3 _, ~
一个典型的I2C接口的从设备,专门用于存储数据的芯片。EEPROM (Electrically ErasableProgrammable read only memory),带电可擦可编程只读存储器,一种掉电后数据不丢失的存储芯片。EEPROM可以在电脑上或专用设备上擦除已有信息,重新编程。
( }1 `( }1 T3 d2 n% }1 _
EEPROM常用来存储一些配置信息,以便系统重新上电的时候加载之,容量不会很高。EEPOM 芯片最常用的通讯方式就是I2C协议。XX表示容量,常用值为01、02、04、16、32、64等,单位Kbit。一般的存储芯片都具有写保护功能,对WP管脚加一个高电平就开启了写保护功能,就无法往芯片内写数据了。在开发中通常将该管脚接地,确保能够写数据
典型24CXX芯片引脚如下:

2 U# p3 N, f* L' p8 I8 N0 I  R
微信图片_20230412162134.png
* z, P, {( f$ |) P
例:24C65的设备地址为7位,高4位恒定为1010,低3位取决于A0-A2的电平状态,般主机在读写24CXX都是把设备地址连同读写位组合成一个字节一起发送。
. Z* Z) ]3 f# P' z+ b) Q+ T% s
3.png

, D3 V6 S" u, G3 P0 ]
24C65的电气连线如下,根据电气连线可知,A0-A2均接地,因此读地址为1010 0001,即0xA1;写地址为10100000,即0xA0 ,且WP接地,用户随时可向芯片内部写入数据。
* R" u: \9 M$ p9 y: c
4.png
) m7 Y, [( E2 Z7 O# H
24C65写时序:首先发送一个起始信号,接着发送从设备地址以及方向位,收到应答后,向从设备发送要写的存储区域的首地址,24C65的存储地址是16位,先发送高8位,收到应答后再发送低8位,再次收到应答后开始写数据。64Kbit大小位8K字节,需要13位即可表示,所以高3位固定定为0,如下图。

. M' B6 c0 j' g$ K$ V
这里是BYTE WRITE,一次写一个字节,此芯片还支持PAGE WRITE,一次写一页,也就是8个字节,如果想写更多,可设置一个for循环实现。
0 n/ o! f% n0 U: i
5.png

7 z; j6 e$ a1 u7 f% v7 Z; Y
24C65 读时序与写时序基本相同,只不过在读之前要发送再发送重复开始位进行读操作。
4 h# p1 e6 x% {% @
6.png

) f5 i$ O5 ]$ v0 }
  • I2C读写EEPROM实例

    ; ?: Q; G8 T7 J6 X9 h

' S6 g% \6 t7 _' m8 ^, Q
由电气原理图可知SCL和SDA分别接入了PB6和PB7管脚,读地址为1010 0001,即0xA1;写地址为10100000,即0xA0

% c, R! |7 f1 b3 b+ F, Y. v
步骤:
1.配置RCC

6 u; G5 `' E" a  v6 K4 _5 `
2.配置PB6和PB7管脚
1 I& r2 y) S. S  |
7.jpg
; N5 p- S) }3 @4 C, V1 Y# l9 X1 g
3.配置I2C协议参数

# H' F$ }' m" S6 t
8.png
; F1 O4 J" ]" }7 f/ ]: `6 _
4.编写代码

  1. 9 P- ~# J6 ?" X" P5 v- M) W% ]
  2. //mian.c
    3 K) F2 x- h3 O% i

  3. 4 j$ H9 X: s9 _5 L
  4. #include "main.h"  B9 R! ?9 _& s  G1 `
  5. #include "stm32f4xx_hal.h"
    3 v! c" S5 V* t, b
  6. #include "i2c.h"3 ?, |( o9 J9 ^( F1 J# [
  7. #include "usart.h"4 p, v8 w8 c7 i0 d& x# b, u7 N
  8. #include "gpio.h"; M4 K: I# @) M# V7 ^

  9. 1 i7 ?5 U" ~' q
  10. #define ReadAddr   0xA1
    2 @- y  ]& F8 B% P+ P
  11. #define WriteAddr  0xA0
    ) _, o' [6 J0 f' [# C3 z
  12. # V5 {% b9 _1 v( x
  13. uint8_t Wbuf[20] = "EEPROM TEST OK!";
    ; E1 d" Q7 C+ p  t
  14. uint8_t Rbuf[20] = {0};
    ( Z3 Q& f7 C3 G) l! H3 u
  15. 2 D1 t+ A, T$ B" O
  16. /*********  24C65写数据函数*****************************/9 M0 t7 [4 v& b/ h/ O6 |7 h

  17. " u' }$ q. q1 \) \# j% W: `
  18. void  Eeprom_Write(uint16_t MemAddr, uint8_t *Wbuf, uint16_t len ){0 K7 v" Q# q$ M/ R1 Y* b6 U' O( T
  19.         while(len--){
    3 A1 n' F+ x0 N0 k9 s( F/ z
  20.         //I2C_MEMADD_SIZE_16BIT表示存储单元大小. J7 F3 D% v' S8 U3 Z- U3 \* d% ^
  21.         //默认为两个参数,分别是I2C_MEMADD_SIZE_16BIT和I2C_MEMADD_SIZE_8BIT. ^- R: a# ?+ r1 x0 ^$ J$ [4 m
  22.         //由于24C65的存储地址是16位的
    ! Z3 H# ?+ M! i' E( j
  23.         //所以我们选择I2C_MEMADD_SIZE_16BIT
    & f: a/ k  v6 Z
  24.         //1表示一次写一个字节' }' T* x1 T7 x0 }% ~5 q  ^
  25.                 while(HAL_I2C_Mem_Write(&hi2c1, WriteAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Wbuf, 1, 100) != HAL_OK){};* Z1 i; @; h$ Y1 C9 f
  26. MemAddr++;
    5 h, v1 r# w$ \/ D. B) y& k8 c4 b! M
  27. Wbuf++;$ i' m' m5 }  l" e8 S8 P0 r2 M- {
  28. }
    % |( a) U1 G, p6 }# G
  29. }" k9 K$ E" [" ^4 f0 w

  30. 6 V/ k( \: v: `6 g" ]! W1 R4 a5 M

  31. + E' A. k! F3 I7 I
  32. /*********  24C65读数据函数*****************************/3 n: E9 k0 }6 \! D
  33. ; A1 n' N* O- i- e
  34. void  Eeprom_Read(uint16_t MemAddr, uint8_t *Rbuf, uint16_t len ){( j; n. D# f; e% h; m
  35.         //可以连续读,所以无需循环2 @* T6 u) |* z2 r0 n
  36.         while(HAL_I2C_Mem_Read(&hi2c1, ReadAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Rbuf, len, 100) != HAL_OK );( O( R& b4 M! @' F( I) _
  37. }
    & ^# m5 t1 R1 Y6 b' f6 c- c9 j
  38. int mian(){
    , C& G3 _" B" I/ G/ A6 r7 y; @
  39.       MX_GPIO_Init();! Q  W% p: g. R6 h, E2 O
  40.       MX_I2C1_Init();
    # j* A0 m' T. t% p
  41.       MX_USART1_UART_Init();0 m! u* ~+ g+ C$ y, F  g
  42.       printf("this is i2c eeprom test\n");
    9 K  P# w) P7 L

  43. ( b; c7 O. s3 K
  44.       Eeprom_Write(0, Wbuf, sizeof(Wbuf) );
    3 K2 U9 j8 b/ h/ W$ o& p( u
  45.       HAL_Delay(500);
    2 M9 P3 j/ w# I. k( q
  46.       Eeprom_Read(0 , Rbuf, sizeof(Rbuf));' P" q1 v( ~- W+ G. d
  47.       printf("READ:  %s\n", Rbuf);' Q+ \! t5 J* }3 h

  48. ' E2 O9 p! A1 K5 Q
  49.       while(){
    % ^3 z& w7 g+ n

  50. 1 U& ?2 U+ B/ ^  M, _# l
  51.       }
    & p: J/ B1 Q3 V5 h( w- ~
  52. }
复制代码

3 T8 Z3 @' h# u2 f$ h# p4 @6 ?
  • STM32 SPI总线通信专题讲解

    5 j3 W0 m/ H3 u5 i( {! p8 \) P6 x
SPI接口是Motorola 首先提出的全双工三线同步串行外围接口,采用主从模式(Master Slave)架构;支持多slave模式应用,一般仅支持单Master。时钟由Master控制,在时钟移位脉冲下,数据按位传输,是高位在前还是低位在前是可以配置的,配置时根据从设备的通信进行相应配置,一般是高位在前,低位在后(MSB first)。SPI接口有2根单向数据线,为全双工通信,目前应用中的数据速率可达几Mbps的水平。
SPI总线被广泛地使用在FLASH、ADC、LCD等设备与MCU间,要求通讯速率较高的场合。
7 c/ U# {% _; O# y  T$ [1 E* h
微信图片_20230412162643.png
- j0 c% y3 T% b% `, k' W* p
SPI接口共有4根信号线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。

6 @' I% n/ N6 p
微信图片_20230412162658.png

; \6 l) p$ S" L7 L0 e
(1)MOSI:主器件数据输出,从器件数据输入,连接从机的MOSI,与串口不同,串口需要反着连接(Rx-----Tx)
(2)MISO:主器件数据输入,从器件数据输出,连接从机的MISO
(3)SCLK :时钟信号,由主器件产生
(4)/SS:从器件使能信号,由主器件控制(片选),一般情况下为地电平选中设备,高电平释放设备。

8 @" c" d  a" M. M# N
  • SPI总线协议
    2 K. ^* h+ o' }% h( Y; A+ `
' |+ R( T' `7 L$ B3 o4 B' n. ~3 |
1.数据交换逻辑:主机和从机都包含一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节发起一次传输。寄存器通过MOSI信号线将字节传送给从机,从机也将自己的移位寄存器中的内容通过MISO信号线返回给主机。这样两个移位寄存器中的内容就被交换了。从机的写操作和读操作时同步完成的,因此SPI成为一个很有效的协议。
9 N, o7 e5 _5 K+ h6 @1 X
如果主机只想写不想读,只需把数据放在数据寄存器,SPI控制器会自动传给外设,同时忽略掉外设传过来的数据即可;如果主机只想读不想写,主机写给外设一个空字符或者随便写一个数据,外设就会把数据传过来,不管是只读还是只写,主机与外设的读和写都h会发生且同时进行。

. j' x& A& q6 _' S' u" }" o
微信图片_20230412162734.png
* k* N. s0 l) J, m
2.起始信号:  NSS信号线由高变低,是SPI通讯的起始信号。

9 f. y# n  q- @( p& I
3.结束信号:NSS信号由低变高,是SPI通讯的停止信号。
% J4 b$ U6 C, G
4.数据传输:SPI使用MOSI及MISO信号线来传输数据,使用SCK信号线进行数据同步。MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,按位传输,且数据输入输出是同时进行的。SPI每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制,要么是8位,要么是16位,可以配置。

+ G* k' a& W4 O# C+ h! i4 a. j
微信图片_20230412162755.png

. y) U5 S' b- A' e
  • SPI的4种通信模式
    : t5 ~+ c( b& q4 r0 D% T  A0 O  ]

2 N9 m( w; [- I! t
在SPI操作中,最重要的两项设置就是时钟极性(CPOL)和时钟相位(CPHA)这两项即是主从设备间数据采样的约定方式。由CPOL及CPHA的不同状态,SPI分成了四种模式,主机与从机需要工作在相同的模式下才可以正常通讯,因此通常主机要按照从机支持的模式去设置。同样在配置时一定要弄明白从机支持什么通信模式进行相应的配置。
( d  n9 r. T* E* M" V1 m8 P2 A9 K4 I
13.png
' M" p' h0 k5 c8 \
1.时钟极性CPOL : 设置时钟空闲时的电平:
a.当CPOL= 0 ,SCK引脚在空闲状态保持低电平;
b.当CPOL= 1 ,SCK引脚在空闲状态保持高电平。

1 E1 j5 L; I* x" ~% j* P
2.时钟相位CPHA :设置数据采样时的时钟沿:
a.当 CPHA=0时,MOSI或 MISO 数据线上的信号将会在 SCK时钟线的奇数边沿被采样
b.当 CPHA=1时, MOSI或 MISO 数据线上的信号将会在 SCK时钟线的偶数边沿被采样

  g! K2 F% |$ w- y% R- f" n
微信图片_20230412162837.png

+ d! l* [" N* c+ V0 [. z7 g
11.png
  • STM32F4-SPI控制器特性

    , n( @! z- W8 K6 i" F
12.png
  n0 K5 L% d3 e8 g% E7 T: T6 S
1.通讯引脚:
STM32F4芯片最多支持6个SPI外设控制器,它们的SPI通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚,以《STM32F4xx规格书》为准。f407只有SPI1、SPI2、SPI3。

$ V0 Q8 h( a& u6 i
微信图片_20230412163014.png
. |9 O2 h* C8 [$ B' C& F% ~! s; I
其中SPI1、SPI4、SPI5、SPI6是APB2上的设备,最高通信速率达42Mbtis/s,SPI2、SPI3是APB1上的设备,最高通信速率为21Mbits/s。其它功能上没有差异。
+ W/ l0 k* j( e/ ]0 u
2.时钟控制逻辑:
SCK线的时钟信号,由波特率发生器根据“控制寄存器CR1”中的BR[0:2]位控制,该位是对f pclk 时钟的分频因子,对f pclk 的分频结果就是SCK引脚的输出时钟频率。
0 o; R( _8 c+ }% z* [' c
微信图片_20230412161739.png
+ J& V9 ^0 @' W: b4 U. H! J& a
其中的fpclk 频率是指SPI所在的APB总线频率,APB1为fpclk1 ,APB2为fpckl2

. p1 I$ g. ^8 \6 t9 D* m3 c9 ]
3.数据控制逻辑:
STM32F4的MOSI及MISO都连接到数据移位寄存器上,数据移位寄存器的数据来源来源于接收缓冲区及发送缓冲区。

  {! U- `. y8 Q! u/ x
a.通过写SPI的“数据寄存器DR”把数据填充到发送缓冲区中。
( q. r" ?& j5 J7 q8 i3 l/ o' f% f
b.通过读“数据寄存器DR”,可以获取接收缓冲区中的内容。

+ \7 N5 U+ D+ J& H, g
c.其中数据帧长度可以通过“控制寄存器CR1”的“DFF位”配置成8位及16位模式;配置“LSBFIRST位”可选择MSB先行(高位在前)还是LSB先行(低位在前)。

3 }! @' [& k5 v1 U, G! I
4.整体控制逻辑:
a.整体控制逻辑负责协调整个SPI外设,控制逻辑的工作模式根据“控制寄存器(CR1/CR2)”的参数而改变,基本的控制参数包括前面提到的SPI模式、波特率、LSB先行、主从模式、单双向模式(同时发送和接收、只发送关掉接收、只接收关掉发送)等等。
& t+ _& e3 F  \1 y! A: k$ l, m2 O
b.在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,只要读取状态寄存器相关的寄存器位,就可以了解SPI的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生SPI中断信号、DMA请求及控制NSS信号线。
" }% e7 R" ?$ N: H
c.实际应用中,一般不使用STM32 SPI外设的标准NSS信号线,而是更简单地使用普通的GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号。
  • 串行FLASH_W25X16简介

    ; Q+ Z8 T6 Z& j: i/ X

* K& _+ O# Q+ J2 B+ J1 A
FLSAH 存储器又称闪存,它与EEPROM都是掉电后数据不丢失的存储器,但FLASH存储器容量普遍大于 EEPROM,现在基本取代了它的地位。我们生活中常用的 U盘、SD卡、SSD 固态硬盘以及我们 STM32 芯片内部用于存储程序的设备,都是 FLASH 类型的存储器。在存储控制上,最主要的区别是FLASH 芯片只能一大片一大片地擦写,而EEPROM可以单个字节擦写。
+ z5 B8 G8 A" U* Z* Z4 f
W25X16有8192个可编程页,每页256字节。用“页编程指令”每次就可以编程256个字节。用扇区擦除指令每次可以擦除16页,即一个扇区包含16页,用块擦除指令每次可以擦除256页,用整片擦除指令即可以擦除整个芯片。W25X16有512个可擦除扇区或32个可擦除块。

! D' \$ i6 Z+ s/ t. B; u/ p! \0 A
微信图片_20230412161735.png
7 x$ P3 ^& H* h7 A& V! y6 }3 F
1.W25X16的硬件连线如下:
3 {  z  u6 `2 U& n
微信图片_20230412161643.png
+ \" H: Z# a) R4 B  O; D
CS:    片选引脚,低电平有效,连接到STM32-PH2管脚
SO:   连接到STM32-PB4管脚(MISO)
SI:    连接到STM32-PB5管脚(MOSI)
CLK:  连接到STM32-PA5管脚(CLK)
WP:   写保护管脚,低电平有效,有效时禁止写入数据。接电源未使用
HOLD: HOLD 引脚可用于暂停通讯,该引脚为低电平时,通讯暂停,未使用

5 d& R' `2 E3 A$ r8 a: M6 N6 \3 v6 D
2.W25X16控制指令:
我们需要了解如何对FLASH芯片进行读写。FLASH 芯片自定义了很多指令,我们通过控制 STM32利用 SPI总线向 FLASH 芯片发送指令,FLASH芯片收到后就会执行相应的操作。
而这些指令,对主机端(STM32)来说,只是它遵守最基本的 SPI通讯协议发送出的数据,但在设备端(FLASH 芯片)把这些数据解释成不同的意义,所以才成为指令。
6 P4 L. N( l# _
微信图片_20230412161719.png

' i# S; B' }5 y# x: C; J
a.读制造商/设备ID(90):该指令通常在调试程序的时候用到,判断SPI通信是否正常。该指令通过主器件拉低/CS片选使能器件开始传输,首先通过DI线传输“90H”指令,接着传输000000H的24位地址(A23-A0),之后从器件会通过DO线返回制造商ID(EFH)和设备ID。(注:SPI为数据交换通信,主器件在发送“90H”指令时也会接收到一个字节FFH,但此数据为无效数据)
& i# P, R% d2 V& \) h
微信图片_20230412161715.png

5 j: X( a+ d* h5 P. h
b.写使能命令(06H):在向 FLASH 芯片存储矩阵写入数据前,首先要使能写操作,通过“Write Enable”命令即可写使能。
2 f! k6 i% K3 g& w1 ]
微信图片_20230412161711.png

5 G/ c/ p) w1 m% m; m8 u
c.扇区擦除(20H):由于 FLASH 存储器的特性决定了它只能把原来为“1”的数据位改写成“0”,而原来为“0”的数据位不能直接改写为“1”。所以这里涉及到数据“擦除”的概念。

$ u3 f- M  x1 F! ?3 H* D
在写入前,必须要对目标存储矩阵进行擦除操作,把矩阵中的数据位擦除为“1”,在数据写入的时候,如果要存储数据“1”,那就不修改存储矩阵 ,在要存储数据“0”时,才更改该位。
( D( r3 g* i$ I4 Y! a
微信图片_20230412161707.png
微信图片_20230412161704.png

3 P$ z$ L$ Y7 q5 p! M: a0 p- f
d.读状态寄存器(05H):FLASH 芯片向内部存储矩阵写入数据需要消耗一定的时间,并不是在总线通讯结束的一瞬间完成的,所以在写操作后需要确认FLASH芯片“空闲”。我们只需要读取FLASH芯片内部的状态寄存器SRP的S0即可(当这个位为“1”时,表明 FLASH芯片处于忙碌状态,它可能正在对内部的存储矩阵进行“擦除”或“数据写入”的操作)
. E7 N) T3 t* n! ~. U
微信图片_20230412161658.png
& G0 g+ [6 }0 b" A
微信图片_20230412161654.png
/ d6 U- B. `  H* Q+ {
e.读数据(03H):读数据指令可从存储器依次一个或多个数据字节,该指令通过主器件拉低/CS电平使能设备开始传输,然后传输“03H”指令,接着通过DI管脚传输24位芯片存储地址,从器件接到地址后,寻址存储器中的数据通过DO引脚输出。每传输一个字节地址自动递增,所以只要时钟继续传输,可以不断读取存储器中的数据。

! `5 b, [5 o/ n3 t
微信图片_20230412161650.png

# l$ ~' I4 {$ ~2 i
f.写数据——页编程(02H):页编程指令可以在已擦除的存储单元中写入256个字节。该指令先拉低/CS引脚电平,接着传输“02H”指令和24位地址。后面接着传输至少一个数据字节,最多256字节。

: ]! `6 K( Z1 X3 W; p
注:当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。
" A+ t9 T1 ?5 }( L1 X5 ^; W1 t
微信图片_20230412161646.png
5 ?* S0 |" P$ O4 W" I
  • STM32 SPI_FLASH基本配置和操作
    , a" c5 i* G' Y6 a  g9 ~# `
7 x- E0 ], ], h
根据如下的硬件连线图进行配置
" [6 x% U/ F- y( w* X  ]4 e$ \" Q. E
微信图片_20230412161729.png
2 |% m1 G  N- n
步骤:
1.使能时钟RCC
1 z. n& I( V9 s) O
2.使能SPI1,配置相应管脚

: f7 j& o2 Z+ ]0 D3 G
微信图片_20230412161640.png
, ~- u- \1 ~1 Y! W4 r
微信图片_20230412161637.png
$ E! t9 ^- u6 \5 B* a% D
3.配置SPI协议
# \. w$ S  L, [) b0 p) c# q2 g7 d
微信图片_20230412161632.png
) z9 i9 ^8 a! C9 E9 j
& Q2 a, P( @0 c0 Z% k5 N
4.编码
  1. //main.c
    8 L' ]. ^9 l- X( }4 L  ~
  2. & A& ^9 n6 ~9 t9 u, {* n4 i8 n% p
  3. #include "w25x16.h"! Q$ \5 c2 Y. o7 @; v
  4. " p4 e7 U& A  F. k5 e" @) z
  5. uint8_t RD_Buffer[5000] = {0};1 q' W0 }$ B6 u5 u6 m% s
  6. uint8_t WR_Buffer[5000] = "SPI FLASH WRITE TEST\n";4 \) ?  ]2 w0 R( ?( x
  7. % s* |, A1 f$ X7 ^1 r7 M' h: I
  8. int main(){
    - J- C. K+ s  w( H' u3 l
  9.     uint16_t FLASH_ID = 0;
    9 T5 y2 P7 R# c& y- y# {
  10.     uint32_t i;: T$ @2 Y8 n3 |6 L7 ^1 e
  11.    
    , z( a1 ~2 r0 ^! A: r* K
  12.     MX_GPIO_Init();9 Q- n2 ?2 A+ P4 |8 c- s
  13.     MX_SPI1_Init();
    . V$ u4 L4 t* w8 [* H) C) l
  14.     FLASH_ID = sFLASH_ReadID();) b% s8 A- u/ M
  15.    
    ) F% d6 D. {- g6 w7 c( O, ]
  16.     /******测试擦除******/
    9 G9 T* e! h7 C- @6 L5 q; E
  17.     sFLASH_EraseSector(4096*0);, Y8 S6 p+ N( l2 r. X+ D7 Q  r8 ]* t1 \( K
  18.     //sFLASH_EraseSector(4096*1);( @2 o5 s- s" R9 d. b( \
  19.     sFLASH_ReadBuffer(RD_Buffer,0,4096);$ F& n  z; m% ^* a6 U
  20.     printf("读数据开始\n");
    . N7 |0 e( Z& ^0 T; g
  21.     for(i=0; i<4096; i++)
    % G+ p' c, [; d9 q
  22.     {% Q" R: K( I9 B
  23.     printf("%x ",RD_Buffer[i]);! _3 g5 M$ D  d& [( s" [! B
  24.     }
    # w' B* ~9 y7 M0 o' }. _' ?/ k( A
  25.     printf("读数据结束\n");' ?: R# {8 g5 Q" s0 |

  26. - R6 J7 W- ]2 N" T
  27. : l5 p  L4 O0 V2 @/ P! J0 h. I
  28.     /******测试写操作1*****// _% [' b+ ?% V  E2 z
  29.     //写之前都需要擦除扇区
    8 h, _1 M! O. ~8 Q8 w  G, d* m. F
  30.     sFLASH_EraseSector(4096*0);  Z' S2 p' ^% {8 @8 {6 P1 \
  31.     sFLASH_WritePage(WR_Buffer,0, 20);% P2 u: g4 j+ X4 ?$ v
  32.     sFLASH_ReadBuffer(RD_Buffer,0,20);
    , ^, d& F4 x+ `. [" ~) R5 ~8 a
  33.     printf("READ DATA: %s\n",RD_Buffer);
    , v! a, P1 c& W! O  i4 w% g2 x, H
  34.    
    ! Q* P2 @$ A  x; T3 R
  35.     /******测试写操作2*****/$ Z. Y% j, M  K" [1 P) Z
  36.     //写之前都需要擦除扇区  @8 k/ {$ a; c6 t0 d+ w  P
  37.     sFLASH_EraseSector(4096*0);
    0 w$ v2 b0 x" @0 Q; C8 O% D
  38.     sFLASH_EraseSector(4096*1);                            ) k; ~- n* E+ d; d) y
  39.     for(i=0; i<4096; i++)
    9 C0 y( l1 Y7 ^( b/ e% X- x+ U1 N
  40.     {3 j  T2 g7 e2 J" q
  41.     WR_Buffer[i] = 0x55;. `$ s7 L2 w* c: z+ W; l
  42.     }
    . V6 I) p* N* W+ o3 R& G4 h
  43.     sFLASH_WriteBuffer(WR_Buffer,4090, 1000);1 P$ {# [& {  p: _
  44.     sFLASH_ReadBuffer(RD_Buffer,4090,1000);9 B! S5 w  g( S
  45.     for(i=0; i<1000; i++): U7 ?: ?  U2 _! O$ Y7 ^
  46.     {# s9 l5 g3 s7 m
  47.     printf("%x ",RD_Buffer[i]);
    2 g$ v0 ~" ?8 W! Y( p+ U8 S* E
  48.     }
      {. l6 W+ C" z/ D, \$ {
  49.     /*****************/
    3 p" i5 p- }9 t: ^  [# c  E1 B
  50.     while(){}
    9 Y( Y; K" Q! h/ n9 K; \
  51. }
复制代码
  1. //w25x16.h
    : o" S, ]1 K3 E3 o

  2. - \0 O8 e$ F2 \0 ~$ u
  3. #ifndef __W25X16_H& h- ?( A1 J' [# ^; `( b6 p0 \
  4. #define __W25X16_H" L2 }2 @$ \9 W1 y

  5. ' x, b* i$ }& N1 F+ Z# f& r/ R
  6. #include "stm32f4xx_hal.h"
    ' a- f- W( N/ f! d: u) X  B
  7. 2 @# C' e5 R# z- c5 |
  8. //使用宏定义芯片指令
    & P( `# P5 y8 x
  9. #define W25X_ManufactDeviceID  0x90  /* Read identification */; h$ z8 a" v9 A
  10. #define sFLASH_CMD_WREN        0x06/* Write enable instruction */
    + K" ]% N+ K5 W6 R- u3 A
  11. #define sFLASH_CMD_RDSR        0x05/* Read Status Register instruction  */
    ' g- o$ [9 B! V% x7 o; K; h
  12. #define sFLASH_CMD_SE          0x20/* Sector Erase instruction */) H' S- s& F+ E! ~
  13. #define sFLASH_CMD_WRITE       0x02  /* Write to Memory instruction */
    ) R% v  j! w# f6 k
  14. #define sFLASH_CMD_READ        0x03/* Read from Memory instruction *// x' s2 h: }5 }- j$ O0 b( V, i; F
  15. #define sFLASH_DUMMY_BYTE      0x00 //空字节,用于只读传回来的数据
    3 @( d; b# Q9 J. Z0 m4 l9 y: P; C
  16. #define sFLASH_BUSY_FLAG       0x01
    ! {4 c; Y. L* Q% I6 t) W) d/ d
  17. #define sFLASH_SPI_PAGESIZE    0x100+ M5 p; j  V! g$ J
  18. 5 B$ D) j7 L6 Q9 h
  19. /* 选中芯片,拉低信号 */
    5 s. p! k7 g& a' C% O
  20. #define sFLASH_CS_LOW()       HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_RESET). w9 |, a3 c0 u) F
  21. /* 释放芯片,拉高信号 */# |: Q0 I2 E% i! p8 i  n
  22. #define sFLASH_CS_HIGH()      HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_SET)8 H2 i8 P8 M! k7 h+ Y0 y( O
  23. 4 I8 m1 O% {$ K3 Z; e. c+ ~$ `3 _7 ~5 f
  24. //定义函数: Q' s" o5 `8 x3 {; D5 ~
  25. uint8_t sFLASH_SendByte(uint8_t byte);
    # h' }, M6 w$ J' S/ s
  26. uint16_t sFLASH_ReadID(void);/ x, X' l! n2 F/ n4 _
  27. void sFLASH_EraseSector(uint32_t SectorAddr);
    0 v6 S1 n/ t( ]6 P. [# m3 \0 v
  28. void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);* c$ x, s4 |  s, G3 x  ]- n
  29. void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);6 M% j5 ^7 v, d# x0 _, ]
  30. void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead);2 a5 C+ r) s1 Z. C; X  U. s
  31. #endif
复制代码

  1. , c' n1 F' {# \6 X% b
  2. //w25x16.c
    % z9 @1 [/ h% b& [/ i& U/ Q

  3. " X1 \5 s  I4 H" I3 `
  4. #include "w25x16.h"
    9 W6 E) A  P4 K1 T' ^( q
  5. % U; g; ~+ C' m8 b7 O$ a  V
  6. extern SPI_HandleTypeDef hspi1;! f- o/ I1 R1 ]7 M3 c

  7. ) _# X. i: D( m
  8. /*读写一个字节函数,因为SPI读和写同时完成*/
    ' }* `. T( l' F; A4 I% B
  9. /*发送数据一定会接收到一个数据*/
    / K) w' f" k% r
  10. uint8_t sFLASH_SendByte(uint8_t byte)1 ^7 A: w$ O8 n4 M) a+ ~
  11. {
    8 s- _1 x" r: Q0 x
  12.     uint8_t TX_DATA = byte;
    $ v$ h1 |! p$ b. m% P" @8 n3 \
  13.     uint8_t RX_DATA = 0;
    8 T5 o1 E1 Z- p* m( ^4 g
  14.     HAL_SPI_TransmitReceive(&hspi1, &TX_DATA ,&RX_DATA , 1, 1000);7 `( h9 d4 b  r0 `; \9 a
  15.     return RX_DATA;
    - `7 _/ |' P( Y& D& m
  16. }
    1 p. W/ l* a6 |: o  W

  17. % `+ Z2 T, j' L9 }: P
  18. /*等待擦除或者写数据完成*/6 a, u& w: o/ s. X9 N2 R
  19. void sFLASH_WaitForEnd(void)
    - G. Z5 w: x6 _
  20. {$ a2 Y# v5 D. p1 ~9 @. r" q& J
  21.     uint8_t sr_value = 0;: {3 j! n& ~6 [8 p4 t
  22.     sFLASH_CS_LOW();
    % e* W' m* X' c! x2 q
  23.     sFLASH_SendByte(sFLASH_CMD_RDSR);" f. [* N1 h  P/ K' ^4 M
  24.             //读S0的值,为1表示忙碌,为0表示停止         
    : Q3 Y5 S, ^9 x) c8 F' T
  25.     do{! `% K# n1 }$ \# S% h# B2 \6 O( Y
  26.                 //发一个空字节,得到S0的值            
    1 e6 S0 y4 v  j: D! p8 b3 K
  27.                 sr_value = sFLASH_SendByte(sFLASH_DUMMY_BYTE);2 k# `& V- N: R0 C* I/ h
  28.     }while( sr_value & sFLASH_BUSY_FLAG);
    / T  Q* z% @) O( [
  29. . t4 X1 c7 ~; H0 x
  30.     sFLASH_CS_HIGH();
    & H+ @( J$ h2 e# V* p1 |
  31. }
    % \7 c6 L% T/ B; H
  32. ' Y( N* g+ P% {; R4 C; ?
  33. void sFLASH_WriteEnable(void)7 f( \  z/ {, D! n
  34. {& Z$ v0 I# A, n, U' H2 M
  35.     sFLASH_CS_LOW();
    7 ~( O8 \7 V" @9 x- G' @
  36.    
    : E; s2 c, c5 T
  37.     sFLASH_SendByte(sFLASH_CMD_WREN);
    + s, t# Z9 p2 v. m
  38.     " d: w6 G0 a. X
  39.     sFLASH_CS_HIGH();
    % ?1 @) \) J& _3 q' Y1 i
  40. }- x: n! r- ~. L. A
  41. 9 H* l4 d  x& `! n
  42. 1 B- k' \4 [0 d
  43. /*读设备ID*/ ; D) P9 J3 Q9 d  v" w
  44. uint16_t sFLASH_ReadID(void)) Q6 L+ w7 i4 ]3 ^& ]
  45. {9 W( ]; [. g: e* v' R
  46.     uint16_t FLASH_ID;; @  E7 B# _9 ]# S
  47.     uint8_t temp0,temp1;9 o4 g. D3 B. J( w* F; H" J- P: G
  48.     8 z9 x& V0 x$ N/ X1 h& W1 j3 w
  49.    
    4 L: F5 p2 |2 y8 E" |* z
  50.     sFLASH_CS_LOW();
    * A6 m  ~6 {. v! I% {  W8 G
  51.     # {7 T, ]# C7 ]1 t* D2 M# l( k
  52.     sFLASH_SendByte(W25X_ManufactDeviceID);( g7 b* _0 x0 r* A
  53.     //读设备指令后要发24位地址,所以要发三次8 P/ Z' W. [! ]1 i' z  e, [
  54.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);5 \. ^  f+ c5 ~9 h" k, Y4 q
  55.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);4 {+ O$ I  ]7 C0 E  M" J6 Q( N; r
  56.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);0 q1 j. x3 T+ k- q
  57.     //制造商ID: S. \) H3 ^: i, i( ?
  58.     temp0 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);( u- v  j- ?1 U& J
  59.     //设备商ID
    # S  c# L% W% E( a
  60.             temp1 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);% I$ s: ~7 x" R6 }0 f, f
  61.    
    ! B: C5 _/ g3 u0 r8 P
  62.     sFLASH_CS_HIGH();
    , n$ @! w3 P( l* S8 d! s* B
  63.     5 F( s- c; ~' N/ b
  64.     FLASH_ID = (temp0 << 8) | temp1;: Z+ M$ F- _0 @  c+ G- N
  65.     8 N: v; P2 v( J! O9 h; O: C
  66.     return FLASH_ID;
    & y" C( T7 v0 E: p/ ]! c, [4 O
  67. }
    * p  f+ Y1 K* o7 }% G: ~
  68.   l  Q* I1 f# ?3 ~  Q
  69. //擦除扇区,擦除为1,因为只能由1变为0 ,不能0变1
    5 A# }$ Y; ?7 W% e
  70. void sFLASH_EraseSector(uint32_t SectorAddr)
    " d* H3 }( l* Z$ l; L
  71. {
    " i1 V: O' m+ f# a; V3 v
  72.     //SectorAddr表示擦除第几个扇区
    ( O# a# B6 M) m( {8 h+ `) P) F: {
  73.     sFLASH_WriteEnable();  //开启写使能2 I, b* g/ }/ x& v& n8 f
  74.     sFLASH_CS_LOW();//拉低,片选
    2 d& y/ W5 h: v2 r* W* ~
  75.     //擦除命令$ M5 e5 s7 `6 M9 m# W4 C4 Z4 |; L
  76.     sFLASH_SendByte(sFLASH_CMD_SE);% l2 ]" w. ~/ T% K4 k6 c. i
  77.             //传24位地址
    " I7 O. f3 R) o
  78.             //传送高8位,将中8位和低8位一共16位移出去,得到高8位                   6 j% v* J0 T5 ~4 J7 K3 ^! \5 q  ]3 ^
  79.     sFLASH_SendByte( (SectorAddr>>16) & 0xff);   8 z* z# f9 `% W
  80.     sFLASH_SendByte( (SectorAddr>>8) & 0xff);    //传送中8位4 l3 ^' I4 ]: G# {: }4 v
  81.     sFLASH_SendByte( (SectorAddr>>0) & 0xff);    //传送低8位
    % A( f: [! w  u9 K
  82.        \9 ~  ], j# `8 ^
  83.     sFLASH_CS_HIGH();3 Y# L/ p/ U9 o, v2 ~$ {
  84.     ) ~* K$ a+ ]2 o$ v& C& \
  85.     /*读状态寄存器,等待擦除完成*// F2 s0 L; \# D, {1 R3 h$ `5 W! b
  86.     sFLASH_WaitForEnd();2 M. B! v7 ^+ z+ u0 ]; q) M( l8 L
  87. }" x2 U- N. }& ^( i6 Z4 o/ z

  88. 3 r8 ]5 Z: ~) F' y. ]5 O- l
  89. //读数据
    6 i1 v$ z! t, T+ J4 a& u5 C( ?
  90. //读命令和读地址发送后,芯片内部会自动不断递增读数据# b( j& C  \, W- p3 [6 p
  91. void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead)4 }) w& M$ q, p/ T3 T
  92. {
    9 x" O2 Q* o7 i5 K) V) p0 D, n
  93.     sFLASH_CS_LOW();
      c+ r% t2 g9 R# `$ T
  94.    
    / A4 y/ v2 S0 x8 K) l* b% c; `- c
  95.     sFLASH_SendByte(sFLASH_CMD_READ);
    3 [) h6 d- J/ G' E/ k+ M5 d; N+ P
  96.     sFLASH_SendByte( (ReadAddr>>16) & 0xff);   //传送高8位3 r* _6 A& N+ ^, O4 A: }. @
  97.     sFLASH_SendByte( (ReadAddr>>8) & 0xff);    //传送中8位
    + }- l% S3 V( q3 d. b1 n
  98.     sFLASH_SendByte( (ReadAddr>>0) & 0xff);    //传送低8位
    # K& n8 Q+ C6 x4 Q1 y
  99.     5 f5 u: }" F9 z5 y8 S1 T- }) q+ M' V
  100.     while(NumByteToRead--). a3 Z6 p1 n& h- g0 F/ O4 r9 Q
  101.     {
    ! u" J. F/ z5 ^' P  n' e
  102.     * pBuffer = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    . ^" L8 m4 {) a% M8 t
  103.     pBuffer++;
    ; K, _+ d- e4 s9 T0 ^9 p: V
  104.     }9 u0 s) J6 H* X4 T: L
  105.       a' t/ T9 _& m# ]) r' q6 O
  106.     sFLASH_CS_HIGH();
    9 B! T& a8 ?# j, Y; \) d/ U
  107. }7 [; H& ^6 T8 P+ m; i* g

  108. 8 n2 m2 j: g$ q( ?
  109. //写一页最多只能写256个字节,一个扇区16页,一个块16个扇区                              
    2 ]" `, P6 L2 r; \. t. O
  110. void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
    8 E1 w  i7 `$ t1 l6 G" o
  111. {9 Z) @7 l: u" ~+ I
  112.     if(NumByteToWrite > sFLASH_SPI_PAGESIZE )
    ( c( L5 T% U; \/ ]. X
  113.     {4 u( R; v! g* H- `
  114.     NumByteToWrite = sFLASH_SPI_PAGESIZE;9 D7 s. W# \* r! A* i
  115.    
    . E$ e  |& H3 C: Q
  116.     printf("写数据量过大,超过一页大小\n");
    * n3 k5 o, M7 \$ M2 I
  117.     }
    0 M: m  x1 t  h8 z- W
  118.    
    8 e$ H+ \$ p& Q  M; p% C. x
  119.     sFLASH_WriteEnable();  //开启写使能$ E4 D) z! v7 D0 q0 p6 j$ s! V
  120.     ) y- U$ v( Q' |1 I9 v
  121.     sFLASH_CS_LOW();1 v! b; ~  F2 g) V4 F
  122.     8 t" M  Z% F; a3 z! c8 x! [+ b
  123.     sFLASH_SendByte(sFLASH_CMD_WRITE);
    1 v' W. a- d& o( k. i# Z6 N
  124.     sFLASH_SendByte( (WriteAddr>>16) & 0xff);   //传送高8位
    & g, F- ^2 }( N! \$ ~+ F# A# c
  125.     sFLASH_SendByte( (WriteAddr>>8) & 0xff);     //传送中8位
    . n7 C& X$ G  R* A3 K* a" l3 E
  126.     sFLASH_SendByte( (WriteAddr>>0) & 0xff);      //传送低8位$ {, E# L" ^/ A- i5 E
  127.     - z9 b  P+ n' S3 H6 ]+ w
  128.     while(NumByteToWrite--)7 J5 h& x3 q8 x/ w9 e) X
  129.     {
    * j' m1 r  m& V2 k. O+ s
  130.     sFLASH_SendByte(* pBuffer);  {  |% u* O0 i2 e
  131.     pBuffer++;
    ) I) k6 A6 a! ^" X' m
  132.     }
    * p1 T6 i9 p. w
  133.    
    3 b; E; }( S. _* Y. J6 V; P
  134.     sFLASH_CS_HIGH();
    / K$ |7 _- I, i! A( @- N9 M
  135.     $ U/ ]8 q: c1 ?4 \/ e# O
  136.     /*擦除和写数据都涉及到写动作,一定要等待完成*/7 m& d' [0 Y: }
  137.     sFLASH_WaitForEnd();) o3 j9 y: w+ j; v
  138. }: |7 u4 }7 i3 C: O0 G
  139.   [! s% C7 h1 n7 j; s
  140. //写任意地址、任意长度
    4 r0 y1 A. z, `- l* o. u; ~8 ^
  141. void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
    8 C: O% x* O" M$ G
  142. {
    ) n3 I3 ?! C+ c" r# z9 X* _
  143.     uint16_t NumOfPage, NumOfBytes, count, offset;
    & z: w2 H6 j/ Z/ _7 J0 ^  R
  144.     //求WriteAddr在某一页的位置        
    ( q  A- M+ Y# o; s* c6 X6 d
  145.     offset = WriteAddr % sFLASH_SPI_PAGESIZE;
    7 `, o" v: ]# r8 v( ^
  146.     //求某一页剩余的大小 ( L% |4 d' f' N1 k9 O( c4 W2 D6 I
  147.     count = sFLASH_SPI_PAGESIZE - offset;3 G- u! s( z6 B6 ]: r9 g2 G/ `
  148.     & y  z: [3 K& V6 |  Y9 @* t% R% }
  149.     /*处理页不对齐的情况,防止页内覆盖*/6 y( A6 ?) \* @8 P% e! @# }& g
  150.     //先把某一页剩下的部分写掉,之后的就能新页的起始处开始写        /*offset有值表示需要页对齐,如果要写的字节数小于某一页剩余的部分,那就无需对齐*/                   4 D7 o; i! e7 v3 O4 W, Q2 \) k, L
  151.     /*这两个条件必须同时满足*/
    3 T; b, U) A7 K7 G' F
  152.     if(offset && (NumByteToWrite > count ))) P0 L, \- T( M( Q0 u/ A9 O, o
  153.     {
      ^, G! D- [8 P. h: q
  154.     sFLASH_WritePage(pBuffer,WriteAddr,count);
    ! \$ G6 P' a4 G# e; s) ?; o
  155.     NumByteToWrite -= count;//去掉已经写了的,从新页开始5 k% \: G5 c+ X$ ^3 {) \! _
  156.     pBuffer += count;  D& f# Q% Z( F* i5 y
  157.     WriteAddr += count;
    ' }+ q  d' {6 E$ f, U: P; z3 p% W. N4 z
  158.     }# K! O5 K' _2 s9 p& x" c
  159.    
    % M0 x7 Z: Y$ W/ O( H" S
  160.     /*最多可分多少页*/
    % q, t6 @5 d( M, d
  161.     NumOfPage = NumByteToWrite / sFLASH_SPI_PAGESIZE;% d& `' k: B3 D0 f* t  u
  162.     /*剩余多少字节*/
    ' E0 @: |: U- f0 h
  163.     NumOfBytes = NumByteToWrite % sFLASH_SPI_PAGESIZE;: r2 h4 q& c: h
  164.     , ~6 {& D- f/ J1 K6 H6 Y
  165.     if(NumOfPage)% _. \/ c5 _4 j' i9 |
  166.     {' p! ?" j* L8 [& ?- J7 S! y% h/ x
  167.         while(NumOfPage--)
    / }5 X/ V4 @( L
  168.         {
    ; r6 O4 d2 I$ R8 [6 P
  169.              //每一页都发起页编程
    " E9 f& y/ H1 m- u3 V  R0 Z
  170.              sFLASH_WritePage(pBuffer,WriteAddr,sFLASH_SPI_PAGESIZE);$ Q; x! [3 E) R3 i1 J1 J, N/ Y
  171.              pBuffer += sFLASH_SPI_PAGESIZE;5 }; @% b3 j) R- y0 I3 w
  172.      WriteAddr += sFLASH_SPI_PAGESIZE;  Q9 P9 |' S/ m! O; r5 O# E" z9 K
  173.          }3 I* T8 s' m! B6 [
  174.     }
    ' ?0 ?4 }5 n, ~6 ~- L
  175.    
    ) r7 d4 T0 v% u) ^
  176.     if(NumOfBytes)
    + z0 s: U8 [0 q$ g
  177.     {
    ) s5 j7 P( a; O5 b- W, p# K4 e1 E8 M
  178.     sFLASH_WritePage(pBuffer,WriteAddr,NumOfBytes);
    5 l9 n/ q; E7 m9 Y% f
  179.     }% o* n( ]% ^1 W) B/ y7 f
  180. }
复制代码

6 l/ ]; H* p) e
  • 为什么会有两种写操作函数,是因为这里的写操作有两个特点:
    ! _& P  t8 c8 N* X7 A0 R

    + A2 f4 z( X/ T. A' D) o
7 [: @% A2 D! l" \
1.无法突破页限制,超过一页需要重新发起页编程信号。另外如果要写的数据大于剩余一页剩余的容量,那么超出的数据会写到当前页起始地址出。例如,初始输入的写地址为200,而要写的数据大小为100,那么要写的前56个字节会从地址200开始依次写入,剩下的44个字节会从当前页的0地址开始依次写入,这很有可能覆盖之前的数据。
7 Q$ m: o: h) D9 j. C5 w) z
2.无法突破扇区的限制,当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。

6 J! r4 L/ Q! p. ^
转载自: 骆驼听海
" }$ C$ x% p* n. K4 ~' Y, v1 S如有侵权请联系删除) I  [& N! w$ A; w

* ], c* d5 e# q+ d3 b
收藏 评论0 发布时间:2023-4-12 16:18

举报

0个回答

所属标签

相似分享

官网相关资源

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