記錄一下,方便以後翻閱~
主要內容:
1) SPI接口原理;
2) 相關寄存器及庫函數解讀;
3) W25Qxx配置介紹;
4) 相關實驗代碼解讀。
實驗功能:系統啓動後,按鍵KEY1控制W25Q128的寫入,按鍵KEY0控制W25Q128的讀取。並在串口調試助手上面顯示相關信息,LED0閃爍提示程序正在運行。
官方資料:《STM32中文參考手冊V10》第23章——串行外設接口SPI和W25Q128芯片資料
1. SPI(SerialPeripheral Interface)接口原理
1.1 SPI簡介
SPI 是串行外圍設備接口,是Motorola首先在其MC68HCXX系列處理器上定義的。
SPI,是一種高速,全雙工,同步的通信總線,並且在芯片的管腳上只佔用四根線,節約了芯片的管腳,同時爲PCB的佈局上節省空間,提供方便,主要應用在 EEPROM,FLASH,實時時鐘,AD轉換器,還有數字信號處理器和數字信號解碼器之間。
1.2 SPI結構圖
SPI接口一般使用4條線通信:
MISO 主設備數據輸入,從設備數據輸出(該引腳做主機時爲輸入,做從機時爲輸出);
MOSI 主設備數據輸出,從設備數據輸入(該引腳做主機時爲輸出,做從機時爲輸入);
SCLK時鐘信號,由主設備產生;
CS從設備片選信號,由主設備控制。
若主機傳1個字節給從機,那麼從機也必須傳1個字節給主機。
1.3 SPI接口框圖
1.4 SPI工作原理總結
1.4.1 硬件上爲4根線;
1.4.2 主機和從機有一個串行移位寄存器,主機通過向它的SPI串行寄存器寫入一個字節來發起一次傳輸;
1.4.3 串行移位寄存器通過MOSI信號線將字節傳送給從機,從機也將自己的串行移位寄存器中的內容通過MISO信號線返回給主機。這樣,兩個移位寄存器中的內容就被交換;
1.4.4 外設的寫操作和讀操作是同步完成的。如果只進行寫操作,主機只需忽略接收到的字節;反之,若主機要讀取從機的一個字節,就必鬚髮送一個空字節來引發從機的傳輸。
2. SPI特點
2.1 特點一覽
2.1.1 3線全雙工同步傳輸;
2.1.2 8或16位傳輸幀格式選擇;
2.1.3 主或從操作;
2.1.4 支持多主模式;
2.1.5 8個主模式波特率預分頻係數(最大爲fPCLK/2);
2.1.6 從模式頻率(最大爲fPCLK/2);
2.1.7 主模式和從模式的快速通信;
2.1.8 主模式和從模式下均可以由軟件或硬件進行NSS管理:主/從操作模式的動態改變;
2.1.9 可編程的時鐘極性和相位;
2.1.10 可編程的數據順序,MSB在前或LSB在前;
2.1.11 可觸發中斷的專用發送和接收標誌;
2.1.12 SPI總線忙狀態標誌;
2.1.13 支持可靠通信的硬件CRC:1)在發送模式下,CRC值可以被作爲最後一個字節發送;2)在全雙工模式中對接收到的最後一個字節自動進行CRC校驗;
2.1.14 可觸發中斷的主模式故障、過載以及CRC錯誤標誌;
2.1.15 支持DMA功能的1字節發送和接收緩衝器:產生髮送和接受請求;
2.1.16 STM32 SPI接口可配置爲支持SPI協議或者支持I2S音頻協議,默認是SPI模式。可以通過軟件切換到I2S方式。
2.2 具體特點詳解
2.2.1 從選擇(NSS)腳管理
2.2.2 時鐘信號的相位和極性
CPHA和CPOL的目的是爲了配合從機外設(其極性一般有嚴格要求)。
2.2.3 數據幀格式
2.2.4 狀態標誌
2.2.5 SPI中斷
2.3 SPI引腳配置和模式
2.3.1 SPI1引腳配置
2.3.2 SPI2引腳配置
2.3.3 SPI3引腳配置
2.3.4 引腳模式
3. 常用寄存器及庫函數
3.1 相關寄存器
3.1.1 SPI控制寄存器1(SPI_CR1);
3.1.2 SPI控制寄存器2(SPI_CR2);
3.1.3 SPI狀態寄存器(SPI_SR);
3.1.4 SPI數據寄存器(SPI_DR);
3.1.5 SPI_I2S配置寄存器(SPI_I2S_CFGR);
3.1.6 SPI_I2S預分頻寄存器(SPI_I2SPR)。
3.2 相關庫函數
3.2.1 void SPI_I2S_DeInit(SPI_TypeDef* SPIx);
3.2.2 void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
詳解 void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct)的第二個入口參數
typedef struct
{
uint16_t SPI_Direction; /*Specifies the SPI unidirectional or bidirectional data mode.*/
uint16_t SPI_Mode; /* Specifies the SPI operating mode.*/
uint16_t SPI_DataSize; /* Specifies the SPI data size.*/
uint16_t SPI_CPOL; /* Specifies the serial clock steady state.*/
uint16_t SPI_CPHA; /*Specifies the clock active edge for the bit capture.*/
uint16_t SPI_NSS;
/* Specifies whether the NSS signal is managed by hardware (NSS pin) or by software using the SSI bit.*/
uint16_t SPI_BaudRatePrescaler;
/* Specifies the Baud Rate prescaler value which will be used to configure the transmit and receive SCK clock.*/
uint16_t SPI_FirstBit; /* Specifies whether data transfers start from MSB or LSB bit.*/
uint16_t SPI_CRCPolynomial; /* Specifies the polynomial used for the CRC calculation. */
}SPI_InitTypeDef;
3.2.3 void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
3.2.4 void SPI_I2S_ITConfig(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT, FunctionalState NewState);
3.2.5 void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState);
3.2.6 void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
3.2.7 uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);
3.2.8 void SPI_DataSizeConfig(SPI_TypeDef* SPIx, uint16_t SPI_DataSize);
3.2.9 FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
3.2.10 void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
3.2.11 ITStatus SPI_I2S_GetITStatus(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
3.2.12 void SPI_I2S_ClearITPendingBit(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
4. 程序配置過程
4.1 配置相關引腳的複用功能,使能SPIx時鐘:
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
4.2 初始化SPIx,設置SPIx工作模式:
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
4.3 使能SPIx:
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
4.4 SPI傳輸數據:
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx) ;
4.5 查看SPI傳輸狀態:
SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE);
5. 硬件連接
6. W25Q128JV芯片
6.1 概念
W25Q128JV(128M-bit)串行閃存爲有限空間、引腳和電源的系統提供了存儲解決方案。25Q系列提供的靈活性和性能遠遠超過普通的串行閃存設備。
W25Q128將16M的容量分爲256個塊(Block),每個塊大小爲64K字節,每個塊又分爲16個扇區(Sector),每個扇區4K個字節。W25Qxx的最小擦除單位爲一個扇區,也就是每次必須擦除4K個字節。這樣我們需要給W25Qxx開闢一個至少4K的緩存區,這樣對SRAM要求比較高,要求芯片必須有4K以上SRAM才能很好的操作。
JV表示速度爲133MHz:
6.2 W25Q128部分指令集及驅動代碼編寫(還可以參考)
6.2.1 Write Enable (06h) 寫入使能指令
舉例:
void W25QXX_Write_Enable(void)
{
W25QXX_CS=0; //W25QXX_CS對應PBout(12),即PB12置0//
SPI2_ReadWriteByte(W25X_WriteEnable); //寫入使能,W25X_WriteEnable爲0x06//
W25QXX_CS=1; //取消片選,PB12值1//
}
6.2.2 W25X_WriteDisable (0x04) 寫入禁止指令
舉例:
void W25QXX_Write_Disable(void)
{
W25QXX_CS=0; //使能器件//
SPI2_ReadWriteByte(W25X_WriteDisable); //發送寫入禁止指令,W25X_WriteDisable爲0x04//
W25QXX_CS=1; //取消片選//
}
6.2.3 W25X_ReadStatusReg (05h) 讀取狀態寄存器指令
6.2.4 W25X_WriteStatusReg (01h)寫入狀態寄存器
6.2.5 W25X_ReadData (03h)讀取數據指令
6.2.6 W25X_FastReadData (0Bh) 快速讀取數據指令
6.2.7 W25X_FastReadDual (3Bh) 快速雙端口輸出方式讀取存儲器數據指令
6.2.8 W25X_PageProgram (02h) 頁編程指令
6.2.9 W25X_BlockErase (D8h) 塊擦除指令
6.2.10 W25X_SectorErase (20h) 扇擦除指令
6.2.11 W25X_ChipErase (C7h) 芯片擦除指令
6.2.12 W25X_PowerDown (B9h) 掉電指令
6.2.13 W25X_ReleasePowerDown (ABh) 釋放掉電指令
6.2.14 W25X_DeviceID (ABh) 設備ID號指令
6.2.15 W25X_ManufactDeviceID (90h) 製造商設備ID號指令
6.2.16 W25X_JedecDeviceID (9Fh) JEDEC(Joint Electron Device Engineering Council)讀取電子元件工業聯合會設備號指令
6.3 W25Qxx_Write函數思路
6.3.1 每個sector是4K,也就是4096個地址,在寫任何一個地址之前,如果該地址的值不是0xFF,必須先擦除對應的sector,然後再寫。
6.3.2 主要步驟:
1) 據要寫的起始地址,確定要寫的起始區域的Sector號以及在起始Sector中的偏移量;
2) 根據要寫的起始地址和字節數,確定要寫的數據是否跨sector;
3) 定好要操作的sector以及sector的地址範圍;
4) 對每一個sector,先遍歷要寫的地址區域保存的數據是不是0xff,如果都是,就不用擦除。如果有不是0xff的區域,先讀出裏面的數據,保存在緩存W25QXX_BUFFER,然後擦除裏面的內容。然後把這個sector要操作的數據,寫到緩存。最後一次性吧緩存W25QXX_BUFFER的數據寫到這個對應的sector。
7. 部分代碼解讀
7.1 W25Q128.h頭文件代碼解讀
#ifndef __FLASH_H
#define __FLASH_H
#include "sys.h"
#define W25Q128 0XEF17 //本開發版採用的芯片是W25Q128,對應制造商設備ID號0xEF17//
extern u16 W25QXX_TYPE; //定義W25QXX芯片型號//
#define W25QXX_CS PBout(12) //W25QXX的片選信號//
//W25Q128 指令表//
#define W25X_WriteEnable 0x06 //寫入使能//
#define W25X_WriteDisable 0x04 //寫入禁止//
#define W25X_ReadStatusReg 0x05 //讀取狀態寄存器//
#define W25X_WriteStatusReg 0x01 //寫入狀態寄存器//
#define W25X_ReadData 0x03 //讀取數據//
#define W25X_FastReadData 0x0B //快速讀取數據//
#define W25X_FastReadDual 0x3B //快速雙端口輸出方式讀取存儲器數據//
#define W25X_PageProgram 0x02 //頁編程//
#define W25X_BlockErase 0xD8 //塊擦除//
#define W25X_SectorErase 0x20 //扇擦除//
#define W25X_ChipErase 0xC7 //芯片擦除//
#define W25X_PowerDown 0xB9 //掉電//
#define W25X_ReleasePowerDown 0xAB //釋放掉電//
#define W25X_DeviceID 0xAB //設備ID號//
#define W25X_ManufactDeviceID 0x90 //製造商設備ID號//
#define W25X_JedecDeviceID 0x9F //JEDEC(Joint Electron Device Engineering Council)電子元件工業聯合會//
//申明14個函數//
void W25QXX_Init(void); //W25Qxx初始化函數*//
u16 W25QXX_ReadID(void); //讀取FLASH ID函數*//
u8 W25QXX_ReadSR(void); //讀取狀態寄存器函數*//
void W25QXX_Write_SR(u8 sr); //寫入狀態寄存器函數*//
void W25QXX_Write_Enable(void); //寫入使能函數*//
void W25QXX_Write_Disable(void); //寫入失能函數*//
void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);//無檢查寫入flash函數*//
void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead); //讀取flash函數*//
void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite); //寫入flash函數//
void W25QXX_Erase_Chip(void); //整片芯片擦除函數*//
void W25QXX_Erase_Sector(u32 Dst_Addr); //扇區擦除函數*//
void W25QXX_Wait_Busy(void); //等待空閒函數*//
void W25QXX_PowerDown(void); //進入掉電模式函數*//
void W25QXX_WAKEUP(void); //喚醒函數*//
#endif
7.2 W25Q128.c文件代碼解讀
#include "w25qxx.h"
#include "spi.h"
#include "delay.h"
#include "usart.h"
u16 W25QXX_TYPE=W25Q128; //型號W25Q128,4K字節爲一個扇區,16個扇區爲1個塊,容量爲16M字節,共128個塊,4096個扇區//
//初始化SPI FLASH的IO口//
void W25QXX_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );//PORTB時鐘使能
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; // PB12,對應該閃存的CS引腳//
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推輓輸出//
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_12); //PB12置高電平//
W25QXX_CS=1; //SPI FLASH不選中,其實跟上一行代碼實現功能一樣//
SPI2_Init(); //初始化SPI//
SPI2_SetSpeed(SPI_BaudRatePrescaler_2); //重新設置波特率爲18M時鐘,高速模式//
W25QXX_TYPE=W25QXX_ReadID(); //讀取FLASH ID//
}
//編寫讀取W25QXX的狀態寄存器函數,這裏只讀取S0-S7位的值//
u8 W25QXX_ReadSR(void)
{
u8 byte=0;
W25QXX_CS=0; //使能器件//
SPI2_ReadWriteByte(W25X_ReadStatusReg); //發送讀取狀態寄存器指令,W25X_ReadStatusReg=0x05,可忽略接收值//
byte=SPI2_ReadWriteByte(0Xff); //讀取一個字節,發送0xff,讀取回來的值傳至byte//
W25QXX_CS=1; //取消片選
return byte;
}
//編寫寫入W25QXX狀態寄存器函數,這裏只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以寫???//
void W25QXX_Write_SR(u8 sr)
{
W25QXX_CS=0; //使能器件//
SPI2_ReadWriteByte(W25X_WriteStatusReg);//發送寫入狀態寄存器指令,W25X_WriteStatusReg=0x01,忽略接收值//
SPI2_ReadWriteByte(sr); //寫入一個字節,忽略接收值//
W25QXX_CS=1; //取消片選//
}
//W25QXX寫入使能,將WEL置1//
void W25QXX_Write_Enable(void)
{
W25QXX_CS=0; //W25QXX_CS對應PBout(12),即PB12置0//
SPI2_ReadWriteByte(W25X_WriteEnable); //寫入使能,W25X_WriteEnable的指令爲0x06//
W25QXX_CS=1; //取消片選,即PB12置1//
}
//W25QXX寫入禁止,將WEL清零,即置0//
void W25QXX_Write_Disable(void)
{
W25QXX_CS=0; //使能器件//
SPI2_ReadWriteByte(W25X_WriteDisable); //發送寫入禁止指令,W25X_WriteDisable爲0x04//
W25QXX_CS=1; //取消片選//
}
//讀取芯片ID,本開發版返回值0XEF17,表示芯片型號爲W25Q128//
u16 W25QXX_ReadID(void)
{
u16 Temp = 0;
W25QXX_CS=0;
SPI2_ReadWriteByte(0x90); //發送讀取ID命令,即0x90//
SPI2_ReadWriteByte(0x00); //後面跟一個24位0x000000地址//
SPI2_ReadWriteByte(0x00);
SPI2_ReadWriteByte(0x00);
Temp|=SPI2_ReadWriteByte(0xFF)<<8; //讀取Manufacturer ID號,即0xEF//
Temp|=SPI2_ReadWriteByte(0xFF); //讀取Device ID號,即0x17//
W25QXX_CS=1;
return Temp;
}
//pBuffer:數據存儲區,ReadAddr:開始讀取的地址(24bit),NumByteToRead:要讀取的字節數(最大65535)//
void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)
{
u16 i;
W25QXX_CS=0; //使能器件//
SPI2_ReadWriteByte(W25X_ReadData); //發送讀取指令,W25X_ReadData爲0x03//
SPI2_ReadWriteByte((u8)((ReadAddr)>>16)); //發送24bit地址,最低的8位//
SPI2_ReadWriteByte((u8)((ReadAddr)>>8)); //發送24bit地址,次低的8位//
SPI2_ReadWriteByte((u8)ReadAddr); //發送24bit地址,高8位//
for(i=0;i<NumByteToRead;i++)
{
pBuffer[i]=SPI2_ReadWriteByte(0XFF); //循環讀數,每次讀1個字節//
}
W25QXX_CS=1;
}
//SPI在一頁(0~65535個字節)內寫入少於256個字節的數據//
//pBuffer:數據存儲區,WriteAddr:開始寫入的地址(24bit),NumByteToWrite:要寫入的字節數(最大256),不應超過該頁的剩餘字節數//
void W25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
u16 i;
W25QXX_Write_Enable(); //SET WEL//
W25QXX_CS=0; //使能器件//
SPI2_ReadWriteByte(W25X_PageProgram); //發送寫頁命令,W25X_PageProgram爲0x02//
SPI2_ReadWriteByte((u8)((WriteAddr)>>16)); //發送24bit地址,最低的8位//
SPI2_ReadWriteByte((u8)((WriteAddr)>>8)); //發送24bit地址,次低的8位//
SPI2_ReadWriteByte((u8)WriteAddr); //發送24bit地址,高8位//
for(i=0;i<NumByteToWrite;i++)
SPI2_ReadWriteByte(pBuffer[i]); //循環寫數,每次寫1個字節//
W25QXX_CS=1; //取消片選//
W25QXX_Wait_Busy(); //等待寫入結束//
}
//無檢驗寫入SPI FLASH,確保所寫地址範圍內的數據全部爲0XFF,否則在非0XFF處寫入的數據將失敗//
//具有自動換頁功能,在指定地址開始寫入指定長度的數據,但是要確保地址不越界//
//pBuffer:數據存儲區,WriteAddr:開始寫入的地址(24bit),NumByteToWrite:要寫入的字節數(最大65535)//
void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
u16 pageremain;
pageremain=256-WriteAddr%256; //單頁剩餘的字節數//
if(NumByteToWrite<=pageremain) //判斷要寫入的字節數是否小於單頁剩餘的字節數//
pageremain=NumByteToWrite; //如果小於,就將NumByteToWrite值賦給pageremain//
while(1)
{
W25QXX_Write_Page(pBuffer,WriteAddr,pageremain); //一次寫入少於256個字節的數據//
if(NumByteToWrite==pageremain)break; //如果寫入字節數小於剩餘字節數,則寫完//
else
{
pBuffer+=pageremain;
WriteAddr+=pageremain;
NumByteToWrite-=pageremain; //減去已經寫入了的字節數//
if(NumByteToWrite>256)pageremain=256; //一次可以寫入256個字節//
else pageremain=NumByteToWrite; //不夠256個字節//
}
};
}
//寫SPI FLASH,在指定地址開始寫入指定長度的數據,該函數帶擦除操作//
//pBuffer:數據存儲區,WriteAddr:開始寫入的地址(24bit),NumByteToWrite:要寫入的字節數(最大65535)//
u8 W25QXX_BUFFER[4096]; //4096個字節爲一個扇區,扇區是最小的擦除單位//
void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
u32 secpos;
u16 secoff;
u16 secremain;
u16 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); //讀出整個扇區的內容,保存在W25QXX_BUF裏//
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_BUF緩存中//
}
W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096); //一次性吧緩存W25QXX_BUFFER的數據寫到對應的sector//
}
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; //下一個扇區可以寫完//
}
};
}
//擦除整個芯片,等待時間超長//
void W25QXX_Erase_Chip(void)
{
W25QXX_Write_Enable(); //SET WEL//
W25QXX_Wait_Busy();
W25QXX_CS=0; //使能器件//
SPI2_ReadWriteByte(W25X_ChipErase); //發送片擦除命令,W25X_ChipErase爲0xC7//
W25QXX_CS=1; //取消片選//
W25QXX_Wait_Busy(); //等待芯片擦除結束//
}
//擦除一個扇區,Dst_Addr:扇區地址 根據實際容量設置,擦除一個扇區的最少時間:150ms//
void W25QXX_Erase_Sector(u32 Dst_Addr)
{
Dst_Addr*=4096;
W25QXX_Write_Enable(); //SET WEL//
W25QXX_Wait_Busy();
W25QXX_CS=0; //使能器件//
SPI2_ReadWriteByte(W25X_SectorErase); //發送扇區擦除指令,W25X_SectorErase爲0x20//
SPI2_ReadWriteByte((u8)((Dst_Addr)>>16)); //發送24bit地址//
SPI2_ReadWriteByte((u8)((Dst_Addr)>>8));
SPI2_ReadWriteByte((u8)Dst_Addr);
W25QXX_CS=1; //取消片選//
W25QXX_Wait_Busy(); //等待擦除完成//
}
//等待空閒函數,只有當狀態寄存器的S0位(BUSY)置0時,循環結束//
void W25QXX_Wait_Busy(void)
{
while((W25QXX_ReadSR()&0x01)==0x01);
}
//進入掉電模式//
void W25QXX_PowerDown(void)
{
W25QXX_CS=0; //使能器件//
SPI2_ReadWriteByte(W25X_PowerDown); //發送掉電命令,W25X_PowerDown爲0xB9//
W25QXX_CS=1; //取消片選//
delay_us(3); //等待tDP//
}
//喚醒,釋放掉電模式//
void W25QXX_WAKEUP(void)
{
W25QXX_CS=0; //使能器件//
SPI2_ReadWriteByte(W25X_ReleasePowerDown); //發生釋放掉電指令,ReleasePowerDown爲0xAB//
W25QXX_CS=1; //取消片選//
delay_us(3); //等待TRES1//
}
7.3 spi.h頭文件代碼解讀
#ifndef __SPI_H
#define __SPI_H
#include "sys.h"
//申明三個函數//
void SPI2_Init(void); //初始化SPI2接口//
void SPI2_SetSpeed(u8 SpeedSet); //設置SPI2速度//
u8 SPI2_ReadWriteByte(u8 TxData); //SPI總線讀寫一個字節//
#endif
7.4 spi.c文件代碼解讀
#include "spi.h"
//SPI2初始化函數:配置成主機模式,訪問W25Q128//
void SPI2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //GPIO初始化結構體,選擇SPI2,對應PB12,PB13,PB14,PB15//
SPI_InitTypeDef SPI_InitStructure; //SPI初始化結構體//
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE ); //GPIOB時鐘使能//
RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE ); //SPI2時鐘使能//
//初始化GPIOB,PB13/14/15都設置複用推輓輸出,PB14對應MISO,最好設爲帶上拉輸入//
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15); //PB13/14/15置高電平//
//初始化SPI函數//
//設置SPI單向或者雙向的數據模式:SPI設置爲雙線雙向全雙工//
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
//針對SPI_CR1寄存器的SSI位和MSTR爲,均設置1,即SPI工作模式爲主SPI//
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
//針對SPI_CR1寄存器的DFF位,設置數據幀大小爲8位//
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
//針對SPI_CR1寄存器的CPOL位,串行同步時鐘的空閒狀態爲高電平//
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
//針對SPI_CR1寄存器的CPHA位,串行同步時鐘的第二個跳變沿(上升沿)數據被採樣//
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
//針對SPI_CR1寄存器的SSM位,NSS信號由軟件(使用SSI位)管理//
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
//針對SPI_CR1寄存器的BR位,波特率預分頻值爲256//
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
//針對SPI_CR1寄存器的LSBFIRST位,數據傳輸從MSB位開始//
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
//針對SPI_CRCPR寄存器的CRCPOLY位,設爲0x0007,爲復位值//
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI2, &SPI_InitStructure);
SPI_Cmd(SPI2, ENABLE); //使能SPI外設//
//啓動傳輸,目的是讓MOSI維持高。因爲一般空閒狀態電平都是高,這樣不容易出問題//
SPI2_ReadWriteByte(0xff);
}
//SPI2速度設置函數//
void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler)
{
assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler)); //有效性判斷//
SPI2->CR1&=0XFFC7; //先將SPI_CR1寄存器的BR位置000//
SPI2->CR1|=SPI_BaudRatePrescaler; //再設置SPI2速度//
SPI_Cmd(SPI2,ENABLE);
}
//u8 SPI2_ReadWriteByte(u8 TxData)讀寫一個字節函數,TxData:要寫入的字節,返回值:讀取到的字節//
u8 SPI2_ReadWriteByte(u8 TxData)
{
u8 retry=0;
//檢查SPI_SR寄存器的TXE位(發送緩衝爲空),其值0時爲非空,1時爲空//
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET)
{
retry++; //發送緩衝爲空時,retry++//
if(retry>200)return 0;
}
SPI_I2S_SendData(SPI2, TxData); //通過外設SPI2發送一個數據//
retry=0;
//檢查SPI_SR寄存器的RXNE位(接收緩衝爲空),其值0時爲空,1時爲非空//
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET)
{
retry++; //當接收緩衝爲非空時,retry++//
if(retry>200)return 0;
}
return SPI_I2S_ReceiveData(SPI2); //返回通過SPIx最近接收的數據//
}
7.4 main.c文件代碼解讀
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"
#include "w25qxx.h"
//寫入到W25Q128的字符串數組//
const u8 TEXT_Buffer[]={"這是我的SPI實驗"};
#define SIZE sizeof(TEXT_Buffer)
int main(void)
{
u8 key;
u16 i=0;
u8 datatemp[SIZE];
u32 FLASH_SIZE;
delay_init(); //延時函數初始化//
uart_init(115200); //串口初始化爲115200//
LED_Init(); //初始化與LED連接的硬件接口//
KEY_Init(); //按鍵初始化//
W25QXX_Init(); //W25QXX初始化//
while(W25QXX_ReadID()!=W25Q128) //while循環直到檢測到W25Q128閃存才退出//
{
printf("檢測不到W25Q128閃存\n");
delay_ms(500);
LED1=!LED1;
}
LED1=0;
FLASH_SIZE=256*16*4*1024*8; //FLASH大小爲16M字節,分256塊,每個塊分16個扇區,每個扇區4k字節//
while(1)
{
key=KEY_Scan(0);
if(key==KEY1_PRES) //KEY1按下,寫入W25Q128//
{
printf("\n開始寫入W25Q128閃存數據\r\n");
W25QXX_Write((u8*)TEXT_Buffer,FLASH_SIZE-1000,SIZE); //從倒數第1000個地址處開始,寫入SIZE長度的數據//
printf("\n寫入W25Q128閃存數據完成\r\n");
}
if(key==KEY0_PRES) //KEY0按下,讀取字符串並顯示//
{
printf("\n開始讀取W25Q128閃存數據\r\n");
W25QXX_Read(datatemp,FLASH_SIZE-1000,SIZE); //從倒數第1000個地址處開始,讀出SIZE個字節//
printf("\n讀取W25Q128閃存數據完成\r\n");
printf("\n從W25Q128閃存讀取數據的內容爲:%s\r\n",datatemp);
}
i++;
delay_ms(10);
if(i==20)
{
LED0=!LED0; //LED0每隔200ms閃爍,提示系統正在運行//
i=0;
}
}
}
8. 實驗結果
舊知識點
1)複習如何新建工程模板,可參考STM32學習心得二:新建工程模板;
2)複習基於庫函數的初始化函數的一般格式,可參考STM32學習心得三:GPIO實驗-基於庫函數;
3)複習寄存器地址,可參考STM32學習心得四:GPIO實驗-基於寄存器;
4)複習位操作,可參考STM32學習心得五:GPIO實驗-基於位操作;
5)複習寄存器地址名稱映射,可參考STM32學習心得六:相關C語言學習及寄存器地址名稱映射解讀;
6)複習時鐘系統框圖,可參考STM32學習心得七:STM32時鐘系統框圖解讀及相關函數;
7)複習延遲函數,可參考STM32學習心得九:Systick滴答定時器和延時函數解讀;
8)複習ST-LINK仿真器的參數配置,可參考STM32學習心得十:在Keil MDK軟件中配置ST-LINK仿真器;
9)複習ST-LINK調試方法,可參考STM32學習心得十一:ST-LINK調試原理+軟硬件仿真調試方法;
10)複習串口通信相關知識,可參考STM32學習心得十四:串口通信相關知識及配置方法。