STM32學習心得三十:SPI接口原理、配置及實驗

記錄一下,方便以後翻閱~
主要內容:
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學習心得十四:串口通信相關知識及配置方法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章