STM32实战:HAL库中的SPI技术

SPI基础内容参考:

嵌入式常见接口协议总结_路溪非溪的博客-CSDN博客

STM32的SPI可自行查阅数据手册。本文不赘述。 

NSS是片选信号

STM32的Falsh 

概念补充:FLASH的块和页

FLASH中可以被单次访问的最小单元(就是说对FLASH进行一次读写至少要读写这么多,或者是这么多的整数倍)叫做Page(页)。如果某FLASH一页是2KB,那么我们要读写FLASH,每次至少要读写2KB或者n*2KB,即使我们只是想要其中的一个字节。这就是我们说的典型的块设备(PS:现在有些块设备为了方便,提供了一种random read模式,可以只读取1个字节)。

页往上还有个Block(块)的概念,1个块等于若干个页。

块设备分page、block有什么意义?首先要明白,块设备不能完全按字节访问而必须块访问是物理上的限制,而不是人为设置的障碍。其次,Page和Block各有各的意义,譬如FLASH中:Page是读写FLASH的最小单位;Block是擦除FLASH的最小单位。这些规则都是FLASH的物理原理所限制要求的。

FLASH芯片中主要包含2部分:FLASH存储颗粒+FLASH接口电路。存储颗粒就是纯粹的FLASH的存储单元,类似于仓库;FLASH接口电路是用来管理存储颗粒,并且给外界提供一个统一的FLASH接口规格的访问接口的。

FLASH中有多个存储单元,每个单元都有自己的地址(地址是精确到字节的)。所以FLASH是地址编排精确到字节,但是实际读写却只能精确到页(所以FLASH的很多操作都要求给的地址是页对齐的,譬如2K、4K、512K等这样的地址,不能给3000B这样的地址)。

坏块标志:FLASH芯片用一段时间后,可能某些块会坏掉(这些块无法擦除了,或者无法读写了),FLASH的坏块非常类似于硬盘的坏道。坏块是不可避免的,而且随着FLASH的使用坏块会越来越多。当坏块还不算太多时这个FLASH都是可以用的,除非坏块太多了不能用了才会换新的。所以我们为了管理FLASH发明了一种坏块标志机制。有的FLASH每个页中的一部分会定义一个固定位置(譬如定位第24字节)来标记这个块是好的还是坏的。文件系统在发现这个块已经坏了没法用了时会将这个块标记为坏块,以后访问FLASH时直接跳过这个块即可。

说到STM32的FLSAH,我们的第一反应是用来装程序的,实际上,STM32的片内FLASH不仅用来装程序,还用来装芯片配置、芯片ID、自举程序等等。当然, FLASH还可以用来装数据。

FLASH组成
根据用途,STM32片内的FLASH分成两部分:主存储块、信息块。

主存储块用于存储程序,我们写的程序一般存储在这里。

信息块又分成两部分:系统存储器、选项字节。  系统存储器存储用于存放在系统存储器自举模式下的启动程序(BootLoader),当使用ISP方式加载程序时,就是由这个程序执行。这个区域由芯片厂写入BootLoader,然后锁死,用户是无法改变这个区域的。 选项字节存储芯片的配置信息及对主存储块的保护信息。   

FLASH的页面
STM32的FLASH主存储块按页组织,有的产品每页1KB,有的产品每页2KB。页面典型的用途就是用于按页擦除FLASH。从这点来看,页面有点像通用FLASH的扇区。

实际使用中,会使用到各种各样的FLASH芯片,所以需要在使用时认真细致地查看对应的数据手册。

本文我们通过使用SPI接口来操作Flash,Flash型号为W25Q64。

具体查阅芯片手册,本文不赘述。

硬件原理图

MX配置

关键代码

/* Includes ------------------------------------------------------------------*/
#include "MyApplication.h"

/* Private define-------------------------------------------------------------*/

/* Private variables----------------------------------------------------------*/

/* Private function prototypes------------------------------------------------*/      
static void SPI_Flash_ReadFlashID(void);                          //读取FlashID
static void SPI_Flash_EraseSector(uint32_t);                      //擦除扇区(4kB)
static void SPI_Flash_EraseTotal(void);                           //擦除全部
static void SPI_Flash_WriteUnfixed(uint8_t*,uint32_t,uint32_t);   //写入不固定长度数据
static void SPI_Flash_ReadUnfixed(uint8_t*,uint32_t,uint32_t);    //读取不固定长度数据

/* Public variables-----------------------------------------------------------*/
SPI_Flash_t  SPI_Flash = 
{
	0,
		
	SPI_Flash_ReadFlashID,
	SPI_Flash_EraseSector,
	SPI_Flash_EraseTotal,
	SPI_Flash_WriteUnfixed,
	SPI_Flash_ReadUnfixed
};

/* Private function prototypes------------------------------------------------*/
static uint8_t SPI_Flash_ReadByte(void);      //从Flash读1个字节
static void    SPI_Flash_WriteByte(uint8_t);  //给Flash写1个字节

static void SPI_Flash_WriteEnable(void);      //Flash写使能
static void SPI_Flash_WaitForWriteEnd(void);  //等待Flash写入完成

static void SPI_Flash_WritePage(uint8_t*,uint32_t,uint16_t);      //写入页(256Bytes),写入长度不超过256字节

/*
	* @name   SPI_Flash_ReadByte
	* @brief  从Flash读1个字节
	* @param  None
	* @retval 读取到的数据     
*/
static uint8_t SPI_Flash_ReadByte()
{	
	uint8_t ReceiveByte;
	//等待模式读出1个字节
	if(HAL_SPI_Receive(&hspi3,&ReceiveByte,1,0x0A) != HAL_OK)
		ReceiveByte = Dummy_Byte;
	//返回字节
	return ReceiveByte;
}

/*
	* @name   SPI_Flash_ReadByte
	* @brief  给Flash写1个字节
	* @param  Byte -> 待写入的字节
	* @retval 读取到的数据     
*/
static void SPI_Flash_WriteByte(uint8_t Byte)
{	
	uint8_t SendByte = Byte;
	//等待模式写入1个字节
	HAL_SPI_Transmit(&hspi3,&SendByte,1,0x0A);
}

/*
	* @name   SPI_Flash_WriteEnable
	* @brief  Flash写使能
	* @param  None
	* @retval None
*/
static void SPI_Flash_WriteEnable()
{
	//选择Flash芯片: CS输出低电平
	CLR_SPI_Flash_CS;
	//发送命令:写使能
	SPI_Flash_WriteByte(W25X_WriteEnable);
	//禁用Flash芯片: CS输出高电平
	SET_SPI_Flash_CS;
}

/*
	* @name   SPI_Flash_WaitForWriteEnd
	* @brief  等待Flash写入完成
	* @param  None
	* @retval None
*/
void SPI_Flash_WaitForWriteEnd()
{
	uint8_t Flash_Status1 = 0;
	
	//选择Flash芯片: CS输出低电平
	CLR_SPI_Flash_CS;
	//发送命令:读状态寄存器1
	SPI_Flash_WriteByte(W25X_ReadStatusReg1);
	
	//连续读取状态寄存器1,直到WEL为低电?
	Timer6.usDelay_Timer = 0;
	do
	{
		Flash_Status1 = SPI_Flash_ReadByte();
		//防止意外,概率极低
		if(Timer6.usDelay_Timer >= TIMER0_10S)
			break;
	}
	while((Flash_Status1 & Flash_Status1_BUSY) == Flash_Status1_BUSY);
	
	//禁用Flash芯片: CS输出高电平
	SET_SPI_Flash_CS;
}

/*
	* @name   SPI_Flash_ReadFlashID
	* @brief  读取FlashID
	* @param  None
	* @retval None
*/
static void SPI_Flash_ReadFlashID()
{
	uint8_t buf[3];
	
	//检测flash是否处于忙碌状态
	SPI_Flash_WaitForWriteEnd();
	
	//选择Flash芯片: CS输出低电平
	CLR_SPI_Flash_CS;
	//发送命令:读取JEDEC ID(设备标识符 -> 制造商+内存类型+容量)
	SPI_Flash_WriteByte(W25X_ReadJedecID);
	buf[0] = SPI_Flash_ReadByte();
	buf[1] = SPI_Flash_ReadByte();
	buf[2] = SPI_Flash_ReadByte();
	//禁用Flash芯片: CS输出高电平
	SET_SPI_Flash_CS;
	
	SPI_Flash.JedecID = (buf[0] << 16) + (buf[1] << 8) + buf[2];
	printf("The JEDEC ID of SPI flash is 0x%.6X\r\n",SPI_Flash.JedecID);
	
	/***
	JEDEC ID      IC型号             存储空间大小         
  0xEF4015	    W25Q16JV-IQ/JQ     16M-bit /2M-byte
  0XEF4017      W25Q64JV-IQ/JQ     64M-bit /8M-byte
  0XEF4018      W25Q128JV-IQ/JQ    128M-bit/16M-byte
	***/
	switch(SPI_Flash.JedecID)
	{
		case 0xEF4015: printf("Flash芯片型号为W25Q16JV-IQ/JQ, 16M-bit /2M-byte\r\n"); break;
		case 0xEF4017: printf("Flash芯片型号为W25Q64JV-IQ/JQ, 64M-bit /8M-byte\r\n"); break;	 
    case 0xEF4018: printf("Flash芯片型号为W25Q128JV-IQ/JQ,128M-bit/16M-byte\r\n"); break;
    default: printf("Flash芯片型号未知\r\n");	
	}
}

/*
	* @name   SPI_Flash_EraseSector
	* @brief  擦除扇区(4kB)
	* @param  SectorAddr:待擦除扇区地址,要求为4k倍数
	* @retval None
*/
static void SPI_Flash_EraseSector(uint32_t SectorAddr)
{
	//检测flash是否处于忙碌状态
	SPI_Flash_WaitForWriteEnd();
	
	//Flash写使能,允许擦除
	SPI_Flash_WriteEnable();
	
	/* 擦除扇区 */
	//选择Flash芯片: CS输出低电平
	CLR_SPI_Flash_CS;
	
	//发送命令:扇区擦除
	SPI_Flash_WriteByte(W25X_SectorErase);
	//发送擦除扇区地址的高字节
	SPI_Flash_WriteByte((SectorAddr & 0xFF0000) >> 16);
	//发送擦除扇区地址的中字节
	SPI_Flash_WriteByte((SectorAddr & 0xFF00) >> 8);
	//发送擦除扇区地址的低字节
	SPI_Flash_WriteByte(SectorAddr & 0xFF);	
	
	//禁用Flash芯片: CS输出高电平
	SET_SPI_Flash_CS;
	
	//等待擦除完毕
	SPI_Flash_WaitForWriteEnd();
}

/*
	* @name   SPI_Flash_EraseTotal
	* @brief  擦除全部
	* @param  None
	* @retval None
*/
static void SPI_Flash_EraseTotal()
{
	//检测flash是否处于忙碌状态
	SPI_Flash_WaitForWriteEnd();
	
	//Flash写使能,允许擦除
	SPI_Flash_WriteEnable();
	
	/* 擦除整片 */
	//选择Flash芯片: CS输出低电平
	CLR_SPI_Flash_CS;
	
	//发送命令:整片擦除
	SPI_Flash_WriteByte(W25X_ChipErase);	
	
	//禁用Flash芯片: CS输出高电平
	SET_SPI_Flash_CS;
	
	//等待擦除完毕
	SPI_Flash_WaitForWriteEnd();
}

/*
	* @name   SPI_Flash_WritePage
	* @brief  写入页(256Bytes),写入长度不超过256字节
	* @param  pWriteBuffer:待写入数据的指针
  *         WriteAddr   :写入地址
  *         WriteLength :写入数据长度,必须小于等于SPI_FLASH_PerWritePageSize(256Bytes)
	* @retval None
*/
static void SPI_Flash_WritePage(uint8_t* pWriteBuffer, uint32_t WriteAddr, uint16_t WriteLength)
{
	//检测flash是否处于忙碌状态
	SPI_Flash_WaitForWriteEnd();
	
	//Flash写使能,允许写入
	SPI_Flash_WriteEnable();
	
	//选择Flash芯片: CS输出低电平
	CLR_SPI_Flash_CS;
	
	//发送命令:页面编程
	SPI_Flash_WriteByte(W25X_PageProgram);	
	//发送地址高字节
	SPI_Flash_WriteByte((WriteAddr & 0xFF0000) >> 16);
	//发送地址中字节
	SPI_Flash_WriteByte((WriteAddr & 0xFF00) >> 8);
	//发送地址低字节
	SPI_Flash_WriteByte(WriteAddr & 0xFF);
	
	if(WriteLength > SPI_FLASH_PageSize)
  {
     WriteLength = SPI_FLASH_PageSize;
		printf("Error: Flash每次写入数据不能超过256字节!\n");
  }
	
	//开始写入数据
	while (WriteLength--)
  {
     /* 读取一个字节*/
    SPI_Flash_WriteByte(*pWriteBuffer);
    /* 指向下一个字节缓冲区 */
    pWriteBuffer++;
  }
	
	//禁用Flash芯片: CS输出高电平
	SET_SPI_Flash_CS;
	
	//等待写入完毕
	SPI_Flash_WaitForWriteEnd();
}

/*
	* @name   SPI_Flash_WriteUnfixed
	* @brief  写入不固定长度数据
	* @param  pWriteBuffer:待写入数据的缓存指针
  *         WriteAddr   :写入地址
  *         WriteLength :写入数据长度
	* @retval None
*/
static void SPI_Flash_WriteUnfixed(uint8_t* pWriteBuffer, uint32_t WriteAddr, uint32_t WriteLength)
{
	uint32_t PageNumofWirteLength     = WriteLength / SPI_FLASH_PageSize;            //待写入页数
	uint8_t  NotEnoughNumofPage       = WriteLength % SPI_FLASH_PageSize;            //不足一页的数量
	uint8_t  WriteAddrPageAlignment   = WriteAddr % SPI_FLASH_PageSize;              //如果取余为0,则地址页对齐,可以写连续写入256字节
	uint8_t  NotAlignmentNumofPage    = SPI_FLASH_PageSize - WriteAddrPageAlignment; //地址不对齐部分,最多可以写入的字节数
	
	//写入地址页对齐
	if(WriteAddrPageAlignment == 0)
	{
		//待写入数据不足一页
		if(PageNumofWirteLength == 0)
		{
			SPI_Flash_WritePage(pWriteBuffer,WriteAddr,WriteLength);
		}
		//待写入数据超过一页
		else
		{
			//先写入整页
			while(PageNumofWirteLength--)
			{
				SPI_Flash_WritePage(pWriteBuffer,WriteAddr,SPI_FLASH_PageSize);
				pWriteBuffer += SPI_FLASH_PageSize;
				WriteAddr    += SPI_FLASH_PageSize;
			}
			//再写入不足一页的数据
			if(NotEnoughNumofPage > 0)
			{
				SPI_Flash_WritePage(pWriteBuffer,WriteAddr,NotEnoughNumofPage);
			}
		}
	}
	//写入地址与页不对齐
	else
	{
		//待写入数据不足一页
		if(PageNumofWirteLength == 0)
		{
			//不足一页的数据 <= 地址不对齐部分
			if(NotEnoughNumofPage <= NotAlignmentNumofPage)
			{
				SPI_Flash_WritePage(pWriteBuffer,WriteAddr,WriteLength);
			}
			//不足一页的数据 > 地址不对齐部分
			else
			{
				//先写地址不对齐部分允许写入的最大长度
				SPI_Flash_WritePage(pWriteBuffer,WriteAddr,NotAlignmentNumofPage);				
				pWriteBuffer += NotAlignmentNumofPage;
				WriteAddr    += NotAlignmentNumofPage;
				
				//再写没写完的数据
				SPI_Flash_WritePage(pWriteBuffer,WriteAddr,NotEnoughNumofPage-NotAlignmentNumofPage);
			}
		}
		//待写入数据超过一页
		else
		{
			//先写地址不对齐部分允许写入的最大长度,地址此时对齐了
		    SPI_Flash_WritePage(pWriteBuffer,WriteAddr,NotAlignmentNumofPage);				
			pWriteBuffer += NotAlignmentNumofPage;
			WriteAddr    += NotAlignmentNumofPage;
			
			//地址对其后,重新计算写入页数与不足一页的数量
			WriteLength           -= NotAlignmentNumofPage;
			PageNumofWirteLength   = WriteLength / SPI_FLASH_PageSize;            //待写入页数
	        NotEnoughNumofPage     = WriteLength % SPI_FLASH_PageSize; 
			
			//先写入整页
			while(PageNumofWirteLength--)
			{
				SPI_Flash_WritePage(pWriteBuffer,WriteAddr,SPI_FLASH_PageSize);
				pWriteBuffer += SPI_FLASH_PageSize;
				WriteAddr    += SPI_FLASH_PageSize;
			}
			//再写入不足一页的数据
			if(NotEnoughNumofPage > 0)
			{
				SPI_Flash_WritePage(pWriteBuffer,WriteAddr,NotEnoughNumofPage);
			}
		}
	}
}

/*
	* @name   SPI_Flash_ReadUnfixed
	* @brief  读取不固定长度数据
	* @param  pReadBuffer:存放读取数据的缓存指针
  *         ReadAddr   :读取地址
  *         ReadLength :读取数据长度
	* @retval None
*/
static void SPI_Flash_ReadUnfixed(uint8_t* pReadBuffer, uint32_t ReadAddr, uint32_t ReadLength)
{
	//检测flash是否处于忙碌状态
	SPI_Flash_WaitForWriteEnd();
	
	//选择Flash芯片: CS输出低电平
	CLR_SPI_Flash_CS;
	
	//发送命令:读取数据
	SPI_Flash_WriteByte(W25X_ReadData);	
	//发送地址高字节
	SPI_Flash_WriteByte((ReadAddr & 0xFF0000) >> 16);
	//发送地址中字节
	SPI_Flash_WriteByte((ReadAddr & 0xFF00) >> 8);
	//发送地址低字节
	SPI_Flash_WriteByte(ReadAddr & 0xFF);
	//开始读取数据
	while (ReadLength--)
  {
     /* 读取一个字节*/
    *pReadBuffer = SPI_Flash_ReadByte();
    /* 指向下一个字节缓冲区 */
    pReadBuffer++;
  }
	
	//禁用Flash芯片: CS输出高电平
	SET_SPI_Flash_CS;
}
/********************************************************
  End Of File
********************************************************/

物联沃分享整理
物联沃-IOTWORD物联网 » STM32实战:HAL库中的SPI技术

发表评论