STM32通过硬件SPI模块软件模拟驱动来进行拓展

发布时间:2023-09-06  

FSMC一般只有STM32大容量产品才具备。因此在使用中小容量产品外接存储器时,一般会通过硬件SPI模块软件模拟驱动来进行拓展。


本文将以常见的 NOR Flash(多个厂家有对标的同类产品)为例。


我使用的是普亚的P25Q32SH,这个flash除了贵和多一些功能外,在基本控制方面和华邦的W25Q32差不多,基本指令通用。但不同flash之间还是存在一些差异,要注意适配。


一、封装


8引脚的spi Flash除了封装方式有些差异,引脚排列基本是一模一样的。

图片

代码:


总的来说还是很简单的。因为时间比较赶,只求能用,存在代码冗余和效率较低的问题,欢迎改进指正!


复制

//******************************************************************************

//* 文件名   ExtFlashSPI.h

//* 介绍:    利用STM32硬件spi实现对spi的控制

//*  基于W25Q32,在基础指令方面兼容

//*  使用其他芯片请参照手册进行指令集和参数的适配

//*  

//*  ※适用最大容量为16M(128Mbit)Flash 

//* 

//* @Author  Sachefgh Xu 

//*********************************模块介绍************************************

// 适用8引脚的spi flash

//

//

//

//引脚配置: /VCC  一般选择 2.7-3.6v 的元件,flash对电压有要求,推荐供电接稳压管

//    /GND  接地

//    /CS   片选,低电平使能;上电时应当置高电平,推荐NSS引脚使能上拉或外接上拉

//    /DI(IO0) Data-in 

//    /DO         Data-out

//    /CLK  时钟线

//    /WP   写保护 默认不启用;启用后高电平+写使能指令解锁----------本驱动中WP接vcc拉高

//    /HOLD  Hold-input; 时钟线和hold均为低电平时触发暂停;默认高电平------本驱动中HOLD接vcc拉高

//说明:

//对Flash时序的规定:MOSI-》DI,  flash在时钟上升沿采样

// MISO- >DO flash在下降沿设置。当片选使能时时钟处于低电平,视为已接收一个下降沿。主机在上升沿读取采样

//配置spi模块时,时钟线空闲为低电平,上升沿采样(CPHA=0,CPOL=0); MSB模式

//

//上电时,模块写使能被禁用。

//

/***********************************ED***********************************/

#ifndef _SWSPI_FLASH_H_

#define _SWSPI_FLASH_H_  

#include "stm32f1xx_ll_gpio.h"

#include "stm32f1xx_ll_spi.h"

#include "stm32f1xx_ll_dma.h"

#include "stm32f1xx_ll_utils.h"


/***********************************配置参数***********************************/


#define Flash_SPI SPI1   //连接的硬件spi模块,spi应配置全双工主机模式

#define Flash_CSPORT     GPIOA //片选线;应当配置为高速输出,初始高电平

#define Flash_CSPIN      LL_GPIO_PIN_4


#define BlockNumber  64  //块数量


#define Page /*Each Page has*/ 256 /*Bytes*/

#define Sector /*Each Sector has*/ 16 /*Pages*/

#define Block /*Each Block has*/ 16 /*Sectors*/

#define AddressMax (BlockNumber * Page * Sector* Block-1) //最大内存地址,每一地址对应一字节



#if (Page==256)

#define PageMsk  0xFFFF00  


 #if (Sector==16)

#define SectorMsk  0xFFF000     

 #endif // (Sector==16)



#endif 



//不同容量Flash只有块数量有区别,一般扇区数量和页数量一致。

//24Bits地址 最高8位标定block,高16位标定page


//页地址     addr & 0xFFFF00

//扇区地址   addr & 0xFFF000

//块地址  addr & 0xFF0000


//额外指令配置:


//#define _81H  //page erase页擦除功能.-----w25qxx系列无此功能



/******************************************************************************/


 uint8_t ManufacturerID; //制造商信息

 uint8_t MemoryTypeID;

 uint8_t CapacityID; //容量信息

//上述信息在初始化时读取


//临时数据 



/***********************/


__STATIC_INLINE void Flash_GetInformation();

__STATIC_INLINE void Flash_WaitWriteToFinish();




/**

 * @brief  初始化函数,首先调用

 * @note  一并读取和存储制造商信息、容量和存储类型数据

 */

__STATIC_INLINE void Flash_Init()

 LL_mDelay(7); //等待上电初始化,可删

 LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);//关闭片选

 //

 LL_SPI_Enable(Flash_SPI); //重新开启SPI模块

 LL_SPI_ReceiveData8(Flash_SPI); //置零RXNE

 Flash_GetInformation();

}


/**

 * @brief  读取制造商ID、存储类型ID、容量ID

 * @cmd:  90h

 * @note  读取后存入 ManufacturerID、MemoryTypeID、CapacityID变量中

 */

__STATIC_INLINE void Flash_GetInformation()

{ //读取Manufacturer ID& Device ID (90h)

 LL_GPIO_ResetOutputPin(Flash_CSPORT, Flash_CSPIN);

 LL_SPI_TransmitData8(Flash_SPI, 0x9FU);

 while (!LL_SPI_IsActiveFlag_RXNE(Flash_SPI)) ; //等待接收完

 LL_SPI_ReceiveData8(Flash_SPI);   //置零RXNE

 LL_SPI_TransmitData8(Flash_SPI, 0x00U);  //生成时钟

 while(!LL_SPI_IsActiveFlag_RXNE(Flash_SPI)); //等待接收完

 ManufacturerID = LL_SPI_ReceiveData8(Flash_SPI);

 LL_SPI_TransmitData8(Flash_SPI, 0x00U);

 while (!LL_SPI_IsActiveFlag_RXNE(Flash_SPI)) ;//等待接收完

 MemoryTypeID = LL_SPI_ReceiveData8(Flash_SPI);

 LL_SPI_TransmitData8(Flash_SPI, 0x00U);

 while (!LL_SPI_IsActiveFlag_RXNE(Flash_SPI)) ;//等待接收完

 CapacityID = LL_SPI_ReceiveData8(Flash_SPI);

 LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);

 

}


/**

 * @brief  使能擦写

 * @cmd: 06h

 * @note  再通过指令进行页写入、扇区擦除、块擦除、整片擦除、写状态寄存器时均需调用

 */

__STATIC_INLINE void Flash_WriteEnable()

{

 LL_GPIO_ResetOutputPin(Flash_CSPORT, Flash_CSPIN);

 LL_SPI_TransmitData8(Flash_SPI, 0x06U);

 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;

 LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);

}


/**

 * @brief  禁用擦写(写入锁)

 * @cmd: 04h

 * @note  写入、擦除、写状态寄存器完成后调用

 */

__STATIC_INLINE void Flash_WriteDisable()

{

 LL_GPIO_ResetOutputPin(Flash_CSPORT, Flash_CSPIN);

 LL_SPI_TransmitData8(Flash_SPI, 0x04U);

 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;

 LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);

}

/**

 * @brief  连续字节读取(常速)

 * @cmd: 03h

 * @param 

 *  uint32_t addr  //24位地址(数据最高8位忽略),每一位代表一字节数据;addr可取任意有效地址

 *  uint8_t    *data //传入的uint_8数组地址或者 变量地址(当读取数为1时)

 *  uint8_t  number  //读取字节数

 *

 * @note  发送指令03h后分3字节从高到低传输地址位; Flash将在之后的时钟周期从传入地址开始

 * 以地址递增顺序传出片上数据(数据位数共number位),直到CS被拉高

 * 当number=1,读取指定位数据

 */

__STATIC_INLINE void Flash_ReadData(uint32_t addr, uint8_t *data, uint16_t length)

{

 LL_GPIO_ResetOutputPin(Flash_CSPORT, Flash_CSPIN);

 LL_SPI_TransmitData8(Flash_SPI, 0x03);

 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;//传输完毕

 LL_SPI_TransmitData8(Flash_SPI, (uint8_t)(addr >>16)&0xFF);

 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;//传输完毕

 LL_SPI_TransmitData8(Flash_SPI, (uint8_t)((addr > > 8)&0xFF));

 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;//传输完毕

 LL_SPI_TransmitData8(Flash_SPI, (uint8_t)(addr & 0xFF));

 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;

 LL_SPI_ReceiveData8(Flash_SPI);//置零标志

 //开始读取

 for(uint16_t i = 0 ; i < length ; i++)

 {

  LL_SPI_TransmitData8(Flash_SPI, 0x00U);//generate clock

  while (!LL_SPI_IsActiveFlag_RXNE(Flash_SPI)) ;//wait till tranfer complete

  data[i] = LL_SPI_ReceiveData8(Flash_SPI);

 }

 LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);

 //延时

 uint16_t dlay=0;

 while (dlay < 960){dlay++;}

}

/**

 * @brief  整片擦除(变为FF)  ※此操作无法复原,使用请谨慎

 * @cmd: 60h(或C7h)

 * @note 将整片flash数据擦除

 */

__STATIC_INLINE void Flash_EraseChip()

{

 Flash_WriteEnable();//使能写

 LL_GPIO_ResetOutputPin(Flash_CSPORT, Flash_CSPIN);

 LL_SPI_TransmitData8(Flash_SPI, 0x60U);

 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;//传输完毕

 LL_mDelay(1);

 LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);

 Flash_WaitWriteToFinish();

}


#ifdef _81H    

/**

 * @brief  擦除整个page(将整页256bytes数据写为FF)  ※此操作无法复原,使用请谨慎

 * @cmd: 81h

 * @param:  uint32_t addr  //24位页地址(数据最高8位忽略)。前16位规定页地址,最后8位无意义(dummy)。

 *  addr可填位于 目标页 的任一地址 

 * 

 * @note 擦除指定Page上的内容;写入前必须先进行擦除

 */

__STATIC_INLINE void Flash_ErasePage(uint32_t addr)

{

 Flash_WriteEnable();//使能读写

 

 LL_GPIO_ResetOutputPin(Flash_CSPORT, Flash_CSPIN);

 LL_SPI_TransmitData8(Flash_SPI, 0x81U);

 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;//传输完毕

 LL_SPI_TransmitData8(Flash_SPI, (uint8_t)(addr > > 16)&0xFF);

 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;//传输完毕

 LL_SPI_TransmitData8(Flash_SPI, (uint8_t)((addr > > 8) & 0xFF));

 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;//传输完毕

 LL_SPI_TransmitData8(Flash_SPI, 0X00U);

 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;//传输完毕

 LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);

 Flash_WaitWriteToFinish();

}

#endif

/**

 * @brief  擦除整个sector(4096bytes = 16 pages)  ※此操作无法复原,使用请谨慎

 * @cmd: 20h

 * @param:  uint32_t addr  //24位扇区地址(数据最高8位忽略)。扇区由addr A23-A12确定

 *  addr可填位于 目标扇区 的任一地址

 * @note 擦除指定Page上的内容;写入前必须先进行擦除

 */

__STATIC_INLINE void Flash_EraseSector(uint32_t addr)

{

 Flash_WriteEnable();

 LL_GPIO_ResetOutputPin(Flash_CSPORT, Flash_CSPIN);

 LL_SPI_TransmitData8(Flash_SPI, 0x20U);

 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;

 LL_SPI_TransmitData8(Flash_SPI, (uint8_t)(addr > > 16)&0xFF);

 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;

 LL_SPI_TransmitData8(Flash_SPI, (uint8_t)((addr > > 8) & 0xF0));

 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;

 LL_SPI_TransmitData8(Flash_SPI, 0X00U);

 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;

 LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);

 Flash_WaitWriteToFinish();

}

/**

 * @brief  等待退出写BUSY状态

 * @retval   

 */

__STATIC_INLINE void Flash_WaitWriteToFinish()

{

 LL_GPIO_ResetOutputPin(Flash_CSPORT, Flash_CSPIN);

 LL_SPI_TransmitData8(Flash_SPI, 0x05U);

 while (!LL_SPI_IsActiveFlag_RXNE(Flash_SPI)) ;

 LL_SPI_ReceiveData8(Flash_SPI);//clear DR

 do

 {

  LL_SPI_TransmitData8(Flash_SPI, 0x00);//dummy   

  while (!LL_SPI_IsActiveFlag_RXNE(Flash_SPI)) ;

 } while ((LL_SPI_ReceiveData8(Flash_SPI) & 0x01));//忙时循环,不忙退出

 LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);

}


/**************************************************************************************************/


//有问题

/**

 * @brief  写入数据,伴有覆盖擦除功能

 * @cmd: 02h

 * @param:  uint32_t addr  //24位地址(数据最高8位忽略),可填片上任意地址;写入

 * 将从该地址开始递增, ※但写入数据长度不能溢出地址所在页(1页256Bytes).

 * 

 * @param  uint8_t length //数据长度,必须大于0

 * @param  uint8_t *data  //写入数据所在地址指针

 * 

 * @note 本函数工作原理如下:

 * 1.通过宏定义判断是否有页擦除功能

 * 2.将需要写入地址所在的扇区/页数据暂存至temp中

 * 3.进行页/扇区擦除操作

 * 4.将temp对应位置数据用data中待写入数据替换

 * 5.将更改后的temp原位写入

 */

__STATIC_INLINE void Flash_WriteData(uint32_t addr, uint8_t *data, uint16_t length)

{

#ifdef _81H //有页擦除功能

 Flash_WriteEnable();

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>