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_
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。
举报投诉
-
存储器
+关注
关注
38文章
7422浏览量
163485 -
SPI
+关注
关注
17文章
1687浏览量
91151 -
逻辑分析仪
+关注
关注
3文章
213浏览量
23121 -
FSMC模块
+关注
关注
0文章
9浏览量
1916 -
模拟驱动电路
+关注
关注
0文章
2浏览量
738
发布评论请先 登录
相关推荐
基于GPIO模拟的SPI接口驱动设计与实现
SPI总线是我们常用的串行设备接口,一般情况下我们都会适应硬件SPI接口,但有些时候当硬件端口不足时,我们也希望可以使用软件
发表于 12-07 16:21
•6141次阅读
硬件SPI与软件模拟SPI速度区别
,LORA芯片SX1278等。最近为了驱动彩色OLED显示屏,为了提高显示刷新率,需要对程序代码进行优化。于是,将相关SPI驱动从软件
发表于 07-01 06:40
怎样通过硬件spi读取offset与gain寄存器的值呢
之前写过了mcu通过硬件spi接口向dac芯片ad5764的数据寄存器写值输出电压,ad5764的offset与gain寄存器的值也是可以通过硬件spi读出来的。第一步:将待读取的芯片
发表于 02-14 07:39
单片机通过模拟SPI驱动LD3320模块
单片机通过模拟SPI驱动LD3320模块仅完成识别部分!仅完成识别部分!仅完成识别部分!根据手册推荐使用3.3V,而不是5V
发表于 12-16 16:52
•7次下载
STM32 SPI 软件NSS和硬件NSS解读
[导读]SSM可以控制内部NSS引脚与SSI(一个寄存器,软件模式)相连,还是与NSS外部引脚(真正的STM32引脚,硬件模式)相连。真正作用的是内部NSS引脚(内部NSS引脚才真正连接到SP
发表于 12-22 19:12
•14次下载
硬件SPI与软件模拟SPI速度区别实测
,LORA芯片SX1278等。最近为了驱动彩色OLED显示屏,为了提高显示刷新率,需要对程序代码进行优化。于是,将相关SPI驱动从软件
发表于 12-22 19:13
•9次下载
stm32f103使用dma和fpga进行spi通信
stm32作为从机,fpga作为主机。进行spi通信。stm32使用dma进行数据接收。在dma中断中进
发表于 12-22 19:29
•94次下载
STM32L4 模拟SPI 驱动LCD 240*240屏幕
,屏幕驱动ST7789V2,MCU:STM32L431RCT6这些资料在网上都能找到,这里就不提供下载了。上图是开发板连接的硬件SPI对应的引脚,模
发表于 12-22 19:29
•22次下载
单片机spi接口的使用方法有哪些(spi接口和串口的区别)
如果单片机没有硬件SPI模块,或者需要额外的IO引脚来实现多个SPI设备的通信,可以使用软件
评论