STM32中的SPI

SPI简介

接口、工作原理与传输方式

SPI接口共有四个Pin:

  • MISO(Master In / Slave Out)主设备数据输入,从设备数据输出。
  • MOSI(Master Out / Slave In)主设备数据输出,从设备数据输入。
  • SCLK(Serial Clock)时钟信号,由主设备产生。
  • CS(Chip Select)从设备片选信号,由主设备产生。

SPI的工作原理:在主机和从机都有一个串行移位寄存器,主机通过向它的 SPI 串行寄存器写入一个字节来发起一次传输。串行移位寄存器通过 MOSI 信号线将字节传送给从机,从机也将自己的串行移位寄存器中的内容通过 MISO 信号线返回给主机。这样,两个移位寄存器中
的内容就被交换。外设的写操作和读操作是同步完成的。如果只是进行写操作,主机只需忽略接收到的字节。反之,若主机要读取从机的一个字节,就必须发送一个空字节引发从机传输。

SPI的传输方式:

  • 全双工:任何时刻,主机与从机之间都可以同时进行数据的发送和接收
  • 单工:数据传输是固定单向的
  • 半双工:允许数据在两个方向上传输,但同一时刻只能传输一个方向的数据(如IIC)

SPI依据CPOL和CPHA的不同状态分成了4种模式。其中CPOL和CPHA分别是:

  • CPOL(Clock Polarity,时钟极性):定义为主从机没有数据传输时,SCL线的电平状态。假如空闲状态下SCL线为高电平,则COPL=1;若为低电平,则COPL=0
  • CPHA(Clock Phase,时钟相位):数据采样时刻,即在时钟信号的哪个边沿上数据被传输或采样。若CPHA=0,则数据在时钟的第一个边沿(即时钟信号的上升沿或下降沿)采样,数据在第二个边沿(另一个相反的边沿)被传输;若CPHA=1,则数据在时钟的第二个边沿(与CPHA = 0时不同)采样,数据在第一个边沿传输。

按照CPOL和CPHA的不同,SPI的四种模式分别为:

SPI工作模式 CPOL CPHA SCL空闲状态 采样边沿 采样时刻
0 0 0 LOW 上升沿 奇数边沿
1 0 1 LOW 下降沿 偶数边沿
2 1 0 HIGH 下降沿 奇数边沿
3 1 1 HIGH 上升沿 偶数边沿

实际应用中,通常使用模式0模式3,即都在奇数边沿采样。

SPI寄存器

SPI通常需要用到以下16位寄存器:

  • SPI控制寄存器(SPI_CR1):控制主设备模式选择,传输方向,数据格式,时钟极性、时钟相位和使能等。
  • SPI状态寄存器(SPI_SR):用于查询SPI当前状态
  • SPI数据寄存器(SPI_DR):双寄存器,包含发送缓存与接收缓存

NOR Flash (W25Q256)

简介

NOR Flash 是一种非易失性存储器,广泛应用于嵌入式系统中,尤其是在需要快速读取数据的场景。它与 NAND Flash 的主要区别在于存储单元的结构和读写性能。NOR Flash 使用并行的地址和数据总线,其存储单元是基于 NOR 门(一个逻辑门)。这种结构使得它能够按字节(Byte)直接读取数据。由于 NOR Flash 支持按字节随机访问,因此在读取速度上优于 NAND Flash。它适合于存储固件、代码等经常读取的应用。其写入操作较慢,通常需要先将整个块(block)擦除后才能进行写入。每个单元的擦写次数有限,通常为数万到数十万次,因此在需要频繁写入的场景中,NOR Flash 的耐用性较差。NOR Flash 主要应用于嵌入式系统,如存储引导程序、固件(BIOS)等。它也广泛应用于汽车、智能设备、消费电子等领域,用作存储启动代码、配置数据等。对于低功耗设备,NOR Flash 适合用于电池供电且不频繁写入的应用。总的来说,NOR Flash 主要优势是其快速的读取能力,适合存储代码和配置等经常读取的内容,而写入和擦除操作较为缓慢。

NOR与NAND在数据写入前都需要有擦除操作。

NM25Q128 支持标准的 SPI,还支持双输出/四输出的 SPI,最大 SPI 时钟可以到 104Mhz(双输出时相当于 208Mhz,四输出时相当于 416M)。

W25Q256 IC引脚

  • CS:片选引脚,低电平有效
  • DO:MISO引脚
  • WP:写保护引脚。高电平时可读可写,低电平时只读
  • DI:MOSI引脚
  • CLK:串行时钟引脚
  • HOLD:保持引脚,低电平有效

对于W25Q256的存储架构而言,从大到小可以分为这么几级:

  • 256个块(Block),每个64K字节
    • 每个块分为16个扇区(Sector),每个4K字节
      • 每个扇区分为16个页(Page),每个256字节

W25Q256的最小擦除单位为一个扇区,也就是每次必须擦除4K个字节。这样我们需要给W25Q256开辟一个至少4K字节的缓存区,这样对SRAM要求比较高,要求芯片必须有4K以上的SRAM才能很好的操作。

状态寄存器(Status Register)

W25Q256拥有3个状态寄存器,分别为SR1,SR2和SR3.具体作用可参见手册7.1节。下面是比较重要的一些寄存器位:
SR1

  • S0:BUSY位,只读。置1表示设备忙(有操作正在进行),置0表示设备空闲,准备好接收其他指令。
  • S1:写使能锁存器(WEL),只读。置1表示写使能,置0表示写禁用。

SR3

  • S17:ADP位。置0时,设备启动将会引导至3字节地址模式,该模式下若要访问128Mb以外的flash则必须启用附加地址寄存器(Extended Addr Reg);置1时,设备启动引导至4字节地址模式。

指令与工作时序

写使能(Write Enable)- 06H

写使能指令用于将SR1的WEL位置1.该指令必须在所有页操作(Page Program)、扇区擦除、块擦除、芯片擦除和写入状态寄存器操作前调用。

写使能指令时序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 /**
* @brief IC 写使能 Write Enable
* @param NULL
* @retval NULL
*/
void w25q256_write_enable(void)
{
NORFLASH_CS(0);

uint8_t code = 0x06;
HAL_SPI_Transmit(&hspi5, (uint8_t *)&code, 1, 1000);

NORFLASH_CS(1);
}

写禁用(Write Disable) - 04H

该指令会将SR1-WEL置0.一般情况下,WEL位会在所有页操作(Page Program)、扇区擦除、块擦除、芯片擦除、写入状态寄存器操作和IC复位后自动置为0.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 /**
* @brief IC 关闭写使能 Write Disable
* @param NULL
* @retval NULL
*/
void w25q256_write_disable(void)
{
NORFLASH_CS(0);

uint8_t code = 0x04;
HAL_SPI_Transmit(&hspi5, (uint8_t *)&code, 1, 1000);

NORFLASH_CS(1);
}

读取状态寄存器 - 05H/35H/15H

该指令用于读取状态寄存器中的值,05H用于读取SR1,35H读取SR2,15H读取SR3.该指令可以在任何时候被调用,通过位操作用于确认BUSY位是否为0(&0x01),以确认IC是否准备好接收另一条指令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
* @brief 读取IC状态寄存器 Read Status Register
* @note SR-1
* BIT7 6 5 4 3 2 1 0
* SPR RV TB BP2 BP1 BP0 WEL BUSY
* SPR:默认为0,保护位,配合WP使用
* TB,BP2,BP1,BP0:FLASH区域写保护设置
* WEL:写使能锁定
* BUSY:忙标记为(1:忙;0:空闲)
* 默认:0x00
*
* SR-2
* BIT7 6 5 4 3 2 1 0
* SUS CMP LB3 LB2 LB1 (R) QE SRP1
*
* SR-3
* BIT7 6 5 4 3 2 1 0
* HOLD/RST DRV1 DRV0 (R) (R) WPS ADP ADS
* @param sr:状态寄存器编号
* @retval rxbyte:接收到的8位寄存器值
*/
uint8_t w25q256_read_status_reg(uint8_t sr)
{
uint8_t rxbyte, readsr = 0;

NORFLASH_CS(0);

switch (sr)
{
case 1:
readsr = 0x05; /* SR1读指令 */
break;

case 2:
readsr = 0x35; /* SR2读指令 */
break;

case 3:
readsr = 0x15; /* SR3读指令 */
break;
}

HAL_SPI_Transmit(&hspi5, (uint8_t *)&readsr, sizeof(readsr), 1000); /* 主机发送对应指令 */
HAL_SPI_Receive(&hspi5, (uint8_t *)&rxbyte, sizeof(rxbyte), 1000);

NORFLASH_CS(1);

return rxbyte;
}
1
2
3
4
5
6
7
8
9
/**
* @brief 等待空闲
* @param NULL
* @retval NULL
*/
static void norflash_wait_busy(void)
{
while ((w25q256_read_status_reg(1) & 0x01) == 0x01); /* 等待BUSY位清空 */
}

进入/退出4字节地址模式 - B7H/E9H

4字节地址启用时,存入/读取数据时寻址地址为32位而非24位,可以访问128Mb以外的Flash。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @brief 进入4字节地址模式 Enter 4-Byte Address Mode
* @note 允许使用32位地址以访问128Mb以外区域
* @param NULL
* @retval NULL
*/
void w25q256_enter_4byte_mode(void)
{
uint8_t code = 0xB7;

NORFLASH_CS(0);
HAL_SPI_Transmit(&hspi5, &code, 1, 1000);
NORFLASH_CS(1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
 /**
* @brief 退出4字节地址模式 Exit 4-Byte Address Mode
* @note 非4字节地址模式下要访问128Mb以外区域需要使用拓展地址寄存器(Extended Addr Reg)
* @param NULL
* @retval NULL
*/
void w25q256_exit_4byte_mode(void)
{
uint8_t code = 0xE9;

NORFLASH_CS(0);
HAL_SPI_Transmit(&hspi5, &code, 1, 1000);
NORFLASH_CS(1);
}

读操作(32位地址模式) - 13H

W25Q256 读操作(32位地址模式)时序
读操作时序包含以下步骤:

  1. 拉低CS电平,激活片选
  2. MOSI发送13H,然后发送待读取32位地址
  3. 从机在CLK下降沿从MISO发送对应地址的数据
  4. 拉高CS电平,传输停止
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 /**
* @brief 读取数据(32位地址模式) Read Data(4Byte Addr Mode)
* @note 参见datasheet 8.2.10
* @param pbuf:接收缓存区地址
* @param addr:目标地址
* @param datalen:接收长度(字节)
* @retval NULL
*/
void w25q256_read_data_4byte_mode(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
uint8_t read_data_code = 0x13;

NORFLASH_CS(0);
HAL_SPI_Transmit(&hspi5, &read_data_code, 1, 1000); /* 发送32位模式读指令 */
HAL_SPI_Transmit(&hspi5, (uint8_t *) &addr, 4, 1000); /* 发送32位目标内存地址 */
HAL_SPI_Receive(&hspi5, pbuf, datalen, 1000); /* 接收读取值 */
NORFLASH_CS(1);
}

页编程(Page Program) - 02H

参见:datasheet 8.2.25
W25Q256 页写操作时序

页面编程指令允许在先前擦除的 (FFh) 内存位置对 1 字节到 256 字节(一页)的数据进行编程。必须先执行写使能指令,设备才会接受页面编程指令(WEL= 1)。

如果要编程整个 256 字节页面,则最后一个地址字节(8 个最低有效地址位)应设置为 0。如果最后一个地址字节不为零,并且时钟数超过剩余页面长度,则寻址将绕回到页面的开头。在某些情况下,可以编程少于 256 个字节(部分页面),而不会对同一页面内的其他字节产生任何影响。

注意:执行部分页面编程的一个条件是时钟数不能超过剩余的页长度。如果向设备发送了超过 256 个字节,寻址将绕回到页面的开头并覆盖先前发送的数据。

1

W25Q256 扇区擦除时序
W25Q256扇区大小为4K字节,擦除扇区后所有扇区位全部置1,即扇区字节为FFH

  1. 主机发送“写使能”指令
  2. 确定SPI总线状态:读取状态寄存器的BUSY位,等待BUSY位为0后,继续下一步
  3. 主机拉低CS引脚
  4. 主机从MOSI发送20H到从机,然后发送24位扇区地址
  5. 主机拉高CS引脚,读取寄存器状态,等待扇区擦除完成

W25Q256还有整个IC擦除的操作,只需发送C7H到IC即可实现整片擦除。


STM32中的SPI
http://akichen891.github.io/2024/12/17/STM32中的SPI/
作者
Aki
发布于
2024年12月17日
更新于
2024年12月19日
许可协议