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

STM32(ARM)开发进阶知识(一)

[复制链接]
STMCU小助手 发布时间:2022-10-11 23:44
微信图片_20221011234243.jpg
本系列为ARM开发的进阶知识,讲解与ARM芯片进行通信的大部分高级外设的原理及使用方法,不局限于ARM芯片内部,实操性较强且晦涩,需要一定的读者具备一定代码阅读和理解能力。

2 _( j1 e+ W: z" v8 Q5 I1 t
  • STM32 I2C总线通信专题讲解

    + Q- `9 W! V+ q1 t. o
5 u5 R( M, v# p  D9 M1 G' z

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

    4 X% |4 U, W8 g7 P& J" J
    % D, r" p+ g2 m  K  d( q* h
  • 总线特征:
    - D  G  z( v$ J0 y$ ]
1.两条总线线路:一条串行数据SDA,一条串行时钟线SCL(主从设备使用同一时钟,属于同步通信)来完成数据的传输及外围器件的扩展
/ A! X: {  _' n8 g& i9 L
2.I2C总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址,通常是7位,有时候是10位
# }2 n; L5 S3 L5 w* J" V
3.I2C总线数据传输速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。在开发配置的时候,最好检查从设备的传输速率从而对主设备(一般是MCU)进行相应的配置。一般通过I2C总线接口可编程时钟来实现传输速率的调整,同时也跟所接的上拉电阻的阻值有关。
5 y7 i$ N9 g5 ^6 t& B) y3 e, Y
4.I2C总线上的主设备与从设备之间以字节(8位)为单位进行单双工的数据传输。

7 u' v) w& p& c- p, P
  • 拓扑结构——总线型

    4 q8 J3 V, J3 x  \# l
I2C 总线在物理连接上分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成,SCL由主机发出,SCL越快,通讯速率越快。通信原理是通过对SCL和SDA线高低电平时序的控制来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。

4 O1 t' H% z. E
微信图片_20221011234252.png
6 l! q4 j: H" \
  • I2C总线协议
    & c- \/ {& c/ z& S' ~* x
1.I2C协议规定:  总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生。

, V( A) T1 f! B% _) e9 I0 w5 Z
2.空闲状态:SCL和SDA都保持着高电平。

8 J! ~  L$ u/ ]0 f: Q6 _( Q
3.起始信号:  当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件,所有的从设备都能感受到这个跳变,做好准备等待被选择。

/ U1 ~, S, ]0 L+ L; t- c$ G
4.结束信号:当SCL为高而SDA由低到高的跳变,表示产生一个 停止条件

( M4 ?3 V1 E: L' K
微信图片_20221011234255.png

( q2 G' n+ T* w+ W. X
5.数据传输:数据传输以字节为单位 , 主设备在SCL线上产生每个时钟脉冲的过程中将在SDA线上传输一个数据位,数据在时钟的高电平被采样这时候采集到是1就是1,是0就是0,所以在传输数据时,当时钟处于高电平时一定要保持稳定,时钟处于低电平时可以变换数据。(高电平采样,低电平变换)一个字节按数据位从高位到低位的顺序进行传输。主设备在传输有效数据之前 要先指定从设备的地址,一般为7位,然后再发生数据传输的方向位, 0表示主设备向从设备写数据,1表示主设备向从设备读数据。主从设备以字节为单位(8位)进行数据传输,开始传输数据时把从设备地址加上方向位组成一个8位的字节进行发送并接收一个应答。
6 m" h+ h3 g/ X* C
6.应答信号:接收数据的器件在接收到 8bit 数据后,向发送数据的器件发出低电平的应答信号,表示已收到数据。这个信号可以是主控器件发出,也可以是从动器件发出。总之,由接收数据的器件发出。

1 d, b; }* Y6 G$ h6 L
微信图片_20221011234258.png

+ N( Q1 X7 B; J& V( m0 i2 s
a.主设备向从设备写数据:
微信图片_20221011234300.png
) g/ C% S+ x* X# i% [( \' h
b.主设备读从设备的数据:
微信图片_20221011234303.png

- w6 c$ j9 u$ _/ A4 d6 ?  Y4 G
c.主设备读从设备的某个寄存器:读设备的寄存器首先应该对该设备发送写命令,很多设备都可以看成是一段内存,所以写命令写给从设备,指明要读取哪个地址(寄存器)的数据,接下来才是真正的读数据。不同的从设备是由区别的,在驱动I2C从设备时应当查明设备的时序图,又怎样的要求,不同的时序对应了不同的命令。
4 x  Z  n8 Q  K9 L+ H; m5 p0 l
微信图片_20221011234305.png
3 |9 Y7 @) W! ]# m0 y
' A, D7 i' ~* ?7 ^- E  V
  • STM32F4-I2C控制器特性

    3 V3 w& E- _) H5 y6 w7 G. M
软件模拟I2C时序:由于直接控制 GPIO 引脚电平产生通讯时序时,需要由 CPU 控制每个时刻的引脚状态,所以称之为“软件模拟协议”方式。我们知道,驱动I2C设备只需要两根管脚,即使单片机上没有I2C控制器,根据协议控制每根管脚每一时刻的电平状态,一根模拟数据线,一根模拟时钟线,就可以驱动从设备,相对而言效率低,但是可以实现控制驱动。STM32内部具备专门的I2C控制器,使用时只需对其进行相应的配置即可。

- o* x6 U  m( b+ {: v. B
硬件控制产生I2C时序:STM32 的 I2C 片上外设专门负责实现 I2C 通讯协议,只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并缓存起来,CPU只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种由硬件外设处理I2C协议的方式减轻了 CPU 的工作,且使软件设计更加简单。
4 ~  v, J  {* x
控制器功能:配置主从模式(一般都把STM32当作主机使用,作为从机时应当对其赋一个地址),通过配置其内部的寄存器产生一些中断和错误信号,配置通信速率位标准模式、快速模式、超快速模式等

2 `3 i1 ?- O* ~+ y! f6 k) ^# V
STM32芯片有3组I2C外设,可以同时进行3组I2C传输。它们的I2C通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚。

; |( u5 S1 Y! f; y# B0 N
微信图片_20221011234308.png
9 \5 Z3 a" @+ }4 I- @
  • EEPROM(AT24CXX)存储芯片介绍
    , y9 h: b( d# D# k/ j( Q
一个典型的I2C接口的从设备,专门用于存储数据的芯片。EEPROM (Electrically ErasableProgrammable read only memory),带电可擦可编程只读存储器,一种掉电后数据不丢失的存储芯片。EEPROM可以在电脑上或专用设备上擦除已有信息,重新编程。

7 C5 }0 y! C3 L* f% h0 H; ?
EEPROM常用来存储一些配置信息,以便系统重新上电的时候加载之,容量不会很高。EEPOM 芯片最常用的通讯方式就是I2C协议。XX表示容量,常用值为01、02、04、16、32、64等,单位Kbit。一般的存储芯片都具有写保护功能,对WP管脚加一个高电平就开启了写保护功能,就无法往芯片内写数据了。在开发中通常将该管脚接地,确保能够写数据
典型24CXX芯片引脚如下:
& d9 w! Q' D* ~- @
微信图片_20221011234310.png
; l& K) P6 X, d# m/ g, M. L
例:24C65的设备地址为7位,高4位恒定为1010,低3位取决于A0-A2的电平状态,般主机在读写24CXX都是把设备地址连同读写位组合成一个字节一起发送。
7 }& {: P! ~& p4 X& A
微信图片_20221011234314.png
* Z# v7 V6 p) Q% U7 e! M* z
24C65的电气连线如下,根据电气连线可知,A0-A2均接地,因此读地址为1010 0001,即0xA1;写地址为10100000,即0xA0 ,且WP接地,用户随时可向芯片内部写入数据。
  D. ~; J) L* o9 h/ x6 `; E1 u
微信图片_20221011234319.png

( n( f6 \  j" n: D
24C65写时序:首先发送一个起始信号,接着发送从设备地址以及方向位,收到应答后,向从设备发送要写的存储区域的首地址,24C65的存储地址是16位,先发送高8位,收到应答后再发送低8位,再次收到应答后开始写数据。64Kbit大小位8K字节,需要13位即可表示,所以高3位固定定为0,如下图。
, ~9 n$ |- B2 ~  g" v6 e
这里是BYTE WRITE,一次写一个字节,此芯片还支持PAGE WRITE,一次写一页,也就是8个字节,如果想写更多,可设置一个for循环实现。
' n- g: Z) p& C# D8 K
微信图片_20221011234320.png

/ N+ Y) Y1 W$ x' F
24C65 读时序与写时序基本相同,只不过在读之前要发送再发送重复开始位进行读操作。

$ Q3 z+ X( J% y0 b+ J4 T! L
微信图片_20221011234323.png
# {% C* K: G  {2 {6 E: x( c% S
  • I2C读写EEPROM实例

    7 S+ ]8 N# j. @2 f

    " N( [0 \: ~% S0 }& G( r& R7 \
微信图片_20221011234325.png

9 m4 d) P& \( o5 r# Q2 r
由电气原理图可知SCL和SDA分别接入了PB6和PB7管脚,读地址为1010 0001,即0xA1;写地址为10100000,即0xA0

- ^+ q( [, H1 L$ X
步骤:
1.配置RCC

, x( d5 J) g# v0 ]; v5 N
2.配置PB6和PB7管脚
微信图片_20221011234328.jpg
% x# ~3 s, I( R5 s3 [
3.配置I2C协议参数
微信图片_20221011234330.png
6 ~# u0 n% }, d
4.编写代码
  1. //mian.c/ N/ `, Y. ~! q% ?
  2. ( a+ A2 V/ m8 C$ J& O' v
  3. #include "main.h"
    8 F: S* p( {1 T6 y/ p# V
  4. #include "stm32f4xx_hal.h"
    * O& d7 \+ j3 y) L3 K! q- k. q! p
  5. #include "i2c.h"
    # c8 ?# M5 A7 R. Q! Z
  6. #include "usart.h"
    " V. F' F: @3 R3 T. G) \, N/ N
  7. #include "gpio.h"
    & b% {( T8 ^" O+ K) n8 v
  8. - d! R! K( ~8 x* T! E7 v
  9. #define ReadAddr   0xA1
    % d2 U% N% h. ?5 }: C$ Z0 [
  10. #define WriteAddr  0xA05 u1 `" o' E* ^
  11. ; P8 f4 B# C% a' R. B
  12. uint8_t Wbuf[20] = "EEPROM TEST OK!";0 b8 f" Q$ L' c
  13. uint8_t Rbuf[20] = {0};
    * P. N& F) b( c* T8 J3 q" d" V# r
  14. # f4 b4 j+ f0 [: o0 P/ O( C
  15. /*********  24C65写数据函数*****************************/
    ) [# M1 R- B: c: y2 P% `' d' s6 F
  16. ; E' J! Q% K7 \+ a, u* r* V
  17. void  Eeprom_Write(uint16_t MemAddr, uint8_t *Wbuf, uint16_t len ){
    , A( G6 S8 P9 g; T
  18.         while(len--){; E' P1 U% t- s) @
  19.         //I2C_MEMADD_SIZE_16BIT表示存储单元大小& `6 @' w* ~2 ]& i  g% Q+ n, q
  20.         //默认为两个参数,分别是I2C_MEMADD_SIZE_16BIT和I2C_MEMADD_SIZE_8BIT5 L; ]8 W, q7 F/ ]1 m6 [% B
  21.         //由于24C65的存储地址是16位的( q4 ^, n8 Y5 N4 z2 \7 Z. w
  22.         //所以我们选择I2C_MEMADD_SIZE_16BIT
    + b0 J2 m0 A( |  V1 Z
  23.         //1表示一次写一个字节
    , r1 f; L' t( o" g; J+ B
  24.                 while(HAL_I2C_Mem_Write(&hi2c1, WriteAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Wbuf, 1, 100) != HAL_OK){};
    0 G% C: n' f; h$ Y
  25. MemAddr++;
    4 o! O* g6 n* R6 T0 z
  26. Wbuf++;
    # G. T: y" V* _
  27. }
    ) l9 s9 C/ T0 p
  28. }
    , l0 i- M% j8 u
  29. 4 f2 _; ^* t! y; N/ ]

  30. . I% Q6 G7 n/ q' z& A- }3 L
  31. /*********  24C65读数据函数*****************************/7 s' X' k8 C9 p
  32. 6 V+ c/ Y5 j1 z5 i5 B! j" ^
  33. void  Eeprom_Read(uint16_t MemAddr, uint8_t *Rbuf, uint16_t len ){( @0 W$ t* M7 T9 Z" X* G$ l
  34.         //可以连续读,所以无需循环# u" `. g2 h# c3 p
  35.         while(HAL_I2C_Mem_Read(&hi2c1, ReadAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Rbuf, len, 100) != HAL_OK );" I- A& F' f( {4 o6 `4 h7 j. w# s/ T
  36. }
    ; [* h7 y+ x' H3 I% {+ {
  37. int mian(){: }6 l; A6 r: Y. V+ P; o2 w
  38.       MX_GPIO_Init();
    8 d7 U4 I5 C9 c- ~
  39.       MX_I2C1_Init();4 {3 M/ A. Y% @1 {) R- d3 {
  40.       MX_USART1_UART_Init();. d4 @* ^" A& l$ @% L! n) i
  41.       printf("this is i2c eeprom test\n");
    9 g1 }: g2 Z, Q. w

  42. ' ]5 [1 ?" ?* w8 R1 m- W7 P3 ^7 ^
  43.       Eeprom_Write(0, Wbuf, sizeof(Wbuf) );6 k6 ]3 Z& S* H, W/ V: u$ v
  44.       HAL_Delay(500);0 Z7 M1 G2 s# N6 k
  45.       Eeprom_Read(0 , Rbuf, sizeof(Rbuf));
    6 T- p5 r0 G! z8 ~
  46.       printf("READ:  %s\n", Rbuf);* h6 n8 K& f8 U
  47. 9 Y9 P% Z  @  Q7 ]. p* I
  48.       while(){2 S7 x1 g  l$ [) {3 U) n7 }
  49. 1 q& w/ O6 h3 h/ L$ k
  50.       }
    & X. ^5 j% i6 Y# h) v& x3 ^
  51. }
复制代码
5 K2 a# f: \4 X- `% g, i, R" j
  • STM32 SPI总线通信专题讲解

    * L& m* O4 _# H% W8 ^
SPI接口是Motorola 首先提出的全双工三线同步串行外围接口,采用主从模式(Master Slave)架构;支持多slave模式应用,一般仅支持单Master。时钟由Master控制,在时钟移位脉冲下,数据按位传输,是高位在前还是低位在前是可以配置的,配置时根据从设备的通信进行相应配置,一般是高位在前,低位在后(MSB first)。SPI接口有2根单向数据线,为全双工通信,目前应用中的数据速率可达几Mbps的水平。
SPI总线被广泛地使用在FLASH、ADC、LCD等设备与MCU间,要求通讯速率较高的场合。
微信图片_20221011234338.png
. S* w& T6 A6 M% B3 T, b
SPI接口共有4根信号线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。
! Z5 \+ w& @6 S! T5 l
微信图片_20221011234341.png
(1)MOSI:主器件数据输出,从器件数据输入,连接从机的MOSI,与串口不同,串口需要反着连接(Rx-----Tx)
(2)MISO:主器件数据输入,从器件数据输出,连接从机的MISO
(3)SCLK :时钟信号,由主器件产生
(4)/SS:从器件使能信号,由主器件控制(片选),一般情况下为地电平选中设备,高电平释放设备。

) w% a0 ~9 W9 _+ R+ g* T' N) j
  • SPI总线协议

    6 K0 |+ V+ c; b& ?8 x

    - L9 ]2 f' V: _" e+ a5 I. e4 h
1.数据交换逻辑:主机和从机都包含一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节发起一次传输。寄存器通过MOSI信号线将字节传送给从机,从机也将自己的移位寄存器中的内容通过MISO信号线返回给主机。这样两个移位寄存器中的内容就被交换了。从机的写操作和读操作时同步完成的,因此SPI成为一个很有效的协议。
! e, T# `* g+ l
如果主机只想写不想读,只需把数据放在数据寄存器,SPI控制器会自动传给外设,同时忽略掉外设传过来的数据即可;如果主机只想读不想写,主机写给外设一个空字符或者随便写一个数据,外设就会把数据传过来,不管是只读还是只写,主机与外设的读和写都h会发生且同时进行。
, W! J& q5 a3 L6 x+ A) E. t
微信图片_20221011234343.png
- h6 }, s+ V' A: I4 v
2.起始信号:  NSS信号线由高变低,是SPI通讯的起始信号。

% G, V0 i( \& J. }
3.结束信号:NSS信号由低变高,是SPI通讯的停止信号。

, e" p% J. ~4 R
4.数据传输:SPI使用MOSI及MISO信号线来传输数据,使用SCK信号线进行数据同步。MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,按位传输,且数据输入输出是同时进行的。SPI每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制,要么是8位,要么是16位,可以配置。

9 x# o9 k# I0 j. T9 k5 s, o
微信图片_20221011234345.png
- @% P( \2 p' I9 S
  • SPI的4种通信模式

    % j) l1 m* f0 a' `! Y! U/ H' R
在SPI操作中,最重要的两项设置就是时钟极性(CPOL)和时钟相位(CPHA)这两项即是主从设备间数据采样的约定方式。由CPOL及CPHA的不同状态,SPI分成了四种模式,主机与从机需要工作在相同的模式下才可以正常通讯,因此通常主机要按照从机支持的模式去设置。同样在配置时一定要弄明白从机支持什么通信模式进行相应的配置。

, h: `, r2 Y9 f1 T2 @4 {1 g* m, S
微信图片_20221011234348.png
; t: \, N  S6 c- l) O9 Y% W9 y$ ^
1.时钟极性CPOL : 设置时钟空闲时的电平:
a.当CPOL= 0 ,SCK引脚在空闲状态保持低电平;
b.当CPOL= 1 ,SCK引脚在空闲状态保持高电平。

( y* d( j  O8 k# f  L- ^
2.时钟相位CPHA :设置数据采样时的时钟沿:
a.当 CPHA=0时,MOSI或 MISO 数据线上的信号将会在 SCK时钟线的奇数边沿被采样
b.当 CPHA=1时, MOSI或 MISO 数据线上的信号将会在 SCK时钟线的偶数边沿被采样

, F, Y2 E" S( q/ S, B/ `7 h! ?) `% ]
微信图片_20221011234350.png

; V' R+ v' @( h! U3 K7 o3 r5 {
微信图片_20221011234353.png
' ^" d; q5 X) i, g2 t
  • STM32F4-SPI控制器特性

    9 J& Z: Q6 V! X8 g" h
% {& K: @" R! g' a: t
微信图片_20221011234356.png

6 I" [( E1 o( E- C8 y
1.通讯引脚:
STM32F4芯片最多支持6个SPI外设控制器,它们的SPI通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚,以《STM32F4xx规格书》为准。f407只有SPI1、SPI2、SPI3。
5 D. l3 r, ^( a; z5 b* R
微信图片_20221011234358.png

' k* A0 g& r# v1 C
其中SPI1、SPI4、SPI5、SPI6是APB2上的设备,最高通信速率达42Mbtis/s,SPI2、SPI3是APB1上的设备,最高通信速率为21Mbits/s。其它功能上没有差异。

% T7 _& f2 d- d* z8 P
2.时钟控制逻辑:
SCK线的时钟信号,由波特率发生器根据“控制寄存器CR1”中的BR[0:2]位控制,该位是对f pclk 时钟的分频因子,对f pclk 的分频结果就是SCK引脚的输出时钟频率。

% f; i% ~' z( R  n# W8 p/ |. I
微信图片_20221011234402.png

' X3 S3 S$ B7 B2 f! m5 k* s
其中的fpclk 频率是指SPI所在的APB总线频率,APB1为fpclk1 ,APB2为fpckl2
% d- A8 f2 |) J" _
3.数据控制逻辑:
STM32F4的MOSI及MISO都连接到数据移位寄存器上,数据移位寄存器的数据来源来源于接收缓冲区及发送缓冲区。

1 O; d5 z0 u1 m0 j4 q+ h
a.通过写SPI的“数据寄存器DR”把数据填充到发送缓冲区中。

$ t. ]/ t3 i& x; h- J# {
b.通过读“数据寄存器DR”,可以获取接收缓冲区中的内容。

" Z" S9 H' o$ u* T$ q6 l, z) Q
c.其中数据帧长度可以通过“控制寄存器CR1”的“DFF位”配置成8位及16位模式;配置“LSBFIRST位”可选择MSB先行(高位在前)还是LSB先行(低位在前)。
- I- K/ ?1 o, f" v7 F
4.整体控制逻辑:
a.整体控制逻辑负责协调整个SPI外设,控制逻辑的工作模式根据“控制寄存器(CR1/CR2)”的参数而改变,基本的控制参数包括前面提到的SPI模式、波特率、LSB先行、主从模式、单双向模式(同时发送和接收、只发送关掉接收、只接收关掉发送)等等。
1 t. \+ N; b8 [5 _) C8 ]" d+ Y
b.在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,只要读取状态寄存器相关的寄存器位,就可以了解SPI的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生SPI中断信号、DMA请求及控制NSS信号线。
2 d5 c7 _2 t4 D1 U, Z* m6 K: A4 E
c.实际应用中,一般不使用STM32 SPI外设的标准NSS信号线,而是更简单地使用普通的GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号。

2 c1 \/ g: }1 O, q1 f7 k
  • 串行FLASH_W25X16简介
    ) x( E1 w+ W; `* D$ ~7 G5 f

) ?$ o& ]5 N/ u2 I1 D1 T4 o; \! |
FLSAH 存储器又称闪存,它与EEPROM都是掉电后数据不丢失的存储器,但FLASH存储器容量普遍大于 EEPROM,现在基本取代了它的地位。我们生活中常用的 U盘、SD卡、SSD 固态硬盘以及我们 STM32 芯片内部用于存储程序的设备,都是 FLASH 类型的存储器。在存储控制上,最主要的区别是FLASH 芯片只能一大片一大片地擦写,而EEPROM可以单个字节擦写。
- Z# K0 e- u2 q- @
W25X16有8192个可编程页,每页256字节。用“页编程指令”每次就可以编程256个字节。用扇区擦除指令每次可以擦除16页,即一个扇区包含16页,用块擦除指令每次可以擦除256页,用整片擦除指令即可以擦除整个芯片。W25X16有512个可擦除扇区或32个可擦除块。

$ h1 ~& _, ~% x0 {6 A
微信图片_20221011234404.png
2 _* f) z9 c8 M5 F* M7 A
1.W25X16的硬件连线如下:
微信图片_20221011234433.png
# j; ~: d. U( b" L5 f
CS:    片选引脚,低电平有效,连接到STM32-PH2管脚
SO:   连接到STM32-PB4管脚(MISO)
SI:    连接到STM32-PB5管脚(MOSI)
CLK:  连接到STM32-PA5管脚(CLK)
WP:   写保护管脚,低电平有效,有效时禁止写入数据。接电源未使用
HOLD: HOLD 引脚可用于暂停通讯,该引脚为低电平时,通讯暂停,未使用

5 x5 X6 r9 H5 c6 @$ _5 ~4 ]
2.W25X16控制指令:
我们需要了解如何对FLASH芯片进行读写。FLASH 芯片自定义了很多指令,我们通过控制 STM32利用 SPI总线向 FLASH 芯片发送指令,FLASH芯片收到后就会执行相应的操作。
而这些指令,对主机端(STM32)来说,只是它遵守最基本的 SPI通讯协议发送出的数据,但在设备端(FLASH 芯片)把这些数据解释成不同的意义,所以才成为指令。

* T' n" l' F. A1 e
微信图片_20221011234411.png

8 u+ D9 D, G7 w8 S. U
a.读制造商/设备ID(90):该指令通常在调试程序的时候用到,判断SPI通信是否正常。该指令通过主器件拉低/CS片选使能器件开始传输,首先通过DI线传输“90H”指令,接着传输000000H的24位地址(A23-A0),之后从器件会通过DO线返回制造商ID(EFH)和设备ID。(注:SPI为数据交换通信,主器件在发送“90H”指令时也会接收到一个字节FFH,但此数据为无效数据)
* Y' D+ [, |  g' W$ B* [! g; E8 u
微信图片_20221011234414.png

. o3 h; Q, f9 r- W1 H' `! {2 O
b.写使能命令(06H):在向 FLASH 芯片存储矩阵写入数据前,首先要使能写操作,通过“Write Enable”命令即可写使能。
. F; m1 g2 P8 U1 B# g# u0 v5 W
微信图片_20221011234417.png

+ h# r4 z9 o* F# A/ J0 Z
c.扇区擦除(20H):由于 FLASH 存储器的特性决定了它只能把原来为“1”的数据位改写成“0”,而原来为“0”的数据位不能直接改写为“1”。所以这里涉及到数据“擦除”的概念。
' x, b- ]- l8 M) w& K; l9 z
在写入前,必须要对目标存储矩阵进行擦除操作,把矩阵中的数据位擦除为“1”,在数据写入的时候,如果要存储数据“1”,那就不修改存储矩阵 ,在要存储数据“0”时,才更改该位。
1 e: _/ x$ m+ O: Z' G
微信图片_20221011235116.png
微信图片_20221011234419.png

/ m, I* u" q0 C& C7 V
d.读状态寄存器(05H):FLASH 芯片向内部存储矩阵写入数据需要消耗一定的时间,并不是在总线通讯结束的一瞬间完成的,所以在写操作后需要确认FLASH芯片“空闲”。我们只需要读取FLASH芯片内部的状态寄存器SRP的S0即可(当这个位为“1”时,表明 FLASH芯片处于忙碌状态,它可能正在对内部的存储矩阵进行“擦除”或“数据写入”的操作)
" {  Y% w1 E) M6 `
微信图片_20221011234421.png
. d. h$ d8 G0 [6 p( y$ \
微信图片_20221011234424.png

- _& {( a3 n3 t# I
e.读数据(03H):读数据指令可从存储器依次一个或多个数据字节,该指令通过主器件拉低/CS电平使能设备开始传输,然后传输“03H”指令,接着通过DI管脚传输24位芯片存储地址,从器件接到地址后,寻址存储器中的数据通过DO引脚输出。每传输一个字节地址自动递增,所以只要时钟继续传输,可以不断读取存储器中的数据。

" k4 h: I; Q  a  z9 Z
微信图片_20221011234427.png

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

6 B+ W. T- k( Z8 {3 W  Z1 `" H
注:当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。

+ S4 R2 [% i' r4 s6 t5 T% [" W
微信图片_20221011234430.png

/ G/ H7 d9 l! m7 e0 X) I
  • STM32 SPI_FLASH基本配置和操作
    . _% W) z: u7 d7 i
    ; h* w, K$ D& U8 U5 `5 x2 f
根据如下的硬件连线图进行配置
# F. s. R( h  l: x
微信图片_20221011234406.png
1 w, }& z$ c6 Z7 K
步骤:
1.使能时钟RCC
& B( E- p+ r6 j7 c
2.使能SPI1,配置相应管脚

6 N# w, ^: W* d+ R8 h
微信图片_20221011234436.png

( U4 q2 W' \2 z( ^
微信图片_20221011234439.png
! Z3 R4 Z" b# p, U% N- m
3.配置SPI协议
微信图片_20221011234443.png
7 w( c+ f5 i( _+ Q" s/ M* U% d0 Q
4.编码
  1. //main.c
    5 f" f. t' D, Y* x! K6 f2 p

  2.   I# N! k5 i( q; L2 b
  3. #include "w25x16.h"0 _1 L4 [+ i( v4 l

  4. ; V, H* `* s- B' u. l8 G
  5. uint8_t RD_Buffer[5000] = {0};
    7 d3 I; D* L9 V. M& ^8 y
  6. uint8_t WR_Buffer[5000] = "SPI FLASH WRITE TEST\n";, |: S+ j$ `  R, S) b
  7. & T4 b5 z* P$ d2 {1 X" `
  8. int main(){
    ) l" U" ]' M5 x$ H
  9.     uint16_t FLASH_ID = 0;
    " [4 t, G0 M2 D7 j% e. y3 v
  10.     uint32_t i;
    - i& ?$ }6 K( M& ]4 {; o0 I+ j
  11.     % T2 K4 H* M9 J# Y+ N# Z( m
  12.     MX_GPIO_Init();! i& j9 J. g5 R0 h0 B1 A
  13.     MX_SPI1_Init();
    $ n9 c& b- d# x) N6 T/ k7 ^
  14.     FLASH_ID = sFLASH_ReadID();
    , F9 d8 k- `. x3 P
  15.    
    7 s# o4 L* b/ ?+ h5 A3 |" Q4 D
  16.     /******测试擦除******/
    ' Y% n/ U, u$ C8 A; d  N( \
  17.     sFLASH_EraseSector(4096*0);
    4 M8 e7 p( A& K. r/ y
  18.     //sFLASH_EraseSector(4096*1);
    - h! N# i8 h5 |, W% m+ q% J* ?  A& ~6 Y
  19.     sFLASH_ReadBuffer(RD_Buffer,0,4096);
    - m! p8 W. e" Q7 c# X
  20.     printf("读数据开始\n");
    : x' a, a7 q# Q6 t
  21.     for(i=0; i<4096; i++)" t  q1 B2 y1 v; h5 X  D9 C
  22.     {
    5 g- R+ K- K2 F# z
  23.     printf("%x ",RD_Buffer[i]);- X. ~4 c6 u; g% [- \" f) W1 B
  24.     }: A3 S$ _# p# x, j- n9 H
  25.     printf("读数据结束\n");, d; Z  ~$ T9 M$ d' R; A3 K
  26. 5 V' `6 |0 G% x
  27. ' ~0 i, H3 l# R' p: K& M; f
  28.     /******测试写操作1*****/
    , a5 e- i, x/ h1 ]" @% o* {
  29.     //写之前都需要擦除扇区; U5 p, i6 H" K& R6 k* Z
  30.     sFLASH_EraseSector(4096*0);
    ; C+ M2 ^5 a) d4 T8 z7 \
  31.     sFLASH_WritePage(WR_Buffer,0, 20);
      e- u9 r. @' {( S
  32.     sFLASH_ReadBuffer(RD_Buffer,0,20);
    6 D9 ^3 T. c) [- t  G6 ^2 g8 ^% I( f
  33.     printf("READ DATA: %s\n",RD_Buffer);! T" c0 p% ]: {& I. Z# Y
  34.    
    % u9 O" D' n7 k* Y# ~0 R+ b
  35.     /******测试写操作2*****/& B# X6 b* H* X4 W6 }# d' l
  36.     //写之前都需要擦除扇区
    1 M& [: X: I1 \$ k
  37.     sFLASH_EraseSector(4096*0);, m4 D% V. K* N
  38.     sFLASH_EraseSector(4096*1);                            & G. a5 V; j9 i" }
  39.     for(i=0; i<4096; i++)
    7 y/ V; x# k- N. ?
  40.     {
    5 t) s; Y% M( J# q
  41.     WR_Buffer[i] = 0x55;' s4 H, ~4 A$ s. g
  42.     }
    8 Q( H6 ]: b( K3 ]' ?( F
  43.     sFLASH_WriteBuffer(WR_Buffer,4090, 1000);- \% I% b: d# f
  44.     sFLASH_ReadBuffer(RD_Buffer,4090,1000);- T5 y( o4 ^2 z3 N
  45.     for(i=0; i<1000; i++)' `6 i# n: ~; b+ }6 X
  46.     {- }  v3 |* f9 [
  47.     printf("%x ",RD_Buffer[i]);
    " ^% T9 S* Y0 G. h8 d+ d! a( P
  48.     }; N  }3 U6 m, V9 l
  49.     /*****************/4 {2 a3 u6 d, q$ N3 X9 [2 E& P* l
  50.     while(){}
    ! |9 V+ s, k( g0 ?" v1 I8 F0 F
  51. }% l! \7 o# B1 n' P" W4 F
  52. //w25x16.h8 T! s" I; B+ ]. V2 `% i
  53. # i) D$ t/ \% h* |# D% \3 _5 T
  54. #ifndef __W25X16_H
    ( M  [5 v8 ^7 b. @9 S3 E% z
  55. #define __W25X16_H% N; o4 M5 E4 z* u5 c2 n  ~; E! x# W! j
  56. 3 `/ [' E  b3 w" b6 Y4 n. X
  57. #include "stm32f4xx_hal.h"
    0 a4 Q) V  P5 h, B4 Y* G4 M5 Q

  58. " f* K0 u6 N! |! _# z: v6 ~* B1 j# d
  59. //使用宏定义芯片指令
    + U4 `6 }9 r  D8 k' X
  60. #define W25X_ManufactDeviceID  0x90  /* Read identification */
    ( N1 G$ h3 U$ Q0 M8 b. |0 u
  61. #define sFLASH_CMD_WREN        0x06/* Write enable instruction */: F2 e8 x, o" z7 {
  62. #define sFLASH_CMD_RDSR        0x05/* Read Status Register instruction  */! z% Y9 [" _$ @8 r. D$ h/ G! T
  63. #define sFLASH_CMD_SE          0x20/* Sector Erase instruction */6 F3 n: w' x$ X! l( h; b4 B$ @
  64. #define sFLASH_CMD_WRITE       0x02  /* Write to Memory instruction */
    # j# |$ x7 K! M0 I1 @. P
  65. #define sFLASH_CMD_READ        0x03/* Read from Memory instruction */7 ]- b3 o8 o& X  X6 _
  66. #define sFLASH_DUMMY_BYTE      0x00 //空字节,用于只读传回来的数据
    2 `) ~6 Q3 x/ t& W7 s% q
  67. #define sFLASH_BUSY_FLAG       0x017 b' f+ S) D% i' I6 a+ z
  68. #define sFLASH_SPI_PAGESIZE    0x100' O3 u  Q' z" G, [5 w$ V* a8 M  O1 U

  69. ; u( ]# l7 y4 K/ y5 j
  70. /* 选中芯片,拉低信号 */2 A* V3 F, L5 `* ]# a
  71. #define sFLASH_CS_LOW()       HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_RESET)8 ^' v& e& Y5 b$ n
  72. /* 释放芯片,拉高信号 */* H$ J* U5 h% C2 Q! l
  73. #define sFLASH_CS_HIGH()      HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_SET)
    2 v, m$ z4 }/ l$ J; y# S7 V. [* n
  74. 6 V5 f4 @( ?' |
  75. //定义函数
      Y7 ~) K5 g8 q3 x
  76. uint8_t sFLASH_SendByte(uint8_t byte);
    & B% ]/ n* ?4 U, |2 y7 f! c+ Q8 d
  77. uint16_t sFLASH_ReadID(void);$ {$ |3 B# O- Z" L& H# ^. [3 \
  78. void sFLASH_EraseSector(uint32_t SectorAddr);
    ( {5 g, L, x/ Y" N3 Z( F
  79. void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);1 l" g" I7 }, z: o
  80. void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);! p+ D/ C$ g7 w" O, ^+ O
  81. void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead);
      `: s: ^2 }+ s% ?7 m; k
  82. #endif
复制代码
  1. //w25x16.c$ \9 [+ A  j6 o. _
  2. ( B/ M- J$ {# Y: h  A
  3. #include "w25x16.h"
    0 N+ k7 h6 N& k. {1 R0 O

  4. 6 M" A* O- C# ^, ], U/ o. q) I
  5. extern SPI_HandleTypeDef hspi1;
    1 Q* @$ t: _* s2 c( A+ f2 T
  6. ! ]8 l) i: a/ T' q- C& J' t
  7. /*读写一个字节函数,因为SPI读和写同时完成*/1 c; B' m: W( P9 `/ o- R: b
  8. /*发送数据一定会接收到一个数据*/
    1 f+ e, u4 s6 w0 f: u
  9. uint8_t sFLASH_SendByte(uint8_t byte)# s, ~3 ~2 l& b/ N& j% D+ F
  10. {9 p9 r6 g/ ^0 e9 _4 d
  11.     uint8_t TX_DATA = byte;
    / x- K( g9 P5 i8 j' F, B" f
  12.     uint8_t RX_DATA = 0;
    9 t7 H4 R0 A) `% e# w
  13.     HAL_SPI_TransmitReceive(&hspi1, &TX_DATA ,&RX_DATA , 1, 1000);" {$ i5 X: T$ N* ]: J! L
  14.     return RX_DATA;
    5 {5 m4 R2 D6 ~* `8 l
  15. }9 O1 h, r3 a' t: a' t
  16. * V% j" S5 B. w" e8 V( v
  17. /*等待擦除或者写数据完成*/
    4 {1 F/ e6 _7 ^7 q& Q/ a+ _
  18. void sFLASH_WaitForEnd(void)
    8 y3 a1 z, f( g
  19. {* V: O7 f5 b) ~4 P5 `
  20.     uint8_t sr_value = 0;5 J$ `4 j+ _8 E3 X! O/ T
  21.     sFLASH_CS_LOW();
    5 O& V% j8 K' |. |# h/ t; Q4 M
  22.     sFLASH_SendByte(sFLASH_CMD_RDSR);6 T& q  _9 I  f' C# _
  23.             //读S0的值,为1表示忙碌,为0表示停止         
    7 Z8 f! z! d! L+ }
  24.     do{
    ! M. V* Y% V. _8 \& V
  25.                 //发一个空字节,得到S0的值            
      L4 V5 Z0 F; w
  26.                 sr_value = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    7 ~% ^- p7 E' b' q& E5 E
  27.     }while( sr_value & sFLASH_BUSY_FLAG);% _$ \# N2 }$ v! N
  28. - r- ]% y0 d  C7 X2 k- ?' \' j
  29.     sFLASH_CS_HIGH();* v! X) Q2 k# J: A7 O$ @% o
  30. }/ c7 c  D; _$ _5 k7 y6 z. `( ~
  31. # B3 h4 v9 x, f( F
  32. void sFLASH_WriteEnable(void)/ H- X4 T4 B7 w' c+ C2 i
  33. {
    3 I8 f- }- u3 ?: |1 i" O' u  C
  34.     sFLASH_CS_LOW();4 B# y# L+ y2 @  h* g
  35.    
    ' T1 Y3 m4 x' _: Z
  36.     sFLASH_SendByte(sFLASH_CMD_WREN);, q. H4 ?- B# p7 A) r
  37.     2 J, W+ L- X8 M7 l6 \+ @8 |  I
  38.     sFLASH_CS_HIGH();* c9 r4 t  z# h4 e
  39. }
    ; t5 w% A5 T& ^2 Z6 c- P3 v- ~
  40. * a! n: T5 e1 }  u# c/ f
  41. ! v5 ]( D; O( m1 Z
  42. /*读设备ID*/ . r! F  |1 ~! c
  43. uint16_t sFLASH_ReadID(void)
    - ^  k0 e/ n2 k; Q. W. \+ H
  44. {/ D% j+ E6 H. v
  45.     uint16_t FLASH_ID;
    & f3 S# G- Y% ]9 P
  46.     uint8_t temp0,temp1;
    # t) t2 I) |  Y. }& z8 F0 Z
  47.     / |! _) A/ W9 T  ?
  48.    
    . H2 D- N9 N" f
  49.     sFLASH_CS_LOW();/ c+ A0 j% Y4 m. j* R
  50.    
    : Z1 z% C  L' f/ @9 M
  51.     sFLASH_SendByte(W25X_ManufactDeviceID);
    5 Z* b( u6 ^: _3 S0 h
  52.     //读设备指令后要发24位地址,所以要发三次
    7 j+ E' V7 |$ g4 I
  53.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);* g5 e3 P5 o! O* w  ^$ `. j
  54.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);" B  i4 u0 ?/ ~
  55.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);' K( {. G& V9 x; ^1 p
  56.     //制造商ID
    . \8 @4 m5 e2 E, p( z' @* {( {/ P
  57.     temp0 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);: `$ D6 H# V% @0 B" {. ]* b5 t
  58.     //设备商ID7 q9 z! w3 {0 [5 C& f' V, q8 y! M
  59.             temp1 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);& D  Y* y5 w/ c; u3 U' }  c
  60.    
    ! r# w  ~% N% j
  61.     sFLASH_CS_HIGH();+ i% J! O) M2 }" j9 z9 \8 g2 V% @3 B
  62.    
    6 K6 n; B' B4 R; Z
  63.     FLASH_ID = (temp0 << 8) | temp1;
    8 N; i& G" p* E0 L
  64.    
    - f; H$ w6 G- Z+ I+ i
  65.     return FLASH_ID;
    3 z# o; h* q' b7 G
  66. }/ j/ s5 X) B$ d7 k8 v; m
  67. 9 G0 ~# _3 N3 ]! X
  68. //擦除扇区,擦除为1,因为只能由1变为0 ,不能0变18 n: J  [  m. T% \& [, a
  69. void sFLASH_EraseSector(uint32_t SectorAddr)" L+ u5 u$ u( c& c5 l, n3 {. u+ e
  70. {3 h$ H: @3 D/ p5 r/ \( d7 X
  71.     //SectorAddr表示擦除第几个扇区! `. U9 F* J5 ^7 b0 L
  72.     sFLASH_WriteEnable();  //开启写使能
    ! x( D# w" ?7 w# V
  73.     sFLASH_CS_LOW();//拉低,片选! Q8 ~+ T; J1 ^3 S4 c
  74.     //擦除命令
    9 S3 p' n! M: x, |$ O+ S( [
  75.     sFLASH_SendByte(sFLASH_CMD_SE);# Y& k% \% r4 t5 J3 w
  76.             //传24位地址! v5 F1 G# F/ j! P! k* r# F
  77.             //传送高8位,将中8位和低8位一共16位移出去,得到高8位                  
    2 d. h- ]7 Q+ W$ c, F. \1 i
  78.     sFLASH_SendByte( (SectorAddr>>16) & 0xff);   
    - H: n' m  `0 a. E' p2 h4 N8 ]* g1 c
  79.     sFLASH_SendByte( (SectorAddr>>8) & 0xff);    //传送中8位8 [. H4 ?! g9 M9 D
  80.     sFLASH_SendByte( (SectorAddr>>0) & 0xff);    //传送低8位: C! }# R% n" v/ b9 C
  81.      4 ^) t/ ?& K0 q. H( V
  82.     sFLASH_CS_HIGH();5 i! h+ L0 u3 @7 h6 z6 b' g/ E
  83.    
    0 R. h% v1 S: b2 o$ E0 A$ b7 ^( A% E
  84.     /*读状态寄存器,等待擦除完成*/
    6 ]# K$ L. ?! f6 g
  85.     sFLASH_WaitForEnd();+ a2 U$ {# G! M& `$ O! m8 i8 a4 B
  86. }
    4 n$ W4 `9 L- z' ?! n$ {! m1 Q
  87.   I1 h6 X, D+ }$ B/ H6 N% p
  88. //读数据
    ! J8 {1 Y4 P2 c1 {$ s0 p
  89. //读命令和读地址发送后,芯片内部会自动不断递增读数据
    , |0 H* B0 u) ?% F2 L; ~
  90. void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead)
    ; u7 }6 j- b# M: V3 _5 f! F" P7 p
  91. {
    , M8 H) t6 d; C+ D/ n+ G
  92.     sFLASH_CS_LOW();
    - D* t5 G# s  J# R  K
  93.     , N" j: v- q- s! ]% b7 z  h
  94.     sFLASH_SendByte(sFLASH_CMD_READ);( m( k& f" R8 a
  95.     sFLASH_SendByte( (ReadAddr>>16) & 0xff);   //传送高8位0 [0 u  n/ H, l* E7 D4 b
  96.     sFLASH_SendByte( (ReadAddr>>8) & 0xff);    //传送中8位
    9 r3 e( |$ H) k/ y
  97.     sFLASH_SendByte( (ReadAddr>>0) & 0xff);    //传送低8位
    # _1 H, X6 m6 y( h4 R3 t8 |
  98.     * C1 s/ y8 X* W+ v+ v$ x$ }
  99.     while(NumByteToRead--)
    8 J, k% h: U2 J/ N* X
  100.     {
    1 y' h  B, N2 g  I: u4 |/ P
  101.     * pBuffer = sFLASH_SendByte(sFLASH_DUMMY_BYTE);( o/ [; \: a9 M& W1 j
  102.     pBuffer++;
    * F9 c; g: c4 ~$ S( I
  103.     }& q; Y2 `$ I1 Z- L
  104.     1 z/ ^$ W* A% a
  105.     sFLASH_CS_HIGH();* x" n2 |, j! h/ I8 C
  106. }
    3 d' T. F8 C& Y. J/ I
  107. 8 p7 |' f$ q7 K- }
  108. //写一页最多只能写256个字节,一个扇区16页,一个块16个扇区                               : d7 B/ s+ _; ^4 e9 U3 z2 [
  109. void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)- B2 H& i& r$ v" D/ I, `
  110. {# W+ S+ w% [" ~1 g+ v$ V3 {
  111.     if(NumByteToWrite > sFLASH_SPI_PAGESIZE )* u$ v3 \1 x6 q/ b+ G# j4 J
  112.     {$ I. X! J6 n& p6 ?0 X- a
  113.     NumByteToWrite = sFLASH_SPI_PAGESIZE;; L1 F1 m. j5 O  T1 q' w
  114.       r7 ?5 h! s$ S5 s/ T" t8 o
  115.     printf("写数据量过大,超过一页大小\n");
    1 V, H/ b5 L! v9 L8 l7 r
  116.     }: k/ @* ~6 \$ t- m6 V# d. V
  117.    
    2 A+ C4 L( c# ~7 ?7 O4 y
  118.     sFLASH_WriteEnable();  //开启写使能" k* z$ {) k  R; n! X) v
  119.     . d" `3 X2 K/ a# t9 {7 g& n% v
  120.     sFLASH_CS_LOW();
    8 J3 A" ^5 k* C* w
  121.    
    ( Y& V3 g* {6 T9 t8 @# t2 ]
  122.     sFLASH_SendByte(sFLASH_CMD_WRITE);$ H2 t. @1 S2 o* J9 y3 [9 ]- H
  123.     sFLASH_SendByte( (WriteAddr>>16) & 0xff);   //传送高8位4 }/ m0 u5 Z$ d
  124.     sFLASH_SendByte( (WriteAddr>>8) & 0xff);     //传送中8位
    4 k4 ^  V- F% I( X$ `2 e1 w. Z
  125.     sFLASH_SendByte( (WriteAddr>>0) & 0xff);      //传送低8位
    & |4 u4 Z* @: V
  126.     5 r5 M9 b- B; f! O  `
  127.     while(NumByteToWrite--)
    ! a, m/ `7 _/ G) p+ q
  128.     {2 l2 U: }0 {/ T' Y' |
  129.     sFLASH_SendByte(* pBuffer);
    # e; P9 c3 X3 k
  130.     pBuffer++;8 q. {  q  S+ I; x, ]7 }
  131.     }  Z7 K! l) o- Z5 Z$ t. q/ d2 o% ^
  132.    
    2 @" P  @( m( J" m9 N( ?; P& I& a$ }
  133.     sFLASH_CS_HIGH();) E' d6 y0 n# W, S$ S
  134.    
    * H* M' `. E7 J8 |" S
  135.     /*擦除和写数据都涉及到写动作,一定要等待完成*/) k1 a" [0 C. z; o% b2 ]
  136.     sFLASH_WaitForEnd();/ |  v& _" J9 _5 h
  137. }0 _/ \+ @3 O+ C+ Q' w; @

  138. ( K1 B; F4 a* I$ A2 X/ h
  139. //写任意地址、任意长度
    . M, p3 \1 K2 D3 g: M% y# z$ m
  140. void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
    : t5 S1 _: g' t5 e9 _& C: E& q
  141. {& E  q) K3 Q3 Z# |
  142.     uint16_t NumOfPage, NumOfBytes, count, offset;1 Q3 k4 r9 L4 S( ^. f& Q, e
  143.     //求WriteAddr在某一页的位置        
      ~5 b, T! O4 l2 U! N, O! _# i- [
  144.     offset = WriteAddr % sFLASH_SPI_PAGESIZE;
    / W* I# s. v( P/ n* Q
  145.     //求某一页剩余的大小 / Z, h( \2 e2 g' J
  146.     count = sFLASH_SPI_PAGESIZE - offset;! W/ q; r' _4 O+ \
  147.    
    & ?) h0 `- B0 `& I' q- ~
  148.     /*处理页不对齐的情况,防止页内覆盖*/
    ' [% s$ y" s2 E* i8 A! F
  149.     //先把某一页剩下的部分写掉,之后的就能新页的起始处开始写        /*offset有值表示需要页对齐,如果要写的字节数小于某一页剩余的部分,那就无需对齐*/                  
    8 ^2 u# N0 c" C. ^- f
  150.     /*这两个条件必须同时满足*/
    % r! E% A9 ^3 a, x/ I' [, i/ |  z
  151.     if(offset && (NumByteToWrite > count ))' G; X  B/ B4 T* l; W( m
  152.     {
    1 B. p2 ~% L4 L8 b$ o4 c' F! C! D3 v
  153.     sFLASH_WritePage(pBuffer,WriteAddr,count);2 D' k! i& \' Y
  154.     NumByteToWrite -= count;//去掉已经写了的,从新页开始' K- Q0 u4 o7 ?; S+ j9 o
  155.     pBuffer += count;
    " p+ z- C' v8 v
  156.     WriteAddr += count;
    " p1 J3 N8 [1 d* J" F) j, R1 o+ H
  157.     }. O" X2 M8 Q# Y. }) y6 @
  158.     ) S$ P+ D- N' C% j9 r
  159.     /*最多可分多少页*/
    9 C; K' A& W" X2 J& g
  160.     NumOfPage = NumByteToWrite / sFLASH_SPI_PAGESIZE;* T8 j( ^! Z& P
  161.     /*剩余多少字节*/
    $ a% g. E$ R. o; k/ Z# z9 n
  162.     NumOfBytes = NumByteToWrite % sFLASH_SPI_PAGESIZE;: \$ I$ f4 {* t1 A# ^5 b& ~: g% c
  163.    
    - [5 ]2 q7 W% @: w3 O$ ~. P; P1 u
  164.     if(NumOfPage)* N6 q0 R) m# o; u0 V. n! I4 h/ I
  165.     {
    5 e; l5 _# w7 a1 ^
  166.         while(NumOfPage--)
    , l% I# K' E9 g! N; v
  167.         {! I( Q. c0 c; [
  168.              //每一页都发起页编程7 I8 B: Z. d2 |$ ]  D2 L
  169.              sFLASH_WritePage(pBuffer,WriteAddr,sFLASH_SPI_PAGESIZE);; X5 b7 i1 S; t* Y0 D/ p
  170.              pBuffer += sFLASH_SPI_PAGESIZE;" b' W! H" U8 s% V% n8 f# _: M4 Y
  171.      WriteAddr += sFLASH_SPI_PAGESIZE;0 h4 K* B4 f- d
  172.          }
    8 {: c% A1 F' \8 C& p
  173.     }
    6 F* n* B# q5 v9 f5 B
  174.    
    ) V& A" E0 T  ?/ }) y
  175.     if(NumOfBytes)9 d1 w1 r/ ~! {+ Z' c+ w  ^( x
  176.     {
    6 }4 y- f- ?. ?# |) P
  177.     sFLASH_WritePage(pBuffer,WriteAddr,NumOfBytes);
    3 L7 `' F1 [7 V6 d- W& b% s! @* ^
  178.     }) F% d. ~8 A1 b2 p/ h: S$ D$ h
  179. }
复制代码
) J1 e. }# c0 a- b" U. B
  • 为什么会有两种写操作函数,是因为这里的写操作有两个特点:
    & W8 S  _* R& {* _( [! ?
    6 _5 \: [* f# p9 S3 j
1.无法突破页限制,超过一页需要重新发起页编程信号。另外如果要写的数据大于剩余一页剩余的容量,那么超出的数据会写到当前页起始地址出。例如,初始输入的写地址为200,而要写的数据大小为100,那么要写的前56个字节会从地址200开始依次写入,剩下的44个字节会从当前页的0地址开始依次写入,这很有可能覆盖之前的数据。
8 g+ P& s) F, l( h; ?/ V0 n
2.无法突破扇区的限制,当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。

' E3 ?/ v1 q% b  k: o转载自:骆驼听海5 _( z1 Y8 J" |/ q$ B5 s0 \' _
8 A$ Z* v4 \0 Q7 y
赞 收藏 评论0 发布时间:2022-10-11 23:44

举报

0个回答

所属标签

相似技术帖

官网相关资源

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