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文章
7514瀏覽量
164008 -
SPI
+關注
關注
17文章
1711瀏覽量
91774 -
邏輯分析儀
+關注
關注
3文章
214瀏覽量
23203 -
FSMC模塊
+關注
關注
0文章
9瀏覽量
1940 -
模擬驅動電路
+關注
關注
0文章
2瀏覽量
758
發布評論請先 登錄
相關推薦
基于GPIO模擬的SPI接口驅動設計與實現
SPI總線是我們常用的串行設備接口,一般情況下我們都會適應硬件SPI接口,但有些時候當硬件端口不足時,我們也希望可以使用軟件
發表于 12-07 16:21
?6444次閱讀
關于在軟件上開發過硬件的模擬測試軟件
不知道各位有沒有關于在軟件上開發過硬件的模擬測試軟件,或者是知道有這么一個軟件的,介紹給我,謝謝!或者有關于這個想法的可以與我討論,謝謝!這
發表于 10-08 15:23
硬件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
?95次下載
單片機spi接口的使用方法有哪些(spi接口和串口的區別)
如果單片機沒有硬件SPI模塊,或者需要額外的IO引腳來實現多個SPI設備的通信,可以使用軟件
評論