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

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

[复制链接]
攻城狮Melo 发布时间:2023-4-12 16:18
. M7 d. X+ {0 v. H2 F
微信图片_20230412161833.jpg

* s/ H  E; ?3 z' o
本系列为ARM开发的进阶知识,讲解与ARM芯片进行通信的大部分高级外设的原理及使用方法,不局限于ARM芯片内部,实操性较强且晦涩,需要一定的读者具备一定代码阅读和理解能力。
- b- C7 R: `/ s! U
  • STM32 I2C总线通信专题讲解
    , u+ s7 I  Y. M- v
1 e) f2 c) x0 M& z
  • 总线介绍:I2C(Inter-Integrated Circuit)总线(也称IIC或I2C)是由PHILIPS公司开发的两线式串行总线(单双工),用于连接微控制器及其外围设备,在这两根线上可以挂很多设备,同一时刻只能有一个节点处于主机模式,其他节点处于从机模式,总线上数据的传送都由主机发起。I2C总线没有片选信号线,所以需要通过协议来找到对应操作的芯片。是微电子通信控制领域广泛采用的一种总线标准。它是同步通信的一种特殊形式,具有接口线少,控制方式简单,期间封装形式少,通信速率高等优点。* t6 R8 |( [. c' a
    0 \5 k5 y6 ^8 u& Z9 I
/ v. {1 H. {/ @$ f* q! k$ O5 [
  • 总线特征:
    # ?3 n$ C4 r6 d, M6 _
1.两条总线线路:一条串行数据SDA,一条串行时钟线SCL(主从设备使用同一时钟,属于同步通信)来完成数据的传输及外围器件的扩展
- B" G; V4 c0 S% Q1 F3 ^
2.I2C总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址,通常是7位,有时候是10位
* t- m9 j) b  o# G7 b& X
3.I2C总线数据传输速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。在开发配置的时候,最好检查从设备的传输速率从而对主设备(一般是MCU)进行相应的配置。一般通过I2C总线接口可编程时钟来实现传输速率的调整,同时也跟所接的上拉电阻的阻值有关。
2 f, h, g' f* {) X0 ~
4.I2C总线上的主设备与从设备之间以字节(8位)为单位进行单双工的数据传输。

% K% A  O2 d* {; @- X  e
  • 拓扑结构——总线型
    - a0 y9 C/ N, H( \' v; H
I2C 总线在物理连接上分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成,SCL由主机发出,SCL越快,通讯速率越快。通信原理是通过对SCL和SDA线高低电平时序的控制来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。
3 }% J: H4 `1 {9 `" q% Q7 o
微信图片_20230412161906.png

: C4 W8 {9 |+ \5 i0 i1 z( c0 q
  • I2C总线协议
    " H7 S; r, W5 g) Q! ?
1.I2C协议规定:  总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生。

  b0 e& q) J- P, l: a% u
2.空闲状态:SCL和SDA都保持着高电平。

7 U$ E2 P6 J. ~. }) Q  S
3.起始信号:  当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件,所有的从设备都能感受到这个跳变,做好准备等待被选择。

8 h/ s4 X/ t1 e  A
4.结束信号:当SCL为高而SDA由低到高的跳变,表示产生一个 停止条件
6 C! H- q" o  ^' j0 S) |4 y' ?
微信图片_20230412161928.png
% W1 k/ @/ p, ?/ m6 k2 ^
5.数据传输:数据传输以字节为单位 , 主设备在SCL线上产生每个时钟脉冲的过程中将在SDA线上传输一个数据位,数据在时钟的高电平被采样这时候采集到是1就是1,是0就是0,所以在传输数据时,当时钟处于高电平时一定要保持稳定,时钟处于低电平时可以变换数据。(高电平采样,低电平变换)一个字节按数据位从高位到低位的顺序进行传输。主设备在传输有效数据之前 要先指定从设备的地址,一般为7位,然后再发生数据传输的方向位, 0表示主设备向从设备写数据,1表示主设备向从设备读数据。主从设备以字节为单位(8位)进行数据传输,开始传输数据时把从设备地址加上方向位组成一个8位的字节进行发送并接收一个应答。

  I5 m+ y. ?5 v+ `5 I; p+ H+ ^# @
6.应答信号:接收数据的器件在接收到 8bit 数据后,向发送数据的器件发出低电平的应答信号,表示已收到数据。这个信号可以是主控器件发出,也可以是从动器件发出。总之,由接收数据的器件发出。

! W4 r: Q/ s4 c+ @. q9 }
微信图片_20230412161941.png

3 I& e0 M1 R( \3 O* n
a.主设备向从设备写数据:

3 h7 {; M  w0 E2 q" l
微信图片_20230412161956.png

/ }$ q5 K* E1 i& }  F  E! g9 b
b.主设备读从设备的数据:
# ^: q6 E$ j  o( s
1.png
; R( s, `# v+ ~1 \9 I
c.主设备读从设备的某个寄存器:读设备的寄存器首先应该对该设备发送写命令,很多设备都可以看成是一段内存,所以写命令写给从设备,指明要读取哪个地址(寄存器)的数据,接下来才是真正的读数据。不同的从设备是由区别的,在驱动I2C从设备时应当查明设备的时序图,又怎样的要求,不同的时序对应了不同的命令。
& ]' A0 d2 f, L
2.png

6 d4 b' I& Y  d- y; b8 D2 P. v9 J
2 O1 V) G+ w. h1 q0 L: G& _4 e: j
  • STM32F4-I2C控制器特性
    ' Q. _! M7 S3 ^. }' w: B9 y
软件模拟I2C时序:由于直接控制 GPIO 引脚电平产生通讯时序时,需要由 CPU 控制每个时刻的引脚状态,所以称之为“软件模拟协议”方式。我们知道,驱动I2C设备只需要两根管脚,即使单片机上没有I2C控制器,根据协议控制每根管脚每一时刻的电平状态,一根模拟数据线,一根模拟时钟线,就可以驱动从设备,相对而言效率低,但是可以实现控制驱动。STM32内部具备专门的I2C控制器,使用时只需对其进行相应的配置即可。

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

0 _( @5 A! _7 F' d2 E
控制器功能:配置主从模式(一般都把STM32当作主机使用,作为从机时应当对其赋一个地址),通过配置其内部的寄存器产生一些中断和错误信号,配置通信速率位标准模式、快速模式、超快速模式等

& e& O0 I; O  U
STM32芯片有3组I2C外设,可以同时进行3组I2C传输。它们的I2C通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚。

5 a* M$ N( A6 r' \2 j; ]) G9 W
微信图片_20230412162110.png
2 Z0 [( |! R& u7 b* t$ C
  • EEPROM(AT24CXX)存储芯片介绍
    ( Z8 d4 f. S/ v/ o# I) o9 }/ X
一个典型的I2C接口的从设备,专门用于存储数据的芯片。EEPROM (Electrically ErasableProgrammable read only memory),带电可擦可编程只读存储器,一种掉电后数据不丢失的存储芯片。EEPROM可以在电脑上或专用设备上擦除已有信息,重新编程。

6 Y* ]' M2 T- U
EEPROM常用来存储一些配置信息,以便系统重新上电的时候加载之,容量不会很高。EEPOM 芯片最常用的通讯方式就是I2C协议。XX表示容量,常用值为01、02、04、16、32、64等,单位Kbit。一般的存储芯片都具有写保护功能,对WP管脚加一个高电平就开启了写保护功能,就无法往芯片内写数据了。在开发中通常将该管脚接地,确保能够写数据
典型24CXX芯片引脚如下:
  Y6 U+ o  f6 g" `) b  z5 b
微信图片_20230412162134.png

/ v2 B2 e8 d( P! b# k7 _
例:24C65的设备地址为7位,高4位恒定为1010,低3位取决于A0-A2的电平状态,般主机在读写24CXX都是把设备地址连同读写位组合成一个字节一起发送。

( t7 C/ U/ q+ V4 x4 L) \
3.png

7 o' m* {* [5 I* r! Y
24C65的电气连线如下,根据电气连线可知,A0-A2均接地,因此读地址为1010 0001,即0xA1;写地址为10100000,即0xA0 ,且WP接地,用户随时可向芯片内部写入数据。
4 B5 i# n( c& U6 C' X0 x  T7 n1 q" B
4.png

& I" W( C% U+ ^+ {& l
24C65写时序:首先发送一个起始信号,接着发送从设备地址以及方向位,收到应答后,向从设备发送要写的存储区域的首地址,24C65的存储地址是16位,先发送高8位,收到应答后再发送低8位,再次收到应答后开始写数据。64Kbit大小位8K字节,需要13位即可表示,所以高3位固定定为0,如下图。
7 p. H1 G% I3 E9 V
这里是BYTE WRITE,一次写一个字节,此芯片还支持PAGE WRITE,一次写一页,也就是8个字节,如果想写更多,可设置一个for循环实现。

  s# N, H, E7 f0 J' H" @, w
5.png

$ H* m9 i. f% z6 z1 V' _
24C65 读时序与写时序基本相同,只不过在读之前要发送再发送重复开始位进行读操作。

7 V4 v" ~1 G5 z' p+ l9 Z# E: W) R% e
6.png
5 k9 F  n' _9 w: S+ T6 w
  • I2C读写EEPROM实例

      o2 Q! g1 w+ B4 Y6 t
3 C+ I0 T) A) R: u, q7 m& h1 y
由电气原理图可知SCL和SDA分别接入了PB6和PB7管脚,读地址为1010 0001,即0xA1;写地址为10100000,即0xA0
4 t7 g  U% ~! Q( Q
步骤:
1.配置RCC
1 N0 P8 A6 i1 C; r; j4 |
2.配置PB6和PB7管脚

2 Y2 j7 J0 {! I0 K2 W8 N
7.jpg
9 k4 b# s/ b0 r, N) z
3.配置I2C协议参数
+ i0 C9 a. t2 Y2 o6 I: _
8.png
! I; I0 x8 e5 u) v' l& s  `- K2 }- `  l* x% F
4.编写代码
  1. 9 {3 Y& k4 s, w, [
  2. //mian.c: M; i4 ^8 u1 ?! Y+ W& b

  3. " f" z1 R& t2 `$ z7 h. h9 G: D
  4. #include "main.h"
    ( U& B4 I3 W6 ~2 R3 ?1 p2 a0 \, g
  5. #include "stm32f4xx_hal.h"
    $ c( X8 ]! _( I. `' T2 i3 G
  6. #include "i2c.h"8 ]9 f, A: M; K  A6 B
  7. #include "usart.h", J% Q- e2 e* W7 ^
  8. #include "gpio.h"
    % _3 |$ |# R/ _( c6 S/ E
  9. 8 ]# |" A$ {# j1 o
  10. #define ReadAddr   0xA1
    , @  p8 ^# `% T) c8 r2 L
  11. #define WriteAddr  0xA05 N. O, P* f! n  ]
  12. 5 T; d3 e$ m9 r) j- ^0 I# ?
  13. uint8_t Wbuf[20] = "EEPROM TEST OK!";. {" b! j# M0 x! v' A
  14. uint8_t Rbuf[20] = {0};
    # u# l. p7 x) }9 \$ a: V
  15.   `( M- q! Y& Y4 c" z% ]5 |1 r( X
  16. /*********  24C65写数据函数*****************************/
    ! R6 _! @8 i( G% v9 L+ r: c! }& `

  17. : E! g- o6 j! z# ^9 ~% |
  18. void  Eeprom_Write(uint16_t MemAddr, uint8_t *Wbuf, uint16_t len ){: {" L: j. f9 ~5 M0 S* J2 {
  19.         while(len--){" N' B) _# {: y& v
  20.         //I2C_MEMADD_SIZE_16BIT表示存储单元大小6 e7 A( l8 |0 w5 p
  21.         //默认为两个参数,分别是I2C_MEMADD_SIZE_16BIT和I2C_MEMADD_SIZE_8BIT  o% [( B  [8 C# T2 m
  22.         //由于24C65的存储地址是16位的. C. w' T, K3 |  C6 y, s, i9 t" M1 f
  23.         //所以我们选择I2C_MEMADD_SIZE_16BIT% A' J) _3 \2 n8 z* V; b# \: D
  24.         //1表示一次写一个字节
    / o4 O/ m1 ?" W' t* w" c: ~
  25.                 while(HAL_I2C_Mem_Write(&hi2c1, WriteAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Wbuf, 1, 100) != HAL_OK){};
    % y; f; o5 s, s. P% I- Q
  26. MemAddr++;- O  c$ Q* D/ O
  27. Wbuf++;
    3 S1 J: _( ]2 b5 E$ [2 c1 `, f1 a
  28. }
    - _* X- e0 y- T, h
  29. }# `$ H) P, W3 j& l2 i. J# t3 x
  30. % B. L; x) ?- m  h7 P$ g

  31. 8 I. o3 H& r) m
  32. /*********  24C65读数据函数*****************************/2 t7 ]1 w- m2 K- n1 I

  33. - `3 l! f: c0 R
  34. void  Eeprom_Read(uint16_t MemAddr, uint8_t *Rbuf, uint16_t len ){
    6 S8 W* Z" A2 Z* r4 v% |* a
  35.         //可以连续读,所以无需循环
    1 ?& U! p5 y( o& Y
  36.         while(HAL_I2C_Mem_Read(&hi2c1, ReadAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Rbuf, len, 100) != HAL_OK );9 V8 j2 y8 E6 ]$ Y9 g1 y9 w
  37. }) p1 |$ m' m. `. c/ H
  38. int mian(){1 V4 P' w4 I/ v9 f, B- i
  39.       MX_GPIO_Init();
    # t- L; X; E! _" X: I2 N, {, U
  40.       MX_I2C1_Init();1 G% t6 [2 q5 U! l' F* [9 V/ @
  41.       MX_USART1_UART_Init();
    # q* J* R6 y% G3 Y  s! A8 ~) _$ t
  42.       printf("this is i2c eeprom test\n");
    & m- b. E8 ^' {8 r2 n% D3 K' @
  43. 1 N$ N; I, c4 g. Z2 h: _3 n; ^
  44.       Eeprom_Write(0, Wbuf, sizeof(Wbuf) );! |$ g/ R* z; I( t- o- f$ P8 D
  45.       HAL_Delay(500);, C! E. `5 O( C  s3 b
  46.       Eeprom_Read(0 , Rbuf, sizeof(Rbuf));
    & C4 @0 @% G' Z8 g) K; h
  47.       printf("READ:  %s\n", Rbuf);0 G1 M' `  c! z0 x
  48. $ P6 E2 f# b  l) b% s  h
  49.       while(){
    " `+ Q! k( a' O1 t  h# ~& k; n
  50. 8 b: [, \# v; A
  51.       }  e* W# _6 H. v' y
  52. }
复制代码

4 `: X* X' S# a
  • STM32 SPI总线通信专题讲解

    0 Y4 H2 H* \8 w
SPI接口是Motorola 首先提出的全双工三线同步串行外围接口,采用主从模式(Master Slave)架构;支持多slave模式应用,一般仅支持单Master。时钟由Master控制,在时钟移位脉冲下,数据按位传输,是高位在前还是低位在前是可以配置的,配置时根据从设备的通信进行相应配置,一般是高位在前,低位在后(MSB first)。SPI接口有2根单向数据线,为全双工通信,目前应用中的数据速率可达几Mbps的水平。
SPI总线被广泛地使用在FLASH、ADC、LCD等设备与MCU间,要求通讯速率较高的场合。
- y, J7 }2 T$ t
微信图片_20230412162643.png
( H9 [) \' W; e7 ]9 M4 A
SPI接口共有4根信号线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。

! ?+ Y, J. k( G/ X* T( a4 A" _
微信图片_20230412162658.png
$ p9 l5 x) L0 V1 ^0 @. v
(1)MOSI:主器件数据输出,从器件数据输入,连接从机的MOSI,与串口不同,串口需要反着连接(Rx-----Tx)
(2)MISO:主器件数据输入,从器件数据输出,连接从机的MISO
(3)SCLK :时钟信号,由主器件产生
(4)/SS:从器件使能信号,由主器件控制(片选),一般情况下为地电平选中设备,高电平释放设备。
0 b% S9 r# d& B0 y- B9 \3 d
  • SPI总线协议

    5 R; C; t) D# p( O& N/ B& e% ^
1 r, }7 T2 M  F2 H# D
1.数据交换逻辑:主机和从机都包含一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节发起一次传输。寄存器通过MOSI信号线将字节传送给从机,从机也将自己的移位寄存器中的内容通过MISO信号线返回给主机。这样两个移位寄存器中的内容就被交换了。从机的写操作和读操作时同步完成的,因此SPI成为一个很有效的协议。
& V3 E, X" t$ t) G( p. {: K4 d
如果主机只想写不想读,只需把数据放在数据寄存器,SPI控制器会自动传给外设,同时忽略掉外设传过来的数据即可;如果主机只想读不想写,主机写给外设一个空字符或者随便写一个数据,外设就会把数据传过来,不管是只读还是只写,主机与外设的读和写都h会发生且同时进行。
  Y2 u8 k& ]; ^# O' \4 n" C5 c
微信图片_20230412162734.png
, x# \+ T9 v' W
2.起始信号:  NSS信号线由高变低,是SPI通讯的起始信号。
, `  |) f' F- ]+ I
3.结束信号:NSS信号由低变高,是SPI通讯的停止信号。

! }- {" W2 z1 P+ w9 w
4.数据传输:SPI使用MOSI及MISO信号线来传输数据,使用SCK信号线进行数据同步。MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,按位传输,且数据输入输出是同时进行的。SPI每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制,要么是8位,要么是16位,可以配置。

$ a4 D8 w" j: h2 i1 J
微信图片_20230412162755.png

7 r! a" y  f8 I3 }$ Z, n  T* E5 d
  • SPI的4种通信模式
    . S. v+ v  f) d% K0 T& o7 ~

/ ?3 e$ n# \  P4 a0 j4 r; W7 u
在SPI操作中,最重要的两项设置就是时钟极性(CPOL)和时钟相位(CPHA)这两项即是主从设备间数据采样的约定方式。由CPOL及CPHA的不同状态,SPI分成了四种模式,主机与从机需要工作在相同的模式下才可以正常通讯,因此通常主机要按照从机支持的模式去设置。同样在配置时一定要弄明白从机支持什么通信模式进行相应的配置。
! q1 U$ t5 V' S) I5 M) \: n
13.png

7 q$ r* Q3 T$ A, Z* X
1.时钟极性CPOL : 设置时钟空闲时的电平:
a.当CPOL= 0 ,SCK引脚在空闲状态保持低电平;
b.当CPOL= 1 ,SCK引脚在空闲状态保持高电平。

: ^+ n- |2 z4 E' Z7 [- [$ @! o5 b
2.时钟相位CPHA :设置数据采样时的时钟沿:
a.当 CPHA=0时,MOSI或 MISO 数据线上的信号将会在 SCK时钟线的奇数边沿被采样
b.当 CPHA=1时, MOSI或 MISO 数据线上的信号将会在 SCK时钟线的偶数边沿被采样

" Z" V( w0 t+ D( ~- r: W4 w
微信图片_20230412162837.png

( X( I: v& G  H2 J& ]$ {
11.png
  • STM32F4-SPI控制器特性

    3 a# {4 t9 U6 u0 m6 S7 G7 P
12.png

; r5 ?7 B) P7 p2 D. c
1.通讯引脚:
STM32F4芯片最多支持6个SPI外设控制器,它们的SPI通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚,以《STM32F4xx规格书》为准。f407只有SPI1、SPI2、SPI3。

8 r+ {$ }, a/ ^9 t
微信图片_20230412163014.png

# g, n7 F( D) a* ?1 u
其中SPI1、SPI4、SPI5、SPI6是APB2上的设备,最高通信速率达42Mbtis/s,SPI2、SPI3是APB1上的设备,最高通信速率为21Mbits/s。其它功能上没有差异。

9 S3 T8 O3 \6 s" B! j. D; w1 V
2.时钟控制逻辑:
SCK线的时钟信号,由波特率发生器根据“控制寄存器CR1”中的BR[0:2]位控制,该位是对f pclk 时钟的分频因子,对f pclk 的分频结果就是SCK引脚的输出时钟频率。

  U0 x) U. V3 C% V  I; K
微信图片_20230412161739.png
* J/ F, ]& b  {9 s1 `( Z- V4 Y
其中的fpclk 频率是指SPI所在的APB总线频率,APB1为fpclk1 ,APB2为fpckl2

( N/ p6 E, A* z
3.数据控制逻辑:
STM32F4的MOSI及MISO都连接到数据移位寄存器上,数据移位寄存器的数据来源来源于接收缓冲区及发送缓冲区。

' R! D) x/ i6 O/ v1 A1 C! Q
a.通过写SPI的“数据寄存器DR”把数据填充到发送缓冲区中。
- }, {0 l0 R; C: m
b.通过读“数据寄存器DR”,可以获取接收缓冲区中的内容。
" G! B; i9 g  J& X, G5 o
c.其中数据帧长度可以通过“控制寄存器CR1”的“DFF位”配置成8位及16位模式;配置“LSBFIRST位”可选择MSB先行(高位在前)还是LSB先行(低位在前)。

! d9 D: e7 B; y/ K4 b3 p
4.整体控制逻辑:
a.整体控制逻辑负责协调整个SPI外设,控制逻辑的工作模式根据“控制寄存器(CR1/CR2)”的参数而改变,基本的控制参数包括前面提到的SPI模式、波特率、LSB先行、主从模式、单双向模式(同时发送和接收、只发送关掉接收、只接收关掉发送)等等。
/ O% a/ o1 z+ K1 Q# o2 y
b.在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,只要读取状态寄存器相关的寄存器位,就可以了解SPI的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生SPI中断信号、DMA请求及控制NSS信号线。

- P( G0 O8 T6 C& ^. s+ B+ i* a
c.实际应用中,一般不使用STM32 SPI外设的标准NSS信号线,而是更简单地使用普通的GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号。
  • 串行FLASH_W25X16简介

    ) ^3 l! x" q) z, o9 q

0 ]( h: _+ X# T
FLSAH 存储器又称闪存,它与EEPROM都是掉电后数据不丢失的存储器,但FLASH存储器容量普遍大于 EEPROM,现在基本取代了它的地位。我们生活中常用的 U盘、SD卡、SSD 固态硬盘以及我们 STM32 芯片内部用于存储程序的设备,都是 FLASH 类型的存储器。在存储控制上,最主要的区别是FLASH 芯片只能一大片一大片地擦写,而EEPROM可以单个字节擦写。
8 e. T2 V" w/ L& X$ k+ P
W25X16有8192个可编程页,每页256字节。用“页编程指令”每次就可以编程256个字节。用扇区擦除指令每次可以擦除16页,即一个扇区包含16页,用块擦除指令每次可以擦除256页,用整片擦除指令即可以擦除整个芯片。W25X16有512个可擦除扇区或32个可擦除块。
& g6 ?+ a, R% e$ C5 D' b; [8 y
微信图片_20230412161735.png
# g/ C1 i! J3 E  l3 N9 n
1.W25X16的硬件连线如下:

8 h. A' A# {8 h; B3 |
微信图片_20230412161643.png
: ^6 [5 M! z6 i, o$ ?1 `+ E
CS:    片选引脚,低电平有效,连接到STM32-PH2管脚
SO:   连接到STM32-PB4管脚(MISO)
SI:    连接到STM32-PB5管脚(MOSI)
CLK:  连接到STM32-PA5管脚(CLK)
WP:   写保护管脚,低电平有效,有效时禁止写入数据。接电源未使用
HOLD: HOLD 引脚可用于暂停通讯,该引脚为低电平时,通讯暂停,未使用

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

( a, a) X# N; R& ~5 @1 L  I
微信图片_20230412161719.png

' s6 `9 V4 q$ {! b! A
a.读制造商/设备ID(90):该指令通常在调试程序的时候用到,判断SPI通信是否正常。该指令通过主器件拉低/CS片选使能器件开始传输,首先通过DI线传输“90H”指令,接着传输000000H的24位地址(A23-A0),之后从器件会通过DO线返回制造商ID(EFH)和设备ID。(注:SPI为数据交换通信,主器件在发送“90H”指令时也会接收到一个字节FFH,但此数据为无效数据)
. l; ]  c  x+ ~2 Q  q  S; ]
微信图片_20230412161715.png
) E+ T4 o! _' ]9 I
b.写使能命令(06H):在向 FLASH 芯片存储矩阵写入数据前,首先要使能写操作,通过“Write Enable”命令即可写使能。

) ?# E& b% ~: A5 C- j9 ]# I
微信图片_20230412161711.png
6 A+ P* e0 a( L' {  F6 O
c.扇区擦除(20H):由于 FLASH 存储器的特性决定了它只能把原来为“1”的数据位改写成“0”,而原来为“0”的数据位不能直接改写为“1”。所以这里涉及到数据“擦除”的概念。

! y* b7 |4 g5 T4 b$ J
在写入前,必须要对目标存储矩阵进行擦除操作,把矩阵中的数据位擦除为“1”,在数据写入的时候,如果要存储数据“1”,那就不修改存储矩阵 ,在要存储数据“0”时,才更改该位。

0 `7 r2 e% {# c+ X3 ]
微信图片_20230412161707.png
微信图片_20230412161704.png
) v: S; D) H1 ?) N+ c4 O
d.读状态寄存器(05H):FLASH 芯片向内部存储矩阵写入数据需要消耗一定的时间,并不是在总线通讯结束的一瞬间完成的,所以在写操作后需要确认FLASH芯片“空闲”。我们只需要读取FLASH芯片内部的状态寄存器SRP的S0即可(当这个位为“1”时,表明 FLASH芯片处于忙碌状态,它可能正在对内部的存储矩阵进行“擦除”或“数据写入”的操作)

# c: Q( V. r3 S1 q* j6 x
微信图片_20230412161658.png
. j+ Z' u/ K7 K# j0 _) Q; [
微信图片_20230412161654.png
" x! _( l, ~5 o
e.读数据(03H):读数据指令可从存储器依次一个或多个数据字节,该指令通过主器件拉低/CS电平使能设备开始传输,然后传输“03H”指令,接着通过DI管脚传输24位芯片存储地址,从器件接到地址后,寻址存储器中的数据通过DO引脚输出。每传输一个字节地址自动递增,所以只要时钟继续传输,可以不断读取存储器中的数据。
- y) o9 }. ^. F5 D5 K; z9 F2 i
微信图片_20230412161650.png
; F6 b" x0 F3 B) E& p; L' e
f.写数据——页编程(02H):页编程指令可以在已擦除的存储单元中写入256个字节。该指令先拉低/CS引脚电平,接着传输“02H”指令和24位地址。后面接着传输至少一个数据字节,最多256字节。
# g0 `' h" a; z% z; ?- n- O
注:当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。
8 T. N' K3 z+ v1 F8 d3 s- g
微信图片_20230412161646.png

* [1 w# q5 B! H, q) c  E
  • STM32 SPI_FLASH基本配置和操作

    ) a( X8 i! a( w

( _( o7 e; w! W
根据如下的硬件连线图进行配置
" y9 E9 S8 J. ^
微信图片_20230412161729.png

* b( Y. n' i5 l2 G  \
步骤:
1.使能时钟RCC
- L. q" y- ]6 K6 ^( u/ `" X
2.使能SPI1,配置相应管脚
5 a& n: h9 F7 O% d1 ^" @
微信图片_20230412161640.png

( z# o6 E* L9 R
微信图片_20230412161637.png

* v9 r$ L5 t7 h/ I: S, m8 u
3.配置SPI协议
9 K+ Y% Q2 F3 ?% r- B, d2 j; Z
微信图片_20230412161632.png

: Y0 @$ d: e$ L) @$ D9 D1 L
1 [& G( r  k$ {! e# R1 m
4.编码
  1. //main.c3 a0 z4 L- }  B# }: T6 N; }% Z' w
  2. 0 R3 y. j1 G0 o9 o4 F! M
  3. #include "w25x16.h"
    ; |8 A+ l: k4 w+ X' d$ m, f5 b0 X. A

  4. 4 w- s6 u- F" n) F) p
  5. uint8_t RD_Buffer[5000] = {0};2 Q6 n+ i. G( s- Y9 }" w- `
  6. uint8_t WR_Buffer[5000] = "SPI FLASH WRITE TEST\n";
    % V* t2 U7 C# o/ A/ I8 S9 O
  7. $ P% {* K9 F8 Q. t
  8. int main(){5 L* @* Y% W: e  n% K) H
  9.     uint16_t FLASH_ID = 0;
    , V% J3 n8 m) n* u# M; @3 V  Z4 @
  10.     uint32_t i;, M  N' v2 y' ?* T- F1 X# R
  11.     7 u! r( I! P& j2 H8 Z
  12.     MX_GPIO_Init();
    ' d+ x4 O) _2 J: _9 M8 q- m
  13.     MX_SPI1_Init();, k2 `# q; D4 Z' T7 F4 N
  14.     FLASH_ID = sFLASH_ReadID();
    $ m. H- m, x1 t3 f1 J- ~
  15.    
    0 Z# c( ~( K/ f8 j8 A
  16.     /******测试擦除******/9 c) S# ^. Z( I2 J- d5 T
  17.     sFLASH_EraseSector(4096*0);
    5 W9 R- V2 Q0 y+ I% M5 D
  18.     //sFLASH_EraseSector(4096*1);
    ( G; f" s) u" ^$ S  f$ S( E
  19.     sFLASH_ReadBuffer(RD_Buffer,0,4096);
    9 K2 u/ Y/ }, L
  20.     printf("读数据开始\n");8 v1 X( K# B- @7 A& K  U
  21.     for(i=0; i<4096; i++)
    3 A2 s/ V$ v. v' B; ^0 t. f
  22.     {
    % E' X+ H0 p( q- f
  23.     printf("%x ",RD_Buffer[i]);( }: J* W- H$ K* c. e& J! A
  24.     }4 W. T, B+ w: r; F. p
  25.     printf("读数据结束\n");
    6 I3 c0 L  l, T6 |) V* Z; R8 c/ U+ ~3 J* \8 F

  26. - G# i" w  y2 E9 ~) Y7 x* u- s
  27. : w' }, R) ^  N. B3 Y3 y+ r! ?
  28.     /******测试写操作1*****/+ g9 Z! p0 e3 |
  29.     //写之前都需要擦除扇区7 U/ ^' |# a( G; P) x4 }. w
  30.     sFLASH_EraseSector(4096*0);: t) A& y5 P$ J- `+ E
  31.     sFLASH_WritePage(WR_Buffer,0, 20);
    1 I6 P1 r" g( C5 Y# s
  32.     sFLASH_ReadBuffer(RD_Buffer,0,20);
    * ?1 \2 R9 L7 F2 U3 g
  33.     printf("READ DATA: %s\n",RD_Buffer);& Y0 `5 i5 K- F2 U, g
  34.    
    6 F, {( l, f0 a3 r  H
  35.     /******测试写操作2*****/
    / q7 u. L. W, N( x7 s7 O: l$ m
  36.     //写之前都需要擦除扇区
    0 f& d0 a8 y5 m$ B% F
  37.     sFLASH_EraseSector(4096*0);/ x# y& Z) F( e9 {( e
  38.     sFLASH_EraseSector(4096*1);                           
    ; }8 J# g$ ^" v$ l7 [* a: ?
  39.     for(i=0; i<4096; i++)
    , n0 \1 M3 h6 k! k
  40.     {
    5 C* U, L8 ^: `5 e- @$ F! u
  41.     WR_Buffer[i] = 0x55;, z2 g+ U6 ?; l5 g; [! b
  42.     }( |) U6 L. r3 o* D" A1 k; B9 j
  43.     sFLASH_WriteBuffer(WR_Buffer,4090, 1000);
    & A9 U% |- Y  m# F) ?+ D' a
  44.     sFLASH_ReadBuffer(RD_Buffer,4090,1000);
    $ K4 K) H! R, `& @. B# A  ^
  45.     for(i=0; i<1000; i++)
    4 c7 G7 y0 r. V9 G
  46.     {
    4 a$ i' t, ~, w9 @7 N: a
  47.     printf("%x ",RD_Buffer[i]);
    3 ^2 `) q( b+ v9 s
  48.     }: y" T) E/ }$ f7 ~( R
  49.     /*****************/1 F; M5 f- V( W6 P
  50.     while(){}
    - m3 w% i$ ?4 J6 k  c' f& }9 X
  51. }
复制代码
  1. //w25x16.h% Y# k2 T  u$ \6 t
  2. 0 A6 m# S8 q5 x" r. p
  3. #ifndef __W25X16_H1 J+ X3 b) G+ @1 Q2 e
  4. #define __W25X16_H
    2 g( W) M$ o$ Q) j
  5. ! h1 Q8 u; q0 j6 ~1 b9 T
  6. #include "stm32f4xx_hal.h"; \2 J0 D) L9 F8 p# P; ]
  7. ( T3 h3 e, L8 u. E2 E4 v+ ~
  8. //使用宏定义芯片指令
    6 m0 C( g  ?9 |& F$ X. m: N! |
  9. #define W25X_ManufactDeviceID  0x90  /* Read identification */0 J( D! B9 G* f
  10. #define sFLASH_CMD_WREN        0x06/* Write enable instruction */! [! I& O* }$ ^  T% b: V$ O
  11. #define sFLASH_CMD_RDSR        0x05/* Read Status Register instruction  */' R9 l$ z3 j" Z! L
  12. #define sFLASH_CMD_SE          0x20/* Sector Erase instruction */
    ' g4 P2 l$ f9 X  ~
  13. #define sFLASH_CMD_WRITE       0x02  /* Write to Memory instruction */9 K9 ?$ q; m8 U/ K
  14. #define sFLASH_CMD_READ        0x03/* Read from Memory instruction */
    + f5 h, d( z  q' U, |2 p) Q0 x6 y! L: M
  15. #define sFLASH_DUMMY_BYTE      0x00 //空字节,用于只读传回来的数据; i" G. B+ w: Y* Y& z1 k' F
  16. #define sFLASH_BUSY_FLAG       0x01- [( N' ~) g# Q  a  s5 \# C
  17. #define sFLASH_SPI_PAGESIZE    0x100
    0 l- x, J" G) I" I

  18. / |) k# g) `7 z
  19. /* 选中芯片,拉低信号 */
    # W- E1 F1 ^2 d
  20. #define sFLASH_CS_LOW()       HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_RESET)! p/ w4 r1 U! J& g
  21. /* 释放芯片,拉高信号 */
    # M/ ?& T( C" n  ?+ |5 W! A. _* Y
  22. #define sFLASH_CS_HIGH()      HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_SET)
    5 `9 ^5 A  q) m. n6 Q' U
  23. / i# E0 i9 Q; E" _! o" P1 l1 D; A2 {
  24. //定义函数! b. d1 k4 n8 \. u
  25. uint8_t sFLASH_SendByte(uint8_t byte);* ^( Y& C% l4 q' B4 _- j
  26. uint16_t sFLASH_ReadID(void);0 W1 p% S$ x8 j# w! l( A1 k. X' ]
  27. void sFLASH_EraseSector(uint32_t SectorAddr);
    " b9 l, _7 U, N$ }
  28. void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);
    6 l( K, [7 A* a. {0 q
  29. void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);" S& L$ l0 ]1 q5 S/ g
  30. void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead);( M( M% n% q- {# ^) M* e
  31. #endif
复制代码

  1. ) s# U4 ~: P7 [6 I$ X
  2. //w25x16.c! ^) e$ j! C- X6 f8 B4 K
  3. ) P8 v1 S6 X) b# d
  4. #include "w25x16.h"
    6 w% x# t6 @9 O

  5. 7 Q) q) t% \" W/ m
  6. extern SPI_HandleTypeDef hspi1;7 l( g% Y# R8 w- [  I. K
  7. - [; D- R+ X% w8 _8 ^0 F4 ?) U( m6 j
  8. /*读写一个字节函数,因为SPI读和写同时完成*/
    - f: m4 m- S* N# m% p
  9. /*发送数据一定会接收到一个数据*/
    # h5 K8 I: O6 d. E! |% g, n/ a% w9 V; ?
  10. uint8_t sFLASH_SendByte(uint8_t byte)! d. V( }8 p$ I9 j
  11. {# {; @2 B/ L+ i$ ?
  12.     uint8_t TX_DATA = byte;
    3 n/ j4 {& m1 }! o$ B
  13.     uint8_t RX_DATA = 0;
    - z; Y; U" ]% N4 T- q" e
  14.     HAL_SPI_TransmitReceive(&hspi1, &TX_DATA ,&RX_DATA , 1, 1000);
    , e) B2 F5 [! f! [5 r6 w
  15.     return RX_DATA;
    7 A7 m9 V2 w9 ?) r+ w0 J# {6 s0 d
  16. }0 D3 b+ i$ Q2 L6 s# @

  17. / f! f) {' ?8 |2 `# f4 O
  18. /*等待擦除或者写数据完成*/
    # j& X! ?4 s6 b( r( X
  19. void sFLASH_WaitForEnd(void)
    + {$ l; J* D+ |. f) _  I' ~  e
  20. {
    - x# `5 @: [0 j
  21.     uint8_t sr_value = 0;
    ) n; {2 v% z1 @6 Y/ e! Z
  22.     sFLASH_CS_LOW();
    * G, D- Y  K/ {
  23.     sFLASH_SendByte(sFLASH_CMD_RDSR);
    ' K, g- k+ a  R1 @+ n" t! [
  24.             //读S0的值,为1表示忙碌,为0表示停止            B% }$ ?; L: s7 d5 P
  25.     do{
    ) U0 O& K, T* U$ H2 C
  26.                 //发一个空字节,得到S0的值             2 J4 @( X3 A5 @; X
  27.                 sr_value = sFLASH_SendByte(sFLASH_DUMMY_BYTE);6 }$ n% j2 K, ]  V& T( P
  28.     }while( sr_value & sFLASH_BUSY_FLAG);' h3 f$ O. u5 w$ h

  29. " M  Y/ P! x6 ?  X6 \
  30.     sFLASH_CS_HIGH();2 Z5 q  C: @4 D1 }8 w1 N
  31. }6 x  Q+ S1 F. e  U9 d0 O& ]

  32. 9 z  {+ n7 w7 _- f6 W2 e0 k
  33. void sFLASH_WriteEnable(void)% u' z. b0 }1 l* ?  Y
  34. {
    ' e' T% B0 p' _  d' w
  35.     sFLASH_CS_LOW();5 }/ q; ^7 M7 n/ X3 K! G
  36.     - h( V, Y  H! G" S4 X
  37.     sFLASH_SendByte(sFLASH_CMD_WREN);2 ~: V) Q, U2 ?; j9 B* ~  o: D2 h
  38.     & t6 H+ Z2 \- G. g& A) k  E1 h8 {
  39.     sFLASH_CS_HIGH();2 D* j* A" x" J, `$ }
  40. }8 H& m, D0 l4 `0 J; ^. m

  41. 8 X, S: z8 Y. q- L5 ]( m' I

  42. & x# ?+ q2 R) O! a  s" {: b
  43. /*读设备ID*/ ( d8 D$ B2 E/ x
  44. uint16_t sFLASH_ReadID(void)
    1 f8 Y/ \; b6 }# J: W2 c1 e+ O
  45. {
    5 u4 x. a3 u3 K; V
  46.     uint16_t FLASH_ID;
    8 ^% L& n1 r3 |: [& P6 ?
  47.     uint8_t temp0,temp1;
    * b2 V3 V. E+ K* {2 A7 Z
  48.    
    , C  A( e0 A' V! ]& j
  49.     : E8 j5 y+ Z/ M+ w
  50.     sFLASH_CS_LOW();
    9 z% p; U4 M  X5 u. e
  51.    
    0 h( T( m6 l# X. f' c4 S
  52.     sFLASH_SendByte(W25X_ManufactDeviceID);$ f, z9 y/ ^) ?; D
  53.     //读设备指令后要发24位地址,所以要发三次
    - ~' H$ o( n) j2 l5 |0 d+ Y0 X
  54.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    3 i: c6 {0 ~5 K+ @  i% @* G
  55.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    ) j5 r" o4 H  _+ K; S
  56.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);3 u/ e! `  m! X3 z( ]7 R" Z
  57.     //制造商ID
    ( _; @9 T) x. Z  |) ]7 C9 k
  58.     temp0 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    ' O! e4 \! X) j; A
  59.     //设备商ID
    $ l6 C, r* W7 T7 d, N, a3 R
  60.             temp1 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    3 f6 `" J- q6 s5 o+ w$ z6 `$ B
  61.     9 T1 y/ l2 C) X! ^, w
  62.     sFLASH_CS_HIGH();* Z% }$ R& X8 s5 a2 H# s; M0 L
  63.    
    9 H+ p1 P% k. ^0 H) x
  64.     FLASH_ID = (temp0 << 8) | temp1;- L0 z  k- f8 D1 j- L, f
  65.    
    - d2 g# ?/ G- I/ a
  66.     return FLASH_ID;! F; O* s- a( j; A3 k; E  n
  67. }- u$ k& T6 Y$ ]) K' u

  68. & d7 O& O/ P4 E, e* R5 ~
  69. //擦除扇区,擦除为1,因为只能由1变为0 ,不能0变1
    ) c' \5 S' [9 S. O
  70. void sFLASH_EraseSector(uint32_t SectorAddr)
    5 @# H4 e) n1 q
  71. {9 w% c% d* f( {# r4 ]( R. O$ Y3 x
  72.     //SectorAddr表示擦除第几个扇区0 p# I# M. V* q, D9 X' ^' q
  73.     sFLASH_WriteEnable();  //开启写使能
    ( c* g4 T9 d# L! J4 o" C6 O, ^
  74.     sFLASH_CS_LOW();//拉低,片选
    5 x+ F4 H' Z4 Y) z! R: s7 w: Z
  75.     //擦除命令% O# Q2 u' {" v6 K: ~1 e- p" ~( R' M
  76.     sFLASH_SendByte(sFLASH_CMD_SE);
    # Q3 @+ f- J7 B) V/ o2 t7 }% q
  77.             //传24位地址; J4 Z% q2 S& q: Q+ a( l
  78.             //传送高8位,将中8位和低8位一共16位移出去,得到高8位                  
    ! `) a. |1 X5 o) n, Z% ?
  79.     sFLASH_SendByte( (SectorAddr>>16) & 0xff);   # Z+ r# M: m# [! f" M
  80.     sFLASH_SendByte( (SectorAddr>>8) & 0xff);    //传送中8位
    ! ]0 O' E9 z* f: i5 s% q
  81.     sFLASH_SendByte( (SectorAddr>>0) & 0xff);    //传送低8位3 f" d6 s; L$ l5 U
  82.      / w$ C9 k% _  z( P( y
  83.     sFLASH_CS_HIGH();
    * z4 I1 y5 H) k$ }
  84.     ; E) H. E3 n0 p* Y0 T3 _; d) @, Z
  85.     /*读状态寄存器,等待擦除完成*/
    7 w3 q3 y# L: q0 L' h, D9 Q7 s7 |
  86.     sFLASH_WaitForEnd();& t( o2 [% d8 m
  87. }
    + t! A5 o8 t0 [1 \4 i- J3 A. }

  88. 7 Z7 P. L5 j/ ]
  89. //读数据
    4 A) ^4 P! p! F1 R1 |: V
  90. //读命令和读地址发送后,芯片内部会自动不断递增读数据  l; `# e9 O" d
  91. void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead)5 Q6 k9 y: S2 j
  92. {
    - L$ S7 E6 y( G9 H
  93.     sFLASH_CS_LOW();3 P+ q4 w% [  ~% J( @
  94.     - u% X/ Y: x3 Y' T; l
  95.     sFLASH_SendByte(sFLASH_CMD_READ);( v- u3 a5 g. Z( s! p* }
  96.     sFLASH_SendByte( (ReadAddr>>16) & 0xff);   //传送高8位3 o5 E& H; T9 b4 e+ J
  97.     sFLASH_SendByte( (ReadAddr>>8) & 0xff);    //传送中8位
    ; y8 P) n8 Q2 A. I- E
  98.     sFLASH_SendByte( (ReadAddr>>0) & 0xff);    //传送低8位  M0 }7 b: l) r# B6 [
  99.     ' S2 X% d5 n7 k4 H: O& U* X& Q
  100.     while(NumByteToRead--)6 j' ?) Q+ p$ I. F/ ^# K0 [' }
  101.     {
    ) a0 O! A5 Q: X. n- R
  102.     * pBuffer = sFLASH_SendByte(sFLASH_DUMMY_BYTE);# L  N; a. [$ w7 T$ H, J% N
  103.     pBuffer++;4 S8 L  m7 Q7 |+ ]! e6 P' |3 [
  104.     }
    . [" [5 A7 O4 K5 y( D
  105.    
    3 b5 c) |6 a7 [1 x  p6 {0 T! d
  106.     sFLASH_CS_HIGH();1 l" J; W( k9 H2 _2 R# l0 Q) g
  107. }
    - L, f; V$ L1 t6 ^8 w6 V! P7 w
  108. . Z+ v% q0 w5 L% B2 I. F  q
  109. //写一页最多只能写256个字节,一个扇区16页,一个块16个扇区                               . r" S9 v& B4 x+ y* U
  110. void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
    " \" k4 @, \- w3 H# E4 }
  111. {
    # Y) h! K9 o) r2 t6 M' ^
  112.     if(NumByteToWrite > sFLASH_SPI_PAGESIZE )
    9 F- k% V7 V5 j) r9 g. g
  113.     {! `- M2 }" H0 H5 w
  114.     NumByteToWrite = sFLASH_SPI_PAGESIZE;
    " y4 d/ C% ~) U, u& K
  115.    
    5 v6 t+ @$ |9 }
  116.     printf("写数据量过大,超过一页大小\n");
    % ^! L! a9 [7 L6 K( Z
  117.     }9 u! V1 `* Q3 ?  n7 N8 f3 v) n
  118.    
    ) L# |( q/ l# ]3 u4 \8 T5 v
  119.     sFLASH_WriteEnable();  //开启写使能" N7 z( j; @( X! a6 J. S6 l
  120.    
    ! Q& S( ?  q8 Y/ Y! Z
  121.     sFLASH_CS_LOW();& P5 x& l' S7 o8 _: Y9 f
  122.     6 ?8 G. w) ^# N; H) j/ i2 ]
  123.     sFLASH_SendByte(sFLASH_CMD_WRITE);+ x1 ~3 C6 y! z' y
  124.     sFLASH_SendByte( (WriteAddr>>16) & 0xff);   //传送高8位
    . N0 T8 w7 Z; a: ^0 ?7 g
  125.     sFLASH_SendByte( (WriteAddr>>8) & 0xff);     //传送中8位
      G: k1 i& O/ Q; \$ r- G
  126.     sFLASH_SendByte( (WriteAddr>>0) & 0xff);      //传送低8位
    5 R+ ~; \( E4 o6 A
  127.    
    * i9 i" `: O) }! D: J. p
  128.     while(NumByteToWrite--)$ X& F  l! h7 g0 l
  129.     {9 y, M9 ?& h! A& W4 [
  130.     sFLASH_SendByte(* pBuffer);3 y# r' S: V4 b0 V) R* Y8 ?; _
  131.     pBuffer++;
    6 ~! ~+ R5 T6 ]4 k$ I+ U. G) l
  132.     }
    . @2 o) ], Y! a. b
  133.     . a" ?( z: [( Q! h4 T! F" e) `2 y
  134.     sFLASH_CS_HIGH();
    2 u$ R4 {( V% H6 T; V: V
  135.    
    0 T- m& ?; a5 p* W: c; b3 O
  136.     /*擦除和写数据都涉及到写动作,一定要等待完成*/, b! N  W/ H- ]! D8 b. U
  137.     sFLASH_WaitForEnd();
    ; n! R+ r7 g5 g# B/ o5 N* o
  138. }
    ) x' _$ ~- F2 F* Q) ^) ~% ]
  139. ! l+ {+ t2 j( Y) h1 O5 P
  140. //写任意地址、任意长度9 m: i9 R, a% K  |, Y+ b* t5 V" \( ]
  141. void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
    8 @& n& v; k; G. \* `
  142. {; V( E  J- A! d
  143.     uint16_t NumOfPage, NumOfBytes, count, offset;
    & v' Y) u9 O( v1 v3 v
  144.     //求WriteAddr在某一页的位置        & V8 X+ `( }0 }. Q. ?
  145.     offset = WriteAddr % sFLASH_SPI_PAGESIZE;
    ; w# I7 Y1 z5 x4 y8 e% m5 L
  146.     //求某一页剩余的大小 0 V: @/ z- A5 I# O7 [) I
  147.     count = sFLASH_SPI_PAGESIZE - offset;
    ; A8 P/ k4 p7 J# V; v
  148.    
      j) E0 F& _3 f
  149.     /*处理页不对齐的情况,防止页内覆盖*/
    . @. ]& ?- O; w
  150.     //先把某一页剩下的部分写掉,之后的就能新页的起始处开始写        /*offset有值表示需要页对齐,如果要写的字节数小于某一页剩余的部分,那就无需对齐*/                   5 M) C' _. [: r6 y8 [7 {1 P
  151.     /*这两个条件必须同时满足*/
    " A1 w* N6 ~" L( X; @
  152.     if(offset && (NumByteToWrite > count ))
    & q8 q7 L' i* X; A  |8 j
  153.     {( w/ G; v5 B( `2 T& u5 f( ?
  154.     sFLASH_WritePage(pBuffer,WriteAddr,count);
    - ?% f$ g9 ]. Y+ D& S7 b; C' e
  155.     NumByteToWrite -= count;//去掉已经写了的,从新页开始
    8 _* I4 d3 R* d4 U2 K3 J$ G
  156.     pBuffer += count;  ]. ?* x/ ^! T- o
  157.     WriteAddr += count;# X: Q+ q: s% V! @. @
  158.     }8 e- I' X2 H) t3 J" L
  159.     . f  \4 w; y4 R( K+ `& w" A5 N
  160.     /*最多可分多少页*/1 z1 O, K1 \0 ~8 R. }: G& g
  161.     NumOfPage = NumByteToWrite / sFLASH_SPI_PAGESIZE;7 H& C' }8 `- m, o% S- C3 C
  162.     /*剩余多少字节*/7 q! L" L7 L$ P( q+ V
  163.     NumOfBytes = NumByteToWrite % sFLASH_SPI_PAGESIZE;, Z, X0 i2 P* i; u9 Q3 S
  164.    
    ) W# @; \% @! i2 D; F: R8 J" j
  165.     if(NumOfPage)' X. a: G" G2 c. t
  166.     {$ F' i0 b! s" x, O/ }4 x" v
  167.         while(NumOfPage--)
    1 E! v9 G  p5 N2 F2 H. g  p
  168.         {
      M) q2 T- J7 W* F. `' a/ @, i7 y9 O
  169.              //每一页都发起页编程
    0 e6 I% @& c$ U" M! Y( T6 O
  170.              sFLASH_WritePage(pBuffer,WriteAddr,sFLASH_SPI_PAGESIZE);
    6 Q, I+ W4 Q: x
  171.              pBuffer += sFLASH_SPI_PAGESIZE;- t! h$ p7 N; I
  172.      WriteAddr += sFLASH_SPI_PAGESIZE;4 \6 l% I8 x3 w# w
  173.          }7 \8 E" _4 w" a/ L
  174.     }
    7 S8 W/ Q* v2 @  F) j5 {8 n
  175.    
    / h* {: j! t! K" \4 K
  176.     if(NumOfBytes)
    % V3 a  v& H% |" a/ C  t' b& b, q
  177.     {$ m( A3 L! F# v! F. z
  178.     sFLASH_WritePage(pBuffer,WriteAddr,NumOfBytes);+ B" G7 z& K3 H! i: f) C0 q8 C: m
  179.     }
    1 S) A2 g, x& I
  180. }
复制代码
* G% F! m2 y" J% M% U! c" k
  • 为什么会有两种写操作函数,是因为这里的写操作有两个特点:

    " j/ e& D# A! ?5 K* p% L1 N
    . h. S* h. B1 o
9 v/ o. v( }; y$ C* R+ i  C
1.无法突破页限制,超过一页需要重新发起页编程信号。另外如果要写的数据大于剩余一页剩余的容量,那么超出的数据会写到当前页起始地址出。例如,初始输入的写地址为200,而要写的数据大小为100,那么要写的前56个字节会从地址200开始依次写入,剩下的44个字节会从当前页的0地址开始依次写入,这很有可能覆盖之前的数据。
8 X- I) r+ ?0 k
2.无法突破扇区的限制,当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。
' j6 O3 {1 h# b; |
转载自: 骆驼听海
) Y) H! u: R% s# i8 i5 [如有侵权请联系删除- h: L2 }( [/ V+ I- ~, _) ]% E

' ^' H! k6 c/ L. ~9 `
赞 收藏 评论0 发布时间:2023-4-12 16:18

举报

0个回答

所属标签

相似技术帖

官网相关资源

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