草庐IT

STM32实战总结:HAL之SPI

路溪非溪 2023-04-08 原文

SPI基础内容参考:

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

SPI的补充:

通信的起始和停止信号

NSS又由高变低,是SPI通信的起始信号。NSS由低变高,是SPI通信的停止信号,表示本次通信结束。

IIC的任何一个设备既可以作为主设备,也可以作为从设备,但是SPI主从是事先确定的。

关于SPI时序图有一些疑问,有的地方说,发送和接收是同时进行的,但是有的老师又说是在一个边沿发送,但是在另一个边沿接收?我自己看时序图,都只是说在某个边沿采集,并没有提到一个边沿发送一个边沿接收的问题。
其实这里面的接收有两个意思,一个是主机的发送和接收,一个是主机发送,从机的接收。
数据在边沿被采集,发送数据应该不用采集,只有接收数据才叫采集吧。

读时序图,关键的是考虑怎么读数据,因为发送端发数据,就是把数据放到引脚上即可,接收方怎么读,就成了重点关注的问题。主机发送其实就是完成了从机的接收。

任何时刻,只要是工作的,数据线上都是有数据的,只是这个数据是不是我们想要的,SPI在某个时钟边沿被采样,所以需要在采样之前,把我们要传送的数据准备好。

SPI通常在采集边沿的另一个边沿将数据准备好:

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

NSS是片选信号

STM32内置Flash

注意:不要把STM32的内置Flash和外置Flash搞混了,STM32内部自带不同大小的Flash,其主要功能是保存已经编译完成的代码。

内部flash和外部flash的主要区别是在读取速度上,内部flash读取速度更快,而且不需要通过接口函数来读取,外部flash的读取速度较慢。

内部flash是用来储存程序的,外部flash一般是用来储存用户数据的。

内部Flash可由ICode直接访问

说到STM32的内置FLSAH,我们的第一反应是用来装程序的,实际上,STM32的片内FLASH不仅用来装程序,还用来装芯片配置、芯片ID、自举程序等等。
根据用途,STM32片内的FLASH分成两部分:主存储块、信息块。

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

信息块又分成两部分:系统存储器、选项字节。  系统存储器存储用于存放在系统存储器自举模式下的启动程序(BootLoader),当使用ISP方式加载程序时,就是由这个程序执行。这个区域由芯片厂写入BootLoader,然后锁死,用户是无法改变这个区域的。 选项字节存储芯片的配置信息及对主存储块的保护信息。   
STM32的FLASH主存储块按页组织,有的产品每页1KB,有的产品每页2KB。页面典型的用途就是用于按页擦除FLASH。从这点来看,页面有点像通用FLASH的扇区。

Flash基础知识 

一般来说,Flash属于典型的块设备。

什么是块设备?通常是指不能像RAM一样以字节为基本读写单位的存储设备。块设备不能完全按字节访问而必须块访问是物理上的限制,而不是人为设置的障碍。

Flash通常分为多个存储块,每个块又分为多个扇区,每个扇区又分为多个页。不过有些块设备只分为块和页,具体芯片具体对待。

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

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


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

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

以下以某系列Flash芯片W25QXX为例来说明。

SPIFlash:分为SpiNorFlash和SpiNandFlash,用Spi接口把nand和nor进行了封装,封装成SPI的时序。

常用FLASH型号大小

FLASH的块/扇区/页关系

常用FLASH擦写规则

注意对Flash的三大操作,读、写、擦除。

  • 最小擦除单位:扇区
  • 可选择擦除单位:扇区、块、全片
  • 最大编程(写入)单位:页( 256 Byte),大于256 Byte则需分多次写入。
  • 最小编程(写入)单位:1 Byte,即一次可写入 1~256 Byte的任意长度字节。
  • 未写入时FLASH里面的数据为全1,即0xFF。
  • 写入时,只支持把1写成0,不能把0写成1,如果要把0变成1,只能擦除后再整体写入。对这一点,暂时并不是很理解。我的想法是,如果原来有个数据11110000,现在我想把这个数据变成00001111,高4字节是可以写入的,但是低4位因为无法把0写成1,所以该操作无法直接完成,需要先把原来的数据擦除,变成11111111,然后再写入00001111,这样就能完成“修改”。如果是这样的话,那岂不是每次写入数据之前都要将原来的目标区域给擦除?感觉该操作有风险。

常用FLASH封装引脚定义

如图的 8-pin 是较常用的一种封装. 各 pin 的意义:

CS:chip select, 芯片使能引脚。

DO:输出数据。

WP:低电平有效,写保护。

GND:接地。

DI:(在CLK上升沿)向 Flash 输入指令,地址或数据。

CLK:提供输入输出操作的同步时钟。

HOLD:当多个芯片共用 SPI 总线时非常有用。HOLD 为低电平时,DO 引脚变为高阻态,且此时DI/CLK上的信号被忽略。相当于芯片此时不工作。假设对一个 SPI FLASH 的页写操作只进行到一半,此时一个中断来了,另一个更高优先级的任务要占用 SPI 总线,此时就可以使用HOLD拉低来暂停SPI FLASH内部的工作,等到任务切换回来再让操作继续下去。

VCC:2.7 ~ 3.6V

操作外置Flash

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

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

硬件原理图

MX配置

1、根据需要选择模式

一般选择全双工主模式不会有啥问题;

2、硬件NSS,如果只有一个从设备,则不需要;

3、STM32自带的SPI的通信速率不应超过18Mbps,

在数据手册上有描述。这是芯片设计时决定的。为了不超过最高18MHz的设计要求,配置SPI2和SPI3时可以使用fPCLK/2的选项,而配置SPI1时则不能使用fPCLK/2的选项

4、极性和相位选择;

5、NSS是软件还是硬件。

相关函数

/* Exported functions --------------------------------------------------------*/
/** @addtogroup SPI_Exported_Functions
  * @{
  */

/** @addtogroup SPI_Exported_Functions_Group1
  * @{
  */
/* Initialization/de-initialization functions  ********************************/
HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef *hspi);
HAL_StatusTypeDef HAL_SPI_DeInit(SPI_HandleTypeDef *hspi);
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi);
void HAL_SPI_MspDeInit(SPI_HandleTypeDef *hspi);

/* Callbacks Register/UnRegister functions  ***********************************/
#if (USE_HAL_SPI_REGISTER_CALLBACKS == 1U)
HAL_StatusTypeDef HAL_SPI_RegisterCallback(SPI_HandleTypeDef *hspi, HAL_SPI_CallbackIDTypeDef CallbackID, pSPI_CallbackTypeDef pCallback);
HAL_StatusTypeDef HAL_SPI_UnRegisterCallback(SPI_HandleTypeDef *hspi, HAL_SPI_CallbackIDTypeDef CallbackID);
#endif /* USE_HAL_SPI_REGISTER_CALLBACKS */
/**
  * @}
  */

/** @addtogroup SPI_Exported_Functions_Group2
  * @{
  */
/* I/O operation functions  ***************************************************/
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size,
                                          uint32_t Timeout);
HAL_StatusTypeDef HAL_SPI_Transmit_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_Receive_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_TransmitReceive_IT(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData,
                                             uint16_t Size);
HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_Receive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_TransmitReceive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData,
                                              uint16_t Size);
HAL_StatusTypeDef HAL_SPI_DMAPause(SPI_HandleTypeDef *hspi);
HAL_StatusTypeDef HAL_SPI_DMAResume(SPI_HandleTypeDef *hspi);
HAL_StatusTypeDef HAL_SPI_DMAStop(SPI_HandleTypeDef *hspi);
/* Transfer Abort functions */
HAL_StatusTypeDef HAL_SPI_Abort(SPI_HandleTypeDef *hspi);
HAL_StatusTypeDef HAL_SPI_Abort_IT(SPI_HandleTypeDef *hspi);

void HAL_SPI_IRQHandler(SPI_HandleTypeDef *hspi);
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi);
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi);
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi);
void HAL_SPI_TxHalfCpltCallback(SPI_HandleTypeDef *hspi);
void HAL_SPI_RxHalfCpltCallback(SPI_HandleTypeDef *hspi);
void HAL_SPI_TxRxHalfCpltCallback(SPI_HandleTypeDef *hspi);
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi);
void HAL_SPI_AbortCpltCallback(SPI_HandleTypeDef *hspi);
/**
  * @}
  */

/** @addtogroup SPI_Exported_Functions_Group3
  * @{
  */
/* Peripheral State and Error functions ***************************************/
HAL_SPI_StateTypeDef HAL_SPI_GetState(SPI_HandleTypeDef *hspi);
uint32_t             HAL_SPI_GetError(SPI_HandleTypeDef *hspi);
/**
  * @}
  */

关键代码

/* 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
********************************************************/

有关STM32实战总结:HAL之SPI的更多相关文章

  1. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

  2. SPI接收数据异常问题总结 - 2

    SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手

  3. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  4. STM32的HAL和LL库区别和性能对比 - 2

    LL库和HAL库简介LL:Low-Layer,底层库HAL:HardwareAbstractionLayer,硬件抽象层库LL库和hal库对比,很精简,这实际上是一个精简的库。LL库的配置选择如下:在STM32CUBEMX中,点击菜单的“ProjectManager”–>“AdvancedSettings”,在下面的界面中选择“AdvancedSettings”,然后在每个模块后面选择使用的库总结:1、如果使用的MCU是小容量的,那么STM32CubeLL将是最佳选择;2、如果结合可移植性和优化,使用STM32CubeHAL并使用特定的优化实现替换一些调用,可保持最大的可移植性。另外HAL和L

  5. Simulink方法总结和避坑指南(一)——Simulink入门与基本调试方法 - 2

    文章目录一、项目场景二、基本模块原理与调试方法分析——信源部分:三、信号处理部分和显示部分:四、基本的通信链路搭建:四、特殊模块:interpretedMATLABfunction:五、总结和坑点提醒一、项目场景  最近一个任务是使用simulink搭建一个MIMO串扰消除的链路,并用实际收到的数据进行测试,在搭建的过程中也遇到了不少的问题(当然这比vivado里面的debug好不知道多少倍)。准备趁着这个机会,先以一个很基本的通信链路对simulink基础和相关的debug方法进行总结。  在本篇中,主要记录simulink的基本原理和基本的SISO通信传输链路(QPSK方式),计划在下篇记

  6. 你真正了解什么是接口测试么?接口实战一“篇”入魂 - 2

    最近在工作中,看到一些新手测试同学,对接口测试存在很多疑问,甚至包括一些从事软件测试3,5年的同学,在聊到接口时,也是一知半解;今天借着这个机会,对接口测试做个实战教学,顺便总结一下经验,分享给大家。计划拆分成4个模块跟大家做一个分享,(接口测试、接口基础知识、接口自动化、接口进阶)感兴趣的小伙伴记得关注,希望对你的日常工作和求职面试,带来一些帮助。注:文章较长有5000多字,希望小伙伴们认真看完,当然有些内容对小白同学不是太友好,如果你需要详细了解其中的一些概念或者名词,请在文章之后留言,后续我将针对大家的疑问,整理输出一些大家感兴趣的文章。随着开发模式的迭代更新,前后端分离已不是新的概念,

  7. ESP32学习入门:WiFi连接网络 - 2

    目录一、ESP32简单介绍二、ESP32Wi-Fi模块介绍三、ESP32Wi-Fi编程模型四、ESP32Wi-Fi事件处理流程 五、ESP32Wi-Fi开发环境六、ESP32Wi-Fi具体代码七、ESP32Wi-Fi代码解读6.1主程序app_main7.2自定义代码wifi_init_sta()八、ESP32Wi-Fi连接验证8.1测试方法8.2服务器模拟工具sscom58.3测试代码8.4测试结果前言为了开发一款亚马逊物联网产品,开始入手ESP32模块。为了能够记录自己的学习过程,特记录如下操作过程。一、ESP32简单介绍ESP32是一套Wi-Fi(2.4GHz)和蓝牙(4.2)双模解决方

  8. Spring Security 6.0系列【32】授权服务器篇之默认过滤器 - 2

    有道无术,术尚可求,有术无道,止于术。本系列SpringBoot版本3.0.4本系列SpringSecurity版本6.0.2本系列SpringAuthorizationServer版本1.0.2源码地址:https://gitee.com/pearl-organization/study-spring-security-demo文章目录前言1.OAuth2AuthorizationServerMetadataEndpointFilter2.OAuth2AuthorizationEndpointFilter3.OidcProviderConfigurationEndpointFilter4.N

  9. ruby - 摘要::CRC32 与 Zlib - 2

    在我的代码中,我需要使用各种算法(包括CRC32)对文件进行哈希处理。因为我还在Digest系列中使用其他加密哈希函数,所以我认为为它们维护一个一致的接口(interface)会很好。为了记录,我确实找到了digest-crc,一颗完全符合我要求的gem。问题是,Zlib是标准库的一部分,并且有一个我想重用的CRC32工作实现。此外,它是用C编写的,因此它应该提供与digest-crc相关的卓越性能,后者是纯ruby​​实现。实现Digest::CRC32一开始看起来非常简单:%w(digestzlib).each{|f|requiref}classDigest::CRC32一切正常:

  10. ruby - 安装gem : Couldn't reserve space for cygwin's heap, Win32错误487错误 - 2

    我正在尝试在我的机器上安装win32-apigem,但在构建native扩展时我遇到了一些问题:$geminstallwin32-api--no-ri--rdocTemporarilyenhancingPATHtoincludeDevKit...Buildingnativeextensions.Thiscouldtakeawhile...C:\Programs\dev_kit\bin\make.exe:***Couldn'treservespaceforcygwin'sheap,Win32error0ERROR:Errorinstallingwin32-api:ERROR:Failed

随机推荐