STM32F4-SPI

2020-08-10 11:10:34

SPI 簡介
SPI 是英語 Serial Peripheral interface 的縮寫,顧名思義就是序列外圍裝置介面。是 Motorola首先在其 MC68HCXX 系列處理器上定義的。SPI 介面主要應用在 EEPROM,FLASH,實時時鐘,AD 轉換器,還有數位信號處理器和數位信號解碼器之間。SPI,是一種高速的,全雙工,同步的通訊匯流排,並且在晶片的管腳上只佔用四根線,節約了晶片的管腳,同時爲 PCB 的佈局上節省空間,提供方便,正是出於這種簡單易用的特性,現在越來越多的晶片整合了這種通訊協定,STM32F4 也有 SPI 介面。下面 下麪我們看看 SPI 的內部簡明圖(圖 30.1.1).
在这里插入图片描述
SPI 介面一般使用 4 條線通訊:
MISO 主裝置數據輸入,從裝置數據輸出。
MOSI 主裝置數據輸出,從裝置數據輸入。
SCLK 時鐘信號,由主裝置產生。
CS 從裝置片選信號,由主裝置控制。

從圖中可以看出,主機和從機都有一個序列移位暫存器,主機通過向它的 SPI 序列暫存器寫入一個位元組來發起一次傳輸。暫存器通過 MOSI 信號線將位元組傳送給從機,從機也將自己的移位暫存器中的內容通過 MISO 信號線返回給主機。這樣,兩個移位暫存器中的內容就被交換。
外設的寫操作和讀操作是同步完成的。如果只進行寫操作,主機只需忽略接收到的位元組;反之,若主機要讀取從機的一個位元組,就必須發送一個空位元組來引發從機的傳輸。
SPI 主要特點有:可以同時發出和接收序列數據;可以當作主機或從機工作;提供頻率可程式化時鐘;發送結束中斷標誌;寫衝突保護;匯流排競爭保護等。
SPI 匯流排四種工作方式 SPI 模組爲了和外設進行數據交換,根據外設工作要求,其輸出序列同步時鐘極性和相位可以進行設定,時鐘極性(CPOL)對傳輸協定沒有重大的影響。如果CPOL=0,序列同步時鐘的空閒狀態爲低電平;如果 CPOL=1,序列同步時鐘的空閒狀態爲高電平。時鐘相位(CPHA)能夠設定用於選擇兩種不同的傳輸協定之一進行數據傳輸。如果 CPHA=0,在序列同步時鐘的第一個跳變沿(上升或下降)數據被採樣;如果 CPHA=1,在序列同步時鐘的第二個跳變沿(上升或下降)數據被採樣。SPI 主模組和與之通訊的外裝置時鐘相位和極性應該一致。
不同時鐘相位下的匯流排數據傳輸時序如圖 32.1.1 所示:
在这里插入图片描述
STM32F429 的 SPI 功能很強大,SPI 時鐘最高可以到 45Mhz,支援 DMA,可以設定爲 SPI協定或者 I2S 協定(支援全雙工 I2S)。
本章,我們將使用 STM32F429 的 SPI 來讀取外部 SPI FLASH 晶片(W25Q256),實現類似上節的功能。這裏對 SPI 我們只簡單介紹一下 SPI 的使用,STM32F429 的 SPI 詳細介紹請參考《STM32F4xx 中文參考手冊》第 721 頁,27 節。然後我們再介紹下 SPI FLASH 晶片。
這節,我們使用 STM32F429 的 SPI5 的主模式,下面 下麪就來看看 SPI5 部分的設定步驟吧。SPI 相關的庫函數和定義分佈在檔案 stm32f4xx_hal_spi.c 以及標頭檔案 stm32f4xx_hal_spi.h 中。STM32 的主模式設定步驟如下:
1)設定相關引腳的複用功能,使能 SPI5 時鐘。
我們要用 SPI5,第一步就要使能 SPI5 時鐘和響應引腳時鐘。其次要設定 SPI5 的相關引腳
爲複用(AF5)輸出,這樣纔會連線到 SPI5 上。這裏我們使用的是 PF7、8、9 這 3 個(SCK.、
MISO、MOSI,CS 使用軟體管理方式)
,所以設定這三個爲複用 IO,複用功能爲 AF5。
使能 SPI5 時鐘的方法爲:

__HAL_RCC_SPI5_CLK_ENABLE();

//使能 SPI5 時鐘
複用 PF7,PF8,PF9 爲 SPI5 引腳是通過 HAL_GPIO_Init 函數實現,程式碼如下:

GPIO_InitTypeDef GPIO_Initure;
GPIO_Initure.Pin=GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9;
GPIO_Initure.Mode=GPIO_MODE_AF_PP;//複用推輓輸出
GPIO_Initure.Pull=GPIO_PULLUP;//上拉
GPIO_Initure.Speed=GPIO_SPEED_FAST;//快速
GPIO_Initure.Alternate=GPIO_AF5_SPI5;//複用爲 SPI5
HAL_GPIO_Init(GPIOF,&GPIO_Initure);

2)初始化 SPI5,設定 SPI5 工作模式等。
這一步全部是通過 SPI5_CR1 來設定,我們設定 SPI5 爲主機模式,設定數據格式爲 8 位,然後通過 CPOL 和 CPHA 位來設定 SCK 時鐘極性及採樣方式。並設定 SPI5 的時鐘頻率(最大45Mhz),以及數據的格式(MSB 在前還是 LSB 在前)。在 HAL 庫中初始化 SPI 的函數爲:

HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef *hspi);

下面 下麪我們來看看 SPI_HandleTypeDef 定義:

typedef struct __SPI_HandleTypeDef
{
SPI_TypeDef     *Instance;//基地址
SPI_InitTypeDef Init;//初始化接哦固體
uint8_t         *pTxBuffPtr;//發送快取
uint16_t        TxXferSize;//發送數據大小
uint16_t        TxXferCount;//還剩餘多少個數據要發送
uint8_t         *pRxBuffPtr;//接收快取
uint16_t        RxXferSize;//接收數據大小
uint16_t        RxXferCount;//還剩餘多少個數據要接收
DMA_HandleTypeDef *hdmatx;//DMA 發送控制代碼
DMA_HandleTypeDef *hdmarx;//DMA 接收控制代碼
void			(*RxISR)(struct __SPI_HandleTypeDef * hspi);
void			(*TxISR)(struct __SPI_HandleTypeDef * hspi);
HAL_LockTypeDef   Lock;
__IO HAL_SPI_StateTypeDef   State;
__IO uint32_t               ErrorCode;
}SPI_HandleTypeDef;

該結構體和串列埠控制代碼結構體類似,同樣有 6 個成員變數和 2 個 DMA_HandleTypeDef 指針型別變數。這幾個參數的作用這裏我們就不做過多講解,大家如果對 HAL 庫串列埠通訊理解了,那麼這些就很好理解。這裏我們主要講解第二個成員變數 Init,它是 SPI_InitTypeDef 結構體型別,該結構體定義如下:

typedef struct
{
	uint32_t Mode;// 模式:主(SPI_MODE_MASTER),從(SPI_MODE_SLAVE)
	uint32_t Direction; //方式: 只接受模式,單線雙向通訊數據模式,全雙工
	uint32_t DataSize;//8 位還是 16 位幀格式選擇項
	uint32_t CLKPolarity; //時鐘極性
	uint32_t CLKPhase; //時鐘相位
	uint32_t NSS;//SS 信號由硬體(NSS 管腳)還是軟體控制
	uint32_t BaudRatePrescaler; //設定 SPI 波特率預分頻值
	uint32_t FirstBit;//起始位是 MSB 還是 LSB
	uint32_t TIMode;//幀格式 SPI motorola 模式還是 TI 模式
	uint32_t CRCCalculation; //硬體 CRC 是否使能
	uint32_t CRCPolynomial; //CRC 多項式
}SPI_InitTypeDef;

該結構體個個成員變數的含義我們已經在成員變數後面註釋了,請大家參考學習。SPI 初
始化範例程式碼如下:

SPI5_Handler.Instance=SPI5;//SP5
SPI5_Handler.Init.Mode=SPI_MODE_MASTER;//模式:主模式
SPI5_Handler.Init.Direction=SPI_DIRECTION_2LINES; //雙線模式
SPI5_Handler.Init.DataSize=SPI_DATASIZE_8BIT;//發送接收 8 位幀結構
SPI5_Handler.Init.CLKPolarity=SPI_POLARITY_HIGH; //序列同步時鐘空閒狀態爲高電平
SPI5_Handler.Init.CLKPhase=SPI_PHASE_2EDGE;//第二個跳變沿數據被採樣
SPI5_Handler.Init.NSS=SPI_NSS_SOFT;//NSS 信號由硬體管理
SPI5_Handler.Init.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_256;
//定義波特率預分頻的值:波特率預分頻值爲 256
SPI5_Handler.Init.FirstBit=SPI_FIRSTBIT_MSB; //指定數據傳輸從 MSB 位開始
SPI5_Handler.Init.TIMode=SPI_TIMODE_DISABLE; //關閉 TI 模式
SPI5_Handler.Init.CRCCalculation=SPI_CRCCALCULATION_DISABLE;/關閉硬體 CRC
SPI5_Handler.Init.CRCPolynomial=7; //CRC 值計算的多項式
HAL_SPI_Init(&SPI5_Handler);//初始化

同樣,HAL 庫也提供了 SPI 初始化 MSP 回撥函數 HAL_SPI_MspInit,定義如下:

void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi);

關於回撥函數使用,這裏我們就不做過多講解。
3)使能 SPI1。
這一步通過 SPI5_CR1 的 bit6 來設定,以啓動 SPI5,在啓動之後,我們就可以開始 SPI 通
訊了。使能 SPI5 的方法爲:

__HAL_SPI_ENABLE(&SPI5_Handler);//使能 SPI5

4)SPI 傳輸數據
通訊介面當然需要有發送數據和接受數據的函數,HAL 庫提供的發送數據函數原型爲:

HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData,uint16_t Size,uint32_t Timeout);

這個函數很好理解,往 SPIx 數據暫存器寫入數據 Data,從而實現發送。
HAL 庫提供的接受數據函數原型爲:

HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData,
uint16_t Size, uint32_t Timeout);

這個函數也不難理解,從 SPIx 數據暫存器讀出接受到的數據。
前面我們講解了 SPI 通訊的原理,因爲 SPI 是全雙工,發送一個位元組的同時接受一個位元組,
發送和接收同時完成,所以 HAL 也提供了一個發送接收統一函數:

HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData,
uint8_t *pRxData, uint16_t Size, uint32_t Timeout);

該函數發送一個位元組的同時負責接收一個位元組。
5)設定 SPI 傳輸速度
SPI 初始化結構體 SPI_InitTypeDef 有一個成員變數是 BaudRatePrescaler,該成員變數用來設定 SPI 的預分頻係數,從而決定了 SPI 的傳輸速度。但是 HAL 庫並沒有提供單獨的 SPI 分頻係數修改函數,如果我們需要在程式中不時的修改速度,那麼我們就要通過設定 SPI 的 CR1 暫存器來修改,具體實現方法請參考後面軟體設計小節相關函數。
SPI5 的使用就介紹到這裏,接下來介紹一下 W25Q128。W25Q128 是華邦公司推出的大容量 SPI FLASH 產品, W25Q128 的容量爲 128Mb,該系列還有 W25Q80/16/32/64 等。 ALIENTEK所選擇的 W25Q128 容量爲 128Mb,也就是 16M 位元組。
W25Q128 將 16M 的容量分爲 256 個塊(Block),每個塊大小爲 64K 位元組,每個塊又分爲16 個磁區(Sector)
,每個磁區 4K 個位元組。W25Q128 的最小擦除單位爲一個磁區,也就是每次必須擦除 4K 個位元組。這樣我們需要給 W25Q128 開闢一個至少 4K 的快取區,這樣對 SRAM 要求比較高,要求晶片必須有 4K 以上 SRAM 才能 纔能很好的操作。
W25Q128 的擦寫週期多達 10W 次,具有 20 年的數據儲存期限,支援電壓爲 2.7~3.6V,W25Q128 支援標準的 SPI,還支援雙輸出/四輸出的 SPI,最大 SPI 時鐘可以到 80Mhz(雙輸出時相當於 160Mhz,四輸出時相當於 320M),更多的 W25Q128 的介紹,請參考 W25Q128 的DATASHEET。
硬體設計
本章實驗功能簡介:開機的時候先檢測 W25Q256 是否存在,然後在主回圈裏面檢測兩個
按鍵,其中 1 個按鍵(KEY1)用來執行寫入 W25Q256 的操作,另外一個按鍵(KEY0)用來
執行讀出操作,在 LCD 模組上顯示相關資訊。同時用 DS0 提示程式正在執行。
所要用到的硬體資源如下:

  1. 指示燈 DS0
  2. KEY0 和 KEY1 按鍵
  3. LCD 模組
  4. SPI
  5. W25Q256
    這裏只介紹 W25Q256 與 STM32F429 的連線,板上的 W25Q256 是直接連在 STM32F429
    的 SPI5 上的,連線關係如圖 32.2.1 所示:
    在这里插入图片描述
    軟體設計
    開啓我們光碟的 SPI 實驗工程,可以看到我們加入了 spi.c,flash.c 檔案以及標頭檔案 spi.h 和
    flash.h,同時引入了庫函數檔案 stm32f4xx_hal_spi.c 檔案以及標頭檔案 stm32f4xx_hal_spi.h。
    開啓 spi.c 檔案,看到如下程式碼:
SPI_HandleTypeDef SPI5_Handler; //SPI 控制代碼
//以下是 SPI 模組的初始化程式碼,設定成主機模式
//SPI 口初始化
//這裏針是對 SPI5 的初始化
void SPI5_Init(void)
{
	SPI5_Handler.Instance=SPI5;
	//SP5
	SPI5_Handler.Init.Mode=SPI_MODE_MASTER; //設定 SPI 工作模式,設定爲主模式
	SPI5_Handler.Init.Direction=SPI_DIRECTION_2LINES; // SPI 設定爲雙線模式
	SPI5_Handler.Init.DataSize=SPI_DATASIZE_8BIT; // PI 發送接收 8 位幀結構
	SPI5_Handler.Init.CLKPolarity=SPI_POLARITY_HIGH; //同步時鐘空閒狀態爲高電平
	SPI5_Handler.Init.CLKPhase=SPI_PHASE_2EDGE;//同步時鐘第 2 個跳變沿數據被採樣
	SPI5_Handler.Init.NSS=SPI_NSS_SOFT; //NSS 信號由硬體(NSS 管腳)控制
	SPI5_Handler.Init.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_256;//定義波特率預分頻的值:波特率預分頻值爲 256
	SPI5_Handler.Init.FirstBit=SPI_FIRSTBIT_MSB;//指定數據傳輸從 MSB 位開始
	SPI5_Handler.Init.TIMode=SPI_TIMODE_DISABLE;//關閉 TI 模式
	SPI5_Handler.Init.CRCCalculation=SPI_CRCCALCULATION_DISABLE; //關閉 CRC
	SPI5_Handler.Init.CRCPolynomial=7; //CRC 值計算的多項式
	HAL_SPI_Init(&SPI5_Handler);//初始化
	__HAL_SPI_ENABLE(&SPI5_Handler); //使能 SPI5
	SPI5_ReadWriteByte(0Xff); //啓動傳輸
	}
	//SPI5 底層驅動,時鐘使能,引腳設定
	//此函數會被 HAL_SPI_Init()呼叫
	//hspi:SPI 控制代碼
	void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
	{
	GPIO_InitTypeDef GPIO_Initure;
	__HAL_RCC_GPIOF_CLK_ENABLE();
	__HAL_RCC_SPI5_CLK_ENABLE();
	519
	//使能 GPIOF 時鐘
	//使能 SPI5 時鐘
	GPIO_Initure.Pin=GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9; //PF7,8,9
	GPIO_Initure.Mode=GPIO_MODE_AF_PP;
	//複用推輓輸出
	GPIO_Initure.Pull=GPIO_PULLUP;
	//上拉
	GPIO_Initure.Speed=GPIO_SPEED_FAST;
	//快速
	GPIO_Initure.Alternate=GPIO_AF5_SPI5;
	//複用爲 SPI5
	HAL_GPIO_Init(GPIOF,&GPIO_Initure);
	}
	//SPI 速度設定函數
	//SPI 速度=fAPB1/分頻係數
	//@ref SPI_BaudRate_Prescaler:SPI_BAUDRATEPRESCALER_2~
	//
	SPI_BAUDRATEPRESCALER_2 256
	//fAPB1 時鐘一般爲 45Mhz:
	void SPI5_SetSpeed(u8 SPI_BaudRatePrescaler)
	{
	assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//判斷有效性
	__HAL_SPI_DISABLE(&SPI5_Handler); //關閉 SPI
	SPI5_Handler.Instance->CR1&=0XFFC7; //位 3-5 清零,用來設定波特率
	SPI5_Handler.Instance->CR1|=SPI_BaudRatePrescaler;//設定 SPI 速度
	__HAL_SPI_ENABLE(&SPI5_Handler); //使能 SPI
	}
	//SPI5 讀寫一個位元組
	//TxData:要寫入的位元組
	返回值:讀取到的位元組
	u8 SPI5_ReadWriteByte(u8 TxData)
	{
	u8 Rxdata;
	HAL_SPI_TransmitReceive(&SPI5_Handler,&TxData,&Rxdata,1, 1000);
	return Rxdata; //返回收到的數據
}

此部分程式碼主要初始化 SPI,這裏我們選擇的是 SPI5,所以在 SPI5_Init 函數裏面,其相關的操作都是針對 SPI5 的,其初始化主要是通過函數 HAL_SPI_Init 來實現的,初始化之後同時開啓 SPI5。在初始化之後,我們就可以開始使用 SPI5 了,這裏特別注意,SPI 初始化函數的最後有一個啓動傳輸,這句話最大的作用就是維持 MOSI 爲高電平,而且這句話也不是必須的,可以去掉。
在 SPI5_Init 函數裏面,我們把 SPI5 的頻率設定成了最低(90Mhz,256 分頻),而在外部我們可以隨時通過函數 SPI5_SetSpeed 來設定 SPI5 的速度。函數 SPI5_ReadWriteByte 則主要通過呼叫 HAL 庫函數 HAL_SPI_TransmitReceive 來實現數據的發送和接收。
接下來我們來看看 w25qxx.c 檔案內容。由於篇幅所限,詳細程式碼,這裏就不貼出了。我們僅介紹幾個重要的函數,首先是 W25QXX_Read 函數,該函數用於從 W25Q128 的指定地址讀出指定長度的數據。其程式碼如下:

//讀取 SPI FLASH
//在指定地址開始讀取指定長度的數據
//pBuffer:數據儲存區
//ReadAddr:開始讀取的地址(24bit)
//NumByteToRead:要讀取的位元組數(最大 65535)
void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)
{
	u16 i;
	W25QXX_CS=0;
	//使能器件
	SPI5_ReadWriteByte(W25X_ReadData); //發送讀取命令
	if(W25QXX_TYPE==W25Q256) //如果是 W25Q256 地址爲 4 位元組的,要發送最高 8 位
	{
	SPI5_ReadWriteByte((u8)((ReadAddr)>>24));
	}
	SPI5_ReadWriteByte((u8)((ReadAddr)>>16)); //發送 24bit 地址
	SPI5_ReadWriteByte((u8)((ReadAddr)>>8));
	SPI5_ReadWriteByte((u8)ReadAddr);
	for(i=0;i<NumByteToRead;i++)
	{
	pBuffer[i]=SPI5_ReadWriteByte(0XFF);
	//回圈讀數
	}
	W25QXX_CS=1;
}

由於 W25Q256 支援以任意地址(但是不能超過 W25Q256 的地址範圍)開始讀取數據,所以,這個程式碼相對來說就比較簡單了,在發送 32 位地址(25Q256 及以上型號有 32 位地址,其他型號只有 24 位地址)之後,程式就可以開始回圈讀數據了,其地址會自動增加的,不過要注意,不能讀的數據超過了 W25Q256 的地址範圍哦!否則讀出來的數據,就不是你想要的數據了。
有讀的函數,當然就有寫的函數了,接下來,我們介紹 W25QXX_Write 這個函數,該函數的作用與W25QXX_Flash_Read 的作用類似,不過是用來寫數據到 W25Q256 裏面的,程式碼如下:

//寫 SPI FLASH
//在指定地址開始寫入指定長度的數據
//該函數帶擦除操作!
//pBuffer:數據儲存區
//WriteAddr:開始寫入的地址(24bit)
//NumByteToWrite:要寫入的位元組數(最大 65535)
u8 W25QXX_BUFFER[4096];
void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
	u32 secpos;
	u16 secoff,secremain,i;
	u8 * W25QXX_BUF;
	W25QXX_BUF=W25QXX_BUFFER;
	secpos=WriteAddr/4096;//磁區地址
	secoff=WriteAddr%4096;//在磁區內的偏移
	secremain=4096-secoff;//磁區剩餘空間大小
	if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大於 4096 個位元組
	while(1)
	{
		W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//讀出整個磁區的內容
		for(i=0;i<secremain;i++)//校驗數據
		{
		if(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除
		}
		if(i<secremain)//需要擦除
		{
		W25QXX_Erase_Sector(secpos);//擦除這個磁區
		for(i=0;i<secremain;i++)
		//複製
		{
		W25QXX_BUF[i+secoff]=pBuffer[i];
		}
		W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//寫入整個磁區
		}else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);
		//寫已經擦除了的,直接寫入磁區剩餘區間.
		if(NumByteToWrite==secremain)break;//寫入結束了
		else//寫入未結束
		{
		secpos++;//磁區地址增 1
		secoff=0;//偏移位置爲 0
		pBuffer+=secremain; //指針偏移
		WriteAddr+=secremain;//寫地址偏移
		NumByteToWrite-=secremain;
		//位元組數遞減
		if(NumByteToWrite>4096)secremain=4096; //下一個磁區還是寫不完
		else secremain=NumByteToWrite;
		//下一個磁區可以寫完了
		}
	};
}

該函數可以在 W25Q256 的任意地址開始寫入任意長度(必須不超過 W25Q256 的容量)的數據。我們這裏簡單介紹一下思路:先獲得首地址(WriteAddr)所在的磁區,並計算在磁區內的偏移,然後判斷要寫入的數據長度是否超過本磁區所剩下的長度,如果不超過,再先看看是否要擦除,如果不要,則直接寫入數據即可,如果要則讀出整個磁區,在偏移處開始寫入指定長度的數據,然後擦除這個磁區,再一次性寫入。當所需要寫入的數據長度超過一個磁區的長度的時候,我們先按照前面的步驟把磁區剩餘部分寫完,再在新磁區內執行同樣的操作,如此回圈,直到寫入結束。這裏我們還定義了一個 W25QXX_BUFFER 的全域性變數,用於擦除時快取磁區內的數據。
其他的程式碼就比較簡單了,我們這裏不介紹了。對於標頭檔案 w25qxx.h,這裏面就定義了一些與 W25Q128 操作相關的命令和函數(部分省略了),這些命令在 W25Q128 的數據手冊上都有詳細的介紹,感興趣的讀者可以參考該數據手冊。
最後,我們看看 main 函數,程式碼如下:

//要寫入到 W25Q16 的字串陣列
const u8 TEXT_Buffer[]={"Apollo STM32F4 SPI TEST"};
#define SIZE sizeof(TEXT_Buffer)
int main(void)
{
	u8 key;
	u16 i=0;
	u8 datatemp[SIZE];
	u32 FLASH_SIZE;
	HAL_Init();
	//初始化 HAL 庫
	Stm32_Clock_Init(360,25,2,8); //設定時鐘,180Mhz
	...//此處省略部分程式碼
	W25QXX_Init();
	//W25QXX 初始化
	while(W25QXX_ReadID()!=W25Q256)
	//檢測不到 W25Q256
	{
		LCD_ShowString(30,150,200,16,16,"W25Q256 Check Failed!");
		delay_ms(500);
		LCD_ShowString(30,150,200,16,16,"Please Check!
	");
	delay_ms(500);
	LED0=!LED0;
	//DS0 閃爍
	}
	LCD_ShowString(30,150,200,16,16,"W25Q256 Ready!");
	FLASH_SIZE=32*1024*1024; //FLASH 大小爲 32M 位元組
	POINT_COLOR=BLUE;
	//設定字型爲藍色
	while(1)
	{
		key=KEY_Scan(0);
		if(key==KEY1_PRES)//KEY1 按下,寫入 W25Q128
		{
		LCD_Fill(0,170,239,319,WHITE);//清除半屏
		LCD_ShowString(30,170,200,16,16,"Start Write W25Q256....");
		W25QXX_Write((u8*)TEXT_Buffer,FLASH_SIZE-100,SIZE);
		//從倒數第 100 個地址處開始,寫入 SIZE 長度的數據
		LCD_ShowString(30,170,200,16,16,"W25Q256 Write Finished!");
		//提示傳送完成
		}
		if(key==KEY0_PRES)//KEY0 按下,讀取字串並顯示
		{
			LCD_ShowString(30,170,200,16,16,"Start Read W25Q256.... ");
			W25QXX_Read(datatemp,FLASH_SIZE-100,SIZE);
			//從倒數第 100 個地址處開始,讀出 SIZE 個位元組
			LCD_ShowString(30,170,200,16,16,"The Data Readed Is: "); //提示傳送完成
			LCD_ShowString(30,190,200,16,16,datatemp); //顯示讀到的字串
		}
		i++;
		delay_ms(10);
		if(i==20)
		{
			LED0=!LED0;//提示系統正在執行
			i=0;
		}
	}
}

在这里插入图片描述
伴隨 DS0 的不停閃爍,提示程式在執行。程式在開機的時候會檢測 W25Q256 是否存在,如果不存在則會在 LCD 模組上顯示錯誤資訊,同時 DS0 慢閃。大家可以通過跳線帽把 PF7 和PF8 短接就可以看到報錯了。