FSMC一般只有STM32大容量產(chǎn)品才具備。因此在使用中小容量產(chǎn)品外接存儲器時,一般會通過硬件SPI模塊軟件模擬驅(qū)動來進(jìn)行拓展。
本文將以常見的 NOR Flash(多個廠家有對標(biāo)的同類產(chǎn)品)為例。
我使用的是普亞的P25Q32SH,這個flash除了貴和多一些功能外,在基本控制方面和華邦的W25Q32差不多,基本指令通用。但不同flash之間還是存在一些差異,要注意適配。
一、封裝
8引腳的spi Flash除了封裝方式有些差異,引腳排列基本是一模一樣的。
代碼:
總的來說還是很簡單的。因?yàn)闀r間比較趕,只求能用,存在代碼冗余和效率較低的問題,歡迎改進(jìn)指正!
//******************************************************************************
//* 文件名 ExtFlashSPI.h
//* 介紹: 利用STM32硬件spi實(shí)現(xiàn)對spi的控制
//* 基于W25Q32,在基礎(chǔ)指令方面兼容
//* 使用其他芯片請參照手冊進(jìn)行指令集和參數(shù)的適配
//*
//* ※適用最大容量為16M(128Mbit)Flash
//*
//* @Author Sachefgh Xu
//*********************************模塊介紹************************************
// 適用8引腳的spi flash
//
//
//
//引腳配置: /VCC 一般選擇 2.7-3.6v 的元件,flash對電壓有要求,推薦供電接穩(wěn)壓管
// /GND 接地
// /CS 片選,低電平使能;上電時應(yīng)當(dāng)置高電平,推薦NSS引腳使能上拉或外接上拉
// /DI(IO0) Data-in
// /DO Data-out
// /CLK 時鐘線
// /WP 寫保護(hù) 默認(rèn)不啟用;啟用后高電平+寫使能指令解鎖----------本驅(qū)動中WP接vcc拉高
// /HOLD Hold-input; 時鐘線和hold均為低電平時觸發(fā)暫停;默認(rèn)高電平------本驅(qū)動中HOLD接vcc拉高
//說明:
//對Flash時序的規(guī)定:MOSI-》DI, flash在時鐘上升沿采樣
// MISO- >DO flash在下降沿設(shè)置。當(dāng)片選使能時時鐘處于低電平,視為已接收一個下降沿。主機(jī)在上升沿讀取采樣
//配置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"
/***********************************配置參數(shù)***********************************/
#define Flash_SPI SPI1 //連接的硬件spi模塊,spi應(yīng)配置全雙工主機(jī)模式
#define Flash_CSPORT GPIOA //片選線;應(yīng)當(dāng)配置為高速輸出,初始高電平
#define Flash_CSPIN LL_GPIO_PIN_4
#define BlockNumber 64 //塊數(shù)量
#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) //最大內(nèi)存地址,每一地址對應(yīng)一字節(jié)
#if (Page==256)
#define PageMsk 0xFFFF00
#if (Sector==16)
#define SectorMsk 0xFFF000
#endif // (Sector==16)
#endif
//不同容量Flash只有塊數(shù)量有區(qū)別,一般扇區(qū)數(shù)量和頁數(shù)量一致。
//24Bits地址 最高8位標(biāo)定block,高16位標(biāo)定page
//頁地址 addr & 0xFFFF00
//扇區(qū)地址 addr & 0xFFF000
//塊地址 addr & 0xFF0000
//額外指令配置:
//#define _81H //page erase頁擦除功能.-----w25qxx系列無此功能
/******************************************************************************/
uint8_t ManufacturerID; //制造商信息
uint8_t MemoryTypeID;
uint8_t CapacityID; //容量信息
//上述信息在初始化時讀取
//臨時數(shù)據(jù)
/***********************/
__STATIC_INLINE void Flash_GetInformation();
__STATIC_INLINE void Flash_WaitWriteToFinish();
/**
* @brief 初始化函數(shù),首先調(diào)用
* @note 一并讀取和存儲制造商信息、容量和存儲類型數(shù)據(jù)
*/
__STATIC_INLINE void Flash_Init()
{
LL_mDelay(7); //等待上電初始化,可刪
LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);//關(guān)閉片選
//
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 再通過指令進(jìn)行頁寫入、扇區(qū)擦除、塊擦除、整片擦除、寫狀態(tài)寄存器時均需調(diào)用
*/
__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 寫入、擦除、寫狀態(tài)寄存器完成后調(diào)用
*/
__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 連續(xù)字節(jié)讀取(常速)
* @cmd: 03h
* @param
* uint32_t addr //24位地址(數(shù)據(jù)最高8位忽略),每一位代表一字節(jié)數(shù)據(jù);addr可取任意有效地址
* uint8_t *data //傳入的uint_8數(shù)組地址或者 變量地址(當(dāng)讀取數(shù)為1時)
* uint8_t number //讀取字節(jié)數(shù)
*
* @note 發(fā)送指令03h后分3字節(jié)從高到低傳輸?shù)刂肺? Flash將在之后的時鐘周期從傳入地址開始
* 以地址遞增順序傳出片上數(shù)據(jù)(數(shù)據(jù)位數(shù)共number位),直到CS被拉高
* 當(dāng)number=1,讀取指定位數(shù)據(jù)
*/
__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);//置零標(biāo)志
//開始讀取
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 整片擦除(變?yōu)镕F) ※此操作無法復(fù)原,使用請謹(jǐn)慎
* @cmd: 60h(或C7h)
* @note 將整片flash數(shù)據(jù)擦除
*/
__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數(shù)據(jù)寫為FF) ※此操作無法復(fù)原,使用請謹(jǐn)慎
* @cmd: 81h
* @param: uint32_t addr //24位頁地址(數(shù)據(jù)最高8位忽略)。前16位規(guī)定頁地址,最后8位無意義(dummy)。
* addr可填位于 目標(biāo)頁 的任一地址
*
* @note 擦除指定Page上的內(nèi)容;寫入前必須先進(jìn)行擦除
*/
__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) ※此操作無法復(fù)原,使用請謹(jǐn)慎
* @cmd: 20h
* @param: uint32_t addr //24位扇區(qū)地址(數(shù)據(jù)最高8位忽略)。扇區(qū)由addr A23-A12確定
* addr可填位于 目標(biāo)扇區(qū) 的任一地址
* @note 擦除指定Page上的內(nèi)容;寫入前必須先進(jìn)行擦除
*/
__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 等待退出寫B(tài)USY狀態(tài)
* @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));//忙時循環(huán),不忙退出
LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);
}
/**************************************************************************************************/
//有問題
/**
* @brief 寫入數(shù)據(jù),伴有覆蓋擦除功能
* @cmd: 02h
* @param: uint32_t addr //24位地址(數(shù)據(jù)最高8位忽略),可填片上任意地址;寫入
* 將從該地址開始遞增, ※但寫入數(shù)據(jù)長度不能溢出地址所在頁(1頁256Bytes).
*
* @param uint8_t length //數(shù)據(jù)長度,必須大于0
* @param uint8_t *data //寫入數(shù)據(jù)所在地址指針
*
* @note 本函數(shù)工作原理如下:
* 1.通過宏定義判斷是否有頁擦除功能
* 2.將需要寫入地址所在的扇區(qū)/頁數(shù)據(jù)暫存至temp中
* 3.進(jìn)行頁/扇區(qū)擦除操作
* 4.將temp對應(yīng)位置數(shù)據(jù)用data中待寫入數(shù)據(jù)替換
* 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
//根據(jù)邏輯分析儀調(diào)整ReadData函數(shù)最后延遲時間,當(dāng)執(zhí)行下一個06h指令時MISO應(yīng)當(dāng)不輸出(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++)
{ //發(fā)送沒問題
LL_SPI_TransmitData8(Flash_SPI,temp[j]);
while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;
}
LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);
Flash_WaitWriteToFinish();
#else //無頁擦除功能,扇區(qū)(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];
}//替換完成
//將數(shù)據(jù)原位寫入
Flash_WriteEnable();
uint32_t iaddpage= addr & SectorMsk; //扇區(qū)的起始地址
//每次寫一頁,共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_
-
存儲器
+關(guān)注
關(guān)注
38文章
7440瀏覽量
163529 -
SPI
+關(guān)注
關(guān)注
17文章
1688瀏覽量
91234 -
邏輯分析儀
+關(guān)注
關(guān)注
3文章
214瀏覽量
23127 -
FSMC模塊
+關(guān)注
關(guān)注
0文章
9瀏覽量
1916 -
模擬驅(qū)動電路
+關(guān)注
關(guān)注
0文章
2瀏覽量
738
發(fā)布評論請先 登錄
相關(guān)推薦
評論