Skip to content

Flash(SPI)

Serial Peripheral Interface串行外设设备接口

是一种全双工的协议, 传输的速率比较较高

image-20230705092142745

SS(CS): 设备选择线, 片选信号, 每一个从机都有一根SS线, 低电平表示选中

SCK: 时钟, 用于信号的同步, STM32支持的最大频率为F~pclk~/2

MOSI: 主机输入, 从机发送

MISO: 主机发送, 从机接收

SPI1在APB2上面, SPI2/3在APB1上面

image-20230705092906490

高位字节先行

image-20230705093308220

时钟相位: 设备处于空闲状态的时候, SCK时钟线的电平状态, CPHA指数据采样的时刻, CPHA=0的时候会在奇数边沿采样, CPHA=1的时候会在偶数边沿采样

image-20230705094033124

常用的是模式0和3

image-20240101100448303

STM32的实现

SPI1最高36MHz, SPI2/3最高18MHz, 支持上面的四种模式, 数据可以是8位或16位, 也可以控制高位先行还是低位先行

还可以两根线传输同方向

SPI2/3可以作为I2S通讯, SPI3的引脚和下载器共用引脚

image-20230705094536352

image-20240101095846457

image-20240101095903088

image-20230705094925096

image-20240101100040941

image-20230705102243699

TXE表示发送缓冲区为空, 可以写入

RXNE表示受到一个数据, 可以读取

在接收的时候需要写入从而产生时钟

寄存器

image-20240101100559293

  • SPI_CR1: 控制寄存器

image-20230705101748310

image-20230705101843244

image-20240101101413740

image-20230705102036757

image-20230705101616487

  • SPI_CR2: 控制寄存器

image-20230705100758700

  • SPI_SR: 状态寄存器

image-20240101101615769

  • SPI_DR: 数据寄存器

image-20230705100548326

  • SPI_CRCPR: 包含CRC计算数值

  • SPI_RXCRCR: SPI Rx CRC寄存器

CRC校验用于保证全双工通信的可靠性。数据发送和数据接收分别使用单独的CRC计算器。通 过对每一个接收位进行可编程的多项式运算来计算CRC。CRC的计算是在由SPI_CR1寄存器中 CPHA和CPOL位定义的采样时钟边沿进行的。 注意: 该SPI接口提供了两种CRC计算方法,取决于所选的发送和/或接收的数据帧格式:8位数据帧采 用CR8;16位数据帧采用CRC16

标准库

c
typedef struct
{
  uint16_t SPI_Direction;           /*!< Specifies the SPI unidirectional or bidirectional data mode.
                                         This parameter can be a value of @ref SPI_data_direction 
                                         控制使用的模式, 几根线以及传输方向*/

  uint16_t SPI_Mode;                /*!< Specifies the SPI operating mode.
                                         This parameter can be a value of @ref SPI_mode控制主机还是从机 */

  uint16_t SPI_DataSize;            /*!< Specifies the SPI data size.
                                         This parameter can be a value of @ref SPI_data_size
                                         控制数据的位数*/

  uint16_t SPI_CPOL;                /*!< Specifies the serial clock steady state.
                                         This parameter can be a value of @ref SPI_Clock_Polarity
                                         不用的时候时钟线的电平*/

  uint16_t SPI_CPHA;                /*!< Specifies the clock active edge for the bit capture.
                                         This parameter can be a value of @ref SPI_Clock_Phase 
                                         在第几个边沿进行采集*/

  uint16_t SPI_NSS;                 /*!< Specifies whether the NSS signal is managed by
                                         hardware (NSS pin) or by software using the SSI bit.
                                         This parameter can be a value of @ref SPI_Slave_Select_management
                                         软件还是硬件控制片选*/
 
  uint16_t SPI_BaudRatePrescaler;   /*!< Specifies the Baud Rate prescaler value which will be
                                         used to configure the transmit and receive SCK clock.
                                         This parameter can be a value of @ref SPI_BaudRate_Prescaler.
                                         @note The communication clock is derived from the master
                                               clock. The slave clock does not need to be set. 时钟
                                               分频因子*/

  uint16_t SPI_FirstBit;            /*!< Specifies whether data transfers start from MSB or LSB bit.
                                         This parameter can be a value of @ref SPI_MSB_LSB_transmission
                                         MSB还是LSB先行*/

  uint16_t SPI_CRCPolynomial;       /*!< Specifies the polynomial used for the CRC calculation.CRC校验 */
}SPI_InitTypeDef;

FLashW25Q64

8M字节, nor flash

image-20230705113847085

DO: MISO

DI: MDIO

/CS: 片选

/WP: 写保护

/HOLD: 暂停通讯

CLK: 时钟

Standard SPI instructions use the unidirectional DI (input) pin to serially write instructions, addresses or data to the device on the rising edge of the Serial Clock (CLK) input pin. Standard SPI also uses the unidirectional DO (output) to read data or status from the device on the falling edge CLK. (可以使用模式0或者模式3)

image-20230705120249483

分为127个块, 每个块64kb, 块又分为16个扇区, 每一个4kb, 在写入的数据之前需要进行擦除, 只能把为1的数据位改为0, 擦除的时候只能按照最小的单元进行, 在写入的时候没有限制, 为nor Flash, nand Flash需要以扇区进行写入

有一个状态寄存器, BUSY位为忙状态

image-20230705123108519

  • 实际读写

image-20230705123255295

image-20230705173533798

带括号的为返回值

擦除, 0x20之后发送三字节的地址

写入, 0x02

image-20230705124123214

几个id可以读取, 用来验证是否连接成功

image-20230705124036127

开发板情况

image-20230705135254082

指南者CS连接PC0

实际实现

__I, __O, __IO: 控制编译器不会进行优化, 每次读取的时候都会从内存中获取数据

在写\擦除之前需要进行写使能

c
#ifndef __BSP_SPI_H__
#define __BSP_SPI_H__

#include "stm32f10x.h"
#include <stdio.h>

#define  FLASH_SPIx                    SPI1
#define  FLASH_SPIx_CLK                RCC_APB2Periph_SPI1
#define  FLASH_SPIx_APBxClkCmd         RCC_APB2PeriphClockCmd
#define  FLASH_SPIx_BAUDRATE           400000


#define SPIT_PLAG_TIMEOUT				((uint32_t)0x1000)


// USART GPIO 引脚宏定义
#define  FLASH_SPIx_GPIO_CLK           (RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOA)
#define  FLASH_SPIx_GPIO_APBxClkCmd    RCC_APB2PeriphClockCmd


#define  FLASH_SPIx_MISO_GPIO_PORT       	GPIOA
#define  FLASH_SPIx_MISO_GPIO_PIN        	GPIO_Pin_6
#define  FLASH_SPIx_MOSI_GPIO_PORT       	GPIOA
#define  FLASH_SPIx_MOSI_GPIO_PIN        	GPIO_Pin_7
#define  FLASH_SPIx_CLK_GPIO_PORT       	GPIOA
#define  FLASH_SPIx_CLK_GPIO_PIN        	GPIO_Pin_5

#define  FLASH_SPIx_CS_GPIO_PORT       	GPIOC
#define  FLASH_SPIx_CS_GPIO_PIN        	GPIO_Pin_0
//控制CS引脚
#define FLASH_SPI_CS_HIGH			GPIO_SetBits(FLASH_SPIx_CS_GPIO_PORT, FLASH_SPIx_CS_GPIO_PIN);
#define FLASH_SPI_CS_LOW			GPIO_ResetBits(FLASH_SPIx_CS_GPIO_PORT, FLASH_SPIx_CS_GPIO_PIN);

/*信息输出*/
#define FLASH_DEBUG_ON         1

#define FLASH_INFO(fmt,arg...)           printf("<<-FLASH-INFO->> "fmt"\n",##arg)
#define FLASH_ERROR(fmt,arg...)          printf("<<-FLASH-ERROR->> "fmt"\n",##arg)
#define FLASH_DEBUG(fmt,arg...)          do{\
                                          if(FLASH_DEBUG_ON)\
                                          printf("<<-FLASH-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
                                          }while(0)
//无意义产生时钟数据
#define DUMMY								0x00
										  
//命令
#define READ_IEDEC_ID						0x9f												  
#define ERASE_SECTOR						0x20												  
#define READ_STATUS							0x05										  
#define READ_DATA							0x03										  
#define WRITE_ENABLE						0x06

										  
void Flash_SPI_Config(void);
uint8_t Flash_SPI_Send_Receive_Byte(uint8_t data);
uint8_t SPI_FLASH_Read_Byte(void);
uint32_t SPI_Read_ID(void);
void Flash_SPI_WatiFanish(void);
void Flash_ERASE_SECTOR(uint32_t addr);
void Flash_READ_SECTOR(uint32_t addr, uint8_t *buff, uint32_t numByteToRead);
void SPI_Write_Enable(void);


#endif
c
#include "bsp_spi.h"

static __IO uint32_t SPITimeOut = SPIT_PLAG_TIMEOUT;
static uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode);
//初始化SPI1的引脚
static void Flash_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	//初始化时钟
	FLASH_SPIx_GPIO_APBxClkCmd(FLASH_SPIx_GPIO_CLK, ENABLE);
	
	//设置GPIO, CLK
	GPIO_InitStructure.GPIO_Pin = FLASH_SPIx_CLK_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(FLASH_SPIx_CLK_GPIO_PORT, &GPIO_InitStructure);
	//MOSI
	GPIO_InitStructure.GPIO_Pin = FLASH_SPIx_MOSI_GPIO_PIN;
	GPIO_Init(FLASH_SPIx_MOSI_GPIO_PORT, &GPIO_InitStructure);
	//MISO
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin = FLASH_SPIx_MISO_GPIO_PIN;
	GPIO_Init(FLASH_SPIx_MISO_GPIO_PORT, &GPIO_InitStructure);
	
	//CS
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = FLASH_SPIx_CS_GPIO_PIN;
	GPIO_Init(FLASH_SPIx_CS_GPIO_PORT, &GPIO_InitStructure);
	FLASH_SPI_CS_HIGH;
}
//初始化FLASH的SPI
void Flash_SPI_Config(void)
{
	//初始化引脚
	Flash_GPIO_Config();
	//初始化时钟
	FLASH_SPIx_APBxClkCmd(FLASH_SPIx_CLK, ENABLE);
	//初始化结构体
	SPI_InitTypeDef SPI_InitStructure; 
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;	//使用模式3
	SPI_InitStructure.SPI_CRCPolynomial = 0;//不使用CRC校验
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//大端先行
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
	SPI_Init(FLASH_SPIx, &SPI_InitStructure);
	//使能
	SPI_Cmd(FLASH_SPIx, ENABLE);
}

//发送接收, 一个字节, 接收为返回值
uint8_t Flash_SPI_Send_Receive_Byte(uint8_t data)
{
	SPITimeOut = SPIT_PLAG_TIMEOUT;
	
	//读取寄存器是否为空
	while(SPI_I2S_GetFlagStatus(FLASH_SPIx, SPI_I2S_FLAG_TXE)==RESET)
	{
		if((SPITimeOut--)==0)
			return SPI_TIMEOUT_UserCallback(0);
	}
	//缓冲区为空, 开始发送
	SPI_I2S_SendData(FLASH_SPIx,data);
	//确认发送完成
	while(SPI_I2S_GetFlagStatus(FLASH_SPIx, SPI_I2S_FLAG_RXNE)==RESET)
	{
		if((SPITimeOut--)==0)
			return SPI_TIMEOUT_UserCallback(1);
	}
	return SPI_I2S_ReceiveData(FLASH_SPIx);
}


//读取一个字节, 作为返回值
uint8_t SPI_FLASH_Read_Byte()
{
	return Flash_SPI_Send_Receive_Byte(DUMMY);
}

void SPI_Write_Enable(void)
{
	FLASH_SPI_CS_LOW
	
	Flash_SPI_Send_Receive_Byte(WRITE_ENABLE);
	
	FLASH_SPI_CS_HIGH
}

//读取ID
uint32_t SPI_Read_ID(void)
{
	uint32_t flash_id;
	//片选
	FLASH_SPI_CS_LOW
	Flash_SPI_Send_Receive_Byte(READ_IEDEC_ID);
	flash_id = SPI_FLASH_Read_Byte();

	flash_id <<= 8;
	flash_id |= SPI_FLASH_Read_Byte();

	flash_id <<= 8;
	flash_id |= SPI_FLASH_Read_Byte();
	
	FLASH_SPI_CS_HIGH
	return flash_id;
}

//等待操作完成, 通过判断Flash状态寄存器的状态完成
void Flash_SPI_WatiFanish(void)
{
	SPITimeOut = SPIT_PLAG_TIMEOUT;

	FLASH_SPI_CS_LOW

	uint8_t status = 0;
	do{
		Flash_SPI_Send_Receive_Byte(READ_STATUS);
		status = Flash_SPI_Send_Receive_Byte(DUMMY);
		SPITimeOut--;
	}while((status & 0x01 ==1) && SPITimeOut);
	if(SPITimeOut==0)
	{
		SPI_TIMEOUT_UserCallback(2);
	}
	//取消片选
	FLASH_SPI_CS_HIGH

}

//进行擦除4k
void Flash_ERASE_SECTOR(uint32_t addr)
{
	SPI_Write_Enable();
	Flash_SPI_WatiFanish();
	//片选
	FLASH_SPI_CS_LOW
	//发送命令
	Flash_SPI_Send_Receive_Byte(ERASE_SECTOR);
	Flash_SPI_Send_Receive_Byte((addr>>16)&0xff);
	Flash_SPI_Send_Receive_Byte((addr>>8)&0xff);
	Flash_SPI_Send_Receive_Byte((addr>>0)&0xff);
	FLASH_SPI_CS_HIGH
}


//读取的函数
void Flash_READ_SECTOR(uint32_t addr, uint8_t *buff, uint32_t numByteToRead)
{
	//片选
	FLASH_SPI_CS_LOW
	//发送命令
	Flash_SPI_Send_Receive_Byte(READ_DATA);
	//发送地址
	Flash_SPI_Send_Receive_Byte((addr>>16)&0xff);
	Flash_SPI_Send_Receive_Byte((addr>>8)&0xff);
	Flash_SPI_Send_Receive_Byte(addr&0xff);
	//进行读取
	
	while(numByteToRead--)
	{
		*buff = SPI_FLASH_Read_Byte();
		buff++;
	}
	FLASH_SPI_CS_HIGH
}
//写入的时候最大为256, 且需要对齐
void Flash_Write_Sector(uint32_t addr, uint8_t *buff, uint32_t numByteToWrite){
	//写使能
	SPI_Write_Enable();
	//片选
	FLASH_SPI_CS_LOW
	//发送命令
	Flash_SPI_Send_Receive_Byte(WRITE_DATA);
	//发送地址
	Flash_SPI_Send_Receive_Byte((addr>>16)&0xff);
	Flash_SPI_Send_Receive_Byte((addr>>8)&0xff);
	Flash_SPI_Send_Receive_Byte(addr&0xff);
	//进行读取
	while(numByteToWrite--)
	{
		Flash_SPI_Send_Receive_Byte(*buff);
		buff++;
	}
	FLASH_SPI_CS_HIGH
	
	Flash_SPI_WatiFanish();
}

/**
  * @brief  等待超时回调函数
  * @param  None.
  * @retval None.
  */
static  uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode)
{
  /* 等待超时后的处理,输出错误信息 */
  FLASH_ERROR("SPI 等待超时!errorCode = %d",errorCode);
  return 0;
}

地址规划

image-20230705184906454

NAND FLASH

使用FMC-NAND FLASH接口进行驱动

是一种非易失性的存储器, 内部使用非线性宏单元模式, 为固态大容量内存提供了解决方案

存储的密度高, 速度快, 功耗低, 寿命长, 价格比较低

MT29F4G08/H27U4G8

板载的是512M的

SD卡, U盘, 固态硬盘使用的都是这一种, 但是坏块的问题, 还有EMMC这个有一个控制芯片解决坏块

存储

image-20231216150602797

image-20231216151703437

image-20231216152140631

他的地址分为三类, 块地址, 页地址, 和列地址

这三个地址通过5个周期进行发送, 首先发送列地址, 之后是页地址和块地址

image-20231216152516784

通常情况下会把块和页地址看为同一个地址, 理解为page的地址

image-20231216155152552

命令

image-20231216155850179

最后一个只能在同一个plane里面操作

image-20231216160138490

出了查手册看时间,也可以通过检查忙引脚的状态

image-20231216160625888

image-20231216160902490

image-20231216161757651

image-20231216161929595

管理的难点

  • 坏块的解决

image-20231216162536458

image-20231216162851414

image-20231216163433357

image-20231216163454565

image-20231216163914441

image-20231216164152745

image-20231216165323211

image-20231216165509060

接口(FMC)

FMC主要就是把读写的操作变成满足外部存储器的时序

image-20231218194616930

主要就是配置FMC的时序寄存器

image-20231218195053449

image-20231218195154665

image-20231218200002649

image-20231218200019289

image-20231218200309317

寄存器

image-20231218200556781

image-20231218200925469

image-20231218201018549

image-20231218201042524

image-20231218201057704