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

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

[复制链接]
攻城狮Melo 发布时间:2023-4-12 16:18
+ m* B: L0 A3 i1 X! M& h
微信图片_20230412161833.jpg
8 B6 Y2 `3 ?4 N6 l5 y" U8 {
本系列为ARM开发的进阶知识,讲解与ARM芯片进行通信的大部分高级外设的原理及使用方法,不局限于ARM芯片内部,实操性较强且晦涩,需要一定的读者具备一定代码阅读和理解能力。
6 ?& f/ o! E+ I( h  w6 P
  • STM32 I2C总线通信专题讲解

    6 h3 e4 j2 R. u0 g. {+ Q6 l7 D

7 W. [. `9 q* @* a  |- A4 q
  • 总线介绍:I2C(Inter-Integrated Circuit)总线(也称IIC或I2C)是由PHILIPS公司开发的两线式串行总线(单双工),用于连接微控制器及其外围设备,在这两根线上可以挂很多设备,同一时刻只能有一个节点处于主机模式,其他节点处于从机模式,总线上数据的传送都由主机发起。I2C总线没有片选信号线,所以需要通过协议来找到对应操作的芯片。是微电子通信控制领域广泛采用的一种总线标准。它是同步通信的一种特殊形式,具有接口线少,控制方式简单,期间封装形式少,通信速率高等优点。0 J# S' \9 U' T* H
    . X1 V( H) s( I1 A
3 l1 G9 }3 |* {6 v) }' |
  • 总线特征:

    8 B: c+ d) O; ?; p6 x1 ]& n
1.两条总线线路:一条串行数据SDA,一条串行时钟线SCL(主从设备使用同一时钟,属于同步通信)来完成数据的传输及外围器件的扩展
) Y2 z  O/ f+ W
2.I2C总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址,通常是7位,有时候是10位

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

( z" @6 E5 F8 e
4.I2C总线上的主设备与从设备之间以字节(8位)为单位进行单双工的数据传输。
) j  U4 K" Q' U+ k) y6 X* u' x
  • 拓扑结构——总线型
    ( O6 F% y) `  A8 z8 P
I2C 总线在物理连接上分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成,SCL由主机发出,SCL越快,通讯速率越快。通信原理是通过对SCL和SDA线高低电平时序的控制来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。

4 L1 w, J& L$ q% @8 y
微信图片_20230412161906.png

) l2 c$ z5 [2 l  l1 c; F9 l$ B
  • I2C总线协议

    2 \2 c  f3 @6 I6 Q4 L7 e
1.I2C协议规定:  总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生。

! g$ q' X) d3 l, R9 y* g: t
2.空闲状态:SCL和SDA都保持着高电平。

- t& n8 k3 D$ V4 ?: p2 h
3.起始信号:  当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件,所有的从设备都能感受到这个跳变,做好准备等待被选择。

$ ]7 R: ^+ V9 c7 `+ T6 X
4.结束信号:当SCL为高而SDA由低到高的跳变,表示产生一个 停止条件

; c0 l- u3 I" W. _
微信图片_20230412161928.png

1 \+ x4 i7 V9 p1 c) R
5.数据传输:数据传输以字节为单位 , 主设备在SCL线上产生每个时钟脉冲的过程中将在SDA线上传输一个数据位,数据在时钟的高电平被采样这时候采集到是1就是1,是0就是0,所以在传输数据时,当时钟处于高电平时一定要保持稳定,时钟处于低电平时可以变换数据。(高电平采样,低电平变换)一个字节按数据位从高位到低位的顺序进行传输。主设备在传输有效数据之前 要先指定从设备的地址,一般为7位,然后再发生数据传输的方向位, 0表示主设备向从设备写数据,1表示主设备向从设备读数据。主从设备以字节为单位(8位)进行数据传输,开始传输数据时把从设备地址加上方向位组成一个8位的字节进行发送并接收一个应答。

0 S8 W6 _, w# P0 V& ?$ M( m/ l
6.应答信号:接收数据的器件在接收到 8bit 数据后,向发送数据的器件发出低电平的应答信号,表示已收到数据。这个信号可以是主控器件发出,也可以是从动器件发出。总之,由接收数据的器件发出。

1 L# e% q% L0 v, Z0 O
微信图片_20230412161941.png
( U) K1 J1 D# G- W9 ]+ Y& [% _
a.主设备向从设备写数据:
/ ^" C/ L4 g4 o% g; V
微信图片_20230412161956.png

$ L: E/ U! V& q' v
b.主设备读从设备的数据:

$ R. V* b/ h+ X, v! K
1.png
; z! \" f$ A8 }) G
c.主设备读从设备的某个寄存器:读设备的寄存器首先应该对该设备发送写命令,很多设备都可以看成是一段内存,所以写命令写给从设备,指明要读取哪个地址(寄存器)的数据,接下来才是真正的读数据。不同的从设备是由区别的,在驱动I2C从设备时应当查明设备的时序图,又怎样的要求,不同的时序对应了不同的命令。
) R" c, |! J* m3 j3 x9 [
2.png
% r8 s/ h* e( Q9 M( b9 s
0 S9 r3 u! f3 [6 {+ t! u, P
  • STM32F4-I2C控制器特性

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

( y# N6 `! I, F8 v5 X! {( ^1 l
控制器功能:配置主从模式(一般都把STM32当作主机使用,作为从机时应当对其赋一个地址),通过配置其内部的寄存器产生一些中断和错误信号,配置通信速率位标准模式、快速模式、超快速模式等

" N: C! S6 J, v0 P6 f. b  E8 p0 |
STM32芯片有3组I2C外设,可以同时进行3组I2C传输。它们的I2C通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚。

9 x" s3 O' N4 T3 ^+ P# ^. R: K
微信图片_20230412162110.png

) Z# k, `1 S- x- J: t& `' Q$ ?% Z
  • EEPROM(AT24CXX)存储芯片介绍
    4 J* E, R8 p( T9 _/ e+ k( x3 \; S/ ~
一个典型的I2C接口的从设备,专门用于存储数据的芯片。EEPROM (Electrically ErasableProgrammable read only memory),带电可擦可编程只读存储器,一种掉电后数据不丢失的存储芯片。EEPROM可以在电脑上或专用设备上擦除已有信息,重新编程。

2 u  P4 a" F  y) y
EEPROM常用来存储一些配置信息,以便系统重新上电的时候加载之,容量不会很高。EEPOM 芯片最常用的通讯方式就是I2C协议。XX表示容量,常用值为01、02、04、16、32、64等,单位Kbit。一般的存储芯片都具有写保护功能,对WP管脚加一个高电平就开启了写保护功能,就无法往芯片内写数据了。在开发中通常将该管脚接地,确保能够写数据
典型24CXX芯片引脚如下:
7 Z# B& {3 w' N* m' G0 R4 B/ @
微信图片_20230412162134.png
) X. n; w5 u/ g1 |$ m4 K9 D4 h
例:24C65的设备地址为7位,高4位恒定为1010,低3位取决于A0-A2的电平状态,般主机在读写24CXX都是把设备地址连同读写位组合成一个字节一起发送。
+ f6 [: n* `' j0 K6 \
3.png
/ ~. B& I- `4 i( a; A, V0 Z
24C65的电气连线如下,根据电气连线可知,A0-A2均接地,因此读地址为1010 0001,即0xA1;写地址为10100000,即0xA0 ,且WP接地,用户随时可向芯片内部写入数据。
2 q5 W0 T% y3 a& z# J
4.png
* A2 L7 q8 e5 U# {
24C65写时序:首先发送一个起始信号,接着发送从设备地址以及方向位,收到应答后,向从设备发送要写的存储区域的首地址,24C65的存储地址是16位,先发送高8位,收到应答后再发送低8位,再次收到应答后开始写数据。64Kbit大小位8K字节,需要13位即可表示,所以高3位固定定为0,如下图。
9 @. i# {6 v4 ]7 I+ ?4 _
这里是BYTE WRITE,一次写一个字节,此芯片还支持PAGE WRITE,一次写一页,也就是8个字节,如果想写更多,可设置一个for循环实现。

/ }1 v" I3 i1 t2 Y# t1 z0 i
5.png

1 }+ r! \1 F0 P+ \- o4 P
24C65 读时序与写时序基本相同,只不过在读之前要发送再发送重复开始位进行读操作。

, I) g: N; d$ o* Z7 ~& t
6.png
2 k  a/ B: ?$ q( e: U; G
  • I2C读写EEPROM实例
    0 O) A# O' C" g9 i% M, t

. B+ q  p+ H% c
由电气原理图可知SCL和SDA分别接入了PB6和PB7管脚,读地址为1010 0001,即0xA1;写地址为10100000,即0xA0
# S. x) O6 g- V5 M" Q
步骤:
1.配置RCC
  p' c( Y  X4 A& b/ g4 W
2.配置PB6和PB7管脚

) ^4 A( }  t" b
7.jpg
1 {* q$ D, S, Y" P9 R9 e1 w- }
3.配置I2C协议参数
2 [- ^4 [7 H, N; E
8.png

$ ^2 M: b) X4 N" R. M8 h
4.编写代码

  1. $ O6 k6 M- f* E( ?" J' I3 Y
  2. //mian.c" ?3 x2 U7 H% N- p' m
  3.   V$ E8 e: [$ ^4 Y* L9 c: V* n
  4. #include "main.h"5 R; l9 D9 V0 a8 g5 l$ ]7 z1 k
  5. #include "stm32f4xx_hal.h"
    ; S0 q$ r$ c$ _& o; X6 [
  6. #include "i2c.h") X- g) Y7 U' A; ~
  7. #include "usart.h"
    - n) j% T( a5 o1 d. e
  8. #include "gpio.h"
    , }. A7 O7 D7 B3 S3 {

  9. 2 e9 K7 f4 M# w( g" j0 p
  10. #define ReadAddr   0xA1
    ' J2 D! r9 J: M& Z6 h! k- n
  11. #define WriteAddr  0xA0
    5 }1 }, z% E5 o, u
  12. & i. W9 q3 m9 v- g9 T9 J
  13. uint8_t Wbuf[20] = "EEPROM TEST OK!";/ M' H6 ]. q1 [' W
  14. uint8_t Rbuf[20] = {0};  a1 x. x# q9 k6 I
  15. 8 v" F3 i; I$ E2 }2 n! p+ E, \) k
  16. /*********  24C65写数据函数*****************************/$ K. c! @( j9 r3 C

  17. $ s! x: U( Z  X; D. X
  18. void  Eeprom_Write(uint16_t MemAddr, uint8_t *Wbuf, uint16_t len ){
    0 N* s: N5 R9 [" a
  19.         while(len--){
    , Z5 b8 I  V2 i' s* K- U
  20.         //I2C_MEMADD_SIZE_16BIT表示存储单元大小1 ^  _  B. }4 `3 ?
  21.         //默认为两个参数,分别是I2C_MEMADD_SIZE_16BIT和I2C_MEMADD_SIZE_8BIT( A% e9 o$ ^( n5 ^- E
  22.         //由于24C65的存储地址是16位的3 ?2 N- ~2 o9 a3 X
  23.         //所以我们选择I2C_MEMADD_SIZE_16BIT; m# S+ X7 {. g& V4 ]) d" |
  24.         //1表示一次写一个字节2 t2 z( F+ n1 l+ ?1 h
  25.                 while(HAL_I2C_Mem_Write(&hi2c1, WriteAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Wbuf, 1, 100) != HAL_OK){};
    / A; Y. r! j/ ~/ y
  26. MemAddr++;
    ( M0 O* _* U% J
  27. Wbuf++;
    , T/ H- F' o, e7 B7 E
  28. }( e* ?5 H8 ]( C; ~5 ?
  29. }
    & c/ q% x, M; w

  30. - u" }/ M3 A2 m3 z% ~  u

  31. 7 Y5 r( n; R) c8 @
  32. /*********  24C65读数据函数*****************************/8 h) y2 j( y/ S+ }( j5 C

  33. 6 O) s. o& A+ i- y3 f( ?( Y
  34. void  Eeprom_Read(uint16_t MemAddr, uint8_t *Rbuf, uint16_t len ){" r! |2 v: x6 p- x# N" Q
  35.         //可以连续读,所以无需循环7 B5 ~7 ~9 Y* R/ O, r, {
  36.         while(HAL_I2C_Mem_Read(&hi2c1, ReadAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Rbuf, len, 100) != HAL_OK );% S4 s# q) {1 J( I, ~
  37. }
    7 B/ N4 r5 V; s2 P
  38. int mian(){
    , g$ i( A* k. o! `6 K6 J% e' @
  39.       MX_GPIO_Init();8 ~4 i$ y7 u; q- g# `6 f7 _3 G
  40.       MX_I2C1_Init();
    * ~" H% D4 V% [
  41.       MX_USART1_UART_Init();
    - r* X. g/ w% ^
  42.       printf("this is i2c eeprom test\n");
    ; x; j9 e1 s; G) s% i7 T' N+ D

  43. ; L6 W. p  r. X" }
  44.       Eeprom_Write(0, Wbuf, sizeof(Wbuf) );- S, j. p; S: h* m
  45.       HAL_Delay(500);9 D: v# f! [& F0 G7 v) g" m. H3 h/ j
  46.       Eeprom_Read(0 , Rbuf, sizeof(Rbuf));: L9 b+ @- ^+ a) |& H
  47.       printf("READ:  %s\n", Rbuf);
    6 C7 P( w$ H0 h5 H& j

  48. + [" D. H, Q; Z4 }0 y
  49.       while(){2 P! F$ {# H4 Q: o* X

  50. 4 ?" w, h' b$ u% c5 D) }" W
  51.       }
    ( }* s7 M6 }/ I. j. q% I7 g0 z
  52. }
复制代码

* B$ h0 o+ z" `' E/ p2 b
  • STM32 SPI总线通信专题讲解
    ) B$ x9 F* @4 M/ W, |3 Y% W
SPI接口是Motorola 首先提出的全双工三线同步串行外围接口,采用主从模式(Master Slave)架构;支持多slave模式应用,一般仅支持单Master。时钟由Master控制,在时钟移位脉冲下,数据按位传输,是高位在前还是低位在前是可以配置的,配置时根据从设备的通信进行相应配置,一般是高位在前,低位在后(MSB first)。SPI接口有2根单向数据线,为全双工通信,目前应用中的数据速率可达几Mbps的水平。
SPI总线被广泛地使用在FLASH、ADC、LCD等设备与MCU间,要求通讯速率较高的场合。
5 L5 c- |9 F: G" w6 @5 d# a0 |
微信图片_20230412162643.png

, k* I& p1 H8 Q& }3 x5 l' E$ z/ B
SPI接口共有4根信号线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。
' K) s+ `* u3 w6 j9 z: z# P$ ~
微信图片_20230412162658.png
1 L; L. s; M& }  ~
(1)MOSI:主器件数据输出,从器件数据输入,连接从机的MOSI,与串口不同,串口需要反着连接(Rx-----Tx)
(2)MISO:主器件数据输入,从器件数据输出,连接从机的MISO
(3)SCLK :时钟信号,由主器件产生
(4)/SS:从器件使能信号,由主器件控制(片选),一般情况下为地电平选中设备,高电平释放设备。
. o, _& u- P- G/ s" x7 _
  • SPI总线协议

    " `) [& j8 P% V, @; [# C9 Q
3 L( r  @1 ^8 e% b  |- U7 o
1.数据交换逻辑:主机和从机都包含一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节发起一次传输。寄存器通过MOSI信号线将字节传送给从机,从机也将自己的移位寄存器中的内容通过MISO信号线返回给主机。这样两个移位寄存器中的内容就被交换了。从机的写操作和读操作时同步完成的,因此SPI成为一个很有效的协议。

4 X$ M" B. l9 @" \  U) B
如果主机只想写不想读,只需把数据放在数据寄存器,SPI控制器会自动传给外设,同时忽略掉外设传过来的数据即可;如果主机只想读不想写,主机写给外设一个空字符或者随便写一个数据,外设就会把数据传过来,不管是只读还是只写,主机与外设的读和写都h会发生且同时进行。

9 u, |+ u# w1 B9 L6 g1 U9 O. c
微信图片_20230412162734.png

& B: r3 ?- B) I9 R2 g6 Q+ O
2.起始信号:  NSS信号线由高变低,是SPI通讯的起始信号。
9 O5 J2 ~( U  \  d6 ~
3.结束信号:NSS信号由低变高,是SPI通讯的停止信号。
+ |) H, d# X" \) D$ O
4.数据传输:SPI使用MOSI及MISO信号线来传输数据,使用SCK信号线进行数据同步。MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,按位传输,且数据输入输出是同时进行的。SPI每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制,要么是8位,要么是16位,可以配置。
4 i" {6 i/ K2 D) _8 O
微信图片_20230412162755.png

& v/ x* ], w  Q6 t) I- m* b
  • SPI的4种通信模式

    + _! t. @; ]* J: d) F% \

5 e# Z1 P+ Q9 i6 D  d
在SPI操作中,最重要的两项设置就是时钟极性(CPOL)和时钟相位(CPHA)这两项即是主从设备间数据采样的约定方式。由CPOL及CPHA的不同状态,SPI分成了四种模式,主机与从机需要工作在相同的模式下才可以正常通讯,因此通常主机要按照从机支持的模式去设置。同样在配置时一定要弄明白从机支持什么通信模式进行相应的配置。
2 v% m2 f& t! @, k9 T
13.png

, N) Q, o9 U+ d! E- l: Z/ }& {
1.时钟极性CPOL : 设置时钟空闲时的电平:
a.当CPOL= 0 ,SCK引脚在空闲状态保持低电平;
b.当CPOL= 1 ,SCK引脚在空闲状态保持高电平。
9 k7 w( u2 L5 T
2.时钟相位CPHA :设置数据采样时的时钟沿:
a.当 CPHA=0时,MOSI或 MISO 数据线上的信号将会在 SCK时钟线的奇数边沿被采样
b.当 CPHA=1时, MOSI或 MISO 数据线上的信号将会在 SCK时钟线的偶数边沿被采样

0 v9 h2 w8 R, y, J
微信图片_20230412162837.png

3 O3 t6 U% G8 o" {
11.png
  • STM32F4-SPI控制器特性

    ; S- E% h& C( `
12.png

, q( \. o& P; U8 x+ H
1.通讯引脚:
STM32F4芯片最多支持6个SPI外设控制器,它们的SPI通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚,以《STM32F4xx规格书》为准。f407只有SPI1、SPI2、SPI3。

4 @& l. {/ ^- g' P
微信图片_20230412163014.png

- n2 W& I  b# o5 {
其中SPI1、SPI4、SPI5、SPI6是APB2上的设备,最高通信速率达42Mbtis/s,SPI2、SPI3是APB1上的设备,最高通信速率为21Mbits/s。其它功能上没有差异。

8 E8 t% E4 E4 a/ j
2.时钟控制逻辑:
SCK线的时钟信号,由波特率发生器根据“控制寄存器CR1”中的BR[0:2]位控制,该位是对f pclk 时钟的分频因子,对f pclk 的分频结果就是SCK引脚的输出时钟频率。

8 k& X# [; ~! u+ Y# q9 c" L$ k
微信图片_20230412161739.png

) O" ]  y, ?& g, q% x
其中的fpclk 频率是指SPI所在的APB总线频率,APB1为fpclk1 ,APB2为fpckl2

. e% L8 I( @& {" E
3.数据控制逻辑:
STM32F4的MOSI及MISO都连接到数据移位寄存器上,数据移位寄存器的数据来源来源于接收缓冲区及发送缓冲区。
- l5 W' ^) T6 T# k1 s  K- i- R8 ~/ A0 K  p
a.通过写SPI的“数据寄存器DR”把数据填充到发送缓冲区中。

. [5 v; C$ T8 l- m- [" j" d
b.通过读“数据寄存器DR”,可以获取接收缓冲区中的内容。

5 j  b1 o( m6 {' g
c.其中数据帧长度可以通过“控制寄存器CR1”的“DFF位”配置成8位及16位模式;配置“LSBFIRST位”可选择MSB先行(高位在前)还是LSB先行(低位在前)。
0 D' D. h+ A0 C- N! M# {, Z$ D
4.整体控制逻辑:
a.整体控制逻辑负责协调整个SPI外设,控制逻辑的工作模式根据“控制寄存器(CR1/CR2)”的参数而改变,基本的控制参数包括前面提到的SPI模式、波特率、LSB先行、主从模式、单双向模式(同时发送和接收、只发送关掉接收、只接收关掉发送)等等。
; B" [/ i% i% @: r# e
b.在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,只要读取状态寄存器相关的寄存器位,就可以了解SPI的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生SPI中断信号、DMA请求及控制NSS信号线。
5 M  E  y6 z: U% s
c.实际应用中,一般不使用STM32 SPI外设的标准NSS信号线,而是更简单地使用普通的GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号。
  • 串行FLASH_W25X16简介

    . A% G, f$ W. g+ \* l2 W

( \# B6 T$ }+ V# Z8 D( H1 a
FLSAH 存储器又称闪存,它与EEPROM都是掉电后数据不丢失的存储器,但FLASH存储器容量普遍大于 EEPROM,现在基本取代了它的地位。我们生活中常用的 U盘、SD卡、SSD 固态硬盘以及我们 STM32 芯片内部用于存储程序的设备,都是 FLASH 类型的存储器。在存储控制上,最主要的区别是FLASH 芯片只能一大片一大片地擦写,而EEPROM可以单个字节擦写。
' |" R. u, u  U% ?! T
W25X16有8192个可编程页,每页256字节。用“页编程指令”每次就可以编程256个字节。用扇区擦除指令每次可以擦除16页,即一个扇区包含16页,用块擦除指令每次可以擦除256页,用整片擦除指令即可以擦除整个芯片。W25X16有512个可擦除扇区或32个可擦除块。

6 |" u$ L2 t! R  J' ?3 J
微信图片_20230412161735.png

' S2 ^9 K4 Y$ m; ]4 o6 ]2 F
1.W25X16的硬件连线如下:
# E5 }2 ]: |  T
微信图片_20230412161643.png

- ~, A: D/ Z, ]  q
CS:    片选引脚,低电平有效,连接到STM32-PH2管脚
SO:   连接到STM32-PB4管脚(MISO)
SI:    连接到STM32-PB5管脚(MOSI)
CLK:  连接到STM32-PA5管脚(CLK)
WP:   写保护管脚,低电平有效,有效时禁止写入数据。接电源未使用
HOLD: HOLD 引脚可用于暂停通讯,该引脚为低电平时,通讯暂停,未使用

/ d; p4 A! c: P0 c" B$ E
2.W25X16控制指令:
我们需要了解如何对FLASH芯片进行读写。FLASH 芯片自定义了很多指令,我们通过控制 STM32利用 SPI总线向 FLASH 芯片发送指令,FLASH芯片收到后就会执行相应的操作。
而这些指令,对主机端(STM32)来说,只是它遵守最基本的 SPI通讯协议发送出的数据,但在设备端(FLASH 芯片)把这些数据解释成不同的意义,所以才成为指令。
( N6 b2 Q# E9 c  Y8 E5 W$ u7 \
微信图片_20230412161719.png
; v+ j! e4 F9 d6 y
a.读制造商/设备ID(90):该指令通常在调试程序的时候用到,判断SPI通信是否正常。该指令通过主器件拉低/CS片选使能器件开始传输,首先通过DI线传输“90H”指令,接着传输000000H的24位地址(A23-A0),之后从器件会通过DO线返回制造商ID(EFH)和设备ID。(注:SPI为数据交换通信,主器件在发送“90H”指令时也会接收到一个字节FFH,但此数据为无效数据)

2 ~& A% C' q! _" @7 a; v7 d. a
微信图片_20230412161715.png
5 K0 C, B3 v5 c- Q& a; D8 `
b.写使能命令(06H):在向 FLASH 芯片存储矩阵写入数据前,首先要使能写操作,通过“Write Enable”命令即可写使能。

1 Y2 l8 D% v' c. ^
微信图片_20230412161711.png
' D2 h1 s) e2 b; T
c.扇区擦除(20H):由于 FLASH 存储器的特性决定了它只能把原来为“1”的数据位改写成“0”,而原来为“0”的数据位不能直接改写为“1”。所以这里涉及到数据“擦除”的概念。

6 H& Z: w6 i- C
在写入前,必须要对目标存储矩阵进行擦除操作,把矩阵中的数据位擦除为“1”,在数据写入的时候,如果要存储数据“1”,那就不修改存储矩阵 ,在要存储数据“0”时,才更改该位。

" k2 e. q5 y. d& P
微信图片_20230412161707.png
微信图片_20230412161704.png
2 F2 a( v+ R3 V
d.读状态寄存器(05H):FLASH 芯片向内部存储矩阵写入数据需要消耗一定的时间,并不是在总线通讯结束的一瞬间完成的,所以在写操作后需要确认FLASH芯片“空闲”。我们只需要读取FLASH芯片内部的状态寄存器SRP的S0即可(当这个位为“1”时,表明 FLASH芯片处于忙碌状态,它可能正在对内部的存储矩阵进行“擦除”或“数据写入”的操作)
6 X, z+ h( q2 C. G5 |8 J+ X  j
微信图片_20230412161658.png
9 B7 Q$ ~7 [) H! Q7 x: a
微信图片_20230412161654.png

8 I, L8 D8 z! o* Y% t, H
e.读数据(03H):读数据指令可从存储器依次一个或多个数据字节,该指令通过主器件拉低/CS电平使能设备开始传输,然后传输“03H”指令,接着通过DI管脚传输24位芯片存储地址,从器件接到地址后,寻址存储器中的数据通过DO引脚输出。每传输一个字节地址自动递增,所以只要时钟继续传输,可以不断读取存储器中的数据。
+ E# c. s2 n5 p% n1 u: `* G/ T
微信图片_20230412161650.png

& G+ [2 h) n7 p" U) T
f.写数据——页编程(02H):页编程指令可以在已擦除的存储单元中写入256个字节。该指令先拉低/CS引脚电平,接着传输“02H”指令和24位地址。后面接着传输至少一个数据字节,最多256字节。
& f# D1 N+ p4 o& L6 g- G) x8 ]
注:当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。
% l# _2 J# U) S  b
微信图片_20230412161646.png

( _1 f) k# M5 U8 O9 `, ~* V
  • STM32 SPI_FLASH基本配置和操作

    1 J& D7 k, j& p
4 |3 l- n5 S; d  C6 e& R
根据如下的硬件连线图进行配置

! @3 }8 G! W5 e8 y2 D1 h. y- C
微信图片_20230412161729.png

9 L( O0 h" C1 W/ O/ {
步骤:
1.使能时钟RCC
/ U- _) W  r8 F9 R7 b! ?
2.使能SPI1,配置相应管脚

. c( F5 `2 b: H' g/ G6 Z
微信图片_20230412161640.png
, M/ S* B5 z2 W0 U1 l) p; M
微信图片_20230412161637.png
* }8 s2 a( d& n) ]% w. s
3.配置SPI协议
5 _7 R3 X4 T7 O! c  ^
微信图片_20230412161632.png
' n: |; g8 V0 R, m# Y# ~

4 s3 g" A4 [7 p5 g1 }. d
4.编码
  1. //main.c1 g* [! y  y. ~: J# t0 S
  2. : i' S; J  F/ m1 G
  3. #include "w25x16.h"3 @, b3 s, |' [6 w9 o, c
  4. 9 h8 C% `! f! @
  5. uint8_t RD_Buffer[5000] = {0};5 }. [2 b# H) g2 I) S: m
  6. uint8_t WR_Buffer[5000] = "SPI FLASH WRITE TEST\n";
    $ t) a7 X& R  {; Z4 @! p( Y; S

  7. " o- v7 a  J8 I9 k1 Q3 _4 [' s
  8. int main(){
    . m; `2 N# b( E) \0 L) R
  9.     uint16_t FLASH_ID = 0;& j. o3 h3 i/ N! D
  10.     uint32_t i;/ S6 y& n8 e( b" \. Y4 F
  11.    
    # X. l6 I8 H: g3 P/ s
  12.     MX_GPIO_Init();5 [1 P8 i2 s9 s. S8 u$ u$ {
  13.     MX_SPI1_Init();' T, l0 Y) i' `8 j
  14.     FLASH_ID = sFLASH_ReadID();
    2 b. C* R; _1 L# P
  15.     : f, p# O% F0 L4 F0 ^* |: n
  16.     /******测试擦除******/
    . F/ Q& v2 r# x+ r9 W* _
  17.     sFLASH_EraseSector(4096*0);
    ) z( W2 e8 f% D! t
  18.     //sFLASH_EraseSector(4096*1);
    . Q9 \, Y9 @; s2 I  d
  19.     sFLASH_ReadBuffer(RD_Buffer,0,4096);$ L" N+ Z1 h) ~
  20.     printf("读数据开始\n");
    ! J, e) D: k  J4 Z
  21.     for(i=0; i<4096; i++)
    % X' B4 y" ?; V4 S
  22.     {
    0 k9 @2 L- E8 L/ J9 F8 ~
  23.     printf("%x ",RD_Buffer[i]);
    + h7 Z1 P% ~9 n) `
  24.     }
    ( P: s# v8 r9 a- E/ ]
  25.     printf("读数据结束\n");
    5 ^8 l; T5 p) ?3 a
  26. ; s7 d6 s8 _* R( j6 L

  27. % x4 D  a$ R) {& ]& t- C
  28.     /******测试写操作1*****/3 |% A- z+ @. u" j8 S* g
  29.     //写之前都需要擦除扇区* i0 M' W! Y9 w
  30.     sFLASH_EraseSector(4096*0);) Z4 G  O$ j$ ^  ?8 P) t
  31.     sFLASH_WritePage(WR_Buffer,0, 20);
    & v! N4 r! U! }- R  o. A
  32.     sFLASH_ReadBuffer(RD_Buffer,0,20);7 _% h+ q  T& c% f+ y
  33.     printf("READ DATA: %s\n",RD_Buffer);
    , L; l3 ~# |* A  e* @
  34.    
    # `+ E8 p" h- i6 U  e7 S5 C9 D1 H
  35.     /******测试写操作2*****/
    & u! x) h8 B: x7 i1 ?
  36.     //写之前都需要擦除扇区0 E9 g* |+ d, {) _' y" K( M' y* b4 j
  37.     sFLASH_EraseSector(4096*0);  ~9 D1 u; X! n$ Z3 F
  38.     sFLASH_EraseSector(4096*1);                            & j6 g9 z0 Y6 S6 V5 E
  39.     for(i=0; i<4096; i++), l9 @# r/ K, Z6 d8 n, Q
  40.     {0 z* i( L$ n) m& z& K6 L
  41.     WR_Buffer[i] = 0x55;( s$ g% b6 W' l1 Z2 F( |. @  R
  42.     }
    / ]: _+ U, W/ N' K2 |+ k0 L5 h$ \
  43.     sFLASH_WriteBuffer(WR_Buffer,4090, 1000);% r$ V' \+ ^4 I) [8 m* D
  44.     sFLASH_ReadBuffer(RD_Buffer,4090,1000);
    1 h. w1 R  S* G/ L* N1 e
  45.     for(i=0; i<1000; i++)
    5 `: p8 [; D0 D- L2 l- l& n
  46.     {
    - X4 m/ Q/ \# O5 k& v
  47.     printf("%x ",RD_Buffer[i]);
    0 p* p# Y2 C5 G7 ~: ^0 L- I
  48.     }
    ; j4 c9 `2 V- G$ n1 P% z! {
  49.     /*****************/
    9 V' E$ W. U. x; i
  50.     while(){}
    ! x. k! F1 }5 y9 h  r& b7 @- M# R
  51. }
复制代码
  1. //w25x16.h
    ( o6 R( ?! P' e: H( D, @

  2. . h6 @% B' H2 w" q7 k
  3. #ifndef __W25X16_H3 d( G7 Z- m: ~& K
  4. #define __W25X16_H% k) B4 m9 a9 _% s* C) M6 ^( {
  5. : l  o# x- ^% q
  6. #include "stm32f4xx_hal.h"2 _9 W8 m0 x. V
  7. - ]3 P# b; j% z' W0 R
  8. //使用宏定义芯片指令
    1 o" K* {+ {2 o4 w- t  N  U
  9. #define W25X_ManufactDeviceID  0x90  /* Read identification */  t" C! {" |# p7 {, r
  10. #define sFLASH_CMD_WREN        0x06/* Write enable instruction *// C4 b" M, G1 T# W$ X- t, o7 g4 J
  11. #define sFLASH_CMD_RDSR        0x05/* Read Status Register instruction  */
    6 w' V9 e5 R/ o/ a* U/ i
  12. #define sFLASH_CMD_SE          0x20/* Sector Erase instruction */
    ' x8 _& H; }1 \/ @
  13. #define sFLASH_CMD_WRITE       0x02  /* Write to Memory instruction */
    ' }2 r. M: x: l9 ~8 m5 n# T6 o
  14. #define sFLASH_CMD_READ        0x03/* Read from Memory instruction */$ h& I; t, q9 ?  ?) ]* d  q9 d9 i
  15. #define sFLASH_DUMMY_BYTE      0x00 //空字节,用于只读传回来的数据% `8 t: q4 I' ?
  16. #define sFLASH_BUSY_FLAG       0x01
    1 l8 l1 |5 D( Y. M& |' X
  17. #define sFLASH_SPI_PAGESIZE    0x100
    # w5 j! R  F  z, l
  18. # h5 ?& x5 }$ U
  19. /* 选中芯片,拉低信号 */5 O3 a/ H9 @2 W( P" |
  20. #define sFLASH_CS_LOW()       HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_RESET)+ m7 T& a4 ~% ^' V9 r6 l
  21. /* 释放芯片,拉高信号 */3 M; ]% q6 M% k: e
  22. #define sFLASH_CS_HIGH()      HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_SET)2 m+ R& \/ d2 b# ~
  23. ! a/ O! n) {! p% Y
  24. //定义函数- Q" N6 p9 N0 b2 L* _  t
  25. uint8_t sFLASH_SendByte(uint8_t byte);
    ! \! ^/ P' @' [* }
  26. uint16_t sFLASH_ReadID(void);' r) z( [' |) l
  27. void sFLASH_EraseSector(uint32_t SectorAddr);: q6 M% R3 _8 G' `
  28. void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);9 R/ E0 P( j, H
  29. void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);
    8 O6 i( o8 d, P6 j) I
  30. void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead);
    / C) k; Z. k0 K3 T( t- y
  31. #endif
复制代码

  1. ) I4 P/ q9 |6 E( n/ |
  2. //w25x16.c
    4 l# \- a  L+ h/ O& x2 l* D
  3. & p" k3 K6 _& m- T/ X1 S
  4. #include "w25x16.h"
    $ N$ p0 U7 {; d" ~  W4 X. D! ]& d1 |

  5. 0 K8 t2 ?8 X. G8 Q6 i+ H/ M
  6. extern SPI_HandleTypeDef hspi1;
    ; v+ u2 x* ]8 a: r8 d. q* H

  7. % o1 a$ b" T, P7 t
  8. /*读写一个字节函数,因为SPI读和写同时完成*/0 d9 C" b4 G) ~3 @, _3 D. c' j: Q
  9. /*发送数据一定会接收到一个数据*/
    7 z& m1 U! y( X; R) S
  10. uint8_t sFLASH_SendByte(uint8_t byte). S/ v8 m. }) ~/ O
  11. {
    $ C3 U3 `" G( ]5 V1 D' j2 U
  12.     uint8_t TX_DATA = byte;9 w! h* q. r# j* G) ~
  13.     uint8_t RX_DATA = 0;6 T1 ^7 I  H' l3 v7 F
  14.     HAL_SPI_TransmitReceive(&hspi1, &TX_DATA ,&RX_DATA , 1, 1000);; R# t" }& _9 V" d! C; [: o
  15.     return RX_DATA;
    1 \# m6 X& h9 r  y# D
  16. }
    : K; R( }$ @. V$ ^- p/ L

  17. 0 N4 {* T9 V' {% I/ ?1 c' @
  18. /*等待擦除或者写数据完成*/
    0 `) _5 |4 t0 Y; \4 d" \
  19. void sFLASH_WaitForEnd(void)
    ( ~  q6 j+ }) `# q- @2 {* v
  20. {
    # D8 p7 d  e6 ^" m, I% T- a
  21.     uint8_t sr_value = 0;
      L5 W3 `8 D& f) }7 \
  22.     sFLASH_CS_LOW();  x2 E0 B: B2 c8 z: E' i
  23.     sFLASH_SendByte(sFLASH_CMD_RDSR);: l' ^/ f, m' F- J+ S
  24.             //读S0的值,为1表示忙碌,为0表示停止         
      _! Q$ P$ P$ m, |8 z( {
  25.     do{; [  L" A8 u+ h  w$ W
  26.                 //发一个空字节,得到S0的值             ' ^! @& V& {0 C7 U
  27.                 sr_value = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    ; t4 e$ [! \" r2 ^+ R8 x1 K
  28.     }while( sr_value & sFLASH_BUSY_FLAG);
    & |, W) d5 r  K# U2 I3 s: ^. D

  29. , M8 V2 x4 Y1 p8 \
  30.     sFLASH_CS_HIGH();
    1 Y( k$ K: \3 ^: @* ^4 h& Z
  31. }
    $ R! u5 W/ y4 O

  32. " C' s1 {3 t( _$ x8 @
  33. void sFLASH_WriteEnable(void)
    1 V" i9 v$ A$ f  c
  34. {, W0 u: m0 w9 {
  35.     sFLASH_CS_LOW();
    / t' J+ u4 w# B2 n' J- J, G. Q
  36.    
    2 h  `" X$ x2 I, f0 k  Z" b. E
  37.     sFLASH_SendByte(sFLASH_CMD_WREN);6 D. z1 ?/ n! x1 t
  38.     5 Q& l, g; P9 S8 I+ D  J
  39.     sFLASH_CS_HIGH();
    + t, L9 e# Q% k- T: ]4 U/ O# `! v
  40. }
    4 q! a3 h5 @7 n% W" N
  41. 1 u  O* T2 m! C2 ]
  42. + G) Y0 _+ m" h: q+ {" g; h$ Q- n
  43. /*读设备ID*/
    7 R; Y7 d- Y' \- J, K# d6 J6 Z8 G
  44. uint16_t sFLASH_ReadID(void)
    ! J- A: W; A7 `  [' \
  45. {
    % Z2 f, {3 p+ d  c0 ]& {8 f
  46.     uint16_t FLASH_ID;9 u" g( C: D( k3 [: H: z$ o, U7 A
  47.     uint8_t temp0,temp1;' E0 L( @2 Y: l' L5 L
  48.    
    9 E0 d( K; c  K; L
  49.    
    4 m1 X, n/ n: m
  50.     sFLASH_CS_LOW();9 x2 {$ ^( ~* N/ E, B
  51.       g# o- z! t' k( F% d
  52.     sFLASH_SendByte(W25X_ManufactDeviceID);
    # b* M$ a7 Z$ V
  53.     //读设备指令后要发24位地址,所以要发三次9 s1 U$ z8 O7 p% c
  54.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);( C4 G( _" i, s' h
  55.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    3 {" N  k- o8 i/ x- W4 e" g* h% [. R
  56.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    ; ]5 P% b0 D- J4 F
  57.     //制造商ID
    0 p, Q6 L2 [1 q/ M9 ~, \
  58.     temp0 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    0 B& o/ p: s5 v! F
  59.     //设备商ID
    4 I7 {; j, x6 g: |
  60.             temp1 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    6 i0 h( R* ?' e: D; D; P+ e7 Z% D3 i2 D
  61.    
    * a4 V- ]$ _& B$ m
  62.     sFLASH_CS_HIGH();  o8 q% \& k! {6 B+ m5 F' c2 ]
  63.     / U+ |( |0 f2 d" c* j
  64.     FLASH_ID = (temp0 << 8) | temp1;
      f7 m6 {/ f3 X0 B
  65.     7 m5 c' V7 ~) b9 t% q
  66.     return FLASH_ID;8 J, O2 s8 }7 i
  67. }/ M2 L' S4 z' t% K# q9 b+ N
  68. # C8 A) X8 N; K! z
  69. //擦除扇区,擦除为1,因为只能由1变为0 ,不能0变16 J) Z, d) W1 o) _; m: O6 I+ @6 Y; [
  70. void sFLASH_EraseSector(uint32_t SectorAddr)
    ) R3 k" ?4 E4 I+ A! h9 `- d' u
  71. {" Q" C) Y3 \6 `% y# q3 j
  72.     //SectorAddr表示擦除第几个扇区
    7 l6 _$ m1 }% y, r" M
  73.     sFLASH_WriteEnable();  //开启写使能" d% e! M) r. \: ^2 n; }3 w4 C
  74.     sFLASH_CS_LOW();//拉低,片选: R1 u& G& }6 c9 s2 u" [8 Y
  75.     //擦除命令% e" v$ E  |( ?9 U9 b
  76.     sFLASH_SendByte(sFLASH_CMD_SE);
    $ Y, e1 V: W1 n9 M0 R8 ^
  77.             //传24位地址( j4 P! ^1 K$ s8 d. S" |$ G
  78.             //传送高8位,将中8位和低8位一共16位移出去,得到高8位                   , H( L; \: ^' j* P
  79.     sFLASH_SendByte( (SectorAddr>>16) & 0xff);   / W( Y: k5 _5 H- Z% w
  80.     sFLASH_SendByte( (SectorAddr>>8) & 0xff);    //传送中8位' R* Q( i5 k; w7 O8 l; p3 w! b0 g
  81.     sFLASH_SendByte( (SectorAddr>>0) & 0xff);    //传送低8位
    / Z! O' w4 M. T" ]" m1 e
  82.      
    7 _- q2 x' ^6 L7 b( F0 ^
  83.     sFLASH_CS_HIGH();5 x. S! s+ e' Z, o" M
  84.    
    1 @; A4 W& Q5 N# a; C0 }4 x! p
  85.     /*读状态寄存器,等待擦除完成*/
    / ~: C) a2 S. _; K2 G
  86.     sFLASH_WaitForEnd();" M# Q( \; m9 {' U- E
  87. }/ g9 L- l  J3 v* [
  88. 1 T/ v5 u- L0 ~+ N
  89. //读数据
    + I( ?1 s" U4 w- ]3 ]9 k. W
  90. //读命令和读地址发送后,芯片内部会自动不断递增读数据
    ) o& F8 v5 D* O4 Q. E" J: X' V
  91. void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead)
    ) d) k: m9 U8 R$ ]; ~
  92. {* Y$ f; R, ~, K4 {% U
  93.     sFLASH_CS_LOW();
    * s5 J' i6 _1 u' j5 X2 S
  94.    
    0 G  V: ]( G  }
  95.     sFLASH_SendByte(sFLASH_CMD_READ);9 S5 }* i: Z( P, t4 k: @* B; o+ S# g5 ]
  96.     sFLASH_SendByte( (ReadAddr>>16) & 0xff);   //传送高8位* f, B) A) J0 Q0 E: f* P( s. b0 w
  97.     sFLASH_SendByte( (ReadAddr>>8) & 0xff);    //传送中8位
    - _; u3 ^9 h9 [; T
  98.     sFLASH_SendByte( (ReadAddr>>0) & 0xff);    //传送低8位5 j2 _" C/ [# U- d7 C: L' H
  99.    
    0 c5 @$ L, \/ ~& Z
  100.     while(NumByteToRead--)5 d! i6 e1 y, c* B8 n
  101.     {3 E/ R+ R" h9 Y
  102.     * pBuffer = sFLASH_SendByte(sFLASH_DUMMY_BYTE);9 C4 O$ ~, E- Z0 I# ^; ?' K2 y" G
  103.     pBuffer++;" D4 Z9 i1 m3 G1 ]* g
  104.     }( |! i( f# @: s
  105.     * L2 V) C: l" [0 ?, W! @" v
  106.     sFLASH_CS_HIGH();9 s, R. f' M; d
  107. }
    $ w' `" ~) C% L

  108. 6 d. `, O, E0 |- J: E  \) |
  109. //写一页最多只能写256个字节,一个扇区16页,一个块16个扇区                              
    % z" e, k3 I# r0 m1 N9 c
  110. void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
    : n6 ?7 y! ]7 |. J! }/ {
  111. {6 n) c! L- A$ A+ \9 z0 |- F) W+ }
  112.     if(NumByteToWrite > sFLASH_SPI_PAGESIZE )) |0 e8 F! N4 ?
  113.     {  X9 h/ G0 _  [; s9 w
  114.     NumByteToWrite = sFLASH_SPI_PAGESIZE;( O" p+ G  d& B$ P9 T: r
  115.    
    9 R1 M7 j. T8 H$ O9 H
  116.     printf("写数据量过大,超过一页大小\n");) }5 l& e$ i3 ]; R; K
  117.     }4 U* {9 M" S0 q# u$ I
  118.    
    6 x, G  z0 A6 ]! R7 n
  119.     sFLASH_WriteEnable();  //开启写使能
    ' ~5 d$ {, O( T% z
  120.     9 q0 ?9 a/ M) ?, H
  121.     sFLASH_CS_LOW();% J+ o. b2 K0 [5 T0 r4 \0 Z1 o
  122.    
    , {1 c- A' E0 W: ]! q
  123.     sFLASH_SendByte(sFLASH_CMD_WRITE);
    # B/ o- L/ p  Q8 K6 x8 ]* @( q
  124.     sFLASH_SendByte( (WriteAddr>>16) & 0xff);   //传送高8位
    8 u7 d( M, Y6 I
  125.     sFLASH_SendByte( (WriteAddr>>8) & 0xff);     //传送中8位
    % p) |1 ?0 N/ Y$ i: j+ y1 K' k; h8 g
  126.     sFLASH_SendByte( (WriteAddr>>0) & 0xff);      //传送低8位
    ; g* Y, E3 @# g$ b, M6 v* S
  127.    
    / l( }7 {7 Q7 d5 e- e* ?
  128.     while(NumByteToWrite--)1 S7 j6 U6 v: K1 x4 e
  129.     {
    + K4 Q4 X# L3 U$ \# @! n" r; f6 _
  130.     sFLASH_SendByte(* pBuffer);
    & {8 i, W8 @4 d8 s$ i6 m2 A
  131.     pBuffer++;
    " F$ }6 r% F5 Y8 K% M" q+ q
  132.     }3 Y) _7 U8 {3 i0 g- a
  133.    
    3 W6 ~8 D8 n0 q0 A$ q1 [
  134.     sFLASH_CS_HIGH();
    % z3 F9 Z4 D. O/ h  X$ M  U
  135.    
    + F7 ^% B* [6 H8 _' c, D% x' Y  i6 o
  136.     /*擦除和写数据都涉及到写动作,一定要等待完成*/
    # ?3 [( g+ |7 Z5 H; y8 T
  137.     sFLASH_WaitForEnd();4 Q% ^) ]" Y4 n! m" k/ c3 w7 A
  138. }
    0 O( t7 n/ l* |. v5 s

  139. * Y4 ?0 C2 x/ M  R7 w
  140. //写任意地址、任意长度$ B* ?" a+ ^1 L) \0 u) y" J; \
  141. void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
    8 L9 u1 _& A4 L* I9 `
  142. {" i6 c, y6 J" @7 u
  143.     uint16_t NumOfPage, NumOfBytes, count, offset;# R0 b: `7 [3 v
  144.     //求WriteAddr在某一页的位置        # l) W1 \$ j( n: V2 v
  145.     offset = WriteAddr % sFLASH_SPI_PAGESIZE;
    # X9 q  o/ i. E1 p% R+ _0 W! ]. v
  146.     //求某一页剩余的大小 - f/ M8 D' x5 [1 B# d. S3 U  @
  147.     count = sFLASH_SPI_PAGESIZE - offset;
    9 O$ d3 O4 L' I
  148.    
    & \$ G& B7 s  A" O" k
  149.     /*处理页不对齐的情况,防止页内覆盖*/
    " V6 F! ~7 j, C( w# f/ I9 `
  150.     //先把某一页剩下的部分写掉,之后的就能新页的起始处开始写        /*offset有值表示需要页对齐,如果要写的字节数小于某一页剩余的部分,那就无需对齐*/                  
    ) Y; `% f$ p5 ]# _, ^
  151.     /*这两个条件必须同时满足*/
    0 f% r" l" Q3 z2 e  ]4 c5 V
  152.     if(offset && (NumByteToWrite > count ))
    4 z0 a# R! C4 V, o
  153.     {& L9 G- P& U+ l8 z# K, v1 j0 \
  154.     sFLASH_WritePage(pBuffer,WriteAddr,count);
    6 I! t/ p, x' E( A2 K
  155.     NumByteToWrite -= count;//去掉已经写了的,从新页开始6 b5 O$ N  F  j0 }1 `
  156.     pBuffer += count;
    & Z' }$ y8 M& L5 s
  157.     WriteAddr += count;
    ; o9 d7 D2 ?$ f! w/ N7 @! p  J
  158.     }
    $ K" n4 w. t; ^* ]0 v, g0 X/ x
  159.    
    6 u$ i( Y% d& _" Z8 U
  160.     /*最多可分多少页*/
    8 J4 ?  s! c" v% q
  161.     NumOfPage = NumByteToWrite / sFLASH_SPI_PAGESIZE;
    7 R* j; l0 ~& n& @$ O
  162.     /*剩余多少字节*/
    # Z. ~. S: G; c# K7 T
  163.     NumOfBytes = NumByteToWrite % sFLASH_SPI_PAGESIZE;
    $ ^. K3 i& X8 j2 g6 g# \
  164.     3 @; {' E9 R+ z' r& ~, _7 [
  165.     if(NumOfPage)1 Q  T! X2 K) O" W
  166.     {
      |+ n, F9 x8 s2 d
  167.         while(NumOfPage--)
    5 u& r0 l4 H# {' S& P  r" O" n# |/ B
  168.         {
    $ T8 W) O3 H7 F1 `% t7 b  x* o$ K
  169.              //每一页都发起页编程9 Z+ W. A3 [$ m) z0 ^; t
  170.              sFLASH_WritePage(pBuffer,WriteAddr,sFLASH_SPI_PAGESIZE);
    $ }2 N! a+ V4 l* V4 K
  171.              pBuffer += sFLASH_SPI_PAGESIZE;& k, n, X: t* Q: I+ x8 F. ]
  172.      WriteAddr += sFLASH_SPI_PAGESIZE;5 m! O/ n4 m! u0 b, K
  173.          }
    : {* V  m& z2 k( P2 x  S" P0 c, ]
  174.     }
    + U, j  m9 @7 }9 J
  175.    
    3 T2 J' a+ ]: h# I
  176.     if(NumOfBytes)
    : D  h& L% n1 J. g+ ?
  177.     {
    0 ?3 O, f. n8 a6 Y( X9 {/ A7 t
  178.     sFLASH_WritePage(pBuffer,WriteAddr,NumOfBytes);- v0 F. [0 M+ |* Z
  179.     }
    : y  X, h) L* R5 `% g6 d
  180. }
复制代码

  d' @, }) L9 m
  • 为什么会有两种写操作函数,是因为这里的写操作有两个特点:
    ; {  h8 N0 H/ L7 R* M5 t- O

    2 w* @3 ^5 _6 `+ C, G/ W
/ H1 v4 y8 o9 v. N6 e
1.无法突破页限制,超过一页需要重新发起页编程信号。另外如果要写的数据大于剩余一页剩余的容量,那么超出的数据会写到当前页起始地址出。例如,初始输入的写地址为200,而要写的数据大小为100,那么要写的前56个字节会从地址200开始依次写入,剩下的44个字节会从当前页的0地址开始依次写入,这很有可能覆盖之前的数据。
; j0 U" o; \4 r0 H2 t! U0 l3 |
2.无法突破扇区的限制,当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。

3 p- _0 w# [9 P+ l! i/ I* _9 d
转载自: 骆驼听海
3 u* J/ \, j6 W7 C4 a2 v如有侵权请联系删除* E0 u6 r, [% Q" S

" I; _' X2 ?# x, p0 g4 ^
赞 收藏 评论0 发布时间:2023-4-12 16:18

举报

0个回答

所属标签

相似技术帖

官网相关资源

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