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从设备片选信号,由主设备控制。
STM32学习心得三十:SPI接口原理、配置及实验_第1张图片
若主机传1个字节给从机,那么从机也必须传1个字节给主机。
1.3 SPI接口框图
STM32学习心得三十:SPI接口原理、配置及实验_第2张图片
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)脚管理
STM32学习心得三十:SPI接口原理、配置及实验_第3张图片
2.2.2 时钟信号的相位和极性
STM32学习心得三十:SPI接口原理、配置及实验_第4张图片
STM32学习心得三十:SPI接口原理、配置及实验_第5张图片
STM32学习心得三十:SPI接口原理、配置及实验_第6张图片
CPHA和CPOL的目的是为了配合从机外设(其极性一般有严格要求)。
2.2.3 数据帧格式
在这里插入图片描述
2.2.4 状态标志
STM32学习心得三十:SPI接口原理、配置及实验_第7张图片
2.2.5 SPI中断
STM32学习心得三十:SPI接口原理、配置及实验_第8张图片
2.3 SPI引脚配置和模式
2.3.1 SPI1引脚配置
STM32学习心得三十:SPI接口原理、配置及实验_第9张图片
2.3.2 SPI2引脚配置
STM32学习心得三十:SPI接口原理、配置及实验_第10张图片
2.3.3 SPI3引脚配置
STM32学习心得三十:SPI接口原理、配置及实验_第11张图片
2.3.4 引脚模式
STM32学习心得三十:SPI接口原理、配置及实验_第12张图片
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. 硬件连接
STM32学习心得三十:SPI接口原理、配置及实验_第13张图片
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:
STM32学习心得三十:SPI接口原理、配置及实验_第14张图片
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. 实验结果
STM32学习心得三十:SPI接口原理、配置及实验_第15张图片
旧知识点
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学习心得十四:串口通信相关知识及配置方法。

你可能感兴趣的:(数字信号处理,STM32学习心得,综合学习)