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

电子说

1.3w人已加入

描述

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();
 uint8_t temp[Page];
 Flash_ReadData((addr & 0xFFFF00), temp, Page);//Read Page
 //根据逻辑分析仪调整ReadData函数最后延迟时间,当执行下一个06h指令时MISO应当不输出(0x00)
 Flash_ErasePage(addr); 
 for (uint16_t i = 0; i < length; i++)
 {
  temp[(addr & 0x0000FF) + i] = data[i];
 }//操作成功 
 Flash_WaitWriteToFinish(); 
 Flash_WriteEnable();  
 LL_GPIO_ResetOutputPin(Flash_CSPORT, Flash_CSPIN); 
 LL_SPI_TransmitData8(Flash_SPI, 0x02U); 
 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)) ;
 
 for (uint16_t j = 0; j < Page; j++)
 { //发送没问题
  LL_SPI_TransmitData8(Flash_SPI,temp[j]);
  while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;
 }
 LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);
 Flash_WaitWriteToFinish();
#else //无页擦除功能,扇区(Sector)擦除
 uint8_t temp[Sector * Page]; // 缓存 
 Flash_WriteEnable();
 Flash_ReadData((addr & SectorMsk), temp, Sector * Page); 
 Flash_EraseSector(addr);
 for (uint16_t i = 0; i < length; i++)
 {
  temp[(uint16_t)(addr & 0x000FFF) + i] = data[i];
 }//替换完成
 //将数据原位写入
 Flash_WriteEnable();
 uint32_t iaddpage= addr & SectorMsk; //扇区的起始地址
 //每次写一页,共Sector次
 int m = 0;
 for(uint8_t j = 0 ; j < Sector ; j++)
 {
  Flash_WriteEnable();
  LL_GPIO_ResetOutputPin(Flash_CSPORT, Flash_CSPIN);
  LL_SPI_TransmitData8(Flash_SPI, 0x02U);
  while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;//传送页地址
  LL_SPI_TransmitData8(Flash_SPI, (uint8_t)(iaddpage > > 16)&0xFF);
  while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;
  LL_SPI_TransmitData8(Flash_SPI, (uint8_t)((iaddpage > > 8) & 0xFF));
  while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;
  LL_SPI_TransmitData8(Flash_SPI, 0x00U);
  while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;
  for (uint16_t i = 0; i < Page; i++)
  {
   LL_SPI_TransmitData8(Flash_SPI, temp[m +i]);
   while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;
  }
  LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);
  m += 256;
  iaddpage += 0x000100U;
  Flash_WaitWriteToFinish(); 
 }
#endif 

}


#endif // !_SWSPI_FLASH_H_
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分